summaryrefslogtreecommitdiffstats
path: root/src/util
diff options
context:
space:
mode:
authorSebastian Wüst <sebi@timewaster.de>2013-10-20 15:32:08 +0000
committerSebastian Wüst <sebi@timewaster.de>2013-10-20 15:32:08 +0000
commit82908f949129e1fcbf62002799ee7b1b77986eed (patch)
treec02098dd7720cdf424f2793ecd3ddac2ea86b969 /src/util
parentchanged text (diff)
parentFix build errors with clang 3.3 and c++11 enabled. (diff)
downloadinkscape-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.cpp668
-rw-r--r--src/util/expression-evaluator.h112
-rw-r--r--src/util/units.cpp654
-rw-r--r--src/util/units.h180
-rw-r--r--src/util/unordered-containers.h29
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