mirror of
https://github.com/miniflux/v2.git
synced 2025-08-31 18:31:01 +00:00
First commit
This commit is contained in:
commit
8ffb773f43
2121 changed files with 1118910 additions and 0 deletions
130
vendor/github.com/tdewolff/minify/svg/buffer.go
generated
vendored
Normal file
130
vendor/github.com/tdewolff/minify/svg/buffer.go
generated
vendored
Normal file
|
@ -0,0 +1,130 @@
|
|||
package svg // import "github.com/tdewolff/minify/svg"
|
||||
|
||||
import (
|
||||
"github.com/tdewolff/parse"
|
||||
"github.com/tdewolff/parse/svg"
|
||||
"github.com/tdewolff/parse/xml"
|
||||
)
|
||||
|
||||
// Token is a single token unit with an attribute value (if given) and hash of the data.
|
||||
type Token struct {
|
||||
xml.TokenType
|
||||
Hash svg.Hash
|
||||
Data []byte
|
||||
Text []byte
|
||||
AttrVal []byte
|
||||
}
|
||||
|
||||
// TokenBuffer is a buffer that allows for token look-ahead.
|
||||
type TokenBuffer struct {
|
||||
l *xml.Lexer
|
||||
|
||||
buf []Token
|
||||
pos int
|
||||
|
||||
attrBuffer []*Token
|
||||
}
|
||||
|
||||
// NewTokenBuffer returns a new TokenBuffer.
|
||||
func NewTokenBuffer(l *xml.Lexer) *TokenBuffer {
|
||||
return &TokenBuffer{
|
||||
l: l,
|
||||
buf: make([]Token, 0, 8),
|
||||
}
|
||||
}
|
||||
|
||||
func (z *TokenBuffer) read(t *Token) {
|
||||
t.TokenType, t.Data = z.l.Next()
|
||||
t.Text = z.l.Text()
|
||||
if t.TokenType == xml.AttributeToken {
|
||||
t.AttrVal = z.l.AttrVal()
|
||||
if len(t.AttrVal) > 1 && (t.AttrVal[0] == '"' || t.AttrVal[0] == '\'') {
|
||||
t.AttrVal = parse.ReplaceMultipleWhitespace(parse.TrimWhitespace(t.AttrVal[1 : len(t.AttrVal)-1])) // quotes will be readded in attribute loop if necessary
|
||||
}
|
||||
t.Hash = svg.ToHash(t.Text)
|
||||
} else if t.TokenType == xml.StartTagToken || t.TokenType == xml.EndTagToken {
|
||||
t.AttrVal = nil
|
||||
t.Hash = svg.ToHash(t.Text)
|
||||
} else {
|
||||
t.AttrVal = nil
|
||||
t.Hash = 0
|
||||
}
|
||||
}
|
||||
|
||||
// Peek returns the ith element and possibly does an allocation.
|
||||
// Peeking past an error will panic.
|
||||
func (z *TokenBuffer) Peek(pos int) *Token {
|
||||
pos += z.pos
|
||||
if pos >= len(z.buf) {
|
||||
if len(z.buf) > 0 && z.buf[len(z.buf)-1].TokenType == xml.ErrorToken {
|
||||
return &z.buf[len(z.buf)-1]
|
||||
}
|
||||
|
||||
c := cap(z.buf)
|
||||
d := len(z.buf) - z.pos
|
||||
p := pos - z.pos + 1 // required peek length
|
||||
var buf []Token
|
||||
if 2*p > c {
|
||||
buf = make([]Token, 0, 2*c+p)
|
||||
} else {
|
||||
buf = z.buf
|
||||
}
|
||||
copy(buf[:d], z.buf[z.pos:])
|
||||
|
||||
buf = buf[:p]
|
||||
pos -= z.pos
|
||||
for i := d; i < p; i++ {
|
||||
z.read(&buf[i])
|
||||
if buf[i].TokenType == xml.ErrorToken {
|
||||
buf = buf[:i+1]
|
||||
pos = i
|
||||
break
|
||||
}
|
||||
}
|
||||
z.pos, z.buf = 0, buf
|
||||
}
|
||||
return &z.buf[pos]
|
||||
}
|
||||
|
||||
// Shift returns the first element and advances position.
|
||||
func (z *TokenBuffer) Shift() *Token {
|
||||
if z.pos >= len(z.buf) {
|
||||
t := &z.buf[:1][0]
|
||||
z.read(t)
|
||||
return t
|
||||
}
|
||||
t := &z.buf[z.pos]
|
||||
z.pos++
|
||||
return t
|
||||
}
|
||||
|
||||
// Attributes extracts the gives attribute hashes from a tag.
|
||||
// It returns in the same order pointers to the requested token data or nil.
|
||||
func (z *TokenBuffer) Attributes(hashes ...svg.Hash) ([]*Token, *Token) {
|
||||
n := 0
|
||||
for {
|
||||
if t := z.Peek(n); t.TokenType != xml.AttributeToken {
|
||||
break
|
||||
}
|
||||
n++
|
||||
}
|
||||
if len(hashes) > cap(z.attrBuffer) {
|
||||
z.attrBuffer = make([]*Token, len(hashes))
|
||||
} else {
|
||||
z.attrBuffer = z.attrBuffer[:len(hashes)]
|
||||
for i := range z.attrBuffer {
|
||||
z.attrBuffer[i] = nil
|
||||
}
|
||||
}
|
||||
var replacee *Token
|
||||
for i := z.pos; i < z.pos+n; i++ {
|
||||
attr := &z.buf[i]
|
||||
for j, hash := range hashes {
|
||||
if hash == attr.Hash {
|
||||
z.attrBuffer[j] = attr
|
||||
replacee = attr
|
||||
}
|
||||
}
|
||||
}
|
||||
return z.attrBuffer, replacee
|
||||
}
|
68
vendor/github.com/tdewolff/minify/svg/buffer_test.go
generated
vendored
Normal file
68
vendor/github.com/tdewolff/minify/svg/buffer_test.go
generated
vendored
Normal file
|
@ -0,0 +1,68 @@
|
|||
package svg // import "github.com/tdewolff/minify/svg"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/tdewolff/parse/svg"
|
||||
"github.com/tdewolff/parse/xml"
|
||||
"github.com/tdewolff/test"
|
||||
)
|
||||
|
||||
func TestBuffer(t *testing.T) {
|
||||
// 0 12 3 4 5 6 7 8 9 01
|
||||
s := `<svg><path d="M0 0L1 1z"/>text<tag/>text</svg>`
|
||||
z := NewTokenBuffer(xml.NewLexer(bytes.NewBufferString(s)))
|
||||
|
||||
tok := z.Shift()
|
||||
test.That(t, tok.Hash == svg.Svg, "first token is <svg>")
|
||||
test.That(t, z.pos == 0, "shift first token and restore position")
|
||||
test.That(t, len(z.buf) == 0, "shift first token and restore length")
|
||||
|
||||
test.That(t, z.Peek(2).Hash == svg.D, "third token is d")
|
||||
test.That(t, z.pos == 0, "don't change position after peeking")
|
||||
test.That(t, len(z.buf) == 3, "mtwo tokens after peeking")
|
||||
|
||||
test.That(t, z.Peek(8).Hash == svg.Svg, "ninth token is <svg>")
|
||||
test.That(t, z.pos == 0, "don't change position after peeking")
|
||||
test.That(t, len(z.buf) == 9, "nine tokens after peeking")
|
||||
|
||||
test.That(t, z.Peek(9).TokenType == xml.ErrorToken, "tenth token is an error")
|
||||
test.That(t, z.Peek(9) == z.Peek(10), "tenth and eleventh token are EOF")
|
||||
test.That(t, len(z.buf) == 10, "ten tokens after peeking")
|
||||
|
||||
_ = z.Shift()
|
||||
tok = z.Shift()
|
||||
test.That(t, tok.Hash == svg.Path, "third token is <path>")
|
||||
test.That(t, z.pos == 2, "don't change position after peeking")
|
||||
}
|
||||
|
||||
func TestAttributes(t *testing.T) {
|
||||
r := bytes.NewBufferString(`<rect x="0" y="1" width="2" height="3" rx="4" ry="5"/>`)
|
||||
l := xml.NewLexer(r)
|
||||
tb := NewTokenBuffer(l)
|
||||
tb.Shift()
|
||||
for k := 0; k < 2; k++ { // run twice to ensure similar results
|
||||
attrs, _ := tb.Attributes(svg.X, svg.Y, svg.Width, svg.Height, svg.Rx, svg.Ry)
|
||||
for i := 0; i < 6; i++ {
|
||||
test.That(t, attrs[i] != nil, "attr must not be nil")
|
||||
val := string(attrs[i].AttrVal)
|
||||
j, _ := strconv.ParseInt(val, 10, 32)
|
||||
test.That(t, int(j) == i, "attr data is bad at position", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
func BenchmarkAttributes(b *testing.B) {
|
||||
r := bytes.NewBufferString(`<rect x="0" y="1" width="2" height="3" rx="4" ry="5"/>`)
|
||||
l := xml.NewLexer(r)
|
||||
tb := NewTokenBuffer(l)
|
||||
tb.Shift()
|
||||
tb.Peek(6)
|
||||
for i := 0; i < b.N; i++ {
|
||||
tb.Attributes(svg.X, svg.Y, svg.Width, svg.Height, svg.Rx, svg.Ry)
|
||||
}
|
||||
}
|
282
vendor/github.com/tdewolff/minify/svg/pathdata.go
generated
vendored
Normal file
282
vendor/github.com/tdewolff/minify/svg/pathdata.go
generated
vendored
Normal file
|
@ -0,0 +1,282 @@
|
|||
package svg
|
||||
|
||||
import (
|
||||
strconvStdlib "strconv"
|
||||
|
||||
"github.com/tdewolff/minify"
|
||||
"github.com/tdewolff/parse"
|
||||
"github.com/tdewolff/parse/strconv"
|
||||
)
|
||||
|
||||
type PathData struct {
|
||||
o *Minifier
|
||||
|
||||
x, y float64
|
||||
coords [][]byte
|
||||
coordFloats []float64
|
||||
|
||||
state PathDataState
|
||||
curBuffer []byte
|
||||
altBuffer []byte
|
||||
coordBuffer []byte
|
||||
}
|
||||
|
||||
type PathDataState struct {
|
||||
cmd byte
|
||||
prevDigit bool
|
||||
prevDigitIsInt bool
|
||||
}
|
||||
|
||||
func NewPathData(o *Minifier) *PathData {
|
||||
return &PathData{
|
||||
o: o,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PathData) ShortenPathData(b []byte) []byte {
|
||||
var x0, y0 float64
|
||||
var cmd byte
|
||||
|
||||
p.x, p.y = 0.0, 0.0
|
||||
p.coords = p.coords[:0]
|
||||
p.coordFloats = p.coordFloats[:0]
|
||||
p.state = PathDataState{}
|
||||
|
||||
j := 0
|
||||
for i := 0; i < len(b); i++ {
|
||||
c := b[i]
|
||||
if c == ' ' || c == ',' || c == '\n' || c == '\r' || c == '\t' {
|
||||
continue
|
||||
} else if c >= 'A' && (cmd == 0 || cmd != c || c == 'M' || c == 'm') { // any command
|
||||
if cmd != 0 {
|
||||
j += p.copyInstruction(b[j:], cmd)
|
||||
if cmd == 'M' || cmd == 'm' {
|
||||
x0 = p.x
|
||||
y0 = p.y
|
||||
} else if cmd == 'Z' || cmd == 'z' {
|
||||
p.x = x0
|
||||
p.y = y0
|
||||
}
|
||||
}
|
||||
cmd = c
|
||||
p.coords = p.coords[:0]
|
||||
p.coordFloats = p.coordFloats[:0]
|
||||
} else if n := parse.Number(b[i:]); n > 0 {
|
||||
f, _ := strconv.ParseFloat(b[i : i+n])
|
||||
p.coords = append(p.coords, b[i:i+n])
|
||||
p.coordFloats = append(p.coordFloats, f)
|
||||
i += n - 1
|
||||
}
|
||||
}
|
||||
if cmd != 0 {
|
||||
j += p.copyInstruction(b[j:], cmd)
|
||||
}
|
||||
return b[:j]
|
||||
}
|
||||
|
||||
func (p *PathData) copyInstruction(b []byte, cmd byte) int {
|
||||
n := len(p.coords)
|
||||
if n == 0 {
|
||||
if cmd == 'Z' || cmd == 'z' {
|
||||
b[0] = 'z'
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
isRelCmd := cmd >= 'a'
|
||||
|
||||
// get new cursor coordinates
|
||||
di := 0
|
||||
if (cmd == 'M' || cmd == 'm' || cmd == 'L' || cmd == 'l' || cmd == 'T' || cmd == 't') && n%2 == 0 {
|
||||
di = 2
|
||||
// reprint M always, as the first pair is a move but subsequent pairs are L
|
||||
if cmd == 'M' || cmd == 'm' {
|
||||
p.state.cmd = byte(0)
|
||||
}
|
||||
} else if cmd == 'H' || cmd == 'h' || cmd == 'V' || cmd == 'v' {
|
||||
di = 1
|
||||
} else if (cmd == 'S' || cmd == 's' || cmd == 'Q' || cmd == 'q') && n%4 == 0 {
|
||||
di = 4
|
||||
} else if (cmd == 'C' || cmd == 'c') && n%6 == 0 {
|
||||
di = 6
|
||||
} else if (cmd == 'A' || cmd == 'a') && n%7 == 0 {
|
||||
di = 7
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
|
||||
j := 0
|
||||
origCmd := cmd
|
||||
ax, ay := 0.0, 0.0
|
||||
for i := 0; i < n; i += di {
|
||||
// subsequent coordinate pairs for M are really L
|
||||
if i > 0 && (origCmd == 'M' || origCmd == 'm') {
|
||||
origCmd = 'L' + (origCmd - 'M')
|
||||
}
|
||||
|
||||
cmd = origCmd
|
||||
coords := p.coords[i : i+di]
|
||||
coordFloats := p.coordFloats[i : i+di]
|
||||
|
||||
if cmd == 'H' || cmd == 'h' {
|
||||
ax = coordFloats[di-1]
|
||||
if isRelCmd {
|
||||
ay = 0
|
||||
} else {
|
||||
ay = p.y
|
||||
}
|
||||
} else if cmd == 'V' || cmd == 'v' {
|
||||
if isRelCmd {
|
||||
ax = 0
|
||||
} else {
|
||||
ax = p.x
|
||||
}
|
||||
ay = coordFloats[di-1]
|
||||
} else {
|
||||
ax = coordFloats[di-2]
|
||||
ay = coordFloats[di-1]
|
||||
}
|
||||
|
||||
// switch from L to H or V whenever possible
|
||||
if cmd == 'L' || cmd == 'l' {
|
||||
if isRelCmd {
|
||||
if coordFloats[0] == 0 {
|
||||
cmd = 'v'
|
||||
coords = coords[1:]
|
||||
coordFloats = coordFloats[1:]
|
||||
} else if coordFloats[1] == 0 {
|
||||
cmd = 'h'
|
||||
coords = coords[:1]
|
||||
coordFloats = coordFloats[:1]
|
||||
}
|
||||
} else {
|
||||
if coordFloats[0] == p.x {
|
||||
cmd = 'V'
|
||||
coords = coords[1:]
|
||||
coordFloats = coordFloats[1:]
|
||||
} else if coordFloats[1] == p.y {
|
||||
cmd = 'H'
|
||||
coords = coords[:1]
|
||||
coordFloats = coordFloats[:1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// make a current and alternated path with absolute/relative altered
|
||||
var curState, altState PathDataState
|
||||
curState = p.shortenCurPosInstruction(cmd, coords)
|
||||
if isRelCmd {
|
||||
altState = p.shortenAltPosInstruction(cmd-'a'+'A', coordFloats, p.x, p.y)
|
||||
} else {
|
||||
altState = p.shortenAltPosInstruction(cmd-'A'+'a', coordFloats, -p.x, -p.y)
|
||||
}
|
||||
|
||||
// choose shortest, relative or absolute path?
|
||||
if len(p.altBuffer) < len(p.curBuffer) {
|
||||
j += copy(b[j:], p.altBuffer)
|
||||
p.state = altState
|
||||
} else {
|
||||
j += copy(b[j:], p.curBuffer)
|
||||
p.state = curState
|
||||
}
|
||||
|
||||
if isRelCmd {
|
||||
p.x += ax
|
||||
p.y += ay
|
||||
} else {
|
||||
p.x = ax
|
||||
p.y = ay
|
||||
}
|
||||
}
|
||||
return j
|
||||
}
|
||||
|
||||
func (p *PathData) shortenCurPosInstruction(cmd byte, coords [][]byte) PathDataState {
|
||||
state := p.state
|
||||
p.curBuffer = p.curBuffer[:0]
|
||||
if cmd != state.cmd && !(state.cmd == 'M' && cmd == 'L' || state.cmd == 'm' && cmd == 'l') {
|
||||
p.curBuffer = append(p.curBuffer, cmd)
|
||||
state.cmd = cmd
|
||||
state.prevDigit = false
|
||||
state.prevDigitIsInt = false
|
||||
}
|
||||
for i, coord := range coords {
|
||||
isFlag := false
|
||||
if (cmd == 'A' || cmd == 'a') && (i%7 == 3 || i%7 == 4) {
|
||||
isFlag = true
|
||||
}
|
||||
|
||||
coord = minify.Number(coord, p.o.Decimals)
|
||||
state.copyNumber(&p.curBuffer, coord, isFlag)
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
func (p *PathData) shortenAltPosInstruction(cmd byte, coordFloats []float64, x, y float64) PathDataState {
|
||||
state := p.state
|
||||
p.altBuffer = p.altBuffer[:0]
|
||||
if cmd != state.cmd && !(state.cmd == 'M' && cmd == 'L' || state.cmd == 'm' && cmd == 'l') {
|
||||
p.altBuffer = append(p.altBuffer, cmd)
|
||||
state.cmd = cmd
|
||||
state.prevDigit = false
|
||||
state.prevDigitIsInt = false
|
||||
}
|
||||
for i, f := range coordFloats {
|
||||
isFlag := false
|
||||
if cmd == 'L' || cmd == 'l' || cmd == 'C' || cmd == 'c' || cmd == 'S' || cmd == 's' || cmd == 'Q' || cmd == 'q' || cmd == 'T' || cmd == 't' || cmd == 'M' || cmd == 'm' {
|
||||
if i%2 == 0 {
|
||||
f += x
|
||||
} else {
|
||||
f += y
|
||||
}
|
||||
} else if cmd == 'H' || cmd == 'h' {
|
||||
f += x
|
||||
} else if cmd == 'V' || cmd == 'v' {
|
||||
f += y
|
||||
} else if cmd == 'A' || cmd == 'a' {
|
||||
if i%7 == 5 {
|
||||
f += x
|
||||
} else if i%7 == 6 {
|
||||
f += y
|
||||
} else if i%7 == 3 || i%7 == 4 {
|
||||
isFlag = true
|
||||
}
|
||||
}
|
||||
|
||||
p.coordBuffer = strconvStdlib.AppendFloat(p.coordBuffer[:0], f, 'g', -1, 64)
|
||||
coord := minify.Number(p.coordBuffer, p.o.Decimals)
|
||||
state.copyNumber(&p.altBuffer, coord, isFlag)
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
func (state *PathDataState) copyNumber(buffer *[]byte, coord []byte, isFlag bool) {
|
||||
if state.prevDigit && (coord[0] >= '0' && coord[0] <= '9' || coord[0] == '.' && state.prevDigitIsInt) {
|
||||
if coord[0] == '0' && !state.prevDigitIsInt {
|
||||
if isFlag {
|
||||
*buffer = append(*buffer, ' ', '0')
|
||||
state.prevDigitIsInt = true
|
||||
} else {
|
||||
*buffer = append(*buffer, '.', '0') // aggresively add dot so subsequent numbers could drop leading space
|
||||
// prevDigit stays true and prevDigitIsInt stays false
|
||||
}
|
||||
return
|
||||
}
|
||||
*buffer = append(*buffer, ' ')
|
||||
}
|
||||
state.prevDigit = true
|
||||
state.prevDigitIsInt = true
|
||||
if len(coord) > 2 && coord[len(coord)-2] == '0' && coord[len(coord)-1] == '0' {
|
||||
coord[len(coord)-2] = 'e'
|
||||
coord[len(coord)-1] = '2'
|
||||
state.prevDigitIsInt = false
|
||||
} else {
|
||||
for _, c := range coord {
|
||||
if c == '.' || c == 'e' || c == 'E' {
|
||||
state.prevDigitIsInt = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
*buffer = append(*buffer, coord...)
|
||||
}
|
60
vendor/github.com/tdewolff/minify/svg/pathdata_test.go
generated
vendored
Normal file
60
vendor/github.com/tdewolff/minify/svg/pathdata_test.go
generated
vendored
Normal file
|
@ -0,0 +1,60 @@
|
|||
package svg // import "github.com/tdewolff/minify/svg"
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/tdewolff/test"
|
||||
)
|
||||
|
||||
func TestPathData(t *testing.T) {
|
||||
var pathDataTests = []struct {
|
||||
pathData string
|
||||
expected string
|
||||
}{
|
||||
{"M10 10 20 10", "M10 10H20"},
|
||||
{"M10 10 10 20", "M10 10V20"},
|
||||
{"M50 50 100 100", "M50 50l50 50"},
|
||||
{"m50 50 40 40m50 50", "m50 50 40 40m50 50"},
|
||||
{"M10 10zM15 15", "M10 10zm5 5"},
|
||||
{"M50 50H55V55", "M50 50h5v5"},
|
||||
{"M10 10L11 10 11 11", "M10 10h1v1"},
|
||||
{"M10 10l1 0 0 1", "M10 10h1v1"},
|
||||
{"M10 10L11 11 0 0", "M10 10l1 1L0 0"},
|
||||
{"M246.614 51.028L246.614-5.665 189.922-5.665", "M246.614 51.028V-5.665H189.922"},
|
||||
{"M100,200 C100,100 250,100 250,200 S400,300 400,200", "M1e2 2e2c0-1e2 150-1e2 150 0s150 1e2 150 0"},
|
||||
{"M200,300 Q400,50 600,300 T1000,300", "M2e2 3e2q2e2-250 4e2.0t4e2.0"},
|
||||
{"M300,200 h-150 a150,150 0 1,0 150,-150 z", "M3e2 2e2H150A150 150 0 1 0 3e2 50z"},
|
||||
{"x5 5L10 10", "L10 10"},
|
||||
|
||||
{"M.0.1", "M0 .1"},
|
||||
{"M200.0.1", "M2e2.1"},
|
||||
{"M0 0a3.28 3.28.0.0.0 3.279 3.28", "M0 0a3.28 3.28.0 0 0 3.279 3.28"}, // #114
|
||||
{"A1.1.0.0.0.0.2.3", "A1.1.0.0 0 0 .2."}, // bad input (sweep and large-arc are not booleans) gives bad output
|
||||
|
||||
// fuzz
|
||||
{"", ""},
|
||||
{"ML", ""},
|
||||
{".8.00c0", ""},
|
||||
{".1.04h0e6.0e6.0e0.0", "h0 0 0 0"},
|
||||
{"M.1.0.0.2Z", "M.1.0.0.2z"},
|
||||
{"A.0.0.0.0.3.2e3.7.0.0.0.0.0.1.3.0.0.0.0.2.3.2.0.0.0.0.20.2e-10.0.0.0.0.0.0.0.0", "A0 0 0 0 .3 2e2.7.0.0.0 0 0 .1.3 30 0 0 0 .2.3.2 3 20 0 0 .2 2e-1100 11 0 0 0 "}, // bad input (sweep and large-arc are not booleans) gives bad output
|
||||
}
|
||||
|
||||
p := NewPathData(&Minifier{Decimals: -1})
|
||||
for _, tt := range pathDataTests {
|
||||
t.Run(tt.pathData, func(t *testing.T) {
|
||||
path := p.ShortenPathData([]byte(tt.pathData))
|
||||
test.Minify(t, tt.pathData, nil, string(path), tt.expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
func BenchmarkShortenPathData(b *testing.B) {
|
||||
p := NewPathData(&Minifier{})
|
||||
r := []byte("M8.64,223.948c0,0,143.468,3.431,185.777-181.808c2.673-11.702-1.23-20.154,1.316-33.146h16.287c0,0-3.14,17.248,1.095,30.848c21.392,68.692-4.179,242.343-204.227,196.59L8.64,223.948z")
|
||||
for i := 0; i < b.N; i++ {
|
||||
p.ShortenPathData(r)
|
||||
}
|
||||
}
|
434
vendor/github.com/tdewolff/minify/svg/svg.go
generated
vendored
Normal file
434
vendor/github.com/tdewolff/minify/svg/svg.go
generated
vendored
Normal file
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
199
vendor/github.com/tdewolff/minify/svg/svg_test.go
generated
vendored
Normal file
199
vendor/github.com/tdewolff/minify/svg/svg_test.go
generated
vendored
Normal file
|
@ -0,0 +1,199 @@
|
|||
package svg // import "github.com/tdewolff/minify/svg"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/tdewolff/minify"
|
||||
"github.com/tdewolff/minify/css"
|
||||
"github.com/tdewolff/test"
|
||||
)
|
||||
|
||||
func TestSVG(t *testing.T) {
|
||||
svgTests := []struct {
|
||||
svg string
|
||||
expected string
|
||||
}{
|
||||
{`<!-- comment -->`, ``},
|
||||
{`<!DOCTYPE svg SYSTEM "foo.dtd">`, ``},
|
||||
{`<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "foo.dtd" [ <!ENTITY x "bar"> ]>`, `<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "foo.dtd" [ <!ENTITY x "bar"> ]>`},
|
||||
{`<!DOCTYPE svg SYSTEM "foo.dtd">`, ``},
|
||||
{`<?xml version="1.0" ?>`, ``},
|
||||
{`<style> <![CDATA[ x ]]> </style>`, `<style>x</style>`},
|
||||
{`<style> <![CDATA[ <<<< ]]> </style>`, `<style><<<<</style>`},
|
||||
{`<style> <![CDATA[ <<<<< ]]> </style>`, `<style><![CDATA[ <<<<< ]]></style>`},
|
||||
{`<style/><![CDATA[ <<<<< ]]>`, `<style/><![CDATA[ <<<<< ]]>`},
|
||||
{`<svg version="1.0"></svg>`, `<svg version="1.0"/>`},
|
||||
{`<svg version="1.1" x="0" y="0px" width="100%" height="100%"><path/></svg>`, `<svg><path/></svg>`},
|
||||
{`<path x="a"> </path>`, `<path x="a"/>`},
|
||||
{`<path x=" a "/>`, `<path x="a"/>`},
|
||||
{"<path x=\" a \n b \"/>", `<path x="a b"/>`},
|
||||
{`<path x="5.0px" y="0%"/>`, `<path x="5" y="0"/>`},
|
||||
{`<svg viewBox="5.0px 5px 240IN px"><path/></svg>`, `<svg viewBox="5 5 240in px"><path/></svg>`},
|
||||
{`<svg viewBox="5.0!5px"><path/></svg>`, `<svg viewBox="5!5px"><path/></svg>`},
|
||||
{`<path d="M 100 100 L 300 100 L 200 100 z"/>`, `<path d="M1e2 1e2H3e2 2e2z"/>`},
|
||||
{`<path d="M100 -100M200 300z"/>`, `<path d="M1e2-1e2M2e2 3e2z"/>`},
|
||||
{`<path d="M0.5 0.6 M -100 0.5z"/>`, `<path d="M.5.6M-1e2.5z"/>`},
|
||||
{`<path d="M01.0 0.6 z"/>`, `<path d="M1 .6z"/>`},
|
||||
{`<path d="M20 20l-10-10z"/>`, `<path d="M20 20 10 10z"/>`},
|
||||
{`<?xml version="1.0" encoding="utf-8"?>`, ``},
|
||||
{`<svg viewbox="0 0 16 16"><path/></svg>`, `<svg viewbox="0 0 16 16"><path/></svg>`},
|
||||
{`<g></g>`, ``},
|
||||
{`<g><path/></g>`, `<path/>`},
|
||||
{`<g id="a"><g><path/></g></g>`, `<g id="a"><path/></g>`},
|
||||
{`<path fill="#ffffff"/>`, `<path fill="#fff"/>`},
|
||||
{`<path fill="#fff"/>`, `<path fill="#fff"/>`},
|
||||
{`<path fill="white"/>`, `<path fill="#fff"/>`},
|
||||
{`<path fill="#ff0000"/>`, `<path fill="red"/>`},
|
||||
{`<line x1="5" y1="10" x2="20" y2="40"/>`, `<path d="M5 10 20 40z"/>`},
|
||||
{`<rect x="5" y="10" width="20" height="40"/>`, `<path d="M5 10h20v40H5z"/>`},
|
||||
{`<rect x="-5.669" y="147.402" fill="#843733" width="252.279" height="14.177"/>`, `<path fill="#843733" d="M-5.669 147.402h252.279v14.177H-5.669z"/>`},
|
||||
{`<rect x="5" y="10" rx="2" ry="3"/>`, `<rect x="5" y="10" rx="2" ry="3"/>`},
|
||||
{`<rect x="5" y="10" height="40"/>`, ``},
|
||||
{`<rect x="5" y="10" width="30" height="0"/>`, ``},
|
||||
{`<polygon points="1,2 3,4"/>`, `<path d="M1 2 3 4z"/>`},
|
||||
{`<polyline points="1,2 3,4"/>`, `<path d="M1 2 3 4"/>`},
|
||||
{`<svg contentStyleType="text/json ; charset=iso-8859-1"><style>{a : true}</style></svg>`, `<svg contentStyleType="text/json;charset=iso-8859-1"><style>{a : true}</style></svg>`},
|
||||
{`<metadata><dc:title /></metadata>`, ``},
|
||||
|
||||
// from SVGO
|
||||
{`<!DOCTYPE bla><?xml?><!-- comment --><metadata/>`, ``},
|
||||
|
||||
{`<polygon fill="none" stroke="#000" points="-0.1,"/>`, `<polygon fill="none" stroke="#000" points="-0.1,"/>`}, // #45
|
||||
{`<path stroke="url(#UPPERCASE)"/>`, `<path stroke="url(#UPPERCASE)"/>`}, // #117
|
||||
|
||||
// go fuzz
|
||||
{`<0 d=09e9.6e-9e0`, `<0 d=""`},
|
||||
{`<line`, `<line`},
|
||||
}
|
||||
|
||||
m := minify.New()
|
||||
for _, tt := range svgTests {
|
||||
t.Run(tt.svg, func(t *testing.T) {
|
||||
r := bytes.NewBufferString(tt.svg)
|
||||
w := &bytes.Buffer{}
|
||||
err := Minify(m, w, r, nil)
|
||||
test.Minify(t, tt.svg, err, w.String(), tt.expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSVGStyle(t *testing.T) {
|
||||
svgTests := []struct {
|
||||
svg string
|
||||
expected string
|
||||
}{
|
||||
{`<style> a > b {} </style>`, `<style>a>b{}</style>`},
|
||||
{`<style> <![CDATA[ @media x < y {} ]]> </style>`, `<style>@media x < y{}</style>`},
|
||||
{`<style> <![CDATA[ * { content: '<<<<<'; } ]]> </style>`, `<style><![CDATA[*{content:'<<<<<'}]]></style>`},
|
||||
{`<style/><![CDATA[ * { content: '<<<<<'; ]]>`, `<style/><![CDATA[ * { content: '<<<<<'; ]]>`},
|
||||
{`<path style="fill: black; stroke: #ff0000;"/>`, `<path style="fill:#000;stroke:red"/>`},
|
||||
}
|
||||
|
||||
m := minify.New()
|
||||
m.AddFunc("text/css", css.Minify)
|
||||
for _, tt := range svgTests {
|
||||
t.Run(tt.svg, func(t *testing.T) {
|
||||
r := bytes.NewBufferString(tt.svg)
|
||||
w := &bytes.Buffer{}
|
||||
err := Minify(m, w, r, nil)
|
||||
test.Minify(t, tt.svg, err, w.String(), tt.expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSVGDecimals(t *testing.T) {
|
||||
var svgTests = []struct {
|
||||
svg string
|
||||
expected string
|
||||
}{
|
||||
{`<svg x="1.234" y="0.001" width="1.001"><path/></svg>`, `<svg x="1.2" width="1"><path/></svg>`},
|
||||
}
|
||||
|
||||
m := minify.New()
|
||||
o := &Minifier{Decimals: 1}
|
||||
for _, tt := range svgTests {
|
||||
t.Run(tt.svg, func(t *testing.T) {
|
||||
r := bytes.NewBufferString(tt.svg)
|
||||
w := &bytes.Buffer{}
|
||||
err := o.Minify(m, w, r, nil)
|
||||
test.Minify(t, tt.svg, err, w.String(), tt.expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReaderErrors(t *testing.T) {
|
||||
r := test.NewErrorReader(0)
|
||||
w := &bytes.Buffer{}
|
||||
m := minify.New()
|
||||
err := Minify(m, w, r, nil)
|
||||
test.T(t, err, test.ErrPlain, "return error at first read")
|
||||
}
|
||||
|
||||
func TestWriterErrors(t *testing.T) {
|
||||
errorTests := []struct {
|
||||
svg string
|
||||
n []int
|
||||
}{
|
||||
{`<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "foo.dtd" [ <!ENTITY x "bar"> ]>`, []int{0}},
|
||||
{`abc`, []int{0}},
|
||||
{`<style>abc</style>`, []int{2}},
|
||||
{`<![CDATA[ <<<< ]]>`, []int{0}},
|
||||
{`<![CDATA[ <<<<< ]]>`, []int{0}},
|
||||
{`<path d="x"/>`, []int{0, 1, 2, 3, 4, 5}},
|
||||
{`<path></path>`, []int{1}},
|
||||
{`<svg>x</svg>`, []int{1, 3}},
|
||||
{`<svg>x</svg >`, []int{3}},
|
||||
}
|
||||
|
||||
m := minify.New()
|
||||
for _, tt := range errorTests {
|
||||
for _, n := range tt.n {
|
||||
t.Run(fmt.Sprint(tt.svg, " ", tt.n), func(t *testing.T) {
|
||||
r := bytes.NewBufferString(tt.svg)
|
||||
w := test.NewErrorWriter(n)
|
||||
err := Minify(m, w, r, nil)
|
||||
test.T(t, err, test.ErrPlain)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMinifyErrors(t *testing.T) {
|
||||
errorTests := []struct {
|
||||
svg string
|
||||
err error
|
||||
}{
|
||||
{`<style>abc</style>`, test.ErrPlain},
|
||||
{`<style><![CDATA[abc]]></style>`, test.ErrPlain},
|
||||
{`<path style="abc"/>`, test.ErrPlain},
|
||||
}
|
||||
|
||||
m := minify.New()
|
||||
m.AddFunc("text/css", func(_ *minify.M, w io.Writer, r io.Reader, _ map[string]string) error {
|
||||
return test.ErrPlain
|
||||
})
|
||||
for _, tt := range errorTests {
|
||||
t.Run(tt.svg, func(t *testing.T) {
|
||||
r := bytes.NewBufferString(tt.svg)
|
||||
w := &bytes.Buffer{}
|
||||
err := Minify(m, w, r, nil)
|
||||
test.T(t, err, tt.err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
func ExampleMinify() {
|
||||
m := minify.New()
|
||||
m.AddFunc("image/svg+xml", Minify)
|
||||
m.AddFunc("text/css", css.Minify)
|
||||
|
||||
if err := m.Minify("image/svg+xml", os.Stdout, os.Stdin); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
96
vendor/github.com/tdewolff/minify/svg/table.go
generated
vendored
Normal file
96
vendor/github.com/tdewolff/minify/svg/table.go
generated
vendored
Normal file
|
@ -0,0 +1,96 @@
|
|||
package svg // import "github.com/tdewolff/minify/svg"
|
||||
|
||||
import "github.com/tdewolff/parse/svg"
|
||||
|
||||
var containerTagMap = map[svg.Hash]bool{
|
||||
svg.A: true,
|
||||
svg.Defs: true,
|
||||
svg.G: true,
|
||||
svg.Marker: true,
|
||||
svg.Mask: true,
|
||||
svg.Missing_Glyph: true,
|
||||
svg.Pattern: true,
|
||||
svg.Switch: true,
|
||||
svg.Symbol: true,
|
||||
}
|
||||
|
||||
var colorAttrMap = map[svg.Hash]bool{
|
||||
svg.Color: true,
|
||||
svg.Fill: true,
|
||||
svg.Stroke: true,
|
||||
svg.Stop_Color: true,
|
||||
svg.Flood_Color: true,
|
||||
svg.Lighting_Color: true,
|
||||
}
|
||||
|
||||
// var styleAttrMap = map[svg.Hash]bool{
|
||||
// svg.Font: true,
|
||||
// svg.Font_Family: true,
|
||||
// svg.Font_Size: true,
|
||||
// svg.Font_Size_Adjust: true,
|
||||
// svg.Font_Stretch: true,
|
||||
// svg.Font_Style: true,
|
||||
// svg.Font_Variant: true,
|
||||
// svg.Font_Weight: true,
|
||||
// svg.Direction: true,
|
||||
// svg.Letter_Spacing: true,
|
||||
// svg.Text_Decoration: true,
|
||||
// svg.Unicode_Bidi: true,
|
||||
// svg.White_Space: true,
|
||||
// svg.Word_Spacing: true,
|
||||
// svg.Clip: true,
|
||||
// svg.Color: true,
|
||||
// svg.Cursor: true,
|
||||
// svg.Display: true,
|
||||
// svg.Overflow: true,
|
||||
// svg.Visibility: true,
|
||||
// svg.Clip_Path: true,
|
||||
// svg.Clip_Rule: true,
|
||||
// svg.Mask: true,
|
||||
// svg.Opacity: true,
|
||||
// svg.Enable_Background: true,
|
||||
// svg.Filter: true,
|
||||
// svg.Flood_Color: true,
|
||||
// svg.Flood_Opacity: true,
|
||||
// svg.Lighting_Color: true,
|
||||
// svg.Solid_Color: true,
|
||||
// svg.Solid_Opacity: true,
|
||||
// svg.Stop_Color: true,
|
||||
// svg.Stop_Opacity: true,
|
||||
// svg.Pointer_Events: true,
|
||||
// svg.Buffered_Rendering: true,
|
||||
// svg.Color_Interpolation: true,
|
||||
// svg.Color_Interpolation_Filters: true,
|
||||
// svg.Color_Profile: true,
|
||||
// svg.Color_Rendering: true,
|
||||
// svg.Fill: true,
|
||||
// svg.Fill_Opacity: true,
|
||||
// svg.Fill_Rule: true,
|
||||
// svg.Image_Rendering: true,
|
||||
// svg.Marker: true,
|
||||
// svg.Marker_End: true,
|
||||
// svg.Marker_Mid: true,
|
||||
// svg.Marker_Start: true,
|
||||
// svg.Shape_Rendering: true,
|
||||
// svg.Stroke: true,
|
||||
// svg.Stroke_Dasharray: true,
|
||||
// svg.Stroke_Dashoffset: true,
|
||||
// svg.Stroke_Linecap: true,
|
||||
// svg.Stroke_Linejoin: true,
|
||||
// svg.Stroke_Miterlimit: true,
|
||||
// svg.Stroke_Opacity: true,
|
||||
// svg.Stroke_Width: true,
|
||||
// svg.Paint_Order: true,
|
||||
// svg.Vector_Effect: true,
|
||||
// svg.Viewport_Fill: true,
|
||||
// svg.Viewport_Fill_Opacity: true,
|
||||
// svg.Text_Rendering: true,
|
||||
// svg.Alignment_Baseline: true,
|
||||
// svg.Baseline_Shift: true,
|
||||
// svg.Dominant_Baseline: true,
|
||||
// svg.Glyph_Orientation_Horizontal: true,
|
||||
// svg.Glyph_Orientation_Vertical: true,
|
||||
// svg.Kerning: true,
|
||||
// svg.Text_Anchor: true,
|
||||
// svg.Writing_Mode: true,
|
||||
// }
|
Loading…
Add table
Add a link
Reference in a new issue