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