Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
90.91% covered (success)
90.91%
20 / 22
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
Path
90.91% covered (success)
90.91%
20 / 22
75.00% covered (warning)
75.00%
3 / 4
11.09
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
 dRaw
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 d
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 parseDProperty
84.62% covered (warning)
84.62%
11 / 13
0.00% covered (danger)
0.00%
0 / 1
6.13
1<?php
2
3declare(strict_types=1);
4
5namespace Phpdftk\Svg;
6
7use 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 */
14final 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}