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