Skip to content

Commit bee4172

Browse files
committed
fix: correct visible stamp text placement
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
1 parent 0515951 commit bee4172

5 files changed

Lines changed: 95 additions & 3 deletions

File tree

src/Html/SubsetHtmlParser.php

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,14 @@ final class SubsetHtmlParser
1616
{
1717
private const HTML_WRAPPER = '<?xml encoding="utf-8" ?><body>%s</body>';
1818
private const LIBXML_HTML_PARSE_FLAGS = 96; // LIBXML_NOERROR | LIBXML_NOWARNING
19+
private const INHERITABLE_STYLE_PROPERTIES = [
20+
'color' => true,
21+
'font-family' => true,
22+
'font-size' => true,
23+
'font-weight' => true,
24+
'line-height' => true,
25+
'text-align' => true,
26+
];
1927

2028
/** @var array<string, true> */
2129
private array $allowedTags = [
@@ -130,6 +138,8 @@ private function collectAttributes(DOMElement $node): array
130138

131139
private function mergeStyle(string $inheritedStyle, string $ownStyle): string
132140
{
141+
$inheritedStyle = $this->filterInheritableStyle($inheritedStyle);
142+
133143
if ($inheritedStyle === '') {
134144
return $ownStyle;
135145
}
@@ -140,4 +150,35 @@ private function mergeStyle(string $inheritedStyle, string $ownStyle): string
140150

141151
return $inheritedStyle . ';' . $ownStyle;
142152
}
153+
154+
private function filterInheritableStyle(string $style): string
155+
{
156+
if ($style === '') {
157+
return '';
158+
}
159+
160+
$resolvedDeclarations = [];
161+
162+
foreach (explode(';', $style) as $declaration) {
163+
$trimmedDeclaration = trim($declaration);
164+
if ($trimmedDeclaration === '') {
165+
continue;
166+
}
167+
168+
$segments = explode(':', $trimmedDeclaration, 2);
169+
if (count($segments) !== 2) {
170+
continue;
171+
}
172+
173+
$property = strtolower(trim($segments[0]));
174+
$value = trim($segments[1]);
175+
if ($value === '' || !isset(self::INHERITABLE_STYLE_PROPERTIES[$property])) {
176+
continue;
177+
}
178+
179+
$resolvedDeclarations[$property] = $property . ':' . $value;
180+
}
181+
182+
return implode(';', array_values($resolvedDeclarations));
183+
}
143184
}

src/Pdf/TemplateDocumentBuilder.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ public function buildContentStream(LayoutResult $layout): string
8888
foreach ($layout->lines as $line) {
8989
$stream[] = sprintf('/%s %F Tf', $line->fontAlias, $line->fontSize);
9090
$stream[] = $this->colorParser->toPdfRgb($line->rgbColor);
91-
$stream[] = sprintf('%F %F Td', $line->x, $line->y);
91+
$stream[] = sprintf('1 0 0 1 %F %F Tm', $line->x, $line->y);
9292
$stream[] = sprintf('(%s) Tj', $this->pdfEscaper->escapeLiteralString($line->text));
9393
}
9494
$stream[] = 'ET';

tests/Unit/Html/SubsetHtmlParserTest.php

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,34 @@ public function testParseMergesInheritedStylesAndKeepsAllowedTags(): void
5252
self::assertCount(3, $nodes[0]->children);
5353
self::assertSame('span', $nodes[0]->children[0]->tag);
5454
self::assertSame('Hello', $nodes[0]->children[0]->children[0]->text);
55+
self::assertSame('font-size:10;font-weight:bold', $nodes[0]->children[0]->attributes['style']);
5556
self::assertSame(
56-
'font-size:10; margin:2;font-weight:bold',
57+
'font-size:10;font-weight:bold',
5758
$nodes[0]->children[0]->children[0]->attributes['style'],
5859
);
5960
self::assertSame('br', $nodes[0]->children[1]->tag);
6061
self::assertSame('World', $nodes[0]->children[2]->text);
6162
self::assertSame('font-size:10; margin:2', $nodes[0]->children[2]->attributes['style']);
6263
}
6364

65+
public function testParseOnlyInheritsTextualStylesToDescendants(): void
66+
{
67+
$parser = new SubsetHtmlParser();
68+
69+
$nodes = $parser->parse(
70+
'<div style="width:58%;height:100%;padding:18 24;font-size:20;color:#123456">'
71+
. '<div style="font-weight:700">Title</div>'
72+
. '</div>',
73+
);
74+
75+
self::assertSame('width:58%;height:100%;padding:18 24;font-size:20;color:#123456', $nodes[0]->attributes['style']);
76+
self::assertSame('font-size:20;color:#123456;font-weight:700', $nodes[0]->children[0]->attributes['style']);
77+
self::assertSame('font-size:20;color:#123456;font-weight:700', $nodes[0]->children[0]->children[0]->attributes['style']);
78+
self::assertStringNotContainsString('width:58%', $nodes[0]->children[0]->attributes['style']);
79+
self::assertStringNotContainsString('height:100%', $nodes[0]->children[0]->attributes['style']);
80+
self::assertStringNotContainsString('padding:18 24', $nodes[0]->children[0]->attributes['style']);
81+
}
82+
6483
public function testParseNormalizesTagAndAttributeNamesAndKeepsAllAttributes(): void
6584
{
6685
$parser = new SubsetHtmlParser();

tests/Unit/Pdf/TemplateDocumentBuilderTest.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,41 @@ public function testBuildContentStreamIsDirectlyUsableForImagesAndEscapedText():
129129
self::assertStringContainsString('q 3.000000 0 0 4.000000 1.000000 2.000000 cm /Im7 Do Q', $stream);
130130
self::assertStringContainsString('/F2 9.000000 Tf', $stream);
131131
self::assertStringContainsString('0.6706 0.8039 0.9373 rg', $stream);
132+
self::assertStringContainsString('1 0 0 1 12.000000 22.000000 Tm', $stream);
132133
self::assertStringContainsString('(Marker \\(QA\\)) Tj', $stream);
133134
}
134135

136+
public function testBuildContentStreamUsesAbsoluteTextMatrixForMultipleLines(): void
137+
{
138+
$builder = new TemplateDocumentBuilder();
139+
140+
$stream = $builder->buildContentStream(new LayoutResult(
141+
lines: [
142+
new LayoutLine(
143+
text: 'First line',
144+
x: 18.0,
145+
y: 72.0,
146+
fontSize: 10.0,
147+
fontAlias: 'F1',
148+
rgbColor: '#000000',
149+
),
150+
new LayoutLine(
151+
text: 'Second line',
152+
x: 120.0,
153+
y: 40.0,
154+
fontSize: 10.0,
155+
fontAlias: 'F1',
156+
rgbColor: '#000000',
157+
),
158+
],
159+
images: [],
160+
));
161+
162+
self::assertStringContainsString('1 0 0 1 18.000000 72.000000 Tm', $stream);
163+
self::assertStringContainsString('1 0 0 1 120.000000 40.000000 Tm', $stream);
164+
self::assertStringNotContainsString(' Td', $stream);
165+
}
166+
135167
public function testBuildResourcesExposesImageDictionaryAndCustomFontsFromDerivedBuilder(): void
136168
{
137169
$builder = (new TemplateDocumentBuilder())->withFontResources([

tests/Unit/XObjectTemplateCompilerTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public static function htmlProvider(): iterable
6565

6666
yield 'margin and padding affect position' => [
6767
'html' => '<p style="font-size:10;margin:8;padding:4">Offset Text</p>',
68-
'expectedSnippet' => '20.000000 48.000000 Td',
68+
'expectedSnippet' => '1 0 0 1 20.000000 48.000000 Tm',
6969
];
7070
}
7171

0 commit comments

Comments
 (0)