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