Clone in a terminal:
Commit 1a7b08e
Johan Mattsson - 2015-12-15T19:45:10+00:00
1a7b08e939cb75ac0ef4a3b9d10e4cb1b38d2947johan.mattsson.m@gmail.com

Speed optimizations for the overview tab This is done by caching labels and creating an array that stores the length entire lenght of the set at all unicode ranges in GlyphRange. The current version runs in a single thread but some work has been done in order to render overview glyphs in a background thread.

Changed files (6)

Diff

diff --git a/libbirdfont/BirdFont.vala b/libbirdfont/BirdFont.valaindex 2f03ddf..522994c 100644--- a/libbirdfont/BirdFont.vala+++ b/libbirdfont/BirdFont.vala@@ -259,7 +259,7 @@ public class BirdFont { 		if (has_argument ("--codepages")) { 			codepage_bits = new CodePageBits (); 			codepage_bits.generate_codepage_database ();-		}+		}		 	}  	public static bool has_logging () {diff --git a/libbirdfont/GlyphRange.vala b/libbirdfont/GlyphRange.valaindex e06ee3e..1c82d17 100644--- a/libbirdfont/GlyphRange.vala+++ b/libbirdfont/GlyphRange.vala@@ -1,5 +1,5 @@ /*-	Copyright (C) 2012 2014 Johan Mattsson+	Copyright (C) 2012 2014 2015 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 @@ -26,6 +26,8 @@ public class GlyphRange { 	uint32 len = 0; 	 	bool range_is_class = false;+	uint32* range_index = null;+	int index_size = 0; 	 	public GlyphRange () { 		ranges = new Gee.ArrayList<UniRange> ();@@ -33,6 +35,30 @@ public class GlyphRange { 		name = "No name"; 	} 	+	~GlyphRange () {+		if (range_index != null) {+			delete range_index;+		}+	}+	+	private void generate_unirange_index () {+		if (range_index != null) {+			delete range_index;+		}+		+		index_size = ranges.size;+		range_index = new uint32[index_size];+		+		int i = 0;+		uint32 next_index = 0;+		+		foreach (UniRange range in ranges) {+			range_index[i] = next_index;+			next_index += (uint32) range.length ();+			i++;+		}+	}+	 	public void add_unassigned (string glyph_name) { 		unassigned.add (glyph_name); 	}@@ -53,13 +79,14 @@ public class GlyphRange { 		unassigned.clear (); 		ranges.clear (); 		len = 0;+		generate_unirange_index (); 	} 	 	public unowned Gee.ArrayList<UniRange> get_ranges () { 		return ranges; 	} 		-	// TODO: complete localized alphabetical sort åäö is not the right order for example.+	// sort by unicode value 	public void sort () { 		ranges.sort ((a, b) => { 			UniRange first, next;@@ -73,6 +100,8 @@ public class GlyphRange { 			 			return (r) ? 1 : -1; 		});+		+		generate_unirange_index (); 	} 	 	public void add_single (unichar c) {@@ -122,6 +151,8 @@ public class GlyphRange { 				}				 			} 		}+		+		generate_unirange_index (); 	} 	 	/** Parse ranges on the form a-z. Single characters can be added as well as @@ -131,6 +162,7 @@ public class GlyphRange { 	 */ 	public void parse_ranges (string ranges) throws MarkupError { 		parse_range_string (ranges);+		generate_unirange_index (); 	} 	 	private void parse_range_string (string ranges) throws MarkupError {@@ -200,11 +232,11 @@ public class GlyphRange { 			 			first = false; 		}+		 		return s.str; 	} 	 	public static string serialize (string s) {-		 		if (s == "space") { 			return s; 		}@@ -351,7 +383,7 @@ public class GlyphRange { 	private void append_range (unichar start, unichar stop) { 		UniRange r; 		r = insert_range (start, stop); // insert a unique range-		merge_range (r); // join connecting ranges+		merge_range (r); 	} 	 	private void merge_range (UniRange r) {@@ -393,47 +425,89 @@ public class GlyphRange { 			merge_range (r); 		} 	}+	+	/** Find a range which containd index. */+	private void get_unirange_index (uint32 index, out UniRange? range, out uint32 range_start_index) {+		int lower = 0;+		int upper = index_size - 1;+		int i = lower + (upper - lower) / 2;+		int end = index_size - 1;+		+		range_start_index = -1;+		range = null;+		+		if (unlikely (ranges.size != index_size)) {+			warning (@"Range size does not match index size: $(ranges.size) != $index_size");+		}+		+		while (true) {+			if (i == end) {+				range_start_index = range_index[i];+				range = ranges.get (i);+				break;+			} else if (range_index[i] <= index && range_index[i + 1] > index) {+				range_start_index = range_index[i];+				range = ranges.get (i);+				break;+			}+			+			if (lower >= upper) {+				break;+			} +			if (range_index[i] < index) {+				lower = i + 1;+			} else {+				upper = i - 1;+			}+			+			i = lower + (upper - lower) / 2;+		}+	}+	 	public string get_char (uint32 index) { 		int64 ti; 		string chr; 		UniRange r; 		StringBuilder sb; 		unichar c;		+		UniRange? range;+		uint32 range_start_index; 		-		if (index > len + unassigned.size) {+		if (unlikely (index > len + unassigned.size)) { 			return "\0".dup(); 		} 		 		if (index >= len) {-			if (index - len >= unassigned.size) {+			if (unlikely (index - len >= unassigned.size)) { 				return "\0".dup(); 			}  			 			chr = unassigned.get ((int) (index - len)); 			return chr; 		}--		r = ranges.get (0);-		ti = index;--		foreach (UniRange u in ranges) {-			ti -= u.length ();-			-			if (ti < 0) {-				r = u;-				break;-			}+		+		get_unirange_index (index, out range, out range_start_index);+		+		if (unlikely (range == null)) {+			warning (@"No range found for index $index");+			return "";+		} +		+		if (unlikely (range_start_index > index || range_start_index == -1)) {+			warning ("Index out of bounds in glyph range.");+			return ""; 		}-				-		sb = new StringBuilder ();-		c = r.get_char ((unichar) (ti + r.length ()));+		+		r = (!) range;+		c = r.get_char ((unichar) (index - range_start_index)); 		 		if (unlikely (!c.validate ())) { 			warning ("Not a valid unicode character."); 			return ""; 		} 		+		sb = new StringBuilder (); 		sb.append_unichar (c); 		 		return sb.str;diff --git a/libbirdfont/LabelTool.vala b/libbirdfont/LabelTool.valaindex 8a3fbba..8806c70 100644--- a/libbirdfont/LabelTool.vala+++ b/libbirdfont/LabelTool.vala@@ -24,11 +24,24 @@ public class LabelTool : Tool { 		} 		 		set {+			clear_cache (); 			label_text.set_text (value); 		} 	} 	-	public string number { get; set; }+	public string number {+		get {+			return counter_number;+		}+	+		set {+			clear_cache ();+			counter_number = value;+		} +	}+	+	string counter_number = "";+	 	public bool has_counter { get; set; } 	public bool has_delete_button { get; set; } 	public signal void delete_action (LabelTool self);@@ -39,6 +52,9 @@ public class LabelTool : Tool { 	 	Text label_text; +	Surface? selected_cache = null;+	Surface? deselected_cache = null;+ 	public LabelTool (string label) { 		double text_height; 		@@ -62,18 +78,58 @@ public class LabelTool : Tool { 			} 		}); 	}++	void clear_cache () {+		selected_cache = null;+		deselected_cache = null;+	} 	 	public override void draw_tool (Context cr, double px, double py) {+		double x = this.x - px;+		double y = this.y - py;+		+		if (is_selected ()) {+		+			if (selected_cache == null) {+				selected_cache = Screen.create_background_surface ((int) w, (int) h + 2);+				Context c = new Context ((!) selected_cache);+				c.scale (1 / Screen.get_scale (), 1 / Screen.get_scale ());+				draw_tool_surface (c, x, 2, true);+			}+			+			cr.save ();+			cr.set_antialias (Cairo.Antialias.NONE);+			cr.set_source_surface ((!) selected_cache, 0, (int) y - 2);+			cr.paint ();+			cr.restore ();+		} else {+		+			if (deselected_cache == null) {+				deselected_cache = Screen.create_background_surface ((int) w, (int) h + 2);+				Context c = new Context ((!) deselected_cache);+				c.scale (1 / Screen.get_scale (), 1 / Screen.get_scale ());+				draw_tool_surface (c, x, 2, false);+			}+			+			cr.save ();+			cr.set_antialias (Cairo.Antialias.NONE);+			cr.set_source_surface ((!) deselected_cache, 0, (int) y - 2);+			cr.paint ();+			cr.restore ();+		}+	}+	+	public void draw_tool_surface (Context cr, double px, double py, bool selected) { 		Text glyph_count; 		double bgx, bgy; 		double center_x, center_y;-		double x = this.x - px;-		double y = this.y - py; 		double text_height; 		double text_width;+		double x = px;+		double y = py; 		 		// background-		if (is_selected ()) {+		if (selected) { 			cr.save (); 			Theme.color (cr, "Menu Background"); 			cr.rectangle (0, y - 2 * Toolbox.get_scale (), w, h); // labels overlap with 2 pixels@@ -84,11 +140,7 @@ public class LabelTool : Tool { 		// tab label 		cr.save (); -		if (is_selected ()) {-			Theme.text_color (label_text, "Text Tool Box");-		} else {-			Theme.text_color (label_text, "Text Tool Box");-		}+		Theme.text_color (label_text, "Text Tool Box"); 		 		text_width = Toolbox.allocation_width; 		@@ -101,7 +153,6 @@ public class LabelTool : Tool { 		} 		 		label_text.truncate (text_width);-		 		label_text.draw_at_top (cr, x, y); 		cr.restore (); diff --git a/libbirdfont/MainWindow.vala b/libbirdfont/MainWindow.valaindex 7b851a3..988069c 100644--- a/libbirdfont/MainWindow.vala+++ b/libbirdfont/MainWindow.vala@@ -170,6 +170,7 @@ public class MainWindow : GLib.Object { 
 	public void set_native (NativeWindow nw) {
 		native_window = nw;
+		OverViewItem.start_thumbnail_processing ();
 	}
 	 
 	public static FontDisplay get_current_display () {
diff --git a/libbirdfont/OverView.vala b/libbirdfont/OverView.valaindex 1ff2261..ce0338b 100644--- a/libbirdfont/OverView.vala+++ b/libbirdfont/OverView.vala@@ -76,6 +76,8 @@ public class OverView : FontDisplay { 	double scroll_size = 1; 	const double UCD_LINE_HEIGHT = 17 * 1.3; +	private bool update_scheduled = true;+ 	public OverView (GlyphRange? range = null, 		bool open_selected = true, bool default_character_set = true) { 			@@ -512,7 +514,11 @@ public class OverView : FontDisplay { 		return i - 1; 	} 	-	public void update_item_list (int item_list_length = -1) {+	public void update_item_list () {+		update_scheduled = true;+	}+	+	public void process_update_item_list () { 		string character_string; 		Font f = BirdFont.get_current_font (); 		GlyphCollection? glyphs = null;@@ -522,17 +528,21 @@ public class OverView : FontDisplay { 		unichar character; 		Glyph glyph; 		double tab_with;+		int item_list_length;+		+		Test test = new Test.time("update_item_list"); 		 		tab_with =  allocation.width - 30; // scrollbar 		 		items_per_row = get_items_per_row (); 		rows = (int) (allocation.height /  OverViewItem.full_height ()) + 2; 		-		if (item_list_length == -1) {-			item_list_length = items_per_row * rows;+		item_list_length = items_per_row * rows;+		+		foreach  (OverViewItem overview_item in visible_items) {+			overview_item.cancel_thumbnail_rendering (); 		} 		-		visible_items.clear (); 		visible_items = new Gee.ArrayList<OverViewItem> (); 						 		// update item list@@ -561,9 +571,6 @@ public class OverView : FontDisplay { 				character = character_string.get_char (0); 			} 			-			item = new OverViewItem (glyphs, character, x, y);-			item.adjust_scale ();-			 			x += OverViewItem.full_width (); 			 			if (x + OverViewItem.full_width () >= tab_with) {@@ -571,31 +578,44 @@ public class OverView : FontDisplay { 				y += OverViewItem.full_height (); 			} 			-			item.selected = (i == selected);+			bool selected_item = (i == selected); 			 			if (glyphs != null) {-				item.selected |= selected_items.index_of ((!) glyphs) != -1;+				selected_item |= selected_items.index_of ((!) glyphs) != -1;+			}+			+			if (i >= visible_items.size) {+				item = new OverViewItem (glyphs, character, x, y);+				item.adjust_scale ();+				item.selected = selected_item;+				visible_items.add (item);+			} else {+				visible_items.get (i).init (glyphs, character, x, y); 			} 			-			visible_items.add (item); 			index++; 		}-				+		 		foreach (OverViewItem i in visible_items) { 			i.y += view_offset_y; 			i.x += view_offset_x;-		}		+		}+		+		warning (test.get_test_time ());+		+		update_scheduled = false; 	} 	 	public override void draw (WidgetAllocation allocation, Context cr) {-		-		if (this.allocation.width != allocation.width+		if (update_scheduled+			|| this.allocation.width != allocation.width 			|| this.allocation.height != allocation.height 			|| this.allocation.width == 0) { 			this.allocation = allocation;-			update_item_list ();+			process_update_item_list (); 		} 		+		Test test = new Test.time("Overview.draw"); 		this.allocation = allocation; 		 		// clear canvas@@ -616,6 +636,8 @@ public class OverView : FontDisplay { 		if (unlikely (character_info != null)) { 			draw_character_info (cr); 		}+		+		warning (test.get_test_time ()); 	} 		 	void draw_empty_canvas (WidgetAllocation allocation, Context cr) {diff --git a/libbirdfont/OverViewItem.vala b/libbirdfont/OverViewItem.valaindex bc251c9..fb72d59 100644--- a/libbirdfont/OverViewItem.vala+++ b/libbirdfont/OverViewItem.vala@@ -38,12 +38,26 @@ public class OverViewItem : GLib.Object { 	public VersionList version_menu; 	Text label; 	+	private Surface? cache = null;+	 	public static Surface? label_background = null; 	public static Surface? selected_label_background = null; 	public static Surface? label_background_no_menu = null; 	public static Surface? selected_label_background_no_menu = null; 	+	private static Task thumbnail_task;+	private static Gee.PriorityQueue<OverViewItem> thumbnail_queue;+	private static Cond has_thumnail_task = new Cond ();+	private static Mutex thumbnail_mutex = new Mutex ();+	+	private bool cancel_thumbnail = false;+	 	public OverViewItem (GlyphCollection? glyphs, unichar character, double x, double y) {	+		init (glyphs, character, x, y);+	}++	// this method makes it possible for the overview tab to reuse its items as a speed optimization+	public void init (GlyphCollection? glyphs, unichar character, double x, double y) { 		this.x = x; 		this.y = y; 		this.character = character;@@ -68,6 +82,123 @@ public class OverViewItem : GLib.Object { 		} else { 			version_menu = new VersionList (new GlyphCollection (character, (!) character.to_string ())); 		}+		+		thumbnail_mutex.lock ();+		if (!is_null (thumbnail_queue)) {+			thumbnail_queue.offer (this);+			has_thumnail_task.signal ();+		}+		thumbnail_mutex.unlock ();+	}++	public static void start_thumbnail_processing () {+		thumbnail_task  = new Task (process_thumbnails, true);+		thumbnail_queue = new Gee.PriorityQueue<OverViewItem> ();	+		MainWindow.native_window.run_non_blocking_background_thread (thumbnail_task);+	}++	public void cancel_thumbnail_rendering () {+		thumbnail_mutex.lock ();+		cancel_thumbnail = true;+		thumbnail_mutex.unlock ();+	}++	private static void process_thumbnails () {+		bool cancel = false;+		+		while (!thumbnail_task.is_cancelled ()) {+			OverViewItem item;+			+			thumbnail_mutex.lock ();+			item = thumbnail_queue.poll ();+			thumbnail_mutex.unlock ();+			+			if (!is_null (item)) {+				thumbnail_mutex.lock ();+				cancel = item.cancel_thumbnail;+				thumbnail_mutex.unlock ();+			+				if (!cancel) {+					IdleSource idle = new IdleSource ();+					idle.set_callback (() => {+						item.draw_background ();+						return false;+					});+					idle.attach (null);+				}+			} else {+				thumbnail_mutex.lock ();+				has_thumnail_task.wait (thumbnail_mutex);+				thumbnail_mutex.unlock ();+			}+		}+	}++	public void draw_background () { // FIXME: LOCK for Text and thread exit+		Glyph g;+		Font font;+		double gx, gy;+		double x1, x2, y1, y2;+		double scale_box;+		double w, h;+		double glyph_width, glyph_height;+		Surface s;+		Context c;+		Color color = Color.black ();+		+		w = width;+		h = height;+		+		scale_box = width / DEFAULT_WIDTH;++		s = Screen.create_background_surface ((int) width, (int) height - 20);+		c = new Context (s);+		+		if (glyphs != null) {+			font = BirdFont.get_current_font ();+			g = ((!) glyphs).get_current ();+			+			c.save ();+			g.boundaries (out x1, out y1, out x2, out y2);+		+			glyph_width = x2 - x1;+			glyph_height = y2 - y1;+			+			c.save ();+			c.scale (glyph_scale * Screen.get_scale (), glyph_scale * Screen.get_scale ());++			g.add_help_lines ();+			+			gx = ((w / glyph_scale) - glyph_width) / 2 - g.get_left_side_bearing ();+			gy = (h / glyph_scale) - 25 / glyph_scale;+			+			c.translate (gx - Glyph.xc () - g.get_lsb (), g.get_baseline () + gy - Glyph.yc ());+			+			g.draw_paths (c, color);+			c.restore ();+		} else {+			c.scale (Screen.get_scale (), Screen.get_scale ());+			+			c.save ();+			Text fallback = new Text ();+			fallback.set_use_cache (false);+			Theme.text_color (fallback, "Overview Glyph");+			fallback.set_text ((!) character.to_string ());+			double font_size = height * 0.8;+			gx = (width - fallback.get_extent ()) / 2.0;+			gy = height - 30;+			fallback.set_font_size (font_size);+			fallback.draw_at_baseline (c, gx, gy);+			c.restore ();+		}+		+		IdleSource idle = new IdleSource ();+		idle.set_callback (() => {+			cache = s;+			GlyphCanvas.redraw ();+			return false;+		});+		idle.attach (null); 	}  	public static void reset_label () {@@ -143,7 +274,7 @@ public class OverViewItem : GLib.Object { 		if (!is_on_screen (allocation)) { 			return; 		}-		+ 		cr.save (); 		Theme.color (cr, "Background 1"); 		cr.rectangle (x, y, width, height);@@ -157,9 +288,10 @@ public class OverViewItem : GLib.Object { 		cr.stroke (); 		cr.restore (); 		-		draw_thumbnail (cr, glyphs, x, y + height); 	 		draw_caption (cr); 		draw_menu (cr);+		+		draw_thumbnail (cr, x, y + height); 	}  	public void adjust_scale () {@@ -189,72 +321,15 @@ public class OverViewItem : GLib.Object { 		} 	} -	private void draw_thumbnail (Context cr, GlyphCollection? gl, double x, double y) {-		Glyph g;-		Font font;-		double gx, gy;-		double x1, x2, y1, y2;-		double scale_box;-		double w, h;-		double glyph_width, glyph_height;-		Surface s;-		Context c;-		Color color = Color.black ();-		-		w = width;-		h = height;-		-		scale_box = width / DEFAULT_WIDTH;--		s = Screen.create_background_surface ((int) width, (int) height - 20);-		c = new Context (s);-		-		if (gl != null) {-			font = BirdFont.get_current_font ();-			g = ((!) gl).get_interpolated_fast (OverviewTools.current_master_size);--			c.save ();-			g.boundaries (out x1, out y1, out x2, out y2);-		-			glyph_width = x2 - x1;-			glyph_height = y2 - y1;-			-			c.save ();-			c.scale (glyph_scale * Screen.get_scale (), glyph_scale * Screen.get_scale ());--			g.add_help_lines ();-			-			gx = ((w / glyph_scale) - glyph_width) / 2 - g.get_left_side_bearing ();-			gy = (h / glyph_scale) - 25 / glyph_scale;-			-			c.translate (gx - Glyph.xc () - g.get_lsb (), g.get_baseline () + gy - Glyph.yc ());-			-			g.draw_paths (c, color);-			c.restore ();-		} else {-			c.scale (Screen.get_scale (), Screen.get_scale ());-			-			c.save ();-			Text fallback = new Text ();-			fallback.set_use_cache (false);-			Theme.text_color (fallback, "Overview Glyph");-			fallback.set_text ((!) character.to_string ());-			double font_size = height * 0.8;-			fallback.set_font_size (font_size);--			gx = (width - fallback.get_extent ()) / 2.0;-			gy = height - 30;-			fallback.set_font_size (font_size);-			fallback.draw_at_baseline (c, gx, gy);-			c.restore ();+	private void draw_thumbnail (Context cr, double x, double y) {		+		if (cache != null) {+			cr.save ();+			cr.set_antialias (Cairo.Antialias.NONE);+			cr.scale (1 / Screen.get_scale (), 1 / Screen.get_scale ());	+			cr.set_source_surface ((!) cache, (int) (x * Screen.get_scale ()), (int) ((y - height)) * Screen.get_scale ());+			cr.paint ();+			cr.restore (); 		}-		-		cr.save ();-		cr.set_antialias (Cairo.Antialias.NONE);-		cr.scale (1 / Screen.get_scale (), 1 / Screen.get_scale ());	-		cr.set_source_surface (s, (int) (x * Screen.get_scale ()), (int) ((y - h)) * Screen.get_scale ());-		cr.paint ();-		cr.restore (); 	}  	public bool has_icons () {@@ -321,7 +396,7 @@ public class OverViewItem : GLib.Object {  		selected_label_background = (!) cache;	 	-		// unselected item without menu icon+		// deselected item without menu icon 		cache = Screen.create_background_surface ((int) width, 20); 		cc = new Context (cache); 		cc.scale(Screen.get_scale(), Screen.get_scale());

Contact

Email: johan.mattsson.m@gmail.com
Mastodon: typo.social/@birdfont
GitHub: johanmattssonm
LinkedIn: johan-mattsson-2ab324112