Ligatures
Playing around with swing on java 17 I encountered some funky glitches: When enabling font ligatures and switching to a ligature heavy font, like Fira Code
, selecting part of the ligated Text has following effect:
- The selected text seems to be re-rendered out of context, however the parts outside the ligature still are unchanged.
- When exiting the selection the caret (text cursor) leaves some unrendered space between letters.
- Both problems can also happen simultaneous, possibly leading to unreadable content.
Kerning
When considering a kerning intensive font like Segoe UI
or Segoe UI Variable
, the new default font of Microsoft Windows 11, following glitches are happening:
- Re-rendering of selected text without kerning being calculated in the context of the full text.
- Trying to place the caret in a long line of kerned text (in this case the text is rendered more condensed) is nearly impossible - it seems like the internal selection logic is calculating the target based on the unkerned version.
- Kerned text does also seem to have the problem of not being correctly re rendered when selections are canceled.
Analysis
The behaviour seems to show from at least Java 1.8 on, I spared my time and harddrive space checking previous versions. Also different LAF's do not seem to influence the glitches in any way. I think the culprit seems to be java.desktop/javax.swing.text.PlainView
, especially its way in treating text as splittable segments, re rendering only 'damaged' text in updateDamage#668
and its calculations of change bounding boxes.
Workaround
The only consistent workaround I found working all the time was to deactivate kerning and ligatures. I also tried to hook my own PlainView class into swing, but its architecture actively tries to discourage such fixes.
Questions
Finally I want to ask some questions, it would be very helpful in understanding the problematic:
- Did you also encounter such problems or is my fabricated example too synthetic?
- Are other rendering pipelines also affected? I assume so but I only was able to test on Windows 11.
- Do you know if it is the fault of the JVM standard library or are the stored font metrics non-standard?
- Are there any workarounds you know of improving the situation?
- Is there a way in exchanging the default font rendering pipeline in swing?
Thank you for your help! I have attached the source code as well as my current machine configuration below.
Edit:
The Problem also seems to be present on Mac OSX Big Sur and Java 17 leading to artefacts when using kerning and ligatures. To reproduce I used the default Helvetica
font. This may hint on the cause for the glitches being the jdk.
import javax.swing.*;
import java.awt.*;
import java.awt.font.*;
import java.util.*;
/**
Tested on: Edition Windows 11 Pro
Version 22H2
OS build 22623.875
Experience Windows Feature Experience Pack 1000.22636.1000.0
openjdk 17.0.4.1 2022-08-12
OpenJDK Runtime Environment Temurin-17.0.4.1+1 (build 17.0.4.1+1)
OpenJDK 64-Bit Server VM Temurin-17.0.4.1+1 (build 17.0.4.1+1, mixed mode, sharing)
**/
public class Main {
private static final boolean TEST_KERNING = true;
private static final boolean TEST_LIGATURES = true;
private static final String TEST_FONT = "Segoe UI Variable";
private static final Number TEST_FONT_SIZE = 40;
private static final Number TEST_FONT_WIDTH = TextAttribute.WEIGHT_BOLD;
public static void main(String[] args) throws Exception {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
Map<TextAttribute, Object> textAttributes = new HashMap<>();
textAttributes.put(TextAttribute.FAMILY, TEST_FONT);
textAttributes.put(TextAttribute.SIZE, TEST_FONT_SIZE);
textAttributes.put(TextAttribute.WEIGHT, TEST_FONT_WIDTH);
if(TEST_KERNING)
textAttributes.put(TextAttribute.KERNING, TextAttribute.KERNING_ON);
if(TEST_LIGATURES)
textAttributes.put(TextAttribute.LIGATURES, TextAttribute.LIGATURES_ON);
Font font = Font.getFont(textAttributes);
JTextField txtInput = new JTextField();
txtInput.setFont(font);
JFrame frame = new JFrame("Demo");
frame.setSize(400, 100);
frame.add(txtInput);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
SwingUtilities.invokeAndWait(() ->
frame.setVisible(true)
);
}
}
ViewFactory
to return custom views which perform all the calculations taking ligatures and kerning into account. You can also submit a bug at bugreport.java.com. – Downwards