The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

Glyph.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/Glyph.vala.
Merge ../birdfont-2.x
1 /* 2 Copyright (C) 2012 - 2016 Johan Mattsson 3 4 This library is free software; you can redistribute it and/or modify 5 it under the terms of the GNU Lesser General Public License as 6 published by the Free Software Foundation; either version 3 of the 7 License, or (at your option) any later version. 8 9 This library is distributed in the hope that it will be useful, but 10 WITHOUT ANY WARRANTY; without even the implied warranty of 11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 Lesser General Public License for more details. 13 */ 14 15 using Cairo; 16 using Math; 17 using Gee; 18 19 namespace BirdFont { 20 21 public class Glyph : FontDisplay { 22 23 // Background image 24 BackgroundImage? background_image = null; 25 bool background_image_visible = true; 26 27 // Glyph zoom level 28 public double view_zoom = 0.1; 29 30 public double view_offset_x = 0; 31 public double view_offset_y = 0; 32 Gee.ArrayList<ZoomView> zoom_list = new Gee.ArrayList<ZoomView> (); 33 int zoom_list_index = 0; 34 35 // The point where edit event begun 36 double pointer_begin_x = 0; 37 double pointer_begin_y = 0; 38 39 // Tap last tap event position in pixels 40 int last_tap0_y = 0; 41 int last_tap0_x = 0; 42 int last_tap1_y = 0; 43 int last_tap1_x = 0; 44 double zoom_distance = 0; 45 bool change_view; 46 47 bool ignore_input = false; 48 49 // Current pointer position 50 public double motion_x = 0; 51 public double motion_y = 0; 52 53 // Zoom area 54 public double zoom_x1 = 0; 55 public double zoom_y1 = 0; 56 public double zoom_x2 = 0; 57 public double zoom_y2 = 0; 58 public bool zoom_area_is_visible = false; 59 60 bool view_is_moving = false; 61 public double move_offset_x = 0; 62 public double move_offset_y = 0; 63 bool move_canvas = false; 64 65 public WidgetAllocation allocation = new WidgetAllocation (); 66 67 // FIXME: name and unichar should be moved to to glyph collection 68 public unichar unichar_code = 0; 69 public string name; 70 71 public double left_limit { 72 get { 73 return _left_limit; 74 } 75 76 set { 77 ttf_data = null; 78 _left_limit = value; 79 } 80 } 81 82 public double right_limit { 83 get { 84 return _right_limit; 85 } 86 87 set { 88 ttf_data = null; 89 _right_limit = value; 90 } 91 } 92 93 private double _right_limit = 0; 94 private double _left_limit = 0; 95 96 // x-height, lsb, etc. 97 public Gee.ArrayList<Line> vertical_help_lines = new Gee.ArrayList<Line> (); 98 public Gee.ArrayList<Line> horizontal_help_lines = new Gee.ArrayList<Line> (); 99 public bool show_help_lines = true; 100 bool xheight_lines_visible = false; 101 bool margin_boundaries_visible = false; 102 string new_guide_name = ""; 103 104 Gee.ArrayList<Glyph> undo_list = new Gee.ArrayList<Glyph> (); 105 Gee.ArrayList<Glyph> redo_list = new Gee.ArrayList<Glyph> (); 106 107 string glyph_sequence = ""; 108 bool open = true; 109 110 public static Glyph? background_glyph = null; 111 112 bool empty = false; 113 114 /** Id in the version list. */ 115 public int version_id = 0; 116 117 /** Cache quadratic form on export. */ 118 GlyfData? ttf_data = null; 119 120 Line left_line; 121 Line right_line; 122 123 /** Cache for Cairo surface rendering */ 124 HashMap<string, Surface> glyph_cache = new HashMap<string, Surface> (); 125 126 public const double CANVAS_MIN = -10000; 127 public const double CANVAS_MAX = 10000; 128 129 public static bool show_orientation_arrow = false; 130 public static double orientation_arrow_opacity = 1; 131 132 public Layer layers = new Layer (); 133 public int current_layer = 0; 134 public Gee.ArrayList<Path> active_paths = new Gee.ArrayList<Path> (); 135 public Gee.ArrayList<Layer> selected_groups = new Gee.ArrayList<Layer> (); 136 137 // used if this glyph is fetched from a fallback font 138 public double top_limit = 0; 139 public double baseline = 0; 140 public double bottom_limit = 0; 141 142 public Surface? overview_thumbnail = null; 143 144 public Glyph (string name, unichar unichar_code = 0) { 145 this.name = name; 146 this.unichar_code = unichar_code; 147 148 add_help_lines (); 149 150 left_limit = -28; 151 right_limit = 28; 152 } 153 154 public Glyph.no_lines (string name, unichar unichar_code = 0) { 155 this.name = name; 156 this.unichar_code = unichar_code; 157 } 158 159 public Gee.ArrayList<Path> get_active_paths () { 160 return active_paths; 161 } 162 163 public Layer get_current_layer () { 164 return_val_if_fail (0 <= current_layer < layers.subgroups.size, new Layer ()); 165 return layers.subgroups.get (current_layer); 166 } 167 168 public void set_current_layer (Layer layer) { 169 int i = 0; 170 foreach (Layer l in layers.subgroups) { 171 if (likely (l == layer)) { 172 current_layer = i; 173 return; 174 } 175 i++; 176 } 177 178 warning ("Layer is not added to glyph."); 179 } 180 181 public Gee.ArrayList<Path> get_visible_paths () { 182 return layers.get_visible_paths ().paths; 183 } 184 185 public PathList get_visible_path_list () { 186 return layers.get_visible_paths (); 187 } 188 189 public Gee.ArrayList<Path> get_paths_in_current_layer () { 190 return get_current_layer ().get_all_paths ().paths; 191 } 192 193 public Gee.ArrayList<Path> get_all_paths () { 194 return layers.get_all_paths ().paths; 195 } 196 197 public void add_new_layer () { 198 layers.add_layer (new Layer ()); 199 current_layer = layers.subgroups.size - 1; 200 } 201 202 public int get_layer_index (Layer layer) { 203 return layers.index_of (layer); 204 } 205 206 public GlyfData get_ttf_data () { 207 if (ttf_data == null) { 208 209 ttf_data = new GlyfData (this); 210 } 211 212 return (!) ttf_data; 213 } 214 215 public PathList get_quadratic_paths () { 216 PointConverter pc; 217 PathList pl; 218 PathList stroke; 219 220 pl = new PathList (); 221 222 foreach (Path p in get_visible_paths ()) { 223 if (p.stroke > 0) { 224 stroke = p.get_completed_stroke (); 225 foreach (Path stroke_part in stroke.paths) { 226 pc = new PointConverter (stroke_part); 227 pl.add (pc.get_quadratic_path ()); 228 } 229 } else { 230 pc = new PointConverter (p); 231 pl.add (pc.get_quadratic_path ()); 232 } 233 } 234 235 return pl; 236 } 237 238 public override void close () { 239 undo_list.clear (); 240 redo_list.clear (); 241 } 242 243 public void set_empty_ttf (bool e) { 244 empty = e; 245 } 246 247 public bool is_empty_ttf () { 248 return empty; 249 } 250 251 public void clear_active_paths () { 252 selected_groups.clear (); 253 active_paths.clear (); 254 } 255 256 public void add_active_path (Layer? group, Path? p) { 257 Path path; 258 Layer g; 259 260 if (p != null) { 261 path = (!) p; 262 263 if (Toolbox.get_move_tool ().is_selected ()) { 264 if (path.stroke > 0) { 265 Toolbox.set_object_stroke (path.stroke); 266 } 267 } 268 269 if (!active_paths.contains (path)) { 270 active_paths.add (path); 271 } 272 PenTool.active_path = path; 273 } 274 275 if (group != null) { 276 g = (!) group; 277 if (!selected_groups.contains (g)) { 278 selected_groups.add (g); 279 } 280 } 281 } 282 283 public void delete_background () { 284 store_undo_state (); 285 background_image = null; 286 GlyphCanvas.redraw (); 287 } 288 289 public Path? get_active_path () { 290 return_val_if_fail (active_paths.size > 0, null); 291 return active_paths.get (active_paths.size - 1); 292 } 293 294 public bool boundaries (out double x1, out double y1, out double x2, out double y2) { 295 var paths = get_all_paths (); 296 297 if (paths.size == 0) { 298 x1 = 0; 299 y1 = 0; 300 x2 = 0; 301 y2 = 0; 302 return false; 303 } 304 305 x1 = CANVAS_MAX; 306 x2 = CANVAS_MIN; 307 y1 = CANVAS_MAX; 308 y2 = CANVAS_MIN; 309 310 foreach (Path p in paths) { 311 p.update_region_boundaries (); 312 313 if (p.points.size > 1) { 314 if (p.xmin < x1) { 315 x1 = p.xmin; 316 } 317 318 if (p.xmax > x2) { 319 x2 = p.xmax; 320 } 321 322 if (p.ymin < y1) { 323 y1 = p.ymin; 324 } 325 326 if (p.ymax > y2) { 327 y2 = p.ymax; 328 } 329 } 330 } 331 332 return x1 != double.MAX; 333 } 334 335 public void selection_boundaries (out double x, out double y, out double w, out double h) { 336 double px, py, px2, py2; 337 338 px = 10000; 339 py = 10000; 340 px2 = -10000; 341 py2 = -10000; 342 343 foreach (Path p in active_paths) { 344 if (p.xmin < px) { 345 px = p.xmin; 346 } 347 348 if (p.ymin < py) { 349 py = p.ymin; 350 } 351 352 if (p.xmax > px2) { 353 px2 = p.xmax; 354 } 355 356 if (p.ymax > py2) { 357 py2 = p.ymax; 358 } 359 } 360 361 if (px2 == -10000 || px == 10000) { 362 warning (@"No box for selected paths. ($(active_paths.size))"); 363 px = 0; 364 py = 0; 365 px2 = 0; 366 py2 = 0; 367 } 368 369 x = px; 370 y = py2; 371 w = px2 - px; 372 h = py2 - py; 373 } 374 375 /** @return centrum pixel for x coordinates. */ 376 public static double xc () { 377 double c = MainWindow.get_current_glyph ().allocation.width / 2.0; 378 return c; 379 } 380 381 /** @return centrum pixel for y coordinates. */ 382 public static double yc () { 383 double c = MainWindow.get_current_glyph ().allocation.height / 2.0; 384 return c; 385 } 386 387 /** @return 1/view_zoom */ 388 public static double ivz () { 389 return 1 / MainWindow.get_current_glyph ().view_zoom; 390 } 391 392 public void resized (WidgetAllocation alloc) { 393 double a, b, c, d; 394 395 a = Glyph.path_coordinate_x (0); 396 b = Glyph.path_coordinate_y (0); 397 398 this.allocation = alloc; 399 400 c = Glyph.path_coordinate_x (0); 401 d = Glyph.path_coordinate_y (0); 402 403 view_offset_x -= c - a; 404 view_offset_y -= b - d; 405 } 406 407 public void set_background_image (BackgroundImage? b) { 408 BackgroundImage bg; 409 410 if (b == null) { 411 background_image = null; 412 } else { 413 bg = (!) b; 414 background_image = bg; 415 } 416 417 BirdFont.get_current_font ().touch (); 418 } 419 420 public BackgroundImage? get_background_image () { 421 return background_image; 422 } 423 424 public override void scroll_wheel (double x, double y, 425 double pixeldelta_x, double pixeldelta_y) { 426 427 if (KeyBindings.has_alt () || KeyBindings.has_ctrl ()) { 428 if (pixeldelta_y > 0) { 429 zoom_in_at_point (x, y, pixeldelta_y); 430 } else { 431 zoom_out_at_point (x, y, pixeldelta_y); 432 } 433 } else { 434 if (!KeyBindings.has_shift ()) { 435 view_offset_x -= pixeldelta_x / view_zoom; 436 view_offset_y -= pixeldelta_y / view_zoom; 437 } else { 438 // move canvas a long x axis instead of y 439 view_offset_x -= pixeldelta_y / view_zoom; 440 view_offset_y -= pixeldelta_x / view_zoom; 441 } 442 } 443 444 redraw_area (0, 0, allocation.width, allocation.height); 445 } 446 447 public virtual void add_path (Path p) { 448 if (layers.subgroups.size == 0) { 449 layers.add_layer (new Layer ()); 450 } 451 452 get_current_layer ().add_path (p); 453 } 454 455 public override void selected_canvas () { 456 TimeoutSource input_delay; 457 458 ttf_data = null; // recreate quatradic path on export 459 overview_thumbnail = null; 460 461 ignore_input = true; // make sure that tripple clicks in overview are ignored 462 463 input_delay = new TimeoutSource (250); 464 input_delay.set_callback(() => { 465 ignore_input = false; 466 return false; 467 }); 468 input_delay.attach (null); 469 470 add_help_lines (); 471 KeyBindings.set_require_modifier (false); 472 glyph_sequence = Preferences.get ("glyph_sequence"); 473 474 GridTool.update_lines (); 475 476 if (!is_null (MainWindow.native_window)) { 477 MainWindow.native_window.set_scrollbar_size (0); 478 } 479 480 update_zoom_bar (); 481 482 Font font = BirdFont.get_current_font (); 483 string index = font.settings.get_setting (@"Active Layer $(get_name ())"); 484 485 if (index != "") { 486 int i = int.parse (index); 487 488 if (0 <= i < layers.subgroups.size) { 489 current_layer = i; 490 } 491 } 492 493 DrawingTools.update_layers (); 494 MainWindow.get_toolbox ().update_expanders (); 495 496 Tool current_tool = MainWindow.get_toolbox ().get_current_tool (); 497 current_tool.select_action (current_tool); 498 } 499 500 void update_zoom_bar () { 501 if (!is_null (Toolbox.drawing_tools) 502 && !is_null (Toolbox.drawing_tools.zoom_bar)) { 503 Toolbox.drawing_tools.zoom_bar.set_zoom ((view_zoom - 1) / 20); 504 } 505 } 506 507 public void remove_lines () { 508 vertical_help_lines.clear (); 509 horizontal_help_lines.clear (); 510 } 511 512 public void add_help_lines () { 513 remove_lines (); 514 515 return_if_fail (!is_null (BirdFont.get_current_font ())); 516 517 double bgt = BirdFont.get_current_font ().top_limit; 518 Line top_margin_line = new Line ("top margin", t_("top margin"), bgt, false); 519 top_margin_line.set_color_theme ("Guide 2"); 520 top_margin_line.position_updated.connect ((pos) => { 521 BirdFont.get_current_font ().top_limit = pos; 522 }); 523 524 double thp = BirdFont.get_current_font ().top_position; 525 Line top_line = new Line ("top", t_("top"), thp, false); 526 top_line.position_updated.connect ((pos) => { 527 Font f = BirdFont.get_current_font (); 528 f.top_position = pos; 529 }); 530 531 double xhp = BirdFont.get_current_font ().xheight_position; 532 Line xheight_line = new Line ("x-height", t_("x-height"), xhp, false); 533 xheight_line.set_color_theme ("Guide 3"); 534 xheight_line.dashed = true; 535 xheight_line.position_updated.connect ((pos) => { 536 Font f = BirdFont.get_current_font (); 537 f.xheight_position = pos; 538 }); 539 540 double xbl = BirdFont.get_current_font ().base_line; 541 Line base_line = new Line ("baseline", t_("baseline"), xbl, false); 542 base_line.position_updated.connect ((pos) => { 543 Font f = BirdFont.get_current_font (); 544 f.base_line = pos; 545 }); 546 547 double bp = BirdFont.get_current_font ().bottom_position; 548 Line bottom_line = new Line ("bottom", t_("bottom"), bp, false); 549 bottom_line.position_updated.connect ((pos) => { 550 BirdFont.get_current_font ().bottom_position = pos; 551 }); 552 553 double bgb = BirdFont.get_current_font ().bottom_limit; 554 Line bottom_margin_line = new Line ("bottom margin", t_("bottom margin"), bgb, false); 555 bottom_margin_line.set_color_theme ("Guide 2"); 556 bottom_margin_line.position_updated.connect ((pos) => { 557 BirdFont.get_current_font ().bottom_limit = pos; 558 }); 559 560 left_line = new Line ("left", t_("left"), left_limit, true); 561 left_line.lsb = true; 562 left_line.position_updated.connect ((pos) => { 563 left_limit = pos; 564 update_other_spacing_classes (); 565 left_line.set_metrics (get_left_side_bearing ()); 566 }); 567 left_line.set_metrics (get_left_side_bearing ()); 568 569 right_line = new Line ("right", t_("right"), right_limit, true); 570 right_line.rsb = true; 571 right_line.position_updated.connect ((pos) => { 572 right_limit = pos; 573 update_other_spacing_classes (); 574 right_line.set_metrics (get_right_side_bearing ()); 575 }); 576 right_line.set_metrics (get_right_side_bearing ()); 577 578 // lists of lines are sorted and lines are added only if 579 // they are relevant for a particular glyph. 580 581 // left to right 582 add_line (left_line); 583 add_line (right_line); 584 585 bool glyph_has_top = has_top_line (); 586 587 // top to bottom 588 add_line (top_margin_line); 589 top_margin_line.set_visible (margin_boundaries_visible); 590 591 add_line (top_line); 592 top_line.set_visible (glyph_has_top || xheight_lines_visible); 593 594 add_line (xheight_line); 595 xheight_line.set_visible (!glyph_has_top || xheight_lines_visible); 596 597 add_line (base_line); 598 599 add_line (bottom_line); 600 bottom_line.set_visible (CharDatabase.has_descender (unichar_code) || xheight_lines_visible); 601 602 add_line (bottom_margin_line); 603 bottom_margin_line.set_visible (margin_boundaries_visible); 604 605 foreach (Line guide in BirdFont.get_current_font ().custom_guides) { 606 add_line (guide); 607 } 608 } 609 610 public double get_left_side_bearing () { 611 double x1, y1, x2, y2; 612 613 if (get_boundaries (out x1, out y1, out x2, out y2)) { 614 return x1 - left_limit; 615 } else { 616 return right_limit - left_limit; 617 } 618 } 619 620 public double get_right_side_bearing () { 621 double x1, y1, x2, y2; 622 623 if (get_boundaries (out x1, out y1, out x2, out y2)) { 624 return right_limit - x2; 625 } else { 626 return right_limit - left_limit; 627 } 628 } 629 630 public bool get_boundaries (out double x1, out double y1, 631 out double x2, out double y2) { 632 633 double max_x, min_x, max_y, min_y; 634 PathList pl; 635 var paths = get_all_paths (); 636 637 if (paths.size == 0) { 638 x1 = 0; 639 y1 = 0; 640 x2 = 0; 641 y2 = 0; 642 return false; 643 } 644 645 max_x = CANVAS_MIN; 646 min_x = CANVAS_MAX; 647 max_y = CANVAS_MIN; 648 min_y = CANVAS_MAX; 649 650 // FIXME: optimize 651 foreach (Path p in paths) { 652 653 if (p.stroke > 0) { 654 pl = p.get_stroke_fast (); 655 656 foreach (Path part in pl.paths) { 657 boundaries_for_path (part, ref min_x, ref max_x, ref min_y, ref max_y); 658 } 659 } else { 660 boundaries_for_path (p, ref min_x, ref max_x, ref min_y, ref max_y); 661 } 662 } 663 664 x1 = min_x; 665 y1 = max_y; 666 x2 = max_x; 667 y2 = min_y; 668 669 return max_x != CANVAS_MIN; 670 } 671 672 void boundaries_for_path (Path p, ref double min_x, ref double max_x, 673 ref double min_y, ref double max_y) { 674 675 double max_x2, max_y2, min_x2, min_y2; 676 677 max_x2 = max_x; 678 min_x2 = min_x; 679 max_y2 = max_y; 680 min_y2 = min_y; 681 682 p.all_of_path ((x, y, t) => { 683 if (x > max_x2) { 684 max_x2 = x; 685 } 686 687 if (y > max_y2) { 688 max_y2 = y; 689 } 690 691 if (x < min_x2) { 692 min_x2 = x; 693 } 694 695 if (y < min_y2) { 696 min_y2 = y; 697 } 698 699 return true; 700 }); 701 702 max_x = max_x2; 703 min_x = min_x2; 704 max_y = max_y2; 705 min_y = min_y2; 706 } 707 708 bool has_top_line () { 709 return !unichar_code.islower () || CharDatabase.has_ascender (unichar_code); 710 } 711 712 public bool get_show_help_lines () { 713 return show_help_lines; 714 } 715 716 /** Show both x-height and top lines. */ 717 public bool get_xheight_lines_visible () { 718 return xheight_lines_visible; 719 } 720 721 /** Show both x-height and top lines. */ 722 public void set_xheight_lines_visible (bool x) { 723 xheight_lines_visible = x; 724 add_help_lines (); 725 } 726 727 public void set_margin_lines_visible (bool m) { 728 margin_boundaries_visible = m; 729 add_help_lines (); 730 } 731 732 public bool get_margin_lines_visible () { 733 return margin_boundaries_visible; 734 } 735 736 public void remove_empty_paths () { 737 foreach (Path p in get_all_paths ()) { 738 if (p.points.size < 2) { 739 delete_path (p); 740 remove_empty_paths (); 741 return; 742 } 743 } 744 } 745 746 public void delete_path (Path p) { 747 layers.remove_path(p); 748 } 749 750 public string get_svg_data () { 751 return Svg.to_svg_glyph (this); 752 } 753 754 public int get_height () { 755 Font f = BirdFont.get_current_font (); 756 return (int) Math.fabs (f.top_limit - f.bottom_limit); 757 } 758 759 public double get_width () { 760 return Math.fabs (right_limit - left_limit); 761 } 762 763 public unichar get_unichar () { 764 return unichar_code; 765 } 766 767 public string get_unichar_string () { 768 string? s = (!)get_unichar ().to_string (); 769 770 if (unlikely (s == null)) { 771 warning ("Invalid character."); 772 return "".dup (); 773 } 774 775 return (!) s; 776 } 777 778 public void redraw_help_lines () { 779 redraw_area (0, 0, allocation.width, allocation.height); 780 } 781 782 public void set_show_help_lines (bool hl) { 783 show_help_lines = hl; 784 } 785 786 private void add_line (Line line) { 787 if (line.is_vertical ()) { 788 vertical_help_lines.add (line); 789 } else { 790 horizontal_help_lines.add (line); 791 } 792 793 sort_help_lines (); 794 795 line.queue_draw_area.connect ((x, y, w, h) => { 796 this.redraw_area (x, y, w, h); 797 }); 798 } 799 800 public void sort_help_lines () { 801 vertical_help_lines.sort ((a, b) => { 802 Line first, next; 803 first = (Line) a; 804 next = (Line) b; 805 return (int) (first.get_pos () - next.get_pos ()); 806 }); 807 808 horizontal_help_lines.sort ((a, b) => { 809 Line first, next; 810 first = (Line) a; 811 next = (Line) b; 812 return (int) (first.get_pos () - next.get_pos ()); 813 }); 814 } 815 816 public override string get_name () { 817 return name; 818 } 819 820 public override string get_label () { 821 return name; 822 } 823 824 private void help_line_event (int x, int y) { 825 bool m = false; 826 827 foreach (Line line in vertical_help_lines) { 828 if (!m && line.event_move_to (x, y, allocation)) { 829 m = true; 830 } 831 } 832 833 foreach (Line line in horizontal_help_lines) { 834 if (!m && line.event_move_to (x, y, allocation)) { 835 m = true; 836 } 837 } 838 } 839 840 public override void key_release (uint keyval) { 841 Tool t; 842 t = MainWindow.get_toolbox ().get_current_tool (); 843 t.key_release_action (t, keyval); 844 845 if (keyval == (uint)' ') { 846 move_canvas = false; 847 } 848 } 849 850 public override void key_press (uint keyval) { 851 Tool t = MainWindow.get_toolbox ().get_current_tool (); 852 t.key_press_action (t, keyval); 853 854 if (keyval == (uint)' ') { 855 move_canvas = true; 856 } 857 858 switch (keyval) { 859 case Key.NUM_PLUS: 860 zoom_in (); 861 break; 862 case Key.NUM_MINUS: 863 zoom_out (); 864 break; 865 } 866 } 867 868 /** Delete edit point from path. 869 * @return false if no points was deleted 870 */ 871 public bool process_deleted () { 872 Gee.ArrayList<Path> deleted_paths = new Gee.ArrayList<Path> (); 873 foreach (Path p in get_all_paths ()) { 874 if (p.points.size > 0) { 875 if (process_deleted_points_in_path (p)) { 876 return true; 877 } 878 } else { 879 deleted_paths.add (p); 880 } 881 } 882 883 foreach (Path p in deleted_paths) { 884 delete_path (p); 885 } 886 887 return false; 888 } 889 890 private bool process_deleted_points_in_path (Path p) { 891 PathList remaining_points; 892 remaining_points = p.process_deleted_points (); 893 foreach (Path path in remaining_points.paths) { 894 add_path (path); 895 path.reopen (); 896 path.create_list (); 897 898 add_active_path (null, path); 899 } 900 901 if (remaining_points.paths.size > 0) { 902 delete_path (p); 903 return true; 904 } 905 906 return false; 907 } 908 909 public override void motion_notify (double x, double y) { 910 Tool t = MainWindow.get_toolbox ().get_current_tool (); 911 912 if (view_is_moving) { 913 move_view_offset (x, y); 914 return; 915 } 916 917 help_line_event ((int) x, (int) y); 918 t.move_action (t, (int) x, (int) y); 919 920 motion_x = x * ivz () - xc () + view_offset_x; 921 motion_y = yc () - y * ivz () - view_offset_y; 922 } 923 924 public override void button_release (int button, double ex, double ey) { 925 bool line_moving = false; 926 view_is_moving = false; 927 928 foreach (Line line in get_all_help_lines ()) { 929 if (!line.set_move (false)) { 930 line_moving = true; 931 } 932 } 933 934 if (!line_moving) { 935 Tool t = MainWindow.get_toolbox ().get_current_tool (); 936 t.release_action (t, (int) button, (int) ex, (int) ey); 937 } 938 939 update_view (); 940 } 941 942 private Gee.ArrayList<Line> get_all_help_lines () { 943 Gee.ArrayList<Line> all_lines = new Gee.ArrayList<Line> (); 944 945 foreach (Line l in vertical_help_lines) { 946 all_lines.add (l); 947 } 948 949 foreach (Line l in horizontal_help_lines) { 950 all_lines.add (l); 951 } 952 953 if (GridTool.is_visible ()) { 954 foreach (Line l in GridTool.get_vertical_lines ()) { 955 all_lines.add (l); 956 } 957 958 foreach (Line l in GridTool.get_horizontal_lines ()) { 959 all_lines.add (l); 960 } 961 } 962 963 return all_lines; 964 } 965 966 public void update_view () { 967 GlyphCanvas.redraw (); 968 } 969 970 public override void double_click (uint button, double ex, double ey) { 971 Tool t = MainWindow.get_toolbox ().get_current_tool (); 972 t.double_click_action (t, (int) button, (int) ex, (int) ey); 973 } 974 975 public override void button_press (uint button, double ex, double ey) { 976 bool moving_lines = false; 977 978 pointer_begin_x = ex; 979 pointer_begin_y = ey; 980 981 foreach (Line line in horizontal_help_lines) { 982 if (!moving_lines && line.is_visible () && line.button_press (button)) { 983 moving_lines = true; 984 } 985 } 986 987 foreach (Line line in vertical_help_lines) { 988 if (!moving_lines && line.is_visible () && line.button_press (button)) { 989 moving_lines = true; 990 } 991 } 992 993 if (moving_lines) { 994 return; 995 } 996 997 if (move_canvas 998 || DrawingTools.move_canvas.is_selected () 999 || (KeyBindings.has_ctrl () && KeyBindings.has_shift ())) { 1000 view_is_moving = true; 1001 move_offset_x = view_offset_x; 1002 move_offset_y = view_offset_y; 1003 } else { 1004 Tool t = MainWindow.get_toolbox ().get_current_tool (); 1005 t.press_action (t, (int) button, (int) ex, (int) ey); 1006 } 1007 } 1008 1009 /** Add new points to this path. */ 1010 public void set_active_path (Path p) { 1011 p.reopen (); 1012 clear_active_paths (); 1013 add_active_path (null, p); 1014 } 1015 1016 /** Move view port centrum to this coordinate. */ 1017 public void set_center (double x, double y) { 1018 x -= allocation.width / 2.0; 1019 y -= allocation.height / 2.0; 1020 1021 view_offset_x += x / view_zoom; 1022 view_offset_y += y / view_zoom; 1023 } 1024 1025 public void set_zoom_from_area () { 1026 double x = Math.fmin (zoom_x1, zoom_x2); 1027 double y = Math.fmin (zoom_y1, zoom_y2); 1028 1029 double w = Math.fabs (zoom_x1 - zoom_x2); 1030 double h = Math.fabs (zoom_y1 - zoom_y2); 1031 1032 double view_zoom_x, view_zoom_y; 1033 double ivz, off; 1034 1035 if (move_canvas) { 1036 return; 1037 } 1038 1039 if (Path.distance (x, x + w, y, y + h) < 7) { 1040 zoom_in (); 1041 } else { 1042 view_offset_x += x / view_zoom; 1043 view_offset_y += y / view_zoom; 1044 1045 if (unlikely (allocation.width == 0 || allocation.height == 0)) { 1046 return; 1047 } 1048 1049 view_zoom_x = allocation.width * view_zoom / w; 1050 view_zoom_y = allocation.height * view_zoom / h; 1051 1052 // TODO: there is a max zoom level 1053 1054 if (view_zoom_x * allocation.width < view_zoom_y * allocation.height) { 1055 view_zoom = view_zoom_x; 1056 ivz = 1 / view_zoom; 1057 1058 off = (view_zoom / view_zoom_y) * allocation.height / view_zoom; 1059 off = allocation.height/view_zoom - off; 1060 off /= 2; 1061 1062 view_offset_y -= off; 1063 1064 } else { 1065 view_zoom = view_zoom_y; 1066 ivz = 1 / view_zoom; 1067 1068 off = (view_zoom / view_zoom_x) * allocation.width / view_zoom; 1069 off = allocation.width / view_zoom - off; 1070 off /= 2; 1071 1072 view_offset_x -= off; 1073 } 1074 1075 zoom_area_is_visible = false; 1076 store_current_view (); 1077 } 1078 1079 update_zoom_bar (); 1080 } 1081 1082 public void show_zoom_area (int sx, int sy, int nx, int ny) { 1083 set_zoom_area (sx, sy, nx, ny); 1084 zoom_area_is_visible = true; 1085 GlyphCanvas.redraw (); 1086 } 1087 1088 public void set_zoom_area (int sx, int sy, int nx, int ny) { 1089 zoom_x1 = sx; 1090 zoom_y1 = sy; 1091 zoom_x2 = nx; 1092 zoom_y2 = ny; 1093 } 1094 1095 public static void validate_zoom () { 1096 Glyph g = MainWindow.get_current_glyph (); 1097 if (unlikely (g.view_zoom == 0)) { 1098 warning ("Zoom is zero."); 1099 g.reset_zoom (); 1100 1101 if (g.view_zoom == 0) { 1102 g.view_zoom = 1; 1103 g.view_offset_x = 0; 1104 g.view_offset_y = 0; 1105 } 1106 } 1107 } 1108 1109 public static double path_coordinate_x (double x) { 1110 Glyph g = MainWindow.get_current_glyph (); 1111 validate_zoom (); 1112 return x * ivz () - xc () + g.view_offset_x; 1113 } 1114 1115 public static int reverse_path_coordinate_x (double x) { 1116 Glyph g = MainWindow.get_current_glyph (); 1117 validate_zoom (); 1118 return (int) Math.rint ((x - g.view_offset_x + xc ()) * g.view_zoom); 1119 } 1120 1121 public static double precise_reverse_path_coordinate_x (double x) { 1122 Glyph g = MainWindow.get_current_glyph (); 1123 validate_zoom (); 1124 return (x - g.view_offset_x + xc ()) * g.view_zoom; 1125 } 1126 1127 public static double path_coordinate_y (double y) { 1128 Glyph g = MainWindow.get_current_glyph (); 1129 validate_zoom (); 1130 return yc () - y * ivz () - g.view_offset_y; 1131 } 1132 1133 public static int reverse_path_coordinate_y (double y) { 1134 Glyph g = MainWindow.get_current_glyph (); 1135 validate_zoom (); 1136 y = Math.rint ((y + g.view_offset_y - yc ()) * g.view_zoom); 1137 return (int) (-y); 1138 } 1139 1140 public static double precise_reverse_path_coordinate_y (double y) { 1141 Glyph g = MainWindow.get_current_glyph (); 1142 validate_zoom (); 1143 y = (y + g.view_offset_y - yc ()) * g.view_zoom; 1144 return -y; 1145 } 1146 1147 public Layer? get_path_at (double x, double y) { 1148 Layer? group = null; 1149 bool found = false; 1150 1151 foreach (Layer layer in get_current_layer ().subgroups) { 1152 foreach (Path pt in layer.paths.paths) { 1153 if (pt.is_over (x, y)) { 1154 found = true; 1155 group = layer; 1156 } 1157 } 1158 } 1159 1160 if (!found) { 1161 foreach (Path pt in get_paths_in_current_layer ()) { 1162 if (pt.is_over (x, y)) { 1163 Layer layer = new Layer (); 1164 layer.is_counter = true; 1165 layer.single_path = true; 1166 layer.add_path (pt); 1167 group = layer; 1168 } 1169 } 1170 } 1171 1172 return group; 1173 } 1174 1175 public bool select_path (double x, double y) { 1176 Path? p = null; 1177 bool found = false; 1178 1179 foreach (Path pt in get_paths_in_current_layer ()) { 1180 if (pt.is_over (x, y)) { 1181 p = pt; 1182 found = true; 1183 } 1184 } 1185 1186 if (!KeyBindings.has_shift ()) { 1187 clear_active_paths (); 1188 } 1189 1190 add_active_path (null, p); 1191 1192 return found; 1193 } 1194 1195 public bool is_over_selected_path (double x, double y) { 1196 foreach (Path pt in active_paths) { 1197 if (pt.is_over (x, y)) { 1198 return true; 1199 } 1200 } 1201 return false; 1202 } 1203 1204 public void queue_redraw_path (Path path) { 1205 redraw_path (path.xmin, path.ymin, path.xmax, path.ymax); 1206 } 1207 1208 private void redraw_path (double xmin, double ymin, double xmax, double ymax) { 1209 int yc = (int)(allocation.height / 2.0); 1210 1211 double yta = yc - ymin - view_offset_y; 1212 double ytb = yc - ymax - view_offset_y; 1213 1214 double xta = -view_offset_x - xmin; 1215 double xtb = -view_offset_x - xmax; 1216 1217 redraw_area ((int)xtb - 10, (int)yta - 10, (int)(xtb - xta) + 10, (int) (yta - ytb) + 10); 1218 } 1219 1220 public Path get_closeset_path (double x, double y) { 1221 double d; 1222 EditPoint ep = new EditPoint (); 1223 1224 Path min_point = new Path (); 1225 double min_distance = double.MAX; 1226 1227 double xt = path_coordinate_x (x); 1228 double yt = path_coordinate_y (y); 1229 var paths = get_visible_paths (); 1230 1231 foreach (Path p in paths) { 1232 if (p.is_over (xt, yt)) { 1233 return p; 1234 } 1235 } 1236 1237 foreach (Path p in paths) { 1238 if (p.points.size == 0) continue; 1239 1240 p.get_closest_point_on_path (ep, xt, yt); 1241 d = Math.pow (ep.x - xt, 2) + Math.pow (ep.y - yt, 2); 1242 1243 if (d < min_distance) { 1244 min_distance = d; 1245 min_point = p; 1246 } 1247 1248 } 1249 1250 // a path without any editpoints 1251 if (paths.size > 0) { 1252 return paths.get (0); 1253 } 1254 1255 if (unlikely (min_distance == double.MAX)) { 1256 warning (@"No path found in path_list."); 1257 } 1258 1259 return min_point; 1260 } 1261 1262 public void move_selected_edit_point_coordinates (EditPoint selected_point, double xt, double yt) { 1263 double x, y; 1264 1265 BirdFont.get_current_font ().touch (); 1266 1267 x = reverse_path_coordinate_x (xt); 1268 y = reverse_path_coordinate_y (yt); 1269 1270 // redraw control point 1271 redraw_area ((int)(x - 4*view_zoom), (int)(y - 4*view_zoom), (int)(x + 3*view_zoom), (int)(y + 3*view_zoom)); 1272 1273 // update position of selected point 1274 selected_point.set_position (xt, yt); 1275 1276 if (view_zoom >= 2) { 1277 redraw_area (0, 0, allocation.width, allocation.height); 1278 } else { 1279 redraw_last_stroke (x, y); 1280 } 1281 } 1282 1283 public void move_selected_edit_point (EditPoint selected_point, double x, double y) { 1284 double xt = path_coordinate_x (x); 1285 double yt = path_coordinate_y (y); 1286 move_selected_edit_point_coordinates (selected_point, xt, yt); 1287 } 1288 1289 public void redraw_segment (EditPoint a, EditPoint b) { 1290 double margin = 10; 1291 double x = Math.fmin (reverse_path_coordinate_x (a.x), reverse_path_coordinate_x (b.x)) - margin; 1292 double y = Math.fmin (reverse_path_coordinate_y (a.y), reverse_path_coordinate_y(b.y)) - margin; 1293 double w = Math.fabs (reverse_path_coordinate_x (a.x) - reverse_path_coordinate_x(b.x)) + 2 * margin; 1294 double h = Math.fabs (reverse_path_coordinate_y (a.y) - reverse_path_coordinate_y (b.y)) + 2 * margin; 1295 1296 redraw_area ((int)x, (int)y, (int)w, (int)h); 1297 } 1298 1299 private void redraw_last_stroke (double x, double y) { 1300 // redraw line, if we have more than one new point on path 1301 double px = 0; 1302 double py = 0; 1303 int tw = 0; 1304 int th = 0; 1305 1306 double xc = (allocation.width / 2.0); 1307 1308 if (active_paths.size == 0) { 1309 return; 1310 } 1311 1312 foreach (Path path in active_paths) { 1313 EditPoint p; 1314 EditPoint pl = path.get_last_point (); 1315 1316 if (pl.prev != null) { 1317 p = pl.get_prev (); 1318 1319 px = p.x + xc; 1320 py = p.y - xc; 1321 1322 tw = (px > x) ? (int) (px - x) : (int) (x - px); 1323 th = (py > y) ? (int) (py - y) : (int) (y - py); 1324 1325 if (px > x) px -= tw + 60; 1326 if (py > y) py -= th + 60; 1327 1328 } else { 1329 px = x - 60; 1330 py = y - 60; 1331 tw = 0; 1332 th = 0; 1333 } 1334 } 1335 1336 redraw_area ((int)px - 20, (int)py - 20, tw + 120, th + 120); 1337 } 1338 1339 public bool has_active_path () { 1340 return active_paths.size > 0; 1341 } 1342 1343 public bool is_open () { 1344 return open; 1345 } 1346 1347 /** Close all editable paths and return false if no path have been closed. */ 1348 public bool close_path () { 1349 bool r = false; 1350 1351 foreach (Path p in get_all_paths ()) { 1352 if (p.is_editable ()) { 1353 r = true; 1354 p.set_editable (false); 1355 } 1356 } 1357 1358 open = false; 1359 clear_active_paths (); 1360 GlyphCanvas.redraw (); 1361 1362 MainWindow.set_cursor (NativeWindow.VISIBLE); 1363 1364 return r; 1365 } 1366 1367 public void open_path () { 1368 foreach (Path p in get_visible_paths ()) { 1369 p.set_editable (true); 1370 p.recalculate_linear_handles (); 1371 1372 if (p.is_open () && p.points.size > 0) { 1373 p.get_first_point ().set_reflective_handles (false); 1374 p.get_first_point ().set_tie_handle (false); 1375 p.get_last_point ().set_reflective_handles (false); 1376 p.get_last_point ().set_tie_handle (false); 1377 } 1378 } 1379 1380 open = true; 1381 redraw_area (0, 0, allocation.width, allocation.height); 1382 } 1383 1384 public void redraw_path_region (Path p) { 1385 int x, y, w, h; 1386 1387 p.update_region_boundaries (); 1388 1389 x = reverse_path_coordinate_x (p.xmin); 1390 y = reverse_path_coordinate_x (p.xmin); 1391 w = reverse_path_coordinate_x (p.xmax) - x; 1392 h = reverse_path_coordinate_x (p.ymax) - y; // FIXME: redraw path 1393 1394 redraw_area (x, y, w, h); 1395 } 1396 1397 public Line get_line (string name) { 1398 foreach (Line line in vertical_help_lines) { 1399 if (likely (line.get_label () == name)) { 1400 return line; 1401 } 1402 } 1403 1404 foreach (Line line in horizontal_help_lines) { 1405 if (likely (line.get_label () == name)) { 1406 return line; 1407 } 1408 } 1409 1410 warning (@"No line with label $name found"); 1411 return new Line ("Err"); 1412 } 1413 1414 public override void zoom_in () { 1415 if (move_canvas) { 1416 return; 1417 } 1418 1419 set_zoom_area (10, 10, allocation.width - 10, allocation.height - 10); 1420 set_zoom_from_area (); 1421 update_view (); 1422 update_zoom_bar (); 1423 } 1424 1425 public override void zoom_out () { 1426 double w = allocation.width; 1427 int n = (int) (10 * ((w - 10) / allocation.width)); 1428 set_zoom_area (-n, -n, allocation.width + n, allocation.height + n); 1429 set_zoom_from_area (); 1430 update_view (); 1431 update_zoom_bar (); 1432 } 1433 1434 public override void zoom_max () { 1435 default_zoom (); 1436 update_zoom_bar (); 1437 } 1438 1439 public override void zoom_min () { 1440 double ax = 1000; 1441 double ay = 1000; 1442 double bx = -1000; 1443 double by = -1000; 1444 1445 int iax, iay, ibx, iby; 1446 1447 reset_zoom (); 1448 1449 foreach (var p in get_visible_paths ()) { 1450 p.update_region_boundaries (); 1451 1452 if (p.points.size > 2) { 1453 if (p.xmin < ax) ax = p.xmin; 1454 if (p.ymin < ay) ay = p.ymin; 1455 if (p.xmax > bx) bx = p.xmax; 1456 if (p.ymax > by) by = p.ymax; 1457 } 1458 } 1459 1460 if (ax == 1000) return; // empty page 1461 1462 iax = (int) ((ax + view_offset_x + allocation.width / 2.0) * view_zoom); 1463 iay = (int) ((-ay + view_offset_y + allocation.height / 2.0) * view_zoom); 1464 ibx = (int) ((bx + view_offset_x + allocation.width / 2.0) * view_zoom); 1465 iby = (int) ((-by + view_offset_y + allocation.height / 2.0) * view_zoom); 1466 1467 show_zoom_area (iax, iay, ibx, iby); // set this later on button release 1468 set_zoom_from_area (); 1469 zoom_out (); // add some margin 1470 1471 redraw_area (0, 0, allocation.width, allocation.height); 1472 update_zoom_bar (); 1473 } 1474 1475 public override void store_current_view () { 1476 ZoomView n; 1477 1478 if (zoom_list_index + 1 < zoom_list.size) { 1479 n = zoom_list.get (zoom_list_index); 1480 while (n != zoom_list.get (zoom_list.size - 1)) { 1481 zoom_list.remove_at (zoom_list.size - 1); 1482 } 1483 } 1484 1485 zoom_list.add (new ZoomView (view_offset_x, view_offset_y, view_zoom, allocation)); 1486 zoom_list_index = (int) zoom_list.size - 1; 1487 1488 if (zoom_list.size > 50) { 1489 zoom_list.remove_at (0); 1490 } 1491 } 1492 1493 public override void restore_last_view () { 1494 if (zoom_list.size == 0 || zoom_list_index - 1 < 0) { 1495 return; 1496 } 1497 1498 zoom_list_index--; 1499 1500 ZoomView z = zoom_list.get (zoom_list_index); 1501 1502 view_offset_x = z.x; 1503 view_offset_y = z.y; 1504 view_zoom = z.zoom; 1505 allocation = z.allocation; 1506 1507 update_zoom_bar (); 1508 } 1509 1510 public override void next_view () { 1511 ZoomView z; 1512 1513 if (zoom_list.size == 0 || zoom_list_index + 1 >= zoom_list.size) { 1514 return; 1515 } 1516 1517 zoom_list_index++; 1518 1519 z = zoom_list.get (zoom_list_index); 1520 1521 view_offset_x = z.x; 1522 view_offset_y = z.y; 1523 view_zoom = z.zoom; 1524 allocation = z.allocation; 1525 1526 update_zoom_bar (); 1527 } 1528 1529 public override void reset_zoom () { 1530 view_offset_x = 0; 1531 view_offset_y = 0; 1532 1533 set_zoom (1); 1534 1535 store_current_view (); 1536 update_zoom_bar (); 1537 } 1538 1539 /** Get x-height or top line. */ 1540 public Line get_upper_line () { 1541 if (has_top_line () || xheight_lines_visible) { 1542 return get_line ("top"); 1543 } 1544 1545 return get_line ("x-height"); 1546 } 1547 1548 /** Get base line. */ 1549 public Line get_lower_line () { 1550 return get_line ("baseline"); 1551 } 1552 1553 /** Set default zoom. See default_zoom. */ 1554 public void set_default_zoom () { 1555 int bottom = 0; 1556 int top = 0; 1557 int left = 0; 1558 int right = 0; 1559 1560 return_if_fail (vertical_help_lines.size != 0); 1561 return_if_fail (horizontal_help_lines.size != 0); 1562 1563 reset_zoom (); 1564 1565 bottom = get_lower_line ().get_position_pixel (); 1566 top = get_upper_line ().get_position_pixel (); 1567 1568 left = vertical_help_lines.get (vertical_help_lines.size - 1).get_position_pixel (); 1569 right = vertical_help_lines.get (0).get_position_pixel (); 1570 1571 set_zoom_area (left + 10, top - 10, right - 10, bottom + 10); 1572 set_zoom_from_area (); 1573 } 1574 1575 /** Set default zoom and redraw canvas. */ 1576 public void default_zoom () { 1577 set_default_zoom (); 1578 update_view (); 1579 } 1580 1581 public bool is_empty () { 1582 foreach (Path p in get_visible_paths ()) { 1583 if (p.points.size > 0) { 1584 return false; 1585 } 1586 } 1587 1588 return true; 1589 } 1590 1591 public void set_zoom (double z) 1592 requires (z > 0) 1593 { 1594 view_zoom = z; 1595 } 1596 1597 public void set_background_visible (bool visibility) { 1598 background_image_visible = visibility; 1599 } 1600 1601 public bool get_background_visible () { 1602 return background_image_visible; 1603 } 1604 1605 public void draw_coordinate (Context cr) { 1606 Theme.color (cr, "Table Border"); 1607 cr.set_font_size (12); 1608 cr.move_to (0, 10); 1609 cr.show_text (@"($motion_x, $motion_y)"); 1610 cr.stroke (); 1611 } 1612 1613 /** Draw filled paths. */ 1614 public void draw_paths (Context cr, Color? c = null) { 1615 PathList stroke; 1616 Color color; 1617 bool open; 1618 1619 cr.save (); 1620 cr.new_path (); 1621 foreach (Path p in get_visible_paths ()) { 1622 if (c != null) { 1623 color = (!) c; 1624 } else if (p.color != null) { 1625 color = (!) p.color; 1626 } else { 1627 color = Color.black (); 1628 } 1629 1630 if (p.stroke > 0) { 1631 stroke = p.get_stroke_fast (); 1632 draw_path_list (stroke, cr, color); 1633 } else { 1634 open = p.is_open (); 1635 1636 if (open) { 1637 p.close (); 1638 p.recalculate_linear_handles (); 1639 } 1640 1641 p.draw_path (cr, this, color); 1642 1643 if (open) { 1644 p.reopen (); 1645 } 1646 } 1647 } 1648 cr.fill (); 1649 cr.restore (); 1650 } 1651 1652 public void draw_path (Context cr) { 1653 PathList stroke; 1654 Color color; 1655 1656 cr.save (); 1657 cr.new_path (); 1658 foreach (Path p in get_visible_paths ()) { 1659 if (p.stroke > 0) { 1660 stroke = p.get_stroke_fast (); 1661 1662 if (p.is_editable ()) { 1663 color = Theme.get_color ("Filled Stroke"); 1664 color.a = 0.8; 1665 } else { 1666 color = Color.black (); 1667 } 1668 1669 draw_path_list (stroke, cr, color); 1670 } 1671 } 1672 cr.fill (); 1673 cr.restore (); 1674 1675 if (!(MainWindow.get_toolbox ().get_current_tool () is PenTool) 1676 && !(MainWindow.get_toolbox ().get_current_tool () is PointTool) 1677 && !(MainWindow.get_toolbox ().get_current_tool () is TrackTool) 1678 && !(MainWindow.get_toolbox ().get_current_tool () is BezierTool)) { 1679 cr.save (); 1680 cr.new_path (); 1681 foreach (Path p in active_paths) { 1682 if (p.stroke > 0) { 1683 stroke = p.get_stroke_fast (); 1684 color = Theme.get_color ("Selected Objects"); 1685 draw_path_list (stroke, cr, color); 1686 } 1687 } 1688 cr.fill (); 1689 cr.restore (); 1690 } 1691 1692 if (is_open () && Path.fill_open_path) { 1693 cr.save (); 1694 cr.new_path (); 1695 foreach (Path p in get_visible_paths ()) { 1696 if (p.stroke == 0) { 1697 color = p.color == null ? get_path_fill_color () : (!) p.color; 1698 p.draw_path (cr, this, color); 1699 } 1700 } 1701 cr.fill (); 1702 cr.restore (); 1703 } 1704 1705 if (is_open ()) { 1706 cr.save (); 1707 cr.new_path (); 1708 foreach (Path p in get_visible_paths ()) { 1709 p.draw_outline (cr); 1710 p.draw_edit_points (cr); 1711 } 1712 cr.restore (); 1713 } 1714 1715 if (!is_open ()) { 1716 // This was good for testing but it is way too slow: 1717 // Svg.draw_svg_path (cr, get_svg_data (), Glyph.xc () + left, Glyph.yc () - baseline); 1718 1719 cr.save (); 1720 cr.new_path (); 1721 foreach (Path p in get_visible_paths ()) { 1722 if (p.stroke == 0) { 1723 color = p.color == null ? Color.black () : (!) p.color; 1724 p.draw_path (cr, this, color); 1725 } 1726 } 1727 cr.close_path (); 1728 cr.fill (); 1729 cr.restore (); 1730 1731 foreach (Path p in active_paths) { 1732 cr.save (); 1733 cr.new_path (); 1734 if (p.stroke == 0) { 1735 p.draw_path (cr, this); 1736 } 1737 cr.close_path (); 1738 cr.fill (); 1739 cr.restore (); 1740 } 1741 } 1742 1743 if (show_orientation_arrow) { 1744 foreach (Path p in get_visible_paths ()) { 1745 if (p.stroke > 0) { 1746 stroke = p.get_stroke_fast (); 1747 foreach (Path ps in stroke.paths) { 1748 ps.draw_orientation_arrow (cr, orientation_arrow_opacity); 1749 } 1750 } else { 1751 p.draw_orientation_arrow (cr, orientation_arrow_opacity); 1752 } 1753 } 1754 } 1755 } 1756 1757 private Color get_path_fill_color () { 1758 return Theme.get_color ("Fill Color"); 1759 } 1760 1761 public void draw_path_list (PathList pl, Context cr, Color? c = null) { 1762 foreach (Path p in pl.paths) { 1763 p.draw_path (cr, this, c); 1764 } 1765 } 1766 1767 public void draw_background_color (Context cr, double opacity) { 1768 if (opacity > 0) { 1769 cr.save (); 1770 cr.rectangle (0, 0, allocation.width, allocation.height); 1771 Theme.color (cr, "Canvas Background"); 1772 cr.fill (); 1773 cr.restore (); 1774 } 1775 } 1776 1777 public void draw_help_lines (Context cr) { 1778 foreach (Line line in get_all_help_lines ()) { 1779 cr.save (); 1780 line.draw (cr, allocation); 1781 cr.restore (); 1782 } 1783 } 1784 1785 public void set_allocation (WidgetAllocation a) { 1786 allocation = a; 1787 } 1788 1789 public override void draw (WidgetAllocation allocation, Context cmp) { 1790 Tool tool; 1791 1792 this.allocation = allocation; 1793 1794 cmp.save (); 1795 draw_background_color (cmp, 1); 1796 cmp.restore (); 1797 1798 if (background_image != null && background_image_visible) { 1799 ((!)background_image).draw (cmp, allocation, view_offset_x, view_offset_y, view_zoom); 1800 } 1801 1802 if (unlikely (Preferences.draw_boundaries)) { 1803 foreach (Path p in get_visible_paths ()) { 1804 p.draw_boundaries (cmp); 1805 } 1806 } 1807 1808 draw_background_glyph (allocation, cmp); 1809 juxtapose (allocation, cmp); 1810 1811 if (BirdFont.show_coordinates) { 1812 draw_coordinate (cmp); 1813 } 1814 1815 if (show_help_lines) { 1816 cmp.save (); 1817 draw_help_lines (cmp); 1818 cmp.restore (); 1819 } 1820 1821 if (!is_empty ()) { 1822 cmp.save (); 1823 cmp.scale (view_zoom, view_zoom); 1824 cmp.translate (-view_offset_x, -view_offset_y); 1825 draw_path (cmp); 1826 cmp.restore (); 1827 } 1828 1829 cmp.save (); 1830 tool = MainWindow.get_toolbox ().get_current_tool (); 1831 tool.draw_action (tool, cmp, this); 1832 cmp.restore (); 1833 } 1834 1835 private void zoom_in_at_point (double x, double y, double amount = 15) { 1836 int n = (int) (-amount); 1837 zoom_at_point (x, y, n); 1838 } 1839 1840 private void zoom_out_at_point (double x, double y, double amount = 15) { 1841 double a = -amount; 1842 int n = (int) (a * ((allocation.width - a) / allocation.width)); 1843 zoom_at_point (x, y, n); 1844 } 1845 1846 public void zoom_tap (double distance) { 1847 int w = (int) (distance); 1848 if (distance != 0) { 1849 show_zoom_area (-w , -w, allocation.width + w, allocation.height + w); 1850 set_zoom_from_area (); 1851 } 1852 } 1853 1854 /** Zoom in @param n pixels. */ 1855 private void zoom_at_point (double x, double y, int n) { 1856 double w = allocation.width; 1857 double h = allocation.height; 1858 1859 double rx = Math.fabs (w / 2 - x) / (w / 2); 1860 double ry = Math.fabs (h / 2 - y) / (h / 2); 1861 1862 int xd = (x < w / 2) ? (int) (n * rx) : (int) (-n * rx); 1863 int yd = (y < h / 2) ? (int) (n * ry) : (int) (-n * ry); 1864 1865 show_zoom_area (-n + xd, -n + yd, allocation.width + n + xd, allocation.height + n + yd); 1866 set_zoom_from_area (); 1867 } 1868 1869 private void move_view_offset (double x, double y) { 1870 view_offset_x = move_offset_x + (pointer_begin_x - x) * (1/view_zoom); 1871 view_offset_y = move_offset_y + (pointer_begin_y - y) * (1/view_zoom); 1872 GlyphCanvas.redraw (); 1873 } 1874 1875 public void store_undo_state (bool clear_redo = false) { 1876 Glyph g = copy (); 1877 undo_list.add (g); 1878 1879 if (clear_redo) { 1880 redo_list.clear (); 1881 } 1882 } 1883 1884 public void store_redo_state () { 1885 Glyph g = copy (); 1886 redo_list.add (g); 1887 } 1888 1889 public Glyph copy () { 1890 Glyph g = new Glyph.no_lines (name, unichar_code); 1891 1892 g.current_layer = current_layer; 1893 1894 g.left_limit = left_limit; 1895 g.right_limit = right_limit; 1896 1897 g.remove_lines (); 1898 1899 foreach (Line line in get_all_help_lines ()) { 1900 g.add_line (line.copy ()); 1901 } 1902 1903 g.layers = layers.copy (); 1904 1905 foreach (Path p in active_paths) { 1906 g.active_paths.add (p); 1907 } 1908 1909 if (background_image != null) { 1910 g.background_image = ((!) background_image).copy (); 1911 } 1912 1913 g.empty = empty; 1914 g.open = open; 1915 g.version_id = version_id; 1916 1917 return g; 1918 } 1919 1920 public void reload () { 1921 Font f = BirdFont.get_current_font (); 1922 1923 if (f.has_glyph (name)) { 1924 set_glyph_data ((!) f.get_glyph (name)); 1925 } 1926 } 1927 1928 public override void undo () { 1929 Glyph g; 1930 Tool tool; 1931 1932 if (undo_list.size == 0) { 1933 return; 1934 } 1935 1936 tool = MainWindow.get_toolbox ().get_current_tool (); 1937 tool.before_undo (); 1938 1939 g = undo_list.get (undo_list.size - 1); 1940 1941 store_redo_state (); 1942 set_glyph_data (g); 1943 1944 undo_list.remove_at (undo_list.size - 1); 1945 1946 DrawingTools.update_layers (); 1947 PenTool.update_selected_points (); 1948 1949 clear_active_paths (); 1950 1951 tool.after_undo (); 1952 } 1953 1954 public override void redo () { 1955 Glyph g; 1956 1957 if (redo_list.size == 0) { 1958 return; 1959 } 1960 1961 g = redo_list.get (redo_list.size - 1); 1962 1963 store_undo_state (false); 1964 set_glyph_data (g); 1965 1966 redo_list.remove_at (redo_list.size - 1); 1967 1968 DrawingTools.update_layers (); 1969 PenTool.update_selected_points (); 1970 1971 clear_active_paths (); 1972 } 1973 1974 void set_glyph_data (Glyph g) { 1975 current_layer = g.current_layer; 1976 layers = g.layers.copy (); 1977 1978 left_limit = g.left_limit; 1979 right_limit = g.right_limit; 1980 1981 remove_lines (); 1982 foreach (Line line in g.get_all_help_lines ()) { 1983 add_line (line.copy ()); 1984 } 1985 1986 add_help_lines (); 1987 1988 if (g.background_image != null) { 1989 background_image = ((!) g.background_image).copy (); 1990 } 1991 1992 clear_active_paths (); 1993 foreach (Path p in g.active_paths) { 1994 add_active_path (null, p); 1995 } 1996 1997 redraw_area (0, 0, allocation.width, allocation.height); 1998 } 1999 2000 /** Split curve in two parts and add a new point in between. 2001 * @return the new point 2002 */ 2003 public void insert_new_point_on_path (double x, double y) { 2004 double min, distance; 2005 Path? p = null; 2006 Path path; 2007 EditPoint? np = null; 2008 EditPoint lep; 2009 2010 double xt; 2011 double yt; 2012 2013 xt = x * ivz () - xc () + view_offset_x; 2014 yt = yc () - y * ivz () - view_offset_y; 2015 2016 min = double.MAX; 2017 2018 foreach (Path pp in get_visible_paths ()) { 2019 lep = new EditPoint (); 2020 pp.get_closest_point_on_path (lep, xt, yt); 2021 distance = Math.sqrt (Math.pow (Math.fabs (xt - lep.x), 2) + Math.pow (Math.fabs (yt - lep.y), 2)); 2022 2023 if (distance < min) { 2024 min = distance; 2025 p = pp; 2026 np = lep; 2027 } 2028 } 2029 2030 if (p == null) { 2031 return; 2032 } 2033 2034 path = (!) p; 2035 2036 lep = new EditPoint (); 2037 path.get_closest_point_on_path (lep, xt, yt); 2038 path.insert_new_point_on_path (lep); 2039 } 2040 2041 static bool in_range (double offset_x, double coordinate_x1, double coordinate_x2) { 2042 return coordinate_x1 <= offset_x <= coordinate_x2; 2043 } 2044 2045 public void juxtapose (WidgetAllocation allocation, Context cr) { 2046 string glyph_sequence = Preferences.get ("glyph_sequence"); 2047 unichar c; 2048 Font font = BirdFont.get_current_font (); 2049 Glyph glyph = MainWindow.get_current_glyph (); 2050 Glyph juxtaposed; 2051 StringBuilder current = new StringBuilder (); 2052 int pos; 2053 string name; 2054 double x, kern; 2055 double left, baseline; 2056 string last_name; 2057 2058 double box_x1, box_x2, box_y1, box_y2; 2059 double marker_x, marker_y; 2060 2061 KerningClasses classes = font.get_kerning_classes (); 2062 2063 x = 0; 2064 2065 box_x1 = path_coordinate_x (0); 2066 box_y1 = path_coordinate_y (0); 2067 box_x2 = path_coordinate_x (allocation.width); 2068 box_y2 = path_coordinate_y (allocation.height); 2069 2070 current.append_unichar (glyph.unichar_code); 2071 pos = glyph_sequence.index_of (current.str); 2072 2073 baseline = font.base_line;; 2074 left = glyph.get_line ("left").pos; 2075 2076 x = glyph.get_width (); 2077 last_name = glyph.name; 2078 for (int i = pos + 1; i < glyph_sequence.char_count (); i++) { 2079 c = glyph_sequence.get_char (i); 2080 name = (!) c.to_string (); 2081 juxtaposed = (font.has_glyph (name)) ? (!) font.get_glyph (name) : font.get_space ().get_current (); 2082 2083 if (font.has_glyph (last_name) && font.has_glyph (name)) { 2084 kern = classes.get_kerning (last_name, name); 2085 } else { 2086 kern = 0; 2087 } 2088 2089 if (!juxtaposed.is_empty () 2090 && (in_range (left + x + kern, box_x1, box_x2) // the letter is visible 2091 || in_range (left + x + kern + juxtaposed.get_width (), box_x1, box_x2))) { 2092 2093 marker_x = Glyph.xc () + left + x + kern - glyph.view_offset_x; 2094 marker_y = Glyph.yc () - baseline - glyph.view_offset_y; 2095 2096 cr.save (); 2097 cr.scale (glyph.view_zoom, glyph.view_zoom); 2098 Theme.color (cr, "Foreground 1"); 2099 2100 Svg.draw_svg_path (cr, juxtaposed.get_svg_data (), marker_x, marker_y); 2101 cr.restore (); 2102 } 2103 2104 x += juxtaposed.get_width () + kern; 2105 2106 last_name = name; 2107 } 2108 2109 x = 0; 2110 last_name = glyph.name; 2111 for (int i = pos - 1; i >= 0; i--) { 2112 c = glyph_sequence.get_char (i); 2113 name = (!) c.to_string (); 2114 juxtaposed = (font.has_glyph (name)) ? (!) font.get_glyph (name) : font.get_space ().get_current (); 2115 2116 if (font.has_glyph (last_name) && font.has_glyph (name)) { 2117 kern = classes.get_kerning (name, last_name); 2118 } else { 2119 kern = 0; 2120 } 2121 2122 x -= juxtaposed.get_width (); 2123 x -= kern; 2124 2125 marker_x = Glyph.xc () + left + x; 2126 marker_y = Glyph.yc () - baseline; 2127 if (!juxtaposed.is_empty () 2128 &&(in_range (left + x, box_x1, box_x2) 2129 || in_range (left + x + juxtaposed.get_width (), box_x1, box_x2))) { 2130 cr.save (); 2131 cr.scale (glyph.view_zoom, glyph.view_zoom); 2132 cr.translate (-glyph.view_offset_x, -glyph.view_offset_y); 2133 Theme.color (cr, "Foreground 1"); 2134 Svg.draw_svg_path (cr, juxtaposed.get_svg_data (), marker_x, marker_y); 2135 cr.restore (); 2136 } 2137 2138 last_name = name; 2139 } 2140 } 2141 2142 /** @return left side bearing */ 2143 public double get_lsb () { 2144 return get_line ("left").pos; 2145 } 2146 2147 /** @return bottom line */ 2148 public double get_baseline () { 2149 Font font = BirdFont.get_current_font (); 2150 return font.base_line; 2151 } 2152 2153 void draw_background_glyph (WidgetAllocation allocation, Context cr) { 2154 double left, baseline, current_left; 2155 Glyph g; 2156 Font font = BirdFont.get_current_font (); 2157 2158 current_left = get_line ("left").pos; 2159 2160 if (background_glyph != null) { 2161 g = (!) background_glyph; 2162 baseline = font.base_line; 2163 left = g.get_line ("left").pos; 2164 cr.save (); 2165 cr.scale (view_zoom, view_zoom); 2166 cr.translate (-view_offset_x, -view_offset_y); 2167 Theme.color (cr, "Background Glyph"); 2168 2169 Svg.draw_svg_path (cr, g.get_svg_data (), 2170 Glyph.xc () + left - (left - current_left) , 2171 Glyph.yc () - baseline); 2172 cr.restore (); 2173 } 2174 2175 } 2176 2177 public string get_hex () { 2178 return Font.to_hex_code (unichar_code); 2179 } 2180 2181 public override void move_view (double x, double y) { 2182 view_offset_x += x / view_zoom; 2183 view_offset_y += y / view_zoom; 2184 GlyphCanvas.redraw (); 2185 } 2186 2187 /** Scroll or zoom from tap events. */ 2188 public void change_view_event (int finger, int x, int y) { 2189 double dx, dy; 2190 double last_distance, new_distance; 2191 2192 dx = 0; 2193 dy = 0; 2194 2195 new_distance = 0; 2196 2197 if (last_tap0_y == -1 || last_tap0_x == -1 || last_tap1_y == -1 || last_tap1_x == -1) { 2198 return; 2199 } 2200 2201 if (finger == 0) { 2202 dx = last_tap0_x - x; 2203 dy = last_tap0_y - y; 2204 new_distance = Path.distance (last_tap1_x, x, last_tap1_y, y); 2205 } 2206 2207 if (finger == 1) { 2208 dx = last_tap1_x - x; 2209 dy = last_tap1_y - y; 2210 new_distance = Path.distance (last_tap0_x, x, last_tap0_y, y); 2211 } 2212 2213 last_distance = Path.distance (last_tap0_x, last_tap1_x, last_tap0_y, last_tap1_y); 2214 2215 if (zoom_distance != 0) { 2216 zoom_tap (zoom_distance - new_distance); 2217 } 2218 2219 if (finger == 1) { 2220 warning (@"dx $dx dy $dy last_tap1_x $last_tap1_x last_tap1_y $last_tap1_y x $x y $y"); 2221 move_view (dx, dy); 2222 } 2223 2224 zoom_distance = new_distance; 2225 } 2226 2227 public override void tap_down (int finger, int x, int y) { 2228 TimeoutSource delay; 2229 2230 if (finger == 0) { 2231 delay = new TimeoutSource (400); // wait for second finger 2232 delay.set_callback(() => { 2233 if (!change_view && !ignore_input) { 2234 button_press (1, x, y); 2235 } 2236 return false; 2237 }); 2238 delay.attach (null); 2239 2240 last_tap0_x = x; 2241 last_tap0_y = y; 2242 } 2243 2244 if (finger == 1) { 2245 change_view = true; 2246 last_tap1_x = x; 2247 last_tap1_y = y; 2248 } 2249 } 2250 2251 public override void tap_up (int finger, int x, int y) { 2252 if (finger == 0) { 2253 button_release (1, x, y); 2254 2255 last_tap0_x = -1; 2256 last_tap0_y = -1; 2257 } 2258 2259 if (finger == 1) { 2260 last_tap1_x = -1; 2261 last_tap1_y = -1; 2262 2263 change_view = false; 2264 zoom_distance = 0; 2265 } 2266 } 2267 2268 public override void tap_move (int finger, int x, int y) { 2269 if (!change_view) { 2270 motion_notify (x, y); 2271 } else { 2272 change_view_event (finger, x, y); 2273 } 2274 2275 if (finger == 0) { 2276 last_tap0_x = x; 2277 last_tap0_y = y; 2278 } 2279 2280 if (finger == 1) { 2281 last_tap1_x = x; 2282 last_tap1_y = y; 2283 } 2284 } 2285 2286 public void update_spacing_class () { 2287 Font font = BirdFont.get_current_font (); 2288 GlyphCollection? g; 2289 GlyphCollection gc; 2290 Glyph glyph; 2291 Gee.ArrayList<string> s; 2292 SpacingData sd; 2293 2294 sd = font.get_spacing (); 2295 s = sd.get_all_connections ((!) unichar_code.to_string ()); 2296 foreach (string l in s) { 2297 if (l != (!) unichar_code.to_string ()) { 2298 g = font.get_glyph_collection (l); 2299 if (g != null) { 2300 gc = (!) g; 2301 glyph = gc.get_current (); 2302 2303 if (glyph.left_limit == glyph.right_limit) { 2304 warning ("Zero width glyph in kerning class."); 2305 } 2306 2307 left_limit = glyph.left_limit; 2308 right_limit = glyph.right_limit; 2309 2310 break; 2311 } 2312 } 2313 } 2314 2315 add_help_lines (); 2316 } 2317 2318 public void update_other_spacing_classes () { 2319 Font font = BirdFont.get_current_font (); 2320 GlyphCollection? g; 2321 GlyphCollection gc; 2322 Glyph glyph; 2323 Gee.ArrayList<string> s; 2324 SpacingData sd; 2325 2326 sd = font.get_spacing (); 2327 s = sd.get_all_connections (get_name ()); 2328 2329 foreach (string l in s) { 2330 if (l != (!) unichar_code.to_string ()) { 2331 g = font.get_glyph_collection (l); 2332 if (g != null) { 2333 gc = (!) g; 2334 glyph = gc.get_current (); 2335 glyph.left_limit = left_limit; 2336 glyph.right_limit = right_limit; 2337 } 2338 } 2339 } 2340 } 2341 2342 public void set_cache (string key, Surface cache) { 2343 glyph_cache.set (key, cache); 2344 } 2345 2346 public bool has_cache (string key) { 2347 return glyph_cache.has_key (key); 2348 } 2349 2350 public Surface get_cache (string key) { 2351 if (unlikely (!has_cache (key))) { 2352 warning ("No cache for glyph."); 2353 return new ImageSurface (Cairo.Format.ARGB32, 1, 1); 2354 } 2355 2356 return glyph_cache.get (key); 2357 } 2358 2359 public void add_custom_guide () { 2360 TextListener listener; 2361 2362 listener = new TextListener (t_("Guide"), "", t_("Add")); 2363 2364 listener.signal_text_input.connect ((text) => { 2365 new_guide_name = text; 2366 }); 2367 2368 listener.signal_submit.connect (() => { 2369 Line guide; 2370 double position; 2371 2372 position = path_coordinate_y (allocation.height / 2.0); 2373 guide = new Line (new_guide_name, new_guide_name, position); 2374 horizontal_help_lines.add (guide); 2375 2376 BirdFont.get_current_font ().custom_guides.add (guide); 2377 2378 TabContent.hide_text_input (); 2379 GlyphCanvas.redraw (); 2380 }); 2381 2382 TabContent.show_text_input (listener); 2383 } 2384 2385 public Path? get_last_path () { 2386 var paths = get_all_paths (); 2387 return_val_if_fail (paths.size > 0, null); 2388 return paths.get (paths.size - 1); 2389 } 2390 2391 public void move_layer_up () { 2392 Layer layer = get_current_layer (); 2393 2394 if (current_layer + 2 <= layers.subgroups.size) { 2395 return_if_fail (0 <= current_layer + 2 <= layers.subgroups.size); 2396 layers.subgroups.insert (current_layer + 2, layer); 2397 2398 return_if_fail (0 <= current_layer + 1 < layers.subgroups.size); 2399 layers.subgroups.remove_at (current_layer); 2400 2401 set_current_layer (layer); 2402 } 2403 } 2404 2405 public void move_layer_down () { 2406 Layer layer = get_current_layer (); 2407 2408 if (current_layer >= 1) { 2409 return_if_fail (0 <= current_layer - 1 < layers.subgroups.size); 2410 layers.subgroups.insert (current_layer - 1, layer); 2411 2412 return_if_fail (0 <= current_layer + 1 < layers.subgroups.size); 2413 layers.subgroups.remove_at (current_layer + 1); 2414 2415 set_current_layer (layer); 2416 } 2417 } 2418 2419 public void fix_curve_orientation () { 2420 int inside_count; 2421 bool inside; 2422 Path outline; 2423 2424 foreach (Path p1 in get_visible_paths ()) { 2425 inside_count = 0; 2426 2427 foreach (Path p2 in get_visible_paths ()) { 2428 if (p1 != p2) { 2429 inside = true; 2430 outline = p2.flatten (); 2431 2432 foreach (EditPoint ep in p1.points) { 2433 if (!SvgParser.is_inside (ep, outline)) { 2434 inside = false; 2435 } 2436 } 2437 2438 if (inside) { 2439 inside_count++; 2440 } 2441 } 2442 } 2443 2444 if (inside_count % 2 == 0) { 2445 p1.force_direction (Direction.CLOCKWISE); 2446 } else { 2447 p1.force_direction (Direction.COUNTER_CLOCKWISE); 2448 } 2449 } 2450 } 2451 2452 public override void magnify (double magnification) { 2453 double x_center = path_coordinate_x (xc ()); 2454 double y_center = path_coordinate_y (yc ()); 2455 2456 view_zoom *= 1 + magnification; 2457 2458 view_offset_x -= path_coordinate_x (xc ()) - x_center; 2459 view_offset_y += path_coordinate_y (yc ()) - y_center; 2460 2461 GlyphCanvas.redraw (); 2462 } 2463 2464 public Glyph self_interpolate (double weight) { 2465 Glyph g1 = copy (); 2466 Glyph g2 = copy (); 2467 2468 g1.fix_curve_orientation (); 2469 g2.layers = new Layer (); // remove all paths 2470 2471 foreach (Path p in g1.get_visible_paths ()) { 2472 bool counter = !p.is_clockwise (); 2473 2474 g2.add_path (p.copy ()); 2475 2476 p.remove_points_on_points (); 2477 Path master = p.get_self_interpolated_master (counter, weight); 2478 p = p.interpolate_estimated_path (master, weight); 2479 p.recalculate_linear_handles (); 2480 2481 g2.add_path (p); 2482 g2.add_path (master); 2483 } 2484 2485 return g2; 2486 } 2487 } 2488 2489 } 2490