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.
Fix memory leak
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