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 text alignment to pixel grid
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 CachedFont cached_font; 23 24 Surface? cache = null; 25 26 public string text; 27 28 GlyphSequence glyph_sequence { 29 get { 30 if (gs == null) { 31 gs = generate_glyphs (); 32 } 33 34 return (!) gs; 35 } 36 } 37 38 Gee.ArrayList<string> glyph_names; 39 GlyphSequence? gs = null; 40 41 public delegate void Iterator (Glyph glyph, double kerning, bool last); 42 public double font_size; 43 double sidebearing_extent = 0; 44 45 public double r = 0; 46 public double g = 0; 47 public double b = 0; 48 public double a = 1; 49 double truncated_width = -1; 50 51 public Text (string text = "", double size = 17, double margin_bottom = 0) { 52 this.margin_bottom = margin_bottom; 53 font_cache = FontCache.get_default_cache (); 54 cached_font = font_cache.get_fallback (); 55 56 set_text (text); 57 set_font_size (size); 58 } 59 60 public string get_text () { 61 return text; 62 } 63 64 /** Set font for this text area. 65 * @param font_absolute path to the font file or a file name for one of the font files in search paths. 66 * @return true if the font was found 67 */ 68 public bool load_font (string font_file) { 69 File path; 70 File f; 71 FontCache fc; 72 73 f = File.new_for_path (font_file); 74 path = (f.query_exists ()) ? f : SearchPaths.find_file (null, font_file); 75 76 fc = FontCache.get_default_cache (); 77 cached_font = fc.get_font ((!) path.get_path ()); 78 gs = generate_glyphs (); 79 80 return cached_font.font != null; 81 } 82 83 public void set_font_size (double height_in_pixels) { 84 font_size = height_in_pixels; 85 sidebearing_extent = 0; 86 87 if (gs == null) { // ensure height is loaded for the font 88 gs = generate_glyphs (); 89 } 90 } 91 92 public void set_font_cache (FontCache font_cache) { 93 this.font_cache = font_cache; 94 } 95 96 public void set_text (string text) { 97 this.text = text; 98 gs = null; 99 sidebearing_extent = 0; 100 } 101 102 private GlyphSequence generate_glyphs () { 103 int index; 104 unichar c; 105 string name; 106 Glyph? g; 107 GlyphSequence gs; 108 109 gs = new GlyphSequence (); 110 111 glyph_names = new Gee.ArrayList<string> (); 112 index = 0; 113 while (text.get_next_char (ref index, out c)) { 114 name = (!) c.to_string (); 115 g = cached_font.get_glyph_by_name (name); 116 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 Font empty = Font.empty; 135 136 glyph = new Glyph.no_lines ("", '\0'); 137 138 w = 0; 139 prev = null; 140 kern = 0; 141 142 word = glyph_sequence; 143 wi = 0; 144 145 if (cached_font.font != null) { 146 word_with_ligatures = word.process_ligatures ((!) cached_font.font); 147 } else { 148 word_with_ligatures = word.process_ligatures (new Font ()); 149 } 150 151 gr_left = null; 152 gr_right = null; 153 154 if (cached_font.font != null) { 155 kc = ((!) cached_font.font).get_kerning_classes (); 156 } else { 157 kc = new KerningClasses (empty); 158 } 159 160 for (int i = 0; i < word_with_ligatures.glyph.size; i++) { 161 g = word_with_ligatures.glyph.get (i); 162 163 if (g == null || prev == null || wi == 0) { 164 kern = 0; 165 } else { 166 return_if_fail (wi < word_with_ligatures.ranges.size); 167 return_if_fail (wi - 1 >= 0); 168 169 gr_left = word_with_ligatures.ranges.get (wi - 1); 170 gr_right = word_with_ligatures.ranges.get (wi); 171 172 kern = kc.get_kerning_for_pair (((!) prev).get_name (), ((!) g).get_name (), gr_left, gr_right); 173 } 174 175 // process glyph 176 if (g == null && (0 <= i < glyph_names.size)) { 177 g = cached_font.get_glyph_by_name (glyph_names.get (i)); 178 } 179 180 glyph = (g == null) ? new Glyph ("") : (!) g; 181 iter (glyph, kern, i + 1 == word_with_ligatures.glyph.size); 182 prev = g; 183 wi++; 184 } 185 } 186 187 // FIXME: some fonts doesn't have on curve extrema 188 public double get_extent () { 189 double x = 0; 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) * get_scale (glyph); 199 } else { 200 glyph.boundaries (out x1, out y1, out x2, out y2); 201 x += (x2 - lsb) * get_scale (glyph); 202 } 203 }); 204 205 return x; 206 } 207 208 public double get_sidebearing_extent () { 209 double x ; 210 211 if (likely (sidebearing_extent > 0)) { 212 return sidebearing_extent; 213 } 214 215 x = 0; 216 217 iterate ((glyph, kerning, last) => { 218 double lsb; 219 lsb = glyph.left_limit; 220 x += (glyph.get_width () + kerning) * get_scale (glyph); 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 max_height = 0; 233 234 iterate ((glyph, kerning, last) => { 235 double x1, y1, x2, y2; 236 double h; 237 glyph.boundaries (out x1, out y1, out x2, out y2); 238 h = Math.fmax (y1, y2) - Math.fmin (y1, y2); 239 h *= get_scale(glyph) - glyph.baseline * get_scale(glyph); 240 if (h > max_height) { 241 max_height = h; 242 } 243 }); 244 245 return max_height; 246 } 247 248 public override double get_width () { 249 double x = 0; 250 bool first = true; 251 252 iterate ((glyph, kerning, last) => { 253 double x1, y1, x2, y2; 254 double lsb; 255 256 lsb = glyph.left_limit; 257 258 if (first) { 259 glyph.boundaries (out x1, out y1, out x2, out y2); 260 x += (glyph.get_width () + kerning - Math.fmin (x1, x2)) * get_scale (glyph); 261 first = false; 262 } else if (!last) { 263 x += (glyph.get_width () + kerning) * get_scale (glyph); 264 } else { 265 glyph.boundaries (out x1, out y1, out x2, out y2); 266 x += (x2 - lsb) * get_scale (glyph); 267 } 268 }); 269 270 return x; 271 } 272 273 public double get_decender () { 274 double decender_max = get_max_decender (); 275 return decender_max > 0 ? decender_max : 0; 276 } 277 278 private double get_max_decender () { 279 double decender = 0; 280 double decender_max = 0; 281 282 iterate ((glyph, kerning, last) => { 283 double x1, y1, x2, y2; 284 double y; 285 glyph.boundaries (out x1, out y1, out x2, out y2); 286 y = Math.fmin (y1, y2); 287 decender = (glyph.baseline - y) * get_scale (glyph); 288 if (decender > decender_max) { 289 decender_max = decender; 290 } 291 }); 292 293 return decender_max; 294 } 295 296 public override void draw (Context cr) { 297 double descender = cached_font.bottom_limit + cached_font.base_line; 298 double y = widget_y + get_height () + get_font_scale () * descender; // FIXME: 299 draw_at_baseline (cr, widget_x, y); 300 } 301 302 public void draw_at_top (Context cr, double px, double py, string cacheid = "") { 303 double s = get_font_scale (); 304 double y = py + s * (cached_font.top_limit - cached_font.base_line); 305 draw_at_baseline (cr, px, y, cacheid); 306 } 307 308 public void set_source_rgba (double r, double g, double b, double a) { 309 this.r = r; 310 this.g = g; 311 this.b = b; 312 this.a = a; 313 } 314 315 public string get_cache_id (int offset_x, int offset_y) { 316 string key; 317 int64 c; 318 319 c = (((int64) (r * 255)) << 24) 320 | (((int64) (g * 255)) << 16) 321 | (((int64) (b * 255)) << 8) 322 | (((int64) (a * 255)) << 0); 323 324 // FIXME: use binary key 325 key = @"$font_size $c $offset_x $offset_y"; 326 327 return key; 328 } 329 330 public void draw_at_baseline (Context cr, double px, double py, string cacheid = "") { 331 double x, y; 332 double ratio; 333 double cc_y; 334 335 if (cache == null) { 336 cache = draw_on_cache_surface (cacheid); 337 } 338 339 double s = get_font_scale (); 340 double cache_y = py - s * (cached_font.top_limit - cached_font.base_line); 341 cr.set_source_surface ((!) cache, (int) px, (int) cache_y); 342 cr.paint (); 343 } 344 345 Surface draw_on_cache_surface (string cacheid) { 346 double x, y; 347 double ratio; 348 double cc_y; 349 Context cr; 350 Surface cache_surface; 351 double screen_scale = Screen.get_scale(); 352 double h = font_size * screen_scale + 1; 353 double w = get_sidebearing_extent () * screen_scale + 1; 354 355 cache_surface = Screen.create_background_surface ((int) w, (int) h); 356 cr = new Context (cache_surface); 357 cr.scale(screen_scale, screen_scale); 358 359 ratio = get_font_scale (); 360 cc_y = (cached_font.top_limit - cached_font.base_line) * ratio; 361 362 double px = 0; 363 double py = cc_y; 364 365 y = cc_y; 366 x = 0; 367 368 if (unlikely (cached_font.base_line != 0)) { 369 warning ("Base line not zero."); 370 } 371 372 iterate ((glyph, kerning, last) => { 373 double end; 374 375 x += kerning * ratio; 376 end = x + glyph.get_width () * ratio; 377 378 // truncation 379 if (truncated_width > 0 && end - px > truncated_width) { 380 return; 381 } 382 383 draw_chached (cr, glyph, kerning, last, x, y, cc_y, 384 ratio, cacheid); 385 386 x = end; 387 }); 388 389 return cache_surface; 390 } 391 392 void draw_without_cache (Context cr, Glyph glyph, double kerning, bool last, 393 double x, double y, double cc_y, double ratio) { 394 395 double lsb; 396 397 cr.save (); 398 cr.set_source_rgba (r, g, b, a); 399 cr.new_path (); 400 401 lsb = glyph.left_limit; 402 403 foreach (Path path in glyph.get_visible_paths ()) { 404 draw_path (cr, glyph, path, lsb, x, y, ratio); 405 } 406 407 cr.fill (); 408 cr.restore (); 409 410 } 411 412 void draw_chached (Context cr, Glyph glyph, double kerning, bool last, 413 double x, double y, double cc_y, double ratio, 414 string cacheid = "") { 415 416 double lsb; 417 Surface cache; 418 Context cc; 419 string cache_id; 420 double xp = x; 421 double yp = y - cc_y; 422 int offset_x, offset_y; 423 424 offset_x = (int) (10 * (xp - (int) xp)); 425 offset_y = (int) (10 * (yp - (int) yp)); 426 427 cache_id = (cacheid == "") ? get_cache_id (offset_x, offset_y) : cacheid; 428 429 if (unlikely (!glyph.has_cache (cache_id))) { 430 int w = (int) (glyph.get_width () * ratio) + 2; 431 int h = (int) font_size + 2; 432 cache = Screen.create_background_surface (w, h); 433 cc = new Context (cache); 434 435 cc.scale(Screen.get_scale (), Screen.get_scale ()); 436 437 lsb = glyph.left_limit; 438 439 cc.save (); 440 cc.set_source_rgba (r, g, b, a); 441 cc.new_path (); 442 443 foreach (Path path in glyph.get_visible_paths ()) { 444 draw_path (cc, glyph, path, lsb, offset_x / 10.0, cc_y + offset_y / 10.0, ratio); 445 } 446 447 cc.fill (); 448 cc.restore (); 449 450 glyph.set_cache (cache_id, cache); 451 } 452 453 cr.save (); 454 cr.set_antialias (Cairo.Antialias.NONE); 455 cr.scale(1 / Screen.get_scale (), 1 / Screen.get_scale ()); 456 cr.set_source_surface (glyph.get_cache (cache_id), 457 (int) (xp * Screen.get_scale ()), 458 (int) (yp * Screen.get_scale ())); 459 cr.paint (); 460 cr.restore (); 461 } 462 463 void draw_path (Context cr, Glyph glyph, Path path, 464 double lsb, double x, double y, double scale) { 465 466 EditPoint e, prev; 467 double xa, ya, xb, yb, xc, yc, xd, yd; 468 double by; 469 double s = get_scale (glyph); 470 471 if (path.points.size > 0) { 472 if (unlikely (path.is_open ())) { 473 warning (@"Path is open in $(glyph.get_name ())."); 474 } 475 476 //path.add_hidden_double_points (); // FIXME: this distorts shapes 477 478 prev = path.points.get (path.points.size - 1); 479 xa = (prev.x - lsb) * s + x; 480 ya = y - prev.y * s; 481 cr.move_to (xa, ya); 482 483 by = (y - cached_font.base_line * s); 484 for (int i = 0; i < path.points.size; i++) { 485 e = path.points.get (i).copy (); 486 487 PenTool.convert_point_segment_type (prev, e, PointType.CUBIC); 488 489 xb = (prev.get_right_handle ().x - lsb) * s + x; 490 yb = by - prev.get_right_handle ().y * s; 491 492 xc = (e.get_left_handle ().x - lsb) * s + x; 493 yc = by - e.get_left_handle ().y * s; 494 495 xd = (e.x - lsb) * s + x; 496 yd = by - e.y * s; 497 498 cr.curve_to (xb, yb, xc, yc, xd, yd); 499 cr.line_to (xd, yd); 500 501 prev = e; 502 } 503 } 504 } 505 506 public double get_baseline_to_bottom (Glyph g) { 507 return get_scale (g) * (-g.baseline - g.bottom_limit); 508 } 509 510 public double get_scale (Glyph g) { 511 double s = g.top_limit - g.bottom_limit; 512 513 if (s == 0) { 514 s = cached_font.top_limit - cached_font.bottom_limit; 515 } 516 517 return font_size / s; 518 } 519 520 public double get_font_scale () { 521 return font_size / (cached_font.top_limit - cached_font.bottom_limit); 522 } 523 524 public double get_baseline_to_bottom_for_font () { 525 return get_font_scale () * (-cached_font.base_line - cached_font.bottom_limit); 526 } 527 528 public void truncate (double max_width) { 529 truncated_width = max_width; 530 } 531 } 532 533 } 534