maplibre-gl 6.8.0 → 6.9.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 (64) hide show
  1. package/build/generate-unicode-data.ts +321 -110
  2. package/dist/maplibre-gl-dev.mjs +183 -112
  3. package/dist/maplibre-gl-dev.mjs.map +1 -1
  4. package/dist/maplibre-gl-shared-dev.mjs +1877 -330
  5. package/dist/maplibre-gl-shared-dev.mjs.map +1 -1
  6. package/dist/maplibre-gl-shared.mjs +2 -2
  7. package/dist/maplibre-gl-shared.mjs.map +1 -1
  8. package/dist/maplibre-gl-worker-dev.mjs +9 -2
  9. package/dist/maplibre-gl-worker-dev.mjs.map +1 -1
  10. package/dist/maplibre-gl-worker.mjs +2 -2
  11. package/dist/maplibre-gl-worker.mjs.map +1 -1
  12. package/dist/maplibre-gl.d.ts +52 -25
  13. package/dist/maplibre-gl.mjs +36 -36
  14. package/dist/maplibre-gl.mjs.map +1 -1
  15. package/package.json +10 -9
  16. package/src/data/bucket/symbol_bucket.test.ts +2 -9
  17. package/src/data/bucket/symbol_bucket.ts +4 -15
  18. package/src/geo/projection/globe_transform.test.ts +28 -0
  19. package/src/geo/projection/mercator_transform.test.ts +38 -0
  20. package/src/geo/projection/mercator_transform.ts +0 -2
  21. package/src/geo/projection/vertical_perspective_transform.ts +0 -4
  22. package/src/geo/transform_helper.test.ts +11 -0
  23. package/src/geo/transform_helper.ts +18 -16
  24. package/src/index.ts +5 -1
  25. package/src/render/glyph_manager.test.ts +22 -2
  26. package/src/render/glyph_manager.ts +20 -14
  27. package/src/render/painter.ts +3 -1
  28. package/src/render/render_to_texture_interface.ts +2 -1
  29. package/src/source/geojson_source_diff.test.ts +59 -0
  30. package/src/source/geojson_source_diff.ts +3 -5
  31. package/src/source/rtl_text_plugin_status.ts +12 -0
  32. package/src/source/rtl_text_plugin_worker.ts +6 -0
  33. package/src/source/worker.ts +7 -1
  34. package/src/style/evaluation_parameters.ts +0 -12
  35. package/src/style/style.test.ts +46 -1
  36. package/src/style/style.ts +0 -3
  37. package/src/style/style_image.ts +1 -1
  38. package/src/symbol/arabic_shaping.test.ts +61 -0
  39. package/src/symbol/arabic_shaping.ts +207 -0
  40. package/src/symbol/bidi.test.ts +88 -0
  41. package/src/symbol/bidi.ts +210 -0
  42. package/src/symbol/shaping.ts +81 -52
  43. package/src/symbol/transform_text.test.ts +46 -0
  44. package/src/symbol/transform_text.ts +2 -5
  45. package/src/ui/camera.test.ts +69 -0
  46. package/src/ui/camera.ts +25 -17
  47. package/src/ui/map.ts +22 -5
  48. package/src/ui/map_tests/map_render.test.ts +24 -0
  49. package/src/ui/map_tests/map_style.test.ts +53 -1
  50. package/src/util/browser.test.ts +67 -0
  51. package/src/util/browser.ts +18 -5
  52. package/src/util/dom.test.ts +18 -0
  53. package/src/util/dom.ts +4 -3
  54. package/src/util/offscreen_canvas_supported.test.ts +22 -0
  55. package/src/util/offscreen_canvas_supported.ts +8 -1
  56. package/src/util/script_detection.test.ts +53 -1
  57. package/src/util/script_detection.ts +7 -100
  58. package/src/util/unicode_properties.g.ts +128 -9
  59. package/src/webgl/index_buffer.ts +2 -2
  60. package/src/webgl/render_to_texture.test.ts +122 -7
  61. package/src/webgl/render_to_texture.ts +41 -15
  62. package/src/webgl/rtt_fingerprint.test.ts +28 -13
  63. package/src/webgl/rtt_fingerprint.ts +24 -15
  64. package/src/webgl/vertex_array_object.ts +0 -17
@@ -4,7 +4,7 @@ import * as regenerate from 'regenerate';
4
4
  /**
5
5
  * The heuristics in the functions below are based on this version of the
6
6
  * Unicode Standard. This constant should match the `@unicode/unicode-*` package
7
- * in package.json.
7
+ * in package.json, and the vendored extracts in `build/unicode`.
8
8
  *
9
9
  * When upgrading to a new version of the standard, consider any new scripts,
10
10
  * blocks, and characters that may require different script detection.
@@ -27,16 +27,65 @@ async function createSet(blocks: string[], scripts: string[]): Promise<regenerat
27
27
  return set;
28
28
  }
29
29
 
30
+ /**
31
+ * Returns a character class matching the scripts that are written cursively, and so cannot have
32
+ * their letters spaced apart without coming apart.
33
+ *
34
+ * A script is one of them when its letters have a joining type, which says they are drawn joined to
35
+ * what stands beside them. Duployan is written joined up as well, by a rule the character database
36
+ * does not state as a joining type, so it is named here.
37
+ */
38
+ async function isInCursiveScript(): Promise<string> {
39
+ const joiningTypes = await readJoiningTypes();
40
+ const joins = (codePoint: number) => ['D', 'L', 'R', 'C'].includes(joiningTypes.get(codePoint));
41
+
42
+ const set = await createSet([], ['Duployan']);
43
+ const scripts = (await import(`@unicode/unicode-${unicodeVersion}/index.js`)).default.Script;
44
+ for (const script of scripts) {
45
+ if (script === 'Common' || script === 'Inherited') continue;
46
+ const codePoints = (await import(`@unicode/unicode-${unicodeVersion}/Script/${script}/code-points.js`)).default;
47
+ if (codePoints.some(joins)) set.add(codePoints);
48
+ }
49
+
50
+ return set.toString();
51
+ }
52
+
53
+ /**
54
+ * Returns a character class matching the scripts that are written horizontally from right to left.
55
+ *
56
+ * A script is one of them when the bidirectional algorithm reads its letters from right to left. The
57
+ * whole script is taken, the marks and digits written with it included, so that a word keeps all its
58
+ * parts. `Common` is left out: it holds the characters every script shares, a handful of which are
59
+ * read right to left while the rest of it is not.
60
+ */
61
+ async function isInRTLScript(): Promise<string> {
62
+ const readRightToLeft = new Set<number>();
63
+ for (const bidiClass of ['Right_To_Left', 'Arabic_Letter']) {
64
+ const codePoints = (await import(`@unicode/unicode-${unicodeVersion}/Bidi_Class/${bidiClass}/code-points.js`)).default;
65
+ for (const codePoint of codePoints) readRightToLeft.add(codePoint);
66
+ }
67
+
68
+ const scripts = (await import(`@unicode/unicode-${unicodeVersion}/index.js`)).default.Script;
69
+ const set = regenerate.default();
70
+ for (const script of scripts) {
71
+ if (script === 'Common') continue;
72
+ const codePoints = (await import(`@unicode/unicode-${unicodeVersion}/Script/${script}/code-points.js`)).default;
73
+ if (codePoints.some((codePoint: number) => readRightToLeft.has(codePoint))) set.add(codePoints);
74
+ }
75
+
76
+ return set.toString();
77
+ }
78
+
79
+ /**
80
+ * Returns a character class matching the blocks whose glyphs are drawn locally by TinySDF.
81
+ *
82
+ * These are the writing systems TinySDF draws well and cheaply: in general, any system typically set
83
+ * in a monospaced font. Hanzi, Kanji and Hanja, from the CJK Unified Ideographs blocks, are the
84
+ * clearest case, with more than 99,000 codepoints reached essentially at random, which is a great
85
+ * deal of bandwidth to spend on a glyph server. The smaller CJKV and other siniform blocks are drawn
86
+ * locally too, so that text mixing them looks of a piece.
87
+ */
30
88
  async function usesLocalIdeographFontFamily(): Promise<string> {
31
- // Local rendering is preferred for Unicode code blocks that represent
32
- // writing systems for which TinySDF produces optimal results and greatly
33
- // reduces bandwidth consumption. In general, TinySDF is best for any
34
- // writing system typically set in a monospaced font. With more than 99,000
35
- // codepoints accessed essentially at random, Hanzi/Kanji/Hanja (from the
36
- // CJK Unified Ideographs blocks) is the canonical example of wasteful
37
- // bandwidth consumption when rendered remotely. For visual consistency
38
- // within CJKV text, even relatively small CJKV and other siniform code
39
- // blocks prefer local rendering.
40
89
  const set = await createSet([
41
90
  'CJK Compatibility Forms',
42
91
  'CJK Compatibility',
@@ -112,67 +161,28 @@ async function allowsIdeographicBreaking(): Promise<string> {
112
161
  return set.toString();
113
162
  }
114
163
 
115
- // The following logic comes from
116
- // <https://www.unicode.org/Public/17.0.0/ucd/VerticalOrientation.txt>.
117
- // Keep it synchronized with
118
- // <https://www.unicode.org/Public/UCD/latest/ucd/VerticalOrientation.txt>.
119
- // The data file denotes with “U” or “Tu” any codepoint that may be drawn
120
- // upright in vertical text but does not distinguish between upright and
121
- // neutral characters.
122
-
164
+ /**
165
+ * Returns a character class matching the characters drawn upright in vertical text.
166
+ *
167
+ * `Vertical_Orientation` names every character that may be drawn upright, without saying which of
168
+ * them stand upright in their own right and which only follow the characters beside them. Those that
169
+ * follow are taken out, being the ones {@link neutralVerticalOrientationSet} names, and the blocks
170
+ * added back below are the ones read both ways: upright standing alone, neutral in company. What is
171
+ * removed after that is a character that block reads the other way.
172
+ */
123
173
  async function hasUprightVerticalOrientation(): Promise<string> {
124
- const set = await createSet([
125
- 'Alchemical Symbols',
126
- 'Anatolian Hieroglyphs',
127
- 'Byzantine Musical Symbols',
128
- 'Chess Symbols',
174
+ const set = regenerate.default();
175
+ for (const orientation of ['U', 'Tu']) {
176
+ set.add((await import(`@unicode/unicode-${unicodeVersion}/Vertical_Orientation/${orientation}/code-points.js`)).default);
177
+ }
178
+ set.remove(await neutralVerticalOrientationSet());
179
+ set.add(await createSet([
129
180
  'CJK Compatibility Forms',
130
- 'CJK Compatibility',
131
- 'CJK Strokes',
132
181
  'CJK Symbols And Punctuation',
133
- 'Counting Rod Numerals',
134
- 'Domino Tiles',
135
- 'Emoticons',
136
- 'Enclosed Alphanumeric Supplement',
137
- 'Enclosed CJK Letters And Months',
138
- 'Geometric Shapes Extended',
139
182
  'Halfwidth And Fullwidth Forms',
140
- 'Ideographic Description Characters',
141
- 'Kanbun',
142
183
  'Katakana',
143
- 'Mahjong Tiles',
144
- 'Mayan Numerals',
145
- 'Meroitic Hieroglyphs',
146
- 'Miscellaneous Symbols And Pictographs',
147
- 'Miscellaneous Symbols Supplement',
148
- 'Musical Symbols',
149
- 'Ornamental Dingbats',
150
- 'Playing Cards',
151
- 'Siddham',
152
184
  'Small Form Variants',
153
- 'Small Kana Extension',
154
- 'Soyombo',
155
- 'Supplemental Symbols And Pictographs',
156
- 'Sutton SignWriting',
157
- 'Symbols And Pictographs Extended-A',
158
- 'Tai Xuan Jing Symbols',
159
- 'Transport And Map Symbols',
160
- 'Vertical Forms',
161
- 'Yijing Hexagram Symbols',
162
- 'Zanabazar Square',
163
- 'Znamenny Musical Notation',
164
- ], [
165
- 'Bopomofo',
166
- 'Canadian Aboriginal',
167
- 'Han',
168
- 'Hangul',
169
- 'Hiragana',
170
- 'Katakana',
171
- 'Khitan Small Script',
172
- 'Nushu',
173
- 'Tangut',
174
- 'Yi',
175
- ]);
185
+ ], []));
176
186
 
177
187
  set.add(0x02EA /* modifier letter yin departing tone mark */);
178
188
  set.add(0x02EB /* modifier letter yang departing tone mark */);
@@ -208,6 +218,16 @@ async function hasUprightVerticalOrientation(): Promise<string> {
208
218
  }
209
219
 
210
220
  async function hasNeutralVerticalOrientation(): Promise<string> {
221
+ return (await neutralVerticalOrientationSet()).toString();
222
+ }
223
+
224
+ /**
225
+ * The characters drawn upright in vertical text only because the characters beside them are.
226
+ *
227
+ * `Vertical_Orientation` does not draw this distinction, naming both these and the characters that
228
+ * stand upright in their own right, so which of them merely follow their neighbours is settled here.
229
+ */
230
+ async function neutralVerticalOrientationSet(): Promise<regenerate.regenerate> {
211
231
  const set = await createSet([
212
232
  'CJK Compatibility Forms',
213
233
  'CJK Symbols And Punctuation',
@@ -281,36 +301,7 @@ async function hasNeutralVerticalOrientation(): Promise<string> {
281
301
  set.add(0xFFFC /* object replacement character */);
282
302
  set.add(0xFFFD /* replacement character */);
283
303
 
284
- return set.toString();
285
- }
286
-
287
- async function requiresComplexTextShaping(): Promise<string> {
288
- // This is a rough heuristic: whether we "can render" a script
289
- // actually depends on the properties of the font being used
290
- // and whether differences from the ideal rendering are considered
291
- // semantically significant.
292
-
293
- // These blocks cover common scripts that require
294
- // complex text shaping, based on unicode script metadata:
295
- // https://www.unicode.org/repos/cldr/trunk/common/properties/scriptMetadata.txt
296
- // where "Web Rank <= 32" "Shaping Required = YES"
297
- const set = await createSet([
298
- 'Bengali',
299
- 'Devanagari',
300
- 'Gujarati',
301
- 'Gurmukhi',
302
- 'Kannada',
303
- 'Khmer',
304
- 'Malayalam',
305
- 'Myanmar',
306
- 'Oriya',
307
- 'Tamil',
308
- 'Telugu',
309
- 'Tibetan',
310
- 'Sinhala',
311
- ], []);
312
-
313
- return set.toString();
304
+ return set;
314
305
  }
315
306
 
316
307
  /**
@@ -337,19 +328,20 @@ async function canFormGraphemeCluster(): Promise<string> {
337
328
  * Text in these has no punctuation to break a line at, so the only way to wrap it is to ask the
338
329
  * browser's word segmenter where the words are. Elsewhere the segmenter is the wrong tool: it
339
330
  * isolates a comma as a word of its own, and a line must not begin with one.
331
+ *
332
+ * `Line_Break` names the South East Asian scripts that need a dictionary to be broken into lines.
333
+ * The three scripts added to them are written without spaces as well, and are broken by the same
334
+ * means, but the standard gives them a line breaking class of their own.
340
335
  */
341
336
  async function isWrittenWithoutSpaces(): Promise<string> {
342
- const set = await createSet([], [
337
+ const set = regenerate.default();
338
+ set.add((await import(`@unicode/unicode-${unicodeVersion}/Line_Break/Complex_Context/code-points.js`)).default);
339
+
340
+ return set.add(await createSet([], [
343
341
  'Balinese',
344
342
  'Javanese',
345
- 'Khmer',
346
- 'Lao',
347
- 'Myanmar',
348
- 'Thai',
349
343
  'Tibetan',
350
- ]);
351
-
352
- return set.toString();
344
+ ])).toString();
353
345
  }
354
346
 
355
347
  /**
@@ -368,9 +360,214 @@ async function joinsToTheFollowingGrapheme(): Promise<string> {
368
360
  return set.toString();
369
361
  }
370
362
 
363
+ const downloadedRows = new Map<string, Promise<string[][]>>();
364
+ /**
365
+ * The rows of one file of the Unicode Character Database, each split into its fields, with the
366
+ * comments and blank lines dropped. A file is downloaded from unicode.org and parsed once, and kept
367
+ * for the rest of the run.
368
+ */
369
+ function unicodeDataRows(file: string): Promise<string[][]> {
370
+ if (!downloadedRows.has(file)) {
371
+ downloadedRows.set(file, (async () => {
372
+ const url = `https://www.unicode.org/Public/${unicodeVersion}/ucd/${file}`;
373
+ const response = await fetch(url);
374
+ if (!response.ok) {
375
+ throw new Error(`Failed to download ${url}: ${response.status} ${response.statusText}`);
376
+ }
377
+ return (await response.text())
378
+ .split('\n')
379
+ .map(line => line.split('#')[0])
380
+ .filter(line => line.trim())
381
+ .map(line => line.split(';').map(field => field.trim()));
382
+ })());
383
+ }
384
+ return downloadedRows.get(file);
385
+ }
386
+
387
+ /**
388
+ * The blocks the Arabic script is written from, plus the two joiners that steer it.
389
+ *
390
+ * Only Arabic is tabulated. A cursive script outside these blocks has no presentation forms, and so
391
+ * no code point by which MapLibre could name a shaped glyph.
392
+ */
393
+ const arabicBlocks: Array<[number, number]> = [
394
+ [0x0600, 0x06ff],
395
+ [0x0750, 0x077f],
396
+ [0x0870, 0x089f],
397
+ [0x08a0, 0x08ff],
398
+ [0xfb50, 0xfdff],
399
+ [0xfe70, 0xfeff],
400
+ [0x200c, 0x200d],
401
+ ];
402
+
403
+ function isArabic(codePoint: number): boolean {
404
+ return arabicBlocks.some(([start, end]) => codePoint >= start && codePoint <= end);
405
+ }
406
+
407
+ /**
408
+ * How a character joins to the ones beside it: `R` to the character before it, `L` to the one after
409
+ * it, `D` to both, `C` without being written itself, `T` not at all while letting the two around it
410
+ * join through it, and `U` not at all.
411
+ */
412
+ type JoiningType = 'R' | 'L' | 'D' | 'C' | 'U' | 'T';
413
+
414
+ /**
415
+ * The joining type of every character: which side, if any, it joins the letters beside it on.
416
+ *
417
+ * `DerivedJoiningType.txt` states the property for every code point, the combining marks and format
418
+ * characters that join through included.
419
+ */
420
+ async function readJoiningTypes(): Promise<Map<number, JoiningType>> {
421
+ const joiningTypes = new Map<number, JoiningType>();
422
+
423
+ for (const [codePoints, type] of await unicodeDataRows('extracted/DerivedJoiningType.txt')) {
424
+ const [first, last] = codePoints.split('..').map(hex => parseInt(hex, 16));
425
+ for (let codePoint = first; codePoint <= (last ?? first); codePoint++) {
426
+ joiningTypes.set(codePoint, type as JoiningType);
427
+ }
428
+ }
429
+
430
+ return joiningTypes;
431
+ }
432
+
433
+ /** The four shapes a cursive script writes a letter in. */
434
+ type PresentationForms = {isolated: number; final: number; initial: number; medial: number};
435
+
436
+ /**
437
+ * Tatweel, the stroke a word is stretched along, and the space a mark is shown over on its own.
438
+ *
439
+ * A mark has no shape without a letter under it, so the database gives its presentation forms as
440
+ * decompositions onto one of these: `<medial> 0640 064B` is "fathatan, as written over a letter
441
+ * mid-word", and `<isolated> 0020 064B` is the same mark standing by itself.
442
+ */
443
+ const markCarriers = new Set([0x0020, 0x0640]);
444
+
445
+ /**
446
+ * The Presentation Forms shape of each Arabic letter, and the two shapes of each lam-alef ligature.
447
+ *
448
+ * The shapes come from the compatibility decompositions of the presentation blocks: `<final> 0628`
449
+ * is what says U+FE90 is the final form of beh.
450
+ *
451
+ * Of the two-character decompositions only lam-alef is taken. It is the one ligature Arabic shaping
452
+ * is required to form; the rest of the presentation blocks are typographic ligatures, which a font
453
+ * offers and the text is free to do without.
454
+ */
455
+ async function readPresentationForms(): Promise<{
456
+ forms: Map<number, PresentationForms>;
457
+ ligatures: Map<string, {isolated: number; final: number}>;
458
+ }> {
459
+ const forms = new Map<number, PresentationForms>();
460
+ const ligatures = new Map<string, {isolated: number; final: number}>();
461
+
462
+ for (const row of await unicodeDataRows('UnicodeData.txt')) {
463
+ const codePoint = parseInt(row[0], 16);
464
+ if (codePoint < 0xfb50 || codePoint > 0xfeff) continue;
465
+
466
+ const match = /^<(isolated|final|initial|medial)>\s+(.+)$/.exec(row[5]);
467
+ if (!match) continue;
468
+
469
+ const shape = match[1] as keyof PresentationForms;
470
+ const bases = match[2].split(/\s+/).map(base => parseInt(base, 16));
471
+ const carried = bases.length === 2 && markCarriers.has(bases[0]);
472
+
473
+ if (bases.length === 1 || carried) {
474
+ const base = carried ? bases[1] : bases[0];
475
+ const existing = forms.get(base) ?? {isolated: 0, final: 0, initial: 0, medial: 0};
476
+ existing[shape] = codePoint;
477
+ forms.set(base, existing);
478
+ } else if (codePoint >= 0xfef5 && codePoint <= 0xfefc) {
479
+ const pair = String.fromCodePoint(...bases);
480
+ const existing = ligatures.get(pair) ?? {isolated: 0, final: 0};
481
+ existing[shape as 'isolated' | 'final'] = codePoint;
482
+ ligatures.set(pair, existing);
483
+ }
484
+ }
485
+
486
+ return {forms, ligatures};
487
+ }
488
+
489
+ /**
490
+ * Packs a sorted list of code points into `start,length` pairs, delta-encoded in base 36.
491
+ *
492
+ * The joining types run in long unbroken stretches, so the ranges take a fraction of the space the
493
+ * code points would.
494
+ */
495
+ function encodeCodePointRanges(codePoints: number[]): string {
496
+ const ranges: number[][] = [];
497
+ for (const codePoint of codePoints.sort((a, b) => a - b)) {
498
+ const last = ranges[ranges.length - 1];
499
+ if (last && last[0] + last[1] === codePoint) {
500
+ last[1]++;
501
+ } else {
502
+ ranges.push([codePoint, 1]);
503
+ }
504
+ }
505
+
506
+ let previousEnd = 0;
507
+ return ranges
508
+ .map(([start, length]) => {
509
+ const encoded = `${(start - previousEnd).toString(36)},${length.toString(36)}`;
510
+ previousEnd = start + length;
511
+ return encoded;
512
+ })
513
+ .join(';');
514
+ }
515
+
516
+ /** The joining types, as one encoded set of ranges per type, ready to print as an object literal. */
517
+ async function encodedJoiningTypes(): Promise<string> {
518
+ const byType = new Map<JoiningType, number[]>();
519
+ for (const [codePoint, type] of await readJoiningTypes()) {
520
+ if (type === 'U' || !isArabic(codePoint)) continue;
521
+ byType.set(type, (byType.get(type) ?? []).concat(codePoint));
522
+ }
523
+
524
+ return [...byType]
525
+ .sort(([a], [b]) => a.localeCompare(b))
526
+ .map(([type, codePoints]) => ` ${type}: '${encodeCodePointRanges(codePoints)}',`)
527
+ .join('\n');
528
+ }
529
+
530
+ /** Each Arabic letter's four presentation forms, ready to print as an object literal. */
531
+ async function encodedPresentationForms(): Promise<string> {
532
+ return [...(await readPresentationForms()).forms]
533
+ .sort(([a], [b]) => a - b)
534
+ .map(([base, {isolated, final, initial, medial}]) =>
535
+ ` ${base}: [${isolated}, ${final}, ${initial}, ${medial}],`)
536
+ .join('\n');
537
+ }
538
+
539
+ /** The lam-alef ligatures, ready to print as an object literal. */
540
+ async function encodedLigatures(): Promise<string> {
541
+ return [...(await readPresentationForms()).ligatures]
542
+ .sort(([a], [b]) => a.localeCompare(b))
543
+ .map(([pair, {isolated, final}]) => {
544
+ const escaped = [...pair]
545
+ .map(character => `\\u${character.codePointAt(0).toString(16).padStart(4, '0')}`)
546
+ .join('');
547
+ return ` '${escaped}': [${isolated}, ${final}],`;
548
+ })
549
+ .join('\n');
550
+ }
551
+
371
552
  fs.writeFileSync('src/util/unicode_properties.g.ts',
372
553
  `// This file is generated. Edit build/generate-unicode-data.ts, then run \`npm run generate-unicode-data\`.
373
554
 
555
+ /**
556
+ * Returns whether the given codepoint belongs to a script that is written cursively, whose letters
557
+ * therefore cannot be spaced apart.
558
+ */
559
+ export function codePointIsInCursiveScript(codePoint: number): boolean {
560
+ return /${await isInCursiveScript()}/gim.test(String.fromCodePoint(codePoint));
561
+ }
562
+
563
+ /**
564
+ * Returns whether the given codepoint belongs to a script that is written horizontally from right
565
+ * to left.
566
+ */
567
+ export function codePointIsInRTLScript(codePoint: number): boolean {
568
+ return /${await isInRTLScript()}/gim.test(String.fromCodePoint(codePoint));
569
+ }
570
+
374
571
  /**
375
572
  * Returns whether the fallback fonts specified by the
376
573
  * \`localIdeographFontFamily\` map option apply to the given codepoint.
@@ -416,13 +613,6 @@ export function codePointHasNeutralVerticalOrientation(codePoint: number): boole
416
613
  return /${await hasNeutralVerticalOrientation()}/gim.test(String.fromCodePoint(codePoint));
417
614
  }
418
615
 
419
- /**
420
- * Returns whether the give codepoint is likely to require complex text shaping.
421
- */
422
- export function codePointRequiresComplexTextShaping(codePoint: number): boolean {
423
- return /${await requiresComplexTextShaping()}/gim.test(String.fromCodePoint(codePoint));
424
- }
425
-
426
616
  /**
427
617
  * Returns whether the text could hold a grapheme cluster of more than one codepoint, and so is worth
428
618
  * segmenting. A negative answer means every codepoint of it stands alone.
@@ -450,4 +640,25 @@ export function codePointIsWrittenWithoutSpaces(codePoint: number): boolean {
450
640
  export function canCombineGraphemes(former: string, latter: string): boolean {
451
641
  return /(?:${await joinsToTheFollowingGrapheme()})$/.test(former) || /^\\p{gc=Mc}/u.test(latter);
452
642
  }
643
+
644
+ /**
645
+ * The joining type of each Arabic character, as \`start,length\` code point ranges delta-encoded in
646
+ * base 36, one entry per type. Anything absent from all of them is non-joining.
647
+ */
648
+ export const ENCODED_JOINING_TYPES: Record<string, string> = {
649
+ ${await encodedJoiningTypes()}
650
+ };
651
+
652
+ /**
653
+ * Each Arabic letter's Presentation Forms code points, as \`[isolated, final, initial, medial]\`.
654
+ * A shape the letter is not written in is 0.
655
+ */
656
+ export const PRESENTATION_FORMS: Record<number, [number, number, number, number]> = {
657
+ ${await encodedPresentationForms()}
658
+ };
659
+
660
+ /** The lam-alef ligatures, keyed by the pair of letters they replace, as \`[isolated, final]\`. */
661
+ export const LIGATURES: Record<string, [number, number]> = {
662
+ ${await encodedLigatures()}
663
+ };
453
664
  `);