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.
Redraw after switching from rsb to lsb in spacing tab
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 577 public static void next_pair () { 578 KerningDisplay kd; 579 FontDisplay fd; 580 SpacingTab st; 581 582 fd = MainWindow.get_current_display (); 583 584 if (fd is SpacingTab) { 585 st = (SpacingTab) fd; 586 if (st.right_side_bearing) { 587 st.right_side_bearing = false; 588 } else { 589 st.right_side_bearing = true; 590 st.set_selected_handle (st.selected_handle + 1); 591 } 592 } else if (fd is KerningDisplay) { 593 kd = (KerningDisplay) fd; 594 kd.set_selected_handle (kd.selected_handle + 1); 595 } 596 } 597 598 private static string round (double d) { 599 char[] b = new char [22]; 600 unowned string s = d.format (b, "%.2f"); 601 string n = s.dup (); 602 603 n = n.replace (",", "."); 604 605 if (n == "-0.00") { 606 n = "0.00"; 607 } 608 609 return n; 610 } 611 612 public override void key_press (uint keyval) { 613 unichar c; 614 615 if (MenuTab.has_suppress_event ()) { // don't update kerning while saving font 616 return; 617 } 618 619 c = (unichar) keyval; 620 621 if (suppress_input) { 622 return; 623 } 624 625 if ((keyval == 'u' || keyval == 'U') && KeyBindings.has_ctrl ()) { 626 insert_unichar (); 627 } else { 628 if (keyval == Key.LEFT && KeyBindings.modifier == NONE) { 629 first_update = true; 630 set_space (selected_handle, -1 / KerningTools.font_size); 631 } 632 633 if (keyval == Key.RIGHT && KeyBindings.modifier == NONE) { 634 first_update = true; 635 set_space (selected_handle, 1 / KerningTools.font_size); 636 } 637 638 if (KeyBindings.modifier == NONE 639 || KeyBindings.modifier == SHIFT 640 || KeyBindings.modifier == ALT) { 641 642 if (keyval == Key.BACK_SPACE) { 643 remove_last_character (); 644 } 645 646 if (c == Key.ENTER) { 647 new_line (); 648 } 649 650 add_character (c); 651 } 652 } 653 654 GlyphCanvas.redraw (); 655 } 656 657 void remove_last_character () { 658 if (first_row.size > 0) { 659 GlyphSequence gs = first_row.get (first_row.size - 1); 660 661 if (gs.glyph.size > 0) { 662 gs.glyph.remove_at (gs.glyph.size - 1); 663 return_if_fail (gs.ranges.size > 0); 664 gs.ranges.remove_at (gs.ranges.size - 1); 665 } else { 666 first_row.remove_at (first_row.size - 1); 667 remove_last_character (); 668 } 669 } 670 } 671 672 public void insert_unichar () { 673 TextListener listener; 674 string submitted_value = ""; 675 string unicodestart; 676 677 unicodestart = (KeyBindings.has_shift ()) ? "" : "U+"; 678 listener = new TextListener (t_("Unicode"), unicodestart, t_("Insert")); 679 680 listener.signal_text_input.connect ((text) => { 681 submitted_value = text; 682 683 if (MenuTab.has_suppress_event ()) { 684 return; 685 } 686 687 GlyphCanvas.redraw (); 688 }); 689 690 listener.signal_submit.connect (() => { 691 unichar c; 692 TabContent.hide_text_input (); 693 694 text_input = false; 695 suppress_input = false; 696 697 if (submitted_value.has_prefix ("u+") || submitted_value.has_prefix ("U+")) { 698 c = Font.to_unichar (submitted_value); 699 add_character (c); 700 } else { 701 add_text (submitted_value); 702 } 703 }); 704 705 suppress_input = true; 706 text_input = true; 707 TabContent.show_text_input (listener); 708 } 709 710 public void new_line () { 711 rows.insert (0, get_first_row ()); 712 first_row = new Gee.ArrayList<GlyphSequence> (); 713 GlyphSequence gs = new GlyphSequence (); 714 gs.set_otf_tags (KerningTools.get_otf_tags ()); 715 first_row.add (gs); 716 } 717 718 void add_character (unichar c) { 719 Glyph? g; 720 string name; 721 722 if (MenuTab.has_suppress_event ()) { 723 return; 724 } 725 726 if (!is_modifier_key (c) && c.validate ()) { 727 name = (!) c.to_string (); 728 g = BirdFont.get_current_font ().get_glyph_by_name (name); 729 inser_glyph (g); 730 } 731 } 732 733 public void inser_glyph (Glyph? g) { 734 int handle; 735 736 if (first_row.size == 0) { 737 GlyphSequence gs = new GlyphSequence (); 738 gs.set_otf_tags (KerningTools.get_otf_tags ()); 739 first_row.add (gs); 740 } 741 742 if (g != null) { 743 first_row.get (first_row.size - 1).glyph.add (g); 744 first_row.get (first_row.size - 1).ranges.add (null); 745 746 handle = get_first_row ().glyph.size - 1; 747 set_selected_handle (handle); 748 set_active_handle_index (handle); 749 } 750 } 751 752 public override void motion_notify (double ex, double ey) { 753 double k, y; 754 755 if (MenuTab.has_suppress_event ()) { 756 return; 757 } 758 759 if (!moving) { 760 set_active_handle (ex, ey); 761 } else { 762 y = 1; 763 764 if (Math.fabs (ey - begin_handle_y) > 20) { 765 y = ((Math.fabs (ey - begin_handle_y) / 100) + 1); 766 } 767 768 k = (ex - last_handle_x) / y; // y-axis is for variable precision 769 k /= KerningTools.font_size; 770 set_space (selected_handle, k); 771 GlyphCanvas.redraw (); 772 } 773 774 last_handle_x = ex; 775 } 776 777 public void set_active_handle (double ex, double ey) { 778 double w = 0; 779 double d, kern; 780 double min = double.MAX; 781 int i = 0; 782 int row_index = 0; 783 int col_index = 0; 784 Glyph glyph = new Glyph.no_lines (""); 785 double fs = KerningTools.font_size; 786 double x = 20; 787 788 GlyphRange? gr_left, gr_right; 789 790 Glyph? prev = null; 791 string gl_name = ""; 792 GlyphSequence word_with_ligatures; 793 794 col_index = 0; 795 796 word_with_ligatures = get_first_row (); 797 foreach (Glyph? g in word_with_ligatures.glyph) { 798 if (g == null) { 799 w = 50; 800 warning ("glyph does not exist"); 801 } else { 802 glyph = (!) g; 803 w = glyph.get_width (); 804 } 805 806 gl_name = glyph.get_name (); 807 808 if (prev == null && col_index != 0) { 809 warning (@"previous glyph does not exist row: $row_index column: $col_index"); 810 } 811 812 if (prev == null || col_index == 0) { 813 kern = 0; 814 } else { 815 return_if_fail (col_index < word_with_ligatures.ranges.size); 816 return_if_fail (col_index - 1 >= 0); 817 818 gr_left = word_with_ligatures.ranges.get (col_index - 1); 819 gr_right = word_with_ligatures.ranges.get (col_index); 820 821 kern = get_kerning_for_pair (((!)prev).get_name (), ((!)g).get_name (), gr_left, gr_right); 822 } 823 824 d = Math.pow (fs * (x + kern) - ex, 2); 825 826 if (d < min) { 827 min = d; 828 829 if (ex != fs * (x + kern)) { // don't swap direction after button release 830 right_side_bearing = ex < fs * (x + kern); // right or left side bearing handle 831 } 832 833 if (active_handle != i - row_index) { 834 set_active_handle_index (i - row_index); 835 GlyphCanvas.redraw (); 836 } 837 838 if (col_index == word_with_ligatures.glyph.size || col_index == 0) { 839 set_active_handle_index (-1); 840 } else { 841 set_active_handle_index (active_handle + row_index); 842 } 843 } 844 845 prev = g; 846 x += w + kern; 847 i++; 848 col_index++; 849 } 850 851 row_index++; 852 x = 20; 853 } 854 855 public override void button_release (int button, double ex, double ey) { 856 set_active_handle (ex, ey); 857 moving = false; 858 first_update = true; 859 860 if (button == 3 || text_input) { 861 set_kerning_by_text (); 862 } 863 } 864 865 public void set_kerning_by_text () { 866 TextListener listener; 867 string kerning = @"$(get_kerning_for_handle (selected_handle))"; 868 869 if (MenuTab.has_suppress_event ()) { 870 return; 871 } 872 873 if (selected_handle == -1) { 874 set_selected_handle (0); 875 } 876 877 listener = new TextListener (t_("Kerning"), kerning, t_("Close")); 878 879 listener.signal_text_input.connect ((text) => { 880 string submitted_value; 881 double parsed_value; 882 883 if (MenuTab.has_suppress_event ()) { 884 return; 885 } 886 887 submitted_value = text.replace (",", "."); 888 parsed_value = double.parse (submitted_value); 889 set_absolute_kerning (selected_handle, parsed_value); 890 GlyphCanvas.redraw (); 891 }); 892 893 listener.signal_submit.connect (() => { 894 TabContent.hide_text_input (); 895 text_input = false; 896 suppress_input = false; 897 }); 898 899 suppress_input = true; 900 text_input = true; 901 TabContent.show_text_input (listener); 902 903 GlyphCanvas.redraw (); 904 } 905 906 public override void button_press (uint button, double ex, double ey) { 907 set_active_handle (ex, ey); 908 set_selected_handle (active_handle); 909 begin_handle_x = ex; 910 begin_handle_y = ey; 911 last_handle_x = ex; 912 moving = true; 913 } 914 915 /** Insert text form clipboard. */ 916 public void add_text (string t) { 917 int c; 918 919 if (MenuTab.has_suppress_event ()) { 920 return; 921 } 922 923 c = t.char_count (); 924 for (int i = 0; i <= c; i++) { 925 add_character (t.get_char (t.index_of_nth_char (i))); 926 } 927 928 GlyphCanvas.redraw (); 929 } 930 931 public override void undo () { 932 UndoItem ui; 933 UndoItem redo_state; 934 935 if (MenuTab.has_suppress_event ()) { 936 return; 937 } 938 939 if (undo_items.size == 0) { 940 return; 941 } 942 943 ui = undo_items.get (undo_items.size - 1); 944 945 redo_state = apply_undo (ui); 946 redo_items.add (redo_state); 947 948 undo_items.remove_at (undo_items.size - 1); 949 } 950 951 public override void redo () { 952 UndoItem ui; 953 954 if (MenuTab.has_suppress_event ()) { 955 return; 956 } 957 958 if (redo_items.size == 0) { 959 return; 960 } 961 962 ui = redo_items.get (redo_items.size - 1); 963 apply_undo (ui); 964 redo_items.remove_at (redo_items.size - 1); 965 } 966 967 /** @return redo state. */ 968 public UndoItem apply_undo (UndoItem ui) { 969 KerningClasses classes = BirdFont.get_current_font ().get_kerning_classes (); 970 GlyphRange glyph_range_first, glyph_range_next; 971 Font font = BirdFont.get_current_font (); 972 string l, r; 973 UndoItem redo_state = new UndoItem ("", "", 0, false); 974 double? k; 975 976 l = GlyphRange.unserialize (ui.first); 977 r = GlyphRange.unserialize (ui.next); 978 979 try { 980 glyph_range_first = new GlyphRange (); 981 glyph_range_next = new GlyphRange (); 982 983 glyph_range_first.parse_ranges (ui.first); 984 glyph_range_next.parse_ranges (ui.next); 985 986 if (!ui.has_kerning) { 987 if (glyph_range_first.is_class () || glyph_range_next.is_class ()) { 988 redo_state.first = glyph_range_first.get_all_ranges (); 989 redo_state.next = glyph_range_next.get_all_ranges (); 990 redo_state.has_kerning = true; 991 redo_state.kerning = classes.get_kerning_for_range (glyph_range_first, glyph_range_next); 992 993 classes.delete_kerning_for_class (ui.first, ui.next); 994 } else { 995 996 redo_state.first = ui.first; 997 redo_state.next = ui.next; 998 redo_state.has_kerning = true; 999 k = classes.get_kerning_for_single_glyphs (ui.first, ui.next); 1000 1001 if (k != null) { 1002 redo_state.kerning = (!) k; 1003 } else { 1004 warning ("No kerning"); 1005 } 1006 1007 classes.delete_kerning_for_pair (ui.first, ui.next); 1008 } 1009 } else if (glyph_range_first.is_class () || glyph_range_next.is_class ()) { 1010 glyph_range_first = new GlyphRange (); 1011 glyph_range_next = new GlyphRange (); 1012 1013 glyph_range_first.parse_ranges (ui.first); 1014 glyph_range_next.parse_ranges (ui.next); 1015 1016 redo_state.first = glyph_range_first.get_all_ranges (); 1017 redo_state.next = glyph_range_next.get_all_ranges (); 1018 k = classes.get_kerning_for_range (glyph_range_first, glyph_range_next); 1019 1020 if (k != null) { 1021 redo_state.kerning = (!) k; 1022 redo_state.has_kerning = true; 1023 } else { 1024 redo_state.has_kerning = false; 1025 } 1026 1027 classes.set_kerning (glyph_range_first, glyph_range_next, ui.kerning); 1028 } else { 1029 redo_state.first = ui.first; 1030 redo_state.next = ui.next; 1031 redo_state.has_kerning = true; 1032 k = classes.get_kerning_for_single_glyphs (ui.first, ui.next); 1033 1034 if (k != null) { 1035 redo_state.kerning = (!) k; 1036 redo_state.has_kerning = true; 1037 } else { 1038 redo_state.has_kerning = false; 1039 } 1040 1041 classes.set_kerning_for_single_glyphs (ui.first, ui.next, ui.kerning); 1042 } 1043 } catch (MarkupError e) { 1044 warning (e.message); 1045 } 1046 1047 font.touch (); 1048 GlyphCanvas.redraw (); 1049 1050 return redo_state; 1051 } 1052 1053 public override void zoom_in () { 1054 KerningTools.font_size += 0.1; 1055 1056 if (KerningTools.font_size > 3) { 1057 KerningTools.font_size = 3; 1058 } 1059 1060 KerningTools.zoom_bar.set_zoom (KerningTools.font_size / 3); 1061 GlyphCanvas.redraw (); 1062 } 1063 1064 public override void zoom_out () { 1065 KerningTools.font_size -= 0.1; 1066 1067 if (KerningTools.font_size < 0.3) { 1068 KerningTools.font_size = 0.3; 1069 } 1070 1071 KerningTools.zoom_bar.set_zoom (KerningTools.font_size / 3); 1072 GlyphCanvas.redraw (); 1073 } 1074 1075 public override bool needs_modifier () { 1076 return true; 1077 } 1078 1079 public class UndoItem : GLib.Object { 1080 public string first; 1081 public string next; 1082 public double kerning; 1083 public bool has_kerning; 1084 1085 public UndoItem (string first, string next, double kerning, bool has_kerning) { 1086 this.first = first; 1087 this.next = next; 1088 this.kerning = kerning; 1089 this.has_kerning = has_kerning; 1090 } 1091 } 1092 } 1093 1094 } 1095