Merge x/exp/shiny/font to x/image/font.
diff --git a/example/font/main.go b/example/font/main.go
new file mode 100644
index 0000000..10909f4
--- /dev/null
+++ b/example/font/main.go
@@ -0,0 +1,105 @@
+// 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.
+
+// +build ignore
+//
+// This build tag means that "go install golang.org/x/exp/shiny/..." doesn't
+// install this example program. Use "go run main.go" to run it.
+
+// Font is a basic example of using fonts.
+package main
+
+import (
+ "flag"
+ "image"
+ "image/color"
+ "image/draw"
+ "image/png"
+ "io/ioutil"
+ "log"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "golang.org/x/exp/shiny/font"
+ "golang.org/x/exp/shiny/font/plan9font"
+ "golang.org/x/image/math/fixed"
+)
+
+var (
+ 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 {
+ return image.Point{
+ X: int(p.X+32) >> 6,
+ Y: int(p.Y+32) >> 6,
+ }
+}
+
+func main() {
+ flag.Parse()
+
+ // TODO: mmap the files.
+ if *fontFlag == "" {
+ flag.Usage()
+ log.Fatal("no font specified")
+ }
+ 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, 300))
+ draw.Draw(dst, dst.Bounds(), image.Black, image.Point{}, draw.Src)
+
+ d := &font.Drawer{
+ Dst: dst,
+ Src: image.White,
+ Face: face,
+ }
+ 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 {
+ log.Fatal(err)
+ }
+ defer out.Close()
+ if err := png.Encode(out, dst); err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/font/font.go b/font/font.go
new file mode 100644
index 0000000..a9b9729
--- /dev/null
+++ b/font/font.go
@@ -0,0 +1,204 @@
+// 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 font defines an interface for font faces, for drawing text on an
+// image.
+//
+// Other packages provide font face implementations. For example, a truetype
+// package would provide one based on .ttf font files.
+package font
+
+// TODO: move this from golang.org/x/exp to golang.org/x/image ??
+
+import (
+ "image"
+ "image/draw"
+ "io"
+
+ "golang.org/x/image/math/fixed"
+)
+
+// TODO: who is responsible for caches (glyph images, glyph indices, kerns)?
+// The Drawer or the Face?
+
+// Face is a font face. Its glyphs are often derived from a font file, such as
+// "Comic_Sans_MS.ttf", but a face has a specific size, style, weight and
+// hinting. For example, the 12pt and 18pt versions of Comic Sans are two
+// different faces, even if derived from the same font file.
+//
+// A Face is not safe for concurrent use by multiple goroutines, as its methods
+// may re-use implementation-specific caches and mask image buffers.
+//
+// To create a Face, look to other packages that implement specific font file
+// formats.
+type Face interface {
+ io.Closer
+
+ // Glyph returns the draw.DrawMask parameters (dr, mask, maskp) to draw r's
+ // glyph at the sub-pixel destination location dot, and that glyph's
+ // advance width.
+ //
+ // It returns !ok if the face does not contain a glyph for r.
+ //
+ // The contents of the mask image returned by one Glyph call may change
+ // after the next Glyph call. Callers that want to cache the mask must make
+ // a copy.
+ Glyph(dot fixed.Point26_6, r rune) (
+ dr image.Rectangle, mask image.Image, maskp image.Point, advance fixed.Int26_6, ok bool)
+
+ // GlyphBounds returns the bounding box of r's glyph, drawn at a dot equal
+ // to the origin, and that glyph's advance width.
+ //
+ // It returns !ok if the face does not contain a glyph for r.
+ //
+ // The glyph's ascent and descent equal -bounds.Min.Y and +bounds.Max.Y. A
+ // visual depiction of what these metrics are is at
+ // https://developer.apple.com/library/mac/documentation/TextFonts/Conceptual/CocoaTextArchitecture/Art/glyph_metrics_2x.png
+ GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool)
+
+ // GlyphAdvance returns the advance width of r's glyph.
+ //
+ // It returns !ok if the face does not contain a glyph for r.
+ GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool)
+
+ // Kern returns the horizontal adjustment for the kerning pair (r0, r1). A
+ // positive kern means to move the glyphs further apart.
+ Kern(r0, r1 rune) fixed.Int26_6
+
+ // TODO: per-font Metrics.
+ // TODO: ColoredGlyph for various emoji?
+ // TODO: Ligatures? Shaping?
+}
+
+// TODO: Drawer.Layout or Drawer.Measure methods to measure text without
+// drawing?
+
+// Drawer draws text on a destination image.
+//
+// A Drawer is not safe for concurrent use by multiple goroutines, since its
+// Face is not.
+type Drawer struct {
+ // Dst is the destination image.
+ Dst draw.Image
+ // Src is the source image.
+ Src image.Image
+ // Face provides the glyph mask images.
+ Face Face
+ // Dot is the baseline location to draw the next glyph. The majority of the
+ // affected pixels will be above and to the right of the dot, but some may
+ // be below or to the left. For example, drawing a 'j' in an italic face
+ // may affect pixels below and to the left of the dot.
+ Dot fixed.Point26_6
+
+ // TODO: Clip image.Image?
+ // TODO: SrcP image.Point for Src images other than *image.Uniform? How
+ // does it get updated during DrawString?
+}
+
+// TODO: should DrawString return the last rune drawn, so the next DrawString
+// call can kern beforehand? Or should that be the responsibility of the caller
+// if they really want to do that, since they have to explicitly shift d.Dot
+// anyway?
+//
+// In general, we'd have a DrawBytes([]byte) and DrawRuneReader(io.RuneReader)
+// and the last case can't assume that you can rewind the stream.
+//
+// TODO: how does this work with line breaking: drawing text up until a
+// vertical line? Should DrawString return the number of runes drawn?
+
+// DrawString draws s at the dot and advances the dot's location.
+func (d *Drawer) DrawString(s string) {
+ var prevC rune
+ for i, c := range s {
+ if i != 0 {
+ d.Dot.X += d.Face.Kern(prevC, c)
+ }
+ dr, mask, maskp, advance, ok := d.Face.Glyph(d.Dot, c)
+ if !ok {
+ // TODO: is falling back on the U+FFFD glyph the responsibility of
+ // the Drawer or the Face?
+ // TODO: set prevC = '\ufffd'?
+ continue
+ }
+ draw.DrawMask(d.Dst, dr, d.Src, image.Point{}, mask, maskp, draw.Over)
+ d.Dot.X += advance
+ prevC = c
+ }
+}
+
+// MeasureString returns how far dot would advance by drawing s.
+func (d *Drawer) MeasureString(s string) (advance fixed.Int26_6) {
+ var prevC rune
+ for i, c := range s {
+ if i != 0 {
+ advance += d.Face.Kern(prevC, c)
+ }
+ a, ok := d.Face.GlyphAdvance(c)
+ if !ok {
+ // TODO: is falling back on the U+FFFD glyph the responsibility of
+ // the Drawer or the Face?
+ // TODO: set prevC = '\ufffd'?
+ continue
+ }
+ advance += a
+ prevC = c
+ }
+ return advance
+}
+
+// Hinting selects how to quantize a vector font's glyph nodes.
+//
+// Not all fonts support hinting.
+type Hinting int
+
+const (
+ HintingNone Hinting = iota
+ HintingVertical
+ HintingFull
+)
+
+// Stretch selects a normal, condensed, or expanded face.
+//
+// Not all fonts support stretches.
+type Stretch int
+
+const (
+ StretchUltraCondensed Stretch = -4
+ StretchExtraCondensed Stretch = -3
+ StretchCondensed Stretch = -2
+ StretchSemiCondensed Stretch = -1
+ StretchNormal Stretch = +0
+ StretchSemiExpanded Stretch = +1
+ StretchExpanded Stretch = +2
+ StretchExtraExpanded Stretch = +3
+ StretchUltraExpanded Stretch = +4
+)
+
+// Style selects a normal, italic, or oblique face.
+//
+// Not all fonts support styles.
+type Style int
+
+const (
+ StyleNormal Style = iota
+ StyleItalic
+ StyleOblique
+)
+
+// Weight selects a normal, light or bold face.
+//
+// Not all fonts support weights.
+type Weight int
+
+const (
+ WeightThin Weight = 100
+ WeightExtraLight Weight = 200
+ WeightLight Weight = 300
+ WeightNormal Weight = 400
+ WeightMedium Weight = 500
+ WeightSemiBold Weight = 600
+ WeightBold Weight = 700
+ WeightExtraBold Weight = 800
+ WeightBlack Weight = 900
+)
diff --git a/font/plan9font/example_test.go b/font/plan9font/example_test.go
new file mode 100644
index 0000000..071fcfa
--- /dev/null
+++ b/font/plan9font/example_test.go
@@ -0,0 +1,93 @@
+// 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_test
+
+import (
+ "image"
+ "image/draw"
+ "io/ioutil"
+ "log"
+ "os"
+ "path"
+ "path/filepath"
+
+ "golang.org/x/exp/shiny/font"
+ "golang.org/x/exp/shiny/font/plan9font"
+ "golang.org/x/image/math/fixed"
+)
+
+func ExampleParseFont() {
+ readFile := func(name string) ([]byte, error) {
+ return ioutil.ReadFile(filepath.FromSlash(path.Join("../testdata/fixed", name)))
+ }
+ fontData, err := readFile("unicode.7x13.font")
+ if err != nil {
+ log.Fatal(err)
+ }
+ face, err := plan9font.ParseFont(fontData, readFile)
+ if err != nil {
+ log.Fatal(err)
+ }
+ // TODO: derive the ascent from the face's metrics.
+ const ascent = 11
+
+ dst := image.NewRGBA(image.Rect(0, 0, 4*7, 13))
+ draw.Draw(dst, dst.Bounds(), image.Black, image.Point{}, draw.Src)
+ d := &font.Drawer{
+ Dst: dst,
+ Src: image.White,
+ Face: face,
+ Dot: fixed.P(0, ascent),
+ }
+ // Draw:
+ // - U+0053 LATIN CAPITAL LETTER S
+ // - U+03A3 GREEK CAPITAL LETTER SIGMA
+ // - U+222B INTEGRAL
+ // - U+3055 HIRAGANA LETTER SA
+ // The testdata does not contain the CJK subfont files, so U+3055 HIRAGANA
+ // LETTER SA (さ) should be rendered as U+FFFD REPLACEMENT CHARACTER (�).
+ //
+ // The missing subfont file will trigger an "open
+ // ../testdata/shinonome/k12.3000: no such file or directory" log message.
+ // This is expected and can be ignored.
+ d.DrawString("SΣ∫さ")
+
+ // Convert the dst image to ASCII art.
+ var out []byte
+ b := dst.Bounds()
+ for y := b.Min.Y; y < b.Max.Y; y++ {
+ out = append(out, '0'+byte(y%10), ' ')
+ for x := b.Min.X; x < b.Max.X; x++ {
+ if dst.RGBAAt(x, y).R > 0 {
+ out = append(out, 'X')
+ } else {
+ out = append(out, '.')
+ }
+ }
+ // Highlight the last row before the baseline. Glyphs like 'S' without
+ // descenders should not affect any pixels whose Y coordinate is >= the
+ // baseline.
+ if y == ascent-1 {
+ out = append(out, '_')
+ }
+ out = append(out, '\n')
+ }
+ os.Stdout.Write(out)
+
+ // Output:
+ // 0 ..................X.........
+ // 1 .................X.X........
+ // 2 .XXXX..XXXXXX....X.....XXX..
+ // 3 X....X.X.........X....XX.XX.
+ // 4 X.......X........X....X.X.X.
+ // 5 X........X.......X....XXX.X.
+ // 6 .XXXX.....X......X....XX.XX.
+ // 7 .....X...X.......X....XX.XX.
+ // 8 .....X..X........X....XXXXX.
+ // 9 X....X.X.........X....XX.XX.
+ // 0 .XXXX..XXXXXX....X.....XXX.._
+ // 1 ...............X.X..........
+ // 2 ................X...........
+}
diff --git a/font/plan9font/plan9font.go b/font/plan9font/plan9font.go
new file mode 100644
index 0000000..89f0a2d
--- /dev/null
+++ b/font/plan9font/plan9font.go
@@ -0,0 +1,556 @@
+// 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
+
+// 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"
+ "errors"
+ "fmt"
+ "image"
+ "image/color"
+ "log"
+ "strconv"
+ "strings"
+
+ "golang.org/x/exp/shiny/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 *plan9Image // 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) 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) 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")
+ }
+ return &subface{
+ firstRune: firstRune,
+ n: n,
+ height: height,
+ ascent: ascent,
+ fontchars: parseFontchars(data),
+ img: m,
+ }, 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) {
+ 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 color.Alpha{0xff}
+ }
+ return color.Alpha{0x00}
+ 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 color.Alpha{y}
+ }
+ }
+ return color.Alpha{0x00}
+}
+
+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
+}
diff --git a/font/testdata/fixed/7x13.0000 b/font/testdata/fixed/7x13.0000
new file mode 100644
index 0000000..9509cdf
--- /dev/null
+++ b/font/testdata/fixed/7x13.0000
Binary files differ
diff --git a/font/testdata/fixed/7x13.0100 b/font/testdata/fixed/7x13.0100
new file mode 100644
index 0000000..0a79f55
--- /dev/null
+++ b/font/testdata/fixed/7x13.0100
Binary files differ
diff --git a/font/testdata/fixed/7x13.0200 b/font/testdata/fixed/7x13.0200
new file mode 100644
index 0000000..e25247e
--- /dev/null
+++ b/font/testdata/fixed/7x13.0200
Binary files differ
diff --git a/font/testdata/fixed/7x13.0300 b/font/testdata/fixed/7x13.0300
new file mode 100644
index 0000000..86eb33f
--- /dev/null
+++ b/font/testdata/fixed/7x13.0300
Binary files differ
diff --git a/font/testdata/fixed/7x13.0400 b/font/testdata/fixed/7x13.0400
new file mode 100644
index 0000000..43300ad
--- /dev/null
+++ b/font/testdata/fixed/7x13.0400
Binary files differ
diff --git a/font/testdata/fixed/7x13.0500 b/font/testdata/fixed/7x13.0500
new file mode 100644
index 0000000..2d93267
--- /dev/null
+++ b/font/testdata/fixed/7x13.0500
Binary files differ
diff --git a/font/testdata/fixed/7x13.0E00 b/font/testdata/fixed/7x13.0E00
new file mode 100644
index 0000000..7c51a1e
--- /dev/null
+++ b/font/testdata/fixed/7x13.0E00
Binary files differ
diff --git a/font/testdata/fixed/7x13.1000 b/font/testdata/fixed/7x13.1000
new file mode 100644
index 0000000..019698c
--- /dev/null
+++ b/font/testdata/fixed/7x13.1000
Binary files differ
diff --git a/font/testdata/fixed/7x13.1600 b/font/testdata/fixed/7x13.1600
new file mode 100644
index 0000000..f69a977
--- /dev/null
+++ b/font/testdata/fixed/7x13.1600
Binary files differ
diff --git a/font/testdata/fixed/7x13.1E00 b/font/testdata/fixed/7x13.1E00
new file mode 100644
index 0000000..3bc5068
--- /dev/null
+++ b/font/testdata/fixed/7x13.1E00
Binary files differ
diff --git a/font/testdata/fixed/7x13.1F00 b/font/testdata/fixed/7x13.1F00
new file mode 100644
index 0000000..43b320b
--- /dev/null
+++ b/font/testdata/fixed/7x13.1F00
Binary files differ
diff --git a/font/testdata/fixed/7x13.2000 b/font/testdata/fixed/7x13.2000
new file mode 100644
index 0000000..f9244e1
--- /dev/null
+++ b/font/testdata/fixed/7x13.2000
Binary files differ
diff --git a/font/testdata/fixed/7x13.2100 b/font/testdata/fixed/7x13.2100
new file mode 100644
index 0000000..c565abb
--- /dev/null
+++ b/font/testdata/fixed/7x13.2100
Binary files differ
diff --git a/font/testdata/fixed/7x13.2200 b/font/testdata/fixed/7x13.2200
new file mode 100644
index 0000000..a992d35
--- /dev/null
+++ b/font/testdata/fixed/7x13.2200
Binary files differ
diff --git a/font/testdata/fixed/7x13.2300 b/font/testdata/fixed/7x13.2300
new file mode 100644
index 0000000..8ff099d
--- /dev/null
+++ b/font/testdata/fixed/7x13.2300
Binary files differ
diff --git a/font/testdata/fixed/7x13.2400 b/font/testdata/fixed/7x13.2400
new file mode 100644
index 0000000..99927a1
--- /dev/null
+++ b/font/testdata/fixed/7x13.2400
Binary files differ
diff --git a/font/testdata/fixed/7x13.2500 b/font/testdata/fixed/7x13.2500
new file mode 100644
index 0000000..60dc224
--- /dev/null
+++ b/font/testdata/fixed/7x13.2500
Binary files differ
diff --git a/font/testdata/fixed/7x13.2600 b/font/testdata/fixed/7x13.2600
new file mode 100644
index 0000000..1b393c2
--- /dev/null
+++ b/font/testdata/fixed/7x13.2600
Binary files differ
diff --git a/font/testdata/fixed/7x13.2700 b/font/testdata/fixed/7x13.2700
new file mode 100644
index 0000000..c39a572
--- /dev/null
+++ b/font/testdata/fixed/7x13.2700
Binary files differ
diff --git a/font/testdata/fixed/7x13.2800 b/font/testdata/fixed/7x13.2800
new file mode 100644
index 0000000..c7572de
--- /dev/null
+++ b/font/testdata/fixed/7x13.2800
Binary files differ
diff --git a/font/testdata/fixed/7x13.2A00 b/font/testdata/fixed/7x13.2A00
new file mode 100644
index 0000000..71791ac
--- /dev/null
+++ b/font/testdata/fixed/7x13.2A00
Binary files differ
diff --git a/font/testdata/fixed/7x13.3000 b/font/testdata/fixed/7x13.3000
new file mode 100644
index 0000000..fb830f4
--- /dev/null
+++ b/font/testdata/fixed/7x13.3000
Binary files differ
diff --git a/font/testdata/fixed/7x13.FB00 b/font/testdata/fixed/7x13.FB00
new file mode 100644
index 0000000..3a0b30a
--- /dev/null
+++ b/font/testdata/fixed/7x13.FB00
Binary files differ
diff --git a/font/testdata/fixed/7x13.FE00 b/font/testdata/fixed/7x13.FE00
new file mode 100644
index 0000000..3989d26
--- /dev/null
+++ b/font/testdata/fixed/7x13.FE00
Binary files differ
diff --git a/font/testdata/fixed/7x13.FF00 b/font/testdata/fixed/7x13.FF00
new file mode 100644
index 0000000..78ed398
--- /dev/null
+++ b/font/testdata/fixed/7x13.FF00
Binary files differ
diff --git a/font/testdata/fixed/README b/font/testdata/fixed/README
new file mode 100644
index 0000000..a39f8a5
--- /dev/null
+++ b/font/testdata/fixed/README
@@ -0,0 +1,9 @@
+These font files were copied from the Plan 9 Port's font/fixed directory. The
+README in that directory states that: "These fonts are converted from the BDFs
+in the XFree86 distribution. They were all marked as public domain."
+
+The Plan 9 Port is at https://github.com/9fans/plan9port and the copy was made
+from commit a78b1841 (2015-08-18).
+
+The unicode.7x13.font file also refers to a ../shinonome directory, but this
+testdata does not include those subfont files.
diff --git a/font/testdata/fixed/unicode.7x13.font b/font/testdata/fixed/unicode.7x13.font
new file mode 100644
index 0000000..337b428
--- /dev/null
+++ b/font/testdata/fixed/unicode.7x13.font
@@ -0,0 +1,68 @@
+13 10
+0x0000 0x001F 7x13.2400
+0x0000 0x00FF 7x13.0000
+0x0100 0x01FF 7x13.0100
+0x0200 0x02FF 7x13.0200
+0x0300 0x03FF 7x13.0300
+0x0400 0x04FF 7x13.0400
+0x0500 0x05FF 7x13.0500
+0x0E00 0x0EFF 7x13.0E00
+0x1000 0x10FF 7x13.1000
+0x1600 0x16FF 7x13.1600
+0x1E00 0x1EFF 7x13.1E00
+0x1F00 0x1FFF 7x13.1F00
+0x2000 0x20FF 7x13.2000
+0x2100 0x21FF 7x13.2100
+0x2200 0x22FF 7x13.2200
+0x2300 0x23FF 7x13.2300
+0x2400 0x24FF 7x13.2400
+0x2500 0x25FF 7x13.2500
+0x2600 0x26FF 7x13.2600
+0x2700 0x27FF 7x13.2700
+0x2800 0x28FF 7x13.2800
+0x2A00 0x2AFF 7x13.2A00
+0x3000 0x30fe ../shinonome/k12.3000
+0x4e00 0x4ffe ../shinonome/k12.4e00
+0x5005 0x51fe ../shinonome/k12.5005
+0x5200 0x53fa ../shinonome/k12.5200
+0x5401 0x55fe ../shinonome/k12.5401
+0x5606 0x57fc ../shinonome/k12.5606
+0x5800 0x59ff ../shinonome/k12.5800
+0x5a01 0x5bff ../shinonome/k12.5a01
+0x5c01 0x5dfe ../shinonome/k12.5c01
+0x5e02 0x5fff ../shinonome/k12.5e02
+0x600e 0x61ff ../shinonome/k12.600e
+0x6200 0x63fa ../shinonome/k12.6200
+0x6406 0x65fb ../shinonome/k12.6406
+0x6602 0x67ff ../shinonome/k12.6602
+0x6802 0x69ff ../shinonome/k12.6802
+0x6a02 0x6bf3 ../shinonome/k12.6a02
+0x6c08 0x6dfb ../shinonome/k12.6c08
+0x6e05 0x6ffe ../shinonome/k12.6e05
+0x7001 0x71ff ../shinonome/k12.7001
+0x7206 0x73fe ../shinonome/k12.7206
+0x7403 0x75ff ../shinonome/k12.7403
+0x7601 0x77fc ../shinonome/k12.7601
+0x7802 0x79fb ../shinonome/k12.7802
+0x7a00 0x7bf7 ../shinonome/k12.7a00
+0x7c00 0x7dfb ../shinonome/k12.7c00
+0x7e01 0x7ffc ../shinonome/k12.7e01
+0x8000 0x81fe ../shinonome/k12.8000
+0x8201 0x83fd ../shinonome/k12.8201
+0x8403 0x85fe ../shinonome/k12.8403
+0x8602 0x87fe ../shinonome/k12.8602
+0x8805 0x89f8 ../shinonome/k12.8805
+0x8a00 0x8b9a ../shinonome/k12.8a00
+0x8c37 0x8dff ../shinonome/k12.8c37
+0x8e08 0x8ffd ../shinonome/k12.8e08
+0x9000 0x91ff ../shinonome/k12.9000
+0x920d 0x93e8 ../shinonome/k12.920d
+0x9403 0x95e5 ../shinonome/k12.9403
+0x961c 0x97ff ../shinonome/k12.961c
+0x9801 0x99ff ../shinonome/k12.9801
+0x9a01 0x9bf5 ../shinonome/k12.9a01
+0x9c04 0x9dfd ../shinonome/k12.9c04
+0x9e1a 0x9fa0 ../shinonome/k12.9e1a
+0xFB00 0xFBFF 7x13.FB00
+0xFE00 0xFEFF 7x13.FE00
+0xFF00 0xFFFF 7x13.FF00