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