1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /**
3  * @file
4  * Miscellaneous helpers for reprs.
5  */
6 
7 /*
8  * Authors:
9  *   Lauris Kaplinski <lauris@ximian.com>
10  *   Jon A. Cruz <jon@joncruz.org>
11  *
12  * Copyright (C) 1999-2000 Lauris Kaplinski
13  * Copyright (C) 2000-2001 Ximian, Inc.
14  * g++ port Copyright (C) 2003 Nathan Hurst
15  *
16  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
17  */
18 
19 #include <cstring>
20 #include <cstdlib>
21 
22 
23 #include <glib.h>
24 #include <glibmm.h>
25 
26 #include <2geom/point.h>
27 #include "svg/stringstream.h"
28 #include "svg/css-ostringstream.h"
29 #include "svg/svg-length.h"
30 
31 #include "xml/repr.h"
32 #include "xml/repr-sorting.h"
33 
34 
35 struct SPXMLNs {
36     SPXMLNs *next;
37     unsigned int uri, prefix;
38 };
39 
40 /*#####################
41 # DEFINITIONS
42 #####################*/
43 
44 #ifndef FALSE
45 # define FALSE 0
46 #endif
47 
48 #ifndef TRUE
49 # define TRUE (!FALSE)
50 #endif
51 
52 #ifndef MAX
53 # define MAX(a,b) (((a) < (b)) ? (b) : (a))
54 #endif
55 
56 /*#####################
57 # FORWARD DECLARATIONS
58 #####################*/
59 
60 static void sp_xml_ns_register_defaults();
61 static char *sp_xml_ns_auto_prefix(char const *uri);
62 
63 /*#####################
64 # MAIN
65 #####################*/
66 
67 /**
68  * SPXMLNs
69  */
70 
71 static SPXMLNs *namespaces=nullptr;
72 
73 /*
74  * There are the prefixes to use for the XML namespaces defined
75  * in repr.h
76  */
sp_xml_ns_register_defaults()77 static void sp_xml_ns_register_defaults()
78 {
79     static SPXMLNs defaults[11];
80 
81     defaults[0].uri = g_quark_from_static_string(SP_SODIPODI_NS_URI);
82     defaults[0].prefix = g_quark_from_static_string("sodipodi");
83     defaults[0].next = &defaults[1];
84 
85     defaults[1].uri = g_quark_from_static_string(SP_XLINK_NS_URI);
86     defaults[1].prefix = g_quark_from_static_string("xlink");
87     defaults[1].next = &defaults[2];
88 
89     defaults[2].uri = g_quark_from_static_string(SP_SVG_NS_URI);
90     defaults[2].prefix = g_quark_from_static_string("svg");
91     defaults[2].next = &defaults[3];
92 
93     defaults[3].uri = g_quark_from_static_string(SP_INKSCAPE_NS_URI);
94     defaults[3].prefix = g_quark_from_static_string("inkscape");
95     defaults[3].next = &defaults[4];
96 
97     defaults[4].uri = g_quark_from_static_string(SP_RDF_NS_URI);
98     defaults[4].prefix = g_quark_from_static_string("rdf");
99     defaults[4].next = &defaults[5];
100 
101     defaults[5].uri = g_quark_from_static_string(SP_CC_NS_URI);
102     defaults[5].prefix = g_quark_from_static_string("cc");
103     defaults[5].next = &defaults[6];
104 
105     defaults[6].uri = g_quark_from_static_string(SP_DC_NS_URI);
106     defaults[6].prefix = g_quark_from_static_string("dc");
107     defaults[6].next = &defaults[8];
108 
109     //defaults[7].uri = g_quark_from_static_string("https://inkscape.org/namespaces/deprecated/osb");
110     //defaults[7].prefix = g_quark_from_static_string("osb");
111     //defaults[7].next = &defaults[8];
112 
113     // Inkscape versions prior to 0.44 would write this namespace
114     // URI instead of the correct sodipodi namespace; by adding this
115     // entry to the table last (where it gets used for URI -> prefix
116     // lookups, but not prefix -> URI lookups), we effectively transfer
117     // elements in this namespace to the correct sodipodi namespace:
118 
119     defaults[8].uri = g_quark_from_static_string(SP_BROKEN_SODIPODI_NS_URI);
120     defaults[8].prefix = g_quark_from_static_string("sodipodi");
121     defaults[8].next = &defaults[9];
122 
123     // "Duck prion"
124     // This URL became widespread due to a bug in versions <= 0.43
125 
126     defaults[9].uri = g_quark_from_static_string("http://inkscape.sourceforge.net/DTD/s odipodi-0.dtd");
127     defaults[9].prefix = g_quark_from_static_string("sodipodi");
128     defaults[9].next = &defaults[10];
129 
130     // This namespace URI is being phased out by Creative Commons
131 
132     defaults[10].uri = g_quark_from_static_string(SP_OLD_CC_NS_URI);
133     defaults[10].prefix = g_quark_from_static_string("cc");
134     defaults[10].next = nullptr;
135 
136     namespaces = &defaults[0];
137 }
138 
sp_xml_ns_auto_prefix(char const * uri)139 char *sp_xml_ns_auto_prefix(char const *uri)
140 {
141     char const *start, *end;
142     char *new_prefix;
143     start = uri;
144     while ((end = strpbrk(start, ":/"))) {
145         start = end + 1;
146     }
147     end = start + strspn(start, "abcdefghijklmnopqrstuvwxyz");
148     if (end == start) {
149         start = "ns";
150         end = start + 2;
151     }
152     new_prefix = g_strndup(start, end - start);
153     if (sp_xml_ns_prefix_uri(new_prefix)) {
154         char *temp;
155         int counter=0;
156         do {
157             temp = g_strdup_printf("%s%d", new_prefix, counter++);
158         } while (sp_xml_ns_prefix_uri(temp));
159         g_free(new_prefix);
160         new_prefix = temp;
161     }
162     return new_prefix;
163 }
164 
sp_xml_ns_uri_prefix(gchar const * uri,gchar const * suggested)165 gchar const *sp_xml_ns_uri_prefix(gchar const *uri, gchar const *suggested)
166 {
167     char const *prefix;
168 
169     if (!uri) return nullptr;
170 
171     if (!namespaces) {
172         sp_xml_ns_register_defaults();
173     }
174 
175     GQuark const key = g_quark_from_string(uri);
176     prefix = nullptr;
177     for ( SPXMLNs *iter=namespaces ; iter ; iter = iter->next ) {
178         if ( iter->uri == key ) {
179             prefix = g_quark_to_string(iter->prefix);
180             break;
181         }
182     }
183 
184     if (!prefix) {
185         char *new_prefix;
186         SPXMLNs *ns;
187         if (suggested) {
188             GQuark const prefix_key=g_quark_from_string(suggested);
189 
190             SPXMLNs *found=namespaces;
191             while (found) {
192                 if (found->prefix != prefix_key) {
193                     found = found->next;
194                 }
195                 else {
196                     break;
197                 }
198             }
199 
200             if (found) { // prefix already used?
201                 new_prefix = sp_xml_ns_auto_prefix(uri);
202             } else { // safe to use suggested
203                 new_prefix = g_strdup(suggested);
204             }
205         } else {
206             new_prefix = sp_xml_ns_auto_prefix(uri);
207         }
208 
209         ns = g_new(SPXMLNs, 1);
210         g_assert( ns != nullptr );
211         ns->uri = g_quark_from_string(uri);
212         ns->prefix = g_quark_from_string(new_prefix);
213 
214         g_free(new_prefix);
215 
216         ns->next = namespaces;
217         namespaces = ns;
218 
219         prefix = g_quark_to_string(ns->prefix);
220     }
221 
222     return prefix;
223 }
224 
sp_xml_ns_prefix_uri(gchar const * prefix)225 gchar const *sp_xml_ns_prefix_uri(gchar const *prefix)
226 {
227     SPXMLNs *iter;
228     char const *uri;
229 
230     if (!prefix) return nullptr;
231 
232     if (!namespaces) {
233         sp_xml_ns_register_defaults();
234     }
235 
236     GQuark const key = g_quark_from_string(prefix);
237     uri = nullptr;
238     for ( iter = namespaces ; iter ; iter = iter->next ) {
239         if ( iter->prefix == key ) {
240             uri = g_quark_to_string(iter->uri);
241             break;
242         }
243     }
244     return uri;
245 }
246 
247 /**
248  *  Works for different-parent objects, so long as they have a common ancestor. Return value:
249  *    0    positions are equivalent
250  *    1    first object's position is greater than the second
251  *   -1    first object's position is less than the second
252  * @todo Rewrite this function's description to be understandable
253  */
sp_repr_compare_position(Inkscape::XML::Node const * first,Inkscape::XML::Node const * second)254 int sp_repr_compare_position(Inkscape::XML::Node const *first, Inkscape::XML::Node const *second)
255 {
256     int p1, p2;
257     if (first->parent() == second->parent()) {
258         /* Basic case - first and second have same parent */
259         p1 = first->position();
260         p2 = second->position();
261     } else {
262         /* Special case - the two objects have different parents.  They
263            could be in different groups or on different layers for
264            instance. */
265 
266         // Find the lowest common ancestor(LCA)
267         Inkscape::XML::Node const *ancestor = LCA(first, second);
268         g_assert(ancestor != nullptr);
269 
270         if (ancestor == first) {
271             return 1;
272         } else if (ancestor == second) {
273             return -1;
274         } else {
275             Inkscape::XML::Node const *to_first = AncetreFils(first, ancestor);
276             Inkscape::XML::Node const *to_second = AncetreFils(second, ancestor);
277             g_assert(to_second->parent() == to_first->parent());
278             p1 = to_first->position();
279             p2 = to_second->position();
280         }
281     }
282 
283     if (p1 > p2) return 1;
284     if (p1 < p2) return -1;
285     return 0;
286 
287     /* effic: Assuming that the parent--child relationship is consistent
288        (i.e. that the parent really does contain first and second among
289        its list of children), it should be equivalent to walk along the
290        children and see which we encounter first (returning 0 iff first
291        == second).
292 
293        Given that this function is used solely for sorting, we can use a
294        similar approach to do the sort: gather the things to be sorted,
295        into an STL vector (to allow random access and faster
296        traversals).  Do a single pass of the parent's children; for each
297        child, do a pass on whatever items in the vector we haven't yet
298        encountered.  If the child is found, then swap it to the
299        beginning of the yet-unencountered elements of the vector.
300        Continue until no more than one remains unencountered.  --
301        pjrm */
302 }
303 
sp_repr_compare_position_bool(Inkscape::XML::Node const * first,Inkscape::XML::Node const * second)304 bool sp_repr_compare_position_bool(Inkscape::XML::Node const *first, Inkscape::XML::Node const *second){
305     return sp_repr_compare_position(first, second)<0;
306 }
307 
308 
309 /**
310  * Find an element node using an unique attribute.
311  *
312  * This function returns the first child of the specified node that has the attribute
313  * @c key equal to @c value. Note that this function does not recurse.
314  *
315  * @param repr The node to start from
316  * @param key The name of the attribute to use for comparisons
317  * @param value The value of the attribute to look for
318  * @relatesalso Inkscape::XML::Node
319  */
sp_repr_lookup_child(Inkscape::XML::Node * repr,gchar const * key,gchar const * value)320 Inkscape::XML::Node *sp_repr_lookup_child(Inkscape::XML::Node *repr,
321                                           gchar const *key,
322                                           gchar const *value)
323 {
324     g_return_val_if_fail(repr != nullptr, NULL);
325     for ( Inkscape::XML::Node *child = repr->firstChild() ; child ; child = child->next() ) {
326         gchar const *child_value = child->attribute(key);
327         if ( (child_value == value) ||
328              (value && child_value && !strcmp(child_value, value)) )
329         {
330             return child;
331         }
332     }
333     return nullptr;
334 }
335 
336 /**
337  * Recursive version of sp_repr_lookup_child().
338  */
sp_repr_lookup_descendant(Inkscape::XML::Node const * repr,gchar const * key,gchar const * value)339 Inkscape::XML::Node const *sp_repr_lookup_descendant(Inkscape::XML::Node const *repr,
340                                                      gchar const *key,
341                                                      gchar const *value)
342 {
343     Inkscape::XML::Node const *found = nullptr;
344     g_return_val_if_fail(repr != nullptr, NULL);
345     gchar const *repr_value = repr->attribute(key);
346     if ( (repr_value == value) ||
347          (repr_value && value && strcmp(repr_value, value) == 0) ) {
348         found = repr;
349     } else {
350         for (Inkscape::XML::Node const *child = repr->firstChild() ; child && !found; child = child->next() ) {
351             found = sp_repr_lookup_descendant( child, key, value );
352         }
353     }
354     return found;
355 }
356 
357 
sp_repr_lookup_descendant(Inkscape::XML::Node * repr,gchar const * key,gchar const * value)358 Inkscape::XML::Node *sp_repr_lookup_descendant(Inkscape::XML::Node *repr,
359                                               gchar const *key,
360                                               gchar const *value)
361 {
362     Inkscape::XML::Node const *found = sp_repr_lookup_descendant( const_cast<Inkscape::XML::Node const *>(repr), key, value );
363     return const_cast<Inkscape::XML::Node *>(found);
364 }
365 
sp_repr_lookup_name(Inkscape::XML::Node const * repr,gchar const * name,gint maxdepth)366 Inkscape::XML::Node const *sp_repr_lookup_name( Inkscape::XML::Node const *repr, gchar const *name, gint maxdepth )
367 {
368     Inkscape::XML::Node const *found = nullptr;
369     g_return_val_if_fail(repr != nullptr, NULL);
370     g_return_val_if_fail(name != nullptr, NULL);
371 
372     GQuark const quark = g_quark_from_string(name);
373 
374     if ( (GQuark)repr->code() == quark ) {
375         found = repr;
376     } else if ( maxdepth != 0 ) {
377         // maxdepth == -1 means unlimited
378         if ( maxdepth == -1 ) {
379             maxdepth = 0;
380         }
381 
382         for (Inkscape::XML::Node const *child = repr->firstChild() ; child && !found; child = child->next() ) {
383             found = sp_repr_lookup_name( child, name, maxdepth - 1 );
384         }
385     }
386     return found;
387 }
388 
sp_repr_lookup_name(Inkscape::XML::Node * repr,gchar const * name,gint maxdepth)389 Inkscape::XML::Node *sp_repr_lookup_name( Inkscape::XML::Node *repr, gchar const *name, gint maxdepth )
390 {
391     Inkscape::XML::Node const *found = sp_repr_lookup_name( const_cast<Inkscape::XML::Node const *>(repr), name, maxdepth );
392     return const_cast<Inkscape::XML::Node *>(found);
393 }
394 
sp_repr_lookup_name_many(Inkscape::XML::Node const * repr,gchar const * name,gint maxdepth)395 std::vector<Inkscape::XML::Node const *> sp_repr_lookup_name_many( Inkscape::XML::Node const *repr, gchar const *name, gint maxdepth )
396 {
397     std::vector<Inkscape::XML::Node const *> nodes;
398     std::vector<Inkscape::XML::Node const *> found;
399     g_return_val_if_fail(repr != nullptr, nodes);
400     g_return_val_if_fail(name != nullptr, nodes);
401 
402     GQuark const quark = g_quark_from_string(name);
403 
404     if ( (GQuark)repr->code() == quark ) {
405         nodes.push_back(repr);
406     }
407 
408     if ( maxdepth != 0 ) {
409         // maxdepth == -1 means unlimited
410         if ( maxdepth == -1 ) {
411             maxdepth = 0;
412         }
413 
414         for (Inkscape::XML::Node const *child = repr->firstChild() ; child; child = child->next() ) {
415             found = sp_repr_lookup_name_many( child, name, maxdepth - 1);
416             nodes.insert(nodes.end(), found.begin(), found.end());
417         }
418     }
419 
420     return nodes;
421 }
422 
423 std::vector<Inkscape::XML::Node *>
sp_repr_lookup_property_many(Inkscape::XML::Node * repr,Glib::ustring const & property,Glib::ustring const & value,int maxdepth)424 sp_repr_lookup_property_many( Inkscape::XML::Node *repr, Glib::ustring const& property,
425                               Glib::ustring const &value, int maxdepth )
426 {
427     std::vector<Inkscape::XML::Node *> nodes;
428     std::vector<Inkscape::XML::Node *> found;
429     g_return_val_if_fail(repr     != nullptr, nodes);
430 
431     SPCSSAttr* css = sp_repr_css_attr (repr, "style");
432     if (value == sp_repr_css_property (css, property, "")) {
433         nodes.push_back(repr);
434     }
435 
436     if ( maxdepth != 0 ) {
437         // maxdepth == -1 means unlimited
438         if ( maxdepth == -1 ) {
439             maxdepth = 0;
440         }
441 
442         for (Inkscape::XML::Node *child = repr->firstChild() ; child; child = child->next() ) {
443             found = sp_repr_lookup_property_many( child, property, value, maxdepth - 1);
444             nodes.insert(nodes.end(), found.begin(), found.end());
445         }
446     }
447 
448     return nodes;
449 }
450 
451 /**
452  * Determine if the node is a 'title', 'desc' or 'metadata' element.
453  */
sp_repr_is_meta_element(const Inkscape::XML::Node * node)454 bool sp_repr_is_meta_element(const Inkscape::XML::Node *node)
455 {
456     if (node == nullptr) return false;
457     if (node->type() != Inkscape::XML::NodeType::ELEMENT_NODE) return false;
458     gchar const *name = node->name();
459     if (name == nullptr) return false;
460     if (!std::strcmp(name, "svg:title")) return true;
461     if (!std::strcmp(name, "svg:desc")) return true;
462     if (!std::strcmp(name, "svg:metadata")) return true;
463     return false;
464 }
465 
466 /**
467  * Parses the boolean value of an attribute "key" in repr and sets val accordingly, or to FALSE if
468  * the attr is not set.
469  *
470  * \return TRUE if the attr was set, FALSE otherwise.
471  */
sp_repr_get_boolean(Inkscape::XML::Node * repr,gchar const * key,unsigned int * val)472 unsigned int sp_repr_get_boolean(Inkscape::XML::Node *repr, gchar const *key, unsigned int *val)
473 {
474     gchar const *v;
475 
476     g_return_val_if_fail(repr != nullptr, FALSE);
477     g_return_val_if_fail(key != nullptr, FALSE);
478     g_return_val_if_fail(val != nullptr, FALSE);
479 
480     v = repr->attribute(key);
481 
482     if (v != nullptr) {
483         if (!g_ascii_strcasecmp(v, "true") ||
484             !g_ascii_strcasecmp(v, "yes" ) ||
485             !g_ascii_strcasecmp(v, "y"   ) ||
486             (atoi(v) != 0)) {
487             *val = TRUE;
488         } else {
489             *val = FALSE;
490         }
491         return TRUE;
492     } else {
493         *val = FALSE;
494         return FALSE;
495     }
496 }
497 
sp_repr_get_int(Inkscape::XML::Node * repr,gchar const * key,int * val)498 unsigned int sp_repr_get_int(Inkscape::XML::Node *repr, gchar const *key, int *val)
499 {
500     gchar const *v;
501 
502     g_return_val_if_fail(repr != nullptr, FALSE);
503     g_return_val_if_fail(key != nullptr, FALSE);
504     g_return_val_if_fail(val != nullptr, FALSE);
505 
506     v = repr->attribute(key);
507 
508     if (v != nullptr) {
509         *val = atoi(v);
510         return TRUE;
511     }
512 
513     return FALSE;
514 }
515 
sp_repr_get_double(Inkscape::XML::Node * repr,gchar const * key,double * val)516 unsigned int sp_repr_get_double(Inkscape::XML::Node *repr, gchar const *key, double *val)
517 {
518     g_return_val_if_fail(repr != nullptr, FALSE);
519     g_return_val_if_fail(key != nullptr, FALSE);
520     g_return_val_if_fail(val != nullptr, FALSE);
521 
522     gchar const *v = repr->attribute(key);
523 
524     if (v != nullptr) {
525         *val = g_ascii_strtod(v, nullptr);
526         return TRUE;
527     }
528 
529     return FALSE;
530 }
531 
sp_repr_set_boolean(Inkscape::XML::Node * repr,gchar const * key,unsigned int val)532 unsigned int sp_repr_set_boolean(Inkscape::XML::Node *repr, gchar const *key, unsigned int val)
533 {
534     g_return_val_if_fail(repr != nullptr, FALSE);
535     g_return_val_if_fail(key != nullptr, FALSE);
536 
537     repr->setAttribute(key, (val) ? "true" : "false");
538     return true;
539 }
540 
sp_repr_set_int(Inkscape::XML::Node * repr,gchar const * key,int val)541 unsigned int sp_repr_set_int(Inkscape::XML::Node *repr, gchar const *key, int val)
542 {
543     gchar c[32];
544 
545     g_return_val_if_fail(repr != nullptr, FALSE);
546     g_return_val_if_fail(key != nullptr, FALSE);
547 
548     g_snprintf(c, 32, "%d", val);
549 
550     repr->setAttribute(key, c);
551     return true;
552 }
553 
554 /**
555  * Set a property attribute to \a val [slightly rounded], in the format
556  * required for CSS properties: in particular, it never uses exponent
557  * notation.
558  */
sp_repr_set_css_double(Inkscape::XML::Node * repr,gchar const * key,double val)559 unsigned int sp_repr_set_css_double(Inkscape::XML::Node *repr, gchar const *key, double val)
560 {
561     g_return_val_if_fail(repr != nullptr, FALSE);
562     g_return_val_if_fail(key != nullptr, FALSE);
563 
564     Inkscape::CSSOStringStream os;
565     os << val;
566 
567     repr->setAttribute(key, os.str());
568     return true;
569 }
570 
571 /**
572  * For attributes where an exponent is allowed.
573  *
574  * Not suitable for property attributes (fill-opacity, font-size etc.).
575  */
sp_repr_set_svg_double(Inkscape::XML::Node * repr,gchar const * key,double val)576 unsigned int sp_repr_set_svg_double(Inkscape::XML::Node *repr, gchar const *key, double val)
577 {
578     g_return_val_if_fail(repr != nullptr, FALSE);
579     g_return_val_if_fail(key != nullptr, FALSE);
580     g_return_val_if_fail(val==val, FALSE);//tests for nan
581 
582     Inkscape::SVGOStringStream os;
583     os << val;
584 
585     repr->setAttribute(key, os.str());
586     return true;
587 }
588 
sp_repr_set_svg_non_default_double(Inkscape::XML::Node * repr,gchar const * key,double val,double default_value)589 unsigned int sp_repr_set_svg_non_default_double(Inkscape::XML::Node *repr, gchar const *key, double val, double default_value)
590 {
591     if (val==default_value){
592         repr->removeAttribute(key);
593         return true;
594     }
595     return sp_repr_set_svg_double(repr, key, val);
596 }
597 
598 /**
599  * For attributes where an exponent is allowed.
600  *
601  * Not suitable for property attributes.
602  */
sp_repr_set_svg_length(Inkscape::XML::Node * repr,gchar const * key,SVGLength & val)603 unsigned int sp_repr_set_svg_length(Inkscape::XML::Node *repr, gchar const *key, SVGLength &val)
604 {
605     g_return_val_if_fail(repr != nullptr, FALSE);
606     g_return_val_if_fail(key != nullptr, FALSE);
607 
608     repr->setAttribute(key, val.write());
609     return true;
610 }
611 
sp_repr_set_point(Inkscape::XML::Node * repr,gchar const * key,Geom::Point const & val)612 unsigned sp_repr_set_point(Inkscape::XML::Node *repr, gchar const *key, Geom::Point const & val)
613 {
614     g_return_val_if_fail(repr != nullptr, FALSE);
615     g_return_val_if_fail(key != nullptr, FALSE);
616 
617     Inkscape::SVGOStringStream os;
618     os << val[Geom::X] << "," << val[Geom::Y];
619 
620     repr->setAttribute(key, os.str());
621     return true;
622 }
623 
sp_repr_get_point(Inkscape::XML::Node * repr,gchar const * key,Geom::Point * val)624 unsigned int sp_repr_get_point(Inkscape::XML::Node *repr, gchar const *key, Geom::Point *val)
625 {
626     g_return_val_if_fail(repr != nullptr, FALSE);
627     g_return_val_if_fail(key != nullptr, FALSE);
628     g_return_val_if_fail(val != nullptr, FALSE);
629 
630     gchar const *v = repr->attribute(key);
631 
632     g_return_val_if_fail(v != nullptr, FALSE);
633 
634     gchar ** strarray = g_strsplit(v, ",", 2);
635 
636     if (strarray && strarray[0] && strarray[1]) {
637         double newx, newy;
638         newx = g_ascii_strtod(strarray[0], nullptr);
639         newy = g_ascii_strtod(strarray[1], nullptr);
640         g_strfreev (strarray);
641         *val = Geom::Point(newx, newy);
642         return TRUE;
643     }
644 
645     g_strfreev (strarray);
646     return FALSE;
647 }
648 
649 /*
650   Local Variables:
651   mode:c++
652   c-file-style:"stroustrup"
653   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
654   indent-tabs-mode:nil
655   fill-column:99
656   End:
657 */
658 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
659