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