@intactfile/engines 0.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/README.md +68 -0
- package/dist/archive/diagnose.d.ts +37 -0
- package/dist/archive/diagnose.d.ts.map +1 -0
- package/dist/archive/diagnose.js +165 -0
- package/dist/archive/diagnose.js.map +1 -0
- package/dist/archive/format.d.ts +52 -0
- package/dist/archive/format.d.ts.map +1 -0
- package/dist/archive/format.js +87 -0
- package/dist/archive/format.js.map +1 -0
- package/dist/archive/host.d.ts +83 -0
- package/dist/archive/host.d.ts.map +1 -0
- package/dist/archive/host.js +92 -0
- package/dist/archive/host.js.map +1 -0
- package/dist/archive/index.d.ts +22 -0
- package/dist/archive/index.d.ts.map +1 -0
- package/dist/archive/index.js +19 -0
- package/dist/archive/index.js.map +1 -0
- package/dist/archive/salvage.d.ts +73 -0
- package/dist/archive/salvage.d.ts.map +1 -0
- package/dist/archive/salvage.js +210 -0
- package/dist/archive/salvage.js.map +1 -0
- package/dist/byte-source.d.ts +19 -0
- package/dist/byte-source.d.ts.map +1 -0
- package/dist/byte-source.js +16 -0
- package/dist/byte-source.js.map +1 -0
- package/dist/bytesource/blob.d.ts +14 -0
- package/dist/bytesource/blob.d.ts.map +1 -0
- package/dist/bytesource/blob.js +23 -0
- package/dist/bytesource/blob.js.map +1 -0
- package/dist/crc32.d.ts +2 -0
- package/dist/crc32.d.ts.map +1 -0
- package/dist/crc32.js +18 -0
- package/dist/crc32.js.map +1 -0
- package/dist/detect/index.d.ts +16 -0
- package/dist/detect/index.d.ts.map +1 -0
- package/dist/detect/index.js +159 -0
- package/dist/detect/index.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/jpeg/browser-loader.d.ts +21 -0
- package/dist/jpeg/browser-loader.d.ts.map +1 -0
- package/dist/jpeg/browser-loader.js +28 -0
- package/dist/jpeg/browser-loader.js.map +1 -0
- package/dist/jpeg/diagnose.d.ts +50 -0
- package/dist/jpeg/diagnose.d.ts.map +1 -0
- package/dist/jpeg/diagnose.js +124 -0
- package/dist/jpeg/diagnose.js.map +1 -0
- package/dist/jpeg/exif.d.ts +11 -0
- package/dist/jpeg/exif.d.ts.map +1 -0
- package/dist/jpeg/exif.js +50 -0
- package/dist/jpeg/exif.js.map +1 -0
- package/dist/jpeg/format.d.ts +106 -0
- package/dist/jpeg/format.d.ts.map +1 -0
- package/dist/jpeg/format.js +248 -0
- package/dist/jpeg/format.js.map +1 -0
- package/dist/jpeg/index.d.ts +18 -0
- package/dist/jpeg/index.d.ts.map +1 -0
- package/dist/jpeg/index.js +14 -0
- package/dist/jpeg/index.js.map +1 -0
- package/dist/jpeg/mozjpeg-host.d.ts +60 -0
- package/dist/jpeg/mozjpeg-host.d.ts.map +1 -0
- package/dist/jpeg/mozjpeg-host.js +138 -0
- package/dist/jpeg/mozjpeg-host.js.map +1 -0
- package/dist/jpeg/repair.d.ts +89 -0
- package/dist/jpeg/repair.d.ts.map +1 -0
- package/dist/jpeg/repair.js +216 -0
- package/dist/jpeg/repair.js.map +1 -0
- package/dist/node.d.ts +11 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +29 -0
- package/dist/node.js.map +1 -0
- package/dist/pdf/browser-loader.d.ts +30 -0
- package/dist/pdf/browser-loader.d.ts.map +1 -0
- package/dist/pdf/browser-loader.js +21 -0
- package/dist/pdf/browser-loader.js.map +1 -0
- package/dist/pdf/diagnose.d.ts +23 -0
- package/dist/pdf/diagnose.d.ts.map +1 -0
- package/dist/pdf/diagnose.js +113 -0
- package/dist/pdf/diagnose.js.map +1 -0
- package/dist/pdf/format.d.ts +61 -0
- package/dist/pdf/format.d.ts.map +1 -0
- package/dist/pdf/format.js +150 -0
- package/dist/pdf/format.js.map +1 -0
- package/dist/pdf/index.d.ts +17 -0
- package/dist/pdf/index.d.ts.map +1 -0
- package/dist/pdf/index.js +12 -0
- package/dist/pdf/index.js.map +1 -0
- package/dist/pdf/qpdf-host.d.ts +66 -0
- package/dist/pdf/qpdf-host.d.ts.map +1 -0
- package/dist/pdf/qpdf-host.js +149 -0
- package/dist/pdf/qpdf-host.js.map +1 -0
- package/dist/pdf/rebuild.d.ts +32 -0
- package/dist/pdf/rebuild.d.ts.map +1 -0
- package/dist/pdf/rebuild.js +90 -0
- package/dist/pdf/rebuild.js.map +1 -0
- package/dist/pdf/repair.d.ts +61 -0
- package/dist/pdf/repair.d.ts.map +1 -0
- package/dist/pdf/repair.js +167 -0
- package/dist/pdf/repair.js.map +1 -0
- package/dist/png/diagnose.d.ts +50 -0
- package/dist/png/diagnose.d.ts.map +1 -0
- package/dist/png/diagnose.js +261 -0
- package/dist/png/diagnose.js.map +1 -0
- package/dist/png/format.d.ts +133 -0
- package/dist/png/format.d.ts.map +1 -0
- package/dist/png/format.js +350 -0
- package/dist/png/format.js.map +1 -0
- package/dist/png/index.d.ts +13 -0
- package/dist/png/index.d.ts.map +1 -0
- package/dist/png/index.js +10 -0
- package/dist/png/index.js.map +1 -0
- package/dist/png/repair.d.ts +31 -0
- package/dist/png/repair.d.ts.map +1 -0
- package/dist/png/repair.js +335 -0
- package/dist/png/repair.js.map +1 -0
- package/dist/sqlite/diagnose.d.ts +31 -0
- package/dist/sqlite/diagnose.d.ts.map +1 -0
- package/dist/sqlite/diagnose.js +169 -0
- package/dist/sqlite/diagnose.js.map +1 -0
- package/dist/sqlite/format.d.ts +103 -0
- package/dist/sqlite/format.d.ts.map +1 -0
- package/dist/sqlite/format.js +237 -0
- package/dist/sqlite/format.js.map +1 -0
- package/dist/sqlite/index.d.ts +24 -0
- package/dist/sqlite/index.d.ts.map +1 -0
- package/dist/sqlite/index.js +19 -0
- package/dist/sqlite/index.js.map +1 -0
- package/dist/sqlite/recover.d.ts +66 -0
- package/dist/sqlite/recover.d.ts.map +1 -0
- package/dist/sqlite/recover.js +305 -0
- package/dist/sqlite/recover.js.map +1 -0
- package/dist/sqlite/scan.d.ts +94 -0
- package/dist/sqlite/scan.d.ts.map +1 -0
- package/dist/sqlite/scan.js +356 -0
- package/dist/sqlite/scan.js.map +1 -0
- package/dist/sqlite/sqlite-host.d.ts +37 -0
- package/dist/sqlite/sqlite-host.d.ts.map +1 -0
- package/dist/sqlite/sqlite-host.js +80 -0
- package/dist/sqlite/sqlite-host.js.map +1 -0
- package/dist/sqlite/wal.d.ts +14 -0
- package/dist/sqlite/wal.d.ts.map +1 -0
- package/dist/sqlite/wal.js +79 -0
- package/dist/sqlite/wal.js.map +1 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/video/diagnose.d.ts +41 -0
- package/dist/video/diagnose.d.ts.map +1 -0
- package/dist/video/diagnose.js +188 -0
- package/dist/video/diagnose.js.map +1 -0
- package/dist/video/format.d.ts +87 -0
- package/dist/video/format.d.ts.map +1 -0
- package/dist/video/format.js +273 -0
- package/dist/video/format.js.map +1 -0
- package/dist/video/index.d.ts +21 -0
- package/dist/video/index.d.ts.map +1 -0
- package/dist/video/index.js +17 -0
- package/dist/video/index.js.map +1 -0
- package/dist/video/libav-host.d.ts +88 -0
- package/dist/video/libav-host.d.ts.map +1 -0
- package/dist/video/libav-host.js +66 -0
- package/dist/video/libav-host.js.map +1 -0
- package/dist/video/mp4-rebuild.d.ts +167 -0
- package/dist/video/mp4-rebuild.d.ts.map +1 -0
- package/dist/video/mp4-rebuild.js +270 -0
- package/dist/video/mp4-rebuild.js.map +1 -0
- package/dist/video/mp4box-host.d.ts +62 -0
- package/dist/video/mp4box-host.d.ts.map +1 -0
- package/dist/video/mp4box-host.js +209 -0
- package/dist/video/mp4box-host.js.map +1 -0
- package/dist/video/reconstruct.d.ts +97 -0
- package/dist/video/reconstruct.d.ts.map +1 -0
- package/dist/video/reconstruct.js +271 -0
- package/dist/video/reconstruct.js.map +1 -0
- package/dist/video/remux.d.ts +85 -0
- package/dist/video/remux.d.ts.map +1 -0
- package/dist/video/remux.js +350 -0
- package/dist/video/remux.js.map +1 -0
- package/dist/zip/diagnose.d.ts +22 -0
- package/dist/zip/diagnose.d.ts.map +1 -0
- package/dist/zip/diagnose.js +126 -0
- package/dist/zip/diagnose.js.map +1 -0
- package/dist/zip/format.d.ts +79 -0
- package/dist/zip/format.d.ts.map +1 -0
- package/dist/zip/format.js +129 -0
- package/dist/zip/format.js.map +1 -0
- package/dist/zip/repair.d.ts +32 -0
- package/dist/zip/repair.d.ts.map +1 -0
- package/dist/zip/repair.js +82 -0
- package/dist/zip/repair.js.map +1 -0
- package/dist/zip/salvage.d.ts +23 -0
- package/dist/zip/salvage.d.ts.map +1 -0
- package/dist/zip/salvage.js +140 -0
- package/dist/zip/salvage.js.map +1 -0
- package/dist/zip/scan.d.ts +64 -0
- package/dist/zip/scan.d.ts.map +1 -0
- package/dist/zip/scan.js +386 -0
- package/dist/zip/scan.js.map +1 -0
- package/dist/zip/writer.d.ts +14 -0
- package/dist/zip/writer.d.ts.map +1 -0
- package/dist/zip/writer.js +131 -0
- package/dist/zip/writer.js.map +1 -0
- package/package.json +71 -0
- package/src/archive/LICENSE-unrar.txt +53 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Low-level PDF byte helpers shared by diagnosis and repair. Pure functions,
|
|
3
|
+
* no dependency on qpdf.
|
|
4
|
+
*/
|
|
5
|
+
/** How far into the file we look for the %PDF- header (spec says 1024; we are tolerant). */
|
|
6
|
+
export declare const PDF_HEADER_SCAN_WINDOW: number;
|
|
7
|
+
/** How much of the tail we scan for startxref / trailer / %%EOF. */
|
|
8
|
+
export declare const PDF_TAIL_SCAN_WINDOW: number;
|
|
9
|
+
/**
|
|
10
|
+
* Lossless byte-per-char decode so offsets in the string equal byte offsets.
|
|
11
|
+
* Deliberately not TextDecoder('latin1'): per WHATWG that is windows-1252,
|
|
12
|
+
* which remaps 0x80-0x9F and breaks binary round-trips.
|
|
13
|
+
*/
|
|
14
|
+
export declare function toLatin1(bytes: Uint8Array): string;
|
|
15
|
+
export declare function fromLatin1(text: string): Uint8Array;
|
|
16
|
+
export declare function indexOfBytes(haystack: Uint8Array, needle: string, from?: number): number;
|
|
17
|
+
export declare function lastIndexOfBytes(haystack: Uint8Array, needle: string): number;
|
|
18
|
+
/** Position of '%PDF-' within the window, or -1. */
|
|
19
|
+
export declare function findPdfHeader(window: Uint8Array): number;
|
|
20
|
+
/** "1.7" from a window positioned at the %PDF- header, or null. */
|
|
21
|
+
export declare function parsePdfVersion(window: Uint8Array, headerOffset: number): string | null;
|
|
22
|
+
export interface PdfTailInfo {
|
|
23
|
+
/** Absolute offset of the last %%EOF marker, or -1. */
|
|
24
|
+
eofOffset: number;
|
|
25
|
+
/** Absolute offset of the last `startxref` keyword, or -1. */
|
|
26
|
+
startxrefOffset: number;
|
|
27
|
+
/** The number following `startxref` (an offset into the file), or null. */
|
|
28
|
+
startxrefTarget: number | null;
|
|
29
|
+
/** Absolute offset of the last `trailer` keyword, or -1. */
|
|
30
|
+
trailerOffset: number;
|
|
31
|
+
/** A `/Encrypt` key appears in the tail (trailer or xref stream dict). */
|
|
32
|
+
hasEncrypt: boolean;
|
|
33
|
+
}
|
|
34
|
+
/** Scan a tail window (last bytes of the file) for the closing PDF structures. */
|
|
35
|
+
export declare function scanPdfTail(tail: Uint8Array, tailBase: number): PdfTailInfo;
|
|
36
|
+
/** True when the bytes at an xref target look like a classic xref table or an xref stream object. */
|
|
37
|
+
export declare function looksLikeXrefStart(probe: Uint8Array): boolean;
|
|
38
|
+
/** Parse "OFFSET 00000 n" of the first in-use entry of a classic xref table, or null. */
|
|
39
|
+
export declare function firstInUseXrefEntry(probe: Uint8Array): {
|
|
40
|
+
objectOffset: number;
|
|
41
|
+
} | null;
|
|
42
|
+
export interface ScannedPdfObject {
|
|
43
|
+
num: number;
|
|
44
|
+
gen: number;
|
|
45
|
+
/** Byte offset of the object number token. */
|
|
46
|
+
offset: number;
|
|
47
|
+
/** Offset just past the matching `endobj`, when present. */
|
|
48
|
+
end: number | undefined;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Forward scan for `N G obj ... endobj` object frames. Used by diagnosis
|
|
52
|
+
* spot-checks and by the xref rebuild pre-pass. Objects hidden inside object
|
|
53
|
+
* streams are invisible to this scan (documented limitation).
|
|
54
|
+
*/
|
|
55
|
+
export declare function scanIndirectObjects(bytes: Uint8Array): ScannedPdfObject[];
|
|
56
|
+
/** Extract the `/Root N G R` reference from a trailer-ish region of text. */
|
|
57
|
+
export declare function findRootRef(text: string): {
|
|
58
|
+
num: number;
|
|
59
|
+
gen: number;
|
|
60
|
+
} | null;
|
|
61
|
+
//# sourceMappingURL=format.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/pdf/format.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,4FAA4F;AAC5F,eAAO,MAAM,sBAAsB,QAAY,CAAC;AAChD,oEAAoE;AACpE,eAAO,MAAM,oBAAoB,QAAY,CAAC;AAE9C;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAOlD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAInD;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,SAAI,GAAG,MAAM,CAQnF;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAQ7E;AAED,oDAAoD;AACpD,wBAAgB,aAAa,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAExD;AAED,mEAAmE;AACnE,wBAAgB,eAAe,CAAC,MAAM,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGvF;AAED,MAAM,WAAW,WAAW;IAC1B,uDAAuD;IACvD,SAAS,EAAE,MAAM,CAAC;IAClB,8DAA8D;IAC9D,eAAe,EAAE,MAAM,CAAC;IACxB,2EAA2E;IAC3E,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,4DAA4D;IAC5D,aAAa,EAAE,MAAM,CAAC;IACtB,0EAA0E;IAC1E,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,kFAAkF;AAClF,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,WAAW,CAiB3E;AAED,qGAAqG;AACrG,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAQ7D;AAED,yFAAyF;AACzF,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,GAAG;IAAE,YAAY,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAatF;AAED,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,8CAA8C;IAC9C,MAAM,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;CACzB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,GAAG,gBAAgB,EAAE,CAmBzE;AAgBD,6EAA6E;AAC7E,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAG7E"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Low-level PDF byte helpers shared by diagnosis and repair. Pure functions,
|
|
3
|
+
* no dependency on qpdf.
|
|
4
|
+
*/
|
|
5
|
+
/** How far into the file we look for the %PDF- header (spec says 1024; we are tolerant). */
|
|
6
|
+
export const PDF_HEADER_SCAN_WINDOW = 64 * 1024;
|
|
7
|
+
/** How much of the tail we scan for startxref / trailer / %%EOF. */
|
|
8
|
+
export const PDF_TAIL_SCAN_WINDOW = 32 * 1024;
|
|
9
|
+
/**
|
|
10
|
+
* Lossless byte-per-char decode so offsets in the string equal byte offsets.
|
|
11
|
+
* Deliberately not TextDecoder('latin1'): per WHATWG that is windows-1252,
|
|
12
|
+
* which remaps 0x80-0x9F and breaks binary round-trips.
|
|
13
|
+
*/
|
|
14
|
+
export function toLatin1(bytes) {
|
|
15
|
+
let out = '';
|
|
16
|
+
const CHUNK = 0x8000;
|
|
17
|
+
for (let i = 0; i < bytes.length; i += CHUNK) {
|
|
18
|
+
out += String.fromCharCode(...bytes.subarray(i, i + CHUNK));
|
|
19
|
+
}
|
|
20
|
+
return out;
|
|
21
|
+
}
|
|
22
|
+
export function fromLatin1(text) {
|
|
23
|
+
const out = new Uint8Array(text.length);
|
|
24
|
+
for (let i = 0; i < text.length; i++)
|
|
25
|
+
out[i] = text.charCodeAt(i) & 0xff;
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
export function indexOfBytes(haystack, needle, from = 0) {
|
|
29
|
+
outer: for (let i = from; i + needle.length <= haystack.length; i++) {
|
|
30
|
+
for (let j = 0; j < needle.length; j++) {
|
|
31
|
+
if (haystack[i + j] !== needle.charCodeAt(j))
|
|
32
|
+
continue outer;
|
|
33
|
+
}
|
|
34
|
+
return i;
|
|
35
|
+
}
|
|
36
|
+
return -1;
|
|
37
|
+
}
|
|
38
|
+
export function lastIndexOfBytes(haystack, needle) {
|
|
39
|
+
outer: for (let i = haystack.length - needle.length; i >= 0; i--) {
|
|
40
|
+
for (let j = 0; j < needle.length; j++) {
|
|
41
|
+
if (haystack[i + j] !== needle.charCodeAt(j))
|
|
42
|
+
continue outer;
|
|
43
|
+
}
|
|
44
|
+
return i;
|
|
45
|
+
}
|
|
46
|
+
return -1;
|
|
47
|
+
}
|
|
48
|
+
/** Position of '%PDF-' within the window, or -1. */
|
|
49
|
+
export function findPdfHeader(window) {
|
|
50
|
+
return indexOfBytes(window, '%PDF-');
|
|
51
|
+
}
|
|
52
|
+
/** "1.7" from a window positioned at the %PDF- header, or null. */
|
|
53
|
+
export function parsePdfVersion(window, headerOffset) {
|
|
54
|
+
const m = /^%PDF-(\d+\.\d+)/.exec(toLatin1(window.subarray(headerOffset, headerOffset + 16)));
|
|
55
|
+
return m ? m[1] : null;
|
|
56
|
+
}
|
|
57
|
+
/** Scan a tail window (last bytes of the file) for the closing PDF structures. */
|
|
58
|
+
export function scanPdfTail(tail, tailBase) {
|
|
59
|
+
const text = toLatin1(tail);
|
|
60
|
+
const eof = text.lastIndexOf('%%EOF');
|
|
61
|
+
const sx = text.lastIndexOf('startxref');
|
|
62
|
+
let target = null;
|
|
63
|
+
if (sx !== -1) {
|
|
64
|
+
const m = /^startxref[ \t]*(?:\r\n|\r|\n)[ \t]*(\d{1,15})/.exec(text.slice(sx, sx + 64));
|
|
65
|
+
if (m)
|
|
66
|
+
target = Number(m[1]);
|
|
67
|
+
}
|
|
68
|
+
const trailer = text.lastIndexOf('trailer');
|
|
69
|
+
return {
|
|
70
|
+
eofOffset: eof === -1 ? -1 : tailBase + eof,
|
|
71
|
+
startxrefOffset: sx === -1 ? -1 : tailBase + sx,
|
|
72
|
+
startxrefTarget: target,
|
|
73
|
+
trailerOffset: trailer === -1 ? -1 : tailBase + trailer,
|
|
74
|
+
hasEncrypt: /\/Encrypt[^a-zA-Z]/.test(text),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/** True when the bytes at an xref target look like a classic xref table or an xref stream object. */
|
|
78
|
+
export function looksLikeXrefStart(probe) {
|
|
79
|
+
const text = toLatin1(probe).replace(/^[\s\0]*/, '');
|
|
80
|
+
if (text.startsWith('xref')) {
|
|
81
|
+
// Classic table: "xref" then "start count" then 20-byte entries.
|
|
82
|
+
return /^xref\s+\d+\s+\d+\s/.test(text);
|
|
83
|
+
}
|
|
84
|
+
// Cross-reference stream: "N G obj".
|
|
85
|
+
return /^\d{1,9}\s+\d{1,5}\s+obj\b/.test(text);
|
|
86
|
+
}
|
|
87
|
+
/** Parse "OFFSET 00000 n" of the first in-use entry of a classic xref table, or null. */
|
|
88
|
+
export function firstInUseXrefEntry(probe) {
|
|
89
|
+
const text = toLatin1(probe);
|
|
90
|
+
const m = /^xref\s+\d+\s+\d+\s*(?:\r\n|\r|\n)/.exec(text);
|
|
91
|
+
if (!m)
|
|
92
|
+
return null;
|
|
93
|
+
const entryRe = /(\d{10})\s(\d{5})\s([fn])/g;
|
|
94
|
+
entryRe.lastIndex = m[0].length;
|
|
95
|
+
let e;
|
|
96
|
+
while ((e = entryRe.exec(text)) !== null) {
|
|
97
|
+
if (e[3] === 'n')
|
|
98
|
+
return { objectOffset: Number(e[1]) };
|
|
99
|
+
// Only walk a handful of entries; this is a spot check.
|
|
100
|
+
if (entryRe.lastIndex > m[0].length + 20 * 32)
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Forward scan for `N G obj ... endobj` object frames. Used by diagnosis
|
|
107
|
+
* spot-checks and by the xref rebuild pre-pass. Objects hidden inside object
|
|
108
|
+
* streams are invisible to this scan (documented limitation).
|
|
109
|
+
*/
|
|
110
|
+
export function scanIndirectObjects(bytes) {
|
|
111
|
+
const text = toLatin1(bytes);
|
|
112
|
+
const re = /(?:^|[\r\n \t>])(\d{1,9})[ \t]+(\d{1,5})[ \t]+obj\b/g;
|
|
113
|
+
const objects = [];
|
|
114
|
+
let m;
|
|
115
|
+
while ((m = re.exec(text)) !== null) {
|
|
116
|
+
const lead = /^\d/.test(m[0]) ? 0 : 1;
|
|
117
|
+
const offset = m.index + lead;
|
|
118
|
+
objects.push({ num: Number(m[1]), gen: Number(m[2]), offset, end: undefined });
|
|
119
|
+
// If the object carries a stream, jump past `endstream` so binary stream
|
|
120
|
+
// bytes cannot fake object headers.
|
|
121
|
+
const bodyStart = re.lastIndex;
|
|
122
|
+
const endobj = findObjectEnd(text, bodyStart);
|
|
123
|
+
if (endobj !== undefined) {
|
|
124
|
+
objects[objects.length - 1].end = endobj;
|
|
125
|
+
re.lastIndex = endobj;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return objects;
|
|
129
|
+
}
|
|
130
|
+
function findObjectEnd(text, bodyStart) {
|
|
131
|
+
const streamAt = text.indexOf('stream', bodyStart);
|
|
132
|
+
const endobjAt = text.indexOf('endobj', bodyStart);
|
|
133
|
+
if (endobjAt === -1)
|
|
134
|
+
return undefined;
|
|
135
|
+
if (streamAt !== -1 && streamAt < endobjAt && text.slice(streamAt - 3, streamAt) !== 'end') {
|
|
136
|
+
// Stream body: skip to endstream, then require endobj after it.
|
|
137
|
+
const endstream = text.indexOf('endstream', streamAt);
|
|
138
|
+
if (endstream === -1)
|
|
139
|
+
return undefined;
|
|
140
|
+
const after = text.indexOf('endobj', endstream);
|
|
141
|
+
return after === -1 ? undefined : after + 'endobj'.length;
|
|
142
|
+
}
|
|
143
|
+
return endobjAt + 'endobj'.length;
|
|
144
|
+
}
|
|
145
|
+
/** Extract the `/Root N G R` reference from a trailer-ish region of text. */
|
|
146
|
+
export function findRootRef(text) {
|
|
147
|
+
const m = /\/Root[\s\0]+(\d{1,9})[\s\0]+(\d{1,5})[\s\0]+R\b/.exec(text);
|
|
148
|
+
return m ? { num: Number(m[1]), gen: Number(m[2]) } : null;
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/pdf/format.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,4FAA4F;AAC5F,MAAM,CAAC,MAAM,sBAAsB,GAAG,EAAE,GAAG,IAAI,CAAC;AAChD,oEAAoE;AACpE,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,GAAG,IAAI,CAAC;AAE9C;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAiB;IACxC,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,MAAM,KAAK,GAAG,MAAM,CAAC;IACrB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC;QAC7C,GAAG,IAAI,MAAM,CAAC,YAAY,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACxC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IACzE,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,QAAoB,EAAE,MAAc,EAAE,IAAI,GAAG,CAAC;IACzE,KAAK,EAAE,KAAK,IAAI,CAAC,GAAG,IAAI,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACpE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACvC,IAAI,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;gBAAE,SAAS,KAAK,CAAC;QAC/D,CAAC;QACD,OAAO,CAAC,CAAC;IACX,CAAC;IACD,OAAO,CAAC,CAAC,CAAC;AACZ,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,QAAoB,EAAE,MAAc;IACnE,KAAK,EAAE,KAAK,IAAI,CAAC,GAAG,QAAQ,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QACjE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACvC,IAAI,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;gBAAE,SAAS,KAAK,CAAC;QAC/D,CAAC;QACD,OAAO,CAAC,CAAC;IACX,CAAC;IACD,OAAO,CAAC,CAAC,CAAC;AACZ,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,aAAa,CAAC,MAAkB;IAC9C,OAAO,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AACvC,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,eAAe,CAAC,MAAkB,EAAE,YAAoB;IACtE,MAAM,CAAC,GAAG,kBAAkB,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,EAAE,YAAY,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;IAC9F,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAC1B,CAAC;AAeD,kFAAkF;AAClF,MAAM,UAAU,WAAW,CAAC,IAAgB,EAAE,QAAgB;IAC5D,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC5B,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IACtC,MAAM,EAAE,GAAG,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC;IACzC,IAAI,MAAM,GAAkB,IAAI,CAAC;IACjC,IAAI,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC;QACd,MAAM,CAAC,GAAG,gDAAgD,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;QACzF,IAAI,CAAC;YAAE,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/B,CAAC;IACD,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;IAC5C,OAAO;QACL,SAAS,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,GAAG,GAAG;QAC3C,eAAe,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,GAAG,EAAE;QAC/C,eAAe,EAAE,MAAM;QACvB,aAAa,EAAE,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,GAAG,OAAO;QACvD,UAAU,EAAE,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC;KAC5C,CAAC;AACJ,CAAC;AAED,qGAAqG;AACrG,MAAM,UAAU,kBAAkB,CAAC,KAAiB;IAClD,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IACrD,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QAC5B,iEAAiE;QACjE,OAAO,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1C,CAAC;IACD,qCAAqC;IACrC,OAAO,4BAA4B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACjD,CAAC;AAED,yFAAyF;AACzF,MAAM,UAAU,mBAAmB,CAAC,KAAiB;IACnD,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC7B,MAAM,CAAC,GAAG,oCAAoC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC1D,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACpB,MAAM,OAAO,GAAG,4BAA4B,CAAC;IAC7C,OAAO,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAChC,IAAI,CAAyB,CAAC;IAC9B,OAAO,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QACzC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG;YAAE,OAAO,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACxD,wDAAwD;QACxD,IAAI,OAAO,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,EAAE,GAAG,EAAE;YAAE,MAAM;IACvD,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAWD;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAiB;IACnD,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC7B,MAAM,EAAE,GAAG,sDAAsD,CAAC;IAClE,MAAM,OAAO,GAAuB,EAAE,CAAC;IACvC,IAAI,CAAyB,CAAC;IAC9B,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QACpC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACtC,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,GAAG,IAAI,CAAC;QAC9B,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,CAAC;QAC/E,yEAAyE;QACzE,oCAAoC;QACpC,MAAM,SAAS,GAAG,EAAE,CAAC,SAAS,CAAC;QAC/B,MAAM,MAAM,GAAG,aAAa,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QAC9C,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC,GAAG,GAAG,MAAM,CAAC;YAC1C,EAAE,CAAC,SAAS,GAAG,MAAM,CAAC;QACxB,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAS,aAAa,CAAC,IAAY,EAAE,SAAiB;IACpD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IACnD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IACnD,IAAI,QAAQ,KAAK,CAAC,CAAC;QAAE,OAAO,SAAS,CAAC;IACtC,IAAI,QAAQ,KAAK,CAAC,CAAC,IAAI,QAAQ,GAAG,QAAQ,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,EAAE,QAAQ,CAAC,KAAK,KAAK,EAAE,CAAC;QAC3F,gEAAgE;QAChE,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;QACtD,IAAI,SAAS,KAAK,CAAC,CAAC;YAAE,OAAO,SAAS,CAAC;QACvC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;QAChD,OAAO,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC;IAC5D,CAAC;IACD,OAAO,QAAQ,GAAG,QAAQ,CAAC,MAAM,CAAC;AACpC,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,MAAM,CAAC,GAAG,kDAAkD,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACxE,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAC7D,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PDF engine subpath export (`@intactfile/engines/pdf`). Kept out of the root
|
|
3
|
+
* export so browser bundles that never repair PDFs never see the qpdf wasm
|
|
4
|
+
* dependency; even here, the wasm is only loaded on the first repairPdf call.
|
|
5
|
+
*/
|
|
6
|
+
export { diagnosePdf } from './diagnose.js';
|
|
7
|
+
export type { PdfDiagnosis, PdfDamageClass } from './diagnose.js';
|
|
8
|
+
export { repairPdf, qpdfArgLadder, buildRepairCandidates } from './repair.js';
|
|
9
|
+
export type { PdfRepairReport, PdfRepairAttempt, PdfRepairOutcome, PdfVerification, RepairCandidate, } from './repair.js';
|
|
10
|
+
export { rebuildXrefSkeleton } from './rebuild.js';
|
|
11
|
+
export type { XrefRebuildResult } from './rebuild.js';
|
|
12
|
+
export { runQpdf, setQpdfLoader, resetQpdfHost } from './qpdf-host.js';
|
|
13
|
+
export type { QpdfRunResult, QpdfRunSpec, QpdfLoader, QpdfFactory } from './qpdf-host.js';
|
|
14
|
+
export { createBrowserQpdfLoader, DEFAULT_QPDF_BASE_URL } from './browser-loader.js';
|
|
15
|
+
export { findPdfHeader, scanPdfTail, scanIndirectObjects, toLatin1, fromLatin1, } from './format.js';
|
|
16
|
+
export type { PdfTailInfo, ScannedPdfObject } from './format.js';
|
|
17
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/pdf/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAClE,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAC9E,YAAY,EACV,eAAe,EACf,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,eAAe,GAChB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,YAAY,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACvE,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC1F,OAAO,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACrF,OAAO,EACL,aAAa,EACb,WAAW,EACX,mBAAmB,EACnB,QAAQ,EACR,UAAU,GACX,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PDF engine subpath export (`@intactfile/engines/pdf`). Kept out of the root
|
|
3
|
+
* export so browser bundles that never repair PDFs never see the qpdf wasm
|
|
4
|
+
* dependency; even here, the wasm is only loaded on the first repairPdf call.
|
|
5
|
+
*/
|
|
6
|
+
export { diagnosePdf } from './diagnose.js';
|
|
7
|
+
export { repairPdf, qpdfArgLadder, buildRepairCandidates } from './repair.js';
|
|
8
|
+
export { rebuildXrefSkeleton } from './rebuild.js';
|
|
9
|
+
export { runQpdf, setQpdfLoader, resetQpdfHost } from './qpdf-host.js';
|
|
10
|
+
export { createBrowserQpdfLoader, DEFAULT_QPDF_BASE_URL } from './browser-loader.js';
|
|
11
|
+
export { findPdfHeader, scanPdfTail, scanIndirectObjects, toLatin1, fromLatin1, } from './format.js';
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/pdf/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAQ9E,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAEnD,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEvE,OAAO,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACrF,OAAO,EACL,aAAa,EACb,WAAW,EACX,mBAAmB,EACnB,QAAQ,EACR,UAAU,GACX,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Isolated host for the qpdf wasm build (@neslinesli93/qpdf-wasm, qpdf 12.2.0,
|
|
3
|
+
* ~1.3 MB wasm). All Emscripten plumbing lives here; nothing else in the
|
|
4
|
+
* package touches the dependency, and the module is only loaded on first use.
|
|
5
|
+
*
|
|
6
|
+
* Loading is injectable: in Node the default loader resolves the CJS factory
|
|
7
|
+
* and the .wasm file from node_modules; a browser host calls setQpdfLoader()
|
|
8
|
+
* with its own factory/wasm URL (e.g. served from R2) before using repairPdf.
|
|
9
|
+
*
|
|
10
|
+
* Known build quirks this host works around or documents:
|
|
11
|
+
* - INCOMING_MODULE_JS_API is restricted: print/printErr hooks are ignored.
|
|
12
|
+
* stdout/stderr are captured via `preRun` + `FS.init` character callbacks.
|
|
13
|
+
* - The build's `quit_` sets `process.exitCode` in Node; we save/restore it
|
|
14
|
+
* around every callMain.
|
|
15
|
+
* - libqpdf was compiled without exception catching (only the CLI translation
|
|
16
|
+
* unit gets it), so qpdf's own damaged-file recovery (`reconstruct_xref`)
|
|
17
|
+
* is dead-stripped from the wasm: any structural error is fatal (exit 2)
|
|
18
|
+
* instead of triggering reconstruction. The TS pre-pass in rebuild.ts
|
|
19
|
+
* compensates. A future in-house Emscripten build (see docs/14-tech-bom.md)
|
|
20
|
+
* restores native recovery.
|
|
21
|
+
*/
|
|
22
|
+
export interface QpdfRunResult {
|
|
23
|
+
exitCode: number;
|
|
24
|
+
stdout: string[];
|
|
25
|
+
stderr: string[];
|
|
26
|
+
/** Contents of requested output paths that qpdf actually produced. */
|
|
27
|
+
outputs: Map<string, Uint8Array>;
|
|
28
|
+
}
|
|
29
|
+
export interface QpdfRunSpec {
|
|
30
|
+
/** Files written into the wasm FS before the run (absolute paths like /in.pdf). */
|
|
31
|
+
inputs: Record<string, Uint8Array>;
|
|
32
|
+
/** CLI arguments, excluding argv[0]. */
|
|
33
|
+
args: string[];
|
|
34
|
+
/** Output paths to read back after the run (missing ones are skipped). */
|
|
35
|
+
readBack?: string[];
|
|
36
|
+
}
|
|
37
|
+
interface EmscriptenFs {
|
|
38
|
+
init(input: (() => number | null) | null, output: ((charCode: number | null) => void) | null, error: ((charCode: number | null) => void) | null): void;
|
|
39
|
+
writeFile(path: string, data: Uint8Array): void;
|
|
40
|
+
readFile(path: string): Uint8Array;
|
|
41
|
+
unlink(path: string): void;
|
|
42
|
+
}
|
|
43
|
+
interface QpdfInstance {
|
|
44
|
+
callMain(args: string[]): number;
|
|
45
|
+
FS: EmscriptenFs;
|
|
46
|
+
}
|
|
47
|
+
export type QpdfFactory = (config: Record<string, unknown>) => Promise<QpdfInstance>;
|
|
48
|
+
export interface QpdfLoader {
|
|
49
|
+
factory: QpdfFactory;
|
|
50
|
+
/** Absolute path or URL of qpdf.wasm handed to Emscripten's locateFile. */
|
|
51
|
+
locateWasm: () => string;
|
|
52
|
+
}
|
|
53
|
+
/** Inject how the qpdf factory and wasm binary are obtained (browser hosts). */
|
|
54
|
+
export declare function setQpdfLoader(load: () => Promise<QpdfLoader>): void;
|
|
55
|
+
/** Drop the cached loader (tests). */
|
|
56
|
+
export declare function resetQpdfHost(): void;
|
|
57
|
+
/**
|
|
58
|
+
* Run the qpdf CLI once inside the wasm sandbox. Every run gets a fresh
|
|
59
|
+
* instance: reusing one across many callMain invocations eventually corrupts
|
|
60
|
+
* the runtime of this build (indirect-call signature mismatch after repeated
|
|
61
|
+
* error exits), and a fresh MEMFS guarantees no state bleeds between files.
|
|
62
|
+
* Runs are serialized to bound memory use.
|
|
63
|
+
*/
|
|
64
|
+
export declare function runQpdf(spec: QpdfRunSpec): Promise<QpdfRunResult>;
|
|
65
|
+
export {};
|
|
66
|
+
//# sourceMappingURL=qpdf-host.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qpdf-host.d.ts","sourceRoot":"","sources":["../../src/pdf/qpdf-host.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,sEAAsE;IACtE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CAClC;AAED,MAAM,WAAW,WAAW;IAC1B,mFAAmF;IACnF,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACnC,wCAAwC;IACxC,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,UAAU,YAAY;IACpB,IAAI,CACF,KAAK,EAAE,CAAC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,IAAI,EACnC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC,GAAG,IAAI,EAClD,KAAK,EAAE,CAAC,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC,GAAG,IAAI,GAChD,IAAI,CAAC;IACR,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,IAAI,CAAC;IAChD,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAAC;IACnC,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,UAAU,YAAY;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;IACjC,EAAE,EAAE,YAAY,CAAC;CAClB;AAED,MAAM,MAAM,WAAW,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;AAErF,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,WAAW,CAAC;IACrB,2EAA2E;IAC3E,UAAU,EAAE,MAAM,MAAM,CAAC;CAC1B;AAKD,gFAAgF;AAChF,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,GAAG,IAAI,CAGnE;AAED,sCAAsC;AACtC,wBAAgB,aAAa,IAAI,IAAI,CAEpC;AA4ED;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,CAAC,CAKjE"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Isolated host for the qpdf wasm build (@neslinesli93/qpdf-wasm, qpdf 12.2.0,
|
|
3
|
+
* ~1.3 MB wasm). All Emscripten plumbing lives here; nothing else in the
|
|
4
|
+
* package touches the dependency, and the module is only loaded on first use.
|
|
5
|
+
*
|
|
6
|
+
* Loading is injectable: in Node the default loader resolves the CJS factory
|
|
7
|
+
* and the .wasm file from node_modules; a browser host calls setQpdfLoader()
|
|
8
|
+
* with its own factory/wasm URL (e.g. served from R2) before using repairPdf.
|
|
9
|
+
*
|
|
10
|
+
* Known build quirks this host works around or documents:
|
|
11
|
+
* - INCOMING_MODULE_JS_API is restricted: print/printErr hooks are ignored.
|
|
12
|
+
* stdout/stderr are captured via `preRun` + `FS.init` character callbacks.
|
|
13
|
+
* - The build's `quit_` sets `process.exitCode` in Node; we save/restore it
|
|
14
|
+
* around every callMain.
|
|
15
|
+
* - libqpdf was compiled without exception catching (only the CLI translation
|
|
16
|
+
* unit gets it), so qpdf's own damaged-file recovery (`reconstruct_xref`)
|
|
17
|
+
* is dead-stripped from the wasm: any structural error is fatal (exit 2)
|
|
18
|
+
* instead of triggering reconstruction. The TS pre-pass in rebuild.ts
|
|
19
|
+
* compensates. A future in-house Emscripten build (see docs/14-tech-bom.md)
|
|
20
|
+
* restores native recovery.
|
|
21
|
+
*/
|
|
22
|
+
let loaderOverride = null;
|
|
23
|
+
let loaderPromise = null;
|
|
24
|
+
/** Inject how the qpdf factory and wasm binary are obtained (browser hosts). */
|
|
25
|
+
export function setQpdfLoader(load) {
|
|
26
|
+
loaderOverride = load;
|
|
27
|
+
loaderPromise = null;
|
|
28
|
+
}
|
|
29
|
+
/** Drop the cached loader (tests). */
|
|
30
|
+
export function resetQpdfHost() {
|
|
31
|
+
loaderPromise = null;
|
|
32
|
+
}
|
|
33
|
+
async function defaultNodeLoader() {
|
|
34
|
+
const isNode = typeof process !== 'undefined' && !!process.versions?.node;
|
|
35
|
+
if (!isNode) {
|
|
36
|
+
throw new Error('qpdf host: no loader configured. In the browser, call setQpdfLoader() with the qpdf-wasm factory and a wasm URL before repairing PDFs.');
|
|
37
|
+
}
|
|
38
|
+
const { createRequire } = await import('node:module');
|
|
39
|
+
const require = createRequire(import.meta.url);
|
|
40
|
+
const factory = require('@neslinesli93/qpdf-wasm');
|
|
41
|
+
const wasmPath = require.resolve('@neslinesli93/qpdf-wasm/dist/qpdf.wasm');
|
|
42
|
+
return { factory, locateWasm: () => wasmPath };
|
|
43
|
+
}
|
|
44
|
+
let runChain = Promise.resolve();
|
|
45
|
+
function makeLineSink(push) {
|
|
46
|
+
let buf = [];
|
|
47
|
+
const flushBuf = () => {
|
|
48
|
+
if (buf.length > 0) {
|
|
49
|
+
push()(String.fromCharCode(...buf));
|
|
50
|
+
buf = [];
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
const fn = (c) => {
|
|
54
|
+
if (c === null || c === 10)
|
|
55
|
+
flushBuf();
|
|
56
|
+
else
|
|
57
|
+
buf.push(c & 0xff);
|
|
58
|
+
};
|
|
59
|
+
fn.flush = flushBuf;
|
|
60
|
+
return fn;
|
|
61
|
+
}
|
|
62
|
+
async function createInstance() {
|
|
63
|
+
loaderPromise ??= (loaderOverride ?? defaultNodeLoader)().catch((e) => {
|
|
64
|
+
loaderPromise = null;
|
|
65
|
+
throw e;
|
|
66
|
+
});
|
|
67
|
+
const { factory, locateWasm } = await loaderPromise;
|
|
68
|
+
let onOut = () => { };
|
|
69
|
+
let onErr = () => { };
|
|
70
|
+
const outSink = makeLineSink(() => onOut);
|
|
71
|
+
const errSink = makeLineSink(() => onErr);
|
|
72
|
+
// The Emscripten factory mutates and resolves with the config object itself,
|
|
73
|
+
// so `config.FS` is available by the time preRun callbacks execute.
|
|
74
|
+
const config = {
|
|
75
|
+
locateFile: () => locateWasm(),
|
|
76
|
+
preRun: [
|
|
77
|
+
() => {
|
|
78
|
+
config.FS?.init(() => null, outSink, errSink);
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
};
|
|
82
|
+
const qpdf = (await factory(config));
|
|
83
|
+
return {
|
|
84
|
+
qpdf,
|
|
85
|
+
setSinks(out, err) {
|
|
86
|
+
onOut = out;
|
|
87
|
+
onErr = err;
|
|
88
|
+
},
|
|
89
|
+
flush() {
|
|
90
|
+
outSink.flush();
|
|
91
|
+
errSink.flush();
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Run the qpdf CLI once inside the wasm sandbox. Every run gets a fresh
|
|
97
|
+
* instance: reusing one across many callMain invocations eventually corrupts
|
|
98
|
+
* the runtime of this build (indirect-call signature mismatch after repeated
|
|
99
|
+
* error exits), and a fresh MEMFS guarantees no state bleeds between files.
|
|
100
|
+
* Runs are serialized to bound memory use.
|
|
101
|
+
*/
|
|
102
|
+
export function runQpdf(spec) {
|
|
103
|
+
const run = runChain.then(() => runQpdfExclusive(spec));
|
|
104
|
+
// Keep the chain alive even when a run rejects.
|
|
105
|
+
runChain = run.catch(() => undefined);
|
|
106
|
+
return run;
|
|
107
|
+
}
|
|
108
|
+
async function runQpdfExclusive(spec) {
|
|
109
|
+
const host = await createInstance();
|
|
110
|
+
const stdout = [];
|
|
111
|
+
const stderr = [];
|
|
112
|
+
host.setSinks((l) => stdout.push(l), (l) => stderr.push(l));
|
|
113
|
+
const paths = new Set([...Object.keys(spec.inputs), ...(spec.readBack ?? [])]);
|
|
114
|
+
for (const [path, data] of Object.entries(spec.inputs)) {
|
|
115
|
+
host.qpdf.FS.writeFile(path, data);
|
|
116
|
+
}
|
|
117
|
+
const isNode = typeof process !== 'undefined' && !!process.versions?.node;
|
|
118
|
+
const savedExitCode = isNode ? process.exitCode : undefined;
|
|
119
|
+
let exitCode;
|
|
120
|
+
try {
|
|
121
|
+
// Copy: Emscripten's callMain mutates the array (unshifts argv[0]).
|
|
122
|
+
exitCode = host.qpdf.callMain([...spec.args]);
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
if (isNode)
|
|
126
|
+
process.exitCode = savedExitCode;
|
|
127
|
+
host.flush();
|
|
128
|
+
host.setSinks(() => { }, () => { });
|
|
129
|
+
}
|
|
130
|
+
const outputs = new Map();
|
|
131
|
+
for (const path of spec.readBack ?? []) {
|
|
132
|
+
try {
|
|
133
|
+
outputs.set(path, host.qpdf.FS.readFile(path));
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
// qpdf did not produce this output; reflected by its absence.
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
for (const path of paths) {
|
|
140
|
+
try {
|
|
141
|
+
host.qpdf.FS.unlink(path);
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
// Already absent.
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return { exitCode, stdout, stderr, outputs };
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=qpdf-host.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"qpdf-host.js","sourceRoot":"","sources":["../../src/pdf/qpdf-host.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface XrefRebuildResult {
|
|
2
|
+
bytes: Uint8Array;
|
|
3
|
+
/** Complete `N G obj ... endobj` frames found and indexed. */
|
|
4
|
+
objectsIndexed: number;
|
|
5
|
+
/** Objects whose `endobj` never arrives (cut off by truncation); dropped. */
|
|
6
|
+
objectsDropped: number;
|
|
7
|
+
/** Object numbers missing from 1..maxObjectNumber (holes usually mean lost data). */
|
|
8
|
+
missingNumbers: number[];
|
|
9
|
+
/** Where the /Root reference came from. */
|
|
10
|
+
rootSource: 'trailer' | 'catalog-scan';
|
|
11
|
+
notes: string[];
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Pre-pass for files whose xref/trailer is unusable: index every complete
|
|
15
|
+
* `N G obj ... endobj` frame with a forward scan and append a fresh classic
|
|
16
|
+
* xref table + trailer + startxref + %%EOF. The result is fed to qpdf, which
|
|
17
|
+
* re-parses, validates and rewrites every object properly.
|
|
18
|
+
*
|
|
19
|
+
* This mirrors what `qpdf` itself does natively when it reconstructs a
|
|
20
|
+
* damaged file; the community wasm builds ship with that recovery path
|
|
21
|
+
* compiled out (see qpdf-host.ts), so we rebuild the skeleton ourselves.
|
|
22
|
+
*
|
|
23
|
+
* Limitations (honest): objects packed inside object streams are invisible to
|
|
24
|
+
* the scan (their frames live compressed inside another object), and content
|
|
25
|
+
* physically truncated away is gone — callers must not report full success
|
|
26
|
+
* for truncated inputs.
|
|
27
|
+
*
|
|
28
|
+
* Returns null when there is nothing to rebuild from (no complete objects or
|
|
29
|
+
* no locatable document catalog).
|
|
30
|
+
*/
|
|
31
|
+
export declare function rebuildXrefSkeleton(bytes: Uint8Array): XrefRebuildResult | null;
|
|
32
|
+
//# sourceMappingURL=rebuild.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rebuild.d.ts","sourceRoot":"","sources":["../../src/pdf/rebuild.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,UAAU,CAAC;IAClB,8DAA8D;IAC9D,cAAc,EAAE,MAAM,CAAC;IACvB,6EAA6E;IAC7E,cAAc,EAAE,MAAM,CAAC;IACvB,qFAAqF;IACrF,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,2CAA2C;IAC3C,UAAU,EAAE,SAAS,GAAG,cAAc,CAAC;IACvC,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,GAAG,iBAAiB,GAAG,IAAI,CAoE/E"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { findRootRef, fromLatin1, scanIndirectObjects, toLatin1 } from './format.js';
|
|
2
|
+
/**
|
|
3
|
+
* Pre-pass for files whose xref/trailer is unusable: index every complete
|
|
4
|
+
* `N G obj ... endobj` frame with a forward scan and append a fresh classic
|
|
5
|
+
* xref table + trailer + startxref + %%EOF. The result is fed to qpdf, which
|
|
6
|
+
* re-parses, validates and rewrites every object properly.
|
|
7
|
+
*
|
|
8
|
+
* This mirrors what `qpdf` itself does natively when it reconstructs a
|
|
9
|
+
* damaged file; the community wasm builds ship with that recovery path
|
|
10
|
+
* compiled out (see qpdf-host.ts), so we rebuild the skeleton ourselves.
|
|
11
|
+
*
|
|
12
|
+
* Limitations (honest): objects packed inside object streams are invisible to
|
|
13
|
+
* the scan (their frames live compressed inside another object), and content
|
|
14
|
+
* physically truncated away is gone — callers must not report full success
|
|
15
|
+
* for truncated inputs.
|
|
16
|
+
*
|
|
17
|
+
* Returns null when there is nothing to rebuild from (no complete objects or
|
|
18
|
+
* no locatable document catalog).
|
|
19
|
+
*/
|
|
20
|
+
export function rebuildXrefSkeleton(bytes) {
|
|
21
|
+
const notes = [];
|
|
22
|
+
const objects = scanIndirectObjects(bytes);
|
|
23
|
+
const complete = objects.filter((o) => o.end !== undefined);
|
|
24
|
+
const dropped = objects.length - complete.length;
|
|
25
|
+
if (dropped > 0)
|
|
26
|
+
notes.push(`${dropped} incomplete object frame(s) dropped (missing endobj)`);
|
|
27
|
+
if (complete.length === 0)
|
|
28
|
+
return null;
|
|
29
|
+
// Last definition of an object number wins (incremental updates).
|
|
30
|
+
const byNum = new Map();
|
|
31
|
+
const text = toLatin1(bytes);
|
|
32
|
+
for (const o of complete) {
|
|
33
|
+
byNum.set(o.num, { gen: o.gen, offset: o.offset, body: text.slice(o.offset, o.end) });
|
|
34
|
+
}
|
|
35
|
+
// Prefer the /Root from a surviving trailer; fall back to scanning for the
|
|
36
|
+
// document catalog.
|
|
37
|
+
let root = null;
|
|
38
|
+
let rootSource = 'trailer';
|
|
39
|
+
const trailerAt = text.lastIndexOf('trailer');
|
|
40
|
+
if (trailerAt !== -1) {
|
|
41
|
+
root = findRootRef(text.slice(trailerAt, trailerAt + 4096));
|
|
42
|
+
}
|
|
43
|
+
if (!root || !byNum.has(root.num)) {
|
|
44
|
+
root = null;
|
|
45
|
+
rootSource = 'catalog-scan';
|
|
46
|
+
for (const [num, o] of byNum) {
|
|
47
|
+
if (/\/Type[\s\0]*\/Catalog\b/.test(o.body)) {
|
|
48
|
+
root = { num, gen: o.gen };
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (root)
|
|
53
|
+
notes.push(`/Root not found in a trailer; using object ${root.num} ${root.gen} (/Type /Catalog)`);
|
|
54
|
+
}
|
|
55
|
+
if (!root) {
|
|
56
|
+
notes.push('no document catalog found; cannot rebuild');
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
const maxNum = Math.max(...byNum.keys());
|
|
60
|
+
const missingNumbers = [];
|
|
61
|
+
for (let i = 1; i <= maxNum; i++)
|
|
62
|
+
if (!byNum.has(i))
|
|
63
|
+
missingNumbers.push(i);
|
|
64
|
+
if (missingNumbers.length > 0) {
|
|
65
|
+
notes.push(`object number(s) ${missingNumbers.join(', ')} missing; references to them are lost`);
|
|
66
|
+
}
|
|
67
|
+
let out = text;
|
|
68
|
+
if (!out.endsWith('\n') && !out.endsWith('\r'))
|
|
69
|
+
out += '\n';
|
|
70
|
+
const xrefOffset = out.length;
|
|
71
|
+
let xref = `xref\n0 ${maxNum + 1}\n0000000000 65535 f \n`;
|
|
72
|
+
for (let i = 1; i <= maxNum; i++) {
|
|
73
|
+
const o = byNum.get(i);
|
|
74
|
+
xref += o
|
|
75
|
+
? `${String(o.offset).padStart(10, '0')} ${String(o.gen).padStart(5, '0')} n \n`
|
|
76
|
+
: '0000000000 65535 f \n';
|
|
77
|
+
}
|
|
78
|
+
out +=
|
|
79
|
+
xref +
|
|
80
|
+
`trailer\n<< /Size ${maxNum + 1} /Root ${root.num} ${root.gen} R >>\nstartxref\n${xrefOffset}\n%%EOF\n`;
|
|
81
|
+
return {
|
|
82
|
+
bytes: fromLatin1(out),
|
|
83
|
+
objectsIndexed: byNum.size,
|
|
84
|
+
objectsDropped: dropped,
|
|
85
|
+
missingNumbers,
|
|
86
|
+
rootSource,
|
|
87
|
+
notes,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=rebuild.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rebuild.js","sourceRoot":"","sources":["../../src/pdf/rebuild.ts"],"names":[],"mappings":"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"}
|