The Birdfont Source Code


All Repositories / birdfont.git / commitdiff – RSS feed

Rename text rendering directory

These changes was commited to the Birdfont repository Sat, 26 Dec 2015 11:05:51 +0000.

Contributing

Send patches or pull requests to johan.mattsson.m@gmail.com.
Clone this repository: git clone https://github.com/johanmattssonm/birdfont.git
[Sat, 26 Dec 2015 11:05:51 +0000]

Updated Files

birdfont/GtkWindow.vala
libbirdfont/GlyphSequence.vala
libbirdfont/Path.vala
libbirdfont/Renderer/CachedFont.vala
libbirdfont/Renderer/Drawing.vala
libbirdfont/Renderer/FallbackFont.vala
libbirdfont/Renderer/FontCache.vala
libbirdfont/Renderer/LineTextArea.vala
libbirdfont/Renderer/Text.vala
libbirdfont/Renderer/TextArea.vala
libbirdfont/Renderer/fontconfig.c
libbirdfont/TextRendering/CachedFont.vala
libbirdfont/TextRendering/Drawing.vala
libbirdfont/TextRendering/FallbackFont.vala
libbirdfont/TextRendering/FontCache.vala
libbirdfont/TextRendering/LineTextArea.vala
libbirdfont/TextRendering/Text.vala
libbirdfont/TextRendering/TextArea.vala
libbirdfont/TextRendering/fontconfig.c
--- a/birdfont/GtkWindow.vala +++ b/birdfont/GtkWindow.vala @@ -1,5 +1,5 @@ /* - Copyright (C) 2012s 2013 2014 Johan Mattsson + Copyright (C) 2012 2013 2014 2015 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
--- a/libbirdfont/GlyphSequence.vala +++ b/libbirdfont/GlyphSequence.vala @@ -111,7 +111,7 @@ g = font.get_glyph_by_name (name); - if (g != null) { + if (likely (g != null)) { old.add (g); if (a.alternates.size > 0) { @@ -119,7 +119,7 @@ string alt_name = a.alternates.get (0); Glyph? alt = font.get_glyph_by_name (alt_name); - if (alt != null) { + if (likely (alt != null)) { GlyphSequence replacement = new GlyphSequence (); replacement.add (alt); ligature_sequence.replace (old, replacement);
--- a/libbirdfont/Path.vala +++ b/libbirdfont/Path.vala @@ -90,7 +90,7 @@ /** The stroke of an outline when the path is not filled. */ public static double stroke_width = 0; public static bool show_all_line_handles = true; - public static bool fill_open_path {get; set;} + public static bool fill_open_path { get; set; } public double rotation = 0; public double skew = 0;
--- a/libbirdfont/Renderer/CachedFont.vala +++ /dev/null @@ -1,100 +1,1 @@ - /* - Copyright (C) 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 - 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 Gee; - - namespace BirdFont { - - public class CachedFont : GLib.Object { - public Font? font; - - public double top_limit { - get { return _top_limit; } - set { _top_limit = value; } - } - - public double bottom_limit { - get { return _bottom_limit; } - set { _bottom_limit = value; } - } - - public double base_line = 0; - double _top_limit = 92.77; // FIXME: load before first glyph - double _bottom_limit = -24.4; - - static FallbackFont fallback_font { - get { - if (_fallback_font == null) { - _fallback_font = new FallbackFont (); - } - - return (!) _fallback_font; - } - } - static FallbackFont? _fallback_font = null; - - public CachedFont (Font? font) { - Glyph? g; - Glyph glyph; - - this.font = font; - - g = get_glyph_by_name ("a"); - if (g != null) { - glyph = (!) g; - base_line = glyph.baseline; - top_limit = glyph.top_limit; - bottom_limit = glyph.bottom_limit; - } else { - warning("No default chararacter found in font."); - } - } - - public Glyph? get_glyph_by_name (string name) { - Glyph? g = null; - Font f; - Glyph glyph; - - if (font != null) { - f = (!) font; - g = f.get_glyph_by_name (name); - - if (g != null) { - glyph = (!) g; - glyph.top_limit = f.top_limit; - glyph.baseline = f.base_line; - glyph.bottom_limit = f.bottom_limit; - } - } - - if (g == null && name.char_count () == 1) { - f = fallback_font.get_single_glyph_font (name.get_char (0)); - g = f.get_glyph_by_name (name); - - if (g == null) { - return null; - } - - glyph = (!) g; - glyph.top_limit = f.top_limit; - glyph.baseline = f.base_line; - glyph.bottom_limit = f.bottom_limit; - } - - return g; - } - } - - }
diff --git libbirdfont/Renderer/Drawing.vala(deleted)
--- a/libbirdfont/Renderer/Drawing.vala +++ /dev/null @@ -1,51 +1,1 @@ - /* - 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 { - - /** Interface for creating drawing callbacks. */ - [Compact] - [CCode (ref_function = "bird_font_drawing_ref", unref_function = "bird_font_drawing_unref")] - public class Drawing { - - public int iterator_refcount = 1; - - public Drawing () { - } - - public void new_path (double x, double y) { - } - - public void curve_to (double xb, double yb, double xc, double yc, double xd, double yd) { - } - - public void close_path (double x, double y) { - } - - public unowned Drawing @ref () { - iterator_refcount++; - return this; - } - - public void unref () { - if (--iterator_refcount == 0) { - this.free (); - } - } - - private extern void free (); - } - - }
--- a/libbirdfont/Renderer/FallbackFont.vala +++ /dev/null @@ -1,338 +1,1 @@ - /* - Copyright (C) 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 - 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 Gee; - - [SimpleType] - [CCode (has_type_id = false)] - public extern struct FcConfig { - } - - [CCode (cname = "FcInitLoadConfigAndFonts")] - public extern FcConfig* FcInitLoadConfigAndFonts (); - - [CCode (cname = "FcConfigAppFontAddDir")] - public extern string* FcConfigAppFontAddDir (FcConfig* config, string path); - - [CCode (cname = "FcConfigSetSysRoot")] - public extern void FcConfigSetSysRoot (FcConfig* config, string path); - - [CCode (cname = "FcConfigParseAndLoad")] - public extern bool FcConfigParseAndLoad (FcConfig* config, string path, bool complain); - - [CCode (cname = "FcConfigSetCurrent")] - public extern void FcConfigSetCurrent (FcConfig* config); - - [CCode (cname = "FcConfigCreate")] - public extern FcConfig* FcConfigCreate (); - - [CCode (cname = "FcConfigFilename")] - public extern string FcConfigFilename (string path); - - [CCode (cname = "find_font")] - public extern string? find_font (FcConfig* font_config, string characters); - - [CCode (cname = "find_font_family")] - public extern string? find_font_family (FcConfig* font_config, string characters); - - [CCode (cname = "find_font_file")] - public extern string? find_font_file (FcConfig* font_config, string font_name); - - namespace BirdFont { - - // TODO: use font config - public class FallbackFont : GLib.Object { - Gee.ArrayList<File> font_directories; - - FontFace* default_font = null; - public static FcConfig* font_config = null; - static bool font_config_started = false; - - string default_font_file_name = "Roboto-Regular.ttf"; - string default_font_family_name = "Roboto"; - - Gee.HashMap<unichar, CachePair> glyphs; - Gee.ArrayList<CachePair> cached; - - public int max_cached_fonts = 300; - - string? default_font_file = null; - - public FallbackFont () { - string home = Environment.get_home_dir (); - font_directories = new Gee.ArrayList<File> (); - - if (!font_config_started) { - font_config_started = true; - - IdleSource idle = new IdleSource (); - idle.set_callback (() => { - Task t = new Task (init_font_config); - MainWindow.native_window.run_non_blocking_background_thread (t); - return false; - }); - idle.attach (null); - } - - add_font_folder ("/usr/share/fonts/"); - add_font_folder ("/usr/local/share/fonts/"); - add_font_folder (home + "/.local/share/fonts"); - add_font_folder (home + "/.fonts"); - add_font_folder ("C:\\Windows\\Fonts"); - add_font_folder (home + "/Library/Fonts"); - add_font_folder ("/Library/Fonts"); - add_font_folder ("/Network/Library/Fonts"); - add_font_folder ("/System/Library/Fonts"); - add_font_folder ("/System Folder/Fonts"); - - glyphs = new Gee.HashMap<unichar, CachePair> (); - cached = new Gee.ArrayList<CachePair> (); - - open_default_font (); - } - - ~FallbackFont () { - if (default_font != null) { - close_font (default_font); - } - } - - public void init_font_config () { - FcConfig* config; - - #if MAC - config = FcConfigCreate(); - - string bundle = (!) BirdFont.get_settings_directory ().get_path (); - FcConfigSetSysRoot(config, bundle); - - string path = FcConfigFilename((!) SearchPaths.search_file(null, "fontconfig.settings").get_path ()); - bool loaded = FcConfigParseAndLoad(config, path, true); - - if (!loaded) { - warning ("Fontconfig initialization failed."); - } - - FcConfigSetCurrent (config); - #else - config = FcInitLoadConfigAndFonts (); - #endif - - IdleSource idle = new IdleSource (); - - idle.set_callback (() => { - font_config = config; - printd("Fontconfig loaded\n"); - return false; - }); - idle.attach (null); - } - - public Font get_single_glyph_font (unichar c) { - Font f; - unichar last; - CachePair p; - - if (likely (glyphs.has_key (c))) { - p = glyphs.get (c); - - if (p.referenced < int.MAX) { - p.referenced++; - } - - return p.font; - } - - // remove glyphs from cache if it is full - if (cached.size > max_cached_fonts - 100) { - - cached.sort ((a, b) => { - CachePair pa = (CachePair) a; - CachePair pb = (CachePair) b; - return pb.referenced - pa.referenced; - }); - - int j = 0; - for (int i = cached.size - 1; i > 0; i--) { - if (j > 100) { - break; - } - - j++; - - last = cached.get (i).character; - glyphs.unset (last); - cached.remove_at (i); - } - } - - f = get_single_fallback_glyph_font (c); - p = new CachePair (f, c); - - glyphs.set (c, p); - cached.add (p); - - return (Font) f; - } - - Font get_single_fallback_glyph_font (unichar c) { - string? font_file; - BirdFontFile bf_parser; - Font bf_font; - StringBuilder? glyph_data; - FontFace* font; - - bf_font = new Font (); - font_file = null; - glyph_data = null; - - // don't use fallback font in private use area - if (0xe000 <= c <= 0xf8ff) { - return bf_font; - } - - // control characters - if (c <= 0x001f || (0x007f <= c <= 0x008d)) { - return bf_font; - } - - // check if glyph is available in roboto - if (default_font != null) { - glyph_data = get_glyph_in_font ((!) default_font, c); - } - - // use fontconfig to find a fallback font - if (glyph_data == null) { - font_file = find_font (font_config, (!) c.to_string ()); - if (font_file != null) { - font = open_font ((!) font_file); - glyph_data = get_glyph_in_font (font, c); - close_font (font); - } - } - - if (glyph_data != null) { - bf_parser = new BirdFontFile (bf_font); - bf_parser.load_data (((!) glyph_data).str); - } - - return bf_font; - } - - public StringBuilder? get_glyph_in_font (FontFace* font, unichar c) { - StringBuilder? glyph_data = null; - GlyphCollection gc; - - gc = new GlyphCollection (c, (!)c.to_string ()); - glyph_data = load_glyph (font, (uint) c); - - return glyph_data; - } - - void add_font_folder (string f) { - File folder = File.new_for_path (f); - FileInfo? file_info; - string fn; - string file_attributes; - try { - if (folder.query_exists ()) { - font_directories.add (folder); - - file_attributes = FileAttribute.STANDARD_NAME; - file_attributes += ","; - file_attributes += FileAttribute.STANDARD_TYPE; - var enumerator = folder.enumerate_children (file_attributes, 0); - - while ((file_info = enumerator.next_file ()) != null) { - fn = ((!) file_info).get_name (); - - if (((!)file_info).get_file_type () == FileType.DIRECTORY) { - add_font_folder ((!) get_child (folder, fn).get_path ()); - } - } - } - } catch (GLib.Error e) { - warning (e.message); - } - } - - File search_font_file (string font_file) { - File d, f; - - for (int i = font_directories.size - 1; i >= 0; i--) { - d = font_directories.get (i); - f = get_child (d, font_file); - - if (f.query_exists ()) { - return f; - } - } - - warning (@"The font $font_file not found"); - return File.new_for_path (font_file); - } - - public string? get_default_font_file () { - File font_file; - string? fn = null; - - if (likely (default_font_file != null)) { - return default_font_file; - } - - font_file = SearchPaths.search_file (null, default_font_file_name); - - if (font_file.query_exists ()) { - fn = (!) font_file.get_path (); - } else { - font_file = search_font_file (default_font_file_name); - - if (font_file.query_exists ()) { - fn = (!) font_file.get_path (); - } else { - fn = find_font_file (font_config, default_font_family_name); - } - } - - if (likely (fn != null)) { - default_font_file = fn; - return fn; - } - - warning(default_font_family_name + " not found"); - return null; - } - - void open_default_font () { - string? fn = get_default_font_file (); - - if (fn != null) { - default_font = open_font ((!) fn); - } - } - - class CachePair : GLib.Object { - public Font font; - public unichar character; - public int referenced = 1; - - public CachePair (Font f, unichar c) { - font = f; - character = c; - } - } - } - - }
--- a/libbirdfont/Renderer/FontCache.vala +++ /dev/null @@ -1,84 +1,1 @@ - /* - 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 Gee; - - namespace BirdFont { - - /** Thread specific font cache. */ - public class FontCache { - public static FallbackFont fallback_font; - - static FontCache? default_cache = null; - Gee.HashMap<string, CachedFont> fonts; - CachedFont fallback; - - public FontCache () { - if (is_null (fallback_font)) { - fallback_font = new FallbackFont (); - } - - fallback = new CachedFont (null); - fonts = new Gee.HashMap<string, CachedFont> (); - } - - public CachedFont get_font (string file_name) { - CachedFont c; - Font f; - bool ok; - - if (file_name == "") { - return fallback; - } - - if (fonts.has_key (file_name)) { - c = fonts.get (file_name); - return c; - } - - f = new Font (); - f.set_file (file_name); - ok = f.load (); - if (!ok) { - stderr.printf ("Can't load %s\n", file_name); - return new CachedFont (null); - } - - c = new CachedFont (f); - - if (file_name == "") { - warning ("No file."); - return c; - } - - fonts.set (file_name, c); - return c; - } - - public static FontCache get_default_cache () { - if (default_cache == null) { - default_cache = new FontCache (); - } - - return (!) default_cache; - } - - public CachedFont get_fallback () { - return fallback; - } - - } - - }
--- a/libbirdfont/Renderer/LineTextArea.vala +++ /dev/null @@ -1,34 +1,1 @@ - /* - 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 Math; - - namespace BirdFont { - - public class LineTextArea : TextArea { - - public LineTextArea (double size) { - base (size); - - single_line = true; - min_height = size; - height = min_height; - - layout (); - } - } - - }
diff --git libbirdfont/Renderer/Text.vala(deleted)
--- a/libbirdfont/Renderer/Text.vala +++ /dev/null @@ -1,585 +1,1 @@ - /* - Copyright (C) 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 - 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 Math; - - namespace BirdFont { - - /** Test implementation of a birdfont rendering engine. */ - public class Text : Widget { - FontCache font_cache; - public CachedFont cached_font; - - Surface? cache = null; - - public string text; - - private bool use_cache = true; - - GlyphSequence glyph_sequence { - get { - if (gs == null) { - gs = generate_glyphs (); - } - - return (!) gs; - } - } - - Gee.ArrayList<string> glyph_names; - GlyphSequence? gs = null; - - public delegate void Iterator (Glyph glyph, double kerning, bool last); - public double font_size; - double sidebearing_extent = 0; - - public double r = 0; - public double g = 0; - public double b = 0; - public double a = 1; - double truncated_width = -1; - - double margin_left = 0; - - public Text (string text = "", double size = 17, double margin_bottom = 0) { - this.margin_bottom = margin_bottom; - font_cache = FontCache.get_default_cache (); - cached_font = font_cache.get_fallback (); - - set_text (text); - set_font_size (size); - } - - public void set_use_cache (bool cache) { - use_cache = cache; - } - - public string get_text () { - return text; - } - - /** Set font for this text area. - * @param font_absolute path to the font file or a file name for one of the font files in search paths. - * @return true if the font was found - */ - public bool load_font (string font_file) { - File path; - File f; - FontCache fc; - - f = File.new_for_path (font_file); - path = (f.query_exists ()) ? f : SearchPaths.find_file (null, font_file); - - fc = FontCache.get_default_cache (); - cached_font = fc.get_font ((!) path.get_path ()); - gs = generate_glyphs (); - - return cached_font.font != null; - } - - public void set_font_size (double height_in_pixels) { - font_size = height_in_pixels; - sidebearing_extent = 0; - - if (gs == null) { // ensure height is loaded for the font - gs = generate_glyphs (); - } - } - - public void set_font_cache (FontCache font_cache) { - this.font_cache = font_cache; - } - - public void set_text (string text) { - this.text = text; - gs = null; - sidebearing_extent = 0; - cache = null; - } - - private GlyphSequence generate_glyphs () { - int index; - unichar c; - string name; - Glyph? g; - GlyphSequence gs; - - gs = new GlyphSequence (); - - glyph_names = new Gee.ArrayList<string> (); - index = 0; - while (text.get_next_char (ref index, out c)) { - name = (!) c.to_string (); - g = cached_font.get_glyph_by_name (name); - - gs.glyph.add (g); - glyph_names.add (name); - } - - return gs; - } - - public void iterate (Iterator iter) { - Glyph glyph; - double w, kern; - int wi; - Glyph? prev; - Glyph? g; - GlyphSequence word_with_ligatures; - GlyphRange? gr_left, gr_right; - GlyphSequence word; - KerningClasses kc; - Font empty = Font.empty; - - glyph = new Glyph.no_lines ("", '\0'); - - w = 0; - prev = null; - kern = 0; - - word = glyph_sequence; - wi = 0; - - if (cached_font.font != null) { - word_with_ligatures = word.process_ligatures ((!) cached_font.font); - } else { - word_with_ligatures = word.process_ligatures (new Font ()); - } - - gr_left = null; - gr_right = null; - - if (cached_font.font != null) { - kc = ((!) cached_font.font).get_kerning_classes (); - } else { - kc = new KerningClasses (empty); - } - - if (word_with_ligatures.glyph.size > 0) { - g = word_with_ligatures.glyph.get (0); - if (g != null) { - margin_left = ((!) g).get_left_side_bearing (); - - if (margin_left < 0) { - margin_left = -margin_left; - } else { - margin_left = 0; - } - } - } - - for (int i = 0; i < word_with_ligatures.glyph.size; i++) { - g = word_with_ligatures.glyph.get (i); - - if (g == null || prev == null || wi == 0) { - kern = 0; - } else { - return_if_fail (wi < word_with_ligatures.ranges.size); - return_if_fail (wi - 1 >= 0); - - gr_left = word_with_ligatures.ranges.get (wi - 1); - gr_right = word_with_ligatures.ranges.get (wi); - - kern = kc.get_kerning_for_pair (((!) prev).get_name (), ((!) g).get_name (), gr_left, gr_right); - } - - // process glyph - if (g == null && (0 <= i < glyph_names.size)) { - g = cached_font.get_glyph_by_name (glyph_names.get (i)); - } - - glyph = (g == null) ? new Glyph ("") : (!) g; - iter (glyph, kern, i + 1 == word_with_ligatures.glyph.size); - prev = g; - wi++; - } - } - - // FIXME: some fonts doesn't have on curve extrema - public double get_extent () { - double x = 0; - - iterate ((glyph, kerning, last) => { - double x1, y1, x2, y2; - double lsb; - - lsb = glyph.left_limit; - - if (!last) { - x += (glyph.get_width () + kerning) * get_scale (glyph); - } else { - glyph.boundaries (out x1, out y1, out x2, out y2); - x += (x2 - lsb) * get_scale (glyph); - } - }); - - return x; - } - - public double get_sidebearing_extent () { - double x ; - - if (likely (sidebearing_extent > 0)) { - return sidebearing_extent; - } - - x = 0; - - iterate ((glyph, kerning, last) => { - double lsb; - lsb = glyph.left_limit; - x += (glyph.get_width () + kerning) * get_scale (glyph); - }); - - sidebearing_extent = x; - return x; - } - - public override double get_height () { - return font_size; - } - - public double get_acender () { - double max_height = 0; - - iterate ((glyph, kerning, last) => { - double x1, y1, x2, y2; - double h; - glyph.boundaries (out x1, out y1, out x2, out y2); - h = Math.fmax (y1, y2) - Math.fmin (y1, y2); - h *= get_scale (glyph) - glyph.baseline * get_scale (glyph); - if (h > max_height) { - max_height = h; - } - }); - - return max_height; - } - - public override double get_width () { - double x = 0; - bool first = true; - - iterate ((glyph, kerning, last) => { - double x1, y1, x2, y2; - double lsb; - - lsb = glyph.left_limit; - - if (first) { - glyph.boundaries (out x1, out y1, out x2, out y2); - x += (glyph.get_width () + kerning - Math.fmin (x1, x2)) * get_scale (glyph); - first = false; - } else if (!last) { - x += (glyph.get_width () + kerning) * get_scale (glyph); - } else { - glyph.boundaries (out x1, out y1, out x2, out y2); - x += (x2 - lsb) * get_scale (glyph); - } - }); - - return x; - } - - public double get_decender () { - double decender_max = get_max_decender (); - return decender_max > 0 ? decender_max : 0; - } - - private double get_max_decender () { - double decender = 0; - double decender_max = 0; - - iterate ((glyph, kerning, last) => { - double x1, y1, x2, y2; - double y; - glyph.boundaries (out x1, out y1, out x2, out y2); - y = Math.fmin (y1, y2); - decender = (glyph.baseline - y) * get_scale (glyph); - if (decender > decender_max) { - decender_max = decender; - } - }); - - return decender_max; - } - - public override void draw (Context cr) { - double descender = cached_font.bottom_limit + cached_font.base_line; - double y = widget_y + get_height () + get_font_scale () * descender; // FIXME: - draw_at_baseline (cr, widget_x, y); - } - - public void draw_at_top (Context cr, double px, double py, string cacheid = "") { - double s = get_font_scale (); - double y = py + s * (cached_font.top_limit - cached_font.base_line); - draw_at_baseline (cr, px, y, cacheid); - } - - public void set_source_rgba (double r, double g, double b, double a) { - if (this.r != r || - this.g != g || - this.b != b || - this.a != a) { - - this.r = r; - this.g = g; - this.b = b; - this.a = a; - cache = null; - } - } - - public string get_cache_id (int offset_x, int offset_y) { - string key; - int64 c; - - c = (((int64) (r * 255)) << 24) - | (((int64) (g * 255)) << 16) - | (((int64) (b * 255)) << 8) - | (((int64) (a * 255)) << 0); - - // FIXME: use binary key - key = @"$font_size $c $offset_x $offset_y"; - - return key; - } - - public void draw_at_baseline (Context cr, double px, double py, string cacheid = "") { - if (cache == null) { - cache = draw_on_cache_surface (cacheid); - } - - double screen_scale = Screen.get_scale (); - double font_scale = get_font_scale (); - double cache_y = py - font_scale * (cached_font.top_limit - cached_font.base_line); - - cr.save(); - cr.scale (1 / screen_scale, 1 / screen_scale); - double scaled_x = (px - margin_left) * screen_scale; - double scaled_y = cache_y * screen_scale; - cr.set_source_surface ((!) cache, (int) rint (scaled_x), (int) rint (scaled_y)); - cr.paint (); - cr.restore(); - } - - Surface draw_on_cache_surface (string cacheid) { - double y; - double ratio; - double cc_y; - Context cr; - Surface cache_surface; - double screen_scale = Screen.get_scale(); - double h = font_size * screen_scale + 1; - - ratio = get_font_scale (); - cc_y = (cached_font.top_limit - cached_font.base_line) * ratio; - - // double x = margin_left * ratio; - double x = 0; - double w = get_sidebearing_extent () * screen_scale + x + margin_left + 1; - - cache_surface = Screen.create_background_surface ((int) w, (int) h); - cr = new Context (cache_surface); - cr.scale (screen_scale, screen_scale); - - y = cc_y; - - if (unlikely (cached_font.base_line != 0)) { - warning ("Base line not zero."); - } - - iterate ((glyph, kerning, last) => { - double end; - - x += kerning * ratio; - end = x + glyph.get_width () * ratio; - - // truncation - if (truncated_width > 0 && end > truncated_width) { - return; - } - - if (use_cache) { - draw_chached (cr, glyph, kerning, last, x, y, cc_y, - ratio, cacheid); - } else { - draw_without_cache (cr, glyph, kerning, last, x, y, cc_y, ratio); - } - - x = end; - }); - - return cache_surface; - } - - void draw_without_cache (Context cr, Glyph glyph, double kerning, bool last, - double x, double y, double cc_y, double ratio) { - - double lsb; - - cr.save (); - cr.set_source_rgba (r, g, b, a); - cr.new_path (); - - lsb = glyph.left_limit; - - foreach (Path path in glyph.get_visible_paths ()) { - draw_path (cr, glyph, path, lsb, x, y, ratio); - } - - cr.fill (); - cr.restore (); - - } - - void draw_chached (Context cr, Glyph glyph, double kerning, bool last, - double x, double y, double cc_y, double ratio, - string cacheid = "") { - - double lsb; - Surface cache; - Surface cached_glyph; - Context cc; - string cache_id; - double glyph_margin_left = glyph.get_left_side_bearing (); - - if (glyph_margin_left < 0) { - glyph_margin_left = -glyph_margin_left; - } else { - glyph_margin_left = 0; - } - - double xp = (x - glyph_margin_left * ratio) * Screen.get_scale (); - double yp = (y - cc_y) * Screen.get_scale (); - int offset_x, offset_y; - - offset_x = (int) (10 * (xp - (int) xp)); - offset_y = (int) (10 * (yp - (int) yp)); - - cache_id = (cacheid == "") ? get_cache_id (offset_x, offset_y) : cacheid; - - if (!glyph.has_cache (cache_id)) { - int w = (int) ((2 * glyph_margin_left * ratio + glyph.get_width ()) * ratio) + 2; - int h = (int) font_size + 2; - cache = Screen.create_background_surface (w, h); - cc = new Context (cache); - - cc.scale(Screen.get_scale (), Screen.get_scale ()); - - lsb = glyph.left_limit - glyph_margin_left; - - cc.save (); - cc.set_source_rgba (r, g, b, a); - cc.new_path (); - - foreach (Path path in glyph.get_visible_paths ()) { - draw_path (cc, glyph, path, lsb, glyph_margin_left * ratio + offset_x / 10.0, cc_y + offset_y / 10.0, ratio); - } - - cc.fill (); - cc.restore (); - - if (use_cache) { - glyph.set_cache (cache_id, cache); - } - - cached_glyph = cache; - } else { - cached_glyph = glyph.get_cache (cache_id); - } - - cr.save (); - cr.set_antialias (Cairo.Antialias.NONE); - cr.scale(1 / Screen.get_scale (), 1 / Screen.get_scale ()); - cr.set_source_surface (cached_glyph, - (int) xp, - (int) yp); - cr.paint (); - cr.restore (); - } - - void draw_path (Context cr, Glyph glyph, Path path, - double lsb, double x, double y, double scale) { - - EditPoint e, prev; - double xa, ya, xb, yb, xc, yc, xd, yd; - double by; - double s = get_scale (glyph); - - if (path.points.size > 0) { - if (unlikely (path.is_open ())) { - warning (@"Path is open in $(glyph.get_name ())."); - } - - //path.add_hidden_double_points (); // FIXME: this distorts shapes - - prev = path.points.get (path.points.size - 1); - xa = (prev.x - lsb) * s + x; - ya = y - prev.y * s; - cr.move_to (xa, ya); - - by = (y - cached_font.base_line * s); - for (int i = 0; i < path.points.size; i++) { - e = path.points.get (i).copy (); - - PenTool.convert_point_segment_type (prev, e, PointType.CUBIC); - - xb = (prev.get_right_handle ().x - lsb) * s + x; - yb = by - prev.get_right_handle ().y * s; - - xc = (e.get_left_handle ().x - lsb) * s + x; - yc = by - e.get_left_handle ().y * s; - - xd = (e.x - lsb) * s + x; - yd = by - e.y * s; - - cr.curve_to (xb, yb, xc, yc, xd, yd); - cr.line_to (xd, yd); - - prev = e; - } - } - } - - public double get_baseline_to_bottom (Glyph g) { - return get_scale (g) * (-g.baseline - g.bottom_limit); - } - - public double get_scale (Glyph g) { - double s = g.top_limit - g.bottom_limit; - - if (s == 0) { - s = cached_font.top_limit - cached_font.bottom_limit; - } - - return font_size / s; - } - - public double get_font_scale () { - return font_size / (cached_font.top_limit - cached_font.bottom_limit); - } - - public double get_baseline_to_bottom_for_font () { - return get_font_scale () * (-cached_font.base_line - cached_font.bottom_limit); - } - - public void truncate (double max_width) { - truncated_width = max_width; - } - } - - }
--- a/libbirdfont/Renderer/TextArea.vala +++ /dev/null @@ -1,1666 +1,1 @@ - /* - 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 Math; - - namespace BirdFont { - - public class TextArea : Widget { - - public double min_width = 500; - public double min_height = 100; - public double font_size; - public double padding = 3.3; - public bool single_line = false; - public Color text_color = Color.black (); - - public bool draw_carret { - get { return carret_is_visible; } - set { - carret_is_visible = value; - if (!value) { - update_selection = false; - selection_end = carret.copy (); - } - } - } - public bool carret_is_visible = false; - public bool draw_border = true; - - public double width; - public double height; - - Carret carret = new Carret (); - Carret selection_end = new Carret (); - bool update_selection = false; - public bool show_selection = false; - - public signal void scroll (double pixels); - public signal void text_changed (string text); - public signal void enter (string text); - - Gee.ArrayList<Paragraph> paragraphs = new Gee.ArrayList<Paragraph> (); - private static const int DONE = -2; - - int last_paragraph = 0; - string text; - int text_length; - - Gee.ArrayList<TextUndoItem> undo_items = new Gee.ArrayList<TextUndoItem> (); - Gee.ArrayList<TextUndoItem> redo_items = new Gee.ArrayList<TextUndoItem> (); - - bool store_undo_state_at_next_event = false; - - public bool editable; - - public TextArea (double font_size = 20, Color? c = null) { - this.font_size = font_size; - width = min_width; - height = min_height; - editable = true; - - if (c != null) { - text_color = (!) c; - } - } - - public override void focus (bool focus) { - draw_carret = focus; - } - - public override double get_height () { - return height + 2 * padding; - } - - public override double get_width () { - return width + 2 * padding; - } - - public void set_font_size (double z) { - font_size = z; - } - - bool generate_paragraphs () { - Paragraph paragraph; - - int next_paragraph = -1; - - if (is_null (text)) { - warning ("No text"); - return false; - } - - if (last_paragraph == DONE) { - return false; - } - - next_paragraph = text.index_of ("\n", last_paragraph); - - if (next_paragraph == -1) { - paragraph = new Paragraph (text.substring (last_paragraph), font_size, paragraphs.size, text_color); - paragraphs.add (paragraph); - last_paragraph = DONE; - } else { - next_paragraph += "\n".length; - paragraph = new Paragraph (text.substring (last_paragraph, next_paragraph - last_paragraph), font_size, paragraphs.size, text_color); - paragraphs.add (paragraph); - last_paragraph = next_paragraph; - } - - return last_paragraph != DONE; - } - - void generate_all_paragraphs () { - while (generate_paragraphs ()) { - } - } - - public override void key_press (uint keyval) { - unichar c; - TextUndoItem ui; - - if (!editable) { - return; - } - - c = (unichar) keyval; - - switch (c) { - case ' ': - store_undo_edit_state (); - add_character (keyval); - break; - case 'a': - if (KeyBindings.has_ctrl () || KeyBindings.has_logo ()) { - select_all (); - } else { - add_character (keyval); - } - break; - case 'c': - if (KeyBindings.has_ctrl () || KeyBindings.has_logo ()) { - ClipTool.copy_text (this); - } else { - add_character (keyval); - } - break; - case 'v': - if (KeyBindings.has_ctrl () || KeyBindings.has_logo ()) { - ClipTool.paste_text (this); - store_undo_state_at_next_event = true; - } else { - add_character (keyval); - } - break; - case 'y': - if (KeyBindings.has_ctrl () || KeyBindings.has_logo ()) { - redo (); - } else { - add_character (keyval); - } - break; - case 'z': - if (KeyBindings.has_ctrl () || KeyBindings.has_logo ()) { - undo (); - } else { - add_character (keyval); - } - break; - case Key.RIGHT: - check_selection (); - move_carret_next (); - break; - case Key.LEFT: - check_selection (); - move_carret_previous (); - break; - case Key.DOWN: - check_selection (); - move_carret_next_row (); - break; - case Key.UP: - check_selection (); - move_carret_previous_row (); - break; - case Key.END: - check_selection (); - move_carret_to_end_of_line (); - break; - case Key.HOME: - check_selection (); - move_carret_to_beginning_of_line (); - break; - case Key.BACK_SPACE: - if (has_selection ()) { - ui = delete_selected_text (); - undo_items.add (ui); - redo_items.clear (); - store_undo_state_at_next_event = true; - } else { - ui = remove_last_character (); - undo_items.add (ui); - redo_items.clear (); - store_undo_state_at_next_event = true; - } - text_changed (get_text ()); - break; - case Key.ENTER: - store_undo_edit_state (); - insert_text ("\n"); - - if (single_line) { - enter (get_text ()); - } - break; - case Key.DEL: - if (has_selection ()) { - ui = delete_selected_text (); - undo_items.add (ui); - redo_items.clear (); - store_undo_state_at_next_event = true; - } else { - ui = remove_next_character (); - undo_items.add (ui); - redo_items.clear (); - store_undo_state_at_next_event = true; - } - text_changed (get_text ()); - break; - default: - if (!KeyBindings.has_ctrl () && !KeyBindings.has_logo ()) { - add_character (keyval); - } - break; - } - - GlyphCanvas.redraw (); - } - - void check_selection () { - if (!has_selection () && KeyBindings.has_shift ()) { - show_selection = true; - selection_end = carret.copy (); - } - - if (!KeyBindings.has_shift ()) { - show_selection = false; - } - } - - private void add_character (uint keyval) { - unichar c = (unichar) keyval; - string s; - - if (!is_modifier_key (keyval) - && !KeyBindings.has_ctrl () - && !KeyBindings.has_alt ()) { - - s = (!) c.to_string (); - - if (s.validate ()) { - if (store_undo_state_at_next_event) { - store_undo_edit_state (); - store_undo_state_at_next_event = false; - } - - insert_text (s); - } - } - } - - Paragraph get_current_paragraph () { - Paragraph p; - - if (unlikely (!(0 <= carret.paragraph < paragraphs.size))) { - warning (@"No paragraph, index: $(carret.paragraph), size: $(paragraphs.size)"); - p = new Paragraph ("", 0, 0, text_color); - paragraphs.add (p); - return p; - } - - p = paragraphs.get (carret.paragraph); - return p; - } - - public void set_text (string t) { - int tl; - - if (single_line) { - text = t.replace ("\n", "").replace ("\r", ""); - } else { - text = t; - } - - tl = t.length; - text_length += tl; - - paragraphs.clear (); - generate_paragraphs (); - - return_if_fail (paragraphs.size != 0); - - carret.paragraph = paragraphs.size - 1; - carret.character_index = paragraphs.get (paragraphs.size - 1).text.length; - selection_end = carret.copy (); - show_selection = false; - - text_changed (get_text ()); - } - - Carret get_selection_start () { - if (carret.paragraph == selection_end.paragraph) { - return carret.character_index < selection_end.character_index ? carret : selection_end; - } - - return carret.paragraph < selection_end.paragraph ? carret : selection_end; - } - - Carret get_selection_stop () { - if (carret.paragraph == selection_end.paragraph) { - return carret.character_index > selection_end.character_index ? carret : selection_end; - } - - return carret.paragraph > selection_end.paragraph ? carret : selection_end; - } - - public string get_selected_text () { - Carret selection_start, selection_stop; - int i; - Paragraph pg; - StringBuilder sb; - - sb = new StringBuilder (); - - if (!has_selection ()) { - return "".dup (); - } - - selection_start = get_selection_start (); - selection_stop = get_selection_stop (); - - if (selection_start.paragraph == selection_stop.paragraph) { - pg = paragraphs.get (selection_start.paragraph); - return pg.text.substring (selection_start.character_index, selection_stop.character_index - selection_start.character_index); - } - - pg = paragraphs.get (selection_start.paragraph); - sb.append (pg.text.substring (selection_start.character_index)); - - for (i = selection_start.paragraph + 1; i < selection_stop.paragraph; i++) { - return_val_if_fail (0 <= i < paragraphs.size, "".dup ()); - pg = paragraphs.get (i); - sb.append (pg.text); - } - - pg = paragraphs.get (selection_stop.paragraph); - sb.append (pg.text.substring (0, selection_stop.character_index)); - - return sb.str; - } - - public void select_all () { - while (last_paragraph != DONE) { - generate_paragraphs (); - } - - if (paragraphs.size > 0) { - carret.paragraph = 0; - carret.character_index = 0; - selection_end.paragraph = paragraphs.size - 1; - selection_end.character_index = paragraphs.get (paragraphs.size - 1).text_length; - show_selection = true; - } - } - - public TextUndoItem delete_selected_text () { - Carret selection_start, selection_stop; - int i; - Paragraph pg, pge; - string e, s, n; - bool same; - TextUndoItem ui; - - ui = new TextUndoItem (carret); - - e = ""; - s = ""; - n = ""; - - if (!has_selection ()) { - warning ("No selected text."); - return ui; - } - - selection_start = get_selection_start (); - selection_stop = get_selection_stop (); - - same = selection_start.paragraph == selection_stop.paragraph; - - if (!same) { - return_val_if_fail (0 <= selection_start.paragraph < paragraphs.size, ui); - pg = paragraphs.get (selection_start.paragraph); - s = pg.text.substring (0, selection_start.character_index); - - return_val_if_fail (0 <= selection_stop.paragraph < paragraphs.size, ui); - pge = paragraphs.get (selection_stop.paragraph); - e = pge.text.substring (selection_stop.character_index); - - if (!s.has_suffix ("\n")) { - ui.deleted.add (pge.copy ()); - ui.edited.add (pg.copy ()); - - pg.set_text (s + e); - pge.set_text (""); - } else { - ui.edited.add (pg.copy ()); - ui.edited.add (pge.copy ()); - - pg.set_text (s); - pge.set_text (e); - } - } else { - return_val_if_fail (0 <= selection_start.paragraph < paragraphs.size, ui); - - pg = paragraphs.get (selection_start.paragraph); - n = pg.text.substring (0, selection_start.character_index); - n += pg.text.substring (selection_stop.character_index); - - if (n == "") { - ui.deleted.add (pg.copy ()); - paragraphs.remove_at (selection_start.paragraph); - } else { - ui.edited.add (pg.copy ()); - } - - pg.set_text (n); - } - - if (e == "" && !same) { - paragraphs.remove_at (selection_stop.paragraph); - } - - for (i = selection_stop.paragraph - 1; i > selection_start.paragraph; i--) { - return_val_if_fail (0 <= i < paragraphs.size, ui); - ui.deleted.add (paragraphs.get (i)); - paragraphs.remove_at (i); - } - - if (s == "" && !same) { - return_val_if_fail (0 <= selection_start.paragraph < paragraphs.size, ui); - paragraphs.remove_at (selection_start.paragraph); - } - - carret = selection_start.copy (); - selection_end = carret.copy (); - - show_selection = false; - update_paragraph_index (); - layout (); - - return ui; - } - - void update_paragraph_index () { - int i = 0; - foreach (Paragraph p in paragraphs) { - p.index = i; - i++; - } - } - - public TextUndoItem remove_last_character () { - TextUndoItem ui; - move_carret_previous (); - ui = remove_next_character (); - return ui; - } - - public TextUndoItem remove_next_character () { - Paragraph paragraph; - Paragraph next_paragraph; - int index; - unichar c; - string np; - TextUndoItem ui; - - ui = new TextUndoItem (carret); - - return_val_if_fail (0 <= carret.paragraph < paragraphs.size, ui); - paragraph = paragraphs.get (carret.paragraph); - - index = carret.character_index; - - paragraph.text.get_next_char (ref index, out c); - - if (index >= paragraph.text_length) { - np = paragraph.text.substring (0, carret.character_index); - - if (carret.paragraph + 1 < paragraphs.size) { - next_paragraph = paragraphs.get (carret.paragraph + 1); - paragraphs.remove_at (carret.paragraph + 1); - - np = np + next_paragraph.text; - - ui.deleted.add (next_paragraph); - } - - paragraph.set_text (np); - ui.edited.add (paragraph); - } else { - np = paragraph.text.substring (0, carret.character_index) + paragraph.text.substring (index); - paragraph.set_text (np); - - if (np == "") { - return_val_if_fail (carret.paragraph > 0, ui); - carret.paragraph--; - paragraph = paragraphs.get (carret.paragraph); - carret.character_index = paragraph.text_length; - - ui.deleted.add (paragraphs.get (carret.paragraph + 1)); - - paragraphs.remove_at (carret.paragraph + 1); - } else { - ui.edited.add (paragraph); - } - } - - update_paragraph_index (); - layout (); - - return ui; - } - - public void move_carret_next () { - unichar c; - - move_carret_one_character (); - - if (KeyBindings.has_ctrl ()) { - while (true) { - c = move_carret_one_character (); - - if (c == '\0' || c == ' ') { - break; - } - } - } - } - - unichar move_carret_one_character () { - Paragraph paragraph; - int index; - unichar c; - - return_val_if_fail (0 <= carret.paragraph < paragraphs.size, '\0'); - paragraph = paragraphs.get (carret.paragraph); - - index = carret.character_index; - - paragraph.text.get_next_char (ref index, out c); - - if (index >= paragraph.text_length && carret.paragraph + 1 < paragraphs.size) { - carret.paragraph++; - carret.character_index = 0; - c = ' '; - } else { - carret.character_index = index; - } - - return c; - } - - public void move_carret_previous () { - unichar c; - - move_carret_back_one_character (); - - if (KeyBindings.has_ctrl ()) { - while (true) { - c = move_carret_back_one_character (); - - if (c == '\0' || c == ' ') { - break; - } - } - } - } - - unichar move_carret_back_one_character () { - Paragraph paragraph; - int index, last_index; - unichar c; - - return_val_if_fail (0 <= carret.paragraph < paragraphs.size, '\0'); - paragraph = paragraphs.get (carret.paragraph); - - index = 0; - last_index = -1; - - while (paragraph.text.get_next_char (ref index, out c) && index < carret.character_index) { - last_index = index; - } - - if (last_index <= 0 && carret.paragraph > 0) { - carret.paragraph--; - - return_val_if_fail (0 <= carret.paragraph < paragraphs.size, '\0'); - paragraph = paragraphs.get (carret.paragraph); - carret.character_index = paragraph.text_length; - - if (paragraph.text.has_suffix ("\n")) { - carret.character_index -= "\n".length; - } - - c = ' '; - } else if (last_index > 0) { - carret.character_index = last_index; - } else { - carret.character_index = 0; - c = ' '; - } - - return_val_if_fail (0 <= carret.paragraph < paragraphs.size, '\0'); - - return c; - } - - public void move_carret_next_row () { - double nr = font_size; - - if (carret.desired_y + 2 * font_size >= allocation.height) { - scroll (2 * font_size); - nr = -font_size; - } - - if (carret.desired_y + nr < widget_y + height - padding) { - carret = get_carret_at (carret.desired_x - widget_x - padding, carret.desired_y + nr); - } - } - - public void move_carret_to_end_of_line () { - carret = get_carret_at (widget_x + padding + width, carret.desired_y, false); - } - - public void move_carret_to_beginning_of_line () { - carret = get_carret_at (widget_x, carret.desired_y, false); - } - - public void move_carret_previous_row () { - double nr = -font_size; - - if (carret.desired_y - 2 * font_size < 0) { - scroll (-2 * font_size); - nr = font_size; - } - - if (carret.desired_y + nr > widget_y + padding) { - carret = get_carret_at (carret.desired_x, carret.desired_y + nr); - } - } - - public bool has_selection () { - return show_selection && selection_is_visible (); - } - - private bool selection_is_visible () { - return carret.paragraph != selection_end.paragraph || carret.character_index != selection_end.character_index; - } - - public void insert_text (string t) { - string s; - Paragraph paragraph; - TextUndoItem ui; - Gee.ArrayList<string> pgs; - bool u = false; - - pgs = new Gee.ArrayList<string> (); - - if (single_line) { - s = t.replace ("\n", "").replace ("\r", ""); - pgs.add (s); - } else { - if (t.last_index_of ("\n") > 0) { - string[] parts = t.split ("\n"); - int i; - for (i = 0; i < parts.length -1; i++) { - pgs.add (parts[i]); - pgs.add ("\n"); - } - - pgs.add (parts[parts.length - 1]); - - if (t.has_suffix ("\n")) { - pgs.add ("\n"); - } - } else { - s = t; - pgs.add (s); - } - } - - if (has_selection () && show_selection) { - ui = delete_selected_text (); - u = true; - - if (paragraphs.size == 0) { - paragraphs.add (new Paragraph ("", font_size, 0, text_color)); - } - } else { - ui = new TextUndoItem (carret); - } - - return_if_fail (0 <= carret.paragraph < paragraphs.size); - paragraph = paragraphs.get (carret.paragraph); - - if (pgs.size > 0) { - if (!u) { - ui.edited.add (paragraph.copy ()); - } - - string first = pgs.get (0); - - string end; - string nt = paragraph.text.substring (0, carret.character_index); - - nt += first; - end = paragraph.text.substring (carret.character_index); - - paragraph.set_text (nt); - - int paragraph_index = carret.paragraph; - Paragraph next_paragraph = paragraph; - for (int i = 1; i < pgs.size; i++) { - paragraph_index++; - string next = pgs.get (i); - next_paragraph = new Paragraph (next, font_size, paragraph_index, text_color); - paragraphs.insert (paragraph_index, next_paragraph); - ui.added.add (next_paragraph); - u = true; - } - - carret.paragraph = paragraph_index; - carret.character_index = next_paragraph.text.length; - - next_paragraph.set_text (next_paragraph.text + end); - } - - if (u) { - undo_items.add (ui); - redo_items.clear (); - } - - update_paragraph_index (); - layout (); - - text_changed (get_text ()); - show_selection = false; - } - - public string get_text () { - StringBuilder sb = new StringBuilder (); - - generate_all_paragraphs (); - - foreach (Paragraph p in paragraphs) { - sb.append (p.text); - } - - return sb.str; - } - - Carret get_carret_at (double click_x, double click_y, - bool check_boundaries = true) { - - int i = 0; - double tx, ty; - double p; - string w; - int ch_index; - double min_d = double.MAX; - Carret c = new Carret (); - double dt; - - c.paragraph = -1; - c.desired_x = click_x; - c.desired_y = click_y; - - foreach (Paragraph paragraph in paragraphs) { - if (!check_boundaries || paragraph.text_is_on_screen (allocation, widget_y)) { - ch_index = 0; - - if (paragraph.start_y + widget_y - font_size <= click_y <= paragraph.end_y + widget_y + font_size) { - foreach (Text next_word in paragraph.words) { - double tt_click = click_y - widget_y - padding + font_size; - - w = next_word.text; - - if (next_word.widget_y <= tt_click <= next_word.widget_y + font_size) { - - p = next_word.get_sidebearing_extent (); - - if ((next_word.widget_y <= tt_click <= next_word.widget_y + font_size) - && (next_word.widget_x + widget_x <= click_x <= next_word.widget_x + widget_x + padding + next_word.get_sidebearing_extent ())) { - - tx = widget_x + next_word.widget_x + padding; - ty = widget_y + next_word.widget_y + padding; - - next_word.iterate ((glyph, kerning, last) => { - double cw; - int ci; - double d; - string gc = (!) glyph.get_unichar ().to_string (); - - d = Math.fabs (click_x - tx); - - if (d <= min_d) { - min_d = d; - c.character_index = ch_index; - c.paragraph = i; - } - - cw = (glyph.get_width ()) * next_word.get_font_scale () + kerning; - ci = gc.length; - - tx += cw; - ch_index += ci; - }); - - dt = Math.fabs (click_x - (tx + widget_x + padding)); - if (dt < min_d) { - min_d = dt; - c.character_index = ch_index; - c.paragraph = i; - } - } else { - dt = Math.fabs (click_x - (next_word.widget_x + widget_x + padding + next_word.get_sidebearing_extent ())); - - if (dt < min_d) { - min_d = dt; - c.character_index = ch_index + w.length; - - if (w.has_suffix ("\n")) { - c.character_index -= "\n".length; - } - - c.paragraph = i; - } - - ch_index += w.length; - } - } else { - ch_index += w.length; - } - } - } - } - i++; - } - - if (unlikely (c.paragraph < 0)) { - c.paragraph = paragraphs.size > 0 ? paragraphs.size - 1 : 0; - c.character_index = paragraphs.size > 0 ? paragraphs.get (c.paragraph).text.length : 0; - } - - store_undo_state_at_next_event = true; - - return c; - } - - /** @return offset to click in text. */ - public override void layout () { - double p; - double tx, ty; - string w; - double xmax = 0; - int i = 0; - double dd; - - tx = 0; - ty = font_size; - - if (allocation.width <= 0 || allocation.height <= 0) { - warning ("Parent widget allocation is not set."); - } - - for (i = paragraphs.size - 1; i >= 0 && paragraphs.size > 1; i--) { - if (unlikely (paragraphs.get (i).is_empty ())) { - warning ("Empty paragraph."); - paragraphs.remove_at (i); - update_paragraph_index (); - } - } - - i = 0; - foreach (Paragraph paragraph in paragraphs) { - if (paragraph.need_layout - || (paragraph.text_area_width != width - && paragraph.text_is_on_screen (allocation, widget_y))) { - - paragraph.start_y = ty; - paragraph.start_x = tx; - - paragraph.cached_surface = null; - - foreach (Text next_word in paragraph.words) { - next_word.set_font_size (font_size); - - w = next_word.text; - p = next_word.get_sidebearing_extent (); - - if (unlikely (p == 0)) { - warning (@"Zero width word: $(w)"); - } - - if (w == "") { - break; - } - - if (w == "\n") { - next_word.widget_x = tx; - next_word.widget_y = ty; - - tx = 0; - ty += next_word.font_size; - } else { - if (!single_line) { - if (tx + p + 2 * padding > width || w == "\n") { - tx = 0; - ty += next_word.font_size; - } - } - - if (tx + p > xmax) { - xmax = tx + p; - } - - next_word.widget_x = tx; - next_word.widget_y = ty; - - if (w != "\n") { - tx += p; - } - } - } - - if (tx > xmax) { - xmax = tx; - } - - paragraph.text_area_width = width; - paragraph.width = xmax; - paragraph.end_x = tx; - paragraph.end_y = ty; - paragraph.need_layout = false; - } - - if (xmax > width) { - break; - } - - tx = paragraph.end_x; - ty = paragraph.end_y; - i++; - } - - if (xmax > width) { - this.width = xmax + 2 * padding; - layout (); - return; - } - - this.height = fmax (min_height, ty + 2 * padding); - - 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 (); - layout (); - return; - } - - ty = font_size; - tx = 0; - - foreach (Paragraph paragraph in paragraphs) { - dd = ty - paragraph.start_y; - - if (dd != 0) { - paragraph.start_y += dd; - paragraph.end_y += dd; - foreach (Text word in paragraph.words) { - word.widget_y += dd; - } - } - - ty = paragraph.end_y; - } - } - - public override void button_press (uint button, double x, double y) { - if (is_over (x, y)) { - carret = get_carret_at (x, y); - selection_end = carret.copy (); - update_selection = true; - } - } - - public override void button_release (uint button, double x, double y) { - update_selection = false; - show_selection = selection_is_visible (); - } - - public override bool motion (double x, double y) { - if (update_selection) { - selection_end = get_carret_at (x, y); - show_selection = selection_is_visible (); - } - - return update_selection; - } - - public override void draw (Context cr) { - Text word; - double tx, ty; - string w; - double scale; - double width; - double x = widget_x; - double y = widget_y; - Carret selection_start, selection_stop; - double carret_x; - double carret_y; - - layout (); - - if (draw_border) { - // background - cr.save (); - cr.set_line_width (1); - Theme.color (cr, "Text Area Background"); - draw_rounded_rectangle (cr, x, y, this.width, this.height - padding, padding); - cr.fill (); - cr.restore (); - - // border - cr.save (); - cr.set_line_width (1); - Theme.color (cr, "Foreground 1"); - draw_rounded_rectangle (cr, x, y, this.width, this.height - padding, padding); - cr.stroke (); - cr.restore (); - } - - cr.save (); - - word = new Text (); - - width = this.width - padding; - x += padding; - scale = word.get_font_scale (); - y += font_size; - - // draw selection background - if (has_selection ()) { - tx = 0; - ty = 0; - - selection_start = get_selection_start (); - selection_stop = get_selection_stop (); - - cr.save (); - Theme.color (cr, "Highlighted 1"); - - for (int i = selection_start.paragraph; i <= selection_stop.paragraph; i++) { - return_if_fail (0 <= i < paragraphs.size); - Paragraph pg = paragraphs.get (i); - - if (pg.text_is_on_screen (allocation, widget_y)) { - int char_index = 0; - - foreach (Text next_word in pg.words) { - double cw = next_word.get_sidebearing_extent (); - bool paint_background = false; - bool partial_start = false; - bool partial_stop = false; - int wl; - - w = next_word.text; - wl = w.length; - scale = next_word.get_font_scale (); - - if (selection_start.paragraph == selection_stop.paragraph) { - partial_start = true; - partial_stop = true; - } else if (selection_start.paragraph < i < selection_stop.paragraph) { - paint_background = true; - } else if (selection_start.paragraph == i) { - paint_background = true; - partial_start = true; - } else if (selection_stop.paragraph == i) { - paint_background = char_index + wl < selection_stop.character_index; - partial_stop = !paint_background; - } - - if (paint_background && !(partial_start || partial_stop)) { - double selection_y = widget_y + next_word.widget_y + scale * -next_word.cached_font.bottom_limit - font_size; - cr.rectangle (widget_x + padding + next_word.widget_x - 1, selection_y, cw + 1, font_size); - cr.fill (); - } - - if (partial_start || partial_stop) { - int index = char_index; - double bx = widget_x + padding + next_word.widget_x + (partial_start ? 0 : 1); - - next_word.iterate ((glyph, kerning, last) => { - double cwi; - int ci; - bool draw = (index >= selection_start.character_index && partial_start && !partial_stop) - || (index < selection_stop.character_index && !partial_start && partial_stop) - || (selection_start.character_index <= index < selection_stop.character_index && partial_start && partial_stop); - - cwi = (glyph.get_width ()) * next_word.get_font_scale () + kerning; - - if (draw) { - double selection_y = widget_y + next_word.widget_y + scale * -next_word.cached_font.bottom_limit - font_size; - cr.rectangle (bx - 1, selection_y, cwi + 1, font_size); - cr.fill (); - } - - bx += cwi; - ci = ((!) glyph.get_unichar ().to_string ()).length; - index += ci; - }); - } - - char_index += w.length; - } - } - } - - 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) { - if (carret_is_visible) { - draw_carret_at (cr, widget_x + padding, widget_y + font_size + padding); - } - - return; - } - - Context cc; // cached context - Paragraph paragraph; - paragraph = paragraphs.get (0); - - tx = paragraph.start_x; - ty = paragraph.start_y; - - 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 = Screen.create_background_surface ((int) width + 2, paragraph.get_height () + (int) font_size + 2); - cc = new Context ((!) paragraph.cached_surface); - cc.scale (Screen.get_scale(), Screen.get_scale()); - - foreach (Text next_word in paragraph.words) { - if (next_word.text != "\n") { - next_word.draw_at_top (cc, next_word.widget_x, next_word.widget_y - ty); - } - } - } - - if (likely (paragraph.cached_surface != null)) { - // FIXME: subpixel offset in text area - Screen.paint_background_surface(cr, - (!) paragraph.cached_surface, - (int) (x + tx), - (int) (widget_y + paragraph.start_y - font_size + padding)); - } else { - warning ("No paragraph image."); - } - } - - if (carret_is_visible) { - get_carret_position (carret, out carret_x, out carret_y); - - if (carret_y < 0) { - draw_carret_at (cr, widget_x + padding, widget_y + font_size + padding); - } else { - draw_carret_at (cr, carret_x, carret_y); - } - } - - if (has_selection ()) { - get_carret_position (selection_end, out carret_x, out carret_y); - - if (carret_y < 0) { - draw_carret_at (cr, widget_x + padding, widget_y + font_size + padding); - } else { - draw_carret_at (cr, carret_x, carret_y); - } - } - } - - void get_carret_position (Carret carret, out double carret_x, out double carret_y) { - Paragraph paragraph; - double tx; - double ty; - int ch_index; - int wl; - double pos_x, pos_y; - - ch_index = 0; - - carret_x = -1; - carret_y = -1; - - return_if_fail (0 <= carret.paragraph < paragraphs.size); - paragraph = paragraphs.get (carret.paragraph); - - pos_x = -1; - pos_y = -1; - - foreach (Text next_word in paragraph.words) { - string w = next_word.text; - wl = w.length; - - if (carret.character_index == ch_index) { - pos_x = next_word.widget_x + widget_x + padding; - pos_y = widget_y + next_word.widget_y + next_word.get_baseline_to_bottom_for_font (); - } else if (carret.character_index >= ch_index + wl) { - pos_x = next_word.widget_x + next_word.get_sidebearing_extent () + widget_x + padding; - pos_y = widget_y + next_word.widget_y + next_word.get_baseline_to_bottom_for_font (); - - if (next_word.text.has_suffix ("\n")) { - pos_x = widget_x + padding; - pos_y += next_word.font_size; - } - } else if (ch_index < carret.character_index <= ch_index + wl) { - tx = widget_x + next_word.widget_x; - ty = widget_y + next_word.widget_y + next_word.get_baseline_to_bottom_for_font (); - - if (carret.character_index <= ch_index) { - pos_x = widget_x + padding; - pos_y = ty; - } - - next_word.iterate ((glyph, kerning, last) => { - double cw; - int ci; - - cw = (glyph.get_width ()) * next_word.get_font_scale () + kerning; - ci = ((!) glyph.get_unichar ().to_string ()).length; - - if (ch_index < carret.character_index <= ch_index + ci) { - pos_x = tx + cw + padding; - pos_y = ty; - - if (glyph.get_unichar () == '\n') { - pos_x = widget_x + padding; - pos_y += next_word.font_size; - } - } - - tx += cw; - ch_index += ci; - }); - } - - ch_index += wl; - } - - carret_x = pos_x; - carret_y = pos_y; - } - - void draw_carret_at (Context cr, double x, double y) { - cr.save (); - cr.set_source_rgba (0, 0, 0, 0.5); - cr.set_line_width (1); - cr.move_to (x, y); - cr.line_to (x, y - font_size); - cr.stroke (); - cr.restore (); - } - - public void store_undo_edit_state () { - TextUndoItem ui = new TextUndoItem (carret); - ui.edited.add (get_current_paragraph ().copy ()); - undo_items.add (ui); - redo_items.clear (); - } - - public void redo () { - TextUndoItem i; - TextUndoItem undo_item; - - if (redo_items.size > 0) { - i = redo_items.get (redo_items.size - 1); - - undo_item = new TextUndoItem (i.carret); - - i.deleted.sort ((a, b) => { - Paragraph pa = (Paragraph) a; - Paragraph pb = (Paragraph) b; - return pb.index - pa.index; - }); - - i.added.sort ((a, b) => { - Paragraph pa = (Paragraph) a; - Paragraph pb = (Paragraph) b; - return pa.index - pb.index; - }); - - foreach (Paragraph p in i.deleted) { - if (unlikely (!(0 <= p.index < paragraphs.size))) { - warning ("Paragraph not found."); - } else { - undo_item.deleted.add (p.copy ()); - paragraphs.remove_at (p.index); - } - } - - foreach (Paragraph p in i.added) { - if (p.index == paragraphs.size) { - paragraphs.add (p.copy ()); - } else { - if (unlikely (!(0 <= p.index < paragraphs.size))) { - warning (@"Index: $(p.index) out of bounds, size: $(paragraphs.size)"); - } else { - undo_item.added.add (paragraphs.get (p.index).copy ()); - paragraphs.insert (p.index, p.copy ()); - } - } - } - - foreach (Paragraph p in i.edited) { - if (unlikely (!(0 <= p.index < paragraphs.size))) { - warning (@"Index: $(p.index ) out of bounds, size: $(paragraphs.size)"); - return; - } - - undo_item.edited.add (paragraphs.get (p.index).copy ()); - paragraphs.set (p.index, p.copy ()); - } - - redo_items.remove_at (redo_items.size - 1); - undo_items.add (undo_item); - - carret = i.carret.copy (); - layout (); - } - } - - public void undo () { - TextUndoItem i; - TextUndoItem redo_item; - - if (undo_items.size > 0) { - i = undo_items.get (undo_items.size - 1); - redo_item = new TextUndoItem (i.carret); - - i.deleted.sort ((a, b) => { - Paragraph pa = (Paragraph) a; - Paragraph pb = (Paragraph) b; - return pa.index - pb.index; - }); - - i.added.sort ((a, b) => { - Paragraph pa = (Paragraph) a; - Paragraph pb = (Paragraph) b; - return pb.index - pa.index; - }); - - foreach (Paragraph p in i.added) { - if (unlikely (!(0 <= p.index < paragraphs.size))) { - warning ("Paragraph not found."); - } else { - redo_item.added.add (paragraphs.get (p.index).copy ()); - paragraphs.remove_at (p.index); - } - } - - foreach (Paragraph p in i.deleted) { - if (p.index == paragraphs.size) { - paragraphs.add (p.copy ()); - } else { - if (unlikely (!(0 <= p.index < paragraphs.size))) { - warning (@"Index: $(p.index) out of bounds, size: $(paragraphs.size)"); - } else { - redo_item.deleted.add (p.copy ()); - paragraphs.insert (p.index, p.copy ()); - } - } - } - - foreach (Paragraph p in i.edited) { - if (unlikely (!(0 <= p.index < paragraphs.size))) { - warning (@"Index: $(p.index ) out of bounds, size: $(paragraphs.size)"); - return; - } - - redo_item.edited.add (paragraphs.get (p.index).copy ()); - paragraphs.set (p.index, p.copy ()); - } - - undo_items.remove_at (undo_items.size - 1); - redo_items.add (redo_item); - - carret = i.carret.copy (); - layout (); - } - } - - public void set_editable (bool editable) { - this.editable = editable; - } - - public class TextUndoItem : GLib.Object { - public Carret carret; - public Gee.ArrayList<Paragraph> added = new Gee.ArrayList<Paragraph> (); - public Gee.ArrayList<Paragraph> edited = new Gee.ArrayList<Paragraph> (); - public Gee.ArrayList<Paragraph> deleted = new Gee.ArrayList<Paragraph> (); - - public TextUndoItem (Carret c) { - carret = c.copy (); - } - } - - public class Paragraph : GLib.Object { - 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 double text_area_width = -10000; - - public string text; - - public Gee.ArrayList<Text> words { - get { - if (words_in_paragraph.size == 0) { - generate_words (); - } - - return words_in_paragraph; - } - } - - private Gee.ArrayList<Text> words_in_paragraph = new Gee.ArrayList<Text> (); - public int text_length; - public bool need_layout = true; - public Surface? cached_surface = null; - double font_size; - public int index; - Color text_color; - - public Paragraph (string text, double font_size, int index, Color c) { - this.index = index; - this.font_size = font_size; - text_color = c; - set_text (text); - } - - public Paragraph copy () { - Paragraph p = new Paragraph (text.dup (), font_size, index, text_color); - p.need_layout = true; - return p; - } - - public bool is_empty () { - return text == ""; - } - - public void set_text (string t) { - this.text = t; - text_length = t.length; - need_layout = true; - words.clear (); - cached_surface = null; - } - - 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; - } - - private void generate_words () { - string w; - int p = 0; - bool carret_at_word_end = false; - Text word; - int carret = 0; - int iter_pos = 0; - - return_if_fail (words_in_paragraph.size == 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); - - word.r = text_color.r; - word.g = text_color.g; - word.b = text_color.b; - word.a = text_color.a; - - words_in_paragraph.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; - } - } - - public class Carret : GLib.Object { - - public int paragraph = 0; - - public int character_index { - get { - return ci; - } - - set { - ci = value; - } - } - - private int ci = 0; - - public double desired_x = 0; - public double desired_y = 0; - - public Carret () { - } - - public void print () { - stdout.printf (@"paragraph: $paragraph, character_index: $character_index\n"); - } - - public Carret copy () { - Carret c = new Carret (); - - c.paragraph = paragraph; - c.character_index = character_index; - - c.desired_x = desired_x; - c.desired_y = desired_y; - - return c; - } - } - } - - }
diff --git libbirdfont/Renderer/fontconfig.c(deleted)
--- a/libbirdfont/Renderer/fontconfig.c +++ /dev/null @@ -1,141 +1,1 @@ - /* - Copyright (C) 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 - 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. - */ - - #include <stdio.h> - #include <glib.h> - #include <fontconfig/fontconfig.h> - - gchar* find_font_with_property (FcConfig* fontconfig, const gchar* characters, const gchar* property) { - FcPattern* pattern; - FcCharSet* character_set; - FcObjectSet* font_properties; - FcFontSet* fonts; - FcPattern* font; - FcChar8* path; - gchar* result; - gchar* remaining_characters; - gunichar character; - - if (fontconfig == NULL) { - g_warning("Font config not loaded."); - return NULL; - } - - result = NULL; - pattern = FcPatternCreate (); - - character_set = FcCharSetCreate (); - - remaining_characters = (gchar*) characters; - while (TRUE) { - character = g_utf8_get_char (remaining_characters); - - if (character == '\0') { - break; - } - - FcCharSetAddChar(character_set, character); - - remaining_characters = g_utf8_next_char (remaining_characters); - } - - FcPatternAddCharSet (pattern, FC_CHARSET, character_set); - FcCharSetDestroy (character_set); - FcPatternAddInteger (pattern, FC_SLANT, FC_SLANT_ROMAN); - - FcPatternAddBool(pattern, FC_SCALABLE, FcTrue); - font_properties = FcObjectSetBuild (property, NULL); - fonts = FcFontList (fontconfig, pattern, font_properties); - - if (fonts && fonts->nfont > 0) { - font = fonts->fonts[0]; - if (FcPatternGetString(font, property, 0, &path) == FcResultMatch) { - result = g_strdup ((gchar*) path); - } - } - - if (fonts) { - FcFontSetDestroy(fonts); - } - - if (pattern) { - FcPatternDestroy(pattern); - } - - return result; - } - - /** Find a fallback font for a set of characters. - * @return A path to the font file. - */ - gchar* find_font (FcConfig* fontconfig, const gchar* characters) { - return find_font_with_property (fontconfig, characters, FC_FILE); - } - - /** Find a fallback font for a set of characters. - * @return Family name of the font. - */ - gchar* find_font_family (FcConfig* fontconfig, const gchar* characters) { - return find_font_with_property (fontconfig, characters, FC_FAMILY); - } - - /** Find a font file from its family name. - * @param font_config fontconfig instance - * @param font_name name of the font - * @return full path to the font file - */ - gchar* find_font_file (FcConfig* font_config, const gchar* font_name) { - const FcChar8* name; - FcPattern* search_pattern; - FcPattern* font; - FcChar8* file; - gchar* path; - FcObjectSet* font_properties; - FcFontSet* fonts; - int i; - - if (font_config == NULL) { - g_warning("Font config not loaded."); - return NULL; - } - - path = NULL; - name = font_name; - - search_pattern = FcPatternCreate (); - FcPatternAddString (search_pattern, FC_FAMILY, name); - FcPatternAddBool (search_pattern, FC_SCALABLE, FcTrue); - FcPatternAddInteger (search_pattern, FC_WEIGHT, FC_WEIGHT_MEDIUM); - FcPatternAddInteger (search_pattern, FC_SLANT, FC_SLANT_ROMAN); - - font_properties = FcObjectSetBuild (FC_FILE, NULL); - fonts = FcFontList (font_config, search_pattern, font_properties); - - if (fonts->nfont > 0) { - for (i = 0; i < fonts->nfont; i++) { - font = fonts->fonts[i]; - - if (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch) { - path = g_strdup ((gchar*) file); - break; - } - } - FcPatternDestroy (font); - } - - FcPatternDestroy (search_pattern); - - return path; - }
--- /dev/null +++ b/libbirdfont/TextRendering/CachedFont.vala @@ -1,1 +1,100 @@ + /* + Copyright (C) 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 + 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 Gee; + + namespace BirdFont { + + public class CachedFont : GLib.Object { + public Font? font; + + public double top_limit { + get { return _top_limit; } + set { _top_limit = value; } + } + + public double bottom_limit { + get { return _bottom_limit; } + set { _bottom_limit = value; } + } + + public double base_line = 0; + double _top_limit = 92.77; // FIXME: load before first glyph + double _bottom_limit = -24.4; + + static FallbackFont fallback_font { + get { + if (_fallback_font == null) { + _fallback_font = new FallbackFont (); + } + + return (!) _fallback_font; + } + } + static FallbackFont? _fallback_font = null; + + public CachedFont (Font? font) { + Glyph? g; + Glyph glyph; + + this.font = font; + + g = get_glyph_by_name ("a"); + if (g != null) { + glyph = (!) g; + base_line = glyph.baseline; + top_limit = glyph.top_limit; + bottom_limit = glyph.bottom_limit; + } else { + warning("No default chararacter found in font."); + } + } + + public Glyph? get_glyph_by_name (string name) { + Glyph? g = null; + Font f; + Glyph glyph; + + if (font != null) { + f = (!) font; + g = f.get_glyph_by_name (name); + + if (g != null) { + glyph = (!) g; + glyph.top_limit = f.top_limit; + glyph.baseline = f.base_line; + glyph.bottom_limit = f.bottom_limit; + } + } + + if (g == null && name.char_count () == 1) { + f = fallback_font.get_single_glyph_font (name.get_char (0)); + g = f.get_glyph_by_name (name); + + if (g == null) { + return null; + } + + glyph = (!) g; + glyph.top_limit = f.top_limit; + glyph.baseline = f.base_line; + glyph.bottom_limit = f.bottom_limit; + } + + return g; + } + } + + }
--- /dev/null +++ b/libbirdfont/TextRendering/Drawing.vala @@ -1,1 +1,51 @@ + /* + 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 { + + /** Interface for creating drawing callbacks. */ + [Compact] + [CCode (ref_function = "bird_font_drawing_ref", unref_function = "bird_font_drawing_unref")] + public class Drawing { + + public int iterator_refcount = 1; + + public Drawing () { + } + + public void new_path (double x, double y) { + } + + public void curve_to (double xb, double yb, double xc, double yc, double xd, double yd) { + } + + public void close_path (double x, double y) { + } + + public unowned Drawing @ref () { + iterator_refcount++; + return this; + } + + public void unref () { + if (--iterator_refcount == 0) { + this.free (); + } + } + + private extern void free (); + } + + }
--- /dev/null +++ b/libbirdfont/TextRendering/FallbackFont.vala @@ -1,1 +1,338 @@ + /* + Copyright (C) 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 + 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 Gee; + + [SimpleType] + [CCode (has_type_id = false)] + public extern struct FcConfig { + } + + [CCode (cname = "FcInitLoadConfigAndFonts")] + public extern FcConfig* FcInitLoadConfigAndFonts (); + + [CCode (cname = "FcConfigAppFontAddDir")] + public extern string* FcConfigAppFontAddDir (FcConfig* config, string path); + + [CCode (cname = "FcConfigSetSysRoot")] + public extern void FcConfigSetSysRoot (FcConfig* config, string path); + + [CCode (cname = "FcConfigParseAndLoad")] + public extern bool FcConfigParseAndLoad (FcConfig* config, string path, bool complain); + + [CCode (cname = "FcConfigSetCurrent")] + public extern void FcConfigSetCurrent (FcConfig* config); + + [CCode (cname = "FcConfigCreate")] + public extern FcConfig* FcConfigCreate (); + + [CCode (cname = "FcConfigFilename")] + public extern string FcConfigFilename (string path); + + [CCode (cname = "find_font")] + public extern string? find_font (FcConfig* font_config, string characters); + + [CCode (cname = "find_font_family")] + public extern string? find_font_family (FcConfig* font_config, string characters); + + [CCode (cname = "find_font_file")] + public extern string? find_font_file (FcConfig* font_config, string font_name); + + namespace BirdFont { + + // TODO: use font config + public class FallbackFont : GLib.Object { + Gee.ArrayList<File> font_directories; + + FontFace* default_font = null; + public static FcConfig* font_config = null; + static bool font_config_started = false; + + string default_font_file_name = "Roboto-Regular.ttf"; + string default_font_family_name = "Roboto"; + + Gee.HashMap<unichar, CachePair> glyphs; + Gee.ArrayList<CachePair> cached; + + public int max_cached_fonts = 300; + + string? default_font_file = null; + + public FallbackFont () { + string home = Environment.get_home_dir (); + font_directories = new Gee.ArrayList<File> (); + + if (!font_config_started) { + font_config_started = true; + + IdleSource idle = new IdleSource (); + idle.set_callback (() => { + Task t = new Task (init_font_config); + MainWindow.native_window.run_non_blocking_background_thread (t); + return false; + }); + idle.attach (null); + } + + add_font_folder ("/usr/share/fonts/"); + add_font_folder ("/usr/local/share/fonts/"); + add_font_folder (home + "/.local/share/fonts"); + add_font_folder (home + "/.fonts"); + add_font_folder ("C:\\Windows\\Fonts"); + add_font_folder (home + "/Library/Fonts"); + add_font_folder ("/Library/Fonts"); + add_font_folder ("/Network/Library/Fonts"); + add_font_folder ("/System/Library/Fonts"); + add_font_folder ("/System Folder/Fonts"); + + glyphs = new Gee.HashMap<unichar, CachePair> (); + cached = new Gee.ArrayList<CachePair> (); + + open_default_font (); + } + + ~FallbackFont () { + if (default_font != null) { + close_font (default_font); + } + } + + public void init_font_config () { + FcConfig* config; + + #if MAC + config = FcConfigCreate(); + + string bundle = (!) BirdFont.get_settings_directory ().get_path (); + FcConfigSetSysRoot(config, bundle); + + string path = FcConfigFilename((!) SearchPaths.search_file(null, "fontconfig.settings").get_path ()); + bool loaded = FcConfigParseAndLoad(config, path, true); + + if (!loaded) { + warning ("Fontconfig initialization failed."); + } + + FcConfigSetCurrent (config); + #else + config = FcInitLoadConfigAndFonts (); + #endif + + IdleSource idle = new IdleSource (); + + idle.set_callback (() => { + font_config = config; + printd("Fontconfig loaded\n"); + return false; + }); + idle.attach (null); + } + + public Font get_single_glyph_font (unichar c) { + Font f; + unichar last; + CachePair p; + + if (likely (glyphs.has_key (c))) { + p = glyphs.get (c); + + if (p.referenced < int.MAX) { + p.referenced++; + } + + return p.font; + } + + // remove glyphs from cache if it is full + if (cached.size > max_cached_fonts - 100) { + + cached.sort ((a, b) => { + CachePair pa = (CachePair) a; + CachePair pb = (CachePair) b; + return pb.referenced - pa.referenced; + }); + + int j = 0; + for (int i = cached.size - 1; i > 0; i--) { + if (j > 100) { + break; + } + + j++; + + last = cached.get (i).character; + glyphs.unset (last); + cached.remove_at (i); + } + } + + f = get_single_fallback_glyph_font (c); + p = new CachePair (f, c); + + glyphs.set (c, p); + cached.add (p); + + return (Font) f; + } + + Font get_single_fallback_glyph_font (unichar c) { + string? font_file; + BirdFontFile bf_parser; + Font bf_font; + StringBuilder? glyph_data; + FontFace* font; + + bf_font = new Font (); + font_file = null; + glyph_data = null; + + // don't use fallback font in private use area + if (0xe000 <= c <= 0xf8ff) { + return bf_font; + } + + // control characters + if (c <= 0x001f || (0x007f <= c <= 0x008d)) { + return bf_font; + } + + // check if glyph is available in roboto + if (default_font != null) { + glyph_data = get_glyph_in_font ((!) default_font, c); + } + + // use fontconfig to find a fallback font + if (glyph_data == null) { + font_file = find_font (font_config, (!) c.to_string ()); + if (font_file != null) { + font = open_font ((!) font_file); + glyph_data = get_glyph_in_font (font, c); + close_font (font); + } + } + + if (glyph_data != null) { + bf_parser = new BirdFontFile (bf_font); + bf_parser.load_data (((!) glyph_data).str); + } + + return bf_font; + } + + public StringBuilder? get_glyph_in_font (FontFace* font, unichar c) { + StringBuilder? glyph_data = null; + GlyphCollection gc; + + gc = new GlyphCollection (c, (!)c.to_string ()); + glyph_data = load_glyph (font, (uint) c); + + return glyph_data; + } + + void add_font_folder (string f) { + File folder = File.new_for_path (f); + FileInfo? file_info; + string fn; + string file_attributes; + try { + if (folder.query_exists ()) { + font_directories.add (folder); + + file_attributes = FileAttribute.STANDARD_NAME; + file_attributes += ","; + file_attributes += FileAttribute.STANDARD_TYPE; + var enumerator = folder.enumerate_children (file_attributes, 0); + + while ((file_info = enumerator.next_file ()) != null) { + fn = ((!) file_info).get_name (); + + if (((!)file_info).get_file_type () == FileType.DIRECTORY) { + add_font_folder ((!) get_child (folder, fn).get_path ()); + } + } + } + } catch (GLib.Error e) { + warning (e.message); + } + } + + File search_font_file (string font_file) { + File d, f; + + for (int i = font_directories.size - 1; i >= 0; i--) { + d = font_directories.get (i); + f = get_child (d, font_file); + + if (f.query_exists ()) { + return f; + } + } + + warning (@"The font $font_file not found"); + return File.new_for_path (font_file); + } + + public string? get_default_font_file () { + File font_file; + string? fn = null; + + if (likely (default_font_file != null)) { + return default_font_file; + } + + font_file = SearchPaths.search_file (null, default_font_file_name); + + if (font_file.query_exists ()) { + fn = (!) font_file.get_path (); + } else { + font_file = search_font_file (default_font_file_name); + + if (font_file.query_exists ()) { + fn = (!) font_file.get_path (); + } else { + fn = find_font_file (font_config, default_font_family_name); + } + } + + if (likely (fn != null)) { + default_font_file = fn; + return fn; + } + + warning(default_font_family_name + " not found"); + return null; + } + + void open_default_font () { + string? fn = get_default_font_file (); + + if (fn != null) { + default_font = open_font ((!) fn); + } + } + + class CachePair : GLib.Object { + public Font font; + public unichar character; + public int referenced = 1; + + public CachePair (Font f, unichar c) { + font = f; + character = c; + } + } + } + + }
--- /dev/null +++ b/libbirdfont/TextRendering/FontCache.vala @@ -1,1 +1,84 @@ + /* + 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 Gee; + + namespace BirdFont { + + /** Thread specific font cache. */ + public class FontCache { + public static FallbackFont fallback_font; + + static FontCache? default_cache = null; + Gee.HashMap<string, CachedFont> fonts; + CachedFont fallback; + + public FontCache () { + if (is_null (fallback_font)) { + fallback_font = new FallbackFont (); + } + + fallback = new CachedFont (null); + fonts = new Gee.HashMap<string, CachedFont> (); + } + + public CachedFont get_font (string file_name) { + CachedFont c; + Font f; + bool ok; + + if (file_name == "") { + return fallback; + } + + if (fonts.has_key (file_name)) { + c = fonts.get (file_name); + return c; + } + + f = new Font (); + f.set_file (file_name); + ok = f.load (); + if (!ok) { + stderr.printf ("Can't load %s\n", file_name); + return new CachedFont (null); + } + + c = new CachedFont (f); + + if (file_name == "") { + warning ("No file."); + return c; + } + + fonts.set (file_name, c); + return c; + } + + public static FontCache get_default_cache () { + if (default_cache == null) { + default_cache = new FontCache (); + } + + return (!) default_cache; + } + + public CachedFont get_fallback () { + return fallback; + } + + } + + }
--- /dev/null +++ b/libbirdfont/TextRendering/LineTextArea.vala @@ -1,1 +1,34 @@ + /* + 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 Math; + + namespace BirdFont { + + public class LineTextArea : TextArea { + + public LineTextArea (double size) { + base (size); + + single_line = true; + min_height = size; + height = min_height; + + layout (); + } + } + + }
--- /dev/null +++ b/libbirdfont/TextRendering/Text.vala @@ -1,1 +1,585 @@ + /* + Copyright (C) 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 + 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 Math; + + namespace BirdFont { + + /** Test implementation of a birdfont rendering engine. */ + public class Text : Widget { + FontCache font_cache; + public CachedFont cached_font; + + Surface? cache = null; + + public string text; + + private bool use_cache = true; + + GlyphSequence glyph_sequence { + get { + if (gs == null) { + gs = generate_glyphs (); + } + + return (!) gs; + } + } + + Gee.ArrayList<string> glyph_names; + GlyphSequence? gs = null; + + public delegate void Iterator (Glyph glyph, double kerning, bool last); + public double font_size; + double sidebearing_extent = 0; + + public double r = 0; + public double g = 0; + public double b = 0; + public double a = 1; + double truncated_width = -1; + + double margin_left = 0; + + public Text (string text = "", double size = 17, double margin_bottom = 0) { + this.margin_bottom = margin_bottom; + font_cache = FontCache.get_default_cache (); + cached_font = font_cache.get_fallback (); + + set_text (text); + set_font_size (size); + } + + public void set_use_cache (bool cache) { + use_cache = cache; + } + + public string get_text () { + return text; + } + + /** Set font for this text area. + * @param font_absolute path to the font file or a file name for one of the font files in search paths. + * @return true if the font was found + */ + public bool load_font (string font_file) { + File path; + File f; + FontCache fc; + + f = File.new_for_path (font_file); + path = (f.query_exists ()) ? f : SearchPaths.find_file (null, font_file); + + fc = FontCache.get_default_cache (); + cached_font = fc.get_font ((!) path.get_path ()); + gs = generate_glyphs (); + + return cached_font.font != null; + } + + public void set_font_size (double height_in_pixels) { + font_size = height_in_pixels; + sidebearing_extent = 0; + + if (gs == null) { // ensure height is loaded for the font + gs = generate_glyphs (); + } + } + + public void set_font_cache (FontCache font_cache) { + this.font_cache = font_cache; + } + + public void set_text (string text) { + this.text = text; + gs = null; + sidebearing_extent = 0; + cache = null; + } + + private GlyphSequence generate_glyphs () { + int index; + unichar c; + string name; + Glyph? g; + GlyphSequence gs; + + gs = new GlyphSequence (); + + glyph_names = new Gee.ArrayList<string> (); + index = 0; + while (text.get_next_char (ref index, out c)) { + name = (!) c.to_string (); + g = cached_font.get_glyph_by_name (name); + + gs.glyph.add (g); + glyph_names.add (name); + } + + return gs; + } + + public void iterate (Iterator iter) { + Glyph glyph; + double w, kern; + int wi; + Glyph? prev; + Glyph? g; + GlyphSequence word_with_ligatures; + GlyphRange? gr_left, gr_right; + GlyphSequence word; + KerningClasses kc; + Font empty = Font.empty; + + glyph = new Glyph.no_lines ("", '\0'); + + w = 0; + prev = null; + kern = 0; + + word = glyph_sequence; + wi = 0; + + if (cached_font.font != null) { + word_with_ligatures = word.process_ligatures ((!) cached_font.font); + } else { + word_with_ligatures = word.process_ligatures (new Font ()); + } + + gr_left = null; + gr_right = null; + + if (cached_font.font != null) { + kc = ((!) cached_font.font).get_kerning_classes (); + } else { + kc = new KerningClasses (empty); + } + + if (word_with_ligatures.glyph.size > 0) { + g = word_with_ligatures.glyph.get (0); + if (g != null) { + margin_left = ((!) g).get_left_side_bearing (); + + if (margin_left < 0) { + margin_left = -margin_left; + } else { + margin_left = 0; + } + } + } + + for (int i = 0; i < word_with_ligatures.glyph.size; i++) { + g = word_with_ligatures.glyph.get (i); + + if (g == null || prev == null || wi == 0) { + kern = 0; + } else { + return_if_fail (wi < word_with_ligatures.ranges.size); + return_if_fail (wi - 1 >= 0); + + gr_left = word_with_ligatures.ranges.get (wi - 1); + gr_right = word_with_ligatures.ranges.get (wi); + + kern = kc.get_kerning_for_pair (((!) prev).get_name (), ((!) g).get_name (), gr_left, gr_right); + } + + // process glyph + if (g == null && (0 <= i < glyph_names.size)) { + g = cached_font.get_glyph_by_name (glyph_names.get (i)); + } + + glyph = (g == null) ? new Glyph ("") : (!) g; + iter (glyph, kern, i + 1 == word_with_ligatures.glyph.size); + prev = g; + wi++; + } + } + + // FIXME: some fonts doesn't have on curve extrema + public double get_extent () { + double x = 0; + + iterate ((glyph, kerning, last) => { + double x1, y1, x2, y2; + double lsb; + + lsb = glyph.left_limit; + + if (!last) { + x += (glyph.get_width () + kerning) * get_scale (glyph); + } else { + glyph.boundaries (out x1, out y1, out x2, out y2); + x += (x2 - lsb) * get_scale (glyph); + } + }); + + return x; + } + + public double get_sidebearing_extent () { + double x ; + + if (likely (sidebearing_extent > 0)) { + return sidebearing_extent; + } + + x = 0; + + iterate ((glyph, kerning, last) => { + double lsb; + lsb = glyph.left_limit; + x += (glyph.get_width () + kerning) * get_scale (glyph); + }); + + sidebearing_extent = x; + return x; + } + + public override double get_height () { + return font_size; + } + + public double get_acender () { + double max_height = 0; + + iterate ((glyph, kerning, last) => { + double x1, y1, x2, y2; + double h; + glyph.boundaries (out x1, out y1, out x2, out y2); + h = Math.fmax (y1, y2) - Math.fmin (y1, y2); + h *= get_scale (glyph) - glyph.baseline * get_scale (glyph); + if (h > max_height) { + max_height = h; + } + }); + + return max_height; + } + + public override double get_width () { + double x = 0; + bool first = true; + + iterate ((glyph, kerning, last) => { + double x1, y1, x2, y2; + double lsb; + + lsb = glyph.left_limit; + + if (first) { + glyph.boundaries (out x1, out y1, out x2, out y2); + x += (glyph.get_width () + kerning - Math.fmin (x1, x2)) * get_scale (glyph); + first = false; + } else if (!last) { + x += (glyph.get_width () + kerning) * get_scale (glyph); + } else { + glyph.boundaries (out x1, out y1, out x2, out y2); + x += (x2 - lsb) * get_scale (glyph); + } + }); + + return x; + } + + public double get_decender () { + double decender_max = get_max_decender (); + return decender_max > 0 ? decender_max : 0; + } + + private double get_max_decender () { + double decender = 0; + double decender_max = 0; + + iterate ((glyph, kerning, last) => { + double x1, y1, x2, y2; + double y; + glyph.boundaries (out x1, out y1, out x2, out y2); + y = Math.fmin (y1, y2); + decender = (glyph.baseline - y) * get_scale (glyph); + if (decender > decender_max) { + decender_max = decender; + } + }); + + return decender_max; + } + + public override void draw (Context cr) { + double descender = cached_font.bottom_limit + cached_font.base_line; + double y = widget_y + get_height () + get_font_scale () * descender; // FIXME: + draw_at_baseline (cr, widget_x, y); + } + + public void draw_at_top (Context cr, double px, double py, string cacheid = "") { + double s = get_font_scale (); + double y = py + s * (cached_font.top_limit - cached_font.base_line); + draw_at_baseline (cr, px, y, cacheid); + } + + public void set_source_rgba (double r, double g, double b, double a) { + if (this.r != r || + this.g != g || + this.b != b || + this.a != a) { + + this.r = r; + this.g = g; + this.b = b; + this.a = a; + cache = null; + } + } + + public string get_cache_id (int offset_x, int offset_y) { + string key; + int64 c; + + c = (((int64) (r * 255)) << 24) + | (((int64) (g * 255)) << 16) + | (((int64) (b * 255)) << 8) + | (((int64) (a * 255)) << 0); + + // FIXME: use binary key + key = @"$font_size $c $offset_x $offset_y"; + + return key; + } + + public void draw_at_baseline (Context cr, double px, double py, string cacheid = "") { + if (cache == null) { + cache = draw_on_cache_surface (cacheid); + } + + double screen_scale = Screen.get_scale (); + double font_scale = get_font_scale (); + double cache_y = py - font_scale * (cached_font.top_limit - cached_font.base_line); + + cr.save(); + cr.scale (1 / screen_scale, 1 / screen_scale); + double scaled_x = (px - margin_left) * screen_scale; + double scaled_y = cache_y * screen_scale; + cr.set_source_surface ((!) cache, (int) rint (scaled_x), (int) rint (scaled_y)); + cr.paint (); + cr.restore(); + } + + Surface draw_on_cache_surface (string cacheid) { + double y; + double ratio; + double cc_y; + Context cr; + Surface cache_surface; + double screen_scale = Screen.get_scale(); + double h = font_size * screen_scale + 1; + + ratio = get_font_scale (); + cc_y = (cached_font.top_limit - cached_font.base_line) * ratio; + + // double x = margin_left * ratio; + double x = 0; + double w = get_sidebearing_extent () * screen_scale + x + margin_left + 1; + + cache_surface = Screen.create_background_surface ((int) w, (int) h); + cr = new Context (cache_surface); + cr.scale (screen_scale, screen_scale); + + y = cc_y; + + if (unlikely (cached_font.base_line != 0)) { + warning ("Base line not zero."); + } + + iterate ((glyph, kerning, last) => { + double end; + + x += kerning * ratio; + end = x + glyph.get_width () * ratio; + + // truncation + if (truncated_width > 0 && end > truncated_width) { + return; + } + + if (use_cache) { + draw_chached (cr, glyph, kerning, last, x, y, cc_y, + ratio, cacheid); + } else { + draw_without_cache (cr, glyph, kerning, last, x, y, cc_y, ratio); + } + + x = end; + }); + + return cache_surface; + } + + void draw_without_cache (Context cr, Glyph glyph, double kerning, bool last, + double x, double y, double cc_y, double ratio) { + + double lsb; + + cr.save (); + cr.set_source_rgba (r, g, b, a); + cr.new_path (); + + lsb = glyph.left_limit; + + foreach (Path path in glyph.get_visible_paths ()) { + draw_path (cr, glyph, path, lsb, x, y, ratio); + } + + cr.fill (); + cr.restore (); + + } + + void draw_chached (Context cr, Glyph glyph, double kerning, bool last, + double x, double y, double cc_y, double ratio, + string cacheid = "") { + + double lsb; + Surface cache; + Surface cached_glyph; + Context cc; + string cache_id; + double glyph_margin_left = glyph.get_left_side_bearing (); + + if (glyph_margin_left < 0) { + glyph_margin_left = -glyph_margin_left; + } else { + glyph_margin_left = 0; + } + + double xp = (x - glyph_margin_left * ratio) * Screen.get_scale (); + double yp = (y - cc_y) * Screen.get_scale (); + int offset_x, offset_y; + + offset_x = (int) (10 * (xp - (int) xp)); + offset_y = (int) (10 * (yp - (int) yp)); + + cache_id = (cacheid == "") ? get_cache_id (offset_x, offset_y) : cacheid; + + if (!glyph.has_cache (cache_id)) { + int w = (int) ((2 * glyph_margin_left * ratio + glyph.get_width ()) * ratio) + 2; + int h = (int) font_size + 2; + cache = Screen.create_background_surface (w, h); + cc = new Context (cache); + + cc.scale(Screen.get_scale (), Screen.get_scale ()); + + lsb = glyph.left_limit - glyph_margin_left; + + cc.save (); + cc.set_source_rgba (r, g, b, a); + cc.new_path (); + + foreach (Path path in glyph.get_visible_paths ()) { + draw_path (cc, glyph, path, lsb, glyph_margin_left * ratio + offset_x / 10.0, cc_y + offset_y / 10.0, ratio); + } + + cc.fill (); + cc.restore (); + + if (use_cache) { + glyph.set_cache (cache_id, cache); + } + + cached_glyph = cache; + } else { + cached_glyph = glyph.get_cache (cache_id); + } + + cr.save (); + cr.set_antialias (Cairo.Antialias.NONE); + cr.scale(1 / Screen.get_scale (), 1 / Screen.get_scale ()); + cr.set_source_surface (cached_glyph, + (int) xp, + (int) yp); + cr.paint (); + cr.restore (); + } + + void draw_path (Context cr, Glyph glyph, Path path, + double lsb, double x, double y, double scale) { + + EditPoint e, prev; + double xa, ya, xb, yb, xc, yc, xd, yd; + double by; + double s = get_scale (glyph); + + if (path.points.size > 0) { + if (unlikely (path.is_open ())) { + warning (@"Path is open in $(glyph.get_name ())."); + } + + //path.add_hidden_double_points (); // FIXME: this distorts shapes + + prev = path.points.get (path.points.size - 1); + xa = (prev.x - lsb) * s + x; + ya = y - prev.y * s; + cr.move_to (xa, ya); + + by = (y - cached_font.base_line * s); + for (int i = 0; i < path.points.size; i++) { + e = path.points.get (i).copy (); + + PenTool.convert_point_segment_type (prev, e, PointType.CUBIC); + + xb = (prev.get_right_handle ().x - lsb) * s + x; + yb = by - prev.get_right_handle ().y * s; + + xc = (e.get_left_handle ().x - lsb) * s + x; + yc = by - e.get_left_handle ().y * s; + + xd = (e.x - lsb) * s + x; + yd = by - e.y * s; + + cr.curve_to (xb, yb, xc, yc, xd, yd); + cr.line_to (xd, yd); + + prev = e; + } + } + } + + public double get_baseline_to_bottom (Glyph g) { + return get_scale (g) * (-g.baseline - g.bottom_limit); + } + + public double get_scale (Glyph g) { + double s = g.top_limit - g.bottom_limit; + + if (s == 0) { + s = cached_font.top_limit - cached_font.bottom_limit; + } + + return font_size / s; + } + + public double get_font_scale () { + return font_size / (cached_font.top_limit - cached_font.bottom_limit); + } + + public double get_baseline_to_bottom_for_font () { + return get_font_scale () * (-cached_font.base_line - cached_font.bottom_limit); + } + + public void truncate (double max_width) { + truncated_width = max_width; + } + } + + }
--- /dev/null +++ b/libbirdfont/TextRendering/TextArea.vala @@ -1,1 +1,1666 @@ + /* + 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 Math; + + namespace BirdFont { + + public class TextArea : Widget { + + public double min_width = 500; + public double min_height = 100; + public double font_size; + public double padding = 3.3; + public bool single_line = false; + public Color text_color = Color.black (); + + public bool draw_carret { + get { return carret_is_visible; } + set { + carret_is_visible = value; + if (!value) { + update_selection = false; + selection_end = carret.copy (); + } + } + } + public bool carret_is_visible = false; + public bool draw_border = true; + + public double width; + public double height; + + Carret carret = new Carret (); + Carret selection_end = new Carret (); + bool update_selection = false; + public bool show_selection = false; + + public signal void scroll (double pixels); + public signal void text_changed (string text); + public signal void enter (string text); + + Gee.ArrayList<Paragraph> paragraphs = new Gee.ArrayList<Paragraph> (); + private static const int DONE = -2; + + int last_paragraph = 0; + string text; + int text_length; + + Gee.ArrayList<TextUndoItem> undo_items = new Gee.ArrayList<TextUndoItem> (); + Gee.ArrayList<TextUndoItem> redo_items = new Gee.ArrayList<TextUndoItem> (); + + bool store_undo_state_at_next_event = false; + + public bool editable; + + public TextArea (double font_size = 20, Color? c = null) { + this.font_size = font_size; + width = min_width; + height = min_height; + editable = true; + + if (c != null) { + text_color = (!) c; + } + } + + public override void focus (bool focus) { + draw_carret = focus; + } + + public override double get_height () { + return height + 2 * padding; + } + + public override double get_width () { + return width + 2 * padding; + } + + public void set_font_size (double z) { + font_size = z; + } + + bool generate_paragraphs () { + Paragraph paragraph; + + int next_paragraph = -1; + + if (is_null (text)) { + warning ("No text"); + return false; + } + + if (last_paragraph == DONE) { + return false; + } + + next_paragraph = text.index_of ("\n", last_paragraph); + + if (next_paragraph == -1) { + paragraph = new Paragraph (text.substring (last_paragraph), font_size, paragraphs.size, text_color); + paragraphs.add (paragraph); + last_paragraph = DONE; + } else { + next_paragraph += "\n".length; + paragraph = new Paragraph (text.substring (last_paragraph, next_paragraph - last_paragraph), font_size, paragraphs.size, text_color); + paragraphs.add (paragraph); + last_paragraph = next_paragraph; + } + + return last_paragraph != DONE; + } + + void generate_all_paragraphs () { + while (generate_paragraphs ()) { + } + } + + public override void key_press (uint keyval) { + unichar c; + TextUndoItem ui; + + if (!editable) { + return; + } + + c = (unichar) keyval; + + switch (c) { + case ' ': + store_undo_edit_state (); + add_character (keyval); + break; + case 'a': + if (KeyBindings.has_ctrl () || KeyBindings.has_logo ()) { + select_all (); + } else { + add_character (keyval); + } + break; + case 'c': + if (KeyBindings.has_ctrl () || KeyBindings.has_logo ()) { + ClipTool.copy_text (this); + } else { + add_character (keyval); + } + break; + case 'v': + if (KeyBindings.has_ctrl () || KeyBindings.has_logo ()) { + ClipTool.paste_text (this); + store_undo_state_at_next_event = true; + } else { + add_character (keyval); + } + break; + case 'y': + if (KeyBindings.has_ctrl () || KeyBindings.has_logo ()) { + redo (); + } else { + add_character (keyval); + } + break; + case 'z': + if (KeyBindings.has_ctrl () || KeyBindings.has_logo ()) { + undo (); + } else { + add_character (keyval); + } + break; + case Key.RIGHT: + check_selection (); + move_carret_next (); + break; + case Key.LEFT: + check_selection (); + move_carret_previous (); + break; + case Key.DOWN: + check_selection (); + move_carret_next_row (); + break; + case Key.UP: + check_selection (); + move_carret_previous_row (); + break; + case Key.END: + check_selection (); + move_carret_to_end_of_line (); + break; + case Key.HOME: + check_selection (); + move_carret_to_beginning_of_line (); + break; + case Key.BACK_SPACE: + if (has_selection ()) { + ui = delete_selected_text (); + undo_items.add (ui); + redo_items.clear (); + store_undo_state_at_next_event = true; + } else { + ui = remove_last_character (); + undo_items.add (ui); + redo_items.clear (); + store_undo_state_at_next_event = true; + } + text_changed (get_text ()); + break; + case Key.ENTER: + store_undo_edit_state (); + insert_text ("\n"); + + if (single_line) { + enter (get_text ()); + } + break; + case Key.DEL: + if (has_selection ()) { + ui = delete_selected_text (); + undo_items.add (ui); + redo_items.clear (); + store_undo_state_at_next_event = true; + } else { + ui = remove_next_character (); + undo_items.add (ui); + redo_items.clear (); + store_undo_state_at_next_event = true; + } + text_changed (get_text ()); + break; + default: + if (!KeyBindings.has_ctrl () && !KeyBindings.has_logo ()) { + add_character (keyval); + } + break; + } + + GlyphCanvas.redraw (); + } + + void check_selection () { + if (!has_selection () && KeyBindings.has_shift ()) { + show_selection = true; + selection_end = carret.copy (); + } + + if (!KeyBindings.has_shift ()) { + show_selection = false; + } + } + + private void add_character (uint keyval) { + unichar c = (unichar) keyval; + string s; + + if (!is_modifier_key (keyval) + && !KeyBindings.has_ctrl () + && !KeyBindings.has_alt ()) { + + s = (!) c.to_string (); + + if (s.validate ()) { + if (store_undo_state_at_next_event) { + store_undo_edit_state (); + store_undo_state_at_next_event = false; + } + + insert_text (s); + } + } + } + + Paragraph get_current_paragraph () { + Paragraph p; + + if (unlikely (!(0 <= carret.paragraph < paragraphs.size))) { + warning (@"No paragraph, index: $(carret.paragraph), size: $(paragraphs.size)"); + p = new Paragraph ("", 0, 0, text_color); + paragraphs.add (p); + return p; + } + + p = paragraphs.get (carret.paragraph); + return p; + } + + public void set_text (string t) { + int tl; + + if (single_line) { + text = t.replace ("\n", "").replace ("\r", ""); + } else { + text = t; + } + + tl = t.length; + text_length += tl; + + paragraphs.clear (); + generate_paragraphs (); + + return_if_fail (paragraphs.size != 0); + + carret.paragraph = paragraphs.size - 1; + carret.character_index = paragraphs.get (paragraphs.size - 1).text.length; + selection_end = carret.copy (); + show_selection = false; + + text_changed (get_text ()); + } + + Carret get_selection_start () { + if (carret.paragraph == selection_end.paragraph) { + return carret.character_index < selection_end.character_index ? carret : selection_end; + } + + return carret.paragraph < selection_end.paragraph ? carret : selection_end; + } + + Carret get_selection_stop () { + if (carret.paragraph == selection_end.paragraph) { + return carret.character_index > selection_end.character_index ? carret : selection_end; + } + + return carret.paragraph > selection_end.paragraph ? carret : selection_end; + } + + public string get_selected_text () { + Carret selection_start, selection_stop; + int i; + Paragraph pg; + StringBuilder sb; + + sb = new StringBuilder (); + + if (!has_selection ()) { + return "".dup (); + } + + selection_start = get_selection_start (); + selection_stop = get_selection_stop (); + + if (selection_start.paragraph == selection_stop.paragraph) { + pg = paragraphs.get (selection_start.paragraph); + return pg.text.substring (selection_start.character_index, selection_stop.character_index - selection_start.character_index); + } + + pg = paragraphs.get (selection_start.paragraph); + sb.append (pg.text.substring (selection_start.character_index)); + + for (i = selection_start.paragraph + 1; i < selection_stop.paragraph; i++) { + return_val_if_fail (0 <= i < paragraphs.size, "".dup ()); + pg = paragraphs.get (i); + sb.append (pg.text); + } + + pg = paragraphs.get (selection_stop.paragraph); + sb.append (pg.text.substring (0, selection_stop.character_index)); + + return sb.str; + } + + public void select_all () { + while (last_paragraph != DONE) { + generate_paragraphs (); + } + + if (paragraphs.size > 0) { + carret.paragraph = 0; + carret.character_index = 0; + selection_end.paragraph = paragraphs.size - 1; + selection_end.character_index = paragraphs.get (paragraphs.size - 1).text_length; + show_selection = true; + } + } + + public TextUndoItem delete_selected_text () { + Carret selection_start, selection_stop; + int i; + Paragraph pg, pge; + string e, s, n; + bool same; + TextUndoItem ui; + + ui = new TextUndoItem (carret); + + e = ""; + s = ""; + n = ""; + + if (!has_selection ()) { + warning ("No selected text."); + return ui; + } + + selection_start = get_selection_start (); + selection_stop = get_selection_stop (); + + same = selection_start.paragraph == selection_stop.paragraph; + + if (!same) { + return_val_if_fail (0 <= selection_start.paragraph < paragraphs.size, ui); + pg = paragraphs.get (selection_start.paragraph); + s = pg.text.substring (0, selection_start.character_index); + + return_val_if_fail (0 <= selection_stop.paragraph < paragraphs.size, ui); + pge = paragraphs.get (selection_stop.paragraph); + e = pge.text.substring (selection_stop.character_index); + + if (!s.has_suffix ("\n")) { + ui.deleted.add (pge.copy ()); + ui.edited.add (pg.copy ()); + + pg.set_text (s + e); + pge.set_text (""); + } else { + ui.edited.add (pg.copy ()); + ui.edited.add (pge.copy ()); + + pg.set_text (s); + pge.set_text (e); + } + } else { + return_val_if_fail (0 <= selection_start.paragraph < paragraphs.size, ui); + + pg = paragraphs.get (selection_start.paragraph); + n = pg.text.substring (0, selection_start.character_index); + n += pg.text.substring (selection_stop.character_index); + + if (n == "") { + ui.deleted.add (pg.copy ()); + paragraphs.remove_at (selection_start.paragraph); + } else { + ui.edited.add (pg.copy ()); + } + + pg.set_text (n); + } + + if (e == "" && !same) { + paragraphs.remove_at (selection_stop.paragraph); + } + + for (i = selection_stop.paragraph - 1; i > selection_start.paragraph; i--) { + return_val_if_fail (0 <= i < paragraphs.size, ui); + ui.deleted.add (paragraphs.get (i)); + paragraphs.remove_at (i); + } + + if (s == "" && !same) { + return_val_if_fail (0 <= selection_start.paragraph < paragraphs.size, ui); + paragraphs.remove_at (selection_start.paragraph); + } + + carret = selection_start.copy (); + selection_end = carret.copy (); + + show_selection = false; + update_paragraph_index (); + layout (); + + return ui; + } + + void update_paragraph_index () { + int i = 0; + foreach (Paragraph p in paragraphs) { + p.index = i; + i++; + } + } + + public TextUndoItem remove_last_character () { + TextUndoItem ui; + move_carret_previous (); + ui = remove_next_character (); + return ui; + } + + public TextUndoItem remove_next_character () { + Paragraph paragraph; + Paragraph next_paragraph; + int index; + unichar c; + string np; + TextUndoItem ui; + + ui = new TextUndoItem (carret); + + return_val_if_fail (0 <= carret.paragraph < paragraphs.size, ui); + paragraph = paragraphs.get (carret.paragraph); + + index = carret.character_index; + + paragraph.text.get_next_char (ref index, out c); + + if (index >= paragraph.text_length) { + np = paragraph.text.substring (0, carret.character_index); + + if (carret.paragraph + 1 < paragraphs.size) { + next_paragraph = paragraphs.get (carret.paragraph + 1); + paragraphs.remove_at (carret.paragraph + 1); + + np = np + next_paragraph.text; + + ui.deleted.add (next_paragraph); + } + + paragraph.set_text (np); + ui.edited.add (paragraph); + } else { + np = paragraph.text.substring (0, carret.character_index) + paragraph.text.substring (index); + paragraph.set_text (np); + + if (np == "") { + return_val_if_fail (carret.paragraph > 0, ui); + carret.paragraph--; + paragraph = paragraphs.get (carret.paragraph); + carret.character_index = paragraph.text_length; + + ui.deleted.add (paragraphs.get (carret.paragraph + 1)); + + paragraphs.remove_at (carret.paragraph + 1); + } else { + ui.edited.add (paragraph); + } + } + + update_paragraph_index (); + layout (); + + return ui; + } + + public void move_carret_next () { + unichar c; + + move_carret_one_character (); + + if (KeyBindings.has_ctrl ()) { + while (true) { + c = move_carret_one_character (); + + if (c == '\0' || c == ' ') { + break; + } + } + } + } + + unichar move_carret_one_character () { + Paragraph paragraph; + int index; + unichar c; + + return_val_if_fail (0 <= carret.paragraph < paragraphs.size, '\0'); + paragraph = paragraphs.get (carret.paragraph); + + index = carret.character_index; + + paragraph.text.get_next_char (ref index, out c); + + if (index >= paragraph.text_length && carret.paragraph + 1 < paragraphs.size) { + carret.paragraph++; + carret.character_index = 0; + c = ' '; + } else { + carret.character_index = index; + } + + return c; + } + + public void move_carret_previous () { + unichar c; + + move_carret_back_one_character (); + + if (KeyBindings.has_ctrl ()) { + while (true) { + c = move_carret_back_one_character (); + + if (c == '\0' || c == ' ') { + break; + } + } + } + } + + unichar move_carret_back_one_character () { + Paragraph paragraph; + int index, last_index; + unichar c; + + return_val_if_fail (0 <= carret.paragraph < paragraphs.size, '\0'); + paragraph = paragraphs.get (carret.paragraph); + + index = 0; + last_index = -1; + + while (paragraph.text.get_next_char (ref index, out c) && index < carret.character_index) { + last_index = index; + } + + if (last_index <= 0 && carret.paragraph > 0) { + carret.paragraph--; + + return_val_if_fail (0 <= carret.paragraph < paragraphs.size, '\0'); + paragraph = paragraphs.get (carret.paragraph); + carret.character_index = paragraph.text_length; + + if (paragraph.text.has_suffix ("\n")) { + carret.character_index -= "\n".length; + } + + c = ' '; + } else if (last_index > 0) { + carret.character_index = last_index; + } else { + carret.character_index = 0; + c = ' '; + } + + return_val_if_fail (0 <= carret.paragraph < paragraphs.size, '\0'); + + return c; + } + + public void move_carret_next_row () { + double nr = font_size; + + if (carret.desired_y + 2 * font_size >= allocation.height) { + scroll (2 * font_size); + nr = -font_size; + } + + if (carret.desired_y + nr < widget_y + height - padding) { + carret = get_carret_at (carret.desired_x - widget_x - padding, carret.desired_y + nr); + } + } + + public void move_carret_to_end_of_line () { + carret = get_carret_at (widget_x + padding + width, carret.desired_y, false); + } + + public void move_carret_to_beginning_of_line () { + carret = get_carret_at (widget_x, carret.desired_y, false); + } + + public void move_carret_previous_row () { + double nr = -font_size; + + if (carret.desired_y - 2 * font_size < 0) { + scroll (-2 * font_size); + nr = font_size; + } + + if (carret.desired_y + nr > widget_y + padding) { + carret = get_carret_at (carret.desired_x, carret.desired_y + nr); + } + } + + public bool has_selection () { + return show_selection && selection_is_visible (); + } + + private bool selection_is_visible () { + return carret.paragraph != selection_end.paragraph || carret.character_index != selection_end.character_index; + } + + public void insert_text (string t) { + string s; + Paragraph paragraph; + TextUndoItem ui; + Gee.ArrayList<string> pgs; + bool u = false; + + pgs = new Gee.ArrayList<string> (); + + if (single_line) { + s = t.replace ("\n", "").replace ("\r", ""); + pgs.add (s); + } else { + if (t.last_index_of ("\n") > 0) { + string[] parts = t.split ("\n"); + int i; + for (i = 0; i < parts.length -1; i++) { + pgs.add (parts[i]); + pgs.add ("\n"); + } + + pgs.add (parts[parts.length - 1]); + + if (t.has_suffix ("\n")) { + pgs.add ("\n"); + } + } else { + s = t; + pgs.add (s); + } + } + + if (has_selection () && show_selection) { + ui = delete_selected_text (); + u = true; + + if (paragraphs.size == 0) { + paragraphs.add (new Paragraph ("", font_size, 0, text_color)); + } + } else { + ui = new TextUndoItem (carret); + } + + return_if_fail (0 <= carret.paragraph < paragraphs.size); + paragraph = paragraphs.get (carret.paragraph); + + if (pgs.size > 0) { + if (!u) { + ui.edited.add (paragraph.copy ()); + } + + string first = pgs.get (0); + + string end; + string nt = paragraph.text.substring (0, carret.character_index); + + nt += first; + end = paragraph.text.substring (carret.character_index); + + paragraph.set_text (nt); + + int paragraph_index = carret.paragraph; + Paragraph next_paragraph = paragraph; + for (int i = 1; i < pgs.size; i++) { + paragraph_index++; + string next = pgs.get (i); + next_paragraph = new Paragraph (next, font_size, paragraph_index, text_color); + paragraphs.insert (paragraph_index, next_paragraph); + ui.added.add (next_paragraph); + u = true; + } + + carret.paragraph = paragraph_index; + carret.character_index = next_paragraph.text.length; + + next_paragraph.set_text (next_paragraph.text + end); + } + + if (u) { + undo_items.add (ui); + redo_items.clear (); + } + + update_paragraph_index (); + layout (); + + text_changed (get_text ()); + show_selection = false; + } + + public string get_text () { + StringBuilder sb = new StringBuilder (); + + generate_all_paragraphs (); + + foreach (Paragraph p in paragraphs) { + sb.append (p.text); + } + + return sb.str; + } + + Carret get_carret_at (double click_x, double click_y, + bool check_boundaries = true) { + + int i = 0; + double tx, ty; + double p; + string w; + int ch_index; + double min_d = double.MAX; + Carret c = new Carret (); + double dt; + + c.paragraph = -1; + c.desired_x = click_x; + c.desired_y = click_y; + + foreach (Paragraph paragraph in paragraphs) { + if (!check_boundaries || paragraph.text_is_on_screen (allocation, widget_y)) { + ch_index = 0; + + if (paragraph.start_y + widget_y - font_size <= click_y <= paragraph.end_y + widget_y + font_size) { + foreach (Text next_word in paragraph.words) { + double tt_click = click_y - widget_y - padding + font_size; + + w = next_word.text; + + if (next_word.widget_y <= tt_click <= next_word.widget_y + font_size) { + + p = next_word.get_sidebearing_extent (); + + if ((next_word.widget_y <= tt_click <= next_word.widget_y + font_size) + && (next_word.widget_x + widget_x <= click_x <= next_word.widget_x + widget_x + padding + next_word.get_sidebearing_extent ())) { + + tx = widget_x + next_word.widget_x + padding; + ty = widget_y + next_word.widget_y + padding; + + next_word.iterate ((glyph, kerning, last) => { + double cw; + int ci; + double d; + string gc = (!) glyph.get_unichar ().to_string (); + + d = Math.fabs (click_x - tx); + + if (d <= min_d) { + min_d = d; + c.character_index = ch_index; + c.paragraph = i; + } + + cw = (glyph.get_width ()) * next_word.get_font_scale () + kerning; + ci = gc.length; + + tx += cw; + ch_index += ci; + }); + + dt = Math.fabs (click_x - (tx + widget_x + padding)); + if (dt < min_d) { + min_d = dt; + c.character_index = ch_index; + c.paragraph = i; + } + } else { + dt = Math.fabs (click_x - (next_word.widget_x + widget_x + padding + next_word.get_sidebearing_extent ())); + + if (dt < min_d) { + min_d = dt; + c.character_index = ch_index + w.length; + + if (w.has_suffix ("\n")) { + c.character_index -= "\n".length; + } + + c.paragraph = i; + } + + ch_index += w.length; + } + } else { + ch_index += w.length; + } + } + } + } + i++; + } + + if (unlikely (c.paragraph < 0)) { + c.paragraph = paragraphs.size > 0 ? paragraphs.size - 1 : 0; + c.character_index = paragraphs.size > 0 ? paragraphs.get (c.paragraph).text.length : 0; + } + + store_undo_state_at_next_event = true; + + return c; + } + + /** @return offset to click in text. */ + public override void layout () { + double p; + double tx, ty; + string w; + double xmax = 0; + int i = 0; + double dd; + + tx = 0; + ty = font_size; + + if (allocation.width <= 0 || allocation.height <= 0) { + warning ("Parent widget allocation is not set."); + } + + for (i = paragraphs.size - 1; i >= 0 && paragraphs.size > 1; i--) { + if (unlikely (paragraphs.get (i).is_empty ())) { + warning ("Empty paragraph."); + paragraphs.remove_at (i); + update_paragraph_index (); + } + } + + i = 0; + foreach (Paragraph paragraph in paragraphs) { + if (paragraph.need_layout + || (paragraph.text_area_width != width + && paragraph.text_is_on_screen (allocation, widget_y))) { + + paragraph.start_y = ty; + paragraph.start_x = tx; + + paragraph.cached_surface = null; + + foreach (Text next_word in paragraph.words) { + next_word.set_font_size (font_size); + + w = next_word.text; + p = next_word.get_sidebearing_extent (); + + if (unlikely (p == 0)) { + warning (@"Zero width word: $(w)"); + } + + if (w == "") { + break; + } + + if (w == "\n") { + next_word.widget_x = tx; + next_word.widget_y = ty; + + tx = 0; + ty += next_word.font_size; + } else { + if (!single_line) { + if (tx + p + 2 * padding > width || w == "\n") { + tx = 0; + ty += next_word.font_size; + } + } + + if (tx + p > xmax) { + xmax = tx + p; + } + + next_word.widget_x = tx; + next_word.widget_y = ty; + + if (w != "\n") { + tx += p; + } + } + } + + if (tx > xmax) { + xmax = tx; + } + + paragraph.text_area_width = width; + paragraph.width = xmax; + paragraph.end_x = tx; + paragraph.end_y = ty; + paragraph.need_layout = false; + } + + if (xmax > width) { + break; + } + + tx = paragraph.end_x; + ty = paragraph.end_y; + i++; + } + + if (xmax > width) { + this.width = xmax + 2 * padding; + layout (); + return; + } + + this.height = fmax (min_height, ty + 2 * padding); + + 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 (); + layout (); + return; + } + + ty = font_size; + tx = 0; + + foreach (Paragraph paragraph in paragraphs) { + dd = ty - paragraph.start_y; + + if (dd != 0) { + paragraph.start_y += dd; + paragraph.end_y += dd; + foreach (Text word in paragraph.words) { + word.widget_y += dd; + } + } + + ty = paragraph.end_y; + } + } + + public override void button_press (uint button, double x, double y) { + if (is_over (x, y)) { + carret = get_carret_at (x, y); + selection_end = carret.copy (); + update_selection = true; + } + } + + public override void button_release (uint button, double x, double y) { + update_selection = false; + show_selection = selection_is_visible (); + } + + public override bool motion (double x, double y) { + if (update_selection) { + selection_end = get_carret_at (x, y); + show_selection = selection_is_visible (); + } + + return update_selection; + } + + public override void draw (Context cr) { + Text word; + double tx, ty; + string w; + double scale; + double width; + double x = widget_x; + double y = widget_y; + Carret selection_start, selection_stop; + double carret_x; + double carret_y; + + layout (); + + if (draw_border) { + // background + cr.save (); + cr.set_line_width (1); + Theme.color (cr, "Text Area Background"); + draw_rounded_rectangle (cr, x, y, this.width, this.height - padding, padding); + cr.fill (); + cr.restore (); + + // border + cr.save (); + cr.set_line_width (1); + Theme.color (cr, "Foreground 1"); + draw_rounded_rectangle (cr, x, y, this.width, this.height - padding, padding); + cr.stroke (); + cr.restore (); + } + + cr.save (); + + word = new Text (); + + width = this.width - padding; + x += padding; + scale = word.get_font_scale (); + y += font_size; + + // draw selection background + if (has_selection ()) { + tx = 0; + ty = 0; + + selection_start = get_selection_start (); + selection_stop = get_selection_stop (); + + cr.save (); + Theme.color (cr, "Highlighted 1"); + + for (int i = selection_start.paragraph; i <= selection_stop.paragraph; i++) { + return_if_fail (0 <= i < paragraphs.size); + Paragraph pg = paragraphs.get (i); + + if (pg.text_is_on_screen (allocation, widget_y)) { + int char_index = 0; + + foreach (Text next_word in pg.words) { + double cw = next_word.get_sidebearing_extent (); + bool paint_background = false; + bool partial_start = false; + bool partial_stop = false; + int wl; + + w = next_word.text; + wl = w.length; + scale = next_word.get_font_scale (); + + if (selection_start.paragraph == selection_stop.paragraph) { + partial_start = true; + partial_stop = true; + } else if (selection_start.paragraph < i < selection_stop.paragraph) { + paint_background = true; + } else if (selection_start.paragraph == i) { + paint_background = true; + partial_start = true; + } else if (selection_stop.paragraph == i) { + paint_background = char_index + wl < selection_stop.character_index; + partial_stop = !paint_background; + } + + if (paint_background && !(partial_start || partial_stop)) { + double selection_y = widget_y + next_word.widget_y + scale * -next_word.cached_font.bottom_limit - font_size; + cr.rectangle (widget_x + padding + next_word.widget_x - 1, selection_y, cw + 1, font_size); + cr.fill (); + } + + if (partial_start || partial_stop) { + int index = char_index; + double bx = widget_x + padding + next_word.widget_x + (partial_start ? 0 : 1); + + next_word.iterate ((glyph, kerning, last) => { + double cwi; + int ci; + bool draw = (index >= selection_start.character_index && partial_start && !partial_stop) + || (index < selection_stop.character_index && !partial_start && partial_stop) + || (selection_start.character_index <= index < selection_stop.character_index && partial_start && partial_stop); + + cwi = (glyph.get_width ()) * next_word.get_font_scale () + kerning; + + if (draw) { + double selection_y = widget_y + next_word.widget_y + scale * -next_word.cached_font.bottom_limit - font_size; + cr.rectangle (bx - 1, selection_y, cwi + 1, font_size); + cr.fill (); + } + + bx += cwi; + ci = ((!) glyph.get_unichar ().to_string ()).length; + index += ci; + }); + } + + char_index += w.length; + } + } + } + + 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) { + if (carret_is_visible) { + draw_carret_at (cr, widget_x + padding, widget_y + font_size + padding); + } + + return; + } + + Context cc; // cached context + Paragraph paragraph; + paragraph = paragraphs.get (0); + + tx = paragraph.start_x; + ty = paragraph.start_y; + + 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 = Screen.create_background_surface ((int) width + 2, paragraph.get_height () + (int) font_size + 2); + cc = new Context ((!) paragraph.cached_surface); + cc.scale (Screen.get_scale(), Screen.get_scale()); + + foreach (Text next_word in paragraph.words) { + if (next_word.text != "\n") { + next_word.draw_at_top (cc, next_word.widget_x, next_word.widget_y - ty); + } + } + } + + if (likely (paragraph.cached_surface != null)) { + // FIXME: subpixel offset in text area + Screen.paint_background_surface(cr, + (!) paragraph.cached_surface, + (int) (x + tx), + (int) (widget_y + paragraph.start_y - font_size + padding)); + } else { + warning ("No paragraph image."); + } + } + + if (carret_is_visible) { + get_carret_position (carret, out carret_x, out carret_y); + + if (carret_y < 0) { + draw_carret_at (cr, widget_x + padding, widget_y + font_size + padding); + } else { + draw_carret_at (cr, carret_x, carret_y); + } + } + + if (has_selection ()) { + get_carret_position (selection_end, out carret_x, out carret_y); + + if (carret_y < 0) { + draw_carret_at (cr, widget_x + padding, widget_y + font_size + padding); + } else { + draw_carret_at (cr, carret_x, carret_y); + } + } + } + + void get_carret_position (Carret carret, out double carret_x, out double carret_y) { + Paragraph paragraph; + double tx; + double ty; + int ch_index; + int wl; + double pos_x, pos_y; + + ch_index = 0; + + carret_x = -1; + carret_y = -1; + + return_if_fail (0 <= carret.paragraph < paragraphs.size); + paragraph = paragraphs.get (carret.paragraph); + + pos_x = -1; + pos_y = -1; + + foreach (Text next_word in paragraph.words) { + string w = next_word.text; + wl = w.length; + + if (carret.character_index == ch_index) { + pos_x = next_word.widget_x + widget_x + padding; + pos_y = widget_y + next_word.widget_y + next_word.get_baseline_to_bottom_for_font (); + } else if (carret.character_index >= ch_index + wl) { + pos_x = next_word.widget_x + next_word.get_sidebearing_extent () + widget_x + padding; + pos_y = widget_y + next_word.widget_y + next_word.get_baseline_to_bottom_for_font (); + + if (next_word.text.has_suffix ("\n")) { + pos_x = widget_x + padding; + pos_y += next_word.font_size; + } + } else if (ch_index < carret.character_index <= ch_index + wl) { + tx = widget_x + next_word.widget_x; + ty = widget_y + next_word.widget_y + next_word.get_baseline_to_bottom_for_font (); + + if (carret.character_index <= ch_index) { + pos_x = widget_x + padding; + pos_y = ty; + } + + next_word.iterate ((glyph, kerning, last) => { + double cw; + int ci; + + cw = (glyph.get_width ()) * next_word.get_font_scale () + kerning; + ci = ((!) glyph.get_unichar ().to_string ()).length; + + if (ch_index < carret.character_index <= ch_index + ci) { + pos_x = tx + cw + padding; + pos_y = ty; + + if (glyph.get_unichar () == '\n') { + pos_x = widget_x + padding; + pos_y += next_word.font_size; + } + } + + tx += cw; + ch_index += ci; + }); + } + + ch_index += wl; + } + + carret_x = pos_x; + carret_y = pos_y; + } + + void draw_carret_at (Context cr, double x, double y) { + cr.save (); + cr.set_source_rgba (0, 0, 0, 0.5); + cr.set_line_width (1); + cr.move_to (x, y); + cr.line_to (x, y - font_size); + cr.stroke (); + cr.restore (); + } + + public void store_undo_edit_state () { + TextUndoItem ui = new TextUndoItem (carret); + ui.edited.add (get_current_paragraph ().copy ()); + undo_items.add (ui); + redo_items.clear (); + } + + public void redo () { + TextUndoItem i; + TextUndoItem undo_item; + + if (redo_items.size > 0) { + i = redo_items.get (redo_items.size - 1); + + undo_item = new TextUndoItem (i.carret); + + i.deleted.sort ((a, b) => { + Paragraph pa = (Paragraph) a; + Paragraph pb = (Paragraph) b; + return pb.index - pa.index; + }); + + i.added.sort ((a, b) => { + Paragraph pa = (Paragraph) a; + Paragraph pb = (Paragraph) b; + return pa.index - pb.index; + }); + + foreach (Paragraph p in i.deleted) { + if (unlikely (!(0 <= p.index < paragraphs.size))) { + warning ("Paragraph not found."); + } else { + undo_item.deleted.add (p.copy ()); + paragraphs.remove_at (p.index); + } + } + + foreach (Paragraph p in i.added) { + if (p.index == paragraphs.size) { + paragraphs.add (p.copy ()); + } else { + if (unlikely (!(0 <= p.index < paragraphs.size))) { + warning (@"Index: $(p.index) out of bounds, size: $(paragraphs.size)"); + } else { + undo_item.added.add (paragraphs.get (p.index).copy ()); + paragraphs.insert (p.index, p.copy ()); + } + } + } + + foreach (Paragraph p in i.edited) { + if (unlikely (!(0 <= p.index < paragraphs.size))) { + warning (@"Index: $(p.index ) out of bounds, size: $(paragraphs.size)"); + return; + } + + undo_item.edited.add (paragraphs.get (p.index).copy ()); + paragraphs.set (p.index, p.copy ()); + } + + redo_items.remove_at (redo_items.size - 1); + undo_items.add (undo_item); + + carret = i.carret.copy (); + layout (); + } + } + + public void undo () { + TextUndoItem i; + TextUndoItem redo_item; + + if (undo_items.size > 0) { + i = undo_items.get (undo_items.size - 1); + redo_item = new TextUndoItem (i.carret); + + i.deleted.sort ((a, b) => { + Paragraph pa = (Paragraph) a; + Paragraph pb = (Paragraph) b; + return pa.index - pb.index; + }); + + i.added.sort ((a, b) => { + Paragraph pa = (Paragraph) a; + Paragraph pb = (Paragraph) b; + return pb.index - pa.index; + }); + + foreach (Paragraph p in i.added) { + if (unlikely (!(0 <= p.index < paragraphs.size))) { + warning ("Paragraph not found."); + } else { + redo_item.added.add (paragraphs.get (p.index).copy ()); + paragraphs.remove_at (p.index); + } + } + + foreach (Paragraph p in i.deleted) { + if (p.index == paragraphs.size) { + paragraphs.add (p.copy ()); + } else { + if (unlikely (!(0 <= p.index < paragraphs.size))) { + warning (@"Index: $(p.index) out of bounds, size: $(paragraphs.size)"); + } else { + redo_item.deleted.add (p.copy ()); + paragraphs.insert (p.index, p.copy ()); + } + } + } + + foreach (Paragraph p in i.edited) { + if (unlikely (!(0 <= p.index < paragraphs.size))) { + warning (@"Index: $(p.index ) out of bounds, size: $(paragraphs.size)"); + return; + } + + redo_item.edited.add (paragraphs.get (p.index).copy ()); + paragraphs.set (p.index, p.copy ()); + } + + undo_items.remove_at (undo_items.size - 1); + redo_items.add (redo_item); + + carret = i.carret.copy (); + layout (); + } + } + + public void set_editable (bool editable) { + this.editable = editable; + } + + public class TextUndoItem : GLib.Object { + public Carret carret; + public Gee.ArrayList<Paragraph> added = new Gee.ArrayList<Paragraph> (); + public Gee.ArrayList<Paragraph> edited = new Gee.ArrayList<Paragraph> (); + public Gee.ArrayList<Paragraph> deleted = new Gee.ArrayList<Paragraph> (); + + public TextUndoItem (Carret c) { + carret = c.copy (); + } + } + + public class Paragraph : GLib.Object { + 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 double text_area_width = -10000; + + public string text; + + public Gee.ArrayList<Text> words { + get { + if (words_in_paragraph.size == 0) { + generate_words (); + } + + return words_in_paragraph; + } + } + + private Gee.ArrayList<Text> words_in_paragraph = new Gee.ArrayList<Text> (); + public int text_length; + public bool need_layout = true; + public Surface? cached_surface = null; + double font_size; + public int index; + Color text_color; + + public Paragraph (string text, double font_size, int index, Color c) { + this.index = index; + this.font_size = font_size; + text_color = c; + set_text (text); + } + + public Paragraph copy () { + Paragraph p = new Paragraph (text.dup (), font_size, index, text_color); + p.need_layout = true; + return p; + } + + public bool is_empty () { + return text == ""; + } + + public void set_text (string t) { + this.text = t; + text_length = t.length; + need_layout = true; + words.clear (); + cached_surface = null; + } + + 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; + } + + private void generate_words () { + string w; + int p = 0; + bool carret_at_word_end = false; + Text word; + int carret = 0; + int iter_pos = 0; + + return_if_fail (words_in_paragraph.size == 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); + + word.r = text_color.r; + word.g = text_color.g; + word.b = text_color.b; + word.a = text_color.a; + + words_in_paragraph.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; + } + } + + public class Carret : GLib.Object { + + public int paragraph = 0; + + public int character_index { + get { + return ci; + } + + set { + ci = value; + } + } + + private int ci = 0; + + public double desired_x = 0; + public double desired_y = 0; + + public Carret () { + } + + public void print () { + stdout.printf (@"paragraph: $paragraph, character_index: $character_index\n"); + } + + public Carret copy () { + Carret c = new Carret (); + + c.paragraph = paragraph; + c.character_index = character_index; + + c.desired_x = desired_x; + c.desired_y = desired_y; + + return c; + } + } + } + + }
--- /dev/null +++ b/libbirdfont/TextRendering/fontconfig.c @@ -1,1 +1,141 @@ + /* + Copyright (C) 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 + 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. + */ + + #include <stdio.h> + #include <glib.h> + #include <fontconfig/fontconfig.h> + + gchar* find_font_with_property (FcConfig* fontconfig, const gchar* characters, const gchar* property) { + FcPattern* pattern; + FcCharSet* character_set; + FcObjectSet* font_properties; + FcFontSet* fonts; + FcPattern* font; + FcChar8* path; + gchar* result; + gchar* remaining_characters; + gunichar character; + + if (fontconfig == NULL) { + g_warning("Font config not loaded."); + return NULL; + } + + result = NULL; + pattern = FcPatternCreate (); + + character_set = FcCharSetCreate (); + + remaining_characters = (gchar*) characters; + while (TRUE) { + character = g_utf8_get_char (remaining_characters); + + if (character == '\0') { + break; + } + + FcCharSetAddChar(character_set, character); + + remaining_characters = g_utf8_next_char (remaining_characters); + } + + FcPatternAddCharSet (pattern, FC_CHARSET, character_set); + FcCharSetDestroy (character_set); + FcPatternAddInteger (pattern, FC_SLANT, FC_SLANT_ROMAN); + + FcPatternAddBool(pattern, FC_SCALABLE, FcTrue); + font_properties = FcObjectSetBuild (property, NULL); + fonts = FcFontList (fontconfig, pattern, font_properties); + + if (fonts && fonts->nfont > 0) { + font = fonts->fonts[0]; + if (FcPatternGetString(font, property, 0, &path) == FcResultMatch) { + result = g_strdup ((gchar*) path); + } + } + + if (fonts) { + FcFontSetDestroy(fonts); + } + + if (pattern) { + FcPatternDestroy(pattern); + } + + return result; + } + + /** Find a fallback font for a set of characters. + * @return A path to the font file. + */ + gchar* find_font (FcConfig* fontconfig, const gchar* characters) { + return find_font_with_property (fontconfig, characters, FC_FILE); + } + + /** Find a fallback font for a set of characters. + * @return Family name of the font. + */ + gchar* find_font_family (FcConfig* fontconfig, const gchar* characters) { + return find_font_with_property (fontconfig, characters, FC_FAMILY); + } + + /** Find a font file from its family name. + * @param font_config fontconfig instance + * @param font_name name of the font + * @return full path to the font file + */ + gchar* find_font_file (FcConfig* font_config, const gchar* font_name) { + const FcChar8* name; + FcPattern* search_pattern; + FcPattern* font; + FcChar8* file; + gchar* path; + FcObjectSet* font_properties; + FcFontSet* fonts; + int i; + + if (font_config == NULL) { + g_warning("Font config not loaded."); + return NULL; + } + + path = NULL; + name = font_name; + + search_pattern = FcPatternCreate (); + FcPatternAddString (search_pattern, FC_FAMILY, name); + FcPatternAddBool (search_pattern, FC_SCALABLE, FcTrue); + FcPatternAddInteger (search_pattern, FC_WEIGHT, FC_WEIGHT_MEDIUM); + FcPatternAddInteger (search_pattern, FC_SLANT, FC_SLANT_ROMAN); + + font_properties = FcObjectSetBuild (FC_FILE, NULL); + fonts = FcFontList (font_config, search_pattern, font_properties); + + if (fonts->nfont > 0) { + for (i = 0; i < fonts->nfont; i++) { + font = fonts->fonts[i]; + + if (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch) { + path = g_strdup ((gchar*) file); + break; + } + } + FcPatternDestroy (font); + } + + FcPatternDestroy (search_pattern); + + return path; + }