 |
Font Hints for Custom Components
Posted by chet on January 10, 2007 at 02:40 PM | Comments (12)
I just got finished writing a small section on text-anti-aliasing for Romain's
and my Filthy Rich Clients book (did you notice how smoothly I slid that
teaser into the discussion?). It seemed like such a bite-sized and useful bit
of info that I thought it would go well here, so here it is:
Setting text anti-aliasing hints can be confusing, especially with new
RenderingHints added in Java SE 6 for LCD text. What text quality do you want?
What is appropriate for the application? What is appropriate for the platform?
What does your user prefer?
A better solution than setting hints directly in most situations is for your
application to figure out what the user’s native desktop settings are for fonts
and to do something similar for their Java application. This, in fact, is what
Swing does in some of its look and feels (Metal, Windows, and GTK in
particular); it queries desktop properties for how text is rendered by native
applications and sets RenderingHints appropriately.
Custom components which perform their own text operations do not get this Swing
behavior; the Graphics object you get in paintComponent is set up with
defaults that do not know anything about the desktop properties. So if you want
your strings to look like Swing’s strings, or to look like native applications'
text for that matter, you’ll need to mimic Swing.
I wrote a simple app that shows how to do this, by rendering one string with the
default Graphics object in paintComponent (for comparison), setting
RenderingHints appropriately to match desktop settings, and then rendering
another string with this modified Graphics object. Here is the result on my
test system (Windows Vista, with ClearType enabled):
First, note that the image may look a lot better on my display than it does here
for you; LCD text rendering is optimized to look good on the runtime display,
not whatever display you may be using to view this screenshot. But the
important thing to note here is not how good it looks here, but that the two
strings are different because the second string was drawn after setting the
appropriate desktop property hints on the Graphics object.
Here is the simple code that produced this output:
g2d.drawString("Unhinted string", 10, 20);
if (desktopHints == null) {
Toolkit tk = Toolkit.getDefaultToolkit();
desktopHints = (Map) (tk.getDesktopProperty("awt.font.desktophints"));
}
if (desktopHints != null) {
g2d.addRenderingHints(desktopHints);
}
g2d.drawString("Desktop-hinted string", 10, 40);
Here, we first check to see whether desktopHints is null; this keeps us from
re-creating it every time through paintComponent. Desktop properties will probably
not change during the lifetime of your Java application, but to be
completely correct, we should establish a listener on the desktop properties to
get notified if they do change, and to recreate desktopHints at that time; the JavaDocs on this subject tell you how you
can do this. To get the desktop properties we do a query on
awt.font.desktophints, which returns a Map of all of the properties. Then we
simply add all of those hints through a call to Graphics2D.addRenderingHints().
Now our Graphics object is set up to render text just like native applications.
For more information on desktop properties, check out the article “AWT Desktop
Properties” in the JavaDocs (the document name is DesktopProperties.html, but
it might be easier to find it by clicking on the link in the JavaDoc for
Toolkit.getDesktopProperty).
Thanks to Phil Race for helpful pointers on this one
Bookmark blog post: del.icio.us Digg DZone Furl Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment
-
Why not have this put in the JComponent.paint(Graphics) method? In thi s case:
Custom component that provides UI delegates gets this for free.
Custom component that doesn't override paint(Graphics) gets this for free
Applications running under core LAFs (Metal, Windows, GTK) get this for free
Only if the user / application code take the unrecommended route (overriding paint), the AA settings will be ignored. This would save much trouble (code changes) in migrating apps from 5.0 to 6.0, especially since already application running under 6.0 will look different (under core LAFs) than the same application running under 5.0. In most cases, real applications do have custom controls which will stick like a sore thumb (having non-AA text).
Posted by: kirillcool on January 10, 2007 at 03:35 PM
-
For me this is user friendly:
g2d.drawString("Unhinted string", 10, 20);
g2d.addRenderingHint(RenderingHints.KEY_TEXT_ANTI_ALIAS, RenderingHints.VALUE_PLATFORM_DEFAULT);
g2d.drawString("Desktop-hinted string", 10, 40);
Posted by: mikaelgrev on January 10, 2007 at 04:58 PM
-
Mikael - what if this property (RenderingHints.VALUE_PLATFORM_DEFAULT) was not available in 5.0 and added only in 6.0? To take advantage of it, you'd require 6.0 runtime which will take some time. The most user-friendly would be to require nothing at all and have Graphics configured by the RepaintManager automatically - this would be consistent (in behaviour, not in implementation) with core components under core LAFs.
Posted by: kirillcool on January 10, 2007 at 05:05 PM
-
I was talking 6.0+, for which you still have to do the whole property get/check/add thing that Chet outlined. Pre 6.0 you're right.
Posted by: mikaelgrev on January 10, 2007 at 09:27 PM
-
Chet's code works under 5.0 as well (i've integrated it into Substance after the unproductive conversation over at javalobby a few days back) - the new AA settings are not there / the map is empty (i don't remember which one).
Posted by: kirillcool on January 10, 2007 at 10:03 PM
-
It's a bit of a pain if you do want to be "complete" and detect changes in the desktop settings. I caught got caught out when I realised the PropertyChangeListener I'd registered with the Toolkit was never removed, so the Toolkit had an implicit reference to the component through the listener, so the entire dialog would never be garbage collected even after it closed! To avoid that, a custom component needs to listen for when its made un-displayable, or removed or whatever., and adding it back later if your its made displayable again etc. Plus it means every custom component is registering a listener with the toolkit, all a bit redundant.
What about a simple LookAndFeel.getDesktopHints() that returns an cached immuatable map, and we could just call that on every paint, and maybe have top level windows revalidate & repaint when the hints change?
Posted by: benloud on January 10, 2007 at 10:05 PM
-
benloud: There's an easier way. Just make your PropertyChangeListener extend WeakReference. I suggest you to take a look at the source code the class javax.swing.plaf.synth.SynthLookAndFeel$AATextListener. You'll see how it is implemented in look and feels.
Posted by: gfx on January 11, 2007 at 03:17 AM
-
i've integrated it into Substance after the unproductive conversation over at javalobby a few days back
Why was it unproductive if you were given a solution? (can't resist)
Dmitri
Posted by: trembovetski on January 11, 2007 at 01:50 PM
-
Dmitri,
If you recall the conversation, all the participants outside Sun (= API users) were not happy with the proposed solution (including myself, Karsten and Mikael). I am still very unhappy with this solution (for many reasons which were discussed at length in that thread), but it seems to be the only one that is available to both LAF vendors and component vendors. To sum up, the mere fact that the solution was given (as in "you don't have any say in this matter and now go do as you're told") doesn't mean that the conversation was productive. Far from it.
Posted by: kirillcool on January 11, 2007 at 03:06 PM
-
"Why not have this put in the JComponent.paint(Graphics) method?"
Well, as much as I'd love to have AA enabled by default in all Swing components, this would break any existing piece of code relying on non-AA drawing in paintComponent().
Posted by: gfx on January 11, 2007 at 05:55 PM
-
I don't understand the point of all this - under JDK1.5 the default value for the key
RenderingHint.KEY_TEXT_ANTIALIASING
is
RenderingHint.VALUE_TEXT_ANTIALIAS_DEFAULT
i.e. it is already set to platform default by the time the paint() method is called.
Posted by: grandinj on January 12, 2007 at 12:35 AM
-
One of the problems with Java programs on Windows XP and later is that Java does not (did not?) support ClearType. This situation that is described, "Setting text anti-aliasing hints can be confusing, especially with new RenderingHints added in Java SE 6 for LCD text. What text quality do you want? What is appropriate for the application? What is appropriate for the platform? What does your user prefer?" is a vast overcomplication for most apps.
If Windows is using ClearType, Java should use ClearType. If Windows is not using ClearType, then provide options that fit the monitor type. Obviously for Linux and Mac, similar conventions should be followed. Keep it simple and make the 80% case painless.
The lack of quality text drawing in Java is one reason Eclipse is popular on Windows (and why many people don't like the look of Java apps in general). IDEA and Netbeans look blurry by comparison.
Lastly, is there a way in the new "Java 6 N" to use ClearType??
Posted by: simple_mind on December 19, 2007 at 05:29 PM
|