aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/tdewolff/minify/svg/svg.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/tdewolff/minify/svg/svg.go')
-rw-r--r--vendor/github.com/tdewolff/minify/svg/svg.go434
1 files changed, 434 insertions, 0 deletions
diff --git a/vendor/github.com/tdewolff/minify/svg/svg.go b/vendor/github.com/tdewolff/minify/svg/svg.go
new file mode 100644
index 0000000..984544f
--- /dev/null
+++ b/vendor/github.com/tdewolff/minify/svg/svg.go
@@ -0,0 +1,434 @@
+// Package svg minifies SVG1.1 following the specifications at http://www.w3.org/TR/SVG11/.
+package svg // import "github.com/tdewolff/minify/svg"
+
+import (
+ "bytes"
+ "io"
+
+ "github.com/tdewolff/minify"
+ minifyCSS "github.com/tdewolff/minify/css"
+ "github.com/tdewolff/parse"
+ "github.com/tdewolff/parse/buffer"
+ "github.com/tdewolff/parse/css"
+ "github.com/tdewolff/parse/svg"
+ "github.com/tdewolff/parse/xml"
+)
+
+var (
+ voidBytes = []byte("/>")
+ isBytes = []byte("=")
+ spaceBytes = []byte(" ")
+ cdataEndBytes = []byte("]]>")
+ pathBytes = []byte("<path")
+ dBytes = []byte("d")
+ zeroBytes = []byte("0")
+ cssMimeBytes = []byte("text/css")
+ urlBytes = []byte("url(")
+)
+
+////////////////////////////////////////////////////////////////
+
+// DefaultMinifier is the default minifier.
+var DefaultMinifier = &Minifier{Decimals: -1}
+
+// Minifier is an SVG minifier.
+type Minifier struct {
+ Decimals int
+}
+
+// Minify minifies SVG data, it reads from r and writes to w.
+func Minify(m *minify.M, w io.Writer, r io.Reader, params map[string]string) error {
+ return DefaultMinifier.Minify(m, w, r, params)
+}
+
+// Minify minifies SVG data, it reads from r and writes to w.
+func (o *Minifier) Minify(m *minify.M, w io.Writer, r io.Reader, _ map[string]string) error {
+ var tag svg.Hash
+ defaultStyleType := cssMimeBytes
+ defaultStyleParams := map[string]string(nil)
+ defaultInlineStyleParams := map[string]string{"inline": "1"}
+
+ p := NewPathData(o)
+ minifyBuffer := buffer.NewWriter(make([]byte, 0, 64))
+ attrByteBuffer := make([]byte, 0, 64)
+ gStack := make([]bool, 0)
+
+ l := xml.NewLexer(r)
+ defer l.Restore()
+
+ tb := NewTokenBuffer(l)
+ for {
+ t := *tb.Shift()
+ SWITCH:
+ switch t.TokenType {
+ case xml.ErrorToken:
+ if l.Err() == io.EOF {
+ return nil
+ }
+ return l.Err()
+ case xml.DOCTYPEToken:
+ if len(t.Text) > 0 && t.Text[len(t.Text)-1] == ']' {
+ if _, err := w.Write(t.Data); err != nil {
+ return err
+ }
+ }
+ case xml.TextToken:
+ t.Data = parse.ReplaceMultipleWhitespace(parse.TrimWhitespace(t.Data))
+ if tag == svg.Style && len(t.Data) > 0 {
+ if err := m.MinifyMimetype(defaultStyleType, w, buffer.NewReader(t.Data), defaultStyleParams); err != nil {
+ if err != minify.ErrNotExist {
+ return err
+ } else if _, err := w.Write(t.Data); err != nil {
+ return err
+ }
+ }
+ } else if _, err := w.Write(t.Data); err != nil {
+ return err
+ }
+ case xml.CDATAToken:
+ if tag == svg.Style {
+ minifyBuffer.Reset()
+ if err := m.MinifyMimetype(defaultStyleType, minifyBuffer, buffer.NewReader(t.Text), defaultStyleParams); err == nil {
+ t.Data = append(t.Data[:9], minifyBuffer.Bytes()...)
+ t.Text = t.Data[9:]
+ t.Data = append(t.Data, cdataEndBytes...)
+ } else if err != minify.ErrNotExist {
+ return err
+ }
+ }
+ var useText bool
+ if t.Text, useText = xml.EscapeCDATAVal(&attrByteBuffer, t.Text); useText {
+ t.Text = parse.ReplaceMultipleWhitespace(parse.TrimWhitespace(t.Text))
+ if _, err := w.Write(t.Text); err != nil {
+ return err
+ }
+ } else if _, err := w.Write(t.Data); err != nil {
+ return err
+ }
+ case xml.StartTagPIToken:
+ for {
+ if t := *tb.Shift(); t.TokenType == xml.StartTagClosePIToken || t.TokenType == xml.ErrorToken {
+ break
+ }
+ }
+ case xml.StartTagToken:
+ tag = t.Hash
+ if containerTagMap[tag] { // skip empty containers
+ i := 0
+ for {
+ next := tb.Peek(i)
+ i++
+ if next.TokenType == xml.EndTagToken && next.Hash == tag || next.TokenType == xml.StartTagCloseVoidToken || next.TokenType == xml.ErrorToken {
+ for j := 0; j < i; j++ {
+ tb.Shift()
+ }
+ break SWITCH
+ } else if next.TokenType != xml.AttributeToken && next.TokenType != xml.StartTagCloseToken {
+ break
+ }
+ }
+ if tag == svg.G {
+ if tb.Peek(0).TokenType == xml.StartTagCloseToken {
+ gStack = append(gStack, false)
+ tb.Shift()
+ break
+ }
+ gStack = append(gStack, true)
+ }
+ } else if tag == svg.Metadata {
+ skipTag(tb, tag)
+ break
+ } else if tag == svg.Line {
+ o.shortenLine(tb, &t, p)
+ } else if tag == svg.Rect && !o.shortenRect(tb, &t, p) {
+ skipTag(tb, tag)
+ break
+ } else if tag == svg.Polygon || tag == svg.Polyline {
+ o.shortenPoly(tb, &t, p)
+ }
+ if _, err := w.Write(t.Data); err != nil {
+ return err
+ }
+ case xml.AttributeToken:
+ if len(t.AttrVal) == 0 || t.Text == nil { // data is nil when attribute has been removed
+ continue
+ }
+
+ attr := t.Hash
+ val := t.AttrVal
+ if n, m := parse.Dimension(val); n+m == len(val) && attr != svg.Version { // TODO: inefficient, temporary measure
+ val, _ = o.shortenDimension(val)
+ }
+ if attr == svg.Xml_Space && bytes.Equal(val, []byte("preserve")) ||
+ tag == svg.Svg && (attr == svg.Version && bytes.Equal(val, []byte("1.1")) ||
+ attr == svg.X && bytes.Equal(val, []byte("0")) ||
+ attr == svg.Y && bytes.Equal(val, []byte("0")) ||
+ attr == svg.Width && bytes.Equal(val, []byte("100%")) ||
+ attr == svg.Height && bytes.Equal(val, []byte("100%")) ||
+ attr == svg.PreserveAspectRatio && bytes.Equal(val, []byte("xMidYMid meet")) ||
+ attr == svg.BaseProfile && bytes.Equal(val, []byte("none")) ||
+ attr == svg.ContentScriptType && bytes.Equal(val, []byte("application/ecmascript")) ||
+ attr == svg.ContentStyleType && bytes.Equal(val, []byte("text/css"))) ||
+ tag == svg.Style && attr == svg.Type && bytes.Equal(val, []byte("text/css")) {
+ continue
+ }
+
+ if _, err := w.Write(spaceBytes); err != nil {
+ return err
+ }
+ if _, err := w.Write(t.Text); err != nil {
+ return err
+ }
+ if _, err := w.Write(isBytes); err != nil {
+ return err
+ }
+
+ if tag == svg.Svg && attr == svg.ContentStyleType {
+ val = minify.ContentType(val)
+ defaultStyleType = val
+ } else if attr == svg.Style {
+ minifyBuffer.Reset()
+ if err := m.MinifyMimetype(defaultStyleType, minifyBuffer, buffer.NewReader(val), defaultInlineStyleParams); err == nil {
+ val = minifyBuffer.Bytes()
+ } else if err != minify.ErrNotExist {
+ return err
+ }
+ } else if attr == svg.D {
+ val = p.ShortenPathData(val)
+ } else if attr == svg.ViewBox {
+ j := 0
+ newVal := val[:0]
+ for i := 0; i < 4; i++ {
+ if i != 0 {
+ if j >= len(val) || val[j] != ' ' && val[j] != ',' {
+ newVal = append(newVal, val[j:]...)
+ break
+ }
+ newVal = append(newVal, ' ')
+ j++
+ }
+ if dim, n := o.shortenDimension(val[j:]); n > 0 {
+ newVal = append(newVal, dim...)
+ j += n
+ } else {
+ newVal = append(newVal, val[j:]...)
+ break
+ }
+ }
+ val = newVal
+ } else if colorAttrMap[attr] && len(val) > 0 && (len(val) < 5 || !parse.EqualFold(val[:4], urlBytes)) {
+ parse.ToLower(val)
+ if val[0] == '#' {
+ if name, ok := minifyCSS.ShortenColorHex[string(val)]; ok {
+ val = name
+ } else if len(val) == 7 && val[1] == val[2] && val[3] == val[4] && val[5] == val[6] {
+ val[2] = val[3]
+ val[3] = val[5]
+ val = val[:4]
+ }
+ } else if hex, ok := minifyCSS.ShortenColorName[css.ToHash(val)]; ok {
+ val = hex
+ // } else if len(val) > 5 && bytes.Equal(val[:4], []byte("rgb(")) && val[len(val)-1] == ')' {
+ // TODO: handle rgb(x, y, z) and hsl(x, y, z)
+ }
+ }
+
+ // prefer single or double quotes depending on what occurs more often in value
+ val = xml.EscapeAttrVal(&attrByteBuffer, val)
+ if _, err := w.Write(val); err != nil {
+ return err
+ }
+ case xml.StartTagCloseToken:
+ next := tb.Peek(0)
+ skipExtra := false
+ if next.TokenType == xml.TextToken && parse.IsAllWhitespace(next.Data) {
+ next = tb.Peek(1)
+ skipExtra = true
+ }
+ if next.TokenType == xml.EndTagToken {
+ // collapse empty tags to single void tag
+ tb.Shift()
+ if skipExtra {
+ tb.Shift()
+ }
+ if _, err := w.Write(voidBytes); err != nil {
+ return err
+ }
+ } else {
+ if _, err := w.Write(t.Data); err != nil {
+ return err
+ }
+ }
+ case xml.StartTagCloseVoidToken:
+ tag = 0
+ if _, err := w.Write(t.Data); err != nil {
+ return err
+ }
+ case xml.EndTagToken:
+ tag = 0
+ if t.Hash == svg.G && len(gStack) > 0 {
+ if !gStack[len(gStack)-1] {
+ gStack = gStack[:len(gStack)-1]
+ break
+ }
+ gStack = gStack[:len(gStack)-1]
+ }
+ if len(t.Data) > 3+len(t.Text) {
+ t.Data[2+len(t.Text)] = '>'
+ t.Data = t.Data[:3+len(t.Text)]
+ }
+ if _, err := w.Write(t.Data); err != nil {
+ return err
+ }
+ }
+ }
+}
+
+func (o *Minifier) shortenDimension(b []byte) ([]byte, int) {
+ if n, m := parse.Dimension(b); n > 0 {
+ unit := b[n : n+m]
+ b = minify.Number(b[:n], o.Decimals)
+ if len(b) != 1 || b[0] != '0' {
+ if m == 2 && unit[0] == 'p' && unit[1] == 'x' {
+ unit = nil
+ } else if m > 1 { // only percentage is length 1
+ parse.ToLower(unit)
+ }
+ b = append(b, unit...)
+ }
+ return b, n + m
+ }
+ return b, 0
+}
+
+func (o *Minifier) shortenLine(tb *TokenBuffer, t *Token, p *PathData) {
+ x1, y1, x2, y2 := zeroBytes, zeroBytes, zeroBytes, zeroBytes
+ if attrs, replacee := tb.Attributes(svg.X1, svg.Y1, svg.X2, svg.Y2); replacee != nil {
+ if attrs[0] != nil {
+ x1 = minify.Number(attrs[0].AttrVal, o.Decimals)
+ attrs[0].Text = nil
+ }
+ if attrs[1] != nil {
+ y1 = minify.Number(attrs[1].AttrVal, o.Decimals)
+ attrs[1].Text = nil
+ }
+ if attrs[2] != nil {
+ x2 = minify.Number(attrs[2].AttrVal, o.Decimals)
+ attrs[2].Text = nil
+ }
+ if attrs[3] != nil {
+ y2 = minify.Number(attrs[3].AttrVal, o.Decimals)
+ attrs[3].Text = nil
+ }
+
+ d := make([]byte, 0, 5+len(x1)+len(y1)+len(x2)+len(y2))
+ d = append(d, 'M')
+ d = append(d, x1...)
+ d = append(d, ' ')
+ d = append(d, y1...)
+ d = append(d, 'L')
+ d = append(d, x2...)
+ d = append(d, ' ')
+ d = append(d, y2...)
+ d = append(d, 'z')
+ d = p.ShortenPathData(d)
+
+ t.Data = pathBytes
+ replacee.Text = dBytes
+ replacee.AttrVal = d
+ }
+}
+
+func (o *Minifier) shortenRect(tb *TokenBuffer, t *Token, p *PathData) bool {
+ if attrs, replacee := tb.Attributes(svg.X, svg.Y, svg.Width, svg.Height, svg.Rx, svg.Ry); replacee != nil && attrs[4] == nil && attrs[5] == nil {
+ x, y, w, h := zeroBytes, zeroBytes, zeroBytes, zeroBytes
+ if attrs[0] != nil {
+ x = minify.Number(attrs[0].AttrVal, o.Decimals)
+ attrs[0].Text = nil
+ }
+ if attrs[1] != nil {
+ y = minify.Number(attrs[1].AttrVal, o.Decimals)
+ attrs[1].Text = nil
+ }
+ if attrs[2] != nil {
+ w = minify.Number(attrs[2].AttrVal, o.Decimals)
+ attrs[2].Text = nil
+ }
+ if attrs[3] != nil {
+ h = minify.Number(attrs[3].AttrVal, o.Decimals)
+ attrs[3].Text = nil
+ }
+ if len(w) == 0 || w[0] == '0' || len(h) == 0 || h[0] == '0' {
+ return false
+ }
+
+ d := make([]byte, 0, 6+2*len(x)+len(y)+len(w)+len(h))
+ d = append(d, 'M')
+ d = append(d, x...)
+ d = append(d, ' ')
+ d = append(d, y...)
+ d = append(d, 'h')
+ d = append(d, w...)
+ d = append(d, 'v')
+ d = append(d, h...)
+ d = append(d, 'H')
+ d = append(d, x...)
+ d = append(d, 'z')
+ d = p.ShortenPathData(d)
+
+ t.Data = pathBytes
+ replacee.Text = dBytes
+ replacee.AttrVal = d
+ }
+ return true
+}
+
+func (o *Minifier) shortenPoly(tb *TokenBuffer, t *Token, p *PathData) {
+ if attrs, replacee := tb.Attributes(svg.Points); replacee != nil && attrs[0] != nil {
+ points := attrs[0].AttrVal
+
+ i := 0
+ for i < len(points) && !(points[i] == ' ' || points[i] == ',' || points[i] == '\n' || points[i] == '\r' || points[i] == '\t') {
+ i++
+ }
+ for i < len(points) && (points[i] == ' ' || points[i] == ',' || points[i] == '\n' || points[i] == '\r' || points[i] == '\t') {
+ i++
+ }
+ for i < len(points) && !(points[i] == ' ' || points[i] == ',' || points[i] == '\n' || points[i] == '\r' || points[i] == '\t') {
+ i++
+ }
+ endMoveTo := i
+ for i < len(points) && (points[i] == ' ' || points[i] == ',' || points[i] == '\n' || points[i] == '\r' || points[i] == '\t') {
+ i++
+ }
+ startLineTo := i
+
+ if i == len(points) {
+ return
+ }
+
+ d := make([]byte, 0, len(points)+3)
+ d = append(d, 'M')
+ d = append(d, points[:endMoveTo]...)
+ d = append(d, 'L')
+ d = append(d, points[startLineTo:]...)
+ if t.Hash == svg.Polygon {
+ d = append(d, 'z')
+ }
+ d = p.ShortenPathData(d)
+
+ t.Data = pathBytes
+ replacee.Text = dBytes
+ replacee.AttrVal = d
+ }
+}
+
+////////////////////////////////////////////////////////////////
+
+func skipTag(tb *TokenBuffer, tag svg.Hash) {
+ for {
+ if t := *tb.Shift(); (t.TokenType == xml.EndTagToken || t.TokenType == xml.StartTagCloseVoidToken) && t.Hash == tag || t.TokenType == xml.ErrorToken {
+ break
+ }
+ }
+}