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.
Move the SVG parser and the SVG rendering code to libsvgbird
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 (Object path in g.active_paths) { 769 if (path is PathObject) { 770 PathObject p = (PathObject) path; 771 p.get_path ().reset_stroke (); 772 } 773 } 774 } 775 776 public void move (int x, int y) { 777 double coordinate_x, coordinate_y; 778 double delta_coordinate_x, delta_coordinate_y; 779 double angle = 0; 780 bool tied; 781 Glyph g; 782 783 g = MainWindow.get_current_glyph (); 784 785 if (move_selected || move_selected_handle) { 786 cancel_stroke_creator (); 787 } 788 789 control_point_event (x, y); 790 curve_active_corner_event (x, y); 791 set_default_handle_positions (); 792 793 if (move_selected_handle && move_selected) { 794 warning ("move_selected_handle && move_selected"); 795 move_selected = false; 796 move_selected_handle = false; 797 } 798 799 if (move_selected_handle || move_selected) { 800 MainWindow.set_cursor (NativeWindow.HIDDEN); 801 reset_stroke (); 802 } else { 803 MainWindow.set_cursor (NativeWindow.VISIBLE); 804 } 805 806 // move control point handles 807 if (move_selected_handle) { 808 set_type_for_moving_handle (); 809 810 // don't update angle if the user is holding down shift 811 if (KeyBindings.modifier == SHIFT || PenTool.retain_angle) { 812 angle = selected_handle.angle; 813 } 814 815 if (GridTool.is_visible ()) { 816 coordinate_x = Glyph.path_coordinate_x (x); 817 coordinate_y = Glyph.path_coordinate_y (y); 818 GridTool.tie_coordinate (ref coordinate_x, ref coordinate_y); 819 delta_coordinate_x = coordinate_x - last_point_x; 820 delta_coordinate_y = coordinate_y - last_point_y; 821 selected_handle.move_to_coordinate (selected_handle.x + delta_coordinate_x, selected_handle.y + delta_coordinate_y); 822 } else if (GridTool.has_ttf_grid ()) { 823 coordinate_x = Glyph.path_coordinate_x (x); 824 coordinate_y = Glyph.path_coordinate_y (y); 825 GridTool.ttf_grid_coordinate (ref coordinate_x, ref coordinate_y); 826 delta_coordinate_x = coordinate_x - last_point_x; 827 delta_coordinate_y = coordinate_y - last_point_y; 828 selected_handle.move_delta_coordinate (delta_coordinate_x, delta_coordinate_y); 829 } else { 830 coordinate_x = Glyph.path_coordinate_x (x); 831 coordinate_y = Glyph.path_coordinate_y (y); 832 delta_coordinate_x = coordinate_x - last_point_x; 833 delta_coordinate_y = coordinate_y - last_point_y; 834 selected_handle.move_delta_coordinate (delta_coordinate_x, delta_coordinate_y); 835 836 if (on_axis) { 837 double horizontal, vertical; 838 839 horizontal = Path.distance (selected_handle.parent.x, selected_handle.x, selected_handle.y, selected_handle.y); 840 vertical = Path.distance (selected_handle.x, selected_handle.x, selected_handle.parent.y, selected_handle.y); 841 842 if (horizontal < vertical) { 843 selected_handle.move_to_coordinate (selected_handle.parent.x, selected_handle.y); 844 } else { 845 selected_handle.move_to_coordinate (selected_handle.x, selected_handle.parent.y); 846 } 847 } 848 } 849 850 if (KeyBindings.modifier == SHIFT || PenTool.retain_angle) { 851 selected_handle.angle = angle; 852 selected_handle.process_connected_handle (); 853 854 if (selected_handle.parent.tie_handles) { 855 if (selected_handle.is_left_handle ()) { 856 selected_handle.parent.get_right_handle ().angle = angle - PI; 857 } else { 858 selected_handle.parent.get_left_handle ().angle = angle + PI; 859 } 860 } 861 } 862 863 handle_selection.path.update_region_boundaries (); 864 865 // FIXME: redraw line only 866 GlyphCanvas.redraw (); 867 868 if (GridTool.is_visible ()) { 869 last_point_x = selected_handle.x; 870 last_point_y = selected_handle.y; 871 } else if (GridTool.has_ttf_grid ()) { 872 last_point_x = selected_handle.x; 873 last_point_y = selected_handle.y; 874 } else { 875 last_point_x = Glyph.path_coordinate_x (x); 876 last_point_y = Glyph.path_coordinate_y (y); 877 } 878 } 879 880 // move edit point 881 if (move_selected && !show_selection_box) { 882 883 if (GridTool.is_visible ()) { 884 coordinate_x = Glyph.path_coordinate_x (x); 885 coordinate_y = Glyph.path_coordinate_y (y); 886 887 if (selected_point.tie_handles && KeyBindings.modifier == SHIFT) { 888 if (first_move_action) { 889 last_point_x = selected_point.x; 890 last_point_y = selected_point.y; 891 } 892 893 move_point_on_handles (coordinate_x, coordinate_y, out coordinate_x, out coordinate_y); 894 } else { 895 GridTool.tie_coordinate (ref coordinate_x, ref coordinate_y); 896 } 897 898 delta_coordinate_x = coordinate_x - last_point_x; 899 delta_coordinate_y = coordinate_y - last_point_y; 900 901 foreach (PointSelection selected in selected_points) { 902 if (move_point_independent_of_handle || KeyBindings.modifier == SHIFT) { 903 selected.point.set_independet_position (selected.point.x + delta_coordinate_x, 904 selected.point.y + delta_coordinate_y); 905 } else { 906 selected.point.set_position (selected.point.x + delta_coordinate_x, 907 selected.point.y + delta_coordinate_y); 908 } 909 910 selected.path.reset_stroke (); 911 selected.path.recalculate_linear_handles_for_point(selected.point); 912 selected.path.update_region_boundaries (); 913 } 914 } else if (GridTool.has_ttf_grid ()) { 915 coordinate_x = Glyph.path_coordinate_x (x); 916 coordinate_y = Glyph.path_coordinate_y (y); 917 918 GridTool.ttf_grid_coordinate (ref coordinate_x, ref coordinate_y); 919 920 if (selected_point.tie_handles && KeyBindings.modifier == SHIFT) { 921 if (first_move_action) { 922 last_point_x = selected_point.x; 923 last_point_y = selected_point.y; 924 } 925 926 move_point_on_handles (coordinate_x, coordinate_y, out coordinate_x, out coordinate_y); 927 } 928 929 delta_coordinate_x = coordinate_x - last_point_x; 930 delta_coordinate_y = coordinate_y - last_point_y; 931 932 foreach (PointSelection selected in selected_points) { 933 if (move_point_independent_of_handle || KeyBindings.modifier == SHIFT) { 934 tied = selected.point.tie_handles; 935 selected.point.tie_handles = false; 936 selected.point.set_independet_position (selected.point.x + delta_coordinate_x, 937 selected.point.y + delta_coordinate_y); 938 selected.point.tie_handles = tied; 939 } else { 940 selected.point.set_position (selected.point.x + delta_coordinate_x, 941 selected.point.y + delta_coordinate_y); 942 } 943 944 selected.path.reset_stroke (); 945 selected.path.recalculate_linear_handles_for_point(selected.point); 946 selected.path.update_region_boundaries (); 947 } 948 } else { 949 coordinate_x = Glyph.path_coordinate_x (x); 950 coordinate_y = Glyph.path_coordinate_y (y); 951 952 if (selected_point.tie_handles && KeyBindings.modifier == SHIFT) { 953 if (first_move_action) { 954 last_point_x = selected_point.x; 955 last_point_y = selected_point.y; 956 } 957 958 move_point_on_handles (coordinate_x, coordinate_y, out coordinate_x, out coordinate_y); 959 delta_coordinate_x = coordinate_x - last_point_x; 960 delta_coordinate_y = coordinate_y - last_point_y; 961 } else { 962 delta_coordinate_x = coordinate_x - last_point_x; 963 delta_coordinate_y = coordinate_y - last_point_y; 964 } 965 966 foreach (PointSelection selected in selected_points) { 967 968 if (move_point_independent_of_handle || KeyBindings.modifier == SHIFT) { 969 selected.point.set_independet_position (selected.point.x + delta_coordinate_x, 970 selected.point.y + delta_coordinate_y); 971 } else { 972 selected.point.set_position (selected.point.x + delta_coordinate_x, 973 selected.point.y + delta_coordinate_y); 974 } 975 976 selected.path.recalculate_linear_handles_for_point(selected.point); 977 selected.path.reset_stroke (); 978 selected.path.update_region_boundaries (); 979 } 980 } 981 982 if (selected_point.tie_handles && KeyBindings.modifier == SHIFT) { 983 last_point_x = selected_point.x; 984 last_point_y = selected_point.y; 985 } else if (GridTool.is_visible ()) { 986 last_point_x = selected_point.x; 987 last_point_y = selected_point.y; 988 } else if (GridTool.has_ttf_grid ()) { 989 last_point_x = selected_point.x; 990 last_point_y = selected_point.y; 991 } else { 992 last_point_x = Glyph.path_coordinate_x (x); 993 last_point_y = Glyph.path_coordinate_y (y); 994 } 995 996 GlyphCanvas.redraw (); 997 } 998 999 if (show_selection_box) { 1000 GlyphCanvas.redraw (); 1001 } 1002 } 1003 1004 static void cancel_stroke_creator () { 1005 foreach (PointSelection p in selected_points) { 1006 p.path.stop_stroke_creator (); 1007 } 1008 } 1009 1010 private void move_point_on_handles (double px, double py, out double cx, out double cy) { 1011 EditPoint ep; 1012 ep = selected_point.copy (); 1013 ep.tie_handles = false; 1014 ep.reflective_point = false; 1015 ep.get_right_handle ().angle += PI / 2; 1016 ep.x = px; 1017 ep.y = py; 1018 1019 Path.find_intersection_handle (ep.get_right_handle (), selected_point.get_right_handle (), out cx, out cy); 1020 } 1021 1022 public void press (int button, int x, int y, bool double_click) { 1023 Glyph? g = MainWindow.get_current_glyph (); 1024 Glyph glyph = (!) g; 1025 bool reflective; 1026 1027 return_if_fail (g != null); 1028 1029 if ((double_click && !BirdFont.android) 1030 || Toolbox.drawing_tools.insert_point_on_path_tool.is_selected ()) { 1031 glyph.insert_new_point_on_path (x, y); 1032 return; 1033 } 1034 1035 if (button == 1) { 1036 add_point_event (x, y); 1037 return; 1038 } 1039 1040 if (button == 2) { 1041 if (glyph.is_open ()) { 1042 force_direction (); 1043 glyph.close_path (); 1044 } else { 1045 glyph.open_path (); 1046 } 1047 1048 return; 1049 } 1050 1051 if (button == 3 && !KeyBindings.has_shift () ) { 1052 selected_path = active_path; 1053 move_point_event (x, y); 1054 1055 // alt+click on a handle ends the symmetrical editing 1056 if ((KeyBindings.has_alt () || KeyBindings.has_ctrl ()) 1057 && is_over_handle (x, y)) { 1058 1059 // don't use set point to reflective to on open ends 1060 reflective = true; 1061 foreach (Object path in MainWindow.get_current_glyph ().active_paths) { 1062 if (path.is_open () && !path.is_empty () && path is PathObject) { 1063 Path p = ((PathObject) path).get_path (); 1064 if (selected_handle.parent == p.get_first_point () 1065 || selected_handle.parent == p.get_last_point ()) { 1066 reflective = false; 1067 } 1068 } 1069 } 1070 1071 if (reflective) { 1072 selected_handle.parent.set_reflective_handles (false); 1073 selected_handle.parent.set_tie_handle (false); 1074 GlyphCanvas.redraw (); 1075 } 1076 } 1077 1078 return; 1079 } 1080 1081 if (button == 3) { 1082 selected_path = active_path; 1083 move_point_event (x, y); 1084 } 1085 1086 if (KeyBindings.has_shift () && !is_over_handle (x, y)) { 1087 show_selection_box = true; 1088 return; 1089 } 1090 } 1091 1092 public void add_point_event (int x, int y) { 1093 Glyph? g = MainWindow.get_current_glyph (); 1094 Glyph glyph = (!) g; 1095 PointSelection ps; 1096 1097 if (move_selected_handle) { 1098 warning ("moving handle"); 1099 return; 1100 } 1101 1102 return_if_fail (g != null); 1103 1104 remove_all_selected_points (); 1105 ps = new_point_action (x, y); 1106 active_path = ps.path; 1107 glyph.store_undo_state (); 1108 } 1109 1110 public void move_point_event (int x, int y) { 1111 Glyph? g = MainWindow.get_current_glyph (); 1112 Glyph glyph = (!) g; 1113 1114 return_if_fail (g != null); 1115 1116 control_point_event (x, y); 1117 curve_corner_event (x, y); 1118 1119 if (!move_selected_handle) { 1120 select_active_point (x, y); 1121 last_selected_is_handle = false; 1122 } 1123 1124 if (!KeyBindings.has_shift () 1125 && selected_points.size == 0 1126 && !active_handle.active) { 1127 show_selection_box = true; 1128 } 1129 1130 glyph.store_undo_state (); 1131 } 1132 1133 void set_type_for_moving_handle () { 1134 if (selected_handle.type == PointType.LINE_CUBIC) { 1135 selected_handle.set_point_type (PointType.CUBIC); 1136 } 1137 1138 if (selected_handle.type == PointType.LINE_QUADRATIC) { 1139 selected_handle.set_point_type (PointType.QUADRATIC); 1140 } 1141 1142 if (selected_handle.type == PointType.LINE_DOUBLE_CURVE) { 1143 selected_handle.set_point_type (PointType.DOUBLE_CURVE); 1144 } 1145 } 1146 1147 /** Set fill property to transparend for counter paths. */ 1148 public static void force_direction () { 1149 Glyph g = MainWindow.get_current_glyph (); 1150 1151 clear_directions (); 1152 1153 foreach (Path p in g.get_visible_paths ()) { 1154 if (!p.has_direction ()) { 1155 if (is_counter_path (p)) { 1156 p.force_direction (Direction.COUNTER_CLOCKWISE); 1157 } else { 1158 p.force_direction (Direction.CLOCKWISE); 1159 } 1160 } 1161 } 1162 1163 update_selected_points (); 1164 } 1165 1166 public static void clear_directions () { 1167 clockwise.clear (); 1168 counter_clockwise.clear (); 1169 } 1170 1171 public static bool is_counter_path (Path path) { 1172 Glyph g = MainWindow.get_current_glyph (); 1173 PathList pl = new PathList (); 1174 1175 foreach (Path p in g.get_visible_paths ()) { 1176 pl.add (p); 1177 } 1178 1179 return Path.is_counter (pl, path); 1180 } 1181 1182 public void remove_from_selected (EditPoint ep) 1183 requires (selected_points.size > 0) { 1184 1185 Gee.ArrayList<PointSelection> remove = new Gee.ArrayList<PointSelection> (); 1186 1187 foreach (PointSelection e in selected_points) { 1188 if (e.point.equals (e.point)) { 1189 remove.add (e); 1190 } 1191 } 1192 1193 foreach (PointSelection e in remove) { 1194 selected_points.remove (e); 1195 } 1196 } 1197 1198 public void select_active_point (double x, double y) { 1199 Glyph glyph = MainWindow.get_current_glyph (); 1200 bool reverse; 1201 1202 control_point_event (x, y); 1203 1204 // continue adding points from the other end of the selected path 1205 reverse = false; 1206 foreach (Path p in glyph.get_visible_paths ()) { 1207 if (p.is_open () && p.points.size >= 1 1208 && (active_edit_point == p.points.get (0) 1209 || active_edit_point == p.points.get (p.points.size - 1))) { 1210 update_selection (); 1211 reverse = true; 1212 control_point_event (x, y); 1213 break; 1214 } 1215 } 1216 1217 foreach (Path p in glyph.get_visible_paths ()) { 1218 if (p.is_open () && p.points.size > 1 && active_edit_point == p.points.get (0)) { 1219 p.reverse (); 1220 update_selection (); 1221 reverse = true; 1222 control_point_event (x, y); 1223 break; 1224 } 1225 } 1226 1227 if (active_edit_point == null && KeyBindings.modifier != SHIFT) { 1228 remove_all_selected_points (); 1229 } 1230 1231 move_selected = true; 1232 move_point_on_path = true; 1233 1234 if (active_edit_point != null) { 1235 glyph.clear_active_paths (); 1236 1237 PathObject path = new PathObject.for_path (active_path); 1238 glyph.add_active_object (null, path); 1239 1240 DrawingTools.update_stroke_settings (); 1241 1242 if (KeyBindings.modifier != SHIFT) { 1243 selected_point = (!)active_edit_point; 1244 1245 if (!((!)active_edit_point).is_selected ()) { 1246 remove_all_selected_points (); 1247 ((!)active_edit_point).set_selected (true); 1248 selected_point = (!)active_edit_point; 1249 add_selected_point (selected_point, active_path); // FIXME: double check active path 1250 last_selected_is_handle = false; 1251 } 1252 1253 // alt+click creates a point with symmetrical handles 1254 if (KeyBindings.has_alt () || KeyBindings.has_ctrl ()) { 1255 selected_point.set_reflective_handles (true); 1256 selected_point.process_symmetrical_handles (); 1257 GlyphCanvas.redraw (); 1258 } 1259 } 1260 } 1261 1262 if (reverse) { 1263 clockwise.clear (); 1264 counter_clockwise.clear (); 1265 } 1266 } 1267 1268 public static Path? find_path_to_join (EditPoint end_point) { 1269 Path? m = null; 1270 Glyph glyph = MainWindow.get_current_glyph (); 1271 EditPoint ep_last, ep_first; 1272 1273 foreach (Path path in glyph.get_visible_paths ()) { 1274 if (path.points.size == 0) { 1275 continue; 1276 } 1277 1278 ep_last = path.points.get (path.points.size - 1); 1279 ep_first = path.points.get (0); 1280 1281 if (end_point == ep_last) { 1282 m = path; 1283 break; 1284 } 1285 1286 if (end_point == ep_first) { 1287 m = path; 1288 break; 1289 } 1290 } 1291 1292 return m; 1293 } 1294 1295 public static Path merge_open_paths (Path path1, Path path2) { 1296 Path union, merge; 1297 EditPoint first_point; 1298 1299 union = path1.copy (); 1300 merge = path2.copy (); 1301 1302 return_val_if_fail (path1.points.size >= 1, merge); 1303 return_val_if_fail (path2.points.size >= 1, union); 1304 1305 merge.points.get (0).set_tie_handle (false); 1306 merge.points.get (0).set_reflective_handles (false); 1307 1308 merge.points.get (merge.points.size - 1).set_tie_handle (false); 1309 merge.points.get (merge.points.size - 1).set_reflective_handles (false); 1310 1311 union.points.get (union.points.size - 1).set_tie_handle (false); 1312 union.points.get (union.points.size - 1).set_reflective_handles (false); 1313 1314 union.points.get (0).set_tie_handle (false); 1315 union.points.get (0).set_reflective_handles (false); 1316 1317 first_point = merge.get_first_point (); 1318 1319 if (union.get_last_point ().get_left_handle ().is_curve ()) { 1320 first_point.get_left_handle ().convert_to_curve (); 1321 } else { 1322 if (first_point.type != PointType.QUADRATIC) { 1323 first_point.get_left_handle ().convert_to_line (); 1324 } 1325 } 1326 1327 if (first_point.type != PointType.QUADRATIC) { 1328 first_point.get_left_handle ().move_to_coordinate_internal ( 1329 union.get_last_point ().get_left_handle ().x, 1330 union.get_last_point ().get_left_handle ().y); 1331 } 1332 1333 union.delete_last_point (); 1334 1335 union.append_path (merge); 1336 1337 return union; 1338 } 1339 1340 public static void close_path (Path path) { 1341 bool last_segment_is_line; 1342 bool first_segment_is_line; 1343 1344 return_if_fail (path.points.size > 1); 1345 1346 last_segment_is_line = path.get_last_point ().get_left_handle ().is_line (); 1347 first_segment_is_line = path.get_first_point ().get_right_handle ().is_line (); 1348 1349 // TODO: set point type 1350 path.points.get (0).left_handle.move_to_coordinate ( 1351 path.points.get (path.points.size - 1).left_handle.x, 1352 path.points.get (path.points.size - 1).left_handle.y); 1353 1354 path.points.get (0).left_handle.type = 1355 path.points.get (path.points.size - 1).left_handle.type; 1356 1357 path.recalculate_linear_handles_for_point (path.points.get (0)); 1358 path.recalculate_linear_handles_for_point (path.points.get (path.points.size - 1)); 1359 1360 // force the connected handle to move 1361 path.points.get (0).set_position ( 1362 path.points.get (0).x, path.points.get (0).y); 1363 1364 path.points.remove_at (path.points.size - 1); 1365 1366 path.close (); 1367 1368 if (last_segment_is_line) { 1369 path.get_first_point ().get_left_handle ().convert_to_line (); 1370 path.recalculate_linear_handles_for_point (path.get_first_point ()); 1371 } 1372 1373 if (first_segment_is_line) { 1374 path.get_first_point ().get_right_handle ().convert_to_line (); 1375 path.recalculate_linear_handles_for_point (path.get_first_point ()); 1376 } 1377 } 1378 1379 /** @return the new path or null if no path could be merged with the end point. */ 1380 public static Path? join_paths (EditPoint end_point) { 1381 Glyph glyph = MainWindow.get_current_glyph (); 1382 Path? p; 1383 Path path; 1384 Path union; 1385 bool direction_changed = false; 1386 int px, py; 1387 1388 var paths = glyph.get_visible_paths (); 1389 1390 if (paths.size == 0) { 1391 warning ("No paths."); 1392 return null; 1393 } 1394 1395 p = find_path_to_join (end_point); 1396 if (p == null) { 1397 warning ("No path to join."); 1398 return null; 1399 } 1400 1401 path = (!) p; 1402 if (!path.is_open ()) { 1403 warning ("Path is closed."); 1404 return null; 1405 } 1406 1407 return_val_if_fail (path.points.size > 0, null); 1408 1409 px = Glyph.reverse_path_coordinate_x (end_point.x); 1410 py = Glyph.reverse_path_coordinate_y (end_point.y); 1411 1412 if (path.points.size == 1) { 1413 glyph.delete_path (path); 1414 glyph.clear_active_paths (); 1415 1416 foreach (Path merge in paths) { 1417 if (merge.points.size > 0) { 1418 PathObject merged_path = new PathObject.for_path (merge); 1419 1420 if (is_close_to_point (merge.points.get (merge.points.size - 1), px, py)) { 1421 glyph.add_active_object (null, merged_path); 1422 active_path = merge; 1423 merge.reopen (); 1424 glyph.open_path (); 1425 return merge; 1426 } 1427 1428 if (is_close_to_point (merge.points.get (0), px, py)) { 1429 glyph.add_active_object (null, merged_path); 1430 active_path = merge; 1431 clear_directions (); 1432 merge.reopen (); 1433 glyph.open_path (); 1434 merge.reverse (); 1435 return merge; 1436 } 1437 } 1438 } 1439 1440 warning ("No point to merge."); 1441 return null; 1442 } 1443 1444 if (active_edit_point == path.points.get (0)) { 1445 path.reverse (); 1446 update_selection (); 1447 path.recalculate_linear_handles (); 1448 direction_changed = true; 1449 active_edit_point = path.points.get (path.points.size - 1); 1450 active_path = path; 1451 } 1452 1453 if (path.points.get (0) == active_edit_point) { 1454 warning ("Wrong direction."); 1455 return null; 1456 } 1457 1458 // join path with it self 1459 if (is_close_to_point (path.points.get (0), px, py) && path.points.size > 2) { 1460 close_path (path); 1461 glyph.close_path (); 1462 force_direction (); 1463 1464 glyph.clear_active_paths (); 1465 1466 PathObject closed_path = new PathObject.for_path (path); 1467 glyph.add_active_object (null, closed_path); 1468 1469 if (direction_changed) { 1470 path.reverse (); 1471 update_selection (); 1472 } 1473 1474 remove_all_selected_points (); 1475 1476 return path; 1477 } 1478 1479 foreach (Path merge in paths) { 1480 // don't join path with itself here 1481 if (path == merge) { 1482 continue; 1483 } 1484 1485 // we need both start and end points 1486 if (merge.points.size <= 1 || path.points.size <= 1) { 1487 continue; 1488 } 1489 1490 if (is_close_to_point (merge.points.get (merge.points.size - 1), px, py)) { 1491 merge.reverse (); 1492 update_selection (); 1493 direction_changed = !direction_changed; 1494 } 1495 1496 return_val_if_fail (merge.points.size > 0, null); 1497 1498 if (is_close_to_point (merge.points.get (0), px, py)) { 1499 if (path.points.size == 1) { 1500 warning ("path.points.size == 1\n"); 1501 } else if (merge.points.size == 1) { 1502 warning ("merge.points.size == 1\n"); 1503 } else { 1504 union = merge_open_paths (path, merge); 1505 1506 PathObject union_path = new PathObject.for_path (union); 1507 glyph.add_path (union); 1508 glyph.delete_path (path); 1509 glyph.delete_path (merge); 1510 glyph.clear_active_paths (); 1511 glyph.add_active_object (null, union_path); 1512 1513 union.reopen (); 1514 union.create_list (); 1515 1516 force_direction (); 1517 1518 if (direction_changed) { 1519 path.reverse (); 1520 update_selection (); 1521 } 1522 1523 union.update_region_boundaries (); 1524 1525 return union; 1526 } 1527 } 1528 } 1529 1530 if (direction_changed) { 1531 path.reverse (); 1532 update_selection (); 1533 } 1534 1535 warning ("No paths merged."); 1536 return null; 1537 } 1538 1539 /** Merge paths if ends are close. */ 1540 public static bool is_close_to_point (EditPoint ep, double x, double y) { 1541 double px, py, distance; 1542 1543 px = Glyph.reverse_path_coordinate_x (ep.x); 1544 py = Glyph.reverse_path_coordinate_y (ep.y); 1545 1546 distance = sqrt (fabs (pow (px - x, 2)) + fabs (pow (py - y, 2))); 1547 1548 return (distance < 7 * MainWindow.units); 1549 } 1550 1551 /** Show the user that curves will be merged on release. */ 1552 public void draw_on_canvas (Context cr, Glyph glyph) { 1553 if (show_selection_box) { 1554 draw_selection_box (cr); 1555 } 1556 1557 if (point_selection_image) { 1558 draw_point_selection_circle (cr); 1559 } 1560 1561 draw_merge_icon (cr); 1562 } 1563 1564 /** Higlight the selected point on Android. */ 1565 void draw_point_selection_circle (Context cr) { 1566 PointSelection ps; 1567 1568 if (active_handle.active) { 1569 Path.draw_control_point (cr, Glyph.path_coordinate_x (begin_action_x), 1570 Glyph.path_coordinate_y (begin_action_y), Theme.get_color ("Active Handle")); 1571 } else if (selected_points.size > 0) { 1572 ps = selected_points.get (selected_points.size - 1); 1573 1574 if (ps.point.type == PointType.CUBIC) { 1575 Path.draw_control_point (cr, Glyph.path_coordinate_x (begin_action_x), 1576 Glyph.path_coordinate_y (begin_action_y), Theme.get_color ("Selected Cubic Control Point")); 1577 } else { 1578 Path.draw_control_point (cr, Glyph.path_coordinate_x (begin_action_x), 1579 Glyph.path_coordinate_y (begin_action_y), Theme.get_color ("Selected Quadratic Control Point")); 1580 } 1581 } 1582 } 1583 1584 void draw_selection_box (Context cr) { 1585 double x, y, w, h; 1586 1587 x = fmin (selection_box_x, selection_box_last_x); 1588 y = fmin (selection_box_y, selection_box_last_y); 1589 w = fmax (selection_box_x, selection_box_last_x) - x; 1590 h = fmax (selection_box_y, selection_box_last_y) - y; 1591 1592 cr.save (); 1593 Theme.color (cr, "Foreground 1"); 1594 cr.set_line_width (2); 1595 cr.rectangle (x, y, w, h); 1596 cr.stroke (); 1597 cr.restore (); 1598 } 1599 1600 public static void draw_join_icon (Context cr, double x, double y) { 1601 cr.save (); 1602 Theme.color (cr, "Merge"); 1603 cr.move_to (x, y); 1604 cr.arc (x, y, 15, 0, 2 * Math.PI); 1605 cr.close_path (); 1606 cr.fill (); 1607 cr.restore (); 1608 } 1609 1610 void draw_merge_icon (Context cr) { 1611 double x, y; 1612 if (active_edit_point != null) { 1613 get_tie_position ((!) active_edit_point, out x, out y); 1614 draw_join_icon (cr, x, y); 1615 } 1616 } 1617 1618 /** Obtain the position where to ends meet. */ 1619 static void get_tie_position (EditPoint current_point, out double x, out double y) { 1620 Glyph glyph; 1621 EditPoint active; 1622 double px, py; 1623 1624 x = -100; 1625 y = -100; 1626 1627 if (!is_endpoint (current_point)) { 1628 return; 1629 } 1630 1631 glyph = MainWindow.get_current_glyph (); 1632 active = current_point; 1633 1634 return_if_fail (!is_null (glyph)); 1635 1636 px = Glyph.reverse_path_coordinate_x (active.x); 1637 py = Glyph.reverse_path_coordinate_y (active.y); 1638 1639 foreach (Path path in glyph.get_visible_paths ()) { 1640 1641 if (!path.is_open ()) { 1642 continue; 1643 } 1644 1645 if (path.points.size == 0) { 1646 continue; 1647 } 1648 1649 foreach (EditPoint ep in path.points) { 1650 if (ep == active || !is_endpoint (ep)) { 1651 continue; 1652 } 1653 1654 if (is_close_to_point (ep, px, py)) { 1655 x = Glyph.reverse_path_coordinate_x (ep.x); 1656 y = Glyph.reverse_path_coordinate_y (ep.y); 1657 return; 1658 } 1659 } 1660 } 1661 } 1662 1663 public static bool is_endpoint (EditPoint ep) { 1664 EditPoint start; 1665 EditPoint end; 1666 Glyph glyph = MainWindow.get_current_glyph (); 1667 1668 foreach (Path path in glyph.get_visible_paths ()) { 1669 if (path.points.size < 1) { 1670 continue; 1671 } 1672 1673 start = path.points.get (0); 1674 end = path.points.get (path.points.size - 1); 1675 1676 if (ep == start || ep == end) { 1677 return true; 1678 } 1679 } 1680 1681 return false; 1682 } 1683 1684 public static void set_active_edit_point (EditPoint? e, Path path) { 1685 bool redraw; 1686 Glyph g = MainWindow.get_current_glyph (); 1687 1688 foreach (Path p in g.get_visible_paths ()) { 1689 foreach (var ep in p.points) { 1690 ep.set_active (false); 1691 } 1692 } 1693 1694 redraw = active_edit_point != e; 1695 active_edit_point = e; 1696 active_path = path; 1697 1698 if (e != null) { 1699 ((!)e).set_active (true); 1700 } 1701 1702 if (redraw) { 1703 GlyphCanvas.redraw (); 1704 } 1705 } 1706 1707 PointSelection? get_closest_point (double ex, double ey, out Path? path) { 1708 double x = Glyph.path_coordinate_x (ex); 1709 double y = Glyph.path_coordinate_y (ey); 1710 double d = double.MAX; 1711 double nd; 1712 PointSelection? ep = null; 1713 Glyph g = MainWindow.get_current_glyph (); 1714 1715 path = null; 1716 1717 foreach (Path current_path in g.get_paths_in_current_layer ()) { 1718 if (is_close_to_path (current_path, ex, ey)) { 1719 foreach (EditPoint e in current_path.points) { 1720 nd = e.get_distance (x, y); 1721 1722 if (nd < d) { 1723 d = nd; 1724 ep = new PointSelection (e, current_path); 1725 path = current_path; 1726 } 1727 } 1728 } 1729 } 1730 1731 return ep; 1732 } 1733 1734 public double get_distance_to_closest_edit_point (double event_x, double event_y) { 1735 Path? p; 1736 PointSelection e; 1737 PointSelection? ep = get_closest_point (event_x, event_y, out p); 1738 1739 double x = Glyph.path_coordinate_x (event_x); 1740 double y = Glyph.path_coordinate_y (event_y); 1741 1742 if (ep == null) { 1743 return double.MAX; 1744 } 1745 1746 e = (!) ep; 1747 1748 return e.point.get_distance (x, y); 1749 } 1750 1751 public void control_point_event (double event_x, double event_y, bool reset_active_point = true) { 1752 Path? p; 1753 PointSelection? ep = get_closest_point (event_x, event_y, out p); 1754 Glyph g = MainWindow.get_current_glyph (); 1755 double x = Glyph.path_coordinate_x (event_x); 1756 double y = Glyph.path_coordinate_y (event_y); 1757 double distance; 1758 PointSelection e; 1759 1760 if (reset_active_point) { 1761 set_active_edit_point (null, new Path ()); 1762 } 1763 1764 if (ep == null) { 1765 return; 1766 } 1767 1768 e = (!) ep; 1769 distance = e.point.get_distance (x, y) * g.view_zoom; 1770 1771 if (distance < contact_surface) { 1772 set_active_edit_point (e.point, e.path); 1773 } 1774 } 1775 1776 public PointSelection new_point_action (int x, int y) { 1777 Glyph glyph; 1778 PointSelection new_point; 1779 1780 glyph = MainWindow.get_current_glyph (); 1781 glyph.open_path (); 1782 1783 remove_all_selected_points (); 1784 1785 new_point = add_new_edit_point (x, y); 1786 new_point.point.set_selected (true); 1787 1788 selected_point = new_point.point; 1789 active_edit_point = new_point.point; 1790 1791 return_val_if_fail (glyph.active_paths.size > 0, new PointSelection.empty ()); 1792 Object object = glyph.active_paths.get (glyph.active_paths.size - 1); 1793 1794 if (object is PathObject) { 1795 Path path = ((PathObject) object).get_path (); 1796 1797 add_selected_point (selected_point, path); 1798 1799 active_path = new_point.path; 1800 glyph.clear_active_paths (); 1801 glyph.add_active_object (null, object); 1802 1803 move_selected = true; 1804 } 1805 1806 return new_point; 1807 } 1808 1809 public static PointSelection add_new_edit_point (int x, int y) { 1810 PointSelection new_point; 1811 1812 new_point = insert_edit_point (x, y); 1813 new_point.path.update_region_boundaries (); 1814 1815 if (new_point.path.is_open () && new_point.path.points.size > 0) { 1816 new_point.path.get_first_point ().set_reflective_handles (false); 1817 new_point.path.get_first_point ().set_tie_handle (false); 1818 new_point.path.get_last_point ().set_reflective_handles (false); 1819 new_point.path.get_last_point ().set_tie_handle (false); 1820 } 1821 1822 selected_point = new_point.point; 1823 active_edit_point = new_point.point; 1824 1825 set_point_type (selected_point); 1826 set_default_handle_positions (); 1827 1828 selected_points.clear (); 1829 add_selected_point (new_point.point, new_point.path); 1830 1831 return new_point; 1832 } 1833 1834 private static PointSelection insert_edit_point (double x, double y) { 1835 double xt, yt; 1836 Path np; 1837 EditPoint inserted; 1838 bool stroke = StrokeTool.add_stroke; 1839 Glyph g = MainWindow.get_current_glyph (); 1840 PathObject path; 1841 1842 if (g.active_paths.size == 0) { 1843 np = new Path (); 1844 g.add_path (np); 1845 np.stroke = stroke ? StrokeTool.stroke_width : 0; 1846 np.line_cap = StrokeTool.line_cap; 1847 1848 path = new PathObject.for_path (np); 1849 g.add_active_object (null, path); 1850 1851 active_path = np; 1852 selected_path = np; 1853 } 1854 1855 xt = Glyph.path_coordinate_x (x); 1856 yt = Glyph.path_coordinate_y (y); 1857 1858 if (selected_path.is_open ()) { 1859 np = PenTool.selected_path; 1860 np.add (xt, yt); 1861 } else { 1862 np = new Path (); 1863 np.stroke = stroke ? StrokeTool.stroke_width : 0; 1864 g.add_path (np); 1865 np.add (xt, yt); 1866 1867 if (DrawingTools.pen_tool.is_selected ()) { 1868 np.stroke = PenTool.path_stroke_width; 1869 } 1870 1871 PenTool.active_path = np; 1872 } 1873 1874 g.clear_active_paths (); 1875 path = new PathObject.for_path (np); 1876 g.add_active_object (null, path); 1877 active_path = np; 1878 selected_path = np; 1879 1880 inserted = np.points.get (np.points.size - 1); 1881 1882 return new PointSelection (inserted, np); 1883 } 1884 1885 static void set_point_type (EditPoint p) { 1886 if (p.prev != null && p.get_prev ().right_handle.type == PointType.QUADRATIC) { 1887 p.left_handle.type = PointType.QUADRATIC; 1888 p.right_handle.type = PointType.LINE_QUADRATIC; 1889 p.type = PointType.QUADRATIC; 1890 } else if (DrawingTools.get_selected_point_type () == PointType.QUADRATIC) { 1891 p.left_handle.type = PointType.LINE_QUADRATIC; 1892 p.right_handle.type = PointType.LINE_QUADRATIC; 1893 p.type = PointType.LINE_QUADRATIC; 1894 } else if (DrawingTools.get_selected_point_type () == PointType.DOUBLE_CURVE) { 1895 p.left_handle.type = PointType.LINE_DOUBLE_CURVE; 1896 p.right_handle.type = PointType.LINE_DOUBLE_CURVE; 1897 p.type = PointType.DOUBLE_CURVE; 1898 } else { 1899 p.left_handle.type = PointType.LINE_CUBIC; 1900 p.right_handle.type = PointType.LINE_CUBIC; 1901 p.type = PointType.CUBIC; 1902 } 1903 } 1904 1905 public static void set_default_handle_positions () { 1906 Glyph g = MainWindow.get_current_glyph (); 1907 foreach (var p in g.get_visible_paths ()) { 1908 if (p.is_editable ()) { 1909 p.create_list (); 1910 set_default_handle_positions_on_path (p); 1911 } 1912 } 1913 } 1914 1915 static void set_default_handle_positions_on_path (Path path) { 1916 foreach (EditPoint e in path.points) { 1917 if (!e.tie_handles && !e.reflective_point) { 1918 path.recalculate_linear_handles_for_point (e); 1919 } 1920 } 1921 } 1922 1923 private bool is_over_handle (double event_x, double event_y) { 1924 Glyph g = MainWindow.get_current_glyph (); 1925 double distance_to_edit_point = g.view_zoom * get_distance_to_closest_edit_point (event_x, event_y); 1926 1927 if (!Path.show_all_line_handles) { 1928 foreach (PointSelection selected_corner in selected_points) { 1929 if (is_close_to_handle (selected_corner.point, event_x, event_y, distance_to_edit_point)) { 1930 return true; 1931 } 1932 } 1933 } else { 1934 foreach (Path p in g.get_visible_paths ()) { 1935 foreach (EditPoint ep in p.points) { 1936 if (is_close_to_handle (ep, event_x, event_y, distance_to_edit_point)) { 1937 return true; 1938 } 1939 } 1940 } 1941 } 1942 1943 return false; 1944 } 1945 1946 bool is_close_to_path (Path p, double event_x, double event_y) { 1947 double c = contact_surface * Glyph.ivz (); 1948 double x = Glyph.path_coordinate_x (event_x); 1949 double y = Glyph.path_coordinate_y (event_y); 1950 1951 if (unlikely (!p.has_region_boundaries ())) { 1952 if (p.points.size > 0) { 1953 warning (@"No bounding box. $(p.points.size)"); 1954 p.update_region_boundaries (); 1955 } 1956 } 1957 1958 return p.xmin - c - 10 <= x <= p.xmax + c + 10 && p.ymin - c - 10 <= y <= p.ymax + c + 10 ; 1959 } 1960 1961 private bool is_close_to_handle (EditPoint selected_corner, double event_x, double event_y, double distance_to_edit_point) { 1962 double x = Glyph.path_coordinate_x (event_x); 1963 double y = Glyph.path_coordinate_y (event_y); 1964 Glyph g = MainWindow.get_current_glyph (); 1965 double d_point = distance_to_edit_point; 1966 double dl, dr; 1967 1968 dl = g.view_zoom * selected_corner.get_left_handle ().get_point ().get_distance (x, y); 1969 dr = g.view_zoom * selected_corner.get_right_handle ().get_point ().get_distance (x, y); 1970 1971 if (dl < contact_surface && dl < d_point) { 1972 return true; 1973 } 1974 1975 if (dr < contact_surface && dr < d_point) { 1976 return true; 1977 } 1978 1979 return false; 1980 } 1981 1982 PointSelection get_closest_handle (double event_x, double event_y) { 1983 EditPointHandle left, right; 1984 double x = Glyph.path_coordinate_x (event_x); 1985 double y = Glyph.path_coordinate_y (event_y); 1986 EditPointHandle eh = new EditPointHandle.empty(); 1987 Glyph g = MainWindow.get_current_glyph (); 1988 double d = double.MAX; 1989 double dn; 1990 Path path = new Path (); 1991 bool left_handle = false; 1992 EditPoint parent_point; 1993 EditPoint tied_point; 1994 1995 foreach (Path p in g.get_paths_in_current_layer ()) { 1996 foreach (EditPoint ep in p.points) { 1997 if (ep.is_selected () || Path.show_all_line_handles) { 1998 left = ep.get_left_handle (); 1999 right = ep.get_right_handle (); 2000 2001 dn = left.get_point ().get_distance (x, y); 2002 2003 if (dn < d) { 2004 eh = left; 2005 d = dn; 2006 path = p; 2007 left_handle = true; 2008 } 2009 2010 dn = right.get_point ().get_distance (x, y); 2011 2012 if (dn < d) { 2013 eh = right; 2014 d = dn; 2015 path = p; 2016 left_handle = false; 2017 } 2018 } 2019 } 2020 } 2021 2022 // Make sure the selected handle belongs to the selected point if 2023 // the current segment is quadratic. 2024 if (eh.type == PointType.QUADRATIC) { 2025 parent_point = eh.get_parent (); 2026 2027 if (left_handle) { 2028 if (parent_point.prev != null) { 2029 tied_point = parent_point.get_prev (); 2030 if (tied_point.selected_point) { 2031 eh = tied_point.get_right_handle (); 2032 } 2033 } 2034 } else { 2035 if (parent_point.next != null) { 2036 tied_point = parent_point.get_next (); 2037 if (tied_point.selected_point) { 2038 eh = tied_point.get_left_handle (); 2039 } 2040 } 2041 } 2042 } 2043 2044 return new PointSelection.handle_selection (eh, path); 2045 } 2046 2047 private void curve_active_corner_event (double event_x, double event_y) { 2048 PointSelection eh; 2049 Glyph glyph; 2050 2051 glyph = MainWindow.get_current_glyph (); 2052 active_handle.active = false; 2053 2054 if (!is_over_handle (event_x, event_y)) { 2055 return; 2056 } 2057 2058 eh = get_closest_handle (event_x, event_y); 2059 eh.handle.active = true; 2060 2061 if (active_handle != eh.handle) { 2062 GlyphCanvas.redraw (); 2063 } 2064 2065 active_handle = eh.handle; 2066 } 2067 2068 private void curve_corner_event (double event_x, double event_y) { 2069 Glyph g = MainWindow.get_current_glyph (); 2070 g.open_path (); 2071 PointSelection p; 2072 2073 if (!is_over_handle (event_x, event_y)) { 2074 return; 2075 } 2076 2077 move_selected_handle = true; 2078 last_selected_is_handle = true; 2079 selected_handle.selected = false; 2080 p = get_closest_handle (event_x, event_y); 2081 selected_handle = p.handle; 2082 handle_selection = p; 2083 selected_handle.selected = true; 2084 2085 active_path = p.path; 2086 PathObject path = new PathObject.for_path (active_path); 2087 g.add_active_object (null, path); 2088 } 2089 2090 public static void add_selected_point (EditPoint p, Path path) { 2091 bool in_path = false; 2092 2093 foreach (EditPoint e in path.points) { 2094 if (e == p) { 2095 in_path = true; 2096 break; 2097 } 2098 } 2099 2100 if (!in_path) { 2101 warning ("Point is not in path."); 2102 } 2103 2104 foreach (PointSelection ep in selected_points) { 2105 if (p == ep.point) { 2106 return; 2107 } 2108 } 2109 2110 selected_points.add (new PointSelection (p, path)); 2111 } 2112 2113 public static void remove_all_selected_points () { 2114 Glyph g = MainWindow.get_current_glyph (); 2115 2116 foreach (PointSelection ep in selected_points) { 2117 ep.point.set_active (false); 2118 ep.point.set_selected (false); 2119 } 2120 2121 selected_points.clear (); 2122 2123 foreach (Path p in g.get_visible_paths ()) { 2124 foreach (EditPoint e in p.points) { 2125 e.set_active (false); 2126 e.set_selected (false); 2127 } 2128 } 2129 } 2130 2131 static void move_select_next_point (uint keyval) { 2132 PointSelection next = new PointSelection.empty (); 2133 2134 if (selected_points.size == 0) { 2135 return; 2136 } 2137 2138 cancel_stroke_creator (); 2139 2140 switch (keyval) { 2141 case Key.UP: 2142 next = get_next_point_up (); 2143 break; 2144 case Key.DOWN: 2145 next = get_next_point_down (); 2146 break; 2147 case Key.LEFT: 2148 next = get_next_point_left (); 2149 break; 2150 case Key.RIGHT: 2151 next = get_next_point_right (); 2152 break; 2153 default: 2154 break; 2155 } 2156 2157 set_selected_point (next.point, next.path); 2158 2159 GlyphCanvas.redraw (); 2160 } 2161 2162 private static PointSelection get_next_point (double angle) 2163 requires (selected_points.size != 0) { 2164 PointSelection e = selected_points.get (selected_points.size - 1); 2165 double right_angle = e.point.right_handle.angle; 2166 double left_angle = e.point.left_handle.angle; 2167 double min_right, min_left; 2168 double min; 2169 2170 return_val_if_fail (e.point.next != null, new EditPoint ()); 2171 return_val_if_fail (e.point.prev != null, new EditPoint ()); 2172 2173 // angle might be greater than 2 PI or less than 0 2174 min_right = double.MAX; 2175 min_left = double.MAX; 2176 for (double i = -2 * PI; i <= 2 * PI; i += 2 * PI) { 2177 min = fabs (right_angle - (angle + i)); 2178 if (min < min_right) { 2179 min_right = min; 2180 } 2181 2182 min = fabs (left_angle - (angle + i)); 2183 if (min < min_left) { 2184 min_left = min; 2185 } 2186 } 2187 2188 if (min_right < min_left) { 2189 return new PointSelection (e.point.get_next (), e.path); 2190 } 2191 2192 return new PointSelection (e.point.get_prev (), e.path); 2193 } 2194 2195 private static PointSelection get_next_point_up () { 2196 return get_next_point (PI / 2); 2197 } 2198 2199 private static PointSelection get_next_point_down () { 2200 return get_next_point (PI + PI / 2); 2201 } 2202 2203 private static PointSelection get_next_point_left () { 2204 return get_next_point (PI); 2205 } 2206 2207 private static PointSelection get_next_point_right () { 2208 return get_next_point (0); 2209 } 2210 2211 private static void set_selected_point (EditPoint ep, Path p) { 2212 remove_all_selected_points (); 2213 add_selected_point (ep, p); 2214 set_active_edit_point (ep, p); 2215 edit_active_corner = true; 2216 ep.set_selected (true); 2217 set_default_handle_positions (); 2218 } 2219 2220 public static void select_point_up () { 2221 move_select_next_point (Key.UP); 2222 } 2223 2224 public static void select_point_down () { 2225 move_select_next_point (Key.DOWN); 2226 } 2227 2228 public static void select_point_right () { 2229 move_select_next_point (Key.RIGHT); 2230 } 2231 2232 public static void select_point_left () { 2233 move_select_next_point (Key.LEFT); 2234 } 2235 2236 /** 2237 * Move the selected editpoint one pixel with keyboard irrespectivly of 2238 * current zoom. 2239 */ 2240 void move_selected_points (uint keyval) { 2241 Path? last_path = null; 2242 2243 cancel_stroke_creator (); 2244 update_selected_points (); 2245 2246 if (!last_selected_is_handle) { 2247 if (keyval == Key.UP) { 2248 foreach (PointSelection e in selected_points) { 2249 e.point.set_position (e.point.x, e.point.y + Glyph.ivz ()); 2250 e.path.recalculate_linear_handles_for_point (e.point); 2251 } 2252 } 2253 2254 if (keyval == Key.DOWN) { 2255 foreach (PointSelection e in selected_points) { 2256 e.point.set_position (e.point.x, e.point.y - Glyph.ivz ()); 2257 e.path.recalculate_linear_handles_for_point (e.point); 2258 } 2259 } 2260 2261 if (keyval == Key.LEFT) { 2262 foreach (PointSelection e in selected_points) { 2263 e.point.set_position (e.point.x - Glyph.ivz (), e.point.y); 2264 e.path.recalculate_linear_handles_for_point (e.point); 2265 } 2266 } 2267 2268 if (keyval == Key.RIGHT) { 2269 foreach (PointSelection e in selected_points) { 2270 e.point.set_position (e.point.x + Glyph.ivz (), e.point.y); 2271 e.path.recalculate_linear_handles_for_point (e.point); 2272 } 2273 } 2274 2275 last_path = null; 2276 foreach (PointSelection e in selected_points) { 2277 if (e.path != last_path) { 2278 e.path.update_region_boundaries (); 2279 last_path = e.path; 2280 } 2281 } 2282 2283 } else { 2284 set_type_for_moving_handle (); 2285 active_handle.active = false; 2286 active_handle = new EditPointHandle.empty (); 2287 2288 if (keyval == Key.UP) { 2289 selected_handle.move_delta_coordinate (0, 1 * Glyph.ivz ()); 2290 } 2291 2292 if (keyval == Key.DOWN) { 2293 selected_handle.move_delta_coordinate (0, -1 * Glyph.ivz ()); 2294 } 2295 2296 if (keyval == Key.LEFT) { 2297 selected_handle.move_delta_coordinate (-1 * Glyph.ivz (), 0); 2298 } 2299 2300 if (keyval == Key.RIGHT) { 2301 selected_handle.move_delta_coordinate (1 * Glyph.ivz (), 0); 2302 } 2303 } 2304 2305 reset_stroke (); 2306 2307 // TODO: redraw only the relevant parts 2308 GlyphCanvas.redraw (); 2309 } 2310 2311 public static void convert_point_to_line (EditPoint ep, bool both) { 2312 ep.set_tie_handle (false); 2313 ep.set_reflective_handles (false); 2314 2315 if (ep.next == null) { 2316 // FIXME: write a new method for this case 2317 // warning ("Next is null."); 2318 } 2319 2320 if (ep.prev == null) { 2321 warning ("Prev is null."); 2322 } 2323 2324 if (ep.type == PointType.CUBIC || ep.type == PointType.LINE_CUBIC) { 2325 ep.type = PointType.LINE_CUBIC; 2326 2327 if (both) { 2328 ep.get_left_handle ().type = PointType.LINE_CUBIC; 2329 ep.get_right_handle ().type = PointType.LINE_CUBIC; 2330 } 2331 2332 if (ep.next != null && ep.get_next ().is_selected ()) { 2333 ep.get_right_handle ().type = PointType.LINE_CUBIC; 2334 } 2335 2336 if (ep.prev != null && ep.get_prev ().is_selected ()) { 2337 ep.get_left_handle ().type = PointType.LINE_CUBIC; 2338 } 2339 2340 } 2341 2342 if (ep.type == PointType.DOUBLE_CURVE| ep.type == PointType.LINE_DOUBLE_CURVE) { 2343 ep.type = PointType.LINE_DOUBLE_CURVE; 2344 if (both) { 2345 ep.get_left_handle ().type = PointType.LINE_DOUBLE_CURVE; 2346 ep.get_right_handle ().type = PointType.LINE_DOUBLE_CURVE; 2347 } 2348 2349 if (ep.next != null && ep.get_next ().is_selected ()) { 2350 ep.get_right_handle ().type = PointType.LINE_DOUBLE_CURVE; 2351 } 2352 2353 if (ep.prev != null && ep.get_prev ().is_selected ()) { 2354 ep.get_left_handle ().type = PointType.LINE_DOUBLE_CURVE; 2355 } 2356 } 2357 2358 if (ep.type == PointType.QUADRATIC || ep.type == PointType.LINE_QUADRATIC) { 2359 ep.type = PointType.LINE_QUADRATIC; 2360 2361 if (both) { 2362 ep.get_left_handle ().type = PointType.LINE_QUADRATIC; 2363 ep.get_right_handle ().type = PointType.LINE_QUADRATIC; 2364 2365 if (ep.next != null) { 2366 ep.get_next ().get_left_handle ().type = PointType.LINE_QUADRATIC; 2367 } 2368 2369 if (ep.prev != null) { 2370 ep.get_prev ().get_right_handle ().type = PointType.LINE_QUADRATIC; 2371 } 2372 } 2373 2374 if (ep.next != null && ep.get_next ().is_selected ()) { 2375 ep.get_right_handle ().type = PointType.LINE_QUADRATIC; 2376 ep.get_next ().get_left_handle ().type = PointType.LINE_QUADRATIC; 2377 } 2378 2379 if (ep.prev != null && ep.get_prev ().is_selected ()) { 2380 ep.get_left_handle ().type = PointType.LINE_QUADRATIC; 2381 ep.get_prev ().get_right_handle ().type = PointType.LINE_QUADRATIC; 2382 } 2383 2384 } 2385 2386 //FIXME: ep.recalculate_linear_handles (); 2387 } 2388 2389 public static void convert_segment_to_line () { 2390 if (selected_points.size == 0) { 2391 return; 2392 } 2393 2394 if (selected_points.size == 1) { 2395 convert_point_to_line (selected_points.get (0).point, true); 2396 } else { 2397 foreach (PointSelection p in selected_points) { 2398 convert_point_to_line (p.point, false); 2399 } 2400 } 2401 2402 foreach (PointSelection p in selected_points) { 2403 p.path.recalculate_linear_handles_for_point (p.point); 2404 } 2405 } 2406 2407 public static bool is_line (PointType t) { 2408 return t == PointType.LINE_QUADRATIC 2409 || t == PointType.LINE_DOUBLE_CURVE 2410 || t == PointType.LINE_CUBIC; 2411 } 2412 2413 public static PointType to_line (PointType t) { 2414 switch (t) { 2415 case PointType.QUADRATIC: 2416 return PointType.LINE_QUADRATIC; 2417 case PointType.DOUBLE_CURVE: 2418 return PointType.LINE_DOUBLE_CURVE; 2419 case PointType.CUBIC: 2420 return PointType.LINE_CUBIC; 2421 default: 2422 break; 2423 } 2424 return t; 2425 } 2426 2427 public static PointType to_curve (PointType t) { 2428 switch (t) { 2429 case PointType.LINE_QUADRATIC: 2430 return PointType.QUADRATIC; 2431 case PointType.LINE_DOUBLE_CURVE: 2432 return PointType.DOUBLE_CURVE; 2433 case PointType.LINE_CUBIC: 2434 return PointType.CUBIC; 2435 default: 2436 break; 2437 } 2438 2439 if (unlikely (t == PointType.NONE)) { 2440 warning ("Type is not set."); 2441 } 2442 2443 return t; 2444 } 2445 2446 public static void set_converted_handle_length (EditPointHandle e, PointType pt) { 2447 2448 if (e.type == PointType.QUADRATIC && pt == PointType.DOUBLE_CURVE) { 2449 e.length *= 2; 2450 e.length /= 4; 2451 } 2452 2453 if (e.type == PointType.QUADRATIC && pt == PointType.CUBIC) { 2454 e.length *= 2; 2455 e.length /= 3; 2456 } 2457 2458 if (e.type == PointType.DOUBLE_CURVE && pt == PointType.QUADRATIC) { 2459 e.length *= 4; 2460 e.length /= 2; 2461 } 2462 2463 if (e.type == PointType.DOUBLE_CURVE && pt == PointType.CUBIC) { 2464 e.length *= 4; 2465 e.length /= 3; 2466 } 2467 2468 if (e.type == PointType.CUBIC && pt == PointType.QUADRATIC) { 2469 e.length *= 3; 2470 e.length /= 2; 2471 } 2472 2473 if (e.type == PointType.CUBIC && pt == PointType.DOUBLE_CURVE) { 2474 e.length *= 3; 2475 e.length /= 4; 2476 } 2477 } 2478 2479 public static void convert_point_segment_type (EditPoint first, EditPoint next, PointType point_type) { 2480 bool line; 2481 2482 set_converted_handle_length (first.get_right_handle (), point_type); 2483 set_converted_handle_length (next.get_left_handle (), point_type); 2484 2485 line = is_line (first.type) 2486 && is_line (first.get_right_handle ().type) 2487 && is_line (next.get_left_handle ().type); 2488 2489 if (!line) { 2490 first.type = point_type; 2491 } else { 2492 first.type = to_line (point_type); 2493 } 2494 2495 if (!line) { 2496 first.get_right_handle ().type = point_type; 2497 } else { 2498 first.get_right_handle ().type = to_line (point_type); 2499 } 2500 2501 if (!line) { 2502 next.get_left_handle ().type = point_type; 2503 } else { 2504 next.get_left_handle ().type = to_line (point_type); 2505 } 2506 2507 // process connected handle 2508 if (point_type == PointType.QUADRATIC) { 2509 first.set_position (first.x, first.y); 2510 // FIXME: linear handles 2511 } 2512 } 2513 2514 public static void convert_point_type (EditPoint first, PointType point_type) { 2515 convert_point_segment_type (first, first.get_next (), point_type); 2516 } 2517 2518 public static void convert_point_types () { 2519 Glyph glyph = MainWindow.get_current_glyph (); 2520 glyph.store_undo_state (); 2521 PointSelection selected = new PointSelection.empty (); 2522 bool reset_selected = false; 2523 EditPoint e; 2524 2525 if (selected_points.size == 1) { 2526 selected = selected_points.get (0); 2527 if (selected.point.next != null) { 2528 selected_points.add (new PointSelection (selected.point.get_next (), selected.path)); 2529 selected.point.get_next ().set_selected (true); 2530 } 2531 2532 if (selected.point.prev != null) { 2533 selected_points.add (new PointSelection (selected.point.get_prev (), selected.path)); 2534 selected.point.get_next ().set_selected (true); 2535 } 2536 2537 reset_selected = true; 2538 } 2539 2540 foreach (PointSelection ps in selected_points) { 2541 e = ps.point; 2542 2543 // convert segments not control points 2544 if (e.next == null || !e.get_next ().is_selected ()) { 2545 continue; 2546 } 2547 2548 convert_point_type (e, DrawingTools.point_type); 2549 ps.path.recalculate_linear_handles_for_point (ps.point); 2550 } 2551 2552 if (reset_selected) { 2553 remove_all_selected_points (); 2554 selected_points.add (selected); 2555 selected.point.set_selected (true); 2556 } 2557 2558 foreach (Path p in glyph.get_visible_paths ()) { 2559 p.update_region_boundaries (); 2560 } 2561 } 2562 2563 public static void update_selected_points () { 2564 Glyph g = MainWindow.get_current_glyph (); 2565 selected_points.clear (); 2566 2567 foreach (Path p in g.get_visible_paths ()) { 2568 foreach (EditPoint ep in p.points) { 2569 if (ep.is_selected ()) { 2570 selected_points.add (new PointSelection (ep, p)); 2571 } 2572 } 2573 } 2574 } 2575 2576 public void select_all_points () { 2577 Glyph g = MainWindow.get_current_glyph (); 2578 2579 foreach (Path p in g.get_visible_paths ()) { 2580 foreach (EditPoint ep in p.points) { 2581 ep.set_selected (true); 2582 add_selected_point (ep, p); 2583 } 2584 } 2585 } 2586 2587 public static Path simplify (Path path, bool selected_segments = false, double threshold = 0.3) { 2588 PointSelection ps; 2589 EditPoint ep; 2590 Path p1, new_path; 2591 double d, sumd; 2592 int i; 2593 2594 p1 = path.copy (); 2595 new_path = p1.copy (); 2596 i = 0; 2597 sumd = 0; 2598 while (i < new_path.points.size) { 2599 ep = new_path.points.get (i); 2600 ps = new PointSelection (ep, new_path); 2601 d = PenTool.remove_point_simplify (ps); 2602 sumd += d; 2603 2604 if (sumd < threshold) { 2605 p1 = new_path.copy (); 2606 } else { 2607 new_path = p1.copy (); 2608 sumd = 0; 2609 i++; 2610 } 2611 } 2612 2613 new_path.update_region_boundaries (); 2614 2615 return new_path; 2616 } 2617 2618 public void set_simplification_threshold (double t) { 2619 simplification_threshold = t; 2620 } 2621 2622 public static void move_handle_on_axis () { 2623 on_axis = true; 2624 } 2625 } 2626 2627 } 2628