css.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681
  1. // Package css minifies CSS3 following the specifications at http://www.w3.org/TR/css-syntax-3/.
  2. package css // import "github.com/tdewolff/minify/css"
  3. import (
  4. "bytes"
  5. "encoding/hex"
  6. "fmt"
  7. "io"
  8. "strconv"
  9. "github.com/tdewolff/minify"
  10. "github.com/tdewolff/parse"
  11. "github.com/tdewolff/parse/css"
  12. )
  13. var (
  14. spaceBytes = []byte(" ")
  15. colonBytes = []byte(":")
  16. semicolonBytes = []byte(";")
  17. commaBytes = []byte(",")
  18. leftBracketBytes = []byte("{")
  19. rightBracketBytes = []byte("}")
  20. zeroBytes = []byte("0")
  21. msfilterBytes = []byte("-ms-filter")
  22. backgroundNoneBytes = []byte("0 0")
  23. )
  24. type cssMinifier struct {
  25. m *minify.M
  26. w io.Writer
  27. p *css.Parser
  28. o *Minifier
  29. valuesBuffer []Token
  30. }
  31. ////////////////////////////////////////////////////////////////
  32. // DefaultMinifier is the default minifier.
  33. var DefaultMinifier = &Minifier{Decimals: -1, KeepCSS2: false}
  34. // Minifier is a CSS minifier.
  35. type Minifier struct {
  36. Decimals int
  37. KeepCSS2 bool
  38. }
  39. // Minify minifies CSS data, it reads from r and writes to w.
  40. func Minify(m *minify.M, w io.Writer, r io.Reader, params map[string]string) error {
  41. return DefaultMinifier.Minify(m, w, r, params)
  42. }
  43. // Minify minifies CSS data, it reads from r and writes to w.
  44. func (o *Minifier) Minify(m *minify.M, w io.Writer, r io.Reader, params map[string]string) error {
  45. isInline := params != nil && params["inline"] == "1"
  46. c := &cssMinifier{
  47. m: m,
  48. w: w,
  49. p: css.NewParser(r, isInline),
  50. o: o,
  51. }
  52. defer c.p.Restore()
  53. if err := c.minifyGrammar(); err != nil && err != io.EOF {
  54. return err
  55. }
  56. return nil
  57. }
  58. func (c *cssMinifier) minifyGrammar() error {
  59. semicolonQueued := false
  60. for {
  61. gt, _, data := c.p.Next()
  62. if gt == css.ErrorGrammar {
  63. if perr, ok := c.p.Err().(*parse.Error); ok && perr.Message == "unexpected token in declaration" {
  64. if semicolonQueued {
  65. if _, err := c.w.Write(semicolonBytes); err != nil {
  66. return err
  67. }
  68. }
  69. // write out the offending declaration
  70. if _, err := c.w.Write(data); err != nil {
  71. return err
  72. }
  73. for _, val := range c.p.Values() {
  74. if _, err := c.w.Write(val.Data); err != nil {
  75. return err
  76. }
  77. }
  78. semicolonQueued = true
  79. continue
  80. } else {
  81. return c.p.Err()
  82. }
  83. } else if gt == css.EndAtRuleGrammar || gt == css.EndRulesetGrammar {
  84. if _, err := c.w.Write(rightBracketBytes); err != nil {
  85. return err
  86. }
  87. semicolonQueued = false
  88. continue
  89. }
  90. if semicolonQueued {
  91. if _, err := c.w.Write(semicolonBytes); err != nil {
  92. return err
  93. }
  94. semicolonQueued = false
  95. }
  96. if gt == css.AtRuleGrammar {
  97. if _, err := c.w.Write(data); err != nil {
  98. return err
  99. }
  100. values := c.p.Values()
  101. if css.ToHash(data[1:]) == css.Import && len(values) == 2 && values[1].TokenType == css.URLToken {
  102. url := values[1].Data
  103. if url[4] != '"' && url[4] != '\'' {
  104. url = url[3:]
  105. url[0] = '"'
  106. url[len(url)-1] = '"'
  107. } else {
  108. url = url[4 : len(url)-1]
  109. }
  110. values[1].Data = url
  111. }
  112. for _, val := range values {
  113. if _, err := c.w.Write(val.Data); err != nil {
  114. return err
  115. }
  116. }
  117. semicolonQueued = true
  118. } else if gt == css.BeginAtRuleGrammar {
  119. if _, err := c.w.Write(data); err != nil {
  120. return err
  121. }
  122. for _, val := range c.p.Values() {
  123. if _, err := c.w.Write(val.Data); err != nil {
  124. return err
  125. }
  126. }
  127. if _, err := c.w.Write(leftBracketBytes); err != nil {
  128. return err
  129. }
  130. } else if gt == css.QualifiedRuleGrammar {
  131. if err := c.minifySelectors(data, c.p.Values()); err != nil {
  132. return err
  133. }
  134. if _, err := c.w.Write(commaBytes); err != nil {
  135. return err
  136. }
  137. } else if gt == css.BeginRulesetGrammar {
  138. if err := c.minifySelectors(data, c.p.Values()); err != nil {
  139. return err
  140. }
  141. if _, err := c.w.Write(leftBracketBytes); err != nil {
  142. return err
  143. }
  144. } else if gt == css.DeclarationGrammar {
  145. if _, err := c.w.Write(data); err != nil {
  146. return err
  147. }
  148. if _, err := c.w.Write(colonBytes); err != nil {
  149. return err
  150. }
  151. if err := c.minifyDeclaration(data, c.p.Values()); err != nil {
  152. return err
  153. }
  154. semicolonQueued = true
  155. } else if gt == css.CustomPropertyGrammar {
  156. if _, err := c.w.Write(data); err != nil {
  157. return err
  158. }
  159. if _, err := c.w.Write(colonBytes); err != nil {
  160. return err
  161. }
  162. if _, err := c.w.Write(c.p.Values()[0].Data); err != nil {
  163. return err
  164. }
  165. semicolonQueued = true
  166. } else if gt == css.CommentGrammar {
  167. if len(data) > 5 && data[1] == '*' && data[2] == '!' {
  168. if _, err := c.w.Write(data[:3]); err != nil {
  169. return err
  170. }
  171. comment := parse.TrimWhitespace(parse.ReplaceMultipleWhitespace(data[3 : len(data)-2]))
  172. if _, err := c.w.Write(comment); err != nil {
  173. return err
  174. }
  175. if _, err := c.w.Write(data[len(data)-2:]); err != nil {
  176. return err
  177. }
  178. }
  179. } else if _, err := c.w.Write(data); err != nil {
  180. return err
  181. }
  182. }
  183. }
  184. func (c *cssMinifier) minifySelectors(property []byte, values []css.Token) error {
  185. inAttr := false
  186. isClass := false
  187. for _, val := range c.p.Values() {
  188. if !inAttr {
  189. if val.TokenType == css.IdentToken {
  190. if !isClass {
  191. parse.ToLower(val.Data)
  192. }
  193. isClass = false
  194. } else if val.TokenType == css.DelimToken && val.Data[0] == '.' {
  195. isClass = true
  196. } else if val.TokenType == css.LeftBracketToken {
  197. inAttr = true
  198. }
  199. } else {
  200. if val.TokenType == css.StringToken && len(val.Data) > 2 {
  201. s := val.Data[1 : len(val.Data)-1]
  202. if css.IsIdent([]byte(s)) {
  203. if _, err := c.w.Write(s); err != nil {
  204. return err
  205. }
  206. continue
  207. }
  208. } else if val.TokenType == css.RightBracketToken {
  209. inAttr = false
  210. }
  211. }
  212. if _, err := c.w.Write(val.Data); err != nil {
  213. return err
  214. }
  215. }
  216. return nil
  217. }
  218. type Token struct {
  219. css.TokenType
  220. Data []byte
  221. Components []css.Token // only filled for functions
  222. }
  223. func (t Token) String() string {
  224. if len(t.Components) == 0 {
  225. return t.TokenType.String() + "(" + string(t.Data) + ")"
  226. } else {
  227. return fmt.Sprint(t.Components)
  228. }
  229. }
  230. func (c *cssMinifier) minifyDeclaration(property []byte, components []css.Token) error {
  231. if len(components) == 0 {
  232. return nil
  233. }
  234. prop := css.ToHash(property)
  235. // Strip !important from the component list, this will be added later separately
  236. important := false
  237. if len(components) > 2 && components[len(components)-2].TokenType == css.DelimToken && components[len(components)-2].Data[0] == '!' && css.ToHash(components[len(components)-1].Data) == css.Important {
  238. components = components[:len(components)-2]
  239. important = true
  240. }
  241. // Check if this is a simple list of values separated by whitespace or commas, otherwise we'll not be processing
  242. simple := true
  243. prevSep := true
  244. values := c.valuesBuffer[:0]
  245. for i := 0; i < len(components); i++ {
  246. comp := components[i]
  247. if comp.TokenType == css.LeftParenthesisToken || comp.TokenType == css.LeftBraceToken || comp.TokenType == css.LeftBracketToken || comp.TokenType == css.RightParenthesisToken || comp.TokenType == css.RightBraceToken || comp.TokenType == css.RightBracketToken {
  248. simple = false
  249. break
  250. }
  251. if !prevSep && comp.TokenType != css.WhitespaceToken && comp.TokenType != css.CommaToken {
  252. simple = false
  253. break
  254. }
  255. if comp.TokenType == css.WhitespaceToken || comp.TokenType == css.CommaToken {
  256. prevSep = true
  257. if comp.TokenType == css.CommaToken {
  258. values = append(values, Token{components[i].TokenType, components[i].Data, nil})
  259. }
  260. } else if comp.TokenType == css.FunctionToken {
  261. prevSep = false
  262. j := i + 1
  263. level := 0
  264. for ; j < len(components); j++ {
  265. if components[j].TokenType == css.LeftParenthesisToken {
  266. level++
  267. } else if components[j].TokenType == css.RightParenthesisToken {
  268. if level == 0 {
  269. j++
  270. break
  271. }
  272. level--
  273. }
  274. }
  275. values = append(values, Token{components[i].TokenType, components[i].Data, components[i:j]})
  276. i = j - 1
  277. } else {
  278. prevSep = false
  279. values = append(values, Token{components[i].TokenType, components[i].Data, nil})
  280. }
  281. }
  282. c.valuesBuffer = values
  283. // Do not process complex values (eg. containing blocks or is not alternated between whitespace/commas and flat values
  284. if !simple {
  285. if prop == css.Filter && len(components) == 11 {
  286. if bytes.Equal(components[0].Data, []byte("progid")) &&
  287. components[1].TokenType == css.ColonToken &&
  288. bytes.Equal(components[2].Data, []byte("DXImageTransform")) &&
  289. components[3].Data[0] == '.' &&
  290. bytes.Equal(components[4].Data, []byte("Microsoft")) &&
  291. components[5].Data[0] == '.' &&
  292. bytes.Equal(components[6].Data, []byte("Alpha(")) &&
  293. bytes.Equal(parse.ToLower(components[7].Data), []byte("opacity")) &&
  294. components[8].Data[0] == '=' &&
  295. components[10].Data[0] == ')' {
  296. components = components[6:]
  297. components[0].Data = []byte("alpha(")
  298. }
  299. }
  300. for _, component := range components {
  301. if _, err := c.w.Write(component.Data); err != nil {
  302. return err
  303. }
  304. }
  305. if important {
  306. if _, err := c.w.Write([]byte("!important")); err != nil {
  307. return err
  308. }
  309. }
  310. return nil
  311. }
  312. for i := range values {
  313. values[i].TokenType, values[i].Data = c.shortenToken(prop, values[i].TokenType, values[i].Data)
  314. }
  315. if len(values) > 0 {
  316. switch prop {
  317. case css.Font, css.Font_Weight, css.Font_Family:
  318. if prop == css.Font {
  319. // in "font:" shorthand all values before the size have "normal"
  320. // as valid and, at the same time, default value, so just skip them
  321. for i, value := range values {
  322. if !(value.TokenType == css.IdentToken && css.ToHash(value.Data) == css.Normal) {
  323. values = values[i:]
  324. break
  325. }
  326. }
  327. }
  328. for i, value := range values {
  329. if value.TokenType == css.IdentToken {
  330. val := css.ToHash(value.Data)
  331. if prop == css.Font_Weight && val == css.Normal {
  332. values[i].TokenType = css.NumberToken
  333. values[i].Data = []byte("400")
  334. } else if val == css.Bold {
  335. values[i].TokenType = css.NumberToken
  336. values[i].Data = []byte("700")
  337. }
  338. } else if value.TokenType == css.StringToken && len(value.Data) > 2 {
  339. unquote := true
  340. parse.ToLower(value.Data)
  341. s := value.Data[1 : len(value.Data)-1]
  342. if len(s) > 0 {
  343. for _, split := range bytes.Split(s, spaceBytes) {
  344. val := css.ToHash(split)
  345. // if len is zero, it contains two consecutive spaces
  346. if val == css.Inherit || val == css.Serif || val == css.Sans_Serif || val == css.Monospace || val == css.Fantasy || val == css.Cursive || val == css.Initial || val == css.Default ||
  347. len(split) == 0 || !css.IsIdent(split) {
  348. unquote = false
  349. break
  350. }
  351. }
  352. }
  353. if unquote {
  354. values[i].Data = s
  355. }
  356. }
  357. }
  358. case css.Margin, css.Padding, css.Border_Width:
  359. n := len(values)
  360. if n == 2 {
  361. if bytes.Equal(values[0].Data, values[1].Data) {
  362. values = values[:1]
  363. }
  364. } else if n == 3 {
  365. if bytes.Equal(values[0].Data, values[1].Data) && bytes.Equal(values[0].Data, values[2].Data) {
  366. values = values[:1]
  367. } else if bytes.Equal(values[0].Data, values[2].Data) {
  368. values = values[:2]
  369. }
  370. } else if n == 4 {
  371. if bytes.Equal(values[0].Data, values[1].Data) && bytes.Equal(values[0].Data, values[2].Data) && bytes.Equal(values[0].Data, values[3].Data) {
  372. values = values[:1]
  373. } else if bytes.Equal(values[0].Data, values[2].Data) && bytes.Equal(values[1].Data, values[3].Data) {
  374. values = values[:2]
  375. } else if bytes.Equal(values[1].Data, values[3].Data) {
  376. values = values[:3]
  377. }
  378. }
  379. case css.Outline, css.Border, css.Border_Bottom, css.Border_Left, css.Border_Right, css.Border_Top:
  380. none := false
  381. iZero := -1
  382. for i, value := range values {
  383. if len(value.Data) == 1 && value.Data[0] == '0' {
  384. iZero = i
  385. } else if css.ToHash(value.Data) == css.None {
  386. values[i].TokenType = css.NumberToken
  387. values[i].Data = zeroBytes
  388. none = true
  389. }
  390. }
  391. if none && iZero != -1 {
  392. values = append(values[:iZero], values[iZero+1:]...)
  393. }
  394. case css.Background:
  395. ident := css.ToHash(values[0].Data)
  396. if len(values) == 1 && (ident == css.None || ident == css.Transparent) {
  397. values[0].Data = backgroundNoneBytes
  398. }
  399. case css.Box_Shadow:
  400. if len(values) == 4 && len(values[0].Data) == 1 && values[0].Data[0] == '0' && len(values[1].Data) == 1 && values[1].Data[0] == '0' && len(values[2].Data) == 1 && values[2].Data[0] == '0' && len(values[3].Data) == 1 && values[3].Data[0] == '0' {
  401. values = values[:2]
  402. }
  403. default:
  404. if bytes.Equal(property, msfilterBytes) {
  405. alpha := []byte("progid:DXImageTransform.Microsoft.Alpha(Opacity=")
  406. if values[0].TokenType == css.StringToken && bytes.HasPrefix(values[0].Data[1:len(values[0].Data)-1], alpha) {
  407. values[0].Data = append(append([]byte{values[0].Data[0]}, []byte("alpha(opacity=")...), values[0].Data[1+len(alpha):]...)
  408. }
  409. }
  410. }
  411. }
  412. prevComma := true
  413. for _, value := range values {
  414. if !prevComma && value.TokenType != css.CommaToken {
  415. if _, err := c.w.Write([]byte(" ")); err != nil {
  416. return err
  417. }
  418. }
  419. if value.TokenType == css.FunctionToken {
  420. err := c.minifyFunction(value.Components)
  421. if err != nil {
  422. return err
  423. }
  424. } else {
  425. if _, err := c.w.Write(value.Data); err != nil {
  426. return err
  427. }
  428. }
  429. if value.TokenType == css.CommaToken {
  430. prevComma = true
  431. } else {
  432. prevComma = false
  433. }
  434. }
  435. if important {
  436. if _, err := c.w.Write([]byte("!important")); err != nil {
  437. return err
  438. }
  439. }
  440. return nil
  441. }
  442. func (c *cssMinifier) minifyFunction(values []css.Token) error {
  443. n := len(values)
  444. if n > 2 {
  445. simple := true
  446. for i, value := range values[1 : n-1] {
  447. if i%2 == 0 && (value.TokenType != css.NumberToken && value.TokenType != css.PercentageToken) || (i%2 == 1 && value.TokenType != css.CommaToken) {
  448. simple = false
  449. }
  450. }
  451. if simple && n%2 == 1 {
  452. fun := css.ToHash(values[0].Data[0 : len(values[0].Data)-1])
  453. for i := 1; i < n; i += 2 {
  454. values[i].TokenType, values[i].Data = c.shortenToken(0, values[i].TokenType, values[i].Data)
  455. }
  456. nArgs := (n - 1) / 2
  457. if (fun == css.Rgba || fun == css.Hsla) && nArgs == 4 {
  458. d, _ := strconv.ParseFloat(string(values[7].Data), 32) // can never fail because if simple == true than this is a NumberToken or PercentageToken
  459. if d-1.0 > -minify.Epsilon {
  460. if fun == css.Rgba {
  461. values[0].Data = []byte("rgb(")
  462. fun = css.Rgb
  463. } else {
  464. values[0].Data = []byte("hsl(")
  465. fun = css.Hsl
  466. }
  467. values = values[:len(values)-2]
  468. values[len(values)-1].Data = []byte(")")
  469. nArgs = 3
  470. } else if d < minify.Epsilon {
  471. values[0].Data = []byte("transparent")
  472. values = values[:1]
  473. fun = 0
  474. nArgs = 0
  475. }
  476. }
  477. if fun == css.Rgb && nArgs == 3 {
  478. var err [3]error
  479. rgb := [3]byte{}
  480. for j := 0; j < 3; j++ {
  481. val := values[j*2+1]
  482. if val.TokenType == css.NumberToken {
  483. var d int64
  484. d, err[j] = strconv.ParseInt(string(val.Data), 10, 32)
  485. if d < 0 {
  486. d = 0
  487. } else if d > 255 {
  488. d = 255
  489. }
  490. rgb[j] = byte(d)
  491. } else if val.TokenType == css.PercentageToken {
  492. var d float64
  493. d, err[j] = strconv.ParseFloat(string(val.Data[:len(val.Data)-1]), 32)
  494. if d < 0.0 {
  495. d = 0.0
  496. } else if d > 100.0 {
  497. d = 100.0
  498. }
  499. rgb[j] = byte((d / 100.0 * 255.0) + 0.5)
  500. }
  501. }
  502. if err[0] == nil && err[1] == nil && err[2] == nil {
  503. val := make([]byte, 7)
  504. val[0] = '#'
  505. hex.Encode(val[1:], rgb[:])
  506. parse.ToLower(val)
  507. if s, ok := ShortenColorHex[string(val)]; ok {
  508. if _, err := c.w.Write(s); err != nil {
  509. return err
  510. }
  511. } else {
  512. if len(val) == 7 && val[1] == val[2] && val[3] == val[4] && val[5] == val[6] {
  513. val[2] = val[3]
  514. val[3] = val[5]
  515. val = val[:4]
  516. }
  517. if _, err := c.w.Write(val); err != nil {
  518. return err
  519. }
  520. }
  521. return nil
  522. }
  523. } else if fun == css.Hsl && nArgs == 3 {
  524. if values[1].TokenType == css.NumberToken && values[3].TokenType == css.PercentageToken && values[5].TokenType == css.PercentageToken {
  525. h, err1 := strconv.ParseFloat(string(values[1].Data), 32)
  526. s, err2 := strconv.ParseFloat(string(values[3].Data[:len(values[3].Data)-1]), 32)
  527. l, err3 := strconv.ParseFloat(string(values[5].Data[:len(values[5].Data)-1]), 32)
  528. if err1 == nil && err2 == nil && err3 == nil {
  529. r, g, b := css.HSL2RGB(h/360.0, s/100.0, l/100.0)
  530. rgb := []byte{byte((r * 255.0) + 0.5), byte((g * 255.0) + 0.5), byte((b * 255.0) + 0.5)}
  531. val := make([]byte, 7)
  532. val[0] = '#'
  533. hex.Encode(val[1:], rgb[:])
  534. parse.ToLower(val)
  535. if s, ok := ShortenColorHex[string(val)]; ok {
  536. if _, err := c.w.Write(s); err != nil {
  537. return err
  538. }
  539. } else {
  540. if len(val) == 7 && val[1] == val[2] && val[3] == val[4] && val[5] == val[6] {
  541. val[2] = val[3]
  542. val[3] = val[5]
  543. val = val[:4]
  544. }
  545. if _, err := c.w.Write(val); err != nil {
  546. return err
  547. }
  548. }
  549. return nil
  550. }
  551. }
  552. }
  553. }
  554. }
  555. for _, value := range values {
  556. if _, err := c.w.Write(value.Data); err != nil {
  557. return err
  558. }
  559. }
  560. return nil
  561. }
  562. func (c *cssMinifier) shortenToken(prop css.Hash, tt css.TokenType, data []byte) (css.TokenType, []byte) {
  563. if tt == css.NumberToken || tt == css.PercentageToken || tt == css.DimensionToken {
  564. if tt == css.NumberToken && (prop == css.Z_Index || prop == css.Counter_Increment || prop == css.Counter_Reset || prop == css.Orphans || prop == css.Widows) {
  565. return tt, data // integers
  566. }
  567. n := len(data)
  568. if tt == css.PercentageToken {
  569. n--
  570. } else if tt == css.DimensionToken {
  571. n = parse.Number(data)
  572. }
  573. dim := data[n:]
  574. parse.ToLower(dim)
  575. if !c.o.KeepCSS2 {
  576. data = minify.Number(data[:n], c.o.Decimals)
  577. } else {
  578. data = minify.Decimal(data[:n], c.o.Decimals) // don't use exponents
  579. }
  580. if tt == css.DimensionToken && (len(data) != 1 || data[0] != '0' || !optionalZeroDimension[string(dim)] || prop == css.Flex) {
  581. data = append(data, dim...)
  582. } else if tt == css.PercentageToken {
  583. data = append(data, '%') // TODO: drop percentage for properties that accept <percentage> and <length>
  584. }
  585. } else if tt == css.IdentToken {
  586. //parse.ToLower(data) // TODO: not all identifiers are case-insensitive; all <custom-ident> properties are case-sensitive
  587. if hex, ok := ShortenColorName[css.ToHash(data)]; ok {
  588. tt = css.HashToken
  589. data = hex
  590. }
  591. } else if tt == css.HashToken {
  592. parse.ToLower(data)
  593. if ident, ok := ShortenColorHex[string(data)]; ok {
  594. tt = css.IdentToken
  595. data = ident
  596. } else if len(data) == 7 && data[1] == data[2] && data[3] == data[4] && data[5] == data[6] {
  597. tt = css.HashToken
  598. data[2] = data[3]
  599. data[3] = data[5]
  600. data = data[:4]
  601. }
  602. } else if tt == css.StringToken {
  603. // remove any \\\r\n \\\r \\\n
  604. for i := 1; i < len(data)-2; i++ {
  605. if data[i] == '\\' && (data[i+1] == '\n' || data[i+1] == '\r') {
  606. // encountered first replacee, now start to move bytes to the front
  607. j := i + 2
  608. if data[i+1] == '\r' && len(data) > i+2 && data[i+2] == '\n' {
  609. j++
  610. }
  611. for ; j < len(data); j++ {
  612. if data[j] == '\\' && len(data) > j+1 && (data[j+1] == '\n' || data[j+1] == '\r') {
  613. if data[j+1] == '\r' && len(data) > j+2 && data[j+2] == '\n' {
  614. j++
  615. }
  616. j++
  617. } else {
  618. data[i] = data[j]
  619. i++
  620. }
  621. }
  622. data = data[:i]
  623. break
  624. }
  625. }
  626. } else if tt == css.URLToken {
  627. parse.ToLower(data[:3])
  628. if len(data) > 10 {
  629. uri := parse.TrimWhitespace(data[4 : len(data)-1])
  630. delim := byte('"')
  631. if uri[0] == '\'' || uri[0] == '"' {
  632. delim = uri[0]
  633. uri = uri[1 : len(uri)-1]
  634. }
  635. uri = minify.DataURI(c.m, uri)
  636. if css.IsURLUnquoted(uri) {
  637. data = append(append([]byte("url("), uri...), ')')
  638. } else {
  639. data = append(append(append([]byte("url("), delim), uri...), delim, ')')
  640. }
  641. }
  642. }
  643. return tt, data
  644. }