twitter_cldr_js 1.0.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +5 -0
- data/History.txt +11 -1
- data/README.md +103 -13
- data/Rakefile +1 -1
- data/lib/assets/javascripts/twitter_cldr/af.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/ar.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/be.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/bg.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/bn.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/ca.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/cs.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/cy.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/da.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/de.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/el.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/en.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/es.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/eu.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/fa.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/fi.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/fil.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/fr.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/ga.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/gl.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/he.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/hi.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/hu.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/id.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/it.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/ja.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/ko.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/lv.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/msa.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/nl.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/no.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/pl.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/pt.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/ro.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/ru.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/sk.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/sq.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/sr.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/sv.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/ta.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/th.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/tr.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/uk.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/ur.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/zh-cn.js +1815 -0
- data/lib/assets/javascripts/twitter_cldr/zh-tw.js +1815 -0
- data/lib/twitter_cldr/js/compiler.rb +12 -3
- data/lib/twitter_cldr/js/mustache/bundle.coffee +19 -1
- data/lib/twitter_cldr/js/mustache/calendars/additional_date_format_selector.coffee +85 -0
- data/lib/twitter_cldr/js/mustache/calendars/datetime.coffee +248 -226
- data/lib/twitter_cldr/js/mustache/calendars/timespan.coffee +19 -18
- data/lib/twitter_cldr/js/mustache/numbers/numbers.coffee +62 -19
- data/lib/twitter_cldr/js/mustache/plurals/rules.coffee +1 -1
- data/lib/twitter_cldr/js/mustache/shared/bidi.coffee +433 -0
- data/lib/twitter_cldr/js/mustache/shared/calendar.coffee +25 -0
- data/lib/twitter_cldr/js/mustache/shared/currencies.coffee +5 -11
- data/lib/twitter_cldr/js/mustache/shared/lists.coffee +40 -0
- data/lib/twitter_cldr/js/mustache/shared/prerender/bidi_classes.json +1 -0
- data/lib/twitter_cldr/js/mustache/utilities.coffee +87 -0
- data/lib/twitter_cldr/js/renderers.rb +23 -19
- data/lib/twitter_cldr/js/renderers/bundle.rb +8 -2
- data/lib/twitter_cldr/js/renderers/calendars/additional_date_format_selector_renderer.rb +18 -0
- data/lib/twitter_cldr/js/renderers/calendars/datetime_renderer.rb +13 -9
- data/lib/twitter_cldr/js/renderers/numbers/numbers_renderer.rb +17 -3
- data/lib/twitter_cldr/js/renderers/shared/bidi_renderer.rb +77 -0
- data/lib/twitter_cldr/js/renderers/shared/calendar_renderer.rb +20 -0
- data/lib/twitter_cldr/js/renderers/shared/currencies_renderer.rb +2 -2
- data/lib/twitter_cldr/js/renderers/shared/list_renderer.rb +22 -0
- data/lib/twitter_cldr/js/tasks/tasks.rake +9 -5
- data/lib/twitter_cldr/js/tasks/tasks.rb +6 -5
- data/lib/twitter_cldr/js/version.rb +1 -1
- data/spec/js/calendars/additional_date_format_selector.spec.js +126 -0
- data/spec/js/calendars/datetime.spec.js +29 -2
- data/spec/js/calendars/timespan.spec.js +45 -1
- data/spec/js/numbers/abbreviated/abbreviated_number.spec.js +47 -0
- data/spec/js/numbers/abbreviated/long_decimal.spec.js +57 -0
- data/spec/js/numbers/abbreviated/short_decimal.spec.js +57 -0
- data/spec/js/numbers/currency.spec.js +2 -2
- data/spec/js/numbers/decimal.spec.js +1 -1
- data/spec/js/numbers/helpers/fraction.spec.js +1 -1
- data/spec/js/numbers/helpers/integer.spec.js +1 -1
- data/spec/js/numbers/number.spec.js +1 -1
- data/spec/js/numbers/percent.spec.js +1 -1
- data/spec/js/plurals/plural_rules.spec.js +1 -1
- data/spec/js/shared/bidi.spec.js +105 -0
- data/spec/js/shared/calendar.spec.js +51 -0
- data/spec/js/shared/classpath_bidi_test.txt +217202 -0
- data/spec/js/shared/lists.spec.js +100 -0
- data/spec/js/utilities.spec.js +120 -0
- data/twitter_cldr_js.gemspec +1 -1
- metadata +68 -37
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_af.js +0 -887
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_ar.js +0 -887
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_ca.js +0 -887
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_cs.js +0 -887
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_da.js +0 -887
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_de.js +0 -887
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_el.js +0 -887
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_en.js +0 -887
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_es.js +0 -887
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_eu.js +0 -887
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_fa.js +0 -887
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_fi.js +0 -887
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_fil.js +0 -887
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_fr.js +0 -887
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_he.js +0 -887
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_hi.js +0 -887
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_hu.js +0 -887
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_id.js +0 -887
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_it.js +0 -887
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_ja.js +0 -887
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_ko.js +0 -887
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_msa.js +0 -887
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_nl.js +0 -887
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_no.js +0 -887
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_pl.js +0 -887
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_pt.js +0 -887
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_ru.js +0 -887
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_sv.js +0 -887
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_th.js +0 -887
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_tr.js +0 -887
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_uk.js +0 -887
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_ur.js +0 -887
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_zh-cn.js +0 -887
- data/lib/assets/javascripts/twitter_cldr/twitter_cldr_zh-tw.js +0 -887
@@ -1,8 +1,9 @@
|
|
1
1
|
# Copyright 2012 Twitter, Inc
|
2
2
|
# http://www.apache.org/licenses/LICENSE-2.0
|
3
3
|
|
4
|
-
TwitterCldr.TimespanFormatter
|
4
|
+
class TwitterCldr.TimespanFormatter
|
5
5
|
constructor: ->
|
6
|
+
@approximate_multiplier = 0.75
|
6
7
|
@default_type = "default"
|
7
8
|
@tokens = `{{{tokens}}}`
|
8
9
|
@time_in_seconds = {
|
@@ -15,9 +16,11 @@ TwitterCldr.TimespanFormatter = class TimespanFormatter
|
|
15
16
|
"year": 31556926
|
16
17
|
}
|
17
18
|
|
18
|
-
format: (seconds,
|
19
|
+
format: (seconds, fmt_options = {}) ->
|
20
|
+
options = {}
|
21
|
+
options[key] = obj for key, obj of fmt_options
|
19
22
|
options["direction"] ||= (if seconds < 0 then "ago" else "until")
|
20
|
-
options["unit"] = this.calculate_unit(Math.abs(seconds)) if options["unit"] is null or options["unit"] is undefined
|
23
|
+
options["unit"] = this.calculate_unit(Math.abs(seconds), options) if options["unit"] is null or options["unit"] is undefined
|
21
24
|
options["type"] ||= @default_type
|
22
25
|
options["number"] = this.calculate_time(Math.abs(seconds), options["unit"])
|
23
26
|
number = this.calculate_time(Math.abs(seconds), options["unit"])
|
@@ -26,21 +29,19 @@ TwitterCldr.TimespanFormatter = class TimespanFormatter
|
|
26
29
|
strings = (token.value for token in @tokens[options["direction"]][options["unit"]][options["type"]][options["rule"]])
|
27
30
|
strings.join("").replace(/\{[0-9]\}/, number.toString())
|
28
31
|
|
29
|
-
calculate_unit: (seconds) ->
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
else if seconds <
|
37
|
-
|
38
|
-
else if seconds <
|
39
|
-
|
40
|
-
else if seconds <
|
41
|
-
|
42
|
-
else
|
43
|
-
"year"
|
32
|
+
calculate_unit: (seconds, unit_options = {}) ->
|
33
|
+
options = {}
|
34
|
+
options[key] = obj for key, obj of unit_options
|
35
|
+
options["approximate"] = false unless options.approximate?
|
36
|
+
multiplier = if options.approximate then @approximate_multiplier else 1
|
37
|
+
|
38
|
+
if seconds < (@time_in_seconds.minute * multiplier) then "second"
|
39
|
+
else if seconds < (@time_in_seconds.hour * multiplier) then "minute"
|
40
|
+
else if seconds < (@time_in_seconds.day * multiplier) then "hour"
|
41
|
+
else if seconds < (@time_in_seconds.week * multiplier) then "day"
|
42
|
+
else if seconds < (@time_in_seconds.month * multiplier) then "week"
|
43
|
+
else if seconds < (@time_in_seconds.year * multiplier) then "month"
|
44
|
+
else "year"
|
44
45
|
|
45
46
|
# 0 <-> 29 secs # => seconds
|
46
47
|
# 30 secs <-> 44 mins, 29 secs # => minutes
|
@@ -1,13 +1,13 @@
|
|
1
1
|
# Copyright 2012 Twitter, Inc
|
2
2
|
# http://www.apache.org/licenses/LICENSE-2.0
|
3
3
|
|
4
|
-
TwitterCldr.NumberFormatter
|
5
|
-
|
4
|
+
class TwitterCldr.NumberFormatter
|
5
|
+
constructor: ->
|
6
6
|
@all_tokens = `{{{tokens}}}`
|
7
7
|
@tokens = []
|
8
8
|
@symbols = `{{{symbols}}}`
|
9
9
|
|
10
|
-
@default_symbols =
|
10
|
+
@default_symbols =
|
11
11
|
'group': ','
|
12
12
|
'decimal': '.'
|
13
13
|
'plus_sign': '+'
|
@@ -20,17 +20,23 @@ TwitterCldr.NumberFormatter = class NumberFormatter
|
|
20
20
|
opts[key] = if options[key]? then options[key] else opts[key]
|
21
21
|
|
22
22
|
[prefix, suffix, integer_format, fraction_format] = this.partition_tokens(this.get_tokens(number, opts))
|
23
|
-
|
24
|
-
|
23
|
+
number = this.transform_number(number)
|
24
|
+
[intg, fraction] = this.parse_number(number, opts)
|
25
|
+
result = integer_format.apply(parseFloat(intg), opts)
|
25
26
|
result += fraction_format.apply(fraction, opts) if fraction
|
26
27
|
sign = if number < 0 && prefix != "-" then @symbols.minus_sign || @default_symbols.minus_sign else ""
|
27
28
|
"#{prefix}#{result}#{suffix}"
|
28
29
|
|
30
|
+
transform_number: (number) ->
|
31
|
+
number # noop for base class
|
32
|
+
|
29
33
|
partition_tokens: (tokens) ->
|
30
|
-
[
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
+
[
|
35
|
+
tokens[0] || "",
|
36
|
+
tokens[2] || "",
|
37
|
+
new TwitterCldr.NumberFormatter.IntegerHelper(tokens[1], @symbols),
|
38
|
+
new TwitterCldr.NumberFormatter.FractionHelper(tokens[1], @symbols)
|
39
|
+
]
|
34
40
|
|
35
41
|
parse_number: (number, options = {}) ->
|
36
42
|
if options.precision?
|
@@ -52,7 +58,7 @@ TwitterCldr.NumberFormatter = class NumberFormatter
|
|
52
58
|
get_tokens: ->
|
53
59
|
throw "get_tokens() not implemented - use a derived class like PercentFormatter."
|
54
60
|
|
55
|
-
TwitterCldr.PercentFormatter
|
61
|
+
class TwitterCldr.PercentFormatter extends TwitterCldr.NumberFormatter
|
56
62
|
constructor: (options = {}) ->
|
57
63
|
@default_percent_sign = "%"
|
58
64
|
super
|
@@ -66,7 +72,7 @@ TwitterCldr.PercentFormatter = class PercentFormatter extends NumberFormatter
|
|
66
72
|
get_tokens: (number, options) ->
|
67
73
|
if number < 0 then @all_tokens.percent.negative else @all_tokens.percent.positive
|
68
74
|
|
69
|
-
TwitterCldr.DecimalFormatter
|
75
|
+
class TwitterCldr.DecimalFormatter extends TwitterCldr.NumberFormatter
|
70
76
|
format: (number, options = {}) ->
|
71
77
|
try
|
72
78
|
super(number, options)
|
@@ -79,7 +85,7 @@ TwitterCldr.DecimalFormatter = class DecimalFormatter extends NumberFormatter
|
|
79
85
|
get_tokens: (number, options = {}) ->
|
80
86
|
if number < 0 then @all_tokens.decimal.negative else @all_tokens.decimal.positive
|
81
87
|
|
82
|
-
TwitterCldr.CurrencyFormatter
|
88
|
+
class TwitterCldr.CurrencyFormatter extends TwitterCldr.NumberFormatter
|
83
89
|
constructor: (options = {}) ->
|
84
90
|
@default_currency_symbol = "$"
|
85
91
|
@default_precision = 2
|
@@ -89,7 +95,6 @@ TwitterCldr.CurrencyFormatter = class CurrencyFormatter extends NumberFormatter
|
|
89
95
|
if options.currency
|
90
96
|
if TwitterCldr.Currencies?
|
91
97
|
currency = TwitterCldr.Currencies.for_code(options.currency)
|
92
|
-
currency ||= TwitterCldr.Currencies.for_country(options.currency)
|
93
98
|
currency ||= symbol: options.currency
|
94
99
|
else
|
95
100
|
currency = symbol: options.currency
|
@@ -106,7 +111,44 @@ TwitterCldr.CurrencyFormatter = class CurrencyFormatter extends NumberFormatter
|
|
106
111
|
get_tokens: (number, options = {}) ->
|
107
112
|
if number < 0 then @all_tokens.currency.negative else @all_tokens.currency.positive
|
108
113
|
|
109
|
-
TwitterCldr.
|
114
|
+
class TwitterCldr.AbbreviatedNumberFormatter extends TwitterCldr.NumberFormatter
|
115
|
+
NUMBER_MAX: Math.pow(10, 15)
|
116
|
+
NUMBER_MIN: 1000
|
117
|
+
|
118
|
+
default_format_options_for: (number) ->
|
119
|
+
precision: this.precision_from(number)
|
120
|
+
|
121
|
+
get_type: ->
|
122
|
+
"decimal"
|
123
|
+
|
124
|
+
get_key: (number) ->
|
125
|
+
zeroes = ("0" for i in [0...(Math.floor(number).toString().length - 1)]).join("")
|
126
|
+
"1#{zeroes}"
|
127
|
+
|
128
|
+
get_tokens: (number, options = {}) ->
|
129
|
+
type = if (number < @NUMBER_MAX) && (number >= @NUMBER_MIN) then this.get_type() else "decimal"
|
130
|
+
format = if type == this.get_type() then this.get_key(number) else null
|
131
|
+
tokens = @all_tokens[type]
|
132
|
+
tokens = if number < 0 then tokens.negative else tokens.positive
|
133
|
+
tokens = tokens[format] if format?
|
134
|
+
tokens
|
135
|
+
|
136
|
+
transform_number: (number) ->
|
137
|
+
if (number < @NUMBER_MAX) && (number >= @NUMBER_MIN)
|
138
|
+
sig_figs = ((parseInt(number).toString().length - 1) % 3)
|
139
|
+
parseInt(number.toString()[0..sig_figs])
|
140
|
+
else
|
141
|
+
number
|
142
|
+
|
143
|
+
class TwitterCldr.ShortDecimalFormatter extends TwitterCldr.AbbreviatedNumberFormatter
|
144
|
+
get_type: ->
|
145
|
+
"short_decimal"
|
146
|
+
|
147
|
+
class TwitterCldr.LongDecimalFormatter extends TwitterCldr.AbbreviatedNumberFormatter
|
148
|
+
get_type: ->
|
149
|
+
"long_decimal"
|
150
|
+
|
151
|
+
class TwitterCldr.NumberFormatter.BaseHelper
|
110
152
|
interpolate: (string, value, orientation = "right") ->
|
111
153
|
value = value.toString()
|
112
154
|
length = value.length
|
@@ -120,7 +162,7 @@ TwitterCldr.NumberFormatter.BaseHelper = class BaseHelper
|
|
120
162
|
|
121
163
|
string.replace(/#/g, "")
|
122
164
|
|
123
|
-
TwitterCldr.NumberFormatter.IntegerHelper
|
165
|
+
class TwitterCldr.NumberFormatter.IntegerHelper extends TwitterCldr.NumberFormatter.BaseHelper
|
124
166
|
constructor: (token, symbols = {}) ->
|
125
167
|
format = token.split('.')[0]
|
126
168
|
@format = this.prepare_format(format, symbols)
|
@@ -147,12 +189,13 @@ TwitterCldr.NumberFormatter.IntegerHelper = class IntegerHelper extends BaseHelp
|
|
147
189
|
(token for token in tokens when token != null).reverse().join(@separator)
|
148
190
|
|
149
191
|
parse_groups: (format) ->
|
150
|
-
|
192
|
+
index = format.lastIndexOf(',')
|
193
|
+
return [] unless index > 0
|
151
194
|
rest = format[0...index]
|
152
195
|
widths = [format.length - index - 1]
|
153
196
|
widths.push(rest.length - rest.lastIndexOf(',') - 1) if rest.lastIndexOf(',') > -1
|
154
|
-
widths = (width for width in widths when width != null)
|
155
|
-
widths.reverse()
|
197
|
+
widths = (width for width in widths when width != null) # compact
|
198
|
+
widths.reverse() # uniq
|
156
199
|
(widths[index] for index in [0...widths.length] when widths.indexOf(widths[index], index + 1) == -1).reverse()
|
157
200
|
|
158
201
|
chop_group: (string, size) ->
|
@@ -161,7 +204,7 @@ TwitterCldr.NumberFormatter.IntegerHelper = class IntegerHelper extends BaseHelp
|
|
161
204
|
prepare_format: (format, symbols) ->
|
162
205
|
format.replace(",", "").replace("+", symbols.plus_sign).replace("-", symbols.minus_sign)
|
163
206
|
|
164
|
-
TwitterCldr.NumberFormatter.FractionHelper
|
207
|
+
class TwitterCldr.NumberFormatter.FractionHelper extends TwitterCldr.NumberFormatter.BaseHelper
|
165
208
|
constructor: (token, symbols = {}) ->
|
166
209
|
@format = if token then token.split('.').pop() else ""
|
167
210
|
@decimal = symbols.decimal || "."
|
@@ -0,0 +1,433 @@
|
|
1
|
+
# Copyright 2012 Twitter, Inc
|
2
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
3
|
+
|
4
|
+
class TwitterCldr.Bidi
|
5
|
+
MAX_DEPTH = 62
|
6
|
+
|
7
|
+
constructor: (options = {}) ->
|
8
|
+
@bidi_classes = `{{{bidi_classes}}}`
|
9
|
+
@string_arr = options.string_arr || options.types
|
10
|
+
@types = options.types || []
|
11
|
+
@levels = []
|
12
|
+
@runs = []
|
13
|
+
@direction = options.direction
|
14
|
+
@default_direction = options.default_direction || "LTR"
|
15
|
+
@length = @types.length
|
16
|
+
this.run_bidi()
|
17
|
+
|
18
|
+
@bidi_class_for: (code_point) ->
|
19
|
+
for bidi_class, ranges of @bidi_classes
|
20
|
+
for range_offset, range_list of ranges
|
21
|
+
for range in range_list
|
22
|
+
start = range
|
23
|
+
end = start + parseInt(range_offset)
|
24
|
+
if (code_point >= start) && (code_point <= end)
|
25
|
+
return bidi_class
|
26
|
+
|
27
|
+
null
|
28
|
+
|
29
|
+
@from_string: (str, options = {}) ->
|
30
|
+
string_arr = TwitterCldr.Utilities.unpack_string(str)
|
31
|
+
options.types ||= this.compute_types(string_arr)
|
32
|
+
options.string_arr ||= string_arr
|
33
|
+
new TwitterCldr.Bidi(options)
|
34
|
+
|
35
|
+
@from_type_array: (types, options = {}) ->
|
36
|
+
options.types ||= types
|
37
|
+
new TwitterCldr.Bidi(options)
|
38
|
+
|
39
|
+
@compute_types: (arr) ->
|
40
|
+
TwitterCldr.Bidi.bidi_class_for(code_point) for code_point in arr
|
41
|
+
|
42
|
+
toString: ->
|
43
|
+
TwitterCldr.Utilities.pack_array(@string_arr)
|
44
|
+
|
45
|
+
reorder_visually: ->
|
46
|
+
throw "No string given!" unless @string_arr
|
47
|
+
|
48
|
+
# Do this explicitly so we can also find the maximum depth at the
|
49
|
+
# same time.
|
50
|
+
max = 0
|
51
|
+
lowest_odd = MAX_DEPTH + 1
|
52
|
+
|
53
|
+
for level in @levels
|
54
|
+
max = TwitterCldr.Utilities.max([level, max])
|
55
|
+
lowest_odd = TwitterCldr.Utilities.min([lowest_odd, level]) unless TwitterCldr.Utilities.is_even(level)
|
56
|
+
|
57
|
+
# Reverse the runs starting with the deepest.
|
58
|
+
for depth in [max...0]
|
59
|
+
start = 0
|
60
|
+
|
61
|
+
while start < @levels.length
|
62
|
+
# Find the start of a run >= DEPTH.
|
63
|
+
start += 1 while start < @levels.length && @levels[start] < depth
|
64
|
+
|
65
|
+
break if start == @levels.length
|
66
|
+
|
67
|
+
# Find the end of the run.
|
68
|
+
finish = start + 1
|
69
|
+
finish += 1 while finish < @levels.length && @levels[finish] >= depth
|
70
|
+
|
71
|
+
# Reverse this run.
|
72
|
+
for i in [0...((finish - start) / 2)]
|
73
|
+
tmpb = @levels[finish - i - 1]
|
74
|
+
@levels[finish - i - 1] = @levels[start + i]
|
75
|
+
@levels[start + i] = tmpb
|
76
|
+
|
77
|
+
tmpo = @string_arr[finish - i - 1]
|
78
|
+
@string_arr[finish - i - 1] = @string_arr[start + i]
|
79
|
+
@string_arr[start + i] = tmpo
|
80
|
+
|
81
|
+
# Handle the next run.
|
82
|
+
start = finish + 1
|
83
|
+
|
84
|
+
this
|
85
|
+
|
86
|
+
compute_paragraph_embedding_level: ->
|
87
|
+
# First check to see if the user supplied a directionality override.
|
88
|
+
if ["LTR", "RTL"].indexOf(@direction) > -1
|
89
|
+
if @direction == "LTR" then 0 else 1
|
90
|
+
else
|
91
|
+
# This implements rules P2 and P3.
|
92
|
+
# (Note that we don't need P1, as the user supplies
|
93
|
+
# a paragraph.)
|
94
|
+
for type in @types
|
95
|
+
return 0 if type == "L"
|
96
|
+
return 1 if type == "R"
|
97
|
+
|
98
|
+
if @default_direction == "LTR" then 0 else 1
|
99
|
+
|
100
|
+
compute_explicit_levels: ->
|
101
|
+
current_embedding = @base_embedding
|
102
|
+
|
103
|
+
# The directional override is a Character directionality
|
104
|
+
# constant. -1 means there is no override.
|
105
|
+
directional_override = -1
|
106
|
+
|
107
|
+
# The stack of pushed embeddings, and the stack pointer.
|
108
|
+
# Note that because the direction is inherent in the depth,
|
109
|
+
# and because we have a bit left over in a byte, we can encode
|
110
|
+
# the override, if any, directly in this value on the stack.
|
111
|
+
|
112
|
+
embedding_stack = []
|
113
|
+
@formatter_indices ||= []
|
114
|
+
sp = 0
|
115
|
+
|
116
|
+
for i in [0...@length]
|
117
|
+
is_ltr = false
|
118
|
+
is_special = true
|
119
|
+
is_ltr = @types[i] == "LRE" || @types[i] == "LRO"
|
120
|
+
|
121
|
+
switch @types[i]
|
122
|
+
when "RLE", "RLO", "LRE", "LRO"
|
123
|
+
new_embedding = if is_ltr
|
124
|
+
# Least greater even.
|
125
|
+
((current_embedding & ~1) + 2)
|
126
|
+
else
|
127
|
+
# Least greater odd.
|
128
|
+
((current_embedding + 1) | 1)
|
129
|
+
|
130
|
+
# FIXME: we don't properly handle invalid pushes.
|
131
|
+
if new_embedding < MAX_DEPTH
|
132
|
+
# The new level is valid. Push the old value.
|
133
|
+
# See above for a comment on the encoding here.
|
134
|
+
|
135
|
+
current_embedding |= -0x80 if (directional_override != -1)
|
136
|
+
embedding_stack[sp] = current_embedding
|
137
|
+
current_embedding = new_embedding
|
138
|
+
sp += 1
|
139
|
+
|
140
|
+
directional_override = if @types[i] == "LRO"
|
141
|
+
"L"
|
142
|
+
else if @types[i] == "RLO"
|
143
|
+
"R"
|
144
|
+
else
|
145
|
+
-1
|
146
|
+
|
147
|
+
when "PDF"
|
148
|
+
# FIXME: we don't properly handle a pop with a corresponding
|
149
|
+
# invalid push.
|
150
|
+
# If sp === 0, we saw a pop without a push. Just ignore it.
|
151
|
+
if sp > 0
|
152
|
+
sp -= 1
|
153
|
+
new_embedding = embedding_stack[sp]
|
154
|
+
current_embedding = new_embedding & 0x7f
|
155
|
+
|
156
|
+
directional_override = if new_embedding < 0
|
157
|
+
(new_embedding & 1) == 0 ? "L" : "R"
|
158
|
+
else
|
159
|
+
-1
|
160
|
+
|
161
|
+
else
|
162
|
+
is_special = false
|
163
|
+
|
164
|
+
@levels[i] = current_embedding
|
165
|
+
|
166
|
+
if is_special
|
167
|
+
# Mark this character for removal.
|
168
|
+
@formatter_indices.push(i)
|
169
|
+
else if directional_override != -1
|
170
|
+
@types[i] = directional_override
|
171
|
+
|
172
|
+
# Remove the formatting codes and update both the arrays
|
173
|
+
# and 'length'. It would be more efficient not to remove
|
174
|
+
# these codes, but it is also more complicated. Also, the
|
175
|
+
# Unicode algorithm reference does not properly describe
|
176
|
+
# how this is to be done -- from what I can tell, their suggestions
|
177
|
+
# in this area will not yield the correct results.
|
178
|
+
|
179
|
+
output = 0
|
180
|
+
input = 0
|
181
|
+
size = @formatter_indices.length
|
182
|
+
|
183
|
+
for i in [0..size]
|
184
|
+
next_fmt = if i == size
|
185
|
+
@length
|
186
|
+
else
|
187
|
+
@formatter_indices[i]
|
188
|
+
|
189
|
+
len = next_fmt - input
|
190
|
+
|
191
|
+
# Non-formatter codes are from 'input' to 'next_fmt'.
|
192
|
+
TwitterCldr.Utilities.arraycopy(@levels, input, @levels, output, len)
|
193
|
+
TwitterCldr.Utilities.arraycopy(@types, input, @types, output, len)
|
194
|
+
|
195
|
+
output += len
|
196
|
+
input = next_fmt + 1
|
197
|
+
|
198
|
+
@length -= @formatter_indices.length
|
199
|
+
|
200
|
+
compute_runs: ->
|
201
|
+
run_count = 0
|
202
|
+
current_embedding = @base_embedding
|
203
|
+
|
204
|
+
for i in [0...@length]
|
205
|
+
if @levels[i] != current_embedding
|
206
|
+
current_embedding = @levels[i]
|
207
|
+
run_count += 1
|
208
|
+
|
209
|
+
# This may be called multiple times. If so, and if
|
210
|
+
# the number of runs has not changed, then don't bother
|
211
|
+
# allocating a new array.
|
212
|
+
where = 0
|
213
|
+
last_run_start = 0
|
214
|
+
current_embedding = @base_embedding
|
215
|
+
|
216
|
+
for i in [0...@length]
|
217
|
+
if @levels[i] != current_embedding
|
218
|
+
@runs[where] = last_run_start
|
219
|
+
where += 1
|
220
|
+
last_run_start = i
|
221
|
+
current_embedding = @levels[i]
|
222
|
+
|
223
|
+
@runs[where] = last_run_start
|
224
|
+
|
225
|
+
resolve_weak_types: ->
|
226
|
+
run_count = @runs.length
|
227
|
+
previous_level = @base_embedding
|
228
|
+
|
229
|
+
for run_idx in [0...run_count]
|
230
|
+
start = this.get_run_start(run_idx)
|
231
|
+
finish = this.get_run_limit(run_idx)
|
232
|
+
level = this.get_run_level(run_idx) || 0
|
233
|
+
|
234
|
+
# These are the names used in the Bidi algorithm.
|
235
|
+
sor = if TwitterCldr.Utilities.is_even(TwitterCldr.Utilities.max([previous_level, level])) then "L" else "R"
|
236
|
+
|
237
|
+
next_level = if run_idx == (run_count - 1)
|
238
|
+
@base_embedding
|
239
|
+
else
|
240
|
+
this.get_run_level(run_idx + 1) || 0
|
241
|
+
|
242
|
+
eor = if TwitterCldr.Utilities.is_even(TwitterCldr.Utilities.max([level, next_level])) then "L" else "R"
|
243
|
+
prev_type = sor
|
244
|
+
prev_strong_type = sor
|
245
|
+
|
246
|
+
for i in [start...finish]
|
247
|
+
next_type = if i == (finish - 1) then eor else @types[i + 1]
|
248
|
+
|
249
|
+
# Rule W1: change NSM to the prevailing direction.
|
250
|
+
if @types[i] == "NSM"
|
251
|
+
@types[i] = prev_type
|
252
|
+
else
|
253
|
+
prev_type = @types[i]
|
254
|
+
|
255
|
+
# Rule W2: change EN to AN in some cases.
|
256
|
+
if @types[i] == "EN"
|
257
|
+
if prev_strong_type == "AL"
|
258
|
+
@types[i] = "AN"
|
259
|
+
else if @types[i] == "L" || @types[i] == "R" || @types[i] == "AL"
|
260
|
+
prev_strong_type = @types[i]
|
261
|
+
|
262
|
+
# Rule W3: change AL to R.
|
263
|
+
if @types[i] == "AL"
|
264
|
+
@types[i] = "R"
|
265
|
+
|
266
|
+
# Rule W4: handle separators between two numbers.
|
267
|
+
if prev_type == "EN" && next_type == "EN"
|
268
|
+
if @types[i] == "ES" || @types[i] == "CS"
|
269
|
+
@types[i] = nextType
|
270
|
+
else if prev_type == "AN" && next_type == "AN" && @types[i] == "CS"
|
271
|
+
@types[i] = next_type
|
272
|
+
|
273
|
+
# Rule W5: change a sequence of european terminators to
|
274
|
+
# european numbers, if they are adjacent to european numbers.
|
275
|
+
# We also include BN characters in this.
|
276
|
+
if @types[i] == "ET" || @types[i] == "BN"
|
277
|
+
if prev_type == "EN"
|
278
|
+
@types[i] = prev_type
|
279
|
+
else
|
280
|
+
# Look ahead to see if there is an EN terminating this
|
281
|
+
# sequence of ETs.
|
282
|
+
j = i + 1
|
283
|
+
|
284
|
+
while j < finish && @types[j] == "ET" || @types[j] == "BN"
|
285
|
+
j += 1
|
286
|
+
|
287
|
+
if j < finish && @types[j] == "EN"
|
288
|
+
# Change them all to EN now.
|
289
|
+
for k in [i...j]
|
290
|
+
@types[k] = "EN"
|
291
|
+
|
292
|
+
# Rule W6: separators and terminators change to ON.
|
293
|
+
# Again we include BN.
|
294
|
+
if @types[i] == "ET" || @types[i] == "CS" || @types[i] == "BN"
|
295
|
+
@types[i] = "ON"
|
296
|
+
|
297
|
+
# Rule W7: change european number types.
|
298
|
+
if prev_strong_type == "L" && @types[i] == "EN"
|
299
|
+
@types[i] = prev_strong_type
|
300
|
+
|
301
|
+
previous_level = level
|
302
|
+
|
303
|
+
return
|
304
|
+
|
305
|
+
get_run_count: ->
|
306
|
+
@runs.length
|
307
|
+
|
308
|
+
get_run_level: (which) ->
|
309
|
+
@levels[@runs[which]]
|
310
|
+
|
311
|
+
get_run_limit: (which) ->
|
312
|
+
if which == (@runs.length - 1)
|
313
|
+
@length
|
314
|
+
else
|
315
|
+
@runs[which + 1]
|
316
|
+
|
317
|
+
get_run_start: (which) ->
|
318
|
+
@runs[which]
|
319
|
+
|
320
|
+
resolve_implicit_levels: ->
|
321
|
+
# This implements rules I1 and I2.
|
322
|
+
for i in [0...@length]
|
323
|
+
if (@levels[i] & 1) == 0
|
324
|
+
if @types[i] == "R"
|
325
|
+
@levels[i] += 1
|
326
|
+
else if @types[i] == "AN" || @types[i] == "EN"
|
327
|
+
@levels[i] += 2
|
328
|
+
else
|
329
|
+
if @types[i] == "L" || @types[i] == "AN" || @types[i] == "EN"
|
330
|
+
@levels[i] += 1
|
331
|
+
return
|
332
|
+
|
333
|
+
resolve_neutral_types: ->
|
334
|
+
# This implements rules N1 and N2.
|
335
|
+
run_count = this.get_run_count()
|
336
|
+
previous_level = @base_embedding
|
337
|
+
|
338
|
+
for run in [0...run_count]
|
339
|
+
start = this.get_run_start(run)
|
340
|
+
finish = this.get_run_limit(run)
|
341
|
+
level = this.get_run_level(run)
|
342
|
+
continue unless level?
|
343
|
+
|
344
|
+
embedding_direction = if TwitterCldr.Utilities.is_even(level) then "L" else "R"
|
345
|
+
# These are the names used in the Bidi algorithm.
|
346
|
+
sor = if TwitterCldr.Utilities.is_even(TwitterCldr.Utilities.max([previous_level, level])) then "L" else "R"
|
347
|
+
|
348
|
+
next_level = if run == (run_count - 1)
|
349
|
+
@base_embedding
|
350
|
+
else
|
351
|
+
this.get_run_level(run + 1)
|
352
|
+
|
353
|
+
eor = if TwitterCldr.Utilities.is_even(TwitterCldr.Utilities.max([level, next_level])) then "L" else "R"
|
354
|
+
prev_strong = sor
|
355
|
+
neutral_start = -1
|
356
|
+
|
357
|
+
for i in [start..finish]
|
358
|
+
new_strong = -1
|
359
|
+
this_type = if i == finish then eor else @types[i]
|
360
|
+
|
361
|
+
switch this_type
|
362
|
+
when "L"
|
363
|
+
new_strong = "L"
|
364
|
+
when "R", "AN", "EN"
|
365
|
+
new_strong = "R"
|
366
|
+
when "BN", "ON", "S", "B", "WS"
|
367
|
+
neutral_start = i if neutral_start == -1
|
368
|
+
|
369
|
+
# If we see a strong character, update all the neutrals.
|
370
|
+
if new_strong != -1
|
371
|
+
if neutral_start != -1
|
372
|
+
override = if prev_strong == new_strong then prev_strong else embedding_direction
|
373
|
+
for j in [neutral_start...i]
|
374
|
+
@types[j] = override
|
375
|
+
|
376
|
+
prev_strong = new_strong
|
377
|
+
neutral_start = -1
|
378
|
+
|
379
|
+
previous_level = level
|
380
|
+
|
381
|
+
return
|
382
|
+
|
383
|
+
reinsert_formatting_codes: ->
|
384
|
+
if @formatter_indices? && @formatter_indices.length > 0
|
385
|
+
input = @length
|
386
|
+
output = @levels.length
|
387
|
+
|
388
|
+
# Process from the end as we are copying the array over itself here.
|
389
|
+
for index in [(@formatter_indices.length - 1)..0]
|
390
|
+
next_fmt = @formatter_indices[index]
|
391
|
+
|
392
|
+
# nextFmt points to a location in the original array. So,
|
393
|
+
# nextFmt+1 is the target of our copying. output is the location
|
394
|
+
# to which we last copied, thus we can derive the length of the
|
395
|
+
# copy from it.
|
396
|
+
len = output - next_fmt - 1
|
397
|
+
output = next_fmt
|
398
|
+
input -= len
|
399
|
+
|
400
|
+
# Note that we no longer need 'types' at this point, so we
|
401
|
+
# only edit 'levels'.
|
402
|
+
if next_fmt + 1 < @levels.length
|
403
|
+
TwitterCldr.Utilities.arraycopy(@levels, input, @levels, next_fmt + 1, len)
|
404
|
+
|
405
|
+
# Now set the level at the reinsertion point.
|
406
|
+
right_level = if output == @levels.length - 1
|
407
|
+
@base_embedding
|
408
|
+
else
|
409
|
+
if @levels[output + 1]? then @levels[output + 1] else 0
|
410
|
+
|
411
|
+
left_level = if input == 0
|
412
|
+
@base_embedding
|
413
|
+
else
|
414
|
+
if @levels[input]? then @levels[input] else 0
|
415
|
+
|
416
|
+
@levels[output] = TwitterCldr.Utilities.max([left_level, right_level])
|
417
|
+
|
418
|
+
@length = @levels.length
|
419
|
+
|
420
|
+
run_bidi: ->
|
421
|
+
@base_embedding = this.compute_paragraph_embedding_level()
|
422
|
+
|
423
|
+
this.compute_explicit_levels()
|
424
|
+
this.compute_runs()
|
425
|
+
this.resolve_weak_types()
|
426
|
+
this.resolve_neutral_types()
|
427
|
+
this.resolve_implicit_levels()
|
428
|
+
this.reinsert_formatting_codes()
|
429
|
+
|
430
|
+
# After resolving the implicit levels, the number
|
431
|
+
# of runs may have changed.
|
432
|
+
this.compute_runs()
|
433
|
+
return
|