1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Author(s):
4  *   Jabiertxo Arraiza Cenoz <jabier.arraiza@marker.es>
5  *
6  * Copyright (C) 2014 Author(s)
7  *
8  *
9  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
10  */
11 
12 #include <2geom/elliptical-arc.h>
13 #include <boost/optional.hpp>
14 
15 #include "lpe-fillet-chamfer.h"
16 
17 #include "display/curve.h"
18 
19 #include "helper/geom-curves.h"
20 #include "helper/geom-satellite.h"
21 #include "helper/geom.h"
22 
23 #include "object/sp-shape.h"
24 
25 #include "ui/knot/knot-holder.h"
26 #include "ui/tools/tool-base.h"
27 
28 // TODO due to internal breakage in glibmm headers, this must be last:
29 #include <glibmm/i18n.h>
30 
31 namespace Inkscape {
32 namespace LivePathEffect {
33 
34 static const Util::EnumData<Filletmethod> FilletmethodData[] = {
35     { FM_AUTO, N_("Auto"), "auto" },
36     { FM_ARC, N_("Force arc"), "arc" },
37     { FM_BEZIER, N_("Force bezier"), "bezier" }
38 };
39 static const Util::EnumDataConverter<Filletmethod> FMConverter(FilletmethodData, FM_END);
40 
LPEFilletChamfer(LivePathEffectObject * lpeobject)41 LPEFilletChamfer::LPEFilletChamfer(LivePathEffectObject *lpeobject)
42     : Effect(lpeobject),
43       unit(_("Unit:"), _("Unit"), "unit", &wr, this, "px"),
44       satellites_param("Satellites_param", "Satellites_param",
45                        "satellites_param", &wr, this),
46       method(_("Method:"), _("Method to calculate the fillet or chamfer"),
47              "method", FMConverter, &wr, this, FM_AUTO),
48       mode(_("Mode:"), _("Mode, e.g. fillet or chamfer"),
49              "mode", &wr, this, "F", true),
50       radius(_("Radius:"), _("Radius, in unit or %"), "radius", &wr,
51              this, 0.0),
52       chamfer_steps(_("Chamfer steps:"), _("Chamfer steps"), "chamfer_steps",
53                     &wr, this, 1),
54       flexible(_("Radius in %"), _("Flexible radius size (%)"),
55                "flexible", &wr, this, false),
56       only_selected(_("Change only selected nodes"),
57                     _("Change only selected nodes"), "only_selected", &wr, this,
58                     false),
59       use_knot_distance(_("Use knots distance instead radius"),
60                         _("Use knots distance instead radius"),
61                         "use_knot_distance", &wr, this, true),
62       hide_knots(_("Hide knots"), _("Hide knots"), "hide_knots", &wr, this,
63                  false),
64       apply_no_radius(_("Apply changes if radius = 0"), _("Apply changes if radius = 0"), "apply_no_radius", &wr, this, true),
65       apply_with_radius(_("Apply changes if radius > 0"), _("Apply changes if radius > 0"), "apply_with_radius", &wr, this, true),
66       _pathvector_satellites(nullptr),
67       _degenerate_hide(false)
68 {
69     registerParameter(&satellites_param);
70     registerParameter(&unit);
71     registerParameter(&method);
72     registerParameter(&mode);
73     registerParameter(&radius);
74     registerParameter(&chamfer_steps);
75     registerParameter(&flexible);
76     registerParameter(&use_knot_distance);
77     registerParameter(&apply_no_radius);
78     registerParameter(&apply_with_radius);
79     registerParameter(&only_selected);
80     registerParameter(&hide_knots);
81 
82     radius.param_set_range(0.0, std::numeric_limits<double>::max());
83     radius.param_set_increments(1, 1);
84     radius.param_set_digits(4);
85     radius.param_set_undo(false);
86     chamfer_steps.param_set_range(1, std::numeric_limits<gint>::max());
87     chamfer_steps.param_set_increments(1, 1);
88     chamfer_steps.param_make_integer();
89     _provides_knotholder_entities = true;
90     helperpath = false;
91     previous_unit = Glib::ustring("");
92 }
93 
doOnApply(SPLPEItem const * lpeItem)94 void LPEFilletChamfer::doOnApply(SPLPEItem const *lpeItem)
95 {
96     SPLPEItem *splpeitem = const_cast<SPLPEItem *>(lpeItem);
97     SPShape *shape = dynamic_cast<SPShape *>(splpeitem);
98     if (shape) {
99         Geom::PathVector const pathv = pathv_to_linear_and_cubic_beziers(shape->curve()->get_pathvector());
100         Satellites satellites;
101         double power = radius;
102         if (!flexible) {
103             SPDocument *document = getSPDoc();
104             Glib::ustring display_unit = document->getDisplayUnit()->abbr.c_str();
105             power = Inkscape::Util::Quantity::convert(power, unit.get_abbreviation(), display_unit.c_str());
106         }
107         SatelliteType satellite_type = FILLET;
108         std::map<std::string, SatelliteType> gchar_map_to_satellite_type =
109         boost::assign::map_list_of("F", FILLET)("IF", INVERSE_FILLET)("C", CHAMFER)("IC", INVERSE_CHAMFER)("KO", INVALID_SATELLITE);
110         auto mode_str = mode.param_getSVGValue();
111         std::map<std::string, SatelliteType>::iterator it = gchar_map_to_satellite_type.find(mode_str.raw());
112         if (it != gchar_map_to_satellite_type.end()) {
113             satellite_type = it->second;
114         }
115         Geom::PathVector pathvres;
116         for (const auto & path_it : pathv) {
117             if (path_it.empty() || count_path_nodes(path_it) < 2) {
118                 continue;
119             }
120             std::vector<Satellite> subpath_satellites;
121             Geom::Path::const_iterator curve_it = path_it.begin();
122             Geom::Path::const_iterator curve_endit = path_it.end_default();
123             if (path_it.closed()) {
124                 const Geom::Curve &closingline = path_it.back_closed();
125                 // the closing line segment is always of type
126                 // Geom::LineSegment.
127                 if (are_near(closingline.initialPoint(), closingline.finalPoint())) {
128                     // closingline.isDegenerate() did not work, because it only checks for
129                     // *exact* zero length, which goes wrong for relative coordinates and
130                     // rounding errors...
131                     // the closing line segment has zero-length. So stop before that one!
132                     curve_endit = path_it.end_open();
133                 }
134             }
135             Geom::Path pathresult(curve_it->initialPoint());
136             while (curve_it != curve_endit) {
137                 if (pathresult.size()) {
138                     pathresult.setFinal(curve_it->initialPoint());
139                 }
140                 pathresult.append(*curve_it);
141                 ++curve_it;
142                 Satellite satellite(satellite_type);
143                 satellite.setSteps(chamfer_steps);
144                 satellite.setAmount(power);
145                 satellite.setIsTime(flexible);
146                 satellite.setHasMirror(true);
147                 satellite.setHidden(hide_knots);
148                 subpath_satellites.push_back(satellite);
149             }
150 
151             //we add the last satellite on open path because _pathvector_satellites is related to nodes, not curves
152             //so maybe in the future we can need this last satellite in other effects
153             //don't remove for this effect because _pathvector_satellites class has methods when the path is modified
154             //and we want one method for all uses
155             if (!path_it.closed()) {
156                 Satellite satellite(satellite_type);
157                 satellite.setSteps(chamfer_steps);
158                 satellite.setAmount(power);
159                 satellite.setIsTime(flexible);
160                 satellite.setHasMirror(true);
161                 satellite.setHidden(hide_knots);
162                 subpath_satellites.push_back(satellite);
163             }
164             pathresult.close(path_it.closed());
165             pathvres.push_back(pathresult);
166             pathresult.clear();
167             satellites.push_back(subpath_satellites);
168         }
169         _pathvector_satellites = new PathVectorSatellites();
170         _pathvector_satellites->setPathVector(pathvres);
171         _pathvector_satellites->setSatellites(satellites);
172         satellites_param.setPathVectorSatellites(_pathvector_satellites);
173     } else {
174         g_warning("LPE Fillet/Chamfer can only be applied to shapes (not groups).");
175         SPLPEItem *item = const_cast<SPLPEItem *>(lpeItem);
176         item->removeCurrentPathEffect(false);
177     }
178 }
179 
newWidget()180 Gtk::Widget *LPEFilletChamfer::newWidget()
181 {
182     // use manage here, because after deletion of Effect object, others might
183     // still be pointing to this widget.
184     Gtk::Box *vbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
185 
186     vbox->set_border_width(5);
187     vbox->set_homogeneous(false);
188     vbox->set_spacing(2);
189     std::vector<Parameter *>::iterator it = param_vector.begin();
190     while (it != param_vector.end()) {
191         if ((*it)->widget_is_visible) {
192             Parameter *param = *it;
193             Gtk::Widget *widg = param->param_newWidget();
194             if (param->param_key == "radius") {
195                 Inkscape::UI::Widget::Scalar *widg_registered =
196                     Gtk::manage(dynamic_cast<Inkscape::UI::Widget::Scalar *>(widg));
197                 widg_registered->signal_value_changed().connect(
198                     sigc::mem_fun(*this, &LPEFilletChamfer::updateAmount));
199                 widg = widg_registered;
200                 if (widg) {
201                     Gtk::Box *scalar_parameter = dynamic_cast<Gtk::Box *>(widg);
202                     std::vector<Gtk::Widget *> childList = scalar_parameter->get_children();
203                     Gtk::Entry *entry_widget = dynamic_cast<Gtk::Entry *>(childList[1]);
204                     entry_widget->set_width_chars(6);
205                 }
206             } else if (param->param_key == "chamfer_steps") {
207                 Inkscape::UI::Widget::Scalar *widg_registered =
208                     Gtk::manage(dynamic_cast<Inkscape::UI::Widget::Scalar *>(widg));
209                 widg_registered->signal_value_changed().connect(
210                     sigc::mem_fun(*this, &LPEFilletChamfer::updateChamferSteps));
211                 widg = widg_registered;
212                 if (widg) {
213                     Gtk::Box *scalar_parameter = dynamic_cast<Gtk::Box *>(widg);
214                     std::vector<Gtk::Widget *> childList = scalar_parameter->get_children();
215                     Gtk::Entry *entry_widget = dynamic_cast<Gtk::Entry *>(childList[1]);
216                     entry_widget->set_width_chars(3);
217                 }
218             } else if (param->param_key == "only_selected") {
219                 Gtk::manage(widg);
220             }
221             Glib::ustring *tip = param->param_getTooltip();
222             if (widg) {
223                 vbox->pack_start(*widg, true, true, 2);
224                 if (tip) {
225                     widg->set_tooltip_text(*tip);
226                 } else {
227                     widg->set_tooltip_text("");
228                     widg->set_has_tooltip(false);
229                 }
230             }
231         }
232         ++it;
233     }
234 
235     Gtk::Box *fillet_container = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0));
236     Gtk::Button *fillet =  Gtk::manage(new Gtk::Button(Glib::ustring(_("Fillet"))));
237     fillet->signal_clicked()
238     .connect(sigc::bind<SatelliteType>(sigc::mem_fun(*this, &LPEFilletChamfer::updateSatelliteType),FILLET));
239 
240     fillet_container->pack_start(*fillet, true, true, 2);
241     Gtk::Button *inverse_fillet = Gtk::manage(new Gtk::Button(Glib::ustring(_("Inverse fillet"))));
242     inverse_fillet->signal_clicked()
243     .connect(sigc::bind<SatelliteType>(sigc::mem_fun(*this, &LPEFilletChamfer::updateSatelliteType),INVERSE_FILLET));
244     fillet_container->pack_start(*inverse_fillet, true, true, 2);
245 
246     Gtk::Box *chamfer_container = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0));
247     Gtk::Button *chamfer = Gtk::manage(new Gtk::Button(Glib::ustring(_("Chamfer"))));
248     chamfer->signal_clicked()
249     .connect(sigc::bind<SatelliteType>(sigc::mem_fun(*this, &LPEFilletChamfer::updateSatelliteType),CHAMFER));
250 
251     chamfer_container->pack_start(*chamfer, true, true, 2);
252     Gtk::Button *inverse_chamfer = Gtk::manage(new Gtk::Button(Glib::ustring(_("Inverse chamfer"))));
253     inverse_chamfer->signal_clicked()
254     .connect(sigc::bind<SatelliteType>(sigc::mem_fun(*this, &LPEFilletChamfer::updateSatelliteType),INVERSE_CHAMFER));
255     chamfer_container->pack_start(*inverse_chamfer, true, true, 2);
256 
257     vbox->pack_start(*fillet_container, true, true, 2);
258     vbox->pack_start(*chamfer_container, true, true, 2);
259     if(Gtk::Widget* widg = defaultParamSet()) {
260         vbox->pack_start(*widg, true, true, 2);
261     }
262     return vbox;
263 }
264 
refreshKnots()265 void LPEFilletChamfer::refreshKnots()
266 {
267     if (satellites_param._knoth) {
268         satellites_param._knoth->update_knots();
269     }
270 }
271 
updateAmount()272 void LPEFilletChamfer::updateAmount()
273 {
274     setSelected(_pathvector_satellites);
275     double power = radius;
276     if (!flexible) {
277         SPDocument *document = getSPDoc();
278         Glib::ustring display_unit = document->getDisplayUnit()->abbr.c_str();
279         power = Inkscape::Util::Quantity::convert(power, unit.get_abbreviation(), display_unit.c_str());
280     }
281     _pathvector_satellites->updateAmount(power, apply_no_radius, apply_with_radius, only_selected,
282                                          use_knot_distance, flexible);
283     satellites_param.setPathVectorSatellites(_pathvector_satellites);
284 }
285 
updateChamferSteps()286 void LPEFilletChamfer::updateChamferSteps()
287 {
288     setSelected(_pathvector_satellites);
289     _pathvector_satellites->updateSteps(chamfer_steps, apply_no_radius, apply_with_radius, only_selected);
290     satellites_param.setPathVectorSatellites(_pathvector_satellites);
291 }
292 
updateSatelliteType(SatelliteType satellitetype)293 void LPEFilletChamfer::updateSatelliteType(SatelliteType satellitetype)
294 {
295     std::map<SatelliteType, gchar const *> satellite_type_to_gchar_map =
296     boost::assign::map_list_of(FILLET, "F")(INVERSE_FILLET, "IF")(CHAMFER, "C")(INVERSE_CHAMFER, "IC")(INVALID_SATELLITE, "KO");
297     mode.param_setValue((Glib::ustring)satellite_type_to_gchar_map.at(satellitetype));
298     setSelected(_pathvector_satellites);
299     _pathvector_satellites->updateSatelliteType(satellitetype, apply_no_radius, apply_with_radius, only_selected);
300     satellites_param.setPathVectorSatellites(_pathvector_satellites);
301 }
302 
setSelected(PathVectorSatellites * _pathvector_satellites)303 void LPEFilletChamfer::setSelected(PathVectorSatellites *_pathvector_satellites){
304     std::vector<SPLPEItem *> lpeitems = getCurrrentLPEItems();
305     if (lpeitems.size() == 1) {
306         sp_lpe_item = lpeitems[0];
307         if (!_pathvector_satellites) {
308             sp_lpe_item_update_patheffect(sp_lpe_item, false, false);
309         } else {
310             Geom::PathVector const pathv = _pathvector_satellites->getPathVector();
311             Satellites satellites = _pathvector_satellites->getSatellites();
312             for (size_t i = 0; i < satellites.size(); ++i) {
313                 for (size_t j = 0; j < satellites[i].size(); ++j) {
314                     Geom::Curve const &curve_in = pathv[i][j];
315                     if (only_selected && isNodePointSelected(curve_in.initialPoint()) ){
316                         satellites[i][j].setSelected(true);
317                     } else {
318                         satellites[i][j].setSelected(false);
319                     }
320                 }
321             }
322             _pathvector_satellites->setSatellites(satellites);
323         }
324     }
325 }
326 
doBeforeEffect(SPLPEItem const * lpeItem)327 void LPEFilletChamfer::doBeforeEffect(SPLPEItem const *lpeItem)
328 {
329     if (!pathvector_before_effect.empty()) {
330         //fillet chamfer specific calls
331         satellites_param.setUseDistance(use_knot_distance);
332         satellites_param.setCurrentZoom(current_zoom);
333         //mandatory call
334         satellites_param.setEffectType(effectType());
335         Geom::PathVector const pathv = pathv_to_linear_and_cubic_beziers(pathvector_before_effect);
336         Geom::PathVector pathvres;
337         for (const auto &path_it : pathv) {
338             if (path_it.empty() || count_path_nodes(path_it) < 2) {
339                 continue;
340             }
341             Geom::Path::const_iterator curve_it = path_it.begin();
342             Geom::Path::const_iterator curve_endit = path_it.end_default();
343             if (path_it.closed()) {
344                 const Geom::Curve &closingline = path_it.back_closed();
345                 // the closing line segment is always of type
346                 // Geom::LineSegment.
347                 if (are_near(closingline.initialPoint(), closingline.finalPoint())) {
348                     // closingline.isDegenerate() did not work, because it only checks for
349                     // *exact* zero length, which goes wrong for relative coordinates and
350                     // rounding errors...
351                     // the closing line segment has zero-length. So stop before that one!
352                     curve_endit = path_it.end_open();
353                 }
354             }
355             Geom::Path pathresult(curve_it->initialPoint());
356             while (curve_it != curve_endit) {
357                 if (pathresult.size()) {
358                     pathresult.setFinal(curve_it->initialPoint());
359                 }
360                 if (Geom::are_near((*curve_it).initialPoint(), (*curve_it).finalPoint())) {
361                     return;
362                 }
363                 pathresult.append(*curve_it);
364                 ++curve_it;
365             }
366             pathresult.close(path_it.closed());
367             pathvres.push_back(pathresult);
368             pathresult.clear();
369         } // if are different sizes call to recalculate
370         Satellites satellites = satellites_param.data();
371         if (satellites.empty()) {
372             doOnApply(lpeItem); // dont want _impl to not update versioning
373             satellites = satellites_param.data();
374         }
375         bool write = false;
376         if (_pathvector_satellites) {
377             size_t number_nodes = count_pathvector_nodes(pathvres);
378             size_t previous_number_nodes = _pathvector_satellites->getTotalSatellites();
379             if (number_nodes != previous_number_nodes) {
380                 double power = radius;
381                 if (!flexible) {
382                     SPDocument *document = getSPDoc();
383                     Glib::ustring display_unit = document->getDisplayUnit()->abbr.c_str();
384                     power = Inkscape::Util::Quantity::convert(power, unit.get_abbreviation(), display_unit.c_str());
385                 }
386                 SatelliteType satellite_type = FILLET;
387                 std::map<std::string, SatelliteType> gchar_map_to_satellite_type =
388                 boost::assign::map_list_of("F", FILLET)("IF", INVERSE_FILLET)("C", CHAMFER)("IC", INVERSE_CHAMFER)("KO", INVALID_SATELLITE);
389                 auto mode_str = mode.param_getSVGValue();
390                 std::map<std::string, SatelliteType>::iterator it = gchar_map_to_satellite_type.find(mode_str.raw());
391                 if (it != gchar_map_to_satellite_type.end()) {
392                     satellite_type = it->second;
393                 }
394                 Satellite satellite(satellite_type);
395                 satellite.setSteps(chamfer_steps);
396                 satellite.setAmount(power);
397                 satellite.setIsTime(flexible);
398                 satellite.setHasMirror(true);
399                 satellite.setHidden(hide_knots);
400                 _pathvector_satellites->recalculateForNewPathVector(pathvres, satellite);
401                 satellites = _pathvector_satellites->getSatellites();
402                 write = true;
403             }
404         }
405 
406         if (_degenerate_hide) {
407             satellites_param.setGlobalKnotHide(true);
408         } else {
409             satellites_param.setGlobalKnotHide(false);
410         }
411         for (size_t i = 0; i < satellites.size(); ++i) {
412             for (size_t j = 0; j < satellites[i].size(); ++j) {
413                 if (j >= count_path_nodes(pathvres[i])) {
414                     // we are on the end of a open path
415                     // for the moment we dont want to use
416                     // this satellite so simplest do nothing with it
417                     continue;
418                 }
419                 Geom::Curve const &curve_in = pathvres[i][j];
420                 if (satellites[i][j].is_time != flexible) {
421                     satellites[i][j].is_time = flexible;
422                     double amount = satellites[i][j].amount;
423                     if (satellites[i][j].is_time) {
424                         double time = timeAtArcLength(amount, curve_in);
425                         satellites[i][j].amount = time;
426                     } else {
427                         double size = arcLengthAt(amount, curve_in);
428                         satellites[i][j].amount = size;
429                     }
430                 }
431                 satellites[i][j].hidden = hide_knots;
432                 if (only_selected && isNodePointSelected(curve_in.initialPoint()) ){
433                     satellites[i][j].setSelected(true);
434                 }
435             }
436             if (!pathvres[i].closed()) {
437                 satellites[i][0].amount = 0;
438                 satellites[i][count_path_nodes(pathvres[i]) - 1].amount = 0;
439             }
440         }
441         if (!_pathvector_satellites) {
442             _pathvector_satellites = new PathVectorSatellites();
443         }
444         _pathvector_satellites->setPathVector(pathvres);
445         _pathvector_satellites->setSatellites(satellites);
446         satellites_param.setPathVectorSatellites(_pathvector_satellites, write);
447         size_t number_nodes = count_pathvector_nodes(pathvres);
448         size_t previous_number_nodes = _pathvector_satellites->getTotalSatellites();
449         if (number_nodes != previous_number_nodes) {
450             doOnApply(lpeItem); // dont want _impl to not update versioning
451             satellites = satellites_param.data();
452             satellites_param.setPathVectorSatellites(_pathvector_satellites, write);
453         }
454         Glib::ustring current_unit = Glib::ustring(unit.get_abbreviation());
455         if (previous_unit != current_unit && previous_unit != "") {
456             updateAmount();
457         }
458         if (write) {
459             satellites_param.reloadKnots();
460         } else {
461             refreshKnots();
462         }
463         previous_unit = current_unit;
464     } else {
465         g_warning("LPE Fillet can only be applied to shapes (not groups).");
466     }
467 }
468 
469 void
addCanvasIndicators(SPLPEItem const *,std::vector<Geom::PathVector> & hp_vec)470 LPEFilletChamfer::addCanvasIndicators(SPLPEItem const */*lpeitem*/, std::vector<Geom::PathVector> &hp_vec)
471 {
472     hp_vec.push_back(_hp);
473 }
474 
475 void
addChamferSteps(Geom::Path & tmp_path,Geom::Path path_chamfer,Geom::Point end_arc_point,size_t steps)476 LPEFilletChamfer::addChamferSteps(Geom::Path &tmp_path, Geom::Path path_chamfer, Geom::Point end_arc_point, size_t steps)
477 {
478     setSelected(_pathvector_satellites);
479     double path_subdivision = 1.0 / steps;
480     for (size_t i = 1; i < steps; i++) {
481         Geom::Point chamfer_step = path_chamfer.pointAt(path_subdivision * i);
482         tmp_path.appendNew<Geom::LineSegment>(chamfer_step);
483     }
484     tmp_path.appendNew<Geom::LineSegment>(end_arc_point);
485 }
486 
487 Geom::PathVector
doEffect_path(Geom::PathVector const & path_in)488 LPEFilletChamfer::doEffect_path(Geom::PathVector const &path_in)
489 {
490     const double GAP_HELPER = 0.00001;
491     Geom::PathVector path_out;
492     size_t path = 0;
493     const double K = (4.0 / 3.0) * (sqrt(2.0) - 1.0);
494     _degenerate_hide = false;
495     Geom::PathVector const pathv = _pathvector_satellites->getPathVector();
496     Satellites satellites = _pathvector_satellites->getSatellites();
497     for (const auto &path_it : pathv) {
498         Geom::Path tmp_path;
499 
500         double time0 = 0;
501         size_t curve = 0;
502         Geom::Path::const_iterator curve_it1 = path_it.begin();
503         Geom::Path::const_iterator curve_endit = path_it.end_default();
504         if (path_it.closed()) {
505             const Geom::Curve &closingline = path_it.back_closed();
506             // the closing line segment is always of type
507             // Geom::LineSegment.
508             if (are_near(closingline.initialPoint(), closingline.finalPoint())) {
509                 // closingline.isDegenerate() did not work, because it only checks for
510                 // *exact* zero length, which goes wrong for relative coordinates and
511                 // rounding errors...
512                 // the closing line segment has zero-length. So stop before that one!
513                 curve_endit = path_it.end_open();
514             }
515         }
516         while (curve_it1 != curve_endit) {
517             size_t next_index = curve + 1;
518             if (curve == count_path_nodes(pathv[path]) - 1 && pathv[path].closed()) {
519                 next_index = 0;
520             }
521             //append last extreme of paths on open paths
522             if (curve == count_path_nodes(pathv[path]) - 1 && !pathv[path].closed()) { // the path is open and we are at
523                                                                                        // end of path
524                 if (time0 != 1) { //Previous satellite not at 100% amount
525                     Geom::Curve *last_curve = curve_it1->portion(time0, 1);
526                     last_curve->setInitial(tmp_path.finalPoint());
527                     tmp_path.append(*last_curve);
528                 }
529                 ++curve_it1;
530                 continue;
531             }
532             Geom::Curve const &curve_it2 = pathv[path][next_index];
533             Satellite satellite = satellites[path][next_index];
534             if (Geom::are_near((*curve_it1).initialPoint(), (*curve_it1).finalPoint())) {
535                 _degenerate_hide = true;
536                 g_warning("Knots hidden if consecutive nodes has the same position.");
537                 return path_in;
538             }
539             if (!curve) { //curve == 0
540                 if (!path_it.closed()) {
541                     time0 = 0;
542                 } else {
543                     time0 = satellites[path][0].time(*curve_it1);
544                 }
545             }
546             double s = satellite.arcDistance(curve_it2);
547             double time1 = satellite.time(s, true, (*curve_it1));
548             double time2 = satellite.time(curve_it2);
549             if (time1 <= time0) {
550                 time1 = time0;
551             }
552             if (time2 > 1) {
553                 time2 = 1;
554             }
555             Geom::Curve *knot_curve_1 = curve_it1->portion(time0, time1);
556             Geom::Curve *knot_curve_2 = curve_it2.portion(time2, 1);
557             if (curve > 0) {
558                 knot_curve_1->setInitial(tmp_path.finalPoint());
559             } else {
560                 tmp_path.start((*curve_it1).pointAt(time0));
561             }
562 
563             Geom::Point start_arc_point = knot_curve_1->finalPoint();
564             Geom::Point end_arc_point = curve_it2.pointAt(time2);
565             //add a gap helper
566             if (time2 == 1) {
567                 end_arc_point = curve_it2.pointAt(time2 - GAP_HELPER);
568             }
569             if (time1 == time0) {
570                 start_arc_point = curve_it1->pointAt(time1 + GAP_HELPER);
571             }
572 
573             double k1 = distance(start_arc_point, curve_it1->finalPoint()) * K;
574             double k2 = distance(curve_it2.initialPoint(), end_arc_point) * K;
575             Geom::CubicBezier const *cubic_1 = dynamic_cast<Geom::CubicBezier const *>(&*knot_curve_1);
576             Geom::CubicBezier const *cubic_2 = dynamic_cast<Geom::CubicBezier const *>(&*knot_curve_2);
577             Geom::Ray ray_1(start_arc_point, curve_it1->finalPoint());
578             Geom::Ray ray_2(curve_it2.initialPoint(), end_arc_point);
579             if (cubic_1) {
580                 ray_1.setPoints((*cubic_1)[2], start_arc_point);
581             }
582             if (cubic_2) {
583                 ray_2.setPoints(end_arc_point, (*cubic_2)[1]);
584             }
585             bool ccw_toggle = cross(curve_it1->finalPoint() - start_arc_point, end_arc_point - start_arc_point) < 0;
586             double angle = angle_between(ray_1, ray_2, ccw_toggle);
587             double handle_angle_1 = ray_1.angle() - angle;
588             double handle_angle_2 = ray_2.angle() + angle;
589             if (ccw_toggle) {
590                 handle_angle_1 = ray_1.angle() + angle;
591                 handle_angle_2 = ray_2.angle() - angle;
592             }
593             Geom::Point handle_1 = Geom::Point::polar(ray_1.angle(), k1) + start_arc_point;
594             Geom::Point handle_2 = end_arc_point - Geom::Point::polar(ray_2.angle(), k2);
595             Geom::Point inverse_handle_1 = Geom::Point::polar(handle_angle_1, k1) + start_arc_point;
596             Geom::Point inverse_handle_2 = end_arc_point - Geom::Point::polar(handle_angle_2, k2);
597             if (time0 == 1) {
598                 handle_1 = start_arc_point;
599                 inverse_handle_1 = start_arc_point;
600             }
601             //remove gap helper
602             if (time2 == 1) {
603                 end_arc_point = curve_it2.pointAt(time2);
604             }
605             if (time1 == time0) {
606                 start_arc_point = curve_it1->pointAt(time0);
607             }
608             if (time1 != 1 && !Geom::are_near(angle,Geom::rad_from_deg(360))) {
609                 if (time1 != time0 || (time1 == 1 && time0 == 1)) {
610                     if (!knot_curve_1->isDegenerate()) {
611                         tmp_path.append(*knot_curve_1);
612                     }
613                 }
614                 SatelliteType type = satellite.satellite_type;
615                 size_t steps = satellite.steps;
616                 if (!steps) steps = 1;
617                 Geom::Line const x_line(Geom::Point(0, 0), Geom::Point(1, 0));
618                 Geom::Line const angled_line(start_arc_point, end_arc_point);
619                 double arc_angle = Geom::angle_between(x_line, angled_line);
620                 double radius = Geom::distance(start_arc_point, middle_point(start_arc_point, end_arc_point)) /
621                                                sin(angle / 2.0);
622                 Geom::Coord rx = radius;
623                 Geom::Coord ry = rx;
624                 bool eliptical = (is_straight_curve(*curve_it1) &&
625                                   is_straight_curve(curve_it2) && method != FM_BEZIER) ||
626                                   method == FM_ARC;
627                 switch (type) {
628                 case CHAMFER:
629                     {
630                         Geom::Path path_chamfer;
631                         path_chamfer.start(tmp_path.finalPoint());
632                         if (eliptical) {
633                             ccw_toggle = ccw_toggle ? false : true;
634                             path_chamfer.appendNew<Geom::EllipticalArc>(rx, ry, arc_angle, false, ccw_toggle, end_arc_point);
635                         } else {
636                             path_chamfer.appendNew<Geom::CubicBezier>(handle_1, handle_2, end_arc_point);
637                         }
638                         addChamferSteps(tmp_path, path_chamfer, end_arc_point, steps);
639                     }
640                     break;
641                 case INVERSE_CHAMFER:
642                     {
643                         Geom::Path path_chamfer;
644                         path_chamfer.start(tmp_path.finalPoint());
645                         if (eliptical) {
646                             path_chamfer.appendNew<Geom::EllipticalArc>(rx, ry, arc_angle, false, ccw_toggle, end_arc_point);
647                         } else {
648                             path_chamfer.appendNew<Geom::CubicBezier>(inverse_handle_1, inverse_handle_2, end_arc_point);
649                         }
650                         addChamferSteps(tmp_path, path_chamfer, end_arc_point, steps);
651                     }
652                     break;
653                 case INVERSE_FILLET:
654                     {
655                         if (eliptical) {
656                             bool side = false;
657                             if (helperpath && !getSPDoc()->is_yaxisdown()) {
658                                 side = true;
659                                 ccw_toggle = ccw_toggle ? false : true;
660                             }
661                             tmp_path.appendNew<Geom::EllipticalArc>(rx, ry, arc_angle, side, ccw_toggle, end_arc_point);
662                         } else {
663                             tmp_path.appendNew<Geom::CubicBezier>(inverse_handle_1, inverse_handle_2, end_arc_point);
664                         }
665                     }
666                     break;
667                 default: //fillet
668                     {
669                         if (eliptical) {
670                             bool side = false;
671                             if (helperpath && !getSPDoc()->is_yaxisdown()) {
672                                 side = true;
673                             } else {
674                                 ccw_toggle = ccw_toggle ? false : true;
675                             }
676                             tmp_path.appendNew<Geom::EllipticalArc>(rx, ry, arc_angle, side, ccw_toggle, end_arc_point);
677                         } else {
678                             tmp_path.appendNew<Geom::CubicBezier>(handle_1, handle_2, end_arc_point);
679                         }
680                     }
681                     break;
682                 }
683             } else {
684                 if (!knot_curve_1->isDegenerate()) {
685                     tmp_path.append(*knot_curve_1);
686                 }
687             }
688             curve++;
689             ++curve_it1;
690             time0 = time2;
691         }
692         if (path_it.closed()) {
693             tmp_path.close();
694         }
695         path++;
696         path_out.push_back(tmp_path);
697     }
698     if (helperpath) {
699         _hp = path_out;
700         return pathvector_after_effect;
701     }
702     _hp.clear();
703     return path_out;
704 }
705 
706 }; //namespace LivePathEffect
707 }; /* namespace Inkscape */
708 
709 /*
710   Local Variables:
711   mode:c++
712   c-file-style:"stroustrup"
713   c-file-offset:((innamespace . 0)(inline-open . 0)(case-label . +))
714   indent-tabs-mode:nil
715   fill-column:99
716   End:
717 */
718 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
719