writer.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. /**
  2. * @licstart The following is the entire license notice for the
  3. * Javascript code in this page
  4. *
  5. * Copyright 2021 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. Object.defineProperty(exports, "__esModule", {
  24. value: true
  25. });
  26. exports.incrementalUpdate = incrementalUpdate;
  27. exports.writeDict = writeDict;
  28. var _util = require("../shared/util.js");
  29. var _primitives = require("./primitives.js");
  30. var _core_utils = require("./core_utils.js");
  31. var _xml_parser = require("./xml_parser.js");
  32. var _crypto = require("./crypto.js");
  33. function writeDict(dict, buffer, transform) {
  34. buffer.push("<<");
  35. for (const key of dict.getKeys()) {
  36. buffer.push(` /${(0, _core_utils.escapePDFName)(key)} `);
  37. writeValue(dict.getRaw(key), buffer, transform);
  38. }
  39. buffer.push(">>");
  40. }
  41. function writeStream(stream, buffer, transform) {
  42. writeDict(stream.dict, buffer, transform);
  43. buffer.push(" stream\n");
  44. let string = stream.getString();
  45. if (transform !== null) {
  46. string = transform.encryptString(string);
  47. }
  48. buffer.push(string, "\nendstream\n");
  49. }
  50. function writeArray(array, buffer, transform) {
  51. buffer.push("[");
  52. let first = true;
  53. for (const val of array) {
  54. if (!first) {
  55. buffer.push(" ");
  56. } else {
  57. first = false;
  58. }
  59. writeValue(val, buffer, transform);
  60. }
  61. buffer.push("]");
  62. }
  63. function numberToString(value) {
  64. if (Number.isInteger(value)) {
  65. return value.toString();
  66. }
  67. const roundedValue = Math.round(value * 100);
  68. if (roundedValue % 100 === 0) {
  69. return (roundedValue / 100).toString();
  70. }
  71. if (roundedValue % 10 === 0) {
  72. return value.toFixed(1);
  73. }
  74. return value.toFixed(2);
  75. }
  76. function writeValue(value, buffer, transform) {
  77. if ((0, _primitives.isName)(value)) {
  78. buffer.push(`/${(0, _core_utils.escapePDFName)(value.name)}`);
  79. } else if ((0, _primitives.isRef)(value)) {
  80. buffer.push(`${value.num} ${value.gen} R`);
  81. } else if (Array.isArray(value)) {
  82. writeArray(value, buffer, transform);
  83. } else if (typeof value === "string") {
  84. if (transform !== null) {
  85. value = transform.encryptString(value);
  86. }
  87. buffer.push(`(${(0, _util.escapeString)(value)})`);
  88. } else if (typeof value === "number") {
  89. buffer.push(numberToString(value));
  90. } else if ((0, _primitives.isDict)(value)) {
  91. writeDict(value, buffer, transform);
  92. } else if ((0, _primitives.isStream)(value)) {
  93. writeStream(value, buffer, transform);
  94. }
  95. }
  96. function writeInt(number, size, offset, buffer) {
  97. for (let i = size + offset - 1; i > offset - 1; i--) {
  98. buffer[i] = number & 0xff;
  99. number >>= 8;
  100. }
  101. return offset + size;
  102. }
  103. function writeString(string, offset, buffer) {
  104. for (let i = 0, len = string.length; i < len; i++) {
  105. buffer[offset + i] = string.charCodeAt(i) & 0xff;
  106. }
  107. }
  108. function computeMD5(filesize, xrefInfo) {
  109. const time = Math.floor(Date.now() / 1000);
  110. const filename = xrefInfo.filename || "";
  111. const md5Buffer = [time.toString(), filename, filesize.toString()];
  112. let md5BufferLen = md5Buffer.reduce((a, str) => a + str.length, 0);
  113. for (const value of Object.values(xrefInfo.info)) {
  114. md5Buffer.push(value);
  115. md5BufferLen += value.length;
  116. }
  117. const array = new Uint8Array(md5BufferLen);
  118. let offset = 0;
  119. for (const str of md5Buffer) {
  120. writeString(str, offset, array);
  121. offset += str.length;
  122. }
  123. return (0, _util.bytesToString)((0, _crypto.calculateMD5)(array));
  124. }
  125. function updateXFA(datasetsRef, newRefs, xref) {
  126. if (datasetsRef === null || xref === null) {
  127. return;
  128. }
  129. const datasets = xref.fetchIfRef(datasetsRef);
  130. const str = datasets.getString();
  131. const xml = new _xml_parser.SimpleXMLParser({
  132. hasAttributes: true
  133. }).parseFromString(str);
  134. for (const {
  135. xfa
  136. } of newRefs) {
  137. if (!xfa) {
  138. continue;
  139. }
  140. const {
  141. path,
  142. value
  143. } = xfa;
  144. if (!path) {
  145. continue;
  146. }
  147. const node = xml.documentElement.searchNode((0, _core_utils.parseXFAPath)(path), 0);
  148. if (node) {
  149. node.childNodes = [new _xml_parser.SimpleDOMNode("#text", value)];
  150. } else {
  151. (0, _util.warn)(`Node not found for path: ${path}`);
  152. }
  153. }
  154. const buffer = [];
  155. xml.documentElement.dump(buffer);
  156. let updatedXml = buffer.join("");
  157. const encrypt = xref.encrypt;
  158. if (encrypt) {
  159. const transform = encrypt.createCipherTransform(datasetsRef.num, datasetsRef.gen);
  160. updatedXml = transform.encryptString(updatedXml);
  161. }
  162. const data = `${datasetsRef.num} ${datasetsRef.gen} obj\n` + `<< /Type /EmbeddedFile /Length ${updatedXml.length}>>\nstream\n` + updatedXml + "\nendstream\nendobj\n";
  163. newRefs.push({
  164. ref: datasetsRef,
  165. data
  166. });
  167. }
  168. function incrementalUpdate({
  169. originalData,
  170. xrefInfo,
  171. newRefs,
  172. xref = null,
  173. datasetsRef = null
  174. }) {
  175. updateXFA(datasetsRef, newRefs, xref);
  176. const newXref = new _primitives.Dict(null);
  177. const refForXrefTable = xrefInfo.newRef;
  178. let buffer, baseOffset;
  179. const lastByte = originalData[originalData.length - 1];
  180. if (lastByte === 0x0a || lastByte === 0x0d) {
  181. buffer = [];
  182. baseOffset = originalData.length;
  183. } else {
  184. buffer = ["\n"];
  185. baseOffset = originalData.length + 1;
  186. }
  187. newXref.set("Size", refForXrefTable.num + 1);
  188. newXref.set("Prev", xrefInfo.startXRef);
  189. newXref.set("Type", _primitives.Name.get("XRef"));
  190. if (xrefInfo.rootRef !== null) {
  191. newXref.set("Root", xrefInfo.rootRef);
  192. }
  193. if (xrefInfo.infoRef !== null) {
  194. newXref.set("Info", xrefInfo.infoRef);
  195. }
  196. if (xrefInfo.encryptRef !== null) {
  197. newXref.set("Encrypt", xrefInfo.encryptRef);
  198. }
  199. newRefs.push({
  200. ref: refForXrefTable,
  201. data: ""
  202. });
  203. newRefs = newRefs.sort((a, b) => {
  204. return a.ref.num - b.ref.num;
  205. });
  206. const xrefTableData = [[0, 1, 0xffff]];
  207. const indexes = [0, 1];
  208. let maxOffset = 0;
  209. for (const {
  210. ref,
  211. data
  212. } of newRefs) {
  213. maxOffset = Math.max(maxOffset, baseOffset);
  214. xrefTableData.push([1, baseOffset, Math.min(ref.gen, 0xffff)]);
  215. baseOffset += data.length;
  216. indexes.push(ref.num, 1);
  217. buffer.push(data);
  218. }
  219. newXref.set("Index", indexes);
  220. if (Array.isArray(xrefInfo.fileIds) && xrefInfo.fileIds.length > 0) {
  221. const md5 = computeMD5(baseOffset, xrefInfo);
  222. newXref.set("ID", [xrefInfo.fileIds[0], md5]);
  223. }
  224. const offsetSize = Math.ceil(Math.log2(maxOffset) / 8);
  225. const sizes = [1, offsetSize, 2];
  226. const structSize = sizes[0] + sizes[1] + sizes[2];
  227. const tableLength = structSize * xrefTableData.length;
  228. newXref.set("W", sizes);
  229. newXref.set("Length", tableLength);
  230. buffer.push(`${refForXrefTable.num} ${refForXrefTable.gen} obj\n`);
  231. writeDict(newXref, buffer, null);
  232. buffer.push(" stream\n");
  233. const bufferLen = buffer.reduce((a, str) => a + str.length, 0);
  234. const footer = `\nendstream\nendobj\nstartxref\n${baseOffset}\n%%EOF\n`;
  235. const array = new Uint8Array(originalData.length + bufferLen + tableLength + footer.length);
  236. array.set(originalData);
  237. let offset = originalData.length;
  238. for (const str of buffer) {
  239. writeString(str, offset, array);
  240. offset += str.length;
  241. }
  242. for (const [type, objOffset, gen] of xrefTableData) {
  243. offset = writeInt(type, sizes[0], offset, array);
  244. offset = writeInt(objOffset, sizes[1], offset, array);
  245. offset = writeInt(gen, sizes[2], offset, array);
  246. }
  247. writeString(footer, offset, array);
  248. return array;
  249. }