[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