Thursday, August 21, 2008

Font and Text questions

Q: What are the capabilities of the Java 2D text rendering system?

A: These include:

Antialiased and sub-pixel (aka LCD) text display
Transformed text
Text as shapes that can be filled with colours, gradient paints etc.
Rendering complex text scripts (eg Indic), Bidi text etc
APIs to locate and use platform fonts
APIs to directly load and register fonts from files and network resources.
These capabilities are all uniformly supported across platforms both for on-screen display and for printing.


Q: What are the different ways that text can be rendered using Java 2D?

A: java.awt.Graphics2D.drawString() draws a String of text using the current font and other rendering attributes. This is the most
common and recommended API as it will automatically identify and properly handle complex scripts, Bidi etc.

java.awt.font.TextLayout object allows you to implement text editing yourself: it includes mixed styles, BIDI text layout, carets, highlighting, hit testing and many other features.

java.awt.font.GlyphVector can be used to accomplish
glyph-level manipulation can be accomplished by using GlyphVector and GlyphMetrics classes if you want to implement custom text layout algorithms. Since glyphs are actually shapes, you can do with glyphs anything that you can do with shapes, such as clipping and stroking.

Using Swing text components : since Swing is based upon Java 2D JTextField, JTextArea, and JEditorPane, which supports editing and multiple fonts and styles, all utilise the above Java 2D APIs. Many developers will therefore not directly use the 2D text APIs but will use the UI-oriented Swing interfaces.

Q. What font types does Java 2D support?

A. The primary and preferred font format is the TrueType font format. Java 2D also supports Opentype layout tables within TrueType fonts for complex text rendering (Arabic, Indic etc). In addition Java 2D supports Postscript Type1 fonts.


Q: Are any fonts bundled with Java 2D?

A: Yes, the Java SE SDK includes three Lucida TrueType font families: Lucida Sans (Sans Serif), Lucida Typewriter (Terminal/Monospaced) and Lucida Bright (Serif). However, only one font : Lucida Sans Regular is always distributed with the JRE that is installed with Java Plugin for running applets in web browsers to limit download size as TrueType fonts can be quite large. End users can elect to install the other fonts but only Lucida Sans Regular is guaranteed to be always available in any Java application. However since Lucida Sans Regular supports many character sets, such as Basic Latin, Latin 1, Latin Ext A, Greek, Cyrillic, Hebrew, Arabic, currency symbol, super subscripts, number forms, dingbats, it means applications have a guaranteed good base line of support. The font files are located in the $JAVAHOME/jre/lib/fonts directory.

Q: How can I list all available fonts?

A: You may get a list of all available font family names using java.awt.GraphicsEnvironment.getAvailableFontFamilyNames() or may list all the font instances using java.awt.GraphicsEnvironment.getAllFonts() In each case the returned list will include the logical fonts such as "Dialog". java.awt.GraphicsEnvironment. getAvailableFontFamilyNames() is typically the most useful for presenting a menu of fonts to a user as a particular style (eg Bold) can requested for a font by specifying it in the style field of the Font constructor.

For sample code, see the implementation of the Font2DTest demo shipped with the JDK (demo/jfc/Font2DTest/src)


Q: How can I make my custom font available to my Java application?

A: Since Java 2D can locate fonts installed in the O/S one option is to just install the font on your system in the usual platform manner - eg dragging it into the Windows font folder.
If you don't want other applications to use your font then there are two other options

If you ship a private JRE with your application you can copy your fonts to $JAVAHOME/jre/lib/fonts since they will be available only to java applications executed by that JRE and will be visible via Java's font enumeration APIs.

You may also load custom fonts dynamically - by using java.awt.Font.createFont() to instantiate your fonts from files or network streams.
Fonts instantiated via createFont() offer even more flexibility in limiting the visibility of the font. Font instances using such custom fonts cannot normally be constructed. Instead you must use Font.deriveFont() on the instance returned from the Font.createFont() method. Therefore only code to which you provide a references to the Font can use it. Note that each call to createFont from the same source (file) will create a new distinct Font which will be GC'd only when it is no longer referenced. So if you expect to need to use the created Font in many places simultaneously cache a single copy to derive from rather than re-creating from the source file.

As of Java SE 6, there is a method : GraphicsEnvironment.registerFont() which gives you the ability to make a "created" font available to Font constructors and to be listed via Font enumeration APIs. Font.createFont() and this method combine to provide a way to "install" a Font into the running JRE so it is available just as O/S installed fonts are. However this Font does not persist across JRE invocations.


Q: Why does (eg) a 10 pt font in Java applications appear to have a different size from the same font at 10pt in a native application?

A: Conversion from the size in points into device pixels depends on device resolution as reported by the platform APIs. Java 2D defaults to assuming 72 dpi. Platform defaults vary. Mac OS also uses 72 dpi. Linux desktops based on GTK (Gnome) or Qt (KDE) typically default to 96 dpi and let the end-user customise what they want to use. Windows defaults to 96 dpi (VGA resolution) and also offers 120 dpi (large fonts size) and lets users further specify a custom resolution. So a couple of things can now be seen

The DPI reported by platform APIs likely has no correspondence to the true DPI
of the display device
Its unlikely that Java 2D's default matches the platform default.
So a typical results is that for Window's default 96 DPI that a 10 pt font in a Java application is 72/96 of the size of the native counterpart.

Note that Swing's Windows and GTK L&Fs do scale fonts based on the system DPI to match the desktop. If you want to do the same in your application you can call java.awt.Toolkit.getScreenResolution() and use this to apply a simple scale to the size you specify for fonts.


Q: How to I find if I use a font to display Cyrillic, or Hindi etc?

A: Use Font.canDisplay() to find out if there is a glyph matching a given Unicode character in a given font.


Q. How can I specify the text antialiasing/font smoothing settings to be used by Swing in applications on Java SE 6?

A. This is generally is a question from users of KDE (on Linux or other Unix) or Windows 2000 who would like to use LCD subpixel text. There's no programmatic way to do it in Java SE 6 (but see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6274842 to see if that has changed since this was written). However if you know what you want you can set a system property :

java -Dawt.useSystemAAFontSettings=lcd
which request to use LCD subpixel text in the most common subpixel configuration There are several useful values for this property as follows :

"false" corresponds to disabling font smoothing on the desktop.
"on" corresponds to Gnome Best shapes/Best contrast (no equivalent Windows desktop setting)
"gasp" corresponds to Windows "Standard" font smoothing [see note (*) below] (no equivalent Gnome desktop setting)
"lcd" corresponds to Gnome's "subpixel smoothing" and Windows "ClearType"
The full details are documented in the 2D Flags Guide at http://java.sun.com/javase/6/docs/guide/2d/flags.html#aaFonts

(*) Something that surprises people is that Windows "Standard" font smoothing does not mean always smooth/AA. In fact at typical GUI sizes most text is black and white. It uses information in a TrueType font (the gasp table) which specifies whether to use hinted B&W or greyscale smoothing. So if you expected the JDK to do AA at all sizes with this setting, don't be surprised when it doesn't. If you look closely you'll see windows native apps are behaving similarly. See http://www.microsoft.com/typography/SmoothFonts.mspx.


Q: How do I obtain font metrics?

A: You may obtain a Fontmetrics instance by calling java.awt.Graphics.getFontMetrics(Font) This will decribe the font-wide metrics using the current FontRenderContext of the Graphics instance.

Font wide metrics are not specific to a particular piece of text. It is based on data in the font specified by the font designer to reflect the overall design of the font. Do not expect that, for example, the reported "ascent" of the font will be as high as the highest ascent of any glyph in the font.

If you are looking for accurate bounds for string of text - please look at the next question.

FontMetrics are widely used in helping to layout GUIs. They are also cheap to obtain.


Q. What is the difference between logical, visual and pixel bounds?

A. Logical bounds are the most commonly used and include the ascent, descent, leading and advance of the text. They are useful to position the text correctly, particularly when appending one string after another or for multiple lines of text but they very likely will not enclose the rendered image. Particular examples of this may be glyphs which extend a pixel to the left of the "origin" at which it is drawn. "W" is a particular example - the leftmost long diagonal may extend to the left of the rendering origin. Glyphs in italic fonts are may commonly extend further to the right than the overall "advance" of the text may indicate. These two issues may lead to text being clipped on the left or the right if placed in a tight area measured using the advance of the string. Adding a couple of pixels of padding at each end is probably the simplest workaround for this.

Similarly large descenders, or diacritics that are used in European languages may extend beyond the reported descent or ascent. If used to create a tight bounding box for a label for example this may clip at the bottom or top. Extra padding is again the simplest solution.

To get the logical bounds of a String one may use any of following methods as appropriate :


* Font.getStringBounds(...)
* TextLayout.getAscent()
* TextLayout.getDescent()
* TextLayout.getLeading()
* TextLayout.getAdvance()
* GlyphVector.getLogicalBounds(...)
Note that ascent, descending, and leading which sum to the height, are also available as font-wide properties. That is commonly used in layout of a UI where you would not want the UI to re-layout when the text changes. These font-wide metrics are also not guaranteed to enclose all the text.

Visual bounds guarantee that the theoretical outlines of the glyphs are enclosed. But it still might not enclose the rendered image. Visual bounds are returned by


* GlyphVector.getVisualBounds(...)
* TextLayout.getBounds()
Note that TextLayout.getBounds() in fact returns the union of the visual and logical bounds, including any underline and strikethrough.

Pixel bounds are the most precise for where what matters is the rendered image since pixel bounds are guaranteed to enclose all pixels and can be useful for finding the area required to be cleared to fully erase the text. Note that they depend on many factors (position,FontRenderContext, etc.). Since computing pixel bounds requires that Java 2D retrieve the glyph image and examine it, it is more expensive to compute than logical bounds.

To obtain pixel bounds use:

* GlyphVector.getPixelBounds(...)
* TextLayout.getPixelBounds(...) (new in Java SE 6)
Note that TextLayout.getPixelBounds(...) includes the bounds of any underline etc.

Also note that no bounds need completely enclose any other รข€“ so use the one that's appropriate to your needs.


Q. What are fractional metrics? What are the consequences of setting the fractional metrics rendering hint on text performance, readability etc? When should I use it?

A. In short fractional metrics doesn't affect performance. Principally it affects the spacing between glyphs (characters) which on low resolution (screen) devices can degrade the overall legibility of the text. Understanding this requires some explanation of the topic.

"Fractional metrics" is sometimes called "Printer matching" on other platforms. It refers to the fact that characters in a font are optimized for particular font size and output device resolution to improve legibility of text. These optimizations involve rounding to the pixel grid and therefore may affect overall widths of individual glyphs as well as distances between them.

The text display system can either choose to round these "advance widths" to an integer so that the text will have consistent spacing, or it can accumulate the positions of the characters using the unrounded, or fractional, values.

If it uses the fractional values then the sequence "wi" may not have consistent spacing between the "w" and the "i" at different points along the line. That variation in spacing creates visual noise which is disruptive to the reading process and so is not preferred as the default on the screen. Note that it is only the inter-glyph spacing which changes, not the glyph images,

If it uses the rounded values then the inter-character spacings are much more uniform, but each character's rounded spacing introduces some error and over a long string that error accumulates with each character displayed in succession. The result may be either a longer or a shorter overall string compared with factional metrics. That error is also dependent on the resolution at which the text is being displayed such that the relative lengths of strings will vary as you change the zoom factor or the resolution of the output device. As a result, such rounded widths are considered "not matched to the final printed output" or "not Printermatched". They also complicate layout if you want to view the same text at different sizes or magnifications - you have to customize the layout for each zoom level or expect the strings to start overrunning or underrunning their static positioning.

Since non-fractional (integer) metrics are required for best screen readibility, that is why integer metrics is the default for Java 2D.

The principal reason for turning on fractional metrics is then to closely approximate the layout at high-device resolution when formatting output to a printer, or for displaying a "print preview".

The simplest way to look at it is that "Fractional metrics" improve WYSIWYG on the printer at the expense of legibility on low DPI screens.

0 comments: