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