├── RELEASE ├── VERSION ├── CODEOWNERS ├── resources ├── debian │ ├── compat │ ├── source │ │ └── format │ ├── rules │ ├── changelog │ ├── copyright │ └── control ├── etc │ └── php-tecnickcom-tc-lib-pdf │ │ └── config.json ├── autoload.php └── rpm │ └── rpm.spec ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── pull_request_template.md └── workflows │ └── check.yml ├── src ├── include │ └── sRGB.icc.z ├── Exception.php ├── ClassObjects.php ├── Base.php ├── MetaInfo.php ├── Cell.php ├── CSS.php └── Tcpdf.php ├── examples ├── data │ ├── cert │ │ ├── tcpdf.fdf │ │ ├── tcpdf.p12 │ │ └── tcpdf.crt │ └── utf8test.txt ├── images │ ├── tcpdf_logo.jpg │ ├── tcpdf_signature.png │ ├── tcpdf_box.svg │ ├── tcpdf_box.ai │ └── testsvg.svg └── invoice.php ├── SECURITY.md ├── phpstan.neon ├── phpcompatinfo.json ├── .gitignore ├── phpcs.xml ├── .editorconfig ├── phpunit.xml.dist ├── test ├── TcpdfTest.php └── TestUtil.php ├── CONTRIBUTING.md ├── composer.json ├── CODE_OF_CONDUCT.md ├── README.md └── Makefile /RELEASE: -------------------------------------------------------------------------------- 1 | 0 2 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 8.4.0 2 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @nicolaasuni 2 | -------------------------------------------------------------------------------- /resources/debian/compat: -------------------------------------------------------------------------------- 1 | 10 2 | -------------------------------------------------------------------------------- /resources/debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /resources/etc/php-tecnickcom-tc-lib-pdf/config.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | %: 3 | dh $@ 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: ['https://www.paypal.com/donate/?hosted_button_id=NZUEC5XS8MFBJ'] 2 | -------------------------------------------------------------------------------- /src/include/sRGB.icc.z: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tecnickcom/tc-lib-pdf/HEAD/src/include/sRGB.icc.z -------------------------------------------------------------------------------- /examples/data/cert/tcpdf.fdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tecnickcom/tc-lib-pdf/HEAD/examples/data/cert/tcpdf.fdf -------------------------------------------------------------------------------- /examples/data/cert/tcpdf.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tecnickcom/tc-lib-pdf/HEAD/examples/data/cert/tcpdf.p12 -------------------------------------------------------------------------------- /examples/images/tcpdf_logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tecnickcom/tc-lib-pdf/HEAD/examples/images/tcpdf_logo.jpg -------------------------------------------------------------------------------- /examples/images/tcpdf_signature.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tecnickcom/tc-lib-pdf/HEAD/examples/images/tcpdf_signature.png -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Reporting a Vulnerability 4 | 5 | Please report (suspected) security vulnerabilities to info@tecnick.com. 6 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | level: max 3 | paths: 4 | - src 5 | - test 6 | excludePaths: 7 | - vendor 8 | ignoreErrors: 9 | reportUnmatchedIgnoredErrors: false 10 | treatPhpDocTypesAsCertain: false 11 | -------------------------------------------------------------------------------- /resources/debian/changelog: -------------------------------------------------------------------------------- 1 | ~#PKGNAME#~ (~#VERSION#~-~#RELEASE#~) UNRELEASED; urgency=low 2 | 3 | * Please check the 4 | https://github.com/~#VENDOR#~/~#PROJECT#~ 5 | commit history 6 | 7 | -- Nicola Asuni ~#DATE#~ 8 | -------------------------------------------------------------------------------- /phpcompatinfo.json: -------------------------------------------------------------------------------- 1 | { 2 | "source-providers": [ 3 | { 4 | "in": "src as source", 5 | "exclude": "vendor", 6 | "name": "/\\.(php)$/" 7 | } 8 | ], 9 | "plugins": [ 10 | ], 11 | "analysers": [ 12 | ], 13 | "services": [ 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .phpdoc 2 | .phpunit.cache 3 | .phpunit.result.cache 4 | .vscode 5 | **/._* 6 | **/.#* 7 | **/.DS_Store 8 | **/.idea 9 | **/.vagrant 10 | **/*.bak 11 | **/*.tmp 12 | **/auth.json 13 | **/nbproject 14 | **/temp.php 15 | **/test.php 16 | composer.lock 17 | ecs.php 18 | phpunit.xml 19 | rector.php 20 | target 21 | vendor 22 | -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Test code for compatibility with supported PHP versions 4 | 5 | 6 | src 7 | test 8 | ./vendor/* 9 | 10 | 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Ref: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style end of lines and a blank line at the end of the file 7 | [*] 8 | indent_style = tab 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | 14 | [*.php] 15 | indent_style = space 16 | indent_size = 4 17 | 18 | [*.{js,json,scss,css,yml,vue}] 19 | indent_style = space 20 | indent_size = 2 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. ... 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Logs** 21 | If applicable, copy the relevant logs to help explain your problem. 22 | 23 | **Environment:** 24 | - OS: 25 | - PHP version: 26 | - Version: 27 | 28 | **Additional context** 29 | Add any other context about the problem here. 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /src/Exception.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2015-2025 Nicola Asuni - Tecnick.com LTD 11 | * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) 12 | * @link https://github.com/tecnickcom/tc-lib-pdf 13 | * 14 | * This file is part of tc-lib-pdf software library. 15 | */ 16 | 17 | namespace Com\Tecnick\Pdf; 18 | 19 | /** 20 | * Com\Tecnick\Pdf\Exception 21 | * 22 | * Custom Exception class 23 | * 24 | * @since 2015-02-21 25 | * @category Library 26 | * @package Pdf 27 | * @author Nicola Asuni 28 | * @copyright 2015-2025 Nicola Asuni - Tecnick.com LTD 29 | * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) 30 | * @link https://github.com/tecnickcom/tc-lib-pdf 31 | */ 32 | class Exception extends \Exception 33 | { 34 | } 35 | -------------------------------------------------------------------------------- /resources/autoload.php: -------------------------------------------------------------------------------- 1 | 11 | * @copyright 2002-2025 Nicola Asuni - Tecnick.com LTD 12 | * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) 13 | * @link https://github.com/tecnickcom/tc-lib-pdf 14 | * 15 | * This file is part of tc-lib-pdf software library. 16 | */ 17 | \spl_autoload_register( 18 | function ($class) { 19 | $prefix = 'Com\\Tecnick\\'; 20 | $len = \strlen($prefix); 21 | if (\strncmp($prefix, $class, $len) !== 0) { 22 | return; 23 | } 24 | $relative_class = \substr($class, $len); 25 | $file = \dirname(__DIR__).'/'.\str_replace('\\', '/', $relative_class).'.php'; 26 | if (\file_exists($file)) { 27 | require $file; 28 | } 29 | } 30 | ); 31 | -------------------------------------------------------------------------------- /resources/debian/copyright: -------------------------------------------------------------------------------- 1 | Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: ~#PROJECT#~ 3 | Source: https://github.com/~#VENDOR#~/~#PROJECT#~ 4 | 5 | Files: * 6 | Copyright: Copyright 2001-2025 Nicola Asuni 7 | License: LGPL-3 8 | 9 | License: LGPL-3 10 | This program is free software: you can redistribute it and/or modify 11 | it under the terms of the GNU Lesser General Public License as 12 | published by the Free Software Foundation, either version 3 of the 13 | License, or (at your option) any later version. 14 | This program is distributed in the hope that it will be useful, 15 | but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | GNU General Public License for more details. 18 | You should have received a copy of the GNU General Public License 19 | along with this program. If not, see or 20 | /usr/share/common-licenses/LGPL-3 21 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./test 15 | 16 | 17 | 18 | 19 | src 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | Please include a summary of the change and include relevant motivation and context. 4 | 5 | ... 6 | 7 | 8 | ## Checklist: 9 | 10 | - [ ] The `make buildall` command has been run successfully without any error or warning. 11 | - [ ] Any new code line is covered by unit tests and the coverage has not dropped. 12 | - [ ] Any new code follows the style guidelines of this project. 13 | - [ ] The code changes have been self-reviewed. 14 | - [ ] Corresponding changes to the documentation have been made. 15 | - [ ] The version has been updated in the VERSION file. 16 | 17 | ## Type of change: 18 | 19 | - [ ] Minor non-breaking change (e.g., bug fix, dependencies updates) → The patch number in the VERSION file has been increased. 20 | - [ ] New feature (non-breaking change which adds functionality) → The minor number in the VERSION file has been increased. 21 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) → The major number in the VERSION file has been increased. 22 | - [ ] Automation. 23 | - [ ] Documentation. 24 | - [ ] Example. 25 | - [ ] Testing. 26 | -------------------------------------------------------------------------------- /test/TcpdfTest.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2002-2025 Nicola Asuni - Tecnick.com LTD 11 | * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) 12 | * @link https://github.com/tecnickcom/tc-lib-pdf 13 | * 14 | * This file is part of tc-lib-pdf software library. 15 | */ 16 | 17 | namespace Test; 18 | 19 | /** 20 | * Tcpdf Pdf class test 21 | * 22 | * @since 2002-08-03 23 | * @category Library 24 | * @package Pdf 25 | * @author Nicola Asuni 26 | * @copyright 2002-2025 Nicola Asuni - Tecnick.com LTD 27 | * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) 28 | * @link https://github.com/tecnickcom/tc-lib-pdf 29 | */ 30 | class TcpdfTest extends TestUtil 31 | { 32 | protected function getTestObject(): \Com\Tecnick\Pdf\Tcpdf 33 | { 34 | return new \Com\Tecnick\Pdf\Tcpdf(); 35 | } 36 | 37 | public function testDummy(): void 38 | { 39 | $this->assertEquals(1, 1); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | 4 | ## Reporting a bug 5 | 6 | * **Do not open up a GitHub issue if the bug is a security vulnerability**, and instead to refer to our [Security policy](SECURITY.md). 7 | 8 | * Ensure the bug was not already reported by searching on GitHub Issues. 9 | 10 | * If you're unable to find an open issue addressing the problem, open a new one. Be sure to include a **title and clear description**, as much relevant information as possible, and a **code sample** or an **executable test case** demonstrating the expected behavior that is not occurring. 11 | 12 | 13 | ## Submitting a bug fix 14 | 15 | * Open a new GitHub pull request with the patch. 16 | 17 | * Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable. 18 | 19 | * Ensure the new code is following the existing conventions and the unit test coverage is 100%. 20 | 21 | * Before submitting, please run the following command locally to ensure the code is passing the automatic checks: `make buildall`. 22 | 23 | 24 | ## Add a new feature or change an existing one 25 | 26 | * Before writing any code please suggest the change by opening a new Feature Request on Issues. 27 | -------------------------------------------------------------------------------- /resources/debian/control: -------------------------------------------------------------------------------- 1 | Source: ~#PKGNAME#~ 2 | Maintainer: Nicola Asuni 3 | Section: php 4 | Priority: optional 5 | Build-Depends: debhelper (>= 9) 6 | Standards-Version: 3.9.7 7 | Homepage: https://github.com/~#VENDOR#~/~#PROJECT#~ 8 | Vcs-Git: https://github.com/~#VENDOR#~/~#PROJECT#~.git 9 | 10 | Package: ~#PKGNAME#~ 11 | Provides: php-~#PROJECT#~ 12 | Architecture: all 13 | Depends: php (>= 8.1.0), php-date, php-tecnickcom-tc-lib-barcode (<< 3.0.0), php-tecnickcom-tc-lib-barcode (>= 2.4.18), php-tecnickcom-tc-lib-color (<< 3.0.0), php-tecnickcom-tc-lib-color (>= 2.3.2), php-tecnickcom-tc-lib-pdf-image (<< 3.0.0), php-tecnickcom-tc-lib-pdf-image (>= 2.1.24), php-tecnickcom-tc-lib-pdf-font (<< 3.0.0), php-tecnickcom-tc-lib-pdf-font (>= 2.6.22), php-tecnickcom-tc-lib-file (<< 3.0.0), php-tecnickcom-tc-lib-file (>= 2.2.12), php-tecnickcom-tc-lib-pdf-encrypt (<< 3.0.0), php-tecnickcom-tc-lib-pdf-encrypt (>= 2.1.26), php-tecnickcom-tc-lib-unicode-data (<< 3.0.0), php-tecnickcom-tc-lib-unicode-data (>= 2.0.34), php-tecnickcom-tc-lib-unicode (<< 3.0.0), php-tecnickcom-tc-lib-unicode (>= 2.0.35), php-tecnickcom-tc-lib-pdf-page (<< 5.0.0), php-tecnickcom-tc-lib-pdf-page (>= 4.3.3), php-tecnickcom-tc-lib-pdf-graph (<< 3.0.0), php-tecnickcom-tc-lib-pdf-graph (>= 2.4.2), ${misc:Depends} 14 | Description: PHP Barcode library 15 | This library includes PHP classes to generate PDF documents. 16 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tecnickcom/tc-lib-pdf", 3 | "description": "PHP PDF Library", 4 | "type": "library", 5 | "homepage": "http://www.tecnick.com", 6 | "license": "LGPL-3.0-or-later", 7 | "keywords": [ 8 | "tc-lib-pdf", 9 | "TCPDF", 10 | "PDF", 11 | "PDFD32000-2008", 12 | "document" 13 | ], 14 | "authors": [ 15 | { 16 | "name": "Nicola Asuni", 17 | "email": "info@tecnick.com", 18 | "role": "lead" 19 | } 20 | ], 21 | "require": { 22 | "php": ">=8.1", 23 | "ext-date": "*", 24 | "ext-pcre": "*", 25 | "tecnickcom/tc-lib-barcode": "^2.4", 26 | "tecnickcom/tc-lib-color": "^2.3", 27 | "tecnickcom/tc-lib-pdf-image": "^2.1", 28 | "tecnickcom/tc-lib-pdf-font": "^2.6", 29 | "tecnickcom/tc-lib-file": "^2.2", 30 | "tecnickcom/tc-lib-pdf-encrypt": "^2.1", 31 | "tecnickcom/tc-lib-unicode-data": "^2.0", 32 | "tecnickcom/tc-lib-unicode": "^2.0", 33 | "tecnickcom/tc-lib-pdf-page": "^4.3", 34 | "tecnickcom/tc-lib-pdf-graph": "^2.4" 35 | }, 36 | "config": { 37 | "allow-plugins": { 38 | "dealerdirect/phpcodesniffer-composer-installer": true 39 | } 40 | }, 41 | "require-dev": { 42 | "pdepend/pdepend": "2.16.2", 43 | "phpmd/phpmd": "2.15.0", 44 | "phpunit/phpunit": "12.4.4 || 11.5.44 || 10.5.58", 45 | "squizlabs/php_codesniffer": "4.0.1", 46 | "phpcompatibility/php-compatibility": "^10.0.0@dev" 47 | }, 48 | "autoload": { 49 | "psr-4": { 50 | "Com\\Tecnick\\Pdf\\": "src" 51 | } 52 | }, 53 | "autoload-dev": { 54 | "psr-4": { 55 | "Test\\": "test" 56 | } 57 | }, 58 | "archive": { 59 | "exclude": [ 60 | "/example" 61 | ] 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /test/TestUtil.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2015-2025 Nicola Asuni - Tecnick.com LTD 11 | * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) 12 | * @link https://github.com/tecnickcom/tc-lib-pdf 13 | * 14 | * This file is part of tc-lib-file software library. 15 | */ 16 | 17 | namespace Test; 18 | 19 | use PHPUnit\Framework\TestCase; 20 | 21 | /** 22 | * Test Util 23 | * 24 | * @since 2020-12-19 25 | * @category Library 26 | * @package Pdf 27 | * @author Nicola Asuni 28 | * @copyright 2015-2025 Nicola Asuni - Tecnick.com LTD 29 | * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) 30 | * @link https://github.com/tecnickcom/tc-lib-pdf 31 | */ 32 | class TestUtil extends TestCase 33 | { 34 | public function bcAssertEqualsWithDelta( 35 | mixed $expected, 36 | mixed $actual, 37 | float $delta = 0.01, 38 | string $message = '' 39 | ): void { 40 | parent::assertEqualsWithDelta($expected, $actual, $delta, $message); 41 | } 42 | 43 | /** 44 | * @param class-string<\Throwable> $exception 45 | */ 46 | public function bcExpectException($exception): void 47 | { 48 | parent::expectException($exception); 49 | } 50 | 51 | public function bcAssertIsResource(mixed $res): void 52 | { 53 | parent::assertIsResource($res); 54 | } 55 | 56 | public function bcAssertMatchesRegularExpression(string $pattern, string $string, string $message = ''): void 57 | { 58 | parent::assertMatchesRegularExpression($pattern, $string, $message); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /.github/workflows/check.yml: -------------------------------------------------------------------------------- 1 | name: check 2 | 3 | env: 4 | XDEBUG_MODE: coverage 5 | 6 | permissions: 7 | contents: read 8 | 9 | on: 10 | push: 11 | branches: 12 | - 'main' 13 | pull_request: 14 | types: [opened, synchronize, reopened] 15 | branches: 16 | - main 17 | 18 | jobs: 19 | test-php: 20 | name: Test on php ${{ matrix.php-version }} and ${{ matrix.os }} 21 | runs-on: ${{ matrix.os }} 22 | continue-on-error: ${{ matrix.experimental }} 23 | strategy: 24 | fail-fast: false 25 | matrix: 26 | php-version: ["8.1", "8.2", "8.3", "8.4", "8.5"] 27 | experimental: [false] 28 | os: [ubuntu-latest] 29 | coverage-extension: [pcov] 30 | steps: 31 | - uses: actions/checkout@v6 32 | #- name: Install pdfinfo 33 | # run: sudo apt-get install -y poppler-utils 34 | - name: Use php ${{ matrix.php-version }} 35 | uses: shivammathur/setup-php@v2 36 | with: 37 | php-version: ${{ matrix.php-version }} 38 | coverage: ${{ matrix.coverage-extension }} 39 | extensions: bcmath, curl, date, gd, hash, imagick, json, mbstring, openssl, pcre, zlib 40 | ini-values: display_errors=on, error_reporting=-1, zend.assertions=1 41 | - name: List php modules 42 | run: php -m 43 | - name: List php modules using "no php ini" mode 44 | run: php -m -n 45 | - name: Cache module 46 | uses: actions/cache@v4 47 | with: 48 | path: ~/.composer/cache/ 49 | key: composer-cache 50 | - name: Install dependencies 51 | run: make deps 52 | - name: Run all tests 53 | run: make qa 54 | - name: Send coverage 55 | uses: codecov/codecov-action@v5 56 | with: 57 | flags: php-${{ matrix.php-version }}-${{ matrix.os }} 58 | name: php-${{ matrix.php-version }}-${{ matrix.os }} 59 | -------------------------------------------------------------------------------- /examples/data/cert/tcpdf.crt: -------------------------------------------------------------------------------- 1 | Bag Attributes 2 | localKeyID: 7B AB 1B 7A BE 4C 85 C0 1A A6 DC 59 3F 79 48 C3 93 38 68 9C 3 | subject=/CN=TCPDF DEMO/O=TCPDF/OU=DEMO/emailAddress=you@example.com/C=IT 4 | issuer=/CN=TCPDF DEMO/O=TCPDF/OU=DEMO/emailAddress=you@example.com/C=IT 5 | -----BEGIN CERTIFICATE----- 6 | MIIC1TCCAj6gAwIBAgIKkehOL/XGkB5cjjANBgkqhkiG9w0BAQUFADBhMRMwEQYD 7 | VQQDEwpUQ1BERiBERU1PMQ4wDAYDVQQKEwVUQ1BERjENMAsGA1UECxMEREVNTzEe 8 | MBwGCSqGSIb3DQEJARYPeW91QGV4YW1wbGUuY29tMQswCQYDVQQGEwJJVDAeFw0w 9 | OTA4MjExMjU0NDhaFw0xNDA4MjExMjU0NDhaMGExEzARBgNVBAMTClRDUERGIERF 10 | TU8xDjAMBgNVBAoTBVRDUERGMQ0wCwYDVQQLEwRERU1PMR4wHAYJKoZIhvcNAQkB 11 | Fg95b3VAZXhhbXBsZS5jb20xCzAJBgNVBAYTAklUMIGfMA0GCSqGSIb3DQEBAQUA 12 | A4GNADCBiQKBgQDAqIL0uGKmTR98Lxx2vEEE1OGKkMXFo0JViitALe7Onhxxqx0H 13 | XMUDKF5mvEVu1rcvh7/oAnAfrCuEpL/up3u1mQCgBE7WXBnFFE/AE3jCksh9OkS0 14 | Z0Xj9woN5bzxRDsGoPiOu/4xzk5qSEXt8jf2Ep90QuNkqLIRT4swAzpDbwIDAQAB 15 | o4GTMIGQMDcGA1UdEgQwMC6gEQYDVQQDDApUQ1BERiBERU1PoAwGA1UECgwFVENQ 16 | REagCwYDVQQLDARERU1PMDcGA1UdEQQwMC6gEQYDVQQDDApUQ1BERiBERU1PoAwG 17 | A1UECgwFVENQREagCwYDVQQLDARERU1PMA8GCSqGSIb3LwEBCgQCBQAwCwYDVR0P 18 | BAQDAgSQMA0GCSqGSIb3DQEBBQUAA4GBAEhTQfqX3ZNdHmpTLDbIj22RHXii2roE 19 | OavCbu9WsHoWpva0qSd+yIoD594VHvYAd29sfzDfiN+7W0aiZfDhq5jpaSQMVlN8 20 | RGYMupbHY/+a9Gz1wqxnR84mlTtIkZVRYAhsfPwy6M1BEjdMqfdh9h40JIdkdjtb 21 | 8faTCfXPePWQ 22 | -----END CERTIFICATE----- 23 | Bag Attributes 24 | localKeyID: 7B AB 1B 7A BE 4C 85 C0 1A A6 DC 59 3F 79 48 C3 93 38 68 9C 25 | Key Attributes: 26 | -----BEGIN RSA PRIVATE KEY----- 27 | MIICXQIBAAKBgQDAqIL0uGKmTR98Lxx2vEEE1OGKkMXFo0JViitALe7Onhxxqx0H 28 | XMUDKF5mvEVu1rcvh7/oAnAfrCuEpL/up3u1mQCgBE7WXBnFFE/AE3jCksh9OkS0 29 | Z0Xj9woN5bzxRDsGoPiOu/4xzk5qSEXt8jf2Ep90QuNkqLIRT4swAzpDbwIDAQAB 30 | AoGAXc+wNMmz/5Z+RlIKYia44klmqbplEx+0JULqXI4BQsrqvs67i+I4bJkznoL+ 31 | rEIRYSuQ3sCRKFsFtckjTGpxadnxkB+uwGKc6pZChv99BFX6HFR4hgBlT/BBRAQA 32 | hMDlM2JIRr4S4SMVXR7MHwGMUf9mUeanGLR3ZWtU3aXJrIECQQD7OaYUVYNEEnM9 33 | uXyjm22CuHyqyEf5gb13sK0uQty67547yJTMUQZd/sQc9KGwhzBbhrob2LO2jAhh 34 | S+f+NSRnAkEAxFHm3fMI5RgXmswxlGm4QW07a/Ueo7ZJG6xjTkFXluJhd+XHswRD 35 | dQIO3zG9nGjNUoeMrPhXhPvKqFc2F9RDuQJAQBEGin74N77gxqfr4ik79y8nE8J5 36 | oGZ2s/RJZdfFRKLg3mwbjjNHhWb4Ck5UgZkoOt8TzRApXG8/n9hktE5HFwJBALur 37 | M5AueO1Pl5kB489lNJ9OxUQRYUXMxpxuscuoCQwSwmv0O2+0/qtG2WKhUQnI4aYo 38 | L+FV0YwtivBb1jj3T/kCQQDIWOxq8eRowdaMzvJpRUHFgMcf1AVZExKyrugwYOWd 39 | KNsDxC4KaQOsPt8iT/Ulo4g/MJC0HolCOhWibKmR9Ayl 40 | -----END RSA PRIVATE KEY----- 41 | -------------------------------------------------------------------------------- /resources/rpm/rpm.spec: -------------------------------------------------------------------------------- 1 | # SPEC file 2 | 3 | %global c_vendor %{_vendor} 4 | %global gh_owner %{_owner} 5 | %global gh_project %{_project} 6 | 7 | Name: %{_package} 8 | Version: %{_version} 9 | Release: %{_release}%{?dist} 10 | Summary: PHP library to generate PDF documents 11 | 12 | Group: Development/Libraries 13 | License: LGPLv3+ 14 | URL: https://github.com/%{gh_owner}/%{gh_project} 15 | 16 | BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-%(%{__id_u} -n) 17 | BuildArch: noarch 18 | 19 | Requires: php(language) >= 8.1.0 20 | Requires: php-date 21 | Requires: php-pcre 22 | Requires: php-composer(%{c_vendor}/tc-lib-barcode) < 3.0.0 23 | Requires: php-composer(%{c_vendor}/tc-lib-barcode) >= 2.4.18 24 | Requires: php-composer(%{c_vendor}/tc-lib-color) < 3.0.0 25 | Requires: php-composer(%{c_vendor}/tc-lib-color) >= 2.3.2 26 | Requires: php-composer(%{c_vendor}/tc-lib-pdf-image) < 3.0.0 27 | Requires: php-composer(%{c_vendor}/tc-lib-pdf-image) >= 2.1.24 28 | Requires: php-composer(%{c_vendor}/tc-lib-pdf-font) < 3.0.0 29 | Requires: php-composer(%{c_vendor}/tc-lib-pdf-font) >= 2.6.22 30 | Requires: php-composer(%{c_vendor}/tc-lib-file) < 3.0.0 31 | Requires: php-composer(%{c_vendor}/tc-lib-file) >= 2.2.12 32 | Requires: php-composer(%{c_vendor}/tc-lib-pdf-encrypt) < 3.0.0 33 | Requires: php-composer(%{c_vendor}/tc-lib-pdf-encrypt) >= 2.1.26 34 | Requires: php-composer(%{c_vendor}/tc-lib-unicode-data) < 3.0.0 35 | Requires: php-composer(%{c_vendor}/tc-lib-unicode-data) >= 2.0.34 36 | Requires: php-composer(%{c_vendor}/tc-lib-unicode) < 3.0.0 37 | Requires: php-composer(%{c_vendor}/tc-lib-unicode) >= 2.0.35 38 | Requires: php-composer(%{c_vendor}/tc-lib-pdf-page) < 5.0.0 39 | Requires: php-composer(%{c_vendor}/tc-lib-pdf-page) >= 4.3.3 40 | Requires: php-composer(%{c_vendor}/tc-lib-pdf-graph) < 3.0.0 41 | Requires: php-composer(%{c_vendor}/tc-lib-pdf-graph) >= 2.4.2 42 | 43 | Provides: php-composer(%{c_vendor}/%{gh_project}) = %{version} 44 | Provides: php-%{gh_project} = %{version} 45 | 46 | %description 47 | PHP library to generate PDF documents 48 | 49 | %build 50 | #(cd %{_current_directory} && make build) 51 | 52 | %install 53 | rm -rf $RPM_BUILD_ROOT 54 | (cd %{_current_directory} && make install DESTDIR=$RPM_BUILD_ROOT) 55 | 56 | %clean 57 | rm -rf $RPM_BUILD_ROOT 58 | #(cd %{_current_directory} && make clean) 59 | 60 | %files 61 | %attr(-,root,root) %{_libpath} 62 | %attr(-,root,root) %{_docpath} 63 | %docdir %{_docpath} 64 | %config(noreplace) %{_configpath}* 65 | 66 | %changelog 67 | * Fri Jun 10 2024 Nicola Asuni 8.0.0-1 68 | - Initial commit 69 | -------------------------------------------------------------------------------- /src/ClassObjects.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2002-2025 Nicola Asuni - Tecnick.com LTD 11 | * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) 12 | * @link https://github.com/tecnickcom/tc-lib-pdf 13 | * 14 | * This file is part of tc-lib-pdf software library. 15 | */ 16 | 17 | namespace Com\Tecnick\Pdf; 18 | 19 | use Com\Tecnick\Barcode\Barcode as ObjBarcode; 20 | use Com\Tecnick\Color\Pdf as ObjColor; 21 | use Com\Tecnick\File\Cache as ObjCache; 22 | use Com\Tecnick\File\File as ObjFile; 23 | use Com\Tecnick\Pdf\Encrypt\Encrypt as ObjEncrypt; 24 | use Com\Tecnick\Pdf\Font\Stack as ObjFont; 25 | use Com\Tecnick\Pdf\Graph\Draw as ObjGraph; 26 | use Com\Tecnick\Pdf\Image\Import as ObjImage; 27 | use Com\Tecnick\Pdf\Page\Page as ObjPage; 28 | use Com\Tecnick\Unicode\Convert as ObjUniConvert; 29 | 30 | /** 31 | * Com\Tecnick\Pdf\ClassObjects 32 | * 33 | * External class objects PDF class 34 | * 35 | * @since 2002-08-03 36 | * @category Library 37 | * @package Pdf 38 | * @author Nicola Asuni 39 | * @copyright 2002-2025 Nicola Asuni - Tecnick.com LTD 40 | * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) 41 | * @link https://github.com/tecnickcom/tc-lib-pdf 42 | * 43 | * @SuppressWarnings("PHPMD.DepthOfInheritance") 44 | */ 45 | abstract class ClassObjects extends \Com\Tecnick\Pdf\Output 46 | { 47 | /** 48 | * Initialize dependencies class objects. 49 | * 50 | * @param ?ObjEncrypt $objEncrypt Encryption object. 51 | */ 52 | public function initClassObjects( 53 | ?ObjEncrypt $objEncrypt = null 54 | ): void { 55 | if ($objEncrypt instanceof ObjEncrypt) { 56 | $this->encrypt = $objEncrypt; 57 | } else { 58 | $this->encrypt = new ObjEncrypt(); 59 | } 60 | 61 | $this->color = new ObjColor(); 62 | $this->barcode = new ObjBarcode(); 63 | $this->file = new ObjFile(); 64 | $this->cache = new ObjCache(); 65 | $this->uniconv = new ObjUniConvert(); 66 | 67 | $pdfamode = (bool) ($this->pdfa > 0); 68 | 69 | $this->page = new ObjPage( 70 | $this->unit, 71 | $this->color, 72 | $this->encrypt, 73 | $pdfamode, 74 | $this->compress, 75 | $this->sigapp, 76 | ); 77 | 78 | $this->kunit = $this->page->getKUnit(); 79 | $this->svgminunitlen = $this->toUnit($this::SVGMINPNTLEN); 80 | 81 | $this->graph = new ObjGraph( 82 | $this->kunit, 83 | 0, // $this->graph->setPageWidth($pagew) 84 | 0, // $this->graph->setPageHeight($pageh) 85 | $this->color, 86 | $this->encrypt, 87 | $pdfamode, 88 | $this->compress, 89 | ); 90 | 91 | $this->font = new ObjFont( 92 | $this->kunit, 93 | $this->subsetfont, 94 | $this->isunicode, 95 | $pdfamode, 96 | ); 97 | 98 | $this->image = new ObjImage( 99 | $this->kunit, 100 | $this->encrypt, 101 | $pdfamode, 102 | $this->compress, 103 | ); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /examples/data/utf8test.txt: -------------------------------------------------------------------------------- 1 | Sentences that contain all letters commonly used in a language 2 | -------------------------------------------------------------- 3 | 4 | This file is UTF-8 encoded. 5 | 6 | Czech (cz) 7 | --------- 8 | 9 | Příšerně žluťoučký kůň úpěl ďábelské ódy. 10 | Hleď, toť přízračný kůň v mátožné póze šíleně úpí. 11 | Zvlášť zákeřný učeň s ďolíčky běží podél zóny úlů. 12 | Loď čeří kýlem tůň obzvlášť v Grónské úžině. 13 | Ó, náhlý déšť již zvířil prach a čilá laň teď běží s houfcem gazel k úkrytům. 14 | 15 | Danish (da) 16 | --------- 17 | 18 | Quizdeltagerne spiste jordbær med fløde, mens cirkusklovnen 19 | Wolther spillede på xylofon. 20 | (= Quiz contestants were eating strawbery with cream while Wolther 21 | the circus clown played on xylophone.) 22 | 23 | German (de) 24 | ----------- 25 | 26 | Falsches Üben von Xylophonmusik quält jeden größeren Zwerg 27 | (= Wrongful practicing of xylophone music tortures every larger dwarf) 28 | 29 | Zwölf Boxkämpfer jagten Eva quer über den Sylter Deich 30 | (= Twelve boxing fighters hunted Eva across the dike of Sylt) 31 | 32 | Heizölrückstoßabdämpfung 33 | (= fuel oil recoil absorber) 34 | (jqvwxy missing, but all non-ASCII letters in one word) 35 | 36 | English (en) 37 | ------------ 38 | 39 | The quick brown fox jumps over the lazy dog 40 | 41 | Spanish (es) 42 | ------------ 43 | 44 | El pingüino Wenceslao hizo kilómetros bajo exhaustiva lluvia y 45 | frío, añoraba a su querido cachorro. 46 | (Contains every letter and every accent, but not every combination 47 | of vowel + acute.) 48 | 49 | French (fr) 50 | ----------- 51 | 52 | Portez ce vieux whisky au juge blond qui fume sur son île intérieure, à 53 | côté de l'alcôve ovoïde, où les bûches se consument dans l'âtre, ce 54 | qui lui permet de penser à la cænogenèse de l'être dont il est question 55 | dans la cause ambiguë entendue à Moÿ, dans un capharnaüm qui, 56 | pense-t-il, diminue çà et là la qualité de son œuvre. 57 | 58 | l'île exiguë 59 | Où l'obèse jury mûr 60 | Fête l'haï volapük, 61 | Âne ex aéquo au whist, 62 | Ôtez ce vœu déçu. 63 | 64 | Le cœur déçu mais l'âme plutôt naïve, Louÿs rêva de crapaüter en 65 | canoë au delà des îles, près du mälström où brûlent les novæ. 66 | 67 | Irish Gaelic (ga) 68 | ----------------- 69 | 70 | D'fhuascail Íosa, Úrmhac na hÓighe Beannaithe, pór Éava agus Ádhaimh 71 | 72 | Hungarian (hu) 73 | -------------- 74 | 75 | Árvíztűrő tükörfúrógép 76 | (= flood-proof mirror-drilling machine, only all non-ASCII letters) 77 | 78 | Icelandic (is) 79 | -------------- 80 | 81 | Kæmi ný öxi hér ykist þjófum nú bæði víl og ádrepa 82 | 83 | Sævör grét áðan því úlpan var ónýt 84 | (some ASCII letters missing) 85 | 86 | Greek (el) 87 | ------------- 88 | 89 | Γαζέες καὶ μυρτιὲς δὲν θὰ βρῶ πιὰ στὸ χρυσαφὶ ξέφωτο 90 | (= No more shall I see acacias or myrtles in the golden clearing) 91 | 92 | Ξεσκεπάζω τὴν ψυχοφθόρα βδελυγμία 93 | (= I uncover the soul-destroying abhorrence) 94 | 95 | Hebrew (iw) 96 | ----------- 97 | 98 | ? דג סקרן שט בים מאוכזב ולפתע מצא לו חברה איך הקליטה 99 | 100 | Polish (pl) 101 | ----------- 102 | 103 | Pchnąć w tę łódź jeża lub osiem skrzyń fig 104 | (= To push a hedgehog or eight bins of figs in this boat) 105 | 106 | Zażółć gęślą jaźń 107 | 108 | Russian (ru) 109 | ------------ 110 | 111 | В чащах юга жил бы цитрус? Да, но фальшивый экземпляр! 112 | (= Would a citrus live in the bushes of south? Yes, but only a fake one!) 113 | 114 | Thai (th) 115 | --------- 116 | 117 | [--------------------------|------------------------] 118 | ๏ เป็นมนุษย์สุดประเสริฐเลิศคุณค่า กว่าบรรดาฝูงสัตว์เดรัจฉาน 119 | จงฝ่าฟันพัฒนาวิชาการ อย่าล้างผลาญฤๅเข่นฆ่าบีฑาใคร 120 | ไม่ถือโทษโกรธแช่งซัดฮึดฮัดด่า หัดอภัยเหมือนกีฬาอัชฌาสัย 121 | ปฏิบัติประพฤติกฎกำหนดใจ พูดจาให้จ๊ะๆ จ๋าๆ น่าฟังเอย ฯ 122 | 123 | [The copyright for the Thai example is owned by The Computer 124 | Association of Thailand under the Royal Patronage of His Majesty the 125 | King.] 126 | 127 | Please let me know if you find others! Special thanks to the people 128 | from all over the world who contributed these sentences. 129 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | . 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /examples/images/tcpdf_box.svg: -------------------------------------------------------------------------------- 1 | 2 | 9 | 13 | 17 | 21 | 25 | 29 | 33 | 37 | 41 | 45 | 49 | 53 | 57 | 61 | 65 | 69 | 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tc-lib-pdf 2 | 3 | ***PHP PDF Library*** 4 | 5 | [![Latest Stable Version](https://poser.pugx.org/tecnickcom/tc-lib-pdf/version)](https://packagist.org/packages/tecnickcom/tc-lib-pdf) 6 | ![Build](https://github.com/tecnickcom/tc-lib-pdf/actions/workflows/check.yml/badge.svg) 7 | [![Coverage](https://codecov.io/gh/tecnickcom/tc-lib-pdf/graph/badge.svg?token=rmAqNKVG1c)](https://codecov.io/gh/tecnickcom/tc-lib-pdf) 8 | [![License](https://poser.pugx.org/tecnickcom/tc-lib-pdf/license)](https://packagist.org/packages/tecnickcom/tc-lib-pdf) 9 | [![Downloads](https://poser.pugx.org/tecnickcom/tc-lib-pdf/downloads)](https://packagist.org/packages/tecnickcom/tc-lib-pdf) 10 | 11 | [![Donate via PayPal](https://img.shields.io/badge/donate-paypal-87ceeb.svg)](https://www.paypal.com/donate/?hosted_button_id=NZUEC5XS8MFBJ) 12 | *Please consider supporting this project by making a donation via [PayPal](https://www.paypal.com/donate/?hosted_button_id=NZUEC5XS8MFBJ)* 13 | 14 | * **category** Library 15 | * **package** \Com\Tecnick\Pdf 16 | * **author** Nicola Asuni 17 | * **copyright** 2002-2025 Nicola Asuni - Tecnick.com LTD 18 | * **license** http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) 19 | * **link** https://tcpdf.org 20 | * **source** https://github.com/tecnickcom/tc-lib-pdf 21 | * **SRC DOC** https://tcpdf.org/docs/srcdoc/tc-lib-pdf 22 | 23 | ## Description 24 | 25 | A PHP library for generating PDF documents dynamically. 26 | This is the latest iteration of the TCPDF library; the previous version will be deprecated once all its features have been migrated. 27 | 28 | **Note:** The first fully stable release was version 8.1.0. Subsequent releases adhere to semantic versioning: PATCH versions are reserved for backwards-compatible bug fixes, MINOR versions for backwards-compatible feature enhancements, and MAJOR versions for changes that break backwards compatibility. For further details, see [semver.org](https://semver.org/). 29 | 30 | ### Main Features 31 | 32 | ***Features with strikethrough are planned for future releases and are not yet available.*** 33 | 34 | * all standard page formats, custom page formats, custom margins and units of measure; 35 | * UTF-8 Unicode and Right-To-Left languages; 36 | * TrueTypeUnicode, OpenTypeUnicode v1, TrueType, OpenType v1, Type1 and CID-0 fonts; 37 | * font subsetting; 38 | * SVG 39 | * CSS 40 | * ~~HTML~~ (WIP) 41 | * JavaScript 42 | * images, graphic (geometric figures) and transformation methods; 43 | * supports JPEG, PNG and SVG images natively, all images supported by GD (GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM) and all images supported via ImagMagick (http://www.imagemagick.org/script/formats.php) 44 | * 1D and 2D barcodes via tc-lib-barcode. 45 | * JPEG and PNG ICC profiles, Grayscale, RGB, CMYK, Spot Pdfs and Transparencies; 46 | * page common content support (header/footer); 47 | * document encryption up to 256 bit and digital signature certifications; 48 | * PDF annotations, including links, text and file attachments; 49 | * text rendering modes (fill, stroke and clipping); 50 | * multiple columns mode; 51 | * no-write page regions; 52 | * bookmarks, named destinations and table of content; 53 | * text hyphenation; 54 | * text stretching and spacing (tracking); 55 | * automatic page break, line break and text alignments including justification; 56 | * automatic page numbering and page groups; 57 | * move and delete pages; 58 | * page compression (requires php-zlib extension); 59 | * XOBject Templates; 60 | * Layers and object visibility. 61 | * PDF/A-1b support. 62 | 63 | ### Third party fonts 64 | 65 | This library may include third party font files released with different licenses. 66 | 67 | All the PHP files on the fonts directory are subject to the general TCPDF license (GNU-LGPLv3), 68 | they do not contain any binary data but just a description of the general properties of a particular font. 69 | These files can be also generated on the fly using the font utilities and TCPDF methods. 70 | 71 | All the original binary TTF font files have been renamed for compatibility with TCPDF and compressed using the gzcompress PHP function that uses the ZLIB data format (.z files). 72 | 73 | The binary files (.z) that begins with the prefix "free" have been extracted from the GNU FreeFont collection (GNU-GPLv3). 74 | The binary files (.z) that begins with the prefix "pdfa" have been derived from the GNU FreeFont, so they are subject to the same license. 75 | For the details of Copyright, License and other information, please check the files inside the directory fonts/freefont-20120503 76 | Link : http://www.gnu.org/software/freefont/ 77 | 78 | The binary files (.z) that begins with the prefix "dejavu" have been extracted from the DejaVu fonts 2.33 (Bitstream) collection. 79 | For the details of Copyright, License and other information, please check the files inside the directory fonts/dejavu-fonts-ttf-2.33 80 | Link : http://dejavu-fonts.org 81 | 82 | The binary files (.z) that begins with the prefix "ae" have been extracted from the Arabeyes.org collection (GNU-GPLv2). 83 | Link : http://projects.arabeyes.org/ 84 | 85 | ### ICC profile 86 | 87 | TCPDF includes the sRGB.icc profile from the icc-profiles-free Debian package: 88 | https://packages.debian.org/source/stable/icc-profiles-free 89 | 90 | ## Getting started 91 | 92 | First, you need to install all development dependencies using [Composer](https://getcomposer.org/): 93 | 94 | ```bash 95 | curl -sS https://getcomposer.org/installer | php 96 | mv composer.phar /usr/local/bin/composer 97 | ``` 98 | 99 | You can install the library via composer: 100 | 101 | ```bash 102 | composer require tecnickcom/tc-lib-pdf 103 | ``` 104 | 105 | This project include a Makefile that allows you to test and build the project with simple commands. 106 | To see all available options: 107 | 108 | ```bash 109 | make help 110 | ``` 111 | 112 | To install all the development dependencies: 113 | 114 | ```bash 115 | make deps 116 | ``` 117 | 118 | ## Running all tests 119 | 120 | Before committing the code, please check if it passes all tests using 121 | 122 | ```bash 123 | make qa 124 | ``` 125 | 126 | All artifacts are generated in the target directory. 127 | 128 | ## Example 129 | 130 | Examples are located in the `example` directory. 131 | 132 | Start a development server (requires PHP 8.1+) using the command: 133 | 134 | ```bash 135 | make server 136 | ``` 137 | 138 | and point your browser to 139 | 140 | ## Installation 141 | 142 | Create a composer.json in your projects root-directory: 143 | 144 | ```json 145 | { 146 | "require": { 147 | "tecnickcom/tc-lib-pdf": "^8.4" 148 | }, 149 | } 150 | ``` 151 | 152 | ## Packaging 153 | 154 | This library is mainly intended to be used and included in other PHP projects using Composer. 155 | However, since some production environments dictates the installation of any application as RPM or DEB packages, 156 | this library includes make targets for building these packages (`make rpm` and `make deb`). 157 | The packages are generated under the `target` directory. 158 | 159 | When this library is installed using an RPM or DEB package, you can use it your code by including the autoloader: 160 | 161 | ```bash 162 | require_once ('/usr/share/php/Com/Tecnick/Barcode/autoload.php'); 163 | ``` 164 | 165 | ## Developer(s) Contact 166 | 167 | * Nicola Asuni 168 | -------------------------------------------------------------------------------- /examples/images/tcpdf_box.ai: -------------------------------------------------------------------------------- 1 | %!PS-Adobe-3.0 EPSF 2 | %%Creator: Adobe Illustrator 3 | %%BoundingBox: -7 0 487 327 4 | %%HiResBoundingBox: -6.66162 2.44007e-05 486.662 326.648 5 | %AI5_FileFormat 3 6 | %%EndComments 7 | %%BeginProlog 8 | %%EndProlog 9 | %%BeginSetup 10 | %%EndSetup 11 | 1 XR 12 | %AI5_BeginLayer 13 | 1 1 1 1 0 0 -1 49 80 161 Lb 14 | (New Layer) Ln 15 | 0.620000 0.580000 0.435000 0.996000 K 16 | [] 0 d 17 | 1.402287 w 18 | 0 j 19 | 0 J 20 | 0.263000 0.290000 0.898000 0.263000 k 21 | 72.7885 255.643 m 22 | 277.08 286.778 L 23 | 425.478 260.993 L 24 | 408.269 190.301 L 25 | 113.504 181.247 L 26 | 113.504 181.247 72.9813 241.769 72.7885 255.643 C 27 | b 28 | 0.620000 0.580000 0.435000 0.996000 K 29 | 1 j 30 | 0.094000 0.102000 0.369000 0.016000 k 31 | 423.247 259.914 m 32 | 240.217 207.097 L 33 | 240.635 0.701168 L 34 | 397.776 116.053 L 35 | 423.247 259.914 L 36 | b 37 | 0.620000 0.580000 0.435000 0.996000 K 38 | 0.133000 0.141000 0.541000 0.035000 k 39 | 72.1745 254.207 m 40 | 240.217 207.097 L 41 | 240.561 0.783816 L 42 | 101.054 87.946 L 43 | 72.1745 254.207 L 44 | b 45 | 0.047000 0.059000 0.184000 0.004000 k 46 | 423.247 259.914 m 47 | 308.187 51.1553 L 48 | 396.862 116.972 L 49 | 423.247 259.914 L 50 | f 51 | 0.620000 0.580000 0.435000 0.996000 K 52 | 0 j 53 | 0.047000 0.059000 0.184000 0.004000 k 54 | 479.312 250.415 m 55 | 423.613 260.243 L 56 | 240.385 206.966 L 57 | 314.061 186.394 L 58 | 479.312 250.415 L 59 | b 60 | 0.620000 0.580000 0.435000 0.996000 K 61 | 0.047000 0.059000 0.184000 0.004000 k 62 | 69.9121 254.273 m 63 | 237.965 207.131 L 64 | 163.618 164.537 L 65 | 0.687544 234.686 L 66 | 69.9121 254.273 L 67 | b 68 | 0.620000 0.580000 0.435000 0.996000 K 69 | 0.047000 0.059000 0.184000 0.004000 k 70 | 242.971 319.299 m 71 | 275.613 286.233 L 72 | 72.5703 254.295 L 73 | 16.555 296.161 L 74 | 242.971 319.299 L 75 | b 76 | 0.620000 0.580000 0.435000 0.996000 K 77 | 0.133000 0.141000 0.541000 0.035000 k 78 | 423.496 260.684 m 79 | 275.426 286.441 L 80 | 307.326 316.69 L 81 | 462.053 292.606 L 82 | 423.496 260.684 L 83 | b 84 | 0.196000 0.227000 0.871000 0.106000 k 85 | 75.26 254.037 m 86 | 274.806 285.371 L 87 | 227.928 211.257 L 88 | 163.396 228.836 130.937 238.701 75.26 254.037 C 89 | f 90 | 0.620000 0.580000 0.435000 0.996000 K 91 | 1 j 92 | 0.169000 0.314000 0.424000 0.094000 k 93 | 275.528 286.329 m 94 | 274.75 216.78 L 95 | 275.528 286.329 L 96 | b 97 | 0.031000 0.949000 0.745000 0.729000 k 98 | 285.929 160.982 m 99 | 285.929 160.982 285.078 139.734 285.078 139.734 C 100 | 285.078 139.734 275.378 135.096 275.378 135.096 C 101 | 275.378 135.096 273.058 57.6061 273.058 57.6061 C 102 | 273.058 57.6061 257.133 47.3536 257.133 47.3536 C 103 | 257.133 47.3536 258.059 126.816 258.059 126.816 C 104 | 258.059 126.816 247.186 121.618 247.186 121.618 C 105 | 247.186 121.618 247.186 144.26 247.186 144.26 C 106 | 247.186 144.26 285.929 160.982 285.929 160.982 C 107 | F 108 | 0.031000 0.949000 0.745000 0.729000 k 109 | 320.884 135.342 m 110 | 320.884 135.342 307.279 128.129 307.279 128.129 C 111 | 307.279 128.129 308.36 144.944 308.36 144.944 C 112 | 308.681 149.948 308.724 153.011 308.483 154.097 C 113 | 308.268 155.24 307.632 155.567 306.572 155.073 C 114 | 305.368 154.512 304.553 153.381 304.135 151.687 C 115 | 303.718 149.995 303.36 146.608 303.063 141.565 C 116 | 303.063 141.565 300.477 97.6864 300.477 97.6864 C 117 | 300.22 93.3298 300.208 90.6032 300.439 89.4791 C 118 | 300.668 88.3599 301.318 88.1316 302.385 88.7881 C 119 | 303.403 89.4149 304.103 90.4684 304.487 91.9528 C 120 | 304.894 93.4524 305.253 96.6211 305.567 101.497 C 121 | 305.567 101.497 306.297 112.86 306.297 112.86 C 122 | 306.297 112.86 319.718 120.438 319.718 120.438 C 123 | 319.718 120.438 319.446 116.964 319.446 116.964 C 124 | 318.734 107.862 317.873 101.293 316.856 97.1389 C 125 | 315.864 93.0164 314.026 88.8059 311.329 84.474 C 126 | 308.636 80.1307 305.421 76.743 301.646 74.2975 C 127 | 297.651 71.7096 294.365 70.7251 291.812 71.3887 C 128 | 289.213 72.064 287.545 74.2699 286.838 78.0362 C 129 | 286.121 81.8925 285.954 88.3297 286.349 97.4441 C 130 | 286.349 97.4441 287.575 125.751 287.575 125.751 C 131 | 287.89 133.007 288.277 138.586 288.737 142.421 C 132 | 289.202 146.323 290.251 150.369 291.883 154.544 C 133 | 293.54 158.732 295.716 162.4 298.394 165.529 C 134 | 301.078 168.689 304.052 170.959 307.296 172.342 C 135 | 311.621 174.188 315.02 174.154 317.521 172.296 C 136 | 319.974 170.473 321.427 167.541 321.911 163.514 C 137 | 322.39 159.578 322.275 153.119 321.579 144.225 C 138 | 321.579 144.225 320.884 135.342 320.884 135.342 C 139 | F 140 | 0.031000 0.949000 0.745000 0.729000 k 141 | *u 142 | 329.084 179.607 m 143 | 329.084 179.607 342.074 185.214 342.074 185.214 C 144 | 345.446 186.669 347.94 187.235 349.586 186.932 C 145 | 351.233 186.642 352.374 185.695 353.019 184.1 C 146 | 353.677 182.527 353.999 180.418 353.991 177.777 C 147 | 354.005 175.2 353.703 171.072 353.088 165.429 C 148 | 353.088 165.429 352.244 157.683 352.244 157.683 C 149 | 351.634 152.084 350.908 147.89 350.063 145.056 C 150 | 349.22 142.228 347.957 139.718 346.265 137.514 C 151 | 344.583 135.308 342.474 133.487 339.919 132.046 C 152 | 339.919 132.046 336.736 130.25 336.736 130.25 C 153 | 336.736 130.25 333.512 96.527 333.512 96.527 C 154 | 333.512 96.527 321.545 88.8222 321.545 88.8222 C 155 | 321.545 88.8222 329.084 179.607 329.084 179.607 C 156 | F 157 | 340.42 168.795 m 158 | 340.42 168.795 338.184 145.407 338.184 145.407 C 159 | 338.526 145.552 338.82 145.69 339.068 145.822 C 160 | 340.172 146.404 340.98 147.319 341.498 148.568 C 161 | 342.038 149.863 342.473 152.174 342.805 155.518 C 162 | 342.805 155.518 343.55 163.014 343.55 163.014 C 163 | 343.863 166.167 343.82 168.111 343.416 168.832 C 164 | 343.011 169.557 342.015 169.547 340.42 168.795 C 165 | F 166 | *U 167 | 0.031000 0.949000 0.745000 0.729000 k 168 | *u 169 | 359.204 192.607 m 170 | 359.204 192.607 367.556 196.212 367.556 196.212 C 171 | 372.753 198.454 376.114 199.414 377.734 199.14 C 172 | 379.351 198.877 380.446 197.777 381.03 195.854 C 173 | 381.608 193.948 381.815 191.642 381.655 188.934 C 174 | 381.502 186.28 380.88 180.934 379.804 172.981 C 175 | 379.804 172.981 375.968 144.653 375.968 144.653 C 176 | 375.021 137.654 374.236 132.926 373.604 130.399 C 177 | 372.993 127.921 372.216 125.786 371.268 123.986 C 178 | 370.323 122.215 369.243 120.698 368.023 119.43 C 179 | 366.801 118.187 365.012 116.807 362.638 115.279 C 180 | 362.638 115.279 349.593 106.88 349.593 106.88 C 181 | 349.593 106.88 359.204 192.607 359.204 192.607 C 182 | F 183 | 368.395 181.985 m 184 | 368.395 181.985 361.644 126.918 361.644 126.918 C 185 | 363.113 127.815 364.084 128.947 364.566 130.32 C 186 | 365.053 131.727 365.629 135.077 366.301 140.416 C 187 | 366.301 140.416 370.368 172.752 370.368 172.752 C 188 | 370.86 176.666 371.12 179.163 371.145 180.223 C 189 | 371.168 181.285 371.006 181.972 370.654 182.282 C 190 | 370.306 182.629 369.555 182.531 368.395 181.985 C 191 | F 192 | *U 193 | 0.031000 0.949000 0.745000 0.729000 k 194 | 387.58 204.854 m 195 | 387.58 204.854 403.348 211.659 403.348 211.659 C 196 | 403.348 211.659 400.803 195.062 400.803 195.062 C 197 | 400.803 195.062 394.579 192.087 394.579 192.087 C 198 | 394.579 192.087 392.296 176.619 392.296 176.619 C 199 | 392.296 176.619 397.801 179.482 397.801 179.482 C 200 | 397.801 179.482 395.533 164.634 395.533 164.634 C 201 | 395.533 164.634 390.075 161.574 390.075 161.574 C 202 | 390.075 161.574 385.406 129.937 385.406 129.937 C 203 | 385.406 129.937 376.374 124.122 376.374 124.122 C 204 | 376.374 124.122 387.58 204.854 387.58 204.854 C 205 | F 206 | LB 207 | %AI5_EndLayer-- 208 | %AI5_BeginLayer 209 | 1 1 1 1 0 0 -1 49 80 161 Lb 210 | (MasterLayer 1) Ln 211 | LB 212 | %AI5_EndLayer-- 213 | %%Trailer 214 | %%EOF 215 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile 2 | # 3 | # @since 2015-02-21 4 | # @category Library 5 | # @package Pdf 6 | # @author Nicola Asuni 7 | # @copyright 2015-2025 Nicola Asuni - Tecnick.com LTD 8 | # @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE) 9 | # @link https://github.com/tecnickcom/tc-lib-pdf 10 | # 11 | # This file is part of tc-lib-pdf software library. 12 | # ---------------------------------------------------------------------------------------------------------------------- 13 | 14 | SHELL=/bin/bash 15 | .SHELLFLAGS=-o pipefail -c 16 | 17 | # Project owner 18 | OWNER=tecnickcom 19 | 20 | # Project vendor 21 | VENDOR=${OWNER} 22 | 23 | # Project name 24 | PROJECT=tc-lib-pdf 25 | 26 | # Project version 27 | VERSION=$(shell cat VERSION) 28 | 29 | # Project release number (packaging build number) 30 | RELEASE=$(shell cat RELEASE) 31 | 32 | # Name of RPM or DEB package 33 | PKGNAME=php-${OWNER}-${PROJECT} 34 | 35 | # Data dir 36 | DATADIR=usr/share 37 | 38 | # PHP home folder 39 | PHPHOME=${DATADIR}/php/Com/Tecnick 40 | 41 | # Default installation path for code 42 | LIBPATH=${PHPHOME}/Pdf/ 43 | 44 | # Path for configuration files (etc/$(PKGNAME)/) 45 | CONFIGPATH=etc/$(PKGNAME)/ 46 | 47 | # Default installation path for documentation 48 | DOCPATH=${DATADIR}/doc/$(PKGNAME)/ 49 | 50 | # Installation path for the code 51 | PATHINSTBIN=$(DESTDIR)/$(LIBPATH) 52 | 53 | # Installation path for the configuration files 54 | PATHINSTCFG=$(DESTDIR)/$(CONFIGPATH) 55 | 56 | # Installation path for documentation 57 | PATHINSTDOC=$(DESTDIR)/$(DOCPATH) 58 | 59 | # Current directory 60 | CURRENTDIR=$(dir $(realpath $(firstword $(MAKEFILE_LIST)))) 61 | 62 | # Target directory 63 | TARGETDIR=$(CURRENTDIR)target 64 | 65 | # RPM Packaging path (where RPMs will be stored) 66 | PATHRPMPKG=$(TARGETDIR)/RPM 67 | 68 | # DEB Packaging path (where DEBs will be stored) 69 | PATHDEBPKG=$(TARGETDIR)/DEB 70 | 71 | # BZ2 Packaging path (where BZ2s will be stored) 72 | PATHBZ2PKG=$(TARGETDIR)/BZ2 73 | 74 | # Default port number for the example server 75 | PORT?=8971 76 | 77 | # PHP binary 78 | PHP=$(shell which php) 79 | 80 | # Composer executable (disable APC to as a work-around of a bug) 81 | COMPOSER=$(PHP) -d "apc.enable_cli=0" $(shell which composer) 82 | 83 | # phpDocumentor executable file 84 | PHPDOC=$(shell which phpDocumentor) 85 | 86 | # phpstan version 87 | PHPSTANVER=2.1.33 88 | 89 | # --- MAKE TARGETS --- 90 | 91 | # Display general help about this command 92 | .PHONY: help 93 | help: 94 | @echo "" 95 | @echo "$(PROJECT) Makefile." 96 | @echo "The following commands are available:" 97 | @echo "" 98 | @echo " make buildall : Build and test everything from scratch" 99 | @echo " make bz2 : Package the library in a compressed bz2 archive" 100 | @echo " make clean : Delete the vendor and target directories" 101 | @echo " make codefix : Fix code style violations" 102 | @echo " make deb : Build a DEB package for Debian-like Linux distributions" 103 | @echo " make deps : Download all dependencies" 104 | @echo " make doc : Generate source code documentation" 105 | @echo " make fonts : Build default tc-font-mirror fonts via tc-lib-pdf-font" 106 | @echo " make lint : Test source code for coding standard violations" 107 | @echo " make qa : Run all tests and reports" 108 | @echo " make report : Generate various reports" 109 | @echo " make rpm : Build an RPM package for RedHat-like Linux distributions" 110 | @echo " make server : Start the development server" 111 | @echo " make test : Run unit tests" 112 | @echo " make versionup: Increase the version patch number" 113 | @echo "" 114 | @echo "To test and build everything from scratch:" 115 | @echo "make buildall" 116 | @echo "" 117 | 118 | # alias for help target 119 | .PHONY: all 120 | all: help 121 | 122 | # Full build and test sequence 123 | .PHONY: x 124 | x: buildall 125 | 126 | # Full build and test sequence 127 | .PHONY: buildall 128 | buildall: deps 129 | cd vendor/tecnickcom/tc-lib-pdf-font/ && make deps fonts 130 | $(MAKE) codefix qa bz2 rpm deb 131 | 132 | # Package the library in a compressed bz2 archive 133 | .PHONY: bz2 134 | bz2: 135 | rm -rf $(PATHBZ2PKG) 136 | make install DESTDIR=$(PATHBZ2PKG) 137 | tar -jcvf $(PATHBZ2PKG)/$(PKGNAME)-$(VERSION)-$(RELEASE).tbz2 -C $(PATHBZ2PKG) $(DATADIR) 138 | 139 | # Delete the vendor and target directories 140 | .PHONY: clean 141 | clean: 142 | rm -rf ./vendor $(TARGETDIR) 143 | 144 | # Fix code style violations 145 | .PHONY: codefix 146 | codefix: 147 | ./vendor/bin/phpcbf --ignore="\./vendor/" --standard=psr12 src test 148 | 149 | # Build a DEB package for Debian-like Linux distributions 150 | .PHONY: deb 151 | deb: 152 | rm -rf $(PATHDEBPKG) 153 | make install DESTDIR=$(PATHDEBPKG)/$(PKGNAME)-$(VERSION) 154 | rm -f $(PATHDEBPKG)/$(PKGNAME)-$(VERSION)/$(DOCPATH)LICENSE 155 | tar -zcvf $(PATHDEBPKG)/$(PKGNAME)_$(VERSION).orig.tar.gz -C $(PATHDEBPKG)/ $(PKGNAME)-$(VERSION) 156 | cp -rf ./resources/debian $(PATHDEBPKG)/$(PKGNAME)-$(VERSION)/debian 157 | find $(PATHDEBPKG)/$(PKGNAME)-$(VERSION)/debian/ -type f -exec sed -i "s/~#DATE#~/`date -R`/" {} \; 158 | find $(PATHDEBPKG)/$(PKGNAME)-$(VERSION)/debian/ -type f -exec sed -i "s/~#VENDOR#~/$(VENDOR)/" {} \; 159 | find $(PATHDEBPKG)/$(PKGNAME)-$(VERSION)/debian/ -type f -exec sed -i "s/~#PROJECT#~/$(PROJECT)/" {} \; 160 | find $(PATHDEBPKG)/$(PKGNAME)-$(VERSION)/debian/ -type f -exec sed -i "s/~#PKGNAME#~/$(PKGNAME)/" {} \; 161 | find $(PATHDEBPKG)/$(PKGNAME)-$(VERSION)/debian/ -type f -exec sed -i "s/~#VERSION#~/$(VERSION)/" {} \; 162 | find $(PATHDEBPKG)/$(PKGNAME)-$(VERSION)/debian/ -type f -exec sed -i "s/~#RELEASE#~/$(RELEASE)/" {} \; 163 | echo $(LIBPATH) > $(PATHDEBPKG)/$(PKGNAME)-$(VERSION)/debian/$(PKGNAME).dirs 164 | echo "$(LIBPATH)* $(LIBPATH)" > $(PATHDEBPKG)/$(PKGNAME)-$(VERSION)/debian/install 165 | echo $(DOCPATH) >> $(PATHDEBPKG)/$(PKGNAME)-$(VERSION)/debian/$(PKGNAME).dirs 166 | echo "$(DOCPATH)* $(DOCPATH)" >> $(PATHDEBPKG)/$(PKGNAME)-$(VERSION)/debian/install 167 | ifneq ($(strip $(CONFIGPATH)),) 168 | echo $(CONFIGPATH) >> $(PATHDEBPKG)/$(PKGNAME)-$(VERSION)/debian/$(PKGNAME).dirs 169 | echo "$(CONFIGPATH)* $(CONFIGPATH)" >> $(PATHDEBPKG)/$(PKGNAME)-$(VERSION)/debian/install 170 | endif 171 | echo "new-package-should-close-itp-bug" > $(PATHDEBPKG)/$(PKGNAME)-$(VERSION)/debian/$(PKGNAME).lintian-overrides 172 | cd $(PATHDEBPKG)/$(PKGNAME)-$(VERSION) && debuild -us -uc 173 | 174 | # Clean all artifacts and download all dependencies 175 | .PHONY: deps 176 | deps: ensuretarget 177 | rm -rf ./vendor/* 178 | ($(COMPOSER) install -vvv --no-interaction) 179 | curl --silent --show-error --fail --location --output ./vendor/phpstan.phar https://github.com/phpstan/phpstan/releases/download/${PHPSTANVER}/phpstan.phar \ 180 | && chmod +x ./vendor/phpstan.phar 181 | 182 | # Generate source code documentation 183 | .PHONY: doc 184 | doc: ensuretarget 185 | rm -rf $(TARGETDIR)/doc 186 | $(PHPDOC) -d ./src -t $(TARGETDIR)/doc/ 187 | 188 | # Create missing target directories for test and build artifacts 189 | .PHONY: ensuretarget 190 | ensuretarget: 191 | mkdir -p $(TARGETDIR)/test 192 | mkdir -p $(TARGETDIR)/report 193 | mkdir -p $(TARGETDIR)/doc 194 | 195 | # Build default tc-font-mirror fonts via tc-lib-pdf-font 196 | .PHONY: fonts 197 | fonts: 198 | cd vendor/tecnickcom/tc-lib-pdf-font/ && make deps fonts 199 | 200 | # Install this application 201 | .PHONY: install 202 | install: uninstall 203 | mkdir -p $(PATHINSTBIN) 204 | cp -rf ./src/* $(PATHINSTBIN) 205 | cp -f ./resources/autoload.php $(PATHINSTBIN) 206 | find $(PATHINSTBIN) -type d -exec chmod 755 {} \; 207 | find $(PATHINSTBIN) -type f -exec chmod 644 {} \; 208 | mkdir -p $(PATHINSTDOC) 209 | cp -f ./LICENSE $(PATHINSTDOC) 210 | cp -f ./README.md $(PATHINSTDOC) 211 | cp -f ./VERSION $(PATHINSTDOC) 212 | cp -f ./RELEASE $(PATHINSTDOC) 213 | chmod -R 644 $(PATHINSTDOC)* 214 | ifneq ($(strip $(CONFIGPATH)),) 215 | mkdir -p $(PATHINSTCFG) 216 | touch -c $(PATHINSTCFG)* 217 | cp -ru ./resources/${CONFIGPATH}* $(PATHINSTCFG) 218 | find $(PATHINSTCFG) -type d -exec chmod 755 {} \; 219 | find $(PATHINSTCFG) -type f -exec chmod 644 {} \; 220 | endif 221 | 222 | # Test source code for coding standard violations 223 | .PHONY: lint 224 | lint: 225 | ./vendor/bin/phpcbf --config-set ignore_non_auto_fixable_on_exit 1 226 | ./vendor/bin/phpcs --standard=phpcs.xml 227 | ./vendor/bin/phpmd src text unusedcode,naming,design --exclude vendor 228 | ./vendor/bin/phpmd test text unusedcode,naming,design --exclude */vendor/* 229 | php -r 'exit((int)version_compare(PHP_MAJOR_VERSION, "7", ">"));' || ./vendor/phpstan.phar analyse 230 | 231 | # Run all tests and reports 232 | .PHONY: qa 233 | qa: version ensuretarget lint test report 234 | 235 | # Generate various reports 236 | .PHONY: report 237 | report: ensuretarget 238 | ./vendor/bin/pdepend --jdepend-xml=$(TARGETDIR)/report/dependencies.xml --summary-xml=$(TARGETDIR)/report/metrics.xml --jdepend-chart=$(TARGETDIR)/report/dependecies.svg --overview-pyramid=$(TARGETDIR)/report/overview-pyramid.svg --ignore=vendor ./src 239 | #./vendor/bartlett/php-compatinfo/bin/phpcompatinfo --no-ansi analyser:run src/ > $(TARGETDIR)/report/phpcompatinfo.txt 240 | 241 | # Build the RPM package for RedHat-like Linux distributions 242 | .PHONY: rpm 243 | rpm: 244 | rm -rf $(PATHRPMPKG) 245 | rpmbuild \ 246 | --define "_topdir $(PATHRPMPKG)" \ 247 | --define "_vendor $(VENDOR)" \ 248 | --define "_owner $(OWNER)" \ 249 | --define "_project $(PROJECT)" \ 250 | --define "_package $(PKGNAME)" \ 251 | --define "_version $(VERSION)" \ 252 | --define "_release $(RELEASE)" \ 253 | --define "_current_directory $(CURRENTDIR)" \ 254 | --define "_libpath /$(LIBPATH)" \ 255 | --define "_docpath /$(DOCPATH)" \ 256 | --define "_configpath /$(CONFIGPATH)" \ 257 | -bb resources/rpm/rpm.spec 258 | 259 | # Start the development server 260 | .PHONY: server 261 | server: 262 | $(PHP) -t examples -S localhost:$(PORT) 263 | 264 | # Tag this GIT version 265 | .PHONY: tag 266 | tag: 267 | git checkout main && \ 268 | git tag -a ${VERSION} -m "Release ${VERSION}" && \ 269 | git push origin --tags && \ 270 | git pull 271 | 272 | # Run unit tests 273 | .PHONY: test 274 | test: 275 | cp phpunit.xml.dist phpunit.xml 276 | #./vendor/bin/phpunit --migrate-configuration || true 277 | XDEBUG_MODE=coverage ./vendor/bin/phpunit --stderr test 278 | 279 | # Remove all installed files 280 | .PHONY: uninstall 281 | uninstall: 282 | rm -rf $(PATHINSTBIN) 283 | rm -rf $(PATHINSTDOC) 284 | 285 | # set the version 286 | version: 287 | sed -i -e "s/protected string \$$version = '.*';/protected string \$$version = '${VERSION}';/g" src/Base.php 288 | 289 | # Increase the version patch number 290 | .PHONY: versionup 291 | versionup: 292 | echo ${VERSION} | gawk -F. '{printf("%d.%d.%d\n",$$1,$$2,(($$3+1)));}' > VERSION 293 | $(MAKE) version 294 | 295 | -------------------------------------------------------------------------------- /examples/images/testsvg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | TCPDF SVG EXAMPLE 14 | 16 | 25 | 34 | 44 | 54 | 56 | 60 | 64 | 65 | 75 | 77 | 81 | 85 | 86 | 96 | 98 | 102 | 106 | 107 | 117 | 119 | 123 | 127 | 128 | 138 | 147 | 157 | 167 | 177 | 187 | 197 | 207 | 216 | 217 | 225 | 229 | 233 | 234 | 236 | 240 | 244 | 245 | 255 | 263 | 267 | 271 | 275 | 282 | www.tcpdf.org 287 | 292 | 296 | 300 | 304 | 311 | 315 | 322 | SVG 328 | 329 | -------------------------------------------------------------------------------- /examples/invoice.php: -------------------------------------------------------------------------------- 1 | 9 | * @copyright 2002-2025 Nicola Asuni - Tecnick.com LTD 10 | * @license http://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 | 22 | \define('OUTPUT_FILE', \realpath(__DIR__ . '/../target') . '/example_invoice.pdf'); 23 | 24 | // define fonts directory 25 | \define('K_PATH_FONTS', \realpath(__DIR__ . '/../vendor/tecnickcom/tc-lib-pdf-font/target/fonts')); 26 | 27 | // autoloader when using RPM or DEB package installation 28 | //require ('/usr/share/php/Com/Tecnick/Pdf/autoload.php'); 29 | 30 | // main TCPDF object 31 | $pdf = new \Com\Tecnick\Pdf\Tcpdf( 32 | 'mm', // string $unit = 'mm', 33 | true, // bool $isunicode = true, 34 | false, // bool $subsetfont = false, 35 | true, // bool $compress = true, 36 | 'pdfa3', // string $mode = '', 37 | null, // ?ObjEncrypt $objEncrypt = null, 38 | ); 39 | 40 | // ---------- 41 | 42 | 43 | $pdf->setCreator('tc-lib-pdf'); 44 | $pdf->setAuthor('John Doe'); 45 | $pdf->setSubject('tc-lib-pdf invoice example'); 46 | $pdf->setTitle('Example: Invoice'); 47 | $pdf->setKeywords('TCPDF tc-lib-pdf invoice example'); 48 | $pdf->setPDFFilename('test_invoice.pdf'); 49 | 50 | $pdf->setViewerPreferences(['DisplayDocTitle' => true]); 51 | 52 | $pdf->enableDefaultPageContent(); 53 | 54 | // ---------- 55 | // Insert fonts 56 | 57 | $bfont1 = $pdf->font->insert($pdf->pon, 'helvetica', '', 12); 58 | 59 | // ---------- 60 | 61 | 62 | // Factur-X 1.07 / ZUGFeRD 2.3 63 | 64 | $pageF01 = $pdf->addPage(); 65 | $pdf->setBookmark('Factur', '', 0, -1, 0, 0, 'B', ''); 66 | 67 | $pdf->page->addContent($bfont1['out']); 68 | 69 | $txtF1 = 'Example of custom XMP metadata for Factur-X 1.07 / ZUGFeRD 2.3'; 70 | $txtboxF1 = $pdf->getTextCell($txtF1, 15, 15, 150, valign: 'T', halign: 'L'); 71 | $pdf->page->addContent($txtboxF1); 72 | 73 | $invoiceXML = << 75 | 76 | 77 | 78 | Baurechnung 79 | 80 | 81 | urn:cen.eu:en16931:2017 82 | 83 | 84 | 85 | 181301674 86 | 204 87 | 88 | 20241115 89 | 90 | 91 | Rapport-Nr.: 42389 vom 01.11.2024 92 | Im 2. OG BT1 Besprechungsraum eine Beamerhalterung an die Decke montiert. Dafür eine Deckenplatte ausgesägt. Beamerhalterung zur Montage auseinander gebaut. Ein Stromkabel für den Beamer, ein HDMI Kabel und ein VGA Kabel durch die Halterung gezogen. Beamerhalterung wieder zusammengebaut und Beamer montiert. Beamer verkabelt und ausgerichtet. Decke geschlossen. 93 | 94 | 95 | 96 | 97 | 98 | 01 99 | 100 | 01 Beamermontage 101 | Für die doppelte Verlegung, falls erforderlich. 102 | 103 | 104 | 105 | TGA Obermonteur/Monteur 106 | 107 | 108 | 109 | 43.2 110 | 111 | 112 | 43.2 113 | 114 | 115 | 116 | 3 117 | 118 | 119 | 120 | VAT 121 | S 122 | 19 123 | 124 | 125 | 129.6 126 | 127 | 128 | 129 | 130 | 131 | 02 132 | 133 | 02 Außerhalb Angebot 134 | 135 | 136 | 137 | Beamer-Deckenhalterung 138 | 139 | 140 | 141 | 122.5 142 | 143 | 144 | 122.5 145 | 146 | 147 | 148 | 1 149 | 150 | 151 | 152 | VAT 153 | S 154 | 19 155 | 156 | 157 | 122.5 158 | 159 | 160 | 161 | 162 | Liselotte Müller-Lüdenscheidt 163 | 164 | 549910 165 | ELEKTRON Industrieservice GmbH 166 | Geschäftsführer Egon Schrempp Amtsgericht Stuttgart HRB 1234 167 | 168 | 74465 169 | Erfurter Strasse 13 170 | Demoort 171 | DE 172 | 173 | 174 | DE136695976 175 | 176 | 177 | 178 | 16259 179 | ConsultingService GmbH 180 | 181 | 76138 182 | Musterstr. 18 183 | Karlsruhe 184 | DE 185 | 186 | 187 | 188 | per Mail vom 01.09.2024 189 | 190 | 191 | 13130162 192 | #ef=Aufmass.png 193 | 916 194 | 195 | 196 | 42389 197 | #ef=ElektronRapport_neu-red.pdf 198 | 916 199 | 200 | 201 | 13130162 202 | Projekt 203 | 204 | 205 | 206 | 207 | 208 | 20241101 209 | 210 | 211 | 212 | 213 | Rechnung 181301674 214 | EUR 215 | 216 | 58 217 | 218 | DE91100000000123456789 219 | 220 | 221 | 222 | 47.9 223 | VAT 224 | 252.1 225 | S 226 | 19 227 | 228 | 229 | Zahlbar sofort rein netto 230 | 231 | 232 | 252.1 233 | 0 234 | 0 235 | 252.1 236 | 47.9 237 | 300 238 | 0 239 | 300 240 | 241 | 242 | 420 243 | 244 | 245 | 246 | 247 | XML; 248 | 249 | $pdf->addContentAsEmbeddedFile('factur-x.xml', $invoiceXML); 250 | 251 | $pdf->setCustomXMP('x:xmpmeta.rdf:RDF.rdf:Description.pdfaExtension:schemas.rdf:Bag', 252 | ' 253 | Factur-X PDFA Extension Schema 254 | urn:factur-x:pdfa:CrossIndustryDocument:invoice:1p0# 255 | fx 256 | 257 | 258 | 259 | DocumentFileName 260 | Text 261 | external 262 | The name of the embedded XML document 263 | 264 | 265 | DocumentType 266 | Text 267 | external 268 | The type of the hybrid document in capital letters, e.g. INVOICE or ORDER 269 | 270 | 271 | Version 272 | Text 273 | external 274 | The actual version of the standard applying to the embedded XML document 275 | 276 | 277 | ConformanceLevel 278 | Text 279 | external 280 | The conformance level of the embedded XML document 281 | 282 | 283 | 284 | ' 285 | ); 286 | 287 | $pdf->setCustomXMP('x:xmpmeta.rdf:RDF', 288 | ' 289 | INVOICE 290 | factur-x.xml 291 | 1.0 292 | EN 16931 293 | '); 294 | 295 | // ============================================================= 296 | 297 | // ---------- 298 | // get PDF document as raw string 299 | $rawpdf = $pdf->getOutPDFString(); 300 | 301 | // ---------- 302 | 303 | // Various output modes: 304 | 305 | //$pdf->savePDF(\dirname(__DIR__).'/target', $rawpdf); 306 | $pdf->renderPDF($rawpdf); 307 | //$pdf->downloadPDF($rawpdf); 308 | //echo $pdf->getMIMEAttachmentPDF($rawpdf); 309 | -------------------------------------------------------------------------------- /src/Base.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2002-2025 Nicola Asuni - Tecnick.com LTD 11 | * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) 12 | * @link https://github.com/tecnickcom/tc-lib-pdf 13 | * 14 | * This file is part of tc-lib-pdf software library. 15 | */ 16 | 17 | namespace Com\Tecnick\Pdf; 18 | 19 | use Com\Tecnick\Pdf\Exception as PdfException; 20 | use Com\Tecnick\Barcode\Barcode as ObjBarcode; 21 | use Com\Tecnick\Color\Pdf as ObjColor; 22 | use Com\Tecnick\File\Cache as ObjCache; 23 | use Com\Tecnick\File\File as ObjFile; 24 | use Com\Tecnick\Pdf\Encrypt\Encrypt as ObjEncrypt; 25 | use Com\Tecnick\Pdf\Font\Stack as ObjFont; 26 | use Com\Tecnick\Pdf\Graph\Draw as ObjGraph; 27 | use Com\Tecnick\Pdf\Image\Import as ObjImage; 28 | use Com\Tecnick\Pdf\Page\Page as ObjPage; 29 | use Com\Tecnick\Unicode\Convert as ObjUniConvert; 30 | 31 | /** 32 | * Com\Tecnick\Pdf\Base 33 | * 34 | * Output PDF data 35 | * 36 | * @since 2002-08-03 37 | * @category Library 38 | * @package Pdf 39 | * @author Nicola Asuni 40 | * @copyright 2002-2025 Nicola Asuni - Tecnick.com LTD 41 | * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) 42 | * @link https://github.com/tecnickcom/tc-lib-pdf 43 | * 44 | * @phpstan-import-type PageInputData from \Com\Tecnick\Pdf\Page\Box 45 | * @phpstan-import-type PageData from \Com\Tecnick\Pdf\Page\Box 46 | * @phpstan-import-type TFontMetric from \Com\Tecnick\Pdf\Font\Stack 47 | * 48 | * @phpstan-type TViewerPref array{ 49 | * 'HideToolbar'?: bool, 50 | * 'HideMenubar'?: bool, 51 | * 'HideWindowUI'?: bool, 52 | * 'FitWindow'?: bool, 53 | * 'CenterWindow'?: bool, 54 | * 'DisplayDocTitle'?: bool, 55 | * 'NonFullScreenPageMode'?: string, 56 | * 'Direction'?: string, 57 | * 'ViewArea'?: string, 58 | * 'ViewClip'?: string, 59 | * 'PrintArea'?: string, 60 | * 'PrintClip'?: string, 61 | * 'PrintScaling'?: string, 62 | * 'Duplex'?: string, 63 | * 'PickTrayByPDFSize'?: bool, 64 | * 'PrintPageRange'?: array, 65 | * 'NumCopies'?: int, 66 | * } 67 | * 68 | * @phpstan-type TBBox array{ 69 | * 'x': float, 70 | * 'y': float, 71 | * 'w': float, 72 | * 'h': float, 73 | * } 74 | * 75 | * @phpstan-type TCellBound array{ 76 | * 'T': float, 77 | * 'R': float, 78 | * 'B': float, 79 | * 'L': float, 80 | * } 81 | * 82 | * @phpstan-type TCellDef array{ 83 | * 'margin': TCellBound, 84 | * 'padding': TCellBound, 85 | * 'borderpos': float, 86 | * } 87 | * 88 | * @phpstan-type TRefUnitValues array{ 89 | * 'parent': float, 90 | * 'font': array{ 91 | * 'rootsize': float, 92 | * 'size': float, 93 | * 'xheight': float, 94 | * 'zerowidth': float, 95 | * }, 96 | * 'viewport': array{ 97 | * 'width': float, 98 | * 'height': float, 99 | * }, 100 | * 'page': array{ 101 | * 'width': float, 102 | * 'height': float, 103 | * }, 104 | * } 105 | * 106 | * @phpstan-type TCustomXMP array{ 107 | * 'x:xmpmeta': string, 108 | * 'x:xmpmeta.rdf:RDF': string, 109 | * 'x:xmpmeta.rdf:RDF.rdf:Description': string, 110 | * 'x:xmpmeta.rdf:RDF.rdf:Description.pdfaExtension:schemas': string, 111 | * 'x:xmpmeta.rdf:RDF.rdf:Description.pdfaExtension:schemas.rdf:Bag': string, 112 | * } 113 | * 114 | * @phpstan-type TStackBBox array 115 | * 116 | * @phpstan-import-type TAnnot from Output 117 | * @phpstan-import-type TEmbeddedFile from Output 118 | * @phpstan-import-type TObjID from Output 119 | * @phpstan-import-type TOutline from Output 120 | * @phpstan-import-type TSignature from Output 121 | * @phpstan-import-type TSignTimeStamp from Output 122 | * @phpstan-import-type TGTransparency from Output 123 | * @phpstan-import-type TUserRights from Output 124 | * @phpstan-import-type TXOBject from Output 125 | * 126 | * @SuppressWarnings("PHPMD") 127 | */ 128 | abstract class Base 129 | { 130 | /** 131 | * Encrypt object. 132 | */ 133 | public ObjEncrypt $encrypt; 134 | 135 | /** 136 | * Color object. 137 | */ 138 | public ObjColor $color; 139 | 140 | /** 141 | * Barcode object. 142 | */ 143 | public ObjBarcode $barcode; 144 | 145 | /** 146 | * File object. 147 | */ 148 | public ObjFile $file; 149 | 150 | /** 151 | * Cache object. 152 | */ 153 | public ObjCache $cache; 154 | 155 | /** 156 | * Unicode Convert object. 157 | */ 158 | public ObjUniConvert $uniconv; 159 | 160 | /** 161 | * Page object. 162 | */ 163 | public ObjPage $page; 164 | 165 | /** 166 | * Graph object. 167 | */ 168 | public ObjGraph $graph; 169 | 170 | /** 171 | * Font object. 172 | */ 173 | public ObjFont $font; 174 | 175 | /** 176 | * Image Import object. 177 | */ 178 | public ObjImage $image; 179 | 180 | /** 181 | * TCPDF version. 182 | */ 183 | protected string $version = '8.4.0'; 184 | 185 | /** 186 | * Time is seconds since EPOCH when the document was created. 187 | */ 188 | protected int $doctime = 0; 189 | 190 | /** 191 | * Time is seconds since EPOCH when the document was modified. 192 | */ 193 | protected int $docmodtime = 0; 194 | 195 | /** 196 | * The name of the application that generates the PDF. 197 | * 198 | * If the document was converted to PDF from another format, 199 | * the name of the conforming product that created the original document from which it was converted. 200 | */ 201 | protected string $creator = 'TCPDF'; 202 | 203 | /** 204 | * The name of the person who created the document. 205 | */ 206 | protected string $author = 'TCPDF'; 207 | 208 | /** 209 | * Subject of the document. 210 | */ 211 | protected string $subject = '-'; 212 | 213 | /** 214 | * Title of the document. 215 | */ 216 | protected string $title = 'PDF Document'; 217 | 218 | /** 219 | * Space-separated list of keywords associated with the document. 220 | */ 221 | protected string $keywords = 'TCPDF'; 222 | 223 | /** 224 | * Additional custom XMP data. 225 | * 226 | * @var TCustomXMP 227 | */ 228 | protected array $custom_xmp = [ 229 | 'x:xmpmeta' => '', 230 | 'x:xmpmeta.rdf:RDF' => '', 231 | 'x:xmpmeta.rdf:RDF.rdf:Description' => '', 232 | 'x:xmpmeta.rdf:RDF.rdf:Description.pdfaExtension:schemas' => '', 233 | 'x:xmpmeta.rdf:RDF.rdf:Description.pdfaExtension:schemas.rdf:Bag' => '', 234 | ]; 235 | 236 | /** 237 | * Set this to TRUE to add the default sRGB ICC color profile 238 | */ 239 | protected bool $sRGB = false; 240 | 241 | /** 242 | * Viewer preferences dictionary controlling the way the document is to be presented on the screen or in print. 243 | * (PDF reference, "Viewer Preferences"). 244 | * 245 | * @var TViewerPref 246 | */ 247 | protected array $viewerpref = []; 248 | 249 | /** 250 | * Boolean flag to set the default document language direction. 251 | * False = LTR = Left-To-Right. 252 | * True = RTL = Right-To-Left. 253 | * 254 | * @val bool 255 | */ 256 | protected bool $rtl = false; 257 | 258 | /** 259 | * Document ID. 260 | */ 261 | protected string $fileid; 262 | 263 | /** 264 | * Unit of measure. 265 | */ 266 | protected string $unit = 'mm'; 267 | 268 | /** 269 | * Valid HTML/CSS/SVG units. 270 | * 271 | * @var array 272 | */ 273 | protected const VALIDUNITS = [ 274 | '%', 'ch', 'cm', 'em', 'ex', 275 | 'in', 'mm', 'pc', 'pt', 'px', 276 | 'rem', 'vh', 'vmax', 'vmin', 'vw', 277 | ]; 278 | 279 | /** 280 | * Map of relative font sizes. 281 | * The key is the relative size and the value is the font size increment in points. 282 | * 283 | * @var array 284 | */ 285 | protected const FONTRELSIZE = [ 286 | 'xx-small' => -4.0, 287 | 'x-small' => -3.0, 288 | 'smaller' => -3.0, 289 | 'small' => -2.0, 290 | 'medium' => 0.0, 291 | 'large' => 2.0, 292 | 'larger' => 3.0, 293 | 'x-large' => 4.0, 294 | 'xx-large' => 6.0, 295 | ]; 296 | 297 | /** 298 | * Ration for small font. 299 | * 300 | * @var float 301 | */ 302 | protected const FONT_SMALL_RATIO = 2 / 3; 303 | 304 | /** 305 | * Default monospaced font. 306 | * 307 | * @var string 308 | */ 309 | protected const FONT_MONO = 'courier'; 310 | 311 | /** 312 | * Default eference values for unit conversion. 313 | * 314 | * @var TRefUnitValues 315 | */ 316 | protected const REFUNITVAL = [ 317 | 'parent' => 1.0, 318 | 'font' => [ 319 | 'rootsize' => 10.0, 320 | 'size' => 10.0, 321 | 'xheight' => 5.0, 322 | 'zerowidth' => 3.0, 323 | ], 324 | 'viewport' => [ 325 | 'width' => 1000.0, 326 | 'height' => 1000.0, 327 | ], 328 | 'page' => [ 329 | 'width' => 595.276, 330 | 'height' => 841.890, 331 | ], 332 | ]; 333 | 334 | /** 335 | * DPI (Dot Per Inch) PDF Document Resolution (do not change). 336 | * 1pt = 1/72 inch. 337 | * 338 | * @var float 339 | */ 340 | protected const DPI_PDF = 72.0; 341 | 342 | /** 343 | * DPI (Dot Per Inch) Image/CSS Resolution (do not change). 344 | * 1pt = 1/96 inch. 345 | * 346 | * @var float 347 | */ 348 | protected const DPI_IMG = 96.0; 349 | 350 | /** 351 | * DPI (Dot Per Inch) ratio between internal PDF points and pixels. 352 | * 353 | * @var float 354 | */ 355 | protected const DPI_PIXEL_RATIO = self::DPI_PDF / self::DPI_IMG; 356 | 357 | /** 358 | * Unit of measure conversion ratio. 359 | */ 360 | protected float $kunit = 1.0; 361 | 362 | /** 363 | * Version of the PDF/A mode or 0 otherwise. 364 | */ 365 | protected int $pdfa = 0; 366 | 367 | /** 368 | * Enable stream compression. 369 | */ 370 | protected bool $compress = true; 371 | 372 | /** 373 | * True if we are in PDF/X mode. 374 | */ 375 | protected bool $pdfx = false; 376 | 377 | /** 378 | * True if the document is signed. 379 | */ 380 | protected bool $sign = false; 381 | 382 | /** 383 | * True if the signature approval is enabled (for incremental updates). 384 | */ 385 | protected bool $sigapp = false; 386 | 387 | /** 388 | * True to subset the fonts. 389 | */ 390 | protected bool $subsetfont = false; 391 | 392 | /** 393 | * True for Unicode font mode. 394 | */ 395 | protected bool $isunicode = true; 396 | 397 | /** 398 | * Document encoding. 399 | */ 400 | protected string $encoding = 'UTF-8'; 401 | 402 | /** 403 | * Current PDF object number. 404 | */ 405 | public int $pon = 0; 406 | 407 | /** 408 | * PDF version. 409 | */ 410 | protected string $pdfver = '1.7'; 411 | 412 | /** 413 | * Defines the way the document is to be displayed by the viewer. 414 | * 415 | * @var array{ 416 | * zoom: int|string, 417 | * layout: string, 418 | * mode: string, 419 | * } 420 | */ 421 | protected array $display = [ 422 | 'zoom' => 'default', 423 | 'layout' => 'SinglePage', 424 | 'mode' => 'UseNone', 425 | ]; 426 | 427 | /** 428 | * Embedded files data. 429 | * 430 | * @var array 431 | */ 432 | protected array $embeddedfiles = []; 433 | 434 | /** 435 | * Annotations indexed bu object IDs. 436 | * 437 | * @var array 438 | */ 439 | protected array $annotation = []; 440 | 441 | /** 442 | * Array containing the regular expression used to identify withespaces or word separators. 443 | * 444 | * @var array{ 445 | * r: string, 446 | * p: string, 447 | * m: string, 448 | * } 449 | */ 450 | protected array $spaceregexp = [ 451 | 'r' => '/[^\S\xa0]/', 452 | 'p' => '[^\S\xa0]', 453 | 'm' => '', 454 | ]; 455 | 456 | /** 457 | * File name of the PDF document. 458 | */ 459 | protected string $pdffilename; 460 | 461 | /** 462 | * Raw encoded fFile name of the PDF document. 463 | */ 464 | protected string $encpdffilename; 465 | 466 | /** 467 | * Array containing the ID of some named PDF objects. 468 | * 469 | * @var TObjID 470 | */ 471 | protected array $objid = [ 472 | 'catalog' => 0, 473 | 'dests' => 0, 474 | 'form' => [], 475 | 'info' => 0, 476 | 'pages' => 0, 477 | 'resdic' => 0, 478 | 'signature' => 0, 479 | 'srgbicc' => 0, 480 | 'xmp' => 0, 481 | ]; 482 | 483 | /** 484 | * Current XOBject template ID. 485 | * 486 | * @var string 487 | */ 488 | protected string $xobjtid = ''; 489 | 490 | /** 491 | * Outlines Data. 492 | * 493 | * @var array 494 | */ 495 | protected array $outlines = []; 496 | 497 | /** 498 | * Outlines Root object ID. 499 | */ 500 | protected int $outlinerootoid = 0; 501 | 502 | /** 503 | * Javascript catalog entry. 504 | */ 505 | protected string $jstree = ''; 506 | 507 | /** 508 | * Signature Data. 509 | * 510 | * @var TSignature 511 | */ 512 | protected array $signature = [ 513 | 'appearance' => [ 514 | 'empty' => [], 515 | 'name' => '', 516 | 'page' => 0, 517 | 'rect' => '', 518 | ], 519 | 'approval' => '', 520 | 'cert_type' => -1, 521 | 'extracerts' => null, 522 | 'info' => [ 523 | 'ContactInfo' => '', 524 | 'Location' => '', 525 | 'Name' => '', 526 | 'Reason' => '', 527 | ], 528 | 'password' => '', 529 | 'privkey' => '', 530 | 'signcert' => '', 531 | ]; 532 | 533 | /** 534 | * Signature Timestamp Data. 535 | * 536 | * @var TSignTimeStamp 537 | */ 538 | protected array $sigtimestamp = [ 539 | 'enabled' => false, 540 | 'host' => '', 541 | 'username' => '', 542 | 'password' => '', 543 | 'cert' => '', 544 | ]; 545 | 546 | /** 547 | * ByteRange placemark used during digital signature process. 548 | * 549 | * @var string 550 | */ 551 | protected const BYTERANGE = '/ByteRange[0 ********** ********** **********]'; 552 | 553 | /** 554 | * Digital signature max length. 555 | * 556 | * @var int 557 | */ 558 | protected const SIGMAXLEN = 11742; 559 | 560 | /** 561 | * User rights Data. 562 | * 563 | * @var TUserRights 564 | */ 565 | protected array $userrights = [ 566 | 'annots' => '/Create/Delete/Modify/Copy/Import/Export', 567 | 'document' => '/FullSave', 568 | 'ef' => '/Create/Delete/Modify/Import', 569 | 'enabled' => false, 570 | 'form' => '/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate', 571 | 'formex' => '', // 'BarcodePlaintext', 572 | 'signature' => '/Modify', 573 | ]; 574 | 575 | /** 576 | * XObjects data. 577 | * 578 | * @var array 579 | */ 580 | protected array $xobjects = []; 581 | 582 | /** 583 | * Stack of bounding boxes [x, y, width, height] in user units. 584 | * 585 | * @var TStackBBox 586 | */ 587 | protected array $bbox = [[ 588 | 'x' => 0, 589 | 'y' => 0, 590 | 'w' => 0, 591 | 'h' => 0, 592 | ]]; 593 | 594 | /** 595 | * Set to true to enable the default page footer. 596 | * 597 | * @var bool 598 | */ 599 | protected bool $defPageContentEnabled = false; 600 | 601 | /** 602 | * Default font for defautl page content. 603 | * 604 | * @var ?TFontMetric 605 | */ 606 | protected ?array $defaultfont = null; 607 | /** 608 | * The default relative position of the cell origin when 609 | * the border is centered on the cell edge. 610 | */ 611 | public const BORDERPOS_DEFAULT = 0; 612 | 613 | /** 614 | * The relative position of the cell origin when 615 | * the border is external to the cell edge. 616 | */ 617 | public const BORDERPOS_EXTERNAL = -0.5; //-1/2 618 | 619 | /** 620 | * The relative position of the cell origin when 621 | * the border is internal to the cell edge. 622 | */ 623 | public const BORDERPOS_INTERNAL = 0.5; // 1/2 624 | 625 | /** 626 | * Default values for cell boundaries. 627 | * 628 | * @const TCellBound 629 | */ 630 | public const ZEROCELLBOUND = [ 631 | 'T' => 0, 632 | 'R' => 0, 633 | 'B' => 0, 634 | 'L' => 0, 635 | ]; 636 | 637 | /** 638 | * Default values for cell. 639 | * 640 | * @const TCellDef 641 | */ 642 | public const ZEROCELL = [ 643 | 'margin' => self::ZEROCELLBOUND, 644 | 'padding' => self::ZEROCELLBOUND, 645 | 'borderpos' => self::BORDERPOS_DEFAULT, 646 | ]; 647 | 648 | /** 649 | * Default values for cell. 650 | * 651 | * @var TCellDef 652 | */ 653 | protected $defcell = self::ZEROCELL; 654 | 655 | /** 656 | * Convert user units to internal points unit. 657 | * 658 | * @param float $usr Value to convert. 659 | */ 660 | public function toPoints(float $usr): float 661 | { 662 | return ($usr * $this->kunit); 663 | } 664 | 665 | /** 666 | * Convert internal points to user unit. 667 | * 668 | * @param float $pnt Value to convert in user units. 669 | */ 670 | public function toUnit(float $pnt): float 671 | { 672 | return ($pnt / $this->kunit); 673 | } 674 | 675 | /** 676 | * Convert vertical user value to internal points unit. 677 | * Note: the internal Y points coordinate starts at the bottom left of the page. 678 | * 679 | * @param float $usr Value to convert. 680 | * @param float $pageh Optional page height in internal points ($pageh:$this->page->getPage()['pheight']). 681 | */ 682 | public function toYPoints(float $usr, float $pageh = -1): float 683 | { 684 | if ($pageh < 0) { 685 | return ($this->page->getPage()['pheight'] - $this->toPoints($usr)); 686 | } 687 | return ($pageh - $this->toPoints($usr)); 688 | } 689 | 690 | /** 691 | * Convert vertical internal points value to user unit. 692 | * Note: the internal Y points coordinate starts at the bottom left of the page. 693 | * 694 | * @param float $pnt Value to convert. 695 | * @param float $pageh Optional page height in internal points ($pageh:$this->page->getPage()['pheight']). 696 | */ 697 | public function toYUnit(float $pnt, float $pageh = -1): float 698 | { 699 | if ($pageh < 0) { 700 | return $this->toUnit($this->page->getPage()['pheight'] - $pnt); 701 | } 702 | return $this->toUnit($pageh - $pnt); 703 | } 704 | 705 | /** 706 | * Enable or disable the default page content. 707 | * 708 | * @param bool $enable Enable or disable the default page content. 709 | * 710 | * @return void 711 | */ 712 | public function enableDefaultPageContent(bool $enable = true): void 713 | { 714 | $this->defPageContentEnabled = $enable; 715 | } 716 | 717 | /** 718 | * Converts a string containing value and unit of measure to internal points. 719 | * This is used to convert values for SVG, CSS, HTML. 720 | * 721 | * @param string|float|int $val String containing values and unit. 722 | * @param TRefUnitValues $ref Reference values in internal points. 723 | * @param string $defunit Default unit (can be one of the VALIDUNITS). 724 | * 725 | * @return float Internal points value. 726 | */ 727 | protected function getUnitValuePoints( 728 | string|float|int $val, 729 | array $ref = self::REFUNITVAL, 730 | string $defunit = 'px', 731 | ): float { 732 | $unit = 'px'; 733 | if (\in_array($defunit, self::VALIDUNITS)) { 734 | $unit = $defunit; 735 | } 736 | 737 | $value = 0.0; 738 | if (\is_numeric($val)) { 739 | $value = \floatval($val); 740 | } elseif (\preg_match('/([0-9\.\-\+]+)([a-z%]{0,4})/', $val, $match)) { 741 | $value = \floatval($match[1]); 742 | if (\in_array($match[2], self::VALIDUNITS)) { 743 | $unit = $match[2]; 744 | } 745 | } else { 746 | throw new PdfException('Invalid value: ' . $val); 747 | } 748 | 749 | return match ($unit) { 750 | // Percentage relative to the parent element. 751 | '%' => (($value * $ref['parent']) / 100), 752 | // Relative to the width of the "0" (zero) 753 | 'ch' => ($value * $ref['font']['zerowidth']), 754 | // Centimeters. 755 | 'cm' => (($value * self::DPI_PDF) / 2.54), 756 | // Relative to the font-size of the element. 757 | 'em' => ($value * $ref['font']['size']), 758 | // Relative to the x-height of the current font. 759 | 'ex' => ($value * $ref['font']['xheight']), 760 | // Inches. 761 | 'in' => ($value * self::DPI_PDF), 762 | // Millimeters. 763 | 'mm' => (($value * self::DPI_PDF) / 25.4), 764 | // One pica is 12 points. 765 | 'pc' => ($value * 12), 766 | // Points. 767 | 'pt' => $value, 768 | // Pixels. 769 | 'px' => ($value * self::DPI_PIXEL_RATIO), 770 | // Relative to font-size of the root element. 771 | 'rem' => ($value * $ref['font']['rootsize']), 772 | // Relative to 1% of the height of the viewport. 773 | 'vh' => (($value * $ref['viewport']['height']) / 100), 774 | // Relative to 1% of viewport's* larger dimension. 775 | 'vmax' => (($value * \max($ref['viewport']['height'], $ref['viewport']['width'])) / 100), 776 | // Relative to 1% of viewport's smaller dimension. 777 | 'vmin' => (($value * \min($ref['viewport']['height'], $ref['viewport']['width'])) / 100), 778 | // Relative to 1% of the width of the viewport. 779 | 'vw' => (($value * $ref['viewport']['width']) / 100), 780 | }; 781 | } 782 | 783 | /** 784 | * Converts a string containing font size value to internal points. 785 | * This is used to convert values for SVG, CSS, HTML. 786 | * 787 | * @param string|float|int $val String containing values and unit. 788 | * @param TRefUnitValues $ref Reference values in internal points. 789 | * @param string $defunit Default unit (can be one of the VALIDUNITS). 790 | * 791 | * @return float Internal points value. 792 | */ 793 | protected function getFontValuePoints( 794 | string|float|int $val, 795 | array $ref = self::REFUNITVAL, 796 | string $defunit = 'pt', 797 | ): float { 798 | if (\is_string($val) && isset(self::FONTRELSIZE[$val])) { 799 | return ($ref['parent'] + self::FONTRELSIZE[$val]); 800 | } 801 | 802 | return $this->getUnitValuePoints($val, $ref, $defunit); 803 | } 804 | } 805 | -------------------------------------------------------------------------------- /src/MetaInfo.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2002-2025 Nicola Asuni - Tecnick.com LTD 11 | * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) 12 | * @link https://github.com/tecnickcom/tc-lib-pdf 13 | * 14 | * This file is part of tc-lib-pdf software library. 15 | * 16 | * @phpcs:disable Generic.Files.LineLength 17 | */ 18 | 19 | namespace Com\Tecnick\Pdf; 20 | 21 | use Com\Tecnick\Pdf\Exception as PdfException; 22 | 23 | /** 24 | * Com\Tecnick\Pdf\MetaInfo 25 | * 26 | * Meta Informaton PDF class 27 | * 28 | * @since 2002-08-03 29 | * @category Library 30 | * @package Pdf 31 | * @author Nicola Asuni 32 | * @copyright 2002-2025 Nicola Asuni - Tecnick.com LTD 33 | * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) 34 | * @link https://github.com/tecnickcom/tc-lib-pdf 35 | * 36 | * @phpstan-import-type TViewerPref from Base 37 | * 38 | * @SuppressWarnings("PHPMD.DepthOfInheritance") 39 | */ 40 | abstract class MetaInfo extends \Com\Tecnick\Pdf\HTML 41 | { 42 | /** 43 | * Valid document zoom modes 44 | * 45 | * @var array 46 | */ 47 | protected const VALIDZOOM = ['fullpage', 'fullwidth', 'real', 'default']; 48 | 49 | /** 50 | * Return the program version. 51 | */ 52 | public function getVersion(): string 53 | { 54 | return $this->version; 55 | } 56 | 57 | /** 58 | * Set a field value only if it is not empty. 59 | * 60 | * @param string $field Field name 61 | * @param string $value Value to set 62 | */ 63 | private function setNonEmptyFieldValue(string $field, string $value): static 64 | { 65 | if ($value !== '') { 66 | $this->$field = $value; 67 | } 68 | 69 | return $this; 70 | } 71 | 72 | /** 73 | * Set the value of an existing array key if it is not empty. 74 | * 75 | * @param string $field Field array name 76 | * @param string $key Key name 77 | * @param string $value Value to set 78 | */ 79 | private function setNonEmptyArrayFieldValue(string $field, string $key, string $value): static 80 | { 81 | if ( 82 | isset($this->{$field}) 83 | && \is_array($this->{$field}) 84 | && ($key !== '') 85 | && isset($this->{$field}[$key]) 86 | && ($value !== '') 87 | ) { 88 | $this->{$field}[$key] = $value; 89 | } 90 | 91 | return $this; 92 | } 93 | 94 | /** 95 | * Defines the creator of the document. 96 | * This is typically the name of the application that generates the PDF. 97 | * 98 | * @param string $creator The name of the creator. 99 | */ 100 | public function setCreator(string $creator): static 101 | { 102 | return $this->setNonEmptyFieldValue('creator', $creator); 103 | } 104 | 105 | /** 106 | * Defines the author of the document. 107 | * 108 | * @param string $author The name of the author. 109 | */ 110 | public function setAuthor(string $author): static 111 | { 112 | return $this->setNonEmptyFieldValue('author', $author); 113 | } 114 | 115 | /** 116 | * Defines the subject of the document. 117 | * 118 | * @param string $subject The subject. 119 | */ 120 | public function setSubject(string $subject): static 121 | { 122 | return $this->setNonEmptyFieldValue('subject', $subject); 123 | } 124 | 125 | /** 126 | * Defines the title of the document. 127 | * 128 | * @param string $title The title. 129 | */ 130 | public function setTitle(string $title): static 131 | { 132 | return $this->setNonEmptyFieldValue('title', $title); 133 | } 134 | 135 | /** 136 | * Associates keywords with the document, generally in the form 'keyword1 keyword2 ...'. 137 | * 138 | * @param string $keywords Space-separated list of keywords. 139 | */ 140 | public function setKeywords(string $keywords): static 141 | { 142 | return $this->setNonEmptyFieldValue('keywords', $keywords); 143 | } 144 | 145 | /** 146 | * Set the PDF version (check PDF reference for valid values). 147 | * 148 | * @param string $version PDF document version. 149 | * 150 | * @throw PdfException in case of error. 151 | */ 152 | public function setPDFVersion(string $version = '1.7'): static 153 | { 154 | if ($this->pdfa == 1) { // PDF/A 1 mode 155 | $this->pdfver = '1.4'; 156 | return $this; 157 | } 158 | 159 | $isvalid = \preg_match('/^[1-9]+[.]\d+$/', $version); 160 | if ($isvalid === false) { 161 | throw new PdfException('Invalid PDF version format'); 162 | } 163 | 164 | $this->pdfver = $version; 165 | return $this; 166 | } 167 | 168 | /** 169 | * Set the sRGB mode 170 | * 171 | * @param bool $enabled Set to true to add the default sRGB ICC color profile 172 | */ 173 | public function setSRGB(bool $enabled): static 174 | { 175 | $this->sRGB = $enabled; 176 | return $this; 177 | } 178 | 179 | /** 180 | * Returns a formatted date for meta information 181 | * 182 | * @param int $time Time in seconds. 183 | * 184 | * @return string date-time string. 185 | */ 186 | protected function getFormattedDate(int $time): string 187 | { 188 | return \substr_replace(\date('YmdHisO', $time), "'", (-2), 0) . "'"; 189 | } 190 | 191 | /** 192 | * Returns a formatted date for XMP meta information 193 | * 194 | * @param int $time Time in seconds. 195 | * 196 | * @return string date-time string. 197 | */ 198 | protected function getXMPFormattedDate(int $time): string 199 | { 200 | return \date('Y-m-dTH:i:sP', $time); 201 | } 202 | 203 | /** 204 | * Returns the producer string 205 | */ 206 | protected function getProducer(): string 207 | { 208 | return "\x54\x43\x50\x44\x46\x20" 209 | . $this->version 210 | . "\x20\x28\x68\x74\x74\x70\x73\x3a\x2f\x2f" 211 | . "\x74\x63\x70\x64\x66\x2e\x6f\x72\x67\x29"; 212 | } 213 | 214 | /** 215 | * Returns a formatted date for meta information 216 | * 217 | * @param int $time Time in seconds. 218 | * @param int $oid Current PDF object number. 219 | * 220 | * @return string escaped date-time string. 221 | */ 222 | protected function getOutDateTimeString(int $time, int $oid): string 223 | { 224 | if ($time === 0) { 225 | $time = $this->doctime; 226 | } 227 | 228 | return $this->encrypt->escapeDataString('D:' . $this->getFormattedDate($time), $oid); 229 | } 230 | 231 | /** 232 | * Get the PDF output string for the Document Information Dictionary. 233 | * (ref. Chapter 14.3.3 Document Information Dictionary of PDF32000_2008.pdf) 234 | */ 235 | protected function getOutMetaInfo(): string 236 | { 237 | $oid = ++$this->pon; 238 | $this->objid['info'] = $oid; 239 | return $oid . ' 0 obj' . "\n" 240 | . '<<' 241 | . ' /Creator ' . $this->getOutTextString($this->creator, $oid, true) 242 | . ' /Author ' . $this->getOutTextString($this->author, $oid, true) 243 | . ' /Subject ' . $this->getOutTextString($this->subject, $oid, true) 244 | . ' /Title ' . $this->getOutTextString($this->title, $oid, true) 245 | . ' /Keywords ' . $this->getOutTextString($this->keywords, $oid, true) 246 | . ' /Producer ' . $this->getOutTextString($this->getProducer(), $oid, true) 247 | . ' /CreationDate ' . $this->getOutDateTimeString($this->doctime, $oid) 248 | . ' /ModDate ' . $this->getOutDateTimeString($this->docmodtime, $oid) 249 | . ' /Trapped /False' 250 | . ' >>' . "\n" 251 | . 'endobj' . "\n"; 252 | } 253 | 254 | /** 255 | * Escape some special characters (< > &) for XML output. 256 | * 257 | * @param string $str Input string to escape. 258 | */ 259 | protected function getEscapedXML(string $str): string 260 | { 261 | return \strtr($str, [ 262 | "\0" => '', 263 | '&' => '&', 264 | '<' => '<', 265 | '>' => '>', 266 | ]); 267 | } 268 | 269 | /** 270 | * Set additional custom XMP data to be appended just before the end of the tag indicated by the key. 271 | * 272 | * IMPORTANT: 273 | * This data is added as-is without controls, so you have to validate your data before using this method. 274 | * 275 | * @param string $key Key for the custom XMP data. Valid keys are: 276 | * - 'x:xmpmeta' 277 | * - 'x:xmpmeta.rdf:RDF' 278 | * - 'x:xmpmeta.rdf:RDF.rdf:Description' 279 | * - 'x:xmpmeta.rdf:RDF.rdf:Description.pdfaExtension:schemas' 280 | * - 'x:xmpmeta.rdf:RDF.rdf:Description.pdfaExtension:schemas.rdf:Bag' 281 | * @param string $xmp Custom XMP data. 282 | */ 283 | public function setCustomXMP(string $key, string $xmp): static 284 | { 285 | return $this->setNonEmptyArrayFieldValue('custom_xmp', $key, $xmp); 286 | } 287 | 288 | /** 289 | * Get the PDF output string for the XMP data object 290 | * 291 | * @SuppressWarnings("PHPMD.ExcessiveMethodLength") 292 | */ 293 | protected function getOutXMP(): string 294 | { 295 | $uuid = 'uuid:' . \substr($this->fileid, 0, 8) 296 | . '-' . \substr($this->fileid, 8, 4) 297 | . '-' . \substr($this->fileid, 12, 4) 298 | . '-' . \substr($this->fileid, 16, 4) 299 | . '-' . \substr($this->fileid, 20, 12); 300 | 301 | // @codingStandardsIgnoreStart 302 | $xmp = 'uniconv->chr(0xfeff) . '" id="W5M0MpCehiHzreSzNTczkc9d"?>' . "\n" 303 | . '' . "\n" 304 | . "\t" . '' . "\n" 305 | . "\t\t" . '' . "\n" 306 | . "\t\t\t" . 'application/pdf' . "\n" 307 | . "\t\t\t" . '' . "\n" 308 | . "\t\t\t\t" . '' . "\n" 309 | . "\t\t\t\t\t" . '' . $this->getEscapedXML($this->title) . '' . "\n" 310 | . "\t\t\t\t" . '' . "\n" 311 | . "\t\t\t" . '' . "\n" 312 | . "\t\t\t" . '' . "\n" 313 | . "\t\t\t\t" . '' . "\n" 314 | . "\t\t\t\t\t" . '' . $this->getEscapedXML($this->author) . '' . "\n" 315 | . "\t\t\t\t" . '' . "\n" 316 | . "\t\t\t" . '' . "\n" 317 | . "\t\t\t" . '' . "\n" 318 | . "\t\t\t\t" . '' . "\n" 319 | . "\t\t\t\t\t" . '' . $this->getEscapedXML($this->subject) . '' . "\n" 320 | . "\t\t\t\t" . '' . "\n" 321 | . "\t\t\t" . '' . "\n" 322 | . "\t\t\t" . '' . "\n" 323 | . "\t\t\t\t" . '' . "\n" 324 | . "\t\t\t\t\t" . '' . $this->getEscapedXML($this->keywords) . '' . "\n" 325 | . "\t\t\t\t" . '' . "\n" 326 | . "\t\t\t" . '' . "\n" 327 | . "\t\t" . '' . "\n" 328 | . "\t\t" . '' . "\n" 329 | . "\t\t\t" . '' . $this->getXMPFormattedDate($this->doctime) . '' . "\n" 330 | . "\t\t\t" . '' . $this->getEscapedXML($this->creator) . '' . "\n" 331 | . "\t\t\t" . '' . $this->getXMPFormattedDate($this->docmodtime) . '' . "\n" 332 | . "\t\t\t" . '' . $this->getXMPFormattedDate($this->doctime) . '' . "\n" 333 | . "\t\t" . '' . "\n" 334 | . "\t\t" . '' . "\n" 335 | . "\t\t\t" . '' . $this->getEscapedXML($this->keywords) . '' . "\n" 336 | . "\t\t\t" . '' . $this->getEscapedXML($this->getProducer()) . '' . "\n" 337 | . "\t\t" . '' . "\n" 338 | . "\t\t" . '' . "\n" 339 | . "\t\t\t" . '' . $uuid . '' . "\n" 340 | . "\t\t\t" . '' . $uuid . '' . "\n" 341 | . "\t\t" . '' . "\n"; 342 | 343 | if ($this->pdfa !== 0) { 344 | $xmp .= ' ' . "\n" 345 | . "\t\t\t" . '' . $this->pdfa . '' . "\n" 346 | . "\t\t\t" . 'B' . "\n" 347 | . "\t\t" . '' . "\n"; 348 | } 349 | 350 | // XMP extension schemas 351 | $xmp .= "\t\t" . '' . "\n" 352 | . "\t\t\t" . '' . "\n" 353 | . "\t\t\t\t" . '' . "\n" 354 | . "\t\t\t\t\t" . '' . "\n" 355 | . "\t\t\t\t\t\t" . 'http://ns.adobe.com/pdf/1.3/' . "\n" 356 | . "\t\t\t\t\t\t" . 'pdf' . "\n" 357 | . "\t\t\t\t\t\t" . 'Adobe PDF Schema' . "\n" 358 | . "\t\t\t\t\t" . '' . "\n" 359 | . "\t\t\t\t\t" . '' . "\n" 360 | . "\t\t\t\t\t\t" . 'http://ns.adobe.com/xap/1.0/mm/' . "\n" 361 | . "\t\t\t\t\t\t" . 'xmpMM' . "\n" 362 | . "\t\t\t\t\t\t" . 'XMP Media Management Schema' . "\n" 363 | . "\t\t\t\t\t\t" . '' . "\n" 364 | . "\t\t\t\t\t\t\t" . '' . "\n" 365 | . "\t\t\t\t\t\t\t\t" . '' . "\n" 366 | . "\t\t\t\t\t\t\t\t\t" . 'internal' . "\n" 367 | . "\t\t\t\t\t\t\t\t\t" . 'UUID based identifier for specific incarnation of a document' . "\n" 368 | . "\t\t\t\t\t\t\t\t\t" . 'InstanceID' . "\n" 369 | . "\t\t\t\t\t\t\t\t\t" . 'URI' . "\n" 370 | . "\t\t\t\t\t\t\t\t" . '' . "\n" 371 | . "\t\t\t\t\t\t\t" . '' . "\n" 372 | . "\t\t\t\t\t\t" . '' . "\n" 373 | . "\t\t\t\t\t" . '' . "\n" 374 | . "\t\t\t\t\t" . '' . "\n" 375 | . "\t\t\t\t\t\t" . 'http://www.aiim.org/pdfa/ns/id/' . "\n" 376 | . "\t\t\t\t\t\t" . 'pdfaid' . "\n" 377 | . "\t\t\t\t\t\t" . 'PDF/A ID Schema' . "\n" 378 | . "\t\t\t\t\t\t" . '' . "\n" 379 | . "\t\t\t\t\t\t\t" . '' . "\n" 380 | . "\t\t\t\t\t\t\t\t" . '' . "\n" 381 | . "\t\t\t\t\t\t\t\t\t" . 'internal' . "\n" 382 | . "\t\t\t\t\t\t\t\t\t" . 'Part of PDF/A standard' . "\n" 383 | . "\t\t\t\t\t\t\t\t\t" . 'part' . "\n" 384 | . "\t\t\t\t\t\t\t\t\t" . 'Integer' . "\n" 385 | . "\t\t\t\t\t\t\t\t" . '' . "\n" 386 | . "\t\t\t\t\t\t\t\t" . '' . "\n" 387 | . "\t\t\t\t\t\t\t\t\t" . 'internal' . "\n" 388 | . "\t\t\t\t\t\t\t\t\t" . 'Amendment of PDF/A standard' . "\n" 389 | . "\t\t\t\t\t\t\t\t\t" . 'amd' . "\n" 390 | . "\t\t\t\t\t\t\t\t\t" . 'Text' . "\n" 391 | . "\t\t\t\t\t\t\t\t" . '' . "\n" 392 | . "\t\t\t\t\t\t\t\t" . '' . "\n" 393 | . "\t\t\t\t\t\t\t\t\t" . 'internal' . "\n" 394 | . "\t\t\t\t\t\t\t\t\t" . 'Conformance level of PDF/A standard' . "\n" 395 | . "\t\t\t\t\t\t\t\t\t" . 'conformance' . "\n" 396 | . "\t\t\t\t\t\t\t\t\t" . 'Text' . "\n" 397 | . "\t\t\t\t\t\t\t\t" . '' . "\n" 398 | . "\t\t\t\t\t\t\t" . '' . "\n" 399 | . "\t\t\t\t\t\t" . '' . "\n" 400 | . "\t\t\t\t\t" . '' . "\n" 401 | . $this->custom_xmp['x:xmpmeta.rdf:RDF.rdf:Description.pdfaExtension:schemas.rdf:Bag'] . "\n" 402 | . "\t\t\t\t" . '' . "\n" 403 | . $this->custom_xmp['x:xmpmeta.rdf:RDF.rdf:Description.pdfaExtension:schemas'] . "\n" 404 | . "\t\t\t" . '' . "\n" 405 | . $this->custom_xmp['x:xmpmeta.rdf:RDF.rdf:Description'] . "\n" 406 | . "\t\t" . '' . "\n" 407 | . $this->custom_xmp['x:xmpmeta.rdf:RDF'] . "\n" 408 | . "\t" . '' . "\n" 409 | . $this->custom_xmp['x:xmpmeta'] . "\n" 410 | . '' . "\n" 411 | . ''; 412 | // @codingStandardsIgnoreEnd 413 | 414 | $oid = ++$this->pon; 415 | $this->objid['xmp'] = $oid; 416 | 417 | return $oid . ' 0 obj' . "\n" 418 | . '<<' 419 | . ' /Type /Metadata' 420 | . ' /Subtype /XML' 421 | . ' /Length ' . \strlen($xmp) 422 | . ' >> stream' . "\n" 423 | . $xmp . "\n" 424 | . 'endstream' . "\n" 425 | . 'endobj' . "\n"; 426 | } 427 | 428 | /** 429 | * Set the default document language direction. 430 | * 431 | * @param bool $enabled False = LTR = Left-To-Right; True = RTL = Right-To-Left. 432 | */ 433 | public function setRTL(bool $enabled): static 434 | { 435 | $this->rtl = $enabled; 436 | return $this; 437 | } 438 | 439 | /** 440 | * Set the viewer preferences dictionary 441 | * controlling the way the document is to be presented on the screen or in print. 442 | * 443 | * @param TViewerPref $pref Array of options (see PDF reference "Viewer Preferences"). 444 | */ 445 | public function setViewerPreferences(array $pref): static 446 | { 447 | $this->viewerpref = $pref; 448 | return $this; 449 | } 450 | 451 | /** 452 | * Sanitize the page box name and return the default 'CropBox' in case of error. 453 | * 454 | * @param string $name Entry name. 455 | */ 456 | protected function getPageBoxName(string $name): string 457 | { 458 | $box = 'CropBox'; 459 | if (isset($this->viewerpref[$name])) { 460 | $val = $this->viewerpref[$name]; 461 | if ( 462 | isset($this->page->{$box}[$val]) // @phpstan-ignore offsetAccess.nonOffsetAccessible 463 | && \is_string($this->page->{$box}[$val]) 464 | ) { 465 | $box = $this->page->{$box}[$val]; 466 | } 467 | } 468 | 469 | return ' /' . $name . ' /' . $box; 470 | } 471 | 472 | /** 473 | * Sanitize the page box name and return the default 'CropBox' in case of error. 474 | */ 475 | protected function getPagePrintScaling(): string 476 | { 477 | $mode = 'AppDefault'; 478 | if (isset($this->viewerpref['PrintScaling'])) { 479 | $name = \strtolower($this->viewerpref['PrintScaling']); 480 | $valid = [ 481 | 'none' => 'None', 482 | 'appdefault' => 'AppDefault', 483 | ]; 484 | if (isset($valid[$name])) { 485 | $mode = $valid[$name]; 486 | } 487 | } 488 | 489 | return ' /PrintScaling /' . $mode; 490 | } 491 | 492 | /** 493 | * Returns the Duplex mode for the Viewer Preferences 494 | */ 495 | protected function getDuplexMode(): string 496 | { 497 | if (isset($this->viewerpref['Duplex'])) { 498 | $name = \strtolower($this->viewerpref['Duplex']); 499 | $valid = [ 500 | 'simplex' => 'Simplex', 501 | 'duplexflipshortedge' => 'DuplexFlipShortEdge', 502 | 'duplexfliplongedge' => 'DuplexFlipLongEdge', 503 | ]; 504 | if (isset($valid[$name])) { 505 | return ' /Duplex /' . $valid[$name]; 506 | } 507 | } 508 | 509 | return ''; 510 | } 511 | 512 | /** 513 | * Returns the Viewer Preference boolean entry. 514 | * 515 | * @param string $name Entry name. 516 | */ 517 | protected function getBooleanMode(string $name): string 518 | { 519 | if (isset($this->viewerpref[$name])) { 520 | return ' /' . $name . ' ' . ($this->viewerpref[$name] === true ? 'true' : 'false'); 521 | } 522 | 523 | return ''; 524 | } 525 | 526 | /** 527 | * Returns the PDF viewer preferences for the catalog section 528 | */ 529 | protected function getOutViewerPref(): string 530 | { 531 | $vpr = $this->viewerpref; 532 | $out = ' /ViewerPreferences <<'; 533 | if ($this->rtl) { 534 | $out .= ' /Direction /R2L'; 535 | } else { 536 | $out .= ' /Direction /L2R'; 537 | } 538 | 539 | $out .= $this->getBooleanMode('HideToolbar'); 540 | $out .= $this->getBooleanMode('HideMenubar'); 541 | $out .= $this->getBooleanMode('HideWindowUI'); 542 | $out .= $this->getBooleanMode('FitWindow'); 543 | $out .= $this->getBooleanMode('CenterWindow'); 544 | $out .= $this->getBooleanMode('DisplayDocTitle'); 545 | if (isset($vpr['NonFullScreenPageMode'])) { 546 | $out .= ' /NonFullScreenPageMode /' . $this->page->getDisplay($vpr['NonFullScreenPageMode']); 547 | } 548 | 549 | $out .= $this->getPageBoxName('ViewArea'); 550 | $out .= $this->getPageBoxName('ViewClip'); 551 | $out .= $this->getPageBoxName('PrintArea'); 552 | $out .= $this->getPageBoxName('PrintClip'); 553 | $out .= $this->getPagePrintScaling(); 554 | $out .= $this->getDuplexMode(); 555 | $out .= $this->getBooleanMode('PickTrayByPDFSize'); 556 | if (isset($vpr['PrintPageRange'])) { 557 | $PrintPageRangeNum = ''; 558 | foreach ($vpr['PrintPageRange'] as $pnum) { 559 | $PrintPageRangeNum .= ' ' . ($pnum - 1) . ''; 560 | } 561 | 562 | $out .= ' /PrintPageRange [' . $PrintPageRangeNum . ' ]'; 563 | } 564 | 565 | if (isset($vpr['NumCopies'])) { 566 | $out .= ' /NumCopies ' . (int) $vpr['NumCopies']; 567 | } 568 | 569 | return $out . ' >>'; 570 | } 571 | } 572 | -------------------------------------------------------------------------------- /src/Cell.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2002-2025 Nicola Asuni - Tecnick.com LTD 11 | * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) 12 | * @link https://github.com/tecnickcom/tc-lib-pdf 13 | * 14 | * This file is part of tc-lib-pdf software library. 15 | */ 16 | 17 | namespace Com\Tecnick\Pdf; 18 | 19 | /** 20 | * Com\Tecnick\Pdf\Cell 21 | * 22 | * Cell PDF data 23 | * 24 | * @since 2002-08-03 25 | * @category Library 26 | * @package Pdf 27 | * @author Nicola Asuni 28 | * @copyright 2002-2025 Nicola Asuni - Tecnick.com LTD 29 | * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) 30 | * @link https://github.com/tecnickcom/tc-lib-pdf 31 | * 32 | * @phpstan-import-type StyleDataOpt from \Com\Tecnick\Pdf\Graph\Style 33 | * @phpstan-import-type TCellDef from \Com\Tecnick\Pdf\Base 34 | * 35 | * @SuppressWarnings("PHPMD.DepthOfInheritance") 36 | */ 37 | abstract class Cell extends \Com\Tecnick\Pdf\Base 38 | { 39 | /** 40 | * Set the default cell margin in user units. 41 | * 42 | * @param float $top Top. 43 | * @param float $right Right. 44 | * @param float $bottom Bottom. 45 | * @param float $left Left. 46 | */ 47 | public function setDefaultCellMargin( 48 | float $top, 49 | float $right, 50 | float $bottom, 51 | float $left 52 | ): void { 53 | $this->defcell['margin']['T'] = $this->toPoints($top); 54 | $this->defcell['margin']['R'] = $this->toPoints($right); 55 | $this->defcell['margin']['B'] = $this->toPoints($bottom); 56 | $this->defcell['margin']['L'] = $this->toPoints($left); 57 | } 58 | 59 | /** 60 | * Set the default cell padding in user units. 61 | * 62 | * @param float $top Top. 63 | * @param float $right Right. 64 | * @param float $bottom Bottom. 65 | * @param float $left Left. 66 | */ 67 | public function setDefaultCellPadding( 68 | float $top, 69 | float $right, 70 | float $bottom, 71 | float $left 72 | ): void { 73 | $this->defcell['padding']['T'] = $this->toPoints($top); 74 | $this->defcell['padding']['R'] = $this->toPoints($right); 75 | $this->defcell['padding']['B'] = $this->toPoints($bottom); 76 | $this->defcell['padding']['L'] = $this->toPoints($left); 77 | } 78 | 79 | /** 80 | * Sets the default cell border position. 81 | * 82 | * @param float $borderpos The border position to set: 83 | * BORDERPOS_DEFAULT 84 | * BORDERPOS_EXTERNAL 85 | * BORDERPOS_INTERNAL 86 | */ 87 | public function setDefaultCellBorderPos(float $borderpos): void 88 | { 89 | if ( 90 | ($borderpos == self::BORDERPOS_DEFAULT) 91 | || ($borderpos == self::BORDERPOS_EXTERNAL) 92 | || ($borderpos == self::BORDERPOS_INTERNAL) 93 | ) { 94 | $this->defcell['borderpos'] = $borderpos; 95 | return; 96 | } 97 | 98 | $this->defcell['borderpos'] = self::BORDERPOS_DEFAULT; 99 | } 100 | 101 | /** 102 | * Increase the cell padding to account for the border tickness. 103 | * 104 | * @param array $styles Optional to overwrite the styles (see: getCurrentStyleArray). 105 | * @param ?TCellDef $cell Optional to overwrite cell parameters for padding, margin etc. 106 | * 107 | * @return TCellDef 108 | */ 109 | protected function adjustMinCellPadding( 110 | array $styles = [], 111 | ?array $cell = null 112 | ): array { 113 | if ($cell === null) { 114 | $cell = $this->defcell; 115 | } 116 | 117 | if ($styles === []) { 118 | $styles = $this->graph->getCurrentStyleArray(); 119 | } 120 | 121 | $border_ratio = \round(self::BORDERPOS_INTERNAL + $cell['borderpos'], 1); 122 | 123 | $minT = 0; 124 | $minR = 0; 125 | $minB = 0; 126 | $minL = 0; 127 | if (! empty($styles['all']['lineWidth'])) { 128 | $minT = $this->toPoints((float) $styles['all']['lineWidth'] * $border_ratio); 129 | $minR = $minT; 130 | $minB = $minT; 131 | $minL = $minT; 132 | } elseif ( 133 | (\count($styles) == 4) 134 | && isset($styles[0]['lineWidth']) 135 | && isset($styles[1]['lineWidth']) 136 | && isset($styles[2]['lineWidth']) 137 | && isset($styles[3]['lineWidth']) 138 | ) { 139 | $minT = $this->toPoints((float) $styles[0]['lineWidth'] * $border_ratio); 140 | $minR = $this->toPoints((float) $styles[1]['lineWidth'] * $border_ratio); 141 | $minB = $this->toPoints((float) $styles[2]['lineWidth'] * $border_ratio); 142 | $minL = $this->toPoints((float) $styles[3]['lineWidth'] * $border_ratio); 143 | } else { 144 | return $cell; 145 | } 146 | 147 | $cell['padding']['T'] = \max($cell['padding']['T'], $minT); 148 | $cell['padding']['R'] = \max($cell['padding']['R'], $minR); 149 | $cell['padding']['B'] = \max($cell['padding']['B'], $minB); 150 | $cell['padding']['L'] = \max($cell['padding']['L'], $minL); 151 | return $cell; 152 | } 153 | 154 | /** 155 | * Returns the minimum cell height in points for the current text height. 156 | * 157 | * @param float $pheight Text height in internal points. 158 | * @param string $align Text vertical alignment inside the cell: 159 | * - T=top; 160 | * - C=center; 161 | * - B=bottom; 162 | * - A=center-on-font-ascent; 163 | * - L=center-on-font-baseline; 164 | * - D=center-on-font-descent. 165 | * @param ?TCellDef $cell Optional to overwrite cell parameters for padding, margin etc. 166 | */ 167 | protected function cellMinHeight( 168 | float $pheight = 0, 169 | string $align = 'C', 170 | ?array $cell = null 171 | ): float { 172 | if ($cell === null) { 173 | $cell = $this->defcell; 174 | } 175 | 176 | $curfont = $this->font->getCurrentFont(); 177 | 178 | if ($pheight == 0) { 179 | $pheight = $curfont['height']; 180 | } 181 | 182 | return match ($align) { 183 | 'T', 'B' => ($pheight + $cell['padding']['T'] + $cell['padding']['B']), 184 | 'L' => ($pheight - $curfont['height'] + (2 * \max( 185 | ($cell['padding']['T'] + $curfont['ascent']), 186 | ($cell['padding']['B'] - $curfont['descent']) 187 | ))), 188 | 'A', 'D' => ($pheight 189 | - $curfont['height'] 190 | + (2 * ($curfont['height'] + \max($cell['padding']['T'], $cell['padding']['B'])))), 191 | // default on 'C' case 192 | default => ($pheight + (2 * \max($cell['padding']['T'], $cell['padding']['B']))), 193 | }; 194 | } 195 | 196 | /** 197 | * Returns the minimum cell width in points for the current text 198 | * 199 | * @param float $txtwidth Text width in internal points. 200 | * @param string $align Cell horizontal alignment: L=left; C=center; R=right; J=Justify. 201 | * @param ?TCellDef $cell Optional to overwrite cell parameters for padding, margin etc. 202 | */ 203 | protected function cellMinWidth( 204 | float $txtwidth, 205 | string $align = 'L', 206 | ?array $cell = null 207 | ): float { 208 | if ($cell === null) { 209 | $cell = $this->defcell; 210 | } 211 | 212 | if ($align === '' || $align === 'J') { // Justify 213 | $align = $this->rtl ? 'R' : 'L'; 214 | } 215 | 216 | return match ($align) { 217 | 'C' => \ceil($txtwidth + (2 * \max($cell['padding']['L'], $cell['padding']['R']))), 218 | default => \ceil($txtwidth + $cell['padding']['L'] + $cell['padding']['R']), 219 | }; 220 | } 221 | 222 | /** 223 | * Returns the adjusted cell top Y coordinate to account for margins. 224 | * 225 | * @param float $pnty Starting top Y coordinate in internal points. 226 | * @param float $pheight Cell height in internal points. 227 | * @param string $align Cell vertical alignment: T=top; C=center; B=bottom. 228 | * @param ?TCellDef $cell Optional to overwrite cell parameters for padding, margin etc. 229 | */ 230 | protected function cellVPos( 231 | float $pnty, 232 | float $pheight, 233 | string $align = 'T', 234 | ?array $cell = null 235 | ): float { 236 | if ($cell === null) { 237 | $cell = $this->defcell; 238 | } 239 | 240 | return match ($align) { 241 | 'T' => $pnty - $cell['margin']['T'], 242 | 'C' => $pnty + ($pheight / 2), 243 | 'B' => $pnty + $cell['margin']['B'] + $pheight, 244 | default => $pnty, 245 | }; 246 | } 247 | 248 | /** 249 | * Returns the adjusted cell left X coordinate to account for margins. 250 | * 251 | * @param float $pntx Starting top Y coordinate in internal points. 252 | * @param float $pwidth Cell width in internal points. 253 | * @param string $align Cell horizontal alignment: L=left; C=center; R=right; J=Justify. 254 | * @param TCellDef $cell Optional to overwrite cell parameters for padding, margin etc. 255 | */ 256 | protected function cellHPos( 257 | float $pntx, 258 | float $pwidth, 259 | string $align = 'L', 260 | ?array $cell = null 261 | ): float { 262 | if ($cell === null) { 263 | $cell = $this->defcell; 264 | } 265 | 266 | if ($align === '' || $align === 'J') { // Justify 267 | $align = $this->rtl ? 'R' : 'L'; 268 | } 269 | 270 | return match ($align) { 271 | 'L' => $pntx + $cell['margin']['L'], 272 | 'R' => $pntx - $cell['margin']['R'] - $pwidth, 273 | 'C' => $pntx - ($pwidth / 2), 274 | default => $pntx, 275 | }; 276 | } 277 | 278 | /** 279 | * Returns the vertical distance between the cell top side and the text. 280 | * 281 | * @param float $cellpheight Cell height in internal points. 282 | * @param float $txtpheight Text height in internal points. 283 | * @param string $align Text vertical alignment inside the cell: 284 | * - T=top; 285 | * - C=center; 286 | * - B=bottom; 287 | * - A=center-on-font-ascent; 288 | * - L=center-on-font-baseline; 289 | * - D=center-on-font-descent. 290 | * @param ?TCellDef $cell Optional to overwrite cell parameters for padding, margin etc. 291 | */ 292 | protected function cellTextVAlign( 293 | float $cellpheight, 294 | float $txtpheight = 0, 295 | string $align = 'C', 296 | ?array $cell = null 297 | ): float { 298 | if ($cell === null) { 299 | $cell = $this->defcell; 300 | } 301 | 302 | $curfont = $this->font->getCurrentFont(); 303 | 304 | if ($txtpheight == 0) { 305 | $txtpheight = $curfont['height']; 306 | } 307 | 308 | return match ($align) { 309 | 'T' => ($cell['padding']['T']), 310 | 'B' => (($cellpheight - $txtpheight) - $cell['padding']['B']), 311 | 'L' => ((($cellpheight - $txtpheight + $curfont['height']) / 2) - $curfont['ascent']), 312 | 'A' => (($cellpheight - $txtpheight + $curfont['height']) / 2), 313 | 'D' => ((($cellpheight - $txtpheight + $curfont['height']) / 2) - $curfont['height']), 314 | // default on 'C' case 315 | default => (($cellpheight - $txtpheight) / 2) 316 | }; 317 | } 318 | 319 | /** 320 | * Returns the horizontal distance between the cell left side and the text left side. 321 | * 322 | * @param float $pwidth Cell width in internal points. 323 | * @param float $txtpwidth Text width in internal points. 324 | * @param string $align Text horizontal alignment inside the cell: L=left; C=center; R=right; J=Justify. 325 | * @param ?TCellDef $cell Optional to overwrite cell parameters for padding, margin etc. 326 | */ 327 | protected function cellTextHAlign( 328 | float $pwidth, 329 | float $txtpwidth, 330 | string $align = 'L', 331 | ?array $cell = null 332 | ): float { 333 | if ($cell === null) { 334 | $cell = $this->defcell; 335 | } 336 | 337 | if ($align === '' || $align === 'J') { // Justify 338 | $align = $this->rtl ? 'R' : 'L'; 339 | } 340 | 341 | return match ($align) { 342 | 'C' => (($pwidth - $txtpwidth) / 2), 343 | 'R' => ($pwidth - $cell['padding']['R'] - $txtpwidth), 344 | // default on 'L' case 345 | default => ($cell['padding']['L']), 346 | }; 347 | } 348 | 349 | /** 350 | * Returns the top Y coordinate of the cell wrapping the text. 351 | * 352 | * @param float $txty Text baseline top Y coordinate in internal points. 353 | * @param float $cellpheight Cell height in internal points. 354 | * @param float $txtpheight Text height in internal points. 355 | * @param string $align Text vertical alignment inside the cell: 356 | * - T=top; 357 | * - C=center; 358 | * - B=bottom; 359 | * - A=center-on-font-ascent; 360 | * - L=center-on-font-baseline; 361 | * - D=center-on-font-descent. 362 | * @param ?TCellDef $cell Optional to overwrite cell parameters for padding, margin etc. 363 | */ 364 | protected function cellVPosFromText( 365 | float $txty, 366 | float $cellpheight, 367 | float $txtpheight = 0, 368 | string $align = 'C', 369 | ?array $cell = null 370 | ): float { 371 | return ($txty + $this->cellTextVAlign($cellpheight, $txtpheight, $align, $cell)); 372 | } 373 | 374 | /** 375 | * Returns the left X coordinate of the cell wrapping the text. 376 | * 377 | * @param float $txtx Text left X coordinate in internal points. 378 | * @param float $pwidth Cell width in internal points. 379 | * @param float $txtpwidth Text width in internal points. 380 | * @param string $align Text horizontal alignment inside the cell: L=left; C=center; R=right. 381 | * @param ?TCellDef $cell Optional to overwrite cell parameters for padding, margin etc. 382 | */ 383 | protected function cellHPosFromText( 384 | float $txtx, 385 | float $pwidth, 386 | float $txtpwidth, 387 | string $align = 'L', 388 | ?array $cell = null 389 | ): float { 390 | return ($txtx - $this->cellTextHAlign($pwidth, $txtpwidth, $align, $cell)); 391 | } 392 | 393 | /** 394 | * Returns the top Y coordinate of the text inside the cell. 395 | * 396 | * @param float $pnty Cell top Y coordinate in internal points. 397 | * @param float $cellpheight Cell height in internal points. 398 | * @param float $txtpheight Text height in internal points. 399 | * @param string $align Text vertical alignment inside the cell: 400 | * - T=top; 401 | * - C=center; 402 | * - B=bottom; 403 | * - A=center-on-font-ascent; 404 | * - L=center-on-font-baseline; 405 | * - D=center-on-font-descent. 406 | * @param ?TCellDef $cell Optional to overwrite cell parameters for padding, margin etc. 407 | */ 408 | protected function textVPosFromCell( 409 | float $pnty, 410 | float $cellpheight, 411 | float $txtpheight = 0, 412 | string $align = 'C', 413 | ?array $cell = null 414 | ): float { 415 | return ($pnty - $this->cellTextVAlign($cellpheight, $txtpheight, $align, $cell)); 416 | } 417 | 418 | /** 419 | * Returns the left X coordinate of the text inside the cell. 420 | * 421 | * @param float $pntx Cell left X coordinate in internal points. 422 | * @param float $pwidth Cell width in internal points. 423 | * @param float $txtpwidth Text width in internal points. 424 | * @param string $align Text horizontal alignment inside the cell: L=left; C=center; R=right. 425 | * @param ?TCellDef $cell Optional to overwrite cell parameters for padding, margin etc. 426 | */ 427 | protected function textHPosFromCell( 428 | float $pntx, 429 | float $pwidth, 430 | float $txtpwidth, 431 | string $align = 'L', 432 | ?array $cell = null 433 | ): float { 434 | return ($pntx + $this->cellTextHAlign($pwidth, $txtpwidth, $align, $cell)); 435 | } 436 | 437 | /** 438 | * Calculates the maximum width available for a cell that fits the current region width. 439 | * 440 | * @param float $pntx Cell left X coordinate in internal points. 441 | * @param ?TCellDef $cell Optional to overwrite cell parameters for padding, margin etc. 442 | * 443 | * @return float 444 | */ 445 | protected function cellMaxWidth( 446 | float $pntx = 0, 447 | ?array $cell = null 448 | ): float { 449 | if ($cell === null) { 450 | $cell = $this->defcell; 451 | } 452 | 453 | $region = $this->page->getRegion(); 454 | return ($this->toPoints($region['RW']) - $pntx - $cell['margin']['L'] - $cell['margin']['R']); 455 | } 456 | 457 | /** 458 | * Calculates the maximum width available for text within a cell. 459 | * 460 | * @param float $pwidth Cell width in internal points. 461 | * @param ?TCellDef $cell Optional to overwrite cell parameters for padding, margin etc. 462 | * 463 | * @return float The maximum width available for text within the cell. 464 | */ 465 | protected function textMaxWidth( 466 | float $pwidth, 467 | ?array $cell = null 468 | ): float { 469 | if ($cell === null) { 470 | $cell = $this->defcell; 471 | } 472 | 473 | return ($pwidth - $cell['padding']['L'] - $cell['padding']['R']); 474 | } 475 | 476 | /** 477 | * Calculates the maximum height available for text within a cell. 478 | * 479 | * @param float $pheight Available vertical space in internal points. 480 | * @param string $align Text vertical alignment inside the cell: 481 | * - T=top; 482 | * - C=center; 483 | * - B=bottom; 484 | * - A=center-on-font-ascent; 485 | * - L=center-on-font-baseline; 486 | * - D=center-on-font-descent. 487 | * @param ?TCellDef $cell Optional to overwrite cell parameters for padding, margin etc. 488 | * 489 | * @return float The maximum width available for text within the cell. 490 | */ 491 | protected function textMaxHeight( 492 | float $pheight, 493 | string $align = 'T', 494 | ?array $cell = null 495 | ): float { 496 | if ($cell === null) { 497 | $cell = $this->defcell; 498 | } 499 | 500 | $curfont = $this->font->getCurrentFont(); 501 | $cph = ($pheight - $cell['margin']['T'] - $cell['margin']['B']); 502 | 503 | // Use a match expression to determine the maximum text height based on alignment. 504 | return match ($align) { 505 | // Top or Bottom 506 | 'T', 'B' => ($cph - $cell['padding']['T'] - $cell['padding']['B']), 507 | // Center on font Baseline 508 | 'L' => ($cph + $curfont['height'] - (2 * \max( 509 | ($cell['padding']['T'] + $curfont['ascent']), 510 | ($cell['padding']['B'] - $curfont['descent']) 511 | ))), 512 | // Center on font Ascent or Descent 513 | 'A', 'D' => ($cph 514 | + $curfont['height'] 515 | - (2 * ($curfont['height'] + \max($cell['padding']['T'], $cell['padding']['B'])))), 516 | // Default to Center 'C' case 517 | default => ($cph - (2 * \max($cell['padding']['T'], $cell['padding']['B']))), 518 | }; 519 | } 520 | 521 | /** 522 | * Returns the PDF code to draw the text cell border and background. 523 | * 524 | * @param float $pntx Cell left X coordinate in internal points. 525 | * @param float $pnty Cell top Y coordinate in internal points. 526 | * @param float $pwidth Cell width in internal points. 527 | * @param float $pheight Cell height in internal points. 528 | * @param array $styles Optional to overwrite the styles (see: getCurrentStyleArray). 529 | * @param ?TCellDef $cell Optional to overwrite cell parameters for padding, margin etc. 530 | * 531 | * @return string 532 | */ 533 | protected function drawCell( 534 | float $pntx, 535 | float $pnty, 536 | float $pwidth, 537 | float $pheight, 538 | array $styles = [], 539 | ?array $cell = null 540 | ) { 541 | 542 | $drawfill = (!empty($styles['all']['fillColor'])); 543 | $drawborder = ( 544 | !empty($styles['all']['lineWidth']) 545 | || !empty($styles[0]['lineWidth']) 546 | || !empty($styles[1]['lineWidth']) 547 | || !empty($styles[2]['lineWidth']) 548 | || !empty($styles[3]['lineWidth']) 549 | ); 550 | 551 | if (!$drawfill && !$drawborder) { 552 | return ''; 553 | } 554 | 555 | if ($cell === null) { 556 | $cell = $this->defcell; 557 | } 558 | 559 | $styleall = (empty($styles['all']) ? [] : $styles['all']); 560 | 561 | $out = $this->graph->getStartTransform(); 562 | $stoptr = $this->graph->getStopTransform(); 563 | 564 | if ( 565 | $drawfill 566 | && $drawborder 567 | && ($cell['borderpos'] == self::BORDERPOS_DEFAULT) 568 | && (\count($styles) <= 1) 569 | ) { 570 | // single default border style for all sides 571 | $out .= $this->graph->getBasicRect( 572 | $this->toUnit($pntx), 573 | $this->toYUnit($pnty), 574 | $this->toUnit($pwidth), 575 | $this->toUnit($pheight), 576 | 'b', // close, fill, and then stroke the path 577 | $styleall, 578 | ); 579 | 580 | return $out . $stoptr; 581 | } 582 | 583 | if ($drawfill) { 584 | $out .= $this->graph->getBasicRect( 585 | $this->toUnit($pntx), 586 | $this->toYUnit($pnty), 587 | $this->toUnit($pwidth), 588 | $this->toUnit($pheight), 589 | 'f', // fill the path 590 | $styleall, 591 | ); 592 | } 593 | 594 | if (!$drawborder) { 595 | return $out . $stoptr; 596 | } 597 | 598 | $adj = (isset($styles['all']['lineWidth']) 599 | ? $this->toPoints((float) $styles['all']['lineWidth'] * $cell['borderpos']) 600 | : 0); 601 | $adjx = (isset($styles['3']['lineWidth']) 602 | ? $this->toPoints((float) $styles['3']['lineWidth'] * $cell['borderpos']) 603 | : $adj); 604 | $adjy = isset($styles['0']['lineWidth']) 605 | ? $this->toPoints((float) $styles['0']['lineWidth'] * $cell['borderpos']) 606 | : $adj; 607 | $adjw = $adjx + (isset($styles['1']['lineWidth']) 608 | ? $this->toPoints((float) $styles['1']['lineWidth'] * $cell['borderpos']) 609 | : $adj); 610 | $adjh = $adjy + (isset($styles['2']['lineWidth']) 611 | ? $this->toPoints((float) $styles['2']['lineWidth'] * $cell['borderpos']) 612 | : $adj); 613 | 614 | // different border styles for each side 615 | $out .= $this->graph->getRect( 616 | $this->toUnit($pntx + $adjx), 617 | $this->toYUnit($pnty - $adjy), 618 | $this->toUnit($pwidth - $adjw), 619 | $this->toUnit($pheight - $adjh), 620 | 's', // close and stroke the path 621 | $styles, 622 | ); 623 | 624 | return $out . $stoptr; 625 | } 626 | 627 | /** 628 | * Format a text string for output. 629 | * 630 | * @param string $str String to escape. 631 | * @param int $oid Current PDF object number. 632 | * @param bool $bom If true set the Byte Order Mark (BOM). 633 | * 634 | * @return string escaped string. 635 | */ 636 | protected function getOutTextString( 637 | string $str, 638 | int $oid, 639 | bool $bom = false 640 | ): string { 641 | if ($this->isunicode) { 642 | $str = $this->uniconv->toUTF16BE($str); 643 | if ($bom) { 644 | $str = "\xFE\xFF" . $str; // Byte Order Mark (BOM) 645 | } 646 | } 647 | 648 | return $this->encrypt->escapeDataString($str, $oid); 649 | } 650 | } 651 | -------------------------------------------------------------------------------- /src/CSS.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2002-2025 Nicola Asuni - Tecnick.com LTD 11 | * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) 12 | * @link https://github.com/tecnickcom/tc-lib-pdf 13 | * 14 | * This file is part of tc-lib-pdf software library. 15 | */ 16 | 17 | namespace Com\Tecnick\Pdf; 18 | 19 | use Com\Tecnick\Pdf\Exception as PdfException; 20 | use Com\Tecnick\Color\Model as ColorModel; 21 | 22 | /** 23 | * Com\Tecnick\Pdf\CSS 24 | * 25 | * CSS PDF class 26 | * 27 | * @since 2002-08-03 28 | * @category Library 29 | * @package Pdf 30 | * @author Nicola Asuni 31 | * @copyright 2002-2025 Nicola Asuni - Tecnick.com LTD 32 | * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) 33 | * @link https://github.com/tecnickcom/tc-lib-pdf 34 | * 35 | * @phpstan-import-type TCellBound from \Com\Tecnick\Pdf\Base 36 | * @phpstan-import-type StyleData from \Com\Tecnick\Pdf\Graph\Base as BorderStyle 37 | * 38 | * @phpstan-type TCSSBorderSpacing array{ 39 | * 'H': float, 40 | * 'V': float, 41 | * } 42 | * 43 | * @SuppressWarnings("PHPMD.DepthOfInheritance") 44 | */ 45 | abstract class CSS extends \Com\Tecnick\Pdf\SVG 46 | { 47 | /** 48 | * Default values for cell boundaries. 49 | * 50 | * @const TCSSBorderSpacing 51 | */ 52 | protected const ZEROBORDERSPACE = [ 53 | 'H' => 0, 54 | 'V' => 0, 55 | ]; 56 | 57 | /** 58 | * Default CSS margin. 59 | * 60 | * @var TCellBound 61 | */ 62 | protected $defCSSCellMargin = self::ZEROCELLBOUND; 63 | 64 | /** 65 | * Default CSS padding. 66 | * 67 | * @var TCellBound 68 | */ 69 | protected $defCSSCellPadding = self::ZEROCELLBOUND; 70 | 71 | /** 72 | * Default CSS border space. 73 | * 74 | * @var TCSSBorderSpacing 75 | */ 76 | protected $defCSSBorderSpacing = self::ZEROBORDERSPACE; 77 | 78 | /** 79 | * Maximum value that can be represented in Roman notation. 80 | * 81 | * @var int 82 | */ 83 | protected const ROMAN_LIMIT = 3_999_999_999; 84 | 85 | /** 86 | * Maps Roman Vinculum symbols to number multipliers. 87 | * 88 | * @var array 89 | */ 90 | protected const ROMAN_VINCULUM = [ 91 | '\u{033F}' => 1_000_000, 92 | '\u{0305}' => 1_000, 93 | '' => 1, 94 | ]; 95 | 96 | /** 97 | * Maps Roman symbols to numbers. 98 | * 99 | * @var array 100 | */ 101 | protected const ROMAN_SYMBOL = [ 102 | // standard notation 103 | 'M' => 1_000, 104 | 'CM' => 900, 105 | 'D' => 500, 106 | 'CD' => 400, 107 | 'C' => 100, 108 | 'XC' => 90, 109 | 'L' => 50, 110 | 'XL' => 40, 111 | 'X' => 10, 112 | 'IX' => 9, 113 | 'V' => 5, 114 | 'IV' => 4, 115 | ]; 116 | 117 | /** 118 | * Set the default CSS margin in user units. 119 | * 120 | * @param float $top Top. 121 | * @param float $right Right. 122 | * @param float $bottom Bottom. 123 | * @param float $left Left. 124 | */ 125 | public function setDefaultCSSMargin( 126 | float $top, 127 | float $right, 128 | float $bottom, 129 | float $left 130 | ): void { 131 | $this->defCSSCellMargin['T'] = $this->toPoints($top); 132 | $this->defCSSCellMargin['R'] = $this->toPoints($right); 133 | $this->defCSSCellMargin['B'] = $this->toPoints($bottom); 134 | $this->defCSSCellMargin['L'] = $this->toPoints($left); 135 | } 136 | 137 | /** 138 | * Set the default CSS padding in user units. 139 | * 140 | * @param float $top Top. 141 | * @param float $right Right. 142 | * @param float $bottom Bottom. 143 | * @param float $left Left. 144 | */ 145 | public function setDefaultCSSPadding( 146 | float $top, 147 | float $right, 148 | float $bottom, 149 | float $left 150 | ): void { 151 | $this->defCSSCellPadding['T'] = $this->toPoints($top); 152 | $this->defCSSCellPadding['R'] = $this->toPoints($right); 153 | $this->defCSSCellPadding['B'] = $this->toPoints($bottom); 154 | $this->defCSSCellPadding['L'] = $this->toPoints($left); 155 | } 156 | 157 | /** 158 | * Set the default CSS border spacing in user units. 159 | * 160 | * @param float $horiz Horizontal space. 161 | * @param float $vert Vertical space. 162 | */ 163 | public function setDefaultCSSBorderSpacing( 164 | float $vert, 165 | float $horiz, 166 | ): void { 167 | $this->defCSSBorderSpacing['V'] = $this->toPoints($vert); 168 | $this->defCSSBorderSpacing['H'] = $this->toPoints($horiz); 169 | } 170 | 171 | /** 172 | * Returns the border width from CSS property. 173 | * 174 | * @param string $width border width. 175 | * 176 | * @return float width in internal points. 177 | */ 178 | protected function getCSSBorderWidthPoints(string $width): float 179 | { 180 | return match ($width) { 181 | '' => 0.0, 182 | 'thin' => 2.0, 183 | 'medium' => 4.0, 184 | 'thick' => 6.0, 185 | default => $this->getUnitValuePoints($width), 186 | }; 187 | } 188 | 189 | /** 190 | * Returns the border width from CSS property. 191 | * 192 | * @param string $width border width. 193 | * 194 | * @return float width in user units. 195 | */ 196 | protected function getCSSBorderWidth(string $width): float 197 | { 198 | return $this->toUnit($this->getCSSBorderWidthPoints($width)); 199 | } 200 | 201 | /** 202 | * Returns the border dash style from CSS property. 203 | * 204 | * @param string $style Border style to convert. 205 | * 206 | * @return int Border dash style (return -1 in case of none or hidden border). 207 | */ 208 | protected function getCSSBorderDashStyle(string $style): int 209 | { 210 | return match (\strtolower($style)) { 211 | 'none' => -1, 212 | 'hidden' => -1, 213 | 'dotted' => 1, 214 | 'dashed' => 3, 215 | 'double' => 0, 216 | 'groove' => 0, 217 | 'ridge' => 0, 218 | 'inset' => 0, 219 | 'outset' => 0, 220 | 'solid' => 0, 221 | default => 0, 222 | }; 223 | } 224 | 225 | /** 226 | * Returns the default CSS borer style. 227 | * 228 | * @return BorderStyle 229 | */ 230 | protected function getCSSDefaultBorderStyle(): array 231 | { 232 | return [ 233 | 'lineWidth' => 0, 234 | 'lineCap' => 'square', 235 | 'lineJoin' => 'miter', 236 | 'miterLimit' => $this->toUnit(10.0), 237 | 'dashArray' => [], 238 | 'dashPhase' => 0, 239 | 'lineColor' => 'black', 240 | 'fillColor' => '', 241 | ]; 242 | } 243 | 244 | /** 245 | * Returns the border style array from CSS border properties. 246 | * 247 | * @param string $cssborder border properties. 248 | * 249 | * @return BorderStyle border properties. 250 | */ 251 | protected function getCSSBorderStyle(string $cssborder): array 252 | { 253 | $border = $this->getCSSDefaultBorderStyle(); 254 | $bprop = \preg_split('/[\s]+/', \trim($cssborder)); 255 | if ($bprop === false) { 256 | return $border; 257 | } 258 | $count = \count($bprop); 259 | if (($count > 0) && ($bprop[$count - 1] === '!important')) { 260 | unset($bprop[$count - 1]); 261 | --$count; 262 | } 263 | switch ($count) { 264 | case 2: 265 | $width = 'medium'; 266 | $style = $bprop[0]; 267 | $color = $bprop[1]; 268 | break; 269 | case 1: 270 | $width = 'medium'; 271 | $style = $bprop[0]; 272 | $color = 'black'; 273 | break; 274 | case 0: 275 | $width = 'medium'; 276 | $style = 'solid'; 277 | $color = 'black'; 278 | break; 279 | default: 280 | $width = $bprop[0]; 281 | $style = $bprop[1]; 282 | $color = $bprop[2]; 283 | break; 284 | } 285 | if ($style == 'none') { 286 | return $border; 287 | } 288 | $dash = $this->getCSSBorderDashStyle($style); 289 | if ($dash < 0) { 290 | return $border; 291 | } 292 | $border['dashPhase'] = $dash; 293 | $border['lineWidth'] = $this->getCSSBorderWidth($width); 294 | $colobj = $this->color->getColorObj($color); 295 | $border['lineColor'] = empty($colobj) ? 'black' : $colobj->getCssColor(); 296 | return $border; 297 | } 298 | 299 | /** 300 | * Get the internal Cell padding from CSS attribute. 301 | * 302 | * @param string $csspadding padding properties. 303 | * @param float $width width of the containing element. 304 | * 305 | * @return TCellBound cell paddings. 306 | */ 307 | protected function getCSSPadding(string $csspadding, float $width = 0.0): array 308 | { 309 | /** @var TCellBound $cellpad */ 310 | $cellpad = $this->defCSSCellPadding; 311 | $pad = \preg_split('/[\s]+/', \trim($csspadding)); 312 | if ($pad === false) { 313 | return $cellpad; 314 | } 315 | switch (\count($pad)) { 316 | case 4: 317 | $cellpad['T'] = $pad[0]; 318 | $cellpad['R'] = $pad[1]; 319 | $cellpad['B'] = $pad[2]; 320 | $cellpad['L'] = $pad[3]; 321 | break; 322 | case 3: 323 | $cellpad['T'] = $pad[0]; 324 | $cellpad['R'] = $pad[1]; 325 | $cellpad['B'] = $pad[2]; 326 | $cellpad['L'] = $pad[1]; 327 | break; 328 | case 2: 329 | $cellpad['T'] = $pad[0]; 330 | $cellpad['R'] = $pad[1]; 331 | $cellpad['B'] = $pad[0]; 332 | $cellpad['L'] = $pad[1]; 333 | break; 334 | case 1: 335 | $cellpad['T'] = $pad[0]; 336 | $cellpad['R'] = $pad[0]; 337 | $cellpad['B'] = $pad[0]; 338 | $cellpad['L'] = $pad[0]; 339 | break; 340 | default: 341 | return $cellpad; 342 | } 343 | if ($width <= 0) { 344 | $region = $this->page->getRegion(); 345 | $width = $region['RW']; 346 | } 347 | $ref = self::REFUNITVAL; 348 | $ref['parent'] = $width; 349 | $cellpad['T'] = $this->toUnit($this->getUnitValuePoints($cellpad['T'], $ref)); 350 | $cellpad['R'] = $this->toUnit($this->getUnitValuePoints($cellpad['R'], $ref)); 351 | $cellpad['B'] = $this->toUnit($this->getUnitValuePoints($cellpad['B'], $ref)); 352 | $cellpad['L'] = $this->toUnit($this->getUnitValuePoints($cellpad['L'], $ref)); 353 | return $cellpad; 354 | } 355 | 356 | /** 357 | * Get the internal Cell margin from CSS attribute. 358 | * 359 | * @param string $cssmargin margin properties. 360 | * @param float $width width of the containing element. 361 | * 362 | * @return TCellBound cell margins. 363 | */ 364 | protected function getCSSMargin(string $cssmargin, float $width = 0.0): array 365 | { 366 | /** @var TCellBound $cellmrg */ 367 | $cellmrg = $this->defCSSCellMargin; 368 | $mrg = \preg_split('/[\s]+/', \trim($cssmargin)); 369 | if ($mrg === false) { 370 | return $cellmrg; 371 | } 372 | switch (\count($mrg)) { 373 | case 4: 374 | $cellmrg['T'] = $mrg[0]; 375 | $cellmrg['R'] = $mrg[1]; 376 | $cellmrg['B'] = $mrg[2]; 377 | $cellmrg['L'] = $mrg[3]; 378 | break; 379 | case 3: 380 | $cellmrg['T'] = $mrg[0]; 381 | $cellmrg['R'] = $mrg[1]; 382 | $cellmrg['B'] = $mrg[2]; 383 | $cellmrg['L'] = $mrg[1]; 384 | break; 385 | case 2: 386 | $cellmrg['T'] = $mrg[0]; 387 | $cellmrg['R'] = $mrg[1]; 388 | $cellmrg['B'] = $mrg[0]; 389 | $cellmrg['L'] = $mrg[1]; 390 | break; 391 | case 1: 392 | $cellmrg['T'] = $mrg[0]; 393 | $cellmrg['R'] = $mrg[0]; 394 | $cellmrg['B'] = $mrg[0]; 395 | $cellmrg['L'] = $mrg[0]; 396 | break; 397 | default: 398 | return $cellmrg; 399 | } 400 | if ($width <= 0) { 401 | $region = $this->page->getRegion(); 402 | $width = $region['RW']; 403 | } 404 | $cellmrg['T'] = \str_replace('auto', '0', $cellmrg['T']); 405 | $cellmrg['R'] = \str_replace('auto', '0', $cellmrg['R']); 406 | $cellmrg['B'] = \str_replace('auto', '0', $cellmrg['B']); 407 | $cellmrg['L'] = \str_replace('auto', '0', $cellmrg['L']); 408 | $ref = self::REFUNITVAL; 409 | $ref['parent'] = $width; 410 | $cellmrg['T'] = $this->toUnit($this->getUnitValuePoints($cellmrg['T'], $ref)); 411 | $cellmrg['R'] = $this->toUnit($this->getUnitValuePoints($cellmrg['R'], $ref)); 412 | $cellmrg['B'] = $this->toUnit($this->getUnitValuePoints($cellmrg['B'], $ref)); 413 | $cellmrg['L'] = $this->toUnit($this->getUnitValuePoints($cellmrg['L'], $ref)); 414 | return $cellmrg; 415 | } 416 | 417 | /** 418 | * Get the border-spacing from CSS attribute. 419 | * 420 | * @param string $cssbspace border-spacing CSS properties. 421 | * @param float $width width of the containing element. 422 | * 423 | * @return TCSSBorderSpacing of border spacings. 424 | */ 425 | protected function getCSSBorderMargin(string $cssbspace, float $width = 0.0): array 426 | { 427 | /** @var TCSSBorderSpacing $bsp */ 428 | $bsp = $this->defCSSBorderSpacing; 429 | $space = \preg_split('/[\s]+/', \trim($cssbspace)); 430 | if ($space === false) { 431 | return $bsp; 432 | } 433 | switch (\count($space)) { 434 | case 2: 435 | $bsp['H'] = $space[0]; 436 | $bsp['V'] = $space[1]; 437 | break; 438 | case 1: 439 | $bsp['H'] = $space[0]; 440 | $bsp['V'] = $space[0]; 441 | break; 442 | default: 443 | return $bsp; 444 | } 445 | if ($width <= 0) { 446 | $region = $this->page->getRegion(); 447 | $width = $region['RW']; 448 | } 449 | $ref = self::REFUNITVAL; 450 | $ref['parent'] = $width; 451 | $bsp['H'] = $this->toUnit($this->getUnitValuePoints($bsp['H'], $ref)); 452 | $bsp['V'] = $this->toUnit($this->getUnitValuePoints($bsp['V'], $ref)); 453 | return $bsp; 454 | } 455 | 456 | /** 457 | * Implode CSS data array into a single string. 458 | * 459 | * @param array $css array of CSS properties. 460 | * 461 | * @return string merged CSS properties. 462 | */ 463 | protected function implodeCSSData(array $css): string 464 | { 465 | $out = ''; 466 | foreach ($css as $style) { 467 | if (!\is_array($style) || empty($style['c']) || (!\is_string($style['c']))) { 468 | continue; 469 | } 470 | $csscmds = \explode(';', $style['c']); 471 | foreach ($csscmds as $cmd) { 472 | if (empty($cmd)) { 473 | continue; 474 | } 475 | $pos = \strpos($cmd, ':'); 476 | if ($pos === false) { 477 | continue; 478 | } 479 | $cmd = \substr($cmd, 0, ($pos + 1)); 480 | if (\strpos($out, $cmd) !== false) { 481 | // remove duplicate commands (last commands have high priority) 482 | $out = \preg_replace('/' . $cmd . '[^;]+/i', '', $out) ?? ''; 483 | } 484 | } 485 | $out .= ';' . $style['c']; 486 | } 487 | // remove multiple semicolons 488 | $out = \preg_replace('/[;]+/', ';', $out) ?? ''; 489 | return $out; 490 | } 491 | 492 | /** 493 | * Tidy up the CSS string by removing unsupported properties. 494 | * 495 | * @param string $css string containing CSS definitions. 496 | * 497 | * @return string 498 | */ 499 | protected function tidyCSS($css): string 500 | { 501 | if (empty($css)) { 502 | return ''; 503 | } 504 | // remove comments 505 | $css = \preg_replace('/\/\*[^\*]*\*\//', '', $css) ?? ''; 506 | // remove newlines and multiple spaces 507 | $css = \preg_replace('/[\s]+/', ' ', $css) ?? ''; 508 | // remove some spaces 509 | $css = \preg_replace('/[\s]*([;:\{\}]{1})[\s]*/', '\\1', $css) ?? ''; 510 | // remove empty blocks 511 | $css = \preg_replace('/([^\}\{]+)\{\}/', '', $css) ?? ''; 512 | // replace media type parenthesis 513 | $css = \preg_replace('/@media[\s]+([^\{]*)\{/i', '@media \\1§', $css) ?? ''; 514 | $css = \preg_replace('/\}\}/si', '}§', $css) ?? ''; 515 | // find media blocks (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv) 516 | $blk = []; 517 | $matches = []; 518 | if (\preg_match_all('/@media[\s]+([^\§]*)§([^§]*)§/i', $css, $matches) > 0) { 519 | foreach ($matches[1] as $key => $type) { 520 | $blk[$type] = $matches[2][$key]; 521 | } 522 | // remove media blocks 523 | $css = \preg_replace('/@media[\s]+([^\§]*)§([^§]*)§/i', '', $css) ?? ''; 524 | } 525 | // keep 'all' and 'print' media, other media types are discarded 526 | if (!empty($blk['all'])) { 527 | $css .= $blk['all']; 528 | } 529 | if (!empty($blk['print'])) { 530 | $css .= $blk['print']; 531 | } 532 | return \trim($css); 533 | } 534 | 535 | /** 536 | * Extracts the CSS properties from a CSS string. 537 | * 538 | * @param string $css string containing CSS definitions. 539 | * 540 | * @return array CSS properties. 541 | */ 542 | protected function extractCSSproperties($css): array 543 | { 544 | $css = $this->tidyCSS($css); 545 | if (empty($css)) { 546 | return []; 547 | } 548 | $blk = []; 549 | $matches = []; 550 | // explode css data string into array 551 | if (\substr($css, -1) == '}') { 552 | // remove last parethesis 553 | $css = \substr($css, 0, -1); 554 | } 555 | $matches = \explode('}', $css); 556 | foreach ($matches as $key => $block) { 557 | // index 0 contains the CSS selector, index 1 contains CSS properties 558 | $blk[$key] = \explode('{', $block); 559 | if (!isset($blk[$key][1])) { 560 | // remove empty definitions 561 | unset($blk[$key]); 562 | } 563 | } 564 | // split groups of selectors (comma-separated list of selectors) 565 | foreach ($blk as $key => $block) { 566 | if (\strpos($block[0], ',') > 0) { 567 | $selectors = \explode(',', $block[0]); 568 | foreach ($selectors as $sel) { 569 | $blk[] = [0 => \trim($sel), 1 => $block[1]]; 570 | } 571 | unset($blk[$key]); 572 | } 573 | } 574 | // covert array to selector => properties 575 | $out = []; 576 | foreach ($blk as $block) { 577 | $selector = $block[0]; 578 | // calculate selector's specificity 579 | $matches = []; 580 | $sta = 0; // the declaration is not from is a 'style' attribute 581 | // number of ID attributes 582 | $stb = \intval(\preg_match_all('/[\#]/', $selector, $matches)); 583 | // number of other attributes 584 | $stc = \intval(\preg_match_all('/[\[\.]/', $selector, $matches)); 585 | // number of pseudo-classes 586 | $stc += \intval(\preg_match_all( 587 | '/[\:]link|visited|hover|active|focus|target|lang|enabled|disabled' 588 | . '|checked|indeterminate|root|nth|first|last|only|empty|contains|not/i', 589 | $selector, 590 | $matches, 591 | )); 592 | // number of element names 593 | $std = \intval(\preg_match_all('/[\>\+\~\s]{1}[a-zA-Z0-9]+/', " $selector", $matches)); 594 | // number of pseudo-elements 595 | $std += \intval(\preg_match_all('/[\:][\:]/', $selector, $matches)); 596 | $specificity = $sta . $stb . $stc . $std; 597 | // add specificity to the beginning of the selector 598 | $out["$specificity $selector"] = $block[1]; 599 | } 600 | // sort selectors alphabetically to account for specificity 601 | \ksort($out, SORT_STRING); 602 | return $out; 603 | } 604 | 605 | /** 606 | * Returns the Roman representation of an integer number. 607 | * Roman standard notation can represent numbers up to 3,999. 608 | * For bigger numbers, up to two layers of the "vinculum" notation 609 | * are used for a max value of 3,999,999,999. 610 | * 611 | * @param int $num number to convert. 612 | * 613 | * @return string roman representation of the specified number. 614 | */ 615 | protected function intToRoman(int $num): string 616 | { 617 | if ($num > self::ROMAN_LIMIT) { 618 | return \strval($num); 619 | } 620 | $rmn = ''; 621 | foreach (self::ROMAN_VINCULUM as $sfx => $mul) { 622 | foreach (self::ROMAN_SYMBOL as $sym => $val) { 623 | $limit = (int)($mul * $val); 624 | while ($num >= $limit) { 625 | $rmn .= $sym[0] . $sfx . (!empty($sym[1]) ? $sym[1] . $sfx : ''); 626 | $num -= $limit; 627 | } 628 | } 629 | } 630 | while ($num >= 1) { 631 | $rmn .= 'I'; 632 | $num--; 633 | } 634 | return $rmn; 635 | } 636 | 637 | /** 638 | * Reverse function for htmlentities. 639 | * 640 | * @param string $text_to_convert Text to convert. 641 | * 642 | * @return string converted text string 643 | */ 644 | protected function unhtmlentities(string $text_to_convert): string 645 | { 646 | return \html_entity_decode($text_to_convert, ENT_QUOTES, $this->encoding); 647 | } 648 | 649 | /** 650 | * Extract CSS styles from an HTML string. 651 | * 652 | * @param string $html HTML string to parse. 653 | * 654 | * @return array CSS styles (selector => properties). 655 | */ 656 | protected function getCSSArrayFromHTML(string &$html): array 657 | { 658 | /** @var array $css */ 659 | $css = []; 660 | 661 | $matches = []; 662 | if (\preg_match_all('/([^\<]*?)<\/cssarray>/is', $html, $matches) > 0) { 663 | if (isset($matches[1][0])) { 664 | $dcss = \json_decode( 665 | $this->unhtmlentities($matches[1][0]), 666 | true, 667 | ); 668 | if (\is_array($dcss)) { 669 | $css = $dcss; 670 | } 671 | } 672 | $html = \preg_replace('/(.*?)<\/cssarray>/is', '', $html) ?? ''; 673 | } 674 | 675 | // extract external CSS files 676 | $matches = []; 677 | if (\preg_match_all('/]*?)>/is', $html, $matches) > 0) { 678 | foreach ($matches[1] as $key => $link) { 679 | $type = []; 680 | if (\preg_match('/type[\s]*=[\s]*"text\/css"/', $link, $type) > 0) { 681 | $type = []; 682 | if (\preg_match('/media[\s]*=[\s]*"([^"]*)"/', $link, $type) > 0) { 683 | // get 'all' and 'print' media, other media types are discarded 684 | // (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv) 685 | if (!empty($type[1]) && (($type[1] == 'all') || ($type[1] == 'print'))) { 686 | $type = []; 687 | if (\preg_match('/href[\s]*=[\s]*"([^"]*)"/', $link, $type) > 0) { 688 | // read CSS data file 689 | $cssdata = $this->file->getFileData(\trim($type[1])); 690 | if (($cssdata !== false) && (\strlen($cssdata) > 0)) { 691 | $css = \array_merge($css, $this->extractCSSproperties($cssdata)); 692 | } 693 | } 694 | } 695 | } 696 | } 697 | } 698 | } 699 | 700 | // extract style tags 701 | $matches = []; 702 | if (\preg_match_all('/]*?)>([^\<]*?)<\/style>/is', $html, $matches) > 0) { 703 | foreach ($matches[1] as $key => $media) { 704 | $type = []; 705 | if (\preg_match('/media[\s]*=[\s]*"([^"]*)"/', $media, $type) > 0) { 706 | // get 'all' and 'print' media, other media types are discarded 707 | // (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv) 708 | if (!empty($type[1]) && (($type[1] == 'all') || ($type[1] == 'print'))) { 709 | $cssdata = $matches[2][$key]; 710 | $css = \array_merge($css, $this->extractCSSproperties($cssdata)); 711 | } 712 | } 713 | } 714 | } 715 | 716 | return $css; // @phpstan-ignore return.type 717 | } 718 | 719 | /** 720 | * Parse and normalize CSS color. 721 | * 722 | * @param string $color CSS color string to parse. 723 | * 724 | * @return string CSS color representation. 725 | */ 726 | protected function getCSSColor(string $color): string 727 | { 728 | $colobj = $this->color->getColorObj($color); 729 | if ($colobj === null) { 730 | return ''; 731 | } 732 | return $colobj->getCssColor(); 733 | } 734 | } 735 | -------------------------------------------------------------------------------- /src/Tcpdf.php: -------------------------------------------------------------------------------- 1 | 10 | * @copyright 2002-2025 Nicola Asuni - Tecnick.com LTD 11 | * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) 12 | * @link https://github.com/tecnickcom/tc-lib-pdf 13 | * 14 | * This file is part of tc-lib-pdf software library. 15 | */ 16 | 17 | namespace Com\Tecnick\Pdf; 18 | 19 | use Com\Tecnick\Barcode\Exception as BarcodeException; 20 | use Com\Tecnick\Pdf\Encrypt\Encrypt as ObjEncrypt; 21 | use Com\Tecnick\Pdf\Exception as PdfException; 22 | 23 | /** 24 | * Com\Tecnick\Pdf\Tcpdf 25 | * 26 | * Tcpdf PDF class 27 | * 28 | * @since 2002-08-03 29 | * @category Library 30 | * @package Pdf 31 | * @author Nicola Asuni 32 | * @copyright 2002-2025 Nicola Asuni - Tecnick.com LTD 33 | * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) 34 | * @link https://github.com/tecnickcom/tc-lib-pdf 35 | * 36 | * @phpstan-import-type StyleDataOpt from \Com\Tecnick\Pdf\Graph\Base 37 | * @phpstan-import-type PageData from \Com\Tecnick\Pdf\Page\Box 38 | * @phpstan-import-type PageInputData from \Com\Tecnick\Pdf\Page\Box 39 | * @phpstan-import-type TFontMetric from \Com\Tecnick\Pdf\Font\Stack 40 | * 41 | * @phpstan-import-type TSignature from Output 42 | * @phpstan-import-type TSignTimeStamp from Output 43 | * @phpstan-import-type TUserRights from Output 44 | * 45 | * @SuppressWarnings("PHPMD.DepthOfInheritance") 46 | */ 47 | class Tcpdf extends \Com\Tecnick\Pdf\ClassObjects 48 | { 49 | /** 50 | * Initialize a new PDF object. 51 | * 52 | * @param string $unit Unit of measure ('pt', 'mm', 'cm', 'in'). 53 | * @param bool $isunicode True if the document is in Unicode mode. 54 | * @param bool $subsetfont If true subset the embedded fonts to remove the unused characters. 55 | * @param bool $compress Set to false to disable stream compression. 56 | * @param string $mode PDF mode: "pdfa1", "pdfa2", "pdfa3", "pdfx" or empty. 57 | * @param ?ObjEncrypt $objEncrypt Encryption object. 58 | */ 59 | public function __construct( 60 | string $unit = 'mm', 61 | bool $isunicode = true, 62 | bool $subsetfont = false, 63 | bool $compress = true, 64 | string $mode = '', 65 | ?ObjEncrypt $objEncrypt = null 66 | ) { 67 | $this->setDecimalSeparator(); 68 | $this->doctime = \time(); 69 | $this->docmodtime = $this->doctime; 70 | $seed = new \Com\Tecnick\Pdf\Encrypt\Type\Seed(); 71 | $this->fileid = \md5($seed->encrypt('TCPDF')); 72 | $this->setPDFFilename($this->fileid . '.pdf'); 73 | $this->unit = $unit; 74 | $this->setUnicodeMode($isunicode); 75 | $this->subsetfont = $subsetfont; 76 | $this->setPDFMode($mode); 77 | $this->setCompressMode($compress); 78 | $this->setPDFVersion(); 79 | $this->initClassObjects($objEncrypt); 80 | } 81 | 82 | /** 83 | * Set the pdf mode. 84 | * 85 | * @param string $mode Input PDFA mode. 86 | */ 87 | protected function setPDFMode(string $mode): void 88 | { 89 | $this->pdfx = ($mode == 'pdfx'); 90 | $this->pdfa = 0; 91 | $matches = ['', '0']; 92 | if (\preg_match('/^pdfa([1-3])$/', $mode, $matches) === 1) { 93 | $this->pdfa = (int) $matches[1]; 94 | } 95 | } 96 | 97 | /** 98 | * Set the compression mode. 99 | * 100 | * @param bool $compress Set to false to disable stream compression. 101 | */ 102 | protected function setCompressMode(bool $compress): void 103 | { 104 | $this->compress = (($compress) && ($this->pdfa != 3)); 105 | } 106 | 107 | /** 108 | * Set the decimal separator. 109 | * 110 | * @throw PdfException in case of error. 111 | */ 112 | protected function setDecimalSeparator(): void 113 | { 114 | // check for locale-related bug 115 | if (1.1 == 1) { /* @phpstan-ignore-line */ 116 | throw new PdfException("Don't alter the locale before including class file"); 117 | } 118 | 119 | // check for decimal separator 120 | if (\sprintf('%.1F', 1.0) != '1.0') { 121 | \setlocale(LC_NUMERIC, 'C'); 122 | } 123 | } 124 | 125 | /** 126 | * Set the decimal separator. 127 | * 128 | * @param bool $isunicode True when using Unicode mode. 129 | */ 130 | protected function setUnicodeMode(bool $isunicode): void 131 | { 132 | $this->isunicode = $isunicode; 133 | // check if PCRE Unicode support is enabled 134 | if ($this->isunicode && (@\preg_match('/\pL/u', 'a') == 1)) { 135 | $this->setSpaceRegexp('/(?!\xa0)[\s\p{Z}]/u'); 136 | return; 137 | } 138 | 139 | // PCRE unicode support is turned OFF 140 | $this->setSpaceRegexp('/[^\S\xa0]/'); 141 | } 142 | 143 | /** 144 | * Set the pdf document base file name. 145 | * If the file extension is present, it must be '.pdf' or '.PDF'. 146 | * 147 | * @param string $name File name. 148 | */ 149 | public function setPDFFilename(string $name): void 150 | { 151 | $bname = \basename($name); 152 | if (\preg_match('/^[\w,\s-]+(\.pdf)?$/i', $bname) === 1) { 153 | $this->pdffilename = $bname; 154 | $this->encpdffilename = \rawurlencode($bname); 155 | } 156 | } 157 | 158 | /** 159 | * Set regular expression to detect withespaces or word separators. 160 | * The pattern delimiter must be the forward-slash character "/". 161 | * Some example patterns are: 162 | *
163 |      * Non-Unicode or missing PCRE unicode support: "/[^\S\xa0]/"
164 |      * Unicode and PCRE unicode support: "/(?!\xa0)[\s\p{Z}]/u"
165 |      * Unicode and PCRE unicode support in Chinese mode: "/(?!\xa0)[\s\p{Z}\p{Lo}]/u"
166 |      * if PCRE unicode support is turned ON ("\P" is the negate class of "\p"):
167 |      *      \s     : any whitespace character
168 |      *      \p{Z}  : any separator
169 |      *      \p{Lo} : Unicode letter or ideograph that does not have lowercase and uppercase variants.
170 |      *      \xa0   : Unicode Character 'NO-BREAK SPACE' (U+00A0)
171 |      * 
172 | * 173 | * @param string $regexp regular expression (leave empty for default). 174 | */ 175 | public function setSpaceRegexp(string $regexp = '/[^\S\xa0]/'): void 176 | { 177 | $parts = \explode('/', $regexp); 178 | $this->spaceregexp = [ 179 | 'r' => $regexp, 180 | 'p' => (empty($parts[1]) ? '[\s]' : $parts[1]), 181 | 'm' => (empty($parts[2]) ? '' : $parts[2]), 182 | ]; 183 | } 184 | 185 | /** 186 | * Defines the way the document is to be displayed by the viewer. 187 | * 188 | * @param int|string $zoom The zoom to use. 189 | * It can be one of the following string values or a number indicating the 190 | * zooming factor to use. 191 | * * fullpage: displays the entire page on screen * fullwidth: uses 192 | * maximum width of window 193 | * * real: uses real size (equivalent to 100% zoom) * default: uses 194 | * viewer default mode 195 | * @param string $layout The page layout. Possible values are: 196 | * * SinglePage Display one page at a time 197 | * * OneColumn Display the pages in one column 198 | * * TwoColumnLeft Display the pages in two columns, 199 | * with odd-numbered pages on the left 200 | * * TwoColumnRight Display the pages in 201 | * two columns, with odd-numbered pages 202 | * on the right 203 | * * TwoPageLeft Display the pages two at a time, 204 | * with odd-numbered pages on the left 205 | * * TwoPageRight Display the pages two at a time, 206 | * with odd-numbered pages on the right 207 | * @param string $mode A name object specifying how the document should be displayed when opened: 208 | * * UseNone Neither document outline nor thumbnail images visible 209 | * * UseOutlines Document outline visible 210 | * * UseThumbs Thumbnail images visible 211 | * * FullScreen Full screen, with no menu bar, window controls, 212 | * or any other window visible 213 | * * UseOC (PDF 1.5) Optional content group panel visible 214 | * * UseAttachments (PDF 1.6) Attachments panel visible 215 | */ 216 | public function setDisplayMode( 217 | int|string $zoom = 'default', 218 | string $layout = 'SinglePage', 219 | string $mode = 'UseNone' 220 | ): static { 221 | $this->display['zoom'] = (\is_numeric($zoom) || \in_array($zoom, $this::VALIDZOOM)) ? $zoom : 'default'; 222 | $this->display['layout'] = $this->page->getLayout($layout); 223 | $this->display['page'] = $this->page->getDisplay($mode); 224 | return $this; 225 | } 226 | 227 | // ===| BARCODE |======================================================= 228 | 229 | 230 | /** 231 | * Get a barcode PDF code. 232 | * 233 | * @param string $type Barcode type. 234 | * @param string $code Barcode content. 235 | * @param float $posx Abscissa of upper-left corner. 236 | * @param float $posy Ordinate of upper-left corner. 237 | * @param int $width Barcode width in user units (excluding padding). 238 | * A negative value indicates the multiplication 239 | * factor for each column. 240 | * @param int $height Barcode height in user units (excluding padding). 241 | * A negative value indicates the multiplication 242 | * factor for each row. 243 | * @param array{int, int, int, int} $padding Additional padding to add around the barcode 244 | * (top, right, bottom, left) in user units. A 245 | * negative value indicates the multiplication 246 | * factor for each row or column. 247 | * @param StyleDataOpt $style Array of style options. 248 | * 249 | * @throws BarcodeException in case of error 250 | */ 251 | public function getBarcode( 252 | string $type, 253 | string $code, 254 | float $posx = 0, 255 | float $posy = 0, 256 | int $width = -1, 257 | int $height = -1, 258 | array $padding = [0, 0, 0, 0], 259 | array $style = [] 260 | ): string { 261 | $model = $this->barcode->getBarcodeObj($type, $code, $width, $height, 'black', $padding); 262 | $bars = $model->getBarsArrayXYWH(); 263 | $out = ''; 264 | $out .= $this->graph->getStartTransform(); 265 | $out .= $this->graph->getStyleCmd($style); 266 | foreach ($bars as $bar) { 267 | $out .= $this->graph->getBasicRect(($posx + $bar[0]), ($posy + $bar[1]), $bar[2], $bar[3], 'f'); 268 | } 269 | 270 | return $out . $this->graph->getStopTransform(); 271 | } 272 | 273 | // ===| SIGNATURE |===================================================== 274 | 275 | /** 276 | * Set User's Rights for the PDF Reader. 277 | * WARNING: This is experimental and currently doesn't work because requires a private key. 278 | * Check the PDF Reference 8.7.1 Transform Methods, 279 | * Table 8.105 Entries in the UR transform parameters dictionary. 280 | * 281 | * @param TUserRights $rights User rights: 282 | * - annots (string) Names specifying additional annotation-related usage rights for the document. 283 | * Valid names in PDF 1.5 and later are /Create/Delete/Modify/Copy/Import/Export, which permit 284 | * the user to perform the named operation on annotations. 285 | * - document (string) Names specifying additional document-wide usage rights for the document. 286 | * The only defined value is "/FullSave", which permits a user to save the document along with 287 | * modified form and/or annotation data. 288 | * - ef (string) Names specifying additional usage rights for named embedded files in the document. 289 | * Valid names are /Create/Delete/Modify/Import, which permit the user to perform the named 290 | * operation on named embedded files Names specifying additional embedded-files-related usage 291 | * rights for the document. 292 | * - enabled (bool) If true enable user's rights on PDF reader. 293 | * - form (string) Names specifying additional form-field-related usage rights for the document. 294 | * Valid names are: /Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate. 295 | * - formex (string) Names specifying additional form-field-related usage rights. The only valid 296 | * name is BarcodePlaintext, which permits text form field data to be encoded as a plaintext 297 | * two-dimensional barcode. 298 | * - signature (string) Names specifying additional signature-related usage rights for the document. 299 | * The only defined value is /Modify, which permits a user to apply a digital signature to an 300 | * existing signature form field or clear a signed signature form field. 301 | */ 302 | public function setUserRights(array $rights): void 303 | { 304 | $this->userrights = \array_merge($this->userrights, $rights); 305 | } 306 | 307 | /** 308 | * Enable document signature (requires the OpenSSL Library). 309 | * The digital signature improve document authenticity and integrity and allows 310 | * to enable extra features on PDF Reader. 311 | * 312 | * To create self-signed signature: 313 | * openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt 314 | * To export crt to p12: 315 | * openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12 316 | * To convert pfx certificate to pem: 317 | * openssl pkcs12 -in tcpdf.pfx -out tcpdf.crt -nodes 318 | * 319 | * @param TSignature $data Signature data: 320 | * - appearance (array) Signature appearance. 321 | * - empty (bool) Array of empty signatures: 322 | * - objid (int) Object id. 323 | * - name (string) Name of the signature field. 324 | * - page (int) Page number. 325 | * - rect (array) Rectangle of the signature field. 326 | * - name (string) Name of the signature field. 327 | * - page (int) Page number. 328 | * - rect (array) Rectangle of the signature field. 329 | * - approval (bool) Enable approval signature eg. for PDF incremental update. 330 | * - cert_type (int) The access permissions granted for this document. Valid values shall be: 331 | * 1 = No changes to the document shall be permitted; 332 | * any change to the document shall invalidate the signature; 333 | * 2 = Permitted changes shall be filling in forms, instantiating page templates, and signing; 334 | * other changes shall invalidate the signature; 335 | * 3 = Permitted changes shall be the same as for 2, as well as annotation creation, 336 | * deletion, and modification; 337 | * other changes shall invalidate the signature. 338 | * - extracerts (string) Specifies the name of a file containing a bunch of extra certificates 339 | * to include in the signature 340 | * which can for example be used to help the recipient to verify the certificate that you used. 341 | * - info (array) Optional information. 342 | * - ContactInfo (string) 343 | * - Location (string) 344 | * - Name (string) 345 | * - Reason (string) 346 | * - password (string) 347 | * - privkey (string) Private key (string or filename prefixed with 'file://'). 348 | * - signcert (string) Signing certificate (string or filename prefixed with 'file://'). 349 | */ 350 | public function setSignature(array $data): void 351 | { 352 | $this->signature = \array_merge($this->signature, $data); 353 | 354 | if (empty($this->signature['signcert'])) { 355 | throw new PdfException('Invalid signing certificate (signcert)'); 356 | } 357 | 358 | if (empty($this->signature['privkey'])) { 359 | $this->signature['privkey'] = $this->signature['signcert']; 360 | } 361 | 362 | ++$this->pon; 363 | $this->objid['signature'] = $this->pon; // Signature widget annotation object id. 364 | ++$this->pon; // Signature appearance object id ($this->objid['signature'] + 1). 365 | 366 | $this->setSignAnnotRefs(); 367 | 368 | $this->sign = true; 369 | } 370 | 371 | 372 | /** 373 | * Enable or disable the the Signature Approval 374 | * 375 | * @param bool $enabled It true enable the Signature Approval 376 | */ 377 | protected function enableSignatureApproval(bool $enabled = true): static 378 | { 379 | $this->sigapp = $enabled; 380 | $this->page->enableSignatureApproval($this->sigapp); 381 | return $this; 382 | } 383 | 384 | /** 385 | * Set the signature timestamp. 386 | * 387 | * @param TSignTimeStamp $data Signature timestamp data: 388 | * - enabled (bool) If true enable timestamp signature. 389 | * - host (string) Time Stamping Authority (TSA) server (prefixed with 'https://') 390 | * - username (string) TSA username or authorization PEM file. 391 | * - password (string) TSA password. 392 | * - cert (string) cURL optional location of TSA certificate for authorization. 393 | */ 394 | public function setSignTimeStamp(array $data): void 395 | { 396 | $this->sigtimestamp = \array_merge($this->sigtimestamp, $data); 397 | 398 | if ($this->sigtimestamp['enabled'] && empty($this->sigtimestamp['host'])) { 399 | throw new PdfException('Invalid TSA host'); 400 | } 401 | } 402 | 403 | /** 404 | * Get a signature appearance (page and rectangle coordinates). 405 | * 406 | * @param float $posx Abscissa of the upper-left corner. 407 | * @param float $posy Ordinate of the upper-left corner. 408 | * @param float $width Width of the signature area. 409 | * @param float $heigth Height of the signature area. 410 | * @param int $page Page number (pid). 411 | * @param string $name Name of the signature. 412 | * 413 | * @return array{ 414 | * 'name': string, 415 | * 'page': int, 416 | * 'rect': string, 417 | * } Array defining page and rectangle coordinates of signature appearance. 418 | */ 419 | protected function getSignatureAppearanceArray( 420 | float $posx = 0, 421 | float $posy = 0, 422 | float $width = 0, 423 | float $heigth = 0, 424 | int $page = -1, 425 | string $name = '' 426 | ): array { 427 | $sigapp = []; 428 | 429 | $sigapp['page'] = ($page < 0) ? $this->page->getPageID() : $page; 430 | $sigapp['name'] = (empty($name)) ? 'Signature' : $name; 431 | 432 | $pntx = $this->toPoints($posx); 433 | $pnty = $this->toYUnit(($posy + $heigth), $this->page->getPage($sigapp['page'])['pheight']); 434 | $pntw = $this->toPoints($width); 435 | $pnth = $this->toPoints($heigth); 436 | 437 | $sigapp['rect'] = \sprintf('%F %F %F %F', $pntx, $pnty, ($pntx + $pntw), ($pnty + $pnth)); 438 | 439 | return $sigapp; 440 | } 441 | 442 | /** 443 | * Set the digital signature appearance (a cliccable rectangle area to get signature properties). 444 | * 445 | * @param float $posx Abscissa of the upper-left corner. 446 | * @param float $posy Ordinate of the upper-left corner. 447 | * @param float $width Width of the signature area. 448 | * @param float $heigth Height of the signature area. 449 | * @param int $page option page number (if < 0 the current page is used). 450 | * @param string $name Name of the signature. 451 | */ 452 | public function setSignatureAppearance( 453 | float $posx = 0, 454 | float $posy = 0, 455 | float $width = 0, 456 | float $heigth = 0, 457 | int $page = -1, 458 | string $name = '' 459 | ): void { 460 | $data = $this->getSignatureAppearanceArray($posx, $posy, $width, $heigth, $page, $name); 461 | $this->signature['appearance']['page'] = $data['page']; 462 | $this->signature['appearance']['name'] = $data['name']; 463 | $this->signature['appearance']['rect'] = $data['rect']; 464 | $this->setSignAnnotRefs(); 465 | } 466 | 467 | /** 468 | * Add an empty digital signature appearance (a cliccable rectangle area to get signature properties). 469 | * 470 | * @param float $posx Abscissa of the upper-left corner. 471 | * @param float $posy Ordinate of the upper-left corner. 472 | * @param float $width Width of the signature area. 473 | * @param float $heigth Height of the signature area. 474 | * @param int $page option page number (if < 0 the current page is used). 475 | * @param string $name Name of the signature. 476 | */ 477 | public function addEmptySignatureAppearance( 478 | float $posx = 0, 479 | float $posy = 0, 480 | float $width = 0, 481 | float $heigth = 0, 482 | int $page = -1, 483 | string $name = '' 484 | ): void { 485 | ++$this->pon; 486 | $data = $this->getSignatureAppearanceArray($posx, $posy, $width, $heigth, $page, $name); 487 | $this->signature['appearance']['empty'][] = [ 488 | 'objid' => $this->pon, 489 | 'name' => $data['name'], 490 | 'page' => $data['page'], 491 | 'rect' => $data['rect'], 492 | ]; 493 | $this->setSignAnnotRefs(); 494 | } 495 | 496 | /* 497 | * Set the signature annotation references. 498 | */ 499 | protected function setSignAnnotRefs(): void 500 | { 501 | if (empty($this->objid['signature'])) { 502 | return; 503 | } 504 | 505 | if (!empty($this->signature['appearance']['page'])) { 506 | $this->page->addAnnotRef($this->objid['signature'], $this->signature['appearance']['page']); 507 | } 508 | 509 | if (empty($this->signature['appearance']['empty'])) { 510 | return; 511 | } 512 | 513 | foreach ($this->signature['appearance']['empty'] as $esa) { 514 | $this->page->addAnnotRef($esa['objid'], $esa['page']); 515 | } 516 | } 517 | 518 | // ===| LAYERS |======================================================== 519 | 520 | /** 521 | * Creates and return a new PDF Layer. 522 | * 523 | * @param string $name Layer name (only a-z letters and numbers). Leave empty for automatic name. 524 | * @param array{'view'?: bool, 'design'?: bool} $intent intended use of the graphics in the layer. 525 | * @param bool $print Set the printability of the layer. 526 | * @param bool $view Set the visibility of the layer. 527 | * @param bool $lock Set the lock state of the layer. 528 | * 529 | * @return string 530 | */ 531 | public function newLayer( 532 | string $name = '', 533 | array $intent = [], 534 | bool $print = true, 535 | bool $view = true, 536 | bool $lock = true, 537 | ): string { 538 | $layer = \sprintf('LYR%03d', (\count($this->pdflayer) + 1)); 539 | $name = \preg_replace('/[^a-zA-Z0-9_\-]/', '', $name); 540 | if (empty($name)) { 541 | $name = $layer; 542 | } 543 | 544 | $intarr = []; 545 | if (!empty($intent['view'])) { 546 | $intarr[] = '/View'; 547 | } 548 | if (!empty($intent['design'])) { 549 | $intarr[] = '/Design'; 550 | } 551 | 552 | $this->pdflayer[] = array( 553 | 'layer' => $layer, 554 | 'name' => $name, 555 | 'intent' => \implode(' ', $intarr), 556 | 'print' => $print, 557 | 'view' => $view, 558 | 'lock' => $lock, 559 | 'objid' => 0, 560 | ); 561 | 562 | return ' /OC /' . $layer . ' BDC' . "\n"; 563 | } 564 | 565 | public function closeLayer(): string 566 | { 567 | return 'EMC' . "\n"; 568 | } 569 | 570 | // ===| TOC |=========================================================== 571 | 572 | /** 573 | * Add a Table of Contents (TOC) to the document. 574 | * The bookmars are created via the setBookmark() method. 575 | * 576 | * @param int $page Page number. 577 | * @param float $posx Abscissa of the upper-left corner. 578 | * @param float $posy Ordinate of the upper-left corner. 579 | * @param float $width Width of the signature area. 580 | * @param bool $rtl Right-To-Left - If true prints the TOC in RTL mode. 581 | * @param StyleDataOpt $linestyle Line style for the space filler. 582 | * 583 | * @return void 584 | */ 585 | public function addTOC( 586 | int $page = -1, 587 | float $posx = 0, 588 | float $posy = 0, 589 | float $width = 0, 590 | bool $rtl = false, 591 | array $linestyle = [ 592 | 'lineWidth' => 0.3, 593 | 'lineCap' => 'butt', 594 | 'lineJoin' => 'miter', 595 | 'dashArray' => [1,1], 596 | 'dashPhase' => 0, 597 | 'lineColor' => 'gray', 598 | 'fillColor' => '', 599 | ], 600 | ): void { 601 | if (empty($width) || $width < 0) { 602 | $width = $this->page->getRegion()['RW']; 603 | } 604 | 605 | $curfont = $this->font->getCurrentFont(); 606 | 607 | // width to accomodate the number (max 9 digits space). 608 | $chrw = $this->toUnit($curfont['cw'][48] ?? $curfont['dw']); // 48 ASCII = '0'. 609 | $indent = 2 * $chrw; // each level is indented by 2 characters. 610 | $numwidth = 9 * $chrw; // maximum 9 digits to print the page number. 611 | $txtwidth = ($width - $numwidth); 612 | 613 | $cellSpaceT = $this->toUnit( 614 | $this->defcell['margin']['T'] + 615 | $this->defcell['padding']['T'] 616 | ); 617 | $cellSpaceB = $this->toUnit( 618 | $this->defcell['margin']['B'] + 619 | $this->defcell['padding']['B'] 620 | ); 621 | $cellSpaceH = $chrw + $this->toUnit( 622 | $this->defcell['margin']['L'] + 623 | $this->defcell['margin']['L'] + 624 | $this->defcell['padding']['R'] + 625 | $this->defcell['padding']['R'] 626 | ); 627 | 628 | $aligntext = 'L'; 629 | $alignnum = 'R'; 630 | $txt_posx = $posx; 631 | $num_posx = $posx + $txtwidth; 632 | if ($rtl) { 633 | $aligntext = 'R'; 634 | $alignnum = 'L'; 635 | $txt_posx = $posx + $numwidth; 636 | $num_posx = $posx; 637 | } 638 | 639 | $pid = ($page < 0) ? $this->page->getPageID() : $page; 640 | 641 | foreach ($this->outlines as $bmrk) { 642 | $font = $this->font->cloneFont( 643 | $this->pon, 644 | $curfont['idx'], 645 | $bmrk['s'] . (($bmrk['l'] == 0) ? 'B' : ''), 646 | (int) \round($curfont['size'] - $bmrk['l']), 647 | $curfont['spacing'], 648 | $curfont['stretching'], 649 | ); 650 | 651 | $region = $this->page->getRegion($pid); 652 | 653 | if (($posy + $cellSpaceT + $cellSpaceB + $font['height']) > $region['RH']) { 654 | $this->page->getNextRegion($pid); 655 | $curpid = $this->page->getPageId(); 656 | if ($curpid > $pid) { 657 | $pid = $curpid; 658 | $this->setPageContext($pid); 659 | } 660 | $region = $this->page->getRegion($pid); 661 | $posy = 0; // $region['RY']; 662 | } 663 | 664 | $this->page->addContent($this->graph->getStartTransform(), $pid); 665 | $this->page->addContent($font['out'], $pid); 666 | 667 | if (! empty($bmrk['c'])) { 668 | $col = $this->color->getPdfColor($bmrk['c']); 669 | $this->page->addContent($col, $pid); 670 | } 671 | 672 | if (empty($bmrk['u'])) { 673 | $bmrk['u'] = $this->addInternalLink($bmrk['p'], $bmrk['y']); 674 | } 675 | 676 | $offset = ($indent * $bmrk['l']); 677 | // add bookmark text 678 | $this->addTextCell( 679 | $bmrk['t'], 680 | $pid, 681 | $txt_posx, 682 | $posy, 683 | $txtwidth, 684 | 0, 685 | $offset, 686 | 0, 687 | 'T', 688 | $aligntext, 689 | ); 690 | 691 | $bbox = $this->getLastBBox(); 692 | $wtxt = $bbox['w']; 693 | 694 | $pageid = $this->page->getPageID(); 695 | if ($pageid > $pid) { 696 | $this->page->addContent($this->graph->getStopTransform(), $pid); 697 | $lnkid = $this->setLink( 698 | $posx, 699 | $posy, 700 | $width, 701 | ($region['RH'] - $posy), 702 | $bmrk['u'], 703 | ); 704 | $this->page->addAnnotRef($lnkid, $pid); 705 | $pid = $pageid; 706 | $this->page->addContent($this->graph->getStartTransform(), $pid); 707 | $this->page->addContent($font['out'], $pid); 708 | } 709 | 710 | $posy = $bbox['y'] - $cellSpaceT; // align number with the last line of the text 711 | 712 | // add page number 713 | $this->addTextCell( 714 | (string) ($bmrk['p'] + 1), 715 | $pid, 716 | $num_posx, 717 | $posy, 718 | $numwidth, 719 | 0, 720 | 0, 721 | 0, 722 | 'T', 723 | $alignnum, 724 | ); 725 | 726 | $bbox = $this->getLastBBox(); 727 | $wnum = $bbox['w']; 728 | 729 | // add line to fill the gap between text and number 730 | $line_posx = ($cellSpaceH + $offset + $posx + ($rtl ? $wnum : $wtxt)); 731 | $line_posy = $bbox['y'] + $this->toUnit($font['ascent']); 732 | $line = $this->graph->getLine( 733 | $line_posx, 734 | $line_posy, 735 | $line_posx + ($width - $wtxt - $wnum - (2 * $cellSpaceH) - $offset), 736 | $line_posy, 737 | $linestyle, 738 | ); 739 | $this->page->addContent($line, $pid); 740 | 741 | $lnkid = $this->setLink( 742 | $posx, 743 | $bbox['y'], 744 | $width, 745 | $bbox['h'], 746 | $bmrk['u'], 747 | ); 748 | $this->page->addAnnotRef($lnkid, $pid); 749 | 750 | $this->page->addContent($this->graph->getStopTransform(), $pid); 751 | 752 | // Move to the next line. 753 | $posy = $bbox['y'] + $bbox['h'] + $cellSpaceB; 754 | } 755 | } 756 | } 757 | --------------------------------------------------------------------------------