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