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