1<?php
2
3namespace setasign\Fpdi\Tcpdf;
4
5use setasign\Fpdi\FpdiTrait;
6use setasign\Fpdi\PdfParser\CrossReference\CrossReferenceException;
7use setasign\Fpdi\PdfParser\Filter\AsciiHex;
8use setasign\Fpdi\PdfParser\PdfParserException;
9use setasign\Fpdi\PdfParser\Type\PdfHexString;
10use setasign\Fpdi\PdfParser\Type\PdfIndirectObject;
11use setasign\Fpdi\PdfParser\Type\PdfNull;
12use setasign\Fpdi\PdfParser\Type\PdfNumeric;
13use setasign\Fpdi\PdfParser\Type\PdfStream;
14use setasign\Fpdi\PdfParser\Type\PdfString;
15use setasign\Fpdi\PdfParser\Type\PdfType;
16use setasign\Fpdi\PdfParser\Type\PdfTypeException;
17
18/**
19 * Class Fpdi
20 *
21 * This class let you import pages of existing PDF documents into a reusable structure for TCPDF.
22 *
23 * @package setasign\Fpdi
24 */
25class Fpdi extends \TCPDF
26{
27    use FpdiTrait {
28        writePdfType as fpdiWritePdfType;
29        useImportedPage as fpdiUseImportedPage;
30    }
31
32    /**
33     * FPDI version
34     *
35     * @string
36     */
37    const VERSION = '2.3.1';
38
39    /**
40     * A counter for template ids.
41     *
42     * @var int
43     */
44    protected $templateId = 0;
45
46    /**
47     * The currently used object number.
48     *
49     * @var int
50     */
51    protected $currentObjectNumber;
52
53    protected function _enddoc()
54    {
55        parent::_enddoc();
56        $this->cleanUp();
57    }
58
59    /**
60     * Get the next template id.
61     *
62     * @return int
63     */
64    protected function getNextTemplateId()
65    {
66        return $this->templateId++;
67    }
68
69    /**
70     * Draws an imported page onto the page or another template.
71     *
72     * Give only one of the size parameters (width, height) to calculate the other one automatically in view to the
73     * aspect ratio.
74     *
75     * @param mixed $tpl The template id
76     * @param float|int|array $x The abscissa of upper-left corner. Alternatively you could use an assoc array
77     *                           with the keys "x", "y", "width", "height", "adjustPageSize".
78     * @param float|int $y The ordinate of upper-left corner.
79     * @param float|int|null $width The width.
80     * @param float|int|null $height The height.
81     * @param bool $adjustPageSize
82     * @return array The size
83     * @see FpdiTrait::getTemplateSize()
84     */
85    public function useTemplate($tpl, $x = 0, $y = 0, $width = null, $height = null, $adjustPageSize = false)
86    {
87        return $this->useImportedPage($tpl, $x, $y, $width, $height, $adjustPageSize);
88    }
89
90    /**
91     * Draws an imported page onto the page.
92     *
93     * Give only one of the size parameters (width, height) to calculate the other one automatically in view to the
94     * aspect ratio.
95     *
96     * @param mixed $pageId The page id
97     * @param float|int|array $x The abscissa of upper-left corner. Alternatively you could use an assoc array
98     *                           with the keys "x", "y", "width", "height", "adjustPageSize".
99     * @param float|int $y The ordinate of upper-left corner.
100     * @param float|int|null $width The width.
101     * @param float|int|null $height The height.
102     * @param bool $adjustPageSize
103     * @return array The size.
104     * @see Fpdi::getTemplateSize()
105     */
106    public function useImportedPage($pageId, $x = 0, $y = 0, $width = null, $height = null, $adjustPageSize = false)
107    {
108        $size = $this->fpdiUseImportedPage($pageId, $x, $y, $width, $height, $adjustPageSize);
109        if ($this->inxobj) {
110            $importedPage = $this->importedPages[$pageId];
111            $this->xobjects[$this->xobjid]['importedPages'][$importedPage['id']] = $pageId;
112        }
113
114        return $size;
115    }
116
117    /**
118     * Get the size of an imported page.
119     *
120     * Give only one of the size parameters (width, height) to calculate the other one automatically in view to the
121     * aspect ratio.
122     *
123     * @param mixed $tpl The template id
124     * @param float|int|null $width The width.
125     * @param float|int|null $height The height.
126     * @return array|bool An array with following keys: width, height, 0 (=width), 1 (=height), orientation (L or P)
127     */
128    public function getTemplateSize($tpl, $width = null, $height = null)
129    {
130        return $this->getImportedPageSize($tpl, $width, $height);
131    }
132
133    /**
134     * @inheritdoc
135     */
136    protected function _getxobjectdict()
137    {
138        $out = parent::_getxobjectdict();
139
140        foreach ($this->importedPages as $key => $pageData) {
141            $out .= '/' . $pageData['id'] . ' ' . $pageData['objectNumber'] . ' 0 R ';
142        }
143
144        return $out;
145    }
146
147    /**
148     * @inheritdoc
149     * @throws CrossReferenceException
150     * @throws PdfParserException
151     */
152    protected function _putxobjects()
153    {
154        foreach ($this->importedPages as $key => $pageData) {
155            $this->currentObjectNumber = $this->_newobj();
156            $this->importedPages[$key]['objectNumber'] = $this->currentObjectNumber;
157            $this->currentReaderId = $pageData['readerId'];
158            $this->writePdfType($pageData['stream']);
159            $this->_put('endobj');
160        }
161
162        foreach (\array_keys($this->readers) as $readerId) {
163            $parser = $this->getPdfReader($readerId)->getParser();
164            $this->currentReaderId = $readerId;
165
166            while (($objectNumber = \array_pop($this->objectsToCopy[$readerId])) !== null) {
167                try {
168                    $object = $parser->getIndirectObject($objectNumber);
169
170                } catch (CrossReferenceException $e) {
171                    if ($e->getCode() === CrossReferenceException::OBJECT_NOT_FOUND) {
172                        $object = PdfIndirectObject::create($objectNumber, 0, new PdfNull());
173                    } else {
174                        throw $e;
175                    }
176                }
177
178                $this->writePdfType($object);
179            }
180        }
181
182        // let's prepare resources for imported pages in templates
183        foreach ($this->xobjects as $xObjectId => $data) {
184            if (!isset($data['importedPages'])) {
185                continue;
186            }
187
188            foreach ($data['importedPages'] as $id => $pageKey) {
189                $page = $this->importedPages[$pageKey];
190                $this->xobjects[$xObjectId]['xobjects'][$id] = ['n' => $page['objectNumber']];
191            }
192        }
193
194
195        parent::_putxobjects();
196        $this->currentObjectNumber = null;
197    }
198
199    /**
200     * Append content to the buffer of TCPDF.
201     *
202     * @param string $s
203     * @param bool $newLine
204     */
205    protected function _put($s, $newLine = true)
206    {
207        if ($newLine) {
208            $this->setBuffer($s . "\n");
209        } else {
210            $this->setBuffer($s);
211        }
212    }
213
214    /**
215     * Begin a new object and return the object number.
216     *
217     * @param int|string $objid Object ID (leave empty to get a new ID).
218     * @return int object number
219     */
220    protected function _newobj($objid = '')
221    {
222        $this->_out($this->_getobj($objid));
223        return $this->n;
224    }
225
226    /**
227     * Writes a PdfType object to the resulting buffer.
228     *
229     * @param PdfType $value
230     * @throws PdfTypeException
231     */
232    protected function writePdfType(PdfType $value)
233    {
234        if (!$this->encrypted) {
235            $this->fpdiWritePdfType($value);
236            return;
237        }
238
239        if ($value instanceof PdfString) {
240            $string = PdfString::unescape($value->value);
241            $string = $this->_encrypt_data($this->currentObjectNumber, $string);
242            $value->value = \TCPDF_STATIC::_escape($string);
243
244        } elseif ($value instanceof PdfHexString) {
245            $filter = new AsciiHex();
246            $string = $filter->decode($value->value);
247            $string = $this->_encrypt_data($this->currentObjectNumber, $string);
248            $value->value = $filter->encode($string, true);
249
250        } elseif ($value instanceof PdfStream) {
251            $stream = $value->getStream();
252            $stream = $this->_encrypt_data($this->currentObjectNumber, $stream);
253            $dictionary = $value->value;
254            $dictionary->value['Length'] = PdfNumeric::create(\strlen($stream));
255            $value = PdfStream::create($dictionary, $stream);
256
257        } elseif ($value instanceof PdfIndirectObject) {
258            /**
259             * @var $value PdfIndirectObject
260             */
261            $this->currentObjectNumber = $this->objectMap[$this->currentReaderId][$value->objectNumber];
262        }
263
264        $this->fpdiWritePdfType($value);
265    }
266}