Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
91.30% covered (success)
91.30%
21 / 23
75.00% covered (warning)
75.00%
6 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
Use_
91.30% covered (success)
91.30%
21 / 23
75.00% covered (warning)
75.00%
6 / 8
15.15
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 x
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 y
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 width
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 height
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 href
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
4.02
 resolve
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 optionalLength
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
4.05
1<?php
2
3declare(strict_types=1);
4
5namespace Phpdftk\Svg;
6
7/**
8 * SVG `<use>` per SVG 2 §5.6 — instantiates another element somewhere
9 * else in the document. The class name carries a trailing underscore to
10 * avoid the PHP `use` keyword.
11 *
12 * Only **intra-document** references are honoured at v1. A `href` of the
13 * form `other.svg#foo` (or any non-`#`-prefixed value) returns null from
14 * `href()` — the parser never opens external documents (matching the
15 * security posture established in 3A: no implicit cross-document loads).
16 * Cross-document `<use>` resolution lands behind the same resource-loader
17 * gate that html-to-pdf uses, in Phase 2.
18 */
19final class Use_ extends Element
20{
21    public function __construct()
22    {
23        parent::__construct('use');
24    }
25
26    /** `x` offset for the referenced subtree (SVG 2 §5.6); default 0. */
27    public function x(): float
28    {
29        return $this->parseLengthOrZero('x');
30    }
31
32    /** `y` offset for the referenced subtree; default 0. */
33    public function y(): float
34    {
35        return $this->parseLengthOrZero('y');
36    }
37
38    /**
39     * `width` override. Null when absent so the painter knows to use the
40     * referent's intrinsic width (or `100%` for `<symbol>` referents per
41     * SVG 2 §5.6.2). Non-negative.
42     */
43    public function width(): ?float
44    {
45        return $this->optionalLength('width');
46    }
47
48    /** `height` override; null when absent. Non-negative. */
49    public function height(): ?float
50    {
51        return $this->optionalLength('height');
52    }
53
54    /**
55     * The referenced element's local id (without the leading `#`), or
56     * null when the attribute is absent or names something we can't
57     * resolve locally. Reads `href` first per SVG 2 §5.3.1 then falls
58     * back to the legacy `xlink:href` for older content.
59     */
60    public function href(): ?string
61    {
62        $raw = $this->getAttribute('href')
63            ?? $this->getAttribute('xlink:href');
64        if ($raw === null) {
65            return null;
66        }
67        $trimmed = trim($raw);
68        if (!str_starts_with($trimmed, '#')) {
69            // External or empty reference — no implicit cross-document
70            // load at v1.
71            return null;
72        }
73        $id = substr($trimmed, 1);
74        return $id === '' ? null : $id;
75    }
76
77    /**
78     * Look up the referenced element in `$doc`. Returns null when the
79     * reference is missing, external, or points to an id that doesn't
80     * exist.
81     */
82    public function resolve(SvgDocument $doc): ?Element
83    {
84        $id = $this->href();
85        return $id === null ? null : $doc->findById($id);
86    }
87
88    private function optionalLength(string $attr): ?float
89    {
90        $raw = $this->getAttribute($attr);
91        if ($raw === null) {
92            return null;
93        }
94        if (preg_match('/^\s*([+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?)/', $raw, $m) !== 1) {
95            return null;
96        }
97        $value = (float) $m[1];
98        return $value < 0.0 ? null : $value;
99    }
100}