@f-o-t/pdf 0.1.5 → 0.2.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 +171 -22
- package/dist/plugins/editing/index.js +768 -0
- package/dist/plugins/editing/index.js.map +13 -0
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -1,16 +1,6 @@
|
|
|
1
1
|
# @f-o-t/pdf
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- **Complete PDF 1.7 Support**: Full spec implementation
|
|
8
|
-
- **Generation**: Create PDFs from scratch with text, graphics, images
|
|
9
|
-
- **Parsing**: Read and extract content from PDFs
|
|
10
|
-
- **Digital Signatures**: Sign PDFs with @f-o-t/digital-certificate
|
|
11
|
-
- **Zod-First**: Complete validation
|
|
12
|
-
- **TypeScript-First**: Full type safety
|
|
13
|
-
- **Stream Support**: Memory-efficient processing
|
|
3
|
+
PDF 1.7 library with generation, parsing, and editing plugins.
|
|
14
4
|
|
|
15
5
|
## Installation
|
|
16
6
|
|
|
@@ -18,22 +8,181 @@ Comprehensive PDF 1.7 library with generation, parsing, and digital signatures.
|
|
|
18
8
|
bun add @f-o-t/pdf
|
|
19
9
|
```
|
|
20
10
|
|
|
21
|
-
##
|
|
11
|
+
## Plugins
|
|
12
|
+
|
|
13
|
+
The library is organized into plugins, each importable from a separate entry point.
|
|
14
|
+
|
|
15
|
+
### Generation (`@f-o-t/pdf/plugins/generation`)
|
|
16
|
+
|
|
17
|
+
Create PDFs from scratch with text, graphics, and images.
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { createDocument, createPage, addFont, writePdf } from "@f-o-t/pdf/plugins/generation";
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Parsing (`@f-o-t/pdf/plugins/parsing`)
|
|
24
|
+
|
|
25
|
+
Read and extract content from existing PDFs.
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { createLexer, createParser, createReader } from "@f-o-t/pdf/plugins/parsing";
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Editing (`@f-o-t/pdf/plugins/editing`)
|
|
32
|
+
|
|
33
|
+
Load existing PDFs, modify them, and save with incremental updates. Also supports creating signature placeholders for digital signing workflows.
|
|
34
|
+
|
|
35
|
+
#### `loadPdf(data: Uint8Array): PdfDocument`
|
|
22
36
|
|
|
23
|
-
|
|
24
|
-
|
|
37
|
+
Load an existing PDF for editing. Returns a `PdfDocument` that can be modified and saved.
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { loadPdf } from "@f-o-t/pdf/plugins/editing";
|
|
41
|
+
|
|
42
|
+
const pdfBytes = await Bun.file("document.pdf").bytes();
|
|
43
|
+
const doc = loadPdf(pdfBytes);
|
|
44
|
+
```
|
|
25
45
|
|
|
26
|
-
|
|
27
|
-
pdf.addPage()
|
|
28
|
-
.drawText("Hello, World!", { x: 50, y: 750 });
|
|
46
|
+
#### `PdfDocument`
|
|
29
47
|
|
|
30
|
-
|
|
48
|
+
```ts
|
|
49
|
+
type PdfDocument = {
|
|
50
|
+
pageCount: number;
|
|
51
|
+
getPage(index: number): PdfPage;
|
|
52
|
+
embedPng(data: Uint8Array): PdfImage;
|
|
53
|
+
save(): Uint8Array;
|
|
54
|
+
saveWithPlaceholder(options: SignaturePlaceholderOptions): {
|
|
55
|
+
pdf: Uint8Array;
|
|
56
|
+
byteRange: [number, number, number, number];
|
|
57
|
+
};
|
|
58
|
+
};
|
|
31
59
|
```
|
|
32
60
|
|
|
33
|
-
|
|
61
|
+
#### `PdfPage`
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
type PdfPage = {
|
|
65
|
+
width: number;
|
|
66
|
+
height: number;
|
|
67
|
+
drawText(text: string, options: TextOptions): void;
|
|
68
|
+
drawRectangle(options: RectOptions): void;
|
|
69
|
+
drawImage(image: PdfImage, options: ImageOptions): void;
|
|
70
|
+
};
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
#### Drawing on Pages
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import { loadPdf } from "@f-o-t/pdf/plugins/editing";
|
|
77
|
+
|
|
78
|
+
const doc = loadPdf(pdfBytes);
|
|
79
|
+
const page = doc.getPage(0);
|
|
34
80
|
|
|
35
|
-
|
|
81
|
+
// Draw text
|
|
82
|
+
page.drawText("Hello, World!", { x: 50, y: 750, size: 14, color: "#000000" });
|
|
36
83
|
|
|
37
|
-
|
|
84
|
+
// Draw a rectangle
|
|
85
|
+
page.drawRectangle({
|
|
86
|
+
x: 40,
|
|
87
|
+
y: 740,
|
|
88
|
+
width: 200,
|
|
89
|
+
height: 30,
|
|
90
|
+
borderColor: "#333333",
|
|
91
|
+
borderWidth: 1,
|
|
92
|
+
});
|
|
38
93
|
|
|
39
|
-
|
|
94
|
+
// Embed and draw a PNG image
|
|
95
|
+
const pngBytes = await Bun.file("logo.png").bytes();
|
|
96
|
+
const image = doc.embedPng(pngBytes);
|
|
97
|
+
page.drawImage(image, { x: 50, y: 600, width: 100, height: 100 });
|
|
98
|
+
|
|
99
|
+
// Save with incremental update
|
|
100
|
+
const output = doc.save();
|
|
101
|
+
await Bun.write("modified.pdf", output);
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
#### Signature Placeholders
|
|
105
|
+
|
|
106
|
+
Create a signature placeholder for digital signing workflows:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
import {
|
|
110
|
+
loadPdf,
|
|
111
|
+
findByteRange,
|
|
112
|
+
extractBytesToSign,
|
|
113
|
+
embedSignature,
|
|
114
|
+
} from "@f-o-t/pdf/plugins/editing";
|
|
115
|
+
|
|
116
|
+
const doc = loadPdf(pdfBytes);
|
|
117
|
+
|
|
118
|
+
// Save with signature placeholder
|
|
119
|
+
const { pdf } = doc.saveWithPlaceholder({
|
|
120
|
+
reason: "Document approval",
|
|
121
|
+
name: "John Doe",
|
|
122
|
+
location: "Office",
|
|
123
|
+
signatureLength: 16384,
|
|
124
|
+
docMdpPermission: 2,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Find the byte range and extract bytes to sign
|
|
128
|
+
const { byteRange } = findByteRange(pdf);
|
|
129
|
+
const bytesToSign = extractBytesToSign(pdf, byteRange);
|
|
130
|
+
|
|
131
|
+
// ... create your CMS signature over bytesToSign ...
|
|
132
|
+
|
|
133
|
+
// Embed the signature into the PDF
|
|
134
|
+
const signedPdf = embedSignature(pdf, signatureBytes);
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
#### Signature Utility Functions
|
|
138
|
+
|
|
139
|
+
| Function | Description |
|
|
140
|
+
|---|---|
|
|
141
|
+
| `findByteRange(pdf)` | Locate the `/ByteRange` in a PDF with a signature placeholder |
|
|
142
|
+
| `extractBytesToSign(pdf, byteRange)` | Extract the bytes covered by the byte range |
|
|
143
|
+
| `embedSignature(pdf, signature)` | Embed a DER-encoded signature into the placeholder |
|
|
144
|
+
|
|
145
|
+
## Types
|
|
146
|
+
|
|
147
|
+
### Editing Plugin Types
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
type TextOptions = {
|
|
151
|
+
x: number;
|
|
152
|
+
y: number;
|
|
153
|
+
size?: number; // default: 12
|
|
154
|
+
color?: string; // hex color, default: "#000000"
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
type RectOptions = {
|
|
158
|
+
x: number;
|
|
159
|
+
y: number;
|
|
160
|
+
width: number;
|
|
161
|
+
height: number;
|
|
162
|
+
color?: string;
|
|
163
|
+
borderColor?: string;
|
|
164
|
+
borderWidth?: number;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
type ImageOptions = {
|
|
168
|
+
x: number;
|
|
169
|
+
y: number;
|
|
170
|
+
width: number;
|
|
171
|
+
height: number;
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
type PdfImage = {
|
|
175
|
+
readonly objectNumber: number;
|
|
176
|
+
readonly width: number;
|
|
177
|
+
readonly height: number;
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
type SignaturePlaceholderOptions = {
|
|
181
|
+
reason?: string;
|
|
182
|
+
name?: string;
|
|
183
|
+
location?: string;
|
|
184
|
+
contactInfo?: string;
|
|
185
|
+
signatureLength?: number; // default: 16384
|
|
186
|
+
docMdpPermission?: 1 | 2 | 3; // default: 2
|
|
187
|
+
};
|
|
188
|
+
```
|
|
@@ -0,0 +1,768 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/plugins/editing/parser.ts
|
|
3
|
+
var decoder = new TextDecoder("latin1");
|
|
4
|
+
function toLatin1(data) {
|
|
5
|
+
return decoder.decode(data);
|
|
6
|
+
}
|
|
7
|
+
function findStartXref(data) {
|
|
8
|
+
const pdf = toLatin1(data);
|
|
9
|
+
const idx = pdf.lastIndexOf("startxref");
|
|
10
|
+
if (idx === -1)
|
|
11
|
+
throw new Error("Cannot find startxref in PDF");
|
|
12
|
+
const after = pdf.slice(idx + 9).trim().split(/[\r\n\s]/)[0];
|
|
13
|
+
return parseInt(after, 10);
|
|
14
|
+
}
|
|
15
|
+
function parseTrailer(data) {
|
|
16
|
+
const pdf = toLatin1(data);
|
|
17
|
+
const startxrefIdx = pdf.lastIndexOf("startxref");
|
|
18
|
+
const trailerIdx = pdf.lastIndexOf("trailer");
|
|
19
|
+
let dictStr;
|
|
20
|
+
if (trailerIdx !== -1 && trailerIdx < startxrefIdx) {
|
|
21
|
+
dictStr = pdf.slice(trailerIdx, startxrefIdx);
|
|
22
|
+
} else {
|
|
23
|
+
const xrefOffset = findStartXref(data);
|
|
24
|
+
const xrefObjStr = pdf.slice(xrefOffset, xrefOffset + 4096);
|
|
25
|
+
const dictStart = xrefObjStr.indexOf("<<");
|
|
26
|
+
if (dictStart === -1) {
|
|
27
|
+
throw new Error("Cannot find trailer or xref stream dictionary in PDF");
|
|
28
|
+
}
|
|
29
|
+
const dictEnd = findMatchingDictEnd(xrefObjStr, dictStart);
|
|
30
|
+
if (dictEnd === -1) {
|
|
31
|
+
throw new Error("Cannot find end of xref stream dictionary");
|
|
32
|
+
}
|
|
33
|
+
dictStr = xrefObjStr.slice(dictStart, dictEnd + 2);
|
|
34
|
+
}
|
|
35
|
+
const rootMatch = dictStr.match(/\/Root\s+(\d+)\s+\d+\s+R/);
|
|
36
|
+
if (!rootMatch)
|
|
37
|
+
throw new Error("Cannot find Root ref in trailer");
|
|
38
|
+
const sizeMatch = dictStr.match(/\/Size\s+(\d+)/);
|
|
39
|
+
if (!sizeMatch)
|
|
40
|
+
throw new Error("Cannot find Size in trailer");
|
|
41
|
+
const infoMatch = dictStr.match(/\/Info\s+(\d+)\s+\d+\s+R/);
|
|
42
|
+
const prevMatch = dictStr.match(/\/Prev\s+(\d+)/);
|
|
43
|
+
return {
|
|
44
|
+
root: parseInt(rootMatch[1], 10),
|
|
45
|
+
size: parseInt(sizeMatch[1], 10),
|
|
46
|
+
info: infoMatch ? parseInt(infoMatch[1], 10) : null,
|
|
47
|
+
prevXref: prevMatch ? parseInt(prevMatch[1], 10) : findStartXref(data)
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function extractObjectDictContent(data, objNum) {
|
|
51
|
+
const pdf = toLatin1(data);
|
|
52
|
+
const objRegex = new RegExp(`(?:^|\\s)${objNum}\\s+0\\s+obj`, "m");
|
|
53
|
+
const match = pdf.match(objRegex);
|
|
54
|
+
if (!match || match.index === undefined) {
|
|
55
|
+
throw new Error(`Cannot find object ${objNum} in PDF`);
|
|
56
|
+
}
|
|
57
|
+
const searchStart = match.index + match[0].length;
|
|
58
|
+
const dictStart = pdf.indexOf("<<", searchStart);
|
|
59
|
+
if (dictStart === -1 || dictStart > searchStart + 200) {
|
|
60
|
+
throw new Error(`Cannot find dictionary start for object ${objNum}`);
|
|
61
|
+
}
|
|
62
|
+
const dictEnd = findMatchingDictEnd(pdf, dictStart);
|
|
63
|
+
if (dictEnd === -1) {
|
|
64
|
+
throw new Error(`Cannot find dictionary end for object ${objNum}`);
|
|
65
|
+
}
|
|
66
|
+
return pdf.slice(dictStart + 2, dictEnd);
|
|
67
|
+
}
|
|
68
|
+
function findPageObjects(data, rootNum) {
|
|
69
|
+
const rootContent = extractObjectDictContent(data, rootNum);
|
|
70
|
+
const pagesMatch = rootContent.match(/\/Pages\s+(\d+)\s+\d+\s+R/);
|
|
71
|
+
if (!pagesMatch)
|
|
72
|
+
throw new Error("Cannot find Pages ref in Root catalog");
|
|
73
|
+
const pagesNum = parseInt(pagesMatch[1], 10);
|
|
74
|
+
const pagesContent = extractObjectDictContent(data, pagesNum);
|
|
75
|
+
const kidsMatch = pagesContent.match(/\/Kids\s*\[([^\]]+)\]/);
|
|
76
|
+
if (!kidsMatch)
|
|
77
|
+
throw new Error("Cannot find Kids array in Pages");
|
|
78
|
+
const refs = [];
|
|
79
|
+
const refRegex = /(\d+)\s+\d+\s+R/g;
|
|
80
|
+
let m;
|
|
81
|
+
while ((m = refRegex.exec(kidsMatch[1])) !== null) {
|
|
82
|
+
refs.push(parseInt(m[1], 10));
|
|
83
|
+
}
|
|
84
|
+
return refs;
|
|
85
|
+
}
|
|
86
|
+
function getMediaBox(data, pageObjNum) {
|
|
87
|
+
const content = extractObjectDictContent(data, pageObjNum);
|
|
88
|
+
const mediaBoxMatch = content.match(/\/MediaBox\s*\[\s*([\d.]+)\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)\s*\]/);
|
|
89
|
+
if (!mediaBoxMatch) {
|
|
90
|
+
throw new Error(`Cannot find MediaBox for page object ${pageObjNum}`);
|
|
91
|
+
}
|
|
92
|
+
return [
|
|
93
|
+
parseFloat(mediaBoxMatch[1]),
|
|
94
|
+
parseFloat(mediaBoxMatch[2]),
|
|
95
|
+
parseFloat(mediaBoxMatch[3]),
|
|
96
|
+
parseFloat(mediaBoxMatch[4])
|
|
97
|
+
];
|
|
98
|
+
}
|
|
99
|
+
function parsePdfStructure(data) {
|
|
100
|
+
const xrefOffset = findStartXref(data);
|
|
101
|
+
const trailer = parseTrailer(data);
|
|
102
|
+
const rootContent = extractObjectDictContent(data, trailer.root);
|
|
103
|
+
const pagesMatch = rootContent.match(/\/Pages\s+(\d+)\s+\d+\s+R/);
|
|
104
|
+
if (!pagesMatch)
|
|
105
|
+
throw new Error("Cannot find Pages ref in Root catalog");
|
|
106
|
+
const pagesNum = parseInt(pagesMatch[1], 10);
|
|
107
|
+
const pageNums = findPageObjects(data, trailer.root);
|
|
108
|
+
const pageDictContents = pageNums.map((pn) => extractObjectDictContent(data, pn));
|
|
109
|
+
return {
|
|
110
|
+
xrefOffset,
|
|
111
|
+
rootNum: trailer.root,
|
|
112
|
+
infoNum: trailer.info,
|
|
113
|
+
size: trailer.size,
|
|
114
|
+
pagesNum,
|
|
115
|
+
pageNums,
|
|
116
|
+
rootDictContent: rootContent,
|
|
117
|
+
pageDictContents
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function findMatchingDictEnd(str, startPos) {
|
|
121
|
+
let depth = 0;
|
|
122
|
+
let i = startPos;
|
|
123
|
+
while (i < str.length - 1) {
|
|
124
|
+
if (str[i] === "(") {
|
|
125
|
+
i++;
|
|
126
|
+
while (i < str.length && str[i] !== ")") {
|
|
127
|
+
if (str[i] === "\\")
|
|
128
|
+
i++;
|
|
129
|
+
i++;
|
|
130
|
+
}
|
|
131
|
+
i++;
|
|
132
|
+
} else if (str[i] === "<" && str[i + 1] === "<") {
|
|
133
|
+
depth++;
|
|
134
|
+
i += 2;
|
|
135
|
+
} else if (str[i] === ">" && str[i + 1] === ">") {
|
|
136
|
+
depth--;
|
|
137
|
+
if (depth === 0)
|
|
138
|
+
return i;
|
|
139
|
+
i += 2;
|
|
140
|
+
} else {
|
|
141
|
+
i++;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return -1;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// src/plugins/editing/page.ts
|
|
148
|
+
function parseHexColor(hex) {
|
|
149
|
+
if (!hex)
|
|
150
|
+
return null;
|
|
151
|
+
const m = hex.match(/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/);
|
|
152
|
+
if (!m)
|
|
153
|
+
return null;
|
|
154
|
+
return [
|
|
155
|
+
parseInt(m[1], 16) / 255,
|
|
156
|
+
parseInt(m[2], 16) / 255,
|
|
157
|
+
parseInt(m[3], 16) / 255
|
|
158
|
+
];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
class PdfPageImpl {
|
|
162
|
+
width;
|
|
163
|
+
height;
|
|
164
|
+
pageObjNum;
|
|
165
|
+
originalDictContent;
|
|
166
|
+
operators = [];
|
|
167
|
+
imageRefs = new Map;
|
|
168
|
+
fontObjNum = 0;
|
|
169
|
+
get dirty() {
|
|
170
|
+
return this.operators.length > 0;
|
|
171
|
+
}
|
|
172
|
+
constructor(pageObjNum, width, height, originalDictContent) {
|
|
173
|
+
this.pageObjNum = pageObjNum;
|
|
174
|
+
this.width = width;
|
|
175
|
+
this.height = height;
|
|
176
|
+
this.originalDictContent = originalDictContent;
|
|
177
|
+
}
|
|
178
|
+
drawText(text, options) {
|
|
179
|
+
const { x, y, size = 12, color } = options;
|
|
180
|
+
const rgb = parseHexColor(color);
|
|
181
|
+
if (rgb) {
|
|
182
|
+
this.operators.push(`${rgb[0].toFixed(3)} ${rgb[1].toFixed(3)} ${rgb[2].toFixed(3)} rg`);
|
|
183
|
+
}
|
|
184
|
+
const escaped = text.replace(/\\/g, "\\\\").replace(/\(/g, "\\(").replace(/\)/g, "\\)");
|
|
185
|
+
this.operators.push("BT");
|
|
186
|
+
this.operators.push(`/F1 ${size} Tf`);
|
|
187
|
+
this.operators.push(`${x} ${y} Td`);
|
|
188
|
+
this.operators.push(`(${escaped}) Tj`);
|
|
189
|
+
this.operators.push("ET");
|
|
190
|
+
}
|
|
191
|
+
drawRectangle(options) {
|
|
192
|
+
const { x, y, width, height, color, borderColor, borderWidth } = options;
|
|
193
|
+
const fillRgb = parseHexColor(color);
|
|
194
|
+
const strokeRgb = parseHexColor(borderColor);
|
|
195
|
+
if (fillRgb) {
|
|
196
|
+
this.operators.push(`${fillRgb[0].toFixed(3)} ${fillRgb[1].toFixed(3)} ${fillRgb[2].toFixed(3)} rg`);
|
|
197
|
+
}
|
|
198
|
+
if (strokeRgb) {
|
|
199
|
+
this.operators.push(`${strokeRgb[0].toFixed(3)} ${strokeRgb[1].toFixed(3)} ${strokeRgb[2].toFixed(3)} RG`);
|
|
200
|
+
}
|
|
201
|
+
if (borderWidth !== undefined) {
|
|
202
|
+
this.operators.push(`${borderWidth} w`);
|
|
203
|
+
}
|
|
204
|
+
this.operators.push(`${x} ${y} ${width} ${height} re`);
|
|
205
|
+
if (fillRgb && strokeRgb) {
|
|
206
|
+
this.operators.push("B");
|
|
207
|
+
} else if (fillRgb) {
|
|
208
|
+
this.operators.push("f");
|
|
209
|
+
} else if (strokeRgb) {
|
|
210
|
+
this.operators.push("S");
|
|
211
|
+
} else {
|
|
212
|
+
this.operators.push("f");
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
drawImage(image, options) {
|
|
216
|
+
const { x, y, width, height } = options;
|
|
217
|
+
const imgName = `Im${image.objectNumber}`;
|
|
218
|
+
this.imageRefs.set(imgName, image.objectNumber);
|
|
219
|
+
this.operators.push("q");
|
|
220
|
+
this.operators.push(`${width} 0 0 ${height} ${x} ${y} cm`);
|
|
221
|
+
this.operators.push(`/${imgName} Do`);
|
|
222
|
+
this.operators.push("Q");
|
|
223
|
+
}
|
|
224
|
+
buildContentStream() {
|
|
225
|
+
const content = this.operators.join(`
|
|
226
|
+
`);
|
|
227
|
+
return new TextEncoder().encode(content);
|
|
228
|
+
}
|
|
229
|
+
getImageRefs() {
|
|
230
|
+
return new Map(this.imageRefs);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// src/plugins/editing/document.ts
|
|
235
|
+
var BYTE_RANGE_PLACEHOLDER = "0 0000000000 0000000000 0000000000";
|
|
236
|
+
var DEFAULT_SIGNATURE_LENGTH = 16384;
|
|
237
|
+
var latin1Encoder = new TextEncoder;
|
|
238
|
+
var latin1Decoder = new TextDecoder("latin1");
|
|
239
|
+
function parsePngIhdr(data) {
|
|
240
|
+
const sig = [137, 80, 78, 71, 13, 10, 26, 10];
|
|
241
|
+
for (let i = 0;i < sig.length; i++) {
|
|
242
|
+
if (data[i] !== sig[i])
|
|
243
|
+
throw new Error("Not a valid PNG file");
|
|
244
|
+
}
|
|
245
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
246
|
+
const chunkType = latin1Decoder.decode(data.slice(12, 16));
|
|
247
|
+
if (chunkType !== "IHDR")
|
|
248
|
+
throw new Error("First PNG chunk is not IHDR");
|
|
249
|
+
return {
|
|
250
|
+
width: view.getUint32(16),
|
|
251
|
+
height: view.getUint32(20),
|
|
252
|
+
bitDepth: data[24],
|
|
253
|
+
colorType: data[25]
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function extractIdatData(data) {
|
|
257
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
258
|
+
const chunks = [];
|
|
259
|
+
let offset = 8;
|
|
260
|
+
while (offset < data.length) {
|
|
261
|
+
const chunkLen = view.getUint32(offset);
|
|
262
|
+
const chunkType = latin1Decoder.decode(data.slice(offset + 4, offset + 8));
|
|
263
|
+
if (chunkType === "IDAT") {
|
|
264
|
+
chunks.push(data.slice(offset + 8, offset + 8 + chunkLen));
|
|
265
|
+
}
|
|
266
|
+
offset += 12 + chunkLen;
|
|
267
|
+
}
|
|
268
|
+
if (chunks.length === 0)
|
|
269
|
+
throw new Error("No IDAT chunks found in PNG");
|
|
270
|
+
const totalLen = chunks.reduce((s, c) => s + c.length, 0);
|
|
271
|
+
const result = new Uint8Array(totalLen);
|
|
272
|
+
let pos = 0;
|
|
273
|
+
for (const chunk of chunks) {
|
|
274
|
+
result.set(chunk, pos);
|
|
275
|
+
pos += chunk.length;
|
|
276
|
+
}
|
|
277
|
+
return result;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
class PdfDocumentImpl {
|
|
281
|
+
originalData;
|
|
282
|
+
structure;
|
|
283
|
+
pages = [];
|
|
284
|
+
nextObjNum;
|
|
285
|
+
fontObjNum;
|
|
286
|
+
embeddedImages = [];
|
|
287
|
+
constructor(data) {
|
|
288
|
+
this.originalData = data;
|
|
289
|
+
this.structure = parsePdfStructure(data);
|
|
290
|
+
this.nextObjNum = this.structure.size;
|
|
291
|
+
this.fontObjNum = this.nextObjNum++;
|
|
292
|
+
for (let i = 0;i < this.structure.pageNums.length; i++) {
|
|
293
|
+
const pageNum = this.structure.pageNums[i];
|
|
294
|
+
const mediaBox = getMediaBox(data, pageNum);
|
|
295
|
+
const width = mediaBox[2] - mediaBox[0];
|
|
296
|
+
const height = mediaBox[3] - mediaBox[1];
|
|
297
|
+
const dictContent = this.structure.pageDictContents[i];
|
|
298
|
+
const page = new PdfPageImpl(pageNum, width, height, dictContent);
|
|
299
|
+
page.fontObjNum = this.fontObjNum;
|
|
300
|
+
this.pages.push(page);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
get pageCount() {
|
|
304
|
+
return this.pages.length;
|
|
305
|
+
}
|
|
306
|
+
getPage(index) {
|
|
307
|
+
if (index < 0 || index >= this.pages.length) {
|
|
308
|
+
throw new Error(`Page index ${index} out of range (0-${this.pages.length - 1})`);
|
|
309
|
+
}
|
|
310
|
+
return this.pages[index];
|
|
311
|
+
}
|
|
312
|
+
embedPng(data) {
|
|
313
|
+
const ihdr = parsePngIhdr(data);
|
|
314
|
+
const idatData = extractIdatData(data);
|
|
315
|
+
const objNum = this.nextObjNum++;
|
|
316
|
+
this.embeddedImages.push({
|
|
317
|
+
objNum,
|
|
318
|
+
width: ihdr.width,
|
|
319
|
+
height: ihdr.height,
|
|
320
|
+
idatData,
|
|
321
|
+
colorType: ihdr.colorType,
|
|
322
|
+
bitDepth: ihdr.bitDepth
|
|
323
|
+
});
|
|
324
|
+
return {
|
|
325
|
+
objectNumber: objNum,
|
|
326
|
+
width: ihdr.width,
|
|
327
|
+
height: ihdr.height
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
save() {
|
|
331
|
+
return this.buildIncrementalUpdate(false).pdf;
|
|
332
|
+
}
|
|
333
|
+
saveWithPlaceholder(options) {
|
|
334
|
+
return this.buildIncrementalUpdate(true, options);
|
|
335
|
+
}
|
|
336
|
+
buildIncrementalUpdate(withSignature, sigOptions) {
|
|
337
|
+
const objects = [];
|
|
338
|
+
let currentNextObj = this.nextObjNum;
|
|
339
|
+
const anyDirty = this.pages.some((p) => p.dirty);
|
|
340
|
+
if (anyDirty || this.embeddedImages.length > 0) {
|
|
341
|
+
objects.push({
|
|
342
|
+
objNum: this.fontObjNum,
|
|
343
|
+
content: "<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>"
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
for (const img of this.embeddedImages) {
|
|
347
|
+
const colorSpace = img.colorType === 2 ? "/DeviceRGB" : img.colorType === 0 ? "/DeviceGray" : "/DeviceRGB";
|
|
348
|
+
const colors = img.colorType === 2 ? 3 : 1;
|
|
349
|
+
const bpc = img.bitDepth;
|
|
350
|
+
objects.push({
|
|
351
|
+
objNum: img.objNum,
|
|
352
|
+
content: [
|
|
353
|
+
"<< /Type /XObject",
|
|
354
|
+
"/Subtype /Image",
|
|
355
|
+
`/Width ${img.width}`,
|
|
356
|
+
`/Height ${img.height}`,
|
|
357
|
+
`/ColorSpace ${colorSpace}`,
|
|
358
|
+
`/BitsPerComponent ${bpc}`,
|
|
359
|
+
"/Filter /FlateDecode",
|
|
360
|
+
`/DecodeParms << /Predictor 15 /Colors ${colors} /BitsPerComponent ${bpc} /Columns ${img.width} >>`,
|
|
361
|
+
`/Length ${img.idatData.length}`,
|
|
362
|
+
">>"
|
|
363
|
+
].join(`
|
|
364
|
+
`),
|
|
365
|
+
streamData: img.idatData
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
const contentStreamMap = new Map;
|
|
369
|
+
for (const page of this.pages) {
|
|
370
|
+
if (!page.dirty)
|
|
371
|
+
continue;
|
|
372
|
+
const contentObjNum = currentNextObj++;
|
|
373
|
+
contentStreamMap.set(page.pageObjNum, contentObjNum);
|
|
374
|
+
const streamData = page.buildContentStream();
|
|
375
|
+
objects.push({
|
|
376
|
+
objNum: contentObjNum,
|
|
377
|
+
content: `<< /Length ${streamData.length} >>`,
|
|
378
|
+
streamData
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
for (const page of this.pages) {
|
|
382
|
+
if (!page.dirty && !this.hasImagesForPage(page))
|
|
383
|
+
continue;
|
|
384
|
+
let pageContent = page.originalDictContent;
|
|
385
|
+
if (page.dirty) {
|
|
386
|
+
const contentObjNum = contentStreamMap.get(page.pageObjNum);
|
|
387
|
+
if (pageContent.match(/\/Contents\s/)) {
|
|
388
|
+
pageContent = pageContent.replace(/\/Contents\s+(\d+\s+\d+\s+R)/, `/Contents [$1 ${contentObjNum} 0 R]`);
|
|
389
|
+
pageContent = pageContent.replace(/\/Contents\s*\[([^\]]+)\]/, (match, inner) => {
|
|
390
|
+
if (inner.includes(`${contentObjNum} 0 R`))
|
|
391
|
+
return match;
|
|
392
|
+
return `/Contents [${inner.trim()} ${contentObjNum} 0 R]`;
|
|
393
|
+
});
|
|
394
|
+
} else {
|
|
395
|
+
pageContent += `
|
|
396
|
+
/Contents ${contentObjNum} 0 R`;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
const resourceParts = [];
|
|
400
|
+
resourceParts.push(`/Font << /F1 ${this.fontObjNum} 0 R >>`);
|
|
401
|
+
const imageRefs = page.dirty ? page.getImageRefs() : new Map;
|
|
402
|
+
if (imageRefs.size > 0) {
|
|
403
|
+
const xobjEntries = Array.from(imageRefs.entries()).map(([name, objNum]) => `/${name} ${objNum} 0 R`).join(" ");
|
|
404
|
+
resourceParts.push(`/XObject << ${xobjEntries} >>`);
|
|
405
|
+
}
|
|
406
|
+
if (pageContent.match(/\/Resources\s*<</)) {
|
|
407
|
+
const resIdx = pageContent.indexOf("/Resources");
|
|
408
|
+
const resStart = pageContent.indexOf("<<", resIdx);
|
|
409
|
+
if (resStart !== -1) {
|
|
410
|
+
const resEnd = findMatchingDictEndInContent(pageContent, resStart);
|
|
411
|
+
if (resEnd !== -1) {
|
|
412
|
+
const existingResContent = pageContent.slice(resStart + 2, resEnd);
|
|
413
|
+
let cleanedRes = removeNestedDictEntry(existingResContent, "/Font");
|
|
414
|
+
cleanedRes = removeNestedDictEntry(cleanedRes, "/XObject");
|
|
415
|
+
const newResContent = `${cleanedRes}
|
|
416
|
+
${resourceParts.join(`
|
|
417
|
+
`)}`;
|
|
418
|
+
pageContent = pageContent.slice(0, resStart) + `<< ${newResContent} >>` + pageContent.slice(resEnd + 2);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
} else if (pageContent.match(/\/Resources\s+\d+\s+\d+\s+R/)) {
|
|
422
|
+
pageContent = pageContent.replace(/\/Resources\s+\d+\s+\d+\s+R/, `/Resources << ${resourceParts.join(" ")} >>`);
|
|
423
|
+
} else {
|
|
424
|
+
pageContent += `
|
|
425
|
+
/Resources << ${resourceParts.join(" ")} >>`;
|
|
426
|
+
}
|
|
427
|
+
objects.push({
|
|
428
|
+
objNum: page.pageObjNum,
|
|
429
|
+
content: `<<${pageContent}
|
|
430
|
+
>>`
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
let sigObjNum = 0;
|
|
434
|
+
let widgetObjNum = 0;
|
|
435
|
+
let acroFormObjNum = 0;
|
|
436
|
+
if (withSignature && sigOptions) {
|
|
437
|
+
sigObjNum = currentNextObj++;
|
|
438
|
+
widgetObjNum = currentNextObj++;
|
|
439
|
+
acroFormObjNum = currentNextObj++;
|
|
440
|
+
const signatureLength = sigOptions.signatureLength ?? DEFAULT_SIGNATURE_LENGTH;
|
|
441
|
+
const reason = sigOptions.reason ?? "Digitally signed";
|
|
442
|
+
const name = sigOptions.name ?? "Digital Signature";
|
|
443
|
+
const location = sigOptions.location ?? "";
|
|
444
|
+
const contactInfo = sigOptions.contactInfo ?? "";
|
|
445
|
+
const signingTime = formatPdfDate(new Date);
|
|
446
|
+
const contentsPlaceholder = "0".repeat(signatureLength * 2);
|
|
447
|
+
const sigParts = [
|
|
448
|
+
"<< /Type /Sig",
|
|
449
|
+
"/Filter /Adobe.PPKLite",
|
|
450
|
+
"/SubFilter /adbe.pkcs7.detached",
|
|
451
|
+
`/ByteRange [${BYTE_RANGE_PLACEHOLDER}]`,
|
|
452
|
+
`/Contents <${contentsPlaceholder}>`,
|
|
453
|
+
`/Reason ${pdfString(reason)}`,
|
|
454
|
+
`/M ${pdfString(signingTime)}`,
|
|
455
|
+
`/Name ${pdfString(name)}`
|
|
456
|
+
];
|
|
457
|
+
if (location)
|
|
458
|
+
sigParts.push(`/Location ${pdfString(location)}`);
|
|
459
|
+
if (contactInfo)
|
|
460
|
+
sigParts.push(`/ContactInfo ${pdfString(contactInfo)}`);
|
|
461
|
+
sigParts.push(">>");
|
|
462
|
+
objects.push({ objNum: sigObjNum, content: sigParts.join(`
|
|
463
|
+
`) });
|
|
464
|
+
const firstPageNum = this.structure.pageNums[0];
|
|
465
|
+
objects.push({
|
|
466
|
+
objNum: widgetObjNum,
|
|
467
|
+
content: [
|
|
468
|
+
"<< /Type /Annot",
|
|
469
|
+
"/Subtype /Widget",
|
|
470
|
+
"/FT /Sig",
|
|
471
|
+
"/Rect [0 0 0 0]",
|
|
472
|
+
`/V ${sigObjNum} 0 R`,
|
|
473
|
+
`/T ${pdfString("Signature1")}`,
|
|
474
|
+
"/F 4",
|
|
475
|
+
`/P ${firstPageNum} 0 R`,
|
|
476
|
+
">>"
|
|
477
|
+
].join(`
|
|
478
|
+
`)
|
|
479
|
+
});
|
|
480
|
+
objects.push({
|
|
481
|
+
objNum: acroFormObjNum,
|
|
482
|
+
content: [
|
|
483
|
+
"<< /Type /AcroForm",
|
|
484
|
+
"/SigFlags 3",
|
|
485
|
+
`/Fields [${widgetObjNum} 0 R]`,
|
|
486
|
+
">>"
|
|
487
|
+
].join(`
|
|
488
|
+
`)
|
|
489
|
+
});
|
|
490
|
+
let rootContent = this.structure.rootDictContent;
|
|
491
|
+
rootContent = rootContent.replace(/\/AcroForm\s+\d+\s+\d+\s+R/g, "");
|
|
492
|
+
rootContent = rootContent.replace(/\/Perms\s*<<[^>]*>>/g, "");
|
|
493
|
+
rootContent = rootContent.replace(/\/Perms\s+\d+\s+\d+\s+R/g, "");
|
|
494
|
+
objects.push({
|
|
495
|
+
objNum: this.structure.rootNum,
|
|
496
|
+
content: `<<${rootContent}
|
|
497
|
+
/AcroForm ${acroFormObjNum} 0 R
|
|
498
|
+
>>`
|
|
499
|
+
});
|
|
500
|
+
const firstPage = this.pages[0];
|
|
501
|
+
let pageContent;
|
|
502
|
+
const existingPageObj = objects.find((o) => o.objNum === firstPage.pageObjNum);
|
|
503
|
+
if (existingPageObj) {
|
|
504
|
+
pageContent = existingPageObj.content.slice(2, existingPageObj.content.length - 2);
|
|
505
|
+
} else {
|
|
506
|
+
pageContent = firstPage.originalDictContent;
|
|
507
|
+
}
|
|
508
|
+
if (pageContent.includes("/Annots")) {
|
|
509
|
+
const bracketEnd = pageContent.indexOf("]", pageContent.indexOf("/Annots"));
|
|
510
|
+
pageContent = pageContent.slice(0, bracketEnd) + ` ${widgetObjNum} 0 R` + pageContent.slice(bracketEnd);
|
|
511
|
+
} else {
|
|
512
|
+
pageContent += `
|
|
513
|
+
/Annots [${widgetObjNum} 0 R]`;
|
|
514
|
+
}
|
|
515
|
+
if (existingPageObj) {
|
|
516
|
+
existingPageObj.content = `<<${pageContent}
|
|
517
|
+
>>`;
|
|
518
|
+
} else {
|
|
519
|
+
objects.push({
|
|
520
|
+
objNum: firstPage.pageObjNum,
|
|
521
|
+
content: `<<${pageContent}
|
|
522
|
+
>>`
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
const newSize = currentNextObj;
|
|
527
|
+
const appendParts = [];
|
|
528
|
+
const objectOffsets = [];
|
|
529
|
+
let currentOffset = this.originalData.length;
|
|
530
|
+
for (const obj of objects) {
|
|
531
|
+
const sep = latin1Encoder.encode(`
|
|
532
|
+
`);
|
|
533
|
+
appendParts.push(sep);
|
|
534
|
+
currentOffset += sep.length;
|
|
535
|
+
objectOffsets.push({ objNum: obj.objNum, offset: currentOffset });
|
|
536
|
+
if (obj.streamData) {
|
|
537
|
+
const header = latin1Encoder.encode(`${obj.objNum} 0 obj
|
|
538
|
+
${obj.content}
|
|
539
|
+
stream
|
|
540
|
+
`);
|
|
541
|
+
appendParts.push(header);
|
|
542
|
+
currentOffset += header.length;
|
|
543
|
+
appendParts.push(obj.streamData);
|
|
544
|
+
currentOffset += obj.streamData.length;
|
|
545
|
+
const footer = latin1Encoder.encode(`
|
|
546
|
+
endstream
|
|
547
|
+
endobj
|
|
548
|
+
`);
|
|
549
|
+
appendParts.push(footer);
|
|
550
|
+
currentOffset += footer.length;
|
|
551
|
+
} else {
|
|
552
|
+
const objBytes = latin1Encoder.encode(`${obj.objNum} 0 obj
|
|
553
|
+
${obj.content}
|
|
554
|
+
endobj
|
|
555
|
+
`);
|
|
556
|
+
appendParts.push(objBytes);
|
|
557
|
+
currentOffset += objBytes.length;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
const xrefOffset = currentOffset;
|
|
561
|
+
const xrefStr = buildXrefTable(objectOffsets);
|
|
562
|
+
const xrefBytes = latin1Encoder.encode(xrefStr);
|
|
563
|
+
appendParts.push(xrefBytes);
|
|
564
|
+
currentOffset += xrefBytes.length;
|
|
565
|
+
const trailerLines = ["<<", `/Size ${newSize}`, `/Root ${this.structure.rootNum} 0 R`];
|
|
566
|
+
if (this.structure.infoNum !== null) {
|
|
567
|
+
trailerLines.push(`/Info ${this.structure.infoNum} 0 R`);
|
|
568
|
+
}
|
|
569
|
+
trailerLines.push(`/Prev ${this.structure.xrefOffset}`, ">>");
|
|
570
|
+
const trailerStr = `trailer
|
|
571
|
+
${trailerLines.join(`
|
|
572
|
+
`)}
|
|
573
|
+
startxref
|
|
574
|
+
${xrefOffset}
|
|
575
|
+
%%EOF`;
|
|
576
|
+
const trailerBytes = latin1Encoder.encode(trailerStr);
|
|
577
|
+
appendParts.push(trailerBytes);
|
|
578
|
+
const totalAppendLength = appendParts.reduce((s, p) => s + p.length, 0);
|
|
579
|
+
const result = new Uint8Array(this.originalData.length + totalAppendLength);
|
|
580
|
+
result.set(this.originalData, 0);
|
|
581
|
+
let pos = this.originalData.length;
|
|
582
|
+
for (const part of appendParts) {
|
|
583
|
+
result.set(part, pos);
|
|
584
|
+
pos += part.length;
|
|
585
|
+
}
|
|
586
|
+
let byteRange = [0, 0, 0, 0];
|
|
587
|
+
if (withSignature) {
|
|
588
|
+
const { br, updatedPdf } = updateByteRange(result);
|
|
589
|
+
byteRange = br;
|
|
590
|
+
return { pdf: updatedPdf, byteRange };
|
|
591
|
+
}
|
|
592
|
+
return { pdf: result, byteRange };
|
|
593
|
+
}
|
|
594
|
+
hasImagesForPage(page) {
|
|
595
|
+
return page.dirty && page.getImageRefs().size > 0;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
function buildXrefTable(entries) {
|
|
599
|
+
const sorted = [...entries].sort((a, b) => a.objNum - b.objNum);
|
|
600
|
+
const subsections = [];
|
|
601
|
+
for (const entry of sorted) {
|
|
602
|
+
const last = subsections[subsections.length - 1];
|
|
603
|
+
if (last && entry.objNum === last.start + last.offsets.length) {
|
|
604
|
+
last.offsets.push(entry.offset);
|
|
605
|
+
} else {
|
|
606
|
+
subsections.push({ start: entry.objNum, offsets: [entry.offset] });
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
let result = `xref
|
|
610
|
+
`;
|
|
611
|
+
for (const sub of subsections) {
|
|
612
|
+
result += `${sub.start} ${sub.offsets.length}
|
|
613
|
+
`;
|
|
614
|
+
for (const offset of sub.offsets) {
|
|
615
|
+
result += `${String(offset).padStart(10, "0")} 00000 n
|
|
616
|
+
`;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
return result;
|
|
620
|
+
}
|
|
621
|
+
function formatPdfDate(date) {
|
|
622
|
+
const y = date.getUTCFullYear();
|
|
623
|
+
const m = String(date.getUTCMonth() + 1).padStart(2, "0");
|
|
624
|
+
const d = String(date.getUTCDate()).padStart(2, "0");
|
|
625
|
+
const h = String(date.getUTCHours()).padStart(2, "0");
|
|
626
|
+
const min = String(date.getUTCMinutes()).padStart(2, "0");
|
|
627
|
+
const s = String(date.getUTCSeconds()).padStart(2, "0");
|
|
628
|
+
return `D:${y}${m}${d}${h}${min}${s}Z`;
|
|
629
|
+
}
|
|
630
|
+
function pdfString(str) {
|
|
631
|
+
const escaped = str.replace(/\\/g, "\\\\").replace(/\(/g, "\\(").replace(/\)/g, "\\)");
|
|
632
|
+
return `(${escaped})`;
|
|
633
|
+
}
|
|
634
|
+
function updateByteRange(pdf) {
|
|
635
|
+
const pdfStr = new TextDecoder("latin1").decode(pdf);
|
|
636
|
+
const contentsMarker = "/Contents <";
|
|
637
|
+
const contentsIdx = pdfStr.lastIndexOf(contentsMarker);
|
|
638
|
+
if (contentsIdx === -1)
|
|
639
|
+
throw new Error("Cannot find Contents in signature");
|
|
640
|
+
const contentsStart = contentsIdx + contentsMarker.length;
|
|
641
|
+
const contentsEnd = pdfStr.indexOf(">", contentsStart);
|
|
642
|
+
if (contentsEnd === -1)
|
|
643
|
+
throw new Error("Cannot find end of Contents hex");
|
|
644
|
+
const br = [
|
|
645
|
+
0,
|
|
646
|
+
contentsStart - 1,
|
|
647
|
+
contentsEnd + 1,
|
|
648
|
+
pdf.length - (contentsEnd + 1)
|
|
649
|
+
];
|
|
650
|
+
const byteRangeMarker = "/ByteRange [";
|
|
651
|
+
const brIdx = pdfStr.lastIndexOf(byteRangeMarker);
|
|
652
|
+
if (brIdx === -1)
|
|
653
|
+
throw new Error("Cannot find ByteRange in PDF");
|
|
654
|
+
const brStart = brIdx + byteRangeMarker.length;
|
|
655
|
+
const brEnd = pdfStr.indexOf("]", brStart);
|
|
656
|
+
if (brEnd === -1)
|
|
657
|
+
throw new Error("Cannot find end of ByteRange");
|
|
658
|
+
const placeholderLen = brEnd - brStart;
|
|
659
|
+
const brValueStr = `${br[0]} ${br[1]} ${br[2]} ${br[3]}`;
|
|
660
|
+
const paddedBr = brValueStr.padEnd(placeholderLen, " ");
|
|
661
|
+
const updatedPdf = new Uint8Array(pdf.length);
|
|
662
|
+
updatedPdf.set(pdf);
|
|
663
|
+
const brBytes = new TextEncoder().encode(paddedBr);
|
|
664
|
+
updatedPdf.set(brBytes, brStart);
|
|
665
|
+
return { br, updatedPdf };
|
|
666
|
+
}
|
|
667
|
+
function findMatchingDictEndInContent(str, startPos) {
|
|
668
|
+
let depth = 0;
|
|
669
|
+
let i = startPos;
|
|
670
|
+
while (i < str.length - 1) {
|
|
671
|
+
if (str[i] === "(") {
|
|
672
|
+
i++;
|
|
673
|
+
while (i < str.length && str[i] !== ")") {
|
|
674
|
+
if (str[i] === "\\")
|
|
675
|
+
i++;
|
|
676
|
+
i++;
|
|
677
|
+
}
|
|
678
|
+
i++;
|
|
679
|
+
} else if (str[i] === "<" && str[i + 1] === "<") {
|
|
680
|
+
depth++;
|
|
681
|
+
i += 2;
|
|
682
|
+
} else if (str[i] === ">" && str[i + 1] === ">") {
|
|
683
|
+
depth--;
|
|
684
|
+
if (depth === 0)
|
|
685
|
+
return i;
|
|
686
|
+
i += 2;
|
|
687
|
+
} else {
|
|
688
|
+
i++;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
return -1;
|
|
692
|
+
}
|
|
693
|
+
function removeNestedDictEntry(content, name) {
|
|
694
|
+
const idx = content.indexOf(name);
|
|
695
|
+
if (idx === -1)
|
|
696
|
+
return content;
|
|
697
|
+
const dictOpen = content.indexOf("<<", idx + name.length);
|
|
698
|
+
if (dictOpen === -1)
|
|
699
|
+
return content;
|
|
700
|
+
const dictClose = findMatchingDictEndInContent(content, dictOpen);
|
|
701
|
+
if (dictClose === -1)
|
|
702
|
+
return content;
|
|
703
|
+
return content.slice(0, idx) + content.slice(dictClose + 2);
|
|
704
|
+
}
|
|
705
|
+
function findByteRange(pdfData) {
|
|
706
|
+
const pdf = new TextDecoder("latin1").decode(pdfData);
|
|
707
|
+
const contentsMarker = "/Contents <";
|
|
708
|
+
const contentsIdx = pdf.lastIndexOf(contentsMarker);
|
|
709
|
+
if (contentsIdx === -1)
|
|
710
|
+
throw new Error("Could not find Contents in PDF");
|
|
711
|
+
const contentsStart = contentsIdx + contentsMarker.length;
|
|
712
|
+
const contentsEnd = pdf.indexOf(">", contentsStart);
|
|
713
|
+
if (contentsEnd === -1)
|
|
714
|
+
throw new Error("Could not find end of Contents field");
|
|
715
|
+
const placeholderLength = contentsEnd - contentsStart;
|
|
716
|
+
const byteRange = [
|
|
717
|
+
0,
|
|
718
|
+
contentsStart - 1,
|
|
719
|
+
contentsEnd + 1,
|
|
720
|
+
pdfData.length - (contentsEnd + 1)
|
|
721
|
+
];
|
|
722
|
+
return { byteRange, contentsStart, contentsEnd, placeholderLength };
|
|
723
|
+
}
|
|
724
|
+
function extractBytesToSign(pdfData, byteRange) {
|
|
725
|
+
const [offset1, length1, offset2, length2] = byteRange;
|
|
726
|
+
if (offset1 < 0 || length1 <= 0 || offset2 <= 0 || length2 <= 0) {
|
|
727
|
+
throw new Error(`Invalid ByteRange values: [${byteRange.join(", ")}]`);
|
|
728
|
+
}
|
|
729
|
+
if (offset1 + length1 > pdfData.length || offset2 + length2 > pdfData.length) {
|
|
730
|
+
throw new Error("ByteRange exceeds PDF data size");
|
|
731
|
+
}
|
|
732
|
+
const chunk1 = pdfData.slice(offset1, offset1 + length1);
|
|
733
|
+
const chunk2 = pdfData.slice(offset2, offset2 + length2);
|
|
734
|
+
const result = new Uint8Array(chunk1.length + chunk2.length);
|
|
735
|
+
result.set(chunk1, 0);
|
|
736
|
+
result.set(chunk2, chunk1.length);
|
|
737
|
+
return result;
|
|
738
|
+
}
|
|
739
|
+
function embedSignature(pdfData, signature) {
|
|
740
|
+
const { contentsStart, placeholderLength } = findByteRange(pdfData);
|
|
741
|
+
const hexChars = [];
|
|
742
|
+
for (let i = 0;i < signature.length; i++) {
|
|
743
|
+
hexChars.push(signature[i].toString(16).padStart(2, "0"));
|
|
744
|
+
}
|
|
745
|
+
const signatureHex = hexChars.join("");
|
|
746
|
+
if (signatureHex.length > placeholderLength) {
|
|
747
|
+
throw new Error(`Signature too large: ${signatureHex.length} hex chars, placeholder is ${placeholderLength}`);
|
|
748
|
+
}
|
|
749
|
+
const paddedHex = signatureHex.padEnd(placeholderLength, "0");
|
|
750
|
+
const hexBytes = new TextEncoder().encode(paddedHex);
|
|
751
|
+
const result = new Uint8Array(pdfData.length);
|
|
752
|
+
result.set(pdfData);
|
|
753
|
+
result.set(hexBytes, contentsStart);
|
|
754
|
+
return result;
|
|
755
|
+
}
|
|
756
|
+
// src/plugins/editing/index.ts
|
|
757
|
+
function loadPdf(data) {
|
|
758
|
+
return new PdfDocumentImpl(data);
|
|
759
|
+
}
|
|
760
|
+
export {
|
|
761
|
+
loadPdf,
|
|
762
|
+
findByteRange,
|
|
763
|
+
extractBytesToSign,
|
|
764
|
+
embedSignature,
|
|
765
|
+
PdfDocumentImpl
|
|
766
|
+
};
|
|
767
|
+
|
|
768
|
+
//# debugId=00A6738A73B4346A64756E2164756E21
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/plugins/editing/parser.ts", "../src/plugins/editing/page.ts", "../src/plugins/editing/document.ts", "../src/plugins/editing/index.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * Lightweight PDF structure parser for incremental editing\n *\n * Parses just enough of an existing PDF to enable incremental updates:\n * - Finds the cross-reference table via startxref\n * - Reads the trailer to get Root, Size, Info\n * - Follows Root -> Pages -> Kids to locate page objects\n * - Extracts MediaBox dimensions from pages\n *\n * Uses latin1 string matching on the raw PDF bytes (1 byte = 1 char)\n * for correctness with binary PDF content.\n */\n\n/**\n * Parsed PDF structure needed for incremental updates\n */\nexport type PdfStructure = {\n\txrefOffset: number;\n\trootNum: number;\n\tinfoNum: number | null;\n\tsize: number;\n\tpagesNum: number;\n\tpageNums: number[];\n\trootDictContent: string;\n\tpageDictContents: string[];\n};\n\nconst decoder = new TextDecoder(\"latin1\");\n\n/**\n * Decode Uint8Array to latin1 string for raw PDF text matching\n */\nfunction toLatin1(data: Uint8Array): string {\n\treturn decoder.decode(data);\n}\n\n/**\n * Find the byte offset recorded after the last `startxref` keyword\n */\nexport function findStartXref(data: Uint8Array): number {\n\tconst pdf = toLatin1(data);\n\tconst idx = pdf.lastIndexOf(\"startxref\");\n\tif (idx === -1) throw new Error(\"Cannot find startxref in PDF\");\n\tconst after = pdf.slice(idx + 9).trim().split(/[\\r\\n\\s]/)[0];\n\treturn parseInt(after!, 10);\n}\n\n/**\n * Parse the trailer dictionary to extract Root, Size, Info, and Prev xref offset.\n *\n * Supports both traditional trailers (`trailer << ... >>`) and\n * cross-reference streams (PDF 1.5+) where the trailer entries live\n * inside the xref stream object dictionary.\n */\nexport function parseTrailer(data: Uint8Array): {\n\troot: number;\n\tsize: number;\n\tinfo: number | null;\n\tprevXref: number;\n} {\n\tconst pdf = toLatin1(data);\n\tconst startxrefIdx = pdf.lastIndexOf(\"startxref\");\n\n\t// Try traditional trailer first\n\tconst trailerIdx = pdf.lastIndexOf(\"trailer\");\n\n\tlet dictStr: string;\n\n\tif (trailerIdx !== -1 && trailerIdx < startxrefIdx) {\n\t\t// Traditional trailer\n\t\tdictStr = pdf.slice(trailerIdx, startxrefIdx);\n\t} else {\n\t\t// Cross-reference stream (PDF 1.5+): startxref points to an object\n\t\t// whose dictionary contains the trailer entries (Root, Size, Info, etc.)\n\t\tconst xrefOffset = findStartXref(data);\n\t\tconst xrefObjStr = pdf.slice(xrefOffset, xrefOffset + 4096);\n\t\tconst dictStart = xrefObjStr.indexOf(\"<<\");\n\t\tif (dictStart === -1) {\n\t\t\tthrow new Error(\"Cannot find trailer or xref stream dictionary in PDF\");\n\t\t}\n\t\tconst dictEnd = findMatchingDictEnd(xrefObjStr, dictStart);\n\t\tif (dictEnd === -1) {\n\t\t\tthrow new Error(\"Cannot find end of xref stream dictionary\");\n\t\t}\n\t\tdictStr = xrefObjStr.slice(dictStart, dictEnd + 2);\n\t}\n\n\tconst rootMatch = dictStr.match(/\\/Root\\s+(\\d+)\\s+\\d+\\s+R/);\n\tif (!rootMatch) throw new Error(\"Cannot find Root ref in trailer\");\n\n\tconst sizeMatch = dictStr.match(/\\/Size\\s+(\\d+)/);\n\tif (!sizeMatch) throw new Error(\"Cannot find Size in trailer\");\n\n\tconst infoMatch = dictStr.match(/\\/Info\\s+(\\d+)\\s+\\d+\\s+R/);\n\tconst prevMatch = dictStr.match(/\\/Prev\\s+(\\d+)/);\n\n\treturn {\n\t\troot: parseInt(rootMatch[1]!, 10),\n\t\tsize: parseInt(sizeMatch[1]!, 10),\n\t\tinfo: infoMatch ? parseInt(infoMatch[1]!, 10) : null,\n\t\tprevXref: prevMatch ? parseInt(prevMatch[1]!, 10) : findStartXref(data),\n\t};\n}\n\n/**\n * Extract the dictionary content (between outer << and >>) for a given object number.\n * Returns the content string without the delimiters.\n */\nexport function extractObjectDictContent(\n\tdata: Uint8Array,\n\tobjNum: number,\n): string {\n\tconst pdf = toLatin1(data);\n\tconst objRegex = new RegExp(`(?:^|\\\\s)${objNum}\\\\s+0\\\\s+obj`, \"m\");\n\tconst match = pdf.match(objRegex);\n\tif (!match || match.index === undefined) {\n\t\tthrow new Error(`Cannot find object ${objNum} in PDF`);\n\t}\n\n\tconst searchStart = match.index + match[0].length;\n\tconst dictStart = pdf.indexOf(\"<<\", searchStart);\n\tif (dictStart === -1 || dictStart > searchStart + 200) {\n\t\tthrow new Error(`Cannot find dictionary start for object ${objNum}`);\n\t}\n\n\tconst dictEnd = findMatchingDictEnd(pdf, dictStart);\n\tif (dictEnd === -1) {\n\t\tthrow new Error(`Cannot find dictionary end for object ${objNum}`);\n\t}\n\n\treturn pdf.slice(dictStart + 2, dictEnd);\n}\n\n/**\n * Find all page object numbers by following Root -> Pages -> Kids\n */\nexport function findPageObjects(data: Uint8Array, rootNum: number): number[] {\n\tconst rootContent = extractObjectDictContent(data, rootNum);\n\tconst pagesMatch = rootContent.match(/\\/Pages\\s+(\\d+)\\s+\\d+\\s+R/);\n\tif (!pagesMatch) throw new Error(\"Cannot find Pages ref in Root catalog\");\n\tconst pagesNum = parseInt(pagesMatch[1]!, 10);\n\n\tconst pagesContent = extractObjectDictContent(data, pagesNum);\n\tconst kidsMatch = pagesContent.match(/\\/Kids\\s*\\[([^\\]]+)\\]/);\n\tif (!kidsMatch) throw new Error(\"Cannot find Kids array in Pages\");\n\n\tconst refs: number[] = [];\n\tconst refRegex = /(\\d+)\\s+\\d+\\s+R/g;\n\tlet m: RegExpExecArray | null;\n\twhile ((m = refRegex.exec(kidsMatch[1]!)) !== null) {\n\t\trefs.push(parseInt(m[1]!, 10));\n\t}\n\n\treturn refs;\n}\n\n/**\n * Get the MediaBox for a page object: [x1, y1, x2, y2]\n */\nexport function getMediaBox(\n\tdata: Uint8Array,\n\tpageObjNum: number,\n): [number, number, number, number] {\n\tconst content = extractObjectDictContent(data, pageObjNum);\n\tconst mediaBoxMatch = content.match(\n\t\t/\\/MediaBox\\s*\\[\\s*([\\d.]+)\\s+([\\d.]+)\\s+([\\d.]+)\\s+([\\d.]+)\\s*\\]/,\n\t);\n\tif (!mediaBoxMatch) {\n\t\tthrow new Error(`Cannot find MediaBox for page object ${pageObjNum}`);\n\t}\n\n\treturn [\n\t\tparseFloat(mediaBoxMatch[1]!),\n\t\tparseFloat(mediaBoxMatch[2]!),\n\t\tparseFloat(mediaBoxMatch[3]!),\n\t\tparseFloat(mediaBoxMatch[4]!),\n\t];\n}\n\n/**\n * Parse the full PDF structure needed for incremental editing\n */\nexport function parsePdfStructure(data: Uint8Array): PdfStructure {\n\tconst xrefOffset = findStartXref(data);\n\tconst trailer = parseTrailer(data);\n\n\tconst rootContent = extractObjectDictContent(data, trailer.root);\n\tconst pagesMatch = rootContent.match(/\\/Pages\\s+(\\d+)\\s+\\d+\\s+R/);\n\tif (!pagesMatch) throw new Error(\"Cannot find Pages ref in Root catalog\");\n\tconst pagesNum = parseInt(pagesMatch[1]!, 10);\n\n\tconst pageNums = findPageObjects(data, trailer.root);\n\tconst pageDictContents = pageNums.map((pn) =>\n\t\textractObjectDictContent(data, pn),\n\t);\n\n\treturn {\n\t\txrefOffset,\n\t\trootNum: trailer.root,\n\t\tinfoNum: trailer.info,\n\t\tsize: trailer.size,\n\t\tpagesNum,\n\t\tpageNums,\n\t\trootDictContent: rootContent,\n\t\tpageDictContents,\n\t};\n}\n\n/**\n * Find the position of the >> that closes the dictionary starting at startPos.\n * Handles nested << >> and skips PDF string literals in parentheses.\n */\nfunction findMatchingDictEnd(str: string, startPos: number): number {\n\tlet depth = 0;\n\tlet i = startPos;\n\n\twhile (i < str.length - 1) {\n\t\tif (str[i] === \"(\") {\n\t\t\t// skip parenthesized string\n\t\t\ti++;\n\t\t\twhile (i < str.length && str[i] !== \")\") {\n\t\t\t\tif (str[i] === \"\\\\\") i++;\n\t\t\t\ti++;\n\t\t\t}\n\t\t\ti++; // skip ')'\n\t\t} else if (str[i] === \"<\" && str[i + 1] === \"<\") {\n\t\t\tdepth++;\n\t\t\ti += 2;\n\t\t} else if (str[i] === \">\" && str[i + 1] === \">\") {\n\t\t\tdepth--;\n\t\t\tif (depth === 0) return i;\n\t\t\ti += 2;\n\t\t} else {\n\t\t\ti++;\n\t\t}\n\t}\n\n\treturn -1;\n}\n",
|
|
6
|
+
"/**\n * PdfPage implementation for the editing plugin\n *\n * Collects drawing operations as PDF content stream operators.\n * The accumulated operators are later serialised as a new content stream\n * object appended via incremental update.\n */\n\nimport type { PdfImage, PdfPage, TextOptions, RectOptions, ImageOptions } from \"./types.ts\";\n\n/**\n * Parse a hex colour string like \"#RRGGBB\" into normalised [r, g, b] values (0-1).\n * Returns null for invalid/missing input so callers can fall back to defaults.\n */\nfunction parseHexColor(hex: string | undefined): [number, number, number] | null {\n\tif (!hex) return null;\n\tconst m = hex.match(/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/);\n\tif (!m) return null;\n\treturn [\n\t\tparseInt(m[1]!, 16) / 255,\n\t\tparseInt(m[2]!, 16) / 255,\n\t\tparseInt(m[3]!, 16) / 255,\n\t];\n}\n\nexport class PdfPageImpl implements PdfPage {\n\treadonly width: number;\n\treadonly height: number;\n\n\t/** The existing page object number in the original PDF */\n\treadonly pageObjNum: number;\n\t/** The raw dictionary content string of the original page */\n\treadonly originalDictContent: string;\n\n\t/** Accumulated content-stream operators added by draw* methods */\n\tprivate operators: string[] = [];\n\n\t/** Images referenced by drawImage (name -> obj num) */\n\tprivate imageRefs: Map<string, number> = new Map();\n\n\t/** Font object number allocated by the document (set externally) */\n\tfontObjNum = 0;\n\n\t/** Whether any drawing operations have been recorded */\n\tget dirty(): boolean {\n\t\treturn this.operators.length > 0;\n\t}\n\n\tconstructor(\n\t\tpageObjNum: number,\n\t\twidth: number,\n\t\theight: number,\n\t\toriginalDictContent: string,\n\t) {\n\t\tthis.pageObjNum = pageObjNum;\n\t\tthis.width = width;\n\t\tthis.height = height;\n\t\tthis.originalDictContent = originalDictContent;\n\t}\n\n\t/**\n\t * Draw text on the page using Helvetica\n\t */\n\tdrawText(text: string, options: TextOptions): void {\n\t\tconst { x, y, size = 12, color } = options;\n\t\tconst rgb = parseHexColor(color);\n\n\t\tif (rgb) {\n\t\t\tthis.operators.push(`${rgb[0].toFixed(3)} ${rgb[1].toFixed(3)} ${rgb[2].toFixed(3)} rg`);\n\t\t}\n\n\t\t// Escape special PDF string characters\n\t\tconst escaped = text\n\t\t\t.replace(/\\\\/g, \"\\\\\\\\\")\n\t\t\t.replace(/\\(/g, \"\\\\(\")\n\t\t\t.replace(/\\)/g, \"\\\\)\");\n\n\t\tthis.operators.push(\"BT\");\n\t\tthis.operators.push(`/F1 ${size} Tf`);\n\t\tthis.operators.push(`${x} ${y} Td`);\n\t\tthis.operators.push(`(${escaped}) Tj`);\n\t\tthis.operators.push(\"ET\");\n\t}\n\n\t/**\n\t * Draw a rectangle on the page\n\t */\n\tdrawRectangle(options: RectOptions): void {\n\t\tconst { x, y, width, height, color, borderColor, borderWidth } = options;\n\n\t\tconst fillRgb = parseHexColor(color);\n\t\tconst strokeRgb = parseHexColor(borderColor);\n\n\t\tif (fillRgb) {\n\t\t\tthis.operators.push(`${fillRgb[0].toFixed(3)} ${fillRgb[1].toFixed(3)} ${fillRgb[2].toFixed(3)} rg`);\n\t\t}\n\t\tif (strokeRgb) {\n\t\t\tthis.operators.push(`${strokeRgb[0].toFixed(3)} ${strokeRgb[1].toFixed(3)} ${strokeRgb[2].toFixed(3)} RG`);\n\t\t}\n\t\tif (borderWidth !== undefined) {\n\t\t\tthis.operators.push(`${borderWidth} w`);\n\t\t}\n\n\t\tthis.operators.push(`${x} ${y} ${width} ${height} re`);\n\n\t\tif (fillRgb && strokeRgb) {\n\t\t\tthis.operators.push(\"B\"); // fill and stroke\n\t\t} else if (fillRgb) {\n\t\t\tthis.operators.push(\"f\"); // fill only\n\t\t} else if (strokeRgb) {\n\t\t\tthis.operators.push(\"S\"); // stroke only\n\t\t} else {\n\t\t\tthis.operators.push(\"f\"); // default: fill with current colour (black)\n\t\t}\n\t}\n\n\t/**\n\t * Draw an embedded image on the page\n\t */\n\tdrawImage(image: PdfImage, options: ImageOptions): void {\n\t\tconst { x, y, width, height } = options;\n\t\tconst imgName = `Im${image.objectNumber}`;\n\t\tthis.imageRefs.set(imgName, image.objectNumber);\n\n\t\tthis.operators.push(\"q\");\n\t\tthis.operators.push(`${width} 0 0 ${height} ${x} ${y} cm`);\n\t\tthis.operators.push(`/${imgName} Do`);\n\t\tthis.operators.push(\"Q\");\n\t}\n\n\t/**\n\t * Build the content stream bytes for the accumulated operators\n\t */\n\tbuildContentStream(): Uint8Array {\n\t\tconst content = this.operators.join(\"\\n\");\n\t\treturn new TextEncoder().encode(content);\n\t}\n\n\t/**\n\t * Get image references used in drawing operations\n\t */\n\tgetImageRefs(): Map<string, number> {\n\t\treturn new Map(this.imageRefs);\n\t}\n}\n",
|
|
7
|
+
"/**\n * PdfDocument implementation for the editing plugin\n *\n * Manages loading an existing PDF, tracking modifications, and producing an\n * incremental update (appended after the original %%EOF) that adds or\n * overrides objects without rewriting the original content.\n */\n\nimport { parsePdfStructure, getMediaBox, extractObjectDictContent } from \"./parser.ts\";\nimport { PdfPageImpl } from \"./page.ts\";\nimport type {\n\tPdfDocument,\n\tPdfImage,\n\tPdfPage,\n\tSignaturePlaceholderOptions,\n} from \"./types.ts\";\n\nconst BYTE_RANGE_PLACEHOLDER = \"0 0000000000 0000000000 0000000000\";\nconst DEFAULT_SIGNATURE_LENGTH = 16384;\n\nconst latin1Encoder = new TextEncoder(); // UTF-8 but we only feed ASCII/latin1-safe chars\nconst latin1Decoder = new TextDecoder(\"latin1\");\n\n/**\n * Parse PNG IHDR to extract width, height, bit depth, and colour type.\n */\nfunction parsePngIhdr(data: Uint8Array): {\n\twidth: number;\n\theight: number;\n\tbitDepth: number;\n\tcolorType: number;\n} {\n\t// PNG signature: 8 bytes, then first chunk is IHDR\n\tconst sig = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];\n\tfor (let i = 0; i < sig.length; i++) {\n\t\tif (data[i] !== sig[i]) throw new Error(\"Not a valid PNG file\");\n\t}\n\n\t// IHDR chunk starts at offset 8\n\t// 4 bytes length + 4 bytes \"IHDR\" + 13 bytes data\n\tconst view = new DataView(data.buffer, data.byteOffset, data.byteLength);\n\tconst chunkType = latin1Decoder.decode(data.slice(12, 16));\n\tif (chunkType !== \"IHDR\") throw new Error(\"First PNG chunk is not IHDR\");\n\n\treturn {\n\t\twidth: view.getUint32(16),\n\t\theight: view.getUint32(20),\n\t\tbitDepth: data[24]!,\n\t\tcolorType: data[25]!,\n\t};\n}\n\n/**\n * Extract and concatenate all IDAT chunk data from a PNG\n */\nfunction extractIdatData(data: Uint8Array): Uint8Array {\n\tconst view = new DataView(data.buffer, data.byteOffset, data.byteLength);\n\tconst chunks: Uint8Array[] = [];\n\tlet offset = 8; // skip PNG signature\n\n\twhile (offset < data.length) {\n\t\tconst chunkLen = view.getUint32(offset);\n\t\tconst chunkType = latin1Decoder.decode(data.slice(offset + 4, offset + 8));\n\n\t\tif (chunkType === \"IDAT\") {\n\t\t\tchunks.push(data.slice(offset + 8, offset + 8 + chunkLen));\n\t\t}\n\n\t\t// skip: length(4) + type(4) + data(chunkLen) + crc(4)\n\t\toffset += 12 + chunkLen;\n\t}\n\n\tif (chunks.length === 0) throw new Error(\"No IDAT chunks found in PNG\");\n\n\t// Concatenate all IDAT data\n\tconst totalLen = chunks.reduce((s, c) => s + c.length, 0);\n\tconst result = new Uint8Array(totalLen);\n\tlet pos = 0;\n\tfor (const chunk of chunks) {\n\t\tresult.set(chunk, pos);\n\t\tpos += chunk.length;\n\t}\n\treturn result;\n}\n\nexport class PdfDocumentImpl implements PdfDocument {\n\tprivate originalData: Uint8Array;\n\tprivate structure: ReturnType<typeof parsePdfStructure>;\n\tprivate pages: PdfPageImpl[] = [];\n\tprivate nextObjNum: number;\n\tprivate fontObjNum: number;\n\tprivate embeddedImages: Array<{\n\t\tobjNum: number;\n\t\twidth: number;\n\t\theight: number;\n\t\tidatData: Uint8Array;\n\t\tcolorType: number;\n\t\tbitDepth: number;\n\t}> = [];\n\n\tconstructor(data: Uint8Array) {\n\t\tthis.originalData = data;\n\t\tthis.structure = parsePdfStructure(data);\n\n\t\t// Allocate a font object number right away (Helvetica)\n\t\tthis.nextObjNum = this.structure.size;\n\t\tthis.fontObjNum = this.nextObjNum++;\n\n\t\t// Build page objects\n\t\tfor (let i = 0; i < this.structure.pageNums.length; i++) {\n\t\t\tconst pageNum = this.structure.pageNums[i]!;\n\t\t\tconst mediaBox = getMediaBox(data, pageNum);\n\t\t\tconst width = mediaBox[2] - mediaBox[0];\n\t\t\tconst height = mediaBox[3] - mediaBox[1];\n\t\t\tconst dictContent = this.structure.pageDictContents[i]!;\n\t\t\tconst page = new PdfPageImpl(pageNum, width, height, dictContent);\n\t\t\tpage.fontObjNum = this.fontObjNum;\n\t\t\tthis.pages.push(page);\n\t\t}\n\t}\n\n\tget pageCount(): number {\n\t\treturn this.pages.length;\n\t}\n\n\tgetPage(index: number): PdfPage {\n\t\tif (index < 0 || index >= this.pages.length) {\n\t\t\tthrow new Error(\n\t\t\t\t`Page index ${index} out of range (0-${this.pages.length - 1})`,\n\t\t\t);\n\t\t}\n\t\treturn this.pages[index]!;\n\t}\n\n\tembedPng(data: Uint8Array): PdfImage {\n\t\tconst ihdr = parsePngIhdr(data);\n\t\tconst idatData = extractIdatData(data);\n\t\tconst objNum = this.nextObjNum++;\n\n\t\tthis.embeddedImages.push({\n\t\t\tobjNum,\n\t\t\twidth: ihdr.width,\n\t\t\theight: ihdr.height,\n\t\t\tidatData,\n\t\t\tcolorType: ihdr.colorType,\n\t\t\tbitDepth: ihdr.bitDepth,\n\t\t});\n\n\t\treturn {\n\t\t\tobjectNumber: objNum,\n\t\t\twidth: ihdr.width,\n\t\t\theight: ihdr.height,\n\t\t};\n\t}\n\n\t/**\n\t * Save the modified PDF using an incremental update\n\t */\n\tsave(): Uint8Array {\n\t\treturn this.buildIncrementalUpdate(false).pdf;\n\t}\n\n\t/**\n\t * Save with a signature placeholder for digital signing\n\t */\n\tsaveWithPlaceholder(options: SignaturePlaceholderOptions): {\n\t\tpdf: Uint8Array;\n\t\tbyteRange: [number, number, number, number];\n\t} {\n\t\treturn this.buildIncrementalUpdate(true, options);\n\t}\n\n\tprivate buildIncrementalUpdate(\n\t\twithSignature: boolean,\n\t\tsigOptions?: SignaturePlaceholderOptions,\n\t): { pdf: Uint8Array; byteRange: [number, number, number, number] } {\n\t\tconst objects: Array<{ objNum: number; content: string; streamData?: Uint8Array }> = [];\n\t\tlet currentNextObj = this.nextObjNum;\n\n\t\t// --- 1. Font object (Helvetica, always emitted if any page is dirty) ---\n\t\tconst anyDirty = this.pages.some((p) => p.dirty);\n\t\tif (anyDirty || this.embeddedImages.length > 0) {\n\t\t\tobjects.push({\n\t\t\t\tobjNum: this.fontObjNum,\n\t\t\t\tcontent: \"<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>\",\n\t\t\t});\n\t\t}\n\n\t\t// --- 2. Embedded image XObjects ---\n\t\tfor (const img of this.embeddedImages) {\n\t\t\tconst colorSpace = img.colorType === 2 ? \"/DeviceRGB\" : img.colorType === 0 ? \"/DeviceGray\" : \"/DeviceRGB\";\n\t\t\tconst colors = img.colorType === 2 ? 3 : 1;\n\t\t\tconst bpc = img.bitDepth;\n\n\t\t\t// PNG IDAT data uses per-row filter bytes. Tell the PDF reader\n\t\t\t// via DecodeParms with Predictor 15 (PNG optimum prediction).\n\t\t\tobjects.push({\n\t\t\t\tobjNum: img.objNum,\n\t\t\t\tcontent: [\n\t\t\t\t\t\"<< /Type /XObject\",\n\t\t\t\t\t\"/Subtype /Image\",\n\t\t\t\t\t`/Width ${img.width}`,\n\t\t\t\t\t`/Height ${img.height}`,\n\t\t\t\t\t`/ColorSpace ${colorSpace}`,\n\t\t\t\t\t`/BitsPerComponent ${bpc}`,\n\t\t\t\t\t\"/Filter /FlateDecode\",\n\t\t\t\t\t`/DecodeParms << /Predictor 15 /Colors ${colors} /BitsPerComponent ${bpc} /Columns ${img.width} >>`,\n\t\t\t\t\t`/Length ${img.idatData.length}`,\n\t\t\t\t\t\">>\",\n\t\t\t\t].join(\"\\n\"),\n\t\t\t\tstreamData: img.idatData,\n\t\t\t});\n\t\t}\n\n\t\t// --- 3. Content streams for dirty pages ---\n\t\tconst contentStreamMap = new Map<number, number>(); // pageObjNum -> contentStreamObjNum\n\t\tfor (const page of this.pages) {\n\t\t\tif (!page.dirty) continue;\n\t\t\tconst contentObjNum = currentNextObj++;\n\t\t\tcontentStreamMap.set(page.pageObjNum, contentObjNum);\n\t\t\tconst streamData = page.buildContentStream();\n\t\t\tobjects.push({\n\t\t\t\tobjNum: contentObjNum,\n\t\t\t\tcontent: `<< /Length ${streamData.length} >>`,\n\t\t\t\tstreamData,\n\t\t\t});\n\t\t}\n\n\t\t// --- 4. Updated page dictionaries (add new content stream + font/image resources) ---\n\t\tfor (const page of this.pages) {\n\t\t\tif (!page.dirty && !this.hasImagesForPage(page)) continue;\n\n\t\t\tlet pageContent = page.originalDictContent;\n\n\t\t\t// Add or replace Contents reference if page is dirty\n\t\t\tif (page.dirty) {\n\t\t\t\tconst contentObjNum = contentStreamMap.get(page.pageObjNum)!;\n\n\t\t\t\tif (pageContent.match(/\\/Contents\\s/)) {\n\t\t\t\t\t// Replace existing Contents with an array: [original, new]\n\t\t\t\t\tpageContent = pageContent.replace(\n\t\t\t\t\t\t/\\/Contents\\s+(\\d+\\s+\\d+\\s+R)/,\n\t\t\t\t\t\t`/Contents [$1 ${contentObjNum} 0 R]`,\n\t\t\t\t\t);\n\t\t\t\t\t// Also handle existing Contents arrays\n\t\t\t\t\tpageContent = pageContent.replace(\n\t\t\t\t\t\t/\\/Contents\\s*\\[([^\\]]+)\\]/,\n\t\t\t\t\t\t(match, inner) => {\n\t\t\t\t\t\t\t// If we already added our ref above, skip\n\t\t\t\t\t\t\tif (inner.includes(`${contentObjNum} 0 R`)) return match;\n\t\t\t\t\t\t\treturn `/Contents [${inner.trim()} ${contentObjNum} 0 R]`;\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tpageContent += `\\n/Contents ${contentObjNum} 0 R`;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Build resources additions\n\t\t\tconst resourceParts: string[] = [];\n\t\t\t// Font resource\n\t\t\tresourceParts.push(`/Font << /F1 ${this.fontObjNum} 0 R >>`);\n\n\t\t\t// Image resources\n\t\t\tconst imageRefs = page.dirty ? (page as PdfPageImpl).getImageRefs() : new Map<string, number>();\n\t\t\tif (imageRefs.size > 0) {\n\t\t\t\tconst xobjEntries = Array.from(imageRefs.entries())\n\t\t\t\t\t.map(([name, objNum]) => `/${name} ${objNum} 0 R`)\n\t\t\t\t\t.join(\" \");\n\t\t\t\tresourceParts.push(`/XObject << ${xobjEntries} >>`);\n\t\t\t}\n\n\t\t\t// Merge resources into page dictionary\n\t\t\tif (pageContent.match(/\\/Resources\\s*<</)) {\n\t\t\t\t// There's an existing inline Resources dict — insert our entries before its closing >>\n\t\t\t\t// Find the Resources dict\n\t\t\t\tconst resIdx = pageContent.indexOf(\"/Resources\");\n\t\t\t\tconst resStart = pageContent.indexOf(\"<<\", resIdx);\n\t\t\t\tif (resStart !== -1) {\n\t\t\t\t\tconst resEnd = findMatchingDictEndInContent(pageContent, resStart);\n\t\t\t\t\tif (resEnd !== -1) {\n\t\t\t\t\t\tconst existingResContent = pageContent.slice(resStart + 2, resEnd);\n\t\t\t\t\t\t// Remove existing Font and XObject entries — must handle nested dicts\n\t\t\t\t\t\tlet cleanedRes = removeNestedDictEntry(existingResContent, \"/Font\");\n\t\t\t\t\t\tcleanedRes = removeNestedDictEntry(cleanedRes, \"/XObject\");\n\t\t\t\t\t\tconst newResContent = `${cleanedRes}\\n${resourceParts.join(\"\\n\")}`;\n\t\t\t\t\t\tpageContent =\n\t\t\t\t\t\t\tpageContent.slice(0, resStart) +\n\t\t\t\t\t\t\t`<< ${newResContent} >>` +\n\t\t\t\t\t\t\tpageContent.slice(resEnd + 2);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (pageContent.match(/\\/Resources\\s+\\d+\\s+\\d+\\s+R/)) {\n\t\t\t\t// Resources is a reference — replace with inline dict including our additions\n\t\t\t\tpageContent = pageContent.replace(\n\t\t\t\t\t/\\/Resources\\s+\\d+\\s+\\d+\\s+R/,\n\t\t\t\t\t`/Resources << ${resourceParts.join(\" \")} >>`,\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\t// No resources at all — add them\n\t\t\t\tpageContent += `\\n/Resources << ${resourceParts.join(\" \")} >>`;\n\t\t\t}\n\n\t\t\tobjects.push({\n\t\t\t\tobjNum: page.pageObjNum,\n\t\t\t\tcontent: `<<${pageContent}\\n>>`,\n\t\t\t});\n\t\t}\n\n\t\t// --- 5. Signature placeholder objects (if requested) ---\n\t\tlet sigObjNum = 0;\n\t\tlet widgetObjNum = 0;\n\t\tlet acroFormObjNum = 0;\n\n\t\tif (withSignature && sigOptions) {\n\t\t\tsigObjNum = currentNextObj++;\n\t\t\twidgetObjNum = currentNextObj++;\n\t\t\tacroFormObjNum = currentNextObj++;\n\n\t\t\tconst signatureLength = sigOptions.signatureLength ?? DEFAULT_SIGNATURE_LENGTH;\n\t\t\tconst reason = sigOptions.reason ?? \"Digitally signed\";\n\t\t\tconst name = sigOptions.name ?? \"Digital Signature\";\n\t\t\tconst location = sigOptions.location ?? \"\";\n\t\t\tconst contactInfo = sigOptions.contactInfo ?? \"\";\n\t\t\tconst signingTime = formatPdfDate(new Date());\n\t\t\tconst contentsPlaceholder = \"0\".repeat(signatureLength * 2);\n\n\t\t\tconst sigParts = [\n\t\t\t\t\"<< /Type /Sig\",\n\t\t\t\t\"/Filter /Adobe.PPKLite\",\n\t\t\t\t\"/SubFilter /adbe.pkcs7.detached\",\n\t\t\t\t`/ByteRange [${BYTE_RANGE_PLACEHOLDER}]`,\n\t\t\t\t`/Contents <${contentsPlaceholder}>`,\n\t\t\t\t`/Reason ${pdfString(reason)}`,\n\t\t\t\t`/M ${pdfString(signingTime)}`,\n\t\t\t\t`/Name ${pdfString(name)}`,\n\t\t\t];\n\t\t\tif (location) sigParts.push(`/Location ${pdfString(location)}`);\n\t\t\tif (contactInfo) sigParts.push(`/ContactInfo ${pdfString(contactInfo)}`);\n\t\t\tsigParts.push(\">>\");\n\n\t\t\tobjects.push({ objNum: sigObjNum, content: sigParts.join(\"\\n\") });\n\n\t\t\t// Widget annotation\n\t\t\tconst firstPageNum = this.structure.pageNums[0]!;\n\t\t\tobjects.push({\n\t\t\t\tobjNum: widgetObjNum,\n\t\t\t\tcontent: [\n\t\t\t\t\t\"<< /Type /Annot\",\n\t\t\t\t\t\"/Subtype /Widget\",\n\t\t\t\t\t\"/FT /Sig\",\n\t\t\t\t\t\"/Rect [0 0 0 0]\",\n\t\t\t\t\t`/V ${sigObjNum} 0 R`,\n\t\t\t\t\t`/T ${pdfString(\"Signature1\")}`,\n\t\t\t\t\t\"/F 4\",\n\t\t\t\t\t`/P ${firstPageNum} 0 R`,\n\t\t\t\t\t\">>\",\n\t\t\t\t].join(\"\\n\"),\n\t\t\t});\n\n\t\t\t// AcroForm\n\t\t\tobjects.push({\n\t\t\t\tobjNum: acroFormObjNum,\n\t\t\t\tcontent: [\n\t\t\t\t\t\"<< /Type /AcroForm\",\n\t\t\t\t\t\"/SigFlags 3\",\n\t\t\t\t\t`/Fields [${widgetObjNum} 0 R]`,\n\t\t\t\t\t\">>\",\n\t\t\t\t].join(\"\\n\"),\n\t\t\t});\n\n\t\t\t// Updated Root catalog with AcroForm\n\t\t\tlet rootContent = this.structure.rootDictContent;\n\t\t\trootContent = rootContent.replace(/\\/AcroForm\\s+\\d+\\s+\\d+\\s+R/g, \"\");\n\t\t\trootContent = rootContent.replace(/\\/Perms\\s*<<[^>]*>>/g, \"\");\n\t\t\trootContent = rootContent.replace(/\\/Perms\\s+\\d+\\s+\\d+\\s+R/g, \"\");\n\n\t\t\tobjects.push({\n\t\t\t\tobjNum: this.structure.rootNum,\n\t\t\t\tcontent: `<<${rootContent}\\n/AcroForm ${acroFormObjNum} 0 R\\n>>`,\n\t\t\t});\n\n\t\t\t// Updated first page with Annots\n\t\t\tconst firstPage = this.pages[0]!;\n\t\t\tlet pageContent: string;\n\n\t\t\t// Check if we already have this page in objects (from dirty page update)\n\t\t\tconst existingPageObj = objects.find((o) => o.objNum === firstPage.pageObjNum);\n\t\t\tif (existingPageObj) {\n\t\t\t\t// Extract content from existing updated page (strip outer << >>)\n\t\t\t\tpageContent = existingPageObj.content.slice(2, existingPageObj.content.length - 2);\n\t\t\t} else {\n\t\t\t\tpageContent = firstPage.originalDictContent;\n\t\t\t}\n\n\t\t\tif (pageContent.includes(\"/Annots\")) {\n\t\t\t\tconst bracketEnd = pageContent.indexOf(\"]\", pageContent.indexOf(\"/Annots\"));\n\t\t\t\tpageContent =\n\t\t\t\t\tpageContent.slice(0, bracketEnd) +\n\t\t\t\t\t` ${widgetObjNum} 0 R` +\n\t\t\t\t\tpageContent.slice(bracketEnd);\n\t\t\t} else {\n\t\t\t\tpageContent += `\\n/Annots [${widgetObjNum} 0 R]`;\n\t\t\t}\n\n\t\t\t// Update or add the page object\n\t\t\tif (existingPageObj) {\n\t\t\t\texistingPageObj.content = `<<${pageContent}\\n>>`;\n\t\t\t} else {\n\t\t\t\tobjects.push({\n\t\t\t\t\tobjNum: firstPage.pageObjNum,\n\t\t\t\t\tcontent: `<<${pageContent}\\n>>`,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// --- Serialise incremental update ---\n\t\tconst newSize = currentNextObj;\n\t\tconst appendParts: Uint8Array[] = [];\n\t\tconst objectOffsets: Array<{ objNum: number; offset: number }> = [];\n\n\t\t// Compute starting byte offset (after original data)\n\t\tlet currentOffset = this.originalData.length;\n\n\t\tfor (const obj of objects) {\n\t\t\t// Separator newline\n\t\t\tconst sep = latin1Encoder.encode(\"\\n\");\n\t\t\tappendParts.push(sep);\n\t\t\tcurrentOffset += sep.length;\n\n\t\t\tobjectOffsets.push({ objNum: obj.objNum, offset: currentOffset });\n\n\t\t\tif (obj.streamData) {\n\t\t\t\t// Object with stream\n\t\t\t\tconst header = latin1Encoder.encode(`${obj.objNum} 0 obj\\n${obj.content}\\nstream\\n`);\n\t\t\t\tappendParts.push(header);\n\t\t\t\tcurrentOffset += header.length;\n\n\t\t\t\tappendParts.push(obj.streamData);\n\t\t\t\tcurrentOffset += obj.streamData.length;\n\n\t\t\t\tconst footer = latin1Encoder.encode(\"\\nendstream\\nendobj\\n\");\n\t\t\t\tappendParts.push(footer);\n\t\t\t\tcurrentOffset += footer.length;\n\t\t\t} else {\n\t\t\t\tconst objBytes = latin1Encoder.encode(\n\t\t\t\t\t`${obj.objNum} 0 obj\\n${obj.content}\\nendobj\\n`,\n\t\t\t\t);\n\t\t\t\tappendParts.push(objBytes);\n\t\t\t\tcurrentOffset += objBytes.length;\n\t\t\t}\n\t\t}\n\n\t\t// Xref table\n\t\tconst xrefOffset = currentOffset;\n\t\tconst xrefStr = buildXrefTable(objectOffsets);\n\t\tconst xrefBytes = latin1Encoder.encode(xrefStr);\n\t\tappendParts.push(xrefBytes);\n\t\tcurrentOffset += xrefBytes.length;\n\n\t\t// Trailer\n\t\tconst trailerLines = [\"<<\", `/Size ${newSize}`, `/Root ${this.structure.rootNum} 0 R`];\n\t\tif (this.structure.infoNum !== null) {\n\t\t\ttrailerLines.push(`/Info ${this.structure.infoNum} 0 R`);\n\t\t}\n\t\ttrailerLines.push(`/Prev ${this.structure.xrefOffset}`, \">>\");\n\n\t\tconst trailerStr =\n\t\t\t`trailer\\n${trailerLines.join(\"\\n\")}\\nstartxref\\n${xrefOffset}\\n%%EOF`;\n\t\tconst trailerBytes = latin1Encoder.encode(trailerStr);\n\t\tappendParts.push(trailerBytes);\n\n\t\t// Combine original + append parts\n\t\tconst totalAppendLength = appendParts.reduce((s, p) => s + p.length, 0);\n\t\tconst result = new Uint8Array(this.originalData.length + totalAppendLength);\n\t\tresult.set(this.originalData, 0);\n\t\tlet pos = this.originalData.length;\n\t\tfor (const part of appendParts) {\n\t\t\tresult.set(part, pos);\n\t\t\tpos += part.length;\n\t\t}\n\n\t\t// --- Calculate byte range for signature ---\n\t\tlet byteRange: [number, number, number, number] = [0, 0, 0, 0];\n\n\t\tif (withSignature) {\n\t\t\tconst { br, updatedPdf } = updateByteRange(result);\n\t\t\tbyteRange = br;\n\t\t\treturn { pdf: updatedPdf, byteRange };\n\t\t}\n\n\t\treturn { pdf: result, byteRange };\n\t}\n\n\tprivate hasImagesForPage(page: PdfPageImpl): boolean {\n\t\treturn page.dirty && page.getImageRefs().size > 0;\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction buildXrefTable(\n\tentries: Array<{ objNum: number; offset: number }>,\n): string {\n\tconst sorted = [...entries].sort((a, b) => a.objNum - b.objNum);\n\n\tconst subsections: Array<{ start: number; offsets: number[] }> = [];\n\tfor (const entry of sorted) {\n\t\tconst last = subsections[subsections.length - 1];\n\t\tif (last && entry.objNum === last.start + last.offsets.length) {\n\t\t\tlast.offsets.push(entry.offset);\n\t\t} else {\n\t\t\tsubsections.push({ start: entry.objNum, offsets: [entry.offset] });\n\t\t}\n\t}\n\n\tlet result = \"xref\\n\";\n\tfor (const sub of subsections) {\n\t\tresult += `${sub.start} ${sub.offsets.length}\\n`;\n\t\tfor (const offset of sub.offsets) {\n\t\t\tresult += `${String(offset).padStart(10, \"0\")} 00000 n \\n`;\n\t\t}\n\t}\n\treturn result;\n}\n\nfunction formatPdfDate(date: Date): string {\n\tconst y = date.getUTCFullYear();\n\tconst m = String(date.getUTCMonth() + 1).padStart(2, \"0\");\n\tconst d = String(date.getUTCDate()).padStart(2, \"0\");\n\tconst h = String(date.getUTCHours()).padStart(2, \"0\");\n\tconst min = String(date.getUTCMinutes()).padStart(2, \"0\");\n\tconst s = String(date.getUTCSeconds()).padStart(2, \"0\");\n\treturn `D:${y}${m}${d}${h}${min}${s}Z`;\n}\n\nfunction pdfString(str: string): string {\n\tconst escaped = str\n\t\t.replace(/\\\\/g, \"\\\\\\\\\")\n\t\t.replace(/\\(/g, \"\\\\(\")\n\t\t.replace(/\\)/g, \"\\\\)\");\n\treturn `(${escaped})`;\n}\n\n/**\n * Find and update the ByteRange placeholder in the PDF, returning the\n * actual byte range values and the updated PDF bytes.\n */\nfunction updateByteRange(pdf: Uint8Array): {\n\tbr: [number, number, number, number];\n\tupdatedPdf: Uint8Array;\n} {\n\tconst pdfStr = new TextDecoder(\"latin1\").decode(pdf);\n\n\t// Find /Contents < ... > in the signature object\n\tconst contentsMarker = \"/Contents <\";\n\tconst contentsIdx = pdfStr.lastIndexOf(contentsMarker);\n\tif (contentsIdx === -1) throw new Error(\"Cannot find Contents in signature\");\n\tconst contentsStart = contentsIdx + contentsMarker.length;\n\tconst contentsEnd = pdfStr.indexOf(\">\", contentsStart);\n\tif (contentsEnd === -1) throw new Error(\"Cannot find end of Contents hex\");\n\n\t// The byte range: [0, before-sig-hex, after-sig-hex, rest]\n\t// contentsStart - 1 is the position of '<'\n\t// contentsEnd + 1 is the position after '>'\n\tconst br: [number, number, number, number] = [\n\t\t0,\n\t\tcontentsStart - 1, // length1: everything before '<'\n\t\tcontentsEnd + 1, // offset2: after '>'\n\t\tpdf.length - (contentsEnd + 1), // length2: rest of PDF\n\t];\n\n\t// Now update the ByteRange placeholder with actual values\n\tconst byteRangeMarker = \"/ByteRange [\";\n\tconst brIdx = pdfStr.lastIndexOf(byteRangeMarker);\n\tif (brIdx === -1) throw new Error(\"Cannot find ByteRange in PDF\");\n\tconst brStart = brIdx + byteRangeMarker.length;\n\tconst brEnd = pdfStr.indexOf(\"]\", brStart);\n\tif (brEnd === -1) throw new Error(\"Cannot find end of ByteRange\");\n\n\tconst placeholderLen = brEnd - brStart;\n\tconst brValueStr = `${br[0]} ${br[1]} ${br[2]} ${br[3]}`;\n\tconst paddedBr = brValueStr.padEnd(placeholderLen, \" \");\n\n\t// Build updated PDF\n\tconst updatedPdf = new Uint8Array(pdf.length);\n\tupdatedPdf.set(pdf);\n\n\t// Write the byte range values\n\tconst brBytes = new TextEncoder().encode(paddedBr);\n\tupdatedPdf.set(brBytes, brStart);\n\n\treturn { br, updatedPdf };\n}\n\n/**\n * Find matching >> for a << in a content string\n */\nfunction findMatchingDictEndInContent(str: string, startPos: number): number {\n\tlet depth = 0;\n\tlet i = startPos;\n\n\twhile (i < str.length - 1) {\n\t\tif (str[i] === \"(\") {\n\t\t\ti++;\n\t\t\twhile (i < str.length && str[i] !== \")\") {\n\t\t\t\tif (str[i] === \"\\\\\") i++;\n\t\t\t\ti++;\n\t\t\t}\n\t\t\ti++;\n\t\t} else if (str[i] === \"<\" && str[i + 1] === \"<\") {\n\t\t\tdepth++;\n\t\t\ti += 2;\n\t\t} else if (str[i] === \">\" && str[i + 1] === \">\") {\n\t\t\tdepth--;\n\t\t\tif (depth === 0) return i;\n\t\t\ti += 2;\n\t\t} else {\n\t\t\ti++;\n\t\t}\n\t}\n\n\treturn -1;\n}\n\n/**\n * Remove a named dictionary entry (e.g. \"/Font << ... >>\") from a string,\n * correctly handling arbitrarily nested sub-dictionaries.\n */\nfunction removeNestedDictEntry(content: string, name: string): string {\n\tconst idx = content.indexOf(name);\n\tif (idx === -1) return content;\n\n\t// Find the opening << after the name\n\tconst dictOpen = content.indexOf(\"<<\", idx + name.length);\n\tif (dictOpen === -1) return content;\n\n\t// Use nesting-aware search to find the matching >>\n\tconst dictClose = findMatchingDictEndInContent(content, dictOpen);\n\tif (dictClose === -1) return content;\n\n\t// Remove from name start through the closing >>\n\treturn content.slice(0, idx) + content.slice(dictClose + 2);\n}\n\n// ---------------------------------------------------------------------------\n// Signature utility exports (for use by the e-signature library)\n// ---------------------------------------------------------------------------\n\n/**\n * Find the ByteRange and Contents placeholder in a signed PDF\n */\nexport function findByteRange(pdfData: Uint8Array): {\n\tbyteRange: [number, number, number, number];\n\tcontentsStart: number;\n\tcontentsEnd: number;\n\tplaceholderLength: number;\n} {\n\tconst pdf = new TextDecoder(\"latin1\").decode(pdfData);\n\n\tconst contentsMarker = \"/Contents <\";\n\tconst contentsIdx = pdf.lastIndexOf(contentsMarker);\n\tif (contentsIdx === -1) throw new Error(\"Could not find Contents in PDF\");\n\n\tconst contentsStart = contentsIdx + contentsMarker.length;\n\tconst contentsEnd = pdf.indexOf(\">\", contentsStart);\n\tif (contentsEnd === -1) throw new Error(\"Could not find end of Contents field\");\n\n\tconst placeholderLength = contentsEnd - contentsStart;\n\n\tconst byteRange: [number, number, number, number] = [\n\t\t0,\n\t\tcontentsStart - 1,\n\t\tcontentsEnd + 1,\n\t\tpdfData.length - (contentsEnd + 1),\n\t];\n\n\treturn { byteRange, contentsStart, contentsEnd, placeholderLength };\n}\n\n/**\n * Extract the bytes that need to be signed according to the ByteRange\n */\nexport function extractBytesToSign(\n\tpdfData: Uint8Array,\n\tbyteRange: [number, number, number, number],\n): Uint8Array {\n\tconst [offset1, length1, offset2, length2] = byteRange;\n\n\tif (offset1 < 0 || length1 <= 0 || offset2 <= 0 || length2 <= 0) {\n\t\tthrow new Error(`Invalid ByteRange values: [${byteRange.join(\", \")}]`);\n\t}\n\n\tif (offset1 + length1 > pdfData.length || offset2 + length2 > pdfData.length) {\n\t\tthrow new Error(\"ByteRange exceeds PDF data size\");\n\t}\n\n\tconst chunk1 = pdfData.slice(offset1, offset1 + length1);\n\tconst chunk2 = pdfData.slice(offset2, offset2 + length2);\n\n\tconst result = new Uint8Array(chunk1.length + chunk2.length);\n\tresult.set(chunk1, 0);\n\tresult.set(chunk2, chunk1.length);\n\treturn result;\n}\n\n/**\n * Embed a signature (as raw bytes) into the Contents placeholder\n */\nexport function embedSignature(\n\tpdfData: Uint8Array,\n\tsignature: Uint8Array,\n): Uint8Array {\n\tconst { contentsStart, placeholderLength } = findByteRange(pdfData);\n\n\t// Convert signature to hex\n\tconst hexChars: string[] = [];\n\tfor (let i = 0; i < signature.length; i++) {\n\t\thexChars.push(signature[i]!.toString(16).padStart(2, \"0\"));\n\t}\n\tconst signatureHex = hexChars.join(\"\");\n\n\tif (signatureHex.length > placeholderLength) {\n\t\tthrow new Error(\n\t\t\t`Signature too large: ${signatureHex.length} hex chars, placeholder is ${placeholderLength}`,\n\t\t);\n\t}\n\n\tconst paddedHex = signatureHex.padEnd(placeholderLength, \"0\");\n\tconst hexBytes = new TextEncoder().encode(paddedHex);\n\n\tconst result = new Uint8Array(pdfData.length);\n\tresult.set(pdfData);\n\tresult.set(hexBytes, contentsStart);\n\treturn result;\n}\n",
|
|
8
|
+
"/**\n * PDF Editing Plugin\n *\n * Load existing PDFs, modify them (draw text, rectangles, images),\n * and save with incremental updates. Also supports creating signature\n * placeholders for digital signing workflows.\n */\n\nexport type {\n\tPdfDocument,\n\tPdfPage,\n\tPdfImage,\n\tTextOptions,\n\tRectOptions,\n\tImageOptions,\n\tSignaturePlaceholderOptions,\n} from \"./types.ts\";\n\nexport { PdfDocumentImpl } from \"./document.ts\";\n\nexport {\n\tfindByteRange,\n\textractBytesToSign,\n\tembedSignature,\n} from \"./document.ts\";\n\nimport { PdfDocumentImpl } from \"./document.ts\";\nimport type { PdfDocument } from \"./types.ts\";\n\n/**\n * Load an existing PDF for editing\n *\n * @param data - The PDF file contents as a Uint8Array\n * @returns A PdfDocument that can be modified and saved\n */\nexport function loadPdf(data: Uint8Array): PdfDocument {\n\treturn new PdfDocumentImpl(data);\n}\n"
|
|
9
|
+
],
|
|
10
|
+
"mappings": "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",
|
|
11
|
+
"debugId": "00A6738A73B4346A64756E2164756E21",
|
|
12
|
+
"names": []
|
|
13
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@f-o-t/pdf",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist"
|
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
"./plugins/parsing": {
|
|
20
20
|
"types": "./dist/plugins/parsing/index.d.ts",
|
|
21
21
|
"default": "./dist/plugins/parsing/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./plugins/editing": {
|
|
24
|
+
"types": "./dist/plugins/editing/index.d.ts",
|
|
25
|
+
"default": "./dist/plugins/editing/index.js"
|
|
22
26
|
}
|
|
23
27
|
},
|
|
24
28
|
"scripts": {
|
|
@@ -29,7 +33,6 @@
|
|
|
29
33
|
"typecheck": "bun x --bun fot typecheck"
|
|
30
34
|
},
|
|
31
35
|
"dependencies": {
|
|
32
|
-
"@f-o-t/digital-certificate": "^1.0.4",
|
|
33
36
|
"zod": "^4.3.6"
|
|
34
37
|
},
|
|
35
38
|
"devDependencies": {
|