Index: runtime/lib/pixels.dart |
diff --git a/runtime/lib/pixels.dart b/runtime/lib/pixels.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..fa5eaed7dc8c1053adda41ad092c064bcd6a2ed5 |
--- /dev/null |
+++ b/runtime/lib/pixels.dart |
@@ -0,0 +1,355 @@ |
+// Copyright (c) 2016, 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. |
+ |
+// TODO(regis): Move abstract class pixels to sdk/lib/core/pixels.dart: |
+abstract class pixels extends num { |
+ static const pixels NAN = 0px / 0px; |
+ static const pixels INFINITY = 1px / 0px; |
+ static const pixels NEGATIVE_INFINITY = -INFINITY; |
+ // TODO(regis): Expand as needed with API documentation. |
+} |
+ |
+class _Pixels implements pixels { |
+ static const int _SCALING_BITS = 6; |
+ static const int _SCALING_FACTOR = 1 << _SCALING_BITS; |
+ static const int _HALF_VALUE = _SCALING_FACTOR >> 1; |
+ static const int _NAN_VALUE = (1 << 30) - 1; // Same as kSmiMax32. Positive. |
+ static const int _PLUS_INF_VALUE = _NAN_VALUE - 1; // Same as kSmiMax32 - 1. |
+ static const int _MINUS_INF_VALUE = -_PLUS_INF_VALUE; // Same as kSmiMin32 + 2. |
+ |
+ // TODO(regis): Detect _NAN_VALUE, _PLUS_INF_VALUE and _MINUS_INF_VALUE as |
+ // operands to all operations and treat as non finite values, rather than min and max. |
+ |
+ final int _value; |
+ static const pixels _PLUS_ONE = const _Pixels(1 << _SCALING_BITS); |
+ static const pixels _MINUS_ONE = const _Pixels(-1 << _SCALING_BITS); |
+ static const pixels _PLUS_INF = const _Pixels(_PLUS_INF_VALUE); |
+ static const pixels _MINUS_INF = const _Pixels(_MINUS_INF_VALUE); |
+ static const pixels _NAN = const _Pixels(_NAN_VALUE); |
+ |
+ // Argument is already a scaled value in valid range. |
+ const _Pixels(int this._value); |
+ |
+ // Argument is already a scaled value, but not necessarily in valid range. |
+ _Pixels._clamp(int this._value) { |
+ if (_value > _PLUS_INF_VALUE) _value = _PLUS_INF_VALUE; |
+ else if (_value < _MINUS_INF_VALUE) _value = _MINUS_INF_VALUE; |
+ } |
+ |
+ factory _Pixels.fromInteger(int value) => new _Pixels._clamp(value << _SCALING_BITS); |
+ |
+ factory _Pixels.fromDouble(double value) => value.toPixels(); |
+ |
+ _Pixels.fromPixelsLiteral(String value) { |
+ final decimalPoint = ".".codeUnits.first; |
+ final digit0 = "0".codeUnits.first; |
+ final digit9 = "9".codeUnits.first; |
+ var numerator = 0; |
+ var denominator = 1; |
+ bool seenDecimalPoint = false; |
+ for (var i = 0; i < value.length; i++) { |
+ var c = value.codeUnitAt(i); |
+ if (!seenDecimalPoint && c == decimalPoint) { |
+ seenDecimalPoint = true; |
+ } else if (digit0 <= c && c <= digit9) { |
+ numerator = numerator*10 + (c - digit0); |
+ if (seenDecimalPoint) denominator *= 10; |
+ } else { |
+ break; // TODO(regis): Error handling. Expect px or PX here. |
+ } |
+ } |
+ _value = (numerator << _SCALING_BITS) ~/ denominator; |
+ if (_value > _PLUS_INF_VALUE) _value = _PLUS_INF_VALUE; |
+ } |
+ |
+ Type get runtimeType => pixels; |
+ |
+ int get _identityHashCode { |
+ return _value; |
+ } |
+ |
+ num operator +(num other) { |
+ return other._toDoubleOrPixels()._addFromPixels(this); |
+ } |
+ pixels _addFromPixels(pixels other) { |
+ return other._add(this); |
+ } |
+ pixels _add(pixels other) { |
+ return new _Pixels._clamp(_value + other._value); |
+ } |
+ |
+ num operator -(num other) { |
+ return other._toDoubleOrPixels()._subFromPixels(this); |
+ } |
+ pixels _subFromPixels(pixels other) { |
+ return other._sub(this); |
+ } |
+ pixels _sub(pixels other) { |
+ return new _Pixels._clamp(_value - other._value); |
+ } |
+ |
+ num operator *(num other) { |
+ return other._toDoubleOrPixels()._mulFromPixels(this); |
+ } |
+ pixels _mulFromPixels(pixels other) { |
+ return other._mul(this); |
+ } |
+ pixels _mul(pixels other) { |
+ // Use rounding. |
+ return new _Pixels._clamp((_value*other._value + _HALF_VALUE) >> _SCALING_BITS); |
+ } |
+ |
+ num operator /(num other) { |
+ return other._toDoubleOrPixels()._divFromPixels(this); |
+ } |
+ pixels _divFromPixels(pixels other) { |
+ return other._div(this); |
+ } |
+ pixels _div(pixels other) { |
+ if (other._value == 0) { |
+ if (_value > 0) { |
+ return _PLUS_INF; |
+ } else if (_value == 0) { |
+ return _NAN; |
+ } else { |
+ return _MINUS_INF; |
+ } |
+ } |
+ return new _Pixels._clamp((_value << _SCALING_BITS) ~/ other._value); |
+ } |
+ |
+ num operator %(num other) { |
+ return other._toDoubleOrPixels()._moduloFromPixels(this); |
+ } |
+ pixels _moduloFromPixels(pixels other) { |
+ return other._modulo(this); |
+ } |
+ pixels _modulo(pixels other) { |
+ var q = _div(other); // TODO(regis): Check NAN. |
+ if (other._value < 0) { |
+ q = q.ceil(); |
+ } else { |
+ q = q.floor(); |
+ } |
+ return _sub(q.toPixels()._mul(other)); |
+ } |
+ |
+ int operator ~/(num other) { |
+ return other._toDoubleOrPixels()._truncDivFromPixels(this); |
+ } |
+ int _truncDivFromPixels(pixels other) { |
+ return other._truncDiv(this); |
+ } |
+ int _truncDiv(pixels other) { |
+ if (other._value == 0) throw const IntegerDivisionByZeroException(); |
+ return _value ~/ other._value; |
+ } |
+ |
+ num remainder(num other) { |
+ return other._toDoubleOrPixels()._remainderFromPixels(this); |
+ } |
+ pixels _remainderFromPixels(pixels other) { |
+ return other._remainder(this); |
+ } |
+ pixels _remainder(pixels other) { |
+ if (other._value == 0) throw const IntegerDivisionByZeroException(); |
+ return new _Pixels._clamp(_value - other._value*(_value ~/ other._value)); |
+ } |
+ |
+ pixels operator -() { |
+ if (isNaN) return this; |
+ return new _Pixels(-_value); |
+ } |
+ |
+ bool operator ==(other) { |
+ if (!(other is num)) return false; |
+ return other._equalToPixels(this); |
+ } |
+ bool _equalToPixels(pixels other) { |
+ return other._equal(this); |
+ } |
+ bool _equal(pixels other) { |
+ return _value == other._value; |
+ } |
+ |
+ bool _equalToInteger(int other) { |
+ return _value == (other << _SCALING_BITS); |
+ } |
+ |
+ bool operator <(num other) { |
+ return other > this; |
+ } |
+ bool operator >(num other) { |
+ return other._greaterThanFromPixels(this); |
+ } |
+ bool _greaterThanFromPixels(pixels other) { |
+ return other._value > _value; |
+ } |
+ bool operator >=(num other) { |
+ return (this == other) || (this > other); |
+ } |
+ bool operator <=(num other) { |
+ return (this == other) || (this < other); |
+ } |
+ |
+ pixels _addFromInteger(int other) { |
+ return new _Pixels._clamp((other << _SCALING_BITS) + _value); |
+ } |
+ |
+ pixels _subFromInteger(int other) { |
+ return new _Pixels._clamp((other << _SCALING_BITS) - _value); |
+ } |
+ |
+ pixels _mulFromInteger(int other) { |
+ return new _Pixels._clamp(other * _value); |
+ } |
+ |
+ pixels _divFromInteger(int other) { |
+ if (_value == 0) { |
+ if (other > 0) { |
+ return _PLUS_INF; |
+ } else if (other == 0) { |
+ return _NAN; |
+ } else { |
+ return _MINUS_INF; |
+ } |
+ } |
+ return new _Pixels._clamp((other << (2*_SCALING_BITS)) ~/ _value); |
+ } |
+ |
+ pixels _moduloFromInteger(int other) { |
+ var q = _divFromInteger(other); |
+ if (_value < 0) { |
+ q = q.ceil(); |
+ } else { |
+ q = q.floor(); |
+ } |
+ return _mul(q.toPixels())._subFromInteger(other); |
+ } |
+ |
+ int _truncDivFromInteger(int other) { |
+ if (_value == 0) throw const IntegerDivisionByZeroException(); |
+ return (other << _SCALING_BITS) ~/ _value; |
+ } |
+ |
+ pixels _remainderFromInteger(int other) { |
+ if (_value == 0) throw const IntegerDivisionByZeroException(); |
+ return new _Pixels._clamp( |
+ (other << _SCALING_BITS) - _value*((other << _SCALING_BITS) ~/ _value)); |
+ } |
+ |
+ bool _greaterThanFromInteger(int other) { |
+ return (other << _SCALING_BITS) > _value; |
+ } |
+ |
+ bool get isNegative { |
+ return _value < 0; |
+ } |
+ |
+ bool get isInfinite { |
+ return (_value == _PLUS_INF_VALUE) || (_value == _MINUS_INF_VALUE); |
+ } |
+ bool get isNaN => _value == _NAN_VALUE; |
+ bool get isFinite { |
+ return (_value != _PLUS_INF_VALUE) && |
+ (_value != _MINUS_INF_VALUE) && |
+ (_value != _NAN_VALUE); |
+ } |
+ |
+ pixels abs() { |
+ return isNegative ? -this : this; |
+ } |
+ |
+ pixels get sign { |
+ if (_value > 0) return _PLUS_ONE; |
+ if (_value < 0) return _MINUS_ONE; |
+ return this; |
+ } |
+ |
+ int round() { |
+ if (!isFinite) throw new UnsupportedError("Must be finite"); |
+ return (_value + _HALF_VALUE) >> _SCALING_BITS; |
+ } |
+ int floor() { |
+ if (!isFinite) throw new UnsupportedError("Must be finite"); |
+ return _value >> _SCALING_BITS; |
+ } |
+ int ceil() { |
+ if (!isFinite) throw new UnsupportedError("Must be finite"); |
+ return (_value + _SCALING_FACTOR - 1) >> _SCALING_BITS; |
+ } |
+ int truncate() { |
+ if (!isFinite) throw new UnsupportedError("Must be finite"); |
+ return (_value + ((_value < 0) ? (_SCALING_FACTOR - 1) : 0)) >> _SCALING_BITS; |
+ } |
+ |
+ double roundToDouble() => round().toDouble(); |
+ double floorToDouble() => floor().toDouble(); |
+ double ceilToDouble() => ceil().toDouble(); |
+ double truncateToDouble() => truncate().toDouble(); |
+ |
+ num clamp(num lowerLimit, num upperLimit) { |
+ if (lowerLimit is! num) { |
+ throw new ArgumentError.value(lowerLimit, "lowerLimit", "not a number"); |
+ } |
+ if (upperLimit is! num) { |
+ throw new ArgumentError.value(upperLimit, "upperLimit", "not a number"); |
+ } |
+ |
+ if (lowerLimit.compareTo(upperLimit) > 0) { |
+ throw new ArgumentError(lowerLimit); |
+ } |
+ if (lowerLimit.isNaN) return lowerLimit; |
+ if (this.compareTo(lowerLimit) < 0) return lowerLimit; |
+ if (this.compareTo(upperLimit) > 0) return upperLimit; |
+ return this; |
+ } |
+ |
+ int toInt() => truncate(); // TODO(regis): Should we use round() instead? |
+ double toDouble() { |
+ if (_value == _PLUS_INF_VALUE) return double.INFINITY; |
+ if (_value == _MINUS_INF_VALUE) return double.NEGATIVE_INFINITY; |
+ if (_value == _NAN_VALUE) return double.NAN; |
+ return _value / _SCALING_FACTOR; |
+ } |
+ pixels toPixels() { return this; } |
+ pixels _toDoubleOrPixels() { return this; } |
+ num _toBigintIfInteger() { return this; } |
+ |
+ String toString() => "${toDouble()}px"; |
+ |
+ int compareTo(num other) { |
+ const int EQUAL = 0, LESS = -1, GREATER = 1; |
+ if (isNaN) { |
+ return GREATER; // TODO(regis): Correct? |
+ } |
+ if (other is double) { |
+ double d = other; |
+ if (d.isInfinite) { |
+ if (isInfinite) { |
+ if (((d == double.NEGATIVE_INFINITY) && (_value < 0)) || |
+ ((d == double.INFINITY) && (_value > 0))) { |
+ return EQUAL; |
+ } |
+ } |
+ return _value > 0 ? GREATER : LESS; |
+ } |
+ if (d.isNaN) { |
+ return LESS; // TODO(regis): unless this.isNaN? See above. |
+ } |
+ if ((d == 0.0) && (_value == 0)) { |
+ return d.isNegative ? GREATER : EQUAL; |
+ } |
+ other = other.toPixels(); |
+ } |
+ if (this < other) { |
+ return LESS; |
+ } else if (this > other) { |
+ return GREATER; |
+ } else { |
+ return EQUAL; |
+ } |
+ } |
+} |
+ |