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.
Files changed (129) hide show
  1. data/Gemfile +5 -0
  2. data/History.txt +11 -1
  3. data/README.md +103 -13
  4. data/Rakefile +1 -1
  5. data/lib/assets/javascripts/twitter_cldr/af.js +1815 -0
  6. data/lib/assets/javascripts/twitter_cldr/ar.js +1815 -0
  7. data/lib/assets/javascripts/twitter_cldr/be.js +1815 -0
  8. data/lib/assets/javascripts/twitter_cldr/bg.js +1815 -0
  9. data/lib/assets/javascripts/twitter_cldr/bn.js +1815 -0
  10. data/lib/assets/javascripts/twitter_cldr/ca.js +1815 -0
  11. data/lib/assets/javascripts/twitter_cldr/cs.js +1815 -0
  12. data/lib/assets/javascripts/twitter_cldr/cy.js +1815 -0
  13. data/lib/assets/javascripts/twitter_cldr/da.js +1815 -0
  14. data/lib/assets/javascripts/twitter_cldr/de.js +1815 -0
  15. data/lib/assets/javascripts/twitter_cldr/el.js +1815 -0
  16. data/lib/assets/javascripts/twitter_cldr/en.js +1815 -0
  17. data/lib/assets/javascripts/twitter_cldr/es.js +1815 -0
  18. data/lib/assets/javascripts/twitter_cldr/eu.js +1815 -0
  19. data/lib/assets/javascripts/twitter_cldr/fa.js +1815 -0
  20. data/lib/assets/javascripts/twitter_cldr/fi.js +1815 -0
  21. data/lib/assets/javascripts/twitter_cldr/fil.js +1815 -0
  22. data/lib/assets/javascripts/twitter_cldr/fr.js +1815 -0
  23. data/lib/assets/javascripts/twitter_cldr/ga.js +1815 -0
  24. data/lib/assets/javascripts/twitter_cldr/gl.js +1815 -0
  25. data/lib/assets/javascripts/twitter_cldr/he.js +1815 -0
  26. data/lib/assets/javascripts/twitter_cldr/hi.js +1815 -0
  27. data/lib/assets/javascripts/twitter_cldr/hu.js +1815 -0
  28. data/lib/assets/javascripts/twitter_cldr/id.js +1815 -0
  29. data/lib/assets/javascripts/twitter_cldr/it.js +1815 -0
  30. data/lib/assets/javascripts/twitter_cldr/ja.js +1815 -0
  31. data/lib/assets/javascripts/twitter_cldr/ko.js +1815 -0
  32. data/lib/assets/javascripts/twitter_cldr/lv.js +1815 -0
  33. data/lib/assets/javascripts/twitter_cldr/msa.js +1815 -0
  34. data/lib/assets/javascripts/twitter_cldr/nl.js +1815 -0
  35. data/lib/assets/javascripts/twitter_cldr/no.js +1815 -0
  36. data/lib/assets/javascripts/twitter_cldr/pl.js +1815 -0
  37. data/lib/assets/javascripts/twitter_cldr/pt.js +1815 -0
  38. data/lib/assets/javascripts/twitter_cldr/ro.js +1815 -0
  39. data/lib/assets/javascripts/twitter_cldr/ru.js +1815 -0
  40. data/lib/assets/javascripts/twitter_cldr/sk.js +1815 -0
  41. data/lib/assets/javascripts/twitter_cldr/sq.js +1815 -0
  42. data/lib/assets/javascripts/twitter_cldr/sr.js +1815 -0
  43. data/lib/assets/javascripts/twitter_cldr/sv.js +1815 -0
  44. data/lib/assets/javascripts/twitter_cldr/ta.js +1815 -0
  45. data/lib/assets/javascripts/twitter_cldr/th.js +1815 -0
  46. data/lib/assets/javascripts/twitter_cldr/tr.js +1815 -0
  47. data/lib/assets/javascripts/twitter_cldr/uk.js +1815 -0
  48. data/lib/assets/javascripts/twitter_cldr/ur.js +1815 -0
  49. data/lib/assets/javascripts/twitter_cldr/zh-cn.js +1815 -0
  50. data/lib/assets/javascripts/twitter_cldr/zh-tw.js +1815 -0
  51. data/lib/twitter_cldr/js/compiler.rb +12 -3
  52. data/lib/twitter_cldr/js/mustache/bundle.coffee +19 -1
  53. data/lib/twitter_cldr/js/mustache/calendars/additional_date_format_selector.coffee +85 -0
  54. data/lib/twitter_cldr/js/mustache/calendars/datetime.coffee +248 -226
  55. data/lib/twitter_cldr/js/mustache/calendars/timespan.coffee +19 -18
  56. data/lib/twitter_cldr/js/mustache/numbers/numbers.coffee +62 -19
  57. data/lib/twitter_cldr/js/mustache/plurals/rules.coffee +1 -1
  58. data/lib/twitter_cldr/js/mustache/shared/bidi.coffee +433 -0
  59. data/lib/twitter_cldr/js/mustache/shared/calendar.coffee +25 -0
  60. data/lib/twitter_cldr/js/mustache/shared/currencies.coffee +5 -11
  61. data/lib/twitter_cldr/js/mustache/shared/lists.coffee +40 -0
  62. data/lib/twitter_cldr/js/mustache/shared/prerender/bidi_classes.json +1 -0
  63. data/lib/twitter_cldr/js/mustache/utilities.coffee +87 -0
  64. data/lib/twitter_cldr/js/renderers.rb +23 -19
  65. data/lib/twitter_cldr/js/renderers/bundle.rb +8 -2
  66. data/lib/twitter_cldr/js/renderers/calendars/additional_date_format_selector_renderer.rb +18 -0
  67. data/lib/twitter_cldr/js/renderers/calendars/datetime_renderer.rb +13 -9
  68. data/lib/twitter_cldr/js/renderers/numbers/numbers_renderer.rb +17 -3
  69. data/lib/twitter_cldr/js/renderers/shared/bidi_renderer.rb +77 -0
  70. data/lib/twitter_cldr/js/renderers/shared/calendar_renderer.rb +20 -0
  71. data/lib/twitter_cldr/js/renderers/shared/currencies_renderer.rb +2 -2
  72. data/lib/twitter_cldr/js/renderers/shared/list_renderer.rb +22 -0
  73. data/lib/twitter_cldr/js/tasks/tasks.rake +9 -5
  74. data/lib/twitter_cldr/js/tasks/tasks.rb +6 -5
  75. data/lib/twitter_cldr/js/version.rb +1 -1
  76. data/spec/js/calendars/additional_date_format_selector.spec.js +126 -0
  77. data/spec/js/calendars/datetime.spec.js +29 -2
  78. data/spec/js/calendars/timespan.spec.js +45 -1
  79. data/spec/js/numbers/abbreviated/abbreviated_number.spec.js +47 -0
  80. data/spec/js/numbers/abbreviated/long_decimal.spec.js +57 -0
  81. data/spec/js/numbers/abbreviated/short_decimal.spec.js +57 -0
  82. data/spec/js/numbers/currency.spec.js +2 -2
  83. data/spec/js/numbers/decimal.spec.js +1 -1
  84. data/spec/js/numbers/helpers/fraction.spec.js +1 -1
  85. data/spec/js/numbers/helpers/integer.spec.js +1 -1
  86. data/spec/js/numbers/number.spec.js +1 -1
  87. data/spec/js/numbers/percent.spec.js +1 -1
  88. data/spec/js/plurals/plural_rules.spec.js +1 -1
  89. data/spec/js/shared/bidi.spec.js +105 -0
  90. data/spec/js/shared/calendar.spec.js +51 -0
  91. data/spec/js/shared/classpath_bidi_test.txt +217202 -0
  92. data/spec/js/shared/lists.spec.js +100 -0
  93. data/spec/js/utilities.spec.js +120 -0
  94. data/twitter_cldr_js.gemspec +1 -1
  95. metadata +68 -37
  96. data/lib/assets/javascripts/twitter_cldr/twitter_cldr_af.js +0 -887
  97. data/lib/assets/javascripts/twitter_cldr/twitter_cldr_ar.js +0 -887
  98. data/lib/assets/javascripts/twitter_cldr/twitter_cldr_ca.js +0 -887
  99. data/lib/assets/javascripts/twitter_cldr/twitter_cldr_cs.js +0 -887
  100. data/lib/assets/javascripts/twitter_cldr/twitter_cldr_da.js +0 -887
  101. data/lib/assets/javascripts/twitter_cldr/twitter_cldr_de.js +0 -887
  102. data/lib/assets/javascripts/twitter_cldr/twitter_cldr_el.js +0 -887
  103. data/lib/assets/javascripts/twitter_cldr/twitter_cldr_en.js +0 -887
  104. data/lib/assets/javascripts/twitter_cldr/twitter_cldr_es.js +0 -887
  105. data/lib/assets/javascripts/twitter_cldr/twitter_cldr_eu.js +0 -887
  106. data/lib/assets/javascripts/twitter_cldr/twitter_cldr_fa.js +0 -887
  107. data/lib/assets/javascripts/twitter_cldr/twitter_cldr_fi.js +0 -887
  108. data/lib/assets/javascripts/twitter_cldr/twitter_cldr_fil.js +0 -887
  109. data/lib/assets/javascripts/twitter_cldr/twitter_cldr_fr.js +0 -887
  110. data/lib/assets/javascripts/twitter_cldr/twitter_cldr_he.js +0 -887
  111. data/lib/assets/javascripts/twitter_cldr/twitter_cldr_hi.js +0 -887
  112. data/lib/assets/javascripts/twitter_cldr/twitter_cldr_hu.js +0 -887
  113. data/lib/assets/javascripts/twitter_cldr/twitter_cldr_id.js +0 -887
  114. data/lib/assets/javascripts/twitter_cldr/twitter_cldr_it.js +0 -887
  115. data/lib/assets/javascripts/twitter_cldr/twitter_cldr_ja.js +0 -887
  116. data/lib/assets/javascripts/twitter_cldr/twitter_cldr_ko.js +0 -887
  117. data/lib/assets/javascripts/twitter_cldr/twitter_cldr_msa.js +0 -887
  118. data/lib/assets/javascripts/twitter_cldr/twitter_cldr_nl.js +0 -887
  119. data/lib/assets/javascripts/twitter_cldr/twitter_cldr_no.js +0 -887
  120. data/lib/assets/javascripts/twitter_cldr/twitter_cldr_pl.js +0 -887
  121. data/lib/assets/javascripts/twitter_cldr/twitter_cldr_pt.js +0 -887
  122. data/lib/assets/javascripts/twitter_cldr/twitter_cldr_ru.js +0 -887
  123. data/lib/assets/javascripts/twitter_cldr/twitter_cldr_sv.js +0 -887
  124. data/lib/assets/javascripts/twitter_cldr/twitter_cldr_th.js +0 -887
  125. data/lib/assets/javascripts/twitter_cldr/twitter_cldr_tr.js +0 -887
  126. data/lib/assets/javascripts/twitter_cldr/twitter_cldr_uk.js +0 -887
  127. data/lib/assets/javascripts/twitter_cldr/twitter_cldr_ur.js +0 -887
  128. data/lib/assets/javascripts/twitter_cldr/twitter_cldr_zh-cn.js +0 -887
  129. 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 = class 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, options = {}) ->
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
- if seconds < 30
31
- "second"
32
- else if seconds < 2670
33
- "minute"
34
- else if seconds < 86369
35
- "hour"
36
- else if seconds < 604800
37
- "day"
38
- else if seconds < 2591969
39
- "week"
40
- else if seconds < 31556926
41
- "month"
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 = class NumberFormatter
5
- constructor: ->
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
- [int, fraction] = this.parse_number(number, opts)
24
- result = integer_format.apply(parseFloat(int), opts)
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
- [tokens[0] || "",
31
- tokens[2] || "",
32
- new IntegerHelper(tokens[1], @symbols),
33
- new FractionHelper(tokens[1], @symbols)]
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 = class PercentFormatter extends NumberFormatter
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 = class DecimalFormatter extends NumberFormatter
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 = class CurrencyFormatter extends NumberFormatter
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.NumberFormatter.BaseHelper = class BaseHelper
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 = class IntegerHelper extends BaseHelper
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
- return [] unless index = format.lastIndexOf(',')
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) # compact
155
- widths.reverse() # uniq
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 = class FractionHelper extends BaseHelper
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 || "."
@@ -1,7 +1,7 @@
1
1
  # Copyright 2012 Twitter, Inc
2
2
  # http://www.apache.org/licenses/LICENSE-2.0
3
3
 
4
- TwitterCldr.PluralRules = class PluralRules
4
+ class TwitterCldr.PluralRules
5
5
  @rules = `{{{rules}}}`
6
6
 
7
7
  @all: ->
@@ -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