1 /*
2 Copyright (C) 2012 - 2016 2019 Johan Mattsson
3
4 This library is free software; you can redistribute it and/or modify
5 it under the terms of the GNU Lesser General Public License as
6 published by the Free Software Foundation; either version 3 of the
7 License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful, but
10 WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Lesser General Public License for more details.
13 */
14
15 using Cairo;
16 using Math;
17
18 namespace BirdFont {
19
20 public enum Direction {
21 CLOCKWISE,
22 COUNTER_CLOCKWISE
23 }
24
25 public class Path : GLib.Object {
26
27 public Gee.ArrayList<EditPoint> points {
28 get {
29 if (control_points == null) {
30 control_points = new Gee.ArrayList<EditPoint> ();
31 BirdFontFile.parse_path_data (point_data, this);
32 point_data = "";
33 }
34
35 return (!) control_points;
36 }
37
38 set {
39 control_points = value;
40 }
41 }
42
43 public Gee.ArrayList<EditPoint>? control_points = null;
44
45 EditPoint? last_point = null;
46
47 /** Path boundaries */
48 public double xmax = Glyph.CANVAS_MIN;
49 public double xmin = Glyph.CANVAS_MAX;
50 public double ymax = Glyph.CANVAS_MIN;
51 public double ymin = Glyph.CANVAS_MAX;
52
53 /** Stroke width */
54 public double stroke {
55 get {
56 return path_stroke_width;
57 }
58
59 set {
60 if (0 < value < 0.1) {
61 path_stroke_width = 0.2;
62 } else {
63 path_stroke_width = value;
64 }
65 }
66 }
67
68 private double path_stroke_width = 0;
69
70 public LineCap line_cap = LineCap.BUTT;
71 public PathList? full_stroke = null;
72 PathList? fast_stroke = null;
73 StrokeTask? stroke_creator;
74
75 /** Fill property for closed paths with stroke. */
76 public bool fill = false;
77
78 bool edit = true;
79 bool open = true;
80
81 public bool direction_is_set = false;
82 bool no_derived_direction = false;
83 bool clockwise_direction = true;
84
85 // Iterate over each pixel in a path
86 public delegate bool RasterIterator (double x, double y, double step);
87
88 public delegate bool SegmentIterator (EditPoint start, EditPoint stop);
89
90 /** The stroke of an outline when the path is not filled. */
91 public static double stroke_width = 0;
92 public static bool show_all_line_handles = true;
93 public static bool fill_open_path {get; set;}
94
95 public double rotation = 0;
96 public double skew = 0;
97
98 public bool hide_end_handle = true;
99 public bool highlight_last_segment = false;
100
101 public string point_data = "";
102
103 public Color? color = null;
104 public Color? stroke_color = null;
105
106 public Gradient? gradient = null;
107
108 private static Text? arrow = null;
109
110 public Path () {
111 string width;
112
113 if (unlikely (stroke_width < 1)) {
114 width = Preferences.get ("stroke_width");
115 if (width != "") {
116 stroke_width = double.parse (width);
117 }
118 }
119
120 if (stroke_width < 1) {
121 stroke_width = 1;
122 }
123 }
124
125 public bool is_filled () {
126 return fill;
127 }
128
129 public void set_fill (bool f) {
130 fill = f;
131 }
132
133 public EditPoint get_first_point () {
134 if (unlikely (points.size == 0)) {
135 warning ("No point");
136 return new EditPoint ();
137 }
138
139 return points.get (0);
140 }
141
142 public EditPoint get_last_visible_point () {
143 EditPoint e;
144
145 if (unlikely (points.size == 0)) {
146 warning ("No point");
147 return new EditPoint ();
148 }
149
150 for (int i = points.size - 1; i >= 0; i--) {
151 e = points.get (i);
152 if (e.type != PointType.HIDDEN) {
153 return e;
154 }
155 }
156
157 warning ("Only hidden points");
158 return new EditPoint ();
159 }
160
161 public EditPoint get_last_point () {
162 if (unlikely (points.size == 0)) {
163 warning ("No point");
164 return new EditPoint ();
165 }
166
167 return points.get (points.size - 1);
168 }
169
170 public bool has_direction () {
171 return direction_is_set;
172 }
173
174 public bool empty () {
175 return points.size == 0;
176 }
177
178 public void draw_boundaries (Context cr) {
179 double x = Glyph.reverse_path_coordinate_x (xmin);
180 double y = Glyph.reverse_path_coordinate_y (ymin);
181 double x2 = Glyph.reverse_path_coordinate_x (xmax);
182 double y2 = Glyph.reverse_path_coordinate_y (ymax);
183
184 cr.save ();
185
186 Theme.color (cr, "Default Background");
187 cr.set_line_width (2);
188 cr.rectangle (x, y, x2 - x, y2 - y);
189 cr.stroke ();
190
191 cr.restore ();
192 }
193
194 public void draw_outline (Context cr) {
195 unowned EditPoint? n = null;
196 unowned EditPoint en;
197 unowned EditPoint em;
198 int i;
199
200 if (points.size < 2) {
201 return;
202 }
203
204 cr.new_path ();
205
206 // draw lines
207 i = 0;
208 foreach (EditPoint e in points) {
209 if (n != null) {
210 en = (!) n;
211 if (!highlight_last_segment || i != points.size - 1) {
212 draw_next (en, e, cr);
213 }
214 }
215
216 n = e;
217 i++;
218 }
219
220 // close path
221 if (!is_open () && n != null) {
222 if (highlight_last_segment) {
223 cr.stroke ();
224 en = points.get (points.size - 1).get_link_item ();
225 em = points.get (0).get_link_item ();
226 draw_next (en, em, cr);
227 cr.stroke ();
228 } else {
229 en = (!) n;
230 em = points.get (0).get_link_item ();
231 draw_next (en, em, cr);
232 cr.stroke ();
233 }
234 } else {
235 cr.stroke ();
236 }
237
238 // draw highlighted segment
239 if (highlight_last_segment && points.size >= 2) {
240 draw_next (points.get (points.size - 2), points.get (points.size - 1), cr, true);
241 cr.stroke ();
242 }
243 }
244
245 public void draw_edit_points (Context cr) {
246 if (is_editable ()) {
247 // control points for curvature
248 foreach (EditPoint e in points) {
249 if (show_all_line_handles || e.selected_point || e.selected_handle > 0) {
250 draw_edit_point_handles (e, cr);
251 }
252 }
253
254 // control points
255 foreach (EditPoint e in points) {
256 draw_edit_point (e, cr);
257 }
258 }
259 }
260
261 /** Add all control points for a path to the cairo context.
262 * Call Context.new_path (); before this method and Context.fill ()
263 * to show the path.
264 */
265 public void draw_path (Context cr, Glyph glyph, Color? color = null) {
266 unowned EditPoint? n = null;
267 unowned EditPoint en;
268 unowned EditPoint em;
269 Color c;
270 double center_x, center_y;
271 double ex, ey;
272
273 if (points.size == 0){
274 return;
275 }
276
277 center_x = glyph.allocation.width / 2.0;
278 center_y = glyph.allocation.height / 2.0;
279
280 ex = center_x + points.get (0).x;
281 ey = center_y - points.get (0).y;
282
283 cr.move_to (ex, ey);
284
285 // draw lines
286 foreach (EditPoint e in points) {
287 if (n != null) {
288 en = (!) n;
289 draw_next (en, e, cr);
290 }
291
292 n = e;
293 }
294
295 // close path
296 if (!is_open () && points.size >= 2 && n != null) {
297 en = (!) n;
298 em = points.get (0).get_link_item ();
299 draw_next (en, em, cr);
300 }
301
302 // fill path
303 cr.close_path ();
304
305 if (this.color != null) {
306 c = (!) this.color;
307 cr.set_source_rgba (c.r, c.g, c.b, c.a);
308 } else if (color != null) {
309 c = (!) color;
310 cr.set_source_rgba (c.r, c.g, c.b, c.a);
311 } else {
312 if (is_clockwise ()) {
313 Theme.color_opacity (cr, "Selected Objects", 0.4);
314 } else {
315 Theme.color_opacity (cr, "Selected Objects", 0.8);
316 }
317 }
318 }
319
320 public void draw_orientation_arrow (Context cr, double opacity) {
321 EditPoint top = new EditPoint ();
322 double max = Glyph.CANVAS_MIN;
323 double x, y, angle;
324 double size = 200 * Screen.get_scale ();
325 Text arrow_icon;
326
327 foreach (EditPoint e in points) {
328 if (e.y > max) {
329 max = e.y;
330 top = e;
331 }
332 }
333
334 if (arrow == null) {
335 arrow_icon = new Text ("orientation_arrow", size);
336 arrow_icon.load_font ("icons.birdfont");
337 arrow = arrow_icon;
338 }
339
340 arrow_icon = (!) arrow;
341
342 Theme.text_color_opacity (arrow_icon, "Highlighted 1", opacity);
343
344 angle = top.get_right_handle ().angle;
345 x = Glyph.xc () + top.x + cos (angle + PI / 2) * 10 * Glyph.ivz ();
346 y = Glyph.yc () - top.y - sin (angle + PI / 2) * 10 * Glyph.ivz ();
347
348 if (points.size > 0) {
349 double inverted_zoom = Glyph.ivz ();
350 double zoom = 1 / inverted_zoom;
351 cr.scale (inverted_zoom, inverted_zoom);
352
353 cr.save ();
354 cr.translate (x * zoom, y * zoom);
355 cr.rotate (-angle);
356 cr.translate (-x * zoom, -y * zoom);
357
358 arrow_icon.draw_at_baseline (cr, x * zoom, y * zoom);
359
360 cr.restore ();
361 }
362 }
363
364 private void draw_next (EditPoint e, EditPoint en, Context cr, bool highlighted = false) {
365 PointType r = e.get_right_handle ().type;
366 PointType l = en.get_left_handle ().type;
367
368 if (r == PointType.DOUBLE_CURVE || l == PointType.DOUBLE_CURVE) {
369 draw_double_curve (e, en, cr, highlighted);
370 } else {
371 draw_curve (e, en, cr, highlighted);
372 }
373 }
374
375 private static void draw_double_curve (EditPoint e, EditPoint en, Context cr, bool highlighted) {
376 EditPoint middle;
377 double x, y;
378
379 x = e.get_right_handle ().x + (en.get_left_handle ().x - e.get_right_handle ().x) / 2;
380 y = e.get_right_handle ().y + (en.get_left_handle ().y - e.get_right_handle ().y) / 2;
381
382 middle = new EditPoint (x, y, PointType.DOUBLE_CURVE);
383 middle.right_handle = en.get_left_handle ().copy ();
384
385 middle.right_handle.type = PointType.DOUBLE_CURVE;
386 middle.left_handle.type = PointType.DOUBLE_CURVE;
387
388 draw_curve (e, middle, cr, highlighted);
389 draw_curve (middle, en, cr, highlighted);
390 }
391
392 private static void draw_curve (EditPoint e, EditPoint en, Context cr, bool highlighted = false, double alpha = 1) {
393 Glyph g = MainWindow.get_current_glyph ();
394 double xa, ya, xb, yb, xc, yc, xd, yd;
395 PointType t = e.get_right_handle ().type;
396 PointType u = en.get_left_handle ().type;
397
398 get_bezier_points (e, en, out xa, out ya, out xb, out yb, out xc, out yc, out xd, out yd);
399
400 if (!highlighted) {
401 Theme.color (cr, "Stroke Color");
402 } else {
403 Theme.color (cr, "Highlighted Guide");
404 }
405
406 cr.set_line_width (stroke_width / g.view_zoom);
407
408 cr.line_to (xa, ya); // this point makes sense only if it is in the first or last position
409
410 if (t == PointType.QUADRATIC || t == PointType.LINE_QUADRATIC || t == PointType.DOUBLE_CURVE || u == PointType.QUADRATIC || u == PointType.LINE_QUADRATIC || u == PointType.DOUBLE_CURVE) {
411 cr.curve_to ((xa + 2 * xb) / 3, (ya + 2 * yb) / 3, (xd + 2 * xb) / 3, (yd + 2 * yb) / 3, xd, yd);
412 } else {
413 cr.curve_to (xb, yb, xc, yc, xd, yd);
414 }
415 }
416
417 /** Curve relative to window center. */
418 public static void get_bezier_points (EditPoint e, EditPoint en, out double xa, out double ya, out double xb, out double yb, out double xc, out double yc, out double xd, out double yd) {
419 Glyph g = MainWindow.get_current_glyph ();
420
421 double center_x, center_y;
422
423 center_x = g.allocation.width / 2.0;
424 center_y = g.allocation.height / 2.0;
425
426 xa = center_x + e.x;
427 ya = center_y - e.y;
428
429 xb = center_x + e.get_right_handle ().x;
430 yb = center_y - e.get_right_handle ().y;
431
432 xc = center_x + en.get_left_handle ().x;
433 yc = center_y - en.get_left_handle ().y;
434
435 xd = center_x + en.x;
436 yd = center_y - en.y;
437 }
438
439 /** Curve absolute glyph data. */
440 public static void get_abs_bezier_points (EditPoint e, EditPoint en, out double xa, out double ya, out double xb, out double yb, out double xc, out double yc, out double xd, out double yd) {
441 xa = + e.x;
442 ya = - e.y;
443
444 xb = + e.get_right_handle ().x;
445 yb = - e.get_right_handle ().y;
446
447 xc = + en.get_left_handle ().x;
448 yc = - en.get_left_handle ().y;
449
450 xd = + en.x;
451 yd = - en.y;
452 }
453
454 /** Line points relative to centrum. */
455 public static void get_line_points (EditPoint e, EditPoint en, out double xa, out double ya, out double xb, out double yb) {
456 double xc = Glyph.xc ();
457 double yc = Glyph.yc ();
458
459 xa = xc + e.x;
460 ya = yc - e.y;
461
462 xb = xc + en.x;
463 yb = yc - en.y;
464 }
465
466 public void draw_line (EditPoint e, EditPoint en, Context cr, double alpha = 1) {
467 Glyph g = MainWindow.get_current_glyph ();
468 double ax, ay, bx, by;
469
470 get_line_points (e, en, out ax, out ay, out bx, out by);
471
472 Theme.color (cr, "Handle Color");
473 cr.set_line_width (1.7 * (stroke_width / g.view_zoom));
474
475 cr.line_to (ax, ay);
476 cr.line_to (bx, by);
477
478 cr.stroke ();
479 }
480
481 public void draw_edit_point (EditPoint e, Context cr) {
482 draw_edit_point_center (e, cr);
483 }
484
485 public void draw_edit_point_handles (EditPoint e, Context cr) {
486 Color color_left = Theme.get_color ("Control Point Handle");
487 Color color_right = Theme.get_color ("Control Point Handle");
488 EditPoint handle_right = e.get_right_handle ().get_point ();
489 EditPoint handle_left = e.get_left_handle ().get_point ();
490
491 cr.stroke ();
492
493 if (e.type != PointType.HIDDEN) {
494 if (e.get_right_handle ().selected) {
495 color_right = Theme.get_color ("Selected Control Point Handle");
496 } else if (e.get_right_handle ().active) {
497 color_right = Theme.get_color ("Active Handle");
498 } else {
499 color_right = Theme.get_color ("Control Point Handle");
500 }
501
502 if (e.get_left_handle ().selected) {
503 color_left = Theme.get_color ("Selected Control Point Handle");
504 } else if (e.get_left_handle ().active) {
505 color_left = Theme.get_color ("Active Handle");
506 } else {
507 color_left = Theme.get_color ("Control Point Handle");
508 }
509
510 if (e.get_right_handle ().selected) {
511 color_right = Theme.get_color ("Selected Control Point Handle");
512 } else if (e.get_right_handle ().active) {
513 color_right = Theme.get_color ("Active Handle");
514 } else {
515 color_right = Theme.get_color ("Control Point Handle");
516 }
517
518 if (!hide_end_handle || !(is_open () && e == points.get (points.size - 1))) {
519 draw_line (handle_right, e, cr, 0.15);
520 draw_control_point (cr, e.get_right_handle ().x, e.get_right_handle ().y, color_right);
521 }
522
523 if (!(is_open () && e == points.get (0))) {
524 draw_line (handle_left, e, cr, 0.15);
525 draw_control_point (cr, e.get_left_handle ().x, e.get_left_handle ().y, color_left);
526 }
527 }
528 }
529
530 public static void draw_edit_point_center (EditPoint e, Context cr) {
531 Color c;
532
533 if (e.type != PointType.HIDDEN) {
534 if (e.type == PointType.CUBIC || e.type == PointType.LINE_CUBIC) {
535 if (e.is_selected ()) {
536 if (e.active_point) {
537 if (e.color != null) {
538 c = (!) e.color;
539 } else {
540 c = Theme.get_color ("Selected Active Cubic Control Point");
541 }
542 } else {
543 if (e.color != null) {
544 c = (!) e.color;
545 } else {
546 c = Theme.get_color ("Selected Cubic Control Point");
547 }
548 }
549 } else {
550 if (e.active_point) {
551 if (e.color != null) {
552 c = (!) e.color;
553 } else {
554 c = Theme.get_color ("Active Cubic Control Point");
555 }
556 } else {
557 if (e.color != null) {
558 c = (!) e.color;
559 } else {
560 c = Theme.get_color ("Cubic Control Point");
561 }
562 }
563 }
564 } else {
565 if (e.is_selected ()) {
566 if (e.active_point) {
567 if (e.color != null) {
568 c = (!) e.color;
569 } else {
570 c = Theme.get_color ("Selected Active Quadratic Control Point");
571 }
572 } else {
573 if (e.color != null) {
574 c = (!) e.color;
575 } else {
576 c = Theme.get_color ("Selected Quadratic Control Point");
577 }
578 }
579 } else {
580 if (e.active_point) {
581 if (e.color != null) {
582 c = (!) e.color;
583 } else {
584 c = Theme.get_color ("Active Quadratic Control Point");
585 }
586 } else {
587 if (e.color != null) {
588 c = (!) e.color;
589 } else {
590 c = Theme.get_color ("Quadratic Control Point");
591 }
592 }
593 }
594 }
595
596 draw_control_point (cr, e.x, e.y, c);
597 }
598 }
599
600 public static void draw_control_point (Context cr, double x, double y, Color color, double size = 3.5) {
601 Glyph g = MainWindow.get_current_glyph ();
602 double ivz = 1 / g.view_zoom;
603 double width = size * Math.sqrt (stroke_width) * ivz;
604 double xc = g.allocation.width / 2.0;
605 double yc = g.allocation.height / 2.0;
606
607 cr.save ();
608
609 x = xc + x;
610 y = yc - y;
611
612 cr.set_source_rgba (color.r, color.g, color.b, color.a);
613
614 cr.move_to (x, y);
615 cr.arc (x, y, width, 0, 2 * Math.PI);
616 cr.close_path ();
617 cr.fill ();
618
619 cr.restore ();
620 }
621
622 /** Set direction for this path to clockwise for outline and
623 * counter clockwise for inline paths.
624 */
625 public bool force_direction (Direction direction) {
626 bool c = (direction == Direction.CLOCKWISE);
627 bool d = is_clockwise ();
628 direction_is_set = true;
629
630 if (c != d) {
631 this.reverse ();
632 }
633
634 d = is_clockwise ();
635 if (unlikely (d != c)) {
636 warning ("Failed to set direction for path in force_direction.");
637 return true;
638 }
639
640 return false;
641 }
642
643 /** Switch direction from clockwise path to counter clockwise path or vise versa. */
644 public bool reverse () {
645 bool direction = is_clockwise ();
646
647 if (no_derived_direction) {
648 clockwise_direction = !clockwise_direction;
649 }
650
651 reverse_points ();
652
653 if (unlikely (direction == is_clockwise ())) {
654 return false;
655 }
656
657 return true;
658 }
659
660 private void reverse_points () requires (points.size > 0) {
661 EditPointHandle t;
662 EditPoint e;
663 Gee.ArrayList<EditPoint> new_points;
664
665 new_points = new Gee.ArrayList<EditPoint> ();
666
667 for (int i = points.size - 1; i >= 0 ; i--) {
668 e = points.get (i);
669
670 t = e.right_handle;
671 e.right_handle = e.left_handle;
672 e.left_handle = t;
673
674 new_points.add (e);
675 }
676
677 points = new_points;
678 create_list ();
679 }
680
681 public void print_all_points () {
682 int i = 0;
683 foreach (EditPoint p in points) {
684 ++i;
685 string t = (p.type == PointType.END) ? " endpoint" : "";
686 stdout.printf (@"Point $i at ($(p.x), $(p.y)) $t \n");
687 }
688 }
689
690 private double clockwise_sum () {
691 double sum = 0;
692
693 return_val_if_fail (points.size >= 3, 0);
694
695 foreach (EditPoint e in points) {
696 sum += e.get_direction ();
697 }
698
699 return sum;
700 }
701
702 public bool is_clockwise () {
703 double s;
704 Path p;
705
706 if (unlikely (points.size <= 2)) {
707 no_derived_direction = true;
708 return clockwise_direction;
709 }
710
711 if (unlikely (points.size == 2)) {
712 p = copy ();
713 all_segments ((a, b) => {
714 double px, py;
715 double step;
716 EditPoint new_point;
717
718 step = 0.3;
719
720 Path.get_point_for_step (a, b, step, out px, out py);
721
722 new_point = new EditPoint (px, py);
723 new_point.prev = a;
724 new_point.next = b;
725
726 p.insert_new_point_on_path (new_point, step);
727
728 return true;
729 });
730
731 return p.is_clockwise ();
732 }
733
734 s = clockwise_sum ();
735
736 if (s == 0) {
737 no_derived_direction = true;
738 return clockwise_direction;
739 }
740
741 return s > 0;
742 }
743
744 public bool is_editable () {
745 return edit;
746 }
747
748 /** Show control points on outline path. */
749 public void set_editable (bool e) {
750 edit = e;
751 }
752
753 public bool is_open () {
754 return open;
755 }
756
757 /** Resize path relative to bottom left coordinates. */
758 public void resize (double ratio_x, double ratio_y) {
759 foreach (EditPoint p in points) {
760 p.independent_x *= ratio_x;
761 p.independent_y *= ratio_y;
762 p.get_right_handle ().independent_x *= ratio_x;
763 p.get_right_handle ().independent_y *= ratio_y;
764 p.get_left_handle ().independent_x *= ratio_x;
765 p.get_left_handle ().independent_y *= ratio_y;
766 }
767
768 xmin *= ratio_x;
769 xmax *= ratio_x;
770 ymin *= ratio_y;
771 ymax *= ratio_y;
772 }
773
774 public void scale (double scale_x, double scale_y) {
775 foreach (EditPoint p in points) {
776 p.right_handle.length *= scale_x * scale_y;
777 p.left_handle.length *= scale_x * scale_y;
778 }
779
780 foreach (EditPoint p in points) {
781 p.x *= scale_x;
782 p.y *= scale_y;
783 }
784
785 xmin *= scale_x;
786 xmax *= scale_x;
787 ymin *= scale_y;
788 ymax *= scale_y;
789 }
790
791 public Path copy () {
792 Path new_path = new Path ();
793 EditPoint p;
794
795 foreach (EditPoint ep in points) {
796 p = ep.copy ();
797 new_path.add_point (p);
798 }
799
800 if (gradient != null) {
801 new_path.gradient = ((!) gradient).copy ();
802 }
803
804 if (color != null) {
805 new_path.color = ((!) color).copy ();
806 }
807
808 if (stroke_color != null) {
809 new_path.stroke_color = ((!) stroke_color).copy ();
810 }
811
812 new_path.fill = fill;
813 new_path.edit = edit;
814 new_path.open = open;
815 new_path.stroke = stroke;
816 new_path.line_cap = line_cap;
817 new_path.skew = skew;
818 new_path.fill = fill;
819 new_path.direction_is_set = direction_is_set;
820 new_path.create_list ();
821
822 new_path.hide_end_handle = hide_end_handle;
823 new_path.highlight_last_segment = highlight_last_segment;
824
825 return new_path;
826 }
827
828 public bool is_over (double x, double y) {
829 Glyph g = MainWindow.get_current_glyph ();
830
831 x = x * Glyph.ivz () + g.view_offset_x - Glyph.xc ();
832 y = y * Glyph.ivz () + g.view_offset_y - Glyph.yc ();
833
834 y *= -1;
835
836 return is_over_coordinate (x, y);
837 }
838
839 public bool is_over_coordinate (double x, double y) {
840 return is_over_coordinate_var (x, y);
841 }
842
843 public static double point_distance (EditPoint p1, EditPoint p2) {
844 return distance (p1.x, p2.x, p1.y, p2.y);
845 }
846
847 public static double distance (double ax, double bx, double ay, double by) {
848 return Math.fabs (Math.sqrt (Math.pow (ax - bx, 2) + Math.pow (ay - by, 2)));
849 }
850
851 public static double distance_to_point (EditPoint a, EditPoint b) {
852 return distance (a.x, b.x, a.y, b.y);
853 }
854
855 public static double distance_pixels (double x1, double y1, double x2, double y2) {
856 return distance (Glyph.path_coordinate_x (x1),
857 Glyph.path_coordinate_x (x2),
858 Glyph.path_coordinate_x (y1),
859 Glyph.path_coordinate_x (y2));
860 }
861
862 public static double get_length_from (EditPoint a, EditPoint b) {
863 double x, y;
864
865 x = Math.fabs (a.x - a.get_right_handle ().x);
866 x += Math.fabs (a.get_right_handle ().x - b.get_left_handle ().x );
867 x += Math.fabs (b.get_left_handle ().x - b.x);
868
869 y = Math.fabs (a.y - a.get_right_handle ().y);
870 y += Math.fabs (a.get_right_handle ().y - b.get_left_handle ().y);
871 y += Math.fabs (b.get_left_handle ().y - b.y);
872
873 return Math.fabs (Math.sqrt (x * x + y * y));
874 }
875
876 public Path flatten (int steps = 10) {
877 Path flat = new Path ();
878
879 all_of_path ((x, y, t) => {
880 EditPoint ep = flat.add (x, y);
881 ep.type = PointType.LINE_QUADRATIC;
882 ep.get_right_handle ().type = PointType.LINE_QUADRATIC;
883 ep.get_left_handle ().type = PointType.LINE_QUADRATIC;
884 return true;
885 }, steps); // FIXME: g.view_zoom
886
887 if (!is_open ()) {
888 flat.close ();
889 }
890
891 flat.update_region_boundaries ();
892
893 return flat;
894 }
895
896 /** Variable precision */
897 public bool is_over_coordinate_var (double x, double y) {
898 int insides = 0;
899 Path path;
900
901 if (stroke > 0) {
902 foreach (Path p in get_stroke_fast ().paths) {
903 path = p.flatten ();
904 if (StrokeTool.is_inside (new EditPoint (x, y), path)) {
905 insides++;
906 }
907 }
908
909 if (insides > 0 && is_filled ()) {
910 return true;
911 }
912
913 if (insides % 2 == 1) {
914 return true;
915 }
916 } else if (is_over_boundry (x, y)) {
917 path = flatten ();
918 return StrokeTool.is_inside (new EditPoint (x, y), path);
919 }
920
921 return false;
922 }
923
924 public bool is_over_boundry (double x, double y) {
925 if (unlikely (ymin == double.MAX || ymin == 10000)) {
926 warning ("bounding box is not calculated, run update_region_boundaries first.");
927 update_region_boundaries ();
928 }
929
930 return (ymin <= y <= ymax) && (xmin <= x <= xmax);
931 }
932
933 public bool has_overlapping_boundry (Path p) {
934 return !(xmax <= p.xmin || ymax <= p.ymin) || (xmin >= p.xmax || ymin >= p.ymax);
935 }
936
937 public EditPoint delete_first_point () {
938 EditPoint r;
939 int size;
940
941 size = points.size;
942 if (unlikely (size == 0)) {
943 warning ("No points in path.");
944 return new EditPoint ();
945 }
946
947 r = points.get (0);
948 points.remove_at (0);
949
950 if (size > 1) {
951 r.get_next ().prev = null;
952 }
953
954 return r;
955 }
956
957 public EditPoint delete_last_point () {
958 EditPoint r;
959 int size;
960
961 size = points.size;
962 if (unlikely (size == 0)) {
963 warning ("No points in path.");
964 return new EditPoint ();
965 }
966
967 r = points.get (size - 1);
968 points.remove_at (size - 1);
969
970 if (size > 1) {
971 r.get_prev ().next = null;
972
973 if (r.next != null) {
974 r.get_next ().prev = null;
975 }
976 }
977
978 return r;
979 }
980
981 public EditPoint add (double x, double y) {
982 EditPoint ep = new EditPoint (x, y);
983 return add_point (ep);
984 }
985
986 public EditPoint add_point (EditPoint p) {
987 EditPoint previous_point;
988
989 if (points.size == 0) {
990 points.add (p);
991 p.prev = p;
992 p.next = p;
993 } else {
994 previous_point = points.get (points.size - 1);
995 points.add (p);
996 p.prev = previous_point;
997 p.next = previous_point.next;
998 }
999
1000 last_point = p;
1001
1002 return p;
1003 }
1004
1005 /** @return a list item pointing to the new point */
1006 public EditPoint add_point_after (EditPoint p, EditPoint? previous_point) {
1007 int prev_index;
1008
1009 if (unlikely (previous_point == null && points.size != 0)) {
1010 warning ("previous_point == null");
1011 previous_point = points.get (points.size - 1).get_link_item ();
1012 }
1013
1014 if (points.size == 0) {
1015 points.add (p);
1016 p.prev = points.get (0).get_link_item ();
1017 p.next = points.get (0).get_link_item ();
1018 } else {
1019 p.prev = (!) previous_point;
1020 p.next = ((!) previous_point).next;
1021
1022 prev_index = points.index_of ((!) previous_point);
1023
1024 if (unlikely (!(0 <= prev_index < points.size))) {
1025 warning ("no previous point");
1026 }
1027
1028 points.insert (prev_index + 1, p);
1029 }
1030
1031 last_point = p;
1032
1033 return p;
1034 }
1035
1036 public void close () {
1037 open = false;
1038 edit = false;
1039
1040 create_list ();
1041
1042 if (points.size > 2) {
1043 recalculate_linear_handles_for_point (get_last_point ());
1044 recalculate_linear_handles_for_point (get_first_point ());
1045 }
1046 }
1047
1048 public void reopen () {
1049 open = true;
1050 edit = true;
1051 }
1052
1053 /** Move path. */
1054 public void move (double delta_x, double delta_y) {
1055 foreach (EditPoint ep in points) {
1056 ep.x += delta_x;
1057 ep.y += delta_y;
1058 }
1059
1060 if (gradient != null) {
1061 Gradient g = (!) gradient;
1062 g.x1 += delta_x;
1063 g.x2 += delta_x;
1064 g.y1 += delta_y;
1065 g.y2 += delta_y;
1066 }
1067
1068 update_region_boundaries ();
1069 }
1070
1071 private void update_region_boundaries_for_segment (EditPoint a, EditPoint b) {
1072 EditPointHandle left_handle;
1073 EditPointHandle right_handle;
1074 int steps = 10;
1075
1076 right_handle = a.get_right_handle ();
1077 left_handle = b.get_left_handle ();
1078
1079 if (a.x > xmax || b.x > xmax || left_handle.x > xmax || right_handle.x > xmax) {
1080 all_of (a, b, (cx, cy) => {
1081 if (cx > xmax) {
1082 this.xmax = cx;
1083 }
1084 return true;
1085 }, steps);
1086 }
1087
1088 if (a.x < xmin || b.x < xmin || left_handle.x < xmin || right_handle.x < xmin) {
1089 all_of (a, b, (cx, cy) => {
1090 if (cx < xmin) {
1091 this.xmin = cx;
1092 }
1093 return true;
1094 }, steps);
1095 }
1096
1097 if (a.y > ymax || b.y > ymax || left_handle.y > xmax || right_handle.y > xmax) {
1098 all_of (a, b, (cx, cy) => {
1099 if (cy > ymax) {
1100 this.ymax = cy;
1101 }
1102 return true;
1103 }, steps);
1104 }
1105
1106 if (a.y < ymin || b.y < ymin || left_handle.y < xmin || right_handle.y < xmin) {
1107 all_of (a, b, (cx, cy) => {
1108 if (cy < ymin) {
1109 this.ymin = cy;
1110 }
1111 return true;
1112 }, steps);
1113 }
1114 }
1115
1116 public void update_region_boundaries () {
1117 xmax = Glyph.CANVAS_MIN;
1118 xmin = Glyph.CANVAS_MAX;
1119 ymax = Glyph.CANVAS_MIN;
1120 ymin = Glyph.CANVAS_MAX;
1121
1122 if (points.size == 0) {
1123 xmax = 0;
1124 xmin = 0;
1125 ymax = 0;
1126 ymin = 0;
1127 }
1128
1129 all_segments ((a, b) => {
1130 update_region_boundaries_for_segment (a, b);
1131 return true;
1132 });
1133
1134 if (stroke > 0) {
1135 double stroke2 = stroke / 2;
1136 xmax += stroke2;
1137 xmin -= stroke2;
1138 ymax += stroke2;
1139 ymin -= stroke2;
1140 }
1141
1142 if (points.size == 1) {
1143 EditPoint e = points.get (0);
1144 xmax = e.x;
1145 xmin = e.x;
1146 ymax = e.y;
1147 ymin = e.y;
1148 }
1149 }
1150
1151 public void transform (Matrix matrix) {
1152 double x, y;
1153 EditPointHandle handle;
1154
1155 foreach (EditPoint point in points) {
1156 x = point.x;
1157 y = point.y;
1158
1159 matrix.transform_point (ref x, ref y);
1160
1161 point.independent_x = x;
1162 point.independent_y = y;
1163
1164 handle = point.get_right_handle ();
1165
1166 x = handle.x;
1167 y = handle.y;
1168
1169 matrix.transform_point (ref x, ref y);
1170
1171 handle.independent_x = x;
1172 handle.independent_y = y;
1173
1174 handle = point.get_left_handle ();
1175
1176 x = handle.x;
1177 y = handle.y;
1178
1179 matrix.transform_point (ref x, ref y);
1180
1181 handle.independent_x = x;
1182 handle.independent_y = y;
1183
1184 }
1185
1186 update_region_boundaries ();
1187 }
1188
1189 /** Test if @param path is a valid outline for this object. */
1190 public bool test_is_outline (Path path) {
1191 assert (false);
1192 return this.test_is_outline_of_path (path) && path.test_is_outline_of_path (this);
1193 }
1194
1195 private bool test_is_outline_of_path (Path outline)
1196 requires (outline.points.size >= 2 || points.size >= 2)
1197 {
1198 // rather slow use it for testing, only
1199 unowned EditPoint i = outline.points.get (0).get_link_item ();
1200 unowned EditPoint prev = outline.points.get (outline.points.size - 1).get_link_item ();
1201
1202 double tolerance = 1;
1203 bool g = false;
1204
1205 EditPoint ep = new EditPoint (0, 0);
1206 double min = double.MAX;
1207
1208 while (true) {
1209 min = 10000;
1210
1211 all_of (prev, i, (cx, cy) => {
1212 get_closest_point_on_path (ep, cx, cy);
1213
1214 double n = pow (ep.x - cx, 2) + pow (cy - ep.y, 2);
1215
1216 if (n < min) min = n;
1217
1218 if (n < tolerance) {
1219 g = true;
1220 return false;
1221 }
1222
1223 return true;
1224 });
1225
1226 if (!g) {
1227 critical (@"this path does not seem to be the outline. (min $min)");
1228 }
1229
1230 g = false;
1231
1232 if (i == outline.points.get (outline.points.size - 1)) {
1233 break;
1234 }
1235
1236 i = i.get_next ();
1237 }
1238
1239 return true;
1240 }
1241
1242 /** Add the extra point between line handles for double curve. */
1243 public void add_hidden_double_points () requires (points.size > 1) {
1244 EditPoint hidden;
1245 EditPoint prev;
1246 EditPoint first;
1247 PointType left;
1248 PointType right;
1249 double x, y;
1250 Gee.ArrayList<EditPoint> middle_points = new Gee.ArrayList<EditPoint> ();
1251 Gee.ArrayList<EditPoint> first_points = new Gee.ArrayList<EditPoint> ();
1252
1253 first = is_open () ? points.get (0) : points.get (points.size - 1);
1254
1255 foreach (EditPoint next in points) {
1256 left = first.get_right_handle ().type;
1257 right = next.get_left_handle ().type;
1258
1259 if (next != first && (right == PointType.DOUBLE_CURVE || left == PointType.DOUBLE_CURVE)) {
1260
1261 first.get_right_handle ().type = PointType.QUADRATIC;
1262
1263 // half way between handles
1264 x = first.get_right_handle ().x + (next.get_left_handle ().x - first.get_right_handle ().x) / 2;
1265 y = first.get_right_handle ().y + (next.get_left_handle ().y - first.get_right_handle ().y) / 2;
1266
1267 hidden = new EditPoint (x, y, PointType.QUADRATIC);
1268 hidden.get_right_handle ().type = PointType.QUADRATIC;
1269 hidden.get_left_handle ().type = PointType.QUADRATIC;
1270 hidden.type = PointType.QUADRATIC;
1271
1272 hidden.right_handle.move_to_coordinate_internal (next.get_left_handle ().x, next.get_left_handle ().y);
1273
1274 first.get_right_handle ().type = PointType.QUADRATIC;
1275 first.type = PointType.QUADRATIC;
1276
1277 next.get_left_handle ().type = PointType.QUADRATIC;
1278 next.type = PointType.QUADRATIC;
1279
1280 middle_points.add (hidden);
1281 first_points.add (first);
1282 }
1283 first = next;
1284 }
1285
1286 for (int i = 0; i < middle_points.size; i++) {
1287 hidden = middle_points.get (i);
1288 add_point_after (middle_points.get (i), first_points.get (i));
1289 }
1290
1291 create_list ();
1292
1293 prev = get_last_point ();
1294 foreach (EditPoint ep in points) {
1295 if (ep.type == PointType.QUADRATIC) {
1296 x = prev.get_right_handle ().x;
1297 y = prev.get_right_handle ().y;
1298 ep.get_left_handle ().move_to_coordinate (x, y);
1299 }
1300
1301 prev = ep;
1302 }
1303 }
1304
1305 /** Convert quadratic bezier points to cubic representation of the glyph
1306 * for ttf-export.
1307 */
1308 public Path get_quadratic_points () {
1309 PointConverter converter;
1310 converter = new PointConverter (this);
1311 return converter.get_quadratic_path ();
1312 }
1313
1314 public void insert_new_point_on_path (EditPoint ep, double t = -1, bool move_point_to_path = false) {
1315 EditPoint start, stop;
1316 double x0, x1, y0, y1;
1317 double position, min;
1318 PointType left, right;
1319 double closest_x = 0;
1320 double closest_y = 0;
1321
1322 if (ep.next == null || ep.prev == null) {
1323 warning ("missing point");
1324 return;
1325 }
1326
1327 start = ep.get_prev ();
1328 stop = ep.get_next ();
1329
1330 right = start.get_right_handle ().type;
1331 left = stop.get_left_handle ().type;
1332
1333 if (right == PointType.CUBIC || left == PointType.CUBIC) {
1334 start.get_right_handle ().type = PointType.CUBIC;
1335 stop.get_left_handle ().type = PointType.CUBIC;
1336 }
1337
1338 add_point_after (ep, ep.get_prev ());
1339
1340 min = double.MAX;
1341
1342 position = 0.5;
1343
1344 if (t < 0) {
1345 all_of (start, stop, (cx, cy, t) => {
1346 double n = pow (ep.x - cx, 2) + pow (ep.y - cy, 2);
1347
1348 if (n < min) {
1349 min = n;
1350 position = t;
1351 closest_x = cx;
1352 closest_y = cy;
1353 }
1354
1355 return true;
1356 });
1357
1358 if (move_point_to_path) {
1359 ep.x = closest_x;
1360 ep.y = closest_y;
1361 }
1362 } else {
1363 position = t;
1364 }
1365
1366 if (right == PointType.DOUBLE_CURVE || left == PointType.DOUBLE_CURVE) {
1367 double_bezier_vector (position, start.x, start.get_right_handle ().x, stop.get_left_handle ().x, stop.x, out x0, out x1);
1368 double_bezier_vector (position, start.y, start.get_right_handle ().y, stop.get_left_handle ().y, stop.y, out y0, out y1);
1369
1370 ep.get_left_handle ().set_point_type (PointType.DOUBLE_CURVE);
1371 ep.get_right_handle ().set_point_type (PointType.DOUBLE_CURVE);
1372
1373 ep.get_left_handle ().move_to_coordinate (x0, y0);
1374 ep.get_right_handle ().move_to_coordinate (x1, y1);
1375
1376 ep.type = PointType.DOUBLE_CURVE;
1377 } else if (right == PointType.QUADRATIC) {
1378 x0 = quadratic_bezier_vector (1 - position, stop.x, start.get_right_handle ().x, start.x);
1379 y0 = quadratic_bezier_vector (1 - position, stop.y, start.get_right_handle ().y, start.y);
1380 ep.get_right_handle ().move_to_coordinate (x0, y0);
1381
1382 ep.get_left_handle ().set_point_type (PointType.QUADRATIC);
1383 ep.get_right_handle ().set_point_type (PointType.QUADRATIC);
1384
1385 ep.get_left_handle ().move_to_coordinate_internal (0, 0);
1386
1387 ep.type = PointType.QUADRATIC;
1388 } else if (right == PointType.CUBIC || left == PointType.CUBIC) {
1389 bezier_vector (position, start.x, start.get_right_handle ().x, stop.get_left_handle ().x, stop.x, out x0, out x1);
1390 bezier_vector (position, start.y, start.get_right_handle ().y, stop.get_left_handle ().y, stop.y, out y0, out y1);
1391
1392 ep.get_left_handle ().set_point_type (PointType.CUBIC);
1393 ep.get_left_handle ().move_to_coordinate (x0, y0);
1394
1395 ep.get_right_handle ().set_point_type (PointType.CUBIC);
1396 ep.get_right_handle ().move_to_coordinate (x1, y1);
1397
1398 ep.type = PointType.LINE_CUBIC;
1399 } else if (right == PointType.LINE_QUADRATIC && left == PointType.LINE_QUADRATIC) {
1400 ep.get_right_handle ().set_point_type (PointType.LINE_QUADRATIC);
1401 ep.get_left_handle ().set_point_type (PointType.LINE_QUADRATIC);
1402 ep.type = PointType.QUADRATIC;
1403 } else if (right == PointType.LINE_CUBIC && left == PointType.LINE_CUBIC) {
1404 ep.get_right_handle ().set_point_type (PointType.LINE_CUBIC);
1405 ep.get_left_handle ().set_point_type (PointType.LINE_CUBIC);
1406 ep.type = PointType.LINE_CUBIC;
1407 } else if (right == PointType.LINE_DOUBLE_CURVE && left == PointType.LINE_DOUBLE_CURVE) {
1408 ep.get_right_handle ().set_point_type (PointType.LINE_DOUBLE_CURVE);
1409 ep.get_left_handle ().set_point_type (PointType.LINE_DOUBLE_CURVE);
1410 ep.type = PointType.DOUBLE_CURVE;
1411 } else {
1412 warning (@"Point types: $right and $left in insert_new_point_on_path");
1413 }
1414
1415 ep.get_left_handle ().parent = ep;
1416 ep.get_right_handle ().parent = ep;
1417
1418 stop.get_left_handle ().length *= 1 - position;
1419 start.get_right_handle ().length *= position;
1420
1421 if (right == PointType.QUADRATIC) { // update connected handle
1422 if (ep.prev != null) {
1423 ep.get_left_handle ().move_to_coordinate_internal (
1424 ep.get_prev ().right_handle.x,
1425 ep.get_prev ().right_handle.y);
1426
1427 } else {
1428 warning ("ep.prev is null for quadratic point");
1429 }
1430 }
1431
1432 create_list ();
1433 recalculate_linear_handles ();
1434 }
1435
1436 /** Get a point on the this path closest to x and y coordinates.
1437 * Don't look for a point in the segment skip_previous to skip_next.
1438 */
1439 public void get_closest_point_on_path (EditPoint edit_point, double x, double y,
1440 EditPoint? skip_previous = null, EditPoint? skip_next = null,
1441 int steps = -1) {
1442 return_if_fail (points.size >= 1);
1443
1444 double min = double.MAX;
1445 double n = 0;
1446 bool g = false;
1447
1448 double ox = 0;
1449 double oy = 0;
1450
1451 EditPoint prev = points.get (points.size - 1);
1452 EditPoint i = points.get (0);
1453
1454 bool done = false;
1455 bool exit = false;
1456 bool first = true;
1457
1458 EditPoint? previous_point = null;
1459 EditPoint? next_point = null;
1460
1461 EditPoint previous;
1462 EditPoint next;
1463 double step = 0;
1464
1465 if (points.size == 0) {
1466 warning ("Empty path.");
1467 return;
1468 }
1469
1470 if (points.size == 1) {
1471 edit_point.x = i.x;
1472 edit_point.y = i.y;
1473
1474 edit_point.prev = i;
1475 edit_point.next = i;
1476 return;
1477 }
1478
1479 edit_point.x = i.x;
1480 edit_point.y = i.y;
1481
1482 create_list ();
1483
1484 while (!exit) {
1485 if (!first && i == points.get (points.size - 1)) {
1486 done = true;
1487 }
1488
1489 if (!done) {
1490 i = i.get_next ();
1491 prev = i.get_prev ();
1492 } else if (done && !is_open ()) {
1493 i = points.get (0);
1494 prev = points.get (points.size - 1);
1495 exit = true;
1496 } else {
1497 break;
1498 }
1499
1500 if (skip_previous == prev) {
1501 continue;
1502 }
1503
1504 if (prev.prev != null && skip_previous == prev.get_prev ()) {
1505 continue;
1506 }
1507
1508 if (skip_next == i) {
1509 continue;
1510 }
1511
1512 if (prev.next != null && skip_next == prev.get_next ()) {
1513 continue;
1514 }
1515
1516 all_of (prev, i, (cx, cy, t) => {
1517 n = pow (x - cx, 2) + pow (y - cy, 2);
1518
1519 if (n < min) {
1520 min = n;
1521
1522 ox = cx;
1523 oy = cy;
1524
1525 previous_point = i.prev;
1526 next_point = i;
1527
1528 step = t;
1529
1530 g = true;
1531 }
1532
1533 return true;
1534 }, steps);
1535
1536 first = false;
1537 }
1538
1539 if (previous_point == null && is_open ()) {
1540 previous_point = points.get (points.size - 1).get_link_item ();
1541 }
1542
1543 if (previous_point == null) {
1544 warning (@"previous_point == null, points.size: $(points.size)");
1545 return;
1546 }
1547
1548 if (next_point == null) {
1549 warning ("next_point != null");
1550 return;
1551 }
1552
1553 previous = (!) previous_point;
1554 next = (!) next_point;
1555
1556 edit_point.prev = previous_point;
1557 edit_point.next = next_point;
1558
1559 edit_point.set_position (ox, oy);
1560
1561 edit_point.type = previous.type;
1562 edit_point.get_left_handle ().type = previous.get_right_handle ().type;
1563 edit_point.get_right_handle ().type = next.get_left_handle ().type;
1564 }
1565
1566 public static bool all_of (EditPoint start, EditPoint stop,
1567 RasterIterator iter, int steps = -1,
1568 double min_t = 0, double max_t = 1) {
1569
1570 PointType right = PenTool.to_curve (start.get_right_handle ().type);
1571 PointType left = PenTool.to_curve (stop.get_left_handle ().type);
1572
1573 if (steps == -1) {
1574 steps = (int) (10 * get_length_from (start, stop));
1575 }
1576
1577 if (right == PointType.DOUBLE_CURVE || left == PointType.DOUBLE_CURVE) {
1578 return all_of_double (start.x, start.y, start.get_right_handle ().x, start.get_right_handle ().y, stop.get_left_handle ().x, stop.get_left_handle ().y, stop.x, stop.y, iter, steps, min_t, max_t);
1579 } else if (right == PointType.QUADRATIC && left == PointType.QUADRATIC) {
1580 return all_of_quadratic_curve (start.x, start.y, start.get_right_handle ().x, start.get_right_handle ().y, stop.x, stop.y, iter, steps, min_t, max_t);
1581 } else if (right == PointType.CUBIC && left == PointType.CUBIC) {
1582 return all_of_curve (start.x, start.y, start.get_right_handle ().x, start.get_right_handle ().y, stop.get_left_handle ().x, stop.get_left_handle ().y, stop.x, stop.y, iter, steps, min_t, max_t);
1583 }
1584
1585 if (start.x == stop.x && start.y == stop.y) {
1586 warning ("Zero length.");
1587 return true;
1588 }
1589
1590 // warning (@"Mixed point types in segment $(start.x),$(start.y) to $(stop.x),$(stop.y) right: $(right), left: $(left) (start: $(start.type), stop: $(stop.type))");
1591 return all_of_quadratic_curve (start.x, start.y, start.get_right_handle ().x, start.get_right_handle ().x, stop.x, stop.y, iter, steps);
1592 }
1593
1594 public static void get_point_for_step (EditPoint start, EditPoint stop, double step,
1595 out double x, out double y) {
1596
1597 PointType right = PenTool.to_curve (start.get_right_handle ().type);
1598 PointType left = PenTool.to_curve (stop.get_left_handle ().type);
1599
1600 if (right == PointType.DOUBLE_CURVE && left == PointType.DOUBLE_CURVE) {
1601 x = double_bezier_path (step, start.x, start.get_right_handle ().x, stop.get_left_handle ().x, stop.x);
1602 y = double_bezier_path (step, start.y, start.get_right_handle ().y, stop.get_left_handle ().y, stop.y);
1603 } else if (right == PointType.QUADRATIC && left == PointType.QUADRATIC) {
1604 x = quadratic_bezier_path (step, start.x, start.get_right_handle ().x, stop.x);
1605 y = quadratic_bezier_path (step, start.y, start.get_right_handle ().y, stop.y);
1606 } else if (right == PointType.CUBIC && left == PointType.CUBIC) {
1607 x = bezier_path (step, start.x, start.get_right_handle ().x, stop.get_left_handle ().x, stop.x);
1608 y = bezier_path (step, start.y, start.get_right_handle ().y, stop.get_left_handle ().y, stop.y);
1609 } else if (right == PointType.HIDDEN && left == PointType.HIDDEN) {
1610 x = bezier_path (step, start.x, start.get_right_handle ().x, stop.get_left_handle ().x, stop.x);
1611 y = bezier_path (step, start.y, start.get_right_handle ().y, stop.get_left_handle ().y, stop.y);
1612 } else {
1613 warning (@"Mixed point types in segment $(start.x),$(start.y) to $(stop.x),$(stop.y) right: $(right), left: $(left) (start: $(start.type), stop: $(stop.type))");
1614 x = bezier_path (step, start.x, start.get_right_handle ().x, stop.get_left_handle ().x, stop.x);
1615 y = bezier_path (step, start.y, start.get_right_handle ().y, stop.get_left_handle ().y, stop.y);
1616 }
1617 }
1618
1619 private static bool all_of_double (double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3,
1620 RasterIterator iter, double steps = 400, double min_t = 0, double max_t = 1) {
1621
1622 double px = x1;
1623 double py = y1;
1624
1625 double t;
1626 double middle_x, middle_y;
1627 double double_step;
1628
1629 middle_x = x1 + (x2 - x1) / 2;
1630 middle_y = y1 + (y2 - y1) / 2;
1631
1632 for (int i = 0; i < steps; i++) {
1633 t = i / steps + min_t;
1634
1635 px = quadratic_bezier_path (t, x0, x1, middle_x);
1636 py = quadratic_bezier_path (t, y0, y1, middle_y);
1637
1638 double_step = t / 2;
1639
1640 if (double_step > max_t) {
1641 return false;
1642 }
1643
1644 if (!iter (px, py, double_step)) {
1645 return false;
1646 }
1647 }
1648
1649 for (int i = 0; i < steps; i++) {
1650 t = i / steps + min_t;
1651
1652 px = quadratic_bezier_path (t, middle_x, x2, x3);
1653 py = quadratic_bezier_path (t, middle_y, y2, y3);
1654
1655 double_step = 0.5 + t / 2;
1656
1657 if (double_step > max_t) {
1658 return false;
1659 }
1660
1661 if (!iter (px, py, double_step)) {
1662 return false;
1663 }
1664 }
1665
1666 return true;
1667 }
1668
1669 private static bool all_of_quadratic_curve (double x0, double y0, double x1, double y1, double x2, double y2,
1670 RasterIterator iter, double steps = 400, double min_t = 0, double max_t = 1) {
1671 double px = x1;
1672 double py = y1;
1673
1674 double t;
1675
1676 for (int i = 0; i < steps; i++) {
1677 t = i / steps + min_t;
1678
1679 px = quadratic_bezier_path (t, x0, x1, x2);
1680 py = quadratic_bezier_path (t, y0, y1, y2);
1681
1682 if (t > max_t) {
1683 return false;
1684 }
1685
1686 if (!iter (px, py, t)) {
1687 return false;
1688 }
1689 }
1690
1691 return true;
1692 }
1693
1694 private static bool all_of_curve (double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3,
1695 RasterIterator iter, double steps = 400, double min_t = 0, double max_t = 1) {
1696 double px = x1;
1697 double py = y1;
1698
1699 double t;
1700
1701 for (int i = 0; i < steps; i++) {
1702 t = i / steps + min_t;
1703
1704 px = bezier_path (t, x0, x1, x2, x3);
1705 py = bezier_path (t, y0, y1, y2, y3);
1706
1707 if (t > max_t) {
1708 return false;
1709 }
1710
1711 if (!iter (px, py, t)) {
1712 return false;
1713 }
1714 }
1715
1716 return true;
1717 }
1718
1719 public bool all_segments (SegmentIterator iter) {
1720 unowned EditPoint i, next;
1721
1722 if (points.size < 2) {
1723 return false;
1724 }
1725
1726 for (int j = 0; j < points.size - 1; j++) {
1727 i = points.get (j).get_link_item ();
1728 next = i.get_next ();
1729 if (!iter (i, next)) {
1730 return false;
1731 }
1732 }
1733
1734 if (!is_open ()) {
1735 return iter (points.get (points.size - 1), points.get (0));
1736 }
1737
1738 return true;
1739 }
1740
1741 public void all_of_path (RasterIterator iter, int steps = -1) {
1742 all_segments ((start, stop) => {
1743 return all_of (start, stop, iter, steps);
1744 });
1745 }
1746
1747 public static double bezier_path (double step, double p0, double p1, double p2, double p3) {
1748 double q0, q1, q2;
1749 double r0, r1;
1750
1751 q0 = step * (p1 - p0) + p0;
1752 q1 = step * (p2 - p1) + p1;
1753 q2 = step * (p3 - p2) + p2;
1754
1755 r0 = step * (q1 - q0) + q0;
1756 r1 = step * (q2 - q1) + q1;
1757
1758 return step * (r1 - r0) + r0;
1759 }
1760
1761 public static void bezier_vector (double step, double p0, double p1, double p2, double p3, out double a0, out double a1) {
1762 double q0, q1, q2;
1763
1764 q0 = step * (p1 - p0) + p0;
1765 q1 = step * (p2 - p1) + p1;
1766 q2 = step * (p3 - p2) + p2;
1767
1768 a0 = step * (q1 - q0) + q0;
1769 a1 = step * (q2 - q1) + q1;
1770 }
1771
1772 public static double quadratic_bezier_vector (double step, double p0, double p1, double p2) {
1773 return step * (p1 - p0) + p0;
1774 }
1775
1776 public static double quadratic_bezier_path (double step, double p0, double p1, double p2) {
1777 double q0 = step * (p1 - p0) + p0;
1778 double q1 = step * (p2 - p1) + p1;
1779
1780 return step * (q1 - q0) + q0;
1781 }
1782
1783 public static double double_bezier_path (double step, double p0, double p1, double p2, double p3) {
1784 double middle = p1 + (p2 - p1) / 2;
1785
1786 if (step == 0.5) {
1787 // FIXME: return the middle point
1788 warning ("Middle");
1789 }
1790
1791 if (step < 0.5) {
1792 return quadratic_bezier_path (2 * step, p0, p1, middle);
1793 }
1794
1795 return quadratic_bezier_path (2 * (step - 0.5), middle, p2, p3);
1796 }
1797
1798 public static void double_bezier_vector (double step, double p0, double p1, double p2, double p3, out double a0, out double a1) {
1799 double b0, b1, c0, c1, d0, d1;
1800
1801 if (unlikely (step <= 0 || step >= 1)) {
1802 warning (@"Bad step: $step");
1803 step += 0.00004;
1804 }
1805
1806 // set angle
1807 b0 = double_bezier_path (step + 0.00001, p0, p1, p2, p3);
1808 c0 = double_bezier_path (step + 0.00002, p0, p1, p2, p3);
1809
1810 b1 = double_bezier_path (step - 0.00001, p0, p1, p2, p3);
1811 c1 = double_bezier_path (step - 0.00002, p0, p1, p2, p3);
1812
1813 // adjust length
1814 d0 = b0 + (b0 - c0) * 25000 * (step);
1815 d1 = b1 + (b1 - c1) * 25000 * (1 - step);
1816
1817 a0 = d0;
1818 a1 = d1;
1819 }
1820
1821 public static void get_handles_for_step (EditPoint start, EditPoint stop, double step,
1822 out double x1, out double y1, out double x2, out double y2) {
1823
1824 PointType right = PenTool.to_curve (start.type);
1825 PointType left = PenTool.to_curve (stop.type);
1826
1827 if (right == PointType.DOUBLE_CURVE || left == PointType.DOUBLE_CURVE) {
1828 double_bezier_vector (step, start.x, start.get_right_handle ().x, stop.get_left_handle ().x, stop.x, out x1, out x2); // FIXME: swap parameter?
1829 double_bezier_vector (step, start.y, start.get_right_handle ().y, stop.get_left_handle ().y, stop.y, out y1, out y2);
1830 } else if (right == PointType.QUADRATIC && left == PointType.QUADRATIC) {
1831 x1 = quadratic_bezier_vector (step, start.x, start.get_right_handle ().x, stop.x);
1832 y1 = quadratic_bezier_vector (step, start.y, start.get_right_handle ().y, stop.y);
1833 x2 = x1;
1834 y2 = y1;
1835 } else if (right == PointType.CUBIC && left == PointType.CUBIC) {
1836 bezier_vector (step, start.x, start.get_right_handle ().x, stop.get_left_handle ().x, stop.x, out x1, out x2);
1837 bezier_vector (step, start.y, start.get_right_handle ().y, stop.get_left_handle ().y, stop.y, out y1, out y2);
1838 } else if (right == PointType.HIDDEN && left == PointType.HIDDEN) {
1839 bezier_vector (step, start.x, start.get_right_handle ().x, stop.get_left_handle ().x, stop.x, out x1, out x2);
1840 bezier_vector (step, start.y, start.get_right_handle ().y, stop.get_left_handle ().y, stop.y, out y1, out y2);
1841 } else {
1842 // warning (@"Mixed point types in segment $(start.x),$(start.y) to $(stop.x),$(stop.y) right: $(right), left: $(left) (start: $(start.type), stop: $(stop.type))");
1843 bezier_vector (step, start.x, start.get_right_handle ().x, stop.get_left_handle ().x, stop.x, out x1, out x2);
1844 bezier_vector (step, start.y, start.get_right_handle ().y, stop.get_left_handle ().y, stop.y, out y1, out y2);
1845 }
1846 }
1847
1848 public void plot (Context cr, WidgetAllocation allocation, double view_zoom) {
1849 double px = 0, py = 0;
1850 double xc = allocation.width / 2.0;
1851 double yc = allocation.height / 2.0;
1852
1853 cr.save ();
1854
1855 all_of_path ((x, y) => {
1856 cr.move_to (px + xc, -py + yc);
1857 cr.line_to (x + xc, -y + yc);
1858
1859 px = x;
1860 py = y;
1861
1862 return true;
1863 });
1864
1865 cr.stroke ();
1866 cr.restore ();
1867 }
1868
1869 public void print_boundaries () {
1870 stderr.printf (@"xmax $xmax \n");
1871 stderr.printf (@"xmin $xmin \n");
1872 stderr.printf (@"ymax $ymax \n");
1873 stderr.printf (@"ymin $ymin \n");
1874 }
1875
1876 public bool has_region_boundaries () {
1877 return !(xmax == -10000 || xmin == 10000 || ymax == -10000 || ymin == 10000);
1878 }
1879
1880 public void create_list () {
1881 EditPoint ep;
1882
1883 if (points.size == 0) {
1884 return;
1885 }
1886
1887 if (points.size == 1) {
1888 ep = points.get (0);
1889 ep.next = null;
1890 ep.prev = null;
1891 return;
1892 }
1893
1894 ep = points.get (0);
1895 ep.next = points.get (1).get_link_item ();
1896 ep.prev = points.get (points.size - 1).get_link_item ();
1897
1898 for (int i = 1; i < points.size - 1; i++) {
1899 ep = points.get (i);
1900 ep.prev = points.get (i - 1).get_link_item ();
1901 ep.next = points.get (i + 1).get_link_item ();
1902 }
1903
1904 ep = points.get (points.size - 1);
1905 ep.next = points.get (0).get_link_item ();
1906 ep.prev = points.get (points.size - 2).get_link_item ();
1907 }
1908
1909 public bool has_point (EditPoint ep) {
1910 return points.contains (ep);
1911 }
1912
1913 public bool has_deleted_point () {
1914 foreach (EditPoint p in points) {
1915 if (p.deleted) {
1916 return true;
1917 }
1918 }
1919 return false;
1920 }
1921
1922 /** @return the remaining parts as a new path. */
1923 public PathList process_deleted_points ()
1924 requires (points.size > 0)
1925 {
1926 EditPoint p;
1927 EditPoint ep;
1928 Path current_path = new Path ();
1929 Path remaining_points = new Path ();
1930 PathList path_list = new PathList ();
1931 int i;
1932 int index = 0;
1933
1934 remaining_points.stroke = stroke;
1935 current_path.stroke = stroke;
1936
1937 if (!has_deleted_point ()) {
1938 return path_list;
1939 }
1940
1941 if (points.size == 1) {
1942 points.remove_at (0);
1943 return path_list;
1944 }
1945
1946 // set start position to a point that will be removed
1947 for (i = 0; i < points.size; i++) {
1948 p = points.get (i);
1949
1950 if (p.deleted) {
1951 index = i;
1952 i++;
1953 ep = p;
1954 break;
1955 }
1956 }
1957
1958 // don't tie end points on the open path
1959 if (points.size > 1) {
1960 p = points.get (1);
1961 p.convert_to_curve ();
1962 p.set_reflective_handles (false);
1963 p.set_tie_handle (false);
1964 }
1965
1966 if (points.size > 0) {
1967 p = points.get (points.size - 1);
1968 p.convert_to_curve ();
1969 p.set_reflective_handles (false);
1970 p.set_tie_handle (false);
1971 }
1972
1973 // copy points after the deleted point
1974 while (i < points.size) {
1975 p = points.get (i);
1976 current_path.add_point (p);
1977 i++;
1978 }
1979
1980 // copy points before the deleted point
1981 for (i = 0; i < index; i++) {
1982 p = points.get (i);
1983 remaining_points.add_point (p);
1984 }
1985
1986 // merge if we still only have one path
1987 if (!is_open ()) {
1988 foreach (EditPoint point in remaining_points.points) {
1989 current_path.add_point (point.copy ());
1990 }
1991
1992 if (current_path.points.size > 0) {
1993 ep = current_path.points.get (0);
1994 ep.set_tie_handle (false);
1995 ep.set_reflective_handles (false);
1996 ep.get_left_handle ().type = PenTool.to_line (ep.type);
1997 ep.type = PenTool.to_curve (ep.type);
1998 path_list.add (current_path);
1999
2000 ep = current_path.points.get (current_path.points.size - 1);
2001 ep.get_right_handle ().type = PenTool.to_line (ep.type);
2002 ep.type = PenTool.to_curve (ep.get_right_handle ().type);
2003 }
2004 } else {
2005 if (current_path.points.size > 0) {
2006 ep = current_path.points.get (0);
2007 ep.set_tie_handle (false);
2008 ep.set_reflective_handles (false);
2009 ep.get_left_handle ().type = PenTool.to_line (ep.type);
2010 ep.type = PenTool.to_curve (ep.type);
2011 set_new_start (current_path.points.get (0));
2012 path_list.add (current_path);
2013 ep = current_path.points.get (current_path.points.size - 1);
2014 ep.get_right_handle ().type = PenTool.to_line (ep.type);
2015 ep.type = PenTool.to_curve (ep.get_right_handle ().type);
2016 }
2017
2018 if (remaining_points.points.size > 0) {
2019 remaining_points.points.get (0).set_tie_handle (false);
2020 remaining_points.points.get (0).set_reflective_handles (false);
2021 remaining_points.points.get (0).type = remaining_points.points.get (0).type;
2022 set_new_start (remaining_points.points.get (0));
2023 path_list.add (remaining_points);
2024
2025 if (current_path.points.size > 0) {
2026 ep = current_path.points.get (current_path.points.size - 1);
2027 ep.get_right_handle ().type = PenTool.to_line (ep.type);
2028 ep.type = PenTool.to_curve (ep.get_right_handle ().type);
2029 }
2030 }
2031 }
2032
2033 foreach (Path path in path_list.paths) {
2034 path.update_region_boundaries ();
2035 }
2036
2037 return path_list;
2038 }
2039
2040 public void set_new_start (EditPoint ep)
2041 requires (points.size > 0) {
2042 Gee.ArrayList<EditPoint> list = new Gee.ArrayList<EditPoint> ();
2043 int start = 0;
2044
2045 for (int i = 0; i < points.size; i++) {
2046 if (ep == points.get (i)) {
2047 start = i;
2048 }
2049 }
2050
2051 for (int i = start; i < points.size; i++) {
2052 list.add (points.get (i));
2053 }
2054
2055 for (int i = 0; i < start; i++) {
2056 list.add (points.get (i));
2057 }
2058
2059 control_points = list;
2060 }
2061
2062 public void append_path (Path path) {
2063 if (points.size == 0 || path.points.size == 0) {
2064 warning ("No points");
2065 return;
2066 }
2067
2068 // copy remaining points
2069 foreach (EditPoint p in path.points) {
2070 add_point (p.copy ());
2071 }
2072
2073 path.points.clear ();
2074 }
2075
2076 /** Roatate around coordinate xc, xc. */
2077 public void rotate (double theta, double xc, double yc) {
2078 double a, radius;
2079
2080 foreach (EditPoint ep in points) {
2081 radius = sqrt (pow (xc - ep.x, 2) + pow (yc + ep.y, 2));
2082
2083 if (yc + ep.y < 0) {
2084 radius = -radius;
2085 }
2086
2087 a = acos ((ep.x - xc) / radius);
2088
2089 ep.x = xc + cos (a - theta) * radius;
2090 ep.y = yc + sin (a - theta) * radius;
2091
2092 ep.get_right_handle ().angle -= theta;
2093 ep.get_left_handle ().angle -= theta;
2094
2095 while (ep.get_right_handle ().angle < 0) {
2096 ep.get_right_handle ().angle += 2 * PI;
2097 }
2098
2099 while (ep.get_left_handle ().angle < 0) {
2100 ep.get_left_handle ().angle += 2 * PI;
2101 }
2102 }
2103
2104 rotation += theta;
2105 rotation %= 2 * PI;
2106
2107 update_region_boundaries ();
2108 }
2109
2110 public void flip_vertical () {
2111 EditPointHandle hl, hr;
2112 double lx, ly, rx, ry;
2113
2114 foreach (EditPoint e in points) {
2115 hl = e.get_left_handle ();
2116 hr = e.get_right_handle ();
2117
2118 lx = hl.x;
2119 ly = hl.y;
2120 rx = hr.x;
2121 ry = hr.y;
2122
2123 e.y *= -1;
2124
2125 hr.move_to_coordinate_internal (rx, -1 * ry);
2126 hl.move_to_coordinate_internal (lx, -1 * ly);
2127 }
2128
2129 update_region_boundaries ();
2130 }
2131
2132 public void flip_horizontal () {
2133 EditPointHandle hl, hr;
2134 double lx, ly, rx, ry;
2135 foreach (EditPoint e in points) {
2136 hl = e.get_left_handle ();
2137 hr = e.get_right_handle ();
2138
2139 lx = hl.x;
2140 ly = hl.y;
2141 rx = hr.x;
2142 ry = hr.y;
2143
2144 e.x *= -1;
2145
2146 hr.move_to_coordinate_internal (-1 * rx, ry);
2147 hl.move_to_coordinate_internal (-1 * lx, ly);
2148 }
2149
2150 update_region_boundaries ();
2151 }
2152
2153 public void init_point_type (PointType pt = PointType.NONE) {
2154 PointType type;
2155
2156 if (pt == PointType.NONE) {
2157 pt = DrawingTools.point_type;
2158 }
2159
2160 switch (pt) {
2161 case PointType.QUADRATIC:
2162 type = PointType.LINE_QUADRATIC;
2163 break;
2164 case PointType.DOUBLE_CURVE:
2165 type = PointType.LINE_DOUBLE_CURVE;
2166 break;
2167 case PointType.CUBIC:
2168 type = PointType.LINE_CUBIC;
2169 break;
2170 default:
2171 warning ("No type is set");
2172 type = PointType.LINE_CUBIC;
2173 break;
2174 }
2175
2176 foreach (EditPoint ep in points) {
2177 ep.type = type;
2178 ep.get_right_handle ().type = type;
2179 ep.get_left_handle ().type = type;
2180 }
2181 }
2182
2183 public void convert_path_ending_to_line () {
2184 if (points.size < 2) {
2185 return;
2186 }
2187
2188 get_first_point ().get_left_handle ().convert_to_line ();
2189 get_last_point ().get_right_handle ().convert_to_line ();
2190 }
2191
2192 public void print_all_types () {
2193 print (@"Control points:\n");
2194 foreach (EditPoint ep in points) {
2195 print (@"$(ep.type) L: $(ep.get_left_handle ().type) R: L: $(ep.get_right_handle ().type)\n");
2196 }
2197 }
2198
2199 /** Find the point where two lines intersect. */
2200 public static void find_intersection (double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4,
2201 out double point_x, out double point_y) {
2202 point_x = ((x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4)) / ((x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4));
2203 point_y = ((x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4)) / ((x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4));
2204 }
2205
2206 public static void find_intersection_handle (EditPointHandle h1, EditPointHandle h2, out double point_x, out double point_y) {
2207 find_intersection (h1.parent.x, h1.parent.y, h1.x, h1.y, h2.parent.x, h2.parent.y, h2.x, h2.y, out point_x, out point_y);
2208 }
2209
2210 /** Finx intersection point for two straight lines. */
2211 public static void find_intersection_point (EditPoint p1, EditPoint p2, EditPoint q1, EditPoint q2, out double point_x, out double point_y) {
2212 find_intersection (p1.x, p1.y, p2.x, p2.y, q1.x, q1.y, q2.x, q2.y, out point_x, out point_y);
2213 }
2214
2215 public EditPoint insert_new_point_on_path_at (double x, double y) {
2216 EditPoint ep = new EditPoint ();
2217 EditPoint prev, next;
2218 bool exists;
2219
2220 if (points.size < 2) {
2221 warning ("Can't add extrema to just one point.");
2222 return ep;
2223 }
2224
2225 get_closest_point_on_path (ep, x, y);
2226
2227 next = (ep.next == null) ? points.get (0) : ep.get_next ();
2228 prev = (ep.prev == null) ? points.get (points.size - 1) : ep.get_prev ();
2229
2230 exists = prev.x == ep.x && prev.y == ep.y;
2231 exists |= next.x == ep.x && next.y == ep.y;
2232
2233 if (!exists) {
2234 insert_new_point_on_path (ep);
2235 }
2236
2237 return ep;
2238 }
2239
2240 public static bool is_counter (PathList pl, Path path) {
2241 return counters (pl, path) % 2 != 0;
2242 }
2243
2244 public static int counters (PathList pl, Path path) {
2245 int inside_count = 0;
2246 bool inside;
2247 PathList lines = new PathList ();
2248
2249 lines = pl;
2250
2251 /** // FIXME: Check automatic orientation.
2252 foreach (Path p in pl.paths) {
2253 lines.add (SvgParser.get_lines (p));
2254 }
2255 */
2256
2257 foreach (Path p in lines.paths) {
2258 if (p.points.size > 1 && p != path
2259 && path.boundaries_intersecting (p)) {
2260
2261 inside = false;
2262 foreach (EditPoint ep in path.points) {
2263 if (SvgParser.is_inside (ep, p)) {
2264 inside = true;
2265 }
2266 }
2267
2268 if (inside) {
2269 inside_count++;
2270 }
2271 }
2272 }
2273
2274 return inside_count;
2275 }
2276
2277 public bool boundaries_intersecting (Path p) {
2278 return in_boundaries (p.xmin, p.xmax, p.ymin, p.ymax);
2279 }
2280
2281 public bool in_boundaries (double other_xmin, double other_xmax, double other_ymin, double other_ymax) {
2282 return ((xmin <= other_xmin <= xmax) || (xmin <= other_xmax <= xmax)
2283 || (other_xmin <= xmin <= other_xmax) || (other_xmin <= xmax <= other_xmax))
2284 && ((ymin <= other_ymin <= ymax) || (ymin <= other_ymax <= ymax)
2285 || (other_ymin <= ymin <= other_ymax) || (other_ymin <= ymax <= other_ymax));
2286 }
2287
2288 /** @param t smallest distance to other points. */
2289 public void remove_points_on_points (double t = 0.00001) {
2290 Gee.ArrayList<EditPoint> remove = new Gee.ArrayList<EditPoint> ();
2291 EditPoint n;
2292 EditPointHandle hr, h;
2293 double t3 = t / 3;
2294
2295 if (points.size == 0) {
2296 return;
2297 }
2298
2299 for (int i = 0; i < points.size + 1; i++) {
2300 EditPoint ep = points.get (i % points.size);
2301 if ((ep.flags & EditPoint.STROKE_OFFSET) > 0
2302 && ep.get_right_handle ().length < t3
2303 && ep.get_left_handle ().length < t3
2304 && !is_endpoint (ep)
2305 && (ep.flags & EditPoint.CURVE_KEEP) == 0
2306 && (ep.flags & EditPoint.SEGMENT_END) == 0) {
2307 ep.deleted = true;
2308 }
2309 }
2310
2311 remove_deleted_points ();
2312
2313 for (int i = 0; i < points.size + 1; i++) {
2314 EditPoint ep = points.get (i % points.size);
2315 n = points.get ((i + 1) % points.size);
2316
2317 if (Path.distance_to_point (n, ep) < t) {
2318 remove.add (ep);
2319 }
2320 }
2321
2322 create_list ();
2323
2324 foreach (EditPoint r in remove) {
2325 if (points.size == 0) {
2326 return;
2327 }
2328
2329 if (r.next != null) {
2330 n = r.get_next ();
2331 } else {
2332 n = points.get (0);
2333 }
2334
2335 points.remove (r);
2336 h = n.get_left_handle ();
2337 hr = r.get_left_handle ();
2338 h.length = hr.length;
2339 h.angle = hr.angle;
2340 h.type = hr.type;
2341
2342 if (h.length < t) {
2343 h.length = t;
2344 h.angle = n.get_right_handle ().angle - PI;
2345 }
2346
2347 create_list ();
2348 }
2349
2350 recalculate_linear_handles ();
2351 }
2352
2353 public bool is_endpoint (EditPoint ep) {
2354 if (points.size == 0) {
2355 return false;
2356 }
2357
2358 return ep == points.get (0) || ep == points.get (points.size - 1);
2359 }
2360
2361 public void remove_deleted_points () {
2362 Gee.ArrayList<EditPoint> p = new Gee.ArrayList<EditPoint> ();
2363
2364 foreach (EditPoint ep in points) {
2365 if (ep.deleted) {
2366 p.add (ep);
2367 }
2368 }
2369
2370 foreach (EditPoint e in p) {
2371 points.remove (e);
2372 }
2373
2374 create_list ();
2375 }
2376
2377 public static void find_closes_point_in_segment (EditPoint ep0, EditPoint ep1,
2378 double px, double py,
2379 out double nx, out double ny,
2380 double max_step = 200) {
2381
2382 double min_distance = double.MAX;
2383 double npx, npy;
2384 double min_t, max_t;
2385 double rmin_t, rmax_t;
2386 bool found;
2387 int step;
2388
2389 npx = 0;
2390 npy = 0;
2391
2392 min_t = 0;
2393 max_t = 1;
2394
2395 rmin_t = 0;
2396 rmax_t = 1;
2397
2398 for (step = 3; step <= max_step; step *= 2) {
2399 found = false;
2400 min_distance = double.MAX;
2401 Path.all_of (ep0, ep1, (xa, ya, ta) => {
2402 double d = Path.distance (px, xa, py, ya);
2403
2404 if (d < min_distance) {
2405 min_distance = d;
2406 npx = xa;
2407 npy = ya;
2408 rmin_t = ta - 1.0 / step;
2409 rmax_t = ta + 1.0 / step;
2410 found = true;
2411 }
2412
2413 return true;
2414 }, step, min_t, max_t);
2415
2416 if (!found) {
2417 rmin_t = 1 - (1.0 / step);
2418 rmax_t = 1;
2419 }
2420
2421 min_t = (rmin_t > 0) ? rmin_t : 0;
2422 max_t = (rmax_t < 1) ? rmax_t : 1;
2423 }
2424
2425 nx = npx;
2426 ny = npy;
2427 }
2428
2429 public void reset_stroke () {
2430 full_stroke = null;
2431 fast_stroke = null;
2432 }
2433
2434 public void create_full_stroke () {
2435 if (stroke <= 0) {
2436 return;
2437 }
2438
2439 StrokeTask task = new StrokeTask (this);
2440
2441 // Create idle task in order ignore repeted calls to this method
2442 // during one main loop iteration.
2443 IdleSource idle = new IdleSource ();
2444 idle.set_callback (() => {
2445 MainWindow.native_window.run_non_blocking_background_thread (task);
2446 return false;
2447 });
2448 idle.attach (null);
2449
2450 stop_stroke_creator ();
2451 stroke_creator = task;
2452 }
2453
2454 public void stop_stroke_creator () {
2455 if (stroke_creator != null) {
2456 ((!) stroke_creator).cancel ();
2457 }
2458 }
2459
2460 public PathList get_completed_stroke () {
2461 if (full_stroke == null) {
2462 StrokeTool s = new StrokeTool ();
2463 full_stroke = s.get_stroke (this, stroke);
2464 }
2465
2466 return (!) full_stroke;
2467 }
2468
2469 public PathList get_stroke_fast () {
2470 if (full_stroke != null) {
2471 return (!) full_stroke;
2472 }
2473
2474 if (fast_stroke != null) {
2475 return (!) fast_stroke;
2476 }
2477
2478 StrokeTool s = new StrokeTool ();
2479 fast_stroke = s.get_stroke_fast (this, stroke);
2480
2481 return (!) fast_stroke;
2482 }
2483
2484 // Callback for path simplifier
2485 public void add_cubic_bezier_points (double x0, double y0, double x1, double y1,
2486 double x2, double y2, double x3, double y3) {
2487
2488 EditPoint start;
2489 EditPoint end;
2490
2491 if (points.size > 0) {
2492 start = get_last_point ();
2493 } else {
2494 start = add (x0, y0);
2495 }
2496
2497 end = add (x3, y3);
2498
2499 start.set_point_type (PointType.CUBIC);
2500 end.set_point_type (PointType.CUBIC);
2501
2502 start.convert_to_curve ();
2503 end.convert_to_curve ();
2504
2505 start.get_right_handle ().move_to_coordinate (x1, y1);
2506 end.get_left_handle ().move_to_coordinate (x2, y2);
2507 }
2508
2509 public void recalculate_linear_handles () {
2510 foreach (EditPoint e in points) {
2511 recalculate_linear_handles_for_point (e);
2512 }
2513 }
2514
2515 /** Set bezier points for linear paths. */
2516 public void recalculate_linear_handles_for_point (EditPoint ep) {
2517 EditPointHandle h;
2518 EditPoint n;
2519 double nx, ny;
2520
2521 return_if_fail (!is_null (ep.right_handle) && !is_null (ep.left_handle));
2522
2523 // left handle
2524 if (ep.prev != null) {
2525 n = ep.get_prev ();
2526 h = ep.get_left_handle ();
2527 } else {
2528 n = get_last_point ();
2529 h = ep.get_left_handle ();
2530 }
2531
2532 return_if_fail (!is_null (n) && !is_null (h));
2533
2534 if (h.type == PointType.LINE_CUBIC) {
2535 nx = ep.x + ((n.x - ep.x) / 3);
2536 ny = ep.y + ((n.y - ep.y) / 3);
2537 h.move_to_coordinate (nx, ny);
2538 }
2539
2540 if (h.type == PointType.LINE_DOUBLE_CURVE) {
2541 nx = ep.x + ((n.x - ep.x) / 4);
2542 ny = ep.y + ((n.y - ep.y) / 4);
2543 h.move_to_coordinate (nx, ny);
2544 }
2545
2546 if (h.type == PointType.LINE_QUADRATIC) {
2547 nx = ep.x + ((n.x - ep.x) / 2);
2548 ny = ep.y + ((n.y - ep.y) / 2);
2549 h.move_to_coordinate (nx, ny);
2550 }
2551
2552 // the other side
2553 h = n.get_right_handle ();
2554 return_if_fail (!is_null (h) && !is_null (h));
2555
2556 if (h.type == PointType.LINE_DOUBLE_CURVE) {
2557 nx = n.x + ((ep.x - n.x) / 4);
2558 ny = n.y + ((ep.y - n.y) / 4);
2559 h.move_to_coordinate (nx, ny);
2560 }
2561
2562 if (h.type == PointType.LINE_CUBIC) {
2563 nx = n.x + ((ep.x - n.x) / 3);
2564 ny = n.y + ((ep.y - n.y) / 3);
2565 h.move_to_coordinate (nx, ny);
2566 }
2567
2568 if (h.type == PointType.LINE_QUADRATIC) {
2569 nx = n.x + ((ep.x - n.x) / 2);
2570 ny = n.y + ((ep.y - n.y) / 2);
2571 h.move_to_coordinate (nx, ny);
2572 }
2573
2574 // right handle
2575 if (ep.next != null) {
2576 n = ep.get_next ();
2577 h = ep.get_right_handle ();
2578 } else {
2579 n = get_first_point ();
2580 h = ep.get_right_handle ();
2581 }
2582
2583 return_if_fail (!is_null (n) && !is_null (h));
2584
2585 if (h.type == PointType.LINE_CUBIC) {
2586 nx = ep.x + ((n.x - ep.x) / 3);
2587 ny = ep.y + ((n.y - ep.y) / 3);
2588
2589 h.move_to_coordinate (nx, ny);
2590 }
2591
2592 if (h.type == PointType.LINE_DOUBLE_CURVE) {
2593 nx = ep.x + ((n.x - ep.x) / 4);
2594 ny = ep.y + ((n.y - ep.y) / 4);
2595
2596 h.move_to_coordinate (nx, ny);
2597 }
2598
2599 if (h.type == PointType.LINE_QUADRATIC) {
2600 nx = ep.x + ((n.x - ep.x) / 2);
2601 ny = ep.y + ((n.y - ep.y) / 2);
2602
2603 h.move_to_coordinate (nx, ny);
2604 }
2605
2606 h = n.get_left_handle ();
2607 return_if_fail (!is_null (h));
2608
2609 if (h.type == PointType.LINE_CUBIC) {
2610 nx = n.x + ((ep.x - n.x) / 3);
2611 ny = n.y + ((ep.y - n.y) / 3);
2612
2613 h.move_to_coordinate (nx, ny);
2614 }
2615
2616 if (h.type == PointType.LINE_DOUBLE_CURVE) {
2617 nx = n.x + ((ep.x - n.x) / 4);
2618 ny = n.y + ((ep.y - n.y) / 4);
2619
2620 h.move_to_coordinate (nx, ny);
2621 }
2622
2623 if (h.type == PointType.LINE_QUADRATIC) {
2624 nx = n.x + ((ep.x - n.x) / 2);
2625 ny = n.y + ((ep.y - n.y) / 2);
2626
2627 h.move_to_coordinate (nx, ny);
2628 }
2629 }
2630
2631 public Path self_interpolate (double weight, bool counter) {
2632 Path master;
2633 Path p = new Path ();
2634
2635 if (stroke > 0) {
2636 p = copy ();
2637 p.stroke += 5 * weight * 2;
2638
2639 if (p.stroke < 0.002) {
2640 p.stroke = 0.002;
2641 }
2642 } else {
2643 remove_points_on_points ();
2644 master = get_self_interpolated_master (counter, weight);
2645 p = interpolate_estimated_path (master, weight);
2646 recalculate_linear_handles ();
2647 }
2648
2649 return p;
2650 }
2651
2652 public Path interpolate_estimated_path (Path master, double weight) {
2653 Path p = copy ();
2654 EditPoint ep, master_point;
2655 double x, y;
2656 double direction = weight;
2657
2658 weight = fabs(weight);
2659
2660 if (p.points.size <= 1 || master.points.size <= 1) {
2661 return p;
2662 }
2663
2664 master_point = new EditPoint ();
2665
2666 for (int i = 0; i < p.points.size; i++) {
2667 ep = p.points.get (i);
2668
2669 double right_angle = ep.get_right_handle ().angle;
2670 double left_angle = ep.get_left_handle ().angle;
2671 double angle = EditPointHandle.average_angle (right_angle, left_angle);
2672 angle += direction > 0 ? -PI : PI;
2673
2674 if (angle < 0) {
2675 angle += 2 * PI;
2676 }
2677
2678 angle %= 2 * PI;
2679
2680 double close_x, close_y;
2681 double min_distance = Glyph.CANVAS_MAX;
2682 double distance;
2683 double distance_to_edge = (5 / 2.0) * weight;
2684
2685 close_x = Glyph.CANVAS_MAX;
2686 close_y = Glyph.CANVAS_MAX;
2687
2688 master_point = new EditPoint ();
2689 while (Path.distance (close_x, master_point.x, close_y, master_point.y) > 0.1) {
2690 x = ep.x + distance_to_edge * cos (angle);
2691 y = ep.y + distance_to_edge * sin (angle);
2692
2693 master_point = new EditPoint ();
2694 master.get_closest_point_on_path (master_point, x, y);
2695 master_point.color = Color.red ();
2696 //master.insert_new_point_on_path (master_point);
2697 master_point.convert_to_curve ();
2698 master_point.get_right_handle().angle = angle;
2699
2700 distance_to_edge += 0.1;
2701
2702 distance = Path.distance (x, master_point.x, y, master_point.y);
2703 if (distance < min_distance) {
2704 min_distance = distance;
2705 close_x = x;
2706 close_y = y;
2707 }
2708
2709 if (distance_to_edge > 5) {
2710 break;
2711 }
2712
2713 }
2714 master_point.color = Color.blue ();
2715
2716 x = close_x;
2717 y = close_x;
2718
2719 ep.x += (close_x - ep.x) * direction;
2720 ep.y += (close_y - ep.y) * direction;
2721 }
2722
2723 p.adjust_interpolated_handles (master, fabs ((5 / 2.0) * weight));
2724
2725 return p;
2726 }
2727
2728 public Path get_self_interpolated_master (bool counter, double weight) {
2729 return StrokeTool.change_weight (this, counter, weight);
2730 }
2731
2732 void adjust_interpolated_handles (Path master, double edge) {
2733 EditPoint ep, next;
2734
2735 for (int i = 0; i < points.size; i++) {
2736 ep = points.get (i);
2737 next = points.get (i % points.size);
2738 adjust_interpolated_handle (master, ep, next, edge);
2739 }
2740 }
2741
2742 void adjust_interpolated_handle (Path master,
2743 EditPoint ep, EditPoint next,
2744 double edge) {
2745
2746 double x, y;
2747 double next_length_adjustment = 0;
2748 double prev_length_adjustment_reverse = 0;
2749
2750 double min_distortion = double.MAX;
2751
2752 EditPoint master_point = new EditPoint ();
2753
2754 get_point_for_step (ep, next, 0.55, out x, out y);
2755 master.get_closest_point_on_path (master_point, x, y);
2756
2757 double tolerance = 0.01;
2758 double start_length = ep.get_right_handle ().length;
2759 double stop_length = next.get_left_handle ().length;
2760
2761 EditPoint ep1, ep2;
2762
2763 ep1 = ep.copy ();
2764 ep2 = next.copy ();
2765
2766 double total_distance = Path.distance (ep1.x, ep2.x, ep1.y, ep2.y);
2767
2768 for (double m = 50.0; m >= tolerance / 2.0; m /= 10.0) {
2769 double step = m / 10.0;
2770 min_distortion = double.MAX;
2771
2772 double first = (m == 50.0) ? 0 : -m;
2773
2774 for (double a = first; a < m; a += step) {
2775 for (double b = first; b < m; b += step) {
2776
2777 if (start_length + a + stop_length + b > total_distance) {
2778 break;
2779 }
2780
2781 ep1.get_right_handle ().length = start_length + a;
2782 ep2.get_left_handle ().length = stop_length + b;
2783
2784 get_point_for_step (ep1, ep2, 0.55, out x, out y);
2785 double error = distance (master_point.x, x, master_point.y, y);
2786 error = fabs(error - edge);
2787
2788 if (error < min_distortion
2789 && start_length + a > 0
2790 && stop_length + b > 0) {
2791 min_distortion = error;
2792 prev_length_adjustment_reverse = a;
2793 next_length_adjustment = b;
2794 }
2795 }
2796 }
2797
2798 start_length += prev_length_adjustment_reverse;
2799 stop_length += next_length_adjustment;
2800 }
2801
2802 ep.get_right_handle ().length = start_length;
2803
2804 if (ep.get_right_handle ().type != PointType.QUADRATIC) {
2805 next.get_left_handle ().length = stop_length;
2806 } else {
2807 next.get_left_handle ().move_to_coordinate (
2808 ep.get_right_handle ().x, ep.get_right_handle ().y);
2809 }
2810 }
2811 }
2812
2813 }
2814