The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

Text.vala in libbirdfont/Renderer

This file is a part of the Birdfont project.

Contributing

Send patches or pull requests to johan.mattsson.m@gmail.com.
Clone this repository: git clone https://github.com/johanmattssonm/birdfont.git

Revisions

View the latest version of libbirdfont/Renderer/Text.vala.
Thread safety in text rendering
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