pdfnative 1.0.4 → 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
  }
@@ -5501,6 +5754,8 @@ var DEFAULT_TEXT_COLOR = "0.75 0.75 0.75";
5501
5754
  var DEFAULT_TEXT_OPACITY = 0.15;
5502
5755
  var DEFAULT_TEXT_ANGLE = -45;
5503
5756
  var DEFAULT_IMAGE_OPACITY = 0.1;
5757
+ var DEFAULT_CAP_HEIGHT_RATIO = 0.718;
5758
+ var WATERMARK_SAFETY_MARGIN = 24;
5504
5759
  function validateWatermark(watermark, pdfaLevel) {
5505
5760
  if (pdfaLevel === "pdfa1b") {
5506
5761
  const textOpacity = watermark.text?.opacity ?? DEFAULT_TEXT_OPACITY;
@@ -5546,19 +5801,35 @@ function buildWatermarkState(watermark, pgW, pgH, enc) {
5546
5801
  };
5547
5802
  }
5548
5803
  function _buildTextWatermarkOps(wm, pgW, pgH, enc, gsName) {
5549
- const sz = wm.fontSize ?? DEFAULT_TEXT_FONT_SIZE;
5804
+ let sz = wm.fontSize ?? DEFAULT_TEXT_FONT_SIZE;
5550
5805
  const color = parseColor(wm.color ?? DEFAULT_TEXT_COLOR);
5551
5806
  const angle = (wm.angle ?? DEFAULT_TEXT_ANGLE) * DEG_TO_RAD;
5552
5807
  const cos = Math.cos(angle);
5553
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
+ }
5554
5825
  const cx = pgW / 2;
5555
5826
  const cy = pgH / 2;
5556
5827
  const textWidth = enc.tw(wm.text, sz);
5557
5828
  const offsetX = -textWidth / 2;
5558
- const offsetY = -sz / 2;
5829
+ const offsetY = -sz * capRatio / 2;
5559
5830
  const tx = cx + offsetX * cos - offsetY * sin;
5560
5831
  const ty = cy + offsetX * sin + offsetY * cos;
5561
- const escapedText = pdfString(wm.text);
5832
+ const escapedText = enc.ps(wm.text);
5562
5833
  const ops = [
5563
5834
  "q",
5564
5835
  `${gsName} gs`,
@@ -5743,7 +6014,9 @@ function buildPDF(params, layoutOptions) {
5743
6014
  const fs = layoutOptions?.fontSizes ? { ...DEFAULT_FONT_SIZES, ...layoutOptions.fontSizes } : DEFAULT_FONT_SIZES;
5744
6015
  const { cx, cwi } = computeColumnPositions(columns, mg.l, cw);
5745
6016
  const fontEntries = params.fontEntries || (fontData ? [{ fontData, fontRef: "/F3", lang: "unknown" }] : []);
5746
- const enc = createEncodingContext(fontEntries);
6017
+ const pdfaConfig = resolvePdfAConfig(layoutOptions?.tagged);
6018
+ const tagged = pdfaConfig.enabled;
6019
+ const enc = createEncodingContext(fontEntries, tagged);
5747
6020
  const footerTpl = layoutOptions?.footerTemplate ?? {
5748
6021
  left: footerText || void 0,
5749
6022
  right: "{page}/{pages}"
@@ -5765,8 +6038,6 @@ function buildPDF(params, layoutOptions) {
5765
6038
  totalPages = 1 + Math.ceil((totalRows - rowsPage1) / rowsPerPage);
5766
6039
  }
5767
6040
  if (totalPages < 1) totalPages = 1;
5768
- const pdfaConfig = resolvePdfAConfig(layoutOptions?.tagged);
5769
- const tagged = pdfaConfig.enabled;
5770
6041
  const encryptionOpts = layoutOptions?.encryption;
5771
6042
  if (tagged && encryptionOpts) {
5772
6043
  throw new Error("PDF/A and encryption are mutually exclusive (ISO 19005-1 \xA76.3.2)");
@@ -5935,8 +6206,17 @@ function buildPDF(params, layoutOptions) {
5935
6206
  kids.push(`${pageObjStart + p * 2} 0 R`);
5936
6207
  }
5937
6208
  emitObj(2, `<< /Type /Pages /Kids [${kids.join(" ")}] /Count ${totalPages} >>`);
5938
- emitObj(3, "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>");
5939
- 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
+ }
5940
6220
  for (let fi = 0; fi < fontEntries.length; fi++) {
5941
6221
  const fe = fontEntries[fi];
5942
6222
  const fd = fe.fontData;
@@ -6061,7 +6341,7 @@ function buildPDF(params, layoutOptions) {
6061
6341
  structTreeRootObjNum = tree.structTreeRootObjNum;
6062
6342
  totalObjs = treeStart + tree.totalObjects - 1;
6063
6343
  xmpObjNum = totalObjs + 1;
6064
- const xmpContent = buildXMPMetadata(infoTitle, isoDate, pdfaConfig.pdfaPart, pdfaConfig.pdfaConformance);
6344
+ const xmpContent = utf8EncodeBinaryString(buildXMPMetadata(infoTitle, isoDate, pdfaConfig.pdfaPart, pdfaConfig.pdfaConformance));
6065
6345
  emitStreamObj(
6066
6346
  xmpObjNum,
6067
6347
  `<< /Type /Metadata /Subtype /XML /Length ${xmpContent.length}`,
@@ -6376,7 +6656,8 @@ function buildFormWidget(field, apObjNum, radioCtx) {
6376
6656
  if (field.required) ff |= FF_REQUIRED;
6377
6657
  const parts = [
6378
6658
  "<< /Type /Annot /Subtype /Widget",
6379
- `/Rect [${fmtNum2(x1)} ${fmtNum2(y1)} ${fmtNum2(x2)} ${fmtNum2(y2)}]`
6659
+ `/Rect [${fmtNum2(x1)} ${fmtNum2(y1)} ${fmtNum2(x2)} ${fmtNum2(y2)}]`,
6660
+ "/F 4"
6380
6661
  ];
6381
6662
  if (radioCtx) {
6382
6663
  parts.push(`/Parent ${radioCtx.parentObjNum} 0 R`);
@@ -6475,13 +6756,13 @@ function buildLinkAnnotation(annot, objNum) {
6475
6756
  const [x1, y1, x2, y2] = annot.rect;
6476
6757
  const escapedUrl = escapeUrlForPdf(annot.url);
6477
6758
  return `${objNum} 0 obj
6478
- << /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}) >> >>
6479
6760
  endobj`;
6480
6761
  }
6481
6762
  function buildInternalLinkAnnotation(annot, pageObjNum, objNum) {
6482
6763
  const [x1, y1, x2, y2] = annot.rect;
6483
6764
  return `${objNum} 0 obj
6484
- << /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] >> >>
6485
6766
  endobj`;
6486
6767
  }
6487
6768
  function isLinkAnnotation(annot) {
@@ -8413,6 +8694,39 @@ function renderSvg(data, x, y, w, h, options) {
8413
8694
  return allOps.join("\n");
8414
8695
  }
8415
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
+
8416
8730
  // src/core/pdf-renderers.ts
8417
8731
  var HEADING_SIZES = { 1: 18, 2: 14, 3: 11 };
8418
8732
  var HEADING_SPACING = {
@@ -8632,10 +8946,15 @@ function renderList(block, y, enc, mgL, cw, tagCtx, documentChildren) {
8632
8946
  }
8633
8947
  function renderTable(block, y, enc, mgL, mgR, pgW, cw, tagCtx, documentChildren) {
8634
8948
  const ops = [];
8635
- const columns = block.columns ? [...block.columns] : DEFAULT_COLUMNS;
8949
+ const baseColumns = block.columns ? [...block.columns] : DEFAULT_COLUMNS;
8636
8950
  const fs = DEFAULT_FONT_SIZES;
8637
8951
  const colors = DEFAULT_COLORS;
8952
+ const columns = block.autoFitColumns ? computeAutoFitColumns(baseColumns, block.headers, block.rows, enc, fs.th, fs.td) : baseColumns;
8638
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;
8639
8958
  const tableRows = [];
8640
8959
  ops.push(`${colors.thBg} rg`);
8641
8960
  ops.push(`${fmtNum(mgL)} ${fmtNum(y - TH_H)} ${fmtNum(cw)} ${fmtNum(TH_H)} re f`);
@@ -8649,19 +8968,19 @@ function renderTable(block, y, enc, mgL, mgR, pgW, cw, tagCtx, documentChildren)
8649
8968
  const mcid = tagCtx.mcidAlloc.next(tagCtx.pageObjNum);
8650
8969
  thChildren.push({ type: "TH", children: [{ mcid, pageObjNum: tagCtx.pageObjNum }] });
8651
8970
  if (columns[i].a === "r") {
8652
- 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));
8653
8972
  } else if (columns[i].a === "c") {
8654
- 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));
8655
8974
  } else {
8656
- 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));
8657
8976
  }
8658
8977
  } else {
8659
8978
  if (columns[i].a === "r") {
8660
- 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));
8661
8980
  } else if (columns[i].a === "c") {
8662
- 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));
8663
8982
  } else {
8664
- 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));
8665
8984
  }
8666
8985
  }
8667
8986
  }
@@ -8681,19 +9000,19 @@ function renderTable(block, y, enc, mgL, mgR, pgW, cw, tagCtx, documentChildren)
8681
9000
  const mcid = tagCtx.mcidAlloc.next(tagCtx.pageObjNum);
8682
9001
  tdChildren.push({ type: "TD", children: [{ mcid, pageObjNum: tagCtx.pageObjNum }] });
8683
9002
  if (columns[i].a === "r") {
8684
- 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));
8685
9004
  } else if (columns[i].a === "c") {
8686
- 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));
8687
9006
  } else {
8688
- 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));
8689
9008
  }
8690
9009
  } else {
8691
9010
  if (columns[i].a === "r") {
8692
- 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));
8693
9012
  } else if (columns[i].a === "c") {
8694
- 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));
8695
9014
  } else {
8696
- 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));
8697
9016
  }
8698
9017
  }
8699
9018
  }
@@ -8872,10 +9191,11 @@ function renderToc(tocBlock, headings, y, enc, mgL, cw, pageIndex, pageAnnotatio
8872
9191
  const availTextW = dotLeaderEnd - entryX - 8;
8873
9192
  let displayText = heading.text;
8874
9193
  if (measureText(displayText, sz, enc) > availTextW) {
8875
- while (displayText.length > 1 && measureText(displayText + "...", sz, enc) > availTextW) {
9194
+ const ell = "\u2026";
9195
+ while (displayText.length > 1 && measureText(displayText + ell, sz, enc) > availTextW) {
8876
9196
  displayText = displayText.slice(0, -1);
8877
9197
  }
8878
- displayText += "...";
9198
+ displayText += ell;
8879
9199
  }
8880
9200
  const textW = measureText(displayText, sz, enc);
8881
9201
  const textY = y - sz;
@@ -9120,9 +9440,9 @@ function buildDocumentPDF(params, layoutOptions) {
9120
9440
  const mg = layout?.margins ?? { ...DEFAULT_MARGINS };
9121
9441
  const cw = pgW - mg.l - mg.r;
9122
9442
  const fontEntries = params.fontEntries ? [...params.fontEntries] : [];
9123
- const enc = createEncodingContext(fontEntries);
9124
9443
  const pdfaConfig = resolvePdfAConfig(layout?.tagged);
9125
9444
  const tagged = pdfaConfig.enabled;
9445
+ const enc = createEncodingContext(fontEntries, tagged);
9126
9446
  const encryptionOpts = layout?.encryption;
9127
9447
  if (tagged && encryptionOpts) {
9128
9448
  throw new Error("PDF/A and encryption are mutually exclusive (ISO 19005-1 \xA76.3.2)");
@@ -9453,8 +9773,17 @@ function buildDocumentPDF(params, layoutOptions) {
9453
9773
  kids.push(`${pageObjStart + p * 2} 0 R`);
9454
9774
  }
9455
9775
  emitObj(2, `<< /Type /Pages /Kids [${kids.join(" ")}] /Count ${totalPages} >>`);
9456
- emitObj(3, "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica /Encoding /WinAnsiEncoding >>");
9457
- 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
+ }
9458
9787
  for (let fi = 0; fi < fontEntries.length; fi++) {
9459
9788
  const fe = fontEntries[fi];
9460
9789
  const fd = fe.fontData;
@@ -9548,13 +9877,13 @@ function buildDocumentPDF(params, layoutOptions) {
9548
9877
  const destName = pa.annot.url.slice(1);
9549
9878
  emitObj(
9550
9879
  objNum,
9551
- `<< /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} >>`
9552
9881
  );
9553
9882
  } else {
9554
9883
  const escapedUrl = pa.annot.url.replace(/\\/g, "\\\\").replace(/\(/g, "\\(").replace(/\)/g, "\\)");
9555
9884
  emitObj(
9556
9885
  objNum,
9557
- `<< /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}) >> >>`
9558
9887
  );
9559
9888
  }
9560
9889
  annotIdx++;
@@ -9671,13 +10000,13 @@ function buildDocumentPDF(params, layoutOptions) {
9671
10000
  const destName = pa.annot.url.slice(1);
9672
10001
  emitObj(
9673
10002
  objNum,
9674
- `<< /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} >>`
9675
10004
  );
9676
10005
  } else {
9677
10006
  const escapedUrl = pa.annot.url.replace(/\\/g, "\\\\").replace(/\(/g, "\\(").replace(/\)/g, "\\)");
9678
10007
  emitObj(
9679
10008
  objNum,
9680
- `<< /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}) >> >>`
9681
10010
  );
9682
10011
  }
9683
10012
  annotIdx++;
@@ -9754,7 +10083,7 @@ function buildDocumentPDF(params, layoutOptions) {
9754
10083
  structTreeRootObjNum = tree.structTreeRootObjNum;
9755
10084
  totalObjs = treeStart + tree.totalObjects - 1;
9756
10085
  xmpObjNum = totalObjs + 1;
9757
- 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));
9758
10087
  emitStreamObj(
9759
10088
  xmpObjNum,
9760
10089
  `<< /Type /Metadata /Subtype /XML /Length ${xmpContent.length}`,
@@ -11252,6 +11581,185 @@ function getTrailerRef(trailer, key) {
11252
11581
  return void 0;
11253
11582
  }
11254
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
+
11255
11763
  // src/parser/pdf-reader.ts
11256
11764
  function openPdf(bytes) {
11257
11765
  const xref = parseXrefTable(bytes);
@@ -11311,10 +11819,15 @@ function openPdf(bytes) {
11311
11819
  data = decodePNGPredictor(data, decodeParms);
11312
11820
  }
11313
11821
  }
11822
+ } else if (filterName !== void 0 && KNOWN_DECODE_FILTERS.has(filterName)) {
11823
+ data = applyDecodeFilter(filterName, data);
11314
11824
  } else if (filter !== void 0 && isArray(filter)) {
11315
11825
  for (const f of filter) {
11316
- if (isName(f) && f.value === "FlateDecode") {
11826
+ if (!isName(f)) continue;
11827
+ if (f.value === "FlateDecode") {
11317
11828
  data = inflateSync(data);
11829
+ } else if (KNOWN_DECODE_FILTERS.has(f.value)) {
11830
+ data = applyDecodeFilter(f.value, data);
11318
11831
  }
11319
11832
  }
11320
11833
  }
@@ -11693,6 +12206,7 @@ exports.DEFAULT_MAX_INFLATE_OUTPUT = DEFAULT_MAX_INFLATE_OUTPUT;
11693
12206
  exports.FT_H = FT_H;
11694
12207
  exports.HEADER_H = HEADER_H;
11695
12208
  exports.INFO_LN = INFO_LN;
12209
+ exports.KNOWN_DECODE_FILTERS = KNOWN_DECODE_FILTERS;
11696
12210
  exports.MAX_PARSE_DEPTH = MAX_PARSE_DEPTH;
11697
12211
  exports.MAX_XREF_CHAIN = MAX_XREF_CHAIN;
11698
12212
  exports.PAGE_SIZES = PAGE_SIZES;
@@ -11703,6 +12217,7 @@ exports.TH_H = TH_H;
11703
12217
  exports.TITLE_LN = TITLE_LN;
11704
12218
  exports.WORKER_THRESHOLD = WORKER_THRESHOLD;
11705
12219
  exports.WORKER_TIMEOUT_MS = WORKER_TIMEOUT_MS;
12220
+ exports.applyDecodeFilter = applyDecodeFilter;
11706
12221
  exports.buildAcroFormDict = buildAcroFormDict;
11707
12222
  exports.buildAppearanceStreamDict = buildAppearanceStreamDict;
11708
12223
  exports.buildCmsSignedData = buildCmsSignedData;
@@ -11737,7 +12252,11 @@ exports.createEncodingContext = createEncodingContext;
11737
12252
  exports.createModifier = createModifier;
11738
12253
  exports.createPDF = createPDF;
11739
12254
  exports.createTokenizer = createTokenizer;
12255
+ exports.decodeASCII85 = decodeASCII85;
12256
+ exports.decodeASCIIHex = decodeASCIIHex;
11740
12257
  exports.decodeEcPublicKey = decodeEcPublicKey;
12258
+ exports.decodeLZW = decodeLZW;
12259
+ exports.decodeRunLength = decodeRunLength;
11741
12260
  exports.defaultFieldHeight = defaultFieldHeight;
11742
12261
  exports.derBitString = derBitString;
11743
12262
  exports.derDecode = derDecode;
@@ -11847,6 +12366,7 @@ exports.streamByteLength = streamByteLength;
11847
12366
  exports.toBytes = toBytes;
11848
12367
  exports.toWinAnsi = toWinAnsi;
11849
12368
  exports.truncate = truncate;
12369
+ exports.truncateToWidth = truncateToWidth;
11850
12370
  exports.validateAttachments = validateAttachments;
11851
12371
  exports.validateDocumentStreamable = validateDocumentStreamable;
11852
12372
  exports.validateTableStreamable = validateTableStreamable;