1<?php 2/** 3 * This file is part of FPDI 4 * 5 * @package setasign\Fpdi 6 * @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com) 7 * @license http://opensource.org/licenses/mit-license The MIT License 8 */ 9 10namespace setasign\Fpdi; 11 12/** 13 * Trait FpdfTplTrait 14 * 15 * This class adds a templating feature to tFPDF. 16 * 17 * @package setasign\Fpdi 18 */ 19trait FpdfTplTrait 20{ 21 /** 22 * Data of all created templates. 23 * 24 * @var array 25 */ 26 protected $templates = []; 27 28 /** 29 * The template id for the currently created template. 30 * 31 * @var null|int 32 */ 33 protected $currentTemplateId; 34 35 /** 36 * A counter for template ids. 37 * 38 * @var int 39 */ 40 protected $templateId = 0; 41 42 /** 43 * Set the page format of the current page. 44 * 45 * @param array $size An array with two values defining the size. 46 * @param string $orientation "L" for landscape, "P" for portrait. 47 * @throws \BadMethodCallException 48 */ 49 public function setPageFormat($size, $orientation) 50 { 51 if ($this->currentTemplateId !== null) { 52 throw new \BadMethodCallException('The page format cannot be changed when writing to a template.'); 53 } 54 55 if (!\in_array($orientation, ['P', 'L'], true)) { 56 throw new \InvalidArgumentException(\sprintf( 57 'Invalid page orientation "%s"! Only "P" and "L" are allowed!', 58 $orientation 59 )); 60 } 61 62 $size = $this->_getpagesize($size); 63 64 if ($orientation != $this->CurOrientation 65 || $size[0] != $this->CurPageSize[0] 66 || $size[1] != $this->CurPageSize[1] 67 ) { 68 // New size or orientation 69 if ($orientation === 'P') { 70 $this->w = $size[0]; 71 $this->h = $size[1]; 72 } else { 73 $this->w = $size[1]; 74 $this->h = $size[0]; 75 } 76 $this->wPt = $this->w * $this->k; 77 $this->hPt = $this->h * $this->k; 78 $this->PageBreakTrigger = $this->h - $this->bMargin; 79 $this->CurOrientation = $orientation; 80 $this->CurPageSize = $size; 81 82 $this->PageInfo[$this->page]['size'] = array($this->wPt, $this->hPt); 83 } 84 } 85 86 /** 87 * Draws a template onto the page or another template. 88 * 89 * Give only one of the size parameters (width, height) to calculate the other one automatically in view to the 90 * aspect ratio. 91 * 92 * @param mixed $tpl The template id 93 * @param array|float|int $x The abscissa of upper-left corner. Alternatively you could use an assoc array 94 * with the keys "x", "y", "width", "height", "adjustPageSize". 95 * @param float|int $y The ordinate of upper-left corner. 96 * @param float|int|null $width The width. 97 * @param float|int|null $height The height. 98 * @param bool $adjustPageSize 99 * @return array The size 100 * @see FpdfTplTrait::getTemplateSize() 101 */ 102 public function useTemplate($tpl, $x = 0, $y = 0, $width = null, $height = null, $adjustPageSize = false) 103 { 104 if (!isset($this->templates[$tpl])) { 105 throw new \InvalidArgumentException('Template does not exist!'); 106 } 107 108 if (\is_array($x)) { 109 unset($x['tpl']); 110 \extract($x, EXTR_IF_EXISTS); 111 /** @noinspection NotOptimalIfConditionsInspection */ 112 /** @noinspection CallableParameterUseCaseInTypeContextInspection */ 113 if (\is_array($x)) { 114 $x = 0; 115 } 116 } 117 118 $template = $this->templates[$tpl]; 119 120 $originalSize = $this->getTemplateSize($tpl); 121 $newSize = $this->getTemplateSize($tpl, $width, $height); 122 if ($adjustPageSize) { 123 $this->setPageFormat($newSize, $newSize['orientation']); 124 } 125 126 $this->_out( 127 // reset standard values, translate and scale 128 \sprintf( 129 'q 0 J 1 w 0 j 0 G 0 g %.4F 0 0 %.4F %.4F %.4F cm /%s Do Q', 130 ($newSize['width'] / $originalSize['width']), 131 ($newSize['height'] / $originalSize['height']), 132 $x * $this->k, 133 ($this->h - $y - $newSize['height']) * $this->k, 134 $template['id'] 135 ) 136 ); 137 138 return $newSize; 139 } 140 141 /** 142 * Get the size of a template. 143 * 144 * Give only one of the size parameters (width, height) to calculate the other one automatically in view to the 145 * aspect ratio. 146 * 147 * @param mixed $tpl The template id 148 * @param float|int|null $width The width. 149 * @param float|int|null $height The height. 150 * @return array|bool An array with following keys: width, height, 0 (=width), 1 (=height), orientation (L or P) 151 */ 152 public function getTemplateSize($tpl, $width = null, $height = null) 153 { 154 if (!isset($this->templates[$tpl])) { 155 return false; 156 } 157 158 if ($width === null && $height === null) { 159 $width = $this->templates[$tpl]['width']; 160 $height = $this->templates[$tpl]['height']; 161 } elseif ($width === null) { 162 $width = $height * $this->templates[$tpl]['width'] / $this->templates[$tpl]['height']; 163 } 164 165 if ($height === null) { 166 $height = $width * $this->templates[$tpl]['height'] / $this->templates[$tpl]['width']; 167 } 168 169 if ($height <= 0. || $width <= 0.) { 170 throw new \InvalidArgumentException('Width or height parameter needs to be larger than zero.'); 171 } 172 173 return [ 174 'width' => $width, 175 'height' => $height, 176 0 => $width, 177 1 => $height, 178 'orientation' => $width > $height ? 'L' : 'P' 179 ]; 180 } 181 182 /** 183 * Begins a new template. 184 * 185 * @param float|int|null $width The width of the template. If null, the current page width is used. 186 * @param float|int|null $height The height of the template. If null, the current page height is used. 187 * @param bool $groupXObject Define the form XObject as a group XObject to support transparency (if used). 188 * @return int A template identifier. 189 */ 190 public function beginTemplate($width = null, $height = null, $groupXObject = false) 191 { 192 if ($width === null) { 193 $width = $this->w; 194 } 195 196 if ($height === null) { 197 $height = $this->h; 198 } 199 200 $templateId = $this->getNextTemplateId(); 201 202 // initiate buffer with current state of FPDF 203 $buffer = "2 J\n" 204 . \sprintf('%.2F w', $this->LineWidth * $this->k) . "\n"; 205 206 if ($this->FontFamily) { 207 $buffer .= \sprintf("BT /F%d %.2F Tf ET\n", $this->CurrentFont['i'], $this->FontSizePt); 208 } 209 210 if ($this->DrawColor !== '0 G') { 211 $buffer .= $this->DrawColor . "\n"; 212 } 213 if ($this->FillColor !== '0 g') { 214 $buffer .= $this->FillColor . "\n"; 215 } 216 217 if ($groupXObject && \version_compare('1.4', $this->PDFVersion, '>')) { 218 $this->PDFVersion = '1.4'; 219 } 220 221 $this->templates[$templateId] = [ 222 'objectNumber' => null, 223 'id' => 'TPL' . $templateId, 224 'buffer' => $buffer, 225 'width' => $width, 226 'height' => $height, 227 'groupXObject' => $groupXObject, 228 'state' => [ 229 'x' => $this->x, 230 'y' => $this->y, 231 'AutoPageBreak' => $this->AutoPageBreak, 232 'bMargin' => $this->bMargin, 233 'tMargin' => $this->tMargin, 234 'lMargin' => $this->lMargin, 235 'rMargin' => $this->rMargin, 236 'h' => $this->h, 237 'w' => $this->w, 238 'FontFamily' => $this->FontFamily, 239 'FontStyle' => $this->FontStyle, 240 'FontSizePt' => $this->FontSizePt, 241 'FontSize' => $this->FontSize, 242 'underline' => $this->underline, 243 'TextColor' => $this->TextColor, 244 'DrawColor' => $this->DrawColor, 245 'FillColor' => $this->FillColor, 246 'ColorFlag' => $this->ColorFlag 247 ] 248 ]; 249 250 $this->SetAutoPageBreak(false); 251 $this->currentTemplateId = $templateId; 252 253 $this->h = $height; 254 $this->w = $width; 255 256 $this->SetXY($this->lMargin, $this->tMargin); 257 $this->SetRightMargin($this->w - $width + $this->rMargin); 258 259 return $templateId; 260 } 261 262 /** 263 * Ends a template. 264 * 265 * @return bool|int|null A template identifier. 266 */ 267 public function endTemplate() 268 { 269 if (null === $this->currentTemplateId) { 270 return false; 271 } 272 273 $templateId = $this->currentTemplateId; 274 $template = $this->templates[$templateId]; 275 276 $state = $template['state']; 277 $this->SetXY($state['x'], $state['y']); 278 $this->tMargin = $state['tMargin']; 279 $this->lMargin = $state['lMargin']; 280 $this->rMargin = $state['rMargin']; 281 $this->h = $state['h']; 282 $this->w = $state['w']; 283 $this->SetAutoPageBreak($state['AutoPageBreak'], $state['bMargin']); 284 285 $this->FontFamily = $state['FontFamily']; 286 $this->FontStyle = $state['FontStyle']; 287 $this->FontSizePt = $state['FontSizePt']; 288 $this->FontSize = $state['FontSize']; 289 290 $this->TextColor = $state['TextColor']; 291 $this->DrawColor = $state['DrawColor']; 292 $this->FillColor = $state['FillColor']; 293 $this->ColorFlag = $state['ColorFlag']; 294 295 $this->underline = $state['underline']; 296 297 $fontKey = $this->FontFamily . $this->FontStyle; 298 if ($fontKey) { 299 $this->CurrentFont =& $this->fonts[$fontKey]; 300 } else { 301 unset($this->CurrentFont); 302 } 303 304 $this->currentTemplateId = null; 305 306 return $templateId; 307 } 308 309 /** 310 * Get the next template id. 311 * 312 * @return int 313 */ 314 protected function getNextTemplateId() 315 { 316 return $this->templateId++; 317 } 318 319 /* overwritten FPDF methods: */ 320 321 /** 322 * @inheritdoc 323 */ 324 public function AddPage($orientation = '', $size = '', $rotation = 0) 325 { 326 if ($this->currentTemplateId !== null) { 327 throw new \BadMethodCallException('Pages cannot be added when writing to a template.'); 328 } 329 parent::AddPage($orientation, $size, $rotation); 330 } 331 332 /** 333 * @inheritdoc 334 */ 335 public function Link($x, $y, $w, $h, $link) 336 { 337 if ($this->currentTemplateId !== null) { 338 throw new \BadMethodCallException('Links cannot be set when writing to a template.'); 339 } 340 parent::Link($x, $y, $w, $h, $link); 341 } 342 343 /** 344 * @inheritdoc 345 */ 346 public function SetLink($link, $y = 0, $page = -1) 347 { 348 if ($this->currentTemplateId !== null) { 349 throw new \BadMethodCallException('Links cannot be set when writing to a template.'); 350 } 351 return parent::SetLink($link, $y, $page); 352 } 353 354 /** 355 * @inheritdoc 356 */ 357 public function SetDrawColor($r, $g = null, $b = null) 358 { 359 parent::SetDrawColor($r, $g, $b); 360 if ($this->page === 0 && $this->currentTemplateId !== null) { 361 $this->_out($this->DrawColor); 362 } 363 } 364 365 /** 366 * @inheritdoc 367 */ 368 public function SetFillColor($r, $g = null, $b = null) 369 { 370 parent::SetFillColor($r, $g, $b); 371 if ($this->page === 0 && $this->currentTemplateId !== null) { 372 $this->_out($this->FillColor); 373 } 374 } 375 376 /** 377 * @inheritdoc 378 */ 379 public function SetLineWidth($width) 380 { 381 parent::SetLineWidth($width); 382 if ($this->page === 0 && $this->currentTemplateId !== null) { 383 $this->_out(\sprintf('%.2F w', $width * $this->k)); 384 } 385 } 386 387 /** 388 * @inheritdoc 389 */ 390 public function SetFont($family, $style = '', $size = 0) 391 { 392 parent::SetFont($family, $style, $size); 393 if ($this->page === 0 && $this->currentTemplateId !== null) { 394 $this->_out(\sprintf('BT /F%d %.2F Tf ET', $this->CurrentFont['i'], $this->FontSizePt)); 395 } 396 } 397 398 /** 399 * @inheritdoc 400 */ 401 public function SetFontSize($size) 402 { 403 parent::SetFontSize($size); 404 if ($this->page === 0 && $this->currentTemplateId !== null) { 405 $this->_out(sprintf('BT /F%d %.2F Tf ET', $this->CurrentFont['i'], $this->FontSizePt)); 406 } 407 } 408 409 /** 410 * @inheritdoc 411 */ 412 protected function _putimages() 413 { 414 parent::_putimages(); 415 416 foreach ($this->templates as $key => $template) { 417 $this->_newobj(); 418 $this->templates[$key]['objectNumber'] = $this->n; 419 420 $this->_put('<</Type /XObject /Subtype /Form /FormType 1'); 421 $this->_put(\sprintf('/BBox[0 0 %.2F %.2F]', $template['width'] * $this->k, $template['height'] * $this->k)); 422 $this->_put('/Resources 2 0 R'); // default resources dictionary of FPDF 423 424 if ($this->compress) { 425 $buffer = \gzcompress($template['buffer']); 426 $this->_put('/Filter/FlateDecode'); 427 } else { 428 $buffer = $template['buffer']; 429 } 430 431 $this->_put('/Length ' . \strlen($buffer)); 432 433 if ($template['groupXObject']) { 434 $this->_put('/Group <</Type/Group/S/Transparency>>'); 435 } 436 437 $this->_put('>>'); 438 $this->_putstream($buffer); 439 $this->_put('endobj'); 440 } 441 } 442 443 /** 444 * @inheritdoc 445 */ 446 protected function _putxobjectdict() 447 { 448 foreach ($this->templates as $key => $template) { 449 $this->_put('/' . $template['id'] . ' ' . $template['objectNumber'] . ' 0 R'); 450 } 451 452 parent::_putxobjectdict(); 453 } 454 455 /** 456 * @inheritdoc 457 */ 458 public function _out($s) 459 { 460 if ($this->currentTemplateId !== null) { 461 $this->templates[$this->currentTemplateId]['buffer'] .= $s . "\n"; 462 } else { 463 parent::_out($s); 464 } 465 } 466}