/
pdf.go
236 lines (202 loc) · 4.94 KB
/
pdf.go
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
// Copyright (C) 2011, Ross Light
package pdfapi
import (
"image"
"io"
"strconv"
)
// Unit is a device-independent dimensional type. On a new canvas, this
// represents 1/72 of an inch.
type Unit float32
func (unit Unit) String() string {
return strconv.FormatFloat(float64(unit), 'f', marshalFloatPrec, 32)
}
// Common unit scales
const (
Pt Unit = 1
Inch Unit = 72
Cm Unit = 28.35
)
// Common page sizes
const (
USLetterWidth Unit = 8.5 * Inch
USLetterHeight Unit = 11.0 * Inch
A4Width Unit = 21.0 * Cm
A4Height Unit = 29.7 * Cm
)
// Document provides a high-level drawing interface for the PDF format.
type Document struct {
encoder
catalog *catalog
pages []indirectObject
fonts map[name]Reference
}
// New creates a new document with no pages.
func NewDocument() *Document {
doc := new(Document)
doc.catalog = &catalog{
Type: catalogType,
}
doc.root = doc.add(doc.catalog)
doc.fonts = make(map[name]Reference, 14)
return doc
}
// NewPage creates a new canvas with the given dimensions.
// TODO: do not directly couple to document
// TODO de-couple page geometry from document geometry
func (doc *Document) NewPage(width, height Unit) *Canvas {
page := &pageDict{
Type: pageType,
MediaBox: Rectangle{Point{0, 0}, Point{width, height}},
CropBox: Rectangle{Point{0, 0}, Point{width, height}},
Resources: resources{
ProcSet: []name{pdfProcSet, textProcSet, imageCProcSet},
Font: make(map[name]interface{}),
XObject: make(map[name]interface{}),
},
}
stream := newStream(streamFlateDecode)
page.Contents = doc.add(stream)
return &Canvas{
doc: doc,
page: page,
contents: stream,
}
}
// Assemble takes a page and integrates it into the document's structure.
func (doc *Document) Assemble(c *Canvas) {
page := c.page
c.ref = c.doc.add(page)
doc.pages = append(doc.pages, indirectObject{c.ref, page})
}
// standardFont returns a reference to a standard font dictionary. If there is
// no font dictionary for the font in the document yet, it is added
// automatically.
func (doc *Document) standardFont(fontName name) Reference {
if ref, ok := doc.fonts[fontName]; ok {
return ref
}
// TODO: check name is standard?
ref := doc.add(standardFontDict{
Type: fontType,
Subtype: fontType1Subtype,
BaseFont: fontName,
})
doc.fonts[fontName] = ref
return ref
}
// AddImage encodes an image into the document's stream and returns its PDF
// file reference. This reference can be used to draw the image multiple times
// without storing the image multiple times.
func (doc *Document) AddImage(img image.Image) Reference {
bd := img.Bounds()
st := newImageStream(streamFlateDecode, bd.Dx(), bd.Dy())
defer st.Close()
switch i := img.(type) {
case *image.RGBA:
encodeRGBAStream(st, i)
case *image.NRGBA:
encodeNRGBAStream(st, i)
case *image.YCbCr:
encodeYCbCrStream(st, i)
default:
encodeImageStream(st, i)
}
return doc.add(st)
}
// Encode writes the document to a writer in the PDF format.
func (doc *Document) Encode(w io.Writer) error {
pageRoot := &pageRootNode{
Type: pageNodeType,
Count: len(doc.pages),
}
doc.catalog.Pages = doc.add(pageRoot)
for _, p := range doc.pages {
page := p.Object.(*pageDict)
page.Parent = doc.catalog.Pages
pageRoot.Kids = append(pageRoot.Kids, p.Reference)
}
return doc.encoder.encode(w)
}
// PDF object types
const (
catalogType name = "Catalog"
pageNodeType name = "Pages"
pageType name = "Page"
fontType name = "Font"
xobjectType name = "XObject"
)
// PDF object subtypes
const (
imageSubtype name = "Image"
fontType1Subtype name = "Type1"
)
type catalog struct {
Type name
Pages Reference
}
type pageRootNode struct {
Type name
Kids []Reference
Count int
}
type pageNode struct {
Type name
Parent Reference
Kids []Reference
Count int
}
type pageDict struct {
Type name
Parent Reference
Resources resources
MediaBox Rectangle
CropBox Rectangle
Contents Reference
}
// Point is a 2D point.
type Point struct {
X, Y Unit
}
// A Rectangle defines a rectangle with two points.
type Rectangle struct {
Min, Max Point
}
// Dx returns the rectangle's width.
func (r Rectangle) Dx() Unit {
return r.Max.X - r.Min.X
}
// Dy returns the rectangle's height.
func (r Rectangle) Dy() Unit {
return r.Max.Y - r.Min.Y
}
func (r Rectangle) marshalPDF(dst []byte) ([]byte, error) {
dst = append(dst, '[', ' ')
dst, _ = marshal(dst, r.Min.X)
dst = append(dst, ' ')
dst, _ = marshal(dst, r.Min.Y)
dst = append(dst, ' ')
dst, _ = marshal(dst, r.Max.X)
dst = append(dst, ' ')
dst, _ = marshal(dst, r.Max.Y)
dst = append(dst, ' ', ']')
return dst, nil
}
type resources struct {
ProcSet []name
Font map[name]interface{}
XObject map[name]interface{}
}
// Predefined procedure sets
const (
pdfProcSet name = "PDF"
textProcSet name = "Text"
imageBProcSet name = "ImageB"
imageCProcSet name = "ImageC"
imageIProcSet name = "ImageI"
)
type standardFontDict struct {
Type name
Subtype name
BaseFont name
}