[cairo] Notes from Pango/Cairo integration

Owen Taylor otaylor at redhat.com
Sun Jan 9 12:55:14 PST 2005


So, over the last few days, I spent some time getting an initial 
pass of Pango/Cairo integration. It went pretty well, I got things
working and checked into Pango CVS. (HEAD branch.) But, not
suprisingly, I ran into a few problems with the font handling
in Cairo. 

When reading the following, the glossary and notes about caching
in Cairo/Pango/Xft may help.

Narrative
=========

The first thing I found out was that with the setup now, the caching of
fonts in the FreeType backend didn't do me any good ... it's only
done at the level of the "toy" cairo_select_font().

At one level that's not avoidable ... doing fontconfig matches is
expensive so it's not going to be suprising that Pango has a cache
of "PangoFontDescription => list of resolved patterns", and 
Cairo has a cache family/slant/weight => cairo_unscaled_font_t".

The question is then whether it makes sense to have any common
caches that bring things together at a lower level? The minimum
would be to have a cache like Xft where all fonts for the same file 
on disk share the same FT_Face.

Once I had resigned myself to doing caching myself, my initial thought
was that I'd be able to use cairo_ft_font_create(), but that turns out
not to work because it takes an unresolved FcPattern, not a 
resolved FcPattern. And Pango needs to do its own pattern resolution.
(For one thing, it is using FcFontSort(), not FcFontMatch()).

So, I copied the font loading logic from the FreeType backend, and
used cairo_ft_font_create_for_ft_face(). There is still a residual
problem, however. Pango computes the appropriate load flags for the
FcPattern by looking at FC_ANTIALIAS, FC_HINTING, and so forh.  But
has no way to pass them to Cairo, which always uses FT_LOAD_DEFAULT.

So, Pango needs a function like XftFontOpenPattern() that takes a
resolved pattern and computes the right load flags itself, or the
ability to pass the load flags to
cairo_ft_font_create_for_ft_face(). The former would allow sharing of
FT_Face objects between all uses within the Cairo instance, so may be
preferrable.

One thing that Pango needs to do with a loaded font is get the FT_Face
object for it. Cairo has cairo_ft_font_face(cairo_font_t *ft_font),
but that isn't really right since the FT_Face has to be set up for a
particular size; an example is adjusting positions via GPOS tables.
With the current code, Pango is only ever using a cairo_font_t at a
single size, so the hack I ended up doing was to, the first time I got
a FT_Face for a font, make a dummy call to
cairo_current_font_extents(), which calls _install_font_scale() for
the FT_Face.

Another thing that Pango needs to do with a loaded font is to get
metrics for the font and for individual characters. The font metrics
that Pango needs go beyond what Cairo provides; Pango needs things
like underline and strikethrough position. So, I just used custom code
to compute them from the FT_Face. I don't mind doing that.

Glyph metrics are a different issue...since computing glyph metrics
requires calling FT_Load_Glyph, it's really desirable to do it at the
same time as computing the glyph image.  Having Cairo do the glyph
metrics computations also ensures consistenty between glyph rendering
and glyph layout. But there are multiple problems with
cairo_glyph_extents(). First, it takes a cairo_t as an argument, which
is really inconvenient. I ended up creating a cairo_t on the fly each 
time I need to get glyph metrics. There are the problems that Keith
has being dealing with in terms of the relationship of metrics to the 
CTM and font transform. Which I've handled by simply bypassing Cairo
for metrics computations when a non-pure-scale transform is in
effect. Finally, I'm concerned that the way cairo_glyph_extents is
handled in the FreeType backend is going to be slow; having a global 
glyph cache means that glyph lookups involve fairly complicated hash
logic (*).
And determining glyph metrics is incredibly time-sensitive.  To
determine the size of a 1-megabyte text document, Pango needs call
cairo_glyph_extents() at least 1 million times.

(*) _cairo_glyph_cache_hash looks broken ... any matrix that scales
down will collide with any other matrix that scales down.

Suggested improvements
======================

So, to get to concrete suggestions. The minimum I see is:

 - Cairo should have a function to create a cairo_font_t from a fully
   resolved FcPattern, and should pick up the load flags from the
   pattern. (The load flags computation should also be done when Cairo
   creates the pattern itself, of course.)

 - Something like _cairo_unscaled_font_glyph_extents() needs to be
   exported publically. This could be done without adding any extra
   types by making it take a cairo_font_t (font matrix is ignored) and
   a cairo_matrix_t (translation is ignored.) (**).

 - cairo_ft_font_get_face() should take the same scale arguments as
   the public _cairo_unscaled_font_glyph_extents() variant and call
   _install_font_scale()

 - _install_font_scale() should optimize out installation of the
   current scale. (FT_Set_Pixel_Sizes() doesn't catch installation of
   the same size and looks expensive.)
 
That would solve the basic API problems, and might be fast
enough. Further possibilities:

 - Add a cairo_scaled_font_t type. This would represent a font at a
   particular device resolution, and would have a single set of
   metrics and glyph images.  cairo_scaled_font_t would be passed
   around instead of cairo_unscaled_scaled_font_t / cairo_font_scale_t
   combinations.

   There would be hooks to allow backends to cache information on a
   cairo_scaled_font_t.

 - Using cairo_scaled_font_t, add XftLockFace/XftUnlockFace style 
   functions to get a sized face. Actually XftLockFace is a bit weird 
   because because you if you try to lock two XftFont's pointing to
   the same file the font gets resized. But having the Lock/Unlock
   does allow Xft to limit the size of its FT_Face cache.

 - Add Xft style sharing of FT_Face between all fonts that use the
   same font on disk. (filename/id combination).

 - Switch the FT backend cache that goes from pattern => ft_font_val_t
   to work off of resolved patterns. After all, there is already a
   cache ahead of it in the generic layer, so speed of the secondary
   cache isn't that important. This would allow ft_font_val_t objects
   to be shared between Pango and the toy API.

And some miscellaneous things:

 - I think cairo_ft_font_create_for_ft_face should take a destroy
   notifier from the face, rather than requiring the caller to keep
   track of the life cycle of the font.  (Relates to the FILE *
   discussion, of course)
 
 - To deal with PDF backends and the like, Pango needs the ability to
   tell if a backend is using device-independent metrics or
   not. Probably should be a cairo_surface_t function call.

(**) there is a temptation to define the public function to multiply
the font matrix and the passed in matrix. Resist it
.. cairo_glyph_extents needs to be *fast*.

Glossary
========

 PangoFontDescription: A generic description of a font. This
  has a string form like "Sans Bold 27".
 
 PangoFontSet: A list of PangoFonts that is found by loading
   a PangoFontDescription with a specified output surface,
   user => device transformation matrix, and rendering options.

 PangoFont: The low level font object in Pango. It generally
   corresponds to a single operating system font at a particular
   output size. The metrics of a PangoFont are fixed.

 cairo_font_t: A pair of a cairo_unscaled_font_t and a 
   'font transformation matrix'. The font transformation matrix
   can be changed at any point.

 cairo_unscaled_font_t: An internal Cairo object that represents
   a font with all attributes specified but the scale.
   The scale is determined by a font transformation matrix
   and the CTM. 

   cairo_unscaled_font_t is "derived from" by a particular font
   backend, cairo_ft_font_t is the subclass for the FT backend.
   (What you might expect to be called cairo_ft_unscaled_font_t)

 ft_font_val_t: An object internal to the FreeType backend
   that holds a FT_Face. While, as far as I can see, a
   ft_font_val_t could be shared between every
   cairo_ft_unscaled_font_t that backends to the same font
   on disk, current sharing is much more limited.

 FcPattern: fontconfig's basic data structure. A list of  key/value 
   pairs with keys such as FC_FAMILY, FC_WEIGHT, etc.

 resolved FcPattern: the result of taking a FcPattern that specifies
   basic font information and running it through 
   FcConfigSubstitute()/FcDefaultSubstitute()/FcFontMatch() or
   FcFontSort(). This pattern will have FC_FILE/FC_INDEX keys
   that identify a font on disk, and also rendering option keys
   like FC_ANTIALIAS. Normally these have size information 
   in them as well, though Cairo resolves patterns without
   size.

 XftFont: The basic Xft font object. Corresponds to a resolved
   FcPattern with size.
  
 XftFtFile: An internal Xft object that corresponds to a single
   font on disk. 

Caching inside Cairo
====================

 cairo_font.c:_global_font_cache
   A cache from family/slant/weight => cairo_unscaled_font_t.  This is
   used inside the cairo_select_font() toy API before chaining to the
   backend.

 cairo_font.c:_global_image_glyph_cache
   A cache from cairo_unscaled_font/glyph index/scale =>
   glyph/metrics. Strangely, this glyph references the unscaled font,
   so fonts will be kept alive (but unacessible) until all their
   entries are forced out by random replacement.

 cairo_font.c:_global_ft_cache
   A cache from FcPattern to ft_font_val_t. This is used only in the
   toy API path. It's unclear what function it serves since the
   FcPattern that is the key has only the same information as the
   _global_font_cache, so hits happen only when values are randomly
   forced out of _global_font_cache but not this cache.

 All of the Cairo caches are size limited with random replacement.

Caching inside Pango
====================

 PangoFcFontMap.fontset_hash_list. A set of hash
  from PangoFontDescription/size/render options => 
   {list of FcPattern, PangoFontSet}
  (There is one hash per language tag; the language
  tag probably should be moved inside the key.)

  size here is a combination of the specified size in the font
  description and the scale factors from the CTM. It is the size we
  want to use when calling FcFontSort().
  
  The patterns in the resulting list are uniquified to
  using PangoFcFontMap.pattern_hash() to save memory and to allow 
  fast lookups in PangoFcFontMap.fonts.
  
  This hash grows without bound as long as the fontmap is alive. 
  (Suprisingly, so far, this has never turned out to be a problem in 
  practice.)

  The PangoFontSet isn't referenced and will be cleared
  when it isn't referenced anywhere else.

 PangoFcFontMap.fonts: A hash from FcPattern => PangoFontSet.
  Each uniqified pattern appearing in the results of 
  PangoFcFontMap.fontset_hash_list will have at most one
  PangoFont corresponding to it live. Does not reference
  the PangoFont.

 PangoFcFontMap.fontset_cache: A 16-element queue of recently loaded
  PangoFontSet objects. This cache is what keeps PangoFont
  and PangoFontSet objects from continually being reloaded.

Caching inside Xft
==================
 
 All XftFonts for the same font on disk share the same XftFile and
  (when loaded same) FT_Face.
 
 At most 5 (not currently locked) XftFile objects will have FT_Face
  objects loaded at once, when another is loaded, a random FT_Face
  will be removed.

-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 189 bytes
Desc: This is a digitally signed message part
Url : http://lists.freedesktop.org/archives/cairo/attachments/20050109/19e547a7/attachment.pgp


More information about the cairo mailing list