From 8ea841c5cc6dcdb7071a66b5e9777bbfc125fbcd Mon Sep 17 00:00:00 2001 From: erikn69 Date: Thu, 2 May 2024 09:39:58 -0500 Subject: [PATCH 1/2] Support Attachments --- .gitattributes | 1 + scripts/Attachments/AttachmentsTrait.php | 123 ++++++++++++++++++ scripts/Attachments/README.md | 47 +++++++ scripts/Attachments/attached.txt | 1 + scripts/Attachments/ex.pdf | Bin 0 -> 1852 bytes scripts/Attachments/ex.php | 19 +++ scripts/Attachments/info.htm | 31 +++++ scripts/PDFBookmark/PDFBookmarkTrait.php | 15 ++- src/FawnoFPDF.php | 18 ++- tests/Scripts/AttachmentsTraitTest.php | 26 ++++ tests/TestCase.php | 2 +- tests/examples/example.pdf | Bin 22450 -> 23369 bytes .../examples/exampleAttachmentsTraitTest.pdf | Bin 0 -> 1852 bytes tests/test.php | 8 ++ 14 files changed, 284 insertions(+), 7 deletions(-) create mode 100644 scripts/Attachments/AttachmentsTrait.php create mode 100644 scripts/Attachments/README.md create mode 100644 scripts/Attachments/attached.txt create mode 100644 scripts/Attachments/ex.pdf create mode 100644 scripts/Attachments/ex.php create mode 100644 scripts/Attachments/info.htm create mode 100644 tests/Scripts/AttachmentsTraitTest.php create mode 100644 tests/examples/exampleAttachmentsTraitTest.pdf diff --git a/.gitattributes b/.gitattributes index 01ed98c..5e1e7d4 100644 --- a/.gitattributes +++ b/.gitattributes @@ -77,6 +77,7 @@ fpdf/** eol=crlf /scripts/*/ex*.pdf export-ignore /scripts/*/ex.php export-ignore /scripts/*/info.htm export-ignore +/scripts/*/*.txt export-ignore /scripts/*/*.png export-ignore /scripts/*/*.jpg export-ignore /scripts/*/*.json export-ignore diff --git a/scripts/Attachments/AttachmentsTrait.php b/scripts/Attachments/AttachmentsTrait.php new file mode 100644 index 0000000..fd3f816 --- /dev/null +++ b/scripts/Attachments/AttachmentsTrait.php @@ -0,0 +1,123 @@ +files[] = array('file'=>$file, 'name'=>$name, 'desc'=>$desc); + } + + /** + * Force the PDF viewer to open the attachment pane when the document is loaded + * + * @return void + */ + public function OpenAttachmentPane() + { + $this->open_attachment_pane = true; + } + + protected function _putfiles() + { + if(empty($this->files)) return; + + foreach($this->files as $i=>&$info) + { + $file = $info['file']; + $name = $info['name']; + $desc = $info['desc']; + + $fc = file_get_contents($file); + if($fc===false) + $this->Error('Cannot open file: '.$file); + $size = strlen($fc); + $date = @date('YmdHisO', filemtime($file)); + $md = 'D:'.substr($date,0,-2)."'".substr($date,-2)."'";; + + $this->_newobj(); + $info['n'] = $this->n; + $this->_put('<<'); + $this->_put('/Type /Filespec'); + $this->_put('/F ('.$this->_escape($name).')'); + $this->_put('/UF '.$this->_textstring($name)); + $this->_put('/EF <n+1).' 0 R>>'); + if($desc) + $this->_put('/Desc '.$this->_textstring($desc)); + $this->_put('/AFRelationship /Unspecified'); + $this->_put('>>'); + $this->_put('endobj'); + + $this->_newobj(); + $this->_put('<<'); + $this->_put('/Type /EmbeddedFile'); + $this->_put('/Subtype /application#2Foctet-stream'); + $this->_put('/Length '.$size); + $this->_put('/Params <_textstring($md).'>>'); + $this->_put('>>'); + $this->_putstream($fc); + $this->_put('endobj'); + } + unset($info); + + $this->_newobj(); + $this->n_files = $this->n; + $a = array(); + foreach($this->files as $i=>$info) + $a[] = $this->_textstring(sprintf('%03d',$i)).' '.$info['n'].' 0 R'; + $this->_put('<<'); + $this->_put('/Names ['.implode(' ',$a).']'); + $this->_put('>>'); + $this->_put('endobj'); + } + + protected function _putresources() + { + parent::_putresources(); + $this->_putfiles(); + } + + protected function _putfilescatalog() + { + if(empty($this->files)) return; + + $this->_put('/Names <n_files.' 0 R>>'); + $a = array(); + foreach($this->files as $info) + $a[] = $info['n'].' 0 R'; + $this->_put('/AF ['.implode(' ',$a).']'); + if($this->open_attachment_pane) + $this->_put('/PageMode /UseAttachments'); + } + + protected function _putcatalog() + { + parent::_putcatalog(); + $this->_putfilescatalog(); + } + } diff --git a/scripts/Attachments/README.md b/scripts/Attachments/README.md new file mode 100644 index 0000000..ac3d938 --- /dev/null +++ b/scripts/Attachments/README.md @@ -0,0 +1,47 @@ +# AttachmentsTrait +![GitHub license](https://img.shields.io/badge/license-FPDF-green) +[![Author](https://img.shields.io/badge/author-Olivier-blue)](mailto:oliver@fpdf.org?subject=Bookmarks) + +This script allows to attach files to the PDF. + +## Usage +The method to attach a file is: + +```php +/** + * Add a attachment + * + * @param string $file path to the file to attach. + * @param string $name the name under which the file will be attached. The default value is taken from file. + * @param string $desc an optional description. + * @return void + */ +AttachmentsTrait::Attach(string file [, string name [, string desc]]); +``` + +The `OpenAttachmentPane()` method is also provided to force the PDF viewer to open the attachment pane when the document is loaded. + +## Example + +```php +Attach('attached.txt'); +$pdf->OpenAttachmentPane(); +$pdf->AddPage(); +$pdf->SetFont('Arial','',14); +$pdf->Write(5,'This PDF contains an attached file.'); + +$pdf->Output('F', __DIR__ . '/example.pdf'); +``` +[Result](ex.pdf) diff --git a/scripts/Attachments/attached.txt b/scripts/Attachments/attached.txt new file mode 100644 index 0000000..c876840 --- /dev/null +++ b/scripts/Attachments/attached.txt @@ -0,0 +1 @@ +Attached file. \ No newline at end of file diff --git a/scripts/Attachments/ex.pdf b/scripts/Attachments/ex.pdf new file mode 100644 index 0000000000000000000000000000000000000000..5eacc38c40301aa84b9990132c6cf263acdb422d GIT binary patch literal 1852 zcmaJ?4Nw$i7-lr6*%>u72BpzH$x|R_;OcWk<5w$(@bk@>izB(xL~H8x!c|M+kM~X zdB5j<)>Po~q_Syd%8VGKR#a1VJ6-gA9Y%D4;KvmF2e6_e7FN9!?Zui}9}uyIxNyL! zDmtud$U^QN4hkz0$wF?8Y#zBr#{oopY6Km-u&7FqJQpi|y$ac^@QS7fuuw|{+vCpm zQk&63(O+NcIVPskA9k+apU{?YdCvBw34$~0)FbcRzw3*%CAVifSaVxmX8jV=pm;6o z-9)SKTAMzn;cfnO{Fg^QbtLa=Eo;5+)uX*b`iC8uF2Tcu^@uJJov90E*7Tk}PDYLF8j#&1$ozaXjKJY#MJXr6#FcC)55Pb#uBU zl6$H5tg@AP@Z_yH$HA8#$W7Q{+UCmH&Hw(^g^f|oCU;8Ag20ur6DJxj9L_Eo?)5jU zef9~urE4^P=+qm%hn|To?eWdL6|~OUZr;%N>sJLIG#x#*u;*Msen-pI>Nn%(ue!9e z?3KG?!F0MZZeRDp^Ea;i*>f=Zn~@!ZFZ>WU`p$@@sXwYQw)ypgoiEOA>KOT{YtfT; zmtWkQ+HvRXare2goh|hRSJ~}qw zY+k*&{Abg1+jz&7xiO1lS0%1pJdayivGVkOQy?+Bv)eVg`^<(W&YjCD(Ot)*t3IyY zdTqvD{QbNWi3^03x!K)21_xHeUcM;B4z-SMvx%SI+vPh`@-Vyc$jQWZ^C|!NdBgoL z-+Vm(>Ym&7K>07psbhANa6QMi=hlHf__ZH?pKrLNePo&4$lS4ItP|@_-#Z-avka8= z|2>p9>`M7-Q`DweeTSwCA$@Yk!qWGEswp~4&@j0|bT+POz`8676kS+fp@+tUJPD$3 zD!v%{7>`+#^B)_5>i#mb@(A1<9Ga~93%;XxZ)nO45pd>-p1+fZC zX?jpkrs(47A-4x|!lOJX1$pUAj~Bxn%c`PP$#sY>R!BCvQpQq5UGn%izi?f5Z3UJj zEWt#8sZ2CcsH>}iwvv30avoLGv7TxS>9|F(mXHYvwHkp^C^urnS`eM5N-ppdl;m2$ zF)YWjEWzgj(=7UlPMG$%u5&8#&!9 znf-BYuy<0u2HAK)GyMufN;?RrGB=Fi|4Tt&H5~q%+8w}t|ia{(#0axQd=~k=R ziYn1~jI%;QmLZQW`{;h(}jQ@>KAJ@bCk*C I&i7FN0xK$4f&c&j literal 0 HcmV?d00001 diff --git a/scripts/Attachments/ex.php b/scripts/Attachments/ex.php new file mode 100644 index 0000000..957921b --- /dev/null +++ b/scripts/Attachments/ex.php @@ -0,0 +1,19 @@ +Attach('attached.txt'); + $pdf->OpenAttachmentPane(); + $pdf->AddPage(); + $pdf->SetFont('Arial','',14); + $pdf->Write(5,'This PDF contains an attached file.'); + + $pdf->Output('F', __DIR__ . '/example.pdf'); diff --git a/scripts/Attachments/info.htm b/scripts/Attachments/info.htm new file mode 100644 index 0000000..9679e33 --- /dev/null +++ b/scripts/Attachments/info.htm @@ -0,0 +1,31 @@ + + + + +Attachments + + + +

Attachments

+

Informations

+Author: Olivier
+License: FPDF +

Description

+This script allows to attach files to the PDF. The method to attach a file is:
+
+Attach(string file [, string name [, string desc]])
+
+file: path to the file to attach.
+name: the name under which the file will be attached. The default value is taken from file.
+desc: an optional description.
+
+The OpenAttachmentPane() method is also provided to force the PDF viewer to open the attachment +pane when the document is loaded.
+
+Note: this feature is supported by Adobe Reader but not by all alternative readers. + + diff --git a/scripts/PDFBookmark/PDFBookmarkTrait.php b/scripts/PDFBookmark/PDFBookmarkTrait.php index 9b4ffe7..27bab85 100644 --- a/scripts/PDFBookmark/PDFBookmarkTrait.php +++ b/scripts/PDFBookmark/PDFBookmarkTrait.php @@ -87,17 +87,22 @@ protected function _putbookmarks() protected function _putresources() { - parent::_putresources(); - $this->_putbookmarks(); + parent::_putresources(); + $this->_putbookmarks(); } - protected function _putcatalog() - { - parent::_putcatalog(); + protected function _putbookmarkscatalog() + { if(count($this->outlines)>0) { $this->_put('/Outlines '.$this->outlineRoot.' 0 R'); $this->_put('/PageMode /UseOutlines'); } } + + protected function _putcatalog() + { + parent::_putcatalog(); + $this->_putbookmarkscatalog(); + } } diff --git a/src/FawnoFPDF.php b/src/FawnoFPDF.php index c60351e..04e771f 100644 --- a/src/FawnoFPDF.php +++ b/src/FawnoFPDF.php @@ -7,6 +7,7 @@ use Fawno\FPDF\Traits\PDFMacroableTrait; use Fawno\FPDF\PDFWrapper; use Fawno\FPDF\Traits\FontsTrait; + use FPDF\Scripts\Attachments\AttachmentsTrait; use FPDF\Scripts\PDFBookmark\PDFBookmarkTrait; use FPDF\Scripts\PDFCode128\PDFCode128Trait; use FPDF\Scripts\PDFDraw\PDFDrawTrait; @@ -19,7 +20,14 @@ class FawnoFPDF extends PDFWrapper { use FontsTrait; use PDFMacroableTrait; - use PDFBookmarkTrait { PDFBookmarkTrait::_putresources as PDFBookmark_putresources; } + use AttachmentsTrait { + AttachmentsTrait::_putresources as Attachments_putresources; + AttachmentsTrait::_putcatalog as Attachments_putcatalog; + } + use PDFBookmarkTrait { + PDFBookmarkTrait::_putresources as PDFBookmark_putresources; + PDFBookmarkTrait::_putcatalog as PDFBookmark_putcatalog; + } use PDFProtectionTrait { PDFProtectionTrait::_putresources as PDFProtection_putresources; } use PDFRotateTrait; use CMYKTrait; @@ -33,5 +41,13 @@ protected function _putresources () { parent::_putresources(); $this->_putbookmarks(); $this->_encrypresources(); + $this->_putfiles(); + } + + protected function _putcatalog() + { + parent::_putcatalog(); + $this->_putbookmarkscatalog(); + $this->_putfilescatalog(); } } diff --git a/tests/Scripts/AttachmentsTraitTest.php b/tests/Scripts/AttachmentsTraitTest.php new file mode 100644 index 0000000..2380933 --- /dev/null +++ b/tests/Scripts/AttachmentsTraitTest.php @@ -0,0 +1,26 @@ +Attach(__DIR__ . '/../../scripts/Attachments/attached.txt'); + $pdf->OpenAttachmentPane(); + $pdf->AddPage(); + $pdf->SetFont('Arial','',14); + $pdf->Write(5,'This PDF contains an attached file.'); + + $this->assertFileCanBeCreated($pdf); + + $this->assertPdfIsOk($pdf); + } + } diff --git a/tests/TestCase.php b/tests/TestCase.php index 99228d4..4c656a2 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -68,7 +68,7 @@ public function assertPdfAreEquals (string $expected, string $actual) : void { $diff = []; foreach ($differences as $oid => $obj) { $keys = (is_a($obj->get_value(), PDFValue::class) ? $obj->get_keys() : false) ?: ['OID_' . $obj->get_oid()]; - $diff = array_merge($diff, array_diff($keys, ['Producer', 'CreationDate', 'Title', 'O', 'U'])); + $diff = array_merge($diff, array_diff($keys, ['Producer', 'CreationDate', 'ModDate', 'Title', 'O', 'U', 'UF', 'Params', 'Names'])); } $this->assertEquals([], $diff, 'The PDFs contents have differences.'); diff --git a/tests/examples/example.pdf b/tests/examples/example.pdf index 680f0ebd0e2657c590f81d5b5e5bf9432037c8ab..c6a64e1b185950f3e98a54e992b1f42239eed5b3 100644 GIT binary patch delta 7743 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, 195); $pdf->Write(5, 'ABC sets combined: "' . $code . '"'); + //Attachments + $pdf->AddPage(); + $pdf->Bookmark('Attachments', false); + $pdf->Attach(__DIR__ . '/../scripts/Attachments/attached.txt'); + $pdf->OpenAttachmentPane(); + $pdf->SetFont('Arial','',14); + $pdf->Write(5,'This PDF contains an attached file.'); + $pdf->Output('F', __DIR__ . '/example.pdf'); From 8df38dc1751b90ea2cc583e37bab3da106d3009f Mon Sep 17 00:00:00 2001 From: Fernando Herrero Date: Thu, 2 May 2024 21:16:00 +0200 Subject: [PATCH 2/2] Updated List of FPDFTraits --- scripts/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/README.md b/scripts/README.md index 92a2da6..397d6a2 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -15,3 +15,4 @@ $pdf = new class extends FPDF { 45. [PDFMemImage](PDFMemImage) by **Olivier** (2004-04-05) 69. [PDFDraw](PDFDraw) by **David Hernández Sanz** (2005-01-16) 88. [PDFCode128](PDFCode128) by **Roland Gautier** (2016-01-31) +95. [Attachments](Attachments) by **Olivier** (2012-04-29)