Skip to content

Commit 8ea841c

Browse files
committed
Support Attachments
1 parent 6ea4dd0 commit 8ea841c

14 files changed

+284
-7
lines changed

.gitattributes

+1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ fpdf/** eol=crlf
7777
/scripts/*/ex*.pdf export-ignore
7878
/scripts/*/ex.php export-ignore
7979
/scripts/*/info.htm export-ignore
80+
/scripts/*/*.txt export-ignore
8081
/scripts/*/*.png export-ignore
8182
/scripts/*/*.jpg export-ignore
8283
/scripts/*/*.json export-ignore
+123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace FPDF\Scripts\Attachments;
5+
//http://www.fpdf.org/en/script/script95.php
6+
7+
trait AttachmentsTrait {
8+
9+
protected $files = array();
10+
protected $n_files;
11+
protected $open_attachment_pane = false;
12+
13+
/**
14+
* Add a attachment
15+
*
16+
* @param string $file path to the file to attach.
17+
* @param string $name the name under which the file will be attached, the default value is taken from file.
18+
* @param string $desc an optional description.
19+
* @return void
20+
*/
21+
public function Attach($file, $name='', $desc='')
22+
{
23+
if($name=='')
24+
{
25+
$p = strrpos($file,'/');
26+
if($p===false)
27+
$p = strrpos($file,'\\');
28+
if($p!==false)
29+
$name = substr($file,$p+1);
30+
else
31+
$name = $file;
32+
}
33+
$this->files[] = array('file'=>$file, 'name'=>$name, 'desc'=>$desc);
34+
}
35+
36+
/**
37+
* Force the PDF viewer to open the attachment pane when the document is loaded
38+
*
39+
* @return void
40+
*/
41+
public function OpenAttachmentPane()
42+
{
43+
$this->open_attachment_pane = true;
44+
}
45+
46+
protected function _putfiles()
47+
{
48+
if(empty($this->files)) return;
49+
50+
foreach($this->files as $i=>&$info)
51+
{
52+
$file = $info['file'];
53+
$name = $info['name'];
54+
$desc = $info['desc'];
55+
56+
$fc = file_get_contents($file);
57+
if($fc===false)
58+
$this->Error('Cannot open file: '.$file);
59+
$size = strlen($fc);
60+
$date = @date('YmdHisO', filemtime($file));
61+
$md = 'D:'.substr($date,0,-2)."'".substr($date,-2)."'";;
62+
63+
$this->_newobj();
64+
$info['n'] = $this->n;
65+
$this->_put('<<');
66+
$this->_put('/Type /Filespec');
67+
$this->_put('/F ('.$this->_escape($name).')');
68+
$this->_put('/UF '.$this->_textstring($name));
69+
$this->_put('/EF <</F '.($this->n+1).' 0 R>>');
70+
if($desc)
71+
$this->_put('/Desc '.$this->_textstring($desc));
72+
$this->_put('/AFRelationship /Unspecified');
73+
$this->_put('>>');
74+
$this->_put('endobj');
75+
76+
$this->_newobj();
77+
$this->_put('<<');
78+
$this->_put('/Type /EmbeddedFile');
79+
$this->_put('/Subtype /application#2Foctet-stream');
80+
$this->_put('/Length '.$size);
81+
$this->_put('/Params <</Size '.$size.' /ModDate '.$this->_textstring($md).'>>');
82+
$this->_put('>>');
83+
$this->_putstream($fc);
84+
$this->_put('endobj');
85+
}
86+
unset($info);
87+
88+
$this->_newobj();
89+
$this->n_files = $this->n;
90+
$a = array();
91+
foreach($this->files as $i=>$info)
92+
$a[] = $this->_textstring(sprintf('%03d',$i)).' '.$info['n'].' 0 R';
93+
$this->_put('<<');
94+
$this->_put('/Names ['.implode(' ',$a).']');
95+
$this->_put('>>');
96+
$this->_put('endobj');
97+
}
98+
99+
protected function _putresources()
100+
{
101+
parent::_putresources();
102+
$this->_putfiles();
103+
}
104+
105+
protected function _putfilescatalog()
106+
{
107+
if(empty($this->files)) return;
108+
109+
$this->_put('/Names <</EmbeddedFiles '.$this->n_files.' 0 R>>');
110+
$a = array();
111+
foreach($this->files as $info)
112+
$a[] = $info['n'].' 0 R';
113+
$this->_put('/AF ['.implode(' ',$a).']');
114+
if($this->open_attachment_pane)
115+
$this->_put('/PageMode /UseAttachments');
116+
}
117+
118+
protected function _putcatalog()
119+
{
120+
parent::_putcatalog();
121+
$this->_putfilescatalog();
122+
}
123+
}

scripts/Attachments/README.md

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# AttachmentsTrait
2+
![GitHub license](https://img.shields.io/badge/license-FPDF-green)
3+
[![Author](https://img.shields.io/badge/author-Olivier-blue)](mailto:[email protected]?subject=Bookmarks)
4+
5+
This script allows to attach files to the PDF.
6+
7+
## Usage
8+
The method to attach a file is:
9+
10+
```php
11+
/**
12+
* Add a attachment
13+
*
14+
* @param string $file path to the file to attach.
15+
* @param string $name the name under which the file will be attached. The default value is taken from file.
16+
* @param string $desc an optional description.
17+
* @return void
18+
*/
19+
AttachmentsTrait::Attach(string file [, string name [, string desc]]);
20+
```
21+
22+
The `OpenAttachmentPane()` method is also provided to force the PDF viewer to open the attachment pane when the document is loaded.
23+
24+
## Example
25+
26+
```php
27+
<?php
28+
declare(strict_types=1);
29+
30+
require dirname(dirname(__DIR__)) . '/fpdf/fpdf.php';
31+
require __DIR__ . '/AttachmentsTrait.php';
32+
33+
use FPDF\Scripts\Attachments\AttachmentsTrait;
34+
35+
$pdf = new class extends FPDF {
36+
use AttachmentsTrait;
37+
};
38+
39+
$pdf->Attach('attached.txt');
40+
$pdf->OpenAttachmentPane();
41+
$pdf->AddPage();
42+
$pdf->SetFont('Arial','',14);
43+
$pdf->Write(5,'This PDF contains an attached file.');
44+
45+
$pdf->Output('F', __DIR__ . '/example.pdf');
46+
```
47+
[Result](ex.pdf)

scripts/Attachments/attached.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Attached file.

scripts/Attachments/ex.pdf

1.81 KB
Binary file not shown.

scripts/Attachments/ex.php

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
require dirname(dirname(__DIR__)) . '/fpdf/fpdf.php';
5+
require __DIR__ . '/AttachmentsTrait.php';
6+
7+
use FPDF\Scripts\Attachments\AttachmentsTrait;
8+
9+
$pdf = new class extends FPDF {
10+
use AttachmentsTrait;
11+
};
12+
13+
$pdf->Attach('attached.txt');
14+
$pdf->OpenAttachmentPane();
15+
$pdf->AddPage();
16+
$pdf->SetFont('Arial','',14);
17+
$pdf->Write(5,'This PDF contains an attached file.');
18+
19+
$pdf->Output('F', __DIR__ . '/example.pdf');

scripts/Attachments/info.htm

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
2+
<html>
3+
<head>
4+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
5+
<title>Attachments</title>
6+
<style type="text/css">
7+
body {font-family:"Times New Roman",serif}
8+
h1 {font:bold 135% Arial,sans-serif; color:#4000A0; margin-bottom:0.9em}
9+
h2 {font:bold 95% Arial,sans-serif; color:#900000; margin-top:1.5em; margin-bottom:1em}
10+
</style>
11+
</head>
12+
<body>
13+
<h1>Attachments</h1>
14+
<h2>Informations</h2>
15+
Author: <a href="mailto:[email protected]?subject=Attachments">Olivier</a><br>
16+
License: FPDF
17+
<h2>Description</h2>
18+
This script allows to attach files to the PDF. The method to attach a file is:<br>
19+
<br>
20+
<tt>Attach(<b>string</b> file [, <b>string</b> name [, <b>string</b> desc]])</tt><br>
21+
<br>
22+
<tt><u>file</u></tt>: path to the file to attach.<br>
23+
<tt><u>name</u></tt>: the name under which the file will be attached. The default value is taken from <tt>file</tt>.<br>
24+
<tt><u>desc</u></tt>: an optional description.<br>
25+
<br>
26+
The <code>OpenAttachmentPane()</code> method is also provided to force the PDF viewer to open the attachment
27+
pane when the document is loaded.<br>
28+
<br>
29+
<strong>Note:</strong> this feature is supported by Adobe Reader but not by all alternative readers.
30+
</body>
31+
</html>

scripts/PDFBookmark/PDFBookmarkTrait.php

+10-5
Original file line numberDiff line numberDiff line change
@@ -87,17 +87,22 @@ protected function _putbookmarks()
8787

8888
protected function _putresources()
8989
{
90-
parent::_putresources();
91-
$this->_putbookmarks();
90+
parent::_putresources();
91+
$this->_putbookmarks();
9292
}
9393

94-
protected function _putcatalog()
95-
{
96-
parent::_putcatalog();
94+
protected function _putbookmarkscatalog()
95+
{
9796
if(count($this->outlines)>0)
9897
{
9998
$this->_put('/Outlines '.$this->outlineRoot.' 0 R');
10099
$this->_put('/PageMode /UseOutlines');
101100
}
102101
}
102+
103+
protected function _putcatalog()
104+
{
105+
parent::_putcatalog();
106+
$this->_putbookmarkscatalog();
107+
}
103108
}

src/FawnoFPDF.php

+17-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Fawno\FPDF\Traits\PDFMacroableTrait;
88
use Fawno\FPDF\PDFWrapper;
99
use Fawno\FPDF\Traits\FontsTrait;
10+
use FPDF\Scripts\Attachments\AttachmentsTrait;
1011
use FPDF\Scripts\PDFBookmark\PDFBookmarkTrait;
1112
use FPDF\Scripts\PDFCode128\PDFCode128Trait;
1213
use FPDF\Scripts\PDFDraw\PDFDrawTrait;
@@ -19,7 +20,14 @@
1920
class FawnoFPDF extends PDFWrapper {
2021
use FontsTrait;
2122
use PDFMacroableTrait;
22-
use PDFBookmarkTrait { PDFBookmarkTrait::_putresources as PDFBookmark_putresources; }
23+
use AttachmentsTrait {
24+
AttachmentsTrait::_putresources as Attachments_putresources;
25+
AttachmentsTrait::_putcatalog as Attachments_putcatalog;
26+
}
27+
use PDFBookmarkTrait {
28+
PDFBookmarkTrait::_putresources as PDFBookmark_putresources;
29+
PDFBookmarkTrait::_putcatalog as PDFBookmark_putcatalog;
30+
}
2331
use PDFProtectionTrait { PDFProtectionTrait::_putresources as PDFProtection_putresources; }
2432
use PDFRotateTrait;
2533
use CMYKTrait;
@@ -33,5 +41,13 @@ protected function _putresources () {
3341
parent::_putresources();
3442
$this->_putbookmarks();
3543
$this->_encrypresources();
44+
$this->_putfiles();
45+
}
46+
47+
protected function _putcatalog()
48+
{
49+
parent::_putcatalog();
50+
$this->_putbookmarkscatalog();
51+
$this->_putfilescatalog();
3652
}
3753
}
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Fawno\FPDF\Tests\Scripts;
5+
6+
use FPDF;
7+
use FPDF\Scripts\Attachments\AttachmentsTrait;
8+
use Fawno\FPDF\Tests\TestCase;
9+
10+
class AttachmentsTraitTest extends TestCase {
11+
public function testAttachmentsTrait () {
12+
$pdf = new class extends FPDF {
13+
use AttachmentsTrait;
14+
};
15+
16+
$pdf->Attach(__DIR__ . '/../../scripts/Attachments/attached.txt');
17+
$pdf->OpenAttachmentPane();
18+
$pdf->AddPage();
19+
$pdf->SetFont('Arial','',14);
20+
$pdf->Write(5,'This PDF contains an attached file.');
21+
22+
$this->assertFileCanBeCreated($pdf);
23+
24+
$this->assertPdfIsOk($pdf);
25+
}
26+
}

tests/TestCase.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public function assertPdfAreEquals (string $expected, string $actual) : void {
6868
$diff = [];
6969
foreach ($differences as $oid => $obj) {
7070
$keys = (is_a($obj->get_value(), PDFValue::class) ? $obj->get_keys() : false) ?: ['OID_' . $obj->get_oid()];
71-
$diff = array_merge($diff, array_diff($keys, ['Producer', 'CreationDate', 'Title', 'O', 'U']));
71+
$diff = array_merge($diff, array_diff($keys, ['Producer', 'CreationDate', 'ModDate', 'Title', 'O', 'U', 'UF', 'Params', 'Names']));
7272
}
7373

7474
$this->assertEquals([], $diff, 'The PDFs contents have differences.');

tests/examples/example.pdf

919 Bytes
Binary file not shown.
1.81 KB
Binary file not shown.

tests/test.php

+8
Original file line numberDiff line numberDiff line change
@@ -279,4 +279,12 @@
279279
$pdf->SetXY(50, 195);
280280
$pdf->Write(5, 'ABC sets combined: "' . $code . '"');
281281

282+
//Attachments
283+
$pdf->AddPage();
284+
$pdf->Bookmark('Attachments', false);
285+
$pdf->Attach(__DIR__ . '/../scripts/Attachments/attached.txt');
286+
$pdf->OpenAttachmentPane();
287+
$pdf->SetFont('Arial','',14);
288+
$pdf->Write(5,'This PDF contains an attached file.');
289+
282290
$pdf->Output('F', __DIR__ . '/example.pdf');

0 commit comments

Comments
 (0)