Bug 60317

Summary: Text is scaled in a stair-step pattern
Product: WebKit Reporter: Levi Weintraub <leviw>
Component: TextAssignee: Nobody <webkit-unassigned>
Status: RESOLVED FIXED    
Severity: Normal CC: ap, eae, eric, gregsimon, hyatt, komoroske, krit, leviw, mitz, thorton, zimmermann
Priority: P2    
Version: 528+ (Nightly build)   
Hardware: Unspecified   
OS: Unspecified   
Bug Depends on:    
Bug Blocks: 60318    
Attachments:
Description Flags
Test Case
none
demo of SVG text scaling smoothly using transforms
none
Font-size doesn't solve this problem either
none
Patch
none
Patch
none
Patch none

Description Levi Weintraub 2011-05-05 17:04:16 PDT
Applying css zoom or svg scale to text will snap by font size and not scale evenly. See attached test case.

http://crbug.com/80578
Comment 1 Levi Weintraub 2011-05-05 17:04:43 PDT
Created attachment 92504 [details]
Test Case
Comment 2 Levi Weintraub 2011-05-06 11:15:20 PDT
Currently the code explicitly avoids scaling glyphs, which prevents us from "smoothly" scaling up text. Hyatt knows this code best and understands the rationale behind this decision, I'd love some background and recommendations on how we can maintain our text rendering quality and attain this increased granularity!
Comment 3 Levi Weintraub 2011-05-17 16:44:24 PDT
I'm having some trouble here. Even feeding floats into the CG font system doesn't make font measuring scale smoothly.

Asking the font system for a font size of 8 works fine, but a font size of 9-12 (pixels) all appear identical. Changing the WebKit font system to request fonts by float doesn't make a difference.

Finally, because I can't tell the *actual* font size being returned, I can't adjust the graphics context to compensate by the proper amount and scale the glyphs.

This was all done on OSX. Anyone out there who knows CGFonts well that can help? I know entering glyph-scaling land isn't ideal, but certainly this behavior in SVG particularly is very bad.
Comment 4 Eric Seidel (no email) 2011-05-17 16:59:56 PDT
SVG scales glyphs by applying a transform to them. :)
Comment 5 Levi Weintraub 2011-05-17 18:46:47 PDT
(In reply to comment #4)
> SVG scales glyphs by applying a transform to them. :)

I believe that is false :(

If it were true, I think we'd only see this issue in CSS but no SVG. You can see in SVGInlineTextBox::paintTextWithShadows it rescales the graphics context by the inverse of the scalingFactor, then depends on the Font to draw at the proper scale. Unfortunately, there's no feedback between the font the font system returns and the one requested. Hence, requesting a 13 and 14pt font will return the same thing, much like requesting a 6pt font scaled to 1.1 and 1.2x size.
Comment 6 Eric Seidel (no email) 2011-05-17 18:52:31 PDT
I don't mean with zoom.  I mean with transforms.

<svg>
<text id="text" transform="scale(4)" style="font: Times 24px">HELLO</text>
</svg>

I suspect if we added an <animateTransform target="text" from="scale(1)" to="scale(4)" duration="4s" /> in there, it would be smooth.
Comment 7 Levi Weintraub 2011-05-17 18:59:04 PDT
(In reply to comment #6)
> I don't mean with zoom.  I mean with transforms.
> 
> <svg>
> <text id="text" transform="scale(4)" style="font: Times 24px">HELLO</text>
> </svg>
> 
> I suspect if we added an <animateTransform target="text" from="scale(1)" to="scale(4)" duration="4s" /> in there, it would be smooth.

But you can see in the attached test case that just using scale values doesn't scale smoothly. In fact, as I've seen, it explicitly relies on the font system to do this for it, and the font system doesn't work as a developer would expect.
Comment 8 Eric Seidel (no email) 2011-05-17 19:22:52 PDT
Created attachment 93858 [details]
demo of SVG text scaling smoothly using transforms

Note that due to bugs in WebCore, SVG animation only works when SVG is parsed as XML (and gets an SVGDocument as root), animation does not work when SVG is used in HTML5.
Comment 9 Nikolas Zimmermann 2011-05-18 08:28:56 PDT
Some background:

WebCore/platform/graphics/mac/FontCacheMac.mm:

FontPlatformData* FontCache::createFontPlatformData(const FontDescription& fontDescription, const AtomicString& family)
{
    NSFontTraitMask traits = fontDescription.italic() ? NSFontItalicTrait : 0;
    NSInteger weight = toAppKitFontWeight(fontDescription.weight());
    float size = fontDescription.computedPixelSize();

    NSFont *nsFont = [WebFontCache fontWithFamily:family traits:traits weight:weight size:size];
..

The font is always requested with the "computedPixelSize" font size, not the real floating-point font size.
The computedPixelSize is an integer value, not a float as might be suggested by reading the code.

I ran across this problem a while ago as well.
Levi, does that help?
Comment 10 Dirk Schulze 2011-05-18 10:00:57 PDT
(In reply to comment #8)
> Created an attachment (id=93858) [details]
> demo of SVG text scaling smoothly using transforms
> 
> Note that due to bugs in WebCore, SVG animation only works when SVG is parsed as XML (and gets an SVGDocument as root), animation does not work when SVG is used in HTML5.

It definitely worked in october last year. Are you sure about that Eric? I made a presentation by using the HTML5rocks slides and embedded SVG elements with SMIL animations. It worked!
Comment 11 Levi Weintraub 2011-05-18 10:34:35 PDT
Created attachment 93932 [details]
Font-size doesn't solve this problem either

(In reply to comment #9)
> I ran across this problem a while ago as well.
> Levi, does that help?

Thanks for taking a look!

I actually tried piping floating point sizes down to CGFont (which takes a float for its size) and found that it makes no difference. Requesting fonts of different pixel sizes doesn't even always result in different-sized fonts, which I believe illustrates this point quite well. I wish there were better feedback from the font system as to the *actual* size of the returned font, and how it differs from the specified size.
Comment 12 Levi Weintraub 2011-05-18 15:00:14 PDT
I managed to get the attached test case working properly by scaling the specified Font and not requesting the closest matching one. I ran into a snag when running the regression tests with this change because various SVG tests pick extremely small fonts and scale them up by 50x or more. The font engine throws out fonts that are too small to render, and we end up not displaying anything...

I feel like I'm back to square one. Ideally we could pick the closest font and scale it by the delta between what was provided and what was requested, but I still don't know how to figure out what the *actual* size CGFont we were given is! Any ideas?
Comment 13 Nikolas Zimmermann 2011-05-19 00:47:43 PDT
(In reply to comment #12)
> I managed to get the attached test case working properly by scaling the specified Font and not requesting the closest matching one. I ran into a snag when running the regression tests with this change because various SVG tests pick extremely small fonts and scale them up by 50x or more. The font engine throws out fonts that are too small to render, and we end up not displaying anything...
> 
> I feel like I'm back to square one. Ideally we could pick the closest font and scale it by the delta between what was provided and what was requested, but I still don't know how to figure out what the *actual* size CGFont we were given is! Any ideas?

Hmm, I'm not sure I follow. For SVG we recently changed the way we request fonts. Before we always used the actual specified font size:
<svg viewBox="0 0 5 5">
<text font-size="1" y="1">A</text>
</svg>

So we requested a font with size 1, and then just rendered it on the context, praying for the graphics engine to handle it correctly. What we do now, is calculate the scaling factor, to get the real font size that will appear on the screen (eg. here sth. like 40, if the width/height of the browser window is large enough). Then we scale the current context by the inverse scaling factor, render the text, and scale back.
This way we get antialiasing and hinting working correctly in arbitary transformed documents.

Okay, I think I should be more concrete:
Say we have a document with width=1000, height=1000. Then the <svg> from the example above is scaled to fit in that document. The text appears at y=200, and has a font size of 200.

When painting, the RenderSVGRoot object scales the content by 200, then the font is rendered using font size 200, to do that we scale the context by 1/200, draw the font with size=200, and scale again.
Obviously only text needs this trick, any Path can be arbitary transformed, w/o loosing precision....

I hope that explains what SVG is doing ..... though the bug you've found is still valid, but has nothing to do with that part of the SVG text handling.
Comment 14 Levi Weintraub 2011-05-19 11:28:52 PDT
(In reply to comment #13)
> I hope that explains what SVG is doing ..... though the bug you've found is still valid, but has nothing to do with that part of the SVG text handling.

Thanks again for the great description. What SVG is doing now makes perfect sense, and ideally we'd just tweak the current algorithm. Where we're currently scaling the graphics context by 1 / scalingFactor (SVGInlineTextBox::paintTextWithShadows and elsewhere), I think we should also be scaling it by the difference between the requested size and the actual. There's just no way to figure out what that delta is from the current font system... at least that I can find.
Comment 15 Eric Seidel (no email) 2011-05-19 11:32:30 PDT
(In reply to comment #14)
> (In reply to comment #13)
> > I hope that explains what SVG is doing ..... though the bug you've found is still valid, but has nothing to do with that part of the SVG text handling.
> 
> Thanks again for the great description. What SVG is doing now makes perfect sense, and ideally we'd just tweak the current algorithm. Where we're currently scaling the graphics context by 1 / scalingFactor (SVGInlineTextBox::paintTextWithShadows and elsewhere), I think we should also be scaling it by the difference between the requested size and the actual. There's just no way to figure out what that delta is from the current font system... at least that I can find.

I'm sure there is CoreGraphics/CoreText SPI which Apple could add to WKSystemInterface if this ends up being the way to go.

Can your solution be implemented for other graphics libraries first?
Comment 16 Levi Weintraub 2011-05-31 16:35:46 PDT
Created attachment 95508 [details]
Patch
Comment 17 Nikolas Zimmermann 2011-05-31 23:33:00 PDT
Comment on attachment 95508 [details]
Patch

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

Good catch Levi, can you tweak the ChangeLog? Then I'll r+.

> LayoutTests/ChangeLog:8
> +        Scaling the specified font in SVG when font-rendering: geometricPrecision is specified.

I'm not gettin' this sentence? :-) Don't you want to say:
"Stop scaling the specified font to the actual on-screen value, when font-rendering: geometricPrecision is specified", something like that.
Comment 18 Eric Seidel (no email) 2011-05-31 23:41:59 PDT
Comment on attachment 95508 [details]
Patch

Might be useful to have your test case compare geometricPrecision cases vs. not.
Comment 19 Eric Seidel (no email) 2011-05-31 23:44:30 PDT
http://www.w3.org/1999/07/06/WD-SVG-19990706/render.html
geometric-precision
Indicates that the user agent should emphasize geometric precision over legibility and rendering speed. This option will usually cause the user agent to suspend the use of hinting so that glyph outlines are drawn with comparable geometric precision to the rendering of path data.
Comment 20 Levi Weintraub 2011-06-01 09:45:12 PDT
Created attachment 95614 [details]
Patch
Comment 21 Eric Seidel (no email) 2011-06-01 09:54:41 PDT
Comment on attachment 95614 [details]
Patch

Ok, so now you'll want to avoid scrollbars to avoid needing paltform results.  You'll also want to use ahem. :)
Comment 22 Levi Weintraub 2011-06-01 10:41:17 PDT
(In reply to comment #21)
> (From update of attachment 95614 [details])
> Ok, so now you'll want to avoid scrollbars to avoid needing paltform results.  You'll also want to use ahem. :)

D'oh! I got that part right the first time at least! Thanks, I'll fix it :)
Comment 23 Levi Weintraub 2011-06-01 11:09:37 PDT
(In reply to comment #21)
> (From update of attachment 95614 [details])
> Ok, so now you'll want to avoid scrollbars to avoid needing paltform results.  You'll also want to use ahem. :)

Scrollbars are easy, but Ahem makes this test much harder to follow...
Comment 24 Levi Weintraub 2011-06-01 11:10:20 PDT
Created attachment 95629 [details]
Patch
Comment 25 Eric Seidel (no email) 2011-06-01 12:28:49 PDT
Comment on attachment 95629 [details]
Patch

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

I'm not sure why ahem makes this any harder?  I suspect due to platform differences you'll need multiple image results, which is sad.  There may be a set of safe fonts to use, I just know that ahem is one such safe font.

> LayoutTests/svg/text/scaling-font-with-geometric-precision.html:42
> +<text class="geometric" transform="scale(1.2)" dy="15" fill="navy">iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii</text>

I would have probably generated these with javascript.  BUt it's OK as is too.
Comment 26 Levi Weintraub 2011-06-01 14:00:59 PDT
Comment on attachment 95629 [details]
Patch

Clearing flags on attachment: 95629

Committed r87846: <http://trac.webkit.org/changeset/87846>