// SPDX-License-Identifier: GPL-2.0-or-later /* * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ #include "live_effects/lpe-powerclip.h" #include "live_effects/lpeobject.h" #include "live_effects/lpeobject-reference.h" #include <2geom/path-intersection.h> #include <2geom/intersection-graph.h> #include "display/curve.h" #include "helper/geom.h" #include "path-chemistry.h" #include "extract-uri.h" #include #include "object/sp-clippath.h" #include "object/sp-path.h" #include "object/sp-shape.h" #include "object/sp-item.h" #include "object/sp-item-group.h" #include "object/uri.h" // TODO due to internal breakage in glibmm headers, this must be last: #include namespace Inkscape { namespace LivePathEffect { LPEPowerClip::LPEPowerClip(LivePathEffectObject *lpeobject) : Effect(lpeobject), hide_clip(_("Hide clip"), _("Hide clip"), "hide_clip", &wr, this, false), is_inverse("Store the last inverse apply", "", "is_inverse", &wr, this, "false", false), uri("Store the uri of clip", "", "uri", &wr, this, "false", false), inverse(_("Inverse clip"), _("Inverse clip"), "inverse", &wr, this, false), flatten(_("Flatten clip"), _("Flatten clip, see fill rule once convert to paths"), "flatten", &wr, this, false), message(_("Info Box"), _("Important messages"), "message", &wr, this, _("Use fill-rule evenodd on fill and stroke dialog if no flatten result after convert clip to paths.")) { registerParameter(&uri); registerParameter(&inverse); registerParameter(&flatten); registerParameter(&hide_clip); registerParameter(&is_inverse); registerParameter(&message); message.param_set_min_height(55); } LPEPowerClip::~LPEPowerClip() = default; void LPEPowerClip::doOnApply (SPLPEItem const * lpeitem) { SPLPEItem * item = const_cast(lpeitem); SPObject * clip_path = item->clip_ref->getObject(); if (!clip_path) { item->removeCurrentPathEffect(false); } } void LPEPowerClip::doBeforeEffect (SPLPEItem const* lpeitem){ SPObject * clip_path = SP_ITEM(sp_lpe_item)->clip_ref->getObject(); gchar * uri_str = uri.param_getSVGValue(); if(hide_clip && clip_path) { SP_ITEM(sp_lpe_item)->clip_ref->detach(); } else if (!hide_clip && !clip_path && uri_str) { try { SP_ITEM(sp_lpe_item)->clip_ref->attach(Inkscape::URI(uri_str)); } catch (Inkscape::BadURIException &e) { g_warning("%s", e.what()); SP_ITEM(sp_lpe_item)->clip_ref->detach(); } } clip_path = SP_ITEM(sp_lpe_item)->clip_ref->getObject(); if (clip_path) { uri.param_setValue(Glib::ustring(extract_uri(sp_lpe_item->getRepr()->attribute("clip-path"))), true); SP_ITEM(sp_lpe_item)->clip_ref->detach(); Geom::OptRect bbox = sp_lpe_item->visualBounds(); if(!bbox) { return; } uri_str = uri.param_getSVGValue(); if (uri_str) { try { SP_ITEM(sp_lpe_item)->clip_ref->attach(Inkscape::URI(uri_str)); } catch (Inkscape::BadURIException &e) { g_warning("%s", e.what()); SP_ITEM(sp_lpe_item)->clip_ref->detach(); } } else { SP_ITEM(sp_lpe_item)->clip_ref->detach(); } g_free(uri_str); Geom::Rect bboxrect = (*bbox); bboxrect.expandBy(1); Geom::Point topleft = bboxrect.corner(0); Geom::Point topright = bboxrect.corner(1); Geom::Point bottomright = bboxrect.corner(2); Geom::Point bottomleft = bboxrect.corner(3); clip_box.clear(); clip_box.start(topleft); clip_box.appendNew(topright); clip_box.appendNew(bottomright); clip_box.appendNew(bottomleft); clip_box.close(); std::vector clip_path_list = clip_path->childList(true); for ( std::vector::const_iterator iter=clip_path_list.begin();iter!=clip_path_list.end();++iter) { SPObject * clip_data = *iter; gchar * is_inverse_str = is_inverse.param_getSVGValue(); if(!strcmp(is_inverse_str,"false") && inverse && isVisible()) { SPCurve * clipcurve = new SPCurve(); addInverse(SP_ITEM(clip_data), clipcurve, Geom::Affine::identity(), true); } else if((!strcmp(is_inverse_str,"true") && !inverse && isVisible()) || (inverse && !is_visible && is_inverse_str == (Glib::ustring)"true")) { removeInverse(SP_ITEM(clip_data)); } else if(inverse && isVisible()) { updateInverse(SP_ITEM(clip_data)); } g_free(is_inverse_str); } } else if(!hide_clip) { SPLPEItem * item = const_cast(lpeitem); item->removeCurrentPathEffect(false); } } void LPEPowerClip::doAfterEffect (SPLPEItem const* lpeitem){ is_load = false; if (!hide_clip && flatten && isVisible()) { SP_ITEM(sp_lpe_item)->clip_ref->detach(); } } void LPEPowerClip::addInverse (SPItem * clip_data, SPCurve * clipcurve, Geom::Affine affine, bool root){ gchar * is_inverse_str = is_inverse.param_getSVGValue(); SPDocument * document = SP_ACTIVE_DOCUMENT; if (!document) { return; } SPObject *elemref = NULL; if(root) { Inkscape::XML::Document *xml_doc = document->getReprDoc(); SP_LPE_ITEM(clip_data)->removeAllPathEffects(true); Inkscape::XML::Node *clip_path_node = xml_doc->createElement("svg:path"); Inkscape::XML::Node *parent = clip_data->getRepr()->parent(); parent->appendChild(clip_path_node); elemref = document->getObjectByRepr(clip_path_node); elemref->setAttribute("style","fill-rule:evenodd"); elemref->setAttribute("id", (Glib::ustring("lpe_") + Glib::ustring(this->getLPEObj()->getId())).c_str()); } if(!strcmp(is_inverse_str,"false")) { if (SP_IS_GROUP(clip_data)) { std::vector item_list = sp_item_group_item_list(SP_GROUP(clip_data)); for ( std::vector::const_iterator iter=item_list.begin();iter!=item_list.end();++iter) { SPItem *subitem = *iter; Geom::Affine affine_group = SP_ITEM(clip_data)->transform; addInverse(subitem, clipcurve, affine_group, false); if (root) { clip_box *= affine_group; } } } else if (SP_IS_SHAPE(clip_data)) { SPCurve * c = nullptr; c = SP_SHAPE(clip_data)->getCurve(); c->transform(affine); if (c) { Geom::PathVector c_pv = c->get_pathvector(); //TODO: this can be not correct but no better way bool dir_a = Geom::path_direction(c_pv[0]); bool dir_b = Geom::path_direction(clip_box); if (dir_a == dir_b) { clip_box = clip_box.reversed(); } clipcurve->append(c, false); gchar * is_inverse_str = is_inverse.param_getSVGValue(); if (strcmp(is_inverse_str, "true") != 0) { is_inverse.param_setValue((Glib::ustring)"true", true); } c->reset(); delete c; } Geom::Affine hidetrans = (Geom::Affine)Geom::Translate(0,999999); SP_ITEM(clip_data)->doWriteTransform(hidetrans); } } if(root) { SPCurve * container = new SPCurve; container->set_pathvector(clip_box); clipcurve->append(container, false); SP_SHAPE(elemref)->setCurve(clipcurve); container->reset(); delete container; } g_free(is_inverse_str); } void LPEPowerClip::updateInverse (SPItem * clip_data) { SPDocument * document = SP_ACTIVE_DOCUMENT; if (!document) { return; } if(inverse && isVisible()) { if (SP_IS_GROUP(clip_data)) { std::vector item_list = sp_item_group_item_list(SP_GROUP(clip_data)); for ( std::vector::const_iterator iter=item_list.begin();iter!=item_list.end();++iter) { SPItem *subitem = *iter; updateInverse(subitem); } } else if (SP_IS_SHAPE(clip_data)) { SPObject *elemref = document->getObjectById(Glib::ustring("lpe_") + Glib::ustring(this->getLPEObj()->getId())); if (elemref) { SPCurve * c = nullptr; c = SP_SHAPE(elemref)->getCurve(); if (c) { Geom::PathVector c_pv = c->get_pathvector(); //TODO: this can be not correct but no better way bool dir_a = Geom::path_direction(c_pv[0]); bool dir_b = Geom::path_direction(clip_box); if (dir_a == dir_b) { clip_box = clip_box.reversed(); } if(c_pv.size() > 1) { c_pv.pop_back(); } c_pv.push_back(clip_box); c->set_pathvector(c_pv); SP_SHAPE(elemref)->setCurve(c); c->unref(); } } } } } void LPEPowerClip::removeInverse (SPItem * clip_data){ SPDocument * document = SP_ACTIVE_DOCUMENT; if (!document) { return; } gchar * is_inverse_str = is_inverse.param_getSVGValue(); if(!strcmp(is_inverse_str,"true")) { if (SP_IS_GROUP(clip_data)) { std::vector item_list = sp_item_group_item_list(SP_GROUP(clip_data)); for ( std::vector::const_iterator iter=item_list.begin();iter!=item_list.end();++iter) { SPItem *subitem = *iter; removeInverse(subitem); } } else if (SP_IS_SHAPE(clip_data)) { Geom::Affine unhidetrans = (Geom::Affine)Geom::Translate(0,-999999); SP_ITEM(clip_data)->doWriteTransform(unhidetrans); gchar * is_inverse_str = is_inverse.param_getSVGValue(); if (strcmp(is_inverse_str, "false") != 0) { is_inverse.param_setValue((Glib::ustring)"false", true); } } SPObject *elemref = document->getObjectById(Glib::ustring("lpe_") + Glib::ustring(this->getLPEObj()->getId())); if (elemref) { elemref ->deleteObject(false); } } g_free(is_inverse_str); } void LPEPowerClip::doOnRemove (SPLPEItem const* /*lpeitem*/) { SPClipPath *clip_path = SP_ITEM(sp_lpe_item)->clip_ref->getObject(); if(clip_path) { if(!keep_paths) { gchar * is_inverse_str = is_inverse.param_getSVGValue(); std::vector clip_path_list = clip_path->childList(true); for ( std::vector::const_iterator iter=clip_path_list.begin();iter!=clip_path_list.end();++iter) { SPObject * clip_data = *iter; if(!strcmp(is_inverse_str,"true")) { removeInverse(SP_ITEM(clip_data)); } } g_free(is_inverse_str); } else if (flatten) { clip_path->deleteObject(); } } } Geom::PathVector LPEPowerClip::doEffect_path(Geom::PathVector const & path_in){ Geom::PathVector path_out = path_in; SPClipPath *clip_path = SP_ITEM(sp_lpe_item)->clip_ref->getObject(); if (!hide_clip && flatten && isVisible()) { path_out *= sp_item_transform_repr (sp_lpe_item).inverse(); SPDocument * document = SP_ACTIVE_DOCUMENT; if (!document) { return path_out; } SPObject *elemref = document->getObjectById(Glib::ustring("lpe_") + Glib::ustring(this->getLPEObj()->getId())); if (elemref) { SPCurve * c = nullptr; c = SP_SHAPE(elemref)->getCurve(); if (c) { Geom::PathVector c_pv = c->get_pathvector(); c_pv *= sp_item_transform_repr (sp_lpe_item).inverse(); Geom::PathIntersectionGraph *pig = new Geom::PathIntersectionGraph(c_pv, path_out); if (pig && !c_pv.empty() && !path_in.empty()) { path_out = pig->getIntersection(); } c->unref(); } } else { if(clip_path) { std::vector clip_path_list = clip_path->childList(true); for ( std::vector::const_iterator iter=clip_path_list.begin();iter!=clip_path_list.end();++iter) { SPObject * clip_data = *iter; flattenClip(SP_ITEM(clip_data), path_out); } } } path_out *= sp_item_transform_repr (sp_lpe_item); } return path_out; } void LPEPowerClip::doOnVisibilityToggled(SPLPEItem const* lpeitem) { doBeforeEffect(lpeitem); } void LPEPowerClip::flattenClip(SPItem * clip_data, Geom::PathVector &path_in) { SPClipPath *clip_path = SP_ITEM(sp_lpe_item)->clip_ref->getObject(); if (SP_IS_GROUP(clip_data)) { std::vector item_list = sp_item_group_item_list(SP_GROUP(clip_data)); for ( std::vector::const_iterator iter=item_list.begin();iter!=item_list.end();++iter) { SPItem *subitem = *iter; flattenClip(subitem, path_in); } } else if (SP_IS_SHAPE(clip_data)) { SPCurve * c = nullptr; c = SP_SHAPE(clip_data)->getCurve(); if (c) { Geom::PathVector c_pv = c->get_pathvector(); c_pv *= sp_item_transform_repr (sp_lpe_item).inverse(); Geom::PathIntersectionGraph *pig = new Geom::PathIntersectionGraph(c_pv, path_in); if (pig && !c_pv.empty() && !path_in.empty()) { path_in = pig->getIntersection(); } c->unref(); } } } void sp_inverse_powerclip(Inkscape::Selection *sel) { if (!sel->isEmpty()) { auto selList = sel->items(); for(auto i = boost::rbegin(selList); i != boost::rend(selList); ++i) { SPLPEItem* lpeitem = dynamic_cast(*i); if (lpeitem) { Effect::createAndApply(POWERCLIP, SP_ACTIVE_DOCUMENT, lpeitem); Effect* lpe = lpeitem->getCurrentLPE(); lpe->getRepr()->setAttribute("is_inverse", "false"); lpe->getRepr()->setAttribute("is_visible", "true"); lpe->getRepr()->setAttribute("inverse", "true"); lpe->getRepr()->setAttribute("flatten", "false"); lpe->getRepr()->setAttribute("hide_clip", "false"); } } } } void sp_remove_powerclip(Inkscape::Selection *sel) { if (!sel->isEmpty()) { auto selList = sel->items(); for(auto i = boost::rbegin(selList); i != boost::rend(selList); ++i) { SPLPEItem* lpeitem = dynamic_cast(*i); if (lpeitem) { if (lpeitem->hasPathEffect() && lpeitem->pathEffectsEnabled()) { for (PathEffectList::iterator it = lpeitem->path_effect_list->begin(); it != lpeitem->path_effect_list->end(); ++it) { LivePathEffectObject *lpeobj = (*it)->lpeobject; if (!lpeobj) { /** \todo Investigate the cause of this. * For example, this happens when copy pasting an object with LPE applied. Probably because the object is pasted while the effect is not yet pasted to defs, and cannot be found. */ g_warning("SPLPEItem::performPathEffect - NULL lpeobj in list!"); return; } Inkscape::LivePathEffect::Effect *lpe = lpeobj->get_lpe(); if (lpe->getName() == "powerclip") { lpe->doOnRemove(lpeitem); break; } } } } } } } }; //namespace LivePathEffect }; /* namespace Inkscape */ /* 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 :