[cairo] Font rendering options

Owen Taylor otaylor at redhat.com
Thu Jul 7 15:57:50 PDT 2005


When handling text, there are quite a few relevant options:

- Whether we hint font outlines
- How strongly we hint font outlines ("hint style")
- Whether we hint font metrics (quantize them to integer quantities)
- Whether we antialias text rendering
- Whether we use subpixel antialiasing
- The subpixel order for subpixel antialiasing

These options aren't really coming from a single place - they have
default values based on properties of the output device, but 
applications also need to override those values.

Some examples:

* Application uses the toy API to render to the screen; the user's
  preferences from the Xft X resources should be picked up.

* Application is rendering to the screen via GTK+ and Pango. 
  GTK+ tracks the user's preferences through XSETTINGS to handle
  dynamic changes. GTK+ wants to pass these options down and
  override the values from Xft.

* A vector-graphics application wants to render text in a way that
  will be as independent of zoom factor as possible. It turns off
  metrics and outline hinting.

* Someone writes a short program which creates a PDF surface,
  and then renders on it with Pango using 
  pango_cairo_create_layout (cr). Text layout should automatically
  be unhinted.

Right now, the main way to pass rendering options to Cairo is as
part of a FcPattern passed to 
cairo_ft_font_face_create_for_ft_face(). Not all the options are
properly honored at the moment, but that's easy to fix up. 
(In fact I have a patch locally with all the non-subpixel parts
fixed up.)

The FcPattern approach can be used to implement the GTK+ usage above,
and the vector-graphics app case (assuming the app is using Pango and
can set options on its PangoContext.) However, it doesn't work for the
toy API case, or the case of the PDF-generating application: we don't
have a mechanism for the target device to influence selected 
font options.

A complication that needs to be taken into account here is that
we have both surface backends and font backends. Rendering options
are implemented by font backends, and the set of rendering options
is currently specific to the font backend, but the properties of
of a surface are belong to the sphere of the surface backend.

We should turn off hinting of metrics when drawing to a PDF surface
whether the font backend is the FreeType font backend or the Win32
font backend. On the other hand, values that the Xlib surface
backend  picks up from the Xft X resources are closely tied to
the FreeType font backend.

Another consideration is the structure of how font lookup happens.

For the  API, the application specifies the font as a triplet of
family, slant, and weight. These values are looked up in a cache to
find a cairo_font_face_t. The font_face_t here is a "simple"
font_face_t and contains no backend specific information.

The font_face_t is combined with the font matrix and CTM and
looked up in another cache to find a cairo_scaled_font_t.
The scaled_font_t is then used directly to look up metrics
and glyph images. So, the font_face_t, the scaled_font_t, and
the glyph and metric lookup are all independent of the properties
of the surface being drawn onto.

So, at some point, we need to introduce surface dependence.
Is that?

 - When creating the font_face_t
 - When creating the scaled_font_t
 - When looking up metrics and glyph images

Any of these are possible (try sketching out the APIs for yourself) ...
the main difficulty we run into is the above mentioned dissonance
between surface backends and font backends.

If we wanted to introduce surface dependence at the level of
lookup up metrics, for example, we couldn't simply add a 
cairo_surface_t to the font backend method:

 cairo_status_t (*font_extents) (cairo_scaled_font_t  *font,
                                 cairo_surface_t      *surface,
                                 cairo_font_extents_t *extents);

Instead we'd have to to add a surface backend method:

 cairo_status_t (*font_extents) (cairo_surface_t      *surface, 
                                 cairo_scaled_font_t  *font,
                                 cairo_font_extents_t *extents);

The surface then could check the type of the scaled_font_t ...
if it was an unknown type, it would just call 
cairo_scaled_font_extents() on it, but if it was a recognized
type, it could also call, say:

 _cairo_ft_scaled_font_extents (cairo_scaled_font_t      *font,
                                cairo_font_extents_t     *extents,
                                cairo_ft_render_options_t options);

This is certainly workable, though from the point of view of code
organization it is rather hideous ... we've made every surface backend
care about different types of font backends. (The 3 generic surface
backends PS, PDF, image, would have to care about all font backends,
the Win32 surface might have to care about Win32 and FreeType font
backends, and so forth.)

A different approach would be to try to take the
cairo_ft_render_options_t type above and make it cross-platform.
The set of options I identified for similar use in Pango is:

 hint_metrics: true, false
 hint_style: none, slight, medium, full
 antialias: none, gray, subpixel
 subpixel_order: rgb, bgr, vrgb, vbgr

Are these all going to be implementable for all backends? No. 
Windows we can do hint_metrics, and antialias, but not the other
two. FreeType actually only supports 3 hint_style cases, not 4.

Are these the complete set of possible options? Certainly not,
though the only other one I can think of off the top of my head is
rendering optimized for interlaced TV.

When we come at things from the approach of Pango, the sets of 
concerns is somewhat different. At the bottom, all rendering
options and backend information is attached to a PangoContext.

For Cairo, the PangoContext API looks like:
 
 /* Creates a default PangoContext */
 PangoContext *pango_cairo_font_map_create_context (PangoCairoFontMap *fontmap);

 /* Updates a PangoContext to match the properties of a cairo_t */
 void  pango_cairo_update_context (cairo_t      *cr,
                                   PangoContext *context);

Once we have a PangoContext, we can do things like:

 PangoLayout *pango_layout_new (PangoContext   *context);
 void pango_layout_get_size (PangoLayout    *layout, 
                             int            *width,
                             int            *height);

A fundamental property that I want to maintain is that a PangoContext
does not reference a cairo_t or cairo_surface_t. When a GtkLabel
is created, GTK+ needs the size of the GtkLabel before the window that
will be drawn onto exist. To support such usages,
pango_cairo_update_context() is actually only half the 
API - a sophisticated user of Pango can also set all the rendering
options via functions like:

 pango_context_set_matrix()
 pango_cairo_context_set_hint_metrics()
 pango_cairo_context_set_antialias()

And so forth. In fact, for the CTM, pango_cairo_update_context()
actually just calls cairo_get_matrix() then pango_context_set_metrics().
So,  PangoContext cannot simply always hold a pointer to a cairo_t.

Making it *sometimes* hold a pointer to a cairo_t is also 
problematical in my view. It is awkward, and leads to unnatural
lifetimes for cairo_t's and surfaces in natural code sequences. 
How do you create a PNG holding an image of some text?

 surface = cairo_image_surface_new (CAIRO_FORMAT_RGB24, 1, 1);
 cr = cairo_create (surface);
 layout = pango_cairo_create_layout (cr);
 pango_layout_set_text (layout, "Hello");
 pango_layout_get_pixel_extents (layout, &ink, NULL);
 cairo_destroy (cr);
 cairo_surface_destroy (surface);

 surface = cairo_image_surface_new (CAIRO_FORMAT_RGB24, 
                                    ink.width, ink.height); 
 cr = cairo_create (surface);
 pango_cairo_show_layout (cr, layout, - ink.x, - ink.y);
 cairo_destroy (cr);
 cairo_surface_write_png (surface, "hello.png");
 cairo_surface_destroy (surface);
 g_object_unref (layout);

While this isn't pretty in any case, it's a lot conceptually
worse if the the first cairo_t and surface are actually referenced 
and used when drawing onto the second cairo_t and surface.

I'd like font rendering options to work the same way as the CTM -
we read them off the cairo_t and store them on the context. So,
through a essentially different line of reasoning, we've returned
to the idea of a cairo_render_options_t. In one case, we were
trying to prevent a complex interdepenency between the surface
and font backends. In the other case, we wanted the ability to
store font render options and pass them back to cairo without
holding onto a cairo_t or cairo_surface_t.

So, what would the API look like? The simplest thing to do would
be to make cairo_render_options_t a bitfield

typedef enum {
  CAIRO_RENDER_HINT_METRICS_DEFAULT = 0 << 0,
  CAIRO_RENDER_HINT_METRICS_OFF     = 1 << 0,
  CAIRO_RENDER_HINT_METRICS_ON      = 2 << 0,

  CAIRO_RENDER_HINT_STYLE_DEFAULT   = 0 << 4,
  CAIRO_RENDER_HINT_STYLE_NONE      = 1 << 4,
  CAIRO_RENDER_HINT_STYLE_SLIGHT    = 2 << 4,
  CAIRO_RENDER_HINT_STYLE_MEDIUM    = 3 << 4,
  CAIRO_RENDER_HINT_STYLE_FULL      = 4 << 4,

  CAIRO_RENDER_ANTIALIAS_DEFAULT    = 0 << 8,
  CAIRO_RENDER_ANTIALIAS_NONE       = 1 << 8,
  CAIRO_RENDER_ANTIALIAS_GRAY       = 2 << 8,
  CAIRO_RENDER_ANTIALIAS_SUBPIXEL   = 3 << 8,

  CAIRO_RENDER_SUBPIXEL_DEFAULT     = 0 << 12,
  CAIRO_RENDER_SUBPIXEL_RGB         = 1 << 12,
  CAIRO_RENDER_SUBPIXEL_BGR         = 2 << 12,
  CAIRO_RENDER_SUBPIXEL_VRGB        = 3 << 12,
  CAIRO_RENDER_SUBPIXEL_VBGR        = 4 << 12,
} cairo_render_options_t;

I've made the fields in the above constant 4-bits in width to 
leave lots of extra space for extra values in each "field". The reasons 
for the "DEFAULT" values are a bit more subtle:

 - To explicitly allow people to omit values they don't care
   about.
 - To make future expansion cleaner ... we can just add another
   "field" and its clear that not specifying it will give reasonable
   behavior.
 - If we want to reuse the enumeration in places where a
   "transparent" DEFAULT  makes sense ... imagine adding to the toy 
   API:
   cairo_set_font_render_options (cr, CAIRO_RENDER_HINT_METRICS_OFF);
   we wouldn't want that to override the subpixel order for the surface.

We'd then add a new function to cairo_surface_t:

 /* Gets the default render options for a surface */
 cairo_render_options_t
 cairo_surface_get_render_options (cairo_surface_t *surface);

And then add a cairo_render_options_t to  cairo_scaled_font_create():

 cairo_scaled_font_t *
 cairo_scaled_font_create (cairo_font_face_t     *font_face,
                           const cairo_matrix_t  *font_matrix,
                           const cairo_matrix_t  *ctm,
                           cairo_render_options_t options);

(And thus also to the create() function of the 
cairo_scaled_font_backend_t and cairo_font_face_backend_t)

We could have instead introduced the render options elsewhere
in the API -- at any of the three points sketched above. This seems to
be the most natural place to me.... with the idea that a
cairo_font_face_t is an abstract description but a cairo_scaled_font_t
is specialized to a particular backend.

Good things about this proposed API:

 - It's easy to implement for both the X11 and Win32 font backends
 - It satisfies the needs of the toy API
 - It satisfies the needs of Pango
 - It's fundamentally a whole lot simpler than anything else I've 
   come up with

Bad things about this proposed API:

 - It puts some stuff into the generic cairo API which is not 
   very generic: the hint style options, for example, are something
   that is pretty unique to the fontconfig/FreeType stack.

 - It doesn't allow for surface and font backends to interact in
   new ways without extending the enumeration

 - The use of a bitfield means that we can't extend the approach
   to rendering options that aren't boolean or enum-valued.

 - The use of a bitfield isn't language binding friendly (though we 
   could encourage people to bind it as an object with setters
   and getters for the different subfields)

Open questions:

 - How should render options from a FcPattern interact with the 
   options from the bitfeld?

    - The FcPattern options always win ... if not specified, 
      comes from the render_options_t
    - The render_option_t options always win, if DEFAULT 
      is specified, the FcPattern value is used.
    - The FcPattern options are ignored completely

   (I think the first works best due to the way that fonts.conf
   is used... fonts.conf might turn off outline hinting for
   certain fonts.)

 - Should we in fact have something like 
   cairo_set_font_render_options() in the toy API? Disabling
   metrics and outline hinting is something people will frequently
   want to do, especially with cairo_text_path().

 - Naming specifics - cairo_render_options_t is a bit too 
   generic, but cairo_font_render_options_t is too long.

-------------- 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/20050707/2dd864d7/attachment.pgp


More information about the cairo mailing list