/src/libsass/src/units.cpp
Line | Count | Source |
1 | | #include "sass.hpp" |
2 | | #include <map> |
3 | | #include <stdexcept> |
4 | | #include <algorithm> |
5 | | #include "units.hpp" |
6 | | #include "error_handling.hpp" |
7 | | |
8 | | namespace Sass { |
9 | | |
10 | | /* the conversion matrix can be readed the following way */ |
11 | | /* if you go down, the factor is for the numerator (multiply) */ |
12 | | /* if you go right, the factor is for the denominator (divide) */ |
13 | | /* and yes, we actually use both, not sure why, but why not!? */ |
14 | | |
15 | | const double size_conversion_factors[6][6] = |
16 | | { |
17 | | /* in cm pc mm pt px */ |
18 | | /* in */ { 1, 2.54, 6, 25.4, 72, 96, }, |
19 | | /* cm */ { 1.0/2.54, 1, 6.0/2.54, 10, 72.0/2.54, 96.0/2.54 }, |
20 | | /* pc */ { 1.0/6.0, 2.54/6.0, 1, 25.4/6.0, 72.0/6.0, 96.0/6.0 }, |
21 | | /* mm */ { 1.0/25.4, 1.0/10.0, 6.0/25.4, 1, 72.0/25.4, 96.0/25.4 }, |
22 | | /* pt */ { 1.0/72.0, 2.54/72.0, 6.0/72.0, 25.4/72.0, 1, 96.0/72.0 }, |
23 | | /* px */ { 1.0/96.0, 2.54/96.0, 6.0/96.0, 25.4/96.0, 72.0/96.0, 1, } |
24 | | }; |
25 | | |
26 | | const double angle_conversion_factors[4][4] = |
27 | | { |
28 | | /* deg grad rad turn */ |
29 | | /* deg */ { 1, 40.0/36.0, PI/180.0, 1.0/360.0 }, |
30 | | /* grad */ { 36.0/40.0, 1, PI/200.0, 1.0/400.0 }, |
31 | | /* rad */ { 180.0/PI, 200.0/PI, 1, 0.5/PI }, |
32 | | /* turn */ { 360.0, 400.0, 2.0*PI, 1 } |
33 | | }; |
34 | | |
35 | | const double time_conversion_factors[2][2] = |
36 | | { |
37 | | /* s ms */ |
38 | | /* s */ { 1, 1000.0 }, |
39 | | /* ms */ { 1/1000.0, 1 } |
40 | | }; |
41 | | const double frequency_conversion_factors[2][2] = |
42 | | { |
43 | | /* Hz kHz */ |
44 | | /* Hz */ { 1, 1/1000.0 }, |
45 | | /* kHz */ { 1000.0, 1 } |
46 | | }; |
47 | | const double resolution_conversion_factors[3][3] = |
48 | | { |
49 | | /* dpi dpcm dppx */ |
50 | | /* dpi */ { 1, 1/2.54, 1/96.0 }, |
51 | | /* dpcm */ { 2.54, 1, 2.54/96 }, |
52 | | /* dppx */ { 96, 96/2.54, 1 } |
53 | | }; |
54 | | |
55 | | UnitClass get_unit_type(UnitType unit) |
56 | 0 | { |
57 | 0 | switch (unit & 0xFF00) |
58 | 0 | { |
59 | 0 | case UnitClass::LENGTH: return UnitClass::LENGTH; |
60 | 0 | case UnitClass::ANGLE: return UnitClass::ANGLE; |
61 | 0 | case UnitClass::TIME: return UnitClass::TIME; |
62 | 0 | case UnitClass::FREQUENCY: return UnitClass::FREQUENCY; |
63 | 0 | case UnitClass::RESOLUTION: return UnitClass::RESOLUTION; |
64 | 0 | default: return UnitClass::INCOMMENSURABLE; |
65 | 0 | } |
66 | 0 | }; |
67 | | |
68 | | sass::string get_unit_class(UnitType unit) |
69 | 0 | { |
70 | 0 | switch (unit & 0xFF00) |
71 | 0 | { |
72 | 0 | case UnitClass::LENGTH: return "LENGTH"; |
73 | 0 | case UnitClass::ANGLE: return "ANGLE"; |
74 | 0 | case UnitClass::TIME: return "TIME"; |
75 | 0 | case UnitClass::FREQUENCY: return "FREQUENCY"; |
76 | 0 | case UnitClass::RESOLUTION: return "RESOLUTION"; |
77 | 0 | default: return "INCOMMENSURABLE"; |
78 | 0 | } |
79 | 0 | }; |
80 | | |
81 | | UnitType get_main_unit(const UnitClass unit) |
82 | 0 | { |
83 | 0 | switch (unit) |
84 | 0 | { |
85 | 0 | case UnitClass::LENGTH: return UnitType::PX; |
86 | 0 | case UnitClass::ANGLE: return UnitType::DEG; |
87 | 0 | case UnitClass::TIME: return UnitType::SEC; |
88 | 0 | case UnitClass::FREQUENCY: return UnitType::HERTZ; |
89 | 0 | case UnitClass::RESOLUTION: return UnitType::DPI; |
90 | 0 | default: return UnitType::UNKNOWN; |
91 | 0 | } |
92 | 0 | }; |
93 | | |
94 | | UnitType string_to_unit(const sass::string& s) |
95 | 0 | { |
96 | | // size units |
97 | 0 | if (s == "px") return UnitType::PX; |
98 | 0 | else if (s == "pt") return UnitType::PT; |
99 | 0 | else if (s == "pc") return UnitType::PC; |
100 | 0 | else if (s == "mm") return UnitType::MM; |
101 | 0 | else if (s == "cm") return UnitType::CM; |
102 | 0 | else if (s == "in") return UnitType::IN; |
103 | | // angle units |
104 | 0 | else if (s == "deg") return UnitType::DEG; |
105 | 0 | else if (s == "grad") return UnitType::GRAD; |
106 | 0 | else if (s == "rad") return UnitType::RAD; |
107 | 0 | else if (s == "turn") return UnitType::TURN; |
108 | | // time units |
109 | 0 | else if (s == "s") return UnitType::SEC; |
110 | 0 | else if (s == "ms") return UnitType::MSEC; |
111 | | // frequency units |
112 | 0 | else if (s == "Hz") return UnitType::HERTZ; |
113 | 0 | else if (s == "kHz") return UnitType::KHERTZ; |
114 | | // resolutions units |
115 | 0 | else if (s == "dpi") return UnitType::DPI; |
116 | 0 | else if (s == "dpcm") return UnitType::DPCM; |
117 | 0 | else if (s == "dppx") return UnitType::DPPX; |
118 | | // for unknown units |
119 | 0 | else return UnitType::UNKNOWN; |
120 | 0 | } |
121 | | |
122 | | const char* unit_to_string(UnitType unit) |
123 | 0 | { |
124 | 0 | switch (unit) { |
125 | | // size units |
126 | 0 | case UnitType::PX: return "px"; |
127 | 0 | case UnitType::PT: return "pt"; |
128 | 0 | case UnitType::PC: return "pc"; |
129 | 0 | case UnitType::MM: return "mm"; |
130 | 0 | case UnitType::CM: return "cm"; |
131 | 0 | case UnitType::IN: return "in"; |
132 | | // angle units |
133 | 0 | case UnitType::DEG: return "deg"; |
134 | 0 | case UnitType::GRAD: return "grad"; |
135 | 0 | case UnitType::RAD: return "rad"; |
136 | 0 | case UnitType::TURN: return "turn"; |
137 | | // time units |
138 | 0 | case UnitType::SEC: return "s"; |
139 | 0 | case UnitType::MSEC: return "ms"; |
140 | | // frequency units |
141 | 0 | case UnitType::HERTZ: return "Hz"; |
142 | 0 | case UnitType::KHERTZ: return "kHz"; |
143 | | // resolutions units |
144 | 0 | case UnitType::DPI: return "dpi"; |
145 | 0 | case UnitType::DPCM: return "dpcm"; |
146 | 0 | case UnitType::DPPX: return "dppx"; |
147 | | // for unknown units |
148 | 0 | default: return ""; |
149 | 0 | } |
150 | 0 | } |
151 | | |
152 | | sass::string unit_to_class(const sass::string& s) |
153 | 0 | { |
154 | 0 | if (s == "px") return "LENGTH"; |
155 | 0 | else if (s == "pt") return "LENGTH"; |
156 | 0 | else if (s == "pc") return "LENGTH"; |
157 | 0 | else if (s == "mm") return "LENGTH"; |
158 | 0 | else if (s == "cm") return "LENGTH"; |
159 | 0 | else if (s == "in") return "LENGTH"; |
160 | | // angle units |
161 | 0 | else if (s == "deg") return "ANGLE"; |
162 | 0 | else if (s == "grad") return "ANGLE"; |
163 | 0 | else if (s == "rad") return "ANGLE"; |
164 | 0 | else if (s == "turn") return "ANGLE"; |
165 | | // time units |
166 | 0 | else if (s == "s") return "TIME"; |
167 | 0 | else if (s == "ms") return "TIME"; |
168 | | // frequency units |
169 | 0 | else if (s == "Hz") return "FREQUENCY"; |
170 | 0 | else if (s == "kHz") return "FREQUENCY"; |
171 | | // resolutions units |
172 | 0 | else if (s == "dpi") return "RESOLUTION"; |
173 | 0 | else if (s == "dpcm") return "RESOLUTION"; |
174 | 0 | else if (s == "dppx") return "RESOLUTION"; |
175 | | // for unknown units |
176 | 0 | return "CUSTOM:" + s; |
177 | 0 | } |
178 | | |
179 | | // throws incompatibleUnits exceptions |
180 | | double conversion_factor(const sass::string& s1, const sass::string& s2) |
181 | 0 | { |
182 | | // assert for same units |
183 | 0 | if (s1 == s2) return 1; |
184 | | // get unit enum from string |
185 | 0 | UnitType u1 = string_to_unit(s1); |
186 | 0 | UnitType u2 = string_to_unit(s2); |
187 | | // query unit group types |
188 | 0 | UnitClass t1 = get_unit_type(u1); |
189 | 0 | UnitClass t2 = get_unit_type(u2); |
190 | | // return the conversion factor |
191 | 0 | return conversion_factor(u1, u2, t1, t2); |
192 | 0 | } |
193 | | |
194 | | // throws incompatibleUnits exceptions |
195 | | double conversion_factor(UnitType u1, UnitType u2, UnitClass t1, UnitClass t2) |
196 | 0 | { |
197 | | // can't convert between groups |
198 | 0 | if (t1 != t2) return 0; |
199 | | // get absolute offset |
200 | | // used for array acces |
201 | 0 | size_t i1 = u1 - t1; |
202 | 0 | size_t i2 = u2 - t2; |
203 | | // process known units |
204 | 0 | switch (t1) { |
205 | 0 | case LENGTH: |
206 | 0 | return size_conversion_factors[i1][i2]; |
207 | 0 | case ANGLE: |
208 | 0 | return angle_conversion_factors[i1][i2]; |
209 | 0 | case TIME: |
210 | 0 | return time_conversion_factors[i1][i2]; |
211 | 0 | case FREQUENCY: |
212 | 0 | return frequency_conversion_factors[i1][i2]; |
213 | 0 | case RESOLUTION: |
214 | 0 | return resolution_conversion_factors[i1][i2]; |
215 | 0 | case INCOMMENSURABLE: |
216 | 0 | return 0; |
217 | 0 | } |
218 | | // fallback |
219 | 0 | return 0; |
220 | 0 | } |
221 | | |
222 | | double convert_units(const sass::string& lhs, const sass::string& rhs, int& lhsexp, int& rhsexp) |
223 | 0 | { |
224 | 0 | double f = 0; |
225 | | // do not convert same ones |
226 | 0 | if (lhs == rhs) return 0; |
227 | | // skip already canceled out unit |
228 | 0 | if (lhsexp == 0) return 0; |
229 | 0 | if (rhsexp == 0) return 0; |
230 | | // check if it can be converted |
231 | 0 | UnitType ulhs = string_to_unit(lhs); |
232 | 0 | UnitType urhs = string_to_unit(rhs); |
233 | | // skip units we cannot convert |
234 | 0 | if (ulhs == UNKNOWN) return 0; |
235 | 0 | if (urhs == UNKNOWN) return 0; |
236 | | // query unit group types |
237 | 0 | UnitClass clhs = get_unit_type(ulhs); |
238 | 0 | UnitClass crhs = get_unit_type(urhs); |
239 | | // skip units we cannot convert |
240 | 0 | if (clhs != crhs) return 0; |
241 | | // if right denominator is bigger than lhs, we want to keep it in rhs unit |
242 | 0 | if (rhsexp < 0 && lhsexp > 0 && - rhsexp > lhsexp) { |
243 | | // get the conversion factor for units |
244 | 0 | f = conversion_factor(urhs, ulhs, clhs, crhs); |
245 | | // left hand side has been consumned |
246 | 0 | f = std::pow(f, lhsexp); |
247 | 0 | rhsexp += lhsexp; |
248 | 0 | lhsexp = 0; |
249 | 0 | } |
250 | 0 | else { |
251 | | // get the conversion factor for units |
252 | 0 | f = conversion_factor(ulhs, urhs, clhs, crhs); |
253 | | // right hand side has been consumned |
254 | 0 | f = std::pow(f, rhsexp); |
255 | 0 | lhsexp += rhsexp; |
256 | 0 | rhsexp = 0; |
257 | 0 | } |
258 | 0 | return f; |
259 | 0 | } |
260 | | |
261 | | bool Units::operator< (const Units& rhs) const |
262 | 0 | { |
263 | 0 | return (numerators < rhs.numerators) && |
264 | 0 | (denominators < rhs.denominators); |
265 | 0 | } |
266 | | bool Units::operator== (const Units& rhs) const |
267 | 0 | { |
268 | 0 | return (numerators == rhs.numerators) && |
269 | 0 | (denominators == rhs.denominators); |
270 | 0 | } |
271 | | bool Units::operator!= (const Units& rhs) const |
272 | 0 | { |
273 | 0 | return ! (*this == rhs); |
274 | 0 | } |
275 | | |
276 | | double Units::normalize() |
277 | 0 | { |
278 | |
|
279 | 0 | size_t iL = numerators.size(); |
280 | 0 | size_t nL = denominators.size(); |
281 | | |
282 | | // the final conversion factor |
283 | 0 | double factor = 1; |
284 | |
|
285 | 0 | for (size_t i = 0; i < iL; i++) { |
286 | 0 | sass::string &lhs = numerators[i]; |
287 | 0 | UnitType ulhs = string_to_unit(lhs); |
288 | 0 | if (ulhs == UNKNOWN) continue; |
289 | 0 | UnitClass clhs = get_unit_type(ulhs); |
290 | 0 | UnitType umain = get_main_unit(clhs); |
291 | 0 | if (ulhs == umain) continue; |
292 | 0 | double f(conversion_factor(umain, ulhs, clhs, clhs)); |
293 | 0 | if (f == 0) throw std::runtime_error("INVALID"); |
294 | 0 | numerators[i] = unit_to_string(umain); |
295 | 0 | factor /= f; |
296 | 0 | } |
297 | | |
298 | 0 | for (size_t n = 0; n < nL; n++) { |
299 | 0 | sass::string &rhs = denominators[n]; |
300 | 0 | UnitType urhs = string_to_unit(rhs); |
301 | 0 | if (urhs == UNKNOWN) continue; |
302 | 0 | UnitClass crhs = get_unit_type(urhs); |
303 | 0 | UnitType umain = get_main_unit(crhs); |
304 | 0 | if (urhs == umain) continue; |
305 | 0 | double f(conversion_factor(umain, urhs, crhs, crhs)); |
306 | 0 | if (f == 0) throw std::runtime_error("INVALID"); |
307 | 0 | denominators[n] = unit_to_string(umain); |
308 | 0 | factor /= f; |
309 | 0 | } |
310 | | |
311 | 0 | std::sort (numerators.begin(), numerators.end()); |
312 | 0 | std::sort (denominators.begin(), denominators.end()); |
313 | | |
314 | | // return for conversion |
315 | 0 | return factor; |
316 | 0 | } |
317 | | |
318 | | double Units::reduce() |
319 | 192 | { |
320 | | |
321 | 192 | size_t iL = numerators.size(); |
322 | 192 | size_t nL = denominators.size(); |
323 | | |
324 | | // have less than two units? |
325 | 192 | if (iL + nL < 2) return 1; |
326 | | |
327 | | // first make sure same units cancel each other out |
328 | | // it seems that a map table will fit nicely to do this |
329 | | // we basically construct exponents for each unit |
330 | | // has the advantage that they will be pre-sorted |
331 | 0 | std::map<sass::string, int> exponents; |
332 | | |
333 | | // initialize by summing up occurrences in unit vectors |
334 | | // this will already cancel out equivalent units (e.q. px/px) |
335 | 0 | for (size_t i = 0; i < iL; i ++) exponents[numerators[i]] += 1; |
336 | 0 | for (size_t n = 0; n < nL; n ++) exponents[denominators[n]] -= 1; |
337 | | |
338 | | // the final conversion factor |
339 | 0 | double factor = 1; |
340 | | |
341 | | // convert between compatible units |
342 | 0 | for (size_t i = 0; i < iL; i++) { |
343 | 0 | for (size_t n = 0; n < nL; n++) { |
344 | 0 | sass::string &lhs = numerators[i], &rhs = denominators[n]; |
345 | 0 | int &lhsexp = exponents[lhs], &rhsexp = exponents[rhs]; |
346 | 0 | double f(convert_units(lhs, rhs, lhsexp, rhsexp)); |
347 | 0 | if (f == 0) continue; |
348 | 0 | factor /= f; |
349 | 0 | } |
350 | 0 | } |
351 | | |
352 | | // now we can build up the new unit arrays |
353 | 0 | numerators.clear(); |
354 | 0 | denominators.clear(); |
355 | | |
356 | | // recreate sorted units vectors |
357 | 0 | for (auto exp : exponents) { |
358 | 0 | int &exponent = exp.second; |
359 | 0 | while (exponent > 0 && exponent --) |
360 | 0 | numerators.push_back(exp.first); |
361 | 0 | while (exponent < 0 && exponent ++) |
362 | 0 | denominators.push_back(exp.first); |
363 | 0 | } |
364 | | |
365 | | // return for conversion |
366 | 0 | return factor; |
367 | | |
368 | 192 | } |
369 | | |
370 | | sass::string Units::unit() const |
371 | 112 | { |
372 | 112 | sass::string u; |
373 | 112 | size_t iL = numerators.size(); |
374 | 112 | size_t nL = denominators.size(); |
375 | 198 | for (size_t i = 0; i < iL; i += 1) { |
376 | 86 | if (i) u += '*'; |
377 | 86 | u += numerators[i]; |
378 | 86 | } |
379 | 112 | if (nL != 0) u += '/'; |
380 | 112 | for (size_t n = 0; n < nL; n += 1) { |
381 | 0 | if (n) u += '*'; |
382 | 0 | u += denominators[n]; |
383 | 0 | } |
384 | 112 | return u; |
385 | 112 | } |
386 | | |
387 | | bool Units::is_unitless() const |
388 | 0 | { |
389 | 0 | return numerators.empty() && |
390 | 0 | denominators.empty(); |
391 | 0 | } |
392 | | |
393 | | bool Units::is_valid_css_unit() const |
394 | 80 | { |
395 | 80 | return numerators.size() <= 1 && |
396 | 80 | denominators.size() == 0; |
397 | 80 | } |
398 | | |
399 | | // this does not cover all cases (multiple preferred units) |
400 | | double Units::convert_factor(const Units& r) const |
401 | 0 | { |
402 | |
|
403 | 0 | sass::vector<sass::string> miss_nums(0); |
404 | 0 | sass::vector<sass::string> miss_dens(0); |
405 | | // create copy since we need these for state keeping |
406 | 0 | sass::vector<sass::string> r_nums(r.numerators); |
407 | 0 | sass::vector<sass::string> r_dens(r.denominators); |
408 | |
|
409 | 0 | auto l_num_it = numerators.begin(); |
410 | 0 | auto l_num_end = numerators.end(); |
411 | |
|
412 | 0 | bool l_unitless = is_unitless(); |
413 | 0 | auto r_unitless = r.is_unitless(); |
414 | | |
415 | | // overall conversion |
416 | 0 | double factor = 1; |
417 | | |
418 | | // process all left numerators |
419 | 0 | while (l_num_it != l_num_end) |
420 | 0 | { |
421 | | // get and increment afterwards |
422 | 0 | const sass::string l_num = *(l_num_it ++); |
423 | |
|
424 | 0 | auto r_num_it = r_nums.begin(), r_num_end = r_nums.end(); |
425 | |
|
426 | 0 | bool found = false; |
427 | | // search for compatible numerator |
428 | 0 | while (r_num_it != r_num_end) |
429 | 0 | { |
430 | | // get and increment afterwards |
431 | 0 | const sass::string r_num = *(r_num_it); |
432 | | // get possible conversion factor for units |
433 | 0 | double conversion = conversion_factor(l_num, r_num); |
434 | | // skip incompatible numerator |
435 | 0 | if (conversion == 0) { |
436 | 0 | ++ r_num_it; |
437 | 0 | continue; |
438 | 0 | } |
439 | | // apply to global factor |
440 | 0 | factor *= conversion; |
441 | | // remove item from vector |
442 | 0 | r_nums.erase(r_num_it); |
443 | | // found numerator |
444 | 0 | found = true; |
445 | 0 | break; |
446 | 0 | } |
447 | | // maybe we did not find any |
448 | | // left numerator is leftover |
449 | 0 | if (!found) miss_nums.push_back(l_num); |
450 | 0 | } |
451 | |
|
452 | 0 | auto l_den_it = denominators.begin(); |
453 | 0 | auto l_den_end = denominators.end(); |
454 | | |
455 | | // process all left denominators |
456 | 0 | while (l_den_it != l_den_end) |
457 | 0 | { |
458 | | // get and increment afterwards |
459 | 0 | const sass::string l_den = *(l_den_it ++); |
460 | |
|
461 | 0 | auto r_den_it = r_dens.begin(); |
462 | 0 | auto r_den_end = r_dens.end(); |
463 | |
|
464 | 0 | bool found = false; |
465 | | // search for compatible denominator |
466 | 0 | while (r_den_it != r_den_end) |
467 | 0 | { |
468 | | // get and increment afterwards |
469 | 0 | const sass::string r_den = *(r_den_it); |
470 | | // get possible conversion factor for units |
471 | 0 | double conversion = conversion_factor(l_den, r_den); |
472 | | // skip incompatible denominator |
473 | 0 | if (conversion == 0) { |
474 | 0 | ++ r_den_it; |
475 | 0 | continue; |
476 | 0 | } |
477 | | // apply to global factor |
478 | 0 | factor /= conversion; |
479 | | // remove item from vector |
480 | 0 | r_dens.erase(r_den_it); |
481 | | // found denominator |
482 | 0 | found = true; |
483 | 0 | break; |
484 | 0 | } |
485 | | // maybe we did not find any |
486 | | // left denominator is leftover |
487 | 0 | if (!found) miss_dens.push_back(l_den); |
488 | 0 | } |
489 | | |
490 | | // check left-overs (ToDo: might cancel out?) |
491 | 0 | if (miss_nums.size() > 0 && !r_unitless) { |
492 | 0 | throw Exception::IncompatibleUnits(r, *this); |
493 | 0 | } |
494 | 0 | else if (miss_dens.size() > 0 && !r_unitless) { |
495 | 0 | throw Exception::IncompatibleUnits(r, *this); |
496 | 0 | } |
497 | 0 | else if (r_nums.size() > 0 && !l_unitless) { |
498 | 0 | throw Exception::IncompatibleUnits(r, *this); |
499 | 0 | } |
500 | 0 | else if (r_dens.size() > 0 && !l_unitless) { |
501 | 0 | throw Exception::IncompatibleUnits(r, *this); |
502 | 0 | } |
503 | | |
504 | 0 | return factor; |
505 | 0 | } |
506 | | |
507 | | } |