-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathobject.go
More file actions
244 lines (205 loc) · 6.12 KB
/
Copy pathobject.go
File metadata and controls
244 lines (205 loc) · 6.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
// Package reader takes existing PDF bytes apart: the object syntax, the
// cross-reference machinery, stream filters, and the document structure built
// on top of them. It is the parsing counterpart of github.com/go-pdfkit/pdfkit,
// which writes PDF.
//
// Nothing outside the Go standard library is used, so the package builds for
// js/wasm and for every architecture the fleet targets.
package reader
import "fmt"
// Kind names the eight basic PDF object types plus the two the file format
// adds on top of them: streams and indirect references.
type Kind uint8
// The object kinds. [Object.Kind] reports one of these.
const (
KindNull Kind = iota
KindBool
KindInteger
KindReal
KindString
KindName
KindArray
KindDict
KindStream
KindRef
)
var kindNames = [...]string{
KindNull: "null",
KindBool: "boolean",
KindInteger: "integer",
KindReal: "real",
KindString: "string",
KindName: "name",
KindArray: "array",
KindDict: "dictionary",
KindStream: "stream",
KindRef: "reference",
}
// String names the kind, for error messages.
func (k Kind) String() string {
if int(k) >= len(kindNames) {
return fmt.Sprintf("Kind(%d)", uint8(k))
}
return kindNames[k]
}
// An Object is any value the PDF object syntax can express. The concrete types
// are [Null], [Bool], [Integer], [Real], [String], [Name], [Array], [Dict],
// [Ref] and *[Stream]; Kind tells them apart without a type switch.
type Object interface {
Kind() Kind
}
// Null is the PDF null object. A missing dictionary entry resolves to Null
// rather than to a nil Object, so callers never have to nil-check.
type Null struct{}
// Kind implements [Object].
func (Null) Kind() Kind { return KindNull }
// Bool is a PDF boolean.
type Bool bool
// Kind implements [Object].
func (Bool) Kind() Kind { return KindBool }
// Integer is a PDF integer. Numbers too large for an int64 are parsed as
// [Real], which is what every other reader does.
type Integer int64
// Kind implements [Object].
func (Integer) Kind() Kind { return KindInteger }
// Real is a PDF real number.
type Real float64
// Kind implements [Object].
func (Real) Kind() Kind { return KindReal }
// String is a PDF string, already unescaped: the bytes a literal or hex string
// denotes, with no interpretation of their text encoding.
type String []byte
// Kind implements [Object].
func (String) Kind() Kind { return KindString }
// Name is a PDF name with its #xx escapes resolved and the leading slash
// dropped, so /Type is Name("Type").
type Name string
// Kind implements [Object].
func (Name) Kind() Kind { return KindName }
// Array is a PDF array.
type Array []Object
// Kind implements [Object].
func (Array) Kind() Kind { return KindArray }
// Dict is a PDF dictionary.
type Dict map[Name]Object
// Kind implements [Object].
func (Dict) Kind() Kind { return KindDict }
// Get returns the entry, or [Null] when it is absent. The value may still be a
// [Ref]; use [Resolve] to follow it.
func (d Dict) Get(k Name) Object {
if v, ok := d[k]; ok && v != nil {
return v
}
return Null{}
}
// Ref is an indirect reference: the "12 0 R" that stands in for an object
// stored elsewhere in the file.
type Ref struct {
Num int
Gen int
}
// Kind implements [Object].
func (Ref) Kind() Kind { return KindRef }
// String renders the reference the way it appears in a file.
func (r Ref) String() string { return fmt.Sprintf("%d %d R", r.Num, r.Gen) }
// Stream is a PDF stream: a dictionary plus the raw, still-encoded bytes
// between the stream and endstream keywords. [Decode] applies the filter chain.
type Stream struct {
Dict Dict
Raw []byte
}
// Kind implements [Object].
func (*Stream) Kind() Kind { return KindStream }
// A Resolver fetches the object an indirect reference names. A document
// supplies one; a nil Resolver makes every reference resolve to [Null], which
// is what a caller parsing an isolated fragment wants.
type Resolver func(Ref) (Object, error)
// maxRefDepth bounds a chain of references pointing at references. A file can
// make that chain a cycle, and a cycle must not hang the reader.
const maxRefDepth = 64
// Resolve follows indirect references until it reaches a direct object. A nil
// object, an absent entry and an unresolvable reference all yield [Null].
func Resolve(o Object, r Resolver) (Object, error) {
if o == nil {
return Null{}, nil
}
for depth := 0; ; depth++ {
ref, ok := o.(Ref)
if !ok {
return o, nil
}
if r == nil {
return Null{}, nil
}
if depth >= maxRefDepth {
return nil, fmt.Errorf("reader: reference chain longer than %d at %s", maxRefDepth, ref)
}
v, err := r(ref)
if err != nil {
return nil, err
}
if v == nil {
return Null{}, nil
}
o = v
}
}
// ToBool reports the value of a boolean object.
func ToBool(o Object) (bool, bool) {
v, ok := o.(Bool)
return bool(v), ok
}
// ToInt reports the value of an integer object, and of a real whose value is a
// whole number — files do write "3.0" where an integer belongs.
func ToInt(o Object) (int64, bool) {
switch v := o.(type) {
case Integer:
return int64(v), true
case Real:
if float64(int64(v)) == float64(v) {
return int64(v), true
}
}
return 0, false
}
// ToFloat reports the value of any numeric object.
func ToFloat(o Object) (float64, bool) {
switch v := o.(type) {
case Integer:
return float64(v), true
case Real:
return float64(v), true
}
return 0, false
}
// ToString reports the bytes of a string object.
func ToString(o Object) ([]byte, bool) {
v, ok := o.(String)
return []byte(v), ok
}
// ToName reports the value of a name object.
func ToName(o Object) (Name, bool) {
v, ok := o.(Name)
return v, ok
}
// ToArray reports the elements of an array object.
func ToArray(o Object) (Array, bool) {
v, ok := o.(Array)
return v, ok
}
// ToDict reports the dictionary of a dictionary object, or a stream's own
// dictionary — a stream is a dictionary everywhere a dictionary is expected.
func ToDict(o Object) (Dict, bool) {
switch v := o.(type) {
case Dict:
return v, true
case *Stream:
return v.Dict, true
}
return nil, false
}
// ToStream reports the stream object.
func ToStream(o Object) (*Stream, bool) {
v, ok := o.(*Stream)
return v, ok
}