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