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