modern-pdf-lib 0.35.0 → 0.36.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.
- package/README.md +3 -3
- package/dist/browser.cjs +8 -1
- package/dist/browser.d.cts +2 -2
- package/dist/browser.d.mts +2 -2
- package/dist/browser.mjs +2 -2
- package/dist/{index-CIdDdXlK.d.cts → index-06FgCx99.d.cts} +317 -3
- package/dist/index-06FgCx99.d.cts.map +1 -0
- package/dist/{index-CwgepM_G.d.mts → index-Bg3y1BTk.d.mts} +317 -3
- package/dist/index-Bg3y1BTk.d.mts.map +1 -0
- package/dist/index.cjs +8 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/{src-BlUlmYsU.mjs → src-4wfgzZvz.mjs} +1413 -2
- package/dist/{src-C-mBizNU.cjs → src-MIBq9xGq.cjs} +1454 -1
- package/package.json +1 -1
- package/dist/index-CIdDdXlK.d.cts.map +0 -1
- package/dist/index-CwgepM_G.d.mts.map +0 -1
|
@@ -29116,6 +29116,1417 @@ function getNumber(dict, key) {
|
|
|
29116
29116
|
if (obj !== void 0 && obj.kind === "number") return obj.value;
|
|
29117
29117
|
}
|
|
29118
29118
|
//#endregion
|
|
29119
|
+
//#region src/text/bidi.ts
|
|
29120
|
+
/** UAX #9 §3.3.2: the maximum explicit embedding depth. */
|
|
29121
|
+
const MAX_DEPTH = 125;
|
|
29122
|
+
/**
|
|
29123
|
+
* Classify a Unicode scalar value into its {@link BC} Bidi_Class.
|
|
29124
|
+
*
|
|
29125
|
+
* Range-based subset of DerivedBidiClass.txt (Unicode 16.0.0). Covers Latin,
|
|
29126
|
+
* Hebrew, Arabic, the digit groups, neutrals/weaks and all explicit-format and
|
|
29127
|
+
* isolate characters. Anything outside the listed ranges falls back to the
|
|
29128
|
+
* Unicode default class for that area (`R`/`AL` in the default-RTL blocks,
|
|
29129
|
+
* else `L`).
|
|
29130
|
+
*
|
|
29131
|
+
* @param cp Unicode scalar value (code point).
|
|
29132
|
+
* @returns The character's Bidi_Class.
|
|
29133
|
+
*/
|
|
29134
|
+
function bidiClass(cp) {
|
|
29135
|
+
switch (cp) {
|
|
29136
|
+
case 8234: return "LRE";
|
|
29137
|
+
case 8235: return "RLE";
|
|
29138
|
+
case 8237: return "LRO";
|
|
29139
|
+
case 8238: return "RLO";
|
|
29140
|
+
case 8236: return "PDF";
|
|
29141
|
+
case 8294: return "LRI";
|
|
29142
|
+
case 8295: return "RLI";
|
|
29143
|
+
case 8296: return "FSI";
|
|
29144
|
+
case 8297: return "PDI";
|
|
29145
|
+
default: break;
|
|
29146
|
+
}
|
|
29147
|
+
if (cp < 128) {
|
|
29148
|
+
if (cp === 10 || cp === 13 || cp === 28 || cp === 29 || cp === 30 || cp === 133) return "B";
|
|
29149
|
+
if (cp === 9 || cp === 11 || cp === 31) return "S";
|
|
29150
|
+
if (cp === 12 || cp === 32) return "WS";
|
|
29151
|
+
if (cp >= 48 && cp <= 57) return "EN";
|
|
29152
|
+
if (cp === 43 || cp === 45) return "ES";
|
|
29153
|
+
if (cp === 35 || cp === 36 || cp === 37) return "ET";
|
|
29154
|
+
if (cp === 44 || cp === 46 || cp === 47 || cp === 58) return "CS";
|
|
29155
|
+
if (cp >= 65 && cp <= 90 || cp >= 97 && cp <= 122) return "L";
|
|
29156
|
+
return "ON";
|
|
29157
|
+
}
|
|
29158
|
+
if (cp === 133) return "B";
|
|
29159
|
+
if (cp === 160) return "CS";
|
|
29160
|
+
if (cp === 163 || cp === 164 || cp === 165 || cp === 176 || cp === 177) return "ET";
|
|
29161
|
+
if (cp === 171 || cp === 187) return "ON";
|
|
29162
|
+
if (cp >= 1425 && cp <= 1469) return "NSM";
|
|
29163
|
+
if (cp === 1471 || cp === 1473 || cp === 1474 || cp === 1476 || cp === 1477 || cp === 1479) return "NSM";
|
|
29164
|
+
if (cp >= 1424 && cp <= 1535) return "R";
|
|
29165
|
+
if (cp >= 1536 && cp <= 1541) return "AN";
|
|
29166
|
+
if (cp >= 1632 && cp <= 1641) return "AN";
|
|
29167
|
+
if (cp === 1643 || cp === 1644) return "AN";
|
|
29168
|
+
if (cp === 1642) return "ET";
|
|
29169
|
+
if (cp >= 1776 && cp <= 1785) return "EN";
|
|
29170
|
+
if (cp >= 1552 && cp <= 1562) return "NSM";
|
|
29171
|
+
if (cp >= 1611 && cp <= 1631) return "NSM";
|
|
29172
|
+
if (cp === 1648) return "NSM";
|
|
29173
|
+
if (cp >= 1750 && cp <= 1756) return "NSM";
|
|
29174
|
+
if (cp >= 1759 && cp <= 1764) return "NSM";
|
|
29175
|
+
if (cp === 1767 || cp === 1768) return "NSM";
|
|
29176
|
+
if (cp >= 1770 && cp <= 1773) return "NSM";
|
|
29177
|
+
if (cp >= 1536 && cp <= 1791) return "AL";
|
|
29178
|
+
if (cp >= 1792 && cp <= 1871) return "AL";
|
|
29179
|
+
if (cp >= 1872 && cp <= 1919) return "AL";
|
|
29180
|
+
if (cp >= 1920 && cp <= 1983) return "AL";
|
|
29181
|
+
if (cp === 8232) return "WS";
|
|
29182
|
+
if (cp === 8233) return "B";
|
|
29183
|
+
if (cp >= 8192 && cp <= 8202) return "WS";
|
|
29184
|
+
if (cp === 8203) return "BN";
|
|
29185
|
+
if (cp === 8206) return "L";
|
|
29186
|
+
if (cp === 8207) return "R";
|
|
29187
|
+
if (cp === 1564) return "AL";
|
|
29188
|
+
if (cp === 8239) return "CS";
|
|
29189
|
+
if (cp === 8287) return "WS";
|
|
29190
|
+
if (cp === 12288) return "WS";
|
|
29191
|
+
if (cp === 65279) return "BN";
|
|
29192
|
+
if (cp >= 768 && cp <= 879) return "NSM";
|
|
29193
|
+
if (cp >= 8400 && cp <= 8447) return "NSM";
|
|
29194
|
+
return "L";
|
|
29195
|
+
}
|
|
29196
|
+
/**
|
|
29197
|
+
* Map of opening bracket → closing bracket and vice versa for UAX #9 N0
|
|
29198
|
+
* (paired brackets). Subset of BidiBrackets.txt (Unicode 16.0.0) covering the
|
|
29199
|
+
* ASCII and common CJK/typographic pairs. Canonical-equivalent singletons
|
|
29200
|
+
* (U+2329/U+232A) are folded onto their canonical forms per BD16.
|
|
29201
|
+
*/
|
|
29202
|
+
const BRACKET_PAIRS = /* @__PURE__ */ new Map([
|
|
29203
|
+
[40, {
|
|
29204
|
+
other: 41,
|
|
29205
|
+
open: true
|
|
29206
|
+
}],
|
|
29207
|
+
[41, {
|
|
29208
|
+
other: 40,
|
|
29209
|
+
open: false
|
|
29210
|
+
}],
|
|
29211
|
+
[91, {
|
|
29212
|
+
other: 93,
|
|
29213
|
+
open: true
|
|
29214
|
+
}],
|
|
29215
|
+
[93, {
|
|
29216
|
+
other: 91,
|
|
29217
|
+
open: false
|
|
29218
|
+
}],
|
|
29219
|
+
[123, {
|
|
29220
|
+
other: 125,
|
|
29221
|
+
open: true
|
|
29222
|
+
}],
|
|
29223
|
+
[125, {
|
|
29224
|
+
other: 123,
|
|
29225
|
+
open: false
|
|
29226
|
+
}],
|
|
29227
|
+
[3898, {
|
|
29228
|
+
other: 3899,
|
|
29229
|
+
open: true
|
|
29230
|
+
}],
|
|
29231
|
+
[3899, {
|
|
29232
|
+
other: 3898,
|
|
29233
|
+
open: false
|
|
29234
|
+
}],
|
|
29235
|
+
[3900, {
|
|
29236
|
+
other: 3901,
|
|
29237
|
+
open: true
|
|
29238
|
+
}],
|
|
29239
|
+
[3901, {
|
|
29240
|
+
other: 3900,
|
|
29241
|
+
open: false
|
|
29242
|
+
}],
|
|
29243
|
+
[5787, {
|
|
29244
|
+
other: 5788,
|
|
29245
|
+
open: true
|
|
29246
|
+
}],
|
|
29247
|
+
[5788, {
|
|
29248
|
+
other: 5787,
|
|
29249
|
+
open: false
|
|
29250
|
+
}],
|
|
29251
|
+
[8261, {
|
|
29252
|
+
other: 8262,
|
|
29253
|
+
open: true
|
|
29254
|
+
}],
|
|
29255
|
+
[8262, {
|
|
29256
|
+
other: 8261,
|
|
29257
|
+
open: false
|
|
29258
|
+
}],
|
|
29259
|
+
[8317, {
|
|
29260
|
+
other: 8318,
|
|
29261
|
+
open: true
|
|
29262
|
+
}],
|
|
29263
|
+
[8318, {
|
|
29264
|
+
other: 8317,
|
|
29265
|
+
open: false
|
|
29266
|
+
}],
|
|
29267
|
+
[8333, {
|
|
29268
|
+
other: 8334,
|
|
29269
|
+
open: true
|
|
29270
|
+
}],
|
|
29271
|
+
[8334, {
|
|
29272
|
+
other: 8333,
|
|
29273
|
+
open: false
|
|
29274
|
+
}],
|
|
29275
|
+
[9001, {
|
|
29276
|
+
other: 9002,
|
|
29277
|
+
open: true
|
|
29278
|
+
}],
|
|
29279
|
+
[9002, {
|
|
29280
|
+
other: 9001,
|
|
29281
|
+
open: false
|
|
29282
|
+
}],
|
|
29283
|
+
[10088, {
|
|
29284
|
+
other: 10089,
|
|
29285
|
+
open: true
|
|
29286
|
+
}],
|
|
29287
|
+
[10089, {
|
|
29288
|
+
other: 10088,
|
|
29289
|
+
open: false
|
|
29290
|
+
}],
|
|
29291
|
+
[10090, {
|
|
29292
|
+
other: 10091,
|
|
29293
|
+
open: true
|
|
29294
|
+
}],
|
|
29295
|
+
[10091, {
|
|
29296
|
+
other: 10090,
|
|
29297
|
+
open: false
|
|
29298
|
+
}],
|
|
29299
|
+
[10092, {
|
|
29300
|
+
other: 10093,
|
|
29301
|
+
open: true
|
|
29302
|
+
}],
|
|
29303
|
+
[10093, {
|
|
29304
|
+
other: 10092,
|
|
29305
|
+
open: false
|
|
29306
|
+
}],
|
|
29307
|
+
[10094, {
|
|
29308
|
+
other: 10095,
|
|
29309
|
+
open: true
|
|
29310
|
+
}],
|
|
29311
|
+
[10095, {
|
|
29312
|
+
other: 10094,
|
|
29313
|
+
open: false
|
|
29314
|
+
}],
|
|
29315
|
+
[10096, {
|
|
29316
|
+
other: 10097,
|
|
29317
|
+
open: true
|
|
29318
|
+
}],
|
|
29319
|
+
[10097, {
|
|
29320
|
+
other: 10096,
|
|
29321
|
+
open: false
|
|
29322
|
+
}],
|
|
29323
|
+
[10098, {
|
|
29324
|
+
other: 10099,
|
|
29325
|
+
open: true
|
|
29326
|
+
}],
|
|
29327
|
+
[10099, {
|
|
29328
|
+
other: 10098,
|
|
29329
|
+
open: false
|
|
29330
|
+
}],
|
|
29331
|
+
[10100, {
|
|
29332
|
+
other: 10101,
|
|
29333
|
+
open: true
|
|
29334
|
+
}],
|
|
29335
|
+
[10101, {
|
|
29336
|
+
other: 10100,
|
|
29337
|
+
open: false
|
|
29338
|
+
}],
|
|
29339
|
+
[10214, {
|
|
29340
|
+
other: 10215,
|
|
29341
|
+
open: true
|
|
29342
|
+
}],
|
|
29343
|
+
[10215, {
|
|
29344
|
+
other: 10214,
|
|
29345
|
+
open: false
|
|
29346
|
+
}],
|
|
29347
|
+
[10216, {
|
|
29348
|
+
other: 10217,
|
|
29349
|
+
open: true
|
|
29350
|
+
}],
|
|
29351
|
+
[10217, {
|
|
29352
|
+
other: 10216,
|
|
29353
|
+
open: false
|
|
29354
|
+
}],
|
|
29355
|
+
[10218, {
|
|
29356
|
+
other: 10219,
|
|
29357
|
+
open: true
|
|
29358
|
+
}],
|
|
29359
|
+
[10219, {
|
|
29360
|
+
other: 10218,
|
|
29361
|
+
open: false
|
|
29362
|
+
}],
|
|
29363
|
+
[10220, {
|
|
29364
|
+
other: 10221,
|
|
29365
|
+
open: true
|
|
29366
|
+
}],
|
|
29367
|
+
[10221, {
|
|
29368
|
+
other: 10220,
|
|
29369
|
+
open: false
|
|
29370
|
+
}],
|
|
29371
|
+
[10222, {
|
|
29372
|
+
other: 10223,
|
|
29373
|
+
open: true
|
|
29374
|
+
}],
|
|
29375
|
+
[10223, {
|
|
29376
|
+
other: 10222,
|
|
29377
|
+
open: false
|
|
29378
|
+
}],
|
|
29379
|
+
[10627, {
|
|
29380
|
+
other: 10628,
|
|
29381
|
+
open: true
|
|
29382
|
+
}],
|
|
29383
|
+
[10628, {
|
|
29384
|
+
other: 10627,
|
|
29385
|
+
open: false
|
|
29386
|
+
}],
|
|
29387
|
+
[12296, {
|
|
29388
|
+
other: 12297,
|
|
29389
|
+
open: true
|
|
29390
|
+
}],
|
|
29391
|
+
[12297, {
|
|
29392
|
+
other: 12296,
|
|
29393
|
+
open: false
|
|
29394
|
+
}],
|
|
29395
|
+
[12298, {
|
|
29396
|
+
other: 12299,
|
|
29397
|
+
open: true
|
|
29398
|
+
}],
|
|
29399
|
+
[12299, {
|
|
29400
|
+
other: 12298,
|
|
29401
|
+
open: false
|
|
29402
|
+
}],
|
|
29403
|
+
[12300, {
|
|
29404
|
+
other: 12301,
|
|
29405
|
+
open: true
|
|
29406
|
+
}],
|
|
29407
|
+
[12301, {
|
|
29408
|
+
other: 12300,
|
|
29409
|
+
open: false
|
|
29410
|
+
}],
|
|
29411
|
+
[12302, {
|
|
29412
|
+
other: 12303,
|
|
29413
|
+
open: true
|
|
29414
|
+
}],
|
|
29415
|
+
[12303, {
|
|
29416
|
+
other: 12302,
|
|
29417
|
+
open: false
|
|
29418
|
+
}],
|
|
29419
|
+
[12304, {
|
|
29420
|
+
other: 12305,
|
|
29421
|
+
open: true
|
|
29422
|
+
}],
|
|
29423
|
+
[12305, {
|
|
29424
|
+
other: 12304,
|
|
29425
|
+
open: false
|
|
29426
|
+
}],
|
|
29427
|
+
[12308, {
|
|
29428
|
+
other: 12309,
|
|
29429
|
+
open: true
|
|
29430
|
+
}],
|
|
29431
|
+
[12309, {
|
|
29432
|
+
other: 12308,
|
|
29433
|
+
open: false
|
|
29434
|
+
}],
|
|
29435
|
+
[12310, {
|
|
29436
|
+
other: 12311,
|
|
29437
|
+
open: true
|
|
29438
|
+
}],
|
|
29439
|
+
[12311, {
|
|
29440
|
+
other: 12310,
|
|
29441
|
+
open: false
|
|
29442
|
+
}],
|
|
29443
|
+
[12312, {
|
|
29444
|
+
other: 12313,
|
|
29445
|
+
open: true
|
|
29446
|
+
}],
|
|
29447
|
+
[12313, {
|
|
29448
|
+
other: 12312,
|
|
29449
|
+
open: false
|
|
29450
|
+
}],
|
|
29451
|
+
[12314, {
|
|
29452
|
+
other: 12315,
|
|
29453
|
+
open: true
|
|
29454
|
+
}],
|
|
29455
|
+
[12315, {
|
|
29456
|
+
other: 12314,
|
|
29457
|
+
open: false
|
|
29458
|
+
}]
|
|
29459
|
+
]);
|
|
29460
|
+
/** BD16 canonical-equivalence folding for bracket matching. */
|
|
29461
|
+
function canonicalBracket(cp) {
|
|
29462
|
+
if (cp === 12296) return 9001;
|
|
29463
|
+
if (cp === 12297) return 9002;
|
|
29464
|
+
return cp;
|
|
29465
|
+
}
|
|
29466
|
+
/** True if `bc` is a removed-by-X9 explicit/BN type. */
|
|
29467
|
+
function isRemovedByX9(bc) {
|
|
29468
|
+
return bc === "RLE" || bc === "LRE" || bc === "RLO" || bc === "LRO" || bc === "PDF" || bc === "BN";
|
|
29469
|
+
}
|
|
29470
|
+
/** Strong direction (0 = L, 1 = R) implied by an embedding level's parity. */
|
|
29471
|
+
function dirFromLevel(level) {
|
|
29472
|
+
return level % 2 === 0 ? "L" : "R";
|
|
29473
|
+
}
|
|
29474
|
+
/**
|
|
29475
|
+
* Compute the first-strong base level over `types`, skipping the contents of
|
|
29476
|
+
* isolate initiators (LRI/RLI/FSI ... matching PDI) per UAX #9 P2/P3, X5c.
|
|
29477
|
+
* Returns 0 (LTR) when no strong character is found.
|
|
29478
|
+
*/
|
|
29479
|
+
function computeBaseLevel(types) {
|
|
29480
|
+
let isolateDepth = 0;
|
|
29481
|
+
for (const t of types) if (t === "LRI" || t === "RLI" || t === "FSI") isolateDepth++;
|
|
29482
|
+
else if (t === "PDI") {
|
|
29483
|
+
if (isolateDepth > 0) isolateDepth--;
|
|
29484
|
+
} else if (isolateDepth === 0) {
|
|
29485
|
+
if (t === "L") return 0;
|
|
29486
|
+
if (t === "R" || t === "AL") return 1;
|
|
29487
|
+
}
|
|
29488
|
+
return 0;
|
|
29489
|
+
}
|
|
29490
|
+
/**
|
|
29491
|
+
* Resolve explicit embedding levels and per-character override directions
|
|
29492
|
+
* (UAX #9 X1–X8 plus the isolate rules X5a/X5b/X5c/X6a). Mutates `levels` and
|
|
29493
|
+
* returns an array of resolved working types (with overrides applied and X9
|
|
29494
|
+
* removals tagged as BN).
|
|
29495
|
+
*/
|
|
29496
|
+
function resolveExplicit(types, baseLevel, levels) {
|
|
29497
|
+
const result = types.slice();
|
|
29498
|
+
const stack = [{
|
|
29499
|
+
level: baseLevel,
|
|
29500
|
+
override: "neutral",
|
|
29501
|
+
isolate: false
|
|
29502
|
+
}];
|
|
29503
|
+
let overflowIsolate = 0;
|
|
29504
|
+
let overflowEmbedding = 0;
|
|
29505
|
+
let validIsolate = 0;
|
|
29506
|
+
const n = types.length;
|
|
29507
|
+
const matchingPDI = computeMatchingPDI(types);
|
|
29508
|
+
for (let i = 0; i < n; i++) {
|
|
29509
|
+
const t = types[i];
|
|
29510
|
+
const top = stack[stack.length - 1];
|
|
29511
|
+
switch (t) {
|
|
29512
|
+
case "RLE":
|
|
29513
|
+
case "LRE":
|
|
29514
|
+
case "RLO":
|
|
29515
|
+
case "LRO": {
|
|
29516
|
+
levels[i] = top.level;
|
|
29517
|
+
result[i] = "BN";
|
|
29518
|
+
const newLevel = t === "RLE" || t === "RLO" ? nextOddLevel(top.level) : nextEvenLevel(top.level);
|
|
29519
|
+
if (newLevel <= MAX_DEPTH && overflowIsolate === 0 && overflowEmbedding === 0) stack.push({
|
|
29520
|
+
level: newLevel,
|
|
29521
|
+
override: t === "RLO" ? "R" : t === "LRO" ? "L" : "neutral",
|
|
29522
|
+
isolate: false
|
|
29523
|
+
});
|
|
29524
|
+
else if (overflowIsolate === 0) overflowEmbedding++;
|
|
29525
|
+
break;
|
|
29526
|
+
}
|
|
29527
|
+
case "RLI":
|
|
29528
|
+
case "LRI":
|
|
29529
|
+
case "FSI": {
|
|
29530
|
+
levels[i] = top.level;
|
|
29531
|
+
if (top.override !== "neutral") result[i] = top.override;
|
|
29532
|
+
let isRTL = t === "RLI";
|
|
29533
|
+
if (t === "FSI") {
|
|
29534
|
+
const pdi = matchingPDI[i];
|
|
29535
|
+
isRTL = computeBaseLevel(types.slice(i + 1, pdi)) === 1;
|
|
29536
|
+
}
|
|
29537
|
+
const newLevel = isRTL ? nextOddLevel(top.level) : nextEvenLevel(top.level);
|
|
29538
|
+
if (newLevel <= MAX_DEPTH && overflowIsolate === 0 && overflowEmbedding === 0) {
|
|
29539
|
+
validIsolate++;
|
|
29540
|
+
stack.push({
|
|
29541
|
+
level: newLevel,
|
|
29542
|
+
override: "neutral",
|
|
29543
|
+
isolate: true
|
|
29544
|
+
});
|
|
29545
|
+
} else overflowIsolate++;
|
|
29546
|
+
break;
|
|
29547
|
+
}
|
|
29548
|
+
case "PDI": {
|
|
29549
|
+
if (overflowIsolate > 0) overflowIsolate--;
|
|
29550
|
+
else if (validIsolate > 0) {
|
|
29551
|
+
overflowEmbedding = 0;
|
|
29552
|
+
while (!stack[stack.length - 1].isolate) stack.pop();
|
|
29553
|
+
stack.pop();
|
|
29554
|
+
validIsolate--;
|
|
29555
|
+
}
|
|
29556
|
+
const after = stack[stack.length - 1];
|
|
29557
|
+
levels[i] = after.level;
|
|
29558
|
+
if (after.override !== "neutral") result[i] = after.override;
|
|
29559
|
+
break;
|
|
29560
|
+
}
|
|
29561
|
+
case "PDF":
|
|
29562
|
+
levels[i] = top.level;
|
|
29563
|
+
result[i] = "BN";
|
|
29564
|
+
if (overflowIsolate > 0) {} else if (overflowEmbedding > 0) overflowEmbedding--;
|
|
29565
|
+
else if (!top.isolate && stack.length >= 2) stack.pop();
|
|
29566
|
+
break;
|
|
29567
|
+
case "B":
|
|
29568
|
+
stack.length = 1;
|
|
29569
|
+
stack[0] = {
|
|
29570
|
+
level: baseLevel,
|
|
29571
|
+
override: "neutral",
|
|
29572
|
+
isolate: false
|
|
29573
|
+
};
|
|
29574
|
+
overflowIsolate = 0;
|
|
29575
|
+
overflowEmbedding = 0;
|
|
29576
|
+
validIsolate = 0;
|
|
29577
|
+
levels[i] = baseLevel;
|
|
29578
|
+
break;
|
|
29579
|
+
case "BN":
|
|
29580
|
+
levels[i] = top.level;
|
|
29581
|
+
break;
|
|
29582
|
+
default:
|
|
29583
|
+
levels[i] = top.level;
|
|
29584
|
+
if (top.override !== "neutral") result[i] = top.override;
|
|
29585
|
+
break;
|
|
29586
|
+
}
|
|
29587
|
+
}
|
|
29588
|
+
return result;
|
|
29589
|
+
}
|
|
29590
|
+
/** Next odd level strictly greater than `level` (for RTL embeddings). */
|
|
29591
|
+
function nextOddLevel(level) {
|
|
29592
|
+
return level % 2 === 0 ? level + 1 : level + 2;
|
|
29593
|
+
}
|
|
29594
|
+
/** Next even level strictly greater than `level` (for LTR embeddings). */
|
|
29595
|
+
function nextEvenLevel(level) {
|
|
29596
|
+
return level % 2 === 0 ? level + 2 : level + 1;
|
|
29597
|
+
}
|
|
29598
|
+
/**
|
|
29599
|
+
* BD9: for each isolate initiator, find the index of its matching PDI (or `n`
|
|
29600
|
+
* if unmatched). Non-initiator positions map to `-1`.
|
|
29601
|
+
*/
|
|
29602
|
+
function computeMatchingPDI(types) {
|
|
29603
|
+
const n = types.length;
|
|
29604
|
+
const out = new Array(n).fill(-1);
|
|
29605
|
+
for (let i = 0; i < n; i++) {
|
|
29606
|
+
const t = types[i];
|
|
29607
|
+
if (t === "LRI" || t === "RLI" || t === "FSI") {
|
|
29608
|
+
let depth = 1;
|
|
29609
|
+
let j = i + 1;
|
|
29610
|
+
for (; j < n; j++) {
|
|
29611
|
+
const tj = types[j];
|
|
29612
|
+
if (tj === "LRI" || tj === "RLI" || tj === "FSI") depth++;
|
|
29613
|
+
else if (tj === "PDI") {
|
|
29614
|
+
depth--;
|
|
29615
|
+
if (depth === 0) break;
|
|
29616
|
+
}
|
|
29617
|
+
}
|
|
29618
|
+
out[i] = j;
|
|
29619
|
+
}
|
|
29620
|
+
}
|
|
29621
|
+
return out;
|
|
29622
|
+
}
|
|
29623
|
+
/**
|
|
29624
|
+
* Build the isolating run sequences (UAX #9 X10 / BD13) from the resolved
|
|
29625
|
+
* levels, skipping characters removed by X9. Computes each sequence's sos/eos
|
|
29626
|
+
* boundary directions from the higher of the adjacent levels (rule X10).
|
|
29627
|
+
*/
|
|
29628
|
+
function buildIsolatingRunSequences(types, levels, baseLevel) {
|
|
29629
|
+
const n = types.length;
|
|
29630
|
+
const retained = [];
|
|
29631
|
+
for (let i = 0; i < n; i++) if (!isRemovedByX9(types[i])) retained.push(i);
|
|
29632
|
+
const levelRuns = [];
|
|
29633
|
+
let cur = [];
|
|
29634
|
+
let curLevel = -1;
|
|
29635
|
+
for (const i of retained) {
|
|
29636
|
+
if (levels[i] !== curLevel) {
|
|
29637
|
+
if (cur.length > 0) levelRuns.push(cur);
|
|
29638
|
+
cur = [];
|
|
29639
|
+
curLevel = levels[i];
|
|
29640
|
+
}
|
|
29641
|
+
cur.push(i);
|
|
29642
|
+
}
|
|
29643
|
+
if (cur.length > 0) levelRuns.push(cur);
|
|
29644
|
+
const runByStart = /* @__PURE__ */ new Map();
|
|
29645
|
+
for (let r = 0; r < levelRuns.length; r++) runByStart.set(levelRuns[r][0], r);
|
|
29646
|
+
const matchingPDI = computeMatchingPDI(types);
|
|
29647
|
+
const matchingInitiator = new Array(n).fill(-1);
|
|
29648
|
+
for (let i = 0; i < n; i++) {
|
|
29649
|
+
const t = types[i];
|
|
29650
|
+
if ((t === "LRI" || t === "RLI" || t === "FSI") && matchingPDI[i] < n) matchingInitiator[matchingPDI[i]] = i;
|
|
29651
|
+
}
|
|
29652
|
+
const used = new Array(levelRuns.length).fill(false);
|
|
29653
|
+
const sequences = [];
|
|
29654
|
+
for (let r = 0; r < levelRuns.length; r++) {
|
|
29655
|
+
if (used[r]) continue;
|
|
29656
|
+
const first = levelRuns[r][0];
|
|
29657
|
+
if (types[first] === "PDI" && matchingInitiator[first] >= 0) continue;
|
|
29658
|
+
const seqIndices = [];
|
|
29659
|
+
let runIdx = r;
|
|
29660
|
+
for (;;) {
|
|
29661
|
+
used[runIdx] = true;
|
|
29662
|
+
const run = levelRuns[runIdx];
|
|
29663
|
+
for (const idx of run) seqIndices.push(idx);
|
|
29664
|
+
const last = run[run.length - 1];
|
|
29665
|
+
const t = types[last];
|
|
29666
|
+
if ((t === "LRI" || t === "RLI" || t === "FSI") && matchingPDI[last] < n) {
|
|
29667
|
+
const next = runByStart.get(matchingPDI[last]);
|
|
29668
|
+
if (next === void 0 || used[next]) break;
|
|
29669
|
+
runIdx = next;
|
|
29670
|
+
} else break;
|
|
29671
|
+
}
|
|
29672
|
+
const seqLevel = levels[seqIndices[0]];
|
|
29673
|
+
const startPos = retained.indexOf(seqIndices[0]);
|
|
29674
|
+
const endPos = retained.indexOf(seqIndices[seqIndices.length - 1]);
|
|
29675
|
+
const prevLevel = startPos > 0 ? levels[retained[startPos - 1]] : baseLevel;
|
|
29676
|
+
const lastType = types[seqIndices[seqIndices.length - 1]];
|
|
29677
|
+
const nextLevel = (lastType === "LRI" || lastType === "RLI" || lastType === "FSI") && matchingPDI[seqIndices[seqIndices.length - 1]] >= n || endPos === retained.length - 1 ? baseLevel : levels[retained[endPos + 1]];
|
|
29678
|
+
sequences.push({
|
|
29679
|
+
indices: seqIndices,
|
|
29680
|
+
sos: dirFromLevel(Math.max(seqLevel, prevLevel)),
|
|
29681
|
+
eos: dirFromLevel(Math.max(seqLevel, nextLevel))
|
|
29682
|
+
});
|
|
29683
|
+
}
|
|
29684
|
+
return sequences;
|
|
29685
|
+
}
|
|
29686
|
+
/** Apply the weak-type rules W1–W7 to one isolating run sequence in place. */
|
|
29687
|
+
function resolveWeak(seq, types) {
|
|
29688
|
+
const idx = seq.indices;
|
|
29689
|
+
const m = idx.length;
|
|
29690
|
+
for (let k = 0; k < m; k++) {
|
|
29691
|
+
const i = idx[k];
|
|
29692
|
+
if (types[i] === "NSM") if (k === 0) types[i] = seq.sos;
|
|
29693
|
+
else {
|
|
29694
|
+
const prev = types[idx[k - 1]];
|
|
29695
|
+
types[i] = prev === "LRI" || prev === "RLI" || prev === "FSI" || prev === "PDI" ? "ON" : prev;
|
|
29696
|
+
}
|
|
29697
|
+
}
|
|
29698
|
+
for (let k = 0; k < m; k++) {
|
|
29699
|
+
const i = idx[k];
|
|
29700
|
+
if (types[i] === "EN") {
|
|
29701
|
+
let strong = seq.sos;
|
|
29702
|
+
for (let j = k - 1; j >= 0; j--) {
|
|
29703
|
+
const tj = types[idx[j]];
|
|
29704
|
+
if (tj === "R" || tj === "L" || tj === "AL") {
|
|
29705
|
+
strong = tj;
|
|
29706
|
+
break;
|
|
29707
|
+
}
|
|
29708
|
+
}
|
|
29709
|
+
if (strong === "AL") types[i] = "AN";
|
|
29710
|
+
}
|
|
29711
|
+
}
|
|
29712
|
+
for (let k = 0; k < m; k++) {
|
|
29713
|
+
const i = idx[k];
|
|
29714
|
+
if (types[i] === "AL") types[i] = "R";
|
|
29715
|
+
}
|
|
29716
|
+
for (let k = 1; k < m - 1; k++) {
|
|
29717
|
+
const i = idx[k];
|
|
29718
|
+
const prev = types[idx[k - 1]];
|
|
29719
|
+
const next = types[idx[k + 1]];
|
|
29720
|
+
if (types[i] === "ES" && prev === "EN" && next === "EN") types[i] = "EN";
|
|
29721
|
+
else if (types[i] === "CS" && prev === next && (prev === "EN" || prev === "AN")) types[i] = prev;
|
|
29722
|
+
}
|
|
29723
|
+
for (let k = 0; k < m; k++) {
|
|
29724
|
+
if (types[idx[k]] !== "ET") continue;
|
|
29725
|
+
let end = k;
|
|
29726
|
+
while (end < m && types[idx[end]] === "ET") end++;
|
|
29727
|
+
const before = k > 0 ? types[idx[k - 1]] : seq.sos;
|
|
29728
|
+
const after = end < m ? types[idx[end]] : seq.eos;
|
|
29729
|
+
if (before === "EN" || after === "EN") for (let j = k; j < end; j++) types[idx[j]] = "EN";
|
|
29730
|
+
k = end - 1;
|
|
29731
|
+
}
|
|
29732
|
+
for (let k = 0; k < m; k++) {
|
|
29733
|
+
const i = idx[k];
|
|
29734
|
+
if (types[i] === "ES" || types[i] === "ET" || types[i] === "CS") types[i] = "ON";
|
|
29735
|
+
}
|
|
29736
|
+
for (let k = 0; k < m; k++) {
|
|
29737
|
+
const i = idx[k];
|
|
29738
|
+
if (types[i] === "EN") {
|
|
29739
|
+
let strong = seq.sos;
|
|
29740
|
+
for (let j = k - 1; j >= 0; j--) {
|
|
29741
|
+
const tj = types[idx[j]];
|
|
29742
|
+
if (tj === "R" || tj === "L") {
|
|
29743
|
+
strong = tj;
|
|
29744
|
+
break;
|
|
29745
|
+
}
|
|
29746
|
+
}
|
|
29747
|
+
if (strong === "L") types[i] = "L";
|
|
29748
|
+
}
|
|
29749
|
+
}
|
|
29750
|
+
}
|
|
29751
|
+
/** True if a working type counts as a "neutral or isolate formatting" char (NI). */
|
|
29752
|
+
function isNI(t) {
|
|
29753
|
+
return t === "B" || t === "S" || t === "WS" || t === "ON" || t === "FSI" || t === "LRI" || t === "RLI" || t === "PDI";
|
|
29754
|
+
}
|
|
29755
|
+
/** Apply N0 (paired brackets) then N1/N2 to one isolating run sequence. */
|
|
29756
|
+
function resolveNeutral(seq, types, levels, codepoints) {
|
|
29757
|
+
const idx = seq.indices;
|
|
29758
|
+
const m = idx.length;
|
|
29759
|
+
const e = dirFromLevel(levels[idx[0]]);
|
|
29760
|
+
resolveBrackets(seq, types, codepoints, e);
|
|
29761
|
+
let k = 0;
|
|
29762
|
+
while (k < m) {
|
|
29763
|
+
if (!isNI(types[idx[k]])) {
|
|
29764
|
+
k++;
|
|
29765
|
+
continue;
|
|
29766
|
+
}
|
|
29767
|
+
let end = k;
|
|
29768
|
+
while (end < m && isNI(types[idx[end]])) end++;
|
|
29769
|
+
const beforeRaw = k > 0 ? types[idx[k - 1]] : seq.sos;
|
|
29770
|
+
const afterRaw = end < m ? types[idx[end]] : seq.eos;
|
|
29771
|
+
const before = beforeRaw === "EN" || beforeRaw === "AN" ? "R" : beforeRaw;
|
|
29772
|
+
const resolved = before === (afterRaw === "EN" || afterRaw === "AN" ? "R" : afterRaw) && (before === "L" || before === "R") ? before : e;
|
|
29773
|
+
for (let j = k; j < end; j++) types[idx[j]] = resolved;
|
|
29774
|
+
k = end;
|
|
29775
|
+
}
|
|
29776
|
+
}
|
|
29777
|
+
/** N0 paired-bracket resolution (UAX #9 N0 with BD16 pairing). */
|
|
29778
|
+
function resolveBrackets(seq, types, codepoints, e) {
|
|
29779
|
+
const idx = seq.indices;
|
|
29780
|
+
const m = idx.length;
|
|
29781
|
+
const stack = [];
|
|
29782
|
+
const pairs = [];
|
|
29783
|
+
for (let k = 0; k < m; k++) {
|
|
29784
|
+
const i = idx[k];
|
|
29785
|
+
if (types[i] !== "ON") continue;
|
|
29786
|
+
const cp = canonicalBracket(codepoints[i]);
|
|
29787
|
+
const info = BRACKET_PAIRS.get(codepoints[i]);
|
|
29788
|
+
if (!info) continue;
|
|
29789
|
+
if (info.open) {
|
|
29790
|
+
if (stack.length >= 63) break;
|
|
29791
|
+
stack.push({
|
|
29792
|
+
bracket: canonicalBracket(info.other),
|
|
29793
|
+
seqPos: k
|
|
29794
|
+
});
|
|
29795
|
+
} else for (let s = stack.length - 1; s >= 0; s--) if (stack[s].bracket === cp) {
|
|
29796
|
+
pairs.push({
|
|
29797
|
+
open: stack[s].seqPos,
|
|
29798
|
+
close: k
|
|
29799
|
+
});
|
|
29800
|
+
stack.length = s;
|
|
29801
|
+
break;
|
|
29802
|
+
}
|
|
29803
|
+
}
|
|
29804
|
+
pairs.sort((a, b) => a.open - b.open);
|
|
29805
|
+
for (const pair of pairs) {
|
|
29806
|
+
let foundE = false;
|
|
29807
|
+
let foundOpposite = false;
|
|
29808
|
+
const opposite = e === "L" ? "R" : "L";
|
|
29809
|
+
for (let k = pair.open + 1; k < pair.close; k++) {
|
|
29810
|
+
const t = strongClass(types[idx[k]]);
|
|
29811
|
+
if (t === e) {
|
|
29812
|
+
foundE = true;
|
|
29813
|
+
break;
|
|
29814
|
+
}
|
|
29815
|
+
if (t === opposite) foundOpposite = true;
|
|
29816
|
+
}
|
|
29817
|
+
let setDir = null;
|
|
29818
|
+
if (foundE) setDir = e;
|
|
29819
|
+
else if (foundOpposite) {
|
|
29820
|
+
let context = seq.sos;
|
|
29821
|
+
for (let k = pair.open - 1; k >= 0; k--) {
|
|
29822
|
+
const t = strongClass(types[idx[k]]);
|
|
29823
|
+
if (t === "L" || t === "R") {
|
|
29824
|
+
context = t;
|
|
29825
|
+
break;
|
|
29826
|
+
}
|
|
29827
|
+
}
|
|
29828
|
+
setDir = context === opposite ? opposite : e;
|
|
29829
|
+
}
|
|
29830
|
+
if (setDir) {
|
|
29831
|
+
types[idx[pair.open]] = setDir;
|
|
29832
|
+
types[idx[pair.close]] = setDir;
|
|
29833
|
+
for (let k = pair.open + 1; k < m; k++) if (originalIsNSM(idx[k], codepoints)) types[idx[k]] = setDir;
|
|
29834
|
+
else break;
|
|
29835
|
+
for (let k = pair.close + 1; k < m; k++) if (originalIsNSM(idx[k], codepoints)) types[idx[k]] = setDir;
|
|
29836
|
+
else break;
|
|
29837
|
+
}
|
|
29838
|
+
}
|
|
29839
|
+
}
|
|
29840
|
+
/** Reduce a working type to the strong direction it counts as for N0 (EN/AN→R). */
|
|
29841
|
+
function strongClass(t) {
|
|
29842
|
+
if (t === "L") return "L";
|
|
29843
|
+
if (t === "R" || t === "EN" || t === "AN") return "R";
|
|
29844
|
+
return null;
|
|
29845
|
+
}
|
|
29846
|
+
/** True if the original code point at logical index `i` has Bidi_Class NSM. */
|
|
29847
|
+
function originalIsNSM(i, codepoints) {
|
|
29848
|
+
return bidiClass(codepoints[i]) === "NSM";
|
|
29849
|
+
}
|
|
29850
|
+
/** Resolve implicit embedding levels (UAX #9 I1/I2) for one sequence. */
|
|
29851
|
+
function resolveImplicit(seq, types, levels) {
|
|
29852
|
+
for (const i of seq.indices) {
|
|
29853
|
+
const level = levels[i];
|
|
29854
|
+
const t = types[i];
|
|
29855
|
+
if (level % 2 === 0) {
|
|
29856
|
+
if (t === "R") levels[i] = level + 1;
|
|
29857
|
+
else if (t === "AN" || t === "EN") levels[i] = level + 2;
|
|
29858
|
+
} else if (t === "L" || t === "EN" || t === "AN") levels[i] = level + 1;
|
|
29859
|
+
}
|
|
29860
|
+
}
|
|
29861
|
+
/**
|
|
29862
|
+
* L1: reset to the paragraph level (1) segment/paragraph separators, and
|
|
29863
|
+
* (2) any sequence of whitespace / isolate formatting chars preceding a
|
|
29864
|
+
* separator or the end of the line. Operates on a single line (= the whole
|
|
29865
|
+
* paragraph here, since we do not perform line breaking).
|
|
29866
|
+
*/
|
|
29867
|
+
function resetWhitespaceLevels(originalTypes, levels, baseLevel) {
|
|
29868
|
+
const n = levels.length;
|
|
29869
|
+
let resetFrom = n;
|
|
29870
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
29871
|
+
const t = originalTypes[i];
|
|
29872
|
+
if (t === "B" || t === "S") {
|
|
29873
|
+
levels[i] = baseLevel;
|
|
29874
|
+
for (let j = i + 1; j < resetFrom; j++) levels[j] = baseLevel;
|
|
29875
|
+
resetFrom = i;
|
|
29876
|
+
} else if (t === "WS" || t === "FSI" || t === "LRI" || t === "RLI" || t === "PDI" || isRemovedByX9(t)) {} else {
|
|
29877
|
+
if (resetFrom === n) {}
|
|
29878
|
+
resetFrom = i + 1 > resetFrom ? resetFrom : i + 1;
|
|
29879
|
+
resetFrom = i + 1;
|
|
29880
|
+
}
|
|
29881
|
+
}
|
|
29882
|
+
for (let i = n - 1; i >= 0; i--) {
|
|
29883
|
+
const t = originalTypes[i];
|
|
29884
|
+
if (t === "WS" || t === "FSI" || t === "LRI" || t === "RLI" || t === "PDI" || isRemovedByX9(t)) levels[i] = baseLevel;
|
|
29885
|
+
else break;
|
|
29886
|
+
}
|
|
29887
|
+
}
|
|
29888
|
+
/** L2: reorder code-unit indices into visual order from the resolved levels. */
|
|
29889
|
+
function reorderLevels(levels) {
|
|
29890
|
+
const n = levels.length;
|
|
29891
|
+
const order = Array.from({ length: n }, (_, i) => i);
|
|
29892
|
+
if (n === 0) return order;
|
|
29893
|
+
let highest = 0;
|
|
29894
|
+
let lowestOdd = 127;
|
|
29895
|
+
for (const l of levels) {
|
|
29896
|
+
if (l > highest) highest = l;
|
|
29897
|
+
if (l % 2 === 1 && l < lowestOdd) lowestOdd = l;
|
|
29898
|
+
}
|
|
29899
|
+
for (let level = highest; level >= lowestOdd; level--) {
|
|
29900
|
+
let i = 0;
|
|
29901
|
+
while (i < n) if (levels[order[i]] >= level) {
|
|
29902
|
+
let j = i;
|
|
29903
|
+
while (j < n && levels[order[j]] >= level) j++;
|
|
29904
|
+
for (let a = i, b = j - 1; a < b; a++, b--) {
|
|
29905
|
+
const tmp = order[a];
|
|
29906
|
+
order[a] = order[b];
|
|
29907
|
+
order[b] = tmp;
|
|
29908
|
+
}
|
|
29909
|
+
i = j;
|
|
29910
|
+
} else i++;
|
|
29911
|
+
}
|
|
29912
|
+
return order;
|
|
29913
|
+
}
|
|
29914
|
+
/**
|
|
29915
|
+
* Run the Unicode Bidirectional Algorithm (UAX #9) over `text`.
|
|
29916
|
+
*
|
|
29917
|
+
* Indices in the result refer to UTF-16 code units of the input string (the
|
|
29918
|
+
* same units JavaScript's `string[i]` and `.length` use). Astral characters
|
|
29919
|
+
* (surrogate pairs) are classified by their scalar value but occupy two code
|
|
29920
|
+
* units, both assigned the same level.
|
|
29921
|
+
*
|
|
29922
|
+
* @param text The logical-order input string.
|
|
29923
|
+
* @param base Paragraph direction: `'ltr'` / `'rtl'` force the base level;
|
|
29924
|
+
* `'auto'` (the default) derives it from the first strong character (P2/P3).
|
|
29925
|
+
* @returns The resolved levels, same-level runs, visual order and base level.
|
|
29926
|
+
*/
|
|
29927
|
+
function resolveBidi(text, base = "auto") {
|
|
29928
|
+
const n = text.length;
|
|
29929
|
+
const codepoints = new Array(n);
|
|
29930
|
+
for (let i = 0; i < n; i++) {
|
|
29931
|
+
const c = text.charCodeAt(i);
|
|
29932
|
+
if (c >= 55296 && c <= 56319 && i + 1 < n) {
|
|
29933
|
+
const c2 = text.charCodeAt(i + 1);
|
|
29934
|
+
if (c2 >= 56320 && c2 <= 57343) {
|
|
29935
|
+
const scalar = (c - 55296) * 1024 + (c2 - 56320) + 65536;
|
|
29936
|
+
codepoints[i] = scalar;
|
|
29937
|
+
codepoints[i + 1] = scalar;
|
|
29938
|
+
i++;
|
|
29939
|
+
continue;
|
|
29940
|
+
}
|
|
29941
|
+
}
|
|
29942
|
+
codepoints[i] = c;
|
|
29943
|
+
}
|
|
29944
|
+
const originalTypes = codepoints.map(bidiClass);
|
|
29945
|
+
let baseLevel;
|
|
29946
|
+
if (base === "ltr") baseLevel = 0;
|
|
29947
|
+
else if (base === "rtl") baseLevel = 1;
|
|
29948
|
+
else baseLevel = computeBaseLevel(originalTypes);
|
|
29949
|
+
const levels = new Array(n).fill(baseLevel);
|
|
29950
|
+
if (n === 0) return {
|
|
29951
|
+
runs: [],
|
|
29952
|
+
levels: [],
|
|
29953
|
+
visualOrder: [],
|
|
29954
|
+
baseLevel
|
|
29955
|
+
};
|
|
29956
|
+
const workingTypes = resolveExplicit(originalTypes, baseLevel, levels);
|
|
29957
|
+
const sequences = buildIsolatingRunSequences(workingTypes, levels, baseLevel);
|
|
29958
|
+
for (const seq of sequences) {
|
|
29959
|
+
resolveWeak(seq, workingTypes);
|
|
29960
|
+
resolveNeutral(seq, workingTypes, levels, codepoints);
|
|
29961
|
+
resolveImplicit(seq, workingTypes, levels);
|
|
29962
|
+
}
|
|
29963
|
+
resetWhitespaceLevels(originalTypes, levels, baseLevel);
|
|
29964
|
+
const visualOrder = reorderLevels(levels);
|
|
29965
|
+
const runs = [];
|
|
29966
|
+
let start = 0;
|
|
29967
|
+
while (start < n) {
|
|
29968
|
+
const level = levels[start];
|
|
29969
|
+
let end = start + 1;
|
|
29970
|
+
while (end < n && levels[end] === level) end++;
|
|
29971
|
+
runs.push({
|
|
29972
|
+
text: text.slice(start, end),
|
|
29973
|
+
level,
|
|
29974
|
+
direction: dirFromLevel(level) === "L" ? "ltr" : "rtl",
|
|
29975
|
+
start,
|
|
29976
|
+
length: end - start
|
|
29977
|
+
});
|
|
29978
|
+
start = end;
|
|
29979
|
+
}
|
|
29980
|
+
return {
|
|
29981
|
+
runs,
|
|
29982
|
+
levels,
|
|
29983
|
+
visualOrder,
|
|
29984
|
+
baseLevel
|
|
29985
|
+
};
|
|
29986
|
+
}
|
|
29987
|
+
/**
|
|
29988
|
+
* Convenience wrapper that returns `text` reordered into visual (left-to-right)
|
|
29989
|
+
* order via {@link resolveBidi}'s L2 result.
|
|
29990
|
+
*
|
|
29991
|
+
* Note: this performs pure reordering of code units. It does not apply Arabic
|
|
29992
|
+
* cursive shaping or mirror neutral glyphs; callers that need shaped glyphs
|
|
29993
|
+
* should pass the resolved runs to a shaping engine.
|
|
29994
|
+
*
|
|
29995
|
+
* @param text The logical-order input string.
|
|
29996
|
+
* @param base Paragraph direction (see {@link resolveBidi}).
|
|
29997
|
+
* @returns The visually reordered string.
|
|
29998
|
+
*/
|
|
29999
|
+
function reorderVisual(text, base = "auto") {
|
|
30000
|
+
const { visualOrder } = resolveBidi(text, base);
|
|
30001
|
+
let out = "";
|
|
30002
|
+
for (const i of visualOrder) out += text[i];
|
|
30003
|
+
return out;
|
|
30004
|
+
}
|
|
30005
|
+
//#endregion
|
|
30006
|
+
//#region src/assets/font/variableFont.ts
|
|
30007
|
+
function readTableDirectory$1(data) {
|
|
30008
|
+
if (data.length < 12) return null;
|
|
30009
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
30010
|
+
const numTables = view.getUint16(4, false);
|
|
30011
|
+
const dirEnd = 12 + numTables * 16;
|
|
30012
|
+
if (numTables === 0 || dirEnd > data.length) return null;
|
|
30013
|
+
const tables = /* @__PURE__ */ new Map();
|
|
30014
|
+
for (let i = 0; i < numTables; i++) {
|
|
30015
|
+
const recOff = 12 + i * 16;
|
|
30016
|
+
const tag = String.fromCharCode(data[recOff], data[recOff + 1], data[recOff + 2], data[recOff + 3]);
|
|
30017
|
+
const offset = view.getUint32(recOff + 8, false);
|
|
30018
|
+
const length = view.getUint32(recOff + 12, false);
|
|
30019
|
+
tables.set(tag, {
|
|
30020
|
+
offset,
|
|
30021
|
+
length
|
|
30022
|
+
});
|
|
30023
|
+
}
|
|
30024
|
+
return tables;
|
|
30025
|
+
}
|
|
30026
|
+
/**
|
|
30027
|
+
* Read a Fixed (16.16 signed) value: a big-endian int32 divided by 65536.
|
|
30028
|
+
* Used by 'fvar' for axis min/default/max and instance coordinates.
|
|
30029
|
+
*/
|
|
30030
|
+
function readFixed(view, off) {
|
|
30031
|
+
return view.getInt32(off, false) / 65536;
|
|
30032
|
+
}
|
|
30033
|
+
/**
|
|
30034
|
+
* Read an F2DOT14 (2.14 signed) value: a big-endian int16 divided by 16384.
|
|
30035
|
+
* Used by 'avar' for AxisValueMap from/to coordinates.
|
|
30036
|
+
*/
|
|
30037
|
+
function readF2Dot14(view, off) {
|
|
30038
|
+
return view.getInt16(off, false) / 16384;
|
|
30039
|
+
}
|
|
30040
|
+
function parseAvar(data, rec, expectedAxisCount) {
|
|
30041
|
+
if (rec.offset + 8 > data.length) return void 0;
|
|
30042
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
30043
|
+
if (view.getUint16(rec.offset, false) !== 1) return void 0;
|
|
30044
|
+
const axisCount = view.getUint16(rec.offset + 6, false);
|
|
30045
|
+
if (axisCount !== expectedAxisCount) return void 0;
|
|
30046
|
+
const segmentMaps = [];
|
|
30047
|
+
let cursor = rec.offset + 8;
|
|
30048
|
+
const tableEnd = rec.offset + rec.length;
|
|
30049
|
+
for (let a = 0; a < axisCount; a++) {
|
|
30050
|
+
if (cursor + 2 > data.length || cursor + 2 > tableEnd) return void 0;
|
|
30051
|
+
const positionMapCount = view.getUint16(cursor, false);
|
|
30052
|
+
cursor += 2;
|
|
30053
|
+
const recordsBytes = positionMapCount * 4;
|
|
30054
|
+
if (cursor + recordsBytes > data.length || cursor + recordsBytes > tableEnd) return;
|
|
30055
|
+
const maps = [];
|
|
30056
|
+
for (let i = 0; i < positionMapCount; i++) {
|
|
30057
|
+
const from = readF2Dot14(view, cursor);
|
|
30058
|
+
const to = readF2Dot14(view, cursor + 2);
|
|
30059
|
+
maps.push({
|
|
30060
|
+
fromCoordinate: from,
|
|
30061
|
+
toCoordinate: to
|
|
30062
|
+
});
|
|
30063
|
+
cursor += 4;
|
|
30064
|
+
}
|
|
30065
|
+
segmentMaps.push(maps);
|
|
30066
|
+
}
|
|
30067
|
+
return segmentMaps;
|
|
30068
|
+
}
|
|
30069
|
+
/**
|
|
30070
|
+
* Parse the variable-font model from raw OpenType/TrueType font bytes.
|
|
30071
|
+
*
|
|
30072
|
+
* If the font has no 'fvar' table (or it is malformed / has zero axes), the
|
|
30073
|
+
* font is treated as non-variable and
|
|
30074
|
+
* `{ isVariable: false, axes: [], namedInstances: [] }` is returned.
|
|
30075
|
+
*
|
|
30076
|
+
* @param fontData Raw font file bytes (sfnt: TrueType 0x00010000 or 'OTTO' CFF).
|
|
30077
|
+
* @returns The parsed {@link VariableFontInfo}.
|
|
30078
|
+
*/
|
|
30079
|
+
function parseVariableFont(fontData) {
|
|
30080
|
+
const notVariable = {
|
|
30081
|
+
isVariable: false,
|
|
30082
|
+
axes: [],
|
|
30083
|
+
namedInstances: []
|
|
30084
|
+
};
|
|
30085
|
+
const tables = readTableDirectory$1(fontData);
|
|
30086
|
+
if (!tables) return notVariable;
|
|
30087
|
+
const fvar = tables.get("fvar");
|
|
30088
|
+
if (!fvar) return notVariable;
|
|
30089
|
+
if (fvar.offset + 16 > fontData.length) return notVariable;
|
|
30090
|
+
const view = new DataView(fontData.buffer, fontData.byteOffset, fontData.byteLength);
|
|
30091
|
+
if (view.getUint16(fvar.offset, false) !== 1) return notVariable;
|
|
30092
|
+
const axesArrayOffset = view.getUint16(fvar.offset + 4, false);
|
|
30093
|
+
const axisCount = view.getUint16(fvar.offset + 8, false);
|
|
30094
|
+
const axisSize = view.getUint16(fvar.offset + 10, false);
|
|
30095
|
+
const instanceCount = view.getUint16(fvar.offset + 12, false);
|
|
30096
|
+
const instanceSize = view.getUint16(fvar.offset + 14, false);
|
|
30097
|
+
if (axisCount === 0) return notVariable;
|
|
30098
|
+
if (axisSize < 20) return notVariable;
|
|
30099
|
+
const axes = [];
|
|
30100
|
+
const axesStart = fvar.offset + axesArrayOffset;
|
|
30101
|
+
const axesBytes = axisCount * axisSize;
|
|
30102
|
+
if (axesStart + axesBytes > fontData.length) return notVariable;
|
|
30103
|
+
for (let i = 0; i < axisCount; i++) {
|
|
30104
|
+
const recOff = axesStart + i * axisSize;
|
|
30105
|
+
const tag = String.fromCharCode(view.getUint8(recOff), view.getUint8(recOff + 1), view.getUint8(recOff + 2), view.getUint8(recOff + 3));
|
|
30106
|
+
const minValue = readFixed(view, recOff + 4);
|
|
30107
|
+
const defaultValue = readFixed(view, recOff + 8);
|
|
30108
|
+
const maxValue = readFixed(view, recOff + 12);
|
|
30109
|
+
const flags = view.getUint16(recOff + 16, false);
|
|
30110
|
+
axes.push({
|
|
30111
|
+
tag,
|
|
30112
|
+
minValue,
|
|
30113
|
+
defaultValue,
|
|
30114
|
+
maxValue,
|
|
30115
|
+
flags,
|
|
30116
|
+
name: void 0
|
|
30117
|
+
});
|
|
30118
|
+
}
|
|
30119
|
+
const coordsBytes = axisCount * 4;
|
|
30120
|
+
const baseInstanceSize = coordsBytes + 4;
|
|
30121
|
+
const hasPostScriptName = instanceSize >= baseInstanceSize + 2;
|
|
30122
|
+
const namedInstances = [];
|
|
30123
|
+
const instancesStart = axesStart + axesBytes;
|
|
30124
|
+
if (instanceSize >= baseInstanceSize) {
|
|
30125
|
+
if (instancesStart + instanceCount * instanceSize <= fontData.length) for (let i = 0; i < instanceCount; i++) {
|
|
30126
|
+
const recOff = instancesStart + i * instanceSize;
|
|
30127
|
+
const subfamilyNameID = view.getUint16(recOff, false);
|
|
30128
|
+
const coordinates = {};
|
|
30129
|
+
for (let a = 0; a < axisCount; a++) {
|
|
30130
|
+
const value = readFixed(view, recOff + 4 + a * 4);
|
|
30131
|
+
const axis = axes[a];
|
|
30132
|
+
coordinates[axis.tag] = value;
|
|
30133
|
+
}
|
|
30134
|
+
const instance = {
|
|
30135
|
+
nameId: subfamilyNameID,
|
|
30136
|
+
coordinates,
|
|
30137
|
+
name: void 0
|
|
30138
|
+
};
|
|
30139
|
+
if (hasPostScriptName) {
|
|
30140
|
+
const psNameId = view.getUint16(recOff + 4 + coordsBytes, false);
|
|
30141
|
+
instance.postScriptNameId = psNameId === 65535 ? void 0 : psNameId;
|
|
30142
|
+
}
|
|
30143
|
+
namedInstances.push(instance);
|
|
30144
|
+
}
|
|
30145
|
+
}
|
|
30146
|
+
let avar;
|
|
30147
|
+
const avarRec = tables.get("avar");
|
|
30148
|
+
if (avarRec) avar = parseAvar(fontData, avarRec, axisCount);
|
|
30149
|
+
const info = {
|
|
30150
|
+
isVariable: true,
|
|
30151
|
+
axes,
|
|
30152
|
+
namedInstances
|
|
30153
|
+
};
|
|
30154
|
+
if (avar) info.avar = avar;
|
|
30155
|
+
return info;
|
|
30156
|
+
}
|
|
30157
|
+
/**
|
|
30158
|
+
* Apply an 'avar' segment map to a default-normalized coordinate.
|
|
30159
|
+
*
|
|
30160
|
+
* The segment map is an ordered list of (from, to) pairs in normalized space.
|
|
30161
|
+
* The input is located between two consecutive `fromCoordinate` nodes and the
|
|
30162
|
+
* output is linearly interpolated between the corresponding `toCoordinate`
|
|
30163
|
+
* values. Per spec, a valid map includes the mandatory nodes (-1,-1), (0,0),
|
|
30164
|
+
* (1,1); if it does not span the input, the input is clamped to the map's
|
|
30165
|
+
* endpoints.
|
|
30166
|
+
*/
|
|
30167
|
+
function applyAvarSegmentMap(value, segmentMap) {
|
|
30168
|
+
const n = segmentMap.length;
|
|
30169
|
+
if (n === 0) return value;
|
|
30170
|
+
const first = segmentMap[0];
|
|
30171
|
+
if (value <= first.fromCoordinate) return first.toCoordinate;
|
|
30172
|
+
const last = segmentMap[n - 1];
|
|
30173
|
+
if (value >= last.fromCoordinate) return last.toCoordinate;
|
|
30174
|
+
for (let i = 1; i < n; i++) {
|
|
30175
|
+
const lo = segmentMap[i - 1];
|
|
30176
|
+
const hi = segmentMap[i];
|
|
30177
|
+
if (value >= lo.fromCoordinate && value <= hi.fromCoordinate) {
|
|
30178
|
+
const span = hi.fromCoordinate - lo.fromCoordinate;
|
|
30179
|
+
if (span <= 0) return lo.toCoordinate;
|
|
30180
|
+
const t = (value - lo.fromCoordinate) / span;
|
|
30181
|
+
return lo.toCoordinate + t * (hi.toCoordinate - lo.toCoordinate);
|
|
30182
|
+
}
|
|
30183
|
+
}
|
|
30184
|
+
return value;
|
|
30185
|
+
}
|
|
30186
|
+
/**
|
|
30187
|
+
* Normalize a user-scale coordinate for an axis to the normalized [-1, 0, +1]
|
|
30188
|
+
* scale, per the OpenType default-normalization algorithm.
|
|
30189
|
+
*
|
|
30190
|
+
* Steps:
|
|
30191
|
+
* 1. Clamp `userValue` to the axis's [minValue, maxValue].
|
|
30192
|
+
* 2. Map to normalized space: minValue→-1, defaultValue→0, maxValue→+1, with
|
|
30193
|
+
* linear interpolation on each side of the default (note: the slopes on the
|
|
30194
|
+
* two sides differ unless the default is exactly centered).
|
|
30195
|
+
* 3. If an 'avar' `segmentMap` is supplied, apply it to the result; otherwise
|
|
30196
|
+
* no avar adjustment is made (the caller can pass `info.avar[axisIndex]`).
|
|
30197
|
+
*
|
|
30198
|
+
* @param axis The axis whose user-scale bounds define the normalization.
|
|
30199
|
+
* @param userValue The user-scale coordinate (e.g. 250 for a 'wght' axis).
|
|
30200
|
+
* @param avar Optional 'avar' segment map for this axis.
|
|
30201
|
+
* @returns The normalized coordinate in [-1, 1].
|
|
30202
|
+
*/
|
|
30203
|
+
function normalizeAxisCoordinate(axis, userValue, avar) {
|
|
30204
|
+
const { minValue, defaultValue, maxValue } = axis;
|
|
30205
|
+
let v = userValue;
|
|
30206
|
+
if (v < minValue) v = minValue;
|
|
30207
|
+
else if (v > maxValue) v = maxValue;
|
|
30208
|
+
let normalized;
|
|
30209
|
+
if (v < defaultValue) {
|
|
30210
|
+
const denom = defaultValue - minValue;
|
|
30211
|
+
normalized = denom === 0 ? 0 : -(defaultValue - v) / denom;
|
|
30212
|
+
} else if (v > defaultValue) {
|
|
30213
|
+
const denom = maxValue - defaultValue;
|
|
30214
|
+
normalized = denom === 0 ? 0 : (v - defaultValue) / denom;
|
|
30215
|
+
} else normalized = 0;
|
|
30216
|
+
if (normalized < -1) normalized = -1;
|
|
30217
|
+
else if (normalized > 1) normalized = 1;
|
|
30218
|
+
if (avar && avar.length > 0) normalized = applyAvarSegmentMap(normalized, avar);
|
|
30219
|
+
return normalized;
|
|
30220
|
+
}
|
|
30221
|
+
/**
|
|
30222
|
+
* Resolve a named instance's coordinates against the font's axes.
|
|
30223
|
+
*
|
|
30224
|
+
* Produces a complete axis-tag → user-scale-coordinate map covering every axis
|
|
30225
|
+
* in the font: any axis the instance does not specify is filled with that
|
|
30226
|
+
* axis's `defaultValue`, and any specified coordinate is clamped to the axis's
|
|
30227
|
+
* [minValue, maxValue] range (per the fvar "Variation Instance Selection"
|
|
30228
|
+
* rules). Coordinates for tags not present on any axis are ignored.
|
|
30229
|
+
*
|
|
30230
|
+
* @param info The parsed variable-font model.
|
|
30231
|
+
* @param instance The named instance to resolve.
|
|
30232
|
+
* @returns A validated axis-tag → user-scale coordinate map.
|
|
30233
|
+
*/
|
|
30234
|
+
function resolveInstanceCoordinates(info, instance) {
|
|
30235
|
+
const resolved = {};
|
|
30236
|
+
for (const axis of info.axes) {
|
|
30237
|
+
let value = instance.coordinates[axis.tag] ?? axis.defaultValue;
|
|
30238
|
+
if (value < axis.minValue) value = axis.minValue;
|
|
30239
|
+
else if (value > axis.maxValue) value = axis.maxValue;
|
|
30240
|
+
resolved[axis.tag] = value;
|
|
30241
|
+
}
|
|
30242
|
+
return resolved;
|
|
30243
|
+
}
|
|
30244
|
+
//#endregion
|
|
30245
|
+
//#region src/assets/font/colorFont.ts
|
|
30246
|
+
/**
|
|
30247
|
+
* @module assets/font/colorFont
|
|
30248
|
+
*
|
|
30249
|
+
* COLR v0 + CPAL color-font parsing (layered color glyphs).
|
|
30250
|
+
*
|
|
30251
|
+
* This module reads the two OpenType tables that together describe simple,
|
|
30252
|
+
* layered color glyphs:
|
|
30253
|
+
*
|
|
30254
|
+
* - **CPAL** (Color Palette Table): one or more palettes, each a list of
|
|
30255
|
+
* sRGB colors. Color records are stored on disk as **BGRA** bytes and are
|
|
30256
|
+
* exposed here as **RGBA** (0..255 per channel).
|
|
30257
|
+
* - **COLR** v0 (Color Table, version 0): maps a *base* glyph to an ordered
|
|
30258
|
+
* list of *layers*. Each layer is another glyph (a monochrome outline) plus
|
|
30259
|
+
* a CPAL palette-entry index giving its color.
|
|
30260
|
+
*
|
|
30261
|
+
* A renderer paints a color glyph by drawing each layer's outline glyph filled
|
|
30262
|
+
* with that layer's resolved palette color, bottom layer first.
|
|
30263
|
+
*
|
|
30264
|
+
* ## Verified against the OpenType 1.9.1 specification
|
|
30265
|
+
*
|
|
30266
|
+
* - CPAL header v0 / v1 and the BGRA ColorRecord byte order:
|
|
30267
|
+
* https://learn.microsoft.com/en-us/typography/opentype/spec/cpal
|
|
30268
|
+
* - CPAL v0 header (big-endian): version(u16)\@0, numPaletteEntries(u16)\@2,
|
|
30269
|
+
* numPalettes(u16)\@4, numColorRecords(u16)\@6,
|
|
30270
|
+
* colorRecordsArrayOffset(Offset32)\@8, colorRecordIndices[numPalettes]
|
|
30271
|
+
* (u16 each)\@12.
|
|
30272
|
+
* - "Each color record specifies a color ... using 8-bit BGRA (blue, green,
|
|
30273
|
+
* red, alpha) representation." ColorRecord = blue(u8), green(u8), red(u8),
|
|
30274
|
+
* alpha(u8).
|
|
30275
|
+
* - "colorRecordIndex = colorRecordIndices[paletteIndex] + paletteEntryIndex".
|
|
30276
|
+
* - COLR v0 header and records:
|
|
30277
|
+
* https://learn.microsoft.com/en-us/typography/opentype/spec/colr
|
|
30278
|
+
* - COLR v0 header (big-endian): version(u16)\@0, numBaseGlyphRecords(u16)\@2,
|
|
30279
|
+
* baseGlyphRecordsOffset(Offset32)\@4, layerRecordsOffset(Offset32)\@8,
|
|
30280
|
+
* numLayerRecords(u16)\@12.
|
|
30281
|
+
* - BaseGlyph record (6 bytes): glyphID(u16), firstLayerIndex(u16),
|
|
30282
|
+
* numLayers(u16). "The BaseGlyph records must be sorted in increasing
|
|
30283
|
+
* glyphID order ... a binary search can be used" — we binary-search them.
|
|
30284
|
+
* - Layer record (4 bytes): glyphID(u16), paletteIndex(u16).
|
|
30285
|
+
*
|
|
30286
|
+
* ## Scope
|
|
30287
|
+
*
|
|
30288
|
+
* - Only **COLR version 0** (flat layered glyphs) is parsed. **COLR version 1**
|
|
30289
|
+
* (PaintColrLayers, gradients, affine transforms, compositing) is OUT OF
|
|
30290
|
+
* SCOPE and intentionally not parsed here.
|
|
30291
|
+
* - The CPAL `paletteIndex` special value `0xFFFF` ("use the current text
|
|
30292
|
+
* foreground color") is preserved on each layer via `paletteIndex` but cannot
|
|
30293
|
+
* be resolved to an RGBA value here (there is no palette entry for it); such a
|
|
30294
|
+
* layer is reported with `rgba = [0, 0, 0, 255]` as a neutral fallback. A
|
|
30295
|
+
* renderer should substitute the active foreground color when it sees
|
|
30296
|
+
* `paletteIndex === 0xFFFF`.
|
|
30297
|
+
* - This module exposes the *layer + palette model only*. Actually RENDERING or
|
|
30298
|
+
* EMBEDDING color glyphs into a PDF (e.g. as a Type3 font) is OUT OF SCOPE and
|
|
30299
|
+
* left to a consuming renderer.
|
|
30300
|
+
*
|
|
30301
|
+
* No external dependencies. No Buffer — uses Uint8Array and DataView.
|
|
30302
|
+
*/
|
|
30303
|
+
/** The CPAL special palette index meaning "use the text foreground color". */
|
|
30304
|
+
const FOREGROUND_PALETTE_INDEX = 65535;
|
|
30305
|
+
/**
|
|
30306
|
+
* Read the sfnt table directory. The directory is at offset 12 as a sequence
|
|
30307
|
+
* of 16-byte records: { tag[4], checksum[4], offset[4], length[4] }, all
|
|
30308
|
+
* big-endian. Supports sfnt version 0x00010000 (TrueType) and 'OTTO' (CFF);
|
|
30309
|
+
* the actual sfnt version value is not needed beyond reaching the directory.
|
|
30310
|
+
*
|
|
30311
|
+
* @returns A map from 4-char table tag to its offset/length, or `undefined`
|
|
30312
|
+
* if the data is too small to contain a valid directory.
|
|
30313
|
+
*/
|
|
30314
|
+
function readTableDirectory(data) {
|
|
30315
|
+
if (data.length < 12) return void 0;
|
|
30316
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
30317
|
+
const numTables = view.getUint16(4, false);
|
|
30318
|
+
const requiredLen = 12 + numTables * 16;
|
|
30319
|
+
if (data.length < requiredLen) return void 0;
|
|
30320
|
+
const tables = /* @__PURE__ */ new Map();
|
|
30321
|
+
for (let i = 0; i < numTables; i++) {
|
|
30322
|
+
const off = 12 + i * 16;
|
|
30323
|
+
const tag = String.fromCharCode(data[off], data[off + 1], data[off + 2], data[off + 3]);
|
|
30324
|
+
const offset = view.getUint32(off + 8, false);
|
|
30325
|
+
const length = view.getUint32(off + 12, false);
|
|
30326
|
+
tables.set(tag, {
|
|
30327
|
+
offset,
|
|
30328
|
+
length
|
|
30329
|
+
});
|
|
30330
|
+
}
|
|
30331
|
+
return tables;
|
|
30332
|
+
}
|
|
30333
|
+
/**
|
|
30334
|
+
* Return a `DataView` bounded to a single table, or `undefined` if the table
|
|
30335
|
+
* is absent or its declared extent falls outside the font data.
|
|
30336
|
+
*/
|
|
30337
|
+
function tableView(data, tables, tag) {
|
|
30338
|
+
const rec = tables.get(tag);
|
|
30339
|
+
if (rec === void 0) return void 0;
|
|
30340
|
+
if (rec.offset + rec.length > data.length) return void 0;
|
|
30341
|
+
return new DataView(data.buffer, data.byteOffset + rec.offset, rec.length);
|
|
30342
|
+
}
|
|
30343
|
+
/**
|
|
30344
|
+
* Parse the CPAL table into palettes of RGBA colors.
|
|
30345
|
+
*
|
|
30346
|
+
* Handles CPAL versions 0 and 1 (the v1 trailing offset arrays are ignored —
|
|
30347
|
+
* they only carry optional UI labels and palette-type flags). Color records
|
|
30348
|
+
* are read as BGRA bytes and converted to RGBA.
|
|
30349
|
+
*
|
|
30350
|
+
* @returns The parsed palettes, or `undefined` if the table is malformed.
|
|
30351
|
+
*/
|
|
30352
|
+
function parseCpal(view) {
|
|
30353
|
+
if (view.byteLength < 12) return void 0;
|
|
30354
|
+
const numPaletteEntries = view.getUint16(2, false);
|
|
30355
|
+
const numPalettes = view.getUint16(4, false);
|
|
30356
|
+
const numColorRecords = view.getUint16(6, false);
|
|
30357
|
+
const colorRecordsArrayOffset = view.getUint32(8, false);
|
|
30358
|
+
const indicesStart = 12;
|
|
30359
|
+
if (indicesStart + numPalettes * 2 > view.byteLength) return void 0;
|
|
30360
|
+
if (colorRecordsArrayOffset + numColorRecords * 4 > view.byteLength) return;
|
|
30361
|
+
const colorRecordIndices = [];
|
|
30362
|
+
for (let i = 0; i < numPalettes; i++) colorRecordIndices.push(view.getUint16(indicesStart + i * 2, false));
|
|
30363
|
+
const palettes = [];
|
|
30364
|
+
for (let p = 0; p < numPalettes; p++) {
|
|
30365
|
+
const firstRecord = colorRecordIndices[p];
|
|
30366
|
+
const colors = [];
|
|
30367
|
+
for (let e = 0; e < numPaletteEntries; e++) {
|
|
30368
|
+
const recIndex = firstRecord + e;
|
|
30369
|
+
if (recIndex >= numColorRecords) break;
|
|
30370
|
+
const recOff = colorRecordsArrayOffset + recIndex * 4;
|
|
30371
|
+
const blue = view.getUint8(recOff + 0);
|
|
30372
|
+
const green = view.getUint8(recOff + 1);
|
|
30373
|
+
const red = view.getUint8(recOff + 2);
|
|
30374
|
+
const alpha = view.getUint8(recOff + 3);
|
|
30375
|
+
colors.push([
|
|
30376
|
+
red,
|
|
30377
|
+
green,
|
|
30378
|
+
blue,
|
|
30379
|
+
alpha
|
|
30380
|
+
]);
|
|
30381
|
+
}
|
|
30382
|
+
palettes.push({ colors });
|
|
30383
|
+
}
|
|
30384
|
+
return palettes;
|
|
30385
|
+
}
|
|
30386
|
+
/**
|
|
30387
|
+
* Read the COLR v0 header. Returns `undefined` if the table is too small or
|
|
30388
|
+
* its declared offsets/counts fall outside the table.
|
|
30389
|
+
*/
|
|
30390
|
+
function readColrV0(view) {
|
|
30391
|
+
if (view.byteLength < 14) return void 0;
|
|
30392
|
+
const numBaseGlyphRecords = view.getUint16(2, false);
|
|
30393
|
+
const baseGlyphRecordsOffset = view.getUint32(4, false);
|
|
30394
|
+
const layerRecordsOffset = view.getUint32(8, false);
|
|
30395
|
+
const numLayerRecords = view.getUint16(12, false);
|
|
30396
|
+
if (baseGlyphRecordsOffset + numBaseGlyphRecords * 6 > view.byteLength || layerRecordsOffset + numLayerRecords * 4 > view.byteLength) return;
|
|
30397
|
+
return {
|
|
30398
|
+
baseGlyphRecordsOffset,
|
|
30399
|
+
numBaseGlyphRecords,
|
|
30400
|
+
layerRecordsOffset,
|
|
30401
|
+
numLayerRecords,
|
|
30402
|
+
view
|
|
30403
|
+
};
|
|
30404
|
+
}
|
|
30405
|
+
/**
|
|
30406
|
+
* Binary-search the base-glyph records (sorted by glyphID, per spec) for the
|
|
30407
|
+
* record matching `glyphId`.
|
|
30408
|
+
*
|
|
30409
|
+
* @returns `{ firstLayerIndex, numLayers }` or `undefined` if not a base glyph.
|
|
30410
|
+
*/
|
|
30411
|
+
function findBaseGlyph(colr, glyphId) {
|
|
30412
|
+
const { view, baseGlyphRecordsOffset } = colr;
|
|
30413
|
+
let lo = 0;
|
|
30414
|
+
let hi = colr.numBaseGlyphRecords - 1;
|
|
30415
|
+
while (lo <= hi) {
|
|
30416
|
+
const mid = lo + hi >> 1;
|
|
30417
|
+
const recOff = baseGlyphRecordsOffset + mid * 6;
|
|
30418
|
+
const gid = view.getUint16(recOff, false);
|
|
30419
|
+
if (gid === glyphId) return {
|
|
30420
|
+
firstLayerIndex: view.getUint16(recOff + 2, false),
|
|
30421
|
+
numLayers: view.getUint16(recOff + 4, false)
|
|
30422
|
+
};
|
|
30423
|
+
if (gid < glyphId) lo = mid + 1;
|
|
30424
|
+
else hi = mid - 1;
|
|
30425
|
+
}
|
|
30426
|
+
}
|
|
30427
|
+
/**
|
|
30428
|
+
* Parse a font's color capability and CPAL palettes.
|
|
30429
|
+
*
|
|
30430
|
+
* `hasColor` is true iff the font contains a 'COLR' table. The palettes come
|
|
30431
|
+
* from the 'CPAL' table (which is required whenever 'COLR' is present, per the
|
|
30432
|
+
* OpenType spec). Color records are returned as RGBA (converted from the on-disk
|
|
30433
|
+
* BGRA layout).
|
|
30434
|
+
*
|
|
30435
|
+
* @param fontData - Raw sfnt font bytes (TrueType 0x00010000 or 'OTTO').
|
|
30436
|
+
* @returns Color info; `{ hasColor: false, numPalettes: 0, palettes: [] }` if
|
|
30437
|
+
* the font has no COLR/CPAL tables or cannot be parsed.
|
|
30438
|
+
*/
|
|
30439
|
+
function parseColorFont(fontData) {
|
|
30440
|
+
const empty = {
|
|
30441
|
+
hasColor: false,
|
|
30442
|
+
numPalettes: 0,
|
|
30443
|
+
palettes: []
|
|
30444
|
+
};
|
|
30445
|
+
const tables = readTableDirectory(fontData);
|
|
30446
|
+
if (tables === void 0) return empty;
|
|
30447
|
+
const hasColor = tables.has("COLR");
|
|
30448
|
+
const cpalView = tableView(fontData, tables, "CPAL");
|
|
30449
|
+
if (cpalView === void 0) return {
|
|
30450
|
+
hasColor,
|
|
30451
|
+
numPalettes: 0,
|
|
30452
|
+
palettes: []
|
|
30453
|
+
};
|
|
30454
|
+
const palettes = parseCpal(cpalView);
|
|
30455
|
+
if (palettes === void 0) return {
|
|
30456
|
+
hasColor,
|
|
30457
|
+
numPalettes: 0,
|
|
30458
|
+
palettes: []
|
|
30459
|
+
};
|
|
30460
|
+
return {
|
|
30461
|
+
hasColor,
|
|
30462
|
+
numPalettes: palettes.length,
|
|
30463
|
+
palettes
|
|
30464
|
+
};
|
|
30465
|
+
}
|
|
30466
|
+
/**
|
|
30467
|
+
* Resolve the COLR v0 layers of a base glyph into colored layers.
|
|
30468
|
+
*
|
|
30469
|
+
* For each layer of the requested base glyph, the layer's outline glyph id is
|
|
30470
|
+
* returned together with its CPAL palette-entry index and the RGBA color that
|
|
30471
|
+
* index resolves to in the selected palette.
|
|
30472
|
+
*
|
|
30473
|
+
* A glyph that is **not** a COLR base glyph (or a font with no COLR/CPAL table)
|
|
30474
|
+
* returns `[]` — such a glyph is drawn as a normal monochrome glyph.
|
|
30475
|
+
*
|
|
30476
|
+
* The special palette index `0xFFFF` ("foreground color") is preserved on the
|
|
30477
|
+
* layer's `paletteIndex` but, having no palette entry, resolves to the neutral
|
|
30478
|
+
* fallback `[0, 0, 0, 255]`; a renderer should substitute the active text color.
|
|
30479
|
+
*
|
|
30480
|
+
* @param fontData - Raw sfnt font bytes.
|
|
30481
|
+
* @param glyphId - The base glyph id to expand.
|
|
30482
|
+
* @param paletteIndex - Which CPAL palette to resolve colors from. Defaults to
|
|
30483
|
+
* palette 0 (the default palette). Out-of-range values fall back to palette 0.
|
|
30484
|
+
* @returns The ordered (bottom-first) list of colored layers, or `[]`.
|
|
30485
|
+
*/
|
|
30486
|
+
function getColorGlyphLayers(fontData, glyphId, paletteIndex = 0) {
|
|
30487
|
+
const tables = readTableDirectory(fontData);
|
|
30488
|
+
if (tables === void 0) return [];
|
|
30489
|
+
const colrView = tableView(fontData, tables, "COLR");
|
|
30490
|
+
if (colrView === void 0) return [];
|
|
30491
|
+
const colr = readColrV0(colrView);
|
|
30492
|
+
if (colr === void 0) return [];
|
|
30493
|
+
const base = findBaseGlyph(colr, glyphId);
|
|
30494
|
+
if (base === void 0) return [];
|
|
30495
|
+
const cpalView = tableView(fontData, tables, "CPAL");
|
|
30496
|
+
const palettes = cpalView !== void 0 ? parseCpal(cpalView) : void 0;
|
|
30497
|
+
const selectedPalette = palettes !== void 0 && palettes.length > 0 ? palettes[paletteIndex] ?? palettes[0] : void 0;
|
|
30498
|
+
const layers = [];
|
|
30499
|
+
const { view, layerRecordsOffset, numLayerRecords } = colr;
|
|
30500
|
+
for (let i = 0; i < base.numLayers; i++) {
|
|
30501
|
+
const layerIndex = base.firstLayerIndex + i;
|
|
30502
|
+
if (layerIndex >= numLayerRecords) break;
|
|
30503
|
+
const recOff = layerRecordsOffset + layerIndex * 4;
|
|
30504
|
+
const layerGid = view.getUint16(recOff, false);
|
|
30505
|
+
const layerPaletteIndex = view.getUint16(recOff + 2, false);
|
|
30506
|
+
let rgba = [
|
|
30507
|
+
0,
|
|
30508
|
+
0,
|
|
30509
|
+
0,
|
|
30510
|
+
255
|
|
30511
|
+
];
|
|
30512
|
+
if (layerPaletteIndex !== FOREGROUND_PALETTE_INDEX && selectedPalette !== void 0 && layerPaletteIndex < selectedPalette.colors.length) {
|
|
30513
|
+
const c = selectedPalette.colors[layerPaletteIndex];
|
|
30514
|
+
rgba = [
|
|
30515
|
+
c[0],
|
|
30516
|
+
c[1],
|
|
30517
|
+
c[2],
|
|
30518
|
+
c[3]
|
|
30519
|
+
];
|
|
30520
|
+
}
|
|
30521
|
+
layers.push({
|
|
30522
|
+
glyphId: layerGid,
|
|
30523
|
+
paletteIndex: layerPaletteIndex,
|
|
30524
|
+
rgba
|
|
30525
|
+
});
|
|
30526
|
+
}
|
|
30527
|
+
return layers;
|
|
30528
|
+
}
|
|
30529
|
+
//#endregion
|
|
29119
30530
|
//#region src/render/matrix.ts
|
|
29120
30531
|
/** The identity transform. */
|
|
29121
30532
|
function identity() {
|
|
@@ -34263,6 +35674,12 @@ Object.defineProperty(exports, "getCertificationLevel", {
|
|
|
34263
35674
|
return getCertificationLevel;
|
|
34264
35675
|
}
|
|
34265
35676
|
});
|
|
35677
|
+
Object.defineProperty(exports, "getColorGlyphLayers", {
|
|
35678
|
+
enumerable: true,
|
|
35679
|
+
get: function() {
|
|
35680
|
+
return getColorGlyphLayers;
|
|
35681
|
+
}
|
|
35682
|
+
});
|
|
34266
35683
|
Object.defineProperty(exports, "getComponentDepths", {
|
|
34267
35684
|
enumerable: true,
|
|
34268
35685
|
get: function() {
|
|
@@ -34467,6 +35884,12 @@ Object.defineProperty(exports, "nameHalftone", {
|
|
|
34467
35884
|
return nameHalftone;
|
|
34468
35885
|
}
|
|
34469
35886
|
});
|
|
35887
|
+
Object.defineProperty(exports, "normalizeAxisCoordinate", {
|
|
35888
|
+
enumerable: true,
|
|
35889
|
+
get: function() {
|
|
35890
|
+
return normalizeAxisCoordinate;
|
|
35891
|
+
}
|
|
35892
|
+
});
|
|
34470
35893
|
Object.defineProperty(exports, "normalizeComponentDepth", {
|
|
34471
35894
|
enumerable: true,
|
|
34472
35895
|
get: function() {
|
|
@@ -34503,6 +35926,12 @@ Object.defineProperty(exports, "parseCiiXml", {
|
|
|
34503
35926
|
return parseCiiXml;
|
|
34504
35927
|
}
|
|
34505
35928
|
});
|
|
35929
|
+
Object.defineProperty(exports, "parseColorFont", {
|
|
35930
|
+
enumerable: true,
|
|
35931
|
+
get: function() {
|
|
35932
|
+
return parseColorFont;
|
|
35933
|
+
}
|
|
35934
|
+
});
|
|
34506
35935
|
Object.defineProperty(exports, "parseExistingTrailer", {
|
|
34507
35936
|
enumerable: true,
|
|
34508
35937
|
get: function() {
|
|
@@ -34521,6 +35950,12 @@ Object.defineProperty(exports, "parseTimestampResponse", {
|
|
|
34521
35950
|
return parseTimestampResponse;
|
|
34522
35951
|
}
|
|
34523
35952
|
});
|
|
35953
|
+
Object.defineProperty(exports, "parseVariableFont", {
|
|
35954
|
+
enumerable: true,
|
|
35955
|
+
get: function() {
|
|
35956
|
+
return parseVariableFont;
|
|
35957
|
+
}
|
|
35958
|
+
});
|
|
34524
35959
|
Object.defineProperty(exports, "parseXmpMetadata", {
|
|
34525
35960
|
enumerable: true,
|
|
34526
35961
|
get: function() {
|
|
@@ -34695,6 +36130,12 @@ Object.defineProperty(exports, "renderToPdf", {
|
|
|
34695
36130
|
return renderToPdf;
|
|
34696
36131
|
}
|
|
34697
36132
|
});
|
|
36133
|
+
Object.defineProperty(exports, "reorderVisual", {
|
|
36134
|
+
enumerable: true,
|
|
36135
|
+
get: function() {
|
|
36136
|
+
return reorderVisual;
|
|
36137
|
+
}
|
|
36138
|
+
});
|
|
34698
36139
|
Object.defineProperty(exports, "replaceTemplateVariables", {
|
|
34699
36140
|
enumerable: true,
|
|
34700
36141
|
get: function() {
|
|
@@ -34707,6 +36148,12 @@ Object.defineProperty(exports, "requestTimestamp", {
|
|
|
34707
36148
|
return requestTimestamp;
|
|
34708
36149
|
}
|
|
34709
36150
|
});
|
|
36151
|
+
Object.defineProperty(exports, "resolveBidi", {
|
|
36152
|
+
enumerable: true,
|
|
36153
|
+
get: function() {
|
|
36154
|
+
return resolveBidi;
|
|
36155
|
+
}
|
|
36156
|
+
});
|
|
34710
36157
|
Object.defineProperty(exports, "resolveFallback", {
|
|
34711
36158
|
enumerable: true,
|
|
34712
36159
|
get: function() {
|
|
@@ -34719,6 +36166,12 @@ Object.defineProperty(exports, "resolveFieldReference", {
|
|
|
34719
36166
|
return resolveFieldReference;
|
|
34720
36167
|
}
|
|
34721
36168
|
});
|
|
36169
|
+
Object.defineProperty(exports, "resolveInstanceCoordinates", {
|
|
36170
|
+
enumerable: true,
|
|
36171
|
+
get: function() {
|
|
36172
|
+
return resolveInstanceCoordinates;
|
|
36173
|
+
}
|
|
36174
|
+
});
|
|
34722
36175
|
Object.defineProperty(exports, "sampleShadingColor", {
|
|
34723
36176
|
enumerable: true,
|
|
34724
36177
|
get: function() {
|
|
@@ -35062,4 +36515,4 @@ Object.defineProperty(exports, "wrapText", {
|
|
|
35062
36515
|
}
|
|
35063
36516
|
});
|
|
35064
36517
|
|
|
35065
|
-
//# sourceMappingURL=src-
|
|
36518
|
+
//# sourceMappingURL=src-MIBq9xGq.cjs.map
|