| /** |
| * @licstart The following is the entire license notice for the |
| * Javascript code in this page |
| * |
| * Copyright 2020 Mozilla Foundation |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| * @licend The above is the entire license notice for the |
| * Javascript code in this page |
| */ |
| "use strict"; |
| |
| Object.defineProperty(exports, "__esModule", { |
| value: true |
| }); |
| exports.writeDict = writeDict; |
| exports.incrementalUpdate = incrementalUpdate; |
| |
| var _util = require("../shared/util.js"); |
| |
| var _primitives = require("./primitives.js"); |
| |
| var _crypto = require("./crypto.js"); |
| |
| function writeDict(dict, buffer, transform) { |
| buffer.push("<<"); |
| |
| for (const key of dict.getKeys()) { |
| buffer.push(` /${key} `); |
| writeValue(dict.getRaw(key), buffer, transform); |
| } |
| |
| buffer.push(">>"); |
| } |
| |
| function writeStream(stream, buffer, transform) { |
| writeDict(stream.dict, buffer, transform); |
| buffer.push(" stream\n"); |
| let string = (0, _util.bytesToString)(stream.getBytes()); |
| |
| if (transform !== null) { |
| string = transform.encryptString(string); |
| } |
| |
| buffer.push(string); |
| buffer.push("\nendstream\n"); |
| } |
| |
| function writeArray(array, buffer, transform) { |
| buffer.push("["); |
| let first = true; |
| |
| for (const val of array) { |
| if (!first) { |
| buffer.push(" "); |
| } else { |
| first = false; |
| } |
| |
| writeValue(val, buffer, transform); |
| } |
| |
| buffer.push("]"); |
| } |
| |
| function numberToString(value) { |
| if (Number.isInteger(value)) { |
| return value.toString(); |
| } |
| |
| const roundedValue = Math.round(value * 100); |
| |
| if (roundedValue % 100 === 0) { |
| return (roundedValue / 100).toString(); |
| } |
| |
| if (roundedValue % 10 === 0) { |
| return value.toFixed(1); |
| } |
| |
| return value.toFixed(2); |
| } |
| |
| function writeValue(value, buffer, transform) { |
| if ((0, _primitives.isName)(value)) { |
| buffer.push(`/${value.name}`); |
| } else if ((0, _primitives.isRef)(value)) { |
| buffer.push(`${value.num} ${value.gen} R`); |
| } else if (Array.isArray(value)) { |
| writeArray(value, buffer, transform); |
| } else if (typeof value === "string") { |
| if (transform !== null) { |
| value = transform.encryptString(value); |
| } |
| |
| buffer.push(`(${(0, _util.escapeString)(value)})`); |
| } else if (typeof value === "number") { |
| buffer.push(numberToString(value)); |
| } else if ((0, _primitives.isDict)(value)) { |
| writeDict(value, buffer, transform); |
| } else if ((0, _primitives.isStream)(value)) { |
| writeStream(value, buffer, transform); |
| } |
| } |
| |
| function writeInt(number, size, offset, buffer) { |
| for (let i = size + offset - 1; i > offset - 1; i--) { |
| buffer[i] = number & 0xff; |
| number >>= 8; |
| } |
| |
| return offset + size; |
| } |
| |
| function writeString(string, offset, buffer) { |
| for (let i = 0, len = string.length; i < len; i++) { |
| buffer[offset + i] = string.charCodeAt(i) & 0xff; |
| } |
| } |
| |
| function computeMD5(filesize, xrefInfo) { |
| const time = Math.floor(Date.now() / 1000); |
| const filename = xrefInfo.filename || ""; |
| const md5Buffer = [time.toString(), filename, filesize.toString()]; |
| let md5BufferLen = md5Buffer.reduce((a, str) => a + str.length, 0); |
| |
| for (const value of Object.values(xrefInfo.info)) { |
| md5Buffer.push(value); |
| md5BufferLen += value.length; |
| } |
| |
| const array = new Uint8Array(md5BufferLen); |
| let offset = 0; |
| |
| for (const str of md5Buffer) { |
| writeString(str, offset, array); |
| offset += str.length; |
| } |
| |
| return (0, _util.bytesToString)((0, _crypto.calculateMD5)(array)); |
| } |
| |
| function incrementalUpdate(originalData, xrefInfo, newRefs) { |
| const newXref = new _primitives.Dict(null); |
| const refForXrefTable = xrefInfo.newRef; |
| let buffer, baseOffset; |
| const lastByte = originalData[originalData.length - 1]; |
| |
| if (lastByte === 0x0a || lastByte === 0x0d) { |
| buffer = []; |
| baseOffset = originalData.length; |
| } else { |
| buffer = ["\n"]; |
| baseOffset = originalData.length + 1; |
| } |
| |
| newXref.set("Size", refForXrefTable.num + 1); |
| newXref.set("Prev", xrefInfo.startXRef); |
| newXref.set("Type", _primitives.Name.get("XRef")); |
| |
| if (xrefInfo.rootRef !== null) { |
| newXref.set("Root", xrefInfo.rootRef); |
| } |
| |
| if (xrefInfo.infoRef !== null) { |
| newXref.set("Info", xrefInfo.infoRef); |
| } |
| |
| if (xrefInfo.encrypt !== null) { |
| newXref.set("Encrypt", xrefInfo.encrypt); |
| } |
| |
| newRefs.push({ |
| ref: refForXrefTable, |
| data: "" |
| }); |
| newRefs = newRefs.sort((a, b) => { |
| return a.ref.num - b.ref.num; |
| }); |
| const xrefTableData = [[0, 1, 0xffff]]; |
| const indexes = [0, 1]; |
| let maxOffset = 0; |
| |
| for (const { |
| ref, |
| data |
| } of newRefs) { |
| maxOffset = Math.max(maxOffset, baseOffset); |
| xrefTableData.push([1, baseOffset, Math.min(ref.gen, 0xffff)]); |
| baseOffset += data.length; |
| indexes.push(ref.num); |
| indexes.push(1); |
| buffer.push(data); |
| } |
| |
| newXref.set("Index", indexes); |
| |
| if (xrefInfo.fileIds.length !== 0) { |
| const md5 = computeMD5(baseOffset, xrefInfo); |
| newXref.set("ID", [xrefInfo.fileIds[0], md5]); |
| } |
| |
| const offsetSize = Math.ceil(Math.log2(maxOffset) / 8); |
| const sizes = [1, offsetSize, 2]; |
| const structSize = sizes[0] + sizes[1] + sizes[2]; |
| const tableLength = structSize * xrefTableData.length; |
| newXref.set("W", sizes); |
| newXref.set("Length", tableLength); |
| buffer.push(`${refForXrefTable.num} ${refForXrefTable.gen} obj\n`); |
| writeDict(newXref, buffer, null); |
| buffer.push(" stream\n"); |
| const bufferLen = buffer.reduce((a, str) => a + str.length, 0); |
| const footer = `\nendstream\nendobj\nstartxref\n${baseOffset}\n%%EOF\n`; |
| const array = new Uint8Array(originalData.length + bufferLen + tableLength + footer.length); |
| array.set(originalData); |
| let offset = originalData.length; |
| |
| for (const str of buffer) { |
| writeString(str, offset, array); |
| offset += str.length; |
| } |
| |
| for (const [type, objOffset, gen] of xrefTableData) { |
| offset = writeInt(type, sizes[0], offset, array); |
| offset = writeInt(objOffset, sizes[1], offset, array); |
| offset = writeInt(gen, sizes[2], offset, array); |
| } |
| |
| writeString(footer, offset, array); |
| return array; |
| } |