forked from jung-kurt/gofpdf
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
47a2c24
commit 5e6addf
Showing
4 changed files
with
225 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
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. | ||
// You only need to call `Embed` explicitly | ||
// if you want to put multiple links towards the same content, using AddAttachmentAnnotation(). | ||
// To avoid useless copies in the resulting pdf, call `Embded` once. | ||
// After the call, `a` can now be used in several annotations | ||
// without duplicating its content . | ||
func (a *Attachment) Embed(f *Fpdf) { | ||
if a.objectNumber != 0 { // already embeded (objectNumber start at 2) | ||
return | ||
} | ||
oldState := f.state | ||
if f.state == 2 { // page mode | ||
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 | ||
} | ||
|
||
// Write 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 usefull, previous calls are discarded. | ||
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 { | ||
a.Embed(f) | ||
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. | ||
// No drawing is done. | ||
// See Attachment.Embed() to avoid unwanted data duplication. | ||
func (f *Fpdf) AddAttachmentAnnotation(a Attachment, x, y, w, h float64) { | ||
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() { | ||
for _, l := range f.pageAttachments { | ||
for i, an := range l { | ||
an.Attachment.Embed(f) | ||
l[i] = an | ||
} | ||
} | ||
} | ||
|
||
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 >>", an.objectNumber) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters