1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
4  */
5 #include "live_effects/lpe-dashed-stroke.h"
6 #include "2geom/path.h"
7 #include "2geom/pathvector.h"
8 #include "helper/geom.h"
9 
10 // TODO due to internal breakage in glibmm headers, this must be last:
11 #include <glibmm/i18n.h>
12 
13 namespace Inkscape {
14 namespace LivePathEffect {
15 
LPEDashedStroke(LivePathEffectObject * lpeobject)16 LPEDashedStroke::LPEDashedStroke(LivePathEffectObject *lpeobject)
17     : Effect(lpeobject)
18     , numberdashes(_("Number of dashes"), _("Number of dashes"), "numberdashes", &wr, this, 3)
19     , holefactor(_("Hole factor"), _("Hole factor"), "holefactor", &wr, this, 0.0)
20     , splitsegments(_("Use segments"), _("Use segments"), "splitsegments", &wr, this, true)
21     , halfextreme(_("Half start/end"), _("Start and end of each segment has half size"), "halfextreme", &wr, this, true)
22     , unifysegment(_("Equalize dashes"), _("Global dash length is approximately the length of the dashes in the shortest path segment"),
23                    "unifysegment", &wr, this, true)
24     , message(_("Note"), _("Important messages"), "message", &wr, this,
25               _("Add <b>\"Fill Between Many LPE\"</b> to add fill."))
26 {
27     registerParameter(&numberdashes);
28     registerParameter(&holefactor);
29     registerParameter(&splitsegments);
30     registerParameter(&halfextreme);
31     registerParameter(&unifysegment);
32     registerParameter(&message);
33     numberdashes.param_set_range(2, 999999999);
34     numberdashes.param_set_increments(1, 1);
35     numberdashes.param_set_digits(0);
36     holefactor.param_set_range(-0.99999, 0.99999);
37     holefactor.param_set_increments(0.01, 0.01);
38     holefactor.param_set_digits(5);
39     message.param_set_min_height(30);
40 }
41 
42 LPEDashedStroke::~LPEDashedStroke() = default;
43 
doBeforeEffect(SPLPEItem const * lpeitem)44 void LPEDashedStroke::doBeforeEffect(SPLPEItem const *lpeitem) {}
45 
46 ///Calculate the time in curve_in with a real time of A
47 //TODO: find a better place to it
timeAtLength(double const A,Geom::Path const & segment)48 double LPEDashedStroke::timeAtLength(double const A, Geom::Path const &segment)
49 {
50     if ( A == 0 || segment[0].isDegenerate()) {
51         return 0;
52     }
53     double t = 1;
54     t = timeAtLength(A, segment.toPwSb());
55     return t;
56 }
57 
58 ///Calculate the time in curve_in with a real time of A
59 //TODO: find a better place to it
timeAtLength(double const A,Geom::Piecewise<Geom::D2<Geom::SBasis>> pwd2)60 double LPEDashedStroke::timeAtLength(double const A, Geom::Piecewise<Geom::D2<Geom::SBasis>> pwd2)
61 {
62     if ( A == 0 || pwd2.size() == 0) {
63         return 0;
64     }
65 
66     double t = pwd2.size();
67     std::vector<double> t_roots = roots(Geom::arcLengthSb(pwd2) - A);
68     if (!t_roots.empty()) {
69         t = t_roots[0];
70     }
71     return t;
72 }
73 
doEffect_path(Geom::PathVector const & path_in)74 Geom::PathVector LPEDashedStroke::doEffect_path(Geom::PathVector const &path_in)
75 {
76     Geom::PathVector const pv = pathv_to_linear_and_cubic_beziers(path_in);
77     Geom::PathVector result;
78     for (const auto & path_it : pv) {
79         if (path_it.empty()) {
80             continue;
81         }
82         Geom::Path::const_iterator curve_it1 = path_it.begin();
83         Geom::Path::const_iterator curve_it2 = ++(path_it.begin());
84         Geom::Path::const_iterator curve_endit = path_it.end_default();
85         if (path_it.closed()) {
86           const Geom::Curve &closingline = path_it.back_closed();
87           // the closing line segment is always of type
88           // Geom::LineSegment.
89           if (are_near(closingline.initialPoint(), closingline.finalPoint())) {
90             // closingline.isDegenerate() did not work, because it only checks for
91             // *exact* zero length, which goes wrong for relative coordinates and
92             // rounding errors...
93             // the closing line segment has zero-length. So stop before that one!
94             curve_endit = path_it.end_open();
95           }
96         }
97         size_t numberdashes_fixed = numberdashes;
98         if(!splitsegments) {
99             numberdashes_fixed++;
100         }
101         size_t numberholes = numberdashes_fixed - 1;
102         size_t ammount = numberdashes_fixed + numberholes;
103         if (halfextreme) {
104             ammount--;
105         }
106         double base = 1/(double)ammount;
107         double globaldash =  base * numberdashes_fixed * (1 + holefactor);
108         if (halfextreme) {
109             globaldash =  base * (numberdashes_fixed - 1) * (1 + holefactor);
110         }
111         double globalhole =  1-globaldash;
112         double dashpercent = globaldash/numberdashes_fixed;
113         if (halfextreme) {
114            dashpercent = globaldash/(numberdashes_fixed -1);
115         }
116         double holepercent = globalhole/numberholes;
117         double dashsize_fixed = 0;
118         double holesize_fixed = 0;
119         Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2 = path_it.toPwSb();
120         double length_pwd2 = length (pwd2);
121         double minlength = length_pwd2;
122         if(unifysegment) {
123             while (curve_it1 != curve_endit) {
124                 double length_segment = (*curve_it1).length();
125                 if (length_segment < minlength) {
126                     minlength = length_segment;
127                     dashsize_fixed = (*curve_it1).length() * dashpercent;
128                     holesize_fixed = (*curve_it1).length() * holepercent;
129                 }
130                 ++curve_it1;
131                 ++curve_it2;
132             }
133             curve_it1 = path_it.begin();
134             curve_it2 = ++(path_it.begin());
135             curve_endit = path_it.end_default();
136         }
137         size_t p_index = 0;
138         size_t start_index = result.size();
139         if(splitsegments) {
140             while (curve_it1 != curve_endit) {
141                 Geom::Path segment = path_it.portion(p_index, p_index + 1);
142                 if(unifysegment) {
143                     double integral;
144                     modf((*curve_it1).length()/(dashsize_fixed + holesize_fixed), &integral);
145                     numberdashes_fixed = (size_t)integral + 1;
146                     numberholes = numberdashes_fixed - 1;
147                     ammount = numberdashes_fixed + numberholes;
148                     if (halfextreme) {
149                         ammount--;
150                     }
151                     base = 1/(double)ammount;
152                     globaldash =  base * numberdashes_fixed * (1 + holefactor);
153                     if (halfextreme) {
154                         globaldash =  base * (numberdashes_fixed - 1) * (1 + holefactor);
155                     }
156                     globalhole =  1-globaldash;
157                     dashpercent = globaldash/numberdashes_fixed;
158                     if (halfextreme) {
159                        dashpercent = globaldash/(numberdashes_fixed -1);
160                     }
161                     holepercent = globalhole/numberholes;
162                 }
163                 double dashsize = (*curve_it1).length() * dashpercent;
164                 double holesize = (*curve_it1).length() * holepercent;
165                 if ((*curve_it1).isLineSegment()) {
166                     if (result.size() && Geom::are_near(segment.initialPoint(),result[result.size()-1].finalPoint())) {
167                         result[result.size()-1].setFinal(segment.initialPoint());
168                         if (halfextreme) {
169                             result[result.size()-1].append(segment.portion(0.0, dashpercent/2.0));
170                         } else {
171                             result[result.size()-1].append(segment.portion(0.0, dashpercent));
172                         }
173                     } else {
174                         if (halfextreme) {
175                             result.push_back(segment.portion(0.0, dashpercent/2.0));
176                         } else {
177                             result.push_back(segment.portion(0.0, dashpercent));
178                         }
179                     }
180 
181                     double start = dashpercent + holepercent;
182                     if (halfextreme) {
183                         start = (dashpercent/2.0) + holepercent;
184                     }
185                     while (start  < 1) {
186                         if (start + dashpercent > 1) {
187                             result.push_back(segment.portion(start, 1));
188                         } else {
189                             result.push_back(segment.portion(start, start + dashpercent));
190                         }
191                         start += dashpercent + holepercent;
192                     }
193                 } else if (!(*curve_it1).isLineSegment()) {
194                     double start = 0.0;
195                     double end = 0.0;
196                     if (halfextreme) {
197                         end = timeAtLength(dashsize/2.0,segment);
198                     } else {
199                         end = timeAtLength(dashsize,segment);
200                     }
201                     if (result.size() && Geom::are_near(segment.initialPoint(),result[result.size()-1].finalPoint())) {
202                         result[result.size()-1].setFinal(segment.initialPoint());
203                         result[result.size()-1].append(segment.portion(start, end));
204                     } else {
205                         result.push_back(segment.portion(start, end));
206                     }
207                     double startsize = dashsize + holesize;
208                     if (halfextreme) {
209                         startsize = (dashsize/2.0) + holesize;
210                     }
211                     double endsize = startsize + dashsize;
212                     start = timeAtLength(startsize,segment);
213                     end   = timeAtLength(endsize,segment);
214                     while (start  < 1 && start  > 0) {
215                         result.push_back(segment.portion(start, end));
216                         startsize = endsize + holesize;
217                         endsize = startsize + dashsize;
218                         start = timeAtLength(startsize,segment);
219                         end   = timeAtLength(endsize,segment);
220                     }
221                 }
222                 if (curve_it2 == curve_endit) {
223                     if (path_it.closed()) {
224                         Geom::Path end = result[result.size()-1];
225                         end.setFinal(result[start_index].initialPoint());
226                         end.append(result[start_index]);
227                         result[start_index] = end;
228                     }
229                 }
230                 p_index ++;
231                 ++curve_it1;
232                 ++curve_it2;
233             }
234         } else {
235             double start = 0.0;
236             double end = 0.0;
237             double dashsize = length_pwd2 * dashpercent;
238             double holesize = length_pwd2 * holepercent;
239             if (halfextreme) {
240                 end = timeAtLength(dashsize/2.0,pwd2);
241             } else {
242                 end = timeAtLength(dashsize,pwd2);
243             }
244             result.push_back(path_it.portion(start, end));
245             double startsize = dashsize + holesize;
246             if (halfextreme) {
247                 startsize = (dashsize/2.0) + holesize;
248             }
249             double endsize = startsize + dashsize;
250             start = timeAtLength(startsize,pwd2);
251             end   = timeAtLength(endsize,pwd2);
252             while (start  < path_it.size() && start  > 0) {
253                 result.push_back(path_it.portion(start, end));
254                 startsize = endsize + holesize;
255                 endsize = startsize + dashsize;
256                 start = timeAtLength(startsize,pwd2);
257                 end   = timeAtLength(endsize,pwd2);
258             }
259             if (path_it.closed()) {
260                 Geom::Path end = result[result.size()-1];
261                 end.setFinal(result[start_index].initialPoint());
262                 end.append(result[start_index]);
263                 result[start_index] = end;
264             }
265         }
266     }
267     return result;
268 }
269 
270 }; //namespace LivePathEffect
271 }; /* namespace Inkscape */
272 
273 /*
274   Local Variables:
275   mode:c++
276   c-file-style:"stroustrup"
277   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
278   indent-tabs-mode:nil
279   fill-column:99
280   End:
281 */
282 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
283