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
A number of changes we should make to Inkscape's code even if it is not completely rewritten.
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).
The use of USE_PANGO_WIN32 (directly using Window fonts) was disabled in 2011. Some functionality (e.g. variable fonts) aren't implemented for PANGO_WIN32.
Remove Support for Bitmap Fonts
Pango has dropped support for bitmap fonts (and type 1 fonts) with Pango 1.44. (Bitmap glyphs are still supported inside OpenType fonts.)
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.
Mapping characters into glyphs.
HarfBuzz vs. Pango Coordinates
- 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()).
Locating the baseline, ascent, and descent of glyphs is important for laying out text. (See http://tavmjong.free.fr/blog/?p=1632.)
- Face or Font Face
- A collection of glyphs with the same style (family, slant, weigth, etc.) used to render text.
- A font face with a specified size.
- Nominally a box with the width and height equivalent to the width of the 'M' glyph. The 'em' box is scaled to the CSS 'font-size'. It is the basic unit in font design, usually 1000 (OpenType or 1024/2048 (TrueType) designer units in width/height.
- The point at which glyphs are aligned along a line of text. For horizontal text, this is usually the Alphabetic baseline which is at the bottom of most capital letters. Vertical text usually uses a Center baseline. Alternative baselines include the "Hanging" baseline, used by South-Asian scripts and the "Math" baseline. Baselines are important when mixing different sized fonts.
- The distance from the alphabetic baseline to the top of the font. What is meant by the top of the font depends on context. In CSS, it is the top of the 'em' box. Fonts may contain ascent values that differ in meaning. In particular, the OS/2 typo table value winAscent is used on Windows to clip rendering. The OpenType specification use to require the ascent (typoAscender) + descent (typoDescender) equal the em box height. It no longer does so. See: https://glyphsapp.com/learn/vertical-metrics. CSS recommends using the OpenType sTypoAscender value. See https://www.w3.org/TR/CSS2/visudet.html#sTypoAscender.
- The distance from the alphabetic baseline to the bottom of the font. See Ascent.
- Gap/Line spacing
- The extra space between lines of text. Fonts make contain information on the recommended spacing (OS/2 typeLineGap). This is not used by CSS.
- The distance from the baseline to the top of the lower-case 'x' glyph (or equivalent).