Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.83% covered (success)
95.83%
23 / 24
87.50% covered (warning)
87.50%
7 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
RadialGradient
95.83% covered (success)
95.83%
23 / 24
87.50% covered (warning)
87.50%
7 / 8
17
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
 cx
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 cy
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 r
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
3
 fx
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 fy
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 fr
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
3
 parseOptionalLength
93.33% covered (success)
93.33%
14 / 15
0.00% covered (danger)
0.00%
0 / 1
6.01
1<?php
2
3declare(strict_types=1);
4
5namespace Phpdftk\Svg\Gradient;
6
7/**
8 * SVG `<radialGradient>` per SVG 2 §13.7. Defines a gradient whose colour
9 * stops vary radially from a focal point `(fx, fy)` to a circle of radius
10 * `r` centred at `(cx, cy)`. SVG 2 added the optional `fr` focal radius
11 * for proper inner-circle support.
12 *
13 * Accessors return null when the attribute is absent so the painter
14 * applies the SVG 2 §13.7.5 defaults (`cx`/`cy`/`r` = 50%, focal point
15 * coincident with centre).
16 */
17final class RadialGradient extends Gradient
18{
19    public function __construct()
20    {
21        parent::__construct('radialGradient');
22    }
23
24    public function cx(): ?float
25    {
26        return $this->parseOptionalLength('cx');
27    }
28
29    public function cy(): ?float
30    {
31        return $this->parseOptionalLength('cy');
32    }
33
34    /** Outer-circle radius. Non-negative; null otherwise. */
35    public function r(): ?float
36    {
37        $v = $this->parseOptionalLength('r');
38        return $v !== null && $v < 0.0 ? null : $v;
39    }
40
41    public function fx(): ?float
42    {
43        return $this->parseOptionalLength('fx');
44    }
45
46    public function fy(): ?float
47    {
48        return $this->parseOptionalLength('fy');
49    }
50
51    /** Focal-circle radius (SVG 2 addition). Non-negative; null otherwise. */
52    public function fr(): ?float
53    {
54        $v = $this->parseOptionalLength('fr');
55        return $v !== null && $v < 0.0 ? null : $v;
56    }
57
58    private function parseOptionalLength(string $attr): ?float
59    {
60        $raw = $this->getAttribute($attr);
61        if ($raw === null) {
62            return null;
63        }
64        if (preg_match(
65            '/^\s*([+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?)\s*(%|[a-zA-Z]*)/',
66            $raw,
67            $m,
68        ) !== 1) {
69            return null;
70        }
71        $value = (float) $m[1];
72        if ($m[2] !== '' && $m[2] !== '%') {
73            // A gradient coordinate is a `<length>`, so an ABSOLUTE
74            // unit suffix is meaningful: `x2="1in"` is 96 CSS px, which
75            // in objectBoundingBox mode is 96 bounding-box widths, not
76            // one. Throwing the suffix away read `1in` as `1`.
77            // Font-relative units need context this accessor doesn't
78            // have, so `absoluteUnitScale()` leaves them at 1.
79            return $value * \Phpdftk\Svg\Element::absoluteUnitScale($m[2]);
80        }
81        // SVG 2 §13.7.5 — a percentage in objectBoundingBox mode (the default
82        // gradientUnits) is a fraction of the bounding box, so `cx="50%"` ==
83        // `0.5`. Normalise to the fraction the GradientPainter bbox mapping
84        // expects (a `%` radius parsed as e.g. `50` blew the gradient far past
85        // the shape).
86        if ($m[2] === '%') {
87            $value /= 100.0;
88        }
89        return $value;
90    }
91}