I’ve discussed before how WebKit lays out text found in the HTML you send it, but GTK needs to do much the same thing itself. Today I want to describe the supporting libraries GTK (and to a large extent WebKitGTK) uses for this task.
In some ways this is simpler, because it only needs consider one fully-styled paragraph at a time. With GTK’s CSS engine only covering what ammounts to block elements.
But fundamentally text is a cultural construct, and hence complex.
GTK directly calls Pango (means “every language”) for it’s text rendering needs.
At it’s simplest Pango takes a string and ordered linked-list of rich-text “attributes” to fit as much as it can in each line, before rendering it with Cairo or other platform libraries. These inputs may be parsed from a simple XML format, with some help from GLib.
It’s also worth mentioning that GTK4 uses FreeType and GPU-compositing for the rendering.
But there’s a lot around the edges…
PangoLayout and it’s internal .check_lines() method is the main entrypoint here, though PangoLayout also implements logic for hit-testing and moving the text cursor for GTK.
And PangoLayout stores the inputs and outputs from .check_lines() as object properties, with the output being a tree: The Layout contains Lines, and Lines contains Runs
The first thing .check_lines() does is to finalize the attributes, and seperate out those that effecting break/shape, and seperately for itemization.
Whilst adding “items” to a line, a PangoLayout will first make sure the characters are translated to “glyphs” via Harfbuzz before it determines whether:
- the entire item can fit in the line [so repeat],
- there was nothing,
- it partially fits,
- it doesn’t fit,
- or it was a newline.
These cases are handled differently. There’s several conditions it checks to determine this, both before and after it examines the characters themselves.
Throughout that process a PangoContext subclass calls platform APIs to load fonts and measure the size of text in those fonts. And there’s a PangoRenderer template class which iterates over the computed layout in order to dispatch drawing operations to the system graphics library.
On GNU/Linux Pango uses FontConfig (with an in-memory cache), FreeType, and Cairo for system libraries.
I won’t discuss Cairo here.
Fribidi splits text by it’s writing direction according to Unicode’s standard algorithm. And I really struggle to say much more than that, because I don’t really understand the issue beyond knowing it exists.
Harfbuzz is a library that converts the characters of a string into positioned “glyphs” for display. The main purpose of this is to fix up the “kerning” (spacing between letters), but it’s also used to:
- Position accents
- Align features so the text appears joined-up
- Make certain character sequences look either Unicode characters in fancy new programming fonts (no thank you, that sounds confusing!)
Harfbuzz does this mostly by interpreting domain-specific bytecode found in the font files, though it also includes fallback algorithms I can’t explain further.
The majority of Harfbuzz meanwhile is dedicated to reading the OpenType font files, which is pretty easy due to it arranging data in “tables” (arrays of fixed-size structs).
A bit of nuance I’ve seen lost on even Google is that a font family can consist of multiple font files. Because not only might there be multiple styles (italic, bold, etc), but you might also want to correct smaller sizes so it looks correct rather than being mathematically correct.
FontConfig is some infrastructure Pango & WebKitGTK uses to handle this.
FontConfig consists of:
- A memory-mapped database (with relative pointers) that’s linearly scanned for the closest match.
- A database schema describing font files.
- An interpreter for XML configuration files to preprocessing fonts before they’re loaded into that database.
Because it’s a linear scan, performance is only alright. As such callers often want to wrap FontConfig with a simple in-memory cache.
And finally, FreeType is a library for reading font files of various formats.
That is the majority of it’s code is dedicated to parsing these various formats (raster and vector) to consistant structures, and providing callbacks to read out specified glyphs.
But also contains utilities to embolden those glyphs, compute bounding boxes for some text in a font it has parsed, convert from vector fonts to raster fonts, etc.
The most complex of FreeType’s secondary tasks is to convert vector fonts to raster fonts. Because for that it implemented a simplistic vector graphics library (like Cairo).
To do so it interprets drawing instructions from the font file and preprocesses it into a stroke “path”. Whose data will then copied over into a “profile” to be sorted by x or y, so it can be iterated over to produce “scanlines” to be filled in.