pdfnative 1.1.0 → 1.2.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.
@@ -352,401 +352,638 @@ function splitTextByFont(str, fontEntries) {
352
352
  return runs;
353
353
  }
354
354
 
355
- // src/fonts/encoding.ts
356
- function toWinAnsi(str) {
357
- if (!str) return "";
358
- let r = "";
359
- for (let i = 0; i < str.length; i++) {
360
- const c = str.charCodeAt(i);
361
- if (c >= 32 && c <= 126) r += str[i];
362
- else if (c >= 160 && c <= 255) r += str[i];
363
- else if (c === 8364) r += "\x80";
364
- else if (c === 8218) r += "\x82";
365
- else if (c === 402) r += "\x83";
366
- else if (c === 8222) r += "\x84";
367
- else if (c === 8230) r += "\x85";
368
- else if (c === 8224) r += "\x86";
369
- else if (c === 8225) r += "\x87";
370
- else if (c === 710) r += "\x88";
371
- else if (c === 8240) r += "\x89";
372
- else if (c === 352) r += "\x8A";
373
- else if (c === 8249) r += "\x8B";
374
- else if (c === 338) r += "\x8C";
375
- else if (c === 381) r += "\x8E";
376
- else if (c === 8216) r += "\x91";
377
- else if (c === 8217) r += "\x92";
378
- else if (c === 8220) r += "\x93";
379
- else if (c === 8221) r += "\x94";
380
- else if (c === 8226) r += "\x95";
381
- else if (c === 8211) r += "\x96";
382
- else if (c === 8212) r += "\x97";
383
- else if (c === 732) r += "\x98";
384
- else if (c === 8482) r += "\x99";
385
- else if (c === 353) r += "\x9A";
386
- else if (c === 8250) r += "\x9B";
387
- else if (c === 339) r += "\x9C";
388
- else if (c === 382) r += "\x9E";
389
- else if (c === 376) r += "\x9F";
390
- else if (c === 160 || c === 8239) r += " ";
391
- else if (c === 9 || c === 10 || c === 13) r += " ";
392
- else if (c < 32) ; else r += "?";
393
- }
394
- return r;
395
- }
396
- function pdfString(str) {
397
- const s = toWinAnsi(str);
398
- return "(" + s.replace(/\\/g, "\\\\").replace(/\(/g, "\\(").replace(/\)/g, "\\)") + ")";
399
- }
400
- function truncate(str, max) {
401
- if (!str || str.length <= max) return str || "";
402
- if (max <= 1) return "\u2026";
403
- return str.slice(0, max - 1) + "\u2026";
355
+ // src/shaping/bidi.ts
356
+ function classifyBidiType(cp) {
357
+ if (cp >= 768 && cp <= 879 || // Combining Diacritical Marks
358
+ cp >= 1425 && cp <= 1469 || // Hebrew marks
359
+ cp >= 1471 && cp <= 1471 || cp >= 1473 && cp <= 1474 || cp >= 1476 && cp <= 1477 || cp >= 1479 && cp <= 1479 || cp >= 1552 && cp <= 1562 || // Arabic marks
360
+ cp >= 1611 && cp <= 1631 || // Arabic harakat
361
+ cp >= 1648 && cp <= 1648 || // Arabic superscript alef
362
+ cp >= 1750 && cp <= 1756 || cp >= 1759 && cp <= 1764 || cp >= 1767 && cp <= 1768 || cp >= 1770 && cp <= 1773 || cp >= 65056 && cp <= 65071) return "NSM";
363
+ if (cp === 8203 || cp === 8204 || cp === 8205 || cp === 8206 || cp === 8207 || cp === 65279 || cp === 8294 || cp === 8295 || cp === 8296 || cp === 8297) return "BN";
364
+ if (cp >= 1632 && cp <= 1641) return "AN";
365
+ if (cp >= 1776 && cp <= 1785) return "AN";
366
+ if (cp >= 48 && cp <= 57) return "EN";
367
+ if (cp === 43 || cp === 45) return "ES";
368
+ if (cp === 35 || cp === 36 || cp === 37 || cp === 162 || cp === 163 || cp === 164 || cp === 165 || cp === 8364 || cp === 8377 || cp === 8378) return "ET";
369
+ if (cp === 44 || cp === 46 || cp === 47 || cp === 58 || cp === 160) return "CS";
370
+ if (cp === 32 || cp === 9 || cp === 10 || cp === 13 || cp === 12 || cp === 8192 || cp === 8202 || cp === 8232 || cp === 8233 || cp === 8239 || cp === 8287 || cp === 12288) return "WS";
371
+ if (cp >= 1536 && cp <= 1791) return "AL";
372
+ if (cp >= 1872 && cp <= 1919) return "AL";
373
+ if (cp >= 2208 && cp <= 2303) return "AL";
374
+ if (cp >= 64336 && cp <= 65023) return "AL";
375
+ if (cp >= 65136 && cp <= 65278) return "AL";
376
+ if (cp >= 1488 && cp <= 1514) return "R";
377
+ if (cp >= 1520 && cp <= 1524) return "R";
378
+ if (cp >= 1792 && cp <= 1871) return "R";
379
+ if (cp >= 1920 && cp <= 1983) return "R";
380
+ if (cp >= 64285 && cp <= 64335) return "R";
381
+ if (cp >= 65 && cp <= 90) return "L";
382
+ if (cp >= 97 && cp <= 122) return "L";
383
+ if (cp >= 192 && cp <= 591) return "L";
384
+ if (cp >= 880 && cp <= 1023) return "L";
385
+ if (cp >= 1024 && cp <= 1279) return "L";
386
+ if (cp >= 3584 && cp <= 3711) return "L";
387
+ if (cp >= 2304 && cp <= 2431) return "L";
388
+ if (cp >= 12352 && cp <= 12543) return "L";
389
+ if (cp >= 19968 && cp <= 40959) return "L";
390
+ if (cp >= 44032 && cp <= 55215) return "L";
391
+ if (cp >= 33 && cp <= 47) return "ON";
392
+ if (cp >= 58 && cp <= 64) return "ON";
393
+ if (cp >= 91 && cp <= 96) return "ON";
394
+ if (cp >= 123 && cp <= 126) return "ON";
395
+ if (cp >= 161 && cp <= 191) return "ON";
396
+ if (cp >= 8208 && cp <= 8231) return "ON";
397
+ if (cp >= 8240 && cp <= 8286) return "ON";
398
+ return "L";
404
399
  }
405
- function helveticaWidth(str, sz) {
406
- let w = 0;
407
- for (let i = 0; i < str.length; i++) {
408
- const cp = str.codePointAt(i) ?? 0;
409
- if (cp > 65535) i++;
410
- if (cp >= 48 && cp <= 57) w += 556;
411
- else if (cp >= 65 && cp <= 90) w += 680;
412
- else if (cp >= 97 && cp <= 122) w += 500;
413
- else if (cp === 32) w += 278;
414
- else if (cp === 46 || cp === 44) w += 278;
415
- else if (cp === 43) w += 584;
416
- else if (cp === 45) w += 333;
417
- else if (cp === 47 || cp === 58) w += 278;
418
- else if (cp === 8212) w += 1e3;
419
- else if (cp === 8211) w += 556;
420
- else if (cp === 8230) w += 1e3;
421
- else if (cp === 8216 || cp === 8217) w += 222;
422
- else if (cp === 8220 || cp === 8221) w += 333;
423
- else if (cp === 8364) w += 556;
424
- else w += 556;
400
+ function detectParagraphLevel(types) {
401
+ for (const t of types) {
402
+ if (t === "L") return 0;
403
+ if (t === "R" || t === "AL") return 1;
425
404
  }
426
- return w * sz / 1e3;
405
+ return 0;
427
406
  }
428
-
429
- // src/shaping/gsub-driver.ts
430
- function tryLigature(gids, ligatures) {
431
- if (!ligatures || gids.length < 2) return null;
432
- const firstGid = gids[0];
433
- const entries = ligatures[firstGid];
434
- if (!entries) return null;
435
- for (const entry of entries) {
436
- const compCount = entry.length - 1;
437
- if (compCount > gids.length - 1) continue;
438
- let match = true;
439
- for (let ci = 0; ci < compCount; ci++) {
440
- if (gids[1 + ci] !== entry[1 + ci]) {
441
- match = false;
442
- break;
443
- }
407
+ function resolveWeakTypes(types, paraLevel) {
408
+ const len = types.length;
409
+ let prevType = paraLevel === 0 ? "L" : "R";
410
+ for (let i = 0; i < len; i++) {
411
+ if (types[i] === "BN") continue;
412
+ if (types[i] === "NSM") {
413
+ types[i] = prevType;
444
414
  }
445
- if (match) return { resultGid: entry[0], consumed: compCount + 1 };
446
- }
447
- return null;
448
- }
449
-
450
- // src/shaping/bengali-shaper.ts
451
- var HALANT = 2509;
452
- var NUKTA = 2492;
453
- var RA = 2480;
454
- function bengaliCharType(cp) {
455
- if (cp === HALANT) return 7;
456
- if (cp === NUKTA) return 8;
457
- if (cp >= 2433 && cp <= 2435) return 6;
458
- if (cp >= 2437 && cp <= 2452) return 1;
459
- if (cp >= 2453 && cp <= 2489) return 0;
460
- if (cp === 2495) return 4;
461
- if (cp === 2503) return 4;
462
- if (cp === 2504) return 4;
463
- if (cp === 2497 || cp === 2498 || cp === 2499 || cp === 2500) return 3;
464
- if (cp === 2494) return 5;
465
- if (cp === 2496) return 5;
466
- if (cp === 2507) return 4;
467
- if (cp === 2508) return 4;
468
- if (cp >= 2494 && cp <= 2508) return 2;
469
- if (cp === 2519) return 5;
470
- if (cp >= 2534 && cp <= 2543) return 9;
471
- if (cp === 2527) return 0;
472
- if (cp === 2493) return 1;
473
- if (cp === 2510) return 0;
474
- return -1;
475
- }
476
- function isConsonant(cp) {
477
- return bengaliCharType(cp) === 0;
478
- }
479
- function buildBengaliClusters(str) {
480
- const clusters = [];
481
- const cps = [];
482
- for (let i2 = 0; i2 < str.length; ) {
483
- const cp = str.codePointAt(i2) ?? 0;
484
- cps.push(cp);
485
- i2 += cp > 65535 ? 2 : 1;
415
+ prevType = types[i];
486
416
  }
487
- let i = 0;
488
- while (i < cps.length) {
489
- const cp = cps[i];
490
- const type = bengaliCharType(cp);
491
- if (type < 0 || cp < BENGALI_START || cp > BENGALI_END) {
492
- clusters.push({ codepoints: [cp], baseIndex: 0, hasReph: false, preBaseMatras: [] });
493
- i++;
494
- continue;
417
+ let lastStrong = paraLevel === 0 ? "L" : "R";
418
+ for (let i = 0; i < len; i++) {
419
+ if (types[i] === "BN") continue;
420
+ if (types[i] === "R" || types[i] === "L" || types[i] === "AL") {
421
+ lastStrong = types[i];
422
+ } else if (types[i] === "EN" && lastStrong === "AL") {
423
+ types[i] = "AN";
495
424
  }
496
- const syllable = [];
497
- let baseIdx = 0;
498
- let hasReph = false;
499
- const preMatras = [];
500
- if (isConsonant(cp) && cp === RA && i + 2 < cps.length && cps[i + 1] === HALANT && isConsonant(cps[i + 2])) {
501
- hasReph = true;
502
- syllable.push(cp, cps[i + 1]);
503
- i += 2;
425
+ }
426
+ for (let i = 0; i < len; i++) {
427
+ if (types[i] === "AL") types[i] = "R";
428
+ }
429
+ for (let i = 1; i < len - 1; i++) {
430
+ if (types[i] === "BN") continue;
431
+ if (types[i] === "ES" && types[i - 1] === "EN" && types[i + 1] === "EN") {
432
+ types[i] = "EN";
433
+ } else if (types[i] === "CS") {
434
+ if (types[i - 1] === "EN" && types[i + 1] === "EN") types[i] = "EN";
435
+ else if (types[i - 1] === "AN" && types[i + 1] === "AN") types[i] = "AN";
504
436
  }
505
- let lastConsonantIdx = -1;
506
- while (i < cps.length) {
507
- const cc = cps[i];
508
- const ct = bengaliCharType(cc);
509
- if (ct === 0) {
510
- lastConsonantIdx = syllable.length;
511
- syllable.push(cc);
512
- i++;
513
- if (i < cps.length && cps[i] === NUKTA) {
514
- syllable.push(cps[i]);
515
- i++;
437
+ }
438
+ for (let i = 0; i < len; i++) {
439
+ if (types[i] === "ET") {
440
+ let found = false;
441
+ for (let j = i - 1; j >= 0; j--) {
442
+ if (types[j] === "EN") {
443
+ found = true;
444
+ break;
516
445
  }
517
- if (i < cps.length && cps[i] === HALANT) {
518
- if (i + 1 < cps.length && isConsonant(cps[i + 1])) {
519
- syllable.push(cps[i]);
520
- i++;
521
- continue;
522
- } else {
523
- syllable.push(cps[i]);
524
- i++;
446
+ if (types[j] !== "ET" && types[j] !== "BN") break;
447
+ }
448
+ if (!found) {
449
+ for (let j = i + 1; j < len; j++) {
450
+ if (types[j] === "EN") {
451
+ found = true;
525
452
  break;
526
453
  }
454
+ if (types[j] !== "ET" && types[j] !== "BN") break;
527
455
  }
528
- break;
529
- } else {
530
- break;
531
456
  }
457
+ if (found) types[i] = "EN";
532
458
  }
533
- baseIdx = lastConsonantIdx >= 0 ? lastConsonantIdx : 0;
534
- while (i < cps.length) {
535
- const ct = bengaliCharType(cps[i]);
536
- if (ct >= 2 && ct <= 5) {
537
- if (ct === 4) {
538
- preMatras.push(syllable.length);
539
- }
540
- syllable.push(cps[i]);
541
- i++;
542
- } else {
459
+ }
460
+ for (let i = 0; i < len; i++) {
461
+ if (types[i] === "ES" || types[i] === "ET" || types[i] === "CS") {
462
+ types[i] = "ON";
463
+ }
464
+ }
465
+ lastStrong = paraLevel === 0 ? "L" : "R";
466
+ for (let i = 0; i < len; i++) {
467
+ if (types[i] === "BN") continue;
468
+ if (types[i] === "L" || types[i] === "R") {
469
+ lastStrong = types[i];
470
+ } else if (types[i] === "EN" && lastStrong === "L") {
471
+ types[i] = "L";
472
+ }
473
+ }
474
+ }
475
+ function resolveNeutralTypes(types, paraLevel) {
476
+ const len = types.length;
477
+ const paraDir = paraLevel === 0 ? "L" : "R";
478
+ for (let i = 0; i < len; i++) {
479
+ if (types[i] !== "ON" && types[i] !== "WS" && types[i] !== "BN") continue;
480
+ const start = i;
481
+ while (i < len && (types[i] === "ON" || types[i] === "WS" || types[i] === "BN")) i++;
482
+ const end = i;
483
+ let prevStrong = paraDir;
484
+ for (let j = start - 1; j >= 0; j--) {
485
+ if (types[j] === "L" || types[j] === "R" || types[j] === "EN" || types[j] === "AN") {
486
+ prevStrong = types[j] === "EN" || types[j] === "AN" ? "R" : types[j];
543
487
  break;
544
488
  }
545
489
  }
546
- while (i < cps.length && bengaliCharType(cps[i]) === 6) {
547
- syllable.push(cps[i]);
548
- i++;
490
+ let nextStrong = paraDir;
491
+ for (let j = end; j < len; j++) {
492
+ if (types[j] === "L" || types[j] === "R" || types[j] === "EN" || types[j] === "AN") {
493
+ nextStrong = types[j] === "EN" || types[j] === "AN" ? "R" : types[j];
494
+ break;
495
+ }
549
496
  }
550
- if (syllable.length === 0) {
551
- syllable.push(cps[i] ?? 32);
552
- i++;
497
+ const resolved = prevStrong === nextStrong ? prevStrong : paraDir;
498
+ for (let j = start; j < end; j++) {
499
+ types[j] = resolved;
553
500
  }
554
- clusters.push({
555
- codepoints: syllable,
556
- baseIndex: hasReph ? baseIdx + 2 : baseIdx,
557
- // Adjust for reph prefix
558
- hasReph,
559
- preBaseMatras: preMatras
560
- });
561
501
  }
562
- return clusters;
563
502
  }
564
- function shapeBengaliText(str, fontData) {
565
- const { cmap, gsub, ligatures, markAnchors, widths, defaultWidth } = fontData;
566
- const shaped = [];
567
- function resolveGid(cp) {
568
- const normCp = cp === 8239 || cp === 160 ? 32 : cp;
569
- return cmap[normCp] || 0;
570
- }
571
- function resolveGidGsub(cp) {
572
- const gid = resolveGid(cp);
573
- if (gsub[gid] !== void 0) return gsub[gid];
574
- return gid;
575
- }
576
- function tryLig(gids) {
577
- return tryLigature(gids, ligatures);
578
- }
579
- function getAdv(gid) {
580
- return widths[gid] !== void 0 ? widths[gid] : defaultWidth;
581
- }
582
- function getBaseAnchor2(baseGid, markClass) {
583
- const base = markAnchors && markAnchors.bases && markAnchors.bases[baseGid];
584
- if (!base) return null;
585
- return base[markClass] ?? null;
586
- }
587
- function getMarkAnchor2(markGid) {
588
- const mark = markAnchors && markAnchors.marks && markAnchors.marks[markGid];
589
- if (!mark) return null;
590
- return { classIdx: mark[0], x: mark[1], y: mark[2] };
591
- }
592
- function emitGlyph(gid, isZero, baseGid) {
593
- if (isZero && baseGid !== void 0) {
594
- const markAnchor = getMarkAnchor2(gid);
595
- if (markAnchor) {
596
- const baseAnchorPt = getBaseAnchor2(baseGid, markAnchor.classIdx);
597
- if (baseAnchorPt) {
598
- const baseAdv = getAdv(baseGid);
599
- shaped.push({
600
- gid,
601
- dx: baseAnchorPt[0] - markAnchor.x - baseAdv,
602
- dy: baseAnchorPt[1] - markAnchor.y,
603
- isZeroAdvance: true
604
- });
605
- return;
606
- }
607
- }
608
- shaped.push({ gid, dx: 0, dy: 0, isZeroAdvance: true });
503
+ function assignLevels(types, paraLevel) {
504
+ const levels = [];
505
+ for (const t of types) {
506
+ if (paraLevel === 0) {
507
+ levels.push(t === "R" || t === "AN" ? 1 : 0);
609
508
  } else {
610
- shaped.push({ gid, dx: 0, dy: 0, isZeroAdvance: false });
509
+ levels.push(t === "L" ? 2 : 1);
611
510
  }
612
511
  }
613
- const clusters = buildBengaliClusters(str);
614
- for (const cluster of clusters) {
615
- const { codepoints, hasReph, preBaseMatras } = cluster;
616
- const baseStart = hasReph ? 2 : 0;
617
- let baseGid = 0;
618
- for (let ci = baseStart; ci < codepoints.length; ci++) {
619
- const ct = bengaliCharType(codepoints[ci]);
620
- if (ct === 0) {
621
- baseGid = resolveGid(codepoints[ci]);
622
- } else if (ct >= 2) {
623
- break;
512
+ return levels;
513
+ }
514
+ var SENTENCE_PUNCT = /* @__PURE__ */ new Set([
515
+ 46,
516
+ // .
517
+ 44,
518
+ // ,
519
+ 59,
520
+ // ;
521
+ 58,
522
+ // :
523
+ 33,
524
+ // !
525
+ 63
526
+ // ?
527
+ ]);
528
+ function fixPunctuationAffinity(types, codePoints, len) {
529
+ for (let i = 1; i < len; i++) {
530
+ if (types[i] === "R" && SENTENCE_PUNCT.has(codePoints[i])) {
531
+ let prevIdx = i - 1;
532
+ while (prevIdx >= 0 && (types[prevIdx] === "WS" || types[prevIdx] === "BN")) prevIdx--;
533
+ if (prevIdx >= 0 && types[prevIdx] === "L") {
534
+ types[i] = "L";
624
535
  }
625
536
  }
626
- const splitPostComponents = [];
627
- for (const mIdx of preBaseMatras) {
628
- if (mIdx < codepoints.length) {
629
- const mCp = codepoints[mIdx];
630
- if (mCp === 2507) {
631
- emitGlyph(resolveGid(2503), false);
632
- splitPostComponents.push(2494);
633
- } else if (mCp === 2508) {
634
- emitGlyph(resolveGid(2503), false);
635
- splitPostComponents.push(2519);
636
- } else {
637
- emitGlyph(resolveGid(mCp), false);
537
+ }
538
+ }
539
+ function fixBracketPairing(types, codePoints, len) {
540
+ const OPEN_BRACKETS = {
541
+ 40: 41,
542
+ // ( )
543
+ 91: 93,
544
+ // [ ]
545
+ 123: 125
546
+ // { → }
547
+ };
548
+ for (let i = 0; i < len; i++) {
549
+ const closer = OPEN_BRACKETS[codePoints[i]];
550
+ if (closer === void 0) continue;
551
+ let depth = 1;
552
+ let closeIdx = -1;
553
+ for (let j = i + 1; j < len; j++) {
554
+ if (codePoints[j] === codePoints[i]) depth++;
555
+ else if (codePoints[j] === closer) {
556
+ depth--;
557
+ if (depth === 0) {
558
+ closeIdx = j;
559
+ break;
638
560
  }
639
561
  }
640
562
  }
641
- const clusterGids = [];
642
- const clusterEndIdx = [];
643
- let matraStart = codepoints.length;
644
- for (let ci = baseStart; ci < codepoints.length; ci++) {
645
- const ct = bengaliCharType(codepoints[ci]);
646
- if (ct === 0 || ct === 7 || ct === 8) {
647
- clusterGids.push(resolveGid(codepoints[ci]));
648
- clusterEndIdx.push(ci);
649
- } else if (ct < 0 || ct === 1 || ct === 9) {
650
- emitGlyph(resolveGid(codepoints[ci]), false);
651
- } else {
652
- matraStart = ci;
563
+ if (closeIdx === -1) continue;
564
+ let hasL = false;
565
+ for (let j = i + 1; j < closeIdx; j++) {
566
+ if (types[j] === "L") {
567
+ hasL = true;
653
568
  break;
654
569
  }
655
570
  }
656
- let ligConsumed = 0;
657
- const ligResult = tryLig(clusterGids);
658
- if (ligResult) {
659
- emitGlyph(ligResult.resultGid, false);
660
- baseGid = ligResult.resultGid;
661
- ligConsumed = ligResult.consumed;
662
- let gi = ligConsumed;
663
- while (gi < clusterGids.length) {
664
- const subSeq = clusterGids.slice(gi);
665
- const subLig = tryLig(subSeq);
666
- if (subLig) {
667
- emitGlyph(subLig.resultGid, false);
668
- gi += subLig.consumed;
669
- } else {
670
- const origCi = clusterEndIdx[gi];
671
- const ct = bengaliCharType(codepoints[origCi]);
672
- if (ct === 7) {
673
- emitGlyph(clusterGids[gi], true, baseGid);
674
- } else {
675
- emitGlyph(clusterGids[gi], false);
571
+ if (hasL) {
572
+ types[i] = "L";
573
+ types[closeIdx] = "L";
574
+ }
575
+ }
576
+ }
577
+ function findOutermostIsolatePairs(codePoints) {
578
+ const pairs = [];
579
+ let i = 0;
580
+ while (i < codePoints.length) {
581
+ const cp = codePoints[i];
582
+ if (cp === 8294 || cp === 8295 || cp === 8296) {
583
+ let depth = 1;
584
+ let close = -1;
585
+ for (let j = i + 1; j < codePoints.length; j++) {
586
+ const cj = codePoints[j];
587
+ if (cj === 8294 || cj === 8295 || cj === 8296) depth++;
588
+ else if (cj === 8297) {
589
+ depth--;
590
+ if (depth === 0) {
591
+ close = j;
592
+ break;
676
593
  }
677
- gi++;
678
594
  }
679
595
  }
680
- } else {
681
- for (let ci = baseStart; ci < matraStart; ci++) {
682
- const cp = codepoints[ci];
683
- const ct = bengaliCharType(cp);
684
- if (ct === 0) {
685
- emitGlyph(resolveGid(cp), false);
686
- } else if (ct === 7) {
687
- emitGlyph(resolveGid(cp), true, baseGid);
688
- } else if (ct === 8) {
689
- emitGlyph(resolveGid(cp), true, baseGid);
690
- }
596
+ if (close === -1) {
597
+ i++;
598
+ continue;
691
599
  }
692
- }
693
- for (let ci = matraStart; ci < codepoints.length; ci++) {
694
- const cp = codepoints[ci];
695
- const ct = bengaliCharType(cp);
696
- if (ct === 4) continue;
697
- if (ct === 2 || ct === 3) {
698
- emitGlyph(resolveGid(cp), true, baseGid);
699
- } else if (ct === 5) {
700
- emitGlyph(resolveGid(cp), false);
701
- } else if (ct === 6) {
702
- emitGlyph(resolveGid(cp), true, baseGid);
703
- } else if (ct === 9) {
704
- emitGlyph(resolveGid(cp), false);
705
- } else {
706
- emitGlyph(resolveGid(cp), false);
600
+ const kind = cp === 8294 ? "LRI" : cp === 8295 ? "RLI" : "FSI";
601
+ pairs.push({ open: i, close, kind });
602
+ i = close + 1;
603
+ } else {
604
+ i++;
605
+ }
606
+ }
607
+ return pairs;
608
+ }
609
+ function normalizeBidiEmbeddings(text) {
610
+ const LRE = 8234, RLE = 8235, PDF_CP = 8236, LRO = 8237, RLO = 8238;
611
+ const LRI = 8294, RLI = 8295, PDI = 8297;
612
+ let hasEmbed = false;
613
+ for (let i = 0; i < text.length; i++) {
614
+ const c = text.charCodeAt(i);
615
+ if (c === LRE || c === RLE || c === PDF_CP || c === LRO || c === RLO) {
616
+ hasEmbed = true;
617
+ break;
618
+ }
619
+ }
620
+ if (!hasEmbed) return text;
621
+ const stack = [];
622
+ const out = [];
623
+ const MAX_DEPTH = 125;
624
+ for (let i = 0; i < text.length; ) {
625
+ const cp = text.codePointAt(i) ?? 0;
626
+ const cpLen = cp > 65535 ? 2 : 1;
627
+ if (cp === LRE || cp === RLO || cp === LRO || cp === RLE) {
628
+ if (stack.length >= MAX_DEPTH) {
629
+ i += cpLen;
630
+ continue;
707
631
  }
632
+ stack.push(1);
633
+ out.push(cp === LRE || cp === LRO ? LRI : RLI);
634
+ i += cpLen;
635
+ } else if (cp === PDF_CP) {
636
+ if (stack.pop()) {
637
+ out.push(PDI);
638
+ }
639
+ i += cpLen;
640
+ } else {
641
+ out.push(cp);
642
+ i += cpLen;
708
643
  }
709
- for (const postCp of splitPostComponents) {
710
- emitGlyph(resolveGid(postCp), false);
644
+ }
645
+ let result = "";
646
+ for (let i = 0; i < out.length; i++) result += String.fromCodePoint(out[i]);
647
+ return result;
648
+ }
649
+ function resolveBidiRuns(text) {
650
+ if (!text) return [];
651
+ const normalized = normalizeBidiEmbeddings(text);
652
+ if (normalized !== text) {
653
+ return resolveBidiRuns(normalized);
654
+ }
655
+ const codePoints = [];
656
+ const cpToStr = [];
657
+ for (let i = 0; i < text.length; ) {
658
+ cpToStr.push(i);
659
+ const cp = text.codePointAt(i) ?? 0;
660
+ codePoints.push(cp);
661
+ i += cp > 65535 ? 2 : 1;
662
+ }
663
+ cpToStr.push(text.length);
664
+ const isolates = findOutermostIsolatePairs(codePoints);
665
+ if (isolates.length === 0) {
666
+ return resolveBidiCore(text, codePoints, cpToStr);
667
+ }
668
+ const insideIsolate = new Array(codePoints.length).fill(false);
669
+ for (const p of isolates) {
670
+ for (let k = p.open; k <= p.close; k++) insideIsolate[k] = true;
671
+ }
672
+ const outerTypes = codePoints.map((cp, idx) => insideIsolate[idx] ? "BN" : classifyBidiType(cp));
673
+ const parentLevel = detectParagraphLevel(outerTypes);
674
+ const out = [];
675
+ const emitSegment = (cpStart, cpEnd, forced) => {
676
+ if (cpStart >= cpEnd) return;
677
+ const segText = text.substring(cpToStr[cpStart], cpToStr[cpEnd]);
678
+ const segCps = codePoints.slice(cpStart, cpEnd);
679
+ const baseStrIdx = cpToStr[cpStart];
680
+ const segCpToStr = cpToStr.slice(cpStart, cpEnd + 1).map((x) => x - baseStrIdx);
681
+ const segRuns = forced === void 0 ? resolveBidiRuns(segText) : resolveBidiCore(segText, segCps, segCpToStr, forced);
682
+ for (const r of segRuns) {
683
+ out.push({ text: r.text, level: r.level, start: r.start + baseStrIdx });
711
684
  }
712
- if (hasReph) {
713
- const raGid = resolveGidGsub(RA);
714
- const halantGid = resolveGid(HALANT);
715
- emitGlyph(raGid, true, baseGid);
716
- emitGlyph(halantGid, true, baseGid);
685
+ };
686
+ let cursor = 0;
687
+ for (const pair of isolates) {
688
+ emitSegment(cursor, pair.open, parentLevel);
689
+ const innerStart = pair.open + 1;
690
+ const innerEnd = pair.close;
691
+ let innerLevel;
692
+ if (pair.kind === "LRI") innerLevel = 0;
693
+ else if (pair.kind === "RLI") innerLevel = 1;
694
+ else {
695
+ const innerTypes = codePoints.slice(innerStart, innerEnd).map(classifyBidiType);
696
+ innerLevel = detectParagraphLevel(innerTypes);
697
+ }
698
+ if (innerStart < innerEnd) {
699
+ const innerText = text.substring(cpToStr[innerStart], cpToStr[innerEnd]);
700
+ const innerRuns = resolveBidiRunsForced(innerText, innerLevel);
701
+ const baseStrIdx = cpToStr[innerStart];
702
+ for (const r of innerRuns) {
703
+ out.push({ text: r.text, level: r.level, start: r.start + baseStrIdx });
704
+ }
705
+ }
706
+ cursor = pair.close + 1;
707
+ }
708
+ emitSegment(cursor, codePoints.length, parentLevel);
709
+ return out;
710
+ }
711
+ function resolveBidiRunsForced(text, forcedLevel) {
712
+ if (!text) return [];
713
+ const codePoints = [];
714
+ const cpToStr = [];
715
+ for (let i = 0; i < text.length; ) {
716
+ cpToStr.push(i);
717
+ const cp = text.codePointAt(i) ?? 0;
718
+ codePoints.push(cp);
719
+ i += cp > 65535 ? 2 : 1;
720
+ }
721
+ cpToStr.push(text.length);
722
+ const isolates = findOutermostIsolatePairs(codePoints);
723
+ if (isolates.length === 0) {
724
+ return resolveBidiCore(text, codePoints, cpToStr, forcedLevel);
725
+ }
726
+ const out = [];
727
+ const emit = (cpStart, cpEnd, forced) => {
728
+ if (cpStart >= cpEnd) return;
729
+ const segText = text.substring(cpToStr[cpStart], cpToStr[cpEnd]);
730
+ const segCps = codePoints.slice(cpStart, cpEnd);
731
+ const baseStrIdx = cpToStr[cpStart];
732
+ const segCpToStr = cpToStr.slice(cpStart, cpEnd + 1).map((x) => x - baseStrIdx);
733
+ const segRuns = resolveBidiCore(segText, segCps, segCpToStr, forced);
734
+ for (const r of segRuns) {
735
+ out.push({ text: r.text, level: r.level, start: r.start + baseStrIdx });
736
+ }
737
+ };
738
+ let cursor = 0;
739
+ for (const pair of isolates) {
740
+ emit(cursor, pair.open, forcedLevel);
741
+ const innerStart = pair.open + 1;
742
+ const innerEnd = pair.close;
743
+ let innerLevel;
744
+ if (pair.kind === "LRI") innerLevel = 0;
745
+ else if (pair.kind === "RLI") innerLevel = 1;
746
+ else {
747
+ const innerTypes = codePoints.slice(innerStart, innerEnd).map(classifyBidiType);
748
+ innerLevel = detectParagraphLevel(innerTypes);
749
+ }
750
+ if (innerStart < innerEnd) {
751
+ const innerText = text.substring(cpToStr[innerStart], cpToStr[innerEnd]);
752
+ const innerRuns = resolveBidiRunsForced(innerText, innerLevel);
753
+ const baseStrIdx = cpToStr[innerStart];
754
+ for (const r of innerRuns) {
755
+ out.push({ text: r.text, level: r.level, start: r.start + baseStrIdx });
756
+ }
757
+ }
758
+ cursor = pair.close + 1;
759
+ }
760
+ emit(cursor, codePoints.length, forcedLevel);
761
+ return out;
762
+ }
763
+ function resolveBidiCore(text, codePoints, cpToStr, forcedLevel) {
764
+ const len = codePoints.length;
765
+ if (len === 0) return [];
766
+ const types = codePoints.map(classifyBidiType);
767
+ const paraLevel = forcedLevel !== void 0 ? forcedLevel : detectParagraphLevel(types);
768
+ resolveWeakTypes(types, paraLevel);
769
+ resolveNeutralTypes(types, paraLevel);
770
+ if (paraLevel === 1) {
771
+ fixPunctuationAffinity(types, codePoints, len);
772
+ fixBracketPairing(types, codePoints, len);
773
+ }
774
+ const levels = assignLevels(types, paraLevel);
775
+ const runs = [];
776
+ let runStart = 0;
777
+ let runLevel = levels[0];
778
+ for (let i = 1; i <= len; i++) {
779
+ if (i === len || levels[i] !== runLevel) {
780
+ const start = cpToStr[runStart];
781
+ const end = cpToStr[i];
782
+ let runText = text.substring(start, end);
783
+ if (runLevel % 2 === 1) {
784
+ runText = reverseString(runText);
785
+ }
786
+ runs.push({ text: runText, level: runLevel, start });
787
+ if (i < len) {
788
+ runStart = i;
789
+ runLevel = levels[i];
790
+ }
791
+ }
792
+ }
793
+ if (paraLevel === 1 && runs.length > 1) {
794
+ runs.reverse();
795
+ }
796
+ return runs;
797
+ }
798
+ function containsRTL(text) {
799
+ for (let i = 0; i < text.length; ) {
800
+ const cp = text.codePointAt(i) ?? 0;
801
+ const t = classifyBidiType(cp);
802
+ if (t === "R" || t === "AL") return true;
803
+ i += cp > 65535 ? 2 : 1;
804
+ }
805
+ return false;
806
+ }
807
+ function stripBidiControls(text) {
808
+ if (!text) return text;
809
+ let needs = false;
810
+ for (let i = 0; i < text.length; i++) {
811
+ const c = text.charCodeAt(i);
812
+ if (c === 8206 || c === 8207 || c >= 8234 && c <= 8238 || c >= 8294 && c <= 8297) {
813
+ needs = true;
814
+ break;
815
+ }
816
+ }
817
+ if (!needs) return text;
818
+ let out = "";
819
+ for (let i = 0; i < text.length; i++) {
820
+ const c = text.charCodeAt(i);
821
+ if (c === 8206 || c === 8207 || c >= 8234 && c <= 8238 || c >= 8294 && c <= 8297) continue;
822
+ out += text[i];
823
+ }
824
+ return out;
825
+ }
826
+ function reverseString(str) {
827
+ const cps = [];
828
+ for (let i = 0; i < str.length; ) {
829
+ const cp = str.codePointAt(i) ?? 0;
830
+ cps.push(cp);
831
+ i += cp > 65535 ? 2 : 1;
832
+ }
833
+ cps.reverse();
834
+ return String.fromCodePoint(...cps);
835
+ }
836
+
837
+ // src/fonts/encoding.ts
838
+ function toWinAnsi(str) {
839
+ if (!str) return "";
840
+ let r = "";
841
+ for (let i = 0; i < str.length; i++) {
842
+ const c = str.charCodeAt(i);
843
+ if (c >= 32 && c <= 126) r += str[i];
844
+ else if (c >= 160 && c <= 255) r += str[i];
845
+ else if (c === 8364) r += "\x80";
846
+ else if (c === 8218) r += "\x82";
847
+ else if (c === 402) r += "\x83";
848
+ else if (c === 8222) r += "\x84";
849
+ else if (c === 8230) r += "\x85";
850
+ else if (c === 8224) r += "\x86";
851
+ else if (c === 8225) r += "\x87";
852
+ else if (c === 710) r += "\x88";
853
+ else if (c === 8240) r += "\x89";
854
+ else if (c === 352) r += "\x8A";
855
+ else if (c === 8249) r += "\x8B";
856
+ else if (c === 338) r += "\x8C";
857
+ else if (c === 381) r += "\x8E";
858
+ else if (c === 8216) r += "\x91";
859
+ else if (c === 8217) r += "\x92";
860
+ else if (c === 8220) r += "\x93";
861
+ else if (c === 8221) r += "\x94";
862
+ else if (c === 8226) r += "\x95";
863
+ else if (c === 8211) r += "\x96";
864
+ else if (c === 8212) r += "\x97";
865
+ else if (c === 732) r += "\x98";
866
+ else if (c === 8482) r += "\x99";
867
+ else if (c === 353) r += "\x9A";
868
+ else if (c === 8250) r += "\x9B";
869
+ else if (c === 339) r += "\x9C";
870
+ else if (c === 382) r += "\x9E";
871
+ else if (c === 376) r += "\x9F";
872
+ else if (c === 160 || c === 8239) r += " ";
873
+ else if (c === 9 || c === 10 || c === 13) r += " ";
874
+ else if (c < 32) ; else r += "?";
875
+ }
876
+ return r;
877
+ }
878
+ function pdfString(str) {
879
+ const s = toWinAnsi(stripBidiControls(str));
880
+ return "(" + s.replace(/\\/g, "\\\\").replace(/\(/g, "\\(").replace(/\)/g, "\\)") + ")";
881
+ }
882
+ function truncate(str, max) {
883
+ if (!str || str.length <= max) return str || "";
884
+ if (max <= 1) return "\u2026";
885
+ return str.slice(0, max - 1) + "\u2026";
886
+ }
887
+ function helveticaWidth(str, sz) {
888
+ str = stripBidiControls(str);
889
+ let w = 0;
890
+ for (let i = 0; i < str.length; i++) {
891
+ const cp = str.codePointAt(i) ?? 0;
892
+ if (cp > 65535) i++;
893
+ if (cp >= 48 && cp <= 57) w += 556;
894
+ else if (cp >= 65 && cp <= 90) w += 680;
895
+ else if (cp >= 97 && cp <= 122) w += 500;
896
+ else if (cp === 32) w += 278;
897
+ else if (cp === 46 || cp === 44) w += 278;
898
+ else if (cp === 43) w += 584;
899
+ else if (cp === 45) w += 333;
900
+ else if (cp === 47 || cp === 58) w += 278;
901
+ else if (cp === 8212) w += 1e3;
902
+ else if (cp === 8211) w += 556;
903
+ else if (cp === 8230) w += 1e3;
904
+ else if (cp === 8216 || cp === 8217) w += 222;
905
+ else if (cp === 8220 || cp === 8221) w += 333;
906
+ else if (cp === 8364) w += 556;
907
+ else w += 556;
908
+ }
909
+ return w * sz / 1e3;
910
+ }
911
+ function helveticaBoldWidth(str, sz) {
912
+ str = stripBidiControls(str);
913
+ let w = 0;
914
+ for (let i = 0; i < str.length; i++) {
915
+ const cp = str.codePointAt(i) ?? 0;
916
+ if (cp > 65535) i++;
917
+ if (cp >= 48 && cp <= 57) w += 556;
918
+ else if (cp >= 65 && cp <= 90) w += 722;
919
+ else if (cp >= 97 && cp <= 122) w += 611;
920
+ else if (cp === 32) w += 278;
921
+ else if (cp === 46 || cp === 44) w += 278;
922
+ else if (cp === 43) w += 584;
923
+ else if (cp === 45) w += 333;
924
+ else if (cp === 47 || cp === 58) w += 278;
925
+ else if (cp === 8212) w += 1e3;
926
+ else if (cp === 8211) w += 556;
927
+ else if (cp === 8230) w += 1e3;
928
+ else if (cp === 8216 || cp === 8217) w += 278;
929
+ else if (cp === 8220 || cp === 8221) w += 500;
930
+ else if (cp === 8364) w += 556;
931
+ else w += 611;
932
+ }
933
+ return w * sz / 1e3;
934
+ }
935
+
936
+ // src/shaping/gsub-driver.ts
937
+ function tryLigature(gids, ligatures) {
938
+ if (!ligatures || gids.length < 2) return null;
939
+ const firstGid = gids[0];
940
+ const entries = ligatures[firstGid];
941
+ if (!entries) return null;
942
+ for (const entry of entries) {
943
+ const compCount = entry.length - 1;
944
+ if (compCount > gids.length - 1) continue;
945
+ let match = true;
946
+ for (let ci = 0; ci < compCount; ci++) {
947
+ if (gids[1 + ci] !== entry[1 + ci]) {
948
+ match = false;
949
+ break;
950
+ }
717
951
  }
952
+ if (match) return { resultGid: entry[0], consumed: compCount + 1 };
718
953
  }
719
- return shaped;
954
+ return null;
720
955
  }
721
956
 
722
- // src/shaping/tamil-shaper.ts
723
- var PULLI = 3021;
724
- function tamilCharType(cp) {
725
- if (cp === PULLI) return 7;
726
- if (cp === 2946 || cp === 2947) return 6;
727
- if (cp >= 2949 && cp <= 2964) return 1;
728
- if (cp >= 2965 && cp <= 3001) return 0;
729
- if (cp === 3007) return 4;
730
- if (cp === 3014) return 4;
731
- if (cp === 3015) return 4;
732
- if (cp === 3016) return 4;
733
- if (cp === 3018) return 4;
734
- if (cp === 3019) return 4;
735
- if (cp === 3020) return 4;
736
- if (cp === 3006) return 5;
737
- if (cp === 3008) return 5;
738
- if (cp === 3009 || cp === 3010) return 2;
739
- if (cp === 3031) return 5;
740
- if (cp >= 3006 && cp <= 3020) return 2;
741
- if (cp >= 3046 && cp <= 3055) return 9;
742
- if (cp >= 3056 && cp <= 3066) return 9;
743
- if (cp === 3024) return 6;
957
+ // src/shaping/bengali-shaper.ts
958
+ var HALANT = 2509;
959
+ var NUKTA = 2492;
960
+ var RA = 2480;
961
+ function bengaliCharType(cp) {
962
+ if (cp === HALANT) return 7;
963
+ if (cp === NUKTA) return 8;
964
+ if (cp >= 2433 && cp <= 2435) return 6;
965
+ if (cp >= 2437 && cp <= 2452) return 1;
966
+ if (cp >= 2453 && cp <= 2489) return 0;
967
+ if (cp === 2495) return 4;
968
+ if (cp === 2503) return 4;
969
+ if (cp === 2504) return 4;
970
+ if (cp === 2497 || cp === 2498 || cp === 2499 || cp === 2500) return 3;
971
+ if (cp === 2494) return 5;
972
+ if (cp === 2496) return 5;
973
+ if (cp === 2507) return 4;
974
+ if (cp === 2508) return 4;
975
+ if (cp >= 2494 && cp <= 2508) return 2;
976
+ if (cp === 2519) return 5;
977
+ if (cp >= 2534 && cp <= 2543) return 9;
978
+ if (cp === 2527) return 0;
979
+ if (cp === 2493) return 1;
980
+ if (cp === 2510) return 0;
744
981
  return -1;
745
982
  }
746
- function isConsonant2(cp) {
747
- return tamilCharType(cp) === 0;
983
+ function isConsonant(cp) {
984
+ return bengaliCharType(cp) === 0;
748
985
  }
749
- function buildTamilClusters(str) {
986
+ function buildBengaliClusters(str) {
750
987
  const clusters = [];
751
988
  const cps = [];
752
989
  for (let i2 = 0; i2 < str.length; ) {
@@ -757,24 +994,35 @@ function buildTamilClusters(str) {
757
994
  let i = 0;
758
995
  while (i < cps.length) {
759
996
  const cp = cps[i];
760
- const type = tamilCharType(cp);
761
- if (type < 0 || cp < TAMIL_START || cp > TAMIL_END) {
762
- clusters.push({ codepoints: [cp], baseIndex: 0, preBaseMatras: [] });
997
+ const type = bengaliCharType(cp);
998
+ if (type < 0 || cp < BENGALI_START || cp > BENGALI_END) {
999
+ clusters.push({ codepoints: [cp], baseIndex: 0, hasReph: false, preBaseMatras: [] });
763
1000
  i++;
764
1001
  continue;
765
1002
  }
766
1003
  const syllable = [];
767
- let lastConsonantIdx = -1;
1004
+ let baseIdx = 0;
1005
+ let hasReph = false;
768
1006
  const preMatras = [];
1007
+ if (isConsonant(cp) && cp === RA && i + 2 < cps.length && cps[i + 1] === HALANT && isConsonant(cps[i + 2])) {
1008
+ hasReph = true;
1009
+ syllable.push(cp, cps[i + 1]);
1010
+ i += 2;
1011
+ }
1012
+ let lastConsonantIdx = -1;
769
1013
  while (i < cps.length) {
770
1014
  const cc = cps[i];
771
- const ct = tamilCharType(cc);
1015
+ const ct = bengaliCharType(cc);
772
1016
  if (ct === 0) {
773
1017
  lastConsonantIdx = syllable.length;
774
1018
  syllable.push(cc);
775
1019
  i++;
776
- if (i < cps.length && cps[i] === PULLI) {
777
- if (i + 1 < cps.length && isConsonant2(cps[i + 1])) {
1020
+ if (i < cps.length && cps[i] === NUKTA) {
1021
+ syllable.push(cps[i]);
1022
+ i++;
1023
+ }
1024
+ if (i < cps.length && cps[i] === HALANT) {
1025
+ if (i + 1 < cps.length && isConsonant(cps[i + 1])) {
778
1026
  syllable.push(cps[i]);
779
1027
  i++;
780
1028
  continue;
@@ -789,9 +1037,9 @@ function buildTamilClusters(str) {
789
1037
  break;
790
1038
  }
791
1039
  }
792
- const baseIdx = lastConsonantIdx >= 0 ? lastConsonantIdx : 0;
1040
+ baseIdx = lastConsonantIdx >= 0 ? lastConsonantIdx : 0;
793
1041
  while (i < cps.length) {
794
- const ct = tamilCharType(cps[i]);
1042
+ const ct = bengaliCharType(cps[i]);
795
1043
  if (ct >= 2 && ct <= 5) {
796
1044
  if (ct === 4) {
797
1045
  preMatras.push(syllable.length);
@@ -802,7 +1050,7 @@ function buildTamilClusters(str) {
802
1050
  break;
803
1051
  }
804
1052
  }
805
- while (i < cps.length && tamilCharType(cps[i]) === 6) {
1053
+ while (i < cps.length && bengaliCharType(cps[i]) === 6) {
806
1054
  syllable.push(cps[i]);
807
1055
  i++;
808
1056
  }
@@ -812,19 +1060,26 @@ function buildTamilClusters(str) {
812
1060
  }
813
1061
  clusters.push({
814
1062
  codepoints: syllable,
815
- baseIndex: baseIdx,
1063
+ baseIndex: hasReph ? baseIdx + 2 : baseIdx,
1064
+ // Adjust for reph prefix
1065
+ hasReph,
816
1066
  preBaseMatras: preMatras
817
1067
  });
818
1068
  }
819
1069
  return clusters;
820
1070
  }
821
- function shapeTamilText(str, fontData) {
822
- const { cmap, ligatures, markAnchors, widths, defaultWidth } = fontData;
1071
+ function shapeBengaliText(str, fontData) {
1072
+ const { cmap, gsub, ligatures, markAnchors, widths, defaultWidth } = fontData;
823
1073
  const shaped = [];
824
1074
  function resolveGid(cp) {
825
1075
  const normCp = cp === 8239 || cp === 160 ? 32 : cp;
826
1076
  return cmap[normCp] || 0;
827
1077
  }
1078
+ function resolveGidGsub(cp) {
1079
+ const gid = resolveGid(cp);
1080
+ if (gsub[gid] !== void 0) return gsub[gid];
1081
+ return gid;
1082
+ }
828
1083
  function tryLig(gids) {
829
1084
  return tryLigature(gids, ligatures);
830
1085
  }
@@ -862,12 +1117,13 @@ function shapeTamilText(str, fontData) {
862
1117
  shaped.push({ gid, dx: 0, dy: 0, isZeroAdvance: false });
863
1118
  }
864
1119
  }
865
- const clusters = buildTamilClusters(str);
1120
+ const clusters = buildBengaliClusters(str);
866
1121
  for (const cluster of clusters) {
867
- const { codepoints, preBaseMatras } = cluster;
1122
+ const { codepoints, hasReph, preBaseMatras } = cluster;
1123
+ const baseStart = hasReph ? 2 : 0;
868
1124
  let baseGid = 0;
869
- for (let ci = 0; ci < codepoints.length; ci++) {
870
- const ct = tamilCharType(codepoints[ci]);
1125
+ for (let ci = baseStart; ci < codepoints.length; ci++) {
1126
+ const ct = bengaliCharType(codepoints[ci]);
871
1127
  if (ct === 0) {
872
1128
  baseGid = resolveGid(codepoints[ci]);
873
1129
  } else if (ct >= 2) {
@@ -878,15 +1134,12 @@ function shapeTamilText(str, fontData) {
878
1134
  for (const mIdx of preBaseMatras) {
879
1135
  if (mIdx < codepoints.length) {
880
1136
  const mCp = codepoints[mIdx];
881
- if (mCp === 3018) {
882
- emitGlyph(resolveGid(3014), false);
883
- splitPostComponents.push(3006);
884
- } else if (mCp === 3019) {
885
- emitGlyph(resolveGid(3015), false);
886
- splitPostComponents.push(3006);
887
- } else if (mCp === 3020) {
888
- emitGlyph(resolveGid(3014), false);
889
- splitPostComponents.push(3031);
1137
+ if (mCp === 2507) {
1138
+ emitGlyph(resolveGid(2503), false);
1139
+ splitPostComponents.push(2494);
1140
+ } else if (mCp === 2508) {
1141
+ emitGlyph(resolveGid(2503), false);
1142
+ splitPostComponents.push(2519);
890
1143
  } else {
891
1144
  emitGlyph(resolveGid(mCp), false);
892
1145
  }
@@ -895,18 +1148,16 @@ function shapeTamilText(str, fontData) {
895
1148
  const clusterGids = [];
896
1149
  const clusterEndIdx = [];
897
1150
  let matraStart = codepoints.length;
898
- for (let ci = 0; ci < codepoints.length; ci++) {
899
- const ct = tamilCharType(codepoints[ci]);
900
- if (ct === 0 || ct === 7) {
1151
+ for (let ci = baseStart; ci < codepoints.length; ci++) {
1152
+ const ct = bengaliCharType(codepoints[ci]);
1153
+ if (ct === 0 || ct === 7 || ct === 8) {
901
1154
  clusterGids.push(resolveGid(codepoints[ci]));
902
1155
  clusterEndIdx.push(ci);
903
- } else if (ct >= 2) {
1156
+ } else if (ct < 0 || ct === 1 || ct === 9) {
1157
+ emitGlyph(resolveGid(codepoints[ci]), false);
1158
+ } else {
904
1159
  matraStart = ci;
905
1160
  break;
906
- } else if (ct < 0) {
907
- emitGlyph(resolveGid(codepoints[ci]), false);
908
- } else if (ct === 1) {
909
- emitGlyph(resolveGid(codepoints[ci]), false);
910
1161
  }
911
1162
  }
912
1163
  let ligConsumed = 0;
@@ -924,7 +1175,7 @@ function shapeTamilText(str, fontData) {
924
1175
  gi += subLig.consumed;
925
1176
  } else {
926
1177
  const origCi = clusterEndIdx[gi];
927
- const ct = tamilCharType(codepoints[origCi]);
1178
+ const ct = bengaliCharType(codepoints[origCi]);
928
1179
  if (ct === 7) {
929
1180
  emitGlyph(clusterGids[gi], true, baseGid);
930
1181
  } else {
@@ -934,19 +1185,21 @@ function shapeTamilText(str, fontData) {
934
1185
  }
935
1186
  }
936
1187
  } else {
937
- for (let ci = 0; ci < matraStart; ci++) {
1188
+ for (let ci = baseStart; ci < matraStart; ci++) {
938
1189
  const cp = codepoints[ci];
939
- const ct = tamilCharType(cp);
1190
+ const ct = bengaliCharType(cp);
940
1191
  if (ct === 0) {
941
1192
  emitGlyph(resolveGid(cp), false);
942
1193
  } else if (ct === 7) {
943
1194
  emitGlyph(resolveGid(cp), true, baseGid);
1195
+ } else if (ct === 8) {
1196
+ emitGlyph(resolveGid(cp), true, baseGid);
944
1197
  }
945
1198
  }
946
1199
  }
947
1200
  for (let ci = matraStart; ci < codepoints.length; ci++) {
948
1201
  const cp = codepoints[ci];
949
- const ct = tamilCharType(cp);
1202
+ const ct = bengaliCharType(cp);
950
1203
  if (ct === 4) continue;
951
1204
  if (ct === 2 || ct === 3) {
952
1205
  emitGlyph(resolveGid(cp), true, baseGid);
@@ -963,60 +1216,44 @@ function shapeTamilText(str, fontData) {
963
1216
  for (const postCp of splitPostComponents) {
964
1217
  emitGlyph(resolveGid(postCp), false);
965
1218
  }
1219
+ if (hasReph) {
1220
+ const raGid = resolveGidGsub(RA);
1221
+ const halantGid = resolveGid(HALANT);
1222
+ emitGlyph(raGid, true, baseGid);
1223
+ emitGlyph(halantGid, true, baseGid);
1224
+ }
966
1225
  }
967
1226
  return shaped;
968
1227
  }
969
1228
 
970
- // src/shaping/gpos-positioner.ts
971
- function getBaseAnchor(markAnchors, baseGid, markClass) {
972
- if (!markAnchors) return null;
973
- const base = markAnchors.bases[baseGid];
974
- if (!base) return null;
975
- return base[markClass] ?? null;
976
- }
977
- function getMarkAnchor(markAnchors, markGid) {
978
- if (!markAnchors) return null;
979
- const mark = markAnchors.marks[markGid];
980
- if (!mark) return null;
981
- return { classIdx: mark[0], x: mark[1], y: mark[2] };
982
- }
983
- function positionMarkOnBase(markAnchors, markGid, baseGid, baseAdv) {
984
- const mark = getMarkAnchor(markAnchors, markGid);
985
- if (!mark) return null;
986
- const base = getBaseAnchor(markAnchors, baseGid, mark.classIdx);
987
- if (!base) return null;
988
- return {
989
- dx: base[0] - mark.x - baseAdv,
990
- dy: base[1] - mark.y
991
- };
992
- }
993
-
994
- // src/shaping/devanagari-shaper.ts
995
- var HALANT2 = 2381;
996
- var NUKTA2 = 2364;
997
- var RA2 = 2352;
998
- function devanagariCharType(cp) {
999
- if (cp === HALANT2) return 7;
1000
- if (cp === NUKTA2) return 8;
1001
- if (cp >= 2305 && cp <= 2307) return 6;
1002
- if (cp >= 2308 && cp <= 2324) return 1;
1003
- if (cp >= 2325 && cp <= 2361) return 0;
1004
- if (cp === 2367) return 4;
1005
- if (cp >= 2369 && cp <= 2372) return 3;
1006
- if (cp === 2366 || cp === 2368) return 5;
1007
- if (cp === 2375 || cp === 2376) return 2;
1008
- if (cp === 2379 || cp === 2380) return 4;
1009
- if (cp >= 2373 && cp <= 2380) return 2;
1010
- if (cp >= 2406 && cp <= 2415) return 9;
1011
- if (cp >= 2392 && cp <= 2399) return 0;
1012
- if (cp === 2365) return 1;
1013
- if (cp === 2384) return 1;
1229
+ // src/shaping/tamil-shaper.ts
1230
+ var PULLI = 3021;
1231
+ function tamilCharType(cp) {
1232
+ if (cp === PULLI) return 7;
1233
+ if (cp === 2946 || cp === 2947) return 6;
1234
+ if (cp >= 2949 && cp <= 2964) return 1;
1235
+ if (cp >= 2965 && cp <= 3001) return 0;
1236
+ if (cp === 3007) return 4;
1237
+ if (cp === 3014) return 4;
1238
+ if (cp === 3015) return 4;
1239
+ if (cp === 3016) return 4;
1240
+ if (cp === 3018) return 4;
1241
+ if (cp === 3019) return 4;
1242
+ if (cp === 3020) return 4;
1243
+ if (cp === 3006) return 5;
1244
+ if (cp === 3008) return 5;
1245
+ if (cp === 3009 || cp === 3010) return 2;
1246
+ if (cp === 3031) return 5;
1247
+ if (cp >= 3006 && cp <= 3020) return 2;
1248
+ if (cp >= 3046 && cp <= 3055) return 9;
1249
+ if (cp >= 3056 && cp <= 3066) return 9;
1250
+ if (cp === 3024) return 6;
1014
1251
  return -1;
1015
1252
  }
1016
- function isConsonant3(cp) {
1017
- return devanagariCharType(cp) === 0;
1253
+ function isConsonant2(cp) {
1254
+ return tamilCharType(cp) === 0;
1018
1255
  }
1019
- function buildDevanagariClusters(str) {
1256
+ function buildTamilClusters(str) {
1020
1257
  const clusters = [];
1021
1258
  const cps = [];
1022
1259
  for (let i2 = 0; i2 < str.length; ) {
@@ -1027,34 +1264,24 @@ function buildDevanagariClusters(str) {
1027
1264
  let i = 0;
1028
1265
  while (i < cps.length) {
1029
1266
  const cp = cps[i];
1030
- const type = devanagariCharType(cp);
1031
- if (type < 0 || cp < DEVANAGARI_START || cp > DEVANAGARI_END) {
1032
- clusters.push({ codepoints: [cp], baseIndex: 0, hasReph: false, preBaseMatras: [] });
1267
+ const type = tamilCharType(cp);
1268
+ if (type < 0 || cp < TAMIL_START || cp > TAMIL_END) {
1269
+ clusters.push({ codepoints: [cp], baseIndex: 0, preBaseMatras: [] });
1033
1270
  i++;
1034
1271
  continue;
1035
1272
  }
1036
1273
  const syllable = [];
1037
- let hasReph = false;
1038
- const preMatras = [];
1039
- if (isConsonant3(cp) && cp === RA2 && i + 2 < cps.length && cps[i + 1] === HALANT2 && isConsonant3(cps[i + 2])) {
1040
- hasReph = true;
1041
- syllable.push(cp, cps[i + 1]);
1042
- i += 2;
1043
- }
1044
1274
  let lastConsonantIdx = -1;
1275
+ const preMatras = [];
1045
1276
  while (i < cps.length) {
1046
1277
  const cc = cps[i];
1047
- const ct = devanagariCharType(cc);
1278
+ const ct = tamilCharType(cc);
1048
1279
  if (ct === 0) {
1049
1280
  lastConsonantIdx = syllable.length;
1050
1281
  syllable.push(cc);
1051
1282
  i++;
1052
- if (i < cps.length && cps[i] === NUKTA2) {
1053
- syllable.push(cps[i]);
1054
- i++;
1055
- }
1056
- if (i < cps.length && cps[i] === HALANT2) {
1057
- if (i + 1 < cps.length && isConsonant3(cps[i + 1])) {
1283
+ if (i < cps.length && cps[i] === PULLI) {
1284
+ if (i + 1 < cps.length && isConsonant2(cps[i + 1])) {
1058
1285
  syllable.push(cps[i]);
1059
1286
  i++;
1060
1287
  continue;
@@ -1071,7 +1298,7 @@ function buildDevanagariClusters(str) {
1071
1298
  }
1072
1299
  const baseIdx = lastConsonantIdx >= 0 ? lastConsonantIdx : 0;
1073
1300
  while (i < cps.length) {
1074
- const ct = devanagariCharType(cps[i]);
1301
+ const ct = tamilCharType(cps[i]);
1075
1302
  if (ct >= 2 && ct <= 5) {
1076
1303
  if (ct === 4) {
1077
1304
  preMatras.push(syllable.length);
@@ -1082,7 +1309,7 @@ function buildDevanagariClusters(str) {
1082
1309
  break;
1083
1310
  }
1084
1311
  }
1085
- while (i < cps.length && devanagariCharType(cps[i]) === 6) {
1312
+ while (i < cps.length && tamilCharType(cps[i]) === 6) {
1086
1313
  syllable.push(cps[i]);
1087
1314
  i++;
1088
1315
  }
@@ -1092,15 +1319,14 @@ function buildDevanagariClusters(str) {
1092
1319
  }
1093
1320
  clusters.push({
1094
1321
  codepoints: syllable,
1095
- baseIndex: hasReph ? baseIdx + 2 : baseIdx,
1096
- hasReph,
1322
+ baseIndex: baseIdx,
1097
1323
  preBaseMatras: preMatras
1098
1324
  });
1099
1325
  }
1100
1326
  return clusters;
1101
1327
  }
1102
- function shapeDevanagariText(str, fontData) {
1103
- const { cmap, gsub, ligatures, markAnchors, widths, defaultWidth } = fontData;
1328
+ function shapeTamilText(str, fontData) {
1329
+ const { cmap, ligatures, markAnchors, widths, defaultWidth } = fontData;
1104
1330
  const shaped = [];
1105
1331
  function resolveGid(cp) {
1106
1332
  const normCp = cp === 8239 || cp === 160 ? 32 : cp;
@@ -1112,11 +1338,21 @@ function shapeDevanagariText(str, fontData) {
1112
1338
  function getAdv(gid) {
1113
1339
  return widths[gid] !== void 0 ? widths[gid] : defaultWidth;
1114
1340
  }
1341
+ function getBaseAnchor2(baseGid, markClass) {
1342
+ const base = markAnchors && markAnchors.bases && markAnchors.bases[baseGid];
1343
+ if (!base) return null;
1344
+ return base[markClass] ?? null;
1345
+ }
1346
+ function getMarkAnchor2(markGid) {
1347
+ const mark = markAnchors && markAnchors.marks && markAnchors.marks[markGid];
1348
+ if (!mark) return null;
1349
+ return { classIdx: mark[0], x: mark[1], y: mark[2] };
1350
+ }
1115
1351
  function emitGlyph(gid, isZero, baseGid) {
1116
1352
  if (isZero && baseGid !== void 0) {
1117
- const markAnchor = getMarkAnchor(markAnchors, gid);
1353
+ const markAnchor = getMarkAnchor2(gid);
1118
1354
  if (markAnchor) {
1119
- const baseAnchorPt = getBaseAnchor(markAnchors, baseGid, markAnchor.classIdx);
1355
+ const baseAnchorPt = getBaseAnchor2(baseGid, markAnchor.classIdx);
1120
1356
  if (baseAnchorPt) {
1121
1357
  const baseAdv = getAdv(baseGid);
1122
1358
  shaped.push({
@@ -1133,13 +1369,12 @@ function shapeDevanagariText(str, fontData) {
1133
1369
  shaped.push({ gid, dx: 0, dy: 0, isZeroAdvance: false });
1134
1370
  }
1135
1371
  }
1136
- const clusters = buildDevanagariClusters(str);
1372
+ const clusters = buildTamilClusters(str);
1137
1373
  for (const cluster of clusters) {
1138
- const { codepoints, hasReph, preBaseMatras } = cluster;
1139
- const baseStart = hasReph ? 2 : 0;
1374
+ const { codepoints, preBaseMatras } = cluster;
1140
1375
  let baseGid = 0;
1141
- for (let ci = baseStart; ci < codepoints.length; ci++) {
1142
- const ct = devanagariCharType(codepoints[ci]);
1376
+ for (let ci = 0; ci < codepoints.length; ci++) {
1377
+ const ct = tamilCharType(codepoints[ci]);
1143
1378
  if (ct === 0) {
1144
1379
  baseGid = resolveGid(codepoints[ci]);
1145
1380
  } else if (ct >= 2) {
@@ -1150,48 +1385,44 @@ function shapeDevanagariText(str, fontData) {
1150
1385
  for (const mIdx of preBaseMatras) {
1151
1386
  if (mIdx < codepoints.length) {
1152
1387
  const mCp = codepoints[mIdx];
1153
- if (mCp === 2379) {
1154
- emitGlyph(resolveGid(2375), false);
1155
- splitPostComponents.push(2366);
1156
- } else if (mCp === 2380) {
1157
- emitGlyph(resolveGid(2375), false);
1158
- splitPostComponents.push(2380);
1388
+ if (mCp === 3018) {
1389
+ emitGlyph(resolveGid(3014), false);
1390
+ splitPostComponents.push(3006);
1391
+ } else if (mCp === 3019) {
1392
+ emitGlyph(resolveGid(3015), false);
1393
+ splitPostComponents.push(3006);
1394
+ } else if (mCp === 3020) {
1395
+ emitGlyph(resolveGid(3014), false);
1396
+ splitPostComponents.push(3031);
1159
1397
  } else {
1160
1398
  emitGlyph(resolveGid(mCp), false);
1161
1399
  }
1162
1400
  }
1163
1401
  }
1164
- if (hasReph) {
1165
- const raGid = resolveGid(RA2);
1166
- const halantGid = resolveGid(HALANT2);
1167
- const rephLig = tryLig([raGid, halantGid]);
1168
- if (rephLig) {
1169
- emitGlyph(rephLig.resultGid, true, baseGid);
1170
- } else {
1171
- const raGsubbed = gsub[raGid] !== void 0 ? gsub[raGid] : raGid;
1172
- emitGlyph(raGsubbed, true, baseGid);
1173
- }
1174
- }
1175
1402
  const clusterGids = [];
1176
1403
  const clusterEndIdx = [];
1177
1404
  let matraStart = codepoints.length;
1178
- for (let ci = baseStart; ci < codepoints.length; ci++) {
1179
- const ct = devanagariCharType(codepoints[ci]);
1180
- if (ct === 0 || ct === 7 || ct === 8) {
1405
+ for (let ci = 0; ci < codepoints.length; ci++) {
1406
+ const ct = tamilCharType(codepoints[ci]);
1407
+ if (ct === 0 || ct === 7) {
1181
1408
  clusterGids.push(resolveGid(codepoints[ci]));
1182
1409
  clusterEndIdx.push(ci);
1183
- } else if (ct < 0 || ct === 1 || ct === 9) {
1184
- emitGlyph(resolveGid(codepoints[ci]), false);
1185
- } else {
1410
+ } else if (ct >= 2) {
1186
1411
  matraStart = ci;
1187
1412
  break;
1413
+ } else if (ct < 0) {
1414
+ emitGlyph(resolveGid(codepoints[ci]), false);
1415
+ } else if (ct === 1) {
1416
+ emitGlyph(resolveGid(codepoints[ci]), false);
1188
1417
  }
1189
1418
  }
1419
+ let ligConsumed = 0;
1190
1420
  const ligResult = tryLig(clusterGids);
1191
1421
  if (ligResult) {
1192
1422
  emitGlyph(ligResult.resultGid, false);
1193
1423
  baseGid = ligResult.resultGid;
1194
- let gi = ligResult.consumed;
1424
+ ligConsumed = ligResult.consumed;
1425
+ let gi = ligConsumed;
1195
1426
  while (gi < clusterGids.length) {
1196
1427
  const subSeq = clusterGids.slice(gi);
1197
1428
  const subLig = tryLig(subSeq);
@@ -1200,7 +1431,7 @@ function shapeDevanagariText(str, fontData) {
1200
1431
  gi += subLig.consumed;
1201
1432
  } else {
1202
1433
  const origCi = clusterEndIdx[gi];
1203
- const ct = devanagariCharType(codepoints[origCi]);
1434
+ const ct = tamilCharType(codepoints[origCi]);
1204
1435
  if (ct === 7) {
1205
1436
  emitGlyph(clusterGids[gi], true, baseGid);
1206
1437
  } else {
@@ -1210,21 +1441,19 @@ function shapeDevanagariText(str, fontData) {
1210
1441
  }
1211
1442
  }
1212
1443
  } else {
1213
- for (let ci = baseStart; ci < matraStart; ci++) {
1444
+ for (let ci = 0; ci < matraStart; ci++) {
1214
1445
  const cp = codepoints[ci];
1215
- const ct = devanagariCharType(cp);
1446
+ const ct = tamilCharType(cp);
1216
1447
  if (ct === 0) {
1217
1448
  emitGlyph(resolveGid(cp), false);
1218
1449
  } else if (ct === 7) {
1219
1450
  emitGlyph(resolveGid(cp), true, baseGid);
1220
- } else if (ct === 8) {
1221
- emitGlyph(resolveGid(cp), true, baseGid);
1222
1451
  }
1223
1452
  }
1224
1453
  }
1225
1454
  for (let ci = matraStart; ci < codepoints.length; ci++) {
1226
1455
  const cp = codepoints[ci];
1227
- const ct = devanagariCharType(cp);
1456
+ const ct = tamilCharType(cp);
1228
1457
  if (ct === 4) continue;
1229
1458
  if (ct === 2 || ct === 3) {
1230
1459
  emitGlyph(resolveGid(cp), true, baseGid);
@@ -1245,596 +1474,455 @@ function shapeDevanagariText(str, fontData) {
1245
1474
  return shaped;
1246
1475
  }
1247
1476
 
1248
- // src/shaping/arabic-shaper.ts
1249
- function getJoiningType(cp) {
1250
- if (cp >= 1611 && cp <= 1631 || // Harakat (vowel marks)
1251
- cp === 1648 || // Superscript alef
1252
- cp >= 1750 && cp <= 1756 || cp >= 1759 && cp <= 1764 || cp >= 1767 && cp <= 1768 || cp >= 1770 && cp <= 1773 || cp >= 1552 && cp <= 1562) return "T";
1253
- if (cp === 1600) return "C";
1254
- if (cp >= 1574 && cp <= 1576 || // YEH HAMZA, BA series
1255
- cp >= 1578 && cp <= 1582 || // TA through KHA
1256
- cp >= 1587 && cp <= 1594 || // SEEN through GHAIN
1257
- cp >= 1601 && cp <= 1607 || // FA through HA
1258
- cp === 1609 || // ALEF MAKSURA
1259
- cp === 1610 || // YA
1260
- cp === 1656 || // HIGH HAMZA YEH
1261
- cp >= 1690 && cp <= 1727 || // Extended Arabic
1262
- cp >= 1729 && cp <= 1731 || cp >= 1740 && cp <= 1742 || cp >= 1744 && cp <= 1747 || cp === 1749 || cp === 1786 || cp === 1787 || cp === 1788) return "D";
1263
- if (cp === 1570 || cp === 1571 || cp === 1572 || cp === 1573 || cp === 1575 || // ALEF
1264
- cp === 1577 || // TEH MARBUTA
1265
- cp === 1583 || cp === 1584 || // DAL, THAL
1266
- cp === 1585 || cp === 1586 || // RA, ZAIN
1267
- cp === 1608 || // WAW
1268
- cp >= 1649 && cp <= 1651 || cp === 1653 || cp === 1654 || cp === 1655 || cp >= 1672 && cp <= 1689 || // Extended DAL/RA series
1269
- cp === 1728 || cp >= 1732 && cp <= 1739 || cp === 1743 || cp === 1774 || cp === 1775) return "R";
1270
- if (cp >= ARABIC_START && cp <= ARABIC_END) return "U";
1271
- return "U";
1477
+ // src/shaping/gpos-positioner.ts
1478
+ function getBaseAnchor(markAnchors, baseGid, markClass) {
1479
+ if (!markAnchors) return null;
1480
+ const base = markAnchors.bases[baseGid];
1481
+ if (!base) return null;
1482
+ return base[markClass] ?? null;
1272
1483
  }
1273
- var ARABIC_PRES_FORMS = /* @__PURE__ */ new Map([
1274
- [1569, { isol: 65152 }],
1275
- [1570, { isol: 65153, fina: 65154 }],
1276
- [1571, { isol: 65155, fina: 65156 }],
1277
- [1572, { isol: 65157, fina: 65158 }],
1278
- [1573, { isol: 65159, fina: 65160 }],
1279
- [1574, { isol: 65161, fina: 65162, init: 65163, medi: 65164 }],
1280
- [1575, { isol: 65165, fina: 65166 }],
1281
- [1576, { isol: 65167, fina: 65168, init: 65169, medi: 65170 }],
1282
- [1577, { isol: 65171, fina: 65172 }],
1283
- [1578, { isol: 65173, fina: 65174, init: 65175, medi: 65176 }],
1284
- [1579, { isol: 65177, fina: 65178, init: 65179, medi: 65180 }],
1285
- [1580, { isol: 65181, fina: 65182, init: 65183, medi: 65184 }],
1286
- [1581, { isol: 65185, fina: 65186, init: 65187, medi: 65188 }],
1287
- [1582, { isol: 65189, fina: 65190, init: 65191, medi: 65192 }],
1288
- [1583, { isol: 65193, fina: 65194 }],
1289
- [1584, { isol: 65195, fina: 65196 }],
1290
- [1585, { isol: 65197, fina: 65198 }],
1291
- [1586, { isol: 65199, fina: 65200 }],
1292
- [1587, { isol: 65201, fina: 65202, init: 65203, medi: 65204 }],
1293
- [1588, { isol: 65205, fina: 65206, init: 65207, medi: 65208 }],
1294
- [1589, { isol: 65209, fina: 65210, init: 65211, medi: 65212 }],
1295
- [1590, { isol: 65213, fina: 65214, init: 65215, medi: 65216 }],
1296
- [1591, { isol: 65217, fina: 65218, init: 65219, medi: 65220 }],
1297
- [1592, { isol: 65221, fina: 65222, init: 65223, medi: 65224 }],
1298
- [1593, { isol: 65225, fina: 65226, init: 65227, medi: 65228 }],
1299
- [1594, { isol: 65229, fina: 65230, init: 65231, medi: 65232 }],
1300
- [1601, { isol: 65233, fina: 65234, init: 65235, medi: 65236 }],
1301
- [1602, { isol: 65237, fina: 65238, init: 65239, medi: 65240 }],
1302
- [1603, { isol: 65241, fina: 65242, init: 65243, medi: 65244 }],
1303
- [1604, { isol: 65245, fina: 65246, init: 65247, medi: 65248 }],
1304
- [1605, { isol: 65249, fina: 65250, init: 65251, medi: 65252 }],
1305
- [1606, { isol: 65253, fina: 65254, init: 65255, medi: 65256 }],
1306
- [1607, { isol: 65257, fina: 65258, init: 65259, medi: 65260 }],
1307
- [1608, { isol: 65261, fina: 65262 }],
1308
- [1609, { isol: 65263, fina: 65264 }],
1309
- [1610, { isol: 65265, fina: 65266, init: 65267, medi: 65268 }]
1310
- ]);
1311
- var LAM_ALEF_PRES = /* @__PURE__ */ new Map([
1312
- [1570, [65269, 65270]],
1313
- // LAM + ALEF WITH MADDA ABOVE
1314
- [1571, [65271, 65272]],
1315
- // LAM + ALEF WITH HAMZA ABOVE
1316
- [1573, [65273, 65274]],
1317
- // LAM + ALEF WITH HAMZA BELOW
1318
- [1575, [65275, 65276]]
1319
- // LAM + ALEF
1320
- ]);
1321
- function resolvePositionalForms(codePoints) {
1322
- const len = codePoints.length;
1323
- const forms = new Array(len).fill("isol");
1324
- const joining = codePoints.map(getJoiningType);
1325
- for (let i = 0; i < len; i++) {
1326
- if (joining[i] === "T" || joining[i] === "U") continue;
1327
- let prevJoin = "U";
1328
- for (let j = i - 1; j >= 0; j--) {
1329
- if (joining[j] !== "T") {
1330
- prevJoin = joining[j];
1484
+ function getMarkAnchor(markAnchors, markGid) {
1485
+ if (!markAnchors) return null;
1486
+ const mark = markAnchors.marks[markGid];
1487
+ if (!mark) return null;
1488
+ return { classIdx: mark[0], x: mark[1], y: mark[2] };
1489
+ }
1490
+ function positionMarkOnBase(markAnchors, markGid, baseGid, baseAdv) {
1491
+ const mark = getMarkAnchor(markAnchors, markGid);
1492
+ if (!mark) return null;
1493
+ const base = getBaseAnchor(markAnchors, baseGid, mark.classIdx);
1494
+ if (!base) return null;
1495
+ return {
1496
+ dx: base[0] - mark.x - baseAdv,
1497
+ dy: base[1] - mark.y
1498
+ };
1499
+ }
1500
+
1501
+ // src/shaping/devanagari-shaper.ts
1502
+ var HALANT2 = 2381;
1503
+ var NUKTA2 = 2364;
1504
+ var RA2 = 2352;
1505
+ function devanagariCharType(cp) {
1506
+ if (cp === HALANT2) return 7;
1507
+ if (cp === NUKTA2) return 8;
1508
+ if (cp >= 2305 && cp <= 2307) return 6;
1509
+ if (cp >= 2308 && cp <= 2324) return 1;
1510
+ if (cp >= 2325 && cp <= 2361) return 0;
1511
+ if (cp === 2367) return 4;
1512
+ if (cp >= 2369 && cp <= 2372) return 3;
1513
+ if (cp === 2366 || cp === 2368) return 5;
1514
+ if (cp === 2375 || cp === 2376) return 2;
1515
+ if (cp === 2379 || cp === 2380) return 4;
1516
+ if (cp >= 2373 && cp <= 2380) return 2;
1517
+ if (cp >= 2406 && cp <= 2415) return 9;
1518
+ if (cp >= 2392 && cp <= 2399) return 0;
1519
+ if (cp === 2365) return 1;
1520
+ if (cp === 2384) return 1;
1521
+ return -1;
1522
+ }
1523
+ function isConsonant3(cp) {
1524
+ return devanagariCharType(cp) === 0;
1525
+ }
1526
+ function buildDevanagariClusters(str) {
1527
+ const clusters = [];
1528
+ const cps = [];
1529
+ for (let i2 = 0; i2 < str.length; ) {
1530
+ const cp = str.codePointAt(i2) ?? 0;
1531
+ cps.push(cp);
1532
+ i2 += cp > 65535 ? 2 : 1;
1533
+ }
1534
+ let i = 0;
1535
+ while (i < cps.length) {
1536
+ const cp = cps[i];
1537
+ const type = devanagariCharType(cp);
1538
+ if (type < 0 || cp < DEVANAGARI_START || cp > DEVANAGARI_END) {
1539
+ clusters.push({ codepoints: [cp], baseIndex: 0, hasReph: false, preBaseMatras: [] });
1540
+ i++;
1541
+ continue;
1542
+ }
1543
+ const syllable = [];
1544
+ let hasReph = false;
1545
+ const preMatras = [];
1546
+ if (isConsonant3(cp) && cp === RA2 && i + 2 < cps.length && cps[i + 1] === HALANT2 && isConsonant3(cps[i + 2])) {
1547
+ hasReph = true;
1548
+ syllable.push(cp, cps[i + 1]);
1549
+ i += 2;
1550
+ }
1551
+ let lastConsonantIdx = -1;
1552
+ while (i < cps.length) {
1553
+ const cc = cps[i];
1554
+ const ct = devanagariCharType(cc);
1555
+ if (ct === 0) {
1556
+ lastConsonantIdx = syllable.length;
1557
+ syllable.push(cc);
1558
+ i++;
1559
+ if (i < cps.length && cps[i] === NUKTA2) {
1560
+ syllable.push(cps[i]);
1561
+ i++;
1562
+ }
1563
+ if (i < cps.length && cps[i] === HALANT2) {
1564
+ if (i + 1 < cps.length && isConsonant3(cps[i + 1])) {
1565
+ syllable.push(cps[i]);
1566
+ i++;
1567
+ continue;
1568
+ } else {
1569
+ syllable.push(cps[i]);
1570
+ i++;
1571
+ break;
1572
+ }
1573
+ }
1331
1574
  break;
1332
- }
1333
- }
1334
- let nextJoin = "U";
1335
- for (let j = i + 1; j < len; j++) {
1336
- if (joining[j] !== "T") {
1337
- nextJoin = joining[j];
1575
+ } else {
1338
1576
  break;
1339
1577
  }
1340
1578
  }
1341
- const joinsToPrev = prevJoin === "D" || prevJoin === "C";
1342
- const joinsToNext = (nextJoin === "D" || nextJoin === "R" || nextJoin === "C") && (joining[i] === "D" || joining[i] === "C");
1343
- if (joinsToPrev && joinsToNext) {
1344
- forms[i] = "medi";
1345
- } else if (joinsToPrev) {
1346
- forms[i] = "fina";
1347
- } else if (joinsToNext) {
1348
- forms[i] = "init";
1349
- } else {
1350
- forms[i] = "isol";
1351
- }
1352
- }
1353
- return forms;
1354
- }
1355
- var LAM = 1604;
1356
- var ALEF_VARIANTS = /* @__PURE__ */ new Set([1570, 1571, 1573, 1575]);
1357
- function isLamAlef(cp1, cp2) {
1358
- return cp1 === LAM && ALEF_VARIANTS.has(cp2);
1359
- }
1360
- function shapeArabicText(str, fontData) {
1361
- if (!str) return [];
1362
- const codePoints = [];
1363
- for (let i = 0; i < str.length; ) {
1364
- const cp = str.codePointAt(i) ?? 0;
1365
- codePoints.push(cp);
1366
- i += cp > 65535 ? 2 : 1;
1367
- }
1368
- const forms = resolvePositionalForms(codePoints);
1369
- const glyphs = [];
1370
- const cmap = fontData.cmap;
1371
- const widths = fontData.widths;
1372
- const defaultWidth = fontData.defaultWidth;
1373
- const markAnchors = fontData.markAnchors;
1374
- let lastBaseGid = 0;
1375
- for (let i = 0; i < codePoints.length; i++) {
1376
- const cp = codePoints[i];
1377
- if (i < codePoints.length - 1 && isLamAlef(cp, codePoints[i + 1])) {
1378
- const ligForms = LAM_ALEF_PRES.get(codePoints[i + 1]);
1379
- if (ligForms) {
1380
- const isFinal = forms[i] === "medi" || forms[i] === "fina";
1381
- const ligCP = isFinal ? ligForms[1] : ligForms[0];
1382
- const ligGid = cmap[ligCP];
1383
- if (ligGid) {
1384
- glyphs.push({ gid: ligGid, dx: 0, dy: 0, isZeroAdvance: false });
1385
- lastBaseGid = ligGid;
1386
- i++;
1387
- continue;
1579
+ const baseIdx = lastConsonantIdx >= 0 ? lastConsonantIdx : 0;
1580
+ while (i < cps.length) {
1581
+ const ct = devanagariCharType(cps[i]);
1582
+ if (ct >= 2 && ct <= 5) {
1583
+ if (ct === 4) {
1584
+ preMatras.push(syllable.length);
1388
1585
  }
1586
+ syllable.push(cps[i]);
1587
+ i++;
1588
+ } else {
1589
+ break;
1389
1590
  }
1390
1591
  }
1391
- let gid = cmap[cp] ?? 0;
1392
- const presForm = ARABIC_PRES_FORMS.get(cp);
1393
- if (presForm) {
1394
- const form = forms[i];
1395
- let presCP;
1396
- if (form === "init") presCP = presForm.init;
1397
- else if (form === "medi") presCP = presForm.medi;
1398
- else if (form === "fina") presCP = presForm.fina;
1399
- else presCP = presForm.isol;
1400
- if (presCP) {
1401
- const presGid = cmap[presCP];
1402
- if (presGid) gid = presGid;
1403
- }
1592
+ while (i < cps.length && devanagariCharType(cps[i]) === 6) {
1593
+ syllable.push(cps[i]);
1594
+ i++;
1404
1595
  }
1405
- const joining = getJoiningType(cp);
1406
- const isZeroAdvance = joining === "T";
1407
- if (isZeroAdvance && lastBaseGid !== 0) {
1408
- const baseAdv = widths[lastBaseGid] !== void 0 ? widths[lastBaseGid] : defaultWidth;
1409
- const offset = positionMarkOnBase(markAnchors, gid, lastBaseGid, baseAdv);
1410
- if (offset) {
1411
- glyphs.push({ gid, dx: offset.dx, dy: offset.dy, isZeroAdvance: true });
1412
- continue;
1413
- }
1596
+ if (syllable.length === 0) {
1597
+ syllable.push(cps[i] ?? 32);
1598
+ i++;
1414
1599
  }
1415
- glyphs.push({ gid, dx: 0, dy: 0, isZeroAdvance });
1416
- if (!isZeroAdvance) lastBaseGid = gid;
1417
- }
1418
- return glyphs;
1419
- }
1420
-
1421
- // src/shaping/bidi.ts
1422
- function classifyBidiType(cp) {
1423
- if (cp >= 768 && cp <= 879 || // Combining Diacritical Marks
1424
- cp >= 1425 && cp <= 1469 || // Hebrew marks
1425
- cp >= 1471 && cp <= 1471 || cp >= 1473 && cp <= 1474 || cp >= 1476 && cp <= 1477 || cp >= 1479 && cp <= 1479 || cp >= 1552 && cp <= 1562 || // Arabic marks
1426
- cp >= 1611 && cp <= 1631 || // Arabic harakat
1427
- cp >= 1648 && cp <= 1648 || // Arabic superscript alef
1428
- cp >= 1750 && cp <= 1756 || cp >= 1759 && cp <= 1764 || cp >= 1767 && cp <= 1768 || cp >= 1770 && cp <= 1773 || cp >= 65056 && cp <= 65071) return "NSM";
1429
- if (cp === 8203 || cp === 8204 || cp === 8205 || cp === 8206 || cp === 8207 || cp === 65279 || cp === 8294 || cp === 8295 || cp === 8296 || cp === 8297) return "BN";
1430
- if (cp >= 1632 && cp <= 1641) return "AN";
1431
- if (cp >= 1776 && cp <= 1785) return "AN";
1432
- if (cp >= 48 && cp <= 57) return "EN";
1433
- if (cp === 43 || cp === 45) return "ES";
1434
- if (cp === 35 || cp === 36 || cp === 37 || cp === 162 || cp === 163 || cp === 164 || cp === 165 || cp === 8364 || cp === 8377 || cp === 8378) return "ET";
1435
- if (cp === 44 || cp === 46 || cp === 47 || cp === 58 || cp === 160) return "CS";
1436
- if (cp === 32 || cp === 9 || cp === 10 || cp === 13 || cp === 12 || cp === 8192 || cp === 8202 || cp === 8232 || cp === 8233 || cp === 8239 || cp === 8287 || cp === 12288) return "WS";
1437
- if (cp >= 1536 && cp <= 1791) return "AL";
1438
- if (cp >= 1872 && cp <= 1919) return "AL";
1439
- if (cp >= 2208 && cp <= 2303) return "AL";
1440
- if (cp >= 64336 && cp <= 65023) return "AL";
1441
- if (cp >= 65136 && cp <= 65278) return "AL";
1442
- if (cp >= 1488 && cp <= 1514) return "R";
1443
- if (cp >= 1520 && cp <= 1524) return "R";
1444
- if (cp >= 1792 && cp <= 1871) return "R";
1445
- if (cp >= 1920 && cp <= 1983) return "R";
1446
- if (cp >= 64285 && cp <= 64335) return "R";
1447
- if (cp >= 65 && cp <= 90) return "L";
1448
- if (cp >= 97 && cp <= 122) return "L";
1449
- if (cp >= 192 && cp <= 591) return "L";
1450
- if (cp >= 880 && cp <= 1023) return "L";
1451
- if (cp >= 1024 && cp <= 1279) return "L";
1452
- if (cp >= 3584 && cp <= 3711) return "L";
1453
- if (cp >= 2304 && cp <= 2431) return "L";
1454
- if (cp >= 12352 && cp <= 12543) return "L";
1455
- if (cp >= 19968 && cp <= 40959) return "L";
1456
- if (cp >= 44032 && cp <= 55215) return "L";
1457
- if (cp >= 33 && cp <= 47) return "ON";
1458
- if (cp >= 58 && cp <= 64) return "ON";
1459
- if (cp >= 91 && cp <= 96) return "ON";
1460
- if (cp >= 123 && cp <= 126) return "ON";
1461
- if (cp >= 161 && cp <= 191) return "ON";
1462
- if (cp >= 8208 && cp <= 8231) return "ON";
1463
- if (cp >= 8240 && cp <= 8286) return "ON";
1464
- return "L";
1465
- }
1466
- function detectParagraphLevel(types) {
1467
- for (const t of types) {
1468
- if (t === "L") return 0;
1469
- if (t === "R" || t === "AL") return 1;
1600
+ clusters.push({
1601
+ codepoints: syllable,
1602
+ baseIndex: hasReph ? baseIdx + 2 : baseIdx,
1603
+ hasReph,
1604
+ preBaseMatras: preMatras
1605
+ });
1470
1606
  }
1471
- return 0;
1607
+ return clusters;
1472
1608
  }
1473
- function resolveWeakTypes(types, paraLevel) {
1474
- const len = types.length;
1475
- let prevType = paraLevel === 0 ? "L" : "R";
1476
- for (let i = 0; i < len; i++) {
1477
- if (types[i] === "BN") continue;
1478
- if (types[i] === "NSM") {
1479
- types[i] = prevType;
1480
- }
1481
- prevType = types[i];
1609
+ function shapeDevanagariText(str, fontData) {
1610
+ const { cmap, gsub, ligatures, markAnchors, widths, defaultWidth } = fontData;
1611
+ const shaped = [];
1612
+ function resolveGid(cp) {
1613
+ const normCp = cp === 8239 || cp === 160 ? 32 : cp;
1614
+ return cmap[normCp] || 0;
1482
1615
  }
1483
- let lastStrong = paraLevel === 0 ? "L" : "R";
1484
- for (let i = 0; i < len; i++) {
1485
- if (types[i] === "BN") continue;
1486
- if (types[i] === "R" || types[i] === "L" || types[i] === "AL") {
1487
- lastStrong = types[i];
1488
- } else if (types[i] === "EN" && lastStrong === "AL") {
1489
- types[i] = "AN";
1490
- }
1616
+ function tryLig(gids) {
1617
+ return tryLigature(gids, ligatures);
1491
1618
  }
1492
- for (let i = 0; i < len; i++) {
1493
- if (types[i] === "AL") types[i] = "R";
1619
+ function getAdv(gid) {
1620
+ return widths[gid] !== void 0 ? widths[gid] : defaultWidth;
1494
1621
  }
1495
- for (let i = 1; i < len - 1; i++) {
1496
- if (types[i] === "BN") continue;
1497
- if (types[i] === "ES" && types[i - 1] === "EN" && types[i + 1] === "EN") {
1498
- types[i] = "EN";
1499
- } else if (types[i] === "CS") {
1500
- if (types[i - 1] === "EN" && types[i + 1] === "EN") types[i] = "EN";
1501
- else if (types[i - 1] === "AN" && types[i + 1] === "AN") types[i] = "AN";
1622
+ function emitGlyph(gid, isZero, baseGid) {
1623
+ if (isZero && baseGid !== void 0) {
1624
+ const markAnchor = getMarkAnchor(markAnchors, gid);
1625
+ if (markAnchor) {
1626
+ const baseAnchorPt = getBaseAnchor(markAnchors, baseGid, markAnchor.classIdx);
1627
+ if (baseAnchorPt) {
1628
+ const baseAdv = getAdv(baseGid);
1629
+ shaped.push({
1630
+ gid,
1631
+ dx: baseAnchorPt[0] - markAnchor.x - baseAdv,
1632
+ dy: baseAnchorPt[1] - markAnchor.y,
1633
+ isZeroAdvance: true
1634
+ });
1635
+ return;
1636
+ }
1637
+ }
1638
+ shaped.push({ gid, dx: 0, dy: 0, isZeroAdvance: true });
1639
+ } else {
1640
+ shaped.push({ gid, dx: 0, dy: 0, isZeroAdvance: false });
1502
1641
  }
1503
1642
  }
1504
- for (let i = 0; i < len; i++) {
1505
- if (types[i] === "ET") {
1506
- let found = false;
1507
- for (let j = i - 1; j >= 0; j--) {
1508
- if (types[j] === "EN") {
1509
- found = true;
1510
- break;
1511
- }
1512
- if (types[j] !== "ET" && types[j] !== "BN") break;
1643
+ const clusters = buildDevanagariClusters(str);
1644
+ for (const cluster of clusters) {
1645
+ const { codepoints, hasReph, preBaseMatras } = cluster;
1646
+ const baseStart = hasReph ? 2 : 0;
1647
+ let baseGid = 0;
1648
+ for (let ci = baseStart; ci < codepoints.length; ci++) {
1649
+ const ct = devanagariCharType(codepoints[ci]);
1650
+ if (ct === 0) {
1651
+ baseGid = resolveGid(codepoints[ci]);
1652
+ } else if (ct >= 2) {
1653
+ break;
1513
1654
  }
1514
- if (!found) {
1515
- for (let j = i + 1; j < len; j++) {
1516
- if (types[j] === "EN") {
1517
- found = true;
1518
- break;
1519
- }
1520
- if (types[j] !== "ET" && types[j] !== "BN") break;
1655
+ }
1656
+ const splitPostComponents = [];
1657
+ for (const mIdx of preBaseMatras) {
1658
+ if (mIdx < codepoints.length) {
1659
+ const mCp = codepoints[mIdx];
1660
+ if (mCp === 2379) {
1661
+ emitGlyph(resolveGid(2375), false);
1662
+ splitPostComponents.push(2366);
1663
+ } else if (mCp === 2380) {
1664
+ emitGlyph(resolveGid(2375), false);
1665
+ splitPostComponents.push(2380);
1666
+ } else {
1667
+ emitGlyph(resolveGid(mCp), false);
1521
1668
  }
1522
1669
  }
1523
- if (found) types[i] = "EN";
1524
- }
1525
- }
1526
- for (let i = 0; i < len; i++) {
1527
- if (types[i] === "ES" || types[i] === "ET" || types[i] === "CS") {
1528
- types[i] = "ON";
1529
- }
1530
- }
1531
- lastStrong = paraLevel === 0 ? "L" : "R";
1532
- for (let i = 0; i < len; i++) {
1533
- if (types[i] === "BN") continue;
1534
- if (types[i] === "L" || types[i] === "R") {
1535
- lastStrong = types[i];
1536
- } else if (types[i] === "EN" && lastStrong === "L") {
1537
- types[i] = "L";
1538
1670
  }
1539
- }
1540
- }
1541
- function resolveNeutralTypes(types, paraLevel) {
1542
- const len = types.length;
1543
- const paraDir = paraLevel === 0 ? "L" : "R";
1544
- for (let i = 0; i < len; i++) {
1545
- if (types[i] !== "ON" && types[i] !== "WS" && types[i] !== "BN") continue;
1546
- const start = i;
1547
- while (i < len && (types[i] === "ON" || types[i] === "WS" || types[i] === "BN")) i++;
1548
- const end = i;
1549
- let prevStrong = paraDir;
1550
- for (let j = start - 1; j >= 0; j--) {
1551
- if (types[j] === "L" || types[j] === "R" || types[j] === "EN" || types[j] === "AN") {
1552
- prevStrong = types[j] === "EN" || types[j] === "AN" ? "R" : types[j];
1553
- break;
1671
+ if (hasReph) {
1672
+ const raGid = resolveGid(RA2);
1673
+ const halantGid = resolveGid(HALANT2);
1674
+ const rephLig = tryLig([raGid, halantGid]);
1675
+ if (rephLig) {
1676
+ emitGlyph(rephLig.resultGid, true, baseGid);
1677
+ } else {
1678
+ const raGsubbed = gsub[raGid] !== void 0 ? gsub[raGid] : raGid;
1679
+ emitGlyph(raGsubbed, true, baseGid);
1554
1680
  }
1555
1681
  }
1556
- let nextStrong = paraDir;
1557
- for (let j = end; j < len; j++) {
1558
- if (types[j] === "L" || types[j] === "R" || types[j] === "EN" || types[j] === "AN") {
1559
- nextStrong = types[j] === "EN" || types[j] === "AN" ? "R" : types[j];
1682
+ const clusterGids = [];
1683
+ const clusterEndIdx = [];
1684
+ let matraStart = codepoints.length;
1685
+ for (let ci = baseStart; ci < codepoints.length; ci++) {
1686
+ const ct = devanagariCharType(codepoints[ci]);
1687
+ if (ct === 0 || ct === 7 || ct === 8) {
1688
+ clusterGids.push(resolveGid(codepoints[ci]));
1689
+ clusterEndIdx.push(ci);
1690
+ } else if (ct < 0 || ct === 1 || ct === 9) {
1691
+ emitGlyph(resolveGid(codepoints[ci]), false);
1692
+ } else {
1693
+ matraStart = ci;
1560
1694
  break;
1561
1695
  }
1562
1696
  }
1563
- const resolved = prevStrong === nextStrong ? prevStrong : paraDir;
1564
- for (let j = start; j < end; j++) {
1565
- types[j] = resolved;
1566
- }
1567
- }
1568
- }
1569
- function assignLevels(types, paraLevel) {
1570
- const levels = [];
1571
- for (const t of types) {
1572
- if (paraLevel === 0) {
1573
- levels.push(t === "R" || t === "AN" ? 1 : 0);
1697
+ const ligResult = tryLig(clusterGids);
1698
+ if (ligResult) {
1699
+ emitGlyph(ligResult.resultGid, false);
1700
+ baseGid = ligResult.resultGid;
1701
+ let gi = ligResult.consumed;
1702
+ while (gi < clusterGids.length) {
1703
+ const subSeq = clusterGids.slice(gi);
1704
+ const subLig = tryLig(subSeq);
1705
+ if (subLig) {
1706
+ emitGlyph(subLig.resultGid, false);
1707
+ gi += subLig.consumed;
1708
+ } else {
1709
+ const origCi = clusterEndIdx[gi];
1710
+ const ct = devanagariCharType(codepoints[origCi]);
1711
+ if (ct === 7) {
1712
+ emitGlyph(clusterGids[gi], true, baseGid);
1713
+ } else {
1714
+ emitGlyph(clusterGids[gi], false);
1715
+ }
1716
+ gi++;
1717
+ }
1718
+ }
1574
1719
  } else {
1575
- levels.push(t === "L" ? 2 : 1);
1720
+ for (let ci = baseStart; ci < matraStart; ci++) {
1721
+ const cp = codepoints[ci];
1722
+ const ct = devanagariCharType(cp);
1723
+ if (ct === 0) {
1724
+ emitGlyph(resolveGid(cp), false);
1725
+ } else if (ct === 7) {
1726
+ emitGlyph(resolveGid(cp), true, baseGid);
1727
+ } else if (ct === 8) {
1728
+ emitGlyph(resolveGid(cp), true, baseGid);
1729
+ }
1730
+ }
1576
1731
  }
1577
- }
1578
- return levels;
1579
- }
1580
- var SENTENCE_PUNCT = /* @__PURE__ */ new Set([
1581
- 46,
1582
- // .
1583
- 44,
1584
- // ,
1585
- 59,
1586
- // ;
1587
- 58,
1588
- // :
1589
- 33,
1590
- // !
1591
- 63
1592
- // ?
1593
- ]);
1594
- function fixPunctuationAffinity(types, codePoints, len) {
1595
- for (let i = 1; i < len; i++) {
1596
- if (types[i] === "R" && SENTENCE_PUNCT.has(codePoints[i])) {
1597
- let prevIdx = i - 1;
1598
- while (prevIdx >= 0 && (types[prevIdx] === "WS" || types[prevIdx] === "BN")) prevIdx--;
1599
- if (prevIdx >= 0 && types[prevIdx] === "L") {
1600
- types[i] = "L";
1732
+ for (let ci = matraStart; ci < codepoints.length; ci++) {
1733
+ const cp = codepoints[ci];
1734
+ const ct = devanagariCharType(cp);
1735
+ if (ct === 4) continue;
1736
+ if (ct === 2 || ct === 3) {
1737
+ emitGlyph(resolveGid(cp), true, baseGid);
1738
+ } else if (ct === 5) {
1739
+ emitGlyph(resolveGid(cp), false);
1740
+ } else if (ct === 6) {
1741
+ emitGlyph(resolveGid(cp), true, baseGid);
1742
+ } else if (ct === 9) {
1743
+ emitGlyph(resolveGid(cp), false);
1744
+ } else {
1745
+ emitGlyph(resolveGid(cp), false);
1601
1746
  }
1602
1747
  }
1748
+ for (const postCp of splitPostComponents) {
1749
+ emitGlyph(resolveGid(postCp), false);
1750
+ }
1603
1751
  }
1752
+ return shaped;
1604
1753
  }
1605
- function fixBracketPairing(types, codePoints, len) {
1606
- const OPEN_BRACKETS = {
1607
- 40: 41,
1608
- // ( )
1609
- 91: 93,
1610
- // [ ]
1611
- 123: 125
1612
- // { }
1613
- };
1754
+
1755
+ // src/shaping/arabic-shaper.ts
1756
+ function getJoiningType(cp) {
1757
+ if (cp >= 1611 && cp <= 1631 || // Harakat (vowel marks)
1758
+ cp === 1648 || // Superscript alef
1759
+ cp >= 1750 && cp <= 1756 || cp >= 1759 && cp <= 1764 || cp >= 1767 && cp <= 1768 || cp >= 1770 && cp <= 1773 || cp >= 1552 && cp <= 1562) return "T";
1760
+ if (cp === 1600) return "C";
1761
+ if (cp >= 1574 && cp <= 1576 || // YEH HAMZA, BA series
1762
+ cp >= 1578 && cp <= 1582 || // TA through KHA
1763
+ cp >= 1587 && cp <= 1594 || // SEEN through GHAIN
1764
+ cp >= 1601 && cp <= 1607 || // FA through HA
1765
+ cp === 1609 || // ALEF MAKSURA
1766
+ cp === 1610 || // YA
1767
+ cp === 1656 || // HIGH HAMZA YEH
1768
+ cp >= 1690 && cp <= 1727 || // Extended Arabic
1769
+ cp >= 1729 && cp <= 1731 || cp >= 1740 && cp <= 1742 || cp >= 1744 && cp <= 1747 || cp === 1749 || cp === 1786 || cp === 1787 || cp === 1788) return "D";
1770
+ if (cp === 1570 || cp === 1571 || cp === 1572 || cp === 1573 || cp === 1575 || // ALEF
1771
+ cp === 1577 || // TEH MARBUTA
1772
+ cp === 1583 || cp === 1584 || // DAL, THAL
1773
+ cp === 1585 || cp === 1586 || // RA, ZAIN
1774
+ cp === 1608 || // WAW
1775
+ cp >= 1649 && cp <= 1651 || cp === 1653 || cp === 1654 || cp === 1655 || cp >= 1672 && cp <= 1689 || // Extended DAL/RA series
1776
+ cp === 1728 || cp >= 1732 && cp <= 1739 || cp === 1743 || cp === 1774 || cp === 1775) return "R";
1777
+ if (cp >= ARABIC_START && cp <= ARABIC_END) return "U";
1778
+ return "U";
1779
+ }
1780
+ var ARABIC_PRES_FORMS = /* @__PURE__ */ new Map([
1781
+ [1569, { isol: 65152 }],
1782
+ [1570, { isol: 65153, fina: 65154 }],
1783
+ [1571, { isol: 65155, fina: 65156 }],
1784
+ [1572, { isol: 65157, fina: 65158 }],
1785
+ [1573, { isol: 65159, fina: 65160 }],
1786
+ [1574, { isol: 65161, fina: 65162, init: 65163, medi: 65164 }],
1787
+ [1575, { isol: 65165, fina: 65166 }],
1788
+ [1576, { isol: 65167, fina: 65168, init: 65169, medi: 65170 }],
1789
+ [1577, { isol: 65171, fina: 65172 }],
1790
+ [1578, { isol: 65173, fina: 65174, init: 65175, medi: 65176 }],
1791
+ [1579, { isol: 65177, fina: 65178, init: 65179, medi: 65180 }],
1792
+ [1580, { isol: 65181, fina: 65182, init: 65183, medi: 65184 }],
1793
+ [1581, { isol: 65185, fina: 65186, init: 65187, medi: 65188 }],
1794
+ [1582, { isol: 65189, fina: 65190, init: 65191, medi: 65192 }],
1795
+ [1583, { isol: 65193, fina: 65194 }],
1796
+ [1584, { isol: 65195, fina: 65196 }],
1797
+ [1585, { isol: 65197, fina: 65198 }],
1798
+ [1586, { isol: 65199, fina: 65200 }],
1799
+ [1587, { isol: 65201, fina: 65202, init: 65203, medi: 65204 }],
1800
+ [1588, { isol: 65205, fina: 65206, init: 65207, medi: 65208 }],
1801
+ [1589, { isol: 65209, fina: 65210, init: 65211, medi: 65212 }],
1802
+ [1590, { isol: 65213, fina: 65214, init: 65215, medi: 65216 }],
1803
+ [1591, { isol: 65217, fina: 65218, init: 65219, medi: 65220 }],
1804
+ [1592, { isol: 65221, fina: 65222, init: 65223, medi: 65224 }],
1805
+ [1593, { isol: 65225, fina: 65226, init: 65227, medi: 65228 }],
1806
+ [1594, { isol: 65229, fina: 65230, init: 65231, medi: 65232 }],
1807
+ [1601, { isol: 65233, fina: 65234, init: 65235, medi: 65236 }],
1808
+ [1602, { isol: 65237, fina: 65238, init: 65239, medi: 65240 }],
1809
+ [1603, { isol: 65241, fina: 65242, init: 65243, medi: 65244 }],
1810
+ [1604, { isol: 65245, fina: 65246, init: 65247, medi: 65248 }],
1811
+ [1605, { isol: 65249, fina: 65250, init: 65251, medi: 65252 }],
1812
+ [1606, { isol: 65253, fina: 65254, init: 65255, medi: 65256 }],
1813
+ [1607, { isol: 65257, fina: 65258, init: 65259, medi: 65260 }],
1814
+ [1608, { isol: 65261, fina: 65262 }],
1815
+ [1609, { isol: 65263, fina: 65264 }],
1816
+ [1610, { isol: 65265, fina: 65266, init: 65267, medi: 65268 }]
1817
+ ]);
1818
+ var LAM_ALEF_PRES = /* @__PURE__ */ new Map([
1819
+ [1570, [65269, 65270]],
1820
+ // LAM + ALEF WITH MADDA ABOVE
1821
+ [1571, [65271, 65272]],
1822
+ // LAM + ALEF WITH HAMZA ABOVE
1823
+ [1573, [65273, 65274]],
1824
+ // LAM + ALEF WITH HAMZA BELOW
1825
+ [1575, [65275, 65276]]
1826
+ // LAM + ALEF
1827
+ ]);
1828
+ function resolvePositionalForms(codePoints) {
1829
+ const len = codePoints.length;
1830
+ const forms = new Array(len).fill("isol");
1831
+ const joining = codePoints.map(getJoiningType);
1614
1832
  for (let i = 0; i < len; i++) {
1615
- const closer = OPEN_BRACKETS[codePoints[i]];
1616
- if (closer === void 0) continue;
1617
- let depth = 1;
1618
- let closeIdx = -1;
1619
- for (let j = i + 1; j < len; j++) {
1620
- if (codePoints[j] === codePoints[i]) depth++;
1621
- else if (codePoints[j] === closer) {
1622
- depth--;
1623
- if (depth === 0) {
1624
- closeIdx = j;
1625
- break;
1626
- }
1627
- }
1628
- }
1629
- if (closeIdx === -1) continue;
1630
- let hasL = false;
1631
- for (let j = i + 1; j < closeIdx; j++) {
1632
- if (types[j] === "L") {
1633
- hasL = true;
1833
+ if (joining[i] === "T" || joining[i] === "U") continue;
1834
+ let prevJoin = "U";
1835
+ for (let j = i - 1; j >= 0; j--) {
1836
+ if (joining[j] !== "T") {
1837
+ prevJoin = joining[j];
1634
1838
  break;
1635
1839
  }
1636
1840
  }
1637
- if (hasL) {
1638
- types[i] = "L";
1639
- types[closeIdx] = "L";
1640
- }
1641
- }
1642
- }
1643
- function findOutermostIsolatePairs(codePoints) {
1644
- const pairs = [];
1645
- let i = 0;
1646
- while (i < codePoints.length) {
1647
- const cp = codePoints[i];
1648
- if (cp === 8294 || cp === 8295 || cp === 8296) {
1649
- let depth = 1;
1650
- let close = -1;
1651
- for (let j = i + 1; j < codePoints.length; j++) {
1652
- const cj = codePoints[j];
1653
- if (cj === 8294 || cj === 8295 || cj === 8296) depth++;
1654
- else if (cj === 8297) {
1655
- depth--;
1656
- if (depth === 0) {
1657
- close = j;
1658
- break;
1659
- }
1660
- }
1661
- }
1662
- if (close === -1) {
1663
- i++;
1664
- continue;
1841
+ let nextJoin = "U";
1842
+ for (let j = i + 1; j < len; j++) {
1843
+ if (joining[j] !== "T") {
1844
+ nextJoin = joining[j];
1845
+ break;
1665
1846
  }
1666
- const kind = cp === 8294 ? "LRI" : cp === 8295 ? "RLI" : "FSI";
1667
- pairs.push({ open: i, close, kind });
1668
- i = close + 1;
1847
+ }
1848
+ const joinsToPrev = prevJoin === "D" || prevJoin === "C";
1849
+ const joinsToNext = (nextJoin === "D" || nextJoin === "R" || nextJoin === "C") && (joining[i] === "D" || joining[i] === "C");
1850
+ if (joinsToPrev && joinsToNext) {
1851
+ forms[i] = "medi";
1852
+ } else if (joinsToPrev) {
1853
+ forms[i] = "fina";
1854
+ } else if (joinsToNext) {
1855
+ forms[i] = "init";
1669
1856
  } else {
1670
- i++;
1857
+ forms[i] = "isol";
1671
1858
  }
1672
1859
  }
1673
- return pairs;
1860
+ return forms;
1674
1861
  }
1675
- function resolveBidiRuns(text) {
1676
- if (!text) return [];
1677
- const codePoints = [];
1678
- const cpToStr = [];
1679
- for (let i = 0; i < text.length; ) {
1680
- cpToStr.push(i);
1681
- const cp = text.codePointAt(i) ?? 0;
1682
- codePoints.push(cp);
1683
- i += cp > 65535 ? 2 : 1;
1684
- }
1685
- cpToStr.push(text.length);
1686
- const isolates = findOutermostIsolatePairs(codePoints);
1687
- if (isolates.length === 0) {
1688
- return resolveBidiCore(text, codePoints, cpToStr);
1689
- }
1690
- const insideIsolate = new Array(codePoints.length).fill(false);
1691
- for (const p of isolates) {
1692
- for (let k = p.open; k <= p.close; k++) insideIsolate[k] = true;
1693
- }
1694
- const outerTypes = codePoints.map((cp, idx) => insideIsolate[idx] ? "BN" : classifyBidiType(cp));
1695
- const parentLevel = detectParagraphLevel(outerTypes);
1696
- const out = [];
1697
- const emitSegment = (cpStart, cpEnd, forced) => {
1698
- if (cpStart >= cpEnd) return;
1699
- const segText = text.substring(cpToStr[cpStart], cpToStr[cpEnd]);
1700
- const segCps = codePoints.slice(cpStart, cpEnd);
1701
- const baseStrIdx = cpToStr[cpStart];
1702
- const segCpToStr = cpToStr.slice(cpStart, cpEnd + 1).map((x) => x - baseStrIdx);
1703
- const segRuns = forced === void 0 ? resolveBidiRuns(segText) : resolveBidiCore(segText, segCps, segCpToStr, forced);
1704
- for (const r of segRuns) {
1705
- out.push({ text: r.text, level: r.level, start: r.start + baseStrIdx });
1706
- }
1707
- };
1708
- let cursor = 0;
1709
- for (const pair of isolates) {
1710
- emitSegment(cursor, pair.open, parentLevel);
1711
- const innerStart = pair.open + 1;
1712
- const innerEnd = pair.close;
1713
- let innerLevel;
1714
- if (pair.kind === "LRI") innerLevel = 0;
1715
- else if (pair.kind === "RLI") innerLevel = 1;
1716
- else {
1717
- const innerTypes = codePoints.slice(innerStart, innerEnd).map(classifyBidiType);
1718
- innerLevel = detectParagraphLevel(innerTypes);
1719
- }
1720
- if (innerStart < innerEnd) {
1721
- const innerText = text.substring(cpToStr[innerStart], cpToStr[innerEnd]);
1722
- const innerRuns = resolveBidiRunsForced(innerText, innerLevel);
1723
- const baseStrIdx = cpToStr[innerStart];
1724
- for (const r of innerRuns) {
1725
- out.push({ text: r.text, level: r.level, start: r.start + baseStrIdx });
1726
- }
1727
- }
1728
- cursor = pair.close + 1;
1729
- }
1730
- emitSegment(cursor, codePoints.length, parentLevel);
1731
- return out;
1862
+ var LAM = 1604;
1863
+ var ALEF_VARIANTS = /* @__PURE__ */ new Set([1570, 1571, 1573, 1575]);
1864
+ function isLamAlef(cp1, cp2) {
1865
+ return cp1 === LAM && ALEF_VARIANTS.has(cp2);
1732
1866
  }
1733
- function resolveBidiRunsForced(text, forcedLevel) {
1734
- if (!text) return [];
1867
+ function shapeArabicText(str, fontData) {
1868
+ if (!str) return [];
1735
1869
  const codePoints = [];
1736
- const cpToStr = [];
1737
- for (let i = 0; i < text.length; ) {
1738
- cpToStr.push(i);
1739
- const cp = text.codePointAt(i) ?? 0;
1870
+ for (let i = 0; i < str.length; ) {
1871
+ const cp = str.codePointAt(i) ?? 0;
1740
1872
  codePoints.push(cp);
1741
1873
  i += cp > 65535 ? 2 : 1;
1742
1874
  }
1743
- cpToStr.push(text.length);
1744
- const isolates = findOutermostIsolatePairs(codePoints);
1745
- if (isolates.length === 0) {
1746
- return resolveBidiCore(text, codePoints, cpToStr, forcedLevel);
1747
- }
1748
- const out = [];
1749
- const emit = (cpStart, cpEnd, forced) => {
1750
- if (cpStart >= cpEnd) return;
1751
- const segText = text.substring(cpToStr[cpStart], cpToStr[cpEnd]);
1752
- const segCps = codePoints.slice(cpStart, cpEnd);
1753
- const baseStrIdx = cpToStr[cpStart];
1754
- const segCpToStr = cpToStr.slice(cpStart, cpEnd + 1).map((x) => x - baseStrIdx);
1755
- const segRuns = resolveBidiCore(segText, segCps, segCpToStr, forced);
1756
- for (const r of segRuns) {
1757
- out.push({ text: r.text, level: r.level, start: r.start + baseStrIdx });
1758
- }
1759
- };
1760
- let cursor = 0;
1761
- for (const pair of isolates) {
1762
- emit(cursor, pair.open, forcedLevel);
1763
- const innerStart = pair.open + 1;
1764
- const innerEnd = pair.close;
1765
- let innerLevel;
1766
- if (pair.kind === "LRI") innerLevel = 0;
1767
- else if (pair.kind === "RLI") innerLevel = 1;
1768
- else {
1769
- const innerTypes = codePoints.slice(innerStart, innerEnd).map(classifyBidiType);
1770
- innerLevel = detectParagraphLevel(innerTypes);
1771
- }
1772
- if (innerStart < innerEnd) {
1773
- const innerText = text.substring(cpToStr[innerStart], cpToStr[innerEnd]);
1774
- const innerRuns = resolveBidiRunsForced(innerText, innerLevel);
1775
- const baseStrIdx = cpToStr[innerStart];
1776
- for (const r of innerRuns) {
1777
- out.push({ text: r.text, level: r.level, start: r.start + baseStrIdx });
1875
+ const forms = resolvePositionalForms(codePoints);
1876
+ const glyphs = [];
1877
+ const cmap = fontData.cmap;
1878
+ const widths = fontData.widths;
1879
+ const defaultWidth = fontData.defaultWidth;
1880
+ const markAnchors = fontData.markAnchors;
1881
+ let lastBaseGid = 0;
1882
+ for (let i = 0; i < codePoints.length; i++) {
1883
+ const cp = codePoints[i];
1884
+ if (i < codePoints.length - 1 && isLamAlef(cp, codePoints[i + 1])) {
1885
+ const ligForms = LAM_ALEF_PRES.get(codePoints[i + 1]);
1886
+ if (ligForms) {
1887
+ const isFinal = forms[i] === "medi" || forms[i] === "fina";
1888
+ const ligCP = isFinal ? ligForms[1] : ligForms[0];
1889
+ const ligGid = cmap[ligCP];
1890
+ if (ligGid) {
1891
+ glyphs.push({ gid: ligGid, dx: 0, dy: 0, isZeroAdvance: false });
1892
+ lastBaseGid = ligGid;
1893
+ i++;
1894
+ continue;
1895
+ }
1778
1896
  }
1779
1897
  }
1780
- cursor = pair.close + 1;
1781
- }
1782
- emit(cursor, codePoints.length, forcedLevel);
1783
- return out;
1784
- }
1785
- function resolveBidiCore(text, codePoints, cpToStr, forcedLevel) {
1786
- const len = codePoints.length;
1787
- if (len === 0) return [];
1788
- const types = codePoints.map(classifyBidiType);
1789
- const paraLevel = forcedLevel !== void 0 ? forcedLevel : detectParagraphLevel(types);
1790
- resolveWeakTypes(types, paraLevel);
1791
- resolveNeutralTypes(types, paraLevel);
1792
- if (paraLevel === 1) {
1793
- fixPunctuationAffinity(types, codePoints, len);
1794
- fixBracketPairing(types, codePoints, len);
1795
- }
1796
- const levels = assignLevels(types, paraLevel);
1797
- const runs = [];
1798
- let runStart = 0;
1799
- let runLevel = levels[0];
1800
- for (let i = 1; i <= len; i++) {
1801
- if (i === len || levels[i] !== runLevel) {
1802
- const start = cpToStr[runStart];
1803
- const end = cpToStr[i];
1804
- let runText = text.substring(start, end);
1805
- if (runLevel % 2 === 1) {
1806
- runText = reverseString(runText);
1898
+ let gid = cmap[cp] ?? 0;
1899
+ const presForm = ARABIC_PRES_FORMS.get(cp);
1900
+ if (presForm) {
1901
+ const form = forms[i];
1902
+ let presCP;
1903
+ if (form === "init") presCP = presForm.init;
1904
+ else if (form === "medi") presCP = presForm.medi;
1905
+ else if (form === "fina") presCP = presForm.fina;
1906
+ else presCP = presForm.isol;
1907
+ if (presCP) {
1908
+ const presGid = cmap[presCP];
1909
+ if (presGid) gid = presGid;
1807
1910
  }
1808
- runs.push({ text: runText, level: runLevel, start });
1809
- if (i < len) {
1810
- runStart = i;
1811
- runLevel = levels[i];
1911
+ }
1912
+ const joining = getJoiningType(cp);
1913
+ const isZeroAdvance = joining === "T";
1914
+ if (isZeroAdvance && lastBaseGid !== 0) {
1915
+ const baseAdv = widths[lastBaseGid] !== void 0 ? widths[lastBaseGid] : defaultWidth;
1916
+ const offset = positionMarkOnBase(markAnchors, gid, lastBaseGid, baseAdv);
1917
+ if (offset) {
1918
+ glyphs.push({ gid, dx: offset.dx, dy: offset.dy, isZeroAdvance: true });
1919
+ continue;
1812
1920
  }
1813
1921
  }
1922
+ glyphs.push({ gid, dx: 0, dy: 0, isZeroAdvance });
1923
+ if (!isZeroAdvance) lastBaseGid = gid;
1814
1924
  }
1815
- if (paraLevel === 1 && runs.length > 1) {
1816
- runs.reverse();
1817
- }
1818
- return runs;
1819
- }
1820
- function containsRTL(text) {
1821
- for (let i = 0; i < text.length; ) {
1822
- const cp = text.codePointAt(i) ?? 0;
1823
- const t = classifyBidiType(cp);
1824
- if (t === "R" || t === "AL") return true;
1825
- i += cp > 65535 ? 2 : 1;
1826
- }
1827
- return false;
1828
- }
1829
- function reverseString(str) {
1830
- const cps = [];
1831
- for (let i = 0; i < str.length; ) {
1832
- const cp = str.codePointAt(i) ?? 0;
1833
- cps.push(cp);
1834
- i += cp > 65535 ? 2 : 1;
1835
- }
1836
- cps.reverse();
1837
- return String.fromCodePoint(...cps);
1925
+ return glyphs;
1838
1926
  }
1839
1927
 
1840
1928
  // src/core/encoding-context.ts
@@ -1970,6 +2058,8 @@ function createEncodingContext(fontEntries, pdfA = false) {
1970
2058
  return _usedGids;
1971
2059
  },
1972
2060
  textRuns(str, sz) {
2061
+ if (!str) return [];
2062
+ str = stripBidiControls(str);
1973
2063
  if (!str) return [];
1974
2064
  if (containsRTL(str)) {
1975
2065
  const bidiRuns = resolveBidiRuns(str);
@@ -2107,6 +2197,8 @@ function createEncodingContext(fontEntries, pdfA = false) {
2107
2197
  });
2108
2198
  },
2109
2199
  ps(str) {
2200
+ if (!str) return "<>";
2201
+ str = stripBidiControls(str);
2110
2202
  if (!str) return "<>";
2111
2203
  const { cmap } = primary.fontData;
2112
2204
  if (containsRTL(str)) {
@@ -2790,22 +2882,22 @@ function txt(str, x, y, font, sz, enc) {
2790
2882
  }
2791
2883
  return parts.join("\n");
2792
2884
  }
2793
- function txtR(str, rightX, y, font, sz, enc) {
2794
- const width = enc.isUnicode ? enc.tw(str, sz) : helveticaWidth(toWinAnsi(str), sz);
2885
+ function txtR(str, rightX, y, font, sz, enc, bold = false) {
2886
+ const width = enc.isUnicode ? enc.tw(str, sz) : bold ? helveticaBoldWidth(str, sz) : helveticaWidth(toWinAnsi(str), sz);
2795
2887
  return txt(str, rightX - width, y, font, sz, enc);
2796
2888
  }
2797
- function txtC(str, leftX, y, font, sz, colW, enc) {
2798
- const width = enc.isUnicode ? enc.tw(str, sz) : helveticaWidth(toWinAnsi(str), sz);
2889
+ function txtC(str, leftX, y, font, sz, colW, enc, bold = false) {
2890
+ const width = enc.isUnicode ? enc.tw(str, sz) : bold ? helveticaBoldWidth(str, sz) : helveticaWidth(toWinAnsi(str), sz);
2799
2891
  return txt(str, leftX + (colW - width) / 2, y, font, sz, enc);
2800
2892
  }
2801
2893
  function txtTagged(str, x, y, font, sz, enc, mcid) {
2802
2894
  return wrapSpan(txt(str, x, y, font, sz, enc), str, mcid);
2803
2895
  }
2804
- function txtRTagged(str, rightX, y, font, sz, enc, mcid) {
2805
- return wrapSpan(txtR(str, rightX, y, font, sz, enc), str, mcid);
2896
+ function txtRTagged(str, rightX, y, font, sz, enc, mcid, bold = false) {
2897
+ return wrapSpan(txtR(str, rightX, y, font, sz, enc, bold), str, mcid);
2806
2898
  }
2807
- function txtCTagged(str, leftX, y, font, sz, colW, enc, mcid) {
2808
- return wrapSpan(txtC(str, leftX, y, font, sz, colW, enc), str, mcid);
2899
+ function txtCTagged(str, leftX, y, font, sz, colW, enc, mcid, bold = false) {
2900
+ return wrapSpan(txtC(str, leftX, y, font, sz, colW, enc, bold), str, mcid);
2809
2901
  }
2810
2902
  function encodePdfTextString(str) {
2811
2903
  let ascii = true;
@@ -3263,17 +3355,17 @@ function _buildTableHeader(y, headers, enc, cx, cwi, columns, cw, mgL, mgR, pgW,
3263
3355
  const thEl = { type: "TH", children: [mcref] };
3264
3356
  thChildren.push(thEl);
3265
3357
  if (columns[i].a === "r") {
3266
- ops.push(txtRTagged(t, cx[i] + cwi[i] - 3, y - TH_H + 4, enc.f2, fs.th, enc, mcid));
3358
+ ops.push(txtRTagged(t, cx[i] + cwi[i] - 3, y - TH_H + 4, enc.f2, fs.th, enc, mcid, true));
3267
3359
  } else if (columns[i].a === "c") {
3268
- ops.push(txtCTagged(t, cx[i], y - TH_H + 4, enc.f2, fs.th, cwi[i], enc, mcid));
3360
+ ops.push(txtCTagged(t, cx[i], y - TH_H + 4, enc.f2, fs.th, cwi[i], enc, mcid, true));
3269
3361
  } else {
3270
3362
  ops.push(txtTagged(t, cx[i] + 3, y - TH_H + 4, enc.f2, fs.th, enc, mcid));
3271
3363
  }
3272
3364
  } else {
3273
3365
  if (columns[i].a === "r") {
3274
- ops.push(txtR(t, cx[i] + cwi[i] - 3, y - TH_H + 4, enc.f2, fs.th, enc));
3366
+ ops.push(txtR(t, cx[i] + cwi[i] - 3, y - TH_H + 4, enc.f2, fs.th, enc, true));
3275
3367
  } else if (columns[i].a === "c") {
3276
- ops.push(txtC(t, cx[i], y - TH_H + 4, enc.f2, fs.th, cwi[i], enc));
3368
+ ops.push(txtC(t, cx[i], y - TH_H + 4, enc.f2, fs.th, cwi[i], enc, true));
3277
3369
  } else {
3278
3370
  ops.push(txt(t, cx[i] + 3, y - TH_H + 4, enc.f2, fs.th, enc));
3279
3371
  }