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