Skip to content

Commit 416a330

Browse files
committed
feat: add PDF import subsystem (single page, append, n-up region)
1 parent 6514cac commit 416a330

37 files changed

Lines changed: 4374 additions & 8 deletions

Makefile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -225,9 +225,9 @@ endif
225225
.PHONY: lint
226226
lint:
227227
#./vendor/bin/phpcbf --config-set ignore_non_auto_fixable_on_exit 1
228-
./vendor/bin/phpcs --standard=phpcs.xml
229-
./vendor/bin/phpmd src text unusedcode,naming,design --exclude vendor
230-
./vendor/bin/phpmd test text unusedcode,naming,design --exclude */vendor/*
228+
./vendor/bin/phpcs --standard=phpcs.xml --cache=$(TARGETDIR)/phpcs.cache
229+
./vendor/bin/phpmd src text unusedcode,naming,design --exclude vendor --cache --cache-file $(TARGETDIR)/phpmd-src.cache
230+
./vendor/bin/phpmd test text unusedcode,naming,design --exclude */vendor/* --cache --cache-file $(TARGETDIR)/phpmd-test.cache
231231
php -r 'exit((int)version_compare(PHP_MAJOR_VERSION, "7", ">"));' || ./vendor/phpstan.phar analyse
232232

233233
## Run all tests and reports

README.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ If this project is useful to you, please consider [supporting development via Gi
2424
- [Installation](#installation)
2525
- [Font Setup](#font-setup)
2626
- [Quick Start](#quick-start)
27+
- [PDF Import](#pdf-import)
2728
- [Remote Resources and fileOptions](#remote-resources-and-fileoptions)
2829
- [Digital Signatures](#digital-signatures)
2930
- [PDF/X Conformance](#pdfx-conformance)
@@ -124,6 +125,12 @@ The fastest way to evaluate the library is to follow the installation and font s
124125
- **PDF/X** (generic alias, PDF/X-1a, PDF/X-3, PDF/X-4, PDF/X-5) — print-exchange conformance: per-variant OutputIntent identifiers, GTS_PDFXVersion in Info dict and XMP, PDF version enforcement, CMYK color forcing for restrictive profiles (X-1a, X-3), transparency restrictions, and suppression of encryption and JavaScript
125126
- **PDF/UA** (generic alias, PDF/UA-1, PDF/UA-2) — accessibility conformance: tagged structure tree (`StructTreeRoot` / `ParentTree`), `MarkInfo /Marked true`, document language (`/Lang`), `DisplayDocTitle true`, `ActualText` for ligatures and special glyphs, figure alt-text tagging, and heading-level clamping to prevent skipped levels; PDF/UA-2 targets PDF 2.0
126127

128+
### PDF Import
129+
- Import pages from existing PDFs as **Form XObjects** and place them on any destination page
130+
- Import a single page at a user-defined position and scale (`importPage` / `useImportedPage`)
131+
- Append full documents page-by-page, auto-sized to the source page dimensions (`addPageFromImport`)
132+
- Load source PDFs from a file path or raw byte string (`setImportSourceFile` / `setImportSourceData`)
133+
127134
### Other
128135
- **1D and 2D barcodes** via [`tc-lib-barcode`](https://github.com/tecnickcom/tc-lib-barcode)
129136
- Interactive AcroForm fields (buttons, checkboxes, radio buttons, text, combo boxes, list boxes)
@@ -277,6 +284,78 @@ If the minimal example fails on first run, verify these two points first:
277284

278285
---
279286

287+
## PDF Import
288+
289+
`tc-lib-pdf` can import pages from existing PDFs as Form XObjects and place them on destination pages.
290+
291+
### Source registration and page count
292+
293+
```php
294+
$sourceId = $pdf->setImportSourceFile('/path/to/source.pdf');
295+
// or: $sourceId = $pdf->setImportSourceData($rawPdfBytes);
296+
297+
$count = $pdf->getSourcePageCount($sourceId);
298+
```
299+
300+
### Import one page and place it
301+
302+
```php
303+
$tpl = $pdf->importPage($sourceId, 1, [
304+
'box' => 'CropBox', // MediaBox|CropBox|BleedBox|TrimBox|ArtBox
305+
'groupXObject' => true,
306+
'cache' => true,
307+
'respectRotation' => true,
308+
]);
309+
310+
$pdf->addPage();
311+
$placed = $pdf->useImportedPage($tpl, 20, 20, 120, 80, [
312+
'keepAspectRatio' => true,
313+
'align' => 'CC', // TL|TC|TR|CL|CC|CR|BL|BC|BR
314+
'clip' => true,
315+
]);
316+
```
317+
318+
### Append pages from a source document
319+
320+
```php
321+
// Append all pages.
322+
$templates = $pdf->appendDocument($sourceId);
323+
324+
// Append only selected pages.
325+
$templates = $pdf->appendDocument($sourceId, [1, 3, 5]);
326+
327+
// Add one imported page sized to the source page.
328+
$tpl = $pdf->addPageFromImport($sourceId, 2);
329+
```
330+
331+
### Import examples
332+
333+
- Single page import: [examples/E065_import_single_page.php](examples/E065_import_single_page.php)
334+
- Full document append: [examples/E066_import_document_append.php](examples/E066_import_document_append.php)
335+
- Advanced N-up composition from imported pages: [examples/E067_import_page_region_nup.php](examples/E067_import_page_region_nup.php)
336+
337+
### Migrating from FPDI-style workflows
338+
339+
If you are migrating legacy FPDI-like code paths, map concepts to the native API as follows:
340+
341+
- `setSourceFile()` -> `setImportSourceFile()` + `getSourcePageCount()`
342+
- `importPage()` -> `importPage()` (returns a typed `PageTemplate` value object)
343+
- `useTemplate()` -> `useImportedPage()`
344+
- manual page-loop append flows -> `appendDocument()`
345+
346+
Unlike FPDI numeric template IDs, `tc-lib-pdf` returns typed `PageTemplate` objects and keeps import behavior inside the same library stack (`tc-lib-*`) without external importer dependencies.
347+
348+
### Import limitations and fidelity notes
349+
350+
- Form and annotation semantics are not merged into editable destination structures; pages are imported as Form XObjects.
351+
- Digital signatures in source files are not preserved as valid signatures in the destination output.
352+
- Encrypted source PDFs are currently not importable with the bundled parser backend. Password-like options are accepted by the import API, but encrypted inputs fail with an explicit actionable exception.
353+
- For multi-stream page contents, import normalizes by decoding and concatenating stream bytes; this can change low-level byte representation while preserving rendered appearance in typical cases.
354+
- Transparency-group behavior is conformance-aware: when transparency is disallowed by the active PDF mode (for example PDF/X-1a or PDF/X-3), import suppresses transparency groups to remain compliant.
355+
- Setting `groupXObject` to `false` can reduce output size, but may change compositing on source pages that rely on transparency blending.
356+
357+
---
358+
280359
## Remote Resources and `fileOptions`
281360

282361
By default `tc-lib-pdf` **does not fetch any remote URLs**. Images, fonts, and SVG files referenced by HTTP or HTTPS are blocked unless you explicitly allow the originating hosts. Local file paths are never restricted.

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
8.11.3
1+
8.12.0

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@
3737
"tecnickcom/tc-lib-unicode-data": "^2.0",
3838
"tecnickcom/tc-lib-unicode": "^2.1",
3939
"tecnickcom/tc-lib-pdf-page": "^4.3",
40-
"tecnickcom/tc-lib-pdf-graph": "^2.4"
40+
"tecnickcom/tc-lib-pdf-graph": "^2.4",
41+
"tecnickcom/tc-lib-pdf-parser": "*"
4142
},
4243
"minimum-stability": "dev",
4344
"prefer-stable": true,

examples/E000_overview.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@
166166
);
167167

168168
$pdf->page->addContent($bodyFont['out']);
169-
$features = '<ul style="font-size:10pt; line-height:1.35; color:rgb(22,37,61); margin:0; padding-left:12pt;">'
169+
$features = '<ul style="font-size:10pt; color:rgb(22,37,61); margin:0; padding-left:12pt;">'
170170
. '<li>Native SVG rendering</li>'
171171
. '<li>JPEG/PNG with transparency</li>'
172172
. '<li>1D/2D barcodes (QR, DataMatrix)</li>'
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
/**
3+
* E065_import_single_page.php
4+
*
5+
* @since 2026-05-03
6+
* @category Library
7+
* @package Pdf
8+
* @author Nicola Asuni <info@tecnick.com>
9+
* @copyright 2002-2026 Nicola Asuni - Tecnick.com LTD
10+
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT)
11+
* @link https://github.com/tecnickcom/tc-lib-pdf
12+
*
13+
* This file is part of tc-lib-pdf software library.
14+
*/
15+
16+
// NOTE: run make deps fonts in the project root to generate the dependencies and example fonts.
17+
18+
// autoloader when using Composer
19+
require(__DIR__ . '/../vendor/autoload.php');
20+
21+
// define fonts directory
22+
\define('K_PATH_FONTS', \realpath(__DIR__ . '/../vendor/tecnickcom/tc-lib-pdf-font/target/fonts'));
23+
24+
// ---- Step 1: create a source PDF to import from ----
25+
26+
$src = new \Com\Tecnick\Pdf\Tcpdf();
27+
$bfont = $src->font->insert($src->pon, 'helvetica', '', 14);
28+
29+
$srcPage = $src->addPage();
30+
$src->page->addContent($bfont['out']);
31+
$src->addHTMLCell('<h1>Source document</h1><p>This page will be imported.</p>', 15, 20, 160);
32+
33+
$sourcePdfData = $src->getOutPDFString();
34+
35+
// ---- Step 2: create a new document and import the source page ----
36+
37+
$pdf = new \Com\Tecnick\Pdf\Tcpdf();
38+
$bfont = $pdf->font->insert($pdf->pon, 'helvetica', '', 12);
39+
40+
// Register the source document from its raw bytes.
41+
$sourceId = $pdf->setImportSourceData($sourcePdfData);
42+
43+
// Count pages available in the source.
44+
$pageCount = $pdf->getSourcePageCount($sourceId);
45+
46+
// Import page 1 as a reusable Form XObject template.
47+
$tpl = $pdf->importPage($sourceId, 1);
48+
49+
// Add a new page to the destination document.
50+
$page = $pdf->addPage();
51+
$pdf->page->addContent($bfont['out']);
52+
53+
// Place the imported page as a thumbnail (100 mm wide) at (20, 20).
54+
// The height is computed automatically to preserve the aspect ratio.
55+
$placed = $pdf->useImportedPage($tpl, 20, 20, 140);
56+
57+
// Add an annotation below the imported page.
58+
$pdf->addHTMLCell(
59+
'<p>Imported ' . $pageCount . ' page(s). Placed page 1 above (scaled).</p>',
60+
15,
61+
(float) $placed['y'] + (float) $placed['height'] + 5,
62+
160
63+
);
64+
65+
// Get and render the PDF content.
66+
$rawpdf = $pdf->getOutPDFString();
67+
$pdf->renderPDF($rawpdf);
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
/**
3+
* E066_import_document_append.php
4+
*
5+
* @since 2026-05-03
6+
* @category Library
7+
* @package Pdf
8+
* @author Nicola Asuni <info@tecnick.com>
9+
* @copyright 2002-2026 Nicola Asuni - Tecnick.com LTD
10+
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT)
11+
* @link https://github.com/tecnickcom/tc-lib-pdf
12+
*
13+
* This file is part of tc-lib-pdf software library.
14+
*/
15+
16+
// NOTE: run make deps fonts in the project root to generate the dependencies and example fonts.
17+
18+
// autoloader when using Composer
19+
require(__DIR__ . '/../vendor/autoload.php');
20+
21+
// define fonts directory
22+
\define('K_PATH_FONTS', \realpath(__DIR__ . '/../vendor/tecnickcom/tc-lib-pdf-font/target/fonts'));
23+
24+
// ---- Step 1: build a multi-page source PDF ----
25+
26+
$src = new \Com\Tecnick\Pdf\Tcpdf();
27+
$bfont = $src->font->insert($src->pon, 'helvetica', '', 14);
28+
29+
$pages = ['First page', 'Second page', 'Third page'];
30+
foreach ($pages as $idx => $label) {
31+
$srcPage = $src->addPage();
32+
$src->page->addContent($bfont['out']);
33+
$src->addHTMLCell(
34+
'<h2>Source: ' . \htmlspecialchars($label) . '</h2>'
35+
. '<p>Page ' . ($idx + 1) . ' of ' . \count($pages) . ' in the source document.</p>',
36+
15,
37+
20,
38+
160
39+
);
40+
}
41+
42+
$sourcePdfData = $src->getOutPDFString();
43+
44+
// ---- Step 2: create destination document with an intro page ----
45+
46+
$pdf = new \Com\Tecnick\Pdf\Tcpdf();
47+
$bfont = $pdf->font->insert($pdf->pon, 'helvetica', '', 12);
48+
49+
$pdf->addPage();
50+
$pdf->page->addContent($bfont['out']);
51+
$pdf->addHTMLCell(
52+
'<h1>Document append demo</h1>'
53+
. '<p>The following pages are imported from a separate source document.</p>',
54+
15,
55+
20,
56+
160
57+
);
58+
59+
// ---- Step 3: register source and append all its pages ----
60+
61+
$sourceId = $pdf->setImportSourceData($sourcePdfData);
62+
$pageCount = $pdf->getSourcePageCount($sourceId);
63+
64+
// appendDocument adds one new destination page per source page and places the template.
65+
$templates = $pdf->appendDocument($sourceId);
66+
67+
// ---- Step 4: add a closing page with a summary ----
68+
69+
$pdf->addPage();
70+
$pdf->page->addContent($bfont['out']);
71+
$pdf->addHTMLCell(
72+
'<p>Appended ' . \count($templates) . ' page(s) from source ('
73+
. $pageCount . ' available).</p>',
74+
15,
75+
20,
76+
160
77+
);
78+
79+
// Render
80+
$rawpdf = $pdf->getOutPDFString();
81+
$pdf->renderPDF($rawpdf);

0 commit comments

Comments
 (0)