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