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