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>
diff --git a/example/font/main.go b/example/font/main.go
index 34b16f6..10909f4 100644
--- a/example/font/main.go
+++ b/example/font/main.go
@@ -19,6 +19,8 @@
 	"io/ioutil"
 	"log"
 	"os"
+	"path/filepath"
+	"strings"
 
 	"golang.org/x/exp/shiny/font"
 	"golang.org/x/exp/shiny/font/plan9font"
@@ -26,8 +28,9 @@
 )
 
 var (
-	subfont   = flag.String("subfont", "", `filename of the Plan 9 subfont file, such as "lucsans/lsr.14"`)
-	firstRune = flag.Int("firstrune", 0, "the Unicode code point of the first rune in the subfont file")
+	fontFlag = flag.String("font", "",
+		`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 {
@@ -40,38 +43,56 @@
 func main() {
 	flag.Parse()
 
-	// TODO: mmap the file.
-	if *subfont == "" {
+	// TODO: mmap the files.
+	if *fontFlag == "" {
 		flag.Usage()
-		log.Fatal("no subfont specified")
+		log.Fatal("no font specified")
 	}
-	fontData, err := ioutil.ReadFile(*subfont)
-	if err != nil {
-		log.Fatal(err)
-	}
-	face, err := plan9font.ParseSubfont(fontData, rune(*firstRune))
-	if err != nil {
-		log.Fatal(err)
+	var face font.Face
+	if strings.HasSuffix(*fontFlag, ".font") {
+		fontData, err := ioutil.ReadFile(*fontFlag)
+		if err != nil {
+			log.Fatal(err)
+		}
+		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 {
+			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)
 
 	d := &font.Drawer{
 		Dst:  dst,
 		Src:  image.White,
 		Face: face,
-		Dot: fixed.Point26_6{
-			X: 20 << 6,
-			Y: 80 << 6,
-		},
 	}
-	dot0 := pt(d.Dot)
-	d.DrawString("The quick brown fox jumps over the lazy dog.")
-	dot1 := pt(d.Dot)
-
-	dst.SetRGBA(dot0.X, dot0.Y, color.RGBA{0xff, 0x00, 0x00, 0xff})
-	dst.SetRGBA(dot1.X, dot1.Y, color.RGBA{0x00, 0x00, 0xff, 0xff})
+	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)
+		d.DrawString(s)
+		dot1 := pt(d.Dot)
+		dst.SetRGBA(dot0.X, dot0.Y, color.RGBA{0xff, 0x00, 0x00, 0xff})
+		dst.SetRGBA(dot1.X, dot1.Y, color.RGBA{0x00, 0x00, 0xff, 0xff})
+	}
 
 	out, err := os.Create("out.png")
 	if err != nil {
diff --git a/font/font.go b/font/font.go
index 7698772..b3647f5 100644
--- a/font/font.go
+++ b/font/font.go
@@ -55,10 +55,6 @@
 	// TODO: Ligatures? Shaping?
 }
 
-type MultiFace struct {
-	// TODO.
-}
-
 // TODO: Drawer.Layout or Drawer.Measure methods to measure text without
 // drawing?
 
diff --git a/font/plan9font/plan9font.go b/font/plan9font/plan9font.go
index 3193ec7..35d6ee8 100644
--- a/font/plan9font/plan9font.go
+++ b/font/plan9font/plan9font.go
@@ -2,12 +2,14 @@
 // 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 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
 
-// TODO: have a face use an *image.Alpha instead of plan9Image implementing the
-// image.Image interface? The image/draw code has a fast path for *image.Alpha
-// masks.
+// TODO: have a subface use an *image.Alpha instead of plan9Image implementing
+// the image.Image interface? The image/draw code has a fast path for
+// *image.Alpha masks.
 
 import (
 	"bytes"
@@ -15,7 +17,8 @@
 	"fmt"
 	"image"
 	"image/color"
-	"io"
+	"log"
+	"strconv"
 	"strings"
 
 	"golang.org/x/exp/shiny/font"
@@ -49,8 +52,8 @@
 	return fc
 }
 
-// face implements font.Face.
-type face struct {
+// 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.
@@ -59,10 +62,10 @@
 	img       *plan9Image // Image holding the glyphs.
 }
 
-func (f *face) Close() error                   { return nil }
-func (f *face) Kern(r0, r1 rune) fixed.Int26_6 { return 0 }
+func (f *subface) Close() error                   { return nil }
+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) {
 
 	r -= f.firstRune
@@ -91,9 +94,132 @@
 	return newDot, dr, f.img, image.Point{int(i.x), int(i.top)}, true
 }
 
-// ParseFont parses a Plan 9 font file.
-func ParseFont(data []byte, openFunc func(name string) (io.ReadCloser, error)) (*font.MultiFace, error) {
-	panic("TODO")
+// 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) 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.
@@ -116,7 +242,7 @@
 	if len(data) != 6*(n+1) {
 		return nil, errors.New("plan9font: invalid subfont: data length mismatch")
 	}
-	return &face{
+	return &subface{
 		firstRune: firstRune,
 		n:         n,
 		height:    height,