unit.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. /*!
  2. * Stylus - Unit
  3. * Copyright (c) Automattic <developer.wordpress.com>
  4. * MIT Licensed
  5. */
  6. /**
  7. * Module dependencies.
  8. */
  9. var Node = require('./node')
  10. , nodes = require('./');
  11. /**
  12. * Unit conversion table.
  13. */
  14. var FACTOR_TABLE = {
  15. 'mm': {val: 1, label: 'mm'},
  16. 'cm': {val: 10, label: 'mm'},
  17. 'in': {val: 25.4, label: 'mm'},
  18. 'pt': {val: 25.4/72, label: 'mm'},
  19. 'ms': {val: 1, label: 'ms'},
  20. 's': {val: 1000, label: 'ms'},
  21. 'Hz': {val: 1, label: 'Hz'},
  22. 'kHz': {val: 1000, label: 'Hz'}
  23. };
  24. /**
  25. * Initialize a new `Unit` with the given `val` and unit `type`
  26. * such as "px", "pt", "in", etc.
  27. *
  28. * @param {String} val
  29. * @param {String} type
  30. * @api public
  31. */
  32. var Unit = module.exports = function Unit(val, type){
  33. Node.call(this);
  34. this.val = val;
  35. this.type = type;
  36. };
  37. /**
  38. * Inherit from `Node.prototype`.
  39. */
  40. Unit.prototype.__proto__ = Node.prototype;
  41. /**
  42. * Return Boolean based on the unit value.
  43. *
  44. * @return {Boolean}
  45. * @api public
  46. */
  47. Unit.prototype.toBoolean = function(){
  48. return nodes.Boolean(this.type
  49. ? true
  50. : this.val);
  51. };
  52. /**
  53. * Return unit string.
  54. *
  55. * @return {String}
  56. * @api public
  57. */
  58. Unit.prototype.toString = function(){
  59. return this.val + (this.type || '');
  60. };
  61. /**
  62. * Return a clone of this node.
  63. *
  64. * @return {Node}
  65. * @api public
  66. */
  67. Unit.prototype.clone = function(){
  68. var clone = new Unit(this.val, this.type);
  69. clone.lineno = this.lineno;
  70. clone.column = this.column;
  71. clone.filename = this.filename;
  72. return clone;
  73. };
  74. /**
  75. * Return a JSON representation of this node.
  76. *
  77. * @return {Object}
  78. * @api public
  79. */
  80. Unit.prototype.toJSON = function(){
  81. return {
  82. __type: 'Unit',
  83. val: this.val,
  84. type: this.type,
  85. lineno: this.lineno,
  86. column: this.column,
  87. filename: this.filename
  88. };
  89. };
  90. /**
  91. * Operate on `right` with the given `op`.
  92. *
  93. * @param {String} op
  94. * @param {Node} right
  95. * @return {Node}
  96. * @api public
  97. */
  98. Unit.prototype.operate = function(op, right){
  99. var type = this.type || right.first.type;
  100. // swap color
  101. if ('rgba' == right.nodeName || 'hsla' == right.nodeName) {
  102. return right.operate(op, this);
  103. }
  104. // operate
  105. if (this.shouldCoerce(op)) {
  106. right = right.first;
  107. // percentages
  108. if ('%' != this.type && ('-' == op || '+' == op) && '%' == right.type) {
  109. right = new Unit(this.val * (right.val / 100), '%');
  110. } else {
  111. right = this.coerce(right);
  112. }
  113. switch (op) {
  114. case '-':
  115. return new Unit(this.val - right.val, type);
  116. case '+':
  117. // keyframes interpolation
  118. type = type || (right.type == '%' && right.type);
  119. return new Unit(this.val + right.val, type);
  120. case '/':
  121. return new Unit(this.val / right.val, type);
  122. case '*':
  123. return new Unit(this.val * right.val, type);
  124. case '%':
  125. return new Unit(this.val % right.val, type);
  126. case '**':
  127. return new Unit(Math.pow(this.val, right.val), type);
  128. case '..':
  129. case '...':
  130. var start = this.val
  131. , end = right.val
  132. , expr = new nodes.Expression
  133. , inclusive = '..' == op;
  134. if (start < end) {
  135. do {
  136. expr.push(new nodes.Unit(start));
  137. } while (inclusive ? ++start <= end : ++start < end);
  138. } else {
  139. do {
  140. expr.push(new nodes.Unit(start));
  141. } while (inclusive ? --start >= end : --start > end);
  142. }
  143. return expr;
  144. }
  145. }
  146. return Node.prototype.operate.call(this, op, right);
  147. };
  148. /**
  149. * Coerce `other` unit to the same type as `this` unit.
  150. *
  151. * Supports:
  152. *
  153. * mm -> cm | in
  154. * cm -> mm | in
  155. * in -> mm | cm
  156. *
  157. * ms -> s
  158. * s -> ms
  159. *
  160. * Hz -> kHz
  161. * kHz -> Hz
  162. *
  163. * @param {Unit} other
  164. * @return {Unit}
  165. * @api public
  166. */
  167. Unit.prototype.coerce = function(other){
  168. if ('unit' == other.nodeName) {
  169. var a = this
  170. , b = other
  171. , factorA = FACTOR_TABLE[a.type]
  172. , factorB = FACTOR_TABLE[b.type];
  173. if (factorA && factorB && (factorA.label == factorB.label)) {
  174. var bVal = b.val * (factorB.val / factorA.val);
  175. return new nodes.Unit(bVal, a.type);
  176. } else {
  177. return new nodes.Unit(b.val, a.type);
  178. }
  179. } else if ('string' == other.nodeName) {
  180. // keyframes interpolation
  181. if ('%' == other.val) return new nodes.Unit(0, '%');
  182. var val = parseFloat(other.val);
  183. if (isNaN(val)) Node.prototype.coerce.call(this, other);
  184. return new nodes.Unit(val);
  185. } else {
  186. return Node.prototype.coerce.call(this, other);
  187. }
  188. };