generated from jric11/baseProject
648 lines
21 KiB
PHP
648 lines
21 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
/**
|
||
|
|
* TcpdfImporterFacadeTest.php
|
||
|
|
*
|
||
|
|
* @since 2026-04-25
|
||
|
|
* @category Library
|
||
|
|
* @package Pdf
|
||
|
|
* @author Nicola Asuni <info@tecnick.com>
|
||
|
|
* @copyright 2002-2026 Nicola Asuni - Tecnick.com LTD
|
||
|
|
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
|
||
|
|
* @link https://github.com/tecnickcom/tc-lib-pdf
|
||
|
|
*
|
||
|
|
* This file is part of tc-lib-pdf software library.
|
||
|
|
*/
|
||
|
|
|
||
|
|
namespace Test;
|
||
|
|
|
||
|
|
use Com\Tecnick\Pdf\Import\ImportPageOutOfRangeException;
|
||
|
|
use Com\Tecnick\Pdf\Import\ImportSourceNotFoundException;
|
||
|
|
use Com\Tecnick\Pdf\Import\ImportUnsupportedFeatureException;
|
||
|
|
use Com\Tecnick\Pdf\Import\PageTemplate;
|
||
|
|
use Com\Tecnick\Pdf\Tcpdf;
|
||
|
|
use PHPUnit\Framework\TestCase;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Integration tests for the PDF import facade methods on Tcpdf.
|
||
|
|
*/
|
||
|
|
class TcpdfImporterFacadeTest extends TestCase
|
||
|
|
{
|
||
|
|
/** Path to the single-page test fixture. */
|
||
|
|
private string $simplePdf;
|
||
|
|
|
||
|
|
/** Path to the two-page test fixture with a shared font. */
|
||
|
|
private string $multipagePdf;
|
||
|
|
|
||
|
|
/** Path to a fixture with explicit Media/Crop/Bleed/Trim/Art boxes. */
|
||
|
|
private string $boxOptionsPdf;
|
||
|
|
|
||
|
|
/** Path to a fixture with /Rotate 90 on the page dictionary. */
|
||
|
|
private string $rotatedPdf;
|
||
|
|
|
||
|
|
/** Path to a fixture containing alpha/blend ExtGState content. */
|
||
|
|
private string $transparencyPdf;
|
||
|
|
|
||
|
|
/** Path to a fixture with an /Encrypt trailer entry. */
|
||
|
|
private string $encryptedPdf;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws \Throwable
|
||
|
|
*/
|
||
|
|
protected function setUp(): void
|
||
|
|
{
|
||
|
|
if (!\defined('K_PATH_FONTS')) {
|
||
|
|
$fonts = (string) \realpath(__DIR__ . '/../vendor/tecnickcom/tc-lib-pdf-font/target/fonts');
|
||
|
|
\define('K_PATH_FONTS', $fonts);
|
||
|
|
}
|
||
|
|
|
||
|
|
$this->simplePdf = __DIR__ . '/fixtures/simple_import.pdf';
|
||
|
|
$this->multipagePdf = __DIR__ . '/fixtures/multipage_import.pdf';
|
||
|
|
$this->boxOptionsPdf = __DIR__ . '/fixtures/box_options_import.pdf';
|
||
|
|
$this->rotatedPdf = __DIR__ . '/fixtures/rotated_import.pdf';
|
||
|
|
$this->transparencyPdf = __DIR__ . '/fixtures/transparency_import.pdf';
|
||
|
|
$this->encryptedPdf = __DIR__ . '/fixtures/encrypted_import_stub.pdf';
|
||
|
|
}
|
||
|
|
|
||
|
|
// ------------------------------------------------------------------ helpers
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws \Throwable
|
||
|
|
*/
|
||
|
|
private function makePdf(): Tcpdf
|
||
|
|
{
|
||
|
|
$pdf = new Tcpdf();
|
||
|
|
// A default font must be inserted before any addPage call so that
|
||
|
|
// setPageContext / getOutCurrentFont does not receive a null font key.
|
||
|
|
$pdf->font->insert($pdf->pon, 'helvetica', '', 12);
|
||
|
|
return $pdf;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws \Throwable
|
||
|
|
*/
|
||
|
|
private function makePdfWithMode(string $mode): Tcpdf
|
||
|
|
{
|
||
|
|
$pdf = new Tcpdf(mode: $mode);
|
||
|
|
$pdf->font->insert($pdf->pon, 'helvetica', '', 12);
|
||
|
|
return $pdf;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws \Throwable
|
||
|
|
*/
|
||
|
|
private function getPdfVersion(Tcpdf $pdf): string
|
||
|
|
{
|
||
|
|
$ref = new \ReflectionClass($pdf);
|
||
|
|
while ($ref !== false) {
|
||
|
|
if ($ref->hasProperty('pdfver')) {
|
||
|
|
$prop = $ref->getProperty('pdfver');
|
||
|
|
return $this->stringValue($prop->getValue($pdf));
|
||
|
|
}
|
||
|
|
|
||
|
|
$ref = $ref->getParentClass();
|
||
|
|
}
|
||
|
|
|
||
|
|
return '';
|
||
|
|
}
|
||
|
|
|
||
|
|
private function stringValue(mixed $value): string
|
||
|
|
{
|
||
|
|
return \is_string($value) ? $value : '';
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Build a minimal one-page PDF whose /Contents is an array of two Flate streams.
|
||
|
|
*/
|
||
|
|
private function buildMultiContentFlatePdf(): string
|
||
|
|
{
|
||
|
|
$streamA = \gzcompress("BT /F1 12 Tf 20 160 Td (A) Tj ET\n");
|
||
|
|
$streamB = \gzcompress("BT /F1 12 Tf 20 140 Td (B) Tj ET\n");
|
||
|
|
self::assertNotFalse($streamA);
|
||
|
|
self::assertNotFalse($streamB);
|
||
|
|
|
||
|
|
$objects = [];
|
||
|
|
$objects[] = '1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj' . "\n";
|
||
|
|
$objects[] = '2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj' . "\n";
|
||
|
|
$objects[] =
|
||
|
|
'3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 200 200] '
|
||
|
|
. '/Resources << /Font << /F1 6 0 R >> >> /Contents [4 0 R 5 0 R] >> endobj'
|
||
|
|
. "\n";
|
||
|
|
$objects[] =
|
||
|
|
'4 0 obj << /Length '
|
||
|
|
. \strlen($streamA)
|
||
|
|
. ' /Filter /FlateDecode >> stream'
|
||
|
|
. "\n"
|
||
|
|
. $streamA
|
||
|
|
. "\n"
|
||
|
|
. 'endstream endobj'
|
||
|
|
. "\n";
|
||
|
|
$objects[] =
|
||
|
|
'5 0 obj << /Length '
|
||
|
|
. \strlen($streamB)
|
||
|
|
. ' /Filter /FlateDecode >> stream'
|
||
|
|
. "\n"
|
||
|
|
. $streamB
|
||
|
|
. "\n"
|
||
|
|
. 'endstream endobj'
|
||
|
|
. "\n";
|
||
|
|
$objects[] = '6 0 obj << /Type /Font /Subtype /Type1 /BaseFont /Helvetica >> endobj' . "\n";
|
||
|
|
|
||
|
|
$pdf = "%PDF-1.4\n";
|
||
|
|
$offsets = [0 => 0];
|
||
|
|
|
||
|
|
foreach ($objects as $idx => $obj) {
|
||
|
|
$objNum = $idx + 1;
|
||
|
|
$offsets[$objNum] = \strlen($pdf);
|
||
|
|
$pdf .= $obj;
|
||
|
|
}
|
||
|
|
|
||
|
|
$xrefOffset = \strlen($pdf);
|
||
|
|
$pdf .= 'xref' . "\n";
|
||
|
|
$pdf .= '0 7' . "\n";
|
||
|
|
$pdf .= '0000000000 65535 f ' . "\n";
|
||
|
|
for ($objNum = 1; $objNum <= 6; ++$objNum) {
|
||
|
|
$offset = $offsets[$objNum] ?? 0;
|
||
|
|
$pdf .= \sprintf('%010d 00000 n ' . "\n", $offset);
|
||
|
|
}
|
||
|
|
|
||
|
|
$pdf .= 'trailer << /Size 7 /Root 1 0 R >>' . "\n";
|
||
|
|
$pdf .= 'startxref' . "\n";
|
||
|
|
$pdf .= $xrefOffset . "\n";
|
||
|
|
$pdf .= '%%EOF' . "\n";
|
||
|
|
|
||
|
|
return $pdf;
|
||
|
|
}
|
||
|
|
|
||
|
|
// ------------------------------------------------------------------ setImportSourceFile / setImportSourceData
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws \Throwable
|
||
|
|
*/
|
||
|
|
public function testSetImportSourceFileReturnsNonEmptyId(): void
|
||
|
|
{
|
||
|
|
$pdf = $this->makePdf();
|
||
|
|
$srcId = $pdf->setImportSourceFile($this->simplePdf);
|
||
|
|
$this->assertNotEmpty($srcId);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws \Throwable
|
||
|
|
*/
|
||
|
|
public function testSetImportSourceDataReturnsNonEmptyId(): void
|
||
|
|
{
|
||
|
|
$pdf = $this->makePdf();
|
||
|
|
$data = (string) \file_get_contents($this->simplePdf);
|
||
|
|
$srcId = $pdf->setImportSourceData($data);
|
||
|
|
$this->assertNotEmpty($srcId);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws \Throwable
|
||
|
|
*/
|
||
|
|
public function testSetImportSourceFileThrowsForMissingFile(): void
|
||
|
|
{
|
||
|
|
$pdf = $this->makePdf();
|
||
|
|
$this->expectException(ImportSourceNotFoundException::class);
|
||
|
|
$pdf->setImportSourceFile('/nonexistent/path.pdf');
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws \Throwable
|
||
|
|
*/
|
||
|
|
public function testSetImportSourceFileThrowsForEncryptedPdf(): void
|
||
|
|
{
|
||
|
|
$pdf = $this->makePdf();
|
||
|
|
$this->expectException(ImportUnsupportedFeatureException::class);
|
||
|
|
$this->expectExceptionMessageMatches('/' . preg_quote('encrypted PDF', '/') . '/');
|
||
|
|
$pdf->setImportSourceFile($this->encryptedPdf);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws \Throwable
|
||
|
|
*/
|
||
|
|
public function testSetImportSourceFileWithPasswordThrowsActionableEncryptedError(): void
|
||
|
|
{
|
||
|
|
$pdf = $this->makePdf();
|
||
|
|
$this->expectException(ImportUnsupportedFeatureException::class);
|
||
|
|
$this->expectExceptionMessageMatches('/' . preg_quote('password-based import is not supported', '/') . '/');
|
||
|
|
$pdf->setImportSourceFile($this->encryptedPdf, ['password' => 'secret']);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ------------------------------------------------------------------ getSourcePageCount
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws \Throwable
|
||
|
|
*/
|
||
|
|
public function testGetSourcePageCountSimple(): void
|
||
|
|
{
|
||
|
|
$pdf = $this->makePdf();
|
||
|
|
$srcId = $pdf->setImportSourceFile($this->simplePdf);
|
||
|
|
$this->assertSame(1, $pdf->getSourcePageCount($srcId));
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws \Throwable
|
||
|
|
*/
|
||
|
|
public function testGetSourcePageCountMultipage(): void
|
||
|
|
{
|
||
|
|
$pdf = $this->makePdf();
|
||
|
|
$srcId = $pdf->setImportSourceFile($this->multipagePdf);
|
||
|
|
$this->assertSame(2, $pdf->getSourcePageCount($srcId));
|
||
|
|
}
|
||
|
|
|
||
|
|
// ------------------------------------------------------------------ importPage / importPages
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws \Throwable
|
||
|
|
*/
|
||
|
|
public function testImportPageReturnsPageTemplate(): void
|
||
|
|
{
|
||
|
|
$pdf = $this->makePdf();
|
||
|
|
$srcId = $pdf->setImportSourceFile($this->simplePdf);
|
||
|
|
$tpl = $pdf->importPage($srcId, 1);
|
||
|
|
$this->assertInstanceOf(PageTemplate::class, $tpl);
|
||
|
|
$this->assertGreaterThan(0.0, $tpl->getWidth());
|
||
|
|
$this->assertGreaterThan(0.0, $tpl->getHeight());
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws \Throwable
|
||
|
|
*/
|
||
|
|
public function testImportPageThrowsForOutOfRange(): void
|
||
|
|
{
|
||
|
|
$pdf = $this->makePdf();
|
||
|
|
$srcId = $pdf->setImportSourceFile($this->simplePdf);
|
||
|
|
$this->expectException(ImportPageOutOfRangeException::class);
|
||
|
|
$pdf->importPage($srcId, 99);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws \Throwable
|
||
|
|
*/
|
||
|
|
public function testImportPageUsesTrimBoxWhenRequested(): void
|
||
|
|
{
|
||
|
|
$pdf = $this->makePdf();
|
||
|
|
$srcId = $pdf->setImportSourceFile($this->boxOptionsPdf);
|
||
|
|
$tpl = $pdf->importPage($srcId, 1, ['box' => 'TrimBox']);
|
||
|
|
$this->assertEqualsWithDelta(460.0, $tpl->getWidth(), 0.01);
|
||
|
|
$this->assertEqualsWithDelta(660.0, $tpl->getHeight(), 0.01);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws \Throwable
|
||
|
|
*/
|
||
|
|
public function testImportPageUsesArtBoxWhenRequested(): void
|
||
|
|
{
|
||
|
|
$pdf = $this->makePdf();
|
||
|
|
$srcId = $pdf->setImportSourceFile($this->boxOptionsPdf);
|
||
|
|
$tpl = $pdf->importPage($srcId, 1, ['box' => 'ArtBox']);
|
||
|
|
$this->assertEqualsWithDelta(440.0, $tpl->getWidth(), 0.01);
|
||
|
|
$this->assertEqualsWithDelta(640.0, $tpl->getHeight(), 0.01);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws \Throwable
|
||
|
|
*/
|
||
|
|
public function testImportPageRespectsRotationByDefault(): void
|
||
|
|
{
|
||
|
|
$pdf = $this->makePdf();
|
||
|
|
$srcId = $pdf->setImportSourceFile($this->rotatedPdf);
|
||
|
|
$tpl = $pdf->importPage($srcId, 1);
|
||
|
|
$this->assertSame(90, $tpl->getRotation());
|
||
|
|
$this->assertGreaterThan($tpl->getHeight(), $tpl->getWidth());
|
||
|
|
$this->assertEqualsWithDelta(500.0, $tpl->getWidth(), 0.01);
|
||
|
|
$this->assertEqualsWithDelta(300.0, $tpl->getHeight(), 0.01);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws \Throwable
|
||
|
|
*/
|
||
|
|
public function testImportPageCanDisableRotationRespect(): void
|
||
|
|
{
|
||
|
|
$pdf = $this->makePdf();
|
||
|
|
$srcId = $pdf->setImportSourceFile($this->rotatedPdf);
|
||
|
|
$tpl = $pdf->importPage($srcId, 1, ['respectRotation' => false]);
|
||
|
|
$this->assertSame(0, $tpl->getRotation());
|
||
|
|
$this->assertGreaterThan($tpl->getWidth(), $tpl->getHeight());
|
||
|
|
$this->assertEqualsWithDelta(300.0, $tpl->getWidth(), 0.01);
|
||
|
|
$this->assertEqualsWithDelta(500.0, $tpl->getHeight(), 0.01);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws \Throwable
|
||
|
|
*/
|
||
|
|
public function testImportPageWithGroupXObjectBumpsPdfVersionTo14Minimum(): void
|
||
|
|
{
|
||
|
|
$pdf = $this->makePdf();
|
||
|
|
$pdf->setPDFVersion('1.3');
|
||
|
|
$srcId = $pdf->setImportSourceFile($this->transparencyPdf);
|
||
|
|
$pdf->importPage($srcId, 1, ['groupXObject' => true]);
|
||
|
|
$this->assertSame('1.4', $this->getPdfVersion($pdf));
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws \Throwable
|
||
|
|
*/
|
||
|
|
public function testImportPageWithGroupXObjectDisabledKeepsVersion(): void
|
||
|
|
{
|
||
|
|
$pdf = $this->makePdf();
|
||
|
|
$pdf->setPDFVersion('1.3');
|
||
|
|
$srcId = $pdf->setImportSourceFile($this->transparencyPdf);
|
||
|
|
$pdf->importPage($srcId, 1, ['groupXObject' => false]);
|
||
|
|
$this->assertSame('1.3', $this->getPdfVersion($pdf));
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws \Throwable
|
||
|
|
*/
|
||
|
|
public function testImportPageEmitsTransparencyGroupByDefault(): void
|
||
|
|
{
|
||
|
|
$pdf = $this->makePdf();
|
||
|
|
$srcId = $pdf->setImportSourceFile($this->transparencyPdf);
|
||
|
|
$tpl = $pdf->importPage($srcId, 1);
|
||
|
|
$pdf->addPage();
|
||
|
|
$pdf->useImportedPage($tpl, 10, 10, 120, 80, ['keepAspectRatio' => false]);
|
||
|
|
|
||
|
|
$raw = $pdf->getOutPDFString();
|
||
|
|
$this->assertStringContainsString('/Group << /Type /Group /S /Transparency >>', $raw);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws \Throwable
|
||
|
|
*/
|
||
|
|
public function testImportPageSuppressesTransparencyGroupInPdfx3(): void
|
||
|
|
{
|
||
|
|
$pdf = $this->makePdfWithMode('pdfx3');
|
||
|
|
$srcId = $pdf->setImportSourceFile($this->transparencyPdf);
|
||
|
|
$tpl = $pdf->importPage($srcId, 1, ['groupXObject' => true]);
|
||
|
|
$pdf->addPage();
|
||
|
|
$pdf->useImportedPage($tpl, 10, 10, 120, 80, ['keepAspectRatio' => false]);
|
||
|
|
|
||
|
|
$raw = $pdf->getOutPDFString();
|
||
|
|
$this->assertStringNotContainsString('/Group << /Type /Group /S /Transparency >>', $raw);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws \Throwable
|
||
|
|
*/
|
||
|
|
public function testImportPagesNullRangeImportsAll(): void
|
||
|
|
{
|
||
|
|
$pdf = $this->makePdf();
|
||
|
|
$srcId = $pdf->setImportSourceFile($this->multipagePdf);
|
||
|
|
$tpls = $pdf->importPages($srcId);
|
||
|
|
$this->assertCount(2, $tpls);
|
||
|
|
foreach ($tpls as $tpl) {
|
||
|
|
$this->assertInstanceOf(PageTemplate::class, $tpl);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws \Throwable
|
||
|
|
*/
|
||
|
|
public function testImportPagesExplicitRange(): void
|
||
|
|
{
|
||
|
|
$pdf = $this->makePdf();
|
||
|
|
$srcId = $pdf->setImportSourceFile($this->multipagePdf);
|
||
|
|
$tpls = $pdf->importPages($srcId, [1]);
|
||
|
|
$this->assertCount(1, $tpls);
|
||
|
|
assert(isset($tpls[0]), "\$tpls[0] must be set");
|
||
|
|
$this->assertInstanceOf(PageTemplate::class, $tpls[0]);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws \Throwable
|
||
|
|
*/
|
||
|
|
public function testImportPagesThrowsForOutOfRange(): void
|
||
|
|
{
|
||
|
|
$pdf = $this->makePdf();
|
||
|
|
$srcId = $pdf->setImportSourceFile($this->multipagePdf);
|
||
|
|
$this->expectException(ImportPageOutOfRangeException::class);
|
||
|
|
$pdf->importPages($srcId, [1, 99]);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ------------------------------------------------------------------ useImportedPage
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws \Throwable
|
||
|
|
*/
|
||
|
|
public function testUseImportedPageReturnsPlacementDimensions(): void
|
||
|
|
{
|
||
|
|
$pdf = $this->makePdf();
|
||
|
|
$srcId = $pdf->setImportSourceFile($this->simplePdf);
|
||
|
|
$tpl = $pdf->importPage($srcId, 1);
|
||
|
|
$pdf->addPage();
|
||
|
|
$placed = $pdf->useImportedPage($tpl, 10.0, 10.0, 100.0, null, []);
|
||
|
|
$this->assertArrayHasKey('x', $placed);
|
||
|
|
$this->assertArrayHasKey('y', $placed);
|
||
|
|
$this->assertArrayHasKey('width', $placed);
|
||
|
|
$this->assertArrayHasKey('height', $placed);
|
||
|
|
$this->assertEqualsWithDelta(100.0, $placed['width'], 0.01);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws \Throwable
|
||
|
|
*/
|
||
|
|
public function testUseImportedPageAlignCenterCentersInsideRequestedBox(): void
|
||
|
|
{
|
||
|
|
$pdf = $this->makePdf();
|
||
|
|
$srcId = $pdf->setImportSourceFile($this->simplePdf);
|
||
|
|
$tpl = $pdf->importPage($srcId, 1);
|
||
|
|
$pdf->addPage();
|
||
|
|
|
||
|
|
$placed = $pdf->useImportedPage($tpl, 10.0, 20.0, 100.0, 200.0, ['keepAspectRatio' => true, 'align' => 'CC']);
|
||
|
|
|
||
|
|
// In a 100x200 box with source ratio 612:792, width is the limiting axis.
|
||
|
|
$this->assertEqualsWithDelta(100.0, $placed['width'], 0.01);
|
||
|
|
$this->assertEqualsWithDelta(129.41, $placed['height'], 0.05);
|
||
|
|
$this->assertEqualsWithDelta(10.0, $placed['x'], 0.01);
|
||
|
|
$this->assertEqualsWithDelta(55.29, $placed['y'], 0.05);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws \Throwable
|
||
|
|
*/
|
||
|
|
public function testUseImportedPageWithClipAddsClipOperatorToPageContent(): void
|
||
|
|
{
|
||
|
|
$pdf = $this->makePdf();
|
||
|
|
$srcId = $pdf->setImportSourceFile($this->simplePdf);
|
||
|
|
$tpl = $pdf->importPage($srcId, 1);
|
||
|
|
$pdf->addPage();
|
||
|
|
|
||
|
|
$pdf->useImportedPage($tpl, 15.0, 25.0, 80.0, 60.0, ['clip' => true, 'keepAspectRatio' => false]);
|
||
|
|
|
||
|
|
$page = $pdf->page->getPage();
|
||
|
|
$content = \implode('', $page['content']);
|
||
|
|
$this->assertStringContainsString(' re W n ', $content);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ------------------------------------------------------------------ addPageFromImport
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws \Throwable
|
||
|
|
*/
|
||
|
|
public function testAddPageFromImportCreatesPageAndReturnsTemplate(): void
|
||
|
|
{
|
||
|
|
$pdf = $this->makePdf();
|
||
|
|
$srcId = $pdf->setImportSourceFile($this->simplePdf);
|
||
|
|
$tpl = $pdf->addPageFromImport($srcId, 1);
|
||
|
|
$this->assertInstanceOf(PageTemplate::class, $tpl);
|
||
|
|
|
||
|
|
// The first page has pid 0; subsequent pages have positive pids.
|
||
|
|
$pageId = $pdf->page->getPageID();
|
||
|
|
$this->assertGreaterThanOrEqual(0, $pageId);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws \Throwable
|
||
|
|
*/
|
||
|
|
public function testAddPageFromImportPageDimensionsMatchTemplate(): void
|
||
|
|
{
|
||
|
|
$pdf = $this->makePdf();
|
||
|
|
$srcId = $pdf->setImportSourceFile($this->simplePdf);
|
||
|
|
$tpl = $pdf->addPageFromImport($srcId, 1);
|
||
|
|
|
||
|
|
$pageId = $pdf->page->getPageID();
|
||
|
|
$page = $pdf->page->getPage($pageId);
|
||
|
|
$pageW = $page['width'];
|
||
|
|
$pageH = $page['height'];
|
||
|
|
$this->assertGreaterThan(0.0, $pageW);
|
||
|
|
$this->assertGreaterThan(0.0, $pageH);
|
||
|
|
// The aspect ratio of the page must match the template.
|
||
|
|
$this->assertEqualsWithDelta($tpl->getWidth() / $tpl->getHeight(), $pageW / $pageH, 0.01);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws \Throwable
|
||
|
|
*/
|
||
|
|
public function testAddPageFromImportPlacesXObject(): void
|
||
|
|
{
|
||
|
|
$pdf = $this->makePdf();
|
||
|
|
$srcId = $pdf->setImportSourceFile($this->simplePdf);
|
||
|
|
$tpl = $pdf->addPageFromImport($srcId, 1);
|
||
|
|
|
||
|
|
$pageId = $pdf->page->getPageID();
|
||
|
|
$pageContent = $pdf->page->getPage($pageId);
|
||
|
|
// The page content should reference the XObject.
|
||
|
|
$content = \implode('', $pageContent['content']);
|
||
|
|
$this->assertStringContainsString($tpl->getXobjId(), $content);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ------------------------------------------------------------------ appendDocument
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws \Throwable
|
||
|
|
*/
|
||
|
|
public function testAppendDocumentCreatesOnePagePerSourcePage(): void
|
||
|
|
{
|
||
|
|
$pdf = $this->makePdf();
|
||
|
|
$srcId = $pdf->setImportSourceFile($this->multipagePdf);
|
||
|
|
$tpls = $pdf->appendDocument($srcId);
|
||
|
|
$this->assertCount(2, $tpls);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws \Throwable
|
||
|
|
*/
|
||
|
|
public function testAppendDocumentConcatenatesDecodedMultiStreamContents(): void
|
||
|
|
{
|
||
|
|
$pdf = $this->makePdf();
|
||
|
|
$srcId = $pdf->setImportSourceData($this->buildMultiContentFlatePdf());
|
||
|
|
|
||
|
|
$tpls = $pdf->appendDocument($srcId);
|
||
|
|
$this->assertCount(1, $tpls);
|
||
|
|
|
||
|
|
$raw = $pdf->getOutPDFString();
|
||
|
|
$this->assertStringContainsString('(A) Tj ET', $raw);
|
||
|
|
$this->assertStringContainsString('(B) Tj ET', $raw);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws \Throwable
|
||
|
|
*/
|
||
|
|
public function testAppendDocumentWithRangeCreatesOnlyRequestedPages(): void
|
||
|
|
{
|
||
|
|
$pdf = $this->makePdf();
|
||
|
|
$srcId = $pdf->setImportSourceFile($this->multipagePdf);
|
||
|
|
$tpls = $pdf->appendDocument($srcId, [2]);
|
||
|
|
$this->assertCount(1, $tpls);
|
||
|
|
assert(isset($tpls[0]), "\$tpls[0] must be set");
|
||
|
|
$this->assertSame(2, $tpls[0]->getSourcePage());
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws \Throwable
|
||
|
|
*/
|
||
|
|
public function testAppendDocumentRestoresCallerPageContext(): void
|
||
|
|
{
|
||
|
|
$pdf = $this->makePdf();
|
||
|
|
|
||
|
|
// Create an initial page.
|
||
|
|
$callerPage = $pdf->addPage();
|
||
|
|
if (!isset($callerPage['pid']) || !\is_int($callerPage['pid'])) {
|
||
|
|
$this->fail('Expected integer page id.');
|
||
|
|
}
|
||
|
|
$callerPid = $callerPage['pid'];
|
||
|
|
|
||
|
|
// Append pages from a multi-page source.
|
||
|
|
$srcId = $pdf->setImportSourceFile($this->multipagePdf);
|
||
|
|
$pdf->appendDocument($srcId);
|
||
|
|
|
||
|
|
// Current page ID should be restored to the caller's page.
|
||
|
|
$this->assertSame($callerPid, $pdf->page->getPageID());
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws \Throwable
|
||
|
|
*/
|
||
|
|
public function testAppendDocumentWithNoPriorPageLeavesCurrentOnLastAppended(): void
|
||
|
|
{
|
||
|
|
$pdf = $this->makePdf();
|
||
|
|
$srcId = $pdf->setImportSourceFile($this->multipagePdf);
|
||
|
|
$tpls = $pdf->appendDocument($srcId);
|
||
|
|
|
||
|
|
// No prior page (pid is -1), so restore does not run;
|
||
|
|
// the current page is the last appended one.
|
||
|
|
$finalPid = $pdf->page->getPageID();
|
||
|
|
$this->assertGreaterThan(0, $finalPid);
|
||
|
|
// Both appended pages should be reachable.
|
||
|
|
$this->assertCount(2, $tpls);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws \Throwable
|
||
|
|
*/
|
||
|
|
public function testAppendDocumentThrowsForOutOfRangePage(): void
|
||
|
|
{
|
||
|
|
$pdf = $this->makePdf();
|
||
|
|
$srcId = $pdf->setImportSourceFile($this->multipagePdf);
|
||
|
|
$this->expectException(ImportPageOutOfRangeException::class);
|
||
|
|
$pdf->appendDocument($srcId, [5]);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @throws \Throwable
|
||
|
|
*/
|
||
|
|
public function testAppendDocumentXObjectsRegistered(): void
|
||
|
|
{
|
||
|
|
$pdf = $this->makePdf();
|
||
|
|
$srcId = $pdf->setImportSourceFile($this->multipagePdf);
|
||
|
|
$tpls = $pdf->appendDocument($srcId);
|
||
|
|
|
||
|
|
$pages = $pdf->page->getPages();
|
||
|
|
$pageContent = '';
|
||
|
|
foreach ($pages as $page) {
|
||
|
|
$content = $page['content'];
|
||
|
|
if ($content === []) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
$pageContent .= \implode('', $content);
|
||
|
|
}
|
||
|
|
|
||
|
|
foreach ($tpls as $tpl) {
|
||
|
|
$this->assertStringContainsString($tpl->getXobjId(), $pageContent);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|