UNPKG

12.9 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.XRefParseException = exports.XRefEntryException = exports.ParserEOFException = exports.MissingDataException = exports.DocStats = void 0;
28exports.collectActions = collectActions;
29exports.encodeToXmlString = encodeToXmlString;
30exports.escapePDFName = escapePDFName;
31exports.getArrayLookupTableFactory = getArrayLookupTableFactory;
32exports.getInheritableProperty = getInheritableProperty;
33exports.getLookupTableFactory = getLookupTableFactory;
34exports.getNewAnnotationsMap = getNewAnnotationsMap;
35exports.isWhiteSpace = isWhiteSpace;
36exports.log2 = log2;
37exports.numberToString = numberToString;
38exports.parseXFAPath = parseXFAPath;
39exports.readInt8 = readInt8;
40exports.readUint16 = readUint16;
41exports.readUint32 = readUint32;
42exports.recoverJsURL = recoverJsURL;
43exports.toRomanNumerals = toRomanNumerals;
44exports.validateCSSFont = validateCSSFont;
45
46var _util = require("../shared/util.js");
47
48var _primitives = require("./primitives.js");
49
50var _base_stream = require("./base_stream.js");
51
52function getLookupTableFactory(initializer) {
53 let lookup;
54 return function () {
55 if (initializer) {
56 lookup = Object.create(null);
57 initializer(lookup);
58 initializer = null;
59 }
60
61 return lookup;
62 };
63}
64
65function getArrayLookupTableFactory(initializer) {
66 let lookup;
67 return function () {
68 if (initializer) {
69 let arr = initializer();
70 initializer = null;
71 lookup = Object.create(null);
72
73 for (let i = 0, ii = arr.length; i < ii; i += 2) {
74 lookup[arr[i]] = arr[i + 1];
75 }
76
77 arr = null;
78 }
79
80 return lookup;
81 };
82}
83
84class MissingDataException extends _util.BaseException {
85 constructor(begin, end) {
86 super(`Missing data [${begin}, ${end})`, "MissingDataException");
87 this.begin = begin;
88 this.end = end;
89 }
90
91}
92
93exports.MissingDataException = MissingDataException;
94
95class ParserEOFException extends _util.BaseException {
96 constructor(msg) {
97 super(msg, "ParserEOFException");
98 }
99
100}
101
102exports.ParserEOFException = ParserEOFException;
103
104class XRefEntryException extends _util.BaseException {
105 constructor(msg) {
106 super(msg, "XRefEntryException");
107 }
108
109}
110
111exports.XRefEntryException = XRefEntryException;
112
113class XRefParseException extends _util.BaseException {
114 constructor(msg) {
115 super(msg, "XRefParseException");
116 }
117
118}
119
120exports.XRefParseException = XRefParseException;
121
122class DocStats {
123 constructor(handler) {
124 this._handler = handler;
125 this._streamTypes = new Set();
126 this._fontTypes = new Set();
127 }
128
129 _send() {
130 const streamTypes = Object.create(null),
131 fontTypes = Object.create(null);
132
133 for (const type of this._streamTypes) {
134 streamTypes[type] = true;
135 }
136
137 for (const type of this._fontTypes) {
138 fontTypes[type] = true;
139 }
140
141 this._handler.send("DocStats", {
142 streamTypes,
143 fontTypes
144 });
145 }
146
147 addStreamType(type) {
148 if (this._streamTypes.has(type)) {
149 return;
150 }
151
152 this._streamTypes.add(type);
153
154 this._send();
155 }
156
157 addFontType(type) {
158 if (this._fontTypes.has(type)) {
159 return;
160 }
161
162 this._fontTypes.add(type);
163
164 this._send();
165 }
166
167}
168
169exports.DocStats = DocStats;
170
171function getInheritableProperty({
172 dict,
173 key,
174 getArray = false,
175 stopWhenFound = true
176}) {
177 let values;
178 const visited = new _primitives.RefSet();
179
180 while (dict instanceof _primitives.Dict && !(dict.objId && visited.has(dict.objId))) {
181 if (dict.objId) {
182 visited.put(dict.objId);
183 }
184
185 const value = getArray ? dict.getArray(key) : dict.get(key);
186
187 if (value !== undefined) {
188 if (stopWhenFound) {
189 return value;
190 }
191
192 if (!values) {
193 values = [];
194 }
195
196 values.push(value);
197 }
198
199 dict = dict.get("Parent");
200 }
201
202 return values;
203}
204
205const ROMAN_NUMBER_MAP = ["", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM", "", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC", "", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"];
206
207function toRomanNumerals(number, lowerCase = false) {
208 (0, _util.assert)(Number.isInteger(number) && number > 0, "The number should be a positive integer.");
209 const romanBuf = [];
210 let pos;
211
212 while (number >= 1000) {
213 number -= 1000;
214 romanBuf.push("M");
215 }
216
217 pos = number / 100 | 0;
218 number %= 100;
219 romanBuf.push(ROMAN_NUMBER_MAP[pos]);
220 pos = number / 10 | 0;
221 number %= 10;
222 romanBuf.push(ROMAN_NUMBER_MAP[10 + pos]);
223 romanBuf.push(ROMAN_NUMBER_MAP[20 + number]);
224 const romanStr = romanBuf.join("");
225 return lowerCase ? romanStr.toLowerCase() : romanStr;
226}
227
228function log2(x) {
229 if (x <= 0) {
230 return 0;
231 }
232
233 return Math.ceil(Math.log2(x));
234}
235
236function readInt8(data, offset) {
237 return data[offset] << 24 >> 24;
238}
239
240function readUint16(data, offset) {
241 return data[offset] << 8 | data[offset + 1];
242}
243
244function readUint32(data, offset) {
245 return (data[offset] << 24 | data[offset + 1] << 16 | data[offset + 2] << 8 | data[offset + 3]) >>> 0;
246}
247
248function isWhiteSpace(ch) {
249 return ch === 0x20 || ch === 0x09 || ch === 0x0d || ch === 0x0a;
250}
251
252function parseXFAPath(path) {
253 const positionPattern = /(.+)\[(\d+)\]$/;
254 return path.split(".").map(component => {
255 const m = component.match(positionPattern);
256
257 if (m) {
258 return {
259 name: m[1],
260 pos: parseInt(m[2], 10)
261 };
262 }
263
264 return {
265 name: component,
266 pos: 0
267 };
268 });
269}
270
271function escapePDFName(str) {
272 const buffer = [];
273 let start = 0;
274
275 for (let i = 0, ii = str.length; i < ii; i++) {
276 const char = str.charCodeAt(i);
277
278 if (char < 0x21 || char > 0x7e || char === 0x23 || char === 0x28 || char === 0x29 || char === 0x3c || char === 0x3e || char === 0x5b || char === 0x5d || char === 0x7b || char === 0x7d || char === 0x2f || char === 0x25) {
279 if (start < i) {
280 buffer.push(str.substring(start, i));
281 }
282
283 buffer.push(`#${char.toString(16)}`);
284 start = i + 1;
285 }
286 }
287
288 if (buffer.length === 0) {
289 return str;
290 }
291
292 if (start < str.length) {
293 buffer.push(str.substring(start, str.length));
294 }
295
296 return buffer.join("");
297}
298
299function _collectJS(entry, xref, list, parents) {
300 if (!entry) {
301 return;
302 }
303
304 let parent = null;
305
306 if (entry instanceof _primitives.Ref) {
307 if (parents.has(entry)) {
308 return;
309 }
310
311 parent = entry;
312 parents.put(parent);
313 entry = xref.fetch(entry);
314 }
315
316 if (Array.isArray(entry)) {
317 for (const element of entry) {
318 _collectJS(element, xref, list, parents);
319 }
320 } else if (entry instanceof _primitives.Dict) {
321 if ((0, _primitives.isName)(entry.get("S"), "JavaScript")) {
322 const js = entry.get("JS");
323 let code;
324
325 if (js instanceof _base_stream.BaseStream) {
326 code = js.getString();
327 } else if (typeof js === "string") {
328 code = js;
329 }
330
331 code = code && (0, _util.stringToPDFString)(code).replace(/\u0000/g, "");
332
333 if (code) {
334 list.push(code);
335 }
336 }
337
338 _collectJS(entry.getRaw("Next"), xref, list, parents);
339 }
340
341 if (parent) {
342 parents.remove(parent);
343 }
344}
345
346function collectActions(xref, dict, eventType) {
347 const actions = Object.create(null);
348 const additionalActionsDicts = getInheritableProperty({
349 dict,
350 key: "AA",
351 stopWhenFound: false
352 });
353
354 if (additionalActionsDicts) {
355 for (let i = additionalActionsDicts.length - 1; i >= 0; i--) {
356 const additionalActions = additionalActionsDicts[i];
357
358 if (!(additionalActions instanceof _primitives.Dict)) {
359 continue;
360 }
361
362 for (const key of additionalActions.getKeys()) {
363 const action = eventType[key];
364
365 if (!action) {
366 continue;
367 }
368
369 const actionDict = additionalActions.getRaw(key);
370 const parents = new _primitives.RefSet();
371 const list = [];
372
373 _collectJS(actionDict, xref, list, parents);
374
375 if (list.length > 0) {
376 actions[action] = list;
377 }
378 }
379 }
380 }
381
382 if (dict.has("A")) {
383 const actionDict = dict.get("A");
384 const parents = new _primitives.RefSet();
385 const list = [];
386
387 _collectJS(actionDict, xref, list, parents);
388
389 if (list.length > 0) {
390 actions.Action = list;
391 }
392 }
393
394 return (0, _util.objectSize)(actions) > 0 ? actions : null;
395}
396
397const XMLEntities = {
398 0x3c: "&lt;",
399 0x3e: "&gt;",
400 0x26: "&amp;",
401 0x22: "&quot;",
402 0x27: "&apos;"
403};
404
405function encodeToXmlString(str) {
406 const buffer = [];
407 let start = 0;
408
409 for (let i = 0, ii = str.length; i < ii; i++) {
410 const char = str.codePointAt(i);
411
412 if (0x20 <= char && char <= 0x7e) {
413 const entity = XMLEntities[char];
414
415 if (entity) {
416 if (start < i) {
417 buffer.push(str.substring(start, i));
418 }
419
420 buffer.push(entity);
421 start = i + 1;
422 }
423 } else {
424 if (start < i) {
425 buffer.push(str.substring(start, i));
426 }
427
428 buffer.push(`&#x${char.toString(16).toUpperCase()};`);
429
430 if (char > 0xd7ff && (char < 0xe000 || char > 0xfffd)) {
431 i++;
432 }
433
434 start = i + 1;
435 }
436 }
437
438 if (buffer.length === 0) {
439 return str;
440 }
441
442 if (start < str.length) {
443 buffer.push(str.substring(start, str.length));
444 }
445
446 return buffer.join("");
447}
448
449function validateCSSFont(cssFontInfo) {
450 const DEFAULT_CSS_FONT_OBLIQUE = "14";
451 const DEFAULT_CSS_FONT_WEIGHT = "400";
452 const CSS_FONT_WEIGHT_VALUES = new Set(["100", "200", "300", "400", "500", "600", "700", "800", "900", "1000", "normal", "bold", "bolder", "lighter"]);
453 const {
454 fontFamily,
455 fontWeight,
456 italicAngle
457 } = cssFontInfo;
458
459 if (/^".*"$/.test(fontFamily)) {
460 if (/[^\\]"/.test(fontFamily.slice(1, fontFamily.length - 1))) {
461 (0, _util.warn)(`XFA - FontFamily contains some unescaped ": ${fontFamily}.`);
462 return false;
463 }
464 } else if (/^'.*'$/.test(fontFamily)) {
465 if (/[^\\]'/.test(fontFamily.slice(1, fontFamily.length - 1))) {
466 (0, _util.warn)(`XFA - FontFamily contains some unescaped ': ${fontFamily}.`);
467 return false;
468 }
469 } else {
470 for (const ident of fontFamily.split(/[ \t]+/)) {
471 if (/^(\d|(-(\d|-)))/.test(ident) || !/^[\w-\\]+$/.test(ident)) {
472 (0, _util.warn)(`XFA - FontFamily contains some invalid <custom-ident>: ${fontFamily}.`);
473 return false;
474 }
475 }
476 }
477
478 const weight = fontWeight ? fontWeight.toString() : "";
479 cssFontInfo.fontWeight = CSS_FONT_WEIGHT_VALUES.has(weight) ? weight : DEFAULT_CSS_FONT_WEIGHT;
480 const angle = parseFloat(italicAngle);
481 cssFontInfo.italicAngle = isNaN(angle) || angle < -90 || angle > 90 ? DEFAULT_CSS_FONT_OBLIQUE : italicAngle.toString();
482 return true;
483}
484
485function recoverJsURL(str) {
486 const URL_OPEN_METHODS = ["app.launchURL", "window.open", "xfa.host.gotoURL"];
487 const regex = new RegExp("^\\s*(" + URL_OPEN_METHODS.join("|").split(".").join("\\.") + ")\\((?:'|\")([^'\"]*)(?:'|\")(?:,\\s*(\\w+)\\)|\\))", "i");
488 const jsUrl = regex.exec(str);
489
490 if (jsUrl && jsUrl[2]) {
491 const url = jsUrl[2];
492 let newWindow = false;
493
494 if (jsUrl[3] === "true" && jsUrl[1] === "app.launchURL") {
495 newWindow = true;
496 }
497
498 return {
499 url,
500 newWindow
501 };
502 }
503
504 return null;
505}
506
507function numberToString(value) {
508 if (Number.isInteger(value)) {
509 return value.toString();
510 }
511
512 const roundedValue = Math.round(value * 100);
513
514 if (roundedValue % 100 === 0) {
515 return (roundedValue / 100).toString();
516 }
517
518 if (roundedValue % 10 === 0) {
519 return value.toFixed(1);
520 }
521
522 return value.toFixed(2);
523}
524
525function getNewAnnotationsMap(annotationStorage) {
526 if (!annotationStorage) {
527 return null;
528 }
529
530 const newAnnotationsByPage = new Map();
531
532 for (const [key, value] of annotationStorage) {
533 if (!key.startsWith(_util.AnnotationEditorPrefix)) {
534 continue;
535 }
536
537 let annotations = newAnnotationsByPage.get(value.pageIndex);
538
539 if (!annotations) {
540 annotations = [];
541 newAnnotationsByPage.set(value.pageIndex, annotations);
542 }
543
544 annotations.push(value);
545 }
546
547 return newAnnotationsByPage.size > 0 ? newAnnotationsByPage : null;
548}
\No newline at end of file