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:
parent
eecb4e626f
commit
e87ffe258c
|
@ -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 err != nil {
|
if strings.HasSuffix(*fontFlag, ".font") {
|
||||||
log.Fatal(err)
|
fontData, err := ioutil.ReadFile(*fontFlag)
|
||||||
}
|
if err != nil {
|
||||||
face, err := plan9font.ParseSubfont(fontData, rune(*firstRune))
|
log.Fatal(err)
|
||||||
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)
|
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,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
dot0 := pt(d.Dot)
|
ss := []string{
|
||||||
d.DrawString("The quick brown fox jumps over the lazy dog.")
|
"The quick brown fox jumps over the lazy dog.",
|
||||||
dot1 := pt(d.Dot)
|
"Hello, 世界.",
|
||||||
|
"U+FFFD is \ufffd.",
|
||||||
dst.SetRGBA(dot0.X, dot0.Y, color.RGBA{0xff, 0x00, 0x00, 0xff})
|
}
|
||||||
dst.SetRGBA(dot1.X, dot1.Y, color.RGBA{0x00, 0x00, 0xff, 0xff})
|
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")
|
out, err := os.Create("out.png")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -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?
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue
Block a user