Text Rendering Notes
A summary of observations about text rendering!
This page is the result of an on-going process of planning a refactor of Inkscape's text handling.
Our software stack relies on the FreeType, HarfBuzz, Pango, and Cairo libraries for rendering text.
- FreeType: Access to font internals.
- HarfBuzz: Converts characters to glyphs (i.e. shaping).
- Pango: Determines best fonts to render text for given style and characters.
- Cairo: Rendering of glyphs.
The boundaries between these libraries is somewhat murky and often one can do the same things using routines from different libraries.
HarfBuzz is under active development and is used by many major pieces of software. FreeType has some development. Pango and Cairo are poorly maintained at the moment.
Changes for Inkscape
Currently Inkscape uses a custom hash of the Pango Font descriptor to index fonts in a font map. Pango has a built in hash which we should probably use instead. This font hash includes "gravity" which is missing in our custom hash.
When adjusting glyphs, especially in vertical-upright text, we should move glyphs in the same cluster by the same amount.
(A cluster is a set of glyphs that belong to the same grapheme, the smallest typographic unit. For example, an 'a' with a 'grave' accent can (but need not) be composed of two separate glyphs, which would then belong to the same cluster. Complex scripts (such as south-Asian) can have a many-character to many-glyph mapping within one cluster.
Currently Inkscape does some limited guessing as to which glyphs belong together (basically just that non-spacing marks are considered to belong to the preceding glyphs).
Clusters can also help with positioning the cursor so that one can indicate which character within a cluster is subject to editing. For example, if 'ffi' is represented by one glyph and thus one cluster, one can position the cursor before the cluster (before the first 'f'), one-third of the way into the cluster (between the two 'f's), two-thirds of the way into the cluster (between the second 'f' and the 'i') or at the end of the cluster. In this case, the cursor position closely indicates which character is being edited, with complex scripts, this might not be the case but still will give a visual indication that one is "moving" through the cluster.
HarfBuzz includes some functions to reverse the order of glyphs/clusters. These might be useful as SVG dictates that glyphs should be drawn in character order while the shapers return glyphs in visual order (which is opposite for right-to-left scripts).
General Text Layout Algorithm
- Add text with the same style and SVG positioning attributes as spans.
- Use Pango to itemize spans to find appropriate fonts for each item in span. (An item is a group of characters that share the same font face which can differ with a span if glyphs are missing in the nominal font face.)
- Shape the Pango items to determine which glyphs with positions should be used to render the text. Either Pango or HarfBuzz can be used (Pango uses HarfBuzz under the hood).
- Create Character, Glyph, Cluster mappings. These will be needed for text editing. Use a specialized iterator to walk through maps.
- Layout items in allocated space, determining glyph positions. Must handle SVG kerning, text-length attributes, white-space (spaces, tabs, line-returns), etc.
- Apply SVG positioning attributes.
- Render glyphs. We use FreeType to extract out glyph paths. We could use Cairo directly but as we need access to the paths when converting text to path, it doesn't reduce code.
- Determine cursor position (with link via iterator into Character, Glyph, and Cluster maps). Cursor should use cluster position with fractional adjustment based on character number within cluster.
HarfBuzz vs. Pango
- HarfBuzz: y points upward.
- Pango: y points downward for horizontal text, to the right for vertical text. (Pango handles vertical text as if it was horizontal. The user must rotate the result.)
To translate between HarfBuzz and Pango shaping output:
- Horizontal text (and vertical sideways text): invert y directions.
- Vertical text: dy (HarfBuzz) = dx (Pango) - width (Pango); dx (HarfBuzz) = dy (Pango)
Shaping to rendering
- East -> Vertical text with the glyphs oriented base down.
- South -> Vertical text with the glyphs oriented base to the left (sideways).
Pango to Cairo glyph
|East||-dy||width - dx||y (width)|
HarfBuzz to Cairo glyph
Pango to FreeType glyph
|East||width - dx||-dy||x (width)|
- Can not directly use FreeType font metrics for advance as it fails for vertical text if font does not contain vertical metrics (e.g. for non-spacing marks).
- PangoLayout and pango-view get font-metrics from FreeType which doesn't handle vertical text. Avoid!
- HarfBuzz visually centers glyphs in vertical layout.
- Pango logical_rect and hb_font_extents use "Win Ascent" and "Win Descent" values (FontForge OS/2 Metrics Tab), which can be greater than em-size height. (OS/2 will clip rendering to this region. Super-confusing! Don't use Pango logical_rect!
- For vertical text:
- hb-view can choose between using FreeType and getting directly font metrics (so called OpenType functions). There are small differences. In particular:
- With FreeType, vertical upright glyphs are vertically centered with in the "em-box".
- With OpenType, vertical upright glyphs are positioned so their ink-rect is at the alignment point (top of "em-box").
- HarfBuzz has some baseline API but it's not clear if how useful it is.
- Shaping returns glyphs in visual order (left-to-right). For right-to-left text we must reverse them (SVG dictates glyphs are to be drawn in character order). HarfBuzz has some functions to reverse order that may be useful.
- Proper shaping requires knowledge of characters before and after those being shaped. Both Pango and HarfBuzz have mechanisms for this (e.g. pango_shape_full()).