1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 | "use strict";
|
23 |
|
24 | Object.defineProperty(exports, "__esModule", {
|
25 | value: true
|
26 | });
|
27 | exports.incrementalUpdate = incrementalUpdate;
|
28 | exports.writeDict = writeDict;
|
29 | exports.writeObject = writeObject;
|
30 |
|
31 | var _util = require("../shared/util.js");
|
32 |
|
33 | var _primitives = require("./primitives.js");
|
34 |
|
35 | var _core_utils = require("./core_utils.js");
|
36 |
|
37 | var _xml_parser = require("./xml_parser.js");
|
38 |
|
39 | var _base_stream = require("./base_stream.js");
|
40 |
|
41 | var _crypto = require("./crypto.js");
|
42 |
|
43 | function writeObject(ref, obj, buffer, transform) {
|
44 | buffer.push(`${ref.num} ${ref.gen} obj\n`);
|
45 |
|
46 | if (obj instanceof _primitives.Dict) {
|
47 | writeDict(obj, buffer, transform);
|
48 | } else if (obj instanceof _base_stream.BaseStream) {
|
49 | writeStream(obj, buffer, transform);
|
50 | }
|
51 |
|
52 | buffer.push("\nendobj\n");
|
53 | }
|
54 |
|
55 | function writeDict(dict, buffer, transform) {
|
56 | buffer.push("<<");
|
57 |
|
58 | for (const key of dict.getKeys()) {
|
59 | buffer.push(` /${(0, _core_utils.escapePDFName)(key)} `);
|
60 | writeValue(dict.getRaw(key), buffer, transform);
|
61 | }
|
62 |
|
63 | buffer.push(">>");
|
64 | }
|
65 |
|
66 | function writeStream(stream, buffer, transform) {
|
67 | writeDict(stream.dict, buffer, transform);
|
68 | buffer.push(" stream\n");
|
69 | let string = stream.getString();
|
70 |
|
71 | if (transform !== null) {
|
72 | string = transform.encryptString(string);
|
73 | }
|
74 |
|
75 | buffer.push(string, "\nendstream\n");
|
76 | }
|
77 |
|
78 | function writeArray(array, buffer, transform) {
|
79 | buffer.push("[");
|
80 | let first = true;
|
81 |
|
82 | for (const val of array) {
|
83 | if (!first) {
|
84 | buffer.push(" ");
|
85 | } else {
|
86 | first = false;
|
87 | }
|
88 |
|
89 | writeValue(val, buffer, transform);
|
90 | }
|
91 |
|
92 | buffer.push("]");
|
93 | }
|
94 |
|
95 | function writeValue(value, buffer, transform) {
|
96 | if (value instanceof _primitives.Name) {
|
97 | buffer.push(`/${(0, _core_utils.escapePDFName)(value.name)}`);
|
98 | } else if (value instanceof _primitives.Ref) {
|
99 | buffer.push(`${value.num} ${value.gen} R`);
|
100 | } else if (Array.isArray(value)) {
|
101 | writeArray(value, buffer, transform);
|
102 | } else if (typeof value === "string") {
|
103 | if (transform !== null) {
|
104 | value = transform.encryptString(value);
|
105 | }
|
106 |
|
107 | buffer.push(`(${(0, _util.escapeString)(value)})`);
|
108 | } else if (typeof value === "number") {
|
109 | buffer.push((0, _core_utils.numberToString)(value));
|
110 | } else if (typeof value === "boolean") {
|
111 | buffer.push(value.toString());
|
112 | } else if (value instanceof _primitives.Dict) {
|
113 | writeDict(value, buffer, transform);
|
114 | } else if (value instanceof _base_stream.BaseStream) {
|
115 | writeStream(value, buffer, transform);
|
116 | } else if (value === null) {
|
117 | buffer.push("null");
|
118 | } else {
|
119 | (0, _util.warn)(`Unhandled value in writer: ${typeof value}, please file a bug.`);
|
120 | }
|
121 | }
|
122 |
|
123 | function writeInt(number, size, offset, buffer) {
|
124 | for (let i = size + offset - 1; i > offset - 1; i--) {
|
125 | buffer[i] = number & 0xff;
|
126 | number >>= 8;
|
127 | }
|
128 |
|
129 | return offset + size;
|
130 | }
|
131 |
|
132 | function writeString(string, offset, buffer) {
|
133 | for (let i = 0, len = string.length; i < len; i++) {
|
134 | buffer[offset + i] = string.charCodeAt(i) & 0xff;
|
135 | }
|
136 | }
|
137 |
|
138 | function computeMD5(filesize, xrefInfo) {
|
139 | const time = Math.floor(Date.now() / 1000);
|
140 | const filename = xrefInfo.filename || "";
|
141 | const md5Buffer = [time.toString(), filename, filesize.toString()];
|
142 | let md5BufferLen = md5Buffer.reduce((a, str) => a + str.length, 0);
|
143 |
|
144 | for (const value of Object.values(xrefInfo.info)) {
|
145 | md5Buffer.push(value);
|
146 | md5BufferLen += value.length;
|
147 | }
|
148 |
|
149 | const array = new Uint8Array(md5BufferLen);
|
150 | let offset = 0;
|
151 |
|
152 | for (const str of md5Buffer) {
|
153 | writeString(str, offset, array);
|
154 | offset += str.length;
|
155 | }
|
156 |
|
157 | return (0, _util.bytesToString)((0, _crypto.calculateMD5)(array));
|
158 | }
|
159 |
|
160 | function writeXFADataForAcroform(str, newRefs) {
|
161 | const xml = new _xml_parser.SimpleXMLParser({
|
162 | hasAttributes: true
|
163 | }).parseFromString(str);
|
164 |
|
165 | for (const {
|
166 | xfa
|
167 | } of newRefs) {
|
168 | if (!xfa) {
|
169 | continue;
|
170 | }
|
171 |
|
172 | const {
|
173 | path,
|
174 | value
|
175 | } = xfa;
|
176 |
|
177 | if (!path) {
|
178 | continue;
|
179 | }
|
180 |
|
181 | const node = xml.documentElement.searchNode((0, _core_utils.parseXFAPath)(path), 0);
|
182 |
|
183 | if (node) {
|
184 | if (Array.isArray(value)) {
|
185 | node.childNodes = value.map(val => new _xml_parser.SimpleDOMNode("value", val));
|
186 | } else {
|
187 | node.childNodes = [new _xml_parser.SimpleDOMNode("#text", value)];
|
188 | }
|
189 | } else {
|
190 | (0, _util.warn)(`Node not found for path: ${path}`);
|
191 | }
|
192 | }
|
193 |
|
194 | const buffer = [];
|
195 | xml.documentElement.dump(buffer);
|
196 | return buffer.join("");
|
197 | }
|
198 |
|
199 | function updateXFA({
|
200 | xfaData,
|
201 | xfaDatasetsRef,
|
202 | hasXfaDatasetsEntry,
|
203 | acroFormRef,
|
204 | acroForm,
|
205 | newRefs,
|
206 | xref,
|
207 | xrefInfo
|
208 | }) {
|
209 | if (xref === null) {
|
210 | return;
|
211 | }
|
212 |
|
213 | if (!hasXfaDatasetsEntry) {
|
214 | if (!acroFormRef) {
|
215 | (0, _util.warn)("XFA - Cannot save it");
|
216 | return;
|
217 | }
|
218 |
|
219 | const oldXfa = acroForm.get("XFA");
|
220 | const newXfa = oldXfa.slice();
|
221 | newXfa.splice(2, 0, "datasets");
|
222 | newXfa.splice(3, 0, xfaDatasetsRef);
|
223 | acroForm.set("XFA", newXfa);
|
224 | const encrypt = xref.encrypt;
|
225 | let transform = null;
|
226 |
|
227 | if (encrypt) {
|
228 | transform = encrypt.createCipherTransform(acroFormRef.num, acroFormRef.gen);
|
229 | }
|
230 |
|
231 | const buffer = [`${acroFormRef.num} ${acroFormRef.gen} obj\n`];
|
232 | writeDict(acroForm, buffer, transform);
|
233 | buffer.push("\n");
|
234 | acroForm.set("XFA", oldXfa);
|
235 | newRefs.push({
|
236 | ref: acroFormRef,
|
237 | data: buffer.join("")
|
238 | });
|
239 | }
|
240 |
|
241 | if (xfaData === null) {
|
242 | const datasets = xref.fetchIfRef(xfaDatasetsRef);
|
243 | xfaData = writeXFADataForAcroform(datasets.getString(), newRefs);
|
244 | }
|
245 |
|
246 | const encrypt = xref.encrypt;
|
247 |
|
248 | if (encrypt) {
|
249 | const transform = encrypt.createCipherTransform(xfaDatasetsRef.num, xfaDatasetsRef.gen);
|
250 | xfaData = transform.encryptString(xfaData);
|
251 | }
|
252 |
|
253 | const data = `${xfaDatasetsRef.num} ${xfaDatasetsRef.gen} obj\n` + `<< /Type /EmbeddedFile /Length ${xfaData.length}>>\nstream\n` + xfaData + "\nendstream\nendobj\n";
|
254 | newRefs.push({
|
255 | ref: xfaDatasetsRef,
|
256 | data
|
257 | });
|
258 | }
|
259 |
|
260 | function incrementalUpdate({
|
261 | originalData,
|
262 | xrefInfo,
|
263 | newRefs,
|
264 | xref = null,
|
265 | hasXfa = false,
|
266 | xfaDatasetsRef = null,
|
267 | hasXfaDatasetsEntry = false,
|
268 | acroFormRef = null,
|
269 | acroForm = null,
|
270 | xfaData = null
|
271 | }) {
|
272 | if (hasXfa) {
|
273 | updateXFA({
|
274 | xfaData,
|
275 | xfaDatasetsRef,
|
276 | hasXfaDatasetsEntry,
|
277 | acroFormRef,
|
278 | acroForm,
|
279 | newRefs,
|
280 | xref,
|
281 | xrefInfo
|
282 | });
|
283 | }
|
284 |
|
285 | const newXref = new _primitives.Dict(null);
|
286 | const refForXrefTable = xrefInfo.newRef;
|
287 | let buffer, baseOffset;
|
288 | const lastByte = originalData.at(-1);
|
289 |
|
290 | if (lastByte === 0x0a || lastByte === 0x0d) {
|
291 | buffer = [];
|
292 | baseOffset = originalData.length;
|
293 | } else {
|
294 | buffer = ["\n"];
|
295 | baseOffset = originalData.length + 1;
|
296 | }
|
297 |
|
298 | newXref.set("Size", refForXrefTable.num + 1);
|
299 | newXref.set("Prev", xrefInfo.startXRef);
|
300 | newXref.set("Type", _primitives.Name.get("XRef"));
|
301 |
|
302 | if (xrefInfo.rootRef !== null) {
|
303 | newXref.set("Root", xrefInfo.rootRef);
|
304 | }
|
305 |
|
306 | if (xrefInfo.infoRef !== null) {
|
307 | newXref.set("Info", xrefInfo.infoRef);
|
308 | }
|
309 |
|
310 | if (xrefInfo.encryptRef !== null) {
|
311 | newXref.set("Encrypt", xrefInfo.encryptRef);
|
312 | }
|
313 |
|
314 | newRefs.push({
|
315 | ref: refForXrefTable,
|
316 | data: ""
|
317 | });
|
318 | newRefs = newRefs.sort((a, b) => {
|
319 | return a.ref.num - b.ref.num;
|
320 | });
|
321 | const xrefTableData = [[0, 1, 0xffff]];
|
322 | const indexes = [0, 1];
|
323 | let maxOffset = 0;
|
324 |
|
325 | for (const {
|
326 | ref,
|
327 | data
|
328 | } of newRefs) {
|
329 | maxOffset = Math.max(maxOffset, baseOffset);
|
330 | xrefTableData.push([1, baseOffset, Math.min(ref.gen, 0xffff)]);
|
331 | baseOffset += data.length;
|
332 | indexes.push(ref.num, 1);
|
333 | buffer.push(data);
|
334 | }
|
335 |
|
336 | newXref.set("Index", indexes);
|
337 |
|
338 | if (Array.isArray(xrefInfo.fileIds) && xrefInfo.fileIds.length > 0) {
|
339 | const md5 = computeMD5(baseOffset, xrefInfo);
|
340 | newXref.set("ID", [xrefInfo.fileIds[0], md5]);
|
341 | }
|
342 |
|
343 | const offsetSize = Math.ceil(Math.log2(maxOffset) / 8);
|
344 | const sizes = [1, offsetSize, 2];
|
345 | const structSize = sizes[0] + sizes[1] + sizes[2];
|
346 | const tableLength = structSize * xrefTableData.length;
|
347 | newXref.set("W", sizes);
|
348 | newXref.set("Length", tableLength);
|
349 | buffer.push(`${refForXrefTable.num} ${refForXrefTable.gen} obj\n`);
|
350 | writeDict(newXref, buffer, null);
|
351 | buffer.push(" stream\n");
|
352 | const bufferLen = buffer.reduce((a, str) => a + str.length, 0);
|
353 | const footer = `\nendstream\nendobj\nstartxref\n${baseOffset}\n%%EOF\n`;
|
354 | const array = new Uint8Array(originalData.length + bufferLen + tableLength + footer.length);
|
355 | array.set(originalData);
|
356 | let offset = originalData.length;
|
357 |
|
358 | for (const str of buffer) {
|
359 | writeString(str, offset, array);
|
360 | offset += str.length;
|
361 | }
|
362 |
|
363 | for (const [type, objOffset, gen] of xrefTableData) {
|
364 | offset = writeInt(type, sizes[0], offset, array);
|
365 | offset = writeInt(objOffset, sizes[1], offset, array);
|
366 | offset = writeInt(gen, sizes[2], offset, array);
|
367 | }
|
368 |
|
369 | writeString(footer, offset, array);
|
370 | return array;
|
371 | } |
\ | No newline at end of file |