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

source code of gtk/subprojects/libsass/src/units.cpp