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