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.
Apply OTF substitution in kerning tab
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 public 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 Theme.color (cr, "Background 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 (font); 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 Theme.color (cr, "Foreground 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 cr.restore (); 212 } 213 214 x += w + kern; 215 216 // caption 217 if (g == null || ((!)g).is_empty ()) { 218 cr.save (); 219 cr.set_source_rgba (153/255.0, 153/255.0, 153/255.0, alpha); 220 cr.move_to (x - w / 2.0 - 5, y + 20); 221 cr.set_font_size (10 * item_size); 222 cr.show_text ("?"); 223 cr.restore (); 224 } 225 226 prev = g; 227 228 wi++; 229 i++; 230 } 231 232 // draw caret 233 if (first_row) { 234 x2 = x; 235 caret_y = get_row_height () + font.base_line + 20; 236 cr.save (); 237 cr.set_line_width (1.0 / KerningTools.font_size); 238 Theme.color_opacity (cr, "Foreground 1", 0.5); 239 cr.move_to (x2, caret_y + 20); 240 cr.line_to (x2, 20); 241 cr.stroke (); 242 cr.restore (); 243 244 y += (50 / KerningTools.font_size) * MainWindow.units; 245 } 246 247 y += row_height + 20; 248 x = 20; 249 first_row = false; 250 251 if (y > allocation.height) { 252 break; 253 } 254 } 255 256 for (int j = row.size - 1; j > 30; j--) { 257 row.remove_at (j); 258 } 259 260 cr.fill (); 261 cr.restore (); 262 } 263 264 private void display_kerning_value (double k) { 265 string kerning = round (k); 266 kerning_label = new Text (@"$(kerning)", 17 * MainWindow.units); 267 } 268 269 private void set_active_handle_index (int h) { 270 double kern = get_kerning_for_handle (h); 271 active_handle = h; 272 273 if (1 <= active_handle < row.get (0).glyph.size) { 274 display_kerning_value (kern); 275 } 276 } 277 278 private double get_kerning_for_handle (int handle) { 279 string a, b; 280 GlyphRange? gr_left, gr_right; 281 bool got_pair; 282 283 got_pair = get_kerning_pair (handle, out a, out b, out gr_left, out gr_right); 284 285 if (got_pair) { 286 return get_kerning_for_pair (a, b, gr_left, gr_right); 287 } 288 289 return 0; 290 } 291 292 private bool get_kerning_pair (int handle, out string left, out string right, 293 out GlyphRange? range_left, out GlyphRange? range_right) { 294 string a, b; 295 Font font; 296 int wi = 0; 297 GlyphSequence word_with_ligatures; 298 int ranges_index = 0; 299 GlyphRange? gr_left, gr_right; 300 int row_index = 0; 301 302 font = current_font; 303 304 font.touch (); 305 306 a = ""; 307 b = ""; 308 309 left = ""; 310 right = ""; 311 range_left = null; 312 range_right = null; 313 314 if (handle <= 0) { 315 return false; 316 } 317 318 foreach (GlyphSequence word in row) { 319 word_with_ligatures = word.process_ligatures (font); 320 ranges_index = 0; 321 foreach (Glyph? g in word_with_ligatures.glyph) { 322 323 if (g == null) { 324 continue; 325 } 326 327 b = ((!) g).get_name (); 328 329 if (handle == wi && row_index == 0) { 330 if (wi >= word_with_ligatures.ranges.size) { 331 return false; 332 } 333 return_val_if_fail (wi - 1 >= 0, false); 334 335 if (word_with_ligatures.ranges.size != word_with_ligatures.glyph.size) { 336 return false; 337 } 338 339 gr_left = word_with_ligatures.ranges.get (wi - 1); 340 gr_right = word_with_ligatures.ranges.get (wi); 341 342 left = a; 343 right = b; 344 range_left = gr_left; 345 range_right = gr_right; 346 347 return true; 348 } 349 350 wi++; 351 352 a = b; 353 } 354 355 row_index++; 356 } 357 358 return false; 359 } 360 361 public void set_absolute_kerning (int handle, double val) { 362 double kern; 363 364 if (MenuTab.suppress_event) { 365 return; 366 } 367 368 if (!adjust_side_bearings) { 369 kern = get_kerning_for_handle (handle); 370 set_space (handle, val - kern); 371 } 372 } 373 374 375 /** Adjust kerning or right side bearing. */ 376 private void set_space (int handle, double val) { 377 string a, b; 378 Font font; 379 GlyphRange? gr_left, gr_right; 380 381 font = current_font; 382 font.touch (); 383 384 if (!adjust_side_bearings) { 385 get_kerning_pair (handle, out a, out b, out gr_left, out gr_right); 386 set_kerning_pair (a, b, ref gr_left, ref gr_right, val); 387 } else { 388 if (right_side_bearing) { 389 left_active_glyph.right_limit += val; 390 left_active_glyph.remove_lines (); 391 left_active_glyph.add_help_lines (); 392 left_active_glyph.update_other_spacing_classes (); 393 } else { 394 right_active_glyph.left_limit -= val; 395 right_active_glyph.remove_lines (); 396 right_active_glyph.add_help_lines (); 397 right_active_glyph.update_other_spacing_classes (); 398 } 399 } 400 } 401 402 /** Class based gpos kerning. */ 403 public void set_kerning_pair (string a, string b, 404 ref GlyphRange? gr_left, ref GlyphRange? gr_right, 405 double val) { 406 double kern; 407 GlyphRange grl, grr; 408 KerningClasses classes; 409 string n, f; 410 bool has_kerning; 411 Font font; 412 413 font = current_font; 414 font.touch (); 415 classes = font.get_kerning_classes (); 416 417 kern = get_kerning_for_pair (a, b, gr_left, gr_right); 418 419 try { 420 if (gr_left == null) { 421 grl = new GlyphRange (); 422 grl.parse_ranges (a); 423 gr_left = grl; // update the range list 424 } else { 425 grl = (!) gr_left; 426 } 427 428 if (gr_right == null) { 429 grr = new GlyphRange (); 430 grr.parse_ranges (b); 431 gr_right = grr; 432 } else { 433 grr = (!) gr_right; 434 } 435 436 if (first_update) { 437 f = grl.get_all_ranges (); 438 n = grr.get_all_ranges (); 439 has_kerning = classes.has_kerning (f, n); 440 undo_items.add (new UndoItem (f, n, kern, has_kerning)); 441 redo_items.clear (); 442 first_update = false; 443 } 444 445 classes.set_kerning (grl, grr, kern + val); 446 display_kerning_value (kern + val); 447 } catch (MarkupError e) { 448 // FIXME: unassigned glyphs and ligatures 449 warning (e.message); 450 } 451 } 452 453 public static double get_kerning_for_pair (string a, string b, GlyphRange? gr_left, GlyphRange? gr_right) { 454 KerningClasses k = BirdFont.get_current_font ().get_kerning_classes (); 455 return k.get_kerning_for_pair (a, b, gr_left, gr_right); 456 } 457 458 public void set_current_font (Font f) { 459 current_font = f; 460 } 461 462 public override void selected_canvas () { 463 Glyph g; 464 GlyphSequence w; 465 StringBuilder s = new StringBuilder (); 466 bool append_char = false; 467 468 current_font = BirdFont.get_current_font (); 469 470 KeyBindings.set_require_modifier (true); 471 472 g = MainWindow.get_current_glyph (); 473 s.append_unichar (g.get_unichar ()); 474 475 if (row.size == 0) { 476 append_char = true; 477 } 478 479 if (append_char) { 480 w = new GlyphSequence (); 481 row.add (w); 482 w.glyph.insert (0, current_font.get_glyph (s.str)); 483 } 484 } 485 486 public void add_kerning_class (int index) { 487 add_range (KerningTools.get_kerning_class (index)); 488 } 489 490 public void add_range (GlyphRange range) { 491 Font font = current_font; 492 Glyph? glyph; 493 494 glyph = font.get_glyph_by_name (range.get_char (0)); 495 496 if (glyph == null) { 497 warning ("Kerning range is not represented by a valid glyph."); 498 return; 499 } 500 501 row.get (0).glyph.add ((!) glyph); 502 row.get (0).ranges.add (range); 503 504 GlyphCanvas.redraw (); 505 } 506 507 void set_selected_handle (int handle) { 508 Glyph? g; 509 selected_handle = handle; 510 GlyphSequence sequence_with_ligatures; 511 Font font = BirdFont.get_current_font (); 512 513 sequence_with_ligatures = row.get (0).process_ligatures (font); 514 515 if (selected_handle <= 0) { 516 selected_handle = 1; 517 } 518 519 if (selected_handle >= sequence_with_ligatures.glyph.size) { 520 selected_handle = (int) sequence_with_ligatures.glyph.size - 1; 521 } 522 523 set_active_handle_index (handle); 524 525 if (0 <= selected_handle - 1 < sequence_with_ligatures.glyph.size) { 526 g = sequence_with_ligatures.glyph.get (selected_handle - 1); 527 if (g != null) { 528 left_active_glyph = (!) g; 529 } 530 } 531 532 if (0 <= selected_handle < sequence_with_ligatures.glyph.size) { 533 g = sequence_with_ligatures.glyph.get (selected_handle); 534 if (g != null) { 535 right_active_glyph = (!) g; 536 } 537 } 538 539 GlyphCanvas.redraw (); 540 } 541 542 public static void previous_pair () { 543 KerningDisplay kd; 544 FontDisplay fd; 545 SpacingTab st; 546 547 fd = MainWindow.get_current_display (); 548 549 if (fd is SpacingTab) { 550 st = (SpacingTab) fd; 551 if (!st.right_side_bearing) { 552 st.right_side_bearing = true; 553 } else { 554 st.right_side_bearing = false; 555 st.set_selected_handle (st.selected_handle - 1); 556 } 557 } else if (fd is KerningDisplay) { 558 kd = (KerningDisplay) fd; 559 kd.set_selected_handle (kd.selected_handle - 1); 560 } 561 } 562 563 public static void next_pair () { 564 KerningDisplay kd; 565 FontDisplay fd; 566 SpacingTab st; 567 568 fd = MainWindow.get_current_display (); 569 570 if (fd is SpacingTab) { 571 st = (SpacingTab) fd; 572 if (st.right_side_bearing) { 573 st.right_side_bearing = false; 574 } else { 575 st.right_side_bearing = true; 576 st.set_selected_handle (st.selected_handle + 1); 577 } 578 } else if (fd is KerningDisplay) { 579 kd = (KerningDisplay) fd; 580 kd.set_selected_handle (kd.selected_handle + 1); 581 } 582 } 583 584 private static string round (double d) { 585 char[] b = new char [22]; 586 unowned string s = d.format (b, "%.2f"); 587 string n = s.dup (); 588 589 n = n.replace (",", "."); 590 591 if (n == "-0.00") { 592 n = "0.00"; 593 } 594 595 return n; 596 } 597 598 public override void key_press (uint keyval) { 599 unichar c; 600 601 if (MenuTab.suppress_event) { // don't update kerning while saving font 602 return; 603 } 604 605 c = (unichar) keyval; 606 607 if (suppress_input) { 608 return; 609 } 610 611 if ((keyval == 'u' || keyval == 'U') && KeyBindings.has_ctrl ()) { 612 insert_unichar (); 613 } else { 614 if (keyval == Key.LEFT && KeyBindings.modifier == NONE) { 615 first_update = true; 616 set_space (selected_handle, -1 / KerningTools.font_size); 617 } 618 619 if (keyval == Key.RIGHT && KeyBindings.modifier == NONE) { 620 first_update = true; 621 set_space (selected_handle, 1 / KerningTools.font_size); 622 } 623 624 if (KeyBindings.modifier == NONE 625 || KeyBindings.modifier == SHIFT 626 || KeyBindings.modifier == ALT) { 627 628 if (keyval == Key.BACK_SPACE && row.size > 0 && row.get (0).glyph.size > 0) { 629 row.get (0).glyph.remove_at (row.get (0).glyph.size - 1); 630 row.get (0).ranges.remove_at (row.get (0).ranges.size - 1); 631 } 632 633 if (row.size == 0 || c == Key.ENTER) { 634 new_line (); 635 } 636 637 add_character (c); 638 } 639 } 640 641 GlyphCanvas.redraw (); 642 } 643 644 public void insert_unichar () { 645 TextListener listener; 646 string submitted_value = ""; 647 string unicodestart; 648 649 unicodestart = (KeyBindings.has_shift ()) ? "" : "U+"; 650 651 listener = new TextListener (t_("Unicode"), unicodestart, t_("Insert")); 652 653 listener.signal_text_input.connect ((text) => { 654 submitted_value = text; 655 656 if (MenuTab.suppress_event) { 657 return; 658 } 659 660 GlyphCanvas.redraw (); 661 }); 662 663 listener.signal_submit.connect (() => { 664 unichar c; 665 TabContent.hide_text_input (); 666 667 text_input = false; 668 suppress_input = false; 669 670 if (submitted_value.has_prefix ("u+") || submitted_value.has_prefix ("U+")) { 671 c = Font.to_unichar (submitted_value); 672 add_character (c); 673 } else { 674 add_text (submitted_value); 675 } 676 }); 677 678 suppress_input = true; 679 text_input = true; 680 TabContent.show_text_input (listener); 681 } 682 683 public void new_line () { 684 row.insert (0, new GlyphSequence ()); 685 } 686 687 void add_character (unichar c) { 688 Glyph? g; 689 string name; 690 Font f; 691 692 if (MenuTab.suppress_event) { 693 return; 694 } 695 696 f = current_font; 697 698 if (!is_modifier_key (c) && c.validate ()) { 699 name = (!) c.to_string (); 700 g = f.get_glyph_by_name (name); 701 inser_glyph (g); 702 } 703 } 704 705 public void inser_glyph (Glyph? g) { 706 if (g != null) { 707 row.get (0).glyph.add (g); 708 row.get (0).ranges.add (null); 709 710 set_selected_handle ((int) row.get (0).glyph.size - 1); 711 set_active_handle_index (selected_handle); 712 } 713 } 714 715 public override void motion_notify (double ex, double ey) { 716 double k, y; 717 718 if (MenuTab.suppress_event) { 719 return; 720 } 721 722 if (!moving) { 723 set_active_handle (ex, ey); 724 } else { 725 y = 1; 726 727 if (Math.fabs (ey - begin_handle_y) > 20) { 728 y = ((Math.fabs (ey - begin_handle_y) / 100) + 1); 729 } 730 731 k = (ex - last_handle_x) / y; // y-axis is for variable precision 732 k /= KerningTools.font_size; 733 set_space (selected_handle, k); 734 GlyphCanvas.redraw (); 735 } 736 737 last_handle_x = ex; 738 } 739 740 public void set_active_handle (double ex, double ey) { 741 double w = 0; 742 double d, kern; 743 double min = double.MAX; 744 int i = 0; 745 int row_index = 0; 746 int col_index = 0; 747 Glyph glyph = new Glyph.no_lines (""); 748 Font font = BirdFont.get_current_font (); 749 double fs = KerningTools.font_size; 750 double x = 20; 751 752 GlyphRange? gr_left, gr_right; 753 754 Glyph? prev = null; 755 string gl_name = ""; 756 GlyphSequence word_with_ligatures; 757 758 foreach (GlyphSequence word in row) { 759 col_index = 0; 760 761 word_with_ligatures = word.process_ligatures (font); 762 foreach (Glyph? g in word_with_ligatures.glyph) { 763 if (g == null) { 764 w = 50; 765 warning ("glyph does not exist"); 766 } else { 767 glyph = (!) g; 768 w = glyph.get_width (); 769 } 770 771 gl_name = glyph.get_name (); 772 773 if (prev == null && col_index != 0) { 774 warning (@"previous glyph does not exist row: $row_index column: $col_index"); 775 } 776 777 if (prev == null || col_index == 0) { 778 kern = 0; 779 } else { 780 return_if_fail (col_index < word_with_ligatures.ranges.size); 781 return_if_fail (col_index - 1 >= 0); 782 783 gr_left = word_with_ligatures.ranges.get (col_index - 1); 784 gr_right = word_with_ligatures.ranges.get (col_index); 785 786 kern = get_kerning_for_pair (((!)prev).get_name (), ((!)g).get_name (), gr_left, gr_right); 787 } 788 789 d = Math.pow (fs * (x + kern) - ex, 2); 790 791 if (d < min) { 792 min = d; 793 794 if (ex != fs * (x + kern)) { // don't swap direction after button release 795 right_side_bearing = ex < fs * (x + kern); // right or left side bearing handle 796 } 797 798 if (active_handle != i - row_index) { 799 set_active_handle_index (i - row_index); 800 GlyphCanvas.redraw (); 801 } 802 803 if (col_index == word.glyph.size || col_index == 0) { 804 set_active_handle_index (-1); 805 } else { 806 set_active_handle_index (active_handle + row_index); 807 } 808 } 809 810 prev = g; 811 x += w + kern; 812 i++; 813 col_index++; 814 } 815 816 row_index++; 817 x = 20; 818 return; 819 } 820 } 821 822 public override void button_release (int button, double ex, double ey) { 823 set_active_handle (ex, ey); 824 moving = false; 825 first_update = true; 826 827 if (button == 3 || text_input) { 828 set_kerning_by_text (); 829 } 830 } 831 832 public void set_kerning_by_text () { 833 TextListener listener; 834 string kerning = @"$(get_kerning_for_handle (selected_handle))"; 835 836 if (MenuTab.suppress_event) { 837 return; 838 } 839 840 if (selected_handle == -1) { 841 set_selected_handle (0); 842 } 843 844 listener = new TextListener (t_("Kerning"), kerning, t_("Close")); 845 846 listener.signal_text_input.connect ((text) => { 847 string submitted_value; 848 double parsed_value; 849 850 if (MenuTab.suppress_event) { 851 return; 852 } 853 854 submitted_value = text.replace (",", "."); 855 parsed_value = double.parse (submitted_value); 856 set_absolute_kerning (selected_handle, parsed_value); 857 GlyphCanvas.redraw (); 858 }); 859 860 listener.signal_submit.connect (() => { 861 TabContent.hide_text_input (); 862 text_input = false; 863 suppress_input = false; 864 }); 865 866 suppress_input = true; 867 text_input = true; 868 TabContent.show_text_input (listener); 869 870 GlyphCanvas.redraw (); 871 } 872 873 public override void button_press (uint button, double ex, double ey) { 874 set_active_handle (ex, ey); 875 set_selected_handle (active_handle); 876 begin_handle_x = ex; 877 begin_handle_y = ey; 878 last_handle_x = ex; 879 moving = true; 880 } 881 882 /** Insert text form clipboard. */ 883 public void add_text (string t) { 884 int c; 885 886 if (MenuTab.suppress_event) { 887 return; 888 } 889 890 c = t.char_count (); 891 for (int i = 0; i <= c; i++) { 892 add_character (t.get_char (t.index_of_nth_char (i))); 893 } 894 895 GlyphCanvas.redraw (); 896 } 897 898 public override void undo () { 899 UndoItem ui; 900 UndoItem redo_state; 901 902 if (MenuTab.suppress_event) { 903 return; 904 } 905 906 if (undo_items.size == 0) { 907 return; 908 } 909 910 ui = undo_items.get (undo_items.size - 1); 911 912 redo_state = apply_undo (ui); 913 redo_items.add (redo_state); 914 915 undo_items.remove_at (undo_items.size - 1); 916 } 917 918 public override void redo () { 919 UndoItem ui; 920 921 if (MenuTab.suppress_event) { 922 return; 923 } 924 925 if (redo_items.size == 0) { 926 return; 927 } 928 929 ui = redo_items.get (redo_items.size - 1); 930 apply_undo (ui); 931 redo_items.remove_at (redo_items.size - 1); 932 } 933 934 /** @return redo state. */ 935 public UndoItem apply_undo (UndoItem ui) { 936 KerningClasses classes = BirdFont.get_current_font ().get_kerning_classes (); 937 GlyphRange glyph_range_first, glyph_range_next; 938 Font font = current_font; 939 string l, r; 940 UndoItem redo_state = new UndoItem ("", "", 0, false); 941 double? k; 942 943 l = GlyphRange.unserialize (ui.first); 944 r = GlyphRange.unserialize (ui.next); 945 946 try { 947 glyph_range_first = new GlyphRange (); 948 glyph_range_next = new GlyphRange (); 949 950 glyph_range_first.parse_ranges (ui.first); 951 glyph_range_next.parse_ranges (ui.next); 952 953 if (!ui.has_kerning) { 954 if (glyph_range_first.is_class () || glyph_range_next.is_class ()) { 955 redo_state.first = glyph_range_first.get_all_ranges (); 956 redo_state.next = glyph_range_next.get_all_ranges (); 957 redo_state.has_kerning = true; 958 redo_state.kerning = classes.get_kerning_for_range (glyph_range_first, glyph_range_next); 959 960 classes.delete_kerning_for_class (ui.first, ui.next); 961 } else { 962 963 redo_state.first = ui.first; 964 redo_state.next = ui.next; 965 redo_state.has_kerning = true; 966 k = classes.get_kerning_for_single_glyphs (ui.first, ui.next); 967 968 if (k != null) { 969 redo_state.kerning = (!) k; 970 } else { 971 warning ("No kerning"); 972 } 973 974 classes.delete_kerning_for_pair (ui.first, ui.next); 975 } 976 } else if (glyph_range_first.is_class () || glyph_range_next.is_class ()) { 977 glyph_range_first = new GlyphRange (); 978 glyph_range_next = new GlyphRange (); 979 980 glyph_range_first.parse_ranges (ui.first); 981 glyph_range_next.parse_ranges (ui.next); 982 983 redo_state.first = glyph_range_first.get_all_ranges (); 984 redo_state.next = glyph_range_next.get_all_ranges (); 985 k = classes.get_kerning_for_range (glyph_range_first, glyph_range_next); 986 987 if (k != null) { 988 redo_state.kerning = (!) k; 989 redo_state.has_kerning = true; 990 } else { 991 redo_state.has_kerning = false; 992 } 993 994 classes.set_kerning (glyph_range_first, glyph_range_next, ui.kerning); 995 } else { 996 redo_state.first = ui.first; 997 redo_state.next = ui.next; 998 redo_state.has_kerning = true; 999 k = classes.get_kerning_for_single_glyphs (ui.first, ui.next); 1000 1001 if (k != null) { 1002 redo_state.kerning = (!) k; 1003 redo_state.has_kerning = true; 1004 } else { 1005 redo_state.has_kerning = false; 1006 } 1007 1008 classes.set_kerning_for_single_glyphs (ui.first, ui.next, ui.kerning); 1009 } 1010 } catch (MarkupError e) { 1011 warning (e.message); 1012 } 1013 1014 font.touch (); 1015 GlyphCanvas.redraw (); 1016 1017 return redo_state; 1018 } 1019 1020 public override void zoom_in () { 1021 KerningTools.font_size += 0.1; 1022 1023 if (KerningTools.font_size > 3) { 1024 KerningTools.font_size = 3; 1025 } 1026 1027 KerningTools.zoom_bar.set_zoom (KerningTools.font_size / 3); 1028 GlyphCanvas.redraw (); 1029 } 1030 1031 public override void zoom_out () { 1032 KerningTools.font_size -= 0.1; 1033 1034 if (KerningTools.font_size < 0.3) { 1035 KerningTools.font_size = 0.3; 1036 } 1037 1038 KerningTools.zoom_bar.set_zoom (KerningTools.font_size / 3); 1039 GlyphCanvas.redraw (); 1040 } 1041 1042 public override bool needs_modifier () { 1043 return true; 1044 } 1045 1046 public class UndoItem : GLib.Object { 1047 public string first; 1048 public string next; 1049 public double kerning; 1050 public bool has_kerning; 1051 1052 public UndoItem (string first, string next, double kerning, bool has_kerning) { 1053 this.first = first; 1054 this.next = next; 1055 this.kerning = kerning; 1056 this.has_kerning = has_kerning; 1057 } 1058 } 1059 } 1060 1061 } 1062