Skip to content

Commit

Permalink
Refactor unit handling to enable unit groups
Browse files Browse the repository at this point in the history
Fixes #1167
  • Loading branch information
mgreter committed May 7, 2015
1 parent 66631c0 commit c1adfe9
Show file tree
Hide file tree
Showing 3 changed files with 222 additions and 45 deletions.
17 changes: 10 additions & 7 deletions ast.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -663,7 +663,7 @@ namespace Sass {
// skip already canceled out unit
if (exponents[denom] >= 0) continue;
// skip all units we don't know how to convert
if (string_to_unit(denom) == INCOMMENSURABLE) continue;
if (string_to_unit(denom) == UNKNOWN) continue;
// now search for nominator
while (nom_it != nom_end)
{
Expand All @@ -672,7 +672,7 @@ namespace Sass {
// skip already canceled out unit
if (exponents[nom] <= 0) continue;
// skip all units we don't know how to convert
if (string_to_unit(nom) == INCOMMENSURABLE) continue;
if (string_to_unit(nom) == UNKNOWN) continue;
// we now have two convertable units
// add factor for current conversion
factor *= conversion_factor(nom, denom);
Expand Down Expand Up @@ -707,7 +707,10 @@ namespace Sass {

// maybe convert to other unit
// easier implemented on its own
convert(prefered);
try { convert(prefered); }
catch (incompatibleUnits& err)
{ error(err.what(), pstate()); }
catch (...) { throw; }

}

Expand Down Expand Up @@ -743,7 +746,7 @@ namespace Sass {
// skip already canceled out unit
if (exponents[denom] >= 0) continue;
// skip all units we don't know how to convert
if (string_to_unit(denom) == INCOMMENSURABLE) continue;
if (string_to_unit(denom) == UNKNOWN) continue;
// we now have two convertable units
// add factor for current conversion
factor *= conversion_factor(denom, prefered);
Expand All @@ -764,7 +767,7 @@ namespace Sass {
// skip already canceled out unit
if (exponents[nom] <= 0) continue;
// skip all units we don't know how to convert
if (string_to_unit(nom) == INCOMMENSURABLE) continue;
if (string_to_unit(nom) == UNKNOWN) continue;
// we now have two convertable units
// add factor for current conversion
factor *= conversion_factor(nom, prefered);
Expand Down Expand Up @@ -801,11 +804,11 @@ namespace Sass {
{
for (size_t i = 0, S = numerator_units_.size(); i < S; ++i) {
string u(numerator_units_[i]);
if (string_to_unit(u) != INCOMMENSURABLE) return u;
if (string_to_unit(u) != UNKNOWN) return u;
}
for (size_t i = 0, S = denominator_units_.size(); i < S; ++i) {
string u(denominator_units_[i]);
if (string_to_unit(u) != INCOMMENSURABLE) return u;
if (string_to_unit(u) != UNKNOWN) return u;
}
return string();
}
Expand Down
168 changes: 134 additions & 34 deletions units.cpp
Original file line number Diff line number Diff line change
@@ -1,55 +1,155 @@
#include <stdexcept>
#include "units.hpp"

#define PI 3.14159265358979323846

namespace Sass {

double conversion_factors[10][10] = {
/* in cm pc mm pt px deg grad rad turn */
/* in */ { 1, 2.54, 6, 25.4, 72, 96, 1, 1, 1, 1 },
/* cm */ { 1.0/2.54, 1, 6.0/2.54, 10, 72.0/2.54, 96.0/2.54, 1, 1, 1, 1 },
/* pc */ { 1.0/6.0, 2.54/6.0, 1, 25.4/6.0, 72.0/6.0, 96.0/6.0, 1, 1, 1, 1 },
/* mm */ { 1.0/25.4, 1.0/10.0, 6.0/25.4, 1, 72.0/25.4, 96.0/25.4, 1, 1, 1, 1 },
/* pt */ { 1.0/72.0, 2.54/72.0, 6.0/72.0, 25.4/72.0, 1, 96.0/72.0, 1, 1, 1, 1 },
/* px */ { 1.0/96.0, 2.54/96.0, 6.0/96.0, 25.4/96.0, 72.0/96.0, 1, 1, 1, 1, 1 },
/* deg */ { 1 , 1 , 1 , 1 , 1 , 1, 1, 40.0/36.0, PI/180.0, 1.0/360.0 },
/* grad */ { 1 , 1 , 1 , 1 , 1 , 1, 36.0/40.0, 1, PI/200.0, 1.0/400.0 },
/* rad */ { 1 , 1 , 1 , 1 , 1 , 1, 180.0/PI, 200.0/PI, 1, PI/2.0 },
/* turn */ { 1 , 1 , 1 , 1 , 1 , 1, 360.0/1.0, 400.0/1.0, 2.0*PI, 1 }
/* the conversion matrix can be readed the following way */
/* if you go down, the factor is for the numerator (multiply) */
/* if you go right, the factor is for the denominator (divide) */
/* and yes, we actually use both, not sure why, but why not!? */

const double size_conversion_factors[6][6] =
{
/* in cm pc mm pt px */
/* in */ { 1, 2.54, 6, 25.4, 72, 96, },
/* cm */ { 1.0/2.54, 1, 6.0/2.54, 10, 72.0/2.54, 96.0/2.54 },
/* pc */ { 1.0/6.0, 2.54/6.0, 1, 25.4/6.0, 72.0/6.0, 96.0/6.0 },
/* mm */ { 1.0/25.4, 1.0/10.0, 6.0/25.4, 1, 72.0/25.4, 96.0/25.4 },
/* pt */ { 1.0/72.0, 2.54/72.0, 6.0/72.0, 25.4/72.0, 1, 96.0/72.0 },
/* px */ { 1.0/96.0, 2.54/96.0, 6.0/96.0, 25.4/96.0, 72.0/96.0, 1, },
};

Unit string_to_unit(const string& s)
const double angle_conversion_factors[4][4] =
{
if (s == "in") return IN;
else if (s == "cm") return CM;
/* deg grad rad turn */
/* deg */ { 1, 40.0/36.0, PI/180.0, 1.0/360.0 },
/* grad */ { 36.0/40.0, 1, PI/200.0, 1.0/400.0 },
/* rad */ { 180.0/PI, 200.0/PI, 1, 0.5/PI },
/* turn */ { 360.0, 400.0, 2.0*PI, 1 }
};

const double time_conversion_factors[2][2]
{
/* s ms */
/* s */ { 1, 1000.0 },
/* ms */ { 1/1000.0, 1 },
};
const double frequency_conversion_factors[2][2]
{
/* Hz kHz */
/* Hz */ { 1, 1/1000.0 },
/* kHz */ { 1000.0, 1 },
};
const double resolution_conversion_factors[3][3]
{
/* dpi dpcm dppx */
/* dpi */ { 1, 2.54, 96 },
/* dpcm */ { 1/2.54, 1, 96/2.54 },
/* dppx */ { 1/96.0, 2.54/96, 1 },
};

SassUnitType get_unit_type(SassUnit unit)
{
switch (unit & 0xFF00)
{
case SIZE: return SIZE; break;
case ANGLE: return ANGLE; break;
case TIME: return TIME; break;
case FREQUENCY: return FREQUENCY; break;
case RESOLUTION: return RESOLUTION; break;
default: return INCOMMENSURABLE; break;
}
};

SassUnit string_to_unit(const string& s)
{
// size units
if (s == "px") return PX;
else if (s == "pt") return PT;
else if (s == "pc") return PC;
else if (s == "mm") return MM;
else if (s == "pt") return PT;
else if (s == "px") return PX;
else if (s == "cm") return CM;
else if (s == "in") return IN;
// angle units
else if (s == "deg") return DEG;
else if (s == "grad") return GRAD;
else if (s == "rad") return RAD;
else if (s == "turn") return TURN;
else return INCOMMENSURABLE;
// time units
else if (s == "s") return SEC;
else if (s == "ms") return MSEC;
// frequency units
else if (s == "Hz") return HERTZ;
else if (s == "kHz") return KHERTZ;
// resolutions units
else if (s == "dpi") return DPI;
else if (s == "dpcm") return DPCM;
else if (s == "dppx") return DPPX;
// for unknown units
else return UNKNOWN;
}

double conversion_factor(const string& s1, const string& s2)
const char* unit_to_string(SassUnit unit)
{
Unit u1 = string_to_unit(s1);
Unit u2 = string_to_unit(s2);
double factor;
if (u1 == INCOMMENSURABLE || u2 == INCOMMENSURABLE)
factor = 0;
else
factor = conversion_factors[u1][u2];
return factor;
switch (unit) {
// size units
case PX: return "px"; break;
case PT: return "pt"; break;
case PC: return "pc"; break;
case MM: return "mm"; break;
case CM: return "cm"; break;
case IN: return "in"; break;
// angle units
case DEG: return "deg"; break;
case GRAD: return "grad"; break;
case RAD: return "rad"; break;
case TURN: return "turn"; break;
// time units
case SEC: return "s"; break;
case MSEC: return "ms"; break;
// frequency units
case HERTZ: return "Hz"; break;
case KHERTZ: return "kHz"; break;
// resolutions units
case DPI: return "dpi"; break;
case DPCM: return "dpcm"; break;
case DPPX: return "dppx"; break;
// for unknown units
default: return ""; break;;
}
}

/* not used anymore - remove?
double convert(double n, const string& from, const string& to)
// throws incompatibleUnits exceptions
double conversion_factor(const string& s1, const string& s2)
{
double factor = conversion_factor(from, to);
return factor ? factor * n : n;
} */
// assert for same units
if (s1 == s2) return 1;
// get unit enum from string
SassUnit u1 = string_to_unit(s1);
SassUnit u2 = string_to_unit(s2);
// query unit group types
SassUnitType t1 = get_unit_type(u1);
SassUnitType t2 = get_unit_type(u2);
// get absolute offset
// used for array acces
size_t i1 = u1 - t1;
size_t i2 = u2 - t2;
// error if units are not of the same group
if (t1 != t2) throw incompatibleUnits(u1, u2);
// only process known units
if (u1 != UNKNOWN && u2 != UNKNOWN) {
switch (t1) {
case SIZE: return size_conversion_factors[i1][i2]; break;
case ANGLE: return angle_conversion_factors[i1][i2]; break;
case TIME: return time_conversion_factors[i1][i2]; break;
case FREQUENCY: return frequency_conversion_factors[i1][i2]; break;
case RESOLUTION: return resolution_conversion_factors[i1][i2]; break;
// ToDo: should we throw error here?
case INCOMMENSURABLE: return 0; break;
}
}
// fallback
return 1;
}

}
82 changes: 78 additions & 4 deletions units.hpp
Original file line number Diff line number Diff line change
@@ -1,15 +1,89 @@
#ifndef SASS_UNITS_H
#define SASS_UNITS_H

#include <cmath>
#include <string>
#include <sstream>

namespace Sass {
using namespace std;
enum Unit { IN, CM, PC, MM, PT, PX, DEG, GRAD, RAD, TURN, INCOMMENSURABLE };
extern double conversion_factors[10][10];
Unit string_to_unit(const string&);

const double PI = acos(-1);

enum SassUnitType {
SIZE = 0x000,
ANGLE = 0x100,
TIME = 0x200,
FREQUENCY = 0x300,
RESOLUTION = 0x400,
INCOMMENSURABLE = 0x500
};

enum SassUnit {

// size units
IN = SIZE,
CM,
PC,
MM,
PT,
PX,

// angle units
DEG = ANGLE,
GRAD,
RAD,
TURN,

// time units
SEC = TIME,
MSEC,

// frequency units
HERTZ = FREQUENCY,
KHERTZ,

// resolutions units
DPI = RESOLUTION,
DPCM,
DPPX,

// for unknown units
UNKNOWN = INCOMMENSURABLE

};

extern const double size_conversion_factors[6][6];
extern const double angle_conversion_factors[4][4];
extern const double time_conversion_factors[2][2];
extern const double frequency_conversion_factors[2][2];
extern const double resolution_conversion_factors[3][3];

SassUnit string_to_unit(const string&);
const char* unit_to_string(SassUnit unit);
SassUnitType get_unit_type(SassUnit unit);
// throws incompatibleUnits exceptions
double conversion_factor(const string&, const string&);
// double convert(double, const string&, const string&);

class incompatibleUnits: public exception
{
public:
const char* msg;
incompatibleUnits(SassUnit a, SassUnit b)
: exception()
{
stringstream ss;
ss << "Incompatible units: ";
ss << "'" << unit_to_string(a) << "' and ";
ss << "'" << unit_to_string(b) << "'";
msg = ss.str().c_str();
};
virtual const char* what() const throw()
{
return msg;
}
};

}

#endif

0 comments on commit c1adfe9

Please sign in to comment.