417 lines
12 KiB
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']
|
|
)
|
|
);
|
|
}
|
|
}
|