Skip to content

Commit

Permalink
WIP: Ligature support. Selections ending mid-ligature is a problem
Browse files Browse the repository at this point in the history
  • Loading branch information
bobbylight committed Jun 28, 2024
1 parent b230bf1 commit 445d9d3
Show file tree
Hide file tree
Showing 9 changed files with 138 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import java.awt.geom.Rectangle2D;

import javax.swing.text.TabExpander;
import javax.swing.text.Utilities;


/**
Expand Down Expand Up @@ -43,6 +44,34 @@ public class DefaultTokenPainter implements TokenPainter {
}


@Override
public float nextX(Token token, int charCount, float x,
RSyntaxTextArea host, TabExpander e) {

int textOffs = token.getTextOffset();
char[] text = token.getTextArray();
int end = textOffs + charCount;
int flushLen = 0;
int flushIndex = textOffs;
FontMetrics fm = host.getFontMetricsForTokenType(token.getType());

for (int i=textOffs; i<end; i++) {
if (text[i] == '\t') {
x = e.nextTabStop(
x + fm.charsWidth(text, flushIndex, flushLen), 0);
flushLen = 0;
flushIndex = i + 1;
}
else {
flushLen++;
}
}

return x + fm.charsWidth(text, flushIndex, flushLen);

}


@Override
public final float paint(Token token, Graphics2D g, float x, float y,
RSyntaxTextArea host, TabExpander e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.font.TextAttribute;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.Map;

import javax.swing.text.StyleContext;

Expand Down Expand Up @@ -106,8 +108,8 @@ void changeBaseFont(Font old, Font font) {
if (style.font.getFamily().equals(old.getFamily()) &&
style.font.getSize() == old.getSize()) {
int s = style.font.getStyle(); // Keep bold or italic
StyleContext sc = StyleContext.getDefaultStyleContext();
style.font = sc.getFont(font.getFamily(), s, font.getSize());
style.font = getFont(font.getFamily(), s, font.getSize(),
font.getAttributes());
}
}
}
Expand Down Expand Up @@ -174,6 +176,29 @@ else if (!styles[i].equals(otherSchemes[i])) {
}


/**
* Returns the specified font.
*
* @param family The font family.
* @param style The style of font.
* @param size The size of the font.
* @param attrs Optional text attributes.
* @return The font.
*/
private static Font getFont(String family, int style, int size,
Map<TextAttribute, ?> attrs) {
// Use StyleContext to get a composite font for Asian glyphs.
// WORKAROUND for Sun JRE bug 6282887 (Asian font bug in 1.4/1.5)
// That bug seems to be hidden now, see 6289072 instead.
StyleContext sc = StyleContext.getDefaultStyleContext();
Font font = sc.getFont(family, style, size);
if (attrs != null) {
font = font.deriveFont(attrs);
}
return font;
}


/**
* Returns a hex string representing an RGB color, of the form
* <code>"$rrggbb"</code>.
Expand Down Expand Up @@ -328,10 +353,6 @@ public static SyntaxScheme loadFromString(String string,
tokenCount + ", found " + tokens.length);
}

// Use StyleContext to create fonts to get composite fonts for
// Asian glyphs.
StyleContext sc = StyleContext.getDefaultStyleContext();

// Loop through each token style. Format:
// "index,(fg|-),(bg|-),(t|f),((font,style,size)|(-,,))"
for (int i=0; i<tokenTypeCount; i++) {
Expand Down Expand Up @@ -365,9 +386,10 @@ public static SyntaxScheme loadFromString(String string,
Font font = null;
String family = tokens[pos+4];
if (!"-".equals(family)) {
font = sc.getFont(family,
font = getFont(family,
Integer.parseInt(tokens[pos+5]), // style
Integer.parseInt(tokens[pos+6])); // size
Integer.parseInt(tokens[pos+6]), // size
RSyntaxTextArea.getDefaultFont().getAttributes());
}
scheme.styles[i] = new Style(fg, bg, font, underline);

Expand Down Expand Up @@ -440,13 +462,10 @@ public void restoreDefaults(Font baseFont, boolean fontStyles) {
Font commentFont = baseFont;
Font keywordFont = baseFont;
if (fontStyles) {
// WORKAROUND for Sun JRE bug 6282887 (Asian font bug in 1.4/1.5)
// That bug seems to be hidden now, see 6289072 instead.
StyleContext sc = StyleContext.getDefaultStyleContext();
Font boldFont = sc.getFont(baseFont.getFamily(), Font.BOLD,
baseFont.getSize());
Font italicFont = sc.getFont(baseFont.getFamily(), Font.ITALIC,
baseFont.getSize());
Font boldFont = getFont(baseFont.getFamily(), Font.BOLD,
baseFont.getSize(), baseFont.getAttributes());
Font italicFont = getFont(baseFont.getFamily(), Font.ITALIC,
baseFont.getSize(), baseFont.getAttributes());
commentFont = italicFont;//baseFont.deriveFont(Font.ITALIC);
keywordFont = boldFont;//baseFont.deriveFont(Font.BOLD);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,50 +218,34 @@ private float drawLineWithSelection(TokenPainter painter, Token token,

while (token!=null && token.isPaintable() && nextX<clipEnd) {

// Selection starts in this token
if (token.containsPosition(selStart)) {

if (selStart>token.getOffset()) {
tempToken.copyFrom(token);
tempToken.textCount = selStart - tempToken.getOffset();
nextX = painter.paint(tempToken,g,nextX,y,host, this, clipStart);
tempToken.textCount = token.length();
tempToken.makeStartAt(selStart);
// Clone required since token and tempToken must be
// different tokens for else statement below
token = new TokenImpl(tempToken);
}
// Selection starts or ends in this token
if (token.containsPosition(selStart) || token.containsPosition(selEnd)) {

int tokenLen = token.length();
int selCount = Math.min(tokenLen, selEnd-token.getOffset());
if (selCount==tokenLen) {
nextX = painter.paintSelected(token, g, nextX,y, host,
this, clipStart, useSTC);
// Paint the entire token, unselected, so the unselected parts look good
if (selStart>token.getOffset() || selEnd < token.getEndOffset()) {
painter.paint(token, g, nextX, y, host, this, clipStart);
}
else {
tempToken.copyFrom(token);
tempToken.textCount = selCount;
nextX = painter.paintSelected(tempToken, g, nextX,y, host,
this, clipStart, useSTC);
tempToken.textCount = token.length();
tempToken.makeStartAt(token.getOffset() + selCount);
token = tempToken;
nextX = painter.paint(token, g, nextX,y, host, this,
clipStart);
// ligatures: ## <> => ++ ~~ <= >= <=> ->>
//ligatures: ## <> => ++ ~~ <= >= <=> ->>
// Figure out where the selection starts and ends
float selStartX = nextX;
if (selStart > token.getOffset()) {
int charCount = selStart - token.getOffset();
selStartX = painter.nextX(token, charCount, nextX, host, this);
}
int tokenSelectionEndCharOffs = Math.min(token.getEndOffset(), selEnd) - token.getOffset();
float selEndX = painter.nextX(token, tokenSelectionEndCharOffs, nextX, host, this);

}
// Create a clip region to only paint this token's selection
Rectangle origClip = g.getClipBounds();
g.setClip((int)selStartX, origClip.y, (int)(selEndX - selStartX), origClip.height);
System.out.println("Clip bounds: " + g.getClipBounds());

// Selection ends in this token
else if (token.containsPosition(selEnd)) {
tempToken.copyFrom(token);
tempToken.textCount = selEnd - tempToken.getOffset();
nextX = painter.paintSelected(tempToken, g, nextX,y, host, this,
clipStart, useSTC);
tempToken.textCount = token.length();
tempToken.makeStartAt(selEnd);
token = tempToken;
nextX = painter.paint(token, g, nextX,y, host, this, clipStart);
// Render the entire token, selected, and let the clip region do its magic
nextX = painter.paintSelected(token, g, nextX, y, host, this, clipStart, useSTC);

// Restore the clip
g.setClip(origClip);
}

// This token is entirely selected
Expand Down Expand Up @@ -727,7 +711,6 @@ public void paint(Graphics g, Shape a) {
drawLine(painter, token, g2d, x,y, line);
}
else {
//System.out.println("Drawing line with selection: " + line);
drawLineWithSelection(painter,token,g2d, x,y, selStart, selEnd);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@
import java.awt.Color;
import java.awt.Font;
import java.awt.SystemColor;
import java.awt.font.TextAttribute;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.util.Map;

import javax.swing.UIManager;
import javax.swing.plaf.ColorUIResource;
Expand Down Expand Up @@ -226,7 +228,7 @@ public void apply(RSyntaxTextArea textArea) {
baseFont.getFamily();
int fontSize = lineNumberFontSize>0 ? lineNumberFontSize :
baseFont.getSize();
Font font = getFont(fontName, Font.PLAIN, fontSize);
Font font = getFont(fontName, Font.PLAIN, fontSize, baseFont.getAttributes());
gutter.setLineNumberFont(font);
gutter.setFoldIndicatorForeground(foldIndicatorFG);
gutter.setFoldIndicatorArmedForeground(foldIndicatorArmedFG);
Expand Down Expand Up @@ -320,12 +322,20 @@ private static Color getDefaultSelectionFG() {
* @param family The font family.
* @param style The style of font.
* @param size The size of the font.
* @param attrs Optional text attributes.
* @return The font.
*/
private static Font getFont(String family, int style, int size) {
private static Font getFont(String family, int style, int size,
Map<TextAttribute, ?> attrs) {
// Use StyleContext to get a composite font for Asian glyphs.
// WORKAROUND for Sun JRE bug 6282887 (Asian font bug in 1.4/1.5)
// That bug seems to be hidden now, see 6289072 instead.
StyleContext sc = StyleContext.getDefaultStyleContext();
return sc.getFont(family, style, size);
Font font = sc.getFont(family, style, size);
if (attrs != null) {
font = font.deriveFont(attrs);
}
return font;
}


Expand Down Expand Up @@ -693,7 +703,7 @@ else if ("baseFont".equals(qName)) {
}
String family = attrs.getValue("family");
if (family!=null) {
theme.baseFont = getFont(family, Font.PLAIN, size);
theme.baseFont = getFont(family, Font.PLAIN, size, theme.baseFont.getAttributes());
}
else if (sizeStr!=null) {
// No family specified, keep original family
Expand Down Expand Up @@ -862,7 +872,7 @@ else if ("style".equals(qName)) {
String familyName = attrs.getValue("fontFamily");
if (familyName!=null) {
font = getFont(familyName, font.getStyle(),
font.getSize());
font.getSize(), font.getAttributes());
}
String sizeStr = attrs.getValue("fontSize");
if (sizeStr!=null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,21 @@
public interface TokenPainter {


/**
* Computes how much horizontal space a token, or part of a token, takes.
*
* @param token The token to examine.
* @param charCount The number of characters in the token to measure..
* Should be less than or equal to its length.
* @param x The x-offset at which painting would start.
* @param host The parent text area.
* @param e The tab expander.
* @return The length of the text.
*/
float nextX(Token token, int charCount, float x,
RSyntaxTextArea host, TabExpander e);


/**
* Paints this token.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.font.TextAttribute;
import java.text.AttributedCharacterIterator;
import java.util.HashMap;
import java.util.Map;

import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
Expand Down Expand Up @@ -462,15 +466,23 @@ public static Font getDefaultFont() {
}
}
else {
// Consolas added in Vista, used by VS2010+.
font = sc.getFont("Consolas", Font.PLAIN, 13);
if (!"Consolas".equals(font.getFamily())) {
font = sc.getFont(Font.MONOSPACED, Font.PLAIN, 13);
// Cascadia Code was added in later Windows 10/11, default in VS
// and VS Code. Consolas was added in Vista, used in older VS.
font = sc.getFont("Cascadia Code", Font.PLAIN, 13);
if (!"Cascadia Code".equals(font.getFamily())) {
font = sc.getFont("Consolas", Font.PLAIN, 13);
if (!"Consolas".equals(font.getFamily())) {
font = sc.getFont(Font.MONOSPACED, Font.PLAIN, 13);
}
}
}

//System.out.println(font.getFamily() + ", " + font.getName());
return font;
System.out.println("fontFamily: " + font.getFamily());
// TODO: Make these options configurable via an API, not always on
Map<TextAttribute, Object> attrs = new HashMap<>();
attrs.put(TextAttribute.KERNING, TextAttribute.KERNING_ON);
attrs.put(TextAttribute.LIGATURES, TextAttribute.LIGATURES_ON);
return font.deriveFont(attrs);

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ private JMenuBar createMenuBar() {

JMenu menu = new JMenu("Language");
ButtonGroup bg = new ButtonGroup();
addSyntaxItem("None", "NoneExample.txt", SYNTAX_STYLE_NONE, bg, menu);
addSyntaxItem("6502 Assembler", "Assembler6502.txt", SYNTAX_STYLE_ASSEMBLER_6502, bg, menu);
addSyntaxItem("ActionScript", "ActionScriptExample.txt", SYNTAX_STYLE_ACTIONSCRIPT, bg, menu);
addSyntaxItem("C", "CExample.txt", SYNTAX_STYLE_C, bg, menu);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ package com.mycompany.demo;
* @author Your Name
* http://www.example.com
*/
// ligatures: ## <> => ++ ~~ <= >= <=> ->>
ligatures: ## <> => ++ ~~ <= >= <=> ->>
public class ExampleCode {

private int value;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig unnötig

0 comments on commit 445d9d3

Please sign in to comment.