Issues (2564)

app/Report/PdfRenderer.php (1 issue)

Labels
Severity
1
<?php
2
3
/**
4
 * webtrees: online genealogy
5
 * Copyright (C) 2025 webtrees development team
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
 * GNU General Public License for more details.
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16
 */
17
18
declare(strict_types=1);
19
20
namespace Fisharebest\Webtrees\Report;
21
22
use Fisharebest\Webtrees\MediaFile;
23
use Fisharebest\Webtrees\Webtrees;
24
25
use function count;
26
27
class PdfRenderer extends AbstractRenderer
28
{
29
    /**
30
     * PDF compression - Zlib extension is required
31
     *
32
     * @var bool const
33
     */
34
    private const bool COMPRESSION = true;
0 ignored issues
show
A parse error occurred: Syntax error, unexpected T_STRING, expecting '=' on line 34 at column 23
Loading history...
35
36
    /**
37
     * If true reduce the RAM memory usage by caching temporary data on filesystem (slower).
38
     *
39
     * @var bool const
40
     */
41
    private const bool DISK_CACHE = false;
42
43
    /**
44
     * true means that the input text is unicode (PDF)
45
     *
46
     * @var bool const
47
     */
48
    private const bool UNICODE = true;
49
50
    // Font sub-setting in TCPDF is slow.
51
    private const bool SUBSETTING = false;
52
53
    public TcpdfWrapper $tcpdf;
54
55
    /** @var array<ReportPdfFootnote> Array of elements in the footer notes */
56
    public array $printedfootnotes = [];
57
58
    // The last cell height
59
    public float $lastCellHeight = 0.0;
60
61
    // The largest font size within a TextBox to calculate the height
62
    public float $largestFontHeight = 0.0;
63
64
    // The last pictures page number
65
    public int $lastpicpage = 0;
66
67
    /**
68
     * PDF Header -PDF
69
     *
70
     * @return void
71
     */
72
    public function header(): void
73
    {
74
        foreach ($this->headerElements as $element) {
75
            if ($element instanceof ReportBaseElement) {
76
                $element->render($this);
77
            } elseif ($element === 'footnotetexts') {
78
                $this->footnotes();
79
            } elseif ($element === 'addpage') {
80
                $this->newPage();
81
            }
82
        }
83
    }
84
85
    /**
86
     * PDF Body -PDF
87
     *
88
     * @return void
89
     */
90
    public function body(): void
91
    {
92
        $this->tcpdf->AddPage();
93
94
        foreach ($this->bodyElements as $element) {
95
            if ($element instanceof ReportBaseElement) {
96
                $element->render($this);
97
            } elseif ($element === 'footnotetexts') {
98
                $this->footnotes();
99
            } elseif ($element === 'addpage') {
100
                $this->newPage();
101
            }
102
        }
103
    }
104
105
    /**
106
     * Generate footnotes
107
     *
108
     * @return void
109
     */
110
    public function footnotes(): void
111
    {
112
        foreach ($this->printedfootnotes as $element) {
113
            if ($this->tcpdf->GetY() + $element->getFootnoteHeight($this) > $this->tcpdf->getPageHeight()) {
114
                $this->tcpdf->AddPage();
115
            }
116
117
            $element->renderFootnote($this);
118
119
            if ($this->tcpdf->GetY() > $this->tcpdf->getPageHeight()) {
120
                $this->tcpdf->AddPage();
121
            }
122
        }
123
    }
124
125
    /**
126
     * PDF Footer -PDF
127
     *
128
     * @return void
129
     */
130
    public function footer(): void
131
    {
132
        foreach ($this->footerElements as $element) {
133
            if ($element instanceof ReportBaseElement) {
134
                $element->render($this);
135
            } elseif ($element === 'footnotetexts') {
136
                $this->footnotes();
137
            } elseif ($element === 'addpage') {
138
                $this->newPage();
139
            }
140
        }
141
    }
142
143
    /**
144
     * Remove the header.
145
     *
146
     * @param int $index
147
     *
148
     * @return void
149
     */
150
    public function removeHeader(int $index): void
151
    {
152
        unset($this->headerElements[$index]);
153
    }
154
155
    /**
156
     * Remove the body.
157
     *
158
     * @param int $index
159
     *
160
     * @return void
161
     */
162
    public function removeBody(int $index): void
163
    {
164
        unset($this->bodyElements[$index]);
165
    }
166
167
    /**
168
     * Clear the Header -PDF
169
     *
170
     * @return void
171
     */
172
    public function clearHeader(): void
173
    {
174
        unset($this->headerElements);
175
        $this->headerElements = [];
176
    }
177
178
    /**
179
     * Get the currently used style name -PDF
180
     *
181
     * @return string
182
     */
183
    public function getCurrentStyle(): string
184
    {
185
        return $this->currentStyle;
186
    }
187
188
    /**
189
     * Setup a style for usage -PDF
190
     *
191
     * @param string $s Style name
192
     *
193
     * @return void
194
     */
195
    public function setCurrentStyle(string $s): void
196
    {
197
        $this->currentStyle = $s;
198
        $style              = $this->getStyle($s);
199
        $this->tcpdf->setFont($style['font'], $style['style'], $style['size']);
200
    }
201
202
    /**
203
     * Get the style -PDF
204
     *
205
     * @param string $s Style name
206
     *
207
     * @return array{'name': string, 'font': string, 'style': string, 'size': float}
208
     */
209
    public function getStyle(string $s): array
210
    {
211
        return $this->styles[$s] ?? $this->styles[$this->getCurrentStyle()];
212
    }
213
214
    /**
215
     * Add margin when static horizontal position is used -PDF
216
     * RTL supported
217
     *
218
     * @param float $x Static position
219
     *
220
     * @return float
221
     */
222
    public function addMarginX(float $x): float
223
    {
224
        $m = $this->tcpdf->getMargins();
225
        if ($this->tcpdf->getRTL()) {
226
            $x += $m['right'];
227
        } else {
228
            $x += $m['left'];
229
        }
230
        $this->tcpdf->setX($x);
231
232
        return $x;
233
    }
234
235
    /**
236
     * Get the maximum line width to draw from the current position -PDF
237
     * RTL supported
238
     *
239
     * @return float
240
     */
241
    public function getMaxLineWidth(): float
242
    {
243
        $m = $this->tcpdf->getMargins();
244
        if ($this->tcpdf->getRTL()) {
245
            return $this->tcpdf->getRemainingWidth() + $m['right'];
246
        }
247
248
        return $this->tcpdf->getRemainingWidth() + $m['left'];
249
    }
250
251
    /**
252
     * Get the height of the footnote.
253
     *
254
     * @return float
255
     */
256
    public function getFootnotesHeight(): float
257
    {
258
        $h = 0;
259
        foreach ($this->printedfootnotes as $element) {
260
            $h += $element->getHeight($this);
261
        }
262
263
        return $h;
264
    }
265
266
    /**
267
     * Returns the the current font size height -PDF
268
     *
269
     * @return float
270
     */
271
    public function getCurrentStyleHeight(): float
272
    {
273
        if ($this->currentStyle === '') {
274
            return $this->default_font_size;
275
        }
276
        $style = $this->getStyle($this->currentStyle);
277
278
        return $style['size'];
279
    }
280
281
    /**
282
     * Checks the Footnote and numbers them
283
     *
284
     * @param ReportPdfFootnote $footnote
285
     *
286
     * @return ReportPdfFootnote|bool object if already numbered, false otherwise
287
     */
288
    public function checkFootnote(ReportPdfFootnote $footnote)
289
    {
290
        $ct  = count($this->printedfootnotes);
291
        $val = $footnote->getValue();
292
        $i   = 0;
293
        while ($i < $ct) {
294
            if ($this->printedfootnotes[$i]->getValue() === $val) {
295
                // If this footnote already exist then set up the numbers for this object
296
                $footnote->setNum($i + 1);
297
                $footnote->setAddlink((string) ($i + 1));
298
299
                return $this->printedfootnotes[$i];
300
            }
301
            $i++;
302
        }
303
        // If this Footnote has not been set up yet
304
        $footnote->setNum($ct + 1);
305
        $footnote->setAddlink((string) $this->tcpdf->AddLink());
306
        $this->printedfootnotes[] = $footnote;
307
308
        return false;
309
    }
310
311
    /**
312
     * Used this function instead of AddPage()
313
     * This function will make sure that images will not be overwritten
314
     *
315
     * @return void
316
     */
317
    public function newPage(): void
318
    {
319
        if ($this->lastpicpage > $this->tcpdf->getPage()) {
320
            $this->tcpdf->setPage($this->lastpicpage);
321
        }
322
        $this->tcpdf->AddPage();
323
    }
324
325
    /**
326
     * Add a page if needed -PDF
327
     *
328
     * @param float $height Cell height
329
     *
330
     * @return bool true in case of page break, false otherwise
331
     */
332
    public function checkPageBreakPDF(float $height): bool
333
    {
334
        return $this->tcpdf->checkPageBreak($height);
335
    }
336
337
    /**
338
     * Returns the remaining width between the current position and margins -PDF
339
     *
340
     * @return float Remaining width
341
     */
342
    public function getRemainingWidthPDF(): float
343
    {
344
        return $this->tcpdf->getRemainingWidth();
345
    }
346
    /**
347
     * PDF Setup - ReportPdf
348
     *
349
     * @return void
350
     */
351
    public function setup(): void
352
    {
353
        parent::setup();
354
355
        $this->tcpdf = new TcpdfWrapper(
356
            $this->orientation,
357
            self::UNITS,
358
            [$this->page_width, $this->page_height],
359
            self::UNICODE,
360
            'UTF-8',
361
            self::DISK_CACHE
362
        );
363
364
        $this->tcpdf->setMargins($this->left_margin, $this->top_margin, $this->right_margin);
365
        $this->tcpdf->setHeaderMargin($this->header_margin);
366
        $this->tcpdf->setFooterMargin($this->footer_margin);
367
        $this->tcpdf->setAutoPageBreak(true, $this->bottom_margin);
368
        $this->tcpdf->setFontSubsetting(self::SUBSETTING);
369
        $this->tcpdf->setCompression(self::COMPRESSION);
370
        $this->tcpdf->setRTL($this->rtl);
371
        $this->tcpdf->setCreator(Webtrees::NAME . ' ' . Webtrees::VERSION);
372
        $this->tcpdf->setAuthor($this->rauthor);
373
        $this->tcpdf->setTitle($this->title);
374
        $this->tcpdf->setSubject($this->rsubject);
375
        $this->tcpdf->setKeywords($this->rkeywords);
376
        $this->tcpdf->setHeaderData('', 0, $this->title);
377
        $this->tcpdf->setHeaderFont([$this->default_font, '', $this->default_font_size]);
378
379
        if ($this->show_generated_by) {
380
            // The default style name for Generated by.... is 'genby'
381
            $element = new ReportPdfCell(0.0, 10.0, '', 'C', '', 'genby', 1, ReportBaseElement::CURRENT_POSITION, ReportBaseElement::CURRENT_POSITION, false, 0, '', '', true);
382
            $element->addText($this->generated_by);
383
            $element->setUrl(Webtrees::URL);
384
            $this->addElementToFooter($element);
385
        }
386
    }
387
388
    /**
389
     * Run the report.
390
     *
391
     * @return void
392
     */
393
    public function run(): void
394
    {
395
        $this->body();
396
        echo $this->tcpdf->Output('doc.pdf', 'S');
397
    }
398
399
    /**
400
     * Create a new Cell object.
401
     *
402
     * @param float  $width   cell width (expressed in points)
403
     * @param float  $height  cell height (expressed in points)
404
     * @param string $border  Border style
405
     * @param string $align   Text alignment
406
     * @param string $bgcolor Background color code
407
     * @param string $style   The name of the text style
408
     * @param int    $ln      Indicates where the current position should go after the call
409
     * @param float  $top     Y-position
410
     * @param float  $left    X-position
411
     * @param bool   $fill    Indicates if the cell background must be painted (1) or transparent (0). Default value: 1
412
     * @param int    $stretch Stretch carachter mode
413
     * @param string $bocolor Border color
414
     * @param string $tcolor  Text color
415
     * @param bool   $reseth
416
     *
417
     * @return ReportBaseCell
418
     */
419
    public function createCell(float $width, float $height, string $border, string $align, string $bgcolor, string $style, int $ln, float $top, float $left, bool $fill, int $stretch, string $bocolor, string $tcolor, bool $reseth): ReportBaseCell
420
    {
421
        return new ReportPdfCell($width, $height, $border, $align, $bgcolor, $style, $ln, $top, $left, $fill, $stretch, $bocolor, $tcolor, $reseth);
422
    }
423
424
    /**
425
     * Create a new TextBox object.
426
     *
427
     * @param float  $width   Text box width
428
     * @param float  $height  Text box height
429
     * @param bool   $border
430
     * @param string $bgcolor Background color code in HTML
431
     * @param bool   $newline
432
     * @param float  $left
433
     * @param float  $top
434
     * @param bool   $pagecheck
435
     * @param string $style
436
     * @param bool   $fill
437
     * @param bool   $padding
438
     * @param bool   $reseth
439
     *
440
     * @return ReportBaseTextbox
441
     */
442
    public function createTextBox(
443
        float $width,
444
        float $height,
445
        bool $border,
446
        string $bgcolor,
447
        bool $newline,
448
        float $left,
449
        float $top,
450
        bool $pagecheck,
451
        string $style,
452
        bool $fill,
453
        bool $padding,
454
        bool $reseth
455
    ): ReportBaseTextbox {
456
        return new ReportPdfTextBox($width, $height, $border, $bgcolor, $newline, $left, $top, $pagecheck, $style, $fill, $padding, $reseth);
457
    }
458
459
    /**
460
     * Create a text element.
461
     *
462
     * @param string $style
463
     * @param string $color
464
     *
465
     * @return ReportBaseText
466
     */
467
    public function createText(string $style, string $color): ReportBaseText
468
    {
469
        return new ReportPdfText($style, $color);
470
    }
471
472
    /**
473
     * Create a new Footnote object.
474
     *
475
     * @param string $style Style name
476
     *
477
     * @return ReportBaseFootnote
478
     */
479
    public function createFootnote(string $style): ReportBaseFootnote
480
    {
481
        return new ReportPdfFootnote($style);
482
    }
483
484
    /**
485
     * Create a new image object.
486
     *
487
     * @param string $file  Filename
488
     * @param float  $x
489
     * @param float  $y
490
     * @param float  $w     Image width
491
     * @param float  $h     Image height
492
     * @param string $align L:left, C:center, R:right or empty to use x/y
493
     * @param string $ln    T:same line, N:next line
494
     *
495
     * @return ReportBaseImage
496
     */
497
    public function createImage(string $file, float $x, float $y, float $w, float $h, string $align, string $ln): ReportBaseImage
498
    {
499
        return new ReportPdfImage($file, $x, $y, $w, $h, $align, $ln);
500
    }
501
502
    /**
503
     * Create a new image object from Media Object.
504
     *
505
     * @param MediaFile $media_file
506
     * @param float     $x
507
     * @param float     $y
508
     * @param float     $w     Image width
509
     * @param float     $h     Image height
510
     * @param string    $align L:left, C:center, R:right or empty to use x/y
511
     * @param string    $ln    T:same line, N:next line
512
     *
513
     * @return ReportBaseImage
514
     */
515
    public function createImageFromObject(
516
        MediaFile $media_file,
517
        float $x,
518
        float $y,
519
        float $w,
520
        float $h,
521
        string $align,
522
        string $ln
523
    ): ReportBaseImage {
524
        return new ReportPdfImage('@' . $media_file->fileContents(), $x, $y, $w, $h, $align, $ln);
525
    }
526
527
    /**
528
     * Create a line.
529
     *
530
     * @param float $x1
531
     * @param float $y1
532
     * @param float $x2
533
     * @param float $y2
534
     *
535
     * @return ReportBaseLine
536
     */
537
    public function createLine(float $x1, float $y1, float $x2, float $y2): ReportBaseLine
538
    {
539
        return new ReportPdfLine($x1, $y1, $x2, $y2);
540
    }
541
}
542