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