package draw import ( "bufio" "fmt" "io" "os" "path" "strconv" "strings" ) // Fontchar is one glyph's metrics, unpacked from the 6-byte on-disk form. type Fontchar struct { X int32 // left edge in the subfont image Top, Bottom, Left uint8 Width uint8 // advance } // Subfont is one image of glyphs plus their metrics -- the unit a .font file // is assembled from. type Subfont struct { Bits *Image N int Height, Ascent int Info []Fontchar // N+1 entries; the last one bounds the last glyph } type frange struct { min, max rune offset int file string sub *Subfont // loaded on first use } // Font is a .font file: a height, an ascent, and a set of rune ranges each // served by a subfont. type Font struct { d *Display Name string Height, Ascent int ranges []frange } // OpenFont reads a .font file. Subfonts are loaded lazily, because a unicode // font names dozens of them and a calendar touches two. func (d *Display) OpenFont(name string) (*Font, error) { f, err := os.Open(name) if err != nil { return nil, fmt.Errorf("open font: %w", err) } defer f.Close() fnt := &Font{d: d, Name: name} dir := path.Dir(name) sc := bufio.NewScanner(f) first := true for sc.Scan() { fld := strings.Fields(sc.Text()) if len(fld) == 0 { continue } if first { if len(fld) < 2 { return nil, fmt.Errorf("font %s: bad first line", name) } h, err1 := strconv.ParseInt(fld[0], 0, 32) a, err2 := strconv.ParseInt(fld[1], 0, 32) if err1 != nil || err2 != nil || h <= 0 || a <= 0 { return nil, fmt.Errorf("font %s: bad height/ascent", name) } fnt.Height, fnt.Ascent = int(h), int(a) first = false continue } // min max [offset] file if len(fld) < 3 { continue } min, err1 := strconv.ParseInt(fld[0], 0, 32) max, err2 := strconv.ParseInt(fld[1], 0, 32) if err1 != nil || err2 != nil || min > max { return nil, fmt.Errorf("font %s: bad range %q", name, sc.Text()) } r := frange{min: rune(min), max: rune(max)} rest := fld[2:] // The offset is optional and sits before the filename, so it is // only an offset if it parses as a number and something follows. if len(rest) >= 2 { if off, err := strconv.ParseInt(rest[0], 0, 32); err == nil { r.offset = int(off) rest = rest[1:] } } r.file = rest[0] if !path.IsAbs(r.file) { r.file = path.Join(dir, r.file) } fnt.ranges = append(fnt.ranges, r) } if err := sc.Err(); err != nil { return nil, err } if first { return nil, fmt.Errorf("font %s: empty", name) } return fnt, nil } // lookup finds the subfont holding r and r's index within it. func (f *Font) lookup(r rune) (*Subfont, int) { for i := range f.ranges { g := &f.ranges[i] if r < g.min || r > g.max { continue } if g.sub == nil { s, err := f.d.readSubfont(g.file) if err != nil { return nil, 0 } g.sub = s } n := int(r-g.min) + g.offset if n < 0 || n >= g.sub.N { return nil, 0 } return g.sub, n } return nil, 0 } // String draws s at p (p is the top-left, as in libdraw) in colour src, and // returns the point just past the last glyph. func (f *Font) String(dst *Image, p Point, src *Image, s string) Point { for _, r := range s { sub, n := f.lookup(r) if sub == nil { continue } i, i1 := sub.Info[n], sub.Info[n+1] w := i1.X - i.X if w > 0 { Draw(dst, Rect(p.X+int32(i.Left), p.Y+int32(i.Top), p.X+int32(i.Left)+w, p.Y+int32(i.Bottom)), src, sub.Bits, Point{i.X, int32(i.Top)}) } p.X += int32(i.Width) } return p } // Width is the advance of s, without drawing it. func (f *Font) Width(s string) int32 { var w int32 for _, r := range s { sub, n := f.lookup(r) if sub == nil { continue } w += int32(sub.Info[n].Width) } return w } // readSubfont loads one subfont file: a Plan 9 image, then the glyph table. // // The image is never decompressed here. Plan 9's compressed image format is // exactly what the draw device's 'Y' message accepts, so the blocks go // straight to the kernel -- which is all libdraw's creadimage does too. func (d *Display) readSubfont(name string) (*Subfont, error) { f, err := os.Open(name) if err != nil { return nil, fmt.Errorf("open subfont: %w", err) } defer f.Close() r := bufio.NewReader(f) var hdr [5 * 12]byte if _, err := io.ReadFull(r, hdr[:11]); err != nil { return nil, fmt.Errorf("%s: short header: %w", name, err) } compressed := string(hdr[:11]) == "compressed\n" if compressed { if _, err := io.ReadFull(r, hdr[:]); err != nil { return nil, fmt.Errorf("%s: short header: %w", name, err) } } else if _, err := io.ReadFull(r, hdr[11:]); err != nil { return nil, fmt.Errorf("%s: short header: %w", name, err) } fld := func(i int) int32 { v, _ := strconv.Atoi(trim(string(hdr[i*12 : i*12+12]))) return int32(v) } chn := strToChan(trim(string(hdr[0:12]))) ir := Rect(fld(1), fld(2), fld(3), fld(4)) img, err := d.Alloc(ir, chn, false, 0) if err != nil { return nil, fmt.Errorf("%s: %w", name, err) } if compressed { err = img.loadCompressed(r, ir) } else { err = img.loadRaw(r, ir, chn) } if err != nil { return nil, fmt.Errorf("%s: %w", name, err) } // n, height, ascent, then 6 bytes per glyph for n+1 glyphs. var sh [3 * 12]byte if _, err := io.ReadFull(r, sh[:]); err != nil { return nil, fmt.Errorf("%s: short subfont header: %w", name, err) } n, _ := strconv.Atoi(trim(string(sh[0:12]))) if n <= 0 || n > 0x7fff { return nil, fmt.Errorf("%s: bad glyph count %d", name, n) } sf := &Subfont{ Bits: img, N: n, Height: mustAtoi(trim(string(sh[12:24]))), Ascent: mustAtoi(trim(string(sh[24:36]))), Info: make([]Fontchar, n+1), } pk := make([]byte, 6*(n+1)) if _, err := io.ReadFull(r, pk); err != nil { return nil, fmt.Errorf("%s: short glyph table: %w", name, err) } for i := 0; i <= n; i++ { b := pk[i*6:] sf.Info[i] = Fontchar{ X: int32(b[0]) | int32(b[1])<<8, Top: b[2], Bottom: b[3], Left: b[4], Width: b[5], } } return sf, nil } func mustAtoi(s string) int { v, _ := strconv.Atoi(s); return v } // loadCompressed forwards each compressed block to the draw device verbatim. func (i *Image) loadCompressed(r io.Reader, ir Rectangle) error { var bh [2 * 12]byte miny := ir.Min.Y for miny != ir.Max.Y { if _, err := io.ReadFull(r, bh[:]); err != nil { return fmt.Errorf("short block header: %w", err) } maxy := int32(mustAtoi(trim(string(bh[0:12])))) nb := mustAtoi(trim(string(bh[12:24]))) if maxy <= miny || maxy > ir.Max.Y || nb <= 0 { return fmt.Errorf("bad block: maxy=%d nb=%d", maxy, nb) } buf := make([]byte, 21+nb) buf[0] = 'Y' put32(buf, 1, i.id) put32(buf, 5, uint32(ir.Min.X)) put32(buf, 9, uint32(miny)) put32(buf, 13, uint32(ir.Max.X)) put32(buf, 17, uint32(maxy)) if _, err := io.ReadFull(r, buf[21:]); err != nil { return fmt.Errorf("short block: %w", err) } if err := i.d.write(buf); err != nil { return err } miny = maxy } return nil } // loadRaw uploads an uncompressed image a stripe at a time with 'y'. func (i *Image) loadRaw(r io.Reader, ir Rectangle, chn uint32) error { depth := chanDepth(chn) if depth == 0 { return fmt.Errorf("bad channel %#x", chn) } bpl := bytesPerLine(ir, depth) // Keep each write comfortably inside the device's iounit. rows := 8000 / bpl if rows < 1 { rows = 1 } for y := ir.Min.Y; y < ir.Max.Y; { y1 := y + int32(rows) if y1 > ir.Max.Y { y1 = ir.Max.Y } n := bpl * int(y1-y) buf := make([]byte, 21+n) buf[0] = 'y' put32(buf, 1, i.id) put32(buf, 5, uint32(ir.Min.X)) put32(buf, 9, uint32(y)) put32(buf, 13, uint32(ir.Max.X)) put32(buf, 17, uint32(y1)) if _, err := io.ReadFull(r, buf[21:]); err != nil { return fmt.Errorf("short pixel data: %w", err) } if err := i.d.write(buf); err != nil { return err } y = y1 } return nil } // chanDepth is bits per pixel for a channel descriptor. func chanDepth(c uint32) int { d := 0 for ; c != 0; c >>= 8 { d += int(c & 15) } return d } // bytesPerLine mirrors libdraw's unitsperline(r, d, 8), including the // negative-min.x case. func bytesPerLine(r Rectangle, d int) int { if d <= 0 || d > 32 { return 0 } if r.Min.X >= 0 { l := (int(r.Max.X)*d + 7) / 8 return l - (int(r.Min.X)*d)/8 } l := (int(r.Max.X)*d + 7) / 8 return l + (int(-r.Min.X)*d+7)/8 }