1 // SPDX-License-Identifier: GPL-2.0-or-later
2 
3 /** @file
4  *
5  * Path related functions for ObjectSet.
6  *
7  * Copyright (C) 2020 Tavmjong Bah
8  *
9  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
10  *
11  * TODO: Move related code from path-chemistry.cpp
12  *
13  */
14 
15 #include <glibmm/i18n.h>
16 
17 #include "document-undo.h"
18 #include "message-stack.h"
19 
20 #include "attribute-rel-util.h"
21 
22 #include "object/object-set.h"
23 #include "path/path-outline.h"
24 #include "path/path-simplify.h"
25 
26 using Inkscape::ObjectSet;
27 
28 bool
strokesToPaths(bool legacy,bool skip_undo)29 ObjectSet::strokesToPaths(bool legacy, bool skip_undo)
30 {
31   if (desktop() && isEmpty()) {
32     desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>stroked path(s)</b> to convert stroke to path."));
33     return false;
34   }
35 
36   Inkscape::Preferences *prefs = Inkscape::Preferences::get();
37   if (prefs->getBool("/options/pathoperationsunlink/value", true)) {
38       unlinkRecursive(true);
39   }
40 
41   // Need to turn on stroke scaling to ensure stroke is scaled when transformed!
42   bool scale_stroke = prefs->getBool("/options/transform/stroke", true);
43   prefs->setBool("/options/transform/stroke", true);
44 
45   bool did = false;
46 
47   std::vector<SPItem *> my_items(items().begin(), items().end());
48 
49   for (auto item : my_items) {
50     // Do not remove the object from the selection here
51     // as we want to keep it selected if the whole operation fails
52     Inkscape::XML::Node *new_node = item_to_paths(item, legacy);
53     if (new_node) {
54       SPObject* new_item = document()->getObjectByRepr(new_node);
55 
56       // Markers don't inherit properties from outside the
57       // marker. When converted to paths objects they need to be
58       // protected from inheritance. This is why (probably) the stroke
59       // to path code uses SP_STYLE_FLAG_ALWAYS when defining the
60       // style of the fill and stroke during the conversion. This
61       // means the style contains every possible property. Once we've
62       // finished the stroke to path conversion, we can eliminate
63       // unneeded properties from the style element.
64       sp_attribute_clean_recursive(new_node, SP_ATTRCLEAN_STYLE_REMOVE | SP_ATTRCLEAN_DEFAULT_REMOVE);
65 
66       add(new_item); // Add to selection.
67       did = true;
68     }
69   }
70 
71   // Reset
72   prefs->setBool("/options/transform/stroke", scale_stroke);
73 
74   if (desktop() && !did) {
75     desktop()->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>No stroked paths</b> in the selection."));
76   }
77 
78   if (did && !skip_undo) {
79     Inkscape::DocumentUndo::done(document(), SP_VERB_NONE, _("Convert stroke to path"));
80   }
81 
82   return did;
83 }
84 
85 bool
simplifyPaths(bool skip_undo)86 ObjectSet::simplifyPaths(bool skip_undo)
87 {
88     if (desktop() && isEmpty()) {
89         desktop()->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>path(s)</b> to simplify."));
90         return false;
91     }
92 
93     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
94     double threshold = prefs->getDouble("/options/simplifythreshold/value", 0.003);
95     bool justCoalesce = prefs->getBool(  "/options/simplifyjustcoalesce/value", false);
96 
97     // Keep track of accelerated simplify
98     static gint64 previous_time = 0;
99     static gdouble multiply = 1.0;
100 
101     // Get the current time
102     gint64 current_time = g_get_monotonic_time();
103 
104     // Was the previous call to this function recent? (<0.5 sec)
105     if (previous_time > 0 && current_time - previous_time < 500000) {
106 
107         // add to the threshold 1/2 of its original value
108         multiply  += 0.5;
109         threshold *= multiply;
110 
111     } else {
112         // reset to the default
113         multiply = 1;
114     }
115 
116     // Remember time for next call
117     previous_time = current_time;
118 
119     // set "busy" cursor
120     if (desktop()) {
121         desktop()->setWaitingCursor();
122     }
123 
124     Geom::OptRect selectionBbox = visualBounds();
125     if (!selectionBbox) {
126         std::cerr << "ObjectSet::: selection has no visual bounding box!" << std::endl;
127         return false;
128     }
129     double size = L2(selectionBbox->dimensions());
130 
131     int pathsSimplified = 0;
132     std::vector<SPItem *> my_items(items().begin(), items().end());
133     for (auto item : my_items) {
134         pathsSimplified += path_simplify(item, threshold, justCoalesce, size);
135     }
136 
137     if (pathsSimplified > 0 && !skip_undo) {
138         DocumentUndo::done(document(), SP_VERB_SELECTION_SIMPLIFY,  _("Simplify"));
139     }
140 
141     if (desktop()) {
142         desktop()->clearWaitingCursor();
143         if (pathsSimplified > 0) {
144             desktop()->messageStack()->flashF(Inkscape::NORMAL_MESSAGE, _("<b>%d</b> paths simplified."), pathsSimplified);
145         } else {
146             desktop()->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>No paths</b> to simplify in the selection."));
147         }
148     }
149 
150     return (pathsSimplified > 0);
151 }
152 
153 /*
154   Local Variables:
155   mode:c++
156   c-file-style:"stroustrup"
157   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
158   indent-tabs-mode:nil
159   fill-column:99
160   End:
161 */
162 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
163