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