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