Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
90.91% |
20 / 22 |
|
75.00% |
3 / 4 |
CRAP | |
0.00% |
0 / 1 |
| Path | |
90.91% |
20 / 22 |
|
75.00% |
3 / 4 |
11.09 | |
0.00% |
0 / 1 |
| __construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| dRaw | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| d | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
| parseDProperty | |
84.62% |
11 / 13 |
|
0.00% |
0 / 1 |
6.13 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace Phpdftk\Svg; |
| 6 | |
| 7 | use Phpdftk\Svg\Path\PathData; |
| 8 | |
| 9 | /** |
| 10 | * SVG `<path>` element per SVG 2 §9. The geometry lives in the `d` attribute; |
| 11 | * call `d()` to get a typed `PathData` AST or `dRaw()` for the original |
| 12 | * string (round-tripping, diagnostics). |
| 13 | */ |
| 14 | final class Path extends Element |
| 15 | { |
| 16 | public function __construct() |
| 17 | { |
| 18 | parent::__construct('path'); |
| 19 | } |
| 20 | |
| 21 | /** |
| 22 | * The original `d` attribute value, verbatim. Useful for sanitiser-style |
| 23 | * workflows that want to compare or re-emit without parsing. |
| 24 | */ |
| 25 | public function dRaw(): ?string |
| 26 | { |
| 27 | return $this->getAttribute('d'); |
| 28 | } |
| 29 | |
| 30 | /** |
| 31 | * Parsed path geometry. Always returns a `PathData` — an absent or |
| 32 | * malformed source resolves to an empty command list (the spec |
| 33 | * accumulates commands up to the first error). |
| 34 | * |
| 35 | * SVG 2 §9.3 makes `d` a CSS property as well as a presentation |
| 36 | * attribute, with the grammar `none | path(<string>)`, and the CSS |
| 37 | * side WINS: §6.7 puts a presentation attribute at specificity 0, |
| 38 | * so any rule naming the element out-ranks it. That is why this |
| 39 | * consults the `style` declaration first and the attribute only as |
| 40 | * the fallback — the opposite of every other accessor here. |
| 41 | * |
| 42 | * An unparseable declaration is ignored rather than fatal, which |
| 43 | * per the cascade means the attribute is still in force. |
| 44 | */ |
| 45 | public function d(): PathData |
| 46 | { |
| 47 | $property = self::parseDProperty($this->styleProperty('d')); |
| 48 | if ($property !== null) { |
| 49 | return PathData::parse($property); |
| 50 | } |
| 51 | $raw = $this->getAttribute('d'); |
| 52 | if ($raw === null) { |
| 53 | return new PathData([]); |
| 54 | } |
| 55 | return PathData::parse($raw); |
| 56 | } |
| 57 | |
| 58 | /** |
| 59 | * The path string named by a CSS `d` declaration, or null when the |
| 60 | * declaration is absent or unusable. |
| 61 | * |
| 62 | * `none` returns the empty string — an explicit "no geometry", |
| 63 | * which is NOT the same as "no declaration" and must not fall |
| 64 | * through to the attribute. |
| 65 | */ |
| 66 | private static function parseDProperty(?string $raw): ?string |
| 67 | { |
| 68 | if ($raw === null) { |
| 69 | return null; |
| 70 | } |
| 71 | $value = trim($raw); |
| 72 | if ($value === '') { |
| 73 | return null; |
| 74 | } |
| 75 | if (strcasecmp($value, 'none') === 0) { |
| 76 | return ''; |
| 77 | } |
| 78 | if (preg_match('/^path\(\s*(.*?)\s*\)$/is', $value, $m) !== 1) { |
| 79 | return null; |
| 80 | } |
| 81 | $argument = $m[1]; |
| 82 | // `path()` takes an optional fill-rule keyword before the |
| 83 | // string; the geometry is the quoted part either way. |
| 84 | if (preg_match('/[\x22\x27](.*)[\x22\x27]\s*$/s', $argument, $q) !== 1) { |
| 85 | return null; |
| 86 | } |
| 87 | return $q[1]; |
| 88 | } |
| 89 | } |