1package gofpdf
2
3import (
4	"crypto/md5"
5	"encoding/hex"
6	"fmt"
7	"strings"
8)
9
10// Attachment defines a content to be included in the pdf, in one
11// of the following ways :
12// 	- associated with the document as a whole : see SetAttachments()
13//	- accessible via a link localized on a page : see AddAttachmentAnnotation()
14type Attachment struct {
15	Content []byte
16
17	// Filename is the displayed name of the attachment
18	Filename string
19
20	// Description is only displayed when using AddAttachmentAnnotation(),
21	// and might be modified by the pdf reader.
22	Description string
23
24	objectNumber int // filled when content is included
25}
26
27// return the hex encoded checksum of `data`
28func checksum(data []byte) string {
29	tmp := md5.Sum(data)
30	sl := make([]byte, len(tmp))
31	for i, v := range tmp {
32		sl[i] = v
33	}
34	return hex.EncodeToString(sl)
35}
36
37// Writes a compressed file like object as ``/EmbeddedFile``. Compressing is
38// done with deflate. Includes length, compressed length and MD5 checksum.
39func (f *Fpdf) writeCompressedFileObject(content []byte) {
40	lenUncompressed := len(content)
41	sum := checksum(content)
42	compressed := sliceCompress(content)
43	lenCompressed := len(compressed)
44	f.newobj()
45	f.outf("<< /Type /EmbeddedFile /Length %d /Filter /FlateDecode /Params << /CheckSum <%s> /Size %d >> >>\n",
46		lenCompressed, sum, lenUncompressed)
47	f.putstream(compressed)
48	f.out("endobj")
49}
50
51// Embed includes the content of `a`, and update its internal reference.
52func (f *Fpdf) embed(a *Attachment) {
53	if a.objectNumber != 0 { // already embedded (objectNumber start at 2)
54		return
55	}
56	oldState := f.state
57	f.state = 1 // we write file content in the main buffer
58	f.writeCompressedFileObject(a.Content)
59	streamID := f.n
60	f.newobj()
61	f.outf("<< /Type /Filespec /F () /UF %s /EF << /F %d 0 R >> /Desc %s\n>>",
62		f.textstring(utf8toutf16(a.Filename)),
63		streamID,
64		f.textstring(utf8toutf16(a.Description)))
65	f.out("endobj")
66	a.objectNumber = f.n
67	f.state = oldState
68}
69
70// SetAttachments writes attachments as embedded files (document attachment).
71// These attachments are global, see AddAttachmentAnnotation() for a link
72// anchored in a page. Note that only the last call of SetAttachments is
73// useful, previous calls are discarded. Be aware that not all PDF readers
74// support document attachments. See the SetAttachment example for a
75// demonstration of this method.
76func (f *Fpdf) SetAttachments(as []Attachment) {
77	f.attachments = as
78}
79
80// embed current attachments. store object numbers
81// for later use by getEmbeddedFiles()
82func (f *Fpdf) putAttachments() {
83	for i, a := range f.attachments {
84		f.embed(&a)
85		f.attachments[i] = a
86	}
87}
88
89// return /EmbeddedFiles tree name catalog entry.
90func (f Fpdf) getEmbeddedFiles() string {
91	names := make([]string, len(f.attachments))
92	for i, as := range f.attachments {
93		names[i] = fmt.Sprintf("(Attachement%d) %d 0 R ", i+1, as.objectNumber)
94	}
95	nameTree := fmt.Sprintf("<< /Names [\n %s \n] >>", strings.Join(names, "\n"))
96	return nameTree
97}
98
99// ---------------------------------- Annotations ----------------------------------
100
101type annotationAttach struct {
102	*Attachment
103
104	x, y, w, h float64 // fpdf coordinates (y diff and scaling done)
105}
106
107// AddAttachmentAnnotation puts a link on the current page, on the rectangle
108// defined by `x`, `y`, `w`, `h`. This link points towards the content defined
109// in `a`, which is embedded in the document. Note than no drawing is done by
110// this method : a method like `Cell()` or `Rect()` should be called to
111// indicate to the reader that there is a link here. Requiring a pointer to an
112// Attachment avoids useless copies in the resulting pdf: attachment pointing
113// to the same data will have their content only be included once, and be
114// shared amongst all links. Be aware that not all PDF readers support
115// annotated attachments. See the AddAttachmentAnnotation example for a
116// demonstration of this method.
117func (f *Fpdf) AddAttachmentAnnotation(a *Attachment, x, y, w, h float64) {
118	if a == nil {
119		return
120	}
121	f.pageAttachments[f.page] = append(f.pageAttachments[f.page], annotationAttach{
122		Attachment: a,
123		x:          x * f.k, y: f.hPt - y*f.k, w: w * f.k, h: h * f.k,
124	})
125}
126
127// embed current annotations attachments. store object numbers
128// for later use by putAttachmentAnnotationLinks(), which is
129// called for each page.
130func (f *Fpdf) putAnnotationsAttachments() {
131	// avoid duplication
132	m := map[*Attachment]bool{}
133	for _, l := range f.pageAttachments {
134		for _, an := range l {
135			if m[an.Attachment] { // already embedded
136				continue
137			}
138			f.embed(an.Attachment)
139		}
140	}
141}
142
143func (f *Fpdf) putAttachmentAnnotationLinks(out *fmtBuffer, page int) {
144	for _, an := range f.pageAttachments[page] {
145		x1, y1, x2, y2 := an.x, an.y, an.x+an.w, an.y-an.h
146		as := fmt.Sprintf("<< /Type /XObject /Subtype /Form /BBox [%.2f %.2f %.2f %.2f] /Length 0 >>",
147			x1, y1, x2, y2)
148		as += "\nstream\nendstream"
149
150		out.printf("<< /Type /Annot /Subtype /FileAttachment /Rect [%.2f %.2f %.2f %.2f] /Border [0 0 0]\n",
151			x1, y1, x2, y2)
152		out.printf("/Contents %s ", f.textstring(utf8toutf16(an.Description)))
153		out.printf("/T %s ", f.textstring(utf8toutf16(an.Filename)))
154		out.printf("/AP << /N %s>>", as)
155		out.printf("/FS %d 0 R >>\n", an.objectNumber)
156	}
157}
158