[cairo] Getting real linearly-scalable text
Travis Griggs
tgriggs at cincom.com
Thu May 15 23:27:15 PDT 2008
On May 14, 2008, at 5:56 PM, Behdad Esfahbod wrote:
> Hi,
>
>
> It's quite well-known by now that in cairo animation, text is always
> jagged and jittery, even with all hintings disabled. The main reason
> for that is that we currently do not support sub-pixel text
> positioning
> and round glyph positions to integers. Our workaround so far has been
> to recommend cairo_text_path();cairo_fill() instead of
> cairo_show_text(). That's far from perfect, but should do the job.
> Or
> one would think so... Apparently it doesn't. So I sat down a while
> ago
> and tried to figure out what's going on. And I did.
>
> Check the image:
>
> http://www.flickr.com/photos/behdad/2493693932/sizes/o/
>
> source code is attached. The left column is what you get from
> cairo_show_text(), the middle one is
> cairo_text_path();cairo_fill(),
> and the last column is if you take the path of the font at size 12 and
> let cairo scale the path, then fill it. The graph suggests that the
> problem with the middle column is that we are asking FreeType for
> glyph
> outline at each size, and being beat by FreeType's limited precision
> (26.6 fixed type). In fact, seems like that's contributing to our
> ugly
> show_text() results too. Needless to say, we want to achieve results
> as good as the third column.
>
> So how do fix this then? For show_text(), we want to implement
> sub-pixel glyph positioning. I suggest we do a 4x4 or 8x4 grid. Each
> glyph then will have up to 32 renderings. May also make sense to
> add a
> new metrics-hinting mode to just hint the Y metrics. That's what
> Apple
> does I guess. Anyway, to implement subpixel positioning, I suggest we
> add something like:
>
> cairo_int_status_t
> _cairo_scaled_glyph_lookup_subpixel (cairo_scaled_font_t *scaled_font,
> unsigned long index,
> cairo_fixed_t x, cairo_fixed_y,
> cairo_scaled_glyph_info_t info,
> cairo_scaled_glyph_t
> **scaled_glyph_ret);
>
> unsigned long
> _cairo_scaled_glyph_get_subpixel_index (cairo_scaled_glyph_t
> *scaled_glyph);
>
>
> Basically this will create a new cairo_scaled_glyph_t for each
> position
> on the subpixel grid. Each position on the subpixel grid also has an
> index, with index 0 mapping to pixel-aligned coordinate.
>
> Raster backends (and the fallback path) then will be changed to:
>
> - convert glyph position to fixed
> - fetch the glyph for the fixed position
> - round the fixed position and render the fetched glyph there
>
> To make it less error-prune we can make the x,y in/out such that the
> rounding is done in one place. Or better, make it take double x,y as
> input and return int x,y as output.
>
> The bigger problem though, is how to implement this in the xlib
> backend.
> Obvious solution is to make the Xrender glyph id be the combination of
> input glyph id and the subpixel index. That works great for most
> fonts
> as their glyph id is limited (to 64k), but not for user-fonts. With
> user-fonts one may use the full range of glyph ids and that's a valid
> usecase. Then the input-glyph-id + subpixel-index space is larger
> than
> the Xrender glyph space and we have to use multiple Xrender glyphsets.
> Maybe we can reserve the high byte of the Xrender glyph space for
> subpixel-index, and upon seeing input glyphs that have non-zero high
> byte, use additional glyphsets for those. That definitely works. Is
> just a bit clumsy.
>
>
> Next step is, fixing paths. Turns out we are going towards completely
> skipping FreeType's rasterizer and having cairo rasterize all
> fonts. We
> may need to fix bugs in the non-AA rasterizer to match FreeType's, but
> ignoring that fact, it's quite possible to do that currently. But!
> With the current plan to move to calling into FreeType for subpixel
> filtering, we may have some problems. Worst case it's that we
> rasterize, then convert into a FT bitmap, do filtering, then convert
> back. Not terribly bad, but also not ideal. While doing that, we may
> as well add font-backend API for subpixel filtering. Then we can use
> FreeType to do subpixel rendering of user-fonts. Yay!
>
> Ok, to paths. So, if hinting is set to none, we want to always ask
> font
> backend for glyph path at a fixed font size, and scale it in cairo.
> I'm
> not sure if we need to cache the scaled version. If not, then we can
> cache the path on the font_face instead of the scaled_font. That
> would
> be neat indeed, but needs quite some code refactoring. What should
> the
> fixed size we pass to FreeType be? It should be something to maximize
> precision. We can actually use some really large size (like 1024),
> that
> makes FreeType do all its math in 16.16 instead of 26.6. And we'll
> then
> "scale it down" to 256, such that cairo essentially holds it as 16.16
> instead of 24.8 too. That gives us more than enough precision. The
> sharing should happen based on whether hinting=off or not. So much
> for
> paths.
>
> It's only fair to also talk about metrics at this point. If
> metrics-hinting is off, we should also use metrics from a fixed size,
> scaled by cairo. It gets trickier here. Cairo returns metrics in
> user
> space. So, for a fixed font matrix, regardless of the ctm matrix, one
> should get the same metrics. This is indeed needed to ensure that a
> zoomed-in print preview of a book typeset using pangocairo has the
> exact
> same line breaks that the actual print does. The scaled-font code
> takes
> glyph metrics from backends in font space and converts to user-space
> by
> applying the font matrix. That is, it's completely independent of the
> ctm. That's good. We just need to ensure that for metrics-
> hinting=off,
> we use the metrics for the font at a fixed size. That is, like what
> we
> need for paths. This time however, we probably do need to cache the
> sizes for different font matrices. So we can't just cache it on
> font_face_t. We need a new object, that has all the identifying
> properties of cairo_scaled_font_t except for the CTM. Then whether
> metrics-hinting is off or on decides whether we should share or not
> share this object across scaled-fonts with different CTMs.
>
> Humm... To add to the confusion... we currently don't share anything
> across scaled fonts that have different ctm/font_matrix, but the same
> ctm*font_matrix. We can share the glyph renderings. So that's
> another
> thing to share. And that one is in fact unconditional.
>
> And if and when we fix these all. We should also make the scaled-font
> layer not cache glyphs for sizes larger than some large value (128?),
> and just do path();fill() everytime. Some measurements are in order.
>
>
> So, that's it. Who volunteers for which part? :)
Behdad,
Loved this detailed post. I learned a lot from reading it. Thank you.
One question I have at the end, is how will this effect the cross
platform nature of Cairo? What is the effect, if any, on OSX/Windows
backends. If I ran this code snippet on Windows/OSX, would I expect
similar problems. And will this solution solve it across the board? Or
do things have to be done there?
Thanks again.
--
Travis Griggs
Objologist
10 2 letter words: "If it is to be, it is up to me"
More information about the cairo
mailing list