xref: /plugin/dw2pdf/vendor/setasign/fpdi/src/FpdiTrait.php (revision dc4d9dc689082c963d5c1d9ee679553326788c6e)
1*dc4d9dc6SAnna Dabrowska<?php
2*dc4d9dc6SAnna Dabrowska/**
3*dc4d9dc6SAnna Dabrowska * This file is part of FPDI
4*dc4d9dc6SAnna Dabrowska *
5*dc4d9dc6SAnna Dabrowska * @package   setasign\Fpdi
6*dc4d9dc6SAnna Dabrowska * @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
7*dc4d9dc6SAnna Dabrowska * @license   http://opensource.org/licenses/mit-license The MIT License
8*dc4d9dc6SAnna Dabrowska */
9*dc4d9dc6SAnna Dabrowska
10*dc4d9dc6SAnna Dabrowskanamespace setasign\Fpdi;
11*dc4d9dc6SAnna Dabrowska
12*dc4d9dc6SAnna Dabrowskause setasign\Fpdi\PdfParser\CrossReference\CrossReferenceException;
13*dc4d9dc6SAnna Dabrowskause setasign\Fpdi\PdfParser\Filter\FilterException;
14*dc4d9dc6SAnna Dabrowskause setasign\Fpdi\PdfParser\PdfParser;
15*dc4d9dc6SAnna Dabrowskause setasign\Fpdi\PdfParser\PdfParserException;
16*dc4d9dc6SAnna Dabrowskause setasign\Fpdi\PdfParser\StreamReader;
17*dc4d9dc6SAnna Dabrowskause setasign\Fpdi\PdfParser\Type\PdfArray;
18*dc4d9dc6SAnna Dabrowskause setasign\Fpdi\PdfParser\Type\PdfBoolean;
19*dc4d9dc6SAnna Dabrowskause setasign\Fpdi\PdfParser\Type\PdfDictionary;
20*dc4d9dc6SAnna Dabrowskause setasign\Fpdi\PdfParser\Type\PdfHexString;
21*dc4d9dc6SAnna Dabrowskause setasign\Fpdi\PdfParser\Type\PdfIndirectObject;
22*dc4d9dc6SAnna Dabrowskause setasign\Fpdi\PdfParser\Type\PdfIndirectObjectReference;
23*dc4d9dc6SAnna Dabrowskause setasign\Fpdi\PdfParser\Type\PdfName;
24*dc4d9dc6SAnna Dabrowskause setasign\Fpdi\PdfParser\Type\PdfNull;
25*dc4d9dc6SAnna Dabrowskause setasign\Fpdi\PdfParser\Type\PdfNumeric;
26*dc4d9dc6SAnna Dabrowskause setasign\Fpdi\PdfParser\Type\PdfStream;
27*dc4d9dc6SAnna Dabrowskause setasign\Fpdi\PdfParser\Type\PdfString;
28*dc4d9dc6SAnna Dabrowskause setasign\Fpdi\PdfParser\Type\PdfToken;
29*dc4d9dc6SAnna Dabrowskause setasign\Fpdi\PdfParser\Type\PdfType;
30*dc4d9dc6SAnna Dabrowskause setasign\Fpdi\PdfParser\Type\PdfTypeException;
31*dc4d9dc6SAnna Dabrowskause setasign\Fpdi\PdfReader\PageBoundaries;
32*dc4d9dc6SAnna Dabrowskause setasign\Fpdi\PdfReader\PdfReader;
33*dc4d9dc6SAnna Dabrowskause setasign\Fpdi\PdfReader\PdfReaderException;
34*dc4d9dc6SAnna Dabrowskause /* This namespace/class is used by the commercial FPDI PDF-Parser add-on. */
35*dc4d9dc6SAnna Dabrowska    /** @noinspection PhpUndefinedClassInspection */
36*dc4d9dc6SAnna Dabrowska    /** @noinspection PhpUndefinedNamespaceInspection */
37*dc4d9dc6SAnna Dabrowska    setasign\FpdiPdfParser\PdfParser\PdfParser as FpdiPdfParser;
38*dc4d9dc6SAnna Dabrowska
39*dc4d9dc6SAnna Dabrowska/**
40*dc4d9dc6SAnna Dabrowska * The FpdiTrait
41*dc4d9dc6SAnna Dabrowska *
42*dc4d9dc6SAnna Dabrowska * This trait offers the core functionalities of FPDI. By passing them to a trait we can reuse it with e.g. TCPDF in a
43*dc4d9dc6SAnna Dabrowska * very easy way.
44*dc4d9dc6SAnna Dabrowska *
45*dc4d9dc6SAnna Dabrowska * @package setasign\Fpdi
46*dc4d9dc6SAnna Dabrowska */
47*dc4d9dc6SAnna Dabrowskatrait FpdiTrait
48*dc4d9dc6SAnna Dabrowska{
49*dc4d9dc6SAnna Dabrowska    /**
50*dc4d9dc6SAnna Dabrowska     * The pdf reader instances.
51*dc4d9dc6SAnna Dabrowska     *
52*dc4d9dc6SAnna Dabrowska     * @var PdfReader[]
53*dc4d9dc6SAnna Dabrowska     */
54*dc4d9dc6SAnna Dabrowska    protected $readers = [];
55*dc4d9dc6SAnna Dabrowska
56*dc4d9dc6SAnna Dabrowska    /**
57*dc4d9dc6SAnna Dabrowska     * Instances created internally.
58*dc4d9dc6SAnna Dabrowska     *
59*dc4d9dc6SAnna Dabrowska     * @var array
60*dc4d9dc6SAnna Dabrowska     */
61*dc4d9dc6SAnna Dabrowska    protected $createdReaders = [];
62*dc4d9dc6SAnna Dabrowska
63*dc4d9dc6SAnna Dabrowska    /**
64*dc4d9dc6SAnna Dabrowska     * The current reader id.
65*dc4d9dc6SAnna Dabrowska     *
66*dc4d9dc6SAnna Dabrowska     * @var string
67*dc4d9dc6SAnna Dabrowska     */
68*dc4d9dc6SAnna Dabrowska    protected $currentReaderId;
69*dc4d9dc6SAnna Dabrowska
70*dc4d9dc6SAnna Dabrowska    /**
71*dc4d9dc6SAnna Dabrowska     * Data of all imported pages.
72*dc4d9dc6SAnna Dabrowska     *
73*dc4d9dc6SAnna Dabrowska     * @var array
74*dc4d9dc6SAnna Dabrowska     */
75*dc4d9dc6SAnna Dabrowska    protected $importedPages = [];
76*dc4d9dc6SAnna Dabrowska
77*dc4d9dc6SAnna Dabrowska    /**
78*dc4d9dc6SAnna Dabrowska     * A map from object numbers of imported objects to new assigned object numbers by FPDF.
79*dc4d9dc6SAnna Dabrowska     *
80*dc4d9dc6SAnna Dabrowska     * @var array
81*dc4d9dc6SAnna Dabrowska     */
82*dc4d9dc6SAnna Dabrowska    protected $objectMap = [];
83*dc4d9dc6SAnna Dabrowska
84*dc4d9dc6SAnna Dabrowska    /**
85*dc4d9dc6SAnna Dabrowska     * An array with information about objects, which needs to be copied to the resulting document.
86*dc4d9dc6SAnna Dabrowska     *
87*dc4d9dc6SAnna Dabrowska     * @var array
88*dc4d9dc6SAnna Dabrowska     */
89*dc4d9dc6SAnna Dabrowska    protected $objectsToCopy = [];
90*dc4d9dc6SAnna Dabrowska
91*dc4d9dc6SAnna Dabrowska    /**
92*dc4d9dc6SAnna Dabrowska     * Release resources and file handles.
93*dc4d9dc6SAnna Dabrowska     *
94*dc4d9dc6SAnna Dabrowska     * This method is called internally when the document is created successfully. By default it only cleans up
95*dc4d9dc6SAnna Dabrowska     * stream reader instances which were created internally.
96*dc4d9dc6SAnna Dabrowska     *
97*dc4d9dc6SAnna Dabrowska     * @param bool $allReaders
98*dc4d9dc6SAnna Dabrowska     */
99*dc4d9dc6SAnna Dabrowska    public function cleanUp($allReaders = false)
100*dc4d9dc6SAnna Dabrowska    {
101*dc4d9dc6SAnna Dabrowska        $readers = $allReaders ? array_keys($this->readers) : $this->createdReaders;
102*dc4d9dc6SAnna Dabrowska        foreach ($readers as $id) {
103*dc4d9dc6SAnna Dabrowska            $this->readers[$id]->getParser()->getStreamReader()->cleanUp();
104*dc4d9dc6SAnna Dabrowska            unset($this->readers[$id]);
105*dc4d9dc6SAnna Dabrowska        }
106*dc4d9dc6SAnna Dabrowska
107*dc4d9dc6SAnna Dabrowska        $this->createdReaders= [];
108*dc4d9dc6SAnna Dabrowska    }
109*dc4d9dc6SAnna Dabrowska
110*dc4d9dc6SAnna Dabrowska    /**
111*dc4d9dc6SAnna Dabrowska     * Set the minimal PDF version.
112*dc4d9dc6SAnna Dabrowska     *
113*dc4d9dc6SAnna Dabrowska     * @param string $pdfVersion
114*dc4d9dc6SAnna Dabrowska     */
115*dc4d9dc6SAnna Dabrowska    protected function setMinPdfVersion($pdfVersion)
116*dc4d9dc6SAnna Dabrowska    {
117*dc4d9dc6SAnna Dabrowska        if (\version_compare($pdfVersion, $this->PDFVersion, '>')) {
118*dc4d9dc6SAnna Dabrowska            $this->PDFVersion = $pdfVersion;
119*dc4d9dc6SAnna Dabrowska        }
120*dc4d9dc6SAnna Dabrowska    }
121*dc4d9dc6SAnna Dabrowska
122*dc4d9dc6SAnna Dabrowska    /** @noinspection PhpUndefinedClassInspection */
123*dc4d9dc6SAnna Dabrowska    /**
124*dc4d9dc6SAnna Dabrowska     * Get a new pdf parser instance.
125*dc4d9dc6SAnna Dabrowska     *
126*dc4d9dc6SAnna Dabrowska     * @param StreamReader $streamReader
127*dc4d9dc6SAnna Dabrowska     * @return PdfParser|FpdiPdfParser
128*dc4d9dc6SAnna Dabrowska     */
129*dc4d9dc6SAnna Dabrowska    protected function getPdfParserInstance(StreamReader $streamReader)
130*dc4d9dc6SAnna Dabrowska    {
131*dc4d9dc6SAnna Dabrowska        /** @noinspection PhpUndefinedClassInspection */
132*dc4d9dc6SAnna Dabrowska        if (\class_exists(FpdiPdfParser::class)) {
133*dc4d9dc6SAnna Dabrowska            /** @noinspection PhpUndefinedClassInspection */
134*dc4d9dc6SAnna Dabrowska            return new FpdiPdfParser($streamReader);
135*dc4d9dc6SAnna Dabrowska        }
136*dc4d9dc6SAnna Dabrowska
137*dc4d9dc6SAnna Dabrowska        return new PdfParser($streamReader);
138*dc4d9dc6SAnna Dabrowska    }
139*dc4d9dc6SAnna Dabrowska
140*dc4d9dc6SAnna Dabrowska    /**
141*dc4d9dc6SAnna Dabrowska     * Get an unique reader id by the $file parameter.
142*dc4d9dc6SAnna Dabrowska     *
143*dc4d9dc6SAnna Dabrowska     * @param string|resource|PdfReader|StreamReader $file An open file descriptor, a path to a file, a PdfReader
144*dc4d9dc6SAnna Dabrowska     *                                                     instance or a StreamReader instance.
145*dc4d9dc6SAnna Dabrowska     * @return string
146*dc4d9dc6SAnna Dabrowska     */
147*dc4d9dc6SAnna Dabrowska    protected function getPdfReaderId($file)
148*dc4d9dc6SAnna Dabrowska    {
149*dc4d9dc6SAnna Dabrowska        if (\is_resource($file)) {
150*dc4d9dc6SAnna Dabrowska            $id = (string) $file;
151*dc4d9dc6SAnna Dabrowska        } elseif (\is_string($file)) {
152*dc4d9dc6SAnna Dabrowska            $id = \realpath($file);
153*dc4d9dc6SAnna Dabrowska            if ($id === false) {
154*dc4d9dc6SAnna Dabrowska                $id = $file;
155*dc4d9dc6SAnna Dabrowska            }
156*dc4d9dc6SAnna Dabrowska        } elseif (\is_object($file)) {
157*dc4d9dc6SAnna Dabrowska            $id = \spl_object_hash($file);
158*dc4d9dc6SAnna Dabrowska        } else {
159*dc4d9dc6SAnna Dabrowska            throw new \InvalidArgumentException(
160*dc4d9dc6SAnna Dabrowska                \sprintf('Invalid type in $file parameter (%s)', \gettype($file))
161*dc4d9dc6SAnna Dabrowska            );
162*dc4d9dc6SAnna Dabrowska        }
163*dc4d9dc6SAnna Dabrowska
164*dc4d9dc6SAnna Dabrowska        /** @noinspection OffsetOperationsInspection */
165*dc4d9dc6SAnna Dabrowska        if (isset($this->readers[$id])) {
166*dc4d9dc6SAnna Dabrowska            return $id;
167*dc4d9dc6SAnna Dabrowska        }
168*dc4d9dc6SAnna Dabrowska
169*dc4d9dc6SAnna Dabrowska        if (\is_resource($file)) {
170*dc4d9dc6SAnna Dabrowska            $streamReader = new StreamReader($file);
171*dc4d9dc6SAnna Dabrowska        } elseif (\is_string($file)) {
172*dc4d9dc6SAnna Dabrowska            $streamReader = StreamReader::createByFile($file);
173*dc4d9dc6SAnna Dabrowska            $this->createdReaders[] = $id;
174*dc4d9dc6SAnna Dabrowska        } else {
175*dc4d9dc6SAnna Dabrowska            $streamReader = $file;
176*dc4d9dc6SAnna Dabrowska        }
177*dc4d9dc6SAnna Dabrowska
178*dc4d9dc6SAnna Dabrowska        $reader = new PdfReader($this->getPdfParserInstance($streamReader));
179*dc4d9dc6SAnna Dabrowska        /** @noinspection OffsetOperationsInspection */
180*dc4d9dc6SAnna Dabrowska        $this->readers[$id] = $reader;
181*dc4d9dc6SAnna Dabrowska
182*dc4d9dc6SAnna Dabrowska        return $id;
183*dc4d9dc6SAnna Dabrowska    }
184*dc4d9dc6SAnna Dabrowska
185*dc4d9dc6SAnna Dabrowska    /**
186*dc4d9dc6SAnna Dabrowska     * Get a pdf reader instance by its id.
187*dc4d9dc6SAnna Dabrowska     *
188*dc4d9dc6SAnna Dabrowska     * @param string $id
189*dc4d9dc6SAnna Dabrowska     * @return PdfReader
190*dc4d9dc6SAnna Dabrowska     */
191*dc4d9dc6SAnna Dabrowska    protected function getPdfReader($id)
192*dc4d9dc6SAnna Dabrowska    {
193*dc4d9dc6SAnna Dabrowska        if (isset($this->readers[$id])) {
194*dc4d9dc6SAnna Dabrowska            return $this->readers[$id];
195*dc4d9dc6SAnna Dabrowska        }
196*dc4d9dc6SAnna Dabrowska
197*dc4d9dc6SAnna Dabrowska        throw new \InvalidArgumentException(
198*dc4d9dc6SAnna Dabrowska            \sprintf('No pdf reader with the given id (%s) exists.', $id)
199*dc4d9dc6SAnna Dabrowska        );
200*dc4d9dc6SAnna Dabrowska    }
201*dc4d9dc6SAnna Dabrowska
202*dc4d9dc6SAnna Dabrowska    /**
203*dc4d9dc6SAnna Dabrowska     * Set the source PDF file.
204*dc4d9dc6SAnna Dabrowska     *
205*dc4d9dc6SAnna Dabrowska     * @param string|resource|StreamReader $file Path to the file or a stream resource or a StreamReader instance.
206*dc4d9dc6SAnna Dabrowska     * @return int The page count of the PDF document.
207*dc4d9dc6SAnna Dabrowska     * @throws PdfParserException
208*dc4d9dc6SAnna Dabrowska     */
209*dc4d9dc6SAnna Dabrowska    public function setSourceFile($file)
210*dc4d9dc6SAnna Dabrowska    {
211*dc4d9dc6SAnna Dabrowska        $this->currentReaderId = $this->getPdfReaderId($file);
212*dc4d9dc6SAnna Dabrowska        $this->objectsToCopy[$this->currentReaderId] = [];
213*dc4d9dc6SAnna Dabrowska
214*dc4d9dc6SAnna Dabrowska        $reader = $this->getPdfReader($this->currentReaderId);
215*dc4d9dc6SAnna Dabrowska        $this->setMinPdfVersion($reader->getPdfVersion());
216*dc4d9dc6SAnna Dabrowska
217*dc4d9dc6SAnna Dabrowska        return $reader->getPageCount();
218*dc4d9dc6SAnna Dabrowska    }
219*dc4d9dc6SAnna Dabrowska
220*dc4d9dc6SAnna Dabrowska    /**
221*dc4d9dc6SAnna Dabrowska     * Imports a page.
222*dc4d9dc6SAnna Dabrowska     *
223*dc4d9dc6SAnna Dabrowska     * @param int $pageNumber The page number.
224*dc4d9dc6SAnna Dabrowska     * @param string $box The page boundary to import. Default set to PageBoundaries::CROP_BOX.
225*dc4d9dc6SAnna Dabrowska     * @param bool $groupXObject Define the form XObject as a group XObject to support transparency (if used).
226*dc4d9dc6SAnna Dabrowska     * @return string A unique string identifying the imported page.
227*dc4d9dc6SAnna Dabrowska     * @throws CrossReferenceException
228*dc4d9dc6SAnna Dabrowska     * @throws FilterException
229*dc4d9dc6SAnna Dabrowska     * @throws PdfParserException
230*dc4d9dc6SAnna Dabrowska     * @throws PdfTypeException
231*dc4d9dc6SAnna Dabrowska     * @throws PdfReaderException
232*dc4d9dc6SAnna Dabrowska     * @see PageBoundaries
233*dc4d9dc6SAnna Dabrowska     */
234*dc4d9dc6SAnna Dabrowska    public function importPage($pageNumber, $box = PageBoundaries::CROP_BOX, $groupXObject = true)
235*dc4d9dc6SAnna Dabrowska    {
236*dc4d9dc6SAnna Dabrowska        if (null === $this->currentReaderId) {
237*dc4d9dc6SAnna Dabrowska            throw new \BadMethodCallException('No reader initiated. Call setSourceFile() first.');
238*dc4d9dc6SAnna Dabrowska        }
239*dc4d9dc6SAnna Dabrowska
240*dc4d9dc6SAnna Dabrowska        $pageId = $this->currentReaderId;
241*dc4d9dc6SAnna Dabrowska
242*dc4d9dc6SAnna Dabrowska        $pageNumber = (int)$pageNumber;
243*dc4d9dc6SAnna Dabrowska        $pageId .= '|' . $pageNumber . '|' . ($groupXObject ? '1' : '0');
244*dc4d9dc6SAnna Dabrowska
245*dc4d9dc6SAnna Dabrowska        // for backwards compatibility with FPDI 1
246*dc4d9dc6SAnna Dabrowska        $box = \ltrim($box, '/');
247*dc4d9dc6SAnna Dabrowska        if (!PageBoundaries::isValidName($box)) {
248*dc4d9dc6SAnna Dabrowska            throw new \InvalidArgumentException(
249*dc4d9dc6SAnna Dabrowska                \sprintf('Box name is invalid: "%s"', $box)
250*dc4d9dc6SAnna Dabrowska            );
251*dc4d9dc6SAnna Dabrowska        }
252*dc4d9dc6SAnna Dabrowska
253*dc4d9dc6SAnna Dabrowska        $pageId .= '|' . $box;
254*dc4d9dc6SAnna Dabrowska
255*dc4d9dc6SAnna Dabrowska        if (isset($this->importedPages[$pageId])) {
256*dc4d9dc6SAnna Dabrowska            return $pageId;
257*dc4d9dc6SAnna Dabrowska        }
258*dc4d9dc6SAnna Dabrowska
259*dc4d9dc6SAnna Dabrowska        $reader = $this->getPdfReader($this->currentReaderId);
260*dc4d9dc6SAnna Dabrowska        $page = $reader->getPage($pageNumber);
261*dc4d9dc6SAnna Dabrowska
262*dc4d9dc6SAnna Dabrowska        $bbox = $page->getBoundary($box);
263*dc4d9dc6SAnna Dabrowska        if ($bbox === false) {
264*dc4d9dc6SAnna Dabrowska            throw new PdfReaderException(
265*dc4d9dc6SAnna Dabrowska                \sprintf("Page doesn't have a boundary box (%s).", $box),
266*dc4d9dc6SAnna Dabrowska                PdfReaderException::MISSING_DATA
267*dc4d9dc6SAnna Dabrowska            );
268*dc4d9dc6SAnna Dabrowska        }
269*dc4d9dc6SAnna Dabrowska
270*dc4d9dc6SAnna Dabrowska        $dict = new PdfDictionary();
271*dc4d9dc6SAnna Dabrowska        $dict->value['Type'] = PdfName::create('XObject');
272*dc4d9dc6SAnna Dabrowska        $dict->value['Subtype'] = PdfName::create('Form');
273*dc4d9dc6SAnna Dabrowska        $dict->value['FormType'] = PdfNumeric::create(1);
274*dc4d9dc6SAnna Dabrowska        $dict->value['BBox'] = $bbox->toPdfArray();
275*dc4d9dc6SAnna Dabrowska
276*dc4d9dc6SAnna Dabrowska        if ($groupXObject) {
277*dc4d9dc6SAnna Dabrowska            $this->setMinPdfVersion('1.4');
278*dc4d9dc6SAnna Dabrowska            $dict->value['Group'] = PdfDictionary::create([
279*dc4d9dc6SAnna Dabrowska                'Type' => PdfName::create('Group'),
280*dc4d9dc6SAnna Dabrowska                'S' => PdfName::create('Transparency')
281*dc4d9dc6SAnna Dabrowska            ]);
282*dc4d9dc6SAnna Dabrowska        }
283*dc4d9dc6SAnna Dabrowska
284*dc4d9dc6SAnna Dabrowska        $resources = $page->getAttribute('Resources');
285*dc4d9dc6SAnna Dabrowska        if ($resources !== null) {
286*dc4d9dc6SAnna Dabrowska            $dict->value['Resources'] = $resources;
287*dc4d9dc6SAnna Dabrowska        }
288*dc4d9dc6SAnna Dabrowska
289*dc4d9dc6SAnna Dabrowska        list($width, $height) = $page->getWidthAndHeight($box);
290*dc4d9dc6SAnna Dabrowska
291*dc4d9dc6SAnna Dabrowska        $a = 1;
292*dc4d9dc6SAnna Dabrowska        $b = 0;
293*dc4d9dc6SAnna Dabrowska        $c = 0;
294*dc4d9dc6SAnna Dabrowska        $d = 1;
295*dc4d9dc6SAnna Dabrowska        $e = -$bbox->getLlx();
296*dc4d9dc6SAnna Dabrowska        $f = -$bbox->getLly();
297*dc4d9dc6SAnna Dabrowska
298*dc4d9dc6SAnna Dabrowska        $rotation = $page->getRotation();
299*dc4d9dc6SAnna Dabrowska
300*dc4d9dc6SAnna Dabrowska        if ($rotation !== 0) {
301*dc4d9dc6SAnna Dabrowska            $rotation *= -1;
302*dc4d9dc6SAnna Dabrowska            $angle = $rotation * M_PI/180;
303*dc4d9dc6SAnna Dabrowska            $a = \cos($angle);
304*dc4d9dc6SAnna Dabrowska            $b = \sin($angle);
305*dc4d9dc6SAnna Dabrowska            $c = -$b;
306*dc4d9dc6SAnna Dabrowska            $d = $a;
307*dc4d9dc6SAnna Dabrowska
308*dc4d9dc6SAnna Dabrowska            switch ($rotation) {
309*dc4d9dc6SAnna Dabrowska                case -90:
310*dc4d9dc6SAnna Dabrowska                    $e = -$bbox->getLly();
311*dc4d9dc6SAnna Dabrowska                    $f = $bbox->getUrx();
312*dc4d9dc6SAnna Dabrowska                    break;
313*dc4d9dc6SAnna Dabrowska                case -180:
314*dc4d9dc6SAnna Dabrowska                    $e = $bbox->getUrx();
315*dc4d9dc6SAnna Dabrowska                    $f = $bbox->getUry();
316*dc4d9dc6SAnna Dabrowska                    break;
317*dc4d9dc6SAnna Dabrowska                case -270:
318*dc4d9dc6SAnna Dabrowska                    $e = $bbox->getUry();
319*dc4d9dc6SAnna Dabrowska                    $f = -$bbox->getLlx();
320*dc4d9dc6SAnna Dabrowska                    break;
321*dc4d9dc6SAnna Dabrowska            }
322*dc4d9dc6SAnna Dabrowska        }
323*dc4d9dc6SAnna Dabrowska
324*dc4d9dc6SAnna Dabrowska        // we need to rotate/translate
325*dc4d9dc6SAnna Dabrowska        if ($a != 1 || $b != 0 || $c != 0 || $d != 1 || $e != 0 || $f != 0) {
326*dc4d9dc6SAnna Dabrowska            $dict->value['Matrix'] = PdfArray::create([
327*dc4d9dc6SAnna Dabrowska                PdfNumeric::create($a), PdfNumeric::create($b), PdfNumeric::create($c),
328*dc4d9dc6SAnna Dabrowska                PdfNumeric::create($d), PdfNumeric::create($e), PdfNumeric::create($f)
329*dc4d9dc6SAnna Dabrowska            ]);
330*dc4d9dc6SAnna Dabrowska        }
331*dc4d9dc6SAnna Dabrowska
332*dc4d9dc6SAnna Dabrowska        // try to use the existing content stream
333*dc4d9dc6SAnna Dabrowska        $pageDict = $page->getPageDictionary();
334*dc4d9dc6SAnna Dabrowska
335*dc4d9dc6SAnna Dabrowska        $contentsObject = PdfType::resolve(PdfDictionary::get($pageDict, 'Contents'), $reader->getParser(), true);
336*dc4d9dc6SAnna Dabrowska        $contents =  PdfType::resolve($contentsObject, $reader->getParser());
337*dc4d9dc6SAnna Dabrowska
338*dc4d9dc6SAnna Dabrowska        // just copy the stream reference if it is only a single stream
339*dc4d9dc6SAnna Dabrowska        if (($contentsIsStream = ($contents instanceof PdfStream))
340*dc4d9dc6SAnna Dabrowska            || ($contents instanceof PdfArray && \count($contents->value) === 1)
341*dc4d9dc6SAnna Dabrowska        ) {
342*dc4d9dc6SAnna Dabrowska            if ($contentsIsStream) {
343*dc4d9dc6SAnna Dabrowska                /**
344*dc4d9dc6SAnna Dabrowska                 * @var PdfIndirectObject $contentsObject
345*dc4d9dc6SAnna Dabrowska                 */
346*dc4d9dc6SAnna Dabrowska                $stream = $contents;
347*dc4d9dc6SAnna Dabrowska            } else {
348*dc4d9dc6SAnna Dabrowska                $stream = PdfType::resolve($contents->value[0], $reader->getParser());
349*dc4d9dc6SAnna Dabrowska            }
350*dc4d9dc6SAnna Dabrowska
351*dc4d9dc6SAnna Dabrowska            $filter = PdfDictionary::get($stream->value, 'Filter');
352*dc4d9dc6SAnna Dabrowska            if (!$filter instanceof PdfNull) {
353*dc4d9dc6SAnna Dabrowska                $dict->value['Filter'] = $filter;
354*dc4d9dc6SAnna Dabrowska            }
355*dc4d9dc6SAnna Dabrowska            $length = PdfType::resolve(PdfDictionary::get($stream->value, 'Length'), $reader->getParser());
356*dc4d9dc6SAnna Dabrowska            $dict->value['Length'] = $length;
357*dc4d9dc6SAnna Dabrowska            $stream->value = $dict;
358*dc4d9dc6SAnna Dabrowska
359*dc4d9dc6SAnna Dabrowska        // otherwise extract it from the array and re-compress the whole stream
360*dc4d9dc6SAnna Dabrowska        } else {
361*dc4d9dc6SAnna Dabrowska            $streamContent = $this->compress
362*dc4d9dc6SAnna Dabrowska                ? \gzcompress($page->getContentStream())
363*dc4d9dc6SAnna Dabrowska                : $page->getContentStream();
364*dc4d9dc6SAnna Dabrowska
365*dc4d9dc6SAnna Dabrowska            $dict->value['Length'] = PdfNumeric::create(\strlen($streamContent));
366*dc4d9dc6SAnna Dabrowska            if ($this->compress) {
367*dc4d9dc6SAnna Dabrowska                $dict->value['Filter'] = PdfName::create('FlateDecode');
368*dc4d9dc6SAnna Dabrowska            }
369*dc4d9dc6SAnna Dabrowska
370*dc4d9dc6SAnna Dabrowska            $stream = PdfStream::create($dict, $streamContent);
371*dc4d9dc6SAnna Dabrowska        }
372*dc4d9dc6SAnna Dabrowska
373*dc4d9dc6SAnna Dabrowska        $this->importedPages[$pageId] = [
374*dc4d9dc6SAnna Dabrowska            'objectNumber' => null,
375*dc4d9dc6SAnna Dabrowska            'readerId' => $this->currentReaderId,
376*dc4d9dc6SAnna Dabrowska            'id' => 'TPL' . $this->getNextTemplateId(),
377*dc4d9dc6SAnna Dabrowska            'width' => $width / $this->k,
378*dc4d9dc6SAnna Dabrowska            'height' => $height / $this->k,
379*dc4d9dc6SAnna Dabrowska            'stream' => $stream
380*dc4d9dc6SAnna Dabrowska        ];
381*dc4d9dc6SAnna Dabrowska
382*dc4d9dc6SAnna Dabrowska        return $pageId;
383*dc4d9dc6SAnna Dabrowska    }
384*dc4d9dc6SAnna Dabrowska
385*dc4d9dc6SAnna Dabrowska    /**
386*dc4d9dc6SAnna Dabrowska     * Draws an imported page onto the page.
387*dc4d9dc6SAnna Dabrowska     *
388*dc4d9dc6SAnna Dabrowska     * Give only one of the size parameters (width, height) to calculate the other one automatically in view to the
389*dc4d9dc6SAnna Dabrowska     * aspect ratio.
390*dc4d9dc6SAnna Dabrowska     *
391*dc4d9dc6SAnna Dabrowska     * @param mixed $pageId The page id
392*dc4d9dc6SAnna Dabrowska     * @param float|int|array $x The abscissa of upper-left corner. Alternatively you could use an assoc array
393*dc4d9dc6SAnna Dabrowska     *                           with the keys "x", "y", "width", "height", "adjustPageSize".
394*dc4d9dc6SAnna Dabrowska     * @param float|int $y The ordinate of upper-left corner.
395*dc4d9dc6SAnna Dabrowska     * @param float|int|null $width The width.
396*dc4d9dc6SAnna Dabrowska     * @param float|int|null $height The height.
397*dc4d9dc6SAnna Dabrowska     * @param bool $adjustPageSize
398*dc4d9dc6SAnna Dabrowska     * @return array The size.
399*dc4d9dc6SAnna Dabrowska     * @see Fpdi::getTemplateSize()
400*dc4d9dc6SAnna Dabrowska     */
401*dc4d9dc6SAnna Dabrowska    public function useImportedPage($pageId, $x = 0, $y = 0, $width = null, $height = null, $adjustPageSize = false)
402*dc4d9dc6SAnna Dabrowska    {
403*dc4d9dc6SAnna Dabrowska        if (\is_array($x)) {
404*dc4d9dc6SAnna Dabrowska            /** @noinspection OffsetOperationsInspection */
405*dc4d9dc6SAnna Dabrowska            unset($x['pageId']);
406*dc4d9dc6SAnna Dabrowska            \extract($x, EXTR_IF_EXISTS);
407*dc4d9dc6SAnna Dabrowska            /** @noinspection NotOptimalIfConditionsInspection */
408*dc4d9dc6SAnna Dabrowska            if (\is_array($x)) {
409*dc4d9dc6SAnna Dabrowska                $x = 0;
410*dc4d9dc6SAnna Dabrowska            }
411*dc4d9dc6SAnna Dabrowska        }
412*dc4d9dc6SAnna Dabrowska
413*dc4d9dc6SAnna Dabrowska        if (!isset($this->importedPages[$pageId])) {
414*dc4d9dc6SAnna Dabrowska            throw new \InvalidArgumentException('Imported page does not exist!');
415*dc4d9dc6SAnna Dabrowska        }
416*dc4d9dc6SAnna Dabrowska
417*dc4d9dc6SAnna Dabrowska        $importedPage = $this->importedPages[$pageId];
418*dc4d9dc6SAnna Dabrowska
419*dc4d9dc6SAnna Dabrowska        $originalSize = $this->getTemplateSize($pageId);
420*dc4d9dc6SAnna Dabrowska        $newSize = $this->getTemplateSize($pageId, $width, $height);
421*dc4d9dc6SAnna Dabrowska        if ($adjustPageSize) {
422*dc4d9dc6SAnna Dabrowska            $this->setPageFormat($newSize, $newSize['orientation']);
423*dc4d9dc6SAnna Dabrowska        }
424*dc4d9dc6SAnna Dabrowska
425*dc4d9dc6SAnna Dabrowska        $this->_out(
426*dc4d9dc6SAnna Dabrowska            // reset standard values, translate and scale
427*dc4d9dc6SAnna Dabrowska            \sprintf(
428*dc4d9dc6SAnna Dabrowska                'q 0 J 1 w 0 j 0 G 0 g %.4F 0 0 %.4F %.4F %.4F cm /%s Do Q',
429*dc4d9dc6SAnna Dabrowska                ($newSize['width'] / $originalSize['width']),
430*dc4d9dc6SAnna Dabrowska                ($newSize['height'] / $originalSize['height']),
431*dc4d9dc6SAnna Dabrowska                $x * $this->k,
432*dc4d9dc6SAnna Dabrowska                ($this->h - $y - $newSize['height']) * $this->k,
433*dc4d9dc6SAnna Dabrowska                $importedPage['id']
434*dc4d9dc6SAnna Dabrowska            )
435*dc4d9dc6SAnna Dabrowska        );
436*dc4d9dc6SAnna Dabrowska
437*dc4d9dc6SAnna Dabrowska        return $newSize;
438*dc4d9dc6SAnna Dabrowska    }
439*dc4d9dc6SAnna Dabrowska
440*dc4d9dc6SAnna Dabrowska    /**
441*dc4d9dc6SAnna Dabrowska     * Get the size of an imported page.
442*dc4d9dc6SAnna Dabrowska     *
443*dc4d9dc6SAnna Dabrowska     * Give only one of the size parameters (width, height) to calculate the other one automatically in view to the
444*dc4d9dc6SAnna Dabrowska     * aspect ratio.
445*dc4d9dc6SAnna Dabrowska     *
446*dc4d9dc6SAnna Dabrowska     * @param mixed $tpl The template id
447*dc4d9dc6SAnna Dabrowska     * @param float|int|null $width The width.
448*dc4d9dc6SAnna Dabrowska     * @param float|int|null $height The height.
449*dc4d9dc6SAnna Dabrowska     * @return array|bool An array with following keys: width, height, 0 (=width), 1 (=height), orientation (L or P)
450*dc4d9dc6SAnna Dabrowska     */
451*dc4d9dc6SAnna Dabrowska    public function getImportedPageSize($tpl, $width = null, $height = null)
452*dc4d9dc6SAnna Dabrowska    {
453*dc4d9dc6SAnna Dabrowska        if (isset($this->importedPages[$tpl])) {
454*dc4d9dc6SAnna Dabrowska            $importedPage = $this->importedPages[$tpl];
455*dc4d9dc6SAnna Dabrowska
456*dc4d9dc6SAnna Dabrowska            if ($width === null && $height === null) {
457*dc4d9dc6SAnna Dabrowska                $width = $importedPage['width'];
458*dc4d9dc6SAnna Dabrowska                $height = $importedPage['height'];
459*dc4d9dc6SAnna Dabrowska            } elseif ($width === null) {
460*dc4d9dc6SAnna Dabrowska                $width = $height * $importedPage['width'] / $importedPage['height'];
461*dc4d9dc6SAnna Dabrowska            }
462*dc4d9dc6SAnna Dabrowska
463*dc4d9dc6SAnna Dabrowska            if ($height  === null) {
464*dc4d9dc6SAnna Dabrowska                $height = $width * $importedPage['height'] / $importedPage['width'];
465*dc4d9dc6SAnna Dabrowska            }
466*dc4d9dc6SAnna Dabrowska
467*dc4d9dc6SAnna Dabrowska            if ($height <= 0. || $width <= 0.) {
468*dc4d9dc6SAnna Dabrowska                throw new \InvalidArgumentException('Width or height parameter needs to be larger than zero.');
469*dc4d9dc6SAnna Dabrowska            }
470*dc4d9dc6SAnna Dabrowska
471*dc4d9dc6SAnna Dabrowska            return [
472*dc4d9dc6SAnna Dabrowska                'width' => $width,
473*dc4d9dc6SAnna Dabrowska                'height' => $height,
474*dc4d9dc6SAnna Dabrowska                0 => $width,
475*dc4d9dc6SAnna Dabrowska                1 => $height,
476*dc4d9dc6SAnna Dabrowska                'orientation' => $width > $height ? 'L' : 'P'
477*dc4d9dc6SAnna Dabrowska            ];
478*dc4d9dc6SAnna Dabrowska        }
479*dc4d9dc6SAnna Dabrowska
480*dc4d9dc6SAnna Dabrowska        return false;
481*dc4d9dc6SAnna Dabrowska    }
482*dc4d9dc6SAnna Dabrowska
483*dc4d9dc6SAnna Dabrowska    /**
484*dc4d9dc6SAnna Dabrowska     * Writes a PdfType object to the resulting buffer.
485*dc4d9dc6SAnna Dabrowska     *
486*dc4d9dc6SAnna Dabrowska     * @param PdfType $value
487*dc4d9dc6SAnna Dabrowska     * @throws PdfTypeException
488*dc4d9dc6SAnna Dabrowska     */
489*dc4d9dc6SAnna Dabrowska    protected function writePdfType(PdfType $value)
490*dc4d9dc6SAnna Dabrowska    {
491*dc4d9dc6SAnna Dabrowska        if ($value instanceof PdfNumeric) {
492*dc4d9dc6SAnna Dabrowska            if (\is_int($value->value)) {
493*dc4d9dc6SAnna Dabrowska                $this->_put($value->value . ' ', false);
494*dc4d9dc6SAnna Dabrowska            } else {
495*dc4d9dc6SAnna Dabrowska                $this->_put(\rtrim(\rtrim(\sprintf('%.5F', $value->value), '0'), '.') . ' ', false);
496*dc4d9dc6SAnna Dabrowska            }
497*dc4d9dc6SAnna Dabrowska
498*dc4d9dc6SAnna Dabrowska        } elseif ($value instanceof PdfName) {
499*dc4d9dc6SAnna Dabrowska            $this->_put('/' . $value->value . ' ', false);
500*dc4d9dc6SAnna Dabrowska
501*dc4d9dc6SAnna Dabrowska        } elseif ($value instanceof PdfString) {
502*dc4d9dc6SAnna Dabrowska            $this->_put('(' . $value->value . ')', false);
503*dc4d9dc6SAnna Dabrowska
504*dc4d9dc6SAnna Dabrowska        } elseif ($value instanceof PdfHexString) {
505*dc4d9dc6SAnna Dabrowska            $this->_put('<' . $value->value . '>');
506*dc4d9dc6SAnna Dabrowska
507*dc4d9dc6SAnna Dabrowska        } elseif ($value instanceof PdfBoolean) {
508*dc4d9dc6SAnna Dabrowska            $this->_put($value->value ? 'true ' : 'false ', false);
509*dc4d9dc6SAnna Dabrowska
510*dc4d9dc6SAnna Dabrowska        } elseif ($value instanceof PdfArray) {
511*dc4d9dc6SAnna Dabrowska            $this->_put('[', false);
512*dc4d9dc6SAnna Dabrowska            foreach ($value->value as $entry) {
513*dc4d9dc6SAnna Dabrowska                $this->writePdfType($entry);
514*dc4d9dc6SAnna Dabrowska            }
515*dc4d9dc6SAnna Dabrowska            $this->_put(']');
516*dc4d9dc6SAnna Dabrowska
517*dc4d9dc6SAnna Dabrowska        } elseif ($value instanceof PdfDictionary) {
518*dc4d9dc6SAnna Dabrowska            $this->_put('<<', false);
519*dc4d9dc6SAnna Dabrowska            foreach ($value->value as $name => $entry) {
520*dc4d9dc6SAnna Dabrowska                $this->_put('/' . $name . ' ', false);
521*dc4d9dc6SAnna Dabrowska                $this->writePdfType($entry);
522*dc4d9dc6SAnna Dabrowska            }
523*dc4d9dc6SAnna Dabrowska            $this->_put('>>');
524*dc4d9dc6SAnna Dabrowska
525*dc4d9dc6SAnna Dabrowska        } elseif ($value instanceof PdfToken) {
526*dc4d9dc6SAnna Dabrowska            $this->_put($value->value);
527*dc4d9dc6SAnna Dabrowska
528*dc4d9dc6SAnna Dabrowska        } elseif ($value instanceof PdfNull) {
529*dc4d9dc6SAnna Dabrowska            $this->_put('null ');
530*dc4d9dc6SAnna Dabrowska
531*dc4d9dc6SAnna Dabrowska        } elseif ($value instanceof PdfStream) {
532*dc4d9dc6SAnna Dabrowska            /**
533*dc4d9dc6SAnna Dabrowska             * @var $value PdfStream
534*dc4d9dc6SAnna Dabrowska             */
535*dc4d9dc6SAnna Dabrowska            $this->writePdfType($value->value);
536*dc4d9dc6SAnna Dabrowska            $this->_put('stream');
537*dc4d9dc6SAnna Dabrowska            $this->_put($value->getStream());
538*dc4d9dc6SAnna Dabrowska            $this->_put('endstream');
539*dc4d9dc6SAnna Dabrowska
540*dc4d9dc6SAnna Dabrowska        } elseif ($value instanceof PdfIndirectObjectReference) {
541*dc4d9dc6SAnna Dabrowska            if (!isset($this->objectMap[$this->currentReaderId])) {
542*dc4d9dc6SAnna Dabrowska                $this->objectMap[$this->currentReaderId] = [];
543*dc4d9dc6SAnna Dabrowska            }
544*dc4d9dc6SAnna Dabrowska
545*dc4d9dc6SAnna Dabrowska            if (!isset($this->objectMap[$this->currentReaderId][$value->value])) {
546*dc4d9dc6SAnna Dabrowska                $this->objectMap[$this->currentReaderId][$value->value] = ++$this->n;
547*dc4d9dc6SAnna Dabrowska                $this->objectsToCopy[$this->currentReaderId][] = $value->value;
548*dc4d9dc6SAnna Dabrowska            }
549*dc4d9dc6SAnna Dabrowska
550*dc4d9dc6SAnna Dabrowska            $this->_put($this->objectMap[$this->currentReaderId][$value->value] . ' 0 R ', false);
551*dc4d9dc6SAnna Dabrowska
552*dc4d9dc6SAnna Dabrowska        } elseif ($value instanceof PdfIndirectObject) {
553*dc4d9dc6SAnna Dabrowska            /**
554*dc4d9dc6SAnna Dabrowska             * @var $value PdfIndirectObject
555*dc4d9dc6SAnna Dabrowska             */
556*dc4d9dc6SAnna Dabrowska            $n = $this->objectMap[$this->currentReaderId][$value->objectNumber];
557*dc4d9dc6SAnna Dabrowska            $this->_newobj($n);
558*dc4d9dc6SAnna Dabrowska            $this->writePdfType($value->value);
559*dc4d9dc6SAnna Dabrowska            $this->_put('endobj');
560*dc4d9dc6SAnna Dabrowska        }
561*dc4d9dc6SAnna Dabrowska    }
562*dc4d9dc6SAnna Dabrowska}
563