The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

PenTool.vala in libbirdfont

This file is a part of the Birdfont project.

Contributing

Send patches or pull requests to johan.mattsson.m@gmail.com.
Clone this repository: git clone https://github.com/johanmattssonm/birdfont.git

Revisions

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