2
0

writer.js 9.4 KB

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