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