Files
pdf-odin-llm/font_truetype.odin
T
2026-09-25 23:32:29 +02:00

1038 lines
39 KiB
Odin

package pdf
import "core:math"
import "core:mem"
// TrueType and OpenType font programs: the `glyf`/`loca` outlines, the `cmap`
// character map, `hmtx` advances and `post` glyph names.
//
// References are to ISO/IEC 14496-22 (Open Font Format), whose table layouts
// are identical to Apple's TrueType Reference Manual for everything read here.
//
// Nothing in this file knows about PDF. Bytes in, outlines out: a font program
// is a self-contained format that happens to be embedded in PDF, and keeping
// the two apart is what makes this separately testable and what lets the same
// reader serve a FontFile2, a CIDFontType2 and an OpenType wrapper alike.
//
// The fonts this sees are the most malformed data in a PDF. Subsetters strip
// tables, rewrite `loca` badly, leave `numGlyphs` disagreeing with the data,
// and truncate the last table in the file. Every offset here is therefore
// bounds-checked at the point of use and every loop is bounded, and a glyph
// that cannot be read draws nothing rather than failing the font: the page
// still has to come out.
//
// MEMORY: `tt_parse` takes an `allocator` and makes exactly two lasting
// allocations from it -- the TrueType_Font itself and, when the font has a
// usable `post` table, its glyph-name array. Everything else it keeps is a
// sub-slice of `data`, which the caller owns and must keep alive for as long
// as the font. There is no destructor: the intended allocator is the
// Document's arena. `tt_parse` and `tt_glyph_path` each take scratch from
// `context.allocator` as well, and free it before returning.
// A composite glyph may reference other composites. Five levels is deeper than
// any real font goes; a subsetter that produces a glyph referencing itself is
// stopped here.
@(private)
MAX_TT_COMPONENT_DEPTH :: 6
// Components one glyph may expand to, across all nesting levels. A composite
// chain is a graph the file controls, so it needs fuel as well as a depth cap.
@(private)
MAX_TT_COMPONENTS :: 512
// Points one glyph may expand to. The format's own ceiling is 32767 points per
// simple glyph; a composite can legitimately hold several of those.
@(private)
MAX_TT_GLYPH_POINTS :: 1 << 18
// `numGlyphs` is a u16 in `maxp`, so this is the format's own limit and not an
// invented one.
@(private)
MAX_TT_GLYPHS :: 65536
// Table records one directory may name. The format allows 65535; no real font
// has fifty.
@(private)
MAX_TT_TABLES :: 512
// Sfnt tags, big-endian, as they appear in the file.
@(private) TT_TAG_TTCF :: u32(0x74746366) // 'ttcf', a TrueType Collection
@(private) TT_TAG_OTTO :: u32(0x4F54544F) // 'OTTO', CFF outlines
@(private) TT_TAG_TRUE :: u32(0x74727565) // 'true', Apple's variant
@(private) TT_TAG_V1 :: u32(0x00010000)
@(private) TT_TAG_V2 :: u32(0x00020000)
TrueType_Font :: struct {
data: []byte,
// Tables, as sub-slices of `data`. Empty when the font has none.
glyf: []byte,
loca: []byte,
hmtx: []byte,
cff: []byte,
units_per_em: f64,
num_glyphs: int,
num_h_metrics: int,
// `head.indexToLocFormat`: false for u16 offsets stored halved, true for
// u32 offsets.
long_loca: bool,
// An OpenType font whose outlines are CFF: no `glyf`, and `tt_cff_data`
// hands the table to the CFF reader.
is_otto: bool,
// The `cmap` subtables worth having, chosen once when the font is parsed.
// All three are exposed rather than merged because the rules for which one
// a PDF font should use depend on the font dictionary's symbolic flag,
// which this file deliberately knows nothing about.
cmap_unicode: []byte, // (3,10), (3,1) or a platform-0 table
cmap_symbol: []byte, // (3,0): codes are conventionally 0xF000 + a byte
cmap_mac: []byte, // (1,0): Mac Roman byte codes
// `post` glyph names indexed by glyph id, "" where the font gives none.
// Entries point into `data` or at a static name; the slice itself is the
// font's only other allocation.
names: []string,
}
// ---------------------------------------------------------------------------
// Byte access
//
// Every reader returns zero for an offset that is not wholly inside the slice.
// A header field that reads as zero degrades into a default a few lines later,
// which is what a truncated table should do; the loops that walk arrays check
// their own bounds before reading, because there a zero would be data.
// ---------------------------------------------------------------------------
@(private)
tt_u8 :: proc "contextless" (b: []byte, off: int) -> u8 {
if off < 0 || off >= len(b) { return 0 }
return b[off]
}
@(private)
tt_u16 :: proc "contextless" (b: []byte, off: int) -> u16 {
if off < 0 || off + 2 > len(b) { return 0 }
return u16(b[off]) << 8 | u16(b[off + 1])
}
@(private)
tt_i16 :: proc "contextless" (b: []byte, off: int) -> i16 {
return i16(tt_u16(b, off))
}
@(private)
tt_u32 :: proc "contextless" (b: []byte, off: int) -> u32 {
if off < 0 || off + 4 > len(b) { return 0 }
return u32(b[off]) << 24 | u32(b[off + 1]) << 16 | u32(b[off + 2]) << 8 | u32(b[off + 3])
}
// tt_f2dot14 reads the fixed-point number composite transforms are written in:
// a signed 16-bit value with 14 fraction bits.
@(private)
tt_f2dot14 :: proc "contextless" (b: []byte, off: int) -> f64 {
return f64(tt_i16(b, off)) / 16384
}
// tt_slice returns the sub-slice a table record names, clamped to the data
// that is actually there. A last table whose length runs past the end of the
// file is the single most common damage in an embedded font program, so it is
// truncated rather than dropped.
@(private)
tt_slice :: proc "contextless" (data: []byte, off, length: u32) -> []byte {
if u64(off) >= u64(len(data)) { return nil }
o := int(off)
n := len(data) - o
if u64(length) < u64(n) { n = int(length) }
return data[o:][:n]
}
// ---------------------------------------------------------------------------
// Parsing
// ---------------------------------------------------------------------------
// tt_parse reads a font program's table directory.
//
// `data` is borrowed, not copied: the font holds sub-slices of it and the
// caller must keep it alive. The font and its glyph-name table come from
// `allocator`.
@(private)
tt_parse :: proc(data: []byte, allocator := context.allocator) -> (^TrueType_Font, Error) {
if len(data) < 12 { return nil, .Bad_TrueType }
base := 0
tag := tt_u32(data, 0)
if tag == TT_TAG_TTCF {
// A TrueType Collection holds several fonts sharing tables. A PDF
// FontFile2 has no way to say which face it means, so the first one is
// the only defensible choice. ISO 14496-22, 5.2.
if tt_u32(data, 8) == 0 { return nil, .Bad_TrueType }
base = int(tt_u32(data, 12))
if base < 0 || base + 12 > len(data) { return nil, .Bad_TrueType }
tag = tt_u32(data, base)
}
otto := tag == TT_TAG_OTTO
if tag != TT_TAG_V1 && tag != TT_TAG_V2 && tag != TT_TAG_TRUE && !otto {
return nil, .Bad_TrueType
}
num_tables := int(tt_u16(data, base + 4))
if num_tables > MAX_TT_TABLES { num_tables = MAX_TT_TABLES }
// A directory truncated mid-record still names the tables before it.
avail := (len(data) - (base + 12)) / 16
if avail < 0 { avail = 0 }
if num_tables > avail { num_tables = avail }
if num_tables == 0 { return nil, .Bad_TrueType }
head, maxp, hhea, post, cmap: []byte
f := new(TrueType_Font, allocator)
if f == nil { return nil, .Out_Of_Memory }
f.data = data
f.is_otto = otto
for i in 0 ..< num_tables {
rec := base + 12 + i * 16
t := tt_slice(data, tt_u32(data, rec + 8), tt_u32(data, rec + 12))
if len(t) == 0 { continue }
// The first record for a tag wins. Directories with a tag twice exist,
// and no reader agrees on which one is meant; taking the first at
// least makes this deterministic.
switch tt_u32(data, rec) {
case 0x68656164: if head == nil { head = t } // 'head'
case 0x6D617870: if maxp == nil { maxp = t } // 'maxp'
case 0x68686561: if hhea == nil { hhea = t } // 'hhea'
case 0x686D7478: if f.hmtx == nil { f.hmtx = t } // 'hmtx'
case 0x676C7966: if f.glyf == nil { f.glyf = t } // 'glyf'
case 0x6C6F6361: if f.loca == nil { f.loca = t } // 'loca'
case 0x706F7374: if post == nil { post = t } // 'post'
case 0x636D6170: if cmap == nil { cmap = t } // 'cmap'
case 0x43464620: if f.cff == nil { f.cff = t } // 'CFF '
}
}
// ISO 14496-22, 5.2.2: unitsPerEm at 18, indexToLocFormat at 50.
f.units_per_em = f64(tt_u16(head, 18))
if f.units_per_em == 0 {
// Zero would make every glyph infinitely large. 1000 is the value a
// PDF font matrix assumes when it has nothing better.
f.units_per_em = 1000
}
f.long_loca = tt_i16(head, 50) != 0
f.num_glyphs = int(tt_u16(maxp, 4))
if f.num_glyphs == 0 {
// No usable maxp. loca has one more entry than there are glyphs, which
// is enough to keep the outlines reachable.
if f.long_loca {
f.num_glyphs = len(f.loca) / 4 - 1
} else {
f.num_glyphs = len(f.loca) / 2 - 1
}
if f.num_glyphs < 0 { f.num_glyphs = 0 }
}
if f.num_glyphs > MAX_TT_GLYPHS { f.num_glyphs = MAX_TT_GLYPHS }
// ISO 14496-22, 5.2.3: numberOfHMetrics is the last field of hhea.
f.num_h_metrics = int(tt_u16(hhea, 34))
if f.num_h_metrics > f.num_glyphs { f.num_h_metrics = f.num_glyphs }
if f.num_h_metrics == 0 && len(f.hmtx) >= 4 && f.num_glyphs > 0 {
// A hhea that is missing or truncated leaves no metric count, but the
// hmtx beside it is usually intact. One advance shared by every glyph
// is a poor answer; zero for every glyph is a worse one, because it
// piles the whole line of text on one spot.
f.num_h_metrics = 1
}
tt_select_cmaps(f, cmap)
tt_parse_post(f, post, allocator)
return f, .None
}
// tt_glyph_count is the number of glyph ids the font defines. Glyph 0 is
// .notdef and is included.
@(private)
tt_glyph_count :: proc(f: ^TrueType_Font) -> int {
if f == nil { return 0 }
return f.num_glyphs
}
// tt_units_per_em is the side of the em square in font units: the divisor that
// turns a glyph outline into text space. 1000 for a font that gives none.
@(private)
tt_units_per_em :: proc(f: ^TrueType_Font) -> f64 {
if f == nil { return 1000 }
return f.units_per_em
}
// tt_has_glyf reports whether the font carries TrueType outlines. An OpenType
// font with CFF outlines does not; `tt_cff_data` has its font program.
@(private)
tt_has_glyf :: proc(f: ^TrueType_Font) -> bool {
if f == nil { return false }
return len(f.glyf) > 0 && len(f.loca) >= 4
}
// tt_cff_data returns the 'CFF ' table for an OpenType font with CFF outlines,
// or an empty slice. The bytes belong to the data passed to `tt_parse`.
@(private)
tt_cff_data :: proc(f: ^TrueType_Font) -> []byte {
if f == nil { return nil }
return f.cff
}
// ---------------------------------------------------------------------------
// Advances
// ---------------------------------------------------------------------------
// tt_glyph_advance is the horizontal advance in font units, 0 when the font
// has no usable `hmtx`.
@(private)
tt_glyph_advance :: proc(f: ^TrueType_Font, gid: u16) -> f64 {
if f == nil { return 0 }
return f64(tt_hmtx_advance(f, tt_metrics_gid(f, gid, 0)))
}
// tt_hmtx_advance reads `hmtx` directly. The table stores advances only for
// the first numberOfHMetrics glyphs; every glyph after that shares the last
// one, which is how a monospaced or a right-heavy font stays small.
// ISO 14496-22, 5.2.4.
@(private)
tt_hmtx_advance :: proc(f: ^TrueType_Font, gid: u16) -> u16 {
n := f.num_h_metrics
if n <= 0 { return 0 }
i := int(gid)
if i >= n { i = n - 1 }
off := i * 4
if off + 2 > len(f.hmtx) {
// A truncated hmtx still answers for the glyphs it does hold, and the
// last of those stands in for the rest.
have := len(f.hmtx) / 4
if have <= 0 { return 0 }
off = (have - 1) * 4
}
return tt_u16(f.hmtx, off)
}
// tt_metrics_gid follows USE_MY_METRICS: a composite that sets it on one of
// its components takes that component's advance rather than its own. In a
// well-formed font the two agree and this changes nothing; in a font whose
// hmtx was rewritten by a subsetter it is the only remaining truth.
@(private)
tt_metrics_gid :: proc(f: ^TrueType_Font, gid: u16, depth: int) -> u16 {
if depth >= MAX_TT_COMPONENT_DEPTH || !tt_has_glyf(f) { return gid }
g, ok := tt_glyph_data(f, gid)
if !ok || len(g) < 10 || tt_i16(g, 0) >= 0 { return gid }
p := 10
for _ in 0 ..< MAX_TT_COMPONENTS {
if p + 4 > len(g) { break }
flags := tt_u16(g, p)
cgid := tt_u16(g, p + 2)
p += 4
p += 4 if flags & 0x0001 != 0 else 2
switch {
case flags & 0x0008 != 0: p += 2
case flags & 0x0040 != 0: p += 4
case flags & 0x0080 != 0: p += 8
}
if flags & 0x0200 != 0 {
return tt_metrics_gid(f, cgid, depth + 1)
}
if flags & 0x0020 == 0 { break }
}
return gid
}
// ---------------------------------------------------------------------------
// Outlines
// ---------------------------------------------------------------------------
@(private)
Tt_Point :: struct {
x, y: f64,
on: bool,
}
// Tt_Contour is one closed contour as a half-open range of Tt_Outline.pts.
// Ranges are kept rather than end indices because a composite concatenates the
// points of several glyphs and a malformed contour in the middle must not
// shift the ones after it.
@(private)
Tt_Contour :: struct {
start, end: int,
}
// Tt_Outline accumulates a glyph's points before any of them is emitted. A
// composite's point-matching offsets are expressed in point indices, so the
// points have to exist as an array at least until the component that follows
// them is placed.
@(private)
Tt_Outline :: struct {
pts: [dynamic]Tt_Point,
contours: [dynamic]Tt_Contour,
// Scratch for one simple glyph's flag array, reused across components.
flags: [dynamic]u8,
fuel: int,
}
// tt_glyph_path appends glyph `gid`'s outline to `out`, in font units (divide
// by `tt_units_per_em` for text space).
//
// Returns .No_Glyph when the font has no outline for the id at all, and
// .Bad_TrueType when the glyph's data is malformed -- in which case whatever
// could be decoded has still been appended, because half a glyph is closer to
// right than a blank page. An empty glyph, which is what a space is, is not an
// error.
@(private)
tt_glyph_path :: proc(f: ^TrueType_Font, gid: u16, out: ^Path) -> Error {
if f == nil || out == nil { return .Invalid_Argument }
if !tt_has_glyf(f) { return .No_Glyph }
if int(gid) >= f.num_glyphs { return .No_Glyph }
ol := Tt_Outline{
pts = make([dynamic]Tt_Point, 0, 64),
contours = make([dynamic]Tt_Contour, 0, 8),
flags = make([dynamic]u8, 0, 64),
fuel = MAX_TT_COMPONENTS,
}
defer {
delete(ol.pts)
delete(ol.contours)
delete(ol.flags)
}
err := tt_build_glyph(f, gid, MATRIX_IDENTITY, 0, &ol)
for c in ol.contours {
// Fewer than two points enclose no area; emitting them would leave a
// degenerate subpath for the rasteriser to step over.
if c.start < 0 || c.end > len(ol.pts) || c.end - c.start < 2 { continue }
tt_emit_contour(out, ol.pts[c.start:c.end])
}
return err
}
// tt_glyph_data returns the bytes of one glyph's `glyf` entry.
//
// ok is false when `loca` does not cover the id; an empty slice with ok means
// the glyph is legitimately blank. A `loca` whose entries do not increase is a
// subsetter bug seen often enough that a non-increasing pair is read as a
// blank glyph rather than as damage.
@(private)
tt_glyph_data :: proc(f: ^TrueType_Font, gid: u16) -> ([]byte, bool) {
i := int(gid)
start, end: int
if f.long_loca {
if (i + 2) * 4 > len(f.loca) { return nil, false }
start = int(tt_u32(f.loca, i * 4))
end = int(tt_u32(f.loca, i * 4 + 4))
} else {
if (i + 2) * 2 > len(f.loca) { return nil, false }
// Short offsets are stored halved, so the table can reach 128K.
start = int(tt_u16(f.loca, i * 2)) * 2
end = int(tt_u16(f.loca, i * 2 + 2)) * 2
}
if end <= start || start >= len(f.glyf) { return nil, true }
if end > len(f.glyf) { end = len(f.glyf) }
return f.glyf[start:end], true
}
@(private)
tt_build_glyph :: proc(f: ^TrueType_Font, gid: u16, m: Matrix, depth: int, ol: ^Tt_Outline) -> Error {
if depth > MAX_TT_COMPONENT_DEPTH { return .Recursion_Limit }
g, ok := tt_glyph_data(f, gid)
if !ok { return .No_Glyph }
if len(g) == 0 { return .None }
// The glyph header is numberOfContours and a bounding box: 10 bytes.
if len(g) < 10 { return .Bad_TrueType }
nc := int(tt_i16(g, 0))
if nc >= 0 {
return tt_simple_glyph(g, nc, m, ol)
}
return tt_composite_glyph(f, g, m, depth, ol)
}
// tt_simple_glyph decodes one glyph's contours into `ol`, transformed by `m`.
// ISO 14496-22, 5.3.3.
@(private)
tt_simple_glyph :: proc(g: []byte, n_contours: int, m: Matrix, ol: ^Tt_Outline) -> Error {
if n_contours == 0 { return .None }
ends_off := 10
if ends_off + 2 * n_contours + 2 > len(g) { return .Bad_TrueType }
// The last contour's end point index gives the point count.
n_pts := int(tt_u16(g, ends_off + 2 * (n_contours - 1))) + 1
if n_pts <= 0 || n_pts > MAX_TT_GLYPH_POINTS { return .Bad_TrueType }
if len(ol.pts) + n_pts > MAX_TT_GLYPH_POINTS { return .Bad_TrueType }
p := ends_off + 2 * n_contours
instr := int(tt_u16(g, p))
p += 2 + instr
if p > len(g) { return .Bad_TrueType }
// Flags, with the run-length encoding: a flag with REPEAT set is followed
// by a count of extra points that share it.
clear(&ol.flags)
for len(ol.flags) < n_pts {
if p >= len(g) { return .Bad_TrueType }
fl := g[p]
p += 1
append(&ol.flags, fl)
if fl & 0x08 != 0 {
if p >= len(g) { return .Bad_TrueType }
rep := int(g[p])
p += 1
for _ in 0 ..< rep {
if len(ol.flags) >= n_pts { break }
append(&ol.flags, fl)
}
}
}
base := len(ol.pts)
// X then Y, each a delta from the previous point. A short vector is one
// unsigned byte whose sign comes from the SAME_OR_POSITIVE bit; a long one
// is a signed word, and that same bit set with no short flag means the
// coordinate simply repeats.
x := 0
for k in 0 ..< n_pts {
fl := ol.flags[k]
if fl & 0x02 != 0 {
if p >= len(g) { return .Bad_TrueType }
d := int(g[p])
p += 1
x += d if fl & 0x10 != 0 else -d
} else if fl & 0x10 == 0 {
if p + 2 > len(g) { return .Bad_TrueType }
x += int(tt_i16(g, p))
p += 2
}
append(&ol.pts, Tt_Point{x = f64(x), on = fl & 0x01 != 0})
}
y := 0
for k in 0 ..< n_pts {
fl := ol.flags[k]
if fl & 0x04 != 0 {
if p >= len(g) { return .Bad_TrueType }
d := int(g[p])
p += 1
y += d if fl & 0x20 != 0 else -d
} else if fl & 0x20 == 0 {
if p + 2 > len(g) { return .Bad_TrueType }
y += int(tt_i16(g, p))
p += 2
}
ol.pts[base + k].y = f64(y)
}
if m != MATRIX_IDENTITY {
for k in base ..< len(ol.pts) {
pt := transform_point(m, Point{ol.pts[k].x, ol.pts[k].y})
ol.pts[k].x = pt.x
ol.pts[k].y = pt.y
}
}
prev := 0
for c in 0 ..< n_contours {
e := int(tt_u16(g, ends_off + 2 * c)) + 1
// End indices must increase and stay inside the point array. One that
// does not is skipped rather than taken as a reason to drop the glyph.
if e <= prev || e > n_pts { continue }
append(&ol.contours, Tt_Contour{base + prev, base + e})
prev = e
}
return .None
}
// tt_composite_glyph places other glyphs into this one. ISO 14496-22, 5.3.4.
@(private)
tt_composite_glyph :: proc(f: ^TrueType_Font, g: []byte, m: Matrix, depth: int, ol: ^Tt_Outline) -> Error {
// Point-matching offsets index the points of the composite being built, so
// where its own points start has to be remembered.
glyph_base := len(ol.pts)
p := 10
err := Error.None
for {
if ol.fuel <= 0 { return .Recursion_Limit }
ol.fuel -= 1
if p + 4 > len(g) { return .Bad_TrueType }
flags := tt_u16(g, p)
cgid := tt_u16(g, p + 2)
p += 4
xy := flags & 0x0002 != 0 // ARGS_ARE_XY_VALUES
a1, a2: int
if flags & 0x0001 != 0 { // ARG_1_AND_2_ARE_WORDS
if p + 4 > len(g) { return .Bad_TrueType }
if xy {
a1 = int(tt_i16(g, p))
a2 = int(tt_i16(g, p + 2))
} else {
a1 = int(tt_u16(g, p))
a2 = int(tt_u16(g, p + 2))
}
p += 4
} else {
if p + 2 > len(g) { return .Bad_TrueType }
if xy {
a1 = int(i8(g[p]))
a2 = int(i8(g[p + 1]))
} else {
a1 = int(g[p])
a2 = int(g[p + 1])
}
p += 2
}
cm := MATRIX_IDENTITY
switch {
case flags & 0x0008 != 0: // WE_HAVE_A_SCALE
if p + 2 > len(g) { return .Bad_TrueType }
s := tt_f2dot14(g, p)
p += 2
cm.a, cm.d = s, s
case flags & 0x0040 != 0: // WE_HAVE_AN_X_AND_Y_SCALE
if p + 4 > len(g) { return .Bad_TrueType }
cm.a = tt_f2dot14(g, p)
cm.d = tt_f2dot14(g, p + 2)
p += 4
case flags & 0x0080 != 0: // WE_HAVE_A_TWO_BY_TWO
if p + 8 > len(g) { return .Bad_TrueType }
cm.a = tt_f2dot14(g, p)
cm.b = tt_f2dot14(g, p + 2)
cm.c = tt_f2dot14(g, p + 4)
cm.d = tt_f2dot14(g, p + 6)
p += 8
}
if xy {
dx, dy := f64(a1), f64(a2)
// Whether the offset is in the component's own space or the
// composite's is a genuine disagreement between Apple and
// Microsoft. Unscaled is both the Microsoft behaviour and the
// default every font is drawn with, so it takes an explicit
// SCALED_COMPONENT_OFFSET to get the other one.
if flags & 0x0800 != 0 && flags & 0x1000 == 0 {
v := transform_vector(cm, Point{dx, dy})
dx, dy = v.x, v.y
}
if flags & 0x0004 != 0 { // ROUND_XY_TO_GRID
dx = math.round(dx)
dy = math.round(dy)
}
cm.e, cm.f = dx, dy
}
base := len(ol.pts)
cerr := tt_build_glyph(f, cgid, matrix_mul(cm, m), depth + 1, ol)
if cerr != .None && err == .None { err = cerr }
if !xy {
// The alternative to an offset: arg1 names a point already placed
// in this composite and arg2 the matching point of the component,
// and the component moves so that the two coincide.
i1 := glyph_base + a1
i2 := base + a2
if i1 >= glyph_base && i1 < base && i2 >= base && i2 < len(ol.pts) {
dx := ol.pts[i1].x - ol.pts[i2].x
dy := ol.pts[i1].y - ol.pts[i2].y
if flags & 0x0004 != 0 {
dx = math.round(dx)
dy = math.round(dy)
}
for k in base ..< len(ol.pts) {
ol.pts[k].x += dx
ol.pts[k].y += dy
}
}
}
if flags & 0x0020 == 0 { break } // MORE_COMPONENTS
}
return err
}
// tt_emit_contour turns one quadratic contour into cubics on `out`.
//
// A TrueType contour is a closed quadratic B-spline: on-curve points are on the
// outline, off-curve points are control points, and two consecutive off-curve
// points imply an on-curve point at their midpoint. A contour with no on-curve
// point at all -- an ellipse drawn as four control points -- starts at the
// midpoint of its last and first points.
@(private)
tt_emit_contour :: proc(out: ^Path, pts: []Tt_Point) {
n := len(pts)
if n < 2 { return }
first := -1
for i in 0 ..< n {
if pts[i].on {
first = i
break
}
}
sx, sy: f64
start_i, count: int
if first < 0 {
sx = (pts[n - 1].x + pts[0].x) * 0.5
sy = (pts[n - 1].y + pts[0].y) * 0.5
start_i = 0
count = n
} else {
sx, sy = pts[first].x, pts[first].y
// Walk the other n-1 points; the closing segment back to the start is
// the Close verb, or the trailing curve below.
start_i = first + 1
count = n - 1
}
path_move_to(out, sx, sy)
cx, cy := sx, sy
ctl_x, ctl_y: f64
have_ctl := false
for k in 0 ..< count {
pt := pts[(start_i + k) % n]
if pt.on {
if have_ctl {
tt_quad_to(out, cx, cy, ctl_x, ctl_y, pt.x, pt.y)
have_ctl = false
} else {
path_line_to(out, pt.x, pt.y)
}
cx, cy = pt.x, pt.y
} else {
if have_ctl {
mx := (ctl_x + pt.x) * 0.5
my := (ctl_y + pt.y) * 0.5
tt_quad_to(out, cx, cy, ctl_x, ctl_y, mx, my)
cx, cy = mx, my
}
ctl_x, ctl_y = pt.x, pt.y
have_ctl = true
}
}
if have_ctl {
tt_quad_to(out, cx, cy, ctl_x, ctl_y, sx, sy)
}
path_close(out)
}
// tt_quad_to appends the cubic equal to the quadratic from (cx,cy) through
// control (qx,qy) to (x,y). The two are exactly interconvertible: a cubic whose
// controls sit two thirds of the way from each end towards the quadratic's
// control traces the same curve, so nothing is approximated here.
@(private)
tt_quad_to :: proc(out: ^Path, cx, cy, qx, qy, x, y: f64) {
K :: 2.0 / 3.0
path_curve_to(out,
cx + (qx - cx) * K, cy + (qy - cy) * K,
x + (qx - x) * K, y + (qy - y) * K,
x, y)
}
// ---------------------------------------------------------------------------
// cmap
// ---------------------------------------------------------------------------
// tt_select_cmaps picks the best subtable of each of the three kinds this
// reader offers. ISO 14496-22, 5.2.6.
//
// Choosing among the platform/encoding pairs is where readers get symbolic
// fonts wrong, so all three kinds are kept and none is merged into another:
// which one a PDF font should consult depends on its descriptor flags and its
// /Encoding, and that decision belongs in font.odin.
@(private)
tt_select_cmaps :: proc(f: ^TrueType_Font, cm: []byte) {
if len(cm) < 4 { return }
n := int(tt_u16(cm, 2))
avail := (len(cm) - 4) / 8
if avail < 0 { avail = 0 }
if n > avail { n = avail }
best := 0
for i in 0 ..< n {
rec := 4 + i * 8
plat := tt_u16(cm, rec)
enc := tt_u16(cm, rec + 2)
off := tt_u32(cm, rec + 4)
if u64(off) >= u64(len(cm)) { continue }
// A subtable runs to the end of the cmap table rather than to its own
// declared length: format 4 tables whose length excludes the trailing
// glyphIdArray are a well-known producer bug, and reading past a
// correct length costs nothing.
sub := cm[off:]
if len(sub) < 4 { continue }
format := tt_u16(sub, 0)
if !tt_cmap_supported(format) { continue }
// (3,0) and (1,0) are not Unicode and are kept as themselves.
if plat == 3 && enc == 0 {
if f.cmap_symbol == nil { f.cmap_symbol = sub }
continue
}
if plat == 1 && enc == 0 {
if f.cmap_mac == nil { f.cmap_mac = sub }
continue
}
score := 0
switch {
case plat == 3 && enc == 10: score = 5 // Windows, full repertoire
case plat == 3 && enc == 1: score = 4 // Windows, BMP
case plat == 0 && enc >= 4: score = 3 // Unicode, full repertoire
case plat == 0: score = 2 // Unicode, BMP
}
if score > best {
best = score
f.cmap_unicode = sub
}
}
}
@(private)
tt_cmap_supported :: proc "contextless" (format: u16) -> bool {
// Format 2 is the high-byte scheme for legacy CJK encodings, format 8 and
// 10 were never produced. Refusing them here rather than at lookup time
// means a font with both an unreadable (3,1) and a readable (1,0) still
// gets the (1,0).
return format == 0 || format == 4 || format == 6 || format == 12
}
// tt_cmap_lookup maps one character code through one subtable. Zero means the
// subtable has no glyph for the code -- glyph 0 is .notdef, which is the same
// answer.
@(private)
tt_cmap_lookup :: proc(sub: []byte, c: u32) -> u16 {
if len(sub) < 4 { return 0 }
switch tt_u16(sub, 0) {
case 0:
// Byte encoding: 256 one-byte glyph ids at offset 6.
if c > 255 { return 0 }
off := 6 + int(c)
return u16(tt_u8(sub, off)) if off < len(sub) else 0
case 4:
if c > 0xFFFF { return 0 }
seg := int(tt_u16(sub, 6)) / 2
if seg == 0 { return 0 }
cc := u16(c)
// The segments are sorted by endCode, so the one that can hold the
// code is the first whose endCode is not below it. The format also
// ships searchRange/entrySelector for exactly this search; they are
// ignored because a wrong pair is a common damage and recomputing the
// bounds costs nothing.
lo, hi := 0, seg
for lo < hi {
mid := lo + (hi - lo) / 2
if tt_u16(sub, 14 + 2 * mid) < cc {
lo = mid + 1
} else {
hi = mid
}
}
if lo >= seg { return 0 }
start_off := 16 + 2 * seg
delta_off := 16 + 4 * seg
range_off := 16 + 6 * seg
start := tt_u16(sub, start_off + 2 * lo)
if cc < start { return 0 }
delta := u32(u16(tt_i16(sub, delta_off + 2 * lo)))
ro := int(tt_u16(sub, range_off + 2 * lo))
if ro == 0 {
// idDelta is added modulo 65536, which is why it is signed and why
// the sum has to wrap rather than saturate.
return u16((u32(cc) + delta) & 0xFFFF)
}
// idRangeOffset is a byte offset from its own slot, which is what lets
// several segments share one glyphIdArray.
addr := range_off + 2 * lo + ro + 2 * int(cc - start)
if addr < 0 || addr + 2 > len(sub) { return 0 }
g := tt_u16(sub, addr)
if g == 0 { return 0 }
return u16((u32(g) + delta) & 0xFFFF)
case 6:
// A single trimmed range.
first := u32(tt_u16(sub, 6))
count := u32(tt_u16(sub, 8))
if c < first || c - first >= count { return 0 }
off := 10 + 2 * int(c - first)
if off + 2 > len(sub) { return 0 }
return tt_u16(sub, off)
case 12:
// Sorted groups covering the whole code space.
n := int(min(tt_u32(sub, 12), u32(1 << 24)))
have := (len(sub) - 16) / 12
if have < 0 { have = 0 }
if n > have { n = have }
lo, hi := 0, n
for lo < hi {
mid := lo + (hi - lo) / 2
rec := 16 + mid * 12
if c < tt_u32(sub, rec) {
hi = mid
} else if c > tt_u32(sub, rec + 4) {
lo = mid + 1
} else {
g := u64(tt_u32(sub, rec + 8)) + u64(c - tt_u32(sub, rec))
return u16(g) if g <= 0xFFFF else 0
}
}
return 0
}
return 0
}
// tt_gid_for_unicode maps a Unicode code point through the font's Unicode
// cmap. ok is false when the font has no such table or no glyph for the
// character.
@(private)
tt_gid_for_unicode :: proc(f: ^TrueType_Font, r: rune) -> (u16, bool) {
if f == nil || r < 0 || r > 0x10FFFF { return 0, false }
g := tt_cmap_lookup(f.cmap_unicode, u32(r))
return g, g != 0
}
// tt_gid_for_mac_code maps a byte through the (1,0) Mac Roman cmap, which is
// what a non-symbolic TrueType font's built-in encoding amounts to.
@(private)
tt_gid_for_mac_code :: proc(f: ^TrueType_Font, code: u8) -> (u16, bool) {
if f == nil { return 0, false }
g := tt_cmap_lookup(f.cmap_mac, u32(code))
return g, g != 0
}
// tt_gid_for_symbol maps a byte through the (3,0) symbol cmap.
//
// A symbol font's cmap is keyed in the private-use area: the convention is
// 0xF000 plus the byte, and it is only a convention -- fonts exist that map the
// bare byte, and fonts exist that do both. Both are tried, which is what every
// viewer does and the only way Wingdings and Symbol come out right.
@(private)
tt_gid_for_symbol :: proc(f: ^TrueType_Font, code: u8) -> (u16, bool) {
if f == nil { return 0, false }
if g := tt_cmap_lookup(f.cmap_symbol, u32(code)); g != 0 {
return g, true
}
if g := tt_cmap_lookup(f.cmap_symbol, 0xF000 | u32(code)); g != 0 {
return g, true
}
return 0, false
}
// ---------------------------------------------------------------------------
// post
// ---------------------------------------------------------------------------
// tt_parse_post reads glyph names. ISO 14496-22, 5.2.9.
//
// Format 2.0 is the only one that carries names of its own; 1.0 says the font
// is exactly the 258 standard Macintosh glyphs in order, and 3.0 -- what a
// subsetter emits -- says there are no names at all, which is not an error.
@(private)
tt_parse_post :: proc(f: ^TrueType_Font, post: []byte, allocator: mem.Allocator) {
if len(post) < 32 || f.num_glyphs <= 0 { return }
switch tt_u32(post, 0) {
case 0x00010000:
n := min(f.num_glyphs, len(MAC_GLYPH_NAMES))
names := make([]string, n, allocator)
for i in 0 ..< n {
names[i] = MAC_GLYPH_NAMES[i]
}
f.names = names
case 0x00020000:
n := int(tt_u16(post, 32))
if n > f.num_glyphs { n = f.num_glyphs }
// A post whose index array is cut short still names the glyphs it
// reaches.
if 34 + 2 * n > len(post) { n = (len(post) - 34) / 2 }
if n <= 0 { return }
// The names themselves are Pascal strings in one run after the index
// array, and an index of 258 or more selects the (index-258)th of
// them. A well-formed font has at most one string per glyph, so
// collecting more than that would only be following damage.
strs := make([dynamic]string, 0, min(n, 64), context.allocator)
defer delete(strs)
q := 34 + 2 * n
for len(strs) < n && q < len(post) {
l := int(post[q])
q += 1
if q + l > len(post) { break }
append(&strs, string(post[q:][:l]))
q += l
}
names := make([]string, n, allocator)
for i in 0 ..< n {
idx := int(tt_u16(post, 34 + 2 * i))
if idx < len(MAC_GLYPH_NAMES) {
names[i] = MAC_GLYPH_NAMES[idx]
} else if idx - len(MAC_GLYPH_NAMES) < len(strs) {
names[i] = strs[idx - len(MAC_GLYPH_NAMES)]
}
}
f.names = names
}
}
// tt_gid_for_name looks a glyph up by the name its `post` table gives it. ok is
// false when the font has no names or none of them match.
//
// The scan is linear. A simple font asks at most 256 times and a CID font never
// asks at all, so the index a binary search would need would cost more memory
// than the search saves.
@(private)
tt_gid_for_name :: proc(f: ^TrueType_Font, name: string) -> (u16, bool) {
if f == nil || len(name) == 0 { return 0, false }
for i in 0 ..< len(f.names) {
if f.names[i] == name {
return u16(i), true
}
}
return 0, false
}
// The standard Macintosh glyph ordering: the 258 names a `post` table refers to
// by index alone. ISO 14496-22, 5.2.9, table 32.
@(private, rodata)
MAC_GLYPH_NAMES := [258]string{
".notdef", ".null", "nonmarkingreturn", "space", "exclam", "quotedbl",
"numbersign", "dollar", "percent", "ampersand", "quotesingle", "parenleft",
"parenright", "asterisk", "plus", "comma", "hyphen", "period", "slash",
"zero", "one", "two", "three", "four", "five", "six", "seven", "eight",
"nine", "colon", "semicolon", "less", "equal", "greater", "question", "at",
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O",
"P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "bracketleft",
"backslash", "bracketright", "asciicircum", "underscore", "grave",
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o",
"p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "braceleft", "bar",
"braceright", "asciitilde", "Adieresis", "Aring", "Ccedilla", "Eacute",
"Ntilde", "Odieresis", "Udieresis", "aacute", "agrave", "acircumflex",
"adieresis", "atilde", "aring", "ccedilla", "eacute", "egrave",
"ecircumflex", "edieresis", "iacute", "igrave", "icircumflex", "idieresis",
"ntilde", "oacute", "ograve", "ocircumflex", "odieresis", "otilde",
"uacute", "ugrave", "ucircumflex", "udieresis", "dagger", "degree", "cent",
"sterling", "section", "bullet", "paragraph", "germandbls", "registered",
"copyright", "trademark", "acute", "dieresis", "notequal", "AE", "Oslash",
"infinity", "plusminus", "lessequal", "greaterequal", "yen", "mu",
"partialdiff", "summation", "product", "pi", "integral", "ordfeminine",
"ordmasculine", "Omega", "ae", "oslash", "questiondown", "exclamdown",
"logicalnot", "radical", "florin", "approxequal", "Delta", "guillemotleft",
"guillemotright", "ellipsis", "nonbreakingspace", "Agrave", "Atilde",
"Otilde", "OE", "oe", "endash", "emdash", "quotedblleft", "quotedblright",
"quoteleft", "quoteright", "divide", "lozenge", "ydieresis", "Ydieresis",
"fraction", "currency", "guilsinglleft", "guilsinglright", "fi", "fl",
"daggerdbl", "periodcentered", "quotesinglbase", "quotedblbase",
"perthousand", "Acircumflex", "Ecircumflex", "Aacute", "Edieresis",
"Egrave", "Iacute", "Icircumflex", "Idieresis", "Igrave", "Oacute",
"Ocircumflex", "apple", "Ograve", "Uacute", "Ucircumflex", "Ugrave",
"dotlessi", "circumflex", "tilde", "macron", "breve", "dotaccent", "ring",
"cedilla", "hungarumlaut", "ogonek", "caron", "Lslash", "lslash", "Scaron",
"scaron", "Zcaron", "zcaron", "brokenbar", "Eth", "eth", "Yacute", "yacute",
"Thorn", "thorn", "minus", "multiply", "onesuperior", "twosuperior",
"threesuperior", "onehalf", "onequarter", "threequarters", "franc",
"Gbreve", "gbreve", "Idotaccent", "Scedilla", "scedilla", "Cacute",
"cacute", "Ccaron", "ccaron", "dcroat",
}