I'm noticing differences in font spacing using OpenJDK compared to OracleJDK. I've narrowed this down to the fonts. They are rendered by OpenJDK ever so slightly wider... Careful visual inspection of the screenshot above shows the character widths are identical, the only difference is the spacing. I also confirmed this with programmatic check of font metrics for all characters A-Za-z0-9.
e.g. the String "Dialog - plain" at 12pt is
125px wide in OpenJDK - my build of 8u131-b11
125px wide in OpenJDK - stock RPM from redhat disk - 1.8u45-b13
120px wide in OracleJDK - 8u131-b11 release from Oracle website
I've searched extensively for information on this and found various options including -Dawt.useSystemAAFontSettings, -Dswing.useSystemFontSettings, -Dswing.defaultlaf=com.sun.java.swing.plaf.gtk.GTKLookAndFeel from Java_Runtime_Environment_fonts. I've tried altering all of these but the results remain the same.
Further investigation has found sun.font.FontScaler, this uses different underlying fontscaler. This appears partially configurable in sun.font.FontUtilities which checks the system property for -Dsun.java2d.font.scaler=t2k, however setting this makes no difference.
My question: can FreetypeFontScaler be configured to behave in a similar or closer way to T2KFontScaler?
if (FontUtilities.isOpenJDK) {
scalerClass = Class.forName("sun.font.FreetypeFontScaler");
} else {
scalerClass = Class.forName("sun.font.T2KFontScaler");
}
This is the test program I've been using
public class FontTester {
public static void main(String[] args) throws Exception {
System.out.println(String.format("java.home=%s", System.getProperty("java.home")));
String family = Font.DIALOG;
int style = Font.PLAIN;
describeFont(new Font(family, style, 12));
JFrame frame = new JFrame();
frame.setSize(800, 600);
frame.add(new DemoPanel());
frame.setVisible(true);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
private static class DemoPanel extends JPanel {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
String family = Font.DIALOG;
int style = Font.PLAIN;
Font font = new Font(family, style, 20);
g.setFont(font);
String str = family + " - " + name(font) + " ";
Rectangle2D bounds = g.getFontMetrics().getStringBounds(str, g);
str += String.format("%f x %f", bounds.getWidth(), bounds.getHeight());
g.drawString(str, 10, 50);
}
private String name(Font font) {
List<String> attrs = new ArrayList<>();
if (font.isBold()) {
attrs.add("bold");
}
if (font.isItalic()) {
attrs.add("italic");
}
if (font.isPlain()) {
attrs.add("plain");
}
return String.join(",", attrs);
}
}
private static void describeFont(Font font) throws Exception {
Method method = Font.class.getDeclaredMethod("getFont2D");
method.setAccessible(true);
Font2D font2d = (Font2D) method.invoke(font);
System.out.print(String.format("%s: ", font));
describeFont2D(font2d);
}
private static void describeFont2D(Font2D font) {
if (font instanceof CompositeFont) {
CompositeFont cf = (CompositeFont) font;
for (int i = 0; i < cf.getNumSlots(); i++) {
PhysicalFont pf = cf.getSlotFont(i);
describeFont2D(pf);
break;
}
} else {
System.out.print(String.format("-> %s \n", font));
}
}
}
Yet more investigation has traced this though to sun.font.FontStrike.getGlyphMetrics(int) returning different results. For a glyph-id 39 ("D") the advance X value is returned as 14.0px using Oracle JDK (via T2KFontScaler) but 15.0px using OpenJDK (via FreetypeFontScaler)
To figure which is "correct" I used the fontbox Java parser to extract the advance X value for Glyph-ID 39 from the HMTX table within the TTF file LiberationSans-Regular.ttf. The value is 1479 font design units - which maps to 14.44px at 20pt font size.
There is no difference for the next character "i" - glyph-id 76. This is 4.44 according to fontbox, and returned as 4 by both T2KScaler and FreetypeScaler
I've further narrowed this down to the rounding / calculation used when standard "non-fractional" metrics are used. If fractional metrics are enabled then both Oracle and Open JDKs behave identically, although the widths are still slighly smaller than the Oracle non-fractional case.
g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
Update - Jan 2020
* In Java 11 there is new option which may be related - see https://bugs.openjdk.java.net/browse/JDK-8217731
FREETYPE_PROPERTIES=truetype:interpreter-version=35
Further investigation has found sun.font.FontScaler, this uses different underlying fontscaler. This appears partially configurable in sun.font.FontUtilities which checks the system property for -Dsun.java2d.font.scaler=t2k, however setting this makes no difference.
You're correct in that the underlying font scaler is different between Oracle and OpenJDK - unfortunately however, this is hard coded and not configurable.
The relevant code is in FontScaler:97:
if (FontUtilities.isOpenJDK) {
scalerClass = Class.forName("sun.font.FreetypeFontScaler");
} else {
scalerClass = Class.forName("sun.font.T2KFontScaler");
}
And the isOpenJDK flag? It's set by FontUtilities:125:
File lucidaFile = new File(jreFontDirName + File.separator + LUCIDA_FILE_NAME);
isOpenJDK = !lucidaFile.exists();
And that constant:
static final String LUCIDA_FILE_NAME = "LucidaSansRegular.ttf";
Unless I've missed something in those source files, there's no other configuration flag or any other condition that will change that scaler class being used.
So the only vaguely sensible way of doing this (I'm excluding horrible classloader / reflection hacks here) is to add that Lucida file in place. Unfortunately though in most scenarios I can think of (assuming you're not distributing the JRE along with the package) this isn't really going to be a viable solution either.
Related
I have here a simple, complete, self-contained, pure-Java program that displays a frame containing a button with a Unicode emoji character as its text. This program runs correctly and displays the emoji on Windows, but not on Mac OS (Catalina, AdoptOpenJDK15).
Here is the source code; filename is EmojiTest.java:
import java.awt.*;
import javax.swing.*;
public class EmojiTest {
public static void main(String[] args) {
// listFonts(); // Uncomment if you want to see all registered fonts.
JFrame frame = new JFrame();
frame.setTitle("Emoji Demo: " + System.getProperty("os.name"));
frame.setSize(400, 300);
JButton button = new JButton();
String emoji = "\ud83d\udc36"; // dog face
button.setText(emoji);
button.setFont(getFont(emoji));
JPanel holder = new JPanel(); // Let button be natural size
holder.add(button);
frame.getContentPane().add(holder);
frame.setVisible(true);
}
static Font getFont(String emoji) {
String os = System.getProperty("os.name");
String fontFamily = os.equals("Mac OS X") ? "Apple Color Emoji" : "Segoe UI Emoji";
Font font = new Font(fontFamily, Font.PLAIN, 20);
System.out.println("Font: " + font);
System.out.println("can display result: " + font.canDisplayUpTo(emoji));
return font;
}
static void listFonts() {
String fonts[] = GraphicsEnvironment.getLocalGraphicsEnvironment()
.getAvailableFontFamilyNames();
for (int i = 0; i < fonts.length; i++) {
System.out.println(fonts[i]);
}
}
}
A few things to observe here:
The getFont() helper method prints out whether the font thinks it can display the text. On MacOS, it always prints -1, meaning it thinks it can display the text.
All 279 fonts on my Apple system claim (via canDisplayUpTo) that they can display it, but they fail to display it. Yes, I tested all of them visually, and no, none worked.
As you can see from the picture, this program works correctly on Windows. So the code is "correct", in the sense that it should work on Macs.
I'm fairly convinced that this is a JDK bug. However, if you debug the EmojiTest program in IntelliJ, it actually shows you in the debug pane that the text is in fact the correct emoji:
So IntelliJ, a Java program running on Mac OS Catalina, is able to render the emoji correctly in its font—admittedly, not in a JButton. But I have also tried rendering emoji in JTextPanes and other Swing widgets on MacOS, to no avail.
This question has been asked several times over the past few years, often indirectly, but the answer is always "You need to make sure your font can display the emoji".
I think this program pretty clearly demonstrates that the font being able to display the emoji is not the issue. The Apple Color Emoji font can absolutely display emoji, including this one. It just doesn't seem to be able to do it in Java.
Here's my question: How can I modify this EmojiTest.java program to show the emoji on MacOS?
This question is about different behaviour of JEditorPane in Java 8 and Java 9. I’d like to know if others have experienced the same, whether it could be a bug in Java 9, and if possible have your input to how to handle it in Java 9.
Context: In our (age-old) code base we are using a subclass of JTable, and for rendering multi-line HTML in one of the columns we are using a subclass of JEditorPane. We are using JEditorPane.getPreferredSize() for determining the height of the content and using it for setting the height of the table row. It’s been working well for many years. It doesn’t work in Java 9; the rows are displayed just 10 pixels high. Seen both on Windows and Mac.
I should like to show you two code examples. If the first and shorter one suffices for you, feel free to skip the second and longer one.
MCVE:
JEditorPane pane = new JEditorPane("text/html", "");
pane.setText("<html>One line</html>");
System.out.println(pane.getPreferredSize());
pane.setText("<html>Line one<br />Line 2<br />Third line<br />Line four</html>");
System.out.println(pane.getPreferredSize());
Output in Java 8 (1.8.0_131):
java.awt.Dimension[width=48,height=15]
java.awt.Dimension[width=57,height=60]
And on Java 9 (jdk-9.0.4):
java.awt.Dimension[width=49,height=15]
java.awt.Dimension[width=58,height=0]
In Java 9 the first time I set the text, the preferred height reflects it. Every subsequent time it doesn’t.
I have searched to see if I could find any information on a bug that might account for this, did not find anything relevant.
Question: Is this intended (change of) behaviour? Is it a bug??
Longer example
public class TestJEditorPaneAsRenderer extends JFrame {
public TestJEditorPaneAsRenderer() {
super("Test JEditorPane");
MyRenderer renderer = new MyRenderer();
String html2 = "<html>one/2<br />two/2</html>";
String html4 = "<html>one of four<br />two of four<br />"
+ "three of four<br />four of four</html>";
JTable table = new JTable(new String[][] { { html2 }, { html4 } },
new String[] { "Dummy col title" }) {
#Override
public TableCellRenderer getDefaultRenderer(Class<?> colType) {
return renderer;
}
};
add(table);
setSize(100, 150);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
public static void main(String[] args) {
System.out.println(System.getProperty("java.version"));
new TestJEditorPaneAsRenderer().setVisible(true);
}
}
class MyRenderer extends JEditorPane implements TableCellRenderer {
public MyRenderer() {
super("text/html", "");
}
#Override
public Component getTableCellRendererComponent(
JTable table, Object value, boolean selected, boolean hasFocus, int row, int col) {
setText(value.toString());
Dimension preferredSize = getPreferredSize();
System.out.format("Row %d preferred size %s%n",row, preferredSize);
if (preferredSize.height > 0 && table.getRowHeight(row) != preferredSize.height) {
table.setRowHeight(row, preferredSize.height);
}
return this;
}
}
Result on Java 8 is as expected:
Output from Java 8:
1.8.0_131
Row 0 preferred size java.awt.Dimension[width=32,height=30]
Row 1 preferred size java.awt.Dimension[width=72,height=60]
Row 0 preferred size java.awt.Dimension[width=32,height=30]
Row 1 preferred size java.awt.Dimension[width=72,height=60]
On Java 9 the second row is not shown high enough so most of the lines are hidden:
Output from Java 9:
9.0.4
Row 0 preferred size java.awt.Dimension[width=33,height=30]
Row 1 preferred size java.awt.Dimension[width=73,height=0]
Row 0 preferred size java.awt.Dimension[width=33,height=0]
Row 1 preferred size java.awt.Dimension[width=73,height=0]
A possible fix is if I create a new renderer component each time by changing the body of getDefaultRenderer() to:
return new MyRenderer();
Now the table looks good as on Java 8. If necessary I suppose we could live with a similar fix in our production code, but it seems quite a waste. Especially if it’s only necessary until the behaviour change is reverted in a coming Java version.
I have faced similar problem, but with JLabel. My solution was to set the size of the JLabel which seems to force the component to recalculate its preferred size.
Try this:
JEditorPane pane = new JEditorPane("text/html", "");
pane.setText("<html>One line</html>");
System.out.println(pane.getPreferredSize());
pane.setText("<html>Line one<br />Line 2<br />Third line<br />Line four</html>");
// get width from initial preferred size, height should be much larger than necessary
pane.setSize(61, 1000);
System.out.println(pane.getPreferredSize());
In this casse the output is
java.awt.Dimension[width=53,height=25]
java.awt.Dimension[width=61,height=82]
Note: my fonts are different, that is why I get different dimensions.
Edit: I changed your getTableCellRendererComponent in the longer example like this and it is working for me. I am using jdk9.0.4, 64 bit.
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean selected, boolean hasFocus, int row, int col) {
setText(value.toString());
Dimension preferredSize = getPreferredSize();
setSize(new Dimension(preferredSize.width, 1000));
preferredSize = getPreferredSize();
System.out.format("Row %d preferred size %s%n", row, preferredSize);
if (preferredSize.height > 0 && table.getRowHeight(row) != preferredSize.height) {
table.setRowHeight(row, preferredSize.height);
}
return this;
}
Based on previous suggestion and digging in JDK sources I'm suggesting a little bit simpler solution:
setSize(new Dimention(0, 0))
Based on BasicTextUI.getPreferredSize implementation it forces to recalculate root view size
} else if (d.width == 0 && d.height == 0) {
// Probably haven't been layed out yet, force some sort of
// initial sizing.
rootView.setSize(Integer.MAX_VALUE, Integer.MAX_VALUE);
}
I am using a Swing application which on my computer shows text with a ridiculously small font size.
Is there a way to change the font size, or maybe some kind of DPI setting, from the command line or with some kind of configuration file (for example, something like a swing.properties file)?
I don't have access to the source code.
EDIT:
Small font sizes should not be a problem any more since Java 9. Swing has started to scale its GUI components depending on the screen resolution.
There is no command line switch to change the font size for Swing. What you would have to do is to invoke the following method:
public static void adjustFontSize(int adjustment) {
UIDefaults defaults = UIManager.getDefaults();
List<Object> newDefaults = new ArrayList<Object>();
Map<Object, Font> newFonts = new HashMap<Object, Font>();
Enumeration<Object> en = defaults.keys();
while (en.hasMoreElements()) {
Object key = en.nextElement();
Object value = defaults.get(key);
if (value instanceof Font) {
Font oldFont = (Font)value;
Font newFont = newFonts.get(oldFont);
if (newFont == null) {
newFont = new Font(oldFont.getName(), oldFont.getStyle(), oldFont.getSize() + adjustment);
newFonts.put(oldFont, newFont);
}
newDefaults.add(key);
newDefaults.add(newFont);
}
}
defaults.putDefaults(newDefaults.toArray());
}
where adjustment is the number of points that should be added to each font size.
If you don't have access to the source code, you can always write your own wrapper main class where you call
UIManager.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent event) {
if (event.getPropertyName().equals("lookAndFeel")) {
adjustFontSize(5);
}
}
});
before invoking the main method of the actual application.
However, if the font size is very small, it has likely been set explicitly, so changing the defaults might not help.
I don't think there are such options since all the component Font sizes are defined in Look and Feel (L&F) default values. Some of L&Fs allow quick font changes, some of them doesn't. In most cases you should be able change the font size by changing the UI defaults:
UIManager.put ( "Button.font", new SwingLazyValue ( "javax.swing.plaf.FontUIResource", null, new Object[]{ fontName, fontStyle, fontSize } ) );
UIManager.put ( "Label.font", new SwingLazyValue ( "javax.swing.plaf.FontUIResource", null, new Object[]{ fontName, fontStyle, fontSize } ) );
UIManager.put ( "TextField.font", new SwingLazyValue ( "javax.swing.plaf.FontUIResource", null, new Object[]{ fontName, fontStyle, fontSize } ) );
e.t.c for each component.
And i am not sure if those values could be passed to application without changing its code or atleast having some font-size change support inside the application.
I have not tested it, but you might try launching the app. using Java Web Start. It allows properties like swing.useSystemFontSettings & swing.metalTheme to be specified even for sand-boxed apps. Doing either might 'override' small fonts set in the code.
See the JNLP file syntax for more details.
(source: google.com)
Recently, I realize the Chinese Character displayed are rather ugly in my application.
I thought I should make them to "anti-alias". But, how can I do that in Java?
FYI, I didn't explicitly choose the font I want to use in my GUI application. I solely let the system decide their own during startup. I however, do explicitly set the locale, before show up the GUI.
Locale.setDefault(locale);
The system will always choose
javax.swing.plaf.FontUIResource[family=Tahoma,name=Tahoma,style=plain,size=11]
no matter I am in English or Chinese locale.
Anti-aliasing considered harmful: http://www.joelonsoftware.com/articles/fog0000000041.html
The point is, that beauty of characters is not necessarily the user interface goal. It is not everything. What you should look for, is readability of text. When your Chinese characters look not smooth, it may be exactly what helps human eye's control loop to know that it is in focus and stop blaming the eye muscules for blurriness. Really, don't fall in this pitfal.
Here's a method to read a truetype font from the classpath and register it with the graphics environment:
private static Font readFont(String name) {
InputStream in = Fonts.class.getResourceAsStream(name + ".ttf");
if (in == null) {
throw new IllegalArgumentException(name);
}
try {
Font retval = Font.createFont(Font.TRUETYPE_FONT, in);
GraphicsEnvironment.getLocalGraphicsEnvironment().registerFont(retval);
return retval;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
You can then use this font object to derive characters of different sizes, or you could try applying this font using Swing CSS. In this case, the value you would put in the "font-family" attribute is the value returned by Font.getName().
For example:
static {
Font font = readFont("VeraMono");
if (font != null) {
font = font.deriveFont(14f);
} else {
throw new IllegalStateException();
}
MONOSPACED_TEXT_FONT = font;
MONOSPACED_TEXT_FONT_STYLE = "font-family: " + font.getName() + "; font-size: 14pt; font-weight: normal;";
}
I have been attempting to enhance my GUI system written in Java to use subpixel antialiasing and have been successful, except for one remaining anomaly. This is a follow on to my other question from yesterday.
The remaining problem is that setting rendering hints KEY_ANTIALIASING to VALUE_ANTIALIAS_ON causes KEY_TEXT_ANTIALIASING to be ignored when it is set to an LCD (subpixel) AA value. Can anyone shed some light on this? Currently I am forced to VALUE_ANTIALIAS_OFF before rendering text and turn it back on after rendering text (so that other painting, like circles, etc, is AA'd).
This problem is proven by the self-contained test program below. As you can see if you run it, the circle is painted with AA when the font isn't, and vice versa. It would be nice to have AA preconfigured to work for all painting.
Self Contained Test Program
import java.awt.*;
import java.awt.event.*;
public class AwtTestFrame1c extends Panel {
AwtTestFrame1c() {
setBackground(SystemColor.control);
}
public void paint(Graphics gc) {
Graphics2D g2d = (Graphics2D)gc;
int py=0;
py=paintText(g2d,py,RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB,true );
py=paintText(g2d,py,RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB,false);
}
private int paintText(Graphics2D dgc, int py, Object val, boolean aa) {
char[] txt=("The quick brown fox jumped over the lazy dog ("+val+", General AA: "+aa+")").toCharArray();
if(aa ) { dgc.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON ); }
else { dgc.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_OFF); }
if(val !=null) { dgc.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,val); }
dgc.setFont(font);
dgc.drawOval(5,py+5,15,15);
dgc.drawChars(txt,0,txt.length,30,py+line-5);
return (py+line);
}
static private final Font font=new Font("SansSerif",Font.PLAIN,16);
static private final int line=25;
static public void main(String[] args) {
Frame wnd=new Frame("AWT Antialiased Text Sample");
wnd.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
wnd.add(new AwtTestFrame1c());
wnd.setSize(new Dimension(1000, 300));
wnd.setVisible(true);
}
}
I was updating VirtualBox, so I took pictures. I may just be seeing the host's rendering, but I suspect it is also implementation dependent.
Ubuntu 9.10
Mac OS X 10.5
Windows 7
A bit of poking around found this: bug 6263951.
Apparently the bustage is fixed in b17? I'm not exactly sure how to interpret the bug report.
Definitely broken here on 1.6.0_17-b04 (MacOS X 10.5).