attachments.go 5.1 KB

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