Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
InlineSvgAdapter
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
2 / 2
7
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 adapt
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
6
1<?php
2
3declare(strict_types=1);
4
5namespace Phpdftk\HtmlToPdf\Svg;
6
7use Phpdftk\Html\Dom\Element as HtmlElement;
8use Phpdftk\HtmlToPdf\ForeignContent\DomXmlSerializer;
9use Phpdftk\Svg\Parser as SvgParser;
10use Phpdftk\Svg\SvgDocument;
11
12/**
13 * Convert an inline-SVG subtree from the HTML DOM into a typed
14 * {@see SvgDocument} so the existing SVG renderer can paint it.
15 *
16 * Routes through {@see DomXmlSerializer} for the HTML-DOM-to-XML
17 * walk + namespace plumbing, then hands the XML to the SVG parser.
18 * The adapter's own responsibility is:
19 *
20 *   - Reject elements that aren't an `<svg>` in `SVG_NS`.
21 *   - Cache parsed documents by element identity so the same
22 *     `<svg>` painted on N pages only pays the parse cost once.
23 *
24 * The cache uses `===` identity, not equality — two distinct `<svg>`
25 * elements with byte-identical markup get parsed once each.
26 */
27final class InlineSvgAdapter
28{
29    /** @var \SplObjectStorage<HtmlElement, SvgDocument> */
30    private \SplObjectStorage $cache;
31
32    public function __construct(
33        private readonly SvgParser $parser = new SvgParser(),
34        private readonly DomXmlSerializer $serializer = new DomXmlSerializer(),
35    ) {
36        $this->cache = new \SplObjectStorage();
37    }
38
39    /**
40     * Adapt an HTML DOM `<svg>` element into a typed SvgDocument.
41     *
42     * @throws \InvalidArgumentException When the element isn't an
43     *   `<svg>` in the SVG namespace.
44     */
45    public function adapt(HtmlElement $element): SvgDocument
46    {
47        // Accept both `<svg>` in the SVG namespace and the prefixed XHTML
48        // form `<svg:svg>` (left as a plain HTML element by the HTML
49        // parser). The serializer below re-namespaces the subtree before
50        // the SVG parser sees it.
51        $tag = strtolower($element->localName);
52        $colon = strrpos($tag, ':');
53        $local = $colon !== false ? substr($tag, $colon + 1) : $tag;
54        $isSvgNs = $element->namespaceUri() === SvgParser::SVG_NS;
55        if ($local !== 'svg' || (!$isSvgNs && $colon === false)) {
56            throw new \InvalidArgumentException(sprintf(
57                'InlineSvgAdapter expects an <svg> element in the SVG namespace '
58                . '(or the prefixed <svg:svg> form), got <%s> in namespace %s.',
59                $element->localName,
60                $element->namespaceUri() ?? '(null)',
61            ));
62        }
63        if ($this->cache->contains($element)) {
64            return $this->cache[$element];
65        }
66        $xml = $this->serializer->serialize($element, SvgParser::SVG_NS);
67        $svg = $this->parser->parse($xml);
68        $this->cache[$element] = $svg;
69        return $svg;
70    }
71}