.
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 Math;
16 using Cairo;
17
18 namespace BirdFont {
19
20 /** Create new points. */
21 public class PenTool : Tool {
22
23 private static double contact_surface {
24 get {
25 return MainWindow.units * 20;
26 }
27 }
28
29 public static bool move_selected = false;
30 public static bool move_selected_handle = false;
31
32 public static bool move_point_on_path = false;
33
34 public static bool edit_active_corner = false;
35
36 public static bool move_point_independent_of_handle = false;
37
38 public static Gee.ArrayList<PointSelection> selected_points;
39
40 public static EditPointHandle active_handle;
41 public static EditPointHandle selected_handle;
42 public static PointSelection handle_selection;
43
44 public static EditPoint? active_edit_point;
45 public static Path active_path;
46
47 public static EditPoint selected_point;
48 public static Path selected_path;
49
50 public static double last_point_x = 0;
51 public static double last_point_y = 0;
52
53 public static bool show_selection_box = false;
54 private static double selection_box_x = 0;
55 private static double selection_box_y = 0;
56 private static double selection_box_last_x = 0;
57 private static double selection_box_last_y = 0;
58
59 private static bool point_selection_image = false;
60
61 public static double precision = 1;
62
63 // The pixel where the user pressed the mouse button
64 public static int begin_action_x = 0;
65 public static int begin_action_y = 0;
66
67 /* First move action must move the current point in to the grid. */
68 bool first_move_action = false;
69
70 /** Move curve handle instead of control point. */
71 private bool last_selected_is_handle = false;
72
73 static Gee.ArrayList<Path> clockwise;
74 static Gee.ArrayList<Path> counter_clockwise;
75
76 public static double path_stroke_width = 0;
77 public static double simplification_threshold = 0.5;
78
79 public static bool retain_angle = false;
80
81 public PenTool (string name) {
82 base (name, t_("Add new points"));
83
84 selected_points = new Gee.ArrayList<PointSelection> ();
85
86 active_handle = new EditPointHandle.empty ();
87 selected_handle = new EditPointHandle.empty ();
88 handle_selection = new PointSelection.empty ();
89
90 active_edit_point = new EditPoint ();
91 active_path = new Path ();
92 selected_path = new Path ();
93
94 selected_point = new EditPoint ();
95 clockwise = new Gee.ArrayList<Path> ();
96 counter_clockwise = new Gee.ArrayList<Path> ();
97
98 select_action.connect ((self) => {
99 MainWindow.get_current_glyph ().clear_active_paths ();
100 });
101
102 deselect_action.connect ((self) => {
103 MainWindow.get_current_glyph ().clear_active_paths ();
104 });
105
106 press_action.connect ((self, b, x, y) => {
107 // retain path direction
108 clockwise = new Gee.ArrayList<Path> ();
109 counter_clockwise = new Gee.ArrayList<Path> ();
110
111 begin_action_x = x;
112 begin_action_y = y;
113
114 update_orientation ();
115
116 first_move_action = true;
117
118 last_point_x = Glyph.path_coordinate_x (x);
119 last_point_y = Glyph.path_coordinate_y (y);
120
121 move_action (this, x, y);
122
123 press (b, x, y, false);
124
125 if (BirdFont.android) {
126 point_selection_image = true;
127 }
128
129 selection_box_x = x;
130 selection_box_y = y;
131
132 last_point_x = Glyph.path_coordinate_x (x);
133 last_point_y = Glyph.path_coordinate_y (y);
134
135 BirdFont.get_current_font ().touch ();
136
137 // move new points on to grid
138 if (b == 1 && (GridTool.has_ttf_grid () || GridTool.is_visible ())) {
139 move (x, y);
140 }
141
142 reset_stroke ();
143 });
144
145 double_click_action.connect ((self, b, x, y) => {
146 last_point_x = Glyph.path_coordinate_x (x);
147 last_point_y = Glyph.path_coordinate_y (y);
148
149 press (b, x, y, true);
150 });
151
152 release_action.connect ((self, b, ix, iy) => {
153 double x, y;
154 Glyph g;
155
156 g = MainWindow.get_current_glyph ();
157 x = Glyph.path_coordinate_x (ix);
158 y = Glyph.path_coordinate_y (iy);
159
160 if (has_join_icon () && active_edit_point != null) {
161 join_paths ((!) active_edit_point);
162 }
163
164 active_handle = new EditPointHandle.empty ();
165
166 if (show_selection_box) {
167 select_points_in_box ();
168 }
169
170 move_selected = false;
171 move_selected_handle = false;
172 edit_active_corner = false;
173 show_selection_box = false;
174
175 set_orientation ();
176
177 MainWindow.set_cursor (NativeWindow.VISIBLE);
178
179 point_selection_image = false;
180 BirdFont.get_current_font ().touch ();
181 reset_stroke ();
182
183 foreach (Path p in g.path_list) {
184 if (p.is_open () && p.points.size > 0) {
185 p.get_first_point ().set_tie_handle (false);
186 p.get_first_point ().set_reflective_handles (false);
187 p.get_last_point ().set_tie_handle (false);
188 p.get_last_point ().set_reflective_handles (false);
189 }
190
191 p.create_full_stroke (); // cache good stroke
192 }
193 });
194
195 move_action.connect ((self, x, y) => {
196 selection_box_last_x = x;
197 selection_box_last_y = y;
198
199 if (Path.distance (begin_action_x, x, begin_action_y, y) > 10 * MainWindow.units) {
200 point_selection_image = false;
201 }
202
203 move (x, y);
204 });
205
206 key_press_action.connect ((self, keyval) => {
207 reset_stroke ();
208
209 if (keyval == Key.DEL || keyval == Key.BACK_SPACE) {
210 if (KeyBindings.has_shift ()) {
211 delete_selected_points ();
212 } else {
213 delete_simplify ();
214 }
215 }
216
217 if (is_arrow_key (keyval)) {
218 if (KeyBindings.modifier != CTRL) {
219 move_selected_points (keyval);
220 active_edit_point = selected_point;
221 } else {
222 move_select_next_point (keyval);
223 }
224 }
225
226 if (KeyBindings.has_shift ()) {
227 if (selected_point.tie_handles && KeyBindings.modifier == SHIFT) {
228 last_point_x = selected_point.x;
229 last_point_y = selected_point.y;
230 }
231 }
232
233 GlyphCanvas.redraw ();
234 BirdFont.get_current_font ().touch ();
235 });
236
237 key_release_action.connect ((self, keyval) => {
238 double x, y;
239 if (is_arrow_key (keyval)) {
240 if (KeyBindings.modifier != CTRL && active_edit_point != null) {
241 x = Glyph.reverse_path_coordinate_x (selected_point.x);
242 y = Glyph.reverse_path_coordinate_y (selected_point.y);
243 join_paths ((!) active_edit_point);
244 reset_stroke ();
245 }
246 }
247 });
248
249 draw_action.connect ((tool, cairo_context, glyph) => {
250 draw_on_canvas (cairo_context, glyph);
251 });
252 }
253
254 public static void update_orientation () {
255 Glyph glyph = MainWindow.get_current_glyph ();
256
257 clockwise.clear ();
258 counter_clockwise.clear ();
259 foreach (Path p in glyph.path_list) {
260 if (p.is_clockwise ()) {
261 clockwise.add (p);
262 } else {
263 counter_clockwise.add (p);
264 }
265 }
266 }
267
268 public static void set_orientation () {
269 // update path direction if it has changed
270 foreach (Path p in clockwise) {
271 if (!p.is_open () && !p.is_clockwise ()) {
272 p.reverse ();
273 update_selection ();
274 }
275 }
276
277 foreach (Path p in counter_clockwise) {
278 if (!p.is_open () && p.is_clockwise ()) {
279 p.reverse ();
280 update_selection ();
281 }
282 }
283 }
284
285 public bool has_join_icon () {
286 if (active_edit_point != null) {
287 return can_join ((!) active_edit_point);
288 }
289
290 return false;
291 }
292
293 public static bool can_join (EditPoint ep) {
294 double mx, my;
295 get_tie_position (ep, out mx, out my);
296 return (mx > -10 * MainWindow.units && my > -10 * MainWindow.units);
297 }
298
299 public static void select_points_in_box () {
300 double x1, y1, x2, y2;
301 Glyph g;
302
303 g = MainWindow.get_current_glyph ();
304
305 x1 = Glyph.path_coordinate_x (fmin (selection_box_x, selection_box_last_x));
306 y1 = Glyph.path_coordinate_y (fmin (selection_box_y, selection_box_last_y));
307 x2 = Glyph.path_coordinate_x (fmax (selection_box_x, selection_box_last_x));
308 y2 = Glyph.path_coordinate_y (fmax (selection_box_y, selection_box_last_y));
309
310 remove_all_selected_points ();
311
312 foreach (Path p in g.path_list) {
313 // TODO: Select path only of bounding box is in selection box
314 foreach (EditPoint ep in p.points) {
315 if (x1 <= ep.x <= x2 && y2 <= ep.y <= y1) {
316 add_selected_point (ep, p);
317 ep.set_selected (true);
318 }
319 }
320 }
321 }
322
323 public static void delete_selected_points () {
324 Glyph g = MainWindow.get_current_glyph ();
325
326 foreach (PointSelection p in selected_points) {
327 p.point.deleted = true;
328 }
329
330 process_deleted ();
331
332 foreach (Path p in g.path_list) {
333 if (p.has_deleted_point ()) {
334 process_deleted ();
335 }
336 }
337
338 g.update_view ();
339
340 selected_points.clear ();
341 selected_handle.selected = false;
342
343 active_handle = new EditPointHandle.empty ();
344 selected_handle = new EditPointHandle.empty ();
345
346 active_edit_point = null;
347 selected_point = new EditPoint ();
348 }
349
350 static void get_closes_point_in_segment (EditPoint ep0, EditPoint ep1, EditPoint ep2,
351 double px, double py,
352 out double nx, out double ny) {
353 double npx0, npy0;
354 double npx1, npy1;
355
356 Path.find_closes_point_in_segment (ep0, ep1, px, py, out npx0, out npy0, 50);
357 Path.find_closes_point_in_segment (ep1, ep2, px, py, out npx1, out npy1, 50);
358
359 if (Path.distance (px, npx0, py, npy0) < Path.distance (px, npx1, py, npy1)) {
360 nx = npx0;
361 ny = npy0;
362 } else {
363 nx = npx1;
364 ny = npy1;
365 }
366 }
367
368 public static void get_path_distortion (EditPoint oe0, EditPoint oe1, EditPoint oe2,
369 EditPoint ep1, EditPoint ep2,
370 out double distortion_first, out double distortion_next) {
371 double nx, ny;
372 double df, dn;
373 int step;
374
375 df = 0;
376 dn = 0;
377 nx = 0;
378 ny = 0;
379
380 step = 4;
381
382 Path.all_of (ep1, ep2, (xa, ya, ta) => {
383 double f, n;
384
385 get_closes_point_in_segment (oe0, oe1, oe2, xa, ya, out nx, out ny);
386
387 if (ta < 0.5) {
388 f = Path.distance (nx, xa, ny, ya);
389 if (f > df) {
390 df += f;
391 }
392 } else {
393 n = Path.distance (nx, xa, ny, ya);
394 if (n > dn) {
395 dn += n;
396 }
397 }
398
399 return true;
400 }, step);
401
402 distortion_first = df;
403 distortion_next = dn;
404 }
405
406 public static void delete_simplify () {
407 Glyph g = MainWindow.get_current_glyph ();
408
409 foreach (PointSelection p in selected_points) {
410 remove_point_simplify (p);
411 }
412
413 g.update_view ();
414
415 selected_points.clear ();
416 selected_handle.selected = false;
417
418 active_handle = new EditPointHandle.empty ();
419 selected_handle = new EditPointHandle.empty ();
420
421 active_edit_point = null;
422 selected_point = new EditPoint ();
423 }
424
425 /** @return path distortion. */
426 public static double remove_point_simplify (PointSelection p, double tolerance = 0.6) {
427 double e;
428
429 e = remove_point_simplify_path (p, tolerance, Glyph.CANVAS_MAX);
430 p.path.update_region_boundaries ();
431
432 return e;
433 }
434
435 /** @return path distortion. */
436 public static double remove_point_simplify_path (PointSelection p,
437 double tolerance = 0.6, double keep_tolerance = 10000) {
438
439 double start_length, stop_length;
440 double start_distortion, start_min_distortion, start_previous_length;
441 double stop_distortion, stop_min_distortion, stop_previous_length;
442 double distortion, min_distortion;
443 double prev_length_adjustment, next_length_adjustment;
444 double prev_length_adjustment_reverse, next_length_adjustment_reverse;
445 EditPoint ep1, ep2;
446 EditPoint next, prev;
447 double step, distance;
448
449 return_if_fail (p.path.points.size > 0);
450
451 if (p.path.points.size <= 2) {
452 p.point.deleted = true;
453 p.path.remove_deleted_points ();
454 return 0;
455 }
456
457 p.point.deleted = true;
458
459 if (p.point.next != null) {
460 next = p.point.get_next ();
461 } else {
462 next = p.path.points.get (0);
463 }
464
465 if (p.point.prev != null) {
466 prev = p.point.get_prev ();
467 } else {
468 prev = p.path.points.get (p.path.points.size - 1);
469 }
470
471 prev.get_right_handle ().convert_to_curve ();
472 next.get_left_handle ().convert_to_curve ();
473
474 if (prev.get_right_handle ().type == PointType.QUADRATIC
475 && next.get_left_handle ().type != PointType.QUADRATIC) {
476 convert_point_type (prev, next.get_left_handle ().type);
477 }
478
479 if (prev.get_right_handle ().type != PointType.QUADRATIC
480 && next.get_left_handle ().type == PointType.QUADRATIC) {
481 convert_point_type (next, prev.get_right_handle ().type);
482 }
483
484 ep1 = prev.copy ();
485 ep2 = next.copy ();
486
487 start_length = ep1.get_right_handle ().length;
488 stop_length = ep2.get_left_handle ().length;
489
490 stop_previous_length = start_length;
491 start_previous_length = stop_length;
492
493 stop_min_distortion = double.MAX;
494 ep1.get_right_handle ().length = start_length;
495
496 start_min_distortion = double.MAX;
497 ep2.get_left_handle ().length = stop_length;
498
499 prev_length_adjustment = 0;
500 next_length_adjustment = 0;
501 prev_length_adjustment_reverse = 0;
502 next_length_adjustment_reverse = 0;
503
504 min_distortion = double.MAX;
505 distance = Path.distance (ep1.x, ep2.x, ep1.y, ep2.y);
506
507 for (double m = 50.0; m >= tolerance / 2.0; m /= 10.0) {
508 step = m / 10.0;
509 min_distortion = double.MAX;
510
511 double first = (m == 50.0) ? 0 : -m;
512
513 for (double a = first; a < m; a += step) {
514 for (double b = first; b < m; b += step) {
515
516 if (start_length + a + stop_length + b > distance) {
517 break;
518 }
519
520 ep1.get_right_handle ().length = start_length + a;
521 ep2.get_left_handle ().length = stop_length + b;
522
523 get_path_distortion (prev, p.point, next,
524 ep1, ep2,
525 out start_distortion, out stop_distortion);
526
527 distortion = Math.fmax (start_distortion, stop_distortion);
528
529 if (distortion < min_distortion
530 && start_length + a > 0
531 && stop_length + b > 0) {
532 min_distortion = distortion;
533 prev_length_adjustment_reverse = a;
534 next_length_adjustment = b;
535 }
536 }
537 }
538
539 start_length += prev_length_adjustment_reverse;
540 stop_length += next_length_adjustment;
541 }
542
543
544 if (min_distortion < keep_tolerance || keep_tolerance >= Glyph.CANVAS_MAX) {
545 prev.get_right_handle ().length = start_length;
546
547 if (prev.get_right_handle ().type != PointType.QUADRATIC) {
548 next.get_left_handle ().length = stop_length;
549 } else {
550 next.get_left_handle ().move_to_coordinate (
551 prev.get_right_handle ().x, prev.get_right_handle ().y);
552 }
553
554 p.point.deleted = true;
555 p.path.remove_deleted_points ();
556 }
557
558 return min_distortion;
559 }
560
561 /** @return path distortion. */
562 public static double remove_point_simplify_path_fast (PointSelection p,
563 double tolerance = 0.6, double keep_tolerance = 10000) {
564
565 double start_length, stop_length;
566 double start_distortion, start_min_distortion, start_previous_length;
567 double stop_distortion, stop_min_distortion, stop_previous_length;
568 double distortion, min_distortion;
569 double prev_length_adjustment, next_length_adjustment;
570 double prev_length_adjustment_reverse, next_length_adjustment_reverse;
571 EditPoint ep1, ep2;
572 EditPoint next, prev;
573 double step, distance;
574
575 return_if_fail (p.path.points.size > 0);
576
577 if (p.path.points.size <= 2) {
578 p.point.deleted = true;
579 p.path.remove_deleted_points ();
580 return 0;
581 }
582
583 p.point.deleted = true;
584
585 if (p.point.next != null) {
586 next = p.point.get_next ();
587 } else {
588 next = p.path.points.get (0);
589 }
590
591 if (p.point.prev != null) {
592 prev = p.point.get_prev ();
593 } else {
594 prev = p.path.points.get (p.path.points.size - 1);
595 }
596
597 prev.get_right_handle ().convert_to_curve ();
598 next.get_left_handle ().convert_to_curve ();
599
600 if (prev.get_right_handle ().type == PointType.QUADRATIC
601 && next.get_left_handle ().type != PointType.QUADRATIC) {
602 convert_point_type (prev, next.get_left_handle ().type);
603 }
604
605 if (prev.get_right_handle ().type != PointType.QUADRATIC
606 && next.get_left_handle ().type == PointType.QUADRATIC) {
607 convert_point_type (next, prev.get_right_handle ().type);
608 }
609
610 ep1 = prev.copy ();
611 ep2 = next.copy ();
612
613 start_length = ep1.get_right_handle ().length;
614 stop_length = ep2.get_left_handle ().length;
615
616 stop_previous_length = start_length;
617 start_previous_length = stop_length;
618
619 stop_min_distortion = double.MAX;
620 ep1.get_right_handle ().length = start_length;
621
622 start_min_distortion = double.MAX;
623 ep2.get_left_handle ().length = stop_length;
624
625 prev_length_adjustment = 0;
626 next_length_adjustment = 0;
627 prev_length_adjustment_reverse = 0;
628 next_length_adjustment_reverse = 0;
629
630 min_distortion = double.MAX;
631 distance = Path.distance (ep1.x, ep2.x, ep1.y, ep2.y);
632
633 for (double m = 50.0; m >= tolerance / 2.0; m /= 10.0) {
634 step = m / 10.0;
635 min_distortion = double.MAX;
636
637 double first = (m == 50.0) ? 0 : -m;
638
639 for (double a = first; a < m; a += step) {
640 for (double b = first; b < m; b += step) {
641
642 if (start_length + a + stop_length + b > distance) {
643 break;
644 }
645
646 ep1.get_right_handle ().length = start_length + a;
647 ep2.get_left_handle ().length = stop_length + b;
648
649 get_path_distortion (prev, p.point, next,
650 ep1, ep2,
651 out start_distortion, out stop_distortion);
652
653 distortion = Math.fmax (start_distortion, stop_distortion);
654
655 if (distortion < min_distortion
656 && start_length + a > 0
657 && stop_length + b > 0) {
658 min_distortion = distortion;
659 prev_length_adjustment_reverse = a;
660 next_length_adjustment = b;
661 }
662 }
663 }
664
665 start_length += prev_length_adjustment_reverse;
666 stop_length += next_length_adjustment;
667 }
668
669
670 if (min_distortion < keep_tolerance || keep_tolerance >= Glyph.CANVAS_MAX) {
671 prev.get_right_handle ().length = start_length;
672
673 if (prev.get_right_handle ().type != PointType.QUADRATIC) {
674 next.get_left_handle ().length = stop_length;
675 } else {
676 next.get_left_handle ().move_to_coordinate (
677 prev.get_right_handle ().x, prev.get_right_handle ().y);
678 }
679
680 p.point.deleted = true;
681 p.path.remove_deleted_points ();
682 }
683
684 return min_distortion;
685 }
686
687 /** Retain selected points even if path is copied after running reverse. */
688 public static void update_selection () {
689 Glyph g = MainWindow.get_current_glyph ();
690
691 selected_points.clear ();
692
693 foreach (Path p in g.path_list) {
694 foreach (EditPoint e in p.points) {
695 if (e.is_selected ()) {
696 selected_points.add (new PointSelection (e, p));
697 }
698 }
699 }
700 }
701
702 static void process_deleted () {
703 Glyph g = MainWindow.get_current_glyph ();
704 while (g.process_deleted ());
705 }
706
707 public static void close_all_paths () {
708 Glyph g = MainWindow.get_current_glyph ();
709 foreach (Path p in g.path_list) {
710 if (p.stroke == 0) {
711 p.close ();
712 }
713 }
714 g.close_path ();
715 GlyphCanvas.redraw ();
716 }
717
718 public void set_precision (double p) {
719 precision = p;
720 SettingsDisplay.precision.set_value_round (p, false, false);
721 }
722
723 public static void reset_stroke () {
724 Glyph g = MainWindow.get_current_glyph ();
725
726 foreach (PointSelection p in selected_points) {
727 p.path.reset_stroke ();
728 }
729
730 foreach (Path path in g.active_paths) {
731 path.reset_stroke ();
732 }
733 }
734
735 public void move (int x, int y) {
736 double coordinate_x, coordinate_y;
737 double delta_coordinate_x, delta_coordinate_y;
738 double angle = 0;
739 bool tied;
740 Glyph g;
741
742 g = MainWindow.get_current_glyph ();
743
744 control_point_event (x, y);
745 curve_active_corner_event (x, y);
746 set_default_handle_positions ();
747
748 if (move_selected_handle && move_selected) {
749 warning ("move_selected_handle && move_selected");
750 move_selected = false;
751 move_selected_handle = false;
752 }
753
754 if (move_selected_handle || move_selected) {
755 MainWindow.set_cursor (NativeWindow.HIDDEN);
756 reset_stroke ();
757 } else {
758 MainWindow.set_cursor (NativeWindow.VISIBLE);
759 }
760
761 // move control point handles
762 if (move_selected_handle) {
763 set_type_for_moving_handle ();
764
765 // don't update angle if the user is holding down shift
766 if (KeyBindings.modifier == SHIFT || PenTool.retain_angle) {
767 angle = selected_handle.angle;
768 }
769
770 if (GridTool.is_visible ()) {
771 coordinate_x = Glyph.path_coordinate_x (x);
772 coordinate_y = Glyph.path_coordinate_y (y);
773 GridTool.tie_coordinate (ref coordinate_x, ref coordinate_y);
774 delta_coordinate_x = coordinate_x - last_point_x;
775 delta_coordinate_y = coordinate_y - last_point_y;
776 selected_handle.move_to_coordinate (selected_handle.x + delta_coordinate_x, selected_handle.y + delta_coordinate_y);
777 } else if (GridTool.has_ttf_grid ()) {
778 coordinate_x = Glyph.path_coordinate_x (x);
779 coordinate_y = Glyph.path_coordinate_y (y);
780 GridTool.ttf_grid_coordinate (ref coordinate_x, ref coordinate_y);
781 delta_coordinate_x = coordinate_x - last_point_x;
782 delta_coordinate_y = coordinate_y - last_point_y;
783 selected_handle.move_delta_coordinate (delta_coordinate_x, delta_coordinate_y);
784 } else {
785 coordinate_x = Glyph.path_coordinate_x (x);
786 coordinate_y = Glyph.path_coordinate_y (y);
787 delta_coordinate_x = coordinate_x - last_point_x;
788 delta_coordinate_y = coordinate_y - last_point_y;
789 selected_handle.move_delta_coordinate (delta_coordinate_x, delta_coordinate_y);
790 }
791
792 if (KeyBindings.modifier == SHIFT || PenTool.retain_angle) {
793 selected_handle.angle = angle;
794 selected_handle.process_connected_handle ();
795
796 if (selected_handle.parent.tie_handles) {
797 if (selected_handle.is_left_handle ()) {
798 selected_handle.parent.get_right_handle ().angle = angle - PI;
799 } else {
800 selected_handle.parent.get_left_handle ().angle = angle + PI;
801 }
802 }
803 }
804
805 handle_selection.path.update_region_boundaries ();
806
807 // FIXME: redraw line only
808 GlyphCanvas.redraw ();
809
810 if (GridTool.is_visible ()) {
811 last_point_x = selected_handle.x;
812 last_point_y = selected_handle.y;
813 } else if (GridTool.has_ttf_grid ()) {
814 last_point_x = selected_handle.x;
815 last_point_y = selected_handle.y;
816 } else {
817 last_point_x = Glyph.path_coordinate_x (x);
818 last_point_y = Glyph.path_coordinate_y (y);
819 }
820 }
821
822 // move edit point
823 if (move_selected) {
824
825 if (GridTool.is_visible ()) {
826 coordinate_x = Glyph.path_coordinate_x (x);
827 coordinate_y = Glyph.path_coordinate_y (y);
828
829 if (selected_point.tie_handles && KeyBindings.modifier == SHIFT) {
830
831 if (first_move_action) {
832 last_point_x = selected_point.x;
833 last_point_y = selected_point.y;
834 }
835
836 move_point_on_handles (coordinate_x, coordinate_y, out coordinate_x, out coordinate_y);
837 } else {
838 GridTool.tie_coordinate (ref coordinate_x, ref coordinate_y);
839 }
840
841 delta_coordinate_x = coordinate_x - last_point_x;
842 delta_coordinate_y = coordinate_y - last_point_y;
843
844 foreach (PointSelection selected in selected_points) {
845 if (move_point_independent_of_handle || KeyBindings.modifier == SHIFT) {
846 selected.point.set_independet_position (selected.point.x + delta_coordinate_x,
847 selected.point.y + delta_coordinate_y);
848 } else {
849 selected.point.set_position (selected.point.x + delta_coordinate_x,
850 selected.point.y + delta_coordinate_y);
851 }
852
853 selected.path.reset_stroke ();
854 selected.point.recalculate_linear_handles ();
855 selected.path.update_region_boundaries ();
856 }
857 } else if (GridTool.has_ttf_grid ()) {
858 coordinate_x = Glyph.path_coordinate_x (x);
859 coordinate_y = Glyph.path_coordinate_y (y);
860
861 GridTool.ttf_grid_coordinate (ref coordinate_x, ref coordinate_y);
862
863 if (selected_point.tie_handles && KeyBindings.modifier == SHIFT) {
864
865 if (first_move_action) {
866 last_point_x = selected_point.x;
867 last_point_y = selected_point.y;
868 }
869
870 move_point_on_handles (coordinate_x, coordinate_y, out coordinate_x, out coordinate_y);
871 }
872
873 delta_coordinate_x = coordinate_x - last_point_x;
874 delta_coordinate_y = coordinate_y - last_point_y;
875
876 foreach (PointSelection selected in selected_points) {
877 if (move_point_independent_of_handle || KeyBindings.modifier == SHIFT) {
878 tied = selected.point.tie_handles;
879 selected.point.tie_handles = false;
880 selected.point.set_independet_position (selected.point.x + delta_coordinate_x,
881 selected.point.y + delta_coordinate_y);
882 selected.point.tie_handles = tied;
883 } else {
884 selected.point.set_position (selected.point.x + delta_coordinate_x,
885 selected.point.y + delta_coordinate_y);
886 }
887
888 selected.path.reset_stroke ();
889 selected.point.recalculate_linear_handles ();
890 selected.path.update_region_boundaries ();
891 }
892 } else {
893
894 coordinate_x = Glyph.path_coordinate_x (x);
895 coordinate_y = Glyph.path_coordinate_y (y);
896
897 if (selected_point.tie_handles && KeyBindings.modifier == SHIFT) {
898
899 if (first_move_action) {
900 last_point_x = selected_point.x;
901 last_point_y = selected_point.y;
902 }
903
904 move_point_on_handles (coordinate_x, coordinate_y, out coordinate_x, out coordinate_y);
905 delta_coordinate_x = coordinate_x - last_point_x;
906 delta_coordinate_y = coordinate_y - last_point_y;
907 } else {
908 delta_coordinate_x = coordinate_x - last_point_x;
909 delta_coordinate_y = coordinate_y - last_point_y;
910 }
911
912 foreach (PointSelection selected in selected_points) {
913
914 if (move_point_independent_of_handle || KeyBindings.modifier == SHIFT) {
915 selected.point.set_independet_position (selected.point.x + delta_coordinate_x,
916 selected.point.y + delta_coordinate_y);
917 } else {
918 selected.point.set_position (selected.point.x + delta_coordinate_x,
919 selected.point.y + delta_coordinate_y);
920 }
921
922 selected.point.recalculate_linear_handles ();
923 selected.path.reset_stroke ();
924 selected.path.update_region_boundaries ();
925 }
926 }
927
928 if (selected_point.tie_handles && KeyBindings.modifier == SHIFT) {
929 last_point_x = selected_point.x;
930 last_point_y = selected_point.y;
931 } else if (GridTool.is_visible ()) {
932 last_point_x = selected_point.x;
933 last_point_y = selected_point.y;
934 } else if (GridTool.has_ttf_grid ()) {
935 last_point_x = selected_point.x;
936 last_point_y = selected_point.y;
937 } else {
938 last_point_x = Glyph.path_coordinate_x (x);
939 last_point_y = Glyph.path_coordinate_y (y);
940 }
941
942 GlyphCanvas.redraw ();
943 }
944
945 if (show_selection_box) {
946 GlyphCanvas.redraw ();
947 }
948 }
949
950 private void move_point_on_handles (double px, double py, out double cx, out double cy) {
951 EditPoint ep;
952 ep = selected_point.copy ();
953 ep.tie_handles = false;
954 ep.reflective_point = false;
955 ep.get_right_handle ().angle += PI / 2;
956 ep.x = px;
957 ep.y = py;
958
959 Path.find_intersection_handle (ep.get_right_handle (), selected_point.get_right_handle (), out cx, out cy);
960 }
961
962 public void press (int button, int x, int y, bool double_click) {
963 Glyph? g = MainWindow.get_current_glyph ();
964 Glyph glyph = (!) g;
965 bool reflective;
966
967 return_if_fail (g != null);
968
969 if ((double_click && !BirdFont.android)
970 || Toolbox.drawing_tools.insert_point_on_path_tool.is_selected ()) {
971 glyph.insert_new_point_on_path (x, y);
972 return;
973 }
974
975 if (button == 1) {
976 add_point_event (x, y);
977 return;
978 }
979
980 if (button == 2) {
981 if (glyph.is_open ()) {
982 force_direction ();
983 glyph.close_path ();
984 } else {
985 glyph.open_path ();
986 }
987
988 glyph.clear_active_paths ();
989
990 return;
991 }
992
993 if (button == 3) {
994 selected_path = active_path;
995 move_point_event (x, y);
996
997 // alt+click on a handle ends the symmetrical editing
998 if ((KeyBindings.has_alt () || KeyBindings.has_ctrl ())
999 && is_over_handle (x, y)) {
1000
1001 // don't use set point to reflective to on open ends
1002 reflective = true;
1003 foreach (Path path in MainWindow.get_current_glyph ().active_paths) {
1004 if (path.is_open () && path.points.size > 0) {
1005 if (selected_handle.parent == path.get_first_point ()
1006 || selected_handle.parent == path.get_last_point ()) {
1007 reflective = false;
1008 }
1009 }
1010 }
1011
1012 if (reflective) {
1013 selected_handle.parent.set_reflective_handles (false);
1014 selected_handle.parent.set_tie_handle (false);
1015 GlyphCanvas.redraw ();
1016 }
1017 }
1018
1019 return;
1020 }
1021 }
1022
1023 public void add_point_event (int x, int y) {
1024 Glyph? g = MainWindow.get_current_glyph ();
1025 Glyph glyph = (!) g;
1026 PointSelection ps;
1027
1028 if (move_selected_handle) {
1029 warning ("moving handle");
1030 return;
1031 }
1032
1033 return_if_fail (g != null);
1034
1035 remove_all_selected_points ();
1036 ps = new_point_action (x, y);
1037 active_path = ps.path;
1038 glyph.store_undo_state ();
1039 }
1040
1041 public void move_point_event (int x, int y) {
1042 Glyph? g = MainWindow.get_current_glyph ();
1043 Glyph glyph = (!) g;
1044
1045 return_if_fail (g != null);
1046
1047 control_point_event (x, y);
1048 curve_corner_event (x, y);
1049
1050 if (!move_selected_handle) {
1051 select_active_point (x, y);
1052 last_selected_is_handle = false;
1053 }
1054
1055 if (!KeyBindings.has_shift ()
1056 && selected_points.size == 0
1057 && !active_handle.active) {
1058 show_selection_box = true;
1059 }
1060
1061 glyph.store_undo_state ();
1062 }
1063
1064 void set_type_for_moving_handle () {
1065 if (selected_handle.type == PointType.LINE_CUBIC) {
1066 selected_handle.set_point_type (PointType.CUBIC);
1067 }
1068
1069 if (selected_handle.type == PointType.LINE_QUADRATIC) {
1070 selected_handle.set_point_type (PointType.QUADRATIC);
1071 }
1072
1073 if (selected_handle.type == PointType.LINE_DOUBLE_CURVE) {
1074 selected_handle.set_point_type (PointType.DOUBLE_CURVE);
1075 }
1076 }
1077
1078 /** Set fill property to transparend for counter paths. */
1079 public static void force_direction () {
1080 Glyph g = MainWindow.get_current_glyph ();
1081
1082 clear_directions ();
1083
1084 foreach (Path p in g.path_list) {
1085 if (!p.has_direction ()) {
1086 if (is_counter_path (p)) {
1087 p.force_direction (Direction.COUNTER_CLOCKWISE);
1088 } else {
1089 p.force_direction (Direction.CLOCKWISE);
1090 }
1091 }
1092 }
1093
1094 update_selected_points ();
1095 }
1096
1097 public static void clear_directions () {
1098 clockwise.clear ();
1099 counter_clockwise.clear ();
1100 }
1101
1102 public static bool is_counter_path (Path path) {
1103 Glyph g = MainWindow.get_current_glyph ();
1104 PathList pl = new PathList ();
1105
1106 foreach (Path p in g.path_list) {
1107 pl.add (p);
1108 }
1109
1110 return Path.is_counter (pl, path);
1111 }
1112
1113 public void remove_from_selected (EditPoint ep)
1114 requires (selected_points.size > 0) {
1115
1116 Gee.ArrayList<PointSelection> remove = new Gee.ArrayList<PointSelection> ();
1117
1118 foreach (PointSelection e in selected_points) {
1119 if (e.point.equals (e.point)) {
1120 remove.add (e);
1121 }
1122 }
1123
1124 foreach (PointSelection e in remove) {
1125 selected_points.remove (e);
1126 }
1127 }
1128
1129 public void select_active_point (double x, double y) {
1130 Glyph glyph = MainWindow.get_current_glyph ();
1131 bool reverse;
1132
1133 control_point_event (x, y);
1134
1135 // continue adding points from the other end of the selected path
1136 reverse = false;
1137
1138 foreach (Path p in glyph.path_list) {
1139 if (p.is_open () && p.points.size >= 1
1140 && (active_edit_point == p.points.get (0)
1141 || active_edit_point == p.points.get (p.points.size - 1))) {
1142 update_selection ();
1143 reverse = true;
1144 control_point_event (x, y);
1145 break;
1146 }
1147 }
1148
1149 foreach (Path p in glyph.path_list) {
1150 if (p.is_open () && p.points.size > 1 && active_edit_point == p.points.get (0)) {
1151 p.reverse ();
1152 update_selection ();
1153 reverse = true;
1154 control_point_event (x, y);
1155 break;
1156 }
1157 }
1158
1159 if (active_edit_point == null) {
1160 if (KeyBindings.modifier != SHIFT) {
1161 remove_all_selected_points ();
1162 return;
1163 }
1164 }
1165
1166 move_selected = true;
1167 move_point_on_path = true;
1168
1169 if (active_edit_point != null) {
1170 glyph.clear_active_paths ();
1171 glyph.add_active_path (active_path);
1172 DrawingTools.update_stroke_settings ();
1173
1174 if (KeyBindings.modifier == SHIFT) {
1175 if (((!)active_edit_point).is_selected () && selected_points.size > 1) {
1176 ((!)active_edit_point).set_selected (false);
1177 remove_from_selected ((!)active_edit_point);
1178 selected_point = new EditPoint ();
1179 last_selected_is_handle = false;
1180 } else {
1181 ((!)active_edit_point).set_selected (true);
1182 selected_point = (!)active_edit_point;
1183 add_selected_point (selected_point, active_path);
1184 last_selected_is_handle = false;
1185 }
1186 } else {
1187 selected_point = (!)active_edit_point;
1188
1189 if (!((!)active_edit_point).is_selected ()) {
1190 remove_all_selected_points ();
1191 ((!)active_edit_point).set_selected (true);
1192 selected_point = (!)active_edit_point;
1193 add_selected_point (selected_point, active_path); // FIXME: double check active path
1194 last_selected_is_handle = false;
1195 }
1196
1197 // alt+click creates a point with symmetrical handles
1198 if (KeyBindings.has_alt () || KeyBindings.has_ctrl ()) {
1199 selected_point.set_reflective_handles (true);
1200 selected_point.process_symmetrical_handles ();
1201 GlyphCanvas.redraw ();
1202 }
1203 }
1204 }
1205
1206 if (reverse) {
1207 clockwise.clear ();
1208 counter_clockwise.clear ();
1209 }
1210 }
1211
1212 public static Path? find_path_to_join (EditPoint end_point) {
1213 Path? m = null;
1214 Glyph glyph = MainWindow.get_current_glyph ();
1215 EditPoint ep_last, ep_first;
1216
1217 foreach (Path path in glyph.path_list) {
1218 if (path.points.size == 0) {
1219 continue;
1220 }
1221
1222 ep_last = path.points.get (path.points.size - 1);
1223 ep_first = path.points.get (0);
1224
1225 if (end_point == ep_last) {
1226 m = path;
1227 break;
1228 }
1229
1230 if (end_point == ep_first) {
1231 m = path;
1232 break;
1233 }
1234 }
1235
1236 return m;
1237 }
1238
1239 public static Path merge_open_paths (Path path1, Path path2) {
1240 Path union, merge;
1241 EditPoint first_point;
1242
1243 union = path1.copy ();
1244 merge = path2.copy ();
1245
1246 return_val_if_fail (path1.points.size >= 1, merge);
1247 return_val_if_fail (path2.points.size >= 1, union);
1248
1249 merge.points.get (0).set_tie_handle (false);
1250 merge.points.get (0).set_reflective_handles (false);
1251
1252 merge.points.get (merge.points.size - 1).set_tie_handle (false);
1253 merge.points.get (merge.points.size - 1).set_reflective_handles (false);
1254
1255 union.points.get (union.points.size - 1).set_tie_handle (false);
1256 union.points.get (union.points.size - 1).set_reflective_handles (false);
1257
1258 union.points.get (0).set_tie_handle (false);
1259 union.points.get (0).set_reflective_handles (false);
1260
1261 first_point = merge.get_first_point ();
1262
1263 if (union.get_last_point ().get_left_handle ().is_curve ()) {
1264 first_point.get_left_handle ().convert_to_curve ();
1265 } else {
1266 first_point.get_left_handle ().convert_to_line ();
1267 }
1268
1269 first_point.get_left_handle ().move_to_coordinate_internal (
1270 union.get_last_point ().get_left_handle ().x,
1271 union.get_last_point ().get_left_handle ().y);
1272
1273 union.delete_last_point ();
1274
1275 union.append_path (merge);
1276
1277 return union;
1278 }
1279
1280 public static void close_path (Path path) {
1281 bool last_segment_is_line;
1282 bool first_segment_is_line;
1283
1284 return_if_fail (path.points.size > 1);
1285
1286 last_segment_is_line = path.get_last_point ().get_left_handle ().is_line ();
1287 first_segment_is_line = path.get_first_point ().get_right_handle ().is_line ();
1288
1289 // TODO: set point type
1290 path.points.get (0).left_handle.move_to_coordinate (
1291 path.points.get (path.points.size - 1).left_handle.x,
1292 path.points.get (path.points.size - 1).left_handle.y);
1293
1294 path.points.get (0).left_handle.type =
1295 path.points.get (path.points.size - 1).left_handle.type;
1296
1297 path.points.get (0).recalculate_linear_handles ();
1298 path.points.get (path.points.size - 1).recalculate_linear_handles ();
1299
1300 // force the connected handle to move
1301 path.points.get (0).set_position (
1302 path.points.get (0).x, path.points.get (0).y);
1303
1304 path.points.remove_at (path.points.size - 1);
1305
1306 path.close ();
1307
1308 if (last_segment_is_line) {
1309 path.get_first_point ().get_left_handle ().convert_to_line ();
1310 path.get_first_point ().recalculate_linear_handles ();
1311 }
1312
1313 if (first_segment_is_line) {
1314 path.get_first_point ().get_right_handle ().convert_to_line ();
1315 path.get_first_point ().recalculate_linear_handles ();
1316 }
1317 }
1318
1319 /** @return the new path or null if no path could be merged with the end point. */
1320 public static Path? join_paths (EditPoint end_point) {
1321 Glyph glyph = MainWindow.get_current_glyph ();
1322 Path? p;
1323 Path path;
1324 Path union;
1325 bool direction_changed = false;
1326 int px, py;
1327
1328 if (glyph.path_list.size == 0) {
1329 warning ("No paths.");
1330 return null;
1331 }
1332
1333 p = find_path_to_join (end_point);
1334 if (p == null) {
1335 warning ("No path to join.");
1336 return null;
1337 }
1338
1339 path = (!) p;
1340 if (!path.is_open ()) {
1341 warning ("Path is closed.");
1342 return null;
1343 }
1344
1345 return_if_fail (path.points.size > 0);
1346
1347 px = Glyph.reverse_path_coordinate_x (end_point.x);
1348 py = Glyph.reverse_path_coordinate_y (end_point.y);
1349
1350 if (path.points.size == 1) {
1351 glyph.delete_path (path);
1352 glyph.clear_active_paths ();
1353
1354 foreach (Path merge in glyph.path_list) {
1355 if (merge.points.size > 0) {
1356 if (is_close_to_point (merge.points.get (merge.points.size - 1), px, py)) {
1357 glyph.add_active_path (merge);
1358 active_path = merge;
1359 merge.reopen ();
1360 glyph.open_path ();
1361 return merge;
1362 }
1363
1364 if (is_close_to_point (merge.points.get (0), px, py)) {
1365 glyph.add_active_path (merge);
1366 active_path = merge;
1367 clear_directions ();
1368 merge.reopen ();
1369 glyph.open_path ();
1370 merge.reverse ();
1371 return merge;
1372 }
1373 }
1374 }
1375
1376 warning ("No point to merge.");
1377 return null;
1378 }
1379
1380 if (active_edit_point == path.points.get (0)) {
1381 path.reverse ();
1382 update_selection ();
1383 path.recalculate_linear_handles ();
1384 direction_changed = true;
1385 active_edit_point = path.points.get (path.points.size - 1);
1386 active_path = path;
1387 }
1388
1389 if (path.points.get (0) == active_edit_point) {
1390 warning ("Wrong direction.");
1391 return null;
1392 }
1393
1394 // join path with it self
1395 if (is_close_to_point (path.points.get (0), px, py)) {
1396
1397 close_path (path);
1398 glyph.close_path ();
1399 force_direction ();
1400
1401 glyph.clear_active_paths ();
1402 glyph.add_active_path (path);
1403
1404 if (direction_changed) {
1405 path.reverse ();
1406 update_selection ();
1407 }
1408
1409 remove_all_selected_points ();
1410
1411 return path;
1412 }
1413
1414 foreach (Path merge in glyph.path_list) {
1415 // don't join path with itself here
1416 if (path == merge) {
1417 continue;
1418 }
1419
1420 // we need both start and end points
1421 if (merge.points.size <= 1 || path.points.size <= 1) {
1422 continue;
1423 }
1424
1425 if (is_close_to_point (merge.points.get (merge.points.size - 1), px, py)) {
1426 merge.reverse ();
1427 update_selection ();
1428 direction_changed = !direction_changed;
1429 }
1430
1431 return_if_fail (merge.points.size > 0);
1432
1433 if (is_close_to_point (merge.points.get (0), px, py)) {
1434 if (path.points.size == 1) {
1435 warning ("path.points.size == 1\n");
1436 } else if (merge.points.size == 1) {
1437 warning ("merge.points.size == 1\n");
1438 } else {
1439 union = merge_open_paths (path, merge);
1440
1441 glyph.add_path (union);
1442 glyph.delete_path (path);
1443 glyph.delete_path (merge);
1444 glyph.clear_active_paths ();
1445 glyph.add_active_path (union);
1446
1447 union.reopen ();
1448 union.create_list ();
1449
1450 force_direction ();
1451
1452 if (direction_changed) {
1453 path.reverse ();
1454 update_selection ();
1455 }
1456
1457 union.update_region_boundaries ();
1458
1459 return union;
1460 }
1461 }
1462 }
1463
1464 if (direction_changed) {
1465 path.reverse ();
1466 update_selection ();
1467 }
1468
1469 warning ("No paths merged.");
1470 return null;
1471 }
1472
1473 /** Merge paths if ends are close. */
1474 public static bool is_close_to_point (EditPoint ep, double x, double y) {
1475 double px, py, distance;
1476
1477 px = Glyph.reverse_path_coordinate_x (ep.x);
1478 py = Glyph.reverse_path_coordinate_y (ep.y);
1479
1480 distance = sqrt (fabs (pow (px - x, 2)) + fabs (pow (py - y, 2)));
1481
1482 return (distance < 7 * MainWindow.units);
1483 }
1484
1485 /** Show the user that curves will be merged on release. */
1486 public void draw_on_canvas (Context cr, Glyph glyph) {
1487 if (show_selection_box) {
1488 draw_selection_box (cr);
1489 }
1490
1491 if (point_selection_image) {
1492 draw_point_selection_circle (cr);
1493 }
1494
1495 draw_merge_icon (cr);
1496 }
1497
1498 /** Higlight the selected point on Android. */
1499 void draw_point_selection_circle (Context cr) {
1500 PointSelection ps;
1501
1502 if (active_handle.active) {
1503 Path.draw_control_point (cr, Glyph.path_coordinate_x (begin_action_x),
1504 Glyph.path_coordinate_y (begin_action_y), Theme.get_color ("Control Point Handle"));
1505 } else if (selected_points.size > 0) {
1506 ps = selected_points.get (selected_points.size - 1);
1507
1508 if (ps.point.type == PointType.CUBIC) {
1509 Path.draw_control_point (cr, Glyph.path_coordinate_x (begin_action_x),
1510 Glyph.path_coordinate_y (begin_action_y), Theme.get_color ("Selected Cubic Control Point"));
1511 } else {
1512 Path.draw_control_point (cr, Glyph.path_coordinate_x (begin_action_x),
1513 Glyph.path_coordinate_y (begin_action_y), Theme.get_color ("Selected Quadratic Control Point"));
1514 }
1515 }
1516 }
1517
1518 void draw_selection_box (Context cr) {
1519 double x, y, w, h;
1520
1521 x = fmin (selection_box_x, selection_box_last_x);
1522 y = fmin (selection_box_y, selection_box_last_y);
1523 w = fmax (selection_box_x, selection_box_last_x) - x;
1524 h = fmax (selection_box_y, selection_box_last_y) - y;
1525
1526 cr.save ();
1527 Theme.color (cr, "Foreground 1");
1528 cr.set_line_width (2);
1529 cr.rectangle (x, y, w, h);
1530 cr.stroke ();
1531 cr.restore ();
1532 }
1533
1534 public static void draw_join_icon (Context cr, double x, double y) {
1535 cr.save ();
1536 Theme.color (cr, "Merge");
1537 cr.move_to (x, y);
1538 cr.arc (x, y, 15, 0, 2 * Math.PI);
1539 cr.close_path ();
1540 cr.fill ();
1541 cr.restore ();
1542 }
1543
1544 void draw_merge_icon (Context cr) {
1545 double x, y;
1546 if (active_edit_point != null) {
1547 get_tie_position ((!) active_edit_point, out x, out y);
1548 draw_join_icon (cr, x, y);
1549 }
1550 }
1551
1552 /** Obtain the position where to ends meet. */
1553 static void get_tie_position (EditPoint current_point, out double x, out double y) {
1554 Glyph glyph;
1555 EditPoint active;
1556 double px, py;
1557
1558 x = -100;
1559 y = -100;
1560
1561 if (!is_endpoint (current_point)) {
1562 return;
1563 }
1564
1565 glyph = MainWindow.get_current_glyph ();
1566 active = current_point;
1567
1568 return_if_fail (!is_null (glyph));
1569
1570 px = Glyph.reverse_path_coordinate_x (active.x);
1571 py = Glyph.reverse_path_coordinate_y (active.y);
1572
1573 foreach (Path path in glyph.path_list) {
1574
1575 if (!path.is_open ()) {
1576 continue;
1577 }
1578
1579 if (path.points.size == 0) {
1580 continue;
1581 }
1582
1583 foreach (EditPoint ep in path.points) {
1584 if (ep == active || !is_endpoint (ep)) {
1585 continue;
1586 }
1587
1588 if (is_close_to_point (ep, px, py)) {
1589 x = Glyph.reverse_path_coordinate_x (ep.x);
1590 y = Glyph.reverse_path_coordinate_y (ep.y);
1591 return;
1592 }
1593 }
1594 }
1595 }
1596
1597 public static bool is_endpoint (EditPoint ep) {
1598 EditPoint start;
1599 EditPoint end;
1600 Glyph glyph = MainWindow.get_current_glyph ();
1601
1602 foreach (Path path in glyph.path_list) {
1603 if (path.points.size < 1) {
1604 continue;
1605 }
1606
1607 start = path.points.get (0);
1608 end = path.points.get (path.points.size - 1);
1609
1610 if (ep == start || ep == end) {
1611 return true;
1612 }
1613 }
1614
1615 return false;
1616 }
1617
1618 public static void set_active_edit_point (EditPoint? e, Path path) {
1619 bool redraw;
1620 Glyph g = MainWindow.get_current_glyph ();
1621
1622 foreach (Path p in g.path_list) {
1623 foreach (var ep in p.points) {
1624 ep.set_active (false);
1625 }
1626 }
1627
1628 redraw = active_edit_point != e;
1629 active_edit_point = e;
1630 active_path = path;
1631
1632 if (e != null) {
1633 ((!)e).set_active (true);
1634 }
1635
1636 if (redraw) {
1637 GlyphCanvas.redraw ();
1638 }
1639 }
1640
1641 PointSelection? get_closest_point (double ex, double ey, out Path? path) {
1642 double x = Glyph.path_coordinate_x (ex);
1643 double y = Glyph.path_coordinate_y (ey);
1644 double d = double.MAX;
1645 double nd;
1646 PointSelection? ep = null;
1647 Glyph g = MainWindow.get_current_glyph ();
1648
1649 path = null;
1650
1651 foreach (Path current_path in g.path_list) {
1652 if (is_close_to_path (current_path, ex, ey)) {
1653 foreach (EditPoint e in current_path.points) {
1654 nd = e.get_distance (x, y);
1655
1656 if (nd < d) {
1657 d = nd;
1658 ep = new PointSelection (e, current_path);
1659 path = current_path;
1660 }
1661 }
1662 }
1663 }
1664
1665 return ep;
1666 }
1667
1668 public double get_distance_to_closest_edit_point (double event_x, double event_y) {
1669 Path? p;
1670 PointSelection e;
1671 PointSelection? ep = get_closest_point (event_x, event_y, out p);
1672
1673 double x = Glyph.path_coordinate_x (event_x);
1674 double y = Glyph.path_coordinate_y (event_y);
1675
1676 if (ep == null) {
1677 return double.MAX;
1678 }
1679
1680 e = (!) ep;
1681
1682 return e.point.get_distance (x, y);
1683 }
1684
1685 public void control_point_event (double event_x, double event_y) {
1686 Path? p;
1687 PointSelection? ep = get_closest_point (event_x, event_y, out p);
1688 Glyph g = MainWindow.get_current_glyph ();
1689 double x = Glyph.path_coordinate_x (event_x);
1690 double y = Glyph.path_coordinate_y (event_y);
1691 double distance;
1692 PointSelection e;
1693 set_active_edit_point (null, new Path ());
1694
1695 if (ep == null) {
1696 return;
1697 }
1698
1699 e = (!) ep;
1700 distance = e.point.get_distance (x, y) * g.view_zoom;
1701
1702 if (distance < contact_surface) {
1703 set_active_edit_point (e.point, e.path);
1704 }
1705 }
1706
1707 public PointSelection new_point_action (int x, int y) {
1708 Glyph glyph;
1709 PointSelection new_point;
1710
1711 glyph = MainWindow.get_current_glyph ();
1712 glyph.open_path ();
1713
1714 remove_all_selected_points ();
1715
1716 new_point = add_new_edit_point (x, y);
1717 new_point.point.set_selected (true);
1718
1719 selected_point = new_point.point;
1720 active_edit_point = new_point.point;
1721
1722 return_if_fail (glyph.active_paths.size > 0);
1723 add_selected_point (selected_point, glyph.active_paths.get (glyph.active_paths.size - 1));
1724
1725 active_path = new_point.path;
1726 glyph.clear_active_paths ();
1727 glyph.add_active_path (new_point.path);
1728
1729 move_selected = true;
1730
1731 return new_point;
1732 }
1733
1734 public static PointSelection add_new_edit_point (int x, int y) {
1735 PointSelection new_point;
1736
1737 new_point = insert_edit_point (x, y);
1738 new_point.path.update_region_boundaries ();
1739
1740 if (new_point.path.is_open () && new_point.path.points.size > 0) {
1741 new_point.path.get_first_point ().set_reflective_handles (false);
1742 new_point.path.get_first_point ().set_tie_handle (false);
1743 new_point.path.get_last_point ().set_reflective_handles (false);
1744 new_point.path.get_last_point ().set_tie_handle (false);
1745 }
1746
1747 selected_point = new_point.point;
1748 active_edit_point = new_point.point;
1749
1750 set_point_type (selected_point);
1751 set_default_handle_positions ();
1752
1753 selected_points.clear ();
1754 add_selected_point (new_point.point, new_point.path);
1755
1756 return new_point;
1757 }
1758
1759 private static PointSelection insert_edit_point (double x, double y) {
1760 double xt, yt;
1761 Path np;
1762 EditPoint inserted;
1763 bool stroke = StrokeTool.add_stroke;
1764 Glyph g = MainWindow.get_current_glyph ();
1765
1766 if (g.active_paths.size == 0) {
1767 np = new Path ();
1768 g.add_path (np);
1769 np.stroke = stroke ? StrokeTool.stroke_width : 0;
1770 np.line_cap = StrokeTool.line_cap;
1771
1772 g.add_active_path (np);
1773
1774 active_path = np;
1775 selected_path = np;
1776 }
1777
1778 xt = Glyph.path_coordinate_x (x);
1779 yt = Glyph.path_coordinate_y (y);
1780
1781 if (selected_path.is_open ()) {
1782 np = PenTool.selected_path;
1783 np.add (xt, yt);
1784 } else {
1785 np = new Path ();
1786 np.stroke = stroke ? StrokeTool.stroke_width : 0;
1787 g.add_path (np);
1788 np.add (xt, yt);
1789
1790 if (DrawingTools.pen_tool.is_selected ()) {
1791 np.set_stroke (PenTool.path_stroke_width);
1792 }
1793
1794 PenTool.active_path = np;
1795 }
1796
1797 g.clear_active_paths ();
1798 g.add_active_path (np);
1799 active_path = np;
1800 selected_path = np;
1801
1802 inserted = np.points.get (np.points.size - 1);
1803
1804 return new PointSelection (inserted, np);
1805 }
1806
1807 static void set_point_type (EditPoint p) {
1808 if (p.prev != null && p.get_prev ().right_handle.type == PointType.QUADRATIC) {
1809 p.left_handle.type = PointType.QUADRATIC;
1810 p.right_handle.type = PointType.LINE_QUADRATIC;
1811 p.type = PointType.QUADRATIC;
1812 } else if (DrawingTools.get_selected_point_type () == PointType.QUADRATIC) {
1813 p.left_handle.type = PointType.LINE_QUADRATIC;
1814 p.right_handle.type = PointType.LINE_QUADRATIC;
1815 p.type = PointType.LINE_QUADRATIC;
1816 } else if (DrawingTools.get_selected_point_type () == PointType.DOUBLE_CURVE) {
1817 p.left_handle.type = PointType.LINE_DOUBLE_CURVE;
1818 p.right_handle.type = PointType.LINE_DOUBLE_CURVE;
1819 p.type = PointType.DOUBLE_CURVE;
1820 } else {
1821 p.left_handle.type = PointType.LINE_CUBIC;
1822 p.right_handle.type = PointType.LINE_CUBIC;
1823 p.type = PointType.CUBIC;
1824 }
1825 }
1826
1827 public static void set_default_handle_positions () {
1828 Glyph g = MainWindow.get_current_glyph ();
1829 foreach (var p in g.path_list) {
1830 if (p.is_editable ()) {
1831 p.create_list ();
1832 set_default_handle_positions_on_path (p);
1833 }
1834 }
1835 }
1836
1837 static void set_default_handle_positions_on_path (Path path) {
1838 foreach (EditPoint e in path.points) {
1839 if (!e.tie_handles && !e.reflective_point) {
1840 e.recalculate_linear_handles ();
1841 }
1842 }
1843 }
1844
1845 private bool is_over_handle (double event_x, double event_y) {
1846 Glyph g = MainWindow.get_current_glyph ();
1847 double distance_to_edit_point = g.view_zoom * get_distance_to_closest_edit_point (event_x, event_y);
1848
1849 if (!Path.show_all_line_handles) {
1850 foreach (PointSelection selected_corner in selected_points) {
1851 if (is_close_to_handle (selected_corner.point, event_x, event_y, distance_to_edit_point)) {
1852 return true;
1853 }
1854 }
1855 } else {
1856 foreach (Path p in g.path_list) {
1857 if (is_close_to_path (p, event_x, event_y)) {
1858 foreach (EditPoint ep in p.points) {
1859 if (is_close_to_handle (ep, event_x, event_y, distance_to_edit_point)) {
1860 return true;
1861 }
1862 }
1863 }
1864 }
1865 }
1866
1867 return false;
1868 }
1869
1870 bool is_close_to_path (Path p, double event_x, double event_y) {
1871 double c = contact_surface * Glyph.ivz ();
1872 double x = Glyph.path_coordinate_x (event_x);
1873 double y = Glyph.path_coordinate_y (event_y);
1874
1875 if (unlikely (!p.has_region_boundaries ())) {
1876 if (p.points.size > 0) {
1877 warning (@"No bounding box. $(p.points.size)");
1878 p.update_region_boundaries ();
1879 }
1880 }
1881
1882 return p.xmin - c <= x <= p.xmax + c && p.ymin - c <= y <= p.ymax + c;
1883 }
1884
1885 private bool is_close_to_handle (EditPoint selected_corner, double event_x, double event_y, double distance_to_edit_point) {
1886 double x = Glyph.path_coordinate_x (event_x);
1887 double y = Glyph.path_coordinate_y (event_y);
1888 Glyph g = MainWindow.get_current_glyph ();
1889 double d_point = distance_to_edit_point;
1890 double dl, dr;
1891
1892 dl = g.view_zoom * selected_corner.get_left_handle ().get_point ().get_distance (x, y);
1893 dr = g.view_zoom * selected_corner.get_right_handle ().get_point ().get_distance (x, y);
1894
1895 if (dl < contact_surface && dl < d_point) {
1896 return true;
1897 }
1898
1899 if (dr < contact_surface && dr < d_point) {
1900 return true;
1901 }
1902
1903 return false;
1904 }
1905
1906 PointSelection get_closest_handle (double event_x, double event_y) {
1907 EditPointHandle left, right;
1908 double x = Glyph.path_coordinate_x (event_x);
1909 double y = Glyph.path_coordinate_y (event_y);
1910 EditPointHandle eh = new EditPointHandle.empty();
1911 Glyph g = MainWindow.get_current_glyph ();
1912 double d = double.MAX;
1913 double dn;
1914 Path path = new Path ();
1915 bool left_handle = false;
1916 EditPoint parent_point;
1917 EditPoint tied_point;
1918
1919 foreach (Path p in g.path_list) {
1920 if (is_close_to_path (p, event_x, event_y) || p == active_path) {
1921 foreach (EditPoint ep in p.points) {
1922 if (ep.is_selected () || Path.show_all_line_handles) {
1923 left = ep.get_left_handle ();
1924 right = ep.get_right_handle ();
1925
1926 dn = left.get_point ().get_distance (x, y);
1927
1928 if (dn < d) {
1929 eh = left;
1930 d = dn;
1931 path = p;
1932 left_handle = true;
1933 }
1934
1935 dn = right.get_point ().get_distance (x, y);
1936
1937 if (dn < d) {
1938 eh = right;
1939 d = dn;
1940 path = p;
1941 left_handle = false;
1942 }
1943 }
1944 }
1945 }
1946 }
1947
1948 // Make sure the selected handle belongs to the selected point if
1949 // the current segment is quadratic.
1950 if (eh.type == PointType.QUADRATIC) {
1951 parent_point = eh.get_parent ();
1952
1953 if (left_handle) {
1954 if (parent_point.prev != null) {
1955 tied_point = parent_point.get_prev ();
1956 if (tied_point.selected_point) {
1957 eh = tied_point.get_right_handle ();
1958 }
1959 }
1960 } else {
1961 if (parent_point.next != null) {
1962 tied_point = parent_point.get_next ();
1963 if (tied_point.selected_point) {
1964 eh = tied_point.get_left_handle ();
1965 }
1966 }
1967 }
1968 }
1969
1970 return new PointSelection.handle_selection (eh, path);
1971 }
1972
1973 private void curve_active_corner_event (double event_x, double event_y) {
1974 PointSelection eh;
1975 Glyph glyph;
1976
1977 glyph = MainWindow.get_current_glyph ();
1978 active_handle.active = false;
1979
1980 if (!is_over_handle (event_x, event_y)) {
1981 return;
1982 }
1983
1984 eh = get_closest_handle (event_x, event_y);
1985 eh.handle.active = true;
1986 active_handle = eh.handle;
1987 }
1988
1989 private void curve_corner_event (double event_x, double event_y) {
1990 Glyph g = MainWindow.get_current_glyph ();
1991 g.open_path ();
1992 PointSelection p;
1993
1994 if (!is_over_handle (event_x, event_y)) {
1995 return;
1996 }
1997
1998 move_selected_handle = true;
1999 last_selected_is_handle = true;
2000 selected_handle.selected = false;
2001 p = get_closest_handle (event_x, event_y);
2002 selected_handle = p.handle;
2003 handle_selection = p;
2004 selected_handle.selected = true;
2005
2006 active_path = p.path;
2007 g.add_active_path (active_path);
2008 }
2009
2010 public static void add_selected_point (EditPoint p, Path path) {
2011 bool in_path = false;
2012
2013 foreach (EditPoint e in path.points) {
2014 if (e == p) {
2015 in_path = true;
2016 break;
2017 }
2018 }
2019
2020 if (!in_path) {
2021 warning ("Point is not in path.");
2022 }
2023
2024 foreach (PointSelection ep in selected_points) {
2025 if (p == ep.point) {
2026 return;
2027 }
2028 }
2029
2030 selected_points.add (new PointSelection (p, path));
2031 }
2032
2033 public static void remove_all_selected_points () {
2034 Glyph g = MainWindow.get_current_glyph ();
2035
2036 foreach (PointSelection ep in selected_points) {
2037 ep.point.set_active (false);
2038 ep.point.set_selected (false);
2039 }
2040
2041 selected_points.clear ();
2042
2043 foreach (Path p in g.path_list) {
2044 foreach (EditPoint e in p.points) {
2045 e.set_active (false);
2046 e.set_selected (false);
2047 }
2048 }
2049 }
2050
2051 static void move_select_next_point (uint keyval) {
2052 PointSelection next = new PointSelection.empty ();
2053
2054 if (selected_points.size == 0) {
2055 return;
2056 }
2057
2058 switch (keyval) {
2059 case Key.UP:
2060 next = get_next_point_up ();
2061 break;
2062 case Key.DOWN:
2063 next = get_next_point_down ();
2064 break;
2065 case Key.LEFT:
2066 next = get_next_point_left ();
2067 break;
2068 case Key.RIGHT:
2069 next = get_next_point_right ();
2070 break;
2071 default:
2072 break;
2073 }
2074
2075 set_selected_point (next.point, next.path);
2076 GlyphCanvas.redraw ();
2077 }
2078
2079 private static PointSelection get_next_point (double angle)
2080 requires (selected_points.size != 0) {
2081 PointSelection e = selected_points.get (selected_points.size - 1);
2082 double right_angle = e.point.right_handle.angle;
2083 double left_angle = e.point.left_handle.angle;
2084 double min_right, min_left;
2085 double min;
2086
2087 return_val_if_fail (e.point.next != null, new EditPoint ());
2088 return_val_if_fail (e.point.prev != null, new EditPoint ());
2089
2090 // angle might be greater than 2 PI or less than 0
2091 min_right = double.MAX;
2092 min_left = double.MAX;
2093 for (double i = -2 * PI; i <= 2 * PI; i += 2 * PI) {
2094 min = fabs (right_angle - (angle + i));
2095 if (min < min_right) {
2096 min_right = min;
2097 }
2098
2099 min = fabs (left_angle - (angle + i));
2100 if (min < min_left) {
2101 min_left = min;
2102 }
2103 }
2104
2105 if (min_right < min_left) {
2106 return new PointSelection (e.point.get_next (), e.path);
2107 }
2108
2109 return new PointSelection (e.point.get_prev (), e.path);
2110 }
2111
2112 private static PointSelection get_next_point_up () {
2113 return get_next_point (PI / 2);
2114 }
2115
2116 private static PointSelection get_next_point_down () {
2117 return get_next_point (PI + PI / 2);
2118 }
2119
2120 private static PointSelection get_next_point_left () {
2121 return get_next_point (PI);
2122 }
2123
2124 private static PointSelection get_next_point_right () {
2125 return get_next_point (0);
2126 }
2127
2128 private static void set_selected_point (EditPoint ep, Path p) {
2129 remove_all_selected_points ();
2130 add_selected_point (ep, p);
2131 set_active_edit_point (ep, p);
2132 edit_active_corner = true;
2133 ep.set_selected (true);
2134 set_default_handle_positions ();
2135 }
2136
2137 public static void select_point_up () {
2138 move_select_next_point (Key.UP);
2139 }
2140
2141 public static void select_point_down () {
2142 move_select_next_point (Key.DOWN);
2143 }
2144
2145 public static void select_point_right () {
2146 move_select_next_point (Key.RIGHT);
2147 }
2148
2149 public static void select_point_left () {
2150 move_select_next_point (Key.LEFT);
2151 }
2152
2153 /**
2154 * Move the selected editpoint one pixel with keyboard irrespectivly of
2155 * current zoom.
2156 */
2157 void move_selected_points (uint keyval) {
2158 Path? last_path = null;
2159
2160 if (!last_selected_is_handle) {
2161 if (keyval == Key.UP) {
2162 foreach (PointSelection e in selected_points) {
2163 e.point.set_position (e.point.x, e.point.y + Glyph.ivz ());
2164 e.point.recalculate_linear_handles ();
2165 }
2166 }
2167
2168 if (keyval == Key.DOWN) {
2169 foreach (PointSelection e in selected_points) {
2170 e.point.set_position (e.point.x, e.point.y - Glyph.ivz ());
2171 e.point.recalculate_linear_handles ();
2172 }
2173 }
2174
2175 if (keyval == Key.LEFT) {
2176 foreach (PointSelection e in selected_points) {
2177 e.point.set_position (e.point.x - Glyph.ivz (), e.point.y);
2178 e.point.recalculate_linear_handles ();
2179 }
2180 }
2181
2182 if (keyval == Key.RIGHT) {
2183 foreach (PointSelection e in selected_points) {
2184 e.point.set_position (e.point.x + Glyph.ivz (), e.point.y);
2185 e.point.recalculate_linear_handles ();
2186 }
2187 }
2188
2189 last_path = null;
2190 foreach (PointSelection e in selected_points) {
2191 if (e.path != last_path) {
2192 e.path.update_region_boundaries ();
2193 last_path = e.path;
2194 }
2195 }
2196
2197 } else {
2198 set_type_for_moving_handle ();
2199 active_handle.active = false;
2200 active_handle = new EditPointHandle.empty ();
2201
2202 if (keyval == Key.UP) {
2203 selected_handle.move_delta_coordinate (0, 1 * Glyph.ivz ());
2204 }
2205
2206 if (keyval == Key.DOWN) {
2207 selected_handle.move_delta_coordinate (0, -1 * Glyph.ivz ());
2208 }
2209
2210 if (keyval == Key.LEFT) {
2211 selected_handle.move_delta_coordinate (-1 * Glyph.ivz (), 0);
2212 }
2213
2214 if (keyval == Key.RIGHT) {
2215 selected_handle.move_delta_coordinate (1 * Glyph.ivz (), 0);
2216 }
2217 }
2218
2219 reset_stroke ();
2220
2221 // TODO: redraw only the relevant parts
2222 GlyphCanvas.redraw ();
2223 }
2224
2225 public static void convert_point_to_line (EditPoint ep, bool both) {
2226 ep.set_tie_handle (false);
2227 ep.set_reflective_handles (false);
2228
2229 if (ep.next == null) {
2230 // FIXME: write a new function for this case
2231 // warning ("Next is null.");
2232 }
2233
2234 if (ep.prev == null) {
2235 warning ("Prev is null.");
2236 }
2237
2238 if (ep.type == PointType.CUBIC || ep.type == PointType.LINE_CUBIC) {
2239 ep.type = PointType.LINE_CUBIC;
2240
2241 if (both) {
2242 ep.get_left_handle ().type = PointType.LINE_CUBIC;
2243 ep.get_right_handle ().type = PointType.LINE_CUBIC;
2244 }
2245
2246 if (ep.next != null && ep.get_next ().is_selected ()) {
2247 ep.get_right_handle ().type = PointType.LINE_CUBIC;
2248 }
2249
2250 if (ep.prev != null && ep.get_prev ().is_selected ()) {
2251 ep.get_left_handle ().type = PointType.LINE_CUBIC;
2252 }
2253
2254 }
2255
2256 if (ep.type == PointType.DOUBLE_CURVE| ep.type == PointType.LINE_DOUBLE_CURVE) {
2257 ep.type = PointType.LINE_DOUBLE_CURVE;
2258 if (both) {
2259 ep.get_left_handle ().type = PointType.LINE_DOUBLE_CURVE;
2260 ep.get_right_handle ().type = PointType.LINE_DOUBLE_CURVE;
2261 }
2262
2263 if (ep.next != null && ep.get_next ().is_selected ()) {
2264 ep.get_right_handle ().type = PointType.LINE_DOUBLE_CURVE;
2265 }
2266
2267 if (ep.prev != null && ep.get_prev ().is_selected ()) {
2268 ep.get_left_handle ().type = PointType.LINE_DOUBLE_CURVE;
2269 }
2270 }
2271
2272 if (ep.type == PointType.QUADRATIC || ep.type == PointType.LINE_QUADRATIC) {
2273 ep.type = PointType.LINE_QUADRATIC;
2274
2275 if (both) {
2276 ep.get_left_handle ().type = PointType.LINE_QUADRATIC;
2277 ep.get_right_handle ().type = PointType.LINE_QUADRATIC;
2278
2279 if (ep.next != null) {
2280 ep.get_next ().get_left_handle ().type = PointType.LINE_QUADRATIC;
2281 }
2282
2283 if (ep.prev != null) {
2284 ep.get_prev ().get_right_handle ().type = PointType.LINE_QUADRATIC;
2285 }
2286 }
2287
2288 if (ep.next != null && ep.get_next ().is_selected ()) {
2289 ep.get_right_handle ().type = PointType.LINE_QUADRATIC;
2290 ep.get_next ().get_left_handle ().type = PointType.LINE_QUADRATIC;
2291 }
2292
2293 if (ep.prev != null && ep.get_prev ().is_selected ()) {
2294 ep.get_left_handle ().type = PointType.LINE_QUADRATIC;
2295 ep.get_prev ().get_right_handle ().type = PointType.LINE_QUADRATIC;
2296 }
2297
2298 }
2299
2300 ep.recalculate_linear_handles ();
2301 }
2302
2303 public static void convert_segment_to_line () {
2304 if (selected_points.size == 0) {
2305 return;
2306 }
2307
2308 if (selected_points.size == 1) {
2309 convert_point_to_line (selected_points.get (0).point, true);
2310 } else {
2311 foreach (PointSelection p in selected_points) {
2312 convert_point_to_line (p.point, false);
2313 }
2314 }
2315 }
2316
2317 public static bool is_line (PointType t) {
2318 return t == PointType.LINE_QUADRATIC
2319 || t == PointType.LINE_DOUBLE_CURVE
2320 || t == PointType.LINE_CUBIC;
2321 }
2322
2323 public static PointType to_line (PointType t) {
2324 switch (t) {
2325 case PointType.QUADRATIC:
2326 return PointType.LINE_QUADRATIC;
2327 case PointType.DOUBLE_CURVE:
2328 return PointType.LINE_DOUBLE_CURVE;
2329 case PointType.CUBIC:
2330 return PointType.LINE_CUBIC;
2331 default:
2332 break;
2333 }
2334 return t;
2335 }
2336
2337 public static PointType to_curve (PointType t) {
2338 switch (t) {
2339 case PointType.LINE_QUADRATIC:
2340 return PointType.QUADRATIC;
2341 case PointType.LINE_DOUBLE_CURVE:
2342 return PointType.DOUBLE_CURVE;
2343 case PointType.LINE_CUBIC:
2344 return PointType.CUBIC;
2345 default:
2346 break;
2347 }
2348
2349 if (unlikely (t == PointType.NONE)) {
2350 warning ("Type is not set.");
2351 }
2352
2353 return t;
2354 }
2355
2356 public static void set_converted_handle_length (EditPointHandle e, PointType pt) {
2357
2358 if (e.type == PointType.QUADRATIC && pt == PointType.DOUBLE_CURVE) {
2359 e.length *= 2;
2360 e.length /= 4;
2361 }
2362
2363 if (e.type == PointType.QUADRATIC && pt == PointType.CUBIC) {
2364 e.length *= 2;
2365 e.length /= 3;
2366 }
2367
2368 if (e.type == PointType.DOUBLE_CURVE && pt == PointType.QUADRATIC) {
2369 e.length *= 4;
2370 e.length /= 2;
2371 }
2372
2373 if (e.type == PointType.DOUBLE_CURVE && pt == PointType.CUBIC) {
2374 e.length *= 4;
2375 e.length /= 3;
2376 }
2377
2378 if (e.type == PointType.CUBIC && pt == PointType.QUADRATIC) {
2379 e.length *= 3;
2380 e.length /= 2;
2381 }
2382
2383 if (e.type == PointType.CUBIC && pt == PointType.DOUBLE_CURVE) {
2384 e.length *= 3;
2385 e.length /= 4;
2386 }
2387 }
2388
2389 public static void convert_point_segment_type (EditPoint first, EditPoint next, PointType point_type) {
2390 bool line;
2391
2392 set_converted_handle_length (first.get_right_handle (), point_type);
2393 set_converted_handle_length (next.get_left_handle (), point_type);
2394
2395 line = is_line (first.type)
2396 && is_line (first.get_right_handle ().type)
2397 && is_line (next.get_left_handle ().type);
2398
2399 if (!line) {
2400 first.type = point_type;
2401 } else {
2402 first.type = to_line (point_type);
2403 }
2404
2405 if (!line) {
2406 first.get_right_handle ().type = point_type;
2407 } else {
2408 first.get_right_handle ().type = to_line (point_type);
2409 }
2410
2411 if (!line) {
2412 next.get_left_handle ().type = point_type;
2413 } else {
2414 next.get_left_handle ().type = to_line (point_type);
2415 }
2416
2417 // process connected handle
2418 if (point_type == PointType.QUADRATIC) {
2419 first.set_position (first.x, first.y);
2420 first.recalculate_linear_handles ();
2421 }
2422 }
2423
2424 public static void convert_point_type (EditPoint first, PointType point_type) {
2425 convert_point_segment_type (first, first.get_next (), point_type);
2426 }
2427
2428 public static void convert_point_types () {
2429 Glyph glyph = MainWindow.get_current_glyph ();
2430 glyph.store_undo_state ();
2431 PointSelection selected = new PointSelection.empty ();
2432 bool reset_selected = false;
2433 EditPoint e;
2434
2435 if (selected_points.size == 1) {
2436 selected = selected_points.get (0);
2437 if (selected.point.next != null) {
2438 selected_points.add (new PointSelection (selected.point.get_next (), selected.path));
2439 selected.point.get_next ().set_selected (true);
2440 }
2441
2442 if (selected.point.prev != null) {
2443 selected_points.add (new PointSelection (selected.point.get_prev (), selected.path));
2444 selected.point.get_next ().set_selected (true);
2445 }
2446
2447 reset_selected = true;
2448 }
2449
2450 foreach (PointSelection ps in selected_points) {
2451 e = ps.point;
2452
2453 // convert segments not control points
2454 if (e.next == null || !e.get_next ().is_selected ()) {
2455 continue;
2456 }
2457
2458 convert_point_type (e, DrawingTools.point_type);
2459 }
2460
2461 if (reset_selected) {
2462 remove_all_selected_points ();
2463 selected_points.add (selected);
2464 selected.point.set_selected (true);
2465 }
2466
2467 foreach (Path p in glyph.path_list) {
2468 p.update_region_boundaries ();
2469 }
2470 }
2471
2472 public static void update_selected_points () {
2473 Glyph g = MainWindow.get_current_glyph ();
2474 selected_points.clear ();
2475
2476 foreach (Path p in g.path_list) {
2477 foreach (EditPoint ep in p.points) {
2478 if (ep.is_selected ()) {
2479 selected_points.add (new PointSelection (ep, p));
2480 }
2481 }
2482 }
2483 }
2484
2485 public void select_all_points () {
2486 Glyph g = MainWindow.get_current_glyph ();
2487
2488 foreach (Path p in g.path_list) {
2489 foreach (EditPoint ep in p.points) {
2490 ep.set_selected (true);
2491 add_selected_point (ep, p);
2492 }
2493 }
2494 }
2495
2496 public static Path simplify (Path path, bool selected_segments = false, double threshold = 0.3) {
2497 PointSelection ps;
2498 EditPoint ep;
2499 Path p1, new_path;
2500 double d, sumd;
2501 int i;
2502
2503 p1 = path.copy ();
2504 new_path = p1.copy ();
2505 i = 0;
2506 sumd = 0;
2507 while (i < new_path.points.size) {
2508 ep = new_path.points.get (i);
2509 ps = new PointSelection (ep, new_path);
2510 d = PenTool.remove_point_simplify (ps);
2511 sumd += d;
2512
2513 if (sumd < threshold) {
2514 p1 = new_path.copy ();
2515 } else {
2516 new_path = p1.copy ();
2517 sumd = 0;
2518 i++;
2519 }
2520 }
2521
2522 new_path.update_region_boundaries ();
2523
2524 return new_path;
2525 }
2526
2527 public void set_simplification_threshold (double t) {
2528 simplification_threshold = t;
2529 }
2530 }
2531
2532 }
2533