[cairo] User font feature

Behdad Esfahbod behdad at cs.toronto.edu
Sun Apr 9 14:51:19 PDT 2006


On Fri, 7 Apr 2006, Kristian Høgsberg wrote:

>   cairo_public cairo_font_face_t *
>   cairo_user_font_face_create (cairo_ucs4_to_index_func_t     ucs4_to_index,
>                                cairo_get_glyph_metrics_func_t get_glyph_metrics,
>                                cairo_render_glyph_func_t      render_glyph,
>                                cairo_get_glyph_path_func_t    get_glyph_path);
>
> Using this API the application can create a font face given a set of
> call backs.  The font face can be used as any other font face and
> works with cairo_show_text() and cairo_show_glyphs() etc.  The idea is
> that when cairo needs metrics or the glyph bitmap it calls out to the
> application provided callbacks.  The glyphs bitmaps are cached and
> evicted as needed etc. behind the scenes as for the other cairo font
> face types.
>
> While this API is sufficient to fix the poppler performance problem,
> there's a few other issues to consider:

Is it?  I don't think so.  It doesn't get any data, so you end up
needing one set of functions per font!


> - PS and PDF Type3 fonts and SVG fonts allow for ARGB glyphs.  That
> is, a glyph is either an alpha mask that is used in a mask operation
> with the current source when painting the glyph (as in cairo today) or
> it's a full ARGB image that just gets composited into the destination
> when painting the glyph.  I believe it's not too difficult to extend
> the glyph cache to also handle ARGB glyphs, we just need to inspect
> the surface contents for each glyph surface and either mask it or
> composite it as we paint the glyphs.

ARGB glyphs are pretty interesting, for example you can create a
Valentine's font that draw little red hearts instead of dots for
i and j :).


> - Behdad suggests using the text_to_glyphs API instead of
> ucs4_to_index, which makes it possible for the user font
> implementation to substitute combined glyphs if necessary (I think
> that was the motivation, anyway).

If you need anything other than a toy api, you need this, for
kerning, for ligatures, for non-Latin shaping, and a lot more.


> - We probably need a callback when a scaled font is created creation
> to allow the user font implementation to attach per-scaled font data
> and a callback to free that data.  Most of the built in font backends
> extend cairo_scaled_font_t with backend specific data, after all.

True.  I sat down and wrote my idea of what the API may look
like.  The main problem is that since font_face_t and
scaled_font_t are opaque, the user font cannot embed them.  So
what I came up with was having the user font return a void* that
the user-font backend will put in the font_face_t/scaled_font_t
and pass to user functions on subsequent calles.


> - The render_glyph callback needs to take a cairo_t instead as
> suggested by Behdad.  This way we can render alpha masks for the
> glyphs we do now, but it will also be possible to render a user font
> glyph into an SVG glyph or Type3 glyph.  The glyph path isn't
> sufficient for outputting SVG or Type3 fonts as the source user glyphs
> may be bitmap or ARGB glyphs.  Passing a cairo_t to the render_glyph
> callback also let's us set the font matrix as the ctm for that cairo_t
> and we can specify that the callback must render the glyph in a unit
> sqare.  Not sure this works so well, though, if the application wants
> to do hinting or pixel grid snapping.

I won't say a unit square.  You draw like it's a 1pt font, as the
font matrix is really just a scale.  I have added a scaled_font
level to your design to deal with hinting too.


> - We still need the get_glyph_path callback for the cairo_text_path()
> case. Alternatively, this could be done by passing a meta surface to
> render_glyph and then extracting the path from that.  Of course that
> isn't always possible - again, bitmap fonts and ARGB glyphs.
>
> But anyway, that's a head up on the user-font work.  Feedback welcome.
>
> cheers,
> Kristian

Ok, here is my take.  It may be a bit over-engineered, but is
feature-complete.  It does handle all my needs for user-font in
Pango, and can easily handle yours.  Some design decisions and
goals first:

  - One of the goals was to not have to expose any cairo structs
that is not already exposed.

  - Next, if you have a set of functions to render say SVG <font>
tags, you shouldn't have to pass them to cairo every time you
create such a font.  So I added the idea of a user_font_class,
from which you create new font_faces.

  - And of course to be able to do hinting and other interesting
stuff, you create an scaled_font from a font_face.

  - We may use a struct instead of passing ten methods to a
function, that works too.




/* cu stands for cairo_user here */

cairo_public cairo_user_font_class_t *
cairo_user_font_class_create (cairo_text_to_glyph_func_t,
			      cu_font_face_create_func_t,
			      cu_font_face_destroy_func_t,
			      cu_scaled_font_create_func_t,
			      cu_scaled_font_destroy_func_t,
			      cu_scaled_font_extents_func_t,
			      cu_scaled_font_glyph_extents_func_t,
			      cu_scaled_font_show_glyph_func_t,
			      cu_scaled_font_glyph_path_func_t,
			      cu_font_class_destroy_func_t,
			      void *class_data);

cairo_public cairo_user_font_class_t *
cairo_user_font_class_reference (cairo_user_font_class_t *);

cairo_public void
cairo_user_font_class_destroy (cairo_user_font_class_t *);

cairo_public cairo_font_face_t *
cairo_user_font_face_create (cairo_user_font_class_t *,
			     void *font_face_data);

cairo_public cairo_user_font_class_t *
cairo_user_font_face_get_class (cairo_user_font_t *);

/* convenience */
cairo_public cairo_user_font_class_t *
cairo_user_scaled_font_get_class (cairo_user_font_t *);


where:


- cu_font_face_create_func_t:

If this is not NULL, it will be passed the class and the font_data,
and should return a void* that will be attached to the font_face and
passed to all cu_font_face_* and cu_scaled_font_* instead of the
cairo_font_face_t * which is opaque and has no getters anyway.  (or
maybe passing that around is still useful? I don't think so at this
time.)


- cu_scaled_font_create_func_t:

If this is not NULL, it should return a void* that is attached to
the scaled font and passed to all cu_scaled_font_ functions in
excess to the cairo_scaled_font_t *.


- cu_scaled_font_extents_func_t:

If this is NULL, the extents will be set to zero and the status on
the scaled font will be set to CAIRO_STATUS_NOT_SUPPORTED
(interesting there's no such an status currently.)


- cu_scaled_font_glyph_extents_func_t:

This takes the cairo_scaled_font_t and returns scaled maybe-hinted
extents.


- cu_scaled_font_glyph_path_func_t:

Like show_glyph, but only appends subpaths to current path.  If is
NULL, the backend may return NOT_SUPPORTED or use show_glyph and
surface analysis to emulate it.


- cu_scaled_font_show_glyph_func_t:

This takes the scaled font (and its data) and a cairo_t, and uses
other cairo operations to draw the glyph.  The cairo_t has the
font_matrix applied to it already.  The show glyph may also set
color or other parameters, but of course it should save/restore
appropriately.  It may also use other font functions, like the
toy API (consider a user-font to draw hexboxes for example), or
even other user fonts.  It returns a boolean: whether the glyph
should be cached or not.  This can be used to implement fonts
that have slightly different shapes for each glyph every time
shown. (to simulate hand-writing for example.)

It's up to the user-font backend to appropriately create a
surface of proper type, and to cache them.  For example, for the
PS/PDF surfaces, the noncached glyphs will be directed to the
output while the cached glyphs will be stuffed into Type3 fonts.
Each glyph may be a bitmap or drawing instructions depending on
whether the surface had to fallback to image or not.  There are
a few things about this API that I'm not comfortable with:

  * If the glyph is to not be cached, we may as well draw it
  directly on the target surface, but with this design we can't.

  * The user-font backend may need to call get_glyph_extents to
  create the proper bitmap surface to pass to show_glyph.  While not
  infeasible, I don't think it's optimal.

  * And of course, you don't have a way to know whether the glyph
is going to be ARGB or not.


Cheers,
behdad


More information about the cairo mailing list