1038 lines
39 KiB
Odin
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",
|
|
}
|