Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
units.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Inkscape Units
4 *
5 * Authors:
6 * Matthew Petroff <matthew@mpetroff.net>
7 *
8 * Copyright (C) 2013 Matthew Petroff
9 *
10 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
11 */
12
13#include <cmath>
14#include <cerrno>
15#include <iomanip>
16#include <iostream>
17#include <utility>
18#include <unordered_map>
19#include <glib.h>
20#include <glibmm/regex.h>
21#include <glibmm/fileutils.h>
22#include <glibmm/markup.h>
23
24#include <2geom/coord.h>
25
26#include "io/resource.h"
27#include "util/units.h"
28#include "path-prefix.h"
29#include "streq.h"
31
36
37namespace
38{
39
40#define MAKE_UNIT_CODE(a, b) \
41 ((((unsigned)(a) & 0xdf) << 8) | ((unsigned)(b) & 0xdf))
42
43enum UnitCode {
44 UNIT_CODE_PX = MAKE_UNIT_CODE('p','x'),
45 UNIT_CODE_PT = MAKE_UNIT_CODE('p','t'),
46 UNIT_CODE_PC = MAKE_UNIT_CODE('p','c'),
47 UNIT_CODE_MM = MAKE_UNIT_CODE('m','m'),
48 UNIT_CODE_CM = MAKE_UNIT_CODE('c','m'),
49 UNIT_CODE_IN = MAKE_UNIT_CODE('i','n'),
50 UNIT_CODE_EM = MAKE_UNIT_CODE('e','m'),
51 UNIT_CODE_EX = MAKE_UNIT_CODE('e','x'),
52 UNIT_CODE_PERCENT = MAKE_UNIT_CODE('%',0)
53};
54
55// TODO: convert to constexpr in C++11, so that the above constants can be eliminated
56inline unsigned make_unit_code(char const *str) {
57 if (!str || str[0] == 0) return 0;
58 return MAKE_UNIT_CODE(str[0], str[1]);
59}
60
61
62// This must match SVGLength::Unit
63unsigned const svg_length_lookup[] = {
64 0,
65 UNIT_CODE_PX,
66 UNIT_CODE_PT,
67 UNIT_CODE_PC,
68 UNIT_CODE_MM,
69 UNIT_CODE_CM,
70 UNIT_CODE_IN,
71 UNIT_CODE_EM,
72 UNIT_CODE_EX,
73 UNIT_CODE_PERCENT
74};
75
76
77
78// maps unit codes obtained from their abbreviations to their SVGLength unit indexes
79typedef std::unordered_map<unsigned, SVGLength::Unit> UnitCodeLookup;
80
81UnitCodeLookup make_unit_code_lookup()
82{
83 UnitCodeLookup umap;
84 for (unsigned i = 1; i < G_N_ELEMENTS(svg_length_lookup); ++i) {
85 umap[svg_length_lookup[i]] = static_cast<SVGLength::Unit>(i);
86 }
87 return umap;
88}
89
90UnitCodeLookup const unit_code_lookup = make_unit_code_lookup();
91
92
93
94typedef std::unordered_map<Glib::ustring, Inkscape::Util::UnitType> TypeMap;
95
98TypeMap make_type_map()
99{
100 TypeMap tmap;
101 tmap["DIMENSIONLESS"] = UNIT_TYPE_DIMENSIONLESS;
102 tmap["LINEAR"] = UNIT_TYPE_LINEAR;
103 tmap["RADIAL"] = UNIT_TYPE_RADIAL;
104 tmap["FONT_HEIGHT"] = UNIT_TYPE_FONT_HEIGHT;
105 // Note that code was not yet handling LINEAR_SCALED, TIME, QTY and NONE
106
107 return tmap;
108}
109
110TypeMap const type_map = make_type_map();
111
112} // namespace
113
114namespace Inkscape {
115namespace Util {
116
117class UnitParser : public Glib::Markup::Parser
118{
119public:
120 typedef Glib::Markup::Parser::AttributeMap AttrMap;
121 typedef Glib::Markup::ParseContext Ctx;
122
123 UnitParser(UnitTable *table);
124 ~UnitParser() override = default;
125
126protected:
127 void on_start_element(Ctx &ctx, Glib::ustring const &name, AttrMap const &attrs) override;
128 void on_end_element(Ctx &ctx, Glib::ustring const &name) override;
129 void on_text(Ctx &ctx, Glib::ustring const &text) override;
130
131public:
132 UnitTable *tbl;
133 bool primary;
134 bool skip;
135 Unit unit;
136};
137
138UnitParser::UnitParser(UnitTable *table) :
139 tbl(table),
140 primary(false),
141 skip(false)
142{
143}
144
145#define BUFSIZE (255)
146
147Unit::Unit() :
148 type(UNIT_TYPE_DIMENSIONLESS), // should this or NONE be the default?
149 factor(1.0),
150 name(),
151 name_plural(),
152 abbr(),
153 description()
154{
155}
156
158 double factor,
159 Glib::ustring name,
160 Glib::ustring name_plural,
161 Glib::ustring abbr,
162 Glib::ustring description)
163 : type(type)
164 , factor(factor)
165 , name(std::move(name))
166 , name_plural(std::move(name_plural))
167 , abbr(std::move(abbr))
168 , description(std::move(description))
169{
170 g_return_if_fail(factor <= 0);
171}
172
174{
175 *this = Unit();
176}
177
179{
180 int factor_digits = int(log10(factor));
181 if (factor_digits < 0) {
182 g_warning("factor = %f, factor_digits = %d", factor, factor_digits);
183 g_warning("factor_digits < 0 - returning 0");
184 factor_digits = 0;
185 }
186 return factor_digits;
187}
188
189bool Unit::compatibleWith(Unit const *u) const
190{
191 // Percentages
193 return true;
194 }
195
196 // Other units with same type
197 if (type == u->type) {
198 return true;
199 }
200
201 // Different, incompatible types
202 return false;
203}
204bool Unit::compatibleWith(Glib::ustring const &u) const
205{
206 return compatibleWith(UnitTable::get().getUnit(u));
207}
208
209bool Unit::operator==(Unit const &other) const
210{
211 return (type == other.type && name.compare(other.name) == 0);
212}
213
214int Unit::svgUnit() const
215{
216 char const *astr = abbr.c_str();
217 unsigned code = make_unit_code(astr);
218
219 UnitCodeLookup::const_iterator u = unit_code_lookup.find(code);
220 if (u != unit_code_lookup.end()) {
221 return u->second;
222 }
223 return 0;
224}
225
226double Unit::convert(double from_dist, Unit const *to) const
227{
228 // Percentage
229 if (to->type == UNIT_TYPE_DIMENSIONLESS) {
230 return from_dist * to->factor;
231 }
232
233 // Incompatible units
234 if (type != to->type) {
235 return -1;
236 }
237
238 // Compatible units
239 return from_dist * factor / to->factor;
240}
241double Unit::convert(double from_dist, Glib::ustring const &to) const
242{
243 return convert(from_dist, UnitTable::get().getUnit(to));
244}
245double Unit::convert(double from_dist, char const *to) const
246{
247 return convert(from_dist, UnitTable::get().getUnit(to));
248}
249
250
251
253
255{
256 using namespace Inkscape::IO::Resource;
257 load(get_filename(UIS, "units.xml", false, true));
258}
259
261{
262 for (auto & iter : _unit_map)
263 {
264 delete iter.second;
265 }
266}
267
268void UnitTable::addUnit(Unit const &u, bool primary)
269{
270 _unit_map[make_unit_code(u.abbr.c_str())] = new Unit(u);
271 if (primary) {
272 _primary_unit[u.type] = u.abbr;
273 }
274}
275
276Unit const *UnitTable::getUnit(char const *abbr) const
277{
278 UnitCodeMap::const_iterator f = _unit_map.find(make_unit_code(abbr));
279 if (f != _unit_map.end()) {
280 return &(*f->second);
281 }
282 return &_empty_unit;
283}
284
285Unit const *UnitTable::getUnit(Glib::ustring const &unit_abbr) const
286{
287 return getUnit(unit_abbr.c_str());
288}
290{
291 if (u == 0 || u > SVGLength::LAST_UNIT) {
292 return &_empty_unit;
293 }
294
295 UnitCodeMap::const_iterator f = _unit_map.find(svg_length_lookup[u]);
296 if (f != _unit_map.end()) {
297 return &(*f->second);
298 }
299 return &_empty_unit;
300}
301
302Unit const *UnitTable::findUnit(double factor, UnitType type) const
303{
304 const double eps = factor * 0.01; // allow for 1% deviation
305
306 UnitCodeMap::const_iterator cit = _unit_map.begin();
307 while (cit != _unit_map.end()) {
308 if (cit->second->type == type) {
309 if (Geom::are_near(cit->second->factor, factor, eps)) {
310 // unit found!
311 break;
312 }
313 }
314 ++cit;
315 }
316
317 if (cit != _unit_map.end()) {
318 return cit->second;
319 } else {
320 return getUnit(_primary_unit[type]);
321 }
322}
323
324Quantity UnitTable::parseQuantity(Glib::ustring const &q) const
325{
326 Glib::MatchInfo match_info;
327
328 // Extract value
329 double value = 0;
330 Glib::RefPtr<Glib::Regex> value_regex = Glib::Regex::create("[-+]*[\\d+]*[\\.,]*[\\d+]*[eE]*[-+]*\\d+");
331 if (value_regex->match(q, match_info)) {
332 std::istringstream tmp_v(match_info.fetch(0).raw());
333 tmp_v >> value;
334 }
335 int start_pos, end_pos;
336 match_info.fetch_pos(0, end_pos, start_pos);
337 end_pos = q.size() - start_pos;
338 Glib::ustring u = q.substr(start_pos, end_pos);
339
340 // Extract unit abbreviation
341 Glib::ustring abbr;
342 Glib::RefPtr<Glib::Regex> unit_regex = Glib::Regex::create("[A-z%]+");
343 if (unit_regex->match(u, match_info)) {
344 abbr = match_info.fetch(0);
345 }
346
347 Quantity qty(value, abbr);
348 return qty;
349}
350
351/* UNSAFE while passing around pointers to the Unit objects in this table
352bool UnitTable::deleteUnit(Unit const &u)
353{
354 bool deleted = false;
355 // Cannot delete the primary unit type since it's
356 // used for conversions
357 if (u.abbr != _primary_unit[u.type]) {
358 UnitCodeMap::iterator iter = _unit_map.find(make_unit_code(u.abbr.c_str()));
359 if (iter != _unit_map.end()) {
360 delete (*iter).second;
361 _unit_map.erase(iter);
362 deleted = true;
363 }
364 }
365 return deleted;
366}
367*/
368
369bool UnitTable::hasUnit(Glib::ustring const &unit) const
370{
371 UnitCodeMap::const_iterator iter = _unit_map.find(make_unit_code(unit.c_str()));
372 return (iter != _unit_map.end());
373}
374
376{
377 UnitMap submap;
378 for (auto iter : _unit_map) {
379 if (iter.second->type == type) {
380 submap.insert(UnitMap::value_type(iter.second->abbr, *iter.second));
381 }
382 }
383
384 return submap;
385}
386
387Glib::ustring UnitTable::primary(UnitType type) const
388{
389 return _primary_unit[type];
390}
391
392bool UnitTable::load(std::string const &filename) {
393 UnitParser uparser(this);
394 Glib::Markup::ParseContext ctx(uparser);
395
396 try {
397 Glib::ustring unitfile = Glib::file_get_contents(filename);
398 ctx.parse(unitfile);
399 ctx.end_parse();
400 } catch (Glib::FileError const &e) {
401 g_warning("Units file %s is missing: %s\n", filename.c_str(), e.what());
402 return false;
403 } catch (Glib::MarkupError const &e) {
404 g_warning("Problem loading units file '%s': %s\n", filename.c_str(), e.what());
405 return false;
406 }
407 return true;
408}
409
411{
412 static UnitTable instance;
413 return instance;
414}
415
416/*
417bool UnitTable::save(std::string const &filename) {
418 g_warning("UnitTable::save(): not implemented");
419
420 return false;
421}
422*/
423
424void UnitParser::on_start_element(Ctx &/*ctx*/, Glib::ustring const &name, AttrMap const &attrs)
425{
426 if (name == "unit") {
427 // reset for next use
428 unit.clear();
429 primary = false;
430 skip = false;
431
432 AttrMap::const_iterator f;
433 if ((f = attrs.find("type")) != attrs.end()) {
434 Glib::ustring type = f->second;
435 TypeMap::const_iterator tf = type_map.find(type);
436 if (tf != type_map.end()) {
437 unit.type = tf->second;
438 } else {
439 g_warning("Skipping unknown unit type '%s'.\n", type.c_str());
440 skip = true;
441 }
442 }
443 if ((f = attrs.find("pri")) != attrs.end()) {
444 primary = (f->second[0] == 'y' || f->second[0] == 'Y');
445 }
446 }
447}
448
449void UnitParser::on_text(Ctx &ctx, Glib::ustring const &text)
450{
451 Glib::ustring element = ctx.get_element();
452 if (element == "name") {
453 unit.name = text;
454 } else if (element == "plural") {
455 unit.name_plural = text;
456 } else if (element == "abbr") {
457 unit.abbr = text;
458 } else if (element == "factor") {
459 // TODO make sure we use the right conversion
460 unit.factor = g_ascii_strtod(text.c_str(), nullptr);
461 } else if (element == "description") {
462 unit.description = text;
463 }
464}
465
466void UnitParser::on_end_element(Ctx &/*ctx*/, Glib::ustring const &name)
467{
468 if (name == "unit" && !skip) {
469 tbl->addUnit(unit, primary);
470 }
471}
472
473Quantity::Quantity(double q, Unit const *u)
474 : unit(u)
475 , quantity(q)
476{
477}
478Quantity::Quantity(double q, Glib::ustring const &u)
479 : unit(UnitTable::get().getUnit(u.c_str()))
480 , quantity(q)
481{
482}
483Quantity::Quantity(double q, char const *u)
484 : unit(UnitTable::get().getUnit(u))
485 , quantity(q)
486{
487}
488
489bool Quantity::compatibleWith(Unit const *u) const
490{
491 return unit->compatibleWith(u);
492}
493bool Quantity::compatibleWith(Glib::ustring const &u) const
494{
495 return compatibleWith(u.c_str());
496}
497bool Quantity::compatibleWith(char const *u) const
498{
499 return compatibleWith(UnitTable::get().getUnit(u));
500}
501
502double Quantity::value(Unit const *u) const
503{
504 return convert(quantity, unit, u);
505}
506double Quantity::value(Glib::ustring const &u) const
507{
508 return value(u.c_str());
509}
510double Quantity::value(char const *u) const
511{
512 return value(UnitTable::get().getUnit(u));
513}
514
515Glib::ustring Quantity::string(Unit const *u) const {
516 return Inkscape::ustring::format_classic(std::fixed, std::setprecision(2), value(u)) + " " + u->abbr;
517}
518Glib::ustring Quantity::string(Glib::ustring const &u) const {
519 return string(UnitTable::get().getUnit(u.c_str()));
520}
521Glib::ustring Quantity::string() const {
522 return string(unit);
523}
524
525double Quantity::convert(double from_dist, Unit const *from, Unit const *to)
526{
527 return from->convert(from_dist, to);
528}
529double Quantity::convert(double from_dist, Glib::ustring const &from, Unit const *to)
530{
531 return convert(from_dist, UnitTable::get().getUnit(from.c_str()), to);
532}
533double Quantity::convert(double from_dist, Unit const *from, Glib::ustring const &to)
534{
535 return convert(from_dist, from, UnitTable::get().getUnit(to.c_str()));
536}
537double Quantity::convert(double from_dist, Glib::ustring const &from, Glib::ustring const &to)
538{
539 return convert(from_dist, UnitTable::get().getUnit(from.c_str()), UnitTable::get().getUnit(to.c_str()));
540}
541double Quantity::convert(double from_dist, char const *from, char const *to)
542{
543 return convert(from_dist, UnitTable::get().getUnit(from), UnitTable::get().getUnit(to));
544}
545
546bool Quantity::operator<(Quantity const &rhs) const
547{
548 if (unit->type != rhs.unit->type) {
549 g_warning("Incompatible units");
550 return false;
551 }
552 return quantity < rhs.value(unit);
553}
554bool Quantity::operator==(Quantity const &other) const
555{
563 return (*unit == *other.unit) && (quantity == other.quantity);
564}
565
566} // namespace Util
567} // namespace Inkscape
568
569/*
570 Local Variables:
571 mode:c++
572 c-file-style:"stroustrup"
573 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
574 indent-tabs-mode:nil
575 fill-column:99
576 End:
577*/
578// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
bool compatibleWith(Unit const *u) const
Checks if a quantity is compatible with the specified unit.
Definition units.cpp:489
Quantity(double q, Unit const *u)
Initialize a quantity.
Definition units.cpp:473
double value(Unit const *u) const
Return the quantity's value in the specified unit.
Definition units.cpp:502
Glib::ustring string() const
Definition units.cpp:521
bool operator==(Quantity const &other) const
Definition units.cpp:554
bool operator<(Quantity const &rhs) const
Comparison operators.
Definition units.cpp:546
Unit const * unit
Definition units.h:95
static double convert(double from_dist, Unit const *from, Unit const *to)
Convert distances.
Definition units.cpp:525
Unit const * findUnit(double factor, UnitType type) const
Try to find a unit based on its conversion factor to the primary.
Definition units.cpp:302
Glib::ustring primary(UnitType type) const
Returns the default unit abbr for the given type.
Definition units.cpp:387
UnitCodeMap _unit_map
Definition units.h:197
Quantity parseQuantity(Glib::ustring const &q) const
Retrieve a quantity based on its string identifier.
Definition units.cpp:324
std::unordered_map< Glib::ustring, Unit > UnitMap
Definition units.h:146
UnitMap units(UnitType type) const
Provides an iterable list of items in the given unit table.
Definition units.cpp:375
bool load(std::string const &filename)
Load units from an XML file.
Definition units.cpp:392
void addUnit(Unit const &u, bool primary)
Add a new unit to the table.
Definition units.cpp:268
bool hasUnit(Glib::ustring const &name) const
Remove a unit definition from the given unit type table * / DISABLED, unsafe with the current passing...
Definition units.cpp:369
Glib::ustring _primary_unit[UNIT_TYPE_QTY]
Definition units.h:198
static UnitTable & get()
Definition units.cpp:410
Unit const * getUnit(Glib::ustring const &name) const
Retrieve a given unit based on its string identifier.
Definition units.cpp:285
static Unit _empty_unit
Definition units.h:201
UnitTable()
Initializes the unit tables and identifies the primary unit types.
Definition units.cpp:254
double convert(double from_dist, Unit const *to) const
Convert value from this unit.
Definition units.cpp:226
UnitType type
Definition units.h:72
int defaultDigits() const
Returns the suggested precision to use for displaying numbers of this unit.
Definition units.cpp:178
bool compatibleWith(Unit const *u) const
Checks if a unit is compatible with the specified unit.
Definition units.cpp:189
int svgUnit() const
Get SVG unit code.
Definition units.cpp:214
bool operator==(Unit const &other) const
Check if units are equal.
Definition units.cpp:209
Glib::ustring abbr
Definition units.h:76
Glib::ustring name
Definition units.h:74
Integral and real coordinate types and some basic utilities.
bool are_near(Affine const &a1, Affine const &a2, Coord eps=EPSILON)
Miscellaneous supporting code.
Definition document.h:95
@ UNIT_TYPE_DIMENSIONLESS
Definition units.h:33
@ UNIT_TYPE_FONT_HEIGHT
Definition units.h:38
@ UNIT_TYPE_RADIAL
Definition units.h:36
@ UNIT_TYPE_LINEAR
Definition units.h:34
Glib::ustring format_classic(T const &... args)
Helper class to stream background task notifications as a series of messages.
STL namespace.
TODO: insert short description here.
Inkscape::IO::Resource - simple resource API.
TODO: insert short description here.
Glib::ustring name
Definition toolbars.cpp:55