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