xref: /plugin/dw2pdf/vendor/setasign/fpdi/src/FpdfTplTrait.php (revision 01d4b863f647689752623420d92bf6c538d91460)
1<?php
2
3/**
4 * This file is part of FPDI
5 *
6 * @package   setasign\Fpdi
7 * @copyright Copyright (c) 2024 Setasign GmbH & Co. KG (https://www.setasign.com)
8 * @license   http://opensource.org/licenses/mit-license The MIT License
9 */
10
11namespace setasign\Fpdi;
12
13/**
14 * Trait FpdfTplTrait
15 *
16 * This trait adds a templating feature to FPDF and tFPDF.
17 */
18trait FpdfTplTrait
19{
20    /**
21     * Data of all created templates.
22     *
23     * @var array
24     */
25    protected $templates = [];
26
27    /**
28     * The template id for the currently created template.
29     *
30     * @var null|int
31     */
32    protected $currentTemplateId;
33
34    /**
35     * A counter for template ids.
36     *
37     * @var int
38     */
39    protected $templateId = 0;
40
41    /**
42     * Set the page format of the current page.
43     *
44     * @param array $size An array with two values defining the size.
45     * @param string $orientation "L" for landscape, "P" for portrait.
46     * @throws \BadMethodCallException
47     */
48    public function setPageFormat($size, $orientation)
49    {
50        if ($this->currentTemplateId !== null) {
51            throw new \BadMethodCallException('The page format cannot be changed when writing to a template.');
52        }
53
54        if (!\in_array($orientation, ['P', 'L'], true)) {
55            throw new \InvalidArgumentException(\sprintf(
56                'Invalid page orientation "%s"! Only "P" and "L" are allowed!',
57                $orientation
58            ));
59        }
60
61        $size = $this->_getpagesize($size);
62
63        if (
64            $orientation != $this->CurOrientation
65            || $size[0] != $this->CurPageSize[0]
66            || $size[1] != $this->CurPageSize[1]
67        ) {
68            // New size or orientation
69            if ($orientation === 'P') {
70                $this->w = $size[0];
71                $this->h = $size[1];
72            } else {
73                $this->w = $size[1];
74                $this->h = $size[0];
75            }
76            $this->wPt = $this->w * $this->k;
77            $this->hPt = $this->h * $this->k;
78            $this->PageBreakTrigger = $this->h - $this->bMargin;
79            $this->CurOrientation = $orientation;
80            $this->CurPageSize = $size;
81
82            $this->PageInfo[$this->page]['size'] = array($this->wPt, $this->hPt);
83        }
84    }
85
86    /**
87     * Draws a template onto the page or another template.
88     *
89     * Give only one of the size parameters (width, height) to calculate the other one automatically in view to the
90     * aspect ratio.
91     *
92     * @param mixed $tpl The template id
93     * @param array|float|int $x The abscissa of upper-left corner. Alternatively you could use an assoc array
94     *                           with the keys "x", "y", "width", "height", "adjustPageSize".
95     * @param float|int $y The ordinate of upper-left corner.
96     * @param float|int|null $width The width.
97     * @param float|int|null $height The height.
98     * @param bool $adjustPageSize
99     * @return array The size
100     * @see FpdfTplTrait::getTemplateSize()
101     */
102    public function useTemplate($tpl, $x = 0, $y = 0, $width = null, $height = null, $adjustPageSize = false)
103    {
104        if (!isset($this->templates[$tpl])) {
105            throw new \InvalidArgumentException('Template does not exist!');
106        }
107
108        if (\is_array($x)) {
109            unset($x['tpl']);
110            \extract($x, EXTR_IF_EXISTS);
111            /** @noinspection NotOptimalIfConditionsInspection */
112            /** @phpstan-ignore function.alreadyNarrowedType  */
113            if (\is_array($x)) {
114                $x = 0;
115            }
116        }
117
118        $template = $this->templates[$tpl];
119
120        $originalSize = $this->getTemplateSize($tpl);
121        $newSize = $this->getTemplateSize($tpl, $width, $height);
122        if ($adjustPageSize) {
123            $this->setPageFormat($newSize, $newSize['orientation']);
124        }
125
126        $this->_out(
127        // reset standard values, translate and scale
128            \sprintf(
129                'q 0 J 1 w 0 j 0 G 0 g %.4F 0 0 %.4F %.4F %.4F cm /%s Do Q',
130                ($newSize['width'] / $originalSize['width']),
131                ($newSize['height'] / $originalSize['height']),
132                $x * $this->k,
133                ($this->h - $y - $newSize['height']) * $this->k,
134                $template['id']
135            )
136        );
137
138        return $newSize;
139    }
140
141    /**
142     * Get the size of a template.
143     *
144     * Give only one of the size parameters (width, height) to calculate the other one automatically in view to the
145     * aspect ratio.
146     *
147     * @param mixed $tpl The template id
148     * @param float|int|null $width The width.
149     * @param float|int|null $height The height.
150     * @return array|bool An array with following keys: width, height, 0 (=width), 1 (=height), orientation (L or P)
151     */
152    public function getTemplateSize($tpl, $width = null, $height = null)
153    {
154        if (!isset($this->templates[$tpl])) {
155            return false;
156        }
157
158        if ($width === null && $height === null) {
159            $width = $this->templates[$tpl]['width'];
160            $height = $this->templates[$tpl]['height'];
161        } elseif ($width === null) {
162            $width = $height * $this->templates[$tpl]['width'] / $this->templates[$tpl]['height'];
163        }
164
165        if ($height === null) {
166            $height = $width * $this->templates[$tpl]['height'] / $this->templates[$tpl]['width'];
167        }
168
169        if ($height <= 0. || $width <= 0.) {
170            throw new \InvalidArgumentException('Width or height parameter needs to be larger than zero.');
171        }
172
173        return [
174            'width' => $width,
175            'height' => $height,
176            0 => $width,
177            1 => $height,
178            'orientation' => $width > $height ? 'L' : 'P'
179        ];
180    }
181
182    /**
183     * Begins a new template.
184     *
185     * @param float|int|null $width The width of the template. If null, the current page width is used.
186     * @param float|int|null $height The height of the template. If null, the current page height is used.
187     * @param bool $groupXObject Define the form XObject as a group XObject to support transparency (if used).
188     * @return int A template identifier.
189     */
190    public function beginTemplate($width = null, $height = null, $groupXObject = false)
191    {
192        if ($width === null) {
193            $width = $this->w;
194        }
195
196        if ($height === null) {
197            $height = $this->h;
198        }
199
200        $templateId = $this->getNextTemplateId();
201
202        // initiate buffer with current state of FPDF
203        $buffer = "2 J\n"
204            . \sprintf('%.2F w', $this->LineWidth * $this->k) . "\n";
205
206        if ($this->FontFamily) {
207            $buffer .= \sprintf("BT /F%d %.2F Tf ET\n", $this->CurrentFont['i'], $this->FontSizePt);
208        }
209
210        if ($this->DrawColor !== '0 G') {
211            $buffer .= $this->DrawColor . "\n";
212        }
213        if ($this->FillColor !== '0 g') {
214            $buffer .= $this->FillColor . "\n";
215        }
216
217        if ($groupXObject && \version_compare('1.4', $this->PDFVersion, '>')) {
218            $this->PDFVersion = '1.4';
219        }
220
221        $this->templates[$templateId] = [
222            'objectNumber' => null,
223            'id' => 'TPL' . $templateId,
224            'buffer' => $buffer,
225            'width' => $width,
226            'height' => $height,
227            'groupXObject' => $groupXObject,
228            'state' => [
229                'x' => $this->x,
230                'y' => $this->y,
231                'AutoPageBreak' => $this->AutoPageBreak,
232                'bMargin' => $this->bMargin,
233                'tMargin' => $this->tMargin,
234                'lMargin' => $this->lMargin,
235                'rMargin' => $this->rMargin,
236                'h' => $this->h,
237                'hPt' => $this->hPt,
238                'w' => $this->w,
239                'wPt' => $this->wPt,
240                'FontFamily' => $this->FontFamily,
241                'FontStyle' => $this->FontStyle,
242                'FontSizePt' => $this->FontSizePt,
243                'FontSize' => $this->FontSize,
244                'underline' => $this->underline,
245                'TextColor' => $this->TextColor,
246                'DrawColor' => $this->DrawColor,
247                'FillColor' => $this->FillColor,
248                'ColorFlag' => $this->ColorFlag
249            ]
250        ];
251
252        $this->SetAutoPageBreak(false);
253        $this->currentTemplateId = $templateId;
254
255        $this->h = $height;
256        $this->hPt = $height * $this->k;
257        $this->w = $width;
258        $this->wPt = $width * $this->k;
259
260        $this->SetXY($this->lMargin, $this->tMargin);
261        $this->SetRightMargin($this->w - $width + $this->rMargin);
262
263        return $templateId;
264    }
265
266    /**
267     * Ends a template.
268     *
269     * @return bool|int|null A template identifier.
270     */
271    public function endTemplate()
272    {
273        if ($this->currentTemplateId === null) {
274            return false;
275        }
276
277        $templateId = $this->currentTemplateId;
278        $template = $this->templates[$templateId];
279
280        $state = $template['state'];
281        $this->SetXY($state['x'], $state['y']);
282        $this->tMargin = $state['tMargin'];
283        $this->lMargin = $state['lMargin'];
284        $this->rMargin = $state['rMargin'];
285        $this->h = $state['h'];
286        $this->hPt = $state['hPt'];
287        $this->w = $state['w'];
288        $this->wPt = $state['wPt'];
289        $this->SetAutoPageBreak($state['AutoPageBreak'], $state['bMargin']);
290
291        $this->FontFamily = $state['FontFamily'];
292        $this->FontStyle = $state['FontStyle'];
293        $this->FontSizePt = $state['FontSizePt'];
294        $this->FontSize = $state['FontSize'];
295
296        $this->TextColor = $state['TextColor'];
297        $this->DrawColor = $state['DrawColor'];
298        $this->FillColor = $state['FillColor'];
299        $this->ColorFlag = $state['ColorFlag'];
300
301        $this->underline = $state['underline'];
302
303        $fontKey = $this->FontFamily . $this->FontStyle;
304        if ($fontKey) {
305            $this->CurrentFont =& $this->fonts[$fontKey];
306        } else {
307            unset($this->CurrentFont);
308        }
309
310        $this->currentTemplateId = null;
311
312        return $templateId;
313    }
314
315    /**
316     * Get the next template id.
317     *
318     * @return int
319     */
320    protected function getNextTemplateId()
321    {
322        return $this->templateId++;
323    }
324
325    /* overwritten FPDF methods: */
326
327    /**
328     * @inheritdoc
329     */
330    public function AddPage($orientation = '', $size = '', $rotation = 0)
331    {
332        if ($this->currentTemplateId !== null) {
333            throw new \BadMethodCallException('Pages cannot be added when writing to a template.');
334        }
335        parent::AddPage($orientation, $size, $rotation);
336    }
337
338    /**
339     * @inheritdoc
340     */
341    public function Link($x, $y, $w, $h, $link)
342    {
343        if ($this->currentTemplateId !== null) {
344            throw new \BadMethodCallException('Links cannot be set when writing to a template.');
345        }
346        parent::Link($x, $y, $w, $h, $link);
347    }
348
349    /**
350     * @inheritdoc
351     */
352    public function SetLink($link, $y = 0, $page = -1)
353    {
354        if ($this->currentTemplateId !== null) {
355            throw new \BadMethodCallException('Links cannot be set when writing to a template.');
356        }
357        return parent::SetLink($link, $y, $page);
358    }
359
360    /**
361     * @inheritdoc
362     */
363    public function SetDrawColor($r, $g = null, $b = null)
364    {
365        parent::SetDrawColor($r, $g, $b);
366        if ($this->page === 0 && $this->currentTemplateId !== null) {
367            $this->_out($this->DrawColor);
368        }
369    }
370
371    /**
372     * @inheritdoc
373     */
374    public function SetFillColor($r, $g = null, $b = null)
375    {
376        parent::SetFillColor($r, $g, $b);
377        if ($this->page === 0 && $this->currentTemplateId !== null) {
378            $this->_out($this->FillColor);
379        }
380    }
381
382    /**
383     * @inheritdoc
384     */
385    public function SetLineWidth($width)
386    {
387        parent::SetLineWidth($width);
388        if ($this->page === 0 && $this->currentTemplateId !== null) {
389            $this->_out(\sprintf('%.2F w', $width * $this->k));
390        }
391    }
392
393    /**
394     * @inheritdoc
395     */
396    public function SetFont($family, $style = '', $size = 0)
397    {
398        parent::SetFont($family, $style, $size);
399        if ($this->page === 0 && $this->currentTemplateId !== null) {
400            $this->_out(\sprintf('BT /F%d %.2F Tf ET', $this->CurrentFont['i'], $this->FontSizePt));
401        }
402    }
403
404    /**
405     * @inheritdoc
406     */
407    public function SetFontSize($size)
408    {
409        parent::SetFontSize($size);
410        if ($this->page === 0 && $this->currentTemplateId !== null) {
411            $this->_out(sprintf('BT /F%d %.2F Tf ET', $this->CurrentFont['i'], $this->FontSizePt));
412        }
413    }
414
415    protected function _putimages()
416    {
417        parent::_putimages();
418
419        foreach ($this->templates as $key => $template) {
420            $this->_newobj();
421            $this->templates[$key]['objectNumber'] = $this->n;
422
423            $this->_put('<</Type /XObject /Subtype /Form /FormType 1');
424            $this->_put(\sprintf(
425                '/BBox[0 0 %.2F %.2F]',
426                $template['width'] * $this->k,
427                $template['height'] * $this->k
428            ));
429            $this->_put('/Resources 2 0 R'); // default resources dictionary of FPDF
430
431            if ($this->compress) {
432                $buffer = \gzcompress($template['buffer']);
433                $this->_put('/Filter/FlateDecode');
434            } else {
435                $buffer = $template['buffer'];
436            }
437
438            $this->_put('/Length ' . \strlen($buffer));
439
440            if ($template['groupXObject']) {
441                $this->_put('/Group <</Type/Group/S/Transparency>>');
442            }
443
444            $this->_put('>>');
445            $this->_putstream($buffer);
446            $this->_put('endobj');
447        }
448    }
449
450    /**
451     * @inheritdoc
452     */
453    protected function _putxobjectdict()
454    {
455        foreach ($this->templates as $key => $template) {
456            $this->_put('/' . $template['id'] . ' ' . $template['objectNumber'] . ' 0 R');
457        }
458
459        parent::_putxobjectdict();
460    }
461
462    /**
463     * @inheritdoc
464     */
465    public function _out($s)
466    {
467        if ($this->currentTemplateId !== null) {
468            $this->templates[$this->currentTemplateId]['buffer'] .= $s . "\n";
469        } else {
470            parent::_out($s);
471        }
472    }
473}
474