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