diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 12:45:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 12:45:49 +0000 |
commit | 0ff39c83d38ce538a9f5dba53eca0fa9cb16d9e6 (patch) | |
tree | 84c735df2e97350a721273e9dd425729d43cc8a2 /vendor/iio | |
parent | Initial commit. (diff) | |
download | icingaweb2-module-pdfexport-upstream.tar.xz icingaweb2-module-pdfexport-upstream.zip |
Adding upstream version 0.10.2+dfsg1.upstream/0.10.2+dfsg1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
18 files changed, 3517 insertions, 0 deletions
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 @@ +<?php + +declare(strict_types = 1); + +namespace iio\libmergepdf\Driver; + +use iio\libmergepdf\Source\SourceInterface; + +final class DefaultDriver implements DriverInterface +{ + /** + * @var DriverInterface + */ + private $wrapped; + + public function __construct(DriverInterface $wrapped = null) + { + $this->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 @@ +<?php + +namespace iio\libmergepdf\Driver; + +use iio\libmergepdf\Source\SourceInterface; + +interface DriverInterface +{ + /** + * Merge multiple sources + */ + public function merge(SourceInterface ...$sources): string; +} diff --git a/vendor/iio/libmergepdf/src/Driver/Fpdi2Driver.php b/vendor/iio/libmergepdf/src/Driver/Fpdi2Driver.php new file mode 100644 index 0000000..e56d8b1 --- /dev/null +++ b/vendor/iio/libmergepdf/src/Driver/Fpdi2Driver.php @@ -0,0 +1,64 @@ +<?php + +declare(strict_types = 1); + +namespace iio\libmergepdf\Driver; + +use iio\libmergepdf\Exception; +use iio\libmergepdf\Source\SourceInterface; +use setasign\Fpdi\Fpdi as FpdiFpdf; +use setasign\Fpdi\Tcpdf\Fpdi as FpdiTcpdf; +use setasign\Fpdi\PdfParser\StreamReader; + +final class Fpdi2Driver implements DriverInterface +{ + /** + * @var FpdiFpdf|FpdiTcpdf + */ + private $fpdi; + + /** + * @param FpdiFpdf|FpdiTcpdf $fpdi + */ + public function __construct($fpdi = null) + { + // Tcpdf generates warnings due to argument ordering with php 8 + // suppressing errors is a dirty hack until tcpdf is patched + $this->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 @@ +<?php + +declare(strict_types = 1); + +namespace iio\libmergepdf\Driver; + +use iio\libmergepdf\Exception; +use iio\libmergepdf\Source\SourceInterface; + +final class TcpdiDriver implements DriverInterface +{ + /** + * @var \TCPDI + */ + private $tcpdi; + + public function __construct(\TCPDI $tcpdi = null) + { + $this->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 @@ +<?php + +namespace iio\libmergepdf; + +final class Exception extends \Exception +{ +} diff --git a/vendor/iio/libmergepdf/src/Merger.php b/vendor/iio/libmergepdf/src/Merger.php new file mode 100644 index 0000000..57725c9 --- /dev/null +++ b/vendor/iio/libmergepdf/src/Merger.php @@ -0,0 +1,79 @@ +<?php + +declare(strict_types = 1); + +namespace iio\libmergepdf; + +use iio\libmergepdf\Driver\DriverInterface; +use iio\libmergepdf\Driver\DefaultDriver; +use iio\libmergepdf\Source\SourceInterface; +use iio\libmergepdf\Source\FileSource; +use iio\libmergepdf\Source\RawSource; + +/** + * Merge existing pdfs into one + * + * Note that your PDFs are merged in the order that you add them + */ +final class Merger +{ + /** + * @var SourceInterface[] List of pdf sources to merge + */ + private $sources = []; + + /** + * @var DriverInterface + */ + private $driver; + + public function __construct(DriverInterface $driver = null) + { + $this->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<string> $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 @@ +<?php + +declare(strict_types = 1); + +namespace iio\libmergepdf; + +/** + * Parse page numbers from string + */ +final class Pages implements PagesInterface +{ + /** + * @var int[] Added integer page numbers + */ + private $pages = []; + + /** + * Parse page numbers from expression string + * + * Pages should be formatted as 1,3,6 or 12-16 or combined. Note that pages + * are merged in the order that you provide them. If you put pages 12-14 + * before 1-5 then 12-14 will be placed first. + */ + public function __construct(string $expressionString = '') + { + $expressions = explode( + ',', + str_replace(' ', '', $expressionString) + ); + + foreach ($expressions as $expr) { + if (empty($expr)) { + continue; + } + if (ctype_digit($expr)) { + $this->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 @@ +<?php + +namespace iio\libmergepdf; + +interface PagesInterface +{ + /** + * @return int[] + */ + public function getPageNumbers(): array; +} diff --git a/vendor/iio/libmergepdf/src/Source/FileSource.php b/vendor/iio/libmergepdf/src/Source/FileSource.php new file mode 100644 index 0000000..2802d80 --- /dev/null +++ b/vendor/iio/libmergepdf/src/Source/FileSource.php @@ -0,0 +1,50 @@ +<?php + +declare(strict_types = 1); + +namespace iio\libmergepdf\Source; + +use iio\libmergepdf\PagesInterface; +use iio\libmergepdf\Pages; +use iio\libmergepdf\Exception; + +/** + * Pdf source from file + */ +final class FileSource implements SourceInterface +{ + /** + * @var string + */ + private $filename; + + /** + * @var PagesInterface + */ + private $pages; + + public function __construct(string $filename, PagesInterface $pages = null) + { + if (!is_file($filename) || !is_readable($filename)) { + throw new Exception("Invalid file '$filename'"); + } + + $this->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 @@ +<?php + +declare(strict_types = 1); + +namespace iio\libmergepdf\Source; + +use iio\libmergepdf\PagesInterface; +use iio\libmergepdf\Pages; + +/** + * Pdf source from raw string + */ +final class RawSource implements SourceInterface +{ + /** + * @var string + */ + private $contents; + + /** + * @var PagesInterface + */ + private $pages; + + public function __construct(string $contents, PagesInterface $pages = null) + { + $this->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 @@ +<?php + +declare(strict_types = 1); + +namespace iio\libmergepdf\Source; + +use iio\libmergepdf\PagesInterface; + +interface SourceInterface +{ + /** + * Get name of file or source + */ + public function getName(): string; + + /** + * Get pdf content + */ + public function getContents(): string; + + /** + * Get pages to fetch from source + */ + public function getPages(): PagesInterface; +} diff --git a/vendor/iio/libmergepdf/tcpdi/LICENSE b/vendor/iio/libmergepdf/tcpdi/LICENSE new file mode 100644 index 0000000..37ec93a --- /dev/null +++ b/vendor/iio/libmergepdf/tcpdi/LICENSE @@ -0,0 +1,191 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, "control" means (i) the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising +permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +"Object" form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +"submitted" means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +2. Grant of Copyright License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +3. Grant of Patent License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of +this License; and +You must cause any modified files to carry prominent notices stating that You +changed the files; and +You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +If the Work includes a "NOTICE" text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +5. Submission of Contributions. + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +6. Trademarks. + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +8. Limitation of Liability. + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets "[]" replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same "printed page" as the copyright notice for easier identification within +third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/iio/libmergepdf/tcpdi/README.md b/vendor/iio/libmergepdf/tcpdi/README.md new file mode 100644 index 0000000..73aa22c --- /dev/null +++ b/vendor/iio/libmergepdf/tcpdi/README.md @@ -0,0 +1,65 @@ +TCPDI +===== + +Composer ready TCPDI with PDF annotations handling. + +Imported from https://github.com/RafikHaceb/tcpdi + +PDF importer for [TCPDF](http://www.tcpdf.org/), based on [FPDI](http://www.setasign.de/products/pdf-php-solutions/fpdi/). +Requires [pauln/tcpdi_parser](https://github.com/pauln/tcpdi_parser) and [FPDF_TPL](http://www.setasign.de/products/pdf-php-solutions/fpdi/downloads/) +which included in the repository. + +Usage +----- + +Usage is essentially the same as FPDI, except importing TCPDI rather than FPDI. It also has a "setSourceData()" function which accepts raw PDF data, for cases where the file does not reside on disk or is not readable by TCPDI. + +```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->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 @@ +<?php +// +// FPDF_TPL - Version 1.2.3 +// +// Copyright 2004-2013 Setasign - Jan Slabon +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +class FPDF_TPL extends FPDF { + /** + * Array of Tpl-Data + * @var array + */ + var $tpls = array(); + + /** + * Current Template-ID + * @var int + */ + var $tpl = 0; + + /** + * "In Template"-Flag + * @var boolean + */ + var $_intpl = false; + + /** + * Nameprefix of Templates used in Resources-Dictonary + * @var string A String defining the Prefix used as Template-Object-Names. Have to beginn with an / + */ + var $tplprefix = "/TPL"; + + /** + * Resources used By Templates and Pages + * @var array + */ + var $_res = array(); + + /** + * Last used Template data + * + * @var array + */ + var $lastUsedTemplateData = array(); + + /** + * Start a Template + * + * This method starts a template. You can give own coordinates to build an own sized + * Template. Pay attention, that the margins are adapted to the new templatesize. + * If you want to write outside the template, for example to build a clipped Template, + * you have to set the Margins and "Cursor"-Position manual after beginTemplate-Call. + * + * If no parameter is given, the template uses the current page-size. + * The Method returns an ID of the current Template. This ID is used later for using this template. + * Warning: A created Template is used in PDF at all events. Still if you don't use it after creation! + * + * @param int $x The x-coordinate given in user-unit + * @param int $y The y-coordinate given in user-unit + * @param int $w The width given in user-unit + * @param int $h The height given in user-unit + * @return int The ID of new created Template + */ + function beginTemplate($x = null, $y = null, $w = null, $h = null) { + if (is_subclass_of($this, 'TCPDF')) { + $this->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('<</ProcSet [/PDF /Text /ImageB /ImageC /ImageI]'); + if (isset($this->_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 @@ +<?php +// +// TCPDI - Version 1.1 +// Based on FPDI - Version 1.4.4 +// +// Copyright 2004-2013 Setasign - Jan Slabon +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// Dummy shim to allow unmodified use of fpdf_tpl +class FPDF extends TCPDF {} + +require_once('fpdf_tpl.php'); + +require_once('tcpdi_parser.php'); + + +class TCPDI extends FPDF_TPL { + /** + * Actual filename + * @var string + */ + public $current_filename; + + /** + * Parser-Objects + * @var array + */ + public $parsers = array(); + + /** + * Current parser + * @var object + */ + public $current_parser; + + /** + * object stack + * @var array + */ + protected $_obj_stack = array(); + + /** + * done object stack + * @var array + */ + protected $_don_obj_stack = array(); + + /** + * Current Object Id. + * @var integer + */ + protected $_current_obj_id; + + /** + * The name of the last imported page box + * @var string + */ + public $lastUsedPageBox; + + /** + * Cache for imported pages/template ids + * @var array + */ + protected $_importedPages = array(); + + /** + * Cache for imported page annotations + * @var array + */ + protected $_importedAnnots = array(); + + /** + * Number of TOC pages, used for annotation offset + * @var integer + */ + protected $_numTOCpages = 0; + + /** + * First TOC page, used for annotation offset + * @var integer + */ + protected $_TOCpagenum = 0; + + /** + * Set a source-file + * + * @param string $filename a valid filename + * @return int number of available pages + */ + public function setSourceFile($filename) { + $this->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('<</ProcSet [/PDF /Text /ImageB /ImageC /ImageI]'); + if (isset($this->_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 <</Type/Group/S/Transparency>>'); + + $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 @@ +<?php +//============================================================+ +// File name : tcpdi_parser.php +// Version : 1.1 +// Begin : 2013-09-25 +// Last Update : 2016-05-03 +// Author : Paul Nicholls - https://github.com/pauln +// License : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html) +// +// Based on : tcpdf_parser.php +// Version : 1.0.003 +// Begin : 2011-05-23 +// Last Update : 2013-03-17 +// Author : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - info@tecnick.com +// License : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html) +// ------------------------------------------------------------------- +// Copyright (C) 2011-2013 Nicola Asuni - Tecnick.com LTD +// +// This file is for use with the TCPDF software library. +// +// tcpdi_parser is free software: you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// tcpdi_parser is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU Lesser General Public License for more details. +// +// You should have received a copy of the License +// along with tcpdi_parser. If not, see +// <http://www.tecnick.com/pagefiles/tcpdf/LICENSE.TXT>. +// +// 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.<br> + * @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.<br> + * 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; $j<count($ints); $j++) { + if (($j % 2) == 1) { + $this->objstreamobjs[$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<br>"; + return $this->xref['xref'][$key[0]][$key[1]]; + } + } + if (array_key_exists($objref, $this->objoffsets)) { + //echo "Offset found in internal reftable<br>"; + 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("<strong>TCPDI_PARSER ERROR [{$this->uniqueid}]: </strong>".$msg); + } + +} // END OF TCPDF_PARSER CLASS + +//============================================================+ +// END OF FILE +//============================================================+ |