OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 /** Representations of CSS styles. */ | |
6 | |
7 part of csslib.parser; | |
8 | |
9 // TODO(terry): Prune down this file we do need some of the code in this file | |
10 // for darker, lighter, how to represent a Font, etc but alot of | |
11 // the complexity can be removed. | |
12 // See https://github.com/dart-lang/csslib/issues/7 | |
13 | |
14 /** | |
15 * Base for all style properties (e.g., Color, Font, Border, Margin, etc.) | |
16 */ | |
17 abstract class _StyleProperty { | |
18 /** | |
19 * Returns the expression part of a CSS declaration. Declaration is: | |
20 * | |
21 * property:expression; | |
22 * | |
23 * E.g., if property is color then expression could be rgba(255,255,0) the | |
24 * CSS declaration would be 'color:rgba(255,255,0);'. | |
25 * | |
26 * then _cssExpression would return 'rgba(255,255,0)'. See | |
27 * <http://www.w3.org/TR/CSS21/grammar.html> | |
28 */ | |
29 String get cssExpression; | |
30 } | |
31 | |
32 /** | |
33 * Base interface for Color, HSL and RGB. | |
34 */ | |
35 abstract class ColorBase { | |
36 /** | |
37 * Canonical form for color #rrggbb with alpha blending (0.0 == full | |
38 * transparency and 1.0 == fully opaque). If _argb length is 6 it's an | |
39 * rrggbb otherwise it's aarrggbb. | |
40 */ | |
41 String toHexArgbString(); | |
42 | |
43 /** | |
44 * Return argb as a value (int). | |
45 */ | |
46 int get argbValue; | |
47 } | |
48 | |
49 /** | |
50 * General purpse Color class. Represent a color as an ARGB value that can be | |
51 * converted to and from num, hex string, hsl, hsla, rgb, rgba and SVG pre- | |
52 * defined color constant. | |
53 */ | |
54 class Color implements _StyleProperty, ColorBase { | |
55 // If _argb length is 6 it's an rrggbb otherwise it's aarrggbb. | |
56 final String _argb; | |
57 | |
58 // TODO(terry): Look at reducing Rgba and Hsla classes as factories for | |
59 // converting from Color to an Rgba or Hsla for reading only. | |
60 // Usefulness of creating an Rgba or Hsla is limited. | |
61 | |
62 /** | |
63 * Create a color with an integer representing the rgb value of red, green, | |
64 * and blue. The value 0xffffff is the color white #ffffff (CSS style). | |
65 * The [rgb] value of 0xffd700 would map to #ffd700 or the constant | |
66 * Color.gold, where ff is red intensity, d7 is green intensity, and 00 is | |
67 * blue intensity. | |
68 */ | |
69 Color(int rgb, [num alpha]) : this._argb = Color._rgbToArgbString(rgb, alpha); | |
70 | |
71 /** | |
72 * RGB takes three values. The [red], [green], and [blue] parameters are | |
73 * the intensity of those components where '0' is the least and '256' is the | |
74 * greatest. | |
75 * | |
76 * If [alpha] is provided, it is the level of translucency which ranges from | |
77 * '0' (completely transparent) to '1.0' (completely opaque). It will | |
78 * internally be mapped to an int between '0' and '255' like the other color | |
79 * components. | |
80 */ | |
81 Color.createRgba(int red, int green, int blue, [num alpha]) | |
82 : this._argb = Color.convertToHexString(Color._clamp(red, 0, 255), | |
83 Color._clamp(green, 0, 255), Color._clamp(blue, 0, 255), | |
84 alpha != null ? Color._clamp(alpha, 0, 1) : alpha); | |
85 | |
86 /** | |
87 * Creates a new color from a CSS color string. For more information, see | |
88 * <https://developer.mozilla.org/en/CSS/color>. | |
89 */ | |
90 Color.css(String color) : this._argb = Color._convertCssToArgb(color); | |
91 | |
92 // TODO(jmesserly): I found the use of percents a bit suprising. | |
93 /** | |
94 * HSL takes three values. The [hueDegree] degree on the color wheel; '0' is | |
95 * the least and '100' is the greatest. The value '0' or '360' is red, '120' | |
96 * is green, '240' is blue. Numbers in between reflect different shades. | |
97 * The [saturationPercent] percentage; where'0' is the least and '100' is the | |
98 * greatest (100 represents full color). The [lightnessPercent] percentage; | |
99 * where'0' is the least and '100' is the greatest. The value 0 is dark or | |
100 * black, 100 is light or white and 50 is a medium lightness. | |
101 * | |
102 * If [alpha] is provided, it is the level of translucency which ranges from | |
103 * '0' (completely transparent foreground) to '1.0' (completely opaque | |
104 * foreground). | |
105 */ | |
106 Color.createHsla(num hueDegree, num saturationPercent, num lightnessPercent, | |
107 [num alpha]) | |
108 : this._argb = new Hsla(Color._clamp(hueDegree, 0, 360) / 360, | |
109 Color._clamp(saturationPercent, 0, 100) / 100, | |
110 Color._clamp(lightnessPercent, 0, 100) / 100, | |
111 alpha != null ? Color._clamp(alpha, 0, 1) : alpha).toHexArgbString(); | |
112 | |
113 /** | |
114 * The hslaRaw takes three values. The [hue] degree on the color wheel; '0' | |
115 * is the least and '1' is the greatest. The value '0' or '1' is red, the | |
116 * ratio of 120/360 is green, and the ratio of 240/360 is blue. Numbers in | |
117 * between reflect different shades. The [saturation] is a percentage; '0' | |
118 * is the least and '1' is the greatest. The value of '1' is equivalent to | |
119 * 100% (full colour). The [lightness] is a percentage; '0' is the least and | |
120 * '1' is the greatest. The value of '0' is dark (black), the value of '1' | |
121 * is light (white), and the value of '.50' is a medium lightness. | |
122 * | |
123 * The fourth optional parameter is: | |
124 * [alpha] level of translucency range of values is 0..1, zero is a | |
125 * completely transparent foreground and 1 is a completely | |
126 * opaque foreground. | |
127 */ | |
128 Color.hslaRaw(num hue, num saturation, num lightness, [num alpha]) | |
129 : this._argb = new Hsla(Color._clamp(hue, 0, 1), | |
130 Color._clamp(saturation, 0, 1), Color._clamp(lightness, 0, 1), | |
131 alpha != null ? Color._clamp(alpha, 0, 1) : alpha).toHexArgbString(); | |
132 | |
133 /** | |
134 * Generate a real constant for pre-defined colors (no leading #). | |
135 */ | |
136 const Color.hex(this._argb); | |
137 | |
138 // TODO(jmesserly): this is needed by the example so leave it exposed for now. | |
139 String toString() => cssExpression; | |
140 | |
141 // TODO(terry): Regardless of how color is set (rgb, num, css or hsl) we'll | |
142 // always return a rgb or rgba loses fidelity when debugging in | |
143 // CSS if user uses hsl and would like to edit as hsl, etc. If | |
144 // this is an issue we should keep the original value and not re- | |
145 // create the CSS from the normalized value. | |
146 String get cssExpression { | |
147 if (_argb.length == 6) { | |
148 return "#$_argb"; // RGB only, no alpha blending. | |
149 } else { | |
150 num alpha = Color.hexToInt(_argb.substring(0, 2)); | |
151 String a = (alpha / 255).toStringAsPrecision(2); | |
152 int r = Color.hexToInt(_argb.substring(2, 4)); | |
153 int g = Color.hexToInt(_argb.substring(4, 6)); | |
154 int b = Color.hexToInt(_argb.substring(6, 8)); | |
155 return "rgba($r,$g,$b,$a)"; | |
156 } | |
157 } | |
158 | |
159 Rgba get rgba { | |
160 int nextIndex = 0; | |
161 num a; | |
162 if (_argb.length == 8) { | |
163 // Get alpha blending value 0..255 | |
164 int alpha = Color.hexToInt(_argb.substring(nextIndex, nextIndex + 2)); | |
165 // Convert to value from 0..1 | |
166 a = double.parse((alpha / 255).toStringAsPrecision(2)); | |
167 nextIndex += 2; | |
168 } | |
169 int r = Color.hexToInt(_argb.substring(nextIndex, nextIndex + 2)); | |
170 nextIndex += 2; | |
171 int g = Color.hexToInt(_argb.substring(nextIndex, nextIndex + 2)); | |
172 nextIndex += 2; | |
173 int b = Color.hexToInt(_argb.substring(nextIndex, nextIndex + 2)); | |
174 return new Rgba(r, g, b, a); | |
175 } | |
176 | |
177 Hsla get hsla => new Hsla.fromRgba(rgba); | |
178 | |
179 int get argbValue => Color.hexToInt(_argb); | |
180 | |
181 bool operator ==(other) => Color.equal(this, other); | |
182 | |
183 String toHexArgbString() => _argb; | |
184 | |
185 Color darker(num amount) { | |
186 Rgba newRgba = Color._createNewTintShadeFromRgba(rgba, -amount); | |
187 return new Color.hex("${newRgba.toHexArgbString()}"); | |
188 } | |
189 | |
190 Color lighter(num amount) { | |
191 Rgba newRgba = Color._createNewTintShadeFromRgba(rgba, amount); | |
192 return new Color.hex("${newRgba.toHexArgbString()}"); | |
193 } | |
194 | |
195 static bool equal(ColorBase curr, other) { | |
196 if (other is Color) { | |
197 Color o = other; | |
198 return o.toHexArgbString() == curr.toHexArgbString(); | |
199 } else if (other is Rgba) { | |
200 Rgba rgb = other; | |
201 return rgb.toHexArgbString() == curr.toHexArgbString(); | |
202 } else if (other is Hsla) { | |
203 Hsla hsla = other; | |
204 return hsla.toHexArgbString() == curr.toHexArgbString(); | |
205 } else { | |
206 return false; | |
207 } | |
208 } | |
209 | |
210 int get hashCode => _argb.hashCode; | |
211 | |
212 // Conversion routines: | |
213 | |
214 static String _rgbToArgbString(int rgba, num alpha) { | |
215 int a; | |
216 // If alpha is defined then adjust from 0..1 to 0..255 value, if not set | |
217 // then a is left as undefined and passed to convertToHexString. | |
218 if (alpha != null) { | |
219 a = (Color._clamp(alpha, 0, 1) * 255).round(); | |
220 } | |
221 | |
222 int r = (rgba & 0xff0000) >> 0x10; | |
223 int g = (rgba & 0xff00) >> 8; | |
224 int b = rgba & 0xff; | |
225 | |
226 return Color.convertToHexString(r, g, b, a); | |
227 } | |
228 | |
229 static const int _rgbCss = 1; | |
230 static const int _rgbaCss = 2; | |
231 static const int _hslCss = 3; | |
232 static const int _hslaCss = 4; | |
233 /** | |
234 * Parse CSS expressions of the from #rgb, rgb(r,g,b), rgba(r,g,b,a), | |
235 * hsl(h,s,l), hsla(h,s,l,a) and SVG colors (e.g., darkSlateblue, etc.) and | |
236 * convert to argb. | |
237 */ | |
238 static String _convertCssToArgb(String value) { | |
239 // TODO(terry): Better parser/regex for converting CSS properties. | |
240 String color = value.trim().replaceAll("\\s", ""); | |
241 if (color[0] == '#') { | |
242 String v = color.substring(1); | |
243 Color.hexToInt(v); // Valid hexadecimal, throws if not. | |
244 return v; | |
245 } else if (color.length > 0 && color[color.length - 1] == ')') { | |
246 int type; | |
247 if (color.indexOf("rgb(") == 0 || color.indexOf("RGB(") == 0) { | |
248 color = color.substring(4); | |
249 type = _rgbCss; | |
250 } else if (color.indexOf("rgba(") == 0 || color.indexOf("RGBA(") == 0) { | |
251 type = _rgbaCss; | |
252 color = color.substring(5); | |
253 } else if (color.indexOf("hsl(") == 0 || color.indexOf("HSL(") == 0) { | |
254 type = _hslCss; | |
255 color = color.substring(4); | |
256 } else if (color.indexOf("hsla(") == 0 || color.indexOf("HSLA(") == 0) { | |
257 type = _hslaCss; | |
258 color = color.substring(5); | |
259 } else { | |
260 throw new UnsupportedError('CSS property not implemented'); | |
261 } | |
262 | |
263 color = color.substring(0, color.length - 1); // Strip close paren. | |
264 | |
265 var args = <num>[]; | |
266 List<String> params = color.split(","); | |
267 for (String param in params) { | |
268 args.add(double.parse(param)); | |
269 } | |
270 switch (type) { | |
271 case _rgbCss: | |
272 return Color.convertToHexString(args[0], args[1], args[2]); | |
273 case _rgbaCss: | |
274 return Color.convertToHexString(args[0], args[1], args[2], args[3]); | |
275 case _hslCss: | |
276 return new Hsla(args[0], args[1], args[2]).toHexArgbString(); | |
277 case _hslaCss: | |
278 return new Hsla(args[0], args[1], args[2], args[3]).toHexArgbString(); | |
279 default: | |
280 // Type not defined UnsupportedOperationException should have thrown. | |
281 assert(true); | |
282 break; | |
283 } | |
284 } | |
285 } | |
286 | |
287 static int hexToInt(String hex) => int.parse(hex, radix: 16); | |
288 | |
289 static String convertToHexString(int r, int g, int b, [num a]) { | |
290 String rHex = Color._numAs2DigitHex(Color._clamp(r, 0, 255)); | |
291 String gHex = Color._numAs2DigitHex(Color._clamp(g, 0, 255)); | |
292 String bHex = Color._numAs2DigitHex(Color._clamp(b, 0, 255)); | |
293 String aHex = (a != null) | |
294 ? Color._numAs2DigitHex((Color._clamp(a, 0, 1) * 255).round()) | |
295 : ""; | |
296 | |
297 // TODO(terry) 15.toRadixString(16) return 'F' on Dartium not f as in JS. | |
298 // bug: <http://code.google.com/p/dart/issues/detail?id=2670> | |
299 return "$aHex$rHex$gHex$bHex".toLowerCase(); | |
300 } | |
301 | |
302 static String _numAs2DigitHex(num v) { | |
303 // TODO(terry): v.toInt().toRadixString instead of v.toRadixString | |
304 // Bug <http://code.google.com/p/dart/issues/detail?id=2671>. | |
305 String hex = v.toInt().toRadixString(16); | |
306 if (hex.length == 1) { | |
307 hex = "0${hex}"; | |
308 } | |
309 return hex; | |
310 } | |
311 | |
312 static num _clamp(num value, num min, num max) => | |
313 math.max(math.min(max, value), min); | |
314 | |
315 /** | |
316 * Change the tint (make color lighter) or shade (make color darker) of all | |
317 * parts of [rgba] (r, g and b). The [amount] is percentage darker between | |
318 * -1 to 0 for darker and 0 to 1 for lighter; '0' is no change. The [amount] | |
319 * will darken or lighten the rgb values; it will not change the alpha value. | |
320 * If [amount] is outside of the value -1 to +1 then [amount] is changed to | |
321 * either the min or max direction -1 or 1. | |
322 * | |
323 * Darker will approach the color #000000 (black) and lighter will approach | |
324 * the color #ffffff (white). | |
325 */ | |
326 static Rgba _createNewTintShadeFromRgba(Rgba rgba, num amount) { | |
327 int r, g, b; | |
328 num tintShade = Color._clamp(amount, -1, 1); | |
329 if (amount < 0 && rgba.r == 255 && rgba.g == 255 && rgba.b == 255) { | |
330 // TODO(terry): See TODO in _changeTintShadeColor; eliminate this test | |
331 // by converting to HSL and adjust lightness although this | |
332 // is fastest lighter/darker algorithm. | |
333 // Darkening white special handling. | |
334 r = Color._clamp((255 + (255 * tintShade)).round().toInt(), 0, 255); | |
335 g = Color._clamp((255 + (255 * tintShade)).round().toInt(), 0, 255); | |
336 b = Color._clamp((255 + (255 * tintShade)).round().toInt(), 0, 255); | |
337 } else { | |
338 // All other colors then darkening white go here. | |
339 r = Color._changeTintShadeColor(rgba.r, tintShade).round().toInt(); | |
340 g = Color._changeTintShadeColor(rgba.g, tintShade).round().toInt(); | |
341 b = Color._changeTintShadeColor(rgba.b, tintShade).round().toInt(); | |
342 } | |
343 return new Rgba(r, g, b, rgba.a); | |
344 } | |
345 | |
346 // TODO(terry): This does an okay lighter/darker; better would be convert to | |
347 // HSL then change the lightness. | |
348 /** | |
349 * The parameter [v] is the color to change (r, g, or b) in the range '0' to | |
350 * '255'. The parameter [delta] is a number between '-1' and '1'. A value | |
351 * between '-1' and '0' is darker and a value between '0' and '1' is lighter | |
352 * ('0' imples no change). | |
353 */ | |
354 static num _changeTintShadeColor(num v, num delta) => | |
355 Color._clamp(((1 - delta) * v + (delta * 255)).round(), 0, 255); | |
356 | |
357 // Predefined CSS colors see <http://www.w3.org/TR/css3-color/> | |
358 static final Color transparent = const Color.hex("00ffffff"); // Alpha 0.0 | |
359 static final Color aliceBlue = const Color.hex("0f08ff"); | |
360 static final Color antiqueWhite = const Color.hex("0faebd7"); | |
361 static final Color aqua = const Color.hex("00ffff"); | |
362 static final Color aquaMarine = const Color.hex("7fffd4"); | |
363 static final Color azure = const Color.hex("f0ffff"); | |
364 static final Color beige = const Color.hex("f5f5dc"); | |
365 static final Color bisque = const Color.hex("ffe4c4"); | |
366 static final Color black = const Color.hex("000000"); | |
367 static final Color blanchedAlmond = const Color.hex("ffebcd"); | |
368 static final Color blue = const Color.hex("0000ff"); | |
369 static final Color blueViolet = const Color.hex("8a2be2"); | |
370 static final Color brown = const Color.hex("a52a2a"); | |
371 static final Color burlyWood = const Color.hex("deb887"); | |
372 static final Color cadetBlue = const Color.hex("5f9ea0"); | |
373 static final Color chartreuse = const Color.hex("7fff00"); | |
374 static final Color chocolate = const Color.hex("d2691e"); | |
375 static final Color coral = const Color.hex("ff7f50"); | |
376 static final Color cornFlowerBlue = const Color.hex("6495ed"); | |
377 static final Color cornSilk = const Color.hex("fff8dc"); | |
378 static final Color crimson = const Color.hex("dc143c"); | |
379 static final Color cyan = const Color.hex("00ffff"); | |
380 static final Color darkBlue = const Color.hex("00008b"); | |
381 static final Color darkCyan = const Color.hex("008b8b"); | |
382 static final Color darkGoldenRod = const Color.hex("b8860b"); | |
383 static final Color darkGray = const Color.hex("a9a9a9"); | |
384 static final Color darkGreen = const Color.hex("006400"); | |
385 static final Color darkGrey = const Color.hex("a9a9a9"); | |
386 static final Color darkKhaki = const Color.hex("bdb76b"); | |
387 static final Color darkMagenta = const Color.hex("8b008b"); | |
388 static final Color darkOliveGreen = const Color.hex("556b2f"); | |
389 static final Color darkOrange = const Color.hex("ff8c00"); | |
390 static final Color darkOrchid = const Color.hex("9932cc"); | |
391 static final Color darkRed = const Color.hex("8b0000"); | |
392 static final Color darkSalmon = const Color.hex("e9967a"); | |
393 static final Color darkSeaGreen = const Color.hex("8fbc8f"); | |
394 static final Color darkSlateBlue = const Color.hex("483d8b"); | |
395 static final Color darkSlateGray = const Color.hex("2f4f4f"); | |
396 static final Color darkSlateGrey = const Color.hex("2f4f4f"); | |
397 static final Color darkTurquoise = const Color.hex("00ced1"); | |
398 static final Color darkViolet = const Color.hex("9400d3"); | |
399 static final Color deepPink = const Color.hex("ff1493"); | |
400 static final Color deepSkyBlue = const Color.hex("00bfff"); | |
401 static final Color dimGray = const Color.hex("696969"); | |
402 static final Color dimGrey = const Color.hex("696969"); | |
403 static final Color dodgerBlue = const Color.hex("1e90ff"); | |
404 static final Color fireBrick = const Color.hex("b22222"); | |
405 static final Color floralWhite = const Color.hex("fffaf0"); | |
406 static final Color forestGreen = const Color.hex("228b22"); | |
407 static final Color fuchsia = const Color.hex("ff00ff"); | |
408 static final Color gainsboro = const Color.hex("dcdcdc"); | |
409 static final Color ghostWhite = const Color.hex("f8f8ff"); | |
410 static final Color gold = const Color.hex("ffd700"); | |
411 static final Color goldenRod = const Color.hex("daa520"); | |
412 static final Color gray = const Color.hex("808080"); | |
413 static final Color green = const Color.hex("008000"); | |
414 static final Color greenYellow = const Color.hex("adff2f"); | |
415 static final Color grey = const Color.hex("808080"); | |
416 static final Color honeydew = const Color.hex("f0fff0"); | |
417 static final Color hotPink = const Color.hex("ff69b4"); | |
418 static final Color indianRed = const Color.hex("cd5c5c"); | |
419 static final Color indigo = const Color.hex("4b0082"); | |
420 static final Color ivory = const Color.hex("fffff0"); | |
421 static final Color khaki = const Color.hex("f0e68c"); | |
422 static final Color lavender = const Color.hex("e6e6fa"); | |
423 static final Color lavenderBlush = const Color.hex("fff0f5"); | |
424 static final Color lawnGreen = const Color.hex("7cfc00"); | |
425 static final Color lemonChiffon = const Color.hex("fffacd"); | |
426 static final Color lightBlue = const Color.hex("add8e6"); | |
427 static final Color lightCoral = const Color.hex("f08080"); | |
428 static final Color lightCyan = const Color.hex("e0ffff"); | |
429 static final Color lightGoldenRodYellow = const Color.hex("fafad2"); | |
430 static final Color lightGray = const Color.hex("d3d3d3"); | |
431 static final Color lightGreen = const Color.hex("90ee90"); | |
432 static final Color lightGrey = const Color.hex("d3d3d3"); | |
433 static final Color lightPink = const Color.hex("ffb6c1"); | |
434 static final Color lightSalmon = const Color.hex("ffa07a"); | |
435 static final Color lightSeaGreen = const Color.hex("20b2aa"); | |
436 static final Color lightSkyBlue = const Color.hex("87cefa"); | |
437 static final Color lightSlateGray = const Color.hex("778899"); | |
438 static final Color lightSlateGrey = const Color.hex("778899"); | |
439 static final Color lightSteelBlue = const Color.hex("b0c4de"); | |
440 static final Color lightYellow = const Color.hex("ffffe0"); | |
441 static final Color lime = const Color.hex("00ff00"); | |
442 static final Color limeGreen = const Color.hex("32cd32"); | |
443 static final Color linen = const Color.hex("faf0e6"); | |
444 static final Color magenta = const Color.hex("ff00ff"); | |
445 static final Color maroon = const Color.hex("800000"); | |
446 static final Color mediumAquaMarine = const Color.hex("66cdaa"); | |
447 static final Color mediumBlue = const Color.hex("0000cd"); | |
448 static final Color mediumOrchid = const Color.hex("ba55d3"); | |
449 static final Color mediumPurple = const Color.hex("9370db"); | |
450 static final Color mediumSeaGreen = const Color.hex("3cb371"); | |
451 static final Color mediumSlateBlue = const Color.hex("7b68ee"); | |
452 static final Color mediumSpringGreen = const Color.hex("00fa9a"); | |
453 static final Color mediumTurquoise = const Color.hex("48d1cc"); | |
454 static final Color mediumVioletRed = const Color.hex("c71585"); | |
455 static final Color midnightBlue = const Color.hex("191970"); | |
456 static final Color mintCream = const Color.hex("f5fffa"); | |
457 static final Color mistyRose = const Color.hex("ffe4e1"); | |
458 static final Color moccasin = const Color.hex("ffe4b5"); | |
459 static final Color navajoWhite = const Color.hex("ffdead"); | |
460 static final Color navy = const Color.hex("000080"); | |
461 static final Color oldLace = const Color.hex("fdf5e6"); | |
462 static final Color olive = const Color.hex("808000"); | |
463 static final Color oliveDrab = const Color.hex("6b8e23"); | |
464 static final Color orange = const Color.hex("ffa500"); | |
465 static final Color orangeRed = const Color.hex("ff4500"); | |
466 static final Color orchid = const Color.hex("da70d6"); | |
467 static final Color paleGoldenRod = const Color.hex("eee8aa"); | |
468 static final Color paleGreen = const Color.hex("98fb98"); | |
469 static final Color paleTurquoise = const Color.hex("afeeee"); | |
470 static final Color paleVioletRed = const Color.hex("db7093"); | |
471 static final Color papayaWhip = const Color.hex("ffefd5"); | |
472 static final Color peachPuff = const Color.hex("ffdab9"); | |
473 static final Color peru = const Color.hex("cd85ef"); | |
474 static final Color pink = const Color.hex("ffc0cb"); | |
475 static final Color plum = const Color.hex("dda0dd"); | |
476 static final Color powderBlue = const Color.hex("b0e0e6"); | |
477 static final Color purple = const Color.hex("800080"); | |
478 static final Color red = const Color.hex("ff0000"); | |
479 static final Color rosyBrown = const Color.hex("bc8f8f"); | |
480 static final Color royalBlue = const Color.hex("4169e1"); | |
481 static final Color saddleBrown = const Color.hex("8b4513"); | |
482 static final Color salmon = const Color.hex("fa8072"); | |
483 static final Color sandyBrown = const Color.hex("f4a460"); | |
484 static final Color seaGreen = const Color.hex("2e8b57"); | |
485 static final Color seashell = const Color.hex("fff5ee"); | |
486 static final Color sienna = const Color.hex("a0522d"); | |
487 static final Color silver = const Color.hex("c0c0c0"); | |
488 static final Color skyBlue = const Color.hex("87ceeb"); | |
489 static final Color slateBlue = const Color.hex("6a5acd"); | |
490 static final Color slateGray = const Color.hex("708090"); | |
491 static final Color slateGrey = const Color.hex("708090"); | |
492 static final Color snow = const Color.hex("fffafa"); | |
493 static final Color springGreen = const Color.hex("00ff7f"); | |
494 static final Color steelBlue = const Color.hex("4682b4"); | |
495 static final Color tan = const Color.hex("d2b48c"); | |
496 static final Color teal = const Color.hex("008080"); | |
497 static final Color thistle = const Color.hex("d8bfd8"); | |
498 static final Color tomato = const Color.hex("ff6347"); | |
499 static final Color turquoise = const Color.hex("40e0d0"); | |
500 static final Color violet = const Color.hex("ee82ee"); | |
501 static final Color wheat = const Color.hex("f5deb3"); | |
502 static final Color white = const Color.hex("ffffff"); | |
503 static final Color whiteSmoke = const Color.hex("f5f5f5"); | |
504 static final Color yellow = const Color.hex("ffff00"); | |
505 static final Color yellowGreen = const Color.hex("9acd32"); | |
506 } | |
507 | |
508 /** | |
509 * Rgba class for users that want to interact with a color as a RGBA value. | |
510 */ | |
511 class Rgba implements _StyleProperty, ColorBase { | |
512 // TODO(terry): Consider consolidating rgba to a single 32-bit int, make sure | |
513 // it works under JS and Dart VM. | |
514 final int r; | |
515 final int g; | |
516 final int b; | |
517 final num a; | |
518 | |
519 Rgba(int red, int green, int blue, [num alpha]) | |
520 : this.r = Color._clamp(red, 0, 255), | |
521 this.g = Color._clamp(green, 0, 255), | |
522 this.b = Color._clamp(blue, 0, 255), | |
523 this.a = (alpha != null) ? Color._clamp(alpha, 0, 1) : alpha; | |
524 | |
525 factory Rgba.fromString(String hexValue) => | |
526 new Color.css("#${Color._convertCssToArgb(hexValue)}").rgba; | |
527 | |
528 factory Rgba.fromColor(Color color) => color.rgba; | |
529 | |
530 factory Rgba.fromArgbValue(num value) { | |
531 return new Rgba(((value.toInt() & 0xff000000) >> 0x18), /* a */ | |
532 ((value.toInt() & 0xff0000) >> 0x10), /* r */ | |
533 ((value.toInt() & 0xff00) >> 8), /* g */ | |
534 ((value.toInt() & 0xff))); /* b */ | |
535 } | |
536 | |
537 factory Rgba.fromHsla(Hsla hsla) { | |
538 // Convert to Rgba. | |
539 // See site <http://easyrgb.com/index.php?X=MATH> for good documentation | |
540 // and color conversion routines. | |
541 | |
542 num h = hsla.hue; | |
543 num s = hsla.saturation; | |
544 num l = hsla.lightness; | |
545 num a = hsla.alpha; | |
546 | |
547 int r; | |
548 int g; | |
549 int b; | |
550 | |
551 if (s == 0) { | |
552 r = (l * 255).round().toInt(); | |
553 g = r; | |
554 b = r; | |
555 } else { | |
556 num var2; | |
557 | |
558 if (l < 0.5) { | |
559 var2 = l * (1 + s); | |
560 } else { | |
561 var2 = (l + s) - (s * l); | |
562 } | |
563 num var1 = 2 * l - var2; | |
564 | |
565 r = (255 * Rgba._hueToRGB(var1, var2, h + (1 / 3))).round().toInt(); | |
566 g = (255 * Rgba._hueToRGB(var1, var2, h)).round().toInt(); | |
567 b = (255 * Rgba._hueToRGB(var1, var2, h - (1 / 3))).round().toInt(); | |
568 } | |
569 | |
570 return new Rgba(r, g, b, a); | |
571 } | |
572 | |
573 static num _hueToRGB(num v1, num v2, num vH) { | |
574 if (vH < 0) { | |
575 vH += 1; | |
576 } | |
577 | |
578 if (vH > 1) { | |
579 vH -= 1; | |
580 } | |
581 | |
582 if ((6 * vH) < 1) { | |
583 return (v1 + (v2 - v1) * 6 * vH); | |
584 } | |
585 | |
586 if ((2 * vH) < 1) { | |
587 return v2; | |
588 } | |
589 | |
590 if ((3 * vH) < 2) { | |
591 return (v1 + (v2 - v1) * ((2 / 3 - vH) * 6)); | |
592 } | |
593 | |
594 return v1; | |
595 } | |
596 | |
597 bool operator ==(other) => Color.equal(this, other); | |
598 | |
599 String get cssExpression { | |
600 if (a == null) { | |
601 return "#${Color.convertToHexString(r, g, b)}"; | |
602 } else { | |
603 return "rgba($r,$g,$b,$a)"; | |
604 } | |
605 } | |
606 | |
607 String toHexArgbString() => Color.convertToHexString(r, g, b, a); | |
608 | |
609 int get argbValue { | |
610 int value = 0; | |
611 if (a != null) { | |
612 value = (a.toInt() << 0x18); | |
613 } | |
614 value += (r << 0x10); | |
615 value += (g << 0x08); | |
616 value += b; | |
617 return value; | |
618 } | |
619 | |
620 Color get color => new Color.createRgba(r, g, b, a); | |
621 Hsla get hsla => new Hsla.fromRgba(this); | |
622 | |
623 Rgba darker(num amount) => Color._createNewTintShadeFromRgba(this, -amount); | |
624 Rgba lighter(num amount) => Color._createNewTintShadeFromRgba(this, amount); | |
625 | |
626 int get hashCode => toHexArgbString().hashCode; | |
627 } | |
628 | |
629 /** | |
630 * Hsl class support to interact with a color as a hsl with hue, saturation, and | |
631 * lightness with optional alpha blending. The hue is a ratio of 360 degrees | |
632 * 360° = 1 or 0, (1° == (1/360)), saturation and lightness is a 0..1 fraction | |
633 * (1 == 100%) and alpha is a 0..1 fraction. | |
634 */ | |
635 class Hsla implements _StyleProperty, ColorBase { | |
636 final num _h; // Value from 0..1 | |
637 final num _s; // Value from 0..1 | |
638 final num _l; // Value from 0..1 | |
639 final num _a; // Value from 0..1 | |
640 | |
641 /** | |
642 * [hue] is a 0..1 fraction of 360 degrees (360 == 0). | |
643 * [saturation] is a 0..1 fraction (100% == 1). | |
644 * [lightness] is a 0..1 fraction (100% == 1). | |
645 * [alpha] is a 0..1 fraction, alpha blending between 0..1, 1 == 100% opaque. | |
646 */ | |
647 Hsla(num hue, num saturation, num lightness, [num alpha]) | |
648 : this._h = (hue == 1) ? 0 : Color._clamp(hue, 0, 1), | |
649 this._s = Color._clamp(saturation, 0, 1), | |
650 this._l = Color._clamp(lightness, 0, 1), | |
651 this._a = (alpha != null) ? Color._clamp(alpha, 0, 1) : alpha; | |
652 | |
653 factory Hsla.fromString(String hexValue) { | |
654 Rgba rgba = new Color.css("#${Color._convertCssToArgb(hexValue)}").rgba; | |
655 return _createFromRgba(rgba.r, rgba.g, rgba.b, rgba.a); | |
656 } | |
657 | |
658 factory Hsla.fromColor(Color color) { | |
659 Rgba rgba = color.rgba; | |
660 return _createFromRgba(rgba.r, rgba.g, rgba.b, rgba.a); | |
661 } | |
662 | |
663 factory Hsla.fromArgbValue(num value) { | |
664 num a = (value.toInt() & 0xff000000) >> 0x18; | |
665 int r = (value.toInt() & 0xff0000) >> 0x10; | |
666 int g = (value.toInt() & 0xff00) >> 8; | |
667 int b = value.toInt() & 0xff; | |
668 | |
669 // Convert alpha to 0..1 from (0..255). | |
670 if (a != null) { | |
671 a = double.parse((a / 255).toStringAsPrecision(2)); | |
672 } | |
673 | |
674 return _createFromRgba(r, g, b, a); | |
675 } | |
676 | |
677 factory Hsla.fromRgba(Rgba rgba) => | |
678 _createFromRgba(rgba.r, rgba.g, rgba.b, rgba.a); | |
679 | |
680 static Hsla _createFromRgba(num r, num g, num b, num a) { | |
681 // Convert RGB to hsl. | |
682 // See site <http://easyrgb.com/index.php?X=MATH> for good documentation | |
683 // and color conversion routines. | |
684 r /= 255; | |
685 g /= 255; | |
686 b /= 255; | |
687 | |
688 // Hue, saturation and lightness. | |
689 num h; | |
690 num s; | |
691 num l; | |
692 | |
693 num minRgb = math.min(r, math.min(g, b)); | |
694 num maxRgb = math.max(r, math.max(g, b)); | |
695 l = (maxRgb + minRgb) / 2; | |
696 if (l <= 0) { | |
697 return new Hsla(0, 0, l); // Black; | |
698 } | |
699 | |
700 num vm = maxRgb - minRgb; | |
701 s = vm; | |
702 if (s > 0) { | |
703 s /= (l < 0.5) ? (maxRgb + minRgb) : (2 - maxRgb - minRgb); | |
704 } else { | |
705 return new Hsla(0, 0, l); // White | |
706 } | |
707 | |
708 num r2, g2, b2; | |
709 r2 = (maxRgb - r) / vm; | |
710 g2 = (maxRgb - g) / vm; | |
711 b2 = (maxRgb - b) / vm; | |
712 if (r == maxRgb) { | |
713 h = (g == minRgb) ? 5.0 + b2 : 1 - g2; | |
714 } else if (g == maxRgb) { | |
715 h = (b == minRgb) ? 1 + r2 : 3 - b2; | |
716 } else { | |
717 h = (r == minRgb) ? 3 + g2 : 5 - r2; | |
718 } | |
719 h /= 6; | |
720 | |
721 return new Hsla(h, s, l, a); | |
722 } | |
723 | |
724 /** | |
725 * Returns 0..1 fraction (ratio of 360°, e.g. 1° == 1/360). | |
726 */ | |
727 num get hue => _h; | |
728 | |
729 /** | |
730 * Returns 0..1 fraction (1 == 100%) | |
731 */ | |
732 num get saturation => _s; | |
733 | |
734 /** | |
735 * Returns 0..1 fraction (1 == 100%). | |
736 */ | |
737 num get lightness => _l; | |
738 | |
739 /** | |
740 * Returns number as degrees 0..360. | |
741 */ | |
742 num get hueDegrees => (_h * 360).round(); | |
743 | |
744 /** | |
745 * Returns number as percentage 0..100 | |
746 */ | |
747 num get saturationPercentage => (_s * 100).round(); | |
748 | |
749 /** | |
750 * Returns number as percentage 0..100. | |
751 */ | |
752 num get lightnessPercentage => (_l * 100).round(); | |
753 | |
754 /** | |
755 * Returns number as 0..1 | |
756 */ | |
757 num get alpha => _a; | |
758 | |
759 bool operator ==(other) => Color.equal(this, other); | |
760 | |
761 String get cssExpression => (_a == null) | |
762 ? "hsl($hueDegrees,$saturationPercentage,$lightnessPercentage)" | |
763 : "hsla($hueDegrees,$saturationPercentage,$lightnessPercentage,$_a)"; | |
764 | |
765 String toHexArgbString() => new Rgba.fromHsla(this).toHexArgbString(); | |
766 | |
767 int get argbValue => Color.hexToInt(this.toHexArgbString()); | |
768 | |
769 Color get color => new Color.createHsla(_h, _s, _l, _a); | |
770 Rgba get rgba => new Rgba.fromHsla(this); | |
771 | |
772 Hsla darker(num amount) => | |
773 new Hsla.fromRgba(new Rgba.fromHsla(this).darker(amount)); | |
774 | |
775 Hsla lighter(num amount) => | |
776 new Hsla.fromRgba(new Rgba.fromHsla(this).lighter(amount)); | |
777 | |
778 int get hashCode => toHexArgbString().hashCode; | |
779 } | |
780 | |
781 /** X,Y position. */ | |
782 class PointXY implements _StyleProperty { | |
783 final num x, y; | |
784 const PointXY(this.x, this.y); | |
785 | |
786 String get cssExpression { | |
787 // TODO(terry): TBD | |
788 } | |
789 } | |
790 | |
791 // TODO(terry): Implement style and color. | |
792 /** | |
793 * Supports border for measuring with layout. | |
794 */ | |
795 class Border implements _StyleProperty { | |
796 final int top, left, bottom, right; | |
797 | |
798 // TODO(terry): Just like CSS, 1-arg -> set all properties, 2-args -> top and | |
799 // bottom are first arg, left and right are second, 3-args, and | |
800 // 4-args -> tlbr or trbl. | |
801 const Border([this.top, this.left, this.bottom, this.right]); | |
802 | |
803 // TODO(terry): Consider using Size or width and height. | |
804 Border.uniform(num amount) | |
805 : top = amount, | |
806 left = amount, | |
807 bottom = amount, | |
808 right = amount; | |
809 | |
810 int get width => left + right; | |
811 int get height => top + bottom; | |
812 | |
813 String get cssExpression { | |
814 return (top == left && bottom == right && top == right) | |
815 ? "${left}px" | |
816 : "${top != null ? '$top' : '0'}px ${ | |
817 right != null ? '$right' : '0'}px ${ | |
818 bottom != null ? '$bottom' : '0'}px ${ | |
819 left != null ? '$left' : '0'}px"; | |
820 } | |
821 } | |
822 | |
823 /** Font style constants. */ | |
824 class FontStyle { | |
825 /** Font style [normal] default. */ | |
826 static const String normal = "normal"; | |
827 /** | |
828 * Font style [italic] use explicity crafted italic font otherwise inclined | |
829 * on the fly like oblique. | |
830 */ | |
831 static const String italic = "italic"; | |
832 /** | |
833 * Font style [oblique] is rarely used. The normal style of a font is inclined | |
834 * on the fly to the right by 8-12 degrees. | |
835 */ | |
836 static const String oblique = "oblique"; | |
837 } | |
838 | |
839 /** Font variant constants. */ | |
840 class FontVariant { | |
841 /** Font style [normal] default. */ | |
842 static const String normal = "normal"; | |
843 /** Font variant [smallCaps]. */ | |
844 static const String smallCaps = "small-caps"; | |
845 } | |
846 | |
847 /** Font weight constants values 100, 200, 300, 400, 500, 600, 700, 800, 900. */ | |
848 class FontWeight { | |
849 /** Font weight normal [default] */ | |
850 static const int normal = 400; | |
851 /** Font weight bold */ | |
852 static const int bold = 700; | |
853 | |
854 static const int wt100 = 100; | |
855 static const int wt200 = 200; | |
856 static const int wt300 = 300; | |
857 static const int wt400 = 400; | |
858 static const int wt500 = 500; | |
859 static const int wt600 = 600; | |
860 static const int wt700 = 700; | |
861 static const int wt800 = 800; | |
862 static const int wt900 = 900; | |
863 } | |
864 | |
865 /** Generic font family names. */ | |
866 class FontGeneric { | |
867 /** Generic family sans-serif font (w/o serifs). */ | |
868 static const String sansSerif = "sans-serif"; | |
869 /** Generic family serif font. */ | |
870 static const String serif = "serif"; | |
871 /** Generic family fixed-width font. */ | |
872 static const monospace = "monospace"; | |
873 /** Generic family emulate handwriting font. */ | |
874 static const String cursive = "cursive"; | |
875 /** Generic family decorative font. */ | |
876 static const String fantasy = "fantasy"; | |
877 } | |
878 | |
879 /** | |
880 * List of most common font families across different platforms. Use the | |
881 * collection names in the Font class (e.g., Font.SANS_SERIF, Font.FONT_SERIF, | |
882 * Font.MONOSPACE, Font.CURSIVE or Font.FANTASY). These work best on all | |
883 * platforms using the fonts that best match availability on each platform. | |
884 * See <http://www.angelfire.com/al4/rcollins/style/fonts.html> for a good | |
885 * description of fonts available between platforms and browsers. | |
886 */ | |
887 class FontFamily { | |
888 /** Sans-Serif font for Windows similar to Helvetica on Mac bold/italic. */ | |
889 static const String arial = "arial"; | |
890 /** Sans-Serif font for Windows less common already bolded. */ | |
891 static const String arialBlack = "arial black"; | |
892 /** Sans-Serif font for Mac since 1984, similar to Arial/Helvetica. */ | |
893 static const String geneva = "geneva"; | |
894 /** Sans-Serif font for Windows most readable sans-serif font for displays. */ | |
895 static const String verdana = "verdana"; | |
896 /** Sans-Serif font for Mac since 1984 is identical to Arial. */ | |
897 static const String helvetica = "helvetica"; | |
898 | |
899 /** Serif font for Windows traditional font with “old-style” numerals. */ | |
900 static const String georgia = "georgia"; | |
901 /** | |
902 * Serif font for Mac. PCs may have the non-scalable Times use Times New | |
903 * Roman instead. Times is more compact than Times New Roman. | |
904 */ | |
905 static const String times = "times"; | |
906 /** | |
907 * Serif font for Windows most common serif font and default serif font for | |
908 * most browsers. | |
909 */ | |
910 static const String timesNewRoman = "times new roman"; | |
911 | |
912 /** | |
913 * Monospace font for Mac/Windows most common. Scalable on Mac not scalable | |
914 * on Windows. | |
915 */ | |
916 static const String courier = "courier"; | |
917 /** Monospace font for Mac/Windows scalable on both platforms. */ | |
918 static const String courierNew = "courier new"; | |
919 | |
920 /** Cursive font for Windows and default cursive font for IE. */ | |
921 static const String comicSansMs = "comic sans ms"; | |
922 /** Cursive font for Mac on Macs 2000 and newer. */ | |
923 static const String textile = "textile"; | |
924 /** Cursive font for older Macs. */ | |
925 static const String appleChancery = "apple chancery"; | |
926 /** Cursive font for some PCs. */ | |
927 static const String zaphChancery = "zaph chancery"; | |
928 | |
929 /** Fantasy font on most Mac/Windows/Linux platforms. */ | |
930 static const String impact = "impact"; | |
931 /** Fantasy font for Windows. */ | |
932 static const String webdings = "webdings"; | |
933 } | |
934 | |
935 class LineHeight { | |
936 final num height; | |
937 final bool inPixels; | |
938 const LineHeight(this.height, {this.inPixels: true}); | |
939 } | |
940 | |
941 // TODO(terry): Support @font-face fule. | |
942 /** | |
943 * Font style support for size, family, weight, style, variant, and lineheight. | |
944 */ | |
945 class Font implements _StyleProperty { | |
946 /** Collection of most common sans-serif fonts in order. */ | |
947 static const List<String> sansSerif = const [ | |
948 FontFamily.arial, | |
949 FontFamily.verdana, | |
950 FontFamily.geneva, | |
951 FontFamily.helvetica, | |
952 FontGeneric.sansSerif | |
953 ]; | |
954 | |
955 /** Collection of most common serif fonts in order. */ | |
956 static const List<String> serif = const [ | |
957 FontFamily.georgia, | |
958 FontFamily.timesNewRoman, | |
959 FontFamily.times, | |
960 FontGeneric.serif | |
961 ]; | |
962 /** Collection of most common monospace fonts in order. */ | |
963 static const List<String> monospace = const [ | |
964 FontFamily.courierNew, | |
965 FontFamily.courier, | |
966 FontGeneric.monospace | |
967 ]; | |
968 /** Collection of most common cursive fonts in order. */ | |
969 static const List<String> cursive = const [ | |
970 FontFamily.textile, | |
971 FontFamily.appleChancery, | |
972 FontFamily.zaphChancery, | |
973 FontGeneric.fantasy | |
974 ]; | |
975 /** Collection of most common fantasy fonts in order. */ | |
976 static const List<String> fantasy = const [ | |
977 FontFamily.comicSansMs, | |
978 FontFamily.impact, | |
979 FontFamily.webdings, | |
980 FontGeneric.fantasy | |
981 ]; | |
982 | |
983 // TODO(terry): Should support the values xx-small, small, large, xx-large, | |
984 // etc. (mapped to a pixel sized font)? | |
985 /** Font size in pixels. */ | |
986 final num size; | |
987 | |
988 // TODO(terry): _family should be an immutable list, wrapper class to do this | |
989 // should exist in Dart. | |
990 /** | |
991 * Family specifies a list of fonts, the browser will sequentially select the | |
992 * the first known/supported font. There are two types of font families the | |
993 * family-name (e.g., arial, times, courier, etc) or the generic-family (e.g., | |
994 * serif, sans-seric, etc.) | |
995 */ | |
996 final List<String> family; | |
997 | |
998 /** Font weight from 100, 200, 300, 400, 500, 600, 700, 800, 900 */ | |
999 final int weight; | |
1000 | |
1001 /** Style of a font normal, italic, oblique. */ | |
1002 final String style; | |
1003 | |
1004 /** | |
1005 * Font variant NORMAL (default) or SMALL_CAPS. Different set of font glyph | |
1006 * lower case letters designed to have to fit within the font-height and | |
1007 * weight of the corresponding lowercase letters. | |
1008 */ | |
1009 final String variant; | |
1010 | |
1011 final LineHeight lineHeight; | |
1012 | |
1013 // TODO(terry): Size and computedLineHeight are in pixels. Need to figure out | |
1014 // how to handle in other units (specified in other units) like | |
1015 // points, inches, etc. Do we have helpers like Units.Points(12) | |
1016 // where 12 is in points and that's converted to pixels? | |
1017 // TODO(terry): lineHeight is computed as 1.2 although CSS_RESET is 1.0 we | |
1018 // need to be consistent some browsers use 1 others 1.2. | |
1019 // TODO(terry): There is a school of thought "Golden Ratio Typography". | |
1020 // Where width to display the text is also important in computing the line | |
1021 // height. Classic typography suggest the ratio be 1.5. See | |
1022 // <http://www.pearsonified.com/2011/12/golden-ratio-typography.php> and | |
1023 // <http://meyerweb.com/eric/thoughts/2008/05/06/line-height-abnormal/>. | |
1024 /** | |
1025 * Create a font using [size] of font in pixels, [family] name of font(s) | |
1026 * using [FontFamily], [style] of the font using [FontStyle], [variant] using | |
1027 * [FontVariant], and [lineHeight] extra space (leading) around the font in | |
1028 * pixels, if not specified it's 1.2 the font size. | |
1029 */ | |
1030 const Font({this.size, this.family, this.weight, this.style, this.variant, | |
1031 this.lineHeight}); | |
1032 | |
1033 /** | |
1034 * Merge the two fonts and return the result. See [Style.merge] for | |
1035 * more information. | |
1036 */ | |
1037 factory Font.merge(Font a, Font b) { | |
1038 if (a == null) return b; | |
1039 if (b == null) return a; | |
1040 return new Font._merge(a, b); | |
1041 } | |
1042 | |
1043 Font._merge(Font a, Font b) | |
1044 : size = _mergeVal(a.size, b.size), | |
1045 family = _mergeVal(a.family, b.family), | |
1046 weight = _mergeVal(a.weight, b.weight), | |
1047 style = _mergeVal(a.style, b.style), | |
1048 variant = _mergeVal(a.variant, b.variant), | |
1049 lineHeight = _mergeVal(a.lineHeight, b.lineHeight); | |
1050 | |
1051 /** | |
1052 * Shorthand CSS format for font is: | |
1053 * | |
1054 * font-style font-variant font-weight font-size/line-height font-family | |
1055 * | |
1056 * The font-size and font-family values are required. If any of the other | |
1057 * values are missing the default value is used. | |
1058 */ | |
1059 String get cssExpression { | |
1060 // TODO(jimhug): include variant, style, other options | |
1061 if (weight != null) { | |
1062 // TODO(jacobr): is this really correct for lineHeight? | |
1063 if (lineHeight != null) { | |
1064 return "$weight ${size}px/$lineHeightInPixels $_fontsAsString"; | |
1065 } | |
1066 return '$weight ${size}px $_fontsAsString'; | |
1067 } | |
1068 | |
1069 return '${size}px $_fontsAsString'; | |
1070 } | |
1071 | |
1072 Font scale(num ratio) => new Font( | |
1073 size: size * ratio, | |
1074 family: family, | |
1075 weight: weight, | |
1076 style: style, | |
1077 variant: variant); | |
1078 | |
1079 /** | |
1080 * The lineHeight, provides an indirect means to specify the leading. The | |
1081 * leading is the difference between the font-size height and the (used) | |
1082 * value of line height in pixels. If lineHeight is not specified it's | |
1083 * automatically computed as 1.2 of the font size. Firefox is 1.2, Safari is | |
1084 * ~1.2, and CSS suggest a ration from 1 to 1.2 of the font-size when | |
1085 * computing line-height. The Font class constructor has the computation for | |
1086 * _lineHeight. | |
1087 */ | |
1088 num get lineHeightInPixels { | |
1089 if (lineHeight != null) { | |
1090 if (lineHeight.inPixels) { | |
1091 return lineHeight.height; | |
1092 } else { | |
1093 return (size != null) ? lineHeight.height * size : null; | |
1094 } | |
1095 } else { | |
1096 return (size != null) ? size * 1.2 : null; | |
1097 } | |
1098 } | |
1099 | |
1100 int get hashCode { | |
1101 // TODO(jimhug): Lot's of potential collisions here. List of fonts, etc. | |
1102 return size.toInt() % family[0].hashCode; | |
1103 } | |
1104 | |
1105 bool operator ==(other) { | |
1106 if (other is! Font) return false; | |
1107 Font o = other; | |
1108 return o.size == size && | |
1109 o.family == family && | |
1110 o.weight == weight && | |
1111 o.lineHeight == lineHeight && | |
1112 o.style == style && | |
1113 o.variant == variant; | |
1114 } | |
1115 | |
1116 // TODO(terry): This is fragile should probably just iterate through the list | |
1117 // of fonts construction the font-family string. | |
1118 /** Return fonts as a comma seperated list sans the square brackets. */ | |
1119 String get _fontsAsString { | |
1120 String fonts = family.toString(); | |
1121 return fonts.length > 2 ? fonts.substring(1, fonts.length - 1) : ""; | |
1122 } | |
1123 } | |
1124 | |
1125 /** | |
1126 * This class stores the sizes of the box edges in the CSS [box model][]. Each | |
1127 * edge area is placed around the sides of the content box. The innermost area | |
1128 * is the [Style.padding] area which has a background and surrounds the content. | |
1129 * The content and padding area is surrounded by the [Style.border], which | |
1130 * itself is surrounded by the transparent [Style.margin]. This box represents | |
1131 * the eges of padding, border, or margin depending on which accessor was used | |
1132 * to retrieve it. | |
1133 * | |
1134 * [box model]: https://developer.mozilla.org/en/CSS/box_model | |
1135 */ | |
1136 class BoxEdge { | |
1137 /** The size of the left edge, or null if the style has no edge. */ | |
1138 final num left; | |
1139 | |
1140 /** The size of the top edge, or null if the style has no edge. */ | |
1141 final num top; | |
1142 | |
1143 /** The size of the right edge, or null if the style has no edge. */ | |
1144 final num right; | |
1145 | |
1146 /** The size of the bottom edge, or null if the style has no edge. */ | |
1147 final num bottom; | |
1148 | |
1149 /** | |
1150 * Creates a box edge with the specified [left], [top], [right], and | |
1151 * [bottom] width. | |
1152 */ | |
1153 const BoxEdge([this.left, this.top, this.right, this.bottom]); | |
1154 | |
1155 /** | |
1156 * Creates a box edge with the specified [top], [right], [bottom], and | |
1157 * [left] width. This matches the typical CSS order: | |
1158 * <https://developer.mozilla.org/en/CSS/margin> | |
1159 * <https://developer.mozilla.org/en/CSS/border-width> | |
1160 * <https://developer.mozilla.org/en/CSS/padding>. | |
1161 */ | |
1162 const BoxEdge.clockwiseFromTop(this.top, this.right, this.bottom, this.left); | |
1163 | |
1164 /** | |
1165 * This is a helper to creates a box edge with the same [left], [top] | |
1166 * [right], and [bottom] widths. | |
1167 */ | |
1168 const BoxEdge.uniform(num size) | |
1169 : top = size, | |
1170 left = size, | |
1171 bottom = size, | |
1172 right = size; | |
1173 | |
1174 /** | |
1175 * Takes a possibly null box edge, with possibly null metrics, and fills | |
1176 * them in with 0 instead. | |
1177 */ | |
1178 factory BoxEdge.nonNull(BoxEdge other) { | |
1179 if (other == null) return const BoxEdge(0, 0, 0, 0); | |
1180 num left = other.left; | |
1181 num top = other.top; | |
1182 num right = other.right; | |
1183 num bottom = other.bottom; | |
1184 bool make = false; | |
1185 if (left == null) { | |
1186 make = true; | |
1187 left = 0; | |
1188 } | |
1189 if (top == null) { | |
1190 make = true; | |
1191 top = 0; | |
1192 } | |
1193 if (right == null) { | |
1194 make = true; | |
1195 right = 0; | |
1196 } | |
1197 if (bottom == null) { | |
1198 make = true; | |
1199 bottom = 0; | |
1200 } | |
1201 return make ? new BoxEdge(left, top, right, bottom) : other; | |
1202 } | |
1203 | |
1204 /** | |
1205 * Merge the two box edge sizes and return the result. See [Style.merge] for | |
1206 * more information. | |
1207 */ | |
1208 factory BoxEdge.merge(BoxEdge x, BoxEdge y) { | |
1209 if (x == null) return y; | |
1210 if (y == null) return x; | |
1211 return new BoxEdge._merge(x, y); | |
1212 } | |
1213 | |
1214 BoxEdge._merge(BoxEdge x, BoxEdge y) | |
1215 : left = _mergeVal(x.left, y.left), | |
1216 top = _mergeVal(x.top, y.top), | |
1217 right = _mergeVal(x.right, y.right), | |
1218 bottom = _mergeVal(x.bottom, y.bottom); | |
1219 | |
1220 /** | |
1221 * The total size of the horizontal edges. Equal to [left] + [right], where | |
1222 * null is interpreted as 0px. | |
1223 */ | |
1224 num get width => (left != null ? left : 0) + (right != null ? right : 0); | |
1225 | |
1226 /** | |
1227 * The total size of the vertical edges. Equal to [top] + [bottom], where | |
1228 * null is interpreted as 0px. | |
1229 */ | |
1230 num get height => (top != null ? top : 0) + (bottom != null ? bottom : 0); | |
1231 } | |
1232 | |
1233 _mergeVal(x, y) => y != null ? y : x; | |
OLD | NEW |