Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
87.18% covered (success)
87.18%
68 / 78
72.22% covered (warning)
72.22%
26 / 36
CRAP
0.00% covered (danger)
0.00%
0 / 1
TcpdfEngine
87.18% covered (success)
87.18%
68 / 78
72.22% covered (warning)
72.22%
26 / 36
42.21
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 open
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 output
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addPage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setPrintHeader
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setDisplayMode
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setTitle
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setAuthor
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setCreator
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 bookmark
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setMargins
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setHeaderMargin
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setFooterMargin
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setFont
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setFontSubsetting
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setImageScale
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setDefaultMonospacedFont
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setRtl
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 write
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 ln
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 writeHtml
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 writeHtmlCell
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 multiCell
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 cell
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setY
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setTextColor
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getLastH
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addTocPage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addToc
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 endTocPage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getAliasNumPage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getAliasNbPages
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 onHeader
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 onFooter
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 defineTcpdfConstants
100.00% covered (success)
100.00%
35 / 35
100.00% covered (success)
100.00%
1 / 1
3
 defineIfMissing
50.00% covered (danger)
50.00%
1 / 2
0.00% covered (danger)
0.00%
0 / 1
2.50
1<?php
2
3/**
4 * TCPDF-backed implementation of PdfEngineInterface. The only class that
5 * references the TCPDF library and its K_* and PDF_* constants.
6 *
7 * This Source Code Form is subject to the terms of the Mozilla Public License,
8 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
9 * obtain one at https://mozilla.org/MPL/2.0/.
10 *
11 * @package   phpMyFAQ
12 * @author    Thorsten Rinne <thorsten@phpmyfaq.de>
13 * @copyright 2026 phpMyFAQ Team
14 * @license   https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
15 * @link      https://www.phpmyfaq.de
16 * @since     2026-06-27
17 */
18
19declare(strict_types=1);
20
21namespace phpMyFAQ\Export\Pdf\Engine;
22
23/* @mago-expect lint:too-many-methods - adapts the TCPDF surface the export needs */
24final readonly class TcpdfEngine implements PdfEngineInterface
25{
26    private TcpdfDocument $document;
27
28    public function __construct()
29    {
30        self::defineTcpdfConstants();
31
32        $this->document = new TcpdfDocument(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT);
33        $this->document->setFontSubsetting(false);
34        $this->document->setImageScale(PDF_IMAGE_SCALE_RATIO);
35        $this->document->SetDefaultMonospacedFont(PDF_FONT_MONOSPACED);
36    }
37
38    public function open(): void
39    {
40        $this->document->Open();
41        $this->document->SetMargins(PDF_MARGIN_LEFT, PDF_MARGIN_TOP, PDF_MARGIN_RIGHT);
42        $this->document->SetHeaderMargin(PDF_MARGIN_HEADER);
43        $this->document->SetFooterMargin(PDF_MARGIN_FOOTER);
44    }
45
46    public function output(string $name, string $dest): string
47    {
48        return (string) $this->document->Output($name, $dest);
49    }
50
51    public function addPage(): void
52    {
53        $this->document->AddPage();
54    }
55
56    public function setPrintHeader(bool $val): void
57    {
58        $this->document->setPrintHeader($val);
59    }
60
61    public function setDisplayMode(mixed $zoom): void
62    {
63        $this->document->SetDisplayMode($zoom);
64    }
65
66    public function setTitle(string $title): void
67    {
68        $this->document->SetTitle($title);
69    }
70
71    public function setAuthor(string $author): void
72    {
73        $this->document->SetAuthor($author);
74    }
75
76    public function setCreator(string $creator): void
77    {
78        $this->document->SetCreator($creator);
79    }
80
81    public function bookmark(string $txt, int $level = 0, float $y = -1): void
82    {
83        $this->document->Bookmark($txt, $level, $y);
84    }
85
86    public function setMargins(float $left, float $top, float $right = -1): void
87    {
88        $this->document->SetMargins($left, $top, $right);
89    }
90
91    public function setHeaderMargin(float $margin): void
92    {
93        $this->document->SetHeaderMargin($margin);
94    }
95
96    public function setFooterMargin(float $margin): void
97    {
98        $this->document->SetFooterMargin($margin);
99    }
100
101    public function setFont(string $family, string $style = '', float $size = 0): void
102    {
103        $this->document->SetFont($family, $style, $size);
104    }
105
106    public function setFontSubsetting(bool $enable): void
107    {
108        $this->document->setFontSubsetting($enable);
109    }
110
111    public function setImageScale(float $scale): void
112    {
113        $this->document->setImageScale($scale);
114    }
115
116    public function setDefaultMonospacedFont(string $font): void
117    {
118        $this->document->SetDefaultMonospacedFont($font);
119    }
120
121    public function setRtl(bool $enable): void
122    {
123        $this->document->setRTL($enable);
124    }
125
126    public function write(float $h, string $txt): void
127    {
128        $this->document->Write($h, $txt);
129    }
130
131    public function ln(?float $h = null): void
132    {
133        $this->document->Ln($h);
134    }
135
136    public function writeHtml(
137        string $html,
138        bool $ln = true,
139        bool $fill = false,
140        bool $reseth = false,
141        bool $cell = false,
142        string $align = '',
143    ): void {
144        $this->document->writeHTML($html, $ln, $fill, $reseth, $cell, $align);
145    }
146
147    /* @mago-expect lint:excessive-parameter-list - mirrors the TCPDF method signature */
148    public function writeHtmlCell(
149        float $w,
150        float $h,
151        ?float $x,
152        ?float $y,
153        string $html,
154        mixed $border = 0,
155        int $ln = 0,
156        bool $fill = false,
157        bool $reseth = true,
158        string $align = '',
159    ): void {
160        $this->document->writeHTMLCell($w, $h, $x, $y, $html, $border, $ln, $fill, $reseth, $align);
161    }
162
163    public function multiCell(float $w, float $h, string $txt, mixed $border = 0, string $align = 'J'): void
164    {
165        $this->document->MultiCell($w, $h, $txt, $border, $align);
166    }
167
168    /* @mago-expect lint:excessive-parameter-list - mirrors the TCPDF method signature */
169    public function cell(
170        float $w,
171        float $h,
172        string $txt,
173        mixed $border = 0,
174        int $ln = 0,
175        string $align = '',
176        bool $fill = false,
177        string $link = '',
178    ): void {
179        $this->document->Cell($w, $h, $txt, $border, $ln, $align, $fill, $link);
180    }
181
182    public function setY(float $y): void
183    {
184        $this->document->SetY($y);
185    }
186
187    public function setTextColor(int $col1, int $col2 = -1, int $col3 = -1): void
188    {
189        $this->document->SetTextColor($col1, $col2, $col3);
190    }
191
192    public function getLastH(): float
193    {
194        return $this->document->getLastH();
195    }
196
197    public function addTocPage(): void
198    {
199        $this->document->addTOCPage();
200    }
201
202    public function addToc(
203        int $page,
204        string $numbersfont,
205        string $filler,
206        string $tocName,
207        string $style,
208        array $color,
209    ): void {
210        $this->document->addTOC($page, $numbersfont, $filler, $tocName, $style, $color);
211    }
212
213    public function endTocPage(): void
214    {
215        $this->document->endTOCPage();
216    }
217
218    public function getAliasNumPage(): string
219    {
220        return $this->document->getAliasNumPage();
221    }
222
223    public function getAliasNbPages(): string
224    {
225        return $this->document->getAliasNbPages();
226    }
227
228    public function onHeader(callable $renderer): void
229    {
230        $this->document->setHeaderRenderer($renderer);
231    }
232
233    public function onFooter(callable $renderer): void
234    {
235        $this->document->setFooterRenderer($renderer);
236    }
237
238    private static function defineTcpdfConstants(): void
239    {
240        // Note: this file lives one directory deeper (Engine/) than the original
241        // Wrapper, so the fallback paths carry one extra "../" to resolve identically.
242        $pmfRootDir = defined('PMF_ROOT_DIR') ? (string) PMF_ROOT_DIR : __DIR__ . '/../../../../';
243        $pmfSrcDir = defined('PMF_SRC_DIR') ? (string) PMF_SRC_DIR : __DIR__ . '/../../../';
244
245        self::defineIfMissing('K_TCPDF_EXTERNAL_CONFIG', true);
246        self::defineIfMissing('K_PATH_URL', '');
247        self::defineIfMissing('K_PATH_MAIN', $pmfSrcDir . '/libs/tecnickcom/tcpdf/');
248        self::defineIfMissing('K_PATH_FONTS', $pmfSrcDir . '/fonts/');
249        self::defineIfMissing('K_PATH_CACHE', $pmfRootDir . '/content/user/images/');
250        self::defineIfMissing('K_PATH_URL_CACHE', K_PATH_CACHE);
251        self::defineIfMissing('K_PATH_IMAGES', $pmfRootDir . '/content/user/images/');
252        self::defineIfMissing('K_BLANK_IMAGE', (string) constant('K_PATH_IMAGES') . '_blank.png');
253        self::defineIfMissing('PDF_PAGE_FORMAT', 'A4');
254        self::defineIfMissing('PDF_PAGE_ORIENTATION', 'P');
255        self::defineIfMissing('PDF_CREATOR', 'TCPDF');
256        self::defineIfMissing('PDF_AUTHOR', 'TCPDF');
257        self::defineIfMissing('PDF_HEADER_TITLE', 'phpMyFAQ');
258        self::defineIfMissing('PDF_HEADER_STRING', 'by phpMyFAQ - www.phpmyfaq.de');
259        self::defineIfMissing('PDF_HEADER_LOGO', 'tcpdf_logo.jpg');
260        self::defineIfMissing('PDF_HEADER_LOGO_WIDTH', 30);
261        self::defineIfMissing('PDF_UNIT', 'mm');
262        self::defineIfMissing('PDF_MARGIN_HEADER', 5);
263        self::defineIfMissing('PDF_MARGIN_FOOTER', 10);
264        self::defineIfMissing('PDF_MARGIN_TOP', 27);
265        self::defineIfMissing('PDF_MARGIN_BOTTOM', 25);
266        self::defineIfMissing('PDF_MARGIN_LEFT', 15);
267        self::defineIfMissing('PDF_MARGIN_RIGHT', 15);
268        self::defineIfMissing('PDF_FONT_NAME_MAIN', 'dejavusans');
269        self::defineIfMissing('PDF_FONT_SIZE_MAIN', 10);
270        self::defineIfMissing('PDF_FONT_NAME_DATA', 'dejavusans');
271        self::defineIfMissing('PDF_FONT_SIZE_DATA', 8);
272        self::defineIfMissing('PDF_FONT_MONOSPACED', 'DejaVuSansMono');
273        self::defineIfMissing('PDF_IMAGE_SCALE_RATIO', 1);
274        self::defineIfMissing('HEAD_MAGNIFICATION', 1.1);
275        self::defineIfMissing('K_CELL_HEIGHT_RATIO', 1.25);
276        self::defineIfMissing('K_TITLE_MAGNIFICATION', 1.3);
277        self::defineIfMissing('K_SMALL_RATIO', 2 / 3);
278    }
279
280    private static function defineIfMissing(string $name, mixed $value): void
281    {
282        if (!defined($name)) {
283            define($name, $value);
284        }
285    }
286}