The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

KerningDisplay.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/KerningDisplay.vala.
Thread safety in text rendering
1 /* 2 Copyright (C) 2012, 2014 Johan Mattsson 3 4 This library is free software; you can redistribute it and/or modify 5 it under the terms of the GNU Lesser General Public License as 6 published by the Free Software Foundation; either version 3 of the 7 License, or (at your option) any later version. 8 9 This library is distributed in the hope that it will be useful, but 10 WITHOUT ANY WARRANTY; without even the implied warranty of 11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 Lesser General Public License for more details. 13 */ 14 15 using Cairo; 16 17 namespace BirdFont { 18 19 /** Kerning context. */ 20 public class KerningDisplay : FontDisplay { 21 22 public bool suppress_input = false; 23 24 Gee.ArrayList <GlyphSequence> row; 25 int active_handle = -1; 26 int selected_handle = -1; 27 bool moving = false; 28 Glyph left_active_glyph = new Glyph ("null", '\0'); 29 Glyph right_active_glyph = new Glyph ("null", '\0'); 30 31 double begin_handle_x = 0; 32 double begin_handle_y = 0; 33 34 double last_handle_x = 0; 35 36 public bool text_input = false; 37 38 Gee.ArrayList<UndoItem> undo_items; 39 Gee.ArrayList<UndoItem> redo_items; 40 bool first_update = true; 41 42 Font current_font = new Font (); 43 Text kerning_label = new Text (); 44 45 public bool adjust_side_bearings = false; 46 bool right_side_bearing = true; 47 48 public KerningDisplay () { 49 GlyphSequence w = new GlyphSequence (); 50 row = new Gee.ArrayList <GlyphSequence> (); 51 undo_items = new Gee.ArrayList <UndoItem> (); 52 redo_items = new Gee.ArrayList <UndoItem> (); 53 row.add (w); 54 } 55 56 public GlyphSequence get_first_row () { 57 return row.size > 0 ? row.get (0) : new GlyphSequence (); 58 } 59 60 public override string get_label () { 61 return t_("Kerning"); 62 } 63 64 public override string get_name () { 65 return "Kerning"; 66 } 67 68 public void show_parse_error () { 69 string line1 = t_("The current kerning class is malformed."); 70 string line2 = t_("Add single characters separated by space and ranges on the form A-Z."); 71 string line3 = t_("Type “space” to kern the space character and “divis” to kern -."); 72 73 MainWindow.show_dialog (new MessageDialog (line1 + " " + line2 + " " + line3)); 74 } 75 76 public override void draw (WidgetAllocation allocation, Context cr) { 77 draw_kerning_pairs (allocation, cr); 78 } 79 80 public double get_row_height () { 81 return current_font.top_limit - current_font.bottom_limit; 82 } 83 84 public void draw_kerning_pairs (WidgetAllocation allocation, Context cr) { 85 Glyph glyph; 86 double x, y, w, kern, alpha; 87 double x2; 88 double caret_y; 89 int i, wi; 90 Glyph? prev; 91 GlyphSequence word_with_ligatures; 92 GlyphRange? gr_left, gr_right; 93 bool first_row = true; 94 double row_height; 95 Font font; 96 double item_size = 1.0 / KerningTools.font_size; 97 double item_size2 = 2.0 / KerningTools.font_size; 98 99 font = current_font; 100 i = 0; 101 102 // bg color 103 cr.save (); 104 cr.set_source_rgba (1, 1, 1, 1); 105 cr.rectangle (0, 0, allocation.width, allocation.height); 106 cr.fill (); 107 cr.restore (); 108 109 cr.save (); 110 cr.scale (KerningTools.font_size, KerningTools.font_size); 111 112 glyph = MainWindow.get_current_glyph (); 113 114 row_height = get_row_height (); 115 116 alpha = 1; 117 y = get_row_height () + font.base_line + 20; 118 x = 20; 119 w = 0; 120 prev = null; 121 kern = 0; 122 123 foreach (GlyphSequence word in row) { 124 wi = 0; 125 word_with_ligatures = word.process_ligatures (); 126 gr_left = null; 127 gr_right = null; 128 foreach (Glyph? g in word_with_ligatures.glyph) { 129 if (g == null) { 130 continue; 131 } 132 133 if (prev == null || wi == 0) { 134 kern = 0; 135 } else { 136 return_if_fail (wi < word_with_ligatures.ranges.size); 137 return_if_fail (wi - 1 >= 0); 138 139 gr_left = word_with_ligatures.ranges.get (wi - 1); 140 gr_right = word_with_ligatures.ranges.get (wi); 141 142 kern = get_kerning_for_pair (((!)prev).get_name (), ((!)g).get_name (), gr_left, gr_right); 143 } 144 145 // draw glyph 146 if (g == null) { 147 w = 50; 148 alpha = 1; 149 } else { 150 alpha = 0; 151 glyph = (!) g; 152 153 cr.save (); 154 glyph.add_help_lines (); 155 cr.translate (kern + x - glyph.get_lsb () - Glyph.xc (), glyph.get_baseline () + y - Glyph.yc ()); 156 glyph.draw_paths (cr); 157 cr.restore (); 158 159 w = glyph.get_width (); 160 } 161 162 // handle 163 if (first_row && (active_handle == i || selected_handle == i)) { 164 x2 = x + kern / 2.0; 165 166 cr.save (); 167 168 if (selected_handle == i) { 169 cr.set_source_rgba (0, 0, 0, 1); 170 } else { 171 cr.set_source_rgba (123/255.0, 123/255.0, 123/255.0, 1); 172 } 173 174 if (!adjust_side_bearings) { 175 cr.move_to (x2 - 5 * item_size, y + 20 * item_size); 176 cr.line_to (x2, y + 20 * item_size - 5 * item_size); 177 cr.line_to (x2 + 5 * item_size, y + 20* item_size); 178 cr.fill (); 179 180 if (gr_left != null || gr_right != null) { 181 cr.move_to (x2 - 5 * item_size, y + 20 * item_size); 182 cr.line_to (x2 + 5 * item_size, y + 20 * item_size); 183 cr.line_to (x2 + 5 * item_size, y + 24 * item_size); 184 cr.line_to (x2 - 5 * item_size, y + 24 * item_size); 185 cr.fill (); 186 } 187 } else { 188 if (right_side_bearing) { 189 cr.move_to (x2 - 5 * item_size2, y + 20 * item_size2); 190 cr.line_to (x2, y + 20 * item_size2 - 5 * item_size2); 191 cr.line_to (x2, y + 20* item_size2); 192 cr.fill (); 193 } else { 194 cr.move_to (x2, y + 20 * item_size2); 195 cr.line_to (x2, y + 20 * item_size2 - 5 * item_size2); 196 cr.line_to (x2 + 5 * item_size2, y + 20* item_size2); 197 cr.fill (); 198 } 199 } 200 201 if (active_handle == i && !adjust_side_bearings) { 202 cr.save (); 203 cr.scale (1 / KerningTools.font_size, 1 / KerningTools.font_size); 204 kerning_label.widget_x = x2 * KerningTools.font_size; 205 kerning_label.widget_y = y * KerningTools.font_size + 40; 206 kerning_label.draw (cr); 207 cr.fill (); 208 cr.restore (); 209 } 210 } 211 212 x += w + kern; 213 214 // caption 215 if (g == null || ((!)g).is_empty ()) { 216 cr.save (); 217 cr.set_source_rgba (153/255.0, 153/255.0, 153/255.0, alpha); 218 cr.move_to (x - w / 2.0 - 5, y + 20); 219 cr.set_font_size (10 * item_size); 220 cr.show_text ("?"); 221 cr.restore (); 222 } 223 224 prev = g; 225 226 wi++; 227 i++; 228 } 229 230 // draw caret 231 if (first_row) { 232 x2 = x; 233 caret_y = get_row_height () + font.base_line + 20; 234 cr.save (); 235 cr.set_line_width (1.0 / KerningTools.font_size); 236 cr.set_source_rgba (0, 0, 0, 0.5); 237 cr.move_to (x2, caret_y + 20); 238 cr.line_to (x2, 20); 239 cr.stroke (); 240 cr.restore (); 241 } 242 243 y += row_height + 20; 244 x = 20; 245 first_row = false; 246 247 if (y > allocation.height) { 248 break; 249 } 250 } 251 252 for (int j = row.size - 1; j > 30; j--) { 253 row.remove_at (j); 254 } 255 256 cr.restore (); 257 } 258 259 private void display_kerning_value (double k) { 260 string kerning = round (k); 261 kerning_label = new Text (@"$(kerning)", 17 * MainWindow.units); 262 } 263 264 private void set_active_handle_index (int h) { 265 double kern = get_kerning_for_handle (h); 266 active_handle = h; 267 268 if (1 <= active_handle < row.get (0).glyph.size) { 269 display_kerning_value (kern); 270 } 271 } 272 273 private double get_kerning_for_handle (int handle) { 274 string a, b; 275 GlyphRange? gr_left, gr_right; 276 bool got_pair; 277 278 got_pair = get_kerning_pair (handle, out a, out b, out gr_left, out gr_right); 279 280 if (got_pair) { 281 return get_kerning_for_pair (a, b, gr_left, gr_right); 282 } 283 284 return 0; 285 } 286 287 private bool get_kerning_pair (int handle, out string left, out string right, 288 out GlyphRange? range_left, out GlyphRange? range_right) { 289 string a, b; 290 Font font; 291 int wi = 0; 292 GlyphSequence word_with_ligatures; 293 int ranges_index = 0; 294 GlyphRange? gr_left, gr_right; 295 int row_index = 0; 296 297 font = current_font; 298 299 font.touch (); 300 301 a = ""; 302 b = ""; 303 304 left = ""; 305 right = ""; 306 range_left = null; 307 range_right = null; 308 309 if (handle <= 0) { 310 return false; 311 } 312 313 foreach (GlyphSequence word in row) { 314 word_with_ligatures = word.process_ligatures (); 315 ranges_index = 0; 316 foreach (Glyph? g in word_with_ligatures.glyph) { 317 318 if (g == null) { 319 continue; 320 } 321 322 b = ((!) g).get_name (); 323 324 if (handle == wi && row_index == 0) { 325 if (wi >= word_with_ligatures.ranges.size) { 326 return false; 327 } 328 return_val_if_fail (wi - 1 >= 0, false); 329 330 if (word_with_ligatures.ranges.size != word_with_ligatures.glyph.size) { 331 return false; 332 } 333 334 gr_left = word_with_ligatures.ranges.get (wi - 1); 335 gr_right = word_with_ligatures.ranges.get (wi); 336 337 left = a; 338 right = b; 339 range_left = gr_left; 340 range_right = gr_right; 341 342 return true; 343 } 344 345 wi++; 346 347 a = b; 348 } 349 350 row_index++; 351 } 352 353 return false; 354 } 355 356 public void set_absolute_kerning (int handle, double val) { 357 double kern; 358 359 if (MenuTab.suppress_event) { 360 return; 361 } 362 363 if (!adjust_side_bearings) { 364 kern = get_kerning_for_handle (handle); 365 set_space (handle, val - kern); 366 } 367 } 368 369 370 /** Adjust kerning or right side bearing. */ 371 private void set_space (int handle, double val) { 372 string a, b; 373 Font font; 374 GlyphRange? gr_left, gr_right; 375 376 font = current_font; 377 font.touch (); 378 379 if (!adjust_side_bearings) { 380 get_kerning_pair (handle, out a, out b, out gr_left, out gr_right); 381 set_kerning_pair (a, b, ref gr_left, ref gr_right, val); 382 } else { 383 if (right_side_bearing) { 384 left_active_glyph.right_limit += val; 385 left_active_glyph.remove_lines (); 386 left_active_glyph.add_help_lines (); 387 left_active_glyph.update_other_spacing_classes (); 388 } else { 389 right_active_glyph.left_limit -= val; 390 right_active_glyph.remove_lines (); 391 right_active_glyph.add_help_lines (); 392 right_active_glyph.update_other_spacing_classes (); 393 } 394 } 395 } 396 397 /** Class based gpos kerning. */ 398 public void set_kerning_pair (string a, string b, 399 ref GlyphRange? gr_left, ref GlyphRange? gr_right, 400 double val) { 401 double kern; 402 GlyphRange grl, grr; 403 KerningClasses classes; 404 string n, f; 405 bool has_kerning; 406 Font font; 407 408 font = current_font; 409 font.touch (); 410 classes = font.get_kerning_classes (); 411 412 kern = get_kerning_for_pair (a, b, gr_left, gr_right); 413 414 try { 415 if (gr_left == null) { 416 grl = new GlyphRange (); 417 grl.parse_ranges (a); 418 gr_left = grl; // update the range list 419 } else { 420 grl = (!) gr_left; 421 } 422 423 if (gr_right == null) { 424 grr = new GlyphRange (); 425 grr.parse_ranges (b); 426 gr_right = grr; 427 } else { 428 grr = (!) gr_right; 429 } 430 431 if (first_update) { 432 f = grl.get_all_ranges (); 433 n = grr.get_all_ranges (); 434 has_kerning = classes.has_kerning (f, n); 435 undo_items.add (new UndoItem (f, n, kern, has_kerning)); 436 redo_items.clear (); 437 first_update = false; 438 } 439 440 classes.set_kerning (grl, grr, kern + val); 441 display_kerning_value (kern + val); 442 } catch (MarkupError e) { 443 // FIXME: unassigned glyphs and ligatures 444 warning (e.message); 445 } 446 } 447 448 public static double get_kerning_for_pair (string a, string b, GlyphRange? gr_left, GlyphRange? gr_right) { 449 KerningClasses k = BirdFont.get_current_font ().get_kerning_classes (); 450 return k.get_kerning_for_pair (a, b, gr_left, gr_right); 451 } 452 453 public void set_current_font (Font f) { 454 current_font = f; 455 } 456 457 public override void selected_canvas () { 458 Glyph g; 459 GlyphSequence w; 460 StringBuilder s = new StringBuilder (); 461 bool append_char = false; 462 463 current_font = BirdFont.get_current_font (); 464 465 KeyBindings.set_require_modifier (true); 466 467 g = MainWindow.get_current_glyph (); 468 s.append_unichar (g.get_unichar ()); 469 470 if (row.size == 0) { 471 append_char = true; 472 } 473 474 if (append_char) { 475 w = new GlyphSequence (); 476 row.add (w); 477 w.glyph.insert (0, current_font.get_glyph (s.str)); 478 } 479 } 480 481 public void add_kerning_class (int index) { 482 add_range (KerningTools.get_kerning_class (index)); 483 } 484 485 public void add_range (GlyphRange range) { 486 Font font = current_font; 487 Glyph? glyph; 488 489 glyph = font.get_glyph_by_name (range.get_char (0)); 490 491 if (glyph == null) { 492 warning ("Kerning range is not represented by a valid glyph."); 493 return; 494 } 495 496 row.get (0).glyph.add ((!) glyph); 497 row.get (0).ranges.add (range); 498 499 GlyphCanvas.redraw (); 500 } 501 502 void set_selected_handle (int handle) { 503 Glyph? g; 504 selected_handle = handle; 505 GlyphSequence sequence_with_ligatures; 506 507 sequence_with_ligatures = row.get (0).process_ligatures (); 508 509 if (selected_handle <= 0) { 510 selected_handle = 1; 511 } 512 513 if (selected_handle >= sequence_with_ligatures.glyph.size) { 514 selected_handle = (int) sequence_with_ligatures.glyph.size - 1; 515 } 516 517 set_active_handle_index (handle); 518 519 if (0 <= selected_handle - 1 < sequence_with_ligatures.glyph.size) { 520 g = sequence_with_ligatures.glyph.get (selected_handle - 1); 521 if (g != null) { 522 left_active_glyph = (!) g; 523 } 524 } 525 526 if (0 <= selected_handle < sequence_with_ligatures.glyph.size) { 527 g = sequence_with_ligatures.glyph.get (selected_handle); 528 if (g != null) { 529 right_active_glyph = (!) g; 530 } 531 } 532 533 GlyphCanvas.redraw (); 534 } 535 536 public static void previous_pair () { 537 KerningDisplay d = MainWindow.get_kerning_display (); 538 d.set_selected_handle (d.selected_handle - 1); 539 } 540 541 public static void next_pair () { 542 KerningDisplay d = MainWindow.get_kerning_display (); 543 d.set_selected_handle (d.selected_handle + 1); 544 } 545 546 private static string round (double d) { 547 char[] b = new char [22]; 548 unowned string s = d.format (b, "%.2f"); 549 string n = s.dup (); 550 551 n = n.replace (",", "."); 552 553 if (n == "-0.00") { 554 n = "0.00"; 555 } 556 557 return n; 558 } 559 560 public override void key_press (uint keyval) { 561 unichar c; 562 563 if (MenuTab.suppress_event) { // don't update kerning while saving font 564 warning ("A background thread uses the current font."); 565 return; 566 } 567 568 c = (unichar) keyval; 569 570 if (suppress_input) { 571 return; 572 } 573 574 if ((keyval == 'u' || keyval == 'U') && KeyBindings.has_ctrl ()) { 575 insert_unichar (); 576 } else { 577 if (keyval == Key.LEFT && KeyBindings.modifier == NONE) { 578 first_update = true; 579 set_space (selected_handle, -1 / KerningTools.font_size); 580 } 581 582 if (keyval == Key.RIGHT && KeyBindings.modifier == NONE) { 583 first_update = true; 584 set_space (selected_handle, 1 / KerningTools.font_size); 585 } 586 587 if (KeyBindings.modifier == CTRL && (keyval == Key.LEFT || keyval == Key.RIGHT)) { 588 if (keyval == Key.LEFT) { 589 selected_handle--; 590 } 591 592 if (keyval == Key.RIGHT) { 593 selected_handle++; 594 } 595 596 set_selected_handle (selected_handle); 597 } 598 599 if (KeyBindings.modifier == NONE 600 || KeyBindings.modifier == SHIFT 601 || KeyBindings.modifier == ALT) { 602 603 if (keyval == Key.BACK_SPACE && row.size > 0 && row.get (0).glyph.size > 0) { 604 row.get (0).glyph.remove_at (row.get (0).glyph.size - 1); 605 row.get (0).ranges.remove_at (row.get (0).ranges.size - 1); 606 } 607 608 if (row.size == 0 || c == Key.ENTER) { 609 new_line (); 610 } 611 612 add_character (c); 613 } 614 } 615 616 GlyphCanvas.redraw (); 617 } 618 619 public void insert_unichar () { 620 TextListener listener; 621 string submitted_value = ""; 622 string unicodestart; 623 624 unicodestart = (KeyBindings.has_shift ()) ? "" : "U+"; 625 626 listener = new TextListener (t_("Unicode"), unicodestart, t_("Insert")); 627 628 listener.signal_text_input.connect ((text) => { 629 submitted_value = text; 630 631 if (MenuTab.suppress_event) { 632 return; 633 } 634 635 GlyphCanvas.redraw (); 636 }); 637 638 listener.signal_submit.connect (() => { 639 unichar c; 640 MainWindow.native_window.hide_text_input (); 641 642 text_input = false; 643 suppress_input = false; 644 645 if (submitted_value.has_prefix ("u+") || submitted_value.has_prefix ("U+")) { 646 c = Font.to_unichar (submitted_value); 647 add_character (c); 648 } else { 649 add_text (submitted_value); 650 } 651 }); 652 653 suppress_input = true; 654 text_input = true; 655 MainWindow.native_window.set_text_listener (listener); 656 } 657 658 public void new_line () { 659 row.insert (0, new GlyphSequence ()); 660 } 661 662 void add_character (unichar c) { 663 Glyph? g; 664 string name; 665 Font f; 666 667 if (MenuTab.suppress_event) { 668 return; 669 } 670 671 f = current_font; 672 673 if (!is_modifier_key (c) && c.validate ()) { 674 name = f.get_name_for_character (c); 675 g = f.get_glyph_by_name (name); 676 inser_glyph (g); 677 } 678 } 679 680 public void inser_glyph (Glyph? g) { 681 if (g != null) { 682 row.get (0).glyph.add (g); 683 row.get (0).ranges.add (null); 684 685 set_selected_handle ((int) row.get (0).glyph.size - 1); 686 set_active_handle_index (selected_handle); 687 } 688 } 689 690 public override void motion_notify (double ex, double ey) { 691 double k, y; 692 693 if (MenuTab.suppress_event) { 694 return; 695 } 696 697 if (!moving) { 698 set_active_handle (ex, ey); 699 } else { 700 y = 1; 701 702 if (Math.fabs (ey - begin_handle_y) > 20) { 703 y = ((Math.fabs (ey - begin_handle_y) / 100) + 1); 704 } 705 706 k = (ex - last_handle_x) / y; // y-axis is for variable precision 707 k /= KerningTools.font_size; 708 set_space (selected_handle, k); 709 GlyphCanvas.redraw (); 710 } 711 712 last_handle_x = ex; 713 } 714 715 public void set_active_handle (double ex, double ey) { 716 double item_size = 1.0 / KerningTools.font_size; 717 double y = 100 * item_size; 718 double x = 20; 719 double w = 0; 720 double d, kern; 721 double min = double.MAX; 722 int i = 0; 723 int row_index = 0; 724 int col_index = 0; 725 double fs = KerningTools.font_size; 726 Glyph glyph = new Glyph.no_lines (""); 727 728 GlyphRange? gr_left, gr_right; 729 730 Glyph? prev = null; 731 string gl_name = ""; 732 GlyphSequence word_with_ligatures; 733 734 foreach (GlyphSequence word in row) { 735 col_index = 0; 736 737 word_with_ligatures = word.process_ligatures (); 738 foreach (Glyph? g in word_with_ligatures.glyph) { 739 if (g == null) { 740 w = 50; 741 warning ("glyph does not exist"); 742 } else { 743 glyph = (!) g; 744 w = glyph.get_width (); 745 } 746 747 gl_name = glyph.get_name (); 748 749 if (prev == null && col_index != 0) { 750 warning (@"previous glyph does not exist row: $row_index column: $col_index"); 751 } 752 753 if (prev == null || col_index == 0) { 754 kern = 0; 755 } else { 756 return_if_fail (col_index < word_with_ligatures.ranges.size); 757 return_if_fail (col_index - 1 >= 0); 758 759 gr_left = word_with_ligatures.ranges.get (col_index - 1); 760 gr_right = word_with_ligatures.ranges.get (col_index); 761 762 kern = get_kerning_for_pair (((!)prev).get_name (), ((!)g).get_name (), gr_left, gr_right); 763 } 764 765 d = Math.pow (fs * (x + kern) - ex, 2) + Math.pow (fs * (y - ey), 2); 766 767 if (d < min) { 768 min = d; 769 770 if (ex != fs * (x + kern)) { // don't swap direction after button release 771 right_side_bearing = ex < fs * (x + kern); // right or left side bearing handle 772 } 773 774 if (active_handle != i - row_index) { 775 set_active_handle_index (i - row_index); 776 GlyphCanvas.redraw (); 777 } 778 779 if (col_index == word.glyph.size || col_index == 0) { 780 set_active_handle_index (-1); 781 } else { 782 set_active_handle_index (active_handle + row_index); 783 } 784 } 785 786 prev = g; 787 x += w + kern; 788 i++; 789 col_index++; 790 } 791 792 row_index++; 793 y += MainWindow.get_current_glyph ().get_height () + 20; 794 x = 20; 795 } 796 } 797 798 public override void button_release (int button, double ex, double ey) { 799 set_active_handle (ex, ey); 800 moving = false; 801 first_update = true; 802 803 if (button == 3 || text_input) { 804 set_kerning_by_text (); 805 } 806 } 807 808 public void set_kerning_by_text () { 809 TextListener listener; 810 string kerning = @"$(get_kerning_for_handle (selected_handle))"; 811 812 if (MenuTab.suppress_event) { 813 return; 814 } 815 816 if (selected_handle == -1) { 817 set_selected_handle (0); 818 } 819 820 listener = new TextListener (t_("Kerning"), kerning, t_("Close")); 821 822 listener.signal_text_input.connect ((text) => { 823 string submitted_value; 824 double parsed_value; 825 826 if (MenuTab.suppress_event) { 827 return; 828 } 829 830 submitted_value = text.replace (",", "."); 831 parsed_value = double.parse (submitted_value); 832 set_absolute_kerning (selected_handle, parsed_value); 833 GlyphCanvas.redraw (); 834 }); 835 836 listener.signal_submit.connect (() => { 837 MainWindow.native_window.hide_text_input (); 838 text_input = false; 839 suppress_input = false; 840 }); 841 842 suppress_input = true; 843 text_input = true; 844 MainWindow.native_window.set_text_listener (listener); 845 846 GlyphCanvas.redraw (); 847 } 848 849 public override void button_press (uint button, double ex, double ey) { 850 set_active_handle (ex, ey); 851 set_selected_handle (active_handle); 852 begin_handle_x = ex; 853 begin_handle_y = ey; 854 last_handle_x = ex; 855 moving = true; 856 } 857 858 /** Insert text form clipboard. */ 859 public void add_text (string t) { 860 int c; 861 862 if (MenuTab.suppress_event) { 863 return; 864 } 865 866 c = t.char_count (); 867 for (int i = 0; i <= c; i++) { 868 add_character (t.get_char (t.index_of_nth_char (i))); 869 } 870 871 GlyphCanvas.redraw (); 872 } 873 874 public override void undo () { 875 UndoItem ui; 876 UndoItem redo_state; 877 878 if (MenuTab.suppress_event) { 879 return; 880 } 881 882 if (undo_items.size == 0) { 883 return; 884 } 885 886 ui = undo_items.get (undo_items.size - 1); 887 888 redo_state = apply_undo (ui); 889 redo_items.add (redo_state); 890 891 undo_items.remove_at (undo_items.size - 1); 892 } 893 894 public override void redo () { 895 UndoItem ui; 896 897 if (MenuTab.suppress_event) { 898 return; 899 } 900 901 if (redo_items.size == 0) { 902 return; 903 } 904 905 ui = redo_items.get (redo_items.size - 1); 906 apply_undo (ui); 907 redo_items.remove_at (redo_items.size - 1); 908 } 909 910 /** @return redo state. */ 911 public UndoItem apply_undo (UndoItem ui) { 912 KerningClasses classes = BirdFont.get_current_font ().get_kerning_classes (); 913 GlyphRange glyph_range_first, glyph_range_next; 914 Font font = current_font; 915 string l, r; 916 UndoItem redo_state = new UndoItem ("", "", 0, false); 917 double? k; 918 919 l = GlyphRange.unserialize (ui.first); 920 r = GlyphRange.unserialize (ui.next); 921 922 try { 923 glyph_range_first = new GlyphRange (); 924 glyph_range_next = new GlyphRange (); 925 926 glyph_range_first.parse_ranges (ui.first); 927 glyph_range_next.parse_ranges (ui.next); 928 929 if (!ui.has_kerning) { 930 if (glyph_range_first.is_class () || glyph_range_next.is_class ()) { 931 redo_state.first = glyph_range_first.get_all_ranges (); 932 redo_state.next = glyph_range_next.get_all_ranges (); 933 redo_state.has_kerning = true; 934 redo_state.kerning = classes.get_kerning_for_range (glyph_range_first, glyph_range_next); 935 936 classes.delete_kerning_for_class (ui.first, ui.next); 937 } else { 938 939 redo_state.first = ui.first; 940 redo_state.next = ui.next; 941 redo_state.has_kerning = true; 942 k = classes.get_kerning_for_single_glyphs (ui.first, ui.next); 943 944 if (k != null) { 945 redo_state.kerning = (!) k; 946 } else { 947 warning ("No kerning"); 948 } 949 950 classes.delete_kerning_for_pair (ui.first, ui.next); 951 } 952 } else if (glyph_range_first.is_class () || glyph_range_next.is_class ()) { 953 glyph_range_first = new GlyphRange (); 954 glyph_range_next = new GlyphRange (); 955 956 glyph_range_first.parse_ranges (ui.first); 957 glyph_range_next.parse_ranges (ui.next); 958 959 redo_state.first = glyph_range_first.get_all_ranges (); 960 redo_state.next = glyph_range_next.get_all_ranges (); 961 k = classes.get_kerning_for_range (glyph_range_first, glyph_range_next); 962 963 if (k != null) { 964 redo_state.kerning = (!) k; 965 redo_state.has_kerning = true; 966 } else { 967 redo_state.has_kerning = false; 968 } 969 970 classes.set_kerning (glyph_range_first, glyph_range_next, ui.kerning); 971 } else { 972 redo_state.first = ui.first; 973 redo_state.next = ui.next; 974 redo_state.has_kerning = true; 975 k = classes.get_kerning_for_single_glyphs (ui.first, ui.next); 976 977 if (k != null) { 978 redo_state.kerning = (!) k; 979 redo_state.has_kerning = true; 980 } else { 981 redo_state.has_kerning = false; 982 } 983 984 classes.set_kerning_for_single_glyphs (ui.first, ui.next, ui.kerning); 985 } 986 } catch (MarkupError e) { 987 warning (e.message); 988 } 989 990 font.touch (); 991 GlyphCanvas.redraw (); 992 993 return redo_state; 994 } 995 996 public override void zoom_in () { 997 KerningTools.font_size += 0.1; 998 999 if (KerningTools.font_size > 3) { 1000 KerningTools.font_size = 3; 1001 } 1002 1003 KerningTools.zoom_bar.set_zoom (KerningTools.font_size / 3); 1004 GlyphCanvas.redraw (); 1005 } 1006 1007 public override void zoom_out () { 1008 KerningTools.font_size -= 0.1; 1009 1010 if (KerningTools.font_size < 0.3) { 1011 KerningTools.font_size = 0.3; 1012 } 1013 1014 KerningTools.zoom_bar.set_zoom (KerningTools.font_size / 3); 1015 GlyphCanvas.redraw (); 1016 } 1017 1018 public class UndoItem : GLib.Object { 1019 public string first; 1020 public string next; 1021 public double kerning; 1022 public bool has_kerning; 1023 1024 public UndoItem (string first, string next, double kerning, bool has_kerning) { 1025 this.first = first; 1026 this.next = next; 1027 this.kerning = kerning; 1028 this.has_kerning = has_kerning; 1029 } 1030 } 1031 } 1032 1033 } 1034