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