pdfnative 1.0.5 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -2155,12 +2155,12 @@ function shapeThaiText(str, fontData) {
2155
2155
  function getAdv(gid) {
2156
2156
  return widths[gid] !== void 0 ? widths[gid] : defaultWidth;
2157
2157
  }
2158
- function getBaseAnchor(baseGid, markClass) {
2158
+ function getBaseAnchor2(baseGid, markClass) {
2159
2159
  const base = markAnchors && markAnchors.bases && markAnchors.bases[baseGid];
2160
2160
  if (!base) return null;
2161
2161
  return base[markClass] ?? null;
2162
2162
  }
2163
- function getMarkAnchor(markGid) {
2163
+ function getMarkAnchor2(markGid) {
2164
2164
  const mark = markAnchors && markAnchors.marks && markAnchors.marks[markGid];
2165
2165
  if (!mark) return null;
2166
2166
  return { classIdx: mark[0], x: mark[1], y: mark[2] };
@@ -2197,7 +2197,7 @@ function shapeThaiText(str, fontData) {
2197
2197
  for (let ai = 0; ai < cluster.aboves.length; ai++) {
2198
2198
  const abvCp = cluster.aboves[ai];
2199
2199
  const markGid = resolveMarkGid(abvCp, isTallBase);
2200
- const markAnchor = getMarkAnchor(markGid);
2200
+ const markAnchor = getMarkAnchor2(markGid);
2201
2201
  let dx = 0;
2202
2202
  let dy = 0;
2203
2203
  if (ai > 0 && prevAboveMarkGid !== null) {
@@ -2206,7 +2206,7 @@ function shapeThaiText(str, fontData) {
2206
2206
  dx = prevAboveMarkDx + m2mOffset.dx;
2207
2207
  dy = prevAboveMarkDy + m2mOffset.dy;
2208
2208
  } else if (markAnchor) {
2209
- const baseAnchor = getBaseAnchor(baseGid, markAnchor.classIdx);
2209
+ const baseAnchor = getBaseAnchor2(baseGid, markAnchor.classIdx);
2210
2210
  if (baseAnchor) {
2211
2211
  dx = baseAnchor[0] - markAnchor.x - baseAdv;
2212
2212
  dy = baseAnchor[1] - markAnchor.y;
@@ -2214,7 +2214,7 @@ function shapeThaiText(str, fontData) {
2214
2214
  }
2215
2215
  } else {
2216
2216
  if (markAnchor) {
2217
- const baseAnchor = getBaseAnchor(baseGid, markAnchor.classIdx);
2217
+ const baseAnchor = getBaseAnchor2(baseGid, markAnchor.classIdx);
2218
2218
  if (baseAnchor) {
2219
2219
  dx = baseAnchor[0] - markAnchor.x - baseAdv;
2220
2220
  dy = baseAnchor[1] - markAnchor.y;
@@ -2228,11 +2228,11 @@ function shapeThaiText(str, fontData) {
2228
2228
  }
2229
2229
  for (const blwCp of cluster.belows) {
2230
2230
  const markGid = cmap[blwCp] || 0;
2231
- const markAnchor = getMarkAnchor(markGid);
2231
+ const markAnchor = getMarkAnchor2(markGid);
2232
2232
  let dx = 0;
2233
2233
  let dy = 0;
2234
2234
  if (markAnchor) {
2235
- const baseAnchor = getBaseAnchor(baseGid, markAnchor.classIdx);
2235
+ const baseAnchor = getBaseAnchor2(baseGid, markAnchor.classIdx);
2236
2236
  if (baseAnchor) {
2237
2237
  dx = baseAnchor[0] - markAnchor.x - baseAdv;
2238
2238
  dy = baseAnchor[1] - markAnchor.y;
@@ -2244,9 +2244,155 @@ function shapeThaiText(str, fontData) {
2244
2244
  return shaped;
2245
2245
  }
2246
2246
 
2247
+ // src/shaping/script-registry.ts
2248
+ var ARABIC_START = 1536;
2249
+ var ARABIC_END = 1791;
2250
+ var ARABIC_SUPPLEMENT_START = 1872;
2251
+ var ARABIC_SUPPLEMENT_END = 1919;
2252
+ var ARABIC_EXTENDED_A_START = 2208;
2253
+ var ARABIC_EXTENDED_A_END = 2303;
2254
+ var ARABIC_PRES_A_START = 64336;
2255
+ var ARABIC_PRES_A_END = 65023;
2256
+ var ARABIC_PRES_B_START = 65136;
2257
+ var ARABIC_PRES_B_END = 65279;
2258
+ var HEBREW_START = 1424;
2259
+ var HEBREW_END = 1535;
2260
+ var HEBREW_PRES_START = 64285;
2261
+ var HEBREW_PRES_END = 64335;
2262
+ var THAI_START = 3584;
2263
+ var THAI_END = 3711;
2264
+ var DEVANAGARI_START = 2304;
2265
+ var DEVANAGARI_END = 2431;
2266
+ var DEVANAGARI_EXT_START = 43232;
2267
+ var DEVANAGARI_EXT_END = 43263;
2268
+ var CYRILLIC_START = 1024;
2269
+ var CYRILLIC_END = 1279;
2270
+ var CYRILLIC_SUPPLEMENT_START = 1280;
2271
+ var CYRILLIC_SUPPLEMENT_END = 1327;
2272
+ var CYRILLIC_EXT_A_START = 11744;
2273
+ var CYRILLIC_EXT_A_END = 11775;
2274
+ var CYRILLIC_EXT_B_START = 42560;
2275
+ var CYRILLIC_EXT_B_END = 42655;
2276
+ var GEORGIAN_START = 4256;
2277
+ var GEORGIAN_END = 4351;
2278
+ var GEORGIAN_SUPPLEMENT_START = 11520;
2279
+ var GEORGIAN_SUPPLEMENT_END = 11567;
2280
+ var ARMENIAN_START = 1328;
2281
+ var ARMENIAN_END = 1423;
2282
+ var ARMENIAN_LIGATURES_START = 64275;
2283
+ var ARMENIAN_LIGATURES_END = 64279;
2284
+ var BENGALI_START = 2432;
2285
+ var BENGALI_END = 2559;
2286
+ var TAMIL_START = 2944;
2287
+ var TAMIL_END = 3071;
2288
+ var EMOJI_RANGES = [
2289
+ [127744, 128511],
2290
+ // Miscellaneous Symbols and Pictographs
2291
+ [128512, 128591],
2292
+ // Emoticons
2293
+ [128640, 128767],
2294
+ // Transport and Map Symbols
2295
+ [128768, 128895],
2296
+ // Alchemical Symbols (partial)
2297
+ [128896, 129023],
2298
+ // Geometric Shapes Extended
2299
+ [129024, 129279],
2300
+ // Supplemental Arrows-C
2301
+ [129280, 129535],
2302
+ // Supplemental Symbols and Pictographs
2303
+ [129536, 129647],
2304
+ // Chess Symbols
2305
+ [129648, 129791],
2306
+ // Symbols and Pictographs Extended-A
2307
+ [9728, 9983],
2308
+ // Miscellaneous Symbols
2309
+ [9984, 10175],
2310
+ // Dingbats
2311
+ [126976, 127023],
2312
+ // Mahjong Tiles
2313
+ [127136, 127231]
2314
+ // Playing Cards
2315
+ ];
2316
+ var FITZPATRICK_START = 127995;
2317
+ var FITZPATRICK_END = 127999;
2318
+ function isArabicCodepoint(cp) {
2319
+ return cp >= ARABIC_START && cp <= ARABIC_END || cp >= ARABIC_SUPPLEMENT_START && cp <= ARABIC_SUPPLEMENT_END || cp >= ARABIC_EXTENDED_A_START && cp <= ARABIC_EXTENDED_A_END || cp >= ARABIC_PRES_A_START && cp <= ARABIC_PRES_A_END || cp >= ARABIC_PRES_B_START && cp <= ARABIC_PRES_B_END;
2320
+ }
2321
+ function isHebrewCodepoint(cp) {
2322
+ return cp >= HEBREW_START && cp <= HEBREW_END || cp >= HEBREW_PRES_START && cp <= HEBREW_PRES_END;
2323
+ }
2324
+ function isThaiCodepoint(cp) {
2325
+ return cp >= THAI_START && cp <= THAI_END;
2326
+ }
2327
+ function isCyrillicCodepoint(cp) {
2328
+ return cp >= CYRILLIC_START && cp <= CYRILLIC_END || cp >= CYRILLIC_SUPPLEMENT_START && cp <= CYRILLIC_SUPPLEMENT_END || cp >= CYRILLIC_EXT_A_START && cp <= CYRILLIC_EXT_A_END || cp >= CYRILLIC_EXT_B_START && cp <= CYRILLIC_EXT_B_END;
2329
+ }
2330
+ function isGeorgianCodepoint(cp) {
2331
+ return cp >= GEORGIAN_START && cp <= GEORGIAN_END || cp >= GEORGIAN_SUPPLEMENT_START && cp <= GEORGIAN_SUPPLEMENT_END;
2332
+ }
2333
+ function isArmenianCodepoint(cp) {
2334
+ return cp >= ARMENIAN_START && cp <= ARMENIAN_END || cp >= ARMENIAN_LIGATURES_START && cp <= ARMENIAN_LIGATURES_END;
2335
+ }
2336
+ function isBengaliCodepoint(cp) {
2337
+ return cp >= BENGALI_START && cp <= BENGALI_END;
2338
+ }
2339
+ function isTamilCodepoint(cp) {
2340
+ return cp >= TAMIL_START && cp <= TAMIL_END;
2341
+ }
2342
+ function isDevanagariCodepoint(cp) {
2343
+ return cp >= DEVANAGARI_START && cp <= DEVANAGARI_END || cp >= DEVANAGARI_EXT_START && cp <= DEVANAGARI_EXT_END;
2344
+ }
2345
+ function isEmojiCodepoint(cp) {
2346
+ if (cp >= FITZPATRICK_START && cp <= FITZPATRICK_END) return true;
2347
+ for (const [lo, hi] of EMOJI_RANGES) {
2348
+ if (cp >= lo && cp <= hi) return true;
2349
+ }
2350
+ return false;
2351
+ }
2352
+ function containsArabic(text) {
2353
+ for (let i = 0; i < text.length; ) {
2354
+ const cp = text.codePointAt(i) ?? 0;
2355
+ if (isArabicCodepoint(cp)) return true;
2356
+ i += cp > 65535 ? 2 : 1;
2357
+ }
2358
+ return false;
2359
+ }
2360
+ function containsHebrew(text) {
2361
+ for (let i = 0; i < text.length; ) {
2362
+ const cp = text.codePointAt(i) ?? 0;
2363
+ if (isHebrewCodepoint(cp)) return true;
2364
+ i += cp > 65535 ? 2 : 1;
2365
+ }
2366
+ return false;
2367
+ }
2368
+ function containsThai(str) {
2369
+ for (let i = 0; i < str.length; i++) {
2370
+ if (isThaiCodepoint(str.charCodeAt(i))) return true;
2371
+ }
2372
+ return false;
2373
+ }
2374
+ function containsBengali(str) {
2375
+ for (let i = 0; i < str.length; i++) {
2376
+ if (isBengaliCodepoint(str.charCodeAt(i))) return true;
2377
+ }
2378
+ return false;
2379
+ }
2380
+ function containsTamil(str) {
2381
+ for (let i = 0; i < str.length; i++) {
2382
+ if (isTamilCodepoint(str.charCodeAt(i))) return true;
2383
+ }
2384
+ return false;
2385
+ }
2386
+ function containsDevanagari(str) {
2387
+ for (let i = 0; i < str.length; i++) {
2388
+ if (isDevanagariCodepoint(str.charCodeAt(i))) return true;
2389
+ }
2390
+ return false;
2391
+ }
2392
+
2247
2393
  // src/shaping/script-detect.ts
2248
2394
  function needsUnicodeFont(lang) {
2249
- return ["th", "ja", "zh", "ko", "el", "hi", "tr", "vi", "pl", "ar", "he", "ru", "ka", "hy"].includes(lang);
2395
+ return ["th", "ja", "zh", "ko", "el", "hi", "tr", "vi", "pl", "ar", "he", "ru", "ka", "hy", "emoji"].includes(lang);
2250
2396
  }
2251
2397
  function detectFallbackLangs(texts, primaryLang) {
2252
2398
  const needed = /* @__PURE__ */ new Set();
@@ -2323,6 +2469,10 @@ function detectFallbackLangs(texts, primaryLang) {
2323
2469
  needed.add("hy");
2324
2470
  continue;
2325
2471
  }
2472
+ if (isEmojiCodepoint(cp)) {
2473
+ needed.add("emoji");
2474
+ continue;
2475
+ }
2326
2476
  }
2327
2477
  }
2328
2478
  needed.delete(primaryLang);
@@ -2343,6 +2493,7 @@ function detectCharLang(cp) {
2343
2493
  if (cp >= 1024 && cp <= 1279 || cp >= 1280 && cp <= 1327) return "ru";
2344
2494
  if (cp >= 4256 && cp <= 4351 || cp >= 11520 && cp <= 11567) return "ka";
2345
2495
  if (cp >= 1328 && cp <= 1423 || cp >= 64275 && cp <= 64279) return "hy";
2496
+ if (isEmojiCodepoint(cp)) return "emoji";
2346
2497
  return null;
2347
2498
  }
2348
2499
 
@@ -2442,8 +2593,24 @@ function pdfString(str) {
2442
2593
  }
2443
2594
  function truncate(str, max) {
2444
2595
  if (!str || str.length <= max) return str || "";
2445
- if (max <= 2) return "..";
2446
- return str.slice(0, max - 2) + "..";
2596
+ if (max <= 1) return "\u2026";
2597
+ return str.slice(0, max - 1) + "\u2026";
2598
+ }
2599
+ function truncateToWidth(str, maxWidthPt, sz, enc) {
2600
+ if (!str) return "";
2601
+ const measure = (s) => enc.isUnicode ? enc.tw(s, sz) : helveticaWidth(s, sz);
2602
+ if (measure(str) <= maxWidthPt) return str;
2603
+ const ell = "\u2026";
2604
+ const ellW = measure(ell);
2605
+ if (maxWidthPt <= ellW) return ell;
2606
+ let lo = 0;
2607
+ let hi = str.length;
2608
+ while (lo < hi) {
2609
+ const mid = lo + hi + 1 >> 1;
2610
+ if (measure(str.slice(0, mid) + ell) <= maxWidthPt) lo = mid;
2611
+ else hi = mid - 1;
2612
+ }
2613
+ return lo <= 0 ? ell : str.slice(0, lo) + ell;
2447
2614
  }
2448
2615
  function helveticaWidth(str, sz) {
2449
2616
  let w = 0;
@@ -2469,113 +2636,25 @@ function helveticaWidth(str, sz) {
2469
2636
  return w * sz / 1e3;
2470
2637
  }
2471
2638
 
2472
- // src/shaping/script-registry.ts
2473
- var ARABIC_START = 1536;
2474
- var ARABIC_END = 1791;
2475
- var ARABIC_SUPPLEMENT_START = 1872;
2476
- var ARABIC_SUPPLEMENT_END = 1919;
2477
- var ARABIC_EXTENDED_A_START = 2208;
2478
- var ARABIC_EXTENDED_A_END = 2303;
2479
- var ARABIC_PRES_A_START = 64336;
2480
- var ARABIC_PRES_A_END = 65023;
2481
- var ARABIC_PRES_B_START = 65136;
2482
- var ARABIC_PRES_B_END = 65279;
2483
- var HEBREW_START = 1424;
2484
- var HEBREW_END = 1535;
2485
- var HEBREW_PRES_START = 64285;
2486
- var HEBREW_PRES_END = 64335;
2487
- var THAI_START = 3584;
2488
- var THAI_END = 3711;
2489
- var DEVANAGARI_START = 2304;
2490
- var DEVANAGARI_END = 2431;
2491
- var DEVANAGARI_EXT_START = 43232;
2492
- var DEVANAGARI_EXT_END = 43263;
2493
- var CYRILLIC_START = 1024;
2494
- var CYRILLIC_END = 1279;
2495
- var CYRILLIC_SUPPLEMENT_START = 1280;
2496
- var CYRILLIC_SUPPLEMENT_END = 1327;
2497
- var CYRILLIC_EXT_A_START = 11744;
2498
- var CYRILLIC_EXT_A_END = 11775;
2499
- var CYRILLIC_EXT_B_START = 42560;
2500
- var CYRILLIC_EXT_B_END = 42655;
2501
- var GEORGIAN_START = 4256;
2502
- var GEORGIAN_END = 4351;
2503
- var GEORGIAN_SUPPLEMENT_START = 11520;
2504
- var GEORGIAN_SUPPLEMENT_END = 11567;
2505
- var ARMENIAN_START = 1328;
2506
- var ARMENIAN_END = 1423;
2507
- var ARMENIAN_LIGATURES_START = 64275;
2508
- var ARMENIAN_LIGATURES_END = 64279;
2509
- var BENGALI_START = 2432;
2510
- var BENGALI_END = 2559;
2511
- var TAMIL_START = 2944;
2512
- var TAMIL_END = 3071;
2513
- function isArabicCodepoint(cp) {
2514
- return cp >= ARABIC_START && cp <= ARABIC_END || cp >= ARABIC_SUPPLEMENT_START && cp <= ARABIC_SUPPLEMENT_END || cp >= ARABIC_EXTENDED_A_START && cp <= ARABIC_EXTENDED_A_END || cp >= ARABIC_PRES_A_START && cp <= ARABIC_PRES_A_END || cp >= ARABIC_PRES_B_START && cp <= ARABIC_PRES_B_END;
2515
- }
2516
- function isHebrewCodepoint(cp) {
2517
- return cp >= HEBREW_START && cp <= HEBREW_END || cp >= HEBREW_PRES_START && cp <= HEBREW_PRES_END;
2518
- }
2519
- function isThaiCodepoint(cp) {
2520
- return cp >= THAI_START && cp <= THAI_END;
2521
- }
2522
- function isCyrillicCodepoint(cp) {
2523
- return cp >= CYRILLIC_START && cp <= CYRILLIC_END || cp >= CYRILLIC_SUPPLEMENT_START && cp <= CYRILLIC_SUPPLEMENT_END || cp >= CYRILLIC_EXT_A_START && cp <= CYRILLIC_EXT_A_END || cp >= CYRILLIC_EXT_B_START && cp <= CYRILLIC_EXT_B_END;
2524
- }
2525
- function isGeorgianCodepoint(cp) {
2526
- return cp >= GEORGIAN_START && cp <= GEORGIAN_END || cp >= GEORGIAN_SUPPLEMENT_START && cp <= GEORGIAN_SUPPLEMENT_END;
2527
- }
2528
- function isArmenianCodepoint(cp) {
2529
- return cp >= ARMENIAN_START && cp <= ARMENIAN_END || cp >= ARMENIAN_LIGATURES_START && cp <= ARMENIAN_LIGATURES_END;
2530
- }
2531
- function isBengaliCodepoint(cp) {
2532
- return cp >= BENGALI_START && cp <= BENGALI_END;
2533
- }
2534
- function isTamilCodepoint(cp) {
2535
- return cp >= TAMIL_START && cp <= TAMIL_END;
2536
- }
2537
- function isDevanagariCodepoint(cp) {
2538
- return cp >= DEVANAGARI_START && cp <= DEVANAGARI_END || cp >= DEVANAGARI_EXT_START && cp <= DEVANAGARI_EXT_END;
2539
- }
2540
- function containsArabic(text) {
2541
- for (let i = 0; i < text.length; ) {
2542
- const cp = text.codePointAt(i) ?? 0;
2543
- if (isArabicCodepoint(cp)) return true;
2544
- i += cp > 65535 ? 2 : 1;
2545
- }
2546
- return false;
2547
- }
2548
- function containsHebrew(text) {
2549
- for (let i = 0; i < text.length; ) {
2550
- const cp = text.codePointAt(i) ?? 0;
2551
- if (isHebrewCodepoint(cp)) return true;
2552
- i += cp > 65535 ? 2 : 1;
2553
- }
2554
- return false;
2555
- }
2556
- function containsThai(str) {
2557
- for (let i = 0; i < str.length; i++) {
2558
- if (isThaiCodepoint(str.charCodeAt(i))) return true;
2559
- }
2560
- return false;
2561
- }
2562
- function containsBengali(str) {
2563
- for (let i = 0; i < str.length; i++) {
2564
- if (isBengaliCodepoint(str.charCodeAt(i))) return true;
2565
- }
2566
- return false;
2567
- }
2568
- function containsTamil(str) {
2569
- for (let i = 0; i < str.length; i++) {
2570
- if (isTamilCodepoint(str.charCodeAt(i))) return true;
2571
- }
2572
- return false;
2573
- }
2574
- function containsDevanagari(str) {
2575
- for (let i = 0; i < str.length; i++) {
2576
- if (isDevanagariCodepoint(str.charCodeAt(i))) return true;
2639
+ // src/shaping/gsub-driver.ts
2640
+ function tryLigature(gids, ligatures) {
2641
+ if (!ligatures || gids.length < 2) return null;
2642
+ const firstGid = gids[0];
2643
+ const entries = ligatures[firstGid];
2644
+ if (!entries) return null;
2645
+ for (const entry of entries) {
2646
+ const compCount = entry.length - 1;
2647
+ if (compCount > gids.length - 1) continue;
2648
+ let match = true;
2649
+ for (let ci = 0; ci < compCount; ci++) {
2650
+ if (gids[1 + ci] !== entry[1 + ci]) {
2651
+ match = false;
2652
+ break;
2653
+ }
2654
+ }
2655
+ if (match) return { resultGid: entry[0], consumed: compCount + 1 };
2577
2656
  }
2578
- return false;
2657
+ return null;
2579
2658
  }
2580
2659
 
2581
2660
  // src/shaping/bengali-shaper.ts
@@ -2704,43 +2783,27 @@ function shapeBengaliText(str, fontData) {
2704
2783
  if (gsub[gid] !== void 0) return gsub[gid];
2705
2784
  return gid;
2706
2785
  }
2707
- function tryLigature(gids) {
2708
- if (!ligatures || gids.length < 2) return null;
2709
- const firstGid = gids[0];
2710
- const entries = ligatures[firstGid];
2711
- if (!entries) return null;
2712
- for (const entry of entries) {
2713
- const compCount = entry.length - 1;
2714
- if (compCount > gids.length - 1) continue;
2715
- let match = true;
2716
- for (let ci = 0; ci < compCount; ci++) {
2717
- if (gids[1 + ci] !== entry[1 + ci]) {
2718
- match = false;
2719
- break;
2720
- }
2721
- }
2722
- if (match) return { resultGid: entry[0], consumed: compCount + 1 };
2723
- }
2724
- return null;
2786
+ function tryLig(gids) {
2787
+ return tryLigature(gids, ligatures);
2725
2788
  }
2726
2789
  function getAdv(gid) {
2727
2790
  return widths[gid] !== void 0 ? widths[gid] : defaultWidth;
2728
2791
  }
2729
- function getBaseAnchor(baseGid, markClass) {
2792
+ function getBaseAnchor2(baseGid, markClass) {
2730
2793
  const base = markAnchors && markAnchors.bases && markAnchors.bases[baseGid];
2731
2794
  if (!base) return null;
2732
2795
  return base[markClass] ?? null;
2733
2796
  }
2734
- function getMarkAnchor(markGid) {
2797
+ function getMarkAnchor2(markGid) {
2735
2798
  const mark = markAnchors && markAnchors.marks && markAnchors.marks[markGid];
2736
2799
  if (!mark) return null;
2737
2800
  return { classIdx: mark[0], x: mark[1], y: mark[2] };
2738
2801
  }
2739
2802
  function emitGlyph(gid, isZero, baseGid) {
2740
2803
  if (isZero && baseGid !== void 0) {
2741
- const markAnchor = getMarkAnchor(gid);
2804
+ const markAnchor = getMarkAnchor2(gid);
2742
2805
  if (markAnchor) {
2743
- const baseAnchorPt = getBaseAnchor(baseGid, markAnchor.classIdx);
2806
+ const baseAnchorPt = getBaseAnchor2(baseGid, markAnchor.classIdx);
2744
2807
  if (baseAnchorPt) {
2745
2808
  const baseAdv = getAdv(baseGid);
2746
2809
  shaped.push({
@@ -2801,7 +2864,7 @@ function shapeBengaliText(str, fontData) {
2801
2864
  }
2802
2865
  }
2803
2866
  let ligConsumed = 0;
2804
- const ligResult = tryLigature(clusterGids);
2867
+ const ligResult = tryLig(clusterGids);
2805
2868
  if (ligResult) {
2806
2869
  emitGlyph(ligResult.resultGid, false);
2807
2870
  baseGid = ligResult.resultGid;
@@ -2809,7 +2872,7 @@ function shapeBengaliText(str, fontData) {
2809
2872
  let gi = ligConsumed;
2810
2873
  while (gi < clusterGids.length) {
2811
2874
  const subSeq = clusterGids.slice(gi);
2812
- const subLig = tryLigature(subSeq);
2875
+ const subLig = tryLig(subSeq);
2813
2876
  if (subLig) {
2814
2877
  emitGlyph(subLig.resultGid, false);
2815
2878
  gi += subLig.consumed;
@@ -2972,43 +3035,27 @@ function shapeTamilText(str, fontData) {
2972
3035
  const normCp = cp === 8239 || cp === 160 ? 32 : cp;
2973
3036
  return cmap[normCp] || 0;
2974
3037
  }
2975
- function tryLigature(gids) {
2976
- if (!ligatures || gids.length < 2) return null;
2977
- const firstGid = gids[0];
2978
- const entries = ligatures[firstGid];
2979
- if (!entries) return null;
2980
- for (const entry of entries) {
2981
- const compCount = entry.length - 1;
2982
- if (compCount > gids.length - 1) continue;
2983
- let match = true;
2984
- for (let ci = 0; ci < compCount; ci++) {
2985
- if (gids[1 + ci] !== entry[1 + ci]) {
2986
- match = false;
2987
- break;
2988
- }
2989
- }
2990
- if (match) return { resultGid: entry[0], consumed: compCount + 1 };
2991
- }
2992
- return null;
3038
+ function tryLig(gids) {
3039
+ return tryLigature(gids, ligatures);
2993
3040
  }
2994
3041
  function getAdv(gid) {
2995
3042
  return widths[gid] !== void 0 ? widths[gid] : defaultWidth;
2996
3043
  }
2997
- function getBaseAnchor(baseGid, markClass) {
3044
+ function getBaseAnchor2(baseGid, markClass) {
2998
3045
  const base = markAnchors && markAnchors.bases && markAnchors.bases[baseGid];
2999
3046
  if (!base) return null;
3000
3047
  return base[markClass] ?? null;
3001
3048
  }
3002
- function getMarkAnchor(markGid) {
3049
+ function getMarkAnchor2(markGid) {
3003
3050
  const mark = markAnchors && markAnchors.marks && markAnchors.marks[markGid];
3004
3051
  if (!mark) return null;
3005
3052
  return { classIdx: mark[0], x: mark[1], y: mark[2] };
3006
3053
  }
3007
3054
  function emitGlyph(gid, isZero, baseGid) {
3008
3055
  if (isZero && baseGid !== void 0) {
3009
- const markAnchor = getMarkAnchor(gid);
3056
+ const markAnchor = getMarkAnchor2(gid);
3010
3057
  if (markAnchor) {
3011
- const baseAnchorPt = getBaseAnchor(baseGid, markAnchor.classIdx);
3058
+ const baseAnchorPt = getBaseAnchor2(baseGid, markAnchor.classIdx);
3012
3059
  if (baseAnchorPt) {
3013
3060
  const baseAdv = getAdv(baseGid);
3014
3061
  shaped.push({
@@ -3073,7 +3120,7 @@ function shapeTamilText(str, fontData) {
3073
3120
  }
3074
3121
  }
3075
3122
  let ligConsumed = 0;
3076
- const ligResult = tryLigature(clusterGids);
3123
+ const ligResult = tryLig(clusterGids);
3077
3124
  if (ligResult) {
3078
3125
  emitGlyph(ligResult.resultGid, false);
3079
3126
  baseGid = ligResult.resultGid;
@@ -3081,7 +3128,7 @@ function shapeTamilText(str, fontData) {
3081
3128
  let gi = ligConsumed;
3082
3129
  while (gi < clusterGids.length) {
3083
3130
  const subSeq = clusterGids.slice(gi);
3084
- const subLig = tryLigature(subSeq);
3131
+ const subLig = tryLig(subSeq);
3085
3132
  if (subLig) {
3086
3133
  emitGlyph(subLig.resultGid, false);
3087
3134
  gi += subLig.consumed;
@@ -3130,6 +3177,30 @@ function shapeTamilText(str, fontData) {
3130
3177
  return shaped;
3131
3178
  }
3132
3179
 
3180
+ // src/shaping/gpos-positioner.ts
3181
+ function getBaseAnchor(markAnchors, baseGid, markClass) {
3182
+ if (!markAnchors) return null;
3183
+ const base = markAnchors.bases[baseGid];
3184
+ if (!base) return null;
3185
+ return base[markClass] ?? null;
3186
+ }
3187
+ function getMarkAnchor(markAnchors, markGid) {
3188
+ if (!markAnchors) return null;
3189
+ const mark = markAnchors.marks[markGid];
3190
+ if (!mark) return null;
3191
+ return { classIdx: mark[0], x: mark[1], y: mark[2] };
3192
+ }
3193
+ function positionMarkOnBase(markAnchors, markGid, baseGid, baseAdv) {
3194
+ const mark = getMarkAnchor(markAnchors, markGid);
3195
+ if (!mark) return null;
3196
+ const base = getBaseAnchor(markAnchors, baseGid, mark.classIdx);
3197
+ if (!base) return null;
3198
+ return {
3199
+ dx: base[0] - mark.x - baseAdv,
3200
+ dy: base[1] - mark.y
3201
+ };
3202
+ }
3203
+
3133
3204
  // src/shaping/devanagari-shaper.ts
3134
3205
  var HALANT2 = 2381;
3135
3206
  var NUKTA2 = 2364;
@@ -3245,43 +3316,17 @@ function shapeDevanagariText(str, fontData) {
3245
3316
  const normCp = cp === 8239 || cp === 160 ? 32 : cp;
3246
3317
  return cmap[normCp] || 0;
3247
3318
  }
3248
- function tryLigature(gids) {
3249
- if (!ligatures || gids.length < 2) return null;
3250
- const firstGid = gids[0];
3251
- const entries = ligatures[firstGid];
3252
- if (!entries) return null;
3253
- for (const entry of entries) {
3254
- const compCount = entry.length - 1;
3255
- if (compCount > gids.length - 1) continue;
3256
- let match = true;
3257
- for (let ci = 0; ci < compCount; ci++) {
3258
- if (gids[1 + ci] !== entry[1 + ci]) {
3259
- match = false;
3260
- break;
3261
- }
3262
- }
3263
- if (match) return { resultGid: entry[0], consumed: compCount + 1 };
3264
- }
3265
- return null;
3319
+ function tryLig(gids) {
3320
+ return tryLigature(gids, ligatures);
3266
3321
  }
3267
3322
  function getAdv(gid) {
3268
3323
  return widths[gid] !== void 0 ? widths[gid] : defaultWidth;
3269
3324
  }
3270
- function getBaseAnchor(baseGid, markClass) {
3271
- const base = markAnchors && markAnchors.bases && markAnchors.bases[baseGid];
3272
- if (!base) return null;
3273
- return base[markClass] ?? null;
3274
- }
3275
- function getMarkAnchor(markGid) {
3276
- const mark = markAnchors && markAnchors.marks && markAnchors.marks[markGid];
3277
- if (!mark) return null;
3278
- return { classIdx: mark[0], x: mark[1], y: mark[2] };
3279
- }
3280
3325
  function emitGlyph(gid, isZero, baseGid) {
3281
3326
  if (isZero && baseGid !== void 0) {
3282
- const markAnchor = getMarkAnchor(gid);
3327
+ const markAnchor = getMarkAnchor(markAnchors, gid);
3283
3328
  if (markAnchor) {
3284
- const baseAnchorPt = getBaseAnchor(baseGid, markAnchor.classIdx);
3329
+ const baseAnchorPt = getBaseAnchor(markAnchors, baseGid, markAnchor.classIdx);
3285
3330
  if (baseAnchorPt) {
3286
3331
  const baseAdv = getAdv(baseGid);
3287
3332
  shaped.push({
@@ -3329,7 +3374,7 @@ function shapeDevanagariText(str, fontData) {
3329
3374
  if (hasReph) {
3330
3375
  const raGid = resolveGid(RA2);
3331
3376
  const halantGid = resolveGid(HALANT2);
3332
- const rephLig = tryLigature([raGid, halantGid]);
3377
+ const rephLig = tryLig([raGid, halantGid]);
3333
3378
  if (rephLig) {
3334
3379
  emitGlyph(rephLig.resultGid, true, baseGid);
3335
3380
  } else {
@@ -3352,14 +3397,14 @@ function shapeDevanagariText(str, fontData) {
3352
3397
  break;
3353
3398
  }
3354
3399
  }
3355
- const ligResult = tryLigature(clusterGids);
3400
+ const ligResult = tryLig(clusterGids);
3356
3401
  if (ligResult) {
3357
3402
  emitGlyph(ligResult.resultGid, false);
3358
3403
  baseGid = ligResult.resultGid;
3359
3404
  let gi = ligResult.consumed;
3360
3405
  while (gi < clusterGids.length) {
3361
3406
  const subSeq = clusterGids.slice(gi);
3362
- const subLig = tryLigature(subSeq);
3407
+ const subLig = tryLig(subSeq);
3363
3408
  if (subLig) {
3364
3409
  emitGlyph(subLig.resultGid, false);
3365
3410
  gi += subLig.consumed;
@@ -3533,6 +3578,10 @@ function shapeArabicText(str, fontData) {
3533
3578
  const forms = resolvePositionalForms(codePoints);
3534
3579
  const glyphs = [];
3535
3580
  const cmap = fontData.cmap;
3581
+ const widths = fontData.widths;
3582
+ const defaultWidth = fontData.defaultWidth;
3583
+ const markAnchors = fontData.markAnchors;
3584
+ let lastBaseGid = 0;
3536
3585
  for (let i = 0; i < codePoints.length; i++) {
3537
3586
  const cp = codePoints[i];
3538
3587
  if (i < codePoints.length - 1 && isLamAlef(cp, codePoints[i + 1])) {
@@ -3543,6 +3592,7 @@ function shapeArabicText(str, fontData) {
3543
3592
  const ligGid = cmap[ligCP];
3544
3593
  if (ligGid) {
3545
3594
  glyphs.push({ gid: ligGid, dx: 0, dy: 0, isZeroAdvance: false });
3595
+ lastBaseGid = ligGid;
3546
3596
  i++;
3547
3597
  continue;
3548
3598
  }
@@ -3564,7 +3614,16 @@ function shapeArabicText(str, fontData) {
3564
3614
  }
3565
3615
  const joining = getJoiningType(cp);
3566
3616
  const isZeroAdvance = joining === "T";
3617
+ if (isZeroAdvance && lastBaseGid !== 0) {
3618
+ const baseAdv = widths[lastBaseGid] !== void 0 ? widths[lastBaseGid] : defaultWidth;
3619
+ const offset = positionMarkOnBase(markAnchors, gid, lastBaseGid, baseAdv);
3620
+ if (offset) {
3621
+ glyphs.push({ gid, dx: offset.dx, dy: offset.dy, isZeroAdvance: true });
3622
+ continue;
3623
+ }
3624
+ }
3567
3625
  glyphs.push({ gid, dx: 0, dy: 0, isZeroAdvance });
3626
+ if (!isZeroAdvance) lastBaseGid = gid;
3568
3627
  }
3569
3628
  return glyphs;
3570
3629
  }
@@ -3577,7 +3636,7 @@ function classifyBidiType(cp) {
3577
3636
  cp >= 1611 && cp <= 1631 || // Arabic harakat
3578
3637
  cp >= 1648 && cp <= 1648 || // Arabic superscript alef
3579
3638
  cp >= 1750 && cp <= 1756 || cp >= 1759 && cp <= 1764 || cp >= 1767 && cp <= 1768 || cp >= 1770 && cp <= 1773 || cp >= 65056 && cp <= 65071) return "NSM";
3580
- if (cp === 8203 || cp === 8204 || cp === 8205 || cp === 8206 || cp === 8207 || cp === 65279) return "BN";
3639
+ if (cp === 8203 || cp === 8204 || cp === 8205 || cp === 8206 || cp === 8207 || cp === 65279 || cp === 8294 || cp === 8295 || cp === 8296 || cp === 8297) return "BN";
3581
3640
  if (cp >= 1632 && cp <= 1641) return "AN";
3582
3641
  if (cp >= 1776 && cp <= 1785) return "AN";
3583
3642
  if (cp >= 48 && cp <= 57) return "EN";
@@ -3791,18 +3850,153 @@ function fixBracketPairing(types, codePoints, len) {
3791
3850
  }
3792
3851
  }
3793
3852
  }
3853
+ function findOutermostIsolatePairs(codePoints) {
3854
+ const pairs = [];
3855
+ let i = 0;
3856
+ while (i < codePoints.length) {
3857
+ const cp = codePoints[i];
3858
+ if (cp === 8294 || cp === 8295 || cp === 8296) {
3859
+ let depth = 1;
3860
+ let close = -1;
3861
+ for (let j = i + 1; j < codePoints.length; j++) {
3862
+ const cj = codePoints[j];
3863
+ if (cj === 8294 || cj === 8295 || cj === 8296) depth++;
3864
+ else if (cj === 8297) {
3865
+ depth--;
3866
+ if (depth === 0) {
3867
+ close = j;
3868
+ break;
3869
+ }
3870
+ }
3871
+ }
3872
+ if (close === -1) {
3873
+ i++;
3874
+ continue;
3875
+ }
3876
+ const kind = cp === 8294 ? "LRI" : cp === 8295 ? "RLI" : "FSI";
3877
+ pairs.push({ open: i, close, kind });
3878
+ i = close + 1;
3879
+ } else {
3880
+ i++;
3881
+ }
3882
+ }
3883
+ return pairs;
3884
+ }
3794
3885
  function resolveBidiRuns(text) {
3795
3886
  if (!text) return [];
3796
3887
  const codePoints = [];
3888
+ const cpToStr = [];
3889
+ for (let i = 0; i < text.length; ) {
3890
+ cpToStr.push(i);
3891
+ const cp = text.codePointAt(i) ?? 0;
3892
+ codePoints.push(cp);
3893
+ i += cp > 65535 ? 2 : 1;
3894
+ }
3895
+ cpToStr.push(text.length);
3896
+ const isolates = findOutermostIsolatePairs(codePoints);
3897
+ if (isolates.length === 0) {
3898
+ return resolveBidiCore(text, codePoints, cpToStr);
3899
+ }
3900
+ const insideIsolate = new Array(codePoints.length).fill(false);
3901
+ for (const p of isolates) {
3902
+ for (let k = p.open; k <= p.close; k++) insideIsolate[k] = true;
3903
+ }
3904
+ const outerTypes = codePoints.map((cp, idx) => insideIsolate[idx] ? "BN" : classifyBidiType(cp));
3905
+ const parentLevel = detectParagraphLevel(outerTypes);
3906
+ const out = [];
3907
+ const emitSegment = (cpStart, cpEnd, forced) => {
3908
+ if (cpStart >= cpEnd) return;
3909
+ const segText = text.substring(cpToStr[cpStart], cpToStr[cpEnd]);
3910
+ const segCps = codePoints.slice(cpStart, cpEnd);
3911
+ const baseStrIdx = cpToStr[cpStart];
3912
+ const segCpToStr = cpToStr.slice(cpStart, cpEnd + 1).map((x) => x - baseStrIdx);
3913
+ const segRuns = forced === void 0 ? resolveBidiRuns(segText) : resolveBidiCore(segText, segCps, segCpToStr, forced);
3914
+ for (const r of segRuns) {
3915
+ out.push({ text: r.text, level: r.level, start: r.start + baseStrIdx });
3916
+ }
3917
+ };
3918
+ let cursor = 0;
3919
+ for (const pair of isolates) {
3920
+ emitSegment(cursor, pair.open, parentLevel);
3921
+ const innerStart = pair.open + 1;
3922
+ const innerEnd = pair.close;
3923
+ let innerLevel;
3924
+ if (pair.kind === "LRI") innerLevel = 0;
3925
+ else if (pair.kind === "RLI") innerLevel = 1;
3926
+ else {
3927
+ const innerTypes = codePoints.slice(innerStart, innerEnd).map(classifyBidiType);
3928
+ innerLevel = detectParagraphLevel(innerTypes);
3929
+ }
3930
+ if (innerStart < innerEnd) {
3931
+ const innerText = text.substring(cpToStr[innerStart], cpToStr[innerEnd]);
3932
+ const innerRuns = resolveBidiRunsForced(innerText, innerLevel);
3933
+ const baseStrIdx = cpToStr[innerStart];
3934
+ for (const r of innerRuns) {
3935
+ out.push({ text: r.text, level: r.level, start: r.start + baseStrIdx });
3936
+ }
3937
+ }
3938
+ cursor = pair.close + 1;
3939
+ }
3940
+ emitSegment(cursor, codePoints.length, parentLevel);
3941
+ return out;
3942
+ }
3943
+ function resolveBidiRunsForced(text, forcedLevel) {
3944
+ if (!text) return [];
3945
+ const codePoints = [];
3946
+ const cpToStr = [];
3797
3947
  for (let i = 0; i < text.length; ) {
3948
+ cpToStr.push(i);
3798
3949
  const cp = text.codePointAt(i) ?? 0;
3799
3950
  codePoints.push(cp);
3800
3951
  i += cp > 65535 ? 2 : 1;
3801
3952
  }
3953
+ cpToStr.push(text.length);
3954
+ const isolates = findOutermostIsolatePairs(codePoints);
3955
+ if (isolates.length === 0) {
3956
+ return resolveBidiCore(text, codePoints, cpToStr, forcedLevel);
3957
+ }
3958
+ const out = [];
3959
+ const emit = (cpStart, cpEnd, forced) => {
3960
+ if (cpStart >= cpEnd) return;
3961
+ const segText = text.substring(cpToStr[cpStart], cpToStr[cpEnd]);
3962
+ const segCps = codePoints.slice(cpStart, cpEnd);
3963
+ const baseStrIdx = cpToStr[cpStart];
3964
+ const segCpToStr = cpToStr.slice(cpStart, cpEnd + 1).map((x) => x - baseStrIdx);
3965
+ const segRuns = resolveBidiCore(segText, segCps, segCpToStr, forced);
3966
+ for (const r of segRuns) {
3967
+ out.push({ text: r.text, level: r.level, start: r.start + baseStrIdx });
3968
+ }
3969
+ };
3970
+ let cursor = 0;
3971
+ for (const pair of isolates) {
3972
+ emit(cursor, pair.open, forcedLevel);
3973
+ const innerStart = pair.open + 1;
3974
+ const innerEnd = pair.close;
3975
+ let innerLevel;
3976
+ if (pair.kind === "LRI") innerLevel = 0;
3977
+ else if (pair.kind === "RLI") innerLevel = 1;
3978
+ else {
3979
+ const innerTypes = codePoints.slice(innerStart, innerEnd).map(classifyBidiType);
3980
+ innerLevel = detectParagraphLevel(innerTypes);
3981
+ }
3982
+ if (innerStart < innerEnd) {
3983
+ const innerText = text.substring(cpToStr[innerStart], cpToStr[innerEnd]);
3984
+ const innerRuns = resolveBidiRunsForced(innerText, innerLevel);
3985
+ const baseStrIdx = cpToStr[innerStart];
3986
+ for (const r of innerRuns) {
3987
+ out.push({ text: r.text, level: r.level, start: r.start + baseStrIdx });
3988
+ }
3989
+ }
3990
+ cursor = pair.close + 1;
3991
+ }
3992
+ emit(cursor, codePoints.length, forcedLevel);
3993
+ return out;
3994
+ }
3995
+ function resolveBidiCore(text, codePoints, cpToStr, forcedLevel) {
3802
3996
  const len = codePoints.length;
3803
3997
  if (len === 0) return [];
3804
3998
  const types = codePoints.map(classifyBidiType);
3805
- const paraLevel = detectParagraphLevel(types);
3999
+ const paraLevel = forcedLevel !== void 0 ? forcedLevel : detectParagraphLevel(types);
3806
4000
  resolveWeakTypes(types, paraLevel);
3807
4001
  resolveNeutralTypes(types, paraLevel);
3808
4002
  if (paraLevel === 1) {
@@ -3813,13 +4007,6 @@ function resolveBidiRuns(text) {
3813
4007
  const runs = [];
3814
4008
  let runStart = 0;
3815
4009
  let runLevel = levels[0];
3816
- const cpToStr = [];
3817
- let strIdx = 0;
3818
- for (let i = 0; i < len; i++) {
3819
- cpToStr.push(strIdx);
3820
- strIdx += codePoints[i] > 65535 ? 2 : 1;
3821
- }
3822
- cpToStr.push(strIdx);
3823
4010
  for (let i = 1; i <= len; i++) {
3824
4011
  if (i === len || levels[i] !== runLevel) {
3825
4012
  const start = cpToStr[runStart];
@@ -3903,7 +4090,7 @@ function splitArabicNonArabic(text, fd) {
3903
4090
  if (cur) segments.push({ text: cur, arabic: curArabic });
3904
4091
  return segments;
3905
4092
  }
3906
- function buildTextRunsWithFallback(text, fontRef, fd, sz, trackGid) {
4093
+ function buildTextRunsWithFallback(text, fontRef, fd, sz, trackGid, pdfA = false) {
3907
4094
  const upm = fd.metrics.unitsPerEm;
3908
4095
  const result = [];
3909
4096
  let mode = null;
@@ -3943,11 +4130,11 @@ function buildTextRunsWithFallback(text, fontRef, fd, sz, trackGid) {
3943
4130
  const cp = rawCp === 8239 || rawCp === 160 ? 32 : rawCp;
3944
4131
  const char = text.substring(i, i + charLen);
3945
4132
  const gid = fd.cmap[cp] ?? 0;
3946
- if (gid === 0 && isWinAnsi(cp)) {
4133
+ if (gid === 0 && isWinAnsi(cp) && !pdfA) {
3947
4134
  if (mode === "cid") flushCid();
3948
4135
  mode = "hel";
3949
4136
  helChars += char;
3950
- } else if (mode === "hel" && isWinAnsi(cp)) {
4137
+ } else if (mode === "hel" && isWinAnsi(cp) && !pdfA) {
3951
4138
  helChars += char;
3952
4139
  } else {
3953
4140
  if (mode === "hel") flushHel();
@@ -3964,7 +4151,7 @@ function buildTextRunsWithFallback(text, fontRef, fd, sz, trackGid) {
3964
4151
  if (mode === "hel") flushHel();
3965
4152
  return result;
3966
4153
  }
3967
- function createEncodingContext(fontEntries) {
4154
+ function createEncodingContext(fontEntries, pdfA = false) {
3968
4155
  if (!fontEntries || fontEntries.length === 0) {
3969
4156
  return {
3970
4157
  isUnicode: false,
@@ -4020,12 +4207,12 @@ function createEncodingContext(fontEntries) {
4020
4207
  }
4021
4208
  result.push({ text: seg.text, fontRef, fontData: fd, shaped: visual, hexStr: null, widthPt: designW * sz / upm });
4022
4209
  } else {
4023
- const subRuns = buildTextRunsWithFallback(seg.text, fontRef, fd, sz, _trackGid);
4210
+ const subRuns = buildTextRunsWithFallback(seg.text, fontRef, fd, sz, _trackGid, pdfA);
4024
4211
  result.push(...subRuns);
4025
4212
  }
4026
4213
  }
4027
4214
  } else if (isRTL) {
4028
- const subRuns = buildTextRunsWithFallback(fRun.text, fontRef, fd, sz, _trackGid);
4215
+ const subRuns = buildTextRunsWithFallback(fRun.text, fontRef, fd, sz, _trackGid, pdfA);
4029
4216
  result.push(...subRuns);
4030
4217
  } else {
4031
4218
  if (containsThai(fRun.text)) {
@@ -4069,7 +4256,7 @@ function createEncodingContext(fontEntries) {
4069
4256
  }
4070
4257
  result.push({ text: fRun.text, fontRef, fontData: fd, shaped, hexStr: null, widthPt: designW * sz / upm });
4071
4258
  } else {
4072
- const subRuns = buildTextRunsWithFallback(fRun.text, fontRef, fd, sz, _trackGid);
4259
+ const subRuns = buildTextRunsWithFallback(fRun.text, fontRef, fd, sz, _trackGid, pdfA);
4073
4260
  result.push(...subRuns);
4074
4261
  }
4075
4262
  }
@@ -4126,7 +4313,7 @@ function createEncodingContext(fontEntries) {
4126
4313
  }
4127
4314
  return [{ text: run.text, fontRef, fontData: fd, shaped, hexStr: null, widthPt: designW * sz / upm }];
4128
4315
  }
4129
- return buildTextRunsWithFallback(run.text, fontRef, fd, sz, _trackGid);
4316
+ return buildTextRunsWithFallback(run.text, fontRef, fd, sz, _trackGid, pdfA);
4130
4317
  });
4131
4318
  },
4132
4319
  ps(str) {
@@ -4640,7 +4827,7 @@ function buildPdfMetadata(now = /* @__PURE__ */ new Date()) {
4640
4827
  const xmpDate = `${yyyy}-${mm}-${dd}T${hh}:${mi}:${ss}${tzSign}${tzH}:${tzM}`;
4641
4828
  return { pdfDate, xmpDate };
4642
4829
  }
4643
- function buildXMPMetadata(title, createDate, pdfaPart = 2, pdfaConformance = "B", author) {
4830
+ function buildXMPMetadata(title, createDate, pdfaPart = 2, pdfaConformance = "B", author, subject, keywords) {
4644
4831
  const escapedTitle = escapeXml(title);
4645
4832
  const lines = [
4646
4833
  '<?xpacket begin="\xEF\xBB\xBF" id="W5M0MpCehiHzreSzNTczkc9d"?>',
@@ -4656,13 +4843,21 @@ function buildXMPMetadata(title, createDate, pdfaPart = 2, pdfaConformance = "B"
4656
4843
  if (author !== void 0 && author !== "") {
4657
4844
  lines.push(` <dc:creator><rdf:Seq><rdf:li>${escapeXml(author)}</rdf:li></rdf:Seq></dc:creator>`);
4658
4845
  }
4846
+ if (subject !== void 0 && subject !== "") {
4847
+ lines.push(` <dc:description><rdf:Alt><rdf:li xml:lang="x-default">${escapeXml(subject)}</rdf:li></rdf:Alt></dc:description>`);
4848
+ }
4659
4849
  lines.push(
4660
4850
  " <pdf:Producer>pdfnative</pdf:Producer>",
4661
4851
  ` <xmp:CreateDate>${createDate}</xmp:CreateDate>`,
4662
4852
  ` <xmp:ModifyDate>${createDate}</xmp:ModifyDate>`,
4663
4853
  ` <xmp:MetadataDate>${createDate}</xmp:MetadataDate>`,
4664
4854
  ` <pdfaid:part>${pdfaPart}</pdfaid:part>`,
4665
- ` <pdfaid:conformance>${pdfaConformance}</pdfaid:conformance>`,
4855
+ ` <pdfaid:conformance>${pdfaConformance}</pdfaid:conformance>`
4856
+ );
4857
+ if (keywords !== void 0 && keywords !== "") {
4858
+ lines.push(` <pdf:Keywords>${escapeXml(keywords)}</pdf:Keywords>`);
4859
+ }
4860
+ lines.push(
4666
4861
  " </rdf:Description>",
4667
4862
  " </rdf:RDF>",
4668
4863
  "</x:xmpmeta>",
@@ -4670,6 +4865,35 @@ function buildXMPMetadata(title, createDate, pdfaPart = 2, pdfaConformance = "B"
4670
4865
  );
4671
4866
  return lines.join("\n");
4672
4867
  }
4868
+ function utf8EncodeBinaryString(str) {
4869
+ let out = "";
4870
+ for (let i = 0; i < str.length; i++) {
4871
+ let cp = str.charCodeAt(i);
4872
+ if (cp >= 55296 && cp <= 56319 && i + 1 < str.length) {
4873
+ const lo = str.charCodeAt(i + 1);
4874
+ if (lo >= 56320 && lo <= 57343) {
4875
+ cp = (cp - 55296 << 10) + (lo - 56320) + 65536;
4876
+ i++;
4877
+ }
4878
+ }
4879
+ if (cp < 128) {
4880
+ out += String.fromCharCode(cp);
4881
+ } else if (cp < 2048) {
4882
+ out += String.fromCharCode(192 | cp >> 6);
4883
+ out += String.fromCharCode(128 | cp & 63);
4884
+ } else if (cp < 65536) {
4885
+ out += String.fromCharCode(224 | cp >> 12);
4886
+ out += String.fromCharCode(128 | cp >> 6 & 63);
4887
+ out += String.fromCharCode(128 | cp & 63);
4888
+ } else {
4889
+ out += String.fromCharCode(240 | cp >> 18);
4890
+ out += String.fromCharCode(128 | cp >> 12 & 63);
4891
+ out += String.fromCharCode(128 | cp >> 6 & 63);
4892
+ out += String.fromCharCode(128 | cp & 63);
4893
+ }
4894
+ }
4895
+ return out;
4896
+ }
4673
4897
  function buildOutputIntentDict(iccStreamObjNum, subtype = "GTS_PDFA1") {
4674
4898
  return `<< /Type /OutputIntent /S /${subtype} /OutputConditionIdentifier (sRGB IEC61966-2.1) /RegistryName (http://www.color.org) /DestOutputProfile ${iccStreamObjNum} 0 R >>`;
4675
4899
  }
@@ -5004,13 +5228,42 @@ var DEFAULT_COLUMNS = [
5004
5228
  { f: 0.18, a: "c", mx: 20, mxH: 20 }
5005
5229
  ];
5006
5230
  function computeColumnPositions(columns, marginLeft, contentWidth) {
5007
- const cx = [];
5008
- const cwi = [];
5231
+ const n = columns.length;
5232
+ const cwi = new Array(n).fill(0);
5233
+ const fixed = new Array(n).fill(false);
5234
+ let totalFixed = 0;
5235
+ let freeWeight = 0;
5236
+ for (let i = 0; i < n; i++) {
5237
+ const col = columns[i];
5238
+ let w = col.f * contentWidth;
5239
+ let clamped = false;
5240
+ if (col.minWidth !== void 0 && w < col.minWidth) {
5241
+ w = col.minWidth;
5242
+ clamped = true;
5243
+ }
5244
+ if (col.maxWidth !== void 0 && w > col.maxWidth) {
5245
+ w = col.maxWidth;
5246
+ clamped = true;
5247
+ }
5248
+ if (clamped) {
5249
+ cwi[i] = w;
5250
+ fixed[i] = true;
5251
+ totalFixed += w;
5252
+ } else {
5253
+ freeWeight += col.f;
5254
+ }
5255
+ }
5256
+ const remaining = contentWidth - totalFixed;
5257
+ if (freeWeight > 0) {
5258
+ for (let i = 0; i < n; i++) {
5259
+ if (!fixed[i]) cwi[i] = columns[i].f / freeWeight * remaining;
5260
+ }
5261
+ }
5262
+ const cx = new Array(n);
5009
5263
  let x = marginLeft;
5010
- for (const col of columns) {
5011
- cx.push(x);
5012
- cwi.push(col.f * contentWidth);
5013
- x += col.f * contentWidth;
5264
+ for (let i = 0; i < n; i++) {
5265
+ cx[i] = x;
5266
+ x += cwi[i];
5014
5267
  }
5015
5268
  return { cx, cwi };
5016
5269
  }
@@ -5502,6 +5755,7 @@ var DEFAULT_TEXT_OPACITY = 0.15;
5502
5755
  var DEFAULT_TEXT_ANGLE = -45;
5503
5756
  var DEFAULT_IMAGE_OPACITY = 0.1;
5504
5757
  var DEFAULT_CAP_HEIGHT_RATIO = 0.718;
5758
+ var WATERMARK_SAFETY_MARGIN = 24;
5505
5759
  function validateWatermark(watermark, pdfaLevel) {
5506
5760
  if (pdfaLevel === "pdfa1b") {
5507
5761
  const textOpacity = watermark.text?.opacity ?? DEFAULT_TEXT_OPACITY;
@@ -5547,18 +5801,32 @@ function buildWatermarkState(watermark, pgW, pgH, enc) {
5547
5801
  };
5548
5802
  }
5549
5803
  function _buildTextWatermarkOps(wm, pgW, pgH, enc, gsName) {
5550
- const sz = wm.fontSize ?? DEFAULT_TEXT_FONT_SIZE;
5804
+ let sz = wm.fontSize ?? DEFAULT_TEXT_FONT_SIZE;
5551
5805
  const color = parseColor(wm.color ?? DEFAULT_TEXT_COLOR);
5552
5806
  const angle = (wm.angle ?? DEFAULT_TEXT_ANGLE) * DEG_TO_RAD;
5553
5807
  const cos = Math.cos(angle);
5554
5808
  const sin = Math.sin(angle);
5809
+ const fdMetrics = enc.fontData?.metrics;
5810
+ const capRatio = fdMetrics ? fdMetrics.capHeight / fdMetrics.unitsPerEm : DEFAULT_CAP_HEIGHT_RATIO;
5811
+ if (wm.autoFit !== false) {
5812
+ const textW = enc.tw(wm.text, sz);
5813
+ const textH = sz * capRatio;
5814
+ const aCos = Math.abs(cos);
5815
+ const aSin = Math.abs(sin);
5816
+ const rotW = textW * aCos + textH * aSin;
5817
+ const rotH = textW * aSin + textH * aCos;
5818
+ const safeW = pgW - WATERMARK_SAFETY_MARGIN * 2;
5819
+ const safeH = pgH - WATERMARK_SAFETY_MARGIN * 2;
5820
+ if (rotW > safeW || rotH > safeH) {
5821
+ const scale = Math.min(safeW / rotW, safeH / rotH);
5822
+ sz *= scale;
5823
+ }
5824
+ }
5555
5825
  const cx = pgW / 2;
5556
5826
  const cy = pgH / 2;
5557
5827
  const textWidth = enc.tw(wm.text, sz);
5558
5828
  const offsetX = -textWidth / 2;
5559
- const fd = enc.fontData;
5560
- const capHeightRatio = fd ? fd.metrics.capHeight / fd.metrics.unitsPerEm : DEFAULT_CAP_HEIGHT_RATIO;
5561
- const offsetY = -sz * capHeightRatio / 2;
5829
+ const offsetY = -sz * capRatio / 2;
5562
5830
  const tx = cx + offsetX * cos - offsetY * sin;
5563
5831
  const ty = cy + offsetX * sin + offsetY * cos;
5564
5832
  const escapedText = enc.ps(wm.text);
@@ -5746,7 +6014,9 @@ function buildPDF(params, layoutOptions) {
5746
6014
  const fs = layoutOptions?.fontSizes ? { ...DEFAULT_FONT_SIZES, ...layoutOptions.fontSizes } : DEFAULT_FONT_SIZES;
5747
6015
  const { cx, cwi } = computeColumnPositions(columns, mg.l, cw);
5748
6016
  const fontEntries = params.fontEntries || (fontData ? [{ fontData, fontRef: "/F3", lang: "unknown" }] : []);
5749
- const enc = createEncodingContext(fontEntries);
6017
+ const pdfaConfig = resolvePdfAConfig(layoutOptions?.tagged);
6018
+ const tagged = pdfaConfig.enabled;
6019
+ const enc = createEncodingContext(fontEntries, tagged);
5750
6020
  const footerTpl = layoutOptions?.footerTemplate ?? {
5751
6021
  left: footerText || void 0,
5752
6022
  right: "{page}/{pages}"
@@ -5768,8 +6038,6 @@ function buildPDF(params, layoutOptions) {
5768
6038
  totalPages = 1 + Math.ceil((totalRows - rowsPage1) / rowsPerPage);
5769
6039
  }
5770
6040
  if (totalPages < 1) totalPages = 1;
5771
- const pdfaConfig = resolvePdfAConfig(layoutOptions?.tagged);
5772
- const tagged = pdfaConfig.enabled;
5773
6041
  const encryptionOpts = layoutOptions?.encryption;
5774
6042
  if (tagged && encryptionOpts) {
5775
6043
  throw new Error("PDF/A and encryption are mutually exclusive (ISO 19005-1 \xA76.3.2)");
@@ -5938,8 +6206,17 @@ function buildPDF(params, layoutOptions) {
5938
6206
  kids.push(`${pageObjStart + p * 2} 0 R`);
5939
6207
  }
5940
6208
  emitObj(2, `<< /Type /Pages /Kids [${kids.join(" ")}] /Count ${totalPages} >>`);
5941
- emitObj(3, "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>");
5942
- emitObj(4, "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding >>");
6209
+ if (tagged) {
6210
+ const pf = fontEntries[0];
6211
+ const bfName = `/${pf.fontData.fontName.replace(/[^A-Za-z0-9-]/g, "")}`;
6212
+ const primaryBase = 5;
6213
+ const refDict = `<< /Type /Font /Subtype /Type0 /BaseFont ${bfName} /Encoding /Identity-H /DescendantFonts [${primaryBase + 1} 0 R] /ToUnicode ${primaryBase + 4} 0 R >>`;
6214
+ emitObj(3, refDict);
6215
+ emitObj(4, refDict);
6216
+ } else {
6217
+ emitObj(3, "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>");
6218
+ emitObj(4, "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding >>");
6219
+ }
5943
6220
  for (let fi = 0; fi < fontEntries.length; fi++) {
5944
6221
  const fe = fontEntries[fi];
5945
6222
  const fd = fe.fontData;
@@ -6064,7 +6341,7 @@ function buildPDF(params, layoutOptions) {
6064
6341
  structTreeRootObjNum = tree.structTreeRootObjNum;
6065
6342
  totalObjs = treeStart + tree.totalObjects - 1;
6066
6343
  xmpObjNum = totalObjs + 1;
6067
- const xmpContent = buildXMPMetadata(infoTitle, isoDate, pdfaConfig.pdfaPart, pdfaConfig.pdfaConformance);
6344
+ const xmpContent = utf8EncodeBinaryString(buildXMPMetadata(infoTitle, isoDate, pdfaConfig.pdfaPart, pdfaConfig.pdfaConformance));
6068
6345
  emitStreamObj(
6069
6346
  xmpObjNum,
6070
6347
  `<< /Type /Metadata /Subtype /XML /Length ${xmpContent.length}`,
@@ -6379,7 +6656,8 @@ function buildFormWidget(field, apObjNum, radioCtx) {
6379
6656
  if (field.required) ff |= FF_REQUIRED;
6380
6657
  const parts = [
6381
6658
  "<< /Type /Annot /Subtype /Widget",
6382
- `/Rect [${fmtNum2(x1)} ${fmtNum2(y1)} ${fmtNum2(x2)} ${fmtNum2(y2)}]`
6659
+ `/Rect [${fmtNum2(x1)} ${fmtNum2(y1)} ${fmtNum2(x2)} ${fmtNum2(y2)}]`,
6660
+ "/F 4"
6383
6661
  ];
6384
6662
  if (radioCtx) {
6385
6663
  parts.push(`/Parent ${radioCtx.parentObjNum} 0 R`);
@@ -6478,13 +6756,13 @@ function buildLinkAnnotation(annot, objNum) {
6478
6756
  const [x1, y1, x2, y2] = annot.rect;
6479
6757
  const escapedUrl = escapeUrlForPdf(annot.url);
6480
6758
  return `${objNum} 0 obj
6481
- << /Type /Annot /Subtype /Link /Rect [${fmtNum(x1)} ${fmtNum(y1)} ${fmtNum(x2)} ${fmtNum(y2)}] /Border [0 0 0] /A << /Type /Action /S /URI /URI (${escapedUrl}) >> >>
6759
+ << /Type /Annot /Subtype /Link /Rect [${fmtNum(x1)} ${fmtNum(y1)} ${fmtNum(x2)} ${fmtNum(y2)}] /Border [0 0 0] /F 4 /A << /Type /Action /S /URI /URI (${escapedUrl}) >> >>
6482
6760
  endobj`;
6483
6761
  }
6484
6762
  function buildInternalLinkAnnotation(annot, pageObjNum, objNum) {
6485
6763
  const [x1, y1, x2, y2] = annot.rect;
6486
6764
  return `${objNum} 0 obj
6487
- << /Type /Annot /Subtype /Link /Rect [${fmtNum(x1)} ${fmtNum(y1)} ${fmtNum(x2)} ${fmtNum(y2)}] /Border [0 0 0] /A << /Type /Action /S /GoTo /D [${pageObjNum} 0 R /Fit] >> >>
6765
+ << /Type /Annot /Subtype /Link /Rect [${fmtNum(x1)} ${fmtNum(y1)} ${fmtNum(x2)} ${fmtNum(y2)}] /Border [0 0 0] /F 4 /A << /Type /Action /S /GoTo /D [${pageObjNum} 0 R /Fit] >> >>
6488
6766
  endobj`;
6489
6767
  }
6490
6768
  function isLinkAnnotation(annot) {
@@ -8416,6 +8694,39 @@ function renderSvg(data, x, y, w, h, options) {
8416
8694
  return allOps.join("\n");
8417
8695
  }
8418
8696
 
8697
+ // src/core/pdf-column-fit.ts
8698
+ var CELL_PAD_LEFT = 3;
8699
+ var CELL_PAD_RIGHT = 3;
8700
+ var CELL_PAD_TOTAL = CELL_PAD_LEFT + CELL_PAD_RIGHT;
8701
+ function computeAutoFitColumns(columns, headers, rows, enc, thSize, tdSize) {
8702
+ const n = columns.length;
8703
+ if (n === 0) return [];
8704
+ const desired = new Array(n).fill(0);
8705
+ for (let i = 0; i < n; i++) {
8706
+ let max = 0;
8707
+ const hdr = headers[i];
8708
+ if (hdr) {
8709
+ const w = enc.tw(hdr, thSize);
8710
+ if (w > max) max = w;
8711
+ }
8712
+ for (const row of rows) {
8713
+ const cell = row.cells[i];
8714
+ if (!cell) continue;
8715
+ const w = enc.tw(cell, tdSize);
8716
+ if (w > max) max = w;
8717
+ }
8718
+ desired[i] = max + CELL_PAD_TOTAL;
8719
+ }
8720
+ let total = 0;
8721
+ for (let i = 0; i < n; i++) total += desired[i];
8722
+ if (total <= 0) return columns.slice();
8723
+ const out = new Array(n);
8724
+ for (let i = 0; i < n; i++) {
8725
+ out[i] = { ...columns[i], f: desired[i] / total };
8726
+ }
8727
+ return out;
8728
+ }
8729
+
8419
8730
  // src/core/pdf-renderers.ts
8420
8731
  var HEADING_SIZES = { 1: 18, 2: 14, 3: 11 };
8421
8732
  var HEADING_SPACING = {
@@ -8635,10 +8946,15 @@ function renderList(block, y, enc, mgL, cw, tagCtx, documentChildren) {
8635
8946
  }
8636
8947
  function renderTable(block, y, enc, mgL, mgR, pgW, cw, tagCtx, documentChildren) {
8637
8948
  const ops = [];
8638
- const columns = block.columns ? [...block.columns] : DEFAULT_COLUMNS;
8949
+ const baseColumns = block.columns ? [...block.columns] : DEFAULT_COLUMNS;
8639
8950
  const fs = DEFAULT_FONT_SIZES;
8640
8951
  const colors = DEFAULT_COLORS;
8952
+ const columns = block.autoFitColumns ? computeAutoFitColumns(baseColumns, block.headers, block.rows, enc, fs.th, fs.td) : baseColumns;
8641
8953
  const { cx, cwi } = computeColumnPositions(columns, mgL, cw);
8954
+ const clip = block.clipCells !== false;
8955
+ const clipCell = (op, i, top, h) => clip ? `q ${fmtNum(cx[i])} ${fmtNum(top - h)} ${fmtNum(cwi[i])} ${fmtNum(h)} re W n
8956
+ ${op}
8957
+ Q` : op;
8642
8958
  const tableRows = [];
8643
8959
  ops.push(`${colors.thBg} rg`);
8644
8960
  ops.push(`${fmtNum(mgL)} ${fmtNum(y - TH_H)} ${fmtNum(cw)} ${fmtNum(TH_H)} re f`);
@@ -8652,19 +8968,19 @@ function renderTable(block, y, enc, mgL, mgR, pgW, cw, tagCtx, documentChildren)
8652
8968
  const mcid = tagCtx.mcidAlloc.next(tagCtx.pageObjNum);
8653
8969
  thChildren.push({ type: "TH", children: [{ mcid, pageObjNum: tagCtx.pageObjNum }] });
8654
8970
  if (columns[i].a === "r") {
8655
- ops.push(txtRTagged(t, cx[i] + cwi[i] - 3, y - TH_H + 4, enc.f2, fs.th, enc, mcid));
8971
+ ops.push(clipCell(txtRTagged(t, cx[i] + cwi[i] - 3, y - TH_H + 4, enc.f2, fs.th, enc, mcid), i, y, TH_H));
8656
8972
  } else if (columns[i].a === "c") {
8657
- ops.push(txtCTagged(t, cx[i], y - TH_H + 4, enc.f2, fs.th, cwi[i], enc, mcid));
8973
+ ops.push(clipCell(txtCTagged(t, cx[i], y - TH_H + 4, enc.f2, fs.th, cwi[i], enc, mcid), i, y, TH_H));
8658
8974
  } else {
8659
- ops.push(txtTagged(t, cx[i] + 3, y - TH_H + 4, enc.f2, fs.th, enc, mcid));
8975
+ ops.push(clipCell(txtTagged(t, cx[i] + 3, y - TH_H + 4, enc.f2, fs.th, enc, mcid), i, y, TH_H));
8660
8976
  }
8661
8977
  } else {
8662
8978
  if (columns[i].a === "r") {
8663
- ops.push(txtR(t, cx[i] + cwi[i] - 3, y - TH_H + 4, enc.f2, fs.th, enc));
8979
+ ops.push(clipCell(txtR(t, cx[i] + cwi[i] - 3, y - TH_H + 4, enc.f2, fs.th, enc), i, y, TH_H));
8664
8980
  } else if (columns[i].a === "c") {
8665
- ops.push(txtC(t, cx[i], y - TH_H + 4, enc.f2, fs.th, cwi[i], enc));
8981
+ ops.push(clipCell(txtC(t, cx[i], y - TH_H + 4, enc.f2, fs.th, cwi[i], enc), i, y, TH_H));
8666
8982
  } else {
8667
- ops.push(txt(t, cx[i] + 3, y - TH_H + 4, enc.f2, fs.th, enc));
8983
+ ops.push(clipCell(txt(t, cx[i] + 3, y - TH_H + 4, enc.f2, fs.th, enc), i, y, TH_H));
8668
8984
  }
8669
8985
  }
8670
8986
  }
@@ -8684,19 +9000,19 @@ function renderTable(block, y, enc, mgL, mgR, pgW, cw, tagCtx, documentChildren)
8684
9000
  const mcid = tagCtx.mcidAlloc.next(tagCtx.pageObjNum);
8685
9001
  tdChildren.push({ type: "TD", children: [{ mcid, pageObjNum: tagCtx.pageObjNum }] });
8686
9002
  if (columns[i].a === "r") {
8687
- ops.push(txtRTagged(t, cx[i] + cwi[i] - 3, y - ROW_H + 3, font, fs.td, enc, mcid));
9003
+ ops.push(clipCell(txtRTagged(t, cx[i] + cwi[i] - 3, y - ROW_H + 3, font, fs.td, enc, mcid), i, y, ROW_H));
8688
9004
  } else if (columns[i].a === "c") {
8689
- ops.push(txtCTagged(t, cx[i], y - ROW_H + 3, font, fs.td, cwi[i], enc, mcid));
9005
+ ops.push(clipCell(txtCTagged(t, cx[i], y - ROW_H + 3, font, fs.td, cwi[i], enc, mcid), i, y, ROW_H));
8690
9006
  } else {
8691
- ops.push(txtTagged(t, cx[i] + 3, y - ROW_H + 3, font, fs.td, enc, mcid));
9007
+ ops.push(clipCell(txtTagged(t, cx[i] + 3, y - ROW_H + 3, font, fs.td, enc, mcid), i, y, ROW_H));
8692
9008
  }
8693
9009
  } else {
8694
9010
  if (columns[i].a === "r") {
8695
- ops.push(txtR(t, cx[i] + cwi[i] - 3, y - ROW_H + 3, font, fs.td, enc));
9011
+ ops.push(clipCell(txtR(t, cx[i] + cwi[i] - 3, y - ROW_H + 3, font, fs.td, enc), i, y, ROW_H));
8696
9012
  } else if (columns[i].a === "c") {
8697
- ops.push(txtC(t, cx[i], y - ROW_H + 3, font, fs.td, cwi[i], enc));
9013
+ ops.push(clipCell(txtC(t, cx[i], y - ROW_H + 3, font, fs.td, cwi[i], enc), i, y, ROW_H));
8698
9014
  } else {
8699
- ops.push(txt(t, cx[i] + 3, y - ROW_H + 3, font, fs.td, enc));
9015
+ ops.push(clipCell(txt(t, cx[i] + 3, y - ROW_H + 3, font, fs.td, enc), i, y, ROW_H));
8700
9016
  }
8701
9017
  }
8702
9018
  }
@@ -8875,10 +9191,11 @@ function renderToc(tocBlock, headings, y, enc, mgL, cw, pageIndex, pageAnnotatio
8875
9191
  const availTextW = dotLeaderEnd - entryX - 8;
8876
9192
  let displayText = heading.text;
8877
9193
  if (measureText(displayText, sz, enc) > availTextW) {
8878
- while (displayText.length > 1 && measureText(displayText + "...", sz, enc) > availTextW) {
9194
+ const ell = "\u2026";
9195
+ while (displayText.length > 1 && measureText(displayText + ell, sz, enc) > availTextW) {
8879
9196
  displayText = displayText.slice(0, -1);
8880
9197
  }
8881
- displayText += "...";
9198
+ displayText += ell;
8882
9199
  }
8883
9200
  const textW = measureText(displayText, sz, enc);
8884
9201
  const textY = y - sz;
@@ -9123,9 +9440,9 @@ function buildDocumentPDF(params, layoutOptions) {
9123
9440
  const mg = layout?.margins ?? { ...DEFAULT_MARGINS };
9124
9441
  const cw = pgW - mg.l - mg.r;
9125
9442
  const fontEntries = params.fontEntries ? [...params.fontEntries] : [];
9126
- const enc = createEncodingContext(fontEntries);
9127
9443
  const pdfaConfig = resolvePdfAConfig(layout?.tagged);
9128
9444
  const tagged = pdfaConfig.enabled;
9445
+ const enc = createEncodingContext(fontEntries, tagged);
9129
9446
  const encryptionOpts = layout?.encryption;
9130
9447
  if (tagged && encryptionOpts) {
9131
9448
  throw new Error("PDF/A and encryption are mutually exclusive (ISO 19005-1 \xA76.3.2)");
@@ -9456,8 +9773,17 @@ function buildDocumentPDF(params, layoutOptions) {
9456
9773
  kids.push(`${pageObjStart + p * 2} 0 R`);
9457
9774
  }
9458
9775
  emitObj(2, `<< /Type /Pages /Kids [${kids.join(" ")}] /Count ${totalPages} >>`);
9459
- emitObj(3, "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>");
9460
- emitObj(4, "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding >>");
9776
+ if (tagged) {
9777
+ const pf = fontEntries[0];
9778
+ const bfName = `/${pf.fontData.fontName.replace(/[^A-Za-z0-9-]/g, "")}`;
9779
+ const primaryBase = 5;
9780
+ const refDict = `<< /Type /Font /Subtype /Type0 /BaseFont ${bfName} /Encoding /Identity-H /DescendantFonts [${primaryBase + 1} 0 R] /ToUnicode ${primaryBase + 4} 0 R >>`;
9781
+ emitObj(3, refDict);
9782
+ emitObj(4, refDict);
9783
+ } else {
9784
+ emitObj(3, "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>");
9785
+ emitObj(4, "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding >>");
9786
+ }
9461
9787
  for (let fi = 0; fi < fontEntries.length; fi++) {
9462
9788
  const fe = fontEntries[fi];
9463
9789
  const fd = fe.fontData;
@@ -9551,13 +9877,13 @@ function buildDocumentPDF(params, layoutOptions) {
9551
9877
  const destName = pa.annot.url.slice(1);
9552
9878
  emitObj(
9553
9879
  objNum,
9554
- `<< /Type /Annot /Subtype /Link /Rect [${fmtNum(x1)} ${fmtNum(y1)} ${fmtNum(x2)} ${fmtNum(y2)}] /Border [0 0 0] /Dest /${destName} >>`
9880
+ `<< /Type /Annot /Subtype /Link /Rect [${fmtNum(x1)} ${fmtNum(y1)} ${fmtNum(x2)} ${fmtNum(y2)}] /Border [0 0 0] /F 4 /Dest /${destName} >>`
9555
9881
  );
9556
9882
  } else {
9557
9883
  const escapedUrl = pa.annot.url.replace(/\\/g, "\\\\").replace(/\(/g, "\\(").replace(/\)/g, "\\)");
9558
9884
  emitObj(
9559
9885
  objNum,
9560
- `<< /Type /Annot /Subtype /Link /Rect [${fmtNum(x1)} ${fmtNum(y1)} ${fmtNum(x2)} ${fmtNum(y2)}] /Border [0 0 0] /A << /Type /Action /S /URI /URI (${escapedUrl}) >> >>`
9886
+ `<< /Type /Annot /Subtype /Link /Rect [${fmtNum(x1)} ${fmtNum(y1)} ${fmtNum(x2)} ${fmtNum(y2)}] /Border [0 0 0] /F 4 /A << /Type /Action /S /URI /URI (${escapedUrl}) >> >>`
9561
9887
  );
9562
9888
  }
9563
9889
  annotIdx++;
@@ -9674,13 +10000,13 @@ function buildDocumentPDF(params, layoutOptions) {
9674
10000
  const destName = pa.annot.url.slice(1);
9675
10001
  emitObj(
9676
10002
  objNum,
9677
- `<< /Type /Annot /Subtype /Link /Rect [${fmtNum(x1)} ${fmtNum(y1)} ${fmtNum(x2)} ${fmtNum(y2)}] /Border [0 0 0] /Dest /${destName} >>`
10003
+ `<< /Type /Annot /Subtype /Link /Rect [${fmtNum(x1)} ${fmtNum(y1)} ${fmtNum(x2)} ${fmtNum(y2)}] /Border [0 0 0] /F 4 /Dest /${destName} >>`
9678
10004
  );
9679
10005
  } else {
9680
10006
  const escapedUrl = pa.annot.url.replace(/\\/g, "\\\\").replace(/\(/g, "\\(").replace(/\)/g, "\\)");
9681
10007
  emitObj(
9682
10008
  objNum,
9683
- `<< /Type /Annot /Subtype /Link /Rect [${fmtNum(x1)} ${fmtNum(y1)} ${fmtNum(x2)} ${fmtNum(y2)}] /Border [0 0 0] /A << /Type /Action /S /URI /URI (${escapedUrl}) >> >>`
10009
+ `<< /Type /Annot /Subtype /Link /Rect [${fmtNum(x1)} ${fmtNum(y1)} ${fmtNum(x2)} ${fmtNum(y2)}] /Border [0 0 0] /F 4 /A << /Type /Action /S /URI /URI (${escapedUrl}) >> >>`
9684
10010
  );
9685
10011
  }
9686
10012
  annotIdx++;
@@ -9757,7 +10083,7 @@ function buildDocumentPDF(params, layoutOptions) {
9757
10083
  structTreeRootObjNum = tree.structTreeRootObjNum;
9758
10084
  totalObjs = treeStart + tree.totalObjects - 1;
9759
10085
  xmpObjNum = totalObjs + 1;
9760
- const xmpContent = buildXMPMetadata(infoTitle, isoDate, pdfaConfig.pdfaPart, pdfaConfig.pdfaConformance, params.metadata?.author);
10086
+ const xmpContent = utf8EncodeBinaryString(buildXMPMetadata(infoTitle, isoDate, pdfaConfig.pdfaPart, pdfaConfig.pdfaConformance, params.metadata?.author, params.metadata?.subject, params.metadata?.keywords));
9761
10087
  emitStreamObj(
9762
10088
  xmpObjNum,
9763
10089
  `<< /Type /Metadata /Subtype /XML /Length ${xmpContent.length}`,
@@ -11255,6 +11581,185 @@ function getTrailerRef(trailer, key) {
11255
11581
  return void 0;
11256
11582
  }
11257
11583
 
11584
+ // src/parser/pdf-decode-filters.ts
11585
+ var MAX_DECODE_OUTPUT = 256 * 1024 * 1024;
11586
+ function checkOutputSize(n, filter) {
11587
+ if (n > MAX_DECODE_OUTPUT) {
11588
+ throw new Error(
11589
+ `${filter} output exceeds ${MAX_DECODE_OUTPUT} bytes (possible zip-bomb)`
11590
+ );
11591
+ }
11592
+ }
11593
+ function decodeASCIIHex(data) {
11594
+ const out = [];
11595
+ let nibble = -1;
11596
+ for (let i = 0; i < data.length; i++) {
11597
+ const c = data[i];
11598
+ if (c === 62) break;
11599
+ if (c === 0 || c === 9 || c === 10 || c === 12 || c === 13 || c === 32) continue;
11600
+ let v;
11601
+ if (c >= 48 && c <= 57) v = c - 48;
11602
+ else if (c >= 65 && c <= 70) v = c - 65 + 10;
11603
+ else if (c >= 97 && c <= 102) v = c - 97 + 10;
11604
+ else throw new Error(`ASCIIHexDecode: invalid character 0x${c.toString(16)}`);
11605
+ if (nibble < 0) {
11606
+ nibble = v;
11607
+ } else {
11608
+ out.push(nibble << 4 | v);
11609
+ checkOutputSize(out.length, "ASCIIHexDecode");
11610
+ nibble = -1;
11611
+ }
11612
+ }
11613
+ if (nibble >= 0) out.push(nibble << 4);
11614
+ return Uint8Array.from(out);
11615
+ }
11616
+ function decodeASCII85(data) {
11617
+ const out = [];
11618
+ let group = 0;
11619
+ let count = 0;
11620
+ for (let i = 0; i < data.length; i++) {
11621
+ const c = data[i];
11622
+ if (c === 126) {
11623
+ if (i + 1 < data.length && data[i + 1] === 62) break;
11624
+ throw new Error("ASCII85Decode: lone ~ without >");
11625
+ }
11626
+ if (c === 62) break;
11627
+ if (c === 0 || c === 9 || c === 10 || c === 12 || c === 13 || c === 32) continue;
11628
+ if (c === 122) {
11629
+ if (count !== 0) throw new Error("ASCII85Decode: z inside group");
11630
+ out.push(0, 0, 0, 0);
11631
+ checkOutputSize(out.length, "ASCII85Decode");
11632
+ continue;
11633
+ }
11634
+ if (c < 33 || c > 117) throw new Error(`ASCII85Decode: invalid char 0x${c.toString(16)}`);
11635
+ group = group * 85 + (c - 33);
11636
+ count++;
11637
+ if (count === 5) {
11638
+ if (group > 4294967295) throw new Error("ASCII85Decode: group overflow");
11639
+ out.push(group >>> 24 & 255, group >>> 16 & 255, group >>> 8 & 255, group & 255);
11640
+ checkOutputSize(out.length, "ASCII85Decode");
11641
+ group = 0;
11642
+ count = 0;
11643
+ }
11644
+ }
11645
+ if (count > 0) {
11646
+ for (let k = count; k < 5; k++) group = group * 85 + 84;
11647
+ if (group > 4294967295) throw new Error("ASCII85Decode: trailing group overflow");
11648
+ const tail = [group >>> 24 & 255, group >>> 16 & 255, group >>> 8 & 255, group & 255];
11649
+ for (let k = 0; k < count - 1; k++) out.push(tail[k]);
11650
+ checkOutputSize(out.length, "ASCII85Decode");
11651
+ }
11652
+ return Uint8Array.from(out);
11653
+ }
11654
+ var LZW_CLEAR_CODE = 256;
11655
+ var LZW_EOD_CODE = 257;
11656
+ function decodeLZW(data) {
11657
+ const out = [];
11658
+ let bitBuf = 0;
11659
+ let bitCount = 0;
11660
+ let p = 0;
11661
+ let codeSize = 9;
11662
+ let dict = [];
11663
+ let prev = null;
11664
+ const resetDict = () => {
11665
+ dict = new Array(258);
11666
+ for (let i = 0; i < 256; i++) dict[i] = Uint8Array.of(i);
11667
+ codeSize = 9;
11668
+ prev = null;
11669
+ };
11670
+ resetDict();
11671
+ const readCode = () => {
11672
+ while (bitCount < codeSize) {
11673
+ if (p >= data.length) return -1;
11674
+ bitBuf = bitBuf << 8 | data[p++];
11675
+ bitCount += 8;
11676
+ }
11677
+ const shift = bitCount - codeSize;
11678
+ const code = bitBuf >>> shift & (1 << codeSize) - 1;
11679
+ bitBuf &= (1 << shift) - 1;
11680
+ bitCount = shift;
11681
+ return code;
11682
+ };
11683
+ for (; ; ) {
11684
+ const code = readCode();
11685
+ if (code < 0 || code === LZW_EOD_CODE) break;
11686
+ if (code === LZW_CLEAR_CODE) {
11687
+ resetDict();
11688
+ continue;
11689
+ }
11690
+ let entry;
11691
+ if (code < dict.length) {
11692
+ entry = dict[code];
11693
+ } else if (code === dict.length && prev) {
11694
+ entry = new Uint8Array(prev.length + 1);
11695
+ entry.set(prev);
11696
+ entry[prev.length] = prev[0];
11697
+ } else {
11698
+ throw new Error(`LZWDecode: invalid code ${code}`);
11699
+ }
11700
+ for (let i = 0; i < entry.length; i++) out.push(entry[i]);
11701
+ checkOutputSize(out.length, "LZWDecode");
11702
+ if (prev) {
11703
+ const next = new Uint8Array(prev.length + 1);
11704
+ next.set(prev);
11705
+ next[prev.length] = entry[0];
11706
+ dict.push(next);
11707
+ if (dict.length === (1 << codeSize) - 1 && codeSize < 12) codeSize++;
11708
+ }
11709
+ prev = entry;
11710
+ }
11711
+ return Uint8Array.from(out);
11712
+ }
11713
+ function decodeRunLength(data) {
11714
+ const out = [];
11715
+ let p = 0;
11716
+ while (p < data.length) {
11717
+ const n = data[p++];
11718
+ if (n === 128) break;
11719
+ if (n < 128) {
11720
+ const len = n + 1;
11721
+ if (p + len > data.length) throw new Error("RunLengthDecode: truncated literal");
11722
+ for (let i = 0; i < len; i++) out.push(data[p + i]);
11723
+ p += len;
11724
+ } else {
11725
+ if (p >= data.length) throw new Error("RunLengthDecode: truncated repeat");
11726
+ const v = data[p++];
11727
+ const len = 257 - n;
11728
+ for (let i = 0; i < len; i++) out.push(v);
11729
+ }
11730
+ checkOutputSize(out.length, "RunLengthDecode");
11731
+ }
11732
+ return Uint8Array.from(out);
11733
+ }
11734
+ function applyDecodeFilter(name, data) {
11735
+ switch (name) {
11736
+ case "ASCIIHexDecode":
11737
+ case "AHx":
11738
+ return decodeASCIIHex(data);
11739
+ case "ASCII85Decode":
11740
+ case "A85":
11741
+ return decodeASCII85(data);
11742
+ case "LZWDecode":
11743
+ case "LZW":
11744
+ return decodeLZW(data);
11745
+ case "RunLengthDecode":
11746
+ case "RL":
11747
+ return decodeRunLength(data);
11748
+ default:
11749
+ return data;
11750
+ }
11751
+ }
11752
+ var KNOWN_DECODE_FILTERS = /* @__PURE__ */ new Set([
11753
+ "ASCIIHexDecode",
11754
+ "AHx",
11755
+ "ASCII85Decode",
11756
+ "A85",
11757
+ "LZWDecode",
11758
+ "LZW",
11759
+ "RunLengthDecode",
11760
+ "RL"
11761
+ ]);
11762
+
11258
11763
  // src/parser/pdf-reader.ts
11259
11764
  function openPdf(bytes) {
11260
11765
  const xref = parseXrefTable(bytes);
@@ -11314,10 +11819,15 @@ function openPdf(bytes) {
11314
11819
  data = decodePNGPredictor(data, decodeParms);
11315
11820
  }
11316
11821
  }
11822
+ } else if (filterName !== void 0 && KNOWN_DECODE_FILTERS.has(filterName)) {
11823
+ data = applyDecodeFilter(filterName, data);
11317
11824
  } else if (filter !== void 0 && isArray(filter)) {
11318
11825
  for (const f of filter) {
11319
- if (isName(f) && f.value === "FlateDecode") {
11826
+ if (!isName(f)) continue;
11827
+ if (f.value === "FlateDecode") {
11320
11828
  data = inflateSync(data);
11829
+ } else if (KNOWN_DECODE_FILTERS.has(f.value)) {
11830
+ data = applyDecodeFilter(f.value, data);
11321
11831
  }
11322
11832
  }
11323
11833
  }
@@ -11696,6 +12206,7 @@ exports.DEFAULT_MAX_INFLATE_OUTPUT = DEFAULT_MAX_INFLATE_OUTPUT;
11696
12206
  exports.FT_H = FT_H;
11697
12207
  exports.HEADER_H = HEADER_H;
11698
12208
  exports.INFO_LN = INFO_LN;
12209
+ exports.KNOWN_DECODE_FILTERS = KNOWN_DECODE_FILTERS;
11699
12210
  exports.MAX_PARSE_DEPTH = MAX_PARSE_DEPTH;
11700
12211
  exports.MAX_XREF_CHAIN = MAX_XREF_CHAIN;
11701
12212
  exports.PAGE_SIZES = PAGE_SIZES;
@@ -11706,6 +12217,7 @@ exports.TH_H = TH_H;
11706
12217
  exports.TITLE_LN = TITLE_LN;
11707
12218
  exports.WORKER_THRESHOLD = WORKER_THRESHOLD;
11708
12219
  exports.WORKER_TIMEOUT_MS = WORKER_TIMEOUT_MS;
12220
+ exports.applyDecodeFilter = applyDecodeFilter;
11709
12221
  exports.buildAcroFormDict = buildAcroFormDict;
11710
12222
  exports.buildAppearanceStreamDict = buildAppearanceStreamDict;
11711
12223
  exports.buildCmsSignedData = buildCmsSignedData;
@@ -11740,7 +12252,11 @@ exports.createEncodingContext = createEncodingContext;
11740
12252
  exports.createModifier = createModifier;
11741
12253
  exports.createPDF = createPDF;
11742
12254
  exports.createTokenizer = createTokenizer;
12255
+ exports.decodeASCII85 = decodeASCII85;
12256
+ exports.decodeASCIIHex = decodeASCIIHex;
11743
12257
  exports.decodeEcPublicKey = decodeEcPublicKey;
12258
+ exports.decodeLZW = decodeLZW;
12259
+ exports.decodeRunLength = decodeRunLength;
11744
12260
  exports.defaultFieldHeight = defaultFieldHeight;
11745
12261
  exports.derBitString = derBitString;
11746
12262
  exports.derDecode = derDecode;
@@ -11850,6 +12366,7 @@ exports.streamByteLength = streamByteLength;
11850
12366
  exports.toBytes = toBytes;
11851
12367
  exports.toWinAnsi = toWinAnsi;
11852
12368
  exports.truncate = truncate;
12369
+ exports.truncateToWidth = truncateToWidth;
11853
12370
  exports.validateAttachments = validateAttachments;
11854
12371
  exports.validateDocumentStreamable = validateDocumentStreamable;
11855
12372
  exports.validateTableStreamable = validateTableStreamable;