pdfnative 1.2.0 → 1.4.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 +151 -39
- package/dist/index.cjs +4062 -776
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1107 -30
- package/dist/index.d.ts +1107 -30
- package/dist/index.js +4029 -777
- package/dist/index.js.map +1 -1
- package/dist/tools/build-emoji-font.js +1139 -0
- package/dist/worker/index.cjs +2084 -121
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.js +2084 -121
- package/dist/worker/index.js.map +1 -1
- package/fonts/noto-color-emoji-data.d.ts +28 -0
- package/fonts/noto-color-emoji-data.js +26 -0
- package/fonts/noto-ethiopic-data.d.ts +13 -0
- package/fonts/noto-ethiopic-data.js +64 -0
- package/fonts/noto-khmer-data.d.ts +13 -0
- package/fonts/noto-khmer-data.js +64 -0
- package/fonts/noto-myanmar-data.d.ts +13 -0
- package/fonts/noto-myanmar-data.js +64 -0
- package/fonts/noto-sinhala-data.d.ts +13 -0
- package/fonts/noto-sinhala-data.js +64 -0
- package/fonts/noto-telugu-data.d.ts +13 -0
- package/fonts/noto-telugu-data.js +64 -0
- package/fonts/noto-tibetan-data.d.ts +13 -0
- package/fonts/noto-tibetan-data.js +64 -0
- package/package.json +38 -7
package/dist/worker/index.cjs
CHANGED
|
@@ -200,6 +200,33 @@ var BENGALI_START = 2432;
|
|
|
200
200
|
var BENGALI_END = 2559;
|
|
201
201
|
var TAMIL_START = 2944;
|
|
202
202
|
var TAMIL_END = 3071;
|
|
203
|
+
var TELUGU_START = 3072;
|
|
204
|
+
var TELUGU_END = 3199;
|
|
205
|
+
var ETHIOPIC_START = 4608;
|
|
206
|
+
var ETHIOPIC_END = 4991;
|
|
207
|
+
var ETHIOPIC_SUPPLEMENT_START = 4992;
|
|
208
|
+
var ETHIOPIC_SUPPLEMENT_END = 5023;
|
|
209
|
+
var ETHIOPIC_EXTENDED_START = 11648;
|
|
210
|
+
var ETHIOPIC_EXTENDED_END = 11743;
|
|
211
|
+
var ETHIOPIC_EXTENDED_A_START = 43776;
|
|
212
|
+
var ETHIOPIC_EXTENDED_A_END = 43823;
|
|
213
|
+
var SINHALA_START = 3456;
|
|
214
|
+
var SINHALA_END = 3583;
|
|
215
|
+
var SINHALA_VIRAMA = 3530;
|
|
216
|
+
var TIBETAN_START = 3840;
|
|
217
|
+
var TIBETAN_END = 4095;
|
|
218
|
+
var KHMER_START = 6016;
|
|
219
|
+
var KHMER_END = 6143;
|
|
220
|
+
var KHMER_SYMBOLS_START = 6624;
|
|
221
|
+
var KHMER_SYMBOLS_END = 6655;
|
|
222
|
+
var KHMER_COENG = 6098;
|
|
223
|
+
var MYANMAR_START = 4096;
|
|
224
|
+
var MYANMAR_END = 4255;
|
|
225
|
+
var MYANMAR_EXTENDED_A_START = 43616;
|
|
226
|
+
var MYANMAR_EXTENDED_A_END = 43647;
|
|
227
|
+
var MYANMAR_EXTENDED_B_START = 43488;
|
|
228
|
+
var MYANMAR_EXTENDED_B_END = 43519;
|
|
229
|
+
var MYANMAR_VIRAMA = 4153;
|
|
203
230
|
var EMOJI_RANGES = [
|
|
204
231
|
[127744, 128511],
|
|
205
232
|
// Miscellaneous Symbols and Pictographs
|
|
@@ -230,6 +257,9 @@ var EMOJI_RANGES = [
|
|
|
230
257
|
];
|
|
231
258
|
var FITZPATRICK_START = 127995;
|
|
232
259
|
var FITZPATRICK_END = 127999;
|
|
260
|
+
var ZWJ = 8205;
|
|
261
|
+
var VS15 = 65038;
|
|
262
|
+
var VS16 = 65039;
|
|
233
263
|
function isArabicCodepoint(cp) {
|
|
234
264
|
return cp >= ARABIC_START && cp <= ARABIC_END || cp >= ARABIC_SUPPLEMENT_START && cp <= ARABIC_SUPPLEMENT_END || cp >= ARABIC_EXTENDED_A_START && cp <= ARABIC_EXTENDED_A_END || cp >= ARABIC_PRES_A_START && cp <= ARABIC_PRES_A_END || cp >= ARABIC_PRES_B_START && cp <= ARABIC_PRES_B_END;
|
|
235
265
|
}
|
|
@@ -242,6 +272,24 @@ function isBengaliCodepoint(cp) {
|
|
|
242
272
|
function isTamilCodepoint(cp) {
|
|
243
273
|
return cp >= TAMIL_START && cp <= TAMIL_END;
|
|
244
274
|
}
|
|
275
|
+
function isTeluguCodepoint(cp) {
|
|
276
|
+
return cp >= TELUGU_START && cp <= TELUGU_END;
|
|
277
|
+
}
|
|
278
|
+
function isEthiopicCodepoint(cp) {
|
|
279
|
+
return cp >= ETHIOPIC_START && cp <= ETHIOPIC_END || cp >= ETHIOPIC_SUPPLEMENT_START && cp <= ETHIOPIC_SUPPLEMENT_END || cp >= ETHIOPIC_EXTENDED_START && cp <= ETHIOPIC_EXTENDED_END || cp >= ETHIOPIC_EXTENDED_A_START && cp <= ETHIOPIC_EXTENDED_A_END;
|
|
280
|
+
}
|
|
281
|
+
function isSinhalaCodepoint(cp) {
|
|
282
|
+
return cp >= SINHALA_START && cp <= SINHALA_END;
|
|
283
|
+
}
|
|
284
|
+
function isTibetanCodepoint(cp) {
|
|
285
|
+
return cp >= TIBETAN_START && cp <= TIBETAN_END;
|
|
286
|
+
}
|
|
287
|
+
function isKhmerCodepoint(cp) {
|
|
288
|
+
return cp >= KHMER_START && cp <= KHMER_END || cp >= KHMER_SYMBOLS_START && cp <= KHMER_SYMBOLS_END;
|
|
289
|
+
}
|
|
290
|
+
function isMyanmarCodepoint(cp) {
|
|
291
|
+
return cp >= MYANMAR_START && cp <= MYANMAR_END || cp >= MYANMAR_EXTENDED_A_START && cp <= MYANMAR_EXTENDED_A_END || cp >= MYANMAR_EXTENDED_B_START && cp <= MYANMAR_EXTENDED_B_END;
|
|
292
|
+
}
|
|
245
293
|
function isDevanagariCodepoint(cp) {
|
|
246
294
|
return cp >= DEVANAGARI_START && cp <= DEVANAGARI_END || cp >= DEVANAGARI_EXT_START && cp <= DEVANAGARI_EXT_END;
|
|
247
295
|
}
|
|
@@ -252,6 +300,9 @@ function isEmojiCodepoint(cp) {
|
|
|
252
300
|
}
|
|
253
301
|
return false;
|
|
254
302
|
}
|
|
303
|
+
function isZeroWidthFormat(cp) {
|
|
304
|
+
return cp === ZWJ || cp === 8204 || cp === VS15 || cp === VS16 || cp >= FITZPATRICK_START && cp <= FITZPATRICK_END;
|
|
305
|
+
}
|
|
255
306
|
function containsArabic(text) {
|
|
256
307
|
for (let i = 0; i < text.length; ) {
|
|
257
308
|
const cp = text.codePointAt(i) ?? 0;
|
|
@@ -278,6 +329,36 @@ function containsTamil(str) {
|
|
|
278
329
|
}
|
|
279
330
|
return false;
|
|
280
331
|
}
|
|
332
|
+
function containsTelugu(str) {
|
|
333
|
+
for (let i = 0; i < str.length; i++) {
|
|
334
|
+
if (isTeluguCodepoint(str.charCodeAt(i))) return true;
|
|
335
|
+
}
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
function containsSinhala(str) {
|
|
339
|
+
for (let i = 0; i < str.length; i++) {
|
|
340
|
+
if (isSinhalaCodepoint(str.charCodeAt(i))) return true;
|
|
341
|
+
}
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
function containsTibetan(str) {
|
|
345
|
+
for (let i = 0; i < str.length; i++) {
|
|
346
|
+
if (isTibetanCodepoint(str.charCodeAt(i))) return true;
|
|
347
|
+
}
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
350
|
+
function containsKhmer(str) {
|
|
351
|
+
for (let i = 0; i < str.length; i++) {
|
|
352
|
+
if (isKhmerCodepoint(str.charCodeAt(i))) return true;
|
|
353
|
+
}
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
function containsMyanmar(str) {
|
|
357
|
+
for (let i = 0; i < str.length; i++) {
|
|
358
|
+
if (isMyanmarCodepoint(str.charCodeAt(i))) return true;
|
|
359
|
+
}
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
281
362
|
function containsDevanagari(str) {
|
|
282
363
|
for (let i = 0; i < str.length; i++) {
|
|
283
364
|
if (isDevanagariCodepoint(str.charCodeAt(i))) return true;
|
|
@@ -289,6 +370,12 @@ function containsDevanagari(str) {
|
|
|
289
370
|
function detectCharLang(cp) {
|
|
290
371
|
if (cp >= 880 && cp <= 1023 || cp >= 7936 && cp <= 8191) return "el";
|
|
291
372
|
if (cp >= 2304 && cp <= 2431 || cp >= 43232 && cp <= 43263) return "hi";
|
|
373
|
+
if (cp >= 3072 && cp <= 3199) return "te";
|
|
374
|
+
if (isSinhalaCodepoint(cp)) return "si";
|
|
375
|
+
if (isTibetanCodepoint(cp)) return "bo";
|
|
376
|
+
if (isKhmerCodepoint(cp)) return "km";
|
|
377
|
+
if (isMyanmarCodepoint(cp)) return "my";
|
|
378
|
+
if (isEthiopicCodepoint(cp)) return "am";
|
|
292
379
|
if (cp >= 3584 && cp <= 3711) return "th";
|
|
293
380
|
if (cp >= 12352 && cp <= 12543) return "ja";
|
|
294
381
|
if (cp >= 44032 && cp <= 55215 || cp >= 4352 && cp <= 4607 || cp >= 12592 && cp <= 12687) return "ko";
|
|
@@ -322,6 +409,10 @@ function splitTextByFont(str, fontEntries) {
|
|
|
322
409
|
i += charLen;
|
|
323
410
|
continue;
|
|
324
411
|
}
|
|
412
|
+
if (isZeroWidthFormat(normCp) && !fontEntries.some((fe) => fe.fontData.cmap[normCp])) {
|
|
413
|
+
i += charLen;
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
325
416
|
let newEntry = null;
|
|
326
417
|
const charLang = detectCharLang(normCp);
|
|
327
418
|
if (charLang) {
|
|
@@ -626,18 +717,26 @@ function normalizeBidiEmbeddings(text) {
|
|
|
626
717
|
for (let i = 0; i < text.length; ) {
|
|
627
718
|
const cp = text.codePointAt(i) ?? 0;
|
|
628
719
|
const cpLen = cp > 65535 ? 2 : 1;
|
|
629
|
-
if (cp === LRE || cp ===
|
|
720
|
+
if (cp === LRE || cp === RLE) {
|
|
630
721
|
if (stack.length >= MAX_DEPTH) {
|
|
631
722
|
i += cpLen;
|
|
632
723
|
continue;
|
|
633
724
|
}
|
|
634
|
-
stack.push(
|
|
635
|
-
out.push(cp === LRE
|
|
725
|
+
stack.push("E");
|
|
726
|
+
out.push(cp === LRE ? LRI : RLI);
|
|
636
727
|
i += cpLen;
|
|
637
|
-
} else if (cp ===
|
|
638
|
-
if (stack.
|
|
639
|
-
|
|
728
|
+
} else if (cp === LRO || cp === RLO) {
|
|
729
|
+
if (stack.length >= MAX_DEPTH) {
|
|
730
|
+
i += cpLen;
|
|
731
|
+
continue;
|
|
640
732
|
}
|
|
733
|
+
stack.push("O");
|
|
734
|
+
out.push(cp);
|
|
735
|
+
i += cpLen;
|
|
736
|
+
} else if (cp === PDF_CP) {
|
|
737
|
+
const frame = stack.pop();
|
|
738
|
+
if (frame === "E") out.push(PDI);
|
|
739
|
+
else if (frame === "O") out.push(PDF_CP);
|
|
641
740
|
i += cpLen;
|
|
642
741
|
} else {
|
|
643
742
|
out.push(cp);
|
|
@@ -648,8 +747,95 @@ function normalizeBidiEmbeddings(text) {
|
|
|
648
747
|
for (let i = 0; i < out.length; i++) result += String.fromCodePoint(out[i]);
|
|
649
748
|
return result;
|
|
650
749
|
}
|
|
750
|
+
function matchingPdfIndex(codePoints, openCp) {
|
|
751
|
+
let depth = 1;
|
|
752
|
+
for (let j = openCp + 1; j < codePoints.length; j++) {
|
|
753
|
+
const cj = codePoints[j];
|
|
754
|
+
if (cj === 8234 || cj === 8235 || cj === 8237 || cj === 8238) depth++;
|
|
755
|
+
else if (cj === 8236) {
|
|
756
|
+
depth--;
|
|
757
|
+
if (depth === 0) return j;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
return -1;
|
|
761
|
+
}
|
|
762
|
+
function matchingPdiIndex(codePoints, openCp) {
|
|
763
|
+
let depth = 1;
|
|
764
|
+
for (let j = openCp + 1; j < codePoints.length; j++) {
|
|
765
|
+
const cj = codePoints[j];
|
|
766
|
+
if (cj === 8294 || cj === 8295 || cj === 8296) depth++;
|
|
767
|
+
else if (cj === 8297) {
|
|
768
|
+
depth--;
|
|
769
|
+
if (depth === 0) return j;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
return -1;
|
|
773
|
+
}
|
|
774
|
+
function tryResolveOverrides(text, forcedLevel) {
|
|
775
|
+
const LRE = 8234, RLE = 8235, LRO = 8237, RLO = 8238;
|
|
776
|
+
if (text.indexOf("\u202D") === -1 && text.indexOf("\u202E") === -1) return null;
|
|
777
|
+
const codePoints = [];
|
|
778
|
+
const cpToStr = [];
|
|
779
|
+
for (let i2 = 0; i2 < text.length; ) {
|
|
780
|
+
cpToStr.push(i2);
|
|
781
|
+
const cp = text.codePointAt(i2) ?? 0;
|
|
782
|
+
codePoints.push(cp);
|
|
783
|
+
i2 += cp > 65535 ? 2 : 1;
|
|
784
|
+
}
|
|
785
|
+
cpToStr.push(text.length);
|
|
786
|
+
const len = codePoints.length;
|
|
787
|
+
const spans = [];
|
|
788
|
+
let i = 0;
|
|
789
|
+
while (i < len) {
|
|
790
|
+
const cp = codePoints[i];
|
|
791
|
+
if (cp === LRO || cp === RLO) {
|
|
792
|
+
const close = matchingPdfIndex(codePoints, i);
|
|
793
|
+
if (close === -1) {
|
|
794
|
+
i++;
|
|
795
|
+
continue;
|
|
796
|
+
}
|
|
797
|
+
spans.push({ open: i, close, rtl: cp === RLO });
|
|
798
|
+
i = close + 1;
|
|
799
|
+
} else if (cp === LRE || cp === RLE) {
|
|
800
|
+
const close = matchingPdfIndex(codePoints, i);
|
|
801
|
+
i = close === -1 ? i + 1 : close + 1;
|
|
802
|
+
} else if (cp === 8294 || cp === 8295 || cp === 8296) {
|
|
803
|
+
const close = matchingPdiIndex(codePoints, i);
|
|
804
|
+
i = close === -1 ? i + 1 : close + 1;
|
|
805
|
+
} else {
|
|
806
|
+
i++;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
if (spans.length === 0) return null;
|
|
810
|
+
const out = [];
|
|
811
|
+
const emitGap = (cpStart, cpEnd) => {
|
|
812
|
+
if (cpStart >= cpEnd) return;
|
|
813
|
+
const segText = text.substring(cpToStr[cpStart], cpToStr[cpEnd]);
|
|
814
|
+
const baseStrIdx = cpToStr[cpStart];
|
|
815
|
+
const segRuns = forcedLevel === void 0 ? resolveBidiRuns(segText) : resolveBidiRunsForced(segText, forcedLevel);
|
|
816
|
+
for (const r of segRuns) {
|
|
817
|
+
out.push({ text: r.text, level: r.level, start: r.start + baseStrIdx });
|
|
818
|
+
}
|
|
819
|
+
};
|
|
820
|
+
let cursor = 0;
|
|
821
|
+
for (const span of spans) {
|
|
822
|
+
emitGap(cursor, span.open);
|
|
823
|
+
const innerRaw = text.substring(cpToStr[span.open + 1], cpToStr[span.close]);
|
|
824
|
+
const inner = stripBidiControls(innerRaw);
|
|
825
|
+
if (inner) {
|
|
826
|
+
const level = span.rtl ? 1 : 0;
|
|
827
|
+
const runText = span.rtl ? reverseString(inner) : inner;
|
|
828
|
+
out.push({ text: runText, level, start: cpToStr[span.open + 1] });
|
|
829
|
+
}
|
|
830
|
+
cursor = span.close + 1;
|
|
831
|
+
}
|
|
832
|
+
emitGap(cursor, len);
|
|
833
|
+
return out;
|
|
834
|
+
}
|
|
651
835
|
function resolveBidiRuns(text) {
|
|
652
836
|
if (!text) return [];
|
|
837
|
+
const overrideRuns = tryResolveOverrides(text);
|
|
838
|
+
if (overrideRuns) return overrideRuns;
|
|
653
839
|
const normalized = normalizeBidiEmbeddings(text);
|
|
654
840
|
if (normalized !== text) {
|
|
655
841
|
return resolveBidiRuns(normalized);
|
|
@@ -712,6 +898,8 @@ function resolveBidiRuns(text) {
|
|
|
712
898
|
}
|
|
713
899
|
function resolveBidiRunsForced(text, forcedLevel) {
|
|
714
900
|
if (!text) return [];
|
|
901
|
+
const overrideRuns = tryResolveOverrides(text, forcedLevel);
|
|
902
|
+
if (overrideRuns) return overrideRuns;
|
|
715
903
|
const codePoints = [];
|
|
716
904
|
const cpToStr = [];
|
|
717
905
|
for (let i = 0; i < text.length; ) {
|
|
@@ -800,6 +988,7 @@ function resolveBidiCore(text, codePoints, cpToStr, forcedLevel) {
|
|
|
800
988
|
function containsRTL(text) {
|
|
801
989
|
for (let i = 0; i < text.length; ) {
|
|
802
990
|
const cp = text.codePointAt(i) ?? 0;
|
|
991
|
+
if (cp === 8237 || cp === 8238) return true;
|
|
803
992
|
const t = classifyBidiType(cp);
|
|
804
993
|
if (t === "R" || t === "AL") return true;
|
|
805
994
|
i += cp > 65535 ? 2 : 1;
|
|
@@ -881,6 +1070,57 @@ function pdfString(str) {
|
|
|
881
1070
|
const s = toWinAnsi(stripBidiControls(str));
|
|
882
1071
|
return "(" + s.replace(/\\/g, "\\\\").replace(/\(/g, "\\(").replace(/\)/g, "\\)") + ")";
|
|
883
1072
|
}
|
|
1073
|
+
var WINANSI_HIGH_TO_UNICODE = {
|
|
1074
|
+
128: 8364,
|
|
1075
|
+
130: 8218,
|
|
1076
|
+
131: 402,
|
|
1077
|
+
132: 8222,
|
|
1078
|
+
133: 8230,
|
|
1079
|
+
134: 8224,
|
|
1080
|
+
135: 8225,
|
|
1081
|
+
136: 710,
|
|
1082
|
+
137: 8240,
|
|
1083
|
+
138: 352,
|
|
1084
|
+
139: 8249,
|
|
1085
|
+
140: 338,
|
|
1086
|
+
142: 381,
|
|
1087
|
+
145: 8216,
|
|
1088
|
+
146: 8217,
|
|
1089
|
+
147: 8220,
|
|
1090
|
+
148: 8221,
|
|
1091
|
+
149: 8226,
|
|
1092
|
+
150: 8211,
|
|
1093
|
+
151: 8212,
|
|
1094
|
+
152: 732,
|
|
1095
|
+
153: 8482,
|
|
1096
|
+
154: 353,
|
|
1097
|
+
155: 8250,
|
|
1098
|
+
156: 339,
|
|
1099
|
+
158: 382,
|
|
1100
|
+
159: 376
|
|
1101
|
+
};
|
|
1102
|
+
function buildWinAnsiToUnicodeCMap() {
|
|
1103
|
+
const entries = [];
|
|
1104
|
+
const hex2 = (n2) => n2.toString(16).toUpperCase().padStart(2, "0");
|
|
1105
|
+
const hex4 = (n2) => n2.toString(16).toUpperCase().padStart(4, "0");
|
|
1106
|
+
for (let b = 32; b <= 255; b++) {
|
|
1107
|
+
if (b >= 127 && b <= 159) {
|
|
1108
|
+
const u = WINANSI_HIGH_TO_UNICODE[b];
|
|
1109
|
+
if (u === void 0) continue;
|
|
1110
|
+
entries.push(`<${hex2(b)}> <${hex4(u)}>`);
|
|
1111
|
+
} else {
|
|
1112
|
+
entries.push(`<${hex2(b)}> <${hex4(b)}>`);
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
const blocks = [];
|
|
1116
|
+
for (let i = 0; i < entries.length; i += 100) {
|
|
1117
|
+
const chunk = entries.slice(i, i + 100);
|
|
1118
|
+
blocks.push(`${chunk.length} beginbfchar
|
|
1119
|
+
${chunk.join("\n")}
|
|
1120
|
+
endbfchar`);
|
|
1121
|
+
}
|
|
1122
|
+
return "/CIDInit /ProcSet findresource begin\n12 dict begin\nbegincmap\n/CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def\n/CMapName /Adobe-Identity-UCS def\n/CMapType 2 def\n1 begincodespacerange\n<20> <FF>\nendcodespacerange\n" + blocks.join("\n") + "\nendcmap\nCMapName currentdict /CMap defineresource pop\nend\nend";
|
|
1123
|
+
}
|
|
884
1124
|
function truncate(str, max) {
|
|
885
1125
|
if (!str || str.length <= max) return str || "";
|
|
886
1126
|
if (max <= 1) return "\u2026";
|
|
@@ -956,6 +1196,73 @@ function tryLigature(gids, ligatures) {
|
|
|
956
1196
|
return null;
|
|
957
1197
|
}
|
|
958
1198
|
|
|
1199
|
+
// src/shaping/use-lite.ts
|
|
1200
|
+
function devanagariCategory(cp) {
|
|
1201
|
+
if (cp === 2305 || cp === 2306) return "Mabv";
|
|
1202
|
+
if (cp === 2307) return "Mpst";
|
|
1203
|
+
if (cp >= 2308 && cp <= 2324) return "V";
|
|
1204
|
+
if (cp >= 2325 && cp <= 2361) return "B";
|
|
1205
|
+
if (cp === 2362) return "Mabv";
|
|
1206
|
+
if (cp === 2363) return "Mpst";
|
|
1207
|
+
if (cp === 2364) return "Mblw";
|
|
1208
|
+
if (cp === 2365) return "O";
|
|
1209
|
+
if (cp === 2366) return "Mpst";
|
|
1210
|
+
if (cp >= 2367 && cp <= 2368) return cp === 2367 ? "Mpre" : "Mpst";
|
|
1211
|
+
if (cp >= 2369 && cp <= 2376) return "Mblw";
|
|
1212
|
+
if (cp >= 2377 && cp <= 2380) return "Mpst";
|
|
1213
|
+
if (cp === 2381) return "H";
|
|
1214
|
+
if (cp >= 2382 && cp <= 2383) return "Mpre";
|
|
1215
|
+
if (cp === 2384) return "O";
|
|
1216
|
+
if (cp >= 2385 && cp <= 2391) return "Mabv";
|
|
1217
|
+
if (cp >= 2392 && cp <= 2401) return "B";
|
|
1218
|
+
if (cp >= 2402 && cp <= 2403) return "Mblw";
|
|
1219
|
+
if (cp >= 2406 && cp <= 2415) return "N";
|
|
1220
|
+
return "O";
|
|
1221
|
+
}
|
|
1222
|
+
function bengaliCategory(cp) {
|
|
1223
|
+
if (cp === 2433) return "Mabv";
|
|
1224
|
+
if (cp === 2434) return "Mpst";
|
|
1225
|
+
if (cp === 2435) return "Mpst";
|
|
1226
|
+
if (cp >= 2437 && cp <= 2452) return "V";
|
|
1227
|
+
if (cp >= 2453 && cp <= 2489) return "B";
|
|
1228
|
+
if (cp === 2492) return "Mblw";
|
|
1229
|
+
if (cp === 2493) return "O";
|
|
1230
|
+
if (cp === 2494) return "Mpst";
|
|
1231
|
+
if (cp >= 2495 && cp <= 2496) return cp === 2495 ? "Mpre" : "Mpst";
|
|
1232
|
+
if (cp >= 2497 && cp <= 2500) return "Mblw";
|
|
1233
|
+
if (cp >= 2503 && cp <= 2504) return "Mpre";
|
|
1234
|
+
if (cp >= 2507 && cp <= 2508) return "Mpre";
|
|
1235
|
+
if (cp === 2509) return "H";
|
|
1236
|
+
if (cp === 2510) return "B";
|
|
1237
|
+
if (cp === 2519) return "Mpst";
|
|
1238
|
+
if (cp >= 2524 && cp <= 2527) return "B";
|
|
1239
|
+
if (cp >= 2528 && cp <= 2531) return "O";
|
|
1240
|
+
if (cp >= 2534 && cp <= 2543) return "N";
|
|
1241
|
+
return "O";
|
|
1242
|
+
}
|
|
1243
|
+
function tamilCategory(cp) {
|
|
1244
|
+
if (cp === 2946) return "Mabv";
|
|
1245
|
+
if (cp >= 2949 && cp <= 2964) return "V";
|
|
1246
|
+
if (cp >= 2965 && cp <= 3001) return "B";
|
|
1247
|
+
if (cp === 3006) return "Mpst";
|
|
1248
|
+
if (cp === 3007) return "Mpst";
|
|
1249
|
+
if (cp >= 3008 && cp <= 3010) return "Mpst";
|
|
1250
|
+
if (cp >= 3014 && cp <= 3016) return "Mpre";
|
|
1251
|
+
if (cp >= 3018 && cp <= 3020) return "Mpre";
|
|
1252
|
+
if (cp === 3021) return "H";
|
|
1253
|
+
if (cp === 3031) return "Mpst";
|
|
1254
|
+
if (cp >= 3046 && cp <= 3055) return "N";
|
|
1255
|
+
return "O";
|
|
1256
|
+
}
|
|
1257
|
+
function classifyUseCategory(cp) {
|
|
1258
|
+
if (cp === 8204) return "ZWNJ";
|
|
1259
|
+
if (cp === 8205) return "ZWJ";
|
|
1260
|
+
if (cp >= 2304 && cp <= 2431) return devanagariCategory(cp);
|
|
1261
|
+
if (cp >= 2432 && cp <= 2559) return bengaliCategory(cp);
|
|
1262
|
+
if (cp >= 2944 && cp <= 3071) return tamilCategory(cp);
|
|
1263
|
+
return "O";
|
|
1264
|
+
}
|
|
1265
|
+
|
|
959
1266
|
// src/shaping/bengali-shaper.ts
|
|
960
1267
|
var HALANT = 2509;
|
|
961
1268
|
var NUKTA = 2492;
|
|
@@ -997,6 +1304,10 @@ function buildBengaliClusters(str) {
|
|
|
997
1304
|
while (i < cps.length) {
|
|
998
1305
|
const cp = cps[i];
|
|
999
1306
|
const type = bengaliCharType(cp);
|
|
1307
|
+
if (classifyUseCategory(cp) === "ZWJ" || classifyUseCategory(cp) === "ZWNJ") {
|
|
1308
|
+
i++;
|
|
1309
|
+
continue;
|
|
1310
|
+
}
|
|
1000
1311
|
if (type < 0 || cp < BENGALI_START || cp > BENGALI_END) {
|
|
1001
1312
|
clusters.push({ codepoints: [cp], baseIndex: 0, hasReph: false, preBaseMatras: [] });
|
|
1002
1313
|
i++;
|
|
@@ -1024,13 +1335,24 @@ function buildBengaliClusters(str) {
|
|
|
1024
1335
|
i++;
|
|
1025
1336
|
}
|
|
1026
1337
|
if (i < cps.length && cps[i] === HALANT) {
|
|
1027
|
-
|
|
1338
|
+
let j = i + 1;
|
|
1339
|
+
let zwnj = false;
|
|
1340
|
+
if (j < cps.length) {
|
|
1341
|
+
const jc = classifyUseCategory(cps[j]);
|
|
1342
|
+
if (jc === "ZWJ") {
|
|
1343
|
+
j++;
|
|
1344
|
+
} else if (jc === "ZWNJ") {
|
|
1345
|
+
j++;
|
|
1346
|
+
zwnj = true;
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
if (!zwnj && j < cps.length && isConsonant(cps[j])) {
|
|
1028
1350
|
syllable.push(cps[i]);
|
|
1029
|
-
i
|
|
1351
|
+
i = j;
|
|
1030
1352
|
continue;
|
|
1031
1353
|
} else {
|
|
1032
1354
|
syllable.push(cps[i]);
|
|
1033
|
-
i
|
|
1355
|
+
i = j;
|
|
1034
1356
|
break;
|
|
1035
1357
|
}
|
|
1036
1358
|
}
|
|
@@ -1267,6 +1589,10 @@ function buildTamilClusters(str) {
|
|
|
1267
1589
|
while (i < cps.length) {
|
|
1268
1590
|
const cp = cps[i];
|
|
1269
1591
|
const type = tamilCharType(cp);
|
|
1592
|
+
if (classifyUseCategory(cp) === "ZWJ" || classifyUseCategory(cp) === "ZWNJ") {
|
|
1593
|
+
i++;
|
|
1594
|
+
continue;
|
|
1595
|
+
}
|
|
1270
1596
|
if (type < 0 || cp < TAMIL_START || cp > TAMIL_END) {
|
|
1271
1597
|
clusters.push({ codepoints: [cp], baseIndex: 0, preBaseMatras: [] });
|
|
1272
1598
|
i++;
|
|
@@ -1283,13 +1609,24 @@ function buildTamilClusters(str) {
|
|
|
1283
1609
|
syllable.push(cc);
|
|
1284
1610
|
i++;
|
|
1285
1611
|
if (i < cps.length && cps[i] === PULLI) {
|
|
1286
|
-
|
|
1612
|
+
let j = i + 1;
|
|
1613
|
+
let zwnj = false;
|
|
1614
|
+
if (j < cps.length) {
|
|
1615
|
+
const jc = classifyUseCategory(cps[j]);
|
|
1616
|
+
if (jc === "ZWJ") {
|
|
1617
|
+
j++;
|
|
1618
|
+
} else if (jc === "ZWNJ") {
|
|
1619
|
+
j++;
|
|
1620
|
+
zwnj = true;
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
if (!zwnj && j < cps.length && isConsonant2(cps[j])) {
|
|
1287
1624
|
syllable.push(cps[i]);
|
|
1288
|
-
i
|
|
1625
|
+
i = j;
|
|
1289
1626
|
continue;
|
|
1290
1627
|
} else {
|
|
1291
1628
|
syllable.push(cps[i]);
|
|
1292
|
-
i
|
|
1629
|
+
i = j;
|
|
1293
1630
|
break;
|
|
1294
1631
|
}
|
|
1295
1632
|
}
|
|
@@ -1401,76 +1738,1084 @@ function shapeTamilText(str, fontData) {
|
|
|
1401
1738
|
}
|
|
1402
1739
|
}
|
|
1403
1740
|
}
|
|
1404
|
-
const clusterGids = [];
|
|
1405
|
-
const clusterEndIdx = [];
|
|
1406
|
-
let matraStart = codepoints.length;
|
|
1741
|
+
const clusterGids = [];
|
|
1742
|
+
const clusterEndIdx = [];
|
|
1743
|
+
let matraStart = codepoints.length;
|
|
1744
|
+
for (let ci = 0; ci < codepoints.length; ci++) {
|
|
1745
|
+
const ct = tamilCharType(codepoints[ci]);
|
|
1746
|
+
if (ct === 0 || ct === 7) {
|
|
1747
|
+
clusterGids.push(resolveGid(codepoints[ci]));
|
|
1748
|
+
clusterEndIdx.push(ci);
|
|
1749
|
+
} else if (ct >= 2) {
|
|
1750
|
+
matraStart = ci;
|
|
1751
|
+
break;
|
|
1752
|
+
} else if (ct < 0) {
|
|
1753
|
+
emitGlyph(resolveGid(codepoints[ci]), false);
|
|
1754
|
+
} else if (ct === 1) {
|
|
1755
|
+
emitGlyph(resolveGid(codepoints[ci]), false);
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
let ligConsumed = 0;
|
|
1759
|
+
const ligResult = tryLig(clusterGids);
|
|
1760
|
+
if (ligResult) {
|
|
1761
|
+
emitGlyph(ligResult.resultGid, false);
|
|
1762
|
+
baseGid = ligResult.resultGid;
|
|
1763
|
+
ligConsumed = ligResult.consumed;
|
|
1764
|
+
let gi = ligConsumed;
|
|
1765
|
+
while (gi < clusterGids.length) {
|
|
1766
|
+
const subSeq = clusterGids.slice(gi);
|
|
1767
|
+
const subLig = tryLig(subSeq);
|
|
1768
|
+
if (subLig) {
|
|
1769
|
+
emitGlyph(subLig.resultGid, false);
|
|
1770
|
+
gi += subLig.consumed;
|
|
1771
|
+
} else {
|
|
1772
|
+
const origCi = clusterEndIdx[gi];
|
|
1773
|
+
const ct = tamilCharType(codepoints[origCi]);
|
|
1774
|
+
if (ct === 7) {
|
|
1775
|
+
emitGlyph(clusterGids[gi], true, baseGid);
|
|
1776
|
+
} else {
|
|
1777
|
+
emitGlyph(clusterGids[gi], false);
|
|
1778
|
+
}
|
|
1779
|
+
gi++;
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
} else {
|
|
1783
|
+
for (let ci = 0; ci < matraStart; ci++) {
|
|
1784
|
+
const cp = codepoints[ci];
|
|
1785
|
+
const ct = tamilCharType(cp);
|
|
1786
|
+
if (ct === 0) {
|
|
1787
|
+
emitGlyph(resolveGid(cp), false);
|
|
1788
|
+
} else if (ct === 7) {
|
|
1789
|
+
emitGlyph(resolveGid(cp), true, baseGid);
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
for (let ci = matraStart; ci < codepoints.length; ci++) {
|
|
1794
|
+
const cp = codepoints[ci];
|
|
1795
|
+
const ct = tamilCharType(cp);
|
|
1796
|
+
if (ct === 4) continue;
|
|
1797
|
+
if (ct === 2 || ct === 3) {
|
|
1798
|
+
emitGlyph(resolveGid(cp), true, baseGid);
|
|
1799
|
+
} else if (ct === 5) {
|
|
1800
|
+
emitGlyph(resolveGid(cp), false);
|
|
1801
|
+
} else if (ct === 6) {
|
|
1802
|
+
emitGlyph(resolveGid(cp), true, baseGid);
|
|
1803
|
+
} else if (ct === 9) {
|
|
1804
|
+
emitGlyph(resolveGid(cp), false);
|
|
1805
|
+
} else {
|
|
1806
|
+
emitGlyph(resolveGid(cp), false);
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
for (const postCp of splitPostComponents) {
|
|
1810
|
+
emitGlyph(resolveGid(postCp), false);
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
return shaped;
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
// src/shaping/telugu-shaper.ts
|
|
1817
|
+
var VIRAMA = 3149;
|
|
1818
|
+
function teluguCharType(cp) {
|
|
1819
|
+
if (cp === VIRAMA) return 7;
|
|
1820
|
+
if (cp >= 3072 && cp <= 3076) return 6;
|
|
1821
|
+
if (cp >= 3077 && cp <= 3092) return 1;
|
|
1822
|
+
if (cp === 3133) return 1;
|
|
1823
|
+
if (cp >= 3093 && cp <= 3129) return 0;
|
|
1824
|
+
if (cp >= 3160 && cp <= 3162) return 0;
|
|
1825
|
+
if (cp === 3134) return 5;
|
|
1826
|
+
if (cp === 3135) return 2;
|
|
1827
|
+
if (cp === 3136) return 2;
|
|
1828
|
+
if (cp === 3137) return 5;
|
|
1829
|
+
if (cp === 3138) return 5;
|
|
1830
|
+
if (cp === 3139) return 2;
|
|
1831
|
+
if (cp === 3140) return 2;
|
|
1832
|
+
if (cp === 3142) return 2;
|
|
1833
|
+
if (cp === 3143) return 2;
|
|
1834
|
+
if (cp === 3144) return 2;
|
|
1835
|
+
if (cp === 3146) return 5;
|
|
1836
|
+
if (cp === 3147) return 5;
|
|
1837
|
+
if (cp === 3148) return 5;
|
|
1838
|
+
if (cp === 3170 || cp === 3171) return 3;
|
|
1839
|
+
if (cp === 3157 || cp === 3158) return 6;
|
|
1840
|
+
if (cp === 3168 || cp === 3169) return 1;
|
|
1841
|
+
if (cp >= 3174 && cp <= 3183) return 9;
|
|
1842
|
+
if (cp >= 3192 && cp <= 3199) return 9;
|
|
1843
|
+
return -1;
|
|
1844
|
+
}
|
|
1845
|
+
function isConsonant3(cp) {
|
|
1846
|
+
return teluguCharType(cp) === 0;
|
|
1847
|
+
}
|
|
1848
|
+
function buildTeluguClusters(str) {
|
|
1849
|
+
const clusters = [];
|
|
1850
|
+
const cps = [];
|
|
1851
|
+
for (let i2 = 0; i2 < str.length; ) {
|
|
1852
|
+
const cp = str.codePointAt(i2) ?? 0;
|
|
1853
|
+
cps.push(cp);
|
|
1854
|
+
i2 += cp > 65535 ? 2 : 1;
|
|
1855
|
+
}
|
|
1856
|
+
let i = 0;
|
|
1857
|
+
while (i < cps.length) {
|
|
1858
|
+
const cp = cps[i];
|
|
1859
|
+
const type = teluguCharType(cp);
|
|
1860
|
+
if (classifyUseCategory(cp) === "ZWJ" || classifyUseCategory(cp) === "ZWNJ") {
|
|
1861
|
+
i++;
|
|
1862
|
+
continue;
|
|
1863
|
+
}
|
|
1864
|
+
if (type < 0 || cp < TELUGU_START || cp > TELUGU_END) {
|
|
1865
|
+
clusters.push({ codepoints: [cp], baseIndex: 0 });
|
|
1866
|
+
i++;
|
|
1867
|
+
continue;
|
|
1868
|
+
}
|
|
1869
|
+
const syllable = [];
|
|
1870
|
+
let lastConsonantIdx = -1;
|
|
1871
|
+
while (i < cps.length) {
|
|
1872
|
+
const cc = cps[i];
|
|
1873
|
+
const ct = teluguCharType(cc);
|
|
1874
|
+
if (ct === 0) {
|
|
1875
|
+
lastConsonantIdx = syllable.length;
|
|
1876
|
+
syllable.push(cc);
|
|
1877
|
+
i++;
|
|
1878
|
+
if (i < cps.length && cps[i] === VIRAMA) {
|
|
1879
|
+
let j = i + 1;
|
|
1880
|
+
let zwnj = false;
|
|
1881
|
+
if (j < cps.length) {
|
|
1882
|
+
const jc = classifyUseCategory(cps[j]);
|
|
1883
|
+
if (jc === "ZWJ") {
|
|
1884
|
+
j++;
|
|
1885
|
+
} else if (jc === "ZWNJ") {
|
|
1886
|
+
j++;
|
|
1887
|
+
zwnj = true;
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
if (!zwnj && j < cps.length && isConsonant3(cps[j])) {
|
|
1891
|
+
syllable.push(cps[i]);
|
|
1892
|
+
i = j;
|
|
1893
|
+
continue;
|
|
1894
|
+
} else {
|
|
1895
|
+
syllable.push(cps[i]);
|
|
1896
|
+
i = j;
|
|
1897
|
+
break;
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
break;
|
|
1901
|
+
} else {
|
|
1902
|
+
break;
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
const baseIdx = lastConsonantIdx >= 0 ? lastConsonantIdx : 0;
|
|
1906
|
+
while (i < cps.length) {
|
|
1907
|
+
const ct = teluguCharType(cps[i]);
|
|
1908
|
+
if (ct >= 2 && ct <= 5) {
|
|
1909
|
+
syllable.push(cps[i]);
|
|
1910
|
+
i++;
|
|
1911
|
+
} else {
|
|
1912
|
+
break;
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
while (i < cps.length && teluguCharType(cps[i]) === 6) {
|
|
1916
|
+
syllable.push(cps[i]);
|
|
1917
|
+
i++;
|
|
1918
|
+
}
|
|
1919
|
+
if (syllable.length === 0) {
|
|
1920
|
+
syllable.push(cps[i] ?? 32);
|
|
1921
|
+
i++;
|
|
1922
|
+
}
|
|
1923
|
+
clusters.push({ codepoints: syllable, baseIndex: baseIdx });
|
|
1924
|
+
}
|
|
1925
|
+
return clusters;
|
|
1926
|
+
}
|
|
1927
|
+
function shapeTeluguText(str, fontData) {
|
|
1928
|
+
const { cmap, ligatures, markAnchors, widths, defaultWidth } = fontData;
|
|
1929
|
+
const shaped = [];
|
|
1930
|
+
function resolveGid(cp) {
|
|
1931
|
+
const normCp = cp === 8239 || cp === 160 ? 32 : cp;
|
|
1932
|
+
return cmap[normCp] || 0;
|
|
1933
|
+
}
|
|
1934
|
+
function tryLig(gids) {
|
|
1935
|
+
return tryLigature(gids, ligatures);
|
|
1936
|
+
}
|
|
1937
|
+
function getAdv(gid) {
|
|
1938
|
+
return widths[gid] !== void 0 ? widths[gid] : defaultWidth;
|
|
1939
|
+
}
|
|
1940
|
+
function getBaseAnchor2(baseGid, markClass) {
|
|
1941
|
+
const base = markAnchors && markAnchors.bases && markAnchors.bases[baseGid];
|
|
1942
|
+
if (!base) return null;
|
|
1943
|
+
return base[markClass] ?? null;
|
|
1944
|
+
}
|
|
1945
|
+
function getMarkAnchor2(markGid) {
|
|
1946
|
+
const mark = markAnchors && markAnchors.marks && markAnchors.marks[markGid];
|
|
1947
|
+
if (!mark) return null;
|
|
1948
|
+
return { classIdx: mark[0], x: mark[1], y: mark[2] };
|
|
1949
|
+
}
|
|
1950
|
+
function emitGlyph(gid, isZero, baseGid) {
|
|
1951
|
+
if (isZero && baseGid !== void 0) {
|
|
1952
|
+
const markAnchor = getMarkAnchor2(gid);
|
|
1953
|
+
if (markAnchor) {
|
|
1954
|
+
const baseAnchorPt = getBaseAnchor2(baseGid, markAnchor.classIdx);
|
|
1955
|
+
if (baseAnchorPt) {
|
|
1956
|
+
const baseAdv = getAdv(baseGid);
|
|
1957
|
+
shaped.push({
|
|
1958
|
+
gid,
|
|
1959
|
+
dx: baseAnchorPt[0] - markAnchor.x - baseAdv,
|
|
1960
|
+
dy: baseAnchorPt[1] - markAnchor.y,
|
|
1961
|
+
isZeroAdvance: true
|
|
1962
|
+
});
|
|
1963
|
+
return;
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
shaped.push({ gid, dx: 0, dy: 0, isZeroAdvance: true });
|
|
1967
|
+
} else {
|
|
1968
|
+
shaped.push({ gid, dx: 0, dy: 0, isZeroAdvance: false });
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
const clusters = buildTeluguClusters(str);
|
|
1972
|
+
for (const cluster of clusters) {
|
|
1973
|
+
const { codepoints } = cluster;
|
|
1974
|
+
let baseGid = 0;
|
|
1975
|
+
for (let ci = 0; ci < codepoints.length; ci++) {
|
|
1976
|
+
const ct = teluguCharType(codepoints[ci]);
|
|
1977
|
+
if (ct === 0) {
|
|
1978
|
+
baseGid = resolveGid(codepoints[ci]);
|
|
1979
|
+
} else if (ct >= 2) {
|
|
1980
|
+
break;
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
const clusterGids = [];
|
|
1984
|
+
const clusterEndIdx = [];
|
|
1985
|
+
let matraStart = codepoints.length;
|
|
1986
|
+
for (let ci = 0; ci < codepoints.length; ci++) {
|
|
1987
|
+
const ct = teluguCharType(codepoints[ci]);
|
|
1988
|
+
if (ct === 0 || ct === 7) {
|
|
1989
|
+
clusterGids.push(resolveGid(codepoints[ci]));
|
|
1990
|
+
clusterEndIdx.push(ci);
|
|
1991
|
+
} else if (ct >= 2) {
|
|
1992
|
+
matraStart = ci;
|
|
1993
|
+
break;
|
|
1994
|
+
} else if (ct < 0 || ct === 1 || ct === 9) {
|
|
1995
|
+
emitGlyph(resolveGid(codepoints[ci]), false);
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
const ligResult = tryLig(clusterGids);
|
|
1999
|
+
if (ligResult) {
|
|
2000
|
+
emitGlyph(ligResult.resultGid, false);
|
|
2001
|
+
baseGid = ligResult.resultGid;
|
|
2002
|
+
let gi = ligResult.consumed;
|
|
2003
|
+
while (gi < clusterGids.length) {
|
|
2004
|
+
const subSeq = clusterGids.slice(gi);
|
|
2005
|
+
const subLig = tryLig(subSeq);
|
|
2006
|
+
if (subLig) {
|
|
2007
|
+
emitGlyph(subLig.resultGid, false);
|
|
2008
|
+
gi += subLig.consumed;
|
|
2009
|
+
} else {
|
|
2010
|
+
const origCi = clusterEndIdx[gi];
|
|
2011
|
+
const ct = teluguCharType(codepoints[origCi]);
|
|
2012
|
+
if (ct === 7) {
|
|
2013
|
+
emitGlyph(clusterGids[gi], true, baseGid);
|
|
2014
|
+
} else {
|
|
2015
|
+
emitGlyph(clusterGids[gi], false);
|
|
2016
|
+
}
|
|
2017
|
+
gi++;
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
} else {
|
|
2021
|
+
for (let ci = 0; ci < matraStart; ci++) {
|
|
2022
|
+
const cp = codepoints[ci];
|
|
2023
|
+
const ct = teluguCharType(cp);
|
|
2024
|
+
if (ct === 0) {
|
|
2025
|
+
emitGlyph(resolveGid(cp), false);
|
|
2026
|
+
} else if (ct === 7) {
|
|
2027
|
+
emitGlyph(resolveGid(cp), true, baseGid);
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
for (let ci = matraStart; ci < codepoints.length; ci++) {
|
|
2032
|
+
const cp = codepoints[ci];
|
|
2033
|
+
const ct = teluguCharType(cp);
|
|
2034
|
+
if (ct === 2 || ct === 3) {
|
|
2035
|
+
emitGlyph(resolveGid(cp), true, baseGid);
|
|
2036
|
+
} else if (ct === 5) {
|
|
2037
|
+
emitGlyph(resolveGid(cp), false);
|
|
2038
|
+
} else if (ct === 6) {
|
|
2039
|
+
emitGlyph(resolveGid(cp), true, baseGid);
|
|
2040
|
+
} else if (ct === 9) {
|
|
2041
|
+
emitGlyph(resolveGid(cp), false);
|
|
2042
|
+
} else {
|
|
2043
|
+
emitGlyph(resolveGid(cp), false);
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
return shaped;
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
// src/shaping/sinhala-shaper.ts
|
|
2051
|
+
var VIRAMA2 = SINHALA_VIRAMA;
|
|
2052
|
+
var TWO_PART_VOWELS = {
|
|
2053
|
+
3546: [3545, 3530],
|
|
2054
|
+
// ේ (ee)
|
|
2055
|
+
3548: [3545, 3535],
|
|
2056
|
+
// ො (o)
|
|
2057
|
+
3549: [3545, 3535, 3530],
|
|
2058
|
+
// ෝ (oo)
|
|
2059
|
+
3550: [3545, 3551]
|
|
2060
|
+
// ෞ (au)
|
|
2061
|
+
};
|
|
2062
|
+
function sinhalaCharType(cp) {
|
|
2063
|
+
if (cp === VIRAMA2) return 7;
|
|
2064
|
+
if (cp >= 3458 && cp <= 3460) return 6;
|
|
2065
|
+
if (cp >= 3461 && cp <= 3478) return 1;
|
|
2066
|
+
if (cp >= 3482 && cp <= 3526) return 0;
|
|
2067
|
+
if (cp === 3535) return 5;
|
|
2068
|
+
if (cp === 3536 || cp === 3537) return 5;
|
|
2069
|
+
if (cp === 3538 || cp === 3539) return 2;
|
|
2070
|
+
if (cp === 3540 || cp === 3542) return 3;
|
|
2071
|
+
if (cp === 3544) return 5;
|
|
2072
|
+
if (cp === 3545 || cp === 3547) return 4;
|
|
2073
|
+
if (cp === 3551) return 5;
|
|
2074
|
+
if (cp === 3570 || cp === 3571) return 5;
|
|
2075
|
+
if (cp >= 3558 && cp <= 3567) return 9;
|
|
2076
|
+
return -1;
|
|
2077
|
+
}
|
|
2078
|
+
function isConsonant4(cp) {
|
|
2079
|
+
return sinhalaCharType(cp) === 0;
|
|
2080
|
+
}
|
|
2081
|
+
function buildSinhalaClusters(str) {
|
|
2082
|
+
const clusters = [];
|
|
2083
|
+
const cps = [];
|
|
2084
|
+
for (let i2 = 0; i2 < str.length; ) {
|
|
2085
|
+
const cp = str.codePointAt(i2) ?? 0;
|
|
2086
|
+
const decomp = TWO_PART_VOWELS[cp];
|
|
2087
|
+
if (decomp) {
|
|
2088
|
+
for (const d of decomp) cps.push(d);
|
|
2089
|
+
} else {
|
|
2090
|
+
cps.push(cp);
|
|
2091
|
+
}
|
|
2092
|
+
i2 += cp > 65535 ? 2 : 1;
|
|
2093
|
+
}
|
|
2094
|
+
let i = 0;
|
|
2095
|
+
while (i < cps.length) {
|
|
2096
|
+
const cp = cps[i];
|
|
2097
|
+
const type = sinhalaCharType(cp);
|
|
2098
|
+
if (classifyUseCategory(cp) === "ZWNJ") {
|
|
2099
|
+
i++;
|
|
2100
|
+
continue;
|
|
2101
|
+
}
|
|
2102
|
+
if (type < 0 || cp < SINHALA_START || cp > SINHALA_END) {
|
|
2103
|
+
clusters.push({ codepoints: [cp], baseIndex: 0 });
|
|
2104
|
+
i++;
|
|
2105
|
+
continue;
|
|
2106
|
+
}
|
|
2107
|
+
const syllable = [];
|
|
2108
|
+
let lastConsonantIdx = -1;
|
|
2109
|
+
while (i < cps.length) {
|
|
2110
|
+
const cc = cps[i];
|
|
2111
|
+
const ct = sinhalaCharType(cc);
|
|
2112
|
+
if (ct === 0) {
|
|
2113
|
+
lastConsonantIdx = syllable.length;
|
|
2114
|
+
syllable.push(cc);
|
|
2115
|
+
i++;
|
|
2116
|
+
if (i < cps.length && cps[i] === VIRAMA2) {
|
|
2117
|
+
let j = i + 1;
|
|
2118
|
+
let zwnj = false;
|
|
2119
|
+
let zwj = false;
|
|
2120
|
+
if (j < cps.length) {
|
|
2121
|
+
const jc = classifyUseCategory(cps[j]);
|
|
2122
|
+
if (jc === "ZWJ") {
|
|
2123
|
+
j++;
|
|
2124
|
+
zwj = true;
|
|
2125
|
+
} else if (jc === "ZWNJ") {
|
|
2126
|
+
j++;
|
|
2127
|
+
zwnj = true;
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
if (!zwnj && j < cps.length && isConsonant4(cps[j])) {
|
|
2131
|
+
syllable.push(cps[i]);
|
|
2132
|
+
if (zwj) syllable.push(8205);
|
|
2133
|
+
i = j;
|
|
2134
|
+
continue;
|
|
2135
|
+
} else {
|
|
2136
|
+
syllable.push(cps[i]);
|
|
2137
|
+
i = j;
|
|
2138
|
+
break;
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
break;
|
|
2142
|
+
} else {
|
|
2143
|
+
break;
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
const baseIdx = lastConsonantIdx >= 0 ? lastConsonantIdx : 0;
|
|
2147
|
+
while (i < cps.length) {
|
|
2148
|
+
const ct = sinhalaCharType(cps[i]);
|
|
2149
|
+
if (ct >= 2 && ct <= 5) {
|
|
2150
|
+
syllable.push(cps[i]);
|
|
2151
|
+
i++;
|
|
2152
|
+
} else {
|
|
2153
|
+
break;
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2156
|
+
while (i < cps.length && sinhalaCharType(cps[i]) === 6) {
|
|
2157
|
+
syllable.push(cps[i]);
|
|
2158
|
+
i++;
|
|
2159
|
+
}
|
|
2160
|
+
if (syllable.length === 0) {
|
|
2161
|
+
syllable.push(cps[i] ?? 32);
|
|
2162
|
+
i++;
|
|
2163
|
+
}
|
|
2164
|
+
clusters.push({ codepoints: syllable, baseIndex: baseIdx });
|
|
2165
|
+
}
|
|
2166
|
+
return clusters;
|
|
2167
|
+
}
|
|
2168
|
+
function shapeSinhalaText(str, fontData) {
|
|
2169
|
+
const { cmap, ligatures, markAnchors, widths, defaultWidth } = fontData;
|
|
2170
|
+
const shaped = [];
|
|
2171
|
+
function resolveGid(cp) {
|
|
2172
|
+
const normCp = cp === 8239 || cp === 160 ? 32 : cp;
|
|
2173
|
+
return cmap[normCp] || 0;
|
|
2174
|
+
}
|
|
2175
|
+
function tryLig(gids) {
|
|
2176
|
+
return tryLigature(gids, ligatures);
|
|
2177
|
+
}
|
|
2178
|
+
function getAdv(gid) {
|
|
2179
|
+
return widths[gid] !== void 0 ? widths[gid] : defaultWidth;
|
|
2180
|
+
}
|
|
2181
|
+
function getBaseAnchor2(baseGid, markClass) {
|
|
2182
|
+
const base = markAnchors && markAnchors.bases && markAnchors.bases[baseGid];
|
|
2183
|
+
if (!base) return null;
|
|
2184
|
+
return base[markClass] ?? null;
|
|
2185
|
+
}
|
|
2186
|
+
function getMarkAnchor2(markGid) {
|
|
2187
|
+
const mark = markAnchors && markAnchors.marks && markAnchors.marks[markGid];
|
|
2188
|
+
if (!mark) return null;
|
|
2189
|
+
return { classIdx: mark[0], x: mark[1], y: mark[2] };
|
|
2190
|
+
}
|
|
2191
|
+
function emitGlyph(gid, isZero, baseGid) {
|
|
2192
|
+
if (isZero && baseGid !== void 0) {
|
|
2193
|
+
const markAnchor = getMarkAnchor2(gid);
|
|
2194
|
+
if (markAnchor) {
|
|
2195
|
+
const baseAnchorPt = getBaseAnchor2(baseGid, markAnchor.classIdx);
|
|
2196
|
+
if (baseAnchorPt) {
|
|
2197
|
+
const baseAdv = getAdv(baseGid);
|
|
2198
|
+
shaped.push({
|
|
2199
|
+
gid,
|
|
2200
|
+
dx: baseAnchorPt[0] - markAnchor.x - baseAdv,
|
|
2201
|
+
dy: baseAnchorPt[1] - markAnchor.y,
|
|
2202
|
+
isZeroAdvance: true
|
|
2203
|
+
});
|
|
2204
|
+
return;
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
shaped.push({ gid, dx: 0, dy: 0, isZeroAdvance: true });
|
|
2208
|
+
} else {
|
|
2209
|
+
shaped.push({ gid, dx: 0, dy: 0, isZeroAdvance: false });
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
const clusters = buildSinhalaClusters(str);
|
|
2213
|
+
for (const cluster of clusters) {
|
|
2214
|
+
const { codepoints } = cluster;
|
|
2215
|
+
let baseGid = 0;
|
|
2216
|
+
for (let ci = 0; ci < codepoints.length; ci++) {
|
|
2217
|
+
const ct = sinhalaCharType(codepoints[ci]);
|
|
2218
|
+
if (ct === 0) {
|
|
2219
|
+
baseGid = resolveGid(codepoints[ci]);
|
|
2220
|
+
} else if (ct >= 2 && ct !== 7) {
|
|
2221
|
+
break;
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
for (let ci = 0; ci < codepoints.length; ci++) {
|
|
2225
|
+
if (sinhalaCharType(codepoints[ci]) === 4) {
|
|
2226
|
+
emitGlyph(resolveGid(codepoints[ci]), false);
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
const clusterGids = [];
|
|
2230
|
+
const clusterEndIdx = [];
|
|
2231
|
+
let matraStart = codepoints.length;
|
|
2232
|
+
for (let ci = 0; ci < codepoints.length; ci++) {
|
|
2233
|
+
const cp = codepoints[ci];
|
|
2234
|
+
const ct = sinhalaCharType(cp);
|
|
2235
|
+
if (ct === 0 || ct === 7 || cp === 8205) {
|
|
2236
|
+
clusterGids.push(resolveGid(cp));
|
|
2237
|
+
clusterEndIdx.push(ci);
|
|
2238
|
+
} else if (ct >= 2 && ct <= 6) {
|
|
2239
|
+
matraStart = ci;
|
|
2240
|
+
break;
|
|
2241
|
+
} else if (ct < 0 || ct === 1 || ct === 9) {
|
|
2242
|
+
emitGlyph(resolveGid(cp), false);
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
const ligResult = tryLig(clusterGids);
|
|
2246
|
+
if (ligResult) {
|
|
2247
|
+
emitGlyph(ligResult.resultGid, false);
|
|
2248
|
+
baseGid = ligResult.resultGid;
|
|
2249
|
+
let gi = ligResult.consumed;
|
|
2250
|
+
while (gi < clusterGids.length) {
|
|
2251
|
+
const subSeq = clusterGids.slice(gi);
|
|
2252
|
+
const subLig = tryLig(subSeq);
|
|
2253
|
+
if (subLig) {
|
|
2254
|
+
emitGlyph(subLig.resultGid, false);
|
|
2255
|
+
gi += subLig.consumed;
|
|
2256
|
+
} else {
|
|
2257
|
+
const origCi = clusterEndIdx[gi];
|
|
2258
|
+
const ct = sinhalaCharType(codepoints[origCi]);
|
|
2259
|
+
if (ct === 7) emitGlyph(clusterGids[gi], true, baseGid);
|
|
2260
|
+
else emitGlyph(clusterGids[gi], false);
|
|
2261
|
+
gi++;
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
} else {
|
|
2265
|
+
for (let ci = 0; ci < matraStart; ci++) {
|
|
2266
|
+
const cp = codepoints[ci];
|
|
2267
|
+
const ct = sinhalaCharType(cp);
|
|
2268
|
+
if (ct === 0) emitGlyph(resolveGid(cp), false);
|
|
2269
|
+
else if (ct === 7) emitGlyph(resolveGid(cp), true, baseGid);
|
|
2270
|
+
else ;
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
for (let ci = matraStart; ci < codepoints.length; ci++) {
|
|
2274
|
+
const cp = codepoints[ci];
|
|
2275
|
+
const ct = sinhalaCharType(cp);
|
|
2276
|
+
if (ct === 2 || ct === 3 || ct === 6) emitGlyph(resolveGid(cp), true, baseGid);
|
|
2277
|
+
else if (ct === 5) emitGlyph(resolveGid(cp), false);
|
|
2278
|
+
else if (ct === 4) ; else emitGlyph(resolveGid(cp), false);
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
return shaped;
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2284
|
+
// src/shaping/tibetan-shaper.ts
|
|
2285
|
+
function tibetanCharType(cp) {
|
|
2286
|
+
if (cp >= 3984 && cp <= 4028) return 8;
|
|
2287
|
+
if (cp === 3972) return 6;
|
|
2288
|
+
if (cp >= 3904 && cp <= 3948) return 0;
|
|
2289
|
+
if (cp === 3954 || cp === 3962 || cp === 3963 || cp === 3964 || cp === 3965 || cp === 3968 || cp === 3969 || cp === 3971 || cp === 3970 || cp === 3966) return 2;
|
|
2290
|
+
if (cp === 3953 || cp === 3956 || cp === 3955 || cp === 3957 || cp === 3864 || cp === 3865 || cp === 3893 || cp === 3895 || cp === 3897) return 3;
|
|
2291
|
+
if (cp === 3967) return 5;
|
|
2292
|
+
if (cp === 3840 || cp >= 3976 && cp <= 3980) return 1;
|
|
2293
|
+
if (cp >= 3872 && cp <= 3891) return 9;
|
|
2294
|
+
if (cp >= 3841 && cp <= 3863) return 9;
|
|
2295
|
+
if (cp >= 3898 && cp <= 3903) return 9;
|
|
2296
|
+
if (cp >= 4030 && cp <= 4047) return 9;
|
|
2297
|
+
if (cp === 3892 || cp === 3894 || cp === 3896) return 9;
|
|
2298
|
+
if (cp === 3973) return 9;
|
|
2299
|
+
return -1;
|
|
2300
|
+
}
|
|
2301
|
+
function buildTibetanStacks(str) {
|
|
2302
|
+
const stacks = [];
|
|
2303
|
+
const cps = [];
|
|
2304
|
+
for (let i2 = 0; i2 < str.length; ) {
|
|
2305
|
+
const cp = str.codePointAt(i2) ?? 0;
|
|
2306
|
+
cps.push(cp);
|
|
2307
|
+
i2 += cp > 65535 ? 2 : 1;
|
|
2308
|
+
}
|
|
2309
|
+
let i = 0;
|
|
2310
|
+
while (i < cps.length) {
|
|
2311
|
+
const cp = cps[i];
|
|
2312
|
+
const type = tibetanCharType(cp);
|
|
2313
|
+
if (type < 0 || cp < TIBETAN_START || cp > TIBETAN_END) {
|
|
2314
|
+
stacks.push({ codepoints: [cp] });
|
|
2315
|
+
i++;
|
|
2316
|
+
continue;
|
|
2317
|
+
}
|
|
2318
|
+
if (type !== 0 && type !== 1) {
|
|
2319
|
+
stacks.push({ codepoints: [cp] });
|
|
2320
|
+
i++;
|
|
2321
|
+
continue;
|
|
2322
|
+
}
|
|
2323
|
+
const stack = [cp];
|
|
2324
|
+
i++;
|
|
2325
|
+
while (i < cps.length && tibetanCharType(cps[i]) === 8) {
|
|
2326
|
+
stack.push(cps[i]);
|
|
2327
|
+
i++;
|
|
2328
|
+
}
|
|
2329
|
+
while (i < cps.length) {
|
|
2330
|
+
const ct = tibetanCharType(cps[i]);
|
|
2331
|
+
if (ct === 2 || ct === 3 || ct === 5 || ct === 6) {
|
|
2332
|
+
stack.push(cps[i]);
|
|
2333
|
+
i++;
|
|
2334
|
+
} else {
|
|
2335
|
+
break;
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
stacks.push({ codepoints: stack });
|
|
2339
|
+
}
|
|
2340
|
+
return stacks;
|
|
2341
|
+
}
|
|
2342
|
+
function shapeTibetanText(str, fontData) {
|
|
2343
|
+
const { cmap, ligatures, markAnchors, widths, defaultWidth } = fontData;
|
|
2344
|
+
const shaped = [];
|
|
2345
|
+
function resolveGid(cp) {
|
|
2346
|
+
const normCp = cp === 8239 || cp === 160 ? 32 : cp;
|
|
2347
|
+
return cmap[normCp] || 0;
|
|
2348
|
+
}
|
|
2349
|
+
function tryLig(gids) {
|
|
2350
|
+
return tryLigature(gids, ligatures);
|
|
2351
|
+
}
|
|
2352
|
+
function getAdv(gid) {
|
|
2353
|
+
return widths[gid] !== void 0 ? widths[gid] : defaultWidth;
|
|
2354
|
+
}
|
|
2355
|
+
function getBaseAnchor2(baseGid, markClass) {
|
|
2356
|
+
const base = markAnchors && markAnchors.bases && markAnchors.bases[baseGid];
|
|
2357
|
+
if (!base) return null;
|
|
2358
|
+
return base[markClass] ?? null;
|
|
2359
|
+
}
|
|
2360
|
+
function getMarkAnchor2(markGid) {
|
|
2361
|
+
const mark = markAnchors && markAnchors.marks && markAnchors.marks[markGid];
|
|
2362
|
+
if (!mark) return null;
|
|
2363
|
+
return { classIdx: mark[0], x: mark[1], y: mark[2] };
|
|
2364
|
+
}
|
|
2365
|
+
function emitGlyph(gid, isZero, baseGid) {
|
|
2366
|
+
if (isZero && baseGid !== void 0) {
|
|
2367
|
+
const markAnchor = getMarkAnchor2(gid);
|
|
2368
|
+
if (markAnchor) {
|
|
2369
|
+
const baseAnchorPt = getBaseAnchor2(baseGid, markAnchor.classIdx);
|
|
2370
|
+
if (baseAnchorPt) {
|
|
2371
|
+
const baseAdv = getAdv(baseGid);
|
|
2372
|
+
shaped.push({
|
|
2373
|
+
gid,
|
|
2374
|
+
dx: baseAnchorPt[0] - markAnchor.x - baseAdv,
|
|
2375
|
+
dy: baseAnchorPt[1] - markAnchor.y,
|
|
2376
|
+
isZeroAdvance: true
|
|
2377
|
+
});
|
|
2378
|
+
return;
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
shaped.push({ gid, dx: 0, dy: 0, isZeroAdvance: true });
|
|
2382
|
+
} else {
|
|
2383
|
+
shaped.push({ gid, dx: 0, dy: 0, isZeroAdvance: false });
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
const stacks = buildTibetanStacks(str);
|
|
2387
|
+
for (const stack of stacks) {
|
|
2388
|
+
const { codepoints } = stack;
|
|
2389
|
+
const stackGids = [];
|
|
2390
|
+
const stackEndIdx = [];
|
|
2391
|
+
let markStart = codepoints.length;
|
|
2392
|
+
for (let ci = 0; ci < codepoints.length; ci++) {
|
|
2393
|
+
const ct = tibetanCharType(codepoints[ci]);
|
|
2394
|
+
if (ct === 0 || ct === 8 || ct === 1 || ct === 6) {
|
|
2395
|
+
stackGids.push(resolveGid(codepoints[ci]));
|
|
2396
|
+
stackEndIdx.push(ci);
|
|
2397
|
+
} else if (ct === 2 || ct === 3 || ct === 5) {
|
|
2398
|
+
markStart = ci;
|
|
2399
|
+
break;
|
|
2400
|
+
} else if (ct < 0 || ct === 9) {
|
|
2401
|
+
emitGlyph(resolveGid(codepoints[ci]), false);
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
let baseGid = stackGids.length > 0 ? stackGids[0] : 0;
|
|
2405
|
+
const ligResult = tryLig(stackGids);
|
|
2406
|
+
if (ligResult) {
|
|
2407
|
+
emitGlyph(ligResult.resultGid, false);
|
|
2408
|
+
baseGid = ligResult.resultGid;
|
|
2409
|
+
let gi = ligResult.consumed;
|
|
2410
|
+
while (gi < stackGids.length) {
|
|
2411
|
+
const subSeq = stackGids.slice(gi);
|
|
2412
|
+
const subLig = tryLig(subSeq);
|
|
2413
|
+
if (subLig) {
|
|
2414
|
+
emitGlyph(subLig.resultGid, false);
|
|
2415
|
+
gi += subLig.consumed;
|
|
2416
|
+
} else {
|
|
2417
|
+
const origCi = stackEndIdx[gi];
|
|
2418
|
+
const ct = tibetanCharType(codepoints[origCi]);
|
|
2419
|
+
if (ct === 8 || ct === 6) emitGlyph(stackGids[gi], true, baseGid);
|
|
2420
|
+
else emitGlyph(stackGids[gi], false);
|
|
2421
|
+
gi++;
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
} else {
|
|
2425
|
+
for (let gi = 0; gi < stackGids.length; gi++) {
|
|
2426
|
+
const origCi = stackEndIdx[gi];
|
|
2427
|
+
const ct = tibetanCharType(codepoints[origCi]);
|
|
2428
|
+
if (gi === 0) emitGlyph(stackGids[gi], false);
|
|
2429
|
+
else if (ct === 8 || ct === 6) emitGlyph(stackGids[gi], true, baseGid);
|
|
2430
|
+
else emitGlyph(stackGids[gi], false);
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
for (let ci = markStart; ci < codepoints.length; ci++) {
|
|
2434
|
+
const cp = codepoints[ci];
|
|
2435
|
+
const ct = tibetanCharType(cp);
|
|
2436
|
+
if (ct === 2 || ct === 3) emitGlyph(resolveGid(cp), true, baseGid);
|
|
2437
|
+
else if (ct === 5) emitGlyph(resolveGid(cp), false);
|
|
2438
|
+
else emitGlyph(resolveGid(cp), false);
|
|
2439
|
+
}
|
|
2440
|
+
}
|
|
2441
|
+
return shaped;
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2444
|
+
// src/shaping/khmer-shaper.ts
|
|
2445
|
+
var COENG = KHMER_COENG;
|
|
2446
|
+
var TWO_PART_VOWELS2 = {
|
|
2447
|
+
6078: [6081, 6070],
|
|
2448
|
+
// ើ
|
|
2449
|
+
6079: [6081, 6072],
|
|
2450
|
+
// ឿ (approx: e + y-like)
|
|
2451
|
+
6080: [6081, 6073],
|
|
2452
|
+
// ៀ (approx)
|
|
2453
|
+
6084: [6081, 6070],
|
|
2454
|
+
// ោ (e + aa)
|
|
2455
|
+
6085: [6081, 6070]
|
|
2456
|
+
// ៅ (e + aa-like)
|
|
2457
|
+
};
|
|
2458
|
+
function khmerCharType(cp) {
|
|
2459
|
+
if (cp === COENG) return 7;
|
|
2460
|
+
if (cp >= 6016 && cp <= 6050) return 0;
|
|
2461
|
+
if (cp >= 6051 && cp <= 6069) return 1;
|
|
2462
|
+
if (cp === 6081 || cp === 6082 || cp === 6083) return 4;
|
|
2463
|
+
if (cp === 6071 || cp === 6072 || cp === 6073 || cp === 6074 || cp === 6075 || cp === 6077) return 2;
|
|
2464
|
+
if (cp === 6076) return 3;
|
|
2465
|
+
if (cp === 6070 || cp === 6084 || cp === 6085) return 5;
|
|
2466
|
+
if (cp >= 6086 && cp <= 6097) return 6;
|
|
2467
|
+
if (cp === 6091 || cp === 6093 || cp === 6094 || cp === 6095 || cp === 6096) return 6;
|
|
2468
|
+
if (cp === 6109) return 6;
|
|
2469
|
+
if (cp >= 6112 && cp <= 6121) return 9;
|
|
2470
|
+
if (cp >= 6100 && cp <= 6108) return 9;
|
|
2471
|
+
if (cp >= 6128 && cp <= 6137) return 9;
|
|
2472
|
+
return -1;
|
|
2473
|
+
}
|
|
2474
|
+
function isConsonant5(cp) {
|
|
2475
|
+
return khmerCharType(cp) === 0;
|
|
2476
|
+
}
|
|
2477
|
+
function buildKhmerClusters(str) {
|
|
2478
|
+
const clusters = [];
|
|
2479
|
+
const cps = [];
|
|
2480
|
+
for (let i2 = 0; i2 < str.length; ) {
|
|
2481
|
+
const cp = str.codePointAt(i2) ?? 0;
|
|
2482
|
+
const decomp = TWO_PART_VOWELS2[cp];
|
|
2483
|
+
if (decomp) {
|
|
2484
|
+
for (const d of decomp) cps.push(d);
|
|
2485
|
+
} else cps.push(cp);
|
|
2486
|
+
i2 += cp > 65535 ? 2 : 1;
|
|
2487
|
+
}
|
|
2488
|
+
let i = 0;
|
|
2489
|
+
while (i < cps.length) {
|
|
2490
|
+
const cp = cps[i];
|
|
2491
|
+
const type = khmerCharType(cp);
|
|
2492
|
+
if (type < 0 || cp < KHMER_START || cp > KHMER_END) {
|
|
2493
|
+
clusters.push({ codepoints: [cp] });
|
|
2494
|
+
i++;
|
|
2495
|
+
continue;
|
|
2496
|
+
}
|
|
2497
|
+
if (type !== 0 && type !== 1) {
|
|
2498
|
+
clusters.push({ codepoints: [cp] });
|
|
2499
|
+
i++;
|
|
2500
|
+
continue;
|
|
2501
|
+
}
|
|
2502
|
+
const cluster = [cp];
|
|
2503
|
+
i++;
|
|
2504
|
+
while (i < cps.length && cps[i] === COENG) {
|
|
2505
|
+
if (i + 1 < cps.length && isConsonant5(cps[i + 1])) {
|
|
2506
|
+
cluster.push(cps[i]);
|
|
2507
|
+
cluster.push(cps[i + 1]);
|
|
2508
|
+
i += 2;
|
|
2509
|
+
} else {
|
|
2510
|
+
cluster.push(cps[i]);
|
|
2511
|
+
i++;
|
|
2512
|
+
break;
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
while (i < cps.length) {
|
|
2516
|
+
const ct = khmerCharType(cps[i]);
|
|
2517
|
+
if (ct >= 2 && ct <= 6) {
|
|
2518
|
+
cluster.push(cps[i]);
|
|
2519
|
+
i++;
|
|
2520
|
+
} else break;
|
|
2521
|
+
}
|
|
2522
|
+
clusters.push({ codepoints: cluster });
|
|
2523
|
+
}
|
|
2524
|
+
return clusters;
|
|
2525
|
+
}
|
|
2526
|
+
function shapeKhmerText(str, fontData) {
|
|
2527
|
+
const { cmap, ligatures, markAnchors, widths, defaultWidth } = fontData;
|
|
2528
|
+
const shaped = [];
|
|
2529
|
+
function resolveGid(cp) {
|
|
2530
|
+
const normCp = cp === 8239 || cp === 160 ? 32 : cp;
|
|
2531
|
+
return cmap[normCp] || 0;
|
|
2532
|
+
}
|
|
2533
|
+
function tryLig(gids) {
|
|
2534
|
+
return tryLigature(gids, ligatures);
|
|
2535
|
+
}
|
|
2536
|
+
function getAdv(gid) {
|
|
2537
|
+
return widths[gid] !== void 0 ? widths[gid] : defaultWidth;
|
|
2538
|
+
}
|
|
2539
|
+
function getBaseAnchor2(baseGid, markClass) {
|
|
2540
|
+
const base = markAnchors && markAnchors.bases && markAnchors.bases[baseGid];
|
|
2541
|
+
if (!base) return null;
|
|
2542
|
+
return base[markClass] ?? null;
|
|
2543
|
+
}
|
|
2544
|
+
function getMarkAnchor2(markGid) {
|
|
2545
|
+
const mark = markAnchors && markAnchors.marks && markAnchors.marks[markGid];
|
|
2546
|
+
if (!mark) return null;
|
|
2547
|
+
return { classIdx: mark[0], x: mark[1], y: mark[2] };
|
|
2548
|
+
}
|
|
2549
|
+
function emitGlyph(gid, isZero, baseGid) {
|
|
2550
|
+
if (isZero && baseGid !== void 0) {
|
|
2551
|
+
const markAnchor = getMarkAnchor2(gid);
|
|
2552
|
+
if (markAnchor) {
|
|
2553
|
+
const baseAnchorPt = getBaseAnchor2(baseGid, markAnchor.classIdx);
|
|
2554
|
+
if (baseAnchorPt) {
|
|
2555
|
+
const baseAdv = getAdv(baseGid);
|
|
2556
|
+
shaped.push({
|
|
2557
|
+
gid,
|
|
2558
|
+
dx: baseAnchorPt[0] - markAnchor.x - baseAdv,
|
|
2559
|
+
dy: baseAnchorPt[1] - markAnchor.y,
|
|
2560
|
+
isZeroAdvance: true
|
|
2561
|
+
});
|
|
2562
|
+
return;
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
shaped.push({ gid, dx: 0, dy: 0, isZeroAdvance: true });
|
|
2566
|
+
} else {
|
|
2567
|
+
shaped.push({ gid, dx: 0, dy: 0, isZeroAdvance: false });
|
|
2568
|
+
}
|
|
2569
|
+
}
|
|
2570
|
+
const clusters = buildKhmerClusters(str);
|
|
2571
|
+
for (const cluster of clusters) {
|
|
2572
|
+
const { codepoints } = cluster;
|
|
2573
|
+
let baseGid = 0;
|
|
2574
|
+
for (let ci = 0; ci < codepoints.length; ci++) {
|
|
2575
|
+
const ct = khmerCharType(codepoints[ci]);
|
|
2576
|
+
if (ct === 0 || ct === 1) {
|
|
2577
|
+
baseGid = resolveGid(codepoints[ci]);
|
|
2578
|
+
break;
|
|
2579
|
+
}
|
|
2580
|
+
}
|
|
2581
|
+
for (let ci = 0; ci < codepoints.length; ci++) {
|
|
2582
|
+
if (khmerCharType(codepoints[ci]) === 4) emitGlyph(resolveGid(codepoints[ci]), false);
|
|
2583
|
+
}
|
|
2584
|
+
const stackGids = [];
|
|
2585
|
+
const stackEndIdx = [];
|
|
2586
|
+
let markStart = codepoints.length;
|
|
2587
|
+
for (let ci = 0; ci < codepoints.length; ci++) {
|
|
2588
|
+
const cp = codepoints[ci];
|
|
2589
|
+
const ct = khmerCharType(cp);
|
|
2590
|
+
if (ct === 0 || ct === 1 || ct === 7) {
|
|
2591
|
+
stackGids.push(resolveGid(cp));
|
|
2592
|
+
stackEndIdx.push(ci);
|
|
2593
|
+
} else if (ct >= 2 && ct <= 6) {
|
|
2594
|
+
markStart = ci;
|
|
2595
|
+
break;
|
|
2596
|
+
} else {
|
|
2597
|
+
emitGlyph(resolveGid(cp), false);
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
const ligResult = tryLig(stackGids);
|
|
2601
|
+
if (ligResult) {
|
|
2602
|
+
emitGlyph(ligResult.resultGid, false);
|
|
2603
|
+
baseGid = ligResult.resultGid;
|
|
2604
|
+
let gi = ligResult.consumed;
|
|
2605
|
+
while (gi < stackGids.length) {
|
|
2606
|
+
const subLig = tryLig(stackGids.slice(gi));
|
|
2607
|
+
if (subLig) {
|
|
2608
|
+
emitGlyph(subLig.resultGid, false);
|
|
2609
|
+
gi += subLig.consumed;
|
|
2610
|
+
} else {
|
|
2611
|
+
const origCi = stackEndIdx[gi];
|
|
2612
|
+
const ct = khmerCharType(codepoints[origCi]);
|
|
2613
|
+
if (ct === 7) emitGlyph(stackGids[gi], true, baseGid);
|
|
2614
|
+
else emitGlyph(stackGids[gi], false);
|
|
2615
|
+
gi++;
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2618
|
+
} else {
|
|
2619
|
+
for (let gi = 0; gi < stackGids.length; gi++) {
|
|
2620
|
+
const origCi = stackEndIdx[gi];
|
|
2621
|
+
const ct = khmerCharType(codepoints[origCi]);
|
|
2622
|
+
if (gi === 0) emitGlyph(stackGids[gi], false);
|
|
2623
|
+
else if (ct === 7) emitGlyph(stackGids[gi], true, baseGid);
|
|
2624
|
+
else emitGlyph(stackGids[gi], true, baseGid);
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
for (let ci = markStart; ci < codepoints.length; ci++) {
|
|
2628
|
+
const cp = codepoints[ci];
|
|
2629
|
+
const ct = khmerCharType(cp);
|
|
2630
|
+
if (ct === 2 || ct === 3 || ct === 6) emitGlyph(resolveGid(cp), true, baseGid);
|
|
2631
|
+
else if (ct === 5) emitGlyph(resolveGid(cp), false);
|
|
2632
|
+
else if (ct === 4) ; else emitGlyph(resolveGid(cp), false);
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2635
|
+
return shaped;
|
|
2636
|
+
}
|
|
2637
|
+
|
|
2638
|
+
// src/shaping/myanmar-shaper.ts
|
|
2639
|
+
var VIRAMA3 = MYANMAR_VIRAMA;
|
|
2640
|
+
var MEDIAL_RA = 4156;
|
|
2641
|
+
function myanmarCharType(cp) {
|
|
2642
|
+
if (cp === VIRAMA3) return 7;
|
|
2643
|
+
if (cp === MEDIAL_RA) return 4;
|
|
2644
|
+
if (cp === 4155 || cp === 4157 || cp === 4158) return 8;
|
|
2645
|
+
if (cp >= 4096 && cp <= 4138) return 0;
|
|
2646
|
+
if (cp === 4159) return 0;
|
|
2647
|
+
if (cp >= 4176 && cp <= 4181) return 0;
|
|
2648
|
+
if (cp === 4141 || cp === 4142 || cp === 4146 || cp === 4150 || cp === 4147 || cp === 4148 || cp === 4149) return 2;
|
|
2649
|
+
if (cp === 4143 || cp === 4144 || cp === 4151) return 3;
|
|
2650
|
+
if (cp === 4139 || cp === 4140) return 5;
|
|
2651
|
+
if (cp === 4145) return 4;
|
|
2652
|
+
if (cp === 4154) return 6;
|
|
2653
|
+
if (cp === 4152) return 5;
|
|
2654
|
+
if (cp >= 4160 && cp <= 4169) return 9;
|
|
2655
|
+
if (cp >= 4240 && cp <= 4249) return 9;
|
|
2656
|
+
if (cp === 4170 || cp === 4171) return 9;
|
|
2657
|
+
if (cp >= 4172 && cp <= 4175) return 9;
|
|
2658
|
+
return -1;
|
|
2659
|
+
}
|
|
2660
|
+
function isConsonant6(cp) {
|
|
2661
|
+
return myanmarCharType(cp) === 0;
|
|
2662
|
+
}
|
|
2663
|
+
function buildMyanmarClusters(str) {
|
|
2664
|
+
const clusters = [];
|
|
2665
|
+
const cps = [];
|
|
2666
|
+
for (let i2 = 0; i2 < str.length; ) {
|
|
2667
|
+
const cp = str.codePointAt(i2) ?? 0;
|
|
2668
|
+
cps.push(cp);
|
|
2669
|
+
i2 += cp > 65535 ? 2 : 1;
|
|
2670
|
+
}
|
|
2671
|
+
let i = 0;
|
|
2672
|
+
while (i < cps.length) {
|
|
2673
|
+
const cp = cps[i];
|
|
2674
|
+
const type = myanmarCharType(cp);
|
|
2675
|
+
if (type < 0 || cp < MYANMAR_START || cp > MYANMAR_END) {
|
|
2676
|
+
clusters.push({ codepoints: [cp] });
|
|
2677
|
+
i++;
|
|
2678
|
+
continue;
|
|
2679
|
+
}
|
|
2680
|
+
if (type !== 0) {
|
|
2681
|
+
clusters.push({ codepoints: [cp] });
|
|
2682
|
+
i++;
|
|
2683
|
+
continue;
|
|
2684
|
+
}
|
|
2685
|
+
const cluster = [cp];
|
|
2686
|
+
i++;
|
|
2687
|
+
while (i < cps.length && cps[i] === VIRAMA3) {
|
|
2688
|
+
if (i + 1 < cps.length && isConsonant6(cps[i + 1])) {
|
|
2689
|
+
cluster.push(cps[i]);
|
|
2690
|
+
cluster.push(cps[i + 1]);
|
|
2691
|
+
i += 2;
|
|
2692
|
+
} else {
|
|
2693
|
+
cluster.push(cps[i]);
|
|
2694
|
+
i++;
|
|
2695
|
+
break;
|
|
2696
|
+
}
|
|
2697
|
+
}
|
|
2698
|
+
while (i < cps.length && myanmarCharType(cps[i]) === 8) {
|
|
2699
|
+
cluster.push(cps[i]);
|
|
2700
|
+
i++;
|
|
2701
|
+
}
|
|
2702
|
+
while (i < cps.length) {
|
|
2703
|
+
const ct = myanmarCharType(cps[i]);
|
|
2704
|
+
if (ct >= 2 && ct <= 6) {
|
|
2705
|
+
cluster.push(cps[i]);
|
|
2706
|
+
i++;
|
|
2707
|
+
} else break;
|
|
2708
|
+
}
|
|
2709
|
+
clusters.push({ codepoints: cluster });
|
|
2710
|
+
}
|
|
2711
|
+
return clusters;
|
|
2712
|
+
}
|
|
2713
|
+
function shapeMyanmarText(str, fontData) {
|
|
2714
|
+
const { cmap, ligatures, markAnchors, widths, defaultWidth } = fontData;
|
|
2715
|
+
const shaped = [];
|
|
2716
|
+
function resolveGid(cp) {
|
|
2717
|
+
const normCp = cp === 8239 || cp === 160 ? 32 : cp;
|
|
2718
|
+
return cmap[normCp] || 0;
|
|
2719
|
+
}
|
|
2720
|
+
function tryLig(gids) {
|
|
2721
|
+
return tryLigature(gids, ligatures);
|
|
2722
|
+
}
|
|
2723
|
+
function getAdv(gid) {
|
|
2724
|
+
return widths[gid] !== void 0 ? widths[gid] : defaultWidth;
|
|
2725
|
+
}
|
|
2726
|
+
function getBaseAnchor2(baseGid, markClass) {
|
|
2727
|
+
const base = markAnchors && markAnchors.bases && markAnchors.bases[baseGid];
|
|
2728
|
+
if (!base) return null;
|
|
2729
|
+
return base[markClass] ?? null;
|
|
2730
|
+
}
|
|
2731
|
+
function getMarkAnchor2(markGid) {
|
|
2732
|
+
const mark = markAnchors && markAnchors.marks && markAnchors.marks[markGid];
|
|
2733
|
+
if (!mark) return null;
|
|
2734
|
+
return { classIdx: mark[0], x: mark[1], y: mark[2] };
|
|
2735
|
+
}
|
|
2736
|
+
function emitGlyph(gid, isZero, baseGid) {
|
|
2737
|
+
if (isZero && baseGid !== void 0) {
|
|
2738
|
+
const markAnchor = getMarkAnchor2(gid);
|
|
2739
|
+
if (markAnchor) {
|
|
2740
|
+
const baseAnchorPt = getBaseAnchor2(baseGid, markAnchor.classIdx);
|
|
2741
|
+
if (baseAnchorPt) {
|
|
2742
|
+
const baseAdv = getAdv(baseGid);
|
|
2743
|
+
shaped.push({
|
|
2744
|
+
gid,
|
|
2745
|
+
dx: baseAnchorPt[0] - markAnchor.x - baseAdv,
|
|
2746
|
+
dy: baseAnchorPt[1] - markAnchor.y,
|
|
2747
|
+
isZeroAdvance: true
|
|
2748
|
+
});
|
|
2749
|
+
return;
|
|
2750
|
+
}
|
|
2751
|
+
}
|
|
2752
|
+
shaped.push({ gid, dx: 0, dy: 0, isZeroAdvance: true });
|
|
2753
|
+
} else {
|
|
2754
|
+
shaped.push({ gid, dx: 0, dy: 0, isZeroAdvance: false });
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
2757
|
+
const clusters = buildMyanmarClusters(str);
|
|
2758
|
+
for (const cluster of clusters) {
|
|
2759
|
+
const { codepoints } = cluster;
|
|
2760
|
+
let baseGid = 0;
|
|
2761
|
+
for (let ci = 0; ci < codepoints.length; ci++) {
|
|
2762
|
+
if (myanmarCharType(codepoints[ci]) === 0) {
|
|
2763
|
+
baseGid = resolveGid(codepoints[ci]);
|
|
2764
|
+
break;
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
1407
2767
|
for (let ci = 0; ci < codepoints.length; ci++) {
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
2768
|
+
if (myanmarCharType(codepoints[ci]) === 4) emitGlyph(resolveGid(codepoints[ci]), false);
|
|
2769
|
+
}
|
|
2770
|
+
const stackGids = [];
|
|
2771
|
+
const stackEndIdx = [];
|
|
2772
|
+
let markStart = codepoints.length;
|
|
2773
|
+
for (let ci = 0; ci < codepoints.length; ci++) {
|
|
2774
|
+
const cp = codepoints[ci];
|
|
2775
|
+
const ct = myanmarCharType(cp);
|
|
2776
|
+
if (ct === 0 || ct === 7 || ct === 8) {
|
|
2777
|
+
stackGids.push(resolveGid(cp));
|
|
2778
|
+
stackEndIdx.push(ci);
|
|
2779
|
+
} else if (ct === 2 || ct === 3 || ct === 5 || ct === 6) {
|
|
2780
|
+
markStart = ci;
|
|
1414
2781
|
break;
|
|
1415
|
-
} else if (ct
|
|
1416
|
-
emitGlyph(resolveGid(
|
|
1417
|
-
} else if (ct === 1) {
|
|
1418
|
-
emitGlyph(resolveGid(codepoints[ci]), false);
|
|
2782
|
+
} else if (ct === 4) ; else {
|
|
2783
|
+
emitGlyph(resolveGid(cp), false);
|
|
1419
2784
|
}
|
|
1420
2785
|
}
|
|
1421
|
-
|
|
1422
|
-
const ligResult = tryLig(clusterGids);
|
|
2786
|
+
const ligResult = tryLig(stackGids);
|
|
1423
2787
|
if (ligResult) {
|
|
1424
2788
|
emitGlyph(ligResult.resultGid, false);
|
|
1425
2789
|
baseGid = ligResult.resultGid;
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
const subSeq = clusterGids.slice(gi);
|
|
1430
|
-
const subLig = tryLig(subSeq);
|
|
2790
|
+
let gi = ligResult.consumed;
|
|
2791
|
+
while (gi < stackGids.length) {
|
|
2792
|
+
const subLig = tryLig(stackGids.slice(gi));
|
|
1431
2793
|
if (subLig) {
|
|
1432
2794
|
emitGlyph(subLig.resultGid, false);
|
|
1433
2795
|
gi += subLig.consumed;
|
|
1434
2796
|
} else {
|
|
1435
|
-
const origCi =
|
|
1436
|
-
const ct =
|
|
1437
|
-
if (ct === 7)
|
|
1438
|
-
|
|
1439
|
-
} else {
|
|
1440
|
-
emitGlyph(clusterGids[gi], false);
|
|
1441
|
-
}
|
|
2797
|
+
const origCi = stackEndIdx[gi];
|
|
2798
|
+
const ct = myanmarCharType(codepoints[origCi]);
|
|
2799
|
+
if (ct === 7 || ct === 8) emitGlyph(stackGids[gi], true, baseGid);
|
|
2800
|
+
else emitGlyph(stackGids[gi], false);
|
|
1442
2801
|
gi++;
|
|
1443
2802
|
}
|
|
1444
2803
|
}
|
|
1445
2804
|
} else {
|
|
1446
|
-
for (let
|
|
1447
|
-
const
|
|
1448
|
-
const ct =
|
|
1449
|
-
if (
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
emitGlyph(resolveGid(cp), true, baseGid);
|
|
1453
|
-
}
|
|
2805
|
+
for (let gi = 0; gi < stackGids.length; gi++) {
|
|
2806
|
+
const origCi = stackEndIdx[gi];
|
|
2807
|
+
const ct = myanmarCharType(codepoints[origCi]);
|
|
2808
|
+
if (gi === 0) emitGlyph(stackGids[gi], false);
|
|
2809
|
+
else if (ct === 7 || ct === 8) emitGlyph(stackGids[gi], true, baseGid);
|
|
2810
|
+
else emitGlyph(stackGids[gi], false);
|
|
1454
2811
|
}
|
|
1455
2812
|
}
|
|
1456
|
-
for (let ci =
|
|
2813
|
+
for (let ci = markStart; ci < codepoints.length; ci++) {
|
|
1457
2814
|
const cp = codepoints[ci];
|
|
1458
|
-
const ct =
|
|
1459
|
-
if (ct ===
|
|
1460
|
-
if (ct ===
|
|
1461
|
-
|
|
1462
|
-
} else if (ct === 5) {
|
|
1463
|
-
emitGlyph(resolveGid(cp), false);
|
|
1464
|
-
} else if (ct === 6) {
|
|
1465
|
-
emitGlyph(resolveGid(cp), true, baseGid);
|
|
1466
|
-
} else if (ct === 9) {
|
|
1467
|
-
emitGlyph(resolveGid(cp), false);
|
|
1468
|
-
} else {
|
|
1469
|
-
emitGlyph(resolveGid(cp), false);
|
|
1470
|
-
}
|
|
1471
|
-
}
|
|
1472
|
-
for (const postCp of splitPostComponents) {
|
|
1473
|
-
emitGlyph(resolveGid(postCp), false);
|
|
2815
|
+
const ct = myanmarCharType(cp);
|
|
2816
|
+
if (ct === 2 || ct === 3 || ct === 6) emitGlyph(resolveGid(cp), true, baseGid);
|
|
2817
|
+
else if (ct === 5) emitGlyph(resolveGid(cp), false);
|
|
2818
|
+
else if (ct === 4) ; else emitGlyph(resolveGid(cp), false);
|
|
1474
2819
|
}
|
|
1475
2820
|
}
|
|
1476
2821
|
return shaped;
|
|
@@ -1522,7 +2867,7 @@ function devanagariCharType(cp) {
|
|
|
1522
2867
|
if (cp === 2384) return 1;
|
|
1523
2868
|
return -1;
|
|
1524
2869
|
}
|
|
1525
|
-
function
|
|
2870
|
+
function isConsonant7(cp) {
|
|
1526
2871
|
return devanagariCharType(cp) === 0;
|
|
1527
2872
|
}
|
|
1528
2873
|
function buildDevanagariClusters(str) {
|
|
@@ -1537,6 +2882,10 @@ function buildDevanagariClusters(str) {
|
|
|
1537
2882
|
while (i < cps.length) {
|
|
1538
2883
|
const cp = cps[i];
|
|
1539
2884
|
const type = devanagariCharType(cp);
|
|
2885
|
+
if (classifyUseCategory(cp) === "ZWJ" || classifyUseCategory(cp) === "ZWNJ") {
|
|
2886
|
+
i++;
|
|
2887
|
+
continue;
|
|
2888
|
+
}
|
|
1540
2889
|
if (type < 0 || cp < DEVANAGARI_START || cp > DEVANAGARI_END) {
|
|
1541
2890
|
clusters.push({ codepoints: [cp], baseIndex: 0, hasReph: false, preBaseMatras: [] });
|
|
1542
2891
|
i++;
|
|
@@ -1545,7 +2894,7 @@ function buildDevanagariClusters(str) {
|
|
|
1545
2894
|
const syllable = [];
|
|
1546
2895
|
let hasReph = false;
|
|
1547
2896
|
const preMatras = [];
|
|
1548
|
-
if (
|
|
2897
|
+
if (isConsonant7(cp) && cp === RA2 && i + 2 < cps.length && cps[i + 1] === HALANT2 && isConsonant7(cps[i + 2])) {
|
|
1549
2898
|
hasReph = true;
|
|
1550
2899
|
syllable.push(cp, cps[i + 1]);
|
|
1551
2900
|
i += 2;
|
|
@@ -1563,13 +2912,24 @@ function buildDevanagariClusters(str) {
|
|
|
1563
2912
|
i++;
|
|
1564
2913
|
}
|
|
1565
2914
|
if (i < cps.length && cps[i] === HALANT2) {
|
|
1566
|
-
|
|
2915
|
+
let j = i + 1;
|
|
2916
|
+
let zwnj = false;
|
|
2917
|
+
if (j < cps.length) {
|
|
2918
|
+
const jc = classifyUseCategory(cps[j]);
|
|
2919
|
+
if (jc === "ZWJ") {
|
|
2920
|
+
j++;
|
|
2921
|
+
} else if (jc === "ZWNJ") {
|
|
2922
|
+
j++;
|
|
2923
|
+
zwnj = true;
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
2926
|
+
if (!zwnj && j < cps.length && isConsonant7(cps[j])) {
|
|
1567
2927
|
syllable.push(cps[i]);
|
|
1568
|
-
i
|
|
2928
|
+
i = j;
|
|
1569
2929
|
continue;
|
|
1570
2930
|
} else {
|
|
1571
2931
|
syllable.push(cps[i]);
|
|
1572
|
-
i
|
|
2932
|
+
i = j;
|
|
1573
2933
|
break;
|
|
1574
2934
|
}
|
|
1575
2935
|
}
|
|
@@ -1927,6 +3287,487 @@ function shapeArabicText(str, fontData) {
|
|
|
1927
3287
|
return glyphs;
|
|
1928
3288
|
}
|
|
1929
3289
|
|
|
3290
|
+
// src/fonts/font-loader.ts
|
|
3291
|
+
var _fontBinaryCache = /* @__PURE__ */ new WeakMap();
|
|
3292
|
+
function getDecodedFontBytes(fontData) {
|
|
3293
|
+
const cached = _fontBinaryCache.get(fontData);
|
|
3294
|
+
if (cached) return cached;
|
|
3295
|
+
let bytes;
|
|
3296
|
+
if (typeof atob === "function") {
|
|
3297
|
+
const binaryStr = atob(fontData.ttfBase64);
|
|
3298
|
+
bytes = new Uint8Array(binaryStr.length);
|
|
3299
|
+
for (let i = 0; i < binaryStr.length; i++) bytes[i] = binaryStr.charCodeAt(i);
|
|
3300
|
+
} else {
|
|
3301
|
+
const buf = globalThis["Buffer"];
|
|
3302
|
+
bytes = buf.from(fontData.ttfBase64, "base64");
|
|
3303
|
+
}
|
|
3304
|
+
_fontBinaryCache.set(fontData, bytes);
|
|
3305
|
+
return bytes;
|
|
3306
|
+
}
|
|
3307
|
+
|
|
3308
|
+
// src/fonts/glyf-outline.ts
|
|
3309
|
+
function readTableDirectory(view) {
|
|
3310
|
+
const numTables = view.getUint16(4);
|
|
3311
|
+
const tables = {};
|
|
3312
|
+
for (let i = 0; i < numTables; i++) {
|
|
3313
|
+
const rec = 12 + i * 16;
|
|
3314
|
+
const tag = String.fromCharCode(
|
|
3315
|
+
view.getUint8(rec),
|
|
3316
|
+
view.getUint8(rec + 1),
|
|
3317
|
+
view.getUint8(rec + 2),
|
|
3318
|
+
view.getUint8(rec + 3)
|
|
3319
|
+
);
|
|
3320
|
+
tables[tag] = { offset: view.getUint32(rec + 8), length: view.getUint32(rec + 12) };
|
|
3321
|
+
}
|
|
3322
|
+
return tables;
|
|
3323
|
+
}
|
|
3324
|
+
function parseGlyfFont(bytes) {
|
|
3325
|
+
if (bytes.length < 12) return null;
|
|
3326
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
3327
|
+
const tables = readTableDirectory(view);
|
|
3328
|
+
const head = tables["head"];
|
|
3329
|
+
const maxp = tables["maxp"];
|
|
3330
|
+
const loca = tables["loca"];
|
|
3331
|
+
const glyf = tables["glyf"];
|
|
3332
|
+
if (!head || !maxp || !loca || !glyf) return null;
|
|
3333
|
+
const unitsPerEm = view.getUint16(head.offset + 18) || 1e3;
|
|
3334
|
+
const locaFormat = view.getInt16(head.offset + 50);
|
|
3335
|
+
const numGlyphs = view.getUint16(maxp.offset + 4);
|
|
3336
|
+
const locaOffsets = new Array(numGlyphs + 1);
|
|
3337
|
+
for (let i = 0; i <= numGlyphs; i++) {
|
|
3338
|
+
locaOffsets[i] = locaFormat === 0 ? view.getUint16(loca.offset + i * 2) * 2 : view.getUint32(loca.offset + i * 4);
|
|
3339
|
+
}
|
|
3340
|
+
return { view, glyfOffset: glyf.offset, locaOffsets, unitsPerEm };
|
|
3341
|
+
}
|
|
3342
|
+
function transformPoint(p, a, b, c, d, e, f) {
|
|
3343
|
+
return {
|
|
3344
|
+
x: a * p.x + c * p.y + e,
|
|
3345
|
+
y: b * p.x + d * p.y + f,
|
|
3346
|
+
onCurve: p.onCurve
|
|
3347
|
+
};
|
|
3348
|
+
}
|
|
3349
|
+
function readF2Dot14(view, pos) {
|
|
3350
|
+
return view.getInt16(pos) / 16384;
|
|
3351
|
+
}
|
|
3352
|
+
function extractGlyphContours(font, gid, depth = 0) {
|
|
3353
|
+
if (depth > 8) return [];
|
|
3354
|
+
const { view, glyfOffset, locaOffsets } = font;
|
|
3355
|
+
if (gid < 0 || gid + 1 >= locaOffsets.length) return [];
|
|
3356
|
+
const start = locaOffsets[gid];
|
|
3357
|
+
const end = locaOffsets[gid + 1];
|
|
3358
|
+
if (end <= start) return [];
|
|
3359
|
+
const base = glyfOffset + start;
|
|
3360
|
+
const numberOfContours = view.getInt16(base);
|
|
3361
|
+
if (numberOfContours < 0) {
|
|
3362
|
+
return extractCompositeContours(font, base, depth);
|
|
3363
|
+
}
|
|
3364
|
+
return extractSimpleContours(view, base, numberOfContours);
|
|
3365
|
+
}
|
|
3366
|
+
function extractSimpleContours(view, base, numberOfContours) {
|
|
3367
|
+
let pos = base + 10;
|
|
3368
|
+
const endPts = new Array(numberOfContours);
|
|
3369
|
+
for (let i = 0; i < numberOfContours; i++) {
|
|
3370
|
+
endPts[i] = view.getUint16(pos);
|
|
3371
|
+
pos += 2;
|
|
3372
|
+
}
|
|
3373
|
+
const numPoints = numberOfContours > 0 ? endPts[numberOfContours - 1] + 1 : 0;
|
|
3374
|
+
const instrLen = view.getUint16(pos);
|
|
3375
|
+
pos += 2 + instrLen;
|
|
3376
|
+
const flags = new Array(numPoints);
|
|
3377
|
+
for (let i = 0; i < numPoints; ) {
|
|
3378
|
+
const flag = view.getUint8(pos++);
|
|
3379
|
+
flags[i++] = flag;
|
|
3380
|
+
if (flag & 8) {
|
|
3381
|
+
let repeat = view.getUint8(pos++);
|
|
3382
|
+
while (repeat-- > 0 && i < numPoints) flags[i++] = flag;
|
|
3383
|
+
}
|
|
3384
|
+
}
|
|
3385
|
+
const xs = new Array(numPoints);
|
|
3386
|
+
let x = 0;
|
|
3387
|
+
for (let i = 0; i < numPoints; i++) {
|
|
3388
|
+
const flag = flags[i];
|
|
3389
|
+
if (flag & 2) {
|
|
3390
|
+
const dx = view.getUint8(pos++);
|
|
3391
|
+
x += flag & 16 ? dx : -dx;
|
|
3392
|
+
} else if (!(flag & 16)) {
|
|
3393
|
+
x += view.getInt16(pos);
|
|
3394
|
+
pos += 2;
|
|
3395
|
+
}
|
|
3396
|
+
xs[i] = x;
|
|
3397
|
+
}
|
|
3398
|
+
const ys = new Array(numPoints);
|
|
3399
|
+
let y = 0;
|
|
3400
|
+
for (let i = 0; i < numPoints; i++) {
|
|
3401
|
+
const flag = flags[i];
|
|
3402
|
+
if (flag & 4) {
|
|
3403
|
+
const dy = view.getUint8(pos++);
|
|
3404
|
+
y += flag & 32 ? dy : -dy;
|
|
3405
|
+
} else if (!(flag & 32)) {
|
|
3406
|
+
y += view.getInt16(pos);
|
|
3407
|
+
pos += 2;
|
|
3408
|
+
}
|
|
3409
|
+
ys[i] = y;
|
|
3410
|
+
}
|
|
3411
|
+
const contours = [];
|
|
3412
|
+
let startPt = 0;
|
|
3413
|
+
for (let c = 0; c < numberOfContours; c++) {
|
|
3414
|
+
const endPt = endPts[c];
|
|
3415
|
+
const contour = [];
|
|
3416
|
+
for (let i = startPt; i <= endPt; i++) {
|
|
3417
|
+
contour.push({ x: xs[i], y: ys[i], onCurve: (flags[i] & 1) !== 0 });
|
|
3418
|
+
}
|
|
3419
|
+
if (contour.length > 0) contours.push(contour);
|
|
3420
|
+
startPt = endPt + 1;
|
|
3421
|
+
}
|
|
3422
|
+
return contours;
|
|
3423
|
+
}
|
|
3424
|
+
function extractCompositeContours(font, base, depth) {
|
|
3425
|
+
const { view } = font;
|
|
3426
|
+
let pos = base + 10;
|
|
3427
|
+
const out = [];
|
|
3428
|
+
while (true) {
|
|
3429
|
+
const flags = view.getUint16(pos);
|
|
3430
|
+
pos += 2;
|
|
3431
|
+
const componentGid = view.getUint16(pos);
|
|
3432
|
+
pos += 2;
|
|
3433
|
+
let arg1;
|
|
3434
|
+
let arg2;
|
|
3435
|
+
if (flags & 1) {
|
|
3436
|
+
arg1 = view.getInt16(pos);
|
|
3437
|
+
pos += 2;
|
|
3438
|
+
arg2 = view.getInt16(pos);
|
|
3439
|
+
pos += 2;
|
|
3440
|
+
} else {
|
|
3441
|
+
arg1 = view.getInt8(pos);
|
|
3442
|
+
pos += 1;
|
|
3443
|
+
arg2 = view.getInt8(pos);
|
|
3444
|
+
pos += 1;
|
|
3445
|
+
}
|
|
3446
|
+
let a = 1, b = 0, c = 0, d = 1;
|
|
3447
|
+
if (flags & 8) {
|
|
3448
|
+
a = d = readF2Dot14(view, pos);
|
|
3449
|
+
pos += 2;
|
|
3450
|
+
} else if (flags & 64) {
|
|
3451
|
+
a = readF2Dot14(view, pos);
|
|
3452
|
+
pos += 2;
|
|
3453
|
+
d = readF2Dot14(view, pos);
|
|
3454
|
+
pos += 2;
|
|
3455
|
+
} else if (flags & 128) {
|
|
3456
|
+
a = readF2Dot14(view, pos);
|
|
3457
|
+
pos += 2;
|
|
3458
|
+
b = readF2Dot14(view, pos);
|
|
3459
|
+
pos += 2;
|
|
3460
|
+
c = readF2Dot14(view, pos);
|
|
3461
|
+
pos += 2;
|
|
3462
|
+
d = readF2Dot14(view, pos);
|
|
3463
|
+
pos += 2;
|
|
3464
|
+
}
|
|
3465
|
+
const e = flags & 2 ? arg1 : 0;
|
|
3466
|
+
const f = flags & 2 ? arg2 : 0;
|
|
3467
|
+
const sub = extractGlyphContours(font, componentGid, depth + 1);
|
|
3468
|
+
for (const contour of sub) {
|
|
3469
|
+
out.push(contour.map((p) => transformPoint(p, a, b, c, d, e, f)));
|
|
3470
|
+
}
|
|
3471
|
+
if (!(flags & 32)) break;
|
|
3472
|
+
}
|
|
3473
|
+
return out;
|
|
3474
|
+
}
|
|
3475
|
+
|
|
3476
|
+
// src/core/pdf-color-glyph.ts
|
|
3477
|
+
var ID = [1, 0, 0, 1, 0, 0];
|
|
3478
|
+
function n(v) {
|
|
3479
|
+
if (!Number.isFinite(v)) return "0";
|
|
3480
|
+
if (Number.isInteger(v)) return String(v);
|
|
3481
|
+
let s = v.toFixed(3);
|
|
3482
|
+
s = s.replace(/0+$/, "").replace(/\.$/, "");
|
|
3483
|
+
return s === "-0" ? "0" : s;
|
|
3484
|
+
}
|
|
3485
|
+
function tx(m, x, y) {
|
|
3486
|
+
return [m[0] * x + m[2] * y + m[4], m[1] * x + m[3] * y + m[5]];
|
|
3487
|
+
}
|
|
3488
|
+
function ch(v) {
|
|
3489
|
+
return n(Math.max(0, Math.min(1, v / 255)));
|
|
3490
|
+
}
|
|
3491
|
+
function contoursToPath(contours, m = ID) {
|
|
3492
|
+
const ops = [];
|
|
3493
|
+
for (const contour of contours) {
|
|
3494
|
+
if (contour.length === 0) continue;
|
|
3495
|
+
const pts = contour.slice();
|
|
3496
|
+
let startIdx = pts.findIndex((p) => p.onCurve);
|
|
3497
|
+
let start;
|
|
3498
|
+
if (startIdx < 0) {
|
|
3499
|
+
const a = pts[0], b = pts[pts.length - 1];
|
|
3500
|
+
start = [(a.x + b.x) / 2, (a.y + b.y) / 2];
|
|
3501
|
+
startIdx = 0;
|
|
3502
|
+
} else {
|
|
3503
|
+
start = [pts[startIdx].x, pts[startIdx].y];
|
|
3504
|
+
}
|
|
3505
|
+
const [sx, sy] = tx(m, start[0], start[1]);
|
|
3506
|
+
ops.push(`${n(sx)} ${n(sy)} m`);
|
|
3507
|
+
const len = pts.length;
|
|
3508
|
+
let curX = start[0], curY = start[1];
|
|
3509
|
+
let i = 1;
|
|
3510
|
+
while (i <= len) {
|
|
3511
|
+
const p = pts[(startIdx + i) % len];
|
|
3512
|
+
if (p.onCurve) {
|
|
3513
|
+
const [px, py] = tx(m, p.x, p.y);
|
|
3514
|
+
ops.push(`${n(px)} ${n(py)} l`);
|
|
3515
|
+
curX = p.x;
|
|
3516
|
+
curY = p.y;
|
|
3517
|
+
i++;
|
|
3518
|
+
} else {
|
|
3519
|
+
const next = pts[(startIdx + i + 1) % len];
|
|
3520
|
+
let endX, endY;
|
|
3521
|
+
let consumed;
|
|
3522
|
+
if (next.onCurve) {
|
|
3523
|
+
endX = next.x;
|
|
3524
|
+
endY = next.y;
|
|
3525
|
+
consumed = 2;
|
|
3526
|
+
} else {
|
|
3527
|
+
endX = (p.x + next.x) / 2;
|
|
3528
|
+
endY = (p.y + next.y) / 2;
|
|
3529
|
+
consumed = 1;
|
|
3530
|
+
}
|
|
3531
|
+
const c1x = curX + 2 / 3 * (p.x - curX);
|
|
3532
|
+
const c1y = curY + 2 / 3 * (p.y - curY);
|
|
3533
|
+
const c2x = endX + 2 / 3 * (p.x - endX);
|
|
3534
|
+
const c2y = endY + 2 / 3 * (p.y - endY);
|
|
3535
|
+
const [a1, b1] = tx(m, c1x, c1y);
|
|
3536
|
+
const [a2, b2] = tx(m, c2x, c2y);
|
|
3537
|
+
const [ex, ey] = tx(m, endX, endY);
|
|
3538
|
+
ops.push(`${n(a1)} ${n(b1)} ${n(a2)} ${n(b2)} ${n(ex)} ${n(ey)} c`);
|
|
3539
|
+
curX = endX;
|
|
3540
|
+
curY = endY;
|
|
3541
|
+
i += consumed;
|
|
3542
|
+
}
|
|
3543
|
+
}
|
|
3544
|
+
ops.push("h");
|
|
3545
|
+
}
|
|
3546
|
+
return ops.join("\n");
|
|
3547
|
+
}
|
|
3548
|
+
function buildGradientFunction(stops) {
|
|
3549
|
+
const sorted = stops.slice().sort((a, b) => a.offset - b.offset);
|
|
3550
|
+
if (sorted.length === 0) return "<< /FunctionType 2 /Domain [0 1] /C0 [0 0 0] /C1 [0 0 0] /N 1 >>";
|
|
3551
|
+
if (sorted.length === 1) {
|
|
3552
|
+
const c = sorted[0].color;
|
|
3553
|
+
return `<< /FunctionType 2 /Domain [0 1] /C0 [${ch(c[0])} ${ch(c[1])} ${ch(c[2])}] /C1 [${ch(c[0])} ${ch(c[1])} ${ch(c[2])}] /N 1 >>`;
|
|
3554
|
+
}
|
|
3555
|
+
if (sorted.length === 2) {
|
|
3556
|
+
const a = sorted[0].color, b = sorted[1].color;
|
|
3557
|
+
return `<< /FunctionType 2 /Domain [0 1] /C0 [${ch(a[0])} ${ch(a[1])} ${ch(a[2])}] /C1 [${ch(b[0])} ${ch(b[1])} ${ch(b[2])}] /N 1 >>`;
|
|
3558
|
+
}
|
|
3559
|
+
const subFns = [];
|
|
3560
|
+
const bounds = [];
|
|
3561
|
+
const encode = [];
|
|
3562
|
+
for (let i = 0; i < sorted.length - 1; i++) {
|
|
3563
|
+
const a = sorted[i].color, b = sorted[i + 1].color;
|
|
3564
|
+
subFns.push(`<< /FunctionType 2 /Domain [0 1] /C0 [${ch(a[0])} ${ch(a[1])} ${ch(a[2])}] /C1 [${ch(b[0])} ${ch(b[1])} ${ch(b[2])}] /N 1 >>`);
|
|
3565
|
+
encode.push("0 1");
|
|
3566
|
+
if (i > 0) bounds.push(n(Math.max(0, Math.min(1, sorted[i].offset))));
|
|
3567
|
+
}
|
|
3568
|
+
return `<< /FunctionType 3 /Domain [0 1] /Functions [${subFns.join(" ")}] /Bounds [${bounds.join(" ")}] /Encode [${encode.join(" ")}] >>`;
|
|
3569
|
+
}
|
|
3570
|
+
function extendFlags(extend) {
|
|
3571
|
+
return extend === "pad" ? "[true true]" : "[true true]";
|
|
3572
|
+
}
|
|
3573
|
+
function linearShadingDict(p, m) {
|
|
3574
|
+
const [x0, y0] = tx(m, p.p0[0], p.p0[1]);
|
|
3575
|
+
const [x1, y1] = tx(m, p.p1[0], p.p1[1]);
|
|
3576
|
+
return `<< /ShadingType 2 /ColorSpace /DeviceRGB /Coords [${n(x0)} ${n(y0)} ${n(x1)} ${n(y1)}] /Function ${buildGradientFunction(p.stops)} /Extend ${extendFlags(p.extend)} >>`;
|
|
3577
|
+
}
|
|
3578
|
+
function radialShadingDict(p, m) {
|
|
3579
|
+
const [x0, y0] = tx(m, p.c0[0], p.c0[1]);
|
|
3580
|
+
const [x1, y1] = tx(m, p.c1[0], p.c1[1]);
|
|
3581
|
+
const sx = Math.hypot(m[0], m[1]);
|
|
3582
|
+
const sy = Math.hypot(m[2], m[3]);
|
|
3583
|
+
const s = (sx + sy) / 2 || 1;
|
|
3584
|
+
return `<< /ShadingType 3 /ColorSpace /DeviceRGB /Coords [${n(x0)} ${n(y0)} ${n(p.r0 * s)} ${n(x1)} ${n(y1)} ${n(p.r1 * s)}] /Function ${buildGradientFunction(p.stops)} /Extend ${extendFlags(p.extend)} >>`;
|
|
3585
|
+
}
|
|
3586
|
+
function colorAtOffset(stops, t) {
|
|
3587
|
+
if (stops.length === 0) return [0, 0, 0, 255];
|
|
3588
|
+
const sorted = stops.slice().sort((a, b) => a.offset - b.offset);
|
|
3589
|
+
if (t <= sorted[0].offset) return sorted[0].color;
|
|
3590
|
+
const last = sorted[sorted.length - 1];
|
|
3591
|
+
if (t >= last.offset) return last.color;
|
|
3592
|
+
for (let i = 0; i < sorted.length - 1; i++) {
|
|
3593
|
+
const a = sorted[i], b = sorted[i + 1];
|
|
3594
|
+
if (t >= a.offset && t <= b.offset) {
|
|
3595
|
+
const span = b.offset - a.offset || 1;
|
|
3596
|
+
const f = (t - a.offset) / span;
|
|
3597
|
+
return [
|
|
3598
|
+
Math.round(a.color[0] + (b.color[0] - a.color[0]) * f),
|
|
3599
|
+
Math.round(a.color[1] + (b.color[1] - a.color[1]) * f),
|
|
3600
|
+
Math.round(a.color[2] + (b.color[2] - a.color[2]) * f),
|
|
3601
|
+
Math.round(a.color[3] + (b.color[3] - a.color[3]) * f)
|
|
3602
|
+
];
|
|
3603
|
+
}
|
|
3604
|
+
}
|
|
3605
|
+
return last.color;
|
|
3606
|
+
}
|
|
3607
|
+
function emitSweep(p, cx, cy, maxR, body, gsFor, blendMode) {
|
|
3608
|
+
const start = p.startAngle;
|
|
3609
|
+
const end = p.endAngle;
|
|
3610
|
+
const span = end - start;
|
|
3611
|
+
if (Math.abs(span) < 0.01) {
|
|
3612
|
+
const c = colorAtOffset(p.stops, 0);
|
|
3613
|
+
const gs = gsFor(c[3] / 255, blendMode);
|
|
3614
|
+
if (gs) body.push(`/${gs} gs`);
|
|
3615
|
+
body.push(`${ch(c[0])} ${ch(c[1])} ${ch(c[2])} rg`);
|
|
3616
|
+
body.push(`${n(cx - maxR)} ${n(cy - maxR)} ${n(2 * maxR)} ${n(2 * maxR)} re`);
|
|
3617
|
+
body.push("f");
|
|
3618
|
+
return;
|
|
3619
|
+
}
|
|
3620
|
+
const steps = Math.max(12, Math.min(180, Math.ceil(Math.abs(span) / 3)));
|
|
3621
|
+
const r = maxR * 1.5;
|
|
3622
|
+
const rad = Math.PI / 180;
|
|
3623
|
+
for (let i = 0; i < steps; i++) {
|
|
3624
|
+
const a0 = start + span * i / steps;
|
|
3625
|
+
const a1 = start + span * (i + 1) / steps;
|
|
3626
|
+
const tMid = (i + 0.5) / steps;
|
|
3627
|
+
const c = colorAtOffset(p.stops, tMid);
|
|
3628
|
+
const gs = gsFor(c[3] / 255, blendMode);
|
|
3629
|
+
body.push("q");
|
|
3630
|
+
if (gs) body.push(`/${gs} gs`);
|
|
3631
|
+
body.push(`${ch(c[0])} ${ch(c[1])} ${ch(c[2])} rg`);
|
|
3632
|
+
const x0 = cx + r * Math.cos(a0 * rad), y0 = cy + r * Math.sin(a0 * rad);
|
|
3633
|
+
const x1 = cx + r * Math.cos(a1 * rad), y1 = cy + r * Math.sin(a1 * rad);
|
|
3634
|
+
body.push(`${n(cx)} ${n(cy)} m ${n(x0)} ${n(y0)} l ${n(x1)} ${n(y1)} l h`);
|
|
3635
|
+
body.push("f");
|
|
3636
|
+
body.push("Q");
|
|
3637
|
+
}
|
|
3638
|
+
}
|
|
3639
|
+
function renderColorGlyph(glyph, outlines, unitsPerEm) {
|
|
3640
|
+
const body = [];
|
|
3641
|
+
const shadings = [];
|
|
3642
|
+
const extGStates = [];
|
|
3643
|
+
const gsMap = /* @__PURE__ */ new Map();
|
|
3644
|
+
let shadingIdx = 0;
|
|
3645
|
+
const gsFor = (alpha, bm) => {
|
|
3646
|
+
const a = Math.max(0, Math.min(1, alpha));
|
|
3647
|
+
const needAlpha = a < 0.999;
|
|
3648
|
+
const needBm = bm !== void 0 && bm !== "Normal";
|
|
3649
|
+
if (!needAlpha && !needBm) return "";
|
|
3650
|
+
const key = `${needAlpha ? a.toFixed(3) : "1"}|${needBm ? bm : ""}`;
|
|
3651
|
+
let name = gsMap.get(key);
|
|
3652
|
+
if (!name) {
|
|
3653
|
+
name = `Gs${gsMap.size}`;
|
|
3654
|
+
gsMap.set(key, name);
|
|
3655
|
+
const parts = [];
|
|
3656
|
+
if (needAlpha) parts.push(`/ca ${n(a)}`, `/CA ${n(a)}`);
|
|
3657
|
+
if (needBm) parts.push(`/BM /${bm}`);
|
|
3658
|
+
extGStates.push({ name, dict: `<< ${parts.join(" ")} >>` });
|
|
3659
|
+
}
|
|
3660
|
+
return name;
|
|
3661
|
+
};
|
|
3662
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
3663
|
+
for (const layer of glyph.layers) {
|
|
3664
|
+
const m = layer.transform ?? ID;
|
|
3665
|
+
const bm = layer.blendMode;
|
|
3666
|
+
const contours = outlines(layer.glyphId);
|
|
3667
|
+
if (contours.length === 0) continue;
|
|
3668
|
+
const path = contoursToPath(contours, m);
|
|
3669
|
+
for (const contour of contours) {
|
|
3670
|
+
for (const pt of contour) {
|
|
3671
|
+
const [px, py] = tx(m, pt.x, pt.y);
|
|
3672
|
+
if (px < minX) minX = px;
|
|
3673
|
+
if (py < minY) minY = py;
|
|
3674
|
+
if (px > maxX) maxX = px;
|
|
3675
|
+
if (py > maxY) maxY = py;
|
|
3676
|
+
}
|
|
3677
|
+
}
|
|
3678
|
+
if (layer.paint.kind === "solid") {
|
|
3679
|
+
const c = layer.paint.color;
|
|
3680
|
+
body.push("q");
|
|
3681
|
+
const gs = gsFor(c[3] / 255, bm);
|
|
3682
|
+
if (gs) body.push(`/${gs} gs`);
|
|
3683
|
+
body.push(`${ch(c[0])} ${ch(c[1])} ${ch(c[2])} rg`);
|
|
3684
|
+
body.push(path);
|
|
3685
|
+
body.push("f");
|
|
3686
|
+
body.push("Q");
|
|
3687
|
+
} else if (layer.paint.kind === "sweep") {
|
|
3688
|
+
const [cx, cy] = tx(m, layer.paint.center[0], layer.paint.center[1]);
|
|
3689
|
+
let r2 = 0;
|
|
3690
|
+
for (const contour of contours) {
|
|
3691
|
+
for (const pt of contour) {
|
|
3692
|
+
const [px, py] = tx(m, pt.x, pt.y);
|
|
3693
|
+
const d = (px - cx) * (px - cx) + (py - cy) * (py - cy);
|
|
3694
|
+
if (d > r2) r2 = d;
|
|
3695
|
+
}
|
|
3696
|
+
}
|
|
3697
|
+
const maxR = Math.sqrt(r2) || 1;
|
|
3698
|
+
body.push("q");
|
|
3699
|
+
body.push(path);
|
|
3700
|
+
body.push("W n");
|
|
3701
|
+
emitSweep(layer.paint, cx, cy, maxR, body, gsFor, bm);
|
|
3702
|
+
body.push("Q");
|
|
3703
|
+
} else {
|
|
3704
|
+
const name = `Sh${shadingIdx++}`;
|
|
3705
|
+
const dict = layer.paint.kind === "linear" ? linearShadingDict(layer.paint, m) : radialShadingDict(layer.paint, m);
|
|
3706
|
+
shadings.push({ name, dict });
|
|
3707
|
+
body.push("q");
|
|
3708
|
+
const gs = gsFor(1, bm);
|
|
3709
|
+
if (gs) body.push(`/${gs} gs`);
|
|
3710
|
+
body.push(path);
|
|
3711
|
+
body.push("W n");
|
|
3712
|
+
body.push(`/${name} sh`);
|
|
3713
|
+
body.push("Q");
|
|
3714
|
+
}
|
|
3715
|
+
}
|
|
3716
|
+
const bbox = Number.isFinite(minX) ? [Math.floor(minX) - 1, Math.floor(minY) - 1, Math.ceil(maxX) + 1, Math.ceil(maxY) + 1] : [0, 0, unitsPerEm, unitsPerEm];
|
|
3717
|
+
return {
|
|
3718
|
+
content: body.join("\n"),
|
|
3719
|
+
bbox,
|
|
3720
|
+
shadings,
|
|
3721
|
+
extGStates
|
|
3722
|
+
};
|
|
3723
|
+
}
|
|
3724
|
+
|
|
3725
|
+
// src/core/color-emoji.ts
|
|
3726
|
+
function createColorEmojiCollector() {
|
|
3727
|
+
const forms = [];
|
|
3728
|
+
const nameByGlyph = /* @__PURE__ */ new WeakMap();
|
|
3729
|
+
const glyfByFont = /* @__PURE__ */ new WeakMap();
|
|
3730
|
+
function glyfFor(fontData) {
|
|
3731
|
+
let g = glyfByFont.get(fontData);
|
|
3732
|
+
if (g === void 0) {
|
|
3733
|
+
g = parseGlyfFont(getDecodedFontBytes(fontData));
|
|
3734
|
+
glyfByFont.set(fontData, g);
|
|
3735
|
+
}
|
|
3736
|
+
return g;
|
|
3737
|
+
}
|
|
3738
|
+
function useGlyph(fontData, gid) {
|
|
3739
|
+
const colorGlyph = fontData.colorGlyphs?.[gid];
|
|
3740
|
+
if (!colorGlyph) return null;
|
|
3741
|
+
let perFont = nameByGlyph.get(fontData);
|
|
3742
|
+
if (!perFont) {
|
|
3743
|
+
perFont = /* @__PURE__ */ new Map();
|
|
3744
|
+
nameByGlyph.set(fontData, perFont);
|
|
3745
|
+
}
|
|
3746
|
+
const cached = perFont.get(gid);
|
|
3747
|
+
if (cached) return cached;
|
|
3748
|
+
const glyf = glyfFor(fontData);
|
|
3749
|
+
if (!glyf) return null;
|
|
3750
|
+
const rendered = renderColorGlyph(
|
|
3751
|
+
colorGlyph,
|
|
3752
|
+
(baseGid) => extractGlyphContours(glyf, baseGid),
|
|
3753
|
+
fontData.metrics.unitsPerEm
|
|
3754
|
+
);
|
|
3755
|
+
if (rendered.content.trim() === "") return null;
|
|
3756
|
+
const name = `CEm${forms.length}`;
|
|
3757
|
+
const resParts = [];
|
|
3758
|
+
if (rendered.shadings.length > 0) {
|
|
3759
|
+
resParts.push(`/Shading << ${rendered.shadings.map((s) => `/${s.name} ${s.dict}`).join(" ")} >>`);
|
|
3760
|
+
}
|
|
3761
|
+
if (rendered.extGStates.length > 0) {
|
|
3762
|
+
resParts.push(`/ExtGState << ${rendered.extGStates.map((g) => `/${g.name} ${g.dict}`).join(" ")} >>`);
|
|
3763
|
+
}
|
|
3764
|
+
forms.push({ name, content: rendered.content, resources: resParts.join(" "), bbox: rendered.bbox });
|
|
3765
|
+
perFont.set(gid, name);
|
|
3766
|
+
return name;
|
|
3767
|
+
}
|
|
3768
|
+
return { useGlyph, forms };
|
|
3769
|
+
}
|
|
3770
|
+
|
|
1930
3771
|
// src/core/encoding-context.ts
|
|
1931
3772
|
function isWinAnsi(cp) {
|
|
1932
3773
|
if (cp >= 32 && cp <= 126 || cp >= 160 && cp <= 255) return true;
|
|
@@ -2031,13 +3872,14 @@ function buildTextRunsWithFallback(text, fontRef, fd, sz, trackGid, pdfA = false
|
|
|
2031
3872
|
if (mode === "hel") flushHel();
|
|
2032
3873
|
return result;
|
|
2033
3874
|
}
|
|
2034
|
-
function createEncodingContext(fontEntries, pdfA = false) {
|
|
3875
|
+
function createEncodingContext(fontEntries, pdfA = false, normalize = false) {
|
|
3876
|
+
const _norm = normalize ? (s) => s.normalize(normalize) : (s) => s;
|
|
2035
3877
|
if (!fontEntries || fontEntries.length === 0) {
|
|
2036
3878
|
return {
|
|
2037
3879
|
isUnicode: false,
|
|
2038
3880
|
fontEntries: [],
|
|
2039
|
-
ps: pdfString,
|
|
2040
|
-
tw: helveticaWidth,
|
|
3881
|
+
ps: normalize ? (s) => pdfString(_norm(s)) : pdfString,
|
|
3882
|
+
tw: normalize ? (s, sz) => helveticaWidth(_norm(s), sz) : helveticaWidth,
|
|
2041
3883
|
textRuns: () => [],
|
|
2042
3884
|
f1: "/F1",
|
|
2043
3885
|
f2: "/F2"
|
|
@@ -2050,6 +3892,7 @@ function createEncodingContext(fontEntries, pdfA = false) {
|
|
|
2050
3892
|
const s = _usedGids.get(fontRef);
|
|
2051
3893
|
if (s) s.add(gid);
|
|
2052
3894
|
}
|
|
3895
|
+
const _colorEmoji = fontEntries.some((fe) => fe.fontData.colorGlyphs) ? createColorEmojiCollector() : void 0;
|
|
2053
3896
|
return {
|
|
2054
3897
|
isUnicode: true,
|
|
2055
3898
|
fontEntries,
|
|
@@ -2059,8 +3902,10 @@ function createEncodingContext(fontEntries, pdfA = false) {
|
|
|
2059
3902
|
getUsedGids() {
|
|
2060
3903
|
return _usedGids;
|
|
2061
3904
|
},
|
|
3905
|
+
colorEmoji: _colorEmoji,
|
|
2062
3906
|
textRuns(str, sz) {
|
|
2063
3907
|
if (!str) return [];
|
|
3908
|
+
str = _norm(str);
|
|
2064
3909
|
str = stripBidiControls(str);
|
|
2065
3910
|
if (!str) return [];
|
|
2066
3911
|
if (containsRTL(str)) {
|
|
@@ -2127,6 +3972,56 @@ function createEncodingContext(fontEntries, pdfA = false) {
|
|
|
2127
3972
|
}
|
|
2128
3973
|
}
|
|
2129
3974
|
result.push({ text: fRun.text, fontRef, fontData: fd, shaped, hexStr: null, widthPt: designW * sz / upm });
|
|
3975
|
+
} else if (containsTelugu(fRun.text)) {
|
|
3976
|
+
const shaped = shapeTeluguText(fRun.text, fd);
|
|
3977
|
+
let designW = 0;
|
|
3978
|
+
for (const g of shaped) {
|
|
3979
|
+
_trackGid(fontRef, g.gid);
|
|
3980
|
+
if (!g.isZeroAdvance) {
|
|
3981
|
+
designW += fd.widths[g.gid] !== void 0 ? fd.widths[g.gid] : fd.defaultWidth;
|
|
3982
|
+
}
|
|
3983
|
+
}
|
|
3984
|
+
result.push({ text: fRun.text, fontRef, fontData: fd, shaped, hexStr: null, widthPt: designW * sz / upm });
|
|
3985
|
+
} else if (containsSinhala(fRun.text)) {
|
|
3986
|
+
const shaped = shapeSinhalaText(fRun.text, fd);
|
|
3987
|
+
let designW = 0;
|
|
3988
|
+
for (const g of shaped) {
|
|
3989
|
+
_trackGid(fontRef, g.gid);
|
|
3990
|
+
if (!g.isZeroAdvance) {
|
|
3991
|
+
designW += fd.widths[g.gid] !== void 0 ? fd.widths[g.gid] : fd.defaultWidth;
|
|
3992
|
+
}
|
|
3993
|
+
}
|
|
3994
|
+
result.push({ text: fRun.text, fontRef, fontData: fd, shaped, hexStr: null, widthPt: designW * sz / upm });
|
|
3995
|
+
} else if (containsTibetan(fRun.text)) {
|
|
3996
|
+
const shaped = shapeTibetanText(fRun.text, fd);
|
|
3997
|
+
let designW = 0;
|
|
3998
|
+
for (const g of shaped) {
|
|
3999
|
+
_trackGid(fontRef, g.gid);
|
|
4000
|
+
if (!g.isZeroAdvance) {
|
|
4001
|
+
designW += fd.widths[g.gid] !== void 0 ? fd.widths[g.gid] : fd.defaultWidth;
|
|
4002
|
+
}
|
|
4003
|
+
}
|
|
4004
|
+
result.push({ text: fRun.text, fontRef, fontData: fd, shaped, hexStr: null, widthPt: designW * sz / upm });
|
|
4005
|
+
} else if (containsKhmer(fRun.text)) {
|
|
4006
|
+
const shaped = shapeKhmerText(fRun.text, fd);
|
|
4007
|
+
let designW = 0;
|
|
4008
|
+
for (const g of shaped) {
|
|
4009
|
+
_trackGid(fontRef, g.gid);
|
|
4010
|
+
if (!g.isZeroAdvance) {
|
|
4011
|
+
designW += fd.widths[g.gid] !== void 0 ? fd.widths[g.gid] : fd.defaultWidth;
|
|
4012
|
+
}
|
|
4013
|
+
}
|
|
4014
|
+
result.push({ text: fRun.text, fontRef, fontData: fd, shaped, hexStr: null, widthPt: designW * sz / upm });
|
|
4015
|
+
} else if (containsMyanmar(fRun.text)) {
|
|
4016
|
+
const shaped = shapeMyanmarText(fRun.text, fd);
|
|
4017
|
+
let designW = 0;
|
|
4018
|
+
for (const g of shaped) {
|
|
4019
|
+
_trackGid(fontRef, g.gid);
|
|
4020
|
+
if (!g.isZeroAdvance) {
|
|
4021
|
+
designW += fd.widths[g.gid] !== void 0 ? fd.widths[g.gid] : fd.defaultWidth;
|
|
4022
|
+
}
|
|
4023
|
+
}
|
|
4024
|
+
result.push({ text: fRun.text, fontRef, fontData: fd, shaped, hexStr: null, widthPt: designW * sz / upm });
|
|
2130
4025
|
} else if (containsDevanagari(fRun.text)) {
|
|
2131
4026
|
const shaped = shapeDevanagariText(fRun.text, fd);
|
|
2132
4027
|
let designW = 0;
|
|
@@ -2184,6 +4079,61 @@ function createEncodingContext(fontEntries, pdfA = false) {
|
|
|
2184
4079
|
}
|
|
2185
4080
|
return [{ text: run.text, fontRef, fontData: fd, shaped, hexStr: null, widthPt: designW * sz / upm }];
|
|
2186
4081
|
}
|
|
4082
|
+
if (containsTelugu(run.text)) {
|
|
4083
|
+
const shaped = shapeTeluguText(run.text, fd);
|
|
4084
|
+
let designW = 0;
|
|
4085
|
+
for (const g of shaped) {
|
|
4086
|
+
_trackGid(fontRef, g.gid);
|
|
4087
|
+
if (!g.isZeroAdvance) {
|
|
4088
|
+
designW += fd.widths[g.gid] !== void 0 ? fd.widths[g.gid] : fd.defaultWidth;
|
|
4089
|
+
}
|
|
4090
|
+
}
|
|
4091
|
+
return [{ text: run.text, fontRef, fontData: fd, shaped, hexStr: null, widthPt: designW * sz / upm }];
|
|
4092
|
+
}
|
|
4093
|
+
if (containsSinhala(run.text)) {
|
|
4094
|
+
const shaped = shapeSinhalaText(run.text, fd);
|
|
4095
|
+
let designW = 0;
|
|
4096
|
+
for (const g of shaped) {
|
|
4097
|
+
_trackGid(fontRef, g.gid);
|
|
4098
|
+
if (!g.isZeroAdvance) {
|
|
4099
|
+
designW += fd.widths[g.gid] !== void 0 ? fd.widths[g.gid] : fd.defaultWidth;
|
|
4100
|
+
}
|
|
4101
|
+
}
|
|
4102
|
+
return [{ text: run.text, fontRef, fontData: fd, shaped, hexStr: null, widthPt: designW * sz / upm }];
|
|
4103
|
+
}
|
|
4104
|
+
if (containsTibetan(run.text)) {
|
|
4105
|
+
const shaped = shapeTibetanText(run.text, fd);
|
|
4106
|
+
let designW = 0;
|
|
4107
|
+
for (const g of shaped) {
|
|
4108
|
+
_trackGid(fontRef, g.gid);
|
|
4109
|
+
if (!g.isZeroAdvance) {
|
|
4110
|
+
designW += fd.widths[g.gid] !== void 0 ? fd.widths[g.gid] : fd.defaultWidth;
|
|
4111
|
+
}
|
|
4112
|
+
}
|
|
4113
|
+
return [{ text: run.text, fontRef, fontData: fd, shaped, hexStr: null, widthPt: designW * sz / upm }];
|
|
4114
|
+
}
|
|
4115
|
+
if (containsKhmer(run.text)) {
|
|
4116
|
+
const shaped = shapeKhmerText(run.text, fd);
|
|
4117
|
+
let designW = 0;
|
|
4118
|
+
for (const g of shaped) {
|
|
4119
|
+
_trackGid(fontRef, g.gid);
|
|
4120
|
+
if (!g.isZeroAdvance) {
|
|
4121
|
+
designW += fd.widths[g.gid] !== void 0 ? fd.widths[g.gid] : fd.defaultWidth;
|
|
4122
|
+
}
|
|
4123
|
+
}
|
|
4124
|
+
return [{ text: run.text, fontRef, fontData: fd, shaped, hexStr: null, widthPt: designW * sz / upm }];
|
|
4125
|
+
}
|
|
4126
|
+
if (containsMyanmar(run.text)) {
|
|
4127
|
+
const shaped = shapeMyanmarText(run.text, fd);
|
|
4128
|
+
let designW = 0;
|
|
4129
|
+
for (const g of shaped) {
|
|
4130
|
+
_trackGid(fontRef, g.gid);
|
|
4131
|
+
if (!g.isZeroAdvance) {
|
|
4132
|
+
designW += fd.widths[g.gid] !== void 0 ? fd.widths[g.gid] : fd.defaultWidth;
|
|
4133
|
+
}
|
|
4134
|
+
}
|
|
4135
|
+
return [{ text: run.text, fontRef, fontData: fd, shaped, hexStr: null, widthPt: designW * sz / upm }];
|
|
4136
|
+
}
|
|
2187
4137
|
if (containsDevanagari(run.text)) {
|
|
2188
4138
|
const shaped = shapeDevanagariText(run.text, fd);
|
|
2189
4139
|
let designW = 0;
|
|
@@ -2200,6 +4150,7 @@ function createEncodingContext(fontEntries, pdfA = false) {
|
|
|
2200
4150
|
},
|
|
2201
4151
|
ps(str) {
|
|
2202
4152
|
if (!str) return "<>";
|
|
4153
|
+
str = _norm(str);
|
|
2203
4154
|
str = stripBidiControls(str);
|
|
2204
4155
|
if (!str) return "<>";
|
|
2205
4156
|
const { cmap } = primary.fontData;
|
|
@@ -2228,7 +4179,7 @@ function createEncodingContext(fontEntries, pdfA = false) {
|
|
|
2228
4179
|
}
|
|
2229
4180
|
return `<${hex2.toUpperCase()}>`;
|
|
2230
4181
|
}
|
|
2231
|
-
if (!containsThai(str) && !containsBengali(str) && !containsTamil(str) && !containsDevanagari(str)) {
|
|
4182
|
+
if (!containsThai(str) && !containsBengali(str) && !containsTamil(str) && !containsTelugu(str) && !containsSinhala(str) && !containsTibetan(str) && !containsKhmer(str) && !containsMyanmar(str) && !containsDevanagari(str)) {
|
|
2232
4183
|
let hex2 = "";
|
|
2233
4184
|
for (let i = 0; i < str.length; i++) {
|
|
2234
4185
|
const rawCp = str.codePointAt(i) ?? 0;
|
|
@@ -2240,7 +4191,7 @@ function createEncodingContext(fontEntries, pdfA = false) {
|
|
|
2240
4191
|
}
|
|
2241
4192
|
return `<${hex2.toUpperCase()}>`;
|
|
2242
4193
|
}
|
|
2243
|
-
const shapeFn = containsThai(str) ? shapeThaiText : containsBengali(str) ? shapeBengaliText : containsTamil(str) ? shapeTamilText : shapeDevanagariText;
|
|
4194
|
+
const shapeFn = containsThai(str) ? shapeThaiText : containsBengali(str) ? shapeBengaliText : containsTamil(str) ? shapeTamilText : containsTelugu(str) ? shapeTeluguText : containsSinhala(str) ? shapeSinhalaText : containsTibetan(str) ? shapeTibetanText : containsKhmer(str) ? shapeKhmerText : containsMyanmar(str) ? shapeMyanmarText : shapeDevanagariText;
|
|
2244
4195
|
const shaped = shapeFn(str, primary.fontData);
|
|
2245
4196
|
let hex = "";
|
|
2246
4197
|
for (const g of shaped) {
|
|
@@ -2322,24 +4273,6 @@ function buildSubsetWidthArray(widths, usedGids) {
|
|
|
2322
4273
|
return parts.join(" ");
|
|
2323
4274
|
}
|
|
2324
4275
|
|
|
2325
|
-
// src/fonts/font-loader.ts
|
|
2326
|
-
var _fontBinaryCache = /* @__PURE__ */ new WeakMap();
|
|
2327
|
-
function getDecodedFontBytes(fontData) {
|
|
2328
|
-
const cached = _fontBinaryCache.get(fontData);
|
|
2329
|
-
if (cached) return cached;
|
|
2330
|
-
let bytes;
|
|
2331
|
-
if (typeof atob === "function") {
|
|
2332
|
-
const binaryStr = atob(fontData.ttfBase64);
|
|
2333
|
-
bytes = new Uint8Array(binaryStr.length);
|
|
2334
|
-
for (let i = 0; i < binaryStr.length; i++) bytes[i] = binaryStr.charCodeAt(i);
|
|
2335
|
-
} else {
|
|
2336
|
-
const buf = globalThis["Buffer"];
|
|
2337
|
-
bytes = buf.from(fontData.ttfBase64, "base64");
|
|
2338
|
-
}
|
|
2339
|
-
_fontBinaryCache.set(fontData, bytes);
|
|
2340
|
-
return bytes;
|
|
2341
|
-
}
|
|
2342
|
-
|
|
2343
4276
|
// src/fonts/font-subsetter.ts
|
|
2344
4277
|
function subsetTTF(ttfInput, usedGids) {
|
|
2345
4278
|
try {
|
|
@@ -2653,7 +4586,7 @@ function buildStructureTree(root, startObjNum, pageObjToStructParents) {
|
|
|
2653
4586
|
};
|
|
2654
4587
|
}
|
|
2655
4588
|
function buildPdfMetadata(now = /* @__PURE__ */ new Date()) {
|
|
2656
|
-
const pad2 = (
|
|
4589
|
+
const pad2 = (n2) => String(n2).padStart(2, "0");
|
|
2657
4590
|
const yyyy = now.getFullYear();
|
|
2658
4591
|
const mm = pad2(now.getMonth() + 1);
|
|
2659
4592
|
const dd = pad2(now.getDate());
|
|
@@ -2861,26 +4794,45 @@ function txtShaped(shaped, x, y, font, sz, fontData) {
|
|
|
2861
4794
|
}
|
|
2862
4795
|
return parts.join("\n");
|
|
2863
4796
|
}
|
|
4797
|
+
var fmtScale = (v) => v.toFixed(5).replace(/0+$/, "").replace(/\.$/, "") || "0";
|
|
4798
|
+
function emitColorEmojiRun(parts, run, penX, y, sz, enc) {
|
|
4799
|
+
const fd = run.fontData;
|
|
4800
|
+
const upm = fd.metrics.unitsPerEm;
|
|
4801
|
+
const scale = sz / upm;
|
|
4802
|
+
const s = fmtScale(scale);
|
|
4803
|
+
const hex = (run.hexStr ?? "").replace(/[<>]/g, "");
|
|
4804
|
+
for (let i = 0; i + 4 <= hex.length; i += 4) {
|
|
4805
|
+
const tag = hex.substr(i, 4);
|
|
4806
|
+
const gid = parseInt(tag, 16);
|
|
4807
|
+
const adv = (fd.widths[gid] !== void 0 ? fd.widths[gid] : fd.defaultWidth) * scale;
|
|
4808
|
+
const name = enc.colorEmoji?.useGlyph(fd, gid) ?? null;
|
|
4809
|
+
if (name) {
|
|
4810
|
+
parts.push(`q ${s} 0 0 ${s} ${fmtNum(penX)} ${fmtNum(y)} cm /${name} Do Q`);
|
|
4811
|
+
} else {
|
|
4812
|
+
parts.push(`BT ${run.fontRef} ${sz} Tf ${fmtNum(penX)} ${fmtNum(y)} Td <${tag}> Tj ET`);
|
|
4813
|
+
}
|
|
4814
|
+
penX += adv;
|
|
4815
|
+
}
|
|
4816
|
+
return penX;
|
|
4817
|
+
}
|
|
2864
4818
|
function txt(str, x, y, font, sz, enc) {
|
|
2865
4819
|
if (!enc.isUnicode) {
|
|
2866
4820
|
return `BT ${font} ${sz} Tf ${fmtNum(x)} ${fmtNum(y)} Td ${enc.ps(str)} Tj ET`;
|
|
2867
4821
|
}
|
|
2868
4822
|
const runs = enc.textRuns(str, sz);
|
|
2869
4823
|
if (runs.length === 0) return "";
|
|
2870
|
-
if (runs.length === 1) {
|
|
2871
|
-
const run = runs[0];
|
|
2872
|
-
if (run.shaped) return txtShaped(run.shaped, x, y, run.fontRef, sz, run.fontData);
|
|
2873
|
-
return `BT ${run.fontRef} ${sz} Tf ${fmtNum(x)} ${fmtNum(y)} Td ${run.hexStr} Tj ET`;
|
|
2874
|
-
}
|
|
2875
4824
|
const parts = [];
|
|
2876
4825
|
let penX = x;
|
|
2877
4826
|
for (const run of runs) {
|
|
2878
|
-
if (run.
|
|
4827
|
+
if (enc.colorEmoji && run.fontData.colorGlyphs && run.hexStr) {
|
|
4828
|
+
penX = emitColorEmojiRun(parts, run, penX, y, sz, enc);
|
|
4829
|
+
} else if (run.shaped) {
|
|
2879
4830
|
parts.push(txtShaped(run.shaped, penX, y, run.fontRef, sz, run.fontData));
|
|
4831
|
+
penX += run.widthPt;
|
|
2880
4832
|
} else {
|
|
2881
4833
|
parts.push(`BT ${run.fontRef} ${sz} Tf ${fmtNum(penX)} ${fmtNum(y)} Td ${run.hexStr} Tj ET`);
|
|
4834
|
+
penX += run.widthPt;
|
|
2882
4835
|
}
|
|
2883
|
-
penX += run.widthPt;
|
|
2884
4836
|
}
|
|
2885
4837
|
return parts.join("\n");
|
|
2886
4838
|
}
|
|
@@ -2973,12 +4925,12 @@ var DEFAULT_COLUMNS = [
|
|
|
2973
4925
|
{ f: 0.18, a: "c", mx: 20, mxH: 20 }
|
|
2974
4926
|
];
|
|
2975
4927
|
function computeColumnPositions(columns, marginLeft, contentWidth) {
|
|
2976
|
-
const
|
|
2977
|
-
const cwi = new Array(
|
|
2978
|
-
const fixed = new Array(
|
|
4928
|
+
const n2 = columns.length;
|
|
4929
|
+
const cwi = new Array(n2).fill(0);
|
|
4930
|
+
const fixed = new Array(n2).fill(false);
|
|
2979
4931
|
let totalFixed = 0;
|
|
2980
4932
|
let freeWeight = 0;
|
|
2981
|
-
for (let i = 0; i <
|
|
4933
|
+
for (let i = 0; i < n2; i++) {
|
|
2982
4934
|
const col = columns[i];
|
|
2983
4935
|
let w = col.f * contentWidth;
|
|
2984
4936
|
let clamped = false;
|
|
@@ -3000,13 +4952,13 @@ function computeColumnPositions(columns, marginLeft, contentWidth) {
|
|
|
3000
4952
|
}
|
|
3001
4953
|
const remaining = contentWidth - totalFixed;
|
|
3002
4954
|
if (freeWeight > 0) {
|
|
3003
|
-
for (let i = 0; i <
|
|
4955
|
+
for (let i = 0; i < n2; i++) {
|
|
3004
4956
|
if (!fixed[i]) cwi[i] = columns[i].f / freeWeight * remaining;
|
|
3005
4957
|
}
|
|
3006
4958
|
}
|
|
3007
|
-
const cx = new Array(
|
|
4959
|
+
const cx = new Array(n2);
|
|
3008
4960
|
let x = marginLeft;
|
|
3009
|
-
for (let i = 0; i <
|
|
4961
|
+
for (let i = 0; i < n2; i++) {
|
|
3010
4962
|
cx[i] = x;
|
|
3011
4963
|
x += cwi[i];
|
|
3012
4964
|
}
|
|
@@ -3032,8 +4984,8 @@ function parseColor(input) {
|
|
|
3032
4984
|
`Invalid color format: ${JSON.stringify(input)}. Expected "#RRGGBB", "#RGB", [r, g, b] (0\u2013255), or "R G B" (0.0\u20131.0).`
|
|
3033
4985
|
);
|
|
3034
4986
|
}
|
|
3035
|
-
function fmtChannel(
|
|
3036
|
-
const clamped = Math.max(0, Math.min(1,
|
|
4987
|
+
function fmtChannel(n2) {
|
|
4988
|
+
const clamped = Math.max(0, Math.min(1, n2));
|
|
3037
4989
|
const rounded = Math.round(clamped * 1e3) / 1e3;
|
|
3038
4990
|
return String(rounded);
|
|
3039
4991
|
}
|
|
@@ -3075,8 +5027,8 @@ function parseTupleColor(tuple) {
|
|
|
3075
5027
|
function parsePdfRgbString(str) {
|
|
3076
5028
|
const parts = str.split(" ");
|
|
3077
5029
|
for (const p of parts) {
|
|
3078
|
-
const
|
|
3079
|
-
if (
|
|
5030
|
+
const n2 = Number(p);
|
|
5031
|
+
if (n2 < 0 || n2 > 1) {
|
|
3080
5032
|
throw new Error(
|
|
3081
5033
|
`Invalid PDF RGB value: ${p} in ${JSON.stringify(str)}. Each value must be 0.0\u20131.0.`
|
|
3082
5034
|
);
|
|
@@ -3216,8 +5168,8 @@ var MD5_K = new Uint32Array([
|
|
|
3216
5168
|
718787259,
|
|
3217
5169
|
3951481745
|
|
3218
5170
|
]);
|
|
3219
|
-
function rotl32(x,
|
|
3220
|
-
return (x <<
|
|
5171
|
+
function rotl32(x, n2) {
|
|
5172
|
+
return (x << n2 | x >>> 32 - n2) >>> 0;
|
|
3221
5173
|
}
|
|
3222
5174
|
function md5(input) {
|
|
3223
5175
|
const len = input.length;
|
|
@@ -3455,6 +5407,9 @@ function _buildPageTemplate(template, page, pages, title, date, y, enc, mgL, mgR
|
|
|
3455
5407
|
return { ops, structEls };
|
|
3456
5408
|
}
|
|
3457
5409
|
function buildPDF(params, layoutOptions) {
|
|
5410
|
+
return assembleTableParts(params, layoutOptions).join("");
|
|
5411
|
+
}
|
|
5412
|
+
function assembleTableParts(params, layoutOptions) {
|
|
3458
5413
|
if (!params || typeof params !== "object") {
|
|
3459
5414
|
throw new Error("buildPDF: params is required and must be an object");
|
|
3460
5415
|
}
|
|
@@ -3480,14 +5435,14 @@ function buildPDF(params, layoutOptions) {
|
|
|
3480
5435
|
const fontEntries = params.fontEntries || (fontData ? [{ fontData, fontRef: "/F3", lang: "unknown" }] : []);
|
|
3481
5436
|
const pdfaConfig = resolvePdfAConfig();
|
|
3482
5437
|
const tagged = pdfaConfig.enabled;
|
|
3483
|
-
const enc = createEncodingContext(fontEntries, tagged);
|
|
5438
|
+
const enc = createEncodingContext(fontEntries, tagged, false);
|
|
3484
5439
|
const footerTpl = {
|
|
3485
5440
|
left: footerText || void 0,
|
|
3486
5441
|
right: "{page}/{pages}"
|
|
3487
5442
|
};
|
|
3488
5443
|
const headerH = 0;
|
|
3489
5444
|
const dateNow = /* @__PURE__ */ new Date();
|
|
3490
|
-
const pad2d = (
|
|
5445
|
+
const pad2d = (n2) => String(n2).padStart(2, "0");
|
|
3491
5446
|
const dateStr = `${dateNow.getFullYear()}-${pad2d(dateNow.getMonth() + 1)}-${pad2d(dateNow.getDate())}`;
|
|
3492
5447
|
const infoCount = infoItems.length;
|
|
3493
5448
|
const page1Header = TITLE_LN + 16 + infoCount * INFO_LN + 8 + BAL_H + 10;
|
|
@@ -3509,6 +5464,9 @@ function buildPDF(params, layoutOptions) {
|
|
|
3509
5464
|
const pageStreams = [];
|
|
3510
5465
|
let rowIdx = 0;
|
|
3511
5466
|
const prePageObjStart = enc.isUnicode && fontEntries.length > 0 ? 5 + fontEntries.length * 5 + wmExtraObjs : 5 + wmExtraObjs;
|
|
5467
|
+
const preBaseObjCount = enc.isUnicode && fontEntries.length > 0 ? 4 + fontEntries.length * 5 + wmExtraObjs + totalPages * 2 : 4 + wmExtraObjs + totalPages * 2;
|
|
5468
|
+
const latinToUniObjNum = tagged ? 0 : preBaseObjCount + 2;
|
|
5469
|
+
const baseFontToUniRef = latinToUniObjNum ? ` /ToUnicode ${latinToUniObjNum} 0 R` : "";
|
|
3512
5470
|
const pageObjToStructParents = /* @__PURE__ */ new Map();
|
|
3513
5471
|
for (let p = 0; p < totalPages; p++) {
|
|
3514
5472
|
const pageObjNum = prePageObjStart + p * 2;
|
|
@@ -3638,8 +5596,8 @@ function buildPDF(params, layoutOptions) {
|
|
|
3638
5596
|
emitObj(3, refDict);
|
|
3639
5597
|
emitObj(4, refDict);
|
|
3640
5598
|
} else {
|
|
3641
|
-
emitObj(3,
|
|
3642
|
-
emitObj(4,
|
|
5599
|
+
emitObj(3, `<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding${baseFontToUniRef} >>`);
|
|
5600
|
+
emitObj(4, `<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding${baseFontToUniRef} >>`);
|
|
3643
5601
|
}
|
|
3644
5602
|
for (let fi = 0; fi < fontEntries.length; fi++) {
|
|
3645
5603
|
const fe = fontEntries[fi];
|
|
@@ -3697,8 +5655,8 @@ function buildPDF(params, layoutOptions) {
|
|
|
3697
5655
|
kids.push(`${pageObjStart + p * 2} 0 R`);
|
|
3698
5656
|
}
|
|
3699
5657
|
emitObj(2, `<< /Type /Pages /Kids [${kids.join(" ")}] /Count ${totalPages} >>`);
|
|
3700
|
-
emitObj(3,
|
|
3701
|
-
emitObj(4,
|
|
5658
|
+
emitObj(3, `<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding${baseFontToUniRef} >>`);
|
|
5659
|
+
emitObj(4, `<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding${baseFontToUniRef} >>`);
|
|
3702
5660
|
let wmGsResLatin = "";
|
|
3703
5661
|
let wmImgResLatin = "";
|
|
3704
5662
|
for (let p = 0; p < totalPages; p++) {
|
|
@@ -3715,13 +5673,18 @@ function buildPDF(params, layoutOptions) {
|
|
|
3715
5673
|
}
|
|
3716
5674
|
const baseObjCount = enc.isUnicode ? 4 + fontEntries.length * 5 + wmExtraObjs + totalPages * 2 : 4 + wmExtraObjs + totalPages * 2;
|
|
3717
5675
|
const infoObjNum = baseObjCount + 1;
|
|
3718
|
-
const { pdfDate, xmpDate: isoDate } = buildPdfMetadata();
|
|
5676
|
+
const { pdfDate, xmpDate: isoDate } = buildPdfMetadata(layoutOptions?.creationDate);
|
|
3719
5677
|
const infoTitle = params.docTitle || title || "";
|
|
3720
5678
|
emitObj(
|
|
3721
5679
|
infoObjNum,
|
|
3722
5680
|
`<< /Title ${encodePdfTextString(infoTitle)} /Producer (pdfnative) /CreationDate (${pdfDate}) >>`
|
|
3723
5681
|
);
|
|
3724
5682
|
let totalObjs = infoObjNum;
|
|
5683
|
+
if (latinToUniObjNum) {
|
|
5684
|
+
const cmap = buildWinAnsiToUnicodeCMap();
|
|
5685
|
+
emitStreamObj(latinToUniObjNum, `<< /Length ${cmap.length}`, cmap);
|
|
5686
|
+
totalObjs = latinToUniObjNum;
|
|
5687
|
+
}
|
|
3725
5688
|
let xmpObjNum = 0;
|
|
3726
5689
|
let outputIntentObjNum = 0;
|
|
3727
5690
|
if (tagged) {
|
|
@@ -3777,7 +5740,7 @@ endobj
|
|
|
3777
5740
|
}
|
|
3778
5741
|
const writer = { emit, emitObj, emitStreamObj, offset: getOffset, adjustOffset, objOffsets, parts };
|
|
3779
5742
|
writeXrefTrailer(writer, totalObjs, infoObjNum, encState, `${infoTitle}|${pdfDate}`);
|
|
3780
|
-
return parts
|
|
5743
|
+
return parts;
|
|
3781
5744
|
}
|
|
3782
5745
|
|
|
3783
5746
|
// src/worker/pdf-worker.ts
|