From 0ff39c83d38ce538a9f5dba53eca0fa9cb16d9e6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 14:45:49 +0200 Subject: Adding upstream version 0.10.2+dfsg1. Signed-off-by: Daniel Baumann --- .../.github/ISSUE_TEMPLATE/bug_report.md | 37 + vendor/iio/libmergepdf/composer.json | 35 + .../iio/libmergepdf/src/Driver/DefaultDriver.php | 25 + .../iio/libmergepdf/src/Driver/DriverInterface.php | 13 + vendor/iio/libmergepdf/src/Driver/Fpdi2Driver.php | 64 + vendor/iio/libmergepdf/src/Driver/TcpdiDriver.php | 52 + vendor/iio/libmergepdf/src/Exception.php | 7 + vendor/iio/libmergepdf/src/Merger.php | 79 ++ vendor/iio/libmergepdf/src/Pages.php | 67 + vendor/iio/libmergepdf/src/PagesInterface.php | 11 + vendor/iio/libmergepdf/src/Source/FileSource.php | 50 + vendor/iio/libmergepdf/src/Source/RawSource.php | 45 + .../iio/libmergepdf/src/Source/SourceInterface.php | 25 + vendor/iio/libmergepdf/tcpdi/LICENSE | 191 +++ vendor/iio/libmergepdf/tcpdi/README.md | 65 + vendor/iio/libmergepdf/tcpdi/fpdf_tpl.php | 460 +++++++ vendor/iio/libmergepdf/tcpdi/tcpdi.php | 841 ++++++++++++ vendor/iio/libmergepdf/tcpdi/tcpdi_parser.php | 1450 ++++++++++++++++++++ 18 files changed, 3517 insertions(+) create mode 100644 vendor/iio/libmergepdf/.github/ISSUE_TEMPLATE/bug_report.md create mode 100644 vendor/iio/libmergepdf/composer.json create mode 100644 vendor/iio/libmergepdf/src/Driver/DefaultDriver.php create mode 100644 vendor/iio/libmergepdf/src/Driver/DriverInterface.php create mode 100644 vendor/iio/libmergepdf/src/Driver/Fpdi2Driver.php create mode 100644 vendor/iio/libmergepdf/src/Driver/TcpdiDriver.php create mode 100644 vendor/iio/libmergepdf/src/Exception.php create mode 100644 vendor/iio/libmergepdf/src/Merger.php create mode 100644 vendor/iio/libmergepdf/src/Pages.php create mode 100644 vendor/iio/libmergepdf/src/PagesInterface.php create mode 100644 vendor/iio/libmergepdf/src/Source/FileSource.php create mode 100644 vendor/iio/libmergepdf/src/Source/RawSource.php create mode 100644 vendor/iio/libmergepdf/src/Source/SourceInterface.php create mode 100644 vendor/iio/libmergepdf/tcpdi/LICENSE create mode 100644 vendor/iio/libmergepdf/tcpdi/README.md create mode 100644 vendor/iio/libmergepdf/tcpdi/fpdf_tpl.php create mode 100644 vendor/iio/libmergepdf/tcpdi/tcpdi.php create mode 100644 vendor/iio/libmergepdf/tcpdi/tcpdi_parser.php (limited to 'vendor/iio/libmergepdf') diff --git a/vendor/iio/libmergepdf/.github/ISSUE_TEMPLATE/bug_report.md b/vendor/iio/libmergepdf/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..e6a8753 --- /dev/null +++ b/vendor/iio/libmergepdf/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,37 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Include files** +Please include the files that you are trying to merge so that the error can be reproduced. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Please complete the following information:** + - PHP-version + - Version of library [e.g. 22] + - Driver used + +**Additional context** +Add any other context about the problem here. + +> Please note that this library is only a wrapper around other pdf editing tools. +> As such we are only able to respond to problems regaring the actual +> merging process. Issues concering deeper pdf parsing problems should +> be directed elsewhere. diff --git a/vendor/iio/libmergepdf/composer.json b/vendor/iio/libmergepdf/composer.json new file mode 100644 index 0000000..195d0aa --- /dev/null +++ b/vendor/iio/libmergepdf/composer.json @@ -0,0 +1,35 @@ +{ + "name": "iio/libmergepdf", + "description": "Library for merging multiple PDFs", + "keywords": ["pdf", "merge"], + "homepage": "https://github.com/hanneskod/libmergepdf", + "type": "library", + "license": "WTFPL", + "authors": [ + { + "name": "Hannes Forsgård", + "email": "hannes.forsgard@fripost.org" + } + ], + "autoload": { + "psr-4": { + "iio\\libmergepdf\\": "src/" + }, + "classmap": [ + "tcpdi/" + ] + }, + "require": { + "php": "^7.1||^8.0", + "tecnickcom/tcpdf": "^6.2.22", + "setasign/fpdi": "^2" + }, + "conflict": { + "setasign/fpdf": "*", + "rafikhaceb/tcpdi": "*" + }, + "require-dev": { + "phpunit/phpunit": "^7|^8", + "smalot/pdfparser": "~0.13" + } +} diff --git a/vendor/iio/libmergepdf/src/Driver/DefaultDriver.php b/vendor/iio/libmergepdf/src/Driver/DefaultDriver.php new file mode 100644 index 0000000..9ad6f73 --- /dev/null +++ b/vendor/iio/libmergepdf/src/Driver/DefaultDriver.php @@ -0,0 +1,25 @@ +wrapped = $wrapped ?: new Fpdi2Driver; + } + + public function merge(SourceInterface ...$sources): string + { + return $this->wrapped->merge(...$sources); + } +} diff --git a/vendor/iio/libmergepdf/src/Driver/DriverInterface.php b/vendor/iio/libmergepdf/src/Driver/DriverInterface.php new file mode 100644 index 0000000..39cca1e --- /dev/null +++ b/vendor/iio/libmergepdf/src/Driver/DriverInterface.php @@ -0,0 +1,13 @@ +fpdi = $fpdi ?: @new FpdiTcpdf; + + if (!($this->fpdi instanceof FpdiFpdf) && !($this->fpdi instanceof FpdiTcpdf)) { + throw new \InvalidArgumentException('Constructor argument must be an FPDI instance.'); + } + } + + public function merge(SourceInterface ...$sources): string + { + $sourceName = ''; + + try { + $fpdi = clone $this->fpdi; + + foreach ($sources as $source) { + $sourceName = $source->getName(); + $pageCount = $fpdi->setSourceFile(StreamReader::createByString($source->getContents())); + $pageNumbers = $source->getPages()->getPageNumbers() ?: range(1, $pageCount); + + foreach ($pageNumbers as $pageNr) { + $template = $fpdi->importPage($pageNr); + $size = $fpdi->getTemplateSize($template); + $fpdi->SetPrintHeader(false); + $fpdi->SetPrintFooter(false); + $fpdi->AddPage( + $size['width'] > $size['height'] ? 'L' : 'P', + [$size['width'], $size['height']] + ); + $fpdi->useTemplate($template); + } + } + + return $fpdi->Output('', 'S'); + } catch (\Exception $e) { + throw new Exception("'{$e->getMessage()}' in '$sourceName'", 0, $e); + } + } +} diff --git a/vendor/iio/libmergepdf/src/Driver/TcpdiDriver.php b/vendor/iio/libmergepdf/src/Driver/TcpdiDriver.php new file mode 100644 index 0000000..fa0ddc8 --- /dev/null +++ b/vendor/iio/libmergepdf/src/Driver/TcpdiDriver.php @@ -0,0 +1,52 @@ +tcpdi = $tcpdi ?: new \TCPDI; + } + + public function merge(SourceInterface ...$sources): string + { + $sourceName = ''; + + try { + $tcpdi = clone $this->tcpdi; + + foreach ($sources as $source) { + $sourceName = $source->getName(); + $pageCount = $tcpdi->setSourceData($source->getContents()); + $pageNumbers = $source->getPages()->getPageNumbers() ?: range(1, $pageCount); + + foreach ($pageNumbers as $pageNr) { + $template = $tcpdi->importPage($pageNr); + $size = $tcpdi->getTemplateSize($template); + $tcpdi->SetPrintHeader(false); + $tcpdi->SetPrintFooter(false); + $tcpdi->AddPage( + $size['w'] > $size['h'] ? 'L' : 'P', + [$size['w'], $size['h']] + ); + $tcpdi->useTemplate($template); + } + } + + return $tcpdi->Output('', 'S'); + } catch (\Exception $e) { + throw new Exception("'{$e->getMessage()}' in '$sourceName'", 0, $e); + } + } +} diff --git a/vendor/iio/libmergepdf/src/Exception.php b/vendor/iio/libmergepdf/src/Exception.php new file mode 100644 index 0000000..69b3208 --- /dev/null +++ b/vendor/iio/libmergepdf/src/Exception.php @@ -0,0 +1,7 @@ +driver = $driver ?: new DefaultDriver; + } + + /** + * Add raw PDF from string + */ + public function addRaw(string $content, PagesInterface $pages = null): void + { + $this->sources[] = new RawSource($content, $pages); + } + + /** + * Add PDF from file + */ + public function addFile(string $filename, PagesInterface $pages = null): void + { + $this->sources[] = new FileSource($filename, $pages); + } + + /** + * Add files using iterator + * + * @param iterable $iterator Set of filenames to add + * @param PagesInterface $pages Optional pages constraint used for every added pdf + */ + public function addIterator(iterable $iterator, PagesInterface $pages = null): void + { + foreach ($iterator as $filename) { + $this->addFile($filename, $pages); + } + } + + /** + * Merges loaded PDFs + */ + public function merge(): string + { + return $this->driver->merge(...$this->sources); + } + + /** + * Reset internal state + */ + public function reset(): void + { + $this->sources = []; + } +} diff --git a/vendor/iio/libmergepdf/src/Pages.php b/vendor/iio/libmergepdf/src/Pages.php new file mode 100644 index 0000000..7675315 --- /dev/null +++ b/vendor/iio/libmergepdf/src/Pages.php @@ -0,0 +1,67 @@ +addPage((int)$expr); + continue; + } + if (preg_match("/^(\d+)-(\d+)/", $expr, $matches)) { + $this->addRange((int)$matches[1], (int)$matches[2]); + continue; + } + throw new Exception("Invalid page number(s) for expression '$expr'"); + } + } + + /** + * Add a single page + */ + public function addPage(int $page): void + { + $this->pages[] = $page; + } + + /** + * Add a range of pages + */ + public function addRange(int $start, int $end): void + { + $this->pages = array_merge($this->pages, range($start, $end)); + } + + public function getPageNumbers(): array + { + return $this->pages; + } +} diff --git a/vendor/iio/libmergepdf/src/PagesInterface.php b/vendor/iio/libmergepdf/src/PagesInterface.php new file mode 100644 index 0000000..e85d3c8 --- /dev/null +++ b/vendor/iio/libmergepdf/src/PagesInterface.php @@ -0,0 +1,11 @@ +filename = $filename; + $this->pages = $pages ?: new Pages; + } + + public function getName(): string + { + return $this->filename; + } + + public function getContents(): string + { + return (string)file_get_contents($this->filename); + } + + public function getPages(): PagesInterface + { + return $this->pages; + } +} diff --git a/vendor/iio/libmergepdf/src/Source/RawSource.php b/vendor/iio/libmergepdf/src/Source/RawSource.php new file mode 100644 index 0000000..7966918 --- /dev/null +++ b/vendor/iio/libmergepdf/src/Source/RawSource.php @@ -0,0 +1,45 @@ +contents = $contents; + $this->pages = $pages ?: new Pages; + } + + public function getName(): string + { + return "raw-content"; + } + + public function getContents(): string + { + return $this->contents; + } + + public function getPages(): PagesInterface + { + return $this->pages; + } +} diff --git a/vendor/iio/libmergepdf/src/Source/SourceInterface.php b/vendor/iio/libmergepdf/src/Source/SourceInterface.php new file mode 100644 index 0000000..4ce9c3c --- /dev/null +++ b/vendor/iio/libmergepdf/src/Source/SourceInterface.php @@ -0,0 +1,25 @@ +AddPage(); +$pdf->setSourceFile('/path/to/file-to-import.pdf'); +$idx = $pdf->importPage(1); +$pdf->useTemplate($idx); + +$pdfdata = file_get_contents('/path/to/other-file.pdf'); // Simulate only having raw data available. +$pagecount = $pdf->setSourceData($pdfdata); +for ($i = 1; $i <= $pagecount; $i++) { + $tplidx = $pdf->importPage($i); + $pdf->AddPage(); + $pdf->useTemplate($tplidx); +} +``` + +As of version 1.1, TCPDI also includes additional functionality for handling PDF Annotations. As annotations are positioned relative to the bleed box rather than the crop box, you'll need to ensure that you're importing the full bleed box; a new function has also been introduced to set the page format (the various boxes, including the crop box) from the imported page, so that the imported page matches the original better. The following example demonstrates this: + +```php +// Create new PDF document. +$pdf = new TCPDI(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false); + +// Add a page from a PDF by file path. +$pdf->setSourceFile('/path/to/file-to-import.pdf'); + +// Import the bleed box (default is crop box) for page 1. +$tplidx = $pdf->importPage(1, '/BleedBox'); +$size = $pdf->getTemplatesize($tplidx); +$orientation = ($size['w'] > $size['h']) ? 'L' : 'P'; + +$pdf->AddPage($orientation); + +// Set page boxes from imported page 1. +$pdf->setPageFormatFromTemplatePage(1, $orientation); + +// Import the content for page 1. +$pdf->useTemplate($tplidx); + +// Import the annotations for page 1. +$pdf->importAnnotations(1); +``` + +TCPDI_PARSER +============ + +Parser for use with TCPDI, based on TCPDF_PARSER. Supports PDFs up to v1.7. diff --git a/vendor/iio/libmergepdf/tcpdi/fpdf_tpl.php b/vendor/iio/libmergepdf/tcpdi/fpdf_tpl.php new file mode 100644 index 0000000..0da7d7b --- /dev/null +++ b/vendor/iio/libmergepdf/tcpdi/fpdf_tpl.php @@ -0,0 +1,460 @@ +Error('This method is only usable with FPDF. Use TCPDF methods startTemplate() instead.'); + return; + } + + if ($this->page <= 0) + $this->error("You have to add a page to fpdf first!"); + + if ($x == null) + $x = 0; + if ($y == null) + $y = 0; + if ($w == null) + $w = $this->w; + if ($h == null) + $h = $this->h; + + // Save settings + $this->tpl++; + $tpl =& $this->tpls[$this->tpl]; + $tpl = array( + 'o_x' => $this->x, + 'o_y' => $this->y, + 'o_AutoPageBreak' => $this->AutoPageBreak, + 'o_bMargin' => $this->bMargin, + 'o_tMargin' => $this->tMargin, + 'o_lMargin' => $this->lMargin, + 'o_rMargin' => $this->rMargin, + 'o_h' => $this->h, + 'o_w' => $this->w, + 'o_FontFamily' => $this->FontFamily, + 'o_FontStyle' => $this->FontStyle, + 'o_FontSizePt' => $this->FontSizePt, + 'o_FontSize' => $this->FontSize, + 'buffer' => '', + 'x' => $x, + 'y' => $y, + 'w' => $w, + 'h' => $h + ); + + $this->SetAutoPageBreak(false); + + // Define own high and width to calculate possitions correct + $this->h = $h; + $this->w = $w; + + $this->_intpl = true; + $this->SetXY($x + $this->lMargin, $y + $this->tMargin); + $this->SetRightMargin($this->w - $w + $this->rMargin); + + if ($this->CurrentFont) { + $fontkey = $this->FontFamily . $this->FontStyle; + $this->_res['tpl'][$this->tpl]['fonts'][$fontkey] =& $this->fonts[$fontkey]; + + $this->_out(sprintf('BT /F%d %.2f Tf ET', $this->CurrentFont['i'], $this->FontSizePt)); + } + + return $this->tpl; + } + + /** + * End Template + * + * This method ends a template and reset initiated variables on beginTemplate. + * + * @return mixed If a template is opened, the ID is returned. If not a false is returned. + */ + function endTemplate() { + if (is_subclass_of($this, 'TCPDF')) { + $args = func_get_args(); + return call_user_func_array(array($this, 'TCPDF::endTemplate'), $args); + } + + if ($this->_intpl) { + $this->_intpl = false; + $tpl =& $this->tpls[$this->tpl]; + $this->SetXY($tpl['o_x'], $tpl['o_y']); + $this->tMargin = $tpl['o_tMargin']; + $this->lMargin = $tpl['o_lMargin']; + $this->rMargin = $tpl['o_rMargin']; + $this->h = $tpl['o_h']; + $this->w = $tpl['o_w']; + $this->SetAutoPageBreak($tpl['o_AutoPageBreak'], $tpl['o_bMargin']); + + $this->FontFamily = $tpl['o_FontFamily']; + $this->FontStyle = $tpl['o_FontStyle']; + $this->FontSizePt = $tpl['o_FontSizePt']; + $this->FontSize = $tpl['o_FontSize']; + + $fontkey = $this->FontFamily . $this->FontStyle; + if ($fontkey) + $this->CurrentFont =& $this->fonts[$fontkey]; + + return $this->tpl; + } else { + return false; + } + } + + /** + * Use a Template in current Page or other Template + * + * You can use a template in a page or in another template. + * You can give the used template a new size like you use the Image()-method. + * All parameters are optional. The width or height is calculated automaticaly + * if one is given. If no parameter is given the origin size as defined in + * beginTemplate() is used. + * The calculated or used width and height are returned as an array. + * + * @param int $tplidx A valid template-Id + * @param int $_x The x-position + * @param int $_y The y-position + * @param int $_w The new width of the template + * @param int $_h The new height of the template + * @retrun array The height and width of the template + */ + function useTemplate($tplidx, $_x = null, $_y = null, $_w = 0, $_h = 0) { + if ($this->page <= 0) + $this->error('You have to add a page first!'); + + if (!isset($this->tpls[$tplidx])) + $this->error('Template does not exist!'); + + if ($this->_intpl) { + $this->_res['tpl'][$this->tpl]['tpls'][$tplidx] =& $this->tpls[$tplidx]; + } + + $tpl =& $this->tpls[$tplidx]; + $w = $tpl['w']; + $h = $tpl['h']; + + if ($_x == null) + $_x = 0; + if ($_y == null) + $_y = 0; + + $_x += $tpl['x']; + $_y += $tpl['y']; + + $wh = $this->getTemplateSize($tplidx, $_w, $_h); + $_w = $wh['w']; + $_h = $wh['h']; + + $tData = array( + 'x' => $this->x, + 'y' => $this->y, + 'w' => $_w, + 'h' => $_h, + 'scaleX' => ($_w / $w), + 'scaleY' => ($_h / $h), + 'tx' => $_x, + 'ty' => ($this->h - $_y - $_h), + 'lty' => ($this->h - $_y - $_h) - ($this->h - $h) * ($_h / $h) + ); + + $this->_out(sprintf('q %.4F 0 0 %.4F %.4F %.4F cm', $tData['scaleX'], $tData['scaleY'], $tData['tx'] * $this->k, $tData['ty'] * $this->k)); // Translate + $this->_out(sprintf('%s%d Do Q', $this->tplprefix, $tplidx)); + + $this->lastUsedTemplateData = $tData; + + return array('w' => $_w, 'h' => $_h); + } + + /** + * Get The calculated Size of a Template + * + * If one size is given, this method calculates the other one. + * + * @param int $tplidx A valid template-Id + * @param int $_w The width of the template + * @param int $_h The height of the template + * @return array The height and width of the template + */ + function getTemplateSize($tplidx, $_w = 0, $_h = 0) { + if (!isset($this->tpls[$tplidx])) + return false; + + $tpl =& $this->tpls[$tplidx]; + $w = $tpl['w']; + $h = $tpl['h']; + + if ($_w == 0 and $_h == 0) { + $_w = $w; + $_h = $h; + } + + if($_w == 0) + $_w = $_h * $w / $h; + if($_h == 0) + $_h = $_w * $h / $w; + + return array("w" => $_w, "h" => $_h); + } + + /** + * See FPDF/TCPDF-Documentation ;-) + */ + public function SetFont($family, $style = '', $size = 0, $fontfile='', $subset='default', $out=true) { + if (is_subclass_of($this, 'TCPDF')) { + $args = func_get_args(); + return call_user_func_array(array($this, 'TCPDF::SetFont'), $args); + } + + parent::SetFont($family, $style, $size); + + $fontkey = $this->FontFamily . $this->FontStyle; + + if ($this->_intpl) { + $this->_res['tpl'][$this->tpl]['fonts'][$fontkey] =& $this->fonts[$fontkey]; + } else { + $this->_res['page'][$this->page]['fonts'][$fontkey] =& $this->fonts[$fontkey]; + } + } + + /** + * See FPDF/TCPDF-Documentation ;-) + */ + function Image( + $file, $x = '', $y = '', $w = 0, $h = 0, $type = '', $link = '', $align = '', $resize = false, + $dpi = 300, $palign = '', $ismask = false, $imgmask = false, $border = 0, $fitbox = false, + $hidden = false, $fitonpage = false, $alt = false, $altimgs = array() + ) { + if (is_subclass_of($this, 'TCPDF')) { + $args = func_get_args(); + return call_user_func_array(array($this, 'TCPDF::Image'), $args); + } + + $ret = parent::Image($file, $x, $y, $w, $h, $type, $link); + if ($this->_intpl) { + $this->_res['tpl'][$this->tpl]['images'][$file] =& $this->images[$file]; + } else { + $this->_res['page'][$this->page]['images'][$file] =& $this->images[$file]; + } + + return $ret; + } + + /** + * See FPDF-Documentation ;-) + * + * AddPage is not available when you're "in" a template. + */ + function AddPage($orientation = '', $format = '', $keepmargins = false, $tocpage = false) { + if (is_subclass_of($this, 'TCPDF')) { + $args = func_get_args(); + return call_user_func_array(array($this, 'TCPDF::AddPage'), $args); + } + + if ($this->_intpl) + $this->Error('Adding pages in templates isn\'t possible!'); + + parent::AddPage($orientation, $format); + } + + /** + * Preserve adding Links in Templates ...won't work + */ + function Link($x, $y, $w, $h, $link, $spaces = 0) { + if (is_subclass_of($this, 'TCPDF')) { + $args = func_get_args(); + return call_user_func_array(array($this, 'TCPDF::Link'), $args); + } + + if ($this->_intpl) + $this->Error('Using links in templates aren\'t possible!'); + + parent::Link($x, $y, $w, $h, $link); + } + + function AddLink() { + if (is_subclass_of($this, 'TCPDF')) { + $args = func_get_args(); + return call_user_func_array(array($this, 'TCPDF::AddLink'), $args); + } + + if ($this->_intpl) + $this->Error('Adding links in templates aren\'t possible!'); + return parent::AddLink(); + } + + function SetLink($link, $y = 0, $page = -1) { + if (is_subclass_of($this, 'TCPDF')) { + $args = func_get_args(); + return call_user_func_array(array($this, 'TCPDF::SetLink'), $args); + } + + if ($this->_intpl) + $this->Error('Setting links in templates aren\'t possible!'); + parent::SetLink($link, $y, $page); + } + + /** + * Private Method that writes the form xobjects + */ + function _putformxobjects() { + $filter=($this->compress) ? '/Filter /FlateDecode ' : ''; + reset($this->tpls); + foreach($this->tpls AS $tplidx => $tpl) { + + $p=($this->compress) ? gzcompress($tpl['buffer']) : $tpl['buffer']; + $this->_newobj(); + $this->tpls[$tplidx]['n'] = $this->n; + $this->_out('<<'.$filter.'/Type /XObject'); + $this->_out('/Subtype /Form'); + $this->_out('/FormType 1'); + $this->_out(sprintf('/BBox [%.2F %.2F %.2F %.2F]', + // llx + $tpl['x'] * $this->k, + // lly + -$tpl['y'] * $this->k, + // urx + ($tpl['w'] + $tpl['x']) * $this->k, + // ury + ($tpl['h'] - $tpl['y']) * $this->k + )); + + if ($tpl['x'] != 0 || $tpl['y'] != 0) { + $this->_out(sprintf('/Matrix [1 0 0 1 %.5F %.5F]', + -$tpl['x'] * $this->k * 2, $tpl['y'] * $this->k * 2 + )); + } + + $this->_out('/Resources '); + + $this->_out('<_res['tpl'][$tplidx]['fonts']) && count($this->_res['tpl'][$tplidx]['fonts'])) { + $this->_out('/Font <<'); + foreach($this->_res['tpl'][$tplidx]['fonts'] as $font) + $this->_out('/F' . $font['i'] . ' ' . $font['n'] . ' 0 R'); + $this->_out('>>'); + } + if(isset($this->_res['tpl'][$tplidx]['images']) && count($this->_res['tpl'][$tplidx]['images']) || + isset($this->_res['tpl'][$tplidx]['tpls']) && count($this->_res['tpl'][$tplidx]['tpls'])) + { + $this->_out('/XObject <<'); + if (isset($this->_res['tpl'][$tplidx]['images']) && count($this->_res['tpl'][$tplidx]['images'])) { + foreach($this->_res['tpl'][$tplidx]['images'] as $image) + $this->_out('/I' . $image['i'] . ' ' . $image['n'] . ' 0 R'); + } + if (isset($this->_res['tpl'][$tplidx]['tpls']) && count($this->_res['tpl'][$tplidx]['tpls'])) { + foreach($this->_res['tpl'][$tplidx]['tpls'] as $i => $tpl) + $this->_out($this->tplprefix . $i . ' ' . $tpl['n'] . ' 0 R'); + } + $this->_out('>>'); + } + $this->_out('>>'); + + $this->_out('/Length ' . strlen($p) . ' >>'); + $this->_putstream($p); + $this->_out('endobj'); + } + } + + /** + * Overwritten to add _putformxobjects() after _putimages() + * + */ + function _putimages() { + parent::_putimages(); + $this->_putformxobjects(); + } + + function _putxobjectdict() { + parent::_putxobjectdict(); + + if (count($this->tpls)) { + foreach($this->tpls as $tplidx => $tpl) { + $this->_out(sprintf('%s%d %d 0 R', $this->tplprefix, $tplidx, $tpl['n'])); + } + } + } + + /** + * Private Method + */ + function _out($s) { + if ($this->state == 2 && $this->_intpl) { + $this->tpls[$this->tpl]['buffer'] .= $s . "\n"; + } else { + parent::_out($s); + } + } +} diff --git a/vendor/iio/libmergepdf/tcpdi/tcpdi.php b/vendor/iio/libmergepdf/tcpdi/tcpdi.php new file mode 100644 index 0000000..f210d42 --- /dev/null +++ b/vendor/iio/libmergepdf/tcpdi/tcpdi.php @@ -0,0 +1,841 @@ +current_filename = $filename; + + if (!isset($this->parsers[$filename])) + $this->parsers[$filename] = $this->_getPdfParser($filename); + $this->current_parser =& $this->parsers[$filename]; + $this->setPDFVersion(max($this->getPDFVersion(), $this->current_parser->getPDFVersion())); + + return $this->parsers[$filename]->getPageCount(); + } + + /** + * Set a source-file PDF data + * + * @param string $pdfdata The PDF file content + * @return int number of available pages + */ + public function setSourceData($pdfdata) { + $filename = uniqid('tcpdi-'); + $this->current_filename = $filename; + + if (!isset($this->parsers[$filename])) + $this->parsers[$filename] = new tcpdi_parser($pdfdata, $filename); + $this->current_parser =& $this->parsers[$filename]; + $this->setPDFVersion(max($this->getPDFVersion(), $this->current_parser->getPDFVersion())); + + return $this->parsers[$filename]->getPageCount(); + } + + /** + * Returns a PDF parser object + * + * @param string $filename + * @return fpdi_pdf_parser + */ + protected function _getPdfParser($filename) { + $data = file_get_contents($filename); + return new tcpdi_parser($data, $filename); + } + + /** + * Get the current PDF version + * + * @return string + */ + public function getPDFVersion() { + return $this->PDFVersion; + } + + /** + * Set the PDF version + * + * @return string + */ + public function setPDFVersion($version = '1.3') { + $this->PDFVersion = $version; + } + + /** + * Import a page + * + * @param int $pageno pagenumber + * @return int Index of imported page - to use with fpdf_tpl::useTemplate() + */ + public function importPage($pageno, $boxName = '/CropBox') { + if ($this->_intpl) { + return $this->error('Please import the desired pages before creating a new template.'); + } + + $fn = $this->current_filename; + + // check if page already imported + $pageKey = $fn . '-' . ((int)$pageno) . $boxName; + if (isset($this->_importedPages[$pageKey])) + return $this->_importedPages[$pageKey]; + + $parser =& $this->parsers[$fn]; + $parser->setPageno($pageno); + + if (!in_array($boxName, $parser->availableBoxes)) + return $this->Error(sprintf('Unknown box: %s', $boxName)); + + $pageboxes = $parser->getPageBoxes($pageno, $this->k); + + /** + * MediaBox + * CropBox: Default -> MediaBox + * BleedBox: Default -> CropBox + * TrimBox: Default -> CropBox + * ArtBox: Default -> CropBox + */ + if (!isset($pageboxes[$boxName]) && ($boxName == '/BleedBox' || $boxName == '/TrimBox' || $boxName == '/ArtBox')) + $boxName = '/CropBox'; + if (!isset($pageboxes[$boxName]) && $boxName == '/CropBox') + $boxName = '/MediaBox'; + + if (!isset($pageboxes[$boxName])) + return false; + + $this->lastUsedPageBox = $boxName; + + $box = $pageboxes[$boxName]; + + $this->tpl++; + $this->tpls[$this->tpl] = array(); + $tpl =& $this->tpls[$this->tpl]; + $tpl['parser'] =& $parser; + $tpl['resources'] = $parser->getPageResources(); + $tpl['buffer'] = $parser->getContent(); + $tpl['box'] = $box; + + // To build an array that can be used by PDF_TPL::useTemplate() + $this->tpls[$this->tpl] = array_merge($this->tpls[$this->tpl], $box); + + // An imported page will start at 0,0 everytime. Translation will be set in _putformxobjects() + $tpl['x'] = 0; + $tpl['y'] = 0; + + // handle rotated pages + $rotation = $parser->getPageRotation($pageno); + $tpl['_rotationAngle'] = 0; + if (isset($rotation[1]) && ($angle = $rotation[1] % 360) != 0) { + $steps = $angle / 90; + + $_w = $tpl['w']; + $_h = $tpl['h']; + $tpl['w'] = $steps % 2 == 0 ? $_w : $_h; + $tpl['h'] = $steps % 2 == 0 ? $_h : $_w; + + if ($angle < 0) + $angle += 360; + + $tpl['_rotationAngle'] = $angle * -1; + } + + $this->_importedPages[$pageKey] = $this->tpl; + + return $this->tpl; + } + + public function setPageFormatFromTemplatePage($pageno, $orientation) { + $fn = $this->current_filename; + $parser =& $this->parsers[$fn]; + $parser->setPageno($pageno); + $boxes = $parser->getPageBoxes($pageno, $this->k); + foreach ($boxes as $name => $box) { + if ($name[0] == '/') { + $boxes[substr($name, 1)] = $box; + unset($boxes[$name]); + } + } + $this->setPageFormat($boxes, $orientation); + } + + /* Wrapper for AddPage() which tracks TOC pages to offset annotations later */ + public function AddPage($orientation='', $format='', $keepmargins=false, $tocpage=false) { + if ($this->inxobj) { + // we are inside an XObject template + return; + } + parent::AddPage($orientation, $format, $keepmargins, $tocpage); + if ($this->tocpage) { + $this->_numTOCpages++; + } + } + + /* Wrapper for AddTOC() which tracks TOC position to offset annotations later */ + public function AddTOC($page='', $numbersfont='', $filler='.', $toc_name='TOC', $style='', $color=array(0,0,0)) { + if (!TCPDF_STATIC::empty_string($page)) { + $this->_TOCpagenum = $page; + } else { + $this->_TOCpagenum = $this->page; + } + + parent::AddTOC($page, $numbersfont, $filler, $toc_name, $style, $color); + } + + public function importAnnotations($pageno) { + $fn = $this->current_filename; + $parser =& $this->parsers[$fn]; + $parser->setPageno($pageno); + $annots = $parser->getPageAnnotations(); + + if (is_array($annots) && $annots[0] == PDF_TYPE_OBJECT // We got an object + && is_array($annots[1]) && $annots[1][0] == PDF_TYPE_ARRAY // It's an array + && is_array($annots[1][1]) && count($annots[1][1] > 1) // It's not empty - there are annotations for this page + ) { + if (!isset($this->_obj_stack[$fn])) { + $this->_obj_stack[$fn] = array(); + } + + $this->_importedAnnots[$this->page] = array(); + foreach ($annots[1][1] as $annot) { + $this->importAnnotation($annot); + } + } + } + + public function importAnnotation($annotation) { + $fn = $this->current_filename; + $old_id = $annotation[1]; + $value = array(PDF_TYPE_OBJREF, $old_id, 0); + if (!isset($this->_don_obj_stack[$fn][$old_id])) { + $this->_newobj(false, true); + $this->_obj_stack[$fn][$old_id] = array($this->n, $value); + $this->_don_obj_stack[$fn][$old_id] = array($this->n, $value); + } + $objid = $this->_don_obj_stack[$fn][$old_id][0]; + $this->_importedAnnots[$this->page][] = $objid; + } + + /** + * Get references to page annotations. + * @param $n (int) page number + * @return string + * @protected + * @author Nicola Asuni + * @since 5.0.010 (2010-05-17) + */ + protected function _getannotsrefs($n) { + if (!empty($this->_numTOCpages) && $n >= $this->_TOCpagenum) { + // Offset page number to account for TOC being inserted before page containing annotations. + $n -= $this->_numTOCpages; + } + if (!(isset($this->_importedAnnots[$n]) OR isset($this->PageAnnots[$n]) OR ($this->sign AND isset($this->signature_data['cert_type'])))) { + return ''; + } + $out = ' /Annots ['; + if (isset($this->_importedAnnots[$n])) { + foreach ($this->_importedAnnots[$n] as $key => $val) { + $out .= ' '.$val.' 0 R'; + } + } + if (isset($this->PageAnnots[$n])) { + foreach ($this->PageAnnots[$n] as $key => $val) { + if (!in_array($val['n'], $this->radio_groups)) { + $out .= ' '.$val['n'].' 0 R'; + } + } + // add radiobutton groups + if (isset($this->radiobutton_groups[$n])) { + foreach ($this->radiobutton_groups[$n] as $key => $data) { + if (isset($data['n'])) { + $out .= ' '.$data['n'].' 0 R'; + } + } + } + } + if ($this->sign AND ($n == $this->signature_appearance['page']) AND isset($this->signature_data['cert_type'])) { + // set reference for signature object + $out .= ' '.$this->sig_obj_id.' 0 R'; + } + if (!empty($this->empty_signature_appearance)) { + foreach ($this->empty_signature_appearance as $esa) { + if ($esa['page'] == $n) { + // set reference for empty signature objects + $out .= ' '.$esa['objid'].' 0 R'; + } + } + } + $out .= ' ]'; + return $out; + } + + /** + * Returns the last used page box + * + * @return string + */ + public function getLastUsedPageBox() { + return $this->lastUsedPageBox; + } + + + public function useTemplate($tplidx, $_x = null, $_y = null, $_w = 0, $_h = 0, $adjustPageSize = false) { + if ($adjustPageSize == true && is_null($_x) && is_null($_y)) { + $size = $this->getTemplateSize($tplidx, $_w, $_h); + $orientation = $size['w'] > $size['h'] ? 'L' : 'P'; + $size = array($size['w'], $size['h']); + + $this->setPageFormat($size, $orientation); + } + + $this->_out('q 0 J 1 w 0 j 0 G 0 g'); // reset standard values + $s = parent::useTemplate($tplidx, $_x, $_y, $_w, $_h); + $this->_out('Q'); + + return $s; + } + + /** + * Private method, that rebuilds all needed objects of source files + */ + public function _putimportedobjects() { + if (is_array($this->parsers) && count($this->parsers) > 0) { + foreach($this->parsers AS $filename => $p) { + $this->current_parser =& $this->parsers[$filename]; + if (isset($this->_obj_stack[$filename]) && is_array($this->_obj_stack[$filename])) { + while(($n = key($this->_obj_stack[$filename])) !== null) { + $nObj = $this->current_parser->getObjectVal($this->_obj_stack[$filename][$n][1]); + + $this->_newobj($this->_obj_stack[$filename][$n][0]); + + if ($nObj[0] == PDF_TYPE_STREAM) { + $this->pdf_write_value($nObj); + } else { + $this->pdf_write_value($nObj[1]); + } + + $this->_out('endobj'); + $this->_obj_stack[$filename][$n] = null; // free memory + unset($this->_obj_stack[$filename][$n]); + reset($this->_obj_stack[$filename]); + } + } + + // We're done with this parser. Clean it up to free a bit of RAM. + $this->current_parser->cleanUp(); + unset($this->parsers[$filename]); + } + } + } + + + /** + * Private Method that writes the form xobjects + */ + public function _putformxobjects() { + $filter=($this->compress) ? '/Filter /FlateDecode ' : ''; + reset($this->tpls); + foreach($this->tpls AS $tplidx => $tpl) { + $p=($this->compress) ? gzcompress($tpl['buffer']) : $tpl['buffer']; + $this->_newobj(); + $cN = $this->n; // TCPDF/Protection: rem current "n" + + $this->tpls[$tplidx]['n'] = $this->n; + $this->_out('<<' . $filter . '/Type /XObject'); + $this->_out('/Subtype /Form'); + $this->_out('/FormType 1'); + + $this->_out(sprintf('/BBox [%.2F %.2F %.2F %.2F]', + (isset($tpl['box']['llx']) ? $tpl['box']['llx'] : $tpl['x']) * $this->k, + (isset($tpl['box']['lly']) ? $tpl['box']['lly'] : -$tpl['y']) * $this->k, + (isset($tpl['box']['urx']) ? $tpl['box']['urx'] : $tpl['w'] + $tpl['x']) * $this->k, + (isset($tpl['box']['ury']) ? $tpl['box']['ury'] : $tpl['h'] - $tpl['y']) * $this->k + )); + + $c = 1; + $s = 0; + $tx = 0; + $ty = 0; + + if (isset($tpl['box'])) { + $tx = -$tpl['box']['llx']; + $ty = -$tpl['box']['lly']; + + if ($tpl['_rotationAngle'] <> 0) { + $angle = $tpl['_rotationAngle'] * M_PI/180; + $c=cos($angle); + $s=sin($angle); + + switch($tpl['_rotationAngle']) { + case -90: + $tx = -$tpl['box']['lly']; + $ty = $tpl['box']['urx']; + break; + case -180: + $tx = $tpl['box']['urx']; + $ty = $tpl['box']['ury']; + break; + case -270: + $tx = $tpl['box']['ury']; + $ty = -$tpl['box']['llx']; + break; + } + } + } elseif ($tpl['x'] != 0 || $tpl['y'] != 0) { + $tx = -$tpl['x'] * 2; + $ty = $tpl['y'] * 2; + } + + $tx *= $this->k; + $ty *= $this->k; + + if ($c != 1 || $s != 0 || $tx != 0 || $ty != 0) { + $this->_out(sprintf('/Matrix [%.5F %.5F %.5F %.5F %.5F %.5F]', + $c, $s, -$s, $c, $tx, $ty + )); + } + + $this->_out('/Resources '); + + if (isset($tpl['resources'])) { + $this->current_parser =& $tpl['parser']; + $this->pdf_write_value($tpl['resources']); // "n" will be changed + } else { + $this->_out('<_res['tpl'][$tplidx]['fonts']) && count($this->_res['tpl'][$tplidx]['fonts'])) { + $this->_out('/Font <<'); + foreach($this->_res['tpl'][$tplidx]['fonts'] as $font) + $this->_out('/F' . $font['i'] . ' ' . $font['n'] . ' 0 R'); + $this->_out('>>'); + } + if(isset($this->_res['tpl'][$tplidx]['images']) && count($this->_res['tpl'][$tplidx]['images']) || + isset($this->_res['tpl'][$tplidx]['tpls']) && count($this->_res['tpl'][$tplidx]['tpls'])) + { + $this->_out('/XObject <<'); + if (isset($this->_res['tpl'][$tplidx]['images']) && count($this->_res['tpl'][$tplidx]['images'])) { + foreach($this->_res['tpl'][$tplidx]['images'] as $image) + $this->_out('/I' . $image['i'] . ' ' . $image['n'] . ' 0 R'); + } + if (isset($this->_res['tpl'][$tplidx]['tpls']) && count($this->_res['tpl'][$tplidx]['tpls'])) { + foreach($this->_res['tpl'][$tplidx]['tpls'] as $i => $tpl) + $this->_out($this->tplprefix . $i . ' ' . $tpl['n'] . ' 0 R'); + } + $this->_out('>>'); + } + $this->_out('>>'); + } + + $this->_out('/Group <>'); + + $nN = $this->n; // TCPDF: rem new "n" + $this->n = $cN; // TCPDF: reset to current "n" + + $p = $this->_getrawstream($p); + $this->_out('/Length ' . strlen($p) . ' >>'); + $this->_out("stream\n" . $p . "\nendstream"); + + $this->_out('endobj'); + $this->n = $nN; // TCPDF: reset to new "n" + } + + $this->_putimportedobjects(); + } + + /** + * Rewritten to handle existing own defined objects + */ + protected function _newobj($obj_id = false, $onlynewobj = false) { + if (!$obj_id) { + $obj_id = ++$this->n; + } + + //Begin a new object + if (!$onlynewobj) { + $this->offsets[$obj_id] = $this->bufferlen; + $this->_out($obj_id . ' 0 obj'); + $this->_current_obj_id = $obj_id; // for later use with encryption + } + + return $obj_id; + } + + /** + * Writes a value + * Needed to rebuild the source document + * + * @param mixed $value A PDF-Value. Structure of values see cases in this method + */ + public function pdf_write_value(&$value) + { + switch ($value[0]) { + case PDF_TYPE_STRING: + if ($this->encrypted) { + $value[1] = $this->_unescape($value[1]); + $value[1] = $this->_encrypt_data($this->_current_obj_id, $value[1]); + $value[1] = TCPDF_STATIC::_escape($value[1]); + } + break; + + case PDF_TYPE_STREAM: + if ($this->encrypted) { + $value[2][1] = $this->_encrypt_data($this->_current_obj_id, $value[2][1]); + $value[1][1]['/Length'] = array( + PDF_TYPE_NUMERIC, + strlen($value[2][1]) + ); + } + break; + + case PDF_TYPE_HEX: + if ($this->encrypted) { + $value[1] = $this->hex2str($value[1]); + $value[1] = $this->_encrypt_data($this->_current_obj_id, $value[1]); + + // remake hexstring of encrypted string + $value[1] = $this->str2hex($value[1]); + } + break; + } + + switch ($value[0]) { + + case PDF_TYPE_TOKEN: + $this->_straightOut('/'.$value[1] . ' '); + break; + case PDF_TYPE_NUMERIC: + case PDF_TYPE_REAL: + if (is_float($value[1]) && $value[1] != 0) { + $this->_straightOut(rtrim(rtrim(sprintf('%F', $value[1]), '0'), '.') . ' '); + } else { + $this->_straightOut($value[1] . ' '); + } + break; + + case PDF_TYPE_ARRAY: + + // An array. Output the proper + // structure and move on. + + $this->_straightOut('['); + for ($i = 0; $i < count($value[1]); $i++) { + $this->pdf_write_value($value[1][$i]); + } + + $this->_out(']'); + break; + + case PDF_TYPE_DICTIONARY: + + // A dictionary. + $this->_straightOut('<<'); + + foreach ($value[1] as $k => $v) { + $this->_straightOut($k . ' '); + $this->pdf_write_value($v); + } + + $this->_straightOut('>>'); + break; + + case PDF_TYPE_OBJREF: + + // An indirect object reference + // Fill the object stack if needed + $cpfn =& $this->current_parser->uniqueid; + + if (!isset($this->_don_obj_stack[$cpfn][$value[1]])) { + $this->_newobj(false, true); + $this->_obj_stack[$cpfn][$value[1]] = array($this->n, $value); + $this->_don_obj_stack[$cpfn][$value[1]] = array($this->n, $value); // Value is maybee obsolete!!! + } + $objid = $this->_don_obj_stack[$cpfn][$value[1]][0]; + + $this->_out($objid . ' 0 R'); + break; + + case PDF_TYPE_STRING: + + // A string. + $this->_straightOut('(' . $value[1] . ')'); + + break; + + case PDF_TYPE_STREAM: + + // A stream. First, output the + // stream dictionary, then the + // stream data itself. + $this->pdf_write_value($value[1]); + $this->_out('stream'); + $this->_out($value[2][1]); + $this->_out('endstream'); + break; + + case PDF_TYPE_HEX: + $this->_straightOut('<' . $value[1] . '>'); + break; + + case PDF_TYPE_BOOLEAN: + $this->_straightOut($value[1] ? 'true ' : 'false '); + break; + + case PDF_TYPE_NULL: + // The null object. + + $this->_straightOut('null '); + break; + } + } + + /** + * Modified so not each call will add a newline to the output. + */ + protected function _straightOut($s) { + if ($this->state == 2) { + if ($this->inxobj) { + // we are inside an XObject template + $this->xobjects[$this->xobjid]['outdata'] .= $s; + } elseif ((!$this->InFooter) AND isset($this->footerlen[$this->page]) AND ($this->footerlen[$this->page] > 0)) { + // puts data before page footer + $pagebuff = $this->getPageBuffer($this->page); + $page = substr($pagebuff, 0, -$this->footerlen[$this->page]); + $footer = substr($pagebuff, -$this->footerlen[$this->page]); + $this->setPageBuffer($this->page, $page.$s.$footer); + // update footer position + $this->footerpos[$this->page] += strlen($s); + } else { + // set page data + $this->setPageBuffer($this->page, $s, true); + } + } elseif ($this->state > 0) { + // set general data + $this->setBuffer($s); + } + } + + /** + * rewritten to close opened parsers + * + */ + protected function _enddoc() { + parent::_enddoc(); + $this->_closeParsers(); + } + + /** + * close all files opened by parsers + */ + protected function _closeParsers() { + if ($this->state > 2 && count($this->parsers) > 0) { + $this->cleanUp(); + return true; + } + return false; + } + + /** + * Removes cylced references and closes the file handles of the parser objects + */ + public function cleanUp() { + foreach ($this->parsers as $k => $_){ + $this->parsers[$k]->cleanUp(); + $this->parsers[$k] = null; + unset($this->parsers[$k]); + } + } + + // Functions from here on are taken from FPDI's fpdi2tcpdf_bridge.php to remove dependence on it + protected function _putstream($s, $n=0) { + $this->_out($this->_getstream($s, $n)); + } + + protected function _getxobjectdict() { + $out = parent::_getxobjectdict(); + if (count($this->tpls)) { + foreach($this->tpls as $tplidx => $tpl) { + $out .= sprintf('%s%d %d 0 R', $this->tplprefix, $tplidx, $tpl['n']); + } + } + + return $out; + } + + /** + * Unescapes a PDF string + * + * @param string $s + * @return string + */ + protected function _unescape($s) { + $out = ''; + for ($count = 0, $n = strlen($s); $count < $n; $count++) { + if ($s[$count] != '\\' || $count == $n-1) { + $out .= $s[$count]; + } else { + switch ($s[++$count]) { + case ')': + case '(': + case '\\': + $out .= $s[$count]; + break; + case 'f': + $out .= chr(0x0C); + break; + case 'b': + $out .= chr(0x08); + break; + case 't': + $out .= chr(0x09); + break; + case 'r': + $out .= chr(0x0D); + break; + case 'n': + $out .= chr(0x0A); + break; + case "\r": + if ($count != $n-1 && $s[$count+1] == "\n") + $count++; + break; + case "\n": + break; + default: + // Octal-Values + if (ord($s[$count]) >= ord('0') && + ord($s[$count]) <= ord('9')) { + $oct = ''. $s[$count]; + + if (ord($s[$count+1]) >= ord('0') && + ord($s[$count+1]) <= ord('9')) { + $oct .= $s[++$count]; + + if (ord($s[$count+1]) >= ord('0') && + ord($s[$count+1]) <= ord('9')) { + $oct .= $s[++$count]; + } + } + + $out .= chr(octdec($oct)); + } else { + $out .= $s[$count]; + } + } + } + } + return $out; + } + + /** + * Hexadecimal to string + * + * @param string $hex + * @return string + */ + public function hex2str($hex) { + return pack('H*', str_replace(array("\r", "\n", ' '), '', $hex)); + } + + /** + * String to hexadecimal + * + * @param string $str + * @return string + */ + public function str2hex($str) { + return current(unpack('H*', $str)); + } +} diff --git a/vendor/iio/libmergepdf/tcpdi/tcpdi_parser.php b/vendor/iio/libmergepdf/tcpdi/tcpdi_parser.php new file mode 100644 index 0000000..2391934 --- /dev/null +++ b/vendor/iio/libmergepdf/tcpdi/tcpdi_parser.php @@ -0,0 +1,1450 @@ +. +// +// See LICENSE file for more information. +// ------------------------------------------------------------------- +// +// Description : This is a PHP class for parsing PDF documents. +// +//============================================================+ + +/** + * @file + * This is a PHP class for parsing PDF documents.
+ * @author Paul Nicholls + * @author Nicola Asuni + * @version 1.1 + */ + +if (!defined ('PDF_TYPE_NULL')) + define ('PDF_TYPE_NULL', 0); +if (!defined ('PDF_TYPE_NUMERIC')) + define ('PDF_TYPE_NUMERIC', 1); +if (!defined ('PDF_TYPE_TOKEN')) + define ('PDF_TYPE_TOKEN', 2); +if (!defined ('PDF_TYPE_HEX')) + define ('PDF_TYPE_HEX', 3); +if (!defined ('PDF_TYPE_STRING')) + define ('PDF_TYPE_STRING', 4); +if (!defined ('PDF_TYPE_DICTIONARY')) + define ('PDF_TYPE_DICTIONARY', 5); +if (!defined ('PDF_TYPE_ARRAY')) + define ('PDF_TYPE_ARRAY', 6); +if (!defined ('PDF_TYPE_OBJDEC')) + define ('PDF_TYPE_OBJDEC', 7); +if (!defined ('PDF_TYPE_OBJREF')) + define ('PDF_TYPE_OBJREF', 8); +if (!defined ('PDF_TYPE_OBJECT')) + define ('PDF_TYPE_OBJECT', 9); +if (!defined ('PDF_TYPE_STREAM')) + define ('PDF_TYPE_STREAM', 10); +if (!defined ('PDF_TYPE_BOOLEAN')) + define ('PDF_TYPE_BOOLEAN', 11); +if (!defined ('PDF_TYPE_REAL')) + define ('PDF_TYPE_REAL', 12); + +/** + * @class tcpdi_parser + * This is a PHP class for parsing PDF documents.
+ * Based on TCPDF_PARSER, part of the TCPDF project by Nicola Asuni. + * @brief This is a PHP class for parsing PDF documents.. + * @version 1.1 + * @author Paul Nicholls - github.com/pauln + * @author Nicola Asuni - info@tecnick.com + */ +class tcpdi_parser { + /** + * Unique parser ID + * @public + */ + public $uniqueid = ''; + + /** + * Raw content of the PDF document. + * @private + */ + private $pdfdata = ''; + + /** + * XREF data. + * @protected + */ + protected $xref = array(); + + /** + * Object streams. + * @protected + */ + protected $objstreams = array(); + + /** + * Objects in objstreams. + * @protected + */ + protected $objstreamobjs = array(); + + /** + * List of seen XREF data locations. + * @protected + */ + protected $xref_seen_offsets = array(); + + /** + * Array of PDF objects. + * @protected + */ + protected $objects = array(); + + /** + * Array of object offsets. + * @private + */ + private $objoffsets = array(); + + /** + * Class object for decoding filters. + * @private + */ + private $FilterDecoders; + + /** + * Pages + * + * @private array + */ + private $pages; + + /** + * Page count + * @private integer + */ + private $page_count; + + /** + * actual page number + * @private integer + */ + private $pageno; + + /** + * PDF version of the loaded document + * @private string + */ + private $pdfVersion; + + /** + * Available BoxTypes + * + * @public array + */ + public $availableBoxes = array('/MediaBox', '/CropBox', '/BleedBox', '/TrimBox', '/ArtBox'); + +// ----------------------------------------------------------------------------- + + /** + * Parse a PDF document an return an array of objects. + * @param $data (string) PDF data to parse. + * @public + * @since 1.0.000 (2011-05-24) + */ + public function __construct($data, $uniqueid) { + if (empty($data)) { + $this->Error('Empty PDF data.'); + } + $this->uniqueid = $uniqueid; + $this->pdfdata = $data; + // get length + $pdflen = strlen($this->pdfdata); + // initialize class for decoding filters + $this->FilterDecoders = new TCPDF_FILTERS(); + // get xref and trailer data + $this->xref = $this->getXrefData(); + $this->findObjectOffsets(); + // parse all document objects + $this->objects = array(); + /*foreach ($this->xref['xref'] as $obj => $offset) { + if (!isset($this->objects[$obj]) AND ($offset > 0)) { + // decode only objects with positive offset + //$this->objects[$obj] = $this->getIndirectObject($obj, $offset, true); + } + }*/ + $this->getPDFVersion(); + $this->readPages(); + } + + /** + * Clean up when done, to free memory etc + */ + public function cleanUp() { + unset($this->pdfdata); + $this->pdfdata = ''; + unset($this->objstreams); + $this->objstreams = array(); + unset($this->objects); + $this->objects = array(); + unset($this->objstreamobjs); + $this->objstreamobjs = array(); + unset($this->xref); + $this->xref = array(); + unset($this->objoffsets); + $this->objoffsets = array(); + unset($this->pages); + $this->pages = array(); + } + + /** + * Return an array of parsed PDF document objects. + * @return (array) Array of parsed PDF document objects. + * @public + * @since 1.0.000 (2011-06-26) + */ + public function getParsedData() { + return array($this->xref, $this->objects, $this->pages); + } + + /** + * Get PDF-Version + * + * And reset the PDF Version used in FPDI if needed + * @public + */ + public function getPDFVersion() { + preg_match('/\d\.\d/', substr($this->pdfdata, 0, 16), $m); + if (isset($m[0])) + $this->pdfVersion = $m[0]; + return $this->pdfVersion; + } + + /** + * Read all /Page(es) + * + */ + function readPages() { + $params = $this->getObjectVal($this->xref['trailer'][1]['/Root']); + $objref = null; + if ($params && $params[1] && is_array($params[1][1])) { + foreach ($params[1][1] as $k=>$v) { + if ($k == '/Pages') { + $objref = $v; + break; + } + } + } + if ($objref == null || $objref[0] !== PDF_TYPE_OBJREF) { + // Offset not found. + return; + } + + $dict = $this->getObjectVal($objref); + if ($dict[0] == PDF_TYPE_OBJECT && $dict[1][0] == PDF_TYPE_DICTIONARY) { + // Dict wrapped in an object + $dict = $dict[1]; + } + + if ($dict[0] !== PDF_TYPE_DICTIONARY) { + return; + } + + $this->pages = array(); + if (isset($dict[1]['/Kids'])) { + $v = $dict[1]['/Kids']; + if ($v[0] == PDF_TYPE_ARRAY) { + foreach ($v[1] as $ref) { + $page = $this->getObjectVal($ref); + $this->readPage($page); + } + } + } + + $this->page_count = count($this->pages); + } + + /** + * Read a single /Page element, recursing through /Kids if necessary + * + */ + private function readPage($page) { + if (isset($page[1][1]['/Kids'])) { + // Nested pages! + foreach ($page[1][1]['/Kids'][1] as $subref) { + $subpage = $this->getObjectVal($subref); + $this->readPage($subpage); + } + } else { + $this->pages[] = $page; + } + } + + /** + * Get pagecount from sourcefile + * + * @return int + */ + function getPageCount() { + return $this->page_count; + } + + /** + * Get Cross-Reference (xref) table and trailer data from PDF document data. + * @param $offset (int) xref offset (if know). + * @param $xref (array) previous xref array (if any). + * @return Array containing xref and trailer data. + * @protected + * @since 1.0.000 (2011-05-24) + */ + protected function getXrefData($offset=0, $xref=array()) { + if ($offset == 0) { + // find last startxref + if (preg_match('/.*[\r\n]startxref[\s\r\n]+([0-9]+)[\s\r\n]+%%EOF/is', $this->pdfdata, $matches) == 0) { + $this->Error('Unable to find startxref'); + } + $startxref = $matches[1]; + } else { + if (preg_match('/([0-9]+[\s][0-9]+[\s]obj)/i', $this->pdfdata, $matches, PREG_OFFSET_CAPTURE, $offset)) { + // Cross-Reference Stream object + $startxref = $offset; + } elseif (preg_match('/[\r\n]startxref[\s\r\n]+([0-9]+)[\s\r\n]+%%EOF/i', $this->pdfdata, $matches, PREG_OFFSET_CAPTURE, $offset)) { + // startxref found + $startxref = $matches[1][0]; + } else { + $this->Error('Unable to find startxref'); + } + } + unset($matches); + + // DOMPDF gets the startxref wrong, giving us the linebreak before the xref starts. + $startxref += strspn($this->pdfdata, "\r\n", $startxref); + + // check xref position + if (strpos($this->pdfdata, 'xref', $startxref) == $startxref) { + // Cross-Reference + $xref = $this->decodeXref($startxref, $xref); + } else { + // Cross-Reference Stream + $xref = $this->decodeXrefStream($startxref, $xref); + } + if (empty($xref)) { + $this->Error('Unable to find xref'); + } + + return $xref; + } + + /** + * Decode the Cross-Reference section + * @param $startxref (int) Offset at which the xref section starts. + * @param $xref (array) Previous xref array (if any). + * @return Array containing xref and trailer data. + * @protected + * @since 1.0.000 (2011-06-20) + */ + protected function decodeXref($startxref, $xref=array()) { + $this->xref_seen_offsets[] = $startxref; + if (!isset($xref['xref_location'])) { + $xref['xref_location'] = $startxref; + $xref['max_object'] = 0; + } + // extract xref data (object indexes and offsets) + $xoffset = $startxref + 5; + // initialize object number + $obj_num = 0; + $offset = $xoffset; + while (preg_match('/^([0-9]+)[\s]([0-9]+)[\s]?([nf]?)/im', $this->pdfdata, $matches, PREG_OFFSET_CAPTURE, $offset) > 0) { + $offset = (strlen($matches[0][0]) + $matches[0][1]); + if ($matches[3][0] == 'n') { + // create unique object index: [object number]_[generation number] + $gen_num = intval($matches[2][0]); + $index = $obj_num.'_'.$gen_num; + // check if object already exist + if (!isset($xref['xref'][$obj_num][$gen_num])) { + // store object offset position + $xref['xref'][$obj_num][$gen_num] = intval($matches[1][0]); + } + ++$obj_num; + $offset += 2; + } elseif ($matches[3][0] == 'f') { + ++$obj_num; + $offset += 2; + } else { + // object number (index) + $obj_num = intval($matches[1][0]); + } + } + unset($matches); + $xref['max_object'] = max($xref['max_object'], $obj_num); + // get trailer data + if (preg_match('/trailer[\s]*<<(.*)>>[\s\r\n]+(?:[%].*[\r\n]+)*startxref[\s\r\n]+/isU', $this->pdfdata, $matches, PREG_OFFSET_CAPTURE, $xoffset) > 0) { + $trailer_data = $matches[1][0]; + if (!isset($xref['trailer']) OR empty($xref['trailer'])) { + // get only the last updated version + $xref['trailer'] = array(); + $xref['trailer'][0] = PDF_TYPE_DICTIONARY; + $xref['trailer'][1] = array(); + // parse trailer_data + if (preg_match('/Size[\s]+([0-9]+)/i', $trailer_data, $matches) > 0) { + $xref['trailer'][1]['/Size'] = array(PDF_TYPE_NUMERIC, intval($matches[1])); + } + if (preg_match('/Root[\s]+([0-9]+)[\s]+([0-9]+)[\s]+R/i', $trailer_data, $matches) > 0) { + $xref['trailer'][1]['/Root'] = array(PDF_TYPE_OBJREF, intval($matches[1]), intval($matches[2])); + } + if (preg_match('/Encrypt[\s]+([0-9]+)[\s]+([0-9]+)[\s]+R/i', $trailer_data, $matches) > 0) { + $xref['trailer'][1]['/Encrypt'] = array(PDF_TYPE_OBJREF, intval($matches[1]), intval($matches[2])); + } + if (preg_match('/Info[\s]+([0-9]+)[\s]+([0-9]+)[\s]+R/i', $trailer_data, $matches) > 0) { + $xref['trailer'][1]['/Info'] = array(PDF_TYPE_OBJREF, intval($matches[1]), intval($matches[2])); + } + if (preg_match('/ID[\s]*[\[][\s]*[<]([^>]*)[>][\s]*[<]([^>]*)[>]/i', $trailer_data, $matches) > 0) { + $xref['trailer'][1]['/ID'] = array(PDF_TYPE_ARRAY, array()); + $xref['trailer'][1]['/ID'][1][0] = array(PDF_TYPE_HEX, $matches[1]); + $xref['trailer'][1]['/ID'][1][1] = array(PDF_TYPE_HEX, $matches[2]); + } + } + if (preg_match('/Prev[\s]+([0-9]+)/i', $trailer_data, $matches) > 0) { + // get previous xref + $prevoffset = intval($matches[1]); + if (!in_array($prevoffset, $this->xref_seen_offsets)) { + $this->xref_seen_offsets[] = $prevoffset; + $xref = $this->getXrefData($prevoffset, $xref); + } + } + unset($matches); + } else { + $this->Error('Unable to find trailer'); + } + return $xref; + } + + /** + * Decode the Cross-Reference Stream section + * @param $startxref (int) Offset at which the xref section starts. + * @param $xref (array) Previous xref array (if any). + * @return Array containing xref and trailer data. + * @protected + * @since 1.0.003 (2013-03-16) + */ + protected function decodeXrefStream($startxref, $xref=array()) { + // try to read Cross-Reference Stream + list($xrefobj, $unused) = $this->getRawObject($startxref); + $xrefcrs = $this->getIndirectObject($xrefobj[1], $startxref, true); + if (!isset($xref['xref_location'])) { + $xref['xref_location'] = $startxref; + $xref['max_object'] = 0; + } + if (!isset($xref['xref'])) { + $xref['xref'] = array(); + } + if (!isset($xref['trailer']) OR empty($xref['trailer'])) { + // get only the last updated version + $xref['trailer'] = array(); + $xref['trailer'][0] = PDF_TYPE_DICTIONARY; + $xref['trailer'][1] = array(); + $filltrailer = true; + } else { + $filltrailer = false; + } + $valid_crs = false; + $sarr = $xrefcrs[0][1]; + $keys = array_keys($sarr); + $columns = 1; // Default as per PDF 32000-1:2008. + $predictor = 1; // Default as per PDF 32000-1:2008. + foreach ($keys as $k=>$key) { + $v = $sarr[$key]; + if (($key == '/Type') AND ($v[0] == PDF_TYPE_TOKEN AND ($v[1] == 'XRef'))) { + $valid_crs = true; + } elseif (($key == '/Index') AND ($v[0] == PDF_TYPE_ARRAY AND count($v[1]) >= 2)) { + // first object number in the subsection + $index_first = intval($v[1][0][1]); + // number of entries in the subsection + $index_entries = intval($v[1][1][1]); + } elseif (($key == '/Prev') AND ($v[0] == PDF_TYPE_NUMERIC)) { + // get previous xref offset + $prevxref = intval($v[1]); + } elseif (($key == '/W') AND ($v[0] == PDF_TYPE_ARRAY)) { + // number of bytes (in the decoded stream) of the corresponding field + $wb = array(); + $wb[0] = intval($v[1][0][1]); + $wb[1] = intval($v[1][1][1]); + $wb[2] = intval($v[1][2][1]); + } elseif (($key == '/DecodeParms') AND ($v[0] == PDF_TYPE_DICTIONARY)) { + $decpar = $v[1]; + foreach ($decpar as $kdc => $vdc) { + if (($kdc == '/Columns') AND ($vdc[0] == PDF_TYPE_NUMERIC)) { + $columns = intval($vdc[1]); + } elseif (($kdc == '/Predictor') AND ($vdc[0] == PDF_TYPE_NUMERIC)) { + $predictor = intval($vdc[1]); + } + } + } elseif ($filltrailer) { + switch($key) { + case '/Size': + case '/Root': + case '/Info': + case '/ID': + $xref['trailer'][1][$key] = $v; + break; + default: + break; + } + } + } + // decode data + $obj_num = 0; + if ($valid_crs AND isset($xrefcrs[1][3][0])) { + // number of bytes in a row + $rowlen = ($columns + 1); + // convert the stream into an array of integers + $sdata = unpack('C*', $xrefcrs[1][3][0]); + // split the rows + $sdata = array_chunk($sdata, $rowlen); + // initialize decoded array + $ddata = array(); + // initialize first row with zeros + $prev_row = array_fill (0, $rowlen, 0); + // for each row apply PNG unpredictor + foreach ($sdata as $k => $row) { + // initialize new row + $ddata[$k] = array(); + // get PNG predictor value + if (empty($predictor)) { + $predictor = (10 + $row[0]); + } + // for each byte on the row + for ($i=1; $i<=$columns; ++$i) { + if (!isset($row[$i])) { + // No more data in this row - we're done here. + break; + } + // new index + $j = ($i - 1); + $row_up = $prev_row[$j]; + if ($i == 1) { + $row_left = 0; + $row_upleft = 0; + } else { + $row_left = $row[($i - 1)]; + $row_upleft = $prev_row[($j - 1)]; + } + switch ($predictor) { + case 1: // No prediction (equivalent to PNG None) + case 10: { // PNG prediction (on encoding, PNG None on all rows) + $ddata[$k][$j] = $row[$i]; + break; + } + case 11: { // PNG prediction (on encoding, PNG Sub on all rows) + $ddata[$k][$j] = (($row[$i] + $row_left) & 0xff); + break; + } + case 12: { // PNG prediction (on encoding, PNG Up on all rows) + $ddata[$k][$j] = (($row[$i] + $row_up) & 0xff); + break; + } + case 13: { // PNG prediction (on encoding, PNG Average on all rows) + $ddata[$k][$j] = (($row[$i] + (($row_left + $row_up) / 2)) & 0xff); + break; + } + case 14: { // PNG prediction (on encoding, PNG Paeth on all rows) + // initial estimate + $p = ($row_left + $row_up - $row_upleft); + // distances + $pa = abs($p - $row_left); + $pb = abs($p - $row_up); + $pc = abs($p - $row_upleft); + $pmin = min($pa, $pb, $pc); + // return minumum distance + switch ($pmin) { + case $pa: { + $ddata[$k][$j] = (($row[$i] + $row_left) & 0xff); + break; + } + case $pb: { + $ddata[$k][$j] = (($row[$i] + $row_up) & 0xff); + break; + } + case $pc: { + $ddata[$k][$j] = (($row[$i] + $row_upleft) & 0xff); + break; + } + } + break; + } + default: { // PNG prediction (on encoding, PNG optimum) + $this->Error("Unknown PNG predictor $predictor"); + break; + } + } + } + $prev_row = $ddata[$k]; + } // end for each row + // complete decoding + unset($sdata); + $sdata = array(); + // for every row + foreach ($ddata as $k => $row) { + // initialize new row + $sdata[$k] = array(0, 0, 0); + if ($wb[0] == 0) { + // default type field + $sdata[$k][0] = 1; + } + $i = 0; // count bytes on the row + // for every column + for ($c = 0; $c < 3; ++$c) { + // for every byte on the column + for ($b = 0; $b < $wb[$c]; ++$b) { + if (isset($row[$i])) { + $sdata[$k][$c] += ($row[$i] << (($wb[$c] - 1 - $b) * 8)); + } + ++$i; + } + } + } + unset($ddata); + // fill xref + if (isset($index_first)) { + $obj_num = $index_first; + } else { + $obj_num = 0; + } + foreach ($sdata as $k => $row) { + switch ($row[0]) { + case 0: { // (f) linked list of free objects + ++$obj_num; + break; + } + case 1: { // (n) objects that are in use but are not compressed + // create unique object index: [object number]_[generation number] + $index = $obj_num.'_'.$row[2]; + // check if object already exist + if (!isset($xref['xref'][$obj_num][$row[2]])) { + // store object offset position + $xref['xref'][$obj_num][$row[2]] = $row[1]; + } + ++$obj_num; + break; + } + case 2: { // compressed objects + // $row[1] = object number of the object stream in which this object is stored + // $row[2] = index of this object within the object stream + /*$index = $row[1].'_0_'.$row[2]; + $xref['xref'][$row[1]][0][$row[2]] = -1;*/ + break; + } + default: { // null objects + break; + } + } + } + } // end decoding data + $xref['max_object'] = max($xref['max_object'], $obj_num); + if (isset($prevxref)) { + // get previous xref + $xref = $this->getXrefData($prevxref, $xref); + } + return $xref; + } + + /** + * Get raw stream data + * @param $offset (int) Stream offset. + * @param $length (int) Stream length. + * @return string Steam content + * @protected + */ + protected function getRawStream($offset, $length) { + $offset += strspn($this->pdfdata, "\x00\x09\x0a\x0c\x0d\x20", $offset); + $offset += 6; // "stream" + $offset += strspn($this->pdfdata, "\x20", $offset); + $offset += strspn($this->pdfdata, "\r\n", $offset); + + $obj = array(); + $obj[] = PDF_TYPE_STREAM; + $obj[] = substr($this->pdfdata, $offset, $length); + + return array($obj, $offset+$length); + } + + /** + * Get object type, raw value and offset to next object + * @param $offset (int) Object offset. + * @return array containing object type, raw value and offset to next object + * @protected + * @since 1.0.000 (2011-06-20) + */ + protected function getRawObject($offset=0, $data=null) { + if ($data == null) { + $data =& $this->pdfdata; + } + $objtype = ''; // object type to be returned + $objval = ''; // object value to be returned + // skip initial white space chars: \x00 null (NUL), \x09 horizontal tab (HT), \x0A line feed (LF), \x0C form feed (FF), \x0D carriage return (CR), \x20 space (SP) + while (strspn($data[$offset], "\x00\x09\x0a\x0c\x0d\x20") == 1) { + $offset++; + } + // get first char + $char = $data[$offset]; + // get object type + switch ($char) { + case '%': { // \x25 PERCENT SIGN + // skip comment and search for next token + $next = strcspn($data, "\r\n", $offset); + if ($next > 0) { + $offset += $next; + list($obj, $unused) = $this->getRawObject($offset, $data); + return $obj; + } + break; + } + case '/': { // \x2F SOLIDUS + // name object + $objtype = PDF_TYPE_TOKEN; + ++$offset; + $length = strcspn($data, "\x00\x09\x0a\x0c\x0d\x20\x28\x29\x3c\x3e\x5b\x5d\x7b\x7d\x2f\x25", $offset); + $objval = substr($data, $offset, $length); + $offset += $length; + break; + } + case '(': // \x28 LEFT PARENTHESIS + case ')': { // \x29 RIGHT PARENTHESIS + // literal string object + $objtype = PDF_TYPE_STRING; + ++$offset; + $strpos = $offset; + if ($char == '(') { + $open_bracket = 1; + while ($open_bracket > 0) { + if (!isset($data[$strpos])) { + break; + } + $ch = $data[$strpos]; + switch ($ch) { + case '\\': { // REVERSE SOLIDUS (5Ch) (Backslash) + // skip next character + ++$strpos; + break; + } + case '(': { // LEFT PARENHESIS (28h) + ++$open_bracket; + break; + } + case ')': { // RIGHT PARENTHESIS (29h) + --$open_bracket; + break; + } + } + ++$strpos; + } + $objval = substr($data, $offset, ($strpos - $offset - 1)); + $offset = $strpos; + } + break; + } + case '[': // \x5B LEFT SQUARE BRACKET + case ']': { // \x5D RIGHT SQUARE BRACKET + // array object + $objtype = PDF_TYPE_ARRAY; + ++$offset; + if ($char == '[') { + // get array content + $objval = array(); + do { + // get element + list($element, $offset) = $this->getRawObject($offset, $data); + $objval[] = $element; + } while ($element[0] !== ']'); + // remove closing delimiter + array_pop($objval); + } else { + $objtype = ']'; + } + break; + } + case '<': // \x3C LESS-THAN SIGN + case '>': { // \x3E GREATER-THAN SIGN + if (isset($data[($offset + 1)]) AND ($data[($offset + 1)] == $char)) { + // dictionary object + $objtype = PDF_TYPE_DICTIONARY; + if ($char == '<') { + list ($objval, $offset) = $this->getDictValue($offset, $data); + } else { + $objtype = '>>'; + $offset += 2; + } + } else { + // hexadecimal string object + $objtype = PDF_TYPE_HEX; + ++$offset; + // The "Panose" entry in the FontDescriptor Style dict seems to have hex bytes separated by spaces. + if (($char == '<') AND (preg_match('/^([0-9A-Fa-f ]+)[>]/iU', substr($data, $offset), $matches) == 1)) { + $objval = $matches[1]; + $offset += strlen($matches[0]); + unset($matches); + } + } + break; + } + default: { + $frag = $data[$offset] . @$data[$offset+1] . @$data[$offset+2] . @$data[$offset+3]; + switch ($frag) { + case 'endo': + // indirect object + $objtype = 'endobj'; + $offset += 6; + break; + case 'stre': + // Streams should always be indirect objects, and thus processed by getRawStream(). + // If we get here, treat it as a null object as something has gone wrong. + case 'null': + // null object + $objtype = PDF_TYPE_NULL; + $offset += 4; + $objval = 'null'; + break; + case 'true': + // boolean true object + $objtype = PDF_TYPE_BOOLEAN; + $offset += 4; + $objval = true; + break; + case 'fals': + // boolean false object + $objtype = PDF_TYPE_BOOLEAN; + $offset += 5; + $objval = false; + break; + case 'ends': + // end stream object + $objtype = 'endstream'; + $offset += 9; + break; + default: + if (preg_match('/^([0-9]+)[\s]+([0-9]+)[\s]+([Robj]{1,3})/i', substr($data, $offset, 33), $matches) == 1) { + if ($matches[3] == 'R') { + // indirect object reference + $objtype = PDF_TYPE_OBJREF; + $offset += strlen($matches[0]); + $objval = array(intval($matches[1]), intval($matches[2])); + } elseif ($matches[3] == 'obj') { + // object start + $objtype = PDF_TYPE_OBJECT; + $objval = intval($matches[1]).'_'.intval($matches[2]); + $offset += strlen ($matches[0]); + } + } elseif (($numlen = strspn($data, '+-.0123456789', $offset)) > 0) { + // numeric object + $objval = substr($data, $offset, $numlen); + $objtype = (intval($objval) != $objval) ? PDF_TYPE_REAL : PDF_TYPE_NUMERIC; + $offset += $numlen; + } + unset($matches); + break; + } + break; + } + } + $obj = array(); + $obj[] = $objtype; + if ($objtype == PDF_TYPE_OBJREF && is_array($objval)) { + foreach ($objval as $val) { + $obj[] = $val; + } + } else { + $obj[] = $objval; + } + return array($obj, $offset); + } + private function getDictValue($offset, &$data) { + $objval = array(); + + // Extract dict from data. + $i=1; + $dict = ''; + $offset += 2; + do { + if ($data[$offset] == '>' && $data[$offset+1] == '>') { + $i--; + $dict .= '>>'; + $offset += 2; + } else if ($data[$offset] == '<' && $data[$offset+1] == '<') { + $i++; + $dict .= '<<'; + $offset += 2; + } else { + $dict .= $data[$offset]; + $offset++; + } + } while ($i>0); + + // Now that we have just the dict, parse it. + $dictoffset = 0; + do { + // Get dict element. + list($key, $eloffset) = $this->getRawObject($dictoffset, $dict); + if ($key[0] == '>>') { + break; + } + list($element, $dictoffset) = $this->getRawObject($eloffset, $dict); + $objval['/'.$key[1]] = $element; + unset($key); + unset($element); + } while (true); + + return array($objval, $offset); + } + + /** + * Get content of indirect object. + * @param $obj_ref (string) Object number and generation number separated by underscore character. + * @param $offset (int) Object offset. + * @param $decoding (boolean) If true decode streams. + * @return array containing object data. + * @protected + * @since 1.0.000 (2011-05-24) + */ + protected function getIndirectObject($obj_ref, $offset=0, $decoding=true) { + $obj = explode('_', $obj_ref); + if (($obj === false) OR (count($obj) != 2)) { + $this->Error('Invalid object reference: '.$obj); + return; + } + $objref = $obj[0].' '.$obj[1].' obj'; + + if (strpos($this->pdfdata, $objref, $offset) != $offset) { + // an indirect reference to an undefined object shall be considered a reference to the null object + return array('null', 'null', $offset); + } + // starting position of object content + $offset += strlen($objref); + // get array of object content + $objdata = array(); + $i = 0; // object main index + do { + if (($i > 0) AND (isset($objdata[($i - 1)][0])) AND ($objdata[($i - 1)][0] == PDF_TYPE_DICTIONARY) AND array_key_exists('/Length', $objdata[($i - 1)][1])) { + // Stream - get using /Length in stream's dict + $lengthobj = $objdata[($i-1)][1]['/Length']; + if ($lengthobj[0] === PDF_TYPE_OBJREF) { + $lengthobj = $this->getObjectVal($lengthobj); + if ($lengthobj[0] === PDF_TYPE_OBJECT) { + $lengthobj = $lengthobj[1]; + } + } + $streamlength = $lengthobj[1]; + list($element, $offset) = $this->getRawStream($offset, $streamlength); + } else { + // get element + list($element, $offset) = $this->getRawObject($offset); + } + // decode stream using stream's dictionary information + if ($decoding AND ($element[0] == PDF_TYPE_STREAM) AND (isset($objdata[($i - 1)][0])) AND ($objdata[($i - 1)][0] == PDF_TYPE_DICTIONARY)) { + $element[3] = $this->decodeStream($objdata[($i - 1)][1], $element[1]); + } + $objdata[$i] = $element; + ++$i; + } while ($element[0] != 'endobj'); + // remove closing delimiter + array_pop($objdata); + // return raw object content + return $objdata; + } + + /** + * Get the content of object, resolving indect object reference if necessary. + * @param $obj (string) Object value. + * @return array containing object data. + * @public + * @since 1.0.000 (2011-06-26) + */ + public function getObjectVal($obj) { + if ($obj[0] == PDF_TYPE_OBJREF) { + if (strpos($obj[1], '_') !== false) { + $key = explode('_', $obj[1]); + } else { + $key = array($obj[1], $obj[2]); + } + + $ret = array(0=>PDF_TYPE_OBJECT, 'obj'=>$key[0], 'gen'=>$key[1]); + + // reference to indirect object + $object = null; + if (isset($this->objects[$key[0]][$key[1]])) { + // this object has been already parsed + $object = $this->objects[$key[0]][$key[1]]; + } elseif (($offset = $this->findObjectOffset($key)) !== false) { + // parse new object + $this->objects[$key[0]][$key[1]] = $this->getIndirectObject($key[0].'_'.$key[1], $offset, false); + $object = $this->objects[$key[0]][$key[1]]; + } elseif (($key[1] == 0) && isset($this->objstreamobjs[$key[0]])) { + // Object is in an object stream + $streaminfo = $this->objstreamobjs[$key[0]]; + $objs = $streaminfo[0]; + if (!isset($this->objstreams[$objs[0]][$objs[1]])) { + // Fetch and decode object stream + $offset = $this->findObjectOffset($objs);; + $objstream = $this->getObjectVal(array(PDF_TYPE_OBJREF, $objs[0], $objs[1])); + $decoded = $this->decodeStream($objstream[1][1], $objstream[2][1]); + $this->objstreams[$objs[0]][$objs[1]] = $decoded[0]; // Store just the data, in case we need more from this objstream + // Free memory + unset($objstream); + unset($decoded); + } + $this->objects[$key[0]][$key[1]] = $this->getRawObject($streaminfo[1], $this->objstreams[$objs[0]][$objs[1]]); + $object = $this->objects[$key[0]][$key[1]]; + } + if (!is_null($object)) { + $ret[1] = $object[0]; + if (isset($object[1][0]) && $object[1][0] == PDF_TYPE_STREAM) { + $ret[0] = PDF_TYPE_STREAM; + $ret[2] = $object[1]; + } + return $ret; + } + } + return $obj; + } + + /** + * Extract object stream to find out what it contains. + * + */ + function extractObjectStream($key) { + $objref = array(PDF_TYPE_OBJREF, $key[0], $key[1]); + $obj = $this->getObjectVal($objref); + if ($obj[0] !== PDF_TYPE_STREAM || !isset($obj[1][1]['/First'][1])) { + // Not a valid object stream dictionary - skip it. + return; + } + $stream = $this->decodeStream($obj[1][1], $obj[2][1]);// Decode object stream, as we need the first bit + $first = intval($obj[1][1]['/First'][1]); + $ints = preg_split('/\s/', substr($stream[0], 0, $first)); // Get list of object / offset pairs + for ($j=1; $jobjstreamobjs[$ints[$j-1]] = array($key, $ints[$j]+$first); + } + } + + // Free memory - we may not need this at all. + unset($obj); + unset($stream); + } + + /** + * Find all object offsets. Saves having to scour the file multiple times. + * @private + */ + private function findObjectOffsets() { + $this->objoffsets = array(); + if (preg_match_all('/(*ANYCRLF)^[\s]*([0-9]+)[\s]+([0-9]+)[\s]+obj/im', $this->pdfdata, $matches, PREG_OFFSET_CAPTURE) >= 1) { + $i = 0; + $laststreamend = 0; + foreach($matches[0] as $match) { + $offset = $match[1] + strspn($match[0], "\x00\x09\x0a\x0c\x0d\x20"); + if ($offset < $laststreamend) { + // Contained within another stream, skip it. + continue; + } + $this->objoffsets[trim($match[0])] = $offset; + $dictoffset = $match[1] + strlen($match[0]); + $dictfrag = substr($this->pdfdata, $dictoffset, 256); + if (preg_match('|^\s+<<[^>]+/Length\s+(\d+)|', $dictfrag, $lengthmatch, PREG_OFFSET_CAPTURE) == 1) { + $laststreamend += intval($lengthmatch[1][0]); + } + if (preg_match('|^\s+<<[^>]+/ObjStm|', $dictfrag, $objstm) == 1) { + $this->extractObjectStream(array($matches[1][$i][0], $matches[2][$i][0])); + } + $i++; + } + } + unset($lengthmatch); + unset($dictfrag); + unset($matches); + } + + /** + * Get offset of an object. Checks xref first, then offsets found by scouring the file. + * @param $key (array) Object key to find (obj, gen). + * @return int Offset of the object in $this->pdfdata. + * @private + */ + private function findObjectOffset($key) { + $objref = $key[0].' '.$key[1].' obj'; + if (isset($this->xref['xref'][$key[0]][$key[1]])) { + $offset = $this->xref['xref'][$key[0]][$key[1]]; + if (strpos($this->pdfdata, $objref, $offset) === $offset) { + // Offset is in xref table and matches actual position in file + //echo "Offset in XREF is correct, returning
"; + return $this->xref['xref'][$key[0]][$key[1]]; + } + } + if (array_key_exists($objref, $this->objoffsets)) { + //echo "Offset found in internal reftable
"; + return $this->objoffsets[$objref]; + } + return false; + } + + /** + * Decode the specified stream. + * @param $sdic (array) Stream's dictionary array. + * @param $stream (string) Stream to decode. + * @return array containing decoded stream data and remaining filters. + * @protected + * @since 1.0.000 (2011-06-22) + */ + protected function decodeStream($sdic, $stream) { + // get stream lenght and filters + $slength = strlen($stream); + if ($slength <= 0) { + return array('', array()); + } + $filters = array(); + foreach ($sdic as $k => $v) { + if ($v[0] == PDF_TYPE_TOKEN) { + if (($k == '/Length') AND ($v[0] == PDF_TYPE_NUMERIC)) { + // get declared stream lenght + $declength = intval($v[1]); + if ($declength < $slength) { + $stream = substr($stream, 0, $declength); + $slength = $declength; + } + } elseif ($k == '/Filter') { + if ($v[0] == PDF_TYPE_TOKEN) { + // single filter + $filters[] = $v[1]; + } elseif ($v[0] == PDF_TYPE_ARRAY) { + // array of filters + foreach ($v[1] as $flt) { + if ($flt[0] == PDF_TYPE_TOKEN) { + $filters[] = $flt[1]; + } + } + } + } + } + } + // decode the stream + $remaining_filters = array(); + foreach ($filters as $filter) { + if (in_array($filter, $this->FilterDecoders->getAvailableFilters())) { + $stream = $this->FilterDecoders->decodeFilter($filter, $stream); + } else { + // add missing filter to array + $remaining_filters[] = $filter; + } + } + return array($stream, $remaining_filters); + } + + + /** + * Set pageno + * + * @param int $pageno Pagenumber to use + */ + public function setPageno($pageno) { + $pageno = ((int) $pageno) - 1; + + if ($pageno < 0 || $pageno >= $this->getPageCount()) { + $this->error("Pagenumber is wrong! (Requested $pageno, max ".$this->getPageCount().")"); + } + + $this->pageno = $pageno; + } + + /** + * Get page-resources from current page + * + * @return array + */ + public function getPageResources() { + return $this->_getPageResources($this->pages[$this->pageno]); + } + + /** + * Get page-resources from /Page + * + * @param array $obj Array of pdf-data + */ + private function _getPageResources ($obj) { // $obj = /Page + $obj = $this->getObjectVal($obj); + + // If the current object has a resources + // dictionary associated with it, we use + // it. Otherwise, we move back to its + // parent object. + if (isset ($obj[1][1]['/Resources'])) { + $res = $obj[1][1]['/Resources']; + if ($res[0] == PDF_TYPE_OBJECT) + return $res[1]; + return $res; + } else { + if (!isset ($obj[1][1]['/Parent'])) { + return false; + } else { + $res = $this->_getPageResources($obj[1][1]['/Parent']); + if ($res[0] == PDF_TYPE_OBJECT) + return $res[1]; + return $res; + } + } + } + + /** + * Get annotations from current page + * + * @return array + */ + public function getPageAnnotations() { + return $this->_getPageAnnotations($this->pages[$this->pageno]); + } + + /** + * Get annotations from /Page + * + * @param array $obj Array of pdf-data + */ + private function _getPageAnnotations ($obj) { // $obj = /Page + $obj = $this->getObjectVal($obj); + + // If the current object has an annotations + // dictionary associated with it, we use + // it. Otherwise, we move back to its + // parent object. + if (isset ($obj[1][1]['/Annots'])) { + $annots = $obj[1][1]['/Annots']; + } else { + if (!isset ($obj[1][1]['/Parent'])) { + return false; + } else { + $annots = $this->_getPageAnnotations($obj[1][1]['/Parent']); + } + } + + if ($annots[0] == PDF_TYPE_OBJREF) + return $this->getObjectVal($annots); + return $annots; + } + + + /** + * Get content of current page + * + * If more /Contents is an array, the streams are concated + * + * @return string + */ + public function getContent() { + $buffer = ''; + + if (isset($this->pages[$this->pageno][1][1]['/Contents'])) { + $contents = $this->_getPageContent($this->pages[$this->pageno][1][1]['/Contents']); + foreach($contents AS $tmp_content) { + $buffer .= $this->_rebuildContentStream($tmp_content) . ' '; + } + } + + return $buffer; + } + + + /** + * Resolve all content-objects + * + * @param array $content_ref + * @return array + */ + private function _getPageContent($content_ref) { + $contents = array(); + + if ($content_ref[0] == PDF_TYPE_OBJREF) { + $content = $this->getObjectVal($content_ref); + if ($content[1][0] == PDF_TYPE_ARRAY) { + $contents = $this->_getPageContent($content[1]); + } else { + $contents[] = $content; + } + } elseif ($content_ref[0] == PDF_TYPE_ARRAY) { + foreach ($content_ref[1] AS $tmp_content_ref) { + $contents = array_merge($contents,$this->_getPageContent($tmp_content_ref)); + } + } + + return $contents; + } + + + /** + * Rebuild content-streams + * + * @param array $obj + * @return string + */ + private function _rebuildContentStream($obj) { + $filters = array(); + + if (isset($obj[1][1]['/Filter'])) { + $_filter = $obj[1][1]['/Filter']; + + if ($_filter[0] == PDF_TYPE_OBJREF) { + $tmpFilter = $this->getObjectVal($_filter); + $_filter = $tmpFilter[1]; + } + + if ($_filter[0] == PDF_TYPE_TOKEN) { + $filters[] = $_filter; + } elseif ($_filter[0] == PDF_TYPE_ARRAY) { + $filters = $_filter[1]; + } + } + + $stream = $obj[2][1]; + + foreach ($filters AS $_filter) { + $stream = $this->FilterDecoders->decodeFilter($_filter[1], $stream); + } + + return $stream; + } + + + /** + * Get a Box from a page + * Arrayformat is same as used by fpdf_tpl + * + * @param array $page a /Page + * @param string $box_index Type of Box @see $availableBoxes + * @param float Scale factor from user space units to points + * @return array + */ + public function getPageBox($page, $box_index, $k) { + $page = $this->getObjectVal($page); + $box = null; + if (isset($page[1][1][$box_index])) + $box =& $page[1][1][$box_index]; + + if (!is_null($box) && $box[0] == PDF_TYPE_OBJREF) { + $tmp_box = $this->getObjectVal($box); + $box = $tmp_box[1]; + } + + if (!is_null($box) && $box[0] == PDF_TYPE_ARRAY) { + $b =& $box[1]; + return array('x' => $b[0][1] / $k, + 'y' => $b[1][1] / $k, + 'w' => abs($b[0][1] - $b[2][1]) / $k, + 'h' => abs($b[1][1] - $b[3][1]) / $k, + 'llx' => min($b[0][1], $b[2][1]) / $k, + 'lly' => min($b[1][1], $b[3][1]) / $k, + 'urx' => max($b[0][1], $b[2][1]) / $k, + 'ury' => max($b[1][1], $b[3][1]) / $k, + ); + } elseif (!isset ($page[1][1]['/Parent'])) { + return false; + } else { + return $this->getPageBox($this->getObjectVal($page[1][1]['/Parent']), $box_index, $k); + } + } + + /** + * Get all page boxes by page no + * + * @param int The page number + * @param float Scale factor from user space units to points + * @return array + */ + public function getPageBoxes($pageno, $k) { + return $this->_getPageBoxes($this->pages[$pageno - 1], $k); + } + + /** + * Get all boxes from /Page + * + * @param array a /Page + * @return array + */ + private function _getPageBoxes($page, $k) { + $boxes = array(); + + foreach($this->availableBoxes AS $box) { + if ($_box = $this->getPageBox($page, $box, $k)) { + $boxes[$box] = $_box; + } + } + + return $boxes; + } + + /** + * Get the page rotation by pageno + * + * @param integer $pageno + * @return array + */ + public function getPageRotation($pageno) { + return $this->_getPageRotation($this->pages[$pageno - 1]); + } + + private function _getPageRotation($obj) { // $obj = /Page + $obj = $this->getObjectVal($obj); + if (isset ($obj[1][1]['/Rotate'])) { + $res = $this->getObjectVal($obj[1][1]['/Rotate']); + if ($res[0] == PDF_TYPE_OBJECT) + return $res[1]; + return $res; + } else { + if (!isset ($obj[1][1]['/Parent'])) { + return false; + } else { + $res = (array)$this->_getPageRotation($obj[1][1]['/Parent']); + if ($res[0] == PDF_TYPE_OBJECT) + return $res[1]; + return $res; + } + } + } + + /** + * This method is automatically called in case of fatal error; it simply outputs the message and halts the execution. + * @param $msg (string) The error message + * @public + * @since 1.0.000 (2011-05-23) + */ + public function Error($msg) { + // exit program and print error + die("TCPDI_PARSER ERROR [{$this->uniqueid}]: ".$msg); + } + +} // END OF TCPDF_PARSER CLASS + +//============================================================+ +// END OF FILE +//============================================================+ -- cgit v1.2.3