diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index aa68ef4..defda12 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -23,16 +23,10 @@ jobs: strategy: fail-fast: false matrix: - php-version: ["7.2", "7.3", "7.4", "8.0", "8.1", "8.2"] + php-version: ["8.0", "8.1", "8.2"] experimental: [false] os: [ubuntu-latest] coverage-extension: [pcov] - include: - #- { php-version: '5.3', experimental: false, os: ubuntu-latest, coverage-extension: 'xdebug' } - #- { php-version: '5.4', experimental: false, os: ubuntu-latest, coverage-extension: 'xdebug' } - - { php-version: '5.5', experimental: false, os: ubuntu-latest, coverage-extension: 'xdebug' } - - { php-version: '5.6', experimental: false, os: ubuntu-latest, coverage-extension: 'xdebug' } - - { php-version: '7.1', experimental: false, os: ubuntu-latest, coverage-extension: 'xdebug' } steps: - uses: actions/checkout@v4 - name: Use php ${{ matrix.php-version }} diff --git a/.gitignore b/.gitignore index 291bb86..4dd7d5e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,10 +7,14 @@ **/.vagrant **/auth.json **/nbproject +**/temp.php +**/test.php .phpdoc .phpunit.cache .phpunit.result.cache composer.lock +ecs.php phpunit.xml +rector.php target vendor diff --git a/README.md b/README.md index a244a44..9d42af0 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ All artifacts are generated in the target directory. Examples are located in the `example` directory. -Start a development server (requires PHP 5.4) using the command: +Start a development server (requires PHP 8.0+) using the command: ``` make server @@ -78,7 +78,7 @@ Create a composer.json in your projects root-directory: ```json { "require": { - "tecnickcom/tc-lib-pdf-image": "^1.2" + "tecnickcom/tc-lib-pdf-image": "^2.0" } } ``` @@ -86,7 +86,7 @@ Create a composer.json in your projects root-directory: Or add to an existing project with: ```bash -composer require tecnickcom/tc-lib-pdf-image ^1.2 +composer require tecnickcom/tc-lib-pdf-image ^2.0 ``` diff --git a/VERSION b/VERSION index 771411b..157e54f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.4.20 +2.0.6 diff --git a/composer.json b/composer.json index eb63d6a..ea2440d 100644 --- a/composer.json +++ b/composer.json @@ -20,18 +20,18 @@ } ], "require": { - "php": ">=5.4", + "php": ">=8.0", "ext-gd": "*", "ext-zlib": "*", - "tecnickcom/tc-lib-file": "^1.7", - "tecnickcom/tc-lib-color": "^1.14", - "tecnickcom/tc-lib-pdf-encrypt": "^1.6" + "tecnickcom/tc-lib-file": "^2.0", + "tecnickcom/tc-lib-color": "^2.0", + "tecnickcom/tc-lib-pdf-encrypt": "^2.0" }, "require-dev": { "pdepend/pdepend": "2.13.0", "phpmd/phpmd": "2.13.0", - "phpunit/phpunit": "10.1.2 || 9.6.7 || 8.5.31 || 7.5.20 || 6.5.14 || 5.7.27 || 4.8.36", - "squizlabs/php_codesniffer": "3.7.2 || 2.9.2" + "phpunit/phpunit": "10.1.2 || 9.6.13", + "squizlabs/php_codesniffer": "3.7.2" }, "autoload": { "psr-4": { diff --git a/phpstan.neon b/phpstan.neon index c42b364..bf592ce 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,5 +1,5 @@ parameters: - level: 5 + level: max paths: - src - test diff --git a/resources/debian/control b/resources/debian/control index 5b34b26..a85a270 100644 --- a/resources/debian/control +++ b/resources/debian/control @@ -10,6 +10,6 @@ Vcs-Git: https://github.com/~#VENDOR#~/~#PROJECT#~.git Package: ~#PKGNAME#~ Provides: php-~#PROJECT#~ Architecture: all -Depends: php (>= 5.4.0), php-gd, php-zip, php-tecnickcom-tc-lib-file (<< 2.0.0), php-tecnickcom-tc-lib-file (>= 1.7.39), php-tecnickcom-tc-lib-color (<< 2.0.0), php-tecnickcom-tc-lib-color (>= 1.14.39), php-tecnickcom-tc-lib-pdf-encrypt (<< 2.0.0), php-tecnickcom-tc-lib-pdf-encrypt (>= 1.6.35), ${misc:Depends} +Depends: php (>= 8.0.0), php-gd, php-zip, php-tecnickcom-tc-lib-file (<< 2.0.0), php-tecnickcom-tc-lib-file (>= 2.0.6), php-tecnickcom-tc-lib-color (<< 2.0.0), php-tecnickcom-tc-lib-color (>= 2.0.3), php-tecnickcom-tc-lib-pdf-encrypt (<< 2.0.0), php-tecnickcom-tc-lib-pdf-encrypt (>= 2.0.6), ${misc:Depends} Description: PHP PDF Image Library PHP library containing PDF Image methods. diff --git a/resources/rpm/rpm.spec b/resources/rpm/rpm.spec index 0734ea6..396a00f 100644 --- a/resources/rpm/rpm.spec +++ b/resources/rpm/rpm.spec @@ -16,15 +16,15 @@ URL: https://github.com/%{gh_owner}/%{gh_project} BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-%(%{__id_u} -n) BuildArch: noarch -Requires: php(language) >= 5.4.0 +Requires: php(language) >= 8.0.0 Requires: php-gd Requires: php-zlib Requires: php-composer(%{c_vendor}/tc-lib-file) < 2.0.0 -Requires: php-composer(%{c_vendor}/tc-lib-file) >= 1.7.39 +Requires: php-composer(%{c_vendor}/tc-lib-file) >= 2.0.6 Requires: php-composer(%{c_vendor}/tc-lib-color) < 2.0.0 -Requires: php-composer(%{c_vendor}/tc-lib-color) >= 1.14.39 +Requires: php-composer(%{c_vendor}/tc-lib-color) >= 2.0.3 Requires: php-composer(%{c_vendor}/tc-lib-pdf-encrypt) < 2.0.0 -Requires: php-composer(%{c_vendor}/tc-lib-pdf-encrypt) >= 1.6.35 +Requires: php-composer(%{c_vendor}/tc-lib-pdf-encrypt) >= 2.0.6 Provides: php-composer(%{c_vendor}/%{gh_project}) = %{version} Provides: php-%{gh_project} = %{version} diff --git a/src/Exception.php b/src/Exception.php index 9e4a614..a6300bc 100644 --- a/src/Exception.php +++ b/src/Exception.php @@ -3,13 +3,13 @@ /** * Exception.php * - * @since 2011-05-23 - * @category Library - * @package PdfImage - * @author Nicola Asuni - * @copyright 2011-2023 Nicola Asuni - Tecnick.com LTD - * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) - * @link https://github.com/tecnickcom/tc-lib-pdf-Image + * @since 2011-05-23 + * @category Library + * @package PdfImage + * @author Nicola Asuni + * @copyright 2011-2023 Nicola Asuni - Tecnick.com LTD + * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) + * @link https://github.com/tecnickcom/tc-lib-pdf-Image * * This file is part of tc-lib-pdf-Image software library. */ @@ -21,13 +21,13 @@ * * Custom Exception class * - * @since 2011-05-23 - * @category Library - * @package PdfImage - * @author Nicola Asuni - * @copyright 2011-2023 Nicola Asuni - Tecnick.com LTD - * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) - * @link https://github.com/tecnickcom/tc-lib-pdf-Image + * @since 2011-05-23 + * @category Library + * @package PdfImage + * @author Nicola Asuni + * @copyright 2011-2023 Nicola Asuni - Tecnick.com LTD + * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) + * @link https://github.com/tecnickcom/tc-lib-pdf-Image */ class Exception extends \Exception { diff --git a/src/Import.php b/src/Import.php index 1abdfa2..618da2e 100644 --- a/src/Import.php +++ b/src/Import.php @@ -3,13 +3,13 @@ /** * Import.php * - * @since 2011-05-23 - * @category Library - * @package PdfImage - * @author Nicola Asuni - * @copyright 2011-2023 Nicola Asuni - Tecnick.com LTD - * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) - * @link https://github.com/tecnickcom/tc-lib-pdf-image + * @since 2011-05-23 + * @category Library + * @package PdfImage + * @author Nicola Asuni + * @copyright 2011-2023 Nicola Asuni - Tecnick.com LTD + * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) + * @link https://github.com/tecnickcom/tc-lib-pdf-image * * This file is part of tc-lib-pdf-image software library. */ @@ -23,56 +23,42 @@ /** * Com\Tecnick\Pdf\Image\Import * - * @since 2011-05-23 - * @category Library - * @package PdfImage - * @author Nicola Asuni - * @copyright 2011-2023 Nicola Asuni - Tecnick.com LTD - * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) - * @link https://github.com/tecnickcom/tc-lib-pdf-image + * @since 2011-05-23 + * @category Library + * @package PdfImage + * @author Nicola Asuni + * @copyright 2011-2023 Nicola Asuni - Tecnick.com LTD + * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) + * @link https://github.com/tecnickcom/tc-lib-pdf-image + * + * @SuppressWarnings(PHPMD.ExcessiveClassLength) + * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) */ class Import extends \Com\Tecnick\Pdf\Image\Output { /** * Image index. * Count the number of added images. - * - * @var int - */ - protected $iid = 0; - - /** - * Stack of added images. - * - * @var array - */ - protected $image = array(); - - /** - * Cache used to store imported image data. - * The same image data can be reused multiple times. - * - * @var array */ - protected $cache = array(); + protected int $iid = 0; /** * Native image types and associated importing class. * (Image types for which we have an import method). * - * @var array + * @var array */ - private static $native = array( - IMAGETYPE_PNG => 'Png', + private const NATIVE = [ + IMAGETYPE_PNG => 'Png', IMAGETYPE_JPEG => 'Jpeg', - ); + ]; /** * Lossless image types. * - * @var array + * @var array */ - protected static $lossless = array( + protected const LOSSLESS = [ IMAGETYPE_GIF, IMAGETYPE_PNG, IMAGETYPE_PSD, @@ -84,18 +70,18 @@ class Import extends \Com\Tecnick\Pdf\Image\Output IMAGETYPE_IFF, IMAGETYPE_SWC, IMAGETYPE_ICO, - ); + ]; /** * Map number of channels with color space name. * - * @var array + * @var array */ - protected static $colspacemap = array( + protected const COLSPACEMAP = [ 1 => 'DeviceGray', 3 => 'DeviceRGB', 4 => 'DeviceCMYK', - ); + ]; /** * Add a new image. @@ -108,29 +94,29 @@ class Import extends \Com\Tecnick\Pdf\Image\Output * @param bool $ismask True if the image is a transparency mask. * @param int $quality Quality for JPEG files (0 = max compression; 100 = best quality, bigger file). * @param bool $defprint Indicate if the image is the default for printing when used as alternative image. - * @param array $altimgs Arrays of alternate image keys. + * @param array $altimgs Arrays of alternate image keys. * * @return int Image ID. */ public function add( - $image, - $width = null, - $height = null, - $ismask = false, - $quality = 100, - $defprint = false, - $altimgs = array() - ) { + string $image, + int $width = null, + int $height = null, + bool $ismask = false, + int $quality = 100, + bool $defprint = false, + array $altimgs = [] + ): int { $data = $this->import($image, $width, $height, $ismask, $quality); ++$this->iid; - $this->image[$this->iid] = array( - 'iid' => $this->iid, - 'key' => $data['key'], - 'width' => $data['width'], - 'height' => $data['height'], + $this->image[$this->iid] = [ + 'iid' => $this->iid, + 'key' => $data['key'], + 'width' => $data['width'], + 'height' => $data['height'], 'defprint' => $defprint, - 'altimgs' => $altimgs, - ); + 'altimgs' => $altimgs, + ]; return $this->iid; } @@ -141,11 +127,13 @@ public function add( * @param int $width Width in pixels. * @param int $height Height in pixels. * @param int $quality Quality for JPEG files. - * - * @return string */ - public function getKey($image, $width = 0, $height = 0, $quality = 100) - { + public function getKey( + string $image, + int $width = 0, + int $height = 0, + int $quality = 100, + ): string { return strtr( rtrim( base64_encode( @@ -163,33 +151,205 @@ public function getKey($image, $width = 0, $height = 0, $quality = 100) * * @param string $key Image key. * - * @return array Image raw data array. + * @return array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'mask'?: array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * }, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'plain'?: array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * }, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * } Image raw data array. */ - public function getImageDataByKey($key) + public function getImageDataByKey(string $key): array { if (empty($this->cache[$key])) { throw new ImageException('Unknown key'); } + return $this->cache[$key]; } /** * Import the original image raw data. * - * @param string $image Image file name, URL or a '@' character followed by the image data string. - * To link an image without embedding it on the document, set an asterisk character - * before the URL (i.e.: '*http://www.example.com/image.jpg'). - * @param int $width New width in pixels or null to keep the original value. - * @param int $height New height in pixels or null to keep the original value. - * @param bool $ismask True if the image is a transparency mask. - * @param int $quality Quality for JPEG files (0 = max compression; 100 = best quality, bigger file). + * @param string $image Image file name, URL or a '@' character followed by the image data string. + * To link an image without embedding it on the document, set an asterisk + * character before the URL (i.e.: '*http://www.example.com/image.jpg'). + * @param int $width New width in pixels or null to keep the original value. + * @param int $height New height in pixels or null to keep the original value. + * @param bool $ismask True if the image is a transparency mask. + * @param int $quality Quality for JPEG files (0 = max compression; 100 = best quality, bigger file). * - * @return array Image raw data array + * @return array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'mask'?: array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * }, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'plain'?: array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * }, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * } Image raw data array */ - protected function import($image, $width = null, $height = null, $ismask = false, $quality = 100) - { + protected function import( + string $image, + ?int $width = null, + ?int $height = null, + bool $ismask = false, + int $quality = 100, + ): array { $quality = max(0, min(100, $quality)); - $imgkey = $this->getKey($image, intval($width), intval($height), $quality); + $imgkey = $this->getKey($image, (int) $width, (int) $height, $quality); if (isset($this->cache[$imgkey])) { return $this->cache[$imgkey]; @@ -201,14 +361,16 @@ protected function import($image, $width = null, $height = null, $ismask = false if ($width === null) { $width = $data['width']; } - $width = max(0, intval($width)); + + $width = max(0, (int) $width); if ($height === null) { $height = $data['height']; } - $height = max(0, intval($height)); - if ((!$data['native']) || ($width != $data['width']) || ($height != $data['height'])) { + $height = max(0, (int) $height); + + if ((! $data['native']) || ($width != $data['width']) || ($height != $data['height'])) { $data = $this->getResizedRawData($data, $width, $height, true, $quality); } @@ -216,7 +378,7 @@ protected function import($image, $width = null, $height = null, $ismask = false if ($ismask) { $data['mask'] = $data; - } elseif (!empty($data['splitalpha'])) { + } elseif (! empty($data['splitalpha'])) { // create 2 separate images: plain + mask $rawdata = $data; $data['plain'] = $this->getResizedRawData($rawdata, $width, $height, false, $quality); @@ -234,37 +396,209 @@ protected function import($image, $width = null, $height = null, $ismask = false /** * Extract the relevant data from the image. * - * @param array $data Image raw data. - * @param int $width Width in pixels. - * @param int $height Height in pixels. - * @param int $quality Quality for JPEG files. + * @param array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'mask'?: array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * }, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'plain'?: array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * }, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * } $data Image raw data. + * @param int $width Width in pixels. + * @param int $height Height in pixels. + * @param int $quality Quality for JPEG files. * - * @return array + * @return array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'mask'?: array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * }, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'plain'?: array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * }, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * } Image raw data array. */ - protected function getData($data, $width, $height, $quality) - { - if (!$data['native']) { + protected function getData( + array $data, + int $width, + int $height, + int $quality + ): array { + if (! $data['native']) { throw new ImageException('Unable to import image'); } - $imp = $this->createImportImage($data); - $data = $imp->getData($data); - if (!empty($data['recode'])) { + $imageImport = $this->createImportImage($data); + $data = $imageImport->getData($data); + + if (! empty($data['recode'])) { // re-encode the image as it was not possible to decode it $data = $this->getResizedRawData($data, $width, $height, true, $quality); - $data = $imp->getData($data); + $data = $imageImport->getData($data); } + return $data; } /** - * @param array $data Image raw data. - * - * @return ImageImportInterface + * @param array{ + * 'type': int, + * } $data Image raw data. */ - private function createImportImage($data) + private function createImportImage(array $data): ImageImportInterface { - $class = '\\Com\\Tecnick\\Pdf\\Image\\Import\\' . self::$native[$data['type']]; + $class = '\\Com\\Tecnick\\Pdf\\Image\\Import\\' . self::NATIVE[$data['type']]; return new $class(); } @@ -275,35 +609,124 @@ private function createImportImage($data) * To link an image without embedding it on the document, set an asterisk character * before the URL (i.e.: '*http://www.example.com/image.jpg'). * - * @return array Image data array. + * @return array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'mask'?: array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * }, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'plain'?: array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * }, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * } Image data array. */ - protected function getRawData($image) + protected function getRawData(string $image): array { // default data to return - $data = array( - 'key' => '', // image key - 'defprint' => false, // default printing image when used as alternate - 'raw' => '', // raw image data - 'file' => '', // source file name or URL - 'exturl' => false, // true if the image is an exernal URL that should not be embedded - 'width' => 0, // image width in pixels - 'height' => 0, // image height in pixels - 'type' => 0, // image type constant: IMAGETYPE_XXX - 'native' => false, // true if the image is PNG or JPEG - 'mapto' => IMAGETYPE_PNG, // type to convert to - 'bits' => 8, // number of bits per channel - 'channels' => 3, // number of channels - 'colspace' => 'DeviceRGB', // color space - 'icc' => '', // ICC profile - 'filter' => 'FlateDecode', // decoding filter - 'parms' => '', // additional PDF decoding parameters - 'pal' => '', // colour palette - 'trns' => array(), // colour key masking - 'data' => '', // PDF image data - 'ismask' => false, // true if the image is a transparency mask - ); + $data = [ + 'bits' => 8, // number of bits per channel + 'channels' => 3, // number of channels + 'colspace' => 'DeviceRGB', // color space + 'data' => '', // PDF image data + 'exturl' => false, // true if the image is an exernal URL that should not be embedded + 'file' => '', // source file name or URL + 'filter' => 'FlateDecode', // decoding filter + 'height' => 0, // image height in pixels + 'icc' => '', // ICC profile + 'ismask' => false, // true if the image is a transparency mask + 'key' => '', // image key + 'mapto' => IMAGETYPE_PNG, // type to convert to + 'native' => false, // true if the image is PNG or JPEG + 'obj' => 0, // PDF object number + 'obj_alt' => 0, + 'obj_icc' => 0, + 'obj_pal' => 0, + 'pal' => '', // colour palette + 'parms' => '', // additional PDF decoding parameters + 'raw' => '', // raw image data + 'recode' => false, + 'recoded' => false, + 'splitalpha' => false, + 'trns' => [], // colour key masking + 'type' => 0, // image type constant: IMAGETYPE_XXX + 'width' => 0, // image width in pixels + ]; - if (empty($image) || ((($image[0] === '@') || ($image[0] === '*')) && (strlen($image) === 1))) { + if ($image === '' || ((($image[0] === '@') || ($image[0] === '*')) && (strlen($image) === 1))) { throw new ImageException('Empty image'); } @@ -318,8 +741,13 @@ protected function getRawData($image) } $data['file'] = $image; - $fobj = new File(); - $data['raw'] = $fobj->getFileData($image); + $file = new File(); + $raw = $file->getFileData($image); + if ($raw === false) { + throw new ImageException('Unable to read image file: ' . $image); + } + + $data['raw'] = $raw; return $this->getMetaData($data); } @@ -327,34 +755,149 @@ protected function getRawData($image) /** * Get the image meta data. * - * @param array $data Image raw data. + * @param array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * } $data Image raw data. * - * @return array Image raw data array. + * @return array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'mask'?: array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * }, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'plain'?: array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * }, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * } Image raw data array. */ - protected function getMetaData($data) + protected function getMetaData(array $data): array { try { $meta = @getimagesizefromstring($data['raw']); - } catch (\Exception $exc) { - throw new ImageException('Invalid image format: ' . $exc); + } catch (\Exception $exception) { + throw new ImageException('Invalid image format: ' . $exception); } + if ($meta === false) { - throw new ImageException('Invalid image format'); + throw new ImageException('Invalid image format'); } + $data['width'] = $meta[0]; $data['height'] = $meta[1]; $data['type'] = $meta[2]; - $data['native'] = isset(self::$native[$data['type']]); - $data['mapto'] = (in_array($data['type'], self::$lossless) ? IMAGETYPE_PNG : IMAGETYPE_JPEG); + $data['native'] = isset(self::NATIVE[$data['type']]); + $data['mapto'] = (in_array($data['type'], self::LOSSLESS) ? IMAGETYPE_PNG : IMAGETYPE_JPEG); if (isset($meta['bits'])) { - $data['bits'] = intval($meta['bits']); + $data['bits'] = $meta['bits']; } - if (!empty($meta['channels'])) { - $data['channels'] = intval($meta['channels']); + + if (isset($meta['channels']) && $meta['channels'] !== 0) { + $data['channels'] = (int) $meta['channels']; } - if (!empty(self::$colspacemap[$data['channels']])) { - $data['colspace'] = self::$colspacemap[$data['channels']]; + + if (isset(self::COLSPACEMAP[$data['channels']]) && self::COLSPACEMAP[$data['channels']] !== '') { + $data['colspace'] = self::COLSPACEMAP[$data['channels']]; } + return $data; } @@ -362,23 +905,149 @@ protected function getMetaData($data) * Get the resized image raw data * (always convert the image type to a native format: PNG or JPEG). * - * @param array $data Image raw data as returned by getImageRawData. - * @param int $width New width in pixels. - * @param int $height New height in pixels. - * @param bool $alpha If true save the alpha channel information, if false merge the alpha channel (PNG mode). - * @param int $quality Quality for JPEG files (0 = max compression; 100 = best quality, bigger file). + * @param array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * } $data Image raw data as returned by getImageRawData. + * @param int $width New width in pixels. + * @param int $height New height in pixels. + * @param bool $alpha If true save the alpha channel information, if false merge the alpha channel (PNG mode). + * @param int $quality Quality for JPEG files (0 = max compression; 100 = best quality, bigger file). + * + * @return array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'mask'?: array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * }, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'plain'?: array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * }, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * } Image raw data array. * - * @return array Image raw data array. + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ - protected function getResizedRawData($data, $width, $height, $alpha = true, $quality = 100) - { + protected function getResizedRawData( + array $data, + int $width, + int $height, + bool $alpha = true, + int $quality = 100, + ): array { if (($width <= 0) || ($height <= 0)) { throw new ImageException('Image width and/or height are empty'); } + $img = imagecreatefromstring($data['raw']); + if ($img === false) { + throw new ImageException('Unable to create new image from string'); + } + $newimg = imagecreatetruecolor($width, $height); + if ($newimg === false) { + throw new ImageException('Unable to create new resized image'); + } + imageinterlace($newimg, false); - imagealphablending($newimg, !$alpha); + imagealphablending($newimg, ! $alpha); imagesavealpha($newimg, $alpha); imagecopyresampled($newimg, $img, 0, 0, 0, 0, $width, $height, $data['width'], $data['height']); ob_start(); @@ -391,14 +1060,26 @@ protected function getResizedRawData($data, $width, $height, $alpha = true, $qua // set transparency for Indexed image $tcol = imagecolorsforindex($img, $tid); $tid = imagecolorallocate($newimg, $tcol['red'], $tcol['green'], $tcol['blue']); + if ($tid === false) { + throw new ImageException('Unable to allocate color for transparency'); + } + imagefill($newimg, 0, 0, $tid); imagecolortransparent($newimg, $tid); } + imagepng($newimg, null, 9, PNG_ALL_FILTERS); } else { imagejpeg($newimg, null, $quality); } - $data['raw'] = ob_get_clean(); + + $ogc = ob_get_clean(); + + if ($ogc === false) { + throw new ImageException('Unable to extract alpha channel'); + } + + $data['raw'] = $ogc; $data['exturl'] = false; $data['recoded'] = true; return $this->getMetaData($data); @@ -407,33 +1088,162 @@ protected function getResizedRawData($data, $width, $height, $alpha = true, $qua /** * Extract the alpha channel as separate image to be used as a mask. * - * @param array $data Image raw data as returned by getImageRawData. + * @param array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * } $data Image raw data as returned by getImageRawData. * - * @return array Image raw data array. + * @return array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'mask'?: array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * }, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'plain'?: array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * }, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * } Image raw data array. */ - protected function getAlphaChannelRawData($data) + protected function getAlphaChannelRawData(array $data): array { $img = imagecreatefromstring($data['raw']); + if ($img === false) { + throw new ImageException('Unable to create alpha channel image from string'); + } + $newimg = imagecreate($data['width'], $data['height']); + if ($newimg === false) { + throw new ImageException('Unable to create new empty alpha channel image'); + } + imageinterlace($newimg, false); // generate gray scale palette (0 -> 255) for ($col = 0; $col < 256; ++$col) { ImageColorAllocate($newimg, $col, $col, $col); } + // extract alpha channel for ($xpx = 0; $xpx < $data['width']; ++$xpx) { for ($ypx = 0; $ypx < $data['height']; ++$ypx) { $colindex = imagecolorat($img, $xpx, $ypx); + if ($colindex === false) { + throw new ImageException('Unable to extract alpha channel color index'); + } + // get and correct gamma color $color = imagecolorsforindex($img, $colindex); // GD alpha is only 7 bit (0 -> 127); 2.2 is the gamma value - $alpha = (int)(pow(((float)(127 - $color['alpha']) / 127), 2.2) * 255); + $alpha = (int) (((float) (127 - $color['alpha']) / 127) ** 2.2 * 255); imagesetpixel($newimg, $xpx, $ypx, $alpha); } } + ob_start(); imagepng($newimg, null, 9, PNG_ALL_FILTERS); - $data['raw'] = ob_get_clean(); + $ogc = ob_get_clean(); + if ($ogc === false) { + throw new ImageException('Unable to extract alpha channel'); + } + + $data['raw'] = $ogc; $data['channels'] = 1; $data['colspace'] = 'DeviceGray'; $data['exturl'] = false; diff --git a/src/Import/ImageImportInterface.php b/src/Import/ImageImportInterface.php index 4a85edd..b44edd5 100644 --- a/src/Import/ImageImportInterface.php +++ b/src/Import/ImageImportInterface.php @@ -3,13 +3,13 @@ /** * ImageImportInterface.php * - * @since 2011-05-23 - * @category Library - * @package PdfImage - * @author jmleroux - * @copyright 2011-2023 Nicola Asuni - Tecnick.com LTD - * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) - * @link https://github.com/tecnickcom/tc-lib-pdf-image + * @since 2011-05-23 + * @category Library + * @package PdfImage + * @author jmleroux + * @copyright 2011-2023 Nicola Asuni - Tecnick.com LTD + * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) + * @link https://github.com/tecnickcom/tc-lib-pdf-image * * This file is part of tc-lib-pdf-image software library. */ @@ -19,22 +19,76 @@ /** * Com\Tecnick\Pdf\Image\Import\Jpeg * - * @since 2011-05-23 - * @category Library - * @package PdfImage - * @author jmleroux - * @copyright 2011-2023 Nicola Asuni - Tecnick.com LTD - * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) - * @link https://github.com/tecnickcom/tc-lib-pdf-image + * @since 2011-05-23 + * @category Library + * @package PdfImage + * @author jmleroux + * @copyright 2011-2023 Nicola Asuni - Tecnick.com LTD + * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) + * @link https://github.com/tecnickcom/tc-lib-pdf-image */ interface ImageImportInterface { /** * Extract data from an image. * - * @param array $data Image raw data. + * @param array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * } $data Image raw data. * - * @return array Image raw data array. + * @return array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * } Image raw data array. */ - public function getData($data); + public function getData(array $data): array; } diff --git a/src/Import/Jpeg.php b/src/Import/Jpeg.php index 832ab91..eeb0c0c 100644 Binary files a/src/Import/Jpeg.php and b/src/Import/Jpeg.php differ diff --git a/src/Import/Png.php b/src/Import/Png.php index edae39a..259bc04 100644 --- a/src/Import/Png.php +++ b/src/Import/Png.php @@ -3,13 +3,13 @@ /** * Png.php * - * @since 2011-05-23 - * @category Library - * @package PdfImage - * @author Nicola Asuni - * @copyright 2011-2015 Nicola Asuni - Tecnick.com LTD - * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) - * @link https://github.com/tecnickcom/tc-lib-pdf-image + * @since 2011-05-23 + * @category Library + * @package PdfImage + * @author Nicola Asuni + * @copyright 2011-2015 Nicola Asuni - Tecnick.com LTD + * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) + * @link https://github.com/tecnickcom/tc-lib-pdf-image * * This file is part of tc-lib-pdf-image software library. */ @@ -22,35 +22,90 @@ /** * Com\Tecnick\Pdf\Image\Import\Png * - * @since 2011-05-23 - * @category Library - * @package PdfImage - * @author Nicola Asuni - * @copyright 2011-2016 Nicola Asuni - Tecnick.com LTD - * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) - * @link https://github.com/tecnickcom/tc-lib-pdf-image + * @since 2011-05-23 + * @category Library + * @package PdfImage + * @author Nicola Asuni + * @copyright 2011-2016 Nicola Asuni - Tecnick.com LTD + * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) + * @link https://github.com/tecnickcom/tc-lib-pdf-image */ class Png implements ImageImportInterface { /** * Extract data from a PNG image. * - * @param array $data Image raw data. + * @param array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * } $data Image raw data. * - * @return array Image raw data array. + * @return array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * } Image raw data array. */ - public function getData($data) + public function getData(array $data): array { $data['filter'] = 'FlateDecode'; $byte = new Byte($data['raw']); $offset = 0; // check signature - if (substr($data['raw'], $offset, 8) != chr(137) . 'PNG' . chr(13) . chr(10) . chr(26) . chr(10)) { + if (substr($data['raw'], $offset, 8) !== chr(137) . 'PNG' . chr(13) . chr(10) . chr(26) . chr(10)) { // @codeCoverageIgnoreStart throw new ImageException('Not a PNG image'); // @codeCoverageIgnoreEnd } + $offset += 8; $offset += 4; @@ -64,27 +119,26 @@ public function getData($data) || ($byte->getByte($offset - 2) != 0) || ($byte->getByte($offset - 1) != 0) ) { - if (!empty($data['recoded'])) { + if (! empty($data['recoded'])) { // this image has been already re-encoded // @codeCoverageIgnoreStart throw new ImageException('Unsupported feature'); // @codeCoverageIgnoreEnd } + // re-encode PNG $data['recode'] = true; return $data; } - if (strpos($data['colspace'], '+Alpha') !== false) { + if (str_contains($data['colspace'], '+Alpha')) { // alpha channel: split images (plain + alpha) $data['splitalpha'] = true; $data['colspace'] = substr($data['colspace'], 0, -6); return $data; } - $data['parms'] = '/DecodeParms <<' - . ' /Predictor 15' - . ' /Colors ' . $data['channels'] + $data['parms'] = '/DecodeParms << /Predictor 15 /Colors ' . $data['channels'] . ' /BitsPerComponent ' . $data['bits'] . ' /Columns ' . $data['width'] . ' >>'; @@ -99,12 +153,66 @@ public function getData($data) * The header chunk (IHDR) contains basic information about the image data and must appear as the first chunk, * and there must only be one header chunk in a PNG data stream. * - * @param array $data Image raw data. - * @param int $offset Current byte offset. + * @param array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * } $data Image raw data. + * @param int $offset Current byte offset. * - * @return array Image raw data array. + * @return array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * } Image raw data array. */ - protected function getIhdrChunk($data, &$offset) + protected function getIhdrChunk(array $data, int &$offset): array { $byte = new Byte($data['raw']); if (substr($data['raw'], $offset, 4) != 'IHDR') { @@ -112,23 +220,24 @@ protected function getIhdrChunk($data, &$offset) throw new ImageException('Invalid PNG image'); // @codeCoverageIgnoreEnd } + $offset += 4; $data['width'] = $byte->getULong($offset); $offset += 4; $data['height'] = $byte->getULong($offset); $offset += 4; $data['bits'] = $byte->getByte($offset); - $offset += 1; + ++$offset; $chc = $byte->getByte($offset); // channels code - $offset += 1; + ++$offset; $data['channels'] = (($chc == 2) ? 3 : 1); - $chcmap = array( + $chcmap = [ 0 => 'DeviceGray', 2 => 'DeviceRGB', 3 => 'Indexed', 4 => 'DeviceGray+Alpha', 6 => 'DeviceRGB+Alpha', - ); + ]; if (isset($chcmap[$chc])) { $data['colspace'] = $chcmap[$chc]; } else { @@ -136,18 +245,73 @@ protected function getIhdrChunk($data, &$offset) throw new ImageException('Unknown color mode'); // @codeCoverageIgnoreEnd } + return $data; } /** * Extract chunks data from a PNG image. * - * @param array $data Image raw data. - * @param int $offset Current byte offset. + * @param array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * } $data Image raw data. + * @param int $offset Current byte offset. * - * @return array Image raw data array. + * @return array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * } Image raw data array. */ - protected function getChunks($data, $offset) + protected function getChunks(array $data, int $offset): array { $byte = new Byte($data['raw']); while (($len = $byte->getULong($offset)) >= 0) { @@ -171,11 +335,13 @@ protected function getChunks($data, $offset) $offset += 4; } } + if (($data['colspace'] == 'Indexed') && (empty($data['pal']))) { // @codeCoverageIgnoreStart throw new ImageException('The color palette is missing'); // @codeCoverageIgnoreEnd } + return $data; } @@ -185,13 +351,67 @@ protected function getChunks($data, $offset) * The palette chunk (PLTE) stores the colormap data associated with the image data. * This chunk is presentonly if the image data uses a color palette and must appear before the image data chunk. * - * @param array $data Image raw data. - * @param int $offset Current byte offset. - * @param int $len NUmber of bytes in this chunk. + * @param array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * } $data Image raw data. + * @param int $offset Current byte offset. + * @param int $len NUmber of bytes in this chunk. * - * @return array Image raw data array. + * @return array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * } Image raw data array. */ - protected function getPlteChunk($data, &$offset, $len) + protected function getPlteChunk(array $data, int &$offset, int $len): array { $data['pal'] = substr($data['raw'], $offset, $len); $offset += $len; @@ -202,13 +422,67 @@ protected function getPlteChunk($data, &$offset, $len) /** * Extract the tRNS chunk data. * - * @param array $data Image raw data. - * @param int $offset Current byte offset. - * @param int $len NUmber of bytes in this chunk. + * @param array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * } $data Image raw data. + * @param int $offset Current byte offset. + * @param int $len NUmber of bytes in this chunk. * - * @return array Image raw data array. + * @return array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * } Image raw data array. */ - protected function getTrnsChunk($data, &$offset, $len) + protected function getTrnsChunk(array $data, int &$offset, int $len): array { // read transparency info $trns = substr($data['raw'], $offset, $len); @@ -225,6 +499,7 @@ protected function getTrnsChunk($data, &$offset, $len) // Indexed $data['trns'] = array_map('ord', str_split($trns)); } + $offset += 4; return $data; } @@ -235,13 +510,67 @@ protected function getTrnsChunk($data, &$offset, $len) * The image data chunk (IDAT) stores the actual image data, * and multiple image data chunks may occur in a data stream and must be stored in contiguous order. * - * @param array $data Image raw data. - * @param int $offset Current byte offset. - * @param int $len NUmber of bytes in this chunk. + * @param array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * } $data Image raw data. + * @param int $offset Current byte offset. + * @param int $len NUmber of bytes in this chunk. * - * @return array Image raw data array. + * @return array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * } Image raw data array. */ - protected function getIdatChunk($data, &$offset, $len) + protected function getIdatChunk(array $data, int &$offset, int $len): array { $data['data'] .= substr($data['raw'], $offset, $len); $offset += $len; @@ -252,29 +581,95 @@ protected function getIdatChunk($data, &$offset, $len) /** * Extract the iCCP chunk data. * - * @param Byte $byte Byte class object. - * @param array $data Image raw data. - * @param int $offset Current byte offset. - * @param int $len NUmber of bytes in this chunk. + * @param Byte $byte Byte class object. + * @param array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * } $data Image raw data. + * @param int $offset Current byte offset. + * @param int $len NUmber of bytes in this chunk. * - * @return array Image raw data array. + * @return array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * } Image raw data array. */ - protected function getIccpChunk($byte, $data, &$offset, $len) - { + protected function getIccpChunk( + Byte $byte, + array $data, + int &$offset, + int $len, + ): array { // skip profile name $pos = 0; while (($byte->getByte($offset++) != 0) && ($pos < 80)) { ++$pos; } + // get compression method if ($byte->getByte($offset++) != 0) { // @codeCoverageIgnoreStart throw new ImageException('Unknown filter method'); // @codeCoverageIgnoreEnd } + // read ICC Color Profile $len -= ($pos + 2); - $data['icc'] = gzuncompress(substr($data['raw'], $offset, $len)); + $icc = gzuncompress(substr($data['raw'], $offset, $len)); + if ($icc !== false) { + $data['icc'] = $icc; + } else { + throw new ImageException('Error while decompressing ICC profile'); + } + $offset += $len; $offset += 4; return $data; diff --git a/src/Output.php b/src/Output.php index 0698c46..deb2d92 100644 --- a/src/Output.php +++ b/src/Output.php @@ -3,13 +3,13 @@ /** * Output.php * - * @since 2011-05-23 - * @category Library - * @package PdfImage - * @author Nicola Asuni - * @copyright 2011-2023 Nicola Asuni - Tecnick.com LTD - * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) - * @link https://github.com/tecnickcom/tc-lib-pdf-image + * @since 2011-05-23 + * @category Library + * @package PdfImage + * @author Nicola Asuni + * @copyright 2011-2023 Nicola Asuni - Tecnick.com LTD + * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) + * @link https://github.com/tecnickcom/tc-lib-pdf-image * * This file is part of tc-lib-pdf-image software library. */ @@ -22,98 +22,156 @@ /** * Com\Tecnick\Pdf\Image\Output * - * @since 2011-05-23 - * @category Library - * @package PdfImage - * @author Nicola Asuni - * @copyright 2011-2023 Nicola Asuni - Tecnick.com LTD - * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) - * @link https://github.com/tecnickcom/tc-lib-pdf-image + * @since 2011-05-23 + * @category Library + * @package PdfImage + * @author Nicola Asuni + * @copyright 2011-2023 Nicola Asuni - Tecnick.com LTD + * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) + * @link https://github.com/tecnickcom/tc-lib-pdf-image */ abstract class Output { /** * Current PDF object number. - * - * @var int - */ - protected $pon; - - /** - * Unit of measure conversion ratio. - * - * @var float - */ - protected $kunit = 1.0; - - /** - * Encrypt object. - * - * @var Encrypt - */ - protected $enc; - - /** - * True if we are in PDF/A mode. - * - * @var bool */ - protected $pdfa = false; - - /** - * Enable stream compression. - * - * @var bool - */ - protected $compress = true; + protected int $pon; /** * Store image object IDs for the XObject Dictionary. * - * @var array + * @var array */ - protected $xobjdict = array(); + protected array $xobjdict = []; /** - * Image structure. + * Stack of added images. * - * @var array + * @var array, + * }> */ - protected $image = []; + protected array $image = []; /** - * Images cache. + * Cache used to store imported image data. + * The same image data can be reused multiple times. * - * @var array + * @var array, + * 'type': int, + * 'width': int, + * }, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'plain'?: array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'file': string, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'mapto': int, + * 'native': bool, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * }, + * 'raw': string, + * 'recode': bool, + * 'recoded': bool, + * 'splitalpha': bool, + * 'trns': array, + * 'type': int, + * 'width': int, + * }> */ - protected $cache = []; + protected array $cache = []; /** * Initialize images data. * * @param float $kunit Unit of measure conversion ratio. - * @param Encrypt $enc Encrypt object. + * @param Encrypt $encrypt Encrypt object. * @param bool $pdfa True if we are in PDF/A mode. * @param bool $compress Set to false to disable stream compression. */ public function __construct( - $kunit, - Encrypt $enc, - $pdfa = false, - $compress = true + protected float $kunit, + /** + * Encrypt object. + */ + protected Encrypt $encrypt, + protected bool $pdfa = false, + protected bool $compress = true ) { - $this->kunit = (float) $kunit; - $this->enc = $enc; - $this->pdfa = (bool) $pdfa; - $this->compress = (bool) $compress; } /** * Returns current PDF object number. - * - * @return int */ - public function getObjectNumber() + public function getObjectNumber(): int { return $this->pon; } @@ -130,11 +188,18 @@ public function getObjectNumber() * * @return string Image PDF page content. */ - public function getSetImage($iid, $xpos, $ypos, $width, $height, $pageheight) - { + public function getSetImage( + int $iid, + int $xpos, + int $ypos, + int $width, + int $height, + int $pageheight + ): string { if (empty($this->image[$iid])) { throw new ImageException('Unknown image ID: ' . $iid); } + $out = 'q'; $out .= sprintf( ' %F 0 0 %F %F %F cm', @@ -143,16 +208,16 @@ public function getSetImage($iid, $xpos, $ypos, $width, $height, $pageheight) ($xpos * $this->kunit), (($pageheight - $ypos - $height) * $this->kunit) // reverse coordinate ); - if (!empty($this->cache[$this->image[$iid]['key']]['mask'])) { + if (! empty($this->cache[$this->image[$iid]['key']]['mask'])) { $out .= ' /IMGmask' . $iid . ' Do'; - if (!empty($this->cache[$this->image[$iid]['key']]['plain'])) { + if (! empty($this->cache[$this->image[$iid]['key']]['plain'])) { $out .= ' /IMGplain' . $iid . ' Do'; } } else { $out .= ' /IMG' . $iid . ' Do'; } - $out .= ' Q'; - return $out; + + return $out . ' Q'; } /** @@ -162,47 +227,78 @@ public function getSetImage($iid, $xpos, $ypos, $width, $height, $pageheight) * * @return string PDF code for the images block. */ - public function getOutImagesBlock($pon) + public function getOutImagesBlock(int $pon): string { - $this->pon = (int) $pon; + $this->pon = $pon; $out = ''; foreach ($this->image as $iid => $img) { if (empty($this->cache[$img['key']]['out'])) { - if (!empty($this->cache[$img['key']]['mask'])) { + if (! empty($this->cache[$img['key']]['mask'])) { $out .= $this->getOutImage($img, $this->cache[$img['key']]['mask'], 'mask'); - if (!empty($this->cache[$img['key']]['plain'])) { + if (! empty($this->cache[$img['key']]['plain'])) { $out .= $this->getOutImage($img, $this->cache[$img['key']]['plain'], 'plain'); } } else { $out .= $this->getOutImage($img, $this->cache[$img['key']]); } + $this->image[$iid] = $img; } - if (!empty($this->cache[$img['key']]['mask']['obj'])) { + if (! empty($this->cache[$img['key']]['mask']['obj'])) { // the mask image must be omitted // $this->xobjdict['IMGmask'.$img['iid']] = $this->cache[$img['key']]['mask']['obj']; - if (!empty($this->cache[$img['key']]['plain']['obj'])) { + if (! empty($this->cache[$img['key']]['plain']['obj'])) { $this->xobjdict['IMGplain' . $img['iid']] = $this->cache[$img['key']]['plain']['obj']; } } else { $this->xobjdict['IMG' . $img['iid']] = $this->cache[$img['key']]['obj']; } } + return $out; } /** * Get the PDF output string for Image object. * - * @param array $img Image reference. - * @param array $data Image raw data. + * @param array{ + * 'iid': int, + * 'key': string, + * 'width': int, + * 'height': int, + * 'defprint': bool, + * 'altimgs'?: array, + * } $img Image reference. + * @param array{ + * 'bits': int, + * 'channels': int, + * 'colspace': string, + * 'data': string, + * 'exturl': bool, + * 'filter': string, + * 'height': int, + * 'icc': string, + * 'ismask': bool, + * 'key': string, + * 'obj': int, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * 'parms': string, + * 'trns': array, + * 'width': int, + * } $data Image raw data. * @param string $sub Sub image ('mask', 'plain' or empty string). * * @return string PDF Image object. */ - protected function getOutImage(&$img, &$data, $sub = '') - { + protected function getOutImage( + array &$img, + array &$data, + string $sub = '', + ): string { $out = $this->getOutIcc($data) . $this->getOutPalette($data) . $this->getOutAltImages($img, $data, $sub); @@ -216,32 +312,34 @@ protected function getOutImage(&$img, &$data, $sub = '') . ' /Height ' . $data['height'] . $this->getOutColorInfo($data); - if (!empty($data['exturl'])) { + if (! empty($data['exturl'])) { // external stream - $out .= ' /Length 0' - . ' /F << /FS /URL /F ' . $this->enc->escapeDataString($data['exturl'], $this->pon) . ' >>'; - if (!empty($data['filter'])) { + $out .= ' /Length 0 /F << /FS /URL /F ' + . $this->encrypt->escapeDataString($data['exturl'], $this->pon) . ' >>'; + if (! empty($data['filter'])) { $out .= ' /FFilter /' . $data['filter']; } + $out .= ' >> stream' . "\n" . 'endstream' . "\n"; } else { - if (!empty($data['filter'])) { + if (! empty($data['filter'])) { $out .= ' /Filter /' . $data['filter']; } - if (!empty($data['parms'])) { + + if (! empty($data['parms'])) { $out .= ' ' . $data['parms']; } // Colour Key Masking - if (!empty($data['trns'])) { + if (! empty($data['trns'])) { $trns = $this->getOutTransparency($data); - if (!empty($trns)) { + if ($trns !== '') { $out .= ' /Mask [ ' . $trns . ']'; } } - $stream = $this->enc->encryptString($data['data'], $this->pon); + $stream = $this->encrypt->encryptString($data['data'], $this->pon); $out .= ' /Length ' . strlen($stream) . '>> stream' . "\n" . $stream . "\n" @@ -256,31 +354,34 @@ protected function getOutImage(&$img, &$data, $sub = '') } /** - * Return XObjects Dictionary portion for the images. - * - * @return string - */ - public function getXobjectDict() + * Return XObjects Dictionary portion for the images. + */ + public function getXobjectDict(): string { $out = ''; foreach ($this->xobjdict as $iid => $objid) { $out .= ' /' . $iid . ' ' . $objid . ' 0 R'; } + return $out; } /** * Get the PDF output string for ICC object. * - * @param array $data Image raw data. - * - * @return string + * @param array{ + * 'channels': int, + * 'colspace': string, + * 'icc': string, + * 'obj_icc': int, + * } $data Image raw data. */ - protected function getOutIcc(&$data) + protected function getOutIcc(array &$data): string { if (empty($data['icc'])) { return ''; } + $data['obj_icc'] = ++$this->pon; $out = $data['obj_icc'] . ' 0 obj' . "\n" . '<<' @@ -289,63 +390,79 @@ protected function getOutIcc(&$data) $icc = $data['icc']; if ($this->compress) { $out .= ' /Filter /FlateDecode'; - $icc = gzcompress($icc); + $cicc = gzcompress($icc); + if ($cicc !== false) { + $icc = $cicc; + } } - $stream = $this->enc->encryptString($icc, $this->pon); - $out .= ' /Length ' . strlen($stream) + + $stream = $this->encrypt->encryptString($icc, $this->pon); + return $out . (' /Length ' . strlen($stream) . ' >>' . ' stream' . "\n" . $stream . "\n" . 'endstream' . "\n" - . 'endobj' . "\n"; - return $out; + . 'endobj' . "\n"); } /** * Get the PDF output string for Indexed palette object. * - * @param array $data Image raw data. - * - * @return string + * @param array{ + * 'colspace': string, + * 'obj_pal': int, + * 'pal': string, + * } $data Image raw data. */ - protected function getOutPalette(&$data) + protected function getOutPalette(array &$data): string { if ($data['colspace'] != 'Indexed') { return ''; } + $data['obj_pal'] = ++$this->pon; $out = $data['obj_pal'] . ' 0 obj' . "\n" . '<<'; $pal = $data['pal']; if ($this->compress) { $out .= '/Filter /FlateDecode'; - $pal = gzcompress($pal); + $cpal = gzcompress($pal); + if ($cpal !== false) { + $pal = $cpal; + } } - $stream = $this->enc->encryptString($pal, $this->pon); - $out .= ' /Length ' . strlen($stream) + + $stream = $this->encrypt->encryptString($pal, $this->pon); + return $out . (' /Length ' . strlen($stream) . '>>' . ' stream' . "\n" . $stream . "\n" . 'endstream' . "\n" - . 'endobj' . "\n"; - return $out; + . 'endobj' . "\n"); } /** * Get the PDF output string for color and mask information. * - * @param array $data Image raw data. - * - * @return string + * @param array{ + * 'bits': int, + * 'colspace': string, + * 'ismask': bool, + * 'key': string, + * 'obj_alt': int, + * 'obj_icc': int, + * 'obj_pal': int, + * 'pal': string, + * } $data Image raw data. */ - protected function getOutColorInfo($data) + protected function getOutColorInfo(array $data): string { $out = ''; // set color space - if (!empty($data['obj_icc'])) { + if (! empty($data['obj_icc'])) { // ICC Colour Space $out .= ' /ColorSpace [/ICCBased ' . $data['obj_icc'] . ' 0 R]'; - } elseif (!empty($data['obj_pal'])) { + } elseif (! empty($data['obj_pal'])) { // Indexed Colour Space $out .= ' /ColorSpace [/Indexed /DeviceRGB ' . ((strlen($data['pal']) / 3) - 1) @@ -354,33 +471,46 @@ protected function getOutColorInfo($data) // Device Colour Space $out .= ' /ColorSpace /' . $data['colspace']; } + if ($data['colspace'] == 'DeviceCMYK') { $out .= ' /Decode [1 0 1 0 1 0 1 0]'; } + $out .= ' /BitsPerComponent ' . $data['bits']; - if (!$data['ismask'] && !empty($this->cache[$data['key']]['mask']['obj'])) { + if (! $data['ismask'] && ! empty($this->cache[$data['key']]['mask']['obj'])) { $out .= ' /SMask ' . $this->cache[$data['key']]['mask']['obj'] . ' 0 R'; } - if (!empty($data['obj_alt'])) { + if (! empty($data['obj_alt'])) { // reference to alternate images dictionary $out .= ' /Alternates ' . $data['obj_alt'] . ' 0 R'; } + return $out; } /** * Get the PDF output string for Alternate images object. * - * @param array $img Image reference. - * @param array $data Image raw data. - * @param string $sub Sub image ('mask', 'plain' or empty string). - * - * @return string + * @param array{ + * 'iid': int, + * 'key': string, + * 'width': int, + * 'height': int, + * 'defprint': bool, + * 'altimgs'?: array, + * } $img Image reference. + * @param array{ + * 'obj_alt': int, + * } $data Image raw data. + * @param string $sub Sub image ('mask', 'plain' or empty string). */ - protected function getOutAltImages($img, &$data, $sub = '') - { + protected function getOutAltImages( + array $img, + array &$data, + string $sub = '', + ): string { if ($this->pdfa || empty($img['altimgs']) || ($sub == 'mask')) { return ''; } @@ -390,27 +520,25 @@ protected function getOutAltImages($img, &$data, $sub = '') $out = $this->pon . ' 0 obj' . "\n" . '['; foreach ($img['altimgs'] as $iid) { - if (!empty($this->cache[$this->image[$iid]['key']]['obj'])) { - $out .= ' <<' - . ' /Image ' . $this->cache[$this->image[$iid]['key']]['obj'] . ' 0 R' + if (! empty($this->cache[$this->image[$iid]['key']]['obj'])) { + $out .= ' << /Image ' . $this->cache[$this->image[$iid]['key']]['obj'] . ' 0 R' . ' /DefaultForPrinting ' . (empty($this->image[$iid]['defprint']) ? 'false' : 'true') . ' >>'; } } - $out .= ' ]' . "\n" - . 'endobj' . "\n"; - return $out; + return $out . (' ]' . "\n" + . 'endobj' . "\n"); } /** * Get the PDF output string for color and mask information. * - * @param array $data Image raw data. - * - * @return string + * @param array{ + * 'trns': array, + * } $data Image raw data. */ - protected function getOutTransparency($data) + protected function getOutTransparency(array $data): string { $trns = ''; foreach ($data['trns'] as $idx => $val) { @@ -418,6 +546,7 @@ protected function getOutTransparency($data) $trns .= $idx . ' ' . $idx . ' '; } } + return $trns; } } diff --git a/test/ImportTest.php b/test/ImportTest.php index 2a4833e..e31ee8d 100644 --- a/test/ImportTest.php +++ b/test/ImportTest.php @@ -3,201 +3,201 @@ /** * ImportTest.php * - * @since 2011-05-23 - * @category Library - * @package PdfImage - * @author Nicola Asuni - * @copyright 2011-2023 Nicola Asuni - Tecnick.com LTD - * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) - * @link https://github.com/tecnickcom/tc-lib-pdf-image + * @since 2011-05-23 + * @category Library + * @package PdfImage + * @author Nicola Asuni + * @copyright 2011-2023 Nicola Asuni - Tecnick.com LTD + * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) + * @link https://github.com/tecnickcom/tc-lib-pdf-image * * This file is part of tc-lib-pdf-image software library. */ namespace Test; -use PHPUnit\Framework\TestCase; - /** * Unit Test * - * @since 2011-05-23 - * @category Library - * @package PdfImage - * @author Nicola Asuni - * @copyright 2011-2023 Nicola Asuni - Tecnick.com LTD - * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) - * @link https://github.com/tecnickcom/tc-lib-pdf-image + * @since 2011-05-23 + * @category Library + * @package PdfImage + * @author Nicola Asuni + * @copyright 2011-2023 Nicola Asuni - Tecnick.com LTD + * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) + * @link https://github.com/tecnickcom/tc-lib-pdf-image */ class ImportTest extends TestUtil { - protected function getTestObject() + protected function getTestObject(): \Com\Tecnick\Pdf\Image\Import { - $enc = new \Com\Tecnick\Pdf\Encrypt\Encrypt(); - return new \Com\Tecnick\Pdf\Image\Import(0.75, $enc, false); + $encrypt = new \Com\Tecnick\Pdf\Encrypt\Encrypt(); + return new \Com\Tecnick\Pdf\Image\Import(0.75, $encrypt, false); } - public function testGetKey() + public function testGetKey(): void { - $testObj = $this->getTestObject(); - $result = $testObj->getKey('/images/200x100_RGB.png', 200, 100, 100); + $import = $this->getTestObject(); + $result = $import->getKey('/images/200x100_RGB.png', 200, 100, 100); $this->assertEquals('6EvJjr-KnDm4EnAWVt-7wQ', $result); } - public function testGetImageDataByKeyError() + public function testGetImageDataByKeyError(): void { - $this->bcExpectException('\Com\Tecnick\Pdf\Image\Exception'); - $testObj = $this->getTestObject(); - $testObj->getImageDataByKey('missing'); + $this->bcExpectException('\\' . \Com\Tecnick\Pdf\Image\Exception::class); + $import = $this->getTestObject(); + $import->getImageDataByKey('missing'); } - public function testGetSetImageError() + public function testGetSetImageError(): void { - $this->bcExpectException('\Com\Tecnick\Pdf\Image\Exception'); - $testObj = $this->getTestObject(); - $testObj->getSetImage(1, 2, 3, 5, 7, 17); + $this->bcExpectException('\\' . \Com\Tecnick\Pdf\Image\Exception::class); + $import = $this->getTestObject(); + $import->getSetImage(1, 2, 3, 5, 7, 17); } - public static function getBadAddValues() + /** + * @return array> + */ + public static function getBadAddValues(): array { - return array( - array(''), - array(__DIR__ . '/images/missing.png'), - array('@'), - array('@garbage'), - array('*'), - array('*http://www.example.com/image.png'), - ); + return [ + [''], + [__DIR__ . '/images/missing.png'], + ['@'], + ['@garbage'], + ['*'], + ['*http://www.example.com/image.png'], + ]; } /** * @dataProvider getBadAddValues */ - public function testAddError($bad) + public function testAddError(string $bad): void { - $this->bcExpectException('\Com\Tecnick\Pdf\Image\Exception'); - $testObj = $this->getTestObject(); - $testObj->add($bad); + $this->bcExpectException('\\' . \Com\Tecnick\Pdf\Image\Exception::class); + $import = $this->getTestObject(); + $import->add($bad); } - public function testAdd() + public function testAdd(): void { - $testObj = $this->getTestObject(); - $iid = $testObj->add(__DIR__ . '/images/200x100_RGB.png'); + $import = $this->getTestObject(); + $iid = $import->add(__DIR__ . '/images/200x100_RGB.png'); $this->assertEquals( 'q 150.000000 0 0 75.000000 2.250000 371.250000 cm /IMG1 Do Q', - $testObj->getSetImage($iid, 3, 5, 200, 100, 600) + $import->getSetImage($iid, 3, 5, 200, 100, 600) ); - $iid = $testObj->add(__DIR__ . '/images/200x100_GRAY.jpg'); + $iid = $import->add(__DIR__ . '/images/200x100_GRAY.jpg'); $this->assertEquals( 'q 150.000000 0 0 75.000000 2.250000 371.250000 cm /IMG2 Do Q', - $testObj->getSetImage($iid, 3, 5, 200, 100, 600) + $import->getSetImage($iid, 3, 5, 200, 100, 600) ); - $iid = $testObj->add(__DIR__ . '/images/200x100_GRAY.png'); + $iid = $import->add(__DIR__ . '/images/200x100_GRAY.png'); $this->assertEquals( 'q 150.000000 0 0 75.000000 2.250000 371.250000 cm /IMG3 Do Q', - $testObj->getSetImage($iid, 3, 5, 200, 100, 600) + $import->getSetImage($iid, 3, 5, 200, 100, 600) ); - $iid = $testObj->add(__DIR__ . '/images/200x100_INDEX16.png'); + $iid = $import->add(__DIR__ . '/images/200x100_INDEX16.png'); $this->assertEquals( 'q 150.000000 0 0 75.000000 2.250000 371.250000 cm /IMG4 Do Q', - $testObj->getSetImage($iid, 3, 5, 200, 100, 600) + $import->getSetImage($iid, 3, 5, 200, 100, 600) ); - $iid = $testObj->add(__DIR__ . '/images/200x100_INDEX256.png'); + $iid = $import->add(__DIR__ . '/images/200x100_INDEX256.png'); $this->assertEquals( 'q 150.000000 0 0 75.000000 2.250000 371.250000 cm /IMG5 Do Q', - $testObj->getSetImage($iid, 3, 5, 200, 100, 600) + $import->getSetImage($iid, 3, 5, 200, 100, 600) ); - $iid = $testObj->add(__DIR__ . '/images/200x100_RGB.jpg'); + $iid = $import->add(__DIR__ . '/images/200x100_RGB.jpg'); $this->assertEquals( 'q 150.000000 0 0 75.000000 2.250000 371.250000 cm /IMG6 Do Q', - $testObj->getSetImage($iid, 3, 5, 200, 100, 600) + $import->getSetImage($iid, 3, 5, 200, 100, 600) ); - $iid = $testObj->add(__DIR__ . '/images/200x100_RGB.png'); + $iid = $import->add(__DIR__ . '/images/200x100_RGB.png'); $this->assertEquals( 'q 150.000000 0 0 75.000000 2.250000 371.250000 cm /IMG7 Do Q', - $testObj->getSetImage($iid, 3, 5, 200, 100, 600) + $import->getSetImage($iid, 3, 5, 200, 100, 600) ); - $iid = $testObj->add(__DIR__ . '/images/200x100_RGBALPHA.png'); + $iid = $import->add(__DIR__ . '/images/200x100_RGBALPHA.png'); $this->assertEquals( 'q 150.000000 0 0 75.000000 2.250000 371.250000 cm /IMGmask8 Do /IMGplain8 Do Q', - $testObj->getSetImage($iid, 3, 5, 200, 100, 600) + $import->getSetImage($iid, 3, 5, 200, 100, 600) ); - $iid = $testObj->add(__DIR__ . '/images/200x100_INDEXALPHA.png'); + $iid = $import->add(__DIR__ . '/images/200x100_INDEXALPHA.png'); $this->assertEquals( 'q 150.000000 0 0 75.000000 2.250000 371.250000 cm /IMG9 Do Q', - $testObj->getSetImage($iid, 3, 5, 200, 100, 600) + $import->getSetImage($iid, 3, 5, 200, 100, 600) ); // resize - $iid = $testObj->add(__DIR__ . '/images/200x100_RGB.png', 100, 50, true, 75, true); + $iid = $import->add(__DIR__ . '/images/200x100_RGB.png', 100, 50, true, 75, true); $this->assertEquals( 'q 75.000000 0 0 37.500000 2.250000 408.750000 cm /IMGmask10 Do Q', - $testObj->getSetImage($iid, 3, 5, 100, 50, 600) + $import->getSetImage($iid, 3, 5, 100, 50, 600) ); - $iid = $testObj->add(__DIR__ . '/images/200x100_RGBALPHA.png', 100, 50, true, 75, true); + $iid = $import->add(__DIR__ . '/images/200x100_RGBALPHA.png', 100, 50, true, 75, true); $this->assertEquals( 'q 75.000000 0 0 37.500000 2.250000 408.750000 cm /IMGmask11 Do Q', - $testObj->getSetImage($iid, 3, 5, 100, 50, 600) + $import->getSetImage($iid, 3, 5, 100, 50, 600) ); - $iid = $testObj->add(__DIR__ . '/images/200x100_INDEXALPHA.png', 100, 50, true, 75, true); + $iid = $import->add(__DIR__ . '/images/200x100_INDEXALPHA.png', 100, 50, true, 75, true); $this->assertEquals( 'q 75.000000 0 0 37.500000 2.250000 408.750000 cm /IMGmask12 Do Q', - $testObj->getSetImage($iid, 3, 5, 100, 50, 600) + $import->getSetImage($iid, 3, 5, 100, 50, 600) ); - $iid = $testObj->add(__DIR__ . '/images/200x100_RGB.jpg', 100, 50, false, 75, true, array(1, 2, 3)); + $iid = $import->add(__DIR__ . '/images/200x100_RGB.jpg', 100, 50, false, 75, true, [1, 2, 3]); $this->assertEquals( 'q 75.000000 0 0 37.500000 2.250000 408.750000 cm /IMG13 Do Q', - $testObj->getSetImage($iid, 3, 5, 100, 50, 600) + $import->getSetImage($iid, 3, 5, 100, 50, 600) ); // ICC - $iid = $testObj->add(__DIR__ . '/images/200x100_RGBICC.png'); + $iid = $import->add(__DIR__ . '/images/200x100_RGBICC.png'); $this->assertEquals( 'q 150.000000 0 0 75.000000 2.250000 371.250000 cm /IMG14 Do Q', - $testObj->getSetImage($iid, 3, 5, 200, 100, 600) + $import->getSetImage($iid, 3, 5, 200, 100, 600) ); - $iid = $testObj->add(__DIR__ . '/images/200x100_RGBICC.jpg'); + $iid = $import->add(__DIR__ . '/images/200x100_RGBICC.jpg'); $this->assertEquals( 'q 150.000000 0 0 75.000000 2.250000 371.250000 cm /IMG15 Do Q', - $testObj->getSetImage($iid, 3, 5, 200, 100, 600) + $import->getSetImage($iid, 3, 5, 200, 100, 600) ); - $iid = $testObj->add(__DIR__ . '/images/200x100_RGBINT.png'); + $iid = $import->add(__DIR__ . '/images/200x100_RGBINT.png'); $this->assertEquals( 'q 150.000000 0 0 75.000000 2.250000 371.250000 cm /IMGmask16 Do /IMGplain16 Do Q', - $testObj->getSetImage($iid, 3, 5, 200, 100, 600) + $import->getSetImage($iid, 3, 5, 200, 100, 600) ); - - $iid = $testObj->add(__DIR__ . '/images/200x100_CMYK.jpg'); + $iid = $import->add(__DIR__ . '/images/200x100_CMYK.jpg'); $this->assertEquals( 'q 150.000000 0 0 75.000000 2.250000 371.250000 cm /IMG17 Do Q', - $testObj->getSetImage($iid, 3, 5, 200, 100, 600) + $import->getSetImage($iid, 3, 5, 200, 100, 600) ); - $key = $testObj->getKey(__DIR__ . '/images/200x100_INDEX256.png'); - $data = $testObj->getImageDataByKey($key); + $key = $import->getKey(__DIR__ . '/images/200x100_INDEX256.png'); + $data = $import->getImageDataByKey($key); $this->assertEquals($key, $data['key']); - $iid = $testObj->add('@' . $data['raw']); + $iid = $import->add('@' . $data['raw']); $this->assertEquals( 'q 150.000000 0 0 75.000000 2.250000 371.250000 cm /IMG18 Do Q', - $testObj->getSetImage($iid, 3, 5, 200, 100, 600) + $import->getSetImage($iid, 3, 5, 200, 100, 600) ); // disabled because of libpngerror @@ -207,17 +207,17 @@ public function testAdd() // $testObj->getSetImage($iid, 3, 5, 200, 100, 600) // ); - $out = $testObj->getOutImagesBlock(10); + $out = $import->getOutImagesBlock(10); $this->assertNotEmpty($out); - $this->assertEquals(37, $testObj->getObjectNumber()); + $this->assertEquals(37, $import->getObjectNumber()); - $xob = $testObj->getXobjectDict(); + $xobjectDict = $import->getXobjectDict(); $this->assertEquals( ' /IMG1 11 0 R /IMG2 12 0 R /IMG3 13 0 R /IMG4 15 0 R /IMG5 17 0 R /IMG6 18 0 R' . ' /IMG7 11 0 R /IMGplain8 20 0 R /IMG9 22 0 R /IMG13 27 0 R /IMG14 29 0 R /IMG15 31 0 R' . ' /IMGplain16 33 0 R /IMG17 35 0 R /IMG18 37 0 R', - $xob + $xobjectDict ); } } diff --git a/test/TestUtil.php b/test/TestUtil.php index 26225f7..5dc00b2 100644 --- a/test/TestUtil.php +++ b/test/TestUtil.php @@ -3,13 +3,13 @@ /** * TestUtil.php * - * @since 2020-12-19 - * @category Library - * @package PdfImage - * @author Nicola Asuni - * @copyright 2015-2023 Nicola Asuni - Tecnick.com LTD - * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) - * @link https://github.com/tecnickcom/tc-lib-pdf-image + * @since 2020-12-19 + * @category Library + * @package PdfImage + * @author Nicola Asuni + * @copyright 2015-2023 Nicola Asuni - Tecnick.com LTD + * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) + * @link https://github.com/tecnickcom/tc-lib-pdf-image * * This file is part of tc-lib-color software library. */ @@ -21,23 +21,21 @@ /** * Web Color class test * - * @since 2020-12-19 - * @category Library - * @package PdfImage - * @author Nicola Asuni - * @copyright 2015-2023 Nicola Asuni - Tecnick.com LTD - * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) - * @link https://github.com/tecnickcom/tc-lib-pdf-image + * @since 2020-12-19 + * @category Library + * @package PdfImage + * @author Nicola Asuni + * @copyright 2015-2023 Nicola Asuni - Tecnick.com LTD + * @license http://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE.TXT) + * @link https://github.com/tecnickcom/tc-lib-pdf-image */ class TestUtil extends TestCase { - public function bcExpectException($exception) + /** + * @param class-string<\Throwable> $exception + */ + public function bcExpectException($exception): void { - if (\is_callable([self::class, 'expectException'])) { - parent::expectException($exception); - return; - } - /* @phpstan-ignore-next-line */ - parent::setExpectedException($exception); + parent::expectException($exception); } }