-
-
Notifications
You must be signed in to change notification settings - Fork 13
Correctly support rotated texts #444
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
splitbrain
wants to merge
6
commits into
PrinsFrank:main
Choose a base branch
from
cosmocode:rotation-by-cluster-split
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
63592cb
Add a Vector value object and TransformationMatrix::baselineVector()
splitbrain 52a12a1
Return unscaled glyph widths and project the advance onto a direction
splitbrain b4be7a9
Let the line-grouping strategy decide where spaces belong
splitbrain 15503eb
Group text by its actual baseline and make it the default
splitbrain 4a02a1d
Add rotated-text sample fixtures
splitbrain 6a975fa
Resolve the assumed font size through a single TextState::getFontSize()
splitbrain File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
333 changes: 333 additions & 0 deletions
333
src/Document/ContentStream/PositionedText/LineGroupingStrategy/BaselineClusterStrategy.php
Large diffs are not rendered by default.
Oops, something went wrong.
31 changes: 31 additions & 0 deletions
31
src/Document/ContentStream/PositionedText/LineGroupingStrategy/Block.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| <?php declare(strict_types=1); | ||
|
|
||
| namespace PrinsFrank\PdfParser\Document\ContentStream\PositionedText\LineGroupingStrategy; | ||
|
|
||
| use PrinsFrank\PdfParser\Document\ContentStream\PositionedText\Vector; | ||
|
|
||
| /** | ||
| * A group of parallel {@see Line}s that read as one unit -- a paragraph, a column, or a rotated overlay -- sharing | ||
| * a single baseline normal, with the highest point and earliest content-stream position across its lines computed | ||
| * once so ordering the blocks against each other never rescans them. | ||
| */ | ||
| readonly class Block { | ||
| public float $top; // the highest point on the page reached by any line in the block (largest offsetY) | ||
| public int $documentPosition; // the earliest content-stream position of any line in the block | ||
|
|
||
| /** @param list<Line> $lines */ | ||
| public function __construct( | ||
| public Vector $normal, | ||
| public array $lines, | ||
| ) { | ||
| $top = null; | ||
| $documentPosition = PHP_INT_MAX; | ||
| foreach ($lines as $line) { | ||
| $top = $top === null ? $line->top : max($top, $line->top); | ||
| $documentPosition = min($documentPosition, $line->documentPosition); | ||
| } | ||
|
|
||
| $this->top = $top ?? 0.0; | ||
| $this->documentPosition = $documentPosition; | ||
| } | ||
| } |
56 changes: 56 additions & 0 deletions
56
src/Document/ContentStream/PositionedText/LineGroupingStrategy/Line.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| <?php declare(strict_types=1); | ||
|
|
||
| namespace PrinsFrank\PdfParser\Document\ContentStream\PositionedText\LineGroupingStrategy; | ||
|
|
||
| use PrinsFrank\PdfParser\Document\ContentStream\PositionedText\PositionedTextElement; | ||
| use PrinsFrank\PdfParser\Document\ContentStream\PositionedText\Vector; | ||
|
|
||
| /** | ||
| * A run of text elements sharing a single baseline, ordered along it in reading order, together with the geometry | ||
| * that places it on the page: its baseline direction, a reference origin, its height, its highest point, and its | ||
| * earliest position in the content stream. | ||
| */ | ||
| readonly class Line { | ||
| /** @param list<PositionedTextElement> $elements ordered along the baseline (reading direction) */ | ||
| private function __construct( | ||
| public array $elements, | ||
| public Vector $direction, // unit baseline (advance) direction shared by every element on the line | ||
| public Vector $reference, // a reference origin on the page for the whole line | ||
| public float $height, // the tallest glyph extent on the line | ||
| public float $top, // the highest point the line reaches on the page (largest offsetY) | ||
| public int $documentPosition, // the earliest content-stream position of any element on the line | ||
| ) {} | ||
|
|
||
| /** | ||
| * Collect $elements into a line: order them along $direction (reading direction) and capture the line's | ||
| * highest point on the page and earliest content-stream position. | ||
| * | ||
| * @param list<PositionedTextElement> $elements | ||
| * @param array<int, int> $documentOrder spl_object_id() => position in the content stream | ||
| */ | ||
| public static function fromElements(array $elements, Vector $direction, Vector $reference, float $height, array $documentOrder): self { | ||
| usort( | ||
| $elements, | ||
| static function (PositionedTextElement $a, PositionedTextElement $b) use ($direction): int { | ||
| $projectionA = $a->absoluteMatrix->offsetX * $direction->x + $a->absoluteMatrix->offsetY * $direction->y; | ||
| $projectionB = $b->absoluteMatrix->offsetX * $direction->x + $b->absoluteMatrix->offsetY * $direction->y; | ||
|
|
||
| return $projectionA <=> $projectionB; | ||
| }, | ||
| ); | ||
|
|
||
| $top = $reference->y; | ||
| $documentPosition = PHP_INT_MAX; | ||
| foreach ($elements as $element) { | ||
| $top = max($top, $element->absoluteMatrix->offsetY); | ||
| $documentPosition = min($documentPosition, $documentOrder[spl_object_id($element)]); | ||
| } | ||
|
|
||
| return new self($elements, $direction, $reference, $height, $top, $documentPosition); | ||
| } | ||
|
|
||
| /** Signed coordinate of the line along $normal: its position in a stack of parallel lines. */ | ||
| public function positionAlong(Vector $normal): float { | ||
| return $this->reference->dot($normal); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
32 changes: 32 additions & 0 deletions
32
src/Document/ContentStream/PositionedText/LineGroupingStrategy/MatrixOffsetSpacing.php
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| <?php declare(strict_types=1); | ||
|
|
||
| namespace PrinsFrank\PdfParser\Document\ContentStream\PositionedText\LineGroupingStrategy; | ||
|
|
||
| use PrinsFrank\PdfParser\Document\ContentStream\PositionedText\PositionedTextElement; | ||
| use PrinsFrank\PdfParser\Document\Document; | ||
| use PrinsFrank\PdfParser\Document\Object\Decorator\Page; | ||
| use PrinsFrank\PdfParser\Exception\PdfParserException; | ||
|
|
||
| /** | ||
| * Axis-aligned space-insertion heuristic shared by the strategies that lay text out along page X | ||
| * ({@see TextOverlapStrategy}, {@see StrictLineGrouping}). A space belongs between two runs when the gap between | ||
| * their X offsets, less the reconstructed advance width of the previous run, reaches a single | ||
| * {@see PositionedTextElement::WORD_BREAK_THRESHOLD_EM} fraction of the em. With an accurate advance the within-word residual | ||
| * collapses near zero, so one threshold separates word breaks from kerning across producers. Holds only for | ||
| * upright text; {@see BaselineClusterStrategy} measures the same comparison along an arbitrary baseline. | ||
| */ | ||
| trait MatrixOffsetSpacing { | ||
| /** @throws PdfParserException */ | ||
| public function requiresSpaceBetween(PositionedTextElement $previous, PositionedTextElement $current, Document $document, Page $page): bool { | ||
| $gap = $current->absoluteMatrix->offsetX | ||
| - $previous->absoluteMatrix->offsetX | ||
| - $previous->getAdvanceWidth($document, $page); | ||
|
|
||
| $threshold = $previous->textState->getFontSize() | ||
| * $previous->absoluteMatrix->scaleX | ||
| * ($previous->textState->scale / 100) | ||
| * PositionedTextElement::WORD_BREAK_THRESHOLD_EM; | ||
|
|
||
| return $gap >= $threshold; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| <?php declare(strict_types=1); | ||
|
|
||
| namespace PrinsFrank\PdfParser\Document\ContentStream\PositionedText; | ||
|
|
||
| /** A 2D vector in page (device) space. */ | ||
| readonly class Vector { | ||
| public function __construct( | ||
| public float $x, | ||
| public float $y, | ||
| ) {} | ||
|
|
||
| /** Euclidean length of the vector. */ | ||
| public function length(): float { | ||
| return hypot($this->x, $this->y); | ||
| } | ||
|
|
||
| /** The unit vector in the same direction, or the zero vector when there is no direction to preserve. */ | ||
| public function normalized(): self { | ||
| $length = $this->length(); | ||
| if ($length === 0.0) { | ||
| return new self(0.0, 0.0); | ||
| } | ||
|
|
||
| return new self($this->x / $length, $this->y / $length); | ||
| } | ||
|
|
||
| /** Dot (scalar) product with $other; for unit vectors this is the cosine of the angle between them. */ | ||
| public function dot(self $other): float { | ||
| return $this->x * $other->x + $this->y * $other->y; | ||
| } | ||
|
|
||
| /** This vector rotated 90 degrees counter-clockwise: (x, y) -> (-y, x). */ | ||
| public function normal(): self { | ||
| return new self(-$this->y, $this->x); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.