Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403]

   1  <?php
   2  /**
   3   * This file is part of FPDI
   4   *
   5   * @package   setasign\Fpdi
   6   * @copyright Copyright (c) 2019 Setasign - Jan Slabon (https://www.setasign.com)
   7   * @license   http://opensource.org/licenses/mit-license The MIT License
   8   */
   9  
  10  namespace setasign\Fpdi;
  11  
  12  /**
  13   * Trait FpdfTplTrait
  14   *
  15   * This class adds a templating feature to tFPDF.
  16   *
  17   * @package setasign\Fpdi
  18   */
  19  trait 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  }