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