Преглед на файлове

PDF.js version 2.11.338 - See https://github.com/mozilla/pdf.js/releases/tag/v2.11.338

pdfjsbot преди 3 години
родител
ревизия
bd2a6cd87f
променени са 100 файла, в които са добавени 7850 реда и са изтрити 4394 реда
  1. 1 1
      bower.json
  2. 435 327
      build/pdf.js
  3. 0 0
      build/pdf.js.map
  4. 0 0
      build/pdf.min.js
  5. 0 0
      build/pdf.sandbox.js
  6. 0 0
      build/pdf.sandbox.js.map
  7. 0 0
      build/pdf.sandbox.min.js
  8. 393 138
      build/pdf.worker.js
  9. 0 0
      build/pdf.worker.js.map
  10. 0 0
      build/pdf.worker.min.js
  11. 130 36
      image_decoders/pdf.image_decoders.js
  12. 0 0
      image_decoders/pdf.image_decoders.js.map
  13. 0 0
      image_decoders/pdf.image_decoders.min.js
  14. 361 211
      legacy/build/pdf.js
  15. 0 0
      legacy/build/pdf.js.map
  16. 0 0
      legacy/build/pdf.min.js
  17. 0 0
      legacy/build/pdf.sandbox.js
  18. 0 0
      legacy/build/pdf.sandbox.js.map
  19. 0 0
      legacy/build/pdf.sandbox.min.js
  20. 361 212
      legacy/build/pdf.worker.js
  21. 0 0
      legacy/build/pdf.worker.js.map
  22. 0 0
      legacy/build/pdf.worker.min.js
  23. 352 270
      legacy/image_decoders/pdf.image_decoders.js
  24. 0 0
      legacy/image_decoders/pdf.image_decoders.js.map
  25. 0 0
      legacy/image_decoders/pdf.image_decoders.min.js
  26. 107 27
      legacy/web/pdf_viewer.css
  27. 1 1
      legacy/web/pdf_viewer.d.ts
  28. 587 324
      legacy/web/pdf_viewer.js
  29. 0 0
      legacy/web/pdf_viewer.js.map
  30. 160 45
      lib/core/annotation.js
  31. 1 1
      lib/core/bidi.js
  32. 0 1
      lib/core/calibri_factors.js
  33. 48 31
      lib/core/catalog.js
  34. 3 4
      lib/core/chunked_stream.js
  35. 7 7
      lib/core/cmap.js
  36. 44 6
      lib/core/core_utils.js
  37. 1 5
      lib/core/crypto.js
  38. 16 2
      lib/core/decode_stream.js
  39. 44 29
      lib/core/document.js
  40. 70 38
      lib/core/evaluator.js
  41. 107 37
      lib/core/fonts.js
  42. 0 1
      lib/core/helvetica_factors.js
  43. 6 3
      lib/core/image.js
  44. 17 21
      lib/core/image_utils.js
  45. 1 1
      lib/core/jbig2.js
  46. 8 3
      lib/core/jpg.js
  47. 7 1
      lib/core/jpx.js
  48. 0 1
      lib/core/liberationsans_widths.js
  49. 0 1
      lib/core/myriadpro_factors.js
  50. 2 2
      lib/core/operator_list.js
  51. 17 7
      lib/core/parser.js
  52. 7 9
      lib/core/pattern.js
  53. 2 11
      lib/core/primitives.js
  54. 0 1
      lib/core/segoeui_factors.js
  55. 32 0
      lib/core/standard_fonts.js
  56. 21 14
      lib/core/worker.js
  57. 67 8
      lib/core/writer.js
  58. 56 33
      lib/core/xfa/bind.js
  59. 34 0
      lib/core/xfa/fonts.js
  60. 2 2
      lib/core/xfa/formcalc_lexer.js
  61. 31 10
      lib/core/xfa/html_utils.js
  62. 7 7
      lib/core/xfa/layout.js
  63. 6 2
      lib/core/xfa/som.js
  64. 408 113
      lib/core/xfa/template.js
  65. 19 7
      lib/core/xfa/text.js
  66. 1 1
      lib/core/xfa/utils.js
  67. 29 3
      lib/core/xfa/xfa_object.js
  68. 4 4
      lib/core/xfa/xhtml.js
  69. 89 23
      lib/core/xfa_fonts.js
  70. 383 91
      lib/display/annotation_layer.js
  71. 15 4
      lib/display/annotation_storage.js
  72. 406 310
      lib/display/api.js
  73. 1609 1547
      lib/display/canvas.js
  74. 12 3
      lib/display/display_utils.js
  75. 1 7
      lib/display/fetch_stream.js
  76. 41 40
      lib/display/network.js
  77. 9 0
      lib/display/optional_content_config.js
  78. 20 21
      lib/display/pattern_helper.js
  79. 19 5
      lib/display/text_layer.js
  80. 56 6
      lib/display/xfa_layer.js
  81. 80 0
      lib/display/xfa_text.js
  82. 46 34
      lib/pdf.js
  83. 0 0
      lib/pdf.sandbox.js
  84. 2 2
      lib/pdf.worker.js
  85. 50 52
      lib/shared/message_handler.js
  86. 66 12
      lib/shared/util.js
  87. 41 17
      lib/test/unit/annotation_spec.js
  88. 16 0
      lib/test/unit/annotation_storage_spec.js
  89. 105 5
      lib/test/unit/api_spec.js
  90. 35 0
      lib/test/unit/crypto_spec.js
  91. 7 8
      lib/test/unit/jasmine-boot.js
  92. 19 2
      lib/test/unit/parser_spec.js
  93. 0 9
      lib/test/unit/primitives_spec.js
  94. 30 0
      lib/test/unit/ui_utils_spec.js
  95. 44 1
      lib/test/unit/writer_spec.js
  96. 104 0
      lib/test/unit/xfa_parser_spec.js
  97. 338 4
      lib/test/unit/xfa_tohtml_spec.js
  98. 45 45
      lib/web/annotation_layer_builder.js
  99. 118 119
      lib/web/app.js
  100. 31 8
      lib/web/app_options.js

+ 1 - 1
bower.json

@@ -1,6 +1,6 @@
 {
   "name": "pdfjs-dist",
-  "version": "2.10.377",
+  "version": "2.11.338",
   "main": [
     "build/pdf.js",
     "build/pdf.worker.js"

Файловите разлики са ограничени, защото са твърде много
+ 435 - 327
build/pdf.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
build/pdf.js.map


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
build/pdf.min.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
build/pdf.sandbox.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
build/pdf.sandbox.js.map


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
build/pdf.sandbox.min.js


Файловите разлики са ограничени, защото са твърде много
+ 393 - 138
build/pdf.worker.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
build/pdf.worker.js.map


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
build/pdf.worker.min.js


+ 130 - 36
image_decoders/pdf.image_decoders.js

@@ -73,7 +73,7 @@ exports.stringToUTF8String = stringToUTF8String;
 exports.unreachable = unreachable;
 exports.utf8StringToString = utf8StringToString;
 exports.warn = warn;
-exports.VerbosityLevel = exports.Util = exports.UNSUPPORTED_FEATURES = exports.UnknownErrorException = exports.UnexpectedResponseException = exports.TextRenderingMode = exports.StreamType = exports.PermissionFlag = exports.PasswordResponses = exports.PasswordException = exports.PageActionEventType = exports.OPS = exports.MissingPDFException = exports.IsLittleEndianCached = exports.IsEvalSupportedCached = exports.InvalidPDFException = exports.ImageKind = exports.IDENTITY_MATRIX = exports.FormatError = exports.FontType = exports.FONT_IDENTITY_MATRIX = exports.DocumentActionEventType = exports.CMapCompressionType = exports.BaseException = exports.AnnotationType = exports.AnnotationStateModelType = exports.AnnotationReviewState = exports.AnnotationReplyType = exports.AnnotationMarkedState = exports.AnnotationFlag = exports.AnnotationFieldFlag = exports.AnnotationBorderStyleType = exports.AnnotationActionEventType = exports.AbortException = void 0;
+exports.VerbosityLevel = exports.Util = exports.UNSUPPORTED_FEATURES = exports.UnknownErrorException = exports.UnexpectedResponseException = exports.TextRenderingMode = exports.StreamType = exports.RenderingIntentFlag = exports.PermissionFlag = exports.PasswordResponses = exports.PasswordException = exports.PageActionEventType = exports.OPS = exports.MissingPDFException = exports.IsLittleEndianCached = exports.IsEvalSupportedCached = exports.InvalidPDFException = exports.ImageKind = exports.IDENTITY_MATRIX = exports.FormatError = exports.FontType = exports.FONT_IDENTITY_MATRIX = exports.DocumentActionEventType = exports.CMapCompressionType = exports.BaseException = exports.AnnotationType = exports.AnnotationStateModelType = exports.AnnotationReviewState = exports.AnnotationReplyType = exports.AnnotationMode = exports.AnnotationMarkedState = exports.AnnotationFlag = exports.AnnotationFieldFlag = exports.AnnotationBorderStyleType = exports.AnnotationActionEventType = exports.AbortException = void 0;
 
 __w_pdfjs_require__(2);
 
@@ -81,6 +81,23 @@ const IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0];
 exports.IDENTITY_MATRIX = IDENTITY_MATRIX;
 const FONT_IDENTITY_MATRIX = [0.001, 0, 0, 0.001, 0, 0];
 exports.FONT_IDENTITY_MATRIX = FONT_IDENTITY_MATRIX;
+const RenderingIntentFlag = {
+  ANY: 0x01,
+  DISPLAY: 0x02,
+  PRINT: 0x04,
+  ANNOTATIONS_FORMS: 0x10,
+  ANNOTATIONS_STORAGE: 0x20,
+  ANNOTATIONS_DISABLE: 0x40,
+  OPLIST: 0x100
+};
+exports.RenderingIntentFlag = RenderingIntentFlag;
+const AnnotationMode = {
+  DISABLE: 0,
+  ENABLE: 1,
+  ENABLE_FORMS: 2,
+  ENABLE_STORAGE: 3
+};
+exports.AnnotationMode = AnnotationMode;
 const PermissionFlag = {
   PRINT: 0x04,
   MODIFY_CONTENTS: 0x08,
@@ -391,7 +408,8 @@ const UNSUPPORTED_FEATURES = {
   errorFontLoadNative: "errorFontLoadNative",
   errorFontBuildPath: "errorFontBuildPath",
   errorFontGetPath: "errorFontGetPath",
-  errorMarkedContent: "errorMarkedContent"
+  errorMarkedContent: "errorMarkedContent",
+  errorContentSubStream: "errorContentSubStream"
 };
 exports.UNSUPPORTED_FEATURES = UNSUPPORTED_FEATURES;
 const PasswordResponses = {
@@ -468,12 +486,28 @@ function _isValidProtocol(url) {
   }
 }
 
-function createValidAbsoluteUrl(url, baseUrl) {
+function createValidAbsoluteUrl(url, baseUrl = null, options = null) {
   if (!url) {
     return null;
   }
 
   try {
+    if (options && typeof url === "string") {
+      if (options.addDefaultProtocol && url.startsWith("www.")) {
+        const dots = url.match(/\./g);
+
+        if (dots && dots.length >= 2) {
+          url = `http://${url}`;
+        }
+      }
+
+      if (options.tryConvertEncoding) {
+        try {
+          url = stringToUTF8String(url);
+        } catch (ex) {}
+      }
+    }
+
     const absoluteUrl = baseUrl ? new URL(url, baseUrl) : new URL(url);
 
     if (_isValidProtocol(absoluteUrl)) {
@@ -495,13 +529,13 @@ function shadow(obj, prop, value) {
 }
 
 const BaseException = function BaseExceptionClosure() {
-  function BaseException(message) {
+  function BaseException(message, name) {
     if (this.constructor === BaseException) {
       unreachable("Cannot initialize BaseException.");
     }
 
     this.message = message;
-    this.name = this.constructor.name;
+    this.name = name;
   }
 
   BaseException.prototype = new Error();
@@ -513,7 +547,7 @@ exports.BaseException = BaseException;
 
 class PasswordException extends BaseException {
   constructor(msg, code) {
-    super(msg);
+    super(msg, "PasswordException");
     this.code = code;
   }
 
@@ -523,7 +557,7 @@ exports.PasswordException = PasswordException;
 
 class UnknownErrorException extends BaseException {
   constructor(msg, details) {
-    super(msg);
+    super(msg, "UnknownErrorException");
     this.details = details;
   }
 
@@ -531,17 +565,27 @@ class UnknownErrorException extends BaseException {
 
 exports.UnknownErrorException = UnknownErrorException;
 
-class InvalidPDFException extends BaseException {}
+class InvalidPDFException extends BaseException {
+  constructor(msg) {
+    super(msg, "InvalidPDFException");
+  }
+
+}
 
 exports.InvalidPDFException = InvalidPDFException;
 
-class MissingPDFException extends BaseException {}
+class MissingPDFException extends BaseException {
+  constructor(msg) {
+    super(msg, "MissingPDFException");
+  }
+
+}
 
 exports.MissingPDFException = MissingPDFException;
 
 class UnexpectedResponseException extends BaseException {
   constructor(msg, status) {
-    super(msg);
+    super(msg, "UnexpectedResponseException");
     this.status = status;
   }
 
@@ -549,11 +593,21 @@ class UnexpectedResponseException extends BaseException {
 
 exports.UnexpectedResponseException = UnexpectedResponseException;
 
-class FormatError extends BaseException {}
+class FormatError extends BaseException {
+  constructor(msg) {
+    super(msg, "FormatError");
+  }
+
+}
 
 exports.FormatError = FormatError;
 
-class AbortException extends BaseException {}
+class AbortException extends BaseException {
+  constructor(msg) {
+    super(msg, "AbortException");
+  }
+
+}
 
 exports.AbortException = AbortException;
 const NullCharactersRegExp = /\x00/g;
@@ -978,7 +1032,7 @@ var _ccitt = __w_pdfjs_require__(9);
 
 class Jbig2Error extends _util.BaseException {
   constructor(msg) {
-    super(`JBIG2 error: ${msg}`);
+    super(`JBIG2 error: ${msg}`, "Jbig2Error");
   }
 
 }
@@ -3177,6 +3231,7 @@ exports.parseXFAPath = parseXFAPath;
 exports.readInt8 = readInt8;
 exports.readUint16 = readUint16;
 exports.readUint32 = readUint32;
+exports.recoverJsURL = recoverJsURL;
 exports.toRomanNumerals = toRomanNumerals;
 exports.validateCSSFont = validateCSSFont;
 exports.XRefParseException = exports.XRefEntryException = exports.ParserEOFException = exports.MissingDataException = void 0;
@@ -3219,7 +3274,7 @@ function getArrayLookupTableFactory(initializer) {
 
 class MissingDataException extends _util.BaseException {
   constructor(begin, end) {
-    super(`Missing data [${begin}, ${end})`);
+    super(`Missing data [${begin}, ${end})`, "MissingDataException");
     this.begin = begin;
     this.end = end;
   }
@@ -3228,15 +3283,30 @@ class MissingDataException extends _util.BaseException {
 
 exports.MissingDataException = MissingDataException;
 
-class ParserEOFException extends _util.BaseException {}
+class ParserEOFException extends _util.BaseException {
+  constructor(msg) {
+    super(msg, "ParserEOFException");
+  }
+
+}
 
 exports.ParserEOFException = ParserEOFException;
 
-class XRefEntryException extends _util.BaseException {}
+class XRefEntryException extends _util.BaseException {
+  constructor(msg) {
+    super(msg, "XRefEntryException");
+  }
+
+}
 
 exports.XRefEntryException = XRefEntryException;
 
-class XRefParseException extends _util.BaseException {}
+class XRefParseException extends _util.BaseException {
+  constructor(msg) {
+    super(msg, "XRefParseException");
+  }
+
+}
 
 exports.XRefParseException = XRefParseException;
 
@@ -3322,7 +3392,7 @@ function isWhiteSpace(ch) {
 }
 
 function parseXFAPath(path) {
-  const positionPattern = /(.+)\[([0-9]+)\]$/;
+  const positionPattern = /(.+)\[(\d+)\]$/;
   return path.split(".").map(component => {
     const m = component.match(positionPattern);
 
@@ -3540,7 +3610,7 @@ function validateCSSFont(cssFontInfo) {
     }
   } else {
     for (const ident of fontFamily.split(/[ \t]+/)) {
-      if (/^([0-9]|(-([0-9]|-)))/.test(ident) || !/^[a-zA-Z0-9\-_\\]+$/.test(ident)) {
+      if (/^(\d|(-(\d|-)))/.test(ident) || !/^[\w-\\]+$/.test(ident)) {
         (0, _util.warn)(`XFA - FontFamily contains some invalid <custom-ident>: ${fontFamily}.`);
         return false;
       }
@@ -3554,6 +3624,28 @@ function validateCSSFont(cssFontInfo) {
   return true;
 }
 
+function recoverJsURL(str) {
+  const URL_OPEN_METHODS = ["app.launchURL", "window.open", "xfa.host.gotoURL"];
+  const regex = new RegExp("^\\s*(" + URL_OPEN_METHODS.join("|").split(".").join("\\.") + ")\\((?:'|\")([^'\"]*)(?:'|\")(?:,\\s*(\\w+)\\)|\\))", "i");
+  const jsUrl = regex.exec(str);
+
+  if (jsUrl && jsUrl[2]) {
+    const url = jsUrl[2];
+    let newWindow = false;
+
+    if (jsUrl[3] === "true" && jsUrl[1] === "app.launchURL") {
+      newWindow = true;
+    }
+
+    return {
+      url,
+      newWindow
+    };
+  }
+
+  return null;
+}
+
 /***/ }),
 /* 6 */
 /***/ ((__unused_webpack_module, exports, __w_pdfjs_require__) => {
@@ -3566,7 +3658,6 @@ Object.defineProperty(exports, "__esModule", ({
 exports.clearPrimitiveCaches = clearPrimitiveCaches;
 exports.isCmd = isCmd;
 exports.isDict = isDict;
-exports.isEOF = isEOF;
 exports.isName = isName;
 exports.isRef = isRef;
 exports.isRefsEqual = isRefsEqual;
@@ -3577,7 +3668,7 @@ var _util = __w_pdfjs_require__(1);
 
 var _base_stream = __w_pdfjs_require__(7);
 
-const EOF = {};
+const EOF = Symbol("EOF");
 exports.EOF = EOF;
 
 const Name = function NameClosure() {
@@ -3768,7 +3859,7 @@ class Dict {
         if (property === undefined) {
           property = [];
           properties.set(key, property);
-        } else if (!mergeSubDicts) {
+        } else if (!mergeSubDicts || !(value instanceof Dict)) {
           continue;
         }
 
@@ -3785,10 +3876,6 @@ class Dict {
       const subDict = new Dict(xref);
 
       for (const dict of values) {
-        if (!(dict instanceof Dict)) {
-          continue;
-        }
-
         for (const [key, value] of Object.entries(dict._map)) {
           if (subDict._map[key] === undefined) {
             subDict._map[key] = value;
@@ -3913,10 +4000,6 @@ class RefSetCache {
 
 exports.RefSetCache = RefSetCache;
 
-function isEOF(v) {
-  return v === EOF;
-}
-
 function isName(v, name) {
   return v instanceof Name && (name === undefined || v.name === name);
 }
@@ -5127,20 +5210,25 @@ var _core_utils = __w_pdfjs_require__(5);
 
 class JpegError extends _util.BaseException {
   constructor(msg) {
-    super(`JPEG error: ${msg}`);
+    super(`JPEG error: ${msg}`, "JpegError");
   }
 
 }
 
 class DNLMarkerError extends _util.BaseException {
   constructor(message, scanLines) {
-    super(message);
+    super(message, "DNLMarkerError");
     this.scanLines = scanLines;
   }
 
 }
 
-class EOIMarkerError extends _util.BaseException {}
+class EOIMarkerError extends _util.BaseException {
+  constructor(msg) {
+    super(msg, "EOIMarkerError");
+  }
+
+}
 
 const dctZigZag = new Uint8Array([0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63]);
 const dctCos1 = 4017;
@@ -6371,7 +6459,7 @@ var _arithmetic_decoder = __w_pdfjs_require__(8);
 
 class JpxError extends _util.BaseException {
   constructor(msg) {
-    super(`JPX error: ${msg}`);
+    super(`JPX error: ${msg}`, "JpxError");
   }
 
 }
@@ -7589,6 +7677,12 @@ function parseTilePackets(context, data, offset, dataLength) {
           zeroBitPlanesTree = new TagTree(width, height);
           precinct.inclusionTree = inclusionTree;
           precinct.zeroBitPlanesTree = zeroBitPlanesTree;
+
+          for (let l = 0; l < layerNumber; l++) {
+            if (readBits(1) !== 0) {
+              throw new JpxError("Invalid tag tree");
+            }
+          }
         }
 
         if (inclusionTree.reset(codeblockColumn, codeblockRow, layerNumber)) {
@@ -8763,8 +8857,8 @@ var _jpg = __w_pdfjs_require__(10);
 
 var _jpx = __w_pdfjs_require__(11);
 
-const pdfjsVersion = '2.10.377';
-const pdfjsBuild = '156762c48';
+const pdfjsVersion = '2.11.338';
+const pdfjsBuild = 'dedff3c98';
 })();
 
 /******/ 	return __webpack_exports__;

Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
image_decoders/pdf.image_decoders.js.map


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
image_decoders/pdf.image_decoders.min.js


Файловите разлики са ограничени, защото са твърде много
+ 361 - 211
legacy/build/pdf.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
legacy/build/pdf.js.map


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
legacy/build/pdf.min.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
legacy/build/pdf.sandbox.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
legacy/build/pdf.sandbox.js.map


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
legacy/build/pdf.sandbox.min.js


Файловите разлики са ограничени, защото са твърде много
+ 361 - 212
legacy/build/pdf.worker.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
legacy/build/pdf.worker.js.map


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
legacy/build/pdf.worker.min.js


Файловите разлики са ограничени, защото са твърде много
+ 352 - 270
legacy/image_decoders/pdf.image_decoders.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
legacy/image_decoders/pdf.image_decoders.js.map


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
legacy/image_decoders/pdf.image_decoders.min.js


+ 107 - 27
legacy/web/pdf_viewer.css

@@ -69,6 +69,14 @@
   background: rgba(0, 0, 255, 1);
 }
 
+/* Avoids https://github.com/mozilla/pdf.js/issues/13840 in Chrome */
+.textLayer br::-moz-selection {
+  background: transparent;
+}
+.textLayer br::selection {
+  background: transparent;
+}
+
 .textLayer .endOfContent {
   display: block;
   position: absolute;
@@ -88,6 +96,10 @@
 }
 
 
+:root {
+  --annotation-unfocused-field-background: url("data:image/svg+xml;charset=UTF-8,<svg width='1px' height='1px' xmlns='http://www.w3.org/2000/svg'><rect width='100%' height='100%' style='fill:rgba(0, 54, 255, 0.13);'/></svg>");
+}
+
 .annotationLayer section {
   position: absolute;
   text-align: initial;
@@ -120,7 +132,7 @@
 .annotationLayer .choiceWidgetAnnotation select,
 .annotationLayer .buttonWidgetAnnotation.checkBox input,
 .annotationLayer .buttonWidgetAnnotation.radioButton input {
-  background-color: rgba(0, 54, 255, 0.13);
+  background-image: var(--annotation-unfocused-field-background);
   border: 1px solid transparent;
   box-sizing: border-box;
   font-size: 9px;
@@ -170,6 +182,16 @@
   border: 1px solid transparent;
 }
 
+.annotationLayer .textWidgetAnnotation input :focus,
+.annotationLayer .textWidgetAnnotation textarea :focus,
+.annotationLayer .choiceWidgetAnnotation select :focus,
+.annotationLayer .buttonWidgetAnnotation.checkBox :focus,
+.annotationLayer .buttonWidgetAnnotation.radioButton :focus {
+  background-image: none;
+  background-color: transparent;
+  outline: auto;
+}
+
 .annotationLayer .buttonWidgetAnnotation.checkBox input:checked:before,
 .annotationLayer .buttonWidgetAnnotation.checkBox input:checked:after,
 .annotationLayer .buttonWidgetAnnotation.radioButton input:checked:before {
@@ -208,16 +230,6 @@
   padding-right: 0;
 }
 
-.annotationLayer .textWidgetAnnotation input.comb:focus {
-  /*
-   * Letter spacing is placed on the right side of each character. Hence, the
-   * letter spacing of the last character may be placed outside the visible
-   * area, causing horizontal scrolling. We avoid this by extending the width
-   * when the element has focus and revert this when it loses focus.
-   */
-  width: 103%;
-}
-
 .annotationLayer .buttonWidgetAnnotation.checkBox input,
 .annotationLayer .buttonWidgetAnnotation.radioButton input {
   -webkit-appearance: none;
@@ -284,6 +296,45 @@
 }
 
 
+:root {
+  --xfa-unfocused-field-background: url("data:image/svg+xml;charset=UTF-8,<svg width='1px' height='1px' xmlns='http://www.w3.org/2000/svg'><rect width='100%' height='100%' style='fill:rgba(0, 54, 255, 0.13);'/></svg>");
+}
+
+.xfaLayer .highlight {
+  margin: -1px;
+  padding: 1px;
+  background-color: rgba(239, 203, 237, 1);
+  border-radius: 4px;
+}
+
+.xfaLayer .highlight.appended {
+  position: initial;
+}
+
+.xfaLayer .highlight.begin {
+  border-radius: 4px 0 0 4px;
+}
+
+.xfaLayer .highlight.end {
+  border-radius: 0 4px 4px 0;
+}
+
+.xfaLayer .highlight.middle {
+  border-radius: 0;
+}
+
+.xfaLayer .highlight.selected {
+  background-color: rgba(203, 223, 203, 1);
+}
+
+.xfaLayer ::-moz-selection {
+  background: rgba(0, 0, 255, 1);
+}
+
+.xfaLayer ::selection {
+  background: rgba(0, 0, 255, 1);
+}
+
 .xfaPage {
   overflow: hidden;
   position: relative;
@@ -316,10 +367,11 @@
   text-align: inherit;
   text-decoration: inherit;
   box-sizing: border-box;
-  background: transparent;
+  background-color: transparent;
   padding: 0;
   margin: 0;
   pointer-events: auto;
+  line-height: inherit;
 }
 
 .xfaLayer div {
@@ -355,7 +407,7 @@
 
 .xfaCaption {
   overflow: hidden;
-  flex: 0 1 auto;
+  flex: 0 0 auto;
 }
 
 .xfaCaptionForCheckButton {
@@ -374,8 +426,16 @@
   align-items: center;
 }
 
+.xfaRight {
+  display: flex;
+  flex-direction: row-reverse;
+  align-items: center;
+}
+
 .xfaLeft > .xfaCaption,
-.xfaLeft > .xfaCaptionForCheckButton {
+.xfaLeft > .xfaCaptionForCheckButton,
+.xfaRight > .xfaCaption,
+.xfaRight > .xfaCaptionForCheckButton {
   max-height: 100%;
 }
 
@@ -385,13 +445,21 @@
   align-items: flex-start;
 }
 
+.xfaBottom {
+  display: flex;
+  flex-direction: column-reverse;
+  align-items: flex-start;
+}
+
 .xfaTop > .xfaCaption,
-.xfaTop > .xfaCaptionForCheckButton {
+.xfaTop > .xfaCaptionForCheckButton,
+.xfaBottom > .xfaCaption,
+.xfaBottom > .xfaCaptionForCheckButton {
   width: 100%;
 }
 
 .xfaBorder {
-  background: transparent;
+  background-color: transparent;
   position: absolute;
   pointer-events: none;
 }
@@ -401,24 +469,34 @@
   height: 100%;
 }
 
-.xfaTextfield,
-.xfaSelect {
-  background-color: rgba(0, 54, 255, 0.13);
-}
-
 .xfaTextfield:focus,
 .xfaSelect:focus {
+  background-image: none;
   background-color: transparent;
-  outline: none;
+  outline: auto;
+  outline-offset: -1px;
+}
+
+.xfaCheckbox:focus,
+.xfaRadio:focus {
+  outline: auto;
 }
 
 .xfaTextfield,
 .xfaSelect {
-  width: 100%;
   height: 100%;
-  flex: 1 1 0;
+  width: 100%;
+  flex: 1 1 auto;
   border: none;
   resize: none;
+  background-image: var(--xfa-unfocused-field-background);
+}
+
+.xfaTop > .xfaTextfield,
+.xfaTop > .xfaSelect,
+.xfaBottom > .xfaTextfield,
+.xfaBottom > .xfaSelect {
+  flex: 0 1 auto;
 }
 
 .xfaButton {
@@ -429,8 +507,9 @@
   text-align: center;
 }
 
-.xfaButton:hover {
-  background: Highlight;
+.xfaLink {
+  width: 100%;
+  height: 100%;
 }
 
 .xfaCheckbox,
@@ -528,7 +607,7 @@
 @media print {
   .xfaTextfield,
   .xfaSelect {
-    background-color: transparent;
+    background: transparent;
   }
 
   .xfaSelect {
@@ -545,6 +624,7 @@
   --page-margin: 1px auto -8px;
   --page-border: 9px solid transparent;
   --spreadHorizontalWrapped-margin-LR: -3.5px;
+  --zoom-factor: 1;
 }
 
 @media screen and (forced-colors: active) {

+ 1 - 1
legacy/web/pdf_viewer.d.ts

@@ -1 +1 @@
-export * from "pdfjs-dist/types/web/pdf_viewer.component.d.ts";
+export * from "../../types/web/pdf_viewer.component";

Файловите разлики са ограничени, защото са твърде много
+ 587 - 324
legacy/web/pdf_viewer.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
legacy/web/pdf_viewer.js.map


+ 160 - 45
lib/core/annotation.js

@@ -35,6 +35,8 @@ var _default_appearance = require("./default_appearance.js");
 
 var _primitives = require("./primitives.js");
 
+var _bidi = require("./bidi.js");
+
 var _catalog = require("./catalog.js");
 
 var _colorspace = require("./colorspace.js");
@@ -191,13 +193,13 @@ class AnnotationFactory {
 
 exports.AnnotationFactory = AnnotationFactory;
 
-function getRgbColor(color) {
-  const rgbColor = new Uint8ClampedArray(3);
-
+function getRgbColor(color, defaultColor = new Uint8ClampedArray(3)) {
   if (!Array.isArray(color)) {
-    return rgbColor;
+    return defaultColor;
   }
 
+  const rgbColor = defaultColor || new Uint8ClampedArray(3);
+
   switch (color.length) {
     case 0:
       return null;
@@ -218,7 +220,7 @@ function getRgbColor(color) {
       return rgbColor;
 
     default:
-      return rgbColor;
+      return defaultColor;
   }
 }
 
@@ -286,6 +288,7 @@ function getTransformMatrix(rect, bbox, matrix) {
 class Annotation {
   constructor(params) {
     const dict = params.dict;
+    this.setTitle(dict.get("T"));
     this.setContents(dict.get("Contents"));
     this.setModificationDate(dict.get("M"));
     this.setFlags(dict.get("F"));
@@ -293,6 +296,7 @@ class Annotation {
     this.setColor(dict.getArray("C"));
     this.setBorderStyle(dict);
     this.setAppearance(dict);
+    this.setBorderAndBackgroundColors(dict.get("MK"));
     this._streams = [];
 
     if (this.appearance) {
@@ -303,7 +307,9 @@ class Annotation {
       annotationFlags: this.flags,
       borderStyle: this.borderStyle,
       color: this.color,
-      contents: this.contents,
+      backgroundColor: this.backgroundColor,
+      borderColor: this.borderColor,
+      contentsObj: this._contents,
       hasAppearance: !!this.appearance,
       id: params.id,
       modificationDate: this.modificationDate,
@@ -392,8 +398,21 @@ class Annotation {
     return this._isPrintable(this.flags);
   }
 
+  _parseStringHelper(data) {
+    const str = typeof data === "string" ? (0, _util.stringToPDFString)(data) : "";
+    const dir = str && (0, _bidi.bidi)(str).dir === "rtl" ? "rtl" : "ltr";
+    return {
+      str,
+      dir
+    };
+  }
+
+  setTitle(title) {
+    this._title = this._parseStringHelper(title);
+  }
+
   setContents(contents) {
-    this.contents = (0, _util.stringToPDFString)(contents || "");
+    this._contents = this._parseStringHelper(contents);
   }
 
   setModificationDate(modificationDate) {
@@ -420,6 +439,15 @@ class Annotation {
     this.color = getRgbColor(color);
   }
 
+  setBorderAndBackgroundColors(mk) {
+    if (mk instanceof _primitives.Dict) {
+      this.borderColor = getRgbColor(mk.getArray("BC"), null);
+      this.backgroundColor = getRgbColor(mk.getArray("BG"), null);
+    } else {
+      this.borderColor = this.backgroundColor = null;
+    }
+  }
+
   setBorderStyle(borderStyle) {
     this.borderStyle = new AnnotationBorderStyle();
 
@@ -533,6 +561,8 @@ class Annotation {
         id: this.data.id,
         actions: this.data.actions,
         name: this.data.fieldName,
+        strokeColor: this.data.borderColor,
+        fillColor: this.data.backgroundColor,
         type: "",
         kidIds: this.data.kidIds,
         page: this.data.pageIndex
@@ -711,9 +741,10 @@ class MarkupAnnotation extends Annotation {
 
     if (this.data.replyType === _util.AnnotationReplyType.GROUP) {
       const parent = dict.get("IRT");
-      this.data.title = (0, _util.stringToPDFString)(parent.get("T") || "");
+      this.setTitle(parent.get("T"));
+      this.data.titleObj = this._title;
       this.setContents(parent.get("Contents"));
-      this.data.contents = this.contents;
+      this.data.contentsObj = this._contents;
 
       if (!parent.has("CreationDate")) {
         this.data.creationDate = null;
@@ -738,7 +769,7 @@ class MarkupAnnotation extends Annotation {
         this.data.color = this.color;
       }
     } else {
-      this.data.title = (0, _util.stringToPDFString)(dict.get("T") || "");
+      this.data.titleObj = this._title;
       this.setCreationDate(dict.get("CreationDate"));
       this.data.creationDate = this.creationDate;
       this.data.hasPopup = dict.has("Popup");
@@ -875,6 +906,11 @@ class WidgetAnnotation extends Annotation {
       getArray: true
     });
     data.defaultFieldValue = this._decodeFormValue(defaultFieldValue);
+
+    if (fieldValue === undefined && data.defaultFieldValue !== null) {
+      data.fieldValue = data.defaultFieldValue;
+    }
+
     data.alternativeText = (0, _util.stringToPDFString)(dict.get("TU") || "");
     const defaultAppearance = (0, _core_utils.getInheritableProperty)({
       dict,
@@ -1370,6 +1406,8 @@ class TextWidgetAnnotation extends WidgetAnnotation {
       rect: this.data.rect,
       actions: this.data.actions,
       page: this.data.pageIndex,
+      strokeColor: this.data.borderColor,
+      fillColor: this.data.backgroundColor,
       type: "text"
     };
   }
@@ -1397,39 +1435,41 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
     }
   }
 
-  getOperatorList(evaluator, task, renderForms, annotationStorage) {
+  async getOperatorList(evaluator, task, renderForms, annotationStorage) {
     if (this.data.pushButton) {
       return super.getOperatorList(evaluator, task, false, annotationStorage);
     }
 
+    let value = null;
+
     if (annotationStorage) {
       const storageEntry = annotationStorage.get(this.data.id);
-      const value = storageEntry && storageEntry.value;
+      value = storageEntry ? storageEntry.value : null;
+    }
 
-      if (value === undefined) {
+    if (value === null) {
+      if (this.appearance) {
         return super.getOperatorList(evaluator, task, renderForms, annotationStorage);
       }
 
-      let appearance;
-
-      if (value) {
-        appearance = this.checkedAppearance;
+      if (this.data.checkBox) {
+        value = this.data.fieldValue === this.data.exportValue;
       } else {
-        appearance = this.uncheckedAppearance;
+        value = this.data.fieldValue === this.data.buttonValue;
       }
+    }
 
-      if (appearance) {
-        const savedAppearance = this.appearance;
-        this.appearance = appearance;
-        const operatorList = super.getOperatorList(evaluator, task, renderForms, annotationStorage);
-        this.appearance = savedAppearance;
-        return operatorList;
-      }
+    const appearance = value ? this.checkedAppearance : this.uncheckedAppearance;
 
-      return Promise.resolve(new _operator_list.OperatorList());
+    if (appearance) {
+      const savedAppearance = this.appearance;
+      this.appearance = appearance;
+      const operatorList = super.getOperatorList(evaluator, task, renderForms, annotationStorage);
+      this.appearance = savedAppearance;
+      return operatorList;
     }
 
-    return super.getOperatorList(evaluator, task, renderForms, annotationStorage);
+    return new _operator_list.OperatorList();
   }
 
   async save(evaluator, task, annotationStorage) {
@@ -1456,7 +1496,7 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
       return null;
     }
 
-    const defaultValue = this.data.fieldValue && this.data.fieldValue !== "Off";
+    const defaultValue = this.data.fieldValue === this.data.exportValue;
 
     if (defaultValue === value) {
       return null;
@@ -1575,6 +1615,51 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
     return newRefs;
   }
 
+  _getDefaultCheckedAppearance(params, type) {
+    const width = this.data.rect[2] - this.data.rect[0];
+    const height = this.data.rect[3] - this.data.rect[1];
+    const bbox = [0, 0, width, height];
+    const FONT_RATIO = 0.8;
+    const fontSize = Math.min(width, height) * FONT_RATIO;
+    let metrics, char;
+
+    if (type === "check") {
+      metrics = {
+        width: 0.755 * fontSize,
+        height: 0.705 * fontSize
+      };
+      char = "\x33";
+    } else if (type === "disc") {
+      metrics = {
+        width: 0.791 * fontSize,
+        height: 0.705 * fontSize
+      };
+      char = "\x6C";
+    } else {
+      (0, _util.unreachable)(`_getDefaultCheckedAppearance - unsupported type: ${type}`);
+    }
+
+    const xShift = (width - metrics.width) / 2;
+    const yShift = (height - metrics.height) / 2;
+    const appearance = `q BT /PdfJsZaDb ${fontSize} Tf 0 g ${xShift} ${yShift} Td (${char}) Tj ET Q`;
+    const appearanceStreamDict = new _primitives.Dict(params.xref);
+    appearanceStreamDict.set("FormType", 1);
+    appearanceStreamDict.set("Subtype", _primitives.Name.get("Form"));
+    appearanceStreamDict.set("Type", _primitives.Name.get("XObject"));
+    appearanceStreamDict.set("BBox", bbox);
+    appearanceStreamDict.set("Matrix", [1, 0, 0, 1, 0, 0]);
+    appearanceStreamDict.set("Length", appearance.length);
+    const resources = new _primitives.Dict(params.xref);
+    const font = new _primitives.Dict(params.xref);
+    font.set("PdfJsZaDb", this.fallbackFontDict);
+    resources.set("Font", font);
+    appearanceStreamDict.set("Resources", resources);
+    this.checkedAppearance = new _stream.StringStream(appearance);
+    this.checkedAppearance.dict = appearanceStreamDict;
+
+    this._streams.push(this.checkedAppearance);
+  }
+
   _processCheckBox(params) {
     const customAppearance = params.dict.get("AP");
 
@@ -1588,25 +1673,45 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
       return;
     }
 
-    const exportValues = normalAppearance.getKeys();
+    const asValue = this._decodeFormValue(params.dict.get("AS"));
 
-    if (!exportValues.includes("Off")) {
-      exportValues.push("Off");
+    if (typeof asValue === "string") {
+      this.data.fieldValue = asValue;
     }
 
-    if (!exportValues.includes(this.data.fieldValue)) {
-      this.data.fieldValue = null;
+    const yes = this.data.fieldValue !== null && this.data.fieldValue !== "Off" ? this.data.fieldValue : "Yes";
+    const exportValues = normalAppearance.getKeys();
+
+    if (exportValues.length === 0) {
+      exportValues.push("Off", yes);
+    } else if (exportValues.length === 1) {
+      if (exportValues[0] === "Off") {
+        exportValues.push(yes);
+      } else {
+        exportValues.unshift("Off");
+      }
+    } else if (exportValues.includes(yes)) {
+      exportValues.length = 0;
+      exportValues.push("Off", yes);
+    } else {
+      const otherYes = exportValues.find(v => v !== "Off");
+      exportValues.length = 0;
+      exportValues.push("Off", otherYes);
     }
 
-    if (exportValues.length !== 2) {
-      return;
+    if (!exportValues.includes(this.data.fieldValue)) {
+      this.data.fieldValue = "Off";
     }
 
-    this.data.exportValue = exportValues[0] === "Off" ? exportValues[1] : exportValues[0];
-    this.checkedAppearance = normalAppearance.get(this.data.exportValue);
+    this.data.exportValue = exportValues[1];
+    this.checkedAppearance = normalAppearance.get(this.data.exportValue) || null;
     this.uncheckedAppearance = normalAppearance.get("Off") || null;
 
-    this._streams.push(this.checkedAppearance);
+    if (this.checkedAppearance) {
+      this._streams.push(this.checkedAppearance);
+    } else {
+      this._getDefaultCheckedAppearance(params, "check");
+    }
 
     if (this.uncheckedAppearance) {
       this._streams.push(this.uncheckedAppearance);
@@ -1647,10 +1752,14 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
       }
     }
 
-    this.checkedAppearance = normalAppearance.get(this.data.buttonValue);
+    this.checkedAppearance = normalAppearance.get(this.data.buttonValue) || null;
     this.uncheckedAppearance = normalAppearance.get("Off") || null;
 
-    this._streams.push(this.checkedAppearance);
+    if (this.checkedAppearance) {
+      this._streams.push(this.checkedAppearance);
+    } else {
+      this._getDefaultCheckedAppearance(params, "disc");
+    }
 
     if (this.uncheckedAppearance) {
       this._streams.push(this.uncheckedAppearance);
@@ -1697,6 +1806,8 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
       hidden: this.data.hidden,
       actions: this.data.actions,
       page: this.data.pageIndex,
+      strokeColor: this.data.borderColor,
+      fillColor: this.data.backgroundColor,
       type
     };
   }
@@ -1761,6 +1872,8 @@ class ChoiceWidgetAnnotation extends WidgetAnnotation {
       actions: this.data.actions,
       items: this.data.options,
       page: this.data.pageIndex,
+      strokeColor: this.data.borderColor,
+      fillColor: this.data.backgroundColor,
       type
     };
   }
@@ -1880,8 +1993,10 @@ class PopupAnnotation extends Annotation {
       }
     }
 
-    this.data.title = (0, _util.stringToPDFString)(parentItem.get("T") || "");
-    this.data.contents = (0, _util.stringToPDFString)(parentItem.get("Contents") || "");
+    this.setTitle(parentItem.get("T"));
+    this.data.titleObj = this._title;
+    this.setContents(parentItem.get("Contents"));
+    this.data.contentsObj = this._contents;
   }
 
 }
@@ -1908,7 +2023,7 @@ class LineAnnotation extends MarkupAnnotation {
           interiorColor = parameters.dict.getArray("IC");
 
       if (interiorColor) {
-        interiorColor = getRgbColor(interiorColor);
+        interiorColor = getRgbColor(interiorColor, null);
         fillColor = interiorColor ? Array.from(interiorColor).map(c => c / 255) : null;
       }
 
@@ -1950,7 +2065,7 @@ class SquareAnnotation extends MarkupAnnotation {
           interiorColor = parameters.dict.getArray("IC");
 
       if (interiorColor) {
-        interiorColor = getRgbColor(interiorColor);
+        interiorColor = getRgbColor(interiorColor, null);
         fillColor = interiorColor ? Array.from(interiorColor).map(c => c / 255) : null;
       }
 
@@ -1996,7 +2111,7 @@ class CircleAnnotation extends MarkupAnnotation {
       let interiorColor = parameters.dict.getArray("IC");
 
       if (interiorColor) {
-        interiorColor = getRgbColor(interiorColor);
+        interiorColor = getRgbColor(interiorColor, null);
         fillColor = interiorColor ? Array.from(interiorColor).map(c => c / 255) : null;
       }
 

+ 1 - 1
lib/core/bidi.js

@@ -83,7 +83,7 @@ function createBidiText(str, isLTR, vertical = false) {
 const chars = [];
 const types = [];
 
-function bidi(str, startLevel, vertical) {
+function bidi(str, startLevel = -1, vertical = false) {
   let isLTR = true;
   const strLength = str.length;
 

Файловите разлики са ограничени, защото са твърде много
+ 0 - 1
lib/core/calibri_factors.js


+ 48 - 31
lib/core/catalog.js

@@ -81,6 +81,16 @@ class Catalog {
     return (0, _util.shadow)(this, "version", version.name);
   }
 
+  get needsRendering() {
+    const needsRendering = this._catDict.get("NeedsRendering");
+
+    if (!(0, _util.isBool)(needsRendering)) {
+      return (0, _util.shadow)(this, "needsRendering", false);
+    }
+
+    return (0, _util.shadow)(this, "needsRendering", needsRendering);
+  }
+
   get collection() {
     let collection = null;
 
@@ -121,6 +131,12 @@ class Catalog {
     return (0, _util.shadow)(this, "acroForm", acroForm);
   }
 
+  get acroFormRef() {
+    const value = this._catDict.getRaw("AcroForm");
+
+    return (0, _util.shadow)(this, "acroFormRef", (0, _primitives.isRef)(value) ? value : null);
+  }
+
   get metadata() {
     const streamRef = this._catDict.getRaw("Metadata");
 
@@ -1329,18 +1345,6 @@ class Catalog {
   }
 
   static parseDestDictionary(params) {
-    function addDefaultProtocolToUrl(url) {
-      return url.startsWith("www.") ? `http://${url}` : url;
-    }
-
-    function tryConvertUrlEncoding(url) {
-      try {
-        return (0, _util.stringToUTF8String)(url);
-      } catch (e) {
-        return url;
-      }
-    }
-
     const destDict = params.destDict;
 
     if (!(0, _primitives.isDict)(destDict)) {
@@ -1387,13 +1391,32 @@ class Catalog {
       const actionName = actionType.name;
 
       switch (actionName) {
+        case "ResetForm":
+          const flags = action.get("Flags");
+          const include = (((0, _util.isNum)(flags) ? flags : 0) & 1) === 0;
+          const fields = [];
+          const refs = [];
+
+          for (const obj of action.get("Fields") || []) {
+            if ((0, _primitives.isRef)(obj)) {
+              refs.push(obj.toString());
+            } else if ((0, _util.isString)(obj)) {
+              fields.push((0, _util.stringToPDFString)(obj));
+            }
+          }
+
+          resultObj.resetForm = {
+            fields,
+            refs,
+            include
+          };
+          break;
+
         case "URI":
           url = action.get("URI");
 
-          if ((0, _primitives.isName)(url)) {
+          if (url instanceof _primitives.Name) {
             url = "/" + url.name;
-          } else if ((0, _util.isString)(url)) {
-            url = addDefaultProtocolToUrl(url);
           }
 
           break;
@@ -1457,24 +1480,16 @@ class Catalog {
             js = jsAction;
           }
 
-          if (js) {
-            const URL_OPEN_METHODS = ["app.launchURL", "window.open"];
-            const regex = new RegExp("^\\s*(" + URL_OPEN_METHODS.join("|").split(".").join("\\.") + ")\\((?:'|\")([^'\"]*)(?:'|\")(?:,\\s*(\\w+)\\)|\\))", "i");
-            const jsUrl = regex.exec((0, _util.stringToPDFString)(js));
-
-            if (jsUrl && jsUrl[2]) {
-              url = jsUrl[2];
+          const jsURL = js && (0, _core_utils.recoverJsURL)((0, _util.stringToPDFString)(js));
 
-              if (jsUrl[3] === "true" && jsUrl[1] === "app.launchURL") {
-                resultObj.newWindow = true;
-              }
-
-              break;
-            }
+          if (jsURL) {
+            url = jsURL.url;
+            resultObj.newWindow = jsURL.newWindow;
+            break;
           }
 
         default:
-          if (actionName === "JavaScript" || actionName === "ResetForm" || actionName === "SubmitForm") {
+          if (actionName === "JavaScript" || actionName === "SubmitForm") {
             break;
           }
 
@@ -1486,8 +1501,10 @@ class Catalog {
     }
 
     if ((0, _util.isString)(url)) {
-      url = tryConvertUrlEncoding(url);
-      const absoluteUrl = (0, _util.createValidAbsoluteUrl)(url, docBaseUrl);
+      const absoluteUrl = (0, _util.createValidAbsoluteUrl)(url, docBaseUrl, {
+        addDefaultProtocol: true,
+        tryConvertEncoding: true
+      });
 
       if (absoluteUrl) {
         resultObj.url = absoluteUrl.href;

+ 3 - 4
lib/core/chunked_stream.js

@@ -299,7 +299,7 @@ class ChunkedStreamManager {
 
     let chunks = [],
         loaded = 0;
-    const promise = new Promise((resolve, reject) => {
+    return new Promise((resolve, reject) => {
       const readChunk = chunk => {
         try {
           if (!chunk.done) {
@@ -326,8 +326,7 @@ class ChunkedStreamManager {
       };
 
       rangeReader.read().then(readChunk, reject);
-    });
-    promise.then(data => {
+    }).then(data => {
       if (this.aborted) {
         return;
       }
@@ -389,7 +388,7 @@ class ChunkedStreamManager {
       for (const groupedChunk of groupedChunksToRequest) {
         const begin = groupedChunk.beginChunk * this.chunkSize;
         const end = Math.min(groupedChunk.endChunk * this.chunkSize, this.length);
-        this.sendRequest(begin, end);
+        this.sendRequest(begin, end).catch(capability.reject);
       }
     }
 

+ 7 - 7
lib/core/cmap.js

@@ -630,7 +630,7 @@ const CMapFactory = function CMapFactoryClosure() {
     while (true) {
       let obj = lexer.getObj();
 
-      if ((0, _primitives.isEOF)(obj)) {
+      if (obj === _primitives.EOF) {
         break;
       }
 
@@ -651,7 +651,7 @@ const CMapFactory = function CMapFactoryClosure() {
     while (true) {
       let obj = lexer.getObj();
 
-      if ((0, _primitives.isEOF)(obj)) {
+      if (obj === _primitives.EOF) {
         break;
       }
 
@@ -673,7 +673,7 @@ const CMapFactory = function CMapFactoryClosure() {
         obj = lexer.getObj();
         const array = [];
 
-        while (!(0, _primitives.isCmd)(obj, "]") && !(0, _primitives.isEOF)(obj)) {
+        while (!(0, _primitives.isCmd)(obj, "]") && obj !== _primitives.EOF) {
           array.push(obj);
           obj = lexer.getObj();
         }
@@ -691,7 +691,7 @@ const CMapFactory = function CMapFactoryClosure() {
     while (true) {
       let obj = lexer.getObj();
 
-      if ((0, _primitives.isEOF)(obj)) {
+      if (obj === _primitives.EOF) {
         break;
       }
 
@@ -712,7 +712,7 @@ const CMapFactory = function CMapFactoryClosure() {
     while (true) {
       let obj = lexer.getObj();
 
-      if ((0, _primitives.isEOF)(obj)) {
+      if (obj === _primitives.EOF) {
         break;
       }
 
@@ -736,7 +736,7 @@ const CMapFactory = function CMapFactoryClosure() {
     while (true) {
       let obj = lexer.getObj();
 
-      if ((0, _primitives.isEOF)(obj)) {
+      if (obj === _primitives.EOF) {
         break;
       }
 
@@ -785,7 +785,7 @@ const CMapFactory = function CMapFactoryClosure() {
       try {
         const obj = lexer.getObj();
 
-        if ((0, _primitives.isEOF)(obj)) {
+        if (obj === _primitives.EOF) {
           break;
         } else if ((0, _primitives.isName)(obj)) {
           if (obj.name === "WMode") {

+ 44 - 6
lib/core/core_utils.js

@@ -36,6 +36,7 @@ exports.parseXFAPath = parseXFAPath;
 exports.readInt8 = readInt8;
 exports.readUint16 = readUint16;
 exports.readUint32 = readUint32;
+exports.recoverJsURL = recoverJsURL;
 exports.toRomanNumerals = toRomanNumerals;
 exports.validateCSSFont = validateCSSFont;
 exports.XRefParseException = exports.XRefEntryException = exports.ParserEOFException = exports.MissingDataException = void 0;
@@ -78,7 +79,7 @@ function getArrayLookupTableFactory(initializer) {
 
 class MissingDataException extends _util.BaseException {
   constructor(begin, end) {
-    super(`Missing data [${begin}, ${end})`);
+    super(`Missing data [${begin}, ${end})`, "MissingDataException");
     this.begin = begin;
     this.end = end;
   }
@@ -87,15 +88,30 @@ class MissingDataException extends _util.BaseException {
 
 exports.MissingDataException = MissingDataException;
 
-class ParserEOFException extends _util.BaseException {}
+class ParserEOFException extends _util.BaseException {
+  constructor(msg) {
+    super(msg, "ParserEOFException");
+  }
+
+}
 
 exports.ParserEOFException = ParserEOFException;
 
-class XRefEntryException extends _util.BaseException {}
+class XRefEntryException extends _util.BaseException {
+  constructor(msg) {
+    super(msg, "XRefEntryException");
+  }
+
+}
 
 exports.XRefEntryException = XRefEntryException;
 
-class XRefParseException extends _util.BaseException {}
+class XRefParseException extends _util.BaseException {
+  constructor(msg) {
+    super(msg, "XRefParseException");
+  }
+
+}
 
 exports.XRefParseException = XRefParseException;
 
@@ -181,7 +197,7 @@ function isWhiteSpace(ch) {
 }
 
 function parseXFAPath(path) {
-  const positionPattern = /(.+)\[([0-9]+)\]$/;
+  const positionPattern = /(.+)\[(\d+)\]$/;
   return path.split(".").map(component => {
     const m = component.match(positionPattern);
 
@@ -399,7 +415,7 @@ function validateCSSFont(cssFontInfo) {
     }
   } else {
     for (const ident of fontFamily.split(/[ \t]+/)) {
-      if (/^([0-9]|(-([0-9]|-)))/.test(ident) || !/^[a-zA-Z0-9\-_\\]+$/.test(ident)) {
+      if (/^(\d|(-(\d|-)))/.test(ident) || !/^[\w-\\]+$/.test(ident)) {
         (0, _util.warn)(`XFA - FontFamily contains some invalid <custom-ident>: ${fontFamily}.`);
         return false;
       }
@@ -411,4 +427,26 @@ function validateCSSFont(cssFontInfo) {
   const angle = parseFloat(italicAngle);
   cssFontInfo.italicAngle = isNaN(angle) || angle < -90 || angle > 90 ? DEFAULT_CSS_FONT_OBLIQUE : italicAngle.toString();
   return true;
+}
+
+function recoverJsURL(str) {
+  const URL_OPEN_METHODS = ["app.launchURL", "window.open", "xfa.host.gotoURL"];
+  const regex = new RegExp("^\\s*(" + URL_OPEN_METHODS.join("|").split(".").join("\\.") + ")\\((?:'|\")([^'\"]*)(?:'|\")(?:,\\s*(\\w+)\\)|\\))", "i");
+  const jsUrl = regex.exec(str);
+
+  if (jsUrl && jsUrl[2]) {
+    const url = jsUrl[2];
+    let newWindow = false;
+
+    if (jsUrl[3] === "true" && jsUrl[1] === "app.launchURL") {
+      newWindow = true;
+    }
+
+    return {
+      url,
+      newWindow
+    };
+  }
+
+  return null;
 }

+ 1 - 5
lib/core/crypto.js

@@ -1216,11 +1216,7 @@ class CipherTransform {
     if (cipher instanceof AESBaseCipher) {
       const strLen = s.length;
       const pad = 16 - strLen % 16;
-
-      if (pad !== 16) {
-        s = s.padEnd(16 * Math.ceil(strLen / 16), String.fromCharCode(pad));
-      }
-
+      s += String.fromCharCode(pad).repeat(pad);
       const iv = new Uint8Array(16);
 
       if (typeof crypto !== "undefined") {

+ 16 - 2
lib/core/decode_stream.js

@@ -148,7 +148,7 @@ class DecodeStream extends _base_stream.BaseStream {
 exports.DecodeStream = DecodeStream;
 
 class StreamsSequenceStream extends DecodeStream {
-  constructor(streams) {
+  constructor(streams, onError = null) {
     let maybeLength = 0;
 
     for (const stream of streams) {
@@ -157,6 +157,7 @@ class StreamsSequenceStream extends DecodeStream {
 
     super(maybeLength);
     this.streams = streams;
+    this._onError = onError;
   }
 
   readBlock() {
@@ -168,7 +169,20 @@ class StreamsSequenceStream extends DecodeStream {
     }
 
     const stream = streams.shift();
-    const chunk = stream.getBytes();
+    let chunk;
+
+    try {
+      chunk = stream.getBytes();
+    } catch (reason) {
+      if (this._onError) {
+        this._onError(reason, stream.dict && stream.dict.objId);
+
+        return;
+      }
+
+      throw reason;
+    }
+
     const bufferLength = this.bufferLength;
     const newLength = bufferLength + chunk.length;
     const buffer = this.ensureBuffer(newLength);

+ 44 - 29
lib/core/document.js

@@ -32,6 +32,8 @@ var _primitives = require("./primitives.js");
 
 var _core_utils = require("./core_utils.js");
 
+var _xfa_fonts = require("./xfa_fonts.js");
+
 var _stream = require("./stream.js");
 
 var _annotation = require("./annotation.js");
@@ -42,8 +44,6 @@ var _crypto = require("./crypto.js");
 
 var _catalog = require("./catalog.js");
 
-var _xfa_fonts = require("./xfa_fonts.js");
-
 var _parser = require("./parser.js");
 
 var _object_loader = require("./object_loader.js");
@@ -208,14 +208,26 @@ class Page {
     return (0, _util.shadow)(this, "rotate", rotate);
   }
 
-  getContentStream() {
+  _onSubStreamError(handler, reason, objId) {
+    if (this.evaluatorOptions.ignoreErrors) {
+      handler.send("UnsupportedFeature", {
+        featureId: _util.UNSUPPORTED_FEATURES.errorContentSubStream
+      });
+      (0, _util.warn)(`getContentStream - ignoring sub-stream (${objId}): "${reason}".`);
+      return;
+    }
+
+    throw reason;
+  }
+
+  getContentStream(handler) {
     return this.pdfManager.ensure(this, "content").then(content => {
       if (content instanceof _base_stream.BaseStream) {
         return content;
       }
 
       if (Array.isArray(content)) {
-        return new _decode_stream.StreamsSequenceStream(content);
+        return new _decode_stream.StreamsSequenceStream(content, this._onSubStreamError.bind(this, handler));
       }
 
       return new _stream.NullStream();
@@ -278,10 +290,10 @@ class Page {
     sink,
     task,
     intent,
-    renderInteractiveForms,
-    annotationStorage
+    cacheKey,
+    annotationStorage = null
   }) {
-    const contentStreamPromise = this.getContentStream();
+    const contentStreamPromise = this.getContentStream(handler);
     const resourcesPromise = this.loadResources(["ColorSpace", "ExtGState", "Font", "Pattern", "Properties", "Shading", "XObject"]);
     const partialEvaluator = new _evaluator.PartialEvaluator({
       xref: this.xref,
@@ -300,7 +312,7 @@ class Page {
       handler.send("StartRenderPage", {
         transparency: partialEvaluator.hasBlendModes(this.resources, this.nonBlendModesSet),
         pageIndex: this.pageIndex,
-        intent
+        cacheKey
       });
       return partialEvaluator.getOperatorList({
         stream: contentStream,
@@ -312,19 +324,22 @@ class Page {
       });
     });
     return Promise.all([pageListPromise, this._parsedAnnotations]).then(function ([pageOpList, annotations]) {
-      if (annotations.length === 0) {
+      if (annotations.length === 0 || intent & _util.RenderingIntentFlag.ANNOTATIONS_DISABLE) {
         pageOpList.flush(true);
         return {
           length: pageOpList.totalLength
         };
       }
 
-      const annotationIntent = intent.startsWith("oplist-") ? intent.split("-")[1] : intent;
+      const renderForms = !!(intent & _util.RenderingIntentFlag.ANNOTATIONS_FORMS),
+            intentAny = !!(intent & _util.RenderingIntentFlag.ANY),
+            intentDisplay = !!(intent & _util.RenderingIntentFlag.DISPLAY),
+            intentPrint = !!(intent & _util.RenderingIntentFlag.PRINT);
       const opListPromises = [];
 
       for (const annotation of annotations) {
-        if (annotationIntent === "display" && annotation.mustBeViewed(annotationStorage) || annotationIntent === "print" && annotation.mustBePrinted(annotationStorage)) {
-          opListPromises.push(annotation.getOperatorList(partialEvaluator, task, renderInteractiveForms, annotationStorage).catch(function (reason) {
+        if (intentAny || intentDisplay && annotation.mustBeViewed(annotationStorage) || intentPrint && annotation.mustBePrinted(annotationStorage)) {
+          opListPromises.push(annotation.getOperatorList(partialEvaluator, task, renderForms, annotationStorage).catch(function (reason) {
             (0, _util.warn)("getOperatorList - ignoring annotation data during " + `"${task.name}" task: "${reason}".`);
             return null;
           }));
@@ -355,7 +370,7 @@ class Page {
     sink,
     combineTextItems
   }) {
-    const contentStreamPromise = this.getContentStream();
+    const contentStreamPromise = this.getContentStream(handler);
     const resourcesPromise = this.loadResources(["ExtGState", "Font", "Properties", "XObject"]);
     const dataPromises = Promise.all([contentStreamPromise, resourcesPromise]);
     return dataPromises.then(([contentStream]) => {
@@ -403,9 +418,17 @@ class Page {
     return this._parsedAnnotations.then(function (annotations) {
       const annotationsData = [];
 
-      for (let i = 0, ii = annotations.length; i < ii; i++) {
-        if (!intent || intent === "display" && annotations[i].viewable || intent === "print" && annotations[i].printable) {
-          annotationsData.push(annotations[i].data);
+      if (annotations.length === 0) {
+        return annotationsData;
+      }
+
+      const intentAny = !!(intent & _util.RenderingIntentFlag.ANY),
+            intentDisplay = !!(intent & _util.RenderingIntentFlag.DISPLAY),
+            intentPrint = !!(intent & _util.RenderingIntentFlag.PRINT);
+
+      for (const annotation of annotations) {
+        if (intentAny || intentDisplay && annotation.viewable || intentPrint && annotation.printable) {
+          annotationsData.push(annotation.data);
         }
       }
 
@@ -450,7 +473,7 @@ const STARTXREF_SIGNATURE = new Uint8Array([0x73, 0x74, 0x61, 0x72, 0x74, 0x78,
 const ENDOBJ_SIGNATURE = new Uint8Array([0x65, 0x6e, 0x64, 0x6f, 0x62, 0x6a]);
 const FINGERPRINT_FIRST_BYTES = 1024;
 const EMPTY_FINGERPRINT = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
-const PDF_HEADER_VERSION_REGEXP = /^[1-9]\.[0-9]$/;
+const PDF_HEADER_VERSION_REGEXP = /^[1-9]\.\d$/;
 
 function find(stream, signature, limit = 1024, backwards = false) {
   const signatureLength = signature.length;
@@ -759,7 +782,7 @@ class PDFDocument {
   }
 
   get xfaFactory() {
-    if (this.pdfManager.enableXfa && this.formInfo.hasXfa && !this.formInfo.hasAcroForm) {
+    if (this.pdfManager.enableXfa && this.catalog.needsRendering && this.formInfo.hasXfa && !this.formInfo.hasAcroForm) {
       const data = this.xfaData;
       return (0, _util.shadow)(this, "xfaFactory", data ? new _factory.XFAFactory(data) : null);
     }
@@ -867,7 +890,7 @@ class PDFDocument {
       }
 
       let fontFamily = descriptor.get("FontFamily");
-      fontFamily = fontFamily.replace(/[ ]+([0-9])/g, "$1");
+      fontFamily = fontFamily.replace(/[ ]+(\d)/g, "$1");
       const fontWeight = descriptor.get("FontWeight");
       const italicAngle = -descriptor.get("ItalicAngle");
       const cssFontInfo = {
@@ -899,7 +922,7 @@ class PDFDocument {
     const reallyMissingFonts = new Set();
 
     for (const missing of missingFonts) {
-      if (!(0, _xfa_fonts.getXfaFontWidths)(`${missing}-Regular`)) {
+      if (!(0, _xfa_fonts.getXfaFontName)(`${missing}-Regular`)) {
         reallyMissingFonts.add(missing);
       }
     }
@@ -931,15 +954,7 @@ class PDFDocument {
         italicAngle: 12
       }]) {
         const name = `${missing}-${fontInfo.name}`;
-        const widths = (0, _xfa_fonts.getXfaFontWidths)(name);
-        const dict = new _primitives.Dict(null);
-        dict.set("BaseFont", _primitives.Name.get(name));
-        dict.set("Type", _primitives.Name.get("Font"));
-        dict.set("Subtype", _primitives.Name.get("TrueType"));
-        dict.set("Encoding", _primitives.Name.get("WinAnsiEncoding"));
-        const descriptor = new _primitives.Dict(null);
-        descriptor.set("Widths", widths);
-        dict.set("FontDescriptor", descriptor);
+        const dict = (0, _xfa_fonts.getXfaFontDict)(name);
         promises.push(partialEvaluator.handleSetFont(resources, [_primitives.Name.get(name), 1], null, operatorList, task, initialState, dict, {
           fontFamily: missing,
           fontWeight: fontInfo.fontWeight,

+ 70 - 38
lib/core/evaluator.js

@@ -44,6 +44,8 @@ var _unicode = require("./unicode.js");
 
 var _pattern = require("./pattern.js");
 
+var _xfa_fonts = require("./xfa_fonts.js");
+
 var _to_unicode_map = require("./to_unicode_map.js");
 
 var _function = require("./function.js");
@@ -66,8 +68,6 @@ var _core_utils = require("./core_utils.js");
 
 var _metrics = require("./metrics.js");
 
-var _xfa_fonts = require("./xfa_fonts.js");
-
 var _murmurhash = require("./murmurhash3.js");
 
 var _operator_list = require("./operator_list.js");
@@ -88,6 +88,7 @@ const PatternType = {
   TILING: 1,
   SHADING: 2
 };
+const TEXT_CHUNK_BATCH_SIZE = 10;
 const deferred = Promise.resolve();
 
 function normalizeBlendMode(value, parsingArray = false) {
@@ -445,11 +446,13 @@ class PartialEvaluator {
       bbox = null;
     }
 
-    let optionalContent = null,
-        groupOptions;
+    let optionalContent, groupOptions;
 
     if (dict.has("OC")) {
       optionalContent = await this.parseMarkedContentProps(dict.get("OC"), resources);
+    }
+
+    if (optionalContent !== undefined) {
       operatorList.addOp(_util.OPS.beginMarkedContentProps, ["OC", optionalContent]);
     }
 
@@ -509,7 +512,7 @@ class PartialEvaluator {
         operatorList.addOp(_util.OPS.endGroup, [groupOptions]);
       }
 
-      if (optionalContent) {
+      if (optionalContent !== undefined) {
         operatorList.addOp(_util.OPS.endMarkedContent, []);
       }
     });
@@ -541,17 +544,28 @@ class PartialEvaluator {
 
     if (!(w && (0, _util.isNum)(w)) || !(h && (0, _util.isNum)(h))) {
       (0, _util.warn)("Image dimensions are missing, or not numbers.");
-      return undefined;
+      return;
     }
 
     const maxImageSize = this.options.maxImageSize;
 
     if (maxImageSize !== -1 && w * h > maxImageSize) {
       (0, _util.warn)("Image exceeded maximum allowed size and was removed.");
-      return undefined;
+      return;
+    }
+
+    let optionalContent;
+
+    if (dict.has("OC")) {
+      optionalContent = await this.parseMarkedContentProps(dict.get("OC"), resources);
+    }
+
+    if (optionalContent !== undefined) {
+      operatorList.addOp(_util.OPS.beginMarkedContentProps, ["OC", optionalContent]);
     }
 
     const imageMask = dict.get("ImageMask", "IM") || false;
+    const interpolate = dict.get("Interpolate", "I");
     let imgData, args;
 
     if (imageMask) {
@@ -565,7 +579,8 @@ class PartialEvaluator {
         width,
         height,
         imageIsFromDecodeStream: image instanceof _decode_stream.DecodeStream,
-        inverseDecode: !!decode && decode[0] > 0
+        inverseDecode: !!decode && decode[0] > 0,
+        interpolate
       });
       imgData.cached = !!cacheKey;
       args = [imgData];
@@ -578,7 +593,11 @@ class PartialEvaluator {
         });
       }
 
-      return undefined;
+      if (optionalContent !== undefined) {
+        operatorList.addOp(_util.OPS.endMarkedContent, []);
+      }
+
+      return;
     }
 
     const softMask = dict.get("SMask", "SM") || false;
@@ -596,7 +615,12 @@ class PartialEvaluator {
       });
       imgData = imageObj.createImageData(true);
       operatorList.addOp(_util.OPS.paintInlineImageXObject, [imgData]);
-      return undefined;
+
+      if (optionalContent !== undefined) {
+        operatorList.addOp(_util.OPS.endMarkedContent, []);
+      }
+
+      return;
     }
 
     let objId = `img_${this.idFactory.createObjId()}`,
@@ -658,7 +682,9 @@ class PartialEvaluator {
       }
     }
 
-    return undefined;
+    if (optionalContent !== undefined) {
+      operatorList.addOp(_util.OPS.endMarkedContent, []);
+    }
   }
 
   handleSMask(smask, resources, operatorList, task, stateManager, localColorSpaceCache) {
@@ -739,7 +765,7 @@ class PartialEvaluator {
     return transferMaps;
   }
 
-  handleTilingType(fn, color, resources, pattern, patternDict, operatorList, task, cacheKey, localTilingPatternCache) {
+  handleTilingType(fn, color, resources, pattern, patternDict, operatorList, task, localTilingPatternCache) {
     const tilingOpList = new _operator_list.OperatorList();
 
     const patternResources = _primitives.Dict.merge({
@@ -758,8 +784,8 @@ class PartialEvaluator {
       operatorList.addDependencies(tilingOpList.dependencies);
       operatorList.addOp(fn, tilingPatternIR);
 
-      if (cacheKey) {
-        localTilingPatternCache.set(cacheKey, patternDict.objId, {
+      if (patternDict.objId) {
+        localTilingPatternCache.set(null, patternDict.objId, {
           operatorListIR,
           dict: patternDict
         });
@@ -1161,21 +1187,19 @@ class PartialEvaluator {
   }
 
   parseShading({
-    keyObj,
     shading,
     resources,
     localColorSpaceCache,
-    localShadingPatternCache,
-    matrix = null
+    localShadingPatternCache
   }) {
-    let id = localShadingPatternCache.get(keyObj);
+    let id = localShadingPatternCache.get(shading);
 
     if (!id) {
-      var shadingFill = _pattern.Pattern.parseShading(shading, matrix, this.xref, resources, this.handler, this._pdfFunctionFactory, localColorSpaceCache);
+      var shadingFill = _pattern.Pattern.parseShading(shading, this.xref, resources, this.handler, this._pdfFunctionFactory, localColorSpaceCache);
 
       const patternIR = shadingFill.getIR();
       id = `pattern_${this.idFactory.createObjId()}`;
-      localShadingPatternCache.set(keyObj, id);
+      localShadingPatternCache.set(shading, id);
       this.handler.send("obj", [id, this.pageIndex, "Pattern", patternIR]);
     }
 
@@ -1186,8 +1210,8 @@ class PartialEvaluator {
     const patternName = args.pop();
 
     if (patternName instanceof _primitives.Name) {
-      const name = patternName.name;
-      const localTilingPattern = localTilingPatternCache.getByName(name);
+      const rawPattern = patterns.getRaw(patternName.name);
+      const localTilingPattern = rawPattern instanceof _primitives.Ref && localTilingPatternCache.getByRef(rawPattern);
 
       if (localTilingPattern) {
         try {
@@ -1198,7 +1222,7 @@ class PartialEvaluator {
         } catch (ex) {}
       }
 
-      const pattern = patterns.get(name);
+      const pattern = this.xref.fetchIfRef(rawPattern);
 
       if (pattern) {
         const dict = (0, _primitives.isStream)(pattern) ? pattern.dict : pattern;
@@ -1206,19 +1230,17 @@ class PartialEvaluator {
 
         if (typeNum === PatternType.TILING) {
           const color = cs.base ? cs.base.getRgb(args, 0) : null;
-          return this.handleTilingType(fn, color, resources, pattern, dict, operatorList, task, name, localTilingPatternCache);
+          return this.handleTilingType(fn, color, resources, pattern, dict, operatorList, task, localTilingPatternCache);
         } else if (typeNum === PatternType.SHADING) {
           const shading = dict.get("Shading");
           const matrix = dict.getArray("Matrix");
           const objId = this.parseShading({
-            keyObj: pattern,
             shading,
-            matrix,
             resources,
             localColorSpaceCache,
             localShadingPatternCache
           });
-          operatorList.addOp(fn, ["Shading", objId]);
+          operatorList.addOp(fn, ["Shading", objId, matrix]);
           return undefined;
         }
 
@@ -1723,7 +1745,6 @@ class PartialEvaluator {
             }
 
             const patternId = self.parseShading({
-              keyObj: shading,
               shading,
               resources,
               localColorSpaceCache,
@@ -2250,8 +2271,6 @@ class PartialEvaluator {
       if (textContentItem.initialized) {
         textContentItem.hasEOL = true;
         flushTextContentItem();
-      } else if (textContent.items.length > 0) {
-        textContent.items[textContent.items.length - 1].hasEOL = true;
       } else {
         textContent.items.push({
           str: "",
@@ -2325,20 +2344,26 @@ class PartialEvaluator {
       textContentItem.str.length = 0;
     }
 
-    function enqueueChunk() {
+    function enqueueChunk(batch = false) {
       const length = textContent.items.length;
 
-      if (length > 0) {
-        sink.enqueue(textContent, length);
-        textContent.items = [];
-        textContent.styles = Object.create(null);
+      if (length === 0) {
+        return;
       }
+
+      if (batch && length < TEXT_CHUNK_BATCH_SIZE) {
+        return;
+      }
+
+      sink.enqueue(textContent, length);
+      textContent.items = [];
+      textContent.styles = Object.create(null);
     }
 
     const timeSlotManager = new TimeSlotManager();
     return new Promise(function promiseBody(resolve, reject) {
       const next = function (promise) {
-        enqueueChunk();
+        enqueueChunk(true);
         Promise.all([promise, sink.ready]).then(function () {
           try {
             promiseBody(resolve, reject);
@@ -3030,6 +3055,11 @@ class PartialEvaluator {
 
         const map = new Array(cmap.length);
         cmap.forEach(function (charCode, token) {
+          if (typeof token === "number") {
+            map[charCode] = String.fromCodePoint(token);
+            return;
+          }
+
           const str = [];
 
           for (let k = 0; k < token.length; k += 2) {
@@ -3423,6 +3453,7 @@ class PartialEvaluator {
           loadedName: baseDict.loadedName,
           widths: metrics.widths,
           defaultWidth: metrics.defaultWidth,
+          isSimulatedFlags: true,
           flags,
           firstChar,
           lastChar,
@@ -3525,11 +3556,12 @@ class PartialEvaluator {
 
       if (standardFontName) {
         cssFontInfo.fontFamily = `${cssFontInfo.fontFamily}-PdfJS-XFA`;
-        cssFontInfo.lineHeight = standardFontName.lineHeight || null;
+        cssFontInfo.metrics = standardFontName.metrics || null;
         glyphScaleFactors = standardFontName.factors || null;
         fontFile = await this.fetchStandardFontData(standardFontName.name);
         isInternalFont = !!fontFile;
-        type = "TrueType";
+        baseDict = dict = (0, _xfa_fonts.getXfaFontDict)(fontName.name);
+        composite = true;
       }
     } else if (!isType3Font) {
       const standardFontName = (0, _standard_fonts.getStandardFontName)(fontName.name);

+ 107 - 37
lib/core/fonts.js

@@ -60,8 +60,8 @@ var _type1_font = require("./type1_font.js");
 
 const PRIVATE_USE_AREAS = [[0xe000, 0xf8ff], [0x100000, 0x10fffd]];
 const PDF_GLYPH_SPACE_UNITS = 1000;
-const EXPORT_DATA_PROPERTIES = ["ascent", "bbox", "black", "bold", "charProcOperatorList", "composite", "cssFontInfo", "data", "defaultVMetrics", "defaultWidth", "descent", "fallbackName", "fontMatrix", "fontType", "isMonospace", "isSerifFont", "isType3Font", "italic", "loadedName", "mimetype", "missingFile", "name", "remeasure", "subtype", "type", "vertical"];
-const EXPORT_DATA_EXTRA_PROPERTIES = ["cMap", "defaultEncoding", "differences", "isSymbolicFont", "seacMap", "toFontChar", "toUnicode", "vmetrics", "widths"];
+const EXPORT_DATA_PROPERTIES = ["ascent", "bbox", "black", "bold", "charProcOperatorList", "composite", "cssFontInfo", "data", "defaultVMetrics", "defaultWidth", "descent", "fallbackName", "fontMatrix", "fontType", "isType3Font", "italic", "loadedName", "mimetype", "missingFile", "name", "remeasure", "subtype", "type", "vertical"];
+const EXPORT_DATA_EXTRA_PROPERTIES = ["cMap", "defaultEncoding", "differences", "isMonospace", "isSerifFont", "isSymbolicFont", "seacMap", "toFontChar", "toUnicode", "vmetrics", "widths"];
 
 function adjustWidths(properties) {
   if (!properties.fontMatrix) {
@@ -103,8 +103,8 @@ function adjustToUnicode(properties, builtInEncoding) {
       if (properties.toUnicode.has(charCode)) {
         continue;
       }
-    } else {
-      if (properties.hasEncoding && properties.differences[charCode] !== undefined) {
+    } else if (properties.hasEncoding) {
+      if (properties.differences.length === 0 || properties.differences[charCode] !== undefined) {
         continue;
       }
     }
@@ -278,6 +278,12 @@ function getFontFileType(file, {
   return [fileType, fileSubtype];
 }
 
+function applyStandardFontGlyphMap(map, glyphMap) {
+  for (const charCode in glyphMap) {
+    map[+charCode] = glyphMap[charCode];
+  }
+}
+
 function buildToFontChar(encoding, glyphsUnicodeMap, differences) {
   const toFontChar = [];
   let unicode;
@@ -301,6 +307,25 @@ function buildToFontChar(encoding, glyphsUnicodeMap, differences) {
   return toFontChar;
 }
 
+function convertCidString(charCode, cid, shouldThrow = false) {
+  switch (cid.length) {
+    case 1:
+      return cid.charCodeAt(0);
+
+    case 2:
+      return cid.charCodeAt(0) << 8 | cid.charCodeAt(1);
+  }
+
+  const msg = `Unsupported CID string (charCode ${charCode}): "${cid}".`;
+
+  if (shouldThrow) {
+    throw new _util.FormatError(msg);
+  }
+
+  (0, _util.warn)(msg);
+  return cid;
+}
+
 function adjustMapping(charCodeToGlyphId, hasGlyph, newGlyphZeroId) {
   const newMap = Object.create(null);
   const toFontChar = [];
@@ -653,7 +678,21 @@ class Font {
     this.cssFontInfo = properties.cssFontInfo;
     this._charsCache = Object.create(null);
     this._glyphCache = Object.create(null);
-    this.isSerifFont = !!(properties.flags & _fonts_utils.FontFlags.Serif);
+    let isSerifFont = !!(properties.flags & _fonts_utils.FontFlags.Serif);
+
+    if (!isSerifFont && !properties.isSimulatedFlags) {
+      const baseName = name.replace(/[,_]/g, "-").split("-")[0],
+            serifFonts = (0, _standard_fonts.getSerifFonts)();
+
+      for (const namePart of baseName.split("+")) {
+        if (serifFonts[namePart]) {
+          isSerifFont = true;
+          break;
+        }
+      }
+    }
+
+    this.isSerifFont = isSerifFont;
     this.isSymbolicFont = !!(properties.flags & _fonts_utils.FontFlags.Symbolic);
     this.isMonospace = !!(properties.flags & _fonts_utils.FontFlags.FixedPitch);
     let type = properties.type;
@@ -693,7 +732,7 @@ class Font {
       return;
     }
 
-    this.cidEncoding = properties.cidEncoding;
+    this.cidEncoding = properties.cidEncoding || "";
     this.vertical = !!properties.vertical;
 
     if (this.vertical) {
@@ -803,26 +842,14 @@ class Font {
     this.remeasure = (!isStandardFont || isNarrow) && Object.keys(this.widths).length > 0;
 
     if ((isStandardFont || isMappedToStandardFont) && type === "CIDFontType2" && this.cidEncoding.startsWith("Identity-")) {
-      const GlyphMapForStandardFonts = (0, _standard_fonts.getGlyphMapForStandardFonts)(),
-            cidToGidMap = properties.cidToGidMap;
+      const cidToGidMap = properties.cidToGidMap;
       const map = [];
-
-      for (const charCode in GlyphMapForStandardFonts) {
-        map[+charCode] = GlyphMapForStandardFonts[charCode];
-      }
+      applyStandardFontGlyphMap(map, (0, _standard_fonts.getGlyphMapForStandardFonts)());
 
       if (/Arial-?Black/i.test(name)) {
-        const SupplementalGlyphMapForArialBlack = (0, _standard_fonts.getSupplementalGlyphMapForArialBlack)();
-
-        for (const charCode in SupplementalGlyphMapForArialBlack) {
-          map[+charCode] = SupplementalGlyphMapForArialBlack[charCode];
-        }
+        applyStandardFontGlyphMap(map, (0, _standard_fonts.getSupplementalGlyphMapForArialBlack)());
       } else if (/Calibri/i.test(name)) {
-        const SupplementalGlyphMapForCalibri = (0, _standard_fonts.getSupplementalGlyphMapForCalibri)();
-
-        for (const charCode in SupplementalGlyphMapForCalibri) {
-          map[+charCode] = SupplementalGlyphMapForCalibri[charCode];
-        }
+        applyStandardFontGlyphMap(map, (0, _standard_fonts.getSupplementalGlyphMapForCalibri)());
       }
 
       if (cidToGidMap) {
@@ -833,11 +860,19 @@ class Font {
             map[+charCode] = cidToGidMap[cid];
           }
         }
-      }
 
-      const isIdentityUnicode = this.toUnicode instanceof _to_unicode_map.IdentityToUnicodeMap;
+        if (cidToGidMap.length !== this.toUnicode.length && properties.hasIncludedToUnicodeMap && this.toUnicode instanceof _to_unicode_map.IdentityToUnicodeMap) {
+          this.toUnicode.forEach(function (charCode, unicodeCharCode) {
+            const cid = map[charCode];
+
+            if (cidToGidMap[cid] === undefined) {
+              map[+charCode] = unicodeCharCode;
+            }
+          });
+        }
+      }
 
-      if (!isIdentityUnicode) {
+      if (!(this.toUnicode instanceof _to_unicode_map.IdentityToUnicodeMap)) {
         this.toUnicode.forEach(function (charCode, unicodeCharCode) {
           map[+charCode] = unicodeCharCode;
         });
@@ -854,7 +889,15 @@ class Font {
 
       this.toFontChar = buildToFontChar(_encodings.ZapfDingbatsEncoding, (0, _glyphlist.getDingbatsGlyphsUnicode)(), this.differences);
     } else if (isStandardFont) {
-      this.toFontChar = buildToFontChar(this.defaultEncoding, (0, _glyphlist.getGlyphsUnicode)(), this.differences);
+      const map = buildToFontChar(this.defaultEncoding, (0, _glyphlist.getGlyphsUnicode)(), this.differences);
+
+      if (type === "CIDFontType2" && !this.cidEncoding.startsWith("Identity-") && !(this.toUnicode instanceof _to_unicode_map.IdentityToUnicodeMap)) {
+        this.toUnicode.forEach(function (charCode, unicodeCharCode) {
+          map[+charCode] = unicodeCharCode;
+        });
+      }
+
+      this.toFontChar = map;
     } else {
       const glyphsUnicodeMap = (0, _glyphlist.getGlyphsUnicode)();
       const map = [];
@@ -873,11 +916,7 @@ class Font {
 
       if (this.composite && this.toUnicode instanceof _to_unicode_map.IdentityToUnicodeMap) {
         if (/Verdana/i.test(name)) {
-          const GlyphMapForStandardFonts = (0, _standard_fonts.getGlyphMapForStandardFonts)();
-
-          for (const charCode in GlyphMapForStandardFonts) {
-            map[+charCode] = GlyphMapForStandardFonts[charCode];
-          }
+          applyStandardFontGlyphMap(map, (0, _standard_fonts.getGlyphMapForStandardFonts)());
         }
       }
 
@@ -2184,7 +2223,8 @@ class Font {
     this.lineGap = metricsOverride.lineGap / metricsOverride.unitsPerEm;
 
     if (this.cssFontInfo && this.cssFontInfo.lineHeight) {
-      this.lineHeight = this.cssFontInfo.lineHeight;
+      this.lineHeight = this.cssFontInfo.metrics.lineHeight;
+      this.lineGap = this.cssFontInfo.metrics.lineGap;
     } else {
       this.lineHeight = this.ascent - this.descent + this.lineGap;
     }
@@ -2207,6 +2247,10 @@ class Font {
       const cidToGidMap = properties.cidToGidMap || [];
       const isCidToGidMapEmpty = cidToGidMap.length === 0;
       properties.cMap.forEach(function (charCode, cid) {
+        if (typeof cid === "string") {
+          cid = convertCidString(charCode, cid, true);
+        }
+
         if (cid > 0xffff) {
           throw new _util.FormatError("Max size of CID is 65,535");
         }
@@ -2229,7 +2273,8 @@ class Font {
       const cmapEncodingId = cmapTable.encodingId;
       const cmapMappings = cmapTable.mappings;
       const cmapMappingsLength = cmapMappings.length;
-      let baseEncoding = [];
+      let baseEncoding = [],
+          forcePostTable = false;
 
       if (properties.hasEncoding && (properties.baseEncodingName === "MacRomanEncoding" || properties.baseEncodingName === "WinAnsiEncoding")) {
         baseEncoding = (0, _encodings.getEncoding)(properties.baseEncodingName);
@@ -2243,7 +2288,7 @@ class Font {
 
           if (this.differences[charCode] !== undefined) {
             glyphName = this.differences[charCode];
-          } else if (baseEncoding[charCode] !== "") {
+          } else if (baseEncoding.length && baseEncoding[charCode] !== "") {
             glyphName = baseEncoding[charCode];
           } else {
             glyphName = _encodings.StandardEncoding[charCode];
@@ -2262,6 +2307,20 @@ class Font {
             unicodeOrCharCode = _encodings.MacRomanEncoding.indexOf(standardGlyphName);
           }
 
+          if (unicodeOrCharCode === undefined) {
+            if (!properties.glyphNames && properties.hasIncludedToUnicodeMap && !(this.toUnicode instanceof _to_unicode_map.IdentityToUnicodeMap)) {
+              const unicode = this.toUnicode.get(charCode);
+
+              if (unicode) {
+                unicodeOrCharCode = unicode.codePointAt(0);
+              }
+            }
+
+            if (unicodeOrCharCode === undefined) {
+              continue;
+            }
+          }
+
           for (let i = 0; i < cmapMappingsLength; ++i) {
             if (cmapMappings[i].charCode !== unicodeOrCharCode) {
               continue;
@@ -2275,6 +2334,8 @@ class Font {
         for (let i = 0; i < cmapMappingsLength; ++i) {
           charCodeToGlyphId[cmapMappings[i].charCode] = cmapMappings[i].glyphId;
         }
+
+        forcePostTable = true;
       } else {
         for (let i = 0; i < cmapMappingsLength; ++i) {
           let charCode = cmapMappings[i].charCode;
@@ -2289,7 +2350,7 @@ class Font {
 
       if (properties.glyphNames && (baseEncoding.length || this.differences.length)) {
         for (let i = 0; i < 256; ++i) {
-          if (charCodeToGlyphId[i] !== undefined) {
+          if (!forcePostTable && charCodeToGlyphId[i] !== undefined) {
             continue;
           }
 
@@ -2515,6 +2576,10 @@ class Font {
 
       if (this.composite && this.cMap.contains(glyphUnicode)) {
         charcode = this.cMap.lookup(glyphUnicode);
+
+        if (typeof charcode === "string") {
+          charcode = convertCidString(glyphUnicode, charcode);
+        }
       }
 
       if (!charcode && this.toUnicode) {
@@ -2542,6 +2607,10 @@ class Font {
 
     if (this.cMap && this.cMap.contains(charcode)) {
       widthCode = this.cMap.lookup(charcode);
+
+      if (typeof widthCode === "string") {
+        widthCode = convertCidString(charcode, widthCode);
+      }
     }
 
     width = this.widths[widthCode];
@@ -2673,6 +2742,8 @@ class Font {
 
     const hasCurrentBufErrors = () => buffers.length % 2 === 1;
 
+    const getCharCode = this.toUnicode instanceof _to_unicode_map.IdentityToUnicodeMap ? unicode => this.toUnicode.charCodeOf(unicode) : unicode => this.toUnicode.charCodeOf(String.fromCodePoint(unicode));
+
     for (let i = 0, ii = str.length; i < ii; i++) {
       const unicode = str.codePointAt(i);
 
@@ -2681,8 +2752,7 @@ class Font {
       }
 
       if (this.toUnicode) {
-        const char = String.fromCodePoint(unicode);
-        const charCode = this.toUnicode.charCodeOf(char);
+        const charCode = getCharCode(unicode);
 
         if (charCode !== -1) {
           if (hasCurrentBufErrors()) {

Файловите разлики са ограничени, защото са твърде много
+ 0 - 1
lib/core/helvetica_factors.js


+ 6 - 3
lib/core/image.js

@@ -138,7 +138,7 @@ class PDFImage {
 
     this.width = width;
     this.height = height;
-    this.interpolate = dict.get("Interpolate", "I") || false;
+    this.interpolate = dict.get("Interpolate", "I");
     this.imageMask = dict.get("ImageMask", "IM") || false;
     this.matte = dict.get("Matte") || false;
     let bitsPerComponent = image.bitsPerComponent;
@@ -283,7 +283,8 @@ class PDFImage {
     width,
     height,
     imageIsFromDecodeStream,
-    inverseDecode
+    inverseDecode,
+    interpolate
   }) {
     const computedLength = (width + 7 >> 3) * height;
     const actualLength = imgArray.byteLength;
@@ -313,7 +314,8 @@ class PDFImage {
     return {
       data,
       width,
-      height
+      height,
+      interpolate
     };
   }
 
@@ -543,6 +545,7 @@ class PDFImage {
     const imgData = {
       width: drawWidth,
       height: drawHeight,
+      interpolate: this.interpolate,
       kind: 0,
       data: null
     };

+ 17 - 21
lib/core/image_utils.js

@@ -36,7 +36,9 @@ class BaseLocalCache {
       (0, _util.unreachable)("Cannot initialize BaseLocalCache.");
     }
 
-    if (!options || !options.onlyRefs) {
+    this._onlyRefs = (options && options.onlyRefs) === true;
+
+    if (!this._onlyRefs) {
       this._nameRefMap = new Map();
       this._imageMap = new Map();
     }
@@ -45,6 +47,10 @@ class BaseLocalCache {
   }
 
   getByName(name) {
+    if (this._onlyRefs) {
+      (0, _util.unreachable)("Should not call `getByName` method.");
+    }
+
     const ref = this._nameRefMap.get(name);
 
     if (ref) {
@@ -131,10 +137,6 @@ class LocalFunctionCache extends BaseLocalCache {
     });
   }
 
-  getByName(name) {
-    (0, _util.unreachable)("Should not call `getByName` method.");
-  }
-
   set(name = null, ref, data) {
     if (!ref) {
       throw new Error('LocalFunctionCache.set - expected "ref" argument.');
@@ -181,28 +183,22 @@ class LocalGStateCache extends BaseLocalCache {
 exports.LocalGStateCache = LocalGStateCache;
 
 class LocalTilingPatternCache extends BaseLocalCache {
-  set(name, ref = null, data) {
-    if (typeof name !== "string") {
-      throw new Error('LocalTilingPatternCache.set - expected "name" argument.');
-    }
-
-    if (ref) {
-      if (this._imageCache.has(ref)) {
-        return;
-      }
-
-      this._nameRefMap.set(name, ref);
-
-      this._imageCache.put(ref, data);
+  constructor(options) {
+    super({
+      onlyRefs: true
+    });
+  }
 
-      return;
+  set(name = null, ref, data) {
+    if (!ref) {
+      throw new Error('LocalTilingPatternCache.set - expected "ref" argument.');
     }
 
-    if (this._imageMap.has(name)) {
+    if (this._imageCache.has(ref)) {
       return;
     }
 
-    this._imageMap.set(name, data);
+    this._imageCache.put(ref, data);
   }
 
 }

+ 1 - 1
lib/core/jbig2.js

@@ -36,7 +36,7 @@ var _ccitt = require("./ccitt.js");
 
 class Jbig2Error extends _util.BaseException {
   constructor(msg) {
-    super(`JBIG2 error: ${msg}`);
+    super(`JBIG2 error: ${msg}`, "Jbig2Error");
   }
 
 }

+ 8 - 3
lib/core/jpg.js

@@ -32,20 +32,25 @@ var _core_utils = require("./core_utils.js");
 
 class JpegError extends _util.BaseException {
   constructor(msg) {
-    super(`JPEG error: ${msg}`);
+    super(`JPEG error: ${msg}`, "JpegError");
   }
 
 }
 
 class DNLMarkerError extends _util.BaseException {
   constructor(message, scanLines) {
-    super(message);
+    super(message, "DNLMarkerError");
     this.scanLines = scanLines;
   }
 
 }
 
-class EOIMarkerError extends _util.BaseException {}
+class EOIMarkerError extends _util.BaseException {
+  constructor(msg) {
+    super(msg, "EOIMarkerError");
+  }
+
+}
 
 const dctZigZag = new Uint8Array([0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63]);
 const dctCos1 = 4017;

+ 7 - 1
lib/core/jpx.js

@@ -34,7 +34,7 @@ var _arithmetic_decoder = require("./arithmetic_decoder.js");
 
 class JpxError extends _util.BaseException {
   constructor(msg) {
-    super(`JPX error: ${msg}`);
+    super(`JPX error: ${msg}`, "JpxError");
   }
 
 }
@@ -1252,6 +1252,12 @@ function parseTilePackets(context, data, offset, dataLength) {
           zeroBitPlanesTree = new TagTree(width, height);
           precinct.inclusionTree = inclusionTree;
           precinct.zeroBitPlanesTree = zeroBitPlanesTree;
+
+          for (let l = 0; l < layerNumber; l++) {
+            if (readBits(1) !== 0) {
+              throw new JpxError("Invalid tag tree");
+            }
+          }
         }
 
         if (inclusionTree.reset(codeblockColumn, codeblockRow, layerNumber)) {

Файловите разлики са ограничени, защото са твърде много
+ 0 - 1
lib/core/liberationsans_widths.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 1
lib/core/myriadpro_factors.js


+ 2 - 2
lib/core/operator_list.js

@@ -540,12 +540,12 @@ class OperatorList {
     return (0, _util.shadow)(this, "CHUNK_SIZE_ABOUT", this.CHUNK_SIZE - 5);
   }
 
-  constructor(intent, streamSink) {
+  constructor(intent = 0, streamSink) {
     this._streamSink = streamSink;
     this.fnArray = [];
     this.argsArray = [];
 
-    if (streamSink && !(intent && intent.startsWith("oplist-"))) {
+    if (streamSink && !(intent & _util.RenderingIntentFlag.OPLIST)) {
       this.optimizer = new QueueOptimizer(this);
     } else {
       this.optimizer = new NullOptimizer(this);

+ 17 - 7
lib/core/parser.js

@@ -125,11 +125,11 @@ class Parser {
         case "[":
           const array = [];
 
-          while (!(0, _primitives.isCmd)(this.buf1, "]") && !(0, _primitives.isEOF)(this.buf1)) {
+          while (!(0, _primitives.isCmd)(this.buf1, "]") && this.buf1 !== _primitives.EOF) {
             array.push(this.getObj(cipherTransform));
           }
 
-          if ((0, _primitives.isEOF)(this.buf1)) {
+          if (this.buf1 === _primitives.EOF) {
             if (this.recoveryMode) {
               return array;
             }
@@ -143,7 +143,7 @@ class Parser {
         case "<<":
           const dict = new _primitives.Dict(this.xref);
 
-          while (!(0, _primitives.isCmd)(this.buf1, ">>") && !(0, _primitives.isEOF)(this.buf1)) {
+          while (!(0, _primitives.isCmd)(this.buf1, ">>") && this.buf1 !== _primitives.EOF) {
             if (!(0, _primitives.isName)(this.buf1)) {
               (0, _util.info)("Malformed dictionary: key must be a name object");
               this.shift();
@@ -153,14 +153,14 @@ class Parser {
             const key = this.buf1.name;
             this.shift();
 
-            if ((0, _primitives.isEOF)(this.buf1)) {
+            if (this.buf1 === _primitives.EOF) {
               break;
             }
 
             dict.set(key, this.getObj(cipherTransform));
           }
 
-          if ((0, _primitives.isEOF)(this.buf1)) {
+          if (this.buf1 === _primitives.EOF) {
             if (this.recoveryMode) {
               return dict;
             }
@@ -464,7 +464,7 @@ class Parser {
     const dict = new _primitives.Dict(this.xref);
     let dictLength;
 
-    while (!(0, _primitives.isCmd)(this.buf1, "ID") && !(0, _primitives.isEOF)(this.buf1)) {
+    while (!(0, _primitives.isCmd)(this.buf1, "ID") && this.buf1 !== _primitives.EOF) {
       if (!(0, _primitives.isName)(this.buf1)) {
         throw new _util.FormatError("Dictionary key must be a name object");
       }
@@ -472,7 +472,7 @@ class Parser {
       const key = this.buf1.name;
       this.shift();
 
-      if ((0, _primitives.isEOF)(this.buf1)) {
+      if (this.buf1 === _primitives.EOF) {
         break;
       }
 
@@ -1226,6 +1226,16 @@ class Lexer {
     }
 
     let str = String.fromCharCode(ch);
+
+    if (ch < 0x20 || ch > 0x7f) {
+      const nextCh = this.peekChar();
+
+      if (nextCh >= 0x20 && nextCh <= 0x7f) {
+        this.nextChar();
+        return _primitives.Cmd.get(str);
+      }
+    }
+
     const knownCommands = this.knownCommands;
     let knownCommandFound = knownCommands && knownCommands[str] !== undefined;
 

+ 7 - 9
lib/core/pattern.js

@@ -50,7 +50,7 @@ class Pattern {
     (0, _util.unreachable)("Cannot initialize Pattern.");
   }
 
-  static parseShading(shading, matrix, xref, res, handler, pdfFunctionFactory, localColorSpaceCache) {
+  static parseShading(shading, xref, res, handler, pdfFunctionFactory, localColorSpaceCache) {
     const dict = (0, _primitives.isStream)(shading) ? shading.dict : shading;
     const type = dict.get("ShadingType");
 
@@ -58,13 +58,13 @@ class Pattern {
       switch (type) {
         case ShadingType.AXIAL:
         case ShadingType.RADIAL:
-          return new RadialAxialShading(dict, matrix, xref, res, pdfFunctionFactory, localColorSpaceCache);
+          return new RadialAxialShading(dict, xref, res, pdfFunctionFactory, localColorSpaceCache);
 
         case ShadingType.FREE_FORM_MESH:
         case ShadingType.LATTICE_FORM_MESH:
         case ShadingType.COONS_PATCH_MESH:
         case ShadingType.TENSOR_PATCH_MESH:
-          return new MeshShading(shading, matrix, xref, res, pdfFunctionFactory, localColorSpaceCache);
+          return new MeshShading(shading, xref, res, pdfFunctionFactory, localColorSpaceCache);
 
         default:
           throw new _util.FormatError("Unsupported ShadingType: " + type);
@@ -104,9 +104,8 @@ class BaseShading {
 }
 
 class RadialAxialShading extends BaseShading {
-  constructor(dict, matrix, xref, resources, pdfFunctionFactory, localColorSpaceCache) {
+  constructor(dict, xref, resources, pdfFunctionFactory, localColorSpaceCache) {
     super();
-    this.matrix = matrix;
     this.coordsArr = dict.getArray("Coords");
     this.shadingType = dict.get("ShadingType");
 
@@ -221,7 +220,7 @@ class RadialAxialShading extends BaseShading {
       (0, _util.unreachable)(`getPattern type unknown: ${shadingType}`);
     }
 
-    return ["RadialAxial", type, this.bbox, this.colorStops, p0, p1, r0, r1, this.matrix];
+    return ["RadialAxial", type, this.bbox, this.colorStops, p0, p1, r0, r1];
   }
 
 }
@@ -365,7 +364,7 @@ class MeshShading extends BaseShading {
     return (0, _util.shadow)(this, "TRIANGLE_DENSITY", 20);
   }
 
-  constructor(stream, matrix, xref, resources, pdfFunctionFactory, localColorSpaceCache) {
+  constructor(stream, xref, resources, pdfFunctionFactory, localColorSpaceCache) {
     super();
 
     if (!(0, _primitives.isStream)(stream)) {
@@ -373,7 +372,6 @@ class MeshShading extends BaseShading {
     }
 
     const dict = stream.dict;
-    this.matrix = matrix;
     this.shadingType = dict.get("ShadingType");
     const bbox = dict.getArray("BBox");
 
@@ -931,7 +929,7 @@ class MeshShading extends BaseShading {
   }
 
   getIR() {
-    return ["Mesh", this.shadingType, this.coords, this.colors, this.figures, this.bounds, this.matrix, this.bbox, this.background];
+    return ["Mesh", this.shadingType, this.coords, this.colors, this.figures, this.bounds, this.bbox, this.background];
   }
 
 }

+ 2 - 11
lib/core/primitives.js

@@ -27,7 +27,6 @@ Object.defineProperty(exports, "__esModule", {
 exports.clearPrimitiveCaches = clearPrimitiveCaches;
 exports.isCmd = isCmd;
 exports.isDict = isDict;
-exports.isEOF = isEOF;
 exports.isName = isName;
 exports.isRef = isRef;
 exports.isRefsEqual = isRefsEqual;
@@ -38,7 +37,7 @@ var _util = require("../shared/util.js");
 
 var _base_stream = require("./base_stream.js");
 
-const EOF = {};
+const EOF = Symbol("EOF");
 exports.EOF = EOF;
 
 const Name = function NameClosure() {
@@ -229,7 +228,7 @@ class Dict {
         if (property === undefined) {
           property = [];
           properties.set(key, property);
-        } else if (!mergeSubDicts) {
+        } else if (!mergeSubDicts || !(value instanceof Dict)) {
           continue;
         }
 
@@ -246,10 +245,6 @@ class Dict {
       const subDict = new Dict(xref);
 
       for (const dict of values) {
-        if (!(dict instanceof Dict)) {
-          continue;
-        }
-
         for (const [key, value] of Object.entries(dict._map)) {
           if (subDict._map[key] === undefined) {
             subDict._map[key] = value;
@@ -374,10 +369,6 @@ class RefSetCache {
 
 exports.RefSetCache = RefSetCache;
 
-function isEOF(v) {
-  return v === EOF;
-}
-
 function isName(v, name) {
   return v instanceof Name && (name === undefined || v.name === name);
 }

Файловите разлики са ограничени, защото са твърде много
+ 0 - 1
lib/core/segoeui_factors.js


+ 32 - 0
lib/core/standard_fonts.js

@@ -132,6 +132,12 @@ const getNonStdFontMap = (0, _core_utils.getLookupTableFactory)(function (t) {
   t["ComicSansMS-Bold"] = "Comic Sans MS-Bold";
   t["ComicSansMS-BoldItalic"] = "Comic Sans MS-BoldItalic";
   t["ComicSansMS-Italic"] = "Comic Sans MS-Italic";
+  t["ItcSymbol-Bold"] = "Helvetica-Bold";
+  t["ItcSymbol-BoldItalic"] = "Helvetica-BoldOblique";
+  t["ItcSymbol-Book"] = "Helvetica";
+  t["ItcSymbol-BookItalic"] = "Helvetica-Oblique";
+  t["ItcSymbol-Medium"] = "Helvetica";
+  t["ItcSymbol-MediumItalic"] = "Helvetica-Oblique";
   t.LucidaConsole = "Courier";
   t["LucidaConsole-Bold"] = "Courier-Bold";
   t["LucidaConsole-BoldItalic"] = "Courier-BoldOblique";
@@ -240,6 +246,7 @@ const getSerifFonts = (0, _core_utils.getLookupTableFactory)(function (t) {
   t.Joanna = true;
   t.Korinna = true;
   t.Lexicon = true;
+  t.LiberationSerif = true;
   t["Liberation Serif"] = true;
   t["Linux Libertine"] = true;
   t.Literaturnaya = true;
@@ -471,12 +478,17 @@ const getGlyphMapForStandardFonts = (0, _core_utils.getLookupTableFactory)(funct
   t[169] = 171;
   t[170] = 187;
   t[171] = 8230;
+  t[200] = 193;
+  t[203] = 205;
   t[210] = 218;
   t[223] = 711;
   t[224] = 321;
   t[225] = 322;
+  t[226] = 352;
   t[227] = 353;
+  t[228] = 381;
   t[229] = 382;
+  t[233] = 221;
   t[234] = 253;
   t[252] = 263;
   t[253] = 268;
@@ -486,11 +498,13 @@ const getGlyphMapForStandardFonts = (0, _core_utils.getLookupTableFactory)(funct
   t[261] = 261;
   t[265] = 280;
   t[266] = 281;
+  t[267] = 282;
   t[268] = 283;
   t[269] = 313;
   t[275] = 323;
   t[276] = 324;
   t[278] = 328;
+  t[283] = 344;
   t[284] = 345;
   t[285] = 346;
   t[286] = 347;
@@ -706,14 +720,19 @@ exports.getSupplementalGlyphMapForArialBlack = getSupplementalGlyphMapForArialBl
 const getSupplementalGlyphMapForCalibri = (0, _core_utils.getLookupTableFactory)(function (t) {
   t[1] = 32;
   t[4] = 65;
+  t[6] = 193;
   t[17] = 66;
   t[18] = 67;
+  t[21] = 268;
   t[24] = 68;
   t[28] = 69;
+  t[30] = 201;
+  t[32] = 282;
   t[38] = 70;
   t[39] = 71;
   t[44] = 72;
   t[47] = 73;
+  t[49] = 205;
   t[58] = 74;
   t[60] = 75;
   t[62] = 76;
@@ -723,26 +742,35 @@ const getSupplementalGlyphMapForCalibri = (0, _core_utils.getLookupTableFactory)
   t[87] = 80;
   t[89] = 81;
   t[90] = 82;
+  t[92] = 344;
   t[94] = 83;
+  t[97] = 352;
   t[100] = 84;
   t[104] = 85;
   t[115] = 86;
   t[116] = 87;
   t[121] = 88;
   t[122] = 89;
+  t[124] = 221;
   t[127] = 90;
+  t[129] = 381;
   t[258] = 97;
+  t[260] = 225;
   t[268] = 261;
   t[271] = 98;
   t[272] = 99;
   t[273] = 263;
+  t[275] = 269;
   t[282] = 100;
   t[286] = 101;
+  t[288] = 233;
+  t[290] = 283;
   t[295] = 281;
   t[296] = 102;
   t[336] = 103;
   t[346] = 104;
   t[349] = 105;
+  t[351] = 237;
   t[361] = 106;
   t[364] = 107;
   t[367] = 108;
@@ -754,15 +782,19 @@ const getSupplementalGlyphMapForCalibri = (0, _core_utils.getLookupTableFactory)
   t[393] = 112;
   t[395] = 113;
   t[396] = 114;
+  t[398] = 345;
   t[400] = 115;
   t[401] = 347;
+  t[403] = 353;
   t[410] = 116;
   t[437] = 117;
   t[448] = 118;
   t[449] = 119;
   t[454] = 120;
   t[455] = 121;
+  t[457] = 253;
   t[460] = 122;
+  t[462] = 382;
   t[463] = 380;
   t[853] = 44;
   t[855] = 58;

+ 21 - 14
lib/core/worker.js

@@ -107,7 +107,7 @@ class WorkerMessageHandler {
     const WorkerTasks = [];
     const verbosity = (0, _util.getVerbosityLevel)();
     const apiVersion = docParams.apiVersion;
-    const workerVersion = '2.10.377';
+    const workerVersion = '2.11.338';
 
     if (apiVersion !== workerVersion) {
       throw new Error(`The API version "${apiVersion}" does not match ` + `the Worker version "${workerVersion}".`);
@@ -477,7 +477,7 @@ class WorkerMessageHandler {
       filename
     }) {
       pdfManager.requestLoadedStream();
-      const promises = [pdfManager.onLoadedStream(), pdfManager.ensureCatalog("acroForm"), pdfManager.ensureDoc("xref"), pdfManager.ensureDoc("startXRef")];
+      const promises = [pdfManager.onLoadedStream(), pdfManager.ensureCatalog("acroForm"), pdfManager.ensureCatalog("acroFormRef"), pdfManager.ensureDoc("xref"), pdfManager.ensureDoc("startXRef")];
 
       if (isPureXfa) {
         promises.push(pdfManager.serializeXfaData(annotationStorage));
@@ -492,7 +492,7 @@ class WorkerMessageHandler {
         }
       }
 
-      return Promise.all(promises).then(function ([stream, acroForm, xref, startXRef, ...refs]) {
+      return Promise.all(promises).then(function ([stream, acroForm, acroFormRef, xref, startXRef, ...refs]) {
         let newRefs = [];
         let xfaData = null;
 
@@ -512,16 +512,24 @@ class WorkerMessageHandler {
           }
         }
 
-        const xfa = acroForm instanceof _primitives.Dict && acroForm.get("XFA") || [];
-        let xfaDatasets = null;
+        const xfa = acroForm instanceof _primitives.Dict && acroForm.get("XFA") || null;
+        let xfaDatasetsRef = null;
+        let hasXfaDatasetsEntry = false;
 
         if (Array.isArray(xfa)) {
           for (let i = 0, ii = xfa.length; i < ii; i += 2) {
             if (xfa[i] === "datasets") {
-              xfaDatasets = xfa[i + 1];
+              xfaDatasetsRef = xfa[i + 1];
+              acroFormRef = null;
+              hasXfaDatasetsEntry = true;
             }
           }
-        } else {
+
+          if (xfaDatasetsRef === null) {
+            xfaDatasetsRef = xref.getNewRef();
+          }
+        } else if (xfa) {
+          acroFormRef = null;
           (0, _util.warn)("Unsupported XFA type.");
         }
 
@@ -557,7 +565,11 @@ class WorkerMessageHandler {
           xrefInfo: newXrefInfo,
           newRefs,
           xref,
-          datasetsRef: xfaDatasets,
+          hasXfa: !!xfa,
+          xfaDatasetsRef,
+          hasXfaDatasetsEntry,
+          acroFormRef,
+          acroForm,
           xfaData
         });
       });
@@ -573,7 +585,7 @@ class WorkerMessageHandler {
           sink,
           task,
           intent: data.intent,
-          renderInteractiveForms: data.renderInteractiveForms,
+          cacheKey: data.cacheKey,
           annotationStorage: data.annotationStorage
         }).then(function (operatorListInfo) {
           finishWorkerTask(task);
@@ -599,11 +611,6 @@ class WorkerMessageHandler {
     });
     handler.on("GetTextContent", function wphExtractText(data, sink) {
       const pageIndex = data.pageIndex;
-
-      sink.onPull = function (desiredSize) {};
-
-      sink.onCancel = function (reason) {};
-
       pdfManager.getPage(pageIndex).then(function (page) {
         const task = new WorkerTask("GetTextContent: page " + pageIndex);
         startWorkerTask(task);

+ 67 - 8
lib/core/writer.js

@@ -110,10 +110,16 @@ function writeValue(value, buffer, transform) {
     buffer.push(`(${(0, _util.escapeString)(value)})`);
   } else if (typeof value === "number") {
     buffer.push(numberToString(value));
+  } else if (typeof value === "boolean") {
+    buffer.push(value.toString());
   } else if ((0, _primitives.isDict)(value)) {
     writeDict(value, buffer, transform);
   } else if ((0, _primitives.isStream)(value)) {
     writeStream(value, buffer, transform);
+  } else if (value === null) {
+    buffer.push("null");
+  } else {
+    (0, _util.warn)(`Unhandled value in writer: ${typeof value}, please file a bug.`);
   }
 }
 
@@ -189,26 +195,63 @@ function writeXFADataForAcroform(str, newRefs) {
   return buffer.join("");
 }
 
-function updateXFA(xfaData, datasetsRef, newRefs, xref) {
-  if (datasetsRef === null || xref === null) {
+function updateXFA({
+  xfaData,
+  xfaDatasetsRef,
+  hasXfaDatasetsEntry,
+  acroFormRef,
+  acroForm,
+  newRefs,
+  xref,
+  xrefInfo
+}) {
+  if (xref === null) {
     return;
   }
 
+  if (!hasXfaDatasetsEntry) {
+    if (!acroFormRef) {
+      (0, _util.warn)("XFA - Cannot save it");
+      return;
+    }
+
+    const oldXfa = acroForm.get("XFA");
+    const newXfa = oldXfa.slice();
+    newXfa.splice(2, 0, "datasets");
+    newXfa.splice(3, 0, xfaDatasetsRef);
+    acroForm.set("XFA", newXfa);
+    const encrypt = xref.encrypt;
+    let transform = null;
+
+    if (encrypt) {
+      transform = encrypt.createCipherTransform(acroFormRef.num, acroFormRef.gen);
+    }
+
+    const buffer = [`${acroFormRef.num} ${acroFormRef.gen} obj\n`];
+    writeDict(acroForm, buffer, transform);
+    buffer.push("\n");
+    acroForm.set("XFA", oldXfa);
+    newRefs.push({
+      ref: acroFormRef,
+      data: buffer.join("")
+    });
+  }
+
   if (xfaData === null) {
-    const datasets = xref.fetchIfRef(datasetsRef);
+    const datasets = xref.fetchIfRef(xfaDatasetsRef);
     xfaData = writeXFADataForAcroform(datasets.getString(), newRefs);
   }
 
   const encrypt = xref.encrypt;
 
   if (encrypt) {
-    const transform = encrypt.createCipherTransform(datasetsRef.num, datasetsRef.gen);
+    const transform = encrypt.createCipherTransform(xfaDatasetsRef.num, xfaDatasetsRef.gen);
     xfaData = transform.encryptString(xfaData);
   }
 
-  const data = `${datasetsRef.num} ${datasetsRef.gen} obj\n` + `<< /Type /EmbeddedFile /Length ${xfaData.length}>>\nstream\n` + xfaData + "\nendstream\nendobj\n";
+  const data = `${xfaDatasetsRef.num} ${xfaDatasetsRef.gen} obj\n` + `<< /Type /EmbeddedFile /Length ${xfaData.length}>>\nstream\n` + xfaData + "\nendstream\nendobj\n";
   newRefs.push({
-    ref: datasetsRef,
+    ref: xfaDatasetsRef,
     data
   });
 }
@@ -218,10 +261,26 @@ function incrementalUpdate({
   xrefInfo,
   newRefs,
   xref = null,
-  datasetsRef = null,
+  hasXfa = false,
+  xfaDatasetsRef = null,
+  hasXfaDatasetsEntry = false,
+  acroFormRef = null,
+  acroForm = null,
   xfaData = null
 }) {
-  updateXFA(xfaData, datasetsRef, newRefs, xref);
+  if (hasXfa) {
+    updateXFA({
+      xfaData,
+      xfaDatasetsRef,
+      hasXfaDatasetsEntry,
+      acroFormRef,
+      acroForm,
+      newRefs,
+      xref,
+      xrefInfo
+    });
+  }
+
   const newXref = new _primitives.Dict(null);
   const refForXrefTable = xrefInfo.newRef;
   let buffer, baseOffset;

+ 56 - 33
lib/core/xfa/bind.js

@@ -36,6 +36,8 @@ var _namespaces = require("./namespaces.js");
 
 var _util = require("../../shared/util.js");
 
+const NS_DATASETS = _namespaces.NamespaceIds.datasets.id;
+
 function createText(content) {
   const node = new _template.Text({});
   node[_xfa_object.$content] = content;
@@ -324,6 +326,10 @@ class Binder {
 
     if (matches.length > 1) {
       baseClone = formNode[_xfa_object.$clone]();
+
+      baseClone[_xfa_object.$removeChild](baseClone.occur);
+
+      baseClone.occur = null;
     }
 
     this._bindValue(formNode, matches[0], picture);
@@ -347,9 +353,6 @@ class Binder {
 
       const clone = baseClone[_xfa_object.$clone]();
 
-      clone.occur.min = 1;
-      clone.occur.max = 1;
-      clone.occur.initial = 1;
       parent[name].push(clone);
 
       parent[_xfa_object.$insertAt](pos + i, clone);
@@ -379,25 +382,48 @@ class Binder {
 
     const name = formNode[_xfa_object.$nodeName];
 
-    for (let i = 0, ii = occur.initial; i < ii; i++) {
-      const clone = formNode[_xfa_object.$clone]();
+    if (!(parent[name] instanceof _xfa_object.XFAObjectArray)) {
+      return;
+    }
 
-      clone.occur.min = 1;
-      clone.occur.max = 1;
-      clone.occur.initial = 1;
-      parent[name].push(clone);
+    let currentNumber;
+
+    if (formNode.name) {
+      currentNumber = parent[name].children.filter(e => e.name === formNode.name).length;
+    } else {
+      currentNumber = parent[name].children.length;
+    }
+
+    const pos = parent[_xfa_object.$indexOf](formNode) + 1;
+    const ii = occur.initial - currentNumber;
+
+    if (ii) {
+      const nodeClone = formNode[_xfa_object.$clone]();
+
+      nodeClone[_xfa_object.$removeChild](nodeClone.occur);
+
+      nodeClone.occur = null;
+      parent[name].push(nodeClone);
+
+      parent[_xfa_object.$insertAt](pos, nodeClone);
 
-      parent[_xfa_object.$appendChild](clone);
+      for (let i = 1; i < ii; i++) {
+        const clone = nodeClone[_xfa_object.$clone]();
+
+        parent[name].push(clone);
+
+        parent[_xfa_object.$insertAt](pos + i, clone);
+      }
     }
   }
 
   _getOccurInfo(formNode) {
     const {
+      name,
       occur
     } = formNode;
-    const dataName = formNode.name;
 
-    if (!occur || !dataName) {
+    if (!occur || !name) {
       return [1, 1];
     }
 
@@ -405,6 +431,14 @@ class Binder {
     return [occur.min, max];
   }
 
+  _setAndBind(formNode, dataNode) {
+    this._setProperties(formNode, dataNode);
+
+    this._bindItems(formNode, dataNode);
+
+    this._bindElement(formNode, dataNode);
+  }
+
   _bindElement(formNode, dataNode) {
     const uselessNodes = [];
 
@@ -423,7 +457,8 @@ class Binder {
         if (dataChildren.length > 0) {
           this._bindOccurrences(child, [dataChildren[0]], null);
         } else if (this.emptyMerge) {
-          const dataChild = child[_xfa_object.$data] = new _xfa_object.XmlObject(dataNode[_xfa_object.$namespaceId], child.name || "root");
+          const nsId = dataNode[_xfa_object.$namespaceId] === NS_DATASETS ? -1 : dataNode[_xfa_object.$namespaceId];
+          const dataChild = child[_xfa_object.$data] = new _xfa_object.XmlObject(nsId, child.name || "root");
 
           dataNode[_xfa_object.$appendChild](dataChild);
 
@@ -445,7 +480,7 @@ class Binder {
       if (child.bind) {
         switch (child.bind.match) {
           case "none":
-            this._bindElement(child, dataNode);
+            this._setAndBind(child, dataNode);
 
             continue;
 
@@ -457,7 +492,7 @@ class Binder {
             if (!child.bind.ref) {
               (0, _util.warn)(`XFA - ref is empty in node ${child[_xfa_object.$nodeName]}.`);
 
-              this._bindElement(child, dataNode);
+              this._setAndBind(child, dataNode);
 
               continue;
             }
@@ -490,7 +525,7 @@ class Binder {
             match[_xfa_object.$consumed] = true;
           }
 
-          this._bindElement(child, match);
+          this._setAndBind(child, match);
 
           continue;
         } else {
@@ -512,7 +547,7 @@ class Binder {
         }
       } else {
         if (!child.name) {
-          this._bindElement(child, dataNode);
+          this._setAndBind(child, dataNode);
 
           continue;
         }
@@ -536,7 +571,8 @@ class Binder {
           match = dataNode[_xfa_object.$getRealChildrenByNameIt](child.name, false, this.emptyMerge).next().value;
 
           if (!match) {
-            match = child[_xfa_object.$data] = new _xfa_object.XmlObject(dataNode[_xfa_object.$namespaceId], child.name);
+            const nsId = dataNode[_xfa_object.$namespaceId] === NS_DATASETS ? -1 : dataNode[_xfa_object.$namespaceId];
+            match = child[_xfa_object.$data] = new _xfa_object.XmlObject(nsId, child.name);
 
             if (this.emptyMerge) {
               match[_xfa_object.$consumed] = true;
@@ -544,11 +580,7 @@ class Binder {
 
             dataNode[_xfa_object.$appendChild](match);
 
-            this._setProperties(child, match);
-
-            this._bindItems(child, match);
-
-            this._bindElement(child, match);
+            this._setAndBind(child, match);
 
             continue;
           }
@@ -562,18 +594,9 @@ class Binder {
       }
 
       if (match) {
-        if (match.length < min) {
-          (0, _util.warn)(`XFA - Must have at least ${min} occurrences: ${formNode[_xfa_object.$nodeName]}.`);
-          continue;
-        }
-
         this._bindOccurrences(child, match, picture);
       } else if (min > 0) {
-        this._setProperties(child, dataNode);
-
-        this._bindItems(child, dataNode);
-
-        this._bindElement(child, dataNode);
+        this._setAndBind(child, dataNode);
       } else {
         uselessNodes.push(child);
       }

+ 34 - 0
lib/core/xfa/fonts.js

@@ -24,9 +24,14 @@
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
+exports.getMetrics = getMetrics;
 exports.selectFont = selectFont;
 exports.FontFinder = void 0;
 
+var _xfa_object = require("./xfa_object.js");
+
+var _utils = require("./utils.js");
+
 var _util = require("../../shared/util.js");
 
 class FontFinder {
@@ -188,4 +193,33 @@ function selectFont(xfaFont, typeface) {
   }
 
   return typeface.regular;
+}
+
+function getMetrics(xfaFont, real = false) {
+  let pdfFont = null;
+
+  if (xfaFont) {
+    const name = (0, _utils.stripQuotes)(xfaFont.typeface);
+
+    const typeface = xfaFont[_xfa_object.$globalData].fontFinder.find(name);
+
+    pdfFont = selectFont(xfaFont, typeface);
+  }
+
+  if (!pdfFont) {
+    return {
+      lineHeight: 12,
+      lineGap: 2,
+      lineNoGap: 10
+    };
+  }
+
+  const size = xfaFont.size || 10;
+  const lineHeight = pdfFont.lineHeight ? Math.max(real ? 0 : 1.2, pdfFont.lineHeight) : 1.2;
+  const lineGap = pdfFont.lineGap === undefined ? 0.2 : pdfFont.lineGap;
+  return {
+    lineHeight: lineHeight * size,
+    lineGap: lineGap * size,
+    lineNoGap: Math.max(1, lineHeight - lineGap) * size
+  };
 }

+ 2 - 2
lib/core/xfa/formcalc_lexer.js

@@ -85,8 +85,8 @@ const TOKEN = {
 };
 exports.TOKEN = TOKEN;
 const hexPattern = /^[uU]([0-9a-fA-F]{4,8})/;
-const numberPattern = /^[0-9]*(?:\.[0-9]*)?(?:[Ee][+-]?[0-9]+)?/;
-const dotNumberPattern = /^[0-9]*(?:[Ee][+-]?[0-9]+)?/;
+const numberPattern = /^\d*(?:\.\d*)?(?:[Ee][+-]?\d+)?/;
+const dotNumberPattern = /^\d*(?:[Ee][+-]?\d+)?/;
 const eolPattern = /[\r\n]+/;
 const identifierPattern = new RegExp("^[\\p{L}_$!][\\p{L}\\p{N}_$]*", "u");
 

+ 31 - 10
lib/core/xfa/html_utils.js

@@ -28,6 +28,7 @@ exports.computeBbox = computeBbox;
 exports.createWrapper = createWrapper;
 exports.fixDimensions = fixDimensions;
 exports.fixTextIndent = fixTextIndent;
+exports.fixURL = fixURL;
 exports.isPrintOnly = isPrintOnly;
 exports.layoutClass = layoutClass;
 exports.layoutNode = layoutNode;
@@ -40,14 +41,14 @@ exports.toStyle = toStyle;
 
 var _xfa_object = require("./xfa_object.js");
 
+var _util = require("../../shared/util.js");
+
 var _utils = require("./utils.js");
 
 var _fonts = require("./fonts.js");
 
 var _text = require("./text.js");
 
-var _util = require("../../shared/util.js");
-
 function measureToString(m) {
   if (typeof m === "string") {
     return "0px";
@@ -292,7 +293,7 @@ function layoutNode(node, availableSpace) {
       }
     }
 
-    const maxWidth = !node.w ? availableSpace.width : node.w;
+    const maxWidth = (!node.w ? availableSpace.width : node.w) - marginH;
     const fontFinder = node[_xfa_object.$globalData].fontFinder;
 
     if (node.value.exData && node.value.exData[_xfa_object.$content] && node.value.exData.contentType === "text/html") {
@@ -603,6 +604,12 @@ function isPrintOnly(node) {
   return node.relevant.length > 0 && !node.relevant[0].excluded && node.relevant[0].viewname === "print";
 }
 
+function getCurrentPara(node) {
+  const stack = node[_xfa_object.$getTemplateRoot]()[_xfa_object.$extra].paraStack;
+
+  return stack.length ? stack[stack.length - 1] : null;
+}
+
 function setPara(node, nodeStyle, value) {
   if (value.attributes.class && value.attributes.class.includes("xfaRich")) {
     if (nodeStyle) {
@@ -615,12 +622,14 @@ function setPara(node, nodeStyle, value) {
       }
     }
 
-    if (node.para) {
+    const para = getCurrentPara(node);
+
+    if (para) {
       const valueStyle = value.attributes.style;
       valueStyle.display = "flex";
       valueStyle.flexDirection = "column";
 
-      switch (node.para.vAlign) {
+      switch (para.vAlign) {
         case "top":
           valueStyle.justifyContent = "start";
           break;
@@ -634,7 +643,7 @@ function setPara(node, nodeStyle, value) {
           break;
       }
 
-      const paraStyle = node.para[_xfa_object.$toStyle]();
+      const paraStyle = para[_xfa_object.$toStyle]();
 
       for (const [key, val] of Object.entries(paraStyle)) {
         if (!(key in valueStyle)) {
@@ -645,7 +654,7 @@ function setPara(node, nodeStyle, value) {
   }
 }
 
-function setFontFamily(xfaFont, fontFinder, style) {
+function setFontFamily(xfaFont, node, fontFinder, style) {
   const name = (0, _utils.stripQuotes)(xfaFont.typeface);
   const typeface = fontFinder.find(name);
   style.fontFamily = `"${name}"`;
@@ -659,16 +668,28 @@ function setFontFamily(xfaFont, fontFinder, style) {
       style.fontFamily = `"${fontFamily}"`;
     }
 
+    const para = getCurrentPara(node);
+
+    if (para && para.lineHeight !== "") {
+      return;
+    }
+
     if (style.lineHeight) {
       return;
     }
 
     const pdfFont = (0, _fonts.selectFont)(xfaFont, typeface);
 
-    if (pdfFont && pdfFont.lineHeight > 0) {
+    if (pdfFont) {
       style.lineHeight = Math.max(1.2, pdfFont.lineHeight);
-    } else {
-      style.lineHeight = 1.2;
     }
   }
+}
+
+function fixURL(str) {
+  const absoluteUrl = (0, _util.createValidAbsoluteUrl)(str, null, {
+    addDefaultProtocol: true,
+    tryConvertEncoding: true
+  });
+  return absoluteUrl ? absoluteUrl.href : null;
 }

+ 7 - 7
lib/core/xfa/layout.js

@@ -283,20 +283,20 @@ function checkDimensions(node, space) {
             }
 
             if (parent[_xfa_object.$extra].numberInLine === 0) {
-              return space.height > 0;
+              return space.height > ERROR;
             }
 
             return false;
           }
 
-          return space.width > 0;
+          return space.width > ERROR;
         }
 
         if (node.w !== "") {
           return Math.round(w - space.width) <= ERROR;
         }
 
-        return space.width > 0;
+        return space.width > ERROR;
       }
 
       if (node[_xfa_object.$getTemplateRoot]()[_xfa_object.$extra].noLayoutFailure) {
@@ -308,14 +308,14 @@ function checkDimensions(node, space) {
       }
 
       if (node.w === "" || Math.round(w - space.width) <= ERROR) {
-        return space.height > 0;
+        return space.height > ERROR;
       }
 
       if (parent[_xfa_object.$isThereMoreWidth]()) {
         return false;
       }
 
-      return space.height > 0;
+      return space.height > ERROR;
 
     case "table":
     case "tb":
@@ -328,14 +328,14 @@ function checkDimensions(node, space) {
       }
 
       if (node.w === "" || Math.round(w - space.width) <= ERROR) {
-        return space.height > 0;
+        return space.height > ERROR;
       }
 
       if (parent[_xfa_object.$isThereMoreWidth]()) {
         return false;
       }
 
-      return space.height > 0;
+      return space.height > ERROR;
 
     case "position":
       if (node[_xfa_object.$getTemplateRoot]()[_xfa_object.$extra].noLayoutFailure) {

+ 6 - 2
lib/core/xfa/som.js

@@ -29,6 +29,8 @@ exports.searchNode = searchNode;
 
 var _xfa_object = require("./xfa_object.js");
 
+var _namespaces = require("./namespaces.js");
+
 var _util = require("../../shared/util.js");
 
 const namePattern = /^[^.[]+/;
@@ -40,8 +42,9 @@ const operators = {
   dotBracket: 3,
   dotParen: 4
 };
-const shortcuts = new Map([["$data", (root, current) => root.datasets.data], ["$template", (root, current) => root.template], ["$connectionSet", (root, current) => root.connectionSet], ["$form", (root, current) => root.form], ["$layout", (root, current) => root.layout], ["$host", (root, current) => root.host], ["$dataWindow", (root, current) => root.dataWindow], ["$event", (root, current) => root.event], ["!", (root, current) => root.datasets], ["$xfa", (root, current) => root], ["xfa", (root, current) => root], ["$", (root, current) => current]]);
+const shortcuts = new Map([["$data", (root, current) => root.datasets ? root.datasets.data : root], ["$record", (root, current) => (root.datasets ? root.datasets.data : root)[_xfa_object.$getChildren]()[0]], ["$template", (root, current) => root.template], ["$connectionSet", (root, current) => root.connectionSet], ["$form", (root, current) => root.form], ["$layout", (root, current) => root.layout], ["$host", (root, current) => root.host], ["$dataWindow", (root, current) => root.dataWindow], ["$event", (root, current) => root.event], ["!", (root, current) => root.datasets], ["$xfa", (root, current) => root], ["xfa", (root, current) => root], ["$", (root, current) => current]]);
 const somCache = new WeakMap();
+const NS_DATASETS = _namespaces.NamespaceIds.datasets.id;
 
 function parseIndex(index) {
   index = index.trim();
@@ -267,7 +270,8 @@ function createNodes(root, path) {
     index
   } of path) {
     for (let i = 0, ii = !isFinite(index) ? 0 : index; i <= ii; i++) {
-      node = new _xfa_object.XmlObject(root[_xfa_object.$namespaceId], name);
+      const nsId = root[_xfa_object.$namespaceId] === NS_DATASETS ? -1 : root[_xfa_object.$namespaceId];
+      node = new _xfa_object.XmlObject(nsId, name);
 
       root[_xfa_object.$appendChild](node);
     }

Файловите разлики са ограничени, защото са твърде много
+ 408 - 113
lib/core/xfa/template.js


+ 19 - 7
lib/core/xfa/text.js

@@ -28,7 +28,7 @@ exports.TextMeasure = void 0;
 
 var _fonts = require("./fonts.js");
 
-const WIDTH_FACTOR = 1.01;
+const WIDTH_FACTOR = 1.02;
 
 class FontInfo {
   constructor(xfaFont, margin, lineHeight, fontFinder) {
@@ -165,18 +165,24 @@ class TextMeasure {
     if (lastFont.pdfFont) {
       const letterSpacing = lastFont.xfaFont.letterSpacing;
       const pdfFont = lastFont.pdfFont;
-      const lineHeight = lastFont.lineHeight || Math.ceil(Math.max(1.2, pdfFont.lineHeight) * fontSize);
+      const fontLineHeight = pdfFont.lineHeight || 1.2;
+      const lineHeight = lastFont.lineHeight || Math.max(1.2, fontLineHeight) * fontSize;
+      const lineGap = pdfFont.lineGap === undefined ? 0.2 : pdfFont.lineGap;
+      const noGap = fontLineHeight - lineGap;
+      const firstLineHeight = Math.max(1, noGap) * fontSize;
       const scale = fontSize / 1000;
+      const fallbackWidth = pdfFont.defaultWidth || pdfFont.charsToGlyphs(" ")[0].width;
 
       for (const line of str.split(/[\u2029\n]/)) {
         const encodedLine = pdfFont.encodeString(line).join("");
         const glyphs = pdfFont.charsToGlyphs(encodedLine);
 
         for (const glyph of glyphs) {
-          this.glyphs.push([glyph.width * scale + letterSpacing, lineHeight, glyph.unicode === " ", false]);
+          const width = glyph.width || fallbackWidth;
+          this.glyphs.push([width * scale + letterSpacing, lineHeight, firstLineHeight, glyph.unicode, false]);
         }
 
-        this.glyphs.push([0, 0, false, true]);
+        this.glyphs.push([0, 0, 0, "\n", true]);
       }
 
       this.glyphs.pop();
@@ -185,10 +191,10 @@ class TextMeasure {
 
     for (const line of str.split(/[\u2029\n]/)) {
       for (const char of line.split("")) {
-        this.glyphs.push([fontSize, fontSize, char === " ", false]);
+        this.glyphs.push([fontSize, 1.2 * fontSize, fontSize, char, false]);
       }
 
-      this.glyphs.push([0, 0, false, true]);
+      this.glyphs.push([0, 0, 0, "\n", true]);
     }
 
     this.glyphs.pop();
@@ -202,9 +208,12 @@ class TextMeasure {
         currentLineWidth = 0,
         currentLineHeight = 0;
     let isBroken = false;
+    let isFirstLine = true;
 
     for (let i = 0, ii = this.glyphs.length; i < ii; i++) {
-      const [glyphWidth, glyphHeight, isSpace, isEOL] = this.glyphs[i];
+      const [glyphWidth, lineHeight, firstLineHeight, char, isEOL] = this.glyphs[i];
+      const isSpace = char === " ";
+      const glyphHeight = isFirstLine ? firstLineHeight : lineHeight;
 
       if (isEOL) {
         width = Math.max(width, currentLineWidth);
@@ -213,6 +222,7 @@ class TextMeasure {
         currentLineHeight = glyphHeight;
         lastSpacePos = -1;
         lastSpaceWidth = 0;
+        isFirstLine = false;
         continue;
       }
 
@@ -225,6 +235,7 @@ class TextMeasure {
           lastSpacePos = -1;
           lastSpaceWidth = 0;
           isBroken = true;
+          isFirstLine = false;
         } else {
           currentLineHeight = Math.max(glyphHeight, currentLineHeight);
           lastSpaceWidth = currentLineWidth;
@@ -251,6 +262,7 @@ class TextMeasure {
         }
 
         isBroken = true;
+        isFirstLine = false;
         continue;
       }
 

+ 1 - 1
lib/core/xfa/utils.js

@@ -45,7 +45,7 @@ const dimConverters = {
   in: x => x * 72,
   px: x => x
 };
-const measurementPattern = /([+-]?[0-9]+\.?[0-9]*)(.*)/;
+const measurementPattern = /([+-]?\d+\.?\d*)(.*)/;
 
 function stripQuotes(str) {
   if (str.startsWith("'") || str.startsWith('"')) {

+ 29 - 3
lib/core/xfa/xfa_object.js

@@ -24,7 +24,7 @@
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
-exports.XmlObject = exports.XFAObjectArray = exports.XFAObject = exports.XFAAttribute = exports.StringObject = exports.OptionObject = exports.Option10 = exports.Option01 = exports.IntegerObject = exports.ContentObject = exports.$uid = exports.$toStyle = exports.$toString = exports.$toHTML = exports.$text = exports.$tabIndex = exports.$setValue = exports.$setSetAttributes = exports.$setId = exports.$searchNode = exports.$root = exports.$resolvePrototypes = exports.$removeChild = exports.$pushGlyphs = exports.$onText = exports.$onChildCheck = exports.$onChild = exports.$nsAttributes = exports.$nodeName = exports.$namespaceId = exports.$isUsable = exports.$isTransparent = exports.$isThereMoreWidth = exports.$isSplittable = exports.$isNsAgnostic = exports.$isDescendent = exports.$isDataValue = exports.$isCDATAXml = exports.$isBindable = exports.$insertAt = exports.$indexOf = exports.$ids = exports.$hasSettableValue = exports.$globalData = exports.$getTemplateRoot = exports.$getSubformParent = exports.$getRealChildrenByNameIt = exports.$getParent = exports.$getNextPage = exports.$getExtra = exports.$getDataValue = exports.$getContainedChildren = exports.$getChildrenByNameIt = exports.$getChildrenByName = exports.$getChildrenByClass = exports.$getChildren = exports.$getAvailableSpace = exports.$getAttributes = exports.$getAttributeIt = exports.$flushHTML = exports.$finalize = exports.$extra = exports.$dump = exports.$data = exports.$content = exports.$consumed = exports.$clone = exports.$cleanup = exports.$cleanPage = exports.$clean = exports.$childrenToHTML = exports.$appendChild = exports.$addHTML = exports.$acceptWhitespace = void 0;
+exports.XmlObject = exports.XFAObjectArray = exports.XFAObject = exports.XFAAttribute = exports.StringObject = exports.OptionObject = exports.Option10 = exports.Option01 = exports.IntegerObject = exports.ContentObject = exports.$uid = exports.$toStyle = exports.$toString = exports.$toHTML = exports.$text = exports.$tabIndex = exports.$setValue = exports.$setSetAttributes = exports.$setId = exports.$searchNode = exports.$root = exports.$resolvePrototypes = exports.$removeChild = exports.$pushPara = exports.$pushGlyphs = exports.$popPara = exports.$onText = exports.$onChildCheck = exports.$onChild = exports.$nsAttributes = exports.$nodeName = exports.$namespaceId = exports.$isUsable = exports.$isTransparent = exports.$isThereMoreWidth = exports.$isSplittable = exports.$isNsAgnostic = exports.$isDescendent = exports.$isDataValue = exports.$isCDATAXml = exports.$isBindable = exports.$insertAt = exports.$indexOf = exports.$ids = exports.$hasSettableValue = exports.$globalData = exports.$getTemplateRoot = exports.$getSubformParent = exports.$getRealChildrenByNameIt = exports.$getParent = exports.$getNextPage = exports.$getExtra = exports.$getDataValue = exports.$getContainedChildren = exports.$getChildrenByNameIt = exports.$getChildrenByName = exports.$getChildrenByClass = exports.$getChildren = exports.$getAvailableSpace = exports.$getAttributes = exports.$getAttributeIt = exports.$flushHTML = exports.$finalize = exports.$extra = exports.$dump = exports.$data = exports.$content = exports.$consumed = exports.$clone = exports.$cleanup = exports.$cleanPage = exports.$clean = exports.$childrenToHTML = exports.$appendChild = exports.$addHTML = exports.$acceptWhitespace = void 0;
 
 var _utils = require("./utils.js");
 
@@ -139,6 +139,10 @@ const $onText = Symbol();
 exports.$onText = $onText;
 const $pushGlyphs = Symbol();
 exports.$pushGlyphs = $pushGlyphs;
+const $popPara = Symbol();
+exports.$popPara = $popPara;
+const $pushPara = Symbol();
+exports.$pushPara = $pushPara;
 const $removeChild = Symbol();
 exports.$removeChild = $removeChild;
 const $root = Symbol("root");
@@ -269,6 +273,16 @@ class XFAObject {
     return false;
   }
 
+  [$popPara]() {
+    if (this.para) {
+      this[$getTemplateRoot]()[$extra].paraStack.pop();
+    }
+  }
+
+  [$pushPara]() {
+    this[$getTemplateRoot]()[$extra].paraStack.push(this.para);
+  }
+
   [$setId](ids) {
     if (this.id && this[$namespaceId] === _namespaces.NamespaceIds.template.id) {
       ids.set(this.id, this);
@@ -291,6 +305,10 @@ class XFAObject {
     child[_parent] = this;
 
     this[_children].push(child);
+
+    if (!child[$globalData] && this[$globalData]) {
+      child[$globalData] = this[$globalData];
+    }
   }
 
   [$removeChild](child) {
@@ -326,6 +344,10 @@ class XFAObject {
     child[_parent] = this;
 
     this[_children].splice(i, 0, child);
+
+    if (!child[$globalData] && this[$globalData]) {
+      child[$globalData] = this[$globalData];
+    }
   }
 
   [$isTransparent]() {
@@ -1065,9 +1087,13 @@ class XmlObject extends XFAObject {
     this[$content] = value.toString();
   }
 
-  [$dump]() {
+  [$dump](hasNS = false) {
     const dumped = Object.create(null);
 
+    if (hasNS) {
+      dumped.$ns = this[$namespaceId];
+    }
+
     if (this[$content]) {
       dumped.$content = this[$content];
     }
@@ -1076,7 +1102,7 @@ class XmlObject extends XFAObject {
     dumped.children = [];
 
     for (const child of this[_children]) {
-      dumped.children.push(child[$dump]());
+      dumped.children.push(child[$dump](hasNS));
     }
 
     dumped.attributes = Object.create(null);

+ 4 - 4
lib/core/xfa/xhtml.js

@@ -43,7 +43,7 @@ const StyleMapping = new Map([["page-break-after", "breakAfter"], ["page-break-b
 const spacesRegExp = /\s+/g;
 const crlfRegExp = /[\r\n]+/g;
 
-function mapStyle(styleStr, fontFinder) {
+function mapStyle(styleStr, node) {
   const style = Object.create(null);
 
   if (!styleStr) {
@@ -86,7 +86,7 @@ function mapStyle(styleStr, fontFinder) {
       weight: style.fontWeight || "normal",
       posture: style.fontStyle || "normal",
       size: original.fontSize || 0
-    }, fontFinder, style);
+    }, node, node[_xfa_object.$globalData].fontFinder, style);
   }
 
   (0, _html_utils.fixTextIndent)(style);
@@ -256,7 +256,7 @@ class XhtmlObject extends _xfa_object.XmlObject {
       name: this[_xfa_object.$nodeName],
       attributes: {
         href: this.href,
-        style: mapStyle(this.style, this[_xfa_object.$globalData].fontFinder)
+        style: mapStyle(this.style, this)
       },
       children,
       value: this[_xfa_object.$content] || ""
@@ -268,7 +268,7 @@ class XhtmlObject extends _xfa_object.XmlObject {
 class A extends XhtmlObject {
   constructor(attributes) {
     super(attributes, "a");
-    this.href = attributes.href || "";
+    this.href = (0, _html_utils.fixURL)(attributes.href) || "";
   }
 
 }

+ 89 - 23
lib/core/xfa_fonts.js

@@ -24,11 +24,13 @@
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
+exports.getXfaFontDict = getXfaFontDict;
 exports.getXfaFontName = getXfaFontName;
-exports.getXfaFontWidths = getXfaFontWidths;
 
 var _calibri_factors = require("./calibri_factors.js");
 
+var _primitives = require("./primitives.js");
+
 var _helvetica_factors = require("./helvetica_factors.js");
 
 var _liberationsans_widths = require("./liberationsans_widths.js");
@@ -46,113 +48,133 @@ const getXFAFontMap = (0, _core_utils.getLookupTableFactory)(function (t) {
     name: "LiberationSans-Regular",
     factors: _myriadpro_factors.MyriadProRegularFactors,
     baseWidths: _liberationsans_widths.LiberationSansRegularWidths,
-    lineHeight: _myriadpro_factors.MyriadProRegularLineHeight
+    baseMapping: _liberationsans_widths.LiberationSansRegularMapping,
+    metrics: _myriadpro_factors.MyriadProRegularMetrics
   };
   t["MyriadPro-Bold"] = t["PdfJS-Fallback-Bold"] = {
     name: "LiberationSans-Bold",
     factors: _myriadpro_factors.MyriadProBoldFactors,
     baseWidths: _liberationsans_widths.LiberationSansBoldWidths,
-    lineHeight: _myriadpro_factors.MyriadProBoldLineHeight
+    baseMapping: _liberationsans_widths.LiberationSansBoldMapping,
+    metrics: _myriadpro_factors.MyriadProBoldMetrics
   };
   t["MyriadPro-It"] = t["MyriadPro-Italic"] = t["PdfJS-Fallback-Italic"] = {
     name: "LiberationSans-Italic",
     factors: _myriadpro_factors.MyriadProItalicFactors,
     baseWidths: _liberationsans_widths.LiberationSansItalicWidths,
-    lineHeight: _myriadpro_factors.MyriadProItalicLineHeight
+    baseMapping: _liberationsans_widths.LiberationSansItalicMapping,
+    metrics: _myriadpro_factors.MyriadProItalicMetrics
   };
   t["MyriadPro-BoldIt"] = t["MyriadPro-BoldItalic"] = t["PdfJS-Fallback-BoldItalic"] = {
     name: "LiberationSans-BoldItalic",
     factors: _myriadpro_factors.MyriadProBoldItalicFactors,
     baseWidths: _liberationsans_widths.LiberationSansBoldItalicWidths,
-    lineHeight: _myriadpro_factors.MyriadProBoldItalicLineHeight
+    baseMapping: _liberationsans_widths.LiberationSansBoldItalicMapping,
+    metrics: _myriadpro_factors.MyriadProBoldItalicMetrics
   };
   t.ArialMT = t.Arial = t["Arial-Regular"] = {
     name: "LiberationSans-Regular",
-    baseWidths: _liberationsans_widths.LiberationSansRegularWidths
+    baseWidths: _liberationsans_widths.LiberationSansRegularWidths,
+    baseMapping: _liberationsans_widths.LiberationSansRegularMapping
   };
   t["Arial-BoldMT"] = t["Arial-Bold"] = {
     name: "LiberationSans-Bold",
-    baseWidths: _liberationsans_widths.LiberationSansBoldWidths
+    baseWidths: _liberationsans_widths.LiberationSansBoldWidths,
+    baseMapping: _liberationsans_widths.LiberationSansBoldMapping
   };
   t["Arial-ItalicMT"] = t["Arial-Italic"] = {
     name: "LiberationSans-Italic",
-    baseWidths: _liberationsans_widths.LiberationSansItalicWidths
+    baseWidths: _liberationsans_widths.LiberationSansItalicWidths,
+    baseMapping: _liberationsans_widths.LiberationSansItalicMapping
   };
   t["Arial-BoldItalicMT"] = t["Arial-BoldItalic"] = {
     name: "LiberationSans-BoldItalic",
-    baseWidths: _liberationsans_widths.LiberationSansBoldItalicWidths
+    baseWidths: _liberationsans_widths.LiberationSansBoldItalicWidths,
+    baseMapping: _liberationsans_widths.LiberationSansBoldItalicMapping
   };
   t["Calibri-Regular"] = {
     name: "LiberationSans-Regular",
     factors: _calibri_factors.CalibriRegularFactors,
     baseWidths: _liberationsans_widths.LiberationSansRegularWidths,
-    lineHeight: _calibri_factors.CalibriRegularLineHeight
+    baseMapping: _liberationsans_widths.LiberationSansRegularMapping,
+    metrics: _calibri_factors.CalibriRegularMetrics
   };
   t["Calibri-Bold"] = {
     name: "LiberationSans-Bold",
     factors: _calibri_factors.CalibriBoldFactors,
     baseWidths: _liberationsans_widths.LiberationSansBoldWidths,
-    lineHeight: _calibri_factors.CalibriBoldLineHeight
+    baseMapping: _liberationsans_widths.LiberationSansBoldMapping,
+    metrics: _calibri_factors.CalibriBoldMetrics
   };
   t["Calibri-Italic"] = {
     name: "LiberationSans-Italic",
     factors: _calibri_factors.CalibriItalicFactors,
     baseWidths: _liberationsans_widths.LiberationSansItalicWidths,
-    lineHeight: _calibri_factors.CalibriItalicLineHeight
+    baseMapping: _liberationsans_widths.LiberationSansItalicMapping,
+    metrics: _calibri_factors.CalibriItalicMetrics
   };
   t["Calibri-BoldItalic"] = {
     name: "LiberationSans-BoldItalic",
     factors: _calibri_factors.CalibriBoldItalicFactors,
     baseWidths: _liberationsans_widths.LiberationSansBoldItalicWidths,
-    lineHeight: _calibri_factors.CalibriBoldItalicLineHeight
+    baseMapping: _liberationsans_widths.LiberationSansBoldItalicMapping,
+    metrics: _calibri_factors.CalibriBoldItalicMetrics
   };
   t["Segoeui-Regular"] = {
     name: "LiberationSans-Regular",
     factors: _segoeui_factors.SegoeuiRegularFactors,
     baseWidths: _liberationsans_widths.LiberationSansRegularWidths,
-    lineHeight: _segoeui_factors.SegoeuiRegularLineHeight
+    baseMapping: _liberationsans_widths.LiberationSansRegularMapping,
+    metrics: _segoeui_factors.SegoeuiRegularMetrics
   };
   t["Segoeui-Bold"] = {
     name: "LiberationSans-Bold",
     factors: _segoeui_factors.SegoeuiBoldFactors,
     baseWidths: _liberationsans_widths.LiberationSansBoldWidths,
-    lineHeight: _segoeui_factors.SegoeuiBoldLineHeight
+    baseMapping: _liberationsans_widths.LiberationSansBoldMapping,
+    metrics: _segoeui_factors.SegoeuiBoldMetrics
   };
   t["Segoeui-Italic"] = {
     name: "LiberationSans-Italic",
     factors: _segoeui_factors.SegoeuiItalicFactors,
     baseWidths: _liberationsans_widths.LiberationSansItalicWidths,
-    lineHeight: _segoeui_factors.SegoeuiItalicLineHeight
+    baseMapping: _liberationsans_widths.LiberationSansItalicMapping,
+    metrics: _segoeui_factors.SegoeuiItalicMetrics
   };
   t["Segoeui-BoldItalic"] = {
     name: "LiberationSans-BoldItalic",
     factors: _segoeui_factors.SegoeuiBoldItalicFactors,
     baseWidths: _liberationsans_widths.LiberationSansBoldItalicWidths,
-    lineHeight: _segoeui_factors.SegoeuiBoldItalicLineHeight
+    baseMapping: _liberationsans_widths.LiberationSansBoldItalicMapping,
+    metrics: _segoeui_factors.SegoeuiBoldItalicMetrics
   };
   t["Helvetica-Regular"] = t.Helvetica = {
     name: "LiberationSans-Regular",
     factors: _helvetica_factors.HelveticaRegularFactors,
     baseWidths: _liberationsans_widths.LiberationSansRegularWidths,
-    lineHeight: _helvetica_factors.HelveticaRegularLineHeight
+    baseMapping: _liberationsans_widths.LiberationSansRegularMapping,
+    metrics: _helvetica_factors.HelveticaRegularMetrics
   };
   t["Helvetica-Bold"] = {
     name: "LiberationSans-Bold",
     factors: _helvetica_factors.HelveticaBoldFactors,
     baseWidths: _liberationsans_widths.LiberationSansBoldWidths,
-    lineHeight: _helvetica_factors.HelveticaBoldLineHeight
+    baseMapping: _liberationsans_widths.LiberationSansBoldMapping,
+    metrics: _helvetica_factors.HelveticaBoldMetrics
   };
   t["Helvetica-Italic"] = {
     name: "LiberationSans-Italic",
     factors: _helvetica_factors.HelveticaItalicFactors,
     baseWidths: _liberationsans_widths.LiberationSansItalicWidths,
-    lineHeight: _helvetica_factors.HelveticaItalicLineHeight
+    baseMapping: _liberationsans_widths.LiberationSansItalicMapping,
+    metrics: _helvetica_factors.HelveticaItalicMetrics
   };
   t["Helvetica-BoldItalic"] = {
     name: "LiberationSans-BoldItalic",
     factors: _helvetica_factors.HelveticaBoldItalicFactors,
     baseWidths: _liberationsans_widths.LiberationSansBoldItalicWidths,
-    lineHeight: _helvetica_factors.HelveticaBoldItalicLineHeight
+    baseMapping: _liberationsans_widths.LiberationSansBoldItalicMapping,
+    metrics: _helvetica_factors.HelveticaBoldItalicMetrics
   };
 });
 
@@ -171,12 +193,56 @@ function getXfaFontWidths(name) {
 
   const {
     baseWidths,
+    baseMapping,
     factors
   } = info;
+  let rescaledBaseWidths;
 
   if (!factors) {
-    return baseWidths;
+    rescaledBaseWidths = baseWidths;
+  } else {
+    rescaledBaseWidths = baseWidths.map((w, i) => w * factors[i]);
   }
 
-  return baseWidths.map((w, i) => w * factors[i]);
+  let currentCode = -2;
+  let currentArray;
+  const newWidths = [];
+
+  for (const [unicode, glyphIndex] of baseMapping.map((charUnicode, index) => [charUnicode, index]).sort(([unicode1], [unicode2]) => unicode1 - unicode2)) {
+    if (unicode === -1) {
+      continue;
+    }
+
+    if (unicode === currentCode + 1) {
+      currentArray.push(rescaledBaseWidths[glyphIndex]);
+      currentCode += 1;
+    } else {
+      currentCode = unicode;
+      currentArray = [rescaledBaseWidths[glyphIndex]];
+      newWidths.push(unicode, currentArray);
+    }
+  }
+
+  return newWidths;
+}
+
+function getXfaFontDict(name) {
+  const widths = getXfaFontWidths(name);
+  const dict = new _primitives.Dict(null);
+  dict.set("BaseFont", _primitives.Name.get(name));
+  dict.set("Type", _primitives.Name.get("Font"));
+  dict.set("Subtype", _primitives.Name.get("CIDFontType2"));
+  dict.set("Encoding", _primitives.Name.get("Identity-H"));
+  dict.set("CIDToGIDMap", _primitives.Name.get("Identity"));
+  dict.set("W", widths);
+  dict.set("FirstChar", widths[0]);
+  dict.set("LastChar", widths[widths.length - 2] + widths[widths.length - 1].length - 1);
+  const descriptor = new _primitives.Dict(null);
+  dict.set("FontDescriptor", descriptor);
+  const systemInfo = new _primitives.Dict(null);
+  systemInfo.set("Ordering", "Identity");
+  systemInfo.set("Registry", "Adobe");
+  systemInfo.set("Supplement", 0);
+  dict.set("CIDSystemInfo", systemInfo);
+  return dict;
 }

+ 383 - 91
lib/display/annotation_layer.js

@@ -26,14 +26,17 @@ Object.defineProperty(exports, "__esModule", {
 });
 exports.AnnotationLayer = void 0;
 
-var _display_utils = require("./display_utils.js");
-
 var _util = require("../shared/util.js");
 
+var _display_utils = require("./display_utils.js");
+
 var _annotation_storage = require("./annotation_storage.js");
 
 var _scripting_utils = require("../shared/scripting_utils.js");
 
+const DEFAULT_TAB_INDEX = 1000;
+const GetElementsByNameSet = new WeakSet();
+
 class AnnotationElementFactory {
   static create(parameters) {
     const subtype = parameters.data.annotationType;
@@ -133,11 +136,12 @@ class AnnotationElement {
     this.linkService = parameters.linkService;
     this.downloadManager = parameters.downloadManager;
     this.imageResourcesPath = parameters.imageResourcesPath;
-    this.renderInteractiveForms = parameters.renderInteractiveForms;
+    this.renderForms = parameters.renderForms;
     this.svgFactory = parameters.svgFactory;
     this.annotationStorage = parameters.annotationStorage;
     this.enableScripting = parameters.enableScripting;
     this.hasJSActions = parameters.hasJSActions;
+    this._fieldObjects = parameters.fieldObjects;
     this._mouseState = parameters.mouseState;
 
     if (isRenderable) {
@@ -204,7 +208,9 @@ class AnnotationElement {
           break;
       }
 
-      if (data.color) {
+      const borderColor = data.borderColor || data.color || null;
+
+      if (borderColor) {
         container.style.borderColor = _util.Util.makeHexColor(data.color[0] | 0, data.color[1] | 0, data.color[2] | 0);
       } else {
         container.style.borderWidth = 0;
@@ -254,9 +260,9 @@ class AnnotationElement {
       container,
       trigger,
       color: data.color,
-      title: data.title,
+      titleObj: data.titleObj,
       modificationDate: data.modificationDate,
-      contents: data.contents,
+      contentsObj: data.contentsObj,
       hideWrapper: true
     });
     const popup = popupElement.render();
@@ -276,11 +282,82 @@ class AnnotationElement {
     (0, _util.unreachable)("Abstract method `AnnotationElement.render` called");
   }
 
+  _getElementsByName(name, skipId = null) {
+    const fields = [];
+
+    if (this._fieldObjects) {
+      const fieldObj = this._fieldObjects[name];
+
+      if (fieldObj) {
+        for (const {
+          page,
+          id,
+          exportValues
+        } of fieldObj) {
+          if (page === -1) {
+            continue;
+          }
+
+          if (id === skipId) {
+            continue;
+          }
+
+          const exportValue = typeof exportValues === "string" ? exportValues : null;
+          const domElement = document.getElementById(id);
+
+          if (domElement && !GetElementsByNameSet.has(domElement)) {
+            (0, _util.warn)(`_getElementsByName - element not allowed: ${id}`);
+            continue;
+          }
+
+          fields.push({
+            id,
+            exportValue,
+            domElement
+          });
+        }
+      }
+
+      return fields;
+    }
+
+    for (const domElement of document.getElementsByName(name)) {
+      const {
+        id,
+        exportValue
+      } = domElement;
+
+      if (id === skipId) {
+        continue;
+      }
+
+      if (!GetElementsByNameSet.has(domElement)) {
+        continue;
+      }
+
+      fields.push({
+        id,
+        exportValue,
+        domElement
+      });
+    }
+
+    return fields;
+  }
+
+  static get platform() {
+    const platform = typeof navigator !== "undefined" ? navigator.platform : "";
+    return (0, _util.shadow)(this, "platform", {
+      isWin: platform.includes("Win"),
+      isMac: platform.includes("Mac")
+    });
+  }
+
 }
 
 class LinkAnnotationElement extends AnnotationElement {
   constructor(parameters) {
-    const isRenderable = !!(parameters.data.url || parameters.data.dest || parameters.data.action || parameters.data.isTooltipOnly || parameters.data.actions && (parameters.data.actions.Action || parameters.data.actions["Mouse Up"] || parameters.data.actions["Mouse Down"]));
+    const isRenderable = !!(parameters.data.url || parameters.data.dest || parameters.data.action || parameters.data.isTooltipOnly || parameters.data.resetForm || parameters.data.actions && (parameters.data.actions.Action || parameters.data.actions["Mouse Up"] || parameters.data.actions["Mouse Down"]));
     super(parameters, {
       isRenderable,
       createQuadrilaterals: true
@@ -295,20 +372,29 @@ class LinkAnnotationElement extends AnnotationElement {
     const link = document.createElement("a");
 
     if (data.url) {
-      (0, _display_utils.addLinkAttributes)(link, {
-        url: data.url,
-        target: data.newWindow ? _display_utils.LinkTarget.BLANK : linkService.externalLinkTarget,
-        rel: linkService.externalLinkRel,
-        enabled: linkService.externalLinkEnabled
-      });
+      if (!linkService.addLinkAttributes) {
+        (0, _util.warn)("LinkAnnotationElement.render - missing `addLinkAttributes`-method on the `linkService`-instance.");
+      }
+
+      linkService.addLinkAttributes?.(link, data.url, data.newWindow);
     } else if (data.action) {
       this._bindNamedAction(link, data.action);
     } else if (data.dest) {
       this._bindLink(link, data.dest);
-    } else if (data.actions && (data.actions.Action || data.actions["Mouse Up"] || data.actions["Mouse Down"]) && this.enableScripting && this.hasJSActions) {
-      this._bindJSAction(link, data);
     } else {
-      this._bindLink(link, "");
+      let hasClickAction = false;
+
+      if (data.actions && (data.actions.Action || data.actions["Mouse Up"] || data.actions["Mouse Down"]) && this.enableScripting && this.hasJSActions) {
+        hasClickAction = true;
+
+        this._bindJSAction(link, data);
+      }
+
+      if (data.resetForm) {
+        this._bindResetFormAction(link, data.resetForm);
+      } else if (!hasClickAction) {
+        this._bindLink(link, "");
+      }
     }
 
     if (this.quadrilaterals) {
@@ -374,14 +460,143 @@ class LinkAnnotationElement extends AnnotationElement {
       };
     }
 
+    if (!link.onclick) {
+      link.onclick = () => false;
+    }
+
     link.className = "internalLink";
   }
 
+  _bindResetFormAction(link, resetForm) {
+    const otherClickAction = link.onclick;
+
+    if (!otherClickAction) {
+      link.href = this.linkService.getAnchorUrl("");
+    }
+
+    link.className = "internalLink";
+
+    if (!this._fieldObjects) {
+      (0, _util.warn)(`_bindResetFormAction - "resetForm" action not supported, ` + "ensure that the `fieldObjects` parameter is provided.");
+
+      if (!otherClickAction) {
+        link.onclick = () => false;
+      }
+
+      return;
+    }
+
+    link.onclick = () => {
+      if (otherClickAction) {
+        otherClickAction();
+      }
+
+      const {
+        fields: resetFormFields,
+        refs: resetFormRefs,
+        include
+      } = resetForm;
+      const allFields = [];
+
+      if (resetFormFields.length !== 0 || resetFormRefs.length !== 0) {
+        const fieldIds = new Set(resetFormRefs);
+
+        for (const fieldName of resetFormFields) {
+          const fields = this._fieldObjects[fieldName] || [];
+
+          for (const {
+            id
+          } of fields) {
+            fieldIds.add(id);
+          }
+        }
+
+        for (const fields of Object.values(this._fieldObjects)) {
+          for (const field of fields) {
+            if (fieldIds.has(field.id) === include) {
+              allFields.push(field);
+            }
+          }
+        }
+      } else {
+        for (const fields of Object.values(this._fieldObjects)) {
+          allFields.push(...fields);
+        }
+      }
+
+      const storage = this.annotationStorage;
+      const allIds = [];
+
+      for (const field of allFields) {
+        const {
+          id
+        } = field;
+        allIds.push(id);
+
+        switch (field.type) {
+          case "text":
+            {
+              const value = field.defaultValue || "";
+              storage.setValue(id, {
+                value,
+                valueAsString: value
+              });
+              break;
+            }
+
+          case "checkbox":
+          case "radiobutton":
+            {
+              const value = field.defaultValue === field.exportValues;
+              storage.setValue(id, {
+                value
+              });
+              break;
+            }
+
+          case "combobox":
+          case "listbox":
+            {
+              const value = field.defaultValue || "";
+              storage.setValue(id, {
+                value
+              });
+              break;
+            }
+
+          default:
+            continue;
+        }
+
+        const domElement = document.getElementById(id);
+
+        if (!domElement || !GetElementsByNameSet.has(domElement)) {
+          continue;
+        }
+
+        domElement.dispatchEvent(new Event("resetform"));
+      }
+
+      if (this.enableScripting) {
+        this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
+          source: this,
+          detail: {
+            id: "app",
+            ids: allIds,
+            name: "ResetForm"
+          }
+        });
+      }
+
+      return false;
+    };
+  }
+
 }
 
 class TextAnnotationElement extends AnnotationElement {
   constructor(parameters) {
-    const isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
+    const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str);
     super(parameters, {
       isRenderable
     });
@@ -419,7 +634,11 @@ class WidgetAnnotationElement extends AnnotationElement {
   }
 
   _getKeyModifier(event) {
-    return navigator.platform.includes("Win") && event.ctrlKey || navigator.platform.includes("Mac") && event.metaKey;
+    const {
+      isWin,
+      isMac
+    } = AnnotationElement.platform;
+    return isWin && event.ctrlKey || isMac && event.metaKey;
   }
 
   _setEventListener(element, baseName, eventName, valueGetter) {
@@ -458,6 +677,11 @@ class WidgetAnnotationElement extends AnnotationElement {
     }
   }
 
+  _setBackgroundColor(element) {
+    const color = this.data.backgroundColor || null;
+    element.style.backgroundColor = color === null ? "transparent" : _util.Util.makeHexColor(color[0], color[1], color[2]);
+  }
+
   _dispatchEventFromSandbox(actions, jsEvent) {
     const setColor = (jsName, styleName, event) => {
       const color = event.detail[jsName];
@@ -539,7 +763,7 @@ class WidgetAnnotationElement extends AnnotationElement {
 
 class TextWidgetAnnotationElement extends WidgetAnnotationElement {
   constructor(parameters) {
-    const isRenderable = parameters.renderInteractiveForms || !parameters.data.hasAppearance && !!parameters.data.fieldValue;
+    const isRenderable = parameters.renderForms || !parameters.data.hasAppearance && !!parameters.data.fieldValue;
     super(parameters, {
       isRenderable
     });
@@ -548,13 +772,14 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
   setPropertyOnSiblings(base, key, value, keyInStorage) {
     const storage = this.annotationStorage;
 
-    for (const element of document.getElementsByName(base.name)) {
-      if (element !== base) {
-        element[key] = value;
-        const data = Object.create(null);
-        data[keyInStorage] = value;
-        storage.setValue(element.getAttribute("id"), data);
+    for (const element of this._getElementsByName(base.name, base.id)) {
+      if (element.domElement) {
+        element.domElement[key] = value;
       }
+
+      storage.setValue(element.id, {
+        [keyInStorage]: value
+      });
     }
   }
 
@@ -564,7 +789,7 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
     this.container.className = "textWidgetAnnotation";
     let element = null;
 
-    if (this.renderInteractiveForms) {
+    if (this.renderForms) {
       const storedData = storage.getValue(id, {
         value: this.data.fieldValue,
         valueAsString: this.data.fieldValue
@@ -586,6 +811,10 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
         element.setAttribute("value", textContent);
       }
 
+      GetElementsByNameSet.add(element);
+      element.disabled = this.data.readOnly;
+      element.name = this.data.fieldName;
+      element.tabIndex = DEFAULT_TAB_INDEX;
       elementData.userValue = textContent;
       element.setAttribute("id", id);
       element.addEventListener("input", event => {
@@ -594,6 +823,11 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
         });
         this.setPropertyOnSiblings(element, "value", event.target.value, "value");
       });
+      element.addEventListener("resetform", event => {
+        const defaultValue = this.data.defaultFieldValue || "";
+        element.value = elementData.userValue = defaultValue;
+        delete elementData.formattedValue;
+      });
 
       let blurListener = event => {
         if (elementData.formattedValue) {
@@ -742,9 +976,6 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
         element.addEventListener("blur", blurListener);
       }
 
-      element.disabled = this.data.readOnly;
-      element.name = this.data.fieldName;
-
       if (this.data.maxLen !== null) {
         element.maxLength = this.data.maxLen;
       }
@@ -764,6 +995,8 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
 
     this._setTextStyle(element);
 
+    this._setBackgroundColor(element);
+
     this.container.appendChild(element);
     return this.container;
   }
@@ -792,7 +1025,7 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
 class CheckboxWidgetAnnotationElement extends WidgetAnnotationElement {
   constructor(parameters) {
     super(parameters, {
-      isRenderable: parameters.renderInteractiveForms
+      isRenderable: parameters.renderForms
     });
   }
 
@@ -801,7 +1034,7 @@ class CheckboxWidgetAnnotationElement extends WidgetAnnotationElement {
     const data = this.data;
     const id = data.id;
     let value = storage.getValue(id, {
-      value: data.fieldValue && (data.exportValue && data.exportValue === data.fieldValue || !data.exportValue && data.fieldValue !== "Off")
+      value: data.exportValue === data.fieldValue
     }).value;
 
     if (typeof value === "string") {
@@ -813,31 +1046,44 @@ class CheckboxWidgetAnnotationElement extends WidgetAnnotationElement {
 
     this.container.className = "buttonWidgetAnnotation checkBox";
     const element = document.createElement("input");
+    GetElementsByNameSet.add(element);
     element.disabled = data.readOnly;
     element.type = "checkbox";
-    element.name = this.data.fieldName;
+    element.name = data.fieldName;
 
     if (value) {
       element.setAttribute("checked", true);
     }
 
     element.setAttribute("id", id);
-    element.addEventListener("change", function (event) {
-      const name = event.target.name;
-
-      for (const checkbox of document.getElementsByName(name)) {
-        if (checkbox !== event.target) {
-          checkbox.checked = false;
-          storage.setValue(checkbox.parentNode.getAttribute("data-annotation-id"), {
-            value: false
-          });
+    element.setAttribute("exportValue", data.exportValue);
+    element.tabIndex = DEFAULT_TAB_INDEX;
+    element.addEventListener("change", event => {
+      const {
+        name,
+        checked
+      } = event.target;
+
+      for (const checkbox of this._getElementsByName(name, id)) {
+        const curChecked = checked && checkbox.exportValue === data.exportValue;
+
+        if (checkbox.domElement) {
+          checkbox.domElement.checked = curChecked;
         }
+
+        storage.setValue(checkbox.id, {
+          value: curChecked
+        });
       }
 
       storage.setValue(id, {
-        value: event.target.checked
+        value: checked
       });
     });
+    element.addEventListener("resetform", event => {
+      const defaultValue = data.defaultFieldValue || "Off";
+      event.target.checked = defaultValue === data.exportValue;
+    });
 
     if (this.enableScripting && this.hasJSActions) {
       element.addEventListener("updatefromsandbox", jsEvent => {
@@ -857,6 +1103,8 @@ class CheckboxWidgetAnnotationElement extends WidgetAnnotationElement {
       this._setEventListeners(element, [["change", "Validate"], ["change", "Action"], ["focus", "Focus"], ["blur", "Blur"], ["mousedown", "Mouse Down"], ["mouseenter", "Mouse Enter"], ["mouseleave", "Mouse Exit"], ["mouseup", "Mouse Up"]], event => event.target.checked);
     }
 
+    this._setBackgroundColor(element);
+
     this.container.appendChild(element);
     return this.container;
   }
@@ -866,7 +1114,7 @@ class CheckboxWidgetAnnotationElement extends WidgetAnnotationElement {
 class RadioButtonWidgetAnnotationElement extends WidgetAnnotationElement {
   constructor(parameters) {
     super(parameters, {
-      isRenderable: parameters.renderInteractiveForms
+      isRenderable: parameters.renderForms
     });
   }
 
@@ -887,6 +1135,7 @@ class RadioButtonWidgetAnnotationElement extends WidgetAnnotationElement {
     }
 
     const element = document.createElement("input");
+    GetElementsByNameSet.add(element);
     element.disabled = data.readOnly;
     element.type = "radio";
     element.name = data.fieldName;
@@ -896,40 +1145,47 @@ class RadioButtonWidgetAnnotationElement extends WidgetAnnotationElement {
     }
 
     element.setAttribute("id", id);
-    element.addEventListener("change", function (event) {
+    element.tabIndex = DEFAULT_TAB_INDEX;
+    element.addEventListener("change", event => {
       const {
-        target
-      } = event;
+        name,
+        checked
+      } = event.target;
 
-      for (const radio of document.getElementsByName(target.name)) {
-        if (radio !== target) {
-          storage.setValue(radio.getAttribute("id"), {
-            value: false
-          });
-        }
+      for (const radio of this._getElementsByName(name, id)) {
+        storage.setValue(radio.id, {
+          value: false
+        });
       }
 
       storage.setValue(id, {
-        value: target.checked
+        value: checked
       });
     });
+    element.addEventListener("resetform", event => {
+      const defaultValue = data.defaultFieldValue;
+      event.target.checked = defaultValue !== null && defaultValue !== undefined && defaultValue === data.buttonValue;
+    });
 
     if (this.enableScripting && this.hasJSActions) {
       const pdfButtonValue = data.buttonValue;
       element.addEventListener("updatefromsandbox", jsEvent => {
         const actions = {
-          value(event) {
+          value: event => {
             const checked = pdfButtonValue === event.detail.value;
 
-            for (const radio of document.getElementsByName(event.target.name)) {
-              const radioId = radio.getAttribute("id");
-              radio.checked = radioId === id && checked;
-              storage.setValue(radioId, {
-                value: radio.checked
+            for (const radio of this._getElementsByName(event.target.name)) {
+              const curChecked = checked && radio.id === id;
+
+              if (radio.domElement) {
+                radio.domElement.checked = curChecked;
+              }
+
+              storage.setValue(radio.id, {
+                value: curChecked
               });
             }
           }
-
         };
 
         this._dispatchEventFromSandbox(actions, jsEvent);
@@ -938,6 +1194,8 @@ class RadioButtonWidgetAnnotationElement extends WidgetAnnotationElement {
       this._setEventListeners(element, [["change", "Validate"], ["change", "Action"], ["focus", "Focus"], ["blur", "Blur"], ["mousedown", "Mouse Down"], ["mouseenter", "Mouse Enter"], ["mouseleave", "Mouse Exit"], ["mouseup", "Mouse Up"]], event => event.target.checked);
     }
 
+    this._setBackgroundColor(element);
+
     this.container.appendChild(element);
     return this.container;
   }
@@ -961,7 +1219,7 @@ class PushButtonWidgetAnnotationElement extends LinkAnnotationElement {
 class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
   constructor(parameters) {
     super(parameters, {
-      isRenderable: parameters.renderInteractiveForms
+      isRenderable: parameters.renderForms
     });
   }
 
@@ -972,10 +1230,22 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
     storage.getValue(id, {
       value: this.data.fieldValue.length > 0 ? this.data.fieldValue[0] : undefined
     });
+    let {
+      fontSize
+    } = this.data.defaultAppearanceData;
+
+    if (!fontSize) {
+      fontSize = 9;
+    }
+
+    const fontSizeStyle = `calc(${fontSize}px * var(--zoom-factor))`;
     const selectElement = document.createElement("select");
+    GetElementsByNameSet.add(selectElement);
     selectElement.disabled = this.data.readOnly;
     selectElement.name = this.data.fieldName;
     selectElement.setAttribute("id", id);
+    selectElement.tabIndex = DEFAULT_TAB_INDEX;
+    selectElement.style.fontSize = `${fontSize}px`;
 
     if (!this.data.combo) {
       selectElement.size = this.data.options.length;
@@ -985,11 +1255,23 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
       }
     }
 
+    selectElement.addEventListener("resetform", event => {
+      const defaultValue = this.data.defaultFieldValue;
+
+      for (const option of selectElement.options) {
+        option.selected = option.value === defaultValue;
+      }
+    });
+
     for (const option of this.data.options) {
       const optionElement = document.createElement("option");
       optionElement.textContent = option.displayValue;
       optionElement.value = option.exportValue;
 
+      if (this.data.combo) {
+        optionElement.style.fontSize = fontSizeStyle;
+      }
+
       if (this.data.fieldValue.includes(option.exportValue)) {
         optionElement.setAttribute("selected", true);
       }
@@ -1022,12 +1304,13 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
       selectElement.addEventListener("updatefromsandbox", jsEvent => {
         const actions = {
           value(event) {
-            const options = selectElement.options;
             const value = event.detail.value;
             const values = new Set(Array.isArray(value) ? value : [value]);
-            Array.prototype.forEach.call(options, option => {
+
+            for (const option of selectElement.options) {
               option.selected = values.has(option.value);
-            });
+            }
+
             storage.setValue(id, {
               value: getValue(event, true)
             });
@@ -1116,10 +1399,11 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
 
           indices(event) {
             const indices = new Set(event.detail.indices);
-            const options = event.target.options;
-            Array.prototype.forEach.call(options, (option, i) => {
-              option.selected = indices.has(i);
-            });
+
+            for (const option of event.target.options) {
+              option.selected = indices.has(option.index);
+            }
+
             storage.setValue(id, {
               value: getValue(event, true)
             });
@@ -1162,6 +1446,8 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
       });
     }
 
+    this._setBackgroundColor(selectElement);
+
     this.container.appendChild(selectElement);
     return this.container;
   }
@@ -1170,7 +1456,7 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
 
 class PopupAnnotationElement extends AnnotationElement {
   constructor(parameters) {
-    const isRenderable = !!(parameters.data.title || parameters.data.contents);
+    const isRenderable = !!(parameters.data.titleObj?.str || parameters.data.contentsObj?.str);
     super(parameters, {
       isRenderable
     });
@@ -1195,9 +1481,9 @@ class PopupAnnotationElement extends AnnotationElement {
       container: this.container,
       trigger: Array.from(parentElements),
       color: this.data.color,
-      title: this.data.title,
+      titleObj: this.data.titleObj,
       modificationDate: this.data.modificationDate,
-      contents: this.data.contents
+      contentsObj: this.data.contentsObj
     });
     const page = this.page;
 
@@ -1219,9 +1505,9 @@ class PopupElement {
     this.container = parameters.container;
     this.trigger = parameters.trigger;
     this.color = parameters.color;
-    this.title = parameters.title;
+    this.titleObj = parameters.titleObj;
     this.modificationDate = parameters.modificationDate;
-    this.contents = parameters.contents;
+    this.contentsObj = parameters.contentsObj;
     this.hideWrapper = parameters.hideWrapper || false;
     this.pinned = false;
   }
@@ -1244,7 +1530,8 @@ class PopupElement {
     }
 
     const title = document.createElement("h1");
-    title.textContent = this.title;
+    title.dir = this.titleObj.dir;
+    title.textContent = this.titleObj.str;
     popup.appendChild(title);
 
     const dateObject = _display_utils.PDFDateString.toDateObject(this.modificationDate);
@@ -1260,7 +1547,7 @@ class PopupElement {
       popup.appendChild(modificationDate);
     }
 
-    const contents = this._formatContents(this.contents);
+    const contents = this._formatContents(this.contentsObj);
 
     popup.appendChild(contents);
 
@@ -1279,9 +1566,13 @@ class PopupElement {
     return wrapper;
   }
 
-  _formatContents(contents) {
+  _formatContents({
+    str,
+    dir
+  }) {
     const p = document.createElement("p");
-    const lines = contents.split(/(?:\r\n?|\n)/);
+    p.dir = dir;
+    const lines = str.split(/(?:\r\n?|\n)/);
 
     for (let i = 0, ii = lines.length; i < ii; ++i) {
       const line = lines[i];
@@ -1329,7 +1620,7 @@ class PopupElement {
 
 class FreeTextAnnotationElement extends AnnotationElement {
   constructor(parameters) {
-    const isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
+    const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str);
     super(parameters, {
       isRenderable,
       ignoreBorder: true
@@ -1350,7 +1641,7 @@ class FreeTextAnnotationElement extends AnnotationElement {
 
 class LineAnnotationElement extends AnnotationElement {
   constructor(parameters) {
-    const isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
+    const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str);
     super(parameters, {
       isRenderable,
       ignoreBorder: true
@@ -1382,7 +1673,7 @@ class LineAnnotationElement extends AnnotationElement {
 
 class SquareAnnotationElement extends AnnotationElement {
   constructor(parameters) {
-    const isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
+    const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str);
     super(parameters, {
       isRenderable,
       ignoreBorder: true
@@ -1416,7 +1707,7 @@ class SquareAnnotationElement extends AnnotationElement {
 
 class CircleAnnotationElement extends AnnotationElement {
   constructor(parameters) {
-    const isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
+    const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str);
     super(parameters, {
       isRenderable,
       ignoreBorder: true
@@ -1450,7 +1741,7 @@ class CircleAnnotationElement extends AnnotationElement {
 
 class PolylineAnnotationElement extends AnnotationElement {
   constructor(parameters) {
-    const isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
+    const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str);
     super(parameters, {
       isRenderable,
       ignoreBorder: true
@@ -1500,7 +1791,7 @@ class PolygonAnnotationElement extends PolylineAnnotationElement {
 
 class CaretAnnotationElement extends AnnotationElement {
   constructor(parameters) {
-    const isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
+    const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str);
     super(parameters, {
       isRenderable,
       ignoreBorder: true
@@ -1521,7 +1812,7 @@ class CaretAnnotationElement extends AnnotationElement {
 
 class InkAnnotationElement extends AnnotationElement {
   constructor(parameters) {
-    const isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
+    const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str);
     super(parameters, {
       isRenderable,
       ignoreBorder: true
@@ -1566,7 +1857,7 @@ class InkAnnotationElement extends AnnotationElement {
 
 class HighlightAnnotationElement extends AnnotationElement {
   constructor(parameters) {
-    const isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
+    const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str);
     super(parameters, {
       isRenderable,
       ignoreBorder: true,
@@ -1591,7 +1882,7 @@ class HighlightAnnotationElement extends AnnotationElement {
 
 class UnderlineAnnotationElement extends AnnotationElement {
   constructor(parameters) {
-    const isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
+    const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str);
     super(parameters, {
       isRenderable,
       ignoreBorder: true,
@@ -1616,7 +1907,7 @@ class UnderlineAnnotationElement extends AnnotationElement {
 
 class SquigglyAnnotationElement extends AnnotationElement {
   constructor(parameters) {
-    const isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
+    const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str);
     super(parameters, {
       isRenderable,
       ignoreBorder: true,
@@ -1641,7 +1932,7 @@ class SquigglyAnnotationElement extends AnnotationElement {
 
 class StrikeOutAnnotationElement extends AnnotationElement {
   constructor(parameters) {
-    const isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
+    const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str);
     super(parameters, {
       isRenderable,
       ignoreBorder: true,
@@ -1666,7 +1957,7 @@ class StrikeOutAnnotationElement extends AnnotationElement {
 
 class StampAnnotationElement extends AnnotationElement {
   constructor(parameters) {
-    const isRenderable = !!(parameters.data.hasPopup || parameters.data.title || parameters.data.contents);
+    const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str);
     super(parameters, {
       isRenderable,
       ignoreBorder: true
@@ -1711,7 +2002,7 @@ class FileAttachmentAnnotationElement extends AnnotationElement {
     trigger.style.width = this.container.style.width;
     trigger.addEventListener("dblclick", this._download.bind(this));
 
-    if (!this.data.hasPopup && (this.data.title || this.data.contents)) {
+    if (!this.data.hasPopup && (this.data.titleObj?.str || this.data.contentsObj?.str)) {
       this._createPopup(trigger, this.data);
     }
 
@@ -1756,11 +2047,12 @@ class AnnotationLayer {
         linkService: parameters.linkService,
         downloadManager: parameters.downloadManager,
         imageResourcesPath: parameters.imageResourcesPath || "",
-        renderInteractiveForms: parameters.renderInteractiveForms !== false,
+        renderForms: parameters.renderForms !== false,
         svgFactory: new _display_utils.DOMSVGFactory(),
         annotationStorage: parameters.annotationStorage || new _annotation_storage.AnnotationStorage(),
         enableScripting: parameters.enableScripting,
         hasJSActions: parameters.hasJSActions,
+        fieldObjects: parameters.fieldObjects,
         mouseState: parameters.mouseState || {
           isDown: false
         }

+ 15 - 4
lib/display/annotation_storage.js

@@ -31,15 +31,20 @@ var _util = require("../shared/util.js");
 class AnnotationStorage {
   constructor() {
     this._storage = new Map();
+    this._timeStamp = Date.now();
     this._modified = false;
     this.onSetModified = null;
     this.onResetModified = null;
   }
 
   getValue(key, defaultValue) {
-    const obj = this._storage.get(key);
+    const value = this._storage.get(key);
+
+    if (value === undefined) {
+      return defaultValue;
+    }
 
-    return obj !== undefined ? obj : defaultValue;
+    return Object.assign(defaultValue, value);
   }
 
   setValue(key, value) {
@@ -55,12 +60,14 @@ class AnnotationStorage {
         }
       }
     } else {
-      this._storage.set(key, value);
-
       modified = true;
+
+      this._storage.set(key, value);
     }
 
     if (modified) {
+      this._timeStamp = Date.now();
+
       this._setModified();
     }
   }
@@ -97,6 +104,10 @@ class AnnotationStorage {
     return this._storage.size > 0 ? this._storage : null;
   }
 
+  get lastModified() {
+    return this._timeStamp.toString();
+  }
+
 }
 
 exports.AnnotationStorage = AnnotationStorage;

Файловите разлики са ограничени, защото са твърде много
+ 406 - 310
lib/display/api.js


+ 1609 - 1547
lib/display/canvas.js

@@ -30,9 +30,14 @@ var _util = require("../shared/util.js");
 
 var _pattern_helper = require("./pattern_helper.js");
 
+var _display_utils = require("./display_utils.js");
+
 const MIN_FONT_SIZE = 16;
 const MAX_FONT_SIZE = 100;
 const MAX_GROUP_SIZE = 4096;
+const MAX_CACHED_CANVAS_PATTERNS = 2;
+const EXECUTION_TIME = 15;
+const EXECUTION_STEPS = 10;
 const COMPILE_TYPE3_GLYPHS = true;
 const MAX_SIZE_TO_COMPILE = 1000;
 const FULL_CHUNK_HEIGHT = 16;
@@ -184,6 +189,46 @@ class CachedCanvases {
 
 }
 
+class LRUCache {
+  constructor(maxSize = 0) {
+    this._cache = new Map();
+    this._maxSize = maxSize;
+  }
+
+  has(key) {
+    return this._cache.has(key);
+  }
+
+  get(key) {
+    if (this._cache.has(key)) {
+      const value = this._cache.get(key);
+
+      this._cache.delete(key);
+
+      this._cache.set(key, value);
+    }
+
+    return this._cache.get(key);
+  }
+
+  set(key, value) {
+    if (this._maxSize <= 0) {
+      return;
+    }
+
+    if (this._cache.size + 1 > this._maxSize) {
+      this._cache.delete(this._cache.keys().next().value);
+    }
+
+    this._cache.set(key, value);
+  }
+
+  clear() {
+    this._cache.clear();
+  }
+
+}
+
 function compileType3Glyph(imgData) {
   const POINT_TO_PROCESS_LIMIT = 1000;
   const POINT_TYPES = new Uint8Array([0, 2, 4, 0, 1, 0, 5, 4, 8, 10, 0, 8, 0, 2, 1, 0]);
@@ -397,1989 +442,2006 @@ class CanvasExtraState {
 
 }
 
-const CanvasGraphics = function CanvasGraphicsClosure() {
-  const EXECUTION_TIME = 15;
-  const EXECUTION_STEPS = 10;
-
-  function putBinaryImageData(ctx, imgData, transferMaps = null) {
-    if (typeof ImageData !== "undefined" && imgData instanceof ImageData) {
-      ctx.putImageData(imgData, 0, 0);
-      return;
-    }
-
-    const height = imgData.height,
-          width = imgData.width;
-    const partialChunkHeight = height % FULL_CHUNK_HEIGHT;
-    const fullChunks = (height - partialChunkHeight) / FULL_CHUNK_HEIGHT;
-    const totalChunks = partialChunkHeight === 0 ? fullChunks : fullChunks + 1;
-    const chunkImgData = ctx.createImageData(width, FULL_CHUNK_HEIGHT);
-    let srcPos = 0,
-        destPos;
-    const src = imgData.data;
-    const dest = chunkImgData.data;
-    let i, j, thisChunkHeight, elemsInThisChunk;
-    let transferMapRed, transferMapGreen, transferMapBlue, transferMapGray;
-
-    if (transferMaps) {
-      switch (transferMaps.length) {
-        case 1:
-          transferMapRed = transferMaps[0];
-          transferMapGreen = transferMaps[0];
-          transferMapBlue = transferMaps[0];
-          transferMapGray = transferMaps[0];
-          break;
+function putBinaryImageData(ctx, imgData, transferMaps = null) {
+  if (typeof ImageData !== "undefined" && imgData instanceof ImageData) {
+    ctx.putImageData(imgData, 0, 0);
+    return;
+  }
 
-        case 4:
-          transferMapRed = transferMaps[0];
-          transferMapGreen = transferMaps[1];
-          transferMapBlue = transferMaps[2];
-          transferMapGray = transferMaps[3];
-          break;
-      }
+  const height = imgData.height,
+        width = imgData.width;
+  const partialChunkHeight = height % FULL_CHUNK_HEIGHT;
+  const fullChunks = (height - partialChunkHeight) / FULL_CHUNK_HEIGHT;
+  const totalChunks = partialChunkHeight === 0 ? fullChunks : fullChunks + 1;
+  const chunkImgData = ctx.createImageData(width, FULL_CHUNK_HEIGHT);
+  let srcPos = 0,
+      destPos;
+  const src = imgData.data;
+  const dest = chunkImgData.data;
+  let i, j, thisChunkHeight, elemsInThisChunk;
+  let transferMapRed, transferMapGreen, transferMapBlue, transferMapGray;
+
+  if (transferMaps) {
+    switch (transferMaps.length) {
+      case 1:
+        transferMapRed = transferMaps[0];
+        transferMapGreen = transferMaps[0];
+        transferMapBlue = transferMaps[0];
+        transferMapGray = transferMaps[0];
+        break;
+
+      case 4:
+        transferMapRed = transferMaps[0];
+        transferMapGreen = transferMaps[1];
+        transferMapBlue = transferMaps[2];
+        transferMapGray = transferMaps[3];
+        break;
     }
+  }
 
-    if (imgData.kind === _util.ImageKind.GRAYSCALE_1BPP) {
-      const srcLength = src.byteLength;
-      const dest32 = new Uint32Array(dest.buffer, 0, dest.byteLength >> 2);
-      const dest32DataLength = dest32.length;
-      const fullSrcDiff = width + 7 >> 3;
-      let white = 0xffffffff;
-      let black = _util.IsLittleEndianCached.value ? 0xff000000 : 0x000000ff;
-
-      if (transferMapGray) {
-        if (transferMapGray[0] === 0xff && transferMapGray[0xff] === 0) {
-          [white, black] = [black, white];
+  if (imgData.kind === _util.ImageKind.GRAYSCALE_1BPP) {
+    const srcLength = src.byteLength;
+    const dest32 = new Uint32Array(dest.buffer, 0, dest.byteLength >> 2);
+    const dest32DataLength = dest32.length;
+    const fullSrcDiff = width + 7 >> 3;
+    let white = 0xffffffff;
+    let black = _util.IsLittleEndianCached.value ? 0xff000000 : 0x000000ff;
+
+    if (transferMapGray) {
+      if (transferMapGray[0] === 0xff && transferMapGray[0xff] === 0) {
+        [white, black] = [black, white];
+      }
+    }
+
+    for (i = 0; i < totalChunks; i++) {
+      thisChunkHeight = i < fullChunks ? FULL_CHUNK_HEIGHT : partialChunkHeight;
+      destPos = 0;
+
+      for (j = 0; j < thisChunkHeight; j++) {
+        const srcDiff = srcLength - srcPos;
+        let k = 0;
+        const kEnd = srcDiff > fullSrcDiff ? width : srcDiff * 8 - 7;
+        const kEndUnrolled = kEnd & ~7;
+        let mask = 0;
+        let srcByte = 0;
+
+        for (; k < kEndUnrolled; k += 8) {
+          srcByte = src[srcPos++];
+          dest32[destPos++] = srcByte & 128 ? white : black;
+          dest32[destPos++] = srcByte & 64 ? white : black;
+          dest32[destPos++] = srcByte & 32 ? white : black;
+          dest32[destPos++] = srcByte & 16 ? white : black;
+          dest32[destPos++] = srcByte & 8 ? white : black;
+          dest32[destPos++] = srcByte & 4 ? white : black;
+          dest32[destPos++] = srcByte & 2 ? white : black;
+          dest32[destPos++] = srcByte & 1 ? white : black;
         }
-      }
-
-      for (i = 0; i < totalChunks; i++) {
-        thisChunkHeight = i < fullChunks ? FULL_CHUNK_HEIGHT : partialChunkHeight;
-        destPos = 0;
 
-        for (j = 0; j < thisChunkHeight; j++) {
-          const srcDiff = srcLength - srcPos;
-          let k = 0;
-          const kEnd = srcDiff > fullSrcDiff ? width : srcDiff * 8 - 7;
-          const kEndUnrolled = kEnd & ~7;
-          let mask = 0;
-          let srcByte = 0;
-
-          for (; k < kEndUnrolled; k += 8) {
+        for (; k < kEnd; k++) {
+          if (mask === 0) {
             srcByte = src[srcPos++];
-            dest32[destPos++] = srcByte & 128 ? white : black;
-            dest32[destPos++] = srcByte & 64 ? white : black;
-            dest32[destPos++] = srcByte & 32 ? white : black;
-            dest32[destPos++] = srcByte & 16 ? white : black;
-            dest32[destPos++] = srcByte & 8 ? white : black;
-            dest32[destPos++] = srcByte & 4 ? white : black;
-            dest32[destPos++] = srcByte & 2 ? white : black;
-            dest32[destPos++] = srcByte & 1 ? white : black;
-          }
-
-          for (; k < kEnd; k++) {
-            if (mask === 0) {
-              srcByte = src[srcPos++];
-              mask = 128;
-            }
-
-            dest32[destPos++] = srcByte & mask ? white : black;
-            mask >>= 1;
+            mask = 128;
           }
-        }
 
-        while (destPos < dest32DataLength) {
-          dest32[destPos++] = 0;
+          dest32[destPos++] = srcByte & mask ? white : black;
+          mask >>= 1;
         }
-
-        ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT);
       }
-    } else if (imgData.kind === _util.ImageKind.RGBA_32BPP) {
-      const hasTransferMaps = !!(transferMapRed || transferMapGreen || transferMapBlue);
-      j = 0;
-      elemsInThisChunk = width * FULL_CHUNK_HEIGHT * 4;
-
-      for (i = 0; i < fullChunks; i++) {
-        dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk));
-        srcPos += elemsInThisChunk;
-
-        if (hasTransferMaps) {
-          for (let k = 0; k < elemsInThisChunk; k += 4) {
-            if (transferMapRed) {
-              dest[k + 0] = transferMapRed[dest[k + 0]];
-            }
-
-            if (transferMapGreen) {
-              dest[k + 1] = transferMapGreen[dest[k + 1]];
-            }
 
-            if (transferMapBlue) {
-              dest[k + 2] = transferMapBlue[dest[k + 2]];
-            }
-          }
-        }
-
-        ctx.putImageData(chunkImgData, 0, j);
-        j += FULL_CHUNK_HEIGHT;
+      while (destPos < dest32DataLength) {
+        dest32[destPos++] = 0;
       }
 
-      if (i < totalChunks) {
-        elemsInThisChunk = width * partialChunkHeight * 4;
-        dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk));
+      ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT);
+    }
+  } else if (imgData.kind === _util.ImageKind.RGBA_32BPP) {
+    const hasTransferMaps = !!(transferMapRed || transferMapGreen || transferMapBlue);
+    j = 0;
+    elemsInThisChunk = width * FULL_CHUNK_HEIGHT * 4;
 
-        if (hasTransferMaps) {
-          for (let k = 0; k < elemsInThisChunk; k += 4) {
-            if (transferMapRed) {
-              dest[k + 0] = transferMapRed[dest[k + 0]];
-            }
+    for (i = 0; i < fullChunks; i++) {
+      dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk));
+      srcPos += elemsInThisChunk;
 
-            if (transferMapGreen) {
-              dest[k + 1] = transferMapGreen[dest[k + 1]];
-            }
+      if (hasTransferMaps) {
+        for (let k = 0; k < elemsInThisChunk; k += 4) {
+          if (transferMapRed) {
+            dest[k + 0] = transferMapRed[dest[k + 0]];
+          }
 
-            if (transferMapBlue) {
-              dest[k + 2] = transferMapBlue[dest[k + 2]];
-            }
+          if (transferMapGreen) {
+            dest[k + 1] = transferMapGreen[dest[k + 1]];
           }
-        }
 
-        ctx.putImageData(chunkImgData, 0, j);
-      }
-    } else if (imgData.kind === _util.ImageKind.RGB_24BPP) {
-      const hasTransferMaps = !!(transferMapRed || transferMapGreen || transferMapBlue);
-      thisChunkHeight = FULL_CHUNK_HEIGHT;
-      elemsInThisChunk = width * thisChunkHeight;
-
-      for (i = 0; i < totalChunks; i++) {
-        if (i >= fullChunks) {
-          thisChunkHeight = partialChunkHeight;
-          elemsInThisChunk = width * thisChunkHeight;
+          if (transferMapBlue) {
+            dest[k + 2] = transferMapBlue[dest[k + 2]];
+          }
         }
+      }
 
-        destPos = 0;
+      ctx.putImageData(chunkImgData, 0, j);
+      j += FULL_CHUNK_HEIGHT;
+    }
 
-        for (j = elemsInThisChunk; j--;) {
-          dest[destPos++] = src[srcPos++];
-          dest[destPos++] = src[srcPos++];
-          dest[destPos++] = src[srcPos++];
-          dest[destPos++] = 255;
-        }
+    if (i < totalChunks) {
+      elemsInThisChunk = width * partialChunkHeight * 4;
+      dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk));
 
-        if (hasTransferMaps) {
-          for (let k = 0; k < destPos; k += 4) {
-            if (transferMapRed) {
-              dest[k + 0] = transferMapRed[dest[k + 0]];
-            }
+      if (hasTransferMaps) {
+        for (let k = 0; k < elemsInThisChunk; k += 4) {
+          if (transferMapRed) {
+            dest[k + 0] = transferMapRed[dest[k + 0]];
+          }
 
-            if (transferMapGreen) {
-              dest[k + 1] = transferMapGreen[dest[k + 1]];
-            }
+          if (transferMapGreen) {
+            dest[k + 1] = transferMapGreen[dest[k + 1]];
+          }
 
-            if (transferMapBlue) {
-              dest[k + 2] = transferMapBlue[dest[k + 2]];
-            }
+          if (transferMapBlue) {
+            dest[k + 2] = transferMapBlue[dest[k + 2]];
           }
         }
-
-        ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT);
       }
-    } else {
-      throw new Error(`bad image kind: ${imgData.kind}`);
+
+      ctx.putImageData(chunkImgData, 0, j);
     }
-  }
+  } else if (imgData.kind === _util.ImageKind.RGB_24BPP) {
+    const hasTransferMaps = !!(transferMapRed || transferMapGreen || transferMapBlue);
+    thisChunkHeight = FULL_CHUNK_HEIGHT;
+    elemsInThisChunk = width * thisChunkHeight;
 
-  function putBinaryImageMask(ctx, imgData) {
-    const height = imgData.height,
-          width = imgData.width;
-    const partialChunkHeight = height % FULL_CHUNK_HEIGHT;
-    const fullChunks = (height - partialChunkHeight) / FULL_CHUNK_HEIGHT;
-    const totalChunks = partialChunkHeight === 0 ? fullChunks : fullChunks + 1;
-    const chunkImgData = ctx.createImageData(width, FULL_CHUNK_HEIGHT);
-    let srcPos = 0;
-    const src = imgData.data;
-    const dest = chunkImgData.data;
+    for (i = 0; i < totalChunks; i++) {
+      if (i >= fullChunks) {
+        thisChunkHeight = partialChunkHeight;
+        elemsInThisChunk = width * thisChunkHeight;
+      }
 
-    for (let i = 0; i < totalChunks; i++) {
-      const thisChunkHeight = i < fullChunks ? FULL_CHUNK_HEIGHT : partialChunkHeight;
-      let destPos = 3;
+      destPos = 0;
 
-      for (let j = 0; j < thisChunkHeight; j++) {
-        let elem,
-            mask = 0;
+      for (j = elemsInThisChunk; j--;) {
+        dest[destPos++] = src[srcPos++];
+        dest[destPos++] = src[srcPos++];
+        dest[destPos++] = src[srcPos++];
+        dest[destPos++] = 255;
+      }
 
-        for (let k = 0; k < width; k++) {
-          if (!mask) {
-            elem = src[srcPos++];
-            mask = 128;
+      if (hasTransferMaps) {
+        for (let k = 0; k < destPos; k += 4) {
+          if (transferMapRed) {
+            dest[k + 0] = transferMapRed[dest[k + 0]];
           }
 
-          dest[destPos] = elem & mask ? 0 : 255;
-          destPos += 4;
-          mask >>= 1;
+          if (transferMapGreen) {
+            dest[k + 1] = transferMapGreen[dest[k + 1]];
+          }
+
+          if (transferMapBlue) {
+            dest[k + 2] = transferMapBlue[dest[k + 2]];
+          }
         }
       }
 
       ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT);
     }
+  } else {
+    throw new Error(`bad image kind: ${imgData.kind}`);
   }
+}
 
-  function copyCtxState(sourceCtx, destCtx) {
-    const properties = ["strokeStyle", "fillStyle", "fillRule", "globalAlpha", "lineWidth", "lineCap", "lineJoin", "miterLimit", "globalCompositeOperation", "font"];
-
-    for (let i = 0, ii = properties.length; i < ii; i++) {
-      const property = properties[i];
+function putBinaryImageMask(ctx, imgData) {
+  const height = imgData.height,
+        width = imgData.width;
+  const partialChunkHeight = height % FULL_CHUNK_HEIGHT;
+  const fullChunks = (height - partialChunkHeight) / FULL_CHUNK_HEIGHT;
+  const totalChunks = partialChunkHeight === 0 ? fullChunks : fullChunks + 1;
+  const chunkImgData = ctx.createImageData(width, FULL_CHUNK_HEIGHT);
+  let srcPos = 0;
+  const src = imgData.data;
+  const dest = chunkImgData.data;
+
+  for (let i = 0; i < totalChunks; i++) {
+    const thisChunkHeight = i < fullChunks ? FULL_CHUNK_HEIGHT : partialChunkHeight;
+    let destPos = 3;
+
+    for (let j = 0; j < thisChunkHeight; j++) {
+      let elem,
+          mask = 0;
+
+      for (let k = 0; k < width; k++) {
+        if (!mask) {
+          elem = src[srcPos++];
+          mask = 128;
+        }
 
-      if (sourceCtx[property] !== undefined) {
-        destCtx[property] = sourceCtx[property];
+        dest[destPos] = elem & mask ? 0 : 255;
+        destPos += 4;
+        mask >>= 1;
       }
     }
 
-    if (sourceCtx.setLineDash !== undefined) {
-      destCtx.setLineDash(sourceCtx.getLineDash());
-      destCtx.lineDashOffset = sourceCtx.lineDashOffset;
-    }
+    ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT);
   }
+}
 
-  function resetCtxToDefault(ctx) {
-    ctx.strokeStyle = "#000000";
-    ctx.fillStyle = "#000000";
-    ctx.fillRule = "nonzero";
-    ctx.globalAlpha = 1;
-    ctx.lineWidth = 1;
-    ctx.lineCap = "butt";
-    ctx.lineJoin = "miter";
-    ctx.miterLimit = 10;
-    ctx.globalCompositeOperation = "source-over";
-    ctx.font = "10px sans-serif";
+function copyCtxState(sourceCtx, destCtx) {
+  const properties = ["strokeStyle", "fillStyle", "fillRule", "globalAlpha", "lineWidth", "lineCap", "lineJoin", "miterLimit", "globalCompositeOperation", "font"];
 
-    if (ctx.setLineDash !== undefined) {
-      ctx.setLineDash([]);
-      ctx.lineDashOffset = 0;
+  for (let i = 0, ii = properties.length; i < ii; i++) {
+    const property = properties[i];
+
+    if (sourceCtx[property] !== undefined) {
+      destCtx[property] = sourceCtx[property];
     }
   }
 
-  function composeSMaskBackdrop(bytes, r0, g0, b0) {
-    const length = bytes.length;
-
-    for (let i = 3; i < length; i += 4) {
-      const alpha = bytes[i];
+  if (sourceCtx.setLineDash !== undefined) {
+    destCtx.setLineDash(sourceCtx.getLineDash());
+    destCtx.lineDashOffset = sourceCtx.lineDashOffset;
+  }
+}
 
-      if (alpha === 0) {
-        bytes[i - 3] = r0;
-        bytes[i - 2] = g0;
-        bytes[i - 1] = b0;
-      } else if (alpha < 255) {
-        const alpha_ = 255 - alpha;
-        bytes[i - 3] = bytes[i - 3] * alpha + r0 * alpha_ >> 8;
-        bytes[i - 2] = bytes[i - 2] * alpha + g0 * alpha_ >> 8;
-        bytes[i - 1] = bytes[i - 1] * alpha + b0 * alpha_ >> 8;
-      }
-    }
+function resetCtxToDefault(ctx) {
+  ctx.strokeStyle = "#000000";
+  ctx.fillStyle = "#000000";
+  ctx.fillRule = "nonzero";
+  ctx.globalAlpha = 1;
+  ctx.lineWidth = 1;
+  ctx.lineCap = "butt";
+  ctx.lineJoin = "miter";
+  ctx.miterLimit = 10;
+  ctx.globalCompositeOperation = "source-over";
+  ctx.font = "10px sans-serif";
+
+  if (ctx.setLineDash !== undefined) {
+    ctx.setLineDash([]);
+    ctx.lineDashOffset = 0;
   }
+}
+
+function composeSMaskBackdrop(bytes, r0, g0, b0) {
+  const length = bytes.length;
 
-  function composeSMaskAlpha(maskData, layerData, transferMap) {
-    const length = maskData.length;
-    const scale = 1 / 255;
+  for (let i = 3; i < length; i += 4) {
+    const alpha = bytes[i];
 
-    for (let i = 3; i < length; i += 4) {
-      const alpha = transferMap ? transferMap[maskData[i]] : maskData[i];
-      layerData[i] = layerData[i] * alpha * scale | 0;
+    if (alpha === 0) {
+      bytes[i - 3] = r0;
+      bytes[i - 2] = g0;
+      bytes[i - 1] = b0;
+    } else if (alpha < 255) {
+      const alpha_ = 255 - alpha;
+      bytes[i - 3] = bytes[i - 3] * alpha + r0 * alpha_ >> 8;
+      bytes[i - 2] = bytes[i - 2] * alpha + g0 * alpha_ >> 8;
+      bytes[i - 1] = bytes[i - 1] * alpha + b0 * alpha_ >> 8;
     }
   }
+}
 
-  function composeSMaskLuminosity(maskData, layerData, transferMap) {
-    const length = maskData.length;
+function composeSMaskAlpha(maskData, layerData, transferMap) {
+  const length = maskData.length;
+  const scale = 1 / 255;
 
-    for (let i = 3; i < length; i += 4) {
-      const y = maskData[i - 3] * 77 + maskData[i - 2] * 152 + maskData[i - 1] * 28;
-      layerData[i] = transferMap ? layerData[i] * transferMap[y >> 8] >> 8 : layerData[i] * y >> 16;
-    }
+  for (let i = 3; i < length; i += 4) {
+    const alpha = transferMap ? transferMap[maskData[i]] : maskData[i];
+    layerData[i] = layerData[i] * alpha * scale | 0;
   }
+}
 
-  function genericComposeSMask(maskCtx, layerCtx, width, height, subtype, backdrop, transferMap) {
-    const hasBackdrop = !!backdrop;
-    const r0 = hasBackdrop ? backdrop[0] : 0;
-    const g0 = hasBackdrop ? backdrop[1] : 0;
-    const b0 = hasBackdrop ? backdrop[2] : 0;
-    let composeFn;
+function composeSMaskLuminosity(maskData, layerData, transferMap) {
+  const length = maskData.length;
 
-    if (subtype === "Luminosity") {
-      composeFn = composeSMaskLuminosity;
-    } else {
-      composeFn = composeSMaskAlpha;
-    }
+  for (let i = 3; i < length; i += 4) {
+    const y = maskData[i - 3] * 77 + maskData[i - 2] * 152 + maskData[i - 1] * 28;
+    layerData[i] = transferMap ? layerData[i] * transferMap[y >> 8] >> 8 : layerData[i] * y >> 16;
+  }
+}
 
-    const PIXELS_TO_PROCESS = 1048576;
-    const chunkSize = Math.min(height, Math.ceil(PIXELS_TO_PROCESS / width));
+function genericComposeSMask(maskCtx, layerCtx, width, height, subtype, backdrop, transferMap) {
+  const hasBackdrop = !!backdrop;
+  const r0 = hasBackdrop ? backdrop[0] : 0;
+  const g0 = hasBackdrop ? backdrop[1] : 0;
+  const b0 = hasBackdrop ? backdrop[2] : 0;
+  let composeFn;
+
+  if (subtype === "Luminosity") {
+    composeFn = composeSMaskLuminosity;
+  } else {
+    composeFn = composeSMaskAlpha;
+  }
 
-    for (let row = 0; row < height; row += chunkSize) {
-      const chunkHeight = Math.min(chunkSize, height - row);
-      const maskData = maskCtx.getImageData(0, row, width, chunkHeight);
-      const layerData = layerCtx.getImageData(0, row, width, chunkHeight);
+  const PIXELS_TO_PROCESS = 1048576;
+  const chunkSize = Math.min(height, Math.ceil(PIXELS_TO_PROCESS / width));
 
-      if (hasBackdrop) {
-        composeSMaskBackdrop(maskData.data, r0, g0, b0);
-      }
+  for (let row = 0; row < height; row += chunkSize) {
+    const chunkHeight = Math.min(chunkSize, height - row);
+    const maskData = maskCtx.getImageData(0, row, width, chunkHeight);
+    const layerData = layerCtx.getImageData(0, row, width, chunkHeight);
 
-      composeFn(maskData.data, layerData.data, transferMap);
-      maskCtx.putImageData(layerData, 0, row);
+    if (hasBackdrop) {
+      composeSMaskBackdrop(maskData.data, r0, g0, b0);
     }
+
+    composeFn(maskData.data, layerData.data, transferMap);
+    maskCtx.putImageData(layerData, 0, row);
   }
+}
+
+function composeSMask(ctx, smask, layerCtx) {
+  const mask = smask.canvas;
+  const maskCtx = smask.context;
+  ctx.setTransform(smask.scaleX, 0, 0, smask.scaleY, smask.offsetX, smask.offsetY);
+  genericComposeSMask(maskCtx, layerCtx, mask.width, mask.height, smask.subtype, smask.backdrop, smask.transferMap);
+  ctx.drawImage(mask, 0, 0);
+}
+
+function getImageSmoothingEnabled(transform, interpolate) {
+  const scale = _util.Util.singularValueDecompose2dScale(transform);
 
-  function composeSMask(ctx, smask, layerCtx) {
-    const mask = smask.canvas;
-    const maskCtx = smask.context;
-    ctx.setTransform(smask.scaleX, 0, 0, smask.scaleY, smask.offsetX, smask.offsetY);
-    genericComposeSMask(maskCtx, layerCtx, mask.width, mask.height, smask.subtype, smask.backdrop, smask.transferMap);
-    ctx.drawImage(mask, 0, 0);
+  scale[0] = Math.fround(scale[0]);
+  scale[1] = Math.fround(scale[1]);
+  const actualScale = Math.fround((globalThis.devicePixelRatio || 1) * _display_utils.PixelsPerInch.PDF_TO_CSS_UNITS);
+
+  if (interpolate !== undefined) {
+    return interpolate;
+  } else if (scale[0] <= actualScale || scale[1] <= actualScale) {
+    return true;
   }
 
-  const LINE_CAP_STYLES = ["butt", "round", "square"];
-  const LINE_JOIN_STYLES = ["miter", "round", "bevel"];
-  const NORMAL_CLIP = {};
-  const EO_CLIP = {};
+  return false;
+}
 
-  class CanvasGraphics {
-    constructor(canvasCtx, commonObjs, objs, canvasFactory, imageLayer, optionalContentConfig) {
-      this.ctx = canvasCtx;
-      this.current = new CanvasExtraState();
-      this.stateStack = [];
-      this.pendingClip = null;
-      this.pendingEOFill = false;
-      this.res = null;
-      this.xobjs = null;
-      this.commonObjs = commonObjs;
-      this.objs = objs;
-      this.canvasFactory = canvasFactory;
-      this.imageLayer = imageLayer;
-      this.groupStack = [];
-      this.processingType3 = null;
-      this.baseTransform = null;
-      this.baseTransformStack = [];
-      this.groupLevel = 0;
-      this.smaskStack = [];
-      this.smaskCounter = 0;
-      this.tempSMask = null;
-      this.contentVisible = true;
-      this.markedContentStack = [];
-      this.optionalContentConfig = optionalContentConfig;
-      this.cachedCanvases = new CachedCanvases(this.canvasFactory);
-      this.cachedPatterns = new Map();
-
-      if (canvasCtx) {
-        addContextCurrentTransform(canvasCtx);
-      }
+const LINE_CAP_STYLES = ["butt", "round", "square"];
+const LINE_JOIN_STYLES = ["miter", "round", "bevel"];
+const NORMAL_CLIP = {};
+const EO_CLIP = {};
+
+class CanvasGraphics {
+  constructor(canvasCtx, commonObjs, objs, canvasFactory, imageLayer, optionalContentConfig) {
+    this.ctx = canvasCtx;
+    this.current = new CanvasExtraState();
+    this.stateStack = [];
+    this.pendingClip = null;
+    this.pendingEOFill = false;
+    this.res = null;
+    this.xobjs = null;
+    this.commonObjs = commonObjs;
+    this.objs = objs;
+    this.canvasFactory = canvasFactory;
+    this.imageLayer = imageLayer;
+    this.groupStack = [];
+    this.processingType3 = null;
+    this.baseTransform = null;
+    this.baseTransformStack = [];
+    this.groupLevel = 0;
+    this.smaskStack = [];
+    this.smaskCounter = 0;
+    this.tempSMask = null;
+    this.contentVisible = true;
+    this.markedContentStack = [];
+    this.optionalContentConfig = optionalContentConfig;
+    this.cachedCanvases = new CachedCanvases(this.canvasFactory);
+    this.cachedCanvasPatterns = new LRUCache(MAX_CACHED_CANVAS_PATTERNS);
+    this.cachedPatterns = new Map();
+
+    if (canvasCtx) {
+      addContextCurrentTransform(canvasCtx);
+    }
+
+    this._cachedGetSinglePixelWidth = null;
+  }
 
-      this._cachedGetSinglePixelWidth = null;
+  beginDrawing({
+    transform,
+    viewport,
+    transparency = false,
+    background = null
+  }) {
+    const width = this.ctx.canvas.width;
+    const height = this.ctx.canvas.height;
+    this.ctx.save();
+    this.ctx.fillStyle = background || "rgb(255, 255, 255)";
+    this.ctx.fillRect(0, 0, width, height);
+    this.ctx.restore();
+
+    if (transparency) {
+      const transparentCanvas = this.cachedCanvases.getCanvas("transparent", width, height, true);
+      this.compositeCtx = this.ctx;
+      this.transparentCanvas = transparentCanvas.canvas;
+      this.ctx = transparentCanvas.context;
+      this.ctx.save();
+      this.ctx.transform.apply(this.ctx, this.compositeCtx.mozCurrentTransform);
     }
 
-    beginDrawing({
-      transform,
-      viewport,
-      transparency = false,
-      background = null
-    }) {
-      const width = this.ctx.canvas.width;
-      const height = this.ctx.canvas.height;
-      this.ctx.save();
-      this.ctx.fillStyle = background || "rgb(255, 255, 255)";
-      this.ctx.fillRect(0, 0, width, height);
-      this.ctx.restore();
+    this.ctx.save();
+    resetCtxToDefault(this.ctx);
 
-      if (transparency) {
-        const transparentCanvas = this.cachedCanvases.getCanvas("transparent", width, height, true);
-        this.compositeCtx = this.ctx;
-        this.transparentCanvas = transparentCanvas.canvas;
-        this.ctx = transparentCanvas.context;
-        this.ctx.save();
-        this.ctx.transform.apply(this.ctx, this.compositeCtx.mozCurrentTransform);
-      }
+    if (transform) {
+      this.ctx.transform.apply(this.ctx, transform);
+    }
 
-      this.ctx.save();
-      resetCtxToDefault(this.ctx);
+    this.ctx.transform.apply(this.ctx, viewport.transform);
+    this.baseTransform = this.ctx.mozCurrentTransform.slice();
+    this._combinedScaleFactor = Math.hypot(this.baseTransform[0], this.baseTransform[2]);
 
-      if (transform) {
-        this.ctx.transform.apply(this.ctx, transform);
-      }
+    if (this.imageLayer) {
+      this.imageLayer.beginLayout();
+    }
+  }
 
-      this.ctx.transform.apply(this.ctx, viewport.transform);
-      this.baseTransform = this.ctx.mozCurrentTransform.slice();
-      this._combinedScaleFactor = Math.hypot(this.baseTransform[0], this.baseTransform[2]);
+  executeOperatorList(operatorList, executionStartIdx, continueCallback, stepper) {
+    const argsArray = operatorList.argsArray;
+    const fnArray = operatorList.fnArray;
+    let i = executionStartIdx || 0;
+    const argsArrayLen = argsArray.length;
 
-      if (this.imageLayer) {
-        this.imageLayer.beginLayout();
-      }
+    if (argsArrayLen === i) {
+      return i;
     }
 
-    executeOperatorList(operatorList, executionStartIdx, continueCallback, stepper) {
-      const argsArray = operatorList.argsArray;
-      const fnArray = operatorList.fnArray;
-      let i = executionStartIdx || 0;
-      const argsArrayLen = argsArray.length;
+    const chunkOperations = argsArrayLen - i > EXECUTION_STEPS && typeof continueCallback === "function";
+    const endTime = chunkOperations ? Date.now() + EXECUTION_TIME : 0;
+    let steps = 0;
+    const commonObjs = this.commonObjs;
+    const objs = this.objs;
+    let fnId;
 
-      if (argsArrayLen === i) {
+    while (true) {
+      if (stepper !== undefined && i === stepper.nextBreakPoint) {
+        stepper.breakIt(i, continueCallback);
         return i;
       }
 
-      const chunkOperations = argsArrayLen - i > EXECUTION_STEPS && typeof continueCallback === "function";
-      const endTime = chunkOperations ? Date.now() + EXECUTION_TIME : 0;
-      let steps = 0;
-      const commonObjs = this.commonObjs;
-      const objs = this.objs;
-      let fnId;
-
-      while (true) {
-        if (stepper !== undefined && i === stepper.nextBreakPoint) {
-          stepper.breakIt(i, continueCallback);
-          return i;
-        }
-
-        fnId = fnArray[i];
+      fnId = fnArray[i];
 
-        if (fnId !== _util.OPS.dependency) {
-          this[fnId].apply(this, argsArray[i]);
-        } else {
-          for (const depObjId of argsArray[i]) {
-            const objsPool = depObjId.startsWith("g_") ? commonObjs : objs;
+      if (fnId !== _util.OPS.dependency) {
+        this[fnId].apply(this, argsArray[i]);
+      } else {
+        for (const depObjId of argsArray[i]) {
+          const objsPool = depObjId.startsWith("g_") ? commonObjs : objs;
 
-            if (!objsPool.has(depObjId)) {
-              objsPool.get(depObjId, continueCallback);
-              return i;
-            }
+          if (!objsPool.has(depObjId)) {
+            objsPool.get(depObjId, continueCallback);
+            return i;
           }
         }
+      }
+
+      i++;
 
-        i++;
+      if (i === argsArrayLen) {
+        return i;
+      }
 
-        if (i === argsArrayLen) {
+      if (chunkOperations && ++steps > EXECUTION_STEPS) {
+        if (Date.now() > endTime) {
+          continueCallback();
           return i;
         }
 
-        if (chunkOperations && ++steps > EXECUTION_STEPS) {
-          if (Date.now() > endTime) {
-            continueCallback();
-            return i;
-          }
-
-          steps = 0;
-        }
+        steps = 0;
       }
     }
+  }
 
-    endDrawing() {
-      while (this.stateStack.length || this.current.activeSMask !== null) {
-        this.restore();
-      }
+  endDrawing() {
+    while (this.stateStack.length || this.current.activeSMask !== null) {
+      this.restore();
+    }
 
-      this.ctx.restore();
+    this.ctx.restore();
 
-      if (this.transparentCanvas) {
-        this.ctx = this.compositeCtx;
-        this.ctx.save();
-        this.ctx.setTransform(1, 0, 0, 1, 0, 0);
-        this.ctx.drawImage(this.transparentCanvas, 0, 0);
-        this.ctx.restore();
-        this.transparentCanvas = null;
-      }
+    if (this.transparentCanvas) {
+      this.ctx = this.compositeCtx;
+      this.ctx.save();
+      this.ctx.setTransform(1, 0, 0, 1, 0, 0);
+      this.ctx.drawImage(this.transparentCanvas, 0, 0);
+      this.ctx.restore();
+      this.transparentCanvas = null;
+    }
 
-      this.cachedCanvases.clear();
-      this.cachedPatterns.clear();
+    this.cachedCanvases.clear();
+    this.cachedCanvasPatterns.clear();
+    this.cachedPatterns.clear();
 
-      if (this.imageLayer) {
-        this.imageLayer.endLayout();
-      }
+    if (this.imageLayer) {
+      this.imageLayer.endLayout();
     }
+  }
 
-    _scaleImage(img, inverseTransform) {
-      const width = img.width;
-      const height = img.height;
-      let widthScale = Math.max(Math.hypot(inverseTransform[0], inverseTransform[1]), 1);
-      let heightScale = Math.max(Math.hypot(inverseTransform[2], inverseTransform[3]), 1);
-      let paintWidth = width,
-          paintHeight = height;
-      let tmpCanvasId = "prescale1";
-      let tmpCanvas, tmpCtx;
+  _scaleImage(img, inverseTransform) {
+    const width = img.width;
+    const height = img.height;
+    let widthScale = Math.max(Math.hypot(inverseTransform[0], inverseTransform[1]), 1);
+    let heightScale = Math.max(Math.hypot(inverseTransform[2], inverseTransform[3]), 1);
+    let paintWidth = width,
+        paintHeight = height;
+    let tmpCanvasId = "prescale1";
+    let tmpCanvas, tmpCtx;
+
+    while (widthScale > 2 && paintWidth > 1 || heightScale > 2 && paintHeight > 1) {
+      let newWidth = paintWidth,
+          newHeight = paintHeight;
+
+      if (widthScale > 2 && paintWidth > 1) {
+        newWidth = Math.ceil(paintWidth / 2);
+        widthScale /= paintWidth / newWidth;
+      }
+
+      if (heightScale > 2 && paintHeight > 1) {
+        newHeight = Math.ceil(paintHeight / 2);
+        heightScale /= paintHeight / newHeight;
+      }
+
+      tmpCanvas = this.cachedCanvases.getCanvas(tmpCanvasId, newWidth, newHeight);
+      tmpCtx = tmpCanvas.context;
+      tmpCtx.clearRect(0, 0, newWidth, newHeight);
+      tmpCtx.drawImage(img, 0, 0, paintWidth, paintHeight, 0, 0, newWidth, newHeight);
+      img = tmpCanvas.canvas;
+      paintWidth = newWidth;
+      paintHeight = newHeight;
+      tmpCanvasId = tmpCanvasId === "prescale1" ? "prescale2" : "prescale1";
+    }
+
+    return {
+      img,
+      paintWidth,
+      paintHeight
+    };
+  }
 
-      while (widthScale > 2 && paintWidth > 1 || heightScale > 2 && paintHeight > 1) {
-        let newWidth = paintWidth,
-            newHeight = paintHeight;
+  _createMaskCanvas(img) {
+    const ctx = this.ctx;
+    const width = img.width,
+          height = img.height;
+    const fillColor = this.current.fillColor;
+    const isPatternFill = this.current.patternFill;
+    const maskCanvas = this.cachedCanvases.getCanvas("maskCanvas", width, height);
+    const maskCtx = maskCanvas.context;
+    putBinaryImageMask(maskCtx, img);
+    const objToCanvas = ctx.mozCurrentTransform;
 
-        if (widthScale > 2 && paintWidth > 1) {
-          newWidth = Math.ceil(paintWidth / 2);
-          widthScale /= paintWidth / newWidth;
-        }
+    let maskToCanvas = _util.Util.transform(objToCanvas, [1 / width, 0, 0, -1 / height, 0, 0]);
 
-        if (heightScale > 2 && paintHeight > 1) {
-          newHeight = Math.ceil(paintHeight / 2);
-          heightScale /= paintHeight / newHeight;
-        }
+    maskToCanvas = _util.Util.transform(maskToCanvas, [1, 0, 0, 1, 0, -height]);
 
-        tmpCanvas = this.cachedCanvases.getCanvas(tmpCanvasId, newWidth, newHeight);
-        tmpCtx = tmpCanvas.context;
-        tmpCtx.clearRect(0, 0, newWidth, newHeight);
-        tmpCtx.drawImage(img, 0, 0, paintWidth, paintHeight, 0, 0, newWidth, newHeight);
-        img = tmpCanvas.canvas;
-        paintWidth = newWidth;
-        paintHeight = newHeight;
-        tmpCanvasId = tmpCanvasId === "prescale1" ? "prescale2" : "prescale1";
-      }
+    const cord1 = _util.Util.applyTransform([0, 0], maskToCanvas);
 
-      return {
-        img,
-        paintWidth,
-        paintHeight
-      };
-    }
+    const cord2 = _util.Util.applyTransform([width, height], maskToCanvas);
 
-    _createMaskCanvas(img) {
-      const ctx = this.ctx;
-      const width = img.width,
-            height = img.height;
-      const fillColor = this.current.fillColor;
-      const isPatternFill = this.current.patternFill;
-      const maskCanvas = this.cachedCanvases.getCanvas("maskCanvas", width, height);
-      const maskCtx = maskCanvas.context;
-      putBinaryImageMask(maskCtx, img);
-      const objToCanvas = ctx.mozCurrentTransform;
+    const rect = _util.Util.normalizeRect([cord1[0], cord1[1], cord2[0], cord2[1]]);
 
-      let maskToCanvas = _util.Util.transform(objToCanvas, [1 / width, 0, 0, -1 / height, 0, 0]);
+    const drawnWidth = Math.ceil(rect[2] - rect[0]);
+    const drawnHeight = Math.ceil(rect[3] - rect[1]);
+    const fillCanvas = this.cachedCanvases.getCanvas("fillCanvas", drawnWidth, drawnHeight, true);
+    const fillCtx = fillCanvas.context;
+    const offsetX = Math.min(cord1[0], cord2[0]);
+    const offsetY = Math.min(cord1[1], cord2[1]);
+    fillCtx.translate(-offsetX, -offsetY);
+    fillCtx.transform.apply(fillCtx, maskToCanvas);
 
-      maskToCanvas = _util.Util.transform(maskToCanvas, [1, 0, 0, 1, 0, -height]);
+    const scaled = this._scaleImage(maskCanvas.canvas, fillCtx.mozCurrentTransformInverse);
 
-      const cord1 = _util.Util.applyTransform([0, 0], maskToCanvas);
+    fillCtx.imageSmoothingEnabled = getImageSmoothingEnabled(fillCtx.mozCurrentTransform, img.interpolate);
+    fillCtx.drawImage(scaled.img, 0, 0, scaled.img.width, scaled.img.height, 0, 0, width, height);
+    fillCtx.globalCompositeOperation = "source-in";
 
-      const cord2 = _util.Util.applyTransform([width, height], maskToCanvas);
+    const inverse = _util.Util.transform(fillCtx.mozCurrentTransformInverse, [1, 0, 0, 1, -offsetX, -offsetY]);
 
-      const rect = _util.Util.normalizeRect([cord1[0], cord1[1], cord2[0], cord2[1]]);
+    fillCtx.fillStyle = isPatternFill ? fillColor.getPattern(ctx, this, inverse, false) : fillColor;
+    fillCtx.fillRect(0, 0, width, height);
+    return {
+      canvas: fillCanvas.canvas,
+      offsetX: Math.round(offsetX),
+      offsetY: Math.round(offsetY)
+    };
+  }
 
-      const drawnWidth = Math.ceil(rect[2] - rect[0]);
-      const drawnHeight = Math.ceil(rect[3] - rect[1]);
-      const fillCanvas = this.cachedCanvases.getCanvas("fillCanvas", drawnWidth, drawnHeight, true);
-      const fillCtx = fillCanvas.context;
-      const offsetX = Math.min(cord1[0], cord2[0]);
-      const offsetY = Math.min(cord1[1], cord2[1]);
-      fillCtx.translate(-offsetX, -offsetY);
-      fillCtx.transform.apply(fillCtx, maskToCanvas);
+  setLineWidth(width) {
+    this.current.lineWidth = width;
+    this.ctx.lineWidth = width;
+  }
 
-      const scaled = this._scaleImage(maskCanvas.canvas, fillCtx.mozCurrentTransformInverse);
+  setLineCap(style) {
+    this.ctx.lineCap = LINE_CAP_STYLES[style];
+  }
 
-      fillCtx.drawImage(scaled.img, 0, 0, scaled.img.width, scaled.img.height, 0, 0, width, height);
-      fillCtx.globalCompositeOperation = "source-in";
+  setLineJoin(style) {
+    this.ctx.lineJoin = LINE_JOIN_STYLES[style];
+  }
 
-      const inverse = _util.Util.transform(fillCtx.mozCurrentTransformInverse, [1, 0, 0, 1, -offsetX, -offsetY]);
+  setMiterLimit(limit) {
+    this.ctx.miterLimit = limit;
+  }
 
-      fillCtx.fillStyle = isPatternFill ? fillColor.getPattern(ctx, this, inverse, false) : fillColor;
-      fillCtx.fillRect(0, 0, width, height);
-      return {
-        canvas: fillCanvas.canvas,
-        offsetX: Math.round(offsetX),
-        offsetY: Math.round(offsetY)
-      };
-    }
+  setDash(dashArray, dashPhase) {
+    const ctx = this.ctx;
 
-    setLineWidth(width) {
-      this.current.lineWidth = width;
-      this.ctx.lineWidth = width;
+    if (ctx.setLineDash !== undefined) {
+      ctx.setLineDash(dashArray);
+      ctx.lineDashOffset = dashPhase;
     }
+  }
 
-    setLineCap(style) {
-      this.ctx.lineCap = LINE_CAP_STYLES[style];
-    }
+  setRenderingIntent(intent) {}
 
-    setLineJoin(style) {
-      this.ctx.lineJoin = LINE_JOIN_STYLES[style];
-    }
+  setFlatness(flatness) {}
 
-    setMiterLimit(limit) {
-      this.ctx.miterLimit = limit;
-    }
+  setGState(states) {
+    for (let i = 0, ii = states.length; i < ii; i++) {
+      const state = states[i];
+      const key = state[0];
+      const value = state[1];
 
-    setDash(dashArray, dashPhase) {
-      const ctx = this.ctx;
+      switch (key) {
+        case "LW":
+          this.setLineWidth(value);
+          break;
 
-      if (ctx.setLineDash !== undefined) {
-        ctx.setLineDash(dashArray);
-        ctx.lineDashOffset = dashPhase;
-      }
-    }
+        case "LC":
+          this.setLineCap(value);
+          break;
 
-    setRenderingIntent(intent) {}
+        case "LJ":
+          this.setLineJoin(value);
+          break;
 
-    setFlatness(flatness) {}
+        case "ML":
+          this.setMiterLimit(value);
+          break;
 
-    setGState(states) {
-      for (let i = 0, ii = states.length; i < ii; i++) {
-        const state = states[i];
-        const key = state[0];
-        const value = state[1];
+        case "D":
+          this.setDash(value[0], value[1]);
+          break;
 
-        switch (key) {
-          case "LW":
-            this.setLineWidth(value);
-            break;
+        case "RI":
+          this.setRenderingIntent(value);
+          break;
 
-          case "LC":
-            this.setLineCap(value);
-            break;
+        case "FL":
+          this.setFlatness(value);
+          break;
 
-          case "LJ":
-            this.setLineJoin(value);
-            break;
+        case "Font":
+          this.setFont(value[0], value[1]);
+          break;
 
-          case "ML":
-            this.setMiterLimit(value);
-            break;
+        case "CA":
+          this.current.strokeAlpha = state[1];
+          break;
 
-          case "D":
-            this.setDash(value[0], value[1]);
-            break;
+        case "ca":
+          this.current.fillAlpha = state[1];
+          this.ctx.globalAlpha = state[1];
+          break;
 
-          case "RI":
-            this.setRenderingIntent(value);
-            break;
+        case "BM":
+          this.ctx.globalCompositeOperation = value;
+          break;
 
-          case "FL":
-            this.setFlatness(value);
-            break;
+        case "SMask":
+          if (this.current.activeSMask) {
+            if (this.stateStack.length > 0 && this.stateStack[this.stateStack.length - 1].activeSMask === this.current.activeSMask) {
+              this.suspendSMaskGroup();
+            } else {
+              this.endSMaskGroup();
+            }
+          }
 
-          case "Font":
-            this.setFont(value[0], value[1]);
-            break;
+          this.current.activeSMask = value ? this.tempSMask : null;
 
-          case "CA":
-            this.current.strokeAlpha = state[1];
-            break;
+          if (this.current.activeSMask) {
+            this.beginSMaskGroup();
+          }
 
-          case "ca":
-            this.current.fillAlpha = state[1];
-            this.ctx.globalAlpha = state[1];
-            break;
+          this.tempSMask = null;
+          break;
 
-          case "BM":
-            this.ctx.globalCompositeOperation = value;
-            break;
+        case "TR":
+          this.current.transferMaps = value;
+      }
+    }
+  }
 
-          case "SMask":
-            if (this.current.activeSMask) {
-              if (this.stateStack.length > 0 && this.stateStack[this.stateStack.length - 1].activeSMask === this.current.activeSMask) {
-                this.suspendSMaskGroup();
-              } else {
-                this.endSMaskGroup();
-              }
-            }
+  beginSMaskGroup() {
+    const activeSMask = this.current.activeSMask;
+    const drawnWidth = activeSMask.canvas.width;
+    const drawnHeight = activeSMask.canvas.height;
+    const cacheId = "smaskGroupAt" + this.groupLevel;
+    const scratchCanvas = this.cachedCanvases.getCanvas(cacheId, drawnWidth, drawnHeight, true);
+    const currentCtx = this.ctx;
+    const currentTransform = currentCtx.mozCurrentTransform;
+    this.ctx.save();
+    const groupCtx = scratchCanvas.context;
+    groupCtx.scale(1 / activeSMask.scaleX, 1 / activeSMask.scaleY);
+    groupCtx.translate(-activeSMask.offsetX, -activeSMask.offsetY);
+    groupCtx.transform.apply(groupCtx, currentTransform);
+    activeSMask.startTransformInverse = groupCtx.mozCurrentTransformInverse;
+    copyCtxState(currentCtx, groupCtx);
+    this.ctx = groupCtx;
+    this.setGState([["BM", "source-over"], ["ca", 1], ["CA", 1]]);
+    this.groupStack.push(currentCtx);
+    this.groupLevel++;
+  }
 
-            this.current.activeSMask = value ? this.tempSMask : null;
+  suspendSMaskGroup() {
+    const groupCtx = this.ctx;
+    this.groupLevel--;
+    this.ctx = this.groupStack.pop();
+    composeSMask(this.ctx, this.current.activeSMask, groupCtx);
+    this.ctx.restore();
+    this.ctx.save();
+    copyCtxState(groupCtx, this.ctx);
+    this.current.resumeSMaskCtx = groupCtx;
+
+    const deltaTransform = _util.Util.transform(this.current.activeSMask.startTransformInverse, groupCtx.mozCurrentTransform);
+
+    this.ctx.transform.apply(this.ctx, deltaTransform);
+    groupCtx.save();
+    groupCtx.setTransform(1, 0, 0, 1, 0, 0);
+    groupCtx.clearRect(0, 0, groupCtx.canvas.width, groupCtx.canvas.height);
+    groupCtx.restore();
+  }
 
-            if (this.current.activeSMask) {
-              this.beginSMaskGroup();
-            }
+  resumeSMaskGroup() {
+    const groupCtx = this.current.resumeSMaskCtx;
+    const currentCtx = this.ctx;
+    this.ctx = groupCtx;
+    this.groupStack.push(currentCtx);
+    this.groupLevel++;
+  }
 
-            this.tempSMask = null;
-            break;
+  endSMaskGroup() {
+    const groupCtx = this.ctx;
+    this.groupLevel--;
+    this.ctx = this.groupStack.pop();
+    composeSMask(this.ctx, this.current.activeSMask, groupCtx);
+    this.ctx.restore();
+    copyCtxState(groupCtx, this.ctx);
 
-          case "TR":
-            this.current.transferMaps = value;
-        }
-      }
-    }
+    const deltaTransform = _util.Util.transform(this.current.activeSMask.startTransformInverse, groupCtx.mozCurrentTransform);
 
-    beginSMaskGroup() {
-      const activeSMask = this.current.activeSMask;
-      const drawnWidth = activeSMask.canvas.width;
-      const drawnHeight = activeSMask.canvas.height;
-      const cacheId = "smaskGroupAt" + this.groupLevel;
-      const scratchCanvas = this.cachedCanvases.getCanvas(cacheId, drawnWidth, drawnHeight, true);
-      const currentCtx = this.ctx;
-      const currentTransform = currentCtx.mozCurrentTransform;
-      this.ctx.save();
-      const groupCtx = scratchCanvas.context;
-      groupCtx.scale(1 / activeSMask.scaleX, 1 / activeSMask.scaleY);
-      groupCtx.translate(-activeSMask.offsetX, -activeSMask.offsetY);
-      groupCtx.transform.apply(groupCtx, currentTransform);
-      activeSMask.startTransformInverse = groupCtx.mozCurrentTransformInverse;
-      copyCtxState(currentCtx, groupCtx);
-      this.ctx = groupCtx;
-      this.setGState([["BM", "source-over"], ["ca", 1], ["CA", 1]]);
-      this.groupStack.push(currentCtx);
-      this.groupLevel++;
-    }
-
-    suspendSMaskGroup() {
-      const groupCtx = this.ctx;
-      this.groupLevel--;
-      this.ctx = this.groupStack.pop();
-      composeSMask(this.ctx, this.current.activeSMask, groupCtx);
-      this.ctx.restore();
-      this.ctx.save();
-      copyCtxState(groupCtx, this.ctx);
-      this.current.resumeSMaskCtx = groupCtx;
+    this.ctx.transform.apply(this.ctx, deltaTransform);
+  }
 
-      const deltaTransform = _util.Util.transform(this.current.activeSMask.startTransformInverse, groupCtx.mozCurrentTransform);
+  save() {
+    this.ctx.save();
+    const old = this.current;
+    this.stateStack.push(old);
+    this.current = old.clone();
+    this.current.resumeSMaskCtx = null;
+  }
 
-      this.ctx.transform.apply(this.ctx, deltaTransform);
-      groupCtx.save();
-      groupCtx.setTransform(1, 0, 0, 1, 0, 0);
-      groupCtx.clearRect(0, 0, groupCtx.canvas.width, groupCtx.canvas.height);
-      groupCtx.restore();
+  restore() {
+    if (this.current.resumeSMaskCtx) {
+      this.resumeSMaskGroup();
     }
 
-    resumeSMaskGroup() {
-      const groupCtx = this.current.resumeSMaskCtx;
-      const currentCtx = this.ctx;
-      this.ctx = groupCtx;
-      this.groupStack.push(currentCtx);
-      this.groupLevel++;
+    if (this.current.activeSMask !== null && (this.stateStack.length === 0 || this.stateStack[this.stateStack.length - 1].activeSMask !== this.current.activeSMask)) {
+      this.endSMaskGroup();
     }
 
-    endSMaskGroup() {
-      const groupCtx = this.ctx;
-      this.groupLevel--;
-      this.ctx = this.groupStack.pop();
-      composeSMask(this.ctx, this.current.activeSMask, groupCtx);
+    if (this.stateStack.length !== 0) {
+      this.current = this.stateStack.pop();
       this.ctx.restore();
-      copyCtxState(groupCtx, this.ctx);
+      this.pendingClip = null;
+      this._cachedGetSinglePixelWidth = null;
+    } else {
+      this.current.activeSMask = null;
+    }
+  }
 
-      const deltaTransform = _util.Util.transform(this.current.activeSMask.startTransformInverse, groupCtx.mozCurrentTransform);
+  transform(a, b, c, d, e, f) {
+    this.ctx.transform(a, b, c, d, e, f);
+    this._cachedGetSinglePixelWidth = null;
+  }
 
-      this.ctx.transform.apply(this.ctx, deltaTransform);
-    }
+  constructPath(ops, args) {
+    const ctx = this.ctx;
+    const current = this.current;
+    let x = current.x,
+        y = current.y;
+
+    for (let i = 0, j = 0, ii = ops.length; i < ii; i++) {
+      switch (ops[i] | 0) {
+        case _util.OPS.rectangle:
+          x = args[j++];
+          y = args[j++];
+          const width = args[j++];
+          const height = args[j++];
+          const xw = x + width;
+          const yh = y + height;
+          ctx.moveTo(x, y);
+
+          if (width === 0 || height === 0) {
+            ctx.lineTo(xw, yh);
+          } else {
+            ctx.lineTo(xw, y);
+            ctx.lineTo(xw, yh);
+            ctx.lineTo(x, yh);
+          }
 
-    save() {
-      this.ctx.save();
-      const old = this.current;
-      this.stateStack.push(old);
-      this.current = old.clone();
-      this.current.resumeSMaskCtx = null;
-    }
+          ctx.closePath();
+          break;
 
-    restore() {
-      if (this.current.resumeSMaskCtx) {
-        this.resumeSMaskGroup();
-      }
+        case _util.OPS.moveTo:
+          x = args[j++];
+          y = args[j++];
+          ctx.moveTo(x, y);
+          break;
 
-      if (this.current.activeSMask !== null && (this.stateStack.length === 0 || this.stateStack[this.stateStack.length - 1].activeSMask !== this.current.activeSMask)) {
-        this.endSMaskGroup();
-      }
+        case _util.OPS.lineTo:
+          x = args[j++];
+          y = args[j++];
+          ctx.lineTo(x, y);
+          break;
 
-      if (this.stateStack.length !== 0) {
-        this.current = this.stateStack.pop();
-        this.ctx.restore();
-        this.pendingClip = null;
-        this._cachedGetSinglePixelWidth = null;
-      } else {
-        this.current.activeSMask = null;
-      }
-    }
+        case _util.OPS.curveTo:
+          x = args[j + 4];
+          y = args[j + 5];
+          ctx.bezierCurveTo(args[j], args[j + 1], args[j + 2], args[j + 3], x, y);
+          j += 6;
+          break;
 
-    transform(a, b, c, d, e, f) {
-      this.ctx.transform(a, b, c, d, e, f);
-      this._cachedGetSinglePixelWidth = null;
-    }
+        case _util.OPS.curveTo2:
+          ctx.bezierCurveTo(x, y, args[j], args[j + 1], args[j + 2], args[j + 3]);
+          x = args[j + 2];
+          y = args[j + 3];
+          j += 4;
+          break;
 
-    constructPath(ops, args) {
-      const ctx = this.ctx;
-      const current = this.current;
-      let x = current.x,
-          y = current.y;
-
-      for (let i = 0, j = 0, ii = ops.length; i < ii; i++) {
-        switch (ops[i] | 0) {
-          case _util.OPS.rectangle:
-            x = args[j++];
-            y = args[j++];
-            const width = args[j++];
-            const height = args[j++];
-            const xw = x + width;
-            const yh = y + height;
-            ctx.moveTo(x, y);
-
-            if (width === 0 || height === 0) {
-              ctx.lineTo(xw, yh);
-            } else {
-              ctx.lineTo(xw, y);
-              ctx.lineTo(xw, yh);
-              ctx.lineTo(x, yh);
-            }
+        case _util.OPS.curveTo3:
+          x = args[j + 2];
+          y = args[j + 3];
+          ctx.bezierCurveTo(args[j], args[j + 1], x, y, x, y);
+          j += 4;
+          break;
 
-            ctx.closePath();
-            break;
-
-          case _util.OPS.moveTo:
-            x = args[j++];
-            y = args[j++];
-            ctx.moveTo(x, y);
-            break;
-
-          case _util.OPS.lineTo:
-            x = args[j++];
-            y = args[j++];
-            ctx.lineTo(x, y);
-            break;
-
-          case _util.OPS.curveTo:
-            x = args[j + 4];
-            y = args[j + 5];
-            ctx.bezierCurveTo(args[j], args[j + 1], args[j + 2], args[j + 3], x, y);
-            j += 6;
-            break;
-
-          case _util.OPS.curveTo2:
-            ctx.bezierCurveTo(x, y, args[j], args[j + 1], args[j + 2], args[j + 3]);
-            x = args[j + 2];
-            y = args[j + 3];
-            j += 4;
-            break;
-
-          case _util.OPS.curveTo3:
-            x = args[j + 2];
-            y = args[j + 3];
-            ctx.bezierCurveTo(args[j], args[j + 1], x, y, x, y);
-            j += 4;
-            break;
-
-          case _util.OPS.closePath:
-            ctx.closePath();
-            break;
-        }
+        case _util.OPS.closePath:
+          ctx.closePath();
+          break;
       }
-
-      current.setCurrentPoint(x, y);
     }
 
-    closePath() {
-      this.ctx.closePath();
-    }
+    current.setCurrentPoint(x, y);
+  }
 
-    stroke(consumePath) {
-      consumePath = typeof consumePath !== "undefined" ? consumePath : true;
-      const ctx = this.ctx;
-      const strokeColor = this.current.strokeColor;
-      ctx.globalAlpha = this.current.strokeAlpha;
+  closePath() {
+    this.ctx.closePath();
+  }
 
-      if (this.contentVisible) {
-        if (typeof strokeColor === "object" && strokeColor?.getPattern) {
-          const lineWidth = this.getSinglePixelWidth();
+  stroke(consumePath) {
+    consumePath = typeof consumePath !== "undefined" ? consumePath : true;
+    const ctx = this.ctx;
+    const strokeColor = this.current.strokeColor;
+    ctx.globalAlpha = this.current.strokeAlpha;
+
+    if (this.contentVisible) {
+      if (typeof strokeColor === "object" && strokeColor?.getPattern) {
+        const lineWidth = this.getSinglePixelWidth();
+        ctx.save();
+        ctx.strokeStyle = strokeColor.getPattern(ctx, this, ctx.mozCurrentTransformInverse);
+        ctx.lineWidth = Math.max(lineWidth, this.current.lineWidth);
+        ctx.stroke();
+        ctx.restore();
+      } else {
+        const lineWidth = this.getSinglePixelWidth();
+
+        if (lineWidth < 0 && -lineWidth >= this.current.lineWidth) {
           ctx.save();
-          ctx.strokeStyle = strokeColor.getPattern(ctx, this, ctx.mozCurrentTransformInverse);
-          ctx.lineWidth = Math.max(lineWidth, this.current.lineWidth);
+          ctx.resetTransform();
+          ctx.lineWidth = Math.round(this._combinedScaleFactor);
           ctx.stroke();
           ctx.restore();
         } else {
-          const lineWidth = this.getSinglePixelWidth();
-
-          if (lineWidth < 0 && -lineWidth >= this.current.lineWidth) {
-            ctx.save();
-            ctx.resetTransform();
-            ctx.lineWidth = Math.round(this._combinedScaleFactor);
-            ctx.stroke();
-            ctx.restore();
-          } else {
-            ctx.lineWidth = Math.max(lineWidth, this.current.lineWidth);
-            ctx.stroke();
-          }
+          ctx.lineWidth = Math.max(lineWidth, this.current.lineWidth);
+          ctx.stroke();
         }
       }
+    }
 
-      if (consumePath) {
-        this.consumePath();
-      }
-
-      ctx.globalAlpha = this.current.fillAlpha;
-    }
-
-    closeStroke() {
-      this.closePath();
-      this.stroke();
+    if (consumePath) {
+      this.consumePath();
     }
 
-    fill(consumePath) {
-      consumePath = typeof consumePath !== "undefined" ? consumePath : true;
-      const ctx = this.ctx;
-      const fillColor = this.current.fillColor;
-      const isPatternFill = this.current.patternFill;
-      let needRestore = false;
+    ctx.globalAlpha = this.current.fillAlpha;
+  }
 
-      if (isPatternFill) {
-        ctx.save();
-        ctx.fillStyle = fillColor.getPattern(ctx, this, ctx.mozCurrentTransformInverse);
-        needRestore = true;
-      }
+  closeStroke() {
+    this.closePath();
+    this.stroke();
+  }
 
-      if (this.contentVisible) {
-        if (this.pendingEOFill) {
-          ctx.fill("evenodd");
-          this.pendingEOFill = false;
-        } else {
-          ctx.fill();
-        }
-      }
+  fill(consumePath) {
+    consumePath = typeof consumePath !== "undefined" ? consumePath : true;
+    const ctx = this.ctx;
+    const fillColor = this.current.fillColor;
+    const isPatternFill = this.current.patternFill;
+    let needRestore = false;
 
-      if (needRestore) {
-        ctx.restore();
-      }
+    if (isPatternFill) {
+      ctx.save();
+      ctx.fillStyle = fillColor.getPattern(ctx, this, ctx.mozCurrentTransformInverse);
+      needRestore = true;
+    }
 
-      if (consumePath) {
-        this.consumePath();
+    if (this.contentVisible) {
+      if (this.pendingEOFill) {
+        ctx.fill("evenodd");
+        this.pendingEOFill = false;
+      } else {
+        ctx.fill();
       }
     }
 
-    eoFill() {
-      this.pendingEOFill = true;
-      this.fill();
+    if (needRestore) {
+      ctx.restore();
     }
 
-    fillStroke() {
-      this.fill(false);
-      this.stroke(false);
+    if (consumePath) {
       this.consumePath();
     }
+  }
 
-    eoFillStroke() {
-      this.pendingEOFill = true;
-      this.fillStroke();
-    }
-
-    closeFillStroke() {
-      this.closePath();
-      this.fillStroke();
-    }
+  eoFill() {
+    this.pendingEOFill = true;
+    this.fill();
+  }
 
-    closeEOFillStroke() {
-      this.pendingEOFill = true;
-      this.closePath();
-      this.fillStroke();
-    }
+  fillStroke() {
+    this.fill(false);
+    this.stroke(false);
+    this.consumePath();
+  }
 
-    endPath() {
-      this.consumePath();
-    }
+  eoFillStroke() {
+    this.pendingEOFill = true;
+    this.fillStroke();
+  }
 
-    clip() {
-      this.pendingClip = NORMAL_CLIP;
-    }
+  closeFillStroke() {
+    this.closePath();
+    this.fillStroke();
+  }
 
-    eoClip() {
-      this.pendingClip = EO_CLIP;
-    }
+  closeEOFillStroke() {
+    this.pendingEOFill = true;
+    this.closePath();
+    this.fillStroke();
+  }
 
-    beginText() {
-      this.current.textMatrix = _util.IDENTITY_MATRIX;
-      this.current.textMatrixScale = 1;
-      this.current.x = this.current.lineX = 0;
-      this.current.y = this.current.lineY = 0;
-    }
+  endPath() {
+    this.consumePath();
+  }
 
-    endText() {
-      const paths = this.pendingTextPaths;
-      const ctx = this.ctx;
+  clip() {
+    this.pendingClip = NORMAL_CLIP;
+  }
 
-      if (paths === undefined) {
-        ctx.beginPath();
-        return;
-      }
+  eoClip() {
+    this.pendingClip = EO_CLIP;
+  }
 
-      ctx.save();
-      ctx.beginPath();
+  beginText() {
+    this.current.textMatrix = _util.IDENTITY_MATRIX;
+    this.current.textMatrixScale = 1;
+    this.current.x = this.current.lineX = 0;
+    this.current.y = this.current.lineY = 0;
+  }
 
-      for (let i = 0; i < paths.length; i++) {
-        const path = paths[i];
-        ctx.setTransform.apply(ctx, path.transform);
-        ctx.translate(path.x, path.y);
-        path.addToPath(ctx, path.fontSize);
-      }
+  endText() {
+    const paths = this.pendingTextPaths;
+    const ctx = this.ctx;
 
-      ctx.restore();
-      ctx.clip();
+    if (paths === undefined) {
       ctx.beginPath();
-      delete this.pendingTextPaths;
+      return;
     }
 
-    setCharSpacing(spacing) {
-      this.current.charSpacing = spacing;
-    }
+    ctx.save();
+    ctx.beginPath();
 
-    setWordSpacing(spacing) {
-      this.current.wordSpacing = spacing;
+    for (let i = 0; i < paths.length; i++) {
+      const path = paths[i];
+      ctx.setTransform.apply(ctx, path.transform);
+      ctx.translate(path.x, path.y);
+      path.addToPath(ctx, path.fontSize);
     }
 
-    setHScale(scale) {
-      this.current.textHScale = scale / 100;
-    }
+    ctx.restore();
+    ctx.clip();
+    ctx.beginPath();
+    delete this.pendingTextPaths;
+  }
 
-    setLeading(leading) {
-      this.current.leading = -leading;
-    }
+  setCharSpacing(spacing) {
+    this.current.charSpacing = spacing;
+  }
 
-    setFont(fontRefName, size) {
-      const fontObj = this.commonObjs.get(fontRefName);
-      const current = this.current;
+  setWordSpacing(spacing) {
+    this.current.wordSpacing = spacing;
+  }
 
-      if (!fontObj) {
-        throw new Error(`Can't find font for ${fontRefName}`);
-      }
+  setHScale(scale) {
+    this.current.textHScale = scale / 100;
+  }
 
-      current.fontMatrix = fontObj.fontMatrix || _util.FONT_IDENTITY_MATRIX;
+  setLeading(leading) {
+    this.current.leading = -leading;
+  }
 
-      if (current.fontMatrix[0] === 0 || current.fontMatrix[3] === 0) {
-        (0, _util.warn)("Invalid font matrix for font " + fontRefName);
-      }
+  setFont(fontRefName, size) {
+    const fontObj = this.commonObjs.get(fontRefName);
+    const current = this.current;
 
-      if (size < 0) {
-        size = -size;
-        current.fontDirection = -1;
-      } else {
-        current.fontDirection = 1;
-      }
+    if (!fontObj) {
+      throw new Error(`Can't find font for ${fontRefName}`);
+    }
 
-      this.current.font = fontObj;
-      this.current.fontSize = size;
+    current.fontMatrix = fontObj.fontMatrix || _util.FONT_IDENTITY_MATRIX;
 
-      if (fontObj.isType3Font) {
-        return;
-      }
+    if (current.fontMatrix[0] === 0 || current.fontMatrix[3] === 0) {
+      (0, _util.warn)("Invalid font matrix for font " + fontRefName);
+    }
 
-      const name = fontObj.loadedName || "sans-serif";
-      let bold = "normal";
+    if (size < 0) {
+      size = -size;
+      current.fontDirection = -1;
+    } else {
+      current.fontDirection = 1;
+    }
 
-      if (fontObj.black) {
-        bold = "900";
-      } else if (fontObj.bold) {
-        bold = "bold";
-      }
+    this.current.font = fontObj;
+    this.current.fontSize = size;
 
-      const italic = fontObj.italic ? "italic" : "normal";
-      const typeface = `"${name}", ${fontObj.fallbackName}`;
-      let browserFontSize = size;
+    if (fontObj.isType3Font) {
+      return;
+    }
 
-      if (size < MIN_FONT_SIZE) {
-        browserFontSize = MIN_FONT_SIZE;
-      } else if (size > MAX_FONT_SIZE) {
-        browserFontSize = MAX_FONT_SIZE;
-      }
+    const name = fontObj.loadedName || "sans-serif";
+    let bold = "normal";
 
-      this.current.fontSizeScale = size / browserFontSize;
-      this.ctx.font = `${italic} ${bold} ${browserFontSize}px ${typeface}`;
+    if (fontObj.black) {
+      bold = "900";
+    } else if (fontObj.bold) {
+      bold = "bold";
     }
 
-    setTextRenderingMode(mode) {
-      this.current.textRenderingMode = mode;
-    }
+    const italic = fontObj.italic ? "italic" : "normal";
+    const typeface = `"${name}", ${fontObj.fallbackName}`;
+    let browserFontSize = size;
 
-    setTextRise(rise) {
-      this.current.textRise = rise;
+    if (size < MIN_FONT_SIZE) {
+      browserFontSize = MIN_FONT_SIZE;
+    } else if (size > MAX_FONT_SIZE) {
+      browserFontSize = MAX_FONT_SIZE;
     }
 
-    moveText(x, y) {
-      this.current.x = this.current.lineX += x;
-      this.current.y = this.current.lineY += y;
-    }
+    this.current.fontSizeScale = size / browserFontSize;
+    this.ctx.font = `${italic} ${bold} ${browserFontSize}px ${typeface}`;
+  }
 
-    setLeadingMoveText(x, y) {
-      this.setLeading(-y);
-      this.moveText(x, y);
-    }
+  setTextRenderingMode(mode) {
+    this.current.textRenderingMode = mode;
+  }
 
-    setTextMatrix(a, b, c, d, e, f) {
-      this.current.textMatrix = [a, b, c, d, e, f];
-      this.current.textMatrixScale = Math.hypot(a, b);
-      this.current.x = this.current.lineX = 0;
-      this.current.y = this.current.lineY = 0;
-    }
+  setTextRise(rise) {
+    this.current.textRise = rise;
+  }
 
-    nextLine() {
-      this.moveText(0, this.current.leading);
-    }
+  moveText(x, y) {
+    this.current.x = this.current.lineX += x;
+    this.current.y = this.current.lineY += y;
+  }
 
-    paintChar(character, x, y, patternTransform, resetLineWidthToOne) {
-      const ctx = this.ctx;
-      const current = this.current;
-      const font = current.font;
-      const textRenderingMode = current.textRenderingMode;
-      const fontSize = current.fontSize / current.fontSizeScale;
-      const fillStrokeMode = textRenderingMode & _util.TextRenderingMode.FILL_STROKE_MASK;
-      const isAddToPathSet = !!(textRenderingMode & _util.TextRenderingMode.ADD_TO_PATH_FLAG);
-      const patternFill = current.patternFill && !font.missingFile;
-      let addToPath;
+  setLeadingMoveText(x, y) {
+    this.setLeading(-y);
+    this.moveText(x, y);
+  }
 
-      if (font.disableFontFace || isAddToPathSet || patternFill) {
-        addToPath = font.getPathGenerator(this.commonObjs, character);
-      }
+  setTextMatrix(a, b, c, d, e, f) {
+    this.current.textMatrix = [a, b, c, d, e, f];
+    this.current.textMatrixScale = Math.hypot(a, b);
+    this.current.x = this.current.lineX = 0;
+    this.current.y = this.current.lineY = 0;
+  }
 
-      if (font.disableFontFace || patternFill) {
-        ctx.save();
-        ctx.translate(x, y);
-        ctx.beginPath();
-        addToPath(ctx, fontSize);
+  nextLine() {
+    this.moveText(0, this.current.leading);
+  }
 
-        if (patternTransform) {
-          ctx.setTransform.apply(ctx, patternTransform);
-        }
+  paintChar(character, x, y, patternTransform, resetLineWidthToOne) {
+    const ctx = this.ctx;
+    const current = this.current;
+    const font = current.font;
+    const textRenderingMode = current.textRenderingMode;
+    const fontSize = current.fontSize / current.fontSizeScale;
+    const fillStrokeMode = textRenderingMode & _util.TextRenderingMode.FILL_STROKE_MASK;
+    const isAddToPathSet = !!(textRenderingMode & _util.TextRenderingMode.ADD_TO_PATH_FLAG);
+    const patternFill = current.patternFill && !font.missingFile;
+    let addToPath;
 
-        if (fillStrokeMode === _util.TextRenderingMode.FILL || fillStrokeMode === _util.TextRenderingMode.FILL_STROKE) {
-          ctx.fill();
-        }
+    if (font.disableFontFace || isAddToPathSet || patternFill) {
+      addToPath = font.getPathGenerator(this.commonObjs, character);
+    }
 
-        if (fillStrokeMode === _util.TextRenderingMode.STROKE || fillStrokeMode === _util.TextRenderingMode.FILL_STROKE) {
-          if (resetLineWidthToOne) {
-            ctx.resetTransform();
-            ctx.lineWidth = Math.round(this._combinedScaleFactor);
-          }
+    if (font.disableFontFace || patternFill) {
+      ctx.save();
+      ctx.translate(x, y);
+      ctx.beginPath();
+      addToPath(ctx, fontSize);
 
-          ctx.stroke();
-        }
+      if (patternTransform) {
+        ctx.setTransform.apply(ctx, patternTransform);
+      }
 
-        ctx.restore();
-      } else {
-        if (fillStrokeMode === _util.TextRenderingMode.FILL || fillStrokeMode === _util.TextRenderingMode.FILL_STROKE) {
-          ctx.fillText(character, x, y);
-        }
+      if (fillStrokeMode === _util.TextRenderingMode.FILL || fillStrokeMode === _util.TextRenderingMode.FILL_STROKE) {
+        ctx.fill();
+      }
 
-        if (fillStrokeMode === _util.TextRenderingMode.STROKE || fillStrokeMode === _util.TextRenderingMode.FILL_STROKE) {
-          if (resetLineWidthToOne) {
-            ctx.save();
-            ctx.moveTo(x, y);
-            ctx.resetTransform();
-            ctx.lineWidth = Math.round(this._combinedScaleFactor);
-            ctx.strokeText(character, 0, 0);
-            ctx.restore();
-          } else {
-            ctx.strokeText(character, x, y);
-          }
+      if (fillStrokeMode === _util.TextRenderingMode.STROKE || fillStrokeMode === _util.TextRenderingMode.FILL_STROKE) {
+        if (resetLineWidthToOne) {
+          ctx.resetTransform();
+          ctx.lineWidth = Math.round(this._combinedScaleFactor);
         }
-      }
 
-      if (isAddToPathSet) {
-        const paths = this.pendingTextPaths || (this.pendingTextPaths = []);
-        paths.push({
-          transform: ctx.mozCurrentTransform,
-          x,
-          y,
-          fontSize,
-          addToPath
-        });
+        ctx.stroke();
       }
-    }
 
-    get isFontSubpixelAAEnabled() {
-      const {
-        context: ctx
-      } = this.cachedCanvases.getCanvas("isFontSubpixelAAEnabled", 10, 10);
-      ctx.scale(1.5, 1);
-      ctx.fillText("I", 0, 10);
-      const data = ctx.getImageData(0, 0, 10, 10).data;
-      let enabled = false;
+      ctx.restore();
+    } else {
+      if (fillStrokeMode === _util.TextRenderingMode.FILL || fillStrokeMode === _util.TextRenderingMode.FILL_STROKE) {
+        ctx.fillText(character, x, y);
+      }
 
-      for (let i = 3; i < data.length; i += 4) {
-        if (data[i] > 0 && data[i] < 255) {
-          enabled = true;
-          break;
+      if (fillStrokeMode === _util.TextRenderingMode.STROKE || fillStrokeMode === _util.TextRenderingMode.FILL_STROKE) {
+        if (resetLineWidthToOne) {
+          ctx.save();
+          ctx.moveTo(x, y);
+          ctx.resetTransform();
+          ctx.lineWidth = Math.round(this._combinedScaleFactor);
+          ctx.strokeText(character, 0, 0);
+          ctx.restore();
+        } else {
+          ctx.strokeText(character, x, y);
         }
       }
+    }
 
-      return (0, _util.shadow)(this, "isFontSubpixelAAEnabled", enabled);
+    if (isAddToPathSet) {
+      const paths = this.pendingTextPaths || (this.pendingTextPaths = []);
+      paths.push({
+        transform: ctx.mozCurrentTransform,
+        x,
+        y,
+        fontSize,
+        addToPath
+      });
     }
+  }
 
-    showText(glyphs) {
-      const current = this.current;
-      const font = current.font;
+  get isFontSubpixelAAEnabled() {
+    const {
+      context: ctx
+    } = this.cachedCanvases.getCanvas("isFontSubpixelAAEnabled", 10, 10);
+    ctx.scale(1.5, 1);
+    ctx.fillText("I", 0, 10);
+    const data = ctx.getImageData(0, 0, 10, 10).data;
+    let enabled = false;
 
-      if (font.isType3Font) {
-        return this.showType3Text(glyphs);
+    for (let i = 3; i < data.length; i += 4) {
+      if (data[i] > 0 && data[i] < 255) {
+        enabled = true;
+        break;
       }
+    }
+
+    return (0, _util.shadow)(this, "isFontSubpixelAAEnabled", enabled);
+  }
 
-      const fontSize = current.fontSize;
+  showText(glyphs) {
+    const current = this.current;
+    const font = current.font;
 
-      if (fontSize === 0) {
-        return undefined;
-      }
+    if (font.isType3Font) {
+      return this.showType3Text(glyphs);
+    }
 
-      const ctx = this.ctx;
-      const fontSizeScale = current.fontSizeScale;
-      const charSpacing = current.charSpacing;
-      const wordSpacing = current.wordSpacing;
-      const fontDirection = current.fontDirection;
-      const textHScale = current.textHScale * fontDirection;
-      const glyphsLength = glyphs.length;
-      const vertical = font.vertical;
-      const spacingDir = vertical ? 1 : -1;
-      const defaultVMetrics = font.defaultVMetrics;
-      const widthAdvanceScale = fontSize * current.fontMatrix[0];
-      const simpleFillText = current.textRenderingMode === _util.TextRenderingMode.FILL && !font.disableFontFace && !current.patternFill;
+    const fontSize = current.fontSize;
+
+    if (fontSize === 0) {
+      return undefined;
+    }
+
+    const ctx = this.ctx;
+    const fontSizeScale = current.fontSizeScale;
+    const charSpacing = current.charSpacing;
+    const wordSpacing = current.wordSpacing;
+    const fontDirection = current.fontDirection;
+    const textHScale = current.textHScale * fontDirection;
+    const glyphsLength = glyphs.length;
+    const vertical = font.vertical;
+    const spacingDir = vertical ? 1 : -1;
+    const defaultVMetrics = font.defaultVMetrics;
+    const widthAdvanceScale = fontSize * current.fontMatrix[0];
+    const simpleFillText = current.textRenderingMode === _util.TextRenderingMode.FILL && !font.disableFontFace && !current.patternFill;
+    ctx.save();
+    let patternTransform;
+
+    if (current.patternFill) {
       ctx.save();
-      let patternTransform;
+      const pattern = current.fillColor.getPattern(ctx, this, ctx.mozCurrentTransformInverse);
+      patternTransform = ctx.mozCurrentTransform;
+      ctx.restore();
+      ctx.fillStyle = pattern;
+    }
 
-      if (current.patternFill) {
-        ctx.save();
-        const pattern = current.fillColor.getPattern(ctx, this, ctx.mozCurrentTransformInverse);
-        patternTransform = ctx.mozCurrentTransform;
-        ctx.restore();
-        ctx.fillStyle = pattern;
-      }
+    ctx.transform.apply(ctx, current.textMatrix);
+    ctx.translate(current.x, current.y + current.textRise);
 
-      ctx.transform.apply(ctx, current.textMatrix);
-      ctx.translate(current.x, current.y + current.textRise);
+    if (fontDirection > 0) {
+      ctx.scale(textHScale, -1);
+    } else {
+      ctx.scale(textHScale, 1);
+    }
 
-      if (fontDirection > 0) {
-        ctx.scale(textHScale, -1);
-      } else {
-        ctx.scale(textHScale, 1);
+    let lineWidth = current.lineWidth;
+    let resetLineWidthToOne = false;
+    const scale = current.textMatrixScale;
+
+    if (scale === 0 || lineWidth === 0) {
+      const fillStrokeMode = current.textRenderingMode & _util.TextRenderingMode.FILL_STROKE_MASK;
+
+      if (fillStrokeMode === _util.TextRenderingMode.STROKE || fillStrokeMode === _util.TextRenderingMode.FILL_STROKE) {
+        this._cachedGetSinglePixelWidth = null;
+        lineWidth = this.getSinglePixelWidth();
+        resetLineWidthToOne = lineWidth < 0;
       }
+    } else {
+      lineWidth /= scale;
+    }
+
+    if (fontSizeScale !== 1.0) {
+      ctx.scale(fontSizeScale, fontSizeScale);
+      lineWidth /= fontSizeScale;
+    }
 
-      let lineWidth = current.lineWidth;
-      let resetLineWidthToOne = false;
-      const scale = current.textMatrixScale;
+    ctx.lineWidth = lineWidth;
+    let x = 0,
+        i;
 
-      if (scale === 0 || lineWidth === 0) {
-        const fillStrokeMode = current.textRenderingMode & _util.TextRenderingMode.FILL_STROKE_MASK;
+    for (i = 0; i < glyphsLength; ++i) {
+      const glyph = glyphs[i];
 
-        if (fillStrokeMode === _util.TextRenderingMode.STROKE || fillStrokeMode === _util.TextRenderingMode.FILL_STROKE) {
-          this._cachedGetSinglePixelWidth = null;
-          lineWidth = this.getSinglePixelWidth();
-          resetLineWidthToOne = lineWidth < 0;
-        }
-      } else {
-        lineWidth /= scale;
+      if ((0, _util.isNum)(glyph)) {
+        x += spacingDir * glyph * fontSize / 1000;
+        continue;
       }
 
-      if (fontSizeScale !== 1.0) {
-        ctx.scale(fontSizeScale, fontSizeScale);
-        lineWidth /= fontSizeScale;
-      }
+      let restoreNeeded = false;
+      const spacing = (glyph.isSpace ? wordSpacing : 0) + charSpacing;
+      const character = glyph.fontChar;
+      const accent = glyph.accent;
+      let scaledX, scaledY;
+      let width = glyph.width;
 
-      ctx.lineWidth = lineWidth;
-      let x = 0,
-          i;
+      if (vertical) {
+        const vmetric = glyph.vmetric || defaultVMetrics;
+        const vx = -(glyph.vmetric ? vmetric[1] : width * 0.5) * widthAdvanceScale;
+        const vy = vmetric[2] * widthAdvanceScale;
+        width = vmetric ? -vmetric[0] : width;
+        scaledX = vx / fontSizeScale;
+        scaledY = (x + vy) / fontSizeScale;
+      } else {
+        scaledX = x / fontSizeScale;
+        scaledY = 0;
+      }
 
-      for (i = 0; i < glyphsLength; ++i) {
-        const glyph = glyphs[i];
+      if (font.remeasure && width > 0) {
+        const measuredWidth = ctx.measureText(character).width * 1000 / fontSize * fontSizeScale;
 
-        if ((0, _util.isNum)(glyph)) {
-          x += spacingDir * glyph * fontSize / 1000;
-          continue;
+        if (width < measuredWidth && this.isFontSubpixelAAEnabled) {
+          const characterScaleX = width / measuredWidth;
+          restoreNeeded = true;
+          ctx.save();
+          ctx.scale(characterScaleX, 1);
+          scaledX /= characterScaleX;
+        } else if (width !== measuredWidth) {
+          scaledX += (width - measuredWidth) / 2000 * fontSize / fontSizeScale;
         }
+      }
 
-        let restoreNeeded = false;
-        const spacing = (glyph.isSpace ? wordSpacing : 0) + charSpacing;
-        const character = glyph.fontChar;
-        const accent = glyph.accent;
-        let scaledX, scaledY;
-        let width = glyph.width;
-
-        if (vertical) {
-          const vmetric = glyph.vmetric || defaultVMetrics;
-          const vx = -(glyph.vmetric ? vmetric[1] : width * 0.5) * widthAdvanceScale;
-          const vy = vmetric[2] * widthAdvanceScale;
-          width = vmetric ? -vmetric[0] : width;
-          scaledX = vx / fontSizeScale;
-          scaledY = (x + vy) / fontSizeScale;
+      if (this.contentVisible && (glyph.isInFont || font.missingFile)) {
+        if (simpleFillText && !accent) {
+          ctx.fillText(character, scaledX, scaledY);
         } else {
-          scaledX = x / fontSizeScale;
-          scaledY = 0;
-        }
+          this.paintChar(character, scaledX, scaledY, patternTransform, resetLineWidthToOne);
 
-        if (font.remeasure && width > 0) {
-          const measuredWidth = ctx.measureText(character).width * 1000 / fontSize * fontSizeScale;
-
-          if (width < measuredWidth && this.isFontSubpixelAAEnabled) {
-            const characterScaleX = width / measuredWidth;
-            restoreNeeded = true;
-            ctx.save();
-            ctx.scale(characterScaleX, 1);
-            scaledX /= characterScaleX;
-          } else if (width !== measuredWidth) {
-            scaledX += (width - measuredWidth) / 2000 * fontSize / fontSizeScale;
+          if (accent) {
+            const scaledAccentX = scaledX + fontSize * accent.offset.x / fontSizeScale;
+            const scaledAccentY = scaledY - fontSize * accent.offset.y / fontSizeScale;
+            this.paintChar(accent.fontChar, scaledAccentX, scaledAccentY, patternTransform, resetLineWidthToOne);
           }
         }
+      }
 
-        if (this.contentVisible && (glyph.isInFont || font.missingFile)) {
-          if (simpleFillText && !accent) {
-            ctx.fillText(character, scaledX, scaledY);
-          } else {
-            this.paintChar(character, scaledX, scaledY, patternTransform, resetLineWidthToOne);
+      let charWidth;
 
-            if (accent) {
-              const scaledAccentX = scaledX + fontSize * accent.offset.x / fontSizeScale;
-              const scaledAccentY = scaledY - fontSize * accent.offset.y / fontSizeScale;
-              this.paintChar(accent.fontChar, scaledAccentX, scaledAccentY, patternTransform, resetLineWidthToOne);
-            }
-          }
-        }
+      if (vertical) {
+        charWidth = width * widthAdvanceScale - spacing * fontDirection;
+      } else {
+        charWidth = width * widthAdvanceScale + spacing * fontDirection;
+      }
 
-        let charWidth;
+      x += charWidth;
 
-        if (vertical) {
-          charWidth = width * widthAdvanceScale - spacing * fontDirection;
-        } else {
-          charWidth = width * widthAdvanceScale + spacing * fontDirection;
-        }
+      if (restoreNeeded) {
+        ctx.restore();
+      }
+    }
 
-        x += charWidth;
+    if (vertical) {
+      current.y -= x;
+    } else {
+      current.x += x * textHScale;
+    }
 
-        if (restoreNeeded) {
-          ctx.restore();
-        }
+    ctx.restore();
+    return undefined;
+  }
+
+  showType3Text(glyphs) {
+    const ctx = this.ctx;
+    const current = this.current;
+    const font = current.font;
+    const fontSize = current.fontSize;
+    const fontDirection = current.fontDirection;
+    const spacingDir = font.vertical ? 1 : -1;
+    const charSpacing = current.charSpacing;
+    const wordSpacing = current.wordSpacing;
+    const textHScale = current.textHScale * fontDirection;
+    const fontMatrix = current.fontMatrix || _util.FONT_IDENTITY_MATRIX;
+    const glyphsLength = glyphs.length;
+    const isTextInvisible = current.textRenderingMode === _util.TextRenderingMode.INVISIBLE;
+    let i, glyph, width, spacingLength;
+
+    if (isTextInvisible || fontSize === 0) {
+      return;
+    }
+
+    this._cachedGetSinglePixelWidth = null;
+    ctx.save();
+    ctx.transform.apply(ctx, current.textMatrix);
+    ctx.translate(current.x, current.y);
+    ctx.scale(textHScale, fontDirection);
+
+    for (i = 0; i < glyphsLength; ++i) {
+      glyph = glyphs[i];
+
+      if ((0, _util.isNum)(glyph)) {
+        spacingLength = spacingDir * glyph * fontSize / 1000;
+        this.ctx.translate(spacingLength, 0);
+        current.x += spacingLength * textHScale;
+        continue;
       }
 
-      if (vertical) {
-        current.y -= x;
-      } else {
-        current.x += x * textHScale;
+      const spacing = (glyph.isSpace ? wordSpacing : 0) + charSpacing;
+      const operatorList = font.charProcOperatorList[glyph.operatorListId];
+
+      if (!operatorList) {
+        (0, _util.warn)(`Type3 character "${glyph.operatorListId}" is not available.`);
+        continue;
       }
 
-      ctx.restore();
-      return undefined;
+      if (this.contentVisible) {
+        this.processingType3 = glyph;
+        this.save();
+        ctx.scale(fontSize, fontSize);
+        ctx.transform.apply(ctx, fontMatrix);
+        this.executeOperatorList(operatorList);
+        this.restore();
+      }
+
+      const transformed = _util.Util.applyTransform([glyph.width, 0], fontMatrix);
+
+      width = transformed[0] * fontSize + spacing;
+      ctx.translate(width, 0);
+      current.x += width * textHScale;
     }
 
-    showType3Text(glyphs) {
-      const ctx = this.ctx;
-      const current = this.current;
-      const font = current.font;
-      const fontSize = current.fontSize;
-      const fontDirection = current.fontDirection;
-      const spacingDir = font.vertical ? 1 : -1;
-      const charSpacing = current.charSpacing;
-      const wordSpacing = current.wordSpacing;
-      const textHScale = current.textHScale * fontDirection;
-      const fontMatrix = current.fontMatrix || _util.FONT_IDENTITY_MATRIX;
-      const glyphsLength = glyphs.length;
-      const isTextInvisible = current.textRenderingMode === _util.TextRenderingMode.INVISIBLE;
-      let i, glyph, width, spacingLength;
-
-      if (isTextInvisible || fontSize === 0) {
-        return;
-      }
+    ctx.restore();
+    this.processingType3 = null;
+  }
 
-      this._cachedGetSinglePixelWidth = null;
-      ctx.save();
-      ctx.transform.apply(ctx, current.textMatrix);
-      ctx.translate(current.x, current.y);
-      ctx.scale(textHScale, fontDirection);
-
-      for (i = 0; i < glyphsLength; ++i) {
-        glyph = glyphs[i];
-
-        if ((0, _util.isNum)(glyph)) {
-          spacingLength = spacingDir * glyph * fontSize / 1000;
-          this.ctx.translate(spacingLength, 0);
-          current.x += spacingLength * textHScale;
-          continue;
-        }
+  setCharWidth(xWidth, yWidth) {}
 
-        const spacing = (glyph.isSpace ? wordSpacing : 0) + charSpacing;
-        const operatorList = font.charProcOperatorList[glyph.operatorListId];
+  setCharWidthAndBounds(xWidth, yWidth, llx, lly, urx, ury) {
+    this.ctx.rect(llx, lly, urx - llx, ury - lly);
+    this.clip();
+    this.endPath();
+  }
 
-        if (!operatorList) {
-          (0, _util.warn)(`Type3 character "${glyph.operatorListId}" is not available.`);
-          continue;
-        }
+  getColorN_Pattern(IR) {
+    let pattern;
 
-        if (this.contentVisible) {
-          this.processingType3 = glyph;
-          this.save();
-          ctx.scale(fontSize, fontSize);
-          ctx.transform.apply(ctx, fontMatrix);
-          this.executeOperatorList(operatorList);
-          this.restore();
+    if (IR[0] === "TilingPattern") {
+      const color = IR[1];
+      const baseTransform = this.baseTransform || this.ctx.mozCurrentTransform.slice();
+      const canvasGraphicsFactory = {
+        createCanvasGraphics: ctx => {
+          return new CanvasGraphics(ctx, this.commonObjs, this.objs, this.canvasFactory);
         }
+      };
+      pattern = new _pattern_helper.TilingPattern(IR, color, this.ctx, canvasGraphicsFactory, baseTransform);
+    } else {
+      pattern = this._getPattern(IR[1], IR[2]);
+    }
 
-        const transformed = _util.Util.applyTransform([glyph.width, 0], fontMatrix);
+    return pattern;
+  }
 
-        width = transformed[0] * fontSize + spacing;
-        ctx.translate(width, 0);
-        current.x += width * textHScale;
-      }
+  setStrokeColorN() {
+    this.current.strokeColor = this.getColorN_Pattern(arguments);
+  }
 
-      ctx.restore();
-      this.processingType3 = null;
-    }
+  setFillColorN() {
+    this.current.fillColor = this.getColorN_Pattern(arguments);
+    this.current.patternFill = true;
+  }
 
-    setCharWidth(xWidth, yWidth) {}
+  setStrokeRGBColor(r, g, b) {
+    const color = _util.Util.makeHexColor(r, g, b);
 
-    setCharWidthAndBounds(xWidth, yWidth, llx, lly, urx, ury) {
-      this.ctx.rect(llx, lly, urx - llx, ury - lly);
-      this.clip();
-      this.endPath();
-    }
+    this.ctx.strokeStyle = color;
+    this.current.strokeColor = color;
+  }
 
-    getColorN_Pattern(IR) {
-      let pattern;
+  setFillRGBColor(r, g, b) {
+    const color = _util.Util.makeHexColor(r, g, b);
 
-      if (IR[0] === "TilingPattern") {
-        const color = IR[1];
-        const baseTransform = this.baseTransform || this.ctx.mozCurrentTransform.slice();
-        const canvasGraphicsFactory = {
-          createCanvasGraphics: ctx => {
-            return new CanvasGraphics(ctx, this.commonObjs, this.objs, this.canvasFactory);
-          }
-        };
-        pattern = new _pattern_helper.TilingPattern(IR, color, this.ctx, canvasGraphicsFactory, baseTransform);
-      } else {
-        pattern = this._getPattern(IR[1]);
-      }
+    this.ctx.fillStyle = color;
+    this.current.fillColor = color;
+    this.current.patternFill = false;
+  }
 
-      return pattern;
-    }
+  _getPattern(objId, matrix = null) {
+    let pattern;
 
-    setStrokeColorN() {
-      this.current.strokeColor = this.getColorN_Pattern(arguments);
+    if (this.cachedPatterns.has(objId)) {
+      pattern = this.cachedPatterns.get(objId);
+    } else {
+      pattern = (0, _pattern_helper.getShadingPattern)(this.objs.get(objId), this.cachedCanvasPatterns);
+      this.cachedPatterns.set(objId, pattern);
     }
 
-    setFillColorN() {
-      this.current.fillColor = this.getColorN_Pattern(arguments);
-      this.current.patternFill = true;
+    if (matrix) {
+      pattern.matrix = matrix;
     }
 
-    setStrokeRGBColor(r, g, b) {
-      const color = _util.Util.makeHexColor(r, g, b);
+    return pattern;
+  }
 
-      this.ctx.strokeStyle = color;
-      this.current.strokeColor = color;
+  shadingFill(objId) {
+    if (!this.contentVisible) {
+      return;
     }
 
-    setFillRGBColor(r, g, b) {
-      const color = _util.Util.makeHexColor(r, g, b);
+    const ctx = this.ctx;
+    this.save();
 
-      this.ctx.fillStyle = color;
-      this.current.fillColor = color;
-      this.current.patternFill = false;
-    }
+    const pattern = this._getPattern(objId);
 
-    _getPattern(objId) {
-      if (this.cachedPatterns.has(objId)) {
-        return this.cachedPatterns.get(objId);
-      }
+    ctx.fillStyle = pattern.getPattern(ctx, this, ctx.mozCurrentTransformInverse, true);
+    const inv = ctx.mozCurrentTransformInverse;
 
-      const pattern = (0, _pattern_helper.getShadingPattern)(this.objs.get(objId));
-      this.cachedPatterns.set(objId, pattern);
-      return pattern;
-    }
+    if (inv) {
+      const canvas = ctx.canvas;
+      const width = canvas.width;
+      const height = canvas.height;
 
-    shadingFill(objId) {
-      if (!this.contentVisible) {
-        return;
-      }
+      const bl = _util.Util.applyTransform([0, 0], inv);
 
-      const ctx = this.ctx;
-      this.save();
+      const br = _util.Util.applyTransform([0, height], inv);
 
-      const pattern = this._getPattern(objId);
+      const ul = _util.Util.applyTransform([width, 0], inv);
 
-      ctx.fillStyle = pattern.getPattern(ctx, this, ctx.mozCurrentTransformInverse, true);
-      const inv = ctx.mozCurrentTransformInverse;
+      const ur = _util.Util.applyTransform([width, height], inv);
 
-      if (inv) {
-        const canvas = ctx.canvas;
-        const width = canvas.width;
-        const height = canvas.height;
+      const x0 = Math.min(bl[0], br[0], ul[0], ur[0]);
+      const y0 = Math.min(bl[1], br[1], ul[1], ur[1]);
+      const x1 = Math.max(bl[0], br[0], ul[0], ur[0]);
+      const y1 = Math.max(bl[1], br[1], ul[1], ur[1]);
+      this.ctx.fillRect(x0, y0, x1 - x0, y1 - y0);
+    } else {
+      this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10);
+    }
 
-        const bl = _util.Util.applyTransform([0, 0], inv);
+    this.restore();
+  }
 
-        const br = _util.Util.applyTransform([0, height], inv);
+  beginInlineImage() {
+    (0, _util.unreachable)("Should not call beginInlineImage");
+  }
 
-        const ul = _util.Util.applyTransform([width, 0], inv);
+  beginImageData() {
+    (0, _util.unreachable)("Should not call beginImageData");
+  }
 
-        const ur = _util.Util.applyTransform([width, height], inv);
+  paintFormXObjectBegin(matrix, bbox) {
+    if (!this.contentVisible) {
+      return;
+    }
 
-        const x0 = Math.min(bl[0], br[0], ul[0], ur[0]);
-        const y0 = Math.min(bl[1], br[1], ul[1], ur[1]);
-        const x1 = Math.max(bl[0], br[0], ul[0], ur[0]);
-        const y1 = Math.max(bl[1], br[1], ul[1], ur[1]);
-        this.ctx.fillRect(x0, y0, x1 - x0, y1 - y0);
-      } else {
-        this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10);
-      }
+    this.save();
+    this.baseTransformStack.push(this.baseTransform);
 
-      this.restore();
+    if (Array.isArray(matrix) && matrix.length === 6) {
+      this.transform.apply(this, matrix);
     }
 
-    beginInlineImage() {
-      (0, _util.unreachable)("Should not call beginInlineImage");
+    this.baseTransform = this.ctx.mozCurrentTransform;
+
+    if (bbox) {
+      const width = bbox[2] - bbox[0];
+      const height = bbox[3] - bbox[1];
+      this.ctx.rect(bbox[0], bbox[1], width, height);
+      this.clip();
+      this.endPath();
     }
+  }
 
-    beginImageData() {
-      (0, _util.unreachable)("Should not call beginImageData");
+  paintFormXObjectEnd() {
+    if (!this.contentVisible) {
+      return;
     }
 
-    paintFormXObjectBegin(matrix, bbox) {
-      if (!this.contentVisible) {
-        return;
-      }
+    this.restore();
+    this.baseTransform = this.baseTransformStack.pop();
+  }
 
-      this.save();
-      this.baseTransformStack.push(this.baseTransform);
+  beginGroup(group) {
+    if (!this.contentVisible) {
+      return;
+    }
 
-      if (Array.isArray(matrix) && matrix.length === 6) {
-        this.transform.apply(this, matrix);
-      }
+    this.save();
+    const currentCtx = this.ctx;
 
-      this.baseTransform = this.ctx.mozCurrentTransform;
+    if (!group.isolated) {
+      (0, _util.info)("TODO: Support non-isolated groups.");
+    }
 
-      if (bbox) {
-        const width = bbox[2] - bbox[0];
-        const height = bbox[3] - bbox[1];
-        this.ctx.rect(bbox[0], bbox[1], width, height);
-        this.clip();
-        this.endPath();
-      }
+    if (group.knockout) {
+      (0, _util.warn)("Knockout groups not supported.");
     }
 
-    paintFormXObjectEnd() {
-      if (!this.contentVisible) {
-        return;
-      }
+    const currentTransform = currentCtx.mozCurrentTransform;
 
-      this.restore();
-      this.baseTransform = this.baseTransformStack.pop();
+    if (group.matrix) {
+      currentCtx.transform.apply(currentCtx, group.matrix);
     }
 
-    beginGroup(group) {
-      if (!this.contentVisible) {
-        return;
-      }
+    if (!group.bbox) {
+      throw new Error("Bounding box is required.");
+    }
 
-      this.save();
-      const currentCtx = this.ctx;
+    let bounds = _util.Util.getAxialAlignedBoundingBox(group.bbox, currentCtx.mozCurrentTransform);
 
-      if (!group.isolated) {
-        (0, _util.info)("TODO: Support non-isolated groups.");
-      }
+    const canvasBounds = [0, 0, currentCtx.canvas.width, currentCtx.canvas.height];
+    bounds = _util.Util.intersect(bounds, canvasBounds) || [0, 0, 0, 0];
+    const offsetX = Math.floor(bounds[0]);
+    const offsetY = Math.floor(bounds[1]);
+    let drawnWidth = Math.max(Math.ceil(bounds[2]) - offsetX, 1);
+    let drawnHeight = Math.max(Math.ceil(bounds[3]) - offsetY, 1);
+    let scaleX = 1,
+        scaleY = 1;
 
-      if (group.knockout) {
-        (0, _util.warn)("Knockout groups not supported.");
-      }
+    if (drawnWidth > MAX_GROUP_SIZE) {
+      scaleX = drawnWidth / MAX_GROUP_SIZE;
+      drawnWidth = MAX_GROUP_SIZE;
+    }
 
-      const currentTransform = currentCtx.mozCurrentTransform;
+    if (drawnHeight > MAX_GROUP_SIZE) {
+      scaleY = drawnHeight / MAX_GROUP_SIZE;
+      drawnHeight = MAX_GROUP_SIZE;
+    }
 
-      if (group.matrix) {
-        currentCtx.transform.apply(currentCtx, group.matrix);
-      }
+    let cacheId = "groupAt" + this.groupLevel;
 
-      if (!group.bbox) {
-        throw new Error("Bounding box is required.");
-      }
+    if (group.smask) {
+      cacheId += "_smask_" + this.smaskCounter++ % 2;
+    }
 
-      let bounds = _util.Util.getAxialAlignedBoundingBox(group.bbox, currentCtx.mozCurrentTransform);
+    const scratchCanvas = this.cachedCanvases.getCanvas(cacheId, drawnWidth, drawnHeight, true);
+    const groupCtx = scratchCanvas.context;
+    groupCtx.scale(1 / scaleX, 1 / scaleY);
+    groupCtx.translate(-offsetX, -offsetY);
+    groupCtx.transform.apply(groupCtx, currentTransform);
 
-      const canvasBounds = [0, 0, currentCtx.canvas.width, currentCtx.canvas.height];
-      bounds = _util.Util.intersect(bounds, canvasBounds) || [0, 0, 0, 0];
-      const offsetX = Math.floor(bounds[0]);
-      const offsetY = Math.floor(bounds[1]);
-      let drawnWidth = Math.max(Math.ceil(bounds[2]) - offsetX, 1);
-      let drawnHeight = Math.max(Math.ceil(bounds[3]) - offsetY, 1);
-      let scaleX = 1,
-          scaleY = 1;
+    if (group.smask) {
+      this.smaskStack.push({
+        canvas: scratchCanvas.canvas,
+        context: groupCtx,
+        offsetX,
+        offsetY,
+        scaleX,
+        scaleY,
+        subtype: group.smask.subtype,
+        backdrop: group.smask.backdrop,
+        transferMap: group.smask.transferMap || null,
+        startTransformInverse: null
+      });
+    } else {
+      currentCtx.setTransform(1, 0, 0, 1, 0, 0);
+      currentCtx.translate(offsetX, offsetY);
+      currentCtx.scale(scaleX, scaleY);
+    }
 
-      if (drawnWidth > MAX_GROUP_SIZE) {
-        scaleX = drawnWidth / MAX_GROUP_SIZE;
-        drawnWidth = MAX_GROUP_SIZE;
-      }
+    copyCtxState(currentCtx, groupCtx);
+    this.ctx = groupCtx;
+    this.setGState([["BM", "source-over"], ["ca", 1], ["CA", 1]]);
+    this.groupStack.push(currentCtx);
+    this.groupLevel++;
+    this.current.activeSMask = null;
+  }
 
-      if (drawnHeight > MAX_GROUP_SIZE) {
-        scaleY = drawnHeight / MAX_GROUP_SIZE;
-        drawnHeight = MAX_GROUP_SIZE;
-      }
+  endGroup(group) {
+    if (!this.contentVisible) {
+      return;
+    }
 
-      let cacheId = "groupAt" + this.groupLevel;
+    this.groupLevel--;
+    const groupCtx = this.ctx;
+    this.ctx = this.groupStack.pop();
+    this.ctx.imageSmoothingEnabled = false;
 
-      if (group.smask) {
-        cacheId += "_smask_" + this.smaskCounter++ % 2;
-      }
+    if (group.smask) {
+      this.tempSMask = this.smaskStack.pop();
+    } else {
+      this.ctx.drawImage(groupCtx.canvas, 0, 0);
+    }
 
-      const scratchCanvas = this.cachedCanvases.getCanvas(cacheId, drawnWidth, drawnHeight, true);
-      const groupCtx = scratchCanvas.context;
-      groupCtx.scale(1 / scaleX, 1 / scaleY);
-      groupCtx.translate(-offsetX, -offsetY);
-      groupCtx.transform.apply(groupCtx, currentTransform);
-
-      if (group.smask) {
-        this.smaskStack.push({
-          canvas: scratchCanvas.canvas,
-          context: groupCtx,
-          offsetX,
-          offsetY,
-          scaleX,
-          scaleY,
-          subtype: group.smask.subtype,
-          backdrop: group.smask.backdrop,
-          transferMap: group.smask.transferMap || null,
-          startTransformInverse: null
-        });
-      } else {
-        currentCtx.setTransform(1, 0, 0, 1, 0, 0);
-        currentCtx.translate(offsetX, offsetY);
-        currentCtx.scale(scaleX, scaleY);
-      }
+    this.restore();
+  }
 
-      copyCtxState(currentCtx, groupCtx);
-      this.ctx = groupCtx;
-      this.setGState([["BM", "source-over"], ["ca", 1], ["CA", 1]]);
-      this.groupStack.push(currentCtx);
-      this.groupLevel++;
-      this.current.activeSMask = null;
+  beginAnnotations() {
+    this.save();
+
+    if (this.baseTransform) {
+      this.ctx.setTransform.apply(this.ctx, this.baseTransform);
     }
+  }
 
-    endGroup(group) {
-      if (!this.contentVisible) {
-        return;
-      }
+  endAnnotations() {
+    this.restore();
+  }
 
-      this.groupLevel--;
-      const groupCtx = this.ctx;
-      this.ctx = this.groupStack.pop();
+  beginAnnotation(id, rect, transform, matrix) {
+    this.save();
+    resetCtxToDefault(this.ctx);
+    this.current = new CanvasExtraState();
 
-      if (this.ctx.imageSmoothingEnabled !== undefined) {
-        this.ctx.imageSmoothingEnabled = false;
-      } else {
-        this.ctx.mozImageSmoothingEnabled = false;
-      }
+    if (Array.isArray(rect) && rect.length === 4) {
+      const width = rect[2] - rect[0];
+      const height = rect[3] - rect[1];
+      this.ctx.rect(rect[0], rect[1], width, height);
+      this.clip();
+      this.endPath();
+    }
 
-      if (group.smask) {
-        this.tempSMask = this.smaskStack.pop();
-      } else {
-        this.ctx.drawImage(groupCtx.canvas, 0, 0);
-      }
+    this.transform.apply(this, transform);
+    this.transform.apply(this, matrix);
+  }
 
-      this.restore();
+  endAnnotation() {
+    this.restore();
+  }
+
+  paintImageMaskXObject(img) {
+    if (!this.contentVisible) {
+      return;
     }
 
-    beginAnnotations() {
-      this.save();
+    const ctx = this.ctx;
+    const width = img.width,
+          height = img.height;
+    const glyph = this.processingType3;
 
-      if (this.baseTransform) {
-        this.ctx.setTransform.apply(this.ctx, this.baseTransform);
+    if (COMPILE_TYPE3_GLYPHS && glyph && glyph.compiled === undefined) {
+      if (width <= MAX_SIZE_TO_COMPILE && height <= MAX_SIZE_TO_COMPILE) {
+        glyph.compiled = compileType3Glyph({
+          data: img.data,
+          width,
+          height
+        });
+      } else {
+        glyph.compiled = null;
       }
     }
 
-    endAnnotations() {
-      this.restore();
+    if (glyph?.compiled) {
+      glyph.compiled(ctx);
+      return;
     }
 
-    beginAnnotation(id, rect, transform, matrix) {
-      this.save();
-      resetCtxToDefault(this.ctx);
-      this.current = new CanvasExtraState();
+    const mask = this._createMaskCanvas(img);
 
-      if (Array.isArray(rect) && rect.length === 4) {
-        const width = rect[2] - rect[0];
-        const height = rect[3] - rect[1];
-        this.ctx.rect(rect[0], rect[1], width, height);
-        this.clip();
-        this.endPath();
-      }
+    const maskCanvas = mask.canvas;
+    ctx.save();
+    ctx.setTransform(1, 0, 0, 1, 0, 0);
+    ctx.drawImage(maskCanvas, mask.offsetX, mask.offsetY);
+    ctx.restore();
+  }
 
-      this.transform.apply(this, transform);
-      this.transform.apply(this, matrix);
+  paintImageMaskXObjectRepeat(imgData, scaleX, skewX = 0, skewY = 0, scaleY, positions) {
+    if (!this.contentVisible) {
+      return;
     }
 
-    endAnnotation() {
-      this.restore();
-    }
+    const ctx = this.ctx;
+    ctx.save();
+    const currentTransform = ctx.mozCurrentTransform;
+    ctx.transform(scaleX, skewX, skewY, scaleY, 0, 0);
 
-    paintImageMaskXObject(img) {
-      if (!this.contentVisible) {
-        return;
-      }
+    const mask = this._createMaskCanvas(imgData);
 
-      const ctx = this.ctx;
-      const width = img.width,
-            height = img.height;
-      const glyph = this.processingType3;
-
-      if (COMPILE_TYPE3_GLYPHS && glyph && glyph.compiled === undefined) {
-        if (width <= MAX_SIZE_TO_COMPILE && height <= MAX_SIZE_TO_COMPILE) {
-          glyph.compiled = compileType3Glyph({
-            data: img.data,
-            width,
-            height
-          });
-        } else {
-          glyph.compiled = null;
-        }
-      }
+    ctx.setTransform(1, 0, 0, 1, 0, 0);
 
-      if (glyph?.compiled) {
-        glyph.compiled(ctx);
-        return;
-      }
+    for (let i = 0, ii = positions.length; i < ii; i += 2) {
+      const trans = _util.Util.transform(currentTransform, [scaleX, skewX, skewY, scaleY, positions[i], positions[i + 1]]);
 
-      const mask = this._createMaskCanvas(img);
+      const [x, y] = _util.Util.applyTransform([0, 0], trans);
 
-      const maskCanvas = mask.canvas;
-      ctx.save();
-      ctx.setTransform(1, 0, 0, 1, 0, 0);
-      ctx.drawImage(maskCanvas, mask.offsetX, mask.offsetY);
-      ctx.restore();
+      ctx.drawImage(mask.canvas, x, y);
     }
 
-    paintImageMaskXObjectRepeat(imgData, scaleX, skewX = 0, skewY = 0, scaleY, positions) {
-      if (!this.contentVisible) {
-        return;
-      }
+    ctx.restore();
+  }
 
-      const ctx = this.ctx;
-      ctx.save();
-      const currentTransform = ctx.mozCurrentTransform;
-      ctx.transform(scaleX, skewX, skewY, scaleY, 0, 0);
+  paintImageMaskXObjectGroup(images) {
+    if (!this.contentVisible) {
+      return;
+    }
 
-      const mask = this._createMaskCanvas(imgData);
+    const ctx = this.ctx;
+    const fillColor = this.current.fillColor;
+    const isPatternFill = this.current.patternFill;
 
-      ctx.setTransform(1, 0, 0, 1, 0, 0);
+    for (let i = 0, ii = images.length; i < ii; i++) {
+      const image = images[i];
+      const width = image.width,
+            height = image.height;
+      const maskCanvas = this.cachedCanvases.getCanvas("maskCanvas", width, height);
+      const maskCtx = maskCanvas.context;
+      maskCtx.save();
+      putBinaryImageMask(maskCtx, image);
+      maskCtx.globalCompositeOperation = "source-in";
+      maskCtx.fillStyle = isPatternFill ? fillColor.getPattern(maskCtx, this, ctx.mozCurrentTransformInverse, false) : fillColor;
+      maskCtx.fillRect(0, 0, width, height);
+      maskCtx.restore();
+      ctx.save();
+      ctx.transform.apply(ctx, image.transform);
+      ctx.scale(1, -1);
+      ctx.drawImage(maskCanvas.canvas, 0, 0, width, height, 0, -1, 1, 1);
+      ctx.restore();
+    }
+  }
 
-      for (let i = 0, ii = positions.length; i < ii; i += 2) {
-        const trans = _util.Util.transform(currentTransform, [scaleX, skewX, skewY, scaleY, positions[i], positions[i + 1]]);
+  paintImageXObject(objId) {
+    if (!this.contentVisible) {
+      return;
+    }
 
-        const [x, y] = _util.Util.applyTransform([0, 0], trans);
+    const imgData = objId.startsWith("g_") ? this.commonObjs.get(objId) : this.objs.get(objId);
 
-        ctx.drawImage(mask.canvas, x, y);
-      }
+    if (!imgData) {
+      (0, _util.warn)("Dependent image isn't ready yet");
+      return;
+    }
 
-      ctx.restore();
+    this.paintInlineImageXObject(imgData);
+  }
+
+  paintImageXObjectRepeat(objId, scaleX, scaleY, positions) {
+    if (!this.contentVisible) {
+      return;
     }
 
-    paintImageMaskXObjectGroup(images) {
-      if (!this.contentVisible) {
-        return;
-      }
+    const imgData = objId.startsWith("g_") ? this.commonObjs.get(objId) : this.objs.get(objId);
 
-      const ctx = this.ctx;
-      const fillColor = this.current.fillColor;
-      const isPatternFill = this.current.patternFill;
-
-      for (let i = 0, ii = images.length; i < ii; i++) {
-        const image = images[i];
-        const width = image.width,
-              height = image.height;
-        const maskCanvas = this.cachedCanvases.getCanvas("maskCanvas", width, height);
-        const maskCtx = maskCanvas.context;
-        maskCtx.save();
-        putBinaryImageMask(maskCtx, image);
-        maskCtx.globalCompositeOperation = "source-in";
-        maskCtx.fillStyle = isPatternFill ? fillColor.getPattern(maskCtx, this, ctx.mozCurrentTransformInverse, false) : fillColor;
-        maskCtx.fillRect(0, 0, width, height);
-        maskCtx.restore();
-        ctx.save();
-        ctx.transform.apply(ctx, image.transform);
-        ctx.scale(1, -1);
-        ctx.drawImage(maskCanvas.canvas, 0, 0, width, height, 0, -1, 1, 1);
-        ctx.restore();
-      }
+    if (!imgData) {
+      (0, _util.warn)("Dependent image isn't ready yet");
+      return;
     }
 
-    paintImageXObject(objId) {
-      if (!this.contentVisible) {
-        return;
-      }
+    const width = imgData.width;
+    const height = imgData.height;
+    const map = [];
 
-      const imgData = objId.startsWith("g_") ? this.commonObjs.get(objId) : this.objs.get(objId);
+    for (let i = 0, ii = positions.length; i < ii; i += 2) {
+      map.push({
+        transform: [scaleX, 0, 0, scaleY, positions[i], positions[i + 1]],
+        x: 0,
+        y: 0,
+        w: width,
+        h: height
+      });
+    }
 
-      if (!imgData) {
-        (0, _util.warn)("Dependent image isn't ready yet");
-        return;
-      }
+    this.paintInlineImageXObjectGroup(imgData, map);
+  }
 
-      this.paintInlineImageXObject(imgData);
+  paintInlineImageXObject(imgData) {
+    if (!this.contentVisible) {
+      return;
     }
 
-    paintImageXObjectRepeat(objId, scaleX, scaleY, positions) {
-      if (!this.contentVisible) {
-        return;
-      }
+    const width = imgData.width;
+    const height = imgData.height;
+    const ctx = this.ctx;
+    this.save();
+    ctx.scale(1 / width, -1 / height);
+    let imgToPaint;
 
-      const imgData = objId.startsWith("g_") ? this.commonObjs.get(objId) : this.objs.get(objId);
+    if (typeof HTMLElement === "function" && imgData instanceof HTMLElement || !imgData.data) {
+      imgToPaint = imgData;
+    } else {
+      const tmpCanvas = this.cachedCanvases.getCanvas("inlineImage", width, height);
+      const tmpCtx = tmpCanvas.context;
+      putBinaryImageData(tmpCtx, imgData, this.current.transferMaps);
+      imgToPaint = tmpCanvas.canvas;
+    }
 
-      if (!imgData) {
-        (0, _util.warn)("Dependent image isn't ready yet");
-        return;
-      }
+    const scaled = this._scaleImage(imgToPaint, ctx.mozCurrentTransformInverse);
 
-      const width = imgData.width;
-      const height = imgData.height;
-      const map = [];
-
-      for (let i = 0, ii = positions.length; i < ii; i += 2) {
-        map.push({
-          transform: [scaleX, 0, 0, scaleY, positions[i], positions[i + 1]],
-          x: 0,
-          y: 0,
-          w: width,
-          h: height
-        });
-      }
+    ctx.imageSmoothingEnabled = getImageSmoothingEnabled(ctx.mozCurrentTransform, imgData.interpolate);
+    ctx.drawImage(scaled.img, 0, 0, scaled.paintWidth, scaled.paintHeight, 0, -height, width, height);
 
-      this.paintInlineImageXObjectGroup(imgData, map);
+    if (this.imageLayer) {
+      const position = this.getCanvasPosition(0, -height);
+      this.imageLayer.appendImage({
+        imgData,
+        left: position[0],
+        top: position[1],
+        width: width / ctx.mozCurrentTransformInverse[0],
+        height: height / ctx.mozCurrentTransformInverse[3]
+      });
     }
 
-    paintInlineImageXObject(imgData) {
-      if (!this.contentVisible) {
-        return;
-      }
-
-      const width = imgData.width;
-      const height = imgData.height;
-      const ctx = this.ctx;
-      this.save();
-      ctx.scale(1 / width, -1 / height);
-      let imgToPaint;
+    this.restore();
+  }
 
-      if (typeof HTMLElement === "function" && imgData instanceof HTMLElement || !imgData.data) {
-        imgToPaint = imgData;
-      } else {
-        const tmpCanvas = this.cachedCanvases.getCanvas("inlineImage", width, height);
-        const tmpCtx = tmpCanvas.context;
-        putBinaryImageData(tmpCtx, imgData, this.current.transferMaps);
-        imgToPaint = tmpCanvas.canvas;
-      }
+  paintInlineImageXObjectGroup(imgData, map) {
+    if (!this.contentVisible) {
+      return;
+    }
 
-      const scaled = this._scaleImage(imgToPaint, ctx.mozCurrentTransformInverse);
+    const ctx = this.ctx;
+    const w = imgData.width;
+    const h = imgData.height;
+    const tmpCanvas = this.cachedCanvases.getCanvas("inlineImage", w, h);
+    const tmpCtx = tmpCanvas.context;
+    putBinaryImageData(tmpCtx, imgData, this.current.transferMaps);
 
-      ctx.drawImage(scaled.img, 0, 0, scaled.paintWidth, scaled.paintHeight, 0, -height, width, height);
+    for (let i = 0, ii = map.length; i < ii; i++) {
+      const entry = map[i];
+      ctx.save();
+      ctx.transform.apply(ctx, entry.transform);
+      ctx.scale(1, -1);
+      ctx.drawImage(tmpCanvas.canvas, entry.x, entry.y, entry.w, entry.h, 0, -1, 1, 1);
 
       if (this.imageLayer) {
-        const position = this.getCanvasPosition(0, -height);
+        const position = this.getCanvasPosition(entry.x, entry.y);
         this.imageLayer.appendImage({
           imgData,
           left: position[0],
           top: position[1],
-          width: width / ctx.mozCurrentTransformInverse[0],
-          height: height / ctx.mozCurrentTransformInverse[3]
+          width: w,
+          height: h
         });
       }
 
-      this.restore();
+      ctx.restore();
     }
+  }
 
-    paintInlineImageXObjectGroup(imgData, map) {
-      if (!this.contentVisible) {
-        return;
-      }
-
-      const ctx = this.ctx;
-      const w = imgData.width;
-      const h = imgData.height;
-      const tmpCanvas = this.cachedCanvases.getCanvas("inlineImage", w, h);
-      const tmpCtx = tmpCanvas.context;
-      putBinaryImageData(tmpCtx, imgData, this.current.transferMaps);
-
-      for (let i = 0, ii = map.length; i < ii; i++) {
-        const entry = map[i];
-        ctx.save();
-        ctx.transform.apply(ctx, entry.transform);
-        ctx.scale(1, -1);
-        ctx.drawImage(tmpCanvas.canvas, entry.x, entry.y, entry.w, entry.h, 0, -1, 1, 1);
-
-        if (this.imageLayer) {
-          const position = this.getCanvasPosition(entry.x, entry.y);
-          this.imageLayer.appendImage({
-            imgData,
-            left: position[0],
-            top: position[1],
-            width: w,
-            height: h
-          });
-        }
-
-        ctx.restore();
-      }
+  paintSolidColorImageMask() {
+    if (!this.contentVisible) {
+      return;
     }
 
-    paintSolidColorImageMask() {
-      if (!this.contentVisible) {
-        return;
-      }
+    this.ctx.fillRect(0, 0, 1, 1);
+  }
 
-      this.ctx.fillRect(0, 0, 1, 1);
-    }
+  markPoint(tag) {}
 
-    markPoint(tag) {}
+  markPointProps(tag, properties) {}
 
-    markPointProps(tag, properties) {}
+  beginMarkedContent(tag) {
+    this.markedContentStack.push({
+      visible: true
+    });
+  }
 
-    beginMarkedContent(tag) {
+  beginMarkedContentProps(tag, properties) {
+    if (tag === "OC") {
+      this.markedContentStack.push({
+        visible: this.optionalContentConfig.isVisible(properties)
+      });
+    } else {
       this.markedContentStack.push({
         visible: true
       });
     }
 
-    beginMarkedContentProps(tag, properties) {
-      if (tag === "OC") {
-        this.markedContentStack.push({
-          visible: this.optionalContentConfig.isVisible(properties)
-        });
-      } else {
-        this.markedContentStack.push({
-          visible: true
-        });
-      }
-
-      this.contentVisible = this.isContentVisible();
-    }
-
-    endMarkedContent() {
-      this.markedContentStack.pop();
-      this.contentVisible = this.isContentVisible();
-    }
+    this.contentVisible = this.isContentVisible();
+  }
 
-    beginCompat() {}
+  endMarkedContent() {
+    this.markedContentStack.pop();
+    this.contentVisible = this.isContentVisible();
+  }
 
-    endCompat() {}
+  beginCompat() {}
 
-    consumePath() {
-      const ctx = this.ctx;
+  endCompat() {}
 
-      if (this.pendingClip) {
-        if (this.pendingClip === EO_CLIP) {
-          ctx.clip("evenodd");
-        } else {
-          ctx.clip();
-        }
+  consumePath() {
+    const ctx = this.ctx;
 
-        this.pendingClip = null;
+    if (this.pendingClip) {
+      if (this.pendingClip === EO_CLIP) {
+        ctx.clip("evenodd");
+      } else {
+        ctx.clip();
       }
 
-      ctx.beginPath();
+      this.pendingClip = null;
     }
 
-    getSinglePixelWidth() {
-      if (this._cachedGetSinglePixelWidth === null) {
-        const m = this.ctx.mozCurrentTransform;
-        const absDet = Math.abs(m[0] * m[3] - m[2] * m[1]);
-        const sqNorm1 = m[0] ** 2 + m[2] ** 2;
-        const sqNorm2 = m[1] ** 2 + m[3] ** 2;
-        const pixelHeight = Math.sqrt(Math.max(sqNorm1, sqNorm2)) / absDet;
+    ctx.beginPath();
+  }
 
-        if (sqNorm1 !== sqNorm2 && this._combinedScaleFactor * pixelHeight > 1) {
-          this._cachedGetSinglePixelWidth = -(this._combinedScaleFactor * pixelHeight);
-        } else if (absDet > Number.EPSILON) {
-          this._cachedGetSinglePixelWidth = pixelHeight;
-        } else {
-          this._cachedGetSinglePixelWidth = 1;
-        }
+  getSinglePixelWidth() {
+    if (this._cachedGetSinglePixelWidth === null) {
+      const m = this.ctx.mozCurrentTransform;
+      const absDet = Math.abs(m[0] * m[3] - m[2] * m[1]);
+      const sqNorm1 = m[0] ** 2 + m[2] ** 2;
+      const sqNorm2 = m[1] ** 2 + m[3] ** 2;
+      const pixelHeight = Math.sqrt(Math.max(sqNorm1, sqNorm2)) / absDet;
+
+      if (sqNorm1 !== sqNorm2 && this._combinedScaleFactor * pixelHeight > 1) {
+        this._cachedGetSinglePixelWidth = -(this._combinedScaleFactor * pixelHeight);
+      } else if (absDet > Number.EPSILON) {
+        this._cachedGetSinglePixelWidth = pixelHeight;
+      } else {
+        this._cachedGetSinglePixelWidth = 1;
       }
-
-      return this._cachedGetSinglePixelWidth;
     }
 
-    getCanvasPosition(x, y) {
-      const transform = this.ctx.mozCurrentTransform;
-      return [transform[0] * x + transform[2] * y + transform[4], transform[1] * x + transform[3] * y + transform[5]];
-    }
+    return this._cachedGetSinglePixelWidth;
+  }
 
-    isContentVisible() {
-      for (let i = this.markedContentStack.length - 1; i >= 0; i--) {
-        if (!this.markedContentStack[i].visible) {
-          return false;
-        }
-      }
+  getCanvasPosition(x, y) {
+    const transform = this.ctx.mozCurrentTransform;
+    return [transform[0] * x + transform[2] * y + transform[4], transform[1] * x + transform[3] * y + transform[5]];
+  }
 
-      return true;
+  isContentVisible() {
+    for (let i = this.markedContentStack.length - 1; i >= 0; i--) {
+      if (!this.markedContentStack[i].visible) {
+        return false;
+      }
     }
 
+    return true;
   }
 
-  for (const op in _util.OPS) {
-    CanvasGraphics.prototype[_util.OPS[op]] = CanvasGraphics.prototype[op];
-  }
+}
 
-  return CanvasGraphics;
-}();
+exports.CanvasGraphics = CanvasGraphics;
 
-exports.CanvasGraphics = CanvasGraphics;
+for (const op in _util.OPS) {
+  if (CanvasGraphics.prototype[op] !== undefined) {
+    CanvasGraphics.prototype[_util.OPS[op]] = CanvasGraphics.prototype[op];
+  }
+}

+ 12 - 3
lib/display/display_utils.js

@@ -33,15 +33,24 @@ exports.isDataScheme = isDataScheme;
 exports.isPdfFile = isPdfFile;
 exports.isValidFetchUrl = isValidFetchUrl;
 exports.loadScript = loadScript;
-exports.StatTimer = exports.RenderingCancelledException = exports.PDFDateString = exports.PageViewport = exports.LinkTarget = exports.DOMSVGFactory = exports.DOMStandardFontDataFactory = exports.DOMCMapReaderFactory = exports.DOMCanvasFactory = exports.DEFAULT_LINK_REL = void 0;
+exports.StatTimer = exports.RenderingCancelledException = exports.PixelsPerInch = exports.PDFDateString = exports.PageViewport = exports.LinkTarget = exports.DOMSVGFactory = exports.DOMStandardFontDataFactory = exports.DOMCMapReaderFactory = exports.DOMCanvasFactory = void 0;
 
 var _util = require("../shared/util.js");
 
 var _base_factory = require("./base_factory.js");
 
 const DEFAULT_LINK_REL = "noopener noreferrer nofollow";
-exports.DEFAULT_LINK_REL = DEFAULT_LINK_REL;
 const SVG_NS = "http://www.w3.org/2000/svg";
+const PixelsPerInch = {
+  CSS: 96.0,
+  PDF: 72.0,
+
+  get PDF_TO_CSS_UNITS() {
+    return (0, _util.shadow)(this, "PDF_TO_CSS_UNITS", this.CSS / this.PDF);
+  }
+
+};
+exports.PixelsPerInch = PixelsPerInch;
 
 class DOMCanvasFactory extends _base_factory.BaseCanvasFactory {
   constructor({
@@ -261,7 +270,7 @@ exports.PageViewport = PageViewport;
 
 class RenderingCancelledException extends _util.BaseException {
   constructor(msg, type) {
-    super(msg);
+    super(msg, "RenderingCancelledException");
     this.type = type;
   }
 

+ 1 - 7
lib/display/fetch_stream.js

@@ -251,13 +251,7 @@ class PDFFetchStreamRangeReader {
       this._readCapability.resolve();
 
       this._reader = response.body.getReader();
-    }).catch(reason => {
-      if (reason?.name === "AbortError") {
-        return;
-      }
-
-      throw reason;
-    });
+    }).catch(this._readCapability.reject);
     this.onProgress = null;
   }
 

+ 41 - 40
lib/display/network.js

@@ -46,11 +46,10 @@ function getArrayBuffer(xhr) {
 }
 
 class NetworkManager {
-  constructor(url, args) {
+  constructor(url, args = {}) {
     this.url = url;
-    args = args || {};
     this.isHttp = /^https?:/i.test(url);
-    this.httpHeaders = this.isHttp && args.httpHeaders || {};
+    this.httpHeaders = this.isHttp && args.httpHeaders || Object.create(null);
     this.withCredentials = args.withCredentials || false;
 
     this.getXhr = args.getXhr || function NetworkManager_getXhr() {
@@ -129,9 +128,7 @@ class NetworkManager {
       return;
     }
 
-    if (pendingRequest.onProgress) {
-      pendingRequest.onProgress(evt);
-    }
+    pendingRequest.onProgress?.(evt);
   }
 
   onStateChange(xhrId, evt) {
@@ -159,10 +156,7 @@ class NetworkManager {
     delete this.pendingRequests[xhrId];
 
     if (xhr.status === 0 && this.isHttp) {
-      if (pendingRequest.onError) {
-        pendingRequest.onError(xhr.status);
-      }
-
+      pendingRequest.onError?.(xhr.status);
       return;
     }
 
@@ -170,10 +164,7 @@ class NetworkManager {
     const ok_response_on_range_request = xhrStatus === OK_RESPONSE && pendingRequest.expectedStatus === PARTIAL_CONTENT_RESPONSE;
 
     if (!ok_response_on_range_request && xhrStatus !== pendingRequest.expectedStatus) {
-      if (pendingRequest.onError) {
-        pendingRequest.onError(xhr.status);
-      }
-
+      pendingRequest.onError?.(xhr.status);
       return;
     }
 
@@ -191,8 +182,8 @@ class NetworkManager {
         begin: 0,
         chunk
       });
-    } else if (pendingRequest.onError) {
-      pendingRequest.onError(xhr.status);
+    } else {
+      pendingRequest.onError?.(xhr.status);
     }
   }
 
@@ -248,9 +239,7 @@ class PDFNetworkStream {
   }
 
   cancelAllRequests(reason) {
-    if (this._fullRequestReader) {
-      this._fullRequestReader.cancel(reason);
-    }
+    this._fullRequestReader?.cancel(reason);
 
     for (const reader of this._rangeRequestReaders.slice(0)) {
       reader.cancel(reason);
@@ -324,17 +313,17 @@ class PDFNetworkStreamFullRequestReader {
     this._headersReceivedCapability.resolve();
   }
 
-  _onDone(args) {
-    if (args) {
+  _onDone(data) {
+    if (data) {
       if (this._requests.length > 0) {
         const requestCapability = this._requests.shift();
 
         requestCapability.resolve({
-          value: args.chunk,
+          value: data.chunk,
           done: false
         });
       } else {
-        this._cachedChunks.push(args.chunk);
+        this._cachedChunks.push(data.chunk);
       }
     }
 
@@ -355,27 +344,23 @@ class PDFNetworkStreamFullRequestReader {
   }
 
   _onError(status) {
-    const url = this._url;
-    const exception = (0, _network_utils.createResponseStatusError)(status, url);
-    this._storedError = exception;
+    this._storedError = (0, _network_utils.createResponseStatusError)(status, this._url);
 
-    this._headersReceivedCapability.reject(exception);
+    this._headersReceivedCapability.reject(this._storedError);
 
     for (const requestCapability of this._requests) {
-      requestCapability.reject(exception);
+      requestCapability.reject(this._storedError);
     }
 
     this._requests.length = 0;
     this._cachedChunks.length = 0;
   }
 
-  _onProgress(data) {
-    if (this.onProgress) {
-      this.onProgress({
-        loaded: data.loaded,
-        total: data.lengthComputable ? data.total : this._contentLength
-      });
-    }
+  _onProgress(evt) {
+    this.onProgress?.({
+      loaded: evt.loaded,
+      total: evt.lengthComputable ? evt.total : this._contentLength
+    });
   }
 
   get filename() {
@@ -454,20 +439,21 @@ class PDFNetworkStreamRangeRequestReader {
     this._manager = manager;
     const args = {
       onDone: this._onDone.bind(this),
+      onError: this._onError.bind(this),
       onProgress: this._onProgress.bind(this)
     };
+    this._url = manager.url;
     this._requestId = manager.requestRange(begin, end, args);
     this._requests = [];
     this._queuedChunk = null;
     this._done = false;
+    this._storedError = undefined;
     this.onProgress = null;
     this.onClosed = null;
   }
 
   _close() {
-    if (this.onClosed) {
-      this.onClosed(this);
-    }
+    this.onClosed?.(this);
   }
 
   _onDone(data) {
@@ -498,9 +484,20 @@ class PDFNetworkStreamRangeRequestReader {
     this._close();
   }
 
+  _onError(status) {
+    this._storedError = (0, _network_utils.createResponseStatusError)(status, this._url);
+
+    for (const requestCapability of this._requests) {
+      requestCapability.reject(this._storedError);
+    }
+
+    this._requests.length = 0;
+    this._queuedChunk = null;
+  }
+
   _onProgress(evt) {
-    if (!this.isStreamingSupported && this.onProgress) {
-      this.onProgress({
+    if (!this.isStreamingSupported) {
+      this.onProgress?.({
         loaded: evt.loaded
       });
     }
@@ -511,6 +508,10 @@ class PDFNetworkStreamRangeRequestReader {
   }
 
   async read() {
+    if (this._storedError) {
+      throw this._storedError;
+    }
+
     if (this._queuedChunk !== null) {
       const chunk = this._queuedChunk;
       this._queuedChunk = null;

+ 9 - 0
lib/display/optional_content_config.js

@@ -120,6 +120,15 @@ class OptionalContentConfig {
   }
 
   isVisible(group) {
+    if (this._groups.size === 0) {
+      return true;
+    }
+
+    if (!group) {
+      (0, _util.warn)("Optional content group not defined.");
+      return true;
+    }
+
     if (group.type === "OCG") {
       if (!this._groups.has(group.id)) {
         (0, _util.warn)(`Optional content group not found: ${group.id}`);

+ 20 - 21
lib/display/pattern_helper.js

@@ -55,7 +55,7 @@ class BaseShadingPattern {
 }
 
 class RadialAxialShadingPattern extends BaseShadingPattern {
-  constructor(IR) {
+  constructor(IR, cachedCanvasPatterns) {
     super();
     this._type = IR[1];
     this._bbox = IR[2];
@@ -64,8 +64,8 @@ class RadialAxialShadingPattern extends BaseShadingPattern {
     this._p1 = IR[5];
     this._r0 = IR[6];
     this._r1 = IR[7];
-    this._matrix = IR[8];
-    this._patternCache = null;
+    this.matrix = null;
+    this.cachedCanvasPatterns = cachedCanvasPatterns;
   }
 
   _createGradient(ctx) {
@@ -87,10 +87,10 @@ class RadialAxialShadingPattern extends BaseShadingPattern {
   getPattern(ctx, owner, inverse, shadingFill = false) {
     let pattern;
 
-    if (this._patternCache) {
-      pattern = this._patternCache;
-    } else {
-      if (!shadingFill) {
+    if (!shadingFill) {
+      if (this.cachedCanvasPatterns.has(this)) {
+        pattern = this.cachedCanvasPatterns.get(this);
+      } else {
         const tmpCanvas = owner.cachedCanvases.getCanvas("pattern", owner.ctx.canvas.width, owner.ctx.canvas.height, true);
         const tmpCtx = tmpCanvas.context;
         tmpCtx.clearRect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
@@ -98,20 +98,19 @@ class RadialAxialShadingPattern extends BaseShadingPattern {
         tmpCtx.rect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
         tmpCtx.setTransform.apply(tmpCtx, owner.baseTransform);
 
-        if (this._matrix) {
-          tmpCtx.transform.apply(tmpCtx, this._matrix);
+        if (this.matrix) {
+          tmpCtx.transform.apply(tmpCtx, this.matrix);
         }
 
         applyBoundingBox(tmpCtx, this._bbox);
         tmpCtx.fillStyle = this._createGradient(tmpCtx);
         tmpCtx.fill();
         pattern = ctx.createPattern(tmpCanvas.canvas, "repeat");
-      } else {
-        applyBoundingBox(ctx, this._bbox);
-        pattern = this._createGradient(ctx);
+        this.cachedCanvasPatterns.set(this, pattern);
       }
-
-      this._patternCache = pattern;
+    } else {
+      applyBoundingBox(ctx, this._bbox);
+      pattern = this._createGradient(ctx);
     }
 
     if (!shadingFill) {
@@ -295,9 +294,9 @@ class MeshShadingPattern extends BaseShadingPattern {
     this._colors = IR[3];
     this._figures = IR[4];
     this._bounds = IR[5];
-    this._matrix = IR[6];
     this._bbox = IR[7];
     this._background = IR[8];
+    this.matrix = null;
   }
 
   _createMeshCanvas(combinedScale, backgroundColor, cachedCanvases) {
@@ -361,8 +360,8 @@ class MeshShadingPattern extends BaseShadingPattern {
     } else {
       scale = _util.Util.singularValueDecompose2dScale(owner.baseTransform);
 
-      if (this._matrix) {
-        const matrixScale = _util.Util.singularValueDecompose2dScale(this._matrix);
+      if (this.matrix) {
+        const matrixScale = _util.Util.singularValueDecompose2dScale(this.matrix);
 
         scale = [scale[0] * matrixScale[0], scale[1] * matrixScale[1]];
       }
@@ -373,8 +372,8 @@ class MeshShadingPattern extends BaseShadingPattern {
     if (!shadingFill) {
       ctx.setTransform.apply(ctx, owner.baseTransform);
 
-      if (this._matrix) {
-        ctx.transform.apply(ctx, this._matrix);
+      if (this.matrix) {
+        ctx.transform.apply(ctx, this.matrix);
       }
     }
 
@@ -392,10 +391,10 @@ class DummyShadingPattern extends BaseShadingPattern {
 
 }
 
-function getShadingPattern(IR) {
+function getShadingPattern(IR, cachedCanvasPatterns) {
   switch (IR[0]) {
     case "RadialAxial":
-      return new RadialAxialShadingPattern(IR);
+      return new RadialAxialShadingPattern(IR, cachedCanvasPatterns);
 
     case "Mesh":
       return new MeshShadingPattern(IR);

+ 19 - 5
lib/display/text_layer.js

@@ -93,7 +93,7 @@ function getAscent(fontFamily, ctx) {
 
 function appendText(task, geom, styles, ctx) {
   const textDiv = document.createElement("span");
-  const textDivProperties = {
+  const textDivProperties = task._enhanceTextSelection ? {
     angle: 0,
     canvasWidth: 0,
     hasText: geom.str !== "",
@@ -104,6 +104,11 @@ function appendText(task, geom, styles, ctx) {
     paddingRight: 0,
     paddingTop: 0,
     scale: 1
+  } : {
+    angle: 0,
+    canvasWidth: 0,
+    hasText: geom.str !== "",
+    hasEOL: geom.hasEOL
   };
 
   task._textDivs.push(textDiv);
@@ -532,6 +537,10 @@ class TextLayerRenderTask {
     this._bounds = [];
 
     this._capability.promise.finally(() => {
+      if (!this._enhanceTextSelection) {
+        this._textDivProperties = null;
+      }
+
       if (this._layoutTextCtx) {
         this._layoutTextCtx.canvas.width = 0;
         this._layoutTextCtx.canvas.height = 0;
@@ -548,7 +557,7 @@ class TextLayerRenderTask {
     this._canceled = true;
 
     if (this._reader) {
-      this._reader.cancel(new _util.AbortException("TextLayer task cancelled."));
+      this._reader.cancel(new _util.AbortException("TextLayer task cancelled.")).catch(() => {});
 
       this._reader = null;
     }
@@ -610,8 +619,13 @@ class TextLayerRenderTask {
       } = this._layoutTextCtx.measureText(textDiv.textContent);
 
       if (width > 0) {
-        textDivProperties.scale = textDivProperties.canvasWidth / width;
-        transform = `scaleX(${textDivProperties.scale})`;
+        const scale = textDivProperties.canvasWidth / width;
+
+        if (this._enhanceTextSelection) {
+          textDivProperties.scale = scale;
+        }
+
+        transform = `scaleX(${scale})`;
       }
     }
 
@@ -680,7 +694,7 @@ class TextLayerRenderTask {
       this._reader = this._textContentStream.getReader();
       pump();
     } else {
-      throw new Error('Neither "textContent" nor "textContentStream"' + " parameters specified.");
+      throw new Error('Neither "textContent" nor "textContentStream" parameters specified.');
     }
 
     capability.promise.then(() => {

+ 56 - 6
lib/display/xfa_layer.js

@@ -26,6 +26,10 @@ Object.defineProperty(exports, "__esModule", {
 });
 exports.XfaLayer = void 0;
 
+var _util = require("../shared/util.js");
+
+var _xfa_text = require("./xfa_text.js");
+
 class XfaLayer {
   static setupStorage(html, id, element, storage, intent) {
     const storedData = storage.getValue(id, {
@@ -53,6 +57,8 @@ class XfaLayer {
         if (element.attributes.type === "radio" || element.attributes.type === "checkbox") {
           if (storedData.value === element.attributes.xfaOn) {
             html.setAttribute("checked", true);
+          } else if (storedData.value === element.attributes.xfaOff) {
+            html.removeAttribute("checked");
           }
 
           if (intent === "print") {
@@ -61,7 +67,7 @@ class XfaLayer {
 
           html.addEventListener("change", event => {
             storage.setValue(id, {
-              value: event.target.getAttribute("xfaOn")
+              value: event.target.checked ? event.target.getAttribute("xfaOn") : event.target.getAttribute("xfaOff")
             });
           });
         } else {
@@ -102,10 +108,17 @@ class XfaLayer {
     }
   }
 
-  static setAttributes(html, element, storage, intent) {
+  static setAttributes({
+    html,
+    element,
+    storage = null,
+    intent,
+    linkService
+  }) {
     const {
       attributes
     } = element;
+    const isHTMLAnchorElement = html instanceof HTMLAnchorElement;
 
     if (attributes.type === "radio") {
       attributes.name = `${attributes.name}-${intent}`;
@@ -122,6 +135,10 @@ class XfaLayer {
         } else if (key === "class") {
           html.setAttribute(key, value.join(" "));
         } else {
+          if (isHTMLAnchorElement && (key === "href" || key === "newWindow")) {
+            continue;
+          }
+
           html.setAttribute(key, value);
         }
       } else {
@@ -129,6 +146,14 @@ class XfaLayer {
       }
     }
 
+    if (isHTMLAnchorElement) {
+      if (!linkService.addLinkAttributes) {
+        (0, _util.warn)("XfaLayer.setAttribute - missing `addLinkAttributes`-method on the `linkService`-instance.");
+      }
+
+      linkService.addLinkAttributes?.(html, attributes.href, attributes.newWindow);
+    }
+
     if (storage && attributes.dataId) {
       this.setupStorage(html, attributes.dataId, element, storage);
     }
@@ -136,12 +161,18 @@ class XfaLayer {
 
   static render(parameters) {
     const storage = parameters.annotationStorage;
+    const linkService = parameters.linkService;
     const root = parameters.xfa;
     const intent = parameters.intent || "display";
     const rootHtml = document.createElement(root.name);
 
     if (root.attributes) {
-      this.setAttributes(rootHtml, root);
+      this.setAttributes({
+        html: rootHtml,
+        element: root,
+        intent,
+        linkService
+      });
     }
 
     const stack = [[root, -1, rootHtml]];
@@ -150,6 +181,7 @@ class XfaLayer {
     const transform = `matrix(${parameters.viewport.transform.join(",")})`;
     rootDiv.style.transform = transform;
     rootDiv.setAttribute("class", "xfaLayer xfaFont");
+    const textDivs = [];
 
     while (stack.length > 0) {
       const [parent, i, html] = stack[stack.length - 1];
@@ -170,7 +202,9 @@ class XfaLayer {
       } = child;
 
       if (name === "#text") {
-        html.appendChild(document.createTextNode(child.value));
+        const node = document.createTextNode(child.value);
+        textDivs.push(node);
+        html.appendChild(node);
         continue;
       }
 
@@ -185,19 +219,35 @@ class XfaLayer {
       html.appendChild(childHtml);
 
       if (child.attributes) {
-        this.setAttributes(childHtml, child, storage, intent);
+        this.setAttributes({
+          html: childHtml,
+          element: child,
+          storage,
+          intent,
+          linkService
+        });
       }
 
       if (child.children && child.children.length > 0) {
         stack.push([child, -1, childHtml]);
       } else if (child.value) {
-        childHtml.appendChild(document.createTextNode(child.value));
+        const node = document.createTextNode(child.value);
+
+        if (_xfa_text.XfaText.shouldBuildText(name)) {
+          textDivs.push(node);
+        }
+
+        childHtml.appendChild(node);
       }
     }
 
     for (const el of rootDiv.querySelectorAll(".xfaNonInteractive input, .xfaNonInteractive textarea")) {
       el.setAttribute("readOnly", true);
     }
+
+    return {
+      textDivs
+    };
   }
 
   static update(parameters) {

+ 80 - 0
lib/display/xfa_text.js

@@ -0,0 +1,80 @@
+/**
+ * @licstart The following is the entire license notice for the
+ * Javascript code in this page
+ *
+ * Copyright 2021 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.XfaText = void 0;
+
+class XfaText {
+  static textContent(xfa) {
+    const items = [];
+    const output = {
+      items,
+      styles: Object.create(null)
+    };
+
+    function walk(node) {
+      if (!node) {
+        return;
+      }
+
+      let str = null;
+      const name = node.name;
+
+      if (name === "#text") {
+        str = node.value;
+      } else if (!XfaText.shouldBuildText(name)) {
+        return;
+      } else if (node?.attributes?.textContent) {
+        str = node.attributes.textContent;
+      } else if (node.value) {
+        str = node.value;
+      }
+
+      if (str !== null) {
+        items.push({
+          str
+        });
+      }
+
+      if (!node.children) {
+        return;
+      }
+
+      for (const child of node.children) {
+        walk(child);
+      }
+    }
+
+    walk(xfa);
+    return output;
+  }
+
+  static shouldBuildText(name) {
+    return !(name === "textarea" || name === "input" || name === "option" || name === "select");
+  }
+
+}
+
+exports.XfaText = XfaText;

+ 46 - 34
lib/pdf.js

@@ -72,46 +72,22 @@ Object.defineProperty(exports, "PDFDateString", {
     return _display_utils.PDFDateString;
   }
 });
-Object.defineProperty(exports, "RenderingCancelledException", {
-  enumerable: true,
-  get: function () {
-    return _display_utils.RenderingCancelledException;
-  }
-});
-Object.defineProperty(exports, "build", {
-  enumerable: true,
-  get: function () {
-    return _api.build;
-  }
-});
-Object.defineProperty(exports, "getDocument", {
-  enumerable: true,
-  get: function () {
-    return _api.getDocument;
-  }
-});
-Object.defineProperty(exports, "LoopbackPort", {
+Object.defineProperty(exports, "PixelsPerInch", {
   enumerable: true,
   get: function () {
-    return _api.LoopbackPort;
+    return _display_utils.PixelsPerInch;
   }
 });
-Object.defineProperty(exports, "PDFDataRangeTransport", {
-  enumerable: true,
-  get: function () {
-    return _api.PDFDataRangeTransport;
-  }
-});
-Object.defineProperty(exports, "PDFWorker", {
+Object.defineProperty(exports, "RenderingCancelledException", {
   enumerable: true,
   get: function () {
-    return _api.PDFWorker;
+    return _display_utils.RenderingCancelledException;
   }
 });
-Object.defineProperty(exports, "version", {
+Object.defineProperty(exports, "AnnotationMode", {
   enumerable: true,
   get: function () {
-    return _api.version;
+    return _util.AnnotationMode;
   }
 });
 Object.defineProperty(exports, "CMapCompressionType", {
@@ -204,6 +180,42 @@ Object.defineProperty(exports, "VerbosityLevel", {
     return _util.VerbosityLevel;
   }
 });
+Object.defineProperty(exports, "build", {
+  enumerable: true,
+  get: function () {
+    return _api.build;
+  }
+});
+Object.defineProperty(exports, "getDocument", {
+  enumerable: true,
+  get: function () {
+    return _api.getDocument;
+  }
+});
+Object.defineProperty(exports, "LoopbackPort", {
+  enumerable: true,
+  get: function () {
+    return _api.LoopbackPort;
+  }
+});
+Object.defineProperty(exports, "PDFDataRangeTransport", {
+  enumerable: true,
+  get: function () {
+    return _api.PDFDataRangeTransport;
+  }
+});
+Object.defineProperty(exports, "PDFWorker", {
+  enumerable: true,
+  get: function () {
+    return _api.PDFWorker;
+  }
+});
+Object.defineProperty(exports, "version", {
+  enumerable: true,
+  get: function () {
+    return _api.version;
+  }
+});
 Object.defineProperty(exports, "AnnotationLayer", {
   enumerable: true,
   get: function () {
@@ -237,10 +249,10 @@ Object.defineProperty(exports, "XfaLayer", {
 
 var _display_utils = require("./display/display_utils.js");
 
-var _api = require("./display/api.js");
-
 var _util = require("./shared/util.js");
 
+var _api = require("./display/api.js");
+
 var _annotation_layer = require("./display/annotation_layer.js");
 
 var _worker_options = require("./display/worker_options.js");
@@ -253,8 +265,8 @@ var _svg = require("./display/svg.js");
 
 var _xfa_layer = require("./display/xfa_layer.js");
 
-const pdfjsVersion = '2.10.377';
-const pdfjsBuild = '156762c48';
+const pdfjsVersion = '2.11.338';
+const pdfjsBuild = 'dedff3c98';
 {
   if (_is_node.isNodeJS) {
     const {

Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
lib/pdf.sandbox.js


+ 2 - 2
lib/pdf.worker.js

@@ -33,5 +33,5 @@ Object.defineProperty(exports, "WorkerMessageHandler", {
 
 var _worker = require("./core/worker.js");
 
-const pdfjsVersion = '2.10.377';
-const pdfjsBuild = '156762c48';
+const pdfjsVersion = '2.11.338';
+const pdfjsBuild = 'dedff3c98';

+ 50 - 52
lib/shared/message_handler.js

@@ -46,7 +46,8 @@ const StreamKind = {
 };
 
 function wrapReason(reason) {
-  if (typeof reason !== "object" || reason === null) {
+  if (!(reason instanceof Error || typeof reason === "object" && reason !== null)) {
+    (0, _util.warn)('wrapReason: Expected "reason" to be a (possibly cloned) Error.');
     return reason;
   }
 
@@ -57,6 +58,9 @@ function wrapReason(reason) {
     case "MissingPDFException":
       return new _util.MissingPDFException(reason.message);
 
+    case "PasswordException":
+      return new _util.PasswordException(reason.message, reason.code);
+
     case "UnexpectedResponseException":
       return new _util.UnexpectedResponseException(reason.message, reason.status);
 
@@ -198,10 +202,10 @@ class MessageHandler {
   }
 
   sendWithStream(actionName, data, queueingStrategy, transfers) {
-    const streamId = this.streamId++;
-    const sourceName = this.sourceName;
-    const targetName = this.targetName;
-    const comObj = this.comObj;
+    const streamId = this.streamId++,
+          sourceName = this.sourceName,
+          targetName = this.targetName,
+          comObj = this.comObj;
     return new ReadableStream({
       start: controller => {
         const startCapability = (0, _util.createPromiseCapability)();
@@ -254,12 +258,12 @@ class MessageHandler {
   }
 
   _createStreamSink(data) {
-    const self = this;
-    const action = this.actionHandler[data.action];
-    const streamId = data.streamId;
-    const sourceName = this.sourceName;
-    const targetName = data.sourceName;
-    const comObj = this.comObj;
+    const streamId = data.streamId,
+          sourceName = this.sourceName,
+          targetName = data.sourceName,
+          comObj = this.comObj;
+    const self = this,
+          action = this.actionHandler[data.action];
     const streamSink = {
       enqueue(chunk, size = 1, transfers) {
         if (this.isCancelled) {
@@ -347,32 +351,34 @@ class MessageHandler {
   }
 
   _processStreamMessage(data) {
-    const streamId = data.streamId;
-    const sourceName = this.sourceName;
-    const targetName = data.sourceName;
-    const comObj = this.comObj;
+    const streamId = data.streamId,
+          sourceName = this.sourceName,
+          targetName = data.sourceName,
+          comObj = this.comObj;
+    const streamController = this.streamControllers[streamId],
+          streamSink = this.streamSinks[streamId];
 
     switch (data.stream) {
       case StreamKind.START_COMPLETE:
         if (data.success) {
-          this.streamControllers[streamId].startCall.resolve();
+          streamController.startCall.resolve();
         } else {
-          this.streamControllers[streamId].startCall.reject(wrapReason(data.reason));
+          streamController.startCall.reject(wrapReason(data.reason));
         }
 
         break;
 
       case StreamKind.PULL_COMPLETE:
         if (data.success) {
-          this.streamControllers[streamId].pullCall.resolve();
+          streamController.pullCall.resolve();
         } else {
-          this.streamControllers[streamId].pullCall.reject(wrapReason(data.reason));
+          streamController.pullCall.reject(wrapReason(data.reason));
         }
 
         break;
 
       case StreamKind.PULL:
-        if (!this.streamSinks[streamId]) {
+        if (!streamSink) {
           comObj.postMessage({
             sourceName,
             targetName,
@@ -383,16 +389,13 @@ class MessageHandler {
           break;
         }
 
-        if (this.streamSinks[streamId].desiredSize <= 0 && data.desiredSize > 0) {
-          this.streamSinks[streamId].sinkCapability.resolve();
+        if (streamSink.desiredSize <= 0 && data.desiredSize > 0) {
+          streamSink.sinkCapability.resolve();
         }
 
-        this.streamSinks[streamId].desiredSize = data.desiredSize;
-        const {
-          onPull
-        } = this.streamSinks[data.streamId];
+        streamSink.desiredSize = data.desiredSize;
         new Promise(function (resolve) {
-          resolve(onPull && onPull());
+          resolve(streamSink.onPull && streamSink.onPull());
         }).then(function () {
           comObj.postMessage({
             sourceName,
@@ -413,58 +416,55 @@ class MessageHandler {
         break;
 
       case StreamKind.ENQUEUE:
-        (0, _util.assert)(this.streamControllers[streamId], "enqueue should have stream controller");
+        (0, _util.assert)(streamController, "enqueue should have stream controller");
 
-        if (this.streamControllers[streamId].isClosed) {
+        if (streamController.isClosed) {
           break;
         }
 
-        this.streamControllers[streamId].controller.enqueue(data.chunk);
+        streamController.controller.enqueue(data.chunk);
         break;
 
       case StreamKind.CLOSE:
-        (0, _util.assert)(this.streamControllers[streamId], "close should have stream controller");
+        (0, _util.assert)(streamController, "close should have stream controller");
 
-        if (this.streamControllers[streamId].isClosed) {
+        if (streamController.isClosed) {
           break;
         }
 
-        this.streamControllers[streamId].isClosed = true;
-        this.streamControllers[streamId].controller.close();
+        streamController.isClosed = true;
+        streamController.controller.close();
 
-        this._deleteStreamController(streamId);
+        this._deleteStreamController(streamController, streamId);
 
         break;
 
       case StreamKind.ERROR:
-        (0, _util.assert)(this.streamControllers[streamId], "error should have stream controller");
-        this.streamControllers[streamId].controller.error(wrapReason(data.reason));
+        (0, _util.assert)(streamController, "error should have stream controller");
+        streamController.controller.error(wrapReason(data.reason));
 
-        this._deleteStreamController(streamId);
+        this._deleteStreamController(streamController, streamId);
 
         break;
 
       case StreamKind.CANCEL_COMPLETE:
         if (data.success) {
-          this.streamControllers[streamId].cancelCall.resolve();
+          streamController.cancelCall.resolve();
         } else {
-          this.streamControllers[streamId].cancelCall.reject(wrapReason(data.reason));
+          streamController.cancelCall.reject(wrapReason(data.reason));
         }
 
-        this._deleteStreamController(streamId);
+        this._deleteStreamController(streamController, streamId);
 
         break;
 
       case StreamKind.CANCEL:
-        if (!this.streamSinks[streamId]) {
+        if (!streamSink) {
           break;
         }
 
-        const {
-          onCancel
-        } = this.streamSinks[data.streamId];
         new Promise(function (resolve) {
-          resolve(onCancel && onCancel(wrapReason(data.reason)));
+          resolve(streamSink.onCancel && streamSink.onCancel(wrapReason(data.reason)));
         }).then(function () {
           comObj.postMessage({
             sourceName,
@@ -482,8 +482,8 @@ class MessageHandler {
             reason: wrapReason(reason)
           });
         });
-        this.streamSinks[streamId].sinkCapability.reject(wrapReason(data.reason));
-        this.streamSinks[streamId].isCancelled = true;
+        streamSink.sinkCapability.reject(wrapReason(data.reason));
+        streamSink.isCancelled = true;
         delete this.streamSinks[streamId];
         break;
 
@@ -492,10 +492,8 @@ class MessageHandler {
     }
   }
 
-  async _deleteStreamController(streamId) {
-    await Promise.allSettled([this.streamControllers[streamId].startCall, this.streamControllers[streamId].pullCall, this.streamControllers[streamId].cancelCall].map(function (capability) {
-      return capability && capability.promise;
-    }));
+  async _deleteStreamController(streamController, streamId) {
+    await Promise.allSettled([streamController.startCall && streamController.startCall.promise, streamController.pullCall && streamController.pullCall.promise, streamController.cancelCall && streamController.cancelCall.promise]);
     delete this.streamControllers[streamId];
   }
 

+ 66 - 12
lib/shared/util.js

@@ -55,7 +55,7 @@ exports.stringToUTF8String = stringToUTF8String;
 exports.unreachable = unreachable;
 exports.utf8StringToString = utf8StringToString;
 exports.warn = warn;
-exports.VerbosityLevel = exports.Util = exports.UNSUPPORTED_FEATURES = exports.UnknownErrorException = exports.UnexpectedResponseException = exports.TextRenderingMode = exports.StreamType = exports.PermissionFlag = exports.PasswordResponses = exports.PasswordException = exports.PageActionEventType = exports.OPS = exports.MissingPDFException = exports.IsLittleEndianCached = exports.IsEvalSupportedCached = exports.InvalidPDFException = exports.ImageKind = exports.IDENTITY_MATRIX = exports.FormatError = exports.FontType = exports.FONT_IDENTITY_MATRIX = exports.DocumentActionEventType = exports.CMapCompressionType = exports.BaseException = exports.AnnotationType = exports.AnnotationStateModelType = exports.AnnotationReviewState = exports.AnnotationReplyType = exports.AnnotationMarkedState = exports.AnnotationFlag = exports.AnnotationFieldFlag = exports.AnnotationBorderStyleType = exports.AnnotationActionEventType = exports.AbortException = void 0;
+exports.VerbosityLevel = exports.Util = exports.UNSUPPORTED_FEATURES = exports.UnknownErrorException = exports.UnexpectedResponseException = exports.TextRenderingMode = exports.StreamType = exports.RenderingIntentFlag = exports.PermissionFlag = exports.PasswordResponses = exports.PasswordException = exports.PageActionEventType = exports.OPS = exports.MissingPDFException = exports.IsLittleEndianCached = exports.IsEvalSupportedCached = exports.InvalidPDFException = exports.ImageKind = exports.IDENTITY_MATRIX = exports.FormatError = exports.FontType = exports.FONT_IDENTITY_MATRIX = exports.DocumentActionEventType = exports.CMapCompressionType = exports.BaseException = exports.AnnotationType = exports.AnnotationStateModelType = exports.AnnotationReviewState = exports.AnnotationReplyType = exports.AnnotationMode = exports.AnnotationMarkedState = exports.AnnotationFlag = exports.AnnotationFieldFlag = exports.AnnotationBorderStyleType = exports.AnnotationActionEventType = exports.AbortException = void 0;
 
 require("./compatibility.js");
 
@@ -63,6 +63,23 @@ const IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0];
 exports.IDENTITY_MATRIX = IDENTITY_MATRIX;
 const FONT_IDENTITY_MATRIX = [0.001, 0, 0, 0.001, 0, 0];
 exports.FONT_IDENTITY_MATRIX = FONT_IDENTITY_MATRIX;
+const RenderingIntentFlag = {
+  ANY: 0x01,
+  DISPLAY: 0x02,
+  PRINT: 0x04,
+  ANNOTATIONS_FORMS: 0x10,
+  ANNOTATIONS_STORAGE: 0x20,
+  ANNOTATIONS_DISABLE: 0x40,
+  OPLIST: 0x100
+};
+exports.RenderingIntentFlag = RenderingIntentFlag;
+const AnnotationMode = {
+  DISABLE: 0,
+  ENABLE: 1,
+  ENABLE_FORMS: 2,
+  ENABLE_STORAGE: 3
+};
+exports.AnnotationMode = AnnotationMode;
 const PermissionFlag = {
   PRINT: 0x04,
   MODIFY_CONTENTS: 0x08,
@@ -373,7 +390,8 @@ const UNSUPPORTED_FEATURES = {
   errorFontLoadNative: "errorFontLoadNative",
   errorFontBuildPath: "errorFontBuildPath",
   errorFontGetPath: "errorFontGetPath",
-  errorMarkedContent: "errorMarkedContent"
+  errorMarkedContent: "errorMarkedContent",
+  errorContentSubStream: "errorContentSubStream"
 };
 exports.UNSUPPORTED_FEATURES = UNSUPPORTED_FEATURES;
 const PasswordResponses = {
@@ -450,12 +468,28 @@ function _isValidProtocol(url) {
   }
 }
 
-function createValidAbsoluteUrl(url, baseUrl) {
+function createValidAbsoluteUrl(url, baseUrl = null, options = null) {
   if (!url) {
     return null;
   }
 
   try {
+    if (options && typeof url === "string") {
+      if (options.addDefaultProtocol && url.startsWith("www.")) {
+        const dots = url.match(/\./g);
+
+        if (dots && dots.length >= 2) {
+          url = `http://${url}`;
+        }
+      }
+
+      if (options.tryConvertEncoding) {
+        try {
+          url = stringToUTF8String(url);
+        } catch (ex) {}
+      }
+    }
+
     const absoluteUrl = baseUrl ? new URL(url, baseUrl) : new URL(url);
 
     if (_isValidProtocol(absoluteUrl)) {
@@ -477,13 +511,13 @@ function shadow(obj, prop, value) {
 }
 
 const BaseException = function BaseExceptionClosure() {
-  function BaseException(message) {
+  function BaseException(message, name) {
     if (this.constructor === BaseException) {
       unreachable("Cannot initialize BaseException.");
     }
 
     this.message = message;
-    this.name = this.constructor.name;
+    this.name = name;
   }
 
   BaseException.prototype = new Error();
@@ -495,7 +529,7 @@ exports.BaseException = BaseException;
 
 class PasswordException extends BaseException {
   constructor(msg, code) {
-    super(msg);
+    super(msg, "PasswordException");
     this.code = code;
   }
 
@@ -505,7 +539,7 @@ exports.PasswordException = PasswordException;
 
 class UnknownErrorException extends BaseException {
   constructor(msg, details) {
-    super(msg);
+    super(msg, "UnknownErrorException");
     this.details = details;
   }
 
@@ -513,17 +547,27 @@ class UnknownErrorException extends BaseException {
 
 exports.UnknownErrorException = UnknownErrorException;
 
-class InvalidPDFException extends BaseException {}
+class InvalidPDFException extends BaseException {
+  constructor(msg) {
+    super(msg, "InvalidPDFException");
+  }
+
+}
 
 exports.InvalidPDFException = InvalidPDFException;
 
-class MissingPDFException extends BaseException {}
+class MissingPDFException extends BaseException {
+  constructor(msg) {
+    super(msg, "MissingPDFException");
+  }
+
+}
 
 exports.MissingPDFException = MissingPDFException;
 
 class UnexpectedResponseException extends BaseException {
   constructor(msg, status) {
-    super(msg);
+    super(msg, "UnexpectedResponseException");
     this.status = status;
   }
 
@@ -531,11 +575,21 @@ class UnexpectedResponseException extends BaseException {
 
 exports.UnexpectedResponseException = UnexpectedResponseException;
 
-class FormatError extends BaseException {}
+class FormatError extends BaseException {
+  constructor(msg) {
+    super(msg, "FormatError");
+  }
+
+}
 
 exports.FormatError = FormatError;
 
-class AbortException extends BaseException {}
+class AbortException extends BaseException {
+  constructor(msg) {
+    super(msg, "AbortException");
+  }
+
+}
 
 exports.AbortException = AbortException;
 const NullCharactersRegExp = /\x00/g;

+ 41 - 17
lib/test/unit/annotation_spec.js

@@ -282,7 +282,10 @@ describe("annotation", function () {
         ref
       });
       annotation.setContents("Foo bar baz");
-      expect(annotation.contents).toEqual("Foo bar baz");
+      expect(annotation._contents).toEqual({
+        str: "Foo bar baz",
+        dir: "ltr"
+      });
     });
     it("should not set and get invalid contents", function () {
       const annotation = new _annotation.Annotation({
@@ -290,7 +293,10 @@ describe("annotation", function () {
         ref
       });
       annotation.setContents(undefined);
-      expect(annotation.contents).toEqual("");
+      expect(annotation._contents).toEqual({
+        str: "",
+        dir: "ltr"
+      });
     });
     it("should set and get a valid modification date", function () {
       const annotation = new _annotation.Annotation({
@@ -569,8 +575,14 @@ describe("annotation", function () {
       } = await _annotation.AnnotationFactory.create(xref, replyRef, pdfManagerMock, idFactoryMock);
       expect(data.inReplyTo).toEqual(annotationRef.toString());
       expect(data.replyType).toEqual("Group");
-      expect(data.title).toEqual("ParentTitle");
-      expect(data.contents).toEqual("ParentText");
+      expect(data.titleObj).toEqual({
+        str: "ParentTitle",
+        dir: "ltr"
+      });
+      expect(data.contentsObj).toEqual({
+        str: "ParentText",
+        dir: "ltr"
+      });
       expect(data.creationDate).toEqual("D:20180423");
       expect(data.modificationDate).toEqual("D:20190423");
       expect(data.color).toEqual(new Uint8ClampedArray([0, 0, 255]));
@@ -626,8 +638,14 @@ describe("annotation", function () {
       } = await _annotation.AnnotationFactory.create(xref, replyRef, pdfManagerMock, idFactoryMock);
       expect(data.inReplyTo).toEqual(annotationRef.toString());
       expect(data.replyType).toEqual("R");
-      expect(data.title).toEqual("ReplyTitle");
-      expect(data.contents).toEqual("ReplyText");
+      expect(data.titleObj).toEqual({
+        str: "ReplyTitle",
+        dir: "ltr"
+      });
+      expect(data.contentsObj).toEqual({
+        str: "ReplyText",
+        dir: "ltr"
+      });
       expect(data.creationDate).toEqual("D:20180523");
       expect(data.modificationDate).toEqual("D:20190523");
       expect(data.color).toEqual(new Uint8ClampedArray([102, 102, 102]));
@@ -744,7 +762,7 @@ describe("annotation", function () {
       } = await _annotation.AnnotationFactory.create(xref, annotationRef, pdfManagerMock, idFactoryMock);
       expect(data.annotationType).toEqual(_util.AnnotationType.LINK);
       expect(data.url).toEqual("http://www.hmrc.gov.uk/");
-      expect(data.unsafeUrl).toEqual("http://www.hmrc.gov.uk");
+      expect(data.unsafeUrl).toEqual("www.hmrc.gov.uk");
       expect(data.dest).toBeUndefined();
     });
     it("should correctly parse a URI action, where the URI entry " + "has an incorrect encoding (bug 1122280)", async function () {
@@ -770,7 +788,7 @@ describe("annotation", function () {
       } = await _annotation.AnnotationFactory.create(xref, annotationRef, pdfManagerMock, idFactoryMock);
       expect(data.annotationType).toEqual(_util.AnnotationType.LINK);
       expect(data.url).toEqual(new URL((0, _util.stringToUTF8String)("http://www.example.com/\xC3\xBC\xC3\xB6\xC3\xA4")).href);
-      expect(data.unsafeUrl).toEqual((0, _util.stringToUTF8String)("http://www.example.com/\xC3\xBC\xC3\xB6\xC3\xA4"));
+      expect(data.unsafeUrl).toEqual("http://www.example.com/\xC3\xBC\xC3\xB6\xC3\xA4");
       expect(data.dest).toBeUndefined();
     });
     it("should correctly parse a GoTo action", async function () {
@@ -979,7 +997,7 @@ describe("annotation", function () {
         jsEntry: "window.open('http://www.example.com/test.pdf')",
         expectedUrl: new URL("http://www.example.com/test.pdf").href,
         expectedUnsafeUrl: "http://www.example.com/test.pdf",
-        expectedNewWindow: undefined
+        expectedNewWindow: false
       });
       const annotation3 = checkJsAction({
         jsEntry: new _stream.StringStream('app.launchURL("http://www.example.com/test.pdf", true)'),
@@ -1630,7 +1648,7 @@ describe("annotation", function () {
       const [oldData, newData] = data;
       expect(oldData.ref).toEqual(_primitives.Ref.get(123, 0));
       expect(newData.ref).toEqual(_primitives.Ref.get(2, 0));
-      oldData.data = oldData.data.replace(/\(D:[0-9]+\)/, "(date)");
+      oldData.data = oldData.data.replace(/\(D:\d+\)/, "(date)");
       expect(oldData.data).toEqual("123 0 obj\n" + "<< /Type /Annot /Subtype /Widget /FT /Tx /DA (/Helv 5 Tf) /DR " + "<< /Font << /Helv 314 0 R>>>> /Rect [0 0 32 10] " + "/V (hello world) /AP << /N 2 0 R>> /M (date)>>\nendobj\n");
       expect(newData.data).toEqual("2 0 obj\n<< /Length 77 /Subtype /Form /Resources " + "<< /Font << /Helv 314 0 R>>>> /BBox [0 0 32 10]>> stream\n" + "/Tx BMC q BT /Helv 5 Tf 1 0 0 1 0 0 Tm 2.00 2.00 Td (hello world) Tj " + "ET Q EMC\nendstream\nendobj\n");
     });
@@ -1736,7 +1754,7 @@ describe("annotation", function () {
       const [oldData, newData] = data;
       expect(oldData.ref).toEqual(_primitives.Ref.get(123, 0));
       expect(newData.ref).toEqual(_primitives.Ref.get(2, 0));
-      oldData.data = oldData.data.replace(/\(D:[0-9]+\)/, "(date)");
+      oldData.data = oldData.data.replace(/\(D:\d+\)/, "(date)");
       expect(oldData.data).toEqual("123 0 obj\n" + "<< /Type /Annot /Subtype /Widget /FT /Tx /DA (/Goth 5 Tf) /DR " + "<< /Font << /Helv 314 0 R /Goth 159 0 R>>>> /Rect [0 0 32 10] " + `/V (\xfe\xff${utf16String}) /AP << /N 2 0 R>> /M (date)>>\nendobj\n`);
       expect(newData.data).toEqual("2 0 obj\n<< /Length 82 /Subtype /Form /Resources " + "<< /Font << /Helv 314 0 R /Goth 159 0 R>>>> /BBox [0 0 32 10]>> stream\n" + `/Tx BMC q BT /Goth 5 Tf 1 0 0 1 0 0 Tm 2.00 2.00 Td (${utf16String}) Tj ` + "ET Q EMC\nendstream\nendobj\n");
     });
@@ -1998,7 +2016,7 @@ describe("annotation", function () {
         value: true
       });
       const [oldData] = await annotation.save(partialEvaluator, task, annotationStorage);
-      oldData.data = oldData.data.replace(/\(D:[0-9]+\)/, "(date)");
+      oldData.data = oldData.data.replace(/\(D:\d+\)/, "(date)");
       expect(oldData.ref).toEqual(_primitives.Ref.get(123, 0));
       expect(oldData.data).toEqual("123 0 obj\n" + "<< /Type /Annot /Subtype /Widget /FT /Btn " + "/AP << /N << /Checked 314 0 R /Off 271 0 R>>>> " + "/V /Checked /AS /Checked /M (date)>>\nendobj\n");
       annotationStorage.set(annotation.data.id, {
@@ -2196,7 +2214,7 @@ describe("annotation", function () {
       let data = await annotation.save(partialEvaluator, task, annotationStorage);
       expect(data.length).toEqual(2);
       const [radioData, parentData] = data;
-      radioData.data = radioData.data.replace(/\(D:[0-9]+\)/, "(date)");
+      radioData.data = radioData.data.replace(/\(D:\d+\)/, "(date)");
       expect(radioData.ref).toEqual(_primitives.Ref.get(123, 0));
       expect(radioData.data).toEqual("123 0 obj\n" + "<< /Type /Annot /Subtype /Widget /FT /Btn /Ff 32768 " + "/AP << /N << /Checked 314 0 R /Off 271 0 R>>>> " + "/Parent 456 0 R /AS /Checked /M (date)>>\nendobj\n");
       expect(parentData.ref).toEqual(_primitives.Ref.get(456, 0));
@@ -2242,7 +2260,7 @@ describe("annotation", function () {
       const data = await annotation.save(partialEvaluator, task, annotationStorage);
       expect(data.length).toEqual(2);
       const [radioData, parentData] = data;
-      radioData.data = radioData.data.replace(/\(D:[0-9]+\)/, "(date)");
+      radioData.data = radioData.data.replace(/\(D:\d+\)/, "(date)");
       expect(radioData.ref).toEqual(_primitives.Ref.get(123, 0));
       expect(radioData.data).toEqual("123 0 obj\n" + "<< /Type /Annot /Subtype /Widget /FT /Btn /Ff 32768 " + "/AP << /N << /Checked 314 0 R /Off 271 0 R>>>> " + "/Parent 456 0 R /AS /Checked /M (date)>>\nendobj\n");
       expect(parentData.ref).toEqual(_primitives.Ref.get(456, 0));
@@ -2606,7 +2624,7 @@ describe("annotation", function () {
       const [oldData, newData] = data;
       expect(oldData.ref).toEqual(_primitives.Ref.get(123, 0));
       expect(newData.ref).toEqual(_primitives.Ref.get(1, 0));
-      oldData.data = oldData.data.replace(/\(D:[0-9]+\)/, "(date)");
+      oldData.data = oldData.data.replace(/\(D:\d+\)/, "(date)");
       expect(oldData.data).toEqual("123 0 obj\n" + "<< /Type /Annot /Subtype /Widget /FT /Ch /DA (/Helv 5 Tf) /DR " + "<< /Font << /Helv 314 0 R>>>> " + "/Rect [0 0 32 10] /Opt [(A) (B) (C)] /V (C) " + "/AP << /N 1 0 R>> /M (date)>>\nendobj\n");
       expect(newData.data).toEqual("1 0 obj\n" + "<< /Length 67 /Subtype /Form /Resources << /Font << /Helv 314 0 R>>>> " + "/BBox [0 0 32 10]>> stream\n" + "/Tx BMC q BT /Helv 5 Tf 1 0 0 1 0 0 Tm 2.00 2.00 Td (C) Tj ET Q EMC\n" + "endstream\nendobj\n");
     });
@@ -2805,8 +2823,14 @@ describe("annotation", function () {
       const {
         data
       } = await _annotation.AnnotationFactory.create(xref, popupRef, pdfManagerMock, idFactoryMock);
-      expect(data.title).toEqual("Correct Title");
-      expect(data.contents).toEqual("Correct Text");
+      expect(data.titleObj).toEqual({
+        str: "Correct Title",
+        dir: "ltr"
+      });
+      expect(data.contentsObj).toEqual({
+        str: "Correct Text",
+        dir: "ltr"
+      });
       expect(data.modificationDate).toEqual("D:20190423");
       expect(data.color).toEqual(new Uint8ClampedArray([0, 0, 255]));
     });

+ 16 - 0
lib/test/unit/annotation_storage_spec.js

@@ -39,6 +39,22 @@ describe("AnnotationStorage", function () {
       }).value;
       expect(value).toEqual("hello world");
     });
+    it("should get set values and default ones in the annotation storage", function () {
+      const annotationStorage = new _annotation_storage.AnnotationStorage();
+      annotationStorage.setValue("123A", {
+        value: "hello world",
+        hello: "world"
+      });
+      const result = annotationStorage.getValue("123A", {
+        value: "an other string",
+        world: "hello"
+      });
+      expect(result).toEqual({
+        value: "hello world",
+        hello: "world",
+        world: "hello"
+      });
+    });
   });
   describe("SetValue", function () {
     it("should set a new value in the annotation storage", function () {

+ 105 - 5
lib/test/unit/api_spec.js

@@ -21,10 +21,10 @@
  */
 "use strict";
 
-var _test_utils = require("./test_utils.js");
-
 var _util = require("../../shared/util.js");
 
+var _test_utils = require("./test_utils.js");
+
 var _api = require("../../display/api.js");
 
 var _display_utils = require("../../display/display_utils.js");
@@ -62,6 +62,7 @@ describe("api", function () {
     it("creates pdf doc from URL-string", async function () {
       const urlStr = _test_utils.TEST_PDFS_PATH + basicApiFileName;
       const loadingTask = (0, _api.getDocument)(urlStr);
+      expect(loadingTask instanceof _api.PDFDocumentLoadingTask).toEqual(true);
       const pdfDocument = await loadingTask.promise;
       expect(typeof urlStr).toEqual("string");
       expect(pdfDocument instanceof _api.PDFDocumentProxy).toEqual(true);
@@ -75,6 +76,7 @@ describe("api", function () {
 
       const urlObj = new URL(_test_utils.TEST_PDFS_PATH + basicApiFileName, window.location);
       const loadingTask = (0, _api.getDocument)(urlObj);
+      expect(loadingTask instanceof _api.PDFDocumentLoadingTask).toEqual(true);
       const pdfDocument = await loadingTask.promise;
       expect(urlObj instanceof URL).toEqual(true);
       expect(pdfDocument instanceof _api.PDFDocumentProxy).toEqual(true);
@@ -83,6 +85,7 @@ describe("api", function () {
     });
     it("creates pdf doc from URL", async function () {
       const loadingTask = (0, _api.getDocument)(basicApiGetDocumentParams);
+      expect(loadingTask instanceof _api.PDFDocumentLoadingTask).toEqual(true);
       const progressReportedCapability = (0, _util.createPromiseCapability)();
 
       loadingTask.onProgress = function (progressData) {
@@ -99,6 +102,7 @@ describe("api", function () {
     });
     it("creates pdf doc from URL and aborts before worker initialized", async function () {
       const loadingTask = (0, _api.getDocument)(basicApiGetDocumentParams);
+      expect(loadingTask instanceof _api.PDFDocumentLoadingTask).toEqual(true);
       const destroyed = loadingTask.destroy();
 
       try {
@@ -111,6 +115,7 @@ describe("api", function () {
     });
     it("creates pdf doc from URL and aborts loading after worker initialized", async function () {
       const loadingTask = (0, _api.getDocument)(basicApiGetDocumentParams);
+      expect(loadingTask instanceof _api.PDFDocumentLoadingTask).toEqual(true);
 
       const destroyed = loadingTask._worker.promise.then(function () {
         return loadingTask.destroy();
@@ -125,6 +130,7 @@ describe("api", function () {
       });
       expect(typedArrayPdf.length).toEqual(basicApiFileLength);
       const loadingTask = (0, _api.getDocument)(typedArrayPdf);
+      expect(loadingTask instanceof _api.PDFDocumentLoadingTask).toEqual(true);
       const progressReportedCapability = (0, _util.createPromiseCapability)();
 
       loadingTask.onProgress = function (data) {
@@ -138,6 +144,7 @@ describe("api", function () {
     });
     it("creates pdf doc from invalid PDF file", async function () {
       const loadingTask = (0, _api.getDocument)((0, _test_utils.buildGetDocumentParams)("bug1020226.pdf"));
+      expect(loadingTask instanceof _api.PDFDocumentLoadingTask).toEqual(true);
 
       try {
         await loadingTask.promise;
@@ -155,6 +162,7 @@ describe("api", function () {
       }
 
       const loadingTask = (0, _api.getDocument)((0, _test_utils.buildGetDocumentParams)("non-existent.pdf"));
+      expect(loadingTask instanceof _api.PDFDocumentLoadingTask).toEqual(true);
 
       try {
         await loadingTask.promise;
@@ -167,6 +175,7 @@ describe("api", function () {
     });
     it("creates pdf doc from PDF file protected with user and owner password", async function () {
       const loadingTask = (0, _api.getDocument)((0, _test_utils.buildGetDocumentParams)("pr6531_1.pdf"));
+      expect(loadingTask instanceof _api.PDFDocumentLoadingTask).toEqual(true);
       const passwordNeededCapability = (0, _util.createPromiseCapability)();
       const passwordIncorrectCapability = (0, _util.createPromiseCapability)();
 
@@ -195,6 +204,7 @@ describe("api", function () {
       const passwordNeededLoadingTask = (0, _api.getDocument)((0, _test_utils.buildGetDocumentParams)(filename, {
         password: ""
       }));
+      expect(passwordNeededLoadingTask instanceof _api.PDFDocumentLoadingTask).toEqual(true);
       const result1 = passwordNeededLoadingTask.promise.then(function () {
         expect(false).toEqual(true);
         return Promise.reject(new Error("loadingTask should be rejected"));
@@ -206,6 +216,7 @@ describe("api", function () {
       const passwordIncorrectLoadingTask = (0, _api.getDocument)((0, _test_utils.buildGetDocumentParams)(filename, {
         password: "qwerty"
       }));
+      expect(passwordIncorrectLoadingTask instanceof _api.PDFDocumentLoadingTask).toEqual(true);
       const result2 = passwordIncorrectLoadingTask.promise.then(function () {
         expect(false).toEqual(true);
         return Promise.reject(new Error("loadingTask should be rejected"));
@@ -217,6 +228,7 @@ describe("api", function () {
       const passwordAcceptedLoadingTask = (0, _api.getDocument)((0, _test_utils.buildGetDocumentParams)(filename, {
         password: "asdfasdf"
       }));
+      expect(passwordAcceptedLoadingTask instanceof _api.PDFDocumentLoadingTask).toEqual(true);
       const result3 = passwordAcceptedLoadingTask.promise.then(function (data) {
         expect(data instanceof _api.PDFDocumentProxy).toEqual(true);
         return passwordAcceptedLoadingTask.destroy();
@@ -226,9 +238,11 @@ describe("api", function () {
     it("creates pdf doc from password protected PDF file and aborts/throws " + "in the onPassword callback (issue 7806)", async function () {
       const filename = "issue3371.pdf";
       const passwordNeededLoadingTask = (0, _api.getDocument)((0, _test_utils.buildGetDocumentParams)(filename));
+      expect(passwordNeededLoadingTask instanceof _api.PDFDocumentLoadingTask).toEqual(true);
       const passwordIncorrectLoadingTask = (0, _api.getDocument)((0, _test_utils.buildGetDocumentParams)(filename, {
         password: "qwerty"
       }));
+      expect(passwordIncorrectLoadingTask instanceof _api.PDFDocumentLoadingTask).toEqual(true);
       let passwordNeededDestroyed;
 
       passwordNeededLoadingTask.onPassword = function (callback, reason) {
@@ -269,6 +283,7 @@ describe("api", function () {
     });
     it("creates pdf doc from empty typed array", async function () {
       const loadingTask = (0, _api.getDocument)(new Uint8Array(0));
+      expect(loadingTask instanceof _api.PDFDocumentLoadingTask).toEqual(true);
 
       try {
         await loadingTask.promise;
@@ -280,6 +295,22 @@ describe("api", function () {
 
       await loadingTask.destroy();
     });
+    it("checks that `docId`s are unique and increasing", async function () {
+      const loadingTask1 = (0, _api.getDocument)(basicApiGetDocumentParams);
+      expect(loadingTask1 instanceof _api.PDFDocumentLoadingTask).toEqual(true);
+      await loadingTask1.promise;
+      const docId1 = loadingTask1.docId;
+      const loadingTask2 = (0, _api.getDocument)(basicApiGetDocumentParams);
+      expect(loadingTask2 instanceof _api.PDFDocumentLoadingTask).toEqual(true);
+      await loadingTask2.promise;
+      const docId2 = loadingTask2.docId;
+      expect(docId1).not.toEqual(docId2);
+      const docIdRegExp = /^d(\d+)$/,
+            docNum1 = docIdRegExp.exec(docId1)?.[1],
+            docNum2 = docIdRegExp.exec(docId2)?.[1];
+      expect(+docNum1).toBeLessThan(+docNum2);
+      await Promise.all([loadingTask1.destroy(), loadingTask2.destroy()]);
+    });
   });
   describe("PDFWorker", function () {
     it("worker created or destroyed", async function () {
@@ -368,8 +399,7 @@ describe("api", function () {
         pending("Worker is not supported in Node.js.");
       }
 
-      const workerSrc = _api.PDFWorker.getWorkerSrc();
-
+      const workerSrc = _api.PDFWorker.workerSrc;
       expect(typeof workerSrc).toEqual("string");
       expect(workerSrc).toEqual(_worker_options.GlobalWorkerOptions.workerSrc);
     });
@@ -911,6 +941,24 @@ describe("api", function () {
       expect(fingerprints2).toEqual(["04c7126b34a46b6d4d6e7a1eff7edcb6", null]);
       await Promise.all([loadingTask1.destroy(), loadingTask2.destroy()]);
     });
+    it("write a value in an annotation, save the pdf and load it", async function () {
+      let loadingTask = (0, _api.getDocument)((0, _test_utils.buildGetDocumentParams)("evaljs.pdf"));
+      let pdfDoc = await loadingTask.promise;
+      const value = "Hello World";
+      pdfDoc.annotationStorage.setValue("55R", {
+        value
+      });
+      const data = await pdfDoc.saveDocument();
+      await loadingTask.destroy();
+      loadingTask = (0, _api.getDocument)(data);
+      pdfDoc = await loadingTask.promise;
+      const pdfPage = await pdfDoc.getPage(1);
+      const annotations = await pdfPage.getAnnotations();
+      const field = annotations.find(annotation => annotation.id === "55R");
+      expect(!!field).toEqual(true);
+      expect(field.fieldValue).toEqual(value);
+      await loadingTask.destroy();
+    });
     describe("Cross-origin", function () {
       let loadingTask;
 
@@ -1085,6 +1133,11 @@ describe("api", function () {
       const defaultPromise = page.getAnnotations().then(function (data) {
         expect(data.length).toEqual(4);
       });
+      const anyPromise = page.getAnnotations({
+        intent: "any"
+      }).then(function (data) {
+        expect(data.length).toEqual(4);
+      });
       const displayPromise = page.getAnnotations({
         intent: "display"
       }).then(function (data) {
@@ -1095,7 +1148,7 @@ describe("api", function () {
       }).then(function (data) {
         expect(data.length).toEqual(4);
       });
-      await Promise.all([defaultPromise, displayPromise, printPromise]);
+      await Promise.all([defaultPromise, anyPromise, displayPromise, printPromise]);
     });
     it("gets annotations containing relative URLs (bug 766086)", async function () {
       const filename = "bug766086.pdf";
@@ -1287,6 +1340,45 @@ describe("api", function () {
       expect(operatorList.fnArray.includes(_util.OPS.endAnnotation)).toEqual(true);
       await loadingTask.destroy();
     });
+    it("gets operator list, with `annotationMode`-option", async function () {
+      const loadingTask = (0, _api.getDocument)((0, _test_utils.buildGetDocumentParams)("evaljs.pdf"));
+      const pdfDoc = await loadingTask.promise;
+      const pdfPage = await pdfDoc.getPage(2);
+      pdfDoc.annotationStorage.setValue("30R", {
+        value: "test"
+      });
+      pdfDoc.annotationStorage.setValue("31R", {
+        value: true
+      });
+      const opListAnnotDisable = await pdfPage.getOperatorList({
+        annotationMode: _util.AnnotationMode.DISABLE
+      });
+      expect(opListAnnotDisable.fnArray.length).toEqual(0);
+      expect(opListAnnotDisable.argsArray.length).toEqual(0);
+      expect(opListAnnotDisable.lastChunk).toEqual(true);
+      const opListAnnotEnable = await pdfPage.getOperatorList({
+        annotationMode: _util.AnnotationMode.ENABLE
+      });
+      expect(opListAnnotEnable.fnArray.length).toBeGreaterThan(150);
+      expect(opListAnnotEnable.argsArray.length).toBeGreaterThan(150);
+      expect(opListAnnotEnable.lastChunk).toEqual(true);
+      const opListAnnotEnableForms = await pdfPage.getOperatorList({
+        annotationMode: _util.AnnotationMode.ENABLE_FORMS
+      });
+      expect(opListAnnotEnableForms.fnArray.length).toBeGreaterThan(40);
+      expect(opListAnnotEnableForms.argsArray.length).toBeGreaterThan(40);
+      expect(opListAnnotEnableForms.lastChunk).toEqual(true);
+      const opListAnnotEnableStorage = await pdfPage.getOperatorList({
+        annotationMode: _util.AnnotationMode.ENABLE_STORAGE
+      });
+      expect(opListAnnotEnableStorage.fnArray.length).toBeGreaterThan(170);
+      expect(opListAnnotEnableStorage.argsArray.length).toBeGreaterThan(170);
+      expect(opListAnnotEnableStorage.lastChunk).toEqual(true);
+      expect(opListAnnotDisable.fnArray.length).toBeLessThan(opListAnnotEnableForms.fnArray.length);
+      expect(opListAnnotEnableForms.fnArray.length).toBeLessThan(opListAnnotEnable.fnArray.length);
+      expect(opListAnnotEnable.fnArray.length).toBeLessThan(opListAnnotEnableStorage.fnArray.length);
+      await loadingTask.destroy();
+    });
     it("gets document stats after parsing page", async function () {
       const stats = await page.getOperatorList().then(function () {
         return pdfDocument.getStats();
@@ -1335,6 +1427,7 @@ describe("api", function () {
         canvasFactory: CanvasFactory,
         viewport
       });
+      expect(renderTask instanceof _api.RenderTask).toEqual(true);
       await renderTask.promise;
       const stats = pdfPage.stats;
       expect(stats instanceof _display_utils.StatTimer).toEqual(true);
@@ -1359,6 +1452,7 @@ describe("api", function () {
         canvasFactory: CanvasFactory,
         viewport
       });
+      expect(renderTask instanceof _api.RenderTask).toEqual(true);
       renderTask.cancel();
 
       try {
@@ -1382,6 +1476,7 @@ describe("api", function () {
         canvasFactory: CanvasFactory,
         viewport
       });
+      expect(renderTask instanceof _api.RenderTask).toEqual(true);
       renderTask.cancel();
 
       try {
@@ -1396,6 +1491,7 @@ describe("api", function () {
         canvasFactory: CanvasFactory,
         viewport
       });
+      expect(reRenderTask instanceof _api.RenderTask).toEqual(true);
       await reRenderTask.promise;
       CanvasFactory.destroy(canvasAndCtx);
     });
@@ -1411,12 +1507,14 @@ describe("api", function () {
         viewport,
         optionalContentConfigPromise
       });
+      expect(renderTask1 instanceof _api.RenderTask).toEqual(true);
       const renderTask2 = page.render({
         canvasContext: canvasAndCtx.context,
         canvasFactory: CanvasFactory,
         viewport,
         optionalContentConfigPromise
       });
+      expect(renderTask2 instanceof _api.RenderTask).toEqual(true);
       await Promise.all([renderTask1.promise, renderTask2.promise.then(() => {
         expect(false).toEqual(true);
       }, reason => {
@@ -1436,6 +1534,7 @@ describe("api", function () {
         canvasFactory: CanvasFactory,
         viewport
       });
+      expect(renderTask instanceof _api.RenderTask).toEqual(true);
       await renderTask.promise;
       await pdfDoc.cleanup();
       expect(true).toEqual(true);
@@ -1455,6 +1554,7 @@ describe("api", function () {
         canvasFactory: CanvasFactory,
         viewport
       });
+      expect(renderTask instanceof _api.RenderTask).toEqual(true);
 
       renderTask.onContinue = function (cont) {
         waitSome(cont);

+ 35 - 0
lib/test/unit/crypto_spec.js

@@ -360,6 +360,13 @@ describe("CipherTransformFactory", function () {
     }
   }
 
+  function ensureAESEncryptedStringHasCorrectLength(dict, fileId, password, string) {
+    const factory = new _crypto.CipherTransformFactory(dict, fileId, password);
+    const cipher = factory.createCipherTransform(123, 0);
+    const encrypted = cipher.encryptString(string);
+    expect(encrypted.length).toEqual(16 + 16 * Math.ceil((string.length + 1) / 16));
+  }
+
   function ensureEncryptDecryptIsIdentity(dict, fileId, password, string) {
     const factory = new _crypto.CipherTransformFactory(dict, fileId, password);
     const cipher = factory.createCipherTransform(123, 0);
@@ -513,6 +520,7 @@ describe("CipherTransformFactory", function () {
         })
       });
       const dict = buildDict(dict3);
+      ensureEncryptDecryptIsIdentity(dict, fileId1, "user", "");
       ensureEncryptDecryptIsIdentity(dict, fileId1, "user", "a");
       ensureEncryptDecryptIsIdentity(dict, fileId1, "user", "aa");
       ensureEncryptDecryptIsIdentity(dict, fileId1, "user", "aaaaaaaaaaaaaaaa");
@@ -525,10 +533,37 @@ describe("CipherTransformFactory", function () {
         })
       });
       const dict = buildDict(dict3);
+      ensureEncryptDecryptIsIdentity(dict, fileId1, "user", "");
       ensureEncryptDecryptIsIdentity(dict, fileId1, "user", "aaaa");
       ensureEncryptDecryptIsIdentity(dict, fileId1, "user", "aaaaa");
       ensureEncryptDecryptIsIdentity(dict, fileId1, "user", "aaaaaaaaaaaaaaaa");
       ensureEncryptDecryptIsIdentity(dict, fileId1, "user", "aaaaaaaaaaaaaaaaaaaaaa");
     });
+    it("should encrypt and have the correct length using AES128", function () {
+      dict3.CF = buildDict({
+        Identity: buildDict({
+          CFM: _primitives.Name.get("AESV2")
+        })
+      });
+      const dict = buildDict(dict3);
+      ensureAESEncryptedStringHasCorrectLength(dict, fileId1, "user", "");
+      ensureAESEncryptedStringHasCorrectLength(dict, fileId1, "user", "a");
+      ensureAESEncryptedStringHasCorrectLength(dict, fileId1, "user", "aa");
+      ensureAESEncryptedStringHasCorrectLength(dict, fileId1, "user", "aaaaaaaaaaaaaaaa");
+      ensureAESEncryptedStringHasCorrectLength(dict, fileId1, "user", "aaaaaaaaaaaaaaaaaaa");
+    });
+    it("should encrypt and have the correct length using AES256", function () {
+      dict3.CF = buildDict({
+        Identity: buildDict({
+          CFM: _primitives.Name.get("AESV3")
+        })
+      });
+      const dict = buildDict(dict3);
+      ensureAESEncryptedStringHasCorrectLength(dict, fileId1, "user", "");
+      ensureAESEncryptedStringHasCorrectLength(dict, fileId1, "user", "aaaa");
+      ensureAESEncryptedStringHasCorrectLength(dict, fileId1, "user", "aaaaa");
+      ensureAESEncryptedStringHasCorrectLength(dict, fileId1, "user", "aaaaaaaaaaaaaaaa");
+      ensureAESEncryptedStringHasCorrectLength(dict, fileId1, "user", "aaaaaaaaaaaaaaaaaaaaaa");
+    });
   });
 });

+ 7 - 8
lib/test/unit/jasmine-boot.js

@@ -25,6 +25,8 @@ var _worker_options = require("pdfjs/display/worker_options.js");
 
 var _is_node = require("pdfjs/shared/is_node.js");
 
+var _display_utils = require("pdfjs/display/display_utils.js");
+
 var _fetch_stream = require("pdfjs/display/fetch_stream.js");
 
 var _network = require("pdfjs/display/network.js");
@@ -42,16 +44,13 @@ async function initializePDFJS(callback) {
     throw new Error("The `gulp unittest` command cannot be used in Node.js environments.");
   }
 
-  if (typeof Response !== "undefined" && "body" in Response.prototype && typeof ReadableStream !== "undefined") {
-    (0, _api.setPDFNetworkStreamFactory)(function (params) {
+  (0, _api.setPDFNetworkStreamFactory)(params => {
+    if ((0, _display_utils.isValidFetchUrl)(params.url)) {
       return new _fetch_stream.PDFFetchStream(params);
-    });
-  } else {
-    (0, _api.setPDFNetworkStreamFactory)(function (params) {
-      return new _network.PDFNetworkStream(params);
-    });
-  }
+    }
 
+    return new _network.PDFNetworkStream(params);
+  });
   _worker_options.GlobalWorkerOptions.workerSrc = "../../build/generic/build/pdf.worker.js";
   callback();
 }

+ 19 - 2
lib/test/unit/parser_spec.js

@@ -21,12 +21,12 @@
  */
 "use strict";
 
+var _primitives = require("../../core/primitives.js");
+
 var _parser = require("../../core/parser.js");
 
 var _util = require("../../shared/util.js");
 
-var _primitives = require("../../core/primitives.js");
-
 var _stream = require("../../core/stream.js");
 
 describe("parser", function () {
@@ -187,6 +187,23 @@ describe("parser", function () {
         }
       });
     });
+    describe("getObj", function () {
+      it("should stop immediately when the start of a command is " + "a non-visible ASCII character (issue 13999)", function () {
+        const input = new _stream.StringStream("\x14q\nQ");
+        const lexer = new _parser.Lexer(input);
+        let obj = lexer.getObj();
+        expect(obj instanceof _primitives.Cmd).toEqual(true);
+        expect(obj.cmd).toEqual("\x14");
+        obj = lexer.getObj();
+        expect(obj instanceof _primitives.Cmd).toEqual(true);
+        expect(obj.cmd).toEqual("q");
+        obj = lexer.getObj();
+        expect(obj instanceof _primitives.Cmd).toEqual(true);
+        expect(obj.cmd).toEqual("Q");
+        obj = lexer.getObj();
+        expect(obj).toEqual(_primitives.EOF);
+      });
+    });
   });
   describe("Linearization", function () {
     it("should not find a linearization dictionary", function () {

+ 0 - 9
lib/test/unit/primitives_spec.js

@@ -408,15 +408,6 @@ describe("primitives", function () {
       expect(values).toEqual([obj1, obj2]);
     });
   });
-  describe("isEOF", function () {
-    it("handles non-EOF", function () {
-      const nonEOF = "foo";
-      expect((0, _primitives.isEOF)(nonEOF)).toEqual(false);
-    });
-    it("handles EOF", function () {
-      expect((0, _primitives.isEOF)(_primitives.EOF)).toEqual(true);
-    });
-  });
   describe("isName", function () {
     it("handles non-names", function () {
       const nonName = {};

+ 30 - 0
lib/test/unit/ui_utils_spec.js

@@ -237,6 +237,36 @@ describe("ui_utils", function () {
       })).toEqual(false);
     });
   });
+  describe("parseQueryString", function () {
+    it("should parse one key/value pair", function () {
+      const parameters = (0, _ui_utils.parseQueryString)("key1=value1");
+      expect(parameters.size).toEqual(1);
+      expect(parameters.get("key1")).toEqual("value1");
+    });
+    it("should parse multiple key/value pairs", function () {
+      const parameters = (0, _ui_utils.parseQueryString)("key1=value1&key2=value2&key3=value3");
+      expect(parameters.size).toEqual(3);
+      expect(parameters.get("key1")).toEqual("value1");
+      expect(parameters.get("key2")).toEqual("value2");
+      expect(parameters.get("key3")).toEqual("value3");
+    });
+    it("should parse keys without values", function () {
+      const parameters = (0, _ui_utils.parseQueryString)("key1");
+      expect(parameters.size).toEqual(1);
+      expect(parameters.get("key1")).toEqual("");
+    });
+    it("should decode encoded key/value pairs", function () {
+      const parameters = (0, _ui_utils.parseQueryString)("k%C3%ABy1=valu%C3%AB1");
+      expect(parameters.size).toEqual(1);
+      expect(parameters.get("këy1")).toEqual("valuë1");
+    });
+    it("should convert keys to lowercase", function () {
+      const parameters = (0, _ui_utils.parseQueryString)("Key1=Value1&KEY2=Value2");
+      expect(parameters.size).toEqual(2);
+      expect(parameters.get("key1")).toEqual("Value1");
+      expect(parameters.get("key2")).toEqual("Value2");
+    });
+  });
   describe("waitOnEventOrTimeout", function () {
     let eventBus;
     beforeAll(function () {

+ 44 - 1
lib/test/unit/writer_spec.js

@@ -102,9 +102,13 @@ describe("Writer", function () {
       stream.dict.set("Length", string.length);
       gdict.set("I", stream);
       dict.set("G", gdict);
+      dict.set("J", true);
+      dict.set("K", false);
+      dict.set("NullArr", [null, 10]);
+      dict.set("NullVal", null);
       const buffer = [];
       (0, _writer.writeDict)(dict, buffer, null);
-      const expected = "<< /A /B /B 123 456 R /C 789 /D (hello world) " + "/E (\\(hello\\\\world\\)) /F [1.23 4.5 6] " + "/G << /H 123 /I << /Length 8>> stream\n" + "a stream\n" + "endstream\n>>>>";
+      const expected = "<< /A /B /B 123 456 R /C 789 /D (hello world) " + "/E (\\(hello\\\\world\\)) /F [1.23 4.5 6] " + "/G << /H 123 /I << /Length 8>> stream\n" + "a stream\n" + "endstream\n>> /J true /K false " + "/NullArr [null 10] /NullVal null>>";
       expect(buffer.join("")).toEqual(expected);
     });
     it("should write a Dict in escaping PDF names", function () {
@@ -118,4 +122,43 @@ describe("Writer", function () {
       expect(buffer.join("")).toEqual(expected);
     });
   });
+  describe("XFA", function () {
+    it("should update AcroForm when no datasets in XFA array", function () {
+      const originalData = new Uint8Array();
+      const newRefs = [];
+      const acroForm = new _primitives.Dict(null);
+      acroForm.set("XFA", ["preamble", _primitives.Ref.get(123, 0), "postamble", _primitives.Ref.get(456, 0)]);
+
+      const acroFormRef = _primitives.Ref.get(789, 0);
+
+      const xfaDatasetsRef = _primitives.Ref.get(101112, 0);
+
+      const xfaData = "<hello>world</hello>";
+      const xrefInfo = {
+        newRef: _primitives.Ref.get(131415, 0),
+        startXRef: 314,
+        fileIds: null,
+        rootRef: null,
+        infoRef: null,
+        encryptRef: null,
+        filename: "foo.pdf",
+        info: {}
+      };
+      let data = (0, _writer.incrementalUpdate)({
+        originalData,
+        xrefInfo,
+        newRefs,
+        hasXfa: true,
+        xfaDatasetsRef,
+        hasXfaDatasetsEntry: false,
+        acroFormRef,
+        acroForm,
+        xfaData,
+        xref: {}
+      });
+      data = (0, _util.bytesToString)(data);
+      const expected = "\n" + "789 0 obj\n" + "<< /XFA [(preamble) 123 0 R (datasets) 101112 0 R (postamble) 456 0 R]>>\n" + "101112 0 obj\n" + "<< /Type /EmbeddedFile /Length 20>>\n" + "stream\n" + "<hello>world</hello>\n" + "endstream\n" + "endobj\n" + "131415 0 obj\n" + "<< /Size 131416 /Prev 314 /Type /XRef /Index [0 1 789 1 101112 1 131415 1] /W [1 1 2] /Length 16>> stream\n" + "\u0000\u0001ÿÿ\u0001\u0001\u0000\u0000\u0001T\u0000\u0000\u0001²\u0000\u0000\n" + "endstream\n" + "endobj\n" + "startxref\n" + "178\n" + "%%EOF\n";
+      expect(data).toEqual(expected);
+    });
+  });
 });

+ 104 - 0
lib/test/unit/xfa_parser_spec.js

@@ -800,6 +800,53 @@ describe("XFAParser", function () {
       };
       expect((0, _som.searchNode)(data, data, "A")[0][_xfa_object.$dump]()).toEqual(expected);
     });
+    it("should make a basic binding and create a non-existing node with namespaceId equal to -1", function () {
+      const xml = `
+<?xml version="1.0"?>
+<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
+  <template xmlns="http://www.xfa.org/schema/xfa-template/3.3">
+    <subform name="A">
+      <subform name="B">
+        <field name="C">
+        </field>
+        <field name="D">
+          <value>
+            <text>foobar</text>
+          </value>
+        </field>
+      </subform>
+    </subform>
+  </template>
+</xdp:xdp>
+      `;
+      const root = new _parser.XFAParser().parse(xml);
+      const binder = new _bind.Binder(root);
+      const form = binder.bind();
+      const data = binder.getData();
+      expect((0, _som.searchNode)(form, form, "A.B.D.value.text")[0][_xfa_object.$dump]().$content).toBe("foobar");
+      const expected = {
+        $name: "A",
+        $ns: -1,
+        attributes: {},
+        children: [{
+          $name: "B",
+          $ns: -1,
+          attributes: {},
+          children: [{
+            $name: "C",
+            $ns: -1,
+            attributes: {},
+            children: []
+          }, {
+            $name: "D",
+            $ns: -1,
+            attributes: {},
+            children: []
+          }]
+        }]
+      };
+      expect((0, _som.searchNode)(data, data, "A")[0][_xfa_object.$dump](true)).toEqual(expected);
+    });
     it("should make another basic binding", function () {
       const xml = `
 <?xml version="1.0"?>
@@ -1068,6 +1115,41 @@ describe("XFAParser", function () {
       const form = new _bind.Binder(root).bind();
       expect((0, _som.searchNode)(form, form, "subform.CardName.items[*].text[*]").map(x => x[_xfa_object.$text]())).toEqual(["Visa", "Mastercard", "American Express", "VISA", "MC", "AMEX"]);
     });
+    it("should make binding and bind items with a ref", function () {
+      const xml = `
+<?xml version="1.0"?>
+<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
+  <template xmlns="http://www.xfa.org/schema/xfa-template/3.3">
+    <subform name="main">
+      <field name="CardName">
+        <bind match="dataRef" ref="$data.main.value"/>
+        <bindItems ref="$data.main.ccs.cc[*]" labelRef="uiname" valueRef="token"/>
+        <ui>
+          <choiceList/>
+        </ui>
+      </field>
+    </subform>
+  </template>
+  <xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
+    <xfa:data>
+      <main>
+        <value>VISA</value>
+        <ccs>
+          <cc uiname="Visa" token="VISA"/>
+          <cc uiname="Mastercard" token="MC"/>
+          <cc uiname="American Express" token="AMEX"/>
+        </ccs>
+        <CardName>MC</CardName>
+      </main>
+    </xfa:data>
+  </xfa:datasets>
+</xdp:xdp>
+      `;
+      const root = new _parser.XFAParser().parse(xml);
+      const form = new _bind.Binder(root).bind();
+      expect((0, _som.searchNode)(form, form, "subform.CardName.value.text").map(x => x[_xfa_object.$text]())).toEqual(["VISA"]);
+      expect((0, _som.searchNode)(form, form, "subform.CardName.items[*].text[*]").map(x => x[_xfa_object.$text]())).toEqual(["Visa", "Mastercard", "American Express", "VISA", "MC", "AMEX"]);
+    });
     it("should make binding with occurrences in consumeData mode", function () {
       const xml = `
 <?xml version="1.0"?>
@@ -1233,4 +1315,26 @@ describe("XFAParser", function () {
       expect((0, _som.searchNode)(form, form, "A.B.C.items[1].text[*]").map(x => x[_xfa_object.$dump]().$content)).toEqual(["a", "b", "c", "d", "e"]);
     });
   });
+  it("should make a binding with a element in an area", function () {
+    const xml = `
+<?xml version="1.0"?>
+<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
+  <template xmlns="http://www.xfa.org/schema/xfa-template/3.3">
+    <subform name="A" mergeMode="matchTemplate">
+      <area>
+        <field name="B"/>
+      </area>
+    </subform>
+  </template>
+  <xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
+    <xfa:data>
+      <A><B>foobar</B></A>
+    </xfa:data>
+  </xfa:datasets>
+</xdp:xdp>
+    `;
+    const root = new _parser.XFAParser().parse(xml);
+    const form = new _bind.Binder(root).bind();
+    expect((0, _som.searchNode)(form, form, "A..B..text")[0][_xfa_object.$dump]().$content).toBe("foobar");
+  });
 });

+ 338 - 4
lib/test/unit/xfa_tohtml_spec.js

@@ -21,12 +21,16 @@
  */
 "use strict";
 
+var _is_node = require("../../shared/is_node.js");
+
 var _factory = require("../../core/xfa/factory.js");
 
 describe("XFAFactory", function () {
-  function searchHtmlNode(root, name, value) {
-    if (root[name] === value) {
-      return root;
+  function searchHtmlNode(root, name, value, byAttributes = false, nth = [0]) {
+    if (!byAttributes && root[name] === value || byAttributes && root.attributes && root.attributes[name] === value) {
+      if (nth[0]-- === 0) {
+        return root;
+      }
     }
 
     if (!root.children) {
@@ -34,7 +38,7 @@ describe("XFAFactory", function () {
     }
 
     for (const child of root.children) {
-      const node = searchHtmlNode(child, name, value);
+      const node = searchHtmlNode(child, name, value, byAttributes, nth);
 
       if (node) {
         return node;
@@ -137,6 +141,136 @@ describe("XFAFactory", function () {
       });
       expect(draw.attributes.style).toEqual(pages.children[1].children[0].children[0].attributes.style);
     });
+    it("should have an alt attribute from toolTip", function () {
+      if (_is_node.isNodeJS) {
+        pending("Image is not supported in Node.js.");
+      }
+
+      const xml = `
+<?xml version="1.0"?>
+<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
+  <template xmlns="http://www.xfa.org/schema/xfa-template/3.3">
+    <subform name="root" mergeMode="matchTemplate">
+      <pageSet>
+        <pageArea>
+          <contentArea x="0pt" w="456pt" h="789pt"/>
+          <draw name="BA-Logo" y="5.928mm" x="128.388mm" w="71.237mm" h="9.528mm">
+            <value>
+              <image contentType="image/png">iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVQYV2NgYAAAAAMAAWgmWQ0AAAAASUVORK5CYII=</image>
+            </value>
+            <assist><toolTip>alt text</toolTip></assist>
+          </draw>
+        </pageArea>
+      </pageSet>
+    </subform>
+  </template>
+  <xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
+    <xfa:data>
+    </xfa:data>
+  </xfa:datasets>
+</xdp:xdp>
+      `;
+      const factory = new _factory.XFAFactory({
+        "xdp:xdp": xml
+      });
+      expect(factory.numberPages).toEqual(1);
+      const pages = factory.getPages();
+      const field = searchHtmlNode(pages, "name", "img");
+      expect(field.attributes.alt).toEqual("alt text");
+    });
+    it("should have a aria heading role and level", function () {
+      const xml = `
+<?xml version="1.0"?>
+<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
+  <template xmlns="http://www.xfa.org/schema/xfa-template/3.3">
+    <subform name="root" mergeMode="matchTemplate">
+      <pageSet>
+        <pageArea>
+          <contentArea x="0pt" w="456pt" h="789pt"/>
+          <medium stock="default" short="456pt" long="789pt"/>
+          <draw name="BA-Logo" y="5.928mm" x="128.388mm" w="71.237mm" h="9.528mm">
+            <value><text>foo</text></value>
+            <assist role="H2"></assist>
+          </draw>
+        </pageArea>
+      </pageSet>
+    </subform>
+  </template>
+  <xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
+    <xfa:data>
+    </xfa:data>
+  </xfa:datasets>
+</xdp:xdp>
+      `;
+      const factory = new _factory.XFAFactory({
+        "xdp:xdp": xml
+      });
+      expect(factory.numberPages).toEqual(1);
+      const pages = factory.getPages();
+      const page1 = pages.children[0];
+      const wrapper = page1.children[0];
+      const draw = wrapper.children[0];
+      expect(draw.attributes.role).toEqual("heading");
+      expect(draw.attributes["aria-level"]).toEqual("2");
+    });
+    it("should have aria table role", function () {
+      const xml = `
+<?xml version="1.0"?>
+<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
+  <template xmlns="http://www.xfa.org/schema/xfa-template/3.3">
+    <subform name="root" mergeMode="matchTemplate">
+      <pageSet>
+        <pageArea>
+          <contentArea x="0pt" w="456pt" h="789pt"/>
+          <medium stock="default" short="456pt" long="789pt"/>
+          <font size="7pt" typeface="FooBar" baselineShift="2pt">
+          </font>
+        </pageArea>
+      </pageSet>
+      <subform name="table" mergeMode="matchTemplate" layout="table">
+        <subform layout="row" name="row1">
+          <assist role="TH"></assist>
+          <draw name="header1" y="5.928mm" x="128.388mm" w="71.237mm" h="9.528mm">
+            <value><text>Header Col 1</text></value>
+          </draw>
+          <draw name="header2" y="5.928mm" x="128.388mm" w="71.237mm" h="9.528mm">
+            <value><text>Header Col 2</text></value>
+          </draw>
+        </subform>
+        <subform layout="row" name="row2">
+          <draw name="cell1" y="5.928mm" x="128.388mm" w="71.237mm" h="9.528mm">
+            <value><text>Cell 1</text></value>
+          </draw>
+          <draw name="cell2" y="5.928mm" x="128.388mm" w="71.237mm" h="9.528mm">
+            <value><text>Cell 2</text></value>
+          </draw>
+        </subform>
+      </subform>
+    </subform>
+  </template>
+  <xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
+    <xfa:data>
+    </xfa:data>
+  </xfa:datasets>
+</xdp:xdp>
+      `;
+      const factory = new _factory.XFAFactory({
+        "xdp:xdp": xml
+      });
+      factory.setFonts([]);
+      expect(factory.numberPages).toEqual(1);
+      const pages = factory.getPages();
+      const table = searchHtmlNode(pages, "xfaName", "table", true);
+      expect(table.attributes.role).toEqual("table");
+      const headerRow = searchHtmlNode(pages, "xfaName", "row1", true);
+      expect(headerRow.attributes.role).toEqual("row");
+      const headerCell = searchHtmlNode(pages, "xfaName", "header2", true);
+      expect(headerCell.attributes.role).toEqual("columnheader");
+      const row = searchHtmlNode(pages, "xfaName", "row2", true);
+      expect(row.attributes.role).toEqual("row");
+      const cell = searchHtmlNode(pages, "xfaName", "cell2", true);
+      expect(cell.attributes.role).toEqual("cell");
+    });
     it("should have a maxLength property", function () {
       const xml = `
 <?xml version="1.0"?>
@@ -176,6 +310,86 @@ describe("XFAFactory", function () {
       const field = searchHtmlNode(pages, "name", "input");
       expect(field.attributes.maxLength).toEqual(123);
     });
+    it("should have an aria-label property from speak", function () {
+      const xml = `
+<?xml version="1.0"?>
+<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
+  <template xmlns="http://www.xfa.org/schema/xfa-template/3.3">
+    <subform name="root" mergeMode="matchTemplate">
+      <pageSet>
+        <pageArea>
+          <contentArea x="0pt" w="456pt" h="789pt"/>
+          <medium stock="default" short="456pt" long="789pt"/>
+          <field y="1pt" w="11pt" h="22pt" x="2pt">
+            <assist><speak>Screen Reader</speak></assist>
+            <ui>
+              <textEdit multiLine="0"/>
+            </ui>
+            <value>
+              <text maxChars="123"/>
+            </value>
+          </field>
+        </pageArea>
+      </pageSet>
+      <subform name="first">
+        <draw w="1pt" h="1pt"><value><text>foo</text></value></draw>
+      </subform>
+    </subform>
+  </template>
+  <xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
+    <xfa:data>
+    </xfa:data>
+  </xfa:datasets>
+</xdp:xdp>
+      `;
+      const factory = new _factory.XFAFactory({
+        "xdp:xdp": xml
+      });
+      expect(factory.numberPages).toEqual(1);
+      const pages = factory.getPages();
+      const field = searchHtmlNode(pages, "name", "input");
+      expect(field.attributes["aria-label"]).toEqual("Screen Reader");
+    });
+    it("should have an aria-label property from toolTip", function () {
+      const xml = `
+<?xml version="1.0"?>
+<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
+  <template xmlns="http://www.xfa.org/schema/xfa-template/3.3">
+    <subform name="root" mergeMode="matchTemplate">
+      <pageSet>
+        <pageArea>
+          <contentArea x="0pt" w="456pt" h="789pt"/>
+          <medium stock="default" short="456pt" long="789pt"/>
+          <field y="1pt" w="11pt" h="22pt" x="2pt">
+            <assist><toolTip>Screen Reader</toolTip></assist>
+            <ui>
+              <textEdit multiLine="0"/>
+            </ui>
+            <value>
+              <text maxChars="123"/>
+            </value>
+          </field>
+        </pageArea>
+      </pageSet>
+      <subform name="first">
+        <draw w="1pt" h="1pt"><value><text>foo</text></value></draw>
+      </subform>
+    </subform>
+  </template>
+  <xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
+    <xfa:data>
+    </xfa:data>
+  </xfa:datasets>
+</xdp:xdp>
+      `;
+      const factory = new _factory.XFAFactory({
+        "xdp:xdp": xml
+      });
+      expect(factory.numberPages).toEqual(1);
+      const pages = factory.getPages();
+      const field = searchHtmlNode(pages, "name", "input");
+      expect(field.attributes["aria-label"]).toEqual("Screen Reader");
+    });
     it("should have an input or textarea", function () {
       const xml = `
 <?xml version="1.0"?>
@@ -270,4 +484,124 @@ describe("XFAFactory", function () {
     expect(field1).not.toEqual(null);
     expect(field1.attributes.value).toEqual("123");
   });
+  it("should parse URLs correctly", function () {
+    function getXml(href) {
+      return `
+<?xml version="1.0"?>
+<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
+  <template xmlns="http://www.xfa.org/schema/xfa-template/3.3">
+    <subform name="root" mergeMode="matchTemplate">
+      <pageSet>
+        <pageArea>
+          <contentArea x="0pt" w="456pt" h="789pt"/>
+          <medium stock="default" short="456pt" long="789pt"/>
+          <draw name="url" y="5.928mm" x="128.388mm" w="71.237mm" h="9.528mm">
+            <value>
+              <exData contentType="text/html">
+                <body xmlns="http://www.w3.org/1999/xhtml">
+                  <a href="${href}">${href}</a>
+                </body>
+              </exData>
+            </value>
+          </draw>
+        </pageArea>
+      </pageSet>
+    </subform>
+  </template>
+  <xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
+    <xfa:data>
+    </xfa:data>
+  </xfa:datasets>
+</xdp:xdp>
+      `;
+    }
+
+    let factory, pages, a;
+    factory = new _factory.XFAFactory({
+      "xdp:xdp": getXml("https://www.example.com/")
+    });
+    expect(factory.numberPages).toEqual(1);
+    pages = factory.getPages();
+    a = searchHtmlNode(pages, "name", "a");
+    expect(a.value).toEqual("https://www.example.com/");
+    expect(a.attributes.href).toEqual("https://www.example.com/");
+    factory = new _factory.XFAFactory({
+      "xdp:xdp": getXml("www.example.com/")
+    });
+    expect(factory.numberPages).toEqual(1);
+    pages = factory.getPages();
+    a = searchHtmlNode(pages, "name", "a");
+    expect(a.value).toEqual("www.example.com/");
+    expect(a.attributes.href).toEqual("http://www.example.com/");
+    factory = new _factory.XFAFactory({
+      "xdp:xdp": getXml("mailto:test@example.com")
+    });
+    expect(factory.numberPages).toEqual(1);
+    pages = factory.getPages();
+    a = searchHtmlNode(pages, "name", "a");
+    expect(a.value).toEqual("mailto:test@example.com");
+    expect(a.attributes.href).toEqual("mailto:test@example.com");
+    factory = new _factory.XFAFactory({
+      "xdp:xdp": getXml("qwerty/")
+    });
+    expect(factory.numberPages).toEqual(1);
+    pages = factory.getPages();
+    a = searchHtmlNode(pages, "name", "a");
+    expect(a.value).toEqual("qwerty/");
+    expect(a.attributes.href).toEqual("");
+  });
+  it("should replace button with an URL by a link", function () {
+    const xml = `
+<?xml version="1.0"?>
+<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
+  <template xmlns="http://www.xfa.org/schema/xfa-template/3.3">
+    <subform name="root" mergeMode="matchTemplate">
+      <pageSet>
+        <pageArea>
+          <contentArea x="123pt" w="456pt" h="789pt"/>
+          <medium stock="default" short="456pt" long="789pt"/>
+        </pageArea>
+      </pageSet>
+      <subform name="first">
+        <field y="1pt" w="11pt" h="22pt" x="2pt">
+          <ui>
+            <button/>
+          </ui>
+          <event activity="click" name="event__click">
+            <script contentType="application/x-javascript">
+              app.launchURL("https://github.com/mozilla/pdf.js", true);
+            </script>
+          </event>
+        </field>
+        <field y="1pt" w="11pt" h="22pt" x="2pt">
+          <ui>
+            <button/>
+          </ui>
+          <event activity="click" name="event__click">
+            <script contentType="application/x-javascript">
+              xfa.host.gotoURL("https://github.com/allizom/pdf.js");
+            </script>
+          </event>
+        </field>
+      </subform>
+    </subform>
+  </template>
+  <xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
+    <xfa:data>
+    </xfa:data>
+  </xfa:datasets>
+</xdp:xdp>
+    `;
+    const factory = new _factory.XFAFactory({
+      "xdp:xdp": xml
+    });
+    expect(factory.numberPages).toEqual(1);
+    const pages = factory.getPages();
+    let a = searchHtmlNode(pages, "name", "a");
+    expect(a.attributes.href).toEqual("https://github.com/mozilla/pdf.js");
+    expect(a.attributes.newWindow).toEqual(true);
+    a = searchHtmlNode(pages, "name", "a", false, [1]);
+    expect(a.attributes.href).toEqual("https://github.com/allizom/pdf.js");
+    expect(a.attributes.newWindow).toEqual(false);
+  });
 });

+ 45 - 45
lib/web/annotation_layer_builder.js

@@ -40,10 +40,11 @@ class AnnotationLayerBuilder {
     downloadManager,
     annotationStorage = null,
     imageResourcesPath = "",
-    renderInteractiveForms = true,
+    renderForms = true,
     l10n = _l10n_utils.NullL10n,
     enableScripting = false,
     hasJSActionsPromise = null,
+    fieldObjectsPromise = null,
     mouseState = null
   }) {
     this.pageDiv = pageDiv;
@@ -51,58 +52,56 @@ class AnnotationLayerBuilder {
     this.linkService = linkService;
     this.downloadManager = downloadManager;
     this.imageResourcesPath = imageResourcesPath;
-    this.renderInteractiveForms = renderInteractiveForms;
+    this.renderForms = renderForms;
     this.l10n = l10n;
     this.annotationStorage = annotationStorage;
     this.enableScripting = enableScripting;
     this._hasJSActionsPromise = hasJSActionsPromise;
+    this._fieldObjectsPromise = fieldObjectsPromise;
     this._mouseState = mouseState;
     this.div = null;
     this._cancelled = false;
   }
 
-  render(viewport, intent = "display") {
-    return Promise.all([this.pdfPage.getAnnotations({
+  async render(viewport, intent = "display") {
+    const [annotations, hasJSActions = false, fieldObjects = null] = await Promise.all([this.pdfPage.getAnnotations({
       intent
-    }), this._hasJSActionsPromise]).then(([annotations, hasJSActions = false]) => {
-      if (this._cancelled) {
-        return;
-      }
-
-      if (annotations.length === 0) {
-        return;
-      }
-
-      const parameters = {
-        viewport: viewport.clone({
-          dontFlip: true
-        }),
-        div: this.div,
-        annotations,
-        page: this.pdfPage,
-        imageResourcesPath: this.imageResourcesPath,
-        renderInteractiveForms: this.renderInteractiveForms,
-        linkService: this.linkService,
-        downloadManager: this.downloadManager,
-        annotationStorage: this.annotationStorage,
-        enableScripting: this.enableScripting,
-        hasJSActions,
-        mouseState: this._mouseState
-      };
-
-      if (this.div) {
-        _pdf.AnnotationLayer.update(parameters);
-      } else {
-        this.div = document.createElement("div");
-        this.div.className = "annotationLayer";
-        this.pageDiv.appendChild(this.div);
-        parameters.div = this.div;
-
-        _pdf.AnnotationLayer.render(parameters);
-
-        this.l10n.translate(this.div);
-      }
-    });
+    }), this._hasJSActionsPromise, this._fieldObjectsPromise]);
+
+    if (this._cancelled || annotations.length === 0) {
+      return;
+    }
+
+    const parameters = {
+      viewport: viewport.clone({
+        dontFlip: true
+      }),
+      div: this.div,
+      annotations,
+      page: this.pdfPage,
+      imageResourcesPath: this.imageResourcesPath,
+      renderForms: this.renderForms,
+      linkService: this.linkService,
+      downloadManager: this.downloadManager,
+      annotationStorage: this.annotationStorage,
+      enableScripting: this.enableScripting,
+      hasJSActions,
+      fieldObjects,
+      mouseState: this._mouseState
+    };
+
+    if (this.div) {
+      _pdf.AnnotationLayer.update(parameters);
+    } else {
+      this.div = document.createElement("div");
+      this.div.className = "annotationLayer";
+      this.pageDiv.appendChild(this.div);
+      parameters.div = this.div;
+
+      _pdf.AnnotationLayer.render(parameters);
+
+      this.l10n.translate(this.div);
+    }
   }
 
   cancel() {
@@ -122,17 +121,18 @@ class AnnotationLayerBuilder {
 exports.AnnotationLayerBuilder = AnnotationLayerBuilder;
 
 class DefaultAnnotationLayerFactory {
-  createAnnotationLayerBuilder(pageDiv, pdfPage, annotationStorage = null, imageResourcesPath = "", renderInteractiveForms = true, l10n = _l10n_utils.NullL10n, enableScripting = false, hasJSActionsPromise = null, mouseState = null) {
+  createAnnotationLayerBuilder(pageDiv, pdfPage, annotationStorage = null, imageResourcesPath = "", renderForms = true, l10n = _l10n_utils.NullL10n, enableScripting = false, hasJSActionsPromise = null, mouseState = null, fieldObjectsPromise = null) {
     return new AnnotationLayerBuilder({
       pageDiv,
       pdfPage,
       imageResourcesPath,
-      renderInteractiveForms,
+      renderForms,
       linkService: new _pdf_link_service.SimpleLinkService(),
       l10n,
       annotationStorage,
       enableScripting,
       hasJSActionsPromise,
+      fieldObjectsPromise,
       mouseState
     });
   }

+ 118 - 119
lib/web/app.js

@@ -72,11 +72,8 @@ var _secondary_toolbar = require("./secondary_toolbar.js");
 
 var _toolbar = require("./toolbar.js");
 
-var _viewer_compatibility = require("./viewer_compatibility.js");
-
 var _view_history = require("./view_history.js");
 
-const DEFAULT_SCALE_DELTA = 1.1;
 const DISABLE_AUTO_FETCH_LOADING_BAR_TIMEOUT = 5000;
 const FORCE_PAGES_LOADED_TIMEOUT = 10000;
 const WHEEL_ZOOM_DISABLED_TIMEOUT = 1000;
@@ -182,6 +179,7 @@ const PDFViewerApplication = {
   isViewerEmbedded: window.parent !== window,
   url: "",
   baseUrl: "",
+  _downloadUrl: "",
   externalServices: DefaultExternalServices,
   _boundEvents: Object.create(null),
   documentInfo: null,
@@ -224,6 +222,10 @@ const PDFViewerApplication = {
       return;
     }
 
+    if (_app_options.AppOptions._hasUserOptions()) {
+      console.warn("_readPreferences: The Preferences may override manually set AppOptions; " + 'please use the "disablePreferences"-option in order to prevent that.');
+    }
+
     try {
       _app_options.AppOptions.setAll(await this.preferences.getAll());
     } catch (reason) {
@@ -233,48 +235,48 @@ const PDFViewerApplication = {
 
   async _parseHashParameters() {
     if (!_app_options.AppOptions.get("pdfBugEnabled")) {
-      return undefined;
+      return;
     }
 
     const hash = document.location.hash.substring(1);
 
     if (!hash) {
-      return undefined;
+      return;
     }
 
-    const hashParams = (0, _ui_utils.parseQueryString)(hash),
+    const params = (0, _ui_utils.parseQueryString)(hash),
           waitOn = [];
 
-    if ("disableworker" in hashParams && hashParams.disableworker === "true") {
+    if (params.get("disableworker") === "true") {
       waitOn.push(loadFakeWorker());
     }
 
-    if ("disablerange" in hashParams) {
-      _app_options.AppOptions.set("disableRange", hashParams.disablerange === "true");
+    if (params.has("disablerange")) {
+      _app_options.AppOptions.set("disableRange", params.get("disablerange") === "true");
     }
 
-    if ("disablestream" in hashParams) {
-      _app_options.AppOptions.set("disableStream", hashParams.disablestream === "true");
+    if (params.has("disablestream")) {
+      _app_options.AppOptions.set("disableStream", params.get("disablestream") === "true");
     }
 
-    if ("disableautofetch" in hashParams) {
-      _app_options.AppOptions.set("disableAutoFetch", hashParams.disableautofetch === "true");
+    if (params.has("disableautofetch")) {
+      _app_options.AppOptions.set("disableAutoFetch", params.get("disableautofetch") === "true");
     }
 
-    if ("disablefontface" in hashParams) {
-      _app_options.AppOptions.set("disableFontFace", hashParams.disablefontface === "true");
+    if (params.has("disablefontface")) {
+      _app_options.AppOptions.set("disableFontFace", params.get("disablefontface") === "true");
     }
 
-    if ("disablehistory" in hashParams) {
-      _app_options.AppOptions.set("disableHistory", hashParams.disablehistory === "true");
+    if (params.has("disablehistory")) {
+      _app_options.AppOptions.set("disableHistory", params.get("disablehistory") === "true");
     }
 
-    if ("verbosity" in hashParams) {
-      _app_options.AppOptions.set("verbosity", hashParams.verbosity | 0);
+    if (params.has("verbosity")) {
+      _app_options.AppOptions.set("verbosity", params.get("verbosity") | 0);
     }
 
-    if ("textlayer" in hashParams) {
-      switch (hashParams.textlayer) {
+    if (params.has("textlayer")) {
+      switch (params.get("textlayer")) {
         case "off":
           _app_options.AppOptions.set("textLayerMode", _ui_utils.TextLayerMode.DISABLE);
 
@@ -284,31 +286,33 @@ const PDFViewerApplication = {
         case "shadow":
         case "hover":
           const viewer = this.appConfig.viewerContainer;
-          viewer.classList.add("textLayer-" + hashParams.textlayer);
+          viewer.classList.add(`textLayer-${params.get("textlayer")}`);
           break;
       }
     }
 
-    if ("pdfbug" in hashParams) {
+    if (params.has("pdfbug")) {
       _app_options.AppOptions.set("pdfBug", true);
 
       _app_options.AppOptions.set("fontExtraProperties", true);
 
-      const enabled = hashParams.pdfbug.split(",");
-      waitOn.push(loadAndEnablePDFBug(enabled));
+      const enabled = params.get("pdfbug").split(",");
+      waitOn.push(initPDFBug(enabled));
     }
 
-    if ("locale" in hashParams) {
-      _app_options.AppOptions.set("locale", hashParams.locale);
+    if (params.has("locale")) {
+      _app_options.AppOptions.set("locale", params.get("locale"));
     }
 
     if (waitOn.length === 0) {
-      return undefined;
+      return;
     }
 
-    return Promise.all(waitOn).catch(reason => {
+    try {
+      await Promise.all(waitOn);
+    } catch (reason) {
       console.error(`_parseHashParameters: "${reason.message}".`);
-    });
+    }
   },
 
   async _initializeL10n() {
@@ -358,10 +362,20 @@ const PDFViewerApplication = {
   },
 
   async _initializeViewerComponents() {
-    const appConfig = this.appConfig;
-    const eventBus = appConfig.eventBus || new _ui_utils.EventBus({
-      isInAutomation: this.externalServices.isInAutomation
-    });
+    const {
+      appConfig,
+      externalServices
+    } = this;
+    let eventBus;
+
+    if (appConfig.eventBus) {
+      eventBus = appConfig.eventBus;
+    } else if (externalServices.isInAutomation) {
+      eventBus = new _ui_utils.AutomationEventBus();
+    } else {
+      eventBus = new _ui_utils.EventBus();
+    }
+
     this.eventBus = eventBus;
     this.overlayManager = new _overlay_manager.OverlayManager();
     const pdfRenderingQueue = new _pdf_rendering_queue.PDFRenderingQueue();
@@ -374,7 +388,7 @@ const PDFViewerApplication = {
       ignoreDestinationZoom: _app_options.AppOptions.get("ignoreDestinationZoom")
     });
     this.pdfLinkService = pdfLinkService;
-    const downloadManager = this.externalServices.createDownloadManager();
+    const downloadManager = externalServices.createDownloadManager();
     this.downloadManager = downloadManager;
     const findController = new _pdf_find_controller.PDFFindController({
       linkService: pdfLinkService,
@@ -384,7 +398,7 @@ const PDFViewerApplication = {
     const pdfScriptingManager = new _pdf_scripting_manager.PDFScriptingManager({
       eventBus,
       sandboxBundleSrc: _app_options.AppOptions.get("sandboxBundleSrc"),
-      scriptingFactory: this.externalServices,
+      scriptingFactory: externalServices,
       docPropertiesLookup: this._scriptingDocProperties.bind(this)
     });
     this.pdfScriptingManager = pdfScriptingManager;
@@ -398,16 +412,15 @@ const PDFViewerApplication = {
       linkService: pdfLinkService,
       downloadManager,
       findController,
-      scriptingManager: pdfScriptingManager,
+      scriptingManager: _app_options.AppOptions.get("enableScripting") && pdfScriptingManager,
       renderer: _app_options.AppOptions.get("renderer"),
       l10n: this.l10n,
       textLayerMode: _app_options.AppOptions.get("textLayerMode"),
+      annotationMode: _app_options.AppOptions.get("annotationMode"),
       imageResourcesPath: _app_options.AppOptions.get("imageResourcesPath"),
-      renderInteractiveForms: _app_options.AppOptions.get("renderInteractiveForms"),
       enablePrintAutoRotate: _app_options.AppOptions.get("enablePrintAutoRotate"),
       useOnlyCssZoom: _app_options.AppOptions.get("useOnlyCssZoom"),
-      maxCanvasPixels: _app_options.AppOptions.get("maxCanvasPixels"),
-      enableScripting: _app_options.AppOptions.get("enableScripting")
+      maxCanvasPixels: _app_options.AppOptions.get("maxCanvasPixels")
     });
     pdfRenderingQueue.setViewer(this.pdfViewer);
     pdfLinkService.setViewer(this.pdfViewer);
@@ -420,11 +433,14 @@ const PDFViewerApplication = {
       l10n: this.l10n
     });
     pdfRenderingQueue.setThumbnailViewer(this.pdfThumbnailViewer);
-    this.pdfHistory = new _pdf_history.PDFHistory({
-      linkService: pdfLinkService,
-      eventBus
-    });
-    pdfLinkService.setHistory(this.pdfHistory);
+
+    if (!this.isViewerEmbedded && !_app_options.AppOptions.get("disableHistory")) {
+      this.pdfHistory = new _pdf_history.PDFHistory({
+        linkService: pdfLinkService,
+        eventBus
+      });
+      pdfLinkService.setHistory(this.pdfHistory);
+    }
 
     if (!this.supportsIntegratedFind) {
       this.findBar = new _pdf_find_bar.PDFFindBar(appConfig.findBar, eventBus, this.l10n);
@@ -486,36 +502,20 @@ const PDFViewerApplication = {
     return this._initializedCapability.promise;
   },
 
-  zoomIn(ticks) {
+  zoomIn(steps) {
     if (this.pdfViewer.isInPresentationMode) {
       return;
     }
 
-    let newScale = this.pdfViewer.currentScale;
-
-    do {
-      newScale = (newScale * DEFAULT_SCALE_DELTA).toFixed(2);
-      newScale = Math.ceil(newScale * 10) / 10;
-      newScale = Math.min(_ui_utils.MAX_SCALE, newScale);
-    } while (--ticks > 0 && newScale < _ui_utils.MAX_SCALE);
-
-    this.pdfViewer.currentScaleValue = newScale;
+    this.pdfViewer.increaseScale(steps);
   },
 
-  zoomOut(ticks) {
+  zoomOut(steps) {
     if (this.pdfViewer.isInPresentationMode) {
       return;
     }
 
-    let newScale = this.pdfViewer.currentScale;
-
-    do {
-      newScale = (newScale / DEFAULT_SCALE_DELTA).toFixed(2);
-      newScale = Math.floor(newScale * 10) / 10;
-      newScale = Math.max(_ui_utils.MIN_SCALE, newScale);
-    } while (--ticks > 0 && newScale > _ui_utils.MIN_SCALE);
-
-    this.pdfViewer.currentScaleValue = newScale;
+    this.pdfViewer.decreaseScale(steps);
   },
 
   zoomReset() {
@@ -543,14 +543,7 @@ const PDFViewerApplication = {
   },
 
   get supportsFullscreen() {
-    const doc = document.documentElement;
-    let support = !!(doc.requestFullscreen || doc.mozRequestFullScreen || doc.webkitRequestFullScreen);
-
-    if (document.fullscreenEnabled === false || document.mozFullScreenEnabled === false || document.webkitFullscreenEnabled === false) {
-      support = false;
-    }
-
-    return (0, _pdf.shadow)(this, "supportsFullscreen", support);
+    return (0, _pdf.shadow)(this, "supportsFullscreen", document.fullscreenEnabled || document.mozFullScreenEnabled || document.webkitFullscreenEnabled);
   },
 
   get supportsIntegratedFind() {
@@ -574,9 +567,14 @@ const PDFViewerApplication = {
     throw new Error("Not implemented: initPassiveLoading");
   },
 
-  setTitleUsingUrl(url = "") {
+  setTitleUsingUrl(url = "", downloadUrl = null) {
     this.url = url;
     this.baseUrl = url.split("#")[0];
+
+    if (downloadUrl) {
+      this._downloadUrl = downloadUrl === url ? this.baseUrl : downloadUrl.split("#")[0];
+    }
+
     let title = (0, _pdf.getPdfFilenameFromUrl)(url, "");
 
     if (!title) {
@@ -602,6 +600,15 @@ const PDFViewerApplication = {
     return this._contentDispositionFilename || (0, _pdf.getPdfFilenameFromUrl)(this.url);
   },
 
+  _hideViewBookmark() {
+    const {
+      toolbar,
+      secondaryToolbar
+    } = this.appConfig;
+    toolbar.viewBookmark.hidden = true;
+    secondaryToolbar.viewBookmarkButton.hidden = true;
+  },
+
   _cancelIdleCallbacks() {
     if (!this._idleCallbacks.size) {
       return;
@@ -617,6 +624,8 @@ const PDFViewerApplication = {
   async close() {
     this._unblockDocumentLoadEvent();
 
+    this._hideViewBookmark();
+
     const {
       container
     } = this.appConfig.errorWrapper;
@@ -654,6 +663,7 @@ const PDFViewerApplication = {
     this.downloadComplete = false;
     this.url = "";
     this.baseUrl = "";
+    this._downloadUrl = "";
     this.documentInfo = null;
     this.metadata = null;
     this._contentDispositionFilename = null;
@@ -667,15 +677,8 @@ const PDFViewerApplication = {
     this.pdfOutlineViewer.reset();
     this.pdfAttachmentViewer.reset();
     this.pdfLayerViewer.reset();
-
-    if (this.pdfHistory) {
-      this.pdfHistory.reset();
-    }
-
-    if (this.findBar) {
-      this.findBar.reset();
-    }
-
+    this.pdfHistory?.reset();
+    this.findBar?.reset();
     this.toolbar.reset();
     this.secondaryToolbar.reset();
 
@@ -700,12 +703,12 @@ const PDFViewerApplication = {
     const parameters = Object.create(null);
 
     if (typeof file === "string") {
-      this.setTitleUsingUrl(file);
+      this.setTitleUsingUrl(file, file);
       parameters.url = file;
     } else if (file && "byteLength" in file) {
       parameters.data = file;
     } else if (file.url && file.originalUrl) {
-      this.setTitleUsingUrl(file.originalUrl);
+      this.setTitleUsingUrl(file.originalUrl, file.url);
       parameters.url = file.url;
     }
 
@@ -780,7 +783,7 @@ const PDFViewerApplication = {
   async download({
     sourceEventType = "download"
   } = {}) {
-    const url = this.baseUrl,
+    const url = this._downloadUrl,
           filename = this._docFilename;
 
     try {
@@ -805,7 +808,7 @@ const PDFViewerApplication = {
 
     this._saveInProgress = true;
     await this.pdfScriptingManager.dispatchWillSave();
-    const url = this.baseUrl,
+    const url = this._downloadUrl,
           filename = this._docFilename;
 
     try {
@@ -817,6 +820,7 @@ const PDFViewerApplication = {
       });
       await this.downloadManager.download(blob, url, filename, sourceEventType);
     } catch (reason) {
+      console.error(`Error when saving the document: ${reason.message}`);
       await this.download({
         sourceEventType
       });
@@ -1249,8 +1253,8 @@ const PDFViewerApplication = {
 
     this.documentInfo = info;
     this.metadata = metadata;
-    this._contentDispositionFilename ?? (this._contentDispositionFilename = contentDispositionFilename);
-    this._contentLength ?? (this._contentLength = contentLength);
+    this._contentDispositionFilename ??= contentDispositionFilename;
+    this._contentLength ??= contentLength;
     console.log(`PDF ${pdfDocument.fingerprints[0]} [${info.PDFFormatVersion} ` + `${(info.Producer || "-").trim()} / ${(info.Creator || "-").trim()}] ` + `(PDF.js: ${_pdf.version || "-"})`);
     let pdfTitle = info?.Title;
     const metadataTitle = metadata?.get("dc:title");
@@ -1268,9 +1272,14 @@ const PDFViewerApplication = {
     }
 
     if (info.IsXFAPresent && !info.IsAcroFormPresent && !pdfDocument.isPureXfa) {
-      console.warn("Warning: XFA support is not enabled");
+      if (pdfDocument.loadingParams.enableXfa) {
+        console.warn("Warning: XFA Foreground documents are not supported");
+      } else {
+        console.warn("Warning: XFA support is not enabled");
+      }
+
       this.fallback(_pdf.UNSUPPORTED_FEATURES.forms);
-    } else if ((info.IsAcroFormPresent || info.IsXFAPresent) && !this.pdfViewer.renderInteractiveForms) {
+    } else if ((info.IsAcroFormPresent || info.IsXFAPresent) && !this.pdfViewer.renderForms) {
       console.warn("Warning: Interactive form support is not enabled");
       this.fallback(_pdf.UNSUPPORTED_FEATURES.forms);
     }
@@ -1363,7 +1372,7 @@ const PDFViewerApplication = {
     viewOnLoad,
     initialDest = null
   }) {
-    if (this.isViewerEmbedded || _app_options.AppOptions.get("disableHistory")) {
+    if (!this.pdfHistory) {
       return;
     }
 
@@ -1525,10 +1534,7 @@ const PDFViewerApplication = {
     if (this.printService) {
       this.printService.destroy();
       this.printService = null;
-
-      if (this.pdfDocument) {
-        this.pdfDocument.annotationStorage.resetModified();
-      }
+      this.pdfDocument?.annotationStorage.resetModified();
     }
 
     this.forceRendering();
@@ -1539,11 +1545,7 @@ const PDFViewerApplication = {
   },
 
   requestPresentationMode() {
-    if (!this.pdfPresentationMode) {
-      return;
-    }
-
-    this.pdfPresentationMode.request();
+    this.pdfPresentationMode?.request();
   },
 
   triggerPrinting() {
@@ -1893,17 +1895,18 @@ async function loadFakeWorker() {
     _pdf.GlobalWorkerOptions.workerSrc = _app_options.AppOptions.get("workerSrc");
   }
 
-  return (0, _pdf.loadScript)(_pdf.PDFWorker.getWorkerSrc());
+  await (0, _pdf.loadScript)(_pdf.PDFWorker.workerSrc);
 }
 
-function loadAndEnablePDFBug(enabledTabs) {
-  const appConfig = PDFViewerApplication.appConfig;
-  return (0, _pdf.loadScript)(appConfig.debuggerScriptPath).then(function () {
-    PDFBug.enable(enabledTabs);
-    PDFBug.init({
-      OPS: _pdf.OPS
-    }, appConfig.mainContainer);
-  });
+async function initPDFBug(enabledTabs) {
+  const {
+    debuggerScriptPath,
+    mainContainer
+  } = PDFViewerApplication.appConfig;
+  await (0, _pdf.loadScript)(debuggerScriptPath);
+  PDFBug.init({
+    OPS: _pdf.OPS
+  }, mainContainer, enabledTabs);
 }
 
 function reportPageStatsPDFBug({
@@ -1928,7 +1931,7 @@ function webViewerInitialized() {
   let file;
   const queryString = document.location.search.substring(1);
   const params = (0, _ui_utils.parseQueryString)(queryString);
-  file = "file" in params ? params.file : _app_options.AppOptions.get("defaultUrl");
+  file = params.get("file") ?? _app_options.AppOptions.get("defaultUrl");
   validateFileURL(file);
   const fileInput = document.createElement("input");
   fileInput.id = appConfig.openFileInputName;
@@ -2016,6 +2019,8 @@ function webViewerInitialized() {
 function webViewerOpenFileViaURL(file) {
   if (file) {
     PDFViewerApplication.open(file);
+  } else {
+    PDFViewerApplication._hideViewBookmark();
   }
 }
 
@@ -2204,7 +2209,7 @@ function webViewerHashchange(evt) {
 
   if (!PDFViewerApplication.isInitialViewSet) {
     PDFViewerApplication.initialBookmark = hash;
-  } else if (!PDFViewerApplication.pdfHistory.popStateInProgress) {
+  } else if (!PDFViewerApplication.pdfHistory?.popStateInProgress) {
     PDFViewerApplication.pdfLinkService.setHash(hash);
   }
 }
@@ -2218,7 +2223,7 @@ let webViewerFileInputChange, webViewerOpenFile;
 
     const file = evt.fileInput.files[0];
 
-    if (!_viewer_compatibility.viewerCompatibilityParams.disableCreateObjectURL) {
+    if (!_app_options.compatibilityParams.disableCreateObjectURL) {
       let url = URL.createObjectURL(file);
 
       if (file.name) {
@@ -2240,12 +2245,6 @@ let webViewerFileInputChange, webViewerOpenFile;
 
       fileReader.readAsArrayBuffer(file);
     }
-
-    const appConfig = PDFViewerApplication.appConfig;
-    appConfig.toolbar.viewBookmark.hidden = true;
-    appConfig.secondaryToolbar.viewBookmarkButton.hidden = true;
-    appConfig.toolbar.download.hidden = true;
-    appConfig.secondaryToolbar.downloadButton.hidden = true;
   };
 
   webViewerOpenFile = function (evt) {

+ 31 - 8
lib/web/app_options.js

@@ -24,10 +24,29 @@
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
-exports.OptionKind = exports.AppOptions = void 0;
+exports.OptionKind = exports.compatibilityParams = exports.AppOptions = void 0;
+const compatibilityParams = Object.create(null);
+exports.compatibilityParams = compatibilityParams;
+{
+  const userAgent = typeof navigator !== "undefined" && navigator.userAgent || "";
+  const platform = typeof navigator !== "undefined" && navigator.platform || "";
+  const maxTouchPoints = typeof navigator !== "undefined" && navigator.maxTouchPoints || 1;
+  const isAndroid = /Android/.test(userAgent);
+  const isIOS = /\b(iPad|iPhone|iPod)(?=;)/.test(userAgent) || platform === "MacIntel" && maxTouchPoints > 1;
+  const isIOSChrome = /CriOS/.test(userAgent);
 
-var _viewer_compatibility = require("./viewer_compatibility.js");
+  (function checkOnBlobSupport() {
+    if (isIOSChrome) {
+      compatibilityParams.disableCreateObjectURL = true;
+    }
+  })();
 
+  (function checkCanvasSizeLimitation() {
+    if (isIOS || isAndroid) {
+      compatibilityParams.maxCanvasPixels = 5242880;
+    }
+  })();
+}
 const OptionKind = {
   VIEWER: 0x02,
   API: 0x04,
@@ -36,6 +55,10 @@ const OptionKind = {
 };
 exports.OptionKind = OptionKind;
 const defaultOptions = {
+  annotationMode: {
+    value: 2,
+    kind: OptionKind.VIEWER + OptionKind.PREFERENCE
+  },
   cursorToolOnLoad: {
     value: 0,
     kind: OptionKind.VIEWER + OptionKind.PREFERENCE
@@ -90,7 +113,7 @@ const defaultOptions = {
   },
   maxCanvasPixels: {
     value: 16777216,
-    compatibility: _viewer_compatibility.viewerCompatibilityParams.maxCanvasPixels,
+    compatibility: compatibilityParams.maxCanvasPixels,
     kind: OptionKind.VIEWER
   },
   pdfBugEnabled: {
@@ -105,10 +128,6 @@ const defaultOptions = {
     value: "canvas",
     kind: OptionKind.VIEWER
   },
-  renderInteractiveForms: {
-    value: true,
-    kind: OptionKind.VIEWER + OptionKind.PREFERENCE
-  },
   sidebarViewOnLoad: {
     value: -1,
     kind: OptionKind.VIEWER + OptionKind.PREFERENCE
@@ -166,7 +185,7 @@ const defaultOptions = {
     kind: OptionKind.API
   },
   enableXfa: {
-    value: false,
+    value: true,
     kind: OptionKind.API + OptionKind.PREFERENCE
   },
   fontExtraProperties: {
@@ -285,6 +304,10 @@ class AppOptions {
     delete userOptions[name];
   }
 
+  static _hasUserOptions() {
+    return Object.keys(userOptions).length > 0;
+  }
+
 }
 
 exports.AppOptions = AppOptions;

Някои файлове не бяха показани, защото твърде много файлове са промени