Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.45% covered (success)
95.45%
21 / 22
88.89% covered (warning)
88.89%
8 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
Mask
95.45% covered (success)
95.45%
21 / 22
88.89% covered (warning)
88.89%
8 / 9
19
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
 maskUnits
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 maskContentUnits
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%
2 / 2
100.00% covered (success)
100.00%
1 / 1
3
 height
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
3
 parseUnitsAttribute
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
5
 parseOptionalLength
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
3.04
1<?php
2
3declare(strict_types=1);
4
5namespace Phpdftk\Svg;
6
7/**
8 * SVG `<mask>` per SVG 2 §14.5 — a luminance- or alpha-based stencil applied
9 * to elements that reference it via `mask`. Has its own bounding rectangle
10 * (`x`, `y`, `width`, `height`), and two independent unit attributes:
11 * `maskUnits` controls how the rectangle is interpreted, `maskContentUnits`
12 * controls how the children's coordinates are interpreted.
13 */
14final class Mask extends Element
15{
16    public function __construct()
17    {
18        parent::__construct('mask');
19    }
20
21    /**
22     * `maskUnits` — interpretation of the `x`/`y`/`width`/`height` rect.
23     * Default `objectBoundingBox` per SVG 2 §14.5.4.
24     *
25     * @return 'userSpaceOnUse'|'objectBoundingBox'
26     */
27    public function maskUnits(): string
28    {
29        return $this->parseUnitsAttribute('maskUnits', 'objectBoundingBox');
30    }
31
32    /**
33     * `maskContentUnits` — interpretation of the children's coordinates.
34     * Default `userSpaceOnUse` per SVG 2 §14.5.4.
35     *
36     * @return 'userSpaceOnUse'|'objectBoundingBox'
37     */
38    public function maskContentUnits(): string
39    {
40        return $this->parseUnitsAttribute('maskContentUnits', 'userSpaceOnUse');
41    }
42
43    /**
44     * `x` — null when absent so the painter applies the SVG 2 default
45     * (`-10%` of the masked element's bbox in `objectBoundingBox` mode,
46     * which we can't resolve here without the bbox).
47     */
48    public function x(): ?float
49    {
50        return $this->parseOptionalLength('x');
51    }
52
53    public function y(): ?float
54    {
55        return $this->parseOptionalLength('y');
56    }
57
58    /**
59     * `width` — null when absent so the painter applies the default
60     * `120%` per SVG 2 §14.5.4. Non-negative.
61     */
62    public function width(): ?float
63    {
64        $v = $this->parseOptionalLength('width');
65        return $v !== null && $v < 0.0 ? null : $v;
66    }
67
68    public function height(): ?float
69    {
70        $v = $this->parseOptionalLength('height');
71        return $v !== null && $v < 0.0 ? null : $v;
72    }
73
74    /**
75     * @return 'userSpaceOnUse'|'objectBoundingBox'
76     */
77    private function parseUnitsAttribute(string $attr, string $default): string
78    {
79        $raw = $this->getAttribute($attr);
80        if ($raw === null) {
81            /** @var 'userSpaceOnUse'|'objectBoundingBox' $default */
82            return $default;
83        }
84        $value = trim($raw);
85        return match ($value) {
86            'userSpaceOnUse', 'objectBoundingBox' => $value,
87            default => $default === 'userSpaceOnUse' ? 'userSpaceOnUse' : 'objectBoundingBox',
88        };
89    }
90
91    private function parseOptionalLength(string $attr): ?float
92    {
93        $raw = $this->getAttribute($attr);
94        if ($raw === null) {
95            return null;
96        }
97        if (preg_match('/^\s*([+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?)/', $raw, $m) !== 1) {
98            return null;
99        }
100        return (float) $m[1];
101    }
102}