f8bb7efa9aa3be8d6e33ead2c92696946a31cd0ed2db97c6b2bb309e511aa42d6e93ba74c6b1dc11fc330dd7dd3b595c8132f9778a505fe2790daea4ff8cea 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510
  1. var lexer = require('css-tree').lexer;
  2. var packNumber = require('./Number').pack;
  3. // http://www.w3.org/TR/css3-color/#svg-color
  4. var NAME_TO_HEX = {
  5. 'aliceblue': 'f0f8ff',
  6. 'antiquewhite': 'faebd7',
  7. 'aqua': '0ff',
  8. 'aquamarine': '7fffd4',
  9. 'azure': 'f0ffff',
  10. 'beige': 'f5f5dc',
  11. 'bisque': 'ffe4c4',
  12. 'black': '000',
  13. 'blanchedalmond': 'ffebcd',
  14. 'blue': '00f',
  15. 'blueviolet': '8a2be2',
  16. 'brown': 'a52a2a',
  17. 'burlywood': 'deb887',
  18. 'cadetblue': '5f9ea0',
  19. 'chartreuse': '7fff00',
  20. 'chocolate': 'd2691e',
  21. 'coral': 'ff7f50',
  22. 'cornflowerblue': '6495ed',
  23. 'cornsilk': 'fff8dc',
  24. 'crimson': 'dc143c',
  25. 'cyan': '0ff',
  26. 'darkblue': '00008b',
  27. 'darkcyan': '008b8b',
  28. 'darkgoldenrod': 'b8860b',
  29. 'darkgray': 'a9a9a9',
  30. 'darkgrey': 'a9a9a9',
  31. 'darkgreen': '006400',
  32. 'darkkhaki': 'bdb76b',
  33. 'darkmagenta': '8b008b',
  34. 'darkolivegreen': '556b2f',
  35. 'darkorange': 'ff8c00',
  36. 'darkorchid': '9932cc',
  37. 'darkred': '8b0000',
  38. 'darksalmon': 'e9967a',
  39. 'darkseagreen': '8fbc8f',
  40. 'darkslateblue': '483d8b',
  41. 'darkslategray': '2f4f4f',
  42. 'darkslategrey': '2f4f4f',
  43. 'darkturquoise': '00ced1',
  44. 'darkviolet': '9400d3',
  45. 'deeppink': 'ff1493',
  46. 'deepskyblue': '00bfff',
  47. 'dimgray': '696969',
  48. 'dimgrey': '696969',
  49. 'dodgerblue': '1e90ff',
  50. 'firebrick': 'b22222',
  51. 'floralwhite': 'fffaf0',
  52. 'forestgreen': '228b22',
  53. 'fuchsia': 'f0f',
  54. 'gainsboro': 'dcdcdc',
  55. 'ghostwhite': 'f8f8ff',
  56. 'gold': 'ffd700',
  57. 'goldenrod': 'daa520',
  58. 'gray': '808080',
  59. 'grey': '808080',
  60. 'green': '008000',
  61. 'greenyellow': 'adff2f',
  62. 'honeydew': 'f0fff0',
  63. 'hotpink': 'ff69b4',
  64. 'indianred': 'cd5c5c',
  65. 'indigo': '4b0082',
  66. 'ivory': 'fffff0',
  67. 'khaki': 'f0e68c',
  68. 'lavender': 'e6e6fa',
  69. 'lavenderblush': 'fff0f5',
  70. 'lawngreen': '7cfc00',
  71. 'lemonchiffon': 'fffacd',
  72. 'lightblue': 'add8e6',
  73. 'lightcoral': 'f08080',
  74. 'lightcyan': 'e0ffff',
  75. 'lightgoldenrodyellow': 'fafad2',
  76. 'lightgray': 'd3d3d3',
  77. 'lightgrey': 'd3d3d3',
  78. 'lightgreen': '90ee90',
  79. 'lightpink': 'ffb6c1',
  80. 'lightsalmon': 'ffa07a',
  81. 'lightseagreen': '20b2aa',
  82. 'lightskyblue': '87cefa',
  83. 'lightslategray': '789',
  84. 'lightslategrey': '789',
  85. 'lightsteelblue': 'b0c4de',
  86. 'lightyellow': 'ffffe0',
  87. 'lime': '0f0',
  88. 'limegreen': '32cd32',
  89. 'linen': 'faf0e6',
  90. 'magenta': 'f0f',
  91. 'maroon': '800000',
  92. 'mediumaquamarine': '66cdaa',
  93. 'mediumblue': '0000cd',
  94. 'mediumorchid': 'ba55d3',
  95. 'mediumpurple': '9370db',
  96. 'mediumseagreen': '3cb371',
  97. 'mediumslateblue': '7b68ee',
  98. 'mediumspringgreen': '00fa9a',
  99. 'mediumturquoise': '48d1cc',
  100. 'mediumvioletred': 'c71585',
  101. 'midnightblue': '191970',
  102. 'mintcream': 'f5fffa',
  103. 'mistyrose': 'ffe4e1',
  104. 'moccasin': 'ffe4b5',
  105. 'navajowhite': 'ffdead',
  106. 'navy': '000080',
  107. 'oldlace': 'fdf5e6',
  108. 'olive': '808000',
  109. 'olivedrab': '6b8e23',
  110. 'orange': 'ffa500',
  111. 'orangered': 'ff4500',
  112. 'orchid': 'da70d6',
  113. 'palegoldenrod': 'eee8aa',
  114. 'palegreen': '98fb98',
  115. 'paleturquoise': 'afeeee',
  116. 'palevioletred': 'db7093',
  117. 'papayawhip': 'ffefd5',
  118. 'peachpuff': 'ffdab9',
  119. 'peru': 'cd853f',
  120. 'pink': 'ffc0cb',
  121. 'plum': 'dda0dd',
  122. 'powderblue': 'b0e0e6',
  123. 'purple': '800080',
  124. 'rebeccapurple': '639',
  125. 'red': 'f00',
  126. 'rosybrown': 'bc8f8f',
  127. 'royalblue': '4169e1',
  128. 'saddlebrown': '8b4513',
  129. 'salmon': 'fa8072',
  130. 'sandybrown': 'f4a460',
  131. 'seagreen': '2e8b57',
  132. 'seashell': 'fff5ee',
  133. 'sienna': 'a0522d',
  134. 'silver': 'c0c0c0',
  135. 'skyblue': '87ceeb',
  136. 'slateblue': '6a5acd',
  137. 'slategray': '708090',
  138. 'slategrey': '708090',
  139. 'snow': 'fffafa',
  140. 'springgreen': '00ff7f',
  141. 'steelblue': '4682b4',
  142. 'tan': 'd2b48c',
  143. 'teal': '008080',
  144. 'thistle': 'd8bfd8',
  145. 'tomato': 'ff6347',
  146. 'turquoise': '40e0d0',
  147. 'violet': 'ee82ee',
  148. 'wheat': 'f5deb3',
  149. 'white': 'fff',
  150. 'whitesmoke': 'f5f5f5',
  151. 'yellow': 'ff0',
  152. 'yellowgreen': '9acd32'
  153. };
  154. var HEX_TO_NAME = {
  155. '800000': 'maroon',
  156. '800080': 'purple',
  157. '808000': 'olive',
  158. '808080': 'gray',
  159. '00ffff': 'cyan',
  160. 'f0ffff': 'azure',
  161. 'f5f5dc': 'beige',
  162. 'ffe4c4': 'bisque',
  163. '000000': 'black',
  164. '0000ff': 'blue',
  165. 'a52a2a': 'brown',
  166. 'ff7f50': 'coral',
  167. 'ffd700': 'gold',
  168. '008000': 'green',
  169. '4b0082': 'indigo',
  170. 'fffff0': 'ivory',
  171. 'f0e68c': 'khaki',
  172. '00ff00': 'lime',
  173. 'faf0e6': 'linen',
  174. '000080': 'navy',
  175. 'ffa500': 'orange',
  176. 'da70d6': 'orchid',
  177. 'cd853f': 'peru',
  178. 'ffc0cb': 'pink',
  179. 'dda0dd': 'plum',
  180. 'f00': 'red',
  181. 'ff0000': 'red',
  182. 'fa8072': 'salmon',
  183. 'a0522d': 'sienna',
  184. 'c0c0c0': 'silver',
  185. 'fffafa': 'snow',
  186. 'd2b48c': 'tan',
  187. '008080': 'teal',
  188. 'ff6347': 'tomato',
  189. 'ee82ee': 'violet',
  190. 'f5deb3': 'wheat',
  191. 'ffffff': 'white',
  192. 'ffff00': 'yellow'
  193. };
  194. function hueToRgb(p, q, t) {
  195. if (t < 0) {
  196. t += 1;
  197. }
  198. if (t > 1) {
  199. t -= 1;
  200. }
  201. if (t < 1 / 6) {
  202. return p + (q - p) * 6 * t;
  203. }
  204. if (t < 1 / 2) {
  205. return q;
  206. }
  207. if (t < 2 / 3) {
  208. return p + (q - p) * (2 / 3 - t) * 6;
  209. }
  210. return p;
  211. }
  212. function hslToRgb(h, s, l, a) {
  213. var r;
  214. var g;
  215. var b;
  216. if (s === 0) {
  217. r = g = b = l; // achromatic
  218. } else {
  219. var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
  220. var p = 2 * l - q;
  221. r = hueToRgb(p, q, h + 1 / 3);
  222. g = hueToRgb(p, q, h);
  223. b = hueToRgb(p, q, h - 1 / 3);
  224. }
  225. return [
  226. Math.round(r * 255),
  227. Math.round(g * 255),
  228. Math.round(b * 255),
  229. a
  230. ];
  231. }
  232. function toHex(value) {
  233. value = value.toString(16);
  234. return value.length === 1 ? '0' + value : value;
  235. }
  236. function parseFunctionArgs(functionArgs, count, rgb) {
  237. var cursor = functionArgs.head;
  238. var args = [];
  239. var wasValue = false;
  240. while (cursor !== null) {
  241. var node = cursor.data;
  242. var type = node.type;
  243. switch (type) {
  244. case 'Number':
  245. case 'Percentage':
  246. if (wasValue) {
  247. return;
  248. }
  249. wasValue = true;
  250. args.push({
  251. type: type,
  252. value: Number(node.value)
  253. });
  254. break;
  255. case 'Operator':
  256. if (node.value === ',') {
  257. if (!wasValue) {
  258. return;
  259. }
  260. wasValue = false;
  261. } else if (wasValue || node.value !== '+') {
  262. return;
  263. }
  264. break;
  265. default:
  266. // something we couldn't understand
  267. return;
  268. }
  269. cursor = cursor.next;
  270. }
  271. if (args.length !== count) {
  272. // invalid arguments count
  273. // TODO: remove those tokens
  274. return;
  275. }
  276. if (args.length === 4) {
  277. if (args[3].type !== 'Number') {
  278. // 4th argument should be a number
  279. // TODO: remove those tokens
  280. return;
  281. }
  282. args[3].type = 'Alpha';
  283. }
  284. if (rgb) {
  285. if (args[0].type !== args[1].type || args[0].type !== args[2].type) {
  286. // invalid color, numbers and percentage shouldn't be mixed
  287. // TODO: remove those tokens
  288. return;
  289. }
  290. } else {
  291. if (args[0].type !== 'Number' ||
  292. args[1].type !== 'Percentage' ||
  293. args[2].type !== 'Percentage') {
  294. // invalid color, for hsl values should be: number, percentage, percentage
  295. // TODO: remove those tokens
  296. return;
  297. }
  298. args[0].type = 'Angle';
  299. }
  300. return args.map(function(arg) {
  301. var value = Math.max(0, arg.value);
  302. switch (arg.type) {
  303. case 'Number':
  304. // fit value to [0..255] range
  305. value = Math.min(value, 255);
  306. break;
  307. case 'Percentage':
  308. // convert 0..100% to value in [0..255] range
  309. value = Math.min(value, 100) / 100;
  310. if (!rgb) {
  311. return value;
  312. }
  313. value = 255 * value;
  314. break;
  315. case 'Angle':
  316. // fit value to (-360..360) range
  317. return (((value % 360) + 360) % 360) / 360;
  318. case 'Alpha':
  319. // fit value to [0..1] range
  320. return Math.min(value, 1);
  321. }
  322. return Math.round(value);
  323. });
  324. }
  325. function compressFunction(node, item, list) {
  326. var functionName = node.name;
  327. var args;
  328. if (functionName === 'rgba' || functionName === 'hsla') {
  329. args = parseFunctionArgs(node.children, 4, functionName === 'rgba');
  330. if (!args) {
  331. // something went wrong
  332. return;
  333. }
  334. if (functionName === 'hsla') {
  335. args = hslToRgb.apply(null, args);
  336. node.name = 'rgba';
  337. }
  338. if (args[3] === 0) {
  339. // try to replace `rgba(x, x, x, 0)` to `transparent`
  340. // always replace `rgba(0, 0, 0, 0)` to `transparent`
  341. // otherwise avoid replacement in gradients since it may break color transition
  342. // http://stackoverflow.com/questions/11829410/css3-gradient-rendering-issues-from-transparent-to-white
  343. var scopeFunctionName = this.function && this.function.name;
  344. if ((args[0] === 0 && args[1] === 0 && args[2] === 0) ||
  345. !/^(?:to|from|color-stop)$|gradient$/i.test(scopeFunctionName)) {
  346. item.data = {
  347. type: 'Identifier',
  348. loc: node.loc,
  349. name: 'transparent'
  350. };
  351. return;
  352. }
  353. }
  354. if (args[3] !== 1) {
  355. // replace argument values for normalized/interpolated
  356. node.children.each(function(node, item, list) {
  357. if (node.type === 'Operator') {
  358. if (node.value !== ',') {
  359. list.remove(item);
  360. }
  361. return;
  362. }
  363. item.data = {
  364. type: 'Number',
  365. loc: node.loc,
  366. value: packNumber(args.shift(), null)
  367. };
  368. });
  369. return;
  370. }
  371. // otherwise convert to rgb, i.e. rgba(255, 0, 0, 1) -> rgb(255, 0, 0)
  372. functionName = 'rgb';
  373. }
  374. if (functionName === 'hsl') {
  375. args = args || parseFunctionArgs(node.children, 3, false);
  376. if (!args) {
  377. // something went wrong
  378. return;
  379. }
  380. // convert to rgb
  381. args = hslToRgb.apply(null, args);
  382. functionName = 'rgb';
  383. }
  384. if (functionName === 'rgb') {
  385. args = args || parseFunctionArgs(node.children, 3, true);
  386. if (!args) {
  387. // something went wrong
  388. return;
  389. }
  390. // check if color is not at the end and not followed by space
  391. var next = item.next;
  392. if (next && next.data.type !== 'WhiteSpace') {
  393. list.insert(list.createItem({
  394. type: 'WhiteSpace',
  395. value: ' '
  396. }), next);
  397. }
  398. item.data = {
  399. type: 'Hash',
  400. loc: node.loc,
  401. value: toHex(args[0]) + toHex(args[1]) + toHex(args[2])
  402. };
  403. compressHex(item.data, item);
  404. }
  405. }
  406. function compressIdent(node, item) {
  407. if (this.declaration === null) {
  408. return;
  409. }
  410. var color = node.name.toLowerCase();
  411. if (NAME_TO_HEX.hasOwnProperty(color) &&
  412. lexer.matchDeclaration(this.declaration).isType(node, 'color')) {
  413. var hex = NAME_TO_HEX[color];
  414. if (hex.length + 1 <= color.length) {
  415. // replace for shorter hex value
  416. item.data = {
  417. type: 'Hash',
  418. loc: node.loc,
  419. value: hex
  420. };
  421. } else {
  422. // special case for consistent colors
  423. if (color === 'grey') {
  424. color = 'gray';
  425. }
  426. // just replace value for lower cased name
  427. node.name = color;
  428. }
  429. }
  430. }
  431. function compressHex(node, item) {
  432. var color = node.value.toLowerCase();
  433. // #112233 -> #123
  434. if (color.length === 6 &&
  435. color[0] === color[1] &&
  436. color[2] === color[3] &&
  437. color[4] === color[5]) {
  438. color = color[0] + color[2] + color[4];
  439. }
  440. if (HEX_TO_NAME[color]) {
  441. item.data = {
  442. type: 'Identifier',
  443. loc: node.loc,
  444. name: HEX_TO_NAME[color]
  445. };
  446. } else {
  447. node.value = color;
  448. }
  449. }
  450. module.exports = {
  451. compressFunction: compressFunction,
  452. compressIdent: compressIdent,
  453. compressHex: compressHex
  454. };