shiny/font/plan9font: implement ParseFont.

Also delete font.MultiFace. We can resurrect a font.MultiFace type if we
need it in the future, but for now, it's simpler if it lives in the
plan9font package.

Change-Id: I1493b47696c323424e7d91cb7fac15505bfdd023
Reviewed-on: https://go-review.googlesource.com/13520
Reviewed-by: Rob Pike <r@golang.org>
This commit is contained in:
Nigel Tao 2015-08-11 17:49:26 +10:00
parent eecb4e626f
commit e87ffe258c
3 changed files with 184 additions and 41 deletions

View File

@ -19,6 +19,8 @@ import (
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
"path/filepath"
"strings"
"golang.org/x/exp/shiny/font" "golang.org/x/exp/shiny/font"
"golang.org/x/exp/shiny/font/plan9font" "golang.org/x/exp/shiny/font/plan9font"
@ -26,8 +28,9 @@ import (
) )
var ( var (
subfont = flag.String("subfont", "", `filename of the Plan 9 subfont file, such as "lucsans/lsr.14"`) fontFlag = flag.String("font", "",
firstRune = flag.Int("firstrune", 0, "the Unicode code point of the first rune in the subfont file") `filename of the Plan 9 font or subfont file, such as "lucsans/unicode.8.font" or "lucsans/lsr.14"`)
firstRuneFlag = flag.Int("firstrune", 0, "the Unicode code point of the first rune in the subfont file")
) )
func pt(p fixed.Point26_6) image.Point { func pt(p fixed.Point26_6) image.Point {
@ -40,38 +43,56 @@ func pt(p fixed.Point26_6) image.Point {
func main() { func main() {
flag.Parse() flag.Parse()
// TODO: mmap the file. // TODO: mmap the files.
if *subfont == "" { if *fontFlag == "" {
flag.Usage() flag.Usage()
log.Fatal("no subfont specified") log.Fatal("no font specified")
} }
fontData, err := ioutil.ReadFile(*subfont) var face font.Face
if strings.HasSuffix(*fontFlag, ".font") {
fontData, err := ioutil.ReadFile(*fontFlag)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
face, err := plan9font.ParseSubfont(fontData, rune(*firstRune)) dir := filepath.Dir(*fontFlag)
face, err = plan9font.ParseFont(fontData, func(name string) ([]byte, error) {
return ioutil.ReadFile(filepath.Join(dir, filepath.FromSlash(name)))
})
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
} else {
fontData, err := ioutil.ReadFile(*fontFlag)
if err != nil {
log.Fatal(err)
}
face, err = plan9font.ParseSubfont(fontData, rune(*firstRuneFlag))
if err != nil {
log.Fatal(err)
}
}
dst := image.NewRGBA(image.Rect(0, 0, 800, 100)) dst := image.NewRGBA(image.Rect(0, 0, 800, 300))
draw.Draw(dst, dst.Bounds(), image.Black, image.Point{}, draw.Src) draw.Draw(dst, dst.Bounds(), image.Black, image.Point{}, draw.Src)
d := &font.Drawer{ d := &font.Drawer{
Dst: dst, Dst: dst,
Src: image.White, Src: image.White,
Face: face, Face: face,
Dot: fixed.Point26_6{
X: 20 << 6,
Y: 80 << 6,
},
} }
ss := []string{
"The quick brown fox jumps over the lazy dog.",
"Hello, 世界.",
"U+FFFD is \ufffd.",
}
for i, s := range ss {
d.Dot = fixed.P(20, 100*i+80)
dot0 := pt(d.Dot) dot0 := pt(d.Dot)
d.DrawString("The quick brown fox jumps over the lazy dog.") d.DrawString(s)
dot1 := pt(d.Dot) dot1 := pt(d.Dot)
dst.SetRGBA(dot0.X, dot0.Y, color.RGBA{0xff, 0x00, 0x00, 0xff}) dst.SetRGBA(dot0.X, dot0.Y, color.RGBA{0xff, 0x00, 0x00, 0xff})
dst.SetRGBA(dot1.X, dot1.Y, color.RGBA{0x00, 0x00, 0xff, 0xff}) dst.SetRGBA(dot1.X, dot1.Y, color.RGBA{0x00, 0x00, 0xff, 0xff})
}
out, err := os.Create("out.png") out, err := os.Create("out.png")
if err != nil { if err != nil {

View File

@ -55,10 +55,6 @@ type Face interface {
// TODO: Ligatures? Shaping? // TODO: Ligatures? Shaping?
} }
type MultiFace struct {
// TODO.
}
// TODO: Drawer.Layout or Drawer.Measure methods to measure text without // TODO: Drawer.Layout or Drawer.Measure methods to measure text without
// drawing? // drawing?

View File

@ -2,12 +2,14 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// Package plan9font implements font faces for the Plan 9 font file format. // 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 package plan9font
// TODO: have a face use an *image.Alpha instead of plan9Image implementing the // TODO: have a subface use an *image.Alpha instead of plan9Image implementing
// image.Image interface? The image/draw code has a fast path for *image.Alpha // the image.Image interface? The image/draw code has a fast path for
// masks. // *image.Alpha masks.
import ( import (
"bytes" "bytes"
@ -15,7 +17,8 @@ import (
"fmt" "fmt"
"image" "image"
"image/color" "image/color"
"io" "log"
"strconv"
"strings" "strings"
"golang.org/x/exp/shiny/font" "golang.org/x/exp/shiny/font"
@ -49,8 +52,8 @@ func parseFontchars(p []byte) []fontchar {
return fc return fc
} }
// face implements font.Face. // subface implements font.Face for a Plan 9 subfont.
type face struct { type subface struct {
firstRune rune // First rune in the subfont. firstRune rune // First rune in the subfont.
n int // Number of characters in the subfont. n int // Number of characters in the subfont.
height int // Inter-line spacing. height int // Inter-line spacing.
@ -59,10 +62,10 @@ type face struct {
img *plan9Image // Image holding the glyphs. img *plan9Image // Image holding the glyphs.
} }
func (f *face) Close() error { return nil } func (f *subface) Close() error { return nil }
func (f *face) Kern(r0, r1 rune) fixed.Int26_6 { return 0 } func (f *subface) Kern(r0, r1 rune) fixed.Int26_6 { return 0 }
func (f *face) Glyph(dot fixed.Point26_6, r rune) ( func (f *subface) Glyph(dot fixed.Point26_6, r rune) (
newDot fixed.Point26_6, dr image.Rectangle, mask image.Image, maskp image.Point, ok bool) { newDot fixed.Point26_6, dr image.Rectangle, mask image.Image, maskp image.Point, ok bool) {
r -= f.firstRune r -= f.firstRune
@ -91,9 +94,132 @@ func (f *face) Glyph(dot fixed.Point26_6, r rune) (
return newDot, dr, f.img, image.Point{int(i.x), int(i.top)}, true return newDot, dr, f.img, image.Point{int(i.x), int(i.top)}, true
} }
// ParseFont parses a Plan 9 font file. // runeRange maps a single rune range [lo, hi] to a lazily loaded subface. Both
func ParseFont(data []byte, openFunc func(name string) (io.ReadCloser, error)) (*font.MultiFace, error) { // ends of the range are inclusive.
panic("TODO") 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) Glyph(dot fixed.Point26_6, r rune) (
newDot fixed.Point26_6, dr image.Rectangle, mask image.Image, maskp image.Point, ok bool) {
// 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.Glyph(dot, rr)
}
}
return fixed.Point26_6{}, image.Rectangle{}, nil, image.Point{}, false
}
// 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. // ParseSubfont parses a Plan 9 subfont file.
@ -116,7 +242,7 @@ func ParseSubfont(data []byte, firstRune rune) (font.Face, error) {
if len(data) != 6*(n+1) { if len(data) != 6*(n+1) {
return nil, errors.New("plan9font: invalid subfont: data length mismatch") return nil, errors.New("plan9font: invalid subfont: data length mismatch")
} }
return &face{ return &subface{
firstRune: firstRune, firstRune: firstRune,
n: n, n: n,
height: height, height: height,