twitter_cldr_js 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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