[OpenJDK 2D-Dev] Font rendering issue
Phil Race
philip.race at oracle.com
Thu Jun 10 21:28:46 UTC 2010
I've root-caused this although its still not clear what's the ideal answer,
the simplest and safest may be Mario's proposed fix here from 5/5/2010 :
http://cr.openjdk.java.net/~neugens/100134/webrev.01/
So, yes, I think as Mario suggested it starts with something in
freetype which has a "#define GRID_FIT_METRICS" that causes [premature]
rounding of metrics to integer values :-
#ifdef GRID_FIT_METRICS
metrics->ascender = FT_PIX_CEIL( FT_MulFix( face->ascender,
metrics->y_scale ) );
metrics->descender = FT_PIX_FLOOR( FT_MulFix( face->descender,
metrics->y_scale ) );
metrics->height = FT_PIX_ROUND( FT_MulFix( face->height,
metrics->y_scale ) );
....
So
height is ROUNDED
ascent is CEIL'd
descent is FLOOR'd.
Mario's fix essentially removes this integer rounding.
The FLOOR is because descent is negative and so the apparent intent is
to ensure that the int ascent and descent are at least as great in
magnitude as the float versions. But this is at the cost of
ascent+descent == height.
Freetype extracts ascent and descent from the 'hhea' table (same
as T2K as it happens), and the values there in upem are scaled
to the 12 pt example in Mario's test program. As shown here.
Arithmetic is actually fixed point but FP values are clearer.
You can then see the effect of the conversion to int's
FIELD UPEM | FP 12pt | Integer Val
ascent | 1901 | 11.14 | 12 (CEIL'd)
descent | -483 | -2.83 | -3 (FLOOR'd)
height | 2384 | 13.97 | 14 (ROUNDed)
Note that ceil of ascent greatly increases it by almost a pixel
from its true value
If GRID_FIT_METRICS is not defined then no rounding
occurs and JDK code sees the floating point values
and it rounds in its own preferred way, and then
the results are the same as in the freetype and t2k cases.
But where does freetype get height, since the font provides
ascent, descent and linegap? The answer is its calculated
from those three :
root->ascender = face->horizontal.Ascender;
root->descender = face->horizontal.Descender;
root->height = (FT_Short)( root->ascender - root->descender +
face->horizontal.Line_Gap );
Line_Gap is not exposed freetype API, and so the leading value required
by Java API can only be derived by doing the reverse arithmetic.
However since freetype has already rounded for us, this information
is not reliable and we see the result as 14-(12+3) = -1
JDK therefore cannot use freetype (via this exact API)
to faithfully report leading.
Regardless of how the rest of this works out, this part should be fixed.
Negative leading values are (surprisingly) legal but I think either
deprecated or extremely rare. So I think it reasonable that if we
see a negative leading we ignore it (ie clamp leading to zero).
Mario's proposed fix should have the same effect as it bypasses
the rounding and we will see the same raw values as in the T2K rasteriser
Whether its done like that or as part of something more complex
depends on the relationship of the asc+dsc+ldg to height.
Clamping to zero and summing gives us hgt= 12+3+0=15, whereas freetype reports
its actually 14 because the rounding happened to be almost completely
precise whereas the CEIL(ascent) added a whole pixel.
It could be even worse. Imagine if we had in floating point that descent=2.10
Then height would be rounded to 13, but ascent+descent would still be 15.
So should we ensure height = ascent + descent + leading ?
I consider this a grey area as it depends on how you interpret
ascent and descent and their relationship to height, but freetype clearly
derived height from these but doesn't see it as necessary for them to
add up.
JDK has in practice tried to maintain this equation but freetype clearly doesn't
regard it as invariant. In fact I updated the spec for FontMetrics.getHeight()
back in JDK 1.4 under bug 4455492 and tried to implement something like
this. This led directly to Swing problems listed in 4467709, so I
ended up having to go for the tamer solution today.
So if we allow for openjdk/freetype to return the height as 14, then
I'd still expect some Swing problems, although apparently some do exist
today. Basically, concurrent with the fix, someone would have to take
on everything Roman writes about here :
http://mail.openjdk.java.net/pipermail/swing-dev/2010-May/001034.html
and be on call to evaluate and fix up all the Swing bugs that are
reported. We'd also have to be ready in case anyone complained in
general about metrics changes breaking their UI layout. This has
caused HUGE customer complaints in the past. Technically it was
their own fault, but they still complained.
I also suspect AWT would have some issues, as all the metrics
come via the 2D path.
And to be able to do this, we'd really want to bypass all that
rounding done by freetype, else we'd have to propagate height
up separately. right now we only propagate up the raw ascent,
descent and leading, since we assume that they were left
as raw floating point values.
Mario's concern about hinting isn't founded. It doesn't matter.
So I think (bit of hand waving)
1) Implement Mario's fix which will solve negative leading
2) Separately consider the larger change to
a) tighten up the height we reportywith the consequence that its common that
getAscent()+getDescent()+getLeading() > getHeight()
b) Fix up everywhere in the JDK that assumes otherwise ..
(a) and (b) ought to be as much as possible in the same commit
which means its a bigger job but still with follow-up inevitably needed.
-phil.
More information about the 2d-dev
mailing list