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.
Draw glyphs with negative lsb
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 GlyphSequence glyph_sequence { 30 get { 31 if (gs == null) { 32 gs = generate_glyphs (); 33 } 34 35 return (!) gs; 36 } 37 } 38 39 Gee.ArrayList<string> glyph_names; 40 GlyphSequence? gs = null; 41 42 public delegate void Iterator (Glyph glyph, double kerning, bool last); 43 public double font_size; 44 double sidebearing_extent = 0; 45 46 public double r = 0; 47 public double g = 0; 48 public double b = 0; 49 public double a = 1; 50 double truncated_width = -1; 51 52 public Text (string text = "", double size = 17, double margin_bottom = 0) { 53 this.margin_bottom = margin_bottom; 54 font_cache = FontCache.get_default_cache (); 55 cached_font = font_cache.get_fallback (); 56 57 set_text (text); 58 set_font_size (size); 59 } 60 61 public string get_text () { 62 return text; 63 } 64 65 /** Set font for this text area. 66 * @param font_absolute path to the font file or a file name for one of the font files in search paths. 67 * @return true if the font was found 68 */ 69 public bool load_font (string font_file) { 70 File path; 71 File f; 72 FontCache fc; 73 74 f = File.new_for_path (font_file); 75 path = (f.query_exists ()) ? f : SearchPaths.find_file (null, font_file); 76 77 fc = FontCache.get_default_cache (); 78 cached_font = fc.get_font ((!) path.get_path ()); 79 gs = generate_glyphs (); 80 81 return cached_font.font != null; 82 } 83 84 public void set_font_size (double height_in_pixels) { 85 font_size = height_in_pixels; 86 sidebearing_extent = 0; 87 88 if (gs == null) { // ensure height is loaded for the font 89 gs = generate_glyphs (); 90 } 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 118 gs.glyph.add (g); 119 glyph_names.add (name); 120 } 121 122 return gs; 123 } 124 125 public void iterate (Iterator iter) { 126 Glyph glyph; 127 double w, kern; 128 int wi; 129 Glyph? prev; 130 Glyph? g; 131 GlyphSequence word_with_ligatures; 132 GlyphRange? gr_left, gr_right; 133 GlyphSequence word; 134 KerningClasses kc; 135 Font empty = Font.empty; 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 (empty); 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 && (0 <= i < glyph_names.size)) { 178 g = cached_font.get_glyph_by_name (glyph_names.get (i)); 179 } 180 181 glyph = (g == null) ? new Glyph ("") : (!) 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 192 iterate ((glyph, kerning, last) => { 193 double x1, y1, x2, y2; 194 double lsb; 195 196 lsb = glyph.left_limit; 197 198 if (!last) { 199 x += (glyph.get_width () + kerning) * get_scale (glyph); 200 } else { 201 glyph.boundaries (out x1, out y1, out x2, out y2); 202 x += (x2 - lsb) * get_scale (glyph); 203 } 204 }); 205 206 return x; 207 } 208 209 public double get_sidebearing_extent () { 210 double x ; 211 212 if (likely (sidebearing_extent > 0)) { 213 return sidebearing_extent; 214 } 215 216 x = 0; 217 218 iterate ((glyph, kerning, last) => { 219 double lsb; 220 lsb = glyph.left_limit; 221 x += (glyph.get_width () + kerning) * get_scale (glyph); 222 }); 223 224 sidebearing_extent = x; 225 return x; 226 } 227 228 public override double get_height () { 229 return font_size; 230 } 231 232 public double get_acender () { 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 h *= get_scale(glyph) - glyph.baseline * get_scale(glyph); 241 if (h > max_height) { 242 max_height = h; 243 } 244 }); 245 246 return max_height; 247 } 248 249 public override double get_width () { 250 double x = 0; 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)) * get_scale (glyph); 262 first = false; 263 } else if (!last) { 264 x += (glyph.get_width () + kerning) * get_scale (glyph); 265 } else { 266 glyph.boundaries (out x1, out y1, out x2, out y2); 267 x += (x2 - lsb) * get_scale (glyph); 268 } 269 }); 270 271 return x; 272 } 273 274 public double get_decender () { 275 double decender_max = get_max_decender (); 276 return decender_max > 0 ? decender_max : 0; 277 } 278 279 private double get_max_decender () { 280 double decender = 0; 281 double decender_max = 0; 282 283 iterate ((glyph, kerning, last) => { 284 double x1, y1, x2, y2; 285 double y; 286 glyph.boundaries (out x1, out y1, out x2, out y2); 287 y = Math.fmin (y1, y2); 288 decender = (glyph.baseline - y) * get_scale (glyph); 289 if (decender > decender_max) { 290 decender_max = decender; 291 } 292 }); 293 294 return decender_max; 295 } 296 297 public override void draw (Context cr) { 298 double descender = cached_font.bottom_limit + cached_font.base_line; 299 double y = widget_y + get_height () + get_font_scale () * descender; // FIXME: 300 draw_at_baseline (cr, widget_x, y); 301 } 302 303 public void draw_at_top (Context cr, double px, double py, string cacheid = "") { 304 double s = get_font_scale (); 305 double y = py + s * (cached_font.top_limit - cached_font.base_line); 306 draw_at_baseline (cr, px, y, cacheid); 307 } 308 309 public void set_source_rgba (double r, double g, double b, double a) { 310 this.r = r; 311 this.g = g; 312 this.b = b; 313 this.a = a; 314 } 315 316 public string get_cache_id (int offset_x, int offset_y) { 317 string key; 318 int64 c; 319 320 c = (((int64) (r * 255)) << 24) 321 | (((int64) (g * 255)) << 16) 322 | (((int64) (b * 255)) << 8) 323 | (((int64) (a * 255)) << 0); 324 325 // FIXME: use binary key 326 key = @"$font_size $c $offset_x $offset_y"; 327 328 return key; 329 } 330 331 public void draw_at_baseline (Context cr, double px, double py, string cacheid = "") { 332 double x, y; 333 double ratio; 334 double cc_y; 335 336 if (cache == null) { 337 cache = draw_on_cache_surface (cacheid); 338 } 339 340 double s = get_font_scale (); 341 double cache_y = py - s * (cached_font.top_limit - cached_font.base_line); 342 cr.set_source_surface ((!) cache, (int) rint (px), (int) rint (cache_y)); 343 cr.paint (); 344 } 345 346 Surface draw_on_cache_surface (string cacheid) { 347 double x, y; 348 double ratio; 349 double cc_y; 350 Context cr; 351 Surface cache_surface; 352 double screen_scale = Screen.get_scale(); 353 double h = font_size * screen_scale + 1; 354 double w = get_sidebearing_extent () * screen_scale + 1; 355 356 cache_surface = Screen.create_background_surface ((int) w, (int) h); 357 cr = new Context (cache_surface); 358 cr.scale(screen_scale, screen_scale); 359 360 ratio = get_font_scale (); 361 cc_y = (cached_font.top_limit - cached_font.base_line) * ratio; 362 363 double px = 0; 364 double py = cc_y; 365 366 y = cc_y; 367 x = 0; 368 369 if (unlikely (cached_font.base_line != 0)) { 370 warning ("Base line not zero."); 371 } 372 373 iterate ((glyph, kerning, last) => { 374 double end; 375 376 x += kerning * ratio; 377 end = x + glyph.get_width () * ratio; 378 379 // truncation 380 if (truncated_width > 0 && end - px > truncated_width) { 381 return; 382 } 383 384 draw_chached (cr, glyph, kerning, last, x, y, cc_y, 385 ratio, cacheid); 386 387 x = end; 388 }); 389 390 return cache_surface; 391 } 392 393 void draw_without_cache (Context cr, Glyph glyph, double kerning, bool last, 394 double x, double y, double cc_y, double ratio) { 395 396 double lsb; 397 398 cr.save (); 399 cr.set_source_rgba (r, g, b, a); 400 cr.new_path (); 401 402 lsb = glyph.left_limit; 403 404 foreach (Path path in glyph.get_visible_paths ()) { 405 draw_path (cr, glyph, path, lsb, x, y, ratio); 406 } 407 408 cr.fill (); 409 cr.restore (); 410 411 } 412 413 void draw_chached (Context cr, Glyph glyph, double kerning, bool last, 414 double x, double y, double cc_y, double ratio, 415 string cacheid = "") { 416 417 double lsb; 418 Surface cache; 419 Context cc; 420 string cache_id; 421 double xp = x; 422 double yp = y - cc_y; 423 int offset_x, offset_y; 424 425 offset_x = (int) (10 * (xp - (int) xp)); 426 offset_y = (int) (10 * (yp - (int) yp)); 427 428 cache_id = (cacheid == "") ? get_cache_id (offset_x, offset_y) : cacheid; 429 430 if (unlikely (!glyph.has_cache (cache_id))) { 431 int w = (int) (glyph.get_width () * ratio) + 2; 432 int h = (int) font_size + 2; 433 cache = Screen.create_background_surface (w, h); 434 cc = new Context (cache); 435 436 cc.scale(Screen.get_scale (), Screen.get_scale ()); 437 438 lsb = glyph.left_limit; 439 440 cc.save (); 441 cc.set_source_rgba (r, g, b, a); 442 cc.new_path (); 443 444 foreach (Path path in glyph.get_visible_paths ()) { 445 draw_path (cc, glyph, path, lsb, offset_x / 10.0, cc_y + offset_y / 10.0, ratio); 446 } 447 448 cc.fill (); 449 cc.restore (); 450 451 glyph.set_cache (cache_id, cache); 452 } 453 454 cr.save (); 455 cr.set_antialias (Cairo.Antialias.NONE); 456 cr.scale(1 / Screen.get_scale (), 1 / Screen.get_scale ()); 457 cr.set_source_surface (glyph.get_cache (cache_id), 458 (int) (xp * Screen.get_scale ()), 459 (int) (yp * Screen.get_scale ())); 460 cr.paint (); 461 cr.restore (); 462 } 463 464 void draw_path (Context cr, Glyph glyph, Path path, 465 double lsb, double x, double y, double scale) { 466 467 EditPoint e, prev; 468 double xa, ya, xb, yb, xc, yc, xd, yd; 469 double by; 470 double s = get_scale (glyph); 471 472 if (path.points.size > 0) { 473 if (unlikely (path.is_open ())) { 474 warning (@"Path is open in $(glyph.get_name ())."); 475 } 476 477 //path.add_hidden_double_points (); // FIXME: this distorts shapes 478 479 prev = path.points.get (path.points.size - 1); 480 xa = (prev.x - lsb) * s + x; 481 ya = y - prev.y * s; 482 cr.move_to (xa, ya); 483 484 by = (y - cached_font.base_line * s); 485 for (int i = 0; i < path.points.size; i++) { 486 e = path.points.get (i).copy (); 487 488 PenTool.convert_point_segment_type (prev, e, PointType.CUBIC); 489 490 xb = (prev.get_right_handle ().x - lsb) * s + x; 491 yb = by - prev.get_right_handle ().y * s; 492 493 xc = (e.get_left_handle ().x - lsb) * s + x; 494 yc = by - e.get_left_handle ().y * s; 495 496 xd = (e.x - lsb) * s + x; 497 yd = by - e.y * s; 498 499 cr.curve_to (xb, yb, xc, yc, xd, yd); 500 cr.line_to (xd, yd); 501 502 prev = e; 503 } 504 } 505 } 506 507 public double get_baseline_to_bottom (Glyph g) { 508 return get_scale (g) * (-g.baseline - g.bottom_limit); 509 } 510 511 public double get_scale (Glyph g) { 512 double s = g.top_limit - g.bottom_limit; 513 514 if (s == 0) { 515 s = cached_font.top_limit - cached_font.bottom_limit; 516 } 517 518 return font_size / s; 519 } 520 521 public double get_font_scale () { 522 return font_size / (cached_font.top_limit - cached_font.bottom_limit); 523 } 524 525 public double get_baseline_to_bottom_for_font () { 526 return get_font_scale () * (-cached_font.base_line - cached_font.bottom_limit); 527 } 528 529 public void truncate (double max_width) { 530 truncated_width = max_width; 531 } 532 } 533 534 } 535