blob: cf614fe79e41aeadf39502d36c3cc438fa218d1a [file] [log] [blame]
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
/// Representations of CSS styles.
part of csslib.parser;
// TODO(terry): Prune down this file we do need some of the code in this file
// for darker, lighter, how to represent a Font, etc but alot of
// the complexity can be removed.
// See https://github.com/dart-lang/csslib/issues/7
/// Base for all style properties (e.g., Color, Font, Border, Margin, etc.)
abstract class _StyleProperty {
/// Returns the expression part of a CSS declaration. Declaration is:
///
/// property:expression;
///
/// E.g., if property is color then expression could be rgba(255,255,0) the
/// CSS declaration would be 'color:rgba(255,255,0);'.
///
/// then _cssExpression would return 'rgba(255,255,0)'. See
/// <http://www.w3.org/TR/CSS21/grammar.html>
String get cssExpression;
}
/// Base interface for Color, HSL and RGB.
abstract class ColorBase {
/// Canonical form for color #rrggbb with alpha blending (0.0 == full
/// transparency and 1.0 == fully opaque). If _argb length is 6 it's an
/// rrggbb otherwise it's aarrggbb.
String toHexArgbString();
/// Return argb as a value (int).
int get argbValue;
}
/// General purpse Color class. Represent a color as an ARGB value that can be
/// converted to and from num, hex string, hsl, hsla, rgb, rgba and SVG pre-
/// defined color constant.
class Color implements _StyleProperty, ColorBase {
// If _argb length is 6 it's an rrggbb otherwise it's aarrggbb.
final String _argb;
// TODO(terry): Look at reducing Rgba and Hsla classes as factories for
// converting from Color to an Rgba or Hsla for reading only.
// Usefulness of creating an Rgba or Hsla is limited.
/// Create a color with an integer representing the rgb value of red, green,
/// and blue. The value 0xffffff is the color white #ffffff (CSS style).
/// The [rgb] value of 0xffd700 would map to #ffd700 or the constant
/// Color.gold, where ff is red intensity, d7 is green intensity, and 00 is
/// blue intensity.
Color(int rgb, [num alpha]) : this._argb = Color._rgbToArgbString(rgb, alpha);
/// RGB takes three values. The [red], [green], and [blue] parameters are
/// the intensity of those components where '0' is the least and '256' is the
/// greatest.
///
/// If [alpha] is provided, it is the level of translucency which ranges from
/// '0' (completely transparent) to '1.0' (completely opaque). It will
/// internally be mapped to an int between '0' and '255' like the other color
/// components.
Color.createRgba(int red, int green, int blue, [num alpha])
: this._argb = Color.convertToHexString(
Color._clamp(red, 0, 255),
Color._clamp(green, 0, 255),
Color._clamp(blue, 0, 255),
alpha != null ? Color._clamp(alpha, 0, 1) : alpha);
/// Creates a new color from a CSS color string. For more information, see
/// <https://developer.mozilla.org/en/CSS/color>.
Color.css(String color) : this._argb = Color._convertCssToArgb(color);
// TODO(jmesserly): I found the use of percents a bit suprising.
/// HSL takes three values. The [hueDegree] degree on the color wheel; '0' is
/// the least and '100' is the greatest. The value '0' or '360' is red, '120'
/// is green, '240' is blue. Numbers in between reflect different shades.
/// The [saturationPercent] percentage; where'0' is the least and '100' is the
/// greatest (100 represents full color). The [lightnessPercent] percentage;
/// where'0' is the least and '100' is the greatest. The value 0 is dark or
/// black, 100 is light or white and 50 is a medium lightness.
///
/// If [alpha] is provided, it is the level of translucency which ranges from
/// '0' (completely transparent foreground) to '1.0' (completely opaque
/// foreground).
Color.createHsla(num hueDegree, num saturationPercent, num lightnessPercent,
[num alpha])
: this._argb = new Hsla(
Color._clamp(hueDegree, 0, 360) / 360,
Color._clamp(saturationPercent, 0, 100) / 100,
Color._clamp(lightnessPercent, 0, 100) / 100,
alpha != null ? Color._clamp(alpha, 0, 1) : alpha)
.toHexArgbString();
/// The hslaRaw takes three values. The [hue] degree on the color wheel; '0'
/// is the least and '1' is the greatest. The value '0' or '1' is red, the
/// ratio of 120/360 is green, and the ratio of 240/360 is blue. Numbers in
/// between reflect different shades. The [saturation] is a percentage; '0'
/// is the least and '1' is the greatest. The value of '1' is equivalent to
/// 100% (full colour). The [lightness] is a percentage; '0' is the least and
/// '1' is the greatest. The value of '0' is dark (black), the value of '1'
/// is light (white), and the value of '.50' is a medium lightness.
///
/// The fourth optional parameter is:
/// [alpha] level of translucency range of values is 0..1, zero is a
/// completely transparent foreground and 1 is a completely
/// opaque foreground.
Color.hslaRaw(num hue, num saturation, num lightness, [num alpha])
: this._argb = new Hsla(
Color._clamp(hue, 0, 1),
Color._clamp(saturation, 0, 1),
Color._clamp(lightness, 0, 1),
alpha != null ? Color._clamp(alpha, 0, 1) : alpha)
.toHexArgbString();
/// Generate a real constant for pre-defined colors (no leading #).
const Color.hex(this._argb);
// TODO(jmesserly): this is needed by the example so leave it exposed for now.
String toString() => cssExpression;
// TODO(terry): Regardless of how color is set (rgb, num, css or hsl) we'll
// always return a rgb or rgba loses fidelity when debugging in
// CSS if user uses hsl and would like to edit as hsl, etc. If
// this is an issue we should keep the original value and not re-
// create the CSS from the normalized value.
String get cssExpression {
if (_argb.length == 6) {
return "#$_argb"; // RGB only, no alpha blending.
} else {
num alpha = Color.hexToInt(_argb.substring(0, 2));
String a = (alpha / 255).toStringAsPrecision(2);
int r = Color.hexToInt(_argb.substring(2, 4));
int g = Color.hexToInt(_argb.substring(4, 6));
int b = Color.hexToInt(_argb.substring(6, 8));
return "rgba($r,$g,$b,$a)";
}
}
Rgba get rgba {
int nextIndex = 0;
num a;
if (_argb.length == 8) {
// Get alpha blending value 0..255
int alpha = Color.hexToInt(_argb.substring(nextIndex, nextIndex + 2));
// Convert to value from 0..1
a = double.parse((alpha / 255).toStringAsPrecision(2));
nextIndex += 2;
}
int r = Color.hexToInt(_argb.substring(nextIndex, nextIndex + 2));
nextIndex += 2;
int g = Color.hexToInt(_argb.substring(nextIndex, nextIndex + 2));
nextIndex += 2;
int b = Color.hexToInt(_argb.substring(nextIndex, nextIndex + 2));
return new Rgba(r, g, b, a);
}
Hsla get hsla => new Hsla.fromRgba(rgba);
int get argbValue => Color.hexToInt(_argb);
bool operator ==(other) => Color.equal(this, other);
String toHexArgbString() => _argb;
Color darker(num amount) {
Rgba newRgba = Color._createNewTintShadeFromRgba(rgba, -amount);
return new Color.hex("${newRgba.toHexArgbString()}");
}
Color lighter(num amount) {
Rgba newRgba = Color._createNewTintShadeFromRgba(rgba, amount);
return new Color.hex("${newRgba.toHexArgbString()}");
}
static bool equal(ColorBase curr, other) {
if (other is Color) {
Color o = other;
return o.toHexArgbString() == curr.toHexArgbString();
} else if (other is Rgba) {
Rgba rgb = other;
return rgb.toHexArgbString() == curr.toHexArgbString();
} else if (other is Hsla) {
Hsla hsla = other;
return hsla.toHexArgbString() == curr.toHexArgbString();
} else {
return false;
}
}
int get hashCode => _argb.hashCode;
// Conversion routines:
static String _rgbToArgbString(int rgba, num alpha) {
int a;
// If alpha is defined then adjust from 0..1 to 0..255 value, if not set
// then a is left as undefined and passed to convertToHexString.
if (alpha != null) {
a = (Color._clamp(alpha, 0, 1) * 255).round();
}
int r = (rgba & 0xff0000) >> 0x10;
int g = (rgba & 0xff00) >> 8;
int b = rgba & 0xff;
return Color.convertToHexString(r, g, b, a);
}
static const int _rgbCss = 1;
static const int _rgbaCss = 2;
static const int _hslCss = 3;
static const int _hslaCss = 4;
/// Parse CSS expressions of the from #rgb, rgb(r,g,b), rgba(r,g,b,a),
/// hsl(h,s,l), hsla(h,s,l,a) and SVG colors (e.g., darkSlateblue, etc.) and
/// convert to argb.
static String _convertCssToArgb(String value) {
// TODO(terry): Better parser/regex for converting CSS properties.
String color = value.trim().replaceAll("\\s", "");
if (color[0] == '#') {
String v = color.substring(1);
Color.hexToInt(v); // Valid hexadecimal, throws if not.
return v;
} else if (color.length > 0 && color[color.length - 1] == ')') {
int type;
if (color.indexOf("rgb(") == 0 || color.indexOf("RGB(") == 0) {
color = color.substring(4);
type = _rgbCss;
} else if (color.indexOf("rgba(") == 0 || color.indexOf("RGBA(") == 0) {
type = _rgbaCss;
color = color.substring(5);
} else if (color.indexOf("hsl(") == 0 || color.indexOf("HSL(") == 0) {
type = _hslCss;
color = color.substring(4);
} else if (color.indexOf("hsla(") == 0 || color.indexOf("HSLA(") == 0) {
type = _hslaCss;
color = color.substring(5);
} else {
throw new UnsupportedError('CSS property not implemented');
}
color = color.substring(0, color.length - 1); // Strip close paren.
var args = <num>[];
List<String> params = color.split(",");
for (String param in params) {
args.add(double.parse(param));
}
switch (type) {
case _rgbCss:
return Color.convertToHexString(args[0], args[1], args[2]);
case _rgbaCss:
return Color.convertToHexString(args[0], args[1], args[2], args[3]);
case _hslCss:
return new Hsla(args[0], args[1], args[2]).toHexArgbString();
case _hslaCss:
return new Hsla(args[0], args[1], args[2], args[3]).toHexArgbString();
default:
// Type not defined UnsupportedOperationException should have thrown.
assert(false);
break;
}
}
return null;
}
static int hexToInt(String hex) => int.parse(hex, radix: 16);
static String convertToHexString(int r, int g, int b, [num a]) {
String rHex = Color._numAs2DigitHex(Color._clamp(r, 0, 255));
String gHex = Color._numAs2DigitHex(Color._clamp(g, 0, 255));
String bHex = Color._numAs2DigitHex(Color._clamp(b, 0, 255));
String aHex = (a != null)
? Color._numAs2DigitHex((Color._clamp(a, 0, 1) * 255).round())
: "";
// TODO(terry) 15.toRadixString(16) return 'F' on Dartium not f as in JS.
// bug: <http://code.google.com/p/dart/issues/detail?id=2670>
return "$aHex$rHex$gHex$bHex".toLowerCase();
}
static String _numAs2DigitHex(num v) {
// TODO(terry): v.toInt().toRadixString instead of v.toRadixString
// Bug <http://code.google.com/p/dart/issues/detail?id=2671>.
String hex = v.toInt().toRadixString(16);
if (hex.length == 1) {
hex = "0${hex}";
}
return hex;
}
static num _clamp(num value, num min, num max) =>
math.max(math.min(max, value), min);
/// Change the tint (make color lighter) or shade (make color darker) of all
/// parts of [rgba] (r, g and b). The [amount] is percentage darker between
/// -1 to 0 for darker and 0 to 1 for lighter; '0' is no change. The [amount]
/// will darken or lighten the rgb values; it will not change the alpha value.
/// If [amount] is outside of the value -1 to +1 then [amount] is changed to
/// either the min or max direction -1 or 1.
///
/// Darker will approach the color #000000 (black) and lighter will approach
/// the color #ffffff (white).
static Rgba _createNewTintShadeFromRgba(Rgba rgba, num amount) {
int r, g, b;
num tintShade = Color._clamp(amount, -1, 1);
if (amount < 0 && rgba.r == 255 && rgba.g == 255 && rgba.b == 255) {
// TODO(terry): See TODO in _changeTintShadeColor; eliminate this test
// by converting to HSL and adjust lightness although this
// is fastest lighter/darker algorithm.
// Darkening white special handling.
r = Color._clamp((255 + (255 * tintShade)).round().toInt(), 0, 255);
g = Color._clamp((255 + (255 * tintShade)).round().toInt(), 0, 255);
b = Color._clamp((255 + (255 * tintShade)).round().toInt(), 0, 255);
} else {
// All other colors then darkening white go here.
r = Color._changeTintShadeColor(rgba.r, tintShade).round().toInt();
g = Color._changeTintShadeColor(rgba.g, tintShade).round().toInt();
b = Color._changeTintShadeColor(rgba.b, tintShade).round().toInt();
}
return new Rgba(r, g, b, rgba.a);
}
// TODO(terry): This does an okay lighter/darker; better would be convert to
// HSL then change the lightness.
/// The parameter [v] is the color to change (r, g, or b) in the range '0' to
/// '255'. The parameter [delta] is a number between '-1' and '1'. A value
/// between '-1' and '0' is darker and a value between '0' and '1' is lighter
/// ('0' imples no change).
static num _changeTintShadeColor(num v, num delta) =>
Color._clamp(((1 - delta) * v + (delta * 255)).round(), 0, 255);
// Predefined CSS colors see <http://www.w3.org/TR/css3-color/>
static final Color transparent = const Color.hex("00ffffff"); // Alpha 0.0
static final Color aliceBlue = const Color.hex("0f08ff");
static final Color antiqueWhite = const Color.hex("0faebd7");
static final Color aqua = const Color.hex("00ffff");
static final Color aquaMarine = const Color.hex("7fffd4");
static final Color azure = const Color.hex("f0ffff");
static final Color beige = const Color.hex("f5f5dc");
static final Color bisque = const Color.hex("ffe4c4");
static final Color black = const Color.hex("000000");
static final Color blanchedAlmond = const Color.hex("ffebcd");
static final Color blue = const Color.hex("0000ff");
static final Color blueViolet = const Color.hex("8a2be2");
static final Color brown = const Color.hex("a52a2a");
static final Color burlyWood = const Color.hex("deb887");
static final Color cadetBlue = const Color.hex("5f9ea0");
static final Color chartreuse = const Color.hex("7fff00");
static final Color chocolate = const Color.hex("d2691e");
static final Color coral = const Color.hex("ff7f50");
static final Color cornFlowerBlue = const Color.hex("6495ed");
static final Color cornSilk = const Color.hex("fff8dc");
static final Color crimson = const Color.hex("dc143c");
static final Color cyan = const Color.hex("00ffff");
static final Color darkBlue = const Color.hex("00008b");
static final Color darkCyan = const Color.hex("008b8b");
static final Color darkGoldenRod = const Color.hex("b8860b");
static final Color darkGray = const Color.hex("a9a9a9");
static final Color darkGreen = const Color.hex("006400");
static final Color darkGrey = const Color.hex("a9a9a9");
static final Color darkKhaki = const Color.hex("bdb76b");
static final Color darkMagenta = const Color.hex("8b008b");
static final Color darkOliveGreen = const Color.hex("556b2f");
static final Color darkOrange = const Color.hex("ff8c00");
static final Color darkOrchid = const Color.hex("9932cc");
static final Color darkRed = const Color.hex("8b0000");
static final Color darkSalmon = const Color.hex("e9967a");
static final Color darkSeaGreen = const Color.hex("8fbc8f");
static final Color darkSlateBlue = const Color.hex("483d8b");
static final Color darkSlateGray = const Color.hex("2f4f4f");
static final Color darkSlateGrey = const Color.hex("2f4f4f");
static final Color darkTurquoise = const Color.hex("00ced1");
static final Color darkViolet = const Color.hex("9400d3");
static final Color deepPink = const Color.hex("ff1493");
static final Color deepSkyBlue = const Color.hex("00bfff");
static final Color dimGray = const Color.hex("696969");
static final Color dimGrey = const Color.hex("696969");
static final Color dodgerBlue = const Color.hex("1e90ff");
static final Color fireBrick = const Color.hex("b22222");
static final Color floralWhite = const Color.hex("fffaf0");
static final Color forestGreen = const Color.hex("228b22");
static final Color fuchsia = const Color.hex("ff00ff");
static final Color gainsboro = const Color.hex("dcdcdc");
static final Color ghostWhite = const Color.hex("f8f8ff");
static final Color gold = const Color.hex("ffd700");
static final Color goldenRod = const Color.hex("daa520");
static final Color gray = const Color.hex("808080");
static final Color green = const Color.hex("008000");
static final Color greenYellow = const Color.hex("adff2f");
static final Color grey = const Color.hex("808080");
static final Color honeydew = const Color.hex("f0fff0");
static final Color hotPink = const Color.hex("ff69b4");
static final Color indianRed = const Color.hex("cd5c5c");
static final Color indigo = const Color.hex("4b0082");
static final Color ivory = const Color.hex("fffff0");
static final Color khaki = const Color.hex("f0e68c");
static final Color lavender = const Color.hex("e6e6fa");
static final Color lavenderBlush = const Color.hex("fff0f5");
static final Color lawnGreen = const Color.hex("7cfc00");
static final Color lemonChiffon = const Color.hex("fffacd");
static final Color lightBlue = const Color.hex("add8e6");
static final Color lightCoral = const Color.hex("f08080");
static final Color lightCyan = const Color.hex("e0ffff");
static final Color lightGoldenRodYellow = const Color.hex("fafad2");
static final Color lightGray = const Color.hex("d3d3d3");
static final Color lightGreen = const Color.hex("90ee90");
static final Color lightGrey = const Color.hex("d3d3d3");
static final Color lightPink = const Color.hex("ffb6c1");
static final Color lightSalmon = const Color.hex("ffa07a");
static final Color lightSeaGreen = const Color.hex("20b2aa");
static final Color lightSkyBlue = const Color.hex("87cefa");
static final Color lightSlateGray = const Color.hex("778899");
static final Color lightSlateGrey = const Color.hex("778899");
static final Color lightSteelBlue = const Color.hex("b0c4de");
static final Color lightYellow = const Color.hex("ffffe0");
static final Color lime = const Color.hex("00ff00");
static final Color limeGreen = const Color.hex("32cd32");
static final Color linen = const Color.hex("faf0e6");
static final Color magenta = const Color.hex("ff00ff");
static final Color maroon = const Color.hex("800000");
static final Color mediumAquaMarine = const Color.hex("66cdaa");
static final Color mediumBlue = const Color.hex("0000cd");
static final Color mediumOrchid = const Color.hex("ba55d3");
static final Color mediumPurple = const Color.hex("9370db");
static final Color mediumSeaGreen = const Color.hex("3cb371");
static final Color mediumSlateBlue = const Color.hex("7b68ee");
static final Color mediumSpringGreen = const Color.hex("00fa9a");
static final Color mediumTurquoise = const Color.hex("48d1cc");
static final Color mediumVioletRed = const Color.hex("c71585");
static final Color midnightBlue = const Color.hex("191970");
static final Color mintCream = const Color.hex("f5fffa");
static final Color mistyRose = const Color.hex("ffe4e1");
static final Color moccasin = const Color.hex("ffe4b5");
static final Color navajoWhite = const Color.hex("ffdead");
static final Color navy = const Color.hex("000080");
static final Color oldLace = const Color.hex("fdf5e6");
static final Color olive = const Color.hex("808000");
static final Color oliveDrab = const Color.hex("6b8e23");
static final Color orange = const Color.hex("ffa500");
static final Color orangeRed = const Color.hex("ff4500");
static final Color orchid = const Color.hex("da70d6");
static final Color paleGoldenRod = const Color.hex("eee8aa");
static final Color paleGreen = const Color.hex("98fb98");
static final Color paleTurquoise = const Color.hex("afeeee");
static final Color paleVioletRed = const Color.hex("db7093");
static final Color papayaWhip = const Color.hex("ffefd5");
static final Color peachPuff = const Color.hex("ffdab9");
static final Color peru = const Color.hex("cd85ef");
static final Color pink = const Color.hex("ffc0cb");
static final Color plum = const Color.hex("dda0dd");
static final Color powderBlue = const Color.hex("b0e0e6");
static final Color purple = const Color.hex("800080");
static final Color red = const Color.hex("ff0000");
static final Color rosyBrown = const Color.hex("bc8f8f");
static final Color royalBlue = const Color.hex("4169e1");
static final Color saddleBrown = const Color.hex("8b4513");
static final Color salmon = const Color.hex("fa8072");
static final Color sandyBrown = const Color.hex("f4a460");
static final Color seaGreen = const Color.hex("2e8b57");
static final Color seashell = const Color.hex("fff5ee");
static final Color sienna = const Color.hex("a0522d");
static final Color silver = const Color.hex("c0c0c0");
static final Color skyBlue = const Color.hex("87ceeb");
static final Color slateBlue = const Color.hex("6a5acd");
static final Color slateGray = const Color.hex("708090");
static final Color slateGrey = const Color.hex("708090");
static final Color snow = const Color.hex("fffafa");
static final Color springGreen = const Color.hex("00ff7f");
static final Color steelBlue = const Color.hex("4682b4");
static final Color tan = const Color.hex("d2b48c");
static final Color teal = const Color.hex("008080");
static final Color thistle = const Color.hex("d8bfd8");
static final Color tomato = const Color.hex("ff6347");
static final Color turquoise = const Color.hex("40e0d0");
static final Color violet = const Color.hex("ee82ee");
static final Color wheat = const Color.hex("f5deb3");
static final Color white = const Color.hex("ffffff");
static final Color whiteSmoke = const Color.hex("f5f5f5");
static final Color yellow = const Color.hex("ffff00");
static final Color yellowGreen = const Color.hex("9acd32");
}
/// Rgba class for users that want to interact with a color as a RGBA value.
class Rgba implements _StyleProperty, ColorBase {
// TODO(terry): Consider consolidating rgba to a single 32-bit int, make sure
// it works under JS and Dart VM.
final int r;
final int g;
final int b;
final num a;
Rgba(int red, int green, int blue, [num alpha])
: this.r = Color._clamp(red, 0, 255),
this.g = Color._clamp(green, 0, 255),
this.b = Color._clamp(blue, 0, 255),
this.a = (alpha != null) ? Color._clamp(alpha, 0, 1) : alpha;
factory Rgba.fromString(String hexValue) =>
new Color.css("#${Color._convertCssToArgb(hexValue)}").rgba;
factory Rgba.fromColor(Color color) => color.rgba;
factory Rgba.fromArgbValue(num value) {
return new Rgba(
((value.toInt() & 0xff000000) >> 0x18), // a
((value.toInt() & 0xff0000) >> 0x10), // r
((value.toInt() & 0xff00) >> 8), // g
((value.toInt() & 0xff))); // b
}
factory Rgba.fromHsla(Hsla hsla) {
// Convert to Rgba.
// See site <http://easyrgb.com/index.php?X=MATH> for good documentation
// and color conversion routines.
num h = hsla.hue;
num s = hsla.saturation;
num l = hsla.lightness;
num a = hsla.alpha;
int r;
int g;
int b;
if (s == 0) {
r = (l * 255).round().toInt();
g = r;
b = r;
} else {
num var2;
if (l < 0.5) {
var2 = l * (1 + s);
} else {
var2 = (l + s) - (s * l);
}
num var1 = 2 * l - var2;
r = (255 * Rgba._hueToRGB(var1, var2, h + (1 / 3))).round().toInt();
g = (255 * Rgba._hueToRGB(var1, var2, h)).round().toInt();
b = (255 * Rgba._hueToRGB(var1, var2, h - (1 / 3))).round().toInt();
}
return new Rgba(r, g, b, a);
}
static num _hueToRGB(num v1, num v2, num vH) {
if (vH < 0) {
vH += 1;
}
if (vH > 1) {
vH -= 1;
}
if ((6 * vH) < 1) {
return (v1 + (v2 - v1) * 6 * vH);
}
if ((2 * vH) < 1) {
return v2;
}
if ((3 * vH) < 2) {
return (v1 + (v2 - v1) * ((2 / 3 - vH) * 6));
}
return v1;
}
bool operator ==(other) => Color.equal(this, other);
String get cssExpression {
if (a == null) {
return "#${Color.convertToHexString(r, g, b)}";
} else {
return "rgba($r,$g,$b,$a)";
}
}
String toHexArgbString() => Color.convertToHexString(r, g, b, a);
int get argbValue {
int value = 0;
if (a != null) {
value = (a.toInt() << 0x18);
}
value += (r << 0x10);
value += (g << 0x08);
value += b;
return value;
}
Color get color => new Color.createRgba(r, g, b, a);
Hsla get hsla => new Hsla.fromRgba(this);
Rgba darker(num amount) => Color._createNewTintShadeFromRgba(this, -amount);
Rgba lighter(num amount) => Color._createNewTintShadeFromRgba(this, amount);
int get hashCode => toHexArgbString().hashCode;
}
/// Hsl class support to interact with a color as a hsl with hue, saturation,
/// and lightness with optional alpha blending. The hue is a ratio of 360
/// degrees 360° = 1 or 0, (1° == (1/360)), saturation and lightness is a 0..1
/// fraction (1 == 100%) and alpha is a 0..1 fraction.
class Hsla implements _StyleProperty, ColorBase {
final num _h; // Value from 0..1
final num _s; // Value from 0..1
final num _l; // Value from 0..1
final num _a; // Value from 0..1
/// [hue] is a 0..1 fraction of 360 degrees (360 == 0).
/// [saturation] is a 0..1 fraction (100% == 1).
/// [lightness] is a 0..1 fraction (100% == 1).
/// [alpha] is a 0..1 fraction, alpha blending between 0..1, 1 == 100% opaque.
Hsla(num hue, num saturation, num lightness, [num alpha])
: this._h = (hue == 1) ? 0 : Color._clamp(hue, 0, 1),
this._s = Color._clamp(saturation, 0, 1),
this._l = Color._clamp(lightness, 0, 1),
this._a = (alpha != null) ? Color._clamp(alpha, 0, 1) : alpha;
factory Hsla.fromString(String hexValue) {
Rgba rgba = new Color.css("#${Color._convertCssToArgb(hexValue)}").rgba;
return _createFromRgba(rgba.r, rgba.g, rgba.b, rgba.a);
}
factory Hsla.fromColor(Color color) {
Rgba rgba = color.rgba;
return _createFromRgba(rgba.r, rgba.g, rgba.b, rgba.a);
}
factory Hsla.fromArgbValue(num value) {
num a = (value.toInt() & 0xff000000) >> 0x18;
int r = (value.toInt() & 0xff0000) >> 0x10;
int g = (value.toInt() & 0xff00) >> 8;
int b = value.toInt() & 0xff;
// Convert alpha to 0..1 from (0..255).
if (a != null) {
a = double.parse((a / 255).toStringAsPrecision(2));
}
return _createFromRgba(r, g, b, a);
}
factory Hsla.fromRgba(Rgba rgba) =>
_createFromRgba(rgba.r, rgba.g, rgba.b, rgba.a);
static Hsla _createFromRgba(num r, num g, num b, num a) {
// Convert RGB to hsl.
// See site <http://easyrgb.com/index.php?X=MATH> for good documentation
// and color conversion routines.
r /= 255;
g /= 255;
b /= 255;
// Hue, saturation and lightness.
num h;
num s;
num l;
num minRgb = math.min(r, math.min(g, b));
num maxRgb = math.max(r, math.max(g, b));
l = (maxRgb + minRgb) / 2;
if (l <= 0) {
return new Hsla(0, 0, l); // Black;
}
num vm = maxRgb - minRgb;
s = vm;
if (s > 0) {
s /= (l < 0.5) ? (maxRgb + minRgb) : (2 - maxRgb - minRgb);
} else {
return new Hsla(0, 0, l); // White
}
num r2, g2, b2;
r2 = (maxRgb - r) / vm;
g2 = (maxRgb - g) / vm;
b2 = (maxRgb - b) / vm;
if (r == maxRgb) {
h = (g == minRgb) ? 5.0 + b2 : 1 - g2;
} else if (g == maxRgb) {
h = (b == minRgb) ? 1 + r2 : 3 - b2;
} else {
h = (r == minRgb) ? 3 + g2 : 5 - r2;
}
h /= 6;
return new Hsla(h, s, l, a);
}
/// Returns 0..1 fraction (ratio of 360°, e.g. 1° == 1/360).
num get hue => _h;
/// Returns 0..1 fraction (1 == 100%)
num get saturation => _s;
/// Returns 0..1 fraction (1 == 100%).
num get lightness => _l;
/// Returns number as degrees 0..360.
num get hueDegrees => (_h * 360).round();
/// Returns number as percentage 0..100
num get saturationPercentage => (_s * 100).round();
/// Returns number as percentage 0..100.
num get lightnessPercentage => (_l * 100).round();
/// Returns number as 0..1
num get alpha => _a;
bool operator ==(other) => Color.equal(this, other);
String get cssExpression => (_a == null)
? "hsl($hueDegrees,$saturationPercentage,$lightnessPercentage)"
: "hsla($hueDegrees,$saturationPercentage,$lightnessPercentage,$_a)";
String toHexArgbString() => new Rgba.fromHsla(this).toHexArgbString();
int get argbValue => Color.hexToInt(this.toHexArgbString());
Color get color => new Color.createHsla(_h, _s, _l, _a);
Rgba get rgba => new Rgba.fromHsla(this);
Hsla darker(num amount) =>
new Hsla.fromRgba(new Rgba.fromHsla(this).darker(amount));
Hsla lighter(num amount) =>
new Hsla.fromRgba(new Rgba.fromHsla(this).lighter(amount));
int get hashCode => toHexArgbString().hashCode;
}
/// X,Y position.
class PointXY implements _StyleProperty {
final num x, y;
const PointXY(this.x, this.y);
String get cssExpression {
// TODO(terry): TBD
return null;
}
}
// TODO(terry): Implement style and color.
/// Supports border for measuring with layout.
class Border implements _StyleProperty {
final int top, left, bottom, right;
// TODO(terry): Just like CSS, 1-arg -> set all properties, 2-args -> top and
// bottom are first arg, left and right are second, 3-args, and
// 4-args -> tlbr or trbl.
const Border([this.top, this.left, this.bottom, this.right]);
// TODO(terry): Consider using Size or width and height.
Border.uniform(num amount)
: top = amount,
left = amount,
bottom = amount,
right = amount;
int get width => left + right;
int get height => top + bottom;
String get cssExpression {
return (top == left && bottom == right && top == right)
? "${left}px"
: "${top != null ? '$top' : '0'}px "
"${right != null ? '$right' : '0'}px "
"${bottom != null ? '$bottom' : '0'}px "
"${left != null ? '$left' : '0'}px";
}
}
/// Font style constants.
class FontStyle {
/// Font style [normal] default.
static const String normal = "normal";
/// Font style [italic] use explicity crafted italic font otherwise inclined
/// on the fly like oblique.
static const String italic = "italic";
/// Font style [oblique] is rarely used. The normal style of a font is
/// inclined on the fly to the right by 8-12 degrees.
static const String oblique = "oblique";
}
/// Font variant constants.
class FontVariant {
/// Font style [normal] default.
static const String normal = "normal";
/// Font variant [smallCaps].
static const String smallCaps = "small-caps";
}
/// Font weight constants values 100, 200, 300, 400, 500, 600, 700, 800, 900.
class FontWeight {
/// Font weight normal [default]
static const int normal = 400;
/// Font weight bold
static const int bold = 700;
static const int wt100 = 100;
static const int wt200 = 200;
static const int wt300 = 300;
static const int wt400 = 400;
static const int wt500 = 500;
static const int wt600 = 600;
static const int wt700 = 700;
static const int wt800 = 800;
static const int wt900 = 900;
}
/// Generic font family names.
class FontGeneric {
/// Generic family sans-serif font (w/o serifs).
static const String sansSerif = "sans-serif";
/// Generic family serif font.
static const String serif = "serif";
/// Generic family fixed-width font.
static const monospace = "monospace";
/// Generic family emulate handwriting font.
static const String cursive = "cursive";
/// Generic family decorative font.
static const String fantasy = "fantasy";
}
/// List of most common font families across different platforms. Use the
/// collection names in the Font class (e.g., Font.SANS_SERIF, Font.FONT_SERIF,
/// Font.MONOSPACE, Font.CURSIVE or Font.FANTASY). These work best on all
/// platforms using the fonts that best match availability on each platform.
/// See <http://www.angelfire.com/al4/rcollins/style/fonts.html> for a good
/// description of fonts available between platforms and browsers.
class FontFamily {
/// Sans-Serif font for Windows similar to Helvetica on Mac bold/italic.
static const String arial = "arial";
/// Sans-Serif font for Windows less common already bolded.
static const String arialBlack = "arial black";
/// Sans-Serif font for Mac since 1984, similar to Arial/Helvetica.
static const String geneva = "geneva";
/// Sans-Serif font for Windows most readable sans-serif font for displays.
static const String verdana = "verdana";
/// Sans-Serif font for Mac since 1984 is identical to Arial.
static const String helvetica = "helvetica";
/// Serif font for Windows traditional font with “old-style” numerals.
static const String georgia = "georgia";
/// Serif font for Mac. PCs may have the non-scalable Times use Times New
/// Roman instead. Times is more compact than Times New Roman.
static const String times = "times";
/// Serif font for Windows most common serif font and default serif font for
/// most browsers.
static const String timesNewRoman = "times new roman";
/// Monospace font for Mac/Windows most common. Scalable on Mac not scalable
/// on Windows.
static const String courier = "courier";
/// Monospace font for Mac/Windows scalable on both platforms.
static const String courierNew = "courier new";
/// Cursive font for Windows and default cursive font for IE.
static const String comicSansMs = "comic sans ms";
/// Cursive font for Mac on Macs 2000 and newer.
static const String textile = "textile";
/// Cursive font for older Macs.
static const String appleChancery = "apple chancery";
/// Cursive font for some PCs.
static const String zaphChancery = "zaph chancery";
/// Fantasy font on most Mac/Windows/Linux platforms.
static const String impact = "impact";
/// Fantasy font for Windows.
static const String webdings = "webdings";
}
class LineHeight {
final num height;
final bool inPixels;
const LineHeight(this.height, {this.inPixels: true});
}
// TODO(terry): Support @font-face fule.
/// Font style support for size, family, weight, style, variant, and lineheight.
class Font implements _StyleProperty {
/// Collection of most common sans-serif fonts in order.
static const List<String> sansSerif = const [
FontFamily.arial,
FontFamily.verdana,
FontFamily.geneva,
FontFamily.helvetica,
FontGeneric.sansSerif
];
/// Collection of most common serif fonts in order.
static const List<String> serif = const [
FontFamily.georgia,
FontFamily.timesNewRoman,
FontFamily.times,
FontGeneric.serif
];
/// Collection of most common monospace fonts in order.
static const List<String> monospace = const [
FontFamily.courierNew,
FontFamily.courier,
FontGeneric.monospace
];
/// Collection of most common cursive fonts in order.
static const List<String> cursive = const [
FontFamily.textile,
FontFamily.appleChancery,
FontFamily.zaphChancery,
FontGeneric.fantasy
];
/// Collection of most common fantasy fonts in order.
static const List<String> fantasy = const [
FontFamily.comicSansMs,
FontFamily.impact,
FontFamily.webdings,
FontGeneric.fantasy
];
// TODO(terry): Should support the values xx-small, small, large, xx-large,
// etc. (mapped to a pixel sized font)?
/// Font size in pixels.
final num size;
// TODO(terry): _family should be an immutable list, wrapper class to do this
// should exist in Dart.
/// Family specifies a list of fonts, the browser will sequentially select the
/// the first known/supported font. There are two types of font families the
/// family-name (e.g., arial, times, courier, etc) or the generic-family
/// (e.g., serif, sans-seric, etc.)
final List<String> family;
/// Font weight from 100, 200, 300, 400, 500, 600, 700, 800, 900
final int weight;
/// Style of a font normal, italic, oblique.
final String style;
/// Font variant NORMAL (default) or SMALL_CAPS. Different set of font glyph
/// lower case letters designed to have to fit within the font-height and
/// weight of the corresponding lowercase letters.
final String variant;
final LineHeight lineHeight;
// TODO(terry): Size and computedLineHeight are in pixels. Need to figure out
// how to handle in other units (specified in other units) like
// points, inches, etc. Do we have helpers like Units.Points(12)
// where 12 is in points and that's converted to pixels?
// TODO(terry): lineHeight is computed as 1.2 although CSS_RESET is 1.0 we
// need to be consistent some browsers use 1 others 1.2.
// TODO(terry): There is a school of thought "Golden Ratio Typography".
// Where width to display the text is also important in computing the line
// height. Classic typography suggest the ratio be 1.5. See
// <http://www.pearsonified.com/2011/12/golden-ratio-typography.php> and
// <http://meyerweb.com/eric/thoughts/2008/05/06/line-height-abnormal/>.
/// Create a font using [size] of font in pixels, [family] name of font(s)
/// using [FontFamily], [style] of the font using [FontStyle], [variant] using
/// [FontVariant], and [lineHeight] extra space (leading) around the font in
/// pixels, if not specified it's 1.2 the font size.
const Font(
{this.size,
this.family,
this.weight,
this.style,
this.variant,
this.lineHeight});
/// Merge the two fonts and return the result. See [Style.merge] for
/// more information.
factory Font.merge(Font a, Font b) {
if (a == null) return b;
if (b == null) return a;
return new Font._merge(a, b);
}
Font._merge(Font a, Font b)
: size = _mergeVal(a.size, b.size),
family = _mergeVal(a.family, b.family),
weight = _mergeVal(a.weight, b.weight),
style = _mergeVal(a.style, b.style),
variant = _mergeVal(a.variant, b.variant),
lineHeight = _mergeVal(a.lineHeight, b.lineHeight);
/// Shorthand CSS format for font is:
///
/// font-style font-variant font-weight font-size/line-height font-family
///
/// The font-size and font-family values are required. If any of the other
/// values are missing the default value is used.
String get cssExpression {
// TODO(jimhug): include variant, style, other options
if (weight != null) {
// TODO(jacobr): is this really correct for lineHeight?
if (lineHeight != null) {
return "$weight ${size}px/$lineHeightInPixels $_fontsAsString";
}
return '$weight ${size}px $_fontsAsString';
}
return '${size}px $_fontsAsString';
}
Font scale(num ratio) => new Font(
size: size * ratio,
family: family,
weight: weight,
style: style,
variant: variant);
/// The lineHeight, provides an indirect means to specify the leading. The
/// leading is the difference between the font-size height and the (used)
/// value of line height in pixels. If lineHeight is not specified it's
/// automatically computed as 1.2 of the font size. Firefox is 1.2, Safari is
/// ~1.2, and CSS suggest a ration from 1 to 1.2 of the font-size when
/// computing line-height. The Font class constructor has the computation for
/// _lineHeight.
num get lineHeightInPixels {
if (lineHeight != null) {
if (lineHeight.inPixels) {
return lineHeight.height;
} else {
return (size != null) ? lineHeight.height * size : null;
}
} else {
return (size != null) ? size * 1.2 : null;
}
}
int get hashCode {
// TODO(jimhug): Lot's of potential collisions here. List of fonts, etc.
return size.toInt() % family[0].hashCode;
}
bool operator ==(other) {
if (other is! Font) return false;
Font o = other;
return o.size == size &&
o.family == family &&
o.weight == weight &&
o.lineHeight == lineHeight &&
o.style == style &&
o.variant == variant;
}
// TODO(terry): This is fragile should probably just iterate through the list
// of fonts construction the font-family string.
/// Return fonts as a comma seperated list sans the square brackets.
String get _fontsAsString {
String fonts = family.toString();
return fonts.length > 2 ? fonts.substring(1, fonts.length - 1) : "";
}
}
/// This class stores the sizes of the box edges in the CSS [box model][]. Each
/// edge area is placed around the sides of the content box. The innermost area
/// is the [Style.padding] area which has a background and surrounds the
/// content. The content and padding area is surrounded by the [Style.border],
/// which itself is surrounded by the transparent [Style.margin]. This box
/// represents the eges of padding, border, or margin depending on which
/// accessor was used to retrieve it.
///
/// [box model]: https://developer.mozilla.org/en/CSS/box_model
class BoxEdge {
/// The size of the left edge, or null if the style has no edge.
final num left;
/// The size of the top edge, or null if the style has no edge.
final num top;
/// The size of the right edge, or null if the style has no edge.
final num right;
/// The size of the bottom edge, or null if the style has no edge.
final num bottom;
/// Creates a box edge with the specified [left], [top], [right], and
/// [bottom] width.
const BoxEdge([this.left, this.top, this.right, this.bottom]);
/// Creates a box edge with the specified [top], [right], [bottom], and
/// [left] width. This matches the typical CSS order:
/// <https://developer.mozilla.org/en/CSS/margin>
/// <https://developer.mozilla.org/en/CSS/border-width>
/// <https://developer.mozilla.org/en/CSS/padding>.
const BoxEdge.clockwiseFromTop(this.top, this.right, this.bottom, this.left);
/// This is a helper to creates a box edge with the same [left], [top]
/// [right], and [bottom] widths.
const BoxEdge.uniform(num size)
: top = size,
left = size,
bottom = size,
right = size;
/// Takes a possibly null box edge, with possibly null metrics, and fills
/// them in with 0 instead.
factory BoxEdge.nonNull(BoxEdge other) {
if (other == null) return const BoxEdge(0, 0, 0, 0);
num left = other.left;
num top = other.top;
num right = other.right;
num bottom = other.bottom;
bool make = false;
if (left == null) {
make = true;
left = 0;
}
if (top == null) {
make = true;
top = 0;
}
if (right == null) {
make = true;
right = 0;
}
if (bottom == null) {
make = true;
bottom = 0;
}
return make ? new BoxEdge(left, top, right, bottom) : other;
}
/// Merge the two box edge sizes and return the result. See [Style.merge] for
/// more information.
factory BoxEdge.merge(BoxEdge x, BoxEdge y) {
if (x == null) return y;
if (y == null) return x;
return new BoxEdge._merge(x, y);
}
BoxEdge._merge(BoxEdge x, BoxEdge y)
: left = _mergeVal(x.left, y.left),
top = _mergeVal(x.top, y.top),
right = _mergeVal(x.right, y.right),
bottom = _mergeVal(x.bottom, y.bottom);
/// The total size of the horizontal edges. Equal to [left] + [right], where
/// null is interpreted as 0px.
num get width => (left != null ? left : 0) + (right != null ? right : 0);
/// The total size of the vertical edges. Equal to [top] + [bottom], where
/// null is interpreted as 0px.
num get height => (top != null ? top : 0) + (bottom != null ? bottom : 0);
}
T _mergeVal<T>(T x, T y) => y != null ? y : x;