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  }