.
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
15 using Cairo;
16
17 namespace BirdFont {
18
19 /** Test implementation of a birdfont rendering engine. */
20 public class Text : Widget {
21 FontCache font_cache;
22 public FontCache.CachedFont cached_font;
23
24 public string text;
25
26 GlyphSequence glyph_sequence {
27 get {
28 if (gs == null) {
29 gs = generate_glyphs ();
30 }
31
32 return (!) gs;
33 }
34 }
35
36 Gee.ArrayList<string> glyph_names;
37 GlyphSequence? gs = null;
38
39 public delegate void Iterator (Glyph glyph, double kerning, bool last);
40 public double font_size;
41 public double sidebearing_extent = 0;
42
43 double r = 0;
44 double g = 0;
45 double b = 0;
46 double a = 1;
47
48 bool use_cached_glyphs = true;
49 double truncated_width = -1;
50
51 static int ntext = 0;
52
53 public Text (string text = "", double size = 17, double margin_bottom = 0) {
54 this.margin_bottom = margin_bottom;
55 font_cache = FontCache.get_default_cache ();
56 cached_font = font_cache.get_fallback ();
57
58 set_font_size (size);
59 set_text (text);
60
61 warning (@"ntext: $(ntext)");
62 ntext++;
63 }
64
65 ~Text () {
66 ntext--;
67 warning (@"remove ntext: $(ntext)");
68 }
69
70 public void use_cache (bool cache) {
71 use_cached_glyphs = cache;
72 }
73
74 /** Set font for this text area.
75 * @param font_absolute path to the font file or a file name for one of the font files in search paths.
76 * @return true if the font was found
77 */
78 public bool load_font (string font_file) {
79 File path;
80 File f;
81
82 f = File.new_for_path (font_file);
83 path = (f.query_exists ()) ? f : SearchPaths.find_file (null, font_file);
84
85 cached_font = FontCache.get_default_cache ().get_font ((!) path.get_path ());
86
87 return cached_font.font != null;
88 }
89
90 public void set_font_size (double height_in_pixels) {
91 font_size = height_in_pixels;
92 sidebearing_extent = 0;
93 }
94
95 public void set_font_cache (FontCache font_cache) {
96 this.font_cache = font_cache;
97 }
98
99 public void set_text (string text) {
100 this.text = text;
101 gs = null;
102 sidebearing_extent = 0;
103 }
104
105 private GlyphSequence generate_glyphs () {
106 int index;
107 unichar c;
108 string name;
109 Glyph? g;
110 GlyphSequence gs;
111
112 gs = new GlyphSequence ();
113
114 glyph_names = new Gee.ArrayList<string> ();
115 index = 0;
116 while (text.get_next_char (ref index, out c)) {
117 name = (!) c.to_string ();
118 g = cached_font.get_glyph_by_name (name);
119 gs.glyph.add (g);
120 glyph_names.add (name);
121 }
122
123 return gs;
124 }
125
126 public void iterate (Iterator iter) {
127 Glyph glyph;
128 double w, kern;
129 int wi;
130 Glyph? prev;
131 Glyph? g;
132 GlyphSequence word_with_ligatures;
133 GlyphRange? gr_left, gr_right;
134 GlyphSequence word;
135 KerningClasses kc;
136
137 glyph = new Glyph.no_lines ("", '\0');
138
139 w = 0;
140 prev = null;
141 kern = 0;
142
143 word = glyph_sequence;
144 wi = 0;
145
146 if (cached_font.font != null) {
147 word_with_ligatures = word.process_ligatures ((!) cached_font.font);
148 } else {
149 word_with_ligatures = word.process_ligatures (new Font ());
150 }
151
152 gr_left = null;
153 gr_right = null;
154
155 if (cached_font.font != null) {
156 kc = ((!) cached_font.font).get_kerning_classes ();
157 } else {
158 kc = new KerningClasses (new Font ());
159 }
160
161 for (int i = 0; i < word_with_ligatures.glyph.size; i++) {
162 g = word_with_ligatures.glyph.get (i);
163
164 if (g == null || prev == null || wi == 0) {
165 kern = 0;
166 } else {
167 return_if_fail (wi < word_with_ligatures.ranges.size);
168 return_if_fail (wi - 1 >= 0);
169
170 gr_left = word_with_ligatures.ranges.get (wi - 1);
171 gr_right = word_with_ligatures.ranges.get (wi);
172
173 kern = kc.get_kerning_for_pair (((!) prev).get_name (), ((!) g).get_name (), gr_left, gr_right);
174 }
175
176 // process glyph
177 if (g == null) {
178 g = cached_font.get_glyph_by_name (glyph_names.get (i));
179 }
180
181 glyph = (g == null) ? cached_font.get_not_def_character ().get_current () : (!) g;
182 iter (glyph, kern, i + 1 == word_with_ligatures.glyph.size);
183 prev = g;
184 wi++;
185 }
186 }
187
188 // FIXME: some fonts doesn't have on curve extrema
189 public double get_extent () {
190 double x = 0;
191 double ratio = get_scale ();
192
193 iterate ((glyph, kerning, last) => {
194 double x1, y1, x2, y2;
195 double lsb;
196
197 lsb = glyph.left_limit;
198
199 if (!last) {
200 x += (glyph.get_width () + kerning) * ratio;
201 } else {
202 glyph.boundaries (out x1, out y1, out x2, out y2);
203 x += (x2 - lsb) * ratio;
204 }
205 });
206
207 return x;
208 }
209
210 public double get_sidebearing_extent () {
211 double x ;
212 double ratio;
213
214 if (likely (sidebearing_extent > 0)) {
215 return sidebearing_extent;
216 }
217
218 x = 0;
219 ratio = get_scale ();
220
221 if (unlikely (ratio == 0)) {
222 warning ("No scale.");
223 }
224
225 iterate ((glyph, kerning, last) => {
226 double lsb;
227 lsb = glyph.left_limit;
228 x += (glyph.get_width () + kerning) * ratio;
229 });
230
231 sidebearing_extent = x;
232 return x;
233 }
234
235 public override double get_height () {
236 return font_size;
237 }
238
239 public double get_acender () {
240 double ratio = get_scale ();
241 double max_height = 0;
242
243 iterate ((glyph, kerning, last) => {
244 double x1, y1, x2, y2;
245 double h;
246 glyph.boundaries (out x1, out y1, out x2, out y2);
247 h = Math.fmax (y1, y2) - Math.fmin (y1, y2) ;
248 if (h > max_height) {
249 max_height = h;
250 }
251 });
252
253 return max_height * ratio - cached_font.base_line * ratio;
254 }
255
256 public override double get_width () {
257 double x = 0;
258 double ratio = get_scale ();
259 bool first = true;
260
261 iterate ((glyph, kerning, last) => {
262 double x1, y1, x2, y2;
263 double lsb;
264
265 lsb = glyph.left_limit;
266
267 if (first) {
268 glyph.boundaries (out x1, out y1, out x2, out y2);
269 x += (glyph.get_width () + kerning - Math.fmin (x1, x2)) * ratio;
270 first = false;
271 } else if (!last) {
272 x += (glyph.get_width () + kerning) * ratio;
273 } else {
274 glyph.boundaries (out x1, out y1, out x2, out y2);
275 x += (x2 - lsb) * ratio;
276 }
277 });
278
279 return x;
280 }
281
282 public double get_decender () {
283 double ratio = get_scale ();
284 double min_y = 0;
285 double decender;
286
287 iterate ((glyph, kerning, last) => {
288 double x1, y1, x2, y2;
289 double y;
290 glyph.boundaries (out x1, out y1, out x2, out y2);
291 y = Math.fmin (y1, y2);
292 if (y < min_y) {
293 min_y = y;
294 }
295 });
296
297 decender = cached_font.base_line * ratio - min_y * ratio;
298 return decender > 0 ? decender : 0;
299 }
300
301 public override void draw (Context cr) {
302 double y = widget_y + get_height () + get_scale () * (cached_font.bottom_limit + cached_font.base_line);
303 draw_at_baseline (cr, widget_x, y);
304 }
305
306 public void draw_at_top (Context cr, double px, double py, string cacheid = "") {
307 double s = get_scale ();
308 double y = py + s * (cached_font.top_limit - cached_font.base_line);
309 draw_at_baseline (cr, px, y, cacheid);
310 }
311
312 public void set_source_rgba (double r, double g, double b, double a) {
313 this.r = r;
314 this.g = g;
315 this.b = b;
316 this.a = a;
317 }
318
319 public string get_cache_id (int offset_x, int offset_y) {
320 string key;
321 int64 c;
322
323 c = (((int64) (r * 255)) << 24)
324 | (((int64) (g * 255)) << 16)
325 | (((int64) (b * 255)) << 8)
326 | (((int64) (a * 255)) << 0);
327
328 // FIXME: use binary key
329 key = @"$font_size $c $offset_x $offset_y";
330
331 return key;
332 }
333
334 public void draw_at_baseline (Context cr, double px, double py, string cacheid = "") {
335 double x, y;
336 double ratio;
337 double cc_y;
338
339 ratio = get_scale ();
340 cc_y = (cached_font.top_limit - cached_font.base_line) * ratio;
341
342 y = py;
343 x = px;
344
345 if (use_cached_glyphs) {
346 iterate ((glyph, kerning, last) => {
347 double end;
348
349 x += kerning * ratio;
350 end = x + glyph.get_width () * ratio;
351
352 // truncation
353 if (truncated_width > 0 && end - px > truncated_width) {
354 return;
355 }
356
357 draw_chached (cr, glyph, kerning, last, x, y, cc_y,
358 ratio, cacheid);
359
360 x = end;
361 });
362 } else {
363 iterate ((glyph, kerning, last) => {
364 double end;
365
366 x += kerning * ratio;
367 end = x + glyph.get_width () * ratio;
368
369 // truncation
370 if (truncated_width > 0 && end - px > truncated_width) {
371 return;
372 }
373
374 draw_without_cache (cr, glyph, kerning, last, x, y, cc_y, ratio);
375 x = end;
376 });
377 }
378 }
379
380 void draw_without_cache (Context cr, Glyph glyph, double kerning, bool last,
381 double x, double y, double cc_y, double ratio) {
382
383 double lsb;
384
385 cr.save ();
386 cr.set_source_rgba (r, g, b, a);
387 cr.new_path ();
388
389 lsb = glyph.left_limit;
390
391 foreach (Path path in glyph.path_list) {
392 draw_path (cr, path, lsb, x, y, ratio);
393 }
394
395 cr.fill ();
396 cr.restore ();
397
398 }
399
400 void draw_chached (Context cr, Glyph glyph, double kerning, bool last,
401 double x, double y, double cc_y, double ratio,
402 string cacheid = "") {
403
404 double lsb;
405 Surface cache;
406 Context cc;
407 string cache_id;
408 double xp = x;
409 double yp = y - cc_y;
410 int offset_x, offset_y;
411
412 offset_x = (int) (10 * (xp - (int) xp));
413 offset_y = (int) (10 * (yp - (int) yp));
414
415 cache_id = (cacheid == "") ? get_cache_id (offset_x, offset_y) : cacheid;
416
417 if (unlikely (!glyph.has_cache (cache_id))) {
418 cache = new Surface.similar (cr.get_target (), Cairo.Content.COLOR_ALPHA, (int) (glyph.get_width () * ratio) + 2, (int) font_size + 2);
419 cc = new Context (cache);
420
421 lsb = glyph.left_limit;
422
423 cc.save ();
424 cc.set_source_rgba (r, g, b, a);
425 cc.new_path ();
426
427 foreach (Path path in glyph.path_list) {
428 draw_path (cc, path, lsb, offset_x / 10.0, cc_y + offset_y / 10.0, ratio);
429 }
430
431 cc.fill ();
432 cc.restore ();
433
434 glyph.set_cache (cache_id, cache);
435 }
436
437 cr.save ();
438 cr.set_antialias (Cairo.Antialias.NONE);
439 cr.set_source_surface (glyph.get_cache (cache_id), (int) xp, (int) yp);
440 cr.paint ();
441 cr.restore ();
442 }
443
444 void draw_path (Context cr, Path path, double lsb, double x, double y, double scale) {
445 EditPoint e, prev;
446 double xa, ya, xb, yb, xc, yc, xd, yd;
447 double by;
448
449 if (path.points.size > 0) {
450 path.add_hidden_double_points ();
451
452 prev = path.points.get (path.points.size - 1);
453 xa = (prev.x - lsb) * scale + x;
454 ya = y - prev.y * scale;
455 cr.move_to (xa, ya);
456
457 by = (y - cached_font.base_line * scale);
458 for (int i = 0; i < path.points.size; i++) {
459 e = path.points.get (i).copy ();
460 PenTool.convert_point_segment_type (prev, e, PointType.CUBIC);
461
462 xb = (prev.get_right_handle ().x - lsb) * scale + x;
463 yb = by - prev.get_right_handle ().y * scale;
464
465 xc = (e.get_left_handle ().x - lsb) * scale + x;
466 yc = by - e.get_left_handle ().y * scale;
467
468 xd = (e.x - lsb) * scale + x;
469 yd = by - e.y * scale;
470
471 cr.curve_to (xb, yb, xc, yc, xd, yd);
472 cr.line_to (xd, yd);
473
474 prev = e;
475 }
476 }
477 }
478
479 public double get_baseline_to_bottom () {
480 return get_scale () * (-cached_font.base_line - cached_font.bottom_limit);
481 }
482
483 public double get_scale () {
484 return font_size / (cached_font.top_limit - cached_font.bottom_limit);
485 }
486
487 public void truncate (double max_width) {
488 truncated_width = max_width;
489 }
490 }
491
492 }
493