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