// SPDX-License-Identifier: GPL-2.0-or-later /* * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ #include "live_effects/lpe-dashed-stroke.h" #include "2geom/path.h" #include "2geom/pathvector.h" #include "helper/geom.h" // TODO due to internal breakage in glibmm headers, this must be last: #include namespace Inkscape { namespace LivePathEffect { LPEDashedStroke::LPEDashedStroke(LivePathEffectObject *lpeobject) : Effect(lpeobject) , numberdashes(_("Number of dashes"), _("Number of dashes"), "numberdashes", &wr, this, 3) , holefactor(_("Hole factor"), _("Hole factor"), "holefactor", &wr, this, 0.0) , splitsegments(_("Use segments"), _("Use segments"), "splitsegments", &wr, this, true) , halfextreme(_("Half start/end"), _("Start and end of each segment has half size"), "halfextreme", &wr, this, true) , unifysegment(_("Equalize dashes"), _("Global dash length is approximately the length of the dashes in the shortest path segment"), "unifysegment", &wr, this, true) , message(_("Note"), _("Important messages"), "message", &wr, this, _("Add \"Fill Between Many LPE\" to add fill.")) { registerParameter(&numberdashes); registerParameter(&holefactor); registerParameter(&splitsegments); registerParameter(&halfextreme); registerParameter(&unifysegment); registerParameter(&message); numberdashes.param_set_range(2, 999999999); numberdashes.param_set_increments(1, 1); numberdashes.param_set_digits(0); holefactor.param_set_range(-0.99999, 0.99999); holefactor.param_set_increments(0.01, 0.01); holefactor.param_set_digits(5); message.param_set_min_height(30); } LPEDashedStroke::~LPEDashedStroke() = default; void LPEDashedStroke::doBeforeEffect(SPLPEItem const *lpeitem) {} ///Calculate the time in curve_in with a real time of A //TODO: find a better place to it double LPEDashedStroke::timeAtLength(double const A, Geom::Path const &segment) { if ( A == 0 || segment[0].isDegenerate()) { return 0; } double t = 1; t = timeAtLength(A, segment.toPwSb()); return t; } ///Calculate the time in curve_in with a real time of A //TODO: find a better place to it double LPEDashedStroke::timeAtLength(double const A, Geom::Piecewise> pwd2) { if ( A == 0 || pwd2.size() == 0) { return 0; } double t = pwd2.size(); std::vector t_roots = roots(Geom::arcLengthSb(pwd2) - A); if (!t_roots.empty()) { t = t_roots[0]; } return t; } Geom::PathVector LPEDashedStroke::doEffect_path(Geom::PathVector const &path_in) { Geom::PathVector const pv = pathv_to_linear_and_cubic_beziers(path_in); Geom::PathVector result; for (const auto & path_it : pv) { if (path_it.empty()) { continue; } Geom::Path::const_iterator curve_it1 = path_it.begin(); Geom::Path::const_iterator curve_it2 = ++(path_it.begin()); Geom::Path::const_iterator curve_endit = path_it.end_default(); if (path_it.closed()) { const Geom::Curve &closingline = path_it.back_closed(); // the closing line segment is always of type // Geom::LineSegment. if (are_near(closingline.initialPoint(), closingline.finalPoint())) { // closingline.isDegenerate() did not work, because it only checks for // *exact* zero length, which goes wrong for relative coordinates and // rounding errors... // the closing line segment has zero-length. So stop before that one! curve_endit = path_it.end_open(); } } size_t numberdashes_fixed = numberdashes; if(!splitsegments) { numberdashes_fixed++; } size_t numberholes = numberdashes_fixed - 1; size_t ammount = numberdashes_fixed + numberholes; if (halfextreme) { ammount--; } double base = 1/(double)ammount; double globaldash = base * numberdashes_fixed * (1 + holefactor); if (halfextreme) { globaldash = base * (numberdashes_fixed - 1) * (1 + holefactor); } double globalhole = 1-globaldash; double dashpercent = globaldash/numberdashes_fixed; if (halfextreme) { dashpercent = globaldash/(numberdashes_fixed -1); } double holepercent = globalhole/numberholes; double dashsize_fixed = 0; double holesize_fixed = 0; Geom::Piecewise > pwd2 = path_it.toPwSb(); double length_pwd2 = length (pwd2); double minlength = length_pwd2; if(unifysegment) { while (curve_it1 != curve_endit) { double length_segment = (*curve_it1).length(); if (length_segment < minlength) { minlength = length_segment; dashsize_fixed = (*curve_it1).length() * dashpercent; holesize_fixed = (*curve_it1).length() * holepercent; } ++curve_it1; ++curve_it2; } curve_it1 = path_it.begin(); curve_it2 = ++(path_it.begin()); curve_endit = path_it.end_default(); } size_t p_index = 0; size_t start_index = result.size(); if(splitsegments) { while (curve_it1 != curve_endit) { Geom::Path segment = path_it.portion(p_index, p_index + 1); if(unifysegment) { double integral; modf((*curve_it1).length()/(dashsize_fixed + holesize_fixed), &integral); numberdashes_fixed = (size_t)integral + 1; numberholes = numberdashes_fixed - 1; ammount = numberdashes_fixed + numberholes; if (halfextreme) { ammount--; } base = 1/(double)ammount; globaldash = base * numberdashes_fixed * (1 + holefactor); if (halfextreme) { globaldash = base * (numberdashes_fixed - 1) * (1 + holefactor); } globalhole = 1-globaldash; dashpercent = globaldash/numberdashes_fixed; if (halfextreme) { dashpercent = globaldash/(numberdashes_fixed -1); } holepercent = globalhole/numberholes; } double dashsize = (*curve_it1).length() * dashpercent; double holesize = (*curve_it1).length() * holepercent; if ((*curve_it1).isLineSegment()) { if (result.size() && Geom::are_near(segment.initialPoint(),result[result.size()-1].finalPoint())) { result[result.size()-1].setFinal(segment.initialPoint()); if (halfextreme) { result[result.size()-1].append(segment.portion(0.0, dashpercent/2.0)); } else { result[result.size()-1].append(segment.portion(0.0, dashpercent)); } } else { if (halfextreme) { result.push_back(segment.portion(0.0, dashpercent/2.0)); } else { result.push_back(segment.portion(0.0, dashpercent)); } } double start = dashpercent + holepercent; if (halfextreme) { start = (dashpercent/2.0) + holepercent; } while (start < 1) { if (start + dashpercent > 1) { result.push_back(segment.portion(start, 1)); } else { result.push_back(segment.portion(start, start + dashpercent)); } start += dashpercent + holepercent; } } else if (!(*curve_it1).isLineSegment()) { double start = 0.0; double end = 0.0; if (halfextreme) { end = timeAtLength(dashsize/2.0,segment); } else { end = timeAtLength(dashsize,segment); } if (result.size() && Geom::are_near(segment.initialPoint(),result[result.size()-1].finalPoint())) { result[result.size()-1].setFinal(segment.initialPoint()); result[result.size()-1].append(segment.portion(start, end)); } else { result.push_back(segment.portion(start, end)); } double startsize = dashsize + holesize; if (halfextreme) { startsize = (dashsize/2.0) + holesize; } double endsize = startsize + dashsize; start = timeAtLength(startsize,segment); end = timeAtLength(endsize,segment); while (start < 1 && start > 0) { result.push_back(segment.portion(start, end)); startsize = endsize + holesize; endsize = startsize + dashsize; start = timeAtLength(startsize,segment); end = timeAtLength(endsize,segment); } } if (curve_it2 == curve_endit) { if (path_it.closed()) { Geom::Path end = result[result.size()-1]; end.setFinal(result[start_index].initialPoint()); end.append(result[start_index]); result[start_index] = end; } } p_index ++; ++curve_it1; ++curve_it2; } } else { double start = 0.0; double end = 0.0; double dashsize = length_pwd2 * dashpercent; double holesize = length_pwd2 * holepercent; if (halfextreme) { end = timeAtLength(dashsize/2.0,pwd2); } else { end = timeAtLength(dashsize,pwd2); } result.push_back(path_it.portion(start, end)); double startsize = dashsize + holesize; if (halfextreme) { startsize = (dashsize/2.0) + holesize; } double endsize = startsize + dashsize; start = timeAtLength(startsize,pwd2); end = timeAtLength(endsize,pwd2); while (start < path_it.size() && start > 0) { result.push_back(path_it.portion(start, end)); startsize = endsize + holesize; endsize = startsize + dashsize; start = timeAtLength(startsize,pwd2); end = timeAtLength(endsize,pwd2); } if (path_it.closed()) { Geom::Path end = result[result.size()-1]; end.setFinal(result[start_index].initialPoint()); end.append(result[start_index]); result[start_index] = end; } } } return result; } }; //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 :