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