Commit 7d8765f
a sorted table for glyph collections
Changed files (7)
modifiedsrc/Font.valamodifiedsrc/GlyphCollection.valamodifiedsrc/GlyphRange.valaaddedsrc/GlyphTable.valamodifiedsrc/TestSupplement.valamodifiedsrc/VersionList.valamodifiedsrc/wscript
Diff
@@ -22,11 +22,10 @@ namespace Supplement { class Font : GLib.Object { - // TODO implemet sorted table:- HashTable <string, GlyphCollection> glyph_cache = new HashTable <string, GlyphCollection> (str_hash, str_equal);+ GlyphTable glyph_cache = new GlyphTable (); /** Glyphs that not are tied to a unichar value. */- HashTable <string, GlyphCollection> unassigned_glyphs = new HashTable <string, GlyphCollection> (str_hash, str_equal);+ GlyphTable unassigned_glyphs = new GlyphTable (); /** Last unassigned index */ int next_unindexed = 0;@@ -84,7 +83,7 @@ class Font : GLib.Object { } public uint get_length () {- return glyph_cache.get_keys ().length ();+ return glyph_cache.length () + unassigned_glyphs.length (); } /** Retuns true if the current font has be modified */@@ -260,27 +259,31 @@ class Font : GLib.Object { gr.sort (); - gr.unassigned = unassigned_glyphs.get_keys ();+ gr.set_unassigned (unassigned_glyphs); return gr; } - public void add_glyph (Glyph glyph) {- unowned GlyphCollection? gc = glyph_cache.lookup (glyph.get_name ());+ public void add_glyph (Glyph glyph) + requires (glyph.get_name () != "")+ {+ GlyphCollection? gc = glyph_cache.get (glyph.get_name ()); if (gc == null) {- glyph_cache.insert (glyph.get_name (), new GlyphCollection (glyph));+ glyph_cache.insert (new GlyphCollection (glyph)); } } - public void add_glyph_collection (GlyphCollection glyph_collection) {- unowned GlyphCollection? gc = glyph_cache.lookup (glyph_collection.get_name ());-- if (unlikely (gc != null)) {- warning ("Glyph collection exists.");+ public void add_glyph_collection (GlyphCollection glyph_collection) + requires (glyph_collection.get_name () != "")+ {+ GlyphCollection? gc = get_glyph_collection (glyph_collection.get_name ());+ + if (gc != null) {+ warning ("glyph has been inserted"); } - glyph_cache.insert (glyph_collection.get_name (), glyph_collection);+ glyph_cache.insert (glyph_collection); } public void delete_glyph (string glyph) {@@ -289,10 +292,10 @@ class Font : GLib.Object { /** Obtain all versions and alterntes for this glyph. */ public GlyphCollection? get_glyph_collection (string glyph) {- GlyphCollection? gc = glyph_cache.lookup (glyph);+ GlyphCollection? gc = glyph_cache.get (glyph); if (gc == null) {- gc = unassigned_glyphs.lookup (glyph);+ gc = unassigned_glyphs.get (glyph); if (gc == null) { return null;@@ -319,25 +322,20 @@ class Font : GLib.Object { } public Glyph? get_glyph_indice (unichar glyph_indice) {- List<unowned GlyphCollection> gl;- unowned GlyphCollection gc;+ GlyphCollection gc; - if (glyph_indice >= glyph_cache.size () + unassigned_glyphs.size ()) { + if (glyph_indice >= length ()) { return null;- }+ } - if (glyph_indice >= glyph_cache.size ()) {- gl = unassigned_glyphs.get_values ();- glyph_indice -= glyph_cache.size ();- return null;+ if (glyph_indice >= glyph_cache.length ()) {+ glyph_indice -= glyph_cache.length ();+ gc = (!) unassigned_glyphs.nth (glyph_indice); } else {- gl = glyph_cache.get_values ();+ gc = (!) glyph_cache.nth (glyph_indice); }- - gc = gl.nth (glyph_indice).data;- return ((!) gc).get_current ();-- return null;+ + return gc.get_current (); } public void add_background_image (string file) {@@ -469,7 +467,7 @@ class Font : GLib.Object { os.put_string ("\n"); } - glyph_cache.for_each ((k, gc) => {+ glyph_cache.for_each ((gc) => { try { bool selected; foreach (Glyph g in gc.get_version_list ().glyphs) {@@ -566,12 +564,11 @@ class Font : GLib.Object { } public uint length () {- return glyph_cache.get_keys ().length () + unassigned_glyphs.get_keys ().length ();+ return glyph_cache.length () + unassigned_glyphs.length (); } public bool is_empty () {- uint len = glyph_cache.get_keys ().length ();- return (len == 0);+ return (glyph_cache.length () == 0 && unassigned_glyphs.length () == 0); } public bool load (string path) {@@ -596,23 +593,33 @@ class Font : GLib.Object { /** Callback function for loading glyph in a separate thread. */ public void add_glyph_callback (Glyph g) {- GlyphCollection? gc; - - gc = get_glyph_collection (g.get_name ());+ GlyphCollection? gcl;+ GlyphCollection gc;+ + gcl = get_glyph_collection (g.get_name ());+ + if (gcl != null) {+ warning ("glyph does already exist");+ } if (g.is_unassigned ()) {- gc = new GlyphCollection ();- unassigned_glyphs.insert (@"($(++next_unindexed))", (!) gc);- ((!)gc).insert_glyph (g, true);- } else if (gc == null) {- gc = new GlyphCollection ();- glyph_cache.insert (g.get_name (), (!) gc);- ((!)gc).insert_glyph (g, true); + gc = new GlyphCollection (g);+ g.name = @"($(++next_unindexed))";+ unassigned_glyphs.insert ((!) gc);+ } else if (gcl == null) {+ + print (@"g.name $(g.name)\n");+ + gc = new GlyphCollection (g);+ + print (@"gc.get_name () $(((!)gc).get_name ())\n");+ + glyph_cache.insert ((!) gc); } else { stderr.printf (@"Glyph collection does already have an entry for $(g.get_name ()) char $((uint64) g.unichar_code).\n");- gc = new GlyphCollection ();- unassigned_glyphs.insert (@"($(++next_unindexed))", (!) gc);- ((!)gc).insert_glyph (g, true); + gc = new GlyphCollection (g);+ g.name = @"($(++next_unindexed))";+ unassigned_glyphs.insert ((!) gc); } // take xheight from appropriate lower case letter@@ -888,12 +895,17 @@ class Font : GLib.Object { gc = get_glyph_collection (g.get_name ()); + if (g.get_name () == "") {+ warning ("No name set for glyph.");+ }+ if (gc == null) {- gc = new GlyphCollection ();- glyph_cache.insert (g.get_name (), (!) gc);+ gc = new GlyphCollection (g);+ glyph_cache.insert ((!) gc);+ } else {+ ((!)gc).insert_glyph (g, selected); } - ((!)gc).insert_glyph (g, selected); } /** Parse visual objects and paths */@@ -39,6 +39,13 @@ class GlyphCollection : GLib.Object { public void insert_glyph (Glyph g, bool selected) { versions.add_glyph (g, selected);+ + print (@"insert $(g.get_name ()) at $(versions.glyphs.length ())\n");+ assert (versions.glyphs.length () > 0);+ }+ + public uint length () {+ return versions.glyphs.length (); } public string get_name () {@@ -22,7 +22,7 @@ class GlyphRange { List<UniRange> ranges; List<unichar> index_start; - public List<unowned string> unassigned;+ GlyphTable? unassigned = null; unichar len = 0; @@ -31,6 +31,10 @@ class GlyphRange { public GlyphRange () { } + public void set_unassigned (GlyphTable? unassigned) {+ this.unassigned = unassigned;+ }+ public unowned List<UniRange> get_ranges () { return ranges; }@@ -96,7 +100,13 @@ class GlyphRange { } public unichar get_length () {- return len + unassigned.length ();+ unichar l = len;+ + if (unassigned != null) {+ l += ((!) unassigned).length ();+ }+ + return l; } public void add_range (unichar start, unichar stop) {@@ -145,11 +155,11 @@ class GlyphRange { if (index > ranges.length ()) { i = (int) (index - ranges.length ()); - if (i >= unassigned.length ()) {+ if (unassigned == null || i >= ((!) unassigned).length ()) { return "\0".dup(); } - return unassigned.nth (i).data.dup();+ return ((!) ((!) unassigned).nth (i)).get_name (); } } new file mode 100644@@ -0,0 +1,430 @@+/*+ Copyright (C) 2012 Johan Mattsson++ This program is free software: you can redistribute it and/or modify+ it under the terms of the GNU General Public License as published by+ the Free Software Foundation, either version 3 of the License, or+ (at your option) any later version.++ This program 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 General Public License for more details.++ You should have received a copy of the GNU General Public License+ along with this program. If not, see <http://www.gnu.org/licenses/>.+*/++namespace Supplement {++/** A sorted table of glyphs with search index. */+class GlyphTable : GLib.Object {+ List<Item> data = new List<Item> ();+ + List<RowItem> row1 = new List<RowItem> ();+ List<RowItem> row2 = new List<RowItem> ();+ List<RowItem> row3 = new List<RowItem> ();+ + public GlyphTable () {+ }++ public void remove_all () {+ while (data.length () > 0) {+ data.remove_link (data.first ());+ }+ }++ public void @for_each (Func<GlyphCollection> func) {+ data.foreach ((v) => {+ func (v.glyph_collection);+ });+ }++ unowned List<Item>? find (string n) {+ return find_index (n);+ }++ unowned List<Item>? find_next (string n) {+ return find_next_index (n);+ }+ + public void remove (string name) {+ unowned List<Item>? d = find (name);+ + if (d == null) {+ warning (@"did not find $name");+ return;+ }+ + data.remove_link ((!) d);+ build_index ();+ }++ public GlyphCollection? nth (uint i) {+ unowned List<Item>? d = data.nth (i);+ + if (d == null) {+ warning (@"no glyph for index $i");+ return null;+ }+ + return ((!) d).data.glyph_collection;+ }++ public uint length () {+ return data.length ();+ }++ public GlyphCollection? @get (string name) {+ unowned List<Item>? d = find_index (name);+ + if (d == null) {+ return null;+ }+ + return ((!) d).data.glyph_collection;+ }++ public bool insert (GlyphCollection g) {+ Item item;+ unowned List<Item>? next;+ + if (g.get_name () == "") {+ warning (@"No proper name for glyph collection. g.get_name (): \"$(g.get_name ())\"");+ return false;+ }++ if (find (g.get_name ()) != null) {+ warning (@"Table does already contain character \"$(g.get_name ())\".");+ return false;+ }+ + next = find_next (g.get_name ());+ + item = new Item (g.get_name (), g);+ if (next == null) {+ data.append (item);+ } else {+ data.insert_before ((!) next, item); // _sorted+ }+ + build_index ();+ + if (find (g.get_name ()) == null) {+ warning (@"Can not find glyph \"$(g.get_name ())\".");+ print_all ();+ return false;+ }+ + return true;+ }++ public void print_all () {+ int indice = 0;+ foreach (Item i in data) {+ print (@"$(i.name) indice: $(indice)\n");+ indice++;+ }+ + print ("Index:\n");+ print ("\n");+ print ("row3\n");+ foreach (RowItem i in row3) {+ print (@"$(i.item.data.name)\n");+ }+ + print ("\n");+ print ("row2\n");+ foreach (RowItem i in row2) {+ print (@"$(i.item.data.name)\n");+ }+ + print ("\n");+ print ("row1\n");+ foreach (RowItem i in row1) {+ print (@"$(i.item.data.name)\n");+ } + }++ static int compare (string an, Item b) {+ string? bt = b.glyph_collection.get_name ();+ string bn = (!) bt;+ + uint32 ac = 0;+ uint32 bc = 0;+ + int i1 = 0;+ int i2 = 0;+ + return_if_fail (bt != null);+ + int ca = an.char_count ();+ int cb = bn.char_count ();+ + if (ca != cb) {+ return ca < cb ? 1 : -1;+ }+ + while (true) {+ an.get_next_char (ref i1, out ac);+ bn.get_next_char (ref i2, out bc);+ + if (ac == bc && ac != 0) {+ continue;+ }+ + if (ac == bc) return 0;+ + return (ac < bc) ? 1 : -1;+ }+ + if (ac == bc) {+ return 0;+ }+ + return (ac < bc) ? 1 : -1;+ }+ + public void build_index () {+ uint len;+ RowItem r;+ + while (row1.length () > 0) {+ row1.remove_link (row1.first ());+ }++ while (row2.length () > 0) {+ row2.remove_link (row2.first ());+ }++ while (row3.length () > 0) {+ row3.remove_link (row3.first ());+ }++ if (data.length () == 0) {+ return;+ }+ + // row 1+ len = data.length () / 5;+ if (len == 0) len++;+ for (int i = 0; i < len; i++) {+ r = new RowItem ();+ r.item = data.nth (i * 5);+ r.level = 1;+ row1.append (r);+ }+ r = new RowItem ();+ r.item = data.last ();+ r.level = 1;+ r.last = true;+ row1.append (r); + + // row 2+ len = row1.length () / 5;+ if (len == 0) len++;+ for (int i = 0; i < len; i++) {+ r = new RowItem ();+ r.next_row = row1.nth (i * 5);+ r.item = r.next_row.data.item; + r.level = 2;+ row2.append (r);+ }+ r = new RowItem ();+ r.item = data.last ();+ r.level = 2;+ r.next_row = row1.last (); + r.last = true;+ row2.append (r);+ + // row 3+ len = row2.length () / 5;+ if (len == 0) len++;+ for (int i = 0; i < len; i++) {+ r = new RowItem ();+ r.next_row = row2.nth (i * 5);+ r.item = r.next_row.data.item; + r.level = 3;+ row3.append (r);+ }+ r = new RowItem ();+ r.item = data.last ();+ r.level = 3;+ r.next_row = row2.last ();+ r.last = true; + row3.append (r);+ }++ unowned List<Item>? find_next_index (string n) {+ unowned List<Item>? next = find_next_via_index (n);+ + if (next != null) {+ /*+ if (compare (n, ((!)next).data) == 0) { // not unique+ warning (@"Index is not unique $n == $(((!)next).data.name)");+ }+ */+ //return ((!)next).prev;+ }+ + return next;+ }++ unowned List<Item>? find_index (string n) {+ unowned List<Item>? next = find_next_via_index (n);+ + if (next == null) {+ return null;+ }+ + if (compare (n, ((!)next).data) == 0) {+ return next;+ }+ + if (((!)next) != data.first () && compare (n, ((!)next).prev.data) == 0) {+ return ((!)next).prev; // fetch previous since we are searching for the next one+ }+ + return null; + }+ + unowned List<Item>? find_next_via_index (string n) {+ unowned List<Item> i = data.first ();+ unowned List<RowItem> r;+ unowned List<Item> start, stop;+ int cmp;+ + if (data.length () == 0) {+ return null;+ }++ r = row3.first ();+ int ti = 0;+ while (true) {+ cmp = compare (n, r.data.item.data);+ + ti++;+ + if (cmp == 1) {+ break;+ }+ + if (cmp == 0) {+ return r.data.item;+ }+ + if (r != row3.last ()) {+ r = r.next;+ } else {+ return null;+ }+ } + r = r.data.next_row;++ while (true) {+ cmp = compare (n, r.data.item.data);+ if (cmp == -1) {+ break;+ }++ if (cmp == 0) {+ return r.data.item;+ }+ + if (r == row2.first ()) {;+ return r.data.item;+ } else r = r.prev;+ }+ r = r.data.next_row;+ + while (true) {+ cmp = compare (n, r.data.item.data);+ if (cmp == 1) {+ break;+ }+ + if (cmp == 0) {+ return r.data.item;+ }+ + if (r == row1.last ()) return null;+ else r = r.next;+ }+ i = r.data.item;+ + + while (true) {+ cmp = compare (n, i.data);++ if (cmp == -1) {+ // null?+ return i.next;+ }++ if (cmp == 0) {+ return i;+ }+ + if (i == data.first ()) {+ return i;+ } else { + i = i.prev;+ }+ }+ + return null; + }++ public bool validate_index () {+ int cmp;+ Item prev;+ int index;+ + if (data.length () == 0) {+ return true;+ }+ + prev = data.first ().data;+ index = 0;+ foreach (Item i in data) {+ cmp = compare (prev.name, i);+ + if (cmp == 1) {+ continue;+ }+ + if (cmp == 0 && index != 0) {+ warning (@"Value is not unique: $(i.name).");+ return false;+ }++ if (cmp == -1) {+ warning (@"Value is not sorted: $cmp == cmp $(prev.name) $(i.name).");+ return false;+ }+ + index++;+ }+ + return true;+ }++ class Item : GLib.Object {+ public string name;+ public GlyphCollection glyph_collection;+ + public Item (string n, GlyphCollection gc) {+ glyph_collection = gc;+ name = n;+ }+ }++ class RowItem : GLib.Object {+ public unowned List<RowItem> next_row;+ public unowned List<Item> item;+ public int level = 0;+ public bool last = false;+ }+}++}@@ -33,7 +33,8 @@ class TestCases { public List<Test> test_cases; public TestCases () { - add (test_active_edit_point, "Active edit point"); + add (test_glyph_table, "Glyph table"); +/* add (test_active_edit_point, "Active edit point"); add (test_hex, "Unicode hex values"); add (test_reverse_path, "Reverse path"); add (test_reverse_random_paths, "Reverse random paths"); @@ -42,6 +43,69 @@ class TestCases { add (test_delete_points, "Delete edit points"); add (test_view_result, "View result in web browser"); add (test_save_backup, "Save backup"); +*/ } + + public static void test_glyph_table () { + GlyphTable table = new GlyphTable (); + List<GlyphCollection> gc = new List<GlyphCollection> (); + List<unowned GlyphCollection> gc_copy; + GlyphCollection g; + + return_if_fail (table.length () == 0); + return_if_fail (table.get ("Some glyph") == null); + + // generate test data + for (uint i = 0; i < 1000; i++) { + gc.append (new GlyphCollection (new Glyph (@"TEST $i", i + 'a'))); + } + + // insert in random order + gc_copy = gc.copy (); + for (uint i = 0; gc_copy.length () > 0; i++) { + int t = (int) ((gc_copy.length () - 1) * Random.next_double ()); + g = gc_copy.nth (t).data; + + if (!table.insert (g)) { + warning (@"Failed to insert $(g.get_name ())"); + return; + } + + gc_copy.remove_all (g); + + if (!table.validate_index ()) { + table.print_all (); + warning ("index is invalid"); + return; + } + } + + return_if_fail (table.length () == gc.length ()); + + // validate table + for (uint i = 0; i > 1000; i++) { + g = (!) table.get (gc.nth (i).data.get_name ()); + return_if_fail (gc.nth (i).data == g); + } + + // search + for (int i = 0; i < 2000; i++) { + int t = (int) (999 * Random.next_double ()); + if (table.get (@"TEST $t") == null) { + table.print_all (); + warning (@"Did't find TEST $t in glyph table."); + return; + } + } + + // remove + table.remove ("TEST 0"); + table.remove ("TEST 53"); + return_if_fail (table.get ("TEST 0") == null); + return_if_fail (table.get ("TEST 53") == null); + + // search + return_if_fail (table.get ("TEST 52") != null); + return_if_fail (table.get ("TEST 54") != null); } public static void test_delete_points () { @@ -49,7 +49,7 @@ class VersionList : DropMenu { public Glyph get_current () { if (unlikely (current_version >= glyphs.length ())) {- warning (@"current_version >= glyphs.length ($current_version)");+ warning (@"current_version >= glyphs.length ($current_version >= $(glyphs.length ()))"); return new Glyph (""); } @@ -19,6 +19,7 @@ SUPPLEMENT_MAIN = [ 'GlyphCanvas.vala', 'GlyphCollection.vala', 'GlyphRange.vala', + 'GlyphTable.vala', 'GridTool.vala', 'Icons.vala', 'Kerning.vala',