pdfnative 1.2.0 → 1.3.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 +41 -14
- package/dist/index.cjs +2753 -442
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +603 -29
- package/dist/index.d.ts +603 -29
- package/dist/index.js +2727 -443
- package/dist/index.js.map +1 -1
- package/dist/worker/index.cjs +2003 -121
- package/dist/worker/index.cjs.map +1 -1
- package/dist/worker/index.js +2003 -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 +22 -5
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;
|
|
1405
2759
|
for (let ci = 0; ci < codepoints.length; ci++) {
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
clusterGids.push(resolveGid(codepoints[ci]));
|
|
1409
|
-
clusterEndIdx.push(ci);
|
|
1410
|
-
} else if (ct >= 2) {
|
|
1411
|
-
matraStart = ci;
|
|
2760
|
+
if (myanmarCharType(codepoints[ci]) === 0) {
|
|
2761
|
+
baseGid = resolveGid(codepoints[ci]);
|
|
1412
2762
|
break;
|
|
1413
|
-
} else if (ct < 0) {
|
|
1414
|
-
emitGlyph(resolveGid(codepoints[ci]), false);
|
|
1415
|
-
} else if (ct === 1) {
|
|
1416
|
-
emitGlyph(resolveGid(codepoints[ci]), false);
|
|
1417
2763
|
}
|
|
1418
2764
|
}
|
|
1419
|
-
let
|
|
1420
|
-
|
|
2765
|
+
for (let ci = 0; ci < codepoints.length; ci++) {
|
|
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;
|
|
2779
|
+
break;
|
|
2780
|
+
} else if (ct === 4) ; else {
|
|
2781
|
+
emitGlyph(resolveGid(cp), false);
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
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,406 @@ 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 renderColorGlyph(glyph, outlines, unitsPerEm) {
|
|
3585
|
+
const body = [];
|
|
3586
|
+
const shadings = [];
|
|
3587
|
+
const extGStates = [];
|
|
3588
|
+
const alphaMap = /* @__PURE__ */ new Map();
|
|
3589
|
+
let shadingIdx = 0;
|
|
3590
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
3591
|
+
for (const layer of glyph.layers) {
|
|
3592
|
+
const m = layer.transform ?? ID;
|
|
3593
|
+
const contours = outlines(layer.glyphId);
|
|
3594
|
+
if (contours.length === 0) continue;
|
|
3595
|
+
const path = contoursToPath(contours, m);
|
|
3596
|
+
for (const contour of contours) {
|
|
3597
|
+
for (const pt of contour) {
|
|
3598
|
+
const [px, py] = tx(m, pt.x, pt.y);
|
|
3599
|
+
if (px < minX) minX = px;
|
|
3600
|
+
if (py < minY) minY = py;
|
|
3601
|
+
if (px > maxX) maxX = px;
|
|
3602
|
+
if (py > maxY) maxY = py;
|
|
3603
|
+
}
|
|
3604
|
+
}
|
|
3605
|
+
if (layer.paint.kind === "solid") {
|
|
3606
|
+
const c = layer.paint.color;
|
|
3607
|
+
const alpha = c[3] / 255;
|
|
3608
|
+
body.push("q");
|
|
3609
|
+
if (alpha < 0.999) {
|
|
3610
|
+
let gs = alphaMap.get(c[3]);
|
|
3611
|
+
if (!gs) {
|
|
3612
|
+
gs = `GsA${alphaMap.size}`;
|
|
3613
|
+
alphaMap.set(c[3], gs);
|
|
3614
|
+
extGStates.push({ name: gs, dict: `<< /ca ${n(alpha)} /CA ${n(alpha)} >>` });
|
|
3615
|
+
}
|
|
3616
|
+
body.push(`/${gs} gs`);
|
|
3617
|
+
}
|
|
3618
|
+
body.push(`${ch(c[0])} ${ch(c[1])} ${ch(c[2])} rg`);
|
|
3619
|
+
body.push(path);
|
|
3620
|
+
body.push("f");
|
|
3621
|
+
body.push("Q");
|
|
3622
|
+
} else {
|
|
3623
|
+
const name = `Sh${shadingIdx++}`;
|
|
3624
|
+
const dict = layer.paint.kind === "linear" ? linearShadingDict(layer.paint, m) : radialShadingDict(layer.paint, m);
|
|
3625
|
+
shadings.push({ name, dict });
|
|
3626
|
+
body.push("q");
|
|
3627
|
+
body.push(path);
|
|
3628
|
+
body.push("W n");
|
|
3629
|
+
body.push(`/${name} sh`);
|
|
3630
|
+
body.push("Q");
|
|
3631
|
+
}
|
|
3632
|
+
}
|
|
3633
|
+
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];
|
|
3634
|
+
return {
|
|
3635
|
+
content: body.join("\n"),
|
|
3636
|
+
bbox,
|
|
3637
|
+
shadings,
|
|
3638
|
+
extGStates
|
|
3639
|
+
};
|
|
3640
|
+
}
|
|
3641
|
+
|
|
3642
|
+
// src/core/color-emoji.ts
|
|
3643
|
+
function createColorEmojiCollector() {
|
|
3644
|
+
const forms = [];
|
|
3645
|
+
const nameByGlyph = /* @__PURE__ */ new WeakMap();
|
|
3646
|
+
const glyfByFont = /* @__PURE__ */ new WeakMap();
|
|
3647
|
+
function glyfFor(fontData) {
|
|
3648
|
+
let g = glyfByFont.get(fontData);
|
|
3649
|
+
if (g === void 0) {
|
|
3650
|
+
g = parseGlyfFont(getDecodedFontBytes(fontData));
|
|
3651
|
+
glyfByFont.set(fontData, g);
|
|
3652
|
+
}
|
|
3653
|
+
return g;
|
|
3654
|
+
}
|
|
3655
|
+
function useGlyph(fontData, gid) {
|
|
3656
|
+
const colorGlyph = fontData.colorGlyphs?.[gid];
|
|
3657
|
+
if (!colorGlyph) return null;
|
|
3658
|
+
let perFont = nameByGlyph.get(fontData);
|
|
3659
|
+
if (!perFont) {
|
|
3660
|
+
perFont = /* @__PURE__ */ new Map();
|
|
3661
|
+
nameByGlyph.set(fontData, perFont);
|
|
3662
|
+
}
|
|
3663
|
+
const cached = perFont.get(gid);
|
|
3664
|
+
if (cached) return cached;
|
|
3665
|
+
const glyf = glyfFor(fontData);
|
|
3666
|
+
if (!glyf) return null;
|
|
3667
|
+
const rendered = renderColorGlyph(
|
|
3668
|
+
colorGlyph,
|
|
3669
|
+
(baseGid) => extractGlyphContours(glyf, baseGid),
|
|
3670
|
+
fontData.metrics.unitsPerEm
|
|
3671
|
+
);
|
|
3672
|
+
if (rendered.content.trim() === "") return null;
|
|
3673
|
+
const name = `CEm${forms.length}`;
|
|
3674
|
+
const resParts = [];
|
|
3675
|
+
if (rendered.shadings.length > 0) {
|
|
3676
|
+
resParts.push(`/Shading << ${rendered.shadings.map((s) => `/${s.name} ${s.dict}`).join(" ")} >>`);
|
|
3677
|
+
}
|
|
3678
|
+
if (rendered.extGStates.length > 0) {
|
|
3679
|
+
resParts.push(`/ExtGState << ${rendered.extGStates.map((g) => `/${g.name} ${g.dict}`).join(" ")} >>`);
|
|
3680
|
+
}
|
|
3681
|
+
forms.push({ name, content: rendered.content, resources: resParts.join(" "), bbox: rendered.bbox });
|
|
3682
|
+
perFont.set(gid, name);
|
|
3683
|
+
return name;
|
|
3684
|
+
}
|
|
3685
|
+
return { useGlyph, forms };
|
|
3686
|
+
}
|
|
3687
|
+
|
|
1928
3688
|
// src/core/encoding-context.ts
|
|
1929
3689
|
function isWinAnsi(cp) {
|
|
1930
3690
|
if (cp >= 32 && cp <= 126 || cp >= 160 && cp <= 255) return true;
|
|
@@ -2029,13 +3789,14 @@ function buildTextRunsWithFallback(text, fontRef, fd, sz, trackGid, pdfA = false
|
|
|
2029
3789
|
if (mode === "hel") flushHel();
|
|
2030
3790
|
return result;
|
|
2031
3791
|
}
|
|
2032
|
-
function createEncodingContext(fontEntries, pdfA = false) {
|
|
3792
|
+
function createEncodingContext(fontEntries, pdfA = false, normalize = false) {
|
|
3793
|
+
const _norm = normalize ? (s) => s.normalize(normalize) : (s) => s;
|
|
2033
3794
|
if (!fontEntries || fontEntries.length === 0) {
|
|
2034
3795
|
return {
|
|
2035
3796
|
isUnicode: false,
|
|
2036
3797
|
fontEntries: [],
|
|
2037
|
-
ps: pdfString,
|
|
2038
|
-
tw: helveticaWidth,
|
|
3798
|
+
ps: normalize ? (s) => pdfString(_norm(s)) : pdfString,
|
|
3799
|
+
tw: normalize ? (s, sz) => helveticaWidth(_norm(s), sz) : helveticaWidth,
|
|
2039
3800
|
textRuns: () => [],
|
|
2040
3801
|
f1: "/F1",
|
|
2041
3802
|
f2: "/F2"
|
|
@@ -2048,6 +3809,7 @@ function createEncodingContext(fontEntries, pdfA = false) {
|
|
|
2048
3809
|
const s = _usedGids.get(fontRef);
|
|
2049
3810
|
if (s) s.add(gid);
|
|
2050
3811
|
}
|
|
3812
|
+
const _colorEmoji = fontEntries.some((fe) => fe.fontData.colorGlyphs) ? createColorEmojiCollector() : void 0;
|
|
2051
3813
|
return {
|
|
2052
3814
|
isUnicode: true,
|
|
2053
3815
|
fontEntries,
|
|
@@ -2057,8 +3819,10 @@ function createEncodingContext(fontEntries, pdfA = false) {
|
|
|
2057
3819
|
getUsedGids() {
|
|
2058
3820
|
return _usedGids;
|
|
2059
3821
|
},
|
|
3822
|
+
colorEmoji: _colorEmoji,
|
|
2060
3823
|
textRuns(str, sz) {
|
|
2061
3824
|
if (!str) return [];
|
|
3825
|
+
str = _norm(str);
|
|
2062
3826
|
str = stripBidiControls(str);
|
|
2063
3827
|
if (!str) return [];
|
|
2064
3828
|
if (containsRTL(str)) {
|
|
@@ -2125,6 +3889,56 @@ function createEncodingContext(fontEntries, pdfA = false) {
|
|
|
2125
3889
|
}
|
|
2126
3890
|
}
|
|
2127
3891
|
result.push({ text: fRun.text, fontRef, fontData: fd, shaped, hexStr: null, widthPt: designW * sz / upm });
|
|
3892
|
+
} else if (containsTelugu(fRun.text)) {
|
|
3893
|
+
const shaped = shapeTeluguText(fRun.text, fd);
|
|
3894
|
+
let designW = 0;
|
|
3895
|
+
for (const g of shaped) {
|
|
3896
|
+
_trackGid(fontRef, g.gid);
|
|
3897
|
+
if (!g.isZeroAdvance) {
|
|
3898
|
+
designW += fd.widths[g.gid] !== void 0 ? fd.widths[g.gid] : fd.defaultWidth;
|
|
3899
|
+
}
|
|
3900
|
+
}
|
|
3901
|
+
result.push({ text: fRun.text, fontRef, fontData: fd, shaped, hexStr: null, widthPt: designW * sz / upm });
|
|
3902
|
+
} else if (containsSinhala(fRun.text)) {
|
|
3903
|
+
const shaped = shapeSinhalaText(fRun.text, fd);
|
|
3904
|
+
let designW = 0;
|
|
3905
|
+
for (const g of shaped) {
|
|
3906
|
+
_trackGid(fontRef, g.gid);
|
|
3907
|
+
if (!g.isZeroAdvance) {
|
|
3908
|
+
designW += fd.widths[g.gid] !== void 0 ? fd.widths[g.gid] : fd.defaultWidth;
|
|
3909
|
+
}
|
|
3910
|
+
}
|
|
3911
|
+
result.push({ text: fRun.text, fontRef, fontData: fd, shaped, hexStr: null, widthPt: designW * sz / upm });
|
|
3912
|
+
} else if (containsTibetan(fRun.text)) {
|
|
3913
|
+
const shaped = shapeTibetanText(fRun.text, fd);
|
|
3914
|
+
let designW = 0;
|
|
3915
|
+
for (const g of shaped) {
|
|
3916
|
+
_trackGid(fontRef, g.gid);
|
|
3917
|
+
if (!g.isZeroAdvance) {
|
|
3918
|
+
designW += fd.widths[g.gid] !== void 0 ? fd.widths[g.gid] : fd.defaultWidth;
|
|
3919
|
+
}
|
|
3920
|
+
}
|
|
3921
|
+
result.push({ text: fRun.text, fontRef, fontData: fd, shaped, hexStr: null, widthPt: designW * sz / upm });
|
|
3922
|
+
} else if (containsKhmer(fRun.text)) {
|
|
3923
|
+
const shaped = shapeKhmerText(fRun.text, fd);
|
|
3924
|
+
let designW = 0;
|
|
3925
|
+
for (const g of shaped) {
|
|
3926
|
+
_trackGid(fontRef, g.gid);
|
|
3927
|
+
if (!g.isZeroAdvance) {
|
|
3928
|
+
designW += fd.widths[g.gid] !== void 0 ? fd.widths[g.gid] : fd.defaultWidth;
|
|
3929
|
+
}
|
|
3930
|
+
}
|
|
3931
|
+
result.push({ text: fRun.text, fontRef, fontData: fd, shaped, hexStr: null, widthPt: designW * sz / upm });
|
|
3932
|
+
} else if (containsMyanmar(fRun.text)) {
|
|
3933
|
+
const shaped = shapeMyanmarText(fRun.text, fd);
|
|
3934
|
+
let designW = 0;
|
|
3935
|
+
for (const g of shaped) {
|
|
3936
|
+
_trackGid(fontRef, g.gid);
|
|
3937
|
+
if (!g.isZeroAdvance) {
|
|
3938
|
+
designW += fd.widths[g.gid] !== void 0 ? fd.widths[g.gid] : fd.defaultWidth;
|
|
3939
|
+
}
|
|
3940
|
+
}
|
|
3941
|
+
result.push({ text: fRun.text, fontRef, fontData: fd, shaped, hexStr: null, widthPt: designW * sz / upm });
|
|
2128
3942
|
} else if (containsDevanagari(fRun.text)) {
|
|
2129
3943
|
const shaped = shapeDevanagariText(fRun.text, fd);
|
|
2130
3944
|
let designW = 0;
|
|
@@ -2182,6 +3996,61 @@ function createEncodingContext(fontEntries, pdfA = false) {
|
|
|
2182
3996
|
}
|
|
2183
3997
|
return [{ text: run.text, fontRef, fontData: fd, shaped, hexStr: null, widthPt: designW * sz / upm }];
|
|
2184
3998
|
}
|
|
3999
|
+
if (containsTelugu(run.text)) {
|
|
4000
|
+
const shaped = shapeTeluguText(run.text, fd);
|
|
4001
|
+
let designW = 0;
|
|
4002
|
+
for (const g of shaped) {
|
|
4003
|
+
_trackGid(fontRef, g.gid);
|
|
4004
|
+
if (!g.isZeroAdvance) {
|
|
4005
|
+
designW += fd.widths[g.gid] !== void 0 ? fd.widths[g.gid] : fd.defaultWidth;
|
|
4006
|
+
}
|
|
4007
|
+
}
|
|
4008
|
+
return [{ text: run.text, fontRef, fontData: fd, shaped, hexStr: null, widthPt: designW * sz / upm }];
|
|
4009
|
+
}
|
|
4010
|
+
if (containsSinhala(run.text)) {
|
|
4011
|
+
const shaped = shapeSinhalaText(run.text, fd);
|
|
4012
|
+
let designW = 0;
|
|
4013
|
+
for (const g of shaped) {
|
|
4014
|
+
_trackGid(fontRef, g.gid);
|
|
4015
|
+
if (!g.isZeroAdvance) {
|
|
4016
|
+
designW += fd.widths[g.gid] !== void 0 ? fd.widths[g.gid] : fd.defaultWidth;
|
|
4017
|
+
}
|
|
4018
|
+
}
|
|
4019
|
+
return [{ text: run.text, fontRef, fontData: fd, shaped, hexStr: null, widthPt: designW * sz / upm }];
|
|
4020
|
+
}
|
|
4021
|
+
if (containsTibetan(run.text)) {
|
|
4022
|
+
const shaped = shapeTibetanText(run.text, fd);
|
|
4023
|
+
let designW = 0;
|
|
4024
|
+
for (const g of shaped) {
|
|
4025
|
+
_trackGid(fontRef, g.gid);
|
|
4026
|
+
if (!g.isZeroAdvance) {
|
|
4027
|
+
designW += fd.widths[g.gid] !== void 0 ? fd.widths[g.gid] : fd.defaultWidth;
|
|
4028
|
+
}
|
|
4029
|
+
}
|
|
4030
|
+
return [{ text: run.text, fontRef, fontData: fd, shaped, hexStr: null, widthPt: designW * sz / upm }];
|
|
4031
|
+
}
|
|
4032
|
+
if (containsKhmer(run.text)) {
|
|
4033
|
+
const shaped = shapeKhmerText(run.text, fd);
|
|
4034
|
+
let designW = 0;
|
|
4035
|
+
for (const g of shaped) {
|
|
4036
|
+
_trackGid(fontRef, g.gid);
|
|
4037
|
+
if (!g.isZeroAdvance) {
|
|
4038
|
+
designW += fd.widths[g.gid] !== void 0 ? fd.widths[g.gid] : fd.defaultWidth;
|
|
4039
|
+
}
|
|
4040
|
+
}
|
|
4041
|
+
return [{ text: run.text, fontRef, fontData: fd, shaped, hexStr: null, widthPt: designW * sz / upm }];
|
|
4042
|
+
}
|
|
4043
|
+
if (containsMyanmar(run.text)) {
|
|
4044
|
+
const shaped = shapeMyanmarText(run.text, fd);
|
|
4045
|
+
let designW = 0;
|
|
4046
|
+
for (const g of shaped) {
|
|
4047
|
+
_trackGid(fontRef, g.gid);
|
|
4048
|
+
if (!g.isZeroAdvance) {
|
|
4049
|
+
designW += fd.widths[g.gid] !== void 0 ? fd.widths[g.gid] : fd.defaultWidth;
|
|
4050
|
+
}
|
|
4051
|
+
}
|
|
4052
|
+
return [{ text: run.text, fontRef, fontData: fd, shaped, hexStr: null, widthPt: designW * sz / upm }];
|
|
4053
|
+
}
|
|
2185
4054
|
if (containsDevanagari(run.text)) {
|
|
2186
4055
|
const shaped = shapeDevanagariText(run.text, fd);
|
|
2187
4056
|
let designW = 0;
|
|
@@ -2198,6 +4067,7 @@ function createEncodingContext(fontEntries, pdfA = false) {
|
|
|
2198
4067
|
},
|
|
2199
4068
|
ps(str) {
|
|
2200
4069
|
if (!str) return "<>";
|
|
4070
|
+
str = _norm(str);
|
|
2201
4071
|
str = stripBidiControls(str);
|
|
2202
4072
|
if (!str) return "<>";
|
|
2203
4073
|
const { cmap } = primary.fontData;
|
|
@@ -2226,7 +4096,7 @@ function createEncodingContext(fontEntries, pdfA = false) {
|
|
|
2226
4096
|
}
|
|
2227
4097
|
return `<${hex2.toUpperCase()}>`;
|
|
2228
4098
|
}
|
|
2229
|
-
if (!containsThai(str) && !containsBengali(str) && !containsTamil(str) && !containsDevanagari(str)) {
|
|
4099
|
+
if (!containsThai(str) && !containsBengali(str) && !containsTamil(str) && !containsTelugu(str) && !containsSinhala(str) && !containsTibetan(str) && !containsKhmer(str) && !containsMyanmar(str) && !containsDevanagari(str)) {
|
|
2230
4100
|
let hex2 = "";
|
|
2231
4101
|
for (let i = 0; i < str.length; i++) {
|
|
2232
4102
|
const rawCp = str.codePointAt(i) ?? 0;
|
|
@@ -2238,7 +4108,7 @@ function createEncodingContext(fontEntries, pdfA = false) {
|
|
|
2238
4108
|
}
|
|
2239
4109
|
return `<${hex2.toUpperCase()}>`;
|
|
2240
4110
|
}
|
|
2241
|
-
const shapeFn = containsThai(str) ? shapeThaiText : containsBengali(str) ? shapeBengaliText : containsTamil(str) ? shapeTamilText : shapeDevanagariText;
|
|
4111
|
+
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
4112
|
const shaped = shapeFn(str, primary.fontData);
|
|
2243
4113
|
let hex = "";
|
|
2244
4114
|
for (const g of shaped) {
|
|
@@ -2320,24 +4190,6 @@ function buildSubsetWidthArray(widths, usedGids) {
|
|
|
2320
4190
|
return parts.join(" ");
|
|
2321
4191
|
}
|
|
2322
4192
|
|
|
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
4193
|
// src/fonts/font-subsetter.ts
|
|
2342
4194
|
function subsetTTF(ttfInput, usedGids) {
|
|
2343
4195
|
try {
|
|
@@ -2651,7 +4503,7 @@ function buildStructureTree(root, startObjNum, pageObjToStructParents) {
|
|
|
2651
4503
|
};
|
|
2652
4504
|
}
|
|
2653
4505
|
function buildPdfMetadata(now = /* @__PURE__ */ new Date()) {
|
|
2654
|
-
const pad2 = (
|
|
4506
|
+
const pad2 = (n2) => String(n2).padStart(2, "0");
|
|
2655
4507
|
const yyyy = now.getFullYear();
|
|
2656
4508
|
const mm = pad2(now.getMonth() + 1);
|
|
2657
4509
|
const dd = pad2(now.getDate());
|
|
@@ -2859,26 +4711,45 @@ function txtShaped(shaped, x, y, font, sz, fontData) {
|
|
|
2859
4711
|
}
|
|
2860
4712
|
return parts.join("\n");
|
|
2861
4713
|
}
|
|
4714
|
+
var fmtScale = (v) => v.toFixed(5).replace(/0+$/, "").replace(/\.$/, "") || "0";
|
|
4715
|
+
function emitColorEmojiRun(parts, run, penX, y, sz, enc) {
|
|
4716
|
+
const fd = run.fontData;
|
|
4717
|
+
const upm = fd.metrics.unitsPerEm;
|
|
4718
|
+
const scale = sz / upm;
|
|
4719
|
+
const s = fmtScale(scale);
|
|
4720
|
+
const hex = (run.hexStr ?? "").replace(/[<>]/g, "");
|
|
4721
|
+
for (let i = 0; i + 4 <= hex.length; i += 4) {
|
|
4722
|
+
const tag = hex.substr(i, 4);
|
|
4723
|
+
const gid = parseInt(tag, 16);
|
|
4724
|
+
const adv = (fd.widths[gid] !== void 0 ? fd.widths[gid] : fd.defaultWidth) * scale;
|
|
4725
|
+
const name = enc.colorEmoji?.useGlyph(fd, gid) ?? null;
|
|
4726
|
+
if (name) {
|
|
4727
|
+
parts.push(`q ${s} 0 0 ${s} ${fmtNum(penX)} ${fmtNum(y)} cm /${name} Do Q`);
|
|
4728
|
+
} else {
|
|
4729
|
+
parts.push(`BT ${run.fontRef} ${sz} Tf ${fmtNum(penX)} ${fmtNum(y)} Td <${tag}> Tj ET`);
|
|
4730
|
+
}
|
|
4731
|
+
penX += adv;
|
|
4732
|
+
}
|
|
4733
|
+
return penX;
|
|
4734
|
+
}
|
|
2862
4735
|
function txt(str, x, y, font, sz, enc) {
|
|
2863
4736
|
if (!enc.isUnicode) {
|
|
2864
4737
|
return `BT ${font} ${sz} Tf ${fmtNum(x)} ${fmtNum(y)} Td ${enc.ps(str)} Tj ET`;
|
|
2865
4738
|
}
|
|
2866
4739
|
const runs = enc.textRuns(str, sz);
|
|
2867
4740
|
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
4741
|
const parts = [];
|
|
2874
4742
|
let penX = x;
|
|
2875
4743
|
for (const run of runs) {
|
|
2876
|
-
if (run.
|
|
4744
|
+
if (enc.colorEmoji && run.fontData.colorGlyphs && run.hexStr) {
|
|
4745
|
+
penX = emitColorEmojiRun(parts, run, penX, y, sz, enc);
|
|
4746
|
+
} else if (run.shaped) {
|
|
2877
4747
|
parts.push(txtShaped(run.shaped, penX, y, run.fontRef, sz, run.fontData));
|
|
4748
|
+
penX += run.widthPt;
|
|
2878
4749
|
} else {
|
|
2879
4750
|
parts.push(`BT ${run.fontRef} ${sz} Tf ${fmtNum(penX)} ${fmtNum(y)} Td ${run.hexStr} Tj ET`);
|
|
4751
|
+
penX += run.widthPt;
|
|
2880
4752
|
}
|
|
2881
|
-
penX += run.widthPt;
|
|
2882
4753
|
}
|
|
2883
4754
|
return parts.join("\n");
|
|
2884
4755
|
}
|
|
@@ -2971,12 +4842,12 @@ var DEFAULT_COLUMNS = [
|
|
|
2971
4842
|
{ f: 0.18, a: "c", mx: 20, mxH: 20 }
|
|
2972
4843
|
];
|
|
2973
4844
|
function computeColumnPositions(columns, marginLeft, contentWidth) {
|
|
2974
|
-
const
|
|
2975
|
-
const cwi = new Array(
|
|
2976
|
-
const fixed = new Array(
|
|
4845
|
+
const n2 = columns.length;
|
|
4846
|
+
const cwi = new Array(n2).fill(0);
|
|
4847
|
+
const fixed = new Array(n2).fill(false);
|
|
2977
4848
|
let totalFixed = 0;
|
|
2978
4849
|
let freeWeight = 0;
|
|
2979
|
-
for (let i = 0; i <
|
|
4850
|
+
for (let i = 0; i < n2; i++) {
|
|
2980
4851
|
const col = columns[i];
|
|
2981
4852
|
let w = col.f * contentWidth;
|
|
2982
4853
|
let clamped = false;
|
|
@@ -2998,13 +4869,13 @@ function computeColumnPositions(columns, marginLeft, contentWidth) {
|
|
|
2998
4869
|
}
|
|
2999
4870
|
const remaining = contentWidth - totalFixed;
|
|
3000
4871
|
if (freeWeight > 0) {
|
|
3001
|
-
for (let i = 0; i <
|
|
4872
|
+
for (let i = 0; i < n2; i++) {
|
|
3002
4873
|
if (!fixed[i]) cwi[i] = columns[i].f / freeWeight * remaining;
|
|
3003
4874
|
}
|
|
3004
4875
|
}
|
|
3005
|
-
const cx = new Array(
|
|
4876
|
+
const cx = new Array(n2);
|
|
3006
4877
|
let x = marginLeft;
|
|
3007
|
-
for (let i = 0; i <
|
|
4878
|
+
for (let i = 0; i < n2; i++) {
|
|
3008
4879
|
cx[i] = x;
|
|
3009
4880
|
x += cwi[i];
|
|
3010
4881
|
}
|
|
@@ -3030,8 +4901,8 @@ function parseColor(input) {
|
|
|
3030
4901
|
`Invalid color format: ${JSON.stringify(input)}. Expected "#RRGGBB", "#RGB", [r, g, b] (0\u2013255), or "R G B" (0.0\u20131.0).`
|
|
3031
4902
|
);
|
|
3032
4903
|
}
|
|
3033
|
-
function fmtChannel(
|
|
3034
|
-
const clamped = Math.max(0, Math.min(1,
|
|
4904
|
+
function fmtChannel(n2) {
|
|
4905
|
+
const clamped = Math.max(0, Math.min(1, n2));
|
|
3035
4906
|
const rounded = Math.round(clamped * 1e3) / 1e3;
|
|
3036
4907
|
return String(rounded);
|
|
3037
4908
|
}
|
|
@@ -3073,8 +4944,8 @@ function parseTupleColor(tuple) {
|
|
|
3073
4944
|
function parsePdfRgbString(str) {
|
|
3074
4945
|
const parts = str.split(" ");
|
|
3075
4946
|
for (const p of parts) {
|
|
3076
|
-
const
|
|
3077
|
-
if (
|
|
4947
|
+
const n2 = Number(p);
|
|
4948
|
+
if (n2 < 0 || n2 > 1) {
|
|
3078
4949
|
throw new Error(
|
|
3079
4950
|
`Invalid PDF RGB value: ${p} in ${JSON.stringify(str)}. Each value must be 0.0\u20131.0.`
|
|
3080
4951
|
);
|
|
@@ -3214,8 +5085,8 @@ var MD5_K = new Uint32Array([
|
|
|
3214
5085
|
718787259,
|
|
3215
5086
|
3951481745
|
|
3216
5087
|
]);
|
|
3217
|
-
function rotl32(x,
|
|
3218
|
-
return (x <<
|
|
5088
|
+
function rotl32(x, n2) {
|
|
5089
|
+
return (x << n2 | x >>> 32 - n2) >>> 0;
|
|
3219
5090
|
}
|
|
3220
5091
|
function md5(input) {
|
|
3221
5092
|
const len = input.length;
|
|
@@ -3453,6 +5324,9 @@ function _buildPageTemplate(template, page, pages, title, date, y, enc, mgL, mgR
|
|
|
3453
5324
|
return { ops, structEls };
|
|
3454
5325
|
}
|
|
3455
5326
|
function buildPDF(params, layoutOptions) {
|
|
5327
|
+
return assembleTableParts(params, layoutOptions).join("");
|
|
5328
|
+
}
|
|
5329
|
+
function assembleTableParts(params, layoutOptions) {
|
|
3456
5330
|
if (!params || typeof params !== "object") {
|
|
3457
5331
|
throw new Error("buildPDF: params is required and must be an object");
|
|
3458
5332
|
}
|
|
@@ -3478,14 +5352,14 @@ function buildPDF(params, layoutOptions) {
|
|
|
3478
5352
|
const fontEntries = params.fontEntries || (fontData ? [{ fontData, fontRef: "/F3", lang: "unknown" }] : []);
|
|
3479
5353
|
const pdfaConfig = resolvePdfAConfig();
|
|
3480
5354
|
const tagged = pdfaConfig.enabled;
|
|
3481
|
-
const enc = createEncodingContext(fontEntries, tagged);
|
|
5355
|
+
const enc = createEncodingContext(fontEntries, tagged, false);
|
|
3482
5356
|
const footerTpl = {
|
|
3483
5357
|
left: footerText || void 0,
|
|
3484
5358
|
right: "{page}/{pages}"
|
|
3485
5359
|
};
|
|
3486
5360
|
const headerH = 0;
|
|
3487
5361
|
const dateNow = /* @__PURE__ */ new Date();
|
|
3488
|
-
const pad2d = (
|
|
5362
|
+
const pad2d = (n2) => String(n2).padStart(2, "0");
|
|
3489
5363
|
const dateStr = `${dateNow.getFullYear()}-${pad2d(dateNow.getMonth() + 1)}-${pad2d(dateNow.getDate())}`;
|
|
3490
5364
|
const infoCount = infoItems.length;
|
|
3491
5365
|
const page1Header = TITLE_LN + 16 + infoCount * INFO_LN + 8 + BAL_H + 10;
|
|
@@ -3507,6 +5381,9 @@ function buildPDF(params, layoutOptions) {
|
|
|
3507
5381
|
const pageStreams = [];
|
|
3508
5382
|
let rowIdx = 0;
|
|
3509
5383
|
const prePageObjStart = enc.isUnicode && fontEntries.length > 0 ? 5 + fontEntries.length * 5 + wmExtraObjs : 5 + wmExtraObjs;
|
|
5384
|
+
const preBaseObjCount = enc.isUnicode && fontEntries.length > 0 ? 4 + fontEntries.length * 5 + wmExtraObjs + totalPages * 2 : 4 + wmExtraObjs + totalPages * 2;
|
|
5385
|
+
const latinToUniObjNum = tagged ? 0 : preBaseObjCount + 2;
|
|
5386
|
+
const baseFontToUniRef = latinToUniObjNum ? ` /ToUnicode ${latinToUniObjNum} 0 R` : "";
|
|
3510
5387
|
const pageObjToStructParents = /* @__PURE__ */ new Map();
|
|
3511
5388
|
for (let p = 0; p < totalPages; p++) {
|
|
3512
5389
|
const pageObjNum = prePageObjStart + p * 2;
|
|
@@ -3636,8 +5513,8 @@ function buildPDF(params, layoutOptions) {
|
|
|
3636
5513
|
emitObj(3, refDict);
|
|
3637
5514
|
emitObj(4, refDict);
|
|
3638
5515
|
} else {
|
|
3639
|
-
emitObj(3,
|
|
3640
|
-
emitObj(4,
|
|
5516
|
+
emitObj(3, `<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding${baseFontToUniRef} >>`);
|
|
5517
|
+
emitObj(4, `<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding${baseFontToUniRef} >>`);
|
|
3641
5518
|
}
|
|
3642
5519
|
for (let fi = 0; fi < fontEntries.length; fi++) {
|
|
3643
5520
|
const fe = fontEntries[fi];
|
|
@@ -3695,8 +5572,8 @@ function buildPDF(params, layoutOptions) {
|
|
|
3695
5572
|
kids.push(`${pageObjStart + p * 2} 0 R`);
|
|
3696
5573
|
}
|
|
3697
5574
|
emitObj(2, `<< /Type /Pages /Kids [${kids.join(" ")}] /Count ${totalPages} >>`);
|
|
3698
|
-
emitObj(3,
|
|
3699
|
-
emitObj(4,
|
|
5575
|
+
emitObj(3, `<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding${baseFontToUniRef} >>`);
|
|
5576
|
+
emitObj(4, `<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding${baseFontToUniRef} >>`);
|
|
3700
5577
|
let wmGsResLatin = "";
|
|
3701
5578
|
let wmImgResLatin = "";
|
|
3702
5579
|
for (let p = 0; p < totalPages; p++) {
|
|
@@ -3713,13 +5590,18 @@ function buildPDF(params, layoutOptions) {
|
|
|
3713
5590
|
}
|
|
3714
5591
|
const baseObjCount = enc.isUnicode ? 4 + fontEntries.length * 5 + wmExtraObjs + totalPages * 2 : 4 + wmExtraObjs + totalPages * 2;
|
|
3715
5592
|
const infoObjNum = baseObjCount + 1;
|
|
3716
|
-
const { pdfDate, xmpDate: isoDate } = buildPdfMetadata();
|
|
5593
|
+
const { pdfDate, xmpDate: isoDate } = buildPdfMetadata(layoutOptions?.creationDate);
|
|
3717
5594
|
const infoTitle = params.docTitle || title || "";
|
|
3718
5595
|
emitObj(
|
|
3719
5596
|
infoObjNum,
|
|
3720
5597
|
`<< /Title ${encodePdfTextString(infoTitle)} /Producer (pdfnative) /CreationDate (${pdfDate}) >>`
|
|
3721
5598
|
);
|
|
3722
5599
|
let totalObjs = infoObjNum;
|
|
5600
|
+
if (latinToUniObjNum) {
|
|
5601
|
+
const cmap = buildWinAnsiToUnicodeCMap();
|
|
5602
|
+
emitStreamObj(latinToUniObjNum, `<< /Length ${cmap.length}`, cmap);
|
|
5603
|
+
totalObjs = latinToUniObjNum;
|
|
5604
|
+
}
|
|
3723
5605
|
let xmpObjNum = 0;
|
|
3724
5606
|
let outputIntentObjNum = 0;
|
|
3725
5607
|
if (tagged) {
|
|
@@ -3775,7 +5657,7 @@ endobj
|
|
|
3775
5657
|
}
|
|
3776
5658
|
const writer = { emit, emitObj, emitStreamObj, offset: getOffset, adjustOffset, objOffsets, parts };
|
|
3777
5659
|
writeXrefTrailer(writer, totalObjs, infoObjNum, encState, `${infoTitle}|${pdfDate}`);
|
|
3778
|
-
return parts
|
|
5660
|
+
return parts;
|
|
3779
5661
|
}
|
|
3780
5662
|
|
|
3781
5663
|
// src/worker/pdf-worker.ts
|