Commit 8c9f498
Speed optimizations for the font name tab
Changed files (15)
modifiedlibbirdfont/CheckBox.valamodifiedlibbirdfont/ClipTool.valamodifiedlibbirdfont/DescriptionDisplay.valamodifiedlibbirdfont/Expander.valamodifiedlibbirdfont/FontName.valamodifiedlibbirdfont/Glyph.valamodifiedlibbirdfont/Headline.valamodifiedlibbirdfont/LabelTool.valamodifiedlibbirdfont/Menu.valamodifiedlibbirdfont/OverViewItem.valamodifiedlibbirdfont/Renderer/Text.valamodifiedlibbirdfont/Renderer/TextArea.valamodifiedlibbirdfont/SettingsItem.valamodifiedlibbirdfont/TabBar.valamodifiedlibbirdfont/Widget.vala
Diff
@@ -61,6 +61,7 @@ public class CheckBox : Widget { cr.save (); cr.set_line_width (1);+ cr.set_source_rgba (0, 0, 0, 1); draw_rounded_rectangle (cr, widget_x, widget_y + center_y, w, h - padding, padding); cr.stroke (); cr.restore ();@@ -68,6 +69,7 @@ public class CheckBox : Widget { if (checked) { cr.save (); + cr.set_source_rgba (0, 0, 0, 1); cr.set_line_width (1); cr.move_to (widget_x + d, widget_y + d + center_y);@@ -31,7 +31,6 @@ public class ClipTool : Tool { string svg_data; string bf_data; string data;- TextArea t; if (fd is Glyph) { svg_data = ExportTool.export_selected_paths_to_svg ();@@ -86,7 +85,6 @@ public class ClipTool : Tool { Glyph g = MainWindow.get_current_glyph (); double x, y, w, h; double dx, dy;- TextArea t; if (fd is Glyph) { paste_paths (false);@@ -167,25 +167,31 @@ public class DescriptionDisplay : FontDisplay { cr.restore (); foreach (Widget w in widgets) {- if (w is Text) {- cr.save ();- cr.set_source_rgba (0, 0, 0, 1);- w.draw (cr);- cr.restore ();- } else {- w.draw (cr);+ if (w.is_on_screen ()) { + if (w is Text) {+ cr.save ();+ cr.set_source_rgba (0, 0, 0, 1);+ w.draw (cr);+ cr.restore ();+ } else {+ w.draw (cr);+ } } } } void layout () { double y = -scroll;- double margin; foreach (Widget w in widgets) { w.widget_x = 17 * MainWindow.units; w.widget_y = y; w.allocation = allocation;+ + if (w is TextArea) {+ ((TextArea) w).layout ();+ }+ y += w.get_height () + w.margin_bottom; } @@ -232,7 +232,7 @@ public class Expander : GLib.Object { if (headline != null) { title = new Text (); title.set_text ((!) headline);- cr.set_source_rgba (101 / 255.0, 108 / 255.0, 116 / 255.0, 1);+ title.set_source_rgba (101 / 255.0, 108 / 255.0, 116 / 255.0, 1); title.set_font_size (14); title.draw_at_baseline (cr, x, yt + HEADLINE_MARGIN + 7); } else if (draw_separator) { @@ -50,7 +50,7 @@ public class FontName : Tool { text_height *= max_width / extent; } - cr.set_source_rgba (234 / 255.0, 77 / 255.0, 26 / 255.0, 1);+ font_name.set_source_rgba (234 / 255.0, 77 / 255.0, 26 / 255.0, 1); font_name.set_font_size (text_height); font_name.draw_at_baseline (cr, x, y + 13 * Toolbox.get_scale ()); cr.restore ();@@ -14,6 +14,7 @@ using Cairo; using Math;+using Gee; namespace BirdFont { @@ -94,6 +95,10 @@ public class Glyph : FontDisplay { /** Cache quadratic form on export. */ GlyfData? ttf_data = null; + /** Cache for Cairo rendering */+ + HashMap<int64?, Surface> glyph_cache = new HashMap<int64?, Surface> ((Gee.HashDataFunc<int64?>) hash_int64, (Gee.EqualDataFunc<int64?>) equal_int64);+ public Glyph (string name, unichar unichar_code = 0) { this.name = name; this.unichar_code = unichar_code;@@ -120,7 +125,7 @@ public class Glyph : FontDisplay { return (!) ttf_data; }-+ public PathList get_quadratic_paths () { PointConverter pc; PathList pl;@@ -2068,6 +2073,39 @@ public class Glyph : FontDisplay { } } }++ public void set_cache (int64 font_size, Surface cache) {+ glyph_cache.set (font_size, cache);+ }++ public bool has_cache (int64 font_size) {+ return glyph_cache.has_key (font_size);+ }++ public Surface get_cache (int64 font_size) {+ if (unlikely (!has_cache (font_size))) {+ warning ("No cache for glyph.");+ return new ImageSurface (Cairo.Format.ARGB32, 1, 1);+ }+ + return glyph_cache.get (font_size);+ }+ + public static bool equal_int64 (int64? i1, int64? i2) {+ if (i1 == null || i1 == null) {+ return false;+ }+ + return (!) i1 == (!) i2;+ }++ public static int hash_int64 (int64? i) {+ if (i == 0) {+ return 0;+ }+ + return (int) (0xFFFFFFFF & i);+ } } }@@ -34,7 +34,7 @@ class Headline : Widget { cr.restore (); cr.save ();- cr.set_source_rgba (1, 1, 1, 1);+ label.set_source_rgba (1, 1, 1, 1); label.set_font_size (20 * MainWindow.units); label.draw_at_baseline (cr, 21 * MainWindow.units, widget_y + 25 * MainWindow.units); cr.restore ();@@ -68,9 +68,9 @@ public class LabelTool : Tool { text_height = 18; if (is_selected ()) {- cr.set_source_rgba (1, 1, 1, 1);+ label_text.set_source_rgba (1, 1, 1, 1); } else {- cr.set_source_rgba (101 / 255.0, 108 / 255.0, 116 / 255.0, 1);+ label_text.set_source_rgba (101 / 255.0, 108 / 255.0, 116 / 255.0, 1); } label_text.set_font_size (text_height);@@ -98,9 +98,9 @@ public class LabelTool : Tool { center_y = bgy + ((!) counter_background).get_height () / 2.0 + 4 / scale; if (is_selected ()) {- cr.set_source_rgba (1, 1, 1, 1);+ glyph_count.set_source_rgba (1, 1, 1, 1); } else {- cr.set_source_rgba (51 / 255.0, 54 / 255.0, 59 / 255.0, 1);+ glyph_count.set_source_rgba (51 / 255.0, 54 / 255.0, 59 / 255.0, 1); } glyph_count.set_font_size (text_height);@@ -573,7 +573,6 @@ public class Menu : GLib.Object { } public bool has_menu_item (string identifier) {- int i = 0; foreach (MenuItem mi in sorted_menu_items) { if (mi.identifier == identifier) { return true;@@ -114,7 +114,7 @@ public class OverViewItem : GLib.Object { } public void adjust_scale () {- double x1, x2, y1, y2, glyph_width, glyph_height, scale, gx, gy;+ double x1, x2, y1, y2, glyph_width, glyph_height, scale, gx; Glyph g; Font font; @@ -184,7 +184,7 @@ public class OverViewItem : GLib.Object { } else { c.save (); fallback = new Text ();- c.set_source_rgba (219 / 255.0, 221 / 255.0, 233 / 255.0, 1);+ fallback.set_source_rgba (219 / 255.0, 221 / 255.0, 233 / 255.0, 1); fallback.set_text ((!) character.to_string ()); font_size = height * 0.8; fallback.set_font_size (font_size);@@ -43,7 +43,13 @@ public class Text : Widget { GlyphSequence glyph_sequence; public delegate void Iterator (Glyph glyph, double kerning, bool last); public double font_size;- + public double sidebearing_extent = 0;++ double r = 0;+ double g = 0;+ double b = 0;+ double a = 1;+ public Text (string text = "", double size = 17, double margin_bottom = 0) { current_font = null; this.margin_bottom = margin_bottom;@@ -56,6 +62,7 @@ public class Text : Widget { public void set_font_size (double height_in_pixels) { font_size = height_in_pixels;+ sidebearing_extent = 0; } public void set_font_cache (FontCache font_cache) {@@ -76,7 +83,7 @@ public class Text : Widget { name = font.get_name_for_character (c); g = font.get_glyph_by_name (name); glyph_sequence.glyph.add (g);- } + } } /** @param character a string with a single glyph or the name of the glyph if it is a ligature. */@@ -153,17 +160,24 @@ public class Text : Widget { } public double get_sidebearing_extent () {- double x = 0;- double ratio = get_scale ();-+ double x ;+ double ratio;+ + if (likely (sidebearing_extent > 0)) {+ return sidebearing_extent;+ }+ + x = 0;+ ratio = get_scale ();+ iterate ((glyph, kerning, last) => { double lsb;- glyph.add_help_lines (); lsb = glyph.left_limit; x += (glyph.get_width () + kerning) * ratio; }); + return x; } @@ -249,42 +263,76 @@ public class Text : Widget { draw_at_baseline (cr, widget_x, y); } - public void draw_at_baseline (Context cr, double px, double py) {+ public void draw_at_top (Context cr, double px, double py, int64 cacheid = -1) {+ double s = get_scale ();+ double y = py + s * (font.top_limit - font.base_line);+ draw_at_baseline (cr, px, y, cacheid);+ }+ + public void set_source_rgba (double r, double g, double b, double a) {+ this.r = r;+ this.g = g;+ this.b = b;+ this.a = a;+ }+ + public int64 get_cache_id () {+ int64 s = (((int64) font_size) << 32) + | (((int64) (r * 255)) << 24)+ | (((int64) (g * 255)) << 16)+ | (((int64) (b * 255)) << 8)+ | (((int64) (a * 255)) << 0);+ return s;+ }+ + public void draw_at_baseline (Context cr, double px, double py, int64 cacheid = -1) { double x, y; double ratio;-+ Font f;+ double cc_y;+ int64 cache_id = (cacheid < 0) ? get_cache_id () : cacheid;+ ratio = get_scale ();- - cr.save ();+ f = BirdFont.get_current_font ();+ cc_y = (f.top_limit - f.base_line) * ratio; y = py; x = px; iterate ((glyph, kerning, last) => { double lsb;+ Surface cache;+ Context cc; - glyph.add_help_lines ();- - lsb = glyph.left_limit;- - x += kerning * ratio;- cr.save ();- cr.new_path ();- foreach (Path path in glyph.path_list) {- draw_path (cr, path, lsb, x, y, ratio);+ if (unlikely (!glyph.has_cache (cache_id))) {+ glyph.add_help_lines ();+ + cache = new Surface.similar (cr.get_target (), Cairo.Content.COLOR_ALPHA, (int) (glyph.get_width () * ratio) + 1, (int) font_size + 1);+ cc = new Context (cache);+ + lsb = glyph.left_limit;++ cc.save ();+ cc.set_source_rgba (r, g, b, a);+ cc.new_path ();++ foreach (Path path in glyph.path_list) {+ draw_path (cc, path, lsb, 0, cc_y, ratio);+ }+ cc.fill ();+ cc.restore ();++ glyph.set_cache (cache_id, cache); }- cr.fill ();- cr.restore ();- ++ x += kerning * ratio;+ cr.set_source_surface (glyph.get_cache (cache_id), x, y - cc_y); x += glyph.get_width () * ratio;+ + cr.paint (); });- - cr.set_source_rgba (0, 0, 0, 1);- cr.fill ();-- cr.restore (); - } -+ }+ void draw_path (Context cr, Path path, double lsb, double x, double y, double scale) { EditPoint e, prev; double xa, ya, xb, yb, xc, yc, xd, yd;@@ -49,22 +49,26 @@ public class TextArea : Widget { int text_length = 0; int iter_pos;+ int last_paragraph = 0; public signal void text_changed (string text); Gee.ArrayList<Paragraph> paragraphs = new Gee.ArrayList<Paragraph> ();+ private static const int DONE = -2; - public TextArea (double font_size = 14) {+ int64 cache_id = -1;+ + public TextArea (double font_size = 20) { this.font_size = font_size; width = min_width; height = min_height; } public override double get_height () {- return height;+ return height + 2 * padding; } public override double get_width () {- return width;+ return width + 2 * padding; } public void set_font_size (double z) {@@ -73,15 +77,24 @@ public class TextArea : Widget { void generate_paragraphs () { Paragraph paragraph;- string[] p = text.split ("\n");- double y = 200;++ int next_paragraph = -1;+ + if (last_paragraph == DONE) {+ return;+ }+ + next_paragraph = text.index_of ("\n", last_paragraph); - foreach (string t in p) {- paragraph = new Paragraph (t);+ if (next_paragraph == -1) {+ paragraph = new Paragraph (text.substring (last_paragraph), font_size); paragraphs.add (paragraph);- paragraph.y = y;- - y += 200;+ last_paragraph = DONE;+ } else {+ next_paragraph += "\n".length;+ paragraph = new Paragraph (text.substring (last_paragraph, next_paragraph - last_paragraph), font_size);+ paragraphs.add (paragraph);+ last_paragraph = next_paragraph; } } @@ -98,6 +111,9 @@ public class TextArea : Widget { carret = tl; text_length += tl; + paragraphs.clear ();+ generate_paragraphs ();+ text_changed (text); } @@ -224,62 +240,67 @@ public class TextArea : Widget { text = nt; text_length += tl; + paragraphs.clear (); // DELETE+ generate_paragraphs (); // FIXME:+ text_changed (text); } /** @return offset to click in text. */ public int layout (double click_x = -1, double click_y = -1) {- Text word; double p; double tx, ty; string w; double xmax = 0; double width = this.width - 2 * padding;- double height = this.height - 2 * padding;- bool carret_visibility; int carret_position = text_length;-- word = new Text ();- word.set_font_size (font_size); tx = 0; ty = font_size; iter_pos = 0;- while (iter_pos < text_length) {- w = get_next_word (out carret_visibility, ref iter_pos);- - if (w == "") {- break;- }-- word.set_text (w);- - p = word.get_sidebearing_extent ();- - if (!single_line) {- if (tx + p > width || w == "\n") {- tx = 0;- ty += font_size;+ + foreach (Paragraph paragraph in paragraphs) {+ if (paragraph.need_layout) {+ paragraph.start_y = ty;+ paragraph.start_x = tx;+ + foreach (Text next_word in paragraph.words) {+ w = next_word.text;+ p = next_word.get_sidebearing_extent (); - if (ty + widget_y > 2 * allocation.height) {- height = widget_y + 2 * allocation.height;+ if (w == "") { break; }++ if (!single_line) {+ if (tx + p > width || w == "\n") {+ tx = 0;+ ty += next_word.font_size;+ }+ }+ + if (widget_y + ty - font_size <= click_y <= widget_y + ty + padding+ && widget_x + tx <= click_x) {+ carret_position = find_carret_pos_in_word (next_word, iter_pos, tx, click_x);+ }+ + if (w != "\n") {+ tx += p;+ }+ + if (tx > xmax) {+ xmax = tx;+ } }+ + paragraph.width = xmax;+ paragraph.end_x = tx;+ paragraph.end_y = ty;+ paragraph.need_layout = false; }- - if (widget_y + ty - font_size <= click_y <= widget_y + ty + padding- && widget_x + tx <= click_x) {- carret_position = find_carret_pos_in_word (word, iter_pos, tx, click_x);- }- - if (w != "\n") {- tx += p;- }- - if (tx > xmax) {- xmax = tx;- }+ + tx = paragraph.end_x;+ ty = paragraph.end_y; } this.width = min_width;@@ -293,7 +314,16 @@ public class TextArea : Widget { if (click_x > 0 && click_x < widget_x + padding) { carret_position = 0; }- ++ if (last_paragraph != DONE) {+ this.height = (text_length / (double) last_paragraph) * ty + 2 * padding; // estimate height+ }+ + if (ty + widget_y < allocation.height && last_paragraph != DONE) {+ generate_paragraphs ();+ return layout (click_x, click_y);+ }+ return carret_position; } @@ -313,6 +343,7 @@ public class TextArea : Widget { i += ((!) glyph.get_unichar ().to_string ()).length; wx += (glyph.get_width () + kerning) * ratio; d = Math.fabs (wx - click_x);+ if (d < min_d) { min_d = d; carret_position = iter_pos - w.length + i;@@ -375,6 +406,7 @@ public class TextArea : Widget { // border cr.save (); cr.set_line_width (1);+ cr.set_source_rgba (0, 0, 0, 1); draw_rounded_rectangle (cr, x, y, this.width, this.height, padding); cr.stroke (); cr.restore ();@@ -395,6 +427,8 @@ public class TextArea : Widget { } // draw selection background+ // FIXME: put back+ /* if (selection_end >= 0 && selection_end != carret) { tx = 0; ty = 0;@@ -451,8 +485,89 @@ public class TextArea : Widget { cr.restore (); }+ */++ tx = 0;+ ty = 0; + + int first_visible = 0;+ int last_visible;+ int paragraphs_size = paragraphs.size;+ while (first_visible < paragraphs_size) {+ if (paragraphs.get (first_visible).text_is_on_screen (allocation, widget_y)) {+ break;+ }+ first_visible++;+ }+ + last_visible = first_visible;+ while (last_visible < paragraphs_size) {+ if (!paragraphs.get (last_visible).text_is_on_screen (allocation, widget_y)) {+ last_visible++;+ break;+ }+ last_visible++;+ } + + if (paragraphs_size == 0) {+ return;+ }+ + Context cc; // cached context+ double cc_x, cc_y;+ Paragraph paragraph;+ paragraph = paragraphs.get (0); + tx = paragraph.start_x;+ ty = paragraph.start_y;++ if (cache_id == -1 && paragraphs.size > 0 && paragraphs.get (0).words.size > 0) {+ Text t = paragraphs.get (0).words.get (0);+ t.set_source_rgba (0, 0, 0, 1);+ cache_id = t.get_cache_id ();+ }+ + for (int i = first_visible; i < last_visible; i++) { + paragraph = paragraphs.get (i);+ + tx = paragraph.start_x;+ ty = paragraph.start_y; + if (paragraph.cached_surface == null) { + paragraph.cached_surface = new Surface.similar (cr.get_target (), Cairo.Content.COLOR_ALPHA, paragraph.get_width (), paragraph.get_height () + (int) font_size + 1);+ cc = new Context ((!) paragraph.cached_surface);+ + cc_x = paragraph.start_x;+ cc_y = 0;+ + foreach (Text next_word in paragraph.words) {+ w = next_word.text;+ + next_word.set_source_rgba (0, 0, 0, 1);+ + p = next_word.get_sidebearing_extent ();+ + if (cc_x + p > width || w == "\n") {+ cc_x = 0;+ cc_y += next_word.font_size;+ }+ + if (w != "\n") {+ next_word.draw_at_top (cc, cc_x, cc_y, cache_id);+ cc_x += p;+ }+ }+ }+ + if (likely (paragraph.cached_surface != null)) {+ cr.set_source_surface ((!) paragraph.cached_surface, x + tx, widget_y + paragraph.start_y - font_size + padding);+ cr.paint ();+ } else {+ warning ("No paragraph image.");+ }+ }+ +/* // draw characters tx = 0; ty = 0;@@ -486,6 +601,7 @@ public class TextArea : Widget { } } cr.restore ();+ */ } void draw_carret_at (Context cr, double x, double y) {@@ -569,15 +685,120 @@ public class TextArea : Widget { } class Paragraph : GLib.Object {- public double x = -1;- public double y = -1;+ public double end_x = -10000;+ public double end_y = -10000;+ + public double start_x = -10000;+ public double start_y = -10000;+ + public double width = -10000;+ public string text; public Gee.ArrayList<Text> words = new Gee.ArrayList<Text> (); - public Paragraph (string text) {+ int text_length;+ + public bool need_layout = true;+ + public Surface? cached_surface = null;+ + public Paragraph (string text, double font_size) { this.text = text;+ text_length = text.length;+ generate_words (font_size);+ }++ public int get_height () {+ return (int) (end_y - start_y) + 1;+ }+ + public int get_width () {+ return (int) width + 1; } + public bool text_is_on_screen (WidgetAllocation alloc, double widget_y) {+ bool v = (0 <= start_y + widget_y <= alloc.height)+ || (0 <= end_y + widget_y <= alloc.height)+ || (start_y + widget_y <= 0 && alloc.height <= end_y + widget_y);+ return v;+ }++ void generate_words (double font_size) {+ string w;+ int p = 0;+ bool carret_at_word_end = false;+ Text word;+ int carret = 0;+ int iter_pos = 0;++ while (p < text_length) {+ w = get_next_word (out carret_at_word_end, ref iter_pos, carret);+ + if (w == "") {+ break;+ }+ + word = new Text (w, font_size);+ words.add (word);+ }+ }++ string get_next_word (out bool carret_at_end_of_word, ref int iter_pos, int carret) {+ int i;+ int ni;+ int pi;+ string n;+ int nl;+ + carret_at_end_of_word = false;+ + if (iter_pos >= text_length) {+ carret_at_end_of_word = true;+ return "".dup ();+ }+ + if (text.get_char (iter_pos) == '\n') {+ iter_pos += "\n".length;+ carret_at_end_of_word = (iter_pos == carret);+ return "\n".dup ();+ }+ + i = text.index_of (" ", iter_pos);+ pi = i + " ".length;+ + ni = text.index_of ("\t", iter_pos);+ if (ni != -1 && ni < pi || i == -1) {+ i = ni;+ pi = i + "\t".length;+ }+ + ni = text.index_of ("\n", iter_pos);+ if (ni != -1 && ni < pi || i == -1) {+ i = ni;+ pi = i;+ }+ + if (iter_pos + iter_pos - pi > text_length || i == -1) {+ n = text.substring (iter_pos);+ } else {+ n = text.substring (iter_pos, pi - iter_pos);+ }+ + nl = n.length;+ if (iter_pos < carret < iter_pos + nl) {+ n = text.substring (iter_pos, carret - iter_pos);+ nl = n.length;+ carret_at_end_of_word = true;+ }+ + iter_pos += nl;+ + if (iter_pos == carret) {+ carret_at_end_of_word = true;+ }+ + return n;+ } } } @@ -66,7 +66,7 @@ class SettingsItem : GLib.Object { cr.restore (); cr.save ();- cr.set_source_rgba (1, 1, 1, 1);+ label.set_source_rgba (1, 1, 1, 1); label.set_font_size (20 * MainWindow.units); label.draw_at_baseline (cr, 21 * MainWindow.units, y + 25 * MainWindow.units); cr.restore ();@@ -780,9 +780,9 @@ public class TabBar : GLib.Object { center_y = tab_height / 2.0 + 4 / scale; if (tab_index == selected) {- cr.set_source_rgba (40 / 255.0, 57 / 255.0, 65 / 255.0, 1);+ label.set_source_rgba (40 / 255.0, 57 / 255.0, 65 / 255.0, 1); } else {- cr.set_source_rgba (101 / 255.0, 108 / 255.0, 116 / 255.0, 1);+ label.set_source_rgba (101 / 255.0, 108 / 255.0, 116 / 255.0, 1); } label.set_font_size (text_height);@@ -47,6 +47,12 @@ public abstract class Widget : GLib.Object { return widget_x <= x <= widget_x + get_width () && widget_y <= y <= widget_y + get_height (); }+ + public bool is_on_screen () {+ return (widget_y <= 0 <= widget_y + get_height ())+ || (widget_y <= allocation.height <= widget_y + get_height ())+ || (0 <= widget_y <= allocation.height);+ } } }