The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

Glyph.vala in libbirdfont

This file is a part of the Birdfont project.

Contributing

Send patches or pull requests to johan.mattsson.m@gmail.com.
Clone this repository: git clone https://github.com/johanmattssonm/birdfont.git

Revisions

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