forked from phpdave11/gofpdf
-
Notifications
You must be signed in to change notification settings - Fork 0
/
attachments.go
157 lines (139 loc) · 5.12 KB
/
attachments.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
package gofpdf
import (
"crypto/md5"
"encoding/hex"
"fmt"
"strings"
)
// Attachment defines a content to be included in the pdf, in one
// of the following ways :
// - associated with the document as a whole : see SetAttachments()
// - accessible via a link localized on a page : see AddAttachmentAnnotation()
type Attachment struct {
Content []byte
// Filename is the displayed name of the attachment
Filename string
// Description is only displayed when using AddAttachmentAnnotation(),
// and might be modified by the pdf reader.
Description string
objectNumber int // filled when content is included
}
// return the hex encoded checksum of `data`
func checksum(data []byte) string {
tmp := md5.Sum(data)
sl := make([]byte, len(tmp))
for i, v := range tmp {
sl[i] = v
}
return hex.EncodeToString(sl)
}
// Writes a compressed file like object as ``/EmbeddedFile``. Compressing is
// done with deflate. Includes length, compressed length and MD5 checksum.
func (f *Fpdf) writeCompressedFileObject(content []byte) {
lenUncompressed := len(content)
sum := checksum(content)
compressed := sliceCompress(content)
lenCompressed := len(compressed)
f.newobj()
f.outf("<< /Type /EmbeddedFile /Length %d /Filter /FlateDecode /Params << /CheckSum <%s> /Size %d >> >>\n",
lenCompressed, sum, lenUncompressed)
f.putstream(compressed)
f.out("endobj")
}
// Embed includes the content of `a`, and update its internal reference.
func (f *Fpdf) embed(a *Attachment) {
if a.objectNumber != 0 { // already embedded (objectNumber start at 2)
return
}
oldState := f.state
f.state = 1 // we write file content in the main buffer
f.writeCompressedFileObject(a.Content)
streamID := f.n
f.newobj()
f.outf("<< /Type /Filespec /F () /UF %s /EF << /F %d 0 R >> /Desc %s\n>>",
f.textstring(utf8toutf16(a.Filename)),
streamID,
f.textstring(utf8toutf16(a.Description)))
f.out("endobj")
a.objectNumber = f.n
f.state = oldState
}
// SetAttachments writes attachments as embedded files (document attachment).
// These attachments are global, see AddAttachmentAnnotation() for a link
// anchored in a page. Note that only the last call of SetAttachments is
// useful, previous calls are discarded. Be aware that not all PDF readers
// support document attachments. See the SetAttachment example for a
// demonstration of this method.
func (f *Fpdf) SetAttachments(as []Attachment) {
f.attachments = as
}
// embed current attachments. store object numbers
// for later use by getEmbeddedFiles()
func (f *Fpdf) putAttachments() {
for i, a := range f.attachments {
f.embed(&a)
f.attachments[i] = a
}
}
// return /EmbeddedFiles tree name catalog entry.
func (f Fpdf) getEmbeddedFiles() string {
names := make([]string, len(f.attachments))
for i, as := range f.attachments {
names[i] = fmt.Sprintf("(Attachement%d) %d 0 R ", i+1, as.objectNumber)
}
nameTree := fmt.Sprintf("<< /Names [\n %s \n] >>", strings.Join(names, "\n"))
return nameTree
}
// ---------------------------------- Annotations ----------------------------------
type annotationAttach struct {
*Attachment
x, y, w, h float64 // fpdf coordinates (y diff and scaling done)
}
// AddAttachmentAnnotation puts a link on the current page, on the rectangle
// defined by `x`, `y`, `w`, `h`. This link points towards the content defined
// in `a`, which is embedded in the document. Note than no drawing is done by
// this method : a method like `Cell()` or `Rect()` should be called to
// indicate to the reader that there is a link here. Requiring a pointer to an
// Attachment avoids useless copies in the resulting pdf: attachment pointing
// to the same data will have their content only be included once, and be
// shared amongst all links. Be aware that not all PDF readers support
// annotated attachments. See the AddAttachmentAnnotation example for a
// demonstration of this method.
func (f *Fpdf) AddAttachmentAnnotation(a *Attachment, x, y, w, h float64) {
if a == nil {
return
}
f.pageAttachments[f.page] = append(f.pageAttachments[f.page], annotationAttach{
Attachment: a,
x: x * f.k, y: f.hPt - y*f.k, w: w * f.k, h: h * f.k,
})
}
// embed current annotations attachments. store object numbers
// for later use by putAttachmentAnnotationLinks(), which is
// called for each page.
func (f *Fpdf) putAnnotationsAttachments() {
// avoid duplication
m := map[*Attachment]bool{}
for _, l := range f.pageAttachments {
for _, an := range l {
if m[an.Attachment] { // already embedded
continue
}
f.embed(an.Attachment)
}
}
}
func (f *Fpdf) putAttachmentAnnotationLinks(out *fmtBuffer, page int) {
for _, an := range f.pageAttachments[page] {
x1, y1, x2, y2 := an.x, an.y, an.x+an.w, an.y-an.h
as := fmt.Sprintf("<< /Type /XObject /Subtype /Form /BBox [%.2f %.2f %.2f %.2f] /Length 0 >>",
x1, y1, x2, y2)
as += "\nstream\nendstream"
out.printf("<< /Type /Annot /Subtype /FileAttachment /Rect [%.2f %.2f %.2f %.2f] /Border [0 0 0]\n",
x1, y1, x2, y2)
out.printf("/Contents %s ", f.textstring(utf8toutf16(an.Description)))
out.printf("/T %s ", f.textstring(utf8toutf16(an.Filename)))
out.printf("/AP << /N %s>>", as)
out.printf("/FS %d 0 R >>\n", an.objectNumber)
}
}