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