123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699 |
- // 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"
- "image/color"
- )
- var (
- errInconsistentMetadataChunkLength = errors.New("iconvg: inconsistent metadata chunk length")
- errInvalidColor = errors.New("iconvg: invalid color")
- 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")
- errInvalidSuggestedPalette = errors.New("iconvg: invalid suggested palette")
- 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")
- errUnsupportedUpgrade = errors.New("iconvg: unsupported upgrade")
- )
- 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)
- SetCSel(cSel uint8)
- SetNSel(nSel uint8)
- SetCReg(adj uint8, incr bool, c Color)
- SetNReg(adj uint8, incr bool, f float32)
- SetLOD(lod0, lod1 float32)
- StartPath(adj uint8, 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) {
- // TODO: detect FFV 1 (File Format Version 1), as opposed to the FFV 0
- // that this package implements, and delegate to a FFV 1 decoder.
- return errInvalidMagicIdentifier
- }
- if p != nil {
- p(src[:len(magic)], "IconVG 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:
- if len(src) == 0 {
- return nil, errInvalidSuggestedPalette
- }
- length, format := 1+int(src[0]&0x3f), src[0]>>6
- decode := buffer.decodeColor4
- switch format {
- case 0:
- decode = buffer.decodeColor1
- case 1:
- decode = buffer.decodeColor2
- case 2:
- decode = buffer.decodeColor3Direct
- }
- if p != nil {
- p(src[:1], " %d palette colors, %d bytes per color\n", length, 1+format)
- }
- src = src[1:]
- for i := 0; i < length; i++ {
- c, n := decode(src)
- if n == 0 {
- return nil, errInvalidSuggestedPalette
- }
- rgba := c.rgba()
- if c.typ != ColorTypeRGBA || !validAlphaPremulColor(rgba) {
- rgba = color.RGBA{0x00, 0x00, 0x00, 0xff}
- }
- if p != nil {
- p(src[:n], " RGBA %02x%02x%02x%02x\n", rgba.R, rgba.G, rgba.B, rgba.A)
- }
- src = src[n:]
- if opts == nil || opts.Palette == nil {
- m.Palette[i] = rgba
- }
- }
- 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 < 0x80:
- if opcode < 0x40 {
- opcode &= 0x3f
- if p != nil {
- p(src[:1], "Set CSEL = %d\n", opcode)
- }
- src = src[1:]
- if dst != nil {
- dst.SetCSel(opcode)
- }
- } else {
- opcode &= 0x3f
- if p != nil {
- p(src[:1], "Set NSEL = %d\n", opcode)
- }
- src = src[1:]
- if dst != nil {
- dst.SetNSel(opcode)
- }
- }
- return decodeStyling, src, nil
- case opcode < 0xa8:
- return decodeSetCReg(dst, p, src, opcode)
- case opcode < 0xc0:
- return decodeSetNReg(dst, p, src, opcode)
- case opcode < 0xc7:
- return decodeStartPath(dst, p, src, opcode)
- case opcode == 0xc7:
- return decodeSetLOD(dst, p, src)
- }
- return nil, nil, errUnsupportedStylingOpcode
- }
- func decodeSetCReg(dst Destination, p printer, src buffer, opcode byte) (modeFunc, buffer, error) {
- nBytes, directness, adj := 0, "", opcode&0x07
- var decode func(buffer) (Color, int)
- incr := adj == 7
- if incr {
- adj = 0
- }
- switch (opcode - 0x80) >> 3 {
- case 0:
- nBytes, directness, decode = 1, "", buffer.decodeColor1
- case 1:
- nBytes, directness, decode = 2, "", buffer.decodeColor2
- case 2:
- nBytes, directness, decode = 3, " (direct)", buffer.decodeColor3Direct
- case 3:
- nBytes, directness, decode = 4, "", buffer.decodeColor4
- case 4:
- nBytes, directness, decode = 3, " (indirect)", buffer.decodeColor3Indirect
- }
- if p != nil {
- if incr {
- p(src[:1], "Set CREG[CSEL-0] to a %d byte%s color; CSEL++\n", nBytes, directness)
- } else {
- p(src[:1], "Set CREG[CSEL-%d] to a %d byte%s color\n", adj, nBytes, directness)
- }
- }
- src = src[1:]
- c, n := decode(src)
- if n == 0 {
- return nil, nil, errInvalidColor
- }
- if p != nil {
- printColor(src[:n], p, c, "")
- }
- src = src[n:]
- if dst != nil {
- dst.SetCReg(adj, incr, c)
- }
- return decodeStyling, src, nil
- }
- func printColor(src []byte, p printer, c Color, prefix string) {
- switch c.typ {
- case ColorTypeRGBA:
- if rgba := c.rgba(); validAlphaPremulColor(rgba) {
- p(src, " %sRGBA %02x%02x%02x%02x\n", prefix, rgba.R, rgba.G, rgba.B, rgba.A)
- } else if rgba.A == 0 && rgba.B&0x80 != 0 {
- p(src, " %sgradient (NSTOPS=%d, CBASE=%d, NBASE=%d, %s, %s)\n",
- prefix,
- rgba.R&0x3f,
- rgba.G&0x3f,
- rgba.B&0x3f,
- gradientShapeNames[(rgba.B>>6)&0x01],
- gradientSpreadNames[rgba.G>>6],
- )
- } else {
- p(src, " %snonsensical color\n", prefix)
- }
- case ColorTypePaletteIndex:
- p(src, " %scustomPalette[%d]\n", prefix, c.paletteIndex())
- case ColorTypeCReg:
- p(src, " %sCREG[%d]\n", prefix, c.cReg())
- case ColorTypeBlend:
- t, c0, c1 := c.blend()
- p(src[:1], " blend %d:%d c0:c1\n", 0xff-t, t)
- printColor(src[1:2], p, decodeColor1(c0), " c0: ")
- printColor(src[2:3], p, decodeColor1(c1), " c1: ")
- }
- }
- func decodeSetNReg(dst Destination, p printer, src buffer, opcode byte) (modeFunc, buffer, error) {
- decode, typ, adj := buffer.decodeZeroToOne, "zero-to-one", opcode&0x07
- incr := adj == 7
- if incr {
- adj = 0
- }
- switch (opcode - 0xa8) >> 3 {
- case 0:
- decode, typ = buffer.decodeReal, "real"
- case 1:
- decode, typ = buffer.decodeCoordinate, "coordinate"
- }
- if p != nil {
- if incr {
- p(src[:1], "Set NREG[NSEL-0] to a %s number; NSEL++\n", typ)
- } else {
- p(src[:1], "Set NREG[NSEL-%d] to a %s number\n", adj, typ)
- }
- }
- src = src[1:]
- f, n := decode(src)
- if n == 0 {
- return nil, nil, errInvalidNumber
- }
- if p != nil {
- p(src[:n], " %g\n", f)
- }
- src = src[n:]
- if dst != nil {
- dst.SetNReg(adj, incr, f)
- }
- return decodeStyling, src, nil
- }
- func decodeStartPath(dst Destination, p printer, src buffer, opcode byte) (modeFunc, buffer, error) {
- adj := 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 decodeSetLOD(dst Destination, p printer, src buffer) (modeFunc, buffer, error) {
- if p != nil {
- p(src[:1], "Set LOD\n")
- }
- src = src[1:]
- lod0, src, err := decodeNumber(p, src, buffer.decodeReal)
- if err != nil {
- return nil, nil, err
- }
- lod1, src, err := decodeNumber(p, src, buffer.decodeReal)
- if err != nil {
- return nil, nil, err
- }
- if dst != nil {
- dst.SetLOD(lod0, lod1)
- }
- return decodeStyling, 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)
- }
- var largeArc, sweep bool
- if op[0] != 'A' && op[0] != 'a' {
- src, err = decodeCoordinates(coords[:nCoords], p, src)
- if err != nil {
- return nil, nil, err
- }
- } else {
- // We have an absolute or relative arcTo.
- src, err = decodeCoordinates(coords[:2], p, src)
- if err != nil {
- return nil, nil, err
- }
- coords[2], src, err = decodeAngle(p, src)
- if err != nil {
- return nil, nil, err
- }
- 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 dst == nil {
- continue
- }
- switch op[0] {
- case 'L':
- dst.AbsLineTo(coords[0], coords[1])
- case 'l':
- dst.RelLineTo(coords[0], coords[1])
- case 'T':
- dst.AbsSmoothQuadTo(coords[0], coords[1])
- case 't':
- dst.RelSmoothQuadTo(coords[0], coords[1])
- case 'Q':
- dst.AbsQuadTo(coords[0], coords[1], coords[2], coords[3])
- case 'q':
- dst.RelQuadTo(coords[0], coords[1], coords[2], coords[3])
- case 'S':
- dst.AbsSmoothCubeTo(coords[0], coords[1], coords[2], coords[3])
- case 's':
- dst.RelSmoothCubeTo(coords[0], coords[1], coords[2], coords[3])
- case 'C':
- dst.AbsCubeTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5])
- case 'c':
- dst.RelCubeTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5])
- case 'A':
- dst.AbsArcTo(coords[0], coords[1], coords[2], largeArc, sweep, coords[4], coords[5])
- case 'a':
- 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 decodeCoordinatePairs(coords [][2]float32, p printer, src buffer) (src1 buffer, err error) {
- for i := range coords {
- coords[i][0], src, err = decodeNumber(p, src, buffer.decodeCoordinate)
- if err != nil {
- return nil, err
- }
- coords[i][1], src, err = decodeNumber(p, src, buffer.decodeCoordinate)
- if err != nil {
- return nil, err
- }
- }
- return src, nil
- }
- func decodeAngle(p printer, src buffer) (float32, buffer, error) {
- x, n := src.decodeZeroToOne()
- if n == 0 {
- return 0, nil, errInvalidNumber
- }
- if p != nil {
- p(src[:n], " %v × 360 degrees (%v degrees)\n", x, x*360)
- }
- return x, src[n:], 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
- }
|