@@ -64,24 +64,18 @@ public function testCreateSupportsPolygonAndRectElements(): void
6464 self ::assertStringContainsString ('20.000000 0.000000 l ' , $ xObject ->stream );
6565 }
6666
67- public function testCreateRejectsInvalidSvgPayloads (): void
68- {
69- $ factory = new SvgPdfXObjectFactory ();
70-
71- $ this ->expectException (InvalidArgumentException::class);
72- $ this ->expectExceptionMessage ('Unable to parse SVG source "/tmp/invalid.svg". ' );
73-
74- $ factory ->create ('<html></html> ' , '/tmp/invalid.svg ' );
75- }
76-
77- public function testCreateRejectsMalformedSvgRootEvenWhenSvgTagExists (): void
78- {
67+ #[DataProvider('provideInvalidSvgSources ' )]
68+ public function testCreateRejectsInvalidSvgSources (
69+ string $ svg ,
70+ string $ sourcePath ,
71+ string $ expectedMessage ,
72+ ): void {
7973 $ factory = new SvgPdfXObjectFactory ();
8074
8175 $ this ->expectException (InvalidArgumentException::class);
82- $ this ->expectExceptionMessage (' Unable to parse SVG source "/tmp/malformed-root.svg". ' );
76+ $ this ->expectExceptionMessage ($ expectedMessage );
8377
84- $ factory ->create (' < svg xmlns="http://www.w3.org/2000/svg"><g> ' , ' /tmp/malformed-root.svg ' );
78+ $ factory ->create ($ svg, $ sourcePath );
8579 }
8680
8781 #[DataProvider('provideInvalidViewportScenarios ' )]
@@ -286,26 +280,6 @@ public function testCreateSkipsUnrecognizedShapeElementsGracefully(): void
286280 self ::assertStringContainsString ('1 0 0 rg ' , $ xObject ->stream );
287281 }
288282
289- public function testCreateWithEmptySvgStringThrows (): void
290- {
291- $ factory = new SvgPdfXObjectFactory ();
292-
293- $ this ->expectException (InvalidArgumentException::class);
294- $ this ->expectExceptionMessage ('Unable to parse SVG source "/tmp/empty.svg". ' );
295-
296- $ factory ->create ('' , '/tmp/empty.svg ' );
297- }
298-
299- public function testCreateWithNonSvgRootElementThrows (): void
300- {
301- $ factory = new SvgPdfXObjectFactory ();
302-
303- $ this ->expectException (InvalidArgumentException::class);
304- $ this ->expectExceptionMessage ('Unable to parse SVG source "/tmp/wrong-root.svg". ' );
305-
306- $ factory ->create ('<?xml version="1.0"?><root></root> ' , '/tmp/wrong-root.svg ' );
307- }
308-
309283 public function testCreateAcceptsUppercaseSvgRootElementName (): void
310284 {
311285 $ factory = new SvgPdfXObjectFactory ();
@@ -414,142 +388,19 @@ public function testCreateParsesWidthAndHeightWithSurroundingWhitespace(): void
414388 self ::assertSame ([0.0 , 0.0 , 12.5 , 7.25 ], $ xObject ->dictionary ['BBox ' ]);
415389 }
416390
417- public function testCreateRejectsDimensionWithNonNumericPrefix (): void
418- {
419- $ factory = new SvgPdfXObjectFactory ();
420-
421- $ this ->expectException (InvalidArgumentException::class);
422- $ this ->expectExceptionMessage (
423- 'SVG source "/tmp/non-numeric-dimension.svg" must define either a valid viewBox or positive width/height. ' ,
424- );
425-
426- $ factory ->create (
427- '<svg width="abc12" height="10" xmlns="http://www.w3.org/2000/svg"> '
428- . '<path fill="#000" d="M0,0 L1,1"/> '
429- . '</svg> ' ,
430- '/tmp/non-numeric-dimension.svg ' ,
431- );
432- }
433-
434- public function testCreateRejectsZeroWidthWhenNoViewBoxIsProvided (): void
435- {
436- $ factory = new SvgPdfXObjectFactory ();
437-
438- $ this ->expectException (InvalidArgumentException::class);
439- $ this ->expectExceptionMessage (
440- 'SVG source "/tmp/zero-width.svg" must define either a valid viewBox or positive width/height. ' ,
441- );
442-
443- $ factory ->create (
444- '<svg width="0" height="10" xmlns="http://www.w3.org/2000/svg"> '
445- . '<path fill="#000" d="M0,0 L1,1"/> '
446- . '</svg> ' ,
447- '/tmp/zero-width.svg ' ,
448- );
449- }
450-
451- public function testCreateRejectsZeroHeightWhenNoViewBoxIsProvided (): void
452- {
453- $ factory = new SvgPdfXObjectFactory ();
454-
455- $ this ->expectException (InvalidArgumentException::class);
456- $ this ->expectExceptionMessage (
457- 'SVG source "/tmp/zero-height.svg" must define either a valid viewBox or positive width/height. ' ,
458- );
459-
460- $ factory ->create (
461- '<svg width="10" height="0" xmlns="http://www.w3.org/2000/svg"> '
462- . '<path fill="#000" d="M0,0 L1,1"/> '
463- . '</svg> ' ,
464- '/tmp/zero-height.svg ' ,
465- );
466- }
467-
468- public function testCreateResolvesUppercaseCssFillAndStrokePropertiesFromStyleBlock (): void
469- {
470- $ factory = new SvgPdfXObjectFactory ();
471-
472- $ xObject = $ factory ->create (
473- <<<'SVG'
474- <svg width="12" height="12" xmlns="http://www.w3.org/2000/svg">
475- <style>
476- .box { FILL: #112233; STROKE: #ff0000; }
477- </style>
478- <rect class="box" x="1" y="1" width="10" height="10" style="stroke-width:2"/>
479- </svg>
480- SVG,
481- '/tmp/uppercase-css-style.svg ' ,
482- );
483-
484- self ::assertStringContainsString ('0.0667 0.1333 0.2 rg ' , $ xObject ->stream );
485- self ::assertStringContainsString ('1 0 0 RG ' , $ xObject ->stream );
486- self ::assertStringContainsString ('2.000000 w ' , $ xObject ->stream );
487- }
488-
489- public function testCreateIgnoresEmptyAndNonMatchingStyleBlocksBeforeValidClassRule (): void
490- {
491- $ factory = new SvgPdfXObjectFactory ();
492-
493- $ xObject = $ factory ->create (
494- <<<'SVG'
495- <svg width="12" height="12" xmlns="http://www.w3.org/2000/svg">
496- <style></style>
497- <style>path { fill: #00ff00; }</style>
498- <style>.accent { fill: #112233; stroke: #ff0000; }</style>
499- <rect class="accent" x="1" y="1" width="10" height="10" style="stroke-width:2"/>
500- </svg>
501- SVG,
502- '/tmp/style-continue-coverage.svg ' ,
503- );
504-
505- self ::assertStringContainsString ('0.0667 0.1333 0.2 rg ' , $ xObject ->stream );
506- self ::assertStringContainsString ('1 0 0 RG ' , $ xObject ->stream );
507- self ::assertStringContainsString ('2.000000 w ' , $ xObject ->stream );
508- }
509-
510- public function testCreateWithMissingDimensionsOrViewBoxThrows (): void
511- {
512- $ factory = new SvgPdfXObjectFactory ();
513-
514- $ this ->expectException (InvalidArgumentException::class);
515- $ this ->expectExceptionMessage (
516- 'SVG source "/tmp/no-viewport.svg" must define either a valid viewBox or positive width/height. ' ,
517- );
518-
519- $ factory ->create (
520- '<svg xmlns="http://www.w3.org/2000/svg"><path d="M0,0"/></svg> ' ,
521- '/tmp/no-viewport.svg ' ,
522- );
523- }
524-
525- public function testCreateWithZeroDimensionsThrows (): void
526- {
527- $ factory = new SvgPdfXObjectFactory ();
528-
529- $ this ->expectException (InvalidArgumentException::class);
530- $ this ->expectExceptionMessage (
531- 'SVG source "/tmp/zero-dims.svg" must define a positive viewBox. ' ,
532- );
533-
534- $ factory ->create (
535- '<svg viewBox="0 0 0 0" xmlns="http://www.w3.org/2000/svg"><path d="M0,0"/></svg> ' ,
536- '/tmp/zero-dims.svg ' ,
537- );
538- }
539-
540- public function testCreateWithViewBoxButNegativeHeightThrows (): void
541- {
391+ #[DataProvider('provideStyleBlockColorExtractionScenarios ' )]
392+ public function testCreateResolvesClassColorMapsFromStyleBlocks (
393+ string $ svg ,
394+ string $ sourcePath ,
395+ array $ requiredStreamFragments ,
396+ ): void {
542397 $ factory = new SvgPdfXObjectFactory ();
543398
544- $ this ->expectException (InvalidArgumentException::class);
545- $ this ->expectExceptionMessage (
546- 'SVG source "/tmp/negative-height.svg" must define a positive viewBox. ' ,
547- );
399+ $ xObject = $ factory ->create ($ svg , $ sourcePath );
548400
549- $ factory ->create (
550- '<svg viewBox="0 0 10 -5" xmlns="http://www.w3.org/2000/svg"><path d="M0,0"/></svg> ' ,
551- '/tmp/negative-height.svg ' ,
552- );
401+ foreach ($ requiredStreamFragments as $ fragment ) {
402+ self ::assertStringContainsString ($ fragment , $ xObject ->stream );
403+ }
553404 }
554405
555406 public function testCreateWithViewBoxAndDimensionsCombination (): void
@@ -686,6 +537,92 @@ public static function provideInvalidViewportScenarios(): iterable
686537 '<svg width="auto" height="" xmlns="http://www.w3.org/2000/svg"><path d="M0,0"/></svg> ' ,
687538 'SVG source "/tmp/invalid-viewport.svg" must define either a valid viewBox or positive width/height. ' ,
688539 ];
540+
541+ yield 'missing dimensions and viewbox ' => [
542+ '<svg xmlns="http://www.w3.org/2000/svg"><path d="M0,0"/></svg> ' ,
543+ 'SVG source "/tmp/invalid-viewport.svg" must define either a valid viewBox or positive width/height. ' ,
544+ ];
545+
546+ yield 'zero width without viewbox ' => [
547+ '<svg width="0" height="10" xmlns="http://www.w3.org/2000/svg"><path d="M0,0 L1,1"/></svg> ' ,
548+ 'SVG source "/tmp/invalid-viewport.svg" must define either a valid viewBox or positive width/height. ' ,
549+ ];
550+
551+ yield 'zero height without viewbox ' => [
552+ '<svg width="10" height="0" xmlns="http://www.w3.org/2000/svg"><path d="M0,0 L1,1"/></svg> ' ,
553+ 'SVG source "/tmp/invalid-viewport.svg" must define either a valid viewBox or positive width/height. ' ,
554+ ];
555+
556+ yield 'non numeric dimension prefix ' => [
557+ '<svg width="abc12" height="10" xmlns="http://www.w3.org/2000/svg"><path d="M0,0 L1,1"/></svg> ' ,
558+ 'SVG source "/tmp/invalid-viewport.svg" must define either a valid viewBox or positive width/height. ' ,
559+ ];
560+
561+ yield 'viewbox with zero dimensions ' => [
562+ '<svg viewBox="0 0 0 0" xmlns="http://www.w3.org/2000/svg"><path d="M0,0"/></svg> ' ,
563+ 'SVG source "/tmp/invalid-viewport.svg" must define a positive viewBox. ' ,
564+ ];
565+
566+ yield 'viewbox with negative height ' => [
567+ '<svg viewBox="0 0 10 -5" xmlns="http://www.w3.org/2000/svg"><path d="M0,0"/></svg> ' ,
568+ 'SVG source "/tmp/invalid-viewport.svg" must define a positive viewBox. ' ,
569+ ];
570+ }
571+
572+ public static function provideInvalidSvgSources (): iterable
573+ {
574+ yield 'empty svg string ' => [
575+ '' ,
576+ '/tmp/empty.svg ' ,
577+ 'Unable to parse SVG source "/tmp/empty.svg". ' ,
578+ ];
579+
580+ yield 'html payload ' => [
581+ '<html></html> ' ,
582+ '/tmp/invalid.svg ' ,
583+ 'Unable to parse SVG source "/tmp/invalid.svg". ' ,
584+ ];
585+
586+ yield 'non svg root element ' => [
587+ '<?xml version="1.0"?><root></root> ' ,
588+ '/tmp/wrong-root.svg ' ,
589+ 'Unable to parse SVG source "/tmp/wrong-root.svg". ' ,
590+ ];
591+
592+ yield 'malformed svg root ' => [
593+ '<svg xmlns="http://www.w3.org/2000/svg"><g> ' ,
594+ '/tmp/malformed-root.svg ' ,
595+ 'Unable to parse SVG source "/tmp/malformed-root.svg". ' ,
596+ ];
597+ }
598+
599+ public static function provideStyleBlockColorExtractionScenarios (): iterable
600+ {
601+ yield 'uppercase fill and stroke properties ' => [
602+ <<<'SVG'
603+ <svg width="12" height="12" xmlns="http://www.w3.org/2000/svg">
604+ <style>
605+ .box { FILL: #112233; STROKE: #ff0000; }
606+ </style>
607+ <rect class="box" x="1" y="1" width="10" height="10" style="stroke-width:2"/>
608+ </svg>
609+ SVG,
610+ '/tmp/uppercase-css-style.svg ' ,
611+ ['0.0667 0.1333 0.2 rg ' , '1 0 0 RG ' , '2.000000 w ' ],
612+ ];
613+
614+ yield 'ignore empty and non matching style blocks ' => [
615+ <<<'SVG'
616+ <svg width="12" height="12" xmlns="http://www.w3.org/2000/svg">
617+ <style></style>
618+ <style>path { fill: #00ff00; }</style>
619+ <style>.accent { fill: #112233; stroke: #ff0000; }</style>
620+ <rect class="accent" x="1" y="1" width="10" height="10" style="stroke-width:2"/>
621+ </svg>
622+ SVG,
623+ '/tmp/style-continue-coverage.svg ' ,
624+ ['0.0667 0.1333 0.2 rg ' , '1 0 0 RG ' , '2.000000 w ' ],
625+ ];
689626 }
690627
691628 public static function provideDimensionScenarios (): iterable
0 commit comments