[cairo] Getting real linearly-scalable text (second try)
behdad at behdad.org
Thu May 15 15:54:47 PDT 2008
[Seems like this message of me didn't reach the list yesterday, though
my self-reply to it did.]
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:
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_scaled_glyph_lookup_subpixel (cairo_scaled_font_t *scaled_font,
unsigned long index,
cairo_fixed_t x, cairo_fixed_y,
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
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? :)
============================================= I later wrote:
Just after I sent this it occurred to me that metrics is more
complicated. The "ink" metrics (bearings and width/height) should come
from the same source that the glyph path does, while the advance should
follow the metrics-hinting setting. So what I explained above is only
about the advance values. Seems like we have to compile the metrics
from two different sources.
I also thought a bit about how to approach implementing all this weird
caching behaviors. One approach is to introduce three new objects,
cairo_scaled_font_glyph_metric_array_t. All ref-counted so they can be
shared. Then the scaled font will have a pointer to one of each of
those, and we can share them among multiple scaled fonts. That becomes
quite complex quite fast, specially in the backend interface.
Another approach is to not touch the current backend interface, instead
link scaled fonts to canonical scaled fonts to be used for
raster/path/metrics. That's kinda ugly, but may prove easier to
Anyway, I'll be thinking about it more this weekend while canoeing in
Northern Ontario. Given enough time, the Right design will eventually
reveal its beauty...
"Those who would give up Essential Liberty to purchase a little
Temporary Safety, deserve neither Liberty nor Safety."
-- Benjamin Franklin, 1759
-------------- next part --------------
A non-text attachment was scrubbed...
Size: 2297 bytes
Desc: not available
Url : http://lists.cairographics.org/archives/cairo/attachments/20080515/f9ea903a/attachment-0001.py
More information about the cairo