gfx/font.h
Font: a typeface at a size, and drawing text with it.
#include <sonore/gfx/font.h>- 2types
- 1other declaration
- Guides: The native editor
On this page
Design notes
Where the design units go
A typeface stores everything in its own grid -- 2048 units per em for most fonts, 1000 for some. A Font is that grid divided by unitsPerEm and multiplied by a point size, so every measurement out of this class is in PIXELS and no caller ever handles a design unit.
That conversion happening in one place is the whole reason this class exists. Done at each call site it is a multiply somebody will forget, and the symptom is text that is right in one panel and 2048 times too big in another.
Glyphs are cached as PATHS, not as bitmaps
The usual design caches a rendered bitmap per glyph per size. This caches the outline, and rasterises it with the same code that draws everything else.
It is slower per draw and it is the right trade here: a plugin's editor redraws a few hundred short strings, not a document; the cache is size-independent so a resizable window does not throw it away; and there is no second rasteriser whose antialiasing differs from the first. One quality, one set of tests.
Text that is not a glyph per character
A string with right-to-left letters, combining marks or joining scripts goes through the bidirectional algorithm (bidi.h) and OpenType shaping (text_shaper.h): its runs ordered as they are read, each shaped with the face that has its glyphs. Everything else -- most of what an interface says -- is drawn a glyph per character as it always was, the same pixels.
Types 2
Where each character of one line of text is drawn, for a caret and a selection: Font::layoutCharacters makes one.
A caret stands BETWEEN characters, and in shaped text a character does not always have a place of its own: a ligature, a conjunct, a letter with its marks are one glyph or a stack of them, and a right-to-left word reads from the right. Each such cluster is shared out among its characters, left to right or right to left as it reads, and only its first character is a stop -- a caret inside a conjunct has nowhere to be drawn. A right-to-left character has its caret on its right.
A typeface at a size: what text is measured and drawn with.
The reference's Font, with drawing and wrapping on it rather than on Graphics. The height is the em in pixels (logical units, like everything in the component tree), and every measurement out of it is in pixels too -- see the file comment. A default-constructed Font, or one with no usable face, is not valid: it measures 0 and draws nothing.
Copying is cheap and SHARES the caches -- outlines, flattened glyphs, shaped strings, small-text coverage, colour-glyph pictures -- but withHeight, withFeature, withVariation and withoutFallback each make a Font with caches of its own. So make the Font once (in a constructor, or when the size changes) and keep it, rather than building one in paint().
Any thread may measure and draw with a Font, and several at once with one Font or its copies: a Graphics draws on whatever thread paints, a host may run two editors on two threads, and an image may be rendered on a worker. The caches are filled, and emptied when full, as text is drawn, so a Font and its copies share one lock: taken once a call, not once a glyph, by draw and appendString, and by measuring only where it shapes or reads a variable font's advances -- a plain label is measured without it. One thread pays an uncontended lock a label. Threads drawing with one Font at the same moment take turns; with a Font each (withHeight makes one) they do not.
Enums 1
- enum
enum class JustifyWhere a line goes across the width it is given (Font::drawIn, drawWrapped).gfx::Justify · font.h:59