Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 39 and 401]

   1  <?php
   2  
   3  /**
   4   * This file is part of FPDI
   5   *
   6   * @package   setasign\Fpdi
   7   * @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
   8   * @license   http://opensource.org/licenses/mit-license The MIT License
   9   */
  10  
  11  namespace setasign\Fpdi;
  12  
  13  /**
  14   * Trait FpdfTplTrait
  15   *
  16   * This class adds a templating feature to tFPDF.
  17   */
  18  trait FpdfTplTrait
  19  {
  20      /**
  21       * Data of all created templates.
  22       *
  23       * @var array
  24       */
  25      protected $templates = [];
  26  
  27      /**
  28       * The template id for the currently created template.
  29       *
  30       * @var null|int
  31       */
  32      protected $currentTemplateId;
  33  
  34      /**
  35       * A counter for template ids.
  36       *
  37       * @var int
  38       */
  39      protected $templateId = 0;
  40  
  41      /**
  42       * Set the page format of the current page.
  43       *
  44       * @param array $size An array with two values defining the size.
  45       * @param string $orientation "L" for landscape, "P" for portrait.
  46       * @throws \BadMethodCallException
  47       */
  48      public function setPageFormat($size, $orientation)
  49      {
  50          if ($this->currentTemplateId !== null) {
  51              throw new \BadMethodCallException('The page format cannot be changed when writing to a template.');
  52          }
  53  
  54          if (!\in_array($orientation, ['P', 'L'], true)) {
  55              throw new \InvalidArgumentException(\sprintf(
  56                  'Invalid page orientation "%s"! Only "P" and "L" are allowed!',
  57                  $orientation
  58              ));
  59          }
  60  
  61          $size = $this->_getpagesize($size);
  62  
  63          if (
  64              $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 PhpConditionAlreadyCheckedInspection */
 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 ($this->currentTemplateId === null) {
 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(
 422                  '/BBox[0 0 %.2F %.2F]',
 423                  $template['width'] * $this->k,
 424                  $template['height'] * $this->k
 425              ));
 426              $this->_put('/Resources 2 0 R'); // default resources dictionary of FPDF
 427  
 428              if ($this->compress) {
 429                  $buffer = \gzcompress($template['buffer']);
 430                  $this->_put('/Filter/FlateDecode');
 431              } else {
 432                  $buffer = $template['buffer'];
 433              }
 434  
 435              $this->_put('/Length ' . \strlen($buffer));
 436  
 437              if ($template['groupXObject']) {
 438                  $this->_put('/Group <</Type/Group/S/Transparency>>');
 439              }
 440  
 441              $this->_put('>>');
 442              $this->_putstream($buffer);
 443              $this->_put('endobj');
 444          }
 445      }
 446  
 447      /**
 448       * @inheritdoc
 449       */
 450      protected function _putxobjectdict()
 451      {
 452          foreach ($this->templates as $key => $template) {
 453              $this->_put('/' . $template['id'] . ' ' . $template['objectNumber'] . ' 0 R');
 454          }
 455  
 456          parent::_putxobjectdict();
 457      }
 458  
 459      /**
 460       * @inheritdoc
 461       */
 462      public function _out($s)
 463      {
 464          if ($this->currentTemplateId !== null) {
 465              $this->templates[$this->currentTemplateId]['buffer'] .= $s . "\n";
 466          } else {
 467              parent::_out($s);
 468          }
 469      }
 470  }