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