[cairo] Font analysis
Owen Taylor
otaylor at redhat.com
Fri Mar 18 10:11:34 PST 2005
Rather than replying to specific items in Carl's mail, let me start
from scratch and try to define the objects that we are talking about.
There are several things here that we've been calling "font"
font(1): A logical description of a font that, when combined
with a font scale, produces a specific rendering result.
One example of this is the [family/slant/weight] exposed
in the toy API.
Another example of this is a pair of a FT_Face and rendering
flags. (Basically, what Alex needed for xpdf.)
For remainder of this email, this will be called a FontName.
font(2): A scaled, fully resolved font. This object has a
specific set of metrics in device space, and a renders
with a specific set of glyph images.
This is what cairo_font_t is currently.
For the remainder of this email, this will be called
a ScaledFont.
font(3): A reference to a specific set of font data.
This is what cairo_unscaled_font_t is (a backend only
object.)
An example would be the FC_FILE/FC_INDEX pair that
fontconfig uses. This object is needed internally
inside the PDF backend to deal with font embedding,
it's not clear that it is needed publically.
For the remainder of this email, this will be called
a FontFile
The mapping to existing platforms:
FontName ScaledFont FontFile
Xft FcPattern XftFont ---
Windows LOGFONT HFONT ---
ATSUI ATSUStyle ATSUStyle ATSUFontID
[ I don't entirely understand the ATSUI system.
An ATSUStyle contains an ATSUFontID + style attributes +
optional size/matrix/rendering controls, etc. but I think the
ATSUFontID actually refers to a particular instance, so
the ATSUFontID + style attributes can resolve to a *different*
ATSUFontID. ]
How Pango works: (one example user)
As a starting point for layout, Pango has a
PangoFontDescription, which names a font, and a PangoContext,
which contains a pointer to a font database (PangoFontMap)
and the CTM of the surface the text will be drawn to.
Using the font database, Pango converts the PangoFontDescription
to a PangoFontset, which is basically an array of PangoFont.
Then, for each character in the string, it selects the appropriate
PangoFont.
Once the fonts are selected, Pango "shapes" the text. This means
converting characters to glyphs, applying glyph substitutions,
and positioning the glyphs based on metrics. To do this, Pango
needs the hinted (and thus font scale dependent) metrics,
and direct access to the platform specific, scaled, font object.
(A scaled FT_Face for FreeType, a HFONT for windows)
PangoFont, then is, in the above scheme, a ScaledFont.
The process of going from PangoFontDescription to PangoFont
is a slow one, especially with fontconfig, so Pango keeps
a cache from PangoFontDescription => PangoFontset.
Important things to note:
* You have to resolve FontName+scale => ScaledFont. The two
part resolution FontName => FontFile;
FontFile + scale => ScaledFont doesn't work.
There are multiple reasons for this:
One reason is that with bitmap fonts you can have different
sizes in different files. While we may dislike this state of
affairs, supporting it is a current requirement of users.
Another reason is that it's unnatural with many font
systems... e.g., with ATSUI, to go from a ATSUStyle
without a scale to an ATSUStyle with a scale, you copy
the style and add a scale. The ATSUFontID is mostly
needed if you want to do something like get access
to raw font data to embed the font. Windows doesn't
have any exposed FontFile object.
Finally, rendering options, such as hinting
and antialiasing can be encoded into the FontName
and affect the resolved ScaledFont, but don't belong
in FontFile.
* Looking up glyph metrics from a Font is extremely
performance critical.
Rough numbers ... if your layout system can lay out
a million characters per second on a 1ghz machine,
people will consider it fast. (Pango is about half
that with Xft, and we get fairly frequent speed
complaints.)
Laying out text requires measuring each glyph in
the text at least once. If we are willing for Cairo
to take *half* the time of text layout, then Cairo
needs to be able to measure 2 milion glyphs per second
on a 1ghz machine. If you could do 5 million, you'd
be better off.
* Low-level text APIs on the order of Pango need
access to the underlying font objects ... whether
it be HFONTs on Windows, or *scaled* FT_Face objects
when using FreeType.
Proposals that have been floated:
1. Don't expose a ScaledFont object, but use a FontName + scale
everywhere in the public API. This is more or less how the
older cairo API was set up, though the right set of FontNames
wasn't available. With fixed FontNames it works better, but
falls over on two counts:
- Performance. For every glyph we want to measure, we have
to do a full lookup. Even with the right hash tables, this
is prohibitive.
- Inability to reference the underlying platform font objects.
Currently we have cairo_win32_font_select_font()/done_font()
for Win32 and cairo_ft_font_lock_face()/unlock_face()
for FreeType. These return scaled objects for the
underlying backend, and so need scaled objects in the
FreeType API.
2. Make ScaledFont objects mutable.
The public ScaledFont object basically looks like
[FontName,font matrix,CTM,PlatformScaledFont]
Where PlatformScaledFont is recreated at need.
The idea here is to assign meaning to sequences like
set_font()/scale_font().
This fixes some of the performance problems issues with the
above because there is a place to cache a PlatformScaledFont
but:
- cairo_set_font() mutates the font, which is unpleasant.
- If someone has accessed the platform font objects,
they can unexpectely become invalid when the font
is mutated.
- Figuring out when we should dump the PlatformScaledFont
or not is hard ... do we require exact equality of
doubles?
3. Expose a ScaledFont object, but only allow setting a FontName
on a cairo_t. So, Pango would when creating a PangoFont
would store the current scale, create a FontName, then
create a ScaledFont for the FontName and scale. The
PangoFont holds references to both ScaledFont and FontName.
When getting metrics, Pango would use the ScaledFont, but
to draw to a cairo_t, it would set the FontName and then
just assume that Cairo is going to make it's way back
to the same ScaledFont.
This obviously has some performance impact, though
per-string overhead when drawing is *much* better
than per-glyph overhead when measuring.
4. Allow setting either a ScaledFont or a FontName on a
cairo_t. If both are set, the ScaledFont overrides the
FontName. scale_font() transform_font() don't affect
the ScaledFont.
From a cairo_t API perspective this is obviously a
little weird in two respects - that scaled fonts are
scaled and that we have two ways to set the font,
but it keeps things a lot simpler than most of the
approaches above.
5. Allow setting either a ScaledFont or a FontName on a
cairo_t. If a ScaledFont is set and the scale or
font_matrix don't match the CTM, linearly scale the
outlines or bitmaps to match the CTM.
I see no practical utility in this and considerably
implementation difficulty.
More information about the cairo
mailing list