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