Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
72.92% covered (warning)
72.92%
35 / 48
50.00% covered (danger)
50.00%
4 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
Marker
72.92% covered (warning)
72.92%
35 / 48
50.00% covered (danger)
50.00%
4 / 8
69.22
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
 viewBox
83.33% covered (warning)
83.33%
10 / 12
0.00% covered (danger)
0.00%
0 / 1
4.07
 refX
66.67% covered (warning)
66.67%
6 / 9
0.00% covered (danger)
0.00%
0 / 1
13.70
 refY
55.56% covered (warning)
55.56%
5 / 9
0.00% covered (danger)
0.00%
0 / 1
18.78
 markerWidth
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 markerHeight
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 orient
69.23% covered (warning)
69.23%
9 / 13
0.00% covered (danger)
0.00%
0 / 1
12.91
 markerUnits
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace Phpdftk\Svg;
6
7/**
8 * SVG 2 §11.6 — `<marker>` element. Defines a reusable graphic
9 * (typically an arrowhead) that gets placed at vertices of
10 * `<line>`, `<polyline>`, `<polygon>`, and `<path>` shapes when
11 * the shape sets `marker-start` / `marker-mid` / `marker-end`.
12 *
13 *   <defs>
14 *     <marker id="arrow" viewBox="0 0 10 10"
15 *             refX="5" refY="5"
16 *             markerWidth="6" markerHeight="6"
17 *             orient="auto">
18 *       <path d="M 0 0 L 10 5 L 0 10 z" fill="black"/>
19 *     </marker>
20 *   </defs>
21 *
22 * The marker itself never paints at the document level — it's
23 * only emitted when a shape's `marker-*` property references
24 * it. The Translator's painter consumes the typed accessors
25 * for placement and orientation at each anchor point.
26 */
27final class Marker extends Element
28{
29    public function __construct()
30    {
31        parent::__construct('marker');
32    }
33
34    /**
35     * @return array{0: float, 1: float, 2: float, 3: float}|null
36     */
37    public function viewBox(): ?array
38    {
39        $vb = $this->getAttribute('viewBox');
40        if ($vb === null) {
41            return null;
42        }
43        $parts = preg_split('/[\s,]+/', trim($vb)) ?: [];
44        if (count($parts) !== 4) {
45            return null;
46        }
47        return [
48            (float) $parts[0],
49            (float) $parts[1],
50            (float) $parts[2],
51            (float) $parts[3],
52        ];
53    }
54
55    public function refX(): float
56    {
57        // Per SVG 2 §11.6.3 the `refX` keyword forms `left | center |
58        // right` map to viewport-relative positions. We honour them
59        // numerically as 0 / 0.5*width / width when a viewBox exists.
60        $raw = $this->getAttribute('refX');
61        if ($raw === null || $raw === '') {
62            return 0.0;
63        }
64        $vb = $this->viewBox();
65        return match (strtolower($raw)) {
66            'left' => $vb !== null ? $vb[0] : 0.0,
67            'center' => $vb !== null ? $vb[0] + $vb[2] / 2.0 : 0.0,
68            'right' => $vb !== null ? $vb[0] + $vb[2] : 0.0,
69            default => (float) $raw,
70        };
71    }
72
73    public function refY(): float
74    {
75        $raw = $this->getAttribute('refY');
76        if ($raw === null || $raw === '') {
77            return 0.0;
78        }
79        $vb = $this->viewBox();
80        return match (strtolower($raw)) {
81            'top' => $vb !== null ? $vb[1] : 0.0,
82            'center' => $vb !== null ? $vb[1] + $vb[3] / 2.0 : 0.0,
83            'bottom' => $vb !== null ? $vb[1] + $vb[3] : 0.0,
84            default => (float) $raw,
85        };
86    }
87
88    public function markerWidth(): float
89    {
90        return (float) ($this->getAttribute('markerWidth') ?? 3.0);
91    }
92
93    public function markerHeight(): float
94    {
95        return (float) ($this->getAttribute('markerHeight') ?? 3.0);
96    }
97
98    /**
99     * `auto | auto-start-reverse | <angle>`. Returns:
100     *   - The string `'auto'` or `'auto-start-reverse'` literally.
101     *   - A float angle in degrees for the numeric form.
102     *   - 0.0 when the attribute is absent.
103     */
104    public function orient(): string|float
105    {
106        $raw = strtolower($this->getAttribute('orient') ?? '');
107        if ($raw === '' || $raw === '0') {
108            return 0.0;
109        }
110        if ($raw === 'auto' || $raw === 'auto-start-reverse') {
111            return $raw;
112        }
113        // Strip optional `deg` / `rad` / `turn` / `grad` unit.
114        if (preg_match('/^([\-+]?[0-9.]+)\s*(deg|rad|turn|grad)?$/', $raw, $m) === 1) {
115            $value = (float) $m[1];
116            return match ($m[2] ?? 'deg') {
117                'rad' => $value * 180.0 / M_PI,
118                'turn' => $value * 360.0,
119                'grad' => $value * 0.9,
120                default => $value,
121            };
122        }
123        return 0.0;
124    }
125
126    /**
127     * `strokeWidth` (default) scales the marker by the shape's
128     * current stroke width; `userSpaceOnUse` uses the user
129     * coordinate system directly.
130     */
131    public function markerUnits(): string
132    {
133        $u = strtolower($this->getAttribute('markerUnits') ?? 'strokewidth');
134        return $u === 'userspaceonuse' ? 'userSpaceOnUse' : 'strokeWidth';
135    }
136}