Merge x/exp/shiny/font to x/image/font.

This commit is contained in:
Nigel Tao 2015-09-01 14:51:18 +10:00
commit 00f88f596f
31 changed files with 1035 additions and 0 deletions

105
example/font/main.go Normal file
View File

@ -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)
}
}

204
font/font.go Normal file
View File

@ -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
)

View File

@ -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 (<28>).
//
// 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...........
}

556
font/plan9font/plan9font.go Normal file
View File

@ -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
}

BIN
font/testdata/fixed/7x13.0000 vendored Normal file

Binary file not shown.

BIN
font/testdata/fixed/7x13.0100 vendored Normal file

Binary file not shown.

BIN
font/testdata/fixed/7x13.0200 vendored Normal file

Binary file not shown.

BIN
font/testdata/fixed/7x13.0300 vendored Normal file

Binary file not shown.

BIN
font/testdata/fixed/7x13.0400 vendored Normal file

Binary file not shown.

BIN
font/testdata/fixed/7x13.0500 vendored Normal file

Binary file not shown.

BIN
font/testdata/fixed/7x13.0E00 vendored Normal file

Binary file not shown.

BIN
font/testdata/fixed/7x13.1000 vendored Normal file

Binary file not shown.

BIN
font/testdata/fixed/7x13.1600 vendored Normal file

Binary file not shown.

BIN
font/testdata/fixed/7x13.1E00 vendored Normal file

Binary file not shown.

BIN
font/testdata/fixed/7x13.1F00 vendored Normal file

Binary file not shown.

BIN
font/testdata/fixed/7x13.2000 vendored Normal file

Binary file not shown.

BIN
font/testdata/fixed/7x13.2100 vendored Normal file

Binary file not shown.

BIN
font/testdata/fixed/7x13.2200 vendored Normal file

Binary file not shown.

BIN
font/testdata/fixed/7x13.2300 vendored Normal file

Binary file not shown.

BIN
font/testdata/fixed/7x13.2400 vendored Normal file

Binary file not shown.

BIN
font/testdata/fixed/7x13.2500 vendored Normal file

Binary file not shown.

BIN
font/testdata/fixed/7x13.2600 vendored Normal file

Binary file not shown.

BIN
font/testdata/fixed/7x13.2700 vendored Normal file

Binary file not shown.

BIN
font/testdata/fixed/7x13.2800 vendored Normal file

Binary file not shown.

BIN
font/testdata/fixed/7x13.2A00 vendored Normal file

Binary file not shown.

BIN
font/testdata/fixed/7x13.3000 vendored Normal file

Binary file not shown.

BIN
font/testdata/fixed/7x13.FB00 vendored Normal file

Binary file not shown.

BIN
font/testdata/fixed/7x13.FE00 vendored Normal file

Binary file not shown.

BIN
font/testdata/fixed/7x13.FF00 vendored Normal file

Binary file not shown.

9
font/testdata/fixed/README vendored Normal file
View File

@ -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.

68
font/testdata/fixed/unicode.7x13.font vendored Normal file
View File

@ -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