Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
93.02% covered (success)
93.02%
40 / 43
81.82% covered (warning)
81.82%
9 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
Pattern
93.02% covered (success)
93.02%
40 / 43
81.82% covered (warning)
81.82%
9 / 11
23.18
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
 tileLength
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
4
 patternUnits
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 patternContentUnits
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 viewBox
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
4.01
 patternTransform
71.43% covered (warning)
71.43%
5 / 7
0.00% covered (danger)
0.00%
0 / 1
5.58
 href
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5namespace Phpdftk\Svg;
6
7use Phpdftk\Svg\Value\Transform;
8
9/**
10 * SVG 2 §13.3 — `<pattern>` element. Defines a tile that paints
11 * into the fill or stroke of any shape that references it via
12 * `fill="url(#patternId)"`. Pattern attributes mirror `<marker>`
13 * and `<use>`:
14 *
15 *   - x, y, width, height — the tile rectangle in user space
16 *     (or pattern units, depending on patternUnits).
17 *   - patternUnits — `objectBoundingBox` (default, %-relative
18 *     to the referencing shape's bbox) or `userSpaceOnUse`.
19 *   - patternContentUnits — same flag for child positioning,
20 *     defaults to `userSpaceOnUse`.
21 *   - viewBox + preserveAspectRatio for content scaling.
22 *   - patternTransform — additional transform on the tile.
23 *
24 * For the static print renderer the typed class lifts these
25 * attributes off the generic Element surface; PDF Tiling Pattern
26 * (Pattern Type 1) emission is the next deliverable.
27 */
28final class Pattern extends Element
29{
30    public function __construct()
31    {
32        parent::__construct('pattern');
33    }
34
35    public function x(): float
36    {
37        return $this->tileLength('x');
38    }
39
40    public function y(): float
41    {
42        return $this->tileLength('y');
43    }
44
45    public function width(): float
46    {
47        return $this->tileLength('width');
48    }
49
50    public function height(): float
51    {
52        return $this->tileLength('height');
53    }
54
55    /**
56     * One of the tile-rectangle lengths, in the units
57     * {@see patternUnits} implies.
58     *
59     * SVG 2 §13.3 — in the default `objectBoundingBox` mode a
60     * percentage is a FRACTION of the bounding box, so `100%` is one
61     * bounding box; a straight `(float)` cast read it as a hundred of
62     * them and blew the tile up until a single copy covered the whole
63     * shape. In `userSpaceOnUse` mode an absolute unit suffix converts
64     * to CSS px through the shared table.
65     *
66     * An absent or unparseable value is 0, which the painter already
67     * treats as "no tile, don't paint".
68     */
69    private function tileLength(string $attribute): float
70    {
71        $raw = $this->getAttribute($attribute);
72        if ($raw === null) {
73            return 0.0;
74        }
75        if (preg_match(
76            '/^\s*([+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?)\s*(%|[a-zA-Z]*)/',
77            $raw,
78            $m,
79        ) !== 1) {
80            return 0.0;
81        }
82        $value = (float) $m[1];
83        if ($m[2] === '%') {
84            return $value / 100.0;
85        }
86        return $value * Element::absoluteUnitScale($m[2]);
87    }
88
89    public function patternUnits(): string
90    {
91        $u = strtolower($this->getAttribute('patternUnits') ?? 'objectboundingbox');
92        return $u === 'userspaceonuse' ? 'userSpaceOnUse' : 'objectBoundingBox';
93    }
94
95    public function patternContentUnits(): string
96    {
97        $u = strtolower($this->getAttribute('patternContentUnits') ?? 'userspaceonuse');
98        return $u === 'objectboundingbox' ? 'objectBoundingBox' : 'userSpaceOnUse';
99    }
100
101    /**
102     * @return array{0: float, 1: float, 2: float, 3: float}|null
103     */
104    public function viewBox(): ?array
105    {
106        $vb = $this->getAttribute('viewBox');
107        if ($vb === null) {
108            return null;
109        }
110        $parts = preg_split('/[\s,]+/', trim($vb)) ?: [];
111        if (count($parts) !== 4) {
112            return null;
113        }
114        return [
115            (float) $parts[0],
116            (float) $parts[1],
117            (float) $parts[2],
118            (float) $parts[3],
119        ];
120    }
121
122    /**
123     * `href` (or legacy `xlink:href`) — when present, the
124     * referenced pattern's attributes inherit into this one.
125     * See SVG 2 §13.3 chain-resolution rules.
126     */
127    /**
128     * SVG 2 §13.3 — `patternTransform`, an extra transform applied to
129     * the pattern tile's coordinate system on top of `patternUnits`.
130     *
131     * Read through the same attribute-or-CSS path as `transform`, so a
132     * `<style>` rule reaches it too. Null when absent, empty, `none`,
133     * or malformed.
134     */
135    public function patternTransform(): ?Transform
136    {
137        $raw = $this->presentationOrStyle('patternTransform')
138            ?? $this->getAttribute('patternTransform');
139        if ($raw === null || trim($raw) === '' || strtolower(trim($raw)) === 'none') {
140            return null;
141        }
142        try {
143            return Transform::parse($raw);
144        } catch (\InvalidArgumentException) {
145            return null;
146        }
147    }
148
149    public function href(): ?string
150    {
151        return $this->getAttribute('href')
152            ?? $this->getAttribute('xlink:href');
153    }
154}