[cairo] User-font API, take 3

Behdad Esfahbod 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
  that later.

- 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
implementation.  Details:

- 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
      a malloc.

    - 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);
    cairo_stroke (cr);

  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
	  offset used.

	- 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.

Open questions:

- 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 mailing list