Feature: Labelprint für Kistenetiketten hinzugefügt
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Exception.php
|
||||
*
|
||||
* @since 2011-05-23
|
||||
* @category Library
|
||||
* @package PdfImage
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2011-2024 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 Com\Tecnick\Pdf\Image;
|
||||
|
||||
/**
|
||||
* Com\Tecnick\Pdf\Image\Exception
|
||||
*
|
||||
* Custom Exception class
|
||||
*
|
||||
* @since 2011-05-23
|
||||
* @category Library
|
||||
* @package PdfImage
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2011-2024 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
|
||||
{
|
||||
}
|
||||
+583
@@ -0,0 +1,583 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Import.php
|
||||
*
|
||||
* @since 2011-05-23
|
||||
* @category Library
|
||||
* @package PdfImage
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2011-2024 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 Com\Tecnick\Pdf\Image;
|
||||
|
||||
use Com\Tecnick\File\File;
|
||||
use Com\Tecnick\Pdf\Image\Exception as ImageException;
|
||||
use Com\Tecnick\Pdf\Image\Import\ImageImportInterface;
|
||||
|
||||
/**
|
||||
* Com\Tecnick\Pdf\Image\Import
|
||||
*
|
||||
* @since 2011-05-23
|
||||
* @category Library
|
||||
* @package PdfImage
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2011-2024 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
|
||||
*
|
||||
* @phpstan-type ImageBaseData 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<int, int>,
|
||||
* 'type': int,
|
||||
* 'width': int,
|
||||
* }
|
||||
*
|
||||
* @phpstan-type ImageRawData 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'?: ImageBaseData,
|
||||
* 'native': bool,
|
||||
* 'obj': int,
|
||||
* 'obj_alt': int,
|
||||
* 'obj_icc': int,
|
||||
* 'obj_pal': int,
|
||||
* 'pal': string,
|
||||
* 'parms': string,
|
||||
* 'plain'?: ImageBaseData,
|
||||
* 'raw': string,
|
||||
* 'recode': bool,
|
||||
* 'recoded': bool,
|
||||
* 'splitalpha': bool,
|
||||
* 'trns': array<int, int>,
|
||||
* 'type': int,
|
||||
* 'width': int,
|
||||
* }
|
||||
*
|
||||
* @SuppressWarnings("PHPMD.ExcessiveClassLength")
|
||||
* @SuppressWarnings("PHPMD.ExcessiveClassComplexity")
|
||||
*/
|
||||
class Import extends \Com\Tecnick\Pdf\Image\Output
|
||||
{
|
||||
/**
|
||||
* Image index.
|
||||
* Count the number of added images.
|
||||
*/
|
||||
protected int $iid = 0;
|
||||
|
||||
/**
|
||||
* Native image types and associated importing class.
|
||||
* (Image types for which we have an import method).
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
private const NATIVE = [
|
||||
IMAGETYPE_PNG => 'Png',
|
||||
IMAGETYPE_JPEG => 'Jpeg',
|
||||
];
|
||||
|
||||
/**
|
||||
* Lossless image types.
|
||||
*
|
||||
* @var array<int>
|
||||
*/
|
||||
protected const LOSSLESS = [
|
||||
IMAGETYPE_GIF, // 1
|
||||
IMAGETYPE_PNG, // 3
|
||||
IMAGETYPE_PSD, // 5
|
||||
IMAGETYPE_BMP, // 6
|
||||
IMAGETYPE_WBMP, // 15
|
||||
IMAGETYPE_XBM, // 16
|
||||
IMAGETYPE_TIFF_II, // 7
|
||||
IMAGETYPE_TIFF_MM, // 8
|
||||
IMAGETYPE_IFF, // 14
|
||||
IMAGETYPE_SWC, // 13
|
||||
IMAGETYPE_ICO, // 17
|
||||
];
|
||||
|
||||
/**
|
||||
* Map number of channels with color space name.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected const COLSPACEMAP = [
|
||||
1 => 'DeviceGray',
|
||||
3 => 'DeviceRGB',
|
||||
4 => 'DeviceCMYK',
|
||||
];
|
||||
|
||||
/**
|
||||
* Add a new image.
|
||||
*
|
||||
* @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 bool $defprint Indicate if the image is the default
|
||||
* for printing when used as alternative image.
|
||||
* @param array<int, int> $altimgs Arrays of alternate image keys.
|
||||
*
|
||||
* @return int Image ID.
|
||||
*/
|
||||
public function add(
|
||||
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] = [
|
||||
'iid' => $this->iid,
|
||||
'key' => $data['key'],
|
||||
'width' => $data['width'],
|
||||
'height' => $data['height'],
|
||||
'defprint' => $defprint,
|
||||
'altimgs' => $altimgs,
|
||||
];
|
||||
return $this->iid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Image key used for caching.
|
||||
*
|
||||
* @param string $image Image file name or content.
|
||||
* @param int $width Width in pixels.
|
||||
* @param int $height Height in pixels.
|
||||
* @param int $quality Quality for JPEG files.
|
||||
*/
|
||||
public function getKey(
|
||||
string $image,
|
||||
int $width = 0,
|
||||
int $height = 0,
|
||||
int $quality = 100,
|
||||
): string {
|
||||
return strtr(
|
||||
rtrim(
|
||||
base64_encode(
|
||||
pack('H*', md5($image . $width . $height . $quality))
|
||||
),
|
||||
'='
|
||||
),
|
||||
'+/',
|
||||
'-_'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an imported image by key.
|
||||
*
|
||||
* @param string $key Image key.
|
||||
*
|
||||
* @return ImageRawData Image raw data array.
|
||||
*/
|
||||
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).
|
||||
*
|
||||
* @return ImageRawData Image raw data array
|
||||
*/
|
||||
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, (int) $width, (int) $height, $quality);
|
||||
|
||||
if (isset($this->cache[$imgkey])) {
|
||||
return $this->cache[$imgkey];
|
||||
}
|
||||
|
||||
$data = $this->getRawData($image);
|
||||
$data['key'] = $imgkey;
|
||||
|
||||
if ($width === null) {
|
||||
$width = $data['width'];
|
||||
}
|
||||
|
||||
$width = max(0, (int) $width);
|
||||
|
||||
if ($height === null) {
|
||||
$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);
|
||||
}
|
||||
|
||||
$data = $this->getData($data, $width, $height, $quality);
|
||||
|
||||
if ($ismask) {
|
||||
$data['mask'] = $data;
|
||||
} elseif (! empty($data['splitalpha'])) {
|
||||
// create 2 separate images: plain + mask
|
||||
$rawdata = $data;
|
||||
$data['plain'] = $this->getResizedRawData($rawdata, $width, $height, false, $quality);
|
||||
$data['plain'] = $this->getData($data['plain'], $width, $height, $quality);
|
||||
$data['mask'] = $this->getAlphaChannelRawData($rawdata);
|
||||
$data['mask'] = $this->getData($data['mask'], $width, $height, $quality);
|
||||
$data['mask']['colspace'] = 'DeviceGray';
|
||||
}
|
||||
|
||||
// store data in cache
|
||||
$this->cache[$imgkey] = $data;
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the relevant data from the image.
|
||||
*
|
||||
* @param ImageRawData $data Image raw data.
|
||||
* @param int $width Width in pixels.
|
||||
* @param int $height Height in pixels.
|
||||
* @param int $quality Quality for JPEG files.
|
||||
*
|
||||
* @return ImageRawData Image raw data array.
|
||||
*/
|
||||
protected function getData(
|
||||
array $data,
|
||||
int $width,
|
||||
int $height,
|
||||
int $quality
|
||||
): array {
|
||||
if (! $data['native']) {
|
||||
throw new ImageException('Unable to import image');
|
||||
}
|
||||
|
||||
$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 = $imageImport->getData($data);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{
|
||||
* 'type': int,
|
||||
* } $data Image raw data.
|
||||
*/
|
||||
private function createImportImage(array $data): ImageImportInterface
|
||||
{
|
||||
$class = '\\Com\\Tecnick\\Pdf\\Image\\Import\\' . self::NATIVE[$data['type']];
|
||||
return new $class();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get 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').
|
||||
*
|
||||
* @return ImageRawData Image data array.
|
||||
*/
|
||||
protected function getRawData(string $image): array
|
||||
{
|
||||
// default data to return
|
||||
$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 ($image === '' || ((($image[0] === '@') || ($image[0] === '*')) && (strlen($image) === 1))) {
|
||||
throw new ImageException('Empty image');
|
||||
}
|
||||
|
||||
if ($image[0] === '@') { // image from string
|
||||
$data['raw'] = substr($image, 1);
|
||||
return $this->getMetaData($data);
|
||||
}
|
||||
|
||||
if ($image[0] === '*') { // not-embedded external URL
|
||||
$data['exturl'] = true;
|
||||
$image = substr($image, 1);
|
||||
}
|
||||
|
||||
$data['file'] = $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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the image meta data.
|
||||
*
|
||||
* @param ImageBaseData $data Image raw data.
|
||||
*
|
||||
* @return ImageRawData Image raw data array.
|
||||
*/
|
||||
protected function getMetaData(array $data): array
|
||||
{
|
||||
try {
|
||||
$meta = @getimagesizefromstring($data['raw']);
|
||||
} catch (\Exception $exception) {
|
||||
throw new ImageException('Invalid image format: ' . $exception);
|
||||
}
|
||||
|
||||
if ($meta === false) {
|
||||
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);
|
||||
if (isset($meta['bits'])) {
|
||||
$data['bits'] = $meta['bits'];
|
||||
}
|
||||
|
||||
if (isset($meta['channels']) && $meta['channels'] !== 0) {
|
||||
$data['channels'] = (int) $meta['channels'];
|
||||
}
|
||||
|
||||
if (isset(self::COLSPACEMAP[$data['channels']])) {
|
||||
$data['colspace'] = self::COLSPACEMAP[$data['channels']];
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the resized image raw data
|
||||
* (always convert the image type to a native format: PNG or JPEG).
|
||||
*
|
||||
* @param ImageBaseData $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 ImageRawData Image raw data array.
|
||||
*
|
||||
* @SuppressWarnings("PHPMD.CyclomaticComplexity")
|
||||
* @SuppressWarnings("PHPMD.NPathComplexity")
|
||||
*/
|
||||
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);
|
||||
|
||||
if ($alpha) {
|
||||
$trid = imagecolorallocate($newimg, 0, 0, 0);
|
||||
if ($trid === false) {
|
||||
throw new ImageException('Unable to allocate alpha color for transparency');
|
||||
}
|
||||
imagecolortransparent($newimg, $trid);
|
||||
} else {
|
||||
$tid = imagecolortransparent($img);
|
||||
$palsize = imagecolorstotal($img);
|
||||
if (($tid >= 0) && ($palsize > 0) && ($tid < $palsize)) {
|
||||
// set transparency for Indexed image
|
||||
$tcol = imagecolorsforindex($img, $tid);
|
||||
$trid = imagecolorallocate($newimg, $tcol['red'], $tcol['green'], $tcol['blue']);
|
||||
if ($trid === false) {
|
||||
throw new ImageException('Unable to allocate color for transparency');
|
||||
}
|
||||
imagefill($newimg, 0, 0, $trid);
|
||||
imagecolortransparent($newimg, $trid);
|
||||
}
|
||||
}
|
||||
|
||||
imagealphablending($newimg, ! $alpha);
|
||||
imagesavealpha($newimg, $alpha);
|
||||
|
||||
imagecopyresampled($newimg, $img, 0, 0, 0, 0, $width, $height, $data['width'], $data['height']);
|
||||
|
||||
ob_start();
|
||||
if ($data['mapto'] == IMAGETYPE_PNG) {
|
||||
imagepng($newimg, null, 9, PNG_ALL_FILTERS);
|
||||
} else {
|
||||
imagejpeg($newimg, null, $quality);
|
||||
}
|
||||
$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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the alpha channel as separate image to be used as a mask.
|
||||
*
|
||||
* @param ImageBaseData $data Image raw data as returned by getImageRawData.
|
||||
*
|
||||
* @return ImageRawData Image raw data array.
|
||||
*/
|
||||
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(
|
||||
max(1, $data['width']),
|
||||
max(1, $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) (((float) (127 - $color['alpha']) / 127) ** 2.2 * 255);
|
||||
imagesetpixel($newimg, $xpx, $ypx, $alpha);
|
||||
}
|
||||
}
|
||||
|
||||
ob_start();
|
||||
imagepng($newimg, null, 9, PNG_ALL_FILTERS);
|
||||
$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;
|
||||
$data['recoded'] = true;
|
||||
$data['ismask'] = true;
|
||||
return $this->getMetaData($data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* ImageImportInterface.php
|
||||
*
|
||||
* @since 2011-05-23
|
||||
* @category Library
|
||||
* @package PdfImage
|
||||
* @author jmleroux <jmleroux.pro@gmail.com>
|
||||
* @copyright 2011-2024 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 Com\Tecnick\Pdf\Image\Import;
|
||||
|
||||
/**
|
||||
* Com\Tecnick\Pdf\Image\Import\Jpeg
|
||||
*
|
||||
* @since 2011-05-23
|
||||
* @category Library
|
||||
* @package PdfImage
|
||||
* @author jmleroux <jmleroux.pro@gmail.com>
|
||||
* @copyright 2011-2024 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
|
||||
*
|
||||
* @phpstan-import-type ImageBaseData from \Com\Tecnick\Pdf\Image\Import
|
||||
*/
|
||||
interface ImageImportInterface
|
||||
{
|
||||
/**
|
||||
* Extract data from an image.
|
||||
*
|
||||
* @param ImageBaseData $data Image raw data.
|
||||
*
|
||||
* @return ImageBaseData Image raw data array.
|
||||
*/
|
||||
public function getData(array $data): array;
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,301 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Png.php
|
||||
*
|
||||
* @since 2011-05-23
|
||||
* @category Library
|
||||
* @package PdfImage
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2011-2024 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 Com\Tecnick\Pdf\Image\Import;
|
||||
|
||||
use Com\Tecnick\File\Byte;
|
||||
use Com\Tecnick\Pdf\Image\Exception as ImageException;
|
||||
|
||||
/**
|
||||
* Com\Tecnick\Pdf\Image\Import\Png
|
||||
*
|
||||
* @since 2011-05-23
|
||||
* @category Library
|
||||
* @package PdfImage
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2011-2024 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
|
||||
*
|
||||
* @phpstan-import-type ImageBaseData from \Com\Tecnick\Pdf\Image\Import
|
||||
*/
|
||||
class Png implements ImageImportInterface
|
||||
{
|
||||
/**
|
||||
* Extract data from a PNG image.
|
||||
*
|
||||
* @param ImageBaseData $data Image raw data.
|
||||
*
|
||||
* @return ImageBaseData Image raw data array.
|
||||
*/
|
||||
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)) {
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new ImageException('Not a PNG image');
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
$offset += 8;
|
||||
$offset += 4;
|
||||
|
||||
$data = $this->getIhdrChunk($data, $offset);
|
||||
|
||||
$offset += 3;
|
||||
|
||||
// check compression, filter and interlacing settings
|
||||
if (
|
||||
($byte->getByte($offset - 3) != 0)
|
||||
|| ($byte->getByte($offset - 2) != 0)
|
||||
|| ($byte->getByte($offset - 1) != 0)
|
||||
) {
|
||||
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 (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']
|
||||
. ' /BitsPerComponent ' . $data['bits']
|
||||
. ' /Columns ' . $data['width']
|
||||
. ' >>';
|
||||
|
||||
$offset += 4;
|
||||
return $this->getChunks($data, $offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the IHDR chunk 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 ImageBaseData $data Image raw data.
|
||||
* @param int $offset Current byte offset.
|
||||
*
|
||||
* @return ImageBaseData Image raw data array.
|
||||
*/
|
||||
protected function getIhdrChunk(array $data, int &$offset): array
|
||||
{
|
||||
$byte = new Byte($data['raw']);
|
||||
if (substr($data['raw'], $offset, 4) != 'IHDR') {
|
||||
// @codeCoverageIgnoreStart
|
||||
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;
|
||||
$chc = $byte->getByte($offset); // channels code
|
||||
++$offset;
|
||||
$data['channels'] = (($chc == 2) ? 3 : 1);
|
||||
$chcmap = [
|
||||
0 => 'DeviceGray',
|
||||
2 => 'DeviceRGB',
|
||||
3 => 'Indexed',
|
||||
4 => 'DeviceGray+Alpha',
|
||||
6 => 'DeviceRGB+Alpha',
|
||||
];
|
||||
if (isset($chcmap[$chc])) {
|
||||
$data['colspace'] = $chcmap[$chc];
|
||||
} else {
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new ImageException('Unknown color mode');
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract chunks data from a PNG image.
|
||||
*
|
||||
* @param ImageBaseData $data Image raw data.
|
||||
* @param int $offset Current byte offset.
|
||||
*
|
||||
* @return ImageBaseData Image raw data array.
|
||||
*/
|
||||
protected function getChunks(array $data, int $offset): array
|
||||
{
|
||||
$byte = new Byte($data['raw']);
|
||||
while (($len = $byte->getULong($offset)) >= 0) {
|
||||
$offset += 4;
|
||||
$type = substr($data['raw'], $offset, 4);
|
||||
$offset += 4;
|
||||
if ($type == 'PLTE') {
|
||||
$data = $this->getPlteChunk($data, $offset, $len);
|
||||
} elseif ($type == 'tRNS') {
|
||||
$data = $this->getTrnsChunk($data, $offset, $len);
|
||||
} elseif ($type == 'IDAT') {
|
||||
$data = $this->getIdatChunk($data, $offset, $len);
|
||||
} elseif ($type == 'iCCP') {
|
||||
$data = $this->getIccpChunk($byte, $data, $offset, $len);
|
||||
} elseif ($type == 'IEND') {
|
||||
// The image trailer chunk (IEND) must be the final chunk
|
||||
// and marks the end of the PNG file or data stream.
|
||||
break;
|
||||
} else {
|
||||
$offset += $len;
|
||||
$offset += 4;
|
||||
}
|
||||
}
|
||||
|
||||
if (($data['colspace'] == 'Indexed') && (empty($data['pal']))) {
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new ImageException('The color palette is missing');
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the PLTE chunk data.
|
||||
*
|
||||
* 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 ImageBaseData $data Image raw data.
|
||||
* @param int $offset Current byte offset.
|
||||
* @param int $len NUmber of bytes in this chunk.
|
||||
*
|
||||
* @return ImageBaseData Image raw data array.
|
||||
*/
|
||||
protected function getPlteChunk(array $data, int &$offset, int $len): array
|
||||
{
|
||||
$data['pal'] = substr($data['raw'], $offset, $len);
|
||||
$offset += $len;
|
||||
$offset += 4;
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the tRNS chunk data.
|
||||
*
|
||||
* @param ImageBaseData $data Image raw data.
|
||||
* @param int $offset Current byte offset.
|
||||
* @param int $len NUmber of bytes in this chunk.
|
||||
*
|
||||
* @return ImageBaseData Image raw data array.
|
||||
*/
|
||||
protected function getTrnsChunk(array $data, int &$offset, int $len): array
|
||||
{
|
||||
// read transparency info
|
||||
$trns = substr($data['raw'], $offset, $len);
|
||||
$offset += $len;
|
||||
if ($data['colspace'] == 'DeviceGray') {
|
||||
// DeviceGray
|
||||
$data['trns'][] = ord($trns[1]);
|
||||
} elseif ($data['colspace'] == 'DeviceRGB') {
|
||||
// DeviceRGB
|
||||
$data['trns'][] = ord($trns[1]);
|
||||
$data['trns'][] = ord($trns[3]);
|
||||
$data['trns'][] = ord($trns[5]);
|
||||
} else {
|
||||
// Indexed
|
||||
$data['trns'] = array_map('ord', str_split($trns));
|
||||
}
|
||||
|
||||
$offset += 4;
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the IDAT chunk data.
|
||||
*
|
||||
* 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 ImageBaseData $data Image raw data.
|
||||
* @param int $offset Current byte offset.
|
||||
* @param int $len NUmber of bytes in this chunk.
|
||||
*
|
||||
* @return ImageBaseData Image raw data array.
|
||||
*/
|
||||
protected function getIdatChunk(array $data, int &$offset, int $len): array
|
||||
{
|
||||
$data['data'] .= substr($data['raw'], $offset, $len);
|
||||
$offset += $len;
|
||||
$offset += 4;
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the iCCP chunk data.
|
||||
*
|
||||
* @param Byte $byte Byte class object.
|
||||
* @param ImageBaseData $data Image raw data.
|
||||
* @param int $offset Current byte offset.
|
||||
* @param int $len NUmber of bytes in this chunk.
|
||||
*
|
||||
* @return ImageBaseData Image raw data array.
|
||||
*/
|
||||
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);
|
||||
$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;
|
||||
}
|
||||
}
|
||||
+472
@@ -0,0 +1,472 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Output.php
|
||||
*
|
||||
* @since 2011-05-23
|
||||
* @category Library
|
||||
* @package PdfImage
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2011-2024 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 Com\Tecnick\Pdf\Image;
|
||||
|
||||
use Com\Tecnick\Pdf\Encrypt\Encrypt;
|
||||
use Com\Tecnick\Pdf\Image\Exception as ImageException;
|
||||
|
||||
/**
|
||||
* Com\Tecnick\Pdf\Image\Output
|
||||
*
|
||||
* @since 2011-05-23
|
||||
* @category Library
|
||||
* @package PdfImage
|
||||
* @author Nicola Asuni <info@tecnick.com>
|
||||
* @copyright 2011-2024 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
|
||||
*
|
||||
* @phpstan-import-type ImageBaseData from \Com\Tecnick\Pdf\Image\Import
|
||||
* @phpstan-import-type ImageRawData from \Com\Tecnick\Pdf\Image\Import
|
||||
*
|
||||
* @SuppressWarnings("PHPMD.ExcessiveClassComplexity")
|
||||
*/
|
||||
abstract class Output
|
||||
{
|
||||
/**
|
||||
* Current PDF object number.
|
||||
*/
|
||||
protected int $pon;
|
||||
|
||||
/**
|
||||
* Store image object IDs for the XObject Dictionary.
|
||||
*
|
||||
* @var array<string, int>
|
||||
*/
|
||||
protected array $xobjdict = [];
|
||||
|
||||
/**
|
||||
* Stack of added images.
|
||||
*
|
||||
* @var array<int, array{
|
||||
* 'iid': int,
|
||||
* 'key': string,
|
||||
* 'width': int,
|
||||
* 'height': int,
|
||||
* 'defprint': bool,
|
||||
* 'altimgs'?: array<int, int>,
|
||||
* }>
|
||||
*/
|
||||
protected array $image = [];
|
||||
|
||||
/**
|
||||
* Cache used to store imported image data.
|
||||
* The same image data can be reused multiple times.
|
||||
*
|
||||
* @var array<string, ImageRawData>
|
||||
*/
|
||||
protected array $cache = [];
|
||||
|
||||
/**
|
||||
* Initialize images data.
|
||||
*
|
||||
* @param float $kunit Unit of measure conversion ratio.
|
||||
* @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(
|
||||
protected float $kunit,
|
||||
/**
|
||||
* Encrypt object.
|
||||
*/
|
||||
protected Encrypt $encrypt,
|
||||
protected bool $pdfa = false,
|
||||
protected bool $compress = true
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current PDF object number.
|
||||
*/
|
||||
public function getObjectNumber(): int
|
||||
{
|
||||
return $this->pon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the PDF output string to print the specified image ID.
|
||||
*
|
||||
* @param int $iid Image ID.
|
||||
* @param float $xpos Abscissa (X coordinate) of the upper-left Image corner in user units.
|
||||
* @param float $ypos Ordinate (Y coordinate) of the upper-left Image corner in user units.
|
||||
* @param float $width Image width in user units.
|
||||
* @param float $height Image height in user units.
|
||||
* @param float $pageheight Page height in user units.
|
||||
*
|
||||
* @return string Image PDF page content.
|
||||
*/
|
||||
public function getSetImage(
|
||||
int $iid,
|
||||
float $xpos,
|
||||
float $ypos,
|
||||
float $width,
|
||||
float $height,
|
||||
float $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',
|
||||
($width * $this->kunit),
|
||||
($height * $this->kunit),
|
||||
($xpos * $this->kunit),
|
||||
(($pageheight - $ypos - $height) * $this->kunit) // reverse coordinate
|
||||
);
|
||||
|
||||
if (empty($this->cache[$this->image[$iid]['key']]['mask'])) {
|
||||
return $out . ' /IMG' . $iid . ' Do Q' . "\n";
|
||||
}
|
||||
|
||||
if (empty($this->cache[$this->image[$iid]['key']]['plain'])) {
|
||||
return $out . ' /IMGmask' . $iid . ' Do Q' . "\n";
|
||||
}
|
||||
|
||||
return $out . ' /IMGplain' . $iid . ' Do Q' . "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the PDF output string for Images.
|
||||
*
|
||||
* @param int $pon Current PDF Object Number.
|
||||
*
|
||||
* @return string PDF code for the images block.
|
||||
*/
|
||||
public function getOutImagesBlock(int $pon): string
|
||||
{
|
||||
$this->pon = $pon;
|
||||
$out = '';
|
||||
foreach ($this->image as $iid => $img) {
|
||||
if (empty($this->cache[$img['key']]['out'])) {
|
||||
if (empty($this->cache[$img['key']]['mask'])) {
|
||||
$out .= $this->getOutImage($img, $this->cache[$img['key']]);
|
||||
} else {
|
||||
$out .= $this->getOutImage($img, $this->cache[$img['key']]['mask'], 'mask');
|
||||
if (!empty($this->cache[$img['key']]['plain'])) {
|
||||
$out .= $this->getOutImage($img, $this->cache[$img['key']]['plain'], 'plain');
|
||||
}
|
||||
}
|
||||
|
||||
$this->image[$iid] = $img;
|
||||
}
|
||||
|
||||
if (empty($this->cache[$img['key']]['mask']['obj'])) {
|
||||
$this->xobjdict['IMG' . $img['iid']] = $this->cache[$img['key']]['obj'];
|
||||
} else {
|
||||
if (empty($this->cache[$img['key']]['plain']['obj'])) {
|
||||
$this->xobjdict['IMGmask' . $img['iid']] = $this->cache[$img['key']]['mask']['obj'];
|
||||
} else {
|
||||
$this->xobjdict['IMGplain' . $img['iid']] = $this->cache[$img['key']]['plain']['obj'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the PDF output string for Image object.
|
||||
*
|
||||
* @param array{
|
||||
* 'iid': int,
|
||||
* 'key': string,
|
||||
* 'width': int,
|
||||
* 'height': int,
|
||||
* 'defprint': bool,
|
||||
* 'altimgs'?: array<int, int>,
|
||||
* } $img Image reference.
|
||||
* @param ImageBaseData $data Image raw data.
|
||||
* @param string $sub Sub image ('mask', 'plain' or empty string).
|
||||
*
|
||||
* @return string PDF Image object.
|
||||
*/
|
||||
protected function getOutImage(
|
||||
array &$img,
|
||||
array &$data,
|
||||
string $sub = '',
|
||||
): string {
|
||||
$out = $this->getOutIcc($data)
|
||||
. $this->getOutPalette($data)
|
||||
. $this->getOutAltImages($img, $data, $sub);
|
||||
|
||||
$data['obj'] = ++$this->pon;
|
||||
|
||||
$out .= $data['obj'] . ' 0 obj' . "\n"
|
||||
. '<<'
|
||||
. ' /Type /XObject'
|
||||
. ' /Subtype /Image'
|
||||
. ' /Width ' . $data['width']
|
||||
. ' /Height ' . $data['height']
|
||||
. $this->getOutColorInfo($data);
|
||||
|
||||
if (! empty($data['exturl'])) {
|
||||
// external stream
|
||||
$out .= ' /Length 0 /F'
|
||||
. ' <<'
|
||||
. ' /FS /URL /F '
|
||||
. $this->encrypt->escapeDataString(empty($data['exturl']) ? 'false' : 'true', $this->pon)
|
||||
. ' >>';
|
||||
if (! empty($data['filter'])) {
|
||||
$out .= ' /FFilter /' . $data['filter'];
|
||||
}
|
||||
|
||||
$out .= ' >>' . "\n";
|
||||
} else {
|
||||
if (! empty($data['filter'])) {
|
||||
$out .= ' /Filter /' . $data['filter'];
|
||||
}
|
||||
|
||||
if (! empty($data['parms'])) {
|
||||
$out .= ' ' . $data['parms'];
|
||||
}
|
||||
|
||||
// Colour Key Masking
|
||||
if (! empty($data['trns'])) {
|
||||
$trns = $this->getOutTransparency($data);
|
||||
if ($trns !== '') {
|
||||
$out .= ' /Mask [ ' . $trns . ']';
|
||||
}
|
||||
}
|
||||
|
||||
$stream = $this->encrypt->encryptString($data['data'], $this->pon);
|
||||
$out .= ' /Length ' . strlen($stream)
|
||||
. '>> stream' . "\n"
|
||||
. $stream . "\n"
|
||||
. 'endstream' . "\n";
|
||||
}
|
||||
|
||||
$out .= 'endobj' . "\n";
|
||||
|
||||
$this->cache[$img['key']]['out'] = true; // mark this as done
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return XObjects Dictionary.
|
||||
*
|
||||
* @param array<int> $keys Image IDs.
|
||||
*/
|
||||
public function getXobjectDictByKeys(array $keys): string
|
||||
{
|
||||
if (empty($keys)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$out = '';
|
||||
|
||||
foreach ($keys as $iid) {
|
||||
$key = 'IMG' . $iid;
|
||||
if (!empty($this->xobjdict[$key])) {
|
||||
$out .= ' /' . $key . ' ' . $this->xobjdict[$key] . ' 0 R';
|
||||
continue;
|
||||
}
|
||||
$key = 'IMGplain' . $iid;
|
||||
if (!empty($this->xobjdict[$key])) {
|
||||
$out .= ' /' . $key . ' ' . $this->xobjdict[$key] . ' 0 R';
|
||||
continue;
|
||||
}
|
||||
$key = 'IMGmask' . $iid;
|
||||
if (!empty($this->xobjdict[$key])) {
|
||||
$out .= ' /' . $key . ' ' . $this->xobjdict[$key] . ' 0 R';
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the PDF output string for ICC object.
|
||||
*
|
||||
* @param ImageBaseData $data Image raw data.
|
||||
*/
|
||||
protected function getOutIcc(array &$data): string
|
||||
{
|
||||
if (empty($data['icc'])) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$data['obj_icc'] = ++$this->pon;
|
||||
$out = $data['obj_icc'] . ' 0 obj' . "\n"
|
||||
. '<<'
|
||||
. ' /N ' . $data['channels']
|
||||
. ' /Alternate /' . $data['colspace'];
|
||||
$icc = $data['icc'];
|
||||
if ($this->compress) {
|
||||
$out .= ' /Filter /FlateDecode';
|
||||
$cicc = gzcompress($icc);
|
||||
if ($cicc !== false) {
|
||||
$icc = $cicc;
|
||||
}
|
||||
}
|
||||
|
||||
$stream = $this->encrypt->encryptString($icc, $this->pon);
|
||||
return $out . (' /Length ' . strlen($stream)
|
||||
. ' >>'
|
||||
. ' stream' . "\n"
|
||||
. $stream . "\n"
|
||||
. 'endstream' . "\n"
|
||||
. 'endobj' . "\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the PDF output string for Indexed palette object.
|
||||
*
|
||||
* @param ImageBaseData $data Image raw 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';
|
||||
$cpal = gzcompress($pal);
|
||||
if ($cpal !== false) {
|
||||
$pal = $cpal;
|
||||
}
|
||||
}
|
||||
|
||||
$stream = $this->encrypt->encryptString($pal, $this->pon);
|
||||
return $out . (' /Length ' . strlen($stream)
|
||||
. '>>'
|
||||
. ' stream' . "\n"
|
||||
. $stream . "\n"
|
||||
. 'endstream' . "\n"
|
||||
. 'endobj' . "\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the PDF output string for color and mask information.
|
||||
*
|
||||
* @param ImageBaseData $data Image raw data.
|
||||
*/
|
||||
protected function getOutColorInfo(array $data): string
|
||||
{
|
||||
$out = '';
|
||||
// set color space
|
||||
if (! empty($data['obj_icc'])) {
|
||||
// ICC Colour Space
|
||||
$out .= ' /ColorSpace [/ICCBased ' . $data['obj_icc'] . ' 0 R]';
|
||||
} elseif (! empty($data['obj_pal'])) {
|
||||
// Indexed Colour Space
|
||||
$out .= ' /ColorSpace [/Indexed /DeviceRGB '
|
||||
. ((strlen($data['pal']) / 3) - 1)
|
||||
. ' ' . $data['obj_pal'] . ' 0 R]';
|
||||
} else {
|
||||
// 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'])) {
|
||||
$out .= ' /SMask ' . $this->cache[$data['key']]['mask']['obj'] . ' 0 R';
|
||||
}
|
||||
|
||||
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{
|
||||
* 'iid': int,
|
||||
* 'key': string,
|
||||
* 'width': int,
|
||||
* 'height': int,
|
||||
* 'defprint': bool,
|
||||
* 'altimgs'?: array<int, int>,
|
||||
* } $img Image reference.
|
||||
* @param ImageBaseData $data Image raw data.
|
||||
* @param string $sub Sub image ('mask', 'plain' or empty string).
|
||||
*/
|
||||
protected function getOutAltImages(
|
||||
array $img,
|
||||
array &$data,
|
||||
string $sub = '',
|
||||
): string {
|
||||
if ($this->pdfa || empty($img['altimgs']) || ($sub == 'mask')) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$data['obj_alt'] = ++$this->pon;
|
||||
|
||||
$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'
|
||||
. ' /DefaultForPrinting ' . (empty($this->image[$iid]['defprint']) ? 'false' : 'true')
|
||||
. ' >>';
|
||||
}
|
||||
}
|
||||
|
||||
return $out . (' ]' . "\n"
|
||||
. 'endobj' . "\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the PDF output string for color and mask information.
|
||||
*
|
||||
* @param array{
|
||||
* 'trns': array<int, int>,
|
||||
* } $data Image raw data.
|
||||
*/
|
||||
protected function getOutTransparency(array $data): string
|
||||
{
|
||||
$trns = '';
|
||||
foreach ($data['trns'] as $idx => $val) {
|
||||
if ($val == 0) {
|
||||
$trns .= $idx . ' ' . $idx . ' ';
|
||||
}
|
||||
}
|
||||
|
||||
return $trns;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user