1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * SVG <ellipse> and related implementations
4  *
5  * Authors:
6  *   Lauris Kaplinski <lauris@kaplinski.com>
7  *   Mitsuru Oka
8  *   bulia byak <buliabyak@users.sf.net>
9  *   Abhishek Sharma
10  *
11  * Copyright (C) 1999-2002 Lauris Kaplinski
12  * Copyright (C) 2000-2001 Ximian, Inc.
13  * Copyright (C) 2013 Tavmjong Bah
14  *
15  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
16  */
17 
18 #include <glibmm.h>
19 #include <glibmm/i18n.h>
20 
21 #include "live_effects/effect.h"
22 #include "live_effects/lpeobject.h"
23 #include "live_effects/lpeobject-reference.h"
24 
25 #include <2geom/angle.h>
26 #include <2geom/circle.h>
27 #include <2geom/ellipse.h>
28 #include <2geom/path-sink.h>
29 
30 #include "attributes.h"
31 #include "display/curve.h"
32 #include "document.h"
33 #include "preferences.h"
34 #include "snap-candidate.h"
35 #include "sp-ellipse.h"
36 #include "style.h"
37 #include "svg/svg.h"
38 #include "svg/path-string.h"
39 
40 #define SP_2PI (2 * M_PI)
41 
SPGenericEllipse()42 SPGenericEllipse::SPGenericEllipse()
43     : SPShape()
44     , start(0)
45     , end(SP_2PI)
46     , type(SP_GENERIC_ELLIPSE_UNDEFINED)
47     , arc_type(SP_GENERIC_ELLIPSE_ARC_TYPE_SLICE)
48 {
49 }
50 
51 SPGenericEllipse::~SPGenericEllipse()
52 = default;
53 
build(SPDocument * document,Inkscape::XML::Node * repr)54 void SPGenericEllipse::build(SPDocument *document, Inkscape::XML::Node *repr)
55 {
56     // std::cout << "SPGenericEllipse::build: Entrance: " << this->type
57     //           << "  ("  << g_quark_to_string(repr->code()) << ")" << std::endl;
58 
59     switch ( type ) {
60         case SP_GENERIC_ELLIPSE_ARC:
61             this->readAttr(SPAttr::SODIPODI_CX);
62             this->readAttr(SPAttr::SODIPODI_CY);
63             this->readAttr(SPAttr::SODIPODI_RX);
64             this->readAttr(SPAttr::SODIPODI_RY);
65             this->readAttr(SPAttr::SODIPODI_START);
66             this->readAttr(SPAttr::SODIPODI_END);
67             this->readAttr(SPAttr::SODIPODI_OPEN);
68             this->readAttr(SPAttr::SODIPODI_ARC_TYPE);
69             break;
70 
71         case SP_GENERIC_ELLIPSE_CIRCLE:
72             this->readAttr(SPAttr::CX);
73             this->readAttr(SPAttr::CY);
74             this->readAttr(SPAttr::R);
75             break;
76 
77         case SP_GENERIC_ELLIPSE_ELLIPSE:
78             this->readAttr(SPAttr::CX);
79             this->readAttr(SPAttr::CY);
80             this->readAttr(SPAttr::RX);
81             this->readAttr(SPAttr::RY);
82             break;
83 
84         default:
85             std::cerr << "SPGenericEllipse::build() unknown defined type." << std::endl;
86     }
87 
88     // std::cout << "    cx: " << cx.write() << std::endl;
89     // std::cout << "    cy: " << cy.write() << std::endl;
90     // std::cout << "    rx: " << rx.write() << std::endl;
91     // std::cout << "    ry: " << ry.write() << std::endl;
92     SPShape::build(document, repr);
93 }
94 
set(SPAttr key,gchar const * value)95 void SPGenericEllipse::set(SPAttr key, gchar const *value)
96 {
97     // There are multiple ways to set internal cx, cy, rx, and ry (via SVG attributes or Sodipodi
98     // attributes) thus we don't want to unset them if a read fails (e.g., when we explicitly clear
99     // an attribute by setting it to NULL).
100 
101     // We must update the SVGLengths immediately or nodes may be misplaced after they are moved.
102     double const w = viewport.width();
103     double const h = viewport.height();
104     double const d = hypot(w, h) / sqrt(2); // diagonal
105     double const em = style->font_size.computed;
106     double const ex = em * 0.5;
107 
108     SVGLength t;
109     switch (key) {
110     case SPAttr::CX:
111     case SPAttr::SODIPODI_CX:
112         if( t.read(value) ) cx = t;
113         cx.update( em, ex, w );
114         this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
115         break;
116 
117     case SPAttr::CY:
118     case SPAttr::SODIPODI_CY:
119         if( t.read(value) ) cy = t;
120         cy.update( em, ex, h );
121         this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
122         break;
123 
124     case SPAttr::RX:
125     case SPAttr::SODIPODI_RX:
126         if( t.read(value) && t.value > 0.0 ) rx = t;
127         rx.update( em, ex, w );
128         this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
129         break;
130 
131     case SPAttr::RY:
132     case SPAttr::SODIPODI_RY:
133         if( t.read(value) && t.value > 0.0 ) ry = t;
134         ry.update( em, ex, h );
135         this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
136         break;
137 
138     case SPAttr::R:
139         if( t.read(value) && t.value > 0.0 ) {
140             this->ry = this->rx = t;
141         }
142         rx.update( em, ex, d );
143         ry.update( em, ex, d );
144         this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
145         break;
146 
147     case SPAttr::SODIPODI_START:
148         if (value) {
149             sp_svg_number_read_d(value, &this->start);
150         } else {
151             this->start = 0;
152         }
153         this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
154         break;
155 
156     case SPAttr::SODIPODI_END:
157         if (value) {
158             sp_svg_number_read_d(value, &this->end);
159         } else {
160             this->end = 2 * M_PI;
161         }
162         this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
163         break;
164 
165     case SPAttr::SODIPODI_OPEN:
166         // This is for reading in old files.
167         if ((!value) || strcmp(value,"true")) {
168             this->arc_type = SP_GENERIC_ELLIPSE_ARC_TYPE_SLICE;
169         } else {
170             this->arc_type = SP_GENERIC_ELLIPSE_ARC_TYPE_ARC;
171         }
172         this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
173         break;
174 
175     case SPAttr::SODIPODI_ARC_TYPE:
176         // To read in old files that use 'open', we need to not set if value is null.
177         // We could also check inkscape version.
178         if (value) {
179             if (!strcmp(value,"arc")) {
180                 this->arc_type = SP_GENERIC_ELLIPSE_ARC_TYPE_ARC;
181             } else if (!strcmp(value,"chord")) {
182                 this->arc_type = SP_GENERIC_ELLIPSE_ARC_TYPE_CHORD;
183             } else {
184                 this->arc_type = SP_GENERIC_ELLIPSE_ARC_TYPE_SLICE;
185             }
186         }
187         this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
188         break;
189 
190     default:
191         SPShape::set(key, value);
192         break;
193     }
194 }
195 
update(SPCtx * ctx,guint flags)196 void SPGenericEllipse::update(SPCtx *ctx, guint flags)
197 {
198     // std::cout << "\nSPGenericEllipse::update: Entrance" << std::endl;
199     if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
200         Geom::Rect const &viewbox = ((SPItemCtx const *) ctx)->viewport;
201 
202         double const dx = viewbox.width();
203         double const dy = viewbox.height();
204         double const dr = hypot(dx, dy) / sqrt(2);
205         double const em = this->style->font_size.computed;
206         double const ex = em * 0.5; // fixme: get from pango or libnrtype
207 
208         this->cx.update(em, ex, dx);
209         this->cy.update(em, ex, dy);
210         this->rx.update(em, ex, dr);
211         this->ry.update(em, ex, dr);
212 
213         this->set_shape();
214     }
215 
216     SPShape::update(ctx, flags);
217     // std::cout << "SPGenericEllipse::update: Exit\n" << std::endl;
218 }
219 
write(Inkscape::XML::Document * xml_doc,Inkscape::XML::Node * repr,guint flags)220 Inkscape::XML::Node *SPGenericEllipse::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags)
221 {
222     // std::cout << "\nSPGenericEllipse::write: Entrance ("
223     //           << (repr == NULL ? " NULL" : g_quark_to_string(repr->code()))
224     //           << ")" << std::endl;
225 
226     GenericEllipseType new_type = SP_GENERIC_ELLIPSE_UNDEFINED;
227     if (_isSlice() || hasPathEffect() ) {
228         new_type = SP_GENERIC_ELLIPSE_ARC;
229     } else if ( rx.computed == ry.computed ) {
230         new_type = SP_GENERIC_ELLIPSE_CIRCLE;
231     } else {
232         new_type = SP_GENERIC_ELLIPSE_ELLIPSE;
233     }
234     // std::cout << "  new_type: " << new_type << std::endl;
235 
236     if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
237 
238         switch ( new_type ) {
239 
240             case SP_GENERIC_ELLIPSE_ARC:
241                 repr = xml_doc->createElement("svg:path");
242                 break;
243             case SP_GENERIC_ELLIPSE_CIRCLE:
244                 repr = xml_doc->createElement("svg:circle");
245                 break;
246             case SP_GENERIC_ELLIPSE_ELLIPSE:
247                 repr = xml_doc->createElement("svg:ellipse");
248                 break;
249             case SP_GENERIC_ELLIPSE_UNDEFINED:
250             default:
251                 std::cerr << "SPGenericEllipse::write(): unknown type." << std::endl;
252         }
253     }
254 
255     if (type != new_type) {
256         switch (new_type) {
257             case SP_GENERIC_ELLIPSE_ARC:
258                 repr->setCodeUnsafe(g_quark_from_string("svg:path"));
259                 break;
260             case SP_GENERIC_ELLIPSE_CIRCLE:
261                 repr->setCodeUnsafe(g_quark_from_string("svg:circle"));
262                 break;
263             case SP_GENERIC_ELLIPSE_ELLIPSE:
264                 repr->setCodeUnsafe(g_quark_from_string("svg:ellipse"));
265                 break;
266             default:
267                 std::cerr << "SPGenericEllipse::write(): unknown type." << std::endl;
268         }
269         type = new_type;
270     }
271 
272     // std::cout << "  type: " << g_quark_to_string( repr->code() ) << std::endl;
273     // std::cout << "  cx: " << cx.write() << " " << cx.computed
274     //           << "  cy: " << cy.write() << " " << cy.computed
275     //           << "  rx: " << rx.write() << " " << rx.computed
276     //           << "  ry: " << ry.write() << " " << ry.computed << std::endl;
277 
278     switch ( type ) {
279         case SP_GENERIC_ELLIPSE_UNDEFINED:
280         case SP_GENERIC_ELLIPSE_ARC:
281 
282             repr->removeAttribute("cx");
283             repr->removeAttribute("cy");
284             repr->removeAttribute("rx");
285             repr->removeAttribute("ry");
286             repr->removeAttribute("r");
287 
288             if (flags & SP_OBJECT_WRITE_EXT) {
289 
290                 repr->setAttribute("sodipodi:type", "arc");
291                 sp_repr_set_svg_length(repr, "sodipodi:cx", cx);
292                 sp_repr_set_svg_length(repr, "sodipodi:cy", cy);
293                 sp_repr_set_svg_length(repr, "sodipodi:rx", rx);
294                 sp_repr_set_svg_length(repr, "sodipodi:ry", ry);
295 
296                 // write start and end only if they are non-trivial; otherwise remove
297                 if (_isSlice()) {
298                     sp_repr_set_svg_double(repr, "sodipodi:start", start);
299                     sp_repr_set_svg_double(repr, "sodipodi:end", end);
300 
301                     switch ( arc_type ) {
302                         case SP_GENERIC_ELLIPSE_ARC_TYPE_SLICE:
303                             repr->removeAttribute("sodipodi:open"); // For backwards compat.
304                             repr->setAttribute("sodipodi:arc-type", "slice");
305                             break;
306                         case SP_GENERIC_ELLIPSE_ARC_TYPE_CHORD:
307                             // A chord's path isn't "open" but its fill most closely resembles an arc.
308                             repr->setAttribute("sodipodi:open", "true"); // For backwards compat.
309                             repr->setAttribute("sodipodi:arc-type", "chord");
310                             break;
311                         case SP_GENERIC_ELLIPSE_ARC_TYPE_ARC:
312                             repr->setAttribute("sodipodi:open", "true"); // For backwards compat.
313                             repr->setAttribute("sodipodi:arc-type", "arc");
314                             break;
315                         default:
316                             std::cerr << "SPGenericEllipse::write: unknown arc-type." << std::endl;
317                     }
318                 } else {
319                     repr->removeAttribute("sodipodi:end");
320                     repr->removeAttribute("sodipodi:start");
321                     repr->removeAttribute("sodipodi:open");
322                     repr->removeAttribute("sodipodi:arc-type");
323                 }
324             }
325 
326             // write d=
327             set_elliptical_path_attribute(repr);
328             break;
329 
330         case SP_GENERIC_ELLIPSE_CIRCLE:
331             sp_repr_set_svg_length(repr, "cx", cx);
332             sp_repr_set_svg_length(repr, "cy", cy);
333             sp_repr_set_svg_length(repr, "r",  rx);
334             repr->removeAttribute("rx");
335             repr->removeAttribute("ry");
336             repr->removeAttribute("sodipodi:cx");
337             repr->removeAttribute("sodipodi:cy");
338             repr->removeAttribute("sodipodi:rx");
339             repr->removeAttribute("sodipodi:ry");
340             repr->removeAttribute("sodipodi:end");
341             repr->removeAttribute("sodipodi:start");
342             repr->removeAttribute("sodipodi:open");
343             repr->removeAttribute("sodipodi:arc-type");
344             repr->removeAttribute("sodipodi:type");
345             repr->removeAttribute("d");
346             break;
347 
348         case SP_GENERIC_ELLIPSE_ELLIPSE:
349             sp_repr_set_svg_length(repr, "cx", cx);
350             sp_repr_set_svg_length(repr, "cy", cy);
351             sp_repr_set_svg_length(repr, "rx", rx);
352             sp_repr_set_svg_length(repr, "ry", ry);
353             repr->removeAttribute("r");
354             repr->removeAttribute("sodipodi:cx");
355             repr->removeAttribute("sodipodi:cy");
356             repr->removeAttribute("sodipodi:rx");
357             repr->removeAttribute("sodipodi:ry");
358             repr->removeAttribute("sodipodi:end");
359             repr->removeAttribute("sodipodi:start");
360             repr->removeAttribute("sodipodi:open");
361             repr->removeAttribute("sodipodi:arc-type");
362             repr->removeAttribute("sodipodi:type");
363             repr->removeAttribute("d");
364             break;
365 
366         default:
367             std::cerr << "SPGenericEllipse::write: unknown type." << std::endl;
368     }
369 
370     set_shape(); // evaluate SPCurve
371 
372     SPShape::write(xml_doc, repr, flags);
373 
374     return repr;
375 }
376 
displayName() const377 const char *SPGenericEllipse::displayName() const
378 {
379 
380     switch ( type ) {
381         case SP_GENERIC_ELLIPSE_UNDEFINED:
382         case SP_GENERIC_ELLIPSE_ARC:
383 
384             if (_isSlice()) {
385                 switch ( arc_type ) {
386                     case SP_GENERIC_ELLIPSE_ARC_TYPE_SLICE:
387                         return _("Slice");
388                         break;
389                     case SP_GENERIC_ELLIPSE_ARC_TYPE_CHORD:
390                         return _("Chord");
391                         break;
392                     case SP_GENERIC_ELLIPSE_ARC_TYPE_ARC:
393                         return _("Arc");
394                         break;
395                 }
396             } else {
397                 return _("Ellipse");
398             }
399 
400         case SP_GENERIC_ELLIPSE_CIRCLE:
401             return _("Circle");
402 
403         case SP_GENERIC_ELLIPSE_ELLIPSE:
404             return _("Ellipse");
405 
406         default:
407             return "Unknown ellipse: ERROR";
408     }
409     return ("Shouldn't be here");
410 }
411 
412 // Create path for rendering shape on screen
set_shape()413 void SPGenericEllipse::set_shape()
414 {
415     // std::cout << "SPGenericEllipse::set_shape: Entrance" << std::endl;
416     if (hasBrokenPathEffect()) {
417         g_warning("The ellipse shape has unknown LPE on it! Convert to path to make it editable preserving the appearance; editing it as ellipse will remove the bad LPE");
418 
419         if (this->getRepr()->attribute("d")) {
420             // unconditionally read the curve from d, if any, to preserve appearance
421             Geom::PathVector pv = sp_svg_read_pathv(this->getRepr()->attribute("d"));
422             setCurveInsync(std::make_unique<SPCurve>(pv));
423         }
424 
425         return;
426     }
427     if (Geom::are_near(this->rx.computed, 0) || Geom::are_near(this->ry.computed, 0)) {
428         return;
429     }
430 
431     this->normalize();
432 
433     // For simplicity, we use a circle with center (0, 0) and radius 1 for our calculations.
434     Geom::Circle circle(0, 0, 1);
435 
436     if (!this->_isSlice()) {
437         start = 0.0;
438         end = 2.0*M_PI;
439     }
440     double incr = end - start; // arc angle
441     if (incr < 0.0) incr += 2.0*M_PI;
442 
443     int numsegs = 1 + int(incr*2.0/M_PI); // number of arc segments
444     if (numsegs > 4) numsegs = 4;
445 
446     incr = incr/numsegs; // limit arc angle to less than 90 degrees
447     Geom::Path path(Geom::Point::polar(start));
448     Geom::EllipticalArc* arc;
449     for (int seg = 0; seg < numsegs; seg++) {
450         arc = circle.arc(Geom::Point::polar(start + seg*incr), Geom::Point::polar(start + (seg + 0.5)*incr), Geom::Point::polar(start + (seg + 1.0)*incr));
451         path.append(*arc);
452         delete arc;
453     }
454     Geom::PathBuilder pb;
455     pb.append(path);
456     if (this->_isSlice() && this->arc_type == SP_GENERIC_ELLIPSE_ARC_TYPE_SLICE) {
457         pb.lineTo(Geom::Point(0, 0));
458     }
459 
460     if ((this->arc_type != SP_GENERIC_ELLIPSE_ARC_TYPE_ARC) || (this->type != SP_GENERIC_ELLIPSE_ARC)) {
461         pb.closePath();
462     } else {
463         pb.flush();
464     }
465 
466     auto c = std::make_unique<SPCurve>(pb.peek());
467 
468     // gchar *str = sp_svg_write_path(curve->get_pathvector());
469     // std::cout << "  path: " << str << std::endl;
470     // g_free(str);
471 
472     // Stretching / moving the calculated shape to fit the actual dimensions.
473     Geom::Affine aff = Geom::Scale(rx.computed, ry.computed) * Geom::Translate(cx.computed, cy.computed);
474     c->transform(aff);
475 
476     /* Reset the shape's curve to the "original_curve"
477      * This is very important for LPEs to work properly! (the bbox might be recalculated depending on the curve in shape)*/
478     auto const before = this->curveBeforeLPE();
479     if (before && before->get_pathvector() != c->get_pathvector()) {
480         setCurveBeforeLPE(std::move(c));
481         sp_lpe_item_update_patheffect(this, true, false);
482         return;
483     }
484 
485     if (hasPathEffectOnClipOrMaskRecursive(this)) {
486         setCurveBeforeLPE(std::move(c));
487         return;
488     }
489 
490     // This happends on undo, fix bug:#1791784
491     setCurveInsync(std::move(c));
492 }
493 
set_transform(Geom::Affine const & xform)494 Geom::Affine SPGenericEllipse::set_transform(Geom::Affine const &xform)
495 {
496     if (pathEffectsEnabled() && !optimizeTransforms()) {
497         return xform;
498     }
499 
500     /* Calculate ellipse start in parent coords. */
501     Geom::Point pos(Geom::Point(this->cx.computed, this->cy.computed) * xform);
502 
503     /* This function takes care of translation and scaling, we return whatever parts we can't
504        handle. */
505     Geom::Affine ret(Geom::Affine(xform).withoutTranslation());
506     gdouble const sw = hypot(ret[0], ret[1]);
507     gdouble const sh = hypot(ret[2], ret[3]);
508 
509     if (sw > 1e-9) {
510         ret[0] /= sw;
511         ret[1] /= sw;
512     } else {
513         ret[0] = 1.0;
514         ret[1] = 0.0;
515     }
516 
517     if (sh > 1e-9) {
518         ret[2] /= sh;
519         ret[3] /= sh;
520     } else {
521         ret[2] = 0.0;
522         ret[3] = 1.0;
523     }
524 
525     if (this->rx._set) {
526         this->rx.scale( sw );
527     }
528 
529     if (this->ry._set) {
530         this->ry.scale( sh );
531     }
532 
533     /* Find start in item coords */
534     pos = pos * ret.inverse();
535     this->cx = pos[Geom::X];
536     this->cy = pos[Geom::Y];
537 
538     this->set_shape();
539 
540     // Adjust stroke width
541     this->adjust_stroke(sqrt(fabs(sw * sh)));
542 
543     // Adjust pattern fill
544     this->adjust_pattern(xform * ret.inverse());
545 
546     // Adjust gradient fill
547     this->adjust_gradient(xform * ret.inverse());
548 
549     return ret;
550 }
551 
snappoints(std::vector<Inkscape::SnapCandidatePoint> & p,Inkscape::SnapPreferences const * snapprefs) const552 void SPGenericEllipse::snappoints(std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs) const
553 {
554 	// CPPIFY: is this call necessary?
555 	const_cast<SPGenericEllipse*>(this)->normalize();
556 
557     Geom::Affine const i2dt = this->i2dt_affine();
558 
559     // Snap to the 4 quadrant points of the ellipse, but only if the arc
560     // spans far enough to include them
561     if (snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_ELLIPSE_QUADRANT_POINT)) {
562         for (double angle = 0; angle < SP_2PI; angle += M_PI_2) {
563             if (Geom::AngleInterval(this->start, this->end, true).contains(angle)) {
564                 Geom::Point pt = this->getPointAtAngle(angle) * i2dt;
565                 p.emplace_back(pt, Inkscape::SNAPSOURCE_ELLIPSE_QUADRANT_POINT, Inkscape::SNAPTARGET_ELLIPSE_QUADRANT_POINT);
566             }
567         }
568     }
569 
570     double cx = this->cx.computed;
571     double cy = this->cy.computed;
572 
573 
574     bool slice = this->_isSlice();
575 
576     // Add the centre, if we have a closed slice or when explicitly asked for
577     if (snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_NODE_CUSP) && slice &&
578         this->arc_type == SP_GENERIC_ELLIPSE_ARC_TYPE_SLICE) {
579         Geom::Point pt = Geom::Point(cx, cy) * i2dt;
580         p.emplace_back(pt, Inkscape::SNAPSOURCE_NODE_CUSP, Inkscape::SNAPTARGET_NODE_CUSP);
581     }
582 
583     if (snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_OBJECT_MIDPOINT)) {
584         Geom::Point pt = Geom::Point(cx, cy) * i2dt;
585         p.emplace_back(pt, Inkscape::SNAPSOURCE_OBJECT_MIDPOINT, Inkscape::SNAPTARGET_OBJECT_MIDPOINT);
586     }
587 
588     // And if we have a slice, also snap to the endpoints
589     if (snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_NODE_CUSP) && slice) {
590         // Add the start point, if it's not coincident with a quadrant point
591         if (!Geom::are_near(std::fmod(this->start, M_PI_2), 0)) {
592             Geom::Point pt = this->getPointAtAngle(this->start) * i2dt;
593             p.emplace_back(pt, Inkscape::SNAPSOURCE_NODE_CUSP, Inkscape::SNAPTARGET_NODE_CUSP);
594         }
595 
596         // Add the end point, if it's not coincident with a quadrant point
597         if (!Geom::are_near(std::fmod(this->end, M_PI_2), 0)) {
598             Geom::Point pt = this->getPointAtAngle(this->end) * i2dt;
599             p.emplace_back(pt, Inkscape::SNAPSOURCE_NODE_CUSP, Inkscape::SNAPTARGET_NODE_CUSP);
600         }
601     }
602 }
603 
modified(guint flags)604 void SPGenericEllipse::modified(guint flags)
605 {
606     if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
607         this->set_shape();
608     }
609 
610     SPShape::modified(flags);
611 }
612 
update_patheffect(bool write)613 void SPGenericEllipse::update_patheffect(bool write) {
614     SPShape::update_patheffect(write);
615 }
616 
normalize()617 void SPGenericEllipse::normalize()
618 {
619     Geom::AngleInterval a(this->start, this->end, true);
620 
621     this->start = a.initialAngle().radians0();
622     this->end = a.finalAngle().radians0();
623 }
624 
getPointAtAngle(double arg) const625 Geom::Point SPGenericEllipse::getPointAtAngle(double arg) const
626 {
627     return Geom::Point::polar(arg) * Geom::Scale(rx.computed, ry.computed) * Geom::Translate(cx.computed, cy.computed);
628 }
629 
630 /*
631  * set_elliptical_path_attribute:
632  *
633  * Convert center to endpoint parameterization and set it to repr.
634  *
635  * See SVG 1.0 Specification W3C Recommendation
636  * ``F.6 Elliptical arc implementation notes'' for more detail.
637  */
set_elliptical_path_attribute(Inkscape::XML::Node * repr)638 bool SPGenericEllipse::set_elliptical_path_attribute(Inkscape::XML::Node *repr)
639 {
640     // Make sure our pathvector is up to date.
641     this->set_shape();
642 
643     if (_curve) {
644         repr->setAttribute("d", sp_svg_write_path(_curve->get_pathvector()));
645     } else {
646         repr->removeAttribute("d");
647     }
648 
649     return true;
650 }
651 
position_set(gdouble x,gdouble y,gdouble rx,gdouble ry)652 void SPGenericEllipse::position_set(gdouble x, gdouble y, gdouble rx, gdouble ry)
653 {
654     this->cx = x;
655     this->cy = y;
656     this->rx = rx;
657     this->ry = ry;
658 
659     Inkscape::Preferences * prefs = Inkscape::Preferences::get();
660 
661     // those pref values are in degrees, while we want radians
662     if (prefs->getDouble("/tools/shapes/arc/start", 0.0) != 0) {
663         this->start = Geom::Angle::from_degrees(prefs->getDouble("/tools/shapes/arc/start", 0.0)).radians0();
664     }
665 
666     if (prefs->getDouble("/tools/shapes/arc/end", 0.0) != 0) {
667         this->end = Geom::Angle::from_degrees(prefs->getDouble("/tools/shapes/arc/end", 0.0)).radians0();
668     }
669 
670     this->arc_type = (GenericEllipseArcType)prefs->getInt("/tools/shapes/arc/arc_type", 0);
671     if (this->type != SP_GENERIC_ELLIPSE_ARC && _isSlice()) {
672         // force an update while creating shapes, so correct rendering is shown initially
673         updateRepr();
674     }
675 
676     this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
677 }
678 
_isSlice() const679 bool SPGenericEllipse::_isSlice() const
680 {
681     Geom::AngleInterval a(this->start, this->end, true);
682 
683     return !(Geom::are_near(a.extent(), 0) || Geom::are_near(a.extent(), SP_2PI));
684 }
685 
686 /**
687 Returns the ratio in which the vector from p0 to p1 is stretched by transform
688  */
vectorStretch(Geom::Point p0,Geom::Point p1,Geom::Affine xform)689 gdouble SPGenericEllipse::vectorStretch(Geom::Point p0, Geom::Point p1, Geom::Affine xform) {
690     if (p0 == p1) {
691         return 0;
692     }
693 
694     return (Geom::distance(p0 * xform, p1 * xform) / Geom::distance(p0, p1));
695 }
696 
setVisibleRx(gdouble rx)697 void SPGenericEllipse::setVisibleRx(gdouble rx) {
698     if (rx == 0) {
699         this->rx.unset();
700     } else {
701         this->rx = rx / SPGenericEllipse::vectorStretch(
702             Geom::Point(this->cx.computed + 1, this->cy.computed),
703             Geom::Point(this->cx.computed, this->cy.computed),
704             this->i2doc_affine());
705     }
706 
707     this->updateRepr();
708 }
709 
setVisibleRy(gdouble ry)710 void SPGenericEllipse::setVisibleRy(gdouble ry) {
711     if (ry == 0) {
712         this->ry.unset();
713     } else {
714         this->ry = ry / SPGenericEllipse::vectorStretch(
715             Geom::Point(this->cx.computed, this->cy.computed + 1),
716             Geom::Point(this->cx.computed, this->cy.computed),
717             this->i2doc_affine());
718     }
719 
720     this->updateRepr();
721 }
722 
getVisibleRx() const723 gdouble SPGenericEllipse::getVisibleRx() const {
724     if (!this->rx._set) {
725         return 0;
726     }
727 
728     return this->rx.computed * SPGenericEllipse::vectorStretch(
729         Geom::Point(this->cx.computed + 1, this->cy.computed),
730         Geom::Point(this->cx.computed, this->cy.computed),
731         this->i2doc_affine());
732 }
733 
getVisibleRy() const734 gdouble SPGenericEllipse::getVisibleRy() const {
735     if (!this->ry._set) {
736         return 0;
737     }
738 
739     return this->ry.computed * SPGenericEllipse::vectorStretch(
740         Geom::Point(this->cx.computed, this->cy.computed + 1),
741         Geom::Point(this->cx.computed, this->cy.computed),
742         this->i2doc_affine());
743 }
744 
745 /*
746   Local Variables:
747   mode:c++
748   c-file-style:"stroustrup"
749   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
750   indent-tabs-mode:nil
751   fill-column:99
752   End:
753 */
754 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8 :
755