1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552 |
- // Package css minifies CSS3 following the specifications at http://www.w3.org/TR/css-syntax-3/.
- package css
- import (
- "bytes"
- "fmt"
- "io"
- "math"
- "sort"
- "strconv"
- "strings"
- "github.com/tdewolff/minify/v2"
- "github.com/tdewolff/parse/v2"
- "github.com/tdewolff/parse/v2/css"
- strconvParse "github.com/tdewolff/parse/v2/strconv"
- )
- var (
- spaceBytes = []byte(" ")
- colonBytes = []byte(":")
- semicolonBytes = []byte(";")
- commaBytes = []byte(",")
- leftBracketBytes = []byte("{")
- rightBracketBytes = []byte("}")
- rightParenBytes = []byte(")")
- urlBytes = []byte("url(")
- varBytes = []byte("var(")
- zeroBytes = []byte("0")
- oneBytes = []byte("1")
- transparentBytes = []byte("transparent")
- blackBytes = []byte("#0000")
- initialBytes = []byte("initial")
- noneBytes = []byte("none")
- autoBytes = []byte("auto")
- leftBytes = []byte("left")
- topBytes = []byte("top")
- n400Bytes = []byte("400")
- n700Bytes = []byte("700")
- n50pBytes = []byte("50%")
- n100pBytes = []byte("100%")
- repeatXBytes = []byte("repeat-x")
- repeatYBytes = []byte("repeat-y")
- importantBytes = []byte("!important")
- dataSchemeBytes = []byte("data:")
- )
- type cssMinifier struct {
- m *minify.M
- w io.Writer
- p *css.Parser
- o *Minifier
- tokenBuffer []Token
- tokensLevel int
- }
- ////////////////////////////////////////////////////////////////
- // Minifier is a CSS minifier.
- type Minifier struct {
- KeepCSS2 bool
- Precision int // number of significant digits
- newPrecision int // precision for new numbers
- }
- // Minify minifies CSS 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 (&Minifier{}).Minify(m, w, r, params)
- }
- // Token is a parsed token with extra information for functions.
- type Token struct {
- css.TokenType
- Data []byte
- Args []Token // only filled for functions
- Fun, Ident Hash // only filled for functions and identifiers respectively
- }
- func (t Token) String() string {
- if len(t.Args) == 0 {
- return t.TokenType.String() + "(" + string(t.Data) + ")"
- }
- return fmt.Sprint(t.Args)
- }
- // Equal returns true if both tokens are equal.
- func (t Token) Equal(t2 Token) bool {
- if t.TokenType == t2.TokenType && bytes.Equal(t.Data, t2.Data) && len(t.Args) == len(t2.Args) {
- for i := 0; i < len(t.Args); i++ {
- if t.Args[i].TokenType != t2.Args[i].TokenType || !bytes.Equal(t.Args[i].Data, t2.Args[i].Data) {
- return false
- }
- }
- return true
- }
- return false
- }
- // IsZero return true if a dimension, percentage, or number token is zero.
- func (t Token) IsZero() bool {
- // as each number is already minified, starting with a zero means it is zero
- return (t.TokenType == css.DimensionToken || t.TokenType == css.PercentageToken || t.TokenType == css.NumberToken) && t.Data[0] == '0'
- }
- // IsLength returns true if the token is a length.
- func (t Token) IsLength() bool {
- if t.TokenType == css.DimensionToken {
- return true
- } else if t.TokenType == css.NumberToken && t.Data[0] == '0' {
- return true
- } else if t.TokenType == css.FunctionToken {
- fun := ToHash(t.Data[:len(t.Data)-1])
- if fun == Calc || fun == Min || fun == Max || fun == Clamp || fun == Attr || fun == Var || fun == Env {
- return true
- }
- }
- return false
- }
- // IsLengthPercentage returns true if the token is a length or percentage token.
- func (t Token) IsLengthPercentage() bool {
- return t.TokenType == css.PercentageToken || t.IsLength()
- }
- ////////////////////////////////////////////////////////////////
- // Minify minifies CSS data, it reads from r and writes to w.
- func (o *Minifier) Minify(m *minify.M, w io.Writer, r io.Reader, params map[string]string) error {
- o.newPrecision = o.Precision
- if o.newPrecision <= 0 || 15 < o.newPrecision {
- o.newPrecision = 15 // minimum number of digits a double can represent exactly
- }
- z := parse.NewInput(r)
- defer z.Restore()
- isInline := params != nil && params["inline"] == "1"
- c := &cssMinifier{
- m: m,
- w: w,
- p: css.NewParser(z, isInline),
- o: o,
- }
- c.minifyGrammar()
- if _, err := w.Write(nil); err != nil {
- return err
- }
- if c.p.Err() == io.EOF {
- return nil
- }
- return c.p.Err()
- }
- func (c *cssMinifier) minifyGrammar() {
- semicolonQueued := false
- for {
- gt, _, data := c.p.Next()
- switch gt {
- case css.ErrorGrammar:
- if c.p.HasParseError() {
- if semicolonQueued {
- c.w.Write(semicolonBytes)
- }
- // write out the offending declaration (but save the semicolon)
- vals := c.p.Values()
- if len(vals) > 0 && vals[len(vals)-1].TokenType == css.SemicolonToken {
- vals = vals[:len(vals)-1]
- semicolonQueued = true
- }
- for _, val := range vals {
- c.w.Write(val.Data)
- }
- continue
- }
- return
- case css.EndAtRuleGrammar, css.EndRulesetGrammar:
- c.w.Write(rightBracketBytes)
- semicolonQueued = false
- continue
- }
- if semicolonQueued {
- c.w.Write(semicolonBytes)
- semicolonQueued = false
- }
- switch gt {
- case css.AtRuleGrammar:
- c.w.Write(data)
- values := c.p.Values()
- if ToHash(data[1:]) == Import && len(values) == 2 && values[1].TokenType == css.URLToken && 4 < len(values[1].Data) && values[1].Data[len(values[1].Data)-1] == ')' {
- url := values[1].Data
- if url[4] != '"' && url[4] != '\'' {
- a := 4
- for parse.IsWhitespace(url[a]) || parse.IsNewline(url[a]) {
- a++
- }
- b := len(url) - 2
- for a < b && (parse.IsWhitespace(url[b]) || parse.IsNewline(url[b])) {
- b--
- }
- if a == b {
- url = url[:2]
- } else {
- url = url[a-1 : b+2]
- }
- url[0] = '"'
- url[len(url)-1] = '"'
- } else {
- url = url[4 : len(url)-1]
- }
- values[1].Data = url
- }
- for _, val := range values {
- c.w.Write(val.Data)
- }
- semicolonQueued = true
- case css.BeginAtRuleGrammar:
- c.w.Write(data)
- for _, val := range c.p.Values() {
- c.w.Write(val.Data)
- }
- c.w.Write(leftBracketBytes)
- case css.QualifiedRuleGrammar:
- c.minifySelectors(data, c.p.Values())
- c.w.Write(commaBytes)
- case css.BeginRulesetGrammar:
- c.minifySelectors(data, c.p.Values())
- c.w.Write(leftBracketBytes)
- case css.DeclarationGrammar:
- c.minifyDeclaration(data, c.p.Values())
- semicolonQueued = true
- case css.CustomPropertyGrammar:
- c.w.Write(data)
- c.w.Write(colonBytes)
- value := parse.TrimWhitespace(c.p.Values()[0].Data)
- if len(c.p.Values()[0].Data) != 0 && len(value) == 0 {
- value = spaceBytes
- }
- c.w.Write(value)
- semicolonQueued = true
- case css.CommentGrammar:
- if len(data) > 5 && data[1] == '*' && data[2] == '!' {
- c.w.Write(data[:3])
- comment := parse.TrimWhitespace(parse.ReplaceMultipleWhitespace(data[3 : len(data)-2]))
- c.w.Write(comment)
- c.w.Write(data[len(data)-2:])
- }
- default:
- c.w.Write(data)
- }
- }
- }
- func (c *cssMinifier) minifySelectors(property []byte, values []css.Token) {
- inAttr := false
- isClass := false
- for _, val := range c.p.Values() {
- if !inAttr {
- if val.TokenType == css.IdentToken {
- if !isClass {
- parse.ToLower(val.Data)
- }
- isClass = false
- } else if val.TokenType == css.DelimToken && val.Data[0] == '.' {
- isClass = true
- } else if val.TokenType == css.LeftBracketToken {
- inAttr = true
- }
- } else {
- if val.TokenType == css.StringToken && len(val.Data) > 2 {
- s := val.Data[1 : len(val.Data)-1]
- if css.IsIdent(s) {
- c.w.Write(s)
- continue
- }
- } else if val.TokenType == css.RightBracketToken {
- inAttr = false
- } else if val.TokenType == css.IdentToken && len(val.Data) == 1 && (val.Data[0] == 'i' || val.Data[0] == 'I') {
- c.w.Write(spaceBytes)
- }
- }
- c.w.Write(val.Data)
- }
- }
- func (c *cssMinifier) parseFunction(values []css.Token) ([]Token, int) {
- i := 1
- level := 0
- args := []Token{}
- for ; i < len(values); i++ {
- tt := values[i].TokenType
- data := values[i].Data
- if tt == css.LeftParenthesisToken {
- level++
- } else if tt == css.RightParenthesisToken {
- if level == 0 {
- i++
- break
- }
- level--
- }
- if tt == css.FunctionToken {
- subArgs, di := c.parseFunction(values[i:])
- h := ToHash(parse.ToLower(parse.Copy(data[:len(data)-1]))) // TODO: use ToHashFold
- args = append(args, Token{tt, data, subArgs, h, 0})
- i += di - 1
- } else {
- var h Hash
- if tt == css.IdentToken {
- h = ToHash(parse.ToLower(parse.Copy(data))) // TODO: use ToHashFold
- }
- args = append(args, Token{tt, data, nil, 0, h})
- }
- }
- return args, i
- }
- func (c *cssMinifier) parseDeclaration(values []css.Token) []Token {
- // Check if this is a simple list of values separated by whitespace or commas, otherwise we'll not be processing
- prevSep := true
- tokens := c.tokenBuffer[:0]
- for i := 0; i < len(values); i++ {
- tt := values[i].TokenType
- data := values[i].Data
- if tt == css.LeftParenthesisToken || tt == css.LeftBraceToken || tt == css.LeftBracketToken ||
- tt == css.RightParenthesisToken || tt == css.RightBraceToken || tt == css.RightBracketToken {
- return nil
- }
- if !prevSep && tt != css.WhitespaceToken && tt != css.CommaToken && (tt != css.DelimToken || values[i].Data[0] != '/') {
- return nil
- }
- if tt == css.WhitespaceToken || tt == css.CommaToken || tt == css.DelimToken && values[i].Data[0] == '/' {
- if tt != css.WhitespaceToken {
- tokens = append(tokens, Token{tt, data, nil, 0, 0})
- }
- prevSep = true
- } else if tt == css.FunctionToken {
- args, di := c.parseFunction(values[i:])
- h := ToHash(parse.ToLower(parse.Copy(data[:len(data)-1]))) // TODO: use ToHashFold
- tokens = append(tokens, Token{tt, data, args, h, 0})
- prevSep = true
- i += di - 1
- } else {
- var h Hash
- if tt == css.IdentToken {
- h = ToHash(parse.ToLower(parse.Copy(data))) // TODO: use ToHashFold
- }
- tokens = append(tokens, Token{tt, data, nil, 0, h})
- prevSep = tt == css.URLToken
- }
- }
- c.tokenBuffer = tokens // update buffer size for memory reuse
- return tokens
- }
- func (c *cssMinifier) minifyDeclaration(property []byte, components []css.Token) {
- c.w.Write(property)
- c.w.Write(colonBytes)
- if len(components) == 0 {
- return
- }
- // Strip !important from the component list, this will be added later separately
- important := false
- if len(components) > 2 && components[len(components)-2].TokenType == css.DelimToken && components[len(components)-2].Data[0] == '!' && ToHash(components[len(components)-1].Data) == Important {
- components = components[:len(components)-2]
- important = true
- }
- prop := ToHash(property)
- values := c.parseDeclaration(components)
- // Do not process complex values (eg. containing blocks or is not alternated between whitespace/commas and flat values
- if values == nil {
- if prop == Filter && len(components) == 11 {
- if bytes.Equal(components[0].Data, []byte("progid")) &&
- components[1].TokenType == css.ColonToken &&
- bytes.Equal(components[2].Data, []byte("DXImageTransform")) &&
- components[3].Data[0] == '.' &&
- bytes.Equal(components[4].Data, []byte("Microsoft")) &&
- components[5].Data[0] == '.' &&
- bytes.Equal(components[6].Data, []byte("Alpha(")) &&
- bytes.Equal(parse.ToLower(components[7].Data), []byte("opacity")) &&
- components[8].Data[0] == '=' &&
- components[10].Data[0] == ')' {
- components = components[6:]
- components[0].Data = []byte("alpha(")
- }
- }
- for _, component := range components {
- c.w.Write(component.Data)
- }
- if important {
- c.w.Write(importantBytes)
- }
- return
- }
- values = c.minifyTokens(prop, 0, values)
- if len(values) > 0 {
- values = c.minifyProperty(prop, values)
- }
- c.writeDeclaration(values, important)
- }
- func (c *cssMinifier) writeFunction(args []Token) {
- for _, arg := range args {
- c.w.Write(arg.Data)
- if arg.TokenType == css.FunctionToken {
- c.writeFunction(arg.Args)
- c.w.Write(rightParenBytes)
- }
- }
- }
- func (c *cssMinifier) writeDeclaration(values []Token, important bool) {
- prevSep := true
- for _, value := range values {
- if !prevSep && value.TokenType != css.CommaToken && (value.TokenType != css.DelimToken || value.Data[0] != '/') {
- c.w.Write(spaceBytes)
- }
- c.w.Write(value.Data)
- if value.TokenType == css.FunctionToken {
- c.writeFunction(value.Args)
- c.w.Write(rightParenBytes)
- }
- if value.TokenType == css.CommaToken || value.TokenType == css.DelimToken && value.Data[0] == '/' || value.TokenType == css.FunctionToken || value.TokenType == css.URLToken {
- prevSep = true
- } else {
- prevSep = false
- }
- }
- if important {
- c.w.Write(importantBytes)
- }
- }
- func (c *cssMinifier) minifyTokens(prop Hash, fun Hash, values []Token) []Token {
- if 100 < c.tokensLevel+1 {
- return values
- }
- c.tokensLevel++
- for i, value := range values {
- tt := value.TokenType
- switch tt {
- case css.NumberToken:
- if prop == Z_Index || prop == Counter_Increment || prop == Counter_Reset || prop == Orphans || prop == Widows {
- break // integers
- }
- if c.o.KeepCSS2 {
- values[i].Data = minify.Decimal(values[i].Data, c.o.Precision) // don't use exponents
- } else {
- values[i].Data = minify.Number(values[i].Data, c.o.Precision)
- }
- case css.PercentageToken:
- n := len(values[i].Data) - 1
- if c.o.KeepCSS2 {
- values[i].Data = minify.Decimal(values[i].Data[:n], c.o.Precision) // don't use exponents
- } else {
- values[i].Data = minify.Number(values[i].Data[:n], c.o.Precision)
- }
- values[i].Data = append(values[i].Data, '%')
- case css.DimensionToken:
- var dim []byte
- values[i], dim = c.minifyDimension(values[i])
- if 1 < len(values[i].Data) && values[i].Data[0] == '0' && optionalZeroDimension[string(dim)] && prop != Flex && fun == 0 {
- // cut dimension for zero value, TODO: don't hardcode check for Flex and remove the dimension in minifyDimension
- values[i].Data = values[i].Data[:1]
- }
- case css.StringToken:
- values[i].Data = removeMarkupNewlines(values[i].Data)
- case css.URLToken:
- if 10 < len(values[i].Data) {
- uri := parse.TrimWhitespace(values[i].Data[4 : len(values[i].Data)-1])
- delim := byte('"')
- if 1 < len(uri) && (uri[0] == '\'' || uri[0] == '"') {
- delim = uri[0]
- uri = removeMarkupNewlines(uri)
- uri = uri[1 : len(uri)-1]
- }
- if 4 < len(uri) && parse.EqualFold(uri[:5], dataSchemeBytes) {
- uri = minify.DataURI(c.m, uri)
- }
- if css.IsURLUnquoted(uri) {
- values[i].Data = append(append(urlBytes, uri...), ')')
- } else {
- values[i].Data = append(append(append(urlBytes, delim), uri...), delim, ')')
- }
- }
- case css.FunctionToken:
- values[i].Args = c.minifyTokens(prop, values[i].Fun, values[i].Args)
- fun := values[i].Fun
- args := values[i].Args
- if fun == Rgb || fun == Rgba || fun == Hsl || fun == Hsla {
- valid := true
- vals := []float64{}
- for i, arg := range args {
- numeric := arg.TokenType == css.NumberToken || arg.TokenType == css.PercentageToken
- separator := arg.TokenType == css.CommaToken || i != 5 && arg.TokenType == css.WhitespaceToken || i == 5 && arg.TokenType == css.DelimToken && arg.Data[0] == '/'
- if i%2 == 0 && !numeric || i%2 == 1 && !separator {
- valid = false
- break
- } else if numeric {
- var d float64
- if arg.TokenType == css.PercentageToken {
- var err error
- d, err = strconv.ParseFloat(string(arg.Data[:len(arg.Data)-1]), 32) // can overflow
- if err != nil {
- valid = false
- break
- }
- d /= 100.0
- if d < minify.Epsilon {
- d = 0.0
- } else if 1.0-minify.Epsilon < d {
- d = 1.0
- }
- } else {
- var err error
- d, err = strconv.ParseFloat(string(arg.Data), 32) // can overflow
- if err != nil {
- valid = false
- break
- }
- }
- vals = append(vals, d)
- }
- }
- if !valid {
- break
- }
- a := 1.0
- if len(vals) == 4 {
- if vals[0] < minify.Epsilon && vals[1] < minify.Epsilon && vals[2] < minify.Epsilon && vals[3] < minify.Epsilon {
- values[i] = Token{css.IdentToken, transparentBytes, nil, 0, Transparent}
- break
- } else if 1.0-minify.Epsilon < vals[3] {
- vals = vals[:3]
- values[i].Args = values[i].Args[:len(values[i].Args)-2]
- if fun == Rgba || fun == Hsla {
- values[i].Data = values[i].Data[:len(values[i].Data)-1]
- values[i].Data[len(values[i].Data)-1] = '('
- }
- } else {
- a = vals[3]
- }
- }
- if a == 1.0 && (len(vals) == 3 || len(vals) == 4) { // only minify color if fully opaque
- if fun == Rgb || fun == Rgba {
- for j := 0; j < 3; j++ {
- if args[j*2].TokenType == css.NumberToken {
- vals[j] /= 255.0
- if vals[j] < minify.Epsilon {
- vals[j] = 0.0
- } else if 1.0-minify.Epsilon < vals[j] {
- vals[j] = 1.0
- }
- }
- }
- values[i] = rgbToToken(vals[0], vals[1], vals[2])
- break
- } else if fun == Hsl || fun == Hsla && args[0].TokenType == css.NumberToken && args[2].TokenType == css.PercentageToken && args[4].TokenType == css.PercentageToken {
- vals[0] /= 360.0
- _, vals[0] = math.Modf(vals[0])
- if vals[0] < 0.0 {
- vals[0] = 1.0 + vals[0]
- }
- r, g, b := css.HSL2RGB(vals[0], vals[1], vals[2])
- values[i] = rgbToToken(r, g, b)
- break
- }
- } else if len(vals) == 4 {
- args[6] = minifyNumberPercentage(args[6])
- }
- if 3 <= len(vals) && (fun == Rgb || fun == Rgba) {
- // 0%, 20%, 40%, 60%, 80% and 100% can be represented exactly as, 51, 102, 153, 204, and 255 respectively
- removePercentage := true
- for j := 0; j < 3; j++ {
- if args[j*2].TokenType != css.PercentageToken || 2.0*minify.Epsilon <= math.Mod(vals[j]+minify.Epsilon, 0.2) {
- removePercentage = false
- break
- }
- }
- if removePercentage {
- for j := 0; j < 3; j++ {
- args[j*2].TokenType = css.NumberToken
- if vals[j] < minify.Epsilon {
- args[j*2].Data = zeroBytes
- } else if math.Abs(vals[j]-0.2) < minify.Epsilon {
- args[j*2].Data = []byte("51")
- } else if math.Abs(vals[j]-0.4) < minify.Epsilon {
- args[j*2].Data = []byte("102")
- } else if math.Abs(vals[j]-0.6) < minify.Epsilon {
- args[j*2].Data = []byte("153")
- } else if math.Abs(vals[j]-0.8) < minify.Epsilon {
- args[j*2].Data = []byte("204")
- } else if math.Abs(vals[j]-1.0) < minify.Epsilon {
- args[j*2].Data = []byte("255")
- }
- }
- }
- }
- }
- }
- }
- c.tokensLevel--
- return values
- }
- func (c *cssMinifier) minifyProperty(prop Hash, values []Token) []Token {
- // limit maximum to prevent slow recursions (e.g. for background's append)
- if 100 < len(values) {
- return values
- }
- switch prop {
- case Font:
- if len(values) > 1 { // must contain atleast font-size and font-family
- // the font-families are separated by commas and are at the end of font
- // get index for last token before font family names
- i := len(values) - 1
- for j, value := range values[2:] {
- if value.TokenType == css.CommaToken {
- i = 2 + j - 1 // identifier before first comma is a font-family
- break
- }
- }
- i--
- // advance i while still at font-families when they contain spaces but no quotes
- for ; i > 0; i-- { // i cannot be 0, font-family must be prepended by font-size
- if values[i-1].TokenType == css.DelimToken && values[i-1].Data[0] == '/' {
- break
- } else if values[i].TokenType != css.IdentToken && values[i].TokenType != css.StringToken {
- break
- } else if h := values[i].Ident; h == Xx_Small || h == X_Small || h == Small || h == Medium || h == Large || h == X_Large || h == Xx_Large || h == Smaller || h == Larger || h == Inherit || h == Initial || h == Unset {
- // inherit, initial and unset are followed by an IdentToken/StringToken, so must be for font-size
- break
- }
- }
- // font-family minified in place
- values = append(values[:i+1], c.minifyProperty(Font_Family, values[i+1:])...)
- // fix for IE9, IE10, IE11: font name starting with `-` is not recognized
- if values[i+1].Data[0] == '-' {
- v := make([]byte, len(values[i+1].Data)+2)
- v[0] = '\''
- copy(v[1:], values[i+1].Data)
- v[len(v)-1] = '\''
- values[i+1].Data = v
- }
- if i > 0 {
- // line-height
- if i > 1 && values[i-1].TokenType == css.DelimToken && values[i-1].Data[0] == '/' {
- if values[i].Ident == Normal {
- values = append(values[:i-1], values[i+1:]...)
- }
- i -= 2
- }
- // font-size
- i--
- for ; i > -1; i-- {
- if values[i].Ident == Normal {
- values = append(values[:i], values[i+1:]...)
- } else if values[i].Ident == Bold {
- values[i].TokenType = css.NumberToken
- values[i].Data = n700Bytes
- } else if values[i].TokenType == css.NumberToken && bytes.Equal(values[i].Data, n400Bytes) {
- values = append(values[:i], values[i+1:]...)
- }
- }
- }
- }
- case Font_Family:
- for i, value := range values {
- if value.TokenType == css.StringToken && 2 < len(value.Data) {
- unquote := true
- parse.ToLower(value.Data)
- s := value.Data[1 : len(value.Data)-1]
- if 0 < len(s) {
- for _, split := range bytes.Split(s, spaceBytes) {
- // if len is zero, it contains two consecutive spaces
- if len(split) == 0 || !css.IsIdent(split) {
- unquote = false
- break
- }
- }
- }
- if unquote {
- values[i].Data = s
- }
- }
- }
- case Font_Weight:
- if values[0].Ident == Normal {
- values[0].TokenType = css.NumberToken
- values[0].Data = n400Bytes
- } else if values[0].Ident == Bold {
- values[0].TokenType = css.NumberToken
- values[0].Data = n700Bytes
- }
- case Url:
- for i := 0; i < len(values); i++ {
- if values[i].TokenType == css.FunctionToken && len(values[i].Args) == 1 {
- fun := values[i].Fun
- data := values[i].Args[0].Data
- if fun == Local && (data[0] == '\'' || data[0] == '"') {
- if css.IsURLUnquoted(data[1 : len(data)-1]) {
- data = data[1 : len(data)-1]
- }
- values[i].Args[0].Data = data
- }
- }
- }
- case Margin, Padding, Border_Width:
- switch len(values) {
- case 2:
- if values[0].Equal(values[1]) {
- values = values[:1]
- }
- case 3:
- if values[0].Equal(values[1]) && values[0].Equal(values[2]) {
- values = values[:1]
- } else if values[0].Equal(values[2]) {
- values = values[:2]
- }
- case 4:
- if values[0].Equal(values[1]) && values[0].Equal(values[2]) && values[0].Equal(values[3]) {
- values = values[:1]
- } else if values[0].Equal(values[2]) && values[1].Equal(values[3]) {
- values = values[:2]
- } else if values[1].Equal(values[3]) {
- values = values[:3]
- }
- }
- case Border, Border_Bottom, Border_Left, Border_Right, Border_Top:
- for i := 0; i < len(values); i++ {
- if values[i].Ident == None || values[i].Ident == Currentcolor || values[i].Ident == Medium {
- values = append(values[:i], values[i+1:]...)
- i--
- } else {
- values[i] = minifyColor(values[i])
- }
- }
- if len(values) == 0 {
- values = []Token{{css.IdentToken, noneBytes, nil, 0, None}}
- }
- case Outline:
- for i := 0; i < len(values); i++ {
- if values[i].Ident == Invert || values[i].Ident == None || values[i].Ident == Medium {
- values = append(values[:i], values[i+1:]...)
- i--
- } else {
- values[i] = minifyColor(values[i])
- }
- }
- if len(values) == 0 {
- values = []Token{{css.IdentToken, noneBytes, nil, 0, None}}
- }
- case Background:
- start := 0
- for end := 0; end <= len(values); end++ { // loop over comma-separated lists
- if end != len(values) && values[end].TokenType != css.CommaToken {
- continue
- } else if start == end {
- start++
- continue
- }
- // minify background-size and lowercase all identifiers
- for i := start; i < end; i++ {
- if values[i].TokenType == css.DelimToken && values[i].Data[0] == '/' {
- // background-size consists of either [<length-percentage> | auto | cover | contain] or [<length-percentage> | auto]{2}
- // we can only minify the latter
- if i+1 < end && (values[i+1].TokenType == css.NumberToken || values[i+1].IsLengthPercentage() || values[i+1].Ident == Auto) {
- if i+2 < end && (values[i+2].TokenType == css.NumberToken || values[i+2].IsLengthPercentage() || values[i+2].Ident == Auto) {
- sizeValues := c.minifyProperty(Background_Size, values[i+1:i+3])
- if len(sizeValues) == 1 && sizeValues[0].Ident == Auto {
- // remove background-size if it is '/ auto' after minifying the property
- values = append(values[:i], values[i+3:]...)
- end -= 3
- i--
- } else {
- values = append(values[:i+1], append(sizeValues, values[i+3:]...)...)
- end -= 2 - len(sizeValues)
- i += len(sizeValues) - 1
- }
- } else if values[i+1].Ident == Auto {
- // remove background-size if it is '/ auto'
- values = append(values[:i], values[i+2:]...)
- end -= 2
- i--
- }
- }
- }
- }
- // minify all other values
- iPaddingBox := -1 // position of background-origin that is padding-box
- for i := start; i < end; i++ {
- h := values[i].Ident
- values[i] = minifyColor(values[i])
- if values[i].TokenType == css.IdentToken {
- if i+1 < end && values[i+1].TokenType == css.IdentToken && (h == Space || h == Round || h == Repeat || h == No_Repeat) {
- if h2 := values[i+1].Ident; h2 == Space || h2 == Round || h2 == Repeat || h2 == No_Repeat {
- repeatValues := c.minifyProperty(Background_Repeat, values[i:i+2])
- if len(repeatValues) == 1 && repeatValues[0].Ident == Repeat {
- values = append(values[:i], values[i+2:]...)
- end -= 2
- i--
- } else {
- values = append(values[:i], append(repeatValues, values[i+2:]...)...)
- end -= 2 - len(repeatValues)
- i += len(repeatValues) - 1
- }
- continue
- }
- } else if h == None || h == Scroll || h == Transparent {
- values = append(values[:i], values[i+1:]...)
- end--
- i--
- continue
- } else if h == Border_Box || h == Padding_Box {
- if iPaddingBox == -1 && h == Padding_Box { // background-origin
- iPaddingBox = i
- } else if iPaddingBox != -1 && h == Border_Box { // background-clip
- values = append(values[:i], values[i+1:]...)
- values = append(values[:iPaddingBox], values[iPaddingBox+1:]...)
- end -= 2
- i -= 2
- }
- continue
- }
- } else if values[i].TokenType == css.HashToken && bytes.Equal(values[i].Data, blackBytes) {
- values = append(values[:i], values[i+1:]...)
- end--
- i--
- continue
- } else if values[i].TokenType == css.FunctionToken && bytes.Equal(values[i].Data, varBytes) {
- continue
- }
- // further minify background-position and background-size combination
- if values[i].TokenType == css.NumberToken || values[i].IsLengthPercentage() || h == Left || h == Right || h == Top || h == Bottom || h == Center {
- j := i + 1
- for ; j < len(values); j++ {
- if h := values[j].Ident; h == Left || h == Right || h == Top || h == Bottom || h == Center {
- continue
- } else if values[j].TokenType == css.NumberToken || values[j].IsLengthPercentage() {
- continue
- }
- break
- }
- positionValues := c.minifyProperty(Background_Position, values[i:j])
- hasSize := j < len(values) && values[j].TokenType == css.DelimToken && values[j].Data[0] == '/'
- if !hasSize && len(positionValues) == 2 && positionValues[0].IsZero() && positionValues[1].IsZero() {
- if end-start == 2 {
- values[i] = Token{css.NumberToken, zeroBytes, nil, 0, 0}
- values[i+1] = Token{css.NumberToken, zeroBytes, nil, 0, 0}
- i++
- } else {
- values = append(values[:i], values[j:]...)
- end -= j - i
- i--
- }
- } else {
- if len(positionValues) == j-i {
- for k, positionValue := range positionValues {
- values[i+k] = positionValue
- }
- } else {
- values = append(values[:i], append(positionValues, values[j:]...)...)
- end -= j - i - len(positionValues)
- }
- i += len(positionValues) - 1
- }
- }
- }
- if end-start == 0 {
- values = append(values[:start], append([]Token{{css.NumberToken, zeroBytes, nil, 0, 0}, {css.NumberToken, zeroBytes, nil, 0, 0}}, values[end:]...)...)
- end += 2
- }
- start = end + 1
- }
- case Background_Size:
- start := 0
- for end := 0; end <= len(values); end++ { // loop over comma-separated lists
- if end != len(values) && values[end].TokenType != css.CommaToken {
- continue
- } else if start == end {
- start++
- continue
- }
- if end-start == 2 && values[start+1].Ident == Auto {
- values = append(values[:start+1], values[start+2:]...)
- end--
- }
- start = end + 1
- }
- case Background_Repeat:
- start := 0
- for end := 0; end <= len(values); end++ { // loop over comma-separated lists
- if end != len(values) && values[end].TokenType != css.CommaToken {
- continue
- } else if start == end {
- start++
- continue
- }
- if end-start == 2 && values[start].TokenType == css.IdentToken && values[start+1].TokenType == css.IdentToken {
- if values[start].Ident == values[start+1].Ident {
- values = append(values[:start+1], values[start+2:]...)
- end--
- } else if values[start].Ident == Repeat && values[start+1].Ident == No_Repeat {
- values[start].Data = repeatXBytes
- values[start].Ident = Repeat_X
- values = append(values[:start+1], values[start+2:]...)
- end--
- } else if values[start].Ident == No_Repeat && values[start+1].Ident == Repeat {
- values[start].Data = repeatYBytes
- values[start].Ident = Repeat_Y
- values = append(values[:start+1], values[start+2:]...)
- end--
- }
- }
- start = end + 1
- }
- case Background_Position:
- start := 0
- for end := 0; end <= len(values); end++ { // loop over comma-separated lists
- if end != len(values) && values[end].TokenType != css.CommaToken {
- continue
- } else if start == end {
- start++
- continue
- }
- if end-start == 3 || end-start == 4 {
- // remove zero offsets
- for _, i := range []int{end - start - 1, start + 1} {
- if 2 < end-start && values[i].IsZero() {
- values = append(values[:i], values[i+1:]...)
- end--
- }
- }
- j := start + 1 // position of second set of horizontal/vertical values
- if 2 < end-start && values[start+2].TokenType == css.IdentToken {
- j = start + 2
- }
- b := make([]byte, 0, 4)
- offsets := make([]Token, 2)
- for _, i := range []int{j, start} {
- if i+1 < end && i+1 != j {
- if values[i+1].TokenType == css.PercentageToken {
- // change right or bottom with percentage offset to left or top respectively
- if values[i].Ident == Right || values[i].Ident == Bottom {
- n, _ := strconvParse.ParseInt(values[i+1].Data[:len(values[i+1].Data)-1])
- b = strconv.AppendInt(b[:0], 100-n, 10)
- b = append(b, '%')
- values[i+1].Data = b
- if values[i].Ident == Right {
- values[i].Data = leftBytes
- values[i].Ident = Left
- } else {
- values[i].Data = topBytes
- values[i].Ident = Top
- }
- }
- }
- if values[i].Ident == Left {
- offsets[0] = values[i+1]
- } else if values[i].Ident == Top {
- offsets[1] = values[i+1]
- }
- } else if values[i].Ident == Left {
- offsets[0] = Token{css.NumberToken, zeroBytes, nil, 0, 0}
- } else if values[i].Ident == Top {
- offsets[1] = Token{css.NumberToken, zeroBytes, nil, 0, 0}
- } else if values[i].Ident == Right {
- offsets[0] = Token{css.PercentageToken, n100pBytes, nil, 0, 0}
- values[i].Ident = Left
- } else if values[i].Ident == Bottom {
- offsets[1] = Token{css.PercentageToken, n100pBytes, nil, 0, 0}
- values[i].Ident = Top
- }
- }
- if values[start].Ident == Center || values[j].Ident == Center {
- if values[start].Ident == Left || values[j].Ident == Left {
- offsets = offsets[:1]
- } else if values[start].Ident == Top || values[j].Ident == Top {
- offsets[0] = Token{css.NumberToken, n50pBytes, nil, 0, 0}
- }
- }
- if offsets[0].Data != nil && (len(offsets) == 1 || offsets[1].Data != nil) {
- values = append(append(values[:start], offsets...), values[end:]...)
- end -= end - start - len(offsets)
- }
- }
- // removing zero offsets in the previous loop might make it eligible for the next loop
- if end-start == 1 || end-start == 2 {
- if end-start == 1 && (values[start].Ident == Top || values[start].Ident == Bottom) {
- // we can't make this smaller, and converting to a number will break it
- // (https://github.com/tdewolff/minify/issues/221#issuecomment-415419918)
- break
- }
- if end-start == 2 && (values[start].Ident == Top || values[start].Ident == Bottom || values[start+1].Ident == Left || values[start+1].Ident == Right) {
- // if it's a vertical position keyword, swap it with the next element
- // since otherwise converted number positions won't be valid anymore
- // (https://github.com/tdewolff/minify/issues/221#issue-353067229)
- values[start], values[start+1] = values[start+1], values[start]
- }
- // transform keywords to lengths|percentages
- for i := start; i < end; i++ {
- if values[i].TokenType == css.IdentToken {
- if values[i].Ident == Left || values[i].Ident == Top {
- values[i].TokenType = css.NumberToken
- values[i].Data = zeroBytes
- values[i].Ident = 0
- } else if values[i].Ident == Right || values[i].Ident == Bottom {
- values[i].TokenType = css.PercentageToken
- values[i].Data = n100pBytes
- values[i].Ident = 0
- } else if values[i].Ident == Center {
- if i == start {
- values[i].TokenType = css.PercentageToken
- values[i].Data = n50pBytes
- values[i].Ident = 0
- } else {
- values = append(values[:start+1], values[start+2:]...)
- end--
- }
- }
- } else if i == start+1 && values[i].TokenType == css.PercentageToken && bytes.Equal(values[i].Data, n50pBytes) {
- values = append(values[:start+1], values[start+2:]...)
- end--
- } else if values[i].TokenType == css.PercentageToken && values[i].Data[0] == '0' {
- values[i].TokenType = css.NumberToken
- values[i].Data = zeroBytes
- values[i].Ident = 0
- }
- }
- }
- start = end + 1
- }
- case Box_Shadow:
- start := 0
- for end := 0; end <= len(values); end++ { // loop over comma-separated lists
- if end != len(values) && values[end].TokenType != css.CommaToken {
- continue
- } else if start == end {
- start++
- continue
- }
- if end-start == 1 && values[start].Ident == Initial {
- values[start].Ident = None
- values[start].Data = noneBytes
- } else {
- numbers := []int{}
- for i := start; i < end; i++ {
- if values[i].IsLength() {
- numbers = append(numbers, i)
- }
- }
- if len(numbers) == 4 && values[numbers[3]].IsZero() {
- values = append(values[:numbers[3]], values[numbers[3]+1:]...)
- numbers = numbers[:3]
- end--
- }
- if len(numbers) == 3 && values[numbers[2]].IsZero() {
- values = append(values[:numbers[2]], values[numbers[2]+1:]...)
- end--
- }
- }
- start = end + 1
- }
- case Ms_Filter:
- alpha := []byte("progid:DXImageTransform.Microsoft.Alpha(Opacity=")
- if values[0].TokenType == css.StringToken && 2 < len(values[0].Data) && bytes.HasPrefix(values[0].Data[1:len(values[0].Data)-1], alpha) {
- values[0].Data = append(append([]byte{values[0].Data[0]}, []byte("alpha(opacity=")...), values[0].Data[1+len(alpha):]...)
- }
- case Color:
- values[0] = minifyColor(values[0])
- case Background_Color:
- values[0] = minifyColor(values[0])
- if !c.o.KeepCSS2 {
- if values[0].Ident == Transparent {
- values[0].Data = initialBytes
- values[0].Ident = Initial
- }
- }
- case Border_Color:
- sameValues := true
- for i := range values {
- if values[i].Ident == Currentcolor {
- values[i].Data = initialBytes
- values[i].Ident = Initial
- } else {
- values[i] = minifyColor(values[i])
- }
- if 0 < i && sameValues && !bytes.Equal(values[0].Data, values[i].Data) {
- sameValues = false
- }
- }
- if sameValues {
- values = values[:1]
- }
- case Border_Left_Color, Border_Right_Color, Border_Top_Color, Border_Bottom_Color, Text_Decoration_Color, Text_Emphasis_Color:
- if values[0].Ident == Currentcolor {
- values[0].Data = initialBytes
- values[0].Ident = Initial
- } else {
- values[0] = minifyColor(values[0])
- }
- case Caret_Color, Outline_Color, Fill, Stroke:
- values[0] = minifyColor(values[0])
- case Column_Rule:
- for i := 0; i < len(values); i++ {
- if values[i].Ident == Currentcolor || values[i].Ident == None || values[i].Ident == Medium {
- values = append(values[:i], values[i+1:]...)
- i--
- } else {
- values[i] = minifyColor(values[i])
- }
- }
- if len(values) == 0 {
- values = []Token{{css.IdentToken, noneBytes, nil, 0, None}}
- }
- case Text_Shadow:
- // TODO: minify better (can be comma separated list)
- for i := 0; i < len(values); i++ {
- values[i] = minifyColor(values[i])
- }
- case Text_Decoration:
- for i := 0; i < len(values); i++ {
- if values[i].Ident == Currentcolor || values[i].Ident == None || values[i].Ident == Solid {
- values = append(values[:i], values[i+1:]...)
- i--
- } else {
- values[i] = minifyColor(values[i])
- }
- }
- if len(values) == 0 {
- values = []Token{{css.IdentToken, noneBytes, nil, 0, None}}
- }
- case Text_Emphasis:
- for i := 0; i < len(values); i++ {
- if values[i].Ident == Currentcolor || values[i].Ident == None {
- values = append(values[:i], values[i+1:]...)
- i--
- } else {
- values[i] = minifyColor(values[i])
- }
- }
- if len(values) == 0 {
- values = []Token{{css.IdentToken, noneBytes, nil, 0, None}}
- }
- case Flex:
- if len(values) == 2 && values[0].TokenType == css.NumberToken {
- if values[1].TokenType != css.NumberToken && values[1].IsZero() {
- values = values[:1] // remove <flex-basis> if it is zero
- }
- } else if len(values) == 3 && values[0].TokenType == css.NumberToken && values[1].TokenType == css.NumberToken {
- if len(values[0].Data) == 1 && len(values[1].Data) == 1 {
- if values[2].Ident == Auto {
- if values[0].Data[0] == '0' && values[1].Data[0] == '1' {
- values = values[:1]
- values[0].TokenType = css.IdentToken
- values[0].Data = initialBytes
- values[0].Ident = Initial
- } else if values[0].Data[0] == '1' && values[1].Data[0] == '1' {
- values = values[:1]
- values[0].TokenType = css.IdentToken
- values[0].Data = autoBytes
- values[0].Ident = Auto
- } else if values[0].Data[0] == '0' && values[1].Data[0] == '0' {
- values = values[:1]
- values[0].TokenType = css.IdentToken
- values[0].Data = noneBytes
- values[0].Ident = None
- }
- } else if values[1].Data[0] == '1' && values[2].IsZero() {
- values = values[:1] // remove <flex-shrink> and <flex-basis> if they are 1 and 0 respectively
- } else if values[2].IsZero() {
- values = values[:2] // remove auto to write 2-value syntax of <flex-grow> <flex-shrink>
- } else {
- values[2] = minifyLengthPercentage(values[2])
- }
- }
- }
- case Flex_Basis:
- if values[0].Ident == Initial {
- values[0].Data = autoBytes
- values[0].Ident = Auto
- } else {
- values[0] = minifyLengthPercentage(values[0])
- }
- case Order, Flex_Grow:
- if values[0].Ident == Initial {
- values[0].TokenType = css.NumberToken
- values[0].Data = zeroBytes
- values[0].Ident = 0
- }
- case Flex_Shrink:
- if values[0].Ident == Initial {
- values[0].TokenType = css.NumberToken
- values[0].Data = oneBytes
- values[0].Ident = 0
- }
- case Unicode_Range:
- ranges := [][2]int{}
- for _, value := range values {
- if value.TokenType == css.CommaToken {
- continue
- } else if value.TokenType != css.UnicodeRangeToken {
- return values
- }
- i := 2
- iWildcard := 0
- start := 0
- for i < len(value.Data) && value.Data[i] != '-' {
- start *= 16
- if '0' <= value.Data[i] && value.Data[i] <= '9' {
- start += int(value.Data[i] - '0')
- } else if 'a' <= value.Data[i]|32 && value.Data[i]|32 <= 'f' {
- start += int(value.Data[i]|32-'a') + 10
- } else if iWildcard == 0 && value.Data[i] == '?' {
- iWildcard = i
- }
- i++
- }
- end := start
- if iWildcard != 0 {
- end = start + int(math.Pow(16.0, float64(len(value.Data)-iWildcard))) - 1
- } else if i < len(value.Data) && value.Data[i] == '-' {
- i++
- end = 0
- for i < len(value.Data) {
- end *= 16
- if '0' <= value.Data[i] && value.Data[i] <= '9' {
- end += int(value.Data[i] - '0')
- } else if 'a' <= value.Data[i]|32 && value.Data[i]|32 <= 'f' {
- end += int(value.Data[i]|32-'a') + 10
- }
- i++
- }
- if end <= start {
- end = start
- }
- }
- ranges = append(ranges, [2]int{start, end})
- }
- // sort and remove overlapping ranges
- sort.Slice(ranges, func(i, j int) bool { return ranges[i][0] < ranges[j][0] })
- for i := 0; i < len(ranges)-1; i++ {
- if ranges[i+1][1] <= ranges[i][1] {
- // next range is fully contained in the current range
- ranges = append(ranges[:i+1], ranges[i+2:]...)
- } else if ranges[i+1][0] <= ranges[i][1]+1 {
- // next range is partially covering the current range
- ranges[i][1] = ranges[i+1][1]
- ranges = append(ranges[:i+1], ranges[i+2:]...)
- }
- }
- values = values[:0]
- for i, ran := range ranges {
- if i != 0 {
- values = append(values, Token{css.CommaToken, commaBytes, nil, 0, None})
- }
- if ran[0] == ran[1] {
- urange := []byte(fmt.Sprintf("U+%X", ran[0]))
- values = append(values, Token{css.UnicodeRangeToken, urange, nil, 0, None})
- } else if ran[0] == 0 && ran[1] == 0x10FFFF {
- values = append(values, Token{css.IdentToken, initialBytes, nil, 0, None})
- } else {
- k := 0
- for k < 6 && (ran[0]>>(k*4))&0xF == 0 && (ran[1]>>(k*4))&0xF == 0xF {
- k++
- }
- wildcards := k
- for k < 6 {
- if (ran[0]>>(k*4))&0xF != (ran[1]>>(k*4))&0xF {
- wildcards = 0
- break
- }
- k++
- }
- var urange []byte
- if wildcards != 0 {
- if ran[0]>>(wildcards*4) == 0 {
- urange = []byte(fmt.Sprintf("U+%s", strings.Repeat("?", wildcards)))
- } else {
- urange = []byte(fmt.Sprintf("U+%X%s", ran[0]>>(wildcards*4), strings.Repeat("?", wildcards)))
- }
- } else {
- urange = []byte(fmt.Sprintf("U+%X-%X", ran[0], ran[1]))
- }
- values = append(values, Token{css.UnicodeRangeToken, urange, nil, 0, None})
- }
- }
- }
- return values
- }
- func minifyColor(value Token) Token {
- data := value.Data
- if value.TokenType == css.IdentToken {
- if hexValue, ok := ShortenColorName[value.Ident]; ok {
- value.TokenType = css.HashToken
- value.Data = hexValue
- }
- } else if value.TokenType == css.HashToken {
- parse.ToLower(data[1:])
- if len(data) == 9 && data[7] == data[8] {
- if data[7] == 'f' {
- data = data[:7]
- } else if data[7] == '0' {
- data = blackBytes
- }
- }
- if ident, ok := ShortenColorHex[string(data)]; ok {
- value.TokenType = css.IdentToken
- data = ident
- } else if len(data) == 7 && data[1] == data[2] && data[3] == data[4] && data[5] == data[6] {
- value.TokenType = css.HashToken
- data[2] = data[3]
- data[3] = data[5]
- data = data[:4]
- } else if len(data) == 9 && data[1] == data[2] && data[3] == data[4] && data[5] == data[6] && data[7] == data[8] {
- // from working draft Color Module Level 4
- value.TokenType = css.HashToken
- data[2] = data[3]
- data[3] = data[5]
- data[4] = data[7]
- data = data[:5]
- }
- value.Data = data
- }
- return value
- }
- func minifyNumberPercentage(value Token) Token {
- // assumes input already minified
- if value.TokenType == css.PercentageToken && len(value.Data) == 3 && value.Data[len(value.Data)-2] == '0' {
- value.Data[1] = value.Data[0]
- value.Data[0] = '.'
- value.Data = value.Data[:2]
- value.TokenType = css.NumberToken
- } else if value.TokenType == css.NumberToken && 2 < len(value.Data) && value.Data[0] == '.' && value.Data[1] == '0' {
- if value.Data[2] == '0' {
- value.Data[0] = '.'
- copy(value.Data[1:], value.Data[3:])
- value.Data[len(value.Data)-2] = '%'
- value.Data = value.Data[:len(value.Data)-1]
- value.TokenType = css.PercentageToken
- } else if len(value.Data) == 3 {
- value.Data[0] = value.Data[2]
- value.Data[1] = '%'
- value.Data = value.Data[:2]
- value.TokenType = css.PercentageToken
- }
- }
- return value
- }
- func minifyLengthPercentage(value Token) Token {
- if value.TokenType != css.NumberToken && value.IsZero() {
- value.TokenType = css.NumberToken
- value.Data = value.Data[:1] // remove dimension for zero value
- }
- return value
- }
- func (c *cssMinifier) minifyDimension(value Token) (Token, []byte) {
- // TODO: add check for zero value
- var dim []byte
- if value.TokenType == css.DimensionToken {
- n := len(value.Data)
- for 0 < n {
- lower := 'a' <= value.Data[n-1] && value.Data[n-1] <= 'z'
- upper := 'A' <= value.Data[n-1] && value.Data[n-1] <= 'Z'
- if !lower && !upper {
- break
- } else if upper {
- value.Data[n-1] = value.Data[n-1] + ('a' - 'A')
- }
- n--
- }
- num := value.Data[:n]
- if c.o.KeepCSS2 {
- num = minify.Decimal(num, c.o.Precision) // don't use exponents
- } else {
- num = minify.Number(num, c.o.Precision)
- }
- dim = value.Data[n:]
- value.Data = append(num, dim...)
- }
- return value, dim
- // TODO: optimize
- //if value.TokenType == css.DimensionToken {
- // // TODO: reverse; parse dim not number
- // n := parse.Number(value.Data)
- // num := value.Data[:n]
- // dim = value.Data[n:]
- // parse.ToLower(dim)
- // if c.o.KeepCSS2 {
- // num = minify.Decimal(num, c.o.Precision) // don't use exponents
- // } else {
- // num = minify.Number(num, c.o.Precision)
- // }
- // // change dimension to compress number
- // h := ToHash(dim)
- // if h == Px || h == Pt || h == Pc || h == In || h == Mm || h == Cm || h == Q || h == Deg || h == Grad || h == Rad || h == Turn || h == S || h == Ms || h == Hz || h == Khz || h == Dpi || h == Dpcm || h == Dppx {
- // d, _ := strconv.ParseFloat(string(num), 64) // can never fail
- // var dimensions []Hash
- // var multipliers []float64
- // switch h {
- // case Px:
- // //dimensions = []Hash{In, Cm, Pc, Mm, Pt, Q}
- // //multipliers = []float64{0.010416666666666667, 0.026458333333333333, 0.0625, 0.26458333333333333, 0.75, 1.0583333333333333}
- // dimensions = []Hash{In, Pc, Pt}
- // multipliers = []float64{0.010416666666666667, 0.0625, 0.75}
- // case Pt:
- // //dimensions = []Hash{In, Cm, Pc, Mm, Px, Q}
- // //multipliers = []float64{0.013888888888888889, 0.035277777777777778, 0.083333333333333333, 0.35277777777777778, 1.3333333333333333, 1.4111111111111111}
- // dimensions = []Hash{In, Pc, Px}
- // multipliers = []float64{0.013888888888888889, 0.083333333333333333, 1.3333333333333333}
- // case Pc:
- // //dimensions = []Hash{In, Cm, Mm, Pt, Px, Q}
- // //multipliers = []float64{0.16666666666666667, 0.42333333333333333, 4.2333333333333333, 12.0, 16.0, 16.933333333333333}
- // dimensions = []Hash{In, Pt, Px}
- // multipliers = []float64{0.16666666666666667, 12.0, 16.0}
- // case In:
- // //dimensions = []Hash{Cm, Pc, Mm, Pt, Px, Q}
- // //multipliers = []float64{2.54, 6.0, 25.4, 72.0, 96.0, 101.6}
- // dimensions = []Hash{Pc, Pt, Px}
- // multipliers = []float64{6.0, 72.0, 96.0}
- // case Cm:
- // //dimensions = []Hash{In, Pc, Mm, Pt, Px, Q}
- // //multipliers = []float64{0.39370078740157480, 2.3622047244094488, 10.0, 28.346456692913386, 37.795275590551181, 40.0}
- // dimensions = []Hash{Mm, Q}
- // multipliers = []float64{10.0, 40.0}
- // case Mm:
- // //dimensions = []Hash{In, Cm, Pc, Pt, Px, Q}
- // //multipliers = []float64{0.039370078740157480, 0.1, 0.23622047244094488, 2.8346456692913386, 3.7795275590551181, 4.0}
- // dimensions = []Hash{Cm, Q}
- // multipliers = []float64{0.1, 4.0}
- // case Q:
- // //dimensions = []Hash{In, Cm, Pc, Pt, Px} // Q to mm is never smaller
- // //multipliers = []float64{0.0098425196850393701, 0.025, 0.059055118110236220, 0.70866141732283465, 0.94488188976377953}
- // dimensions = []Hash{Cm} // Q to mm is never smaller
- // multipliers = []float64{0.025}
- // case Deg:
- // //dimensions = []Hash{Turn, Rad, Grad}
- // //multipliers = []float64{0.0027777777777777778, 0.017453292519943296, 1.1111111111111111}
- // dimensions = []Hash{Turn, Grad}
- // multipliers = []float64{0.0027777777777777778, 1.1111111111111111}
- // case Grad:
- // //dimensions = []Hash{Turn, Rad, Deg}
- // //multipliers = []float64{0.0025, 0.015707963267948966, 0.9}
- // dimensions = []Hash{Turn, Deg}
- // multipliers = []float64{0.0025, 0.9}
- // case Turn:
- // //dimensions = []Hash{Rad, Deg, Grad}
- // //multipliers = []float64{6.2831853071795865, 360.0, 400.0}
- // dimensions = []Hash{Deg, Grad}
- // multipliers = []float64{360.0, 400.0}
- // case Rad:
- // //dimensions = []Hash{Turn, Deg, Grad}
- // //multipliers = []float64{0.15915494309189534, 57.295779513082321, 63.661977236758134}
- // case S:
- // dimensions = []Hash{Ms}
- // multipliers = []float64{1000.0}
- // case Ms:
- // dimensions = []Hash{S}
- // multipliers = []float64{0.001}
- // case Hz:
- // dimensions = []Hash{Khz}
- // multipliers = []float64{0.001}
- // case Khz:
- // dimensions = []Hash{Hz}
- // multipliers = []float64{1000.0}
- // case Dpi:
- // dimensions = []Hash{Dppx, Dpcm}
- // multipliers = []float64{0.010416666666666667, 0.39370078740157480}
- // case Dpcm:
- // //dimensions = []Hash{Dppx, Dpi}
- // //multipliers = []float64{0.026458333333333333, 2.54}
- // dimensions = []Hash{Dpi}
- // multipliers = []float64{2.54}
- // case Dppx:
- // //dimensions = []Hash{Dpcm, Dpi}
- // //multipliers = []float64{37.795275590551181, 96.0}
- // dimensions = []Hash{Dpi}
- // multipliers = []float64{96.0}
- // }
- // for i := range dimensions {
- // if dimensions[i] != h { //&& (d < 1.0) == (multipliers[i] > 1.0) {
- // b, _ := strconvParse.AppendFloat([]byte{}, d*multipliers[i], -1)
- // if c.o.KeepCSS2 {
- // b = minify.Decimal(b, c.o.newPrecision) // don't use exponents
- // } else {
- // b = minify.Number(b, c.o.newPrecision)
- // }
- // newDim := []byte(dimensions[i].String())
- // if len(b)+len(newDim) < len(num)+len(dim) {
- // num = b
- // dim = newDim
- // }
- // }
- // }
- // }
- // value.Data = append(num, dim...)
- //}
- //return value, dim
- }
|