UNPKG

9.63 kBJavaScriptView Raw
1/**
2 * @licstart The following is the entire license notice for the
3 * JavaScript code in this page
4 *
5 * Copyright 2022 Mozilla Foundation
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 *
19 * @licend The above is the entire license notice for the
20 * JavaScript code in this page
21 */
22"use strict";
23
24Object.defineProperty(exports, "__esModule", {
25 value: true
26});
27exports.incrementalUpdate = incrementalUpdate;
28exports.writeDict = writeDict;
29exports.writeObject = writeObject;
30
31var _util = require("../shared/util.js");
32
33var _primitives = require("./primitives.js");
34
35var _core_utils = require("./core_utils.js");
36
37var _xml_parser = require("./xml_parser.js");
38
39var _base_stream = require("./base_stream.js");
40
41var _crypto = require("./crypto.js");
42
43function 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
55function 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
66function 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
78function 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
95function 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
123function 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
132function 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
138function 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
160function 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
199function 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
260function 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