Clone in a terminal:
Commit d004468
Johan Mattsson - 2015-12-26T11:05:51+00:00
d004468de20a0a01226b57f9b5ea5b195b1b1256johan.mattsson.m@gmail.com

Rename text rendering directory

Changed files (19)

Diff

diff --git a/birdfont/GtkWindow.vala b/birdfont/GtkWindow.valaindex f124af9..1964b3c 100644--- 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 bydiff --git a/libbirdfont/GlyphSequence.vala b/libbirdfont/GlyphSequence.valaindex 58a6333..9b08df1 100644--- a/libbirdfont/GlyphSequence.vala+++ b/libbirdfont/GlyphSequence.vala@@ -111,7 +111,7 @@ public class GlyphSequence : GLib.Object { 				 				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 @@ public class GlyphSequence : GLib.Object { 						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);diff --git a/libbirdfont/Path.vala b/libbirdfont/Path.valaindex c5fdcc2..f927bad 100644--- a/libbirdfont/Path.vala+++ b/libbirdfont/Path.vala@@ -90,7 +90,7 @@ public class Path : GLib.Object { 	/** 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;diff --git a/libbirdfont/Renderer/CachedFont.vala b/libbirdfont/Renderer/CachedFont.valadeleted file mode 100644index d93368a..0000000--- a/libbirdfont/Renderer/CachedFont.vala+++ /dev/null@@ -1,99 +0,0 @@-/*-	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 a/libbirdfont/Renderer/Drawing.vala b/libbirdfont/Renderer/Drawing.valadeleted file mode 100644index 2de5049..0000000--- a/libbirdfont/Renderer/Drawing.vala+++ /dev/null@@ -1,50 +0,0 @@-/*-	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 ();-}--}diff --git a/libbirdfont/Renderer/FallbackFont.vala b/libbirdfont/Renderer/FallbackFont.valadeleted file mode 100644index 539fe0b..0000000--- a/libbirdfont/Renderer/FallbackFont.vala+++ /dev/null@@ -1,337 +0,0 @@-/*-	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;-		}-	}-}--}diff --git a/libbirdfont/Renderer/FontCache.vala b/libbirdfont/Renderer/FontCache.valadeleted file mode 100644index 5bf2fa3..0000000--- a/libbirdfont/Renderer/FontCache.vala+++ /dev/null@@ -1,83 +0,0 @@-/*-	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;-	}--}--}diff --git a/libbirdfont/Renderer/LineTextArea.vala b/libbirdfont/Renderer/LineTextArea.valadeleted file mode 100644index 24b8f11..0000000--- a/libbirdfont/Renderer/LineTextArea.vala+++ /dev/null@@ -1,33 +0,0 @@-/*-	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 a/libbirdfont/Renderer/Text.vala b/libbirdfont/Renderer/Text.valadeleted file mode 100644index ae9c5be..0000000--- a/libbirdfont/Renderer/Text.vala+++ /dev/null@@ -1,584 +0,0 @@-/*-	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;-	}-}--}diff --git a/libbirdfont/Renderer/TextArea.vala b/libbirdfont/Renderer/TextArea.valadeleted file mode 100644index 797c52c..0000000--- a/libbirdfont/Renderer/TextArea.vala+++ /dev/null@@ -1,1665 +0,0 @@-/*-	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 a/libbirdfont/Renderer/fontconfig.c b/libbirdfont/Renderer/fontconfig.cdeleted file mode 100644index f02b747..0000000--- a/libbirdfont/Renderer/fontconfig.c+++ /dev/null@@ -1,140 +0,0 @@-/*-	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;-}diff --git a/libbirdfont/TextRendering/CachedFont.vala b/libbirdfont/TextRendering/CachedFont.valanew file mode 100644index 0000000..d93368a--- /dev/null+++ b/libbirdfont/TextRendering/CachedFont.vala@@ -0,0 +1,99 @@+/*+	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 a/libbirdfont/TextRendering/Drawing.vala b/libbirdfont/TextRendering/Drawing.valanew file mode 100644index 0000000..2de5049--- /dev/null+++ b/libbirdfont/TextRendering/Drawing.vala@@ -0,0 +1,50 @@+/*+	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 ();+}++}diff --git a/libbirdfont/TextRendering/FallbackFont.vala b/libbirdfont/TextRendering/FallbackFont.valanew file mode 100644index 0000000..539fe0b--- /dev/null+++ b/libbirdfont/TextRendering/FallbackFont.vala@@ -0,0 +1,337 @@+/*+	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;+		}+	}+}++}diff --git a/libbirdfont/TextRendering/FontCache.vala b/libbirdfont/TextRendering/FontCache.valanew file mode 100644index 0000000..5bf2fa3--- /dev/null+++ b/libbirdfont/TextRendering/FontCache.vala@@ -0,0 +1,83 @@+/*+	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;+	}++}++}diff --git a/libbirdfont/TextRendering/LineTextArea.vala b/libbirdfont/TextRendering/LineTextArea.valanew file mode 100644index 0000000..24b8f11--- /dev/null+++ b/libbirdfont/TextRendering/LineTextArea.vala@@ -0,0 +1,33 @@+/*+	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 a/libbirdfont/TextRendering/Text.vala b/libbirdfont/TextRendering/Text.valanew file mode 100644index 0000000..ae9c5be--- /dev/null+++ b/libbirdfont/TextRendering/Text.vala@@ -0,0 +1,584 @@+/*+	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;+	}+}++}diff --git a/libbirdfont/TextRendering/TextArea.vala b/libbirdfont/TextRendering/TextArea.valanew file mode 100644index 0000000..797c52c--- /dev/null+++ b/libbirdfont/TextRendering/TextArea.vala@@ -0,0 +1,1665 @@+/*+	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 a/libbirdfont/TextRendering/fontconfig.c b/libbirdfont/TextRendering/fontconfig.cnew file mode 100644index 0000000..f02b747--- /dev/null+++ b/libbirdfont/TextRendering/fontconfig.c@@ -0,0 +1,140 @@+/*+	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;+}

Contact

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