5 // Copyright 2004-2014 Setasign - Jan Slabon
7 // Licensed under the Apache License, Version 2.0 (the "License");
8 // you may not use this file except in compliance with the License.
9 // You may obtain a copy of the License at
11 // http://www.apache.org/licenses/LICENSE-2.0
13 // Unless required by applicable law or agreed to in writing, software
14 // distributed under the License is distributed on an "AS IS" BASIS,
15 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 // See the License for the specific language governing permissions and
17 // limitations under the License.
20 require_once('fpdf_tpl.php');
25 class FPDI
extends FPDF_TPL
32 const VERSION
= '1.5.0';
39 public $currentFilename;
44 * @var fpdi_pdf_parser[]
51 * @var fpdi_pdf_parser
53 public $currentParser;
56 * The name of the last imported page box
60 public $lastUsedPageBox;
74 protected $_doneObjStack;
81 protected $_currentObjId;
84 * Cache for imported pages/template ids
88 protected $_importedPages = array();
93 * Depending on the PDF version of the used document the PDF version of the resulting document will
94 * be adjusted to the higher version.
96 * @param string $filename A valid path to the PDF document from which pages should be imported from
97 * @return int The number of pages in the document
99 public function setSourceFile($filename)
101 $_filename = realpath($filename);
102 if (false !== $_filename)
103 $filename = $_filename;
105 $this->currentFilename
= $filename;
107 if (!isset($this->parsers
[$filename])) {
108 $this->parsers
[$filename] = $this->_getPdfParser($filename);
109 $this->setPdfVersion(
110 max($this->getPdfVersion(), $this->parsers
[$filename]->getPdfVersion())
114 $this->currentParser
=& $this->parsers
[$filename];
116 return $this->parsers
[$filename]->getPageCount();
120 * Returns a PDF parser object
122 * @param string $filename
123 * @return fpdi_pdf_parser
125 protected function _getPdfParser($filename)
127 require_once('fpdi_pdf_parser.php');
128 return new fpdi_pdf_parser($filename);
132 * Get the current PDF version.
136 public function getPdfVersion()
138 return $this->PDFVersion
;
142 * Set the PDF version.
144 * @param string $version
146 public function setPdfVersion($version = '1.3')
148 $this->PDFVersion
= $version;
154 * The second parameter defines the bounding box that should be used to transform the page into a
157 * Following values are available: MediaBox, CropBox, BleedBox, TrimBox, ArtBox.
158 * If a box is not especially defined its default box will be used:
161 * <li>CropBox: Default -> MediaBox</li>
162 * <li>BleedBox: Default -> CropBox</li>
163 * <li>TrimBox: Default -> CropBox</li>
164 * <li>ArtBox: Default -> CropBox</li>
167 * It is possible to get the used page box by the {@link getLastUsedPageBox()} method.
169 * @param int $pageNo The page number
170 * @param string $boxName The boundary box to use when transforming the page into a form XObject
171 * @param boolean $groupXObject Define the form XObject as a group XObject to support transparency (if used)
172 * @return int An id of the imported page/template to use with e.g. fpdf_tpl::useTemplate()
173 * @throws LogicException|InvalidArgumentException
174 * @see getLastUsedPageBox()
176 public function importPage($pageNo, $boxName = 'CropBox', $groupXObject = true)
179 throw new LogicException('Please import the desired pages before creating a new template.');
182 $fn = $this->currentFilename
;
183 $boxName = '/' . ltrim($boxName, '/');
185 // check if page already imported
186 $pageKey = $fn . '-' . ((int)$pageNo) . $boxName;
187 if (isset($this->_importedPages
[$pageKey])) {
188 return $this->_importedPages
[$pageKey];
191 $parser = $this->parsers
[$fn];
192 $parser->setPageNo($pageNo);
194 if (!in_array($boxName, $parser->availableBoxes
)) {
195 throw new InvalidArgumentException(sprintf('Unknown box: %s', $boxName));
198 $pageBoxes = $parser->getPageBoxes($pageNo, $this->k
);
202 * CropBox: Default -> MediaBox
203 * BleedBox: Default -> CropBox
204 * TrimBox: Default -> CropBox
205 * ArtBox: Default -> CropBox
207 if (!isset($pageBoxes[$boxName]) && ($boxName == '/BleedBox' ||
$boxName == '/TrimBox' ||
$boxName == '/ArtBox'))
208 $boxName = '/CropBox';
209 if (!isset($pageBoxes[$boxName]) && $boxName == '/CropBox')
210 $boxName = '/MediaBox';
212 if (!isset($pageBoxes[$boxName]))
215 $this->lastUsedPageBox
= $boxName;
217 $box = $pageBoxes[$boxName];
220 $this->_tpls
[$this->tpl
] = array();
221 $tpl =& $this->_tpls
[$this->tpl
];
222 $tpl['parser'] = $parser;
223 $tpl['resources'] = $parser->getPageResources();
224 $tpl['buffer'] = $parser->getContent();
226 $tpl['groupXObject'] = $groupXObject;
228 $this->setPdfVersion(max($this->getPdfVersion(), 1.4));
231 // To build an array that can be used by PDF_TPL::useTemplate()
232 $this->_tpls
[$this->tpl
] = array_merge($this->_tpls
[$this->tpl
], $box);
234 // An imported page will start at 0,0 all the time. Translation will be set in _putformxobjects()
238 // handle rotated pages
239 $rotation = $parser->getPageRotation($pageNo);
240 $tpl['_rotationAngle'] = 0;
241 if (isset($rotation[1]) && ($angle = $rotation[1] %
360) != 0) {
242 $steps = $angle / 90;
246 $tpl['w'] = $steps %
2 == 0 ?
$_w : $_h;
247 $tpl['h'] = $steps %
2 == 0 ?
$_h : $_w;
252 $tpl['_rotationAngle'] = $angle * -1;
255 $this->_importedPages
[$pageKey] = $this->tpl
;
261 * Returns the last used page boundary box.
263 * @return string The used boundary box: MediaBox, CropBox, BleedBox, TrimBox or ArtBox
265 public function getLastUsedPageBox()
267 return $this->lastUsedPageBox
;
271 * Use a template or imported page in current page or other template.
273 * You can use a template in a page or in another template.
274 * You can give the used template a new size. All parameters are optional.
275 * The width or height is calculated automatically if one is given. If no
276 * parameter is given the origin size as defined in beginTemplate() or of
277 * the imported page is used.
279 * The calculated or used width and height are returned as an array.
281 * @param int $tplIdx A valid template-id
282 * @param int $x The x-position
283 * @param int $y The y-position
284 * @param int $w The new width of the template
285 * @param int $h The new height of the template
286 * @param boolean $adjustPageSize If set to true the current page will be resized to fit the dimensions
289 * @return array The height and width of the template (array('w' => ..., 'h' => ...))
290 * @throws LogicException|InvalidArgumentException
292 public function useTemplate($tplIdx, $x = null, $y = null, $w = 0, $h = 0, $adjustPageSize = false)
294 if ($adjustPageSize == true && is_null($x) && is_null($y)) {
295 $size = $this->getTemplateSize($tplIdx, $w, $h);
296 $orientation = $size['w'] > $size['h'] ?
'L' : 'P';
297 $size = array($size['w'], $size['h']);
299 if (is_subclass_of($this, 'TCPDF')) {
300 $this->setPageFormat($size, $orientation);
302 $size = $this->_getpagesize($size);
304 if($orientation != $this->CurOrientation ||
305 $size[0] != $this->CurPageSize
[0] ||
306 $size[1] != $this->CurPageSize
[1]
308 // New size or orientation
309 if ($orientation=='P') {
316 $this->wPt
= $this->w
* $this->k
;
317 $this->hPt
= $this->h
* $this->k
;
318 $this->PageBreakTrigger
= $this->h
- $this->bMargin
;
319 $this->CurOrientation
= $orientation;
320 $this->CurPageSize
= $size;
321 $this->PageSizes
[$this->page
] = array($this->wPt
, $this->hPt
);
326 $this->_out('q 0 J 1 w 0 j 0 G 0 g'); // reset standard values
327 $size = parent
::useTemplate($tplIdx, $x, $y, $w, $h);
334 * Copy all imported objects to the resulting document.
336 protected function _putimportedobjects()
338 if (!is_array($this->parsers
) ||
count($this->parsers
) === 0) {
342 foreach($this->parsers
AS $filename => $p) {
343 $this->currentParser
=& $p;
344 if (!isset($this->_objStack
[$filename]) ||
!is_array($this->_objStack
[$filename])) {
347 while(($n = key($this->_objStack
[$filename])) !== null) {
348 $nObj = $this->currentParser
->resolveObject($this->_objStack
[$filename][$n][1]);
350 $this->_newobj($this->_objStack
[$filename][$n][0]);
352 if ($nObj[0] == pdf_parser
::TYPE_STREAM
) {
353 $this->_writeValue($nObj);
355 $this->_writeValue($nObj[1]);
358 $this->_out("\nendobj");
359 $this->_objStack
[$filename][$n] = null; // free memory
360 unset($this->_objStack
[$filename][$n]);
361 reset($this->_objStack
[$filename]);
367 * Writes the form XObjects to the PDF document.
369 protected function _putformxobjects()
371 $filter=($this->compress
) ?
'/Filter /FlateDecode ' : '';
373 foreach($this->_tpls
AS $tplIdx => $tpl) {
375 $currentN = $this->n
; // TCPDF/Protection: rem current "n"
377 $this->_tpls
[$tplIdx]['n'] = $this->n
;
378 $this->_out('<<' . $filter . '/Type /XObject');
379 $this->_out('/Subtype /Form');
380 $this->_out('/FormType 1');
382 $this->_out(sprintf('/BBox [%.2F %.2F %.2F %.2F]',
383 (isset($tpl['box']['llx']) ?
$tpl['box']['llx'] : $tpl['x']) * $this->k
,
384 (isset($tpl['box']['lly']) ?
$tpl['box']['lly'] : -$tpl['y']) * $this->k
,
385 (isset($tpl['box']['urx']) ?
$tpl['box']['urx'] : $tpl['w'] +
$tpl['x']) * $this->k
,
386 (isset($tpl['box']['ury']) ?
$tpl['box']['ury'] : $tpl['h'] - $tpl['y']) * $this->k
394 if (isset($tpl['box'])) {
395 $tx = -$tpl['box']['llx'];
396 $ty = -$tpl['box']['lly'];
398 if ($tpl['_rotationAngle'] <> 0) {
399 $angle = $tpl['_rotationAngle'] * M_PI
/180;
403 switch($tpl['_rotationAngle']) {
405 $tx = -$tpl['box']['lly'];
406 $ty = $tpl['box']['urx'];
409 $tx = $tpl['box']['urx'];
410 $ty = $tpl['box']['ury'];
413 $tx = $tpl['box']['ury'];
414 $ty = -$tpl['box']['llx'];
418 } else if ($tpl['x'] != 0 ||
$tpl['y'] != 0) {
419 $tx = -$tpl['x'] * 2;
426 if ($c != 1 ||
$s != 0 ||
$tx != 0 ||
$ty != 0) {
427 $this->_out(sprintf('/Matrix [%.5F %.5F %.5F %.5F %.5F %.5F]',
428 $c, $s, -$s, $c, $tx, $ty
432 $this->_out('/Resources ');
434 if (isset($tpl['resources'])) {
435 $this->currentParser
= $tpl['parser'];
436 $this->_writeValue($tpl['resources']); // "n" will be changed
439 $this->_out('<</ProcSet [/PDF /Text /ImageB /ImageC /ImageI]');
440 if (isset($this->_res
['tpl'][$tplIdx])) {
441 $res = $this->_res
['tpl'][$tplIdx];
443 if (isset($res['fonts']) && count($res['fonts'])) {
444 $this->_out('/Font <<');
445 foreach ($res['fonts'] as $font)
446 $this->_out('/F' . $font['i'] . ' ' . $font['n'] . ' 0 R');
449 if (isset($res['images']) && count($res['images']) ||
450 isset($res['tpls']) && count($res['tpls']))
452 $this->_out('/XObject <<');
453 if (isset($res['images'])) {
454 foreach ($res['images'] as $image)
455 $this->_out('/I' . $image['i'] . ' ' . $image['n'] . ' 0 R');
457 if (isset($res['tpls'])) {
458 foreach ($res['tpls'] as $i => $_tpl)
459 $this->_out($this->tplPrefix
. $i . ' ' . $_tpl['n'] . ' 0 R');
467 if (isset($tpl['groupXObject']) && $tpl['groupXObject']) {
468 $this->_out('/Group <</Type/Group/S/Transparency>>');
471 $newN = $this->n
; // TCPDF: rem new "n"
472 $this->n
= $currentN; // TCPDF: reset to current "n"
474 $buffer = ($this->compress
) ?
gzcompress($tpl['buffer']) : $tpl['buffer'];
476 if (is_subclass_of($this, 'TCPDF')) {
477 $buffer = $this->_getrawstream($buffer);
478 $this->_out('/Length ' . strlen($buffer) . ' >>');
479 $this->_out("stream\n" . $buffer . "\nendstream");
481 $this->_out('/Length ' . strlen($buffer) . ' >>');
482 $this->_putstream($buffer);
484 $this->_out('endobj');
485 $this->n
= $newN; // TCPDF: reset to new "n"
488 $this->_putimportedobjects();
492 * Creates and optionally write the object definition to the document.
494 * Rewritten to handle existing own defined objects
497 * @param bool $onlyNewObj
500 public function _newobj($objId = false, $onlyNewObj = false)
508 $this->offsets
[$objId] = is_subclass_of($this, 'TCPDF') ?
$this->bufferlen
: strlen($this->buffer
);
509 $this->_out($objId . ' 0 obj');
510 $this->_currentObjId
= $objId; // for later use with encryption
517 * Writes a PDF value to the resulting document.
519 * Needed to rebuild the source document
521 * @param mixed $value A PDF-Value. Structure of values see cases in this method
523 protected function _writeValue(&$value)
525 if (is_subclass_of($this, 'TCPDF')) {
526 parent
::_prepareValue($value);
531 case pdf_parser
::TYPE_TOKEN
:
532 $this->_straightOut($value[1] . ' ');
534 case pdf_parser
::TYPE_NUMERIC
:
535 case pdf_parser
::TYPE_REAL
:
536 if (is_float($value[1]) && $value[1] != 0) {
537 $this->_straightOut(rtrim(rtrim(sprintf('%F', $value[1]), '0'), '.') . ' ');
539 $this->_straightOut($value[1] . ' ');
543 case pdf_parser
::TYPE_ARRAY
:
545 // An array. Output the proper
546 // structure and move on.
548 $this->_straightOut('[');
549 for ($i = 0; $i < count($value[1]); $i++
) {
550 $this->_writeValue($value[1][$i]);
556 case pdf_parser
::TYPE_DICTIONARY
:
559 $this->_straightOut('<<');
563 while (list($k, $v) = each($value[1])) {
564 $this->_straightOut($k . ' ');
565 $this->_writeValue($v);
568 $this->_straightOut('>>');
571 case pdf_parser
::TYPE_OBJREF
:
573 // An indirect object reference
574 // Fill the object stack if needed
575 $cpfn =& $this->currentParser
->filename
;
576 if (!isset($this->_doneObjStack
[$cpfn][$value[1]])) {
577 $this->_newobj(false, true);
578 $this->_objStack
[$cpfn][$value[1]] = array($this->n
, $value);
579 $this->_doneObjStack
[$cpfn][$value[1]] = array($this->n
, $value);
581 $objId = $this->_doneObjStack
[$cpfn][$value[1]][0];
583 $this->_out($objId . ' 0 R');
586 case pdf_parser
::TYPE_STRING
:
589 $this->_straightOut('(' . $value[1] . ')');
593 case pdf_parser
::TYPE_STREAM
:
595 // A stream. First, output the
596 // stream dictionary, then the
597 // stream data itself.
598 $this->_writeValue($value[1]);
599 $this->_out('stream');
600 $this->_out($value[2][1]);
601 $this->_straightOut("endstream");
604 case pdf_parser
::TYPE_HEX
:
605 $this->_straightOut('<' . $value[1] . '>');
608 case pdf_parser
::TYPE_BOOLEAN
:
609 $this->_straightOut($value[1] ?
'true ' : 'false ');
612 case pdf_parser
::TYPE_NULL
:
615 $this->_straightOut('null ');
622 * Modified _out() method so not each call will add a newline to the output.
624 protected function _straightOut($s)
626 if (!is_subclass_of($this, 'TCPDF')) {
627 if ($this->state
== 2) {
628 $this->pages
[$this->page
] .= $s;
634 if ($this->state
== 2) {
636 // we are inside an XObject template
637 $this->xobjects
[$this->xobjid
]['outdata'] .= $s;
638 } else if ((!$this->InFooter
) AND isset($this->footerlen
[$this->page
]) AND ($this->footerlen
[$this->page
] > 0)) {
639 // puts data before page footer
640 $pagebuff = $this->getPageBuffer($this->page
);
641 $page = substr($pagebuff, 0, -$this->footerlen
[$this->page
]);
642 $footer = substr($pagebuff, -$this->footerlen
[$this->page
]);
643 $this->setPageBuffer($this->page
, $page . $s . $footer);
644 // update footer position
645 $this->footerpos
[$this->page
] +
= strlen($s);
648 $this->setPageBuffer($this->page
, $s, true);
650 } else if ($this->state
> 0) {
652 $this->setBuffer($s);
660 * Overwritten to close opened parsers
662 public function _enddoc()
665 $this->_closeParsers();
669 * Close all files opened by parsers.
673 protected function _closeParsers()
675 if ($this->state
> 2) {
684 * Removes cycled references and closes the file handles of the parser objects.
686 public function cleanUp()
688 while (($parser = array_pop($this->parsers
)) !== null) {
690 * @var fpdi_pdf_parser $parser
692 $parser->closeFile();