Changesets can be listed by changeset number.
The Git repository is here.
- Revision:
- 373
- Log:
Initial import of Radiant 0.9.1, which is now packaged as a gem. This is an
import of the tagged 0.9.1 source checked out from GitHub, which isn't quite
the same as the gem distribution - but it doesn't seem to be available in an
archived form and the installed gem already has modifications, so this is
the closest I can get.
- Author:
- rool
- Date:
- Mon Mar 21 13:40:05 +0000 2011
- Size:
- 12859 Bytes
1 | require 'sass/script/literal' |
2 | |
3 | module Sass::Script |
4 | # A SassScript object representing a number. |
5 | # SassScript numbers can have decimal values, |
6 | # and can also have units. |
7 | # For example, `12`, `1px`, and `10.45em` |
8 | # are all valid values. |
9 | # |
10 | # Numbers can also have more complex units, such as `1px*em/in`. |
11 | # These cannot be inputted directly in Sass code at the moment. |
12 | class Number < Literal |
13 | # The Ruby value of the number. |
14 | # |
15 | # @return [Numeric] |
16 | attr_reader :value |
17 | |
18 | # A list of units in the numerator of the number. |
19 | # For example, `1px*em/in*cm` would return `["px", "em"]` |
20 | # @return [Array<String>] |
21 | attr_reader :numerator_units |
22 | |
23 | # A list of units in the denominator of the number. |
24 | # For example, `1px*em/in*cm` would return `["in", "cm"]` |
25 | # @return [Array<String>] |
26 | attr_reader :denominator_units |
27 | |
28 | # The precision with which numbers will be printed to CSS files. |
29 | # For example, if this is `1000.0`, |
30 | # `3.1415926` will be printed as `3.142`. |
31 | PRECISION = 1000.0 |
32 | |
33 | # @param value [Numeric] The value of the number |
34 | # @param numerator_units [Array<String>] See \{#numerator\_units} |
35 | # @param denominator_units [Array<String>] See \{#denominator\_units} |
36 | def initialize(value, numerator_units = [], denominator_units = []) |
37 | super(value) |
38 | @numerator_units = numerator_units |
39 | @denominator_units = denominator_units |
40 | normalize! |
41 | end |
42 | |
43 | # The SassScript `+` operation. |
44 | # Its functionality depends on the type of its argument: |
45 | # |
46 | # {Number} |
47 | # : Adds the two numbers together, converting units if possible. |
48 | # |
49 | # {Color} |
50 | # : Adds this number to each of the RGB color channels. |
51 | # |
52 | # {Literal} |
53 | # : See {Literal#plus}. |
54 | # |
55 | # @param other [Literal] The right-hand side of the operator |
56 | # @return [Literal] The result of the operation |
57 | # @raise [Sass::UnitConversionError] if `other` is a number with incompatible units |
58 | def plus(other) |
59 | if other.is_a? Number |
60 | operate(other, :+) |
61 | elsif other.is_a?(Color) |
62 | other.plus(self) |
63 | else |
64 | super |
65 | end |
66 | end |
67 | |
68 | # The SassScript binary `-` operation (e.g. `!a - !b`). |
69 | # Its functionality depends on the type of its argument: |
70 | # |
71 | # {Number} |
72 | # : Subtracts this number from the other, converting units if possible. |
73 | # |
74 | # {Literal} |
75 | # : See {Literal#minus}. |
76 | # |
77 | # @param other [Literal] The right-hand side of the operator |
78 | # @return [Literal] The result of the operation |
79 | # @raise [Sass::UnitConversionError] if `other` is a number with incompatible units |
80 | def minus(other) |
81 | if other.is_a? Number |
82 | operate(other, :-) |
83 | else |
84 | super |
85 | end |
86 | end |
87 | |
88 | # The SassScript unary `-` operation (e.g. `-!a`). |
89 | # |
90 | # @return [Number] The negative value of this number |
91 | def unary_minus |
92 | Number.new(-value, numerator_units, denominator_units) |
93 | end |
94 | |
95 | # The SassScript `*` operation. |
96 | # Its functionality depends on the type of its argument: |
97 | # |
98 | # {Number} |
99 | # : Multiplies the two numbers together, converting units appropriately. |
100 | # |
101 | # {Color} |
102 | # : Multiplies each of the RGB color channels by this number. |
103 | # |
104 | # @param other [Number, Color] The right-hand side of the operator |
105 | # @return [Number, Color] The result of the operation |
106 | # @raise [NoMethodError] if `other` is an invalid type |
107 | def times(other) |
108 | if other.is_a? Number |
109 | operate(other, :*) |
110 | elsif other.is_a? Color |
111 | other.times(self) |
112 | else |
113 | raise NoMethodError.new(nil, :times) |
114 | end |
115 | end |
116 | |
117 | # The SassScript `/` operation. |
118 | # Its functionality depends on the type of its argument: |
119 | # |
120 | # {Number} |
121 | # : Divides this number by the other, converting units appropriately. |
122 | # |
123 | # {Literal} |
124 | # : See {Literal#div}. |
125 | # |
126 | # @param other [Literal] The right-hand side of the operator |
127 | # @return [Literal] The result of the operation |
128 | def div(other) |
129 | if other.is_a? Number |
130 | operate(other, :/) |
131 | else |
132 | super |
133 | end |
134 | end |
135 | |
136 | # The SassScript `%` operation. |
137 | # |
138 | # @param other [Number] The right-hand side of the operator |
139 | # @return [Number] This number modulo the other |
140 | # @raise [NoMethodError] if `other` is an invalid type |
141 | # @raise [Sass::UnitConversionError] if `other` has any units |
142 | def mod(other) |
143 | if other.is_a?(Number) |
144 | unless other.unitless? |
145 | raise Sass::UnitConversionError.new("Cannot modulo by a number with units: #{other.inspect}.") |
146 | end |
147 | operate(other, :%) |
148 | else |
149 | raise NoMethodError.new(nil, :mod) |
150 | end |
151 | end |
152 | |
153 | # The SassScript `==` operation. |
154 | # |
155 | # @param other [Literal] The right-hand side of the operator |
156 | # @return [Boolean] Whether this number is equal to the other object |
157 | def eq(other) |
158 | return Sass::Script::Bool.new(false) unless other.is_a?(Sass::Script::Number) |
159 | this = self |
160 | begin |
161 | if unitless? |
162 | this = this.coerce(other.numerator_units, other.denominator_units) |
163 | else |
164 | other = other.coerce(numerator_units, denominator_units) |
165 | end |
166 | rescue Sass::UnitConversionError |
167 | return Sass::Script::Bool.new(false) |
168 | end |
169 | |
170 | Sass::Script::Bool.new(this.value == other.value) |
171 | end |
172 | |
173 | # The SassScript `>` operation. |
174 | # |
175 | # @param other [Number] The right-hand side of the operator |
176 | # @return [Boolean] Whether this number is greater than the other |
177 | # @raise [NoMethodError] if `other` is an invalid type |
178 | def gt(other) |
179 | raise NoMethodError.new(nil, :gt) unless other.is_a?(Number) |
180 | operate(other, :>) |
181 | end |
182 | |
183 | # The SassScript `>=` operation. |
184 | # |
185 | # @param other [Number] The right-hand side of the operator |
186 | # @return [Boolean] Whether this number is greater than or equal to the other |
187 | # @raise [NoMethodError] if `other` is an invalid type |
188 | def gte(other) |
189 | raise NoMethodError.new(nil, :gte) unless other.is_a?(Number) |
190 | operate(other, :>=) |
191 | end |
192 | |
193 | # The SassScript `<` operation. |
194 | # |
195 | # @param other [Number] The right-hand side of the operator |
196 | # @return [Boolean] Whether this number is less than the other |
197 | # @raise [NoMethodError] if `other` is an invalid type |
198 | def lt(other) |
199 | raise NoMethodError.new(nil, :lt) unless other.is_a?(Number) |
200 | operate(other, :<) |
201 | end |
202 | |
203 | # The SassScript `<=` operation. |
204 | # |
205 | # @param other [Number] The right-hand side of the operator |
206 | # @return [Boolean] Whether this number is less than or equal to the other |
207 | # @raise [NoMethodError] if `other` is an invalid type |
208 | def lte(other) |
209 | raise NoMethodError.new(nil, :lte) unless other.is_a?(Number) |
210 | operate(other, :<=) |
211 | end |
212 | |
213 | # @return [String] The CSS representation of this number |
214 | # @raise [Sass::SyntaxError] if this number has units that can't be used in CSS |
215 | # (e.g. `px*in`) |
216 | def to_s |
217 | raise Sass::SyntaxError.new("#{inspect} isn't a valid CSS value.") unless legal_units? |
218 | inspect |
219 | end |
220 | |
221 | # Returns a readable representation of this number. |
222 | # |
223 | # This representation is valid CSS (and valid SassScript) |
224 | # as long as there is only one unit. |
225 | # |
226 | # @return [String] The representation |
227 | def inspect |
228 | value = |
229 | if self.value.is_a?(Float) && (self.value.infinite? || self.value.nan?) |
230 | self.value |
231 | elsif int? |
232 | self.value.to_i |
233 | else |
234 | (self.value * PRECISION).round / PRECISION |
235 | end |
236 | "#{value}#{unit_str}" |
237 | end |
238 | |
239 | # @return [Fixnum] The integer value of the number |
240 | # @raise [Sass::SyntaxError] if the number isn't an integer |
241 | def to_i |
242 | super unless int? |
243 | return value |
244 | end |
245 | |
246 | # @return [Boolean] Whether or not this number is an integer. |
247 | def int? |
248 | value % 1 == 0.0 |
249 | end |
250 | |
251 | # @return [Boolean] Whether or not this number has no units. |
252 | def unitless? |
253 | numerator_units.empty? && denominator_units.empty? |
254 | end |
255 | |
256 | # @return [Boolean] Whether or not this number has units that can be represented in CSS |
257 | # (that is, zero or one \{#numerator\_units}). |
258 | def legal_units? |
259 | (numerator_units.empty? || numerator_units.size == 1) && denominator_units.empty? |
260 | end |
261 | |
262 | # Returns this number converted to other units. |
263 | # The conversion takes into account the relationship between e.g. mm and cm, |
264 | # as well as between e.g. in and cm. |
265 | # |
266 | # If this number has no units, it will simply return itself |
267 | # with the given units. |
268 | # |
269 | # An incompatible coercion, e.g. between px and cm, will raise an error. |
270 | # |
271 | # @param num_units [Array<String>] The numerator units to coerce this number into. |
272 | # See {\#numerator\_units} |
273 | # @param den_units [Array<String>] The denominator units to coerce this number into. |
274 | # See {\#denominator\_units} |
275 | # @return [Number] The number with the new units |
276 | # @raise [Sass::UnitConversionError] if the given units are incompatible with the number's |
277 | # current units |
278 | def coerce(num_units, den_units) |
279 | Number.new(if unitless? |
280 | self.value |
281 | else |
282 | self.value * coercion_factor(self.numerator_units, num_units) / |
283 | coercion_factor(self.denominator_units, den_units) |
284 | end, num_units, den_units) |
285 | end |
286 | |
287 | private |
288 | |
289 | def operate(other, operation) |
290 | this = self |
291 | if [:+, :-, :<=, :<, :>, :>=].include?(operation) |
292 | if unitless? |
293 | this = this.coerce(other.numerator_units, other.denominator_units) |
294 | else |
295 | other = other.coerce(numerator_units, denominator_units) |
296 | end |
297 | end |
298 | # avoid integer division |
299 | value = (:/ == operation) ? this.value.to_f : this.value |
300 | result = value.send(operation, other.value) |
301 | |
302 | if result.is_a?(Numeric) |
303 | Number.new(result, *compute_units(this, other, operation)) |
304 | else # Boolean op |
305 | Bool.new(result) |
306 | end |
307 | end |
308 | |
309 | def coercion_factor(from_units, to_units) |
310 | # get a list of unmatched units |
311 | from_units, to_units = sans_common_units(from_units, to_units) |
312 | |
313 | if from_units.size != to_units.size || !convertable?(from_units | to_units) |
314 | raise Sass::UnitConversionError.new("Incompatible units: '#{from_units.join('*')}' and '#{to_units.join('*')}'.") |
315 | end |
316 | |
317 | from_units.zip(to_units).inject(1) {|m,p| m * conversion_factor(p[0], p[1]) } |
318 | end |
319 | |
320 | def compute_units(this, other, operation) |
321 | case operation |
322 | when :* |
323 | [this.numerator_units + other.numerator_units, this.denominator_units + other.denominator_units] |
324 | when :/ |
325 | [this.numerator_units + other.denominator_units, this.denominator_units + other.numerator_units] |
326 | else |
327 | [this.numerator_units, this.denominator_units] |
328 | end |
329 | end |
330 | |
331 | def unit_str |
332 | rv = numerator_units.join("*") |
333 | if denominator_units.any? |
334 | rv << "/" |
335 | rv << denominator_units.join("*") |
336 | end |
337 | rv |
338 | end |
339 | |
340 | def normalize! |
341 | return if unitless? |
342 | @numerator_units, @denominator_units = sans_common_units(numerator_units, denominator_units) |
343 | |
344 | @denominator_units.each_with_index do |d, i| |
345 | if convertable?(d) && (u = @numerator_units.detect(&method(:convertable?))) |
346 | @value /= conversion_factor(d, u) |
347 | @denominator_units.delete_at(i) |
348 | @numerator_units.delete_at(@numerator_units.index(u)) |
349 | end |
350 | end |
351 | end |
352 | |
353 | # A hash of unit names to their index in the conversion table |
354 | # @private |
355 | CONVERTABLE_UNITS = {"in" => 0, "cm" => 1, "pc" => 2, "mm" => 3, "pt" => 4} |
356 | # @private |
357 | CONVERSION_TABLE = [[ 1, 2.54, 6, 25.4, 72 ], # in |
358 | [ nil, 1, 2.36220473, 10, 28.3464567], # cm |
359 | [ nil, nil, 1, 4.23333333, 12 ], # pc |
360 | [ nil, nil, nil, 1, 2.83464567], # mm |
361 | [ nil, nil, nil, nil, 1 ]] # pt |
362 | |
363 | def conversion_factor(from_unit, to_unit) |
364 | res = CONVERSION_TABLE[CONVERTABLE_UNITS[from_unit]][CONVERTABLE_UNITS[to_unit]] |
365 | return 1.0 / conversion_factor(to_unit, from_unit) if res.nil? |
366 | res |
367 | end |
368 | |
369 | def convertable?(units) |
370 | Array(units).all?(&CONVERTABLE_UNITS.method(:include?)) |
371 | end |
372 | |
373 | def sans_common_units(units1, units2) |
374 | units2 = units2.dup |
375 | # Can't just use -, because we want px*px to coerce properly to px*mm |
376 | return units1.map do |u| |
377 | next u unless j = units2.index(u) |
378 | units2.delete_at(j) |
379 | nil |
380 | end.compact, units2 |
381 | end |
382 | end |
383 | end |