shiny/iconvg: implement an encoder.
Change-Id: Ib34dca2ae43ba116784a098d0fa2d4cace92aef1
Reviewed-on: https://go-review.googlesource.com/29135
Reviewed-by: David Crawshaw <crawshaw@golang.org>
diff --git a/shiny/iconvg/buffer.go b/shiny/iconvg/buffer.go
new file mode 100644
index 0000000..473421b
--- /dev/null
+++ b/shiny/iconvg/buffer.go
@@ -0,0 +1,155 @@
+// 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 (
+ "math"
+)
+
+// TODO: decoding and encoding colors, not just numbers.
+
+// buffer holds an encoded IconVG graphic.
+//
+// The decodeXxx methods return the decoded value and an integer n, the number
+// of bytes that value was encoded in. They return n == 0 if an error occured.
+//
+// The encodeXxx methods append to the buffer, modifying the slice in place.
+type buffer []byte
+
+func (b buffer) decodeNatural() (u uint32, n int) {
+ if len(b) < 1 {
+ return 0, 0
+ }
+ x := b[0]
+ if x&0x01 == 0 {
+ return uint32(x) >> 1, 1
+ }
+ if x&0x02 == 0 {
+ if len(b) >= 2 {
+ y := uint16(b[0]) | uint16(b[1])<<8
+ return uint32(y) >> 2, 2
+ }
+ return 0, 0
+ }
+ if len(b) >= 4 {
+ y := uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
+ return y >> 2, 4
+ }
+ return 0, 0
+}
+
+func (b buffer) decodeReal() (f float32, n int) {
+ switch u, n := b.decodeNatural(); n {
+ case 0:
+ return 0, n
+ case 1:
+ return float32(u), n
+ case 2:
+ return float32(u), n
+ default:
+ return math.Float32frombits(u << 2), n
+ }
+}
+
+func (b buffer) decodeCoordinate() (f float32, n int) {
+ switch u, n := b.decodeNatural(); n {
+ case 0:
+ return 0, n
+ case 1:
+ return float32(int32(u) - 64), n
+ case 2:
+ return float32(int32(u)-64*128) / 64, n
+ default:
+ return math.Float32frombits(u << 2), n
+ }
+}
+
+func (b buffer) decodeZeroToOne() (f float32, n int) {
+ switch u, n := b.decodeNatural(); n {
+ case 0:
+ return 0, n
+ case 1:
+ return float32(u) / 120, n
+ case 2:
+ return float32(u) / 15120, n
+ default:
+ return math.Float32frombits(u << 2), n
+ }
+}
+
+func (b *buffer) encodeNatural(u uint32) {
+ if u < 1<<7 {
+ u = (u << 1)
+ *b = append(*b, uint8(u))
+ return
+ }
+ if u < 1<<14 {
+ u = (u << 2) | 1
+ *b = append(*b, uint8(u), uint8(u>>8))
+ return
+ }
+ u = (u << 2) | 3
+ *b = append(*b, uint8(u), uint8(u>>8), uint8(u>>16), uint8(u>>24))
+}
+
+func (b *buffer) encodeReal(f float32) {
+ if u := uint32(f); float32(u) == f && u < 1<<14 {
+ if u < 1<<7 {
+ u = (u << 1)
+ *b = append(*b, uint8(u))
+ } else {
+ u = (u << 2) | 1
+ *b = append(*b, uint8(u), uint8(u>>8))
+ }
+ return
+ }
+ b.encode4ByteReal(f)
+}
+
+func (b *buffer) encode4ByteReal(f float32) {
+ u := math.Float32bits(f)
+
+ // Round the fractional bits (the low 23 bits) to the nearest multiple of
+ // 4, being careful not to overflow into the upper bits.
+ v := u & 0x007fffff
+ if v < 0x007fffffe {
+ v += 2
+ }
+ u = (u & 0xff800000) | v
+
+ // A 4 byte encoding has the low two bits set.
+ u |= 0x03
+ *b = append(*b, uint8(u), uint8(u>>8), uint8(u>>16), uint8(u>>24))
+}
+
+func (b *buffer) encodeCoordinate(f float32) {
+ if i := int32(f); -64 <= i && i < +64 && float32(i) == f {
+ u := uint32(i + 64)
+ u = (u << 1)
+ *b = append(*b, uint8(u))
+ return
+ }
+ if i := int32(f * 64); -128*64 <= i && i < +128*64 && float32(i) == f*64 {
+ u := uint32(i + 128*64)
+ u = (u << 2) | 1
+ *b = append(*b, uint8(u), uint8(u>>8))
+ return
+ }
+ b.encode4ByteReal(f)
+}
+
+func (b *buffer) encodeZeroToOne(f float32) {
+ if u := uint32(f * 15120); float32(u) == f*15120 && u < 15120 {
+ if u%126 == 0 {
+ u = ((u / 126) << 1)
+ *b = append(*b, uint8(u))
+ } else {
+ u = (u << 2) | 1
+ *b = append(*b, uint8(u), uint8(u>>8))
+ }
+ return
+ }
+ b.encode4ByteReal(f)
+}
diff --git a/shiny/iconvg/buffer_test.go b/shiny/iconvg/buffer_test.go
new file mode 100644
index 0000000..5e51aa3
--- /dev/null
+++ b/shiny/iconvg/buffer_test.go
@@ -0,0 +1,187 @@
+// 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 (
+ "math"
+ "testing"
+)
+
+var naturalTestCases = []struct {
+ in buffer
+ want uint32
+ wantN int
+}{{
+ buffer{},
+ 0,
+ 0,
+}, {
+ buffer{0x28},
+ 20,
+ 1,
+}, {
+ buffer{0x59},
+ 0,
+ 0,
+}, {
+ buffer{0x59, 0x83},
+ 8406,
+ 2,
+}, {
+ buffer{0x07, 0x00, 0x80},
+ 0,
+ 0,
+}, {
+ buffer{0x07, 0x00, 0x80, 0x3f},
+ 266338305,
+ 4,
+}}
+
+func TestDecodeNatural(t *testing.T) {
+ for _, tc := range naturalTestCases {
+ got, gotN := tc.in.decodeNatural()
+ if got != tc.want || gotN != tc.wantN {
+ t.Errorf("in=%x: got %v, %d, want %v, %d", tc.in, got, gotN, tc.want, tc.wantN)
+ }
+ }
+}
+
+func TestEncodeNatural(t *testing.T) {
+ for _, tc := range naturalTestCases {
+ if tc.wantN == 0 {
+ continue
+ }
+ var b buffer
+ b.encodeNatural(tc.want)
+ if got, want := string(b), string(tc.in); got != want {
+ t.Errorf("value=%v:\ngot % x\nwant % x", tc.want, got, want)
+ }
+ }
+}
+
+var realTestCases = []struct {
+ in buffer
+ want float32
+ wantN int
+}{{
+ buffer{0x28},
+ 20,
+ 1,
+}, {
+ buffer{0x59, 0x83},
+ 8406,
+ 2,
+}, {
+ buffer{0x07, 0x00, 0x80, 0x3f},
+ 1.000000476837158203125,
+ 4,
+}}
+
+func TestDecodeReal(t *testing.T) {
+ for _, tc := range realTestCases {
+ got, gotN := tc.in.decodeReal()
+ if got != tc.want || gotN != tc.wantN {
+ t.Errorf("in=%x: got %v, %d, want %v, %d", tc.in, got, gotN, tc.want, tc.wantN)
+ }
+ }
+}
+
+func TestEncodeReal(t *testing.T) {
+ for _, tc := range realTestCases {
+ var b buffer
+ b.encodeReal(tc.want)
+ if got, want := string(b), string(tc.in); got != want {
+ t.Errorf("value=%v:\ngot % x\nwant % x", tc.want, got, want)
+ }
+ }
+}
+
+var coordinateTestCases = []struct {
+ in buffer
+ want float32
+ wantN int
+}{{
+ buffer{0x8e},
+ 7,
+ 1,
+}, {
+ buffer{0x81, 0x87},
+ 7.5,
+ 2,
+}, {
+ buffer{0x03, 0x00, 0xf0, 0x40},
+ 7.5,
+ 4,
+}, {
+ buffer{0x07, 0x00, 0xf0, 0x40},
+ 7.5000019073486328125,
+ 4,
+}}
+
+func TestDecodeCoordinate(t *testing.T) {
+ for _, tc := range coordinateTestCases {
+ got, gotN := tc.in.decodeCoordinate()
+ if got != tc.want || gotN != tc.wantN {
+ t.Errorf("in=%x: got %v, %d, want %v, %d", tc.in, got, gotN, tc.want, tc.wantN)
+ }
+ }
+}
+
+func TestEncodeCoordinate(t *testing.T) {
+ for _, tc := range coordinateTestCases {
+ if tc.want == 7.5 && tc.wantN == 4 {
+ // 7.5 can be encoded in fewer than 4 bytes.
+ continue
+ }
+ var b buffer
+ b.encodeCoordinate(tc.want)
+ if got, want := string(b), string(tc.in); got != want {
+ t.Errorf("value=%v:\ngot % x\nwant % x", tc.want, got, want)
+ }
+ }
+}
+
+func trunc(x float32) float32 {
+ u := math.Float32bits(x)
+ u &^= 0x03
+ return math.Float32frombits(u)
+}
+
+var zeroToOneTestCases = []struct {
+ in buffer
+ want float32
+ wantN int
+}{{
+ buffer{0x0a},
+ 1.0 / 24,
+ 1,
+}, {
+ buffer{0x41, 0x1a},
+ 1.0 / 9,
+ 2,
+}, {
+ buffer{0x63, 0x0b, 0x36, 0x3b},
+ trunc(1.0 / 360),
+ 4,
+}}
+
+func TestDecodeZeroToOne(t *testing.T) {
+ for _, tc := range zeroToOneTestCases {
+ got, gotN := tc.in.decodeZeroToOne()
+ if got != tc.want || gotN != tc.wantN {
+ t.Errorf("in=%x: got %v, %d, want %v, %d", tc.in, got, gotN, tc.want, tc.wantN)
+ }
+ }
+}
+
+func TestEncodeZeroToOne(t *testing.T) {
+ for _, tc := range zeroToOneTestCases {
+ var b buffer
+ b.encodeZeroToOne(tc.want)
+ if got, want := string(b), string(tc.in); got != want {
+ t.Errorf("value=%v:\ngot % x\nwant % x", tc.want, got, want)
+ }
+ }
+}
diff --git a/shiny/iconvg/encode.go b/shiny/iconvg/encode.go
new file mode 100644
index 0000000..f502ee8
--- /dev/null
+++ b/shiny/iconvg/encode.go
@@ -0,0 +1,293 @@
+// 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 (
+ "errors"
+)
+
+// TODO: encode colors; opcodes for setting CREGs and NREGs.
+
+var (
+ errDrawingOpsUsedInStylingMode = errors.New("iconvg: drawing ops used in styling mode")
+ errInvalidSelectorAdjustment = errors.New("iconvg: invalid selector adjustment")
+ errStylingOpsUsedInDrawingMode = errors.New("iconvg: styling ops used in drawing mode")
+)
+
+// NewEncoder returns a new Encoder for the given Metadata.
+func NewEncoder(m Metadata) *Encoder {
+ e := &Encoder{
+ buf: make(buffer, 0, 1024),
+ }
+ e.Reset(m)
+ return e
+}
+
+// Encoder is an IconVG encoder.
+type Encoder struct {
+ buf buffer
+ altBuf buffer
+ metadata Metadata
+ err error
+
+ mode mode
+ drawOp byte
+ drawArgs []float32
+
+ cSel uint32
+ nSel uint32
+ lod0 float32
+ lod1 float32
+}
+
+// Bytes returns the encoded form.
+func (e *Encoder) Bytes() ([]byte, error) {
+ if e.err != nil {
+ return nil, e.err
+ }
+ return []byte(e.buf), nil
+}
+
+// Reset resets the Encoder for the given Metadata.
+func (e *Encoder) Reset(m Metadata) {
+ *e = Encoder{
+ buf: append(e.buf[:0], magic...),
+ metadata: m,
+ lod1: positiveInfinity,
+ }
+
+ nMetadataChunks := 0
+ mcViewBox := m.ViewBox != DefaultViewBox
+ if mcViewBox {
+ nMetadataChunks++
+ }
+ mcSuggestedPalette := m.Palette != DefaultPalette
+ if mcSuggestedPalette {
+ nMetadataChunks++
+ }
+ e.buf.encodeNatural(uint32(nMetadataChunks))
+
+ if mcViewBox {
+ e.altBuf = e.altBuf[:0]
+ e.altBuf.encodeNatural(midViewBox)
+ e.altBuf.encodeCoordinate(m.ViewBox.Min[0])
+ e.altBuf.encodeCoordinate(m.ViewBox.Min[1])
+ e.altBuf.encodeCoordinate(m.ViewBox.Max[0])
+ e.altBuf.encodeCoordinate(m.ViewBox.Max[1])
+
+ e.buf.encodeNatural(uint32(len(e.altBuf)))
+ e.buf = append(e.buf, e.altBuf...)
+ }
+
+ if mcSuggestedPalette {
+ panic("TODO: encode mcSuggestedPalette")
+ }
+}
+
+func (e *Encoder) CSel() uint32 { return e.cSel }
+func (e *Encoder) NSel() uint32 { return e.nSel }
+func (e *Encoder) LOD() (lod0, lod1 float32) { return e.lod0, e.lod1 }
+
+func (e *Encoder) SetCSel(cSel uint32) {
+ if e.err != nil {
+ return
+ }
+ if e.mode != modeStyling {
+ e.err = errStylingOpsUsedInDrawingMode
+ return
+ }
+ e.cSel = cSel
+ e.buf = append(e.buf, uint8(cSel&0x3f))
+}
+
+func (e *Encoder) SetNSel(nSel uint32) {
+ if e.err != nil {
+ return
+ }
+ if e.mode != modeStyling {
+ e.err = errStylingOpsUsedInDrawingMode
+ return
+ }
+ e.nSel = nSel
+ e.buf = append(e.buf, uint8((nSel&0x3f)|0x40))
+}
+
+func (e *Encoder) SetLOD(lod0, lod1 float32) {
+ if e.err != nil {
+ return
+ }
+ if e.mode != modeStyling {
+ e.err = errStylingOpsUsedInDrawingMode
+ return
+ }
+ e.lod0 = lod0
+ e.lod1 = lod1
+ e.buf = append(e.buf, 0xe7)
+ e.buf.encodeReal(lod0)
+ e.buf.encodeReal(lod1)
+}
+
+func (e *Encoder) StartPath(adj int, x, y float32) {
+ if e.err != nil {
+ return
+ }
+ if e.mode != modeStyling {
+ e.err = errStylingOpsUsedInDrawingMode
+ return
+ }
+ if adj < -6 || 0 < adj {
+ e.err = errInvalidSelectorAdjustment
+ return
+ }
+ e.buf = append(e.buf, uint8(0xb0-adj))
+ e.buf.encodeCoordinate(x)
+ e.buf.encodeCoordinate(y)
+ e.mode = modeDrawing
+}
+
+func (e *Encoder) AbsHLineTo(x float32) { e.draw('H', x, 0, 0, 0, 0, 0) }
+func (e *Encoder) RelHLineTo(x float32) { e.draw('h', x, 0, 0, 0, 0, 0) }
+func (e *Encoder) AbsVLineTo(y float32) { e.draw('V', y, 0, 0, 0, 0, 0) }
+func (e *Encoder) RelVLineTo(y float32) { e.draw('v', y, 0, 0, 0, 0, 0) }
+func (e *Encoder) AbsLineTo(x, y float32) { e.draw('L', x, y, 0, 0, 0, 0) }
+func (e *Encoder) RelLineTo(x, y float32) { e.draw('l', x, y, 0, 0, 0, 0) }
+func (e *Encoder) AbsSmoothQuadTo(x, y float32) { e.draw('T', x, y, 0, 0, 0, 0) }
+func (e *Encoder) RelSmoothQuadTo(x, y float32) { e.draw('t', x, y, 0, 0, 0, 0) }
+func (e *Encoder) AbsQuadTo(x1, y1, x, y float32) { e.draw('Q', x1, y1, x, y, 0, 0) }
+func (e *Encoder) RelQuadTo(x1, y1, x, y float32) { e.draw('q', x1, y1, x, y, 0, 0) }
+func (e *Encoder) AbsSmoothCubeTo(x2, y2, x, y float32) { e.draw('S', x2, y2, x, y, 0, 0) }
+func (e *Encoder) RelSmoothCubeTo(x2, y2, x, y float32) { e.draw('s', x2, y2, x, y, 0, 0) }
+func (e *Encoder) AbsCubeTo(x1, y1, x2, y2, x, y float32) { e.draw('C', x1, y1, x2, y2, x, y) }
+func (e *Encoder) RelCubeTo(x1, y1, x2, y2, x, y float32) { e.draw('c', x1, y1, x2, y2, x, y) }
+func (e *Encoder) ClosePathEndPath() { e.draw('Z', 0, 0, 0, 0, 0, 0) }
+func (e *Encoder) ClosePathAbsMoveTo(x, y float32) { e.draw('Y', x, y, 0, 0, 0, 0) }
+func (e *Encoder) ClosePathRelMoveTo(x, y float32) { e.draw('y', x, y, 0, 0, 0, 0) }
+
+func (e *Encoder) AbsArcTo(rx, ry, xAxisRotation float32, largeArc, sweep bool, x, y float32) {
+ e.arcTo('A', rx, ry, xAxisRotation, largeArc, sweep, x, y)
+}
+
+func (e *Encoder) RelArcTo(rx, ry, xAxisRotation float32, largeArc, sweep bool, x, y float32) {
+ e.arcTo('a', rx, ry, xAxisRotation, largeArc, sweep, x, y)
+}
+
+func (e *Encoder) arcTo(drawOp byte, rx, ry, xAxisRotation float32, largeArc, sweep bool, x, y float32) {
+ flags := uint32(0)
+ if largeArc {
+ flags |= 0x01
+ }
+ if sweep {
+ flags |= 0x02
+ }
+ e.draw(drawOp, rx, ry, xAxisRotation, float32(flags), x, y)
+}
+
+func (e *Encoder) draw(drawOp byte, arg0, arg1, arg2, arg3, arg4, arg5 float32) {
+ if e.err != nil {
+ return
+ }
+ if e.mode != modeDrawing {
+ e.err = errDrawingOpsUsedInStylingMode
+ return
+ }
+ if e.drawOp != drawOp {
+ e.flushDrawOps()
+ }
+ e.drawOp = drawOp
+ switch drawOps[drawOp].nArgs {
+ case 0:
+ // No-op.
+ case 1:
+ e.drawArgs = append(e.drawArgs, arg0)
+ case 2:
+ e.drawArgs = append(e.drawArgs, arg0, arg1)
+ case 4:
+ e.drawArgs = append(e.drawArgs, arg0, arg1, arg2, arg3)
+ case 6:
+ e.drawArgs = append(e.drawArgs, arg0, arg1, arg2, arg3, arg4, arg5)
+ default:
+ panic("unreachable")
+ }
+
+ switch drawOp {
+ case 'Z':
+ e.mode = modeStyling
+ fallthrough
+ case 'Y', 'y':
+ e.flushDrawOps()
+ }
+}
+
+func (e *Encoder) flushDrawOps() {
+ if e.drawOp == 0x00 {
+ return
+ }
+
+ if op := drawOps[e.drawOp]; op.nArgs == 0 {
+ e.buf = append(e.buf, op.opCodeBase)
+ } else {
+ n := len(e.drawArgs) / int(op.nArgs)
+ for i := 0; n > 0; {
+ m := n
+ if m > int(op.maxRepCount) {
+ m = int(op.maxRepCount)
+ }
+ e.buf = append(e.buf, op.opCodeBase+uint8(m)-1)
+
+ switch e.drawOp {
+ default:
+ for j := m * int(op.nArgs); j > 0; j-- {
+ e.buf.encodeCoordinate(e.drawArgs[i])
+ i++
+ }
+ case 'A', 'a':
+ for j := m; j > 0; j-- {
+ e.buf.encodeCoordinate(e.drawArgs[i+0])
+ e.buf.encodeCoordinate(e.drawArgs[i+1])
+ e.buf.encodeZeroToOne(e.drawArgs[i+2])
+ e.buf.encodeNatural(uint32(e.drawArgs[i+3]))
+ e.buf.encodeCoordinate(e.drawArgs[i+4])
+ e.buf.encodeCoordinate(e.drawArgs[i+5])
+ i += 6
+ }
+ }
+
+ n -= m
+ }
+ }
+
+ e.drawOp = 0x00
+ e.drawArgs = e.drawArgs[:0]
+}
+
+var drawOps = [256]struct {
+ opCodeBase byte
+ maxRepCount uint8
+ nArgs uint8
+}{
+ 'L': {0x00, 32, 2},
+ 'l': {0x20, 32, 2},
+ 'T': {0x40, 16, 2},
+ 't': {0x50, 16, 2},
+ 'Q': {0x60, 16, 4},
+ 'q': {0x70, 16, 4},
+ 'S': {0x80, 16, 4},
+ 's': {0x90, 16, 4},
+ 'C': {0xa0, 16, 6},
+ 'c': {0xb0, 16, 6},
+ 'A': {0xc0, 16, 6},
+ 'a': {0xd0, 16, 6},
+
+ // Z means close path and then end path.
+ 'Z': {0xe1, 1, 0},
+ // Y/y means close path and then open a new path (with a MoveTo/moveTo).
+ 'Y': {0xe2, 1, 2},
+ 'y': {0xe3, 1, 2},
+
+ 'H': {0xe6, 1, 1},
+ 'h': {0xe7, 1, 1},
+ 'V': {0xe8, 1, 1},
+ 'v': {0xe9, 1, 1},
+}
diff --git a/shiny/iconvg/encode_test.go b/shiny/iconvg/encode_test.go
new file mode 100644
index 0000000..62c28a3
--- /dev/null
+++ b/shiny/iconvg/encode_test.go
@@ -0,0 +1,60 @@
+// 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"
+ "testing"
+
+ "golang.org/x/image/math/f32"
+)
+
+// actionInfoIconVG is the IconVG encoding of the "action/info" icon from the
+// Material Design icon set.
+//
+// See doc.go for an annotated version.
+var actionInfoIconVG = []byte{
+ 0x89, 0x49, 0x56, 0x47, 0x02, 0x0a, 0x00, 0x50, 0x50, 0xb0, 0xb0, 0xb0, 0x80, 0x58, 0xa0, 0xcf,
+ 0xcc, 0x30, 0xc1, 0x58, 0x58, 0xcf, 0xcc, 0x30, 0xc1, 0x58, 0x80, 0x91, 0x37, 0x33, 0x0f, 0x41,
+ 0xa8, 0xa8, 0xa8, 0xa8, 0x37, 0x33, 0x0f, 0xc1, 0xa8, 0x58, 0x80, 0xcf, 0xcc, 0x30, 0x41, 0x58,
+ 0x80, 0x58, 0xe3, 0x84, 0xbc, 0xe7, 0x78, 0xe8, 0x7c, 0xe7, 0x88, 0xe9, 0x98, 0xe3, 0x80, 0x60,
+ 0xe7, 0x78, 0xe9, 0x78, 0xe7, 0x88, 0xe9, 0x88, 0xe1,
+}
+
+func TestEncodeActionInfo(t *testing.T) {
+ e := NewEncoder(Metadata{
+ ViewBox: Rectangle{
+ Min: f32.Vec2{-24, -24},
+ Max: f32.Vec2{+24, +24},
+ },
+ Palette: DefaultPalette,
+ })
+
+ e.StartPath(0, 0, -20)
+ e.AbsCubeTo(-11.05, -20, -20, -11.05, -20, 0)
+ e.RelSmoothCubeTo(8.95, 20, 20, 20)
+ e.RelSmoothCubeTo(20, -8.95, 20, -20)
+ e.AbsSmoothCubeTo(11.05, -20, 0, -20)
+ e.ClosePathRelMoveTo(2, 30)
+ e.RelHLineTo(-4)
+ e.AbsVLineTo(-2)
+ e.RelHLineTo(4)
+ e.RelVLineTo(12)
+ e.ClosePathRelMoveTo(0, -16)
+ e.RelHLineTo(-4)
+ e.RelVLineTo(-4)
+ e.RelHLineTo(4)
+ e.RelVLineTo(4)
+ e.ClosePathEndPath()
+
+ got, err := e.Bytes()
+ if err != nil {
+ t.Fatal(err)
+ }
+ want := actionInfoIconVG
+ if !bytes.Equal(got, want) {
+ t.Errorf("\ngot %d bytes:\n% x\nwant %d bytes:\n% x", len(got), got, len(want), want)
+ }
+}
diff --git a/shiny/iconvg/iconvg.go b/shiny/iconvg/iconvg.go
new file mode 100644
index 0000000..d65615a
--- /dev/null
+++ b/shiny/iconvg/iconvg.go
@@ -0,0 +1,127 @@
+// 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/color"
+ "math"
+
+ "golang.org/x/image/math/f32"
+)
+
+const magic = "\x89IVG"
+
+var positiveInfinity = math.Float32frombits(0x7f800000)
+
+const (
+ midViewBox = 0
+ midSuggestedPalette = 1
+)
+
+type mode bool
+
+const (
+ modeStyling mode = false
+ modeDrawing mode = true
+)
+
+// Rectangle is defined by its minimum and maximum coordinates.
+type Rectangle struct {
+ Min, Max f32.Vec2
+}
+
+// AspectRatio returns the Rectangle's aspect ratio. An IconVG graphic is
+// scalable; these dimensions do not necessarily map 1:1 to pixels.
+func (r *Rectangle) AspectRatio() (dx, dy float32) {
+ return r.Max[0] - r.Min[0], r.Max[1] - r.Min[1]
+}
+
+// Palette is an IconVG palette.
+type Palette [64]color.RGBA
+
+// Metadata is an IconVG's metadata.
+type Metadata struct {
+ ViewBox Rectangle
+
+ // Palette is a 64 color palette. When encoding, it is the suggested
+ // palette to place within the IconVG graphic. When decoding, it is either
+ // the optional palette passed to Decode, or if no optional palette was
+ // given, the suggested palette within the IconVG graphic.
+ Palette Palette
+}
+
+// DefaultViewBox is the default ViewBox. Its values should not be modified.
+var DefaultViewBox = Rectangle{
+ Min: f32.Vec2{-32, -32},
+ Max: f32.Vec2{+32, +32},
+}
+
+// DefaultPalette is the default Palette. Its values should not be modified.
+var DefaultPalette = Palette{
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+ color.RGBA{0x00, 0x00, 0x00, 0xff},
+}