2
0

bidi.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. /* Copyright 2017 Mozilla Foundation
  2. *
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15. 'use strict';
  16. var sharedUtil = require('../shared/util.js');
  17. var warn = sharedUtil.warn;
  18. var baseTypes = ['BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'S', 'B', 'S', 'WS', 'B', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'B', 'B', 'B', 'S', 'WS', 'ON', 'ON', 'ET', 'ET', 'ET', 'ON', 'ON', 'ON', 'ON', 'ON', 'ES', 'CS', 'ES', 'CS', 'CS', 'EN', 'EN', 'EN', 'EN', 'EN', 'EN', 'EN', 'EN', 'EN', 'EN', 'CS', 'ON', 'ON', 'ON', 'ON', 'ON', 'ON', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'ON', 'ON', 'ON', 'ON', 'ON', 'ON', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'ON', 'ON', 'ON', 'ON', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'B', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'CS', 'ON', 'ET', 'ET', 'ET', 'ET', 'ON', 'ON', 'ON', 'ON', 'L', 'ON', 'ON', 'BN', 'ON', 'ON', 'ET', 'ET', 'EN', 'EN', 'ON', 'L', 'ON', 'ON', 'ON', 'EN', 'L', 'ON', 'ON', 'ON', 'ON', 'ON', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'ON', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'ON', 'L', 'L', 'L', 'L', 'L', 'L', 'L', 'L'];
  19. var arabicTypes = ['AN', 'AN', 'AN', 'AN', 'AN', 'AN', 'ON', 'ON', 'AL', 'ET', 'ET', 'AL', 'CS', 'AL', 'ON', 'ON', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'AL', 'AL', '', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'AN', 'AN', 'AN', 'AN', 'AN', 'AN', 'AN', 'AN', 'AN', 'AN', 'ET', 'AN', 'AN', 'AL', 'AL', 'AL', 'NSM', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'AN', 'ON', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'NSM', 'AL', 'AL', 'NSM', 'NSM', 'ON', 'NSM', 'NSM', 'NSM', 'NSM', 'AL', 'AL', 'EN', 'EN', 'EN', 'EN', 'EN', 'EN', 'EN', 'EN', 'EN', 'EN', 'AL', 'AL', 'AL', 'AL', 'AL', 'AL'];
  20. function isOdd(i) {
  21. return (i & 1) !== 0;
  22. }
  23. function isEven(i) {
  24. return (i & 1) === 0;
  25. }
  26. function findUnequal(arr, start, value) {
  27. for (var j = start, jj = arr.length; j < jj; ++j) {
  28. if (arr[j] !== value) {
  29. return j;
  30. }
  31. }
  32. return j;
  33. }
  34. function setValues(arr, start, end, value) {
  35. for (var j = start; j < end; ++j) {
  36. arr[j] = value;
  37. }
  38. }
  39. function reverseValues(arr, start, end) {
  40. for (var i = start, j = end - 1; i < j; ++i, --j) {
  41. var temp = arr[i];
  42. arr[i] = arr[j];
  43. arr[j] = temp;
  44. }
  45. }
  46. function createBidiText(str, isLTR, vertical) {
  47. return {
  48. str: str,
  49. dir: vertical ? 'ttb' : isLTR ? 'ltr' : 'rtl'
  50. };
  51. }
  52. var chars = [];
  53. var types = [];
  54. function bidi(str, startLevel, vertical) {
  55. var isLTR = true;
  56. var strLength = str.length;
  57. if (strLength === 0 || vertical) {
  58. return createBidiText(str, isLTR, vertical);
  59. }
  60. chars.length = strLength;
  61. types.length = strLength;
  62. var numBidi = 0;
  63. var i, ii;
  64. for (i = 0; i < strLength; ++i) {
  65. chars[i] = str.charAt(i);
  66. var charCode = str.charCodeAt(i);
  67. var charType = 'L';
  68. if (charCode <= 0x00ff) {
  69. charType = baseTypes[charCode];
  70. } else if (0x0590 <= charCode && charCode <= 0x05f4) {
  71. charType = 'R';
  72. } else if (0x0600 <= charCode && charCode <= 0x06ff) {
  73. charType = arabicTypes[charCode & 0xff];
  74. if (!charType) {
  75. warn('Bidi: invalid Unicode character ' + charCode.toString(16));
  76. }
  77. } else if (0x0700 <= charCode && charCode <= 0x08AC) {
  78. charType = 'AL';
  79. }
  80. if (charType === 'R' || charType === 'AL' || charType === 'AN') {
  81. numBidi++;
  82. }
  83. types[i] = charType;
  84. }
  85. if (numBidi === 0) {
  86. isLTR = true;
  87. return createBidiText(str, isLTR);
  88. }
  89. if (startLevel === -1) {
  90. if (numBidi / strLength < 0.3) {
  91. isLTR = true;
  92. startLevel = 0;
  93. } else {
  94. isLTR = false;
  95. startLevel = 1;
  96. }
  97. }
  98. var levels = [];
  99. for (i = 0; i < strLength; ++i) {
  100. levels[i] = startLevel;
  101. }
  102. var e = isOdd(startLevel) ? 'R' : 'L';
  103. var sor = e;
  104. var eor = sor;
  105. var lastType = sor;
  106. for (i = 0; i < strLength; ++i) {
  107. if (types[i] === 'NSM') {
  108. types[i] = lastType;
  109. } else {
  110. lastType = types[i];
  111. }
  112. }
  113. lastType = sor;
  114. var t;
  115. for (i = 0; i < strLength; ++i) {
  116. t = types[i];
  117. if (t === 'EN') {
  118. types[i] = lastType === 'AL' ? 'AN' : 'EN';
  119. } else if (t === 'R' || t === 'L' || t === 'AL') {
  120. lastType = t;
  121. }
  122. }
  123. for (i = 0; i < strLength; ++i) {
  124. t = types[i];
  125. if (t === 'AL') {
  126. types[i] = 'R';
  127. }
  128. }
  129. for (i = 1; i < strLength - 1; ++i) {
  130. if (types[i] === 'ES' && types[i - 1] === 'EN' && types[i + 1] === 'EN') {
  131. types[i] = 'EN';
  132. }
  133. if (types[i] === 'CS' && (types[i - 1] === 'EN' || types[i - 1] === 'AN') && types[i + 1] === types[i - 1]) {
  134. types[i] = types[i - 1];
  135. }
  136. }
  137. for (i = 0; i < strLength; ++i) {
  138. if (types[i] === 'EN') {
  139. var j;
  140. for (j = i - 1; j >= 0; --j) {
  141. if (types[j] !== 'ET') {
  142. break;
  143. }
  144. types[j] = 'EN';
  145. }
  146. for (j = i + 1; j < strLength; ++j) {
  147. if (types[j] !== 'ET') {
  148. break;
  149. }
  150. types[j] = 'EN';
  151. }
  152. }
  153. }
  154. for (i = 0; i < strLength; ++i) {
  155. t = types[i];
  156. if (t === 'WS' || t === 'ES' || t === 'ET' || t === 'CS') {
  157. types[i] = 'ON';
  158. }
  159. }
  160. lastType = sor;
  161. for (i = 0; i < strLength; ++i) {
  162. t = types[i];
  163. if (t === 'EN') {
  164. types[i] = lastType === 'L' ? 'L' : 'EN';
  165. } else if (t === 'R' || t === 'L') {
  166. lastType = t;
  167. }
  168. }
  169. for (i = 0; i < strLength; ++i) {
  170. if (types[i] === 'ON') {
  171. var end = findUnequal(types, i + 1, 'ON');
  172. var before = sor;
  173. if (i > 0) {
  174. before = types[i - 1];
  175. }
  176. var after = eor;
  177. if (end + 1 < strLength) {
  178. after = types[end + 1];
  179. }
  180. if (before !== 'L') {
  181. before = 'R';
  182. }
  183. if (after !== 'L') {
  184. after = 'R';
  185. }
  186. if (before === after) {
  187. setValues(types, i, end, before);
  188. }
  189. i = end - 1;
  190. }
  191. }
  192. for (i = 0; i < strLength; ++i) {
  193. if (types[i] === 'ON') {
  194. types[i] = e;
  195. }
  196. }
  197. for (i = 0; i < strLength; ++i) {
  198. t = types[i];
  199. if (isEven(levels[i])) {
  200. if (t === 'R') {
  201. levels[i] += 1;
  202. } else if (t === 'AN' || t === 'EN') {
  203. levels[i] += 2;
  204. }
  205. } else {
  206. if (t === 'L' || t === 'AN' || t === 'EN') {
  207. levels[i] += 1;
  208. }
  209. }
  210. }
  211. var highestLevel = -1;
  212. var lowestOddLevel = 99;
  213. var level;
  214. for (i = 0, ii = levels.length; i < ii; ++i) {
  215. level = levels[i];
  216. if (highestLevel < level) {
  217. highestLevel = level;
  218. }
  219. if (lowestOddLevel > level && isOdd(level)) {
  220. lowestOddLevel = level;
  221. }
  222. }
  223. for (level = highestLevel; level >= lowestOddLevel; --level) {
  224. var start = -1;
  225. for (i = 0, ii = levels.length; i < ii; ++i) {
  226. if (levels[i] < level) {
  227. if (start >= 0) {
  228. reverseValues(chars, start, i);
  229. start = -1;
  230. }
  231. } else if (start < 0) {
  232. start = i;
  233. }
  234. }
  235. if (start >= 0) {
  236. reverseValues(chars, start, levels.length);
  237. }
  238. }
  239. for (i = 0, ii = chars.length; i < ii; ++i) {
  240. var ch = chars[i];
  241. if (ch === '<' || ch === '>') {
  242. chars[i] = '';
  243. }
  244. }
  245. return createBidiText(chars.join(''), isLTR);
  246. }
  247. exports.bidi = bidi;