[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? :)


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
10 2 letter words: "If it is to be, it is up to me"

More information about the cairo mailing list