Commit 260d0e6
Kerning by classes This implementation uses the PairPosFormat1 to store kerning values in TTF files and has a new kerning display that allows the user to define kerning classes. Some code to do PairPosFormat2 is included as well but it seems like the PairPosFormat1 is the better solution.
Changed files (26)
modifiedbirdfont/GtkWindow.valamodifiedlibbirdfont/BirdFontFile.valamodifiedlibbirdfont/DrawingTools.valamodifiedlibbirdfont/Expander.valamodifiedlibbirdfont/Font.valamodifiedlibbirdfont/Glyph.valamodifiedlibbirdfont/GlyphRange.valamodifiedlibbirdfont/GlyphSequence.valamodifiedlibbirdfont/Kerning.valaaddedlibbirdfont/KerningClasses.valamodifiedlibbirdfont/KerningDisplay.valaaddedlibbirdfont/KerningRange.valamodifiedlibbirdfont/KerningTools.valamodifiedlibbirdfont/MainWindow.valamodifiedlibbirdfont/OpenFontFormat/DirectoryTable.valamodifiedlibbirdfont/OpenFontFormat/FontData.valamodifiedlibbirdfont/OpenFontFormat/GlyfTable.valamodifiedlibbirdfont/OpenFontFormat/GposTable.valamodifiedlibbirdfont/OpenFontFormat/Kern.valamodifiedlibbirdfont/OpenFontFormat/OpenFontFormatWriter.valamodifiedlibbirdfont/OpenFontFormat/Table.valamodifiedlibbirdfont/SvgFont.valamodifiedlibbirdfont/SvgFontFormatWriter.valamodifiedlibbirdfont/TestCases.valamodifiedlibbirdfont/Tool.valamodifiedlibbirdfont/Toolbox.vala
Diff
@@ -789,8 +789,7 @@ public class GtkWindow : Gtk.Window, NativeWindow { text_box.show (); text_entry.set_text (listener.default_text); text_entry.activate.connect (() => {- listener.set_text (text_entry.text);- hide_text_input ();+ listener.signal_submit (listener.default_text); }); text_entry.grab_focus (); }@@ -89,7 +89,8 @@ class BirdFontFile { public bool write_font_file (string path, bool backup = false) { try { File file = File.new_for_path (path);-+ uint num_kerning_rows;+ if (file.query_file_type (0) == FileType.DIRECTORY) { stderr.printf (@"Can not save font. $path is a directory."); return false;@@ -170,34 +171,6 @@ class BirdFontFile { }); font.glyph_cache.for_each ((gc) => {- Glyph glyph;- - try {- glyph = gc.get_current ();- - foreach (Kerning k in glyph.kerning) {- string l, r;- Glyph? gr = font.get_glyph (k.glyph_right);- Glyph glyph_right;-- if (gr == null) {- warning ("kerning a glyph that does not exist. (" + glyph.name + " -> " + k.glyph_right + ")");- continue;- }- - glyph_right = (!) gr;- - l = Font.to_hex_code (glyph.unichar_code);- r = Font.to_hex_code (glyph_right.unichar_code);- - os.put_string (@"<hkern left=\"U+$l\" right=\"U+$r\" kerning=\"$(k.val)\"/>\n");- }- } catch (GLib.Error e) {- warning (e.message);- }- });-- font.glyph_cache.for_each ((gc) => { GlyphBackgroundImage bg; try {@@ -218,7 +191,6 @@ class BirdFontFile { os.put_string ("\" "); os.put_string (" data=\""); os.put_string (data);- os.put_string (""); os.put_string ("\" />\n"); } }@@ -227,7 +199,23 @@ class BirdFontFile { stderr.printf (@"$(ef.message) \n"); } });- + + uint num_kerning_pairs = KerningClasses.classes_first.length ();+ for (uint i = 0; i < num_kerning_pairs; i++) {+ os.put_string ("<kerning ");+ os.put_string ("left=\"");+ os.put_string (KerningClasses.classes_first.nth (i).data.get_all_ranges ());+ os.put_string ("\" ");+ + os.put_string ("right=\"");+ os.put_string (KerningClasses.classes_last.nth (i).data.get_all_ranges ());+ os.put_string ("\" ");+ + os.put_string ("hadjustment=\"");+ os.put_string (float_point (KerningClasses.classes_kerning.nth (i).data.val));+ os.put_string ("\" />\n");+ }+ os.put_string ("</font>"); } catch (GLib.Error e) {@@ -239,6 +227,11 @@ class BirdFontFile { return true; } + private string float_point (double d) {+ string s = @"$d";+ return s.replace (",", ".");+ }+ /** Get control points in BirdFont format. This function is uses a * cartesian coordinate system with origo in the middle. * @@ -478,6 +471,10 @@ class BirdFontFile { } if (iter->name == "hkern") {+ parse_old_kerning (iter);+ }++ if (iter->name == "kerning") { parse_kerning (iter); } }@@ -501,13 +498,56 @@ class BirdFontFile { } } }- + private void parse_kerning (Xml.Node* node) { string attr_name; string attr_content;+ GlyphRange range_left, range_right;+ double hadjusment = 0;+ KerningRange kerning_range;+ + range_left = new GlyphRange ();+ range_right = new GlyphRange ();+ + for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) {+ attr_name = prop->name;+ attr_content = prop->children->content;+ + if (attr_name == "left") {+ range_left.parse_ranges (attr_content);+ }++ if (attr_name == "right") {+ range_right.parse_ranges (attr_content);+ }++ if (attr_name == "hadjustment") {+ hadjusment = double.parse (attr_content);+ }+ }+ + if (range_left.get_length () > 1) {+ kerning_range = new KerningRange ();+ kerning_range.set_ranges (range_left.get_all_ranges ());+ KerningTools.add_unique_class (kerning_range);+ }++ if (range_right.get_length () > 1) {+ kerning_range = new KerningRange ();+ kerning_range.set_ranges (range_right.get_all_ranges ());+ KerningTools.add_unique_class (kerning_range);+ }++ KerningClasses.set_kerning (range_left, range_right, hadjusment);+ }+ + private void parse_old_kerning (Xml.Node* node) {+ string attr_name;+ string attr_content; string left = ""; string right = ""; string kern = "";+ GlyphRange grr, grl; StringBuilder b; @@ -531,8 +571,14 @@ class BirdFontFile { kern = attr_content; } }- - font.set_kerning (left, right, double.parse (kern));++ grl = new GlyphRange ();+ grl.parse_ranges (left);++ grr = new GlyphRange ();+ grr.parse_ranges (right);++ KerningClasses.set_kerning (grl, grr, double.parse (kern)); } private void parse_background_image (Xml.Node* node) @@ -643,7 +643,18 @@ public class DrawingTools : ToolCollection { MainWindow.get_toolbox ().update_expanders (); MainWindow.get_toolbox ().reset_active_tool ();+ + // let these tools progagate events even when other tools are selectes + foreach (Tool t in draw_tools.tool) {+ t.persistent = true;+ }+ move_background.persistent = true;+ cut_background.persistent = true; + foreach (Tool t in shape_tools.tool) {+ t.persistent = true;+ }+ // Default selection var idle = new IdleSource(); idle.set_callback (() => {@@ -64,21 +64,40 @@ public class Expander : GLib.Object { } private void update_tool_position () {- int i = 0;- double xt = x + 10;- double yt = y - 4;- foreach (var t in tool) {- t.set_properties (xt, yt, w, h);+ double xt = x - 15;+ double yt = y - 30;+ + foreach (Tool t in tool) {+ t.x = xt;+ t.y = yt;+ + xt += t.w + 4; - if (i == 3) {- yt += 31;+ if (xt + t.w > 160 - 20) {+ xt = x - 15;+ yt += t.h + 2;+ }+ }+ + /*+ foreach (Tool t in tool) {+ // FIXME: delete+ //t.set_properties (xt, yt, w, h);+ t.x = xt;+ t.y = yt;+ + print (@"$(xt), $yt\n");+ if (xt + t.w + 10 > 160) {+ yt += t.h; i = 0; xt = x + 10;+ print (@"NEW LINE\n"); } else { i++;- xt += w + 3 + 25;+ print (@"t.w: $(t.w)\n");+ xt += t.w + 5; }- }+ } */ } public void set_offset (double ty) {@@ -79,6 +79,8 @@ public class Font : GLib.Object { /** File format. */ public FontFormat format = FontFormat.BIRDFONT; + KerningClasses kerning_classes;+ public Font () { // positions in pixels at first zoom level // default x-height should be 60 in 1:1@@ -88,6 +90,8 @@ public class Font : GLib.Object { base_line = 0; bottom_position = 20; bottom_limit = 27;+ + kerning_classes = new KerningClasses (); } public void touch () {@@ -427,76 +431,7 @@ public class Font : GLib.Object { public void add_background_image (string file) { background_images.append (file); }-- /** Obtain kerning for pair with name a and b.- * @param a name of left glyph kerning pair- * @param b name of right glyph kerning pair- */- public double get_kerning_by_name (string a, string b) {- Glyph? gl = get_glyph_by_name (a);- Glyph g;- - if (gl == null) {- warning (@"glyph \"$a\" does not exist cannot obtain kerning");- return 0;- }- - g = (!) gl;- - return g.get_kerning (b);- }-- /** Set kerning for pair with name a and b.- * @param a name of left glyph kerning pair- * @param b name of right glyph kerning pair- * @param val kerning- */ - public void set_kerning_by_name (string a, string b, double val) {- Glyph? gl;- Glyph g;- - gl = get_glyph_by_name (a);- - if (unlikely (gl == null)) {- warning (@"glyph \"$a\" is not parsed yet cannot add kerning");- return;- }- - g = (!) gl;- g.add_kerning (b, val); - }-- // TODO: this can be removed- public double get_kerning (string a, string b) {- Glyph? gl = get_glyph (a);- Glyph g;- - if (gl == null) {- warning (@"glyph \"$a\" does not exist cannot obtain kerning");- return 0;- }- - g = (!) gl;- - return g.get_kerning (b);- }-- // TODO: this can be removed- public void set_kerning (string a, string b, double val) {- Glyph? gl;- Glyph g;- - gl = get_glyph (a);- - if (unlikely (gl == null)) {- warning (@"glyph \"$a\" is not parsed yet cannot add kerning");- return;- }- - g = (!) gl;- g.add_kerning (b, val);- }-+ /** Delete temporary rescue files. */ public void delete_backup () { File dir = BirdFont.get_backup_directory ();@@ -618,6 +553,10 @@ public class Font : GLib.Object { } format = FontFormat.FREETYPE;+ + // DELETE+ OpenFontFormatReader or = new OpenFontFormatReader ();+ or.parse_index (path); } if (path.has_suffix (".otf")) {@@ -77,8 +77,6 @@ public class Glyph : FontDisplay { List<Glyph> undo_list = new List<Glyph> (); - public List<Kerning> kerning = new List<Kerning> ();- string glyph_sequence = ""; bool open = true; @@ -179,33 +177,6 @@ public class Glyph : FontDisplay { return active_paths.last ().data; } - - public void add_kerning (string right_glyph, double val) {- Kerning? kl = null;-- foreach (Kerning k in kerning) {- if (k.glyph_right == right_glyph) {- kl = k;- }- }- - if (kl == null) {- kerning.append (new Kerning (right_glyph, val));- } else {- ((!)kl).val = val;- }- }-- public double get_kerning (string right_glyph) {- foreach (Kerning k in kerning) {- if (k.glyph_right == right_glyph) {- return k.val;- }- }- - return 0;- }- // It looks like this can be remove but firefox refuses to load // the generated font if it is removed. See the revert 6bbf and a76d. public void set_unassigned (bool u) {@@ -1742,7 +1713,7 @@ public class Glyph : FontDisplay { juxtaposed = (font.has_glyph (name)) ? (!) font.get_glyph (name) : font.get_space (); if (font.has_glyph (last_name) && font.has_glyph (name)) {- kern = font.get_kerning_by_name (last_name, name);+ kern = KerningClasses.get_kerning (last_name, name); } else { kern = 0; }@@ -1756,7 +1727,7 @@ public class Glyph : FontDisplay { cr.restore (); } - x += juxtaposed.get_width () + font.get_kerning_by_name (glyph.name, name) + kern;+ x += juxtaposed.get_width () + KerningClasses.get_kerning (glyph.name, name) + kern; last_name = name; } @@ -1768,7 +1739,7 @@ public class Glyph : FontDisplay { juxtaposed = (font.has_glyph (name)) ? (!) font.get_glyph (name) : font.get_space (); if (font.has_glyph (last_name) && font.has_glyph (name)) {- kern = font.get_kerning_by_name (name, last_name);+ kern = KerningClasses.get_kerning (name, last_name); } else { kern = 0; }@@ -22,9 +22,35 @@ public class GlyphRange { uint32 len = 0; + bool range_is_class = false;+ public GlyphRange () { }+ + public bool is_class () {+ return range_is_class || len > 1;+ }+ + public void set_class (bool c) {+ range_is_class = true;+ }+ + public bool is_empty () {+ return len == 0;+ }+ + public void empty () {+ while (unassigned.length () > 0) {+ unassigned.remove_link (unassigned.first ());+ }++ while (ranges.length () > 0) {+ ranges.remove_link (ranges.first ());+ } + len = 0;+ }+ public unowned List<UniRange> get_ranges () { return ranges; }@@ -87,6 +113,96 @@ public class GlyphRange { } } + /** Parse ranges on the form a-z. Single characters can be added as well as + * multiple ranges and characters can be added separated by space. The + * word "space" is used to kern against the space character and divis to+ * kern against "-".+ * @param ranges unicode ranges+ * @return true if the range could be fully parsed+ */+ public void parse_ranges (string ranges) throws MarkupError {+ string[] r = ranges.split (" ");+ + foreach (string w in r) {+ w = w.replace (" ", "");+ + if (w == "") {+ continue;+ }+ + if (w.char_count () == 1) {+ add_single (w.get_char ());+ } else if (w == "space") {+ add_single (' ');+ } else if (w == "divis") {+ add_single ('-');+ } else if (w.index_of ("-") > -1) {+ parse_range (w);+ } else {+ throw new MarkupError.PARSE (@"$w is not a single letter or a unicode range.");+ }+ }+ }++ /** A readable representation of ranges, see parse_ranges for parsing + * this string. This function is used for storing ranges in th .bf format.+ */+ public string get_all_ranges () {+ StringBuilder s = new StringBuilder ();+ foreach (UniRange u in ranges) {+ + if (u.start == u.stop) {+ s.append (get_serialized_char (u.start));+ s.append (" ");+ } else {+ s.append (get_serialized_char (u.start));+ s.append ("-");+ s.append (get_serialized_char (u.stop));+ s.append (" ");+ }+ }+ return s.str;+ }+ + private string get_serialized_char (unichar c) {+ StringBuilder s = new StringBuilder ();+ + if (c == ' ') {+ return "space";+ }++ if (c == '-') {+ return "divis";+ }+ + s.append_unichar (c); + return s.str; + }+ + private string unserialize (string c) {+ if (c == "space") {+ return " ";+ }++ if (c == "divis") {+ return "-";+ }+ + return c;+ }+ + private void parse_range (string s) throws MarkupError {+ string[] r = s.split ("-");+ + if (r.length != 2+ || unserialize (r[0]).char_count () != 1 + || unserialize (r[1]).char_count () != 1) {+ throw new MarkupError.PARSE (@"$s is not a valid range, it should be on the form A-Z.");+ }+ + append_range (unserialize (r[0]).get_char (), unserialize (r[1]).get_char ());+ }+ private void append_range (unichar start, unichar stop) { UniRange r; StringBuilder s = new StringBuilder ();@@ -162,6 +278,12 @@ public class GlyphRange { return len; } + public bool has_character (string c) + requires (c.char_count () == 1) {+ unichar s = c.get_char ();+ return !unique (s, s);+ }+ private bool unique (unichar start, unichar stop) { foreach (UniRange u in ranges) { if (inside (start, u.start, u.stop)) return false;@@ -195,16 +317,19 @@ public class GlyphRange { stdout.printf (get_all_ranges ()); } + // FIXME: DELETE+ /* public string get_all_ranges () { StringBuilder s = new StringBuilder (); foreach (UniRange u in ranges) { s.append (Font.to_hex_code (u.start)); s.append (" - "); s.append (Font.to_hex_code (u.stop));- s.append ("\n");+ s.append (" "); } return s.str; }+ */ } }@@ -12,29 +12,49 @@ Lesser General Public License for more details. */ namespace BirdFont {- + public class GlyphSequence { /** A list of all glyphs */ public List <Glyph?> glyph; + /** A list of corresponding glyph ranges if applicable. */ + public List <GlyphRange?> ranges;+ public GlyphSequence () { glyph = new List <Glyph?> ();+ ranges = new List <GlyphRange?> (); } /** Do ligature substitution. * @return a new sequence with ligatures */ public GlyphSequence process_ligatures () {+ // FIXME add range to ligature GlyphSequence ligatures = new GlyphSequence (); Font font = BirdFont.get_current_font (); Glyph liga; GlyphCollection? gc;+ bool has_range = false; foreach (Glyph? g in glyph) { ligatures.glyph.append (g); }+ + foreach (GlyphRange? r in ranges) { + ligatures.ranges.append (r);+ if (r != null) {+ has_range = true;+ }+ }+ + // FIXME: ligatures make this list invalid+ // skip ligature substitution if this sequence contains ranges+ if (has_range) {+ return ligatures;+ } + for (uint i = 0; ; i++) { gc = font.get_ligature (i); @@ -18,11 +18,9 @@ using Xml; namespace BirdFont { public class Kerning : GLib.Object {- public string glyph_right; public double val; - public Kerning (string right, double v) {- glyph_right = right;+ public Kerning (double v) { val = v; } }new file mode 100644@@ -0,0 +1,311 @@+/*+ Copyright (C) 2013 Johan Mattsson++ This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version.++ This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details.+*/++using Cairo;+using Xml;+using Math;++namespace BirdFont {++public class KerningClasses : GLib.Object {+ + // kerning for classes+ public static List<GlyphRange> classes_first;+ public static List<GlyphRange> classes_last;+ public static List<Kerning> classes_kerning;++ public delegate void KerningIterator (string left, string right, double kerning);++ public KerningClasses () {+ init ();+ }+ + public static void init () {+ classes_first = new List<GlyphRange> ();+ classes_last = new List<GlyphRange> ();+ classes_kerning = new List<Kerning> ();+ }+ + public static void set_kerning (GlyphRange left_range, GlyphRange right_range, double k) {+ int index = get_kerning_item_index (left_range, right_range);+ + if (is_null (classes_first)) { // FIXME: find a place to run init+ init ();+ }+ + // keep the list sorted (classes first then single glyphs)+ if (index == -1) {+ classes_first.append (left_range);+ classes_last.append (right_range);+ classes_kerning.append (new Kerning (k));+ update_order (classes_first.length () - 1);+ } else {+ return_if_fail (0 <= index <= classes_first.length ());+ classes_kerning.nth (index).data.val = k;+ update_order (index);+ }+ }+ + static void update_order (uint i) {+ unowned List<GlyphRange> l;+ unowned List<GlyphRange> r;+ unowned List<Kerning> k;+ int first;+ + if (!(0 <= i < classes_first.length ())) {+ warning (@"Index is out of range. Index: $i Length: $(classes_first.length ())");+ return;+ }++ l = classes_first.nth (i);+ r = classes_last.nth (i);+ k = classes_kerning.nth (i);+ first = get_first_non_class ();++ if (first < 0) {+ return;+ }+ + if (first > classes_first.length ()) {+ warning (@"Index is out of range. Index: $first Length: $(classes_first.length ())");+ return;+ }+ + if (l.data.is_class () || r.data.is_class ()) {+ classes_first.insert_before (classes_first.nth (first), l.data);+ classes_last.insert_before (classes_last.nth (first), r.data);+ classes_kerning.insert_before (classes_kerning.nth (first), k.data);++ classes_first.remove_link (l);+ classes_last.remove_link (r);+ classes_kerning.remove_link (k);+ } else {+ classes_first.append (l.data);+ classes_last.append (r.data);+ classes_kerning.append (k.data);++ classes_first.remove_link (l);+ classes_last.remove_link (r);+ classes_kerning.remove_link (k);+ }+ }++ public static double get_kerning_for_range (GlyphRange range_first, GlyphRange range_last) {+ unowned List<GlyphRange> r;+ unowned List<GlyphRange> l;+ int len = (int) classes_first.length ();+ bool search_range_is_class = range_first.is_class () || range_last.is_class ();++ len = (int) classes_first.length ();+ return_val_if_fail (len == classes_last.length (), 0);+ return_val_if_fail (len == classes_kerning.length (), 0);+ + for (int i = len - 1; i >= 0; i--) {+ l = classes_first.nth (i);+ r = classes_last.nth (i);++ if (search_range_is_class && !l.data.is_class () && !r.data.is_class ()) {+ continue;+ }+ + if (l.data.get_all_ranges () == range_first.get_all_ranges ()+ && r.data.get_all_ranges () == range_last.get_all_ranges ()) {+ return classes_kerning.nth (i).data.val;+ }+ }+ + return 0;+ }++ public static int get_first_non_class () {+ unowned List<GlyphRange> r;+ unowned List<GlyphRange> l;+ int len = (int) classes_first.length ();+ + len = (int)classes_first.length ();+ return_val_if_fail (len == classes_last.length (), 0);+ return_val_if_fail (len == classes_kerning.length (), 0);+ + for (int i = 0; i < len; i++) {+ l = classes_first.nth (i);+ r = classes_last.nth (i);+ + if (!l.data.is_class () && !r.data.is_class ()) {+ return i;+ }+ }++ return -1;+ }++ public static int get_kerning_item_index (GlyphRange range_first, GlyphRange range_last) {+ unowned List<GlyphRange> r;+ unowned List<GlyphRange> l;+ int len = (int) classes_first.length ();+ bool search_range_is_class;+ + len = (int)classes_first.length ();+ return_val_if_fail (len == classes_last.length (), 0);+ return_val_if_fail (len == classes_kerning.length (), 0);+ + search_range_is_class = range_first.is_class () || range_last.is_class ();+ + for (int i = len - 1; i >= 0; i--) {+ l = classes_first.nth (i);+ r = classes_last.nth (i);+ + print (@"$(r.data.get_all_ranges ()) == $(range_first.get_all_ranges ())\n");+ print (@"$(l.data.get_all_ranges ()) == $(range_last.get_all_ranges ())\n");+ print (@"$(classes_kerning.nth (i).data.val)\n");+ + if (!l.data.is_class () && !r.data.is_class () && search_range_is_class) {+ continue;+ }+ + if (l.data.get_all_ranges () == range_first.get_all_ranges ()+ && r.data.get_all_ranges () == range_last.get_all_ranges ()) {+ return i;+ }+ }++ return -1;+ }+ + public static double get_kerning (string left_glyph, string right_glyph) {+ unowned List<GlyphRange> r;+ unowned List<GlyphRange> l;+ int len = (int) classes_first.length ();++ len = (int)classes_first.length ();+ return_val_if_fail (len == classes_last.length (), 0);+ return_val_if_fail (len == classes_kerning.length (), 0);+ + for (int i = len - 1; i >= 0; i--) {+ l = classes_first.nth (i);+ r = classes_last.nth (i);+ + if (l.data.has_character (left_glyph)+ && r.data.has_character (right_glyph)) {+ return classes_kerning.nth (i).data.val;+ }+ }+ + return 0;+ }++ public static double get_kern_for_range_to_char (GlyphRange left_range, string right_char) {+ unowned List<GlyphRange> r;+ unowned List<GlyphRange> l;+ int len = (int) classes_first.length ();+ bool search_range_is_class = left_range.is_class ();+ + len = (int)classes_first.length ();+ return_val_if_fail (len == classes_last.length (), 0);+ return_val_if_fail (len == classes_kerning.length (), 0);+ + for (int i = len - 1; i >= 0; i--) {+ l = classes_first.nth (i);+ r = classes_last.nth (i);++ if (!l.data.is_class () && !r.data.is_class () && search_range_is_class) {+ continue;+ }+ + if (l.data.get_all_ranges () == left_range.get_all_ranges ()+ && r.data.has_character (right_char)) {+ return classes_kerning.nth (i).data.val;+ }+ }+ + return 0;+ }++ public static double get_kern_for_char_to_range (string left_char, GlyphRange right_range) {+ unowned List<GlyphRange> r;+ unowned List<GlyphRange> l;+ int len = (int) classes_first.length ();+ bool search_range_is_class = right_range.is_class ();++ len = (int)classes_first.length ();+ return_val_if_fail (len == classes_last.length (), 0);+ return_val_if_fail (len == classes_kerning.length (), 0);+ + for (int i = len - 1; i >= 0; i--) {+ l = classes_first.nth (i);+ r = classes_last.nth (i);++ if (search_range_is_class && !l.data.is_class () && !r.data.is_class ()) {+ continue;+ }+ + if (l.data.has_character (left_char)+ && r.data.get_all_ranges () == right_range.get_all_ranges ()) {+ return classes_kerning.nth (i).data.val;+ }+ }+ + return 0;+ }+ + public static void print_all () {+ print ("Kernings:\n");+ for (int i = 0; i < classes_first.length (); i++) {+ print (classes_first.nth (i).data.get_all_ranges ());+ print (@"\t\t");+ print (classes_last.nth (i).data.get_all_ranges ());+ print ("\t\t");+ print (@"$(classes_kerning.nth (i).data.val)");+ print ("\t\t");+ + if (classes_first.nth (i).data.is_class () || classes_last.nth (i).data.is_class ()) {+ print ("class");+ }+ + print ("\n");+ }+ }+ + public static void all_pairs (KerningIterator kerningIterator) {+ Font f = BirdFont.get_current_font ();+ Glyph? g1, g2;+ Glyph glyph1, glyph2;+ double k;+ int i, j;+ + i = 0;+ g1 = f.get_glyph_indice (i);+ while (g1 != null) {+ glyph1 = (!) g1;+ + j = 0;+ g2 = f.get_glyph_indice (j);+ while (g2 != null) {+ glyph2 = (!) g2;+ + k = KerningClasses.get_kerning (glyph1.get_name (), glyph2.get_name ());+ if (k != 0) {+ kerningIterator (glyph1.get_name (), glyph2.get_name (), k);+ }+ + j++;+ g2 = f.get_glyph_indice (j);+ }+ + i++;+ g1 = f.get_glyph_indice (i);+ }+ }+}++}@@ -19,6 +19,8 @@ namespace BirdFont { /** Kerning context. */ public class KerningDisplay : FontDisplay { + public bool suppress_input = false;+ List <GlyphSequence> row; int active_handle = -1; int selected_handle = -1;@@ -28,6 +30,8 @@ public class KerningDisplay : FontDisplay { double last_handle_x = 0; + bool parse_error = false;+ public KerningDisplay () { GlyphSequence w = new GlyphSequence (); row = new List <GlyphSequence> ();@@ -37,15 +41,62 @@ public class KerningDisplay : FontDisplay { public override string get_name () { return "Kerning"; }++ public void show_parse_error () {+ parse_error = true;+ }++ public static KerningDisplay get_singleton () {+ if (MainWindow.get_current_display () is KerningDisplay) {+ return (KerningDisplay) MainWindow.get_current_display ();+ }+ + warning ("Current display is not for kerning");+ return new KerningDisplay ();+ } public override void draw (WidgetAllocation allocation, Context cr) {+ if (parse_error) {+ draw_error_message (allocation, cr);+ } else {+ draw_kerning_pairs (allocation, cr);+ }+ }+ + public void draw_error_message (WidgetAllocation allocation, Context cr) {+ string line1 = _("The current kerning class is malformed.");+ string line2 = _("Add single characters separated by space and ranges on the form A-Z.");+ string line3 = _("Type “space” to kern the space character.");+ + cr.save ();+ cr.set_source_rgba (1, 1, 1, 1);+ cr.rectangle (0, 0, allocation.width, allocation.height);+ cr.fill ();+ + cr.set_font_size (18);+ cr.set_source_rgba (0.3, 0.3, 0.3, 1);+ cr.move_to (30, 40);+ cr.show_text (line1);+ + cr.set_font_size (14);+ cr.move_to (30, 60);+ cr.show_text (line2);++ cr.set_font_size (14);+ cr.move_to (30, 80);+ cr.show_text (line3);+ + cr.restore ();+ }+ + public void draw_kerning_pairs (WidgetAllocation allocation, Context cr) { Glyph glyph; double x, y, w, kern, alpha; double x2; int i, wi; Glyph? prev;- Font font = BirdFont.get_current_font (); GlyphSequence word_with_ligatures;+ GlyphRange? gr_left, gr_right; i = 0; @@ -65,18 +116,26 @@ public class KerningDisplay : FontDisplay { foreach (GlyphSequence word in row) { wi = 0; word_with_ligatures = word.process_ligatures ();+ gr_left = null;+ gr_right = null; foreach (Glyph? g in word_with_ligatures.glyph) { if (g == null) { continue; } - if (prev == null) {+ if (prev == null || wi == 0) { kern = 0; } else {- kern = font.get_kerning_by_name (((!)prev).get_name (), ((!)g).get_name ());+ return_if_fail (wi < word_with_ligatures.ranges.length ());+ return_if_fail (wi - 1 >= 0);+ + gr_left = word_with_ligatures.ranges.nth (wi - 1).data;+ gr_right = word_with_ligatures.ranges.nth (wi).data;++ kern = get_kerning_for_pair (((!)prev).get_name (), ((!)g).get_name (), gr_left, gr_right); }- - // draw glyph + + // draw glyph if (g == null) { w = 50; alpha = 1;@@ -89,16 +148,29 @@ public class KerningDisplay : FontDisplay { // handle if (active_handle == i) {- //x2 = x + kern; x2 = x + kern / 2.0; cr.save ();- cr.set_source_rgba (153/255.0, 153/255.0, 173/255.0, 1);+ + if (gr_left == null && gr_right == null) {+ cr.set_source_rgba (153/255.0, 153/255.0, 173/255.0, 1);+ } else { + cr.set_source_rgba (46/255.0, 170/255.0, 76/255.0, 1);+ }+ cr.move_to (x2 - 5, y + 20); cr.line_to (x2 + 0, y + 20 - 5); cr.line_to (x2 + 5, y + 20); cr.fill (); + if (gr_left != null || gr_right != null) {+ cr.move_to (x2 - 5, y + 22 - 2);+ cr.line_to (x2 + 5, y + 22 - 2);+ cr.line_to (x2 + 5, y + 22 + 2);+ cr.line_to (x2 - 5, y + 22 + 2);+ cr.fill ();+ }+ cr.set_font_size (10); cr.show_text (((!)g).get_name ()); cr.restore ();@@ -135,8 +207,9 @@ public class KerningDisplay : FontDisplay { string a, b; Font font; int wi = 0;- double kern; GlyphSequence word_with_ligatures;+ int ranges_index = 0;+ GlyphRange? gr_left, gr_right; font = BirdFont.get_current_font (); @@ -147,6 +220,7 @@ public class KerningDisplay : FontDisplay { foreach (GlyphSequence word in row) { word_with_ligatures = word.process_ligatures ();+ ranges_index = 0; foreach (Glyph? g in word_with_ligatures.glyph) { if (g == null) {@@ -156,8 +230,25 @@ public class KerningDisplay : FontDisplay { b = ((!) g).get_name (); if (handle == wi) {- kern = font.get_kerning_by_name (a, b) + val;- font.set_kerning_by_name (a, b, kern);+ return_if_fail (wi < word_with_ligatures.ranges.length ());+ return_if_fail (wi - 1 >= 0);+ + if (word_with_ligatures.ranges.length () != word_with_ligatures.glyph.length ()) {+ warning ("ranges and glyphs does not match.");+ }+ + gr_left = word_with_ligatures.ranges.nth (wi - 1).data;+ gr_right = word_with_ligatures.ranges.nth (wi).data;+ + /*+ * // DELETE+ // Kern table kerning and single glyph positioning+ if (gr_left == null && gr_right == null) {+ kern = font.get_kerning_by_name (a, b) + val;+ font.set_kerning_by_name (a, b, kern);+ }+ */+ set_kerning_pair (a, b, ref gr_left, ref gr_right, val); } wi++;@@ -165,10 +256,83 @@ public class KerningDisplay : FontDisplay { a = b; } }+ }++ /** Class based gpos kerning. */+ public void set_kerning_pair (string a, string b, ref GlyphRange? gr_left, ref GlyphRange? gr_right, double val) {+ double kern;+ GlyphRange grl, grr; + kern = get_kerning_for_pair (a, b, gr_left, gr_right); + try {+ if (gr_left == null) {+ grl = new GlyphRange ();+ grl.parse_ranges (a);+ gr_left = grl; // update the range list+ } else {+ grl = (!) gr_left;+ }++ if (gr_right == null) {+ grr = new GlyphRange ();+ grr.parse_ranges (b);+ gr_right = grr;+ } else {+ grr = (!) gr_right;+ }+ + KerningClasses.set_kerning (grl, grr, kern + val);+ } catch (MarkupError e) {+ // FIXME: unassigned glyphs and ligatures+ warning (e.message);+ }+ + KerningClasses.print_all (); } + /** Class based gpos kerning. */+ public double get_kerning_for_pair (string a, string b, GlyphRange? gr_left, GlyphRange? gr_right) {+ GlyphRange grl, grr;+ try {+ if (gr_left == null) {+ grl = new GlyphRange ();+ grl.parse_ranges (a);+ } else {+ grl = (!) gr_left;+ }++ if (gr_right == null) {+ grr = new GlyphRange ();+ grr.parse_ranges (a);+ } else {+ grr = (!) gr_right;+ }+ + if (gr_left != null && gr_right != null) {+ return KerningClasses.get_kerning_for_range (grl, grr);+ }++ if (gr_left != null && gr_right == null) {+ return KerningClasses.get_kern_for_range_to_char (grl, b);+ }+ + if (gr_left == null && gr_right != null) {+ return KerningClasses.get_kern_for_char_to_range (a, grr);+ }+ + if (gr_left == null && gr_right == null) {+ return KerningClasses.get_kerning (a, b);+ } + } catch (MarkupError e) {+ // FIXME: unassigned glyphs and ligatures+ warning (e.message);+ }+ + warning ("no kerning found");+ + return 0;+ } public override void selected_canvas () { Glyph g; GlyphSequence w;@@ -196,12 +360,36 @@ public class KerningDisplay : FontDisplay { } } + public void add_range (GlyphRange range) {+ Font font = BirdFont.get_current_font ();+ Glyph? glyph;+ + print ("add_range\n");+ + glyph = font.get_glyph_by_name (range.get_char (0));+ + if (glyph == null) {+ glyph = font.get_not_def_character ();+ }+ + row.first ().data.glyph.append ((!) glyph);+ row.first ().data.ranges.append (range);+ + MainWindow.get_glyph_canvas ().redraw ();+ }+ public override void key_press (uint keyval) { unichar c = (unichar) keyval; Glyph? g; Font f = BirdFont.get_current_font (); string name; + parse_error = false;+ + if (suppress_input) {+ return;+ }+ if (KeyBindings.modifier == NONE || KeyBindings.modifier == SHIFT) { if (keyval == Key.BACK_SPACE && row.length () > 0) { @@ -216,6 +404,7 @@ public class KerningDisplay : FontDisplay { name = f.get_name_for_character (c); g = f.get_glyph_by_name (name); row.first ().data.glyph.append (g);+ row.first ().data.ranges.append (null); } } @@ -252,22 +441,14 @@ public class KerningDisplay : FontDisplay { int row_index = 0; int col_index = 0; Glyph glyph = new Glyph.no_lines ("");- Font font = BirdFont.get_current_font ();+ + GlyphRange? gr_left, gr_right; Glyph? prev = null; string gl_name = ""; GlyphSequence word_with_ligatures; foreach (GlyphSequence word in row) {- foreach (Glyph? g in word.glyph) {- if (g == null) print ("null");- else print (((!)g).get_name ());- print ("\n");- }- }- print ("\n");- - foreach (GlyphSequence word in row) { col_index = 0; word_with_ligatures = word.process_ligatures ();@@ -281,10 +462,16 @@ public class KerningDisplay : FontDisplay { gl_name = glyph.get_name (); - if (prev == null) {+ if (prev == null || col_index == 0) { kern = 0; } else {- kern = font.get_kerning_by_name (((!)prev).get_name (), gl_name);+ return_if_fail (col_index < word_with_ligatures.ranges.length ());+ return_if_fail (col_index - 1 >= 0);+ + gr_left = word_with_ligatures.ranges.nth (col_index - 1).data;+ gr_right = word_with_ligatures.ranges.nth (col_index).data;++ kern = get_kerning_for_pair (((!)prev).get_name (), ((!)g).get_name (), gr_left, gr_right); } d = Math.pow (x + kern - ex, 2) + Math.pow (y - ey, 2);@@ -317,6 +504,7 @@ public class KerningDisplay : FontDisplay { } public override void button_release (int button, double ex, double ey) {+ parse_error = false; set_active_handle (ex, ey); selected_handle = -1; }new file mode 100644@@ -0,0 +1,128 @@+/*+ Copyright (C) 2013 Johan Mattsson++ This library is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as + published by the Free Software Foundation; either version 3 of the + License, or (at your option) any later version.++ This library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details.+*/++using Cairo;++namespace BirdFont {++/** Representation of a kerning class. */+public class KerningRange : Tool {+ + public string ranges = "";+ public GlyphRange glyph_range = new GlyphRange (); + bool malformed = false;+ + public KerningRange (string? name = null, string tip = "", unichar key = '\0', uint modifier_flag = 0) {+ base (null , tip, key, modifier_flag);+ + if (name != null) {+ base.name = (!) name;+ }+ + panel_press_action.connect ((selected, button, tx, ty) => {+ KerningDisplay kerning_display = KerningDisplay.get_singleton ();+ + if (button == 3) {+ update_kerning_classes ();+ }+ + if (malformed) {+ kerning_display.show_parse_error ();+ } else if (button == 1 && !glyph_range.is_empty ()) {+ kerning_display.add_range (glyph_range);+ }+ });++ panel_move_action.connect ((selected, button, tx, ty) => {+ active = is_over (tx, ty);+ + if (active) {+ MainWindow.get_tool_tip ().show_text (_("Right click to edit the class and left click to kern glyphs in the class."));+ }+ });++ panel_release_action.connect ((selected, button, tx, ty) => {+ });+ + w = 200;+ h = 17;+ }+ + public void set_ranges (string r) {+ ranges = r;+ name = r;++ try {+ glyph_range.empty ();+ glyph_range.parse_ranges (r);+ glyph_range.set_class (true);+ + malformed = false;+ KerningClasses.print_all ();+ } catch (MarkupError e) {+ KerningClasses.print_all ();+ warning (e.message);+ malformed = true;+ }+ }+ + public void update_kerning_classes () {+ KerningDisplay kerning_display = KerningDisplay.get_singleton ();+ TextListener listener = new TextListener (_("Kerning class"), ranges, _("Set"));+ listener.signal_text_input.connect ((text) => {+ set_ranges (text);+ Toolbox.redraw_tool_box ();+ });+ + listener.signal_submit.connect (() => {+ KerningDisplay.get_singleton ().suppress_input = false;+ MainWindow.native_window.hide_text_input ();+ + // remove all empty classes+ if (ranges == "") {+ glyph_range.empty ();+ KerningTools.remove_empty_classes ();+ }+ });+ + kerning_display.suppress_input = true;+ + MainWindow.native_window.set_text_listener (listener);+ }+ + public override void draw (Context cr) {+ double xt, yt;++ xt = x + 27;+ yt = y + 53;+ + cr.save ();+ + if (malformed) { + cr.set_source_rgba (108/255.0, 0/255.0, 0/255.0, 1);+ } else if (!active) {+ cr.set_source_rgba (99/255.0, 99/255.0, 99/255.0, 1);+ } else {+ cr.set_source_rgba (0, 0, 0, 1);+ }+ + cr.set_font_size (10);+ cr.select_font_face ("Cantarell", FontSlant.NORMAL, FontWeight.NORMAL);+ cr.move_to (xt, yt);+ cr.show_text (name);+ cr.restore ();+ }+}++}@@ -18,8 +18,87 @@ using Math; namespace BirdFont { public class KerningTools : ToolCollection {- public List<Expander> expanders;+ public static List<Expander> expanders;+ public static int next_class = 0;+ public static Expander classes; + public KerningTools () {+ init ();+ MainWindow.get_toolbox ().update_expanders ();+ }+ + public static void init () {+ Expander class_tools = new Expander ();+ classes = new Expander ();+ + Tool new_kerning_class = new Tool ("kerning_class", _("Create new kerning class"));+ new_kerning_class.select_action.connect ((self) => {+ classes.add_tool (new KerningRange (@"Kerning class $(++next_class)"));+ Toolbox.redraw_tool_box ();+ });+ class_tools.add_tool (new_kerning_class);+ + class_tools.set_persistent (false);+ class_tools.set_unique (false);+ class_tools.set_open (true);++ classes.set_persistent (true);+ classes.set_unique (true);+ classes.set_open (true);+ + expanders.append (class_tools);+ expanders.append (classes);+ }+ + public static void add_unique_class (KerningRange kerning_class) {+ KerningRange k;+ + if (is_null (classes)) { // FIXME: export without tools+ init ();+ }+ + foreach (Tool t in classes.tool) {+ if (!(t is KerningRange)) {+ warning ("Tool is not kerning range");+ return;+ }+ + k = (KerningRange) t;+ if (k.glyph_range.get_all_ranges () == kerning_class.glyph_range.get_all_ranges ()) {+ return;+ }+ }+ + classes.add_tool (kerning_class);+ }+ + public static void remove_empty_classes () {+ unowned List<Tool> t = classes.tool.first ();+ KerningRange kr;+ + if (classes.tool.length () == 0) {+ return;+ }+ + while (true) {+ return_if_fail (!is_null (t) && t.data is KerningRange);+ + kr = (KerningRange) t.data;+ if (kr.glyph_range.is_empty ()) {+ classes.tool.remove_link (t);+ remove_empty_classes ();+ Toolbox.redraw_tool_box ();+ return;+ }+ + if (is_null (t.next)) {+ break;+ } else {+ t = t.next;+ }+ }+ }+ public override unowned List<Expander> get_expanders () { return expanders; }@@ -34,8 +34,8 @@ public class MainWindow { key_bindings = new KeyBindings (); glyph_canvas = new GlyphCanvas (); - tools = new Toolbox (glyph_canvas); tabs = new TabBar (); + tools = new Toolbox (glyph_canvas, tabs); menu_tab = new MenuTab (); tool_tip = new TooltipArea (); over_view = new OverView(); @@ -75,7 +75,7 @@ class DirectoryTable : Table { loca_table.process (glyf_table, head_table); post_table.process (); kern_table.process ();- gpos_table.process (kern_table);+ gpos_table.process (glyf_table); offset_table.process (); process_directory (); // this table@@ -86,10 +86,8 @@ class DirectoryTable : Table { tables.append (offset_table); tables.append (this); - if (kern_table.kerning_pairs > 0) {- tables.append (gpos_table);- }- + tables.append (gpos_table);+ // tables.append (gdef_table); // invalid table tables.append (os_2_table); tables.append (cmap_table);@@ -102,11 +100,10 @@ class DirectoryTable : Table { // FIXME: Remove the kern table. // It looks like the old kerning table is no longer needed- // since the most important browsers uses the GPOS table+ // since the most browsers uses the GPOS table // but Windows does not accept fonts without a kern table.- if (kern_table.kerning_pairs > 0) {- tables.append (kern_table);- }+ + // FIXME tables.append (kern_table); tables.append (loca_table); tables.append (maxp_table);@@ -320,7 +317,14 @@ class DirectoryTable : Table { } if (!gpos_table.validate (dis)) {- warning ("gpos_table has invalid checksum");+ warning (@"gpos_table has invalid checksum");+ + if (gpos_table.font_data != null) {+ warning (@"Length: $(((!)gpos_table.font_data).length ())\n");+ } else {+ warning ("font_data is null");+ }+ valid = false; } } catch (GLib.Error e) {@@ -413,6 +413,20 @@ class FontData : Object { // Todo add fraction } }+ + public void append (FontData fd) {+ fd.seek (0);+ for (int i = 0; i < fd.length (); i++) {+ add (fd.read ());+ }+ }+ + public void dump () {+ for (uint32 i = 0; i < length_with_padding (); i++) {+ stdout.printf ("%x " , table_data[i]);+ }+ stdout.printf ("\n");+ } } }@@ -41,10 +41,10 @@ class GlyfTable : Table { public List<uint32> location_offsets; - // list of glyphs sorted in the order we expect to find them in a+ // a list of glyphs sorted in the order we expect to find them in a // ttf font. notdef is the firs glyph followed by null and nonmarkingreturn.- // after that will all assigned glyphs appear in sorted order, all - // remaining unassigned glyphs is in the last part of the file. + // after that will all assigned glyphs appear in sorted (unicode) order, all + // remaining unassigned glyphs will be added in the last part of the file. public List<Glyph> glyphs; uint16 max_points = 0;@@ -60,13 +60,26 @@ class GlyfTable : Table { glyphs = new List<Glyph> (); } + public int get_gid_from_unicode (unichar c) {+ int i = 0;+ foreach (Glyph g in glyphs) {+ if (g.unichar_code == c) {+ return i;+ }+ + i++;+ }+ + return -1;+ }+ public int get_gid (string name) { int i = 0;+ foreach (Glyph g in glyphs) { if (g.name == name) { return i; }- i++; } @@ -307,6 +320,8 @@ class GlyfTable : Table { } public Glyph? read_glyph (string name) throws GLib.Error {+ // Obsolete, can be removed:+/* Glyph? glyph; Glyph g; int i;@@ -336,6 +351,9 @@ class GlyfTable : Table { } return glyph;+ */+ + return null; } public new void parse (FontData dis, CmapTable cmap_table, LocaTable loca, HmtxTable hmtx_table, HeadTable head_table, PostTable post_table, KernTable kern_table) throws GLib.Error {@@ -11,31 +11,44 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. */+using Math; namespace BirdFont { +class PairFormat1 : GLib.Object {+ public uint16 left = -1;+ public List<Kern> pairs = new List<Kern> ();+}+ class GposTable : Table { + GlyfTable glyf_table;+ List<Kern> kerning_pairs = new List<Kern> ();+ List<PairFormat1> pairs = new List<PairFormat1> ();+ public GposTable () { id = "GPOS"; } public override void parse (FontData dis) throws Error { // Not implemented, freetype2 is used for loading fonts+ return_if_fail (offset > 0 && length > 0);++ stdout.printf ("GPOS data:\n");+ dis.seek (offset);+ for (int i = 0; i < length; i++) {+ stdout.printf ("%x ", dis.read ());+ }+ stdout.printf ("\n"); } - public void process (KernTable kern_table) throws GLib.Error {+ public void process (GlyfTable glyf_table) throws GLib.Error { FontData fd = new FontData ();- uint16 num_kerning_values = (uint16) kern_table.kernings.length ();- int i;- int size_of_pair;- - if (num_kerning_values == 0) {- warning ("No kerning to add to gpos table.");- }- - printd (@"Adding $num_kerning_values kerning pairs to gpos table.\n"); + this.glyf_table = glyf_table;++ printd ("Process GPOS\n");+ fd.add_ulong (0x00010000); // table version fd.add_ushort (10); // offset to script list fd.add_ushort (30); // offset to feature list@@ -76,49 +89,413 @@ class GposTable : Table { fd.add_ushort (8); // array of offsets to subtables // MarkFilteringSet ++ fd.append (get_pair_pos_format1 ());+ + fd.pad (); + this.font_data = fd;+ }++ // PairPosFormat1 subtable+ FontData get_pair_pos_format1 () {+ int size_of_pair = 6;+ FontData fd = new FontData ();+ uint16 i;+ + create_kerning_pairs ();+ // FIXME: add more then current maximum of pairs+ + uint16 pair_set_count = (uint16) pairs.length (); - // the kerning pair table- size_of_pair = 6; fd.add_ushort (1); // position format // offset to coverage table from beginning of kern pair table- fd.add_ushort (10 + 2 * num_kerning_values + size_of_pair * num_kerning_values); + fd.add_ushort (10 + pairs_offset_length () + pairs_set_length ()); fd.add_ushort (0x0004); // ValueFormat1 (0x0004 is x advance) fd.add_ushort (0); // ValueFormat2 (0 is null)- fd.add_ushort (num_kerning_values); // n pairs+ fd.add_ushort (pair_set_count); // n pairs // pair offsets orderd by coverage index- i = 0;- foreach (Kern k in kern_table.kernings) {- fd.add_ushort (10 + 2 * num_kerning_values + i * size_of_pair);- i++;+ int pair_set_offset = 10 + pairs_offset_length ();+ foreach (PairFormat1 k in pairs) {+ //fd.add_ushort (10 + 2 * num_kerning_values + i * size_of_pair);+ + print (@"Off: $pair_set_offset\n");+ + fd.add_ushort ((uint16) pair_set_offset);+ pair_set_offset += 2;+ + foreach (Kern pk in k.pairs) {+ pair_set_offset += 4;+ } } + print_pairs ();+ // pair table i = 0; - foreach (Kern k in kern_table.kernings) {- // pair value record- fd.add_ushort (1); // n pair vaules- fd.add_ushort (k.right); // gid to second glyph- fd.add_short (k.kerning); // value of ValueFormat1, horizontal adjustment for advance - // value of ValueFormat2 is null- - ProgressBar.set_progress ((double) (num_kerning_values - ++i) / num_kerning_values);+ int last = -1;+ int index = 0;++ foreach (PairFormat1 p in pairs) {+ fd.add_ushort ((uint16) p.pairs.length ()); + foreach (Kern k in p.pairs) {+ // pair value record + fd.add_ushort (k.right); // gid to second glyph+ fd.add_short (k.kerning); // value of ValueFormat1, horizontal adjustment for advance + // value of ValueFormat2 is null+ } } ProgressBar.set_progress (0); // reset progress bar // coverage fd.add_ushort (1); // format- fd.add_ushort (num_kerning_values); // number of gid- foreach (Kern k in kern_table.kernings) {- fd.add_ushort (k.left); // gid+ fd.add_ushort ((uint16) pairs.length ());+ foreach (PairFormat1 p in pairs) {+ fd.add_ushort (p.left); // gid } - fd.pad (); - this.font_data = fd;+ return fd;+ }+ + public void print_pairs () {+ foreach (PairFormat1 p in pairs) {+ print (@"\nGid: $(p.left)\n");+ foreach (Kern k in p.pairs) {+ print (@"\tKern $(k.right)\t$(k.kerning)\n");+ }+ } }+ + public int pairs_set_length () {+ int len = 0;+ foreach (PairFormat1 p in pairs) {+ len += 2;+ foreach (Kern k in p.pairs) {+ len += 4;+ }+ }+ return len;+ }+ + public int pairs_offset_length () {+ int len = 0;+ foreach (PairFormat1 k in pairs) {+ len += 2;+ }+ return len;+ }++ public int get_pair_index (int gid) {+ int i = 0;+ foreach (PairFormat1 p in pairs) {+ if (p.left == gid) {+ return i;+ }+ i++;+ }+ return -1;+ }+ + public int count_pairs (int index) {+ unowned List<Kern> k = kerning_pairs.nth (index);+ int len = 0;+ + for (int i = index; i < kerning_pairs.length (); i++) {+ if (kerning_pairs.nth (i).data.left == k.data.left) {+ len++;+ } else {+ break;+ }+ }+ + return len;+ }+ + /** Create kerning pairs from classes. */+ public void create_kerning_pairs () {+ while (kerning_pairs.length () > 0) {+ kerning_pairs.remove_link (kerning_pairs.first ());+ }+ + KerningClasses.all_pairs ((left, right, kerning) => {+ uint16 gid1, gid2;+ PairFormat1 pair;+ int pair_index;+ + gid1 = (uint16) glyf_table.get_gid (left);+ gid2 = (uint16) glyf_table.get_gid (right);+ + if (gid1 == -1) {+ warning (@"gid is -1 for \"$left\"");+ return;+ }+ + if (gid2 == -1) {+ warning (@"gid is -1 for \"$right\"");+ return;+ }+ + print (@"kerning: $left $right $kerning\n");+ + pair_index = get_pair_index (gid1);+ if (pair_index == -1) {+ pair = new PairFormat1 ();+ pair.left = gid1; + pairs.append (pair);+ } else {+ pair = pairs.nth (pair_index).data;+ }+ + pair.pairs.append (new Kern (gid1, gid2, (int16)(kerning * HeadTable.UNITS)));+ });+ }++ // I tried several different ways to organize the code with + // kerning classes but I could not find a way to use the+ // PairPosFormat2 without defining a huge array of kerningpairs+ // for every glyph in the class. Storing the kerning pairs + // on a flat array seems like the better solution with less+ // redundant kerning pairs, but this code might be useful in+ // the future.+ /** PairPosFormat2 subtable. */+ FontData get_pair_pos_format_2 () {+ FontData fd, left_classes, right_classes, coverage;+ uint16 coverage_offset, num_kerning_values;+ uint16 left_class_offset, right_class_offset;+ uint16 pairpos2_offset;+ uint16 num_class_left, num_class_right; + fd = new FontData ();+/* + pairpos2_offset = (uint16) fd.length ();+ left_classes = get_classes ();+ num_kerning_values = (uint16) KerningClasses.classes_left.length ();+ coverage_offset = 16 + 2 * num_kerning_values * num_kerning_values;+ coverage = get_coverage_format_2 ();+ left_class_offset = (uint16) (coverage_offset + coverage.length ());+ + num_class_left = num_kerning_values;+ num_class_right = num_kerning_values;+ + printd (@"Adding $num_kerning_values pairs to subtable of type PairPosFormat2\n");+ + fd.add_ushort (2); // position format+ + // offset to coverage table from beginning of kern pair table+ fd.add_ushort (coverage_offset); + fd.add_ushort (0x0004); // ValueFormat1 (0x0004 is x advance)+ fd.add_ushort (0); // ValueFormat2 (0 is null)+ + // Offset to classes for left kerning classes+ fd.add_ushort (left_class_offset); + + printd (@"leftclass offset: $(coverage_offset) + $(coverage.length ())\n");+ right_class_offset = left_class_offset;+ + // Offset to classes for right kerning classes+ fd.add_ushort (right_class_offset); + + fd.add_ushort (num_class_left); // numberof class left kerning classes+ fd.add_ushort (num_class_right); // numberof class right kerning classes+ + double k;+ for (int i = 0; i < num_class_left; i++) {+ for (int j = 0; j < num_class_right; j++) {+ k = KerningClasses.kerning[i][j];+ //fd.add_ushort ((uint16) rint (k * HeadTable.UNITS));+ fd.add_short (1000 - j);+ }+ }+ + // coverage + fd.append (coverage);++ // left classes+ if (left_class_offset != fd.length () - pairpos2_offset) {+ warning (@"Bad class offset, $left_class_offset != $(fd.length () - pairpos2_offset)");+ }+ + fd.append (left_classes);+ + // right classes+ if (right_class_offset != fd.length () - pairpos2_offset) {+ //warning (@"Bad class offset, $right_class_offset != $(fd.length () - pairpos2_offset)");+ }+ + //fd.append (right_classes);+ + print ("left: ");+ left_classes.dump ();++ print ("right:");+ left_classes.dump ();++ print ("All:"); + fd.dump ();+ */+ return fd; + }++ FontData get_coverage_format_2 () throws GLib.Error {+ FontData fd = new FontData ();+/* List<uint16> coverage_left = new List<uint16> ();+ uint16 last_gindex;+ GlyphRange r;+ int gid, length;+ + if (KerningClasses.classes_first.length () == 0) {+ warning ("no kerning pairs");+ KerningClasses.print_all ();+ }+ + for (int j = 0; j < KerningClasses.classes.length (); j++) {+ r = KerningClasses.classes.nth (j).data;+ for (int k = 0; k < (uint16)r.get_length (); k++) {+ gid = glyf_table.get_gid_from_unicode (r.get_char (k).get_char ()); // TODO: glyph names+ + if (gid == -1) {+ warning ("glyph not found");+ } else {+ coverage_left.append ((uint16)gid);+ }+ }+ }++ if (coverage_left.length () == 0) {+ warning ("no gid for kerning pairs");+ }++ length = 0;+ last_gindex = 0;+ foreach (uint16 gindex in coverage_left) {+ if (last_gindex != gindex) {+ length++;+ }+ }+ + printd (@"GPOS class coverage length: $(length)\n");++ fd.add_ushort (2); // format+ fd.add_ushort ((uint16) length);+ + coverage_left.sort ((a, b) => {+ return a - b;+ });+ + last_gindex = 0;+ int next_index = 0;+ foreach (uint16 gindex in coverage_left) {+ if (last_gindex != gindex) {+ fd.add_ushort (gindex);+ fd.add_ushort (gindex);+ fd.add_ushort ((uint16)next_index); // stop - start + 1 ?+ next_index++;+ }+ last_gindex = gindex;+ }+*/+ return fd;+ }+ + FontData get_coverage_format_1 () throws GLib.Error {+ FontData fd = new FontData ();+/* List<uint16> coverage_left = new List<uint16> ();+ uint16 last_gindex;+ GlyphRange r;+ int gid;+ int length;+ + if (KerningClasses.classes.length () == 0) {+ warning ("no kerning pairs");+ KerningClasses.print_all ();+ }+ + for (int j = 0; j < KerningClasses.classes.length (); j++) {+ r = KerningClasses.classes.nth (j).data;+ for (int k = 0; k < (uint16)r.get_length (); k++) {+ gid = glyf_table.get_gid_from_unicode (r.get_char (k).get_char ()); // TODO: glyph names+ + if (gid == -1) {+ warning ("glyph not found");+ } else {+ coverage_left.append ((uint16)gid);+ }+ }+ }++ if (coverage_left.length () == 0) {+ warning ("no gid for kerning pairs");+ }+ + length = 0;+ last_gindex = 0;+ foreach (uint16 gindex in coverage_left) {+ if (last_gindex != gindex) {+ length++;+ }+ }+ + fd.add_ushort (1); // format+ fd.add_ushort ((uint16) length); // TODO: overflow+ + coverage_left.sort ((a, b) => {+ return a - b;+ });+ + last_gindex = 0;+ foreach (uint16 gindex in coverage_left) {+ if (last_gindex != gindex) {+ fd.add_ushort (gindex);+ }+ last_gindex = gindex;+ }+ */+ return fd;+ }+ + FontData get_classes () throws GLib.Error {+ List<uint16> classes;+ FontData fd = new FontData ();+/* uint num_ranges;+ GlyphRange r;+ GlyphRange r_sorted;+ uint length = KerningClasses.classes.length ();++ classes = new List<uint16> (); // start and stop+ num_ranges = 0;+ for (int j = 0; j < length; j++) { + r = KerningClasses.classes.nth (j).data;+ + r_sorted = new GlyphRange ();+ for (int m = 0; m < r.get_length (); m++) {+ r_sorted.add_single (r.get_char (m).get_char ());+ r_sorted.sort ();+ }+ + print ("Sorted range:\n");+ r_sorted.print_all ();+ + for (int k = 0; k < (uint16)r_sorted.get_length (); k++) {+ foreach (UniRange ur in r_sorted.get_ranges ()) {+ classes.append ((uint16) glyf_table.get_gid_from_unicode (ur.start));+ classes.append ((uint16) glyf_table.get_gid_from_unicode (ur.stop));+ classes.append (1); // end of range+ num_ranges++;+ }+ }+ }++ fd.add_ushort (2); // class format+ fd.add_ushort ((uint16) num_ranges); // ranges + + foreach (uint16 l in classes) {+ fd.add_ushort (l);+ }+*/+ return fd;+ } } }@@ -134,11 +134,9 @@ class KernTable : Table { fd.add_ushort (0); // subtable version - foreach (Glyph g in glyf_table.glyphs) {- foreach (Kerning k in g.kerning) {- n_pairs++;- }- }+ KerningClasses.all_pairs ((left, right, k) => {+ n_pairs++;+ }); if (n_pairs > (uint16.MAX - 14) / 6.0) { warning ("Too many kerning pairs!"); @@ -163,31 +161,20 @@ class KernTable : Table { gid_left = 0; i = 0;- foreach (Glyph g in glyf_table.glyphs) {+ + KerningClasses.all_pairs ((left, right, k) => {+ uint16 gid1, gid2; - foreach (Kerning k in g.kerning) {- // n_pairs is used to truncate this table to prevent buffer overflow- if (n_pairs == i++) {- break;- }-- gid_right = glyf_table.get_gid (k.glyph_right);- - if (gid_right == -1) {- warning ("right glyph not found in kerning table");- }+ // n_pairs is used to truncate this table to prevent buffer overflow+ if (n_pairs > i++) {+ gid1 = (uint16) glyf_table.get_gid (left);+ gid2 = (uint16) glyf_table.get_gid (right); - kern = new Kern (gid_left, (uint16)gid_right, (int16) (k.val * HeadTable.UNITS));- - fd.add_ushort (kern.left);- fd.add_ushort (kern.right);- fd.add_short (kern.kerning);- - kernings.append (kern);+ fd.add_ushort (gid1);+ fd.add_ushort (gid2);+ fd.add_short ((int16) (k * HeadTable.UNITS)); }- - gid_left++;- }+ }); fd.pad (); this.font_data = fd;@@ -76,7 +76,7 @@ class OpenFontFormatWriter : Object { } void printd (string s) {- //print (s);+ print (s); } }@@ -1,3 +1,4 @@+ /* Copyright (C) 2012, 2013 Johan Mattsson @@ -74,6 +74,7 @@ class SvgFont : GLib.Object { StringBuilder sl, sr; string attr_name = ""; string attr_content;+ GlyphRange grr, grl; for (Xml.Attr* prop = node->properties; prop != null; prop = prop->next) { attr_name = prop->name;@@ -102,9 +103,8 @@ class SvgFont : GLib.Object { kerning = double.parse (attr_content) * units; } }- - - // TODO: ranges and sequences for u1 & u2 + g1 & g2+ + // FIXME: ranges and sequences for u1 & u2 + g1 & g2 foreach (string lk in left.split (",")) { foreach (string rk in right.split (",")) { l = get_unichar (lk);@@ -116,7 +116,13 @@ class SvgFont : GLib.Object { sr = new StringBuilder (); sr.append_unichar (r); - font.set_kerning (sl.str, sr.str, -kerning); + grl = new GlyphRange ();+ grl.parse_ranges (sl.str);+ + grr = new GlyphRange ();+ grr.parse_ranges (sr.str);+ + KerningClasses.set_kerning (grl, grr, -kerning); } } }@@ -76,7 +76,7 @@ class SvgFontFormatWriter : Object { b.append_unichar (glyph.get_unichar ()); if (glyph.get_unichar () >= ' ' && b.str.validate ()) {- if (b.str == "\"" || b.str == "&" || b.str == "<") {+ if (b.str == "\"" || b.str == "&" || b.str == "<" || b.str == ">") { uni = Font.to_hex_code (glyph.get_unichar ()); put (@"<glyph unicode=\"&#x$(uni);\" horiz-adv-x=\"$(glyph.get_width ())\" d=\"$(glyph.get_svg_data ())\" />"); } else {@@ -87,35 +87,27 @@ class SvgFontFormatWriter : Object { // FIXME: ligatures - indice = 0;- while (true) {- g = font.get_glyph_indice (indice++);+ KerningClasses.all_pairs ((left, right, k) => {+ string l, r;+ Font f = BirdFont.get_current_font ();+ Glyph? gr = f.get_glyph (right);+ Glyph? gl = f.get_glyph (left);+ Glyph glyph_right;+ Glyph glyph_left; - if (g == null) {- break;+ if (gr == null || gl == null) {+ warning ("kerning glyph that does not exist.");+ return; } - glyph = (!) g;+ glyph_right = (!) gr;+ glyph_left = (!) gl; - foreach (Kerning k in glyph.kerning) {- string l, r;- Font f = BirdFont.get_current_font ();- Glyph? gr = f.get_glyph (k.glyph_right);- Glyph glyph_right;- - if (gr == null) {- warning ("kerning glyph that does not exist.");- continue;- }- - glyph_right = (!) gr;- - l = Font.to_hex_code (glyph.unichar_code);- r = Font.to_hex_code (glyph_right.unichar_code);- - os.put_string (@"<hkern u1=\"&#x$l;\" u2=\"&#x$r;\" k=\"$(-k.val)\"/>\n");- }- } + l = Font.to_hex_code (glyph_left.unichar_code);+ r = Font.to_hex_code (glyph_right.unichar_code);+ + os.put_string (@"<hkern u1=\"&#x$l;\" u2=\"&#x$r;\" k=\"$(-k)\"/>\n");+ }); put ("</font>"); put ("</defs>");@@ -694,6 +694,7 @@ class TestCases { public static void test_glyph_ranges () { GlyphRange gr = new GlyphRange ();+ GlyphRange gr_az = new GlyphRange (); gr.add_range ('b', 'c'); gr.add_single ('d');@@ -728,6 +729,16 @@ class TestCases { return_if_fail (gr.get_char (2) == "d"); return_if_fail (gr.get_char (3) == "e"); return_if_fail (gr.get_char (4) == "◊");+ + // a-z + gr_az.add_range ('a', 'z');+ if (!gr_az.has_character ("g")) {+ warning ("Can not g in range a-z ");+ }+ + if (gr_az.has_character ("å")) {+ warning ("Range a-z has å");+ } } public static void test_glyph_table () {@@ -20,8 +20,8 @@ public class Tool : GLib.Object { public double x = 0; public double y = 0;- public double w = 0;- public double h = 0;+ public double w = 30;+ public double h = 30; protected bool active = false; protected bool selected = false;@@ -83,7 +83,7 @@ public class Tool : GLib.Object { private static ImageSurface? deselected_button = null; private static ImageSurface? active_deselected_button = null; - public bool persistent = true;+ public bool persistent = false; /** Create tool with a certain name and load icon "name".png */ public Tool (string? name = null, string tip = "", unichar key = '\0', uint modifier_flag = 0) {@@ -143,20 +143,13 @@ public class Tool : GLib.Object { public bool is_selected () { return selected; }- - public virtual void set_properties (double tx, double ty, double tw, double th) {- x = tx;- y = ty;- w = tw;- h = th;- }-+ public string get_tip () { return tip; } - public bool is_over (double xp, double yp) { - return (x <= xp <= x + w + 12 + 15 && y + 15 <= yp <= y + w + 12 + 30); + public bool is_over (double xp, double yp) {+ return (x <= xp - 28 <= x + w && y <= yp - 37 <= y + h); } public bool set_selected (bool a) {@@ -34,20 +34,32 @@ public class Toolbox : GLib.Object { ImageSurface? toolbox_background = null; - public Toolbox (GlyphCanvas glyph_canvas) {+ public Toolbox (GlyphCanvas glyph_canvas, TabBar tab_bar) { drawing_tools = new DrawingTools (glyph_canvas); kerning_tools = new KerningTools (); current_set = drawing_tools; toolbox_background = Icons.get_icon ("toolbox_background.png");+ + tab_bar.signal_tab_selected.connect ((tab) => {+ if (tab.get_label () == "Kerning") {+ current_set = kerning_tools;+ } else {+ current_set = drawing_tools;+ }+ + update_expanders ();+ redraw (0, 0, allocation_width, allocation_height);+ }); } - public void key_press (uint keyval) { foreach (Expander exp in current_set.get_expanders ()) { foreach (Tool t in exp.tool) { t.set_active (false); - if (t.key == keyval && t.modifier_flag == NONE && KeyBindings.modifier == NONE) {+ if (t.key == keyval + && t.modifier_flag == NONE + && KeyBindings.modifier == NONE) { select_tool (t); } }@@ -119,7 +131,7 @@ public class Toolbox : GLib.Object { update = exp.set_active (a); if (update) {- MainWindow.get_toolbox ().redraw ((int) exp.x - 10, (int) exp.y - 10, (int) (exp.x + exp.w + 10), (int) (exp.y + exp.h + 10));+ redraw ((int) exp.x - 10, (int) exp.y - 10, (int) (exp.x + exp.w + 10), (int) (exp.y + exp.h + 10)); } if (exp.is_open ()) {@@ -135,7 +147,7 @@ public class Toolbox : GLib.Object { } if (update) {- MainWindow.get_toolbox ().redraw (0, 0, allocation_width, allocation_height);+ redraw (0, 0, allocation_width, allocation_height); } t.panel_move_action (t, x, y);@@ -144,6 +156,11 @@ public class Toolbox : GLib.Object { } } + public static void redraw_tool_box () {+ Toolbox t = MainWindow.get_toolbox ();+ t.redraw (0, 0, t.allocation_width, t.allocation_height);+ }+ public void reset_active_tool () { foreach (Expander exp in current_set.get_expanders ()) { foreach (Tool t in exp.tool) {@@ -187,9 +204,8 @@ public class Toolbox : GLib.Object { redraw ((int) exp.x - 10, (int) exp.y - 10, allocation_width, (int) (allocation_height - exp.y + 10)); } - // FIXME: Set persistent to false for && exp == draw_tools || t == move_background - // || t == cut_background || exp == shape_tool if (tool.persistent) {+ print ("PERSIS\n"); current_tool = tool; } }@@ -206,11 +222,6 @@ public class Toolbox : GLib.Object { } } }-- // FIXME:- //if (name == cut_background.get_name ()) {- // return cut_background;- //} warning ("No tool found for name \"%s\".\n", name); @@ -233,7 +244,7 @@ public class Toolbox : GLib.Object { foreach (Expander e in current_set.get_expanders ()) { if (p != null) { pp = (!) p;- e.set_offset (pp.y + pp.margin + 12);+ e.set_offset (pp.y + pp.margin + 9); } p = e;