Skip to content

Commit 5818060

Browse files
committed
test(pdf): fix exporter assertion and normalize CI history
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
1 parent 179b9f7 commit 5818060

6 files changed

Lines changed: 460 additions & 4 deletions

tests/Support/PngFixtureFactory.php

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,61 @@ public static function createPng(
1818
string $scanlines,
1919
int $bitDepth = 8,
2020
int $interlace = 0,
21+
int $compression = 0,
22+
int $filter = 0,
2123
): string {
22-
$ihdr = pack('NNCCCCC', $width, $height, $bitDepth, $colorType, 0, 0, $interlace);
24+
$ihdr = pack('NNCCCCC', $width, $height, $bitDepth, $colorType, $compression, $filter, $interlace);
2325
$idat = gzcompress($scanlines);
2426
if ($idat === false) {
2527
throw new InvalidArgumentException('Failed to compress PNG scanlines.');
2628
}
2729

28-
return "\x89PNG\r\n\x1a\n"
29-
. self::createChunk('IHDR', $ihdr)
30-
. self::createChunk('IDAT', $idat)
30+
return self::createPngFromCompressedIdatChunks(
31+
$width,
32+
$height,
33+
$colorType,
34+
[$idat],
35+
$bitDepth,
36+
$interlace,
37+
$compression,
38+
$filter,
39+
);
40+
}
41+
42+
/**
43+
* @param list<string> $idatChunks
44+
*/
45+
public static function createPngFromCompressedIdatChunks(
46+
int $width,
47+
int $height,
48+
int $colorType,
49+
array $idatChunks,
50+
int $bitDepth = 8,
51+
int $interlace = 0,
52+
int $compression = 0,
53+
int $filter = 0,
54+
): string {
55+
$ihdr = pack('NNCCCCC', $width, $height, $bitDepth, $colorType, $compression, $filter, $interlace);
56+
$png = "\x89PNG\r\n\x1a\n" . self::createChunk('IHDR', $ihdr);
57+
58+
foreach ($idatChunks as $idatChunk) {
59+
$png .= self::createChunk('IDAT', $idatChunk);
60+
}
61+
62+
return $png
3163
. self::createChunk('IEND', '');
3264
}
3365

66+
public static function compressScanlines(string $scanlines): string
67+
{
68+
$idat = gzcompress($scanlines);
69+
if ($idat === false) {
70+
throw new InvalidArgumentException('Failed to compress PNG scanlines.');
71+
}
72+
73+
return $idat;
74+
}
75+
3476
/**
3577
* @param callable(int, int, int, int): array{0: int|float, 1: int|float, 2: int|float, 3: int|float} $pixelRenderer
3678
*/
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?php
2+
3+
// SPDX-FileCopyrightText: 2026 LibreSign
4+
// SPDX-License-Identifier: AGPL-3.0-or-later
5+
6+
declare(strict_types=1);
7+
8+
namespace LibreSign\XObjectTemplate\Tests\Unit\Layout;
9+
10+
use LibreSign\XObjectTemplate\Layout\TextLineBreaker;
11+
use LibreSign\XObjectTemplate\Pdf\StandardFontMetrics;
12+
use PHPUnit\Framework\TestCase;
13+
14+
final class TextLineBreakerTest extends TestCase
15+
{
16+
public function testWrapReturnsOriginalTextForNowrapAndNonPositiveWidths(): void
17+
{
18+
$breaker = new TextLineBreaker(new StandardFontMetrics());
19+
20+
self::assertSame(['alpha beta'], $breaker->wrap('alpha beta', 0.0, 'F1', 10.0, 'auto', 'normal'));
21+
self::assertSame(['alpha beta'], $breaker->wrap('alpha beta', 100.0, 'F1', 10.0, 'auto', 'nowrap'));
22+
}
23+
24+
public function testWrapPreservesWhitespaceOnlyInput(): void
25+
{
26+
$breaker = new TextLineBreaker(new StandardFontMetrics());
27+
28+
self::assertSame([" \t "], $breaker->wrap(" \t ", 20.0, 'F1', 10.0, 'auto', 'normal'));
29+
}
30+
31+
public function testWrapKeepsWordsOnTheSameLineWhenTheyStillFit(): void
32+
{
33+
$breaker = new TextLineBreaker(new StandardFontMetrics());
34+
35+
self::assertSame(['a b'], $breaker->wrap('a b', 20.0, 'F5', 10.0, 'none', 'normal'));
36+
}
37+
38+
public function testWrapKeepsAppendingAfterManualHyphenBreaks(): void
39+
{
40+
$metrics = new StandardFontMetrics();
41+
$breaker = new TextLineBreaker($metrics);
42+
$maxWidth = max(
43+
$metrics->measureString('F1', 10.0, 'hyphen-'),
44+
$metrics->measureString('F1', 10.0, 'ation test'),
45+
);
46+
47+
self::assertSame(
48+
['hyphen-', 'ation test'],
49+
$breaker->wrap("hyphen\u{00AD}ation test", $maxWidth, 'F1', 10.0, 'manual', 'normal'),
50+
);
51+
}
52+
53+
public function testWrapContinuesUsingTheLastBrokenSegmentAsTheCurrentLine(): void
54+
{
55+
$breaker = new TextLineBreaker(new StandardFontMetrics());
56+
57+
self::assertSame(
58+
['abcd-', 'ef gh'],
59+
$breaker->wrap('abcdef gh', 30.0, 'F5', 10.0, 'auto', 'normal'),
60+
);
61+
}
62+
63+
public function testWrapAutomaticallyHyphenatesFixedWidthWords(): void
64+
{
65+
$breaker = new TextLineBreaker(new StandardFontMetrics());
66+
67+
self::assertSame(
68+
['abc-', 'def'],
69+
$breaker->wrap('abcdef', 24.0, 'F5', 10.0, 'auto', 'normal'),
70+
);
71+
}
72+
73+
public function testWrapFallsBackToSingleCharactersWhenTheFirstAutoSegmentDoesNotFit(): void
74+
{
75+
$breaker = new TextLineBreaker(new StandardFontMetrics());
76+
77+
self::assertSame(
78+
['a-', 'b'],
79+
$breaker->wrap('ab', 5.0, 'F1', 10.0, 'auto', 'normal'),
80+
);
81+
}
82+
83+
public function testWrapReturnsInvalidUtf8WordsUnchangedWhenCharacterSplittingFails(): void
84+
{
85+
$breaker = new TextLineBreaker(new StandardFontMetrics());
86+
$invalidUtf8 = "\xc3\x28";
87+
88+
self::assertSame([$invalidUtf8], $breaker->wrap($invalidUtf8, 1.0, 'F1', 10.0, 'auto', 'normal'));
89+
}
90+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
// SPDX-FileCopyrightText: 2026 LibreSign
4+
// SPDX-License-Identifier: AGPL-3.0-or-later
5+
6+
declare(strict_types=1);
7+
8+
namespace LibreSign\XObjectTemplate\Tests\Unit\Layout;
9+
10+
use LibreSign\XObjectTemplate\Layout\TextOverflowTruncator;
11+
use LibreSign\XObjectTemplate\Pdf\StandardFontMetrics;
12+
use PHPUnit\Framework\TestCase;
13+
14+
final class TextOverflowTruncatorTest extends TestCase
15+
{
16+
public function testTruncateWithEllipsisLeavesExactFitStringsUntouched(): void
17+
{
18+
$metrics = new StandardFontMetrics();
19+
$truncator = new TextOverflowTruncator($metrics);
20+
$text = 'exact fit';
21+
22+
self::assertSame(
23+
$text,
24+
$truncator->truncateWithEllipsis($text, $metrics->measureString('F1', 10.0, $text), 'F1', 10.0),
25+
);
26+
}
27+
28+
public function testTruncateWithEllipsisShortensUntilTheCandidateFits(): void
29+
{
30+
$metrics = new StandardFontMetrics();
31+
$truncator = new TextOverflowTruncator($metrics);
32+
33+
self::assertSame(
34+
'ab...',
35+
$truncator->truncateWithEllipsis('abcdef', $metrics->measureString('F5', 10.0, 'ab...'), 'F5', 10.0),
36+
);
37+
}
38+
39+
public function testForceEllipsisTrimsTrailingWhitespaceBeforeAppendingTheMarker(): void
40+
{
41+
$metrics = new StandardFontMetrics();
42+
$truncator = new TextOverflowTruncator($metrics);
43+
44+
self::assertSame(
45+
'word...',
46+
$truncator->forceEllipsis('word ', $metrics->measureString('F1', 10.0, 'word ...'), 'F1', 10.0),
47+
);
48+
}
49+
50+
public function testForceEllipsisFallsBackToOnlyTheMarkerWhenNothingFits(): void
51+
{
52+
$truncator = new TextOverflowTruncator(new StandardFontMetrics());
53+
54+
self::assertSame('...', $truncator->forceEllipsis('text', 1.0, 'F1', 10.0));
55+
}
56+
57+
public function testForceEllipsisReturnsOnlyTheMarkerForInvalidUtf8Input(): void
58+
{
59+
$truncator = new TextOverflowTruncator(new StandardFontMetrics());
60+
61+
self::assertSame('...', $truncator->forceEllipsis("\xc3\x28", 10.0, 'F1', 10.0));
62+
}
63+
}

0 commit comments

Comments
 (0)