tecs.gfx.truetype

TrueType outlines, metrics, coverage rasterization, and signed distance fields.

A face is parsed once from the bytes of a .ttf or .ttc-free single-font file and answers three questions: which glyph a codepoint selects, what that glyph advances, and where its outline runs. rasterize turns one outline into an antialiased coverage bitmap and distanceField turns that bitmap into the field a distance-field text shader samples.

const face, reason = tecs.gfx.truetype.parse(bytes)
assert(face, reason)

const glyph = face:glyphIndex(0x41)
const contours = tecs.gfx.truetype.outline(face, glyph, 48.0 / face.unitsPerEm)

Everything here works in font units until a scale is supplied. A font unit is one of unitsPerEm divisions of the em square, so dividing by unitsPerEm gives em units and multiplying those by a point size gives pixels.

Only the glyf outline format is read. A face whose outlines live in a CFF table, which is what an OpenType font built from PostScript curves carries, parses to a failure reason rather than to empty glyphs. Kerning comes from a format-zero kern table when one is present; GPOS positioning is not read, so a font that kerns only through GPOS lays out with its advance widths alone.

Module contents

Types

TypeKindDescription
FacerecordA parsed font file.
OutlinetypeOne glyph outline, as closed contours of flattened points.

Functions

FunctionKindDescription
distanceFieldfunctionTurns one coverage bitmap into a signed distance field.
outlinefunctionReturns one glyph's flattened outline and its bounding box.
parsefunctionParses a single-font TrueType file.
rasterizefunctionFills one outline into an antialiased coverage bitmap.

Types#

Facerecord#

record Face
    unitsPerEm: integer
    ascender: integer
    descender: integer
    lineGap: integer
    numGlyphs: integer

    glyphIndex: function(borrows self: Face, codepoint: integer): integer
    advanceOf: function(borrows self: Face, glyph: integer): number
    bearingOf: function(borrows self: Face, glyph: integer): number
    kerningOf: function(exclusive self: Face, left: integer, right: integer): number
end

A parsed font file.

Immutable once parse returns, apart from the kerning table it fills in on first use.

Methods

glyphIndex#
glyphIndex: function(borrows self: Face, codepoint: integer): integer

Returns the glyph one codepoint selects.

Zero is the .notdef glyph rather than an error, which is the glyph a font draws for a character it does not carry, so a caller renders it instead of branching.

Arguments
NameTypeDescription
borrows selfFace
codepointinteger
Returns
TypeDescription
integer
advanceOf#
advanceOf: function(borrows self: Face, glyph: integer): number

Returns one glyph's horizontal advance in font units.

Arguments
NameTypeDescription
borrows selfFace
glyphinteger
Returns
TypeDescription
number
bearingOf#
bearingOf: function(borrows self: Face, glyph: integer): number

Returns one glyph's left side bearing in font units.

Arguments
NameTypeDescription
borrows selfFace
glyphinteger
Returns
TypeDescription
number
kerningOf#
kerningOf: function(exclusive self: Face, left: integer, right: integer): number

Returns the kerning between two glyphs in font units.

Reads the pairs on first use and keeps them, because a face that kerns is asked about most of its adjacent pairs within one line of text.

Arguments
NameTypeDescription
exclusive selfFace
leftinteger
rightinteger
Returns
TypeDescription
number

Fields

unitsPerEm#
unitsPerEm: integer

Read-only. Reports the em square's divisions, which every font unit in this face is one of.

ascender#
ascender: integer

Read-only. Reports the distance from the baseline to the top of the typographic ascent, in font units.

descender#
descender: integer

Read-only. Reports the distance from the baseline to the bottom of the typographic descent, in font units. It is negative in every ordinary font.

lineGap#
lineGap: integer

Read-only. Reports the extra leading between two lines, in font units.

numGlyphs#
numGlyphs: integer

Read-only. Reports how many glyphs the face holds, including .notdef at index zero.

Outlinetype#

type Outline = {{number}}

One glyph outline, as closed contours of flattened points.

Each contour is a flat array of alternating x and y in font units, with y increasing upward the way the font file stores it. The last point joins the first; the closing segment is not repeated.

Functions#

distanceFieldfunction#

function distanceField(coverage: {integer}, width: integer, height: integer, spread: number): {integer}

Turns one coverage bitmap into a signed distance field.

The field is stored the way a distance-field shader expects it: 0.5 sits on the outline, values above it are inside, and one texel of distance is 0.5 / spread of the stored range. A shader recovers coverage by smoothstepping around 0.5.

Distance comes from a two-pass sequential Euclidean transform over the thresholded coverage, which is exact for the texel centers it visits and within a fraction of a texel elsewhere. That is well inside what a spread of several texels can represent.

Arguments

NameTypeDescription
coverage{integer}

the bitmap rasterize produced

widthinteger

the bitmap width in texels

heightinteger

the bitmap height in texels

spreadnumber

the distance in texels the field reaches on each side of the outline, which must be positive

Returns

TypeDescription
{integer}

a width times height array of field values from zero to 255, indexed from one, which the caller owns

outlinefunction#

function outline(self: Face, glyph: integer, scale: number): Outline, number, number, number, number

Returns one glyph's flattened outline and its bounding box.

The box comes from the flattened points rather than from the glyph header, so it encloses exactly what rasterize will fill. An empty glyph, which is what a space is, returns no contours and a zero box.

Arguments

NameTypeDescription
selfFace

the parsed face

glyphinteger

the glyph index

scalenumber

the font units to pixels factor the flattening is tuned for

Returns

TypeDescription
Outline

the contours, in font units with y increasing upward

number

the smallest x, then the smallest y, then the largest x, then the largest y, all in font units

number
number
number

parsefunction#

function parse(bytes: string): Face?, string?

Parses a single-font TrueType file.

Arguments

NameTypeDescription
bytesstring

the complete contents of a .ttf or .otf file

Returns

TypeDescription
Face?

the face, or nil when the bytes are not a readable TrueType font

string?

why the bytes were rejected, when unsuccessful

rasterizefunction#

function rasterize(contours: Outline, width: integer, height: integer, originX: number, originY: number, scale: number): {integer}

Fills one outline into an antialiased coverage bitmap.

The bitmap runs left to right and top to bottom, so it uploads as image rows without a flip. Font y increases upward and texel y increases downward, which is the only place the two conventions meet.

Arguments

NameTypeDescription
contoursOutline

the flattened contours, in font units

widthinteger

the bitmap width in texels, which must be positive

heightinteger

the bitmap height in texels, which must be positive

originXnumber

the font-unit x that lands on the bitmap's left edge

originYnumber

the font-unit y that lands on the bitmap's top edge

scalenumber

the font units to texels factor

Returns

TypeDescription
{integer}

a width times height array of coverage from zero to 255, indexed from one, which the caller owns