diff options
| author | Sebastian Wüst <sebi@timewaster.de> | 2013-10-20 15:32:08 +0000 |
|---|---|---|
| committer | Sebastian Wüst <sebi@timewaster.de> | 2013-10-20 15:32:08 +0000 |
| commit | 82908f949129e1fcbf62002799ee7b1b77986eed (patch) | |
| tree | c02098dd7720cdf424f2793ecd3ddac2ea86b969 /src/util | |
| parent | changed text (diff) | |
| parent | Fix build errors with clang 3.3 and c++11 enabled. (diff) | |
| download | inkscape-82908f949129e1fcbf62002799ee7b1b77986eed.tar.gz inkscape-82908f949129e1fcbf62002799ee7b1b77986eed.zip | |
merge from trunk
(bzr r12417.1.24)
Diffstat (limited to 'src/util')
| -rw-r--r-- | src/util/expression-evaluator.cpp | 668 | ||||
| -rw-r--r-- | src/util/expression-evaluator.h | 112 | ||||
| -rw-r--r-- | src/util/units.cpp | 654 | ||||
| -rw-r--r-- | src/util/units.h | 180 | ||||
| -rw-r--r-- | src/util/unordered-containers.h | 29 |
5 files changed, 948 insertions, 695 deletions
diff --git a/src/util/expression-evaluator.cpp b/src/util/expression-evaluator.cpp index 3e1bab6bc..48064e647 100644 --- a/src/util/expression-evaluator.cpp +++ b/src/util/expression-evaluator.cpp @@ -6,6 +6,7 @@ * Copyright (C) 2008 Martin Nordholts <martinn@svn.gnome.org> * Modified for Inkscape by Johan Engelen * Copyright (C) 2011 Johan Engelen + * Copyright (C) 2013 Matthew Petroff * * This library is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -27,502 +28,361 @@ #include "util/expression-evaluator.h" #include "util/units.h" +#include <math.h> #include <string.h> +using Inkscape::Util::unit_table; + namespace Inkscape { namespace Util { -enum +EvaluatorQuantity::EvaluatorQuantity(double value, unsigned int dimension) : + value(value), + dimension(dimension) { - GIMP_EEVL_TOKEN_NUM = 30000, - GIMP_EEVL_TOKEN_IDENTIFIER = 30001, - - GIMP_EEVL_TOKEN_ANY = 40000, - - GIMP_EEVL_TOKEN_END = 50000 -}; - -typedef int GimpEevlTokenType; - - -typedef struct -{ - GimpEevlTokenType type; - - union - { - gdouble fl; - - struct - { - const gchar *c; - gint size; - }; - - } value; - -} GimpEevlToken; +} -typedef struct +EvaluatorToken::EvaluatorToken() { - const gchar *string; - GimpEevlUnitResolverProc unit_resolver_proc; - Unit *unit; - - GimpEevlToken current_token; - const gchar *start_of_current_token; -} GimpEevl; + type = 0; + value.fl = 0; +} -/** Unit Resolver... - */ -static bool unitresolverproc (const gchar* identifier, GimpEevlQuantity *result, Unit* unit) +ExpressionEvaluator::ExpressionEvaluator(const char *string, Unit const *unit) : + string(string), + unit(unit) { - static UnitTable unit_table; - - if (!unit) { - result->value = 1; - result->dimension = 1; - return true; - }else if (!identifier) { - result->value = 1; - result->dimension = unit->isAbsolute() ? 1 : 0; - return true; - } else if (unit_table.hasUnit(identifier)) { - Unit identifier_unit = unit_table.getUnit(identifier); - - // Catch the case of zero or negative unit factors (error!) - if (identifier_unit.factor < 0.0000001) { - return false; - } - - result->value = unit->factor / identifier_unit.factor; - result->dimension = identifier_unit.isAbsolute() ? 1 : 0; - return true; - } else { - return false; - } + current_token.type = TOKEN_END; + + // Preload symbol + parseNextToken(); } -static void gimp_eevl_init (GimpEevl *eva, - const gchar *string, - GimpEevlUnitResolverProc unit_resolver_proc, - Unit *unit); -static GimpEevlQuantity gimp_eevl_complete (GimpEevl *eva); -static GimpEevlQuantity gimp_eevl_expression (GimpEevl *eva); -static GimpEevlQuantity gimp_eevl_term (GimpEevl *eva); -static GimpEevlQuantity gimp_eevl_signed_factor (GimpEevl *eva); -static GimpEevlQuantity gimp_eevl_factor (GimpEevl *eva); -static gboolean gimp_eevl_accept (GimpEevl *eva, - GimpEevlTokenType token_type, - GimpEevlToken *consumed_token); -static void gimp_eevl_lex (GimpEevl *eva); -static void gimp_eevl_lex_accept_count (GimpEevl *eva, - gint count, - GimpEevlTokenType token_type); -static void gimp_eevl_lex_accept_to (GimpEevl *eva, - gchar *to, - GimpEevlTokenType token_type); -static void gimp_eevl_move_past_whitespace (GimpEevl *eva); -static gboolean gimp_eevl_unit_identifier_start (gunichar c); -static gboolean gimp_eevl_unit_identifier_continue (gunichar c); -static gint gimp_eevl_unit_identifier_size (const gchar *s, - gint start); -static void gimp_eevl_expect (GimpEevl *eva, - GimpEevlTokenType token_type, - GimpEevlToken *value); -static void gimp_eevl_error (GimpEevl *eva, - const char *msg); - - /** * Evaluates the given arithmetic expression, along with an optional dimension * analysis, and basic unit conversions. * - * @param string The NULL-terminated string to be evaluated. - * @param unit_resolver_proc Unit resolver callback. - * * All units conversions factors are relative to some implicit - * base-unit (which in GIMP is inches). This is also the unit of the - * returned value. + * base-unit. This is also the unit of the returned value. * - * Returns: A #GimpEevlQuantity with a value given in the base unit along with - * the order of the dimension (i.e. if the base unit is inches, a dimension - * order of two menas in^2). + * Returns: An EvaluatorQuantity with a value given in the base unit along with + * the order of the dimension (e.g. if the base unit is inches, a dimension + * order of two means in^2). * * @return Result of evaluation. * @throws Inkscape::Util::EvaluatorException There was a parse error. **/ -GimpEevlQuantity -gimp_eevl_evaluate (const gchar* string, Unit* unit) +EvaluatorQuantity ExpressionEvaluator::evaluate() { - if (! g_utf8_validate (string, -1, NULL)) { + if (!g_utf8_validate(string, -1, NULL)) { throw EvaluatorException("Invalid UTF8 string", NULL); } - - GimpEevl eva; - gimp_eevl_init (&eva, string, unitresolverproc, unit); - - return gimp_eevl_complete(&eva); -} - -static void -gimp_eevl_init (GimpEevl *eva, - const gchar *string, - GimpEevlUnitResolverProc unit_resolver_proc, - Unit *unit) -{ - eva->string = string; - eva->unit_resolver_proc = unit_resolver_proc; - eva->unit = unit; - - eva->current_token.type = GIMP_EEVL_TOKEN_END; - - /* Preload symbol... */ - gimp_eevl_lex (eva); -} - -static GimpEevlQuantity -gimp_eevl_complete (GimpEevl *eva) -{ - GimpEevlQuantity result = {0, 0}; - GimpEevlQuantity default_unit_factor; - - /* Empty expression evaluates to 0 */ - if (gimp_eevl_accept (eva, GIMP_EEVL_TOKEN_END, NULL)) - return result; - - result = gimp_eevl_expression (eva); - - /* There should be nothing left to parse by now */ - gimp_eevl_expect (eva, GIMP_EEVL_TOKEN_END, 0); - - eva->unit_resolver_proc (NULL, &default_unit_factor, eva->unit); - - /* Entire expression is dimensionless, apply default unit if - * applicable - */ - if (result.dimension == 0 && default_unit_factor.dimension != 0) - { - result.value /= default_unit_factor.value; - result.dimension = default_unit_factor.dimension; + + EvaluatorQuantity result = EvaluatorQuantity(); + EvaluatorQuantity default_unit_factor; + + // Empty expression evaluates to 0 + if (acceptToken(TOKEN_END, NULL)) { + return result; + } + + result = evaluateExpression(); + + // There should be nothing left to parse by now + isExpected(TOKEN_END, 0); + + resolveUnit(NULL, &default_unit_factor, unit); + + // Entire expression is dimensionless, apply default unit if applicable + if ( result.dimension == 0 && default_unit_factor.dimension != 0 ) { + result.value /= default_unit_factor.value; + result.dimension = default_unit_factor.dimension; } - return result; + return result; } -static GimpEevlQuantity -gimp_eevl_expression (GimpEevl *eva) +EvaluatorQuantity ExpressionEvaluator::evaluateExpression() { - gboolean subtract; - GimpEevlQuantity evaluated_terms; - - evaluated_terms = gimp_eevl_term (eva); - - /* continue evaluating terms, chained with + or -. */ - for (subtract = FALSE; - gimp_eevl_accept (eva, '+', NULL) || - (subtract = gimp_eevl_accept (eva, '-', NULL)); - subtract = FALSE) + bool subtract; + EvaluatorQuantity evaluated_terms; + + evaluated_terms = evaluateTerm(); + + // Continue evaluating terms, chained with + or -. + for (subtract = FALSE; + acceptToken('+', NULL) || (subtract = acceptToken('-', NULL)); + subtract = FALSE) { - GimpEevlQuantity new_term = gimp_eevl_term (eva); - - /* If dimensions missmatch, attempt default unit assignent */ - if (new_term.dimension != evaluated_terms.dimension) - { - GimpEevlQuantity default_unit_factor; - - eva->unit_resolver_proc (NULL, - &default_unit_factor, - eva->unit); - - if (new_term.dimension == 0 && - evaluated_terms.dimension == default_unit_factor.dimension) + EvaluatorQuantity new_term = evaluateTerm(); + + // If dimensions mismatch, attempt default unit assignent + if ( new_term.dimension != evaluated_terms.dimension ) { + EvaluatorQuantity default_unit_factor; + + resolveUnit(NULL, &default_unit_factor, unit); + + if ( new_term.dimension == 0 + && evaluated_terms.dimension == default_unit_factor.dimension ) { - new_term.value /= default_unit_factor.value; - new_term.dimension = default_unit_factor.dimension; - } - else if (evaluated_terms.dimension == 0 && - new_term.dimension == default_unit_factor.dimension) - { - evaluated_terms.value /= default_unit_factor.value; - evaluated_terms.dimension = default_unit_factor.dimension; - } - else + new_term.value /= default_unit_factor.value; + new_term.dimension = default_unit_factor.dimension; + } else if ( evaluated_terms.dimension == 0 + && new_term.dimension == default_unit_factor.dimension ) { - gimp_eevl_error (eva, "Dimension missmatch during addition"); + evaluated_terms.value /= default_unit_factor.value; + evaluated_terms.dimension = default_unit_factor.dimension; + } else { + throwError("Dimension mismatch during addition"); } } - - evaluated_terms.value += (subtract ? -new_term.value : new_term.value); + + evaluated_terms.value += (subtract ? -new_term.value : new_term.value); } - - return evaluated_terms; + + return evaluated_terms; } -static GimpEevlQuantity -gimp_eevl_term (GimpEevl *eva) +EvaluatorQuantity ExpressionEvaluator::evaluateTerm() { - gboolean division; - GimpEevlQuantity evaluated_signed_factors; - - evaluated_signed_factors = gimp_eevl_signed_factor (eva); - - for (division = FALSE; - gimp_eevl_accept (eva, '*', NULL) || - (division = gimp_eevl_accept (eva, '/', NULL)); - division = FALSE) + bool division; + EvaluatorQuantity evaluated_exp_terms = evaluateExpTerm(); + + for ( division = false; + acceptToken('*', NULL) || (division = acceptToken('/', NULL)); + division = false ) { - GimpEevlQuantity new_signed_factor = gimp_eevl_signed_factor (eva); - - if (division) - { - evaluated_signed_factors.value /= new_signed_factor.value; - evaluated_signed_factors.dimension -= new_signed_factor.dimension; - - } - else - { - evaluated_signed_factors.value *= new_signed_factor.value; - evaluated_signed_factors.dimension += new_signed_factor.dimension; + EvaluatorQuantity new_exp_term = evaluateExpTerm(); + + if (division) { + evaluated_exp_terms.value /= new_exp_term.value; + evaluated_exp_terms.dimension -= new_exp_term.dimension; + } else { + evaluated_exp_terms.value *= new_exp_term.value; + evaluated_exp_terms.dimension += new_exp_term.dimension; } } - - return evaluated_signed_factors; -} - -static GimpEevlQuantity -gimp_eevl_signed_factor (GimpEevl *eva) -{ - GimpEevlQuantity result; - gboolean negate = FALSE; - - if (! gimp_eevl_accept (eva, '+', NULL)) - negate = gimp_eevl_accept (eva, '-', NULL); - - result = gimp_eevl_factor (eva); - - if (negate) result.value = -result.value; - - return result; + + return evaluated_exp_terms; } -static GimpEevlQuantity -gimp_eevl_factor (GimpEevl *eva) +EvaluatorQuantity ExpressionEvaluator::evaluateExpTerm() { - GimpEevlQuantity evaluated_factor = { 0, 0 }; - GimpEevlToken consumed_token; - - if (gimp_eevl_accept (eva, - GIMP_EEVL_TOKEN_NUM, - &consumed_token)) - { - evaluated_factor.value = consumed_token.value.fl; - } - else if (gimp_eevl_accept (eva, '(', NULL)) - { - evaluated_factor = gimp_eevl_expression (eva); - gimp_eevl_expect (eva, ')', 0); - } - else - { - gimp_eevl_error (eva, "Expected number or '('"); - } - - if (eva->current_token.type == GIMP_EEVL_TOKEN_IDENTIFIER) - { - gchar *identifier; - GimpEevlQuantity result; - - gimp_eevl_accept (eva, - GIMP_EEVL_TOKEN_ANY, - &consumed_token); - - identifier = g_newa (gchar, consumed_token.value.size + 1); - - strncpy (identifier, consumed_token.value.c, consumed_token.value.size); - identifier[consumed_token.value.size] = '\0'; - - if (eva->unit_resolver_proc (identifier, - &result, - eva->unit)) - { - evaluated_factor.value /= result.value; - evaluated_factor.dimension += result.dimension; - } - else - { - gimp_eevl_error (eva, "Unit was not resolved"); + EvaluatorQuantity evaluated_signed_factors = evaluateSignedFactor(); + + while(acceptToken('^', NULL)) { + EvaluatorQuantity new_signed_factor = evaluateSignedFactor(); + + if (new_signed_factor.dimension == 0) { + evaluated_signed_factors.value = pow(evaluated_signed_factors.value, + new_signed_factor.value); + evaluated_signed_factors.dimension *= new_signed_factor.value; + } else { + throwError("Unit in exponent"); } } - - return evaluated_factor; + + return evaluated_signed_factors; } -static gboolean -gimp_eevl_accept (GimpEevl *eva, - GimpEevlTokenType token_type, - GimpEevlToken *consumed_token) +EvaluatorQuantity ExpressionEvaluator::evaluateSignedFactor() { - gboolean existed = FALSE; - - if (token_type == eva->current_token.type || - token_type == GIMP_EEVL_TOKEN_ANY) - { - existed = TRUE; - - if (consumed_token) - *consumed_token = eva->current_token; - - /* Parse next token */ - gimp_eevl_lex (eva); + EvaluatorQuantity result; + bool negate = FALSE; + + if (!acceptToken('+', NULL)) { + negate = acceptToken ('-', NULL); } - - return existed; + + result = evaluateFactor(); + + if (negate) { + result.value = -result.value; + } + + return result; } -static void -gimp_eevl_lex (GimpEevl *eva) +EvaluatorQuantity ExpressionEvaluator::evaluateFactor() { - const gchar *s; - - gimp_eevl_move_past_whitespace (eva); - s = eva->string; - eva->start_of_current_token = s; - - if (! s || s[0] == '\0') - { - /* We're all done */ - eva->current_token.type = GIMP_EEVL_TOKEN_END; - } - else if (s[0] == '+' || s[0] == '-') - { - /* Snatch these before the g_strtod() does, othewise they might - * be used in a numeric conversion. - */ - gimp_eevl_lex_accept_count (eva, 1, s[0]); + EvaluatorQuantity evaluated_factor = EvaluatorQuantity(); + EvaluatorToken consumed_token = EvaluatorToken(); + + if (acceptToken(TOKEN_NUM, &consumed_token)) { + evaluated_factor.value = consumed_token.value.fl; + } else if (acceptToken('(', NULL)) { + evaluated_factor = evaluateExpression(); + isExpected(')', 0); + } else { + throwError("Expected number or '('"); } - else - { - /* Attempt to parse a numeric value */ - gchar *endptr = NULL; - gdouble value = g_strtod (s, &endptr); - - if (endptr && endptr != s) - { - /* A numeric could be parsed, use it */ - eva->current_token.value.fl = value; - - gimp_eevl_lex_accept_to (eva, endptr, GIMP_EEVL_TOKEN_NUM); + if ( current_token.type == TOKEN_IDENTIFIER ) { + char *identifier; + EvaluatorQuantity result; + + acceptToken(TOKEN_ANY, &consumed_token); + + identifier = g_newa(char, consumed_token.value.size + 1); + + strncpy(identifier, consumed_token.value.c, consumed_token.value.size); + identifier[consumed_token.value.size] = '\0'; + + if (resolveUnit(identifier, &result, unit)) { + evaluated_factor.value /= result.value; + evaluated_factor.dimension += result.dimension; + } else { + throwError("Unit was not resolved"); } - else if (gimp_eevl_unit_identifier_start (s[0])) - { - /* Unit identifier */ - eva->current_token.value.c = s; - eva->current_token.value.size = gimp_eevl_unit_identifier_size (s, 0); + } + + return evaluated_factor; +} - gimp_eevl_lex_accept_count (eva, - eva->current_token.value.size, - GIMP_EEVL_TOKEN_IDENTIFIER); - } - else - { - /* Everything else is a single character token */ - gimp_eevl_lex_accept_count (eva, 1, s[0]); +bool ExpressionEvaluator::acceptToken(TokenType token_type, + EvaluatorToken *consumed_token) +{ + bool existed = FALSE; + + if ( token_type == current_token.type || token_type == TOKEN_ANY ) { + existed = TRUE; + + if (consumed_token) { + *consumed_token = current_token; } + + // Parse next token + parseNextToken(); } + + return existed; } -static void -gimp_eevl_lex_accept_count (GimpEevl *eva, - gint count, - GimpEevlTokenType token_type) +void ExpressionEvaluator::parseNextToken() { - eva->current_token.type = token_type; - eva->string += count; + const char *s; + + movePastWhiteSpace(); + s = string; + start_of_current_token = s; + + if ( !s || s[0] == '\0' ) { + // We're all done + current_token.type = TOKEN_END; + } else if ( s[0] == '+' || s[0] == '-' ) { + // Snatch these before the g_strtod() does, othewise they might + // be used in a numeric conversion. + acceptTokenCount(1, s[0]); + } else { + // Attempt to parse a numeric value + char *endptr = NULL; + gdouble value = g_strtod(s, &endptr); + + if ( endptr && endptr != s ) { + // A numeric could be parsed, use it + current_token.value.fl = value; + + current_token.type = TOKEN_NUM; + string = endptr; + } else if (isUnitIdentifierStart(s[0])) { + // Unit identifier + current_token.value.c = s; + current_token.value.size = getIdentifierSize(s, 0); + + acceptTokenCount(current_token.value.size, TOKEN_IDENTIFIER); + } else { + // Everything else is a single character token + acceptTokenCount(1, s[0]); + } + } } -static void -gimp_eevl_lex_accept_to (GimpEevl *eva, - gchar *to, - GimpEevlTokenType token_type) +void ExpressionEvaluator::acceptTokenCount (int count, TokenType token_type) { - eva->current_token.type = token_type; - eva->string = to; + current_token.type = token_type; + string += count; } -static void -gimp_eevl_move_past_whitespace (GimpEevl *eva) +void ExpressionEvaluator::isExpected(TokenType token_type, + EvaluatorToken *value) { - if (! eva->string) - return; - - while (g_ascii_isspace (*eva->string)) - eva->string++; + if (!acceptToken(token_type, value)) { + throwError("Unexpected token"); + } } -static gboolean -gimp_eevl_unit_identifier_start (gunichar c) +void ExpressionEvaluator::movePastWhiteSpace() { - return (g_unichar_isalpha (c) || - c == (gunichar) '%' || - c == (gunichar) '\''); + if (!string) { + return; + } + + while (g_ascii_isspace(*string)) { + string++; + } } -static gboolean -gimp_eevl_unit_identifier_continue (gunichar c) +bool ExpressionEvaluator::isUnitIdentifierStart(gunichar c) { - return (gimp_eevl_unit_identifier_start (c) || - g_unichar_isdigit (c)); + return (g_unichar_isalpha (c) + || c == (gunichar) '%' + || c == (gunichar) '\''); } /** - * gimp_eevl_unit_identifier_size: + * getIdentifierSize: * @s: * @start: * * Returns: Size of identifier in bytes (not including NULL * terminator). **/ -static gint -gimp_eevl_unit_identifier_size (const gchar *string, - gint start_offset) +int ExpressionEvaluator::getIdentifierSize(const char *string, int start_offset) { - const gchar *start = g_utf8_offset_to_pointer (string, start_offset); - const gchar *s = start; - gunichar c = g_utf8_get_char (s); - gint length = 0; - - if (gimp_eevl_unit_identifier_start (c)) - { - s = g_utf8_next_char (s); - c = g_utf8_get_char (s); - length++; - - while (gimp_eevl_unit_identifier_continue (c)) - { - s = g_utf8_next_char (s); - c = g_utf8_get_char (s); - length++; + const char *start = g_utf8_offset_to_pointer(string, start_offset); + const char *s = start; + gunichar c = g_utf8_get_char(s); + int length = 0; + + if (isUnitIdentifierStart(c)) { + s = g_utf8_next_char (s); + c = g_utf8_get_char (s); + length++; + + while ( isUnitIdentifierStart (c) || g_unichar_isdigit (c) ) { + s = g_utf8_next_char(s); + c = g_utf8_get_char(s); + length++; } } - - return g_utf8_offset_to_pointer (start, length) - start; + + return g_utf8_offset_to_pointer(start, length) - start; } -static void -gimp_eevl_expect (GimpEevl *eva, - GimpEevlTokenType token_type, - GimpEevlToken *value) +bool ExpressionEvaluator::resolveUnit (const char* identifier, + EvaluatorQuantity *result, + Unit const* unit) { - if (! gimp_eevl_accept (eva, token_type, value)) - gimp_eevl_error (eva, "Unexpected token"); + if (!unit) { + result->value = 1; + result->dimension = 1; + return true; + }else if (!identifier) { + result->value = 1; + result->dimension = unit->isAbsolute() ? 1 : 0; + return true; + } else if (unit_table.hasUnit(identifier)) { + Unit const *identifier_unit = unit_table.getUnit(identifier); + result->value = Quantity::convert(1, unit, identifier_unit); + result->dimension = identifier_unit->isAbsolute() ? 1 : 0; + return true; + } else { + return false; + } } -static void -gimp_eevl_error (GimpEevl *eva, - const char *msg) +void ExpressionEvaluator::throwError(const char *msg) { - throw EvaluatorException(msg, eva->start_of_current_token); + throw EvaluatorException(msg, start_of_current_token); } } // namespace Util diff --git a/src/util/expression-evaluator.h b/src/util/expression-evaluator.h index 4b1065268..50ff3fb70 100644 --- a/src/util/expression-evaluator.h +++ b/src/util/expression-evaluator.h @@ -6,6 +6,7 @@ * Copyright (C) 2008-2009 Martin Nordholts <martinn@svn.gnome.org> * Modified for Inkscape by Johan Engelen * Copyright (C) 2011 Johan Engelen + * Copyright (C) 2013 Matthew Petroff * * This library is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -22,8 +23,8 @@ * <http://www.gnu.org/licenses/>. */ -#ifndef SEEN_GIMP_EEVL_H -#define SEEN_GIMP_EEVL_H +#ifndef INKSCAPE_UTIL_EXPRESSION_EVALUATOR_H +#define INKSCAPE_UTIL_EXPRESSION_EVALUATOR_H #include "util/units.h" @@ -33,7 +34,7 @@ /** * @file - * Introducing eevl eva, the evaluator. A straightforward recursive + * Expression evaluator: A straightforward recursive * descent parser, no fuss, no new dependencies. The lexer is hand * coded, tedious, not extremely fast but works. It evaluates the * expression as it goes along, and does not create a parse tree or @@ -43,8 +44,8 @@ * * It relies on external unit resolving through a callback and does * elementary dimensionality constraint check (e.g. "2 mm + 3 px * 4 - * in" is an error, as L + L^2 is a missmatch). It uses g_strtod() for numeric - * conversions and it's non-destructive in terms of the paramters, and + * in" is an error, as L + L^2 is a mismatch). It uses g_strtod() for numeric + * conversions and it's non-destructive in terms of the parameters, and * it's reentrant. * * EBNF: @@ -52,7 +53,9 @@ * expression ::= term { ('+' | '-') term }* | * <empty string> ; * - * term ::= signed factor { ( '*' | '/' ) signed factor }* ; + * term ::= exponent { ( '*' | '/' ) exponent }* ; + * + * exponent ::= signed factor { '^' signed factor }* ; * * signed factor ::= ( '+' | '-' )? factor ; * @@ -79,37 +82,104 @@ namespace Util { class Unit; /** -* GimpEevlQuantity: -* @value: In reference units. -* @dimension: in has a dimension of 1, in^2 has a dimension of 2 etc -*/ -typedef struct + * EvaluatorQuantity: + * @param value In reference units. + * @param dimension mm has a dimension of 1, mm^2 has a dimension of 2, etc. + */ +class EvaluatorQuantity { +public: + EvaluatorQuantity(double value = 0, unsigned int dimension = 0); + double value; - gint dimension; -} GimpEevlQuantity; + unsigned int dimension; +}; -typedef bool (* GimpEevlUnitResolverProc) (const gchar *identifier, - GimpEevlQuantity *result, - Unit* unit); +/** + * TokenType + */ +enum { + TOKEN_NUM = 30000, + TOKEN_IDENTIFIER = 30001, + TOKEN_ANY = 40000, + TOKEN_END = 50000 +}; +typedef int TokenType; -GimpEevlQuantity gimp_eevl_evaluate (const gchar* string, Unit* unit = NULL); +/** + * EvaluatorToken + */ +class EvaluatorToken +{ +public: + EvaluatorToken(); + + TokenType type; + + union { + double fl; + struct { + const char *c; + int size; + }; + } value; +}; + +/** + * ExpressionEvaluator + * @param string NULL terminated input string to evaluate + * @param unit Unit output should be in + */ +class ExpressionEvaluator +{ +public: + ExpressionEvaluator(const char *string, Unit const *unit = NULL); + + EvaluatorQuantity evaluate(); + +private: + const char *string; + Unit const *unit; + + EvaluatorToken current_token; + const char *start_of_current_token; + + EvaluatorQuantity evaluateExpression(); + EvaluatorQuantity evaluateTerm(); + EvaluatorQuantity evaluateExpTerm(); + EvaluatorQuantity evaluateSignedFactor(); + EvaluatorQuantity evaluateFactor(); + + bool acceptToken(TokenType token_type, EvaluatorToken *consumed_token); + void parseNextToken(); + void acceptTokenCount(int count, TokenType token_type); + void isExpected(TokenType token_type, EvaluatorToken *value); + + void movePastWhiteSpace(); + + static bool isUnitIdentifierStart(gunichar c); + static int getIdentifierSize(const char *s, int start); + + static bool resolveUnit(const char *identifier, EvaluatorQuantity *result, Unit const *unit); + + void throwError(const char *msg); +}; /** * Special exception class for the expression evaluator. */ class EvaluatorException : public std::exception { public: - EvaluatorException(const char * message, const char *at_position) { + EvaluatorException(const char *message, const char *at_position) { std::ostringstream os; - const char* token = at_position ? at_position : "<End of input>"; + const char *token = at_position ? at_position : "<End of input>"; os << "Expression evaluator error: " << message << " at '" << token << "'"; msgstr = os.str(); } virtual ~EvaluatorException() throw() {} // necessary to destroy the string object!!! - virtual const char* what() const throw () { + virtual const char *what() const throw () { return msgstr.c_str(); } protected: @@ -119,4 +189,4 @@ protected: } } -#endif // SEEN_GIMP_EEVL_H +#endif // INKSCAPE_UTIL_EXPRESSION_EVALUATOR_H diff --git a/src/util/units.cpp b/src/util/units.cpp index f822d01de..1023cfb6e 100644 --- a/src/util/units.cpp +++ b/src/util/units.cpp @@ -1,343 +1,525 @@ +/* + * Inkscape Units + * + * Authors: + * Matthew Petroff <matthew@mpetroff.net> + * + * Copyright (C) 2013 Matthew Petroff + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + #ifdef HAVE_CONFIG_H # include <config.h> #endif #include <cmath> #include <cerrno> +#include <iomanip> #include <glib.h> +#include <glibmm/regex.h> +#include <glibmm/fileutils.h> +#include <glibmm/markup.h> -#include "io/simple-sax.h" #include "util/units.h" #include "path-prefix.h" #include "streq.h" +using Inkscape::Util::UNIT_TYPE_DIMENSIONLESS; +using Inkscape::Util::UNIT_TYPE_LINEAR; +using Inkscape::Util::UNIT_TYPE_RADIAL; +using Inkscape::Util::UNIT_TYPE_FONT_HEIGHT; + +namespace +{ + +#define MAKE_UNIT_CODE(a, b) \ + ((((unsigned)(a) & 0xdf) << 8) | ((unsigned)(b) & 0xdf)) + +enum UnitCode { + UNIT_CODE_PX = MAKE_UNIT_CODE('p','x'), + UNIT_CODE_PT = MAKE_UNIT_CODE('p','t'), + UNIT_CODE_PC = MAKE_UNIT_CODE('p','c'), + UNIT_CODE_MM = MAKE_UNIT_CODE('m','m'), + UNIT_CODE_CM = MAKE_UNIT_CODE('c','m'), + UNIT_CODE_IN = MAKE_UNIT_CODE('i','n'), + UNIT_CODE_FT = MAKE_UNIT_CODE('f','t'), + UNIT_CODE_EM = MAKE_UNIT_CODE('e','m'), + UNIT_CODE_EX = MAKE_UNIT_CODE('e','x'), + UNIT_CODE_PERCENT = MAKE_UNIT_CODE('%',0) +}; + +// TODO: convert to constexpr in C++11, so that the above constants can be eliminated +inline unsigned make_unit_code(char a, char b) { + // this should work without the casts, but let's be 100% sure + // also ensure that the codes are in lowercase + return MAKE_UNIT_CODE(a,b); +} +inline unsigned make_unit_code(char const *str) { + if (!str || str[0] == 0) return 0; + return MAKE_UNIT_CODE(str[0], str[1]); +} + + + +unsigned const svg_length_lookup[] = { + 0, + UNIT_CODE_PX, + UNIT_CODE_PT, + UNIT_CODE_PC, + UNIT_CODE_MM, + UNIT_CODE_CM, + UNIT_CODE_IN, + UNIT_CODE_FT, + UNIT_CODE_EM, + UNIT_CODE_EX, + UNIT_CODE_PERCENT +}; + + + +// maps unit codes obtained from their abbreviations to their SVGLength unit indexes +typedef INK_UNORDERED_MAP<unsigned, SVGLength::Unit> UnitCodeLookup; + +UnitCodeLookup make_unit_code_lookup() +{ + UnitCodeLookup umap; + for (unsigned i = 1; i < G_N_ELEMENTS(svg_length_lookup); ++i) { + umap[svg_length_lookup[i]] = static_cast<SVGLength::Unit>(i); + } + return umap; +} + +UnitCodeLookup const unit_code_lookup = make_unit_code_lookup(); + + + +typedef INK_UNORDERED_MAP<Glib::ustring, Inkscape::Util::UnitType> TypeMap; + +/** A std::map that gives the data type value for the string version. + * @todo consider hiding map behind hasFoo() and getFoo() type functions. */ +TypeMap make_type_map() +{ + TypeMap tmap; + tmap["DIMENSIONLESS"] = UNIT_TYPE_DIMENSIONLESS; + tmap["LINEAR"] = UNIT_TYPE_LINEAR; + tmap["RADIAL"] = UNIT_TYPE_RADIAL; + tmap["FONT_HEIGHT"] = UNIT_TYPE_FONT_HEIGHT; + // Note that code was not yet handling LINEAR_SCALED, TIME, QTY and NONE + + return tmap; +} + +TypeMap const type_map = make_type_map(); + +} // namespace + namespace Inkscape { namespace Util { -class UnitsSAXHandler : public Inkscape::IO::FlatSaxHandler +class UnitParser : public Glib::Markup::Parser { public: - UnitsSAXHandler(UnitTable *table); - virtual ~UnitsSAXHandler() {} + typedef Glib::Markup::Parser::AttributeMap AttrMap; + typedef Glib::Markup::ParseContext Ctx; + + UnitParser(UnitTable *table); + virtual ~UnitParser() {} - virtual void _startElement(xmlChar const *name, xmlChar const **attrs); - virtual void _endElement(xmlChar const *name); +protected: + virtual void on_start_element(Ctx &ctx, Glib::ustring const &name, AttrMap const &attrs); + virtual void on_end_element(Ctx &ctx, Glib::ustring const &name); + virtual void on_text(Ctx &ctx, Glib::ustring const &text); +public: UnitTable *tbl; bool primary; bool skip; Unit unit; }; -UnitsSAXHandler::UnitsSAXHandler(UnitTable *table) : - FlatSaxHandler(), +UnitParser::UnitParser(UnitTable *table) : tbl(table), - primary(0), - skip(0), - unit() + primary(false), + skip(false) { } #define BUFSIZE (255) -/** - * Returns the suggested precision to use for displaying numbers - * of this unit. - */ -int Unit::defaultDigits() const { +Unit::Unit() : + type(UNIT_TYPE_DIMENSIONLESS), // should this or NONE be the default? + factor(1.0), + name(), + name_plural(), + abbr(), + description() +{ +} + +Unit::Unit(UnitType type, + double factor, + Glib::ustring const &name, + Glib::ustring const &name_plural, + Glib::ustring const &abbr, + Glib::ustring const &description) + : type(type) + , factor(factor) + , name(name) + , name_plural(name_plural) + , abbr(abbr) + , description(description) +{ + g_return_if_fail(factor <= 0); +} + +void Unit::clear() +{ + *this = Unit(); +} + +int Unit::defaultDigits() const +{ int factor_digits = int(log10(factor)); if (factor_digits < 0) { g_warning("factor = %f, factor_digits = %d", factor, factor_digits); g_warning("factor_digits < 0 - returning 0"); - return 0; - } else { - return factor_digits; + factor_digits = 0; } + return factor_digits; } -/** - * Initializes the unit tables and identifies the primary unit types. - * - * The primary unit's conversion factor is required to be 1.00 - */ -UnitTable::UnitTable() +bool Unit::compatibleWith(Unit const *u) const +{ + // Percentages + if (type == UNIT_TYPE_DIMENSIONLESS || u->type == UNIT_TYPE_DIMENSIONLESS) { + return true; + } + + // Other units with same type + if (type == u->type) { + return true; + } + + // Different, incompatible types + return false; +} +bool Unit::compatibleWith(Glib::ustring const &u) const +{ + static UnitTable unit_table; + return compatibleWith(unit_table.getUnit(u)); +} + +bool Unit::operator==(Unit const &other) const +{ + return (type == other.type && name.compare(other.name) == 0); +} + +int Unit::svgUnit() const +{ + char const *astr = abbr.c_str(); + unsigned code = make_unit_code(astr); + + UnitCodeLookup::const_iterator u = unit_code_lookup.find(code); + if (u != unit_code_lookup.end()) { + return u->second; + } + return 0; +} + + + +Unit UnitTable::_empty_unit; + +UnitTable::UnitTable() { - // if we swich to the xml file, don't forget to force locale to 'C' - // load("share/ui/units.xml"); // <-- Buggy - gchar *filename = g_build_filename(INKSCAPE_UIDIR, "units.txt", NULL); - loadText(filename); + gchar *filename = g_build_filename(INKSCAPE_UIDIR, "units.xml", NULL); + load(filename); g_free(filename); } -UnitTable::~UnitTable() { - UnitMap::iterator iter = _unit_map.begin(); - while (iter != _unit_map.end()) { - delete (*iter).second; - ++iter; +UnitTable::~UnitTable() +{ + for (UnitCodeMap::iterator iter = _unit_map.begin(); iter != _unit_map.end(); ++iter) + { + delete iter->second; } } -/** Add a new unit to the table */ -void UnitTable::addUnit(Unit const &u, bool primary) { - _unit_map[u.abbr] = new Unit(u); +void UnitTable::addUnit(Unit const &u, bool primary) +{ + _unit_map[make_unit_code(u.abbr.c_str())] = new Unit(u); if (primary) { - _primary_unit[u.type] = u.abbr; + _primary_unit[u.type] = u.abbr; } } -/** Retrieve a given unit based on its string identifier */ -Unit UnitTable::getUnit(Glib::ustring const &unit_abbr) const { - UnitMap::const_iterator iter = _unit_map.find(unit_abbr); - if (iter != _unit_map.end()) { - return *((*iter).second); - } else { - return Unit(); +Unit const *UnitTable::getUnit(char const *abbr) const +{ + UnitCodeMap::const_iterator f = _unit_map.find(make_unit_code(abbr)); + if (f != _unit_map.end()) { + return &(*f->second); } + return &_empty_unit; } -/** Remove a unit definition from the given unit type table */ -bool UnitTable::deleteUnit(Unit const &u) { - if (u.abbr == _primary_unit[u.type]) { - // Cannot delete the primary unit type since it's - // used for conversions - return false; +Unit const *UnitTable::getUnit(Glib::ustring const &unit_abbr) const +{ + return getUnit(unit_abbr.c_str()); +} +Unit const *UnitTable::getUnit(SVGLength::Unit u) const +{ + if (u == 0 || u > SVGLength::LAST_UNIT) { + return &_empty_unit; } - UnitMap::iterator iter = _unit_map.find(u.abbr); - if (iter != _unit_map.end()) { - delete (*iter).second; - _unit_map.erase(iter); - return true; - } else { - return false; + + UnitCodeMap::const_iterator f = _unit_map.find(svg_length_lookup[u]); + if (f != _unit_map.end()) { + return &(*f->second); } + return &_empty_unit; } -/** Returns true if the given string 'name' is a valid unit in the table */ +Quantity UnitTable::parseQuantity(Glib::ustring const &q) const +{ + Glib::MatchInfo match_info; + + // Extract value + double value = 0; + Glib::RefPtr<Glib::Regex> value_regex = Glib::Regex::create("[-+]*[\\d+]*[\\.,]*[\\d+]*[eE]*[-+]*\\d+"); + if (value_regex->match(q, match_info)) { + std::istringstream tmp_v(match_info.fetch(0)); + tmp_v >> value; + } + + // Extract unit abbreviation + Glib::ustring abbr; + Glib::RefPtr<Glib::Regex> unit_regex = Glib::Regex::create("[A-z%]+"); + if (unit_regex->match(q, match_info)) { + abbr = match_info.fetch(0); + } + + Quantity qty(value, abbr); + return qty; +} + +/* UNSAFE while passing around pointers to the Unit objects in this table +bool UnitTable::deleteUnit(Unit const &u) +{ + bool deleted = false; + // Cannot delete the primary unit type since it's + // used for conversions + if (u.abbr != _primary_unit[u.type]) { + UnitCodeMap::iterator iter = _unit_map.find(make_unit_code(u.abbr.c_str())); + if (iter != _unit_map.end()) { + delete (*iter).second; + _unit_map.erase(iter); + deleted = true; + } + } + return deleted; +} +*/ + bool UnitTable::hasUnit(Glib::ustring const &unit) const { - UnitMap::const_iterator iter = _unit_map.find(unit); + UnitCodeMap::const_iterator iter = _unit_map.find(make_unit_code(unit.c_str())); return (iter != _unit_map.end()); } -/** Provides an iteratable list of items in the given unit table */ UnitTable::UnitMap UnitTable::units(UnitType type) const { UnitMap submap; - for (UnitMap::const_iterator iter = _unit_map.begin(); - iter != _unit_map.end(); ++iter) { - if (((*iter).second)->type == type) { - submap.insert(UnitMap::value_type((*iter).first, new Unit(*((*iter).second)))); - } + for (UnitCodeMap::const_iterator iter = _unit_map.begin(); iter != _unit_map.end(); ++iter) { + if (iter->second->type == type) { + submap.insert(UnitMap::value_type(iter->second->abbr, *iter->second)); + } } return submap; } -/** Returns the default unit abbr for the given type */ Glib::ustring UnitTable::primary(UnitType type) const { return _primary_unit[type]; } -/** Loads units from a text file. - - loadText loads and merges the contents of the given file into the UnitTable, - possibly overwriting existing unit definitions. - - @param filename: file to be loaded*/ -bool UnitTable::loadText(Glib::ustring const &filename) -{ - char buf[BUFSIZE]; - - // Open file for reading - FILE * f = fopen(filename.c_str(), "r"); - if (f == NULL) { - g_warning("Could not open units file '%s': %s\n", - filename.c_str(), strerror(errno)); - g_warning("* INKSCAPE_DATADIR is: '%s'\n", INKSCAPE_DATADIR); - g_warning("* INKSCAPE_UIDIR is: '%s'\n", INKSCAPE_UIDIR); - return false; - } - - // bypass current locale in order to make - // sscanf read floats with '.' as a separator - // set locale to 'C' and keep old locale - char *old_locale; - old_locale = g_strdup (setlocale (LC_NUMERIC, NULL)); - setlocale (LC_NUMERIC, "C"); - - while (fgets(buf, BUFSIZE, f) != NULL) { - char name[BUFSIZE]; - char plural[BUFSIZE]; - char abbr[BUFSIZE]; - char type[BUFSIZE]; - double factor; - char primary[BUFSIZE]; - - int nchars = 0; - // locale is set to C, scanning %lf should work _everywhere_ - if (sscanf(buf, "%15s %15s %15s %15s %8lf %1s %15n", - name, plural, abbr, type, &factor, primary, &nchars) != 6) - { - // Skip the line - doesn't appear to be valid - continue; - } - - g_assert(nchars < BUFSIZE); - - char *desc = buf; - desc += nchars; // buf is now only the description - - // insert into _unit_map - Unit u; - u.name = name; - u.name_plural = plural; - u.abbr = abbr; - u.description = desc; - u.factor = factor; - - if (streq(type, "DIMENSIONLESS")) { - u.type = UNIT_TYPE_DIMENSIONLESS; - } else if (streq(type, "LINEAR")) { - u.type = UNIT_TYPE_LINEAR; - } else if (streq(type, "RADIAL")) { - u.type = UNIT_TYPE_RADIAL; - } else if (streq(type, "FONT_HEIGHT")) { - u.type = UNIT_TYPE_FONT_HEIGHT; - } else { - g_warning("Skipping unknown unit type '%s' for %s.\n", - type, name); - continue; - } - - // if primary is 'Y', list this unit as a primary - addUnit(u, (primary[0]=='Y' || primary[0]=='y')); - +bool UnitTable::load(std::string const &filename) { + UnitParser uparser(this); + Glib::Markup::ParseContext ctx(uparser); + + try { + Glib::ustring unitfile = Glib::file_get_contents(filename); + ctx.parse(unitfile); + ctx.end_parse(); + } catch (Glib::FileError const &e) { + g_warning("Units file %s is missing: %s\n", filename.c_str(), e.what().c_str()); + return false; + } catch (Glib::MarkupError const &e) { + g_warning("Problem loading units file '%s': %s\n", filename.c_str(), e.what().c_str()); + return false; } + return true; +} - // set back the saved locale - setlocale (LC_NUMERIC, old_locale); - g_free (old_locale); +bool UnitTable::save(std::string const &filename) { - // close file - if (fclose(f) != 0) { - g_warning("Error closing units file '%s': %s\n", - filename.c_str(), strerror(errno)); - return false; - } + g_warning("UnitTable::save(): not implemented"); return true; } -bool UnitTable::load(Glib::ustring const &filename) { - UnitsSAXHandler handler(this); +Inkscape::Util::UnitTable unit_table; - int result = handler.parseFile( filename.c_str() ); - if ( result != 0 ) { - // perhaps - g_warning("Problem loading units file '%s': %d\n", - filename.c_str(), result); - return false; - } +void UnitParser::on_start_element(Ctx &ctx, Glib::ustring const &name, AttrMap const &attrs) +{ + if (name == "unit") { + // reset for next use + unit.clear(); + primary = false; + skip = false; - return true; + AttrMap::const_iterator f; + if ((f = attrs.find("type")) != attrs.end()) { + Glib::ustring type = f->second; + TypeMap::const_iterator tf = type_map.find(type); + if (tf != type_map.end()) { + unit.type = tf->second; + } else { + g_warning("Skipping unknown unit type '%s'.\n", type.c_str()); + skip = true; + } + } + if ((f = attrs.find("pri")) != attrs.end()) { + primary = (f->second[0] == 'y' || f->second[0] == 'Y'); + } + } } -/** Saves the current UnitTable to the given file. */ -bool UnitTable::save(Glib::ustring const &filename) { - - // open file for writing - FILE *f = fopen(filename.c_str(), "w"); - if (f == NULL) { - g_warning("Could not open units file '%s': %s\n", - filename.c_str(), strerror(errno)); - return false; +void UnitParser::on_text(Ctx &ctx, Glib::ustring const &text) +{ + Glib::ustring element = ctx.get_element(); + if (element == "name") { + unit.name = text; + } else if (element == "plural") { + unit.name_plural = text; + } else if (element == "abbr") { + unit.abbr = text; + } else if (element == "factor") { + // TODO make sure we use the right conversion + unit.factor = g_ascii_strtod(text.c_str(), NULL); + } else if (element == "description") { + unit.description = text; } +} - // write out header - // foreach item in _unit_map, sorted alphabetically by type and then unit name - // sprintf a line - // name - // name_plural - // abbr - // type - // factor - // PRI - if listed in primary unit table, 'Y', else 'N' - // description - // write line to the file - - // close file - if (fclose(f) != 0) { - g_warning("Error closing units file '%s': %s\n", - filename.c_str(), strerror(errno)); - return false; +void UnitParser::on_end_element(Ctx &ctx, Glib::ustring const &name) +{ + if (name == "unit" && !skip) { + tbl->addUnit(unit, primary); } +} - return true; +Quantity::Quantity(double q, Unit const *u) + : unit(u) + , quantity(q) +{ +} +Quantity::Quantity(double q, Glib::ustring const &u) + : unit(unit_table.getUnit(u.c_str())) + , quantity(q) +{ +} +Quantity::Quantity(double q, char const *u) + : unit(unit_table.getUnit(u)) + , quantity(q) +{ } +bool Quantity::compatibleWith(Unit const *u) const +{ + return unit->compatibleWith(u); +} +bool Quantity::compatibleWith(Glib::ustring const &u) const +{ + return compatibleWith(u.c_str()); +} +bool Quantity::compatibleWith(char const *u) const +{ + return compatibleWith(unit_table.getUnit(u)); +} -void UnitsSAXHandler::_startElement(xmlChar const *name, xmlChar const **attrs) +double Quantity::value(Unit const *u) const { - if (streq("unit", (char const *)name)) { - // reset for next use - unit.name.clear(); - unit.name_plural.clear(); - unit.abbr.clear(); - unit.description.clear(); - unit.type = UNIT_TYPE_DIMENSIONLESS; - unit.factor = 1.0; - primary = false; - skip = false; + return convert(quantity, unit, u); +} +double Quantity::value(Glib::ustring const &u) const +{ + return value(u.c_str()); +} +double Quantity::value(char const *u) const +{ + return value(unit_table.getUnit(u)); +} - for ( int i = 0; attrs[i]; i += 2 ) { - char const *const key = (char const *)attrs[i]; - if (streq("type", key)) { - char const *type = (char const*)attrs[i+1]; - if (streq(type, "DIMENSIONLESS")) { - unit.type = UNIT_TYPE_DIMENSIONLESS; - } else if (streq(type, "LINEAR")) { - unit.type = UNIT_TYPE_LINEAR; - } else if (streq(type, "RADIAL")) { - unit.type = UNIT_TYPE_RADIAL; - } else if (streq(type, "FONT_HEIGHT")) { - unit.type = UNIT_TYPE_FONT_HEIGHT; - } else { - g_warning("Skipping unknown unit type '%s' for %s.\n", type, name); - skip = true; - } - } else if (streq("pri", key)) { - primary = attrs[i+1][0] == 'y' || attrs[i+1][0] == 'Y'; - } - } +Glib::ustring Quantity::string(Unit const *u) const { + return Glib::ustring::format(std::fixed, std::setprecision(2), value(u)) + " " + unit->abbr; +} +Glib::ustring Quantity::string(Glib::ustring const &u) const { + return string(unit_table.getUnit(u.c_str())); +} +Glib::ustring Quantity::string() const { + return string(unit); +} + +double Quantity::convert(double from_dist, Unit const *from, Unit const *to) +{ + // Percentage + if (to->type == UNIT_TYPE_DIMENSIONLESS) { + return from_dist * to->factor; + } + + // Incompatible units + if (from->type != to->type) { + return -1; } + + // Compatible units + return from_dist * from->factor / to->factor; +} +double Quantity::convert(double from_dist, Glib::ustring const &from, Unit const *to) +{ + return convert(from_dist, unit_table.getUnit(from.c_str()), to); +} +double Quantity::convert(double from_dist, Unit const *from, Glib::ustring const &to) +{ + return convert(from_dist, from, unit_table.getUnit(to.c_str())); +} +double Quantity::convert(double from_dist, Glib::ustring const &from, Glib::ustring const &to) +{ + return convert(from_dist, unit_table.getUnit(from.c_str()), unit_table.getUnit(to.c_str())); +} +double Quantity::convert(double from_dist, char const *from, char const *to) +{ + return convert(from_dist, unit_table.getUnit(from), unit_table.getUnit(to)); } -void UnitsSAXHandler::_endElement(xmlChar const *xname) +bool Quantity::operator<(Quantity const &other) const { - char const *const name = (char const *) xname; - if (streq("name", name)) { - unit.name = data; - } else if (streq("plural", name)) { - unit.name_plural = data; - } else if (streq("abbr", name)) { - unit.abbr = data; - } else if (streq("factor", name)) { - // TODO make sure we use the right conversion - unit.factor = atol(data.c_str()); - } else if (streq("description", name)) { - unit.description = data; - } else if (streq("unit", name)) { - if (!skip) { - tbl->addUnit(unit, primary); - } + if (unit->type != other.unit->type) { + g_warning("Incompatible units"); + return false; } + return quantity < other.value(unit); +} +bool Quantity::operator==(Quantity const &other) const +{ + return (*unit == *other.unit) && (quantity == other.quantity); } } // namespace Util } // namespace Inkscape - /* Local Variables: mode:c++ diff --git a/src/util/units.h b/src/util/units.h index b22bdb1f2..e1addaa24 100644 --- a/src/util/units.h +++ b/src/util/units.h @@ -1,22 +1,23 @@ /* -This is a rough draft of a global 'units' thingee, to allow dialogs and -the ruler to share info about unit systems... Dunno if this is the -right kind of object though, so we may have to redo this or shift things -around later when it becomes clearer what we need. - -This object is used for defining different unit systems. - -This is intended to eventually replace inkscape/helper/units.*. - -Need to review the Units support that's in Gtkmm already... - -*/ + * Inkscape Units + * These classes are used for defining different unit systems. + * + * Authors: + * Matthew Petroff <matthew@mpetroff.net> + * + * Copyright (C) 2013 Matthew Petroff + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ #ifndef INKSCAPE_UTIL_UNITS_H #define INKSCAPE_UTIL_UNITS_H #include <map> +#include <boost/operators.hpp> #include <glibmm/ustring.h> +#include "svg/svg-length.h" +#include "unordered-containers.h" namespace Inkscape { namespace Util { @@ -34,57 +35,168 @@ enum UnitType { const char DEG[] = "°"; -class Unit { - public: +class Unit + : boost::equality_comparable<Unit> +{ +public: + Unit(); + Unit(UnitType type, + double factor, + Glib::ustring const &name, + Glib::ustring const &name_plural, + Glib::ustring const &abbr, + Glib::ustring const &description); + + void clear(); + + bool isAbsolute() const { return type != UNIT_TYPE_DIMENSIONLESS; } + + /** + * Returns the suggested precision to use for displaying numbers + * of this unit. + */ + int defaultDigits() const; + + /** Checks if a unit is compatible with the specified unit. */ + bool compatibleWith(Unit const *u) const; + bool compatibleWith(Glib::ustring const &) const; + bool compatibleWith(char const *) const; + + UnitType type; + double factor; Glib::ustring name; Glib::ustring name_plural; Glib::ustring abbr; Glib::ustring description; + + /** Check if units are equal. */ + bool operator==(Unit const &other) const; + + /** Get SVG unit code. */ + int svgUnit() const; +}; - UnitType type; - - double factor; - - bool isAbsolute() const { return type != UNIT_TYPE_DIMENSIONLESS; } - int defaultDigits() const; +class Quantity + : boost::totally_ordered<Quantity> +{ +public: + Unit const *unit; + double quantity; + + /** Initialize a quantity. */ + Quantity(double q, Unit const *u); + Quantity(double q, Glib::ustring const &u); + Quantity(double q, char const *u); + + /** Checks if a quantity is compatible with the specified unit. */ + bool compatibleWith(Unit const *u) const; + bool compatibleWith(Glib::ustring const &u) const; + bool compatibleWith(char const *u) const; + + /** Return the quantity's value in the specified unit. */ + double value(Unit const *u) const; + double value(Glib::ustring const &u) const; + double value(char const *u) const; + + /** Return a printable string of the value in the specified unit. */ + Glib::ustring string(Unit const *u) const; + Glib::ustring string(Glib::ustring const &u) const; + Glib::ustring string() const; + + /** Convert distances. + no NULL check is performed on the passed pointers to Unit objects! */ + static double convert(double from_dist, Unit const *from, Unit const *to); + static double convert(double from_dist, Glib::ustring const &from, Unit const *to); + static double convert(double from_dist, Unit const *from, Glib::ustring const &to); + static double convert(double from_dist, Glib::ustring const &from, Glib::ustring const &to); + static double convert(double from_dist, char const *from, char const *to); + + /** Comparison operators. */ + bool operator<(Quantity const &other) const; + bool operator==(Quantity const &other) const; }; class UnitTable { - public: +public: + /** + * Initializes the unit tables and identifies the primary unit types. + * + * The primary unit's conversion factor is required to be 1.00 + */ UnitTable(); virtual ~UnitTable(); - typedef std::map<Glib::ustring, Unit*> UnitMap; + typedef INK_UNORDERED_MAP<Glib::ustring, Unit> UnitMap; + typedef INK_UNORDERED_MAP<unsigned, Unit*> UnitCodeMap; + + /** Add a new unit to the table */ + void addUnit(Unit const &u, bool primary); - void addUnit(Unit const& u, bool primary); - Unit getUnit(Glib::ustring const& name) const; - bool deleteUnit(Unit const& u); + /** Retrieve a given unit based on its string identifier */ + Unit const *getUnit(Glib::ustring const &name) const; + Unit const *getUnit(char const *name) const; + + /** Retrieve a given unit based on its SVGLength unit */ + Unit const *getUnit(SVGLength::Unit u) const; + + /** Retrieve a quantity based on its string identifier */ + Quantity parseQuantity(Glib::ustring const &q) const; + + /** Remove a unit definition from the given unit type table * / + * DISABLED, unsafe with the current passing around pointers to Unit objects in this table */ + //bool deleteUnit(Unit const &u); + + /** Returns true if the given string 'name' is a valid unit in the table */ bool hasUnit(Glib::ustring const &name) const; - UnitTable::UnitMap units(UnitType type) const; + /** Provides an iteratable list of items in the given unit table */ + UnitMap units(UnitType type) const; + /** Returns the default unit abbr for the given type */ Glib::ustring primary(UnitType type) const; double getScale() const; + void setScale(); - bool load(Glib::ustring const &filename); - bool loadText(Glib::ustring const &filename); - bool save(Glib::ustring const &filename); + /** Load units from an XML file. + * + * Loads and merges the contents of the given file into the UnitTable, + * possibly overwriting existing unit definitions. + * + * @param filename file to be loaded + */ + bool load(std::string const &filename); + + /** Saves the current UnitTable to the given file. */ + bool save(std::string const &filename); - protected: - UnitTable::UnitMap _unit_map; +protected: + UnitCodeMap _unit_map; Glib::ustring _primary_unit[UNIT_TYPE_QTY]; double _linear_scale; + static Unit _empty_unit; - private: - UnitTable(UnitTable const& t); - UnitTable operator=(UnitTable const& t); +private: + UnitTable(UnitTable const &t); + UnitTable operator=(UnitTable const &t); }; +extern UnitTable unit_table; + } // namespace Util } // namespace Inkscape #endif // define INKSCAPE_UTIL_UNITS_H +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/util/unordered-containers.h b/src/util/unordered-containers.h index 6f738f0ce..70d36c4dc 100644 --- a/src/util/unordered-containers.h +++ b/src/util/unordered-containers.h @@ -16,6 +16,8 @@ # include "config.h" #endif +#include <glibmm/ustring.h> + #ifndef DOXYGEN_SHOULD_SKIP_THIS #if defined(HAVE_TR1_UNORDERED_SET) @@ -25,6 +27,17 @@ # define INK_UNORDERED_MAP std::tr1::unordered_map # define INK_HASH std::tr1::hash +namespace std { +namespace tr1 { +template <> +struct hash<Glib::ustring> : public std::unary_function<Glib::ustring, std::size_t> { + std::size_t operator()(Glib::ustring const &s) const { + return hash<std::string>()(s.raw()); + } +}; +} // namespace tr1 +} // namespace std + #elif defined(HAVE_BOOST_UNORDERED_SET) # include <boost/unordered_set.hpp> # include <boost/unordered_map.hpp> @@ -32,6 +45,15 @@ # define INK_UNORDERED_MAP boost::unordered_map # define INK_HASH boost::hash +namespace boost { +template <> +struct hash<Glib::ustring> : public std::unary_function<Glib::ustring, std::size_t> { + std::size_t operator()(Glib::ustring const &s) const { + return hash<std::string>()(s.raw()); + } +}; +} // namespace boost + #elif defined(HAVE_EXT_HASH_SET) # include <functional> @@ -54,6 +76,13 @@ struct hash<T *> : public std::unary_function<T *, std::size_t> { return x + (x >> 3); } }; + +template <> +struct hash<Glib::ustring> : public std::unary_function<Glib::ustring, std::size_t> { + std::size_t operator()(Glib::ustring const &s) const { + return hash<std::string>()(s.raw()); + } +}; } // namespace __gnu_cxx #endif |
