Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
path-offset.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
/*
5 * Authors:
6 * see git history
7 * Created by fred on Fri Dec 05 2003.
8 * tweaked endlessly by 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/*
15 * contains lots of stitched pieces of path-chemistry.c
16 */
17
18#include "path-offset.h"
19
20#include <vector>
21
22#include <glibmm/i18n.h>
23
24#include "document.h"
25#include "document-undo.h"
26#include "message-stack.h"
27#include "path-chemistry.h" // copy_object_properties()
28#include "path-util.h"
29#include "preferences.h"
30#include "selection.h"
31
32#include "display/curve.h"
33
34#include "livarot/Path.h"
35#include "livarot/Shape.h"
36
37#include "object/sp-flowtext.h"
38#include "object/sp-path.h"
39#include "object/sp-text.h"
40
41#include "ui/icon-names.h"
42
43#include "style.h"
44
45#define MIN_OFFSET 0.01
46
48
49void sp_selected_path_do_offset(SPDesktop *desktop, bool expand, double prefOffset);
50void sp_selected_path_create_offset_object(SPDesktop *desktop, int expand, bool updating);
51
52void
54{
56 double prefOffset = prefs->getDouble("/options/defaultoffsetwidth/value", 1.0, "px");
57
58 sp_selected_path_do_offset(desktop, true, prefOffset);
59}
60void
62{
64 double prefOffset = prefs->getDouble("/options/defaultoffsetwidth/value", 1.0, "px");
65
66 sp_selected_path_do_offset(desktop, false, prefOffset);
67}
68
69void
74
75void
80
81
86
95
100
105
110
112{
114 SPItem *item = selection->singleItem();
115
116 if (auto shape = cast<SPShape>(item)) {
117 if (!shape->curve())
118 return;
119 } else if (is<SPText>(item)) {
120 } else {
121 desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Selected object is <b>not a path</b>, cannot inset/outset."));
122 return;
123 }
124
125 Geom::Affine const transform(item->transform);
126 auto scaling_factor = item->i2doc_affine().descrim();
127
129
130 // remember the position of the item
131 gint pos = item->getRepr()->position();
132
133 // remember parent
135
136 float o_width = 0;
137 {
139 o_width = prefs->getDouble("/options/defaultoffsetwidth/value", 1.0, "px");
140 o_width /= scaling_factor;
141 if (scaling_factor == 0 || o_width < MIN_OFFSET) {
142 o_width = MIN_OFFSET;
143 }
144 }
145
146 auto orig = Path_for_item(item, true, false);
147 if (!orig) {
148 return;
149 }
150
151 Path *res = new Path;
152 res->SetBackData(false);
153
154 {
155 Shape *theShape = new Shape;
156 Shape *theRes = new Shape;
157
158 orig->ConvertWithBackData(1.0);
159 orig->Fill(theShape, 0);
160
161 SPCSSAttr *css = sp_repr_css_attr(item->getRepr(), "style");
162 gchar const *val = sp_repr_css_property(css, "fill-rule", nullptr);
163 if (val && strcmp(val, "nonzero") == 0)
164 {
165 theRes->ConvertToShape(theShape, fill_nonZero);
166 }
167 else if (val && strcmp(val, "evenodd") == 0)
168 {
169 theRes->ConvertToShape(theShape, fill_oddEven);
170 }
171 else
172 {
173 theRes->ConvertToShape(theShape, fill_nonZero);
174 }
175
176 Path *paths[] = { orig.get() };
177 theRes->ConvertToForme(res, 1, paths);
178
179 delete theShape;
180 delete theRes;
181 }
182
183 if (res->descr_cmd.size() <= 1)
184 {
185 // pas vraiment de points sur le resultat
186 // donc il ne reste rien
188 (updating ? _("Create linked offset")
189 : _("Create dynamic offset")),
190 (updating ? INKSCAPE_ICON("path-offset-linked")
191 : INKSCAPE_ICON("path-offset-dynamic")));
192 selection->clear();
193
194 delete res;
195 return;
196 }
197
198 {
200 Inkscape::XML::Node *repr = xml_doc->createElement("svg:path");
201
202 if (!updating) {
204 } else {
205 gchar const *style = item->getRepr()->attribute("style");
206 repr->setAttribute("style", style);
207 }
208
209 repr->setAttribute("sodipodi:type", "inkscape:offset");
210 repr->setAttributeSvgDouble("inkscape:radius", ( expand > 0
211 ? o_width
212 : expand < 0
213 ? -o_width
214 : 0 ));
215
216 repr->setAttribute("inkscape:original", res->svg_dump_path().c_str());
217
218 if ( updating ) {
219
220 //XML Tree being used directly here while it shouldn't be
221 item->doWriteTransform(transform);
222 char const *id = item->getRepr()->attribute("id");
223 char const *uri = g_strdup_printf("#%s", id);
224 repr->setAttribute("xlink:href", uri);
225 g_free((void *) uri);
226 } else {
227 repr->removeAttribute("inkscape:href");
228 // delete original
229 item->deleteObject(false);
230 }
231
232 // add the new repr to the parent
233 // move to the saved position
234 parent->addChildAtPos(repr, pos);
235
236 SPItem *nitem = reinterpret_cast<SPItem *>(desktop->getDocument()->getObjectByRepr(repr));
237
238 if ( !updating ) {
239 // apply the transform to the offset
240 nitem->doWriteTransform(transform);
241 }
242
243 // The object just created from a temporary repr is only a seed.
244 // We need to invoke its write which will update its real repr (in particular adding d=)
245 nitem->updateRepr();
246
248
249 selection->set(nitem);
250 }
251
253 (updating ? _("Create linked offset")
254 : _("Create dynamic offset")),
255 (updating ? INKSCAPE_ICON("path-offset-linked")
256 : INKSCAPE_ICON("path-offset-dynamic")));
257
258 delete res;
259}
260
267void
268sp_selected_path_do_offset(SPDesktop *desktop, bool expand, double prefOffset)
269{
271
272 if (selection->isEmpty()) {
273 desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>path(s)</b> to inset/outset."));
274 return;
275 }
276
277 bool did = false;
278 std::vector<SPItem*> il(selection->items().begin(), selection->items().end());
279 for (auto item : il){
280 if (auto shape = cast<SPShape>(item)) {
281 if (!shape->curve())
282 continue;
283 } else if (is<SPText>(item) || is<SPFlowtext>(item)) {
284 } else {
285 continue;
286 }
287
288 Geom::Affine const transform(item->transform);
289 auto scaling_factor = item->i2doc_affine().descrim();
290
292
293 float o_width = 0;
294 float o_miter = 0;
295 JoinType o_join = join_straight;
296 //ButtType o_butt = butt_straight;
297
298 {
299 SPStyle *i_style = item->style;
300 int jointype = i_style->stroke_linejoin.value;
301
302 switch (jointype) {
304 o_join = join_pointy;
305 break;
307 o_join = join_round;
308 break;
309 default:
310 o_join = join_straight;
311 break;
312 }
313
314 // scale to account for transforms and document units
315 o_width = prefOffset / scaling_factor;
316
317 if (scaling_factor == 0 || o_width < MIN_OFFSET) {
318 o_width = MIN_OFFSET;
319 }
320 o_miter = i_style->stroke_miterlimit.value * o_width;
321 }
322
323 auto orig = Path_for_item(item, false);
324 if (!orig) {
325 continue;
326 }
327
328 Path *res = new Path;
329 res->SetBackData(false);
330
331 {
332 Shape *theShape = new Shape;
333 Shape *theRes = new Shape;
334
335 orig->ConvertWithBackData(0.03);
336 orig->Fill(theShape, 0);
337
338 SPCSSAttr *css = sp_repr_css_attr(item->getRepr(), "style");
339 gchar const *val = sp_repr_css_property(css, "fill-rule", nullptr);
340 if (val && strcmp(val, "nonzero") == 0)
341 {
342 theRes->ConvertToShape(theShape, fill_nonZero);
343 }
344 else if (val && strcmp(val, "evenodd") == 0)
345 {
346 theRes->ConvertToShape(theShape, fill_oddEven);
347 }
348 else
349 {
350 theRes->ConvertToShape(theShape, fill_nonZero);
351 }
352
353 // et maintenant: offset
354 // methode inexacte
355/* Path *originaux[1];
356 originaux[0] = orig;
357 theRes->ConvertToForme(res, 1, originaux);
358
359 if (expand) {
360 res->OutsideOutline(orig, 0.5 * o_width, o_join, o_butt, o_miter);
361 } else {
362 res->OutsideOutline(orig, -0.5 * o_width, o_join, o_butt, o_miter);
363 }
364
365 orig->ConvertWithBackData(1.0);
366 orig->Fill(theShape, 0);
367 theRes->ConvertToShape(theShape, fill_positive);
368 originaux[0] = orig;
369 theRes->ConvertToForme(res, 1, originaux);
370
371 if (o_width >= 0.5) {
372 // res->Coalesce(1.0);
373 res->ConvertEvenLines(1.0);
374 res->Simplify(1.0);
375 } else {
376 // res->Coalesce(o_width);
377 res->ConvertEvenLines(1.0*o_width);
378 res->Simplify(1.0 * o_width);
379 } */
380 // methode par makeoffset
381
382 if (expand)
383 {
384 theShape->MakeOffset(theRes, o_width, o_join, o_miter);
385 }
386 else
387 {
388 theShape->MakeOffset(theRes, -o_width, o_join, o_miter);
389 }
390 theRes->ConvertToShape(theShape, fill_positive);
391
392 res->Reset();
393 theRes->ConvertToForme(res);
394
395 res->ConvertEvenLines(0.1);
396 res->Simplify(0.1);
397
398 delete theShape;
399 delete theRes;
400 }
401
402 did = true;
403
404 // remember the position of the item
405 gint pos = item->getRepr()->position();
406 // remember parent
408
409 selection->remove(item);
410
411 Inkscape::XML::Node *repr = nullptr;
412
413 if (res->descr_cmd.size() > 1) { // if there's 0 or 1 node left, drop this path altogether
415 repr = xml_doc->createElement("svg:path");
416
418 }
419
420 item->deleteObject(false);
421
422 if (repr) {
423 repr->setAttribute("d", res->svg_dump_path().c_str());
424
425 // add the new repr to the parent
426 // move to the saved position
427 parent->addChildAtPos(repr, pos);
428
429 auto newitem = cast_unsafe<SPItem>(desktop->getDocument()->getObjectByRepr(repr));
430
431 // reapply the transform
432 newitem->doWriteTransform(transform);
433
434 selection->add(repr);
435
437 }
438
439 delete res;
440 }
441
442 if (did) {
444 (expand ? _("Outset path") : _("Inset path")),
445 (expand ? INKSCAPE_ICON("path-outset") : INKSCAPE_ICON("path-inset")));
446 } else {
447 desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>No paths</b> to inset/outset in the selection."));
448 return;
449 }
450}
451
452/*
453 Local Variables:
454 mode:c++
455 c-file-style:"stroustrup"
456 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
457 indent-tabs-mode:nil
458 fill-column:99
459 End:
460*/
461// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
@ fill_oddEven
Definition LivarotDefs.h:68
@ fill_nonZero
Definition LivarotDefs.h:69
@ fill_positive
Definition LivarotDefs.h:70
JoinType
Definition LivarotDefs.h:60
@ join_round
Definition LivarotDefs.h:62
@ join_pointy
Definition LivarotDefs.h:63
@ join_straight
Definition LivarotDefs.h:61
TODO: insert short description here.
TODO: insert short description here.
3x3 matrix representing an affine transformation.
Definition affine.h:70
Coord descrim() const
Calculate the descriminant.
Definition affine.cpp:434
constexpr Coord get() const
Definition point.h:110
static void done(SPDocument *document, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
MessageId flash(MessageType type, char const *message)
Temporarily pushes a message onto the stack.
SPItemRange items()
Returns a range of selected SPItems.
Definition object-set.h:255
void clear()
Unselects all selected objects.
bool isEmpty()
Returns true if no items are selected.
SPItem * singleItem()
Returns a single selected item.
Preference storage class.
Definition preferences.h:66
double getDouble(Glib::ustring const &pref_path, double def=0.0, Glib::ustring const &unit="")
Retrieve a floating point value.
static Preferences * get()
Access the singleton Preferences object.
The set of selected SPObjects for a given document and layer model.
Definition selection.h:80
void add(XML::Node *repr)
Add an XML node's SPObject to the set of selected objects.
Definition selection.h:107
void set(XML::Node *repr)
Set the selection to an XML node's SPObject.
Definition selection.h:118
void remove(XML::Node *repr)
Removes an item from the set of selected objects.
Definition selection.h:131
Interface for refcounted XML nodes.
Definition node.h:80
virtual Node * parent()=0
Get the parent of this node.
void setAttribute(Util::const_char_ptr key, Util::const_char_ptr value)
Change an attribute of this node.
Definition node.cpp:25
virtual unsigned position() const =0
Get the index of this node in parent's child order.
virtual char const * attribute(char const *key) const =0
Get the string representation of a node's attribute.
void removeAttribute(Inkscape::Util::const_char_ptr key)
Remove an attribute of this node.
Definition node.h:280
bool setAttributeSvgDouble(Util::const_char_ptr key, double val)
For attributes where an exponent is allowed.
Definition node.cpp:111
Path and its polyline approximation.
Definition Path.h:93
void SetBackData(bool nVal)
Sets the back variable to the value passed in and clears the polyline approximation.
Definition Path.cpp:232
std::string svg_dump_path() const
Definition Path.cpp:577
void ConvertEvenLines(double treshhold)
Creates a polyline approximation of the path.
std::vector< PathDescr * > descr_cmd
Definition Path.h:108
void Simplify(double treshhold)
Simplify the path.
void Reset()
Clears all stored path commands and resets flags that are used by command functions while adding path...
Definition Path.cpp:45
To do: update description of desktop.
Definition desktop.h:149
double current_zoom() const
Definition desktop.h:335
SPDocument * getDocument() const
Definition desktop.h:189
Inkscape::MessageStack * messageStack() const
Definition desktop.h:160
Inkscape::Selection * getSelection() const
Definition desktop.h:188
SPDocument * doc() const
Definition desktop.h:159
Inkscape::XML::Document * getReprDoc()
Our Inkscape::XML::Document.
Definition document.h:213
SPObject * getObjectByRepr(Inkscape::XML::Node *repr) const
Base class for visual SVG elements.
Definition sp-item.h:109
Geom::Affine transform
Definition sp-item.h:138
Geom::Affine i2doc_affine() const
Returns the accumulated transformation of the item and all its ancestors, including root's viewport.
Definition sp-item.cpp:1816
void doWriteTransform(Geom::Affine const &transform, Geom::Affine const *adv=nullptr, bool compensate=true)
Set a new transform on an object.
Definition sp-item.cpp:1658
SPStyle * style
Represents the style properties, whether from presentation attributes, the style attribute,...
Definition sp-object.h:248
Inkscape::XML::Node * updateRepr(unsigned int flags=SP_OBJECT_WRITE_EXT)
Updates the object's repr based on the object's state.
void deleteObject(bool propagate, bool propagate_descendants)
Deletes an object, unparenting it from its parent.
Inkscape::XML::Node * getRepr()
Returns the XML representation of tree.
An SVG style object.
Definition style.h:45
T< SPAttr::STROKE_LINEJOIN, SPIEnum< SPStrokeJoinType > > stroke_linejoin
stroke-linejoin
Definition style.h:253
T< SPAttr::STROKE_MITERLIMIT, SPIFloat > stroke_miterlimit
stroke-miterlimit
Definition style.h:255
A class to store/manipulate directed graphs.
Definition Shape.h:65
void ConvertToForme(Path *dest)
Extract contours from a directed graph.
Definition ShapeMisc.cpp:42
int MakeOffset(Shape *of, double dec, JoinType join, double miter, bool do_profile=false, double cx=0, double cy=0, double radius=0, Geom::Affine *i2doc=nullptr)
int ConvertToShape(Shape *a, FillRule directed=fill_nonZero, bool invert=false)
Using a given fill rule, find all intersections in the shape given, create a new intersection free sh...
std::shared_ptr< Css const > css
Geom::Point orig
static char const *const parent
Definition dir-util.cpp:70
TODO: insert short description here.
Macro for icon names used in Inkscape.
SPItem * item
Raw stack of active status messages.
vector< vector< Point > > paths
Definition metro.cpp:36
Affine identity()
Create an identity matrix.
Definition affine.h:210
static R & release(R &r)
Decrements the reference count of a anchored object.
void copy_object_properties(XML::Node *dest, XML::Node const *src)
Copy generic object properties, like:
@ ERROR_MESSAGE
Definition message.h:29
@ WARNING_MESSAGE
Definition message.h:28
void sp_selected_path_create_updating_offset_object_zero(SPDesktop *desktop)
void sp_selected_path_offset(SPDesktop *desktop)
void sp_selected_path_create_updating_offset(SPDesktop *desktop)
void sp_selected_path_inset_screen(SPDesktop *desktop, double pixels)
void sp_selected_path_create_offset_object(SPDesktop *desktop, int expand, bool updating)
void sp_selected_path_do_offset(SPDesktop *desktop, bool expand, double prefOffset)
Apply offset to selected paths.
void sp_selected_path_create_offset_object_zero(SPDesktop *desktop)
void sp_selected_path_offset_screen(SPDesktop *desktop, double pixels)
void sp_selected_path_inset(SPDesktop *desktop)
void sp_selected_path_create_offset(SPDesktop *desktop)
void sp_selected_path_create_updating_inset(SPDesktop *desktop)
void sp_selected_path_create_inset(SPDesktop *desktop)
Path offsets.
std::unique_ptr< Path > Path_for_item(SPItem *item, bool doTransformation, bool transformFull)
Creates a Livarot Path object from an SPItem.
Definition path-util.cpp:32
Path utilities.
Singleton class to access the preferences file in a convenient way.
SPCSSAttr * sp_repr_css_attr(Node const *repr, gchar const *attr)
Creates a new SPCSSAttr with one attribute (i.e.
Definition repr-css.cpp:88
char const * sp_repr_css_property(SPCSSAttr *css, gchar const *name, gchar const *defval)
Returns a character string of the value of a given style property or a default value if the attribute...
Definition repr-css.cpp:147
TODO: insert short description here.
Interface for XML documents.
Definition document.h:43
virtual Node * createElement(char const *name)=0
@ SP_STROKE_LINEJOIN_MITER
Definition style-enums.h:34
@ SP_STROKE_LINEJOIN_ROUND
Definition style-enums.h:35
SPStyle - a style object for SPItem objects.
SPDesktop * desktop