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