22f1b5f81b
Also remove the dependency on the image/draw package. That package will give the right answer for arbitrary source images, including those of type plan9Image, but doing the conversion directly avoids bouncing uint8 or color.Alpha values through the general-purpose draw.Image, image.Image and color.Color interfaces. It is possible to optimize this even further, but this will do for now. benchmark old ns/op new ns/op delta BenchmarkParseSubfont-8 2298492 492443 -78.58% Change-Id: Iea9436bffa097a1ba0052dbabf21516bce8b61e0 Reviewed-on: https://go-review.googlesource.com/21693 Reviewed-by: Nigel Tao <nigeltao@golang.org>
586 lines
15 KiB
Go
586 lines
15 KiB
Go
// Copyright 2015 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// Package plan9font implements font faces for the Plan 9 font and subfont file
|
|
// formats. These formats are described at
|
|
// http://plan9.bell-labs.com/magic/man2html/6/font
|
|
package plan9font // import "golang.org/x/image/font/plan9font"
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"image"
|
|
"image/color"
|
|
"log"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"golang.org/x/image/font"
|
|
"golang.org/x/image/math/fixed"
|
|
)
|
|
|
|
// fontchar describes one character glyph in a subfont.
|
|
//
|
|
// For more detail, look for "struct Fontchar" in
|
|
// http://plan9.bell-labs.com/magic/man2html/2/cachechars
|
|
type fontchar struct {
|
|
x uint32 // X position in the image holding the glyphs.
|
|
top uint8 // First non-zero scan line.
|
|
bottom uint8 // Last non-zero scan line.
|
|
left int8 // Offset of baseline.
|
|
width uint8 // Width of baseline.
|
|
}
|
|
|
|
func parseFontchars(p []byte) []fontchar {
|
|
fc := make([]fontchar, len(p)/6)
|
|
for i := range fc {
|
|
fc[i] = fontchar{
|
|
x: uint32(p[0]) | uint32(p[1])<<8,
|
|
top: uint8(p[2]),
|
|
bottom: uint8(p[3]),
|
|
left: int8(p[4]),
|
|
width: uint8(p[5]),
|
|
}
|
|
p = p[6:]
|
|
}
|
|
return fc
|
|
}
|
|
|
|
// subface implements font.Face for a Plan 9 subfont.
|
|
type subface struct {
|
|
firstRune rune // First rune in the subfont.
|
|
n int // Number of characters in the subfont.
|
|
height int // Inter-line spacing.
|
|
ascent int // Height above the baseline.
|
|
fontchars []fontchar // Character descriptions.
|
|
img *image.Alpha // Image holding the glyphs.
|
|
}
|
|
|
|
func (f *subface) Close() error { return nil }
|
|
func (f *subface) Kern(r0, r1 rune) fixed.Int26_6 { return 0 }
|
|
|
|
func (f *subface) Metrics() font.Metrics {
|
|
return font.Metrics{
|
|
Height: fixed.I(f.height),
|
|
Ascent: fixed.I(f.ascent),
|
|
Descent: fixed.I(f.height - f.ascent),
|
|
}
|
|
}
|
|
|
|
func (f *subface) Glyph(dot fixed.Point26_6, r rune) (
|
|
dr image.Rectangle, mask image.Image, maskp image.Point, advance fixed.Int26_6, ok bool) {
|
|
|
|
r -= f.firstRune
|
|
if r < 0 || f.n <= int(r) {
|
|
return image.Rectangle{}, nil, image.Point{}, 0, false
|
|
}
|
|
i := &f.fontchars[r+0]
|
|
j := &f.fontchars[r+1]
|
|
|
|
minX := int(dot.X+32)>>6 + int(i.left)
|
|
minY := int(dot.Y+32)>>6 + int(i.top) - f.ascent
|
|
dr = image.Rectangle{
|
|
Min: image.Point{
|
|
X: minX,
|
|
Y: minY,
|
|
},
|
|
Max: image.Point{
|
|
X: minX + int(j.x-i.x),
|
|
Y: minY + int(i.bottom) - int(i.top),
|
|
},
|
|
}
|
|
return dr, f.img, image.Point{int(i.x), int(i.top)}, fixed.Int26_6(i.width) << 6, true
|
|
}
|
|
|
|
func (f *subface) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) {
|
|
r -= f.firstRune
|
|
if r < 0 || f.n <= int(r) {
|
|
return fixed.Rectangle26_6{}, 0, false
|
|
}
|
|
i := &f.fontchars[r+0]
|
|
j := &f.fontchars[r+1]
|
|
|
|
bounds = fixed.R(
|
|
int(i.left),
|
|
int(i.top)-f.ascent,
|
|
int(i.left)+int(j.x-i.x),
|
|
int(i.bottom)-f.ascent,
|
|
)
|
|
return bounds, fixed.Int26_6(i.width) << 6, true
|
|
}
|
|
|
|
func (f *subface) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) {
|
|
r -= f.firstRune
|
|
if r < 0 || f.n <= int(r) {
|
|
return 0, false
|
|
}
|
|
return fixed.Int26_6(f.fontchars[r].width) << 6, true
|
|
}
|
|
|
|
// runeRange maps a single rune range [lo, hi] to a lazily loaded subface. Both
|
|
// ends of the range are inclusive.
|
|
type runeRange struct {
|
|
lo, hi rune
|
|
offset rune // subfont index that the lo rune maps to.
|
|
relFilename string
|
|
subface *subface
|
|
bad bool
|
|
}
|
|
|
|
// face implements font.Face for a Plan 9 font.
|
|
//
|
|
// It maps multiple rune ranges to *subface values. Rune ranges may overlap;
|
|
// the first match wins.
|
|
type face struct {
|
|
height int
|
|
ascent int
|
|
readFile func(relFilename string) ([]byte, error)
|
|
runeRanges []runeRange
|
|
}
|
|
|
|
func (f *face) Close() error { return nil }
|
|
func (f *face) Kern(r0, r1 rune) fixed.Int26_6 { return 0 }
|
|
|
|
func (f *face) Metrics() font.Metrics {
|
|
return font.Metrics{
|
|
Height: fixed.I(f.height),
|
|
Ascent: fixed.I(f.ascent),
|
|
Descent: fixed.I(f.height - f.ascent),
|
|
}
|
|
}
|
|
|
|
func (f *face) Glyph(dot fixed.Point26_6, r rune) (
|
|
dr image.Rectangle, mask image.Image, maskp image.Point, advance fixed.Int26_6, ok bool) {
|
|
|
|
if s, rr := f.subface(r); s != nil {
|
|
return s.Glyph(dot, rr)
|
|
}
|
|
return image.Rectangle{}, nil, image.Point{}, 0, false
|
|
}
|
|
|
|
func (f *face) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) {
|
|
if s, rr := f.subface(r); s != nil {
|
|
return s.GlyphBounds(rr)
|
|
}
|
|
return fixed.Rectangle26_6{}, 0, false
|
|
}
|
|
|
|
func (f *face) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) {
|
|
if s, rr := f.subface(r); s != nil {
|
|
return s.GlyphAdvance(rr)
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
func (f *face) subface(r rune) (*subface, rune) {
|
|
// Fall back on U+FFFD if we can't find r.
|
|
for _, rr := range [2]rune{r, '\ufffd'} {
|
|
// We have to do linear, not binary search. plan9port's
|
|
// lucsans/unicode.8.font says:
|
|
// 0x2591 0x2593 ../luc/Altshades.7.0
|
|
// 0x2500 0x25ee ../luc/FormBlock.7.0
|
|
// and the rune ranges overlap.
|
|
for i := range f.runeRanges {
|
|
x := &f.runeRanges[i]
|
|
if rr < x.lo || x.hi < rr || x.bad {
|
|
continue
|
|
}
|
|
if x.subface == nil {
|
|
data, err := f.readFile(x.relFilename)
|
|
if err != nil {
|
|
log.Printf("plan9font: couldn't read subfont %q: %v", x.relFilename, err)
|
|
x.bad = true
|
|
continue
|
|
}
|
|
sub, err := ParseSubfont(data, x.lo-x.offset)
|
|
if err != nil {
|
|
log.Printf("plan9font: couldn't parse subfont %q: %v", x.relFilename, err)
|
|
x.bad = true
|
|
continue
|
|
}
|
|
x.subface = sub.(*subface)
|
|
}
|
|
return x.subface, rr
|
|
}
|
|
}
|
|
return nil, 0
|
|
}
|
|
|
|
// ParseFont parses a Plan 9 font file. data is the contents of that font file,
|
|
// which gives relative filenames for subfont files. readFile returns the
|
|
// contents of those subfont files. It is similar to io/ioutil's ReadFile
|
|
// function, except that it takes a relative filename instead of an absolute
|
|
// one.
|
|
func ParseFont(data []byte, readFile func(relFilename string) ([]byte, error)) (font.Face, error) {
|
|
f := &face{
|
|
readFile: readFile,
|
|
}
|
|
// TODO: don't use strconv, to avoid the conversions from []byte to string?
|
|
for first := true; len(data) > 0; first = false {
|
|
i := bytes.IndexByte(data, '\n')
|
|
if i < 0 {
|
|
return nil, errors.New("plan9font: invalid font: no final newline")
|
|
}
|
|
row := string(data[:i])
|
|
data = data[i+1:]
|
|
if first {
|
|
height, s, ok := nextInt32(row)
|
|
if !ok {
|
|
return nil, fmt.Errorf("plan9font: invalid font: invalid header %q", row)
|
|
}
|
|
ascent, s, ok := nextInt32(s)
|
|
if !ok {
|
|
return nil, fmt.Errorf("plan9font: invalid font: invalid header %q", row)
|
|
}
|
|
if height < 0 || 0xffff < height || ascent < 0 || 0xffff < ascent {
|
|
return nil, fmt.Errorf("plan9font: invalid font: invalid header %q", row)
|
|
}
|
|
f.height, f.ascent = int(height), int(ascent)
|
|
continue
|
|
}
|
|
lo, s, ok := nextInt32(row)
|
|
if !ok {
|
|
return nil, fmt.Errorf("plan9font: invalid font: invalid row %q", row)
|
|
}
|
|
hi, s, ok := nextInt32(s)
|
|
if !ok {
|
|
return nil, fmt.Errorf("plan9font: invalid font: invalid row %q", row)
|
|
}
|
|
offset, s, _ := nextInt32(s)
|
|
|
|
f.runeRanges = append(f.runeRanges, runeRange{
|
|
lo: lo,
|
|
hi: hi,
|
|
offset: offset,
|
|
relFilename: s,
|
|
})
|
|
}
|
|
return f, nil
|
|
}
|
|
|
|
func nextInt32(s string) (ret int32, remaining string, ok bool) {
|
|
i := 0
|
|
for ; i < len(s) && s[i] <= ' '; i++ {
|
|
}
|
|
j := i
|
|
for ; j < len(s) && s[j] > ' '; j++ {
|
|
}
|
|
n, err := strconv.ParseInt(s[i:j], 0, 32)
|
|
if err != nil {
|
|
return 0, s, false
|
|
}
|
|
for ; j < len(s) && s[j] <= ' '; j++ {
|
|
}
|
|
return int32(n), s[j:], true
|
|
}
|
|
|
|
// ParseSubfont parses a Plan 9 subfont file.
|
|
//
|
|
// firstRune is the first rune in the subfont file. For example, the
|
|
// Phonetic.6.0 subfont, containing glyphs in the range U+0250 to U+02E9, would
|
|
// set firstRune to '\u0250'.
|
|
func ParseSubfont(data []byte, firstRune rune) (font.Face, error) {
|
|
data, m, err := parseImage(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(data) < 3*12 {
|
|
return nil, errors.New("plan9font: invalid subfont: header too short")
|
|
}
|
|
n := atoi(data[0*12:])
|
|
height := atoi(data[1*12:])
|
|
ascent := atoi(data[2*12:])
|
|
data = data[3*12:]
|
|
if len(data) != 6*(n+1) {
|
|
return nil, errors.New("plan9font: invalid subfont: data length mismatch")
|
|
}
|
|
|
|
// Convert from plan9Image to image.Alpha, as the standard library's
|
|
// image/draw package works best when glyph masks are of that type.
|
|
img := image.NewAlpha(m.Bounds())
|
|
for y := img.Rect.Min.Y; y < img.Rect.Max.Y; y++ {
|
|
i := img.PixOffset(img.Rect.Min.X, y)
|
|
for x := img.Rect.Min.X; x < img.Rect.Max.X; x++ {
|
|
img.Pix[i] = m.at(x, y)
|
|
i++
|
|
}
|
|
}
|
|
|
|
return &subface{
|
|
firstRune: firstRune,
|
|
n: n,
|
|
height: height,
|
|
ascent: ascent,
|
|
fontchars: parseFontchars(data),
|
|
img: img,
|
|
}, nil
|
|
}
|
|
|
|
// plan9Image implements that subset of the Plan 9 image feature set that is
|
|
// used by this font file format.
|
|
//
|
|
// Some features, such as the repl bit and a clip rectangle, are omitted for
|
|
// simplicity.
|
|
type plan9Image struct {
|
|
depth int // Depth of the pixels in bits.
|
|
width int // Width in bytes of a single scan line.
|
|
rect image.Rectangle // Extent of the image.
|
|
pix []byte // Pixel bits.
|
|
}
|
|
|
|
func (m *plan9Image) byteoffset(x, y int) int {
|
|
a := y * m.width
|
|
if m.depth < 8 {
|
|
// We need to always round down, but Go rounds toward zero.
|
|
np := 8 / m.depth
|
|
if x < 0 {
|
|
return a + (x-np+1)/np
|
|
}
|
|
return a + x/np
|
|
}
|
|
return a + x*(m.depth/8)
|
|
}
|
|
|
|
func (m *plan9Image) Bounds() image.Rectangle { return m.rect }
|
|
func (m *plan9Image) ColorModel() color.Model { return color.AlphaModel }
|
|
|
|
func (m *plan9Image) At(x, y int) color.Color {
|
|
if (image.Point{x, y}).In(m.rect) {
|
|
return color.Alpha{m.at(x, y)}
|
|
}
|
|
return color.Alpha{0x00}
|
|
}
|
|
|
|
func (m *plan9Image) at(x, y int) uint8 {
|
|
b := m.pix[m.byteoffset(x, y)]
|
|
switch m.depth {
|
|
case 1:
|
|
// CGrey, 1.
|
|
mask := uint8(1 << uint8(7-x&7))
|
|
if (b & mask) != 0 {
|
|
return 0xff
|
|
}
|
|
return 0
|
|
case 2:
|
|
// CGrey, 2.
|
|
shift := uint(x&3) << 1
|
|
// Place pixel at top of word.
|
|
y := b << shift
|
|
y &= 0xc0
|
|
// Replicate throughout.
|
|
y |= y >> 2
|
|
y |= y >> 4
|
|
return y
|
|
}
|
|
return 0
|
|
}
|
|
|
|
var compressed = []byte("compressed\n")
|
|
|
|
func parseImage(data []byte) (remainingData []byte, m *plan9Image, retErr error) {
|
|
if !bytes.HasPrefix(data, compressed) {
|
|
return nil, nil, errors.New("plan9font: unsupported uncompressed format")
|
|
}
|
|
data = data[len(compressed):]
|
|
|
|
const hdrSize = 5 * 12
|
|
if len(data) < hdrSize {
|
|
return nil, nil, errors.New("plan9font: invalid image: header too short")
|
|
}
|
|
hdr, data := data[:hdrSize], data[hdrSize:]
|
|
|
|
// Distinguish new channel descriptor from old ldepth. Channel descriptors
|
|
// have letters as well as numbers, while ldepths are a single digit
|
|
// formatted as %-11d.
|
|
new := false
|
|
for m := 0; m < 10; m++ {
|
|
if hdr[m] != ' ' {
|
|
new = true
|
|
break
|
|
}
|
|
}
|
|
if hdr[11] != ' ' {
|
|
return nil, nil, errors.New("plan9font: invalid image: bad header")
|
|
}
|
|
if !new {
|
|
return nil, nil, errors.New("plan9font: unsupported ldepth format")
|
|
}
|
|
|
|
depth := 0
|
|
switch s := strings.TrimSpace(string(hdr[:1*12])); s {
|
|
default:
|
|
return nil, nil, fmt.Errorf("plan9font: unsupported pixel format %q", s)
|
|
case "k1":
|
|
depth = 1
|
|
case "k2":
|
|
depth = 2
|
|
}
|
|
r := ator(hdr[1*12:])
|
|
if r.Min.X > r.Max.X || r.Min.Y > r.Max.Y {
|
|
return nil, nil, errors.New("plan9font: invalid image: bad rectangle")
|
|
}
|
|
|
|
width := bytesPerLine(r, depth)
|
|
m = &plan9Image{
|
|
depth: depth,
|
|
width: width,
|
|
rect: r,
|
|
pix: make([]byte, width*r.Dy()),
|
|
}
|
|
|
|
miny := r.Min.Y
|
|
for miny != r.Max.Y {
|
|
if len(data) < 2*12 {
|
|
return nil, nil, errors.New("plan9font: invalid image: data band too short")
|
|
}
|
|
maxy := atoi(data[0*12:])
|
|
nb := atoi(data[1*12:])
|
|
data = data[2*12:]
|
|
|
|
if len(data) < nb {
|
|
return nil, nil, errors.New("plan9font: invalid image: data band length mismatch")
|
|
}
|
|
buf := data[:nb]
|
|
data = data[nb:]
|
|
|
|
if maxy <= miny || r.Max.Y < maxy {
|
|
return nil, nil, fmt.Errorf("plan9font: bad maxy %d", maxy)
|
|
}
|
|
// An old-format image would flip the bits here, but we don't support
|
|
// the old format.
|
|
rr := r
|
|
rr.Min.Y = miny
|
|
rr.Max.Y = maxy
|
|
if err := decompress(m, rr, buf); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
miny = maxy
|
|
}
|
|
return data, m, nil
|
|
}
|
|
|
|
// Compressed data are sequences of byte codes. If the first byte b has the
|
|
// 0x80 bit set, the next (b^0x80)+1 bytes are data. Otherwise, these two bytes
|
|
// specify a previous string to repeat.
|
|
const (
|
|
compShortestMatch = 3 // shortest match possible.
|
|
compWindowSize = 1024 // window size.
|
|
)
|
|
|
|
var (
|
|
errDecompressBufferTooSmall = errors.New("plan9font: decompress: buffer too small")
|
|
errDecompressPhaseError = errors.New("plan9font: decompress: phase error")
|
|
)
|
|
|
|
func decompress(m *plan9Image, r image.Rectangle, data []byte) error {
|
|
if !r.In(m.rect) {
|
|
return errors.New("plan9font: decompress: bad rectangle")
|
|
}
|
|
bpl := bytesPerLine(r, m.depth)
|
|
mem := make([]byte, compWindowSize)
|
|
memi := 0
|
|
omemi := -1
|
|
y := r.Min.Y
|
|
linei := m.byteoffset(r.Min.X, y)
|
|
eline := linei + bpl
|
|
datai := 0
|
|
for {
|
|
if linei == eline {
|
|
y++
|
|
if y == r.Max.Y {
|
|
break
|
|
}
|
|
linei = m.byteoffset(r.Min.X, y)
|
|
eline = linei + bpl
|
|
}
|
|
if datai == len(data) {
|
|
return errDecompressBufferTooSmall
|
|
}
|
|
c := data[datai]
|
|
datai++
|
|
if c >= 128 {
|
|
for cnt := c - 128 + 1; cnt != 0; cnt-- {
|
|
if datai == len(data) {
|
|
return errDecompressBufferTooSmall
|
|
}
|
|
if linei == eline {
|
|
return errDecompressPhaseError
|
|
}
|
|
m.pix[linei] = data[datai]
|
|
linei++
|
|
mem[memi] = data[datai]
|
|
memi++
|
|
datai++
|
|
if memi == len(mem) {
|
|
memi = 0
|
|
}
|
|
}
|
|
} else {
|
|
if datai == len(data) {
|
|
return errDecompressBufferTooSmall
|
|
}
|
|
offs := int(data[datai]) + ((int(c) & 3) << 8) + 1
|
|
datai++
|
|
if memi < offs {
|
|
omemi = memi + (compWindowSize - offs)
|
|
} else {
|
|
omemi = memi - offs
|
|
}
|
|
for cnt := (c >> 2) + compShortestMatch; cnt != 0; cnt-- {
|
|
if linei == eline {
|
|
return errDecompressPhaseError
|
|
}
|
|
m.pix[linei] = mem[omemi]
|
|
linei++
|
|
mem[memi] = mem[omemi]
|
|
memi++
|
|
omemi++
|
|
if omemi == len(mem) {
|
|
omemi = 0
|
|
}
|
|
if memi == len(mem) {
|
|
memi = 0
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ator(b []byte) image.Rectangle {
|
|
return image.Rectangle{atop(b), atop(b[2*12:])}
|
|
}
|
|
|
|
func atop(b []byte) image.Point {
|
|
return image.Pt(atoi(b), atoi(b[12:]))
|
|
}
|
|
|
|
func atoi(b []byte) int {
|
|
i := 0
|
|
for ; i < len(b) && b[i] == ' '; i++ {
|
|
}
|
|
n := 0
|
|
for ; i < len(b) && '0' <= b[i] && b[i] <= '9'; i++ {
|
|
n = n*10 + int(b[i]) - '0'
|
|
}
|
|
return n
|
|
}
|
|
|
|
func bytesPerLine(r image.Rectangle, depth int) int {
|
|
if depth <= 0 || 32 < depth {
|
|
panic("invalid depth")
|
|
}
|
|
var l int
|
|
if r.Min.X >= 0 {
|
|
l = (r.Max.X*depth + 7) / 8
|
|
l -= (r.Min.X * depth) / 8
|
|
} else {
|
|
// Make positive before divide.
|
|
t := (-r.Min.X*depth + 7) / 8
|
|
l = t + (r.Max.X*depth+7)/8
|
|
}
|
|
return l
|
|
}
|