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