mirror of https://github.com/mindoc-org/mindoc.git
添加依赖包
parent
e5b6902fe3
commit
913d248287
|
@ -1,3 +0,0 @@
|
||||||
This repository holds supplementary Go image libraries.
|
|
||||||
|
|
||||||
To submit changes to this repository, see http://golang.org/doc/contribute.html.
|
|
|
@ -1,199 +0,0 @@
|
||||||
// Copyright 2011 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 bmp implements a BMP image decoder and encoder.
|
|
||||||
//
|
|
||||||
// The BMP specification is at http://www.digicamsoft.com/bmp/bmp.html.
|
|
||||||
package bmp // import "golang.org/x/image/bmp"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"image"
|
|
||||||
"image/color"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrUnsupported means that the input BMP image uses a valid but unsupported
|
|
||||||
// feature.
|
|
||||||
var ErrUnsupported = errors.New("bmp: unsupported BMP image")
|
|
||||||
|
|
||||||
func readUint16(b []byte) uint16 {
|
|
||||||
return uint16(b[0]) | uint16(b[1])<<8
|
|
||||||
}
|
|
||||||
|
|
||||||
func readUint32(b []byte) uint32 {
|
|
||||||
return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
|
|
||||||
}
|
|
||||||
|
|
||||||
// decodePaletted reads an 8 bit-per-pixel BMP image from r.
|
|
||||||
// If topDown is false, the image rows will be read bottom-up.
|
|
||||||
func decodePaletted(r io.Reader, c image.Config, topDown bool) (image.Image, error) {
|
|
||||||
paletted := image.NewPaletted(image.Rect(0, 0, c.Width, c.Height), c.ColorModel.(color.Palette))
|
|
||||||
if c.Width == 0 || c.Height == 0 {
|
|
||||||
return paletted, nil
|
|
||||||
}
|
|
||||||
var tmp [4]byte
|
|
||||||
y0, y1, yDelta := c.Height-1, -1, -1
|
|
||||||
if topDown {
|
|
||||||
y0, y1, yDelta = 0, c.Height, +1
|
|
||||||
}
|
|
||||||
for y := y0; y != y1; y += yDelta {
|
|
||||||
p := paletted.Pix[y*paletted.Stride : y*paletted.Stride+c.Width]
|
|
||||||
if _, err := io.ReadFull(r, p); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Each row is 4-byte aligned.
|
|
||||||
if c.Width%4 != 0 {
|
|
||||||
_, err := io.ReadFull(r, tmp[:4-c.Width%4])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return paletted, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// decodeRGB reads a 24 bit-per-pixel BMP image from r.
|
|
||||||
// If topDown is false, the image rows will be read bottom-up.
|
|
||||||
func decodeRGB(r io.Reader, c image.Config, topDown bool) (image.Image, error) {
|
|
||||||
rgba := image.NewRGBA(image.Rect(0, 0, c.Width, c.Height))
|
|
||||||
if c.Width == 0 || c.Height == 0 {
|
|
||||||
return rgba, nil
|
|
||||||
}
|
|
||||||
// There are 3 bytes per pixel, and each row is 4-byte aligned.
|
|
||||||
b := make([]byte, (3*c.Width+3)&^3)
|
|
||||||
y0, y1, yDelta := c.Height-1, -1, -1
|
|
||||||
if topDown {
|
|
||||||
y0, y1, yDelta = 0, c.Height, +1
|
|
||||||
}
|
|
||||||
for y := y0; y != y1; y += yDelta {
|
|
||||||
if _, err := io.ReadFull(r, b); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
p := rgba.Pix[y*rgba.Stride : y*rgba.Stride+c.Width*4]
|
|
||||||
for i, j := 0, 0; i < len(p); i, j = i+4, j+3 {
|
|
||||||
// BMP images are stored in BGR order rather than RGB order.
|
|
||||||
p[i+0] = b[j+2]
|
|
||||||
p[i+1] = b[j+1]
|
|
||||||
p[i+2] = b[j+0]
|
|
||||||
p[i+3] = 0xFF
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rgba, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// decodeNRGBA reads a 32 bit-per-pixel BMP image from r.
|
|
||||||
// If topDown is false, the image rows will be read bottom-up.
|
|
||||||
func decodeNRGBA(r io.Reader, c image.Config, topDown bool) (image.Image, error) {
|
|
||||||
rgba := image.NewNRGBA(image.Rect(0, 0, c.Width, c.Height))
|
|
||||||
if c.Width == 0 || c.Height == 0 {
|
|
||||||
return rgba, nil
|
|
||||||
}
|
|
||||||
y0, y1, yDelta := c.Height-1, -1, -1
|
|
||||||
if topDown {
|
|
||||||
y0, y1, yDelta = 0, c.Height, +1
|
|
||||||
}
|
|
||||||
for y := y0; y != y1; y += yDelta {
|
|
||||||
p := rgba.Pix[y*rgba.Stride : y*rgba.Stride+c.Width*4]
|
|
||||||
if _, err := io.ReadFull(r, p); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for i := 0; i < len(p); i += 4 {
|
|
||||||
// BMP images are stored in BGRA order rather than RGBA order.
|
|
||||||
p[i+0], p[i+2] = p[i+2], p[i+0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rgba, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode reads a BMP image from r and returns it as an image.Image.
|
|
||||||
// Limitation: The file must be 8, 24 or 32 bits per pixel.
|
|
||||||
func Decode(r io.Reader) (image.Image, error) {
|
|
||||||
c, bpp, topDown, err := decodeConfig(r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
switch bpp {
|
|
||||||
case 8:
|
|
||||||
return decodePaletted(r, c, topDown)
|
|
||||||
case 24:
|
|
||||||
return decodeRGB(r, c, topDown)
|
|
||||||
case 32:
|
|
||||||
return decodeNRGBA(r, c, topDown)
|
|
||||||
}
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeConfig returns the color model and dimensions of a BMP image without
|
|
||||||
// decoding the entire image.
|
|
||||||
// Limitation: The file must be 8, 24 or 32 bits per pixel.
|
|
||||||
func DecodeConfig(r io.Reader) (image.Config, error) {
|
|
||||||
config, _, _, err := decodeConfig(r)
|
|
||||||
return config, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func decodeConfig(r io.Reader) (config image.Config, bitsPerPixel int, topDown bool, err error) {
|
|
||||||
// We only support those BMP images that are a BITMAPFILEHEADER
|
|
||||||
// immediately followed by a BITMAPINFOHEADER.
|
|
||||||
const (
|
|
||||||
fileHeaderLen = 14
|
|
||||||
infoHeaderLen = 40
|
|
||||||
)
|
|
||||||
var b [1024]byte
|
|
||||||
if _, err := io.ReadFull(r, b[:fileHeaderLen+infoHeaderLen]); err != nil {
|
|
||||||
return image.Config{}, 0, false, err
|
|
||||||
}
|
|
||||||
if string(b[:2]) != "BM" {
|
|
||||||
return image.Config{}, 0, false, errors.New("bmp: invalid format")
|
|
||||||
}
|
|
||||||
offset := readUint32(b[10:14])
|
|
||||||
if readUint32(b[14:18]) != infoHeaderLen {
|
|
||||||
return image.Config{}, 0, false, ErrUnsupported
|
|
||||||
}
|
|
||||||
width := int(int32(readUint32(b[18:22])))
|
|
||||||
height := int(int32(readUint32(b[22:26])))
|
|
||||||
if height < 0 {
|
|
||||||
height, topDown = -height, true
|
|
||||||
}
|
|
||||||
if width < 0 || height < 0 {
|
|
||||||
return image.Config{}, 0, false, ErrUnsupported
|
|
||||||
}
|
|
||||||
// We only support 1 plane, 8 or 24 bits per pixel and no compression.
|
|
||||||
planes, bpp, compression := readUint16(b[26:28]), readUint16(b[28:30]), readUint32(b[30:34])
|
|
||||||
if planes != 1 || compression != 0 {
|
|
||||||
return image.Config{}, 0, false, ErrUnsupported
|
|
||||||
}
|
|
||||||
switch bpp {
|
|
||||||
case 8:
|
|
||||||
if offset != fileHeaderLen+infoHeaderLen+256*4 {
|
|
||||||
return image.Config{}, 0, false, ErrUnsupported
|
|
||||||
}
|
|
||||||
_, err = io.ReadFull(r, b[:256*4])
|
|
||||||
if err != nil {
|
|
||||||
return image.Config{}, 0, false, err
|
|
||||||
}
|
|
||||||
pcm := make(color.Palette, 256)
|
|
||||||
for i := range pcm {
|
|
||||||
// BMP images are stored in BGR order rather than RGB order.
|
|
||||||
// Every 4th byte is padding.
|
|
||||||
pcm[i] = color.RGBA{b[4*i+2], b[4*i+1], b[4*i+0], 0xFF}
|
|
||||||
}
|
|
||||||
return image.Config{ColorModel: pcm, Width: width, Height: height}, 8, topDown, nil
|
|
||||||
case 24:
|
|
||||||
if offset != fileHeaderLen+infoHeaderLen {
|
|
||||||
return image.Config{}, 0, false, ErrUnsupported
|
|
||||||
}
|
|
||||||
return image.Config{ColorModel: color.RGBAModel, Width: width, Height: height}, 24, topDown, nil
|
|
||||||
case 32:
|
|
||||||
if offset != fileHeaderLen+infoHeaderLen {
|
|
||||||
return image.Config{}, 0, false, ErrUnsupported
|
|
||||||
}
|
|
||||||
return image.Config{ColorModel: color.RGBAModel, Width: width, Height: height}, 32, topDown, nil
|
|
||||||
}
|
|
||||||
return image.Config{}, 0, false, ErrUnsupported
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
image.RegisterFormat("bmp", "BM????\x00\x00\x00\x00", Decode, DecodeConfig)
|
|
||||||
}
|
|
|
@ -1,75 +0,0 @@
|
||||||
// Copyright 2012 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 bmp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"image"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
_ "image/png"
|
|
||||||
)
|
|
||||||
|
|
||||||
const testdataDir = "../testdata/"
|
|
||||||
|
|
||||||
func compare(t *testing.T, img0, img1 image.Image) error {
|
|
||||||
b := img1.Bounds()
|
|
||||||
if !b.Eq(img0.Bounds()) {
|
|
||||||
return fmt.Errorf("wrong image size: want %s, got %s", img0.Bounds(), b)
|
|
||||||
}
|
|
||||||
for y := b.Min.Y; y < b.Max.Y; y++ {
|
|
||||||
for x := b.Min.X; x < b.Max.X; x++ {
|
|
||||||
c0 := img0.At(x, y)
|
|
||||||
c1 := img1.At(x, y)
|
|
||||||
r0, g0, b0, a0 := c0.RGBA()
|
|
||||||
r1, g1, b1, a1 := c1.RGBA()
|
|
||||||
if r0 != r1 || g0 != g1 || b0 != b1 || a0 != a1 {
|
|
||||||
return fmt.Errorf("pixel at (%d, %d) has wrong color: want %v, got %v", x, y, c0, c1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestDecode tests that decoding a PNG image and a BMP image result in the
|
|
||||||
// same pixel data.
|
|
||||||
func TestDecode(t *testing.T) {
|
|
||||||
testCases := []string{
|
|
||||||
"video-001",
|
|
||||||
"yellow_rose-small",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
f0, err := os.Open(testdataDir + tc + ".png")
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("%s: Open PNG: %v", tc, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
defer f0.Close()
|
|
||||||
img0, _, err := image.Decode(f0)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("%s: Decode PNG: %v", tc, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
f1, err := os.Open(testdataDir + tc + ".bmp")
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("%s: Open BMP: %v", tc, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
defer f1.Close()
|
|
||||||
img1, _, err := image.Decode(f1)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("%s: Decode BMP: %v", tc, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := compare(t, img0, img1); err != nil {
|
|
||||||
t.Errorf("%s: %v", tc, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,166 +0,0 @@
|
||||||
// Copyright 2013 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 bmp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"image"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
type header struct {
|
|
||||||
sigBM [2]byte
|
|
||||||
fileSize uint32
|
|
||||||
resverved [2]uint16
|
|
||||||
pixOffset uint32
|
|
||||||
dibHeaderSize uint32
|
|
||||||
width uint32
|
|
||||||
height uint32
|
|
||||||
colorPlane uint16
|
|
||||||
bpp uint16
|
|
||||||
compression uint32
|
|
||||||
imageSize uint32
|
|
||||||
xPixelsPerMeter uint32
|
|
||||||
yPixelsPerMeter uint32
|
|
||||||
colorUse uint32
|
|
||||||
colorImportant uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodePaletted(w io.Writer, pix []uint8, dx, dy, stride, step int) error {
|
|
||||||
var padding []byte
|
|
||||||
if dx < step {
|
|
||||||
padding = make([]byte, step-dx)
|
|
||||||
}
|
|
||||||
for y := dy - 1; y >= 0; y-- {
|
|
||||||
min := y*stride + 0
|
|
||||||
max := y*stride + dx
|
|
||||||
if _, err := w.Write(pix[min:max]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if padding != nil {
|
|
||||||
if _, err := w.Write(padding); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeRGBA(w io.Writer, pix []uint8, dx, dy, stride, step int) error {
|
|
||||||
buf := make([]byte, step)
|
|
||||||
for y := dy - 1; y >= 0; y-- {
|
|
||||||
min := y*stride + 0
|
|
||||||
max := y*stride + dx*4
|
|
||||||
off := 0
|
|
||||||
for i := min; i < max; i += 4 {
|
|
||||||
buf[off+2] = pix[i+0]
|
|
||||||
buf[off+1] = pix[i+1]
|
|
||||||
buf[off+0] = pix[i+2]
|
|
||||||
off += 3
|
|
||||||
}
|
|
||||||
if _, err := w.Write(buf); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func encode(w io.Writer, m image.Image, step int) error {
|
|
||||||
b := m.Bounds()
|
|
||||||
buf := make([]byte, step)
|
|
||||||
for y := b.Max.Y - 1; y >= b.Min.Y; y-- {
|
|
||||||
off := 0
|
|
||||||
for x := b.Min.X; x < b.Max.X; x++ {
|
|
||||||
r, g, b, _ := m.At(x, y).RGBA()
|
|
||||||
buf[off+2] = byte(r >> 8)
|
|
||||||
buf[off+1] = byte(g >> 8)
|
|
||||||
buf[off+0] = byte(b >> 8)
|
|
||||||
off += 3
|
|
||||||
}
|
|
||||||
if _, err := w.Write(buf); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode writes the image m to w in BMP format.
|
|
||||||
func Encode(w io.Writer, m image.Image) error {
|
|
||||||
d := m.Bounds().Size()
|
|
||||||
if d.X < 0 || d.Y < 0 {
|
|
||||||
return errors.New("bmp: negative bounds")
|
|
||||||
}
|
|
||||||
h := &header{
|
|
||||||
sigBM: [2]byte{'B', 'M'},
|
|
||||||
fileSize: 14 + 40,
|
|
||||||
pixOffset: 14 + 40,
|
|
||||||
dibHeaderSize: 40,
|
|
||||||
width: uint32(d.X),
|
|
||||||
height: uint32(d.Y),
|
|
||||||
colorPlane: 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
var step int
|
|
||||||
var palette []byte
|
|
||||||
switch m := m.(type) {
|
|
||||||
case *image.Gray:
|
|
||||||
step = (d.X + 3) &^ 3
|
|
||||||
palette = make([]byte, 1024)
|
|
||||||
for i := 0; i < 256; i++ {
|
|
||||||
palette[i*4+0] = uint8(i)
|
|
||||||
palette[i*4+1] = uint8(i)
|
|
||||||
palette[i*4+2] = uint8(i)
|
|
||||||
palette[i*4+3] = 0xFF
|
|
||||||
}
|
|
||||||
h.imageSize = uint32(d.Y * step)
|
|
||||||
h.fileSize += uint32(len(palette)) + h.imageSize
|
|
||||||
h.pixOffset += uint32(len(palette))
|
|
||||||
h.bpp = 8
|
|
||||||
|
|
||||||
case *image.Paletted:
|
|
||||||
step = (d.X + 3) &^ 3
|
|
||||||
palette = make([]byte, 1024)
|
|
||||||
for i := 0; i < len(m.Palette) && i < 256; i++ {
|
|
||||||
r, g, b, _ := m.Palette[i].RGBA()
|
|
||||||
palette[i*4+0] = uint8(b >> 8)
|
|
||||||
palette[i*4+1] = uint8(g >> 8)
|
|
||||||
palette[i*4+2] = uint8(r >> 8)
|
|
||||||
palette[i*4+3] = 0xFF
|
|
||||||
}
|
|
||||||
h.imageSize = uint32(d.Y * step)
|
|
||||||
h.fileSize += uint32(len(palette)) + h.imageSize
|
|
||||||
h.pixOffset += uint32(len(palette))
|
|
||||||
h.bpp = 8
|
|
||||||
default:
|
|
||||||
step = (3*d.X + 3) &^ 3
|
|
||||||
h.imageSize = uint32(d.Y * step)
|
|
||||||
h.fileSize += h.imageSize
|
|
||||||
h.bpp = 24
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := binary.Write(w, binary.LittleEndian, h); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if palette != nil {
|
|
||||||
if err := binary.Write(w, binary.LittleEndian, palette); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if d.X == 0 || d.Y == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
switch m := m.(type) {
|
|
||||||
case *image.Gray:
|
|
||||||
return encodePaletted(w, m.Pix, d.X, d.Y, m.Stride, step)
|
|
||||||
case *image.Paletted:
|
|
||||||
return encodePaletted(w, m.Pix, d.X, d.Y, m.Stride, step)
|
|
||||||
case *image.RGBA:
|
|
||||||
return encodeRGBA(w, m.Pix, d.X, d.Y, m.Stride, step)
|
|
||||||
}
|
|
||||||
return encode(w, m, step)
|
|
||||||
}
|
|
|
@ -1,91 +0,0 @@
|
||||||
// Copyright 2013 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 bmp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"image"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func openImage(filename string) (image.Image, error) {
|
|
||||||
f, err := os.Open(testdataDir + filename)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
return Decode(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEncode(t *testing.T) {
|
|
||||||
img0, err := openImage("video-001.bmp")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
err = Encode(buf, img0)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
img1, err := Decode(buf)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
compare(t, img0, img1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestZeroWidthVeryLargeHeight tests that encoding and decoding a degenerate
|
|
||||||
// image with zero width but over one billion pixels in height is faster than
|
|
||||||
// naively calling an io.Reader or io.Writer method once per row.
|
|
||||||
func TestZeroWidthVeryLargeHeight(t *testing.T) {
|
|
||||||
c := make(chan error, 1)
|
|
||||||
go func() {
|
|
||||||
b := image.Rect(0, 0, 0, 0x3fffffff)
|
|
||||||
var buf bytes.Buffer
|
|
||||||
if err := Encode(&buf, image.NewRGBA(b)); err != nil {
|
|
||||||
c <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
m, err := Decode(&buf)
|
|
||||||
if err != nil {
|
|
||||||
c <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if got := m.Bounds(); got != b {
|
|
||||||
c <- fmt.Errorf("bounds: got %v, want %v", got, b)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c <- nil
|
|
||||||
}()
|
|
||||||
select {
|
|
||||||
case err := <-c:
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
case <-time.After(3 * time.Second):
|
|
||||||
t.Fatalf("timed out")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BenchmarkEncode benchmarks the encoding of an image.
|
|
||||||
func BenchmarkEncode(b *testing.B) {
|
|
||||||
img, err := openImage("video-001.bmp")
|
|
||||||
if err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
s := img.Bounds().Size()
|
|
||||||
b.SetBytes(int64(s.X * s.Y * 4))
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
Encode(ioutil.Discard, img)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,215 +0,0 @@
|
||||||
// Copyright 2014 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/image/..." doesn't
|
|
||||||
// install this manual test. Use "go run main.go" to explicitly run it.
|
|
||||||
|
|
||||||
// Program webp-manual-test checks that the Go WEBP library's decodings match
|
|
||||||
// the C WEBP library's.
|
|
||||||
package main // import "golang.org/x/image/cmd/webp-manual-test"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/hex"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"image"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/image/webp"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
dwebp = flag.String("dwebp", "/usr/bin/dwebp", "path to the dwebp program "+
|
|
||||||
"installed from https://developers.google.com/speed/webp/download")
|
|
||||||
testdata = flag.String("testdata", "", "path to the libwebp-test-data directory "+
|
|
||||||
"checked out from https://chromium.googlesource.com/webm/libwebp-test-data")
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
flag.Parse()
|
|
||||||
if err := checkDwebp(); err != nil {
|
|
||||||
flag.Usage()
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
if *testdata == "" {
|
|
||||||
flag.Usage()
|
|
||||||
log.Fatal("testdata flag was not specified")
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := os.Open(*testdata)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Open: %v", err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
names, err := f.Readdirnames(-1)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Readdirnames: %v", err)
|
|
||||||
}
|
|
||||||
sort.Strings(names)
|
|
||||||
|
|
||||||
nFail, nPass := 0, 0
|
|
||||||
for _, name := range names {
|
|
||||||
if !strings.HasSuffix(name, "webp") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := test(name); err != nil {
|
|
||||||
fmt.Printf("FAIL\t%s\t%v\n", name, err)
|
|
||||||
nFail++
|
|
||||||
} else {
|
|
||||||
fmt.Printf("PASS\t%s\n", name)
|
|
||||||
nPass++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fmt.Printf("%d PASS, %d FAIL, %d TOTAL\n", nPass, nFail, nPass+nFail)
|
|
||||||
if nFail != 0 {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkDwebp() error {
|
|
||||||
if *dwebp == "" {
|
|
||||||
return fmt.Errorf("dwebp flag was not specified")
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(*dwebp); err != nil {
|
|
||||||
return fmt.Errorf("could not find dwebp program at %q", *dwebp)
|
|
||||||
}
|
|
||||||
b, err := exec.Command(*dwebp, "-version").Output()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not determine the dwebp program version for %q: %v", *dwebp, err)
|
|
||||||
}
|
|
||||||
switch s := string(bytes.TrimSpace(b)); s {
|
|
||||||
case "0.4.0", "0.4.1", "0.4.2":
|
|
||||||
return fmt.Errorf("the dwebp program version %q for %q has a known bug "+
|
|
||||||
"(https://bugs.chromium.org/p/webp/issues/detail?id=239). Please use a newer version.", s, *dwebp)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// test tests a single WEBP image.
|
|
||||||
func test(name string) error {
|
|
||||||
filename := filepath.Join(*testdata, name)
|
|
||||||
f, err := os.Open(filename)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Open: %v", err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
gotImage, err := webp.Decode(f)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Decode: %v", err)
|
|
||||||
}
|
|
||||||
format, encode := "-pgm", encodePGM
|
|
||||||
if _, lossless := gotImage.(*image.NRGBA); lossless {
|
|
||||||
format, encode = "-pam", encodePAM
|
|
||||||
}
|
|
||||||
got, err := encode(gotImage)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("encode: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
stdout := new(bytes.Buffer)
|
|
||||||
stderr := new(bytes.Buffer)
|
|
||||||
c := exec.Command(*dwebp, filename, format, "-o", "/dev/stdout")
|
|
||||||
c.Stdout = stdout
|
|
||||||
c.Stderr = stderr
|
|
||||||
if err := c.Run(); err != nil {
|
|
||||||
os.Stderr.Write(stderr.Bytes())
|
|
||||||
return fmt.Errorf("executing dwebp: %v", err)
|
|
||||||
}
|
|
||||||
want := stdout.Bytes()
|
|
||||||
|
|
||||||
if len(got) != len(want) {
|
|
||||||
return fmt.Errorf("encodings have different length: got %d, want %d", len(got), len(want))
|
|
||||||
}
|
|
||||||
for i, g := range got {
|
|
||||||
if w := want[i]; g != w {
|
|
||||||
return fmt.Errorf("encodings differ at position 0x%x: got 0x%02x, want 0x%02x", i, g, w)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// encodePAM encodes gotImage in the PAM format.
|
|
||||||
func encodePAM(gotImage image.Image) ([]byte, error) {
|
|
||||||
m, ok := gotImage.(*image.NRGBA)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("lossless image did not decode to an *image.NRGBA")
|
|
||||||
}
|
|
||||||
b := m.Bounds()
|
|
||||||
w, h := b.Dx(), b.Dy()
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
fmt.Fprintf(buf, "P7\nWIDTH %d\nHEIGHT %d\nDEPTH 4\nMAXVAL 255\nTUPLTYPE RGB_ALPHA\nENDHDR\n", w, h)
|
|
||||||
for y := b.Min.Y; y < b.Max.Y; y++ {
|
|
||||||
o := m.PixOffset(b.Min.X, y)
|
|
||||||
buf.Write(m.Pix[o : o+4*w])
|
|
||||||
}
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// encodePGM encodes gotImage in the PGM format in the IMC4 layout.
|
|
||||||
func encodePGM(gotImage image.Image) ([]byte, error) {
|
|
||||||
var (
|
|
||||||
m *image.YCbCr
|
|
||||||
ma *image.NYCbCrA
|
|
||||||
)
|
|
||||||
switch g := gotImage.(type) {
|
|
||||||
case *image.YCbCr:
|
|
||||||
m = g
|
|
||||||
case *image.NYCbCrA:
|
|
||||||
m = &g.YCbCr
|
|
||||||
ma = g
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("lossy image did not decode to an *image.YCbCr")
|
|
||||||
}
|
|
||||||
if m.SubsampleRatio != image.YCbCrSubsampleRatio420 {
|
|
||||||
return nil, fmt.Errorf("lossy image did not decode to a 4:2:0 YCbCr")
|
|
||||||
}
|
|
||||||
b := m.Bounds()
|
|
||||||
w, h := b.Dx(), b.Dy()
|
|
||||||
w2, h2 := (w+1)/2, (h+1)/2
|
|
||||||
outW, outH := 2*w2, h+h2
|
|
||||||
if ma != nil {
|
|
||||||
outH += h
|
|
||||||
}
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
fmt.Fprintf(buf, "P5\n%d %d\n255\n", outW, outH)
|
|
||||||
for y := b.Min.Y; y < b.Max.Y; y++ {
|
|
||||||
o := m.YOffset(b.Min.X, y)
|
|
||||||
buf.Write(m.Y[o : o+w])
|
|
||||||
if w&1 != 0 {
|
|
||||||
buf.WriteByte(0x00)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for y := b.Min.Y; y < b.Max.Y; y += 2 {
|
|
||||||
o := m.COffset(b.Min.X, y)
|
|
||||||
buf.Write(m.Cb[o : o+w2])
|
|
||||||
buf.Write(m.Cr[o : o+w2])
|
|
||||||
}
|
|
||||||
if ma != nil {
|
|
||||||
for y := b.Min.Y; y < b.Max.Y; y++ {
|
|
||||||
o := ma.AOffset(b.Min.X, y)
|
|
||||||
buf.Write(ma.A[o : o+w])
|
|
||||||
if w&1 != 0 {
|
|
||||||
buf.WriteByte(0x00)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// dump can be useful for debugging.
|
|
||||||
func dump(w io.Writer, b []byte) {
|
|
||||||
h := hex.Dumper(w)
|
|
||||||
h.Write(b)
|
|
||||||
h.Close()
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
// 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.
|
|
||||||
|
|
||||||
//go:generate go run gen.go
|
|
||||||
|
|
||||||
// Package colornames provides named colors as defined in the SVG 1.1 spec.
|
|
||||||
//
|
|
||||||
// See http://www.w3.org/TR/SVG/types.html#ColorKeywords
|
|
||||||
package colornames
|
|
|
@ -1,42 +0,0 @@
|
||||||
// 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 colornames
|
|
||||||
|
|
||||||
import (
|
|
||||||
"image/color"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestColornames(t *testing.T) {
|
|
||||||
if len(Map) != len(Names) {
|
|
||||||
t.Fatalf("Map and Names have different length: %d vs %d", len(Map), len(Names))
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, want := range testCases {
|
|
||||||
got, ok := Map[name]
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("Did not find %s", name)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if got != want {
|
|
||||||
t.Errorf("%s:\ngot %v\nwant %v", name, got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var testCases = map[string]color.RGBA{
|
|
||||||
"aliceblue": color.RGBA{240, 248, 255, 255},
|
|
||||||
"crimson": color.RGBA{220, 20, 60, 255},
|
|
||||||
"darkorange": color.RGBA{255, 140, 0, 255},
|
|
||||||
"deepskyblue": color.RGBA{0, 191, 255, 255},
|
|
||||||
"greenyellow": color.RGBA{173, 255, 47, 255},
|
|
||||||
"lightgrey": color.RGBA{211, 211, 211, 255},
|
|
||||||
"lightpink": color.RGBA{255, 182, 193, 255},
|
|
||||||
"mediumseagreen": color.RGBA{60, 179, 113, 255},
|
|
||||||
"olivedrab": color.RGBA{107, 142, 35, 255},
|
|
||||||
"purple": color.RGBA{128, 0, 128, 255},
|
|
||||||
"slategrey": color.RGBA{112, 128, 144, 255},
|
|
||||||
"yellowgreen": color.RGBA{154, 205, 50, 255},
|
|
||||||
}
|
|
|
@ -1,197 +0,0 @@
|
||||||
// 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 program generates table.go from
|
|
||||||
// http://www.w3.org/TR/SVG/types.html#ColorKeywords
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"go/format"
|
|
||||||
"image/color"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"regexp"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/net/html"
|
|
||||||
"golang.org/x/net/html/atom"
|
|
||||||
)
|
|
||||||
|
|
||||||
// matchFunc matches HTML nodes.
|
|
||||||
type matchFunc func(*html.Node) bool
|
|
||||||
|
|
||||||
// appendAll recursively traverses the parse tree rooted under the provided
|
|
||||||
// node and appends all nodes matched by the matchFunc to dst.
|
|
||||||
func appendAll(dst []*html.Node, n *html.Node, mf matchFunc) []*html.Node {
|
|
||||||
if mf(n) {
|
|
||||||
dst = append(dst, n)
|
|
||||||
}
|
|
||||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
|
||||||
dst = appendAll(dst, c, mf)
|
|
||||||
}
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
// matchAtom returns a matchFunc that matches a Node with the specified Atom.
|
|
||||||
func matchAtom(a atom.Atom) matchFunc {
|
|
||||||
return func(n *html.Node) bool {
|
|
||||||
return n.DataAtom == a
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// matchAtomAttr returns a matchFunc that matches a Node with the specified
|
|
||||||
// Atom and a html.Attribute's namespace, key and value.
|
|
||||||
func matchAtomAttr(a atom.Atom, namespace, key, value string) matchFunc {
|
|
||||||
return func(n *html.Node) bool {
|
|
||||||
return n.DataAtom == a && getAttr(n, namespace, key) == value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// getAttr fetches the value of a html.Attribute for a given namespace and key.
|
|
||||||
func getAttr(n *html.Node, namespace, key string) string {
|
|
||||||
for _, attr := range n.Attr {
|
|
||||||
if attr.Namespace == namespace && attr.Key == key {
|
|
||||||
return attr.Val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// re extracts RGB values from strings like "rgb( 0, 223, 128)".
|
|
||||||
var re = regexp.MustCompile(`rgb\(\s*([0-9]+),\s*([0-9]+),\s*([0-9]+)\)`)
|
|
||||||
|
|
||||||
// parseRGB parses a color from a string like "rgb( 0, 233, 128)". It sets
|
|
||||||
// the alpha value of the color to full opacity.
|
|
||||||
func parseRGB(s string) (color.RGBA, error) {
|
|
||||||
m := re.FindStringSubmatch(s)
|
|
||||||
if m == nil {
|
|
||||||
return color.RGBA{}, fmt.Errorf("malformed color: %q", s)
|
|
||||||
}
|
|
||||||
var rgb [3]uint8
|
|
||||||
for i, t := range m[1:] {
|
|
||||||
num, err := strconv.ParseUint(t, 10, 8)
|
|
||||||
if err != nil {
|
|
||||||
return color.RGBA{}, fmt.Errorf("malformed value %q in %q: %s", t, s, err)
|
|
||||||
}
|
|
||||||
rgb[i] = uint8(num)
|
|
||||||
}
|
|
||||||
return color.RGBA{rgb[0], rgb[1], rgb[2], 0xFF}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// extractSVGColors extracts named colors from the parse tree of the SVG 1.1
|
|
||||||
// spec HTML document "Chapter 4: Basic data types and interfaces".
|
|
||||||
func extractSVGColors(tree *html.Node) (map[string]color.RGBA, error) {
|
|
||||||
ret := make(map[string]color.RGBA)
|
|
||||||
|
|
||||||
// Find the tables which store the color keywords in the parse tree.
|
|
||||||
colorTables := appendAll(nil, tree, func(n *html.Node) bool {
|
|
||||||
return n.DataAtom == atom.Table && strings.Contains(getAttr(n, "", "summary"), "color keywords part")
|
|
||||||
})
|
|
||||||
|
|
||||||
for _, table := range colorTables {
|
|
||||||
// Color names and values are stored in TextNodes within spans in each row.
|
|
||||||
for _, tr := range appendAll(nil, table, matchAtom(atom.Tr)) {
|
|
||||||
nameSpan := appendAll(nil, tr, matchAtomAttr(atom.Span, "", "class", "prop-value"))
|
|
||||||
valueSpan := appendAll(nil, tr, matchAtomAttr(atom.Span, "", "class", "color-keyword-value"))
|
|
||||||
|
|
||||||
// Since SVG 1.1 defines an odd number of colors, the last row
|
|
||||||
// in the second table does not have contents. We skip it.
|
|
||||||
if len(nameSpan) != 1 || len(valueSpan) != 1 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
n, v := nameSpan[0].FirstChild, valueSpan[0].FirstChild
|
|
||||||
// This sanity checks for the existence of TextNodes under spans.
|
|
||||||
if n == nil || n.Type != html.TextNode || v == nil || v.Type != html.TextNode {
|
|
||||||
return nil, fmt.Errorf("extractSVGColors: couldn't find name/value text nodes")
|
|
||||||
}
|
|
||||||
val, err := parseRGB(v.Data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("extractSVGColors: couldn't parse name/value %q/%q: %s", n.Data, v.Data, err)
|
|
||||||
}
|
|
||||||
ret[n.Data] = val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const preamble = `// generated by go generate; DO NOT EDIT.
|
|
||||||
|
|
||||||
package colornames
|
|
||||||
|
|
||||||
import "image/color"
|
|
||||||
|
|
||||||
`
|
|
||||||
|
|
||||||
// WriteColorNames writes table.go.
|
|
||||||
func writeColorNames(w io.Writer, m map[string]color.RGBA) {
|
|
||||||
keys := make([]string, 0, len(m))
|
|
||||||
for k := range m {
|
|
||||||
keys = append(keys, k)
|
|
||||||
}
|
|
||||||
sort.Strings(keys)
|
|
||||||
|
|
||||||
fmt.Fprintln(w, preamble)
|
|
||||||
fmt.Fprintln(w, "// Map contains named colors defined in the SVG 1.1 spec.")
|
|
||||||
fmt.Fprintln(w, "var Map = map[string]color.RGBA{")
|
|
||||||
for _, k := range keys {
|
|
||||||
c := m[k]
|
|
||||||
fmt.Fprintf(w, "%q:color.RGBA{%#02x, %#02x, %#02x, %#02x}, // rgb(%d, %d, %d)\n",
|
|
||||||
k, c.R, c.G, c.B, c.A, c.R, c.G, c.B)
|
|
||||||
}
|
|
||||||
fmt.Fprintln(w, "}\n")
|
|
||||||
fmt.Fprintln(w, "// Names contains the color names defined in the SVG 1.1 spec.")
|
|
||||||
fmt.Fprintln(w, "var Names = []string{")
|
|
||||||
for _, k := range keys {
|
|
||||||
fmt.Fprintf(w, "%q,\n", k)
|
|
||||||
}
|
|
||||||
fmt.Fprintln(w, "}\n")
|
|
||||||
fmt.Fprintln(w, "var (")
|
|
||||||
for _, k := range keys {
|
|
||||||
c := m[k]
|
|
||||||
// Make the upper case version of k: "Darkred" instead of "darkred".
|
|
||||||
k = string(k[0]-0x20) + k[1:]
|
|
||||||
fmt.Fprintf(w, "%s=color.RGBA{%#02x, %#02x, %#02x, %#02x} // rgb(%d, %d, %d)\n",
|
|
||||||
k, c.R, c.G, c.B, c.A, c.R, c.G, c.B)
|
|
||||||
}
|
|
||||||
fmt.Fprintln(w, ")")
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = "http://www.w3.org/TR/SVG/types.html"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
res, err := http.Get(url)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Couldn't read from %s: %s\n", url, err)
|
|
||||||
}
|
|
||||||
defer res.Body.Close()
|
|
||||||
|
|
||||||
tree, err := html.Parse(res.Body)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Couldn't parse %s: %s\n", url, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
colors, err := extractSVGColors(tree)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Couldn't extract colors: %s\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
writeColorNames(buf, colors)
|
|
||||||
fmted, err := format.Source(buf.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error while formatting code: %s\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ioutil.WriteFile("table.go", fmted, 0644); err != nil {
|
|
||||||
log.Fatalf("Error writing table.go: %s\n", err)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,457 +0,0 @@
|
||||||
// generated by go generate; DO NOT EDIT.
|
|
||||||
|
|
||||||
package colornames
|
|
||||||
|
|
||||||
import "image/color"
|
|
||||||
|
|
||||||
// Map contains named colors defined in the SVG 1.1 spec.
|
|
||||||
var Map = map[string]color.RGBA{
|
|
||||||
"aliceblue": color.RGBA{0xf0, 0xf8, 0xff, 0xff}, // rgb(240, 248, 255)
|
|
||||||
"antiquewhite": color.RGBA{0xfa, 0xeb, 0xd7, 0xff}, // rgb(250, 235, 215)
|
|
||||||
"aqua": color.RGBA{0x00, 0xff, 0xff, 0xff}, // rgb(0, 255, 255)
|
|
||||||
"aquamarine": color.RGBA{0x7f, 0xff, 0xd4, 0xff}, // rgb(127, 255, 212)
|
|
||||||
"azure": color.RGBA{0xf0, 0xff, 0xff, 0xff}, // rgb(240, 255, 255)
|
|
||||||
"beige": color.RGBA{0xf5, 0xf5, 0xdc, 0xff}, // rgb(245, 245, 220)
|
|
||||||
"bisque": color.RGBA{0xff, 0xe4, 0xc4, 0xff}, // rgb(255, 228, 196)
|
|
||||||
"black": color.RGBA{0x00, 0x00, 0x00, 0xff}, // rgb(0, 0, 0)
|
|
||||||
"blanchedalmond": color.RGBA{0xff, 0xeb, 0xcd, 0xff}, // rgb(255, 235, 205)
|
|
||||||
"blue": color.RGBA{0x00, 0x00, 0xff, 0xff}, // rgb(0, 0, 255)
|
|
||||||
"blueviolet": color.RGBA{0x8a, 0x2b, 0xe2, 0xff}, // rgb(138, 43, 226)
|
|
||||||
"brown": color.RGBA{0xa5, 0x2a, 0x2a, 0xff}, // rgb(165, 42, 42)
|
|
||||||
"burlywood": color.RGBA{0xde, 0xb8, 0x87, 0xff}, // rgb(222, 184, 135)
|
|
||||||
"cadetblue": color.RGBA{0x5f, 0x9e, 0xa0, 0xff}, // rgb(95, 158, 160)
|
|
||||||
"chartreuse": color.RGBA{0x7f, 0xff, 0x00, 0xff}, // rgb(127, 255, 0)
|
|
||||||
"chocolate": color.RGBA{0xd2, 0x69, 0x1e, 0xff}, // rgb(210, 105, 30)
|
|
||||||
"coral": color.RGBA{0xff, 0x7f, 0x50, 0xff}, // rgb(255, 127, 80)
|
|
||||||
"cornflowerblue": color.RGBA{0x64, 0x95, 0xed, 0xff}, // rgb(100, 149, 237)
|
|
||||||
"cornsilk": color.RGBA{0xff, 0xf8, 0xdc, 0xff}, // rgb(255, 248, 220)
|
|
||||||
"crimson": color.RGBA{0xdc, 0x14, 0x3c, 0xff}, // rgb(220, 20, 60)
|
|
||||||
"cyan": color.RGBA{0x00, 0xff, 0xff, 0xff}, // rgb(0, 255, 255)
|
|
||||||
"darkblue": color.RGBA{0x00, 0x00, 0x8b, 0xff}, // rgb(0, 0, 139)
|
|
||||||
"darkcyan": color.RGBA{0x00, 0x8b, 0x8b, 0xff}, // rgb(0, 139, 139)
|
|
||||||
"darkgoldenrod": color.RGBA{0xb8, 0x86, 0x0b, 0xff}, // rgb(184, 134, 11)
|
|
||||||
"darkgray": color.RGBA{0xa9, 0xa9, 0xa9, 0xff}, // rgb(169, 169, 169)
|
|
||||||
"darkgreen": color.RGBA{0x00, 0x64, 0x00, 0xff}, // rgb(0, 100, 0)
|
|
||||||
"darkgrey": color.RGBA{0xa9, 0xa9, 0xa9, 0xff}, // rgb(169, 169, 169)
|
|
||||||
"darkkhaki": color.RGBA{0xbd, 0xb7, 0x6b, 0xff}, // rgb(189, 183, 107)
|
|
||||||
"darkmagenta": color.RGBA{0x8b, 0x00, 0x8b, 0xff}, // rgb(139, 0, 139)
|
|
||||||
"darkolivegreen": color.RGBA{0x55, 0x6b, 0x2f, 0xff}, // rgb(85, 107, 47)
|
|
||||||
"darkorange": color.RGBA{0xff, 0x8c, 0x00, 0xff}, // rgb(255, 140, 0)
|
|
||||||
"darkorchid": color.RGBA{0x99, 0x32, 0xcc, 0xff}, // rgb(153, 50, 204)
|
|
||||||
"darkred": color.RGBA{0x8b, 0x00, 0x00, 0xff}, // rgb(139, 0, 0)
|
|
||||||
"darksalmon": color.RGBA{0xe9, 0x96, 0x7a, 0xff}, // rgb(233, 150, 122)
|
|
||||||
"darkseagreen": color.RGBA{0x8f, 0xbc, 0x8f, 0xff}, // rgb(143, 188, 143)
|
|
||||||
"darkslateblue": color.RGBA{0x48, 0x3d, 0x8b, 0xff}, // rgb(72, 61, 139)
|
|
||||||
"darkslategray": color.RGBA{0x2f, 0x4f, 0x4f, 0xff}, // rgb(47, 79, 79)
|
|
||||||
"darkslategrey": color.RGBA{0x2f, 0x4f, 0x4f, 0xff}, // rgb(47, 79, 79)
|
|
||||||
"darkturquoise": color.RGBA{0x00, 0xce, 0xd1, 0xff}, // rgb(0, 206, 209)
|
|
||||||
"darkviolet": color.RGBA{0x94, 0x00, 0xd3, 0xff}, // rgb(148, 0, 211)
|
|
||||||
"deeppink": color.RGBA{0xff, 0x14, 0x93, 0xff}, // rgb(255, 20, 147)
|
|
||||||
"deepskyblue": color.RGBA{0x00, 0xbf, 0xff, 0xff}, // rgb(0, 191, 255)
|
|
||||||
"dimgray": color.RGBA{0x69, 0x69, 0x69, 0xff}, // rgb(105, 105, 105)
|
|
||||||
"dimgrey": color.RGBA{0x69, 0x69, 0x69, 0xff}, // rgb(105, 105, 105)
|
|
||||||
"dodgerblue": color.RGBA{0x1e, 0x90, 0xff, 0xff}, // rgb(30, 144, 255)
|
|
||||||
"firebrick": color.RGBA{0xb2, 0x22, 0x22, 0xff}, // rgb(178, 34, 34)
|
|
||||||
"floralwhite": color.RGBA{0xff, 0xfa, 0xf0, 0xff}, // rgb(255, 250, 240)
|
|
||||||
"forestgreen": color.RGBA{0x22, 0x8b, 0x22, 0xff}, // rgb(34, 139, 34)
|
|
||||||
"fuchsia": color.RGBA{0xff, 0x00, 0xff, 0xff}, // rgb(255, 0, 255)
|
|
||||||
"gainsboro": color.RGBA{0xdc, 0xdc, 0xdc, 0xff}, // rgb(220, 220, 220)
|
|
||||||
"ghostwhite": color.RGBA{0xf8, 0xf8, 0xff, 0xff}, // rgb(248, 248, 255)
|
|
||||||
"gold": color.RGBA{0xff, 0xd7, 0x00, 0xff}, // rgb(255, 215, 0)
|
|
||||||
"goldenrod": color.RGBA{0xda, 0xa5, 0x20, 0xff}, // rgb(218, 165, 32)
|
|
||||||
"gray": color.RGBA{0x80, 0x80, 0x80, 0xff}, // rgb(128, 128, 128)
|
|
||||||
"green": color.RGBA{0x00, 0x80, 0x00, 0xff}, // rgb(0, 128, 0)
|
|
||||||
"greenyellow": color.RGBA{0xad, 0xff, 0x2f, 0xff}, // rgb(173, 255, 47)
|
|
||||||
"grey": color.RGBA{0x80, 0x80, 0x80, 0xff}, // rgb(128, 128, 128)
|
|
||||||
"honeydew": color.RGBA{0xf0, 0xff, 0xf0, 0xff}, // rgb(240, 255, 240)
|
|
||||||
"hotpink": color.RGBA{0xff, 0x69, 0xb4, 0xff}, // rgb(255, 105, 180)
|
|
||||||
"indianred": color.RGBA{0xcd, 0x5c, 0x5c, 0xff}, // rgb(205, 92, 92)
|
|
||||||
"indigo": color.RGBA{0x4b, 0x00, 0x82, 0xff}, // rgb(75, 0, 130)
|
|
||||||
"ivory": color.RGBA{0xff, 0xff, 0xf0, 0xff}, // rgb(255, 255, 240)
|
|
||||||
"khaki": color.RGBA{0xf0, 0xe6, 0x8c, 0xff}, // rgb(240, 230, 140)
|
|
||||||
"lavender": color.RGBA{0xe6, 0xe6, 0xfa, 0xff}, // rgb(230, 230, 250)
|
|
||||||
"lavenderblush": color.RGBA{0xff, 0xf0, 0xf5, 0xff}, // rgb(255, 240, 245)
|
|
||||||
"lawngreen": color.RGBA{0x7c, 0xfc, 0x00, 0xff}, // rgb(124, 252, 0)
|
|
||||||
"lemonchiffon": color.RGBA{0xff, 0xfa, 0xcd, 0xff}, // rgb(255, 250, 205)
|
|
||||||
"lightblue": color.RGBA{0xad, 0xd8, 0xe6, 0xff}, // rgb(173, 216, 230)
|
|
||||||
"lightcoral": color.RGBA{0xf0, 0x80, 0x80, 0xff}, // rgb(240, 128, 128)
|
|
||||||
"lightcyan": color.RGBA{0xe0, 0xff, 0xff, 0xff}, // rgb(224, 255, 255)
|
|
||||||
"lightgoldenrodyellow": color.RGBA{0xfa, 0xfa, 0xd2, 0xff}, // rgb(250, 250, 210)
|
|
||||||
"lightgray": color.RGBA{0xd3, 0xd3, 0xd3, 0xff}, // rgb(211, 211, 211)
|
|
||||||
"lightgreen": color.RGBA{0x90, 0xee, 0x90, 0xff}, // rgb(144, 238, 144)
|
|
||||||
"lightgrey": color.RGBA{0xd3, 0xd3, 0xd3, 0xff}, // rgb(211, 211, 211)
|
|
||||||
"lightpink": color.RGBA{0xff, 0xb6, 0xc1, 0xff}, // rgb(255, 182, 193)
|
|
||||||
"lightsalmon": color.RGBA{0xff, 0xa0, 0x7a, 0xff}, // rgb(255, 160, 122)
|
|
||||||
"lightseagreen": color.RGBA{0x20, 0xb2, 0xaa, 0xff}, // rgb(32, 178, 170)
|
|
||||||
"lightskyblue": color.RGBA{0x87, 0xce, 0xfa, 0xff}, // rgb(135, 206, 250)
|
|
||||||
"lightslategray": color.RGBA{0x77, 0x88, 0x99, 0xff}, // rgb(119, 136, 153)
|
|
||||||
"lightslategrey": color.RGBA{0x77, 0x88, 0x99, 0xff}, // rgb(119, 136, 153)
|
|
||||||
"lightsteelblue": color.RGBA{0xb0, 0xc4, 0xde, 0xff}, // rgb(176, 196, 222)
|
|
||||||
"lightyellow": color.RGBA{0xff, 0xff, 0xe0, 0xff}, // rgb(255, 255, 224)
|
|
||||||
"lime": color.RGBA{0x00, 0xff, 0x00, 0xff}, // rgb(0, 255, 0)
|
|
||||||
"limegreen": color.RGBA{0x32, 0xcd, 0x32, 0xff}, // rgb(50, 205, 50)
|
|
||||||
"linen": color.RGBA{0xfa, 0xf0, 0xe6, 0xff}, // rgb(250, 240, 230)
|
|
||||||
"magenta": color.RGBA{0xff, 0x00, 0xff, 0xff}, // rgb(255, 0, 255)
|
|
||||||
"maroon": color.RGBA{0x80, 0x00, 0x00, 0xff}, // rgb(128, 0, 0)
|
|
||||||
"mediumaquamarine": color.RGBA{0x66, 0xcd, 0xaa, 0xff}, // rgb(102, 205, 170)
|
|
||||||
"mediumblue": color.RGBA{0x00, 0x00, 0xcd, 0xff}, // rgb(0, 0, 205)
|
|
||||||
"mediumorchid": color.RGBA{0xba, 0x55, 0xd3, 0xff}, // rgb(186, 85, 211)
|
|
||||||
"mediumpurple": color.RGBA{0x93, 0x70, 0xdb, 0xff}, // rgb(147, 112, 219)
|
|
||||||
"mediumseagreen": color.RGBA{0x3c, 0xb3, 0x71, 0xff}, // rgb(60, 179, 113)
|
|
||||||
"mediumslateblue": color.RGBA{0x7b, 0x68, 0xee, 0xff}, // rgb(123, 104, 238)
|
|
||||||
"mediumspringgreen": color.RGBA{0x00, 0xfa, 0x9a, 0xff}, // rgb(0, 250, 154)
|
|
||||||
"mediumturquoise": color.RGBA{0x48, 0xd1, 0xcc, 0xff}, // rgb(72, 209, 204)
|
|
||||||
"mediumvioletred": color.RGBA{0xc7, 0x15, 0x85, 0xff}, // rgb(199, 21, 133)
|
|
||||||
"midnightblue": color.RGBA{0x19, 0x19, 0x70, 0xff}, // rgb(25, 25, 112)
|
|
||||||
"mintcream": color.RGBA{0xf5, 0xff, 0xfa, 0xff}, // rgb(245, 255, 250)
|
|
||||||
"mistyrose": color.RGBA{0xff, 0xe4, 0xe1, 0xff}, // rgb(255, 228, 225)
|
|
||||||
"moccasin": color.RGBA{0xff, 0xe4, 0xb5, 0xff}, // rgb(255, 228, 181)
|
|
||||||
"navajowhite": color.RGBA{0xff, 0xde, 0xad, 0xff}, // rgb(255, 222, 173)
|
|
||||||
"navy": color.RGBA{0x00, 0x00, 0x80, 0xff}, // rgb(0, 0, 128)
|
|
||||||
"oldlace": color.RGBA{0xfd, 0xf5, 0xe6, 0xff}, // rgb(253, 245, 230)
|
|
||||||
"olive": color.RGBA{0x80, 0x80, 0x00, 0xff}, // rgb(128, 128, 0)
|
|
||||||
"olivedrab": color.RGBA{0x6b, 0x8e, 0x23, 0xff}, // rgb(107, 142, 35)
|
|
||||||
"orange": color.RGBA{0xff, 0xa5, 0x00, 0xff}, // rgb(255, 165, 0)
|
|
||||||
"orangered": color.RGBA{0xff, 0x45, 0x00, 0xff}, // rgb(255, 69, 0)
|
|
||||||
"orchid": color.RGBA{0xda, 0x70, 0xd6, 0xff}, // rgb(218, 112, 214)
|
|
||||||
"palegoldenrod": color.RGBA{0xee, 0xe8, 0xaa, 0xff}, // rgb(238, 232, 170)
|
|
||||||
"palegreen": color.RGBA{0x98, 0xfb, 0x98, 0xff}, // rgb(152, 251, 152)
|
|
||||||
"paleturquoise": color.RGBA{0xaf, 0xee, 0xee, 0xff}, // rgb(175, 238, 238)
|
|
||||||
"palevioletred": color.RGBA{0xdb, 0x70, 0x93, 0xff}, // rgb(219, 112, 147)
|
|
||||||
"papayawhip": color.RGBA{0xff, 0xef, 0xd5, 0xff}, // rgb(255, 239, 213)
|
|
||||||
"peachpuff": color.RGBA{0xff, 0xda, 0xb9, 0xff}, // rgb(255, 218, 185)
|
|
||||||
"peru": color.RGBA{0xcd, 0x85, 0x3f, 0xff}, // rgb(205, 133, 63)
|
|
||||||
"pink": color.RGBA{0xff, 0xc0, 0xcb, 0xff}, // rgb(255, 192, 203)
|
|
||||||
"plum": color.RGBA{0xdd, 0xa0, 0xdd, 0xff}, // rgb(221, 160, 221)
|
|
||||||
"powderblue": color.RGBA{0xb0, 0xe0, 0xe6, 0xff}, // rgb(176, 224, 230)
|
|
||||||
"purple": color.RGBA{0x80, 0x00, 0x80, 0xff}, // rgb(128, 0, 128)
|
|
||||||
"red": color.RGBA{0xff, 0x00, 0x00, 0xff}, // rgb(255, 0, 0)
|
|
||||||
"rosybrown": color.RGBA{0xbc, 0x8f, 0x8f, 0xff}, // rgb(188, 143, 143)
|
|
||||||
"royalblue": color.RGBA{0x41, 0x69, 0xe1, 0xff}, // rgb(65, 105, 225)
|
|
||||||
"saddlebrown": color.RGBA{0x8b, 0x45, 0x13, 0xff}, // rgb(139, 69, 19)
|
|
||||||
"salmon": color.RGBA{0xfa, 0x80, 0x72, 0xff}, // rgb(250, 128, 114)
|
|
||||||
"sandybrown": color.RGBA{0xf4, 0xa4, 0x60, 0xff}, // rgb(244, 164, 96)
|
|
||||||
"seagreen": color.RGBA{0x2e, 0x8b, 0x57, 0xff}, // rgb(46, 139, 87)
|
|
||||||
"seashell": color.RGBA{0xff, 0xf5, 0xee, 0xff}, // rgb(255, 245, 238)
|
|
||||||
"sienna": color.RGBA{0xa0, 0x52, 0x2d, 0xff}, // rgb(160, 82, 45)
|
|
||||||
"silver": color.RGBA{0xc0, 0xc0, 0xc0, 0xff}, // rgb(192, 192, 192)
|
|
||||||
"skyblue": color.RGBA{0x87, 0xce, 0xeb, 0xff}, // rgb(135, 206, 235)
|
|
||||||
"slateblue": color.RGBA{0x6a, 0x5a, 0xcd, 0xff}, // rgb(106, 90, 205)
|
|
||||||
"slategray": color.RGBA{0x70, 0x80, 0x90, 0xff}, // rgb(112, 128, 144)
|
|
||||||
"slategrey": color.RGBA{0x70, 0x80, 0x90, 0xff}, // rgb(112, 128, 144)
|
|
||||||
"snow": color.RGBA{0xff, 0xfa, 0xfa, 0xff}, // rgb(255, 250, 250)
|
|
||||||
"springgreen": color.RGBA{0x00, 0xff, 0x7f, 0xff}, // rgb(0, 255, 127)
|
|
||||||
"steelblue": color.RGBA{0x46, 0x82, 0xb4, 0xff}, // rgb(70, 130, 180)
|
|
||||||
"tan": color.RGBA{0xd2, 0xb4, 0x8c, 0xff}, // rgb(210, 180, 140)
|
|
||||||
"teal": color.RGBA{0x00, 0x80, 0x80, 0xff}, // rgb(0, 128, 128)
|
|
||||||
"thistle": color.RGBA{0xd8, 0xbf, 0xd8, 0xff}, // rgb(216, 191, 216)
|
|
||||||
"tomato": color.RGBA{0xff, 0x63, 0x47, 0xff}, // rgb(255, 99, 71)
|
|
||||||
"turquoise": color.RGBA{0x40, 0xe0, 0xd0, 0xff}, // rgb(64, 224, 208)
|
|
||||||
"violet": color.RGBA{0xee, 0x82, 0xee, 0xff}, // rgb(238, 130, 238)
|
|
||||||
"wheat": color.RGBA{0xf5, 0xde, 0xb3, 0xff}, // rgb(245, 222, 179)
|
|
||||||
"white": color.RGBA{0xff, 0xff, 0xff, 0xff}, // rgb(255, 255, 255)
|
|
||||||
"whitesmoke": color.RGBA{0xf5, 0xf5, 0xf5, 0xff}, // rgb(245, 245, 245)
|
|
||||||
"yellow": color.RGBA{0xff, 0xff, 0x00, 0xff}, // rgb(255, 255, 0)
|
|
||||||
"yellowgreen": color.RGBA{0x9a, 0xcd, 0x32, 0xff}, // rgb(154, 205, 50)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Names contains the color names defined in the SVG 1.1 spec.
|
|
||||||
var Names = []string{
|
|
||||||
"aliceblue",
|
|
||||||
"antiquewhite",
|
|
||||||
"aqua",
|
|
||||||
"aquamarine",
|
|
||||||
"azure",
|
|
||||||
"beige",
|
|
||||||
"bisque",
|
|
||||||
"black",
|
|
||||||
"blanchedalmond",
|
|
||||||
"blue",
|
|
||||||
"blueviolet",
|
|
||||||
"brown",
|
|
||||||
"burlywood",
|
|
||||||
"cadetblue",
|
|
||||||
"chartreuse",
|
|
||||||
"chocolate",
|
|
||||||
"coral",
|
|
||||||
"cornflowerblue",
|
|
||||||
"cornsilk",
|
|
||||||
"crimson",
|
|
||||||
"cyan",
|
|
||||||
"darkblue",
|
|
||||||
"darkcyan",
|
|
||||||
"darkgoldenrod",
|
|
||||||
"darkgray",
|
|
||||||
"darkgreen",
|
|
||||||
"darkgrey",
|
|
||||||
"darkkhaki",
|
|
||||||
"darkmagenta",
|
|
||||||
"darkolivegreen",
|
|
||||||
"darkorange",
|
|
||||||
"darkorchid",
|
|
||||||
"darkred",
|
|
||||||
"darksalmon",
|
|
||||||
"darkseagreen",
|
|
||||||
"darkslateblue",
|
|
||||||
"darkslategray",
|
|
||||||
"darkslategrey",
|
|
||||||
"darkturquoise",
|
|
||||||
"darkviolet",
|
|
||||||
"deeppink",
|
|
||||||
"deepskyblue",
|
|
||||||
"dimgray",
|
|
||||||
"dimgrey",
|
|
||||||
"dodgerblue",
|
|
||||||
"firebrick",
|
|
||||||
"floralwhite",
|
|
||||||
"forestgreen",
|
|
||||||
"fuchsia",
|
|
||||||
"gainsboro",
|
|
||||||
"ghostwhite",
|
|
||||||
"gold",
|
|
||||||
"goldenrod",
|
|
||||||
"gray",
|
|
||||||
"green",
|
|
||||||
"greenyellow",
|
|
||||||
"grey",
|
|
||||||
"honeydew",
|
|
||||||
"hotpink",
|
|
||||||
"indianred",
|
|
||||||
"indigo",
|
|
||||||
"ivory",
|
|
||||||
"khaki",
|
|
||||||
"lavender",
|
|
||||||
"lavenderblush",
|
|
||||||
"lawngreen",
|
|
||||||
"lemonchiffon",
|
|
||||||
"lightblue",
|
|
||||||
"lightcoral",
|
|
||||||
"lightcyan",
|
|
||||||
"lightgoldenrodyellow",
|
|
||||||
"lightgray",
|
|
||||||
"lightgreen",
|
|
||||||
"lightgrey",
|
|
||||||
"lightpink",
|
|
||||||
"lightsalmon",
|
|
||||||
"lightseagreen",
|
|
||||||
"lightskyblue",
|
|
||||||
"lightslategray",
|
|
||||||
"lightslategrey",
|
|
||||||
"lightsteelblue",
|
|
||||||
"lightyellow",
|
|
||||||
"lime",
|
|
||||||
"limegreen",
|
|
||||||
"linen",
|
|
||||||
"magenta",
|
|
||||||
"maroon",
|
|
||||||
"mediumaquamarine",
|
|
||||||
"mediumblue",
|
|
||||||
"mediumorchid",
|
|
||||||
"mediumpurple",
|
|
||||||
"mediumseagreen",
|
|
||||||
"mediumslateblue",
|
|
||||||
"mediumspringgreen",
|
|
||||||
"mediumturquoise",
|
|
||||||
"mediumvioletred",
|
|
||||||
"midnightblue",
|
|
||||||
"mintcream",
|
|
||||||
"mistyrose",
|
|
||||||
"moccasin",
|
|
||||||
"navajowhite",
|
|
||||||
"navy",
|
|
||||||
"oldlace",
|
|
||||||
"olive",
|
|
||||||
"olivedrab",
|
|
||||||
"orange",
|
|
||||||
"orangered",
|
|
||||||
"orchid",
|
|
||||||
"palegoldenrod",
|
|
||||||
"palegreen",
|
|
||||||
"paleturquoise",
|
|
||||||
"palevioletred",
|
|
||||||
"papayawhip",
|
|
||||||
"peachpuff",
|
|
||||||
"peru",
|
|
||||||
"pink",
|
|
||||||
"plum",
|
|
||||||
"powderblue",
|
|
||||||
"purple",
|
|
||||||
"red",
|
|
||||||
"rosybrown",
|
|
||||||
"royalblue",
|
|
||||||
"saddlebrown",
|
|
||||||
"salmon",
|
|
||||||
"sandybrown",
|
|
||||||
"seagreen",
|
|
||||||
"seashell",
|
|
||||||
"sienna",
|
|
||||||
"silver",
|
|
||||||
"skyblue",
|
|
||||||
"slateblue",
|
|
||||||
"slategray",
|
|
||||||
"slategrey",
|
|
||||||
"snow",
|
|
||||||
"springgreen",
|
|
||||||
"steelblue",
|
|
||||||
"tan",
|
|
||||||
"teal",
|
|
||||||
"thistle",
|
|
||||||
"tomato",
|
|
||||||
"turquoise",
|
|
||||||
"violet",
|
|
||||||
"wheat",
|
|
||||||
"white",
|
|
||||||
"whitesmoke",
|
|
||||||
"yellow",
|
|
||||||
"yellowgreen",
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
Aliceblue = color.RGBA{0xf0, 0xf8, 0xff, 0xff} // rgb(240, 248, 255)
|
|
||||||
Antiquewhite = color.RGBA{0xfa, 0xeb, 0xd7, 0xff} // rgb(250, 235, 215)
|
|
||||||
Aqua = color.RGBA{0x00, 0xff, 0xff, 0xff} // rgb(0, 255, 255)
|
|
||||||
Aquamarine = color.RGBA{0x7f, 0xff, 0xd4, 0xff} // rgb(127, 255, 212)
|
|
||||||
Azure = color.RGBA{0xf0, 0xff, 0xff, 0xff} // rgb(240, 255, 255)
|
|
||||||
Beige = color.RGBA{0xf5, 0xf5, 0xdc, 0xff} // rgb(245, 245, 220)
|
|
||||||
Bisque = color.RGBA{0xff, 0xe4, 0xc4, 0xff} // rgb(255, 228, 196)
|
|
||||||
Black = color.RGBA{0x00, 0x00, 0x00, 0xff} // rgb(0, 0, 0)
|
|
||||||
Blanchedalmond = color.RGBA{0xff, 0xeb, 0xcd, 0xff} // rgb(255, 235, 205)
|
|
||||||
Blue = color.RGBA{0x00, 0x00, 0xff, 0xff} // rgb(0, 0, 255)
|
|
||||||
Blueviolet = color.RGBA{0x8a, 0x2b, 0xe2, 0xff} // rgb(138, 43, 226)
|
|
||||||
Brown = color.RGBA{0xa5, 0x2a, 0x2a, 0xff} // rgb(165, 42, 42)
|
|
||||||
Burlywood = color.RGBA{0xde, 0xb8, 0x87, 0xff} // rgb(222, 184, 135)
|
|
||||||
Cadetblue = color.RGBA{0x5f, 0x9e, 0xa0, 0xff} // rgb(95, 158, 160)
|
|
||||||
Chartreuse = color.RGBA{0x7f, 0xff, 0x00, 0xff} // rgb(127, 255, 0)
|
|
||||||
Chocolate = color.RGBA{0xd2, 0x69, 0x1e, 0xff} // rgb(210, 105, 30)
|
|
||||||
Coral = color.RGBA{0xff, 0x7f, 0x50, 0xff} // rgb(255, 127, 80)
|
|
||||||
Cornflowerblue = color.RGBA{0x64, 0x95, 0xed, 0xff} // rgb(100, 149, 237)
|
|
||||||
Cornsilk = color.RGBA{0xff, 0xf8, 0xdc, 0xff} // rgb(255, 248, 220)
|
|
||||||
Crimson = color.RGBA{0xdc, 0x14, 0x3c, 0xff} // rgb(220, 20, 60)
|
|
||||||
Cyan = color.RGBA{0x00, 0xff, 0xff, 0xff} // rgb(0, 255, 255)
|
|
||||||
Darkblue = color.RGBA{0x00, 0x00, 0x8b, 0xff} // rgb(0, 0, 139)
|
|
||||||
Darkcyan = color.RGBA{0x00, 0x8b, 0x8b, 0xff} // rgb(0, 139, 139)
|
|
||||||
Darkgoldenrod = color.RGBA{0xb8, 0x86, 0x0b, 0xff} // rgb(184, 134, 11)
|
|
||||||
Darkgray = color.RGBA{0xa9, 0xa9, 0xa9, 0xff} // rgb(169, 169, 169)
|
|
||||||
Darkgreen = color.RGBA{0x00, 0x64, 0x00, 0xff} // rgb(0, 100, 0)
|
|
||||||
Darkgrey = color.RGBA{0xa9, 0xa9, 0xa9, 0xff} // rgb(169, 169, 169)
|
|
||||||
Darkkhaki = color.RGBA{0xbd, 0xb7, 0x6b, 0xff} // rgb(189, 183, 107)
|
|
||||||
Darkmagenta = color.RGBA{0x8b, 0x00, 0x8b, 0xff} // rgb(139, 0, 139)
|
|
||||||
Darkolivegreen = color.RGBA{0x55, 0x6b, 0x2f, 0xff} // rgb(85, 107, 47)
|
|
||||||
Darkorange = color.RGBA{0xff, 0x8c, 0x00, 0xff} // rgb(255, 140, 0)
|
|
||||||
Darkorchid = color.RGBA{0x99, 0x32, 0xcc, 0xff} // rgb(153, 50, 204)
|
|
||||||
Darkred = color.RGBA{0x8b, 0x00, 0x00, 0xff} // rgb(139, 0, 0)
|
|
||||||
Darksalmon = color.RGBA{0xe9, 0x96, 0x7a, 0xff} // rgb(233, 150, 122)
|
|
||||||
Darkseagreen = color.RGBA{0x8f, 0xbc, 0x8f, 0xff} // rgb(143, 188, 143)
|
|
||||||
Darkslateblue = color.RGBA{0x48, 0x3d, 0x8b, 0xff} // rgb(72, 61, 139)
|
|
||||||
Darkslategray = color.RGBA{0x2f, 0x4f, 0x4f, 0xff} // rgb(47, 79, 79)
|
|
||||||
Darkslategrey = color.RGBA{0x2f, 0x4f, 0x4f, 0xff} // rgb(47, 79, 79)
|
|
||||||
Darkturquoise = color.RGBA{0x00, 0xce, 0xd1, 0xff} // rgb(0, 206, 209)
|
|
||||||
Darkviolet = color.RGBA{0x94, 0x00, 0xd3, 0xff} // rgb(148, 0, 211)
|
|
||||||
Deeppink = color.RGBA{0xff, 0x14, 0x93, 0xff} // rgb(255, 20, 147)
|
|
||||||
Deepskyblue = color.RGBA{0x00, 0xbf, 0xff, 0xff} // rgb(0, 191, 255)
|
|
||||||
Dimgray = color.RGBA{0x69, 0x69, 0x69, 0xff} // rgb(105, 105, 105)
|
|
||||||
Dimgrey = color.RGBA{0x69, 0x69, 0x69, 0xff} // rgb(105, 105, 105)
|
|
||||||
Dodgerblue = color.RGBA{0x1e, 0x90, 0xff, 0xff} // rgb(30, 144, 255)
|
|
||||||
Firebrick = color.RGBA{0xb2, 0x22, 0x22, 0xff} // rgb(178, 34, 34)
|
|
||||||
Floralwhite = color.RGBA{0xff, 0xfa, 0xf0, 0xff} // rgb(255, 250, 240)
|
|
||||||
Forestgreen = color.RGBA{0x22, 0x8b, 0x22, 0xff} // rgb(34, 139, 34)
|
|
||||||
Fuchsia = color.RGBA{0xff, 0x00, 0xff, 0xff} // rgb(255, 0, 255)
|
|
||||||
Gainsboro = color.RGBA{0xdc, 0xdc, 0xdc, 0xff} // rgb(220, 220, 220)
|
|
||||||
Ghostwhite = color.RGBA{0xf8, 0xf8, 0xff, 0xff} // rgb(248, 248, 255)
|
|
||||||
Gold = color.RGBA{0xff, 0xd7, 0x00, 0xff} // rgb(255, 215, 0)
|
|
||||||
Goldenrod = color.RGBA{0xda, 0xa5, 0x20, 0xff} // rgb(218, 165, 32)
|
|
||||||
Gray = color.RGBA{0x80, 0x80, 0x80, 0xff} // rgb(128, 128, 128)
|
|
||||||
Green = color.RGBA{0x00, 0x80, 0x00, 0xff} // rgb(0, 128, 0)
|
|
||||||
Greenyellow = color.RGBA{0xad, 0xff, 0x2f, 0xff} // rgb(173, 255, 47)
|
|
||||||
Grey = color.RGBA{0x80, 0x80, 0x80, 0xff} // rgb(128, 128, 128)
|
|
||||||
Honeydew = color.RGBA{0xf0, 0xff, 0xf0, 0xff} // rgb(240, 255, 240)
|
|
||||||
Hotpink = color.RGBA{0xff, 0x69, 0xb4, 0xff} // rgb(255, 105, 180)
|
|
||||||
Indianred = color.RGBA{0xcd, 0x5c, 0x5c, 0xff} // rgb(205, 92, 92)
|
|
||||||
Indigo = color.RGBA{0x4b, 0x00, 0x82, 0xff} // rgb(75, 0, 130)
|
|
||||||
Ivory = color.RGBA{0xff, 0xff, 0xf0, 0xff} // rgb(255, 255, 240)
|
|
||||||
Khaki = color.RGBA{0xf0, 0xe6, 0x8c, 0xff} // rgb(240, 230, 140)
|
|
||||||
Lavender = color.RGBA{0xe6, 0xe6, 0xfa, 0xff} // rgb(230, 230, 250)
|
|
||||||
Lavenderblush = color.RGBA{0xff, 0xf0, 0xf5, 0xff} // rgb(255, 240, 245)
|
|
||||||
Lawngreen = color.RGBA{0x7c, 0xfc, 0x00, 0xff} // rgb(124, 252, 0)
|
|
||||||
Lemonchiffon = color.RGBA{0xff, 0xfa, 0xcd, 0xff} // rgb(255, 250, 205)
|
|
||||||
Lightblue = color.RGBA{0xad, 0xd8, 0xe6, 0xff} // rgb(173, 216, 230)
|
|
||||||
Lightcoral = color.RGBA{0xf0, 0x80, 0x80, 0xff} // rgb(240, 128, 128)
|
|
||||||
Lightcyan = color.RGBA{0xe0, 0xff, 0xff, 0xff} // rgb(224, 255, 255)
|
|
||||||
Lightgoldenrodyellow = color.RGBA{0xfa, 0xfa, 0xd2, 0xff} // rgb(250, 250, 210)
|
|
||||||
Lightgray = color.RGBA{0xd3, 0xd3, 0xd3, 0xff} // rgb(211, 211, 211)
|
|
||||||
Lightgreen = color.RGBA{0x90, 0xee, 0x90, 0xff} // rgb(144, 238, 144)
|
|
||||||
Lightgrey = color.RGBA{0xd3, 0xd3, 0xd3, 0xff} // rgb(211, 211, 211)
|
|
||||||
Lightpink = color.RGBA{0xff, 0xb6, 0xc1, 0xff} // rgb(255, 182, 193)
|
|
||||||
Lightsalmon = color.RGBA{0xff, 0xa0, 0x7a, 0xff} // rgb(255, 160, 122)
|
|
||||||
Lightseagreen = color.RGBA{0x20, 0xb2, 0xaa, 0xff} // rgb(32, 178, 170)
|
|
||||||
Lightskyblue = color.RGBA{0x87, 0xce, 0xfa, 0xff} // rgb(135, 206, 250)
|
|
||||||
Lightslategray = color.RGBA{0x77, 0x88, 0x99, 0xff} // rgb(119, 136, 153)
|
|
||||||
Lightslategrey = color.RGBA{0x77, 0x88, 0x99, 0xff} // rgb(119, 136, 153)
|
|
||||||
Lightsteelblue = color.RGBA{0xb0, 0xc4, 0xde, 0xff} // rgb(176, 196, 222)
|
|
||||||
Lightyellow = color.RGBA{0xff, 0xff, 0xe0, 0xff} // rgb(255, 255, 224)
|
|
||||||
Lime = color.RGBA{0x00, 0xff, 0x00, 0xff} // rgb(0, 255, 0)
|
|
||||||
Limegreen = color.RGBA{0x32, 0xcd, 0x32, 0xff} // rgb(50, 205, 50)
|
|
||||||
Linen = color.RGBA{0xfa, 0xf0, 0xe6, 0xff} // rgb(250, 240, 230)
|
|
||||||
Magenta = color.RGBA{0xff, 0x00, 0xff, 0xff} // rgb(255, 0, 255)
|
|
||||||
Maroon = color.RGBA{0x80, 0x00, 0x00, 0xff} // rgb(128, 0, 0)
|
|
||||||
Mediumaquamarine = color.RGBA{0x66, 0xcd, 0xaa, 0xff} // rgb(102, 205, 170)
|
|
||||||
Mediumblue = color.RGBA{0x00, 0x00, 0xcd, 0xff} // rgb(0, 0, 205)
|
|
||||||
Mediumorchid = color.RGBA{0xba, 0x55, 0xd3, 0xff} // rgb(186, 85, 211)
|
|
||||||
Mediumpurple = color.RGBA{0x93, 0x70, 0xdb, 0xff} // rgb(147, 112, 219)
|
|
||||||
Mediumseagreen = color.RGBA{0x3c, 0xb3, 0x71, 0xff} // rgb(60, 179, 113)
|
|
||||||
Mediumslateblue = color.RGBA{0x7b, 0x68, 0xee, 0xff} // rgb(123, 104, 238)
|
|
||||||
Mediumspringgreen = color.RGBA{0x00, 0xfa, 0x9a, 0xff} // rgb(0, 250, 154)
|
|
||||||
Mediumturquoise = color.RGBA{0x48, 0xd1, 0xcc, 0xff} // rgb(72, 209, 204)
|
|
||||||
Mediumvioletred = color.RGBA{0xc7, 0x15, 0x85, 0xff} // rgb(199, 21, 133)
|
|
||||||
Midnightblue = color.RGBA{0x19, 0x19, 0x70, 0xff} // rgb(25, 25, 112)
|
|
||||||
Mintcream = color.RGBA{0xf5, 0xff, 0xfa, 0xff} // rgb(245, 255, 250)
|
|
||||||
Mistyrose = color.RGBA{0xff, 0xe4, 0xe1, 0xff} // rgb(255, 228, 225)
|
|
||||||
Moccasin = color.RGBA{0xff, 0xe4, 0xb5, 0xff} // rgb(255, 228, 181)
|
|
||||||
Navajowhite = color.RGBA{0xff, 0xde, 0xad, 0xff} // rgb(255, 222, 173)
|
|
||||||
Navy = color.RGBA{0x00, 0x00, 0x80, 0xff} // rgb(0, 0, 128)
|
|
||||||
Oldlace = color.RGBA{0xfd, 0xf5, 0xe6, 0xff} // rgb(253, 245, 230)
|
|
||||||
Olive = color.RGBA{0x80, 0x80, 0x00, 0xff} // rgb(128, 128, 0)
|
|
||||||
Olivedrab = color.RGBA{0x6b, 0x8e, 0x23, 0xff} // rgb(107, 142, 35)
|
|
||||||
Orange = color.RGBA{0xff, 0xa5, 0x00, 0xff} // rgb(255, 165, 0)
|
|
||||||
Orangered = color.RGBA{0xff, 0x45, 0x00, 0xff} // rgb(255, 69, 0)
|
|
||||||
Orchid = color.RGBA{0xda, 0x70, 0xd6, 0xff} // rgb(218, 112, 214)
|
|
||||||
Palegoldenrod = color.RGBA{0xee, 0xe8, 0xaa, 0xff} // rgb(238, 232, 170)
|
|
||||||
Palegreen = color.RGBA{0x98, 0xfb, 0x98, 0xff} // rgb(152, 251, 152)
|
|
||||||
Paleturquoise = color.RGBA{0xaf, 0xee, 0xee, 0xff} // rgb(175, 238, 238)
|
|
||||||
Palevioletred = color.RGBA{0xdb, 0x70, 0x93, 0xff} // rgb(219, 112, 147)
|
|
||||||
Papayawhip = color.RGBA{0xff, 0xef, 0xd5, 0xff} // rgb(255, 239, 213)
|
|
||||||
Peachpuff = color.RGBA{0xff, 0xda, 0xb9, 0xff} // rgb(255, 218, 185)
|
|
||||||
Peru = color.RGBA{0xcd, 0x85, 0x3f, 0xff} // rgb(205, 133, 63)
|
|
||||||
Pink = color.RGBA{0xff, 0xc0, 0xcb, 0xff} // rgb(255, 192, 203)
|
|
||||||
Plum = color.RGBA{0xdd, 0xa0, 0xdd, 0xff} // rgb(221, 160, 221)
|
|
||||||
Powderblue = color.RGBA{0xb0, 0xe0, 0xe6, 0xff} // rgb(176, 224, 230)
|
|
||||||
Purple = color.RGBA{0x80, 0x00, 0x80, 0xff} // rgb(128, 0, 128)
|
|
||||||
Red = color.RGBA{0xff, 0x00, 0x00, 0xff} // rgb(255, 0, 0)
|
|
||||||
Rosybrown = color.RGBA{0xbc, 0x8f, 0x8f, 0xff} // rgb(188, 143, 143)
|
|
||||||
Royalblue = color.RGBA{0x41, 0x69, 0xe1, 0xff} // rgb(65, 105, 225)
|
|
||||||
Saddlebrown = color.RGBA{0x8b, 0x45, 0x13, 0xff} // rgb(139, 69, 19)
|
|
||||||
Salmon = color.RGBA{0xfa, 0x80, 0x72, 0xff} // rgb(250, 128, 114)
|
|
||||||
Sandybrown = color.RGBA{0xf4, 0xa4, 0x60, 0xff} // rgb(244, 164, 96)
|
|
||||||
Seagreen = color.RGBA{0x2e, 0x8b, 0x57, 0xff} // rgb(46, 139, 87)
|
|
||||||
Seashell = color.RGBA{0xff, 0xf5, 0xee, 0xff} // rgb(255, 245, 238)
|
|
||||||
Sienna = color.RGBA{0xa0, 0x52, 0x2d, 0xff} // rgb(160, 82, 45)
|
|
||||||
Silver = color.RGBA{0xc0, 0xc0, 0xc0, 0xff} // rgb(192, 192, 192)
|
|
||||||
Skyblue = color.RGBA{0x87, 0xce, 0xeb, 0xff} // rgb(135, 206, 235)
|
|
||||||
Slateblue = color.RGBA{0x6a, 0x5a, 0xcd, 0xff} // rgb(106, 90, 205)
|
|
||||||
Slategray = color.RGBA{0x70, 0x80, 0x90, 0xff} // rgb(112, 128, 144)
|
|
||||||
Slategrey = color.RGBA{0x70, 0x80, 0x90, 0xff} // rgb(112, 128, 144)
|
|
||||||
Snow = color.RGBA{0xff, 0xfa, 0xfa, 0xff} // rgb(255, 250, 250)
|
|
||||||
Springgreen = color.RGBA{0x00, 0xff, 0x7f, 0xff} // rgb(0, 255, 127)
|
|
||||||
Steelblue = color.RGBA{0x46, 0x82, 0xb4, 0xff} // rgb(70, 130, 180)
|
|
||||||
Tan = color.RGBA{0xd2, 0xb4, 0x8c, 0xff} // rgb(210, 180, 140)
|
|
||||||
Teal = color.RGBA{0x00, 0x80, 0x80, 0xff} // rgb(0, 128, 128)
|
|
||||||
Thistle = color.RGBA{0xd8, 0xbf, 0xd8, 0xff} // rgb(216, 191, 216)
|
|
||||||
Tomato = color.RGBA{0xff, 0x63, 0x47, 0xff} // rgb(255, 99, 71)
|
|
||||||
Turquoise = color.RGBA{0x40, 0xe0, 0xd0, 0xff} // rgb(64, 224, 208)
|
|
||||||
Violet = color.RGBA{0xee, 0x82, 0xee, 0xff} // rgb(238, 130, 238)
|
|
||||||
Wheat = color.RGBA{0xf5, 0xde, 0xb3, 0xff} // rgb(245, 222, 179)
|
|
||||||
White = color.RGBA{0xff, 0xff, 0xff, 0xff} // rgb(255, 255, 255)
|
|
||||||
Whitesmoke = color.RGBA{0xf5, 0xf5, 0xf5, 0xff} // rgb(245, 245, 245)
|
|
||||||
Yellow = color.RGBA{0xff, 0xff, 0x00, 0xff} // rgb(255, 255, 0)
|
|
||||||
Yellowgreen = color.RGBA{0x9a, 0xcd, 0x32, 0xff} // rgb(154, 205, 50)
|
|
||||||
)
|
|
|
@ -1,79 +0,0 @@
|
||||||
// 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 draw provides image composition functions.
|
|
||||||
//
|
|
||||||
// See "The Go image/draw package" for an introduction to this package:
|
|
||||||
// http://golang.org/doc/articles/image_draw.html
|
|
||||||
//
|
|
||||||
// This package is a superset of and a drop-in replacement for the image/draw
|
|
||||||
// package in the standard library.
|
|
||||||
package draw
|
|
||||||
|
|
||||||
// This file just contains the API exported by the image/draw package in the
|
|
||||||
// standard library. Other files in this package provide additional features.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"image"
|
|
||||||
"image/color"
|
|
||||||
"image/draw"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Draw calls DrawMask with a nil mask.
|
|
||||||
func Draw(dst Image, r image.Rectangle, src image.Image, sp image.Point, op Op) {
|
|
||||||
draw.Draw(dst, r, src, sp, draw.Op(op))
|
|
||||||
}
|
|
||||||
|
|
||||||
// DrawMask aligns r.Min in dst with sp in src and mp in mask and then
|
|
||||||
// replaces the rectangle r in dst with the result of a Porter-Duff
|
|
||||||
// composition. A nil mask is treated as opaque.
|
|
||||||
func DrawMask(dst Image, r image.Rectangle, src image.Image, sp image.Point, mask image.Image, mp image.Point, op Op) {
|
|
||||||
draw.DrawMask(dst, r, src, sp, mask, mp, draw.Op(op))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Drawer contains the Draw method.
|
|
||||||
type Drawer interface {
|
|
||||||
// Draw aligns r.Min in dst with sp in src and then replaces the
|
|
||||||
// rectangle r in dst with the result of drawing src on dst.
|
|
||||||
Draw(dst Image, r image.Rectangle, src image.Image, sp image.Point)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FloydSteinberg is a Drawer that is the Src Op with Floyd-Steinberg error
|
|
||||||
// diffusion.
|
|
||||||
var FloydSteinberg Drawer = floydSteinberg{}
|
|
||||||
|
|
||||||
type floydSteinberg struct{}
|
|
||||||
|
|
||||||
func (floydSteinberg) Draw(dst Image, r image.Rectangle, src image.Image, sp image.Point) {
|
|
||||||
draw.FloydSteinberg.Draw(dst, r, src, sp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Image is an image.Image with a Set method to change a single pixel.
|
|
||||||
type Image interface {
|
|
||||||
image.Image
|
|
||||||
Set(x, y int, c color.Color)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Op is a Porter-Duff compositing operator.
|
|
||||||
type Op int
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Over specifies ``(src in mask) over dst''.
|
|
||||||
Over Op = Op(draw.Over)
|
|
||||||
// Src specifies ``src in mask''.
|
|
||||||
Src Op = Op(draw.Src)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Draw implements the Drawer interface by calling the Draw function with
|
|
||||||
// this Op.
|
|
||||||
func (op Op) Draw(dst Image, r image.Rectangle, src image.Image, sp image.Point) {
|
|
||||||
(draw.Op(op)).Draw(dst, r, src, sp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Quantizer produces a palette for an image.
|
|
||||||
type Quantizer interface {
|
|
||||||
// Quantize appends up to cap(p) - len(p) colors to p and returns the
|
|
||||||
// updated palette suitable for converting m to a paletted image.
|
|
||||||
Quantize(p color.Palette, m image.Image) color.Palette
|
|
||||||
}
|
|
|
@ -1,118 +0,0 @@
|
||||||
// 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 draw_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"image"
|
|
||||||
"image/color"
|
|
||||||
"image/png"
|
|
||||||
"log"
|
|
||||||
"math"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"golang.org/x/image/draw"
|
|
||||||
"golang.org/x/image/math/f64"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ExampleDraw() {
|
|
||||||
fSrc, err := os.Open("../testdata/blue-purple-pink.png")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer fSrc.Close()
|
|
||||||
src, err := png.Decode(fSrc)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dst := image.NewRGBA(image.Rect(0, 0, 400, 300))
|
|
||||||
green := image.NewUniform(color.RGBA{0x00, 0x1f, 0x00, 0xff})
|
|
||||||
draw.Copy(dst, image.Point{}, green, dst.Bounds(), draw.Src, nil)
|
|
||||||
qs := []draw.Interpolator{
|
|
||||||
draw.NearestNeighbor,
|
|
||||||
draw.ApproxBiLinear,
|
|
||||||
draw.CatmullRom,
|
|
||||||
}
|
|
||||||
const cos60, sin60 = 0.5, 0.866025404
|
|
||||||
t := f64.Aff3{
|
|
||||||
+2 * cos60, -2 * sin60, 100,
|
|
||||||
+2 * sin60, +2 * cos60, 100,
|
|
||||||
}
|
|
||||||
|
|
||||||
draw.Copy(dst, image.Point{20, 30}, src, src.Bounds(), draw.Over, nil)
|
|
||||||
for i, q := range qs {
|
|
||||||
q.Scale(dst, image.Rect(200+10*i, 100*i, 600+10*i, 150+100*i), src, src.Bounds(), draw.Over, nil)
|
|
||||||
}
|
|
||||||
draw.NearestNeighbor.Transform(dst, t, src, src.Bounds(), draw.Over, nil)
|
|
||||||
|
|
||||||
red := image.NewNRGBA(image.Rect(0, 0, 16, 16))
|
|
||||||
for y := 0; y < 16; y++ {
|
|
||||||
for x := 0; x < 16; x++ {
|
|
||||||
red.SetNRGBA(x, y, color.NRGBA{
|
|
||||||
R: uint8(x * 0x11),
|
|
||||||
A: uint8(y * 0x11),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
red.SetNRGBA(0, 0, color.NRGBA{0xff, 0xff, 0x00, 0xff})
|
|
||||||
red.SetNRGBA(15, 15, color.NRGBA{0xff, 0xff, 0x00, 0xff})
|
|
||||||
|
|
||||||
ops := []draw.Op{
|
|
||||||
draw.Over,
|
|
||||||
draw.Src,
|
|
||||||
}
|
|
||||||
for i, op := range ops {
|
|
||||||
dr := image.Rect(120+10*i, 150+60*i, 170+10*i, 200+60*i)
|
|
||||||
draw.NearestNeighbor.Scale(dst, dr, red, red.Bounds(), op, nil)
|
|
||||||
t := f64.Aff3{
|
|
||||||
+cos60, -sin60, float64(190 + 10*i),
|
|
||||||
+sin60, +cos60, float64(140 + 50*i),
|
|
||||||
}
|
|
||||||
draw.NearestNeighbor.Transform(dst, t, red, red.Bounds(), op, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
dr := image.Rect(0, 0, 128, 128)
|
|
||||||
checkerboard := image.NewAlpha(dr)
|
|
||||||
for y := dr.Min.Y; y < dr.Max.Y; y++ {
|
|
||||||
for x := dr.Min.X; x < dr.Max.X; x++ {
|
|
||||||
if (x/20)%2 == (y/20)%2 {
|
|
||||||
checkerboard.SetAlpha(x, y, color.Alpha{0xff})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sr := image.Rect(0, 0, 16, 16)
|
|
||||||
circle := image.NewAlpha(sr)
|
|
||||||
for y := sr.Min.Y; y < sr.Max.Y; y++ {
|
|
||||||
for x := sr.Min.X; x < sr.Max.X; x++ {
|
|
||||||
dx, dy := x-10, y-8
|
|
||||||
if d := 32 * math.Sqrt(float64(dx*dx)+float64(dy*dy)); d < 0xff {
|
|
||||||
circle.SetAlpha(x, y, color.Alpha{0xff - uint8(d)})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cyan := image.NewUniform(color.RGBA{0x00, 0xff, 0xff, 0xff})
|
|
||||||
draw.NearestNeighbor.Scale(dst, dr, cyan, sr, draw.Over, &draw.Options{
|
|
||||||
DstMask: checkerboard,
|
|
||||||
SrcMask: circle,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Change false to true to write the resultant image to disk.
|
|
||||||
if false {
|
|
||||||
fDst, err := os.Create("out.png")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer fDst.Close()
|
|
||||||
err = png.Encode(fDst, dst)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("dst has bounds %v.\n", dst.Bounds())
|
|
||||||
// Output:
|
|
||||||
// dst has bounds (0,0)-(400,300).
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,527 +0,0 @@
|
||||||
// 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.
|
|
||||||
|
|
||||||
//go:generate go run gen.go
|
|
||||||
|
|
||||||
package draw
|
|
||||||
|
|
||||||
import (
|
|
||||||
"image"
|
|
||||||
"image/color"
|
|
||||||
"math"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"golang.org/x/image/math/f64"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Copy copies the part of the source image defined by src and sr and writes
|
|
||||||
// the result of a Porter-Duff composition to the part of the destination image
|
|
||||||
// defined by dst and the translation of sr so that sr.Min translates to dp.
|
|
||||||
func Copy(dst Image, dp image.Point, src image.Image, sr image.Rectangle, op Op, opts *Options) {
|
|
||||||
var o Options
|
|
||||||
if opts != nil {
|
|
||||||
o = *opts
|
|
||||||
}
|
|
||||||
dr := sr.Add(dp.Sub(sr.Min))
|
|
||||||
if o.DstMask == nil {
|
|
||||||
DrawMask(dst, dr, src, sr.Min, o.SrcMask, o.SrcMaskP.Add(sr.Min), op)
|
|
||||||
} else {
|
|
||||||
NearestNeighbor.Scale(dst, dr, src, sr, op, opts)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scaler scales the part of the source image defined by src and sr and writes
|
|
||||||
// the result of a Porter-Duff composition to the part of the destination image
|
|
||||||
// defined by dst and dr.
|
|
||||||
//
|
|
||||||
// A Scaler is safe to use concurrently.
|
|
||||||
type Scaler interface {
|
|
||||||
Scale(dst Image, dr image.Rectangle, src image.Image, sr image.Rectangle, op Op, opts *Options)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transformer transforms the part of the source image defined by src and sr
|
|
||||||
// and writes the result of a Porter-Duff composition to the part of the
|
|
||||||
// destination image defined by dst and the affine transform m applied to sr.
|
|
||||||
//
|
|
||||||
// For example, if m is the matrix
|
|
||||||
//
|
|
||||||
// m00 m01 m02
|
|
||||||
// m10 m11 m12
|
|
||||||
//
|
|
||||||
// then the src-space point (sx, sy) maps to the dst-space point
|
|
||||||
// (m00*sx + m01*sy + m02, m10*sx + m11*sy + m12).
|
|
||||||
//
|
|
||||||
// A Transformer is safe to use concurrently.
|
|
||||||
type Transformer interface {
|
|
||||||
Transform(dst Image, m f64.Aff3, src image.Image, sr image.Rectangle, op Op, opts *Options)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Options are optional parameters to Copy, Scale and Transform.
|
|
||||||
//
|
|
||||||
// A nil *Options means to use the default (zero) values of each field.
|
|
||||||
type Options struct {
|
|
||||||
// Masks limit what parts of the dst image are drawn to and what parts of
|
|
||||||
// the src image are drawn from.
|
|
||||||
//
|
|
||||||
// A dst or src mask image having a zero alpha (transparent) pixel value in
|
|
||||||
// the respective coordinate space means that that dst pixel is entirely
|
|
||||||
// unaffected or that src pixel is considered transparent black. A full
|
|
||||||
// alpha (opaque) value means that the dst pixel is maximally affected or
|
|
||||||
// the src pixel contributes maximally. The default values, nil, are
|
|
||||||
// equivalent to fully opaque, infinitely large mask images.
|
|
||||||
//
|
|
||||||
// The DstMask is otherwise known as a clip mask, and its pixels map 1:1 to
|
|
||||||
// the dst image's pixels. DstMaskP in DstMask space corresponds to
|
|
||||||
// image.Point{X:0, Y:0} in dst space. For example, when limiting
|
|
||||||
// repainting to a 'dirty rectangle', use that image.Rectangle and a zero
|
|
||||||
// image.Point as the DstMask and DstMaskP.
|
|
||||||
//
|
|
||||||
// The SrcMask's pixels map 1:1 to the src image's pixels. SrcMaskP in
|
|
||||||
// SrcMask space corresponds to image.Point{X:0, Y:0} in src space. For
|
|
||||||
// example, when drawing font glyphs in a uniform color, use an
|
|
||||||
// *image.Uniform as the src, and use the glyph atlas image and the
|
|
||||||
// per-glyph offset as SrcMask and SrcMaskP:
|
|
||||||
// Copy(dst, dp, image.NewUniform(color), image.Rect(0, 0, glyphWidth, glyphHeight), &Options{
|
|
||||||
// SrcMask: glyphAtlas,
|
|
||||||
// SrcMaskP: glyphOffset,
|
|
||||||
// })
|
|
||||||
DstMask image.Image
|
|
||||||
DstMaskP image.Point
|
|
||||||
SrcMask image.Image
|
|
||||||
SrcMaskP image.Point
|
|
||||||
|
|
||||||
// TODO: a smooth vs sharp edges option, for arbitrary rotations?
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interpolator is an interpolation algorithm, when dst and src pixels don't
|
|
||||||
// have a 1:1 correspondence.
|
|
||||||
//
|
|
||||||
// Of the interpolators provided by this package:
|
|
||||||
// - NearestNeighbor is fast but usually looks worst.
|
|
||||||
// - CatmullRom is slow but usually looks best.
|
|
||||||
// - ApproxBiLinear has reasonable speed and quality.
|
|
||||||
//
|
|
||||||
// The time taken depends on the size of dr. For kernel interpolators, the
|
|
||||||
// speed also depends on the size of sr, and so are often slower than
|
|
||||||
// non-kernel interpolators, especially when scaling down.
|
|
||||||
type Interpolator interface {
|
|
||||||
Scaler
|
|
||||||
Transformer
|
|
||||||
}
|
|
||||||
|
|
||||||
// Kernel is an interpolator that blends source pixels weighted by a symmetric
|
|
||||||
// kernel function.
|
|
||||||
type Kernel struct {
|
|
||||||
// Support is the kernel support and must be >= 0. At(t) is assumed to be
|
|
||||||
// zero when t >= Support.
|
|
||||||
Support float64
|
|
||||||
// At is the kernel function. It will only be called with t in the
|
|
||||||
// range [0, Support).
|
|
||||||
At func(t float64) float64
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scale implements the Scaler interface.
|
|
||||||
func (q *Kernel) Scale(dst Image, dr image.Rectangle, src image.Image, sr image.Rectangle, op Op, opts *Options) {
|
|
||||||
q.newScaler(dr.Dx(), dr.Dy(), sr.Dx(), sr.Dy(), false).Scale(dst, dr, src, sr, op, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewScaler returns a Scaler that is optimized for scaling multiple times with
|
|
||||||
// the same fixed destination and source width and height.
|
|
||||||
func (q *Kernel) NewScaler(dw, dh, sw, sh int) Scaler {
|
|
||||||
return q.newScaler(dw, dh, sw, sh, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Kernel) newScaler(dw, dh, sw, sh int, usePool bool) Scaler {
|
|
||||||
z := &kernelScaler{
|
|
||||||
kernel: q,
|
|
||||||
dw: int32(dw),
|
|
||||||
dh: int32(dh),
|
|
||||||
sw: int32(sw),
|
|
||||||
sh: int32(sh),
|
|
||||||
horizontal: newDistrib(q, int32(dw), int32(sw)),
|
|
||||||
vertical: newDistrib(q, int32(dh), int32(sh)),
|
|
||||||
}
|
|
||||||
if usePool {
|
|
||||||
z.pool.New = func() interface{} {
|
|
||||||
tmp := z.makeTmpBuf()
|
|
||||||
return &tmp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return z
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
// NearestNeighbor is the nearest neighbor interpolator. It is very fast,
|
|
||||||
// but usually gives very low quality results. When scaling up, the result
|
|
||||||
// will look 'blocky'.
|
|
||||||
NearestNeighbor = Interpolator(nnInterpolator{})
|
|
||||||
|
|
||||||
// ApproxBiLinear is a mixture of the nearest neighbor and bi-linear
|
|
||||||
// interpolators. It is fast, but usually gives medium quality results.
|
|
||||||
//
|
|
||||||
// It implements bi-linear interpolation when upscaling and a bi-linear
|
|
||||||
// blend of the 4 nearest neighbor pixels when downscaling. This yields
|
|
||||||
// nicer quality than nearest neighbor interpolation when upscaling, but
|
|
||||||
// the time taken is independent of the number of source pixels, unlike the
|
|
||||||
// bi-linear interpolator. When downscaling a large image, the performance
|
|
||||||
// difference can be significant.
|
|
||||||
ApproxBiLinear = Interpolator(ablInterpolator{})
|
|
||||||
|
|
||||||
// BiLinear is the tent kernel. It is slow, but usually gives high quality
|
|
||||||
// results.
|
|
||||||
BiLinear = &Kernel{1, func(t float64) float64 {
|
|
||||||
return 1 - t
|
|
||||||
}}
|
|
||||||
|
|
||||||
// CatmullRom is the Catmull-Rom kernel. It is very slow, but usually gives
|
|
||||||
// very high quality results.
|
|
||||||
//
|
|
||||||
// It is an instance of the more general cubic BC-spline kernel with parameters
|
|
||||||
// B=0 and C=0.5. See Mitchell and Netravali, "Reconstruction Filters in
|
|
||||||
// Computer Graphics", Computer Graphics, Vol. 22, No. 4, pp. 221-228.
|
|
||||||
CatmullRom = &Kernel{2, func(t float64) float64 {
|
|
||||||
if t < 1 {
|
|
||||||
return (1.5*t-2.5)*t*t + 1
|
|
||||||
}
|
|
||||||
return ((-0.5*t+2.5)*t-4)*t + 2
|
|
||||||
}}
|
|
||||||
|
|
||||||
// TODO: a Kaiser-Bessel kernel?
|
|
||||||
)
|
|
||||||
|
|
||||||
type nnInterpolator struct{}
|
|
||||||
|
|
||||||
type ablInterpolator struct{}
|
|
||||||
|
|
||||||
type kernelScaler struct {
|
|
||||||
kernel *Kernel
|
|
||||||
dw, dh, sw, sh int32
|
|
||||||
horizontal, vertical distrib
|
|
||||||
pool sync.Pool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (z *kernelScaler) makeTmpBuf() [][4]float64 {
|
|
||||||
return make([][4]float64, z.dw*z.sh)
|
|
||||||
}
|
|
||||||
|
|
||||||
// source is a range of contribs, their inverse total weight, and that ITW
|
|
||||||
// divided by 0xffff.
|
|
||||||
type source struct {
|
|
||||||
i, j int32
|
|
||||||
invTotalWeight float64
|
|
||||||
invTotalWeightFFFF float64
|
|
||||||
}
|
|
||||||
|
|
||||||
// contrib is the weight of a column or row.
|
|
||||||
type contrib struct {
|
|
||||||
coord int32
|
|
||||||
weight float64
|
|
||||||
}
|
|
||||||
|
|
||||||
// distrib measures how source pixels are distributed over destination pixels.
|
|
||||||
type distrib struct {
|
|
||||||
// sources are what contribs each column or row in the source image owns,
|
|
||||||
// and the total weight of those contribs.
|
|
||||||
sources []source
|
|
||||||
// contribs are the contributions indexed by sources[s].i and sources[s].j.
|
|
||||||
contribs []contrib
|
|
||||||
}
|
|
||||||
|
|
||||||
// newDistrib returns a distrib that distributes sw source columns (or rows)
|
|
||||||
// over dw destination columns (or rows).
|
|
||||||
func newDistrib(q *Kernel, dw, sw int32) distrib {
|
|
||||||
scale := float64(sw) / float64(dw)
|
|
||||||
halfWidth, kernelArgScale := q.Support, 1.0
|
|
||||||
// When shrinking, broaden the effective kernel support so that we still
|
|
||||||
// visit every source pixel.
|
|
||||||
if scale > 1 {
|
|
||||||
halfWidth *= scale
|
|
||||||
kernelArgScale = 1 / scale
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make the sources slice, one source for each column or row, and temporarily
|
|
||||||
// appropriate its elements' fields so that invTotalWeight is the scaled
|
|
||||||
// coordinate of the source column or row, and i and j are the lower and
|
|
||||||
// upper bounds of the range of destination columns or rows affected by the
|
|
||||||
// source column or row.
|
|
||||||
n, sources := int32(0), make([]source, dw)
|
|
||||||
for x := range sources {
|
|
||||||
center := (float64(x)+0.5)*scale - 0.5
|
|
||||||
i := int32(math.Floor(center - halfWidth))
|
|
||||||
if i < 0 {
|
|
||||||
i = 0
|
|
||||||
}
|
|
||||||
j := int32(math.Ceil(center + halfWidth))
|
|
||||||
if j > sw {
|
|
||||||
j = sw
|
|
||||||
if j < i {
|
|
||||||
j = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sources[x] = source{i: i, j: j, invTotalWeight: center}
|
|
||||||
n += j - i
|
|
||||||
}
|
|
||||||
|
|
||||||
contribs := make([]contrib, 0, n)
|
|
||||||
for k, b := range sources {
|
|
||||||
totalWeight := 0.0
|
|
||||||
l := int32(len(contribs))
|
|
||||||
for coord := b.i; coord < b.j; coord++ {
|
|
||||||
t := abs((b.invTotalWeight - float64(coord)) * kernelArgScale)
|
|
||||||
if t >= q.Support {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
weight := q.At(t)
|
|
||||||
if weight == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
totalWeight += weight
|
|
||||||
contribs = append(contribs, contrib{coord, weight})
|
|
||||||
}
|
|
||||||
totalWeight = 1 / totalWeight
|
|
||||||
sources[k] = source{
|
|
||||||
i: l,
|
|
||||||
j: int32(len(contribs)),
|
|
||||||
invTotalWeight: totalWeight,
|
|
||||||
invTotalWeightFFFF: totalWeight / 0xffff,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return distrib{sources, contribs}
|
|
||||||
}
|
|
||||||
|
|
||||||
// abs is like math.Abs, but it doesn't care about negative zero, infinities or
|
|
||||||
// NaNs.
|
|
||||||
func abs(f float64) float64 {
|
|
||||||
if f < 0 {
|
|
||||||
f = -f
|
|
||||||
}
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
// ftou converts the range [0.0, 1.0] to [0, 0xffff].
|
|
||||||
func ftou(f float64) uint16 {
|
|
||||||
i := int32(0xffff*f + 0.5)
|
|
||||||
if i > 0xffff {
|
|
||||||
return 0xffff
|
|
||||||
}
|
|
||||||
if i > 0 {
|
|
||||||
return uint16(i)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// fffftou converts the range [0.0, 65535.0] to [0, 0xffff].
|
|
||||||
func fffftou(f float64) uint16 {
|
|
||||||
i := int32(f + 0.5)
|
|
||||||
if i > 0xffff {
|
|
||||||
return 0xffff
|
|
||||||
}
|
|
||||||
if i > 0 {
|
|
||||||
return uint16(i)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// invert returns the inverse of m.
|
|
||||||
//
|
|
||||||
// TODO: move this into the f64 package, once we work out the convention for
|
|
||||||
// matrix methods in that package: do they modify the receiver, take a dst
|
|
||||||
// pointer argument, or return a new value?
|
|
||||||
func invert(m *f64.Aff3) f64.Aff3 {
|
|
||||||
m00 := +m[3*1+1]
|
|
||||||
m01 := -m[3*0+1]
|
|
||||||
m02 := +m[3*1+2]*m[3*0+1] - m[3*1+1]*m[3*0+2]
|
|
||||||
m10 := -m[3*1+0]
|
|
||||||
m11 := +m[3*0+0]
|
|
||||||
m12 := +m[3*1+0]*m[3*0+2] - m[3*1+2]*m[3*0+0]
|
|
||||||
|
|
||||||
det := m00*m11 - m10*m01
|
|
||||||
|
|
||||||
return f64.Aff3{
|
|
||||||
m00 / det,
|
|
||||||
m01 / det,
|
|
||||||
m02 / det,
|
|
||||||
m10 / det,
|
|
||||||
m11 / det,
|
|
||||||
m12 / det,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func matMul(p, q *f64.Aff3) f64.Aff3 {
|
|
||||||
return f64.Aff3{
|
|
||||||
p[3*0+0]*q[3*0+0] + p[3*0+1]*q[3*1+0],
|
|
||||||
p[3*0+0]*q[3*0+1] + p[3*0+1]*q[3*1+1],
|
|
||||||
p[3*0+0]*q[3*0+2] + p[3*0+1]*q[3*1+2] + p[3*0+2],
|
|
||||||
p[3*1+0]*q[3*0+0] + p[3*1+1]*q[3*1+0],
|
|
||||||
p[3*1+0]*q[3*0+1] + p[3*1+1]*q[3*1+1],
|
|
||||||
p[3*1+0]*q[3*0+2] + p[3*1+1]*q[3*1+2] + p[3*1+2],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// transformRect returns a rectangle dr that contains sr transformed by s2d.
|
|
||||||
func transformRect(s2d *f64.Aff3, sr *image.Rectangle) (dr image.Rectangle) {
|
|
||||||
ps := [...]image.Point{
|
|
||||||
{sr.Min.X, sr.Min.Y},
|
|
||||||
{sr.Max.X, sr.Min.Y},
|
|
||||||
{sr.Min.X, sr.Max.Y},
|
|
||||||
{sr.Max.X, sr.Max.Y},
|
|
||||||
}
|
|
||||||
for i, p := range ps {
|
|
||||||
sxf := float64(p.X)
|
|
||||||
syf := float64(p.Y)
|
|
||||||
dx := int(math.Floor(s2d[0]*sxf + s2d[1]*syf + s2d[2]))
|
|
||||||
dy := int(math.Floor(s2d[3]*sxf + s2d[4]*syf + s2d[5]))
|
|
||||||
|
|
||||||
// The +1 adjustments below are because an image.Rectangle is inclusive
|
|
||||||
// on the low end but exclusive on the high end.
|
|
||||||
|
|
||||||
if i == 0 {
|
|
||||||
dr = image.Rectangle{
|
|
||||||
Min: image.Point{dx + 0, dy + 0},
|
|
||||||
Max: image.Point{dx + 1, dy + 1},
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if dr.Min.X > dx {
|
|
||||||
dr.Min.X = dx
|
|
||||||
}
|
|
||||||
dx++
|
|
||||||
if dr.Max.X < dx {
|
|
||||||
dr.Max.X = dx
|
|
||||||
}
|
|
||||||
|
|
||||||
if dr.Min.Y > dy {
|
|
||||||
dr.Min.Y = dy
|
|
||||||
}
|
|
||||||
dy++
|
|
||||||
if dr.Max.Y < dy {
|
|
||||||
dr.Max.Y = dy
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dr
|
|
||||||
}
|
|
||||||
|
|
||||||
func clipAffectedDestRect(adr image.Rectangle, dstMask image.Image, dstMaskP image.Point) (image.Rectangle, image.Image) {
|
|
||||||
if dstMask == nil {
|
|
||||||
return adr, nil
|
|
||||||
}
|
|
||||||
// TODO: enable this fast path once Go 1.5 is released, where an
|
|
||||||
// image.Rectangle implements image.Image.
|
|
||||||
// if r, ok := dstMask.(image.Rectangle); ok {
|
|
||||||
// return adr.Intersect(r.Sub(dstMaskP)), nil
|
|
||||||
// }
|
|
||||||
// TODO: clip to dstMask.Bounds() if the color model implies that out-of-bounds means 0 alpha?
|
|
||||||
return adr, dstMask
|
|
||||||
}
|
|
||||||
|
|
||||||
func transform_Uniform(dst Image, dr, adr image.Rectangle, d2s *f64.Aff3, src *image.Uniform, sr image.Rectangle, bias image.Point, op Op) {
|
|
||||||
switch op {
|
|
||||||
case Over:
|
|
||||||
switch dst := dst.(type) {
|
|
||||||
case *image.RGBA:
|
|
||||||
pr, pg, pb, pa := src.C.RGBA()
|
|
||||||
pa1 := (0xffff - pa) * 0x101
|
|
||||||
|
|
||||||
for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ {
|
|
||||||
dyf := float64(dr.Min.Y+int(dy)) + 0.5
|
|
||||||
d := dst.PixOffset(dr.Min.X+adr.Min.X, dr.Min.Y+int(dy))
|
|
||||||
for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 {
|
|
||||||
dxf := float64(dr.Min.X+int(dx)) + 0.5
|
|
||||||
sx0 := int(d2s[0]*dxf+d2s[1]*dyf+d2s[2]) + bias.X
|
|
||||||
sy0 := int(d2s[3]*dxf+d2s[4]*dyf+d2s[5]) + bias.Y
|
|
||||||
if !(image.Point{sx0, sy0}).In(sr) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
dst.Pix[d+0] = uint8((uint32(dst.Pix[d+0])*pa1/0xffff + pr) >> 8)
|
|
||||||
dst.Pix[d+1] = uint8((uint32(dst.Pix[d+1])*pa1/0xffff + pg) >> 8)
|
|
||||||
dst.Pix[d+2] = uint8((uint32(dst.Pix[d+2])*pa1/0xffff + pb) >> 8)
|
|
||||||
dst.Pix[d+3] = uint8((uint32(dst.Pix[d+3])*pa1/0xffff + pa) >> 8)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
pr, pg, pb, pa := src.C.RGBA()
|
|
||||||
pa1 := 0xffff - pa
|
|
||||||
dstColorRGBA64 := &color.RGBA64{}
|
|
||||||
dstColor := color.Color(dstColorRGBA64)
|
|
||||||
|
|
||||||
for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ {
|
|
||||||
dyf := float64(dr.Min.Y+int(dy)) + 0.5
|
|
||||||
for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx++ {
|
|
||||||
dxf := float64(dr.Min.X+int(dx)) + 0.5
|
|
||||||
sx0 := int(d2s[0]*dxf+d2s[1]*dyf+d2s[2]) + bias.X
|
|
||||||
sy0 := int(d2s[3]*dxf+d2s[4]*dyf+d2s[5]) + bias.Y
|
|
||||||
if !(image.Point{sx0, sy0}).In(sr) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
qr, qg, qb, qa := dst.At(dr.Min.X+int(dx), dr.Min.Y+int(dy)).RGBA()
|
|
||||||
dstColorRGBA64.R = uint16(qr*pa1/0xffff + pr)
|
|
||||||
dstColorRGBA64.G = uint16(qg*pa1/0xffff + pg)
|
|
||||||
dstColorRGBA64.B = uint16(qb*pa1/0xffff + pb)
|
|
||||||
dstColorRGBA64.A = uint16(qa*pa1/0xffff + pa)
|
|
||||||
dst.Set(dr.Min.X+int(dx), dr.Min.Y+int(dy), dstColor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case Src:
|
|
||||||
switch dst := dst.(type) {
|
|
||||||
case *image.RGBA:
|
|
||||||
pr, pg, pb, pa := src.C.RGBA()
|
|
||||||
pr8 := uint8(pr >> 8)
|
|
||||||
pg8 := uint8(pg >> 8)
|
|
||||||
pb8 := uint8(pb >> 8)
|
|
||||||
pa8 := uint8(pa >> 8)
|
|
||||||
|
|
||||||
for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ {
|
|
||||||
dyf := float64(dr.Min.Y+int(dy)) + 0.5
|
|
||||||
d := dst.PixOffset(dr.Min.X+adr.Min.X, dr.Min.Y+int(dy))
|
|
||||||
for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx, d = dx+1, d+4 {
|
|
||||||
dxf := float64(dr.Min.X+int(dx)) + 0.5
|
|
||||||
sx0 := int(d2s[0]*dxf+d2s[1]*dyf+d2s[2]) + bias.X
|
|
||||||
sy0 := int(d2s[3]*dxf+d2s[4]*dyf+d2s[5]) + bias.Y
|
|
||||||
if !(image.Point{sx0, sy0}).In(sr) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
dst.Pix[d+0] = pr8
|
|
||||||
dst.Pix[d+1] = pg8
|
|
||||||
dst.Pix[d+2] = pb8
|
|
||||||
dst.Pix[d+3] = pa8
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
pr, pg, pb, pa := src.C.RGBA()
|
|
||||||
dstColorRGBA64 := &color.RGBA64{
|
|
||||||
uint16(pr),
|
|
||||||
uint16(pg),
|
|
||||||
uint16(pb),
|
|
||||||
uint16(pa),
|
|
||||||
}
|
|
||||||
dstColor := color.Color(dstColorRGBA64)
|
|
||||||
|
|
||||||
for dy := int32(adr.Min.Y); dy < int32(adr.Max.Y); dy++ {
|
|
||||||
dyf := float64(dr.Min.Y+int(dy)) + 0.5
|
|
||||||
for dx := int32(adr.Min.X); dx < int32(adr.Max.X); dx++ {
|
|
||||||
dxf := float64(dr.Min.X+int(dx)) + 0.5
|
|
||||||
sx0 := int(d2s[0]*dxf+d2s[1]*dyf+d2s[2]) + bias.X
|
|
||||||
sy0 := int(d2s[3]*dxf+d2s[4]*dyf+d2s[5]) + bias.Y
|
|
||||||
if !(image.Point{sx0, sy0}).In(sr) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
dst.Set(dr.Min.X+int(dx), dr.Min.Y+int(dy), dstColor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func opaque(m image.Image) bool {
|
|
||||||
o, ok := m.(interface {
|
|
||||||
Opaque() bool
|
|
||||||
})
|
|
||||||
return ok && o.Opaque()
|
|
||||||
}
|
|
|
@ -1,731 +0,0 @@
|
||||||
// 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 draw
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"image"
|
|
||||||
"image/color"
|
|
||||||
"image/png"
|
|
||||||
"math/rand"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"golang.org/x/image/math/f64"
|
|
||||||
|
|
||||||
_ "image/jpeg"
|
|
||||||
)
|
|
||||||
|
|
||||||
var genGoldenFiles = flag.Bool("gen_golden_files", false, "whether to generate the TestXxx golden files.")
|
|
||||||
|
|
||||||
var transformMatrix = func(scale, tx, ty float64) f64.Aff3 {
|
|
||||||
const cos30, sin30 = 0.866025404, 0.5
|
|
||||||
return f64.Aff3{
|
|
||||||
+scale * cos30, -scale * sin30, tx,
|
|
||||||
+scale * sin30, +scale * cos30, ty,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func encode(filename string, m image.Image) error {
|
|
||||||
f, err := os.Create(filename)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Create: %v", err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
if err := png.Encode(f, m); err != nil {
|
|
||||||
return fmt.Errorf("Encode: %v", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// testInterp tests that interpolating the source image gives the exact
|
|
||||||
// destination image. This is to ensure that any refactoring or optimization of
|
|
||||||
// the interpolation code doesn't change the behavior. Changing the actual
|
|
||||||
// algorithm or kernel used by any particular quality setting will obviously
|
|
||||||
// change the resultant pixels. In such a case, use the gen_golden_files flag
|
|
||||||
// to regenerate the golden files.
|
|
||||||
func testInterp(t *testing.T, w int, h int, direction, prefix, suffix string) {
|
|
||||||
f, err := os.Open("../testdata/" + prefix + suffix)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Open: %v", err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
src, _, err := image.Decode(f)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Decode: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
op, scale := Src, 3.75
|
|
||||||
if prefix == "tux" {
|
|
||||||
op, scale = Over, 0.125
|
|
||||||
}
|
|
||||||
green := image.NewUniform(color.RGBA{0x00, 0x22, 0x11, 0xff})
|
|
||||||
|
|
||||||
testCases := map[string]Interpolator{
|
|
||||||
"nn": NearestNeighbor,
|
|
||||||
"ab": ApproxBiLinear,
|
|
||||||
"bl": BiLinear,
|
|
||||||
"cr": CatmullRom,
|
|
||||||
}
|
|
||||||
for name, q := range testCases {
|
|
||||||
goldenFilename := fmt.Sprintf("../testdata/%s-%s-%s.png", prefix, direction, name)
|
|
||||||
|
|
||||||
got := image.NewRGBA(image.Rect(0, 0, w, h))
|
|
||||||
Copy(got, image.Point{}, green, got.Bounds(), Src, nil)
|
|
||||||
if direction == "rotate" {
|
|
||||||
q.Transform(got, transformMatrix(scale, 40, 10), src, src.Bounds(), op, nil)
|
|
||||||
} else {
|
|
||||||
q.Scale(got, got.Bounds(), src, src.Bounds(), op, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *genGoldenFiles {
|
|
||||||
if err := encode(goldenFilename, got); err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
g, err := os.Open(goldenFilename)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Open: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
defer g.Close()
|
|
||||||
wantRaw, err := png.Decode(g)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Decode: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// convert wantRaw to RGBA.
|
|
||||||
want, ok := wantRaw.(*image.RGBA)
|
|
||||||
if !ok {
|
|
||||||
b := wantRaw.Bounds()
|
|
||||||
want = image.NewRGBA(b)
|
|
||||||
Draw(want, b, wantRaw, b.Min, Src)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !reflect.DeepEqual(got, want) {
|
|
||||||
t.Errorf("%s: actual image differs from golden image", goldenFilename)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestScaleDown(t *testing.T) { testInterp(t, 100, 100, "down", "go-turns-two", "-280x360.jpeg") }
|
|
||||||
func TestScaleUp(t *testing.T) { testInterp(t, 75, 100, "up", "go-turns-two", "-14x18.png") }
|
|
||||||
func TestTformSrc(t *testing.T) { testInterp(t, 100, 100, "rotate", "go-turns-two", "-14x18.png") }
|
|
||||||
func TestTformOver(t *testing.T) { testInterp(t, 100, 100, "rotate", "tux", ".png") }
|
|
||||||
|
|
||||||
// TestSimpleTransforms tests Scale and Transform calls that simplify to Copy
|
|
||||||
// or Scale calls.
|
|
||||||
func TestSimpleTransforms(t *testing.T) {
|
|
||||||
f, err := os.Open("../testdata/testpattern.png") // A 100x100 image.
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Open: %v", err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
src, _, err := image.Decode(f)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Decode: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dst0 := image.NewRGBA(image.Rect(0, 0, 120, 150))
|
|
||||||
dst1 := image.NewRGBA(image.Rect(0, 0, 120, 150))
|
|
||||||
for _, op := range []string{"scale/copy", "tform/copy", "tform/scale"} {
|
|
||||||
for _, epsilon := range []float64{0, 1e-50, 1e-1} {
|
|
||||||
Copy(dst0, image.Point{}, image.Transparent, dst0.Bounds(), Src, nil)
|
|
||||||
Copy(dst1, image.Point{}, image.Transparent, dst1.Bounds(), Src, nil)
|
|
||||||
|
|
||||||
switch op {
|
|
||||||
case "scale/copy":
|
|
||||||
dr := image.Rect(10, 30, 10+100, 30+100)
|
|
||||||
if epsilon > 1e-10 {
|
|
||||||
dr.Max.X++
|
|
||||||
}
|
|
||||||
Copy(dst0, image.Point{10, 30}, src, src.Bounds(), Src, nil)
|
|
||||||
ApproxBiLinear.Scale(dst1, dr, src, src.Bounds(), Src, nil)
|
|
||||||
case "tform/copy":
|
|
||||||
Copy(dst0, image.Point{10, 30}, src, src.Bounds(), Src, nil)
|
|
||||||
ApproxBiLinear.Transform(dst1, f64.Aff3{
|
|
||||||
1, 0 + epsilon, 10,
|
|
||||||
0, 1, 30,
|
|
||||||
}, src, src.Bounds(), Src, nil)
|
|
||||||
case "tform/scale":
|
|
||||||
ApproxBiLinear.Scale(dst0, image.Rect(10, 50, 10+50, 50+50), src, src.Bounds(), Src, nil)
|
|
||||||
ApproxBiLinear.Transform(dst1, f64.Aff3{
|
|
||||||
0.5, 0.0 + epsilon, 10,
|
|
||||||
0.0, 0.5, 50,
|
|
||||||
}, src, src.Bounds(), Src, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
differ := !bytes.Equal(dst0.Pix, dst1.Pix)
|
|
||||||
if epsilon > 1e-10 {
|
|
||||||
if !differ {
|
|
||||||
t.Errorf("%s yielded same pixels, want different pixels: epsilon=%v", op, epsilon)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if differ {
|
|
||||||
t.Errorf("%s yielded different pixels, want same pixels: epsilon=%v", op, epsilon)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkSimpleScaleCopy(b *testing.B) {
|
|
||||||
dst := image.NewRGBA(image.Rect(0, 0, 640, 480))
|
|
||||||
src := image.NewRGBA(image.Rect(0, 0, 400, 300))
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
ApproxBiLinear.Scale(dst, image.Rect(10, 20, 10+400, 20+300), src, src.Bounds(), Src, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkSimpleTransformCopy(b *testing.B) {
|
|
||||||
dst := image.NewRGBA(image.Rect(0, 0, 640, 480))
|
|
||||||
src := image.NewRGBA(image.Rect(0, 0, 400, 300))
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
ApproxBiLinear.Transform(dst, f64.Aff3{
|
|
||||||
1, 0, 10,
|
|
||||||
0, 1, 20,
|
|
||||||
}, src, src.Bounds(), Src, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkSimpleTransformScale(b *testing.B) {
|
|
||||||
dst := image.NewRGBA(image.Rect(0, 0, 640, 480))
|
|
||||||
src := image.NewRGBA(image.Rect(0, 0, 400, 300))
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
ApproxBiLinear.Transform(dst, f64.Aff3{
|
|
||||||
0.5, 0.0, 10,
|
|
||||||
0.0, 0.5, 20,
|
|
||||||
}, src, src.Bounds(), Src, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOps(t *testing.T) {
|
|
||||||
blue := image.NewUniform(color.RGBA{0x00, 0x00, 0xff, 0xff})
|
|
||||||
testCases := map[Op]color.RGBA{
|
|
||||||
Over: color.RGBA{0x7f, 0x00, 0x80, 0xff},
|
|
||||||
Src: color.RGBA{0x7f, 0x00, 0x00, 0x7f},
|
|
||||||
}
|
|
||||||
for op, want := range testCases {
|
|
||||||
dst := image.NewRGBA(image.Rect(0, 0, 2, 2))
|
|
||||||
Copy(dst, image.Point{}, blue, dst.Bounds(), Src, nil)
|
|
||||||
|
|
||||||
src := image.NewRGBA(image.Rect(0, 0, 1, 1))
|
|
||||||
src.SetRGBA(0, 0, color.RGBA{0x7f, 0x00, 0x00, 0x7f})
|
|
||||||
|
|
||||||
NearestNeighbor.Scale(dst, dst.Bounds(), src, src.Bounds(), op, nil)
|
|
||||||
|
|
||||||
if got := dst.RGBAAt(0, 0); got != want {
|
|
||||||
t.Errorf("op=%v: got %v, want %v", op, got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestNegativeWeights tests that scaling by a kernel that produces negative
|
|
||||||
// weights, such as the Catmull-Rom kernel, doesn't produce an invalid color
|
|
||||||
// according to Go's alpha-premultiplied model.
|
|
||||||
func TestNegativeWeights(t *testing.T) {
|
|
||||||
check := func(m *image.RGBA) error {
|
|
||||||
b := m.Bounds()
|
|
||||||
for y := b.Min.Y; y < b.Max.Y; y++ {
|
|
||||||
for x := b.Min.X; x < b.Max.X; x++ {
|
|
||||||
if c := m.RGBAAt(x, y); c.R > c.A || c.G > c.A || c.B > c.A {
|
|
||||||
return fmt.Errorf("invalid color.RGBA at (%d, %d): %v", x, y, c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
src := image.NewRGBA(image.Rect(0, 0, 16, 16))
|
|
||||||
for y := 0; y < 16; y++ {
|
|
||||||
for x := 0; x < 16; x++ {
|
|
||||||
a := y * 0x11
|
|
||||||
src.Set(x, y, color.RGBA{
|
|
||||||
R: uint8(x * 0x11 * a / 0xff),
|
|
||||||
A: uint8(a),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := check(src); err != nil {
|
|
||||||
t.Fatalf("src image: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dst := image.NewRGBA(image.Rect(0, 0, 32, 32))
|
|
||||||
CatmullRom.Scale(dst, dst.Bounds(), src, src.Bounds(), Over, nil)
|
|
||||||
if err := check(dst); err != nil {
|
|
||||||
t.Fatalf("dst image: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func fillPix(r *rand.Rand, pixs ...[]byte) {
|
|
||||||
for _, pix := range pixs {
|
|
||||||
for i := range pix {
|
|
||||||
pix[i] = uint8(r.Intn(256))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInterpClipCommute(t *testing.T) {
|
|
||||||
src := image.NewNRGBA(image.Rect(0, 0, 20, 20))
|
|
||||||
fillPix(rand.New(rand.NewSource(0)), src.Pix)
|
|
||||||
|
|
||||||
outer := image.Rect(1, 1, 8, 5)
|
|
||||||
inner := image.Rect(2, 3, 6, 5)
|
|
||||||
qs := []Interpolator{
|
|
||||||
NearestNeighbor,
|
|
||||||
ApproxBiLinear,
|
|
||||||
CatmullRom,
|
|
||||||
}
|
|
||||||
for _, transform := range []bool{false, true} {
|
|
||||||
for _, q := range qs {
|
|
||||||
dst0 := image.NewRGBA(image.Rect(1, 1, 10, 10))
|
|
||||||
dst1 := image.NewRGBA(image.Rect(1, 1, 10, 10))
|
|
||||||
for i := range dst0.Pix {
|
|
||||||
dst0.Pix[i] = uint8(i / 4)
|
|
||||||
dst1.Pix[i] = uint8(i / 4)
|
|
||||||
}
|
|
||||||
|
|
||||||
var interp func(dst *image.RGBA)
|
|
||||||
if transform {
|
|
||||||
interp = func(dst *image.RGBA) {
|
|
||||||
q.Transform(dst, transformMatrix(3.75, 2, 1), src, src.Bounds(), Over, nil)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
interp = func(dst *image.RGBA) {
|
|
||||||
q.Scale(dst, outer, src, src.Bounds(), Over, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Interpolate then clip.
|
|
||||||
interp(dst0)
|
|
||||||
dst0 = dst0.SubImage(inner).(*image.RGBA)
|
|
||||||
|
|
||||||
// Clip then interpolate.
|
|
||||||
dst1 = dst1.SubImage(inner).(*image.RGBA)
|
|
||||||
interp(dst1)
|
|
||||||
|
|
||||||
loop:
|
|
||||||
for y := inner.Min.Y; y < inner.Max.Y; y++ {
|
|
||||||
for x := inner.Min.X; x < inner.Max.X; x++ {
|
|
||||||
if c0, c1 := dst0.RGBAAt(x, y), dst1.RGBAAt(x, y); c0 != c1 {
|
|
||||||
t.Errorf("q=%T: at (%d, %d): c0=%v, c1=%v", q, x, y, c0, c1)
|
|
||||||
break loop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// translatedImage is an image m translated by t.
|
|
||||||
type translatedImage struct {
|
|
||||||
m image.Image
|
|
||||||
t image.Point
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *translatedImage) At(x, y int) color.Color { return t.m.At(x-t.t.X, y-t.t.Y) }
|
|
||||||
func (t *translatedImage) Bounds() image.Rectangle { return t.m.Bounds().Add(t.t) }
|
|
||||||
func (t *translatedImage) ColorModel() color.Model { return t.m.ColorModel() }
|
|
||||||
|
|
||||||
// TestSrcTranslationInvariance tests that Scale and Transform are invariant
|
|
||||||
// under src translations. Specifically, when some source pixels are not in the
|
|
||||||
// bottom-right quadrant of src coordinate space, we consistently round down,
|
|
||||||
// not round towards zero.
|
|
||||||
func TestSrcTranslationInvariance(t *testing.T) {
|
|
||||||
f, err := os.Open("../testdata/testpattern.png")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Open: %v", err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
src, _, err := image.Decode(f)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Decode: %v", err)
|
|
||||||
}
|
|
||||||
sr := image.Rect(2, 3, 16, 12)
|
|
||||||
if !sr.In(src.Bounds()) {
|
|
||||||
t.Fatalf("src bounds too small: got %v", src.Bounds())
|
|
||||||
}
|
|
||||||
qs := []Interpolator{
|
|
||||||
NearestNeighbor,
|
|
||||||
ApproxBiLinear,
|
|
||||||
CatmullRom,
|
|
||||||
}
|
|
||||||
deltas := []image.Point{
|
|
||||||
{+0, +0},
|
|
||||||
{+0, +5},
|
|
||||||
{+0, -5},
|
|
||||||
{+5, +0},
|
|
||||||
{-5, +0},
|
|
||||||
{+8, +8},
|
|
||||||
{+8, -8},
|
|
||||||
{-8, +8},
|
|
||||||
{-8, -8},
|
|
||||||
}
|
|
||||||
m00 := transformMatrix(3.75, 0, 0)
|
|
||||||
|
|
||||||
for _, transform := range []bool{false, true} {
|
|
||||||
for _, q := range qs {
|
|
||||||
want := image.NewRGBA(image.Rect(0, 0, 20, 20))
|
|
||||||
if transform {
|
|
||||||
q.Transform(want, m00, src, sr, Over, nil)
|
|
||||||
} else {
|
|
||||||
q.Scale(want, want.Bounds(), src, sr, Over, nil)
|
|
||||||
}
|
|
||||||
for _, delta := range deltas {
|
|
||||||
tsrc := &translatedImage{src, delta}
|
|
||||||
got := image.NewRGBA(image.Rect(0, 0, 20, 20))
|
|
||||||
if transform {
|
|
||||||
m := matMul(&m00, &f64.Aff3{
|
|
||||||
1, 0, -float64(delta.X),
|
|
||||||
0, 1, -float64(delta.Y),
|
|
||||||
})
|
|
||||||
q.Transform(got, m, tsrc, sr.Add(delta), Over, nil)
|
|
||||||
} else {
|
|
||||||
q.Scale(got, got.Bounds(), tsrc, sr.Add(delta), Over, nil)
|
|
||||||
}
|
|
||||||
if !bytes.Equal(got.Pix, want.Pix) {
|
|
||||||
t.Errorf("pix differ for delta=%v, transform=%t, q=%T", delta, transform, q)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSrcMask(t *testing.T) {
|
|
||||||
srcMask := image.NewRGBA(image.Rect(0, 0, 23, 1))
|
|
||||||
srcMask.SetRGBA(19, 0, color.RGBA{0x00, 0x00, 0x00, 0x7f})
|
|
||||||
srcMask.SetRGBA(20, 0, color.RGBA{0x00, 0x00, 0x00, 0xff})
|
|
||||||
srcMask.SetRGBA(21, 0, color.RGBA{0x00, 0x00, 0x00, 0x3f})
|
|
||||||
srcMask.SetRGBA(22, 0, color.RGBA{0x00, 0x00, 0x00, 0x00})
|
|
||||||
red := image.NewUniform(color.RGBA{0xff, 0x00, 0x00, 0xff})
|
|
||||||
blue := image.NewUniform(color.RGBA{0x00, 0x00, 0xff, 0xff})
|
|
||||||
dst := image.NewRGBA(image.Rect(0, 0, 6, 1))
|
|
||||||
Copy(dst, image.Point{}, blue, dst.Bounds(), Src, nil)
|
|
||||||
NearestNeighbor.Scale(dst, dst.Bounds(), red, image.Rect(0, 0, 3, 1), Over, &Options{
|
|
||||||
SrcMask: srcMask,
|
|
||||||
SrcMaskP: image.Point{20, 0},
|
|
||||||
})
|
|
||||||
got := [6]color.RGBA{
|
|
||||||
dst.RGBAAt(0, 0),
|
|
||||||
dst.RGBAAt(1, 0),
|
|
||||||
dst.RGBAAt(2, 0),
|
|
||||||
dst.RGBAAt(3, 0),
|
|
||||||
dst.RGBAAt(4, 0),
|
|
||||||
dst.RGBAAt(5, 0),
|
|
||||||
}
|
|
||||||
want := [6]color.RGBA{
|
|
||||||
{0xff, 0x00, 0x00, 0xff},
|
|
||||||
{0xff, 0x00, 0x00, 0xff},
|
|
||||||
{0x3f, 0x00, 0xc0, 0xff},
|
|
||||||
{0x3f, 0x00, 0xc0, 0xff},
|
|
||||||
{0x00, 0x00, 0xff, 0xff},
|
|
||||||
{0x00, 0x00, 0xff, 0xff},
|
|
||||||
}
|
|
||||||
if got != want {
|
|
||||||
t.Errorf("\ngot %v\nwant %v", got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDstMask(t *testing.T) {
|
|
||||||
dstMask := image.NewRGBA(image.Rect(0, 0, 23, 1))
|
|
||||||
dstMask.SetRGBA(19, 0, color.RGBA{0x00, 0x00, 0x00, 0x7f})
|
|
||||||
dstMask.SetRGBA(20, 0, color.RGBA{0x00, 0x00, 0x00, 0xff})
|
|
||||||
dstMask.SetRGBA(21, 0, color.RGBA{0x00, 0x00, 0x00, 0x3f})
|
|
||||||
dstMask.SetRGBA(22, 0, color.RGBA{0x00, 0x00, 0x00, 0x00})
|
|
||||||
red := image.NewRGBA(image.Rect(0, 0, 1, 1))
|
|
||||||
red.SetRGBA(0, 0, color.RGBA{0xff, 0x00, 0x00, 0xff})
|
|
||||||
blue := image.NewUniform(color.RGBA{0x00, 0x00, 0xff, 0xff})
|
|
||||||
qs := []Interpolator{
|
|
||||||
NearestNeighbor,
|
|
||||||
ApproxBiLinear,
|
|
||||||
CatmullRom,
|
|
||||||
}
|
|
||||||
for _, q := range qs {
|
|
||||||
dst := image.NewRGBA(image.Rect(0, 0, 3, 1))
|
|
||||||
Copy(dst, image.Point{}, blue, dst.Bounds(), Src, nil)
|
|
||||||
q.Scale(dst, dst.Bounds(), red, red.Bounds(), Over, &Options{
|
|
||||||
DstMask: dstMask,
|
|
||||||
DstMaskP: image.Point{20, 0},
|
|
||||||
})
|
|
||||||
got := [3]color.RGBA{
|
|
||||||
dst.RGBAAt(0, 0),
|
|
||||||
dst.RGBAAt(1, 0),
|
|
||||||
dst.RGBAAt(2, 0),
|
|
||||||
}
|
|
||||||
want := [3]color.RGBA{
|
|
||||||
{0xff, 0x00, 0x00, 0xff},
|
|
||||||
{0x3f, 0x00, 0xc0, 0xff},
|
|
||||||
{0x00, 0x00, 0xff, 0xff},
|
|
||||||
}
|
|
||||||
if got != want {
|
|
||||||
t.Errorf("q=%T:\ngot %v\nwant %v", q, got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRectDstMask(t *testing.T) {
|
|
||||||
f, err := os.Open("../testdata/testpattern.png")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Open: %v", err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
src, _, err := image.Decode(f)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Decode: %v", err)
|
|
||||||
}
|
|
||||||
m00 := transformMatrix(1, 0, 0)
|
|
||||||
|
|
||||||
bounds := image.Rect(0, 0, 50, 50)
|
|
||||||
dstOutside := image.NewRGBA(bounds)
|
|
||||||
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
|
|
||||||
for x := bounds.Min.X; x < bounds.Max.X; x++ {
|
|
||||||
dstOutside.SetRGBA(x, y, color.RGBA{uint8(5 * x), uint8(5 * y), 0x00, 0xff})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mk := func(q Transformer, dstMask image.Image, dstMaskP image.Point) *image.RGBA {
|
|
||||||
m := image.NewRGBA(bounds)
|
|
||||||
Copy(m, bounds.Min, dstOutside, bounds, Src, nil)
|
|
||||||
q.Transform(m, m00, src, src.Bounds(), Over, &Options{
|
|
||||||
DstMask: dstMask,
|
|
||||||
DstMaskP: dstMaskP,
|
|
||||||
})
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
qs := []Interpolator{
|
|
||||||
NearestNeighbor,
|
|
||||||
ApproxBiLinear,
|
|
||||||
CatmullRom,
|
|
||||||
}
|
|
||||||
dstMaskPs := []image.Point{
|
|
||||||
{0, 0},
|
|
||||||
{5, 7},
|
|
||||||
{-3, 0},
|
|
||||||
}
|
|
||||||
rect := image.Rect(10, 10, 30, 40)
|
|
||||||
for _, q := range qs {
|
|
||||||
for _, dstMaskP := range dstMaskPs {
|
|
||||||
dstInside := mk(q, nil, image.Point{})
|
|
||||||
for _, wrap := range []bool{false, true} {
|
|
||||||
// TODO: replace "rectImage(rect)" with "rect" once Go 1.5 is
|
|
||||||
// released, where an image.Rectangle implements image.Image.
|
|
||||||
dstMask := image.Image(rectImage(rect))
|
|
||||||
if wrap {
|
|
||||||
dstMask = srcWrapper{dstMask}
|
|
||||||
}
|
|
||||||
dst := mk(q, dstMask, dstMaskP)
|
|
||||||
|
|
||||||
nError := 0
|
|
||||||
loop:
|
|
||||||
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
|
|
||||||
for x := bounds.Min.X; x < bounds.Max.X; x++ {
|
|
||||||
which := dstOutside
|
|
||||||
if (image.Point{x, y}).Add(dstMaskP).In(rect) {
|
|
||||||
which = dstInside
|
|
||||||
}
|
|
||||||
if got, want := dst.RGBAAt(x, y), which.RGBAAt(x, y); got != want {
|
|
||||||
if nError == 10 {
|
|
||||||
t.Errorf("q=%T dmp=%v wrap=%v: ...and more errors", q, dstMaskP, wrap)
|
|
||||||
break loop
|
|
||||||
}
|
|
||||||
nError++
|
|
||||||
t.Errorf("q=%T dmp=%v wrap=%v: x=%3d y=%3d: got %v, want %v",
|
|
||||||
q, dstMaskP, wrap, x, y, got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: delete this wrapper type once Go 1.5 is released, where an
|
|
||||||
// image.Rectangle implements image.Image.
|
|
||||||
type rectImage image.Rectangle
|
|
||||||
|
|
||||||
func (r rectImage) ColorModel() color.Model { return color.Alpha16Model }
|
|
||||||
func (r rectImage) Bounds() image.Rectangle { return image.Rectangle(r) }
|
|
||||||
func (r rectImage) At(x, y int) color.Color {
|
|
||||||
if (image.Point{x, y}).In(image.Rectangle(r)) {
|
|
||||||
return color.Opaque
|
|
||||||
}
|
|
||||||
return color.Transparent
|
|
||||||
}
|
|
||||||
|
|
||||||
// The fooWrapper types wrap the dst or src image to avoid triggering the
|
|
||||||
// type-specific fast path implementations.
|
|
||||||
type (
|
|
||||||
dstWrapper struct{ Image }
|
|
||||||
srcWrapper struct{ image.Image }
|
|
||||||
)
|
|
||||||
|
|
||||||
func srcGray(boundsHint image.Rectangle) (image.Image, error) {
|
|
||||||
m := image.NewGray(boundsHint)
|
|
||||||
fillPix(rand.New(rand.NewSource(0)), m.Pix)
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func srcNRGBA(boundsHint image.Rectangle) (image.Image, error) {
|
|
||||||
m := image.NewNRGBA(boundsHint)
|
|
||||||
fillPix(rand.New(rand.NewSource(1)), m.Pix)
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func srcRGBA(boundsHint image.Rectangle) (image.Image, error) {
|
|
||||||
m := image.NewRGBA(boundsHint)
|
|
||||||
fillPix(rand.New(rand.NewSource(2)), m.Pix)
|
|
||||||
// RGBA is alpha-premultiplied, so the R, G and B values should
|
|
||||||
// be <= the A values.
|
|
||||||
for i := 0; i < len(m.Pix); i += 4 {
|
|
||||||
m.Pix[i+0] = uint8(uint32(m.Pix[i+0]) * uint32(m.Pix[i+3]) / 0xff)
|
|
||||||
m.Pix[i+1] = uint8(uint32(m.Pix[i+1]) * uint32(m.Pix[i+3]) / 0xff)
|
|
||||||
m.Pix[i+2] = uint8(uint32(m.Pix[i+2]) * uint32(m.Pix[i+3]) / 0xff)
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func srcUnif(boundsHint image.Rectangle) (image.Image, error) {
|
|
||||||
return image.NewUniform(color.RGBA64{0x1234, 0x5555, 0x9181, 0xbeef}), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func srcYCbCr(boundsHint image.Rectangle) (image.Image, error) {
|
|
||||||
m := image.NewYCbCr(boundsHint, image.YCbCrSubsampleRatio420)
|
|
||||||
fillPix(rand.New(rand.NewSource(3)), m.Y, m.Cb, m.Cr)
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func srcLarge(boundsHint image.Rectangle) (image.Image, error) {
|
|
||||||
// 3072 x 2304 is over 7 million pixels at 4:3, comparable to a
|
|
||||||
// 2015 smart-phone camera's output.
|
|
||||||
return srcYCbCr(image.Rect(0, 0, 3072, 2304))
|
|
||||||
}
|
|
||||||
|
|
||||||
func srcTux(boundsHint image.Rectangle) (image.Image, error) {
|
|
||||||
// tux.png is a 386 x 395 image.
|
|
||||||
f, err := os.Open("../testdata/tux.png")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Open: %v", err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
src, err := png.Decode(f)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Decode: %v", err)
|
|
||||||
}
|
|
||||||
return src, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func benchScale(b *testing.B, w int, h int, op Op, srcf func(image.Rectangle) (image.Image, error), q Interpolator) {
|
|
||||||
dst := image.NewRGBA(image.Rect(0, 0, w, h))
|
|
||||||
src, err := srcf(image.Rect(0, 0, 1024, 768))
|
|
||||||
if err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
dr, sr := dst.Bounds(), src.Bounds()
|
|
||||||
scaler := Scaler(q)
|
|
||||||
if n, ok := q.(interface {
|
|
||||||
NewScaler(int, int, int, int) Scaler
|
|
||||||
}); ok {
|
|
||||||
scaler = n.NewScaler(dr.Dx(), dr.Dy(), sr.Dx(), sr.Dy())
|
|
||||||
}
|
|
||||||
|
|
||||||
b.ReportAllocs()
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
scaler.Scale(dst, dr, src, sr, op, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func benchTform(b *testing.B, w int, h int, op Op, srcf func(image.Rectangle) (image.Image, error), q Interpolator) {
|
|
||||||
dst := image.NewRGBA(image.Rect(0, 0, w, h))
|
|
||||||
src, err := srcf(image.Rect(0, 0, 1024, 768))
|
|
||||||
if err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
sr := src.Bounds()
|
|
||||||
m := transformMatrix(3.75, 40, 10)
|
|
||||||
|
|
||||||
b.ReportAllocs()
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
q.Transform(dst, m, src, sr, op, nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkScaleNNLargeDown(b *testing.B) { benchScale(b, 200, 150, Src, srcLarge, NearestNeighbor) }
|
|
||||||
func BenchmarkScaleABLargeDown(b *testing.B) { benchScale(b, 200, 150, Src, srcLarge, ApproxBiLinear) }
|
|
||||||
func BenchmarkScaleBLLargeDown(b *testing.B) { benchScale(b, 200, 150, Src, srcLarge, BiLinear) }
|
|
||||||
func BenchmarkScaleCRLargeDown(b *testing.B) { benchScale(b, 200, 150, Src, srcLarge, CatmullRom) }
|
|
||||||
|
|
||||||
func BenchmarkScaleNNDown(b *testing.B) { benchScale(b, 120, 80, Src, srcTux, NearestNeighbor) }
|
|
||||||
func BenchmarkScaleABDown(b *testing.B) { benchScale(b, 120, 80, Src, srcTux, ApproxBiLinear) }
|
|
||||||
func BenchmarkScaleBLDown(b *testing.B) { benchScale(b, 120, 80, Src, srcTux, BiLinear) }
|
|
||||||
func BenchmarkScaleCRDown(b *testing.B) { benchScale(b, 120, 80, Src, srcTux, CatmullRom) }
|
|
||||||
|
|
||||||
func BenchmarkScaleNNUp(b *testing.B) { benchScale(b, 800, 600, Src, srcTux, NearestNeighbor) }
|
|
||||||
func BenchmarkScaleABUp(b *testing.B) { benchScale(b, 800, 600, Src, srcTux, ApproxBiLinear) }
|
|
||||||
func BenchmarkScaleBLUp(b *testing.B) { benchScale(b, 800, 600, Src, srcTux, BiLinear) }
|
|
||||||
func BenchmarkScaleCRUp(b *testing.B) { benchScale(b, 800, 600, Src, srcTux, CatmullRom) }
|
|
||||||
|
|
||||||
func BenchmarkScaleNNSrcRGBA(b *testing.B) { benchScale(b, 200, 150, Src, srcRGBA, NearestNeighbor) }
|
|
||||||
func BenchmarkScaleNNSrcUnif(b *testing.B) { benchScale(b, 200, 150, Src, srcUnif, NearestNeighbor) }
|
|
||||||
|
|
||||||
func BenchmarkScaleNNOverRGBA(b *testing.B) { benchScale(b, 200, 150, Over, srcRGBA, NearestNeighbor) }
|
|
||||||
func BenchmarkScaleNNOverUnif(b *testing.B) { benchScale(b, 200, 150, Over, srcUnif, NearestNeighbor) }
|
|
||||||
|
|
||||||
func BenchmarkTformNNSrcRGBA(b *testing.B) { benchTform(b, 200, 150, Src, srcRGBA, NearestNeighbor) }
|
|
||||||
func BenchmarkTformNNSrcUnif(b *testing.B) { benchTform(b, 200, 150, Src, srcUnif, NearestNeighbor) }
|
|
||||||
|
|
||||||
func BenchmarkTformNNOverRGBA(b *testing.B) { benchTform(b, 200, 150, Over, srcRGBA, NearestNeighbor) }
|
|
||||||
func BenchmarkTformNNOverUnif(b *testing.B) { benchTform(b, 200, 150, Over, srcUnif, NearestNeighbor) }
|
|
||||||
|
|
||||||
func BenchmarkScaleABSrcGray(b *testing.B) { benchScale(b, 200, 150, Src, srcGray, ApproxBiLinear) }
|
|
||||||
func BenchmarkScaleABSrcNRGBA(b *testing.B) { benchScale(b, 200, 150, Src, srcNRGBA, ApproxBiLinear) }
|
|
||||||
func BenchmarkScaleABSrcRGBA(b *testing.B) { benchScale(b, 200, 150, Src, srcRGBA, ApproxBiLinear) }
|
|
||||||
func BenchmarkScaleABSrcYCbCr(b *testing.B) { benchScale(b, 200, 150, Src, srcYCbCr, ApproxBiLinear) }
|
|
||||||
|
|
||||||
func BenchmarkScaleABOverGray(b *testing.B) { benchScale(b, 200, 150, Over, srcGray, ApproxBiLinear) }
|
|
||||||
func BenchmarkScaleABOverNRGBA(b *testing.B) { benchScale(b, 200, 150, Over, srcNRGBA, ApproxBiLinear) }
|
|
||||||
func BenchmarkScaleABOverRGBA(b *testing.B) { benchScale(b, 200, 150, Over, srcRGBA, ApproxBiLinear) }
|
|
||||||
func BenchmarkScaleABOverYCbCr(b *testing.B) { benchScale(b, 200, 150, Over, srcYCbCr, ApproxBiLinear) }
|
|
||||||
|
|
||||||
func BenchmarkTformABSrcGray(b *testing.B) { benchTform(b, 200, 150, Src, srcGray, ApproxBiLinear) }
|
|
||||||
func BenchmarkTformABSrcNRGBA(b *testing.B) { benchTform(b, 200, 150, Src, srcNRGBA, ApproxBiLinear) }
|
|
||||||
func BenchmarkTformABSrcRGBA(b *testing.B) { benchTform(b, 200, 150, Src, srcRGBA, ApproxBiLinear) }
|
|
||||||
func BenchmarkTformABSrcYCbCr(b *testing.B) { benchTform(b, 200, 150, Src, srcYCbCr, ApproxBiLinear) }
|
|
||||||
|
|
||||||
func BenchmarkTformABOverGray(b *testing.B) { benchTform(b, 200, 150, Over, srcGray, ApproxBiLinear) }
|
|
||||||
func BenchmarkTformABOverNRGBA(b *testing.B) { benchTform(b, 200, 150, Over, srcNRGBA, ApproxBiLinear) }
|
|
||||||
func BenchmarkTformABOverRGBA(b *testing.B) { benchTform(b, 200, 150, Over, srcRGBA, ApproxBiLinear) }
|
|
||||||
func BenchmarkTformABOverYCbCr(b *testing.B) { benchTform(b, 200, 150, Over, srcYCbCr, ApproxBiLinear) }
|
|
||||||
|
|
||||||
func BenchmarkScaleCRSrcGray(b *testing.B) { benchScale(b, 200, 150, Src, srcGray, CatmullRom) }
|
|
||||||
func BenchmarkScaleCRSrcNRGBA(b *testing.B) { benchScale(b, 200, 150, Src, srcNRGBA, CatmullRom) }
|
|
||||||
func BenchmarkScaleCRSrcRGBA(b *testing.B) { benchScale(b, 200, 150, Src, srcRGBA, CatmullRom) }
|
|
||||||
func BenchmarkScaleCRSrcYCbCr(b *testing.B) { benchScale(b, 200, 150, Src, srcYCbCr, CatmullRom) }
|
|
||||||
|
|
||||||
func BenchmarkScaleCROverGray(b *testing.B) { benchScale(b, 200, 150, Over, srcGray, CatmullRom) }
|
|
||||||
func BenchmarkScaleCROverNRGBA(b *testing.B) { benchScale(b, 200, 150, Over, srcNRGBA, CatmullRom) }
|
|
||||||
func BenchmarkScaleCROverRGBA(b *testing.B) { benchScale(b, 200, 150, Over, srcRGBA, CatmullRom) }
|
|
||||||
func BenchmarkScaleCROverYCbCr(b *testing.B) { benchScale(b, 200, 150, Over, srcYCbCr, CatmullRom) }
|
|
||||||
|
|
||||||
func BenchmarkTformCRSrcGray(b *testing.B) { benchTform(b, 200, 150, Src, srcGray, CatmullRom) }
|
|
||||||
func BenchmarkTformCRSrcNRGBA(b *testing.B) { benchTform(b, 200, 150, Src, srcNRGBA, CatmullRom) }
|
|
||||||
func BenchmarkTformCRSrcRGBA(b *testing.B) { benchTform(b, 200, 150, Src, srcRGBA, CatmullRom) }
|
|
||||||
func BenchmarkTformCRSrcYCbCr(b *testing.B) { benchTform(b, 200, 150, Src, srcYCbCr, CatmullRom) }
|
|
||||||
|
|
||||||
func BenchmarkTformCROverGray(b *testing.B) { benchTform(b, 200, 150, Over, srcGray, CatmullRom) }
|
|
||||||
func BenchmarkTformCROverNRGBA(b *testing.B) { benchTform(b, 200, 150, Over, srcNRGBA, CatmullRom) }
|
|
||||||
func BenchmarkTformCROverRGBA(b *testing.B) { benchTform(b, 200, 150, Over, srcRGBA, CatmullRom) }
|
|
||||||
func BenchmarkTformCROverYCbCr(b *testing.B) { benchTform(b, 200, 150, Over, srcYCbCr, CatmullRom) }
|
|
|
@ -1,96 +0,0 @@
|
||||||
// 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 go1.5
|
|
||||||
|
|
||||||
package draw
|
|
||||||
|
|
||||||
// This file contains tests that depend on the exact behavior of the
|
|
||||||
// image/color package in the standard library. The color conversion formula
|
|
||||||
// from YCbCr to RGBA changed between Go 1.4 and Go 1.5, so this file's tests
|
|
||||||
// are only enabled for Go 1.5 and above.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"image"
|
|
||||||
"image/color"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestFastPaths tests that the fast path implementations produce identical
|
|
||||||
// results to the generic implementation.
|
|
||||||
func TestFastPaths(t *testing.T) {
|
|
||||||
drs := []image.Rectangle{
|
|
||||||
image.Rect(0, 0, 10, 10), // The dst bounds.
|
|
||||||
image.Rect(3, 4, 8, 6), // A strict subset of the dst bounds.
|
|
||||||
image.Rect(-3, -5, 2, 4), // Partial out-of-bounds #0.
|
|
||||||
image.Rect(4, -2, 6, 12), // Partial out-of-bounds #1.
|
|
||||||
image.Rect(12, 14, 23, 45), // Complete out-of-bounds.
|
|
||||||
image.Rect(5, 5, 5, 5), // Empty.
|
|
||||||
}
|
|
||||||
srs := []image.Rectangle{
|
|
||||||
image.Rect(0, 0, 12, 9), // The src bounds.
|
|
||||||
image.Rect(2, 2, 10, 8), // A strict subset of the src bounds.
|
|
||||||
image.Rect(10, 5, 20, 20), // Partial out-of-bounds #0.
|
|
||||||
image.Rect(-40, 0, 40, 8), // Partial out-of-bounds #1.
|
|
||||||
image.Rect(-8, -8, -4, -4), // Complete out-of-bounds.
|
|
||||||
image.Rect(5, 5, 5, 5), // Empty.
|
|
||||||
}
|
|
||||||
srcfs := []func(image.Rectangle) (image.Image, error){
|
|
||||||
srcGray,
|
|
||||||
srcNRGBA,
|
|
||||||
srcRGBA,
|
|
||||||
srcUnif,
|
|
||||||
srcYCbCr,
|
|
||||||
}
|
|
||||||
var srcs []image.Image
|
|
||||||
for _, srcf := range srcfs {
|
|
||||||
src, err := srcf(srs[0])
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
srcs = append(srcs, src)
|
|
||||||
}
|
|
||||||
qs := []Interpolator{
|
|
||||||
NearestNeighbor,
|
|
||||||
ApproxBiLinear,
|
|
||||||
CatmullRom,
|
|
||||||
}
|
|
||||||
ops := []Op{
|
|
||||||
Over,
|
|
||||||
Src,
|
|
||||||
}
|
|
||||||
blue := image.NewUniform(color.RGBA{0x11, 0x22, 0x44, 0x7f})
|
|
||||||
|
|
||||||
for _, dr := range drs {
|
|
||||||
for _, src := range srcs {
|
|
||||||
for _, sr := range srs {
|
|
||||||
for _, transform := range []bool{false, true} {
|
|
||||||
for _, q := range qs {
|
|
||||||
for _, op := range ops {
|
|
||||||
dst0 := image.NewRGBA(drs[0])
|
|
||||||
dst1 := image.NewRGBA(drs[0])
|
|
||||||
Draw(dst0, dst0.Bounds(), blue, image.Point{}, Src)
|
|
||||||
Draw(dstWrapper{dst1}, dst1.Bounds(), srcWrapper{blue}, image.Point{}, Src)
|
|
||||||
|
|
||||||
if transform {
|
|
||||||
m := transformMatrix(3.75, 2, 1)
|
|
||||||
q.Transform(dst0, m, src, sr, op, nil)
|
|
||||||
q.Transform(dstWrapper{dst1}, m, srcWrapper{src}, sr, op, nil)
|
|
||||||
} else {
|
|
||||||
q.Scale(dst0, dr, src, sr, op, nil)
|
|
||||||
q.Scale(dstWrapper{dst1}, dr, srcWrapper{src}, sr, op, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bytes.Equal(dst0.Pix, dst1.Pix) {
|
|
||||||
t.Errorf("pix differ for dr=%v, src=%T, sr=%v, transform=%t, q=%T",
|
|
||||||
dr, src, sr, transform, q)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,106 +0,0 @@
|
||||||
// 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 example
|
|
||||||
//
|
|
||||||
// This build tag means that "go install golang.org/x/image/..." doesn't
|
|
||||||
// install this example program. Use "go run main.go" to run it or "go install
|
|
||||||
// -tags=example" to install 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/image/font"
|
|
||||||
"golang.org/x/image/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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,126 +0,0 @@
|
||||||
// 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.
|
|
||||||
|
|
||||||
//go:generate go run gen.go
|
|
||||||
|
|
||||||
// Package basicfont provides fixed-size font faces.
|
|
||||||
package basicfont // import "golang.org/x/image/font/basicfont"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"image"
|
|
||||||
|
|
||||||
"golang.org/x/image/font"
|
|
||||||
"golang.org/x/image/math/fixed"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Range maps a contiguous range of runes to vertically adjacent sub-images of
|
|
||||||
// a Face's Mask image. The rune range is inclusive on the low end and
|
|
||||||
// exclusive on the high end.
|
|
||||||
//
|
|
||||||
// If Low <= r && r < High, then the rune r is mapped to the sub-image of
|
|
||||||
// Face.Mask whose bounds are image.Rect(0, y*h, Face.Width, (y+1)*h),
|
|
||||||
// where y = (int(r-Low) + Offset) and h = (Face.Ascent + Face.Descent).
|
|
||||||
type Range struct {
|
|
||||||
Low, High rune
|
|
||||||
Offset int
|
|
||||||
}
|
|
||||||
|
|
||||||
// Face7x13 is a Face derived from the public domain X11 misc-fixed font files.
|
|
||||||
//
|
|
||||||
// At the moment, it holds the printable characters in ASCII starting with
|
|
||||||
// space, and the Unicode replacement character U+FFFD.
|
|
||||||
//
|
|
||||||
// Its data is entirely self-contained and does not require loading from
|
|
||||||
// separate files.
|
|
||||||
var Face7x13 = &Face{
|
|
||||||
Advance: 7,
|
|
||||||
Width: 6,
|
|
||||||
Height: 13,
|
|
||||||
Ascent: 11,
|
|
||||||
Descent: 2,
|
|
||||||
Mask: mask7x13,
|
|
||||||
Ranges: []Range{
|
|
||||||
{'\u0020', '\u007f', 0},
|
|
||||||
{'\ufffd', '\ufffe', 95},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Face is a basic font face whose glyphs all have the same metrics.
|
|
||||||
//
|
|
||||||
// It is safe to use concurrently.
|
|
||||||
type Face struct {
|
|
||||||
// Advance is the glyph advance, in pixels.
|
|
||||||
Advance int
|
|
||||||
// Width is the glyph width, in pixels.
|
|
||||||
Width int
|
|
||||||
// Height is the inter-line height, in pixels.
|
|
||||||
Height int
|
|
||||||
// Ascent is the glyph ascent, in pixels.
|
|
||||||
Ascent int
|
|
||||||
// Descent is the glyph descent, in pixels.
|
|
||||||
Descent int
|
|
||||||
// Left is the left side bearing, in pixels. A positive value means that
|
|
||||||
// all of a glyph is to the right of the dot.
|
|
||||||
Left int
|
|
||||||
|
|
||||||
// Mask contains all of the glyph masks. Its width is typically the Face's
|
|
||||||
// Width, and its height a multiple of the Face's Height.
|
|
||||||
Mask image.Image
|
|
||||||
// Ranges map runes to sub-images of Mask. The rune ranges must not
|
|
||||||
// overlap, and must be in increasing rune order.
|
|
||||||
Ranges []Range
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Face) Close() error { return nil }
|
|
||||||
func (f *Face) Kern(r0, r1 rune) fixed.Int26_6 { return 0 }
|
|
||||||
|
|
||||||
func (f *Face) Metrics() font.Metrics {
|
|
||||||
return font.Metrics{
|
|
||||||
Height: fixed.I(f.Height),
|
|
||||||
Ascent: fixed.I(f.Ascent),
|
|
||||||
Descent: fixed.I(f.Descent),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
|
|
||||||
loop:
|
|
||||||
for _, rr := range [2]rune{r, '\ufffd'} {
|
|
||||||
for _, rng := range f.Ranges {
|
|
||||||
if rr < rng.Low || rng.High <= rr {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
maskp.Y = (int(rr-rng.Low) + rng.Offset) * (f.Ascent + f.Descent)
|
|
||||||
ok = true
|
|
||||||
break loop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
return image.Rectangle{}, nil, image.Point{}, 0, false
|
|
||||||
}
|
|
||||||
|
|
||||||
x := int(dot.X+32)>>6 + f.Left
|
|
||||||
y := int(dot.Y+32) >> 6
|
|
||||||
dr = image.Rectangle{
|
|
||||||
Min: image.Point{
|
|
||||||
X: x,
|
|
||||||
Y: y - f.Ascent,
|
|
||||||
},
|
|
||||||
Max: image.Point{
|
|
||||||
X: x + f.Width,
|
|
||||||
Y: y + f.Descent,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return dr, f.Mask, maskp, fixed.I(f.Advance), true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Face) GlyphBounds(r rune) (bounds fixed.Rectangle26_6, advance fixed.Int26_6, ok bool) {
|
|
||||||
return fixed.R(0, -f.Ascent, f.Width, +f.Descent), fixed.I(f.Advance), true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Face) GlyphAdvance(r rune) (advance fixed.Int26_6, ok bool) {
|
|
||||||
return fixed.I(f.Advance), true
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,115 +0,0 @@
|
||||||
// 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 program generates data.go.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"go/format"
|
|
||||||
"image"
|
|
||||||
"image/draw"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"path"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"golang.org/x/image/font"
|
|
||||||
"golang.org/x/image/font/plan9font"
|
|
||||||
"golang.org/x/image/math/fixed"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// nGlyphs is the number of glyphs to generate: 95 characters in the range
|
|
||||||
// [0x20, 0x7e], plus the replacement character.
|
|
||||||
const nGlyphs = 95 + 1
|
|
||||||
// The particular font (unicode.7x13.font) leaves the right-most column
|
|
||||||
// empty in its ASCII glyphs. We don't have to include that column in the
|
|
||||||
// generated glyphs, so we subtract one off the effective width.
|
|
||||||
const width, height, ascent = 7 - 1, 13, 11
|
|
||||||
|
|
||||||
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.Fatalf("readFile: %v", err)
|
|
||||||
}
|
|
||||||
face, err := plan9font.ParseFont(fontData, readFile)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("plan9font.ParseFont: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dst := image.NewRGBA(image.Rect(0, 0, width, nGlyphs*height))
|
|
||||||
draw.Draw(dst, dst.Bounds(), image.Black, image.Point{}, draw.Src)
|
|
||||||
d := &font.Drawer{
|
|
||||||
Dst: dst,
|
|
||||||
Src: image.White,
|
|
||||||
Face: face,
|
|
||||||
}
|
|
||||||
for i := 0; i < nGlyphs; i++ {
|
|
||||||
r := '\ufffd'
|
|
||||||
if i < nGlyphs-1 {
|
|
||||||
r = 0x20 + rune(i)
|
|
||||||
}
|
|
||||||
d.Dot = fixed.P(0, height*i+ascent)
|
|
||||||
d.DrawString(string(r))
|
|
||||||
}
|
|
||||||
|
|
||||||
w := bytes.NewBuffer(nil)
|
|
||||||
w.WriteString(preamble)
|
|
||||||
fmt.Fprintf(w, "// mask7x13 contains %d %d×%d glyphs in %d Pix bytes.\n", nGlyphs, width, height, nGlyphs*width*height)
|
|
||||||
fmt.Fprintf(w, "var mask7x13 = &image.Alpha{\n")
|
|
||||||
fmt.Fprintf(w, " Stride: %d,\n", width)
|
|
||||||
fmt.Fprintf(w, " Rect: image.Rectangle{Max: image.Point{%d, %d*%d}},\n", width, nGlyphs, height)
|
|
||||||
fmt.Fprintf(w, " Pix: []byte{\n")
|
|
||||||
b := dst.Bounds()
|
|
||||||
for y := b.Min.Y; y < b.Max.Y; y++ {
|
|
||||||
if y%height == 0 {
|
|
||||||
if y != 0 {
|
|
||||||
w.WriteByte('\n')
|
|
||||||
}
|
|
||||||
i := y / height
|
|
||||||
if i < nGlyphs-1 {
|
|
||||||
i += 0x20
|
|
||||||
fmt.Fprintf(w, "// %#2x %q\n", i, rune(i))
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(w, "// U+FFFD REPLACEMENT CHARACTER\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for x := b.Min.X; x < b.Max.X; x++ {
|
|
||||||
if dst.RGBAAt(x, y).R > 0 {
|
|
||||||
w.WriteString("0xff,")
|
|
||||||
} else {
|
|
||||||
w.WriteString("0x00,")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
w.WriteByte('\n')
|
|
||||||
}
|
|
||||||
w.WriteString("},\n}\n")
|
|
||||||
|
|
||||||
fmted, err := format.Source(w.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("format.Source: %v", err)
|
|
||||||
}
|
|
||||||
if err := ioutil.WriteFile("data.go", fmted, 0644); err != nil {
|
|
||||||
log.Fatalf("ioutil.WriteFile: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const preamble = `// generated by go generate; DO NOT EDIT.
|
|
||||||
|
|
||||||
package basicfont
|
|
||||||
|
|
||||||
// This data is derived from files in the font/fixed directory of the Plan 9
|
|
||||||
// Port source code (https://github.com/9fans/plan9port) which were originally
|
|
||||||
// based on the public domain X11 misc-fixed font files.
|
|
||||||
|
|
||||||
import "image"
|
|
||||||
|
|
||||||
`
|
|
|
@ -1,359 +0,0 @@
|
||||||
// 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 // import "golang.org/x/image/font"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"image"
|
|
||||||
"image/draw"
|
|
||||||
"io"
|
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
"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
|
|
||||||
|
|
||||||
// Metrics returns the metrics for this Face.
|
|
||||||
Metrics() Metrics
|
|
||||||
|
|
||||||
// TODO: ColoredGlyph for various emoji?
|
|
||||||
// TODO: Ligatures? Shaping?
|
|
||||||
}
|
|
||||||
|
|
||||||
// Metrics holds the metrics for a Face. A visual depiction is at
|
|
||||||
// https://developer.apple.com/library/mac/documentation/TextFonts/Conceptual/CocoaTextArchitecture/Art/glyph_metrics_2x.png
|
|
||||||
type Metrics struct {
|
|
||||||
// Height is the recommended amount of vertical space between two lines of
|
|
||||||
// text.
|
|
||||||
Height fixed.Int26_6
|
|
||||||
|
|
||||||
// Ascent is the distance from the top of a line to its baseline.
|
|
||||||
Ascent fixed.Int26_6
|
|
||||||
|
|
||||||
// Descent is the distance from the bottom of a line to its baseline. The
|
|
||||||
// value is typically positive, even though a descender goes below the
|
|
||||||
// baseline.
|
|
||||||
Descent fixed.Int26_6
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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? What if ligatures span more than two runes? What if grapheme
|
|
||||||
// clusters span multiple runes?
|
|
||||||
//
|
|
||||||
// TODO: do we assume that the input is in any particular Unicode Normalization
|
|
||||||
// Form?
|
|
||||||
//
|
|
||||||
// TODO: have DrawRunes(s []rune)? DrawRuneReader(io.RuneReader)?? If we take
|
|
||||||
// io.RuneReader, we can't assume that we 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?
|
|
||||||
|
|
||||||
// DrawBytes draws s at the dot and advances the dot's location.
|
|
||||||
//
|
|
||||||
// It is equivalent to DrawString(string(s)) but may be more efficient.
|
|
||||||
func (d *Drawer) DrawBytes(s []byte) {
|
|
||||||
prevC := rune(-1)
|
|
||||||
for len(s) > 0 {
|
|
||||||
c, size := utf8.DecodeRune(s)
|
|
||||||
s = s[size:]
|
|
||||||
if prevC >= 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DrawString draws s at the dot and advances the dot's location.
|
|
||||||
func (d *Drawer) DrawString(s string) {
|
|
||||||
prevC := rune(-1)
|
|
||||||
for _, c := range s {
|
|
||||||
if prevC >= 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BoundBytes returns the bounding box of s, drawn at the drawer dot, as well as
|
|
||||||
// the advance.
|
|
||||||
//
|
|
||||||
// It is equivalent to BoundBytes(string(s)) but may be more efficient.
|
|
||||||
func (d *Drawer) BoundBytes(s []byte) (bounds fixed.Rectangle26_6, advance fixed.Int26_6) {
|
|
||||||
bounds, advance = BoundBytes(d.Face, s)
|
|
||||||
bounds.Min = bounds.Min.Add(d.Dot)
|
|
||||||
bounds.Max = bounds.Max.Add(d.Dot)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// BoundString returns the bounding box of s, drawn at the drawer dot, as well
|
|
||||||
// as the advance.
|
|
||||||
func (d *Drawer) BoundString(s string) (bounds fixed.Rectangle26_6, advance fixed.Int26_6) {
|
|
||||||
bounds, advance = BoundString(d.Face, s)
|
|
||||||
bounds.Min = bounds.Min.Add(d.Dot)
|
|
||||||
bounds.Max = bounds.Max.Add(d.Dot)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// MeasureBytes returns how far dot would advance by drawing s.
|
|
||||||
//
|
|
||||||
// It is equivalent to MeasureString(string(s)) but may be more efficient.
|
|
||||||
func (d *Drawer) MeasureBytes(s []byte) (advance fixed.Int26_6) {
|
|
||||||
return MeasureBytes(d.Face, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MeasureString returns how far dot would advance by drawing s.
|
|
||||||
func (d *Drawer) MeasureString(s string) (advance fixed.Int26_6) {
|
|
||||||
return MeasureString(d.Face, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BoundBytes returns the bounding box of s with f, drawn at a dot equal to the
|
|
||||||
// origin, as well as the advance.
|
|
||||||
//
|
|
||||||
// It is equivalent to BoundString(string(s)) but may be more efficient.
|
|
||||||
func BoundBytes(f Face, s []byte) (bounds fixed.Rectangle26_6, advance fixed.Int26_6) {
|
|
||||||
prevC := rune(-1)
|
|
||||||
for len(s) > 0 {
|
|
||||||
c, size := utf8.DecodeRune(s)
|
|
||||||
s = s[size:]
|
|
||||||
if prevC >= 0 {
|
|
||||||
advance += f.Kern(prevC, c)
|
|
||||||
}
|
|
||||||
b, a, ok := f.GlyphBounds(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
|
|
||||||
}
|
|
||||||
b.Min.X += advance
|
|
||||||
b.Max.X += advance
|
|
||||||
bounds = bounds.Union(b)
|
|
||||||
advance += a
|
|
||||||
prevC = c
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// BoundString returns the bounding box of s with f, drawn at a dot equal to the
|
|
||||||
// origin, as well as the advance.
|
|
||||||
func BoundString(f Face, s string) (bounds fixed.Rectangle26_6, advance fixed.Int26_6) {
|
|
||||||
prevC := rune(-1)
|
|
||||||
for _, c := range s {
|
|
||||||
if prevC >= 0 {
|
|
||||||
advance += f.Kern(prevC, c)
|
|
||||||
}
|
|
||||||
b, a, ok := f.GlyphBounds(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
|
|
||||||
}
|
|
||||||
b.Min.X += advance
|
|
||||||
b.Max.X += advance
|
|
||||||
bounds = bounds.Union(b)
|
|
||||||
advance += a
|
|
||||||
prevC = c
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// MeasureBytes returns how far dot would advance by drawing s with f.
|
|
||||||
//
|
|
||||||
// It is equivalent to MeasureString(string(s)) but may be more efficient.
|
|
||||||
func MeasureBytes(f Face, s []byte) (advance fixed.Int26_6) {
|
|
||||||
prevC := rune(-1)
|
|
||||||
for len(s) > 0 {
|
|
||||||
c, size := utf8.DecodeRune(s)
|
|
||||||
s = s[size:]
|
|
||||||
if prevC >= 0 {
|
|
||||||
advance += f.Kern(prevC, c)
|
|
||||||
}
|
|
||||||
a, ok := f.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
|
|
||||||
}
|
|
||||||
|
|
||||||
// MeasureString returns how far dot would advance by drawing s with f.
|
|
||||||
func MeasureString(f Face, s string) (advance fixed.Int26_6) {
|
|
||||||
prevC := rune(-1)
|
|
||||||
for _, c := range s {
|
|
||||||
if prevC >= 0 {
|
|
||||||
advance += f.Kern(prevC, c)
|
|
||||||
}
|
|
||||||
a, ok := f.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.
|
|
||||||
//
|
|
||||||
// The named Weight constants (e.g. WeightBold) correspond to CSS' common
|
|
||||||
// weight names (e.g. "Bold"), but the numerical values differ, so that in Go,
|
|
||||||
// the zero value means to use a normal weight. For the CSS names and values,
|
|
||||||
// see https://developer.mozilla.org/en/docs/Web/CSS/font-weight
|
|
||||||
type Weight int
|
|
||||||
|
|
||||||
const (
|
|
||||||
WeightThin Weight = -3 // CSS font-weight value 100.
|
|
||||||
WeightExtraLight Weight = -2 // CSS font-weight value 200.
|
|
||||||
WeightLight Weight = -1 // CSS font-weight value 300.
|
|
||||||
WeightNormal Weight = +0 // CSS font-weight value 400.
|
|
||||||
WeightMedium Weight = +1 // CSS font-weight value 500.
|
|
||||||
WeightSemiBold Weight = +2 // CSS font-weight value 600.
|
|
||||||
WeightBold Weight = +3 // CSS font-weight value 700.
|
|
||||||
WeightExtraBold Weight = +4 // CSS font-weight value 800.
|
|
||||||
WeightBlack Weight = +5 // CSS font-weight value 900.
|
|
||||||
)
|
|
|
@ -1,65 +0,0 @@
|
||||||
// Copyright 2016 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
|
|
||||||
|
|
||||||
import (
|
|
||||||
"image"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"golang.org/x/image/math/fixed"
|
|
||||||
)
|
|
||||||
|
|
||||||
const toyAdvance = fixed.Int26_6(10 << 6)
|
|
||||||
|
|
||||||
type toyFace struct{}
|
|
||||||
|
|
||||||
func (toyFace) Close() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (toyFace) Glyph(dot fixed.Point26_6, r rune) (image.Rectangle, image.Image, image.Point, fixed.Int26_6, bool) {
|
|
||||||
panic("unimplemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (toyFace) GlyphBounds(r rune) (fixed.Rectangle26_6, fixed.Int26_6, bool) {
|
|
||||||
return fixed.Rectangle26_6{
|
|
||||||
Min: fixed.P(2, 0),
|
|
||||||
Max: fixed.P(6, 1),
|
|
||||||
}, toyAdvance, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (toyFace) GlyphAdvance(r rune) (fixed.Int26_6, bool) {
|
|
||||||
return toyAdvance, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (toyFace) Kern(r0, r1 rune) fixed.Int26_6 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (toyFace) Metrics() Metrics {
|
|
||||||
return Metrics{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBound(t *testing.T) {
|
|
||||||
wantBounds := []fixed.Rectangle26_6{
|
|
||||||
{Min: fixed.P(0, 0), Max: fixed.P(0, 0)},
|
|
||||||
{Min: fixed.P(2, 0), Max: fixed.P(6, 1)},
|
|
||||||
{Min: fixed.P(2, 0), Max: fixed.P(16, 1)},
|
|
||||||
{Min: fixed.P(2, 0), Max: fixed.P(26, 1)},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, wantBound := range wantBounds {
|
|
||||||
s := strings.Repeat("x", i)
|
|
||||||
gotBound, gotAdvance := BoundString(toyFace{}, s)
|
|
||||||
if gotBound != wantBound {
|
|
||||||
t.Errorf("i=%d: bound: got %v, want %v", i, gotBound, wantBound)
|
|
||||||
}
|
|
||||||
wantAdvance := toyAdvance * fixed.Int26_6(i)
|
|
||||||
if gotAdvance != wantAdvance {
|
|
||||||
t.Errorf("i=%d: advance: got %v, want %v", i, gotAdvance, wantAdvance)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,107 +0,0 @@
|
||||||
// Copyright 2016 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
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
// This program generates the subdirectories of Go packages that contain []byte
|
|
||||||
// versions of the TrueType font files under ./ttfs.
|
|
||||||
//
|
|
||||||
// Currently, "go run gen.go" needs to be run manually. This isn't done by the
|
|
||||||
// usual "go generate" mechanism as there isn't any other Go code in this
|
|
||||||
// directory (excluding sub-directories) to attach a "go:generate" line to.
|
|
||||||
//
|
|
||||||
// In any case, code generation should only need to happen when the underlying
|
|
||||||
// TTF files change, which isn't expected to happen frequently.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"go/format"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
const suffix = ".ttf"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
ttfs, err := os.Open("ttfs")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer ttfs.Close()
|
|
||||||
|
|
||||||
infos, err := ttfs.Readdir(-1)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
for _, info := range infos {
|
|
||||||
ttfName := info.Name()
|
|
||||||
if !strings.HasSuffix(ttfName, suffix) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
do(ttfName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func do(ttfName string) {
|
|
||||||
fontName := fontName(ttfName)
|
|
||||||
pkgName := pkgName(ttfName)
|
|
||||||
if err := os.Mkdir(pkgName, 0777); err != nil && !os.IsExist(err) {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
src, err := ioutil.ReadFile(filepath.Join("ttfs", ttfName))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
desc := "a proportional-width, sans-serif"
|
|
||||||
if strings.Contains(ttfName, "Mono") {
|
|
||||||
desc = "a fixed-width, slab-serif"
|
|
||||||
}
|
|
||||||
|
|
||||||
b := new(bytes.Buffer)
|
|
||||||
fmt.Fprintf(b, "// generated by go run gen.go; DO NOT EDIT\n\n")
|
|
||||||
fmt.Fprintf(b, "// Package %s provides the %q TrueType font\n", pkgName, fontName)
|
|
||||||
fmt.Fprintf(b, "// from the Go font family. It is %s font.\n", desc)
|
|
||||||
fmt.Fprintf(b, "//\n")
|
|
||||||
fmt.Fprintf(b, "// See https://blog.golang.org/go-fonts for details.\n")
|
|
||||||
fmt.Fprintf(b, "package %s\n\n", pkgName)
|
|
||||||
fmt.Fprintf(b, "// TTF is the data for the %q TrueType font.\n", fontName)
|
|
||||||
fmt.Fprintf(b, "var TTF = []byte{")
|
|
||||||
for i, x := range src {
|
|
||||||
if i&15 == 0 {
|
|
||||||
b.WriteByte('\n')
|
|
||||||
}
|
|
||||||
fmt.Fprintf(b, "%#02x,", x)
|
|
||||||
}
|
|
||||||
fmt.Fprintf(b, "\n}\n")
|
|
||||||
|
|
||||||
dst, err := format.Source(b.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := ioutil.WriteFile(filepath.Join(pkgName, "data.go"), dst, 0666); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fontName maps "Go-Regular.ttf" to "Go Regular".
|
|
||||||
func fontName(ttfName string) string {
|
|
||||||
s := ttfName[:len(ttfName)-len(suffix)]
|
|
||||||
s = strings.Replace(s, "-", " ", -1)
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// pkgName maps "Go-Regular.ttf" to "goregular".
|
|
||||||
func pkgName(ttfName string) string {
|
|
||||||
s := ttfName[:len(ttfName)-len(suffix)]
|
|
||||||
s = strings.Replace(s, "-", "", -1)
|
|
||||||
s = strings.ToLower(s)
|
|
||||||
return s
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,36 +0,0 @@
|
||||||
These fonts were created by the Bigelow & Holmes foundry specifically for the
|
|
||||||
Go project. See https://blog.golang.org/go-fonts for details.
|
|
||||||
|
|
||||||
They are licensed under the same open source license as the rest of the Go
|
|
||||||
project's software:
|
|
||||||
|
|
||||||
Copyright (c) 2016 Bigelow & Holmes Inc.. All rights reserved.
|
|
||||||
|
|
||||||
Distribution of this font is governed by the following license. If you do not
|
|
||||||
agree to this license, including the disclaimer, do not distribute or modify
|
|
||||||
this font.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer.
|
|
||||||
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
* Neither the name of Google Inc. nor the names of its contributors may be
|
|
||||||
used to endorse or promote products derived from this software without
|
|
||||||
specific prior written permission.
|
|
||||||
|
|
||||||
DISCLAIMER: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
|
||||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
||||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,29 +0,0 @@
|
||||||
// Copyright 2016 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.
|
|
||||||
|
|
||||||
//go:generate genbasicfont -size=16 -pkg=inconsolata -hinting=full -var=regular8x16 -fontfile=http://www.levien.com/type/myfonts/inconsolata/InconsolataGo-Regular.ttf
|
|
||||||
//go:generate genbasicfont -size=16 -pkg=inconsolata -hinting=full -var=bold8x16 -fontfile=http://www.levien.com/type/myfonts/inconsolata/InconsolataGo-Bold.ttf
|
|
||||||
|
|
||||||
// The genbasicfont program is github.com/golang/freetype/example/genbasicfont
|
|
||||||
|
|
||||||
// Package inconsolata provides pre-rendered bitmap versions of the Inconsolata
|
|
||||||
// font family.
|
|
||||||
//
|
|
||||||
// Inconsolata is copyright Raph Levien and Cyreal. This package is licensed
|
|
||||||
// under Go's BSD-style license (https://golang.org/LICENSE) with their
|
|
||||||
// permission.
|
|
||||||
//
|
|
||||||
// Inconsolata's home page is at
|
|
||||||
// http://www.levien.com/type/myfonts/inconsolata.html
|
|
||||||
package inconsolata // import "golang.org/x/image/font/inconsolata"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/image/font/basicfont"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Regular8x16 is a regular weight, 8x16 font face.
|
|
||||||
var Regular8x16 *basicfont.Face = ®ular8x16
|
|
||||||
|
|
||||||
// Bold8x16 is a bold weight, 8x16 font face.
|
|
||||||
var Bold8x16 *basicfont.Face = &bold8x16
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,92 +0,0 @@
|
||||||
// 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/image/font"
|
|
||||||
"golang.org/x/image/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)
|
|
||||||
}
|
|
||||||
ascent := face.Metrics().Ascent.Ceil()
|
|
||||||
|
|
||||||
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...........
|
|
||||||
}
|
|
|
@ -1,610 +0,0 @@
|
||||||
// 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 // import "golang.org/x/image/font/plan9font"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"image"
|
|
||||||
"image/color"
|
|
||||||
"log"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/image/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 *image.Alpha // 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) Metrics() font.Metrics {
|
|
||||||
return font.Metrics{
|
|
||||||
Height: fixed.I(f.height),
|
|
||||||
Ascent: fixed.I(f.ascent),
|
|
||||||
Descent: fixed.I(f.height - f.ascent),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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) Metrics() font.Metrics {
|
|
||||||
return font.Metrics{
|
|
||||||
Height: fixed.I(f.height),
|
|
||||||
Ascent: fixed.I(f.ascent),
|
|
||||||
Descent: fixed.I(f.height - f.ascent),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// For subfont files, if reading the given file name fails, we try appending
|
|
||||||
// ".n" where n is the log2 of the grayscale depth in bits (so at most 3) and
|
|
||||||
// then work down to 0. This was done in Plan 9 when antialiased fonts were
|
|
||||||
// introduced so that the 1-bit displays could keep using the 1-bit forms but
|
|
||||||
// higher depth displays could use the antialiased forms.
|
|
||||||
var subfontSuffixes = [...]string{
|
|
||||||
"",
|
|
||||||
".3",
|
|
||||||
".2",
|
|
||||||
".1",
|
|
||||||
".0",
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *face) readSubfontFile(name string) ([]byte, error) {
|
|
||||||
var firstErr error
|
|
||||||
for _, suffix := range subfontSuffixes {
|
|
||||||
if b, err := f.readFile(name + suffix); err == nil {
|
|
||||||
return b, nil
|
|
||||||
} else if firstErr == nil {
|
|
||||||
firstErr = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, firstErr
|
|
||||||
}
|
|
||||||
|
|
||||||
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.readSubfontFile(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")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert from plan9Image to image.Alpha, as the standard library's
|
|
||||||
// image/draw package works best when glyph masks are of that type.
|
|
||||||
img := image.NewAlpha(m.Bounds())
|
|
||||||
for y := img.Rect.Min.Y; y < img.Rect.Max.Y; y++ {
|
|
||||||
i := img.PixOffset(img.Rect.Min.X, y)
|
|
||||||
for x := img.Rect.Min.X; x < img.Rect.Max.X; x++ {
|
|
||||||
img.Pix[i] = m.at(x, y)
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &subface{
|
|
||||||
firstRune: firstRune,
|
|
||||||
n: n,
|
|
||||||
height: height,
|
|
||||||
ascent: ascent,
|
|
||||||
fontchars: parseFontchars(data),
|
|
||||||
img: img,
|
|
||||||
}, 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) {
|
|
||||||
return color.Alpha{m.at(x, y)}
|
|
||||||
}
|
|
||||||
return color.Alpha{0x00}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *plan9Image) at(x, y int) uint8 {
|
|
||||||
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 0xff
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
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 y
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
// Copyright 2016 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
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func BenchmarkParseSubfont(b *testing.B) {
|
|
||||||
subfontData, err := ioutil.ReadFile(filepath.FromSlash("../testdata/fixed/7x13.0000"))
|
|
||||||
if err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
if _, err := ParseSubfont(subfontData, 0); err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,198 +0,0 @@
|
||||||
// Copyright 2017 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 sfnt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
"golang.org/x/text/encoding/charmap"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Platform IDs and Platform Specific IDs as per
|
|
||||||
// https://www.microsoft.com/typography/otspec/name.htm
|
|
||||||
const (
|
|
||||||
pidUnicode = 0
|
|
||||||
pidMacintosh = 1
|
|
||||||
pidWindows = 3
|
|
||||||
|
|
||||||
psidUnicode2BMPOnly = 3
|
|
||||||
psidUnicode2FullRepertoire = 4
|
|
||||||
|
|
||||||
psidMacintoshRoman = 0
|
|
||||||
|
|
||||||
psidWindowsUCS2 = 1
|
|
||||||
psidWindowsUCS4 = 10
|
|
||||||
)
|
|
||||||
|
|
||||||
// platformEncodingWidth returns the number of bytes per character assumed by
|
|
||||||
// the given Platform ID and Platform Specific ID.
|
|
||||||
//
|
|
||||||
// Very old fonts, from before Unicode was widely adopted, assume only 1 byte
|
|
||||||
// per character: a character map.
|
|
||||||
//
|
|
||||||
// Old fonts, from when Unicode meant the Basic Multilingual Plane (BMP),
|
|
||||||
// assume that 2 bytes per character is sufficient.
|
|
||||||
//
|
|
||||||
// Recent fonts naturally support the full range of Unicode code points, which
|
|
||||||
// can take up to 4 bytes per character. Such fonts might still choose one of
|
|
||||||
// the legacy encodings if e.g. their repertoire is limited to the BMP, for
|
|
||||||
// greater compatibility with older software, or because the resultant file
|
|
||||||
// size can be smaller.
|
|
||||||
func platformEncodingWidth(pid, psid uint16) int {
|
|
||||||
switch pid {
|
|
||||||
case pidUnicode:
|
|
||||||
switch psid {
|
|
||||||
case psidUnicode2BMPOnly:
|
|
||||||
return 2
|
|
||||||
case psidUnicode2FullRepertoire:
|
|
||||||
return 4
|
|
||||||
}
|
|
||||||
|
|
||||||
case pidMacintosh:
|
|
||||||
switch psid {
|
|
||||||
case psidMacintoshRoman:
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
case pidWindows:
|
|
||||||
switch psid {
|
|
||||||
case psidWindowsUCS2:
|
|
||||||
return 2
|
|
||||||
case psidWindowsUCS4:
|
|
||||||
return 4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// The various cmap formats are described at
|
|
||||||
// https://www.microsoft.com/typography/otspec/cmap.htm
|
|
||||||
|
|
||||||
var supportedCmapFormat = func(format, pid, psid uint16) bool {
|
|
||||||
switch format {
|
|
||||||
case 0:
|
|
||||||
return pid == pidMacintosh && psid == psidMacintoshRoman
|
|
||||||
case 4:
|
|
||||||
return true
|
|
||||||
case 12:
|
|
||||||
// TODO: implement.
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Font) makeCachedGlyphIndex(buf []byte, offset, length uint32, format uint16) ([]byte, error) {
|
|
||||||
switch format {
|
|
||||||
case 0:
|
|
||||||
return f.makeCachedGlyphIndexFormat0(buf, offset, length)
|
|
||||||
case 4:
|
|
||||||
return f.makeCachedGlyphIndexFormat4(buf, offset, length)
|
|
||||||
case 12:
|
|
||||||
// TODO: implement, including a cmapEntry32 type (32, not 16).
|
|
||||||
}
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Font) makeCachedGlyphIndexFormat0(buf []byte, offset, length uint32) ([]byte, error) {
|
|
||||||
if length != 6+256 || offset+length > f.cmap.length {
|
|
||||||
return nil, errInvalidCmapTable
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
buf, err = f.src.view(buf, int(f.cmap.offset+offset), int(length))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var table [256]byte
|
|
||||||
copy(table[:], buf[6:])
|
|
||||||
f.cached.glyphIndex = func(f *Font, b *Buffer, r rune) (GlyphIndex, error) {
|
|
||||||
// TODO: for this closure to be goroutine-safe, the
|
|
||||||
// golang.org/x/text/encoding/charmap API needs to allocate a new
|
|
||||||
// Encoder and new []byte buffers, for every call to this closure, even
|
|
||||||
// though all we want to do is to encode one rune as one byte. We could
|
|
||||||
// possibly add some fields in the Buffer struct to re-use these
|
|
||||||
// allocations, but a better solution is to improve the charmap API.
|
|
||||||
var dst, src [utf8.UTFMax]byte
|
|
||||||
n := utf8.EncodeRune(src[:], r)
|
|
||||||
_, _, err = charmap.Macintosh.NewEncoder().Transform(dst[:], src[:n], true)
|
|
||||||
if err != nil {
|
|
||||||
// The source rune r is not representable in the Macintosh-Roman encoding.
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
return GlyphIndex(table[dst[0]]), nil
|
|
||||||
}
|
|
||||||
return buf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Font) makeCachedGlyphIndexFormat4(buf []byte, offset, length uint32) ([]byte, error) {
|
|
||||||
const headerSize = 14
|
|
||||||
if offset+headerSize > f.cmap.length {
|
|
||||||
return nil, errInvalidCmapTable
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
buf, err = f.src.view(buf, int(f.cmap.offset+offset), headerSize)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
offset += headerSize
|
|
||||||
|
|
||||||
segCount := u16(buf[6:])
|
|
||||||
if segCount&1 != 0 {
|
|
||||||
return nil, errInvalidCmapTable
|
|
||||||
}
|
|
||||||
segCount /= 2
|
|
||||||
if segCount > maxCmapSegments {
|
|
||||||
return nil, errUnsupportedNumberOfCmapSegments
|
|
||||||
}
|
|
||||||
|
|
||||||
eLength := 8*uint32(segCount) + 2
|
|
||||||
if offset+eLength > f.cmap.length {
|
|
||||||
return nil, errInvalidCmapTable
|
|
||||||
}
|
|
||||||
buf, err = f.src.view(buf, int(f.cmap.offset+offset), int(eLength))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
offset += eLength
|
|
||||||
|
|
||||||
entries := make([]cmapEntry16, segCount)
|
|
||||||
for i := range entries {
|
|
||||||
entries[i] = cmapEntry16{
|
|
||||||
end: u16(buf[0*len(entries)+0+2*i:]),
|
|
||||||
start: u16(buf[2*len(entries)+2+2*i:]),
|
|
||||||
delta: u16(buf[4*len(entries)+2+2*i:]),
|
|
||||||
offset: u16(buf[6*len(entries)+2+2*i:]),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
f.cached.glyphIndex = func(f *Font, b *Buffer, r rune) (GlyphIndex, error) {
|
|
||||||
if uint32(r) > 0xffff {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
c := uint16(r)
|
|
||||||
for i, j := 0, len(entries); i < j; {
|
|
||||||
h := i + (j-i)/2
|
|
||||||
entry := &entries[h]
|
|
||||||
if c < entry.start {
|
|
||||||
j = h
|
|
||||||
} else if entry.end < c {
|
|
||||||
i = h + 1
|
|
||||||
} else if entry.offset == 0 {
|
|
||||||
return GlyphIndex(c + entry.delta), nil
|
|
||||||
} else {
|
|
||||||
// TODO: support the glyphIdArray as per
|
|
||||||
// https://www.microsoft.com/typography/OTSPEC/cmap.htm
|
|
||||||
//
|
|
||||||
// This will probably use the *Font and *Buffer arguments.
|
|
||||||
return 0, errUnsupportedCmapFormat
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
return buf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type cmapEntry16 struct {
|
|
||||||
end, start, delta, offset uint16
|
|
||||||
}
|
|
|
@ -1,865 +0,0 @@
|
||||||
// Copyright 2016 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 sfnt
|
|
||||||
|
|
||||||
// Compact Font Format (CFF) fonts are written in PostScript, a stack-based
|
|
||||||
// programming language.
|
|
||||||
//
|
|
||||||
// A fundamental concept is a DICT, or a key-value map, expressed in reverse
|
|
||||||
// Polish notation. For example, this sequence of operations:
|
|
||||||
// - push the number 379
|
|
||||||
// - version operator
|
|
||||||
// - push the number 392
|
|
||||||
// - Notice operator
|
|
||||||
// - etc
|
|
||||||
// - push the number 100
|
|
||||||
// - push the number 0
|
|
||||||
// - push the number 500
|
|
||||||
// - push the number 800
|
|
||||||
// - FontBBox operator
|
|
||||||
// - etc
|
|
||||||
// defines a DICT that maps "version" to the String ID (SID) 379, "Notice" to
|
|
||||||
// the SID 392, "FontBBox" to the four numbers [100, 0, 500, 800], etc.
|
|
||||||
//
|
|
||||||
// The first 391 String IDs (starting at 0) are predefined as per the CFF spec
|
|
||||||
// Appendix A, in 5176.CFF.pdf referenced below. For example, 379 means
|
|
||||||
// "001.000". String ID 392 is not predefined, and is mapped by a separate
|
|
||||||
// structure, the "String INDEX", inside the CFF data. (String ID 391 is also
|
|
||||||
// not predefined. Specifically for ../testdata/CFFTest.otf, 391 means
|
|
||||||
// "uni4E2D", as this font contains a glyph for U+4E2D).
|
|
||||||
//
|
|
||||||
// The actual glyph vectors are similarly encoded (in PostScript), in a format
|
|
||||||
// called Type 2 Charstrings. The wire encoding is similar to but not exactly
|
|
||||||
// the same as CFF's. For example, the byte 0x05 means FontBBox for CFF DICTs,
|
|
||||||
// but means rlineto (relative line-to) for Type 2 Charstrings. See
|
|
||||||
// 5176.CFF.pdf Appendix H and 5177.Type2.pdf Appendix A in the PDF files
|
|
||||||
// referenced below.
|
|
||||||
//
|
|
||||||
// CFF is a stand-alone format, but CFF as used in SFNT fonts have further
|
|
||||||
// restrictions. For example, a stand-alone CFF can contain multiple fonts, but
|
|
||||||
// https://www.microsoft.com/typography/OTSPEC/cff.htm says that "The Name
|
|
||||||
// INDEX in the CFF must contain only one entry; that is, there must be only
|
|
||||||
// one font in the CFF FontSet".
|
|
||||||
//
|
|
||||||
// The relevant specifications are:
|
|
||||||
// - http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/font/pdfs/5176.CFF.pdf
|
|
||||||
// - http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/font/pdfs/5177.Type2.pdf
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"golang.org/x/image/math/fixed"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// psStackSize is the stack size for a PostScript interpreter. 5176.CFF.pdf
|
|
||||||
// section 4 "DICT Data" says that "An operator may be preceded by up to a
|
|
||||||
// maximum of 48 operands". Similarly, 5177.Type2.pdf Appendix B "Type 2
|
|
||||||
// Charstring Implementation Limits" says that "Argument stack 48".
|
|
||||||
psStackSize = 48
|
|
||||||
)
|
|
||||||
|
|
||||||
func bigEndian(b []byte) uint32 {
|
|
||||||
switch len(b) {
|
|
||||||
case 1:
|
|
||||||
return uint32(b[0])
|
|
||||||
case 2:
|
|
||||||
return uint32(b[0])<<8 | uint32(b[1])
|
|
||||||
case 3:
|
|
||||||
return uint32(b[0])<<16 | uint32(b[1])<<8 | uint32(b[2])
|
|
||||||
case 4:
|
|
||||||
return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3])
|
|
||||||
}
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
|
|
||||||
// cffParser parses the CFF table from an SFNT font.
|
|
||||||
type cffParser struct {
|
|
||||||
src *source
|
|
||||||
base int
|
|
||||||
offset int
|
|
||||||
end int
|
|
||||||
err error
|
|
||||||
|
|
||||||
buf []byte
|
|
||||||
locBuf [2]uint32
|
|
||||||
|
|
||||||
psi psInterpreter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *cffParser) parse() (locations []uint32, err error) {
|
|
||||||
// Parse header.
|
|
||||||
{
|
|
||||||
if !p.read(4) {
|
|
||||||
return nil, p.err
|
|
||||||
}
|
|
||||||
if p.buf[0] != 1 || p.buf[1] != 0 || p.buf[2] != 4 {
|
|
||||||
return nil, errUnsupportedCFFVersion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse Name INDEX.
|
|
||||||
{
|
|
||||||
count, offSize, ok := p.parseIndexHeader()
|
|
||||||
if !ok {
|
|
||||||
return nil, p.err
|
|
||||||
}
|
|
||||||
// https://www.microsoft.com/typography/OTSPEC/cff.htm says that "The
|
|
||||||
// Name INDEX in the CFF must contain only one entry".
|
|
||||||
if count != 1 {
|
|
||||||
return nil, errInvalidCFFTable
|
|
||||||
}
|
|
||||||
if !p.parseIndexLocations(p.locBuf[:2], count, offSize) {
|
|
||||||
return nil, p.err
|
|
||||||
}
|
|
||||||
p.offset = int(p.locBuf[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse Top DICT INDEX.
|
|
||||||
{
|
|
||||||
count, offSize, ok := p.parseIndexHeader()
|
|
||||||
if !ok {
|
|
||||||
return nil, p.err
|
|
||||||
}
|
|
||||||
// 5176.CFF.pdf section 8 "Top DICT INDEX" says that the count here
|
|
||||||
// should match the count of the Name INDEX, which is 1.
|
|
||||||
if count != 1 {
|
|
||||||
return nil, errInvalidCFFTable
|
|
||||||
}
|
|
||||||
if !p.parseIndexLocations(p.locBuf[:2], count, offSize) {
|
|
||||||
return nil, p.err
|
|
||||||
}
|
|
||||||
if !p.read(int(p.locBuf[1] - p.locBuf[0])) {
|
|
||||||
return nil, p.err
|
|
||||||
}
|
|
||||||
p.psi.topDict.initialize()
|
|
||||||
if p.err = p.psi.run(psContextTopDict, p.buf); p.err != nil {
|
|
||||||
return nil, p.err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse the CharStrings INDEX, whose location was found in the Top DICT.
|
|
||||||
if p.psi.topDict.charStrings <= 0 || int32(p.end-p.base) < p.psi.topDict.charStrings {
|
|
||||||
return nil, errInvalidCFFTable
|
|
||||||
}
|
|
||||||
p.offset = p.base + int(p.psi.topDict.charStrings)
|
|
||||||
count, offSize, ok := p.parseIndexHeader()
|
|
||||||
if !ok {
|
|
||||||
return nil, p.err
|
|
||||||
}
|
|
||||||
if count == 0 {
|
|
||||||
return nil, errInvalidCFFTable
|
|
||||||
}
|
|
||||||
locations = make([]uint32, count+1)
|
|
||||||
if !p.parseIndexLocations(locations, count, offSize) {
|
|
||||||
return nil, p.err
|
|
||||||
}
|
|
||||||
return locations, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// read sets p.buf to view the n bytes from p.offset to p.offset+n. It also
|
|
||||||
// advances p.offset by n.
|
|
||||||
//
|
|
||||||
// As per the source.view method, the caller should not modify the contents of
|
|
||||||
// p.buf after read returns, other than by calling read again.
|
|
||||||
//
|
|
||||||
// The caller should also avoid modifying the pointer / length / capacity of
|
|
||||||
// the p.buf slice, not just avoid modifying the slice's contents, in order to
|
|
||||||
// maximize the opportunity to re-use p.buf's allocated memory when viewing the
|
|
||||||
// underlying source data for subsequent read calls.
|
|
||||||
func (p *cffParser) read(n int) (ok bool) {
|
|
||||||
if p.end-p.offset < n {
|
|
||||||
p.err = errInvalidCFFTable
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
p.buf, p.err = p.src.view(p.buf, p.offset, n)
|
|
||||||
p.offset += n
|
|
||||||
return p.err == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *cffParser) parseIndexHeader() (count, offSize int32, ok bool) {
|
|
||||||
if !p.read(2) {
|
|
||||||
return 0, 0, false
|
|
||||||
}
|
|
||||||
count = int32(u16(p.buf[:2]))
|
|
||||||
// 5176.CFF.pdf section 5 "INDEX Data" says that "An empty INDEX is
|
|
||||||
// represented by a count field with a 0 value and no additional fields.
|
|
||||||
// Thus, the total size of an empty INDEX is 2 bytes".
|
|
||||||
if count == 0 {
|
|
||||||
return count, 0, true
|
|
||||||
}
|
|
||||||
if !p.read(1) {
|
|
||||||
return 0, 0, false
|
|
||||||
}
|
|
||||||
offSize = int32(p.buf[0])
|
|
||||||
if offSize < 1 || 4 < offSize {
|
|
||||||
p.err = errInvalidCFFTable
|
|
||||||
return 0, 0, false
|
|
||||||
}
|
|
||||||
return count, offSize, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *cffParser) parseIndexLocations(dst []uint32, count, offSize int32) (ok bool) {
|
|
||||||
if count == 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if len(dst) != int(count+1) {
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
if !p.read(len(dst) * int(offSize)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
buf, prev := p.buf, uint32(0)
|
|
||||||
for i := range dst {
|
|
||||||
loc := bigEndian(buf[:offSize])
|
|
||||||
buf = buf[offSize:]
|
|
||||||
|
|
||||||
// Locations are off by 1 byte. 5176.CFF.pdf section 5 "INDEX Data"
|
|
||||||
// says that "Offsets in the offset array are relative to the byte that
|
|
||||||
// precedes the object data... This ensures that every object has a
|
|
||||||
// corresponding offset which is always nonzero".
|
|
||||||
if loc == 0 {
|
|
||||||
p.err = errInvalidCFFTable
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
loc--
|
|
||||||
|
|
||||||
// In the same paragraph, "Therefore the first element of the offset
|
|
||||||
// array is always 1" before correcting for the off-by-1.
|
|
||||||
if i == 0 {
|
|
||||||
if loc != 0 {
|
|
||||||
p.err = errInvalidCFFTable
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} else if loc <= prev { // Check that locations are increasing.
|
|
||||||
p.err = errInvalidCFFTable
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that locations are in bounds.
|
|
||||||
if uint32(p.end-p.offset) < loc {
|
|
||||||
p.err = errInvalidCFFTable
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
dst[i] = uint32(p.offset) + loc
|
|
||||||
prev = loc
|
|
||||||
}
|
|
||||||
return p.err == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type psContext uint32
|
|
||||||
|
|
||||||
const (
|
|
||||||
psContextTopDict psContext = iota
|
|
||||||
psContextType2Charstring
|
|
||||||
)
|
|
||||||
|
|
||||||
// psTopDictData contains fields specific to the Top DICT context.
|
|
||||||
type psTopDictData struct {
|
|
||||||
charStrings int32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *psTopDictData) initialize() {
|
|
||||||
*d = psTopDictData{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// psType2CharstringsData contains fields specific to the Type 2 Charstrings
|
|
||||||
// context.
|
|
||||||
type psType2CharstringsData struct {
|
|
||||||
segments []Segment
|
|
||||||
x, y int32
|
|
||||||
hintBits int32
|
|
||||||
seenWidth bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *psType2CharstringsData) initialize(segments []Segment) {
|
|
||||||
*d = psType2CharstringsData{
|
|
||||||
segments: segments,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// psInterpreter is a PostScript interpreter.
|
|
||||||
type psInterpreter struct {
|
|
||||||
ctx psContext
|
|
||||||
instructions []byte
|
|
||||||
stack struct {
|
|
||||||
a [psStackSize]int32
|
|
||||||
top int32
|
|
||||||
}
|
|
||||||
parseNumberBuf [maxRealNumberStrLen]byte
|
|
||||||
topDict psTopDictData
|
|
||||||
type2Charstrings psType2CharstringsData
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *psInterpreter) run(ctx psContext, instructions []byte) error {
|
|
||||||
p.ctx = ctx
|
|
||||||
p.instructions = instructions
|
|
||||||
p.stack.top = 0
|
|
||||||
|
|
||||||
loop:
|
|
||||||
for len(p.instructions) > 0 {
|
|
||||||
// Push a numeric operand on the stack, if applicable.
|
|
||||||
if hasResult, err := p.parseNumber(); hasResult {
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, execute an operator.
|
|
||||||
b := p.instructions[0]
|
|
||||||
p.instructions = p.instructions[1:]
|
|
||||||
|
|
||||||
for escaped, ops := false, psOperators[ctx][0]; ; {
|
|
||||||
if b == escapeByte && !escaped {
|
|
||||||
if len(p.instructions) <= 0 {
|
|
||||||
return errInvalidCFFTable
|
|
||||||
}
|
|
||||||
b = p.instructions[0]
|
|
||||||
p.instructions = p.instructions[1:]
|
|
||||||
escaped = true
|
|
||||||
ops = psOperators[ctx][1]
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if int(b) < len(ops) {
|
|
||||||
if op := ops[b]; op.name != "" {
|
|
||||||
if p.stack.top < op.numPop {
|
|
||||||
return errInvalidCFFTable
|
|
||||||
}
|
|
||||||
if op.run != nil {
|
|
||||||
if err := op.run(p); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if op.numPop < 0 {
|
|
||||||
p.stack.top = 0
|
|
||||||
} else {
|
|
||||||
p.stack.top -= op.numPop
|
|
||||||
}
|
|
||||||
continue loop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if escaped {
|
|
||||||
return fmt.Errorf("sfnt: unrecognized CFF 2-byte operator (12 %d)", b)
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("sfnt: unrecognized CFF 1-byte operator (%d)", b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// See 5176.CFF.pdf section 4 "DICT Data".
|
|
||||||
func (p *psInterpreter) parseNumber() (hasResult bool, err error) {
|
|
||||||
number := int32(0)
|
|
||||||
switch b := p.instructions[0]; {
|
|
||||||
case b == 28:
|
|
||||||
if len(p.instructions) < 3 {
|
|
||||||
return true, errInvalidCFFTable
|
|
||||||
}
|
|
||||||
number, hasResult = int32(int16(u16(p.instructions[1:]))), true
|
|
||||||
p.instructions = p.instructions[3:]
|
|
||||||
|
|
||||||
case b == 29 && p.ctx == psContextTopDict:
|
|
||||||
if len(p.instructions) < 5 {
|
|
||||||
return true, errInvalidCFFTable
|
|
||||||
}
|
|
||||||
number, hasResult = int32(u32(p.instructions[1:])), true
|
|
||||||
p.instructions = p.instructions[5:]
|
|
||||||
|
|
||||||
case b == 30 && p.ctx == psContextTopDict:
|
|
||||||
// Parse a real number. This isn't listed in 5176.CFF.pdf Table 3
|
|
||||||
// "Operand Encoding" but that table lists integer encodings. Further
|
|
||||||
// down the page it says "A real number operand is provided in addition
|
|
||||||
// to integer operands. This operand begins with a byte value of 30
|
|
||||||
// followed by a variable-length sequence of bytes."
|
|
||||||
|
|
||||||
s := p.parseNumberBuf[:0]
|
|
||||||
p.instructions = p.instructions[1:]
|
|
||||||
loop:
|
|
||||||
for {
|
|
||||||
if len(p.instructions) == 0 {
|
|
||||||
return true, errInvalidCFFTable
|
|
||||||
}
|
|
||||||
b := p.instructions[0]
|
|
||||||
p.instructions = p.instructions[1:]
|
|
||||||
// Process b's two nibbles, high then low.
|
|
||||||
for i := 0; i < 2; i++ {
|
|
||||||
nib := b >> 4
|
|
||||||
b = b << 4
|
|
||||||
if nib == 0x0f {
|
|
||||||
f, err := strconv.ParseFloat(string(s), 32)
|
|
||||||
if err != nil {
|
|
||||||
return true, errInvalidCFFTable
|
|
||||||
}
|
|
||||||
number, hasResult = int32(math.Float32bits(float32(f))), true
|
|
||||||
break loop
|
|
||||||
}
|
|
||||||
if nib == 0x0d {
|
|
||||||
return true, errInvalidCFFTable
|
|
||||||
}
|
|
||||||
if len(s)+maxNibbleDefsLength > len(p.parseNumberBuf) {
|
|
||||||
return true, errUnsupportedRealNumberEncoding
|
|
||||||
}
|
|
||||||
s = append(s, nibbleDefs[nib]...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case b < 32:
|
|
||||||
// No-op.
|
|
||||||
|
|
||||||
case b < 247:
|
|
||||||
p.instructions = p.instructions[1:]
|
|
||||||
number, hasResult = int32(b)-139, true
|
|
||||||
|
|
||||||
case b < 251:
|
|
||||||
if len(p.instructions) < 2 {
|
|
||||||
return true, errInvalidCFFTable
|
|
||||||
}
|
|
||||||
b1 := p.instructions[1]
|
|
||||||
p.instructions = p.instructions[2:]
|
|
||||||
number, hasResult = +int32(b-247)*256+int32(b1)+108, true
|
|
||||||
|
|
||||||
case b < 255:
|
|
||||||
if len(p.instructions) < 2 {
|
|
||||||
return true, errInvalidCFFTable
|
|
||||||
}
|
|
||||||
b1 := p.instructions[1]
|
|
||||||
p.instructions = p.instructions[2:]
|
|
||||||
number, hasResult = -int32(b-251)*256-int32(b1)-108, true
|
|
||||||
|
|
||||||
case b == 255 && p.ctx == psContextType2Charstring:
|
|
||||||
if len(p.instructions) < 5 {
|
|
||||||
return true, errInvalidCFFTable
|
|
||||||
}
|
|
||||||
number, hasResult = int32(u32(p.instructions[1:])), true
|
|
||||||
p.instructions = p.instructions[5:]
|
|
||||||
}
|
|
||||||
|
|
||||||
if hasResult {
|
|
||||||
if p.stack.top == psStackSize {
|
|
||||||
return true, errInvalidCFFTable
|
|
||||||
}
|
|
||||||
p.stack.a[p.stack.top] = number
|
|
||||||
p.stack.top++
|
|
||||||
}
|
|
||||||
return hasResult, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const maxNibbleDefsLength = len("E-")
|
|
||||||
|
|
||||||
// nibbleDefs encodes 5176.CFF.pdf Table 5 "Nibble Definitions".
|
|
||||||
var nibbleDefs = [16]string{
|
|
||||||
0x00: "0",
|
|
||||||
0x01: "1",
|
|
||||||
0x02: "2",
|
|
||||||
0x03: "3",
|
|
||||||
0x04: "4",
|
|
||||||
0x05: "5",
|
|
||||||
0x06: "6",
|
|
||||||
0x07: "7",
|
|
||||||
0x08: "8",
|
|
||||||
0x09: "9",
|
|
||||||
0x0a: ".",
|
|
||||||
0x0b: "E",
|
|
||||||
0x0c: "E-",
|
|
||||||
0x0d: "",
|
|
||||||
0x0e: "-",
|
|
||||||
0x0f: "",
|
|
||||||
}
|
|
||||||
|
|
||||||
type psOperator struct {
|
|
||||||
// numPop is the number of stack values to pop. -1 means "array" and -2
|
|
||||||
// means "delta" as per 5176.CFF.pdf Table 6 "Operand Types".
|
|
||||||
numPop int32
|
|
||||||
// name is the operator name. An empty name (i.e. the zero value for the
|
|
||||||
// struct overall) means an unrecognized 1-byte operator.
|
|
||||||
name string
|
|
||||||
// run is the function that implements the operator. Nil means that we
|
|
||||||
// ignore the operator, other than popping its arguments off the stack.
|
|
||||||
run func(*psInterpreter) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// psOperators holds the 1-byte and 2-byte operators for PostScript interpreter
|
|
||||||
// contexts.
|
|
||||||
var psOperators = [...][2][]psOperator{
|
|
||||||
// The Top DICT operators are defined by 5176.CFF.pdf Table 9 "Top DICT
|
|
||||||
// Operator Entries" and Table 10 "CIDFont Operator Extensions".
|
|
||||||
psContextTopDict: {{
|
|
||||||
// 1-byte operators.
|
|
||||||
0: {+1, "version", nil},
|
|
||||||
1: {+1, "Notice", nil},
|
|
||||||
2: {+1, "FullName", nil},
|
|
||||||
3: {+1, "FamilyName", nil},
|
|
||||||
4: {+1, "Weight", nil},
|
|
||||||
5: {-1, "FontBBox", nil},
|
|
||||||
13: {+1, "UniqueID", nil},
|
|
||||||
14: {-1, "XUID", nil},
|
|
||||||
15: {+1, "charset", nil},
|
|
||||||
16: {+1, "Encoding", nil},
|
|
||||||
17: {+1, "CharStrings", func(p *psInterpreter) error {
|
|
||||||
p.topDict.charStrings = p.stack.a[p.stack.top-1]
|
|
||||||
return nil
|
|
||||||
}},
|
|
||||||
18: {+2, "Private", nil},
|
|
||||||
}, {
|
|
||||||
// 2-byte operators. The first byte is the escape byte.
|
|
||||||
0: {+1, "Copyright", nil},
|
|
||||||
1: {+1, "isFixedPitch", nil},
|
|
||||||
2: {+1, "ItalicAngle", nil},
|
|
||||||
3: {+1, "UnderlinePosition", nil},
|
|
||||||
4: {+1, "UnderlineThickness", nil},
|
|
||||||
5: {+1, "PaintType", nil},
|
|
||||||
6: {+1, "CharstringType", nil},
|
|
||||||
7: {-1, "FontMatrix", nil},
|
|
||||||
8: {+1, "StrokeWidth", nil},
|
|
||||||
20: {+1, "SyntheticBase", nil},
|
|
||||||
21: {+1, "PostScript", nil},
|
|
||||||
22: {+1, "BaseFontName", nil},
|
|
||||||
23: {-2, "BaseFontBlend", nil},
|
|
||||||
30: {+3, "ROS", nil},
|
|
||||||
31: {+1, "CIDFontVersion", nil},
|
|
||||||
32: {+1, "CIDFontRevision", nil},
|
|
||||||
33: {+1, "CIDFontType", nil},
|
|
||||||
34: {+1, "CIDCount", nil},
|
|
||||||
35: {+1, "UIDBase", nil},
|
|
||||||
36: {+1, "FDArray", nil},
|
|
||||||
37: {+1, "FDSelect", nil},
|
|
||||||
38: {+1, "FontName", nil},
|
|
||||||
}},
|
|
||||||
|
|
||||||
// The Type 2 Charstring operators are defined by 5177.Type2.pdf Appendix A
|
|
||||||
// "Type 2 Charstring Command Codes".
|
|
||||||
psContextType2Charstring: {{
|
|
||||||
// 1-byte operators.
|
|
||||||
0: {}, // Reserved.
|
|
||||||
1: {-1, "hstem", t2CStem},
|
|
||||||
2: {}, // Reserved.
|
|
||||||
3: {-1, "vstem", t2CStem},
|
|
||||||
4: {-1, "vmoveto", t2CVmoveto},
|
|
||||||
5: {-1, "rlineto", t2CRlineto},
|
|
||||||
6: {-1, "hlineto", t2CHlineto},
|
|
||||||
7: {-1, "vlineto", t2CVlineto},
|
|
||||||
8: {-1, "rrcurveto", t2CRrcurveto},
|
|
||||||
9: {}, // Reserved.
|
|
||||||
10: {}, // callsubr.
|
|
||||||
11: {}, // return.
|
|
||||||
12: {}, // escape.
|
|
||||||
13: {}, // Reserved.
|
|
||||||
14: {-1, "endchar", t2CEndchar},
|
|
||||||
15: {}, // Reserved.
|
|
||||||
16: {}, // Reserved.
|
|
||||||
17: {}, // Reserved.
|
|
||||||
18: {-1, "hstemhm", t2CStem},
|
|
||||||
19: {-1, "hintmask", t2CMask},
|
|
||||||
20: {-1, "cntrmask", t2CMask},
|
|
||||||
21: {-1, "rmoveto", t2CRmoveto},
|
|
||||||
22: {-1, "hmoveto", t2CHmoveto},
|
|
||||||
23: {-1, "vstemhm", t2CStem},
|
|
||||||
24: {}, // rcurveline.
|
|
||||||
25: {}, // rlinecurve.
|
|
||||||
26: {}, // vvcurveto.
|
|
||||||
27: {}, // hhcurveto.
|
|
||||||
28: {}, // shortint.
|
|
||||||
29: {}, // callgsubr.
|
|
||||||
30: {-1, "vhcurveto", t2CVhcurveto},
|
|
||||||
31: {-1, "hvcurveto", t2CHvcurveto},
|
|
||||||
}, {
|
|
||||||
// 2-byte operators. The first byte is the escape byte.
|
|
||||||
0: {}, // Reserved.
|
|
||||||
// TODO: more operators.
|
|
||||||
}},
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5176.CFF.pdf section 4 "DICT Data" says that "Two-byte operators have an
|
|
||||||
// initial escape byte of 12".
|
|
||||||
const escapeByte = 12
|
|
||||||
|
|
||||||
// t2CReadWidth reads the optional width adjustment. If present, it is on the
|
|
||||||
// bottom of the stack.
|
|
||||||
//
|
|
||||||
// 5177.Type2.pdf page 16 Note 4 says: "The first stack-clearing operator,
|
|
||||||
// which must be one of hstem, hstemhm, vstem, vstemhm, cntrmask, hintmask,
|
|
||||||
// hmoveto, vmoveto, rmoveto, or endchar, takes an additional argument — the
|
|
||||||
// width... which may be expressed as zero or one numeric argument."
|
|
||||||
func t2CReadWidth(p *psInterpreter, nArgs int32) {
|
|
||||||
if p.type2Charstrings.seenWidth {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.type2Charstrings.seenWidth = true
|
|
||||||
switch nArgs {
|
|
||||||
case 0:
|
|
||||||
if p.stack.top != 1 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case 1:
|
|
||||||
if p.stack.top <= 1 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
if p.stack.top%nArgs != 1 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// When parsing a standalone CFF, we'd save the value of p.stack.a[0] here
|
|
||||||
// as it defines the glyph's width (horizontal advance). Specifically, if
|
|
||||||
// present, it is a delta to the font-global nominalWidthX value found in
|
|
||||||
// the Private DICT. If absent, the glyph's width is the defaultWidthX
|
|
||||||
// value in that dict. See 5176.CFF.pdf section 15 "Private DICT Data".
|
|
||||||
//
|
|
||||||
// For a CFF embedded in an SFNT font (i.e. an OpenType font), glyph widths
|
|
||||||
// are already stored in the hmtx table, separate to the CFF table, and it
|
|
||||||
// is simpler to parse that table for all OpenType fonts (PostScript and
|
|
||||||
// TrueType). We therefore ignore the width value here, and just remove it
|
|
||||||
// from the bottom of the stack.
|
|
||||||
copy(p.stack.a[:p.stack.top-1], p.stack.a[1:p.stack.top])
|
|
||||||
p.stack.top--
|
|
||||||
}
|
|
||||||
|
|
||||||
func t2CStem(p *psInterpreter) error {
|
|
||||||
t2CReadWidth(p, 2)
|
|
||||||
if p.stack.top%2 != 0 {
|
|
||||||
return errInvalidCFFTable
|
|
||||||
}
|
|
||||||
// We update the number of hintBits need to parse hintmask and cntrmask
|
|
||||||
// instructions, but this Type 2 Charstring implementation otherwise
|
|
||||||
// ignores the stem hints.
|
|
||||||
p.type2Charstrings.hintBits += p.stack.top / 2
|
|
||||||
if p.type2Charstrings.hintBits > maxHintBits {
|
|
||||||
return errUnsupportedNumberOfHints
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func t2CMask(p *psInterpreter) error {
|
|
||||||
hintBytes := (p.type2Charstrings.hintBits + 7) / 8
|
|
||||||
t2CReadWidth(p, hintBytes)
|
|
||||||
if len(p.instructions) < int(hintBytes) {
|
|
||||||
return errInvalidCFFTable
|
|
||||||
}
|
|
||||||
p.instructions = p.instructions[hintBytes:]
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func t2CAppendMoveto(p *psInterpreter) {
|
|
||||||
p.type2Charstrings.segments = append(p.type2Charstrings.segments, Segment{
|
|
||||||
Op: SegmentOpMoveTo,
|
|
||||||
Args: [6]fixed.Int26_6{
|
|
||||||
0: fixed.Int26_6(p.type2Charstrings.x) << 6,
|
|
||||||
1: fixed.Int26_6(p.type2Charstrings.y) << 6,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func t2CAppendLineto(p *psInterpreter) {
|
|
||||||
p.type2Charstrings.segments = append(p.type2Charstrings.segments, Segment{
|
|
||||||
Op: SegmentOpLineTo,
|
|
||||||
Args: [6]fixed.Int26_6{
|
|
||||||
0: fixed.Int26_6(p.type2Charstrings.x) << 6,
|
|
||||||
1: fixed.Int26_6(p.type2Charstrings.y) << 6,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func t2CAppendCubeto(p *psInterpreter, dxa, dya, dxb, dyb, dxc, dyc int32) {
|
|
||||||
p.type2Charstrings.x += dxa
|
|
||||||
p.type2Charstrings.y += dya
|
|
||||||
xa := p.type2Charstrings.x
|
|
||||||
ya := p.type2Charstrings.y
|
|
||||||
p.type2Charstrings.x += dxb
|
|
||||||
p.type2Charstrings.y += dyb
|
|
||||||
xb := p.type2Charstrings.x
|
|
||||||
yb := p.type2Charstrings.y
|
|
||||||
p.type2Charstrings.x += dxc
|
|
||||||
p.type2Charstrings.y += dyc
|
|
||||||
xc := p.type2Charstrings.x
|
|
||||||
yc := p.type2Charstrings.y
|
|
||||||
p.type2Charstrings.segments = append(p.type2Charstrings.segments, Segment{
|
|
||||||
Op: SegmentOpCubeTo,
|
|
||||||
Args: [6]fixed.Int26_6{
|
|
||||||
0: fixed.Int26_6(xa) << 6,
|
|
||||||
1: fixed.Int26_6(ya) << 6,
|
|
||||||
2: fixed.Int26_6(xb) << 6,
|
|
||||||
3: fixed.Int26_6(yb) << 6,
|
|
||||||
4: fixed.Int26_6(xc) << 6,
|
|
||||||
5: fixed.Int26_6(yc) << 6,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func t2CHmoveto(p *psInterpreter) error {
|
|
||||||
t2CReadWidth(p, 1)
|
|
||||||
if p.stack.top < 1 {
|
|
||||||
return errInvalidCFFTable
|
|
||||||
}
|
|
||||||
for i := int32(0); i < p.stack.top; i++ {
|
|
||||||
p.type2Charstrings.x += p.stack.a[i]
|
|
||||||
}
|
|
||||||
t2CAppendMoveto(p)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func t2CVmoveto(p *psInterpreter) error {
|
|
||||||
t2CReadWidth(p, 1)
|
|
||||||
if p.stack.top < 1 {
|
|
||||||
return errInvalidCFFTable
|
|
||||||
}
|
|
||||||
for i := int32(0); i < p.stack.top; i++ {
|
|
||||||
p.type2Charstrings.y += p.stack.a[i]
|
|
||||||
}
|
|
||||||
t2CAppendMoveto(p)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func t2CRmoveto(p *psInterpreter) error {
|
|
||||||
t2CReadWidth(p, 2)
|
|
||||||
if p.stack.top < 2 || p.stack.top%2 != 0 {
|
|
||||||
return errInvalidCFFTable
|
|
||||||
}
|
|
||||||
for i := int32(0); i < p.stack.top; i += 2 {
|
|
||||||
p.type2Charstrings.x += p.stack.a[i+0]
|
|
||||||
p.type2Charstrings.y += p.stack.a[i+1]
|
|
||||||
}
|
|
||||||
t2CAppendMoveto(p)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func t2CHlineto(p *psInterpreter) error { return t2CLineto(p, false) }
|
|
||||||
func t2CVlineto(p *psInterpreter) error { return t2CLineto(p, true) }
|
|
||||||
|
|
||||||
func t2CLineto(p *psInterpreter, vertical bool) error {
|
|
||||||
if !p.type2Charstrings.seenWidth {
|
|
||||||
return errInvalidCFFTable
|
|
||||||
}
|
|
||||||
if p.stack.top < 1 {
|
|
||||||
return errInvalidCFFTable
|
|
||||||
}
|
|
||||||
for i := int32(0); i < p.stack.top; i, vertical = i+1, !vertical {
|
|
||||||
if vertical {
|
|
||||||
p.type2Charstrings.y += p.stack.a[i]
|
|
||||||
} else {
|
|
||||||
p.type2Charstrings.x += p.stack.a[i]
|
|
||||||
}
|
|
||||||
t2CAppendLineto(p)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func t2CRlineto(p *psInterpreter) error {
|
|
||||||
if !p.type2Charstrings.seenWidth {
|
|
||||||
return errInvalidCFFTable
|
|
||||||
}
|
|
||||||
if p.stack.top < 2 || p.stack.top%2 != 0 {
|
|
||||||
return errInvalidCFFTable
|
|
||||||
}
|
|
||||||
for i := int32(0); i < p.stack.top; i += 2 {
|
|
||||||
p.type2Charstrings.x += p.stack.a[i+0]
|
|
||||||
p.type2Charstrings.y += p.stack.a[i+1]
|
|
||||||
t2CAppendLineto(p)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// As per 5177.Type2.pdf section 4.1 "Path Construction Operators",
|
|
||||||
//
|
|
||||||
// hvcurveto is one of:
|
|
||||||
// - dx1 dx2 dy2 dy3 {dya dxb dyb dxc dxd dxe dye dyf}* dxf?
|
|
||||||
// - {dxa dxb dyb dyc dyd dxe dye dxf}+ dyf?
|
|
||||||
//
|
|
||||||
// vhcurveto is one of:
|
|
||||||
// - dy1 dx2 dy2 dx3 {dxa dxb dyb dyc dyd dxe dye dxf}* dyf?
|
|
||||||
// - {dya dxb dyb dxc dxd dxe dye dyf}+ dxf?
|
|
||||||
|
|
||||||
func t2CHvcurveto(p *psInterpreter) error { return t2CCurveto(p, false) }
|
|
||||||
func t2CVhcurveto(p *psInterpreter) error { return t2CCurveto(p, true) }
|
|
||||||
|
|
||||||
func t2CCurveto(p *psInterpreter, vertical bool) error {
|
|
||||||
if !p.type2Charstrings.seenWidth || p.stack.top < 4 {
|
|
||||||
return errInvalidCFFTable
|
|
||||||
}
|
|
||||||
for i := int32(0); i != p.stack.top; vertical = !vertical {
|
|
||||||
if vertical {
|
|
||||||
i = t2CVcurveto(p, i)
|
|
||||||
} else {
|
|
||||||
i = t2CHcurveto(p, i)
|
|
||||||
}
|
|
||||||
if i < 0 {
|
|
||||||
return errInvalidCFFTable
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func t2CHcurveto(p *psInterpreter, i int32) (j int32) {
|
|
||||||
if i+4 > p.stack.top {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
dxa := p.stack.a[i+0]
|
|
||||||
dxb := p.stack.a[i+1]
|
|
||||||
dyb := p.stack.a[i+2]
|
|
||||||
dyc := p.stack.a[i+3]
|
|
||||||
dxc := int32(0)
|
|
||||||
i += 4
|
|
||||||
if i+1 == p.stack.top {
|
|
||||||
dxc = p.stack.a[i]
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
t2CAppendCubeto(p, dxa, 0, dxb, dyb, dxc, dyc)
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
func t2CVcurveto(p *psInterpreter, i int32) (j int32) {
|
|
||||||
if i+4 > p.stack.top {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
dya := p.stack.a[i+0]
|
|
||||||
dxb := p.stack.a[i+1]
|
|
||||||
dyb := p.stack.a[i+2]
|
|
||||||
dxc := p.stack.a[i+3]
|
|
||||||
dyc := int32(0)
|
|
||||||
i += 4
|
|
||||||
if i+1 == p.stack.top {
|
|
||||||
dyc = p.stack.a[i]
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
t2CAppendCubeto(p, 0, dya, dxb, dyb, dxc, dyc)
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
func t2CRrcurveto(p *psInterpreter) error {
|
|
||||||
if !p.type2Charstrings.seenWidth || p.stack.top < 6 || p.stack.top%6 != 0 {
|
|
||||||
return errInvalidCFFTable
|
|
||||||
}
|
|
||||||
for i := int32(0); i != p.stack.top; i += 6 {
|
|
||||||
t2CAppendCubeto(p,
|
|
||||||
p.stack.a[i+0],
|
|
||||||
p.stack.a[i+1],
|
|
||||||
p.stack.a[i+2],
|
|
||||||
p.stack.a[i+3],
|
|
||||||
p.stack.a[i+4],
|
|
||||||
p.stack.a[i+5],
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func t2CEndchar(p *psInterpreter) error {
|
|
||||||
t2CReadWidth(p, 0)
|
|
||||||
if p.stack.top != 0 || len(p.instructions) != 0 {
|
|
||||||
if p.stack.top == 4 {
|
|
||||||
// TODO: process the implicit "seac" command as per 5177.Type2.pdf
|
|
||||||
// Appendix C "Compatibility and Deprecated Operators".
|
|
||||||
return errUnsupportedType2Charstring
|
|
||||||
}
|
|
||||||
return errInvalidCFFTable
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,719 +0,0 @@
|
||||||
// Copyright 2016 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 sfnt implements a decoder for SFNT font file formats, including
|
|
||||||
// TrueType and OpenType.
|
|
||||||
package sfnt // import "golang.org/x/image/font/sfnt"
|
|
||||||
|
|
||||||
// This implementation was written primarily to the
|
|
||||||
// https://www.microsoft.com/en-us/Typography/OpenTypeSpecification.aspx
|
|
||||||
// specification. Additional documentation is at
|
|
||||||
// http://developer.apple.com/fonts/TTRefMan/
|
|
||||||
//
|
|
||||||
// The pyftinspect tool from https://github.com/fonttools/fonttools is useful
|
|
||||||
// for inspecting SFNT fonts.
|
|
||||||
//
|
|
||||||
// The ttfdump tool is also useful. For example:
|
|
||||||
// ttfdump -t cmap ../testdata/CFFTest.otf dump.txt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"golang.org/x/image/math/fixed"
|
|
||||||
"golang.org/x/text/encoding/charmap"
|
|
||||||
)
|
|
||||||
|
|
||||||
// These constants are not part of the specifications, but are limitations used
|
|
||||||
// by this implementation.
|
|
||||||
const (
|
|
||||||
maxCmapSegments = 1024
|
|
||||||
maxGlyphDataLength = 64 * 1024
|
|
||||||
maxHintBits = 256
|
|
||||||
maxNumTables = 256
|
|
||||||
maxRealNumberStrLen = 64 // Maximum length in bytes of the "-123.456E-7" representation.
|
|
||||||
|
|
||||||
// (maxTableOffset + maxTableLength) will not overflow an int32.
|
|
||||||
maxTableLength = 1 << 29
|
|
||||||
maxTableOffset = 1 << 29
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrNotFound indicates that the requested value was not found.
|
|
||||||
ErrNotFound = errors.New("sfnt: not found")
|
|
||||||
|
|
||||||
errInvalidBounds = errors.New("sfnt: invalid bounds")
|
|
||||||
errInvalidCFFTable = errors.New("sfnt: invalid CFF table")
|
|
||||||
errInvalidCmapTable = errors.New("sfnt: invalid cmap table")
|
|
||||||
errInvalidGlyphData = errors.New("sfnt: invalid glyph data")
|
|
||||||
errInvalidHeadTable = errors.New("sfnt: invalid head table")
|
|
||||||
errInvalidLocaTable = errors.New("sfnt: invalid loca table")
|
|
||||||
errInvalidLocationData = errors.New("sfnt: invalid location data")
|
|
||||||
errInvalidMaxpTable = errors.New("sfnt: invalid maxp table")
|
|
||||||
errInvalidNameTable = errors.New("sfnt: invalid name table")
|
|
||||||
errInvalidSourceData = errors.New("sfnt: invalid source data")
|
|
||||||
errInvalidTableOffset = errors.New("sfnt: invalid table offset")
|
|
||||||
errInvalidTableTagOrder = errors.New("sfnt: invalid table tag order")
|
|
||||||
errInvalidUCS2String = errors.New("sfnt: invalid UCS-2 string")
|
|
||||||
errInvalidVersion = errors.New("sfnt: invalid version")
|
|
||||||
|
|
||||||
errUnsupportedCFFVersion = errors.New("sfnt: unsupported CFF version")
|
|
||||||
errUnsupportedCmapEncodings = errors.New("sfnt: unsupported cmap encodings")
|
|
||||||
errUnsupportedCmapFormat = errors.New("sfnt: unsupported cmap format")
|
|
||||||
errUnsupportedCompoundGlyph = errors.New("sfnt: unsupported compound glyph")
|
|
||||||
errUnsupportedGlyphDataLength = errors.New("sfnt: unsupported glyph data length")
|
|
||||||
errUnsupportedRealNumberEncoding = errors.New("sfnt: unsupported real number encoding")
|
|
||||||
errUnsupportedNumberOfCmapSegments = errors.New("sfnt: unsupported number of cmap segments")
|
|
||||||
errUnsupportedNumberOfHints = errors.New("sfnt: unsupported number of hints")
|
|
||||||
errUnsupportedNumberOfTables = errors.New("sfnt: unsupported number of tables")
|
|
||||||
errUnsupportedPlatformEncoding = errors.New("sfnt: unsupported platform encoding")
|
|
||||||
errUnsupportedTableOffsetLength = errors.New("sfnt: unsupported table offset or length")
|
|
||||||
errUnsupportedType2Charstring = errors.New("sfnt: unsupported Type 2 Charstring")
|
|
||||||
)
|
|
||||||
|
|
||||||
// GlyphIndex is a glyph index in a Font.
|
|
||||||
type GlyphIndex uint16
|
|
||||||
|
|
||||||
// NameID identifies a name table entry.
|
|
||||||
//
|
|
||||||
// See the "Name IDs" section of
|
|
||||||
// https://www.microsoft.com/typography/otspec/name.htm
|
|
||||||
type NameID uint16
|
|
||||||
|
|
||||||
const (
|
|
||||||
NameIDCopyright NameID = 0
|
|
||||||
NameIDFamily = 1
|
|
||||||
NameIDSubfamily = 2
|
|
||||||
NameIDUniqueIdentifier = 3
|
|
||||||
NameIDFull = 4
|
|
||||||
NameIDVersion = 5
|
|
||||||
NameIDPostScript = 6
|
|
||||||
NameIDTrademark = 7
|
|
||||||
NameIDManufacturer = 8
|
|
||||||
NameIDDesigner = 9
|
|
||||||
NameIDDescription = 10
|
|
||||||
NameIDVendorURL = 11
|
|
||||||
NameIDDesignerURL = 12
|
|
||||||
NameIDLicense = 13
|
|
||||||
NameIDLicenseURL = 14
|
|
||||||
NameIDTypographicFamily = 16
|
|
||||||
NameIDTypographicSubfamily = 17
|
|
||||||
NameIDCompatibleFull = 18
|
|
||||||
NameIDSampleText = 19
|
|
||||||
NameIDPostScriptCID = 20
|
|
||||||
NameIDWWSFamily = 21
|
|
||||||
NameIDWWSSubfamily = 22
|
|
||||||
NameIDLightBackgroundPalette = 23
|
|
||||||
NameIDDarkBackgroundPalette = 24
|
|
||||||
NameIDVariationsPostScriptPrefix = 25
|
|
||||||
)
|
|
||||||
|
|
||||||
// Units are an integral number of abstract, scalable "font units". The em
|
|
||||||
// square is typically 1000 or 2048 "font units". This would map to a certain
|
|
||||||
// number (e.g. 30 pixels) of physical pixels, depending on things like the
|
|
||||||
// display resolution (DPI) and font size (e.g. a 12 point font).
|
|
||||||
type Units int32
|
|
||||||
|
|
||||||
func u16(b []byte) uint16 {
|
|
||||||
_ = b[1] // Bounds check hint to compiler.
|
|
||||||
return uint16(b[0])<<8 | uint16(b[1])<<0
|
|
||||||
}
|
|
||||||
|
|
||||||
func u32(b []byte) uint32 {
|
|
||||||
_ = b[3] // Bounds check hint to compiler.
|
|
||||||
return uint32(b[0])<<24 | uint32(b[1])<<16 | uint32(b[2])<<8 | uint32(b[3])<<0
|
|
||||||
}
|
|
||||||
|
|
||||||
// source is a source of byte data. Conceptually, it is like an io.ReaderAt,
|
|
||||||
// except that a common source of SFNT font data is in-memory instead of
|
|
||||||
// on-disk: a []byte containing the entire data, either as a global variable
|
|
||||||
// (e.g. "goregular.TTF") or the result of an ioutil.ReadFile call. In such
|
|
||||||
// cases, as an optimization, we skip the io.Reader / io.ReaderAt model of
|
|
||||||
// copying from the source to a caller-supplied buffer, and instead provide
|
|
||||||
// direct access to the underlying []byte data.
|
|
||||||
type source struct {
|
|
||||||
b []byte
|
|
||||||
r io.ReaderAt
|
|
||||||
|
|
||||||
// TODO: add a caching layer, if we're using the io.ReaderAt? Note that
|
|
||||||
// this might make a source no longer safe to use concurrently.
|
|
||||||
}
|
|
||||||
|
|
||||||
// valid returns whether exactly one of s.b and s.r is nil.
|
|
||||||
func (s *source) valid() bool {
|
|
||||||
return (s.b == nil) != (s.r == nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// viewBufferWritable returns whether the []byte returned by source.view can be
|
|
||||||
// written to by the caller, including by passing it to the same method
|
|
||||||
// (source.view) on other receivers (i.e. different sources).
|
|
||||||
//
|
|
||||||
// In other words, it returns whether the source's underlying data is an
|
|
||||||
// io.ReaderAt, not a []byte.
|
|
||||||
func (s *source) viewBufferWritable() bool {
|
|
||||||
return s.b == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// view returns the length bytes at the given offset. buf is an optional
|
|
||||||
// scratch buffer to reduce allocations when calling view multiple times. A nil
|
|
||||||
// buf is valid. The []byte returned may be a sub-slice of buf[:cap(buf)], or
|
|
||||||
// it may be an unrelated slice. In any case, the caller should not modify the
|
|
||||||
// contents of the returned []byte, other than passing that []byte back to this
|
|
||||||
// method on the same source s.
|
|
||||||
func (s *source) view(buf []byte, offset, length int) ([]byte, error) {
|
|
||||||
if 0 > offset || offset > offset+length {
|
|
||||||
return nil, errInvalidBounds
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try reading from the []byte.
|
|
||||||
if s.b != nil {
|
|
||||||
if offset+length > len(s.b) {
|
|
||||||
return nil, errInvalidBounds
|
|
||||||
}
|
|
||||||
return s.b[offset : offset+length], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read from the io.ReaderAt.
|
|
||||||
if length <= cap(buf) {
|
|
||||||
buf = buf[:length]
|
|
||||||
} else {
|
|
||||||
// Round length up to the nearest KiB. The slack can lead to fewer
|
|
||||||
// allocations if the buffer is re-used for multiple source.view calls.
|
|
||||||
n := length
|
|
||||||
n += 1023
|
|
||||||
n &^= 1023
|
|
||||||
buf = make([]byte, length, n)
|
|
||||||
}
|
|
||||||
if n, err := s.r.ReadAt(buf, int64(offset)); n != length {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return buf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// u16 returns the uint16 in the table t at the relative offset i.
|
|
||||||
//
|
|
||||||
// buf is an optional scratch buffer as per the source.view method.
|
|
||||||
func (s *source) u16(buf []byte, t table, i int) (uint16, error) {
|
|
||||||
if i < 0 || uint(t.length) < uint(i+2) {
|
|
||||||
return 0, errInvalidBounds
|
|
||||||
}
|
|
||||||
buf, err := s.view(buf, int(t.offset)+i, 2)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return u16(buf), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// table is a section of the font data.
|
|
||||||
type table struct {
|
|
||||||
offset, length uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse parses an SFNT font from a []byte data source.
|
|
||||||
func Parse(src []byte) (*Font, error) {
|
|
||||||
f := &Font{src: source{b: src}}
|
|
||||||
if err := f.initialize(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return f, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseReaderAt parses an SFNT font from an io.ReaderAt data source.
|
|
||||||
func ParseReaderAt(src io.ReaderAt) (*Font, error) {
|
|
||||||
f := &Font{src: source{r: src}}
|
|
||||||
if err := f.initialize(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return f, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Font is an SFNT font.
|
|
||||||
//
|
|
||||||
// Many of its methods take a *Buffer argument, as re-using buffers can reduce
|
|
||||||
// the total memory allocation of repeated Font method calls, such as measuring
|
|
||||||
// and rasterizing every unique glyph in a string of text. If efficiency is not
|
|
||||||
// a concern, passing a nil *Buffer is valid, and implies using a temporary
|
|
||||||
// buffer for a single call.
|
|
||||||
//
|
|
||||||
// It is valid to re-use a *Buffer with multiple Font method calls, even with
|
|
||||||
// different *Font receivers, as long as they are not concurrent calls.
|
|
||||||
//
|
|
||||||
// All of the Font methods are safe to call concurrently, as long as each call
|
|
||||||
// has a different *Buffer (or nil).
|
|
||||||
//
|
|
||||||
// The Font methods that don't take a *Buffer argument are always safe to call
|
|
||||||
// concurrently.
|
|
||||||
type Font struct {
|
|
||||||
src source
|
|
||||||
|
|
||||||
// https://www.microsoft.com/typography/otspec/otff.htm#otttables
|
|
||||||
// "Required Tables".
|
|
||||||
cmap table
|
|
||||||
head table
|
|
||||||
hhea table
|
|
||||||
hmtx table
|
|
||||||
maxp table
|
|
||||||
name table
|
|
||||||
os2 table
|
|
||||||
post table
|
|
||||||
|
|
||||||
// https://www.microsoft.com/typography/otspec/otff.htm#otttables
|
|
||||||
// "Tables Related to TrueType Outlines".
|
|
||||||
//
|
|
||||||
// This implementation does not support hinting, so it does not read the
|
|
||||||
// cvt, fpgm gasp or prep tables.
|
|
||||||
glyf table
|
|
||||||
loca table
|
|
||||||
|
|
||||||
// https://www.microsoft.com/typography/otspec/otff.htm#otttables
|
|
||||||
// "Tables Related to PostScript Outlines".
|
|
||||||
//
|
|
||||||
// TODO: cff2, vorg?
|
|
||||||
cff table
|
|
||||||
|
|
||||||
// https://www.microsoft.com/typography/otspec/otff.htm#otttables
|
|
||||||
// "Advanced Typographic Tables".
|
|
||||||
//
|
|
||||||
// TODO: base, gdef, gpos, gsub, jstf, math?
|
|
||||||
|
|
||||||
// https://www.microsoft.com/typography/otspec/otff.htm#otttables
|
|
||||||
// "Other OpenType Tables".
|
|
||||||
//
|
|
||||||
// TODO: hdmx, kern, vmtx? Others?
|
|
||||||
|
|
||||||
cached struct {
|
|
||||||
glyphIndex func(f *Font, b *Buffer, r rune) (GlyphIndex, error)
|
|
||||||
indexToLocFormat bool // false means short, true means long.
|
|
||||||
isPostScript bool
|
|
||||||
unitsPerEm Units
|
|
||||||
|
|
||||||
// The glyph data for the glyph index i is in
|
|
||||||
// src[locations[i+0]:locations[i+1]].
|
|
||||||
locations []uint32
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NumGlyphs returns the number of glyphs in f.
|
|
||||||
func (f *Font) NumGlyphs() int { return len(f.cached.locations) - 1 }
|
|
||||||
|
|
||||||
// UnitsPerEm returns the number of units per em for f.
|
|
||||||
func (f *Font) UnitsPerEm() Units { return f.cached.unitsPerEm }
|
|
||||||
|
|
||||||
func (f *Font) initialize() error {
|
|
||||||
if !f.src.valid() {
|
|
||||||
return errInvalidSourceData
|
|
||||||
}
|
|
||||||
buf, err := f.initializeTables(nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
buf, err = f.parseHead(buf)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
buf, err = f.parseMaxp(buf)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
buf, err = f.parseCmap(buf)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Font) initializeTables(buf []byte) ([]byte, error) {
|
|
||||||
// https://www.microsoft.com/typography/otspec/otff.htm "Organization of an
|
|
||||||
// OpenType Font" says that "The OpenType font starts with the Offset
|
|
||||||
// Table", which is 12 bytes.
|
|
||||||
buf, err := f.src.view(buf, 0, 12)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
switch u32(buf) {
|
|
||||||
default:
|
|
||||||
return nil, errInvalidVersion
|
|
||||||
case 0x00010000:
|
|
||||||
// No-op.
|
|
||||||
case 0x4f54544f: // "OTTO".
|
|
||||||
f.cached.isPostScript = true
|
|
||||||
}
|
|
||||||
numTables := int(u16(buf[4:]))
|
|
||||||
if numTables > maxNumTables {
|
|
||||||
return nil, errUnsupportedNumberOfTables
|
|
||||||
}
|
|
||||||
|
|
||||||
// "The Offset Table is followed immediately by the Table Record entries...
|
|
||||||
// sorted in ascending order by tag", 16 bytes each.
|
|
||||||
buf, err = f.src.view(buf, 12, 16*numTables)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for b, first, prevTag := buf, true, uint32(0); len(b) > 0; b = b[16:] {
|
|
||||||
tag := u32(b)
|
|
||||||
if first {
|
|
||||||
first = false
|
|
||||||
} else if tag <= prevTag {
|
|
||||||
return nil, errInvalidTableTagOrder
|
|
||||||
}
|
|
||||||
prevTag = tag
|
|
||||||
|
|
||||||
o, n := u32(b[8:12]), u32(b[12:16])
|
|
||||||
if o > maxTableOffset || n > maxTableLength {
|
|
||||||
return nil, errUnsupportedTableOffsetLength
|
|
||||||
}
|
|
||||||
// We ignore the checksums, but "all tables must begin on four byte
|
|
||||||
// boundries [sic]".
|
|
||||||
if o&3 != 0 {
|
|
||||||
return nil, errInvalidTableOffset
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match the 4-byte tag as a uint32. For example, "OS/2" is 0x4f532f32.
|
|
||||||
switch tag {
|
|
||||||
case 0x43464620:
|
|
||||||
f.cff = table{o, n}
|
|
||||||
case 0x4f532f32:
|
|
||||||
f.os2 = table{o, n}
|
|
||||||
case 0x636d6170:
|
|
||||||
f.cmap = table{o, n}
|
|
||||||
case 0x676c7966:
|
|
||||||
f.glyf = table{o, n}
|
|
||||||
case 0x68656164:
|
|
||||||
f.head = table{o, n}
|
|
||||||
case 0x68686561:
|
|
||||||
f.hhea = table{o, n}
|
|
||||||
case 0x686d7478:
|
|
||||||
f.hmtx = table{o, n}
|
|
||||||
case 0x6c6f6361:
|
|
||||||
f.loca = table{o, n}
|
|
||||||
case 0x6d617870:
|
|
||||||
f.maxp = table{o, n}
|
|
||||||
case 0x6e616d65:
|
|
||||||
f.name = table{o, n}
|
|
||||||
case 0x706f7374:
|
|
||||||
f.post = table{o, n}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return buf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Font) parseCmap(buf []byte) ([]byte, error) {
|
|
||||||
// https://www.microsoft.com/typography/OTSPEC/cmap.htm
|
|
||||||
|
|
||||||
const headerSize, entrySize = 4, 8
|
|
||||||
if f.cmap.length < headerSize {
|
|
||||||
return nil, errInvalidCmapTable
|
|
||||||
}
|
|
||||||
u, err := f.src.u16(buf, f.cmap, 2)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
numSubtables := int(u)
|
|
||||||
if f.cmap.length < headerSize+entrySize*uint32(numSubtables) {
|
|
||||||
return nil, errInvalidCmapTable
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
bestWidth int
|
|
||||||
bestOffset uint32
|
|
||||||
bestLength uint32
|
|
||||||
bestFormat uint16
|
|
||||||
)
|
|
||||||
|
|
||||||
// Scan all of the subtables, picking the widest supported one. See the
|
|
||||||
// platformEncodingWidth comment for more discussion of width.
|
|
||||||
for i := 0; i < numSubtables; i++ {
|
|
||||||
buf, err = f.src.view(buf, int(f.cmap.offset)+headerSize+entrySize*i, entrySize)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
pid := u16(buf)
|
|
||||||
psid := u16(buf[2:])
|
|
||||||
width := platformEncodingWidth(pid, psid)
|
|
||||||
if width <= bestWidth {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
offset := u32(buf[4:])
|
|
||||||
|
|
||||||
if offset > f.cmap.length-4 {
|
|
||||||
return nil, errInvalidCmapTable
|
|
||||||
}
|
|
||||||
buf, err = f.src.view(buf, int(f.cmap.offset+offset), 4)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
format := u16(buf)
|
|
||||||
if !supportedCmapFormat(format, pid, psid) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
length := uint32(u16(buf[2:]))
|
|
||||||
|
|
||||||
bestWidth = width
|
|
||||||
bestOffset = offset
|
|
||||||
bestLength = length
|
|
||||||
bestFormat = format
|
|
||||||
}
|
|
||||||
|
|
||||||
if bestWidth == 0 {
|
|
||||||
return nil, errUnsupportedCmapEncodings
|
|
||||||
}
|
|
||||||
return f.makeCachedGlyphIndex(buf, bestOffset, bestLength, bestFormat)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Font) parseHead(buf []byte) ([]byte, error) {
|
|
||||||
// https://www.microsoft.com/typography/otspec/head.htm
|
|
||||||
|
|
||||||
if f.head.length != 54 {
|
|
||||||
return nil, errInvalidHeadTable
|
|
||||||
}
|
|
||||||
u, err := f.src.u16(buf, f.head, 18)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if u == 0 {
|
|
||||||
return nil, errInvalidHeadTable
|
|
||||||
}
|
|
||||||
f.cached.unitsPerEm = Units(u)
|
|
||||||
u, err = f.src.u16(buf, f.head, 50)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
f.cached.indexToLocFormat = u != 0
|
|
||||||
return buf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Font) parseMaxp(buf []byte) ([]byte, error) {
|
|
||||||
// https://www.microsoft.com/typography/otspec/maxp.htm
|
|
||||||
|
|
||||||
if f.cached.isPostScript {
|
|
||||||
if f.maxp.length != 6 {
|
|
||||||
return nil, errInvalidMaxpTable
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if f.maxp.length != 32 {
|
|
||||||
return nil, errInvalidMaxpTable
|
|
||||||
}
|
|
||||||
}
|
|
||||||
u, err := f.src.u16(buf, f.maxp, 4)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
numGlyphs := int(u)
|
|
||||||
|
|
||||||
if f.cached.isPostScript {
|
|
||||||
p := cffParser{
|
|
||||||
src: &f.src,
|
|
||||||
base: int(f.cff.offset),
|
|
||||||
offset: int(f.cff.offset),
|
|
||||||
end: int(f.cff.offset + f.cff.length),
|
|
||||||
}
|
|
||||||
f.cached.locations, err = p.parse()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
f.cached.locations, err = parseLoca(
|
|
||||||
&f.src, f.loca, f.glyf.offset, f.cached.indexToLocFormat, numGlyphs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(f.cached.locations) != numGlyphs+1 {
|
|
||||||
return nil, errInvalidLocationData
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: API for looking up glyph variants?? For example, some fonts may
|
|
||||||
// provide both slashed and dotted zero glyphs ('0'), or regular and 'old
|
|
||||||
// style' numerals, and users can direct software to choose a variant.
|
|
||||||
|
|
||||||
// GlyphIndex returns the glyph index for the given rune.
|
|
||||||
//
|
|
||||||
// It returns (0, nil) if there is no glyph for r.
|
|
||||||
// https://www.microsoft.com/typography/OTSPEC/cmap.htm says that "Character
|
|
||||||
// codes that do not correspond to any glyph in the font should be mapped to
|
|
||||||
// glyph index 0. The glyph at this location must be a special glyph
|
|
||||||
// representing a missing character, commonly known as .notdef."
|
|
||||||
func (f *Font) GlyphIndex(b *Buffer, r rune) (GlyphIndex, error) {
|
|
||||||
return f.cached.glyphIndex(f, b, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Font) viewGlyphData(b *Buffer, x GlyphIndex) ([]byte, error) {
|
|
||||||
xx := int(x)
|
|
||||||
if f.NumGlyphs() <= xx {
|
|
||||||
return nil, ErrNotFound
|
|
||||||
}
|
|
||||||
i := f.cached.locations[xx+0]
|
|
||||||
j := f.cached.locations[xx+1]
|
|
||||||
if j-i > maxGlyphDataLength {
|
|
||||||
return nil, errUnsupportedGlyphDataLength
|
|
||||||
}
|
|
||||||
return b.view(&f.src, int(i), int(j-i))
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadGlyphOptions are the options to the Font.LoadGlyph method.
|
|
||||||
type LoadGlyphOptions struct {
|
|
||||||
// TODO: scale / transform / hinting.
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadGlyph returns the vector segments for the x'th glyph.
|
|
||||||
//
|
|
||||||
// If b is non-nil, the segments become invalid to use once b is re-used.
|
|
||||||
//
|
|
||||||
// It returns ErrNotFound if the glyph index is out of range.
|
|
||||||
func (f *Font) LoadGlyph(b *Buffer, x GlyphIndex, opts *LoadGlyphOptions) ([]Segment, error) {
|
|
||||||
if b == nil {
|
|
||||||
b = &Buffer{}
|
|
||||||
}
|
|
||||||
|
|
||||||
buf, err := f.viewGlyphData(b, x)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
b.segments = b.segments[:0]
|
|
||||||
if f.cached.isPostScript {
|
|
||||||
b.psi.type2Charstrings.initialize(b.segments)
|
|
||||||
if err := b.psi.run(psContextType2Charstring, buf); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b.segments = b.psi.type2Charstrings.segments
|
|
||||||
} else {
|
|
||||||
segments, err := appendGlyfSegments(b.segments, buf)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b.segments = segments
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: look at opts to scale / transform / hint the Buffer.segments.
|
|
||||||
|
|
||||||
return b.segments, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name returns the name value keyed by the given NameID.
|
|
||||||
//
|
|
||||||
// It returns ErrNotFound if there is no value for that key.
|
|
||||||
func (f *Font) Name(b *Buffer, id NameID) (string, error) {
|
|
||||||
if b == nil {
|
|
||||||
b = &Buffer{}
|
|
||||||
}
|
|
||||||
|
|
||||||
const headerSize, entrySize = 6, 12
|
|
||||||
if f.name.length < headerSize {
|
|
||||||
return "", errInvalidNameTable
|
|
||||||
}
|
|
||||||
buf, err := b.view(&f.src, int(f.name.offset), headerSize)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
numSubtables := u16(buf[2:])
|
|
||||||
if f.name.length < headerSize+entrySize*uint32(numSubtables) {
|
|
||||||
return "", errInvalidNameTable
|
|
||||||
}
|
|
||||||
stringOffset := u16(buf[4:])
|
|
||||||
|
|
||||||
seen := false
|
|
||||||
for i, n := 0, int(numSubtables); i < n; i++ {
|
|
||||||
buf, err := b.view(&f.src, int(f.name.offset)+headerSize+entrySize*i, entrySize)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if u16(buf[6:]) != uint16(id) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
seen = true
|
|
||||||
|
|
||||||
var stringify func([]byte) (string, error)
|
|
||||||
switch u32(buf) {
|
|
||||||
default:
|
|
||||||
continue
|
|
||||||
case pidMacintosh<<16 | psidMacintoshRoman:
|
|
||||||
stringify = stringifyMacintosh
|
|
||||||
case pidWindows<<16 | psidWindowsUCS2:
|
|
||||||
stringify = stringifyUCS2
|
|
||||||
}
|
|
||||||
|
|
||||||
nameLength := u16(buf[8:])
|
|
||||||
nameOffset := u16(buf[10:])
|
|
||||||
buf, err = b.view(&f.src, int(f.name.offset)+int(nameOffset)+int(stringOffset), int(nameLength))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return stringify(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
if seen {
|
|
||||||
return "", errUnsupportedPlatformEncoding
|
|
||||||
}
|
|
||||||
return "", ErrNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
func stringifyMacintosh(b []byte) (string, error) {
|
|
||||||
for _, c := range b {
|
|
||||||
if c >= 0x80 {
|
|
||||||
// b contains some non-ASCII bytes.
|
|
||||||
s, _ := charmap.Macintosh.NewDecoder().Bytes(b)
|
|
||||||
return string(s), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// b contains only ASCII bytes.
|
|
||||||
return string(b), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func stringifyUCS2(b []byte) (string, error) {
|
|
||||||
if len(b)&1 != 0 {
|
|
||||||
return "", errInvalidUCS2String
|
|
||||||
}
|
|
||||||
r := make([]rune, len(b)/2)
|
|
||||||
for i := range r {
|
|
||||||
r[i] = rune(u16(b))
|
|
||||||
b = b[2:]
|
|
||||||
}
|
|
||||||
return string(r), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Buffer holds re-usable buffers that can reduce the total memory allocation
|
|
||||||
// of repeated Font method calls.
|
|
||||||
//
|
|
||||||
// See the Font type's documentation comment for more details.
|
|
||||||
type Buffer struct {
|
|
||||||
// buf is a byte buffer for when a Font's source is an io.ReaderAt.
|
|
||||||
buf []byte
|
|
||||||
// segments holds glyph vector path segments.
|
|
||||||
segments []Segment
|
|
||||||
// psi is a PostScript interpreter for when the Font is an OpenType/CFF
|
|
||||||
// font.
|
|
||||||
psi psInterpreter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Buffer) view(src *source, offset, length int) ([]byte, error) {
|
|
||||||
buf, err := src.view(b.buf, offset, length)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Only update b.buf if it is safe to re-use buf.
|
|
||||||
if src.viewBufferWritable() {
|
|
||||||
b.buf = buf
|
|
||||||
}
|
|
||||||
return buf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Segment is a segment of a vector path.
|
|
||||||
type Segment struct {
|
|
||||||
Op SegmentOp
|
|
||||||
Args [6]fixed.Int26_6
|
|
||||||
}
|
|
||||||
|
|
||||||
// SegmentOp is a vector path segment's operator.
|
|
||||||
type SegmentOp uint32
|
|
||||||
|
|
||||||
const (
|
|
||||||
SegmentOpMoveTo SegmentOp = iota
|
|
||||||
SegmentOpLineTo
|
|
||||||
SegmentOpQuadTo
|
|
||||||
SegmentOpCubeTo
|
|
||||||
)
|
|
|
@ -1,340 +0,0 @@
|
||||||
// Copyright 2016 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 sfnt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io/ioutil"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"golang.org/x/image/font/gofont/goregular"
|
|
||||||
"golang.org/x/image/math/fixed"
|
|
||||||
)
|
|
||||||
|
|
||||||
func moveTo(xa, ya int) Segment {
|
|
||||||
return Segment{
|
|
||||||
Op: SegmentOpMoveTo,
|
|
||||||
Args: [6]fixed.Int26_6{
|
|
||||||
0: fixed.I(xa),
|
|
||||||
1: fixed.I(ya),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func lineTo(xa, ya int) Segment {
|
|
||||||
return Segment{
|
|
||||||
Op: SegmentOpLineTo,
|
|
||||||
Args: [6]fixed.Int26_6{
|
|
||||||
0: fixed.I(xa),
|
|
||||||
1: fixed.I(ya),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func quadTo(xa, ya, xb, yb int) Segment {
|
|
||||||
return Segment{
|
|
||||||
Op: SegmentOpQuadTo,
|
|
||||||
Args: [6]fixed.Int26_6{
|
|
||||||
0: fixed.I(xa),
|
|
||||||
1: fixed.I(ya),
|
|
||||||
2: fixed.I(xb),
|
|
||||||
3: fixed.I(yb),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func cubeTo(xa, ya, xb, yb, xc, yc int) Segment {
|
|
||||||
return Segment{
|
|
||||||
Op: SegmentOpCubeTo,
|
|
||||||
Args: [6]fixed.Int26_6{
|
|
||||||
0: fixed.I(xa),
|
|
||||||
1: fixed.I(ya),
|
|
||||||
2: fixed.I(xb),
|
|
||||||
3: fixed.I(yb),
|
|
||||||
4: fixed.I(xc),
|
|
||||||
5: fixed.I(yc),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTrueTypeParse(t *testing.T) {
|
|
||||||
f, err := Parse(goregular.TTF)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Parse: %v", err)
|
|
||||||
}
|
|
||||||
testTrueType(t, f)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTrueTypeParseReaderAt(t *testing.T) {
|
|
||||||
f, err := ParseReaderAt(bytes.NewReader(goregular.TTF))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("ParseReaderAt: %v", err)
|
|
||||||
}
|
|
||||||
testTrueType(t, f)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testTrueType(t *testing.T, f *Font) {
|
|
||||||
if got, want := f.UnitsPerEm(), Units(2048); got != want {
|
|
||||||
t.Errorf("UnitsPerEm: got %d, want %d", got, want)
|
|
||||||
}
|
|
||||||
// The exact number of glyphs in goregular.TTF can vary, and future
|
|
||||||
// versions may add more glyphs, but https://blog.golang.org/go-fonts says
|
|
||||||
// that "The WGL4 character set... [has] more than 650 characters in all.
|
|
||||||
if got, want := f.NumGlyphs(), 650; got <= want {
|
|
||||||
t.Errorf("NumGlyphs: got %d, want > %d", got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGlyphIndex(t *testing.T) {
|
|
||||||
data, err := ioutil.ReadFile(filepath.FromSlash("../testdata/CFFTest.otf"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, format := range []int{-1, 0, 4} {
|
|
||||||
testGlyphIndex(t, data, format)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func testGlyphIndex(t *testing.T, data []byte, cmapFormat int) {
|
|
||||||
if cmapFormat >= 0 {
|
|
||||||
originalSupportedCmapFormat := supportedCmapFormat
|
|
||||||
defer func() {
|
|
||||||
supportedCmapFormat = originalSupportedCmapFormat
|
|
||||||
}()
|
|
||||||
supportedCmapFormat = func(format, pid, psid uint16) bool {
|
|
||||||
return int(format) == cmapFormat && originalSupportedCmapFormat(format, pid, psid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := Parse(data)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("cmapFormat=%d: %v", cmapFormat, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
r rune
|
|
||||||
want GlyphIndex
|
|
||||||
}{
|
|
||||||
{'0', 1},
|
|
||||||
{'1', 2},
|
|
||||||
{'Q', 3},
|
|
||||||
// TODO: add the U+00E0 non-ASCII Latin-1 Supplement rune to
|
|
||||||
// CFFTest.otf and change 0 to something non-zero.
|
|
||||||
{'\u00e0', 0},
|
|
||||||
{'\u4e2d', 4},
|
|
||||||
// TODO: add a rune >= U+00010000 to CFFTest.otf?
|
|
||||||
|
|
||||||
// Glyphs that aren't present in CFFTest.otf.
|
|
||||||
{'?', 0},
|
|
||||||
{'\ufffd', 0},
|
|
||||||
{'\U0001f4a9', 0},
|
|
||||||
}
|
|
||||||
|
|
||||||
var b Buffer
|
|
||||||
for _, tc := range testCases {
|
|
||||||
want := tc.want
|
|
||||||
// cmap format 0, with the Macintosh Roman encoding, can't represent
|
|
||||||
// U+4E2D.
|
|
||||||
if cmapFormat == 0 && tc.r == '\u4e2d' {
|
|
||||||
want = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
got, err := f.GlyphIndex(&b, tc.r)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("cmapFormat=%d, r=%q: %v", cmapFormat, tc.r, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if got != want {
|
|
||||||
t.Errorf("cmapFormat=%d, r=%q: got %d, want %d", cmapFormat, tc.r, got, want)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPostScriptSegments(t *testing.T) {
|
|
||||||
// wants' vectors correspond 1-to-1 to what's in the CFFTest.sfd file,
|
|
||||||
// although OpenType/CFF and FontForge's SFD have reversed orders.
|
|
||||||
// https://fontforge.github.io/validation.html says that "All paths must be
|
|
||||||
// drawn in a consistent direction. Clockwise for external paths,
|
|
||||||
// anti-clockwise for internal paths. (Actually PostScript requires the
|
|
||||||
// exact opposite, but FontForge reverses PostScript contours when it loads
|
|
||||||
// them so that everything is consistant internally -- and reverses them
|
|
||||||
// again when it saves them, of course)."
|
|
||||||
//
|
|
||||||
// The .notdef glyph isn't explicitly in the SFD file, but for some unknown
|
|
||||||
// reason, FontForge generates it in the OpenType/CFF file.
|
|
||||||
wants := [][]Segment{{
|
|
||||||
// .notdef
|
|
||||||
// - contour #0
|
|
||||||
moveTo(50, 0),
|
|
||||||
lineTo(450, 0),
|
|
||||||
lineTo(450, 533),
|
|
||||||
lineTo(50, 533),
|
|
||||||
// - contour #1
|
|
||||||
moveTo(100, 50),
|
|
||||||
lineTo(100, 483),
|
|
||||||
lineTo(400, 483),
|
|
||||||
lineTo(400, 50),
|
|
||||||
}, {
|
|
||||||
// zero
|
|
||||||
// - contour #0
|
|
||||||
moveTo(300, 700),
|
|
||||||
cubeTo(380, 700, 420, 580, 420, 500),
|
|
||||||
cubeTo(420, 350, 390, 100, 300, 100),
|
|
||||||
cubeTo(220, 100, 180, 220, 180, 300),
|
|
||||||
cubeTo(180, 450, 210, 700, 300, 700),
|
|
||||||
// - contour #1
|
|
||||||
moveTo(300, 800),
|
|
||||||
cubeTo(200, 800, 100, 580, 100, 400),
|
|
||||||
cubeTo(100, 220, 200, 0, 300, 0),
|
|
||||||
cubeTo(400, 0, 500, 220, 500, 400),
|
|
||||||
cubeTo(500, 580, 400, 800, 300, 800),
|
|
||||||
}, {
|
|
||||||
// one
|
|
||||||
// - contour #0
|
|
||||||
moveTo(100, 0),
|
|
||||||
lineTo(300, 0),
|
|
||||||
lineTo(300, 800),
|
|
||||||
lineTo(100, 800),
|
|
||||||
}, {
|
|
||||||
// Q
|
|
||||||
// - contour #0
|
|
||||||
moveTo(657, 237),
|
|
||||||
lineTo(289, 387),
|
|
||||||
lineTo(519, 615),
|
|
||||||
// - contour #1
|
|
||||||
moveTo(792, 169),
|
|
||||||
cubeTo(867, 263, 926, 502, 791, 665),
|
|
||||||
cubeTo(645, 840, 380, 831, 228, 673),
|
|
||||||
cubeTo(71, 509, 110, 231, 242, 93),
|
|
||||||
cubeTo(369, -39, 641, 18, 722, 93),
|
|
||||||
lineTo(802, 3),
|
|
||||||
lineTo(864, 83),
|
|
||||||
}, {
|
|
||||||
// uni4E2D
|
|
||||||
// - contour #0
|
|
||||||
moveTo(141, 520),
|
|
||||||
lineTo(137, 356),
|
|
||||||
lineTo(245, 400),
|
|
||||||
lineTo(331, 26),
|
|
||||||
lineTo(355, 414),
|
|
||||||
lineTo(463, 434),
|
|
||||||
lineTo(453, 620),
|
|
||||||
lineTo(341, 592),
|
|
||||||
lineTo(331, 758),
|
|
||||||
lineTo(243, 752),
|
|
||||||
lineTo(235, 562),
|
|
||||||
// TODO: explicitly (not implicitly) close these contours?
|
|
||||||
}}
|
|
||||||
|
|
||||||
testSegments(t, "CFFTest.otf", wants)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTrueTypeSegments(t *testing.T) {
|
|
||||||
// wants' vectors correspond 1-to-1 to what's in the glyfTest.sfd file,
|
|
||||||
// although FontForge's SFD format stores quadratic Bézier curves as cubics
|
|
||||||
// with duplicated off-curve points. quadTo(bx, by, cx, cy) is stored as
|
|
||||||
// "bx by bx by cx cy".
|
|
||||||
//
|
|
||||||
// The .notdef, .null and nonmarkingreturn glyphs aren't explicitly in the
|
|
||||||
// SFD file, but for some unknown reason, FontForge generates them in the
|
|
||||||
// TrueType file.
|
|
||||||
wants := [][]Segment{{
|
|
||||||
// .notdef
|
|
||||||
// - contour #0
|
|
||||||
moveTo(68, 0),
|
|
||||||
lineTo(68, 1365),
|
|
||||||
lineTo(612, 1365),
|
|
||||||
lineTo(612, 0),
|
|
||||||
lineTo(68, 0),
|
|
||||||
// - contour #1
|
|
||||||
moveTo(136, 68),
|
|
||||||
lineTo(544, 68),
|
|
||||||
lineTo(544, 1297),
|
|
||||||
lineTo(136, 1297),
|
|
||||||
lineTo(136, 68),
|
|
||||||
}, {
|
|
||||||
// .null
|
|
||||||
// Empty glyph.
|
|
||||||
}, {
|
|
||||||
// nonmarkingreturn
|
|
||||||
// Empty glyph.
|
|
||||||
}, {
|
|
||||||
// zero
|
|
||||||
// - contour #0
|
|
||||||
moveTo(614, 1434),
|
|
||||||
quadTo(369, 1434, 369, 614),
|
|
||||||
quadTo(369, 471, 435, 338),
|
|
||||||
quadTo(502, 205, 614, 205),
|
|
||||||
quadTo(860, 205, 860, 1024),
|
|
||||||
quadTo(860, 1167, 793, 1300),
|
|
||||||
quadTo(727, 1434, 614, 1434),
|
|
||||||
// - contour #1
|
|
||||||
moveTo(614, 1638),
|
|
||||||
quadTo(1024, 1638, 1024, 819),
|
|
||||||
quadTo(1024, 0, 614, 0),
|
|
||||||
quadTo(205, 0, 205, 819),
|
|
||||||
quadTo(205, 1638, 614, 1638),
|
|
||||||
}, {
|
|
||||||
// one
|
|
||||||
// - contour #0
|
|
||||||
moveTo(205, 0),
|
|
||||||
lineTo(205, 1638),
|
|
||||||
lineTo(614, 1638),
|
|
||||||
lineTo(614, 0),
|
|
||||||
lineTo(205, 0),
|
|
||||||
}}
|
|
||||||
|
|
||||||
testSegments(t, "glyfTest.ttf", wants)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testSegments(t *testing.T, filename string, wants [][]Segment) {
|
|
||||||
data, err := ioutil.ReadFile(filepath.FromSlash("../testdata/" + filename))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
f, err := Parse(data)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ng := f.NumGlyphs(); ng != len(wants) {
|
|
||||||
t.Fatalf("NumGlyphs: got %d, want %d", ng, len(wants))
|
|
||||||
}
|
|
||||||
var b Buffer
|
|
||||||
loop:
|
|
||||||
for i, want := range wants {
|
|
||||||
got, err := f.LoadGlyph(&b, GlyphIndex(i), nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("i=%d: LoadGlyph: %v", i, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if len(got) != len(want) {
|
|
||||||
t.Errorf("i=%d: got %d elements, want %d\noverall:\ngot %v\nwant %v",
|
|
||||||
i, len(got), len(want), got, want)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for j, g := range got {
|
|
||||||
if w := want[j]; g != w {
|
|
||||||
t.Errorf("i=%d: element %d:\ngot %v\nwant %v\noverall:\ngot %v\nwant %v",
|
|
||||||
i, j, g, w, got, want)
|
|
||||||
continue loop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, err := f.LoadGlyph(nil, 0xffff, nil); err != ErrNotFound {
|
|
||||||
t.Errorf("LoadGlyph(..., 0xffff, ...):\ngot %v\nwant %v", err, ErrNotFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
name, err := f.Name(nil, NameIDFamily)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Name: %v", err)
|
|
||||||
} else if want := filename[:len(filename)-len(".ttf")]; name != want {
|
|
||||||
t.Errorf("Name:\ngot %q\nwant %q", name, want)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,489 +0,0 @@
|
||||||
// Copyright 2017 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 sfnt
|
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/image/math/fixed"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Flags for simple (non-compound) glyphs.
|
|
||||||
//
|
|
||||||
// See https://www.microsoft.com/typography/OTSPEC/glyf.htm
|
|
||||||
const (
|
|
||||||
flagOnCurve = 1 << 0 // 0x0001
|
|
||||||
flagXShortVector = 1 << 1 // 0x0002
|
|
||||||
flagYShortVector = 1 << 2 // 0x0004
|
|
||||||
flagRepeat = 1 << 3 // 0x0008
|
|
||||||
|
|
||||||
// The same flag bits are overloaded to have two meanings, dependent on the
|
|
||||||
// value of the flag{X,Y}ShortVector bits.
|
|
||||||
flagPositiveXShortVector = 1 << 4 // 0x0010
|
|
||||||
flagThisXIsSame = 1 << 4 // 0x0010
|
|
||||||
flagPositiveYShortVector = 1 << 5 // 0x0020
|
|
||||||
flagThisYIsSame = 1 << 5 // 0x0020
|
|
||||||
)
|
|
||||||
|
|
||||||
// Flags for compound glyphs.
|
|
||||||
//
|
|
||||||
// See https://www.microsoft.com/typography/OTSPEC/glyf.htm
|
|
||||||
const (
|
|
||||||
flagArg1And2AreWords = 1 << 0 // 0x0001
|
|
||||||
flagArgsAreXYValues = 1 << 1 // 0x0002
|
|
||||||
flagRoundXYToGrid = 1 << 2 // 0x0004
|
|
||||||
flagWeHaveAScale = 1 << 3 // 0x0008
|
|
||||||
flagReserved4 = 1 << 4 // 0x0010
|
|
||||||
flagMoreComponents = 1 << 5 // 0x0020
|
|
||||||
flagWeHaveAnXAndYScale = 1 << 6 // 0x0040
|
|
||||||
flagWeHaveATwoByTwo = 1 << 7 // 0x0080
|
|
||||||
flagWeHaveInstructions = 1 << 8 // 0x0100
|
|
||||||
flagUseMyMetrics = 1 << 9 // 0x0200
|
|
||||||
flagOverlapCompound = 1 << 10 // 0x0400
|
|
||||||
flagScaledComponentOffset = 1 << 11 // 0x0800
|
|
||||||
flagUnscaledComponentOffset = 1 << 12 // 0x1000
|
|
||||||
)
|
|
||||||
|
|
||||||
func midPoint(p, q fixed.Point26_6) fixed.Point26_6 {
|
|
||||||
return fixed.Point26_6{
|
|
||||||
X: (p.X + q.X) / 2,
|
|
||||||
Y: (p.Y + q.Y) / 2,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseLoca(src *source, loca table, glyfOffset uint32, indexToLocFormat bool, numGlyphs int) (locations []uint32, err error) {
|
|
||||||
if indexToLocFormat {
|
|
||||||
if loca.length != 4*uint32(numGlyphs+1) {
|
|
||||||
return nil, errInvalidLocaTable
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if loca.length != 2*uint32(numGlyphs+1) {
|
|
||||||
return nil, errInvalidLocaTable
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
locations = make([]uint32, numGlyphs+1)
|
|
||||||
buf, err := src.view(nil, int(loca.offset), int(loca.length))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if indexToLocFormat {
|
|
||||||
for i := range locations {
|
|
||||||
locations[i] = 1*uint32(u32(buf[4*i:])) + glyfOffset
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for i := range locations {
|
|
||||||
locations[i] = 2*uint32(u16(buf[2*i:])) + glyfOffset
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return locations, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://www.microsoft.com/typography/OTSPEC/glyf.htm says that "Each
|
|
||||||
// glyph begins with the following [10 byte] header".
|
|
||||||
const glyfHeaderLen = 10
|
|
||||||
|
|
||||||
// appendGlyfSegments appends to dst the segments encoded in the glyf data.
|
|
||||||
func appendGlyfSegments(dst []Segment, data []byte) ([]Segment, error) {
|
|
||||||
if len(data) == 0 {
|
|
||||||
return dst, nil
|
|
||||||
}
|
|
||||||
if len(data) < glyfHeaderLen {
|
|
||||||
return nil, errInvalidGlyphData
|
|
||||||
}
|
|
||||||
index := glyfHeaderLen
|
|
||||||
|
|
||||||
numContours, numPoints := int16(u16(data)), 0
|
|
||||||
switch {
|
|
||||||
case numContours == -1:
|
|
||||||
// We have a compound glyph. No-op.
|
|
||||||
case numContours == 0:
|
|
||||||
return dst, nil
|
|
||||||
case numContours > 0:
|
|
||||||
// We have a simple (non-compound) glyph.
|
|
||||||
index += 2 * int(numContours)
|
|
||||||
if index > len(data) {
|
|
||||||
return nil, errInvalidGlyphData
|
|
||||||
}
|
|
||||||
// The +1 for numPoints is because the value in the file format is
|
|
||||||
// inclusive, but Go's slice[:index] semantics are exclusive.
|
|
||||||
numPoints = 1 + int(u16(data[index-2:]))
|
|
||||||
default:
|
|
||||||
return nil, errInvalidGlyphData
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip the hinting instructions.
|
|
||||||
index += 2
|
|
||||||
if index > len(data) {
|
|
||||||
return nil, errInvalidGlyphData
|
|
||||||
}
|
|
||||||
hintsLength := int(u16(data[index-2:]))
|
|
||||||
index += hintsLength
|
|
||||||
if index > len(data) {
|
|
||||||
return nil, errInvalidGlyphData
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: support compound glyphs.
|
|
||||||
if numContours < 0 {
|
|
||||||
return nil, errUnsupportedCompoundGlyph
|
|
||||||
}
|
|
||||||
|
|
||||||
// For simple (non-compound) glyphs, the remainder of the glyf data
|
|
||||||
// consists of (flags, x, y) points: the Bézier curve segments. These are
|
|
||||||
// stored in columns (all the flags first, then all the x co-ordinates,
|
|
||||||
// then all the y co-ordinates), not rows, as it compresses better.
|
|
||||||
//
|
|
||||||
// Decoding those points in row order involves two passes. The first pass
|
|
||||||
// determines the indexes (relative to the data slice) of where the flags,
|
|
||||||
// the x co-ordinates and the y co-ordinates each start.
|
|
||||||
flagIndex := int32(index)
|
|
||||||
xIndex, yIndex, ok := findXYIndexes(data, index, numPoints)
|
|
||||||
if !ok {
|
|
||||||
return nil, errInvalidGlyphData
|
|
||||||
}
|
|
||||||
|
|
||||||
// The second pass decodes each (flags, x, y) tuple in row order.
|
|
||||||
g := glyfIter{
|
|
||||||
data: data,
|
|
||||||
flagIndex: flagIndex,
|
|
||||||
xIndex: xIndex,
|
|
||||||
yIndex: yIndex,
|
|
||||||
endIndex: glyfHeaderLen,
|
|
||||||
// The -1 is because the contour-end index in the file format is
|
|
||||||
// inclusive, but Go's slice[:index] semantics are exclusive.
|
|
||||||
prevEnd: -1,
|
|
||||||
numContours: int32(numContours),
|
|
||||||
}
|
|
||||||
for g.nextContour() {
|
|
||||||
for g.nextSegment() {
|
|
||||||
dst = append(dst, g.seg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if g.err != nil {
|
|
||||||
return nil, g.err
|
|
||||||
}
|
|
||||||
return dst, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func findXYIndexes(data []byte, index, numPoints int) (xIndex, yIndex int32, ok bool) {
|
|
||||||
xDataLen := 0
|
|
||||||
yDataLen := 0
|
|
||||||
for i := 0; ; {
|
|
||||||
if i > numPoints {
|
|
||||||
return 0, 0, false
|
|
||||||
}
|
|
||||||
if i == numPoints {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
repeatCount := 1
|
|
||||||
if index >= len(data) {
|
|
||||||
return 0, 0, false
|
|
||||||
}
|
|
||||||
flag := data[index]
|
|
||||||
index++
|
|
||||||
if flag&flagRepeat != 0 {
|
|
||||||
if index >= len(data) {
|
|
||||||
return 0, 0, false
|
|
||||||
}
|
|
||||||
repeatCount += int(data[index])
|
|
||||||
index++
|
|
||||||
}
|
|
||||||
|
|
||||||
xSize := 0
|
|
||||||
if flag&flagXShortVector != 0 {
|
|
||||||
xSize = 1
|
|
||||||
} else if flag&flagThisXIsSame == 0 {
|
|
||||||
xSize = 2
|
|
||||||
}
|
|
||||||
xDataLen += xSize * repeatCount
|
|
||||||
|
|
||||||
ySize := 0
|
|
||||||
if flag&flagYShortVector != 0 {
|
|
||||||
ySize = 1
|
|
||||||
} else if flag&flagThisYIsSame == 0 {
|
|
||||||
ySize = 2
|
|
||||||
}
|
|
||||||
yDataLen += ySize * repeatCount
|
|
||||||
|
|
||||||
i += repeatCount
|
|
||||||
}
|
|
||||||
if index+xDataLen+yDataLen > len(data) {
|
|
||||||
return 0, 0, false
|
|
||||||
}
|
|
||||||
return int32(index), int32(index + xDataLen), true
|
|
||||||
}
|
|
||||||
|
|
||||||
type glyfIter struct {
|
|
||||||
data []byte
|
|
||||||
err error
|
|
||||||
|
|
||||||
// Various indices into the data slice. See the "Decoding those points in
|
|
||||||
// row order" comment above.
|
|
||||||
flagIndex int32
|
|
||||||
xIndex int32
|
|
||||||
yIndex int32
|
|
||||||
|
|
||||||
// endIndex points to the uint16 that is the inclusive point index of the
|
|
||||||
// current contour's end. prevEnd is the previous contour's end.
|
|
||||||
endIndex int32
|
|
||||||
prevEnd int32
|
|
||||||
|
|
||||||
// c and p count the current contour and point, up to numContours and
|
|
||||||
// numPoints.
|
|
||||||
c, numContours int32
|
|
||||||
p, nPoints int32
|
|
||||||
|
|
||||||
// The next two groups of fields track points and segments. Points are what
|
|
||||||
// the underlying file format provides. Bézier curve segments are what the
|
|
||||||
// rasterizer consumes.
|
|
||||||
//
|
|
||||||
// Points are either on-curve or off-curve. Two consecutive on-curve points
|
|
||||||
// define a linear curve segment between them. N off-curve points between
|
|
||||||
// on-curve points define N quadratic curve segments. The TrueType glyf
|
|
||||||
// format does not use cubic curves. If N is greater than 1, some of these
|
|
||||||
// segment end points are implicit, the midpoint of two off-curve points.
|
|
||||||
// Given the points A, B1, B2, ..., BN, C, where A and C are on-curve and
|
|
||||||
// all the Bs are off-curve, the segments are:
|
|
||||||
//
|
|
||||||
// - A, B1, midpoint(B1, B2)
|
|
||||||
// - midpoint(B1, B2), B2, midpoint(B2, B3)
|
|
||||||
// - midpoint(B2, B3), B3, midpoint(B3, B4)
|
|
||||||
// - ...
|
|
||||||
// - midpoint(BN-1, BN), BN, C
|
|
||||||
//
|
|
||||||
// Note that the sequence of Bs may wrap around from the last point in the
|
|
||||||
// glyf data to the first. A and C may also be the same point (the only
|
|
||||||
// explicit on-curve point), or there may be no explicit on-curve points at
|
|
||||||
// all (but still implicit ones between explicit off-curve points).
|
|
||||||
|
|
||||||
// Points.
|
|
||||||
x, y int16
|
|
||||||
on bool
|
|
||||||
flag uint8
|
|
||||||
repeats uint8
|
|
||||||
|
|
||||||
// Segments.
|
|
||||||
closing bool
|
|
||||||
closed bool
|
|
||||||
firstOnCurveValid bool
|
|
||||||
firstOffCurveValid bool
|
|
||||||
lastOffCurveValid bool
|
|
||||||
firstOnCurve fixed.Point26_6
|
|
||||||
firstOffCurve fixed.Point26_6
|
|
||||||
lastOffCurve fixed.Point26_6
|
|
||||||
seg Segment
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *glyfIter) nextContour() (ok bool) {
|
|
||||||
if g.c == g.numContours {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
g.c++
|
|
||||||
|
|
||||||
end := int32(u16(g.data[g.endIndex:]))
|
|
||||||
g.endIndex += 2
|
|
||||||
if end <= g.prevEnd {
|
|
||||||
g.err = errInvalidGlyphData
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
g.nPoints = end - g.prevEnd
|
|
||||||
g.p = 0
|
|
||||||
g.prevEnd = end
|
|
||||||
|
|
||||||
g.closing = false
|
|
||||||
g.closed = false
|
|
||||||
g.firstOnCurveValid = false
|
|
||||||
g.firstOffCurveValid = false
|
|
||||||
g.lastOffCurveValid = false
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *glyfIter) close() {
|
|
||||||
switch {
|
|
||||||
case !g.firstOffCurveValid && !g.lastOffCurveValid:
|
|
||||||
g.closed = true
|
|
||||||
g.seg = Segment{
|
|
||||||
Op: SegmentOpLineTo,
|
|
||||||
Args: [6]fixed.Int26_6{
|
|
||||||
g.firstOnCurve.X,
|
|
||||||
g.firstOnCurve.Y,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
case !g.firstOffCurveValid && g.lastOffCurveValid:
|
|
||||||
g.closed = true
|
|
||||||
g.seg = Segment{
|
|
||||||
Op: SegmentOpQuadTo,
|
|
||||||
Args: [6]fixed.Int26_6{
|
|
||||||
g.lastOffCurve.X,
|
|
||||||
g.lastOffCurve.Y,
|
|
||||||
g.firstOnCurve.X,
|
|
||||||
g.firstOnCurve.Y,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
case g.firstOffCurveValid && !g.lastOffCurveValid:
|
|
||||||
g.closed = true
|
|
||||||
g.seg = Segment{
|
|
||||||
Op: SegmentOpQuadTo,
|
|
||||||
Args: [6]fixed.Int26_6{
|
|
||||||
g.firstOffCurve.X,
|
|
||||||
g.firstOffCurve.Y,
|
|
||||||
g.firstOnCurve.X,
|
|
||||||
g.firstOnCurve.Y,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
case g.firstOffCurveValid && g.lastOffCurveValid:
|
|
||||||
mid := midPoint(g.lastOffCurve, g.firstOffCurve)
|
|
||||||
g.lastOffCurveValid = false
|
|
||||||
g.seg = Segment{
|
|
||||||
Op: SegmentOpQuadTo,
|
|
||||||
Args: [6]fixed.Int26_6{
|
|
||||||
g.lastOffCurve.X,
|
|
||||||
g.lastOffCurve.Y,
|
|
||||||
mid.X,
|
|
||||||
mid.Y,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *glyfIter) nextSegment() (ok bool) {
|
|
||||||
for !g.closed {
|
|
||||||
if g.closing || !g.nextPoint() {
|
|
||||||
g.closing = true
|
|
||||||
g.close()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
p := fixed.Point26_6{
|
|
||||||
X: fixed.Int26_6(g.x) << 6,
|
|
||||||
Y: fixed.Int26_6(g.y) << 6,
|
|
||||||
}
|
|
||||||
|
|
||||||
if !g.firstOnCurveValid {
|
|
||||||
if g.on {
|
|
||||||
g.firstOnCurve = p
|
|
||||||
g.firstOnCurveValid = true
|
|
||||||
g.seg = Segment{
|
|
||||||
Op: SegmentOpMoveTo,
|
|
||||||
Args: [6]fixed.Int26_6{
|
|
||||||
p.X,
|
|
||||||
p.Y,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
} else if !g.firstOffCurveValid {
|
|
||||||
g.firstOffCurve = p
|
|
||||||
g.firstOffCurveValid = true
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
midp := midPoint(g.firstOffCurve, p)
|
|
||||||
g.firstOnCurve = midp
|
|
||||||
g.firstOnCurveValid = true
|
|
||||||
g.lastOffCurve = p
|
|
||||||
g.lastOffCurveValid = true
|
|
||||||
g.seg = Segment{
|
|
||||||
Op: SegmentOpMoveTo,
|
|
||||||
Args: [6]fixed.Int26_6{
|
|
||||||
midp.X,
|
|
||||||
midp.Y,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if !g.lastOffCurveValid {
|
|
||||||
if !g.on {
|
|
||||||
g.lastOffCurve = p
|
|
||||||
g.lastOffCurveValid = true
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
g.seg = Segment{
|
|
||||||
Op: SegmentOpLineTo,
|
|
||||||
Args: [6]fixed.Int26_6{
|
|
||||||
p.X,
|
|
||||||
p.Y,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
if !g.on {
|
|
||||||
midp := midPoint(g.lastOffCurve, p)
|
|
||||||
g.seg = Segment{
|
|
||||||
Op: SegmentOpQuadTo,
|
|
||||||
Args: [6]fixed.Int26_6{
|
|
||||||
g.lastOffCurve.X,
|
|
||||||
g.lastOffCurve.Y,
|
|
||||||
midp.X,
|
|
||||||
midp.Y,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
g.lastOffCurve = p
|
|
||||||
g.lastOffCurveValid = true
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
g.seg = Segment{
|
|
||||||
Op: SegmentOpQuadTo,
|
|
||||||
Args: [6]fixed.Int26_6{
|
|
||||||
g.lastOffCurve.X,
|
|
||||||
g.lastOffCurve.Y,
|
|
||||||
p.X,
|
|
||||||
p.Y,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
g.lastOffCurveValid = false
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *glyfIter) nextPoint() (ok bool) {
|
|
||||||
if g.p == g.nPoints {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
g.p++
|
|
||||||
|
|
||||||
if g.repeats > 0 {
|
|
||||||
g.repeats--
|
|
||||||
} else {
|
|
||||||
g.flag = g.data[g.flagIndex]
|
|
||||||
g.flagIndex++
|
|
||||||
if g.flag&flagRepeat != 0 {
|
|
||||||
g.repeats = g.data[g.flagIndex]
|
|
||||||
g.flagIndex++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if g.flag&flagXShortVector != 0 {
|
|
||||||
if g.flag&flagPositiveXShortVector != 0 {
|
|
||||||
g.x += int16(g.data[g.xIndex])
|
|
||||||
} else {
|
|
||||||
g.x -= int16(g.data[g.xIndex])
|
|
||||||
}
|
|
||||||
g.xIndex += 1
|
|
||||||
} else if g.flag&flagThisXIsSame == 0 {
|
|
||||||
g.x += int16(u16(g.data[g.xIndex:]))
|
|
||||||
g.xIndex += 2
|
|
||||||
}
|
|
||||||
|
|
||||||
if g.flag&flagYShortVector != 0 {
|
|
||||||
if g.flag&flagPositiveYShortVector != 0 {
|
|
||||||
g.y += int16(g.data[g.yIndex])
|
|
||||||
} else {
|
|
||||||
g.y -= int16(g.data[g.yIndex])
|
|
||||||
}
|
|
||||||
g.yIndex += 1
|
|
||||||
} else if g.flag&flagThisYIsSame == 0 {
|
|
||||||
g.y += int16(u16(g.data[g.yIndex:]))
|
|
||||||
g.yIndex += 2
|
|
||||||
}
|
|
||||||
|
|
||||||
g.on = g.flag&flagOnCurve != 0
|
|
||||||
return true
|
|
||||||
}
|
|
Binary file not shown.
|
@ -1,148 +0,0 @@
|
||||||
SplineFontDB: 3.0
|
|
||||||
FontName: CFFTest
|
|
||||||
FullName: CFFTest
|
|
||||||
FamilyName: CFFTest
|
|
||||||
Weight: Regular
|
|
||||||
Copyright: Copyright 2016 The Go Authors. All rights reserved.\nUse of this font is governed by a BSD-style license that can be found at https://golang.org/LICENSE.
|
|
||||||
Version: 001.000
|
|
||||||
ItalicAngle: -11.25
|
|
||||||
UnderlinePosition: -100
|
|
||||||
UnderlineWidth: 50
|
|
||||||
Ascent: 800
|
|
||||||
Descent: 200
|
|
||||||
LayerCount: 2
|
|
||||||
Layer: 0 0 "Back" 1
|
|
||||||
Layer: 1 0 "Fore" 0
|
|
||||||
XUID: [1021 367 888937226 7862908]
|
|
||||||
FSType: 8
|
|
||||||
OS2Version: 0
|
|
||||||
OS2_WeightWidthSlopeOnly: 0
|
|
||||||
OS2_UseTypoMetrics: 1
|
|
||||||
CreationTime: 1479626795
|
|
||||||
ModificationTime: 1481282599
|
|
||||||
PfmFamily: 17
|
|
||||||
TTFWeight: 400
|
|
||||||
TTFWidth: 5
|
|
||||||
LineGap: 90
|
|
||||||
VLineGap: 0
|
|
||||||
OS2TypoAscent: 0
|
|
||||||
OS2TypoAOffset: 1
|
|
||||||
OS2TypoDescent: 0
|
|
||||||
OS2TypoDOffset: 1
|
|
||||||
OS2TypoLinegap: 90
|
|
||||||
OS2WinAscent: 0
|
|
||||||
OS2WinAOffset: 1
|
|
||||||
OS2WinDescent: 0
|
|
||||||
OS2WinDOffset: 1
|
|
||||||
HheadAscent: 0
|
|
||||||
HheadAOffset: 1
|
|
||||||
HheadDescent: 0
|
|
||||||
HheadDOffset: 1
|
|
||||||
OS2Vendor: 'PfEd'
|
|
||||||
MarkAttachClasses: 1
|
|
||||||
DEI: 91125
|
|
||||||
LangName: 1033
|
|
||||||
Encoding: UnicodeBmp
|
|
||||||
UnicodeInterp: none
|
|
||||||
NameList: Adobe Glyph List
|
|
||||||
DisplaySize: -24
|
|
||||||
AntiAlias: 1
|
|
||||||
FitToEm: 1
|
|
||||||
WinInfo: 64 32 11
|
|
||||||
BeginPrivate: 0
|
|
||||||
EndPrivate
|
|
||||||
TeXData: 1 0 0 346030 173015 115343 0 1048576 115343 783286 444596 497025 792723 393216 433062 380633 303038 157286 324010 404750 52429 2506097 1059062 262144
|
|
||||||
BeginChars: 65536 4
|
|
||||||
|
|
||||||
StartChar: zero
|
|
||||||
Encoding: 48 48 0
|
|
||||||
Width: 600
|
|
||||||
VWidth: 0
|
|
||||||
HStem: 0 100<248.223 341.575> 700 100<258.425 351.777>
|
|
||||||
VStem: 100 80<243.925 531.374> 420 80<268.627 556.075>
|
|
||||||
LayerCount: 2
|
|
||||||
Fore
|
|
||||||
SplineSet
|
|
||||||
300 700 m 0
|
|
||||||
210 700 180 450 180 300 c 24
|
|
||||||
180 220 220 100 300 100 c 0
|
|
||||||
390 100 420 350 420 500 c 24
|
|
||||||
420 580 380 700 300 700 c 0
|
|
||||||
300 800 m 0
|
|
||||||
400 800 500 580 500 400 c 0
|
|
||||||
500 220 400 0 300 0 c 0
|
|
||||||
200 0 100 220 100 400 c 0
|
|
||||||
100 580 200 800 300 800 c 0
|
|
||||||
EndSplineSet
|
|
||||||
Validated: 1
|
|
||||||
EndChar
|
|
||||||
|
|
||||||
StartChar: one
|
|
||||||
Encoding: 49 49 1
|
|
||||||
Width: 400
|
|
||||||
VWidth: 0
|
|
||||||
Flags: W
|
|
||||||
HStem: 0 21G<100 300>
|
|
||||||
VStem: 100 200<0 800>
|
|
||||||
LayerCount: 2
|
|
||||||
Fore
|
|
||||||
SplineSet
|
|
||||||
100 0 m 25
|
|
||||||
100 800 l 25
|
|
||||||
300 800 l 29
|
|
||||||
300 0 l 29
|
|
||||||
100 0 l 25
|
|
||||||
EndSplineSet
|
|
||||||
Validated: 1
|
|
||||||
EndChar
|
|
||||||
|
|
||||||
StartChar: uni4E2D
|
|
||||||
Encoding: 20013 20013 2
|
|
||||||
Width: 600
|
|
||||||
VWidth: 0
|
|
||||||
Flags: W
|
|
||||||
VStem: 245 86<641.8 752>
|
|
||||||
LayerCount: 2
|
|
||||||
Fore
|
|
||||||
SplineSet
|
|
||||||
141 520 m 25
|
|
||||||
235 562 l 25
|
|
||||||
243 752 l 25
|
|
||||||
331 758 l 25
|
|
||||||
341 592 l 25
|
|
||||||
453 620 l 25
|
|
||||||
463 434 l 25
|
|
||||||
355 414 l 25
|
|
||||||
331 26 l 25
|
|
||||||
245 400 l 25
|
|
||||||
137 356 l 25
|
|
||||||
141 520 l 25
|
|
||||||
EndSplineSet
|
|
||||||
Validated: 1
|
|
||||||
EndChar
|
|
||||||
|
|
||||||
StartChar: Q
|
|
||||||
Encoding: 81 81 3
|
|
||||||
Width: 1000
|
|
||||||
VWidth: 0
|
|
||||||
Flags: W
|
|
||||||
LayerCount: 2
|
|
||||||
Fore
|
|
||||||
SplineSet
|
|
||||||
657 237 m 0
|
|
||||||
519 615 l 0
|
|
||||||
289 387 l 0
|
|
||||||
657 237 l 0
|
|
||||||
792 169 m 1
|
|
||||||
864 83 l 25
|
|
||||||
802 3 l 21
|
|
||||||
722 93 l 1
|
|
||||||
641 18 369 -39 242 93 c 0
|
|
||||||
110 231 71 509 228 673 c 24
|
|
||||||
380 831 645 840 791 665 c 0
|
|
||||||
926 502 867 263 792 169 c 1
|
|
||||||
EndSplineSet
|
|
||||||
Validated: 33
|
|
||||||
EndChar
|
|
||||||
EndChars
|
|
||||||
EndSplineFont
|
|
|
@ -1,2 +0,0 @@
|
||||||
CFFTest.sfd is a FontForge file for creating CFFTest.otf, a custom OpenType
|
|
||||||
font for testing the golang.org/x/image/font/sfnt package's CFF support.
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,9 +0,0 @@
|
||||||
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.
|
|
|
@ -1,68 +0,0 @@
|
||||||
13 11
|
|
||||||
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
|
|
|
@ -1,102 +0,0 @@
|
||||||
SplineFontDB: 3.0
|
|
||||||
FontName: glyfTest
|
|
||||||
FullName: glyfTest
|
|
||||||
FamilyName: glyfTest
|
|
||||||
Weight: Regular
|
|
||||||
Copyright: Copyright 2016 The Go Authors. All rights reserved.\nUse of this font is governed by a BSD-style license that can be found at https://golang.org/LICENSE.
|
|
||||||
Version: 001.000
|
|
||||||
ItalicAngle: -11.25
|
|
||||||
UnderlinePosition: -204
|
|
||||||
UnderlineWidth: 102
|
|
||||||
Ascent: 1638
|
|
||||||
Descent: 410
|
|
||||||
LayerCount: 2
|
|
||||||
Layer: 0 1 "Back" 1
|
|
||||||
Layer: 1 1 "Fore" 0
|
|
||||||
XUID: [1021 367 888937226 7862908]
|
|
||||||
FSType: 8
|
|
||||||
OS2Version: 0
|
|
||||||
OS2_WeightWidthSlopeOnly: 0
|
|
||||||
OS2_UseTypoMetrics: 1
|
|
||||||
CreationTime: 1484386143
|
|
||||||
ModificationTime: 1484386143
|
|
||||||
PfmFamily: 17
|
|
||||||
TTFWeight: 400
|
|
||||||
TTFWidth: 5
|
|
||||||
LineGap: 184
|
|
||||||
VLineGap: 0
|
|
||||||
OS2TypoAscent: 0
|
|
||||||
OS2TypoAOffset: 1
|
|
||||||
OS2TypoDescent: 0
|
|
||||||
OS2TypoDOffset: 1
|
|
||||||
OS2TypoLinegap: 184
|
|
||||||
OS2WinAscent: 0
|
|
||||||
OS2WinAOffset: 1
|
|
||||||
OS2WinDescent: 0
|
|
||||||
OS2WinDOffset: 1
|
|
||||||
HheadAscent: 0
|
|
||||||
HheadAOffset: 1
|
|
||||||
HheadDescent: 0
|
|
||||||
HheadDOffset: 1
|
|
||||||
OS2Vendor: 'PfEd'
|
|
||||||
MarkAttachClasses: 1
|
|
||||||
DEI: 91125
|
|
||||||
LangName: 1033
|
|
||||||
Encoding: UnicodeBmp
|
|
||||||
UnicodeInterp: none
|
|
||||||
NameList: Adobe Glyph List
|
|
||||||
DisplaySize: -24
|
|
||||||
AntiAlias: 1
|
|
||||||
FitToEm: 1
|
|
||||||
WinInfo: 0 32 23
|
|
||||||
BeginPrivate: 0
|
|
||||||
EndPrivate
|
|
||||||
TeXData: 1 0 0 346030 173015 115343 0 -1048576 115343 783286 444596 497025 792723 393216 433062 380633 303038 157286 324010 404750 52429 2506097 1059062 262144
|
|
||||||
BeginChars: 65536 2
|
|
||||||
|
|
||||||
StartChar: zero
|
|
||||||
Encoding: 48 48 0
|
|
||||||
Width: 1228
|
|
||||||
VWidth: 0
|
|
||||||
Flags: W
|
|
||||||
HStem: 0 205<508 700> 1434 205<529 720>
|
|
||||||
VStem: 205 164<500 1088> 860 164<550 1139>
|
|
||||||
LayerCount: 2
|
|
||||||
Fore
|
|
||||||
SplineSet
|
|
||||||
614 1434 m 0,0,1
|
|
||||||
369 1434 369 1434 369 614 c 0,2,3
|
|
||||||
369 471 369 471 435 338 c 0,4,5
|
|
||||||
502 205 502 205 614 205 c 0,6,7
|
|
||||||
860 205 860 205 860 1024 c 0,8,9
|
|
||||||
860 1167 860 1167 793 1300 c 0,10,11
|
|
||||||
727 1434 727 1434 614 1434 c 0,0,1
|
|
||||||
614 1638 m 0,12,13
|
|
||||||
1024 1638 1024 1638 1024 819 c 128,-1,14
|
|
||||||
1024 0 1024 0 614 0 c 0,15,16
|
|
||||||
205 0 205 0 205 819 c 128,-1,17
|
|
||||||
205 1638 205 1638 614 1638 c 0,12,13
|
|
||||||
EndSplineSet
|
|
||||||
Validated: 1
|
|
||||||
EndChar
|
|
||||||
|
|
||||||
StartChar: one
|
|
||||||
Encoding: 49 49 1
|
|
||||||
Width: 819
|
|
||||||
VWidth: 0
|
|
||||||
Flags: W
|
|
||||||
HStem: 0 43G<205 614>
|
|
||||||
VStem: 205 410<0 1638>
|
|
||||||
LayerCount: 2
|
|
||||||
Fore
|
|
||||||
SplineSet
|
|
||||||
205 0 m 25,0,-1
|
|
||||||
205 1638 l 1,1,-1
|
|
||||||
614 1638 l 1,2,-1
|
|
||||||
614 0 l 1,3,-1
|
|
||||||
205 0 l 25,0,-1
|
|
||||||
EndSplineSet
|
|
||||||
Validated: 1
|
|
||||||
EndChar
|
|
||||||
EndChars
|
|
||||||
EndSplineFont
|
|
Binary file not shown.
|
@ -1,37 +0,0 @@
|
||||||
// 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 f32 implements float32 vector and matrix types.
|
|
||||||
package f32 // import "golang.org/x/image/math/f32"
|
|
||||||
|
|
||||||
// Vec2 is a 2-element vector.
|
|
||||||
type Vec2 [2]float32
|
|
||||||
|
|
||||||
// Vec3 is a 3-element vector.
|
|
||||||
type Vec3 [3]float32
|
|
||||||
|
|
||||||
// Vec4 is a 4-element vector.
|
|
||||||
type Vec4 [4]float32
|
|
||||||
|
|
||||||
// Mat3 is a 3x3 matrix in row major order.
|
|
||||||
//
|
|
||||||
// m[3*r + c] is the element in the r'th row and c'th column.
|
|
||||||
type Mat3 [9]float32
|
|
||||||
|
|
||||||
// Mat4 is a 4x4 matrix in row major order.
|
|
||||||
//
|
|
||||||
// m[4*r + c] is the element in the r'th row and c'th column.
|
|
||||||
type Mat4 [16]float32
|
|
||||||
|
|
||||||
// Aff3 is a 3x3 affine transformation matrix in row major order, where the
|
|
||||||
// bottom row is implicitly [0 0 1].
|
|
||||||
//
|
|
||||||
// m[3*r + c] is the element in the r'th row and c'th column.
|
|
||||||
type Aff3 [6]float32
|
|
||||||
|
|
||||||
// Aff4 is a 4x4 affine transformation matrix in row major order, where the
|
|
||||||
// bottom row is implicitly [0 0 0 1].
|
|
||||||
//
|
|
||||||
// m[4*r + c] is the element in the r'th row and c'th column.
|
|
||||||
type Aff4 [12]float32
|
|
|
@ -1,37 +0,0 @@
|
||||||
// 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 f64 implements float64 vector and matrix types.
|
|
||||||
package f64 // import "golang.org/x/image/math/f64"
|
|
||||||
|
|
||||||
// Vec2 is a 2-element vector.
|
|
||||||
type Vec2 [2]float64
|
|
||||||
|
|
||||||
// Vec3 is a 3-element vector.
|
|
||||||
type Vec3 [3]float64
|
|
||||||
|
|
||||||
// Vec4 is a 4-element vector.
|
|
||||||
type Vec4 [4]float64
|
|
||||||
|
|
||||||
// Mat3 is a 3x3 matrix in row major order.
|
|
||||||
//
|
|
||||||
// m[3*r + c] is the element in the r'th row and c'th column.
|
|
||||||
type Mat3 [9]float64
|
|
||||||
|
|
||||||
// Mat4 is a 4x4 matrix in row major order.
|
|
||||||
//
|
|
||||||
// m[4*r + c] is the element in the r'th row and c'th column.
|
|
||||||
type Mat4 [16]float64
|
|
||||||
|
|
||||||
// Aff3 is a 3x3 affine transformation matrix in row major order, where the
|
|
||||||
// bottom row is implicitly [0 0 1].
|
|
||||||
//
|
|
||||||
// m[3*r + c] is the element in the r'th row and c'th column.
|
|
||||||
type Aff3 [6]float64
|
|
||||||
|
|
||||||
// Aff4 is a 4x4 affine transformation matrix in row major order, where the
|
|
||||||
// bottom row is implicitly [0 0 0 1].
|
|
||||||
//
|
|
||||||
// m[4*r + c] is the element in the r'th row and c'th column.
|
|
||||||
type Aff4 [12]float64
|
|
|
@ -1,410 +0,0 @@
|
||||||
// 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 fixed implements fixed-point integer types.
|
|
||||||
package fixed // import "golang.org/x/image/math/fixed"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO: implement fmt.Formatter for %f and %g.
|
|
||||||
|
|
||||||
// I returns the integer value i as an Int26_6.
|
|
||||||
//
|
|
||||||
// For example, passing the integer value 2 yields Int26_6(128).
|
|
||||||
func I(i int) Int26_6 {
|
|
||||||
return Int26_6(i << 6)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int26_6 is a signed 26.6 fixed-point number.
|
|
||||||
//
|
|
||||||
// The integer part ranges from -33554432 to 33554431, inclusive. The
|
|
||||||
// fractional part has 6 bits of precision.
|
|
||||||
//
|
|
||||||
// For example, the number one-and-a-quarter is Int26_6(1<<6 + 1<<4).
|
|
||||||
type Int26_6 int32
|
|
||||||
|
|
||||||
// String returns a human-readable representation of a 26.6 fixed-point number.
|
|
||||||
//
|
|
||||||
// For example, the number one-and-a-quarter becomes "1:16".
|
|
||||||
func (x Int26_6) String() string {
|
|
||||||
const shift, mask = 6, 1<<6 - 1
|
|
||||||
if x >= 0 {
|
|
||||||
return fmt.Sprintf("%d:%02d", int32(x>>shift), int32(x&mask))
|
|
||||||
}
|
|
||||||
x = -x
|
|
||||||
if x >= 0 {
|
|
||||||
return fmt.Sprintf("-%d:%02d", int32(x>>shift), int32(x&mask))
|
|
||||||
}
|
|
||||||
return "-33554432:00" // The minimum value is -(1<<25).
|
|
||||||
}
|
|
||||||
|
|
||||||
// Floor returns the greatest integer value less than or equal to x.
|
|
||||||
//
|
|
||||||
// Its return type is int, not Int26_6.
|
|
||||||
func (x Int26_6) Floor() int { return int((x + 0x00) >> 6) }
|
|
||||||
|
|
||||||
// Round returns the nearest integer value to x. Ties are rounded up.
|
|
||||||
//
|
|
||||||
// Its return type is int, not Int26_6.
|
|
||||||
func (x Int26_6) Round() int { return int((x + 0x20) >> 6) }
|
|
||||||
|
|
||||||
// Ceil returns the least integer value greater than or equal to x.
|
|
||||||
//
|
|
||||||
// Its return type is int, not Int26_6.
|
|
||||||
func (x Int26_6) Ceil() int { return int((x + 0x3f) >> 6) }
|
|
||||||
|
|
||||||
// Mul returns x*y in 26.6 fixed-point arithmetic.
|
|
||||||
func (x Int26_6) Mul(y Int26_6) Int26_6 {
|
|
||||||
return Int26_6((int64(x)*int64(y) + 1<<5) >> 6)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int52_12 is a signed 52.12 fixed-point number.
|
|
||||||
//
|
|
||||||
// The integer part ranges from -2251799813685248 to 2251799813685247,
|
|
||||||
// inclusive. The fractional part has 12 bits of precision.
|
|
||||||
//
|
|
||||||
// For example, the number one-and-a-quarter is Int52_12(1<<12 + 1<<10).
|
|
||||||
type Int52_12 int64
|
|
||||||
|
|
||||||
// String returns a human-readable representation of a 52.12 fixed-point
|
|
||||||
// number.
|
|
||||||
//
|
|
||||||
// For example, the number one-and-a-quarter becomes "1:1024".
|
|
||||||
func (x Int52_12) String() string {
|
|
||||||
const shift, mask = 12, 1<<12 - 1
|
|
||||||
if x >= 0 {
|
|
||||||
return fmt.Sprintf("%d:%04d", int64(x>>shift), int64(x&mask))
|
|
||||||
}
|
|
||||||
x = -x
|
|
||||||
if x >= 0 {
|
|
||||||
return fmt.Sprintf("-%d:%04d", int64(x>>shift), int64(x&mask))
|
|
||||||
}
|
|
||||||
return "-2251799813685248:0000" // The minimum value is -(1<<51).
|
|
||||||
}
|
|
||||||
|
|
||||||
// Floor returns the greatest integer value less than or equal to x.
|
|
||||||
//
|
|
||||||
// Its return type is int, not Int52_12.
|
|
||||||
func (x Int52_12) Floor() int { return int((x + 0x000) >> 12) }
|
|
||||||
|
|
||||||
// Round returns the nearest integer value to x. Ties are rounded up.
|
|
||||||
//
|
|
||||||
// Its return type is int, not Int52_12.
|
|
||||||
func (x Int52_12) Round() int { return int((x + 0x800) >> 12) }
|
|
||||||
|
|
||||||
// Ceil returns the least integer value greater than or equal to x.
|
|
||||||
//
|
|
||||||
// Its return type is int, not Int52_12.
|
|
||||||
func (x Int52_12) Ceil() int { return int((x + 0xfff) >> 12) }
|
|
||||||
|
|
||||||
// Mul returns x*y in 52.12 fixed-point arithmetic.
|
|
||||||
func (x Int52_12) Mul(y Int52_12) Int52_12 {
|
|
||||||
const M, N = 52, 12
|
|
||||||
lo, hi := muli64(int64(x), int64(y))
|
|
||||||
ret := Int52_12(hi<<M | lo>>N)
|
|
||||||
ret += Int52_12((lo >> (N - 1)) & 1) // Round to nearest, instead of rounding down.
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
// muli64 multiplies two int64 values, returning the 128-bit signed integer
|
|
||||||
// result as two uint64 values.
|
|
||||||
//
|
|
||||||
// This implementation is similar to $GOROOT/src/runtime/softfloat64.go's mullu
|
|
||||||
// function, which is in turn adapted from Hacker's Delight.
|
|
||||||
func muli64(u, v int64) (lo, hi uint64) {
|
|
||||||
const (
|
|
||||||
s = 32
|
|
||||||
mask = 1<<s - 1
|
|
||||||
)
|
|
||||||
|
|
||||||
u1 := uint64(u >> s)
|
|
||||||
u0 := uint64(u & mask)
|
|
||||||
v1 := uint64(v >> s)
|
|
||||||
v0 := uint64(v & mask)
|
|
||||||
|
|
||||||
w0 := u0 * v0
|
|
||||||
t := u1*v0 + w0>>s
|
|
||||||
w1 := t & mask
|
|
||||||
w2 := uint64(int64(t) >> s)
|
|
||||||
w1 += u0 * v1
|
|
||||||
return uint64(u) * uint64(v), u1*v1 + w2 + uint64(int64(w1)>>s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// P returns the integer values x and y as a Point26_6.
|
|
||||||
//
|
|
||||||
// For example, passing the integer values (2, -3) yields Point26_6{128, -192}.
|
|
||||||
func P(x, y int) Point26_6 {
|
|
||||||
return Point26_6{Int26_6(x << 6), Int26_6(y << 6)}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Point26_6 is a 26.6 fixed-point coordinate pair.
|
|
||||||
//
|
|
||||||
// It is analogous to the image.Point type in the standard library.
|
|
||||||
type Point26_6 struct {
|
|
||||||
X, Y Int26_6
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add returns the vector p+q.
|
|
||||||
func (p Point26_6) Add(q Point26_6) Point26_6 {
|
|
||||||
return Point26_6{p.X + q.X, p.Y + q.Y}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sub returns the vector p-q.
|
|
||||||
func (p Point26_6) Sub(q Point26_6) Point26_6 {
|
|
||||||
return Point26_6{p.X - q.X, p.Y - q.Y}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mul returns the vector p*k.
|
|
||||||
func (p Point26_6) Mul(k Int26_6) Point26_6 {
|
|
||||||
return Point26_6{p.X * k / 64, p.Y * k / 64}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Div returns the vector p/k.
|
|
||||||
func (p Point26_6) Div(k Int26_6) Point26_6 {
|
|
||||||
return Point26_6{p.X * 64 / k, p.Y * 64 / k}
|
|
||||||
}
|
|
||||||
|
|
||||||
// In returns whether p is in r.
|
|
||||||
func (p Point26_6) In(r Rectangle26_6) bool {
|
|
||||||
return r.Min.X <= p.X && p.X < r.Max.X && r.Min.Y <= p.Y && p.Y < r.Max.Y
|
|
||||||
}
|
|
||||||
|
|
||||||
// Point52_12 is a 52.12 fixed-point coordinate pair.
|
|
||||||
//
|
|
||||||
// It is analogous to the image.Point type in the standard library.
|
|
||||||
type Point52_12 struct {
|
|
||||||
X, Y Int52_12
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add returns the vector p+q.
|
|
||||||
func (p Point52_12) Add(q Point52_12) Point52_12 {
|
|
||||||
return Point52_12{p.X + q.X, p.Y + q.Y}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sub returns the vector p-q.
|
|
||||||
func (p Point52_12) Sub(q Point52_12) Point52_12 {
|
|
||||||
return Point52_12{p.X - q.X, p.Y - q.Y}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mul returns the vector p*k.
|
|
||||||
func (p Point52_12) Mul(k Int52_12) Point52_12 {
|
|
||||||
return Point52_12{p.X * k / 4096, p.Y * k / 4096}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Div returns the vector p/k.
|
|
||||||
func (p Point52_12) Div(k Int52_12) Point52_12 {
|
|
||||||
return Point52_12{p.X * 4096 / k, p.Y * 4096 / k}
|
|
||||||
}
|
|
||||||
|
|
||||||
// In returns whether p is in r.
|
|
||||||
func (p Point52_12) In(r Rectangle52_12) bool {
|
|
||||||
return r.Min.X <= p.X && p.X < r.Max.X && r.Min.Y <= p.Y && p.Y < r.Max.Y
|
|
||||||
}
|
|
||||||
|
|
||||||
// R returns the integer values minX, minY, maxX, maxY as a Rectangle26_6.
|
|
||||||
//
|
|
||||||
// For example, passing the integer values (0, 1, 2, 3) yields
|
|
||||||
// Rectangle26_6{Point26_6{0, 64}, Point26_6{128, 192}}.
|
|
||||||
//
|
|
||||||
// Like the image.Rect function in the standard library, the returned rectangle
|
|
||||||
// has minimum and maximum coordinates swapped if necessary so that it is
|
|
||||||
// well-formed.
|
|
||||||
func R(minX, minY, maxX, maxY int) Rectangle26_6 {
|
|
||||||
if minX > maxX {
|
|
||||||
minX, maxX = maxX, minX
|
|
||||||
}
|
|
||||||
if minY > maxY {
|
|
||||||
minY, maxY = maxY, minY
|
|
||||||
}
|
|
||||||
return Rectangle26_6{
|
|
||||||
Point26_6{
|
|
||||||
Int26_6(minX << 6),
|
|
||||||
Int26_6(minY << 6),
|
|
||||||
},
|
|
||||||
Point26_6{
|
|
||||||
Int26_6(maxX << 6),
|
|
||||||
Int26_6(maxY << 6),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rectangle26_6 is a 26.6 fixed-point coordinate rectangle. The Min bound is
|
|
||||||
// inclusive and the Max bound is exclusive. It is well-formed if Min.X <=
|
|
||||||
// Max.X and likewise for Y.
|
|
||||||
//
|
|
||||||
// It is analogous to the image.Rectangle type in the standard library.
|
|
||||||
type Rectangle26_6 struct {
|
|
||||||
Min, Max Point26_6
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add returns the rectangle r translated by p.
|
|
||||||
func (r Rectangle26_6) Add(p Point26_6) Rectangle26_6 {
|
|
||||||
return Rectangle26_6{
|
|
||||||
Point26_6{r.Min.X + p.X, r.Min.Y + p.Y},
|
|
||||||
Point26_6{r.Max.X + p.X, r.Max.Y + p.Y},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sub returns the rectangle r translated by -p.
|
|
||||||
func (r Rectangle26_6) Sub(p Point26_6) Rectangle26_6 {
|
|
||||||
return Rectangle26_6{
|
|
||||||
Point26_6{r.Min.X - p.X, r.Min.Y - p.Y},
|
|
||||||
Point26_6{r.Max.X - p.X, r.Max.Y - p.Y},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Intersect returns the largest rectangle contained by both r and s. If the
|
|
||||||
// two rectangles do not overlap then the zero rectangle will be returned.
|
|
||||||
func (r Rectangle26_6) Intersect(s Rectangle26_6) Rectangle26_6 {
|
|
||||||
if r.Min.X < s.Min.X {
|
|
||||||
r.Min.X = s.Min.X
|
|
||||||
}
|
|
||||||
if r.Min.Y < s.Min.Y {
|
|
||||||
r.Min.Y = s.Min.Y
|
|
||||||
}
|
|
||||||
if r.Max.X > s.Max.X {
|
|
||||||
r.Max.X = s.Max.X
|
|
||||||
}
|
|
||||||
if r.Max.Y > s.Max.Y {
|
|
||||||
r.Max.Y = s.Max.Y
|
|
||||||
}
|
|
||||||
// Letting r0 and s0 be the values of r and s at the time that the method
|
|
||||||
// is called, this next line is equivalent to:
|
|
||||||
//
|
|
||||||
// if max(r0.Min.X, s0.Min.X) >= min(r0.Max.X, s0.Max.X) || likewiseForY { etc }
|
|
||||||
if r.Empty() {
|
|
||||||
return Rectangle26_6{}
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// Union returns the smallest rectangle that contains both r and s.
|
|
||||||
func (r Rectangle26_6) Union(s Rectangle26_6) Rectangle26_6 {
|
|
||||||
if r.Empty() {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
if s.Empty() {
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
if r.Min.X > s.Min.X {
|
|
||||||
r.Min.X = s.Min.X
|
|
||||||
}
|
|
||||||
if r.Min.Y > s.Min.Y {
|
|
||||||
r.Min.Y = s.Min.Y
|
|
||||||
}
|
|
||||||
if r.Max.X < s.Max.X {
|
|
||||||
r.Max.X = s.Max.X
|
|
||||||
}
|
|
||||||
if r.Max.Y < s.Max.Y {
|
|
||||||
r.Max.Y = s.Max.Y
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// Empty returns whether the rectangle contains no points.
|
|
||||||
func (r Rectangle26_6) Empty() bool {
|
|
||||||
return r.Min.X >= r.Max.X || r.Min.Y >= r.Max.Y
|
|
||||||
}
|
|
||||||
|
|
||||||
// In returns whether every point in r is in s.
|
|
||||||
func (r Rectangle26_6) In(s Rectangle26_6) bool {
|
|
||||||
if r.Empty() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// Note that r.Max is an exclusive bound for r, so that r.In(s)
|
|
||||||
// does not require that r.Max.In(s).
|
|
||||||
return s.Min.X <= r.Min.X && r.Max.X <= s.Max.X &&
|
|
||||||
s.Min.Y <= r.Min.Y && r.Max.Y <= s.Max.Y
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rectangle52_12 is a 52.12 fixed-point coordinate rectangle. The Min bound is
|
|
||||||
// inclusive and the Max bound is exclusive. It is well-formed if Min.X <=
|
|
||||||
// Max.X and likewise for Y.
|
|
||||||
//
|
|
||||||
// It is analogous to the image.Rectangle type in the standard library.
|
|
||||||
type Rectangle52_12 struct {
|
|
||||||
Min, Max Point52_12
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add returns the rectangle r translated by p.
|
|
||||||
func (r Rectangle52_12) Add(p Point52_12) Rectangle52_12 {
|
|
||||||
return Rectangle52_12{
|
|
||||||
Point52_12{r.Min.X + p.X, r.Min.Y + p.Y},
|
|
||||||
Point52_12{r.Max.X + p.X, r.Max.Y + p.Y},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sub returns the rectangle r translated by -p.
|
|
||||||
func (r Rectangle52_12) Sub(p Point52_12) Rectangle52_12 {
|
|
||||||
return Rectangle52_12{
|
|
||||||
Point52_12{r.Min.X - p.X, r.Min.Y - p.Y},
|
|
||||||
Point52_12{r.Max.X - p.X, r.Max.Y - p.Y},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Intersect returns the largest rectangle contained by both r and s. If the
|
|
||||||
// two rectangles do not overlap then the zero rectangle will be returned.
|
|
||||||
func (r Rectangle52_12) Intersect(s Rectangle52_12) Rectangle52_12 {
|
|
||||||
if r.Min.X < s.Min.X {
|
|
||||||
r.Min.X = s.Min.X
|
|
||||||
}
|
|
||||||
if r.Min.Y < s.Min.Y {
|
|
||||||
r.Min.Y = s.Min.Y
|
|
||||||
}
|
|
||||||
if r.Max.X > s.Max.X {
|
|
||||||
r.Max.X = s.Max.X
|
|
||||||
}
|
|
||||||
if r.Max.Y > s.Max.Y {
|
|
||||||
r.Max.Y = s.Max.Y
|
|
||||||
}
|
|
||||||
// Letting r0 and s0 be the values of r and s at the time that the method
|
|
||||||
// is called, this next line is equivalent to:
|
|
||||||
//
|
|
||||||
// if max(r0.Min.X, s0.Min.X) >= min(r0.Max.X, s0.Max.X) || likewiseForY { etc }
|
|
||||||
if r.Empty() {
|
|
||||||
return Rectangle52_12{}
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// Union returns the smallest rectangle that contains both r and s.
|
|
||||||
func (r Rectangle52_12) Union(s Rectangle52_12) Rectangle52_12 {
|
|
||||||
if r.Empty() {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
if s.Empty() {
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
if r.Min.X > s.Min.X {
|
|
||||||
r.Min.X = s.Min.X
|
|
||||||
}
|
|
||||||
if r.Min.Y > s.Min.Y {
|
|
||||||
r.Min.Y = s.Min.Y
|
|
||||||
}
|
|
||||||
if r.Max.X < s.Max.X {
|
|
||||||
r.Max.X = s.Max.X
|
|
||||||
}
|
|
||||||
if r.Max.Y < s.Max.Y {
|
|
||||||
r.Max.Y = s.Max.Y
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// Empty returns whether the rectangle contains no points.
|
|
||||||
func (r Rectangle52_12) Empty() bool {
|
|
||||||
return r.Min.X >= r.Max.X || r.Min.Y >= r.Max.Y
|
|
||||||
}
|
|
||||||
|
|
||||||
// In returns whether every point in r is in s.
|
|
||||||
func (r Rectangle52_12) In(s Rectangle52_12) bool {
|
|
||||||
if r.Empty() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// Note that r.Max is an exclusive bound for r, so that r.In(s)
|
|
||||||
// does not require that r.Max.In(s).
|
|
||||||
return s.Min.X <= r.Min.X && r.Max.X <= s.Max.X &&
|
|
||||||
s.Min.Y <= r.Min.Y && r.Max.Y <= s.Max.Y
|
|
||||||
}
|
|
|
@ -1,439 +0,0 @@
|
||||||
// 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 fixed
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math"
|
|
||||||
"math/rand"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
var testCases = []struct {
|
|
||||||
x float64
|
|
||||||
s26_6 string
|
|
||||||
s52_12 string
|
|
||||||
floor int
|
|
||||||
round int
|
|
||||||
ceil int
|
|
||||||
}{{
|
|
||||||
x: 0,
|
|
||||||
s26_6: "0:00",
|
|
||||||
s52_12: "0:0000",
|
|
||||||
floor: 0,
|
|
||||||
round: 0,
|
|
||||||
ceil: 0,
|
|
||||||
}, {
|
|
||||||
x: 1,
|
|
||||||
s26_6: "1:00",
|
|
||||||
s52_12: "1:0000",
|
|
||||||
floor: 1,
|
|
||||||
round: 1,
|
|
||||||
ceil: 1,
|
|
||||||
}, {
|
|
||||||
x: 1.25,
|
|
||||||
s26_6: "1:16",
|
|
||||||
s52_12: "1:1024",
|
|
||||||
floor: 1,
|
|
||||||
round: 1,
|
|
||||||
ceil: 2,
|
|
||||||
}, {
|
|
||||||
x: 2.5,
|
|
||||||
s26_6: "2:32",
|
|
||||||
s52_12: "2:2048",
|
|
||||||
floor: 2,
|
|
||||||
round: 3,
|
|
||||||
ceil: 3,
|
|
||||||
}, {
|
|
||||||
x: 63 / 64.0,
|
|
||||||
s26_6: "0:63",
|
|
||||||
s52_12: "0:4032",
|
|
||||||
floor: 0,
|
|
||||||
round: 1,
|
|
||||||
ceil: 1,
|
|
||||||
}, {
|
|
||||||
x: -0.5,
|
|
||||||
s26_6: "-0:32",
|
|
||||||
s52_12: "-0:2048",
|
|
||||||
floor: -1,
|
|
||||||
round: +0,
|
|
||||||
ceil: +0,
|
|
||||||
}, {
|
|
||||||
x: -4.125,
|
|
||||||
s26_6: "-4:08",
|
|
||||||
s52_12: "-4:0512",
|
|
||||||
floor: -5,
|
|
||||||
round: -4,
|
|
||||||
ceil: -4,
|
|
||||||
}, {
|
|
||||||
x: -7.75,
|
|
||||||
s26_6: "-7:48",
|
|
||||||
s52_12: "-7:3072",
|
|
||||||
floor: -8,
|
|
||||||
round: -8,
|
|
||||||
ceil: -7,
|
|
||||||
}}
|
|
||||||
|
|
||||||
func TestInt26_6(t *testing.T) {
|
|
||||||
const one = Int26_6(1 << 6)
|
|
||||||
for _, tc := range testCases {
|
|
||||||
x := Int26_6(tc.x * (1 << 6))
|
|
||||||
if got, want := x.String(), tc.s26_6; got != want {
|
|
||||||
t.Errorf("tc.x=%v: String: got %q, want %q", tc.x, got, want)
|
|
||||||
}
|
|
||||||
if got, want := x.Floor(), tc.floor; got != want {
|
|
||||||
t.Errorf("tc.x=%v: Floor: got %v, want %v", tc.x, got, want)
|
|
||||||
}
|
|
||||||
if got, want := x.Round(), tc.round; got != want {
|
|
||||||
t.Errorf("tc.x=%v: Round: got %v, want %v", tc.x, got, want)
|
|
||||||
}
|
|
||||||
if got, want := x.Ceil(), tc.ceil; got != want {
|
|
||||||
t.Errorf("tc.x=%v: Ceil: got %v, want %v", tc.x, got, want)
|
|
||||||
}
|
|
||||||
if got, want := x.Mul(one), x; got != want {
|
|
||||||
t.Errorf("tc.x=%v: Mul by one: got %v, want %v", tc.x, got, want)
|
|
||||||
}
|
|
||||||
if got, want := x.mul(one), x; got != want {
|
|
||||||
t.Errorf("tc.x=%v: mul by one: got %v, want %v", tc.x, got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInt52_12(t *testing.T) {
|
|
||||||
const one = Int52_12(1 << 12)
|
|
||||||
for _, tc := range testCases {
|
|
||||||
x := Int52_12(tc.x * (1 << 12))
|
|
||||||
if got, want := x.String(), tc.s52_12; got != want {
|
|
||||||
t.Errorf("tc.x=%v: String: got %q, want %q", tc.x, got, want)
|
|
||||||
}
|
|
||||||
if got, want := x.Floor(), tc.floor; got != want {
|
|
||||||
t.Errorf("tc.x=%v: Floor: got %v, want %v", tc.x, got, want)
|
|
||||||
}
|
|
||||||
if got, want := x.Round(), tc.round; got != want {
|
|
||||||
t.Errorf("tc.x=%v: Round: got %v, want %v", tc.x, got, want)
|
|
||||||
}
|
|
||||||
if got, want := x.Ceil(), tc.ceil; got != want {
|
|
||||||
t.Errorf("tc.x=%v: Ceil: got %v, want %v", tc.x, got, want)
|
|
||||||
}
|
|
||||||
if got, want := x.Mul(one), x; got != want {
|
|
||||||
t.Errorf("tc.x=%v: Mul by one: got %v, want %v", tc.x, got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var mulTestCases = []struct {
|
|
||||||
x float64
|
|
||||||
y float64
|
|
||||||
z26_6 float64 // Equals truncate26_6(x)*truncate26_6(y).
|
|
||||||
z52_12 float64 // Equals truncate52_12(x)*truncate52_12(y).
|
|
||||||
s26_6 string
|
|
||||||
s52_12 string
|
|
||||||
}{{
|
|
||||||
x: 0,
|
|
||||||
y: 1.5,
|
|
||||||
z26_6: 0,
|
|
||||||
z52_12: 0,
|
|
||||||
s26_6: "0:00",
|
|
||||||
s52_12: "0:0000",
|
|
||||||
}, {
|
|
||||||
x: +1.25,
|
|
||||||
y: +4,
|
|
||||||
z26_6: +5,
|
|
||||||
z52_12: +5,
|
|
||||||
s26_6: "5:00",
|
|
||||||
s52_12: "5:0000",
|
|
||||||
}, {
|
|
||||||
x: +1.25,
|
|
||||||
y: -4,
|
|
||||||
z26_6: -5,
|
|
||||||
z52_12: -5,
|
|
||||||
s26_6: "-5:00",
|
|
||||||
s52_12: "-5:0000",
|
|
||||||
}, {
|
|
||||||
x: -1.25,
|
|
||||||
y: +4,
|
|
||||||
z26_6: -5,
|
|
||||||
z52_12: -5,
|
|
||||||
s26_6: "-5:00",
|
|
||||||
s52_12: "-5:0000",
|
|
||||||
}, {
|
|
||||||
x: -1.25,
|
|
||||||
y: -4,
|
|
||||||
z26_6: +5,
|
|
||||||
z52_12: +5,
|
|
||||||
s26_6: "5:00",
|
|
||||||
s52_12: "5:0000",
|
|
||||||
}, {
|
|
||||||
x: 1.25,
|
|
||||||
y: 1.5,
|
|
||||||
z26_6: 1.875,
|
|
||||||
z52_12: 1.875,
|
|
||||||
s26_6: "1:56",
|
|
||||||
s52_12: "1:3584",
|
|
||||||
}, {
|
|
||||||
x: 1234.5,
|
|
||||||
y: -8888.875,
|
|
||||||
z26_6: -10973316.1875,
|
|
||||||
z52_12: -10973316.1875,
|
|
||||||
s26_6: "-10973316:12",
|
|
||||||
s52_12: "-10973316:0768",
|
|
||||||
}, {
|
|
||||||
x: 1.515625, // 1 + 33/64 = 97/64
|
|
||||||
y: 1.531250, // 1 + 34/64 = 98/64
|
|
||||||
z26_6: 2.32080078125, // 2 + 1314/4096 = 9506/4096
|
|
||||||
z52_12: 2.32080078125, // 2 + 1314/4096 = 9506/4096
|
|
||||||
s26_6: "2:21", // 2.32812500000, which is closer than 2:20 (in decimal, 2.3125)
|
|
||||||
s52_12: "2:1314", // 2.32080078125
|
|
||||||
}, {
|
|
||||||
x: 0.500244140625, // 2049/4096, approximately 32/64
|
|
||||||
y: 0.500732421875, // 2051/4096, approximately 32/64
|
|
||||||
z26_6: 0.25, // 4194304/16777216, or 1024/4096
|
|
||||||
z52_12: 0.2504884600639343, // 4202499/16777216
|
|
||||||
s26_6: "0:16", // 0.25000000000
|
|
||||||
s52_12: "0:1026", // 0.25048828125, which is closer than 0:1027 (in decimal, 0.250732421875)
|
|
||||||
}, {
|
|
||||||
x: 0.015625, // 1/64
|
|
||||||
y: 0.000244140625, // 1/4096, approximately 0/64
|
|
||||||
z26_6: 0.0, // 0
|
|
||||||
z52_12: 0.000003814697265625, // 1/262144
|
|
||||||
s26_6: "0:00", // 0
|
|
||||||
s52_12: "0:0000", // 0, which is closer than 0:0001 (in decimal, 0.000244140625)
|
|
||||||
}, {
|
|
||||||
// Round the Int52_12 calculation down.
|
|
||||||
x: 1.44140625, // 1 + 1808/4096 = 5904/4096, approximately 92/64
|
|
||||||
y: 1.44140625, // 1 + 1808/4096 = 5904/4096, approximately 92/64
|
|
||||||
z26_6: 2.06640625, // 2 + 272/4096 = 8464/4096
|
|
||||||
z52_12: 2.0776519775390625, // 2 + 318/4096 + 256/16777216 = 34857216/16777216
|
|
||||||
s26_6: "2:04", // 2.06250000000, which is closer than 2:05 (in decimal, 2.078125000000)
|
|
||||||
s52_12: "2:0318", // 2.07763671875, which is closer than 2:0319 (in decimal, 2.077880859375)
|
|
||||||
}, {
|
|
||||||
// Round the Int52_12 calculation up.
|
|
||||||
x: 1.44140625, // 1 + 1808/4096 = 5904/4096, approximately 92/64
|
|
||||||
y: 1.441650390625, // 1 + 1809/4096 = 5905/4096, approximately 92/64
|
|
||||||
z26_6: 2.06640625, // 2 + 272/4096 = 8464/4096
|
|
||||||
z52_12: 2.0780038833618164, // 2 + 319/4096 + 2064/16777216 = 34863120/16777216
|
|
||||||
s26_6: "2:04", // 2.06250000000, which is closer than 2:05 (in decimal, 2.078125000000)
|
|
||||||
s52_12: "2:0320", // 2.07812500000, which is closer than 2:0319 (in decimal, 2.077880859375)
|
|
||||||
}}
|
|
||||||
|
|
||||||
func TestInt26_6Mul(t *testing.T) {
|
|
||||||
for _, tc := range mulTestCases {
|
|
||||||
x := Int26_6(tc.x * (1 << 6))
|
|
||||||
y := Int26_6(tc.y * (1 << 6))
|
|
||||||
if z := float64(x) * float64(y) / (1 << 12); z != tc.z26_6 {
|
|
||||||
t.Errorf("tc.x=%v, tc.y=%v: z: got %v, want %v", tc.x, tc.y, z, tc.z26_6)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if got, want := x.Mul(y).String(), tc.s26_6; got != want {
|
|
||||||
t.Errorf("tc.x=%v: Mul: got %q, want %q", tc.x, got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInt52_12Mul(t *testing.T) {
|
|
||||||
for _, tc := range mulTestCases {
|
|
||||||
x := Int52_12(tc.x * (1 << 12))
|
|
||||||
y := Int52_12(tc.y * (1 << 12))
|
|
||||||
if z := float64(x) * float64(y) / (1 << 24); z != tc.z52_12 {
|
|
||||||
t.Errorf("tc.x=%v, tc.y=%v: z: got %v, want %v", tc.x, tc.y, z, tc.z52_12)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if got, want := x.Mul(y).String(), tc.s52_12; got != want {
|
|
||||||
t.Errorf("tc.x=%v: Mul: got %q, want %q", tc.x, got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInt26_6MulByOneMinusIota(t *testing.T) {
|
|
||||||
const (
|
|
||||||
totalBits = 32
|
|
||||||
fracBits = 6
|
|
||||||
|
|
||||||
oneMinusIota = Int26_6(1<<fracBits) - 1
|
|
||||||
oneMinusIotaF = float64(oneMinusIota) / (1 << fracBits)
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, neg := range []bool{false, true} {
|
|
||||||
for i := uint(0); i < totalBits; i++ {
|
|
||||||
x := Int26_6(1 << i)
|
|
||||||
if neg {
|
|
||||||
x = -x
|
|
||||||
} else if i == totalBits-1 {
|
|
||||||
// A signed int32 can't represent 1<<31.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// want equals x * oneMinusIota, rounded to nearest.
|
|
||||||
want := Int26_6(0)
|
|
||||||
if -1<<fracBits < x && x < 1<<fracBits {
|
|
||||||
// (x * oneMinusIota) isn't exactly representable as an
|
|
||||||
// Int26_6. Calculate the rounded value using float64 math.
|
|
||||||
xF := float64(x) / (1 << fracBits)
|
|
||||||
wantF := xF * oneMinusIotaF * (1 << fracBits)
|
|
||||||
want = Int26_6(math.Floor(wantF + 0.5))
|
|
||||||
} else {
|
|
||||||
// (x * oneMinusIota) is exactly representable.
|
|
||||||
want = oneMinusIota << (i - fracBits)
|
|
||||||
if neg {
|
|
||||||
want = -want
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if got := x.Mul(oneMinusIota); got != want {
|
|
||||||
t.Errorf("neg=%t, i=%d, x=%v, Mul: got %v, want %v", neg, i, x, got, want)
|
|
||||||
}
|
|
||||||
if got := x.mul(oneMinusIota); got != want {
|
|
||||||
t.Errorf("neg=%t, i=%d, x=%v, mul: got %v, want %v", neg, i, x, got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInt52_12MulByOneMinusIota(t *testing.T) {
|
|
||||||
const (
|
|
||||||
totalBits = 64
|
|
||||||
fracBits = 12
|
|
||||||
|
|
||||||
oneMinusIota = Int52_12(1<<fracBits) - 1
|
|
||||||
oneMinusIotaF = float64(oneMinusIota) / (1 << fracBits)
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, neg := range []bool{false, true} {
|
|
||||||
for i := uint(0); i < totalBits; i++ {
|
|
||||||
x := Int52_12(1 << i)
|
|
||||||
if neg {
|
|
||||||
x = -x
|
|
||||||
} else if i == totalBits-1 {
|
|
||||||
// A signed int64 can't represent 1<<63.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// want equals x * oneMinusIota, rounded to nearest.
|
|
||||||
want := Int52_12(0)
|
|
||||||
if -1<<fracBits < x && x < 1<<fracBits {
|
|
||||||
// (x * oneMinusIota) isn't exactly representable as an
|
|
||||||
// Int52_12. Calculate the rounded value using float64 math.
|
|
||||||
xF := float64(x) / (1 << fracBits)
|
|
||||||
wantF := xF * oneMinusIotaF * (1 << fracBits)
|
|
||||||
want = Int52_12(math.Floor(wantF + 0.5))
|
|
||||||
} else {
|
|
||||||
// (x * oneMinusIota) is exactly representable.
|
|
||||||
want = oneMinusIota << (i - fracBits)
|
|
||||||
if neg {
|
|
||||||
want = -want
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if got := x.Mul(oneMinusIota); got != want {
|
|
||||||
t.Errorf("neg=%t, i=%d, x=%v, Mul: got %v, want %v", neg, i, x, got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInt26_6MulVsMul(t *testing.T) {
|
|
||||||
rng := rand.New(rand.NewSource(1))
|
|
||||||
for i := 0; i < 10000; i++ {
|
|
||||||
u := Int26_6(rng.Uint32())
|
|
||||||
v := Int26_6(rng.Uint32())
|
|
||||||
Mul := u.Mul(v)
|
|
||||||
mul := u.mul(v)
|
|
||||||
if Mul != mul {
|
|
||||||
t.Errorf("u=%#08x, v=%#08x: Mul=%#08x and mul=%#08x differ",
|
|
||||||
uint32(u), uint32(v), uint32(Mul), uint32(mul))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMuli32(t *testing.T) {
|
|
||||||
rng := rand.New(rand.NewSource(2))
|
|
||||||
for i := 0; i < 10000; i++ {
|
|
||||||
u := int32(rng.Uint32())
|
|
||||||
v := int32(rng.Uint32())
|
|
||||||
lo, hi := muli32(u, v)
|
|
||||||
got := uint64(lo) | uint64(hi)<<32
|
|
||||||
want := uint64(int64(u) * int64(v))
|
|
||||||
if got != want {
|
|
||||||
t.Errorf("u=%#08x, v=%#08x: got %#016x, want %#016x", uint32(u), uint32(v), got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMulu32(t *testing.T) {
|
|
||||||
rng := rand.New(rand.NewSource(3))
|
|
||||||
for i := 0; i < 10000; i++ {
|
|
||||||
u := rng.Uint32()
|
|
||||||
v := rng.Uint32()
|
|
||||||
lo, hi := mulu32(u, v)
|
|
||||||
got := uint64(lo) | uint64(hi)<<32
|
|
||||||
want := uint64(u) * uint64(v)
|
|
||||||
if got != want {
|
|
||||||
t.Errorf("u=%#08x, v=%#08x: got %#016x, want %#016x", u, v, got, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// mul (with a lower case 'm') is an alternative implementation of Int26_6.Mul
|
|
||||||
// (with an upper case 'M'). It has the same structure as the Int52_12.Mul
|
|
||||||
// implementation, but Int26_6.mul is easier to test since Go has built-in
|
|
||||||
// 64-bit integers.
|
|
||||||
func (x Int26_6) mul(y Int26_6) Int26_6 {
|
|
||||||
const M, N = 26, 6
|
|
||||||
lo, hi := muli32(int32(x), int32(y))
|
|
||||||
ret := Int26_6(hi<<M | lo>>N)
|
|
||||||
ret += Int26_6((lo >> (N - 1)) & 1) // Round to nearest, instead of rounding down.
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
// muli32 multiplies two int32 values, returning the 64-bit signed integer
|
|
||||||
// result as two uint32 values.
|
|
||||||
//
|
|
||||||
// muli32 isn't used directly by this package, but it has the same structure as
|
|
||||||
// muli64, and muli32 is easier to test since Go has built-in 64-bit integers.
|
|
||||||
func muli32(u, v int32) (lo, hi uint32) {
|
|
||||||
const (
|
|
||||||
s = 16
|
|
||||||
mask = 1<<s - 1
|
|
||||||
)
|
|
||||||
|
|
||||||
u1 := uint32(u >> s)
|
|
||||||
u0 := uint32(u & mask)
|
|
||||||
v1 := uint32(v >> s)
|
|
||||||
v0 := uint32(v & mask)
|
|
||||||
|
|
||||||
w0 := u0 * v0
|
|
||||||
t := u1*v0 + w0>>s
|
|
||||||
w1 := t & mask
|
|
||||||
w2 := uint32(int32(t) >> s)
|
|
||||||
w1 += u0 * v1
|
|
||||||
return uint32(u) * uint32(v), u1*v1 + w2 + uint32(int32(w1)>>s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// mulu32 is like muli32, except that it multiplies unsigned instead of signed
|
|
||||||
// values.
|
|
||||||
//
|
|
||||||
// This implementation comes from $GOROOT/src/runtime/softfloat64.go's mullu
|
|
||||||
// function, which is in turn adapted from Hacker's Delight.
|
|
||||||
//
|
|
||||||
// mulu32 (and its corresponding test, TestMulu32) isn't used directly by this
|
|
||||||
// package. It is provided in this test file as a reference point to compare
|
|
||||||
// the muli32 (and TestMuli32) implementations against.
|
|
||||||
func mulu32(u, v uint32) (lo, hi uint32) {
|
|
||||||
const (
|
|
||||||
s = 16
|
|
||||||
mask = 1<<s - 1
|
|
||||||
)
|
|
||||||
|
|
||||||
u0 := u & mask
|
|
||||||
u1 := u >> s
|
|
||||||
v0 := v & mask
|
|
||||||
v1 := v >> s
|
|
||||||
|
|
||||||
w0 := u0 * v0
|
|
||||||
t := u1*v0 + w0>>s
|
|
||||||
w1 := t & mask
|
|
||||||
w2 := t >> s
|
|
||||||
w1 += u0 * v1
|
|
||||||
return u * v, u1*v1 + w2 + w1>>s
|
|
||||||
}
|
|
|
@ -1,113 +0,0 @@
|
||||||
// Copyright 2014 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 riff_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/image/riff"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ExampleReader() {
|
|
||||||
formType, r, err := riff.NewReader(strings.NewReader(data))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
fmt.Printf("RIFF(%s)\n", formType)
|
|
||||||
if err := dump(r, ".\t"); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
// Output:
|
|
||||||
// RIFF(ROOT)
|
|
||||||
// . ZERO ""
|
|
||||||
// . ONE "a"
|
|
||||||
// . LIST(META)
|
|
||||||
// . . LIST(GOOD)
|
|
||||||
// . . . ONE "a"
|
|
||||||
// . . . FIVE "klmno"
|
|
||||||
// . . ZERO ""
|
|
||||||
// . . LIST(BAD )
|
|
||||||
// . . . THRE "def"
|
|
||||||
// . TWO "bc"
|
|
||||||
// . LIST(UGLY)
|
|
||||||
// . . FOUR "ghij"
|
|
||||||
// . . SIX "pqrstu"
|
|
||||||
}
|
|
||||||
|
|
||||||
func dump(r *riff.Reader, indent string) error {
|
|
||||||
for {
|
|
||||||
chunkID, chunkLen, chunkData, err := r.Next()
|
|
||||||
if err == io.EOF {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if chunkID == riff.LIST {
|
|
||||||
listType, list, err := riff.NewListReader(chunkLen, chunkData)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Printf("%sLIST(%s)\n", indent, listType)
|
|
||||||
if err := dump(list, indent+".\t"); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
b, err := ioutil.ReadAll(chunkData)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Printf("%s%s %q\n", indent, chunkID, b)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeU32(u uint32) string {
|
|
||||||
return string([]byte{
|
|
||||||
byte(u >> 0),
|
|
||||||
byte(u >> 8),
|
|
||||||
byte(u >> 16),
|
|
||||||
byte(u >> 24),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func encode(chunkID, contents string) string {
|
|
||||||
n := len(contents)
|
|
||||||
if n&1 == 1 {
|
|
||||||
contents += "\x00"
|
|
||||||
}
|
|
||||||
return chunkID + encodeU32(uint32(n)) + contents
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeMulti(typ0, typ1 string, chunks ...string) string {
|
|
||||||
n := 4
|
|
||||||
for _, c := range chunks {
|
|
||||||
n += len(c)
|
|
||||||
}
|
|
||||||
s := typ0 + encodeU32(uint32(n)) + typ1
|
|
||||||
for _, c := range chunks {
|
|
||||||
s += c
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
d0 = encode("ZERO", "")
|
|
||||||
d1 = encode("ONE ", "a")
|
|
||||||
d2 = encode("TWO ", "bc")
|
|
||||||
d3 = encode("THRE", "def")
|
|
||||||
d4 = encode("FOUR", "ghij")
|
|
||||||
d5 = encode("FIVE", "klmno")
|
|
||||||
d6 = encode("SIX ", "pqrstu")
|
|
||||||
l0 = encodeMulti("LIST", "GOOD", d1, d5)
|
|
||||||
l1 = encodeMulti("LIST", "BAD ", d3)
|
|
||||||
l2 = encodeMulti("LIST", "UGLY", d4, d6)
|
|
||||||
l01 = encodeMulti("LIST", "META", l0, d0, l1)
|
|
||||||
data = encodeMulti("RIFF", "ROOT", d0, d1, l01, d2, l2)
|
|
||||||
)
|
|
|
@ -1,193 +0,0 @@
|
||||||
// Copyright 2014 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 riff implements the Resource Interchange File Format, used by media
|
|
||||||
// formats such as AVI, WAVE and WEBP.
|
|
||||||
//
|
|
||||||
// A RIFF stream contains a sequence of chunks. Each chunk consists of an 8-byte
|
|
||||||
// header (containing a 4-byte chunk type and a 4-byte chunk length), the chunk
|
|
||||||
// data (presented as an io.Reader), and some padding bytes.
|
|
||||||
//
|
|
||||||
// A detailed description of the format is at
|
|
||||||
// http://www.tactilemedia.com/info/MCI_Control_Info.html
|
|
||||||
package riff // import "golang.org/x/image/riff"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"math"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
errMissingPaddingByte = errors.New("riff: missing padding byte")
|
|
||||||
errMissingRIFFChunkHeader = errors.New("riff: missing RIFF chunk header")
|
|
||||||
errListSubchunkTooLong = errors.New("riff: list subchunk too long")
|
|
||||||
errShortChunkData = errors.New("riff: short chunk data")
|
|
||||||
errShortChunkHeader = errors.New("riff: short chunk header")
|
|
||||||
errStaleReader = errors.New("riff: stale reader")
|
|
||||||
)
|
|
||||||
|
|
||||||
// u32 decodes the first four bytes of b as a little-endian integer.
|
|
||||||
func u32(b []byte) uint32 {
|
|
||||||
return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
|
|
||||||
}
|
|
||||||
|
|
||||||
const chunkHeaderSize = 8
|
|
||||||
|
|
||||||
// FourCC is a four character code.
|
|
||||||
type FourCC [4]byte
|
|
||||||
|
|
||||||
// LIST is the "LIST" FourCC.
|
|
||||||
var LIST = FourCC{'L', 'I', 'S', 'T'}
|
|
||||||
|
|
||||||
// NewReader returns the RIFF stream's form type, such as "AVI " or "WAVE", and
|
|
||||||
// its chunks as a *Reader.
|
|
||||||
func NewReader(r io.Reader) (formType FourCC, data *Reader, err error) {
|
|
||||||
var buf [chunkHeaderSize]byte
|
|
||||||
if _, err := io.ReadFull(r, buf[:]); err != nil {
|
|
||||||
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
|
||||||
err = errMissingRIFFChunkHeader
|
|
||||||
}
|
|
||||||
return FourCC{}, nil, err
|
|
||||||
}
|
|
||||||
if buf[0] != 'R' || buf[1] != 'I' || buf[2] != 'F' || buf[3] != 'F' {
|
|
||||||
return FourCC{}, nil, errMissingRIFFChunkHeader
|
|
||||||
}
|
|
||||||
return NewListReader(u32(buf[4:]), r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewListReader returns a LIST chunk's list type, such as "movi" or "wavl",
|
|
||||||
// and its chunks as a *Reader.
|
|
||||||
func NewListReader(chunkLen uint32, chunkData io.Reader) (listType FourCC, data *Reader, err error) {
|
|
||||||
if chunkLen < 4 {
|
|
||||||
return FourCC{}, nil, errShortChunkData
|
|
||||||
}
|
|
||||||
z := &Reader{r: chunkData}
|
|
||||||
if _, err := io.ReadFull(chunkData, z.buf[:4]); err != nil {
|
|
||||||
if err == io.EOF || err == io.ErrUnexpectedEOF {
|
|
||||||
err = errShortChunkData
|
|
||||||
}
|
|
||||||
return FourCC{}, nil, err
|
|
||||||
}
|
|
||||||
z.totalLen = chunkLen - 4
|
|
||||||
return FourCC{z.buf[0], z.buf[1], z.buf[2], z.buf[3]}, z, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reader reads chunks from an underlying io.Reader.
|
|
||||||
type Reader struct {
|
|
||||||
r io.Reader
|
|
||||||
err error
|
|
||||||
|
|
||||||
totalLen uint32
|
|
||||||
chunkLen uint32
|
|
||||||
|
|
||||||
chunkReader *chunkReader
|
|
||||||
buf [chunkHeaderSize]byte
|
|
||||||
padded bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next returns the next chunk's ID, length and data. It returns io.EOF if there
|
|
||||||
// are no more chunks. The io.Reader returned becomes stale after the next Next
|
|
||||||
// call, and should no longer be used.
|
|
||||||
//
|
|
||||||
// It is valid to call Next even if all of the previous chunk's data has not
|
|
||||||
// been read.
|
|
||||||
func (z *Reader) Next() (chunkID FourCC, chunkLen uint32, chunkData io.Reader, err error) {
|
|
||||||
if z.err != nil {
|
|
||||||
return FourCC{}, 0, nil, z.err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Drain the rest of the previous chunk.
|
|
||||||
if z.chunkLen != 0 {
|
|
||||||
want := z.chunkLen
|
|
||||||
var got int64
|
|
||||||
got, z.err = io.Copy(ioutil.Discard, z.chunkReader)
|
|
||||||
if z.err == nil && uint32(got) != want {
|
|
||||||
z.err = errShortChunkData
|
|
||||||
}
|
|
||||||
if z.err != nil {
|
|
||||||
return FourCC{}, 0, nil, z.err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
z.chunkReader = nil
|
|
||||||
if z.padded {
|
|
||||||
if z.totalLen == 0 {
|
|
||||||
z.err = errListSubchunkTooLong
|
|
||||||
return FourCC{}, 0, nil, z.err
|
|
||||||
}
|
|
||||||
z.totalLen--
|
|
||||||
_, z.err = io.ReadFull(z.r, z.buf[:1])
|
|
||||||
if z.err != nil {
|
|
||||||
if z.err == io.EOF {
|
|
||||||
z.err = errMissingPaddingByte
|
|
||||||
}
|
|
||||||
return FourCC{}, 0, nil, z.err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We are done if we have no more data.
|
|
||||||
if z.totalLen == 0 {
|
|
||||||
z.err = io.EOF
|
|
||||||
return FourCC{}, 0, nil, z.err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the next chunk header.
|
|
||||||
if z.totalLen < chunkHeaderSize {
|
|
||||||
z.err = errShortChunkHeader
|
|
||||||
return FourCC{}, 0, nil, z.err
|
|
||||||
}
|
|
||||||
z.totalLen -= chunkHeaderSize
|
|
||||||
if _, z.err = io.ReadFull(z.r, z.buf[:chunkHeaderSize]); z.err != nil {
|
|
||||||
if z.err == io.EOF || z.err == io.ErrUnexpectedEOF {
|
|
||||||
z.err = errShortChunkHeader
|
|
||||||
}
|
|
||||||
return FourCC{}, 0, nil, z.err
|
|
||||||
}
|
|
||||||
chunkID = FourCC{z.buf[0], z.buf[1], z.buf[2], z.buf[3]}
|
|
||||||
z.chunkLen = u32(z.buf[4:])
|
|
||||||
if z.chunkLen > z.totalLen {
|
|
||||||
z.err = errListSubchunkTooLong
|
|
||||||
return FourCC{}, 0, nil, z.err
|
|
||||||
}
|
|
||||||
z.padded = z.chunkLen&1 == 1
|
|
||||||
z.chunkReader = &chunkReader{z}
|
|
||||||
return chunkID, z.chunkLen, z.chunkReader, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type chunkReader struct {
|
|
||||||
z *Reader
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *chunkReader) Read(p []byte) (int, error) {
|
|
||||||
if c != c.z.chunkReader {
|
|
||||||
return 0, errStaleReader
|
|
||||||
}
|
|
||||||
z := c.z
|
|
||||||
if z.err != nil {
|
|
||||||
if z.err == io.EOF {
|
|
||||||
return 0, errStaleReader
|
|
||||||
}
|
|
||||||
return 0, z.err
|
|
||||||
}
|
|
||||||
|
|
||||||
n := int(z.chunkLen)
|
|
||||||
if n == 0 {
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
if n < 0 {
|
|
||||||
// Converting uint32 to int overflowed.
|
|
||||||
n = math.MaxInt32
|
|
||||||
}
|
|
||||||
if n > len(p) {
|
|
||||||
n = len(p)
|
|
||||||
}
|
|
||||||
n, err := z.r.Read(p[:n])
|
|
||||||
z.totalLen -= uint32(n)
|
|
||||||
z.chunkLen -= uint32(n)
|
|
||||||
if err != io.EOF {
|
|
||||||
z.err = err
|
|
||||||
}
|
|
||||||
return n, err
|
|
||||||
}
|
|
|
@ -1,69 +0,0 @@
|
||||||
// Copyright 2016 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 riff
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func encodeU32(u uint32) []byte {
|
|
||||||
return []byte{
|
|
||||||
byte(u >> 0),
|
|
||||||
byte(u >> 8),
|
|
||||||
byte(u >> 16),
|
|
||||||
byte(u >> 24),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestShortChunks(t *testing.T) {
|
|
||||||
// s is a RIFF(ABCD) with allegedly 256 bytes of data (excluding the
|
|
||||||
// leading 8-byte "RIFF\x00\x01\x00\x00"). The first chunk of that ABCD
|
|
||||||
// list is an abcd chunk of length m followed by n zeroes.
|
|
||||||
for _, m := range []uint32{0, 8, 15, 200, 300} {
|
|
||||||
for _, n := range []int{0, 1, 2, 7} {
|
|
||||||
s := []byte("RIFF\x00\x01\x00\x00ABCDabcd")
|
|
||||||
s = append(s, encodeU32(m)...)
|
|
||||||
s = append(s, make([]byte, n)...)
|
|
||||||
_, r, err := NewReader(bytes.NewReader(s))
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("m=%d, n=%d: NewReader: %v", m, n, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, _, err0 := r.Next()
|
|
||||||
// The total "ABCD" list length is 256 bytes, of which the first 12
|
|
||||||
// bytes are "ABCDabcd" plus the 4-byte encoding of m. If the
|
|
||||||
// "abcd" subchunk length (m) plus those 12 bytes is greater than
|
|
||||||
// the total list length, we have an invalid RIFF, and we expect an
|
|
||||||
// errListSubchunkTooLong error.
|
|
||||||
if m+12 > 256 {
|
|
||||||
if err0 != errListSubchunkTooLong {
|
|
||||||
t.Errorf("m=%d, n=%d: Next #0: got %v, want %v", m, n, err0, errListSubchunkTooLong)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Otherwise, we expect a nil error.
|
|
||||||
if err0 != nil {
|
|
||||||
t.Errorf("m=%d, n=%d: Next #0: %v", m, n, err0)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, _, err1 := r.Next()
|
|
||||||
// If m > 0, then m > n, so that "abcd" subchunk doesn't have m
|
|
||||||
// bytes of data. If m == 0, then that "abcd" subchunk is OK in
|
|
||||||
// that it has 0 extra bytes of data, but the next subchunk (8 byte
|
|
||||||
// header plus body) is missing, as we only have n < 8 more bytes.
|
|
||||||
want := errShortChunkData
|
|
||||||
if m == 0 {
|
|
||||||
want = errShortChunkHeader
|
|
||||||
}
|
|
||||||
if err1 != want {
|
|
||||||
t.Errorf("m=%d, n=%d: Next #1: got %v, want %v", m, n, err1, want)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Binary file not shown.
Before Width: | Height: | Size: 171 KiB |
BIN
vendor/golang.org/x/image/font/testdata/blue-purple-pink-large.no-filter.lossy.webp
generated
vendored
BIN
vendor/golang.org/x/image/font/testdata/blue-purple-pink-large.no-filter.lossy.webp
generated
vendored
Binary file not shown.
Before Width: | Height: | Size: 22 KiB |
Binary file not shown.
Before Width: | Height: | Size: 116 KiB |
BIN
vendor/golang.org/x/image/font/testdata/blue-purple-pink-large.normal-filter.lossy.webp
generated
vendored
BIN
vendor/golang.org/x/image/font/testdata/blue-purple-pink-large.normal-filter.lossy.webp
generated
vendored
Binary file not shown.
Before Width: | Height: | Size: 22 KiB |
Binary file not shown.
Before Width: | Height: | Size: 139 KiB |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue