1/*
2	Copyright (C) 2014 2015 Johan Mattsson
3
4	This library is free software; you can redistribute it and/or modify
5	it under the terms of the GNU Lesser General Public License as
6	published by the Free Software Foundation; either version 3 of the
7	License, or (at your option) any later version.
8
9	This library is distributed in the hope that it will be useful, but
10	WITHOUT ANY WARRANTY; without even the implied warranty of
11	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12	Lesser General Public License for more details.
13*/
14
15using Cairo;
16using Math;
17
18namespace BirdFont {
19
20/** Test implementation of a birdfont rendering engine. */
21public class Text : Widget {
22	FontCache font_cache;
23	public CachedFont cached_font;
24
25	Surface? cache = null;
26
27	public string text;
28
29	private bool use_cache = true;
30
31	GlyphSequence glyph_sequence {
32		get {
33			if (gs == null) {
34				gs = generate_glyphs ();
35			}
36
37			return (!) gs;
38		}
39	}
40
41	Gee.ArrayList<string> glyph_names;
42	GlyphSequence? gs = null;
43
44	public delegate void Iterator (Glyph glyph, double kerning, bool last);
45	public double font_size;
46	double sidebearing_extent = 0;
47
48	public double r = 0;
49	public double g = 0;
50	public double b = 0;
51	public double a = 1;
52	double truncated_width = -1;
53
54	double margin_left = 0;
55
56	public Text (string text = "", double size = 17, double margin_bottom = 0) {
57		this.margin_bottom = margin_bottom;
58		font_cache = FontCache.get_default_cache ();
59		cached_font = font_cache.get_fallback ();
60
61		set_text (text);
62		set_font_size (size);
63	}
64
65	public void set_use_cache (bool cache) {
66		use_cache = cache;
67	}
68
69	public string get_text () {
70		return text;
71	}
72
73	/** Set font for this text area.
74	 * @param font_absolute path to the font file or a file name for one of the font files in search paths.
75	 * @return true if the font was found
76	 */
77	public bool load_font (string font_file) {
78		File path;
79		File f;
80		FontCache fc;
81
82		f = File.new_for_path (font_file);
83		path = (f.query_exists ()) ? f : SearchPaths.find_file (null, font_file);
84
85		fc = FontCache.get_default_cache ();
86		cached_font = fc.get_font ((!) path.get_path ());
87		gs = generate_glyphs ();
88
89		return cached_font.font != null;
90	}
91
92	public void set_font_size (double height_in_pixels) {
93		font_size = height_in_pixels;
94		sidebearing_extent = 0;
95
96		if (gs == null) { // ensure height is loaded for the font
97			gs = generate_glyphs ();
98		}
99	}
100
101	public void set_font_cache (FontCache font_cache) {
102		this.font_cache = font_cache;
103	}
104
105	public void set_text (string text) {
106		this.text = text;
107		gs = null;
108		sidebearing_extent = 0;
109		cache = null;
110	}
111
112	private GlyphSequence generate_glyphs () {
113		int index;
114		unichar c;
115		string name;
116		Glyph? g;
117		GlyphSequence gs;
118
119		gs = new GlyphSequence ();
120
121		glyph_names = new Gee.ArrayList<string> ();
122		index = 0;
123		while (text.get_next_char (ref index, out c)) {
124			name = (!) c.to_string ();
125			g = cached_font.get_glyph_by_name (name);
126
127			gs.glyph.add (g);
128			glyph_names.add (name);
129		}
130
131		return gs;
132	}
133
134	public void iterate (Iterator iter) {
135		Glyph glyph;
136		double w, kern;
137		int wi;
138		Glyph? prev;
139		Glyph? g;
140		GlyphSequence word_with_ligatures;
141		GlyphRange? gr_left, gr_right;
142		GlyphSequence word;
143		KerningClasses kc;
144		Font empty = Font.empty;
145
146		glyph = new Glyph.no_lines ("", '\0');
147
148		w = 0;
149		prev = null;
150		kern = 0;
151
152		word = glyph_sequence;
153		wi = 0;
154
155		if (cached_font.font != null) {
156			word_with_ligatures = word.process_ligatures ((!) cached_font.font);
157		} else {
158			word_with_ligatures = word.process_ligatures (new Font ());
159		}
160
161		gr_left = null;
162		gr_right = null;
163
164		if (cached_font.font != null) {
165			kc = ((!) cached_font.font).get_kerning_classes ();
166		} else {
167			kc = new KerningClasses (empty);
168		}
169
170		if (word_with_ligatures.glyph.size > 0) {
171			g = word_with_ligatures.glyph.get (0);
172			if (g != null) {
173				margin_left = ((!) g).get_left_side_bearing ();
174
175				if (margin_left < 0) {
176					margin_left = -margin_left;
177				} else {
178					margin_left = 0;
179				}
180			}
181		}
182
183		for (int i = 0; i < word_with_ligatures.glyph.size; i++) {
184			g = word_with_ligatures.glyph.get (i);
185
186			if (g == null || prev == null || wi == 0) {
187				kern = 0;
188			} else {
189				return_if_fail (wi < word_with_ligatures.ranges.size);
190				return_if_fail (wi - 1 >= 0);
191
192				gr_left = word_with_ligatures.ranges.get (wi - 1);
193				gr_right = word_with_ligatures.ranges.get (wi);
194
195				kern = kc.get_kerning_for_pair (((!) prev).get_name (), ((!) g).get_name (), gr_left, gr_right);
196			}
197
198			// process glyph
199			if (g == null && (0 <= i < glyph_names.size)) {
200				g = cached_font.get_glyph_by_name (glyph_names.get (i));
201			}
202
203			glyph = (g == null) ? new Glyph ("") : (!) g;
204			iter (glyph, kern, i + 1 == word_with_ligatures.glyph.size);
205			prev = g;
206			wi++;
207		}
208	}
209
210	// FIXME: some fonts doesn't have on curve extrema
211	public double get_extent () {
212		double x = 0;
213
214		iterate ((glyph, kerning, last) => {
215			double x1, y1, x2, y2;
216			double lsb;
217
218			lsb = glyph.left_limit;
219
220			if (!last) {
221				x += (glyph.get_width () + kerning) * get_scale (glyph);
222			} else {
223				glyph.boundaries (out x1, out y1, out x2, out y2);
224				x += (x2 - lsb) * get_scale (glyph);
225			}
226		});
227
228		return x;
229	}
230
231	public double get_sidebearing_extent () {
232		double x ;
233
234		if (likely (sidebearing_extent > 0)) {
235			return sidebearing_extent;
236		}
237
238		x = 0;
239
240		iterate ((glyph, kerning, last) => {
241			double lsb;
242			lsb = glyph.left_limit;
243			x += (glyph.get_width () + kerning) * get_scale (glyph);
244		});
245
246		sidebearing_extent = x;
247		return x;
248	}
249
250	public override double get_height () {
251		return font_size;
252	}
253
254	public double get_acender () {
255		double max_height = 0;
256
257		iterate ((glyph, kerning, last) => {
258			double x1, y1, x2, y2;
259			double h;
260			glyph.boundaries (out x1, out y1, out x2, out y2);
261			h = Math.fmax (y1, y2) - Math.fmin (y1, y2);
262			h *= get_scale (glyph) - glyph.baseline * get_scale (glyph);
263			if (h > max_height) {
264				max_height = h;
265			}
266		});
267
268		return max_height;
269	}
270
271	public override double get_width () {
272		double x = 0;
273		bool first = true;
274
275		iterate ((glyph, kerning, last) => {
276			double x1, y1, x2, y2;
277			double lsb;
278
279			lsb = glyph.left_limit;
280
281			if (first) {
282				glyph.boundaries (out x1, out y1, out x2, out y2);
283				x += (glyph.get_width () + kerning - Math.fmin (x1, x2)) * get_scale (glyph);
284				first = false;
285			} else if (!last) {
286				x += (glyph.get_width () + kerning) * get_scale (glyph);
287			} else {
288				glyph.boundaries (out x1, out y1, out x2, out y2);
289				x += (x2 - lsb) * get_scale (glyph);
290			}
291		});
292
293		return x;
294	}
295
296	public double get_decender () {
297		double decender_max = get_max_decender ();
298		return decender_max > 0 ? decender_max : 0;
299	}
300
301	private double get_max_decender () {
302		double decender = 0;
303		double decender_max = 0;
304
305		iterate ((glyph, kerning, last) => {
306			double x1, y1, x2, y2;
307			double y;
308			glyph.boundaries (out x1, out y1, out x2, out y2);
309			y = Math.fmin (y1, y2);
310			decender = (glyph.baseline - y) * get_scale (glyph);
311			if (decender > decender_max) {
312				decender_max = decender;
313			}
314		});
315
316		return decender_max;
317	}
318
319	public override void draw (Context cr) {
320		double descender = cached_font.bottom_limit + cached_font.base_line;
321		double y = widget_y + get_height () + get_font_scale () * descender; // FIXME:
322		draw_at_baseline (cr, widget_x, y);
323	}
324
325	public void draw_at_top (Context cr, double px, double py, string cacheid = "") {
326		double s = get_font_scale ();
327		double y = py + s * (cached_font.top_limit - cached_font.base_line);
328		draw_at_baseline (cr, px, y, cacheid);
329	}
330
331	public void set_source_rgba (double r, double g, double b, double a) {
332		if (this.r != r ||
333			this.g != g ||
334			this.b != b ||
335			this.a != a) {
336
337			this.r = r;
338			this.g = g;
339			this.b = b;
340			this.a = a;
341			cache = null;
342		}
343	}
344
345	public string get_cache_id (int offset_x, int offset_y) {
346		string key;
347		int64 c;
348
349		c = (((int64) (r * 255)) << 24)
350			| (((int64) (g * 255)) << 16)
351			| (((int64) (b * 255)) << 8)
352			| (((int64) (a * 255)) << 0);
353
354		// FIXME: use binary key
355		key = @"$font_size $c $offset_x $offset_y";
356
357		return key;
358	}
359
360	public void draw_at_baseline (Context cr, double px, double py, string cacheid = "") {
361		if (cache == null) {
362			cache = draw_on_cache_surface (cacheid);
363		}
364
365		double screen_scale = Screen.get_scale ();
366		double font_scale = get_font_scale ();
367		double cache_y = py - font_scale * (cached_font.top_limit - cached_font.base_line);
368
369		cr.save();
370		cr.scale (1 / screen_scale, 1 / screen_scale);
371		double scaled_x = (px - margin_left) * screen_scale;
372		double scaled_y = cache_y * screen_scale;
373		cr.set_source_surface ((!) cache, (int) rint (scaled_x), (int) rint (scaled_y));
374		cr.paint ();
375		cr.restore();
376	}
377
378	Surface draw_on_cache_surface (string cacheid) {
379		double y;
380		double ratio;
381		double cc_y;
382		Context cr;
383		Surface cache_surface;
384		double screen_scale = Screen.get_scale();
385		double h = font_size * screen_scale + 1;
386
387		ratio = get_font_scale ();
388		cc_y = (cached_font.top_limit - cached_font.base_line) * ratio;
389
390		// double x = margin_left * ratio;
391		double x = 0;
392		double py = cc_y;
393
394		double w = get_sidebearing_extent () * screen_scale + x + margin_left + 1;
395
396		if (!w.is_normal () || !h.is_normal ())  {
397			warning (@"Bad text size, h: $h w: $w");
398			return Screen.create_background_surface (1, 1);
399		}
400
401		cache_surface = Screen.create_background_surface ((int) w, (int) h);
402		cr = new Context (cache_surface);
403		cr.scale (screen_scale, screen_scale);
404
405		y = cc_y;
406
407		if (unlikely (cached_font.base_line != 0)) {
408			warning ("Base line not zero.");
409		}
410
411		iterate ((glyph, kerning, last) => {
412			double end;
413
414			x += kerning * ratio;
415			end = x + glyph.get_width () * ratio;
416
417			// truncation
418			if (truncated_width > 0 && end > truncated_width) {
419				return;
420			}
421
422			if (use_cache) {
423				draw_chached (cr, glyph, kerning, last, x, y, cc_y,
424					ratio, cacheid);
425			} else {
426				draw_without_cache (cr, glyph, kerning, last, x, y, cc_y, ratio);
427			}
428
429			x = end;
430		});
431
432		return cache_surface;
433	}
434
435	void draw_without_cache (Context cr, Glyph glyph, double kerning, bool last,
436		double x, double y, double cc_y, double ratio) {
437
438		double lsb;
439
440		cr.save ();
441		cr.set_source_rgba (r, g, b, a);
442		cr.new_path ();
443
444		lsb = glyph.left_limit;
445
446		foreach (Path path in glyph.get_visible_paths ()) {
447			draw_path (cr, glyph, path, lsb, x, y, ratio);
448		}
449
450		cr.fill ();
451		cr.restore ();
452
453	}
454
455	void draw_chached (Context cr, Glyph glyph, double kerning, bool last,
456		double x, double y, double cc_y, double ratio,
457		string cacheid = "") {
458
459		double lsb;
460		Surface cache;
461		Surface cached_glyph;
462		Context cc;
463		string cache_id;
464		double glyph_margin_left = glyph.get_left_side_bearing ();
465
466		if (glyph_margin_left < 0) {
467			glyph_margin_left = -glyph_margin_left;
468		} else {
469			glyph_margin_left = 0;
470		}
471
472		double xp = (x - glyph_margin_left * ratio) * Screen.get_scale ();
473		double yp = (y - cc_y) * Screen.get_scale ();
474		int offset_x, offset_y;
475
476		offset_x = (int) (10 * (xp - (int) xp));
477		offset_y = (int) (10 * (yp - (int) yp));
478
479		cache_id = (cacheid == "") ? get_cache_id (offset_x, offset_y) : cacheid;
480
481		if (!glyph.has_cache (cache_id)) {
482			int w = (int) ((2 * glyph_margin_left * ratio + glyph.get_width ()) * ratio) + 2;
483			int h = (int) font_size + 2;
484			cache = Screen.create_background_surface (w, h);
485			cc = new Context (cache);
486
487			cc.scale(Screen.get_scale (), Screen.get_scale ());
488
489			lsb = glyph.left_limit - glyph_margin_left;
490
491			cc.save ();
492			cc.set_source_rgba (r, g, b, a);
493			cc.new_path ();
494
495			foreach (Path path in glyph.get_visible_paths ()) {
496				draw_path (cc, glyph, path, lsb, glyph_margin_left * ratio + offset_x / 10.0, cc_y + offset_y / 10.0, ratio);
497			}
498
499			cc.fill ();
500			cc.restore ();
501
502			if (use_cache) {
503				glyph.set_cache (cache_id, cache);
504			}
505
506			cached_glyph = cache;
507		} else {
508			cached_glyph = glyph.get_cache (cache_id);
509		}
510
511		cr.save ();
512		cr.set_antialias (Cairo.Antialias.NONE);
513		cr.scale(1 / Screen.get_scale (), 1 / Screen.get_scale ());
514		cr.set_source_surface (cached_glyph,
515			(int) xp,
516			(int) yp);
517		cr.paint ();
518		cr.restore ();
519	}
520
521	void draw_path (Context cr, Glyph glyph, Path path,
522		double lsb, double x, double y, double scale) {
523
524		EditPoint e, prev;
525		double xa, ya, xb, yb, xc, yc, xd, yd;
526		double by;
527		double s = get_scale (glyph);
528
529		if (path.points.size > 0) {
530			if (unlikely (path.is_open ())) {
531				warning (@"Path is open in $(glyph.get_name ()).");
532			}
533
534			//path.add_hidden_double_points (); // FIXME: this distorts shapes
535
536			prev = path.points.get (path.points.size - 1);
537			xa = (prev.x - lsb) * s + x;
538			ya = y - prev.y * s;
539			cr.move_to (xa, ya);
540
541			by = (y - cached_font.base_line * s);
542			for (int i = 0; i < path.points.size; i++) {
543				e = path.points.get (i).copy ();
544
545				PenTool.convert_point_segment_type (prev, e, PointType.CUBIC);
546
547				xb = (prev.get_right_handle ().x - lsb) * s + x;
548				yb = by - prev.get_right_handle ().y * s;
549
550				xc = (e.get_left_handle ().x - lsb) * s + x;
551				yc = by - e.get_left_handle ().y * s;
552
553				xd = (e.x - lsb) * s + x;
554				yd = by - e.y * s;
555
556				cr.curve_to (xb, yb, xc, yc, xd, yd);
557				cr.line_to (xd, yd);
558
559				prev = e;
560			}
561		}
562	}
563
564	public double get_baseline_to_bottom (Glyph g) {
565		return get_scale (g) * (-g.baseline - g.bottom_limit);
566	}
567
568	public double get_scale (Glyph g) {
569		double s = g.top_limit - g.bottom_limit;
570
571		if (s == 0) {
572			s = cached_font.top_limit - cached_font.bottom_limit;
573		}
574
575		return font_size / s;
576	}
577
578	public double get_font_scale () {
579		return font_size / (cached_font.top_limit - cached_font.bottom_limit);
580	}
581
582	public double get_baseline_to_bottom_for_font () {
583		return get_font_scale () * (-cached_font.base_line - cached_font.bottom_limit);
584	}
585
586	public void truncate (double max_width) {
587		truncated_width = max_width;
588	}
589}
590
591}
592