Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
91.30% |
21 / 23 |
|
75.00% |
6 / 8 |
CRAP | |
0.00% |
0 / 1 |
| Use_ | |
91.30% |
21 / 23 |
|
75.00% |
6 / 8 |
15.15 | |
0.00% |
0 / 1 |
| __construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| x | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| y | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| width | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| height | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| href | |
88.89% |
8 / 9 |
|
0.00% |
0 / 1 |
4.02 | |||
| resolve | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| optionalLength | |
85.71% |
6 / 7 |
|
0.00% |
0 / 1 |
4.05 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace 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 | */ |
| 19 | final 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 | } |