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