package pdf import "core:math" import "core:mem" import "core:slice" import "core:strconv" // CFF (Compact Font Format) and its Type 2 charstrings. // // This file knows nothing about PDF: bytes in, outlines out. A CFF reaches it // from three directions -- a /FontFile3 with /Subtype /Type1C or /CIDFontType0C, // the `CFF ` table of an OpenType font, and a bare .cff file -- and none of // those distinctions survive the call. That is what keeps it separately // testable, and it is why nothing here may reference Document, Dict or Object. // // References are to Adobe's "The Compact Font Format Specification" (technical // note 5176, version 1.0, 4 December 2003) and "The Type 2 Charstring Format" // (technical note 5177, 16 March 2000), cited below as CFF and T2. // // Subsetters mangle font programs more thoroughly than any other part of a // PDF: offsets that point past the end, INDEXes whose offset arrays run // backwards, charstrings that call subroutines that do not exist, and // occasionally two subroutines that call each other. Every structure read here // is therefore clamped rather than trusted, and the interpreter is bounded in // depth, stack and total operations. A glyph that cannot be decoded draws what // was decoded before the fault; it never takes the page down with it. // // MEMORY: `cff_parse` takes an `allocator` and keeps from it the Cff_Font, the // offset array of every INDEX it reads, and the per-glyph charset and lookup // tables. Everything else -- names, charstrings, subroutines -- 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, and // a failed parse likewise leaves what it had already allocated for the arena to // reclaim. Reading a glyph allocates nothing at all; the interpreter's state is // the caller's stack frame. // --------------------------------------------------------------------------- // Limits // // Everything below is bounded by a value that came out of the file, so every // one of them is capped. The charstring limits are the spec's own; the rest // are chosen to be far above any real font and far below anything that hurts. // --------------------------------------------------------------------------- // T2 3.1: "the maximum depth of subroutine calls is 10". @(private) CFF_MAX_SUBR_DEPTH :: 10 // T2 Appendix B: the interpreter's operand stack holds 48 numbers. @(private) CFF_MAX_STACK :: 48 // Total charstring operators executed for one glyph, seac and all subroutine // calls included. Subroutines cannot loop, but two of them may call each other, // and the depth cap alone still allows 10 nested copies of a long charstring. @(private) CFF_MAX_CHARSTRING_OPS :: 100_000 // T2 4: the transient array is 32 elements. @(private) CFF_MAX_TRANSIENT :: 32 // CFF 4: "operators may be preceded by up to a maximum of 48 operands". @(private) CFF_MAX_DICT_OPERANDS :: 48 // Longest real number a DICT may spell out, in characters. Reals in a DICT are // font matrices and blue values; 64 digits is already absurd. @(private) CFF_MAX_REAL_CHARS :: 64 // FDSelect stores the font-DICT index in one byte, so no more than 256 of them // can ever be selected however many the FDArray lists. @(private) CFF_MAX_FDS :: 256 // --------------------------------------------------------------------------- // Types // --------------------------------------------------------------------------- // Cff_Index is a CFF INDEX: a counted array of variable-length items. // // `offsets` holds count+1 *absolute* positions into `data`, already clamped to // be non-decreasing and inside the font, so `cff_index_item` needs no further // validation and can never produce a slice that escapes the font. @(private) Cff_Index :: struct { data: []byte, offsets: []u32, count: int, // First byte after the INDEX, so the next one can be read from here. end: int, } // Cff_Private is one Private DICT: the widths every charstring in its scope is // relative to, and the local subroutines it may call. A non-CID font has one; // a CIDFont has one per entry in the FDArray. @(private) Cff_Private :: struct { subrs: Cff_Index, subr_bias: int, default_width: f64, nominal_width: f64, } // Cff_Name_Entry is one (glyph name, GID) pair of the by-name lookup table, // which is sorted so `cff_gid_for_name` is a binary search rather than a walk // over every glyph in the font for every code in the encoding. @(private) Cff_Name_Entry :: struct { name: string, gid: u16, } // Cff_Font is the parsed form of a font program. // // `data` is borrowed, not copied: every name, every charstring and every // subroutine is a sub-slice of it, so it must stay alive and unmodified for as // long as the font does. Everything else comes from the allocator passed to // `cff_parse`. Cff_Font :: struct { data: []byte, // The four INDEXes at the head of the file. names: Cff_Index, top_dicts: Cff_Index, strings: Cff_Index, gsubrs: Cff_Index, gsubr_bias: int, // The glyph outlines themselves. charstrings: Cff_Index, // Top DICT. font_matrix: Matrix, font_bbox: Rect, charstring_type: int, is_cid: bool, // The Private DICT of a non-CID font, or of FD 0 when there is no // FDSelect. `fds` and `fd_select` are empty for a non-CID font. priv: Cff_Private, fds: []Cff_Private, // One font-DICT index per glyph. Empty means every glyph uses fds[0], or // `priv` when there is no FDArray either. fd_select: []u8, // charset: SID per glyph for a name-keyed font, CID per glyph for a // CIDFont. Always `glyph_count` long once parsing succeeds. charset: []u16, // Sorted lookup tables. Exactly one of the two is built, according to what // the charset holds. by_name: []Cff_Name_Entry, // (cid << 16) | gid, sorted, so a binary search on the high half finds the // lowest GID for a CID. by_cid: []u32, // The font's built-in encoding: the glyph each byte code selects, and the // name it selects it by. A CIDFont has neither (CFF 12: "CIDFonts do not // use the Encoding operator"). code_to_gid: [256]u16, enc_names: [256]string, } // --------------------------------------------------------------------------- // Bounds-checked readers // // Every one of these returns zero past the end rather than trapping. Structural // code checks the length it needs before it reads, so a zero here is always a // value the file really contains -- except in the recovery paths, where zero is // exactly the harmless answer wanted. // --------------------------------------------------------------------------- @(private) cff_u16 :: proc "contextless" (d: []byte, p: int) -> u16 { if p < 0 || p + 1 >= len(d) { return 0 } return u16(d[p]) << 8 | u16(d[p + 1]) } // cff_offset reads a big-endian unsigned value `size` bytes wide, as INDEX // offset arrays and DICT offsets are written. @(private) cff_offset :: proc "contextless" (d: []byte, p: int, size: int) -> u32 { if size < 1 || size > 4 || p < 0 || p + size > len(d) { return 0 } v: u32 = 0 for i in 0 ..< size { v = v << 8 | u32(d[p + i]) } return v } // --------------------------------------------------------------------------- // INDEX // --------------------------------------------------------------------------- // cff_index_read parses the INDEX at `pos`. The offset array is materialised as // absolute positions so that later reads need no arithmetic and no checks. // // A count of zero is the whole structure: CFF 5 says an empty INDEX is two // bytes with no offSize byte at all, and writing the extra byte anyway is a // mistake that would push every following structure one byte out. @(private) cff_index_read :: proc(data: []byte, pos: int, allocator: mem.Allocator) -> (idx: Cff_Index, err: Error) { idx.data = data idx.end = pos if pos < 0 || pos + 2 > len(data) { return idx, .Truncated } count := int(cff_u16(data, pos)) idx.end = pos + 2 if count == 0 { return idx, .None } if pos + 3 > len(data) { return idx, .Truncated } off_size := int(data[pos + 2]) if off_size < 1 || off_size > 4 { return idx, .Bad_Cff } table := pos + 3 // count <= 65535 and off_size <= 4, so this cannot overflow, and checking // it against the file is what bounds the allocation below. need := (count + 1) * off_size if need > len(data) - table { return idx, .Truncated } // CFF 5: offsets are relative to the byte preceding the item data, so the // first is always 1. base := table + need - 1 limit := u32(len(data) - base) offsets := make([]u32, count + 1, allocator) prev: u32 = 1 for i in 0 ..= count { v := cff_offset(data, table + i * off_size, off_size) // Clamping instead of rejecting: a subset font with one bad offset // still draws every other glyph, and the item this produces is empty // rather than out of bounds. if v < prev { v = prev } if v > limit { v = limit } offsets[i] = u32(base) + v prev = v } idx.offsets = offsets idx.count = count idx.end = int(offsets[count]) return idx, .None } @(private) cff_index_item :: proc(idx: ^Cff_Index, i: int) -> []byte { if i < 0 || i >= idx.count || len(idx.offsets) < idx.count + 1 { return nil } lo, hi := int(idx.offsets[i]), int(idx.offsets[i + 1]) if lo > hi || hi > len(idx.data) { return nil } return idx.data[lo:hi] } // cff_subr_bias is the constant added to a callsubr/callgsubr index before it // selects an entry. T2 4.7: subroutine numbers are biased so that the small // single-byte encodings reach the middle of the array, where a subsetter puts // the subroutines it uses most. @(private) cff_subr_bias :: proc "contextless" (count: int) -> int { switch { case count < 1240: return 107 case count < 33900: return 1131 case: return 32768 } } // --------------------------------------------------------------------------- // DICT // // A DICT is a flat byte string of operands followed by the operator they // belong to -- postfix, with no nesting and no keys other than the operator // number. CFF 4 gives the operand encoding, which is not the same as the one // charstrings use: a DICT has the 5-byte 29 integer and the nibble-packed 30 // real, and no 16.16 fixed. // --------------------------------------------------------------------------- // Operators are keyed as their byte, with the two-byte "12 x" escapes carried // as 0x0C00 | x, so one switch covers both forms. @(private) CFF_ESC :: 0x0C00 @(private) Cff_Dict_Iter :: struct { data: []byte, pos: int, ops: [CFF_MAX_DICT_OPERANDS]f64, n: int, } // cff_dict_next advances to the next operator, leaving its operands in // `it.ops[:it.n]`. It returns ok = false at the end of the DICT and at the // first byte that cannot be a valid operand or operator, because past that // point the operand/operator alternation has lost sync and anything read would // be invented. @(private) cff_dict_next :: proc(it: ^Cff_Dict_Iter) -> (op: u16, ok: bool) { it.n = 0 for it.pos < len(it.data) { b0 := it.data[it.pos] switch { case b0 == 12: if it.pos + 1 >= len(it.data) { return 0, false } op = CFF_ESC | u16(it.data[it.pos + 1]) it.pos += 2 return op, true case b0 <= 21: // 22..27 and 31 are reserved and never appear as operators. if b0 >= 22 { return 0, false } op = u16(b0) it.pos += 1 return op, true case b0 == 28: if it.pos + 2 >= len(it.data) { return 0, false } v := i16(u16(it.data[it.pos + 1]) << 8 | u16(it.data[it.pos + 2])) cff_dict_push(it, f64(v)) it.pos += 3 case b0 == 29: if it.pos + 4 >= len(it.data) { return 0, false } u := u32(it.data[it.pos + 1]) << 24 | u32(it.data[it.pos + 2]) << 16 | u32(it.data[it.pos + 3]) << 8 | u32(it.data[it.pos + 4]) cff_dict_push(it, f64(i32(u))) it.pos += 5 case b0 == 30: v, next := cff_dict_real(it.data, it.pos + 1) cff_dict_push(it, v) it.pos = next case b0 >= 32 && b0 <= 246: cff_dict_push(it, f64(int(b0) - 139)) it.pos += 1 case b0 >= 247 && b0 <= 250: if it.pos + 1 >= len(it.data) { return 0, false } cff_dict_push(it, f64((int(b0) - 247) * 256 + int(it.data[it.pos + 1]) + 108)) it.pos += 2 case b0 >= 251 && b0 <= 254: if it.pos + 1 >= len(it.data) { return 0, false } cff_dict_push(it, f64(-(int(b0) - 251) * 256 - int(it.data[it.pos + 1]) - 108)) it.pos += 2 case: // 31 and 255 are reserved operand bytes. return 0, false } } return 0, false } // cff_dict_push drops operands past the 48th rather than growing: the format // forbids more, and every operator this reader cares about takes its arguments // from the front. @(private) cff_dict_push :: proc(it: ^Cff_Dict_Iter, v: f64) { if it.n >= CFF_MAX_DICT_OPERANDS { return } it.ops[it.n] = is_finite(v) ? v : 0 it.n += 1 } // cff_dict_real decodes the nibble-packed real of CFF Table 5, returning the // value and the position just past its terminating nibble. A malformed one // yields whatever prefix parsed, which is what keeps a damaged /FontMatrix from // costing the whole font. @(private) cff_dict_real :: proc(d: []byte, start: int) -> (value: f64, next: int) { buf: [CFF_MAX_REAL_CHARS]u8 n := 0 put :: proc(buf: ^[CFF_MAX_REAL_CHARS]u8, n: ^int, c: u8) { if n^ < len(buf) { buf[n^] = c n^ += 1 } } p := start done := false for p < len(d) && !done { b := d[p] p += 1 for shift := uint(4); ; shift -= 4 { nib := (b >> shift) & 0xF switch nib { case 0x0 ..= 0x9: put(&buf, &n, u8('0') + nib) case 0xA: put(&buf, &n, '.') case 0xB: put(&buf, &n, 'E') case 0xC: put(&buf, &n, 'E'); put(&buf, &n, '-') case 0xD: // reserved; skip case 0xE: put(&buf, &n, '-') case 0xF: done = true } if shift == 0 || done { break } } } v, ok := strconv.parse_f64(string(buf[:n])) if !ok || !is_finite(v) { return 0, p } return v, p } // --------------------------------------------------------------------------- // Top DICT and Private DICT // --------------------------------------------------------------------------- @(private) Cff_Top :: struct { charset: int, encoding: int, charstrings: int, priv_size: int, priv_off: int, fdarray: int, fdselect: int, is_cid: bool, charstring_type: int, font_matrix: Matrix, has_matrix: bool, font_bbox: Rect, } @(private) cff_top_dict_parse :: proc(d: []byte) -> (top: Cff_Top) { top.charset = -1 top.encoding = -1 top.charstrings = -1 top.fdarray = -1 top.fdselect = -1 top.priv_off = -1 top.charstring_type = 2 // CFF Table 9: the default FontMatrix is 0.001 units per em. top.font_matrix = Matrix{0.001, 0, 0, 0.001, 0, 0} it := Cff_Dict_Iter{data = d} for { op, ok := cff_dict_next(&it) if !ok { break } switch op { case 15: if it.n >= 1 { top.charset = cff_dict_int(&it, 0) } case 16: if it.n >= 1 { top.encoding = cff_dict_int(&it, 0) } case 17: if it.n >= 1 { top.charstrings = cff_dict_int(&it, 0) } case 18: if it.n >= 2 { top.priv_size = cff_dict_int(&it, 0) top.priv_off = cff_dict_int(&it, 1) } case 5: if it.n >= 4 { top.font_bbox = rect_normalize(Rect{it.ops[0], it.ops[1], it.ops[2], it.ops[3]}) } case CFF_ESC | 6: if it.n >= 1 { top.charstring_type = cff_dict_int(&it, 0) } case CFF_ESC | 7: if it.n >= 6 { m := Matrix{it.ops[0], it.ops[1], it.ops[2], it.ops[3], it.ops[4], it.ops[5]} if matrix_is_finite(m) { top.font_matrix = m top.has_matrix = true } } case CFF_ESC | 30: top.is_cid = true // ROS case CFF_ESC | 36: if it.n >= 1 { top.fdarray = cff_dict_int(&it, 0) } case CFF_ESC | 37: if it.n >= 1 { top.fdselect = cff_dict_int(&it, 0) } } } return } // cff_dict_int reads an operand as a file offset or count. Values outside // int32 cannot be a position in a font and become -1, which every caller // already treats as "absent". @(private) cff_dict_int :: proc(it: ^Cff_Dict_Iter, i: int) -> int { if i < 0 || i >= it.n { return -1 } v := it.ops[i] if !is_finite(v) || v < f64(min(i32)) || v > f64(max(i32)) { return -1 } return int(v) } // cff_private_parse reads the Private DICT at (off, size) and the local Subrs // INDEX it points at. Subrs is an offset from the start of the Private DICT // itself, not from the start of the font (CFF Table 23). @(private) cff_private_parse :: proc(data: []byte, off, size: int, allocator: mem.Allocator) -> (p: Cff_Private) { p.subr_bias = cff_subr_bias(0) if off < 0 || size <= 0 || off >= len(data) { return } end := off + size if end > len(data) || end < off { end = len(data) } subrs_off := -1 it := Cff_Dict_Iter{data = data[off:end]} for { op, ok := cff_dict_next(&it) if !ok { break } switch op { case 19: if it.n >= 1 { subrs_off = cff_dict_int(&it, 0) } case 20: if it.n >= 1 { p.default_width = it.ops[0] } case 21: if it.n >= 1 { p.nominal_width = it.ops[0] } } } if subrs_off > 0 && off + subrs_off < len(data) { idx, err := cff_index_read(data, off + subrs_off, allocator) if err == .None { p.subrs = idx } } p.subr_bias = cff_subr_bias(p.subrs.count) return } // --------------------------------------------------------------------------- // The standard strings // // CFF Appendix A. SIDs below 391 name one of these instead of indexing the // String INDEX, which is how a font that uses only ISO-Adobe glyph names // carries no strings at all. The order is the specification's and is load // bearing: the SID is the position. // --------------------------------------------------------------------------- @(private) CFF_N_STD_STRINGS :: 391 @(private) CFF_STD_STRINGS := [CFF_N_STD_STRINGS]string{ ".notdef", "space", "exclam", "quotedbl", "numbersign", "dollar", "percent", "ampersand", "quoteright", "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", "quoteleft", "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", "exclamdown", "cent", "sterling", "fraction", "yen", "florin", "section", "currency", "quotesingle", "quotedblleft", "guillemotleft", "guilsinglleft", "guilsinglright", "fi", "fl", "endash", "dagger", "daggerdbl", "periodcentered", "paragraph", "bullet", "quotesinglbase", "quotedblbase", "quotedblright", "guillemotright", "ellipsis", "perthousand", "questiondown", "grave", "acute", "circumflex", "tilde", "macron", "breve", "dotaccent", "dieresis", "ring", "cedilla", "hungarumlaut", "ogonek", "caron", "emdash", "AE", "ordfeminine", "Lslash", "Oslash", "OE", "ordmasculine", "ae", "dotlessi", "lslash", "oslash", "oe", "germandbls", "onesuperior", "logicalnot", "mu", "trademark", "Eth", "onehalf", "plusminus", "Thorn", "onequarter", "divide", "brokenbar", "degree", "thorn", "threequarters", "twosuperior", "registered", "minus", "eth", "multiply", "threesuperior", "copyright", "Aacute", "Acircumflex", "Adieresis", "Agrave", "Aring", "Atilde", "Ccedilla", "Eacute", "Ecircumflex", "Edieresis", "Egrave", "Iacute", "Icircumflex", "Idieresis", "Igrave", "Ntilde", "Oacute", "Ocircumflex", "Odieresis", "Ograve", "Otilde", "Scaron", "Uacute", "Ucircumflex", "Udieresis", "Ugrave", "Yacute", "Ydieresis", "Zcaron", "aacute", "acircumflex", "adieresis", "agrave", "aring", "atilde", "ccedilla", "eacute", "ecircumflex", "edieresis", "egrave", "iacute", "icircumflex", "idieresis", "igrave", "ntilde", "oacute", "ocircumflex", "odieresis", "ograve", "otilde", "scaron", "uacute", "ucircumflex", "udieresis", "ugrave", "yacute", "ydieresis", "zcaron", "exclamsmall", "Hungarumlautsmall", "dollaroldstyle", "dollarsuperior", "ampersandsmall", "Acutesmall", "parenleftsuperior", "parenrightsuperior", "twodotenleader", "onedotenleader", "zerooldstyle", "oneoldstyle", "twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", "sixoldstyle", "sevenoldstyle", "eightoldstyle", "nineoldstyle", "commasuperior", "threequartersemdash", "periodsuperior", "questionsmall", "asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior", "lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior", "tsuperior", "ff", "ffi", "ffl", "parenleftinferior", "parenrightinferior", "Circumflexsmall", "hyphensuperior", "Gravesmall", "Asmall", "Bsmall", "Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall", "Hsmall", "Ismall", "Jsmall", "Ksmall", "Lsmall", "Msmall", "Nsmall", "Osmall", "Psmall", "Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall", "Vsmall", "Wsmall", "Xsmall", "Ysmall", "Zsmall", "colonmonetary", "onefitted", "rupiah", "Tildesmall", "exclamdownsmall", "centoldstyle", "Lslashsmall", "Scaronsmall", "Zcaronsmall", "Dieresissmall", "Brevesmall", "Caronsmall", "Dotaccentsmall", "Macronsmall", "figuredash", "hypheninferior", "Ogoneksmall", "Ringsmall", "Cedillasmall", "questiondownsmall", "oneeighth", "threeeighths", "fiveeighths", "seveneighths", "onethird", "twothirds", "zerosuperior", "foursuperior", "fivesuperior", "sixsuperior", "sevensuperior", "eightsuperior", "ninesuperior", "zeroinferior", "oneinferior", "twoinferior", "threeinferior", "fourinferior", "fiveinferior", "sixinferior", "seveninferior", "eightinferior", "nineinferior", "centinferior", "dollarinferior", "periodinferior", "commainferior", "Agravesmall", "Aacutesmall", "Acircumflexsmall", "Atildesmall", "Adieresissmall", "Aringsmall", "AEsmall", "Ccedillasmall", "Egravesmall", "Eacutesmall", "Ecircumflexsmall", "Edieresissmall", "Igravesmall", "Iacutesmall", "Icircumflexsmall", "Idieresissmall", "Ethsmall", "Ntildesmall", "Ogravesmall", "Oacutesmall", "Ocircumflexsmall", "Otildesmall", "Odieresissmall", "OEsmall", "Oslashsmall", "Ugravesmall", "Uacutesmall", "Ucircumflexsmall", "Udieresissmall", "Yacutesmall", "Thornsmall", "Ydieresissmall", "001.000", "001.001", "001.002", "001.003", "Black", "Bold", "Book", "Light", "Medium", "Regular", "Roman", "Semibold", } // cff_sid_string resolves a SID to its glyph or text name. An out-of-range SID // yields "" rather than a name from somewhere else in the font. @(private) cff_sid_string :: proc(f: ^Cff_Font, sid: u16) -> string { if int(sid) < CFF_N_STD_STRINGS { return CFF_STD_STRINGS[sid] } idx := &f.strings item := cff_index_item(idx, int(sid) - CFF_N_STD_STRINGS) return string(item) } // --------------------------------------------------------------------------- // charset // --------------------------------------------------------------------------- // cff_charset_parse fills gid -> SID (or, for a CIDFont, gid -> CID). // // Offsets 0, 1 and 2 name the predefined ISOAdobe, Expert and ExpertSubset // charsets (CFF Appendix C). Only ISOAdobe is tabulated, as the identity it // is; a font using one of the two Expert charsets loses its glyph *names*, and // so any lookup by name, but keeps every GID and therefore every outline. No // PDF-embedded CFF in the corpus uses them, and a table recited wrongly would // be worse than one that is honestly absent. @(private) cff_charset_parse :: proc(f: ^Cff_Font, off: int, allocator: mem.Allocator) { // Every per-glyph allocation in this file is sized by the CharStrings // count, which an INDEX writes as a Card16 and so can never exceed 65535 // however damaged the font is. n := f.charstrings.count if n <= 0 { return } cs := make([]u16, n, allocator) f.charset = cs if off <= 2 { // Identity: SID i for glyph i, which is exactly ISOAdobe for the 229 // glyphs it defines, and the identity CID mapping a CIDFont with no // charset needs. limit := n if !f.is_cid && limit > 229 { limit = 229 } for i in 0 ..< limit { cs[i] = u16(i) } return } if off >= len(f.data) { return } d := f.data format := d[off] p := off + 1 // Glyph 0 is always .notdef and is not listed. gid := 1 switch format { case 0: for gid < n && p + 1 < len(d) { cs[gid] = cff_u16(d, p) p += 2 gid += 1 } case 1, 2: step := format == 1 ? 3 : 4 for gid < n && p + step <= len(d) { first := int(cff_u16(d, p)) n_left := format == 1 ? int(d[p + 2]) : int(cff_u16(d, p + 2)) p += step for i := 0; i <= n_left && gid < n; i += 1 { v := first + i // SIDs and CIDs are 16 bit; a range that would run past that // is a corrupt one, so it stops here instead of wrapping. if v > 0xFFFF { break } cs[gid] = u16(v) gid += 1 } } case: // An unknown format leaves every glyph nameless, which is recoverable: // outlines are indexed by GID and do not need the charset at all. } } // --------------------------------------------------------------------------- // Encoding // --------------------------------------------------------------------------- // cff_encoding_parse fills the code -> glyph tables from the font's built-in // Encoding. CFF 12 gives offsets 0 and 1 to the predefined Standard and Expert // encodings; anything larger is a custom encoding in format 0 or 1, either of // which may carry supplements. @(private) cff_encoding_parse :: proc(f: ^Cff_Font, off: int) { // CIDFonts have no encoding at all: their charstrings are selected by CID. if f.is_cid { return } n := f.charstrings.count if off <= 1 || off >= len(f.data) { // Standard (and, for want of a table, Expert) encoding: the code names // a glyph, and the charset says which glyph carries that name. std := encoding_standard() for code in 0 ..< 256 { name := std[code] if name == "" { continue } gid, ok := cff_gid_for_name(f, name) if !ok { continue } f.code_to_gid[code] = gid f.enc_names[code] = name } return } d := f.data format := d[off] p := off + 1 switch format & 0x7F { case 0: if p >= len(d) { return } n_codes := int(d[p]) p += 1 for i in 0 ..< n_codes { if p >= len(d) { break } code := int(d[p]) p += 1 gid := i + 1 if gid < n { f.code_to_gid[code] = u16(gid) } } case 1: if p >= len(d) { return } n_ranges := int(d[p]) p += 1 gid := 1 for i := 0; i < n_ranges && p + 1 < len(d); i += 1 { first := int(d[p]) n_left := int(d[p + 1]) p += 2 for k := 0; k <= n_left && gid < n; k += 1 { code := first + k if code > 255 { break } f.code_to_gid[code] = u16(gid) gid += 1 } } case: return } // CFF 12: the high bit of the format byte says a supplement list follows, // mapping extra codes to glyphs by NAME rather than by position. if format & 0x80 != 0 && p < len(d) { n_sups := int(d[p]) p += 1 for i := 0; i < n_sups && p + 2 < len(d); i += 1 { code := int(d[p]) sid := cff_u16(d, p + 1) p += 3 name := cff_sid_string(f, sid) if name == "" { continue } f.enc_names[code] = name if gid, ok := cff_gid_for_name(f, name); ok { f.code_to_gid[code] = gid } } } // Fill in the names of everything the format-0/1 part mapped by position. for code in 0 ..< 256 { if f.enc_names[code] == "" && f.code_to_gid[code] != 0 { f.enc_names[code] = cff_glyph_name(f, f.code_to_gid[code]) } } } // --------------------------------------------------------------------------- // FDSelect and FDArray (CIDFonts) // --------------------------------------------------------------------------- // cff_fdselect_parse fills one font-DICT index per glyph. A glyph the table // does not cover keeps index 0, which is the only safe guess: it at least has // a Private DICT. @(private) cff_fdselect_parse :: proc(f: ^Cff_Font, off: int, allocator: mem.Allocator) { n := f.charstrings.count if n <= 0 || off <= 0 || off >= len(f.data) { return } d := f.data format := d[off] sel := make([]u8, n, allocator) f.fd_select = sel switch format { case 0: p := off + 1 for gid in 0 ..< n { if p + gid >= len(d) { break } sel[gid] = d[p + gid] } case 3: n_ranges := int(cff_u16(d, off + 1)) p := off + 3 // Each range is 3 bytes and the array is followed by a 2-byte sentinel. for i := 0; i < n_ranges && p + 4 < len(d); i += 1 { first := int(cff_u16(d, p)) fd := d[p + 2] next := int(cff_u16(d, p + 3)) p += 3 if next > n { next = n } for gid := first; gid < next && gid < n; gid += 1 { if gid >= 0 { sel[gid] = fd } } } case: // Unknown format: every glyph falls back to font DICT 0. } } @(private) cff_fdarray_parse :: proc(f: ^Cff_Font, off: int, allocator: mem.Allocator) { if off <= 0 || off >= len(f.data) { return } idx, err := cff_index_read(f.data, off, allocator) if err != .None || idx.count == 0 { return } count := min(idx.count, CFF_MAX_FDS) fds := make([]Cff_Private, count, allocator) for i in 0 ..< count { fd_dict := cff_index_item(&idx, i) priv_off, priv_size := -1, 0 it := Cff_Dict_Iter{data = fd_dict} for { op, ok := cff_dict_next(&it) if !ok { break } if op == 18 && it.n >= 2 { priv_size = cff_dict_int(&it, 0) priv_off = cff_dict_int(&it, 1) } } fds[i] = cff_private_parse(f.data, priv_off, priv_size, allocator) } f.fds = fds } // --------------------------------------------------------------------------- // Parsing the font // --------------------------------------------------------------------------- // cff_parse reads a CFF font program. // // `data` is borrowed: the returned font holds sub-slices of it for names, // charstrings and subroutines, so it must outlive the font and must not be // modified. Everything else the font owns comes from `allocator` and is // released only by releasing that allocator -- the intended use is an arena, // which is what the Document hands fonts. That holds for a failed parse too: // what was allocated before the failure is left for the arena to reclaim. // // An error is returned only when there is nothing usable at all: no header, no // Top DICT, or no CharStrings INDEX. Everything else degrades -- a broken // charset costs glyph names, a broken Private DICT costs local subroutines and // the default width, and neither stops the outlines from being read. @(private) cff_parse :: proc(data: []byte, allocator := context.allocator) -> (^Cff_Font, Error) { // CFF 6: the header is major, minor, hdrSize, offSize. if len(data) < 4 { return nil, .Truncated } if data[0] != 1 { // CFF2 (major version 2) drops the Name, String and Encoding INDEXes // and changes the charstring operators. Reading it as CFF1 would // produce nonsense, so it is refused rather than guessed at. return nil, .Bad_Cff } hdr_size := int(data[2]) // A hdrSize smaller than the header itself is a subsetter bug; the four // bytes the format defines are still there, so start after them. if hdr_size < 4 { hdr_size = 4 } if hdr_size > len(data) { return nil, .Truncated } f := new(Cff_Font, allocator) f.data = data f.font_matrix = Matrix{0.001, 0, 0, 0.001, 0, 0} f.charstring_type = 2 err: Error f.names, err = cff_index_read(data, hdr_size, allocator) if err != .None { return nil, err } f.top_dicts, err = cff_index_read(data, f.names.end, allocator) if err != .None { return nil, err } // A damaged String or GlobalSubr INDEX costs names and subroutines but // nothing structural: the Top DICT gives every other position absolutely. f.strings, _ = cff_index_read(data, f.top_dicts.end, allocator) f.gsubrs, _ = cff_index_read(data, f.strings.end, allocator) f.gsubr_bias = cff_subr_bias(f.gsubrs.count) top_data := cff_index_item(&f.top_dicts, 0) if len(top_data) == 0 { return nil, .Bad_Cff } top := cff_top_dict_parse(top_data) f.is_cid = top.is_cid f.charstring_type = top.charstring_type f.font_bbox = top.font_bbox f.font_matrix = top.font_matrix if top.charstrings <= 0 || top.charstrings >= len(data) { return nil, .Bad_Cff } f.charstrings, err = cff_index_read(data, top.charstrings, allocator) if err != .None { return nil, .Bad_Cff } if f.charstrings.count == 0 { return nil, .Bad_Cff } f.priv = cff_private_parse(data, top.priv_off, top.priv_size, allocator) if top.fdarray > 0 { cff_fdarray_parse(f, top.fdarray, allocator) } if top.fdselect > 0 { cff_fdselect_parse(f, top.fdselect, allocator) } // CFF 10: a CIDFont's FDArray entries may carry their own FontMatrix. When // the Top DICT has none of its own, the first font DICT's is the font's. if !top.has_matrix && len(f.fds) > 0 { if m, ok := cff_fd_matrix(f, top.fdarray, allocator); ok { f.font_matrix = m } } cff_charset_parse(f, top.charset, allocator) cff_build_lookups(f, allocator) cff_encoding_parse(f, top.encoding) return f, .None } // cff_fd_matrix re-reads the FDArray for a FontMatrix on its first entry. It is // a second pass over a structure already parsed because the matrix is the one // thing in a font DICT that is not per-glyph, and hoisting it into // Cff_Private would put it on every entry to serve one. @(private) cff_fd_matrix :: proc(f: ^Cff_Font, off: int, allocator: mem.Allocator) -> (Matrix, bool) { idx, err := cff_index_read(f.data, off, allocator) if err != .None || idx.count == 0 { return MATRIX_IDENTITY, false } it := Cff_Dict_Iter{data = cff_index_item(&idx, 0)} for { op, ok := cff_dict_next(&it) if !ok { break } if op == CFF_ESC | 7 && it.n >= 6 { m := Matrix{it.ops[0], it.ops[1], it.ops[2], it.ops[3], it.ops[4], it.ops[5]} if matrix_is_finite(m) { return m, true } } } return MATRIX_IDENTITY, false } // cff_build_lookups builds whichever of the two reverse maps the charset calls // for. Both are sorted arrays rather than hash tables: they are built once, // searched a few hundred times, and a sorted array of 16-bit keys is smaller // than the font's own offset array. @(private) cff_build_lookups :: proc(f: ^Cff_Font, allocator: mem.Allocator) { n := len(f.charset) if n == 0 { return } if f.is_cid { table := make([]u32, n, allocator) for gid in 0 ..< n { table[gid] = u32(f.charset[gid]) << 16 | u32(gid) } // Sorting the packed pair orders by CID and then by GID, so a binary // search that walks left over equal CIDs lands on the lowest GID. slice.sort(table) f.by_cid = table return } table := make([]Cff_Name_Entry, n, allocator) for gid in 0 ..< n { table[gid] = Cff_Name_Entry{cff_sid_string(f, f.charset[gid]), u16(gid)} } slice.sort_by(table, proc(a, b: Cff_Name_Entry) -> bool { if a.name != b.name { return a.name < b.name } return a.gid < b.gid }) f.by_name = table } // --------------------------------------------------------------------------- // Accessors // // Package-private, like every other font-program reader: font.odin is the only // caller, and the library's public API is the one pdf.odin's doc comment names. // --------------------------------------------------------------------------- @(private) cff_glyph_count :: proc(f: ^Cff_Font) -> int { if f == nil { return 0 } return f.charstrings.count } // cff_font_matrix maps glyph space to text space. CFF Table 9 defaults it to // 1/1000, which is what all but a handful of fonts use. @(private) cff_font_matrix :: proc(f: ^Cff_Font) -> Matrix { if f == nil { return Matrix{0.001, 0, 0, 0.001, 0, 0} } return f.font_matrix } // cff_gid_for_name finds the glyph with this name. A CIDFont has no glyph // names -- its charset holds CIDs -- and always reports false. @(private) cff_gid_for_name :: proc(f: ^Cff_Font, name: string) -> (u16, bool) { if f == nil || name == "" || len(f.by_name) == 0 { return 0, false } lo, hi := 0, len(f.by_name) for lo < hi { mid := lo + (hi - lo) / 2 if f.by_name[mid].name < name { lo = mid + 1 } else { hi = mid } } if lo < len(f.by_name) && f.by_name[lo].name == name { return f.by_name[lo].gid, true } return 0, false } // cff_gid_for_cid maps a CID to a glyph through the charset, which for a // CIDFont holds CIDs rather than name SIDs (CFF 18). For a font that is not a // CIDFont the CID is the glyph index, which is what a PDF using Identity-H // with a non-CID CFF expects. @(private) cff_gid_for_cid :: proc(f: ^Cff_Font, cid: u16) -> (u16, bool) { if f == nil { return 0, false } if !f.is_cid { if int(cid) < f.charstrings.count { return cid, true } return 0, false } lo, hi := 0, len(f.by_cid) key := u32(cid) << 16 for lo < hi { mid := lo + (hi - lo) / 2 if f.by_cid[mid] < key { lo = mid + 1 } else { hi = mid } } if lo < len(f.by_cid) && f.by_cid[lo] >> 16 == u32(cid) { return u16(f.by_cid[lo] & 0xFFFF), true } return 0, false } // cff_glyph_name is the name the charset gives a glyph, or "" for a CIDFont, // an out-of-range glyph or a SID that names nothing. @(private) cff_glyph_name :: proc(f: ^Cff_Font, gid: u16) -> string { if f == nil || f.is_cid || int(gid) >= len(f.charset) { return "" } return cff_sid_string(f, f.charset[gid]) } // cff_builtin_encoding is the font's own code-to-glyph-name table, the same // shape the built-in encodings in font_standard.odin have, so a simple font // can apply /Differences over a copy of it. Entries are "" where the encoding // selects nothing, and a CIDFont has no encoding at all. // // Not one of the seven procedures the render-layer contract lists; it is here // because a symbolic Type1C font with a custom Encoding cannot be drawn // without it, exactly as a Type1 font cannot be drawn without // `t1_builtin_encoding`. @(private) cff_builtin_encoding :: proc(f: ^Cff_Font) -> ^[256]string { if f == nil { return nil } return &f.enc_names } // cff_gid_for_code resolves a byte code through the font's built-in encoding. // The direct route: a custom Encoding names glyphs by position, so going via // the name would lose any glyph the charset does not name. @(private) cff_gid_for_code :: proc(f: ^Cff_Font, code: byte) -> (u16, bool) { if f == nil { return 0, false } gid := f.code_to_gid[code] if gid == 0 { return 0, false } return gid, true } // --------------------------------------------------------------------------- // The Type 2 charstring interpreter // // T2 is a stack machine whose operators are the drawing commands. Three of its // rules are where implementations go wrong, so they are stated here once: // // 1. The glyph's width is an optional extra leading argument on the FIRST // stack-clearing operator and nowhere else, expressed as a difference from // nominalWidthX. Its presence is inferred from the argument count being one // more than the operator takes -- for the stem operators, from the count // being odd. // 2. hintmask and cntrmask take the arguments left on the stack as an implied // vstem declaration before reading their mask, which is ceil(numStems/8) // bytes of the charstring itself rather than an operand. // 3. Subroutine numbers are biased by 107, 1131 or 32768 according to how many // subroutines the INDEX holds, so that the one-byte operand range reaches // the middle of the array. // --------------------------------------------------------------------------- @(private) Cff_Interp :: struct { f: ^Cff_Font, // nil to run for the width alone, which is what cff_glyph_advance wants: // no outline is built and the run stops at the first stack-clearing // operator. out: ^Path, priv: ^Cff_Private, stack: [CFF_MAX_STACK]f64, sp: int, trans: [CFF_MAX_TRANSIENT]f64, x, y: f64, n_stems: int, width: f64, have_width: bool, width_only: bool, open: bool, done: bool, seac: [4]f64, have_seac: bool, allow_seac: bool, fuel: int, seed: u32, } @(private) cff_push :: proc(ip: ^Cff_Interp, v: f64) -> Error { if ip.sp >= CFF_MAX_STACK { // T2 Appendix B fixes the stack at 48. A charstring that overruns it // is either corrupt or hostile; either way the rest of it cannot be // trusted, so the glyph stops here with what it has drawn so far. return .Bad_Cff } ip.stack[ip.sp] = is_finite(v) ? v : 0 ip.sp += 1 return .None } // cff_take_width settles the width question on the first stack-clearing // operator. `nargs` is how many arguments the operator itself takes; one more // on the stack means the first is the width. @(private) cff_take_width :: proc(ip: ^Cff_Interp, nargs: int) { if ip.have_width { return } ip.have_width = true if ip.sp > nargs && ip.priv != nil { w := ip.priv.nominal_width + ip.stack[0] if is_finite(w) { ip.width = w } } } @(private) cff_moveto :: proc(ip: ^Cff_Interp) { if ip.out != nil { // Every subpath of a charstring is closed: T2 has no explicit close // operator, a moveto ends the previous contour. if ip.open { path_close(ip.out) } path_move_to(ip.out, ip.x, ip.y) } ip.open = true } @(private) cff_lineto :: proc(ip: ^Cff_Interp) { if ip.out != nil { path_line_to(ip.out, ip.x, ip.y) } } @(private) cff_curveto :: proc(ip: ^Cff_Interp, x1, y1, x2, y2: f64) { if ip.out != nil { path_curve_to(ip.out, x1, y1, x2, y2, ip.x, ip.y) } } // cff_int reads an operand that is going to be used as an index or a code. // Anything that is not a small whole number cannot be either. @(private) cff_int :: proc(v: f64) -> (int, bool) { if !is_finite(v) || v < -1e9 || v > 1e9 { return 0, false } return int(v), true } @(private) cff_run :: proc(ip: ^Cff_Interp, code: []byte, depth: int) -> Error { if depth > CFF_MAX_SUBR_DEPTH { return .Bad_Cff } s := &ip.stack i := 0 for i < len(code) { if ip.done { return .None } // Once the width is known there is nothing else a width-only run wants, // and stopping here is what keeps cff_glyph_advance from interpreting // whole outlines. if ip.width_only && ip.have_width { ip.done = true return .None } ip.fuel -= 1 if ip.fuel <= 0 { return .Bad_Cff } b0 := code[i] // -- operands (T2 4) ------------------------------------------------ if b0 == 28 { if i + 2 >= len(code) { return .Bad_Cff } cff_push(ip, f64(i16(u16(code[i + 1]) << 8 | u16(code[i + 2])))) or_return i += 3 continue } if b0 >= 32 { switch { case b0 <= 246: cff_push(ip, f64(int(b0) - 139)) or_return i += 1 case b0 <= 250: if i + 1 >= len(code) { return .Bad_Cff } cff_push(ip, f64((int(b0) - 247) * 256 + int(code[i + 1]) + 108)) or_return i += 2 case b0 <= 254: if i + 1 >= len(code) { return .Bad_Cff } cff_push(ip, f64(-(int(b0) - 251) * 256 - int(code[i + 1]) - 108)) or_return i += 2 case: // 255 is a 16.16 fixed-point number, the only fractional // operand a charstring has. if i + 4 >= len(code) { return .Bad_Cff } u := u32(code[i + 1]) << 24 | u32(code[i + 2]) << 16 | u32(code[i + 3]) << 8 | u32(code[i + 4]) cff_push(ip, f64(i32(u)) / 65536) or_return i += 5 } continue } i += 1 // -- operators ------------------------------------------------------ switch b0 { case 1, 3, 18, 23: // hstem, vstem, hstemhm, vstemhm cff_take_width(ip, (ip.sp / 2) * 2) ip.n_stems += ip.sp / 2 ip.sp = 0 case 19, 20: // hintmask, cntrmask // The arguments still on the stack are an implied vstem list. cff_take_width(ip, (ip.sp / 2) * 2) ip.n_stems += ip.sp / 2 ip.sp = 0 n_bytes := (ip.n_stems + 7) / 8 if i + n_bytes > len(code) { return .Bad_Cff } i += n_bytes case 21: // rmoveto cff_take_width(ip, 2) if ip.sp >= 2 { ip.x += s[ip.sp - 2] ip.y += s[ip.sp - 1] } cff_moveto(ip) ip.sp = 0 case 22: // hmoveto cff_take_width(ip, 1) if ip.sp >= 1 { ip.x += s[ip.sp - 1] } cff_moveto(ip) ip.sp = 0 case 4: // vmoveto cff_take_width(ip, 1) if ip.sp >= 1 { ip.y += s[ip.sp - 1] } cff_moveto(ip) ip.sp = 0 case 5: // rlineto for k := 0; k + 1 < ip.sp; k += 2 { ip.x += s[k] ip.y += s[k + 1] cff_lineto(ip) } ip.sp = 0 case 6, 7: // hlineto, vlineto horiz := b0 == 6 for k in 0 ..< ip.sp { if horiz { ip.x += s[k] } else { ip.y += s[k] } cff_lineto(ip) horiz = !horiz } ip.sp = 0 case 8: // rrcurveto for k := 0; k + 5 < ip.sp; k += 6 { cff_rrcurve(ip, s[k], s[k+1], s[k+2], s[k+3], s[k+4], s[k+5]) } ip.sp = 0 case 24: // rcurveline k := 0 for k + 6 <= ip.sp - 2 { cff_rrcurve(ip, s[k], s[k+1], s[k+2], s[k+3], s[k+4], s[k+5]) k += 6 } if k + 2 <= ip.sp { ip.x += s[k] ip.y += s[k + 1] cff_lineto(ip) } ip.sp = 0 case 25: // rlinecurve k := 0 for k + 2 <= ip.sp - 6 { ip.x += s[k] ip.y += s[k + 1] cff_lineto(ip) k += 2 } if k + 6 <= ip.sp { cff_rrcurve(ip, s[k], s[k+1], s[k+2], s[k+3], s[k+4], s[k+5]) } ip.sp = 0 case 26: // vvcurveto k := 0 dx1 := 0.0 if ip.sp % 4 == 1 { dx1 = s[0] k = 1 } for ; k + 3 < ip.sp; k += 4 { x1 := ip.x + dx1 y1 := ip.y + s[k] x2 := x1 + s[k + 1] y2 := y1 + s[k + 2] ip.x = x2 ip.y = y2 + s[k + 3] cff_curveto(ip, x1, y1, x2, y2) dx1 = 0 } ip.sp = 0 case 27: // hhcurveto k := 0 dy1 := 0.0 if ip.sp % 4 == 1 { dy1 = s[0] k = 1 } for ; k + 3 < ip.sp; k += 4 { x1 := ip.x + s[k] y1 := ip.y + dy1 x2 := x1 + s[k + 1] y2 := y1 + s[k + 2] ip.x = x2 + s[k + 3] ip.y = y2 cff_curveto(ip, x1, y1, x2, y2) dy1 = 0 } ip.sp = 0 case 30, 31: // vhcurveto, hvcurveto horiz := b0 == 31 k := 0 for k + 3 < ip.sp { // The odd 5th argument of the last group is the other axis of // the final point, which is otherwise implied to be unchanged. last := ip.sp - k == 5 x1, y1, x2, y2: f64 if horiz { x1 = ip.x + s[k] y1 = ip.y x2 = x1 + s[k + 1] y2 = y1 + s[k + 2] ip.y = y2 + s[k + 3] ip.x = last ? x2 + s[k + 4] : x2 } else { x1 = ip.x y1 = ip.y + s[k] x2 = x1 + s[k + 1] y2 = y1 + s[k + 2] ip.x = x2 + s[k + 3] ip.y = last ? y2 + s[k + 4] : y2 } cff_curveto(ip, x1, y1, x2, y2) k += 4 horiz = !horiz } ip.sp = 0 case 10, 29: // callsubr, callgsubr if ip.sp < 1 { return .Bad_Cff } ip.sp -= 1 n, ok := cff_int(s[ip.sp]) if !ok { return .Bad_Cff } idx: ^Cff_Index bias: int if b0 == 10 { if ip.priv == nil { return .Bad_Cff } idx = &ip.priv.subrs bias = ip.priv.subr_bias } else { idx = &ip.f.gsubrs bias = ip.f.gsubr_bias } body := cff_index_item(idx, n + bias) if body == nil { return .Bad_Cff } cff_run(ip, body, depth + 1) or_return if ip.done { return .None } case 11: // return return .None case 14: // endchar // endchar takes nothing, or the four arguments of the deprecated // seac composition, either of which may be preceded by the width. if !ip.have_width { ip.have_width = true if (ip.sp == 1 || ip.sp == 5) && ip.priv != nil { w := ip.priv.nominal_width + s[0] if is_finite(w) { ip.width = w } } } if ip.sp >= 4 && ip.allow_seac { ip.seac = {s[ip.sp - 4], s[ip.sp - 3], s[ip.sp - 2], s[ip.sp - 1]} ip.have_seac = true } if ip.open && ip.out != nil { path_close(ip.out) } ip.open = false ip.sp = 0 ip.done = true return .None case 12: // escape if i >= len(code) { return .Bad_Cff } b1 := code[i] i += 1 cff_escape(ip, b1) or_return case: // 0, 2, 9, 13, 15, 16 and 17 are reserved. Reaching one means the // operand/operator alternation has lost sync, and anything read // after it would be invented. return .Bad_Cff } } return .None } // cff_rrcurve is one relative cubic: two relative control points and a // relative end point. @(private) cff_rrcurve :: proc(ip: ^Cff_Interp, dxa, dya, dxb, dyb, dxc, dyc: f64) { x1 := ip.x + dxa y1 := ip.y + dya x2 := x1 + dxb y2 := y1 + dyb ip.x = x2 + dxc ip.y = y2 + dyc cff_curveto(ip, x1, y1, x2, y2) } // cff_escape runs the two-byte "12 x" operators: the four flex variants and // the arithmetic, storage and conditional operators. // // The arithmetic set exists for multiple-master interpolation and is close to // unused in practice, but a charstring that reaches an unimplemented operator // would silently draw the wrong shape, so all of it is here. @(private) cff_escape :: proc(ip: ^Cff_Interp, b1: u8) -> Error { s := &ip.stack switch b1 { case 35: // flex if ip.sp >= 13 { cff_rrcurve(ip, s[0], s[1], s[2], s[3], s[4], s[5]) cff_rrcurve(ip, s[6], s[7], s[8], s[9], s[10], s[11]) } ip.sp = 0 case 34: // hflex if ip.sp >= 7 { y0 := ip.y x1 := ip.x + s[0] y1 := ip.y x2 := x1 + s[1] y2 := y1 + s[2] ip.x = x2 + s[3] ip.y = y2 cff_curveto(ip, x1, y1, x2, y2) x3 := ip.x + s[4] y3 := ip.y x4 := x3 + s[5] y4 := y0 ip.x = x4 + s[6] ip.y = y0 cff_curveto(ip, x3, y3, x4, y4) } ip.sp = 0 case 36: // hflex1 if ip.sp >= 9 { y0 := ip.y x1 := ip.x + s[0] y1 := ip.y + s[1] x2 := x1 + s[2] y2 := y1 + s[3] ip.x = x2 + s[4] ip.y = y2 cff_curveto(ip, x1, y1, x2, y2) x3 := ip.x + s[5] y3 := ip.y x4 := x3 + s[6] y4 := y3 + s[7] ip.x = x4 + s[8] ip.y = y0 cff_curveto(ip, x3, y3, x4, y4) } ip.sp = 0 case 37: // flex1 if ip.sp >= 11 { x0 := ip.x y0 := ip.y dx, dy := 0.0, 0.0 for k in 0 ..< 5 { dx += s[k * 2] dy += s[k * 2 + 1] } x1 := ip.x + s[0] y1 := ip.y + s[1] x2 := x1 + s[2] y2 := y1 + s[3] ip.x = x2 + s[4] ip.y = y2 + s[5] cff_curveto(ip, x1, y1, x2, y2) x3 := ip.x + s[6] y3 := ip.y + s[7] x4 := x3 + s[8] y4 := y3 + s[9] // The 11th argument is whichever coordinate moved further overall; // the other returns to where the flex started. if abs(dx) > abs(dy) { ip.x = x4 + s[10] ip.y = y0 } else { ip.x = x0 ip.y = y4 + s[10] } cff_curveto(ip, x3, y3, x4, y4) } ip.sp = 0 case 3: cff_binary(ip, .And) case 4: cff_binary(ip, .Or) case 5: cff_unary(ip, .Not) case 9: cff_unary(ip, .Abs) case 10: cff_binary(ip, .Add) case 11: cff_binary(ip, .Sub) case 12: cff_binary(ip, .Div) case 14: cff_unary(ip, .Neg) case 15: cff_binary(ip, .Eq) case 24: cff_binary(ip, .Mul) case 26: cff_unary(ip, .Sqrt) case 18: // drop if ip.sp > 0 { ip.sp -= 1 } case 20: // put if ip.sp >= 2 { ip.sp -= 2 if j, ok := cff_int(s[ip.sp + 1]); ok && j >= 0 && j < CFF_MAX_TRANSIENT { ip.trans[j] = s[ip.sp] } } case 21: // get if ip.sp >= 1 { v := 0.0 if j, ok := cff_int(s[ip.sp - 1]); ok && j >= 0 && j < CFF_MAX_TRANSIENT { v = ip.trans[j] } s[ip.sp - 1] = v } case 22: // ifelse if ip.sp >= 4 { ip.sp -= 3 s[ip.sp - 1] = s[ip.sp + 1] <= s[ip.sp + 2] ? s[ip.sp - 1] : s[ip.sp] } case 23: // random // A deterministic generator: the operator only promises a value in // (0,1], and a reader that renders the same glyph differently twice is // worse than one that is boringly repeatable. ip.seed = ip.seed * 1664525 + 1013904223 cff_push(ip, f64(ip.seed >> 8) / f64(1 << 24) * 0.999 + 0.001) or_return case 27: // dup if ip.sp >= 1 { cff_push(ip, s[ip.sp - 1]) or_return } case 28: // exch if ip.sp >= 2 { s[ip.sp - 1], s[ip.sp - 2] = s[ip.sp - 2], s[ip.sp - 1] } case 29: // index if ip.sp >= 1 { j, ok := cff_int(s[ip.sp - 1]) if !ok || j < 0 { j = 0 } if j >= ip.sp - 1 { s[ip.sp - 1] = 0 } else { s[ip.sp - 1] = s[ip.sp - 2 - j] } } case 30: // roll cff_roll(ip) case: // Reserved escapes. As with the one-byte operators, continuing past // one would be guesswork. return .Bad_Cff } return .None } @(private) Cff_Unary :: enum u8 { Not, Abs, Neg, Sqrt } @(private) Cff_Binary :: enum u8 { And, Or, Add, Sub, Div, Eq, Mul } @(private) cff_unary :: proc(ip: ^Cff_Interp, op: Cff_Unary) { if ip.sp < 1 { return } a := ip.stack[ip.sp - 1] r := 0.0 switch op { case .Not: r = a == 0 ? 1 : 0 case .Abs: r = abs(a) case .Neg: r = -a // A negative operand has no square root, and a NaN would poison every // coordinate it later touches. case .Sqrt: r = a > 0 ? math.sqrt(a) : 0 } ip.stack[ip.sp - 1] = is_finite(r) ? r : 0 } @(private) cff_binary :: proc(ip: ^Cff_Interp, op: Cff_Binary) { if ip.sp < 2 { return } a := ip.stack[ip.sp - 2] b := ip.stack[ip.sp - 1] ip.sp -= 1 r := 0.0 switch op { case .And: r = (a != 0 && b != 0) ? 1 : 0 case .Or: r = (a != 0 || b != 0) ? 1 : 0 case .Add: r = a + b case .Sub: r = a - b case .Div: r = b != 0 ? a / b : 0 case .Eq: r = a == b ? 1 : 0 case .Mul: r = a * b } ip.stack[ip.sp - 1] = is_finite(r) ? r : 0 } // cff_roll rotates the top N elements of the stack by J places, upwards for a // positive J (T2 4.4). @(private) cff_roll :: proc(ip: ^Cff_Interp) { if ip.sp < 2 { return } j_raw := ip.stack[ip.sp - 1] n_raw := ip.stack[ip.sp - 2] ip.sp -= 2 n, n_ok := cff_int(n_raw) j, j_ok := cff_int(j_raw) if !n_ok || !j_ok || n <= 0 || n > ip.sp { return } j %= n if j < 0 { j += n } if j == 0 { return } tmp: [CFF_MAX_STACK]f64 base := ip.sp - n for k in 0 ..< n { tmp[(k + j) % n] = ip.stack[base + k] } for k in 0 ..< n { ip.stack[base + k] = tmp[k] } } // --------------------------------------------------------------------------- // Glyphs // --------------------------------------------------------------------------- // cff_private_for is the Private DICT a glyph's charstring runs against. A // CIDFont picks it per glyph through the FDSelect, which is why a CID font read // with the wrong FDSelect draws with the wrong subroutines and produces the // characteristic garbage of a mis-parsed CJK font. @(private) cff_private_for :: proc(f: ^Cff_Font, gid: u16) -> ^Cff_Private { if len(f.fds) > 0 { fd := 0 if int(gid) < len(f.fd_select) { fd = int(f.fd_select[gid]) } if fd >= len(f.fds) { fd = 0 } return &f.fds[fd] } return &f.priv } @(private) cff_charstring :: proc(f: ^Cff_Font, gid: u16) -> ([]byte, bool) { if int(gid) >= f.charstrings.count { return nil, false } return cff_index_item(&f.charstrings, int(gid)), true } // cff_execute runs one glyph's charstring into `ip`, which the caller supplies // so the 700-odd bytes of interpreter state stay on its stack. @(private) cff_execute :: proc( ip: ^Cff_Interp, f: ^Cff_Font, gid: u16, out: ^Path, width_only: bool, allow_seac: bool, ) -> Error { ip^ = Cff_Interp{ f = f, out = out, width_only = width_only, allow_seac = allow_seac, fuel = CFF_MAX_CHARSTRING_OPS, seed = 0x2545_F491, } ip.priv = cff_private_for(f, gid) ip.width = ip.priv.default_width if f.charstring_type != 2 { // CharstringType 1 means the charstrings are Type 1, which is a // different language with a different interpreter. The font still // reports its widths and its glyph names; only outlines are lost. return .Not_Implemented } cs, ok := cff_charstring(f, gid) if !ok { return .No_Glyph } err := cff_run(ip, cs, 0) // A charstring that runs off its end without an endchar still leaves a // usable outline, so its last contour is closed here. if ip.open && out != nil { path_close(out) ip.open = false } return err } // cff_glyph_path appends the outline of `gid` to `out`, in glyph space -- the // charstring's own units, which `cff_font_matrix` maps to text space. // // The path is appended, not assigned, so a caller may accumulate several // glyphs. When an error is returned, `out` holds whatever was decoded before // the fault: a partly drawn glyph is usually still recognisable, and is always // better than a page that stops. @(private) cff_glyph_path :: proc(f: ^Cff_Font, gid: u16, out: ^Path) -> Error { if f == nil || out == nil { return .Invalid_Argument } ip: Cff_Interp cff_execute(&ip, f, gid, out, false, true) or_return if ip.have_seac { return cff_seac_compose(f, ip.seac, out) } return .None } // cff_seac_compose draws the two components of a seac -- the deprecated // "standard encoding accented character", which lives on as the four-argument // form of endchar (T2 4.1). The two components are named by their positions in // the Standard Encoding, not by anything in this font's own encoding. @(private) cff_seac_compose :: proc(f: ^Cff_Font, args: [4]f64, out: ^Path) -> Error { adx, ady := args[0], args[1] bcode, b_ok := cff_int(args[2]) acode, a_ok := cff_int(args[3]) if !b_ok || !a_ok || bcode < 0 || bcode > 255 || acode < 0 || acode > 255 { return .Bad_Cff } std := encoding_standard() bgid, has_b := cff_gid_for_name(f, std[bcode]) agid, has_a := cff_gid_for_name(f, std[acode]) if !has_b || !has_a { return .No_Glyph } sub: Cff_Interp cff_execute(&sub, f, bgid, out, false, false) or_return mark := len(out.points) cff_execute(&sub, f, agid, out, false, false) or_return if !is_finite(adx) || !is_finite(ady) { return .None } shift := Point{adx, ady} for k in mark ..< len(out.points) { out.points[k] += shift } if mark < len(out.points) { out.cur += shift out.start += shift } return .None } // cff_glyph_advance is the glyph's horizontal advance in glyph space. // // A CFF keeps widths in the charstrings themselves rather than in a table: the // width is either defaultWidthX, or an optional first argument expressed as a // difference from nominalWidthX. Finding it therefore means interpreting the // charstring, but only as far as its first stack-clearing operator. @(private) cff_glyph_advance :: proc(f: ^Cff_Font, gid: u16) -> f64 { if f == nil { return 0 } ip: Cff_Interp if err := cff_execute(&ip, f, gid, nil, true, false); err == .No_Glyph { return 0 } return ip.width }