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
|
|
|
|