diff options
Diffstat (limited to 'vendor/github.com/tdewolff/minify/svg/pathdata.go')
-rw-r--r-- | vendor/github.com/tdewolff/minify/svg/pathdata.go | 282 |
1 files changed, 282 insertions, 0 deletions
diff --git a/vendor/github.com/tdewolff/minify/svg/pathdata.go b/vendor/github.com/tdewolff/minify/svg/pathdata.go new file mode 100644 index 0000000..f14e847 --- /dev/null +++ b/vendor/github.com/tdewolff/minify/svg/pathdata.go @@ -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...) +} |