Feature: Labelprint für Kistenetiketten hinzugefügt

This commit is contained in:
2025-10-27 12:14:44 +01:00
parent 43bc416554
commit 14bae6c9ef
1068 changed files with 229014 additions and 1807 deletions

View File

@@ -0,0 +1,593 @@
<?php
/**
* MetaInfo.php
*
* @since 2002-08-03
* @category Library
* @package Pdf
* @author Nicola Asuni <info@tecnick.com>
* @copyright 2002-2025 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
*
* This file is part of tc-lib-pdf software library.
*/
namespace Com\Tecnick\Pdf;
use Com\Tecnick\Pdf\Exception as PdfException;
/**
* Com\Tecnick\Pdf\MetaInfo
*
* Meta Informaton PDF class
*
* @since 2002-08-03
* @category Library
* @package Pdf
* @author Nicola Asuni <info@tecnick.com>
* @copyright 2002-2025 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
*
* @phpstan-import-type TViewerPref from Base
*
* @SuppressWarnings("PHPMD.DepthOfInheritance")
*/
abstract class MetaInfo extends \Com\Tecnick\Pdf\JavaScript
{
/**
* Valid document zoom modes
*
* @var array<string>
*/
protected const VALIDZOOM = ['fullpage', 'fullwidth', 'real', 'default'];
/**
* Return the program version.
*/
public function getVersion(): string
{
return $this->version;
}
/**
* Set a field value only if it is not empty.
*
* @param string $field Field name
* @param string $value Value to set
*/
private function setNonEmptyFieldValue(string $field, string $value): static
{
if ($value !== '') {
$this->$field = $value;
}
return $this;
}
/**
* Set the value of an existing array key if it is not empty.
*
* @param string $field Field array name
* @param string $key Key name
* @param string $value Value to set
*/
private function setNonEmptyArrayFieldValue(string $field, string $key, string $value): static
{
if (
isset($this->$field)
&& is_array($this->$field)
&& ($key !== '')
&& isset($this->$field[$key])
&& ($value !== '')
) {
$this->$field[$key] = $value;
}
return $this;
}
/**
* Defines the creator of the document.
* This is typically the name of the application that generates the PDF.
*
* @param string $creator The name of the creator.
*/
public function setCreator(string $creator): static
{
return $this->setNonEmptyFieldValue('creator', $creator);
}
/**
* Defines the author of the document.
*
* @param string $author The name of the author.
*/
public function setAuthor(string $author): static
{
return $this->setNonEmptyFieldValue('author', $author);
}
/**
* Defines the subject of the document.
*
* @param string $subject The subject.
*/
public function setSubject(string $subject): static
{
return $this->setNonEmptyFieldValue('subject', $subject);
}
/**
* Defines the title of the document.
*
* @param string $title The title.
*/
public function setTitle(string $title): static
{
return $this->setNonEmptyFieldValue('title', $title);
}
/**
* Associates keywords with the document, generally in the form 'keyword1 keyword2 ...'.
*
* @param string $keywords Space-separated list of keywords.
*/
public function setKeywords(string $keywords): static
{
return $this->setNonEmptyFieldValue('keywords', $keywords);
}
/**
* Set the PDF version (check PDF reference for valid values).
*
* @param string $version PDF document version.
*
* @throw PdfException in case of error.
*/
public function setPDFVersion(string $version = '1.7'): static
{
if ($this->pdfa == 1) { // PDF/A 1 mode
$this->pdfver = '1.4';
return $this;
}
$isvalid = preg_match('/^[1-9]+[.]\d+$/', $version);
if ($isvalid === false) {
throw new PdfException('Invalid PDF version format');
}
$this->pdfver = $version;
return $this;
}
/**
* Set the sRGB mode
*
* @param bool $enabled Set to true to add the default sRGB ICC color profile
*/
public function setSRGB(bool $enabled): static
{
$this->sRGB = $enabled;
return $this;
}
/**
* Format a text string for output.
*
* @param string $str String to escape.
* @param int $oid Current PDF object number.
* @param bool $bom If true set the Byte Order Mark (BOM).
*
* @return string escaped string.
*/
protected function getOutTextString(
string $str,
int $oid,
bool $bom = false
): string {
if ($this->isunicode) {
$str = $this->uniconv->toUTF16BE($str);
if ($bom) {
$str = "\xFE\xFF" . $str; // Byte Order Mark (BOM)
}
}
return $this->encrypt->escapeDataString($str, $oid);
}
/**
* Returns a formatted date for meta information
*
* @param int $time Time in seconds.
*
* @return string date-time string.
*/
protected function getFormattedDate(int $time): string
{
return substr_replace(date('YmdHisO', $time), "'", (-2), 0) . "'";
}
/**
* Returns a formatted date for XMP meta information
*
* @param int $time Time in seconds.
*
* @return string date-time string.
*/
protected function getXMPFormattedDate(int $time): string
{
return date('Y-m-dTH:i:sP', $time);
}
/**
* Returns the producer string
*/
protected function getProducer(): string
{
return "\x54\x43\x50\x44\x46\x20"
. $this->version
. "\x20\x28\x68\x74\x74\x70\x73\x3a\x2f\x2f"
. "\x74\x63\x70\x64\x66\x2e\x6f\x72\x67\x29";
}
/**
* Returns a formatted date for meta information
*
* @param int $time Time in seconds.
* @param int $oid Current PDF object number.
*
* @return string escaped date-time string.
*/
protected function getOutDateTimeString(int $time, int $oid): string
{
if ($time === 0) {
$time = $this->doctime;
}
return $this->encrypt->escapeDataString('D:' . $this->getFormattedDate($time), $oid);
}
/**
* Get the PDF output string for the Document Information Dictionary.
* (ref. Chapter 14.3.3 Document Information Dictionary of PDF32000_2008.pdf)
*/
protected function getOutMetaInfo(): string
{
$oid = ++$this->pon;
$this->objid['info'] = $oid;
return $oid . ' 0 obj' . "\n"
. '<<'
. ' /Creator ' . $this->getOutTextString($this->creator, $oid, true)
. ' /Author ' . $this->getOutTextString($this->author, $oid, true)
. ' /Subject ' . $this->getOutTextString($this->subject, $oid, true)
. ' /Title ' . $this->getOutTextString($this->title, $oid, true)
. ' /Keywords ' . $this->getOutTextString($this->keywords, $oid, true)
. ' /Producer ' . $this->getOutTextString($this->getProducer(), $oid, true)
. ' /CreationDate ' . $this->getOutDateTimeString($this->doctime, $oid)
. ' /ModDate ' . $this->getOutDateTimeString($this->docmodtime, $oid)
. ' /Trapped /False'
. ' >>' . "\n"
. 'endobj' . "\n";
}
/**
* Escape some special characters (&lt; &gt; &amp;) for XML output.
*
* @param string $str Input string to escape.
*/
protected function getEscapedXML(string $str): string
{
return strtr($str, [
"\0" => '',
'&' => '&amp;',
'<' => '&lt;',
'>' => '&gt;',
]);
}
/**
* Set additional custom XMP data to be appended just before the end of the tag indicated by the key.
*
* IMPORTANT:
* This data is added as-is without controls, so you have to validate your data before using this method.
*
* @param string $key Key for the custom XMP data. Valid keys are:
* - 'x:xmpmeta'
* - 'x:xmpmeta.rdf:RDF'
* - 'x:xmpmeta.rdf:RDF.rdf:Description'
* - 'x:xmpmeta.rdf:RDF.rdf:Description.pdfaExtension:schemas'
* - 'x:xmpmeta.rdf:RDF.rdf:Description.pdfaExtension:schemas.rdf:Bag'
* @param string $xmp Custom XMP data.
*/
public function setCustomXMP(string $key, string $xmp): static
{
return $this->setNonEmptyArrayFieldValue('custom_xmp', $key, $xmp);
}
/**
* Get the PDF output string for the XMP data object
*
* @SuppressWarnings("PHPMD.ExcessiveMethodLength")
*/
protected function getOutXMP(): string
{
$uuid = 'uuid:' . substr($this->fileid, 0, 8)
. '-' . substr($this->fileid, 8, 4)
. '-' . substr($this->fileid, 12, 4)
. '-' . substr($this->fileid, 16, 4)
. '-' . substr($this->fileid, 20, 12);
// @codingStandardsIgnoreStart
$xmp = '<?xpacket begin="' . $this->uniconv->chr(0xfeff) . '" id="W5M0MpCehiHzreSzNTczkc9d"?>' . "\n"
. '<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 4.2.1-c043 52.372728, 2009/01/18-15:08:04">' . "\n"
. "\t" . '<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">' . "\n"
. "\t\t" . '<rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">' . "\n"
. "\t\t\t" . '<dc:format>application/pdf</dc:format>' . "\n"
. "\t\t\t" . '<dc:title>' . "\n"
. "\t\t\t\t" . '<rdf:Alt>' . "\n"
. "\t\t\t\t\t" . '<rdf:li xml:lang="x-default">' . $this->getEscapedXML($this->title) . '</rdf:li>' . "\n"
. "\t\t\t\t" . '</rdf:Alt>' . "\n"
. "\t\t\t" . '</dc:title>' . "\n"
. "\t\t\t" . '<dc:creator>' . "\n"
. "\t\t\t\t" . '<rdf:Seq>' . "\n"
. "\t\t\t\t\t" . '<rdf:li>' . $this->getEscapedXML($this->author) . '</rdf:li>' . "\n"
. "\t\t\t\t" . '</rdf:Seq>' . "\n"
. "\t\t\t" . '</dc:creator>' . "\n"
. "\t\t\t" . '<dc:description>' . "\n"
. "\t\t\t\t" . '<rdf:Alt>' . "\n"
. "\t\t\t\t\t" . '<rdf:li xml:lang="x-default">' . $this->getEscapedXML($this->subject) . '</rdf:li>' . "\n"
. "\t\t\t\t" . '</rdf:Alt>' . "\n"
. "\t\t\t" . '</dc:description>' . "\n"
. "\t\t\t" . '<dc:subject>' . "\n"
. "\t\t\t\t" . '<rdf:Bag>' . "\n"
. "\t\t\t\t\t" . '<rdf:li>' . $this->getEscapedXML($this->keywords) . '</rdf:li>' . "\n"
. "\t\t\t\t" . '</rdf:Bag>' . "\n"
. "\t\t\t" . '</dc:subject>' . "\n"
. "\t\t" . '</rdf:Description>' . "\n"
. "\t\t" . '<rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/">' . "\n"
. "\t\t\t" . '<xmp:CreateDate>' . $this->getXMPFormattedDate($this->doctime) . '</xmp:CreateDate>' . "\n"
. "\t\t\t" . '<xmp:CreatorTool>' . $this->getEscapedXML($this->creator) . '</xmp:CreatorTool>' . "\n"
. "\t\t\t" . '<xmp:ModifyDate>' . $this->getXMPFormattedDate($this->docmodtime) . '</xmp:ModifyDate>' . "\n"
. "\t\t\t" . '<xmp:MetadataDate>' . $this->getXMPFormattedDate($this->doctime) . '</xmp:MetadataDate>' . "\n"
. "\t\t" . '</rdf:Description>' . "\n"
. "\t\t" . '<rdf:Description rdf:about="" xmlns:pdf="http://ns.adobe.com/pdf/1.3/">' . "\n"
. "\t\t\t" . '<pdf:Keywords>' . $this->getEscapedXML($this->keywords) . '</pdf:Keywords>' . "\n"
. "\t\t\t" . '<pdf:Producer>' . $this->getEscapedXML($this->getProducer()) . '</pdf:Producer>' . "\n"
. "\t\t" . '</rdf:Description>' . "\n"
. "\t\t" . '<rdf:Description rdf:about="" xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/">' . "\n"
. "\t\t\t" . '<xmpMM:DocumentID>' . $uuid . '</xmpMM:DocumentID>' . "\n"
. "\t\t\t" . '<xmpMM:InstanceID>' . $uuid . '</xmpMM:InstanceID>' . "\n"
. "\t\t" . '</rdf:Description>' . "\n";
if ($this->pdfa !== 0) {
$xmp .= ' <rdf:Description rdf:about="" xmlns:pdfaid="http://www.aiim.org/pdfa/ns/id/">' . "\n"
. "\t\t\t" . '<pdfaid:part>' . $this->pdfa . '</pdfaid:part>' . "\n"
. "\t\t\t" . '<pdfaid:conformance>B</pdfaid:conformance>' . "\n"
. "\t\t" . '</rdf:Description>' . "\n";
}
// XMP extension schemas
$xmp .= "\t\t" . '<rdf:Description rdf:about="" xmlns:pdfaExtension="http://www.aiim.org/pdfa/ns/extension/" xmlns:pdfaSchema="http://www.aiim.org/pdfa/ns/schema#" xmlns:pdfaProperty="http://www.aiim.org/pdfa/ns/property#">' . "\n"
. "\t\t\t" . '<pdfaExtension:schemas>' . "\n"
. "\t\t\t\t" . '<rdf:Bag>' . "\n"
. "\t\t\t\t\t" . '<rdf:li rdf:parseType="Resource">' . "\n"
. "\t\t\t\t\t\t" . '<pdfaSchema:namespaceURI>http://ns.adobe.com/pdf/1.3/</pdfaSchema:namespaceURI>' . "\n"
. "\t\t\t\t\t\t" . '<pdfaSchema:prefix>pdf</pdfaSchema:prefix>' . "\n"
. "\t\t\t\t\t\t" . '<pdfaSchema:schema>Adobe PDF Schema</pdfaSchema:schema>' . "\n"
. "\t\t\t\t\t" . '</rdf:li>' . "\n"
. "\t\t\t\t\t" . '<rdf:li rdf:parseType="Resource">' . "\n"
. "\t\t\t\t\t\t" . '<pdfaSchema:namespaceURI>http://ns.adobe.com/xap/1.0/mm/</pdfaSchema:namespaceURI>' . "\n"
. "\t\t\t\t\t\t" . '<pdfaSchema:prefix>xmpMM</pdfaSchema:prefix>' . "\n"
. "\t\t\t\t\t\t" . '<pdfaSchema:schema>XMP Media Management Schema</pdfaSchema:schema>' . "\n"
. "\t\t\t\t\t\t" . '<pdfaSchema:property>' . "\n"
. "\t\t\t\t\t\t\t" . '<rdf:Seq>' . "\n"
. "\t\t\t\t\t\t\t\t" . '<rdf:li rdf:parseType="Resource">' . "\n"
. "\t\t\t\t\t\t\t\t\t" . '<pdfaProperty:category>internal</pdfaProperty:category>' . "\n"
. "\t\t\t\t\t\t\t\t\t" . '<pdfaProperty:description>UUID based identifier for specific incarnation of a document</pdfaProperty:description>' . "\n"
. "\t\t\t\t\t\t\t\t\t" . '<pdfaProperty:name>InstanceID</pdfaProperty:name>' . "\n"
. "\t\t\t\t\t\t\t\t\t" . '<pdfaProperty:valueType>URI</pdfaProperty:valueType>' . "\n"
. "\t\t\t\t\t\t\t\t" . '</rdf:li>' . "\n"
. "\t\t\t\t\t\t\t" . '</rdf:Seq>' . "\n"
. "\t\t\t\t\t\t" . '</pdfaSchema:property>' . "\n"
. "\t\t\t\t\t" . '</rdf:li>' . "\n"
. "\t\t\t\t\t" . '<rdf:li rdf:parseType="Resource">' . "\n"
. "\t\t\t\t\t\t" . '<pdfaSchema:namespaceURI>http://www.aiim.org/pdfa/ns/id/</pdfaSchema:namespaceURI>' . "\n"
. "\t\t\t\t\t\t" . '<pdfaSchema:prefix>pdfaid</pdfaSchema:prefix>' . "\n"
. "\t\t\t\t\t\t" . '<pdfaSchema:schema>PDF/A ID Schema</pdfaSchema:schema>' . "\n"
. "\t\t\t\t\t\t" . '<pdfaSchema:property>' . "\n"
. "\t\t\t\t\t\t\t" . '<rdf:Seq>' . "\n"
. "\t\t\t\t\t\t\t\t" . '<rdf:li rdf:parseType="Resource">' . "\n"
. "\t\t\t\t\t\t\t\t\t" . '<pdfaProperty:category>internal</pdfaProperty:category>' . "\n"
. "\t\t\t\t\t\t\t\t\t" . '<pdfaProperty:description>Part of PDF/A standard</pdfaProperty:description>' . "\n"
. "\t\t\t\t\t\t\t\t\t" . '<pdfaProperty:name>part</pdfaProperty:name>' . "\n"
. "\t\t\t\t\t\t\t\t\t" . '<pdfaProperty:valueType>Integer</pdfaProperty:valueType>' . "\n"
. "\t\t\t\t\t\t\t\t" . '</rdf:li>' . "\n"
. "\t\t\t\t\t\t\t\t" . '<rdf:li rdf:parseType="Resource">' . "\n"
. "\t\t\t\t\t\t\t\t\t" . '<pdfaProperty:category>internal</pdfaProperty:category>' . "\n"
. "\t\t\t\t\t\t\t\t\t" . '<pdfaProperty:description>Amendment of PDF/A standard</pdfaProperty:description>' . "\n"
. "\t\t\t\t\t\t\t\t\t" . '<pdfaProperty:name>amd</pdfaProperty:name>' . "\n"
. "\t\t\t\t\t\t\t\t\t" . '<pdfaProperty:valueType>Text</pdfaProperty:valueType>' . "\n"
. "\t\t\t\t\t\t\t\t" . '</rdf:li>' . "\n"
. "\t\t\t\t\t\t\t\t" . '<rdf:li rdf:parseType="Resource">' . "\n"
. "\t\t\t\t\t\t\t\t\t" . '<pdfaProperty:category>internal</pdfaProperty:category>' . "\n"
. "\t\t\t\t\t\t\t\t\t" . '<pdfaProperty:description>Conformance level of PDF/A standard</pdfaProperty:description>' . "\n"
. "\t\t\t\t\t\t\t\t\t" . '<pdfaProperty:name>conformance</pdfaProperty:name>' . "\n"
. "\t\t\t\t\t\t\t\t\t" . '<pdfaProperty:valueType>Text</pdfaProperty:valueType>' . "\n"
. "\t\t\t\t\t\t\t\t" . '</rdf:li>' . "\n"
. "\t\t\t\t\t\t\t" . '</rdf:Seq>' . "\n"
. "\t\t\t\t\t\t" . '</pdfaSchema:property>' . "\n"
. "\t\t\t\t\t" . '</rdf:li>' . "\n"
. $this->custom_xmp['x:xmpmeta.rdf:RDF.rdf:Description.pdfaExtension:schemas.rdf:Bag'] . "\n"
. "\t\t\t\t" . '</rdf:Bag>' . "\n"
. $this->custom_xmp['x:xmpmeta.rdf:RDF.rdf:Description.pdfaExtension:schemas'] . "\n"
. "\t\t\t" . '</pdfaExtension:schemas>' . "\n"
. $this->custom_xmp['x:xmpmeta.rdf:RDF.rdf:Description'] . "\n"
. "\t\t" . '</rdf:Description>' . "\n"
. $this->custom_xmp['x:xmpmeta.rdf:RDF'] . "\n"
. "\t" . '</rdf:RDF>' . "\n"
. $this->custom_xmp['x:xmpmeta'] . "\n"
. '</x:xmpmeta>' . "\n"
. '<?xpacket end="w"?>';
// @codingStandardsIgnoreEnd
$oid = ++$this->pon;
$this->objid['xmp'] = $oid;
return $oid . ' 0 obj' . "\n"
. '<<'
. ' /Type /Metadata'
. ' /Subtype /XML'
. ' /Length ' . strlen($xmp)
. ' >> stream' . "\n"
. $xmp . "\n"
. 'endstream' . "\n"
. 'endobj' . "\n";
}
/**
* Set the default document language direction.
*
* @param bool $enabled False = LTR = Left-To-Right; True = RTL = Right-To-Left.
*/
public function setRTL(bool $enabled): static
{
$this->rtl = $enabled;
return $this;
}
/**
* Set the viewer preferences dictionary
* controlling the way the document is to be presented on the screen or in print.
*
* @param TViewerPref $pref Array of options (see PDF reference "Viewer Preferences").
*/
public function setViewerPreferences(array $pref): static
{
$this->viewerpref = $pref;
return $this;
}
/**
* Sanitize the page box name and return the default 'CropBox' in case of error.
*
* @param string $name Entry name.
*/
protected function getPageBoxName(string $name): string
{
$box = 'CropBox';
if (isset($this->viewerpref[$name])) {
$val = $this->viewerpref[$name];
if (
isset($this->page->$box[$val]) // @phpstan-ignore offsetAccess.nonOffsetAccessible
&& is_string($this->page->$box[$val])
) {
$box = $this->page->$box[$val];
}
}
return ' /' . $name . ' /' . $box;
}
/**
* Sanitize the page box name and return the default 'CropBox' in case of error.
*/
protected function getPagePrintScaling(): string
{
$mode = 'AppDefault';
if (isset($this->viewerpref['PrintScaling'])) {
$name = strtolower($this->viewerpref['PrintScaling']);
$valid = [
'none' => 'None',
'appdefault' => 'AppDefault',
];
if (isset($valid[$name])) {
$mode = $valid[$name];
}
}
return ' /PrintScaling /' . $mode;
}
/**
* Returns the Duplex mode for the Viewer Preferences
*/
protected function getDuplexMode(): string
{
if (isset($this->viewerpref['Duplex'])) {
$name = strtolower($this->viewerpref['Duplex']);
$valid = [
'simplex' => 'Simplex',
'duplexflipshortedge' => 'DuplexFlipShortEdge',
'duplexfliplongedge' => 'DuplexFlipLongEdge',
];
if (isset($valid[$name])) {
return ' /Duplex /' . $valid[$name];
}
}
return '';
}
/**
* Returns the Viewer Preference boolean entry.
*
* @param string $name Entry name.
*/
protected function getBooleanMode(string $name): string
{
if (isset($this->viewerpref[$name])) {
return ' /' . $name . ' ' . ($this->viewerpref[$name] === true ? 'true' : 'false');
}
return '';
}
/**
* Returns the PDF viewer preferences for the catalog section
*/
protected function getOutViewerPref(): string
{
$vpr = $this->viewerpref;
$out = ' /ViewerPreferences <<';
if ($this->rtl) {
$out .= ' /Direction /R2L';
} else {
$out .= ' /Direction /L2R';
}
$out .= $this->getBooleanMode('HideToolbar');
$out .= $this->getBooleanMode('HideMenubar');
$out .= $this->getBooleanMode('HideWindowUI');
$out .= $this->getBooleanMode('FitWindow');
$out .= $this->getBooleanMode('CenterWindow');
$out .= $this->getBooleanMode('DisplayDocTitle');
if (isset($vpr['NonFullScreenPageMode'])) {
$out .= ' /NonFullScreenPageMode /' . $this->page->getDisplay($vpr['NonFullScreenPageMode']);
}
$out .= $this->getPageBoxName('ViewArea');
$out .= $this->getPageBoxName('ViewClip');
$out .= $this->getPageBoxName('PrintArea');
$out .= $this->getPageBoxName('PrintClip');
$out .= $this->getPagePrintScaling();
$out .= $this->getDuplexMode();
$out .= $this->getBooleanMode('PickTrayByPDFSize');
if (isset($vpr['PrintPageRange'])) {
$PrintPageRangeNum = '';
foreach ($vpr['PrintPageRange'] as $pnum) {
$PrintPageRangeNum .= ' ' . ($pnum - 1) . '';
}
$out .= ' /PrintPageRange [' . $PrintPageRangeNum . ' ]';
}
if (isset($vpr['NumCopies'])) {
$out .= ' /NumCopies ' . (int) $vpr['NumCopies'];
}
return $out . ' >>';
}
}