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