Bug 102374

Summary: [Cairo, FreeType] Incorrect baseline positioning & line spacing due to rounding
Product: WebKit Reporter: Dominik Röttsches (drott) <d-r>
Component: WebKit EFLAssignee: Dominik Röttsches (drott) <d-r>
Status: RESOLVED INVALID    
Severity: Normal CC: alex, behdad, eae, gustavo, gyuyoung.kim, leviw, lucas.de.marchi, mrobinson, ostap73, rakuco, sw0524.lee, wl, zan, zimmermann
Priority: P2    
Version: 528+ (Nightly build)   
Hardware: Unspecified   
OS: Unspecified   
See Also: https://bugs.webkit.org/show_bug.cgi?id=176351
Bug Depends on:    
Bug Blocks: 103740, 89458    
Attachments:
Description Flags
Patch
none
Patch
none
Stacktrace leading to incorrect rounding none

Description Dominik Röttsches (drott) 2012-11-15 05:53:42 PST
When enabling subpixel layout on EFL, many fast/css/* tests have enclosed elements positioned 1 pixel too high.

Example:

--- /fast/dominik/dev/WebKitGit_EFL/lt_sub/fast/css/background-image-with-baseurl-expected.txt
+++ /fast/dominik/dev/WebKitGit_EFL/lt_sub/fast/css/background-image-with-baseurl-actual.txt
@@ -3,5 +3,5 @@
 layer at (0,0) size 800x600
   RenderBlock {HTML} at (0,0) size 800x600
     RenderBody {BODY} at (8,8) size 784x584
-      RenderText {#text} at (0,0) size 196x19
-        text run at (0,0) width 196: "red squares background image."
+      RenderText {#text} at (0,-1) size 196x19
+        text run at (0,-1) width 196: "red squares background image."

Mac & Chromium subpixel-enabled expectations have:

layer at (0,0) size 800x600
  RenderView at (0,0) size 800x600
layer at (0,0) size 800x600
  RenderBlock {HTML} at (0,0) size 800x600
    RenderBody {BODY} at (8,8) size 784x584
      RenderText {#text} at (0,0) size 196x18
        text run at (0,0) width 196: "red squares background image."

Levi, Emil, any suggestions what kind of platform-specific metrics input to the line layout calculations could lead to such an offset?
Comment 1 Dominik Röttsches (drott) 2012-11-19 02:22:30 PST
In 
void SimpleFontData::platformInit()

    float ascent = narrowPrecisionToFloat(font_extents.ascent);
    float descent = narrowPrecisionToFloat(font_extents.descent);
    float lineGap = narrowPrecisionToFloat(font_extents.height - font_extents.ascent - font_extents.descent);

lineGap results in -1. That seems wrong, since the cairo documentation days:

"double height;

the recommended vertical distance between baselines when setting consecutive lines of text with the font. This is greater than ascent+descent by a quantity known as the line spacing or external leading. When space is at a premium, most fonts can be set with only a distance of ascent+descent between lines."
Comment 2 Dominik Röttsches (drott) 2012-11-19 06:54:27 PST
This is originally caused by freetype rounding when "GRID_FIT_METRICS" is on:

#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 ) );

    metrics->max_advance = FT_PIX_ROUND( FT_MulFix( face->max_advance_width,
                                                    metrics->x_scale ) );
#else /* !GRID_FIT_METRICS */

Here, the CEIL + FLOOR of ascender and descender results in a non-uniform scaling - making the sum of ascender and descender larger than the height :-/
Comment 3 Dominik Röttsches (drott) 2012-11-20 04:56:01 PST
Turns out, cairo is only using the Freetype clamping path if cairo_font_options_set_hint_metrics is set to CAIRO_HINT_METRICS_DEFAULT or CAIRO_HINT_METRICS_ON, see http://cgit.freedesktop.org/cairo/tree/src/cairo-ft-font.c?id=1.12.4#n1888

WebCore's Cairo backend does not modify the DEFAULT setting, which results in always receiving integer-clamped values SimpleFontData::platformInit().

As mentioned, the clamping in FreeType can lead to incorrect, negative line gap values which are exposed when enabling subpixel layout. 

Since WebCore maintains the font metrics in floats, there's no reason we should clamp early, allowing such bugs. I suggest to disable cairo's metrics hinting and receive correct, proportionally scaled values in SimpleFontDataFreetype.cpp.
Comment 4 Dominik Röttsches (drott) 2012-11-20 07:13:21 PST
Created attachment 175215 [details]
Patch
Comment 5 Dominik Röttsches (drott) 2012-11-20 07:14:24 PST
If we wanted to keep the old letter-spacing behavior, we can round as follows:

--- a/Source/WebCore/platform/graphics/freetype/SimpleFontDataFreeType.cpp
+++ b/Source/WebCore/platform/graphics/freetype/SimpleFontDataFreeType.cpp
@@ -146,7 +146,7 @@ float SimpleFontData::platformWidthForGlyph(Glyph glyph) const
 
     float w = (float)m_spaceWidth;
     if (cairo_scaled_font_status(m_platformData.scaledFont()) == CAIRO_STATUS_SUCCESS && extents.x_advance)
-        w = (float)extents.x_advance;
+        w = (float)lroundf(extents.x_advance);
 
     return w;    
 }
Comment 6 Dominik Röttsches (drott) 2012-11-20 07:19:21 PST
CC'ing GTK+ people with the hope of supporting this change, agreeing on the big rebaseline that this would require. 

On EFL, we'd like to do this at the same time with enabling subpixel-layout. How do the GTK people feel about doing the same?
Comment 7 Martin Robinson 2012-11-20 08:29:02 PST
We did something like this once and then undid it, which was terribly labor intensive. If this is necessary perhaps we can only do it when subpixel layout is turned on.
Comment 8 Dominik Röttsches (drott) 2012-11-20 08:47:32 PST
(In reply to comment #7)
> We did something like this once and then undid it, which was terribly labor intensive. If this is necessary perhaps we can only do it when subpixel layout is turned on.

Yes, I would like to land this and activate subpixel on EFL hopefully this week - if you think the patch is okay?

Ideally without a platform or subpixel specific ifdef. That's why i am asking whether you would be up for  activating subpixel now as well. Mac has recently switched.
Comment 9 Martin Robinson 2012-11-20 09:16:43 PST
(In reply to comment #8)

> Ideally without a platform or subpixel specific ifdef. That's why i am asking whether you would be up for  activating subpixel now as well. Mac has recently switched.

I think an #ifdef makes sense, as it will allow us to switch to subpixel positioning for the right reason (it's ready and we've tested it). We're in the middle of a development cycle and spending a lot of time updating baselines for a feature we may disable later is a bit risky. Since we have a responsibility to ship stable software on a schedule that we cannot control entirely, we need to be pretty cautious.

Can you elaborate on the problems that you think an #ifdef poses. Perhaps there is some middle ground here.
Comment 10 Dominik Röttsches (drott) 2012-11-20 10:44:58 PST
(In reply to comment #9)
> (In reply to comment #8)
> 
> > Ideally without a platform or subpixel specific ifdef. That's why i am asking whether you would be up for  activating subpixel now as well. Mac has recently switched.

> Can you elaborate on the problems that you think an #ifdef poses. Perhaps there is some middle ground here.

Well, as in this case there would not be a technical reason for the #ifdef since we're sharing the backends, I was hesitant to put one in the patch. Of course, it's possible, with a comment explaining why.

> We did something like this once and then undid it, which was terribly labor intensive.

Can you tell a bit more how similar this attempt was? I am positive that there is actually a bug here. I'd like to know a bit more why you reverted a similar change.

Gyuyoung, what do you think about the letter spacing change implication?
Comment 11 Martin Robinson 2012-11-26 09:08:06 PST
(In reply to comment #10)

> > Can you elaborate on the problems that you think an #ifdef poses. Perhaps there is some middle ground here.

> Well, as in this case there would not be a technical reason for the #ifdef since we're sharing the backends, I was hesitant to put one in the patch. Of course, it's possible, with a comment explaining why.

I think a comment and an #ifdef are a bit safer. We can always remove an #ifdef, but changing hundreds of baselines is a bigger task.

> Can you tell a bit more how similar this attempt was? I am positive that there is actually a bug here. I'd like to know a bit more why you reverted a similar change.

Wildfox made the original change, I believe. I tried searching for it but the relevant patches continue to elude me. If I recall correctly it had to do with enabling and disabling cairo_font_options_set_hint_metrics and the reason we reverted it is that it seemed to introduce flakiness into many tests.
Comment 12 Martin Robinson 2012-11-26 10:28:26 PST
Comment on attachment 175215 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=175215&action=review

> Source/WebCore/platform/graphics/freetype/FontPlatformDataFreeType.cpp:115
> +    cairo_font_options_t* defaultFontOptions = cairo_font_options_create();
> +    // Set to off in order to receive non-rounded floating point values for font metrics in SimpleFontDataFreeType instead
> +    // of integer-clamped values that may cause negative values for line-gap.
> +    cairo_font_options_set_hint_metrics(defaultFontOptions, CAIRO_HINT_METRICS_OFF);

If we turn this off globally we probably want to force it off for the GDK font options as well above.
Comment 13 Dominik Röttsches (drott) 2012-11-27 01:48:33 PST
Martin, thanks for the details, I think I found the changes you refer to:

> Wildfox made the original change, I believe. 

http://trac.webkit.org/changeset/101343

> [...] If I recall correctly it had to do with enabling and disabling cairo_font_options_set_hint_metrics and the reason we reverted it is that it seemed to introduce flakiness into many tests.

And the revert came in two steps,
http://trac.webkit.org/changeset/102748 +
http://trac.webkit.org/changeset/114796

There's some magic there in the ChangeLog. "Re-enable Cairo metrics hinting, which seems to ensure consistent results in reference and pixel test results."

From the other way 'round: Can you recall how the results were inconsistent? Were they inconsistent between runs on the same machine? Or were they inconsistent between different hosts? In the latter case, the reason might be in different fontconfig and gnome-settings-daemon configurations for hinting?

I'd like to really resolve this issue and later hopefully land a patch that doesn't have the word "seems" in the Changelog :-).
Comment 14 Martin Robinson 2012-11-27 07:58:17 PST
(In reply to comment #13)

> From the other way 'round: Can you recall how the results were inconsistent? Were they inconsistent between runs on the same machine? Or were they inconsistent between different hosts? In the latter case, the reason might be in different fontconfig and gnome-settings-daemon configurations for hinting?

Tests started to flake after the change. If I recall correctly we had reftest failures that we didn't have before.

> I'd like to really resolve this issue and later hopefully land a patch that doesn't have the word "seems" in the Changelog :-).

Well, that's mostly just my poor style of writing, but I fully support this work. :)
Comment 15 Martin Robinson 2012-11-28 11:10:39 PST
I've been doing a little research [1] and it seems that Behdad suggests that metrics hinting should be on if subpixel layout is disabled.

"If you don’t have subpixel text positioning (ie. your graphics system knows only how to rasterize one image for a glyph and blit that onto the screen), then you really want to enable metrics hinting."

This is the first bullet point in the "Constraints" section.

1. https://docs.google.com/document/d/1wpzgGMqXgit6FBVaO76epnnFC_rQPdVKswrDQWyqO1M
Comment 16 Martin Robinson 2012-11-28 11:11:56 PST
CCing Behdad, who probably has some insightful input.
Comment 18 Behdad Esfahbod 2012-11-29 04:53:43 PST
Hi,

Martin is correct: cairo does not know how to render subpixel positioned text, and as such it will be a disaster IMO to turn metrics hinting off.  There is an example in the document of the kind of wrong effects one would get doing that.  What I suggest is to do something different only for getting the line metrics perhaps.  For the actual measurement and layout of glyphs, don't disable metrics hinting with cairo.
Comment 19 Behdad Esfahbod 2012-11-29 04:56:45 PST
Another reason why you can't disable metrics hinting is that you are not disabling hinting.  Same document explains why that is a problem also.  Second bullet in the "constraints" section.
Comment 20 Dominik Röttsches (drott) 2012-11-29 05:15:46 PST
(In reply to comment #18)
> What I suggest is to do something different only for getting the line metrics perhaps.  For the actual measurement and layout of glyphs, don't disable metrics hinting with cairo.

Thanks, Behdad. I'll see what I can do to just get the right height info. Maybe I can temporarily disable metrics hinting just to retrieve these.
Comment 21 Dominik Röttsches (drott) 2012-11-29 07:44:45 PST
Created attachment 176736 [details]
Patch
Comment 22 Dominik Röttsches (drott) 2012-11-29 07:46:37 PST
This new patch works for fixing the line height, line gap issue and leaves  hinting (and metrics hinting) enabled. This way, we give non-rounded height information to WebCore for use in layout. Any other comments on this Behdad? What do you think, Martin?

Thanks for your help.
Comment 23 Dominik Röttsches (drott) 2012-11-30 07:14:52 PST
Btw, this is also fixing bug 103740.
Comment 24 Behdad Esfahbod 2012-12-04 12:12:28 PST
LGTM.  I wonder whether there's something to be done in FreeType itself.  Would help knowing what Windows does in this case.  If you can get those numbers, I can do the upstreaming work in FreeType.
Comment 25 Dominik Röttsches (drott) 2012-12-04 12:55:00 PST
(In reply to comment #24)

Behdad, thanks for your feedback, much appreciated. 

> LGTM.  I wonder whether there's something to be done in FreeType itself.  

You mean, with regards to the issue I mentioned in comment 2?

> Would help knowing what Windows does in this case.  If you can get those numbers, I can do the upstreaming work in FreeType.

What numbers would you need? Just the line height, ascent & descent of the same font? And would just retrieving the font details from a small test app be enough, or do you mean, dumping this from the same execution path in Safari/Win?
Comment 26 Behdad Esfahbod 2012-12-04 14:35:20 PST
Thanks.  I'm mostly interested to know what would Windows APIs returns for ascent / descent / linegap for that font at that size . Because I'm sure FreeType / others would insist that the lineheight should match what Windows returns...
Comment 27 Kenneth Rohde Christiansen 2012-12-05 03:07:11 PST
Comment on attachment 176736 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=176736&action=review

> Source/WebCore/ChangeLog:12
> +        than the line height. To work around this without globally disabling font metrics
> +        hinting, I am temporarily creating a new cairo scaled font with metrics hinting
> +        off and retrieving the height values from this one.

isnt metrics hinting bad anyway for scaling? like it might affect layout at different scales.
Comment 28 Dominik Röttsches (drott) 2012-12-05 03:16:48 PST
(In reply to comment #27)

> isnt metrics hinting bad anyway for scaling? like it might affect layout at different scales.

See comment 15, metrics hinting is useful if you don't have subpixel positioning. When scaling, a new font with the right font size is instantiated.
Comment 29 Dominik Röttsches (drott) 2012-12-05 05:40:24 PST
(In reply to comment #26)
> Thanks.  I'm mostly interested to know what would Windows APIs returns for ascent / descent / linegap for that font at that size . Because I'm sure FreeType / others would insist that the lineheight should match what Windows returns...

So, using a quick and dirty C# app [1], I got this:
"Liberation Sans, 16 (fontSize: 16) - lineSpacingPixel: 18.39844, ascentPixel 14.48438, descent 3.390625, ascent+descent: 17.875"

Very very close to what I get from Cairo/Freetype (after my temporary-hinting-off patch):
ascent: 14.484375
descent: 3.390625
height: 18.3984375

[1] https://gist.github.com/4215546

That means, we're good to proceed with this patch, right?
Comment 30 Behdad Esfahbod 2012-12-07 13:49:04 PST
(In reply to comment #29)
> (In reply to comment #26)
> > Thanks.  I'm mostly interested to know what would Windows APIs returns for ascent / descent / linegap for that font at that size . Because I'm sure FreeType / others would insist that the lineheight should match what Windows returns...
> 
> So, using a quick and dirty C# app [1], I got this:
> "Liberation Sans, 16 (fontSize: 16) - lineSpacingPixel: 18.39844, ascentPixel 14.48438, descent 3.390625, ascent+descent: 17.875"
> 
> Very very close to what I get from Cairo/Freetype (after my temporary-hinting-off patch):
> ascent: 14.484375
> descent: 3.390625
> height: 18.3984375
> 
> [1] https://gist.github.com/4215546
> 
> That means, we're good to proceed with this patch, right?

Thanks for testing.  I think the patch is fine to go in.  That said, for Windows. I was more interested in the hinted integer metrics, since that's where the current bug in FreeType is.  If you can get those number I will followup with FreeType upstream to fix it properly.  I don't have access to Windows myself.  Thanks again!
Comment 31 Dominik Röttsches (drott) 2012-12-11 06:39:57 PST
(In reply to comment #30)
> Thanks for testing.  I think the patch is fine to go in.  That said, for Windows. I was more interested in the hinted integer metrics, since that's where the current bug in FreeType is.  If you can get those number I will followup with FreeType upstream to fix it properly.  I don't have access to Windows myself.  Thanks again!

More lovely Win programming [1] using GDI GetTextMetrics on a device context handle optained from .net gives me (Liberation Sans, 16 pixel):
ascent: 14
descent: 3
height: 17

whereas the freetype code here gives me:
ascent: 15
descent: 4
height: 18

Good luck fixing this on Freetype.

[1] https://gist.github.com/d4169b1068c203432846
Comment 32 Behdad Esfahbod 2012-12-11 06:58:03 PST
Thanks Dominik.  I'll see what I can do.
Comment 33 Viatcheslav Ostapenko 2012-12-11 18:07:06 PST
(In reply to comment #31)
> (In reply to comment #30)
> > Thanks for testing.  I think the patch is fine to go in.  That said, for Windows. I was more interested in the hinted integer metrics, since that's where the current bug in FreeType is.  If you can get those number I will followup with FreeType upstream to fix it properly.  I don't have access to Windows myself.  Thanks again!
> 
> More lovely Win programming [1] using GDI GetTextMetrics on a device context handle optained from .net gives me (Liberation Sans, 16 pixel):
> ascent: 14
> descent: 3
> height: 17
> 
> whereas the freetype code here gives me:
> ascent: 15
> descent: 4
> height: 18
> 
> Good luck fixing this on Freetype.
> 
> [1] https://gist.github.com/d4169b1068c203432846

Win GDI uses simple truncate instead of rounding?
Comment 34 Dominik Röttsches (drott) 2012-12-12 02:46:15 PST
(In reply to comment #33)
> Win GDI uses simple truncate instead of rounding?

It looks as if it's flooring all values, yes. But let's say I haven't looked at the GDI code :-). As per comment 2, at least a combination of ceiling some values and rounding others is an unholy combination.
Comment 35 Viatcheslav Ostapenko 2012-12-30 22:26:36 PST
*** Bug 104573 has been marked as a duplicate of this bug. ***
Comment 36 Dominik Röttsches (drott) 2013-01-10 06:27:51 PST
(In reply to comment #32)
> Thanks Dominik.  I'll see what I can do.

Any update on the FreeType front here, Behdad? Thanks.
Comment 37 Behdad Esfahbod 2013-01-10 10:55:16 PST
I wasn't sure what to suggest to FreeType, so I put it off.  Went ahead and sent a mail right now bringing it to others' attention.  We'll see what people have to offer.  It doesn't show up in the list archives yet, but it will be here:

  http://lists.nongnu.org/archive/html/freetype-devel/2013-01/index.html
Comment 38 Werner Lemberg 2013-01-11 05:56:28 PST
There is definitely a bug in FreeType.  Thanks for the report.  It seems that the rounded ascender and descender values must be added to get the height.  Instead, we currently add the ascender and descender values, then do rounding.

For verifying this assumption I ask you to run your program from comment #31 for a whole range of sizes, say, 8ppem to 30ppem (I don't use Windows :-).  Please tell me exactly the used version (and size) of LiberationSans-Regular.otf to avoid any ambiguities.  Maybe it also helps to repeat this with other MS core fonts, just to be sure.

Additionally, I would like to know which FreeType function Webkit was calling to get the incorrect results (in comment #31).  Theoretically, this shouldn't happen at all because there is already code in `sfobjs.c' which uses ROUND only (as requested by TrueType), overriding the CEIL and FLOOR results FreeType is using for other font formats.
Comment 39 Dominik Röttsches (drott) 2013-01-11 09:44:27 PST
(In reply to comment #38)

Thanks for taking a look, Werner. I'll try to get you the data you requested.
Comment 40 Dominik Röttsches (drott) 2013-01-14 02:33:24 PST
I was relying on our jhbuild freetype version being somewhat up to date - turns out this was an incorrect assumption. I should have done the bisecting earlier. In any event, freetype commit b0962ac34e660 
"[truetype] Fix metrics on size request for scalable fonts." by Steven Chu changes my initial failure example to the better looking:

@@ -3,5 +3,5 @@
 layer at (0,0) size 800x600
   RenderBlock {HTML} at (0,0) size 800x600
     RenderBody {BODY} at (8,8) size 784x584
-      RenderText {#text} at (0,0) size 196x19
+      RenderText {#text} at (0,0) size 196x17
         text run at (0,0) width 196: "red squares background image."

So, we're actually hitting the same problem that Steven has been describing in
http://lists.gnu.org/archive/html/freetype-devel/2011-07/msg00029.html

He's commenting there that 
FT_Request_Size - actually computes incorrect values for Ahem - similar for my case of Liberation Sans here. If for TrueType - the ascent and descent metrics are reset afterwards anyway - why are we calling it for scalable fonts?

Werner, regarding your original question: How is the incorrect rounding code reached? It is reached from FT_Request_Size -> tt_size_request -> ft_recompute_scaled_metrics (for details, I attached the stacktrace with freetype 2.4.11). But then, after Steven's patch - as far as I understand his patch - these incorrectly rounded values are discarded and replaced.

Would you still need the values from Windows after this finding?
Comment 41 Dominik Röttsches (drott) 2013-01-14 02:33:54 PST
Created attachment 182532 [details]
Stacktrace leading to incorrect rounding
Comment 42 Werner Lemberg 2013-01-14 02:54:45 PST
Yes, please, just to be sure.

And sorry, I don't fully understand the rest of your last comment: Is there still a problem left?
Comment 43 Dominik Röttsches (drott) 2013-01-14 03:02:47 PST
(In reply to comment #42)
> Yes, please, just to be sure.
> 
> And sorry, I don't fully understand the rest of your last comment: Is there still a problem left?

Well, it seems that the (incorrect) rounding code in question is still run, but then these preliminary values are replaced - do I understand that right? (Same as in Steven's analysis: http://lists.gnu.org/archive/html/freetype-devel/2011-07/msg00029.html )

[...]
"    FT_Request_Metrics( size->face, req ); /* Calculates new metrics,
incorrect for Ahem */

    if ( FT_IS_SCALABLE( size->face ) ) /* Ahem is scalable */
      error = tt_size_reset( ttsize ); /* Calculates proper metrics
stored in ttsize */
[...]

Shouldn't this code be changed so that FT_Request_Metrics is not called for SCALABLE fonts in order to avoid unnecessary computation?
Comment 44 Dominik Röttsches (drott) 2013-01-15 08:50:31 PST
Comment on attachment 176736 [details]
Patch

Obsoleting patch - solved in Freetype >= 2.4.6, see bug 106774.
Comment 45 Dominik Röttsches (drott) 2013-01-22 03:39:09 PST
Werner informed me that there has been indeed a fix/change in FreeType after our discussion here:
http://git.savannah.gnu.org/cgit/freetype/freetype2.git/commit/?id=e0469372be3870a5ad60b2c4586e9c281357bd28