xref: /webtrees/app/Report/ReportPdfTextBox.php (revision b11cdcd45131b1585d66693fab363cfeb18c51a4)
1<?php
2
3/**
4 * webtrees: online genealogy
5 * 'Copyright (C) 2023 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
18declare(strict_types=1);
19
20namespace Fisharebest\Webtrees\Report;
21
22use function count;
23use function hexdec;
24use function is_array;
25use function is_object;
26use function ksort;
27use function preg_match;
28use function str_replace;
29use function trim;
30
31/**
32 * Class ReportPdfTextBox
33 */
34class ReportPdfTextBox extends ReportBaseTextbox
35{
36    /**
37     * PDF Text Box renderer
38     *
39     * @param PdfRenderer $renderer
40     *
41     * @return void
42     */
43    public function render($renderer): void
44    {
45        $newelements      = [];
46        $lastelement      = '';
47        $footnote_element = [];
48        // Element counter
49        $cE = count($this->elements);
50        //-- collapse duplicate elements
51        for ($i = 0; $i < $cE; $i++) {
52            $element = $this->elements[$i];
53            if ($element instanceof ReportBaseElement) {
54                if ($element instanceof ReportBaseText) {
55                    ksort($footnote_element);
56                    foreach ($footnote_element as $links) {
57                        $newelements[] = $links;
58                    }
59                    $footnote_element = [];
60                    if (empty($lastelement)) {
61                        $lastelement = $element;
62                    } elseif ($element->getStyleName() === $lastelement->getStyleName()) {
63                        // Checking if the Text has the same style
64                        $lastelement->addText(str_replace("\n", '<br>', $element->getValue()));
65                    } else {
66                        $newelements[] = $lastelement;
67                        $lastelement   = $element;
68                    }
69                } elseif ($element instanceof ReportPdfFootnote) {
70                    // Check if the Footnote has been set with it’s link number
71                    $renderer->checkFootnote($element);
72                    // Save first the last element if any
73                    if (!empty($lastelement)) {
74                        $newelements[] = $lastelement;
75                        $lastelement   = [];
76                    }
77                    // Save the Footnote with it’s link number as key for sorting later
78                    $footnote_element[$element->num] = $element;
79                } elseif (!$element instanceof ReportPdfFootnote || trim($element->getValue()) !== '') {
80                    // Do not keep empty footnotes
81                    if (!empty($footnote_element)) {
82                        ksort($footnote_element);
83                        foreach ($footnote_element as $links) {
84                            $newelements[] = $links;
85                        }
86                        $footnote_element = [];
87                    }
88                    if (!empty($lastelement)) {
89                        $newelements[] = $lastelement;
90                        $lastelement   = [];
91                    }
92                    $newelements[] = $element;
93                }
94            } else {
95                if (!empty($lastelement)) {
96                    $newelements[] = $lastelement;
97                    $lastelement   = [];
98                }
99                if (!empty($footnote_element)) {
100                    ksort($footnote_element);
101                    foreach ($footnote_element as $links) {
102                        $newelements[] = $links;
103                    }
104                    $footnote_element = [];
105                }
106                $newelements[] = $element;
107            }
108        }
109        if (!empty($lastelement)) {
110            $newelements[] = $lastelement;
111        }
112        if (!empty($footnote_element)) {
113            ksort($footnote_element);
114            foreach ($footnote_element as $links) {
115                $newelements[] = $links;
116            }
117        }
118        $this->elements = $newelements;
119        unset($footnote_element, $lastelement, $links, $newelements);
120
121        // Used with line breaks and cell height calculation within this box
122        $renderer->largestFontHeight = 0;
123
124        // If current position (left)
125        if ($this->left === ReportBaseElement::CURRENT_POSITION) {
126            $cX = $renderer->tcpdf->GetX();
127        } else {
128            // For static position add margin (returns and updates X)
129            $cX = $renderer->addMarginX($this->left);
130        }
131
132        // If current position (top)
133        if ($this->top === ReportBaseElement::CURRENT_POSITION) {
134            $cY = $renderer->tcpdf->GetY();
135        } else {
136            $cY = $this->top;
137            $renderer->tcpdf->setY($cY);
138        }
139
140        // Check the width if set to page wide OR set by xml to larger then page width (margin)
141        if ($this->width === 0.0 || $this->width > $renderer->getRemainingWidthPDF()) {
142            $cW = $renderer->getRemainingWidthPDF();
143        } else {
144            $cW = $this->width;
145        }
146
147        // Save the original margins
148        $cM = $renderer->tcpdf->getMargins();
149        // Use cell padding to wrap the width
150        // Temp Width with cell padding
151        if (is_array($cM['cell'])) {
152            $cWT = $cW - ($cM['padding_left'] + $cM['padding_right']);
153        } else {
154            $cWT = $cW - $cM['cell'] * 2;
155        }
156        // Element height (except text)
157        $eH = 0.0;
158        $w  = 0;
159        // Temp Height
160        $cHT = 0;
161        //-- $lw is an array
162        // 0 => last line width
163        // 1 => 1 if text was wrapped, 0 if text did not wrap
164        // 2 => number of LF
165        $lw = [];
166        // Element counter
167        $cE = count($this->elements);
168        //-- calculate the text box height + width
169        for ($i = 0; $i < $cE; $i++) {
170            if (is_object($this->elements[$i])) {
171                $ew = $this->elements[$i]->setWrapWidth($cWT - $w, $cWT);
172                if ($ew === $cWT) {
173                    $w = 0;
174                }
175                $lw = $this->elements[$i]->getWidth($renderer);
176                // Text is already gets the # LF
177                $cHT += $lw[2];
178                if ($lw[1] === 1) {
179                    $w = $lw[0];
180                } elseif ($lw[1] === 2) {
181                    $w = 0;
182                } else {
183                    $w += $lw[0];
184                }
185                if ($w > $cWT) {
186                    $w = $lw[0];
187                }
188                // Footnote is at the bottom of the page. No need to calculate it’s height or wrap the text!
189                // We are changing the margins anyway!
190                // For anything else but text (images), get the height
191                $eH += $this->elements[$i]->getHeight($renderer);
192            }
193        }
194
195        // Add up what’s the final height
196        $cH = $this->height;
197        // If any element exist
198        if ($cE > 0) {
199            // Check if this is text or some other element, like images
200            if ($eH === 0.0) {
201                // This is text elements. Number of LF but at least one line
202                $cHT = ($cHT + 1) * $renderer->tcpdf->getCellHeightRatio();
203                // Calculate the cell hight with the largest font size used within this Box
204                $cHT *= $renderer->largestFontHeight;
205                // Add cell padding
206                if ($this->padding) {
207                    if (is_array($cM['cell'])) {
208                        $cHT += $cM['padding_bottom'] + $cM['padding_top'];
209                    } else {
210                        $cHT += $cM['cell'] * 2;
211                    }
212                }
213                if ($cH < $cHT) {
214                    $cH = $cHT;
215                }
216            } elseif ($cH < $eH) {
217                // This is any other element
218                $cH = $eH;
219            }
220        }
221        // Finaly, check the last cells height
222        if ($cH < $renderer->lastCellHeight) {
223            $cH = $renderer->lastCellHeight;
224        }
225        // Add a new page if needed
226        if ($this->pagecheck) {
227            // Reset last cell height or Header/Footer will inherit it, in case of pagebreak
228            $renderer->lastCellHeight = 0;
229            if ($renderer->checkPageBreakPDF($cH)) {
230                $cY = $renderer->tcpdf->GetY();
231            }
232        }
233
234        // Setup the border and background color
235        $cS = ''; // Class Style
236        if ($this->border) {
237            $cS = 'D';
238        } // D or empty string: Draw (default)
239        $match = [];
240        // Fill the background
241        if ($this->fill) {
242            if (preg_match('/#?(..)(..)(..)/', $this->bgcolor, $match)) {
243                $cS .= 'F'; // F: Fill the background
244                $r  = hexdec($match[1]);
245                $g  = hexdec($match[2]);
246                $b  = hexdec($match[3]);
247                $renderer->tcpdf->setFillColor($r, $g, $b);
248            }
249        }
250        // Clean up a bit
251        unset($lw, $w, $match, $cE, $eH);
252        // Draw the border
253        if (!empty($cS)) {
254            if (!$renderer->tcpdf->getRTL()) {
255                $cXM = $cX;
256            } else {
257                $cXM = $renderer->tcpdf->getPageWidth() - $cX - $cW;
258            }
259            $renderer->tcpdf->Rect($cXM, $cY, $cW, $cH, $cS);
260        }
261        // Add cell padding if set and if any text (element) exist
262        if ($this->padding) {
263            if ($cHT > 0) {
264                if (is_array($cM['cell'])) {
265                    $renderer->tcpdf->setY($cY + $cM['padding_top']);
266                } else {
267                    $renderer->tcpdf->setY($cY + $cM['cell']);
268                }
269            }
270        }
271        // Change the margins X, Width
272        if (!$renderer->tcpdf->getRTL()) {
273            if ($this->padding) {
274                if (is_array($cM['cell'])) {
275                    $renderer->tcpdf->setLeftMargin($cX + $cM['padding_left']);
276                } else {
277                    $renderer->tcpdf->setLeftMargin($cX + $cM['cell']);
278                }
279            } else {
280                $renderer->tcpdf->setLeftMargin($cX);
281            }
282            $renderer->tcpdf->setRightMargin($renderer->getRemainingWidthPDF() - $cW + $cM['right']);
283        } elseif ($this->padding) {
284            if (is_array($cM['cell'])) {
285                $renderer->tcpdf->setRightMargin($cX + $cM['padding_right']);
286            } else {
287                $renderer->tcpdf->setRightMargin($cX + $cM['cell']);
288            }
289            $renderer->tcpdf->setLeftMargin($renderer->getRemainingWidthPDF() - $cW + $cM['left']);
290        } else {
291            $renderer->tcpdf->setRightMargin($cX);
292            $renderer->tcpdf->setLeftMargin($renderer->getRemainingWidthPDF() - $cW + $cM['left']);
293        }
294        // Save the current page number
295        $cPN = $renderer->tcpdf->getPage();
296
297        // Render the elements (write text, print picture...)
298        foreach ($this->elements as $element) {
299            if ($element instanceof ReportBaseElement) {
300                $element->render($renderer);
301            } elseif ($element === 'footnotetexts') {
302                $renderer->footnotes();
303            } elseif ($element === 'addpage') {
304                $renderer->newPage();
305            }
306        }
307        // Restore the margins
308        $renderer->tcpdf->setLeftMargin($cM['left']);
309        $renderer->tcpdf->setRightMargin($cM['right']);
310
311        // This will be mostly used to trick the multiple images last height
312        if ($this->reseth) {
313            $cH = 0;
314            // This can only happen with multiple images and with pagebreak
315            if ($cPN !== $renderer->tcpdf->getPage()) {
316                $renderer->tcpdf->setPage($cPN);
317            }
318        }
319        // New line and some clean up
320        if (!$this->newline) {
321            $renderer->tcpdf->setXY($cX + $cW, $cY);
322            $renderer->lastCellHeight = $cH;
323        } else {
324            // addMarginX() also updates X
325            $renderer->addMarginX(0);
326            $renderer->tcpdf->setY($cY + $cH);
327            $renderer->lastCellHeight = 0;
328        }
329    }
330}
331