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