summaryrefslogtreecommitdiffstats
path: root/src/svg/svg-length.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/svg/svg-length.cpp')
-rw-r--r--src/svg/svg-length.cpp596
1 files changed, 596 insertions, 0 deletions
diff --git a/src/svg/svg-length.cpp b/src/svg/svg-length.cpp
new file mode 100644
index 0000000..c3baba7
--- /dev/null
+++ b/src/svg/svg-length.cpp
@@ -0,0 +1,596 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * SVG data parser
+ *//*
+ * Authors: see git history
+
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <cmath>
+#include <cstring>
+#include <string>
+#include <glib.h>
+#include <iostream>
+#include <vector>
+
+#include "svg.h"
+#include "stringstream.h"
+#include "util/units.h"
+
+using std::pow;
+
+static unsigned sp_svg_length_read_lff(gchar const *str, SVGLength::Unit *unit, float *val, float *computed, char **next);
+
+#ifndef MAX
+# define MAX(a,b) ((a < b) ? (b) : (a))
+#endif
+
+unsigned int sp_svg_number_read_f(gchar const *str, float *val)
+{
+ if (!str) {
+ return 0;
+ }
+
+ char *e;
+ float const v = g_ascii_strtod(str, &e);
+ if ((gchar const *) e == str) {
+ return 0;
+ }
+
+ *val = v;
+ return 1;
+}
+
+unsigned int sp_svg_number_read_d(gchar const *str, double *val)
+{
+ if (!str) {
+ return 0;
+ }
+
+ char *e;
+ double const v = g_ascii_strtod(str, &e);
+ if ((gchar const *) e == str) {
+ return 0;
+ }
+
+ *val = v;
+ return 1;
+}
+
+static std::string sp_svg_number_write_d( double val, unsigned int tprec, unsigned int fprec)
+{
+
+ std::string buf;
+ /* Process sign */
+ int i = 0;
+ if (val < 0.0) {
+ buf.append("-");
+ val = fabs(val);
+ }
+
+ /* Determine number of integral digits */
+ int idigits = 0;
+ if (val >= 1.0) {
+ idigits = (int) floor(log10(val)) + 1;
+ }
+
+ /* Determine the actual number of fractional digits */
+ fprec = MAX(static_cast<int>(fprec), static_cast<int>(tprec) - idigits);
+ /* Round value */
+ val += 0.5 / pow(10.0, fprec);
+ /* Extract integral and fractional parts */
+ double dival = floor(val);
+ double fval = val - dival;
+ /* Write integra */
+ if (idigits > (int)tprec) {
+ buf.append(std::to_string((unsigned int)floor(dival/pow(10.0, idigits-tprec) + .5)));
+ for(unsigned int j=0; j<(unsigned int)idigits-tprec; j++) {
+ buf.append("0");
+ }
+ i += idigits-tprec;
+ } else {
+ buf.append(std::to_string((unsigned int)dival));
+ }
+
+ if (fprec > 0 && fval > 0.0) {
+ std::string s(".");
+ do {
+ fval *= 10.0;
+ dival = floor(fval);
+ fval -= dival;
+ int const int_dival = (int) dival;
+ s.append(std::to_string(int_dival));
+ if(int_dival != 0){
+ buf.append(s);
+ s="";
+ }
+ fprec -= 1;
+ } while(fprec > 0 && fval > 0.0);
+ }
+ return buf;
+}
+
+std::string sp_svg_number_write_de(double val, unsigned int tprec, int min_exp)
+{
+ std::string buf;
+ int eval = (int)floor(log10(fabs(val)));
+ if (val == 0.0 || eval < min_exp) {
+ buf.append("0");
+ return buf;
+ }
+ unsigned int maxnumdigitsWithoutExp = // This doesn't include the sign because it is included in either representation
+ eval<0?tprec+(unsigned int)-eval+1:
+ eval+1<(int)tprec?tprec+1:
+ (unsigned int)eval+1;
+ unsigned int maxnumdigitsWithExp = tprec + ( eval<0 ? 4 : 3 ); // It's not necessary to take larger exponents into account, because then maxnumdigitsWithoutExp is DEFINITELY larger
+ if (maxnumdigitsWithoutExp <= maxnumdigitsWithExp) {
+ buf.append(sp_svg_number_write_d(val, tprec, 0));
+ } else {
+ val = eval < 0 ? val * pow(10.0, -eval) : val / pow(10.0, eval);
+ buf.append(sp_svg_number_write_d(val, tprec, 0));
+ buf.append("e");
+ buf.append(std::to_string(eval));
+ }
+ return buf;
+
+}
+
+SVGLength::SVGLength()
+ : _set(false)
+ , unit(NONE)
+ , value(0)
+ , computed(0)
+{
+}
+
+/* Length */
+
+bool SVGLength::read(gchar const *str)
+{
+ if (!str) {
+ return false;
+ }
+
+ SVGLength::Unit u;
+ float v;
+ float c;
+ if (!sp_svg_length_read_lff(str, &u, &v, &c, nullptr)) {
+ return false;
+ }
+
+ if (!std::isfinite(v)) {
+ return false;
+ }
+
+ _set = true;
+ unit = u;
+ value = v;
+ computed = c;
+
+ return true;
+}
+
+bool SVGLength::readAbsolute(gchar const *str)
+{
+ if (!str) {
+ return false;
+ }
+
+ SVGLength::Unit u;
+ float v;
+ float c;
+ if (!sp_svg_length_read_lff(str, &u, &v, &c, nullptr)) {
+ return false;
+ }
+
+ if (svg_length_absolute_unit(u) == false) {
+ return false;
+ }
+
+ _set = true;
+ unit = u;
+ value = v;
+ computed = c;
+
+ return true;
+}
+
+/**
+ * Returns the unit used as a string.
+ *
+ * @returns unit string
+ */
+std::string SVGLength::getUnit() const
+{
+ return sp_svg_length_get_css_units(unit);
+}
+
+/**
+ * Is this length an absolute value (uses an absolute unit).
+ *
+ * @returns true if unit is not NONE and not a relative unit (percent etc)
+ */
+bool SVGLength::isAbsolute()
+{
+ return unit && svg_length_absolute_unit(unit);
+}
+
+unsigned int sp_svg_length_read_computed_absolute(gchar const *str, float *length)
+{
+ if (!str) {
+ return 0;
+ }
+
+ SVGLength::Unit unit;
+ float computed;
+ if (!sp_svg_length_read_lff(str, &unit, nullptr, &computed, nullptr)) {
+ // failed to read
+ return 0;
+ }
+
+ if (svg_length_absolute_unit(unit) == false) {
+ return 0;
+ }
+
+ *length = computed;
+
+ return 1;
+}
+
+std::vector<SVGLength> sp_svg_length_list_read(gchar const *str)
+{
+ if (!str) {
+ return std::vector<SVGLength>();
+ }
+
+ SVGLength::Unit unit;
+ float value;
+ float computed;
+ char *next = (char *) str;
+ std::vector<SVGLength> list;
+
+ while (sp_svg_length_read_lff(next, &unit, &value, &computed, &next)) {
+
+ SVGLength length;
+ length.set(unit, value, computed);
+ list.push_back(length);
+
+ while (next && *next &&
+ (*next == ',' || *next == ' ' || *next == '\n' || *next == '\r' || *next == '\t')) {
+ // the list can be comma- or space-separated, but we will be generous and accept
+ // a mix, including newlines and tabs
+ next++;
+ }
+
+ if (!next || !*next) {
+ break;
+ }
+ }
+
+ return list;
+}
+
+
+#define UVAL(a,b) (((unsigned int) (a) << 8) | (unsigned int) (b))
+
+static unsigned sp_svg_length_read_lff(gchar const *str, SVGLength::Unit *unit, float *val, float *computed, char **next)
+{
+/* note: this function is sometimes fed a string with several consecutive numbers, e.g. by sp_svg_length_list_read.
+So after the number, the string does not necessarily have a \0 or a unit, it might also contain a space or comma and then the next number!
+*/
+
+ if (!str) {
+ return 0;
+ }
+
+ gchar const *e;
+ float const v = g_ascii_strtod(str, (char **) &e);
+ if (e == str) {
+ return 0;
+ }
+
+ if (!e[0]) {
+ /* Unitless */
+ if (unit) {
+ *unit = SVGLength::NONE;
+ }
+ if (val) {
+ *val = v;
+ }
+ if (computed) {
+ *computed = v;
+ }
+ if (next) {
+ *next = nullptr; // no more values
+ }
+ return 1;
+ } else if (!g_ascii_isalnum(e[0])) {
+ /* Unitless or percent */
+ if (e[0] == '%') {
+ /* Percent */
+ if (e[1] && g_ascii_isalnum(e[1])) {
+ return 0;
+ }
+ if (unit) {
+ *unit = SVGLength::PERCENT;
+ }
+ if (val) {
+ *val = v * 0.01;
+ }
+ if (computed) {
+ *computed = v * 0.01;
+ }
+ if (next) {
+ *next = (char *) e + 1;
+ }
+ return 1;
+ } else if (g_ascii_isspace(e[0]) && e[1] && g_ascii_isalpha(e[1])) {
+ return 0; // spaces between value and unit are not allowed
+ } else {
+ /* Unitless */
+ if (unit) {
+ *unit = SVGLength::NONE;
+ }
+ if (val) {
+ *val = v;
+ }
+ if (computed) {
+ *computed = v;
+ }
+ if (next) {
+ *next = (char *) e;
+ }
+ return 1;
+ }
+ } else if (e[1] && !g_ascii_isalnum(e[2])) {
+ /* TODO: Allow the number of px per inch to vary (document preferences, X server
+ * or whatever). E.g. don't fill in computed here, do it at the same time as
+ * percentage units are done. */
+ unsigned int const uval = UVAL(e[0], e[1]);
+ switch (uval) {
+ case UVAL('p','x'):
+ if (unit) {
+ *unit = SVGLength::PX;
+ }
+ if (computed) {
+ *computed = v;
+ }
+ break;
+ case UVAL('p','t'):
+ if (unit) {
+ *unit = SVGLength::PT;
+ }
+ if (computed) {
+ *computed = Inkscape::Util::Quantity::convert(v, "pt", "px");
+ }
+ break;
+ case UVAL('p','c'):
+ if (unit) {
+ *unit = SVGLength::PC;
+ }
+ if (computed) {
+ *computed = Inkscape::Util::Quantity::convert(v, "pc", "px");
+ }
+ break;
+ case UVAL('m','m'):
+ if (unit) {
+ *unit = SVGLength::MM;
+ }
+ if (computed) {
+ *computed = Inkscape::Util::Quantity::convert(v, "mm", "px");
+ }
+ break;
+ case UVAL('c','m'):
+ if (unit) {
+ *unit = SVGLength::CM;
+ }
+ if (computed) {
+ *computed = Inkscape::Util::Quantity::convert(v, "cm", "px");
+ }
+ break;
+ case UVAL('i','n'):
+ if (unit) {
+ *unit = SVGLength::INCH;
+ }
+ if (computed) {
+ *computed = Inkscape::Util::Quantity::convert(v, "in", "px");
+ }
+ break;
+ case UVAL('e','m'):
+ if (unit) {
+ *unit = SVGLength::EM;
+ }
+ break;
+ case UVAL('e','x'):
+ if (unit) {
+ *unit = SVGLength::EX;
+ }
+ break;
+ default:
+ /* Invalid */
+ return 0;
+ break;
+ }
+ if (val) {
+ *val = v;
+ }
+ if (next) {
+ *next = (char *) e + 2;
+ }
+ return 1;
+ }
+
+ /* Invalid */
+ return 0;
+}
+
+unsigned int sp_svg_length_read_ldd(gchar const *str, SVGLength::Unit *unit, double *value, double *computed)
+{
+ float a;
+ float b;
+ unsigned int r = sp_svg_length_read_lff(str, unit, &a, &b, nullptr);
+ if (r) {
+ if (value) {
+ *value = a;
+ }
+ if (computed) {
+ *computed = b;
+ }
+ }
+ return r;
+}
+
+std::string SVGLength::write() const
+{
+ return sp_svg_length_write_with_units(*this);
+}
+
+void SVGLength::set(SVGLength::Unit u, float v)
+{
+ _set = true;
+ unit = u;
+ Glib::ustring hack("px");
+ switch( unit ) {
+ case NONE:
+ case PX:
+ case EM:
+ case EX:
+ case PERCENT:
+ break;
+ case PT:
+ hack = "pt";
+ break;
+ case PC:
+ hack = "pc";
+ break;
+ case MM:
+ hack = "pt";
+ break;
+ case CM:
+ hack = "pt";
+ break;
+ case INCH:
+ hack = "pt";
+ break;
+ default:
+ break;
+ }
+ value = v;
+ computed = Inkscape::Util::Quantity::convert(v, hack, "px");
+}
+
+void SVGLength::set(SVGLength::Unit u, float v, float c)
+{
+ _set = true;
+ unit = u;
+ value = v;
+ computed = c;
+}
+
+void SVGLength::unset(SVGLength::Unit u, float v, float c)
+{
+ _set = false;
+ unit = u;
+ value = v;
+ computed = c;
+}
+
+void SVGLength::scale(double scale)
+{
+ value *= scale;
+ computed *= scale;
+}
+
+void SVGLength::update(double em, double ex, double scale)
+{
+ if (unit == EM) {
+ computed = value * em;
+ } else if (unit == EX) {
+ computed = value * ex;
+ } else if (unit == PERCENT) {
+ computed = value * scale;
+ }
+}
+
+double sp_svg_read_percentage(char const *str, double def)
+{
+ if (str == nullptr) {
+ return def;
+ }
+
+ char *u;
+ double v = g_ascii_strtod(str, &u);
+ while (isspace(*u)) {
+ if (*u == '\0') {
+ return v;
+ }
+ u++;
+ }
+ if (*u == '%') {
+ v /= 100.0;
+ }
+
+ return v;
+}
+
+gchar const *sp_svg_length_get_css_units(SVGLength::Unit unit)
+{
+ switch (unit) {
+ case SVGLength::NONE: return "";
+ case SVGLength::PX: return "";
+ case SVGLength::PT: return "pt";
+ case SVGLength::PC: return "pc";
+ case SVGLength::MM: return "mm";
+ case SVGLength::CM: return "cm";
+ case SVGLength::INCH: return "in";
+ case SVGLength::EM: return "em";
+ case SVGLength::EX: return "ex";
+ case SVGLength::PERCENT: return "%";
+ }
+ return "";
+}
+
+bool svg_length_absolute_unit(SVGLength::Unit u)
+{
+ return (u != SVGLength::EM && u != SVGLength::EX && u != SVGLength::PERCENT);
+}
+
+/**
+ * N.B.\ This routine will sometimes return strings with `e' notation, so is unsuitable for CSS
+ * lengths (which don't allow scientific `e' notation).
+ */
+std::string sp_svg_length_write_with_units(SVGLength const &length)
+{
+ Inkscape::SVGOStringStream os;
+ if (length.unit == SVGLength::PERCENT) {
+ os << 100*length.value << sp_svg_length_get_css_units(length.unit);
+ } else {
+ os << length.value << sp_svg_length_get_css_units(length.unit);
+ }
+ return os.str();
+}
+
+
+void SVGLength::readOrUnset(gchar const *str, Unit u, float v, float c)
+{
+ if (!read(str)) {
+ unset(u, v, c);
+ }
+}
+
+
+/*
+ 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 :