diff options
Diffstat (limited to 'src/path/path-offset.cpp')
-rw-r--r-- | src/path/path-offset.cpp | 475 |
1 files changed, 475 insertions, 0 deletions
diff --git a/src/path/path-offset.cpp b/src/path/path-offset.cpp new file mode 100644 index 0000000..6607bc9 --- /dev/null +++ b/src/path/path-offset.cpp @@ -0,0 +1,475 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * Path offsets. + *//* + * Authors: + * see git history + * Created by fred on Fri Dec 05 2003. + * tweaked endlessly by bulia byak <buliabyak@users.sf.net> + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +/* + * contains lots of stitched pieces of path-chemistry.c + */ + +#include <vector> + +#include <glibmm/i18n.h> + +#include "path-offset.h" +#include "path-util.h" + +#include "message-stack.h" +#include "path-chemistry.h" // copy_object_properties() +#include "selection.h" + +#include "display/curve.h" + +#include "livarot/Path.h" +#include "livarot/Shape.h" + +#include "object/sp-flowtext.h" +#include "object/sp-path.h" +#include "object/sp-text.h" + +#include "ui/icon-names.h" + +#include "style.h" + +#define MIN_OFFSET 0.01 + +using Inkscape::DocumentUndo; + +void sp_selected_path_do_offset(SPDesktop *desktop, bool expand, double prefOffset); +void sp_selected_path_create_offset_object(SPDesktop *desktop, int expand, bool updating); + +void +sp_selected_path_offset(SPDesktop *desktop) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + double prefOffset = prefs->getDouble("/options/defaultoffsetwidth/value", 1.0, "px"); + + sp_selected_path_do_offset(desktop, true, prefOffset); +} +void +sp_selected_path_inset(SPDesktop *desktop) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + double prefOffset = prefs->getDouble("/options/defaultoffsetwidth/value", 1.0, "px"); + + sp_selected_path_do_offset(desktop, false, prefOffset); +} + +void +sp_selected_path_offset_screen(SPDesktop *desktop, double pixels) +{ + sp_selected_path_do_offset(desktop, true, pixels / desktop->current_zoom()); +} + +void +sp_selected_path_inset_screen(SPDesktop *desktop, double pixels) +{ + sp_selected_path_do_offset(desktop, false, pixels / desktop->current_zoom()); +} + + +void sp_selected_path_create_offset_object_zero(SPDesktop *desktop) +{ + sp_selected_path_create_offset_object(desktop, 0, false); +} + +void sp_selected_path_create_offset(SPDesktop *desktop) +{ + sp_selected_path_create_offset_object(desktop, 1, false); +} +void sp_selected_path_create_inset(SPDesktop *desktop) +{ + sp_selected_path_create_offset_object(desktop, -1, false); +} + +void sp_selected_path_create_updating_offset_object_zero(SPDesktop *desktop) +{ + sp_selected_path_create_offset_object(desktop, 0, true); +} + +void sp_selected_path_create_updating_offset(SPDesktop *desktop) +{ + sp_selected_path_create_offset_object(desktop, 1, true); +} + +void sp_selected_path_create_updating_inset(SPDesktop *desktop) +{ + sp_selected_path_create_offset_object(desktop, -1, true); +} + +void sp_selected_path_create_offset_object(SPDesktop *desktop, int expand, bool updating) +{ + Inkscape::Selection *selection = desktop->getSelection(); + SPItem *item = selection->singleItem(); + + if (auto shape = dynamic_cast<SPShape const *>(item)) { + if (!shape->curve()) + return; + } else if (auto text = dynamic_cast<SPText const *>(item)) { + if (!text->getNormalizedBpath()) + return; + } else { + desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("Selected object is <b>not a path</b>, cannot inset/outset.")); + return; + } + + Geom::Affine const transform(item->transform); + auto scaling_factor = item->i2doc_affine().descrim(); + + item->doWriteTransform(Geom::identity()); + + // remember the position of the item + gint pos = item->getRepr()->position(); + + // remember parent + Inkscape::XML::Node *parent = item->getRepr()->parent(); + + float o_width = 0; + { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + o_width = prefs->getDouble("/options/defaultoffsetwidth/value", 1.0, "px"); + o_width /= scaling_factor; + if (scaling_factor == 0 || o_width < MIN_OFFSET) { + o_width = MIN_OFFSET; + } + } + + Path *orig = Path_for_item(item, true, false); + if (orig == nullptr) + { + return; + } + + Path *res = new Path; + res->SetBackData(false); + + { + Shape *theShape = new Shape; + Shape *theRes = new Shape; + + orig->ConvertWithBackData(1.0); + orig->Fill(theShape, 0); + + SPCSSAttr *css = sp_repr_css_attr(item->getRepr(), "style"); + gchar const *val = sp_repr_css_property(css, "fill-rule", nullptr); + if (val && strcmp(val, "nonzero") == 0) + { + theRes->ConvertToShape(theShape, fill_nonZero); + } + else if (val && strcmp(val, "evenodd") == 0) + { + theRes->ConvertToShape(theShape, fill_oddEven); + } + else + { + theRes->ConvertToShape(theShape, fill_nonZero); + } + + Path *originaux[1]; + originaux[0] = orig; + theRes->ConvertToForme(res, 1, originaux); + + delete theShape; + delete theRes; + } + + if (res->descr_cmd.size() <= 1) + { + // pas vraiment de points sur le resultat + // donc il ne reste rien + DocumentUndo::done(desktop->getDocument(), + (updating ? _("Create linked offset") + : _("Create dynamic offset")), + (updating ? INKSCAPE_ICON("path-offset-linked") + : INKSCAPE_ICON("path-offset-dynamic"))); + selection->clear(); + + delete res; + delete orig; + return; + } + + { + Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc(); + Inkscape::XML::Node *repr = xml_doc->createElement("svg:path"); + + if (!updating) { + Inkscape::copy_object_properties(repr, item->getRepr()); + } else { + gchar const *style = item->getRepr()->attribute("style"); + repr->setAttribute("style", style); + } + + repr->setAttribute("sodipodi:type", "inkscape:offset"); + repr->setAttributeSvgDouble("inkscape:radius", ( expand > 0 + ? o_width + : expand < 0 + ? -o_width + : 0 )); + + gchar *str = res->svg_dump_path(); + repr->setAttribute("inkscape:original", str); + g_free(str); + str = nullptr; + + if ( updating ) { + + //XML Tree being used directly here while it shouldn't be + item->doWriteTransform(transform); + char const *id = item->getRepr()->attribute("id"); + char const *uri = g_strdup_printf("#%s", id); + repr->setAttribute("xlink:href", uri); + g_free((void *) uri); + } else { + repr->removeAttribute("inkscape:href"); + // delete original + item->deleteObject(false); + } + + // add the new repr to the parent + // move to the saved position + parent->addChildAtPos(repr, pos); + + SPItem *nitem = reinterpret_cast<SPItem *>(desktop->getDocument()->getObjectByRepr(repr)); + + if ( !updating ) { + // apply the transform to the offset + nitem->doWriteTransform(transform); + } + + // The object just created from a temporary repr is only a seed. + // We need to invoke its write which will update its real repr (in particular adding d=) + nitem->updateRepr(); + + Inkscape::GC::release(repr); + + selection->set(nitem); + } + + DocumentUndo::done(desktop->getDocument(), + (updating ? _("Create linked offset") + : _("Create dynamic offset")), + (updating ? INKSCAPE_ICON("path-offset-linked") + : INKSCAPE_ICON("path-offset-dynamic"))); + + delete res; + delete orig; +} + +/** + * Apply offset to selected paths + * @param desktop Targeted desktop + * @param expand True if offset expands, False if it shrinks paths + * @param prefOffset Size of offset in pixels + */ +void +sp_selected_path_do_offset(SPDesktop *desktop, bool expand, double prefOffset) +{ + Inkscape::Selection *selection = desktop->getSelection(); + + if (selection->isEmpty()) { + desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Select <b>path(s)</b> to inset/outset.")); + return; + } + + bool did = false; + std::vector<SPItem*> il(selection->items().begin(), selection->items().end()); + for (auto item : il){ + if (auto shape = dynamic_cast<SPShape const *>(item)) { + if (!shape->curve()) + continue; + } else if (auto text = dynamic_cast<SPText const *>(item)) { + if (!text->getNormalizedBpath()) + continue; + } else if (auto text = dynamic_cast<SPFlowtext const *>(item)) { + if (!text->getNormalizedBpath()) + continue; + } else { + continue; + } + + Geom::Affine const transform(item->transform); + auto scaling_factor = item->i2doc_affine().descrim(); + + item->doWriteTransform(Geom::identity()); + + float o_width = 0; + float o_miter = 0; + JoinType o_join = join_straight; + //ButtType o_butt = butt_straight; + + { + SPStyle *i_style = item->style; + int jointype = i_style->stroke_linejoin.value; + + switch (jointype) { + case SP_STROKE_LINEJOIN_MITER: + o_join = join_pointy; + break; + case SP_STROKE_LINEJOIN_ROUND: + o_join = join_round; + break; + default: + o_join = join_straight; + break; + } + + // scale to account for transforms and document units + o_width = prefOffset / scaling_factor; + + if (scaling_factor == 0 || o_width < MIN_OFFSET) { + o_width = MIN_OFFSET; + } + o_miter = i_style->stroke_miterlimit.value * o_width; + } + + Path *orig = Path_for_item(item, false); + if (orig == nullptr) { + continue; + } + + Path *res = new Path; + res->SetBackData(false); + + { + Shape *theShape = new Shape; + Shape *theRes = new Shape; + + orig->ConvertWithBackData(0.03); + orig->Fill(theShape, 0); + + SPCSSAttr *css = sp_repr_css_attr(item->getRepr(), "style"); + gchar const *val = sp_repr_css_property(css, "fill-rule", nullptr); + if (val && strcmp(val, "nonzero") == 0) + { + theRes->ConvertToShape(theShape, fill_nonZero); + } + else if (val && strcmp(val, "evenodd") == 0) + { + theRes->ConvertToShape(theShape, fill_oddEven); + } + else + { + theRes->ConvertToShape(theShape, fill_nonZero); + } + + // et maintenant: offset + // methode inexacte +/* Path *originaux[1]; + originaux[0] = orig; + theRes->ConvertToForme(res, 1, originaux); + + if (expand) { + res->OutsideOutline(orig, 0.5 * o_width, o_join, o_butt, o_miter); + } else { + res->OutsideOutline(orig, -0.5 * o_width, o_join, o_butt, o_miter); + } + + orig->ConvertWithBackData(1.0); + orig->Fill(theShape, 0); + theRes->ConvertToShape(theShape, fill_positive); + originaux[0] = orig; + theRes->ConvertToForme(res, 1, originaux); + + if (o_width >= 0.5) { + // res->Coalesce(1.0); + res->ConvertEvenLines(1.0); + res->Simplify(1.0); + } else { + // res->Coalesce(o_width); + res->ConvertEvenLines(1.0*o_width); + res->Simplify(1.0 * o_width); + } */ + // methode par makeoffset + + if (expand) + { + theShape->MakeOffset(theRes, o_width, o_join, o_miter); + } + else + { + theShape->MakeOffset(theRes, -o_width, o_join, o_miter); + } + theRes->ConvertToShape(theShape, fill_positive); + + res->Reset(); + theRes->ConvertToForme(res); + + res->ConvertEvenLines(0.1); + res->Simplify(0.1); + + delete theShape; + delete theRes; + } + + did = true; + + // remember the position of the item + gint pos = item->getRepr()->position(); + // remember parent + Inkscape::XML::Node *parent = item->getRepr()->parent(); + + selection->remove(item); + + Inkscape::XML::Node *repr = nullptr; + + if (res->descr_cmd.size() > 1) { // if there's 0 or 1 node left, drop this path altogether + Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc(); + repr = xml_doc->createElement("svg:path"); + + Inkscape::copy_object_properties(repr, item->getRepr()); + } + + item->deleteObject(false); + + if (repr) { + gchar *str = res->svg_dump_path(); + repr->setAttribute("d", str); + g_free(str); + + // add the new repr to the parent + // move to the saved position + parent->addChildAtPos(repr, pos); + + SPItem *newitem = (SPItem *) desktop->getDocument()->getObjectByRepr(repr); + + // reapply the transform + newitem->doWriteTransform(transform); + + selection->add(repr); + + Inkscape::GC::release(repr); + } + + delete orig; + delete res; + } + + if (did) { + DocumentUndo::done(desktop->getDocument(), + (expand ? _("Outset path") : _("Inset path")), + (expand ? INKSCAPE_ICON("path-outset") : INKSCAPE_ICON("path-inset"))); + } else { + desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("<b>No paths</b> to inset/outset in the selection.")); + return; + } +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : |