[cairo] User-font API, take 3
behdad at behdad.org
Tue Feb 6 17:20:23 PST 2007
Last weekend, Kristian and I (and partly Owen) finally sat down
and worked out the user-font API based on previous discussion
Please have a quick look before reading on.
The common theme in the API we came up with was flexibility and
ease of use, at the same time! Anyway, here are the decisions we
made there on top of the previous API proposal:
- Pass the scaled font to all methods
- Add font_extents_t to cairo_user_font_face_create, can be NULL.
If not NULL, this will be linearly scaled and passed to the
scaled_font create function. The idea is to let the user not
have to worry about the scaling of anything, including font
metrics. However, I changed this plan later on. I'll discuss
- Automatically generate glyph metrics out of the meta surface
that holds the rendered glyph. This is very cool indeed. The
only, small, problem with it is if you paint a surface in your
glyph and the surface is not /tight/, that is, it has
completely transparent edges. In that case the generated ink
extents will not be tight. But we don't think this is a major
problem. The guarantee of ink extents is that they completely
include the rendered glyph, and that is satisfied. As a
result, we decided to have get_advance() instead of
get_metrics(). I changed this plan later too.
- To keep with the standards of cairo, all methods (other than
destruct) should return a cairo_status_t.
- Add a text_to_glyphs method in excess to the ucs4_to_index one.
- Like before, no to_path method is needed. Path should be
generated out of meta-surface automatically.
When implementing, I found various flaws with our design and
changed them appropriately, the following is a summary of which.
Oh, the code! It's in the user-font3 branch of my cairo repo
(not the user-font branch):
git clone git+ssh://git.cairographics.org/~behdad/cairo
The main code is in cairo-user-font.c that is based on krh's
- Renamed ucs4_to_index to unicode_to_glyph. It just makes a lot
more sense. If none of unicode_to_glyph and text_to_glyphs are
implemented, an identity mapping is used.
- Since this is the first time we are adding public vtables to
cairo, we need to think about extensibility carefully. I added
five /reserved/ function pointers to the struct. An
alternative would be to pass the size of the struct to the
function taking it, so the user would call it like:
cairo_user_font_face_create (my_font_funcs, sizeof (my_font_funcs));
This is uglier to use, but indefinitely extensible. A related
question is whether the created font_face should make a copy of
the vtable or use the passed in table. Current implementation
doesn't copy it. Pros and cons:
o If we copy:
+ glib does this.
+ good: user doesn't have to make the vfunc static, so it
doesn't. force runtime linker relocations and private .data.
- bad: each font face will have a copy of the vfunc table.
Not much waste though: 10 pointers only (5 if not counting
the reserved ones).
o If we don't copy:
+ good: all font faces using the vtable use the same copy in
- bad: the vtable either causes runtime linker relocations or
- error prune if user passes in an automatic variable for
vtable, or changes table entries behind our back.
- harder for language bindings: they'll probably will copy it
TODO: So I think I'm going to change it to copy.
- I also merged get_advance and show_glyph, into a single method:
get_glyph. The reason is very simple: while advance widths is
all you need for text layout, cairo's API and internal
implementation always deal with advance values and bounding
metrics together. Since we want to extract bounding metrics
from the result of show_glyphs(), a separate get_advance() buys
us absolutely nothing. Instead of adding *x_advance and
*y_advance to get_glyph(), I went on and added a complete
cairo_text_extents_t. It is initialized to such values that
the user needs just set x_advance (or y_advance for vertical
writing mode). In that case the bounding box will be
auto-computed. On the other hand, if the user has set a
non-zero width, no auto-computation happens and the metrics
that the user has set are used. The metrics are in glyph
space. The height is by default set to 1.0 for example.
TODO: Implement get_bounding_size for meta-surface and use it.
- For getting glyph path, we are going to extract it
automagically from the meta-surface. This requires bitmap
tracing in the meta-surface, and stroke_to_path. The latter is
a bit harder than easy.
TODO: Implement get_path in meta-surface and use it.
- Font extents: Owen and Kristian both want to have a way such
that the user doesn't have to deal with the ctm and font
matrices to set the font extents. Their suggestion was to add
a font extents to the cairo_user_font_face_create() that can be
NULL, and scale that and pass it to scaled_font_create(). So
if no hinting is desired, the user can just ignore it in
scaled_font_create(). If the extents passed to
font_face_create() are NULL, we will use default extents (that
have 1. for ascent, height, max_x_advance, and 0. for descent
and max_y_advance). However I dropped the idea for two
o The cairo_user_font_face_create() API doesn't take any user
data. The idea was that you create a font face and later
attach font data to it using the user_data API. In this
use-case, it looks like a mismatch to me to pass the extents
to the font_face_create().
o Since everything else in the API (ie. glyph rendering and
extents) are in font space, so should be the font extents.
That is, the user does not have to deal with the matrices
when setting font extents in the scaled_font_create() method
either. So they can just set numbers in the range of 1.0 and
we will scale them afterward. Or they can ignore it and get
sensible default values.
So we get the good stuff from the old design, with a cleaner
API. Another desired effect here is that it should be easy to
wrap another cairo font with the user font API. So it should
be easy to take font metrics from another font and pass them
on. You can do that in your scaled_font_create() with the
proposed API by:
/* we are in font space already */
cairo_set_font_size (cr, 1.);
cairo_font_extents (cr, extents);
So we are all good.
- Glyph caching: The main advantage of the cairo text API is its
glyph caching. Without glyph caching, the user-font API would
be just a fancy wrapper around cairo_t. However, this is the
first time that we want to support COLOR_ALPHA glyphs in cairo.
This needs some support in the scaled-font level, and in the
backends. Basically, the glyph drawing code in the get_glyph
callback of a user font will make some drawings without setting
a source explicitly, and others with a source. The desired
effect is that those not setting source should take the source
set at the time of a show_glyphs call using the font. That is,
a glyph rendering procedure like this:
cairo_move_to (cr, .2, .1);
cairo_line_to (cr, .2, .6);
cairo_set_source_rgb (cr, 1, 0, 0);
cairo_move_to (cr, .15, .9);
cairo_line_to (cr, .2, .85);
cairo_line_to (cr, .25, .9);
should draw an 'i' with the stem using the source like current
fonts do, but with a red heart-like dot. Kristien and I talked
about various approaches to supporting this. We came up with
the following strategy:
* If no explicit source used, it's just an alpha glyph. Use
an A8 bitmap like the current code does.
* If only explicit source used, it's a self-contained glyph.
Use a ARGB32 bitmap to cache it.
* If it uses both implicit and explicit sources, this cannot
be cached generally. So:
- Use the meta_surface to draw it everytime, by setting
the source on the cairo_t to the source at the time of
the show_glyphs call. This can be done by calling
user's get_glyph() method again, or by some magic in
the meta_surface. Need to take care of the source
- When done the above, if the source was solid, cache the
bitmap and the color of the source. Use the cached
bitmap later only if the source matches.
For the implementation I just thought of this: we set a
special, dummy source pattern (kinda like a nil pattern) in the
cairo_t passed to get_glyph(). Then we look for that dummy
pattern in the meta-surface operations and we know those are
the implicit patterns. Since the dummy pattern is of none of
the public pattern types (and we should document it as such),
the user cannot use any information from the pattern when
drawing the glyph.
TODO: Implement all these.
- Should we add a mutex to the cairo_user_font_face_t and lock it
around scaled_font creation? I have the use case in mind that
the user will look for some user_data attached to the font_face
in scaled_font_create callback and if it's not there, it will
create and attach it. That is a bit racey. Since we have
mutex objects at our hands reach and they cannot be a bottle
here, I suggest we do that for the user.
Changes needed in other backends.
- Fix ps/pdf/svg to be able to emit a meta-surface natively.
Easiest to add a meta_surface member (and GLYPH_INFO) to scaled_glyph.
- Fix ps/pdf/svg to support a1, a8, and argb32 glyphs natively as type3?
make sure fallback Type1 is not emitted for them. We may need
to create separate Type3 fonts for a1/a8 and argb32 glyphs.
- Make xlib and other raster backends support fonts having glyphs
with different formats. xlib currently picks the format of the
first glyph as the subset format. Kinda like previous item.
Maybe we can share some code in the scaled-font level.
- Some changes are also needed in cairo-path-fixed to support
full device_transform matrices as they may be present in
font_matrix. Currently it only handles scale+offset (and I
fixed the way it does that). I tried to move the
device_transform from the bitmap surface to the meta-surface to
avoid needing this, but that didn't help because of the next
item. (Not sure that it would have been useful).
Things that surprised me:
- Metasurface doesn't apply device_transform to the pen. So
setting device_scale on the meta_surface makes the strokes
become thing. God, why are pens not locked when set... /sigh
- Last but by no way least, I figured that the use of
device_offsets in glyph surface is all wrong in cairo.
Everybody please read this commit and check the changes if it
affects code you wrote:
"Those who would give up Essential Liberty to purchase a little
Temporary Safety, deserve neither Liberty nor Safety."
-- Benjamin Franklin, 1759
More information about the cairo