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