Skip to content

Commit 40dbfe3

Browse files
committed
feat: increase coverage
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
1 parent 612eb3b commit 40dbfe3

6 files changed

Lines changed: 533 additions & 23 deletions

File tree

src/Pdf/Svg/PathParsingState.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ final class PathParsingState
1818
public function __construct(
1919
public float $currentX = 0.0,
2020
public float $currentY = 0.0,
21+
public float $subpathStartX = 0.0,
22+
public float $subpathStartY = 0.0,
2123
public ?float $lastCubicControlX = null,
2224
public ?float $lastCubicControlY = null,
2325
public ?float $prevQuadCpX = null,

src/Pdf/Svg/SvgPathCommandParser.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,8 @@ private function handleMoveCommand(
169169
$coordinates = $this->pathNumberReader->readPathNumbers($tokens, $index, 2, $context->source);
170170
$state->currentX = $this->resolveCoord($isRelative, $state->currentX, $coordinates[0]);
171171
$state->currentY = $this->resolveCoord($isRelative, $state->currentY, $coordinates[1]);
172+
$state->subpathStartX = $state->currentX;
173+
$state->subpathStartY = $state->currentY;
172174
[$moveX, $moveY] = $this->transformResolver->applyTransformToPoint(
173175
$context->transformMatrix,
174176
$state->currentX,
@@ -229,6 +231,8 @@ private function handleHorizontalCommand(
229231
$state->commands[] = sprintf('%F %F l', $lineX - $context->minX, $context->maxY - $lineY);
230232
$state->lastCubicControlX = null;
231233
$state->lastCubicControlY = null;
234+
$state->prevQuadCpX = null;
235+
$state->prevQuadCpY = null;
232236
}
233237
}
234238

@@ -254,6 +258,8 @@ private function handleVerticalCommand(
254258
$state->commands[] = sprintf('%F %F l', $lineX - $context->minX, $context->maxY - $lineY);
255259
$state->lastCubicControlX = null;
256260
$state->lastCubicControlY = null;
261+
$state->prevQuadCpX = null;
262+
$state->prevQuadCpY = null;
257263
}
258264
}
259265

@@ -292,6 +298,8 @@ private function handleCubicCommand(
292298
$state->currentY = $y;
293299
$state->lastCubicControlX = $endX2;
294300
$state->lastCubicControlY = $endY2;
301+
$state->prevQuadCpX = null;
302+
$state->prevQuadCpY = null;
295303
}
296304
}
297305

@@ -330,6 +338,8 @@ private function handleSmoothCubicCommand(
330338
$state->currentY = $y;
331339
$state->lastCubicControlX = $endX2;
332340
$state->lastCubicControlY = $endY2;
341+
$state->prevQuadCpX = null;
342+
$state->prevQuadCpY = null;
333343
}
334344
}
335345

@@ -480,14 +490,20 @@ private function handleArcCommand(
480490
$state->currentY = $y;
481491
$state->lastCubicControlX = null;
482492
$state->lastCubicControlY = null;
493+
$state->prevQuadCpX = null;
494+
$state->prevQuadCpY = null;
483495
}
484496
}
485497

486498
private function handleClosePathCommand(PathParsingState $state): void
487499
{
488500
$state->commands[] = 'h';
501+
$state->currentX = $state->subpathStartX;
502+
$state->currentY = $state->subpathStartY;
489503
$state->lastCubicControlX = null;
490504
$state->lastCubicControlY = null;
505+
$state->prevQuadCpX = null;
506+
$state->prevQuadCpY = null;
491507
}
492508

493509
/**
@@ -527,6 +543,8 @@ private function appendLineToState(
527543
);
528544
$state->lastCubicControlX = null;
529545
$state->lastCubicControlY = null;
546+
$state->prevQuadCpX = null;
547+
$state->prevQuadCpY = null;
530548
}
531549

532550
private function appendQuadraticAsCubicToState(

src/Pdf/Svg/SvgTransformResolver.php

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ public function resolveElementTransformMatrix(DOMElement $element): array
3939
}
4040

4141
for ($index = count($ancestors) - 1; $index >= 0; --$index) {
42-
$transform = trim($ancestors[$index]->getAttribute('transform'));
43-
if ($transform === '') {
42+
$transform = $ancestors[$index]->getAttribute('transform');
43+
if (!preg_match('/\S/', $transform)) {
4444
continue;
4545
}
4646

@@ -90,31 +90,40 @@ private function parseTransformList(string $transform): array
9090

9191
foreach ($matches as $match) {
9292
$operatorName = strtolower($match[1]);
93-
$args = preg_split('/[\s,]+/', trim($match[2]));
93+
$args = preg_split('/[\s,]+/', $match[2], -1, PREG_SPLIT_NO_EMPTY);
9494
if (!is_array($args)) {
9595
continue;
9696
}
9797

98-
$values = [];
99-
foreach ($args as $arg) {
100-
if ($arg === '') {
101-
continue;
102-
}
98+
$values = array_map(
99+
static fn(string $arg): float => (float) $arg,
100+
$args,
101+
);
103102

104-
$values[] = (float) $arg;
105-
}
106-
107-
$operationMatrix = match ($operatorName) {
108-
'matrix' => count($values) >= 6
103+
if ($operatorName === 'matrix') {
104+
$operationMatrix = count($values) >= 6
109105
? [$values[0], $values[1], $values[2], $values[3], $values[4], $values[5]]
110-
: [1.0, 0.0, 0.0, 1.0, 0.0, 0.0],
111-
'translate' => [1.0, 0.0, 0.0, 1.0, $values[0] ?? 0.0, $values[1] ?? 0.0],
112-
'scale' => [$values[0] ?? 1.0, 0.0, 0.0, $values[1] ?? ($values[0] ?? 1.0), 0.0, 0.0],
113-
'rotate' => $this->buildRotateMatrix($values),
114-
'skewx' => [1.0, 0.0, tan(deg2rad($values[0] ?? 0.0)), 1.0, 0.0, 0.0],
115-
'skewy' => [1.0, tan(deg2rad($values[0] ?? 0.0)), 0.0, 1.0, 0.0, 0.0],
116-
default => [1.0, 0.0, 0.0, 1.0, 0.0, 0.0],
117-
};
106+
: [1.0, 0.0, 0.0, 1.0, 0.0, 0.0];
107+
} elseif ($operatorName === 'translate') {
108+
$operationMatrix = [1.0, 0.0, 0.0, 1.0, $values[0] ?? 0.0, $values[1] ?? 0.0];
109+
} elseif ($operatorName === 'scale') {
110+
$operationMatrix = [
111+
$values[0] ?? 1.0,
112+
0.0,
113+
0.0,
114+
$values[1] ?? ($values[0] ?? 1.0),
115+
0.0,
116+
0.0,
117+
];
118+
} elseif ($operatorName === 'rotate') {
119+
$operationMatrix = $this->buildRotateMatrix($values);
120+
} elseif ($operatorName === 'skewx') {
121+
$operationMatrix = [1.0, 0.0, tan(deg2rad($values[0] ?? 0.0)), 1.0, 0.0, 0.0];
122+
} elseif ($operatorName === 'skewy') {
123+
$operationMatrix = [1.0, tan(deg2rad($values[0] ?? 0.0)), 0.0, 1.0, 0.0, 0.0];
124+
} else {
125+
continue;
126+
}
118127

119128
$matrix = $this->multiplyMatrices($matrix, $operationMatrix);
120129
}

tests/Unit/Pdf/Svg/SvgArcConverterTest.php

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,50 @@ public function testCalculateArcCenterRespectsSweepDirection(): void
370370
);
371371
}
372372

373+
public function testCalculateArcCenterMatchesExpectedRotatedNormalizedCenter(): void
374+
{
375+
$converter = new SvgArcConverter();
376+
$params = new ArcParams(
377+
0.0,
378+
10.0,
379+
60.0,
380+
30.0,
381+
38.77015604817706,
382+
20.677416559027762,
383+
cos(deg2rad(45.0)),
384+
sin(deg2rad(45.0)),
385+
1,
386+
1,
387+
);
388+
389+
$center = self::invokePrivateMethod($converter, 'calculateArcCenter', [$params]);
390+
391+
self::assertEqualsWithDelta(29.999999923071595, $center[0], 0.0001);
392+
self::assertEqualsWithDelta(19.99999972004405, $center[1], 0.0001);
393+
}
394+
395+
public function testCalculateArcCenterFallsBackToMidpointBelowDenominatorTolerance(): void
396+
{
397+
$converter = new SvgArcConverter();
398+
$params = new ArcParams(
399+
0.0,
400+
0.0,
401+
-1.0e-6,
402+
0.0,
403+
1.0,
404+
1.0,
405+
1.0,
406+
0.0,
407+
0,
408+
1,
409+
);
410+
411+
$center = self::invokePrivateMethod($converter, 'calculateArcCenter', [$params]);
412+
413+
self::assertEqualsWithDelta(-5.0e-7, $center[0], 1.0e-12);
414+
self::assertEqualsWithDelta(0.0, $center[1], 1.0e-12);
415+
}
416+
373417
public function testCalculateArcAnglesReturnsExpectedSweepAdjustedDelta(): void
374418
{
375419
$converter = new SvgArcConverter();
@@ -393,6 +437,61 @@ public function testCalculateArcAnglesReturnsExpectedSweepAdjustedDelta(): void
393437
self::assertEqualsWithDelta(M_PI, $clockwiseAngles[1], 0.0001);
394438
}
395439

440+
public function testCalculateArcAnglesMatchesExpectedRotatedNormalizedValues(): void
441+
{
442+
$converter = new SvgArcConverter();
443+
$params = new ArcParams(
444+
0.0,
445+
10.0,
446+
60.0,
447+
30.0,
448+
38.77015604817706,
449+
20.677416559027762,
450+
cos(deg2rad(45.0)),
451+
sin(deg2rad(45.0)),
452+
1,
453+
1,
454+
);
455+
456+
$angles = self::invokePrivateMethod(
457+
$converter,
458+
'calculateArcAngles',
459+
[$params, 29.999999923071595, 19.99999972004405],
460+
);
461+
462+
self::assertEqualsWithDelta(2.388441372627599, $angles[0], 0.0001);
463+
self::assertEqualsWithDelta(M_PI, $angles[1], 0.0001);
464+
}
465+
466+
public function testCalculateArcCenterAndAnglesMatchExpectedAsymmetricValues(): void
467+
{
468+
$converter = new SvgArcConverter();
469+
$params = new ArcParams(
470+
0.0,
471+
0.0,
472+
25.0,
473+
8.0,
474+
13.566066509534181,
475+
10.174549882150636,
476+
cos(deg2rad(35.0)),
477+
sin(deg2rad(35.0)),
478+
1,
479+
0,
480+
);
481+
482+
$center = self::invokePrivateMethod($converter, 'calculateArcCenter', [$params]);
483+
$angles = self::invokePrivateMethod(
484+
$converter,
485+
'calculateArcAngles',
486+
[$params, $center[0], $center[1]],
487+
);
488+
489+
self::assertEqualsWithDelta(12.499999988863545, $center[0], 0.0001);
490+
self::assertEqualsWithDelta(4.000000104332266, $center[1], 0.0001);
491+
self::assertEqualsWithDelta(2.7489504213408784, $angles[0], 0.0001);
492+
self::assertEqualsWithDelta(-M_PI, $angles[1], 0.0001);
493+
}
494+
396495
public function testGenerateArcCurvesUsesSingleSegmentBelowNinetyDegrees(): void
397496
{
398497
$converter = new SvgArcConverter();
@@ -417,6 +516,79 @@ public function testGenerateArcCurvesUsesSingleSegmentBelowNinetyDegrees(): void
417516
);
418517
}
419518

519+
public function testGenerateArcCurvesReturnsSinglePointCurveWhenDeltaAngleIsZero(): void
520+
{
521+
$converter = new SvgArcConverter();
522+
523+
$curves = self::invokePrivateMethod(
524+
$converter,
525+
'generateArcCurves',
526+
[0.0, 0.0, 10.0, 10.0, 1.0, 0.0, 0.0, 0.0],
527+
);
528+
529+
self::assertCount(1, $curves);
530+
self::assertCurveMatches([10.0, 0.0, 10.0, 0.0, 10.0, 0.0], $curves[0]);
531+
}
532+
533+
public function testGenerateArcCurvesMatchesExpectedRotatedQuarterSegment(): void
534+
{
535+
$converter = new SvgArcConverter();
536+
537+
$curves = self::invokePrivateMethod(
538+
$converter,
539+
'generateArcCurves',
540+
[
541+
3.0,
542+
-2.0,
543+
10.0,
544+
6.0,
545+
cos(deg2rad(45.0)),
546+
sin(deg2rad(45.0)),
547+
0.0,
548+
M_PI / 4.0,
549+
],
550+
);
551+
552+
self::assertCount(1, $curves);
553+
self::assertCurveMatches(
554+
[8.946281087094862, 6.195854536636088, 7.120918187930419, 6.530229546982604, 5.0, 6.0],
555+
$curves[0],
556+
);
557+
}
558+
559+
public function testGenerateArcCurvesMatchesExpectedRotatedQuarterSegmentWithNonZeroStart(): void
560+
{
561+
$converter = new SvgArcConverter();
562+
563+
$curves = self::invokePrivateMethod(
564+
$converter,
565+
'generateArcCurves',
566+
[
567+
3.0,
568+
-2.0,
569+
10.0,
570+
6.0,
571+
cos(deg2rad(45.0)),
572+
sin(deg2rad(45.0)),
573+
M_PI / 4.0,
574+
M_PI / 4.0,
575+
],
576+
);
577+
578+
self::assertCount(1, $curves);
579+
self::assertCurveMatches(
580+
[
581+
2.8790818120695802,
582+
5.469770453017396,
583+
0.632003854165071,
584+
4.117285228403642,
585+
-1.2426406871192843,
586+
2.242640687119286,
587+
],
588+
$curves[0],
589+
);
590+
}
591+
420592
public function testGenerateArcCurvesSplitsLargerAnglesIntoExpectedSegments(): void
421593
{
422594
$converter = new SvgArcConverter();
@@ -452,6 +624,43 @@ public function testGenerateArcCurvesSplitsLargerAnglesIntoExpectedSegments(): v
452624
);
453625
}
454626

627+
public function testGenerateArcCurvesMatchesExpectedRotatedThreeQuarterSegments(): void
628+
{
629+
$converter = new SvgArcConverter();
630+
631+
$curves = self::invokePrivateMethod(
632+
$converter,
633+
'generateArcCurves',
634+
[
635+
3.0,
636+
-2.0,
637+
10.0,
638+
6.0,
639+
cos(deg2rad(45.0)),
640+
sin(deg2rad(45.0)),
641+
0.0,
642+
3.0 * M_PI / 4.0,
643+
],
644+
);
645+
646+
self::assertCount(2, $curves);
647+
self::assertCurveMatches(
648+
[
649+
8.358540583851704,
650+
6.783595039879246,
651+
5.078595495120528,
652+
6.607261689108819,
653+
1.7862916061018566,
654+
4.625669395360115,
655+
],
656+
$curves[0],
657+
);
658+
self::assertCurveMatches(
659+
[-1.506012282916814, 2.64407710161141, -4.192706922736574, -0.7708276909462963, -5.0, -3.9999999999999987],
660+
$curves[1],
661+
);
662+
}
663+
455664
#[DataProvider('provideArcScenarios')]
456665
public function testArcToBezierCurvesGeneratesExpectedCurveShape(
457666
float $fromX,

0 commit comments

Comments
 (0)