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