Passed
Push — master ( d12d76...a43d60 )
by Greg
05:47
created

PdfRenderer   F

Complexity

Total Complexity 69

Size/Duplication

Total Lines 705
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 151
dl 0
loc 705
rs 2.88
c 1
b 0
f 0
wmc 69

38 Methods

Rating   Name   Duplication   Size   Complexity  
A clearPageHeader() 0 4 1
A removePageHeader() 0 3 1
A addPageHeader() 0 3 1
A addHeader() 0 3 1
A footnotes() 0 11 4
A getCurrentStyleHeight() 0 8 2
A addBody() 0 3 1
A removeHeader() 0 3 1
A clearHeader() 0 4 1
B header() 0 19 9
A setup() 0 37 2
A removeBody() 0 3 1
A createText() 0 3 1
A checkFootnote() 0 21 3
A createImageFromObject() 0 11 1
A setReport() 0 3 1
A createLine() 0 3 1
A createHTML() 0 3 1
A createImage() 0 3 1
A getStyle() 0 8 2
A getMaxLineWidth() 0 8 2
A addMarginX() 0 11 2
A createPageHeader() 0 3 1
A newPage() 0 6 2
A run() 0 4 1
A createTextBox() 0 15 1
A getRemainingWidthPDF() 0 3 1
A getFootnotesHeight() 0 8 2
A removeFooter() 0 3 1
A checkPageBreakPDF() 0 3 1
A setCurrentStyle() 0 5 1
A addElement() 0 18 4
A footer() 0 9 5
A getCurrentStyle() 0 3 1
A addFooter() 0 3 1
A body() 0 11 5
A createCell() 0 3 1
A createFootnote() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like PdfRenderer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use PdfRenderer, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * webtrees: online genealogy
5
 * Copyright (C) 2019 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 <http://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
use League\Flysystem\FilesystemInterface;
25
26
use function count;
27
28
/**
29
 * Class PdfRenderer
30
 */
31
class PdfRenderer extends AbstractRenderer
32
{
33
    /**
34
     * PDF compression - Zlib extension is required
35
     *
36
     * @var bool const
37
     */
38
    private const COMPRESSION = true;
39
40
    /**
41
     * If true reduce the RAM memory usage by caching temporary data on filesystem (slower).
42
     *
43
     * @var bool const
44
     */
45
    private const DISK_CACHE = false;
46
47
    /**
48
     * true means that the input text is unicode (PDF)
49
     *
50
     * @var bool const
51
     */
52
    private const UNICODE = true;
53
54
    /**
55
     * false means that the full font is embedded, true means only the used chars
56
     * in TCPDF v5.9 font subsetting is a very slow process, this leads to larger files
57
     *
58
     * @var bool const
59
     */
60
    private const SUBSETTING = false;
61
62
    /**
63
     * @var TcpdfWrapper
64
     */
65
    public $tcpdf;
66
67
    /** @var ReportBaseElement[] Array of elements in the header */
68
    public $headerElements = [];
69
70
    /** @var ReportBaseElement[] Array of elements in the page header */
71
    public $pageHeaderElements = [];
72
73
    /** @var ReportBaseElement[] Array of elements in the footer */
74
    public $footerElements = [];
75
76
    /** @var ReportBaseElement[] Array of elements in the body */
77
    public $bodyElements = [];
78
79
    /** @var ReportPdfFootnote[] Array of elements in the footer notes */
80
    public $printedfootnotes = [];
81
82
    /** @var string Currently used style name */
83
    public $currentStyle = '';
84
85
    /** @var float The last cell height */
86
    public $lastCellHeight = 0;
87
88
    /** @var float The largest font size within a TextBox to calculate the height */
89
    public $largestFontHeight = 0;
90
91
    /** @var int The last pictures page number */
92
    public $lastpicpage = 0;
93
94
    /** @var PdfRenderer The current report. */
95
    public $wt_report;
96
97
    /**
98
     * PDF Header -PDF
99
     *
100
     * @return void
101
     */
102
    public function header(): void
103
    {
104
        foreach ($this->headerElements as $element) {
105
            if ($element instanceof ReportBaseElement) {
106
                $element->render($this);
107
            } elseif ($element === 'footnotetexts') {
108
                $this->footnotes();
109
            } elseif ($element === 'addpage') {
110
                $this->newPage();
111
            }
112
        }
113
114
        foreach ($this->pageHeaderElements as $element) {
115
            if ($element instanceof ReportBaseElement) {
116
                $element->render($this);
117
            } elseif ($element === 'footnotetexts') {
118
                $this->footnotes();
119
            } elseif ($element === 'addpage') {
120
                $this->newPage();
121
            }
122
        }
123
    }
124
125
    /**
126
     * PDF Body -PDF
127
     *
128
     * @return void
129
     */
130
    public function body(): void
131
    {
132
        $this->tcpdf->AddPage();
133
134
        foreach ($this->bodyElements as $key => $element) {
135
            if ($element instanceof ReportBaseElement) {
136
                $element->render($this);
137
            } elseif ($element === 'footnotetexts') {
138
                $this->footnotes();
139
            } elseif ($element === 'addpage') {
140
                $this->newPage();
141
            }
142
        }
143
    }
144
145
    /**
146
     * PDF Footnotes -PDF
147
     *
148
     * @return void
149
     */
150
    public function footnotes(): void
151
    {
152
        foreach ($this->printedfootnotes as $element) {
153
            if (($this->tcpdf->GetY() + $element->getFootnoteHeight($this)) > $this->tcpdf->getPageHeight()) {
154
                $this->tcpdf->AddPage();
155
            }
156
157
            $element->renderFootnote($this);
158
159
            if ($this->tcpdf->GetY() > $this->tcpdf->getPageHeight()) {
160
                $this->tcpdf->AddPage();
161
            }
162
        }
163
    }
164
165
    /**
166
     * PDF Footer -PDF
167
     *
168
     * @return void
169
     */
170
    public function footer(): void
171
    {
172
        foreach ($this->footerElements as $element) {
173
            if ($element instanceof ReportBaseElement) {
174
                $element->render($this);
175
            } elseif ($element === 'footnotetexts') {
176
                $this->footnotes();
177
            } elseif ($element === 'addpage') {
178
                $this->newPage();
179
            }
180
        }
181
    }
182
183
    /**
184
     * Add an element to the Header -PDF
185
     *
186
     * @param ReportBaseElement|string $element
187
     *
188
     * @return void
189
     */
190
    public function addHeader($element): void
191
    {
192
        $this->headerElements[] = $element;
193
    }
194
195
    /**
196
     * Add an element to the Page Header -PDF
197
     *
198
     * @param ReportBaseElement|string $element
199
     *
200
     * @return void
201
     */
202
    public function addPageHeader($element): void
203
    {
204
        $this->pageHeaderElements[] = $element;
205
    }
206
207
    /**
208
     * Add an element to the Body -PDF
209
     *
210
     * @param ReportBaseElement|string $element
211
     *
212
     * @return void
213
     */
214
    public function addBody($element): void
215
    {
216
        $this->bodyElements[] = $element;
217
    }
218
219
    /**
220
     * Add an element to the Footer -PDF
221
     *
222
     * @param ReportBaseElement|string $element
223
     *
224
     * @return void
225
     */
226
    public function addFooter($element): void
227
    {
228
        $this->footerElements[] = $element;
229
    }
230
231
    /**
232
     * Remove the header.
233
     *
234
     * @param int $index
235
     *
236
     * @return void
237
     */
238
    public function removeHeader(int $index): void
239
    {
240
        unset($this->headerElements[$index]);
241
    }
242
243
    /**
244
     * Remove the page header.
245
     *
246
     * @param int $index
247
     *
248
     * @return void
249
     */
250
    public function removePageHeader(int $index): void
251
    {
252
        unset($this->pageHeaderElements[$index]);
253
    }
254
255
    /**
256
     * Remove the body.
257
     *
258
     * @param int $index
259
     *
260
     * @return void
261
     */
262
    public function removeBody(int $index): void
263
    {
264
        unset($this->bodyElements[$index]);
265
    }
266
267
    /**
268
     * Remove the footer.
269
     *
270
     * @param int $index
271
     *
272
     * @return void
273
     */
274
    public function removeFooter(int $index): void
275
    {
276
        unset($this->footerElements[$index]);
277
    }
278
279
    /**
280
     * Clear the Header -PDF
281
     *
282
     * @return void
283
     */
284
    public function clearHeader(): void
285
    {
286
        unset($this->headerElements);
287
        $this->headerElements = [];
288
    }
289
290
    /**
291
     * Clear the Page Header -PDF
292
     *
293
     * @return void
294
     */
295
    public function clearPageHeader(): void
296
    {
297
        unset($this->pageHeaderElements);
298
        $this->pageHeaderElements = [];
299
    }
300
301
    /**
302
     * Set the report.
303
     *
304
     * @param PdfRenderer $report
305
     *
306
     * @return void
307
     */
308
    public function setReport(PdfRenderer $report): void
309
    {
310
        $this->wt_report = $report;
311
    }
312
313
    /**
314
     * Get the currently used style name -PDF
315
     *
316
     * @return string
317
     */
318
    public function getCurrentStyle(): string
319
    {
320
        return $this->currentStyle;
321
    }
322
323
    /**
324
     * Setup a style for usage -PDF
325
     *
326
     * @param string $s Style name
327
     *
328
     * @return void
329
     */
330
    public function setCurrentStyle(string $s): void
331
    {
332
        $this->currentStyle = $s;
333
        $style              = $this->wt_report->getStyle($s);
334
        $this->tcpdf->SetFont($style['font'], $style['style'], $style['size']);
335
    }
336
337
    /**
338
     * Get the style -PDF
339
     *
340
     * @param string $s Style name
341
     *
342
     * @return array
343
     */
344
    public function getStyle(string $s): array
345
    {
346
        if (!isset($this->wt_report->styles[$s])) {
347
            $s                           = $this->getCurrentStyle();
348
            $this->wt_report->styles[$s] = $s;
349
        }
350
351
        return $this->wt_report->styles[$s];
352
    }
353
354
    /**
355
     * Add margin when static horizontal position is used -PDF
356
     * RTL supported
357
     *
358
     * @param float $x Static position
359
     *
360
     * @return float
361
     */
362
    public function addMarginX(float $x): float
363
    {
364
        $m = $this->tcpdf->getMargins();
365
        if ($this->tcpdf->getRTL()) {
366
            $x += $m['right'];
367
        } else {
368
            $x += $m['left'];
369
        }
370
        $this->tcpdf->SetX($x);
371
372
        return $x;
373
    }
374
375
    /**
376
     * Get the maximum line width to draw from the curren position -PDF
377
     * RTL supported
378
     *
379
     * @return float
380
     */
381
    public function getMaxLineWidth(): float
382
    {
383
        $m = $this->tcpdf->getMargins();
384
        if ($this->tcpdf->getRTL()) {
385
            return ($this->tcpdf->getRemainingWidth() + $m['right']);
386
        }
387
388
        return ($this->tcpdf->getRemainingWidth() + $m['left']);
389
    }
390
391
    /**
392
     * Get the height of the footnote.
393
     *
394
     * @return float
395
     */
396
    public function getFootnotesHeight(): float
397
    {
398
        $h = 0;
399
        foreach ($this->printedfootnotes as $element) {
400
            $h += $element->getHeight($this);
401
        }
402
403
        return $h;
404
    }
405
406
    /**
407
     * Returns the the current font size height -PDF
408
     *
409
     * @return float
410
     */
411
    public function getCurrentStyleHeight(): float
412
    {
413
        if ($this->currentStyle === '') {
414
            return $this->wt_report->default_font_size;
415
        }
416
        $style = $this->wt_report->getStyle($this->currentStyle);
417
418
        return (float) $style['size'];
419
    }
420
421
    /**
422
     * Checks the Footnote and numbers them
423
     *
424
     * @param ReportPdfFootnote $footnote
425
     *
426
     * @return ReportPdfFootnote|bool object if already numbered, false otherwise
427
     */
428
    public function checkFootnote(ReportPdfFootnote $footnote)
429
    {
430
        $ct  = count($this->printedfootnotes);
431
        $val = $footnote->getValue();
432
        $i   = 0;
433
        while ($i < $ct) {
434
            if ($this->printedfootnotes[$i]->getValue() == $val) {
435
                // If this footnote already exist then set up the numbers for this object
436
                $footnote->setNum($i + 1);
437
                $footnote->setAddlink((string) ($i + 1));
438
439
                return $this->printedfootnotes[$i];
440
            }
441
            $i++;
442
        }
443
        // If this Footnote has not been set up yet
444
        $footnote->setNum($ct + 1);
445
        $footnote->setAddlink((string) $this->tcpdf->AddLink());
446
        $this->printedfootnotes[] = $footnote;
447
448
        return false;
449
    }
450
451
    /**
452
     * Used this function instead of AddPage()
453
     * This function will make sure that images will not be overwritten
454
     *
455
     * @return void
456
     */
457
    public function newPage(): void
458
    {
459
        if ($this->lastpicpage > $this->tcpdf->getPage()) {
460
            $this->tcpdf->setPage($this->lastpicpage);
461
        }
462
        $this->tcpdf->AddPage();
463
    }
464
465
    /**
466
     * Add a page if needed -PDF
467
     *
468
     * @param float $height Cell height
469
     *
470
     * @return bool true in case of page break, false otherwise
471
     */
472
    public function checkPageBreakPDF(float $height): bool
473
    {
474
        return $this->tcpdf->checkPageBreak($height);
475
    }
476
477
    /**
478
     * Returns the remaining width between the current position and margins -PDF
479
     *
480
     * @return float Remaining width
481
     */
482
    public function getRemainingWidthPDF(): float
483
    {
484
        return $this->tcpdf->getRemainingWidth();
485
    }
486
    /**
487
     * PDF Setup - ReportPdf
488
     *
489
     * @return void
490
     */
491
    public function setup(): void
492
    {
493
        parent::setup();
494
495
        // Setup the PDF class with custom size pages because WT supports more page sizes. If WT sends an unknown size name then the default would be A4
496
        $this->tcpdf = new TcpdfWrapper($this->orientation, parent::UNITS, [
497
            $this->page_width,
498
            $this->page_height,
499
        ], self::UNICODE, 'UTF-8', self::DISK_CACHE);
500
501
        // Setup the PDF margins
502
        $this->tcpdf->SetMargins($this->left_margin, $this->top_margin, $this->right_margin);
503
        $this->tcpdf->setHeaderMargin($this->header_margin);
504
        $this->tcpdf->setFooterMargin($this->footer_margin);
505
        //Set auto page breaks
506
        $this->tcpdf->SetAutoPageBreak(true, $this->bottom_margin);
507
        // Set font subsetting
508
        $this->tcpdf->setFontSubsetting(self::SUBSETTING);
509
        // Setup PDF compression
510
        $this->tcpdf->SetCompression(self::COMPRESSION);
511
        // Setup RTL support
512
        $this->tcpdf->setRTL($this->rtl);
513
        // Set the document information
514
        $this->tcpdf->SetCreator(Webtrees::NAME . ' ' . Webtrees::VERSION);
515
        $this->tcpdf->SetAuthor($this->rauthor);
516
        $this->tcpdf->SetTitle($this->title);
517
        $this->tcpdf->SetSubject($this->rsubject);
518
        $this->tcpdf->SetKeywords($this->rkeywords);
519
520
        $this->setReport($this);
521
522
        if ($this->show_generated_by) {
523
            // The default style name for Generated by.... is 'genby'
524
            $element = new ReportPdfCell(0, 10, 0, 'C', '', 'genby', 1, ReportBaseElement::CURRENT_POSITION, ReportBaseElement::CURRENT_POSITION, 0, 0, '', '', true);
525
            $element->addText($this->generated_by);
526
            $element->setUrl(Webtrees::URL);
527
            $this->addFooter($element);
528
        }
529
    }
530
531
    /**
532
     * Add an element.
533
     *
534
     * @param ReportBaseElement|string $element
535
     *
536
     * @return void
537
     */
538
    public function addElement($element): void
539
    {
540
        if ($this->processing === 'B') {
541
            $this->addBody($element);
542
543
            return;
544
        }
545
546
        if ($this->processing === 'H') {
547
            $this->addHeader($element);
548
549
            return;
550
        }
551
552
        if ($this->processing === 'F') {
553
            $this->addFooter($element);
554
555
            return;
556
        }
557
    }
558
559
    /**
560
     * Run the report.
561
     *
562
     * @return void
563
     */
564
    public function run(): void
565
    {
566
        $this->body();
567
        echo $this->tcpdf->Output('doc.pdf', 'S');
568
    }
569
570
    /**
571
     * Create a new Cell object.
572
     *
573
     * @param int    $width   cell width (expressed in points)
574
     * @param int    $height  cell height (expressed in points)
575
     * @param mixed  $border  Border style
576
     * @param string $align   Text alignement
577
     * @param string $bgcolor Background color code
578
     * @param string $style   The name of the text style
579
     * @param int    $ln      Indicates where the current position should go after the call
580
     * @param mixed  $top     Y-position
581
     * @param mixed  $left    X-position
582
     * @param int    $fill    Indicates if the cell background must be painted (1) or transparent (0). Default value: 1
583
     * @param int    $stretch Stretch carachter mode
584
     * @param string $bocolor Border color
585
     * @param string $tcolor  Text color
586
     * @param bool   $reseth
587
     *
588
     * @return ReportBaseCell
589
     */
590
    public function createCell($width, $height, $border, $align, $bgcolor, $style, $ln, $top, $left, $fill, $stretch, $bocolor, $tcolor, $reseth): ReportBaseCell
591
    {
592
        return new ReportPdfCell($width, $height, $border, $align, $bgcolor, $style, $ln, $top, $left, $fill, $stretch, $bocolor, $tcolor, $reseth);
593
    }
594
595
    /**
596
     * Create a new TextBox object.
597
     *
598
     * @param float  $width   Text box width
599
     * @param float  $height  Text box height
600
     * @param bool   $border
601
     * @param string $bgcolor Background color code in HTML
602
     * @param bool   $newline
603
     * @param float  $left
604
     * @param float  $top
605
     * @param bool   $pagecheck
606
     * @param string $style
607
     * @param bool   $fill
608
     * @param bool   $padding
609
     * @param bool   $reseth
610
     *
611
     * @return ReportBaseTextbox
612
     */
613
    public function createTextBox(
614
        float $width,
615
        float $height,
616
        bool $border,
617
        string $bgcolor,
618
        bool $newline,
619
        float $left,
620
        float $top,
621
        bool $pagecheck,
622
        string $style,
623
        bool $fill,
624
        bool $padding,
625
        bool $reseth
626
    ): ReportBaseTextbox {
627
        return new ReportPdfTextBox($width, $height, $border, $bgcolor, $newline, $left, $top, $pagecheck, $style, $fill, $padding, $reseth);
628
    }
629
630
    /**
631
     * Create a text element.
632
     *
633
     * @param string $style
634
     * @param string $color
635
     *
636
     * @return ReportBaseText
637
     */
638
    public function createText(string $style, string $color): ReportBaseText
639
    {
640
        return new ReportPdfText($style, $color);
641
    }
642
643
    /**
644
     * Create a new Footnote object.
645
     *
646
     * @param string $style Style name
647
     *
648
     * @return ReportBaseFootnote
649
     */
650
    public function createFootnote($style): ReportBaseFootnote
651
    {
652
        return new ReportPdfFootnote($style);
653
    }
654
655
    /**
656
     * Create a new Page Header object
657
     *
658
     * @return ReportBasePageHeader
659
     */
660
    public function createPageHeader(): ReportBasePageHeader
661
    {
662
        return new ReportPdfPageHeader();
663
    }
664
665
    /**
666
     * Create a new image object.
667
     *
668
     * @param string $file  Filename
669
     * @param float  $x
670
     * @param float  $y
671
     * @param float  $w     Image width
672
     * @param float  $h     Image height
673
     * @param string $align L:left, C:center, R:right or empty to use x/y
674
     * @param string $ln    T:same line, N:next line
675
     *
676
     * @return ReportBaseImage
677
     */
678
    public function createImage(string $file, float $x, float $y, float $w, float $h, string $align, string $ln): ReportBaseImage
679
    {
680
        return new ReportPdfImage($file, $x, $y, $w, $h, $align, $ln);
681
    }
682
683
    /**
684
     * Create a new image object from Media Object.
685
     *
686
     * @param MediaFile           $media_file
687
     * @param float               $x
688
     * @param float               $y
689
     * @param float               $w     Image width
690
     * @param float               $h     Image height
691
     * @param string              $align L:left, C:center, R:right or empty to use x/y
692
     * @param string              $ln    T:same line, N:next line
693
     * @param FilesystemInterface $data_filesystem
694
     *
695
     * @return ReportBaseImage
696
     */
697
    public function createImageFromObject(
698
        MediaFile $media_file,
699
        float $x,
700
        float $y,
701
        float $w,
702
        float $h,
703
        string $align,
704
        string $ln,
705
        FilesystemInterface $data_filesystem
706
    ): ReportBaseImage {
707
        return new ReportPdfImage('@' . $media_file->fileContents($data_filesystem), $x, $y, $w, $h, $align, $ln);
708
    }
709
710
    /**
711
     * Create a line.
712
     *
713
     * @param float $x1
714
     * @param float $y1
715
     * @param float $x2
716
     * @param float $y2
717
     *
718
     * @return ReportBaseLine
719
     */
720
    public function createLine(float $x1, float $y1, float $x2, float $y2): ReportBaseLine
721
    {
722
        return new ReportPdfLine($x1, $y1, $x2, $y2);
723
    }
724
725
    /**
726
     * Create an HTML element.
727
     *
728
     * @param string   $tag
729
     * @param string[] $attrs
730
     *
731
     * @return ReportBaseHtml
732
     */
733
    public function createHTML(string $tag, array $attrs): ReportBaseHtml
734
    {
735
        return new ReportPdfHtml($tag, $attrs);
736
    }
737
}
738