I'm working on an application that I thought would be fairly simple / straightforward. All it does is render a string into a BufferedImage
in a particular font (from a file, not installed on the system). I'm making some progress, but after several days of struggling with it, Java seems very fickle about font metrics. Sometimes it likes to give good font metrics and sometimes it likes to return some combination of all zeroes and all 240s (regardless of the size of the font) for no readily apparent reason.
In a few cases I found that just calling f = f.deriveFont(myFloat)
would cause total failure of font metrics, going from valid numbers to all zeroes, whereas calling f = f.deriveFont(Font.PLAIN, (int) myInt)
would preserve the metrics so the new font instance would work properly. (Yes, I know that's supposed to be a float, but for some reason calling it with a float was in several cases breaking the font metrics again.)
Through a combination of TextAttributes
in the Font and RenderingHints
in the Graphics2D
object, I've managed to get most of the fonts to work... but I still have a handful of OpenType fonts (.otf) that have broken metrics in Java (JSE/JDK 7) while they display fine in the Windows 7 font viewer.
So I chose one of these fonts and I stripped out everything in my application and am just getting the most raw view of the font that I can in a Main method. The echo()
method in this code is just a shorthand for System.out.println()
. (Also, I'm sorry for the sloppiness of this code - I'm just throwing in anything I can find that might potentially yield new information at this point.)
BufferedImage img = new BufferedImage(730, 70, TYPE_INT_ARGB);
Graphics2D g = img.createGraphics();
FontRenderContext frc = g.getFontRenderContext();
String text = "Hello World";
File media = new File("z:\\path\\to\\fonts\\LondonDoodles.otf");
Font f7 = Font.createFont(Font.TRUETYPE_FONT, media);
f7 = f7.deriveFont(Font.PLAIN, (int) 20);
TextLayout lay = new TextLayout(text, f7, frc);
Rectangle2D r = lay.getPixelBounds(frc, 0, 0);
echo("NumGlyphs=" + f7.getNumGlyphs());
echo("bounds=" + r); // these bounds are all zeros
// use an array of GlyphCodes instead of a String
// to try and get at the glyphs directly without going through the CMAP
GlyphVector gv = f7.createGlyphVector(frc, new int[] { 1, 2, 3, 4, 5 });
echo("Glyph0Logical=" + gv.getGlyphLogicalBounds(0).getBounds2D()); // all 240s, go figure
echo("Glyph0Pixel=" + gv.getGlyphPixelBounds(0, frc, 0, 0)); // all zeros
echo("GlyphVectorLogical=" + gv.getLogicalBounds()); // random assortment of zeros & 240s
echo("GlyphVectorPixel=" + gv.getPixelBounds(frc, 0, 0)); // all zeros
echo(g.getFontMetrics(f7)); // 240s
LineMetrics lm = f7.getLineMetrics(text, frc);
echo("LineMetrics=" + lm.getAscent() + "/" + lm.getDescent() + "/" + lm.getHeight() + "/" + lm.getLeading()); // 240s
The output from this code looks like this:
NumGlyphs=33
bounds=java.awt.Rectangle[x=0,y=0,width=0,height=0]
Glyph0Logical=java.awt.geom.Rectangle2D$Float[x=240.0,y=240.0,w=240.0,h=240.0]
Glyph0Pixel=java.awt.Rectangle[x=0,y=0,width=0,height=0]
GlyphVectorLogical=java.awt.geom.Rectangle2D$Float[x=0.0,y=240.0,w=0.0,h=240.0]
GlyphVectorPixel=java.awt.Rectangle[x=0,y=0,width=0,height=0]
sun.font.FontDesignMetrics[font=java.awt.Font[family=London Doodles,name=LondonDoodles,style=plain,size=20]ascent=-239, descent=240, height=241]
LineMetrics=-240.0/240.0/240.0/240.0
It's obviously possible to get correct font metrics from the .otf file this font came in, because the Windows 7 font viewer and another app written in C is able to render it onto a PNG like I'm trying to do here, I just can't figure out why Java doesn't like it.
- It's not throwing an
IOException
or aFontFormatException
, so I know it's reading the file and creating an ostensibly validFont
object. - In the code above, I created the
GlyphVector
using an int-array, which is theGlyphCodes
indexed from 1 to the number of glyphs in the font (in this case 33) so it wouldn't have to search the CMAP that translates from Unicode CodePoints to glyphs and potentially not find those characters. - Also you can see in the echo
of the
FontMetrics
that the Font is reporting a size of 20pts, so it's not like it's been set to size=0 or anything. The 240s in the output don't change, regardless of what size I set on the Font.
After several days of trying to come up with search terms for Google, I still can't find mention of this specific problem. It seems really odd. You would think that font metrics seemingly randomly breaking left and right (though I'm sure it's not actually random) would be something people would be asking about if they were having this issue, particularly given that all the fonts I've tested seem to consistently report a combination of zeros and 240s. That latter part, that they all consistently report 240s, seems oddly specific for a problem that can't be easily Googled.
Anyway, I'm wondering if anybody's got a list of items (text attributes, graphics settings, JRE flags, OS environment variables, font-creation programs, flags within the font file, etc.) that might cause bad font metrics in Java? Or maybe a list of workarounds for broken font metrics? Or are fonts just horrible in Java and I should be looking into LaTeX? (Which to be honest, I know nothing about at this point.)
Thanks!
EDIT: Well... this is kind of frustrating... I thought maybe if I dug into the "open source" for OpenJDK that maybe I could produce a derived implementation of Font
that could resolve these metrics issues... but apparently that's been deliberately prevented by making all the necessary internals private, static and final. See: Fonts, how to extend them ... and I can't replace core classes at runtime - see: Replacing java class? ... so the only alternative if I want to try and fix the metrics that way is to recreate the entire Java font architecture with full copies of java.awt.Font
, sun.font.Font2D
, etc. and then draw the glyphs onto the Graphics2D
object manually since Graphics.drawGlyphVector()
and Graphics.drawString()
won't work without Font
or a derivative of Font
because they used the Font
class as the argument to Graphics.setFont()
instead of declaring an interface for Font to implement. ... unless I'm misreading the answer on that 2nd Stack Overflow reference? Am I misreading that? Could I create a custom class loader that would substitute the Font
class with a modified version?
EDIT: Maybe I should have done more research before I posted the last edit, but for anyone who's reading this question and wondering, yes, you can substitute modified versions of core java classes with a JVM argument. See: http://media.techtarget.com/tss/static/articles/content/CovertJava/Sams-CovertJava-15.pdf So what I'm doing right now is digging through reams of core classes to try and find the place where metrics are read in from the font file. If I can find that, then I can substitute that class with a modified version that will correct the broken metrics.