Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
svg-length.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
/*
5 * Authors: see git history
6
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 * bulia byak <buliabyak@users.sf.net>
9 *
10 * Copyright (C) 2018 Authors
11 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
12 */
13
14#include <cmath>
15#include <cstring>
16#include <string>
17#include <glib.h>
18#include <iostream>
19#include <vector>
20
21#include "svg.h"
22#include "stringstream.h"
23#include "util/units.h"
25
26using std::pow;
27
28static unsigned sp_svg_length_read_lff(gchar const *str, SVGLength::Unit *unit, float *val, float *computed, char **next);
29
30#ifndef MAX
31# define MAX(a,b) ((a < b) ? (b) : (a))
32#endif
33
34unsigned int sp_svg_number_read_f(gchar const *str, float *val)
35{
36 if (!str) {
37 return 0;
38 }
39
40 char *e;
41 float const v = g_ascii_strtod(str, &e);
42 if ((gchar const *) e == str) {
43 return 0;
44 }
45
46 *val = v;
47 return 1;
48}
49
50unsigned int sp_svg_number_read_d(gchar const *str, double *val)
51{
52 if (!str) {
53 return 0;
54 }
55
56 char *e;
57 double const v = g_ascii_strtod(str, &e);
58 if ((gchar const *) e == str) {
59 return 0;
60 }
61
62 *val = v;
63 return 1;
64}
65
66static std::string sp_svg_number_write_d( double val, unsigned int tprec, unsigned int fprec)
67{
68
69 std::string buf;
70 /* Process sign */
71 if (val < 0.0) {
72 buf.append("-");
73 val = fabs(val);
74 }
75
76 /* Determine number of integral digits */
77 int idigits = 0;
78 if (val >= 1.0) {
79 idigits = (int) floor(log10(val)) + 1;
80 }
81
82 /* Determine the actual number of fractional digits */
83 fprec = MAX(static_cast<int>(fprec), static_cast<int>(tprec) - idigits);
84 /* Round value */
85 val += 0.5 / pow(10.0, fprec);
86 /* Extract integral and fractional parts */
87 double dival = floor(val);
88 double fval = val - dival;
89 /* Write integra */
90 if (idigits > (int)tprec) {
91 buf.append(std::to_string((unsigned int)floor(dival/pow(10.0, idigits-tprec) + .5)));
92 for(unsigned int j=0; j<(unsigned int)idigits-tprec; j++) {
93 buf.append("0");
94 }
95 } else {
96 buf.append(std::to_string((unsigned int)dival));
97 }
98
99 if (fprec > 0 && fval > 0.0) {
100 std::string s(".");
101 do {
102 fval *= 10.0;
103 dival = floor(fval);
104 fval -= dival;
105 int const int_dival = (int) dival;
106 s.append(std::to_string(int_dival));
107 if(int_dival != 0){
108 buf.append(s);
109 s="";
110 }
111 fprec -= 1;
112 } while(fprec > 0 && fval > 0.0);
113 }
114 return buf;
115}
116
117std::string sp_svg_number_write_de(double val, unsigned int tprec, int min_exp)
118{
119 std::string buf;
120 int eval = (int)floor(log10(fabs(val)));
121 if (val == 0.0 || eval < min_exp) {
122 buf.append("0");
123 return buf;
124 }
125 unsigned int maxnumdigitsWithoutExp = // This doesn't include the sign because it is included in either representation
126 eval<0?tprec+(unsigned int)-eval+1:
127 eval+1<(int)tprec?tprec+1:
128 (unsigned int)eval+1;
129 unsigned int maxnumdigitsWithExp = tprec + ( eval<0 ? 4 : 3 ); // It's not necessary to take larger exponents into account, because then maxnumdigitsWithoutExp is DEFINITELY larger
130 if (maxnumdigitsWithoutExp <= maxnumdigitsWithExp) {
131 buf.append(sp_svg_number_write_d(val, tprec, 0));
132 } else {
133 val = eval < 0 ? val * pow(10.0, -eval) : val / pow(10.0, eval);
134 buf.append(sp_svg_number_write_d(val, tprec, 0));
135 buf.append("e");
136 buf.append(std::to_string(eval));
137 }
138 return buf;
139
140}
141
143 : _set(false)
144 , unit(NONE)
145 , value(0)
146 , computed(0)
147{
148}
149
150/* Length */
151
152bool SVGLength::read(gchar const *str)
153{
154 if (!str) {
155 return false;
156 }
157
159 float v;
160 float c;
161 if (!sp_svg_length_read_lff(str, &u, &v, &c, nullptr)) {
162 return false;
163 }
164
165 if (!std::isfinite(v)) {
166 return false;
167 }
168
169 _set = true;
170 unit = u;
171 value = v;
172 computed = c;
173
174 return true;
175}
176
177bool SVGLength::readAbsolute(gchar const *str)
178{
179 if (!str) {
180 return false;
181 }
182
184 float v;
185 float c;
186 if (!sp_svg_length_read_lff(str, &u, &v, &c, nullptr)) {
187 return false;
188 }
189
190 if (svg_length_absolute_unit(u) == false) {
191 return false;
192 }
193
194 _set = true;
195 unit = u;
196 value = v;
197 computed = c;
198
199 return true;
200}
201
207std::string SVGLength::getUnit() const
208{
210}
211
221
222unsigned int sp_svg_length_read_computed_absolute(gchar const *str, float *length)
223{
224 if (!str) {
225 return 0;
226 }
227
228 SVGLength::Unit unit;
229 float computed;
230 if (!sp_svg_length_read_lff(str, &unit, nullptr, &computed, nullptr)) {
231 // failed to read
232 return 0;
233 }
234
235 if (svg_length_absolute_unit(unit) == false) {
236 return 0;
237 }
238
239 *length = computed;
240
241 return 1;
242}
243
244std::vector<SVGLength> sp_svg_length_list_read(gchar const *str)
245{
246 if (!str) {
247 return std::vector<SVGLength>();
248 }
249
250 SVGLength::Unit unit;
251 float value;
252 float computed;
253 char *next = (char *) str;
254 std::vector<SVGLength> list;
255
256 while (sp_svg_length_read_lff(next, &unit, &value, &computed, &next)) {
257
258 SVGLength length;
259 length.set(unit, value, computed);
260 list.push_back(length);
261
262 while (next && *next &&
263 (*next == ',' || *next == ' ' || *next == '\n' || *next == '\r' || *next == '\t')) {
264 // the list can be comma- or space-separated, but we will be generous and accept
265 // a mix, including newlines and tabs
266 next++;
267 }
268
269 if (!next || !*next) {
270 break;
271 }
272 }
273
274 return list;
275}
276
277
278#define UVAL(a,b) (((unsigned int) (a) << 8) | (unsigned int) (b))
279
280static unsigned sp_svg_length_read_lff(gchar const *str, SVGLength::Unit *unit, float *val, float *computed, char **next)
281{
282/* note: this function is sometimes fed a string with several consecutive numbers, e.g. by sp_svg_length_list_read.
283So 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!
284*/
285
286 if (!str) {
287 return 0;
288 }
289
290 gchar const *e;
291 float const v = g_ascii_strtod(str, (char **) &e);
292 if (e == str) {
293 return 0;
294 }
295
296 if (!e[0]) {
297 /* Unitless */
298 if (unit) {
299 *unit = SVGLength::NONE;
300 }
301 if (val) {
302 *val = v;
303 }
304 if (computed) {
305 *computed = v;
306 }
307 if (next) {
308 *next = nullptr; // no more values
309 }
310 return 1;
311 } else if (!g_ascii_isalnum(e[0])) {
312 /* Unitless or percent */
313 if (e[0] == '%') {
314 /* Percent */
315 if (e[1] && g_ascii_isalnum(e[1])) {
316 return 0;
317 }
318 if (unit) {
319 *unit = SVGLength::PERCENT;
320 }
321 if (val) {
322 *val = v * 0.01;
323 }
324 if (computed) {
325 *computed = v * 0.01;
326 }
327 if (next) {
328 *next = (char *) e + 1;
329 }
330 return 1;
331 } else if (g_ascii_isspace(e[0]) && e[1] && g_ascii_isalpha(e[1])) {
332 return 0; // spaces between value and unit are not allowed
333 } else {
334 /* Unitless */
335 if (unit) {
336 *unit = SVGLength::NONE;
337 }
338 if (val) {
339 *val = v;
340 }
341 if (computed) {
342 *computed = v;
343 }
344 if (next) {
345 *next = (char *) e;
346 }
347 return 1;
348 }
349 } else if (e[1] && !g_ascii_isalnum(e[2])) {
350 /* TODO: Allow the number of px per inch to vary (document preferences, X server
351 * or whatever). E.g. don't fill in computed here, do it at the same time as
352 * percentage units are done. */
353 unsigned int const uval = UVAL(e[0], e[1]);
354 switch (uval) {
355 case UVAL('p','x'):
356 if (unit) {
357 *unit = SVGLength::PX;
358 }
359 if (computed) {
360 *computed = v;
361 }
362 break;
363 case UVAL('p','t'):
364 if (unit) {
365 *unit = SVGLength::PT;
366 }
367 if (computed) {
368 *computed = Inkscape::Util::Quantity::convert(v, "pt", "px");
369 }
370 break;
371 case UVAL('p','c'):
372 if (unit) {
373 *unit = SVGLength::PC;
374 }
375 if (computed) {
376 *computed = Inkscape::Util::Quantity::convert(v, "pc", "px");
377 }
378 break;
379 case UVAL('m','m'):
380 if (unit) {
381 *unit = SVGLength::MM;
382 }
383 if (computed) {
384 *computed = Inkscape::Util::Quantity::convert(v, "mm", "px");
385 }
386 break;
387 case UVAL('c','m'):
388 if (unit) {
389 *unit = SVGLength::CM;
390 }
391 if (computed) {
392 *computed = Inkscape::Util::Quantity::convert(v, "cm", "px");
393 }
394 break;
395 case UVAL('i','n'):
396 if (unit) {
397 *unit = SVGLength::INCH;
398 }
399 if (computed) {
400 *computed = Inkscape::Util::Quantity::convert(v, "in", "px");
401 }
402 break;
403 case UVAL('e','m'):
404 if (unit) {
405 *unit = SVGLength::EM;
406 }
407 break;
408 case UVAL('e','x'):
409 if (unit) {
410 *unit = SVGLength::EX;
411 }
412 break;
413 default:
414 /* Invalid */
415 return 0;
416 break;
417 }
418 if (val) {
419 *val = v;
420 }
421 if (next) {
422 *next = (char *) e + 2;
423 }
424 return 1;
425 }
426
427 /* Invalid */
428 return 0;
429}
430
431unsigned int sp_svg_length_read_ldd(gchar const *str, SVGLength::Unit *unit, double *value, double *computed)
432{
433 float a;
434 float b;
435 unsigned int r = sp_svg_length_read_lff(str, unit, &a, &b, nullptr);
436 if (r) {
437 if (value) {
438 *value = a;
439 }
440 if (computed) {
441 *computed = b;
442 }
443 }
444 return r;
445}
446
447std::string SVGLength::write() const
448{
449 return sp_svg_length_write_with_units(*this);
450}
451
458std::string SVGLength::toString(const std::string &out_unit, double doc_scale, std::optional<unsigned int> precision, bool add_unit) const
459{
460 if (unit == SVGLength::PERCENT) {
461 return write();
462 }
463 double value = toValue(out_unit) * doc_scale;
465 if (precision) {
466 os << Inkscape::Util::format_number(value, *precision);
467 } else {
468 os << value;
469 }
470 if (add_unit)
471 os << out_unit;
472 return os.str();
473}
474
481double SVGLength::toValue(const std::string &out_unit) const
482{
483 return Inkscape::Util::Quantity::convert(computed, "px", out_unit);
484}
485
493bool SVGLength::fromString(const std::string &input, const std::string &default_unit, std::optional<double> doc_scale)
494{
495 if (!read((input + default_unit).c_str()))
496 if (!read(input.c_str()))
497 return false;
498 // Rescale real units to document, since user input is not scaled
499 if (doc_scale && unit != SVGLength::PERCENT && unit != SVGLength::NONE) {
500 value = computed;
502 scale(1 / *doc_scale);
503 }
504 return true;
505}
506
508{
509 _set = true;
510 unit = u;
511 Glib::ustring hack("px");
512 switch( unit ) {
513 case NONE:
514 case PX:
515 case EM:
516 case EX:
517 case PERCENT:
518 break;
519 case PT:
520 hack = "pt";
521 break;
522 case PC:
523 hack = "pc";
524 break;
525 case MM:
526 hack = "mm";
527 break;
528 case CM:
529 hack = "cm";
530 break;
531 case INCH:
532 hack = "in";
533 break;
534 default:
535 break;
536 }
537 value = v;
539}
540
541void SVGLength::set(SVGLength::Unit u, float v, float c)
542{
543 _set = true;
544 unit = u;
545 value = v;
546 computed = c;
547}
548
549void SVGLength::unset(SVGLength::Unit u, float v, float c)
550{
551 _set = false;
552 unit = u;
553 value = v;
554 computed = c;
555}
556
557void SVGLength::scale(double scale)
558{
559 value *= scale;
560 computed *= scale;
561}
562
563void SVGLength::update(double em, double ex, double scale)
564{
565 if (unit == EM) {
566 computed = value * em;
567 } else if (unit == EX) {
568 computed = value * ex;
569 } else if (unit == PERCENT) {
570 computed = value * scale;
571 }
572}
573
574double sp_svg_read_percentage(char const *str, double def)
575{
576 if (str == nullptr) {
577 return def;
578 }
579
580 char *u;
581 double v = g_ascii_strtod(str, &u);
582 while (isspace(*u)) {
583 if (*u == '\0') {
584 return v;
585 }
586 u++;
587 }
588 if (*u == '%') {
589 v /= 100.0;
590 }
591
592 return v;
593}
594
596{
597 switch (unit) {
598 case SVGLength::NONE: return "";
599 case SVGLength::PX: return "";
600 case SVGLength::PT: return "pt";
601 case SVGLength::PC: return "pc";
602 case SVGLength::MM: return "mm";
603 case SVGLength::CM: return "cm";
604 case SVGLength::INCH: return "in";
605 case SVGLength::EM: return "em";
606 case SVGLength::EX: return "ex";
607 case SVGLength::PERCENT: return "%";
608 }
609 return "";
610}
611
613{
614 return (u != SVGLength::EM && u != SVGLength::EX && u != SVGLength::PERCENT);
615}
616
622{
624 if (length.unit == SVGLength::PERCENT) {
625 os << 100*length.value << sp_svg_length_get_css_units(length.unit);
626 } else {
627 os << length.value << sp_svg_length_get_css_units(length.unit);
628 }
629 return os.str();
630}
631
632
633void SVGLength::readOrUnset(gchar const *str, Unit u, float v, float c)
634{
635 if (!read(str)) {
636 unset(u, v, c);
637 }
638}
639
640namespace Inkscape {
641char const *refX_named_to_percent(char const *str)
642{
643 if (str) {
644 if (g_str_equal(str, "left")) {
645 return "0%";
646 } else if (g_str_equal(str, "center")) {
647 return "50%";
648 } else if (g_str_equal(str, "right")) {
649 return "100%";
650 }
651 }
652 return str;
653}
654
655char const *refY_named_to_percent(char const *str)
656{
657 if (str) {
658 if (g_str_equal(str, "top")) {
659 return "0%";
660 } else if (g_str_equal(str, "center")) {
661 return "50%";
662 } else if (g_str_equal(str, "bottom")) {
663 return "100%";
664 }
665 }
666 return str;
667}
668} // namespace Inkscape
669
670/*
671 Local Variables:
672 mode:c++
673 c-file-style:"stroustrup"
674 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
675 indent-tabs-mode:nil
676 fill-column:99
677 End:
678*/
679// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8 :
double scale
Definition aa.cpp:228
std::string str() const
static double convert(double from_dist, Unit const *from, Unit const *to)
Convert distances.
Definition units.cpp:525
SVG length type.
Definition svg-length.h:22
double toValue(const std::string &out_unit) const
Caulate the length in a user unit.
std::string toString(const std::string &unit, double doc_scale, std::optional< unsigned int > precision={}, bool add_unit=true) const
Write out length in user unit, for the user to use.
void set(Unit u, float v)
void scale(double scale)
bool read(char const *str)
std::string getUnit() const
Returns the unit used as a string.
bool isAbsolute()
Is this length an absolute value (uses an absolute unit).
float value
Definition svg-length.h:47
std::string write() const
void readOrUnset(char const *str, Unit u=NONE, float v=0, float c=0)
bool _set
Definition svg-length.h:41
void unset(Unit u=NONE, float v=0, float c=0)
Unit unit
Definition svg-length.h:44
bool fromString(const std::string &input, const std::string &unit, std::optional< double > scale={})
Read from user input, any non-unitised value is converted internally.
bool readAbsolute(char const *str)
float computed
Definition svg-length.h:50
void update(double em, double ex, double scale)
Utility functions to convert ascii representations to numbers.
double c[8][4]
auto floor(Geom::Rect const &rect)
Definition geom.h:130
std::string format_number(double val, unsigned int precision=3)
Definition converters.h:110
Helper class to stream background task notifications as a series of messages.
char const * refY_named_to_percent(char const *str)
char const * refX_named_to_percent(char const *str)
int buf
static unsigned sp_svg_length_read_lff(gchar const *str, SVGLength::Unit *unit, float *val, float *computed, char **next)
std::vector< SVGLength > sp_svg_length_list_read(gchar const *str)
std::string sp_svg_length_write_with_units(SVGLength const &length)
N.B. This routine will sometimes return strings with ā€˜e’ notation, so is unsuitable for CSS lengths (...
unsigned int sp_svg_length_read_computed_absolute(gchar const *str, float *length)
static std::string sp_svg_number_write_d(double val, unsigned int tprec, unsigned int fprec)
bool svg_length_absolute_unit(SVGLength::Unit u)
double sp_svg_read_percentage(char const *str, double def)
std::string sp_svg_number_write_de(double val, unsigned int tprec, int min_exp)
unsigned int sp_svg_number_read_f(gchar const *str, float *val)
gchar const * sp_svg_length_get_css_units(SVGLength::Unit unit)
unsigned int sp_svg_number_read_d(gchar const *str, double *val)
unsigned int sp_svg_length_read_ldd(gchar const *str, SVGLength::Unit *unit, double *value, double *computed)
bool svg_length_absolute_unit(SVGLength::Unit unit)
char const * sp_svg_length_get_css_units(SVGLength::Unit unit)