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