475 lines
15 KiB
Odin
475 lines
15 KiB
Odin
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
|
|
}
|