Files
EpiWebview/vendor/tecnickcom/tc-lib-pdf-page/src/Page.php

417 lines
12 KiB
PHP

<?php
/**
* Page.php
*
* @since 2011-05-23
* @category Library
* @package PdfPage
* @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-page
*
* This file is part of tc-lib-pdf-page software library.
*/
namespace Com\Tecnick\Pdf\Page;
use Com\Tecnick\Color\Pdf as Color;
use Com\Tecnick\Pdf\Encrypt\Encrypt;
use Com\Tecnick\Pdf\Page\Exception as PageException;
/**
* Com\Tecnick\Pdf\Page\Page
*
* @since 2011-05-23
* @category Library
* @package PdfPage
* @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-page
*
* @phpstan-import-type PageBci from \Com\Tecnick\Pdf\Page\Box
* @phpstan-import-type PageBox from \Com\Tecnick\Pdf\Page\Box
* @phpstan-import-type MarginData from \Com\Tecnick\Pdf\Page\Box
* @phpstan-import-type RegionData from \Com\Tecnick\Pdf\Page\Box
* @phpstan-import-type TransitionData from \Com\Tecnick\Pdf\Page\Box
* @phpstan-import-type PageData from \Com\Tecnick\Pdf\Page\Box
*
* @SuppressWarnings("PHPMD.TooManyPublicMethods")
*/
class Page extends \Com\Tecnick\Pdf\Page\Region
{
/**
* Initialize page data.
*
* @param string $unit Unit of measure ('pt', 'mm', 'cm', 'in').
* @param Color $color Color 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.
* @param bool $sigapp True if the signature approval is enabled (for incremental updates).
*/
public function __construct(
string $unit,
Color $color,
Encrypt $encrypt,
bool $pdfa = false,
bool $compress = true,
bool $sigapp = false
) {
$this->kunit = $this->getUnitRatio($unit);
$this->col = $color;
$this->enc = $encrypt;
$this->pdfa = $pdfa;
$this->compress = $compress;
$this->sigapp = $sigapp;
}
/**
* Get the unit ratio.
*
* @return float Unit Ratio.
*/
public function getKUnit(): float
{
return $this->kunit;
}
/**
* Enable Signature Approval.
*
* @param bool $sigapp True if the signature approval is enabled (for incremental updates).
*/
public function enableSignatureApproval(bool $sigapp): static
{
$this->sigapp = $sigapp;
return $this;
}
/**
* Remove the specified page.
*
* @param int $pid page index. Omit or set it to -1 for the current page ID.
*
* @return PageData Removed page.
*/
public function delete(int $pid = -1): array
{
$pid = $this->sanitizePageID($pid);
$page = $this->page[$pid];
--$this->group[$this->page[$pid]['group']];
unset($this->page[$pid]);
$this->page = array_values($this->page); // reindex array
--$this->pmaxid;
return $page;
}
/**
* Remove and return last page.
*
* @return PageData Removed page.
*/
public function pop(): array
{
return $this->delete($this->pmaxid);
}
/**
* Move a page to a previous position.
*
* @param int $from Index of the page to move.
* @param int $new Destination index.
*/
public function move(int $from, int $new): void
{
if (($from <= $new) || ($from > $this->pmaxid)) {
throw new PageException('The new position must be lower than the starting position');
}
$this->page = array_values(
[
...array_slice($this->page, 0, $new),
$this->page[$from],
...array_slice($this->page, $new, ($from - $new)),
...array_slice($this->page, ($from + 1)),
]
);
}
/**
* Returns the array (stack) containing all pages data.
*
* @return array<int, PageData> Pages.
*/
public function getPages(): array
{
return $this->page;
}
/**
* Add Annotation references.
*
* @param int $oid Annotation object IDs.
* @param int $pid page index. Omit or set it to -1 for the current page ID.
*/
public function addAnnotRef(int $oid, int $pid = -1): void
{
$pid = $this->sanitizePageID($pid);
if (in_array($oid, $this->page[$pid]['annotrefs'])) {
return;
}
$this->page[$pid]['annotrefs'][] = $oid;
}
/**
* Add page content.
*
* @param string $content Page content.
* @param int $pid Page index. Omit or set it to -1 for the current page ID.
*/
public function addContent(string $content, int $pid = -1): void
{
$pid = $this->sanitizePageID($pid);
$this->page[$pid]['content'][] = $content;
}
/**
* Remove and return last page content.
*
* @param int $pid page index. Omit or set it to -1 for the current page ID.
*/
public function popContent(int $pid = -1): string
{
$pid = $this->sanitizePageID($pid);
$page = array_pop($this->page[$pid]['content']);
if ($page === null) {
throw new PageException('Page content is empty');
}
return $page;
}
/**
* Add page content mark.
*
* @param int $pid page index. Omit or set it to -1 for the current page ID.
*/
public function addContentMark(int $pid = -1): void
{
$pid = $this->sanitizePageID($pid);
$this->page[$pid]['content_mark'][] = count($this->page[$pid]['content']);
}
/**
* Remove the last marked page content.
*
* @param int $pid page index. Omit or set it to -1 for the current page ID.
*/
public function popContentToLastMark(int $pid = -1): void
{
$pid = $this->sanitizePageID($pid);
$mark = array_pop($this->page[$pid]['content_mark']);
$this->page[$pid]['content'] = array_slice($this->page[$pid]['content'], 0, $mark, true);
}
/**
* Returns the PDF command to output all page sections.
*
* @param int $pon Current PDF object number.
*
* @return string PDF command.
*/
public function getPdfPages(int &$pon): string
{
$out = $this->getPageRootObj($pon);
foreach ($this->page as $num => $page) {
if (! isset($page['num'])) {
if ($num > 0) {
$page['num'] = (
$page['group'] == $this->page[($num - 1)]['group']
) ? (1 + $this->page[($num - 1)]['num']) : 1;
} else {
$page['num'] = (1 + $num);
}
}
$this->page[$num]['num'] = $page['num'];
$content = $this->replacePageTemplates($page);
$out .= $this->getPageContentObj($pon, $content);
$contentobjid = $pon;
$out .= $page['n'] . ' 0 obj' . "\n"
. '<<' . "\n"
. '/Type /Page' . "\n"
. '/Parent ' . $this->rootoid . ' 0 R' . "\n";
if (! $this->pdfa) {
$out .= '/Group << /Type /Group /S /Transparency /CS /DeviceRGB >>' . "\n";
}
if (! $this->sigapp) {
$out .= '/LastModified ' . $this->enc->getFormattedDate($page['time'], $pon) . "\n";
}
$out .= '/Resources ' . $this->rdoid . ' 0 R' . "\n"
. $this->getBox($page['box'])
. $this->getBoxColorInfo($page['box'])
. '/Contents ' . $contentobjid . ' 0 R' . "\n"
. '/Rotate ' . $page['rotation'] . "\n"
. '/PZ ' . sprintf('%F', $page['zoom']) . "\n"
. $this->getPageTransition($page)
. $this->getAnnotationRef($page)
. '>>' . "\n"
. 'endobj' . "\n";
}
return $out;
}
/**
* Returns the reserved Object ID for the Resource dictionary.
*
* @return int Resource dictionary Object ID.
*/
public function getResourceDictObjID(): int
{
return $this->rdoid;
}
/**
* Returns the root object ID.
*
* @return int Root Object ID.
*/
public function getRootObjID(): int
{
return $this->rootoid;
}
/**
* Returns the PDF command to output the page content.
*
* @param PageData $page Page data.
*
* @return string PDF command.
*/
protected function getPageTransition(array $page): string
{
if (empty($page['transition'])) {
return '';
}
$entries = ['B', 'D', 'Di', 'Dm', 'M', 'S', 'SS'];
$out = '';
$out .= '/Dur ' . sprintf('%F', $page['transition']['Dur']) . "\n";
$out .= '/Trans <<' . "\n"
. '/Type /Trans' . "\n";
foreach ($page['transition'] as $key => $val) {
if (in_array($key, $entries)) {
if (is_float($val)) {
$val = sprintf('%F', $val);
}
$out .= '/' . $key . ' /' . $val . "\n";
}
}
return $out . '>>' . "\n";
}
/**
* Get references to page annotations.
*
* @param PageData $page Page data.
*
* @return string PDF command.
*/
protected function getAnnotationRef(array $page): string
{
if (empty($page['annotrefs'])) {
return '';
}
$out = '/Annots [ ';
sort($page['annotrefs']);
foreach ($page['annotrefs'] as $val) {
$out .= (int) $val . ' 0 R ';
}
return $out . ']' . "\n";
}
/**
* Returns the PDF command to output the page content.
*
* @param int $pon Current PDF object number.
* @param string $content Page content.
*
* @return string PDF command.
*/
protected function getPageContentObj(int &$pon, string $content = ''): string
{
$out = ++$pon . ' 0 obj' . "\n"
. '<<';
if ($this->compress) {
$out .= ' /Filter /FlateDecode';
$cmpr = gzcompress($content);
if ($cmpr !== false) {
$content = $cmpr;
}
}
$stream = $this->enc->encryptString($content, $pon);
return $out . ' /Length ' . strlen($stream)
. ' >>' . "\n"
. 'stream' . "\n"
. $stream . "\n"
. 'endstream' . "\n"
. 'endobj' . "\n";
}
/**
* Returns the PDF command to output the page root object.
*
* @param int $pon Current PDF object number.
*
* @return string PDF command.
*/
protected function getPageRootObj(int &$pon): string
{
$this->rdoid = ++$pon; // reserve object ID for the resource dictionary
$this->rootoid = ++$pon;
$out = $this->rootoid . ' 0 obj' . "\n";
$out .= '<< /Type /Pages /Kids [ ';
$numpages = count($this->page);
for ($pid = 0; $pid < $numpages; ++$pid) {
$this->page[$pid]['n'] = ++$pon;
$out .= $this->page[$pid]['n'] . ' 0 R ';
}
return $out . '] /Count ' . $numpages . ' >>' . "\n"
. 'endobj' . "\n";
}
/**
* Replace page templates and numbers.
*
* @param PageData $data Page data.
*/
protected function replacePageTemplates(array $data): string
{
return implode(
"\n",
str_replace(
[self::PAGE_TOT, self::PAGE_NUM],
[(string) $this->group[$data['group']], (string) $data['num']],
$data['content']
)
);
}
}