.
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 glyph = new Glyph ("", '\0');
135
136 w = 0;
137 prev = null;
138 kern = 0;
139
140 word = glyph_sequence;
141 wi = 0;
142
143 word_with_ligatures = word.process_ligatures ();
144
145 gr_left = null;
146 gr_right = null;
147 kc = font.get_kerning_classes ();
148 for (int i = 0; i < word_with_ligatures.glyph.size; i++) {
149
150 g = word_with_ligatures.glyph.get (i);
151
152 if (g == null || prev == null || wi == 0) {
153 kern = 0;
154 } else {
155 return_if_fail (wi < word_with_ligatures.ranges.size);
156 return_if_fail (wi - 1 >= 0);
157
158 gr_left = word_with_ligatures.ranges.get (wi - 1);
159 gr_right = word_with_ligatures.ranges.get (wi);
160
161 kern = kc.get_kerning_for_pair (((!) prev).get_name (), ((!) g).get_name (), gr_left, gr_right);
162 }
163
164 // process glyph
165 glyph = (g == null) ? font.get_not_def_character ().get_current () : (!) g;
166 iter (glyph, kern, i + 1 == word_with_ligatures.glyph.size);
167
168 prev = g;
169 wi++;
170 }
171 }
172
173 // FIXME: some fonts doesn't have on curve extrema
174 public double get_extent () {
175 double x = 0;
176 double ratio = get_scale ();
177
178 iterate ((glyph, kerning, last) => {
179 double x1, y1, x2, y2;
180 double lsb;
181
182 lsb = glyph.left_limit;
183
184 if (!last) {
185 x += (glyph.get_width () + kerning) * ratio;
186 } else {
187 glyph.boundaries (out x1, out y1, out x2, out y2);
188 x += (x2 - lsb) * ratio;
189 }
190 });
191
192 return x;
193 }
194
195 public double get_sidebearing_extent () {
196 double x ;
197 double ratio;
198
199 if (likely (sidebearing_extent > 0)) {
200 return sidebearing_extent;
201 }
202
203 x = 0;
204 ratio = get_scale ();
205
206 if (unlikely (ratio == 0)) {
207 warning ("No scale.");
208 }
209
210 iterate ((glyph, kerning, last) => {
211 double lsb;
212 lsb = glyph.left_limit;
213 x += (glyph.get_width () + kerning) * ratio;
214 });
215
216 sidebearing_extent = x;
217 return x;
218 }
219
220 public override double get_height () {
221 return font_size;
222 }
223
224 public double get_acender () {
225 double ratio = get_scale ();
226 double max_height = 0;
227
228 iterate ((glyph, kerning, last) => {
229 double x1, y1, x2, y2;
230 double h;
231 glyph.boundaries (out x1, out y1, out x2, out y2);
232 h = Math.fmax (y1, y2) - Math.fmin (y1, y2) ;
233 if (h > max_height) {
234 max_height = h;
235 }
236 });
237
238 return max_height * ratio - font.base_line * ratio;
239 }
240
241 public override double get_width () {
242 double x = 0;
243 double ratio = get_scale ();
244 bool first = true;
245
246 iterate ((glyph, kerning, last) => {
247 double x1, y1, x2, y2;
248 double lsb;
249
250 lsb = glyph.left_limit;
251
252 if (first) {
253 glyph.boundaries (out x1, out y1, out x2, out y2);
254 x += (glyph.get_width () + kerning - Math.fmin (x1, x2)) * ratio;
255 first = false;
256 } else if (!last) {
257 x += (glyph.get_width () + kerning) * ratio;
258 } else {
259 glyph.boundaries (out x1, out y1, out x2, out y2);
260 x += (x2 - lsb) * ratio;
261 }
262 });
263
264 return x;
265 }
266
267 public double get_decender () {
268 double ratio = get_scale ();
269 double min_y = 0;
270 double decender;
271
272 iterate ((glyph, kerning, last) => {
273 double x1, y1, x2, y2;
274 double y;
275 glyph.boundaries (out x1, out y1, out x2, out y2);
276 y = Math.fmin (y1, y2);
277 if (y < min_y) {
278 min_y = y;
279 }
280 });
281
282 decender = font.base_line * ratio - min_y * ratio;
283 return decender > 0 ? decender : 0;
284 }
285
286 public bool load_font (string file) {
287 Font? f = font_cache.get_font (file);
288
289 if (f != null) {
290 font = (!) f;
291 }
292
293 return f != null;
294 }
295
296 public override void draw (Context cr) {
297 double y = widget_y + get_height () + get_scale () * (font.bottom_limit + font.base_line);
298 draw_at_baseline (cr, widget_x, y);
299 }
300
301 public void draw_at_top (Context cr, double px, double py, int64 cacheid = -1) {
302 double s = get_scale ();
303 double y = py + s * (font.top_limit - font.base_line);
304 draw_at_baseline (cr, px, y, cacheid);
305 }
306
307 public void set_source_rgba (double r, double g, double b, double a) {
308 this.r = r;
309 this.g = g;
310 this.b = b;
311 this.a = a;
312 }
313
314 public int64 get_cache_id () {
315 int64 s = (((int64) font_size) << 32)
316 | (((int64) (r * 255)) << 24)
317 | (((int64) (g * 255)) << 16)
318 | (((int64) (b * 255)) << 8)
319 | (((int64) (a * 255)) << 0);
320 return s;
321 }
322
323 public void draw_at_baseline (Context cr, double px, double py, int64 cacheid = -1) {
324 double x, y;
325 double ratio;
326 double cc_y;
327 int64 cache_id = (cacheid < 0) ? get_cache_id () : cacheid;
328
329 ratio = get_scale ();
330 cc_y = (font.top_limit - font.base_line) * ratio;
331
332 y = py;
333 x = px;
334
335 iterate ((glyph, kerning, last) => {
336 double lsb;
337 Surface cache;
338 Context cc;
339
340 if (unlikely (!glyph.has_cache (cache_id))) {
341 cache = new Surface.similar (cr.get_target (), Cairo.Content.COLOR_ALPHA, (int) (glyph.get_width () * ratio) + 1, (int) font_size + 1);
342 cc = new Context (cache);
343
344 lsb = glyph.left_limit;
345
346 cc.save ();
347 cc.set_source_rgba (r, g, b, a);
348 cc.new_path ();
349
350 foreach (Path path in glyph.path_list) {
351 draw_path (cc, path, lsb, 0, cc_y, ratio);
352 }
353
354 cc.fill ();
355 cc.restore ();
356
357 glyph.set_cache (cache_id, cache);
358 }
359
360 x += kerning * ratio;
361 cr.set_source_surface (glyph.get_cache (cache_id), x, y - cc_y);
362 x += glyph.get_width () * ratio;
363
364 cr.paint ();
365 });
366 }
367
368 void draw_path (Context cr, Path path, double lsb, double x, double y, double scale) {
369 EditPoint e, prev;
370 double xa, ya, xb, yb, xc, yc, xd, yd;
371 double by;
372
373 if (path.points.size > 0) {
374
375 prev = path.points.get (0);
376 xa = (prev.x - lsb) * scale + x;
377 ya = y - prev.y * scale;
378 cr.move_to (xa, ya);
379
380 by = (y - font.base_line * scale);
381 for (int i = 1; i < path.points.size; i++) {
382 e = path.points.get (i).copy ();
383 PenTool.convert_point_segment_type (prev, e, PointType.CUBIC);
384
385 xb = (prev.get_right_handle ().x - lsb) * scale + x;
386 yb = by - prev.get_right_handle ().y * scale;
387
388 xc = (e.get_left_handle ().x - lsb) * scale + x;
389 yc = by - e.get_left_handle ().y * scale;
390
391 xd = (e.x - lsb) * scale + x;
392 yd = by - e.y * scale;
393
394 cr.curve_to (xb, yb, xc, yc, xd, yd);
395 cr.line_to (xd, yd);
396
397 prev = e;
398 }
399
400 // close path
401 e = path.points.get (0);
402
403 xb = (prev.get_right_handle ().x - lsb) * scale + x;
404 yb = by - prev.get_right_handle ().y * scale;
405
406 xc = (e.get_left_handle ().x - lsb) * scale + x;
407 yc = by - e.get_left_handle ().y * scale;
408
409 xd = (e.x - lsb) * scale + x;
410 yd = by - e.y * scale;
411
412 cr.curve_to (xb, yb, xc, yc, xd, yd);
413 }
414 }
415
416 public double get_baseline_to_bottom () {
417 return get_scale () * (-font.base_line - font.bottom_limit);
418 }
419
420 public double get_scale () {
421 return font_size / (font.top_limit - font.bottom_limit);
422 }
423 }
424
425 }
426