Red Otter logo
Red Otter
0.1.15

Text Rendering

Everything about the process of getting text to the user’s screen.


Overview

Text rendering is based on SDF (Signed Distance Field) font atlas approach. This means that for every font to be rendered, the TTF file must be parsed, glyph information extracted and then the font atlas needs to be generated. The basic and default approach is to use HTML Canvas API to prepare it.


Font lookups

All the information about text that is later needed is bundled in what I call Lookups:

TypeScript
type Lookups = {
  atlas: {
    fontSize: number;
    height: number;
    positions: Array<Vec2>;
    sizes: Array<Vec2>;
    width: number;
  };
  fonts: Array<{
    ascender: number;
    buffer: ArrayBuffer;
    capHeight: number;
    glyphs: Map<number, Glyph>;
    kern: KerningFunction;
    name: string;
    ttf: TTF;
    unitsPerEm: number;
  }>;
  uvs: Map<string, Vec4>;
};

And this is how it is generated:

TypeScript
// Alphabet is a string containing all characters that should be included in
// the atlas.
const alphabet = "AaBbCc…";

const [interTTF, interBoldTTF] = await Promise.all(
  ["/Inter.ttf", "/Inter-SemiBold.ttf"].map((url) =>
    fetch(url).then((response) => response.arrayBuffer()),
  ),
);

const lookups = prepareLookups(
  // Declare all fonts that should be included.
  [
    {
      buffer: interTTF,
      name: "Inter",
      ttf: parseTTF(interTTF),
    },
    {
      buffer: interBoldTTF,
      name: "InterBold",
      ttf: parseTTF(interBoldTTF),
    },
  ],
  // Render at 150px size. The size of the font atlas will be determined by
  // packShelves() algorithm.
  { alphabet, fontSize: 150 },
);

// Generate _one_ font atlas texture for all fonts. In future it would make
// sense to allow splitting for projects that use many fonts.
const fontAtlas = await renderFontAtlas(lookups, { useSDF: true });

API


/font/calculateGlyphQuads.ts

Calculates glyph information for a given font file and optional alphabet.

Parameter
Type and description
ttf
TTF

parsed TTF file (see parseTTF.ts).

alphabet
string

a string of characters to include in the atlas. If not provided, all characters of the font will be included.

returns
Glyph[]

an array of glyph quads.

Type declaration

TypeScript
(ttf: TTF, alphabet?: string) => Glyph[]

/font/generateGlyphToClassMap.ts
Parameter
Type and description
classDef
ClassDefFormat1 | ClassDefFormat2

class definition table.

returns
Map<number, number>

a map from glyph ID to class ID.

Type declaration

TypeScript
(classDef: ClassDefFormat1 | ClassDefFormat2) => Map<number, number>;

/font/generateKerningFunction.ts

Generates a kerning function used by shapeText().

Parameter
Type and description
ttf
TTF

parsed TTF file (see parseTTF()).

returns
KerningFunction

a function that takes two glyph IDs and returns the kerning value.

Type declaration

TypeScript
(ttf: TTF) => KerningFunction;

function

parseTTF

/font/parseTTF.ts

Main function for parsing TTF files.

Parameter
Type and description
data
ArrayBuffer

TTF file in binary format.

returns
TTF

a parsed TTF object.

Type declaration

TypeScript
(data: ArrayBuffer) => TTF;

/font/prepareLookups.ts

This is generally extension of the font parsing process.

Parameter
Type and description
fontFiles
{ buffer: ArrayBuffer; name: string; ttf: TTF; }[]

an array of font files to parse.

options
{ alphabet?: string; fontSize?: number; }

optional parameters.

returns
Lookups

a set of lookups for the font atlas.

Type declaration

TypeScript
(
  fontFiles: { buffer: ArrayBuffer; name: string; ttf: TTF }[],
  options?: { alphabet?: string; fontSize?: number },
) => Lookups;

/font/renderFontAtlas.ts
Parameter
Type and description
lookups
Lookups

see prepareLookups().

options
{ alphabet?: string; useSDF?: boolean; }

optional parameters like alphabet or whether SDF should be used to render the atlas.

returns
Promise<ImageBitmap>

an image bitmap of the font atlas.

Type declaration

TypeScript
(lookups: Lookups, options?: { alphabet?: string; useSDF?: boolean }) =>
  Promise<ImageBitmap>;

/font/renderFontAtlas.ts

Helper function for placing glyphs in the font atlas.

Parameter
Type and description
fontSize
number
returns
number

gap size based on the fontSize.

Type declaration

TypeScript
(fontSize: number) => number;

function

shapeText

/font/shapeText.ts

The most important function for getting text on the screen. Given a string and font data, finds the positions and sizes of each character.

Parameter
Type and description
lookups
Lookups

metadata of fonts.

fontName
string

name of the font.

fontSize
number

size of the font in pixels.

lineHeight
number

height of a line in pixels.

text
string

text to shape.

textAlign
TextAlign

alignment of the text.

maxWidth
number

maximum width of the text. If the text is longer than this, it will be wrapped. Defaults to Number.POSITIVE_INFINITY.

noWrap
boolean

if true, the text will not be wrapped.

returns
Shape

a shape object that can be used to render the text.

Type declaration

TypeScript
(
  lookups: Lookups,
  fontName: string,
  fontSize: number,
  lineHeight: number,
  text: string,
  textAlign: TextAlign,
  maxWidth?: number,
  noWrap?: boolean,
) => Shape;

function

toSDF

/font/toSDF.ts

Takes ImageData and returns a new ImageData() with the SDF applied.

Parameter
Type and description
imageData
ImageData
width
number
height
number
radius
number
returns
ImageData

Type declaration

TypeScript
(imageData: ImageData, width: number, height: number, radius: number) =>
  ImageData;

/font/BinaryReader.ts

A module for reading binary data. Used internally by parseTTF(). Keeps track of the current position, assuming sequential reads.

Field
Type and description
view
DataView
position
number
method

getUint16

Read two bytes as an unsigned integer and advance the position by two bytes.

Type declaration

TypeScript
() => number;
method

getInt16

Read two bytes as a signed integer and advance the position by two bytes.

Type declaration

TypeScript
() => number;
method

getUint32

Read four bytes as an unsigned integer and advance the position by four bytes.

Type declaration

TypeScript
() => number;
method

getInt32

Read four bytes as a signed integer and advance the position by four bytes.

Type declaration

TypeScript
() => number;
method

getFixed

Read four bytes as a fixed-point number (2 bytes integer and 2 byte fraction) and advance the position by four bytes.

Type declaration

TypeScript
() => number;
method

getDate

Read eight bytes as a date (seconds since 1904-01-01 00:00:00) without advancing the position.

Type declaration

TypeScript
() => Date;
method

getFWord

Alias for getUint16.

Type declaration

TypeScript
() => number;
method

getString

Read a string of the given length and advance the position by that length.

Parameter
Type and description
length
number
returns
string

Type declaration

TypeScript
(length: number) => string;

Look up array slice of the given length at the current position without advancing it.

Parameter
Type and description
offset
number
length
number
returns
Uint8Array

Type declaration

TypeScript
(offset: number, length: number) => Uint8Array;

Get the current position.

Type declaration

TypeScript
() => number;

Set the current position.

Parameter
Type and description
position
number
returns
void

Type declaration

TypeScript
(position: number) => void
method

runAt

Run the given action at the given position, restoring the original position afterwards.

Parameter
Type and description
position
number
action
() => T
returns
T

Type declaration

TypeScript
<T,>(position: number, action: () => T) => T;

Copyright © Tomasz Czajęcki 2023