Svg::getMeasurement()   A
last analyzed

Complexity

Conditions 6
Paths 3

Size

Total Lines 13
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 6
eloc 10
c 4
b 0
f 0
nc 3
nop 0
dl 0
loc 13
rs 9.2222
1
<?php
2
3
namespace thoulah\fontawesome\helpers;
4
5
use DOMXPath;
6
use thoulah\fontawesome\config\Defaults;
7
use thoulah\fontawesome\config\Options;
8
use yii\helpers\Html;
9
10
/**
11
 * SVG helper.
12
 */
13
class Svg
14
{
15
    /** @var array values for converting various units to pixels */
16
    private const PIXEL_MAP = [
17
        'px' => 1,
18
        'em' => 16,
19
        'ex' => 16 / 2,
20
        'pt' => 16 / 12,
21
        'pc' => 16,
22
        'in' => 16 * 6,
23
        'cm' => 16 / (2.54 / 6),
24
        'mm' => 16 / (25.4 / 6),
25
    ];
26
27
    /** @var array class property */
28
    private $class;
29
30
    /** @var Defaults default options */
31
    private $defaults;
32
33
    /** @var bool `true` if name resolves to a valid XML file */
34
    private $isCustomFile = false;
35
36
    /** @var Options individual icon options */
37
    private $options;
38
39
    /** @var DOMDocument SVG file */
40
    private $svg;
41
42
    /** @var \DOMElement SVG */
43
    private $svgElement;
44
45
    /** @var array additional properties for the icon not set with Options */
46
    private $svgProperties;
47
48
    /**
49
     * Construct.
50
     *
51
     * @param Defaults $defaults default options
52
     * @param Options  $options  individual icon options
53
     */
54
    public function __construct(Defaults $defaults, Options $options)
55
    {
56
        $this->svg = new DOMDocument();
57
        $this->defaults = $defaults;
58
        $this->options = $options;
59
60
        $class = $this->options->removeValue('class');
61
        $this->class = (is_array($class)) ? $class : ['class' => $class];
62
    }
63
64
    /**
65
     * Magic function, returns the SVG string.
66
     *
67
     * @return string The SVG result
68
     */
69
    public function __toString(): string
70
    {
71
        return $this->svg->saveXML($this->svgElement);
72
    }
73
74
    /**
75
     * Prepares either the size class (default) or the width/height if either of these is given manually.
76
     */
77
    public function getMeasurement(): void
78
    {
79
        [$svgWidth, $svgHeight] = $this->getSize();
80
81
        $width = $this->options->removeValue('width');
82
        $height = $this->options->removeValue('height');
83
        $addClass = $this->options->removeValue('addClass');
84
        if ($width || $height) {
85
            $this->svgProperties['width'] = $width ?? round($height * $svgWidth / $svgHeight);
86
            $this->svgProperties['height'] = $height ?? round($width * $svgHeight / $svgWidth);
87
        } elseif (!$this->isCustomFile || ($this->isCustomFile && $addClass)) {
88
            Html::addCssClass($this->class, $this->defaults->prefix);
89
            Html::addCssClass($this->class, $this->defaults->prefix . '-w-' . ceil($svgWidth / $svgHeight * 16));
90
        }
91
    }
92
93
    /**
94
     * Prepares the values to be set on the SVG.
95
     */
96
    public function getProperties(): void
97
    {
98
        $this->svgProperties['aria-hidden'] = 'true';
99
        $this->svgProperties['role'] = 'img';
100
101
        if ($this->options->removeValue('fixedWidth')) {
102
            Html::addCssClass($this->class, $this->defaults->prefix . '-fw');
103
        }
104
105
        if ($this->class['class']) {
106
            $this->svgProperties['class'] = $this->class['class'];
107
        }
108
109
        if (!empty($this->options->css)) {
110
            $css = $this->options->removeValue('css', []);
111
            $this->svgProperties['style'] = Html::cssStyleFromArray($css);
112
        }
113
114
        $this->svgProperties['fill'] = $this->options->removeValue('fill', $this->defaults->fill);
115
    }
116
117
    /**
118
     * Load Font Awesome SVG file. Falls back to default if not found.
119
     *
120
     * @see Defaults::$fallbackIcon
121
     */
122
    public function load(): void
123
    {
124
        $fontAwesomeFolder = $this->options->removeValue('fontAwesomeFolder', $this->defaults->fontAwesomeFolder);
125
        $style = $this->options->removeValue('style', $this->defaults->style);
126
        $name = $this->options->removeValue('name');
127
        $fileName = implode(DIRECTORY_SEPARATOR, [$fontAwesomeFolder, $style, "{$name}.svg"]);
128
129
        if ($this->svg->load($name)) {
130
            $this->isCustomFile = true;
131
        } elseif (!$this->svg->load($fileName)) {
132
            $this->svg->load($this->defaults->fallbackIcon);
133
        }
134
135
        $xpath = new DOMXPath($this->svg);
136
        while ($node = $xpath->query('//comment()')->item(0)) {
137
            if ($node->parentNode) {
138
                $node->parentNode->removeChild($node);
139
            }
140
        }
141
        $this->svgElement = $this->svg->getElementsByTagName('svg')->item(0);
142
    }
143
144
    /**
145
     * Adds the properties to the SVG.
146
     */
147
    public function setAttributes(): void
148
    {
149
        if ($this->options->title) {
150
            $title = $this->options->removeValue('title');
151
            $titleElement = $this->svg->createElement('title', $title);
152
            $this->svgElement->insertBefore($titleElement, $this->svgElement->firstChild);
153
        }
154
155
        foreach ([$this->options, $this->svgProperties] as $data) {
156
            foreach ($data as $key => $value) {
157
                if (!empty($value)) {
158
                    $this->svgElement->setAttribute($key, $value);
159
                }
160
            }
161
        }
162
    }
163
164
    /**
165
     * Converts various sizes to pixels.
166
     *
167
     * @param string $size
168
     *
169
     * @return int
170
     */
171
    private function getPixelValue(string $size): int
172
    {
173
        $size = trim($size);
174
        $value = substr($size, 0, -2);
175
        $unit = substr($size, -2);
176
177
        if (is_numeric($value) && isset(self::PIXEL_MAP[$unit])) {
178
            $size = $value * self::PIXEL_MAP[$unit];
179
        }
180
181
        return (int) round((float) $size);
182
    }
183
184
    /**
185
     * Determines size of the SVG element.
186
     *
187
     * @return array Width & height
188
     */
189
    private function getSize(): array
190
    {
191
        $svgWidth = $this->getPixelValue($this->svgElement->getAttribute('width'));
192
        $svgHeight = $this->getPixelValue($this->svgElement->getAttribute('height'));
193
194
        if ($this->svgElement->hasAttribute('viewBox')) {
195
            [$xStart, $yStart, $xEnd, $yEnd] = explode(' ', $this->svgElement->getAttribute('viewBox'));
196
            $viewBoxWidth = isset($xStart, $xEnd) ? $xEnd - $xStart : 0;
197
            $viewBoxHeight = isset($yStart, $yEnd) ? $yEnd - $yStart : 0;
198
199
            if ($viewBoxWidth > 0 && $viewBoxHeight > 0) {
200
                $svgWidth = $viewBoxWidth;
201
                $svgHeight = $viewBoxHeight;
202
            }
203
        }
204
205
        return [$svgWidth ?? 1, $svgHeight ?? 1];
206
    }
207
}
208