generated from jric11/baseProject
Initial commit
This commit is contained in:
+118
@@ -0,0 +1,118 @@
|
||||
# External Cache
|
||||
|
||||
Back to root overview: [README.md](../README.md#in-depth-documentation)
|
||||
|
||||
Generating font subsets and processing images (decode, resize, re-encode) is computationally expensive. `tc-lib-pdf` can reuse these results across `Tcpdf` instances and PHP processes through an **optional external cache** that you provide.
|
||||
|
||||
No cache backend is shipped: you implement a tiny interface that bridges to whatever store you already use (filesystem, APCu, Redis, a PSR-16 cache, ...). Caching is **disabled by default**: when no cache is supplied, behavior is unchanged.
|
||||
|
||||
One cache instance is reused by every cacheable subsystem (currently font subsets and images, more may be added later), so a single backend, connection, and configuration serves them all.
|
||||
|
||||
## The CacheInterface
|
||||
|
||||
Implement `Com\Tecnick\Pdf\Cache\CacheInterface`:
|
||||
|
||||
```php
|
||||
namespace Com\Tecnick\Pdf\Cache;
|
||||
|
||||
interface CacheInterface
|
||||
{
|
||||
public function get(string $key): mixed; // stored value, or null on a miss
|
||||
public function set(string $key, mixed $value): void;
|
||||
}
|
||||
```
|
||||
|
||||
Both methods MUST be **best-effort** and **MUST NOT throw**: a backend miss or transient failure must surface as `null` (on `get`) or a silent no-op (on `set`). The font and image libraries call the cache directly and do not catch exceptions, so a throwing implementation will break PDF generation.
|
||||
|
||||
## Enabling the Cache
|
||||
|
||||
Pass your implementation as the `cache` argument of the `Tcpdf` constructor (the last parameter):
|
||||
|
||||
```php
|
||||
$cache = new MyRedisCache(); // implements Com\Tecnick\Pdf\Cache\CacheInterface
|
||||
|
||||
$pdf = new \Com\Tecnick\Pdf\Tcpdf(
|
||||
unit: 'mm',
|
||||
subsetfont: true, // required for the font subset cache to be exercised
|
||||
cache: $cache,
|
||||
);
|
||||
```
|
||||
|
||||
A minimal in-memory implementation (useful for tests or single-request deduplication):
|
||||
|
||||
```php
|
||||
use Com\Tecnick\Pdf\Cache\CacheInterface;
|
||||
|
||||
$cache = new class implements CacheInterface {
|
||||
/** @var array<string, mixed> */
|
||||
private array $store = [];
|
||||
|
||||
public function get(string $key): mixed
|
||||
{
|
||||
return $this->store[$key] ?? null;
|
||||
}
|
||||
|
||||
public function set(string $key, mixed $value): void
|
||||
{
|
||||
$this->store[$key] = $value;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## What Gets Cached
|
||||
|
||||
| Subsystem | Type constant | Cached value | Key prefix |
|
||||
|-----------|---------------|--------------|------------|
|
||||
| Font subsets | `CacheInterface::TYPE_FONT` | Raw subset font program (`string`, uncompressed) | `tc-lib-pdf-font:subset:v2:` |
|
||||
| Images | `CacheInterface::TYPE_IMAGE` | Processed image snapshot (`array`) | `tc-lib-pdf-image:v2:` |
|
||||
|
||||
Keys are already namespaced and schema-versioned by each sub-library, so a single shared store is collision-safe. The font subset cache is only consulted when font subsetting is enabled (`subsetfont: true`).
|
||||
|
||||
The library never evicts entries: expiration, size limits, and (de)serialization are entirely the backend's responsibility. When an implementation deserializes data it MUST disable object restoration, e.g. `unserialize($data, ['allowed_classes' => false])`.
|
||||
|
||||
## Caching Only Some Types
|
||||
|
||||
To cache only a subset of the subsystems, implement `Com\Tecnick\Pdf\Cache\SelectiveCacheInterface` (which extends `CacheInterface`) and report which types you handle:
|
||||
|
||||
```php
|
||||
namespace Com\Tecnick\Pdf\Cache;
|
||||
|
||||
interface SelectiveCacheInterface extends CacheInterface
|
||||
{
|
||||
/** @param CacheInterface::TYPE_* $type */
|
||||
public function supports(string $type): bool;
|
||||
}
|
||||
```
|
||||
|
||||
When `supports()` returns `false` for a type, that type is disabled entirely: the cache is never queried or written for it, and your implementation never receives its data. A plain `CacheInterface` (without `supports()`) caches every type.
|
||||
|
||||
For example, to cache font subsets but never images:
|
||||
|
||||
```php
|
||||
use Com\Tecnick\Pdf\Cache\CacheInterface;
|
||||
use Com\Tecnick\Pdf\Cache\SelectiveCacheInterface;
|
||||
|
||||
$cache = new class implements SelectiveCacheInterface {
|
||||
/** @var array<string, mixed> */
|
||||
private array $store = [];
|
||||
|
||||
public function supports(string $type): bool
|
||||
{
|
||||
return $type === CacheInterface::TYPE_FONT;
|
||||
}
|
||||
|
||||
public function get(string $key): mixed
|
||||
{
|
||||
return $this->store[$key] ?? null;
|
||||
}
|
||||
|
||||
public function set(string $key, mixed $value): void
|
||||
{
|
||||
$this->store[$key] = $value;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Security
|
||||
|
||||
The cache store is a **trust boundary**. Cached values are embedded verbatim into generated PDFs, so anyone able to write to the backend can influence document output. Use a store only your application can write to, and always deserialize with object restoration disabled (`['allowed_classes' => false]`).
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
# Development and Packaging
|
||||
|
||||
Back to root overview: [README.md](../README.md#in-depth-documentation)
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
# Install all development dependencies
|
||||
make deps
|
||||
|
||||
# List all available Make targets
|
||||
make help
|
||||
|
||||
# Run the full quality pipeline (lint, static analysis, tests, coverage)
|
||||
make qa
|
||||
|
||||
# Generate PDF/X + PDF/UA sample matrix and run external validators (if installed)
|
||||
make preflight
|
||||
```
|
||||
|
||||
Build artifacts and reports are written to the `target/` directory.
|
||||
|
||||
## Packaging
|
||||
|
||||
The primary distribution channel is Composer. For system-level deployments, RPM and DEB packages are also provided.
|
||||
|
||||
```bash
|
||||
make rpm # build RPM package -> target/RPM/
|
||||
make deb # build DEB package -> target/DEB/
|
||||
```
|
||||
|
||||
When using the RPM or DEB package, bootstrap the library with its system autoloader:
|
||||
|
||||
```php
|
||||
require_once '/usr/share/php/Com/Tecnick/Pdf/autoload.php';
|
||||
```
|
||||
@@ -0,0 +1,143 @@
|
||||
# Digital Signatures
|
||||
|
||||
Back to root overview: [README.md](../README.md#in-depth-documentation)
|
||||
|
||||
`tc-lib-pdf` produces detached CMS (PKCS#7) signatures and **PAdES baseline** signatures
|
||||
(ETSI EN 319 142-1) with optional RFC 3161 timestamps, LTV (Long-Term Validation)
|
||||
material, and archive timestamps. The cryptography lives in the companion package
|
||||
[`tecnickcom/tc-lib-pdf-sign`](https://github.com/tecnickcom/tc-lib-pdf-sign).
|
||||
|
||||
## Signature profiles
|
||||
|
||||
The `profile` option selects the signature format:
|
||||
|
||||
| Profile | /SubFilter | What it adds |
|
||||
| --- | --- | --- |
|
||||
| `legacy` (default) | `adbe.pkcs7.detached` | ISO 32000-1 detached CMS with the ESS signing-certificate-v2 attribute. |
|
||||
| `pades-b-b` | `ETSI.CAdES.detached` | PAdES-BASELINE-B: CAdES-based CMS; the signing time is carried by the `/M` dictionary entry (the CMS signing-time attribute is omitted, as the baseline requires). |
|
||||
| `pades-b-t` | `ETSI.CAdES.detached` | PAdES-BASELINE-T: B-B plus an RFC 3161 signature timestamp embedded in the CMS. |
|
||||
| `pades-b-lt` | `ETSI.CAdES.detached` | PAdES-BASELINE-LT: B-T plus a Document Security Store (`/DSS`, `/VRI`) with certificates and, where reachable, OCSP/CRL revocation data. |
|
||||
| `pades-b-lta` | `ETSI.CAdES.detached` + `ETSI.RFC3161` | PAdES-BASELINE-LTA: B-LT plus a `/Type /DocTimeStamp` archive timestamp over the whole document. |
|
||||
|
||||
The default profile stays `legacy`, so existing signing output is unchanged unless a PAdES
|
||||
profile is requested. `digest_algorithm` accepts `sha256` (default), `sha384`, or `sha512`;
|
||||
RSA and ECDSA signing keys are both supported.
|
||||
|
||||
Signature-focused runnable examples:
|
||||
|
||||
- [examples/E007_signature_basic.php](../examples/E007_signature_basic.php): PAdES-BASELINE-B signature via the fluent `signature()` facade.
|
||||
- [examples/E008_signature_timestamp.php](../examples/E008_signature_timestamp.php): PAdES-BASELINE-T signature with an RFC 3161 TSA timestamp.
|
||||
- [examples/E009_signature_ltv.php](../examples/E009_signature_ltv.php): PAdES-BASELINE-LT signature with LTV material (`/DSS`, `/VRI`).
|
||||
- [examples/E081_signature_pades_lta.php](../examples/E081_signature_pades_lta.php): PAdES-BASELINE-LTA signature with a document archive timestamp via `upgradeToLta()`.
|
||||
- [examples/E075_external_signature_injection.php](../examples/E075_external_signature_injection.php): external/remote signing workflow with ByteRange digest export and later CMS signature injection.
|
||||
|
||||
## Fluent API: `signature()`
|
||||
|
||||
The preferred entry point is the `signature()` facade. Each call is chainable and forwards
|
||||
to the underlying methods (which remain available as `setSignature()`, `setSignTimeStamp()`,
|
||||
`setUserRights()`, `setSignatureAppearance()`, and so on).
|
||||
|
||||
```php
|
||||
$pdf->signature()
|
||||
->configure([
|
||||
'profile' => 'pades-b-t', // legacy | pades-b-b | pades-b-t | pades-b-lt | pades-b-lta
|
||||
'digest_algorithm' => 'sha256', // sha256 | sha384 | sha512
|
||||
'signcert' => 'file:///path/to/cert.pem',
|
||||
'privkey' => 'file:///path/to/key.pem',
|
||||
'password' => '',
|
||||
'extracerts' => 'file:///path/to/chain.pem', // optional issuer chain
|
||||
'cert_type' => 2,
|
||||
'info' => [
|
||||
'Name' => 'Jane Smith',
|
||||
'Location' => 'London',
|
||||
'Reason' => 'Document approval',
|
||||
'ContactInfo' => 'jane@example.com',
|
||||
],
|
||||
])
|
||||
->timestamp([
|
||||
'enabled' => true,
|
||||
'host' => 'https://freetsa.org/tsr',
|
||||
'hash_algorithm' => 'sha256',
|
||||
'timeout' => 30,
|
||||
'verify_peer' => true,
|
||||
]);
|
||||
|
||||
$pdf->signature()->appearance()->place(posx: 15, posy: 35, width: 90, height: 20, page: -1, name: 'Signature');
|
||||
$widgetObjId = $pdf->signature()->widgetObjectId();
|
||||
```
|
||||
|
||||
## Adding a TSA Timestamp (RFC 3161)
|
||||
|
||||
For `pades-b-t` and above a timestamp is required. Configure it with
|
||||
`signature()->timestamp([...])` (or the legacy `setSignTimeStamp([...])`); the RFC 3161
|
||||
token is embedded in the CMS as the `id-aa-signatureTimeStampToken` unsigned attribute:
|
||||
|
||||
```php
|
||||
$pdf->signature()->timestamp([
|
||||
'enabled' => true,
|
||||
'host' => 'https://freetsa.org/tsr',
|
||||
'username' => '',
|
||||
'password' => '',
|
||||
'cert' => '',
|
||||
'hash_algorithm' => 'sha256', // sha256 | sha384 | sha512
|
||||
'policy_oid' => '', // optional OID string
|
||||
'nonce_enabled' => true,
|
||||
'timeout' => 30,
|
||||
'verify_peer' => true,
|
||||
]);
|
||||
```
|
||||
|
||||
## LTV (Long-Term Validation) and archive timestamps
|
||||
|
||||
Enable LTV via the `ltv` key inside `configure()`. The library fetches OCSP responses and
|
||||
CRL payloads from the certificate's AIA and CRL-DP extensions and writes a `/DSS` (with a
|
||||
`/VRI` map keyed by the uppercase SHA-1 of the signature `/Contents`) in a post-signing
|
||||
incremental revision:
|
||||
|
||||
```php
|
||||
$pdf->signature()->configure([
|
||||
'profile' => 'pades-b-lt',
|
||||
'signcert' => 'file:///path/to/cert.pem',
|
||||
'privkey' => 'file:///path/to/key.pem',
|
||||
'password' => '',
|
||||
'ltv' => [
|
||||
'enabled' => true,
|
||||
'embed_ocsp' => true, // fetch OCSP responses
|
||||
'embed_crl' => true, // fetch CRL payloads (fallback)
|
||||
'embed_certs' => true, // include certificate DER bytes
|
||||
'include_dss' => true, // emit /DSS in the catalog
|
||||
'include_vri' => true, // emit /VRI map keyed by signature SHA-1
|
||||
],
|
||||
]);
|
||||
```
|
||||
|
||||
To reach PAdES-BASELINE-LTA, call `upgradeToLta()` (it selects the `pades-b-lta` profile,
|
||||
forces the DSS on, and adds a `/Type /DocTimeStamp` archive timestamp over the whole
|
||||
document in a further incremental revision; a TSA must be configured):
|
||||
|
||||
```php
|
||||
$pdf->signature()->configure([/* pades-b-lt + ltv */])->timestamp([/* TSA */])->upgradeToLta();
|
||||
```
|
||||
|
||||
A validator only reports the LT/LTA level when the DSS actually contains the revocation data
|
||||
for the chain, so the signing certificate must expose reachable OCSP/CRL responders. A
|
||||
self-signed certificate embeds only its own bytes, so a validator then reports B-T with a
|
||||
DSS present.
|
||||
|
||||
## Generating a Self-Signed Test Certificate
|
||||
|
||||
The bundled `examples/data/cert/tcpdf.crt` is a self-signed demo certificate (certificate
|
||||
and RSA private key in one file). Generate your own with:
|
||||
|
||||
```bash
|
||||
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -sha256 \
|
||||
-keyout tcpdf.key -out tcpdf.crt \
|
||||
-subj "/CN=tc-lib-pdf test certificate"
|
||||
# combine into a single file (as the bundled demo does), or reference them separately
|
||||
cat tcpdf.crt tcpdf.key > tcpdf.pem
|
||||
# convert to PKCS#12 if needed
|
||||
openssl pkcs12 -export -in tcpdf.crt -inkey tcpdf.key -out tcpdf.p12
|
||||
```
|
||||
|
||||
For a real PAdES-BASELINE-LT/LTA validation you need a certificate issued by a CA whose
|
||||
OCSP responder (AIA) and CRL distribution point are reachable at signing time.
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
# Fonts
|
||||
|
||||
Back to root overview: [README.md](../README.md#in-depth-documentation)
|
||||
|
||||
## Font Setup
|
||||
|
||||
When you install `tc-lib-pdf` as a dependency in your project (via `composer require` or `composer install`), the fonts from the companion package [`tc-lib-pdf-font`](https://github.com/tecnickcom/tc-lib-pdf-font) must be generated before they can be used.
|
||||
|
||||
Composer does not execute scripts declared by dependencies, so you need to add the font generation step to your **consuming project's** `composer.json` file:
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"tc-lib-pdf-fonts": [
|
||||
"[ -d vendor/tecnickcom/tc-lib-pdf-font ] && make -C vendor/tecnickcom/tc-lib-pdf-font deps fonts || true"
|
||||
],
|
||||
"post-install-cmd": [
|
||||
"@tc-lib-pdf-fonts"
|
||||
],
|
||||
"post-update-cmd": [
|
||||
"@tc-lib-pdf-fonts"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This ensures fonts are generated automatically when you run:
|
||||
|
||||
```bash
|
||||
composer install
|
||||
composer update
|
||||
composer require ...
|
||||
```
|
||||
|
||||
To also cover `composer dump-autoload` (used in many CI pipelines), add the hook to `post-autoload-dump` as well:
|
||||
|
||||
```json
|
||||
"post-autoload-dump": [
|
||||
"@tc-lib-pdf-fonts"
|
||||
]
|
||||
```
|
||||
|
||||
If you prefer to generate fonts manually, run the build in the `tc-lib-pdf-font` package:
|
||||
|
||||
```bash
|
||||
cd vendor/tecnickcom/tc-lib-pdf-font
|
||||
make fonts
|
||||
```
|
||||
|
||||
Equivalent one-liner from your project root:
|
||||
|
||||
```bash
|
||||
make -C vendor/tecnickcom/tc-lib-pdf-font deps fonts
|
||||
```
|
||||
|
||||
Once fonts are generated, they are cached in `vendor/tecnickcom/tc-lib-pdf-font/target/fonts/` and will not be regenerated unless explicitly rebuilt.
|
||||
|
||||
You can also add your own fonts and generate their PHP font data with `tc-lib-pdf-font`. For shared or immutable environments, generate them once into a persistent directory you control (outside `vendor/`) and point `K_PATH_FONTS` to that location.
|
||||
|
||||
For a runnable end-to-end custom font workflow, see [examples/E072_import_new_font.php](../examples/E072_import_new_font.php).
|
||||
|
||||
Example import commands (from the project root):
|
||||
|
||||
```bash
|
||||
mkdir -p target/fonts/source target/fonts/custom
|
||||
|
||||
curl -fL --retry 3 -o target/fonts/source/NotoSans-Regular.ttf \
|
||||
https://github.com/notofonts/noto-fonts/raw/main/hinted/ttf/NotoSans/NotoSans-Regular.ttf
|
||||
|
||||
php vendor/tecnickcom/tc-lib-pdf-font/util/convert.php \
|
||||
--outpath=target/fonts/custom \
|
||||
--type=TrueTypeUnicode \
|
||||
--flags=32 \
|
||||
--encoding_id=1 \
|
||||
--fonts=target/fonts/source/NotoSans-Regular.ttf
|
||||
```
|
||||
|
||||
Then point `K_PATH_FONTS` to `target/fonts/custom` (or an absolute path to that directory) before creating the `Tcpdf` instance.
|
||||
|
||||
```php
|
||||
\define('K_PATH_FONTS', '/opt/app/fonts/tc-lib-pdf');
|
||||
```
|
||||
|
||||
This avoids regenerating fonts on every dependency reinstall and lets multiple deployments reuse the same prepared font set.
|
||||
|
||||
## Third-Party Fonts
|
||||
|
||||
PHP font metadata files under the fonts directory are covered by the project license (GNU LGPL v3). They can be regenerated with the built-in font utilities.
|
||||
|
||||
Original source files are renamed for compatibility and compressed with PHP `gzcompress` (`.z` extension) where applicable.
|
||||
|
||||
| Prefix | Source | License |
|
||||
|--------|--------|---------|
|
||||
| `freefont` | [GNU FreeFont](https://ftp.gnu.org/gnu/freefont/freefont-ttf-20120503.zip) | GNU GPL v3 |
|
||||
| `pdfa` | [tc-font-pdfa](https://github.com/tecnickcom/tc-font-pdfa) (derived from GNU FreeFont) | GNU GPL v3 |
|
||||
| `dejavu` | [DejaVu Fonts 2.35](https://sourceforge.net/projects/dejavu/files/dejavu/2.35/dejavu-fonts-ttf-2.35.zip) | Bitstream Vera (with DejaVu public-domain changes) |
|
||||
| `unifont` | [GNU Unifont 15.1.03](https://www.unifoundry.com/pub/unifont/unifont-15.1.03/unifont-15.1.03.tar.gz) | GPL v2+ with font embedding exception (also distributed under SIL OFL 1.1) |
|
||||
| `cid0` | [GNU Unifont](http://unifoundry.com/unifont.html) (CID mappings) | GPL v2+ with font embedding exception |
|
||||
| `core` | [Adobe Core14 AFM](https://partners.adobe.com/public/developer/en/pdf/Core14_AFMs.zip) | Adobe copyright terms (see AFM notices) |
|
||||
@@ -0,0 +1,5 @@
|
||||
# ICC Profile
|
||||
|
||||
Back to root overview: [README.md](../README.md#in-depth-documentation)
|
||||
|
||||
The bundled `sRGB.icc.z` profile (stored gzip-compressed) is sourced from the Debian [`icc-profiles-free`](https://packages.debian.org/source/stable/icc-profiles-free) package.
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
# PDF Import
|
||||
|
||||
Back to root overview: [README.md](../README.md#in-depth-documentation)
|
||||
|
||||
`tc-lib-pdf` can import pages from existing PDFs as Form XObjects and place them on destination pages.
|
||||
|
||||
## Source Registration and Page Count
|
||||
|
||||
```php
|
||||
$sourceId = $pdf->setImportSourceFile('/path/to/source.pdf');
|
||||
// or: $sourceId = $pdf->setImportSourceData($rawPdfBytes);
|
||||
|
||||
$count = $pdf->getSourcePageCount($sourceId);
|
||||
```
|
||||
|
||||
The page count is derived from the page tree actually reachable through `/Kids`; the declared `/Count` entry of the `/Pages` dictionary is ignored, so a forged or wrong `/Count` cannot influence how many pages are counted or imported. Structurally broken page trees (missing `/Kids`, unexpected node types, duplicate or cyclic references) raise `ImportCorruptedSourceException`.
|
||||
|
||||
The reachable-page walk runs once per registered source: it produces a flattened page index (one effective page dictionary per page, with inherited attributes already resolved) that is cached and reused by `getSourcePageCount()`, `importPage()`, and `importPages()`. Importing all pages of an n-page document therefore costs a single tree walk plus one index lookup per page, instead of one full walk per page.
|
||||
|
||||
## Import One Page and Place It
|
||||
|
||||
```php
|
||||
$tpl = $pdf->importPage($sourceId, 1, [
|
||||
'box' => 'CropBox', // MediaBox|CropBox|BleedBox|TrimBox|ArtBox
|
||||
'groupXObject' => true,
|
||||
'cache' => true,
|
||||
'respectRotation' => true,
|
||||
]);
|
||||
|
||||
$pdf->addPage();
|
||||
$placed = $pdf->useImportedPage($tpl, 20, 20, 120, 80, [
|
||||
'keepAspectRatio' => true,
|
||||
'align' => 'CC', // TL|TC|TR|CL|CC|CR|BL|BC|BR
|
||||
'clip' => true,
|
||||
]);
|
||||
```
|
||||
|
||||
## Append Pages from a Source Document
|
||||
|
||||
```php
|
||||
// Append all pages.
|
||||
$templates = $pdf->appendDocument($sourceId);
|
||||
|
||||
// Append only selected pages.
|
||||
$templates = $pdf->appendDocument($sourceId, [1, 3, 5]);
|
||||
|
||||
// Add one imported page sized to the source page.
|
||||
$tpl = $pdf->addPageFromImport($sourceId, 2);
|
||||
```
|
||||
|
||||
## Import Examples
|
||||
|
||||
- Single page import: [examples/E065_import_single_page.php](../examples/E065_import_single_page.php)
|
||||
- Full document append: [examples/E066_import_document_append.php](../examples/E066_import_document_append.php)
|
||||
- Advanced N-up composition from imported pages: [examples/E067_import_page_region_nup.php](../examples/E067_import_page_region_nup.php)
|
||||
|
||||
## Import Limitations and Fidelity Notes
|
||||
|
||||
- Form and annotation semantics are not merged into editable destination structures; pages are imported as Form XObjects.
|
||||
- Digital signatures in source files are not preserved as valid signatures in the destination output.
|
||||
- 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.
|
||||
- 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.
|
||||
- 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.
|
||||
- Setting `groupXObject` to `false` can reduce output size, but may change compositing on source pages that rely on transparency blending.
|
||||
@@ -0,0 +1,90 @@
|
||||
# Remote Resources and fileOptions
|
||||
|
||||
Back to root overview: [README.md](../README.md#in-depth-documentation)
|
||||
|
||||
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 reads are split between internal library IO and markup-originated resource loads, with separate allowlists.
|
||||
|
||||
Remote access is controlled by the optional `$fileOptions` array passed as the last argument to the `Tcpdf` constructor (and forwarded to `initClassObjects()`).
|
||||
|
||||
## Allowing Remote Hosts
|
||||
|
||||
```php
|
||||
$pdf = new \Com\Tecnick\Pdf\Tcpdf(
|
||||
unit: 'mm',
|
||||
fileOptions: [
|
||||
'allowedHosts' => ['cdn.example.com', 'assets.myapp.io'],
|
||||
],
|
||||
);
|
||||
```
|
||||
|
||||
Only the listed host names are permitted. Any attempt to load a resource from an unlisted host is silently blocked. Supply an explicit allowlist rather than a wildcard to limit the attack surface when user-supplied URLs might reach this code path.
|
||||
|
||||
## Restricting Local Paths
|
||||
|
||||
`allowedPaths` controls which local path prefixes may be read by the shared file helper for internal library IO, such as fonts, and other explicit file operations. If you omit it, the library computes a default set of trusted roots that covers the system temp directory, the `K_PATH_FONTS` directory when that constant is defined, the tc-lib-pdf package root, and, when the package is installed as a Composer dependency, the `vendor/tecnickcom` directory holding the sibling tecnickcom packages. Paths outside these roots, for example project asset directories, are rejected until you list them here. The helper that returns these defaults is `Com\\Tecnick\\Pdf\\Base::defaultFileAllowedPaths()`.
|
||||
|
||||
`markupAllowedPaths` controls which local path prefixes may be read when resources are referenced by rendered HTML, CSS, or SVG markup. If you omit it, the library reuses an explicit `allowedPaths` value when one is provided; otherwise it computes a stricter default that excludes the system temp directory. The helper that returns those stricter defaults is `Com\\Tecnick\\Pdf\\Base::defaultMarkupAllowedPaths()`.
|
||||
|
||||
Windows absolute paths with drive letters are supported as long as they are absolute and match the trusted root after path normalization. You can provide them in native form (`C:\\...`) or normalized form (`C:/...`), but using the same canonical format for both the allowlist and the resource path is safest.
|
||||
|
||||
```php
|
||||
$pdf = new \Com\Tecnick\Pdf\Tcpdf(
|
||||
unit: 'mm',
|
||||
fileOptions: [
|
||||
'allowedPaths' => [
|
||||
(string) realpath(sys_get_temp_dir()),
|
||||
(string) realpath(__DIR__ . '/../storage/pdf-assets'),
|
||||
(string) realpath(__DIR__ . '/../vendor/tecnickcom/tc-lib-pdf-font/target/fonts'),
|
||||
],
|
||||
'markupAllowedPaths' => [
|
||||
(string) realpath(__DIR__ . '/../storage/pdf-assets'),
|
||||
(string) realpath(__DIR__ . '/../vendor/tecnickcom/tc-lib-pdf-font/target/fonts'),
|
||||
],
|
||||
],
|
||||
);
|
||||
```
|
||||
|
||||
Supplying `allowedPaths` replaces the internal-file defaults instead of merging with them. Include every local directory the PDF run needs for trusted internal operations, such as image fixtures, custom font directories, or cache-backed assets.
|
||||
|
||||
Supplying `markupAllowedPaths` replaces the stricter markup defaults instead of merging with them. Include only directories that should be reachable from rendered markup.
|
||||
|
||||
## All fileOptions Keys
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
|-----|------|---------|-------------|
|
||||
| `allowedHosts` | `string[]` | `[]` (none) | Host names the library may fetch over HTTP/HTTPS. Remote loading is disabled when this list is empty. |
|
||||
| `allowedPaths` | `string[]` | Computed internal trusted roots | Local path prefixes permitted for internal file reads. Passing this key replaces the defaults, so include all required temp/cache/font directories. |
|
||||
| `markupAllowedPaths` | `string[]` | Explicit `allowedPaths` value, else stricter computed roots | Local path prefixes permitted for file reads triggered by rendered HTML, CSS, or SVG markup. Passing this key replaces the markup defaults. |
|
||||
| `maxRemoteSize` | `int` | `52428800` (50 MiB) | Maximum bytes accepted for a single remote download. Requests exceeding this limit are aborted. |
|
||||
| `curlopts` | `array<int, bool\|int\|string>` | `[]` | Per-request cURL options (keyed by `CURLOPT_*` constants) merged on top of the built-in defaults. |
|
||||
| `defaultCurlOpts` | `array<int, bool\|int\|string>` | `null` | Replaces the built-in default cURL option set entirely. Omit this key to keep the safe defaults. |
|
||||
| `fixedCurlOpts` | `array<int, bool\|int\|string>` | `null` | cURL options that are always enforced and cannot be overridden by `curlopts` - useful for pinning TLS settings in locked-down environments. |
|
||||
|
||||
## Example: Pinning TLS and Setting a Short Timeout
|
||||
|
||||
```php
|
||||
$pdf = new \Com\Tecnick\Pdf\Tcpdf(
|
||||
unit: 'mm',
|
||||
fileOptions: [
|
||||
'allowedHosts' => ['cdn.example.com'],
|
||||
'allowedPaths' => [
|
||||
(string) realpath(sys_get_temp_dir()),
|
||||
(string) realpath(__DIR__ . '/../storage/pdf-assets'),
|
||||
(string) realpath(__DIR__ . '/../vendor/tecnickcom/tc-lib-pdf-font/target/fonts'),
|
||||
],
|
||||
'markupAllowedPaths' => [
|
||||
(string) realpath(__DIR__ . '/../storage/pdf-assets'),
|
||||
(string) realpath(__DIR__ . '/../vendor/tecnickcom/tc-lib-pdf-font/target/fonts'),
|
||||
],
|
||||
'maxRemoteSize' => 10 * 1024 * 1024, // 10 MiB
|
||||
'curlopts' => [
|
||||
CURLOPT_TIMEOUT => 10,
|
||||
CURLOPT_CONNECTTIMEOUT => 5,
|
||||
],
|
||||
'fixedCurlOpts' => [
|
||||
CURLOPT_SSL_VERIFYPEER => true,
|
||||
CURLOPT_SSL_VERIFYHOST => 2,
|
||||
],
|
||||
],
|
||||
);
|
||||
```
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
# Standards and Conformance
|
||||
|
||||
Back to root overview: [README.md](../README.md#in-depth-documentation)
|
||||
|
||||
## PDF/A Archival
|
||||
|
||||
`tc-lib-pdf` supports PDF/A output for long-term archival workflows (ISO 19005). Pass the mode string as the `mode` argument to the `Tcpdf` constructor:
|
||||
|
||||
```php
|
||||
// PDF/A-1b (default conformance level when suffix is omitted)
|
||||
$pdf = new \Com\Tecnick\Pdf\Tcpdf(mode: 'pdfa1');
|
||||
|
||||
// Explicit conformance levels
|
||||
$pdf = new \Com\Tecnick\Pdf\Tcpdf(mode: 'pdfa1a'); // PDF/A-1a
|
||||
$pdf = new \Com\Tecnick\Pdf\Tcpdf(mode: 'pdfa1b'); // PDF/A-1b
|
||||
$pdf = new \Com\Tecnick\Pdf\Tcpdf(mode: 'pdfa2a'); // PDF/A-2a
|
||||
$pdf = new \Com\Tecnick\Pdf\Tcpdf(mode: 'pdfa2b'); // PDF/A-2b
|
||||
$pdf = new \Com\Tecnick\Pdf\Tcpdf(mode: 'pdfa2u'); // PDF/A-2u
|
||||
$pdf = new \Com\Tecnick\Pdf\Tcpdf(mode: 'pdfa3a'); // PDF/A-3a
|
||||
$pdf = new \Com\Tecnick\Pdf\Tcpdf(mode: 'pdfa3b'); // PDF/A-3b
|
||||
$pdf = new \Com\Tecnick\Pdf\Tcpdf(mode: 'pdfa3u'); // PDF/A-3u
|
||||
```
|
||||
|
||||
| Mode suffix | Conformance | Unicode ToUnicode | Tagged structure |
|
||||
|-------------|-------------|-------------------|------------------|
|
||||
| `a` | Level A | required | required |
|
||||
| `b` | Level B | required | not required |
|
||||
| `u` | Level U (parts 2/3 only) | required | not required |
|
||||
|
||||
PDF/A-3 supports embedding arbitrary file attachments (for example XML invoice payloads). This is the basis for **Factur-X / ZUGFeRD** workflows - embed the structured XML in a PDF/A-3 document and register the relationship via XMP metadata:
|
||||
|
||||
```php
|
||||
$pdf = new \Com\Tecnick\Pdf\Tcpdf(mode: 'pdfa3');
|
||||
// ... build document ...
|
||||
$pdf->addContentAsEmbeddedFile(
|
||||
file: 'factur-x.xml',
|
||||
content: $invoiceXML,
|
||||
mime: 'text/xml',
|
||||
afrel: \Com\Tecnick\Pdf\AFRelationship::Alternative,
|
||||
);
|
||||
$pdf->setCustomXMP('x:xmpmeta.rdf:RDF.rdf:Description.pdfaExtension:schemas.rdf:Bag', $xmpBag);
|
||||
```
|
||||
|
||||
Runnable example (invoice with embedded Factur-X XML): [examples/E001_invoice.php](../examples/E001_invoice.php).
|
||||
|
||||
## PDF/X Conformance
|
||||
|
||||
`tc-lib-pdf` supports multiple PDF/X profiles for print-exchange workflows. Pass the mode string as the `mode` argument to the `Tcpdf` constructor:
|
||||
|
||||
```php
|
||||
// Generic PDF/X alias (maps to the library's baseline print-exchange workflow)
|
||||
$pdf = new \Com\Tecnick\Pdf\Tcpdf(mode: 'pdfx');
|
||||
|
||||
// Specific variants
|
||||
$pdf = new \Com\Tecnick\Pdf\Tcpdf(mode: 'pdfx1a'); // PDF/X-1a:2003
|
||||
$pdf = new \Com\Tecnick\Pdf\Tcpdf(mode: 'pdfx3'); // PDF/X-3:2003
|
||||
$pdf = new \Com\Tecnick\Pdf\Tcpdf(mode: 'pdfx4'); // PDF/X-4:2010
|
||||
$pdf = new \Com\Tecnick\Pdf\Tcpdf(mode: 'pdfx5'); // PDF/X-5g:2010
|
||||
```
|
||||
|
||||
Each variant automatically applies the appropriate conformance constraints:
|
||||
|
||||
| Mode | Min PDF version | Transparency | Process colors | GTS_PDFXVersion |
|
||||
|------|-----------------|--------------|----------------|-----------------|
|
||||
| `pdfx` / `pdfx3` | 1.3 | blocked | CMYK forced | PDF/X-3:2003 |
|
||||
| `pdfx1a` | 1.3 | blocked | CMYK forced | PDF/X-1a:2003 |
|
||||
| `pdfx4` | 1.6 | allowed | unrestricted | PDF/X-4:2010 |
|
||||
| `pdfx5` | 1.6 | allowed | unrestricted | PDF/X-5g:2010 |
|
||||
|
||||
All PDF/X modes suppress encryption and JavaScript (not permitted by the ISO 15930 standard).
|
||||
|
||||
Runnable examples: [examples/E010_pdfx.php](../examples/E010_pdfx.php) through [examples/E014_pdfx5.php](../examples/E014_pdfx5.php).
|
||||
|
||||
## PDF/UA Accessibility
|
||||
|
||||
`tc-lib-pdf` supports tagged PDF output conforming to PDF/UA (ISO 14289). Pass the mode string as the `mode` argument to the `Tcpdf` constructor:
|
||||
|
||||
```php
|
||||
// Generic PDF/UA alias
|
||||
$pdf = new \Com\Tecnick\Pdf\Tcpdf(mode: 'pdfua');
|
||||
|
||||
// Specific parts
|
||||
$pdf = new \Com\Tecnick\Pdf\Tcpdf(mode: 'pdfua1'); // PDF/UA-1 (PDF 1.7)
|
||||
$pdf = new \Com\Tecnick\Pdf\Tcpdf(mode: 'pdfua2'); // PDF/UA-2 (PDF 2.0)
|
||||
```
|
||||
|
||||
When a PDF/UA mode is active the library automatically:
|
||||
|
||||
- Writes a `StructTreeRoot` with a `ParentTree` that maps every page to its tagged content blocks
|
||||
- Emits `MarkInfo << /Marked true >>` in the document catalog
|
||||
- Sets `/Lang` (defaults to `en-US` when not explicitly provided)
|
||||
- Forces `ViewerPreferences /DisplayDocTitle true`
|
||||
- Maps HTML heading elements (`h1`-`h6`) to PDF structure roles `H1`-`H6` with level-clamping to prevent skipped heading levels
|
||||
- Tags text content with MCIDs and wraps each run in the appropriate structure element (`P`, `H1`-`H6`, `Link`, etc.)
|
||||
- Tags `<img>` elements as `Figure` with their `alt` attribute written as `/Alt` in the structure element
|
||||
- Emits `ActualText` entries for ligatures and special glyphs so text extraction and screen readers work correctly
|
||||
- Provides Artifact marked-content helpers for non-semantic content (`beginArtifact()`, `endArtifact()`, `addArtifactContent()`)
|
||||
|
||||
To provide the document language explicitly:
|
||||
|
||||
```php
|
||||
$pdf->setLanguageArray(['a_meta_language' => 'de-DE']);
|
||||
```
|
||||
|
||||
To tag decorative or repeated content as Artifact (for example headers, footers, and page numbers):
|
||||
|
||||
```php
|
||||
$pid = $pdf->addPage()['pid'];
|
||||
|
||||
$headerOperators = $pdf->graph->getLine(10, 10, 200, 10);
|
||||
$pdf->addArtifactContent($headerOperators, $pid, 'Pagination', 'Header');
|
||||
|
||||
$footerText = $pdf->getTextCell('Page 1', 180, 280, 20, 5);
|
||||
$pdf->addArtifactContent($footerText, $pid, 'Pagination', 'Footer');
|
||||
```
|
||||
|
||||
In PDF/UA mode, the built-in `defaultPageContent()` page-number footer is emitted as `Artifact` with
|
||||
`/Type /Pagination /Subtype /Footer`.
|
||||
|
||||
Runnable examples: [examples/E015_pdfua.php](../examples/E015_pdfua.php) through [examples/E017_pdfua2.php](../examples/E017_pdfua2.php).
|
||||
Reference in New Issue
Block a user