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