@reportwright/pdf 0.1.0-beta.3 → 0.1.0-beta.4

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/README.md CHANGED
@@ -322,7 +322,7 @@ PDF/A levels and the reader. The most used:
322
322
  | `createPdf(sink, o)` / `toBytes(build, o)` | `title`, `author`, `creationDate`, `compress`, `metadata`, `pdfa`, `tagged`, `encrypt`, `sign`, `form.flatten`, `signal`, `timeoutMs`, `id` ([table](#createpdfsink-options)) |
323
323
  | `pdf.embedFont(bytes, o)` | `subset`, `shaper`, `textMapping` ([Fonts](#fonts), [Shaping](#shaping)) |
324
324
  | `page.text` / `page.textBox` | `x`, `y`, `font`, `size`, `color`, `width`, `align`, `maxLines`, `fallback`, `direction`, `kerning` ([Text options](#pages)) |
325
- | `pdf.embedImage` / `page.image` | `colorSpace`, `maxDecodedBytes` / `width`, `height`, `fit`, `opacity`, `alt` ([Images](#images)) |
325
+ | `pdf.embedImage` / `page.image` | `colorSpace`, `maxDecodedBytes`, `compression` / `width`, `height`, `fit`, `opacity`, `alt` ([Images](#images)) |
326
326
  | `page.fill` / `stroke` | `fill`, `stroke`, `width`, `dash`, `cap`, `join`, `opacity`, `blendMode`, `rule` ([Vector graphics](#vector-graphics)) |
327
327
  | `encrypt` | `userPassword`, `ownerPassword`, `algorithm`, `permissions`, `encryptMetadata` ([Encryption](#encryption-1)) |
328
328
  | `pdf.sign(field, o)` | `signer`, `reason`, `location`, `name`, `certify`, `timestamp`, `subFilter`, `reserve` ([Signatures](#signatures)) |
@@ -573,10 +573,12 @@ const logo = await pdf.embedImage(fs.readFileSync('logo.png')); // alpha
573
573
  for (const y of [400, 500, 600]) page.image(logo, { x: 40, y, height: 24, opacity: 0.6 }); // written once
574
574
  ```
575
575
 
576
- - `await pdf.embedImage(bytes, { colorSpace, maxDecodedBytes })` — a JPEG or PNG, written to the file at once as an image XObject;
576
+ - `await pdf.embedImage(bytes, { colorSpace, maxDecodedBytes, compression })` — a JPEG or PNG, written to the file at once as an image XObject;
577
577
  returns a handle with `width` and `height` (pixels, after the EXIF orientation). `colorSpace`: an ICC colour space
578
578
  (`pdf.iccColorSpace`) with the image's number of components, instead of the device space. `maxDecodedBytes`: the
579
- decoded size allowed for a PNG that needs decoding (default 64 MB, 67,108,864 bytes).
579
+ decoded size allowed for a PNG that needs decoding (default 64 MB, 67,108,864 bytes). `compression`: the zlib level
580
+ for a PNG that is re-compressed (alpha or interlacing): `'default'` is 6, or 3 above 2 megapixels (about half the
581
+ time for 2–3% more bytes on photos); `'fast'` is 1; or a level 1–9. JPEGs and passed-through PNGs are not affected.
580
582
  - **JPEG**: baseline and progressive, gray, RGB and CMYK, embedded unchanged (DCTDecode, no re-encoding). Adobe's
581
583
  inverted CMYK (an APP14 "Adobe" segment) is drawn with `/Decode [1 0 1 0 1 0 1 0]`. The EXIF orientation (1–8)
582
584
  is honoured. 12-bit, lossless, hierarchical and arithmetic-coded JPEGs are refused.
@@ -596,7 +598,7 @@ for (const y of [400, 500, 600]) page.image(logo, { x: 40, y, height: 24, opacit
596
598
  PDFs: `alt` makes the image a `Figure` with that alternate text; without `alt` it is an artifact (decoration).
597
599
  - Images are deduplicated automatically: `embedImage` called again with the same bytes (and the same options) in the
598
600
  same document returns the same handle and writes nothing new (keyed by a SHA-256 of the bytes; Node's `node:crypto`,
599
- else WebCrypto, else a fast hash confirmed byte for byte). A different option (`colorSpace`, `maxDecodedBytes`)
601
+ else WebCrypto, else a fast hash confirmed byte for byte). A different option (`colorSpace`, `maxDecodedBytes`, `compression`)
600
602
  embeds it again.
601
603
  - An image drawn any number of times, on any pages, is one XObject in the file. Images are always XObjects, never
602
604
  inline images: an inline image would be repeated in every content stream and cannot have a soft mask.
package/dist/index.js CHANGED
@@ -957,29 +957,32 @@ var init_browser = __esm({
957
957
  });
958
958
 
959
959
  // src/core/deflate.js
960
- async function deflate(bytes) {
960
+ async function deflate(bytes, level = 6) {
961
961
  if (slots.free > 0) slots.free--;
962
962
  else await new Promise((ok) => slots.wait.push(
963
963
  /** @type {() => void} */
964
964
  ok
965
965
  ));
966
966
  try {
967
- return await now(bytes);
967
+ return await now(bytes, level);
968
968
  } finally {
969
969
  const next = slots.wait.shift();
970
970
  if (next) next();
971
971
  else slots.free++;
972
972
  }
973
973
  }
974
- async function now(bytes) {
975
- if (NODE_ZLIB) return new Promise((ok, fail2) => NODE_ZLIB.deflate(bytes, { level: 6 }, (e, z) => e ? fail2(e) : ok(z)));
976
- if (typeof CompressionStream === "undefined") return zlibSync(bytes, { level: 6 });
974
+ async function now(bytes, level) {
975
+ if (NODE_ZLIB) return new Promise((ok, fail2) => NODE_ZLIB.deflate(bytes, { level }, (e, z) => e ? fail2(e) : ok(z)));
976
+ if (typeof CompressionStream === "undefined" || level !== 6) return zlibSync(bytes, { level: (
977
+ /** @type {any} */
978
+ level
979
+ ) });
977
980
  return new Uint8Array(await new Response(new Blob([
978
981
  /** @type {any} */
979
982
  bytes
980
983
  ]).stream().pipeThrough(new CompressionStream("deflate"))).arrayBuffer());
981
984
  }
982
- function deflateStream() {
985
+ function deflateStream(level = 6) {
983
986
  const out = [];
984
987
  const join = () => {
985
988
  let n = 0;
@@ -993,7 +996,7 @@ function deflateStream() {
993
996
  return z;
994
997
  };
995
998
  if (NODE_ZLIB) {
996
- const z = NODE_ZLIB.createDeflate({ level: 6 });
999
+ const z = NODE_ZLIB.createDeflate({ level, chunkSize: 1 << 20 });
997
1000
  const ended = new Promise((ok, fail2) => {
998
1001
  z.on("data", (c) => out.push(c));
999
1002
  z.on("end", ok);
@@ -1017,7 +1020,19 @@ function deflateStream() {
1017
1020
  if (typeof CompressionStream === "undefined") return { async push(b) {
1018
1021
  out.push(b.slice());
1019
1022
  }, async end() {
1020
- return zlibSync(join(), { level: 6 });
1023
+ return zlibSync(join(), { level: (
1024
+ /** @type {any} */
1025
+ level
1026
+ ) });
1027
+ }, abort() {
1028
+ } };
1029
+ if (level !== 6) return { async push(b) {
1030
+ out.push(b.slice());
1031
+ }, async end() {
1032
+ return zlibSync(join(), { level: (
1033
+ /** @type {any} */
1034
+ level
1035
+ ) });
1021
1036
  }, abort() {
1022
1037
  } };
1023
1038
  const cs = new CompressionStream("deflate"), w = cs.writable.getWriter();
@@ -3432,6 +3447,7 @@ var init_links = __esm({
3432
3447
 
3433
3448
  // src/images/png.js
3434
3449
  function crc32(b, from, to) {
3450
+ if (NODE_ZLIB2?.crc32) return NODE_ZLIB2.crc32(b.subarray(from, to));
3435
3451
  if (!CRC_TABLE) {
3436
3452
  CRC_TABLE = new Uint32Array(256);
3437
3453
  for (let n = 0; n < 256; n++) {
@@ -3572,16 +3588,24 @@ function unfilterRow(f, c, p, bpp) {
3572
3588
  }
3573
3589
  for (let i2 = 0; i2 < bpp; i2++) c[i2] += p[i2];
3574
3590
  for (let i2 = bpp; i2 < rb; i2++) {
3575
- const a = c[i2 - bpp], u = p[i2], cc = p[i2 - bpp], pp = a + u - cc, pa = Math.abs(pp - a), pb = Math.abs(pp - u), pc = Math.abs(pp - cc);
3576
- c[i2] += pa <= pb && pa <= pc ? a : pb <= pc ? u : cc;
3591
+ const a = c[i2 - bpp], u = p[i2], cc = p[i2 - bpp];
3592
+ let pa = u - cc, pb = a - cc, pc = pa + pb, k;
3593
+ k = pa >> 31;
3594
+ pa = (pa ^ k) - k;
3595
+ k = pb >> 31;
3596
+ pb = (pb ^ k) - k;
3597
+ k = pc >> 31;
3598
+ pc = (pc ^ k) - k;
3599
+ c[i2] += a ^ (a ^ (u ^ (u ^ cc) & pc - pb >> 31)) & (pb - pa | pc - pa) >> 31;
3577
3600
  }
3578
3601
  }
3579
3602
  function filterRow(x2, up, bpp, out, o, t) {
3580
3603
  const rb = x2.length, m = Math.min(bpp, rb);
3581
- let best = 0, sum = 0;
3604
+ let best = 0, sum = 0, s, k;
3582
3605
  for (let i2 = 0; i2 < rb; i2++) {
3583
- const v = x2[i2];
3584
- sum += v < 128 ? v : 256 - v;
3606
+ s = x2[i2] << 24 >> 24;
3607
+ k = s >> 31;
3608
+ sum += (s ^ k) - k;
3585
3609
  }
3586
3610
  out[o] = 0;
3587
3611
  out.set(x2, o + 1);
@@ -3590,12 +3614,15 @@ function filterRow(x2, up, bpp, out, o, t) {
3590
3614
  for (let i2 = 0; i2 < m; i2++) {
3591
3615
  const v = x2[i2];
3592
3616
  t[i2] = v;
3593
- sum += v < 128 ? v : 256 - v;
3617
+ s = v << 24 >> 24;
3618
+ k = s >> 31;
3619
+ sum += (s ^ k) - k;
3594
3620
  }
3595
3621
  for (let i2 = m; i2 < rb; i2++) {
3596
- const v = x2[i2] - x2[i2 - bpp] & 255;
3597
- t[i2] = v;
3598
- sum += v < 128 ? v : 256 - v;
3622
+ s = x2[i2] - x2[i2 - bpp] << 24 >> 24;
3623
+ t[i2] = s;
3624
+ k = s >> 31;
3625
+ sum += (s ^ k) - k;
3599
3626
  }
3600
3627
  if (sum < best) {
3601
3628
  best = sum;
@@ -3605,9 +3632,10 @@ function filterRow(x2, up, bpp, out, o, t) {
3605
3632
  if (!up) return;
3606
3633
  sum = 0;
3607
3634
  for (let i2 = 0; i2 < rb; i2++) {
3608
- const v = x2[i2] - up[i2] & 255;
3609
- t[i2] = v;
3610
- sum += v < 128 ? v : 256 - v;
3635
+ s = x2[i2] - up[i2] << 24 >> 24;
3636
+ t[i2] = s;
3637
+ k = s >> 31;
3638
+ sum += (s ^ k) - k;
3611
3639
  }
3612
3640
  if (sum < best) {
3613
3641
  best = sum;
@@ -3616,15 +3644,25 @@ function filterRow(x2, up, bpp, out, o, t) {
3616
3644
  }
3617
3645
  sum = 0;
3618
3646
  for (let i2 = 0; i2 < m; i2++) {
3619
- const v = x2[i2] - up[i2] & 255;
3620
- t[i2] = v;
3621
- sum += v < 128 ? v : 256 - v;
3647
+ s = x2[i2] - up[i2] << 24 >> 24;
3648
+ t[i2] = s;
3649
+ k = s >> 31;
3650
+ sum += (s ^ k) - k;
3622
3651
  }
3623
3652
  for (let i2 = m; i2 < rb; i2++) {
3624
- const a = x2[i2 - bpp], u = up[i2], c = up[i2 - bpp], pa = Math.abs(u - c), pb = Math.abs(a - c), pc = Math.abs(a + u - c - c);
3625
- const v = x2[i2] - (pa <= pb && pa <= pc ? a : pb <= pc ? u : c) & 255;
3626
- t[i2] = v;
3627
- sum += v < 128 ? v : 256 - v;
3653
+ const a = x2[i2 - bpp], u = up[i2], c = up[i2 - bpp];
3654
+ let pa = u - c, pb = a - c, pc = pa + pb;
3655
+ k = pa >> 31;
3656
+ pa = (pa ^ k) - k;
3657
+ k = pb >> 31;
3658
+ pb = (pb ^ k) - k;
3659
+ k = pc >> 31;
3660
+ pc = (pc ^ k) - k;
3661
+ const notA = (pb - pa | pc - pa) >> 31, uc = u ^ (u ^ c) & pc - pb >> 31;
3662
+ s = x2[i2] - (a ^ (a ^ uc) & notA) << 24 >> 24;
3663
+ t[i2] = s;
3664
+ k = s >> 31;
3665
+ sum += (s ^ k) - k;
3628
3666
  }
3629
3667
  if (sum < best) {
3630
3668
  out[o] = 4;
@@ -3857,13 +3895,14 @@ async function embedImage(doc, bytes, opt = {}) {
3857
3895
  /** @type {any} */
3858
3896
  opt[k2]
3859
3897
  ) : void 0;
3860
- const o = { colorSpace: own2("colorSpace"), maxDecodedBytes: own2("maxDecodedBytes") ?? MAX_DECODED };
3898
+ const o = { colorSpace: own2("colorSpace"), maxDecodedBytes: own2("maxDecodedBytes") ?? MAX_DECODED, compression: own2("compression") ?? "default" };
3899
+ if (!(o.compression === "default" || o.compression === "fast" || Number.isInteger(o.compression) && o.compression >= 1 && o.compression <= 9)) throw new TypeError("embedImage: compression is 'default', 'fast' or a zlib level 1–9");
3861
3900
  if (!(Number.isSafeInteger(o.maxDecodedBytes) && o.maxDecodedBytes > 0)) throw new TypeError("embedImage: maxDecodedBytes is a whole number of bytes > 0");
3862
3901
  if (b.length > MAX_INPUT) throw new RangeError(`embedImage: at most ${MAX_INPUT >> 20} MB`);
3863
3902
  let seen = SEEN.get(doc);
3864
3903
  if (!seen) SEEN.set(doc, seen = /* @__PURE__ */ new Map());
3865
3904
  const { key, copy } = await digest(b);
3866
- const k = `${key} ${o.colorSpace == null ? "-" : idOf(o.colorSpace)} ${o.maxDecodedBytes}`;
3905
+ const k = `${key} ${o.colorSpace == null ? "-" : idOf(o.colorSpace)} ${o.maxDecodedBytes} ${o.compression}`;
3867
3906
  const hit = seen.get(k);
3868
3907
  if (hit && (!hit.copy || same(hit.copy, b))) return hit.img;
3869
3908
  const img = embed(doc, b, o);
@@ -3921,7 +3960,8 @@ async function embed(doc, b, o) {
3921
3960
  let color, a = null;
3922
3961
  if (p.filtered) color = [p.data, parms(ch, p.bpc)];
3923
3962
  else {
3924
- const ce = encoder(h, rb, bpp, parms(ch, p.bpc)), ae = p.alpha ? encoder(h, arb, Math.max(1, abpc >> 3), parms(1, abpc)) : null;
3963
+ const lv = o.compression === "fast" ? 1 : o.compression === "default" ? w * h > 2e6 ? 3 : 6 : o.compression;
3964
+ const ce = encoder(h, rb, bpp, parms(ch, p.bpc), lv), ae = p.alpha ? encoder(h, arb, Math.max(1, abpc >> 3), parms(1, abpc), lv) : null;
3925
3965
  try {
3926
3966
  await p.rows(async (c, al) => {
3927
3967
  await Promise.all([ce.row(c), ae && al ? ae.row(al) : null]);
@@ -3957,8 +3997,8 @@ async function embed(doc, b, o) {
3957
3997
  { ref, w: turned ? h : w, h: turned ? w : h, orient: ORIENT[orientation], alpha }
3958
3998
  );
3959
3999
  }
3960
- function encoder(rows, rb, bpp, dp) {
3961
- const z = deflateStream(), per = Math.max(1, Math.floor((1 << 20) / (rb + 1))), t = new Uint8Array(rb);
4000
+ function encoder(rows, rb, bpp, dp, level) {
4001
+ const z = deflateStream(level), per = Math.max(1, Math.floor((1 << 20) / (rb + 1))), t = new Uint8Array(rb);
3962
4002
  const plain2 = rows * rb <= 256 << 10 ? new Uint8Array(rows * rb) : null;
3963
4003
  const strips = [new Uint8Array(per * (rb + 1)), new Uint8Array(per * (rb + 1))], busy = [Promise.resolve(), Promise.resolve()];
3964
4004
  let s = 0, strip = strips[0], k = 0, y = 0;
@@ -3982,7 +4022,7 @@ function encoder(rows, rb, bpp, dp) {
3982
4022
  await busy[s ^ 1];
3983
4023
  const f = await z.end();
3984
4024
  if (!plain2) return [f, dp];
3985
- const p = await deflate(plain2);
4025
+ const p = await deflate(plain2, level);
3986
4026
  return p.length <= f.length + dp.length ? [p, ""] : [f, dp];
3987
4027
  },
3988
4028
  abort: () => z.abort()
@@ -12364,13 +12404,13 @@ function createForm(doc, opt = {}) {
12364
12404
  if (!Array.isArray(list) || list.length > MAX_OPTIONS) throw new RangeError(`${who}: options is an array of at most ${MAX_OPTIONS} options`);
12365
12405
  const P = placeOf(o, who), L = lookOf(o, who, { border: 1 }), tip = tipOf(o, who);
12366
12406
  const { t, da } = textOf(P.page, o, who), q = qOf(o.align, who);
12367
- let chars2 = 0;
12407
+ let chars3 = 0;
12368
12408
  const index = /* @__PURE__ */ new Map();
12369
12409
  const opts = list.map((it, i2) => {
12370
12410
  const pair = Array.isArray(it) ? it : [it, it];
12371
12411
  if (pair.length !== 2) throw new TypeError(`${who}: an option is a string or [export, display]`);
12372
12412
  const [ex, dp] = pair.map((v) => str2(v, "an option", MAX_TOOLTIP, who));
12373
- if ((chars2 += ex.length + dp.length) > MAX_OPTION_CHARS) throw new RangeError(`${who}: the options have more than ${MAX_OPTION_CHARS} characters in all`);
12413
+ if ((chars3 += ex.length + dp.length) > MAX_OPTION_CHARS) throw new RangeError(`${who}: the options have more than ${MAX_OPTION_CHARS} characters in all`);
12374
12414
  if (index.has(ex)) throw new TypeError(`${who}: two options have the export value ${JSON.stringify(ex.slice(0, 80))}`);
12375
12415
  index.set(ex, i2);
12376
12416
  return { ex, dp };
@@ -13233,8 +13273,9 @@ function indexOf(buf, needle, from, to = buf.length) {
13233
13273
  to = Math.min(to, buf.length);
13234
13274
  if (to - from > 64) {
13235
13275
  if (NodeBuffer) {
13236
- const i2 = NodeBuffer.from(buf.buffer, buf.byteOffset, to).indexOf(needle, from, "latin1");
13237
- return i2;
13276
+ let v = VIEWS.get(buf);
13277
+ if (!v) VIEWS.set(buf, v = NodeBuffer.from(buf.buffer, buf.byteOffset, buf.length));
13278
+ return (to === v.length ? v : v.subarray(0, to)).indexOf(needle, from, "latin1");
13238
13279
  }
13239
13280
  for (let i2 = buf.indexOf(n0, from); i2 >= 0 && i2 + L <= to; i2 = buf.indexOf(n0, i2 + 1)) {
13240
13281
  let j = 1;
@@ -13258,7 +13299,7 @@ function lastIndexOf(buf, needle, to, span = to) {
13258
13299
  }
13259
13300
  return -1;
13260
13301
  }
13261
- var MAX_DEPTH4, MAX_ITEMS, Ref, PdfString, PdfStream, Op, CLASS, T, HEX, Lexer, parseValueEnd, NodeBuffer;
13302
+ var MAX_DEPTH4, MAX_ITEMS, Ref, PdfString, PdfStream, Op, CLASS, T, HEX, Lexer, parseValueEnd, NodeBuffer, VIEWS;
13262
13303
  var init_lexer = __esm({
13263
13304
  "src/reader/lexer.js"() {
13264
13305
  init_budget();
@@ -13429,7 +13470,7 @@ var init_lexer = __esm({
13429
13470
  }
13430
13471
  this.budget.tick(1 + (n >> 10));
13431
13472
  this.pos = i2;
13432
- this.val = new PdfString(out.slice(0, n), false);
13473
+ this.val = new PdfString(n === out.length ? out : out.slice(0, n), false);
13433
13474
  return T.STR;
13434
13475
  }
13435
13476
  /** @param {number} i after "<" */
@@ -13451,7 +13492,7 @@ var init_lexer = __esm({
13451
13492
  if (hi >= 0) out[n++] = hi << 4;
13452
13493
  this.budget.tick(1 + (n >> 10));
13453
13494
  this.pos = j < e ? j + 1 : j;
13454
- this.val = new PdfString(out.slice(0, n), true);
13495
+ this.val = new PdfString(n === out.length ? out : out.slice(0, n), true);
13455
13496
  return T.STR;
13456
13497
  }
13457
13498
  /** @param {number} i after "/" */
@@ -13483,6 +13524,8 @@ var init_lexer = __esm({
13483
13524
  parseValueEnd = () => void 0;
13484
13525
  NodeBuffer = /** @type {any} */
13485
13526
  globalThis.Buffer;
13527
+ VIEWS = /** @type {WeakMap<Uint8Array, any>} */
13528
+ /* @__PURE__ */ new WeakMap();
13486
13529
  }
13487
13530
  });
13488
13531
 
@@ -14644,14 +14687,26 @@ function decodeText(v) {
14644
14687
  if (!(v instanceof PdfString)) return typeof v === "string" ? v : v == null ? "" : String(v);
14645
14688
  const b = v.bytes;
14646
14689
  if (b.length >= 2 && b[0] === 254 && b[1] === 255) {
14647
- let s2 = "";
14648
- for (let i2 = 2; i2 + 1 < b.length; i2 += 2) s2 += String.fromCharCode(b[i2] << 8 | b[i2 + 1]);
14649
- return s2;
14690
+ const u2 = new Uint16Array(b.length - 2 >> 1);
14691
+ for (let i3 = 0; i3 < u2.length; i3++) u2[i3] = b[2 + 2 * i3] << 8 | b[3 + 2 * i3];
14692
+ return chars2(u2);
14650
14693
  }
14651
14694
  if (b.length >= 3 && b[0] === 239 && b[1] === 187 && b[2] === 191) return new TextDecoder().decode(b.subarray(3));
14652
- let s = "";
14653
- for (const x2 of b) s += String.fromCharCode(DOC.get(x2) ?? x2);
14654
- return s;
14695
+ let i2 = 0;
14696
+ while (i2 < b.length && DOC_T[b[i2]] === b[i2]) i2++;
14697
+ if (i2 === b.length) return latin12(b);
14698
+ const u = new Uint16Array(b.length);
14699
+ for (i2 = 0; i2 < b.length; i2++) u[i2] = DOC_T[b[i2]];
14700
+ return chars2(u);
14701
+ }
14702
+ function chars2(u) {
14703
+ const parts = [];
14704
+ for (let i2 = 0; i2 < u.length; i2 += 8192) parts.push(String.fromCharCode.apply(
14705
+ null,
14706
+ /** @type {any} */
14707
+ u.subarray(i2, i2 + 8192)
14708
+ ));
14709
+ return parts.join("");
14655
14710
  }
14656
14711
  function glyphText(n) {
14657
14712
  if (Object.hasOwn(NAMES2, n)) return NAMES2[n];
@@ -14689,11 +14744,11 @@ function parseCMap(data, budget, held = { n: 0 }) {
14689
14744
  for (let i2 = 0; i2 + 1 < b.length; i2 += 2) u.push(b[i2] << 8 | b[i2 + 1]);
14690
14745
  return String.fromCharCode(...u);
14691
14746
  };
14692
- let chars2 = 0;
14747
+ let chars3 = 0;
14693
14748
  const set2 = (c, t) => {
14694
- chars2 += t.length;
14749
+ chars3 += t.length;
14695
14750
  held.n += 8 + t.length;
14696
- if (chars2 > MAX_CMAP_CHARS) fail("E_BUDGET", `A CMap maps codes to more than ${MAX_CMAP_CHARS.toLocaleString("en")} characters`);
14751
+ if (chars3 > MAX_CMAP_CHARS) fail("E_BUDGET", `A CMap maps codes to more than ${MAX_CMAP_CHARS.toLocaleString("en")} characters`);
14697
14752
  if (held.n > MAX_HELD) fail("E_BUDGET", `A page's fonts hold more than ${MAX_HELD.toLocaleString("en")} units of code mappings (an entry 8, a character 1)`);
14698
14753
  map.set(c, t);
14699
14754
  };
@@ -15012,12 +15067,12 @@ function pageText(src, pageDict, resources, raw = false) {
15012
15067
  const PX = [], PY = [], PE = [], PS = [], PG = [];
15013
15068
  const PA = [], PAE = [], PC = [], PD = [];
15014
15069
  const PT = [];
15015
- let chars2 = 0;
15070
+ let chars3 = 0;
15016
15071
  let moved = true;
15017
15072
  const fonts = /* @__PURE__ */ new Map();
15018
15073
  const add2 = (x2, y, end, size, text3, d0, d1, d2, d3) => {
15019
15074
  if (PT.length >= budget.maxTextItems) throw new PdfReadError("E_BUDGET", `A page has more than ${budget.maxTextItems.toLocaleString("en")} pieces of text (budget.maxTextItems)`);
15020
- if ((chars2 += text3.length) > MAX_TEXT_CHARS) throw new PdfReadError("E_BUDGET", `A page makes more than ${MAX_TEXT_CHARS.toLocaleString("en")} characters of text`);
15075
+ if ((chars3 += text3.length) > MAX_TEXT_CHARS) throw new PdfReadError("E_BUDGET", `A page makes more than ${MAX_TEXT_CHARS.toLocaleString("en")} characters of text`);
15021
15076
  if (raw) {
15022
15077
  PX.push(x2);
15023
15078
  PY.push(y);
@@ -15273,7 +15328,7 @@ function pageText(src, pageDict, resources, raw = false) {
15273
15328
  }
15274
15329
  return out.join("");
15275
15330
  }
15276
- var MAX_FORMS, MAX_CMAP, MAX_MARKS, MAX_ACTUAL, MAX_TEXT_CHARS, MAX_DST, MAX_CMAP_CHARS, MAX_HELD, MAX_W, MAX_DO, TEXT_TIMEOUT_MS, DOC, WIN, WIN_T, MAC_HIGH, BASE, STD_HIGH, NAMES2, ACCENT, own, STD_ALIAS, NONE, stops, MAX_CMAP_FONTS, mul6, mul2, ID, matrixOf, C0, G0, G1, RTL2, STRONG_L, NUM, rev2;
15331
+ var MAX_FORMS, MAX_CMAP, MAX_MARKS, MAX_ACTUAL, MAX_TEXT_CHARS, MAX_DST, MAX_CMAP_CHARS, MAX_HELD, MAX_W, MAX_DO, TEXT_TIMEOUT_MS, DOC, DOC_T, WIN, WIN_T, MAC_HIGH, BASE, STD_HIGH, NAMES2, ACCENT, own, STD_ALIAS, NONE, stops, MAX_CMAP_FONTS, mul6, mul2, ID, matrixOf, C0, G0, G1, RTL2, STRONG_L, NUM, rev2;
15277
15332
  var init_text2 = __esm({
15278
15333
  "src/reader/text.js"() {
15279
15334
  init_lexer();
@@ -15333,6 +15388,7 @@ var init_text2 = __esm({
15333
15388
  [158, 382],
15334
15389
  [160, 8364]
15335
15390
  ]);
15391
+ DOC_T = new Uint16Array(256).map((_, c) => DOC.get(c) ?? c);
15336
15392
  WIN = /* @__PURE__ */ new Map();
15337
15393
  {
15338
15394
  const p = ENCODINGS.WinAnsi;
@@ -15732,9 +15788,15 @@ function numText(v) {
15732
15788
  return Math.abs(v) < 1 ? v.toFixed(10).replace(/\.?0+$/, "") || "0" : v.toFixed(0);
15733
15789
  }
15734
15790
  function hexText(b) {
15735
- let s = "<";
15736
- for (const x2 of b) s += HEXD[x2 >> 4] + HEXD[x2 & 15];
15737
- return s + ">";
15791
+ if (NodeBuffer2 && b.length > 64) return `<${NodeBuffer2.from(b.buffer, b.byteOffset, b.length).toString("hex")}>`;
15792
+ const parts = ["<"];
15793
+ for (let i2 = 0; i2 < b.length; i2 += 4096) {
15794
+ let s = "";
15795
+ for (let k = i2; k < Math.min(b.length, i2 + 4096); k++) s += HEXD[b[k] >> 4] + HEXD[b[k] & 15];
15796
+ parts.push(s);
15797
+ }
15798
+ parts.push(">");
15799
+ return parts.join("");
15738
15800
  }
15739
15801
  function createCopier(t) {
15740
15802
  const maps = /* @__PURE__ */ new Map();
@@ -16136,7 +16198,7 @@ function createCopier(t) {
16136
16198
  }
16137
16199
  };
16138
16200
  }
16139
- var SHARE, GRAY, HEXD, Raw, Foreign, AUDIT, OPEN_A, CLOSE_A, OPEN_D, CLOSE_D;
16201
+ var SHARE, GRAY, HEXD, NodeBuffer2, Raw, Foreign, AUDIT, OPEN_A, CLOSE_A, OPEN_D, CLOSE_D;
16140
16202
  var init_copy = __esm({
16141
16203
  "src/reader/copy.js"() {
16142
16204
  init_lexer();
@@ -16147,6 +16209,8 @@ var init_copy = __esm({
16147
16209
  SHARE = /* @__PURE__ */ new Set(["Font", "FontDescriptor", "ExtGState", "Encoding", "Pattern", "Shading", "Halftone", "Function", "XObject", "Mask", "CMap", "OutputIntent", "Filespec", "EmbeddedFile"]);
16148
16210
  GRAY = -2;
16149
16211
  HEXD = "0123456789abcdef";
16212
+ NodeBuffer2 = /** @type {any} */
16213
+ globalThis.Buffer;
16150
16214
  Raw = class {
16151
16215
  /** @param {string} s */
16152
16216
  constructor(s) {
@@ -16451,6 +16515,13 @@ async function run(self, sink, o) {
16451
16515
  warn("the source's output intent profile was not usable; the bundled sRGB profile was written instead");
16452
16516
  }
16453
16517
  }
16518
+ const infoText = (k) => {
16519
+ const v = info[k];
16520
+ if (v == null) return void 0;
16521
+ if (v.length <= INFO_MAX) return v;
16522
+ warn(`the source's ${k} (${v.length.toLocaleString("en")} characters) was cut to ${INFO_MAX.toLocaleString("en")}`);
16523
+ return v.slice(0, INFO_MAX);
16524
+ };
16454
16525
  const pdf = (
16455
16526
  /** @type {any} */
16456
16527
  createPdf(sink, {
@@ -16461,10 +16532,10 @@ async function run(self, sink, o) {
16461
16532
  creationDate: created,
16462
16533
  metadata: !!(level || self.edits.xmp != null || self.xmp() != null),
16463
16534
  // an XMP packet only when the source had one or PDF/A needs one: a file split into pages is not a kilobyte of XMP each
16464
- title: info.title ?? void 0,
16465
- author: info.author ?? void 0,
16466
- subject: info.subject ?? void 0,
16467
- keywords: info.keywords ?? void 0,
16535
+ title: infoText("title"),
16536
+ author: infoText("author"),
16537
+ subject: infoText("subject"),
16538
+ keywords: infoText("keywords"),
16468
16539
  ...level ? { pdfa: { part: level.part, conformance: "b", ...intentOpt ? { outputIntent: intentOpt } : {} } } : {}
16469
16540
  })
16470
16541
  );
@@ -16775,7 +16846,7 @@ function sameBytes(a, b) {
16775
16846
  for (let i2 = 0; i2 < a.length; i2++) if (a[i2] !== b[i2]) return false;
16776
16847
  return true;
16777
16848
  }
16778
- var MAX_OPEN, KEYS2, OWN;
16849
+ var INFO_MAX, MAX_OPEN, KEYS2, OWN;
16779
16850
  var init_save = __esm({
16780
16851
  "src/reader/save.js"() {
16781
16852
  init_index();
@@ -16789,6 +16860,7 @@ var init_save = __esm({
16789
16860
  init_pdf();
16790
16861
  init_text2();
16791
16862
  init_budget();
16863
+ INFO_MAX = 65536;
16792
16864
  MAX_OPEN = 256;
16793
16865
  KEYS2 = ["compress", "keepActiveContent", "encrypt", "pdfa", "creationDate", "signal", "timeoutMs", "keepStructure"];
16794
16866
  OWN = /* @__PURE__ */ new Set(["Type", "Parent", "Contents", "Resources", "Annots", "MediaBox", "CropBox", "Rotate", "B"]);
package/index.d.ts CHANGED
@@ -650,7 +650,7 @@ export interface Pdf {
650
650
  * textMapping: 'carrier' (default: Firefox, pdf.js, search) or 'actualText' (a shaped cluster's text only in its ActualText: MuPDF, AI pipelines) */
651
651
  embedFont(bytes: Uint8Array | ArrayBuffer, o?: { subset?: boolean | Subsetter; shaper?: Shaper; textMapping?: 'carrier' | 'actualText' }): Promise<Font>;
652
652
  /** a JPEG or PNG, written now as an image XObject. colorSpace: an ICC colour space with the image's component count */
653
- embedImage(bytes: Uint8Array | ArrayBuffer, o?: { colorSpace?: IccColorSpace; /** the decoded size a PNG with alpha or interlacing may have; default 64 MB */ maxDecodedBytes?: number }): Promise<Image>;
653
+ embedImage(bytes: Uint8Array | ArrayBuffer, o?: { colorSpace?: IccColorSpace; /** the decoded size a PNG with alpha or interlacing may have; default 64 MB */ maxDecodedBytes?: number; /** zlib level for a re-compressed PNG (alpha, interlacing): 'default' = 6, or 3 above 2 megapixels; 'fast' = 1; or 1–9 */ compression?: 'default' | 'fast' | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 }): Promise<Image>;
654
654
  /** a spot colour: its name and the device colour (gray, RGB, CMYK) shown for it at full tint. One definition per name */
655
655
  spotColor(name: string, alternate: number | string | readonly number[]): SpotColor;
656
656
  /** an ICC-based colour space from a gray, RGB or CMYK profile (its header is checked) */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reportwright/pdf",
3
- "version": "0.1.0-beta.3",
3
+ "version": "0.1.0-beta.4",
4
4
  "description": "A streaming PDF writer for Node and browsers with no runtime dependencies: flat memory, PDF/A-1b to 3a, PDF/UA-1, Factur-X, embedded TrueType with Unicode text.",
5
5
  "type": "module",
6
6
  "license": "MIT",