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