github.com/phpdave11/gofpdf@v1.4.2/attachments.go (about) 1 package gofpdf 2 3 import ( 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() 14 type 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` 28 func 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. 39 func (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. 52 func (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. 76 func (f *Fpdf) SetAttachments(as []Attachment) { 77 f.attachments = as 78 } 79 80 // embed current attachments. store object numbers 81 // for later use by getEmbeddedFiles() 82 func (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. 90 func (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 101 type 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. 117 func (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. 130 func (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 143 func (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 }