Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions src/Smalot/PdfParser/PDFObject.php
Original file line number Diff line number Diff line change
Expand Up @@ -827,8 +827,14 @@ public function getTextArray(?Page $page = null): array

// Restore previous selected font and graphics matrix
case 'Q':
list($current_font, $current_font_size) = array_pop($clipped_font);
$current_position_cm = array_pop($clipped_position_cm);
// A malformed content stream can restore a graphics state it never saved,
// leaving nothing on these stacks to pop.
if ([] !== $clipped_font) {
list($current_font, $current_font_size) = array_pop($clipped_font);
}
if ([] !== $clipped_position_cm) {
$current_position_cm = array_pop($clipped_position_cm);
}
break;

// End marked content sequence
Expand Down
16 changes: 16 additions & 0 deletions tests/PHPUnit/Unit/PDFObjectTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,22 @@ public function testGetTextOnPageWithoutContent(): void
static::assertSame(' ', (new PDFObject($document, null, null))->getText(new Page($document)));
}

/**
* A stream may concatenate save-state operators without whitespace ("qq"),
* which lexes as a single unknown token instead of two "q" operators. The
* matching "Q"s then restore a state that was never pushed, and the rest of
* the stream must still be read.
*/
public function testGetTextWithUnmatchedGraphicsStateRestore(): void
{
$document = new Document();
$document->init();

$content = "qq\nBT /F1 12 Tf 10 10 Td (Hello) Tj ET\nQ\nQ\nBT 10 -20 Td (World) Tj ET";

static::assertSame("Hello\nWorld ", (new PDFObject($document, null, $content, new Config()))->getText());
}

public function testTextArrayObjects(): void
{
$document = new Document();
Expand Down
Loading