Commit 3ecac50
Speed optimizations of TTF export code Exporting kerning pairs is now more than a thousand times faster for big fonts.
Changed files (15)
modifiedlibbirdfont/Font.valamodifiedlibbirdfont/Glyph.valamodifiedlibbirdfont/GlyphRange.valamodifiedlibbirdfont/Kerning.valamodifiedlibbirdfont/KerningClasses.valaaddedlibbirdfont/KerningPair.valamodifiedlibbirdfont/Line.valamodifiedlibbirdfont/OpenFontFormat/GlyfTable.valamodifiedlibbirdfont/OpenFontFormat/GposTable.valadeletedlibbirdfont/OpenFontFormat/Kern.valaaddedlibbirdfont/OpenFontFormat/KernList.valaaddedlibbirdfont/OpenFontFormat/KernTable.valaaddedlibbirdfont/OpenFontFormat/PairFormat1.valamodifiedlibbirdfont/PreviewTools.valamodifiedlibbirdfont/SvgFontFormatWriter.vala
Diff
@@ -355,10 +355,6 @@ public class Font : GLib.Object { } public string get_name_for_character (unichar c) {- uint i = 0;- Glyph? gl;- Glyph g;- StringBuilder sb; // FIXME: this is too slow@@ -506,6 +506,17 @@ public class Glyph : FontDisplay { return unichar_code; } + public string get_unichar_string () {+ string? s = (!)get_unichar ().to_string ();+ + if (unlikely (s == null)) {+ warning ("Invalid character.");+ return "".dup ();+ }+ + return (!) s;+ }+ public bool get_show_help_lines () { return show_help_lines; }@@ -16,8 +16,9 @@ namespace BirdFont { public class GlyphRange { - List<UniRange> ranges;+ public List<UniRange> ranges; + /** Glyphs without a corresponding unicode value (ligatures). */ public List<string> unassigned; uint32 len = 0;@@ -371,6 +372,10 @@ public class GlyphRange { return !unique (s, s); } + public bool has_unichar (unichar c) {+ return !unique (c, c);+ }+ private bool unique (unichar start, unichar stop) { foreach (UniRange u in ranges) { if (inside (start, u.start, u.stop)) return false;@@ -19,9 +19,25 @@ namespace BirdFont { public class Kerning : GLib.Object { public double val;+ public Glyph? glyph; public Kerning (double v) { val = v;+ glyph = null;+ }+ + public Kerning.for_glyph (Glyph? g, double v) {+ val = v;+ glyph = g;+ }+ + public Glyph get_glyph () {+ if (unlikely (glyph == null)) {+ warning ("No glyph");+ return new Glyph ("");+ }+ + return (!) glyph; } } @@ -1,5 +1,5 @@ /*- Copyright (C) 2013 Johan Mattsson+ Copyright (C) 2013, 2014 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 @@ -28,8 +28,11 @@ public class KerningClasses : GLib.Object { // kerning for single glyphs Gee.HashMap<string, double?> single_kerning;+ public GLib.List<string> single_kerning_letters_left; // FIXME: this needs to be fast and sorted+ public GLib.List<string> single_kerning_letters_right; - public delegate void KerningIterator (string left, string right, double kerning);+ public delegate void KerningIterator (KerningPair list);+ public delegate void KerningClassIterator (string left, string right, double kerning); public KerningClasses () { classes_first = new GLib.List<GlyphRange> ();@@ -52,6 +55,17 @@ public class KerningClasses : GLib.Object { public void set_kerning_for_single_glyphs (string l, string r, double k) { string left = GlyphRange.serialize (l); string right = GlyphRange.serialize (r);+ string cleft = (!)GlyphRange.unserialize (left).get_char ().to_string ();+ string cright = (!)GlyphRange.unserialize (right).get_char ().to_string ();+ + if (single_kerning_letters_left.index (cleft) < 0) {+ single_kerning_letters_left.append (cleft);+ }++ if (single_kerning_letters_right.index (cright) < 0) {+ single_kerning_letters_right.append (cright);+ }+ single_kerning.set (@"$left - $right", k); } @@ -140,12 +154,15 @@ public class KerningClasses : GLib.Object { unowned GLib.List<GlyphRange> l; int len = (int) classes_first.length (); double? d;+ double time; d = get_kerning_for_single_glyphs (left_glyph, right_glyph); if (d != null) { return (!)d; } + time = GLib.get_real_time ();+ len = (int)classes_first.length (); return_val_if_fail (len == classes_last.length (), 0); return_val_if_fail (len == classes_kerning.length (), 0);@@ -156,10 +173,11 @@ public class KerningClasses : GLib.Object { if (l.data.has_character (left_glyph) && r.data.has_character (right_glyph)) {+ return classes_kerning.nth (i).data.val; } }- + return 0; } @@ -243,7 +261,7 @@ public class KerningClasses : GLib.Object { } } - public void get_classes (KerningIterator kerningIterator) {+ public void get_classes (KerningClassIterator kerningIterator) { for (int i = 0; i < classes_first.length (); i++) { kerningIterator (classes_first.nth (i).data.get_all_ranges (), classes_last.nth (i).data.get_all_ranges (),@@ -251,7 +269,7 @@ public class KerningClasses : GLib.Object { } } - public void get_single_position_pairs (KerningIterator kerningIterator) {+ public void get_single_position_pairs (KerningClassIterator kerningIterator) { double k = 0; foreach (string key in single_kerning.keys) {@@ -266,35 +284,92 @@ public class KerningClasses : GLib.Object { } } - public void all_pairs (KerningIterator kerningIterator) {- // FIXME: THIS CODE IS TOO SLOW- 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;+ public void each_pair (KerningClassIterator iter) {+ all_pairs ((kl) => {+ Glyph g2;+ KerningPair kerning_list = kl;+ string g1 = kerning_list.character.get_name ();+ unowned GLib.List<Kerning> kerning = kerning_list.kerning.first (); - j = 0;- g2 = f.get_glyph_indice (j);- while (g2 != null) {- glyph2 = (!) g2;- - k = KerningClasses.get_instance ().get_kerning (glyph1.get_name (), glyph2.get_name ());- if (k != 0) {- kerningIterator (glyph1.get_name (), glyph2.get_name (), k);+ if (unlikely (is_null (kerning))) {+ warning ("No kerning values.");+ }+ + foreach (Kerning k in kerning_list.kerning) {+ g2 = k.get_glyph ();+ iter (g1, g2.get_name (), kerning.data.val);+ kerning = kerning.next;+ }+ });+ }+ + public void all_pairs (KerningIterator kerningIterator) {+ Font font = BirdFont.get_current_font ();+ GLib.List<Glyph> left_glyphs = new GLib.List<Glyph> ();+ GLib.List<KerningPair> pairs = new GLib.List<KerningPair> ();+ double kerning;+ string right;+ + // Create a list of first glyph in all pairs+ foreach (GlyphRange r in classes_first) {+ foreach (UniRange u in r.ranges) {+ for (unichar c = u.start; c <= u.stop; c++) {+ string name = (!)c.to_string ();+ Glyph? g = font.get_glyph (name);+ if (g != null && left_glyphs.index ((!) g) < 0) {+ left_glyphs.append ((!) g);+ } }+ } - j++;- g2 = f.get_glyph_indice (j);+ // TODO: GlyphRange.unassigned+ }+ + foreach (string name in single_kerning_letters_left) {+ Glyph? g = font.get_glyph (name);+ if (g != null && left_glyphs.index ((!) g) < 0) {+ left_glyphs.append ((!) g);+ }+ }+ + left_glyphs.sort ((a, b) => {+ return strcmp (a.get_name (), b.get_name ());+ });++ // add the right hand glyph and the kerning value+ foreach (Glyph character in left_glyphs) {+ KerningPair kl = new KerningPair (character);+ pairs.append (kl);++ foreach (GlyphRange r in classes_last) {+ foreach (UniRange u in r.ranges) {+ for (unichar c = u.start; c <= u.stop; c++) {+ right = (!)c.to_string ();+ + if (font.has_glyph (right)) {+ kerning = get_kerning (character.get_name (), right);+ kl.add_unique ((!) font.get_glyph (right), kerning);+ }+ }+ }+ // TODO: GlyphRange.unassigned+ }++ // TODO: The get_kerning () function is still rather slow. Optimize it.+ foreach (string right_glyph_name in single_kerning_letters_right) {+ Glyph? gl = font.get_glyph (right_glyph_name);+ if (gl != null) {+ kerning = get_kerning (character.get_name (), right_glyph_name);+ kl.add_unique ((!) gl , kerning);+ } } - i++;- g1 = f.get_glyph_indice (i);+ kl.sort ();+ }+ + // obtain the kerning value+ foreach (KerningPair p in pairs) {+ kerningIterator (p); } } @@ -312,7 +387,16 @@ public class KerningClasses : GLib.Object { while (classes_kerning.length () > 0) { classes_kerning.remove_link (classes_kerning.first ()); }++ while (single_kerning_letters_left.length () > 0) {+ single_kerning_letters_left.remove_link (single_kerning_letters_left.first ());+ } ++ while (single_kerning_letters_right.length () > 0) {+ single_kerning_letters_right.remove_link (single_kerning_letters_right.first ());+ } + GlyphCanvas.redraw (); if (!is_null (MainWindow.get_toolbox ())) { // FIXME: reorganizenew file mode 100644@@ -0,0 +1,47 @@+/*+ Copyright (C) 2014 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;++namespace BirdFont {++/** A list of all kerning pairs for one glyph. */+public class KerningPair : GLib.Object {+ public Glyph character;+ public GLib.List<Kerning> kerning;++ GLib.List<Glyph> right; // FIXME: should be fast and sorted+ + public KerningPair (Glyph left) {+ character = left;+ right = new GLib.List<Glyph> ();+ kerning = new GLib.List<Kerning> ();+ }+ + public void add_unique (Glyph g, double k) {+ if (right.index ((!) g) < 0) {+ right.append ((!) g);+ kerning.append (new Kerning.for_glyph (g, k));+ }+ }+ + public void sort () {+ kerning.sort ((a, b) => {+ return strcmp (((!)a.glyph).get_unichar_string (), ((!)b.glyph).get_unichar_string ());+ }); + }+}+ +}@@ -135,7 +135,6 @@ public class Line : GLib.Object { double ivz = 1/g.view_zoom; double margin = 10; double none = 0;- double tpy; if (!moveable) { return false;@@ -70,6 +70,7 @@ public class GlyfTable : Table { i++; } + warning (@"Glyph $name not found in font."); return -1; } @@ -15,16 +15,10 @@ using Math; namespace BirdFont { -public class PairFormat1 : GLib.Object {- public uint16 left = -1;- public List<Kern> pairs = new List<Kern> ();-}- public class GposTable : Table { GlyfTable glyf_table;- List<Kern> kerning_pairs = new List<Kern> ();- List<PairFormat1> pairs = new List<PairFormat1> ();+ KernList pairs; public GposTable () { id = "GPOS";@@ -38,6 +32,8 @@ public class GposTable : Table { FontData fd = new FontData (); this.glyf_table = glyf_table;+ this.pairs = new KernList (glyf_table);+ printd ("Process GPOS\n"); @@ -88,24 +84,46 @@ public class GposTable : Table { this.font_data = fd; } + uint16 get_max_pairs () {+ return (uint16) ((uint16.MAX - 10) / 3.0);+ }+ // PairPosFormat1 subtable FontData get_pair_pos_format1 () throws GLib.Error { FontData fd = new FontData ();- uint16 pair_set_count; uint coverage_offset;+ uint16 max_pairs_per_table = get_max_pairs ();+ uint16 num_pairs;+ int i;+ uint pair_set_offset;+ uint written;+ uint written_pairs;+ uint last_gid_left;+ uint last_gid_right;+ uint16 pair_set_count; - create_kerning_pairs ();+ if (pairs.get_length () == 0) {+ pairs.fetch_all_pairs ();+ }++ // FIXME: add another table instead of truncating it+ if (pairs.get_length () > max_pairs_per_table) {+ warning (@"Too many kerning pairs. $(pairs.get_length ())");+ num_pairs = max_pairs_per_table;+ }++ num_pairs = (pairs.get_length () < max_pairs_per_table) ? + (uint16) pairs.get_length () : max_pairs_per_table; - coverage_offset = 10 + pairs_offset_length () + pairs_set_length ();+ pair_set_count = (uint16) pairs.get_length_left (); // FIXME: boundaries - // FIXME: add more then current maximum of pairs+ coverage_offset = 10 + pairs_offset_length () + pairs_set_length (); - if (pairs.length () > uint16.MAX || coverage_offset > uint16.MAX) {- print_pairs ();- warning (@"Too many kerning pairs. $(pairs.length ())");+ if (coverage_offset > uint16.MAX) {+ warning (@"Invalid coverage offset. Number of pairs: $(pairs.get_length ())");+ num_pairs = 0;+ coverage_offset = 10; }- - pair_set_count = (uint16) pairs.length (); fd.add_ushort (1); // position format // offset to coverage table from beginning of kern pair table@@ -115,65 +133,133 @@ public class GposTable : Table { fd.add_ushort (pair_set_count); // n pairs // pair offsets orderd by coverage index- uint pair_set_offset = 10 + pairs_offset_length ();- foreach (PairFormat1 k in pairs) {- fd.add_ushort ((uint16) pair_set_offset);- pair_set_offset += 2;- - foreach (Kern pk in k.pairs) {- pair_set_offset += 4;+ pair_set_offset = 10 + pairs_offset_length ();+ + written = 0;+ written_pairs = 0;+ pairs.all_pairs_format1 ((k) => {+ try {+ if (pair_set_offset > uint16.MAX) {+ warning ("Invalid offset.");+ return;+ }+ + fd.add_ushort ((uint16) pair_set_offset);+ written += 2;+ + pair_set_offset += 2;+ + foreach (Kern pk in k.pairs) {+ pair_set_offset += 4;+ }+ } catch (Error e) {+ warning (e.message); }+ }, num_pairs);+ + if (unlikely (written != pairs_offset_length ())) {+ warning (@"Bad pairs_offset_length () calculated: $(pairs_offset_length ()), real length $written"); } // pair table - 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+ i = 0;+ written = 0;+ last_gid_left = 0;+ last_gid_right = 0;+ pairs.all_pairs_format1 ((pn) => {+ try {+ PairFormat1 p = pn;+ uint pairset_length = p.pairs.length ();+ + if (pairset_length > uint16.MAX) {+ warning ("Too many pairs");+ pairset_length = uint16.MAX;+ }+ + if (unlikely (p.left < last_gid_left)) {+ warning (@"Kerning table is not sorted $(p.left) < $last_gid_left.");+ }++ last_gid_left = p.left;+ + fd.add_ushort ((uint16) pairset_length); + written += 2;+ last_gid_right = 0;+ written_pairs = 0;+ 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+ + if (unlikely (k.right < last_gid_right)) {+ warning (@"Kerning table is not sorted $(k.right) < $last_gid_right).");+ }+ + last_gid_right = k.right;++ written += 4;+ written_pairs++;+ } + + if (unlikely (written_pairs != p.pairs.length ())) {+ warning (@"written_pairs != p.pairs.length () $(written_pairs) != $(pairs.get_length ()) pairset_length: $pairset_length");+ }+ + i++;+ } catch (Error e) {+ warning (e.message); }+ }, num_pairs);+ + if (unlikely (pairs_set_length () != written)) {+ warning (@"Bad pair set length: $(pairs_set_length ()), real length: $written"); } ProgressBar.set_progress (0); // reset progress bar - if (fd.length () != coverage_offset) {- warning (@"Bad coverage offset, coverage_offset: $coverage_offset, length: $(fd.length ())");+ if (unlikely (fd.length () != coverage_offset)) {+ warning (@"Bad coverage offset, coverage_offset: $coverage_offset, real length: $(fd.length ())"); warning (@"pairs_offset_length: $(pairs_offset_length ()) pairs_set_length: $(pairs_set_length ())"); } // coverage fd.add_ushort (1); // format- fd.add_ushort ((uint16) pairs.length ());- foreach (PairFormat1 p in pairs) {- fd.add_ushort (p.left); // gid- }+ fd.add_ushort (pair_set_count); - 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");+ written = 0;+ pairs.all_pairs_format1 ((p) => {+ try {+ fd.add_ushort (p.left); // gid+ written += 2;+ } catch (Error e) {+ warning (e.message); }+ }, num_pairs);+ + if (unlikely (written != 2 * pair_set_count)) {+ warning ("written != 2 * pair_set_count"); }+ + return fd; } public uint pairs_set_length () { uint len = 0;- foreach (PairFormat1 p in pairs) {+ + pairs.all_pairs_format1 ((p) => { len += 2 + 4 * p.pairs.length ();- }+ });+ return len; } public uint pairs_offset_length () {- return 2 * pairs.length ();+ return 2 * pairs.pairs.length (); } +/*+// FIXME: DELETE public int get_pair_index (int gid) { int i = 0; foreach (PairFormat1 p in pairs) {@@ -184,43 +270,7 @@ public class GposTable : Table { } return -1; }- - /** Create kerning pairs from classes. */- public void create_kerning_pairs () {- while (kerning_pairs.length () > 0) {- kerning_pairs.remove_link (kerning_pairs.first ());- }- - KerningClasses.get_instance ().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;- }- - 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)));- });- }+*/ } }deleted file mode 100644@@ -1,169 +0,0 @@-/*- Copyright (C) 2012, 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.-*/--namespace BirdFont {--public class Kern : GLib.Object {- public uint16 left;- public uint16 right;- public int16 kerning;- - public Kern (uint16 l, uint16 r, int16 k) {- left = l;- right = r;- kerning = k;- }-}--public class KernTable : Table {- - public static const uint16 HORIZONTAL = 1;- public static const uint16 MINIMUM = 1 << 1;- public static const uint16 CROSS_STREAM = 1 << 2;- public static const uint16 OVERRIDE = 1 << 3;- public static const uint16 FORMAT = 1 << 8;- - GlyfTable glyf_table;- - public List<Kern> kerning = new List<Kern> ();- public int kerning_pairs = 0;- - public KernTable (GlyfTable gt) {- glyf_table = gt;- id = "kern";- }- - public override void parse (FontData dis) throws GLib.Error {- uint16 version;- uint16 sub_tables;- - uint16 subtable_version;- uint16 subtable_length;- uint16 subtable_flags;-- uint16 search_range;- uint16 entry_selector;- uint16 range_shift;- - uint16 n_pairs;- - dis.seek (offset);- - version = dis.read_ushort ();- warn_if_fail (version == 0);- sub_tables = dis.read_ushort ();- - for (uint16 i = 0; i < sub_tables; i++) {- subtable_version = dis.read_ushort (); - subtable_length = dis.read_ushort (); - subtable_flags = dis.read_ushort ();-- n_pairs = dis.read_ushort ();- search_range = dis.read_ushort ();- entry_selector = dis.read_ushort ();- range_shift = dis.read_ushort ();- - // TODO: check more flags- if ((subtable_flags & HORIZONTAL) > 0 && (subtable_flags & CROSS_STREAM) == 0 && (subtable_flags & MINIMUM) == 0) {- parse_pairs (dis, n_pairs);- }- }- }- - public void parse_pairs (FontData dis, uint16 n_pairs) throws Error {- uint16 left;- uint16 right;- int16 k;- - for (int i = 0; i < n_pairs; i++) {- left = dis.read_ushort ();- right = dis.read_ushort ();- k = dis.read_short ();- - kerning.append (new Kern (left, right, k));- } - }- - public void process () throws GLib.Error {- FontData fd = new FontData ();- uint16 n_pairs = 0;- - uint16 gid_left;- - uint16 range_shift = 0;- uint16 entry_selector = 0;- uint16 search_range = 0;- - int i;- - fd.add_ushort (0); // version - fd.add_ushort (1); // n subtables-- fd.add_ushort (0); // subtable version -- KerningClasses.get_instance ().all_pairs ((left, right, k) => {- n_pairs++;- });- - if (n_pairs > (uint16.MAX - 14) / 6.0) {- warning ("Too many kerning pairs!"); - n_pairs = (uint16) ((uint16.MAX - 14) / 6.0);- }- - this.kerning_pairs = n_pairs;- - fd.add_ushort (6 * n_pairs + 14); // subtable length- fd.add_ushort (HORIZONTAL); // subtable flags-- fd.add_ushort (n_pairs);- - search_range = 6 * largest_pow2 (n_pairs);- entry_selector = largest_pow2_exponent (n_pairs);- range_shift = 6 * n_pairs - search_range;- - fd.add_ushort (search_range);- fd.add_ushort (entry_selector);- fd.add_ushort (range_shift);-- gid_left = 0;- - i = 0;- - KerningClasses.get_instance ().all_pairs ((left, right, k) => {- uint16 gid1, gid2;- - try {- // n_pairs is used to truncate this table to prevent buffer overflow- if (i++ < n_pairs) {- gid1 = (uint16) glyf_table.get_gid (left);- gid2 = (uint16) glyf_table.get_gid (right);- - fd.add_ushort (gid1);- fd.add_ushort (gid2);- fd.add_short ((int16) (k * HeadTable.UNITS));- }- } catch (GLib.Error e) {- warning (e.message);- }- });- - fd.pad ();- this.font_data = fd;- }--}---}-new file mode 100644@@ -0,0 +1,125 @@+/*+ Copyright (C) 2014 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.+*/++namespace BirdFont {++public class KernList : GLib.Object {+ public List<PairFormat1> pairs;++ public delegate void KernIterator (Kern k);+ public delegate void PairFormat1Iterator (PairFormat1 k);++ GlyfTable glyf_table;+ uint num_pairs;+ + public KernList (GlyfTable glyf_table) {+ this.glyf_table = glyf_table;+ num_pairs = 0;+ }+ + /** @return number of pairs. */+ public uint fetch_all_pairs () {+ double time = GLib.get_real_time ();+ PairFormat1 current_pairs = new PairFormat1 ();+ + if (pairs.length () > 0 || num_pairs > 0) {+ warning ("Pairs already loaded.");+ }+ + num_pairs = 0;+ while (pairs.length () > 0) {+ pairs.remove_link (pairs.first ());+ }+ + KerningClasses.get_instance ().all_pairs ((kp) => {+ uint16 gid_left, gid_right;+ unowned List<Kerning> kerning;+ KerningPair kerning_pair = kp;+ + if (unlikely (kerning_pair.character.get_name () == "")) {+ warning ("No name for glyph");+ }++ current_pairs = new PairFormat1 ();+ gid_left = (uint16) glyf_table.get_gid (kerning_pair.character.get_name ());+ current_pairs.left = gid_left;+ pairs.append (current_pairs);+ + kerning = kerning_pair.kerning.first ();+ + if (unlikely (is_null (kerning))) {+ warning ("No kerning");+ }+ + if (unlikely (kerning_pair.kerning.length () == 0)) {+ warning ("No pairs.");+ }+ + num_pairs += kerning_pair.kerning.length ();+ foreach (Kerning k in kerning_pair.kerning) {+ gid_right = (uint16) glyf_table.get_gid (k.get_glyph ().get_name ());+ current_pairs.pairs.append (new Kern (gid_left, gid_right, (int16) (kerning.data.val * HeadTable.UNITS)));+ kerning = kerning.next;+ }+ });+ + Test.print_time (@"Feching $(num_pairs) pairs in ", time);+ + return num_pairs;+ }++ /** @return the number of glyphs that is kerned on the left side in + * pair pos format 1.+ */+ public uint get_length_left () {+ return pairs.length ();+ }+ + /** @return the total number of pairs. */+ public uint get_length () {+ return num_pairs;+ }+ + public void all_kern (KernIterator iter, int limit) {+ int i = 0;+ foreach (PairFormat1 p in pairs) {+ foreach (Kern k in p.pairs) {+ if (unlikely (i >= limit)) {+ warning (@"Too many pairs. Limit: $limit");+ return;+ }+ + iter (k);+ i++;+ }+ }+ }++ public void all_pairs_format1 (PairFormat1Iterator iter, int limit = -1) {+ uint i = 0;+ + foreach (PairFormat1 p in pairs) {+ if (unlikely (i >= limit && limit != -1)) {+ warning (@"Too many pairs. Limit: $limit");+ return;+ }+ + iter (p);+ + i++;+ }+ }+}++}new file mode 100644@@ -0,0 +1,182 @@+/*+ Copyright (C) 2012, 2013, 2014 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.+*/++namespace BirdFont {++public class KernTable : Table {+ + public static const uint16 HORIZONTAL = 1;+ public static const uint16 MINIMUM = 1 << 1;+ public static const uint16 CROSS_STREAM = 1 << 2;+ public static const uint16 OVERRIDE = 1 << 3;+ public static const uint16 FORMAT = 1 << 8;+ + GlyfTable glyf_table;+ + KernList pairs;+ + // Only used for loading pairs+ public List<Kern> kerning = new List<Kern> (); // TODO: replace with the KernList+ public uint kerning_pairs = 0;+ + public KernTable (GlyfTable gt) {+ glyf_table = gt;+ id = "kern";+ pairs = new KernList (gt);+ }+ + public override void parse (FontData dis) throws GLib.Error {+ uint16 version;+ uint16 sub_tables;+ + uint16 subtable_version;+ uint16 subtable_length;+ uint16 subtable_flags;++ uint16 search_range;+ uint16 entry_selector;+ uint16 range_shift;+ + uint16 n_pairs;+ + dis.seek (offset);+ + version = dis.read_ushort ();+ warn_if_fail (version == 0);+ sub_tables = dis.read_ushort ();+ + for (uint16 i = 0; i < sub_tables; i++) {+ subtable_version = dis.read_ushort (); + subtable_length = dis.read_ushort (); + subtable_flags = dis.read_ushort ();++ n_pairs = dis.read_ushort ();+ search_range = dis.read_ushort ();+ entry_selector = dis.read_ushort ();+ range_shift = dis.read_ushort ();+ + // TODO: check more flags+ if ((subtable_flags & HORIZONTAL) > 0 && (subtable_flags & CROSS_STREAM) == 0 && (subtable_flags & MINIMUM) == 0) {+ parse_pairs (dis, n_pairs);+ }+ }+ }+ + public void parse_pairs (FontData dis, uint16 n_pairs) throws Error {+ uint16 left;+ uint16 right;+ int16 k;+ + for (int i = 0; i < n_pairs; i++) {+ left = dis.read_ushort ();+ right = dis.read_ushort ();+ k = dis.read_short ();+ + kerning.append (new Kern (left, right, k));+ } + }+ + public void process () throws GLib.Error {+ FontData fd = new FontData ();+ uint16 n_pairs = 0;+ + uint16 gid_left;+ + uint16 range_shift = 0;+ uint16 entry_selector = 0;+ uint16 search_range = 0;+ + int i;+ + uint last_gid_left;+ uint last_gid_right;+ + if (pairs.get_length () == 0) {+ pairs.fetch_all_pairs ();+ }+ + fd.add_ushort (0); // version + fd.add_ushort (1); // n subtables++ fd.add_ushort (0); // subtable version + + if (pairs.get_length () > (uint16.MAX - 14) / 6.0) {+ warning ("Too many kerning pairs!"); + n_pairs = (uint16) ((uint16.MAX - 14) / 6.0);+ } else {+ n_pairs = (uint16) pairs.get_length ();+ }++ this.kerning_pairs = n_pairs;++ fd.add_ushort (6 * n_pairs + 14); // subtable length+ fd.add_ushort (HORIZONTAL); // subtable flags++ fd.add_ushort (n_pairs);+ + search_range = 6 * largest_pow2 (n_pairs);+ entry_selector = largest_pow2_exponent (n_pairs);+ range_shift = 6 * n_pairs - search_range;+ + fd.add_ushort (search_range);+ fd.add_ushort (entry_selector);+ fd.add_ushort (range_shift);++ gid_left = 0;+ i = 0; + last_gid_left = 0;+ last_gid_right = 0;++ pairs.all_kern ((kr) => {+ Kern k = kr;+ + try {+ if (k.left != last_gid_left) {+ last_gid_right = 0;+ }+ + if (unlikely (k.right < last_gid_right)) {+ warning (@"Kerning table is not sorted $(k.right) < $last_gid_right");+ }+ + if (k.left < last_gid_left || k.right < last_gid_right) {+ warning (@"Kerning table is not sorted. $(k.left) < $last_gid_left");+ }+ + last_gid_left = k.left; + last_gid_right = k.right;+ + fd.add_ushort (k.left);+ fd.add_ushort (k.right);+ fd.add_short (k.kerning);+ } catch (GLib.Error e) {+ warning (e.message);+ }+ }, n_pairs);++ fd.pad ();+ this.font_data = fd;+ }++ void print_all () {+ print ("All pairs in kern table:\n");+ foreach (Kern k in kerning) {+ print (@"$(k.left) $(k.right) $(k.kerning)\n");+ }+ }+}+++}+new file mode 100644@@ -0,0 +1,26 @@+/*+ Copyright (C) 2014 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 Math;++namespace BirdFont {+ +/** GSUB pairwise positioning format 1. + * A class that stores kerning information for one letter. + */+public class PairFormat1 : GLib.Object {+ public uint16 left = -1;+ public List<Kern> pairs = new List<Kern> ();+}++}@@ -49,8 +49,6 @@ public class PreviewTools : ToolCollection { /** Export fonts and update html canvas. */ public static void update_preview () {- Font font = BirdFont.get_current_font ();- export_fonts (); if (!Preview.has_html_document ()) {@@ -86,7 +86,7 @@ class SvgFontFormatWriter : Object { } // FIXME: ligatures- KerningClasses.get_instance ().all_pairs ((left, right, k) => {+ KerningClasses.get_instance ().each_pair ((left, right, k) => { string l, r; Font f = BirdFont.get_current_font (); Glyph? gr = f.get_glyph (right);