all.js 67 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534
  1. function decode_arithmetic(bytes) {
  2. let pos = 0;
  3. function u16() { return (bytes[pos++] << 8) | bytes[pos++]; }
  4. // decode the frequency table
  5. let symbol_count = u16();
  6. let total = 1;
  7. let acc = [0, 1]; // first symbol has frequency 1
  8. for (let i = 1; i < symbol_count; i++) {
  9. acc.push(total += u16());
  10. }
  11. // skip the sized-payload that the last 3 symbols index into
  12. let skip = u16();
  13. let pos_payload = pos;
  14. pos += skip;
  15. let read_width = 0;
  16. let read_buffer = 0;
  17. function read_bit() {
  18. if (read_width == 0) {
  19. // this will read beyond end of buffer
  20. // but (undefined|0) => zero pad
  21. read_buffer = (read_buffer << 8) | bytes[pos++];
  22. read_width = 8;
  23. }
  24. return (read_buffer >> --read_width) & 1;
  25. }
  26. const N = 31;
  27. const FULL = 2**N;
  28. const HALF = FULL >>> 1;
  29. const QRTR = HALF >> 1;
  30. const MASK = FULL - 1;
  31. // fill register
  32. let register = 0;
  33. for (let i = 0; i < N; i++) register = (register << 1) | read_bit();
  34. let symbols = [];
  35. let low = 0;
  36. let range = FULL; // treat like a float
  37. while (true) {
  38. let value = Math.floor((((register - low + 1) * total) - 1) / range);
  39. let start = 0;
  40. let end = symbol_count;
  41. while (end - start > 1) { // binary search
  42. let mid = (start + end) >>> 1;
  43. if (value < acc[mid]) {
  44. end = mid;
  45. } else {
  46. start = mid;
  47. }
  48. }
  49. if (start == 0) break; // first symbol is end mark
  50. symbols.push(start);
  51. let a = low + Math.floor(range * acc[start] / total);
  52. let b = low + Math.floor(range * acc[start+1] / total) - 1;
  53. while (((a ^ b) & HALF) == 0) {
  54. register = (register << 1) & MASK | read_bit();
  55. a = (a << 1) & MASK;
  56. b = (b << 1) & MASK | 1;
  57. }
  58. while (a & ~b & QRTR) {
  59. register = (register & HALF) | ((register << 1) & (MASK >>> 1)) | read_bit();
  60. a = (a << 1) ^ HALF;
  61. b = ((b ^ HALF) << 1) | HALF | 1;
  62. }
  63. low = a;
  64. range = 1 + b - a;
  65. }
  66. let offset = symbol_count - 4;
  67. return symbols.map(x => { // index into payload
  68. switch (x - offset) {
  69. case 3: return offset + 0x10100 + ((bytes[pos_payload++] << 16) | (bytes[pos_payload++] << 8) | bytes[pos_payload++]);
  70. case 2: return offset + 0x100 + ((bytes[pos_payload++] << 8) | bytes[pos_payload++]);
  71. case 1: return offset + bytes[pos_payload++];
  72. default: return x - 1;
  73. }
  74. });
  75. }
  76. // returns an iterator which returns the next symbol
  77. function read_payload(v) {
  78. let pos = 0;
  79. return () => v[pos++];
  80. }
  81. function read_compressed_payload(s) {
  82. return read_payload(decode_arithmetic(unsafe_atob(s)));
  83. }
  84. // unsafe in the sense:
  85. // expected well-formed Base64 w/o padding
  86. function unsafe_atob(s) {
  87. let lookup = [];
  88. [...'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'].forEach((c, i) => lookup[c.charCodeAt(0)] = i);
  89. let n = s.length;
  90. let ret = new Uint8Array((6 * n) >> 3);
  91. for (let i = 0, pos = 0, width = 0, carry = 0; i < n; i++) {
  92. carry = (carry << 6) | lookup[s.charCodeAt(i)];
  93. width += 6;
  94. if (width >= 8) {
  95. ret[pos++] = (carry >> (width -= 8));
  96. }
  97. }
  98. return ret;
  99. }
  100. // eg. [0,1,2,3...] => [0,-1,1,-2,...]
  101. function signed(i) {
  102. return (i & 1) ? (~i >> 1) : (i >> 1);
  103. }
  104. function read_deltas(n, next) {
  105. let v = Array(n);
  106. for (let i = 0, x = 0; i < n; i++) v[i] = x += signed(next());
  107. return v;
  108. }
  109. // [123][5] => [0 3] [1 1] [0 0]
  110. function read_sorted(next, prev = 0) {
  111. let ret = [];
  112. while (true) {
  113. let x = next();
  114. let n = next();
  115. if (!n) break;
  116. prev += x;
  117. for (let i = 0; i < n; i++) {
  118. ret.push(prev + i);
  119. }
  120. prev += n + 1;
  121. }
  122. return ret;
  123. }
  124. function read_sorted_arrays(next) {
  125. return read_array_while(() => {
  126. let v = read_sorted(next);
  127. if (v.length) return v;
  128. });
  129. }
  130. // returns map of x => ys
  131. function read_mapped(next) {
  132. let ret = [];
  133. while (true) {
  134. let w = next();
  135. if (w == 0) break;
  136. ret.push(read_linear_table(w, next));
  137. }
  138. while (true) {
  139. let w = next() - 1;
  140. if (w < 0) break;
  141. ret.push(read_replacement_table(w, next));
  142. }
  143. return ret.flat();
  144. }
  145. // read until next is falsy
  146. // return array of read values
  147. function read_array_while(next) {
  148. let v = [];
  149. while (true) {
  150. let x = next(v.length);
  151. if (!x) break;
  152. v.push(x);
  153. }
  154. return v;
  155. }
  156. // read w columns of length n
  157. // return as n rows of length w
  158. function read_transposed(n, w, next) {
  159. let m = Array(n).fill().map(() => []);
  160. for (let i = 0; i < w; i++) {
  161. read_deltas(n, next).forEach((x, j) => m[j].push(x));
  162. }
  163. return m;
  164. }
  165. // returns [[x, ys], [x+dx, ys+dy], [x+2*dx, ys+2*dy], ...]
  166. // where dx/dy = steps, n = run size, w = length of y
  167. function read_linear_table(w, next) {
  168. let dx = 1 + next();
  169. let dy = next();
  170. let vN = read_array_while(next);
  171. let m = read_transposed(vN.length, 1+w, next);
  172. return m.flatMap((v, i) => {
  173. let [x, ...ys] = v;
  174. return Array(vN[i]).fill().map((_, j) => {
  175. let j_dy = j * dy;
  176. return [x + j * dx, ys.map(y => y + j_dy)];
  177. });
  178. });
  179. }
  180. // return [[x, ys...], ...]
  181. // where w = length of y
  182. function read_replacement_table(w, next) {
  183. let n = 1 + next();
  184. let m = read_transposed(n, 1+w, next);
  185. return m.map(v => [v[0], v.slice(1)]);
  186. }
  187. // created 2023-02-21T09:18:13.549Z
  188. var r$1 = read_compressed_payload('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');
  189. const FENCED = new Map([[8217,"apostrophe"],[8260,"fraction slash"],[12539,"middle dot"]]);
  190. const NSM_MAX = 4;
  191. function hex_cp(cp) {
  192. return cp.toString(16).toUpperCase().padStart(2, '0');
  193. }
  194. function quote_cp(cp) {
  195. return `{${hex_cp(cp)}}`; // raffy convention: like "\u{X}" w/o the "\u"
  196. }
  197. /*
  198. export function explode_cp(s) {
  199. return [...s].map(c => c.codePointAt(0));
  200. }
  201. */
  202. function explode_cp(s) { // this is about 2x faster
  203. let cps = [];
  204. for (let pos = 0, len = s.length; pos < len; ) {
  205. let cp = s.codePointAt(pos);
  206. pos += cp < 0x10000 ? 1 : 2;
  207. cps.push(cp);
  208. }
  209. return cps;
  210. }
  211. function str_from_cps(cps) {
  212. const chunk = 4096;
  213. let len = cps.length;
  214. if (len < chunk) return String.fromCodePoint(...cps);
  215. let buf = [];
  216. for (let i = 0; i < len; ) {
  217. buf.push(String.fromCodePoint(...cps.slice(i, i += chunk)));
  218. }
  219. return buf.join('');
  220. }
  221. function compare_arrays(a, b) {
  222. let n = a.length;
  223. let c = n - b.length;
  224. for (let i = 0; c == 0 && i < n; i++) c = a[i] - b[i];
  225. return c;
  226. }
  227. function random_choice(v, rng = Math.random) {
  228. return v[rng() * v.length|0];
  229. }
  230. function random_sample(v, n, rng = Math.random) {
  231. v = v.slice(); // make copy
  232. if (v.length > n) {
  233. for (let i = 0; i < n; i++) { // shuffle prefix n
  234. let temp = v[i];
  235. let j = Math.floor(i + rng() * (v.length - i));
  236. v[i] = v[j];
  237. v[j] = temp;
  238. }
  239. v = v.slice(0, n); // truncate
  240. }
  241. return v;
  242. }
  243. function run_tests(fn, tests) {
  244. let errors = [];
  245. for (let test of tests) {
  246. let {name, norm, error} = test;
  247. if (typeof norm !== 'string') norm = name;
  248. try {
  249. let result = fn(name);
  250. if (error) {
  251. errors.push({type: 'expected error', result, ...test});
  252. } else if (result != norm) {
  253. errors.push({type: 'wrong norm', result, ...test});
  254. }
  255. } catch (err) {
  256. if (!error) {
  257. errors.push({type: 'unexpected error', result: err.message, ...test});
  258. }
  259. }
  260. }
  261. return errors;
  262. }
  263. // created 2023-02-21T09:18:13.549Z
  264. var r = read_compressed_payload('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');
  265. // https://unicode.org/reports/tr15/
  266. function unpack_cc(packed) {
  267. return (packed >> 24) & 0xFF;
  268. }
  269. function unpack_cp(packed) {
  270. return packed & 0xFFFFFF;
  271. }
  272. const SHIFTED_RANK = new Map(read_sorted_arrays(r).flatMap((v, i) => v.map(x => [x, (i+1) << 24]))); // pre-shifted
  273. const EXCLUSIONS = new Set(read_sorted(r));
  274. const DECOMP = new Map();
  275. const RECOMP = new Map();
  276. for (let [cp, cps] of read_mapped(r)) {
  277. if (!EXCLUSIONS.has(cp) && cps.length == 2) {
  278. let [a, b] = cps;
  279. let bucket = RECOMP.get(a);
  280. if (!bucket) {
  281. bucket = new Map();
  282. RECOMP.set(a, bucket);
  283. }
  284. bucket.set(b, cp);
  285. }
  286. DECOMP.set(cp, cps.reverse()); // stored reversed
  287. }
  288. // algorithmic hangul
  289. // https://www.unicode.org/versions/Unicode15.0.0/ch03.pdf (page 144)
  290. const S0 = 0xAC00;
  291. const L0 = 0x1100;
  292. const V0 = 0x1161;
  293. const T0 = 0x11A7;
  294. const L_COUNT = 19;
  295. const V_COUNT = 21;
  296. const T_COUNT = 28;
  297. const N_COUNT = V_COUNT * T_COUNT;
  298. const S_COUNT = L_COUNT * N_COUNT;
  299. const S1 = S0 + S_COUNT;
  300. const L1 = L0 + L_COUNT;
  301. const V1 = V0 + V_COUNT;
  302. const T1 = T0 + T_COUNT;
  303. function is_hangul(cp) {
  304. return cp >= S0 && cp < S1;
  305. }
  306. function compose_pair(a, b) {
  307. if (a >= L0 && a < L1 && b >= V0 && b < V1) {
  308. return S0 + (a - L0) * N_COUNT + (b - V0) * T_COUNT;
  309. } else if (is_hangul(a) && b > T0 && b < T1 && (a - S0) % T_COUNT == 0) {
  310. return a + (b - T0);
  311. } else {
  312. let recomp = RECOMP.get(a);
  313. if (recomp) {
  314. recomp = recomp.get(b);
  315. if (recomp) {
  316. return recomp;
  317. }
  318. }
  319. return -1;
  320. }
  321. }
  322. function decomposed(cps) {
  323. let ret = [];
  324. let buf = [];
  325. let check_order = false;
  326. function add(cp) {
  327. let cc = SHIFTED_RANK.get(cp);
  328. if (cc) {
  329. check_order = true;
  330. cp |= cc;
  331. }
  332. ret.push(cp);
  333. }
  334. for (let cp of cps) {
  335. while (true) {
  336. if (cp < 0x80) {
  337. ret.push(cp);
  338. } else if (is_hangul(cp)) {
  339. let s_index = cp - S0;
  340. let l_index = s_index / N_COUNT | 0;
  341. let v_index = (s_index % N_COUNT) / T_COUNT | 0;
  342. let t_index = s_index % T_COUNT;
  343. add(L0 + l_index);
  344. add(V0 + v_index);
  345. if (t_index > 0) add(T0 + t_index);
  346. } else {
  347. let mapped = DECOMP.get(cp);
  348. if (mapped) {
  349. buf.push(...mapped);
  350. } else {
  351. add(cp);
  352. }
  353. }
  354. if (!buf.length) break;
  355. cp = buf.pop();
  356. }
  357. }
  358. if (check_order && ret.length > 1) {
  359. let prev_cc = unpack_cc(ret[0]);
  360. for (let i = 1; i < ret.length; i++) {
  361. let cc = unpack_cc(ret[i]);
  362. if (cc == 0 || prev_cc <= cc) {
  363. prev_cc = cc;
  364. continue;
  365. }
  366. let j = i-1;
  367. while (true) {
  368. let tmp = ret[j+1];
  369. ret[j+1] = ret[j];
  370. ret[j] = tmp;
  371. if (!j) break;
  372. prev_cc = unpack_cc(ret[--j]);
  373. if (prev_cc <= cc) break;
  374. }
  375. prev_cc = unpack_cc(ret[i]);
  376. }
  377. }
  378. return ret;
  379. }
  380. function composed_from_decomposed(v) {
  381. let ret = [];
  382. let stack = [];
  383. let prev_cp = -1;
  384. let prev_cc = 0;
  385. for (let packed of v) {
  386. let cc = unpack_cc(packed);
  387. let cp = unpack_cp(packed);
  388. if (prev_cp == -1) {
  389. if (cc == 0) {
  390. prev_cp = cp;
  391. } else {
  392. ret.push(cp);
  393. }
  394. } else if (prev_cc > 0 && prev_cc >= cc) {
  395. if (cc == 0) {
  396. ret.push(prev_cp, ...stack);
  397. stack.length = 0;
  398. prev_cp = cp;
  399. } else {
  400. stack.push(cp);
  401. }
  402. prev_cc = cc;
  403. } else {
  404. let composed = compose_pair(prev_cp, cp);
  405. if (composed >= 0) {
  406. prev_cp = composed;
  407. } else if (prev_cc == 0 && cc == 0) {
  408. ret.push(prev_cp);
  409. prev_cp = cp;
  410. } else {
  411. stack.push(cp);
  412. prev_cc = cc;
  413. }
  414. }
  415. }
  416. if (prev_cp >= 0) {
  417. ret.push(prev_cp, ...stack);
  418. }
  419. return ret;
  420. }
  421. // note: cps can be iterable
  422. function nfd(cps) {
  423. return decomposed(cps).map(unpack_cp);
  424. }
  425. function nfc(cps) {
  426. return composed_from_decomposed(decomposed(cps));
  427. }
  428. //const t0 = performance.now();
  429. const STOP = 0x2E;
  430. const FE0F = 0xFE0F;
  431. const STOP_CH = '.';
  432. const UNIQUE_PH = 1;
  433. const HYPHEN = 0x2D;
  434. function read_set() {
  435. return new Set(read_sorted(r$1));
  436. }
  437. const MAPPED = new Map(read_mapped(r$1));
  438. const IGNORED = read_set(); // ignored characters are not valid, so just read raw codepoints
  439. /*
  440. // direct include from payload is smaller that the decompression code
  441. const FENCED = new Map(read_array_while(() => {
  442. let cp = r();
  443. if (cp) return [cp, read_str(r())];
  444. }));
  445. */
  446. // 20230217: we still need all CM for proper error formatting
  447. // but norm only needs NSM subset that are potentially-valid
  448. const CM = read_set();
  449. const NSM = new Set(read_sorted(r$1).map(function(i) { return this[i]; }, [...CM]));
  450. /*
  451. const CM_SORTED = read_sorted(r);
  452. const NSM = new Set(read_sorted(r).map(i => CM_SORTED[i]));
  453. const CM = new Set(CM_SORTED);
  454. */
  455. const ESCAPE = read_set(); // characters that should not be printed
  456. const NFC_CHECK = read_set();
  457. const CHUNKS = read_sorted_arrays(r$1);
  458. function read_chunked() {
  459. // deduplicated sets + uniques
  460. return new Set([read_sorted(r$1).map(i => CHUNKS[i]), read_sorted(r$1)].flat(2));
  461. }
  462. const UNRESTRICTED = r$1();
  463. const GROUPS = read_array_while(i => {
  464. // minifier property mangling seems unsafe
  465. // so these are manually renamed to single chars
  466. let N = read_array_while(r$1).map(x => x+0x60);
  467. if (N.length) {
  468. let R = i >= UNRESTRICTED; // first arent restricted
  469. N[0] -= 32; // capitalize
  470. N = str_from_cps(N);
  471. if (R) N=`Restricted[${N}]`;
  472. let P = read_chunked(); // primary
  473. let Q = read_chunked(); // secondary
  474. let V = [...P, ...Q].sort((a, b) => a-b); // derive: sorted valid
  475. //let M = r()-1; // combining mark
  476. let M = !r$1(); // not-whitelisted, check for NSM
  477. // code currently isn't needed
  478. /*if (M < 0) { // whitelisted
  479. M = new Map(read_array_while(() => {
  480. let i = r();
  481. if (i) return [V[i-1], read_array_while(() => {
  482. let v = read_array_while(r);
  483. if (v.length) return v.map(x => x-1);
  484. })];
  485. }));
  486. }*/
  487. return {N, P, M, R, V: new Set(V)};
  488. }
  489. });
  490. const WHOLE_VALID = read_set();
  491. const WHOLE_MAP = new Map();
  492. // decode compressed wholes
  493. [...WHOLE_VALID, ...read_set()].sort((a, b) => a-b).map((cp, i, v) => {
  494. let d = r$1();
  495. let w = v[i] = d ? v[i-d] : {V: [], M: new Map()};
  496. w.V.push(cp); // add to member set
  497. if (!WHOLE_VALID.has(cp)) {
  498. WHOLE_MAP.set(cp, w); // register with whole map
  499. }
  500. });
  501. // compute confusable-extent complements
  502. for (let {V, M} of new Set(WHOLE_MAP.values())) {
  503. // connect all groups that have each whole character
  504. let recs = [];
  505. for (let cp of V) {
  506. let gs = GROUPS.filter(g => g.V.has(cp));
  507. let rec = recs.find(({G}) => gs.some(g => G.has(g)));
  508. if (!rec) {
  509. rec = {G: new Set(), V: []};
  510. recs.push(rec);
  511. }
  512. rec.V.push(cp);
  513. gs.forEach(g => rec.G.add(g));
  514. }
  515. // per character cache groups which are not a member of the extent
  516. let union = recs.flatMap(({G}) => [...G]);
  517. for (let {G, V} of recs) {
  518. let complement = new Set(union.filter(g => !G.has(g)));
  519. for (let cp of V) {
  520. M.set(cp, complement);
  521. }
  522. }
  523. }
  524. let union = new Set(); // exists in 1+ groups
  525. let multi = new Set(); // exists in 2+ groups
  526. for (let g of GROUPS) {
  527. for (let cp of g.V) {
  528. (union.has(cp) ? multi : union).add(cp);
  529. }
  530. }
  531. // dual purpose WHOLE_MAP: return placeholder if unique non-confusable
  532. for (let cp of union) {
  533. if (!WHOLE_MAP.has(cp) && !multi.has(cp)) {
  534. WHOLE_MAP.set(cp, UNIQUE_PH);
  535. }
  536. }
  537. const VALID = new Set([...union, ...nfd(union)]); // possibly valid
  538. // decode emoji
  539. const EMOJI_SORTED = read_sorted(r$1); // temporary
  540. //const EMOJI_SOLO = new Set(read_sorted(r).map(i => EMOJI_SORTED[i])); // not needed
  541. const EMOJI_ROOT = read_emoji_trie([]);
  542. function read_emoji_trie(cps) {
  543. let B = read_array_while(() => {
  544. let keys = read_sorted(r$1).map(i => EMOJI_SORTED[i]);
  545. if (keys.length) return read_emoji_trie(keys);
  546. }).sort((a, b) => b.Q.size - a.Q.size); // sort by likelihood
  547. let temp = r$1();
  548. let V = temp % 3; // valid (0 = false, 1 = true, 2 = weird)
  549. temp = (temp / 3)|0;
  550. let F = temp & 1; // allow FE0F
  551. temp >>= 1;
  552. let S = temp & 1; // save
  553. let C = temp & 2; // check
  554. return {B, V, F, S, C, Q: new Set(cps)};
  555. }
  556. //console.log(performance.now() - t0);
  557. // free tagging system
  558. class Emoji extends Array {
  559. get is_emoji() { return true; }
  560. }
  561. // create a safe to print string
  562. // invisibles are escaped
  563. // leading cm uses placeholder
  564. // quoter(cp) => string, eg. 3000 => "{3000}"
  565. // note: in html, you'd call this function then replace [<>&] with entities
  566. function safe_str_from_cps(cps, quoter = quote_cp) {
  567. //if (Number.isInteger(cps)) cps = [cps];
  568. //if (!Array.isArray(cps)) throw new TypeError(`expected codepoints`);
  569. let buf = [];
  570. if (is_combining_mark(cps[0])) buf.push('◌');
  571. let prev = 0;
  572. let n = cps.length;
  573. for (let i = 0; i < n; i++) {
  574. let cp = cps[i];
  575. if (should_escape(cp)) {
  576. buf.push(str_from_cps(cps.slice(prev, i)));
  577. buf.push(quoter(cp));
  578. prev = i + 1;
  579. }
  580. }
  581. buf.push(str_from_cps(cps.slice(prev, n)));
  582. return buf.join('');
  583. }
  584. // if escaped: {HEX}
  585. // else: "x" {HEX}
  586. function quoted_cp(cp) {
  587. return (should_escape(cp) ? '' : `${bidi_qq(safe_str_from_cps([cp]))} `) + quote_cp(cp);
  588. }
  589. // 20230211: some messages can be mixed-directional and result in spillover
  590. // use 200E after a quoted string to force the remainder of a string from
  591. // acquring the direction of the quote
  592. // https://www.w3.org/International/questions/qa-bidi-unicode-controls#exceptions
  593. function bidi_qq(s) {
  594. return `"${s}"\u200E`; // strong LTR
  595. }
  596. function check_label_extension(cps) {
  597. if (cps.length >= 4 && cps[2] == HYPHEN && cps[3] == HYPHEN) {
  598. throw new Error('invalid label extension');
  599. }
  600. }
  601. function check_leading_underscore(cps) {
  602. const UNDERSCORE = 0x5F;
  603. for (let i = cps.lastIndexOf(UNDERSCORE); i > 0; ) {
  604. if (cps[--i] !== UNDERSCORE) {
  605. throw new Error('underscore allowed only at start');
  606. }
  607. }
  608. }
  609. // check that a fenced cp is not leading, trailing, or touching another fenced cp
  610. function check_fenced(cps) {
  611. let cp = cps[0];
  612. let prev = FENCED.get(cp);
  613. if (prev) throw error_placement(`leading ${prev}`);
  614. let n = cps.length;
  615. let last = -1; // prevents trailing from throwing
  616. for (let i = 1; i < n; i++) {
  617. cp = cps[i];
  618. let match = FENCED.get(cp);
  619. if (match) {
  620. // since cps[0] isn't fenced, cps[1] cannot throw
  621. if (last == i) throw error_placement(`${prev} + ${match}`);
  622. last = i + 1;
  623. prev = match;
  624. }
  625. }
  626. if (last == n) throw error_placement(`trailing ${prev}`);
  627. }
  628. // note: set(s) cannot be exposed because they can be modified
  629. function is_combining_mark(cp) {
  630. return CM.has(cp);
  631. }
  632. function should_escape(cp) {
  633. return ESCAPE.has(cp);
  634. }
  635. function ens_normalize_fragment(frag, decompose) {
  636. let nf = decompose ? nfd : nfc;
  637. return frag.split(STOP_CH).map(label => str_from_cps(process(explode_cp(label), nf).flatMap(x => x.is_emoji ? filter_fe0f(x) : x))).join(STOP_CH);
  638. }
  639. function ens_normalize(name) {
  640. return flatten(ens_split(name));
  641. }
  642. function ens_beautify(name) {
  643. let split = ens_split(name, true);
  644. // this is experimental
  645. for (let {type, output, error} of split) {
  646. if (error) continue;
  647. // replace leading/trailing hyphen
  648. // 20230121: consider beautifing all or leading/trailing hyphen to unicode variant
  649. // not exactly the same in every font, but very similar: "-" vs "‐"
  650. /*
  651. const UNICODE_HYPHEN = 0x2010;
  652. // maybe this should replace all for visual consistancy?
  653. // `node tools/reg-count.js regex ^-\{2,\}` => 592
  654. //for (let i = 0; i < output.length; i++) if (output[i] == 0x2D) output[i] = 0x2010;
  655. if (output[0] == HYPHEN) output[0] = UNICODE_HYPHEN;
  656. let end = output.length-1;
  657. if (output[end] == HYPHEN) output[end] = UNICODE_HYPHEN;
  658. */
  659. // 20230123: WHATWG URL uses "CheckHyphens" false
  660. // https://url.spec.whatwg.org/#idna
  661. // update ethereum symbol
  662. // ξ => Ξ if not greek
  663. if (type !== 'Greek') {
  664. let prev = 0;
  665. while (true) {
  666. let next = output.indexOf(0x3BE, prev);
  667. if (next < 0) break;
  668. output[next] = 0x39E;
  669. prev = next + 1;
  670. }
  671. }
  672. // 20221213: fixes bidi subdomain issue, but breaks invariant (200E is disallowed)
  673. // could be fixed with special case for: 2D (.) + 200E (LTR)
  674. //output.splice(0, 0, 0x200E);
  675. }
  676. return flatten(split);
  677. }
  678. function ens_split(name, preserve_emoji) {
  679. let offset = 0;
  680. // https://unicode.org/reports/tr46/#Validity_Criteria
  681. // 4.) "The label must not contain a U+002E ( . ) FULL STOP."
  682. return name.split(STOP_CH).map(label => {
  683. let input = explode_cp(label);
  684. let info = {
  685. input,
  686. offset, // codepoint, not substring!
  687. };
  688. offset += input.length + 1; // + stop
  689. let norm;
  690. try {
  691. // 1.) "The label must be in Unicode Normalization Form NFC"
  692. let tokens = info.tokens = process(input, nfc); // if we parse, we get [norm and mapped]
  693. let token_count = tokens.length;
  694. let type;
  695. if (!token_count) { // the label was effectively empty (could of had ignored characters)
  696. // 20230120: change to strict
  697. // https://discuss.ens.domains/t/ens-name-normalization-2nd/14564/59
  698. //norm = [];
  699. //type = 'None'; // use this instead of next match, "ASCII"
  700. throw new Error(`empty label`);
  701. } else {
  702. let chars = tokens[0];
  703. let emoji = token_count > 1 || chars.is_emoji;
  704. if (!emoji && chars.every(cp => cp < 0x80)) { // special case for ascii
  705. norm = chars;
  706. check_leading_underscore(norm);
  707. // only needed for ascii
  708. // 20230123: matches matches WHATWG, see note 3.3
  709. check_label_extension(norm);
  710. // cant have fenced
  711. // cant have cm
  712. // cant have wholes
  713. // see derive: "Fastpath ASCII"
  714. type = 'ASCII';
  715. } else {
  716. if (emoji) { // there is at least one emoji
  717. info.emoji = true;
  718. chars = tokens.flatMap(x => x.is_emoji ? [] : x); // all of the nfc tokens concat together
  719. }
  720. norm = tokens.flatMap(x => !preserve_emoji && x.is_emoji ? filter_fe0f(x) : x);
  721. check_leading_underscore(norm);
  722. if (!chars.length) { // theres no text, just emoji
  723. type = 'Emoji';
  724. } else {
  725. // 5. "The label must not begin with a combining mark, that is: General_Category=Mark."
  726. if (CM.has(norm[0])) throw error_placement('leading combining mark');
  727. for (let i = 1; i < token_count; i++) { // we've already checked the first token
  728. let cps = tokens[i];
  729. if (!cps.is_emoji && CM.has(cps[0])) { // every text token has emoji neighbors, eg. EtEEEtEt...
  730. // bidi_qq() not needed since emoji is LTR and cps is a CM
  731. throw error_placement(`emoji + combining mark: "${str_from_cps(tokens[i-1])} + ${safe_str_from_cps([cps[0]])}"`);
  732. }
  733. }
  734. check_fenced(norm);
  735. let unique = [...new Set(chars)];
  736. let [g] = determine_group(unique); // take the first match
  737. // see derive: "Matching Groups have Same CM Style"
  738. // alternative: could form a hybrid type: Latin/Japanese/...
  739. check_group(g, chars); // need text in order
  740. check_whole(g, unique); // only need unique text (order would be required for multiple-char confusables)
  741. type = g.N;
  742. // 20230121: consider exposing restricted flag
  743. // it's simpler to just check for 'Restricted'
  744. // or even better: type.endsWith(']')
  745. //if (g.R) info.restricted = true;
  746. }
  747. }
  748. }
  749. info.type = type;
  750. } catch (err) {
  751. info.error = err; // use full error object
  752. }
  753. info.output = norm;
  754. return info;
  755. });
  756. }
  757. function check_whole(group, unique) {
  758. let maker;
  759. let shared = []; // TODO: can this be avoided?
  760. for (let cp of unique) {
  761. let whole = WHOLE_MAP.get(cp);
  762. if (whole === UNIQUE_PH) return; // unique, non-confusable
  763. if (whole) {
  764. let set = whole.M.get(cp); // groups which have a character that look-like this character
  765. maker = maker ? maker.filter(g => set.has(g)) : [...set];
  766. if (!maker.length) return; // confusable intersection is empty
  767. } else {
  768. shared.push(cp);
  769. }
  770. }
  771. if (maker) {
  772. // we have 1+ confusable
  773. // check if any of the remaning groups
  774. // contain the shared characters too
  775. for (let g of maker) {
  776. if (shared.every(cp => g.V.has(cp))) {
  777. throw new Error(`whole-script confusable: ${group.N}/${g.N}`);
  778. }
  779. }
  780. }
  781. }
  782. // assumption: unique.size > 0
  783. // returns list of matching groups
  784. function determine_group(unique) {
  785. let groups = GROUPS;
  786. for (let cp of unique) {
  787. // note: we need to dodge CM that are whitelisted
  788. // but that code isn't currently necessary
  789. let gs = groups.filter(g => g.V.has(cp));
  790. if (!gs.length) {
  791. if (groups === GROUPS) {
  792. // the character was composed of valid parts
  793. // but it's NFC form is invalid
  794. throw error_disallowed(cp); // this should be rare
  795. } else {
  796. // there is no group that contains all these characters
  797. // throw using the highest priority group that matched
  798. // https://www.unicode.org/reports/tr39/#mixed_script_confusables
  799. throw error_group_member(groups[0], cp);
  800. }
  801. }
  802. groups = gs;
  803. if (gs.length == 1) break; // there is only one group left
  804. }
  805. // there are at least 1 group(s) with all of these characters
  806. return groups;
  807. }
  808. // throw on first error
  809. function flatten(split) {
  810. return split.map(({input, error, output}) => {
  811. if (error) {
  812. // don't print label again if just a single label
  813. let msg = error.message;
  814. // bidi_qq() only necessary if msg is digits
  815. throw new Error(split.length == 1 ? msg : `Invalid label ${bidi_qq(safe_str_from_cps(input))}: ${msg}`);
  816. }
  817. return str_from_cps(output);
  818. }).join(STOP_CH);
  819. }
  820. function error_disallowed(cp) {
  821. // TODO: add cp to error?
  822. return new Error(`disallowed character: ${quoted_cp(cp)}`);
  823. }
  824. function error_group_member(g, cp) {
  825. let quoted = quoted_cp(cp);
  826. let gg = GROUPS.find(g => g.P.has(cp));
  827. if (gg) {
  828. quoted = `${gg.N} ${quoted}`;
  829. }
  830. return new Error(`illegal mixture: ${g.N} + ${quoted}`);
  831. }
  832. function error_placement(where) {
  833. return new Error(`illegal placement: ${where}`);
  834. }
  835. // assumption: cps.length > 0
  836. // assumption: cps[0] isn't a CM
  837. // assumption: the previous character isn't an emoji
  838. function check_group(g, cps) {
  839. let {V, M} = g;
  840. for (let cp of cps) {
  841. if (!V.has(cp)) {
  842. // for whitelisted scripts, this will throw illegal mixture on invalid cm, eg. "e{300}{300}"
  843. // at the moment, it's unnecessary to introduce an extra error type
  844. // until there exists a whitelisted multi-character
  845. // eg. if (M < 0 && is_combining_mark(cp)) { ... }
  846. // there are 3 cases:
  847. // 1. illegal cm for wrong group => mixture error
  848. // 2. illegal cm for same group => cm error
  849. // requires set of whitelist cm per group:
  850. // eg. new Set([...g.V].flatMap(nfc).filter(cp => CM.has(cp)))
  851. // 3. wrong group => mixture error
  852. throw error_group_member(g, cp);
  853. }
  854. }
  855. //if (M >= 0) { // we have a known fixed cm count
  856. if (M) { // we need to check for NSM
  857. let decomposed = nfd(cps);
  858. for (let i = 1, e = decomposed.length; i < e; i++) { // see: assumption
  859. // 20230210: bugfix: using cps instead of decomposed h/t Carbon225
  860. /*
  861. if (CM.has(decomposed[i])) {
  862. let j = i + 1;
  863. while (j < e && CM.has(decomposed[j])) j++;
  864. if (j - i > M) {
  865. throw new Error(`too many combining marks: ${g.N} ${bidi_qq(str_from_cps(decomposed.slice(i-1, j)))} (${j-i}/${M})`);
  866. }
  867. i = j;
  868. }
  869. */
  870. // 20230217: switch to NSM counting
  871. // https://www.unicode.org/reports/tr39/#Optional_Detection
  872. if (NSM.has(decomposed[i])) {
  873. let j = i + 1;
  874. for (let cp; j < e && NSM.has(cp = decomposed[j]); j++) {
  875. // a. Forbid sequences of the same nonspacing mark.
  876. for (let k = i; k < j; k++) { // O(n^2) but n < 100
  877. if (decomposed[k] == cp) {
  878. throw new Error(`non-spacing marks: repeated ${quoted_cp(cp)}`);
  879. }
  880. }
  881. }
  882. // parse to end so we have full nsm count
  883. // b. Forbid sequences of more than 4 nonspacing marks (gc=Mn or gc=Me).
  884. if (j - i > NSM_MAX) {
  885. // note: this slice starts with a base char or spacing-mark cm
  886. throw new Error(`non-spacing marks: too many ${bidi_qq(safe_str_from_cps(decomposed.slice(i-1, j)))} (${j-i}/${NSM_MAX})`);
  887. }
  888. i = j;
  889. }
  890. }
  891. }
  892. // *** this code currently isn't needed ***
  893. /*
  894. let cm_whitelist = M instanceof Map;
  895. for (let i = 0, e = cps.length; i < e; ) {
  896. let cp = cps[i++];
  897. let seqs = cm_whitelist && M.get(cp);
  898. if (seqs) {
  899. // list of codepoints that can follow
  900. // if this exists, this will always be 1+
  901. let j = i;
  902. while (j < e && CM.has(cps[j])) j++;
  903. let cms = cps.slice(i, j);
  904. let match = seqs.find(seq => !compare_arrays(seq, cms));
  905. if (!match) throw new Error(`disallowed combining mark sequence: "${safe_str_from_cps([cp, ...cms])}"`);
  906. i = j;
  907. } else if (!V.has(cp)) {
  908. // https://www.unicode.org/reports/tr39/#mixed_script_confusables
  909. let quoted = quoted_cp(cp);
  910. for (let cp of cps) {
  911. let u = UNIQUE.get(cp);
  912. if (u && u !== g) {
  913. // if both scripts are restricted this error is confusing
  914. // because we don't differentiate RestrictedA from RestrictedB
  915. if (!u.R) quoted = `${quoted} is ${u.N}`;
  916. break;
  917. }
  918. }
  919. throw new Error(`disallowed ${g.N} character: ${quoted}`);
  920. //throw new Error(`disallowed character: ${quoted} (expected ${g.N})`);
  921. //throw new Error(`${g.N} does not allow: ${quoted}`);
  922. }
  923. }
  924. if (!cm_whitelist) {
  925. let decomposed = nfd(cps);
  926. for (let i = 1, e = decomposed.length; i < e; i++) { // we know it can't be cm leading
  927. if (CM.has(decomposed[i])) {
  928. let j = i + 1;
  929. while (j < e && CM.has(decomposed[j])) j++;
  930. if (j - i > M) {
  931. throw new Error(`too many combining marks: "${str_from_cps(decomposed.slice(i-1, j))}" (${j-i}/${M})`);
  932. }
  933. i = j;
  934. }
  935. }
  936. }
  937. */
  938. }
  939. // given a list of codepoints
  940. // returns a list of lists, where emoji are a fully-qualified (as Array subclass)
  941. // eg. explode_cp("abc💩d") => [[61, 62, 63], Emoji[1F4A9, FE0F], [64]]
  942. function process(input, nf) {
  943. let ret = [];
  944. let chars = [];
  945. input = input.slice().reverse(); // flip so we can pop
  946. while (input.length) {
  947. let emoji = consume_emoji_reversed(input);
  948. if (emoji) {
  949. if (chars.length) {
  950. ret.push(nf(chars));
  951. chars = [];
  952. }
  953. ret.push(emoji);
  954. } else {
  955. let cp = input.pop();
  956. if (VALID.has(cp)) {
  957. chars.push(cp);
  958. } else {
  959. let cps = MAPPED.get(cp);
  960. if (cps) {
  961. chars.push(...cps);
  962. } else if (!IGNORED.has(cp)) {
  963. throw error_disallowed(cp);
  964. }
  965. }
  966. }
  967. }
  968. if (chars.length) {
  969. ret.push(nf(chars));
  970. }
  971. return ret;
  972. }
  973. function filter_fe0f(cps) {
  974. return cps.filter(cp => cp != FE0F);
  975. }
  976. // given array of codepoints
  977. // returns the longest valid emoji sequence (or undefined if no match)
  978. // *MUTATES* the supplied array
  979. // allows optional FE0F
  980. // disallows interleaved ignored characters
  981. // fills (optional) eaten array with matched codepoints
  982. function consume_emoji_reversed(cps, eaten) {
  983. let node = EMOJI_ROOT;
  984. let emoji;
  985. let saved;
  986. let stack = [];
  987. let pos = cps.length;
  988. if (eaten) eaten.length = 0; // clear input buffer (if needed)
  989. while (pos) {
  990. let cp = cps[--pos];
  991. node = node.B.find(x => x.Q.has(cp));
  992. if (!node) break;
  993. if (node.S) { // remember
  994. saved = cp;
  995. } else if (node.C) { // check exclusion
  996. if (cp === saved) break;
  997. }
  998. stack.push(cp);
  999. if (node.F) {
  1000. stack.push(FE0F);
  1001. if (pos > 0 && cps[pos - 1] == FE0F) pos--; // consume optional FE0F
  1002. }
  1003. if (node.V) { // this is a valid emoji (so far)
  1004. emoji = conform_emoji_copy(stack, node);
  1005. if (eaten) eaten.push(...cps.slice(pos).reverse()); // copy input (if needed)
  1006. cps.length = pos; // truncate
  1007. }
  1008. }
  1009. /*
  1010. // *** this code currently isn't needed ***
  1011. if (!emoji) {
  1012. let cp = cps[cps.length-1];
  1013. if (EMOJI_SOLO.has(cp)) {
  1014. if (eaten) eaten.push(cp);
  1015. emoji = Emoji.of(cp);
  1016. cps.pop();
  1017. }
  1018. }
  1019. */
  1020. return emoji;
  1021. }
  1022. // create a copy and fix any unicode quirks
  1023. function conform_emoji_copy(cps, node) {
  1024. let copy = Emoji.from(cps); // copy stack
  1025. if (node.V == 2) copy.splice(1, 1); // delete FE0F at position 1 (see: make.js)
  1026. return copy;
  1027. }
  1028. // return all supported emoji as fully-qualified emoji
  1029. // ordered by length then lexicographic
  1030. function ens_emoji() {
  1031. // *** this code currently isn't needed ***
  1032. //let ret = [...EMOJI_SOLO].map(x => [x]);
  1033. let ret = [];
  1034. build(EMOJI_ROOT, []);
  1035. return ret.sort(compare_arrays);
  1036. function build(node, cps, saved) {
  1037. if (node.S) {
  1038. saved = cps[cps.length-1];
  1039. } else if (node.C) {
  1040. if (saved === cps[cps.length-1]) return;
  1041. }
  1042. if (node.F) cps.push(FE0F);
  1043. if (node.V) ret.push(conform_emoji_copy(cps, node));
  1044. for (let br of node.B) {
  1045. for (let cp of br.Q) {
  1046. build(br, [...cps, cp], saved);
  1047. }
  1048. }
  1049. }
  1050. }
  1051. // ************************************************************
  1052. // tokenizer
  1053. const TY_VALID = 'valid';
  1054. const TY_MAPPED = 'mapped';
  1055. const TY_IGNORED = 'ignored';
  1056. const TY_DISALLOWED = 'disallowed';
  1057. const TY_EMOJI = 'emoji';
  1058. const TY_NFC = 'nfc';
  1059. const TY_STOP = 'stop';
  1060. function ens_tokenize(name, {
  1061. nf = true, // collapse unnormalized runs into a single token
  1062. } = {}) {
  1063. let input = explode_cp(name).reverse();
  1064. let eaten = [];
  1065. let tokens = [];
  1066. while (input.length) {
  1067. let emoji = consume_emoji_reversed(input, eaten);
  1068. if (emoji) {
  1069. tokens.push({type: TY_EMOJI, emoji, input: eaten.slice(), cps: filter_fe0f(emoji)});
  1070. } else {
  1071. let cp = input.pop();
  1072. if (cp == STOP) {
  1073. tokens.push({type: TY_STOP, cp});
  1074. } else if (VALID.has(cp)) {
  1075. tokens.push({type: TY_VALID, cps: [cp]});
  1076. } else if (IGNORED.has(cp)) {
  1077. tokens.push({type: TY_IGNORED, cp});
  1078. } else {
  1079. let cps = MAPPED.get(cp);
  1080. if (cps) {
  1081. tokens.push({type: TY_MAPPED, cp, cps: cps.slice()});
  1082. } else {
  1083. tokens.push({type: TY_DISALLOWED, cp});
  1084. }
  1085. }
  1086. }
  1087. }
  1088. if (nf) {
  1089. for (let i = 0, start = -1; i < tokens.length; i++) {
  1090. let token = tokens[i];
  1091. if (is_valid_or_mapped(token.type)) {
  1092. if (requires_check(token.cps)) { // normalization might be needed
  1093. let end = i + 1;
  1094. for (let pos = end; pos < tokens.length; pos++) { // find adjacent text
  1095. let {type, cps} = tokens[pos];
  1096. if (is_valid_or_mapped(type)) {
  1097. if (!requires_check(cps)) break;
  1098. end = pos + 1;
  1099. } else if (type !== TY_IGNORED) { // || type !== TY_DISALLOWED) {
  1100. break;
  1101. }
  1102. }
  1103. if (start < 0) start = i;
  1104. let slice = tokens.slice(start, end);
  1105. let cps0 = slice.flatMap(x => is_valid_or_mapped(x.type) ? x.cps : []); // strip junk tokens
  1106. let cps = nfc(cps0);
  1107. if (compare_arrays(cps, cps0)) { // bundle into an nfc token
  1108. tokens.splice(start, end - start, {
  1109. type: TY_NFC,
  1110. input: cps0, // there are 3 states: tokens0 ==(process)=> input ==(nfc)=> tokens/cps
  1111. cps,
  1112. tokens0: collapse_valid_tokens(slice),
  1113. tokens: ens_tokenize(str_from_cps(cps), {nf: false})
  1114. });
  1115. i = start;
  1116. } else {
  1117. i = end - 1; // skip to end of slice
  1118. }
  1119. start = -1; // reset
  1120. } else {
  1121. start = i; // remember last
  1122. }
  1123. } else if (token.type !== TY_IGNORED) { // 20221024: is this correct?
  1124. start = -1; // reset
  1125. }
  1126. }
  1127. }
  1128. return collapse_valid_tokens(tokens);
  1129. }
  1130. function is_valid_or_mapped(type) {
  1131. return type == TY_VALID || type == TY_MAPPED;
  1132. }
  1133. function requires_check(cps) {
  1134. return cps.some(cp => NFC_CHECK.has(cp));
  1135. }
  1136. function collapse_valid_tokens(tokens) {
  1137. for (let i = 0; i < tokens.length; i++) {
  1138. if (tokens[i].type == TY_VALID) {
  1139. let j = i + 1;
  1140. while (j < tokens.length && tokens[j].type == TY_VALID) j++;
  1141. tokens.splice(i, j - i, {type: TY_VALID, cps: tokens.slice(i, j).flatMap(x => x.cps)});
  1142. }
  1143. }
  1144. return tokens;
  1145. }
  1146. function hex_seq(cps) {
  1147. return cps.map(hex_cp).join(' ');
  1148. }
  1149. function create_arrow_span() {
  1150. let span = document.createElement('span');
  1151. span.classList.add('arrow');
  1152. span.innerHTML = '➔'; // '→';
  1153. return span;
  1154. }
  1155. function span_from_cp(cp, in_emoji) {
  1156. let span = document.createElement('span');
  1157. if (cp == 0x200D) {
  1158. span.classList.add('mod', 'zwj');
  1159. span.innerText = 'ZWJ';
  1160. } else if (cp == 0x200C) {
  1161. span.classList.add('mod', 'zwj');
  1162. span.innerText = 'ZWNJ';
  1163. } else if (cp == 0xFE0F) {
  1164. span.classList.add('mod', 'dropped', 'style');
  1165. span.innerText = 'FE0F';
  1166. } else if (cp == 0x20E3) {
  1167. span.classList.add('mod', 'keycap');
  1168. span.innerText = 'Keycap';
  1169. } else if (cp >= 0xE0021 && cp <= 0xE007E) { // printable ascii tag
  1170. span.classList.add('mod', 'tag');
  1171. span.innerText = String.fromCodePoint(cp - 0xE0000);
  1172. } else if (cp == 0xE007F) { // tag end
  1173. span.classList.add('mod', 'tag', 'end');
  1174. span.innerText = '⌫'; // 🏷️
  1175. } else if (!in_emoji && should_escape(cp)) {
  1176. span.classList.add('code');
  1177. span.innerText = hex_cp(cp);
  1178. } else {
  1179. span.innerText = safe_str_from_cps([cp]);
  1180. }
  1181. return span;
  1182. }
  1183. // idea
  1184. //export function dom_from_token(token) {
  1185. function format_tooltip(obj, extra) {
  1186. let lines = Object.entries(obj).map(([k, v]) => `${k}: ${v}`);
  1187. if (Array.isArray(extra)) lines.push(...extra);
  1188. return lines.join('\n');
  1189. }
  1190. function isolated_safe(cps) {
  1191. return cps.map(cp => safe_str_from_cps([cp])).join('\u{200B}')
  1192. }
  1193. // TODO: these options are shit, fix this
  1194. function dom_from_tokens(tokens, {
  1195. before = false,
  1196. tld_class = true,
  1197. components = false,
  1198. emoji_url = 'https://emojipedia.org/%s',
  1199. extra = () => {},
  1200. } = {}) {
  1201. let div = document.createElement('div');
  1202. div.classList.add('tokens');
  1203. /*
  1204. if (before) {
  1205. // dont use normalized form unless its simple
  1206. tokens = tokens.flatMap(token => {
  1207. if (token.type === 'nfc' && !token.tokens.every(t => t.type == 'valid')) {
  1208. return token.tokens;
  1209. } else {
  1210. return token;
  1211. }
  1212. });
  1213. }
  1214. */
  1215. div.append(...tokens.map((token, i) => {
  1216. let el;
  1217. switch (token.type) {
  1218. case 'emoji': {
  1219. el = document.createElement(emoji_url ? 'a' : 'span');
  1220. if (emoji_url) el.href = emoji_url.replace('%s', String.fromCodePoint(...token.emoji));
  1221. let cps = before ? token.input : token.cps;
  1222. if (components) {
  1223. el.append(...cps.map(cp => span_from_cp(cp, true)));
  1224. } else {
  1225. el.innerText = String.fromCodePoint(...token.emoji); // use fully-qualified form
  1226. }
  1227. el.title = format_tooltip({
  1228. Type: 'Emoji',
  1229. Hex: hex_seq(cps),
  1230. Beautified: hex_seq(token.emoji),
  1231. }, extra(token.type, cps));
  1232. break;
  1233. }
  1234. case 'nfc': {
  1235. el = document.createElement('div');
  1236. // get the cps from the original tokens
  1237. let cps0 = token.tokens0.flatMap(t => t.type === 'valid' ? t.cps : t.cp); // this can only be mapped/ignored/valid
  1238. // break every valid token into individual characters
  1239. let lhs = dom_from_tokens(token.tokens0.flatMap(t => t.type === 'valid' ? t.cps.map(cp => ({type: 'valid', cps: [cp]})) : t), {components, before, emoji_url, extra});
  1240. lhs.title = format_tooltip({
  1241. Type: 'NFC (Unnormalized)',
  1242. Hex: hex_seq(cps0),
  1243. }, extra(token.type, cps0));
  1244. el.append(lhs);
  1245. if (!before) {
  1246. let rhs = dom_from_tokens(token.tokens, {components, emoji_url, extra});
  1247. rhs.title = format_tooltip({
  1248. Type: 'NFC (Normalized)',
  1249. Hex: hex_seq(token.cps),
  1250. }, extra(token.type, token.cps));
  1251. el.append(create_arrow_span(), rhs);
  1252. }
  1253. break;
  1254. }
  1255. case 'valid': {
  1256. el = document.createElement('span');
  1257. let form = safe_str_from_cps(token.cps);
  1258. if (tld_class && (tokens.length == 1 || (i === tokens.length-1 && tokens[i-1].type === 'stop')) && /[a-z]/.test(form)) {
  1259. // theres just 1 token/or we're the last token with a stop before us
  1260. el.classList.add(form);
  1261. }
  1262. el.innerText = form;
  1263. el.title = format_tooltip({
  1264. Type: 'Valid',
  1265. Hex: hex_seq(token.cps),
  1266. }, extra(token.type, token.cps));
  1267. break;
  1268. }
  1269. case 'mapped': {
  1270. el = document.createElement('div');
  1271. let span_src = document.createElement('span');
  1272. span_src.classList.add('before');
  1273. span_src.innerText = safe_str_from_cps([token.cp]); // isolate ? isolated_safe([token.cp]) :
  1274. span_src.title = format_tooltip({
  1275. Type: 'Mapped (Match)',
  1276. Hex: hex_cp(token.cp),
  1277. }, extra(token.type, [token.cp]));
  1278. el.append(span_src);
  1279. if (!before) {
  1280. let span_dst = document.createElement('span');
  1281. span_dst.innerText = isolated_safe(token.cps); // safe_str_from_cps(token.cps);
  1282. span_dst.title = format_tooltip({
  1283. Type: 'Mapped (Replacement)',
  1284. Hex: hex_seq(token.cps),
  1285. }, extra(token.type, token.cps));
  1286. el.append(create_arrow_span(), span_dst);
  1287. }
  1288. break;
  1289. }
  1290. case 'stop':
  1291. case 'ignored':
  1292. case 'disallowed': {
  1293. el = span_from_cp(token.cp);
  1294. el.title = format_tooltip({
  1295. Type: token.type,
  1296. Hex: hex_cp(token.cp),
  1297. }, extra(token.type, [token.cp]));
  1298. break;
  1299. }
  1300. default: throw new TypeError(`unknown token type: ${token.type}`);
  1301. }
  1302. el.classList.add(token.type);
  1303. return el;
  1304. }));
  1305. return div;
  1306. }
  1307. function use_default_style() {
  1308. let style = document.createElement('style');
  1309. style.innerText = `
  1310. .tokens {
  1311. display: flex;
  1312. flex-wrap: wrap;
  1313. gap: 2px;
  1314. }
  1315. .tokens > * {
  1316. padding: 2px 4px;
  1317. display: flex;
  1318. align-items: center;
  1319. gap: 4px;
  1320. border-radius: 5px;
  1321. overflow: hidden;
  1322. }
  1323. .tokens a {
  1324. text-decoration: none;
  1325. }
  1326. .tokens a:hover {
  1327. border-color: #00f;
  1328. }
  1329. .tokens .valid {
  1330. background: #cfc;
  1331. border: 2px solid #0a0;
  1332. line-break: anywhere;
  1333. }
  1334. .tokens .valid.eth {
  1335. color: #fff;
  1336. background: #58f;
  1337. border: none;
  1338. }
  1339. .tokens .valid.art {
  1340. color: #fff;
  1341. background: #333; /*#f63;*/
  1342. border: none;
  1343. }
  1344. .tokens .valid.com,
  1345. .tokens .valid.net,
  1346. .tokens .valid.org,
  1347. .tokens .valid.io,
  1348. .tokens .valid.cash,
  1349. .tokens .valid.xyz {
  1350. color: #fff;
  1351. background: #0a0;
  1352. border: none;
  1353. }
  1354. .tokens .ignored {
  1355. color: #fff;
  1356. background: #aaa;
  1357. font-size: 75%;
  1358. font-family: monospace;
  1359. }
  1360. .tokens .disallowed {
  1361. background: #c00;
  1362. min-width: 5px;
  1363. min-height: 1em;
  1364. border-radius: 5px;
  1365. color: #fff;
  1366. }
  1367. .tokens .disallowed.code {
  1368. font-size: 75%;
  1369. background: #800;
  1370. }
  1371. .tokens .disallowed.mod {
  1372. border: 2px solid #800;
  1373. font-size: 80%;
  1374. }
  1375. .tokens .disallowed.mod.tag {
  1376. background: #f00;
  1377. color: #000;
  1378. }
  1379. .tokens .mapped {
  1380. display: flex;
  1381. border: 2px solid #66f;
  1382. background: #ccf;
  1383. }
  1384. .tokens .mapped span:first-child {
  1385. margin-bottom: -4px;
  1386. border-bottom: 4px solid #000;
  1387. text-align: center;
  1388. min-width: 0.5rem;
  1389. }
  1390. .tokens .stop {
  1391. font-weight: bold;
  1392. background: linear-gradient(#fff, #ff0);
  1393. padding-bottom: 0;
  1394. border: 1px solid #ccc;
  1395. }
  1396. .tokens .emoji {
  1397. border: 2px solid #0aa;
  1398. background: #cff;
  1399. color: #000;
  1400. }
  1401. .tokens .mod {
  1402. color: #fff;
  1403. }
  1404. .tokens * .mod {
  1405. font-size: 70%;
  1406. padding: 2px;
  1407. border-radius: 3px;
  1408. }
  1409. .tokens .emoji .mod {
  1410. background: #333;
  1411. }
  1412. .tokens .emoji .mod.zwj {
  1413. background: #0aa;
  1414. }
  1415. .tokens .emoji .mod.tag {
  1416. background: #0aa;
  1417. }
  1418. .tokens .emoji .mod.tag.end {
  1419. background: #066;
  1420. }
  1421. .tokens .emoji .mod.dropped {
  1422. background: #aaa;
  1423. }
  1424. .tokens .arrow {
  1425. color: rgba(0, 0, 0, 0.35);
  1426. }
  1427. .tokens .code {
  1428. font-family: monospace;
  1429. }
  1430. .tokens .nfc {
  1431. display: flex;
  1432. border: 2px solid #c80;
  1433. background: #fd8;
  1434. border-radius: 5px;
  1435. padding: 2px;
  1436. }`;
  1437. document.body.append(style);
  1438. }
  1439. const derived = "2023-02-21T06:30:22.973Z";
  1440. const unicode = "15.0.0 (2022-10-23T06:00:57.990Z)";
  1441. const cldr = "42 (2022-11-04T04:55:37.180Z)";
  1442. const spec_hash = "962316964553fce6188e25a5166a4c1e906333adf53bdf2964c71dedc0f8e2c8";
  1443. const built = "2023-02-21T09:18:13.549Z";
  1444. const version = "1.9.0";
  1445. var includeVersions = /*#__PURE__*/Object.freeze({
  1446. __proto__: null,
  1447. built: built,
  1448. cldr: cldr,
  1449. derived: derived,
  1450. spec_hash: spec_hash,
  1451. unicode: unicode,
  1452. version: version
  1453. });
  1454. export { compare_arrays, dom_from_tokens, ens_beautify, ens_emoji, ens_normalize, ens_normalize_fragment, ens_split, ens_tokenize, explode_cp, hex_cp, is_combining_mark, nfc, nfd, quote_cp, random_choice, random_sample, run_tests, safe_str_from_cps, should_escape, str_from_cps, use_default_style, includeVersions as versions };