modern-pdf-lib 0.35.0 → 0.37.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 +22 -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-BnS4WpNK.d.cts} +713 -3
- package/dist/index-BnS4WpNK.d.cts.map +1 -0
- package/dist/{index-CwgepM_G.d.mts → index-n0WBuf3b.d.mts} +713 -3
- package/dist/index-n0WBuf3b.d.mts.map +1 -0
- package/dist/index.cjs +22 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/{src-C-mBizNU.cjs → src-CbPUzRbg.cjs} +2411 -15
- package/dist/{src-BlUlmYsU.mjs → src-DGxzt-jG.mjs} +2286 -16
- package/package.json +1 -1
- package/dist/index-CIdDdXlK.d.cts.map +0 -1
- package/dist/index-CwgepM_G.d.mts.map +0 -1
|
@@ -19503,15 +19503,15 @@ const D50_WHITE_POINT = [
|
|
|
19503
19503
|
.8249
|
|
19504
19504
|
];
|
|
19505
19505
|
/** CIE constant δ = 6/29; the linear/cubic break is at t ≤ δ. */
|
|
19506
|
-
const LAB_DELTA = 6 / 29;
|
|
19506
|
+
const LAB_DELTA$2 = 6 / 29;
|
|
19507
19507
|
/** 3δ² — slope of the inverse linear branch. */
|
|
19508
|
-
const LAB_3_DELTA_SQUARED = 3 * LAB_DELTA * LAB_DELTA;
|
|
19508
|
+
const LAB_3_DELTA_SQUARED$1 = 3 * LAB_DELTA$2 * LAB_DELTA$2;
|
|
19509
19509
|
/** Inverse of the CIE L*a*b* nonlinearity f(t). */
|
|
19510
19510
|
function labInverseF(t) {
|
|
19511
|
-
return t > LAB_DELTA ? t * t * t : LAB_3_DELTA_SQUARED * (t - 4 / 29);
|
|
19511
|
+
return t > LAB_DELTA$2 ? t * t * t : LAB_3_DELTA_SQUARED$1 * (t - 4 / 29);
|
|
19512
19512
|
}
|
|
19513
19513
|
/** Apply the sRGB transfer function (linear → companded), input clamped 0..1. */
|
|
19514
|
-
function linearToSrgb(c) {
|
|
19514
|
+
function linearToSrgb$1(c) {
|
|
19515
19515
|
const clamped = c < 0 ? 0 : c > 1 ? 1 : c;
|
|
19516
19516
|
const v = clamped <= .0031308 ? 12.92 * clamped : 1.055 * clamped ** (1 / 2.4) - .055;
|
|
19517
19517
|
return v < 0 ? 0 : v > 1 ? 1 : v;
|
|
@@ -19545,9 +19545,9 @@ function labToRgb(L, a, b, whitePoint = D50_WHITE_POINT) {
|
|
|
19545
19545
|
const gLin = -.9787684 * X + 1.9161415 * Y + .033454 * Z;
|
|
19546
19546
|
const bLin = .0719453 * X - .2289914 * Y + 1.4052427 * Z;
|
|
19547
19547
|
return [
|
|
19548
|
-
linearToSrgb(rLin),
|
|
19549
|
-
linearToSrgb(gLin),
|
|
19550
|
-
linearToSrgb(bLin)
|
|
19548
|
+
linearToSrgb$1(rLin),
|
|
19549
|
+
linearToSrgb$1(gLin),
|
|
19550
|
+
linearToSrgb$1(bLin)
|
|
19551
19551
|
];
|
|
19552
19552
|
}
|
|
19553
19553
|
//#endregion
|
|
@@ -20153,7 +20153,7 @@ function breakLongWord$1(word, font, size, limit) {
|
|
|
20153
20153
|
//#endregion
|
|
20154
20154
|
//#region src/core/pdfFunctions.ts
|
|
20155
20155
|
/** Clamp `x` into the inclusive `[lo, hi]` interval. */
|
|
20156
|
-
function clamp(x, lo, hi) {
|
|
20156
|
+
function clamp$1(x, lo, hi) {
|
|
20157
20157
|
if (x < lo) return lo;
|
|
20158
20158
|
if (x > hi) return hi;
|
|
20159
20159
|
return x;
|
|
@@ -20173,14 +20173,14 @@ function clampToRange(out, range) {
|
|
|
20173
20173
|
for (let i = 0; i < out.length; i++) {
|
|
20174
20174
|
const lo = range[2 * i];
|
|
20175
20175
|
const hi = range[2 * i + 1];
|
|
20176
|
-
if (lo !== void 0 && hi !== void 0) out[i] = clamp(out[i], lo, hi);
|
|
20176
|
+
if (lo !== void 0 && hi !== void 0) out[i] = clamp$1(out[i], lo, hi);
|
|
20177
20177
|
}
|
|
20178
20178
|
return out;
|
|
20179
20179
|
}
|
|
20180
20180
|
function evaluateExponential(fn, inputs) {
|
|
20181
20181
|
const c0 = fn.c0 ?? [0];
|
|
20182
20182
|
const c1 = fn.c1 ?? [1];
|
|
20183
|
-
const x = clamp(inputs[0] ?? 0, fn.domain[0] ?? 0, fn.domain[1] ?? 1);
|
|
20183
|
+
const x = clamp$1(inputs[0] ?? 0, fn.domain[0] ?? 0, fn.domain[1] ?? 1);
|
|
20184
20184
|
const xn = fn.n === 1 ? x : x ** fn.n;
|
|
20185
20185
|
const count = Math.max(c0.length, c1.length);
|
|
20186
20186
|
const out = [];
|
|
@@ -20194,7 +20194,7 @@ function evaluateExponential(fn, inputs) {
|
|
|
20194
20194
|
function evaluateStitching(fn, inputs) {
|
|
20195
20195
|
const d0 = fn.domain[0] ?? 0;
|
|
20196
20196
|
const d1 = fn.domain[1] ?? 1;
|
|
20197
|
-
const x = clamp(inputs[0] ?? 0, d0, d1);
|
|
20197
|
+
const x = clamp$1(inputs[0] ?? 0, d0, d1);
|
|
20198
20198
|
const k = fn.functions.length;
|
|
20199
20199
|
let i = 0;
|
|
20200
20200
|
while (i < fn.bounds.length && x >= fn.bounds[i]) i++;
|
|
@@ -20212,10 +20212,10 @@ function evaluateSampled(fn, inputs) {
|
|
|
20212
20212
|
for (let j = 0; j < m; j++) {
|
|
20213
20213
|
const dMin = fn.domain[2 * j] ?? 0;
|
|
20214
20214
|
const dMax = fn.domain[2 * j + 1] ?? 1;
|
|
20215
|
-
const x = clamp(inputs[j] ?? 0, dMin, dMax);
|
|
20215
|
+
const x = clamp$1(inputs[j] ?? 0, dMin, dMax);
|
|
20216
20216
|
const size = fn.size[j] ?? 1;
|
|
20217
20217
|
const enc = interpolate(x, dMin, dMax, fn.encode ? fn.encode[2 * j] ?? 0 : 0, fn.encode ? fn.encode[2 * j + 1] ?? size - 1 : size - 1);
|
|
20218
|
-
e.push(clamp(enc, 0, size - 1));
|
|
20218
|
+
e.push(clamp$1(enc, 0, size - 1));
|
|
20219
20219
|
}
|
|
20220
20220
|
const lower = [];
|
|
20221
20221
|
const frac = [];
|
|
@@ -20561,7 +20561,7 @@ function evaluatePostScript(fn, inputs) {
|
|
|
20561
20561
|
for (let j = 0; j < m; j++) {
|
|
20562
20562
|
const dMin = fn.domain[2 * j] ?? 0;
|
|
20563
20563
|
const dMax = fn.domain[2 * j + 1] ?? 1;
|
|
20564
|
-
stack.push(clamp(inputs[j] ?? 0, dMin, dMax));
|
|
20564
|
+
stack.push(clamp$1(inputs[j] ?? 0, dMin, dMax));
|
|
20565
20565
|
}
|
|
20566
20566
|
execPostScript(program, stack);
|
|
20567
20567
|
const nOut = Math.floor(fn.range.length / 2);
|
|
@@ -29116,6 +29116,2276 @@ 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
|
|
30530
|
+
//#region src/core/meshShading.ts
|
|
30531
|
+
/**
|
|
30532
|
+
* @module core/meshShading
|
|
30533
|
+
*
|
|
30534
|
+
* Mesh shadings — ShadingType 4, 5, 6 and 7 (ISO 32000-2:2020 §8.7.4.5.5–.8).
|
|
30535
|
+
*
|
|
30536
|
+
* Mesh shadings define colour over a 2-D region by tessellating it into either
|
|
30537
|
+
* Gouraud-shaded triangles (types 4 and 5) or parametric patches (types 6 and
|
|
30538
|
+
* 7). Unlike the dictionary-only shadings (types 1–3), a mesh shading's data is
|
|
30539
|
+
* carried in a **stream** whose body is a packed sequence of binary records.
|
|
30540
|
+
*
|
|
30541
|
+
* Bit-packing rule (ISO 32000-2 §8.7.4.5.5, "All vertex coordinates and colour
|
|
30542
|
+
* values shall be encoded as ... a sequence of bits ... most significant bit
|
|
30543
|
+
* first"): every value — flag, coordinate and colour component — is written
|
|
30544
|
+
* big-endian, MSB-first, into a continuous bit stream with **no padding between
|
|
30545
|
+
* values**. Only the very end of the stream is padded with zero bits up to the
|
|
30546
|
+
* next byte boundary. (§8.7.4.5.5 further notes each value may begin at any bit
|
|
30547
|
+
* position; there is no per-record byte alignment.)
|
|
30548
|
+
*
|
|
30549
|
+
* Numeric mapping (ISO 32000-2 §8.7.4.5.5, Table 84 /Decode semantics): a
|
|
30550
|
+
* coordinate or colour value is stored as an unsigned integer `v` in the range
|
|
30551
|
+
* `[0, 2^bits − 1]` and decoded as
|
|
30552
|
+
* `decoded = Dmin + v · (Dmax − Dmin) / (2^bits − 1)`
|
|
30553
|
+
* where `[Dmin, Dmax]` is the matching pair in the shading's /Decode array. The
|
|
30554
|
+
* /Decode layout is `[xmin xmax ymin ymax c1min c1max … cnmin cnmax]` (or, when
|
|
30555
|
+
* /Function is present, a single `[tmin tmax]` colour pair). The builders here
|
|
30556
|
+
* take **decoded** user values and invert that mapping to obtain the raw
|
|
30557
|
+
* integers to pack, rounding to nearest.
|
|
30558
|
+
*
|
|
30559
|
+
* Record layouts verified against ISO 32000-2:2020:
|
|
30560
|
+
* - Type 4 (§8.7.4.5.5): per vertex → flag (BitsPerFlag) · x · y · colour[n].
|
|
30561
|
+
* - Type 5 (§8.7.4.5.6): per vertex → x · y · colour[n]; NO flag; adds
|
|
30562
|
+
* /VerticesPerRow (≥ 2). Vertices are given row by row.
|
|
30563
|
+
* - Type 6 (§8.7.4.5.7): per patch → flag (BitsPerFlag) · coordinate pairs ·
|
|
30564
|
+
* colours. Flag 0 (new patch) = 12 coordinate pairs + 4 colours; flags 1–3
|
|
30565
|
+
* (shared edge) = 8 coordinate pairs + 2 colours.
|
|
30566
|
+
* - Type 7 (§8.7.4.5.8): like type 6 but flag 0 = 16 coordinate pairs +
|
|
30567
|
+
* 4 colours; flags 1–3 = 12 coordinate pairs + 2 colours.
|
|
30568
|
+
*
|
|
30569
|
+
* No Buffer — uses Uint8Array exclusively.
|
|
30570
|
+
*/
|
|
30571
|
+
/**
|
|
30572
|
+
* Accumulates unsigned integer values of arbitrary bit width into a byte
|
|
30573
|
+
* stream, big-endian and most-significant-bit-first, with no inter-value
|
|
30574
|
+
* padding. The final byte is zero-padded to the byte boundary on
|
|
30575
|
+
* {@link BitPacker.finish}.
|
|
30576
|
+
*
|
|
30577
|
+
* @internal
|
|
30578
|
+
*/
|
|
30579
|
+
var BitPacker = class {
|
|
30580
|
+
bytes = [];
|
|
30581
|
+
/** Bits already filled in the current (pending) byte, 0–7. */
|
|
30582
|
+
bitPos = 0;
|
|
30583
|
+
/** The partially filled current byte (high bits set first). */
|
|
30584
|
+
current = 0;
|
|
30585
|
+
/**
|
|
30586
|
+
* Append `bits`-wide unsigned integer `value`, MSB first.
|
|
30587
|
+
*
|
|
30588
|
+
* @param value - non-negative integer in `[0, 2^bits − 1]`.
|
|
30589
|
+
* @param bits - field width (1–32).
|
|
30590
|
+
*/
|
|
30591
|
+
push(value, bits) {
|
|
30592
|
+
for (let i = bits - 1; i >= 0; i--) {
|
|
30593
|
+
const bit = value >>> i & 1;
|
|
30594
|
+
this.current = this.current << 1 | bit;
|
|
30595
|
+
this.bitPos++;
|
|
30596
|
+
if (this.bitPos === 8) {
|
|
30597
|
+
this.bytes.push(this.current & 255);
|
|
30598
|
+
this.current = 0;
|
|
30599
|
+
this.bitPos = 0;
|
|
30600
|
+
}
|
|
30601
|
+
}
|
|
30602
|
+
}
|
|
30603
|
+
/**
|
|
30604
|
+
* Flush the pending partial byte (zero-padded on the low bits) and return the
|
|
30605
|
+
* packed buffer.
|
|
30606
|
+
*/
|
|
30607
|
+
finish() {
|
|
30608
|
+
if (this.bitPos > 0) {
|
|
30609
|
+
this.bytes.push(this.current << 8 - this.bitPos & 255);
|
|
30610
|
+
this.current = 0;
|
|
30611
|
+
this.bitPos = 0;
|
|
30612
|
+
}
|
|
30613
|
+
return Uint8Array.from(this.bytes);
|
|
30614
|
+
}
|
|
30615
|
+
};
|
|
30616
|
+
/**
|
|
30617
|
+
* Invert the /Decode mapping for one value: given a decoded user value and its
|
|
30618
|
+
* `[min, max]` pair, return the raw unsigned integer in `[0, 2^bits − 1]`.
|
|
30619
|
+
*
|
|
30620
|
+
* `raw = round((decoded − min) / (max − min) · (2^bits − 1))`, clamped to the
|
|
30621
|
+
* representable range (ISO 32000-2 §8.7.4.5.5, /Decode semantics). A degenerate
|
|
30622
|
+
* `min == max` pair maps everything to 0.
|
|
30623
|
+
*
|
|
30624
|
+
* @internal
|
|
30625
|
+
*/
|
|
30626
|
+
function encodeValue(decoded, min, max, bits) {
|
|
30627
|
+
const maxRaw = bits >= 32 ? 4294967295 : (1 << bits) - 1;
|
|
30628
|
+
if (max === min) return 0;
|
|
30629
|
+
const t = (decoded - min) / (max - min);
|
|
30630
|
+
const raw = Math.round(t * maxRaw);
|
|
30631
|
+
if (raw < 0) return 0;
|
|
30632
|
+
if (raw > maxRaw) return maxRaw;
|
|
30633
|
+
return raw;
|
|
30634
|
+
}
|
|
30635
|
+
/**
|
|
30636
|
+
* Number of colour components carried per vertex/corner:
|
|
30637
|
+
* 1 when a /Function is present (parametric `t`), else the count implied by the
|
|
30638
|
+
* colour /Decode pairs.
|
|
30639
|
+
*
|
|
30640
|
+
* @internal
|
|
30641
|
+
*/
|
|
30642
|
+
function colorComponentCount(common) {
|
|
30643
|
+
if (common.function !== void 0) return 1;
|
|
30644
|
+
const colorPairs = (common.decode.length - 4) / 2;
|
|
30645
|
+
return Math.max(1, Math.trunc(colorPairs));
|
|
30646
|
+
}
|
|
30647
|
+
/**
|
|
30648
|
+
* Pack one vertex's coordinates then its colours (no flag) into `packer`.
|
|
30649
|
+
*
|
|
30650
|
+
* @internal
|
|
30651
|
+
*/
|
|
30652
|
+
function packVertexData(packer, vertex, common, nColor) {
|
|
30653
|
+
const { decode, bitsPerCoordinate: bpcoord, bitsPerComponent: bpcomp } = common;
|
|
30654
|
+
packer.push(encodeValue(vertex.x, decode[0] ?? 0, decode[1] ?? 1, bpcoord), bpcoord);
|
|
30655
|
+
packer.push(encodeValue(vertex.y, decode[2] ?? 0, decode[3] ?? 1, bpcoord), bpcoord);
|
|
30656
|
+
for (let k = 0; k < nColor; k++) {
|
|
30657
|
+
const cmin = decode[4 + 2 * k] ?? 0;
|
|
30658
|
+
const cmax = decode[5 + 2 * k] ?? 1;
|
|
30659
|
+
const comp = vertex.color[k] ?? 0;
|
|
30660
|
+
packer.push(encodeValue(comp, cmin, cmax, bpcomp), bpcomp);
|
|
30661
|
+
}
|
|
30662
|
+
}
|
|
30663
|
+
/**
|
|
30664
|
+
* Pack a single `[x, y]` control point (coordinates only) into `packer`.
|
|
30665
|
+
*
|
|
30666
|
+
* @internal
|
|
30667
|
+
*/
|
|
30668
|
+
function packPoint(packer, point, common) {
|
|
30669
|
+
const { decode, bitsPerCoordinate: bpcoord } = common;
|
|
30670
|
+
packer.push(encodeValue(point[0], decode[0] ?? 0, decode[1] ?? 1, bpcoord), bpcoord);
|
|
30671
|
+
packer.push(encodeValue(point[1], decode[2] ?? 0, decode[3] ?? 1, bpcoord), bpcoord);
|
|
30672
|
+
}
|
|
30673
|
+
/**
|
|
30674
|
+
* Pack one colour (n components) into `packer`.
|
|
30675
|
+
*
|
|
30676
|
+
* @internal
|
|
30677
|
+
*/
|
|
30678
|
+
function packColor(packer, color, common, nColor) {
|
|
30679
|
+
const { decode, bitsPerComponent: bpcomp } = common;
|
|
30680
|
+
for (let k = 0; k < nColor; k++) {
|
|
30681
|
+
const cmin = decode[4 + 2 * k] ?? 0;
|
|
30682
|
+
const cmax = decode[5 + 2 * k] ?? 1;
|
|
30683
|
+
const comp = color[k] ?? 0;
|
|
30684
|
+
packer.push(encodeValue(comp, cmin, cmax, bpcomp), bpcomp);
|
|
30685
|
+
}
|
|
30686
|
+
}
|
|
30687
|
+
/**
|
|
30688
|
+
* Build the dictionary shared by every mesh shading and set the common keys.
|
|
30689
|
+
*
|
|
30690
|
+
* @internal
|
|
30691
|
+
*/
|
|
30692
|
+
function buildCommonDict(shadingType, common, includeFlag) {
|
|
30693
|
+
const dict = new PdfDict();
|
|
30694
|
+
dict.set("/ShadingType", PdfNumber.of(shadingType));
|
|
30695
|
+
dict.set("/ColorSpace", common.colorSpace);
|
|
30696
|
+
dict.set("/BitsPerCoordinate", PdfNumber.of(common.bitsPerCoordinate));
|
|
30697
|
+
dict.set("/BitsPerComponent", PdfNumber.of(common.bitsPerComponent));
|
|
30698
|
+
if (includeFlag) dict.set("/BitsPerFlag", PdfNumber.of(common.bitsPerFlag));
|
|
30699
|
+
dict.set("/Decode", PdfArray.fromNumbers([...common.decode]));
|
|
30700
|
+
if (common.function !== void 0) dict.set("/Function", common.function);
|
|
30701
|
+
return dict;
|
|
30702
|
+
}
|
|
30703
|
+
/**
|
|
30704
|
+
* Build a free-form Gouraud-shaded triangle mesh shading
|
|
30705
|
+
* (ISO 32000-2 §8.7.4.5.5, /ShadingType 4).
|
|
30706
|
+
*
|
|
30707
|
+
* Each vertex is packed as `flag · x · y · colour[n]`, MSB-first, with the flag
|
|
30708
|
+
* `bitsPerFlag` wide.
|
|
30709
|
+
*
|
|
30710
|
+
* @param options - the common mesh keys plus the triangle/vertex data.
|
|
30711
|
+
* @returns a {@link PdfStream} whose dict has /ShadingType 4 and whose body is
|
|
30712
|
+
* the packed vertex stream.
|
|
30713
|
+
*/
|
|
30714
|
+
function buildFreeFormGouraudShading(options) {
|
|
30715
|
+
const dict = buildCommonDict(4, options, true);
|
|
30716
|
+
const nColor = colorComponentCount(options);
|
|
30717
|
+
const packer = new BitPacker();
|
|
30718
|
+
for (const tri of options.triangles) for (const vertex of tri.vertices) {
|
|
30719
|
+
packer.push(vertex.flag, options.bitsPerFlag);
|
|
30720
|
+
packVertexData(packer, vertex, options, nColor);
|
|
30721
|
+
}
|
|
30722
|
+
const data = packer.finish();
|
|
30723
|
+
return PdfStream.fromBytes(data, dict);
|
|
30724
|
+
}
|
|
30725
|
+
/**
|
|
30726
|
+
* Build a lattice-form Gouraud-shaded triangle mesh shading
|
|
30727
|
+
* (ISO 32000-2 §8.7.4.5.6, /ShadingType 5).
|
|
30728
|
+
*
|
|
30729
|
+
* Vertices carry **no** flag; the mesh topology is implied by /VerticesPerRow.
|
|
30730
|
+
* Each vertex is packed as `x · y · colour[n]`, MSB-first.
|
|
30731
|
+
*
|
|
30732
|
+
* @param options - the common mesh keys (sans /BitsPerFlag) plus
|
|
30733
|
+
* /VerticesPerRow and the row-major vertex list.
|
|
30734
|
+
* @returns a {@link PdfStream} with /ShadingType 5 and /VerticesPerRow.
|
|
30735
|
+
*/
|
|
30736
|
+
function buildLatticeFormGouraudShading(options) {
|
|
30737
|
+
const common = {
|
|
30738
|
+
colorSpace: options.colorSpace,
|
|
30739
|
+
bitsPerCoordinate: options.bitsPerCoordinate,
|
|
30740
|
+
bitsPerComponent: options.bitsPerComponent,
|
|
30741
|
+
bitsPerFlag: 8,
|
|
30742
|
+
decode: options.decode,
|
|
30743
|
+
function: options.function
|
|
30744
|
+
};
|
|
30745
|
+
const dict = buildCommonDict(5, common, false);
|
|
30746
|
+
dict.set("/VerticesPerRow", PdfNumber.of(options.verticesPerRow));
|
|
30747
|
+
const nColor = colorComponentCount(common);
|
|
30748
|
+
const packer = new BitPacker();
|
|
30749
|
+
for (const vertex of options.vertices) packVertexData(packer, vertex, common, nColor);
|
|
30750
|
+
const data = packer.finish();
|
|
30751
|
+
return PdfStream.fromBytes(data, dict);
|
|
30752
|
+
}
|
|
30753
|
+
/**
|
|
30754
|
+
* Build a Coons patch mesh shading
|
|
30755
|
+
* (ISO 32000-2 §8.7.4.5.7, /ShadingType 6).
|
|
30756
|
+
*
|
|
30757
|
+
* Each patch is packed as `flag · points · colours`. A flag-0 patch carries 12
|
|
30758
|
+
* control points and 4 corner colours; flags 1–3 carry 8 control points and 2
|
|
30759
|
+
* colours (the rest are inherited from the shared edge of the previous patch).
|
|
30760
|
+
*
|
|
30761
|
+
* @param options - the common mesh keys plus the patch list.
|
|
30762
|
+
* @returns a {@link PdfStream} with /ShadingType 6.
|
|
30763
|
+
*/
|
|
30764
|
+
function buildCoonsPatchShading(options) {
|
|
30765
|
+
const dict = buildCommonDict(6, options, true);
|
|
30766
|
+
const nColor = colorComponentCount(options);
|
|
30767
|
+
const packer = new BitPacker();
|
|
30768
|
+
for (const patch of options.patches) {
|
|
30769
|
+
packer.push(patch.flag, options.bitsPerFlag);
|
|
30770
|
+
for (const point of patch.points) packPoint(packer, point, options);
|
|
30771
|
+
for (const color of patch.colors) packColor(packer, color, options, nColor);
|
|
30772
|
+
}
|
|
30773
|
+
const data = packer.finish();
|
|
30774
|
+
return PdfStream.fromBytes(data, dict);
|
|
30775
|
+
}
|
|
30776
|
+
/**
|
|
30777
|
+
* Build a tensor-product patch mesh shading
|
|
30778
|
+
* (ISO 32000-2 §8.7.4.5.8, /ShadingType 7).
|
|
30779
|
+
*
|
|
30780
|
+
* Identical packing to type 6 but a flag-0 patch carries 16 control points (the
|
|
30781
|
+
* 4 internal tensor points in addition to the 12 boundary points) plus 4 corner
|
|
30782
|
+
* colours; flags 1–3 carry 12 control points and 2 colours.
|
|
30783
|
+
*
|
|
30784
|
+
* @param options - the common mesh keys plus the patch list.
|
|
30785
|
+
* @returns a {@link PdfStream} with /ShadingType 7.
|
|
30786
|
+
*/
|
|
30787
|
+
function buildTensorPatchShading(options) {
|
|
30788
|
+
const dict = buildCommonDict(7, options, true);
|
|
30789
|
+
const nColor = colorComponentCount(options);
|
|
30790
|
+
const packer = new BitPacker();
|
|
30791
|
+
for (const patch of options.patches) {
|
|
30792
|
+
packer.push(patch.flag, options.bitsPerFlag);
|
|
30793
|
+
for (const point of patch.points) packPoint(packer, point, options);
|
|
30794
|
+
for (const color of patch.colors) packColor(packer, color, options, nColor);
|
|
30795
|
+
}
|
|
30796
|
+
const data = packer.finish();
|
|
30797
|
+
return PdfStream.fromBytes(data, dict);
|
|
30798
|
+
}
|
|
30799
|
+
//#endregion
|
|
30800
|
+
//#region src/color/iccTransform.ts
|
|
30801
|
+
/**
|
|
30802
|
+
* ICC PCS reference white, D50, in XYZ (ICC.1:2010 §6.3.4.3 / Annex A,
|
|
30803
|
+
* encoded white point `[0.9642, 1.0000, 0.8249]`).
|
|
30804
|
+
*/
|
|
30805
|
+
const D50_WHITE = [
|
|
30806
|
+
.9642,
|
|
30807
|
+
1,
|
|
30808
|
+
.8249
|
|
30809
|
+
];
|
|
30810
|
+
/** CIE L\*a\*b\* threshold δ = 6/29 (CIE 15:2004 §8.2.1). */
|
|
30811
|
+
const LAB_DELTA$1 = 6 / 29;
|
|
30812
|
+
/** δ³ — the f(t) breakpoint in the t domain. */
|
|
30813
|
+
const LAB_DELTA_CUBED$1 = LAB_DELTA$1 * LAB_DELTA$1 * LAB_DELTA$1;
|
|
30814
|
+
/** 1 / (3·δ²) — slope of the linear segment of f(t). */
|
|
30815
|
+
const LAB_LINEAR_SLOPE = 1 / (3 * LAB_DELTA$1 * LAB_DELTA$1);
|
|
30816
|
+
/** 4/29 — intercept of the linear segment of f(t). */
|
|
30817
|
+
const LAB_LINEAR_INTERCEPT = 4 / 29;
|
|
30818
|
+
const HEADER_SIZE = 128;
|
|
30819
|
+
const TAG_COUNT_OFFSET = 128;
|
|
30820
|
+
const TAG_TABLE_OFFSET = 132;
|
|
30821
|
+
const TAG_RECORD_SIZE = 12;
|
|
30822
|
+
/** Decode a 4-byte ASCII signature at `off`. */
|
|
30823
|
+
function readSig(data, off) {
|
|
30824
|
+
return String.fromCharCode(data[off], data[off + 1], data[off + 2], data[off + 3]);
|
|
30825
|
+
}
|
|
30826
|
+
/** s15Fixed16Number → JS number (ICC.1:2010 §4.2): signed int32 / 65536. */
|
|
30827
|
+
function readS15Fixed16(view, off) {
|
|
30828
|
+
return view.getInt32(off, false) / 65536;
|
|
30829
|
+
}
|
|
30830
|
+
/**
|
|
30831
|
+
* Read the tag table into a `sig → {offset,size}` map.
|
|
30832
|
+
*
|
|
30833
|
+
* @throws if the profile is shorter than a header + tag count, or the tag
|
|
30834
|
+
* table extends past the end of the data.
|
|
30835
|
+
*/
|
|
30836
|
+
function readTagTable(data) {
|
|
30837
|
+
if (data.length < TAG_TABLE_OFFSET) throw new Error(`ICC profile too short: ${data.length} bytes (need at least ${TAG_TABLE_OFFSET}).`);
|
|
30838
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
30839
|
+
const count = view.getUint32(TAG_COUNT_OFFSET, false);
|
|
30840
|
+
if (TAG_TABLE_OFFSET + count * TAG_RECORD_SIZE > data.length) throw new Error(`ICC tag table (${count} tags) overruns profile of ${data.length} bytes.`);
|
|
30841
|
+
const table = /* @__PURE__ */ new Map();
|
|
30842
|
+
for (let i = 0; i < count; i++) {
|
|
30843
|
+
const rec = TAG_TABLE_OFFSET + i * TAG_RECORD_SIZE;
|
|
30844
|
+
const sig = readSig(data, rec);
|
|
30845
|
+
const offset = view.getUint32(rec + 4, false);
|
|
30846
|
+
const size = view.getUint32(rec + 8, false);
|
|
30847
|
+
table.set(sig, {
|
|
30848
|
+
offset,
|
|
30849
|
+
size
|
|
30850
|
+
});
|
|
30851
|
+
}
|
|
30852
|
+
return table;
|
|
30853
|
+
}
|
|
30854
|
+
/**
|
|
30855
|
+
* Parse the matrix/TRC-relevant header fields and detect whether the profile
|
|
30856
|
+
* carries a complete matrix/TRC model.
|
|
30857
|
+
*
|
|
30858
|
+
* Reads the 128-byte ICC header (ICC.1:2010 §7.2): version (offset 8),
|
|
30859
|
+
* deviceClass (offset 12), data colour space (offset 16) and PCS (offset 20),
|
|
30860
|
+
* then inspects the tag table (§7.3) for the colorant + TRC tags.
|
|
30861
|
+
*
|
|
30862
|
+
* @param profile - Raw ICC profile bytes.
|
|
30863
|
+
* @returns Parsed {@link IccTransformInfo}.
|
|
30864
|
+
* @throws if the profile is too short to contain a header and tag table.
|
|
30865
|
+
*
|
|
30866
|
+
* @example
|
|
30867
|
+
* ```ts
|
|
30868
|
+
* import { parseIccTransform } from 'modern-pdf-lib';
|
|
30869
|
+
*
|
|
30870
|
+
* const info = parseIccTransform(profileBytes);
|
|
30871
|
+
* if (info.hasMatrixTrc && info.colorSpace === 'RGB ') {
|
|
30872
|
+
* // safe to call deviceRgbToXyz
|
|
30873
|
+
* }
|
|
30874
|
+
* ```
|
|
30875
|
+
*/
|
|
30876
|
+
function parseIccTransform(profile) {
|
|
30877
|
+
if (profile.length < HEADER_SIZE) throw new Error(`ICC profile too short: ${profile.length} bytes (need at least ${HEADER_SIZE}).`);
|
|
30878
|
+
const version = new DataView(profile.buffer, profile.byteOffset, profile.byteLength).getUint32(8, false);
|
|
30879
|
+
const deviceClass = readSig(profile, 12);
|
|
30880
|
+
const colorSpace = readSig(profile, 16);
|
|
30881
|
+
const pcs = readSig(profile, 20);
|
|
30882
|
+
const table = readTagTable(profile);
|
|
30883
|
+
let hasMatrixTrc;
|
|
30884
|
+
if (colorSpace === "GRAY") hasMatrixTrc = table.has("kTRC");
|
|
30885
|
+
else hasMatrixTrc = table.has("rXYZ") && table.has("gXYZ") && table.has("bXYZ") && table.has("rTRC") && table.has("gTRC") && table.has("bTRC");
|
|
30886
|
+
return {
|
|
30887
|
+
version,
|
|
30888
|
+
deviceClass,
|
|
30889
|
+
colorSpace,
|
|
30890
|
+
pcs,
|
|
30891
|
+
hasMatrixTrc
|
|
30892
|
+
};
|
|
30893
|
+
}
|
|
30894
|
+
/**
|
|
30895
|
+
* Evaluate a TRC tag (`curv` or `para`) at a normalised input `x ∈ [0,1]`,
|
|
30896
|
+
* returning the linearised value in `[0,1]`.
|
|
30897
|
+
*
|
|
30898
|
+
* - `curv` (ICC.1:2010 §10.6): `count` u16 follows the 8-byte type header.
|
|
30899
|
+
* `count == 0` → identity; `count == 1` → a single u8Fixed8Number gamma
|
|
30900
|
+
* (Y = X^g); `count >= 2` → a uInt16 sampled curve over the input domain
|
|
30901
|
+
* [0,1], linearly interpolated, output normalised by 65535.
|
|
30902
|
+
* - `para` (ICC.1:2010 §10.18): a u16 function type + s15Fixed16 parameters.
|
|
30903
|
+
* Implements function types 0–4 from the spec.
|
|
30904
|
+
*
|
|
30905
|
+
* @throws if the tag type is neither `curv` nor `para`, or is truncated.
|
|
30906
|
+
*/
|
|
30907
|
+
function evalTrc(data, tag, x) {
|
|
30908
|
+
const start = tag.offset;
|
|
30909
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
30910
|
+
const type = readSig(data, start);
|
|
30911
|
+
const xi = x < 0 ? 0 : x > 1 ? 1 : x;
|
|
30912
|
+
if (type === "curv") {
|
|
30913
|
+
const count = view.getUint32(start + 8, false);
|
|
30914
|
+
if (count === 0) return xi;
|
|
30915
|
+
if (count === 1) return xi ** (view.getUint16(start + 12, false) / 256);
|
|
30916
|
+
const lutStart = start + 12;
|
|
30917
|
+
const pos = xi * (count - 1);
|
|
30918
|
+
const i0 = Math.floor(pos);
|
|
30919
|
+
const i1 = i0 >= count - 1 ? count - 1 : i0 + 1;
|
|
30920
|
+
const frac = pos - i0;
|
|
30921
|
+
const v0 = view.getUint16(lutStart + i0 * 2, false);
|
|
30922
|
+
return (v0 + (view.getUint16(lutStart + i1 * 2, false) - v0) * frac) / 65535;
|
|
30923
|
+
}
|
|
30924
|
+
if (type === "para") {
|
|
30925
|
+
const funcType = view.getUint16(start + 8, false);
|
|
30926
|
+
const p = (i) => readS15Fixed16(view, start + 12 + i * 4);
|
|
30927
|
+
const g = p(0);
|
|
30928
|
+
switch (funcType) {
|
|
30929
|
+
case 0: return xi ** g;
|
|
30930
|
+
case 1: {
|
|
30931
|
+
const a = p(1);
|
|
30932
|
+
const b = p(2);
|
|
30933
|
+
return a * xi + b >= 0 ? (a * xi + b) ** g : 0;
|
|
30934
|
+
}
|
|
30935
|
+
case 2: {
|
|
30936
|
+
const a = p(1);
|
|
30937
|
+
const b = p(2);
|
|
30938
|
+
const c = p(3);
|
|
30939
|
+
return (a * xi + b >= 0 ? (a * xi + b) ** g : 0) + c;
|
|
30940
|
+
}
|
|
30941
|
+
case 3: {
|
|
30942
|
+
const a = p(1);
|
|
30943
|
+
const b = p(2);
|
|
30944
|
+
const c = p(3);
|
|
30945
|
+
return xi >= p(4) ? (a * xi + b) ** g : c * xi;
|
|
30946
|
+
}
|
|
30947
|
+
case 4: {
|
|
30948
|
+
const a = p(1);
|
|
30949
|
+
const b = p(2);
|
|
30950
|
+
const c = p(3);
|
|
30951
|
+
const d = p(4);
|
|
30952
|
+
const e = p(5);
|
|
30953
|
+
const f = p(6);
|
|
30954
|
+
return xi >= d ? (a * xi + b) ** g + e : c * xi + f;
|
|
30955
|
+
}
|
|
30956
|
+
default: throw new Error(`Unsupported parametricCurveType function type ${funcType} (ICC.1:2010 §10.18 defines 0–4).`);
|
|
30957
|
+
}
|
|
30958
|
+
}
|
|
30959
|
+
throw new Error(`Unsupported TRC tag type '${type}': only 'curv' and 'para' are matrix/TRC curves (got a LUT/other type).`);
|
|
30960
|
+
}
|
|
30961
|
+
/** Read a 3-component colorant XYZ from an 'XYZ ' tag. */
|
|
30962
|
+
function readXyzTag(data, tag) {
|
|
30963
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
30964
|
+
const type = readSig(data, tag.offset);
|
|
30965
|
+
if (type !== "XYZ ") throw new Error(`Expected an 'XYZ ' colorant tag but found '${type}' (ICC.1:2010 §10.31).`);
|
|
30966
|
+
return [
|
|
30967
|
+
readS15Fixed16(view, tag.offset + 8),
|
|
30968
|
+
readS15Fixed16(view, tag.offset + 12),
|
|
30969
|
+
readS15Fixed16(view, tag.offset + 16)
|
|
30970
|
+
];
|
|
30971
|
+
}
|
|
30972
|
+
/**
|
|
30973
|
+
* Transform a device colour through a **matrix/TRC** ICC profile into the
|
|
30974
|
+
* D50-relative PCS XYZ.
|
|
30975
|
+
*
|
|
30976
|
+
* For RGB (`colorSpace === 'RGB '`): each channel is linearised through its
|
|
30977
|
+
* `rTRC`/`gTRC`/`bTRC` curve, then multiplied by the colorant matrix whose
|
|
30978
|
+
* columns are the `rXYZ`/`gXYZ`/`bXYZ` tags (ICC.1:2010 §E.1.1).
|
|
30979
|
+
*
|
|
30980
|
+
* For GRAY (`colorSpace === 'GRAY'`): the single value is linearised through
|
|
30981
|
+
* `kTRC` and scaled by the media white point (`wtpt`, defaulting to D50).
|
|
30982
|
+
*
|
|
30983
|
+
* The result is **D50-relative XYZ**, matching the ICC PCS reference
|
|
30984
|
+
* illuminant; feed it directly to {@link xyzToLab} (whose default white point
|
|
30985
|
+
* is D50).
|
|
30986
|
+
*
|
|
30987
|
+
* @param profile - Raw ICC profile bytes.
|
|
30988
|
+
* @param rgb - Device colour in `[0,1]`. For gray profiles only the first
|
|
30989
|
+
* component is used.
|
|
30990
|
+
* @returns PCS XYZ `[X, Y, Z]`, D50-relative.
|
|
30991
|
+
* @throws if the profile is **not** matrix/TRC (i.e. it is LUT-based —
|
|
30992
|
+
* `mft1`/`mft2`/`mAB `/`mBA `), or its colour space is unsupported.
|
|
30993
|
+
*
|
|
30994
|
+
* @example
|
|
30995
|
+
* ```ts
|
|
30996
|
+
* import { deviceRgbToXyz } from 'modern-pdf-lib';
|
|
30997
|
+
*
|
|
30998
|
+
* const xyz = deviceRgbToXyz(srgbProfileBytes, [1, 1, 1]);
|
|
30999
|
+
* // ~ [0.9642, 1.0, 0.8249] (D50 white)
|
|
31000
|
+
* ```
|
|
31001
|
+
*/
|
|
31002
|
+
function deviceRgbToXyz(profile, rgb) {
|
|
31003
|
+
const info = parseIccTransform(profile);
|
|
31004
|
+
const table = readTagTable(profile);
|
|
31005
|
+
if (info.colorSpace === "GRAY") {
|
|
31006
|
+
const kTrc = table.get("kTRC");
|
|
31007
|
+
if (!kTrc) throw new Error("Gray ICC profile lacks a 'kTRC' tag; not a matrix/TRC profile.");
|
|
31008
|
+
const lin = evalTrc(profile, kTrc, rgb[0]);
|
|
31009
|
+
const wtptRec = table.get("wtpt");
|
|
31010
|
+
const white = wtptRec ? readXyzTag(profile, wtptRec) : [
|
|
31011
|
+
D50_WHITE[0],
|
|
31012
|
+
D50_WHITE[1],
|
|
31013
|
+
D50_WHITE[2]
|
|
31014
|
+
];
|
|
31015
|
+
return [
|
|
31016
|
+
lin * white[0],
|
|
31017
|
+
lin * white[1],
|
|
31018
|
+
lin * white[2]
|
|
31019
|
+
];
|
|
31020
|
+
}
|
|
31021
|
+
if (info.colorSpace !== "RGB ") throw new Error(`deviceRgbToXyz supports 'RGB ' and 'GRAY' profiles; got '${info.colorSpace}'.`);
|
|
31022
|
+
if (!info.hasMatrixTrc) throw new Error("ICC profile is not matrix/TRC (LUT-based: mft1/mft2/mAB /mBA ); matrix/TRC transform cannot be applied. Use a CMM/LUT evaluator instead.");
|
|
31023
|
+
const rTrc = table.get("rTRC");
|
|
31024
|
+
const gTrc = table.get("gTRC");
|
|
31025
|
+
const bTrc = table.get("bTRC");
|
|
31026
|
+
const rLin = evalTrc(profile, rTrc, rgb[0]);
|
|
31027
|
+
const gLin = evalTrc(profile, gTrc, rgb[1]);
|
|
31028
|
+
const bLin = evalTrc(profile, bTrc, rgb[2]);
|
|
31029
|
+
const rCol = readXyzTag(profile, table.get("rXYZ"));
|
|
31030
|
+
const gCol = readXyzTag(profile, table.get("gXYZ"));
|
|
31031
|
+
const bCol = readXyzTag(profile, table.get("bXYZ"));
|
|
31032
|
+
return [
|
|
31033
|
+
rCol[0] * rLin + gCol[0] * gLin + bCol[0] * bLin,
|
|
31034
|
+
rCol[1] * rLin + gCol[1] * gLin + bCol[1] * bLin,
|
|
31035
|
+
rCol[2] * rLin + gCol[2] * gLin + bCol[2] * bLin
|
|
31036
|
+
];
|
|
31037
|
+
}
|
|
31038
|
+
/**
|
|
31039
|
+
* CIE L\*a\*b\* nonlinearity f(t) (CIE 15:2004 §8.2.1.1):
|
|
31040
|
+
*
|
|
31041
|
+
* ```
|
|
31042
|
+
* f(t) = t^(1/3) for t > δ³
|
|
31043
|
+
* f(t) = t / (3·δ²) + 4/29 for t <= δ³, δ = 6/29
|
|
31044
|
+
* ```
|
|
31045
|
+
*/
|
|
31046
|
+
function labF$1(t) {
|
|
31047
|
+
return t > LAB_DELTA_CUBED$1 ? Math.cbrt(t) : t * LAB_LINEAR_SLOPE + LAB_LINEAR_INTERCEPT;
|
|
31048
|
+
}
|
|
31049
|
+
/**
|
|
31050
|
+
* Convert PCS XYZ to CIE L\*a\*b\* (CIE 15:2004 §8.2.1 / ICC PCS):
|
|
31051
|
+
*
|
|
31052
|
+
* ```
|
|
31053
|
+
* L* = 116·f(Y/Yn) − 16
|
|
31054
|
+
* a* = 500·(f(X/Xn) − f(Y/Yn))
|
|
31055
|
+
* b* = 200·(f(Y/Yn) − f(Z/Zn))
|
|
31056
|
+
* ```
|
|
31057
|
+
*
|
|
31058
|
+
* with f as in {@link labF}. The default reference white is **D50**
|
|
31059
|
+
* `[0.9642, 1.0000, 0.8249]`, matching the ICC PCS (ICC.1:2010 §6.3.4.3), so
|
|
31060
|
+
* XYZ produced by {@link deviceRgbToXyz} maps straight through.
|
|
31061
|
+
*
|
|
31062
|
+
* @param xyz - XYZ tristimulus, relative to `whitePoint`.
|
|
31063
|
+
* @param whitePoint - Reference white `[Xn, Yn, Zn]`. Defaults to D50.
|
|
31064
|
+
* @returns `[L*, a*, b*]`.
|
|
31065
|
+
*
|
|
31066
|
+
* @example
|
|
31067
|
+
* ```ts
|
|
31068
|
+
* import { xyzToLab } from 'modern-pdf-lib';
|
|
31069
|
+
*
|
|
31070
|
+
* xyzToLab([0.9642, 1.0, 0.8249]); // ~ [100, 0, 0] (D50 white)
|
|
31071
|
+
* ```
|
|
31072
|
+
*/
|
|
31073
|
+
function xyzToLab(xyz, whitePoint = [
|
|
31074
|
+
D50_WHITE[0],
|
|
31075
|
+
D50_WHITE[1],
|
|
31076
|
+
D50_WHITE[2]
|
|
31077
|
+
]) {
|
|
31078
|
+
const fx = labF$1(xyz[0] / whitePoint[0]);
|
|
31079
|
+
const fy = labF$1(xyz[1] / whitePoint[1]);
|
|
31080
|
+
const fz = labF$1(xyz[2] / whitePoint[2]);
|
|
31081
|
+
return [
|
|
31082
|
+
116 * fy - 16,
|
|
31083
|
+
500 * (fx - fy),
|
|
31084
|
+
200 * (fy - fz)
|
|
31085
|
+
];
|
|
31086
|
+
}
|
|
31087
|
+
//#endregion
|
|
31088
|
+
//#region src/color/colorConvert.ts
|
|
31089
|
+
/**
|
|
31090
|
+
* @module color/colorConvert
|
|
31091
|
+
*
|
|
31092
|
+
* Pure **device** colour-space conversions — no ICC, no chromatic adaptation
|
|
31093
|
+
* beyond what each standard formula already bakes in. These are the textbook
|
|
31094
|
+
* conversions used for previewing, picking and authoring colour; they are not
|
|
31095
|
+
* a substitute for ICC-based colour management.
|
|
31096
|
+
*
|
|
31097
|
+
* All channels are normalised to `0..1`, with two exceptions that follow the
|
|
31098
|
+
* natural ranges of their spaces:
|
|
31099
|
+
* - **Hue** is in degrees `[0, 360)`.
|
|
31100
|
+
* - **CIE L\*a\*b\*** uses its native ranges: `L ∈ [0, 100]`, `a`/`b`
|
|
31101
|
+
* unbounded (typically `[-128, 127]`).
|
|
31102
|
+
*
|
|
31103
|
+
* ## Verified reference constants
|
|
31104
|
+
*
|
|
31105
|
+
* **sRGB transfer function** — IEC 61966-2-1:1999 (sRGB). The companded↔linear
|
|
31106
|
+
* piecewise curve:
|
|
31107
|
+
* - expand (companded→linear): `c ≤ 0.04045 → c/12.92`,
|
|
31108
|
+
* else `((c + 0.055)/1.055)^2.4`.
|
|
31109
|
+
* - compress (linear→companded): `c ≤ 0.0031308 → 12.92·c`,
|
|
31110
|
+
* else `1.055·c^(1/2.4) − 0.055`.
|
|
31111
|
+
*
|
|
31112
|
+
* **sRGB ↔ XYZ (D65) matrices** — IEC 61966-2-1, as tabulated by Bruce
|
|
31113
|
+
* Lindbloom (sRGB, reference white D65). Forward (linear sRGB → XYZ):
|
|
31114
|
+
* ```
|
|
31115
|
+
* X = 0.4124564 R + 0.3575761 G + 0.1804375 B
|
|
31116
|
+
* Y = 0.2126729 R + 0.7151522 G + 0.0721750 B
|
|
31117
|
+
* Z = 0.0193339 R + 0.1191920 G + 0.9503041 B
|
|
31118
|
+
* ```
|
|
31119
|
+
* Inverse (XYZ → linear sRGB):
|
|
31120
|
+
* ```
|
|
31121
|
+
* R = 3.2404542 X − 1.5371385 Y − 0.4985314 Z
|
|
31122
|
+
* G = −0.9692660 X + 1.8760108 Y + 0.0415560 Z
|
|
31123
|
+
* B = 0.0556434 X − 0.2040259 Y + 1.0572252 Z
|
|
31124
|
+
* ```
|
|
31125
|
+
*
|
|
31126
|
+
* **D65 reference white** — CIE 1931 2° observer, normalised to `Y = 1`:
|
|
31127
|
+
* `Xn = 0.95047, Yn = 1.00000, Zn = 1.08883`.
|
|
31128
|
+
*
|
|
31129
|
+
* **CIE L\*a\*b\*** — CIE 15 colorimetry, using the rational `δ = 6/29`
|
|
31130
|
+
* formulation (numerically identical to the `0.008856 / 903.3` form but
|
|
31131
|
+
* continuous at the join):
|
|
31132
|
+
* ```
|
|
31133
|
+
* f(t) = t^(1/3) if t > δ³
|
|
31134
|
+
* = t/(3δ²) + 4/29 otherwise
|
|
31135
|
+
* L = 116·f(Y/Yn) − 16
|
|
31136
|
+
* a = 500·(f(X/Xn) − f(Y/Yn))
|
|
31137
|
+
* b = 200·(f(Y/Yn) − f(Z/Zn))
|
|
31138
|
+
* ```
|
|
31139
|
+
* with the inverse `f⁻¹(t) = t³` if `t > δ` else `3δ²·(t − 4/29)`.
|
|
31140
|
+
*
|
|
31141
|
+
* > **Note on Lab white point.** This module's `rgbToLab` / `labToRgb` use the
|
|
31142
|
+
* > **D65** PCS that matches the sRGB→XYZ pipeline, per this module's spec.
|
|
31143
|
+
* > That differs from {@link module:core/colorSpacesCIE}'s `labToRgb`, which
|
|
31144
|
+
* > targets the **D50** ICC profile-connection-space convention. The two are
|
|
31145
|
+
* > intentionally distinct and should not be confused.
|
|
31146
|
+
*
|
|
31147
|
+
* No `Buffer` — plain `number` arithmetic and tuples only.
|
|
31148
|
+
*/
|
|
31149
|
+
/**
|
|
31150
|
+
* CIE 1931 2° D65 reference white, normalised so `Yn = 1`.
|
|
31151
|
+
* (IEC 61966-2-1 / CIE 15.)
|
|
31152
|
+
*/
|
|
31153
|
+
const D65_WHITE = [
|
|
31154
|
+
.95047,
|
|
31155
|
+
1,
|
|
31156
|
+
1.08883
|
|
31157
|
+
];
|
|
31158
|
+
/** CIE Lab constant `δ = 6/29`. */
|
|
31159
|
+
const LAB_DELTA = 6 / 29;
|
|
31160
|
+
/** `δ³` — the linear/cubic break point for the forward nonlinearity `f(t)`. */
|
|
31161
|
+
const LAB_DELTA_CUBED = LAB_DELTA * LAB_DELTA * LAB_DELTA;
|
|
31162
|
+
/** `3δ²` — slope of the linear branch. */
|
|
31163
|
+
const LAB_3_DELTA_SQUARED = 3 * LAB_DELTA * LAB_DELTA;
|
|
31164
|
+
/** Clamp `x` to the closed interval `[lo, hi]`. */
|
|
31165
|
+
function clamp(x, lo, hi) {
|
|
31166
|
+
return x < lo ? lo : x > hi ? hi : x;
|
|
31167
|
+
}
|
|
31168
|
+
/** Normalise a hue in degrees into `[0, 360)`. */
|
|
31169
|
+
function normalizeHue(h) {
|
|
31170
|
+
const m = h % 360;
|
|
31171
|
+
return m < 0 ? m + 360 : m;
|
|
31172
|
+
}
|
|
31173
|
+
/**
|
|
31174
|
+
* Convert device RGB to HSL.
|
|
31175
|
+
*
|
|
31176
|
+
* @param r - Red, 0..1.
|
|
31177
|
+
* @param g - Green, 0..1.
|
|
31178
|
+
* @param b - Blue, 0..1.
|
|
31179
|
+
* @returns `[h, s, l]` with `h` in `[0, 360)`, `s`/`l` in `0..1`.
|
|
31180
|
+
*/
|
|
31181
|
+
function rgbToHsl(r, g, b) {
|
|
31182
|
+
const max = Math.max(r, g, b);
|
|
31183
|
+
const min = Math.min(r, g, b);
|
|
31184
|
+
const delta = max - min;
|
|
31185
|
+
const l = (max + min) / 2;
|
|
31186
|
+
if (delta === 0) return [
|
|
31187
|
+
0,
|
|
31188
|
+
0,
|
|
31189
|
+
l
|
|
31190
|
+
];
|
|
31191
|
+
const s = delta / (1 - Math.abs(2 * l - 1));
|
|
31192
|
+
let h;
|
|
31193
|
+
if (max === r) h = (g - b) / delta % 6;
|
|
31194
|
+
else if (max === g) h = (b - r) / delta + 2;
|
|
31195
|
+
else h = (r - g) / delta + 4;
|
|
31196
|
+
h *= 60;
|
|
31197
|
+
return [
|
|
31198
|
+
normalizeHue(h),
|
|
31199
|
+
s,
|
|
31200
|
+
l
|
|
31201
|
+
];
|
|
31202
|
+
}
|
|
31203
|
+
/**
|
|
31204
|
+
* Convert HSL to device RGB.
|
|
31205
|
+
*
|
|
31206
|
+
* @param h - Hue in degrees (any real; wrapped into `[0, 360)`).
|
|
31207
|
+
* @param s - Saturation, 0..1.
|
|
31208
|
+
* @param l - Lightness, 0..1.
|
|
31209
|
+
* @returns `[r, g, b]`, each 0..1.
|
|
31210
|
+
*/
|
|
31211
|
+
function hslToRgb(h, s, l) {
|
|
31212
|
+
const hue = normalizeHue(h);
|
|
31213
|
+
const c = (1 - Math.abs(2 * l - 1)) * s;
|
|
31214
|
+
const x = c * (1 - Math.abs(hue / 60 % 2 - 1));
|
|
31215
|
+
const mAdd = l - c / 2;
|
|
31216
|
+
let r1 = 0;
|
|
31217
|
+
let g1 = 0;
|
|
31218
|
+
let b1 = 0;
|
|
31219
|
+
if (hue < 60) {
|
|
31220
|
+
r1 = c;
|
|
31221
|
+
g1 = x;
|
|
31222
|
+
} else if (hue < 120) {
|
|
31223
|
+
r1 = x;
|
|
31224
|
+
g1 = c;
|
|
31225
|
+
} else if (hue < 180) {
|
|
31226
|
+
g1 = c;
|
|
31227
|
+
b1 = x;
|
|
31228
|
+
} else if (hue < 240) {
|
|
31229
|
+
g1 = x;
|
|
31230
|
+
b1 = c;
|
|
31231
|
+
} else if (hue < 300) {
|
|
31232
|
+
r1 = x;
|
|
31233
|
+
b1 = c;
|
|
31234
|
+
} else {
|
|
31235
|
+
r1 = c;
|
|
31236
|
+
b1 = x;
|
|
31237
|
+
}
|
|
31238
|
+
return [
|
|
31239
|
+
r1 + mAdd,
|
|
31240
|
+
g1 + mAdd,
|
|
31241
|
+
b1 + mAdd
|
|
31242
|
+
];
|
|
31243
|
+
}
|
|
31244
|
+
/**
|
|
31245
|
+
* Convert device RGB to HSV.
|
|
31246
|
+
*
|
|
31247
|
+
* @param r - Red, 0..1.
|
|
31248
|
+
* @param g - Green, 0..1.
|
|
31249
|
+
* @param b - Blue, 0..1.
|
|
31250
|
+
* @returns `[h, s, v]` with `h` in `[0, 360)`, `s`/`v` in `0..1`.
|
|
31251
|
+
*/
|
|
31252
|
+
function rgbToHsv(r, g, b) {
|
|
31253
|
+
const max = Math.max(r, g, b);
|
|
31254
|
+
const delta = max - Math.min(r, g, b);
|
|
31255
|
+
const v = max;
|
|
31256
|
+
if (delta === 0) return [
|
|
31257
|
+
0,
|
|
31258
|
+
0,
|
|
31259
|
+
v
|
|
31260
|
+
];
|
|
31261
|
+
const s = max === 0 ? 0 : delta / max;
|
|
31262
|
+
let h;
|
|
31263
|
+
if (max === r) h = (g - b) / delta % 6;
|
|
31264
|
+
else if (max === g) h = (b - r) / delta + 2;
|
|
31265
|
+
else h = (r - g) / delta + 4;
|
|
31266
|
+
h *= 60;
|
|
31267
|
+
return [
|
|
31268
|
+
normalizeHue(h),
|
|
31269
|
+
s,
|
|
31270
|
+
v
|
|
31271
|
+
];
|
|
31272
|
+
}
|
|
31273
|
+
/**
|
|
31274
|
+
* Convert HSV to device RGB.
|
|
31275
|
+
*
|
|
31276
|
+
* @param h - Hue in degrees (any real; wrapped into `[0, 360)`).
|
|
31277
|
+
* @param s - Saturation, 0..1.
|
|
31278
|
+
* @param v - Value, 0..1.
|
|
31279
|
+
* @returns `[r, g, b]`, each 0..1.
|
|
31280
|
+
*/
|
|
31281
|
+
function hsvToRgb(h, s, v) {
|
|
31282
|
+
const hue = normalizeHue(h);
|
|
31283
|
+
const c = v * s;
|
|
31284
|
+
const x = c * (1 - Math.abs(hue / 60 % 2 - 1));
|
|
31285
|
+
const mAdd = v - c;
|
|
31286
|
+
let r1 = 0;
|
|
31287
|
+
let g1 = 0;
|
|
31288
|
+
let b1 = 0;
|
|
31289
|
+
if (hue < 60) {
|
|
31290
|
+
r1 = c;
|
|
31291
|
+
g1 = x;
|
|
31292
|
+
} else if (hue < 120) {
|
|
31293
|
+
r1 = x;
|
|
31294
|
+
g1 = c;
|
|
31295
|
+
} else if (hue < 180) {
|
|
31296
|
+
g1 = c;
|
|
31297
|
+
b1 = x;
|
|
31298
|
+
} else if (hue < 240) {
|
|
31299
|
+
g1 = x;
|
|
31300
|
+
b1 = c;
|
|
31301
|
+
} else if (hue < 300) {
|
|
31302
|
+
r1 = x;
|
|
31303
|
+
b1 = c;
|
|
31304
|
+
} else {
|
|
31305
|
+
r1 = c;
|
|
31306
|
+
b1 = x;
|
|
31307
|
+
}
|
|
31308
|
+
return [
|
|
31309
|
+
r1 + mAdd,
|
|
31310
|
+
g1 + mAdd,
|
|
31311
|
+
b1 + mAdd
|
|
31312
|
+
];
|
|
31313
|
+
}
|
|
31314
|
+
/** Gamma-expand a companded sRGB channel to linear (IEC 61966-2-1). */
|
|
31315
|
+
function srgbToLinear(c) {
|
|
31316
|
+
return c <= .04045 ? c / 12.92 : ((c + .055) / 1.055) ** 2.4;
|
|
31317
|
+
}
|
|
31318
|
+
/** Gamma-compress a linear channel to companded sRGB (IEC 61966-2-1). */
|
|
31319
|
+
function linearToSrgb(c) {
|
|
31320
|
+
return c <= .0031308 ? 12.92 * c : 1.055 * c ** (1 / 2.4) - .055;
|
|
31321
|
+
}
|
|
31322
|
+
/**
|
|
31323
|
+
* Convert device (sRGB) RGB to CIE XYZ relative to the D65 white point.
|
|
31324
|
+
*
|
|
31325
|
+
* Gamma-expands each channel, then applies the IEC 61966-2-1 sRGB→XYZ D65
|
|
31326
|
+
* matrix. The returned `Y` is `1.0` for input white `(1, 1, 1)`.
|
|
31327
|
+
*
|
|
31328
|
+
* @param r - Red, 0..1.
|
|
31329
|
+
* @param g - Green, 0..1.
|
|
31330
|
+
* @param b - Blue, 0..1.
|
|
31331
|
+
* @returns `[X, Y, Z]` (D65, `Y = 1` for white).
|
|
31332
|
+
*/
|
|
31333
|
+
function rgbToXyz(r, g, b) {
|
|
31334
|
+
const rl = srgbToLinear(r);
|
|
31335
|
+
const gl = srgbToLinear(g);
|
|
31336
|
+
const bl = srgbToLinear(b);
|
|
31337
|
+
return [
|
|
31338
|
+
.4124564 * rl + .3575761 * gl + .1804375 * bl,
|
|
31339
|
+
.2126729 * rl + .7151522 * gl + .072175 * bl,
|
|
31340
|
+
.0193339 * rl + .119192 * gl + .9503041 * bl
|
|
31341
|
+
];
|
|
31342
|
+
}
|
|
31343
|
+
/**
|
|
31344
|
+
* Convert CIE XYZ (D65) to device (sRGB) RGB.
|
|
31345
|
+
*
|
|
31346
|
+
* Applies the inverse sRGB matrix, then gamma-companding. Results are clamped
|
|
31347
|
+
* to `0..1` to keep them in the displayable device gamut.
|
|
31348
|
+
*
|
|
31349
|
+
* @param x - CIE X (D65).
|
|
31350
|
+
* @param y - CIE Y (D65).
|
|
31351
|
+
* @param z - CIE Z (D65).
|
|
31352
|
+
* @returns `[r, g, b]`, each clamped to 0..1.
|
|
31353
|
+
*/
|
|
31354
|
+
function xyzToRgb(x, y, z) {
|
|
31355
|
+
const rl = 3.2404542 * x - 1.5371385 * y - .4985314 * z;
|
|
31356
|
+
const gl = -.969266 * x + 1.8760108 * y + .041556 * z;
|
|
31357
|
+
const bl = .0556434 * x - .2040259 * y + 1.0572252 * z;
|
|
31358
|
+
return [
|
|
31359
|
+
clamp(linearToSrgb(rl), 0, 1),
|
|
31360
|
+
clamp(linearToSrgb(gl), 0, 1),
|
|
31361
|
+
clamp(linearToSrgb(bl), 0, 1)
|
|
31362
|
+
];
|
|
31363
|
+
}
|
|
31364
|
+
/** Forward CIE Lab nonlinearity `f(t)`. */
|
|
31365
|
+
function labF(t) {
|
|
31366
|
+
return t > LAB_DELTA_CUBED ? Math.cbrt(t) : t / LAB_3_DELTA_SQUARED + 4 / 29;
|
|
31367
|
+
}
|
|
31368
|
+
/**
|
|
31369
|
+
* Convert device (sRGB) RGB to CIE L\*a\*b\* via XYZ, using the D65 white
|
|
31370
|
+
* point.
|
|
31371
|
+
*
|
|
31372
|
+
* @param r - Red, 0..1.
|
|
31373
|
+
* @param g - Green, 0..1.
|
|
31374
|
+
* @param b - Blue, 0..1.
|
|
31375
|
+
* @returns `[L, a, b]` with `L ∈ [0, 100]`, `a`/`b` in their natural ranges.
|
|
31376
|
+
*/
|
|
31377
|
+
function rgbToLab(r, g, b) {
|
|
31378
|
+
const [x, y, z] = rgbToXyz(r, g, b);
|
|
31379
|
+
const fx = labF(x / D65_WHITE[0]);
|
|
31380
|
+
const fy = labF(y / D65_WHITE[1]);
|
|
31381
|
+
const fz = labF(z / D65_WHITE[2]);
|
|
31382
|
+
return [
|
|
31383
|
+
116 * fy - 16,
|
|
31384
|
+
500 * (fx - fy),
|
|
31385
|
+
200 * (fy - fz)
|
|
31386
|
+
];
|
|
31387
|
+
}
|
|
31388
|
+
//#endregion
|
|
29119
31389
|
//#region src/render/matrix.ts
|
|
29120
31390
|
/** The identity transform. */
|
|
29121
31391
|
function identity() {
|
|
@@ -32943,6 +35213,6 @@ async function initWasm(options) {
|
|
|
32943
35213
|
wasmInitialized = true;
|
|
32944
35214
|
}
|
|
32945
35215
|
//#endregion
|
|
32946
|
-
export {
|
|
35216
|
+
export { hslToRgb as $, getFieldLocks as $a, encodePngFromPixels as $i, buildCollection as $n, PluginError as $r, extractCrlUrls as $t, buildSoftMaskNone as A, getToUnicodeCmap as Aa, pdf417ToOperators as Ai, generateOrderX as An, PdfFreeTextAnnotation as Ao, batchFlatten as Ar, validateKeyUsage as At, redactRegions as B, isLinearized as Ba, encodeEan8 as Bi, buildBoxDict as Bn, PdfLinkAnnotation as Bo, BatchProcessingError as Br, downscale16To8 as Bt, tagTableHeaderCell as C, validateXmpMetadata as Ca, readCode128 as Ci, buildVtDpm as Cn, PdfPolyLineAnnotation as Co, accessibilityPlugin as Cr, scanPdfThreats as Ct, buildImageSoftMask as D, generateSymbolToUnicodeCmap as Da, calculateBarcodeDimensions as Di, createRangeFetcher as Dn, PdfSquigglyAnnotation as Do, PdfDocumentBuilder as Dr, EKU_OIDS as Dt, buildColorKeyMask as E, isValidLevel as Ea, readEan8 as Ei, renderToPdf as En, PdfHighlightAnnotation as Eo, StreamingPdfParser as Er, verifyOfflineRevocation as Et, attachAssociatedFiles as F, flattenTransparency as Fa, upcAToOperators as Fi, sampleShadingColor as Fn, generateLineAppearance as Fo, removeAllBookmarks as Fr, layoutParagraph as Ft, extractImages as G, buildDssDictionary as Ga, encodeCode39 as Gi, buildSampledTransferFunction as Gn, PDFOperator as Go, FieldExistsAsNonTerminalError as Gr, upscale8To16 as Gt, compareImages as H, computeObjectHash as Ha, itfToOperators as Hi, buildPdfX6OutputIntent as Hn, buildXmpMetadata as Ho, EncryptedPdfError as Hr, normalizeComponentDepth as Ht, registerEmbeddedFile as I, enforcePdfA as Ia, calculateEanCheckDigit as Ii, didYouMean as In, generateSquareAppearance as Io, removeBookmark as Ir, layoutTextFlow as It, renderPageToCanvas as J, addCounterSignature as Ja, encodeCode128Values as Ji, buildType5Halftone as Jn, ChangeTracker as Jo, InvalidColorError as Jr, decodeTileRegion as Jt, generateThumbnail as K, embedLtvData as Ka, code128ToOperators as Ki, buildThresholdHalftone as Kn, buildDeviceNColorSpace as Ko, FontNotEmbeddedError as Kr, assembleTiles as Kt, RenderCache as L, validatePdfA as La, ean13ToOperators as Li, levenshtein as Ln, generateSquigglyAppearance as Lo, flattenField as Lr, buildPdfXOutputIntent as Lt, buildUnencryptedWrapper as M, SRGB_ICC_PROFILE as Ma, encodeDataMatrix as Mi, buildPdfA4Xmp as Mn, generateFreeTextAppearance as Mo, processBatch as Mr, validateCertificateChain as Mt, attachOutputIntents as N, generateSrgbIccProfile as Na, calculateUpcCheckDigit as Ni, pdfA4Rules as Nn, generateHighlightAppearance as No, addBookmark as Nr, findHyphenationPoints as Nt, buildStencilMask as O, generateWinAnsiToUnicodeCmap as Oa, renderStyledBarcode as Oi, resolveFallback as On, PdfStrikeOutAnnotation as Oo, enforcePdfUa as Or, validateCertificatePolicy as Ot, buildPageOutputIntent as P, detectTransparency as Pa, encodeUpcA as Pi, buildFunctionShading as Pn, generateInkAppearance as Po, getBookmarks as Pr, layoutColumns as Pt, interpretPage as Q, buildFieldLockDict as Qa, embedTiffDirect as Qi, markdownToPdf as Qn, NoSuchFieldError as Qr, downloadCrl as Qt, computeTileGrid as R, delinearizePdf as Ra, ean8ToOperators as Ri, renderCodeFrame as Rn, generateStrikeOutAppearance as Ro, flattenFields as Rr, enforcePdfX as Rt, tagTableDataCell as S, parseXmpMetadata as Sa, readBarcode as Si, buildPdfVtDParts as Sn, PdfLineAnnotation as So, tableToJson as Sr, sanitizePdf as St, buildBlackPointCompensationExtGState as T, getSupportedLevels as Ta, readEan13 as Ti, h as Tn, PdfSquareAnnotation as To, timestampPlugin as Tr, extractEmbeddedRevocationData as Tt, comparePages as U, findChangedObjects as Ua, code39ToOperators as Ui, validateBoxGeometry as Un, createXmpStream as Uo, ExceededMaxLengthError as Ur, offsetSignedToUnsigned as Ut, applyOcr as V, linearizePdf as Va, encodeItf as Vi, buildGtsPdfxVersion as Vn, PdfTextAnnotation as Vo, CombedTextLayoutError as Vr, getComponentDepths as Vt, extractFonts as W, optimizeIncrementalSave as Wa, computeCode39CheckDigit as Wi, STANDARD_SPOT_FUNCTIONS as Wn, parseXmpMetadata$1 as Wo, FieldAlreadyExistsError as Wr, summarizeBitDepth as Wt, renderPageToImage as X, diffSignedContent as Xa, PdfWorker as Xi, nameHalftone as Xn, saveIncremental as Xo, InvalidPageSizeError as Xr, verifySignatureDetailed as Xt, rasterize as Y, getCounterSignatures as Ya, valuesToModules as Yi, identityTransferFunction as Yn, saveDocumentIncremental as Yo, InvalidFieldNamePartError as Yr, parseTileInfo as Yt, interpretContentStream as Z, addFieldLock as Za, canDirectEmbed as Zi, evaluateFunction as Zn, MissingOnValueCheckError as Zr, TrustStore as Zt, tagLink as _, countOccurrences as _a, applyTablePreset as _i, isWoff as _n, PdfPopupAnnotation as _o, buildRequirement as _r, resolveInstanceCoordinates as _t, assembleFacturX as a, isCmykTiff as aa, applyHeaderFooterToPage as ai, resolveFieldReference as an, validateSignatureChain as ao, buildCalRGB as ar, xyzToRgb as at, tagParagraph as b, createAssociatedFile as ba, professionalPreset as bi, signDeferred as bn, PdfStampAnnotation as bo, extractTables as br, inspectEncryption as bt, buildWtpdfIdentificationXmp as c, analyzeJpegMarkers as ca, toAlpha as ci, setFieldVisibility as cn, parseExistingTrailer as co, DEFAULT_SARIF_TOOL_NAME as cr, xyzToLab as ct, autoTagPage as d, optimizeImage as da, ellipsisText as di, AFDate_FormatEx as dn, buildTimestampRequest as do, toSarif as dr, buildLatticeFormGouraudShading as dt, recompressWebP as ea, RemovePageFromEmptyDocumentError as ei, isCertificateRevoked as en, detectModifications as eo, reconstructLines as er, hsvToRgb as et, buildPdfUa2Xmp as f, recompressImage as fa, estimateTextWidth as fi, formatDate as fn, parseTimestampResponse as fo, MATHML_NAMESPACE as fr, buildTensorPatchShading as ft, tagHeading as g, generatePdfAXmpBytes as ga, applyPreset as gi, decodeWoff as gn, PdfCaretAnnotation as go, buildPieceInfo as gr, parseVariableFont as gt, tagFigure as h, generatePdfAXmp as ha, wrapText$2 as hi, formatNumber$1 as hn, PdfFileAttachmentAnnotation as ho, buildNamespacesArray as hr, normalizeAxisCoordinate as ht, validateEn16931 as i, embedTiffCmyk as ia, applyHeaderFooter as ii, getFieldValue as in, setCertificationLevel as io, buildCalGray as ir, rgbToXyz as it, buildEncryptedPayload as j, buildOutputIntent as ja, dataMatrixToOperators as ji, generateXRechnungCii as jn, generateCircleAppearance as jo, batchMerge as jr, buildCertificateChain as jt, buildSoftMaskGroupExtGState as k, generateZapfDingbatsToUnicodeCmap as ka, encodePdf417 as ki, splitByScript as kn, PdfUnderlineAnnotation as ko, validatePdfUa as kr, validateExtendedKeyUsage as kt, convertPdfAConformanceXmp as l, downscaleImage as la, toRoman as li, validateFieldValue as ln, saveIncrementalWithSignaturePreservation as lo, SARIF_SCHEMA_URI as lr, buildCoonsPatchShading as lt, LIST_NUMBERING_KEY as m, enforcePdfAFull as ma, truncateText as mi, AFNumber_Format as mn, searchTextItems as mo, buildNamespace as mr, parseColorFont as mt, detectFacturXProfile as n, webpToPng as na, StreamingParseError as ni, extractOcspUrl as nn, buildDocMdpReference as no, DEFAULT_DOC_TIMESTAMP_CONTENTS_SIZE as nr, rgbToHsv as nt, buildFacturXXmp as o, extractJpegMetadata as oa, formatDate$1 as oi, setFieldValue as on, appendIncrementalUpdate as oo, buildLab as or, deviceRgbToXyz as ot, validatePdfUa2 as p, applyRedaction as pa, shrinkFontSize as pi, parseAcrobatDate as pn, requestTimestamp as po, PDF2_NAMESPACE as pr, getColorGlyphLayers as pt, renderDisplayListToCanvas as q, hasLtvData as qa, encodeCode128 as qi, buildType1Halftone as qn, buildSeparationColorSpace as qo, ForeignPageError as qr, decodeTile as qt, parseCiiXml as r, convertTiffCmykToRgb as ra, UnexpectedFieldTypeError as ri, createSandbox as rn, getCertificationLevel as ro, buildDocTimeStampDict as rr, rgbToLab as rt, buildPdfRIdentificationXmp as s, injectJpegMetadata as sa, replaceTemplateVariables as si, addVisibilityAction as sn, findExistingSignatures as so, labToRgb as sr, parseIccTransform as st, initWasm as t, webpToJpeg as ta, RichTextFieldReadError as ti, checkCertificateStatus as tn, MdpPermission as to, reconstructParagraphs as tr, rgbToHsl as tt, preflightPdfA as u, estimateJpegQuality as ua, applyOverflow as ui, AFSpecial_Format as un, validateByteRangeIntegrity as uo, toJsonReport as ur, buildFreeFormGouraudShading as ut, tagList as v, stripProhibitedFeatures as va, borderedPreset as vi, isWoff2 as vn, PdfRedactAnnotation as vo, buildRequirements as vr, reorderVisual as vt, tagTableRow as w, getProfile as wa, readCode39 as wi, gtsPdfVtVersion as wn, PdfPolygonAnnotation as wo, metadataPlugin as wr, buildCertPath as wt, tagTable as x, extractXmpMetadata as xa, stripedPreset as xi, createWorkerPool as xn, PdfCircleAnnotation as xo, tableToCsv as xr, verifyRedactions as xt, tagListItem as y, buildAfArray as ya, minimalPreset as yi, readWoffHeader as yn, PdfInkAnnotation as yo, buildDPartRoot as yr, resolveBidi as yt, renderPageTile as z, getLinearizationInfo as za, encodeEan13 as zi, generateCiiXml as zn, generateUnderlineAppearance as zo, flattenForm as zr, validatePdfX as zt };
|
|
32947
35217
|
|
|
32948
|
-
//# sourceMappingURL=src-
|
|
35218
|
+
//# sourceMappingURL=src-DGxzt-jG.mjs.map
|