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