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 Binary files /dev/null and b/font/testdata/fixed/7x13.0000 differ diff --git a/font/testdata/fixed/7x13.0100 b/font/testdata/fixed/7x13.0100 new file mode 100644 index 0000000..0a79f55 Binary files /dev/null and b/font/testdata/fixed/7x13.0100 differ diff --git a/font/testdata/fixed/7x13.0200 b/font/testdata/fixed/7x13.0200 new file mode 100644 index 0000000..e25247e Binary files /dev/null and b/font/testdata/fixed/7x13.0200 differ diff --git a/font/testdata/fixed/7x13.0300 b/font/testdata/fixed/7x13.0300 new file mode 100644 index 0000000..86eb33f Binary files /dev/null and b/font/testdata/fixed/7x13.0300 differ diff --git a/font/testdata/fixed/7x13.0400 b/font/testdata/fixed/7x13.0400 new file mode 100644 index 0000000..43300ad Binary files /dev/null and b/font/testdata/fixed/7x13.0400 differ diff --git a/font/testdata/fixed/7x13.0500 b/font/testdata/fixed/7x13.0500 new file mode 100644 index 0000000..2d93267 Binary files /dev/null and b/font/testdata/fixed/7x13.0500 differ diff --git a/font/testdata/fixed/7x13.0E00 b/font/testdata/fixed/7x13.0E00 new file mode 100644 index 0000000..7c51a1e Binary files /dev/null and b/font/testdata/fixed/7x13.0E00 differ diff --git a/font/testdata/fixed/7x13.1000 b/font/testdata/fixed/7x13.1000 new file mode 100644 index 0000000..019698c Binary files /dev/null and b/font/testdata/fixed/7x13.1000 differ diff --git a/font/testdata/fixed/7x13.1600 b/font/testdata/fixed/7x13.1600 new file mode 100644 index 0000000..f69a977 Binary files /dev/null and b/font/testdata/fixed/7x13.1600 differ diff --git a/font/testdata/fixed/7x13.1E00 b/font/testdata/fixed/7x13.1E00 new file mode 100644 index 0000000..3bc5068 Binary files /dev/null and b/font/testdata/fixed/7x13.1E00 differ diff --git a/font/testdata/fixed/7x13.1F00 b/font/testdata/fixed/7x13.1F00 new file mode 100644 index 0000000..43b320b Binary files /dev/null and b/font/testdata/fixed/7x13.1F00 differ diff --git a/font/testdata/fixed/7x13.2000 b/font/testdata/fixed/7x13.2000 new file mode 100644 index 0000000..f9244e1 Binary files /dev/null and b/font/testdata/fixed/7x13.2000 differ diff --git a/font/testdata/fixed/7x13.2100 b/font/testdata/fixed/7x13.2100 new file mode 100644 index 0000000..c565abb Binary files /dev/null and b/font/testdata/fixed/7x13.2100 differ diff --git a/font/testdata/fixed/7x13.2200 b/font/testdata/fixed/7x13.2200 new file mode 100644 index 0000000..a992d35 Binary files /dev/null and b/font/testdata/fixed/7x13.2200 differ diff --git a/font/testdata/fixed/7x13.2300 b/font/testdata/fixed/7x13.2300 new file mode 100644 index 0000000..8ff099d Binary files /dev/null and b/font/testdata/fixed/7x13.2300 differ diff --git a/font/testdata/fixed/7x13.2400 b/font/testdata/fixed/7x13.2400 new file mode 100644 index 0000000..99927a1 Binary files /dev/null and b/font/testdata/fixed/7x13.2400 differ diff --git a/font/testdata/fixed/7x13.2500 b/font/testdata/fixed/7x13.2500 new file mode 100644 index 0000000..60dc224 Binary files /dev/null and b/font/testdata/fixed/7x13.2500 differ diff --git a/font/testdata/fixed/7x13.2600 b/font/testdata/fixed/7x13.2600 new file mode 100644 index 0000000..1b393c2 Binary files /dev/null and b/font/testdata/fixed/7x13.2600 differ diff --git a/font/testdata/fixed/7x13.2700 b/font/testdata/fixed/7x13.2700 new file mode 100644 index 0000000..c39a572 Binary files /dev/null and b/font/testdata/fixed/7x13.2700 differ diff --git a/font/testdata/fixed/7x13.2800 b/font/testdata/fixed/7x13.2800 new file mode 100644 index 0000000..c7572de Binary files /dev/null and b/font/testdata/fixed/7x13.2800 differ diff --git a/font/testdata/fixed/7x13.2A00 b/font/testdata/fixed/7x13.2A00 new file mode 100644 index 0000000..71791ac Binary files /dev/null and b/font/testdata/fixed/7x13.2A00 differ diff --git a/font/testdata/fixed/7x13.3000 b/font/testdata/fixed/7x13.3000 new file mode 100644 index 0000000..fb830f4 Binary files /dev/null and b/font/testdata/fixed/7x13.3000 differ diff --git a/font/testdata/fixed/7x13.FB00 b/font/testdata/fixed/7x13.FB00 new file mode 100644 index 0000000..3a0b30a Binary files /dev/null and b/font/testdata/fixed/7x13.FB00 differ diff --git a/font/testdata/fixed/7x13.FE00 b/font/testdata/fixed/7x13.FE00 new file mode 100644 index 0000000..3989d26 Binary files /dev/null and b/font/testdata/fixed/7x13.FE00 differ diff --git a/font/testdata/fixed/7x13.FF00 b/font/testdata/fixed/7x13.FF00 new file mode 100644 index 0000000..78ed398 Binary files /dev/null and b/font/testdata/fixed/7x13.FF00 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