* @copyright 2002-2026 Nicola Asuni - Tecnick.com LTD
* @license https://www.gnu.org/copyleft/lesser.html GNU-LGPL v3 (see LICENSE)
* @link https://github.com/tecnickcom/tc-lib-pdf
*
* This file is part of tc-lib-pdf software library.
*/
namespace Test;
class SVGTest extends TestUtil
{
public static function setUpBeforeClass(): void
{
self::setUpFontsPath();
}
/** @throws \Throwable */
protected function getTestObject(): \Com\Tecnick\Pdf\Tcpdf
{
return new \Com\Tecnick\Pdf\Tcpdf();
}
/** @throws \Throwable */
protected function getInternalTestObject(): TestableSVG
{
return new TestableSVG();
}
/** @throws \Throwable */
public function testAddSVGStoresInlineSvgObject(): void
{
$obj = $this->getTestObject();
$page = $this->initFontAndPage($obj);
$svg =
'@';
$soid = $obj->addSVG($svg, 10, 20, 40, 20, $page['height']);
$this->assertSame(1, $soid);
/** @var array> $svgobjs */
$svgobjs = $this->getObjectProperty($obj, 'svgobjs');
$this->assertArrayHasKey($soid, $svgobjs);
$svgObj = $svgobjs[$soid] ?? null;
$this->assertIsArray($svgObj);
$this->assertNotSame('', $svgObj['out'] ?? '');
}
/** @throws \Throwable */
public function testAddSVGRejectsInvalidSvg(): void
{
$obj = $this->getTestObject();
$this->initFontAndPage($obj);
$this->bcExpectException(\Com\Tecnick\Pdf\Exception::class);
$obj->addSVG('@');
}
/** @throws \Throwable */
public function testGetSetSVGReturnsRenderableOutput(): void
{
$obj = $this->getTestObject();
$page = $this->initFontAndPage($obj);
$svg =
'@';
$soid = $obj->addSVG($svg, 5, 6, 12, 12, $page['height']);
$out = $obj->getSetSVG($soid);
$this->assertNotSame('', $out);
$this->assertStringStartsWith("q\n", $out);
$this->assertStringContainsString(" cm\n", $out);
$this->assertStringEndsWith("Q\nQ\n", $out);
}
/** @throws \Throwable */
public function testSvgPatternFillEmitsPdfPatternResources(): void
{
$obj = new \Com\Tecnick\Pdf\Tcpdf('mm', true, false, false);
$page = $this->initFontAndPage($obj);
$svg =
'@';
$soid = $obj->addSVG($svg, 5, 6, 12, 12, $page['height']);
$svgOut = $obj->getSetSVG($soid);
$pdf = $obj->getOutPDFString();
$this->assertStringContainsString('/Pattern cs /PTN_', $svgOut);
$this->assertStringContainsString(' scn', $svgOut);
$this->assertStringContainsString('/Type /Pattern', $pdf);
$this->assertStringContainsString('/Pattern <<', $pdf);
$this->assertStringContainsString('/Type /Pattern /PatternType 1', $pdf);
$this->assertStringContainsString('/Resources << /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]', $pdf);
$this->assertMatchesRegularExpression('/\/PTN_[A-F0-9]{16}\s+\d+\s+0\s+R/', $pdf);
}
/** @throws \Throwable */
public function testGetSetSVGThrowsForUnknownId(): void
{
$obj = $this->getTestObject();
$this->bcExpectException(\Com\Tecnick\Pdf\Exception::class);
$obj->getSetSVG(999);
}
/** @throws \Throwable */
public function testParseSVGTMtranslateWithTwoValues(): void
{
$obj = $this->getInternalTestObject();
$result = $obj->exposeParseSVGTMtranslate('10.5, 20.3');
$this->assertSame([1.0, 0.0, 0.0, 1.0, 10.5, 20.3], $result);
}
/** @throws \Throwable */
public function testParseSVGTMtranslateWithOneValue(): void
{
$obj = $this->getInternalTestObject();
$result = $obj->exposeParseSVGTMtranslate('5.5');
$this->assertSame([1.0, 0.0, 0.0, 1.0, 5.5, 0.0], $result);
}
/** @throws \Throwable */
public function testParseSVGTMscaleWithTwoValues(): void
{
$obj = $this->getInternalTestObject();
$result = $obj->exposeParseSVGTMscale('2.0, 3.0');
$this->assertSame([2.0, 0.0, 0.0, 3.0, 0.0, 0.0], $result);
}
/** @throws \Throwable */
public function testParseSVGTMscaleWithOneValue(): void
{
$obj = $this->getInternalTestObject();
$result = $obj->exposeParseSVGTMscale('1.5');
$this->assertSame([1.5, 0.0, 0.0, 1.5, 0.0, 0.0], $result);
}
/** @throws \Throwable */
public function testParseSVGTMrotateWithDegrees(): void
{
$obj = $this->getInternalTestObject();
$result = $obj->exposeParseSVGTMrotate('45');
$this->assertCount(6, $result);
// Rotation matrix has cos and sin components that can be floating point
$this->assertNotSame([1.0, 0.0, 0.0, 1.0, 0.0, 0.0], $result);
}
/** @throws \Throwable */
public function testParseSVGTMskewXWithDegrees(): void
{
$obj = $this->getInternalTestObject();
$result = $obj->exposeParseSVGTMskewX('30');
$this->assertCount(6, $result);
$this->assertNotSame([1.0, 0.0, 0.0, 1.0, 0.0, 0.0], $result);
}
/** @throws \Throwable */
public function testParseSVGTMskewYWithDegrees(): void
{
$obj = $this->getInternalTestObject();
$result = $obj->exposeParseSVGTMskewY('30');
$this->assertCount(6, $result);
$this->assertNotSame([1.0, 0.0, 0.0, 1.0, 0.0, 0.0], $result);
}
/** @throws \Throwable */
public function testParseSVGTMmatrixWithSixValues(): void
{
$obj = $this->getInternalTestObject();
$result = $obj->exposeParseSVGTMmatrix('1 2 3 4 5 6');
$this->assertSame([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], $result);
}
/** @throws \Throwable */
public function testParseSVGTMmatrixWithCSVFormat(): void
{
$obj = $this->getInternalTestObject();
$result = $obj->exposeParseSVGTMmatrix('1.5, 2.5, 3.5, 4.5, 5.5, 6.5');
$this->assertSame([1.5, 2.5, 3.5, 4.5, 5.5, 6.5], $result);
}
/** @throws \Throwable */
public function testSvgUnitConvertersAndTransformHelpers(): void
{
$obj = $this->getInternalTestObject();
$obj->setSvgRefUnit(1);
$this->assertSame(72.0, $obj->exposeSvgUnitToPoints('1in', 1));
$this->assertEqualsWithDelta(25.4, $obj->exposeSvgUnitToUnit('25.4mm', 1), 0.0001);
$transformMatrix = $obj->exposeGetSVGTransformMatrix('translate(10,20) scale(2) rotate(0)');
$this->assertCount(6, $transformMatrix);
$converted = $obj->exposeConvertSVGMatrix([1, 0, 0, 1, 10, 20], 1);
$this->assertCount(6, $converted);
$this->assertSame(7.5, $converted[4]);
$this->assertSame(-15.0, $converted[5]);
$out = $obj->exposeGetOutSVGTransformation([1, 0, 0, 1, 10, 20], 1);
$this->assertStringContainsString(' cm', $out);
}
/** @throws \Throwable */
public function testSvgTagPathAndCssAttributeHelpers(): void
{
$obj = $this->getInternalTestObject();
$obj->setSvgRefUnit(2);
$this->assertSame('rect', $obj->exposeRemoveTagNamespace('svg:rect'));
$this->assertSame('', $obj->exposeGetSVGPath(2, 'M 0 0 L 10 10 Z', ''));
$path = $obj->exposeGetSVGPath(2, 'M 0 0 L 10 10 H 5 V 3 Z', 'S');
$this->assertStringContainsString('m', $path);
$this->assertStringContainsString('l', $path);
$this->assertStringContainsString("h\n", $path);
$tag = 'font-size: 12px; font-weight: bold;';
$this->assertSame('12px', $obj->exposeParseCSSAttrib($tag, 'font-size', '10px'));
$this->assertSame('fallback', $obj->exposeParseCSSAttrib($tag, 'line-height', 'fallback'));
}
/** @throws \Throwable */
public function testSvgHelperNormalizationAndClipDispatchBranches(): void
{
$obj = $this->getInternalTestObject();
$this->initFontAndPage($obj);
$obj->setSvgRefUnit(4);
$this->assertSame(4.0, $obj->exposeNormalizeSVGStyleFloatValue(4, 1.5));
$this->assertSame(2.5, $obj->exposeNormalizeSVGStyleFloatValue(2.5, 1.5));
$this->assertSame(3.75, $obj->exposeNormalizeSVGStyleFloatValue(' 3.75 ', 1.5));
$this->assertSame(1.5, $obj->exposeNormalizeSVGStyleFloatValue('not-a-number', 1.5));
$this->assertTrue($obj->exposeNormalizeSVGBoolLike(true));
$this->assertFalse($obj->exposeNormalizeSVGBoolLike(false));
$this->assertTrue($obj->exposeNormalizeSVGBoolLike(1));
$this->assertFalse($obj->exposeNormalizeSVGBoolLike(0.0));
$this->assertTrue($obj->exposeNormalizeSVGBoolLike(' yes '));
$this->assertFalse($obj->exposeNormalizeSVGBoolLike('off'));
$this->assertSame('', $obj->exposeGetSVGMarkerId(''));
$this->assertSame('', $obj->exposeGetSVGMarkerId('none'));
$this->assertSame('marker1', $obj->exposeGetSVGMarkerId('url(#marker1)'));
$this->assertSame('', $obj->exposeGetSVGMarkerId('invalid-marker-value'));
$this->assertSame('', $obj->exposeApplySVGClipFunction('unknownClip', []));
$this->assertStringContainsString('m', $obj->exposeApplySVGClipFunction('getSVGPath', [
4,
'M 0 0 L 10 10 Z',
'S',
]));
$this->assertNotSame('', $obj->exposeApplySVGClipFunction('getRoundedRect', [
5.0,
5.0,
10.0,
8.0,
2.0,
2.0,
'1111',
'DF',
]));
$this->assertNotSame('', $obj->exposeApplySVGClipFunction('getCircle', [10.0, 10.0, 5.0, 0.0, 360.0, 'S']));
$this->assertNotSame('', $obj->exposeApplySVGClipFunction('getEllipse', [
10.0,
10.0,
6.0,
3.0,
0.0,
0.0,
360.0,
'S',
]));
$this->assertNotSame('', $obj->exposeApplySVGClipFunction('getLine', [0.0, 0.0, 10.0, 10.0]));
$this->assertNotSame('', $obj->exposeApplySVGClipFunction('getPolygon', [
[0.0, 0.0, 10.0, 0.0, 10.0, 10.0],
'S',
]));
$this->assertNotSame('', $obj->exposeApplySVGClipFunction('getClippingRect', [0.0, 0.0, 10.0, 10.0, true]));
$this->assertSame(0.0, $obj->exposeResolveSVGMarkerRefCoordinate('', 10.0, 20.0, 4));
$this->assertEqualsWithDelta(20.0, $obj->exposeResolveSVGMarkerRefCoordinate('50%', 10.0, 20.0, 4), 0.001);
$this->assertEqualsWithDelta(12.5, $obj->exposeResolveSVGMarkerRefCoordinate('12.5mm', 10.0, 20.0, 4), 0.001);
$this->assertEqualsWithDelta(100.0, $obj->exposeResolveSVGPatternLength('50%', 200.0, 4), 0.001);
$this->assertEqualsWithDelta(25.4, $obj->exposeResolveSVGPatternLength('25.4mm', 200.0, 4), 0.001);
$this->assertEqualsWithDelta(0.0, $obj->exposeResolveSVGPatternLength('', 200.0, 4), 0.001);
}
/** @throws \Throwable */
public function testSvgTextAttributeAndBlendAlphaHelpers(): void
{
$obj = $this->getInternalTestObject();
$this->assertSame(0.0, $obj->exposeGetTALetterSpacing('normal'));
$this->assertSame(1.25, $obj->exposeGetTALetterSpacing('inherit', 1.25));
$this->assertGreaterThan(0, $obj->exposeGetTALetterSpacing('2px'));
$this->assertSame(70.0, $obj->exposeGetTAFontStretching('condensed'));
$this->assertSame(120.0, $obj->exposeGetTAFontStretching('wider', 110));
$this->assertSame(95.0, $obj->exposeGetTAFontStretching('narrower', 105));
$this->assertSame(80.0, $obj->exposeGetTAFontStretching('80%'));
$this->assertSame('B', $obj->exposeGetTAFontWeight('bold'));
$this->assertSame('', $obj->exposeGetTAFontWeight('normal'));
$this->assertSame('I', $obj->exposeGetTAFontStyle('italic'));
$this->assertSame('', $obj->exposeGetTAFontStyle('normal'));
$this->assertSame('U', $obj->exposeGetTAFontDecoration('underline'));
$this->assertSame('O', $obj->exposeGetTAFontDecoration('overline'));
$this->assertSame('D', $obj->exposeGetTAFontDecoration('line-through'));
$this->assertSame('', $obj->exposeGetTAFontDecoration('none'));
$this->assertSame('Multiply', $obj->exposeNormalizeSVGBlendMode('multiply'));
$this->assertSame('Normal', $obj->exposeNormalizeSVGBlendMode('no-such-mode'));
$this->assertSame(1.0, $obj->exposeNormalizeSVGAlphaValue(1.5));
$this->assertSame(0.0, $obj->exposeNormalizeSVGAlphaValue(-0.4));
$this->assertSame(0.5, $obj->exposeNormalizeSVGAlphaValue('0.5'));
$this->assertSame('', $obj->exposeGetSVGExtGState(null, null, ''));
$gstate = $obj->exposeGetSVGExtGState(0.8, 0.5, 'Multiply');
$this->assertStringContainsString('gs', $gstate);
$pdfx3 = new TestableSVG('mm', true, false, true, 'pdfx3');
$this->assertSame('', $pdfx3->exposeGetSVGExtGState(0.8, 0.5, 'Multiply'));
$pdfx4 = new TestableSVG('mm', true, false, true, 'pdfx4');
$this->assertStringContainsString('gs', $pdfx4->exposeGetSVGExtGState(0.8, 0.5, 'Multiply'));
}
/** @throws \Throwable */
public function testSvgPathCommandHelpersProducePathOperations(): void
{
$obj = $this->getInternalTestObject();
$coord = $obj->getPathCoordDefaults();
[$mout, $coord] = $obj->exposeSvgPathCmdM([1, 2, 3, 4], $coord);
$this->assertStringContainsString('m', $mout);
[$lout, $coord] = $obj->exposeSvgPathCmdL([5, 6, 7, 8], $coord);
$this->assertStringContainsString('l', $lout);
[$hout, $coord] = $obj->exposeSvgPathCmdH([9, 10], $coord);
$this->assertStringContainsString('l', $hout);
[$vout, $coord] = $obj->exposeSvgPathCmdV([11, 12], $coord);
$this->assertStringContainsString('l', $vout);
[$cout, $coord] = $obj->exposeSvgPathCmdC([13, 14, 15, 16, 17, 18], $coord);
$this->assertStringContainsString('c', $cout);
[$qout, $coord] = $obj->exposeSvgPathCmdQ([19, 20, 21, 22], $coord);
$this->assertStringContainsString('c', $qout);
[$sout, $coord] = $obj->exposeSvgPathCmdS([23, 24, 25, 26], $coord);
$this->assertStringContainsString('c', $sout);
[$tout, $coord] = $obj->exposeSvgPathCmdT([27, 28], $coord);
$this->assertStringContainsString('c', $tout);
$arcPaths = [['0', 'A'], ['1', 'z']];
[$aout, $coord] = $obj->exposeSvgPathCmdA([5, 5, 0, 0, 1, 30, 35], $coord, $arcPaths, 0, [
'5',
'5',
'0',
'0',
'1',
'30',
'35',
]);
$this->assertNotSame('', $aout);
[$zout, $coord] = $obj->exposeSvgPathCmdZ($coord);
$this->assertSame("h\n", $zout);
$this->assertSame($coord['xinit'], $coord['x']);
$this->assertSame($coord['yinit'], $coord['y']);
}
/** @throws \Throwable */
public function testSvgStyleHelperMethodsCoverCorePaths(): void
{
$obj = $this->getInternalTestObject();
$this->initFontAndPage($obj);
$obj->setSvgObjMeta(3);
$base = $obj->exposeDefaultSVGStyle();
[$fontOut, $fontStyle] = $obj->exposeParseSVGStyleFont($base, $base);
$this->assertNotSame('', $fontOut);
$this->assertArrayHasKey('font-size-val', $fontStyle);
$strokeStyle = $base;
$strokeStyle['stroke'] = '#112233';
$strokeStyle['stroke-width'] = 1.0;
$strokeStyle['stroke-linecap'] = 'butt';
$strokeStyle['stroke-linejoin'] = 'miter';
$strokeStyle['stroke-opacity'] = 1.0;
$strokeStyle['opacity'] = 1.0;
$strokeStyle['mix-blend-mode'] = 'normal';
$strokeStyle['objstyle'] = '';
[$strokeOut, $strokeStyle] = $obj->exposeParseSVGStyleStroke(3, $strokeStyle);
$this->assertNotSame('', $strokeOut);
$this->assertStringContainsString('D', $strokeStyle['objstyle']);
$colorStyle = $base;
$colorStyle['opacity'] = 0.5;
$colorStyle['mix-blend-mode'] = 'multiply';
$colorStyle['color'] = '#ff0000';
$colorStyle['text-color'] = '#00ff00';
[$colorOut] = $obj->exposeParseSVGStyleColor($colorStyle);
$this->assertNotSame('', $colorOut);
$pdfx3 = new TestableSVG('mm', true, false, true, 'pdfx3');
[$pdfx3ColorOut] = $pdfx3->exposeParseSVGStyleColor($colorStyle);
$this->assertStringNotContainsString('/GS', $pdfx3ColorOut);
$clipStyle = $base;
$clipStyle['clip'] = 'rect(1 2 3 4)';
$clipStyle['clip-rule'] = 'evenodd';
[$clipOut] = $obj->exposeParseSVGStyleClip($clipStyle, 10, 20, 100, 50);
$this->assertNotSame('', $clipOut);
$this->assertSame('', $obj->exposeParseSVGStyleGradient(3, [], 'missing', 0, 0, 10, 10));
$fillNone = $base;
$fillNone['fill'] = 'none';
[$fillNoneOut] = $obj->exposeParseSVGStyleFill(3, $fillNone, [], 0, 0, 10, 10);
$this->assertSame('', $fillNoneOut);
$fillUrl = $base;
$fillUrl['fill'] = 'url(#missing)';
$fillUrl['opacity'] = 1.0;
$fillUrl['fill-opacity'] = 1.0;
[$fillUrlOut] = $obj->exposeParseSVGStyleFill(3, $fillUrl, [], 0, 0, 10, 10);
$this->assertSame('', $fillUrlOut);
$obj->patchSvgObj(3, [
'defs' => [
'patEmpty' => [
'name' => 'pattern',
'attr' => [
'id' => 'patEmpty',
'patternUnits' => 'userSpaceOnUse',
'x' => '0',
'y' => '0',
'width' => '3',
'height' => '3',
],
'child' => [
'DF_1' => [
'name' => 'rect',
'attr' => [
'x' => '0',
'y' => '0',
'width' => '3',
'height' => '3',
'fill' => 'none',
'stroke' => '#000000',
'stroke-width' => '1',
],
],
'DF_1_CLOSE' => [
'name' => 'rect',
'attr' => [
'closing_tag' => true,
'content' => '',
],
],
],
],
],
]);
$fillPattern = $base;
$fillPattern['fill'] = 'url(#patEmpty)';
$fillPattern['opacity'] = 1.0;
$fillPattern['fill-opacity'] = 1.0;
$obj->patchSvgObj(3, ['out' => 'BASE']);
[$fillPatternOut] = $obj->exposeParseSVGStyleFill(3, $fillPattern, [], 0, 0, 10, 10, 'getClippingRect', [
0.0,
0.0,
10.0,
10.0,
0.0,
]);
$this->assertSame('BASE', $obj->getSvgObj(3)['out']);
$this->assertIsString($fillPatternOut);
$parser = \xml_parser_create('UTF-8');
$obj->exposeParseSVGStyleClipPath($parser, 3, []);
$earlyStyle = $base;
$earlyStyle['opacity'] = 0;
[$styleOut, $objstyle] = $obj->exposeParseSVGStyle($parser, 3, $earlyStyle, $base);
$this->assertSame('', $styleOut);
$this->assertSame('', $objstyle);
}
/** @throws \Throwable */
public function testSvgRawSizeAndPrescanHelpers(): void
{
$obj = $this->getInternalTestObject();
$obj->initSvgObjForHandlers(40);
$inline = '';
$this->assertSame($inline, $obj->exposeGetRawSVGData('@' . $inline));
$this->assertSame('', $obj->exposeGetRawSVGData('@'));
$tmp = \tempnam(\sys_get_temp_dir(), 'tc-svg-');
$this->assertNotFalse($tmp);
\file_put_contents($tmp, $inline);
try {
$this->assertSame($inline, $obj->exposeGetRawSVGData($tmp));
} finally {
if (\file_exists($tmp)) {
\unlink($tmp);
}
}
$size = $obj->exposeGetSVGSize(
'',
);
$this->assertGreaterThan(0, $size['width']);
$this->assertGreaterThan(0, $size['height']);
$this->assertSame('xMinYMin', $size['ar_align']);
$getPrescanAttributes = new \ReflectionMethod($obj, 'exposeGetSVGPrescanAttributes');
/** @var array $attr */
$attr = $getPrescanAttributes->invokeArgs($obj, [['id' => 'g1', 'stop-color' => '#fff', 'junk' => 'x']]);
$this->assertArrayHasKey('id', $attr);
$this->assertArrayHasKey('stop-color', $attr);
$this->assertArrayNotHasKey('junk', $attr);
$stopStyle = $obj->exposeGetSVGPrescanStopStyle(['style' => 'stop-color: #123456; stop-opacity: 0.25']);
$this->assertSame('#123456', $stopStyle['stop-color']);
$this->assertSame(0.25, $stopStyle['stop-opacity']);
$obj->exposePrescanSVGGradients(
'',
40,
);
$svgobj = $obj->getSvgObj(40);
$this->assertArrayHasKey('lg', $svgobj['gradients']);
$lg = $svgobj['gradients']['lg'] ?? null;
$this->assertIsArray($lg);
$stops = $lg['stops'];
$this->assertIsArray($stops);
$this->assertGreaterThanOrEqual(1, \count($stops));
}
/** @throws \Throwable */
public function testSvgGetSVGSizeCoversMissingViewboxAndThreeTokenAspectRatio(): void
{
$obj = $this->getInternalTestObject();
$noViewBox = $obj->exposeGetSVGSize('');
$this->assertGreaterThan(0.0, $noViewBox['x']);
$this->assertGreaterThan(0.0, $noViewBox['y']);
$this->assertGreaterThan(0.0, $noViewBox['width']);
$this->assertGreaterThan(0.0, $noViewBox['height']);
$this->assertSame([0.0, 0.0, 0.0, 0.0], $noViewBox['viewBox']);
$threeTokens = $obj->exposeGetSVGSize(
'',
);
$this->assertSame('xMaxYMax', $threeTokens['ar_align']);
$this->assertSame('slice', $threeTokens['ar_ms']);
}
/** @throws \Throwable */
public function testSvgPrescanRadialGradientWithDirectStopAttributes(): void
{
$obj = $this->getInternalTestObject();
$obj->initSvgObjForHandlers(44);
$obj->exposePrescanSVGGradients(
'',
44,
);
$svgobj = $obj->getSvgObj(44);
$this->assertArrayHasKey('rg', $svgobj['gradients']);
$rg = $svgobj['gradients']['rg'] ?? null;
$this->assertIsArray($rg);
$stops = $rg['stops'];
$this->assertIsArray($stops);
$this->assertGreaterThanOrEqual(1, \count($stops));
}
/** @throws \Throwable */
public function testSvgTagHandlersAndEndStartHelpers(): void
{
$obj = $this->getInternalTestObject();
$this->initFontAndPage($obj);
$obj->initSvgObjForHandlers(41);
$parser = \xml_parser_create('UTF-8');
$obj->exposeHandlerSVGCharacter($parser, 'Hello');
$svgobj = $obj->getSvgObj(41);
$this->assertSame('Hello', $svgobj['text']);
$obj->exposeParseSVGTagSTARTdefs(41);
$this->assertTrue($obj->getSvgObj(41)['defsmode']);
$obj->exposeParseSVGTagENDdefs(41);
$this->assertFalse($obj->getSvgObj(41)['defsmode']);
$obj->exposeParseSVGTagSTARTclipPath(41, [1, 0, 0, 1, 0, 0]);
$this->assertTrue($obj->getSvgObj(41)['clipmode']);
$obj->exposeParseSVGTagENDclipPath(41);
$this->assertFalse($obj->getSvgObj(41)['clipmode']);
$obj->patchSvgObj(41, ['styles' => [$obj->exposeDefaultSVGStyle(), $obj->exposeDefaultSVGStyle()]]);
$this->assertSame('', $obj->exposeParseSVGTagENDg(41));
$this->assertCount(1, $obj->getSvgObj(41)['styles']);
$obj->patchSvgObj(41, ['textmode' => ['invisible' => true]]);
$this->assertSame('', $obj->exposeParseSVGTagENDtext(41));
$this->assertSame('', $obj->exposeParseSVGTagENDtspan(41));
$obj->patchSvgObj(41, ['tagdepth' => 1]);
$this->assertSame('', $obj->exposeParseSVGTagENDsvg(41));
$obj->patchSvgObj(41, ['clipmode' => true, 'cliptm' => [1, 0, 0, 1, 0, 0]]);
$obj->exposeHandleSVGTagStart($parser, 'rect', ['x' => '1', 'y' => '2'], 41, false, [1, 0, 0, 1, 0, 0]);
$this->assertNotEmpty($obj->getSvgObj(41)['clippaths']);
$obj->patchSvgObj(41, ['defsmode' => false]);
$obj->exposeHandleSVGTagEnd($parser, 'defs');
$this->assertFalse($obj->getSvgObj(41)['defsmode']);
}
/** @throws \Throwable */
public function testSvgRemainingStartTagMethodsCoveredViaGuardBranches(): void
{
$obj = $this->getInternalTestObject();
$this->initFontAndPage($obj);
$obj->initSvgObjForHandlers(42);
$base = $obj->exposeDefaultSVGStyle();
$parser = \xml_parser_create('UTF-8');
// Root svg branch exits immediately.
$obj->patchSvgObj(42, ['tagdepth' => 0]);
$this->assertSame('', $obj->exposeParseSVGTagSTARTsvg($parser, 42, [], $base, $base));
// Group start path should return a transform block.
$gOut = $obj->exposeParseSVGTagSTARTg($parser, 42, [], $base, $base);
$this->assertNotSame('', $gOut);
// Gradient starts and stop insertion.
$this->assertSame('', $obj->exposeParseSVGTagSTARTlinearGradient(42, ['id' => 'lg2']));
$this->assertSame('', $obj->exposeParseSVGTagSTARTradialGradient(42, ['id' => 'rg2']));
$obj->patchSvgObj(42, ['gradientid' => 'lg2']);
$this->assertSame('', $obj->exposeParseSVGTagSTARTstop(42, ['offset' => '50%'], $base));
$svg42 = $obj->getSvgObj(42);
$this->assertNotEmpty($svg42['gradients']['lg2']['stops'] ?? []);
// Invisibility/clip guards for shape and text tags.
$obj->patchSvgObj(42, ['textmode' => ['invisible' => true], 'clipmode' => false]);
$this->assertSame('', $obj->exposeParseSVGTagSTARTpath($parser, 42, ['d' => 'M 0 0 L 1 1'], $base, $base));
$this->assertSame('', $obj->exposeParseSVGTagSTARTrect(
$parser,
42,
['width' => '1', 'height' => '1'],
$base,
$base,
));
$this->assertSame('', $obj->exposeParseSVGTagSTARTcircle($parser, 42, ['r' => '1'], $base, $base));
$this->assertSame('', $obj->exposeParseSVGTagSTARTellipse(
$parser,
42,
['rx' => '1', 'ry' => '1'],
$base,
$base,
));
$this->assertSame('', $obj->exposeParseSVGTagSTARTline(
$parser,
42,
['x1' => '0', 'y1' => '0', 'x2' => '1', 'y2' => '1'],
$base,
$base,
));
$this->assertSame('', $obj->exposeParseSVGTagSTARTpolygon($parser, 42, ['points' => '0,0 1,1'], $base, $base));
$this->assertSame('', $obj->exposeParseSVGTagSTARTimage($parser, 42, ['xlink:href' => 'x.png'], $base, $base));
$this->assertSame('', $obj->exposeParseSVGTagSTARTtext($parser, 42, ['x' => '0', 'y' => '0'], $base, $base));
$this->assertSame('', $obj->exposeParseSVGTagSTARTtspan($parser, 42, ['dx' => '1'], $base, $base));
// use tag guards when href is missing or unknown.
$this->assertSame('', $obj->exposeParseSVGTagSTARTuse($parser, 42, []));
$this->assertSame('', $obj->exposeParseSVGTagSTARTuse($parser, 42, ['xlink:href' => '#missing']));
}
/** @throws \Throwable */
public function testSvgUseTagResolvesDefinitionAndAppendsOutput(): void
{
$obj = $this->getInternalTestObject();
$this->initFontAndPage($obj);
$obj->initSvgObjForHandlers(45);
$obj->patchSvgObj(45, [
'defs' => [
'shape1' => [
'name' => 'rect',
'attr' => [
'x' => '2',
'y' => '3',
'width' => '5',
'height' => '4',
'style' => 'fill:#000000;stroke:none;',
],
],
],
'out' => '',
]);
$parser = \xml_parser_create('UTF-8');
$this->assertSame('', $obj->exposeParseSVGTagSTARTuse($parser, 45, [
'xlink:href' => '#shape1',
'x' => '5',
'y' => '7',
]));
$svgobj = $obj->getSvgObj(45);
$this->assertNotSame('', $svgobj['out']);
}
/**
* A