miniflux/vendor/github.com/tdewolff/minify/common.go
2018-07-05 22:18:51 -07:00

462 lines
10 KiB
Go

package minify // import "github.com/tdewolff/minify"
import (
"bytes"
"encoding/base64"
"net/url"
"github.com/tdewolff/parse"
"github.com/tdewolff/parse/strconv"
)
// Epsilon is the closest number to zero that is not considered to be zero.
var Epsilon = 0.00001
// Mediatype minifies a given mediatype by removing all whitespace.
func Mediatype(b []byte) []byte {
j := 0
start := 0
inString := false
for i, c := range b {
if !inString && parse.IsWhitespace(c) {
if start != 0 {
j += copy(b[j:], b[start:i])
} else {
j += i
}
start = i + 1
} else if c == '"' {
inString = !inString
}
}
if start != 0 {
j += copy(b[j:], b[start:])
return parse.ToLower(b[:j])
}
return parse.ToLower(b)
}
// DataURI minifies a data URI and calls a minifier by the specified mediatype. Specifications: https://www.ietf.org/rfc/rfc2397.txt.
func DataURI(m *M, dataURI []byte) []byte {
if mediatype, data, err := parse.DataURI(dataURI); err == nil {
dataURI, _ = m.Bytes(string(mediatype), data)
base64Len := len(";base64") + base64.StdEncoding.EncodedLen(len(dataURI))
asciiLen := len(dataURI)
for _, c := range dataURI {
if 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' || c == '-' || c == '_' || c == '.' || c == '~' || c == ' ' {
asciiLen++
} else {
asciiLen += 2
}
if asciiLen > base64Len {
break
}
}
if asciiLen > base64Len {
encoded := make([]byte, base64Len-len(";base64"))
base64.StdEncoding.Encode(encoded, dataURI)
dataURI = encoded
mediatype = append(mediatype, []byte(";base64")...)
} else {
dataURI = []byte(url.QueryEscape(string(dataURI)))
dataURI = bytes.Replace(dataURI, []byte("\""), []byte("\\\""), -1)
}
if len("text/plain") <= len(mediatype) && parse.EqualFold(mediatype[:len("text/plain")], []byte("text/plain")) {
mediatype = mediatype[len("text/plain"):]
}
for i := 0; i+len(";charset=us-ascii") <= len(mediatype); i++ {
// must start with semicolon and be followed by end of mediatype or semicolon
if mediatype[i] == ';' && parse.EqualFold(mediatype[i+1:i+len(";charset=us-ascii")], []byte("charset=us-ascii")) && (i+len(";charset=us-ascii") >= len(mediatype) || mediatype[i+len(";charset=us-ascii")] == ';') {
mediatype = append(mediatype[:i], mediatype[i+len(";charset=us-ascii"):]...)
break
}
}
dataURI = append(append(append([]byte("data:"), mediatype...), ','), dataURI...)
}
return dataURI
}
const MaxInt = int(^uint(0) >> 1)
const MinInt = -MaxInt - 1
// Decimal minifies a given byte slice containing a number (see parse.Number) and removes superfluous characters.
// It does not parse or output exponents.
func Decimal(num []byte, prec int) []byte {
// omit first + and register mantissa start and end, whether it's negative and the exponent
neg := false
start := 0
dot := -1
end := len(num)
if 0 < end && (num[0] == '+' || num[0] == '-') {
if num[0] == '-' {
neg = true
}
start++
}
for i, c := range num[start:] {
if c == '.' {
dot = start + i
break
}
}
if dot == -1 {
dot = end
}
// trim leading zeros but leave at least one digit
for start < end-1 && num[start] == '0' {
start++
}
// trim trailing zeros
i := end - 1
for ; i > dot; i-- {
if num[i] != '0' {
end = i + 1
break
}
}
if i == dot {
end = dot
if start == end {
num[start] = '0'
return num[start : start+1]
}
} else if start == end-1 && num[start] == '0' {
return num[start:end]
}
// apply precision
if prec > -1 && dot+1+prec < end {
end = dot + 1 + prec
inc := num[end] >= '5'
if inc || num[end-1] == '0' {
for i := end - 1; i > start; i-- {
if i == dot {
end--
} else if inc {
if num[i] == '9' {
if i > dot {
end--
} else {
num[i] = '0'
}
} else {
num[i]++
inc = false
break
}
} else if i > dot && num[i] == '0' {
end--
}
}
}
if dot == start && end == start+1 {
if inc {
num[start] = '1'
} else {
num[start] = '0'
}
} else {
if dot+1 == end {
end--
}
if inc {
if num[start] == '9' {
num[start] = '0'
copy(num[start+1:], num[start:end])
end++
num[start] = '1'
} else {
num[start]++
}
}
}
}
if neg {
start--
num[start] = '-'
}
return num[start:end]
}
// Number minifies a given byte slice containing a number (see parse.Number) and removes superfluous characters.
func Number(num []byte, prec int) []byte {
// omit first + and register mantissa start and end, whether it's negative and the exponent
neg := false
start := 0
dot := -1
end := len(num)
origExp := 0
if 0 < end && (num[0] == '+' || num[0] == '-') {
if num[0] == '-' {
neg = true
}
start++
}
for i, c := range num[start:] {
if c == '.' {
dot = start + i
} else if c == 'e' || c == 'E' {
end = start + i
i += start + 1
if i < len(num) && num[i] == '+' {
i++
}
if tmpOrigExp, n := strconv.ParseInt(num[i:]); n > 0 && tmpOrigExp >= int64(MinInt) && tmpOrigExp <= int64(MaxInt) {
// range checks for when int is 32 bit
origExp = int(tmpOrigExp)
} else {
return num
}
break
}
}
if dot == -1 {
dot = end
}
// trim leading zeros but leave at least one digit
for start < end-1 && num[start] == '0' {
start++
}
// trim trailing zeros
i := end - 1
for ; i > dot; i-- {
if num[i] != '0' {
end = i + 1
break
}
}
if i == dot {
end = dot
if start == end {
num[start] = '0'
return num[start : start+1]
}
} else if start == end-1 && num[start] == '0' {
return num[start:end]
}
// n is the number of significant digits
// normExp would be the exponent if it were normalised (0.1 <= f < 1)
n := 0
normExp := 0
if dot == start {
for i = dot + 1; i < end; i++ {
if num[i] != '0' {
n = end - i
normExp = dot - i + 1
break
}
}
} else if dot == end {
normExp = end - start
for i = end - 1; i >= start; i-- {
if num[i] != '0' {
n = i + 1 - start
end = i + 1
break
}
}
} else {
n = end - start - 1
normExp = dot - start
}
if origExp < 0 && (normExp < MinInt-origExp || normExp-n < MinInt-origExp) || origExp > 0 && (normExp > MaxInt-origExp || normExp-n > MaxInt-origExp) {
return num
}
normExp += origExp
// intExp would be the exponent if it were an integer
intExp := normExp - n
lenIntExp := 1
if intExp <= -10 || intExp >= 10 {
lenIntExp = strconv.LenInt(int64(intExp))
}
// there are three cases to consider when printing the number
// case 1: without decimals and with an exponent (large numbers)
// case 2: with decimals and without an exponent (around zero)
// case 3: without decimals and with a negative exponent (small numbers)
if normExp >= n {
// case 1
if dot < end {
if dot == start {
start = end - n
} else {
// TODO: copy the other part if shorter?
copy(num[dot:], num[dot+1:end])
end--
}
}
if normExp >= n+3 {
num[end] = 'e'
end++
for i := end + lenIntExp - 1; i >= end; i-- {
num[i] = byte(intExp%10) + '0'
intExp /= 10
}
end += lenIntExp
} else if normExp == n+2 {
num[end] = '0'
num[end+1] = '0'
end += 2
} else if normExp == n+1 {
num[end] = '0'
end++
}
} else if normExp >= -lenIntExp-1 {
// case 2
zeroes := -normExp
newDot := 0
if zeroes > 0 {
// dot placed at the front and add zeroes
newDot = end - n - zeroes - 1
if newDot != dot {
d := start - newDot
if d > 0 {
if dot < end {
// copy original digits behind the dot backwards
copy(num[dot+1+d:], num[dot+1:end])
if dot > start {
// copy original digits before the dot backwards
copy(num[start+d+1:], num[start:dot])
}
} else if dot > start {
// copy original digits before the dot backwards
copy(num[start+d:], num[start:dot])
}
newDot = start
end += d
} else {
start += -d
}
num[newDot] = '.'
for i := 0; i < zeroes; i++ {
num[newDot+1+i] = '0'
}
}
} else {
// placed in the middle
if dot == start {
// TODO: try if placing at the end reduces copying
// when there are zeroes after the dot
dot = end - n - 1
start = dot
} else if dot >= end {
// TODO: try if placing at the start reduces copying
// when input has no dot in it
dot = end
end++
}
newDot = start + normExp
if newDot > dot {
// copy digits forwards
copy(num[dot:], num[dot+1:newDot+1])
} else if newDot < dot {
// copy digits backwards
copy(num[newDot+1:], num[newDot:dot])
}
num[newDot] = '.'
}
// apply precision
dot = newDot
if prec > -1 && dot+1+prec < end {
end = dot + 1 + prec
inc := num[end] >= '5'
if inc || num[end-1] == '0' {
for i := end - 1; i > start; i-- {
if i == dot {
end--
} else if inc {
if num[i] == '9' {
if i > dot {
end--
} else {
num[i] = '0'
}
} else {
num[i]++
inc = false
break
}
} else if i > dot && num[i] == '0' {
end--
}
}
}
if dot == start && end == start+1 {
if inc {
num[start] = '1'
} else {
num[start] = '0'
}
} else {
if dot+1 == end {
end--
}
if inc {
if num[start] == '9' {
num[start] = '0'
copy(num[start+1:], num[start:end])
end++
num[start] = '1'
} else {
num[start]++
}
}
}
}
} else {
// case 3
// find new end, considering moving numbers to the front, removing the dot and increasing the length of the exponent
newEnd := end
if dot == start {
newEnd = start + n
} else {
newEnd--
}
newEnd += 2 + lenIntExp
exp := intExp
lenExp := lenIntExp
if newEnd < len(num) {
// it saves space to convert the decimal to an integer and decrease the exponent
if dot < end {
if dot == start {
copy(num[start:], num[end-n:end])
end = start + n
} else {
copy(num[dot:], num[dot+1:end])
end--
}
}
} else {
// it does not save space and will panic, so we revert to the original representation
exp = origExp
lenExp = 1
if origExp <= -10 || origExp >= 10 {
lenExp = strconv.LenInt(int64(origExp))
}
}
num[end] = 'e'
num[end+1] = '-'
end += 2
exp = -exp
for i := end + lenExp - 1; i >= end; i-- {
num[i] = byte(exp%10) + '0'
exp /= 10
}
end += lenExp
}
if neg {
start--
num[start] = '-'
}
return num[start:end]
}