pdf-parser.js 4.6.0 → 4.7.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/README.md CHANGED
@@ -264,7 +264,7 @@ Dependency direction is strictly downward and checkable: `layout` imports only `
264
264
  - **Nothing in this codec's reader accepts, prompts for, or guesses a password.** Authenticating as owner is a permissions escalation, not a way to read a file you were already allowed to read.
265
265
  - **`writePdf` produces real, spec-conformant encryption for all four of the schemes above** (`src/encrypt-write.ts`, `WritePdfOptions.encryption`) — genuine `/O`/`/U`/`/OE`/`/UE`/`/Perms` computation per ISO 32000-2 7.6.4.4's Algorithms 3, 8, 9, and 10, a real user and (optional, defaulting to the user password) owner password, and per-`/P`-bit permission flags. Unlike the reader, the writer's own password is not scoped to empty: a genuinely non-empty user password is fully supported and round-trips against the published algorithm (see `encrypt-write.test.ts`), even though this package's own reader — scoped as above — can only ever open the result back up if the user password used to encrypt it was itself empty. Two boundaries are deliberately narrower than the full spec rather than approximated silently: a revision 2-4 (rc4-40/rc4-128/aes-128) password must be plain printable ASCII, not the full 8-bit PDFDocEncoding table, and a revision 6 (aes-256) password is Unicode-normalised with NFKC rather than the full SASLPrep/stringprep profile (prohibited-character tables and the bidirectional-text rule are not implemented). Both throw `PdfEncryptionError` for a password outside that scope rather than silently mis-encoding it into one that will not open the file it was meant to protect.
266
266
  - **`CCITTFaxDecode`, `JBIG2Decode` and `JPXDecode` images all decode for real** via hand-written decoders (`src/image/ccitt.ts` for ITU-T T.4/T.6 fax; `src/image/jbig2*.ts` for ITU-T T.88; `src/image/jpeg2000*.ts` for ISO/IEC 15444-1). JPEG images (`DCTDecode`) pass through losslessly in both directions. See [JBIG2 scope](#jbig2-scope) and [JPEG 2000 scope](#jpeg-2000-scope) for exactly what is and is not implemented.
267
- - **The write side re-encodes bilevel images as CCITT Group 4** (`src/image/ccitt-encode.ts`, a hand-written ITU-T T.6 encoder sharing the decoder's own code-table transcription): a grayscale image whose samples are all 0 or 255, with no soft mask, is written as `/CCITTFaxDecode` with `/K -1` whenever the G4 encoding comes out smaller than Flate over the same pixels — which for vertically coherent bilevel content (a scan's edges and text baselines) it does by an order of magnitude. Whichever encoding is smaller wins, deterministically, so decorrelated or repetitive bilevel content where Flate happens to win keeps Flate. **JBIG2 and JPEG 2000 re-encoding are permanent boundaries, not gaps**: a hand-written JBIG2 encoder is research-grade symbol-dictionary design and a JPEG 2000 encoder is the full EBCOT/wavelet stack, and re-encoding lossy JPX back is not fidelity-preserving anyway. Verbatim passthrough of a source's original JBIG2/JPX compressed bytes would need `LayoutImageAsset` and `document-schema.js`'s own `ContentImageBlock` to carry the original filter and bytes (the model normalises to decoded pixels at read time), a cross-package schema design change recorded in #975's scorecard rather than smuggled into this feature.
267
+ - **The write side re-encodes bilevel images as CCITT Group 4** (`src/image/ccitt-encode.ts`, a hand-written ITU-T T.6 encoder sharing the decoder's own code-table transcription): a grayscale image whose samples are all 0 or 255, with no soft mask, is written as `/CCITTFaxDecode` with `/K -1` whenever the G4 encoding comes out smaller than Flate over the same pixels — which for vertically coherent bilevel content (a scan's edges and text baselines) it does by an order of magnitude. Whichever encoding is smaller wins, deterministically, so decorrelated or repetitive bilevel content where Flate happens to win keeps Flate. **JBIG2 and JPEG 2000 re-encoding remain permanent boundaries, not gaps**: a hand-written JBIG2 encoder is research-grade symbol-dictionary design and a JPEG 2000 encoder is the full EBCOT/wavelet stack, and re-encoding lossy JPX back is not fidelity-preserving anyway. **What no longer follows from that boundary is generation loss on a pdf-to-pdf round trip**: `LayoutImageAsset.original` (the layout twin of `document-schema.js`'s `ContentImageOriginal`, landed through the #975-scorecard schema change it named) carries a source's own JBIG2/JPX compressed stream beside the decoded canonical, captured at read time together with the decoded `/JBIG2Globals` segments a symbol-dictionary stream needs, and the writer re-embeds those bytes verbatim under their own filter — the decoded canonical never reaches the file at all for such an image, a source soft mask still re-emits as a generated `/SMask`, and the round trip pays zero generation loss for exactly the two filters this package cannot re-encode.
268
268
  - **`interpret.ts` tracks general vector paths, not just axis-aligned `re` rectangles.** `m`/`l`/`c`/`v`/`y`/`h` (and `re` itself) accumulate real subpaths — CTM-transformed line/cubic segments, open or closed — and any paint operator emits an item built from them. A recovered path matching one of three characteristic shape patterns comes back as that shape's own kind, not a generic `LayoutPath`: an axis-aligned closed four-corner subpath is a `LayoutRect`; a closed subpath of four cubic segments meeting its bounding box at cardinal points with kappa-ratio control points is a `LayoutEllipse`; an open single-straight-segment stroke-only subpath is a `LayoutLine` (tolerance: `max(1e-3pt, 1e-4 × extent)`). **These are deliberate, bounded heuristics** — a false positive changes an item's kind, never its geometry, since every detected shape reproduces its source path's own points exactly. Off-axis rotations, non-quadrant curves, polygons, and multi-subpath figures stay a `LayoutPath`.
269
269
  - **`writePdf`/`readPdf` round-trip a page's own `notes` field via a hidden `/Subtype /Text` annotation** with the `Hidden` flag set so it never renders or prints, distinguished from a genuine third-party sticky note by an internal author marker. This is a round-trip mechanism specific to this package's own writer/reader pair. `documents.js` uses this to carry pptx/odp speaker notes through PDF.
270
270
  - **STIX Two Math is a CFF-flavoured OpenType font, not TrueType/glyf** — the **entire** `CFF ` table is embedded verbatim as a single `/FontFile3` `/Subtype /CIDFontType0C` stream (a real, correct, working embedded font, just not glyph-subsetted). Everything else genuinely IS built from a targeted parse of only what's used: `cmap` resolves exactly the Unicode code points a document's formulas reference, and the emitted `/W` widths array covers only drawn glyph IDs. A CID-keyed composite font built this way needs no `/CIDToGIDMap` — per ISO 32000-1 9.7.4.2, a `/CIDFontType0` whose `/FontFile3` is a non-CID-keyed CFF program is read with CID directly indexing `CharStrings` by glyph order (CID == GID).
package/dist/codec.d.cts CHANGED
@@ -253,6 +253,14 @@ declare const pdfCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Arr
253
253
  base64: z.ZodString;
254
254
  widthPx: z.ZodNumber;
255
255
  heightPx: z.ZodNumber;
256
+ original: z.ZodOptional<z.ZodObject<{
257
+ filter: z.ZodEnum<{
258
+ jbig2: "jbig2";
259
+ jpeg2000: "jpeg2000";
260
+ }>;
261
+ base64: z.ZodString;
262
+ jbig2GlobalsBase64: z.ZodOptional<z.ZodString>;
263
+ }, z.core.$strip>>;
256
264
  }, z.core.$strip>>;
257
265
  destinations: z.ZodOptional<z.ZodArray<z.ZodObject<{
258
266
  name: z.ZodString;
package/dist/codec.d.ts CHANGED
@@ -253,6 +253,14 @@ declare const pdfCodec: z.ZodCodec<z.ZodCustom<Uint8Array<ArrayBuffer>, Uint8Arr
253
253
  base64: z.ZodString;
254
254
  widthPx: z.ZodNumber;
255
255
  heightPx: z.ZodNumber;
256
+ original: z.ZodOptional<z.ZodObject<{
257
+ filter: z.ZodEnum<{
258
+ jbig2: "jbig2";
259
+ jpeg2000: "jpeg2000";
260
+ }>;
261
+ base64: z.ZodString;
262
+ jbig2GlobalsBase64: z.ZodOptional<z.ZodString>;
263
+ }, z.core.$strip>>;
256
264
  }, z.core.$strip>>;
257
265
  destinations: z.ZodOptional<z.ZodArray<z.ZodObject<{
258
266
  name: z.ZodString;
package/dist/filters.cjs CHANGED
@@ -10,6 +10,7 @@ function decodeStream(raw, dict, sink, resolve) {
10
10
  const filters = filterNames(dict);
11
11
  const parms = decodeParmsList(dict, filters.length);
12
12
  let bytes = raw;
13
+ let jbig2;
13
14
  for (let i = 0; i < filters.length; i++) {
14
15
  const filter = filters[i];
15
16
  const parm = parms[i];
@@ -27,7 +28,11 @@ function decodeStream(raw, dict, sink, resolve) {
27
28
  bytes,
28
29
  remainingFilter: "JBIG2Decode"
29
30
  };
30
- bytes = decoded;
31
+ jbig2 = {
32
+ encoded: bytes,
33
+ globals: decoded.globals
34
+ };
35
+ bytes = decoded.bytes;
31
36
  } else if (filter === "DCTDecode" || filter === "DCT") return {
32
37
  bytes,
33
38
  remainingFilter: "DCTDecode"
@@ -48,7 +53,10 @@ function decodeStream(raw, dict, sink, resolve) {
48
53
  };
49
54
  }
50
55
  }
51
- return { bytes };
56
+ return jbig2 === void 0 ? { bytes } : {
57
+ bytes,
58
+ jbig2
59
+ };
52
60
  }
53
61
  function applyPredictorIfPresent(data, parm, sink) {
54
62
  return require_predictors.applyPredictor(data, require_predictors.readPredictorParams(parm), sink);
@@ -94,7 +102,10 @@ function jbig2Decode(data, parm, dict, sink, resolve) {
94
102
  });
95
103
  }
96
104
  });
97
- return Uint8Array.from(image.bytes, (byte) => byte ^ 255);
105
+ return {
106
+ bytes: Uint8Array.from(image.bytes, (byte) => byte ^ 255),
107
+ globals
108
+ };
98
109
  } catch (error) {
99
110
  sink({
100
111
  code: "pdf/jbig2-undecodable",
@@ -4,6 +4,10 @@ import { n as PdfDict, r as PdfObject } from "./objects-B7HdMeWS.cjs";
4
4
  interface DecodedStream {
5
5
  readonly bytes: Uint8Array<ArrayBuffer>;
6
6
  readonly remainingFilter?: string;
7
+ readonly jbig2?: {
8
+ readonly encoded: Uint8Array<ArrayBuffer>;
9
+ readonly globals?: Uint8Array<ArrayBuffer>;
10
+ };
7
11
  }
8
12
  type PdfIndirectResolver = (obj: PdfObject | undefined) => PdfObject | undefined;
9
13
  declare function decodeStream(raw: Uint8Array<ArrayBuffer>, dict: PdfDict, sink: PdfDiagnosticSink, resolve?: PdfIndirectResolver): DecodedStream;
package/dist/filters.d.ts CHANGED
@@ -4,6 +4,10 @@ import { n as PdfDict, r as PdfObject } from "./objects-B7HdMeWS.js";
4
4
  interface DecodedStream {
5
5
  readonly bytes: Uint8Array<ArrayBuffer>;
6
6
  readonly remainingFilter?: string;
7
+ readonly jbig2?: {
8
+ readonly encoded: Uint8Array<ArrayBuffer>;
9
+ readonly globals?: Uint8Array<ArrayBuffer>;
10
+ };
7
11
  }
8
12
  type PdfIndirectResolver = (obj: PdfObject | undefined) => PdfObject | undefined;
9
13
  declare function decodeStream(raw: Uint8Array<ArrayBuffer>, dict: PdfDict, sink: PdfDiagnosticSink, resolve?: PdfIndirectResolver): DecodedStream;
package/dist/filters.js CHANGED
@@ -9,6 +9,7 @@ function decodeStream(raw, dict, sink, resolve) {
9
9
  const filters = filterNames(dict);
10
10
  const parms = decodeParmsList(dict, filters.length);
11
11
  let bytes = raw;
12
+ let jbig2;
12
13
  for (let i = 0; i < filters.length; i++) {
13
14
  const filter = filters[i];
14
15
  const parm = parms[i];
@@ -26,7 +27,11 @@ function decodeStream(raw, dict, sink, resolve) {
26
27
  bytes,
27
28
  remainingFilter: "JBIG2Decode"
28
29
  };
29
- bytes = decoded;
30
+ jbig2 = {
31
+ encoded: bytes,
32
+ globals: decoded.globals
33
+ };
34
+ bytes = decoded.bytes;
30
35
  } else if (filter === "DCTDecode" || filter === "DCT") return {
31
36
  bytes,
32
37
  remainingFilter: "DCTDecode"
@@ -47,7 +52,10 @@ function decodeStream(raw, dict, sink, resolve) {
47
52
  };
48
53
  }
49
54
  }
50
- return { bytes };
55
+ return jbig2 === void 0 ? { bytes } : {
56
+ bytes,
57
+ jbig2
58
+ };
51
59
  }
52
60
  function applyPredictorIfPresent(data, parm, sink) {
53
61
  return applyPredictor(data, readPredictorParams(parm), sink);
@@ -93,7 +101,10 @@ function jbig2Decode(data, parm, dict, sink, resolve) {
93
101
  });
94
102
  }
95
103
  });
96
- return Uint8Array.from(image.bytes, (byte) => byte ^ 255);
104
+ return {
105
+ bytes: Uint8Array.from(image.bytes, (byte) => byte ^ 255),
106
+ globals
107
+ };
97
108
  } catch (error) {
98
109
  sink({
99
110
  code: "pdf/jbig2-undecodable",
@@ -249,7 +249,11 @@ function readJpeg2000Image(dict, raw, resolver, sink) {
249
249
  format: "png",
250
250
  bytes: (0, byte_codec.encodePng)(withAlpha),
251
251
  widthPx: image.width,
252
- heightPx: image.height
252
+ heightPx: image.height,
253
+ original: {
254
+ filter: "jpeg2000",
255
+ bytes: raw
256
+ }
253
257
  };
254
258
  }
255
259
  function readImageXObject(dict, raw, resolver, sink) {
@@ -262,6 +266,11 @@ function readImageXObject(dict, raw, resolver, sink) {
262
266
  return;
263
267
  }
264
268
  const decoded = require_filters.decodeStream(raw, dict, sink, (obj) => resolver.resolve(obj));
269
+ const jbig2Original = decoded.jbig2 === void 0 ? void 0 : {
270
+ filter: "jbig2",
271
+ bytes: decoded.jbig2.encoded,
272
+ ...decoded.jbig2.globals !== void 0 ? { globalsBytes: decoded.jbig2.globals } : {}
273
+ };
265
274
  if (decoded.remainingFilter === "DCTDecode") {
266
275
  let info;
267
276
  try {
@@ -323,7 +332,8 @@ function readImageXObject(dict, raw, resolver, sink) {
323
332
  format: "png",
324
333
  bytes: (0, byte_codec.encodePng)(withAlpha),
325
334
  widthPx: width,
326
- heightPx: height
335
+ heightPx: height,
336
+ ...jbig2Original !== void 0 ? { original: jbig2Original } : {}
327
337
  };
328
338
  }
329
339
  //#endregion
@@ -7,6 +7,11 @@ interface ExtractedPdfImage {
7
7
  readonly bytes: Uint8Array<ArrayBuffer>;
8
8
  readonly widthPx: number;
9
9
  readonly heightPx: number;
10
+ readonly original?: {
11
+ readonly filter: "jbig2" | "jpeg2000";
12
+ readonly bytes: Uint8Array<ArrayBuffer>;
13
+ readonly globalsBytes?: Uint8Array<ArrayBuffer>;
14
+ };
10
15
  }
11
16
  declare function readImageXObject(dict: PdfDict, raw: Uint8Array<ArrayBuffer>, resolver: PdfObjectResolver, sink: PdfDiagnosticSink): ExtractedPdfImage | undefined;
12
17
  //#endregion
@@ -7,6 +7,11 @@ interface ExtractedPdfImage {
7
7
  readonly bytes: Uint8Array<ArrayBuffer>;
8
8
  readonly widthPx: number;
9
9
  readonly heightPx: number;
10
+ readonly original?: {
11
+ readonly filter: "jbig2" | "jpeg2000";
12
+ readonly bytes: Uint8Array<ArrayBuffer>;
13
+ readonly globalsBytes?: Uint8Array<ArrayBuffer>;
14
+ };
10
15
  }
11
16
  declare function readImageXObject(dict: PdfDict, raw: Uint8Array<ArrayBuffer>, resolver: PdfObjectResolver, sink: PdfDiagnosticSink): ExtractedPdfImage | undefined;
12
17
  //#endregion
@@ -248,7 +248,11 @@ function readJpeg2000Image(dict, raw, resolver, sink) {
248
248
  format: "png",
249
249
  bytes: encodePng(withAlpha),
250
250
  widthPx: image.width,
251
- heightPx: image.height
251
+ heightPx: image.height,
252
+ original: {
253
+ filter: "jpeg2000",
254
+ bytes: raw
255
+ }
252
256
  };
253
257
  }
254
258
  function readImageXObject(dict, raw, resolver, sink) {
@@ -261,6 +265,11 @@ function readImageXObject(dict, raw, resolver, sink) {
261
265
  return;
262
266
  }
263
267
  const decoded = decodeStream(raw, dict, sink, (obj) => resolver.resolve(obj));
268
+ const jbig2Original = decoded.jbig2 === void 0 ? void 0 : {
269
+ filter: "jbig2",
270
+ bytes: decoded.jbig2.encoded,
271
+ ...decoded.jbig2.globals !== void 0 ? { globalsBytes: decoded.jbig2.globals } : {}
272
+ };
264
273
  if (decoded.remainingFilter === "DCTDecode") {
265
274
  let info;
266
275
  try {
@@ -322,7 +331,8 @@ function readImageXObject(dict, raw, resolver, sink) {
322
331
  format: "png",
323
332
  bytes: encodePng(withAlpha),
324
333
  widthPx: width,
325
- heightPx: height
334
+ heightPx: height,
335
+ ...jbig2Original !== void 0 ? { original: jbig2Original } : {}
326
336
  };
327
337
  }
328
338
  //#endregion
package/dist/layout.cjs CHANGED
@@ -178,7 +178,12 @@ const LayoutImageAssetSchema = zod.z.object({
178
178
  format: zod.z.enum(["png", "jpeg"]),
179
179
  base64: zod.z.string(),
180
180
  widthPx: zod.z.number().int().positive(),
181
- heightPx: zod.z.number().int().positive()
181
+ heightPx: zod.z.number().int().positive(),
182
+ original: zod.z.object({
183
+ filter: zod.z.enum(["jbig2", "jpeg2000"]),
184
+ base64: zod.z.string(),
185
+ jbig2GlobalsBase64: zod.z.string().optional()
186
+ }).optional()
182
187
  });
183
188
  const LayoutDestinationTargetSchema = zod.z.object({
184
189
  kind: zod.z.enum([
package/dist/layout.d.cts CHANGED
@@ -675,6 +675,14 @@ declare const LayoutImageAssetSchema: z.ZodObject<{
675
675
  base64: z.ZodString;
676
676
  widthPx: z.ZodNumber;
677
677
  heightPx: z.ZodNumber;
678
+ original: z.ZodOptional<z.ZodObject<{
679
+ filter: z.ZodEnum<{
680
+ jbig2: "jbig2";
681
+ jpeg2000: "jpeg2000";
682
+ }>;
683
+ base64: z.ZodString;
684
+ jbig2GlobalsBase64: z.ZodOptional<z.ZodString>;
685
+ }, z.core.$strip>>;
678
686
  }, z.core.$strip>;
679
687
  type LayoutImageAsset = z.infer<typeof LayoutImageAssetSchema>;
680
688
  declare const LayoutDestinationTargetSchema: z.ZodObject<{
@@ -1016,6 +1024,14 @@ declare const LayoutDocumentSchema: z.ZodObject<{
1016
1024
  base64: z.ZodString;
1017
1025
  widthPx: z.ZodNumber;
1018
1026
  heightPx: z.ZodNumber;
1027
+ original: z.ZodOptional<z.ZodObject<{
1028
+ filter: z.ZodEnum<{
1029
+ jbig2: "jbig2";
1030
+ jpeg2000: "jpeg2000";
1031
+ }>;
1032
+ base64: z.ZodString;
1033
+ jbig2GlobalsBase64: z.ZodOptional<z.ZodString>;
1034
+ }, z.core.$strip>>;
1019
1035
  }, z.core.$strip>>;
1020
1036
  destinations: z.ZodOptional<z.ZodArray<z.ZodObject<{
1021
1037
  name: z.ZodString;
package/dist/layout.d.ts CHANGED
@@ -675,6 +675,14 @@ declare const LayoutImageAssetSchema: z.ZodObject<{
675
675
  base64: z.ZodString;
676
676
  widthPx: z.ZodNumber;
677
677
  heightPx: z.ZodNumber;
678
+ original: z.ZodOptional<z.ZodObject<{
679
+ filter: z.ZodEnum<{
680
+ jbig2: "jbig2";
681
+ jpeg2000: "jpeg2000";
682
+ }>;
683
+ base64: z.ZodString;
684
+ jbig2GlobalsBase64: z.ZodOptional<z.ZodString>;
685
+ }, z.core.$strip>>;
678
686
  }, z.core.$strip>;
679
687
  type LayoutImageAsset = z.infer<typeof LayoutImageAssetSchema>;
680
688
  declare const LayoutDestinationTargetSchema: z.ZodObject<{
@@ -1016,6 +1024,14 @@ declare const LayoutDocumentSchema: z.ZodObject<{
1016
1024
  base64: z.ZodString;
1017
1025
  widthPx: z.ZodNumber;
1018
1026
  heightPx: z.ZodNumber;
1027
+ original: z.ZodOptional<z.ZodObject<{
1028
+ filter: z.ZodEnum<{
1029
+ jbig2: "jbig2";
1030
+ jpeg2000: "jpeg2000";
1031
+ }>;
1032
+ base64: z.ZodString;
1033
+ jbig2GlobalsBase64: z.ZodOptional<z.ZodString>;
1034
+ }, z.core.$strip>>;
1019
1035
  }, z.core.$strip>>;
1020
1036
  destinations: z.ZodOptional<z.ZodArray<z.ZodObject<{
1021
1037
  name: z.ZodString;
package/dist/layout.js CHANGED
@@ -177,7 +177,12 @@ const LayoutImageAssetSchema = z.object({
177
177
  format: z.enum(["png", "jpeg"]),
178
178
  base64: z.string(),
179
179
  widthPx: z.number().int().positive(),
180
- heightPx: z.number().int().positive()
180
+ heightPx: z.number().int().positive(),
181
+ original: z.object({
182
+ filter: z.enum(["jbig2", "jpeg2000"]),
183
+ base64: z.string(),
184
+ jbig2GlobalsBase64: z.string().optional()
185
+ }).optional()
181
186
  });
182
187
  const LayoutDestinationTargetSchema = z.object({
183
188
  kind: z.enum([
package/dist/read.cjs CHANGED
@@ -497,13 +497,18 @@ function imagePlacementFrom(matrix) {
497
497
  rotationDeg: rotationDeg !== 0 ? rotationDeg : void 0
498
498
  };
499
499
  }
500
- function registerExtractedImage(format, bytes, widthPx, heightPx, images) {
500
+ function registerExtractedImage(format, bytes, widthPx, heightPx, images, original) {
501
501
  const imageId = `img${require_bytes_crc32.crc32(bytes).toString(16)}`;
502
502
  if (!(imageId in images)) images[imageId] = {
503
503
  format,
504
504
  base64: require_util_base64.bytesToBase64(bytes),
505
505
  widthPx,
506
- heightPx
506
+ heightPx,
507
+ ...original !== void 0 ? { original: {
508
+ filter: original.filter,
509
+ base64: require_util_base64.bytesToBase64(original.bytes),
510
+ ...original.globalsBytes !== void 0 ? { jbig2GlobalsBase64: require_util_base64.bytesToBase64(original.globalsBytes) } : {}
511
+ } } : {}
507
512
  };
508
513
  return imageId;
509
514
  }
@@ -514,7 +519,7 @@ function resolveCachedImageId(dict, raw, images, cache, resolver, sink) {
514
519
  cache.set(dict, null);
515
520
  return;
516
521
  }
517
- const imageId = registerExtractedImage(decoded.format, decoded.bytes, decoded.widthPx, decoded.heightPx, images);
522
+ const imageId = registerExtractedImage(decoded.format, decoded.bytes, decoded.widthPx, decoded.heightPx, images, decoded.original);
518
523
  cache.set(dict, imageId);
519
524
  return imageId;
520
525
  }
@@ -536,7 +541,7 @@ function convertInlineImage(item, pageMatrix, images, resolver, sink) {
536
541
  if (decoded === void 0) return;
537
542
  return {
538
543
  kind: "image",
539
- imageId: registerExtractedImage(decoded.format, decoded.bytes, decoded.widthPx, decoded.heightPx, images),
544
+ imageId: registerExtractedImage(decoded.format, decoded.bytes, decoded.widthPx, decoded.heightPx, images, decoded.original),
540
545
  ...imagePlacementFrom(require_matrix.multiplyMatrices(item.matrix, pageMatrix)),
541
546
  ...item.layerName !== void 0 ? { layer: item.layerName } : {}
542
547
  };
package/dist/read.js CHANGED
@@ -496,13 +496,18 @@ function imagePlacementFrom(matrix) {
496
496
  rotationDeg: rotationDeg !== 0 ? rotationDeg : void 0
497
497
  };
498
498
  }
499
- function registerExtractedImage(format, bytes, widthPx, heightPx, images) {
499
+ function registerExtractedImage(format, bytes, widthPx, heightPx, images, original) {
500
500
  const imageId = `img${crc32(bytes).toString(16)}`;
501
501
  if (!(imageId in images)) images[imageId] = {
502
502
  format,
503
503
  base64: bytesToBase64(bytes),
504
504
  widthPx,
505
- heightPx
505
+ heightPx,
506
+ ...original !== void 0 ? { original: {
507
+ filter: original.filter,
508
+ base64: bytesToBase64(original.bytes),
509
+ ...original.globalsBytes !== void 0 ? { jbig2GlobalsBase64: bytesToBase64(original.globalsBytes) } : {}
510
+ } } : {}
506
511
  };
507
512
  return imageId;
508
513
  }
@@ -513,7 +518,7 @@ function resolveCachedImageId(dict, raw, images, cache, resolver, sink) {
513
518
  cache.set(dict, null);
514
519
  return;
515
520
  }
516
- const imageId = registerExtractedImage(decoded.format, decoded.bytes, decoded.widthPx, decoded.heightPx, images);
521
+ const imageId = registerExtractedImage(decoded.format, decoded.bytes, decoded.widthPx, decoded.heightPx, images, decoded.original);
517
522
  cache.set(dict, imageId);
518
523
  return imageId;
519
524
  }
@@ -535,7 +540,7 @@ function convertInlineImage(item, pageMatrix, images, resolver, sink) {
535
540
  if (decoded === void 0) return;
536
541
  return {
537
542
  kind: "image",
538
- imageId: registerExtractedImage(decoded.format, decoded.bytes, decoded.widthPx, decoded.heightPx, images),
543
+ imageId: registerExtractedImage(decoded.format, decoded.bytes, decoded.widthPx, decoded.heightPx, images, decoded.original),
539
544
  ...imagePlacementFrom(multiplyMatrices(item.matrix, pageMatrix)),
540
545
  ...item.layerName !== void 0 ? { layer: item.layerName } : {}
541
546
  };
package/dist/write.cjs CHANGED
@@ -206,7 +206,28 @@ function preparePngImage(bytes, compress) {
206
206
  }
207
207
  };
208
208
  }
209
+ function preparePassthroughImage(asset, compress) {
210
+ if (asset.original === void 0) throw new Error("preparePassthroughImage: asset carries no original stream");
211
+ const png = preparePngImage(require_util_base64.base64ToBytes(asset.base64), compress);
212
+ const entries = /* @__PURE__ */ new Map([
213
+ ["Type", require_objects.pdfName("XObject")],
214
+ ["Subtype", require_objects.pdfName("Image")],
215
+ ["Width", require_objects.pdfNum(asset.widthPx)],
216
+ ["Height", require_objects.pdfNum(asset.heightPx)],
217
+ ["Filter", require_objects.pdfName(asset.original.filter === "jbig2" ? "JBIG2Decode" : "JPXDecode")]
218
+ ]);
219
+ if (asset.original.filter === "jbig2") {
220
+ entries.set("ColorSpace", require_objects.pdfName("DeviceGray"));
221
+ entries.set("BitsPerComponent", require_objects.pdfNum(1));
222
+ }
223
+ return {
224
+ dict: require_objects.pdfDict(entries),
225
+ raw: require_util_base64.base64ToBytes(asset.original.base64),
226
+ ...png.alpha !== void 0 ? { alpha: png.alpha } : {}
227
+ };
228
+ }
209
229
  function prepareImage(asset, compress) {
230
+ if (asset.original !== void 0) return preparePassthroughImage(asset, compress);
210
231
  const bytes = require_util_base64.base64ToBytes(asset.base64);
211
232
  return asset.format === "jpeg" ? prepareJpegImage(bytes) : preparePngImage(bytes, compress);
212
233
  }
@@ -361,9 +382,13 @@ function writePdf(doc, options = {}) {
361
382
  const prepared = prepareImage(asset, compress);
362
383
  const imageNum = nextObjNum++;
363
384
  const smaskNum = prepared.alpha === void 0 ? void 0 : nextObjNum++;
385
+ const globalsBase64 = asset.original?.filter === "jbig2" ? asset.original.jbig2GlobalsBase64 : void 0;
386
+ const globalsNum = globalsBase64 !== void 0 ? nextObjNum++ : void 0;
364
387
  imageAllocs.set(imageId, {
365
388
  imageNum,
366
389
  smaskNum,
390
+ globalsNum,
391
+ globalsBase64,
367
392
  resourceName: `Im${index + 1}`,
368
393
  prepared
369
394
  });
@@ -708,6 +733,14 @@ function writePdf(doc, options = {}) {
708
733
  value: require_objects.pdfStream(alloc.prepared.alpha.dict, alloc.prepared.alpha.raw)
709
734
  });
710
735
  }
736
+ if (alloc.globalsNum !== void 0 && alloc.globalsBase64 !== void 0) {
737
+ const globalsRaw = require_util_base64.base64ToBytes(alloc.globalsBase64);
738
+ alloc.prepared.dict.entries.set("DecodeParms", require_objects.pdfDict({ JBIG2Globals: require_objects.pdfRef(alloc.globalsNum, 0) }));
739
+ objects.push({
740
+ num: alloc.globalsNum,
741
+ value: require_objects.pdfStream(require_objects.pdfDict(compress ? /* @__PURE__ */ new Map([["Filter", require_objects.pdfName("FlateDecode")]]) : /* @__PURE__ */ new Map()), compress ? require_bytes_flate.deflate(globalsRaw) : globalsRaw)
742
+ });
743
+ }
711
744
  objects.push({
712
745
  num: alloc.imageNum,
713
746
  value: require_objects.pdfStream(alloc.prepared.dict, alloc.prepared.raw)
package/dist/write.js CHANGED
@@ -205,7 +205,28 @@ function preparePngImage(bytes, compress) {
205
205
  }
206
206
  };
207
207
  }
208
+ function preparePassthroughImage(asset, compress) {
209
+ if (asset.original === void 0) throw new Error("preparePassthroughImage: asset carries no original stream");
210
+ const png = preparePngImage(base64ToBytes(asset.base64), compress);
211
+ const entries = /* @__PURE__ */ new Map([
212
+ ["Type", pdfName("XObject")],
213
+ ["Subtype", pdfName("Image")],
214
+ ["Width", pdfNum(asset.widthPx)],
215
+ ["Height", pdfNum(asset.heightPx)],
216
+ ["Filter", pdfName(asset.original.filter === "jbig2" ? "JBIG2Decode" : "JPXDecode")]
217
+ ]);
218
+ if (asset.original.filter === "jbig2") {
219
+ entries.set("ColorSpace", pdfName("DeviceGray"));
220
+ entries.set("BitsPerComponent", pdfNum(1));
221
+ }
222
+ return {
223
+ dict: pdfDict(entries),
224
+ raw: base64ToBytes(asset.original.base64),
225
+ ...png.alpha !== void 0 ? { alpha: png.alpha } : {}
226
+ };
227
+ }
208
228
  function prepareImage(asset, compress) {
229
+ if (asset.original !== void 0) return preparePassthroughImage(asset, compress);
209
230
  const bytes = base64ToBytes(asset.base64);
210
231
  return asset.format === "jpeg" ? prepareJpegImage(bytes) : preparePngImage(bytes, compress);
211
232
  }
@@ -360,9 +381,13 @@ function writePdf(doc, options = {}) {
360
381
  const prepared = prepareImage(asset, compress);
361
382
  const imageNum = nextObjNum++;
362
383
  const smaskNum = prepared.alpha === void 0 ? void 0 : nextObjNum++;
384
+ const globalsBase64 = asset.original?.filter === "jbig2" ? asset.original.jbig2GlobalsBase64 : void 0;
385
+ const globalsNum = globalsBase64 !== void 0 ? nextObjNum++ : void 0;
363
386
  imageAllocs.set(imageId, {
364
387
  imageNum,
365
388
  smaskNum,
389
+ globalsNum,
390
+ globalsBase64,
366
391
  resourceName: `Im${index + 1}`,
367
392
  prepared
368
393
  });
@@ -707,6 +732,14 @@ function writePdf(doc, options = {}) {
707
732
  value: pdfStream(alloc.prepared.alpha.dict, alloc.prepared.alpha.raw)
708
733
  });
709
734
  }
735
+ if (alloc.globalsNum !== void 0 && alloc.globalsBase64 !== void 0) {
736
+ const globalsRaw = base64ToBytes(alloc.globalsBase64);
737
+ alloc.prepared.dict.entries.set("DecodeParms", pdfDict({ JBIG2Globals: pdfRef(alloc.globalsNum, 0) }));
738
+ objects.push({
739
+ num: alloc.globalsNum,
740
+ value: pdfStream(pdfDict(compress ? /* @__PURE__ */ new Map([["Filter", pdfName("FlateDecode")]]) : /* @__PURE__ */ new Map()), compress ? deflate(globalsRaw) : globalsRaw)
741
+ });
742
+ }
710
743
  objects.push({
711
744
  num: alloc.imageNum,
712
745
  value: pdfStream(alloc.prepared.dict, alloc.prepared.raw)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pdf-parser.js",
3
- "version": "4.6.0",
3
+ "version": "4.7.0",
4
4
  "description": "Hand-written, dependency-minimal PDF codec: parses arbitrary real-world PDFs and generates new ones, built on its own codec-owned LayoutDocument item model and Zod 4 codecs.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -72,8 +72,8 @@
72
72
  ],
73
73
  "license": "MIT",
74
74
  "dependencies": {
75
- "byte-codec": "1.4.2",
76
- "document-schema.js": "7.10.0",
75
+ "byte-codec": "1.5.0",
76
+ "document-schema.js": "7.11.0",
77
77
  "fflate": "0.8.3",
78
78
  "zod": "4.4.3"
79
79
  },