Merge branch '3.1' into 5.2
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / system / api / leafo / scssphp / src / Node / Number.php
1 <?php
2 /**
3 * SCSSPHP
4 *
5 * @copyright 2012-2018 Leaf Corcoran
6 *
7 * @license http://opensource.org/licenses/MIT MIT
8 *
9 * @link http://leafo.github.io/scssphp
10 */
11
12 namespace Leafo\ScssPhp\Node;
13
14 use Leafo\ScssPhp\Compiler;
15 use Leafo\ScssPhp\Node;
16 use Leafo\ScssPhp\Type;
17
18 /**
19 * Dimension + optional units
20 *
21 * {@internal
22 * This is a work-in-progress.
23 *
24 * The \ArrayAccess interface is temporary until the migration is complete.
25 * }}
26 *
27 * @author Anthon Pang <anthon.pang@gmail.com>
28 */
29 class Number extends Node implements \ArrayAccess
30 {
31 /**
32 * @var integer
33 */
34 static public $precision = 10;
35
36 /**
37 * @see http://www.w3.org/TR/2012/WD-css3-values-20120308/
38 *
39 * @var array
40 */
41 static protected $unitTable = [
42 'in' => [
43 'in' => 1,
44 'pc' => 6,
45 'pt' => 72,
46 'px' => 96,
47 'cm' => 2.54,
48 'mm' => 25.4,
49 'q' => 101.6,
50 ],
51 'turn' => [
52 'deg' => 360,
53 'grad' => 400,
54 'rad' => 6.28318530717958647692528676, // 2 * M_PI
55 'turn' => 1,
56 ],
57 's' => [
58 's' => 1,
59 'ms' => 1000,
60 ],
61 'Hz' => [
62 'Hz' => 1,
63 'kHz' => 0.001,
64 ],
65 'dpi' => [
66 'dpi' => 1,
67 'dpcm' => 2.54,
68 'dppx' => 96,
69 ],
70 ];
71
72 /**
73 * @var integer|float
74 */
75 public $dimension;
76
77 /**
78 * @var array
79 */
80 public $units;
81
82 /**
83 * Initialize number
84 *
85 * @param mixed $dimension
86 * @param mixed $initialUnit
87 */
88 public function __construct($dimension, $initialUnit)
89 {
90 $this->type = Type::T_NUMBER;
91 $this->dimension = $dimension;
92 $this->units = is_array($initialUnit)
93 ? $initialUnit
94 : ($initialUnit ? [$initialUnit => 1]
95 : []);
96 }
97
98 /**
99 * Coerce number to target units
100 *
101 * @param array $units
102 *
103 * @return \Leafo\ScssPhp\Node\Number
104 */
105 public function coerce($units)
106 {
107 if ($this->unitless()) {
108 return new Number($this->dimension, $units);
109 }
110
111 $dimension = $this->dimension;
112
113 foreach (static::$unitTable['in'] as $unit => $conv) {
114 $from = isset($this->units[$unit]) ? $this->units[$unit] : 0;
115 $to = isset($units[$unit]) ? $units[$unit] : 0;
116 $factor = pow($conv, $from - $to);
117 $dimension /= $factor;
118 }
119
120 return new Number($dimension, $units);
121 }
122
123 /**
124 * Normalize number
125 *
126 * @return \Leafo\ScssPhp\Node\Number
127 */
128 public function normalize()
129 {
130 $dimension = $this->dimension;
131 $units = [];
132
133 $this->normalizeUnits($dimension, $units, 'in');
134
135 return new Number($dimension, $units);
136 }
137
138 /**
139 * {@inheritdoc}
140 */
141 public function offsetExists($offset)
142 {
143 if ($offset === -3) {
144 return $this->sourceColumn !== null;
145 }
146
147 if ($offset === -2) {
148 return $this->sourceLine !== null;
149 }
150
151 if ($offset === -1
152 || $offset === 0
153 || $offset === 1
154 || $offset === 2
155 ) {
156 return true;
157 }
158
159 return false;
160 }
161
162 /**
163 * {@inheritdoc}
164 */
165 public function offsetGet($offset)
166 {
167 switch ($offset) {
168 case -3:
169 return $this->sourceColumn;
170
171 case -2:
172 return $this->sourceLine;
173
174 case -1:
175 return $this->sourceIndex;
176
177 case 0:
178 return $this->type;
179
180 case 1:
181 return $this->dimension;
182
183 case 2:
184 return $this->units;
185 }
186 }
187
188 /**
189 * {@inheritdoc}
190 */
191 public function offsetSet($offset, $value)
192 {
193 if ($offset === 1) {
194 $this->dimension = $value;
195 } elseif ($offset === 2) {
196 $this->units = $value;
197 } elseif ($offset == -1) {
198 $this->sourceIndex = $value;
199 } elseif ($offset == -2) {
200 $this->sourceLine = $value;
201 } elseif ($offset == -3) {
202 $this->sourceColumn = $value;
203 }
204 }
205
206 /**
207 * {@inheritdoc}
208 */
209 public function offsetUnset($offset)
210 {
211 if ($offset === 1) {
212 $this->dimension = null;
213 } elseif ($offset === 2) {
214 $this->units = null;
215 } elseif ($offset === -1) {
216 $this->sourceIndex = null;
217 } elseif ($offset === -2) {
218 $this->sourceLine = null;
219 } elseif ($offset === -3) {
220 $this->sourceColumn = null;
221 }
222 }
223
224 /**
225 * Returns true if the number is unitless
226 *
227 * @return boolean
228 */
229 public function unitless()
230 {
231 return ! array_sum($this->units);
232 }
233
234 /**
235 * Returns unit(s) as the product of numerator units divided by the product of denominator units
236 *
237 * @return string
238 */
239 public function unitStr()
240 {
241 $numerators = [];
242 $denominators = [];
243
244 foreach ($this->units as $unit => $unitSize) {
245 if ($unitSize > 0) {
246 $numerators = array_pad($numerators, count($numerators) + $unitSize, $unit);
247 continue;
248 }
249
250 if ($unitSize < 0) {
251 $denominators = array_pad($denominators, count($denominators) + $unitSize, $unit);
252 continue;
253 }
254 }
255
256 return implode('*', $numerators) . (count($denominators) ? '/' . implode('*', $denominators) : '');
257 }
258
259 /**
260 * Output number
261 *
262 * @param \Leafo\ScssPhp\Compiler $compiler
263 *
264 * @return string
265 */
266 public function output(Compiler $compiler = null)
267 {
268 $dimension = round($this->dimension, static::$precision);
269
270 $units = array_filter($this->units, function ($unitSize) {
271 return $unitSize;
272 });
273
274 if (count($units) > 1 && array_sum($units) === 0) {
275 $dimension = $this->dimension;
276 $units = [];
277
278 $this->normalizeUnits($dimension, $units, 'in');
279
280 $dimension = round($dimension, static::$precision);
281 $units = array_filter($units, function ($unitSize) {
282 return $unitSize;
283 });
284 }
285
286 $unitSize = array_sum($units);
287
288 if ($compiler && ($unitSize > 1 || $unitSize < 0 || count($units) > 1)) {
289 $compiler->throwError((string) $dimension . $this->unitStr() . " isn't a valid CSS value.");
290 }
291
292 reset($units);
293 $unit = key($units);
294 $dimension = number_format($dimension, static::$precision, '.', '');
295
296 return (static::$precision ? rtrim(rtrim($dimension, '0'), '.') : $dimension) . $unit;
297 }
298
299 /**
300 * {@inheritdoc}
301 */
302 public function __toString()
303 {
304 return $this->output();
305 }
306
307 /**
308 * Normalize units
309 *
310 * @param integer|float $dimension
311 * @param array $units
312 * @param string $baseUnit
313 */
314 private function normalizeUnits(&$dimension, &$units, $baseUnit = 'in')
315 {
316 $dimension = $this->dimension;
317 $units = [];
318
319 foreach ($this->units as $unit => $exp) {
320 if (isset(static::$unitTable[$baseUnit][$unit])) {
321 $factor = pow(static::$unitTable[$baseUnit][$unit], $exp);
322
323 $unit = $baseUnit;
324 $dimension /= $factor;
325 }
326
327 $units[$unit] = $exp + (isset($units[$unit]) ? $units[$unit] : 0);
328 }
329 }
330 }