package pdf import "core:strconv" import "core:strings" // Ref is an indirect reference: "12 0 R". // // Object number 0 is reserved by the format for the head of the free list, so // a Ref with num == 0 is the library's "no reference" value. Ref :: struct { num: u32, gen: u16, } // Name is a PDF name object with the leading '/' stripped and any #xx escapes // already decoded, so `Name("Adobe Green")` is what a file wrote as // `/Adobe#20Green`. Name :: distinct string // String is a PDF string object as raw bytes: escapes resolved, hex pairs // packed, and -- in an encrypted document -- already decrypted. It is *not* // text. A string that is meant to be read by a human still has to go through // `decode_text_string`, which handles the UTF-16BE and UTF-8 byte-order marks // and the PDFDocEncoding fallback. String :: distinct []byte // Array is a PDF array. Elements may be indirect references; use `array_get` // rather than indexing directly when the element could be one. // // The struct wrapper is not decoration: `Object` holds a `^Array` and an array // holds `Object`s, and Odin will not close that cycle through a bare // `[dynamic]Object`. Going through `items` also keeps every read site honest // about the fact that an element may be a reference. Array :: struct { items: [dynamic]Object, } // Dict is a PDF dictionary, keyed by the decoded name without its '/'. // // A key that is present with a null value and a key that is absent mean the // same thing in almost every part of the format, and this library does not // distinguish them: nulls are dropped at parse time. Dict :: map[string]Object // Stream is a PDF stream: a dictionary plus a run of bytes. // // `raw` is the encoded payload exactly as stored (but decrypted, if the // document was encrypted). The decoded bytes are produced on demand by // `stream_data`, which caches them here. Stream :: struct { dict: ^Dict, raw: []byte, // Cache filled in by `stream_data`. `state` distinguishes "not decoded // yet" from "decoded to nothing", which a length-0 slice cannot. decoded: []byte, state: Stream_Decode_State, // The object this stream was parsed from, needed to derive its per-object // decryption key. {0,0} for streams that came from an object stream (those // are decrypted as part of their container) or were synthesised. ref: Ref, } Stream_Decode_State :: enum u8 { Pending, Decoded, Failed, } // Object is any PDF object. The zero value -- an empty union -- is the null // object, which is also what a missing dictionary entry reads as. Object :: union { bool, i64, // integer object f64, // real object Name, String, Ref, ^Array, ^Dict, ^Stream, } // --------------------------------------------------------------------------- // Construction // // Everything allocated here is meant to live in a Document's arena, so nothing // has a destructor: `close` reclaims the lot. Passing a non-arena allocator is // supported (the parser is usable standalone) but then the caller owns the // teardown. // --------------------------------------------------------------------------- new_dict :: proc(allocator := context.allocator, cap := 8) -> ^Dict { d := new(Dict, allocator) d^ = make(Dict, cap, allocator) return d } new_array :: proc(allocator := context.allocator, cap := 8) -> ^Array { a := new(Array, allocator) a.items = make([dynamic]Object, 0, cap, allocator) return a } new_stream :: proc(dict: ^Dict, raw: []byte, allocator := context.allocator) -> ^Stream { s := new(Stream, allocator) s.dict = dict s.raw = raw s.state = .Pending return s } // --------------------------------------------------------------------------- // Type tests and coercions // // Every `to_*` returns (value, ok) and never resolves references, so a caller // that might be holding a Ref must go through `resolve` (or one of the // resolving accessors below) first. // --------------------------------------------------------------------------- is_null :: proc(o: Object) -> bool { return o == nil } to_bool :: proc(o: Object) -> (val: bool, ok: bool) { val, ok = o.(bool) return } to_int :: proc(o: Object) -> (val: i64, ok: bool) { #partial switch v in o { case i64: return v, true // Files write "1.0" where an integer is required often enough that // refusing it would reject working documents. case f64: if v >= -9.22e18 && v <= 9.22e18 { return i64(v), true } } return 0, false } to_real :: proc(o: Object) -> (val: f64, ok: bool) { val, ok = o.(f64) return } // to_number accepts either numeric object type. This is what almost every // caller wants: the format's distinction between 1 and 1.0 carries no meaning // outside of a handful of places (array indices, object numbers, /Length). to_number :: proc(o: Object) -> (val: f64, ok: bool) { #partial switch v in o { case i64: return f64(v), true case f64: return v, true } return 0, false } to_name :: proc(o: Object) -> (val: string, ok: bool) { n, is_name := o.(Name) return string(n), is_name } to_bytes :: proc(o: Object) -> (val: []byte, ok: bool) { s, is_str := o.(String) return transmute([]byte)s, is_str } to_array :: proc(o: Object) -> (val: ^Array, ok: bool) { val, ok = o.(^Array) return } // to_dict accepts a stream where a dictionary is wanted and hands back the // stream's dictionary. Several parts of the format (/Resources, /Group, // annotation appearances) are reached through entries that are sometimes one // and sometimes the other. to_dict :: proc(o: Object) -> (val: ^Dict, ok: bool) { #partial switch v in o { case ^Dict: return v, true case ^Stream: return v.dict, v.dict != nil } return nil, false } to_stream :: proc(o: Object) -> (val: ^Stream, ok: bool) { val, ok = o.(^Stream) return } to_ref :: proc(o: Object) -> (val: Ref, ok: bool) { val, ok = o.(Ref) return } // name_is reports whether o is exactly this name. Saves a cast at every // /Type and /Subtype check in the library. name_is :: proc(o: Object, want: string) -> bool { n, ok := o.(Name) return ok && string(n) == want } // object_kind names an object's type for diagnostics. object_kind :: proc(o: Object) -> string { switch _ in o { case bool: return "boolean" case i64: return "integer" case f64: return "real" case Name: return "name" case String: return "string" case Ref: return "reference" case ^Array: return "array" case ^Dict: return "dictionary" case ^Stream: return "stream" } return "null" } // object_identity is a stable cache key for an object: the address of the // container it parsed to. Document.cache guarantees one address per object // number, so the same indirect reference always keys the same slot, and a // dictionary written inline in a resource is keyed by its own address. // Scalars (a name, a number) have no identity and key nothing -- they cost // nothing to rebuild. This is what `colorspace_load` and `function_load` cache // on, which is why it lives here rather than in either of them. @(private) object_identity :: proc(o: Object) -> uintptr { #partial switch v in o { case ^Dict: return uintptr(rawptr(v)) case ^Array: return uintptr(rawptr(v)) case ^Stream: return uintptr(rawptr(v)) } return 0 } // --------------------------------------------------------------------------- // Dictionary access // // The `*_raw` forms never touch the Document and never follow a reference. // Everything else takes a Document (which may be nil, degrading to the raw // behaviour) and resolves indirect references before answering, because at // almost every read site in a PDF the value is allowed to be one. // --------------------------------------------------------------------------- dict_has :: proc(d: ^Dict, key: string) -> bool { if d == nil { return false } return key in d^ } dict_get_raw :: proc(d: ^Dict, key: string) -> Object { if d == nil { return nil } return d[key] or_else nil } dict_get :: proc(doc: ^Document, d: ^Dict, key: string) -> Object { if d == nil { return nil } v := d[key] or_else nil return resolve(doc, v) } // dict_get_alt reads `key`, falling back to `alt`. Inline image dictionaries // abbreviate their keys (/W for /Width, /BPC for /BitsPerComponent), and the // abbreviation is legal in both spellings for some of them. dict_get_alt :: proc(doc: ^Document, d: ^Dict, key, alt: string) -> Object { v := dict_get(doc, d, key) if v == nil { v = dict_get(doc, d, alt) } return v } dict_set :: proc(d: ^Dict, key: string, value: Object) { if d == nil { return } d[key] = value } dict_bool :: proc(doc: ^Document, d: ^Dict, key: string, default := false) -> bool { v, ok := to_bool(dict_get(doc, d, key)) return ok ? v : default } dict_int :: proc(doc: ^Document, d: ^Dict, key: string, default: i64 = 0) -> i64 { v, ok := to_int(dict_get(doc, d, key)) return ok ? v : default } dict_num :: proc(doc: ^Document, d: ^Dict, key: string, default: f64 = 0) -> f64 { v, ok := to_number(dict_get(doc, d, key)) return ok ? v : default } dict_name :: proc(doc: ^Document, d: ^Dict, key: string, default := "") -> string { v, ok := to_name(dict_get(doc, d, key)) return ok ? v : default } dict_bytes :: proc(doc: ^Document, d: ^Dict, key: string) -> []byte { v, _ := to_bytes(dict_get(doc, d, key)) return v } dict_dict :: proc(doc: ^Document, d: ^Dict, key: string) -> ^Dict { v, _ := to_dict(dict_get(doc, d, key)) return v } dict_array :: proc(doc: ^Document, d: ^Dict, key: string) -> ^Array { v, _ := to_array(dict_get(doc, d, key)) return v } dict_stream :: proc(doc: ^Document, d: ^Dict, key: string) -> ^Stream { v, _ := to_stream(dict_get(doc, d, key)) return v } // dict_ref returns the entry *without* resolving it, for the places that need // the identity of the referenced object rather than its value -- structural // keys like /Parent, where following the reference is exactly what causes a // cyclic walk. dict_ref :: proc(d: ^Dict, key: string) -> (Ref, bool) { return to_ref(dict_get_raw(d, key)) } // dict_type reads /Type. Missing /Type is extremely common in the wild // (it is optional on most dictionaries), so callers should treat "" as // "unknown", never as "wrong". dict_type :: proc(doc: ^Document, d: ^Dict) -> string { return dict_name(doc, d, "Type") } dict_subtype :: proc(doc: ^Document, d: ^Dict) -> string { return dict_name(doc, d, "Subtype") } // --------------------------------------------------------------------------- // Array access // --------------------------------------------------------------------------- array_len :: proc(a: ^Array) -> int { if a == nil { return 0 } return len(a.items) } array_get_raw :: proc(a: ^Array, i: int) -> Object { if a == nil || i < 0 || i >= len(a.items) { return nil } return a.items[i] } array_get :: proc(doc: ^Document, a: ^Array, i: int) -> Object { return resolve(doc, array_get_raw(a, i)) } array_num :: proc(doc: ^Document, a: ^Array, i: int, default: f64 = 0) -> f64 { v, ok := to_number(array_get(doc, a, i)) return ok ? v : default } array_int :: proc(doc: ^Document, a: ^Array, i: int, default: i64 = 0) -> i64 { v, ok := to_int(array_get(doc, a, i)) return ok ? v : default } array_name :: proc(doc: ^Document, a: ^Array, i: int, default := "") -> string { v, ok := to_name(array_get(doc, a, i)) return ok ? v : default } array_dict :: proc(doc: ^Document, a: ^Array, i: int) -> ^Dict { v, _ := to_dict(array_get(doc, a, i)) return v } array_push :: proc(a: ^Array, o: Object) { if a == nil { return } append(&a.items, o) } // array_numbers copies an array of numbers into a freshly allocated slice. // Non-numeric entries read as 0 rather than failing, matching how viewers // treat a stray null in a /Widths or /Decode array. array_numbers :: proc(doc: ^Document, a: ^Array, allocator := context.allocator) -> []f64 { n := array_len(a) if n == 0 { return nil } out := make([]f64, n, allocator) for i in 0 ..< n { out[i] = array_num(doc, a, i, 0) } return out } // --------------------------------------------------------------------------- // Names // --------------------------------------------------------------------------- // decode_name_escapes resolves the #xx escapes in a raw name token. // Returns the input unchanged (and allocates nothing) when there are none, // which is the overwhelmingly common case. decode_name_escapes :: proc(raw: string, allocator := context.allocator) -> string { if strings.index_byte(raw, '#') < 0 { return raw } b := strings.builder_make(0, len(raw), allocator) for i := 0; i < len(raw); { if raw[i] == '#' && i + 2 < len(raw) { hi, hi_ok := hex_value(raw[i + 1]) lo, lo_ok := hex_value(raw[i + 2]) // "#" followed by anything that isn't two hex digits is kept // verbatim: it is a broken name, not a reason to drop bytes. if hi_ok && lo_ok { strings.write_byte(&b, hi << 4 | lo) i += 3 continue } } strings.write_byte(&b, raw[i]) i += 1 } return strings.to_string(b) } @(private) hex_value :: proc "contextless" (c: byte) -> (val: byte, ok: bool) { switch c { case '0' ..= '9': return c - '0', true case 'a' ..= 'f': return c - 'a' + 10, true case 'A' ..= 'F': return c - 'A' + 10, true } return 0, false } // --------------------------------------------------------------------------- // Numbers // --------------------------------------------------------------------------- // parse_number reads the number syntax PDF actually uses, which is looser than // the grammar in the spec: a leading '+', repeated signs, a bare '.', a // trailing '.', and exponent notation all turn up in files produced by real // software. Anything unparsable reads as 0, the value a viewer substitutes. parse_number :: proc(s: string) -> (value: Object, ok: bool) { if len(s) == 0 { return i64(0), false } is_real := false for c in transmute([]byte)s { if c == '.' || c == 'e' || c == 'E' { is_real = true break } } if !is_real { if v, iok := strconv.parse_i64_of_base(s, 10); iok { return v, true } // Integers too large for i64 (seen in damaged files) clamp rather than // failing, so a bad /Length cannot abort a parse on its own. if v, fok := strconv.parse_f64(s); fok { return v, true } return i64(0), false } if v, fok := strconv.parse_f64(s); fok { return v, true } // ".5" and "4." are both legal PDF and both rejected by strict parsers. cleaned := s if len(cleaned) > 0 && cleaned[len(cleaned) - 1] == '.' { cleaned = cleaned[:len(cleaned) - 1] if v, fok := strconv.parse_f64(cleaned); fok { return v, true } } return f64(0), false }