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