[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