1<?php
2/**
3 * This file is part of FPDI
4 *
5 * @package   FPDI
6 * @copyright Copyright (c) 2015 Setasign - Jan Slabon (http://www.setasign.com)
7 * @license   http://opensource.org/licenses/mit-license The MIT License
8 * @version   1.6.1
9 */
10
11if (!class_exists('fpdi_bridge')) {
12    require_once('fpdi_bridge.php');
13}
14
15/**
16 * Class FPDF_TPL
17 */
18class FPDF_TPL extends fpdi_bridge
19{
20    /**
21     * Array of template data
22     *
23     * @var array
24     */
25    protected $_tpls = array();
26
27    /**
28     * Current Template-Id
29     *
30     * @var int
31     */
32    public $tpl = 0;
33
34    /**
35     * "In Template"-Flag
36     *
37     * @var boolean
38     */
39    protected $_inTpl = false;
40
41    /**
42     * Name prefix of templates used in Resources dictionary
43     *
44     * @var string A String defining the Prefix used as Template-Object-Names. Have to begin with an /
45     */
46    public $tplPrefix = "/TPL";
47
48    /**
49     * Resources used by templates and pages
50     *
51     * @var array
52     */
53    protected  $_res = array();
54
55    /**
56     * Last used template data
57     *
58     * @var array
59     */
60    public $lastUsedTemplateData = array();
61
62    /**
63     * Start a template.
64     *
65     * This method starts a template. You can give own coordinates to build an own sized
66     * template. Pay attention, that the margins are adapted to the new template size.
67     * If you want to write outside the template, for example to build a clipped template,
68     * you have to set the margins and "cursor"-position manual after beginTemplate()-call.
69     *
70     * If no parameter is given, the template uses the current page-size.
71     * The method returns an id of the current template. This id is used later for using this template.
72     * Warning: A created template is saved in the resulting PDF at all events. Also if you don't use it after creation!
73     *
74     * @param int $x The x-coordinate given in user-unit
75     * @param int $y The y-coordinate given in user-unit
76     * @param int $w The width given in user-unit
77     * @param int $h The height given in user-unit
78     * @return int The id of new created template
79     * @throws LogicException
80     */
81    public function beginTemplate($x = null, $y = null, $w = null, $h = null)
82    {
83        if (is_subclass_of($this, 'TCPDF')) {
84            throw new LogicException('This method is only usable with FPDF. Use TCPDF methods startTemplate() instead.');
85        }
86
87        if ($this->page <= 0) {
88            throw new LogicException("You have to add at least a page first!");
89        }
90
91        if ($x == null)
92            $x = 0;
93        if ($y == null)
94            $y = 0;
95        if ($w == null)
96            $w = $this->w;
97        if ($h == null)
98            $h = $this->h;
99
100        // Save settings
101        $this->tpl++;
102        $tpl =& $this->_tpls[$this->tpl];
103        $tpl = array(
104            'o_x' => $this->x,
105            'o_y' => $this->y,
106            'o_AutoPageBreak' => $this->AutoPageBreak,
107            'o_bMargin' => $this->bMargin,
108            'o_tMargin' => $this->tMargin,
109            'o_lMargin' => $this->lMargin,
110            'o_rMargin' => $this->rMargin,
111            'o_h' => $this->h,
112            'o_w' => $this->w,
113            'o_FontFamily' => $this->FontFamily,
114            'o_FontStyle' => $this->FontStyle,
115            'o_FontSizePt' => $this->FontSizePt,
116            'o_FontSize' => $this->FontSize,
117            'buffer' => '',
118            'x' => $x,
119            'y' => $y,
120            'w' => $w,
121            'h' => $h
122        );
123
124        $this->SetAutoPageBreak(false);
125
126        // Define own high and width to calculate correct positions
127        $this->h = $h;
128        $this->w = $w;
129
130        $this->_inTpl = true;
131        $this->SetXY($x + $this->lMargin, $y + $this->tMargin);
132        $this->SetRightMargin($this->w - $w + $this->rMargin);
133
134        if ($this->CurrentFont) {
135            $fontKey = $this->FontFamily . $this->FontStyle;
136            if ($fontKey) {
137                $this->_res['tpl'][$this->tpl]['fonts'][$fontKey] =& $this->fonts[$fontKey];
138                $this->_out(sprintf('BT /F%d %.2F Tf ET', $this->CurrentFont['i'], $this->FontSizePt));
139            }
140        }
141
142        return $this->tpl;
143    }
144
145    /**
146     * End template.
147     *
148     * This method ends a template and reset initiated variables collected in {@link beginTemplate()}.
149     *
150     * @return int|boolean If a template is opened, the id is returned. If not a false is returned.
151     */
152    public function endTemplate()
153    {
154        if (is_subclass_of($this, 'TCPDF')) {
155            $args = func_get_args();
156            return call_user_func_array(array($this, 'TCPDF::endTemplate'), $args);
157        }
158
159        if ($this->_inTpl) {
160            $this->_inTpl = false;
161            $tpl = $this->_tpls[$this->tpl];
162            $this->SetXY($tpl['o_x'], $tpl['o_y']);
163            $this->tMargin = $tpl['o_tMargin'];
164            $this->lMargin = $tpl['o_lMargin'];
165            $this->rMargin = $tpl['o_rMargin'];
166            $this->h = $tpl['o_h'];
167            $this->w = $tpl['o_w'];
168            $this->SetAutoPageBreak($tpl['o_AutoPageBreak'], $tpl['o_bMargin']);
169
170            $this->FontFamily = $tpl['o_FontFamily'];
171            $this->FontStyle = $tpl['o_FontStyle'];
172            $this->FontSizePt = $tpl['o_FontSizePt'];
173            $this->FontSize = $tpl['o_FontSize'];
174
175            $fontKey = $this->FontFamily . $this->FontStyle;
176            if ($fontKey)
177                $this->CurrentFont =& $this->fonts[$fontKey];
178
179            return $this->tpl;
180        } else {
181            return false;
182        }
183    }
184
185    /**
186     * Use a template in current page or other template.
187     *
188     * You can use a template in a page or in another template.
189     * You can give the used template a new size.
190     * All parameters are optional. The width or height is calculated automatically
191     * if one is given. If no parameter is given the origin size as defined in
192     * {@link beginTemplate()} method is used.
193     *
194     * The calculated or used width and height are returned as an array.
195     *
196     * @param int $tplIdx A valid template-id
197     * @param int $x The x-position
198     * @param int $y The y-position
199     * @param int $w The new width of the template
200     * @param int $h The new height of the template
201     * @return array The height and width of the template (array('w' => ..., 'h' => ...))
202     * @throws LogicException|InvalidArgumentException
203     */
204    public function useTemplate($tplIdx, $x = null, $y = null, $w = 0, $h = 0)
205    {
206        if ($this->page <= 0) {
207            throw new LogicException('You have to add at least a page first!');
208        }
209
210        if (!isset($this->_tpls[$tplIdx])) {
211            throw new InvalidArgumentException('Template does not exist!');
212        }
213
214        if ($this->_inTpl) {
215            $this->_res['tpl'][$this->tpl]['tpls'][$tplIdx] =& $this->_tpls[$tplIdx];
216        }
217
218        $tpl = $this->_tpls[$tplIdx];
219        $_w = $tpl['w'];
220        $_h = $tpl['h'];
221
222        if ($x == null) {
223            $x = 0;
224        }
225
226        if ($y == null) {
227            $y = 0;
228        }
229
230        $x += $tpl['x'];
231        $y += $tpl['y'];
232
233        $wh = $this->getTemplateSize($tplIdx, $w, $h);
234        $w = $wh['w'];
235        $h = $wh['h'];
236
237        $tplData = array(
238            'x' => $this->x,
239            'y' => $this->y,
240            'w' => $w,
241            'h' => $h,
242            'scaleX' => ($w / $_w),
243            'scaleY' => ($h / $_h),
244            'tx' => $x,
245            'ty' =>  ($this->h - $y - $h),
246            'lty' => ($this->h - $y - $h) - ($this->h - $_h) * ($h / $_h)
247        );
248
249        $this->_out(sprintf('q %.4F 0 0 %.4F %.4F %.4F cm',
250            $tplData['scaleX'], $tplData['scaleY'], $tplData['tx'] * $this->k, $tplData['ty'] * $this->k)
251        ); // Translate
252        $this->_out(sprintf('%s%d Do Q', $this->tplPrefix, $tplIdx));
253
254        $this->lastUsedTemplateData = $tplData;
255
256        return array('w' => $w, 'h' => $h);
257    }
258
259    /**
260     * Get the calculated size of a template.
261     *
262     * If one size is given, this method calculates the other one.
263     *
264     * @param int $tplIdx A valid template-id
265     * @param int $w The width of the template
266     * @param int $h The height of the template
267     * @return array The height and width of the template (array('w' => ..., 'h' => ...))
268     */
269    public function getTemplateSize($tplIdx, $w = 0, $h = 0)
270    {
271        if (!isset($this->_tpls[$tplIdx]))
272            return false;
273
274        $tpl = $this->_tpls[$tplIdx];
275        $_w = $tpl['w'];
276        $_h = $tpl['h'];
277
278        if ($w == 0 && $h == 0) {
279            $w = $_w;
280            $h = $_h;
281        }
282
283        if ($w == 0)
284            $w = $h * $_w / $_h;
285        if($h == 0)
286            $h = $w * $_h / $_w;
287
288        return array("w" => $w, "h" => $h);
289    }
290
291    /**
292     * Sets the font used to print character strings.
293     *
294     * See FPDF/TCPDF documentation.
295     *
296     * @see http://fpdf.org/en/doc/setfont.htm
297     * @see http://www.tcpdf.org/doc/code/classTCPDF.html#afd56e360c43553830d543323e81bc045
298     */
299    public function SetFont($family, $style = '', $size = null, $fontfile = '', $subset = 'default', $out = true)
300    {
301        if (is_subclass_of($this, 'TCPDF')) {
302            $args = func_get_args();
303            return call_user_func_array(array($this, 'TCPDF::SetFont'), $args);
304        }
305
306        parent::SetFont($family, $style, $size);
307
308        $fontkey = $this->FontFamily . $this->FontStyle;
309
310        if ($this->_inTpl) {
311            $this->_res['tpl'][$this->tpl]['fonts'][$fontkey] =& $this->fonts[$fontkey];
312        } else {
313            $this->_res['page'][$this->page]['fonts'][$fontkey] =& $this->fonts[$fontkey];
314        }
315    }
316
317    /**
318     * Puts an image.
319     *
320     * See FPDF/TCPDF documentation.
321     *
322     * @see http://fpdf.org/en/doc/image.htm
323     * @see http://www.tcpdf.org/doc/code/classTCPDF.html#a714c2bee7d6b39d4d6d304540c761352
324     */
325    public function Image(
326        $file, $x = '', $y = '', $w = 0, $h = 0, $type = '', $link = '', $align = '', $resize = false,
327        $dpi = 300, $palign = '', $ismask = false, $imgmask = false, $border = 0, $fitbox = false,
328        $hidden = false, $fitonpage = false, $alt = false, $altimgs = array()
329    )
330    {
331        if (is_subclass_of($this, 'TCPDF')) {
332            $args = func_get_args();
333            return call_user_func_array(array($this, 'TCPDF::Image'), $args);
334        }
335
336        $ret = parent::Image($file, $x, $y, $w, $h, $type, $link);
337        if ($this->_inTpl) {
338            $this->_res['tpl'][$this->tpl]['images'][$file] =& $this->images[$file];
339        } else {
340            $this->_res['page'][$this->page]['images'][$file] =& $this->images[$file];
341        }
342
343        return $ret;
344    }
345
346    /**
347     * Adds a new page to the document.
348     *
349     * See FPDF/TCPDF documentation.
350     *
351     * This method cannot be used if you'd started a template.
352     *
353     * @see http://fpdf.org/en/doc/addpage.htm
354     * @see http://www.tcpdf.org/doc/code/classTCPDF.html#a5171e20b366b74523709d84c349c1ced
355     */
356    public function AddPage($orientation = '', $format = '', $rotationOrKeepmargins = false, $tocpage = false)
357    {
358        if (is_subclass_of($this, 'TCPDF')) {
359            $args = func_get_args();
360            return call_user_func_array(array($this, 'TCPDF::AddPage'), $args);
361        }
362
363        if ($this->_inTpl) {
364            throw new LogicException('Adding pages in templates is not possible!');
365        }
366
367        parent::AddPage($orientation, $format, $rotationOrKeepmargins);
368    }
369
370    /**
371     * Puts a link on a rectangular area of the page.
372     *
373     * Overwritten because adding links in a template will not work.
374     *
375     * @see http://fpdf.org/en/doc/link.htm
376     * @see http://www.tcpdf.org/doc/code/classTCPDF.html#ab87bf1826384fbfe30eb499d42f1d994
377     */
378    public function Link($x, $y, $w, $h, $link, $spaces = 0)
379    {
380        if (is_subclass_of($this, 'TCPDF')) {
381            $args = func_get_args();
382            return call_user_func_array(array($this, 'TCPDF::Link'), $args);
383        }
384
385        if ($this->_inTpl) {
386            throw new LogicException('Using links in templates is not posible!');
387        }
388
389        parent::Link($x, $y, $w, $h, $link);
390    }
391
392    /**
393     * Creates a new internal link and returns its identifier.
394     *
395     * Overwritten because adding links in a template will not work.
396     *
397     * @see http://fpdf.org/en/doc/addlink.htm
398     * @see http://www.tcpdf.org/doc/code/classTCPDF.html#a749522038ed7786c3e1701435dcb891e
399     */
400    public function AddLink()
401    {
402        if (is_subclass_of($this, 'TCPDF')) {
403            $args = func_get_args();
404            return call_user_func_array(array($this, 'TCPDF::AddLink'), $args);
405        }
406
407        if ($this->_inTpl) {
408            throw new LogicException('Adding links in templates is not possible!');
409        }
410
411        return parent::AddLink();
412    }
413
414    /**
415     * Defines the page and position a link points to.
416     *
417     * Overwritten because adding links in a template will not work.
418     *
419     * @see http://fpdf.org/en/doc/setlink.htm
420     * @see http://www.tcpdf.org/doc/code/classTCPDF.html#ace5be60e7857953ea5e2b89cb90df0ae
421     */
422    public function SetLink($link, $y = 0, $page = -1)
423    {
424        if (is_subclass_of($this, 'TCPDF')) {
425            $args = func_get_args();
426            return call_user_func_array(array($this, 'TCPDF::SetLink'), $args);
427        }
428
429        if ($this->_inTpl) {
430            throw new LogicException('Setting links in templates is not possible!');
431        }
432
433        parent::SetLink($link, $y, $page);
434    }
435
436    /**
437     * Writes the form XObjects to the PDF document.
438     */
439    protected function _putformxobjects()
440    {
441        $filter=($this->compress) ? '/Filter /FlateDecode ' : '';
442        reset($this->_tpls);
443
444        foreach($this->_tpls AS $tplIdx => $tpl) {
445            $this->_newobj();
446            $this->_tpls[$tplIdx]['n'] = $this->n;
447            $this->_out('<<'.$filter.'/Type /XObject');
448            $this->_out('/Subtype /Form');
449            $this->_out('/FormType 1');
450            $this->_out(sprintf('/BBox [%.2F %.2F %.2F %.2F]',
451                // llx
452                $tpl['x'] * $this->k,
453                // lly
454                -$tpl['y'] * $this->k,
455                // urx
456                ($tpl['w'] + $tpl['x']) * $this->k,
457                // ury
458                ($tpl['h'] - $tpl['y']) * $this->k
459            ));
460
461            if ($tpl['x'] != 0 || $tpl['y'] != 0) {
462                $this->_out(sprintf('/Matrix [1 0 0 1 %.5F %.5F]',
463                    -$tpl['x'] * $this->k * 2, $tpl['y'] * $this->k * 2
464                ));
465            }
466
467            $this->_out('/Resources ');
468            $this->_out('<</ProcSet [/PDF /Text /ImageB /ImageC /ImageI]');
469
470            if (isset($this->_res['tpl'][$tplIdx])) {
471                $res = $this->_res['tpl'][$tplIdx];
472                if (isset($res['fonts']) && count($res['fonts'])) {
473                    $this->_out('/Font <<');
474
475                    foreach($res['fonts'] as $font) {
476                        $this->_out('/F' . $font['i'] . ' ' . $font['n'] . ' 0 R');
477                    }
478
479                    $this->_out('>>');
480                }
481
482                if(isset($res['images']) || isset($res['tpls'])) {
483                    $this->_out('/XObject <<');
484
485                    if (isset($res['images'])) {
486                        foreach($res['images'] as $image)
487                            $this->_out('/I' . $image['i'] . ' ' . $image['n'] . ' 0 R');
488                    }
489
490                    if (isset($res['tpls'])) {
491                        foreach($res['tpls'] as $i => $_tpl)
492                            $this->_out($this->tplPrefix . $i . ' ' . $_tpl['n'] . ' 0 R');
493                    }
494
495                    $this->_out('>>');
496                }
497            }
498
499            $this->_out('>>');
500
501            $buffer = ($this->compress) ? gzcompress($tpl['buffer']) : $tpl['buffer'];
502            $this->_out('/Length ' . strlen($buffer) . ' >>');
503            $this->_putstream($buffer);
504            $this->_out('endobj');
505        }
506    }
507
508    /**
509     * Output images.
510     *
511     * Overwritten to add {@link _putformxobjects()} after _putimages().
512     */
513    public function _putimages()
514    {
515        parent::_putimages();
516        $this->_putformxobjects();
517    }
518
519    /**
520     * Writes the references of XObject resources to the document.
521     *
522     * Overwritten to add the the templates to the XObject resource dictionary.
523     */
524    public function _putxobjectdict()
525    {
526        parent::_putxobjectdict();
527
528        foreach($this->_tpls as $tplIdx => $tpl) {
529            $this->_out(sprintf('%s%d %d 0 R', $this->tplPrefix, $tplIdx, $tpl['n']));
530        }
531    }
532
533    /**
534     * Writes bytes to the resulting document.
535     *
536     * Overwritten to delegate the data to the template buffer.
537     *
538     * @param string $s
539     */
540    public function _out($s)
541    {
542        if ($this->state == 2 && $this->_inTpl) {
543            $this->_tpls[$this->tpl]['buffer'] .= $s . "\n";
544        } else {
545            parent::_out($s);
546        }
547    }
548}
549