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