shiny/iconvg: implement a decoder.
Change-Id: I488531c8ae5b929e178de481f30acf0f88833b83
Reviewed-on: https://go-review.googlesource.com/29698
Reviewed-by: David Crawshaw <crawshaw@golang.org>
diff --git a/shiny/iconvg/decode.go b/shiny/iconvg/decode.go
new file mode 100644
index 0000000..7626c24
--- /dev/null
+++ b/shiny/iconvg/decode.go
@@ -0,0 +1,479 @@
+// 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 iconvg
+
+import (
+ "bytes"
+ "errors"
+)
+
+var (
+ errInconsistentMetadataChunkLength = errors.New("iconvg: inconsistent metadata chunk length")
+ errInvalidMagicIdentifier = errors.New("iconvg: invalid magic identifier")
+ errInvalidMetadataChunkLength = errors.New("iconvg: invalid metadata chunk length")
+ errInvalidMetadataIdentifier = errors.New("iconvg: invalid metadata identifier")
+ errInvalidNumber = errors.New("iconvg: invalid number")
+ errInvalidNumberOfMetadataChunks = errors.New("iconvg: invalid number of metadata chunks")
+ errInvalidViewBox = errors.New("iconvg: invalid view box")
+ errUnsupportedDrawingOpcode = errors.New("iconvg: unsupported drawing opcode")
+ errUnsupportedMetadataIdentifier = errors.New("iconvg: unsupported metadata identifier")
+ errUnsupportedStylingOpcode = errors.New("iconvg: unsupported styling opcode")
+)
+
+var midDescriptions = [...]string{
+ midViewBox: "viewBox",
+ midSuggestedPalette: "suggested palette",
+}
+
+// Destination handles the actions decoded from an IconVG graphic's opcodes.
+//
+// When passed to Decode, the first method called (if any) will be Reset. No
+// methods will be called at all if an error is encountered in the encoded form
+// before the metadata is fully decoded.
+type Destination interface {
+ Reset(m Metadata)
+
+ // TODO: styling mode ops other than StartPath.
+
+ StartPath(adj int, x, y float32)
+ ClosePathEndPath()
+ ClosePathAbsMoveTo(x, y float32)
+ ClosePathRelMoveTo(x, y float32)
+
+ AbsHLineTo(x float32)
+ RelHLineTo(x float32)
+ AbsVLineTo(y float32)
+ RelVLineTo(y float32)
+ AbsLineTo(x, y float32)
+ RelLineTo(x, y float32)
+ AbsSmoothQuadTo(x, y float32)
+ RelSmoothQuadTo(x, y float32)
+ AbsQuadTo(x1, y1, x, y float32)
+ RelQuadTo(x1, y1, x, y float32)
+ AbsSmoothCubeTo(x2, y2, x, y float32)
+ RelSmoothCubeTo(x2, y2, x, y float32)
+ AbsCubeTo(x1, y1, x2, y2, x, y float32)
+ RelCubeTo(x1, y1, x2, y2, x, y float32)
+ AbsArcTo(rx, ry, xAxisRotation float32, largeArc, sweep bool, x, y float32)
+ RelArcTo(rx, ry, xAxisRotation float32, largeArc, sweep bool, x, y float32)
+}
+
+type printer func(b []byte, format string, args ...interface{})
+
+// DecodeOptions are the optional parameters to the Decode function.
+type DecodeOptions struct {
+ // Palette is an optional 64 color palette. If one isn't provided, the
+ // IconVG graphic's suggested palette will be used.
+ Palette *Palette
+}
+
+// DecodeMetadata decodes only the metadata in an IconVG graphic.
+func DecodeMetadata(src []byte) (m Metadata, err error) {
+ m.ViewBox = DefaultViewBox
+ m.Palette = DefaultPalette
+ if err = decode(nil, nil, &m, true, src, nil); err != nil {
+ return Metadata{}, err
+ }
+ return m, nil
+}
+
+// Decode decodes an IconVG graphic.
+func Decode(dst Destination, src []byte, opts *DecodeOptions) error {
+ m := Metadata{
+ ViewBox: DefaultViewBox,
+ Palette: DefaultPalette,
+ }
+ if opts != nil && opts.Palette != nil {
+ m.Palette = *opts.Palette
+ }
+ return decode(dst, nil, &m, false, src, opts)
+}
+
+func decode(dst Destination, p printer, m *Metadata, metadataOnly bool, src buffer, opts *DecodeOptions) (err error) {
+ if !bytes.HasPrefix(src, magicBytes) {
+ return errInvalidMagicIdentifier
+ }
+ if p != nil {
+ p(src[:len(magic)], "Magic identifier\n")
+ }
+ src = src[len(magic):]
+
+ nMetadataChunks, n := src.decodeNatural()
+ if n == 0 {
+ return errInvalidNumberOfMetadataChunks
+ }
+ if p != nil {
+ p(src[:n], "Number of metadata chunks: %d\n", nMetadataChunks)
+ }
+ src = src[n:]
+
+ for ; nMetadataChunks > 0; nMetadataChunks-- {
+ src, err = decodeMetadataChunk(p, m, src, opts)
+ if err != nil {
+ return err
+ }
+ }
+ if metadataOnly {
+ return nil
+ }
+ if dst != nil {
+ dst.Reset(*m)
+ }
+
+ mf := modeFunc(decodeStyling)
+ for len(src) > 0 {
+ mf, src, err = mf(dst, p, src)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func decodeMetadataChunk(p printer, m *Metadata, src buffer, opts *DecodeOptions) (src1 buffer, err error) {
+ length, n := src.decodeNatural()
+ if n == 0 {
+ return nil, errInvalidMetadataChunkLength
+ }
+ if p != nil {
+ p(src[:n], "Metadata chunk length: %d\n", length)
+ }
+ src = src[n:]
+ lenSrcWant := int64(len(src)) - int64(length)
+
+ mid, n := src.decodeNatural()
+ if n == 0 {
+ return nil, errInvalidMetadataIdentifier
+ }
+ if mid >= uint32(len(midDescriptions)) {
+ return nil, errUnsupportedMetadataIdentifier
+ }
+ if p != nil {
+ p(src[:n], "Metadata Identifier: %d (%s)\n", mid, midDescriptions[mid])
+ }
+ src = src[n:]
+
+ switch mid {
+ case midViewBox:
+ if m.ViewBox.Min[0], src, err = decodeNumber(p, src, buffer.decodeCoordinate); err != nil {
+ return nil, errInvalidViewBox
+ }
+ if m.ViewBox.Min[1], src, err = decodeNumber(p, src, buffer.decodeCoordinate); err != nil {
+ return nil, errInvalidViewBox
+ }
+ if m.ViewBox.Max[0], src, err = decodeNumber(p, src, buffer.decodeCoordinate); err != nil {
+ return nil, errInvalidViewBox
+ }
+ if m.ViewBox.Max[1], src, err = decodeNumber(p, src, buffer.decodeCoordinate); err != nil {
+ return nil, errInvalidViewBox
+ }
+ if m.ViewBox.Min[0] > m.ViewBox.Max[0] || m.ViewBox.Min[1] > m.ViewBox.Max[1] ||
+ isNaNOrInfinity(m.ViewBox.Min[0]) || isNaNOrInfinity(m.ViewBox.Min[1]) ||
+ isNaNOrInfinity(m.ViewBox.Max[0]) || isNaNOrInfinity(m.ViewBox.Max[1]) {
+ return nil, errInvalidViewBox
+ }
+
+ case midSuggestedPalette:
+ panic("TODO")
+
+ default:
+ return nil, errUnsupportedMetadataIdentifier
+ }
+
+ if int64(len(src)) != lenSrcWant {
+ return nil, errInconsistentMetadataChunkLength
+ }
+ return src, nil
+}
+
+// modeFunc is the decoding mode: whether we are decoding styling or drawing
+// opcodes.
+//
+// It is a function type. The decoding loop calls this function to decode and
+// execute the next opcode from the src buffer, returning the subsequent mode
+// and the remaining source bytes.
+type modeFunc func(dst Destination, p printer, src buffer) (modeFunc, buffer, error)
+
+func decodeStyling(dst Destination, p printer, src buffer) (modeFunc, buffer, error) {
+ switch opcode := src[0]; {
+ case opcode < 0xc0:
+ panic("TODO")
+ case opcode < 0xc7:
+ return decodeStartPath(dst, p, src, opcode)
+ case opcode == 0xc7:
+ panic("TODO")
+ }
+ return nil, nil, errUnsupportedStylingOpcode
+}
+
+func decodeStartPath(dst Destination, p printer, src buffer, opcode byte) (modeFunc, buffer, error) {
+ adj := int(opcode & 0x07)
+ if p != nil {
+ p(src[:1], "Start path, filled with CREG[CSEL-%d]; M (absolute moveTo)\n", adj)
+ }
+ src = src[1:]
+
+ x, src, err := decodeNumber(p, src, buffer.decodeCoordinate)
+ if err != nil {
+ return nil, nil, err
+ }
+ y, src, err := decodeNumber(p, src, buffer.decodeCoordinate)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ if dst != nil {
+ dst.StartPath(adj, x, y)
+ }
+
+ return decodeDrawing, src, nil
+}
+
+func decodeDrawing(dst Destination, p printer, src buffer) (mf modeFunc, src1 buffer, err error) {
+ var coords [6]float32
+
+ switch opcode := src[0]; {
+ case opcode < 0xe0:
+ op, nCoords, nReps := "", 0, 1+int(opcode&0x0f)
+ switch opcode >> 4 {
+ case 0x00, 0x01:
+ op = "L (absolute lineTo)"
+ nCoords = 2
+ nReps = 1 + int(opcode&0x1f)
+ case 0x02, 0x03:
+ op = "l (relative lineTo)"
+ nCoords = 2
+ nReps = 1 + int(opcode&0x1f)
+ case 0x04:
+ op = "T (absolute smooth quadTo)"
+ nCoords = 2
+ case 0x05:
+ op = "t (relative smooth quadTo)"
+ nCoords = 2
+ case 0x06:
+ op = "Q (absolute quadTo)"
+ nCoords = 4
+ case 0x07:
+ op = "q (relative quadTo)"
+ nCoords = 4
+ case 0x08:
+ op = "S (absolute smooth cubeTo)"
+ nCoords = 4
+ case 0x09:
+ op = "s (relative smooth cubeTo)"
+ nCoords = 4
+ case 0x0a:
+ op = "C (absolute cubeTo)"
+ nCoords = 6
+ case 0x0b:
+ op = "c (relative cubeTo)"
+ nCoords = 6
+ case 0x0c:
+ op = "A (absolute arcTo)"
+ nCoords = 0
+ case 0x0d:
+ op = "a (relative arcTo)"
+ nCoords = 0
+ }
+
+ if p != nil {
+ p(src[:1], "%s, %d reps\n", op, nReps)
+ }
+ src = src[1:]
+
+ for i := 0; i < nReps; i++ {
+ if p != nil && i != 0 {
+ p(nil, "%s, implicit\n", op)
+ }
+ src, err = decodeCoordinates(coords[:nCoords], p, src)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ if dst == nil {
+ continue
+ }
+ switch op[0] {
+ case 'L':
+ dst.AbsLineTo(coords[0], coords[1])
+ continue
+ case 'l':
+ dst.RelLineTo(coords[0], coords[1])
+ continue
+ case 'T':
+ dst.AbsSmoothQuadTo(coords[0], coords[1])
+ continue
+ case 't':
+ dst.RelSmoothQuadTo(coords[0], coords[1])
+ continue
+ case 'Q':
+ dst.AbsQuadTo(coords[0], coords[1], coords[2], coords[3])
+ continue
+ case 'q':
+ dst.RelQuadTo(coords[0], coords[1], coords[2], coords[3])
+ continue
+ case 'S':
+ dst.AbsSmoothCubeTo(coords[0], coords[1], coords[2], coords[3])
+ continue
+ case 's':
+ dst.RelSmoothCubeTo(coords[0], coords[1], coords[2], coords[3])
+ continue
+ case 'C':
+ dst.AbsCubeTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5])
+ continue
+ case 'c':
+ dst.RelCubeTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5])
+ continue
+ }
+
+ // We have an absolute or relative arcTo.
+ src, err = decodeCoordinates(coords[:3], p, src)
+ if err != nil {
+ return nil, nil, err
+ }
+ var largeArc, sweep bool
+ largeArc, sweep, src, err = decodeArcToFlags(p, src)
+ if err != nil {
+ return nil, nil, err
+ }
+ src, err = decodeCoordinates(coords[4:6], p, src)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ if op[0] == 'A' {
+ dst.AbsArcTo(coords[0], coords[1], coords[2], largeArc, sweep, coords[4], coords[5])
+ } else {
+ dst.RelArcTo(coords[0], coords[1], coords[2], largeArc, sweep, coords[4], coords[5])
+ }
+ }
+
+ case opcode == 0xe1:
+ if p != nil {
+ p(src[:1], "z (closePath); end path\n")
+ }
+ src = src[1:]
+ if dst != nil {
+ dst.ClosePathEndPath()
+ }
+ return decodeStyling, src, nil
+
+ case opcode == 0xe2:
+ if p != nil {
+ p(src[:1], "z (closePath); M (absolute moveTo)\n")
+ }
+ src = src[1:]
+ src, err = decodeCoordinates(coords[:2], p, src)
+ if err != nil {
+ return nil, nil, err
+ }
+ if dst != nil {
+ dst.ClosePathAbsMoveTo(coords[0], coords[1])
+ }
+
+ case opcode == 0xe3:
+ if p != nil {
+ p(src[:1], "z (closePath); m (relative moveTo)\n")
+ }
+ src = src[1:]
+ src, err = decodeCoordinates(coords[:2], p, src)
+ if err != nil {
+ return nil, nil, err
+ }
+ if dst != nil {
+ dst.ClosePathRelMoveTo(coords[0], coords[1])
+ }
+
+ case opcode == 0xe6:
+ if p != nil {
+ p(src[:1], "H (absolute horizontal lineTo)\n")
+ }
+ src = src[1:]
+ src, err = decodeCoordinates(coords[:1], p, src)
+ if err != nil {
+ return nil, nil, err
+ }
+ if dst != nil {
+ dst.AbsHLineTo(coords[0])
+ }
+
+ case opcode == 0xe7:
+ if p != nil {
+ p(src[:1], "h (relative horizontal lineTo)\n")
+ }
+ src = src[1:]
+ src, err = decodeCoordinates(coords[:1], p, src)
+ if err != nil {
+ return nil, nil, err
+ }
+ if dst != nil {
+ dst.RelHLineTo(coords[0])
+ }
+
+ case opcode == 0xe8:
+ if p != nil {
+ p(src[:1], "V (absolute vertical lineTo)\n")
+ }
+ src = src[1:]
+ src, err = decodeCoordinates(coords[:1], p, src)
+ if err != nil {
+ return nil, nil, err
+ }
+ if dst != nil {
+ dst.AbsVLineTo(coords[0])
+ }
+
+ case opcode == 0xe9:
+ if p != nil {
+ p(src[:1], "v (relative vertical lineTo)\n")
+ }
+ src = src[1:]
+ src, err = decodeCoordinates(coords[:1], p, src)
+ if err != nil {
+ return nil, nil, err
+ }
+ if dst != nil {
+ dst.RelVLineTo(coords[0])
+ }
+
+ default:
+ return nil, nil, errUnsupportedDrawingOpcode
+ }
+ return decodeDrawing, src, nil
+}
+
+type decodeNumberFunc func(buffer) (float32, int)
+
+func decodeNumber(p printer, src buffer, dnf decodeNumberFunc) (float32, buffer, error) {
+ x, n := dnf(src)
+ if n == 0 {
+ return 0, nil, errInvalidNumber
+ }
+ if p != nil {
+ p(src[:n], " %+g\n", x)
+ }
+ return x, src[n:], nil
+}
+
+func decodeCoordinates(coords []float32, p printer, src buffer) (src1 buffer, err error) {
+ for i := range coords {
+ coords[i], src, err = decodeNumber(p, src, buffer.decodeCoordinate)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return src, nil
+}
+
+func decodeArcToFlags(p printer, src buffer) (bool, bool, buffer, error) {
+ x, n := src.decodeNatural()
+ if n == 0 {
+ return false, false, nil, errInvalidNumber
+ }
+ if p != nil {
+ p(src[:n], " %#x (largeArc=%d, sweep=%d)\n", x, (x>>0)&0x01, (x>>1)&0x01)
+ }
+ return (x>>0)&0x01 != 0, (x>>1)&0x01 != 0, src[n:], nil
+}
diff --git a/shiny/iconvg/decode_test.go b/shiny/iconvg/decode_test.go
new file mode 100644
index 0000000..4aa2cd6
--- /dev/null
+++ b/shiny/iconvg/decode_test.go
@@ -0,0 +1,212 @@
+// 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 iconvg
+
+import (
+ "bytes"
+ "fmt"
+ "image"
+ "image/draw"
+ "image/png"
+ "os"
+ "strings"
+ "testing"
+)
+
+// disassemble returns a disassembly of an encoded IconVG graphic. Users of
+// this package aren't expected to want to do this, so it lives in a _test.go
+// file, but it can be useful for debugging.
+func disassemble(src []byte) (string, error) {
+ w := new(bytes.Buffer)
+ p := func(b []byte, format string, args ...interface{}) {
+ const hex = "0123456789abcdef"
+ var buf [14]byte
+ for i := range buf {
+ buf[i] = ' '
+ }
+ for i, x := range b {
+ buf[3*i+0] = hex[x>>4]
+ buf[3*i+1] = hex[x&0x0f]
+ }
+ w.Write(buf[:])
+ fmt.Fprintf(w, format, args...)
+ }
+ m := Metadata{}
+ if err := decode(nil, p, &m, false, buffer(src), nil); err != nil {
+ return "", err
+ }
+ return w.String(), nil
+}
+
+var (
+ _ Destination = (*Encoder)(nil)
+ _ Destination = (*Rasterizer)(nil)
+)
+
+// encodePNG is useful for manually debugging the tests.
+func encodePNG(dstFilename string, src image.Image) error {
+ f, err := os.Create(dstFilename)
+ if err != nil {
+ return err
+ }
+ encErr := png.Encode(f, src)
+ closeErr := f.Close()
+ if encErr != nil {
+ return encErr
+ }
+ return closeErr
+}
+
+func diffLines(t *testing.T, got, want string) {
+ gotLines := strings.Split(got, "\n")
+ wantLines := strings.Split(want, "\n")
+ for i := 1; ; i++ {
+ if len(gotLines) == 0 {
+ t.Errorf("line %d:\ngot %q\nwant %q", i, "", wantLines[0])
+ return
+ }
+ if len(wantLines) == 0 {
+ t.Errorf("line %d:\ngot %q\nwant %q", i, gotLines[0], "")
+ return
+ }
+ g, w := gotLines[0], wantLines[0]
+ gotLines = gotLines[1:]
+ wantLines = wantLines[1:]
+ if g != w {
+ t.Errorf("line %d:\ngot %q\nwant %q", i, g, w)
+ return
+ }
+ }
+}
+
+func rasterizeASCIIArt(width int, encoded []byte) (string, error) {
+ dst := image.NewAlpha(image.Rect(0, 0, width, width))
+ var z Rasterizer
+ z.SetDstImage(dst, dst.Bounds(), draw.Src)
+ if err := Decode(&z, encoded, nil); err != nil {
+ return "", err
+ }
+
+ const asciiArt = ".++8"
+ buf := make([]byte, 0, width*(width+1))
+ for y := 0; y < width; y++ {
+ for x := 0; x < width; x++ {
+ a := dst.AlphaAt(x, y).A
+ buf = append(buf, asciiArt[a>>6])
+ }
+ buf = append(buf, '\n')
+ }
+ return string(buf), nil
+}
+
+func TestDisassembleActionInfo(t *testing.T) {
+ got, err := disassemble(actionInfoIconVG)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ want := strings.Join([]string{
+ "89 49 56 47 Magic identifier",
+ "02 Number of metadata chunks: 1",
+ "0a Metadata chunk length: 5",
+ "00 Metadata Identifier: 0 (viewBox)",
+ "50 -24",
+ "50 -24",
+ "b0 +24",
+ "b0 +24",
+ "c0 Start path, filled with CREG[CSEL-0]; M (absolute moveTo)",
+ "80 +0",
+ "58 -20",
+ "a0 C (absolute cubeTo), 1 reps",
+ "cf cc 30 c1 -11.049999",
+ "58 -20",
+ "58 -20",
+ "cf cc 30 c1 -11.049999",
+ "58 -20",
+ "80 +0",
+ "91 s (relative smooth cubeTo), 2 reps",
+ "37 33 0f 41 +8.950001",
+ "a8 +20",
+ "a8 +20",
+ "a8 +20",
+ " s (relative smooth cubeTo), implicit",
+ "a8 +20",
+ "37 33 0f c1 -8.950001",
+ "a8 +20",
+ "58 -20",
+ "80 S (absolute smooth cubeTo), 1 reps",
+ "cf cc 30 41 +11.049999",
+ "58 -20",
+ "80 +0",
+ "58 -20",
+ "e3 z (closePath); m (relative moveTo)",
+ "84 +2",
+ "bc +30",
+ "e7 h (relative horizontal lineTo)",
+ "78 -4",
+ "e8 V (absolute vertical lineTo)",
+ "7c -2",
+ "e7 h (relative horizontal lineTo)",
+ "88 +4",
+ "e9 v (relative vertical lineTo)",
+ "98 +12",
+ "e3 z (closePath); m (relative moveTo)",
+ "80 +0",
+ "60 -16",
+ "e7 h (relative horizontal lineTo)",
+ "78 -4",
+ "e9 v (relative vertical lineTo)",
+ "78 -4",
+ "e7 h (relative horizontal lineTo)",
+ "88 +4",
+ "e9 v (relative vertical lineTo)",
+ "88 +4",
+ "e1 z (closePath); end path",
+ }, "\n") + "\n"
+
+ if got != want {
+ t.Errorf("got:\n%s\nwant:\n%s", got, want)
+ diffLines(t, got, want)
+ }
+}
+
+func TestDecodeActionInfo(t *testing.T) {
+ got, err := rasterizeASCIIArt(24, actionInfoIconVG)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ want := strings.Join([]string{
+ "........................",
+ "........................",
+ "........++8888++........",
+ "......+8888888888+......",
+ ".....+888888888888+.....",
+ "....+88888888888888+....",
+ "...+8888888888888888+...",
+ "...88888888..88888888...",
+ "..+88888888..88888888+..",
+ "..+888888888888888888+..",
+ "..88888888888888888888..",
+ "..888888888..888888888..",
+ "..888888888..888888888..",
+ "..888888888..888888888..",
+ "..+88888888..88888888+..",
+ "..+88888888..88888888+..",
+ "...88888888..88888888...",
+ "...+8888888888888888+...",
+ "....+88888888888888+....",
+ ".....+888888888888+.....",
+ "......+8888888888+......",
+ "........++8888++........",
+ "........................",
+ "........................",
+ }, "\n") + "\n"
+
+ if got != want {
+ t.Errorf("got:\n%s\nwant:\n%s", got, want)
+ diffLines(t, got, want)
+ }
+}
diff --git a/shiny/iconvg/encode.go b/shiny/iconvg/encode.go
index 9822bd4..4c0d950 100644
--- a/shiny/iconvg/encode.go
+++ b/shiny/iconvg/encode.go
@@ -16,6 +16,8 @@
errStylingOpsUsedInDrawingMode = errors.New("iconvg: styling ops used in drawing mode")
)
+// TODO: delete the NewEncoder function, and just make the zero value usable.
+
// NewEncoder returns a new Encoder for the given Metadata.
func NewEncoder(m Metadata) *Encoder {
e := &Encoder{
@@ -226,7 +228,7 @@
}
if op := drawOps[e.drawOp]; op.nArgs == 0 {
- e.buf = append(e.buf, op.opCodeBase)
+ e.buf = append(e.buf, op.opcodeBase)
} else {
n := len(e.drawArgs) / int(op.nArgs)
for i := 0; n > 0; {
@@ -234,7 +236,7 @@
if m > int(op.maxRepCount) {
m = int(op.maxRepCount)
}
- e.buf = append(e.buf, op.opCodeBase+uint8(m)-1)
+ e.buf = append(e.buf, op.opcodeBase+uint8(m)-1)
switch e.drawOp {
default:
@@ -263,7 +265,7 @@
}
var drawOps = [256]struct {
- opCodeBase byte
+ opcodeBase byte
maxRepCount uint8
nArgs uint8
}{
diff --git a/shiny/iconvg/iconvg.go b/shiny/iconvg/iconvg.go
index d65615a..ec5a881 100644
--- a/shiny/iconvg/iconvg.go
+++ b/shiny/iconvg/iconvg.go
@@ -13,8 +13,14 @@
const magic = "\x89IVG"
+var magicBytes = []byte(magic)
+
var positiveInfinity = math.Float32frombits(0x7f800000)
+func isNaNOrInfinity(f float32) bool {
+ return math.Float32bits(f)&0x7f800000 == 0x7f800000
+}
+
const (
midViewBox = 0
midSuggestedPalette = 1
diff --git a/shiny/iconvg/rasterizer.go b/shiny/iconvg/rasterizer.go
new file mode 100644
index 0000000..e774b6a
--- /dev/null
+++ b/shiny/iconvg/rasterizer.go
@@ -0,0 +1,254 @@
+// 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 iconvg
+
+import (
+ "image"
+ "image/color"
+ "image/draw"
+
+ "golang.org/x/image/math/f32"
+ "golang.org/x/image/vector"
+)
+
+const (
+ smoothTypeNone = iota
+ smoothTypeQuad
+ smoothTypeCube
+)
+
+// Rasterizer is a Destination that draws an IconVG graphic onto a raster
+// image.
+//
+// The zero value is usable, in that it has no raster image to draw onto, so
+// that calling Decode with this Destination is a no-op (other than checking
+// the encoded form for errors in the byte code). Call SetDstImage to change
+// the raster image, before calling Decode or between calls to Decode.
+type Rasterizer struct {
+ z vector.Rasterizer
+
+ dst draw.Image
+ r image.Rectangle
+ drawOp draw.Op
+
+ // scale and bias transforms the metadata.ViewBox rectangle to the (0, 0) -
+ // (r.Dx(), r.Dy()) rectangle.
+ scaleX float32
+ biasX float32
+ scaleY float32
+ biasY float32
+
+ metadata Metadata
+
+ firstStartPath bool
+ prevSmoothType uint8
+ prevSmoothPoint f32.Vec2
+
+ cSel uint32
+ nSel uint32
+ lod0 float32
+ lod1 float32
+
+ creg [64]color.RGBA
+ nreg [64]float32
+}
+
+// SetDstImage sets the Rasterizer to draw onto a destination image, given by
+// dst and r, with the given compositing operator.
+//
+// The IconVG graphic (which does not have a fixed size in pixels) will be
+// scaled in the X and Y dimensions to fit the rectangle r. The scaling factors
+// may differ in the two dimensions.
+func (z *Rasterizer) SetDstImage(dst draw.Image, r image.Rectangle, drawOp draw.Op) {
+ z.dst = dst
+ if r.Empty() {
+ r = image.Rectangle{}
+ }
+ z.r = r
+ z.drawOp = drawOp
+ z.recalcTransform()
+}
+
+// Reset resets the Rasterizer for the given Metadata.
+func (z *Rasterizer) Reset(m Metadata) {
+ z.metadata = m
+ z.firstStartPath = true
+ z.prevSmoothType = smoothTypeNone
+ z.prevSmoothPoint = f32.Vec2{}
+ z.cSel = 0
+ z.nSel = 0
+ z.lod0 = 0
+ z.lod1 = positiveInfinity
+ z.creg = [64]color.RGBA{}
+ z.nreg = [64]float32{}
+ z.recalcTransform()
+}
+
+func (z *Rasterizer) recalcTransform() {
+ z.scaleX = float32(z.r.Dx()) / (z.metadata.ViewBox.Max[0] - z.metadata.ViewBox.Min[0])
+ z.biasX = -z.metadata.ViewBox.Min[0]
+ z.scaleY = float32(z.r.Dy()) / (z.metadata.ViewBox.Max[1] - z.metadata.ViewBox.Min[1])
+ z.biasY = -z.metadata.ViewBox.Min[1]
+}
+
+func (z *Rasterizer) absX(x float32) float32 { return z.scaleX * (x + z.biasX) }
+func (z *Rasterizer) absY(y float32) float32 { return z.scaleY * (y + z.biasY) }
+func (z *Rasterizer) relX(x float32) float32 { return z.scaleX * x }
+func (z *Rasterizer) relY(y float32) float32 { return z.scaleY * y }
+
+func (z *Rasterizer) absVec2(x, y float32) f32.Vec2 {
+ return f32.Vec2{z.absX(x), z.absY(y)}
+}
+
+func (z *Rasterizer) relVec2(x, y float32) f32.Vec2 {
+ pen := z.z.Pen()
+ return f32.Vec2{pen[0] + z.relX(x), pen[1] + z.relY(y)}
+}
+
+// implicitSmoothPoint returns the implicit control point for smooth-quadratic
+// and smooth-cubic Bézier curves.
+//
+// https://www.w3.org/TR/SVG/paths.html#PathDataCurveCommands says, "The first
+// control point is assumed to be the reflection of the second control point on
+// the previous command relative to the current point. (If there is no previous
+// command or if the previous command was not [a quadratic or cubic command],
+// assume the first control point is coincident with the current point.)"
+func (z *Rasterizer) implicitSmoothPoint(thisSmoothType uint8) f32.Vec2 {
+ pen := z.z.Pen()
+ if z.prevSmoothType != thisSmoothType {
+ return pen
+ }
+ return f32.Vec2{
+ 2*pen[0] - z.prevSmoothPoint[0],
+ 2*pen[1] - z.prevSmoothPoint[1],
+ }
+}
+
+func (z *Rasterizer) StartPath(adj int, x, y float32) {
+ // TODO: note adj, use it in ClosePathEndPath.
+
+ z.z.Reset(z.r.Dx(), z.r.Dy())
+ if z.firstStartPath {
+ z.firstStartPath = false
+ z.z.DrawOp = z.drawOp
+ }
+ z.prevSmoothType = smoothTypeNone
+ z.z.MoveTo(z.absVec2(x, y))
+}
+
+func (z *Rasterizer) ClosePathEndPath() {
+ z.z.ClosePath()
+ if z.dst == nil {
+ return
+ }
+ // TODO: don't assume image.Opaque.
+ z.z.Draw(z.dst, z.r, image.Opaque, image.Point{})
+}
+
+func (z *Rasterizer) ClosePathAbsMoveTo(x, y float32) {
+ z.prevSmoothType = smoothTypeNone
+ z.z.ClosePath()
+ z.z.MoveTo(z.absVec2(x, y))
+}
+
+func (z *Rasterizer) ClosePathRelMoveTo(x, y float32) {
+ z.prevSmoothType = smoothTypeNone
+ z.z.ClosePath()
+ z.z.MoveTo(z.relVec2(x, y))
+}
+
+func (z *Rasterizer) AbsHLineTo(x float32) {
+ z.prevSmoothType = smoothTypeNone
+ pen := z.z.Pen()
+ z.z.LineTo(f32.Vec2{z.absX(x), pen[1]})
+}
+
+func (z *Rasterizer) RelHLineTo(x float32) {
+ z.prevSmoothType = smoothTypeNone
+ pen := z.z.Pen()
+ z.z.LineTo(f32.Vec2{pen[0] + z.relX(x), pen[1]})
+}
+
+func (z *Rasterizer) AbsVLineTo(y float32) {
+ z.prevSmoothType = smoothTypeNone
+ pen := z.z.Pen()
+ z.z.LineTo(f32.Vec2{pen[0], z.absY(y)})
+}
+
+func (z *Rasterizer) RelVLineTo(y float32) {
+ z.prevSmoothType = smoothTypeNone
+ pen := z.z.Pen()
+ z.z.LineTo(f32.Vec2{pen[0], pen[1] + z.relY(y)})
+}
+
+func (z *Rasterizer) AbsLineTo(x, y float32) {
+ z.prevSmoothType = smoothTypeNone
+ z.z.LineTo(z.absVec2(x, y))
+}
+
+func (z *Rasterizer) RelLineTo(x, y float32) {
+ z.prevSmoothType = smoothTypeNone
+ z.z.LineTo(z.relVec2(x, y))
+}
+
+func (z *Rasterizer) AbsSmoothQuadTo(x, y float32) {
+ z.prevSmoothType = smoothTypeQuad
+ z.prevSmoothPoint = z.implicitSmoothPoint(smoothTypeQuad)
+ z.z.QuadTo(z.prevSmoothPoint, z.absVec2(x, y))
+}
+
+func (z *Rasterizer) RelSmoothQuadTo(x, y float32) {
+ z.prevSmoothType = smoothTypeQuad
+ z.prevSmoothPoint = z.implicitSmoothPoint(smoothTypeQuad)
+ z.z.QuadTo(z.prevSmoothPoint, z.relVec2(x, y))
+}
+
+func (z *Rasterizer) AbsQuadTo(x1, y1, x, y float32) {
+ z.prevSmoothType = smoothTypeQuad
+ z.prevSmoothPoint = z.absVec2(x1, y1)
+ z.z.QuadTo(z.prevSmoothPoint, z.absVec2(x, y))
+}
+
+func (z *Rasterizer) RelQuadTo(x1, y1, x, y float32) {
+ z.prevSmoothType = smoothTypeQuad
+ z.prevSmoothPoint = z.relVec2(x1, y1)
+ z.z.QuadTo(z.prevSmoothPoint, z.relVec2(x, y))
+}
+
+func (z *Rasterizer) AbsSmoothCubeTo(x2, y2, x, y float32) {
+ p1 := z.implicitSmoothPoint(smoothTypeCube)
+ z.prevSmoothType = smoothTypeCube
+ z.prevSmoothPoint = z.absVec2(x2, y2)
+ z.z.CubeTo(p1, z.prevSmoothPoint, z.absVec2(x, y))
+}
+
+func (z *Rasterizer) RelSmoothCubeTo(x2, y2, x, y float32) {
+ p1 := z.implicitSmoothPoint(smoothTypeCube)
+ z.prevSmoothType = smoothTypeCube
+ z.prevSmoothPoint = z.relVec2(x2, y2)
+ z.z.CubeTo(p1, z.prevSmoothPoint, z.relVec2(x, y))
+}
+
+func (z *Rasterizer) AbsCubeTo(x1, y1, x2, y2, x, y float32) {
+ z.prevSmoothType = smoothTypeCube
+ z.prevSmoothPoint = z.absVec2(x2, y2)
+ z.z.CubeTo(z.absVec2(x1, y1), z.prevSmoothPoint, z.absVec2(x, y))
+}
+
+func (z *Rasterizer) RelCubeTo(x1, y1, x2, y2, x, y float32) {
+ z.prevSmoothType = smoothTypeCube
+ z.prevSmoothPoint = z.relVec2(x2, y2)
+ z.z.CubeTo(z.relVec2(x1, y1), z.prevSmoothPoint, z.relVec2(x, y))
+}
+
+func (z *Rasterizer) AbsArcTo(rx, ry, xAxisRotation float32, largeArc, sweep bool, x, y float32) {
+ z.prevSmoothType = smoothTypeNone
+ // TODO: implement.
+}
+
+func (z *Rasterizer) RelArcTo(rx, ry, xAxisRotation float32, largeArc, sweep bool, x, y float32) {
+ z.prevSmoothType = smoothTypeNone
+ // TODO: implement.
+}