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