Skip to content
Merged
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
53 changes: 1 addition & 52 deletions src/Document/ContentStream/ContentStream.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,14 @@

use PrinsFrank\PdfParser\Document\ContentStream\Command\ContentStreamCommand;
use PrinsFrank\PdfParser\Document\ContentStream\Command\Operator\State\GraphicsStateOperator;
use PrinsFrank\PdfParser\Document\ContentStream\Command\Operator\State\Interaction\InteractsWithTransformationMatrix;
use PrinsFrank\PdfParser\Document\ContentStream\Command\Operator\State\Interaction\InteractsWithTextState;
use PrinsFrank\PdfParser\Document\ContentStream\Command\Operator\State\Interaction\InteractsWithTransformationMatrix;
use PrinsFrank\PdfParser\Document\ContentStream\Command\Operator\State\Interaction\ProducesPositionedTextElements;
use PrinsFrank\PdfParser\Document\ContentStream\Object\TextObject;
use PrinsFrank\PdfParser\Document\ContentStream\PositionedText\LineGroupingStrategy\LineGroupingStrategy;
use PrinsFrank\PdfParser\Document\ContentStream\PositionedText\PositionedTextElement;
use PrinsFrank\PdfParser\Document\ContentStream\PositionedText\TextState;
use PrinsFrank\PdfParser\Document\ContentStream\PositionedText\TransformationMatrix;
use PrinsFrank\PdfParser\Document\Document;
use PrinsFrank\PdfParser\Document\Object\Decorator\Page;
use PrinsFrank\PdfParser\Exception\ParseFailureException;
use PrinsFrank\PdfParser\Exception\PdfParserException;

/** @api */
readonly class ContentStream {
Expand Down Expand Up @@ -76,51 +72,4 @@ public function getPositionedTextElements(): array {

return $positionedTextElements;
}

/** @throws PdfParserException */
public function getText(Document $document, Page $page, LineGroupingStrategy $lineGroupingStrategy): string {
$text = '';
foreach ($lineGroupingStrategy->group($this->getPositionedTextElements()) as $i => $positionedTextElementsForLine) {
if ($i !== 0) {
$text .= "\n";
}

$previousTextElementOnLine = null;
foreach ($positionedTextElementsForLine as $positionedTextElement) {
$elementText = $positionedTextElement->getText($document, $page);
if ($elementText === '') {
$previousTextElementOnLine = $positionedTextElement;
continue;
}

if ($previousTextElementOnLine !== null) {
// The gap between two elements is what remains of the horizontal distance once the previous
// element's own advance is subtracted. That advance is reconstructed by getAdvanceWidth() because
// Tj/TJ do not move the text matrix here; ignoring it (as the old next-element-font width did) left
// the TJ kerning term in the gap and forced a slack threshold.
$gap = $positionedTextElement->absoluteMatrix->offsetX
- $previousTextElementOnLine->absoluteMatrix->offsetX
- $previousTextElementOnLine->getAdvanceWidth($document, $page);

$wordBreakThreshold = $previousTextElementOnLine->textState->getFontSize()
* $previousTextElementOnLine->absoluteMatrix->scaleX
* ($previousTextElementOnLine->textState->scale / 100)
* PositionedTextElement::WORD_BREAK_THRESHOLD_EM;

if (
$gap >= $wordBreakThreshold
&& str_ends_with($text, ' ') === false
&& str_starts_with($elementText, ' ') === false
) {
$text .= ' ';
}
}

$text .= $elementText;
$previousTextElementOnLine = $positionedTextElement;
}
}

return $text;
}
}
10 changes: 5 additions & 5 deletions src/Document/Object/Decorator/Page.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,18 @@

namespace PrinsFrank\PdfParser\Document\Object\Decorator;

use PrinsFrank\PdfParser\Document\ContentStream\PositionedText\LineGroupingStrategy\TextOverlapStrategy;
use PrinsFrank\PdfParser\Document\ContentStream\ContentStream;
use PrinsFrank\PdfParser\Document\ContentStream\ContentStreamParser;
use PrinsFrank\PdfParser\Document\ContentStream\PositionedText\PositionedTextElement;
use PrinsFrank\PdfParser\Document\Dictionary\Dictionary;
use PrinsFrank\PdfParser\Document\Dictionary\DictionaryKey\DictionaryKey;
use PrinsFrank\PdfParser\Document\ContentStream\ContentStream;
use PrinsFrank\PdfParser\Document\ContentStream\ContentStreamParser;
use PrinsFrank\PdfParser\Document\Dictionary\DictionaryValue\Rectangle\Rectangle;
use PrinsFrank\PdfParser\Document\Dictionary\DictionaryValue\Reference\ReferenceValue;
use PrinsFrank\PdfParser\Exception\InvalidArgumentException;
use PrinsFrank\PdfParser\Exception\ParseFailureException;
use PrinsFrank\PdfParser\Exception\PdfParserException;
use PrinsFrank\PdfParser\Extraction\Text\TextExtractor;
use PrinsFrank\PdfParser\Extraction\Text\TextGrouping\LineGrouping\TextOverlapStrategy;

class Page extends DecoratedObject {
/**
Expand All @@ -26,8 +27,7 @@ public function getPositionedTextElements(): array {

/** @throws PdfParserException */
public function getText(): string {
return $this->getContentStream()
?->getText($this->document, $this, new TextOverlapStrategy()) ?? '';
return TextExtractor::extractText($this->getPositionedTextElements(), $this->document, $this, new TextOverlapStrategy());
}

/** @throws PdfParserException */
Expand Down
57 changes: 57 additions & 0 deletions src/Extraction/Text/TextExtractor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php declare(strict_types=1);

namespace PrinsFrank\PdfParser\Extraction\Text;

use PrinsFrank\PdfParser\Document\ContentStream\PositionedText\PositionedTextElement;
use PrinsFrank\PdfParser\Document\Document;
use PrinsFrank\PdfParser\Document\Object\Decorator\Page;
use PrinsFrank\PdfParser\Exception\PdfParserException;
use PrinsFrank\PdfParser\Extraction\Text\TextGrouping\LineGrouping\LineGroupingStrategy;

class TextExtractor {
/**
* @param list<PositionedTextElement> $positionedTextElements
* @throws PdfParserException
*/
public static function extractText(array $positionedTextElements, Document $document, Page $page, LineGroupingStrategy $lineGroupingStrategy): string {
$text = '';
foreach ($lineGroupingStrategy->group($positionedTextElements) as $i => $positionedTextElementsForLine) {
if ($i !== 0) {
$text .= "\n";
}

$previousTextElementOnLine = null;
foreach ($positionedTextElementsForLine as $positionedTextElement) {
$elementText = $positionedTextElement->getText($document, $page);
if ($elementText === '') {
$previousTextElementOnLine = $positionedTextElement;
continue;
}

if ($previousTextElementOnLine !== null) {
$gap = $positionedTextElement->absoluteMatrix->offsetX
- $previousTextElementOnLine->absoluteMatrix->offsetX
- $previousTextElementOnLine->getAdvanceWidth($document, $page);

$wordBreakThreshold = $previousTextElementOnLine->textState->getFontSize()
* $previousTextElementOnLine->absoluteMatrix->scaleX
* ($previousTextElementOnLine->textState->scale / 100)
* PositionedTextElement::WORD_BREAK_THRESHOLD_EM;

if (
$gap >= $wordBreakThreshold
&& str_ends_with($text, ' ') === false
&& str_starts_with($elementText, ' ') === false
) {
$text .= ' ';
}
}

$text .= $elementText;
$previousTextElementOnLine = $positionedTextElement;
}
}

return $text;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php declare(strict_types=1);

namespace PrinsFrank\PdfParser\Document\ContentStream\PositionedText\LineGroupingStrategy;
namespace PrinsFrank\PdfParser\Extraction\Text\TextGrouping\LineGrouping;

use PrinsFrank\PdfParser\Document\ContentStream\PositionedText\PositionedTextElement;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php declare(strict_types=1);

namespace PrinsFrank\PdfParser\Document\ContentStream\PositionedText\LineGroupingStrategy;
namespace PrinsFrank\PdfParser\Extraction\Text\TextGrouping\LineGrouping;

use Override;
use PrinsFrank\PdfParser\Document\ContentStream\PositionedText\PositionedTextElement;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php declare(strict_types=1);

namespace PrinsFrank\PdfParser\Document\ContentStream\PositionedText\LineGroupingStrategy;
namespace PrinsFrank\PdfParser\Extraction\Text\TextGrouping\LineGrouping;

use Override;
use PrinsFrank\PdfParser\Document\ContentStream\PositionedText\PositionedTextElement;
Expand All @@ -25,9 +25,6 @@ public function __construct(

#[Override]
public function group(array $positionedTextElements): iterable {
// Order lines top to bottom by descending offsetY. Not abs(offsetY): a page whose MediaBox origin sits
// above its content (negative lower-left, e.g. [0 -792 612 0]) has all-negative offsetY with the topmost
// line the least negative, which abs() would reverse.
usort(
$positionedTextElements,
fn(PositionedTextElement $a, PositionedTextElement $b): int => $b->absoluteMatrix->offsetY <=> $a->absoluteMatrix->offsetY,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@

use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;
use PrinsFrank\PdfParser\Document\ContentStream\PositionedText\LineGroupingStrategy\TextOverlapStrategy;
use PrinsFrank\PdfParser\Document\ContentStream\PositionedText\PositionedTextElement;
use PrinsFrank\PdfParser\Document\ContentStream\PositionedText\TextSegment\TextSegment;
use PrinsFrank\PdfParser\Document\ContentStream\PositionedText\TextState;
use PrinsFrank\PdfParser\Document\ContentStream\PositionedText\TransformationMatrix;
use PrinsFrank\PdfParser\Document\Dictionary\DictionaryValue\TextString\TextStringValue;
use PrinsFrank\PdfParser\Extraction\Text\TextGrouping\LineGrouping\TextOverlapStrategy;

#[CoversClass(TextOverlapStrategy::class)]
class TextOverlapStrategyTest extends TestCase {
Expand Down
Loading