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