9fans.net/go@v0.0.5/draw/font.go (about) 1 package draw 2 3 import ( 4 "fmt" 5 "os" 6 "sync" 7 "unicode/utf8" 8 ) 9 10 // A Font represents a font that may be used to draw on the display. 11 // A Font is constructed by reading a font file that describes how to 12 // create a full font from a collection of subfonts, each of which 13 // covers a section of the Unicode code space. 14 // 15 // A Font is a set of character images, indexed by runes. 16 // The images are organized into Subfonts, each containing the images 17 // for a small, contiguous set of runes. 18 // 19 // Font defines two important dimension fields: ‘Ascent’, 20 // the distance from the top of the highest character 21 // (actually the top of the image holding all the characters) to the baseline, 22 // and ‘Height’, the distance from the top of the highest character 23 // to the bottom of the lowest character (and hence, the interline spacing). 24 type Font struct { 25 Display *Display 26 Name string // name, typically from file. 27 Height int // max height of image; interline spacing 28 Ascent int // top of image to baseline 29 Scale int // pixel scaling 30 31 namespec string 32 mu sync.Mutex // only used if Display == nil 33 width int // widest so far; used in caching only 34 age uint32 // increasing counter; used for LUR 35 maxdepth int // maximum depth of all loaded subfonts 36 cache []cacheinfo 37 subf []cachesubf 38 sub []*cachefont // as read from file 39 cacheimage *Image 40 41 // doubly linked list of fonts known to display 42 ondisplaylist bool 43 next *Font 44 prev *Font 45 46 // on hi-dpi systems, one of these is f and the other is the other-dpi version of f 47 lodpi *Font 48 hidpi *Font 49 } 50 51 func (f *Font) lock() { 52 if f.Display != nil { 53 f.Display.mu.Lock() 54 } else { 55 f.mu.Lock() 56 } 57 } 58 59 func (f *Font) unlock() { 60 if f.Display != nil { 61 f.Display.mu.Unlock() 62 } else { 63 f.mu.Unlock() 64 } 65 } 66 67 type cachefont struct { 68 min rune 69 max rune 70 offset int 71 name string 72 subfontname string 73 } 74 75 type cacheinfo struct { 76 x uint16 77 width uint8 78 left int8 79 value rune 80 age uint32 81 } 82 83 type cachesubf struct { 84 age uint32 85 cf *cachefont 86 f *subfont 87 } 88 89 // A subfont is a set of images for a contiguous range of Unicode code points, 90 // stored as a single image with the characters placed side-by-side on a 91 // common baseline. 92 // 93 // The Bits image fills the rectangle (0, 0, w, Height), where w is the sum 94 // of the horizontal extents (of non-zero pixels) for all characters. 95 // The pixels to be displayed for character c are in the rectangle 96 // (i.X, i.Top, next.X, i.Bottom) where i is Info[c] and next is Info[c+1]. 97 // When a character is displayed at Point p in an image, 98 // the character rectangle is placed at (p.X+i.Left, p.Y) 99 // and the next character of the string is displayed at (p.X+i.Width, p.Y). 100 // The baseline of the characters is ‘ascent’ rows down from the top 101 // of the subfont image. 102 // The `info' array has n+1 elements, one each for characters 0 to n-1 103 // plus an additional entry so the width of the last character can be calculated. 104 // Thus the width, w, of the Image associated with a subfont s is 105 // s.Info[s.N].X. 106 type subfont struct { 107 Name string // name, typically the file from which it was read 108 N int // number of characters in the subfont. 109 Height int // interline spacing 110 Ascent int // height above the baseline 111 Info []Fontchar // character descriptions 112 Bits *Image // image holding the glyphs 113 ref int 114 } 115 116 // A Fontchar descibes one character glyph in a font (really a subfont). 117 type Fontchar struct { 118 X int // x position in the image holding the glyphs. 119 Top uint8 // first non-zero scan line. 120 Bottom uint8 // last non-zero scan line. 121 Left int8 // offset of baseline. 122 Width uint8 // width of baseline. 123 } 124 125 const ( 126 /* starting values */ 127 _LOG2NFCACHE = 6 128 _NFCACHE = (1 << _LOG2NFCACHE) /* #chars cached */ 129 _NFLOOK = 5 /* #chars to scan in cache */ 130 _NFSUBF = 2 /* #subfonts to cache */ 131 /* max value */ 132 _MAXFCACHE = 1024 + _NFLOOK /* upper limit */ 133 _MAXSUBF = 50 /* generous upper limit */ 134 /* deltas */ 135 _DSUBF = 4 136 /* expiry ages */ 137 _SUBFAGE = 10000 138 _CACHEAGE = 10000 139 ) 140 141 const pjw = 0 /* use NUL==pjw for invisible characters */ 142 143 func cachechars(f *Font, in *input, cp []uint16, max int) (n, wid int, subfontname string) { 144 var i int 145 //println("cachechars", i<max, in.done) 146 Loop: 147 for ; i < max && !in.done; in.next() { 148 r := in.ch 149 var ( 150 c, tc *cacheinfo 151 a uint32 152 sh, esh, h, th, ld int 153 ) 154 155 sh = (17 * int(r)) & (len(f.cache) - _NFLOOK - 1) 156 esh = sh + _NFLOOK 157 h = sh 158 for h < esh { 159 c = &f.cache[h] 160 if c.value == r && c.age > 0 { 161 goto Found 162 } 163 h++ 164 } 165 166 /* 167 * Not found; toss out oldest entry 168 */ 169 a = ^uint32(0) 170 th = sh 171 for th < esh { 172 tc = &f.cache[th] 173 if tc.age < a { 174 a = tc.age 175 h = th 176 c = tc 177 } 178 th++ 179 } 180 181 if a != 0 && f.age-a < 500 { // kicking out too recent; resize 182 nc := 2*(len(f.cache)-_NFLOOK) + _NFLOOK 183 if nc <= _MAXFCACHE { 184 if i == 0 { 185 fontresize(f, f.width, nc, f.maxdepth) 186 } 187 // else flush first; retry will resize 188 break Loop 189 } 190 } 191 192 if c.age == f.age { // flush pending string output 193 break Loop 194 } 195 196 ld, subfontname = loadchar(f, r, c, h, i > 0) 197 if ld <= 0 { 198 if ld == 0 { 199 continue Loop 200 } 201 break Loop 202 } 203 c = &f.cache[h] 204 205 Found: 206 //println("FOUND") 207 wid += int(c.width) 208 c.age = f.age 209 cp[i] = uint16(h) 210 i++ 211 } 212 return i, wid, subfontname 213 } 214 215 func agefont(f *Font) { 216 f.age++ 217 if f.age == 65536 { 218 /* 219 * Renormalize ages 220 */ 221 for i := range f.cache { 222 c := &f.cache[i] 223 if c.age > 0 { 224 c.age >>= 2 225 c.age++ 226 } 227 } 228 for i := range f.subf { 229 s := &f.subf[i] 230 if s.age > 0 { 231 if s.age < _SUBFAGE && s.cf.name != "" { 232 /* clean up */ 233 if f.Display == nil || s.f != f.Display.defaultSubfont { 234 s.f.free() 235 } 236 s.cf = nil 237 s.f = nil 238 s.age = 0 239 } else { 240 s.age >>= 2 241 s.age++ 242 } 243 } 244 } 245 f.age = (65536 >> 2) + 1 246 } 247 } 248 249 func cf2subfont(cf *cachefont, f *Font) (*subfont, error) { 250 name := cf.subfontname 251 if name == "" { 252 depth := 0 253 if f.Display != nil { 254 if f.Display.ScreenImage != nil { 255 depth = f.Display.ScreenImage.Depth 256 } 257 } else { 258 depth = 8 259 } 260 name = subfontname(cf.name, f.Name, depth) 261 if name == "" { 262 return nil, fmt.Errorf("unknown subfont") 263 } 264 cf.subfontname = name 265 } 266 sf := lookupsubfont(f.Display, name) 267 return sf, nil 268 } 269 270 // return 1 if load succeeded, 0 if failed, -1 if must retry 271 func loadchar(f *Font, r rune, c *cacheinfo, h int, noflush bool) (int, string) { 272 var ( 273 i, oi, wid, top, bottom int 274 pic rune 275 fi []Fontchar 276 cf *cachefont 277 subf *cachesubf 278 b []byte 279 ) 280 281 pic = r 282 Again: 283 for i, cf = range f.sub { 284 if cf.min <= pic && pic <= cf.max { 285 goto Found 286 } 287 } 288 TryPJW: 289 if pic != pjw { 290 pic = pjw 291 goto Again 292 } 293 return 0, "" 294 295 Found: 296 /* 297 * Choose exact or oldest 298 */ 299 oi = 0 300 for i := range f.subf { 301 subf = &f.subf[i] 302 if cf == subf.cf { 303 goto Found2 304 } 305 if subf.age < f.subf[oi].age { 306 oi = i 307 } 308 } 309 subf = &f.subf[oi] 310 311 if subf.f != nil { 312 if f.age-subf.age > _SUBFAGE || len(f.subf) > _MAXSUBF { 313 // ancient data; toss 314 subf.f.free() 315 subf.cf = nil 316 subf.f = nil 317 subf.age = 0 318 } else { // too recent; grow instead 319 of := f.subf 320 f.subf = make([]cachesubf, len(f.subf)+_DSUBF) 321 copy(f.subf, of) 322 subf = &f.subf[len(of)] 323 } 324 } 325 326 subf.age = 0 327 subf.cf = nil 328 subf.f, _ = cf2subfont(cf, f) 329 if subf.f == nil { 330 if cf.subfontname == "" { 331 goto TryPJW 332 } 333 return -1, cf.subfontname 334 } 335 336 subf.cf = cf 337 if subf.f.Ascent > f.Ascent && f.Display != nil { 338 /* should print something? this is a mistake in the font file */ 339 /* must prevent c.top from going negative when loading cache */ 340 d := subf.f.Ascent - f.Ascent 341 b := subf.f.Bits 342 b.draw(b.R, b, nil, b.R.Min.Add(Pt(0, d))) 343 b.draw(Rect(b.R.Min.X, b.R.Max.Y-d, b.R.Max.X, b.R.Max.Y), f.Display.Black, nil, b.R.Min) 344 for i := 0; i < subf.f.N; i++ { 345 t := int(subf.f.Info[i].Top) - d 346 if t < 0 { 347 t = 0 348 } 349 subf.f.Info[i].Top = uint8(t) 350 t = int(subf.f.Info[i].Bottom) - d 351 if t < 0 { 352 t = 0 353 } 354 subf.f.Info[i].Bottom = uint8(t) 355 } 356 subf.f.Ascent = f.Ascent 357 } 358 359 Found2: 360 subf.age = f.age 361 362 /* possible overflow here, but works out okay */ 363 pic += rune(cf.offset) 364 pic -= cf.min 365 if int(pic) >= subf.f.N { 366 goto TryPJW 367 } 368 fi = subf.f.Info[pic : pic+2] 369 if fi[0].Width == 0 { 370 goto TryPJW 371 } 372 wid = fi[1].X - fi[0].X 373 if f.width < wid || f.width == 0 || f.maxdepth < subf.f.Bits.Depth { 374 /* 375 * Flush, free, reload (easier than reformatting f.b) 376 */ 377 if noflush { 378 return -1, "" 379 } 380 if f.width < wid { 381 f.width = wid 382 } 383 if f.maxdepth < subf.f.Bits.Depth { 384 f.maxdepth = subf.f.Bits.Depth 385 } 386 i = fontresize(f, f.width, len(f.cache), f.maxdepth) 387 if i <= 0 { 388 return i, "" 389 } 390 /* c is still valid as didn't reallocate f.cache */ 391 } 392 c.value = r 393 top = int(fi[0].Top) + (f.Ascent - subf.f.Ascent) 394 bottom = int(fi[0].Bottom) + (f.Ascent - subf.f.Ascent) 395 c.width = fi[0].Width 396 c.x = uint16(h * int(f.width)) 397 c.left = fi[0].Left 398 if f.Display == nil { 399 return 1, "" 400 } 401 f.Display.flush(false) /* flush any pending errors */ 402 b = f.Display.bufimage(37) 403 b[0] = 'l' 404 bplong(b[1:], uint32(f.cacheimage.id)) 405 bplong(b[5:], uint32(subf.f.Bits.id)) 406 bpshort(b[9:], uint16(h)) 407 bplong(b[11:], uint32(c.x)) 408 bplong(b[15:], uint32(top)) 409 bplong(b[19:], uint32(int(c.x)+int(fi[1].X-fi[0].X))) 410 bplong(b[23:], uint32(bottom)) 411 bplong(b[27:], uint32(fi[0].X)) 412 bplong(b[31:], uint32(fi[0].Top)) 413 b[35] = byte(fi[0].Left) 414 b[36] = fi[0].Width 415 return 1, "" 416 } 417 418 // return whether resize succeeded && f.cache is unchanged 419 func fontresize(f *Font, wid, ncache, depth int) int { 420 var ( 421 ret int 422 new *Image 423 b []byte 424 d *Display 425 err error 426 ) 427 428 if depth <= 0 { 429 depth = 1 430 } 431 432 d = f.Display 433 if d == nil { 434 goto Nodisplay 435 } 436 new, err = d.allocImage(Rect(0, 0, ncache*wid, f.Height), MakePix(CGrey, depth), false, 0) 437 if err != nil { 438 fmt.Fprintf(os.Stderr, "font cache resize failed\n") 439 panic("resize") 440 } 441 d.flush(false) // flush any pending errors 442 b = d.bufimage(1 + 4 + 4 + 1) 443 b[0] = 'i' 444 bplong(b[1:], new.id) 445 bplong(b[5:], uint32(ncache)) 446 b[9] = byte(f.Ascent) 447 if err := d.flush(false); err != nil { 448 fmt.Fprintf(os.Stderr, "resize: init failed\n") 449 new.free() 450 goto Return 451 } 452 f.cacheimage.free() 453 f.cacheimage = new 454 455 Nodisplay: 456 f.width = wid 457 f.maxdepth = depth 458 ret = 1 459 if len(f.cache) != ncache { 460 f.cache = make([]cacheinfo, ncache) 461 } 462 463 Return: 464 for i := range f.cache { 465 f.cache[i] = cacheinfo{} 466 } 467 return ret 468 } 469 470 // An input can read a rune at a time from a string, []byte, or []rune. 471 type input struct { 472 mode int 473 s string 474 b []byte 475 r []rune 476 size int 477 ch rune 478 done bool 479 } 480 481 func (in *input) init(s string, b []byte, r []rune) { 482 //println("init:", s) 483 in.s = s 484 in.b = b 485 in.r = r 486 in.mode = 0 487 if len(in.s) == 0 { 488 in.mode = 1 489 if len(in.b) == 0 { 490 in.mode = 2 491 } 492 } 493 494 in.next() 495 } 496 497 func (in *input) next() { 498 switch in.mode { 499 case 0: 500 in.s = in.s[in.size:] 501 if len(in.s) == 0 { 502 in.done = true 503 return 504 } 505 in.ch, in.size = utf8.DecodeRuneInString(in.s) 506 case 1: 507 in.b = in.b[in.size:] 508 if len(in.b) == 0 { 509 in.done = true 510 return 511 } 512 in.ch, in.size = utf8.DecodeRune(in.b) 513 case 2: 514 in.r = in.r[in.size:] 515 if len(in.r) == 0 { 516 in.done = true 517 return 518 } 519 in.ch = in.r[0] 520 in.size = 1 521 } 522 //println("next is ", in.ch, in.done) 523 }