Kaynağa Gözat

PDF.js version 2.12.313

Brendan Dahl 3 yıl önce
ebeveyn
işleme
a7fc0d33a1
100 değiştirilmiş dosya ile 5190 ekleme ve 2655 silme
  1. 3 2
      README.md
  2. 1 1
      bower.json
  3. 408 181
      build/pdf.js
  4. 0 0
      build/pdf.js.map
  5. 0 0
      build/pdf.min.js
  6. 2 2
      build/pdf.sandbox.js
  7. 0 0
      build/pdf.sandbox.js.map
  8. 0 0
      build/pdf.sandbox.min.js
  9. 293 93
      build/pdf.worker.js
  10. 0 0
      build/pdf.worker.js.map
  11. 0 0
      build/pdf.worker.min.js
  12. 148 19
      image_decoders/pdf.image_decoders.js
  13. 0 0
      image_decoders/pdf.image_decoders.js.map
  14. 0 0
      image_decoders/pdf.image_decoders.min.js
  15. 416 191
      legacy/build/pdf.js
  16. 0 0
      legacy/build/pdf.js.map
  17. 0 0
      legacy/build/pdf.min.js
  18. 2 2
      legacy/build/pdf.sandbox.js
  19. 0 0
      legacy/build/pdf.sandbox.js.map
  20. 0 0
      legacy/build/pdf.sandbox.min.js
  21. 359 176
      legacy/build/pdf.worker.js
  22. 0 0
      legacy/build/pdf.worker.js.map
  23. 0 0
      legacy/build/pdf.worker.min.js
  24. 412 187
      legacy/image_decoders/pdf.image_decoders.js
  25. 0 0
      legacy/image_decoders/pdf.image_decoders.js.map
  26. 0 0
      legacy/image_decoders/pdf.image_decoders.min.js
  27. 59 20
      legacy/web/pdf_viewer.css
  28. 340 480
      legacy/web/pdf_viewer.js
  29. 0 0
      legacy/web/pdf_viewer.js.map
  30. 53 20
      lib/core/annotation.js
  31. 1 1
      lib/core/bidi.js
  32. 193 40
      lib/core/catalog.js
  33. 4 0
      lib/core/ccitt.js
  34. 11 3
      lib/core/chunked_stream.js
  35. 8 1
      lib/core/cmap.js
  36. 8 8
      lib/core/colorspace.js
  37. 50 1
      lib/core/core_utils.js
  38. 3 1
      lib/core/crypto.js
  39. 141 58
      lib/core/document.js
  40. 1 1
      lib/core/encodings.js
  41. 144 151
      lib/core/evaluator.js
  42. 48 0
      lib/core/fonts.js
  43. 1 1
      lib/core/fonts_utils.js
  44. 1 1
      lib/core/function.js
  45. 10 10
      lib/core/image.js
  46. 2 2
      lib/core/jpeg_stream.js
  47. 1 1
      lib/core/jpg.js
  48. 77 68
      lib/core/parser.js
  49. 3 3
      lib/core/pattern.js
  50. 2 1
      lib/core/pdf_manager.js
  51. 1 1
      lib/core/predictor_stream.js
  52. 3 1
      lib/core/primitives.js
  53. 2 1
      lib/core/standard_fonts.js
  54. 6 0
      lib/core/struct_tree.js
  55. 1 1
      lib/core/unicode.js
  56. 6 23
      lib/core/worker.js
  57. 5 0
      lib/core/xfa/bind.js
  58. 2 2
      lib/core/xfa/builder.js
  59. 78 6
      lib/core/xfa/factory.js
  60. 1 1
      lib/core/xfa/fonts.js
  61. 1 1
      lib/core/xfa/formcalc_lexer.js
  62. 7 2
      lib/core/xfa/html_utils.js
  63. 1 1
      lib/core/xfa/layout.js
  64. 5 4
      lib/core/xfa/parser.js
  65. 24 10
      lib/core/xfa/template.js
  66. 1 1
      lib/core/xfa/utils.js
  67. 8 4
      lib/core/xfa/xfa_object.js
  68. 37 8
      lib/core/xfa/xhtml.js
  69. 91 40
      lib/core/xref.js
  70. 147 36
      lib/display/annotation_layer.js
  71. 136 128
      lib/display/api.js
  72. 1 1
      lib/display/base_factory.js
  73. 410 159
      lib/display/canvas.js
  74. 1 1
      lib/display/display_utils.js
  75. 1 2
      lib/display/font_loader.js
  76. 9 6
      lib/display/metadata.js
  77. 1 1
      lib/display/node_utils.js
  78. 39 36
      lib/display/pattern_helper.js
  79. 4 0
      lib/display/svg.js
  80. 1 1
      lib/display/text_layer.js
  81. 14 5
      lib/display/xfa_layer.js
  82. 70 70
      lib/pdf.js
  83. 2 2
      lib/pdf.sandbox.js
  84. 2 2
      lib/pdf.worker.js
  85. 4 15
      lib/shared/message_handler.js
  86. 78 4
      lib/shared/util.js
  87. 18 18
      lib/test/unit/annotation_spec.js
  88. 328 17
      lib/test/unit/api_spec.js
  89. 108 0
      lib/test/unit/base_viewer_spec.js
  90. 18 0
      lib/test/unit/bidi_spec.js
  91. 259 0
      lib/test/unit/event_utils_spec.js
  92. 1 1
      lib/test/unit/jasmine-boot.js
  93. 25 36
      lib/test/unit/pdf_find_controller_spec.js
  94. 1 1
      lib/test/unit/primitives_spec.js
  95. 2 0
      lib/test/unit/struct_tree_spec.js
  96. 6 5
      lib/test/unit/test_utils.js
  97. 9 271
      lib/test/unit/ui_utils_spec.js
  98. 5 0
      lib/test/unit/util_spec.js
  99. 2 2
      lib/test/unit/xfa_parser_spec.js
  100. 3 2
      lib/test/unit/xfa_serialize_data_spec.js

+ 3 - 2
README.md

@@ -8,7 +8,8 @@ This is a pre-built version of the PDF.js source code. It is automatically
 generated by the build scripts.
 
 For usage with older browsers or environments, without support for modern
-features such as e.g. `async`/`await`, `ReadableStream`, optional chaining, and
-nullish coalescing; please see the `legacy` folder.
+features such as e.g. `async`/`await`, `ReadableStream`, optional chaining,
+nullish coalescing, and private `class` fields/methods; please see the `legacy`
+folder.
 
 See https://github.com/mozilla/pdf.js for learning and contributing.

+ 1 - 1
bower.json

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

Dosya farkı çok büyük olduğundan ihmal edildi
+ 408 - 181
build/pdf.js


Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
build/pdf.js.map


Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
build/pdf.min.js


Dosya farkı çok büyük olduğundan ihmal edildi
+ 2 - 2
build/pdf.sandbox.js


Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
build/pdf.sandbox.js.map


Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
build/pdf.sandbox.min.js


Dosya farkı çok büyük olduğundan ihmal edildi
+ 293 - 93
build/pdf.worker.js


Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
build/pdf.worker.js.map


Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
build/pdf.worker.min.js


+ 148 - 19
image_decoders/pdf.image_decoders.js

@@ -42,6 +42,7 @@ return /******/ (() => { // webpackBootstrap
 Object.defineProperty(exports, "__esModule", ({
   value: true
 }));
+exports.VerbosityLevel = exports.Util = exports.UnknownErrorException = exports.UnexpectedResponseException = exports.UNSUPPORTED_FEATURES = 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;
 exports.arrayByteLength = arrayByteLength;
 exports.arraysToBytes = arraysToBytes;
 exports.assert = assert;
@@ -73,7 +74,6 @@ 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.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);
 
@@ -610,14 +610,19 @@ class AbortException extends BaseException {
 }
 
 exports.AbortException = AbortException;
-const NullCharactersRegExp = /\x00/g;
+const NullCharactersRegExp = /\x00+/g;
+const InvisibleCharactersRegExp = /[\x01-\x1F]/g;
 
-function removeNullCharacters(str) {
+function removeNullCharacters(str, replaceInvisible = false) {
   if (typeof str !== "string") {
     warn("The argument for removeNullCharacters must be a string.");
     return str;
   }
 
+  if (replaceInvisible) {
+    str = str.replace(InvisibleCharactersRegExp, " ");
+  }
+
   return str.replace(NullCharactersRegExp, "");
 }
 
@@ -844,6 +849,75 @@ class Util {
     return result;
   }
 
+  static bezierBoundingBox(x0, y0, x1, y1, x2, y2, x3, y3) {
+    const tvalues = [],
+          bounds = [[], []];
+    let a, b, c, t, t1, t2, b2ac, sqrtb2ac;
+
+    for (let i = 0; i < 2; ++i) {
+      if (i === 0) {
+        b = 6 * x0 - 12 * x1 + 6 * x2;
+        a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3;
+        c = 3 * x1 - 3 * x0;
+      } else {
+        b = 6 * y0 - 12 * y1 + 6 * y2;
+        a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3;
+        c = 3 * y1 - 3 * y0;
+      }
+
+      if (Math.abs(a) < 1e-12) {
+        if (Math.abs(b) < 1e-12) {
+          continue;
+        }
+
+        t = -c / b;
+
+        if (0 < t && t < 1) {
+          tvalues.push(t);
+        }
+
+        continue;
+      }
+
+      b2ac = b * b - 4 * c * a;
+      sqrtb2ac = Math.sqrt(b2ac);
+
+      if (b2ac < 0) {
+        continue;
+      }
+
+      t1 = (-b + sqrtb2ac) / (2 * a);
+
+      if (0 < t1 && t1 < 1) {
+        tvalues.push(t1);
+      }
+
+      t2 = (-b - sqrtb2ac) / (2 * a);
+
+      if (0 < t2 && t2 < 1) {
+        tvalues.push(t2);
+      }
+    }
+
+    let j = tvalues.length,
+        mt;
+    const jlen = j;
+
+    while (j--) {
+      t = tvalues[j];
+      mt = 1 - t;
+      bounds[0][j] = mt * mt * mt * x0 + 3 * mt * mt * t * x1 + 3 * mt * t * t * x2 + t * t * t * x3;
+      bounds[1][j] = mt * mt * mt * y0 + 3 * mt * mt * t * y1 + 3 * mt * t * t * y2 + t * t * t * y3;
+    }
+
+    bounds[0][jlen] = x0;
+    bounds[1][jlen] = y0;
+    bounds[0][jlen + 1] = x3;
+    bounds[1][jlen + 1] = y3;
+    bounds[0].length = bounds[1].length = jlen + 2;
+    return [Math.min(...bounds[0]), Math.min(...bounds[1]), Math.max(...bounds[0]), Math.max(...bounds[1])];
+  }
+
 }
 
 exports.Util = Util;
@@ -965,7 +1039,7 @@ function createPromiseCapability() {
 }
 
 function createObjectURL(data, contentType = "", forceDataSchema = false) {
-  if (URL.createObjectURL && !forceDataSchema) {
+  if (URL.createObjectURL && typeof Blob !== "undefined" && !forceDataSchema) {
     return URL.createObjectURL(new Blob([data], {
       type: contentType
     }));
@@ -3219,6 +3293,7 @@ exports.Jbig2Image = Jbig2Image;
 Object.defineProperty(exports, "__esModule", ({
   value: true
 }));
+exports.XRefParseException = exports.XRefEntryException = exports.ParserEOFException = exports.MissingDataException = exports.DocStats = void 0;
 exports.collectActions = collectActions;
 exports.encodeToXmlString = encodeToXmlString;
 exports.escapePDFName = escapePDFName;
@@ -3234,7 +3309,6 @@ exports.readUint32 = readUint32;
 exports.recoverJsURL = recoverJsURL;
 exports.toRomanNumerals = toRomanNumerals;
 exports.validateCSSFont = validateCSSFont;
-exports.XRefParseException = exports.XRefEntryException = exports.ParserEOFException = exports.MissingDataException = void 0;
 
 var _util = __w_pdfjs_require__(1);
 
@@ -3310,6 +3384,55 @@ class XRefParseException extends _util.BaseException {
 
 exports.XRefParseException = XRefParseException;
 
+class DocStats {
+  constructor(handler) {
+    this._handler = handler;
+    this._streamTypes = new Set();
+    this._fontTypes = new Set();
+  }
+
+  _send() {
+    const streamTypes = Object.create(null),
+          fontTypes = Object.create(null);
+
+    for (const type of this._streamTypes) {
+      streamTypes[type] = true;
+    }
+
+    for (const type of this._fontTypes) {
+      fontTypes[type] = true;
+    }
+
+    this._handler.send("DocStats", {
+      streamTypes,
+      fontTypes
+    });
+  }
+
+  addStreamType(type) {
+    if (this._streamTypes.has(type)) {
+      return;
+    }
+
+    this._streamTypes.add(type);
+
+    this._send();
+  }
+
+  addFontType(type) {
+    if (this._fontTypes.has(type)) {
+      return;
+    }
+
+    this._fontTypes.add(type);
+
+    this._send();
+  }
+
+}
+
+exports.DocStats = DocStats;
+
 function getInheritableProperty({
   dict,
   key,
@@ -3655,6 +3778,7 @@ function recoverJsURL(str) {
 Object.defineProperty(exports, "__esModule", ({
   value: true
 }));
+exports.RefSetCache = exports.RefSet = exports.Ref = exports.Name = exports.EOF = exports.Dict = exports.Cmd = exports.CIRCULAR_REF = void 0;
 exports.clearPrimitiveCaches = clearPrimitiveCaches;
 exports.isCmd = isCmd;
 exports.isDict = isDict;
@@ -3662,12 +3786,13 @@ exports.isName = isName;
 exports.isRef = isRef;
 exports.isRefsEqual = isRefsEqual;
 exports.isStream = isStream;
-exports.RefSetCache = exports.RefSet = exports.Ref = exports.Name = exports.EOF = exports.Dict = exports.Cmd = void 0;
 
 var _util = __w_pdfjs_require__(1);
 
 var _base_stream = __w_pdfjs_require__(7);
 
+const CIRCULAR_REF = Symbol("CIRCULAR_REF");
+exports.CIRCULAR_REF = CIRCULAR_REF;
 const EOF = Symbol("EOF");
 exports.EOF = EOF;
 
@@ -4927,6 +5052,10 @@ class CCITTFaxDecoder {
       c = 0;
 
       do {
+        if (typeof this.outputBits !== "number") {
+          throw new _util.FormatError('Invalid /CCITTFaxDecode data, "outputBits" must be a number.');
+        }
+
         if (this.outputBits > bits) {
           c <<= bits;
 
@@ -6387,7 +6516,7 @@ class JpegImage {
       y = data[i + 2];
       k = data[i + 3];
       data[offset++] = 255 + c * (-0.00006747147073602441 * c + 0.0008379262121013727 * m + 0.0002894718188643294 * y + 0.003264231057537806 * k - 1.1185611867203937) + m * (0.000026374107616089405 * m - 0.00008626949158638572 * y - 0.0002748769067499491 * k - 0.02155688794978967) + y * (-0.00003878099212869363 * y - 0.0003267808279485286 * k + 0.0686742238595345) - k * (0.0003361971776183937 * k + 0.7430659151342254);
-      data[offset++] = 255 + c * (0.00013596372813588848 * c + 0.000924537132573585 * m + 0.00010567359618683593 * y + 0.0004791864687436512 * k - 0.3109689587515875) + m * (-0.00023545346108370344 * m + 0.0002702845253534714 * y + 0.0020200308977307156 * k - 0.7488052167015494) + y * (0.00006834815998235662 * y + 0.00015168452363460973 * k - 0.09751927774728933) - k * (0.00031891311758832814 * k + 0.7364883807733168);
+      data[offset++] = 255 + c * (0.00013596372813588848 * c + 0.000924537132573585 * m + 0.00010567359618683593 * y + 0.0004791864687436512 * k - 0.3109689587515875) + m * (-0.00023545346108370344 * m + 0.0002702845253534714 * y + 0.0020200308977307156 * k - 0.7488052167015494) + y * (0.00006834815998235662 * y + 0.00015168452363460973 * k - 0.09751927774728933) - k * (0.0003189131175883281 * k + 0.7364883807733168);
       data[offset++] = 255 + c * (0.000013598650411385307 * c + 0.00012423956175490851 * m + 0.0004751985097583589 * y - 0.0000036729317476630422 * k - 0.05562186980264034) + m * (0.00016141380598724676 * m + 0.0009692239130725186 * y + 0.0007782692450036253 * k - 0.44015232367526463) + y * (5.068882914068769e-7 * y + 0.0017778369011375071 * k - 0.7591454649749609) - k * (0.0003435319965105553 * k + 0.7063770186160144);
     }
 
@@ -8818,34 +8947,34 @@ var exports = __webpack_exports__;
 Object.defineProperty(exports, "__esModule", ({
   value: true
 }));
-Object.defineProperty(exports, "getVerbosityLevel", ({
+Object.defineProperty(exports, "Jbig2Image", ({
   enumerable: true,
   get: function () {
-    return _util.getVerbosityLevel;
+    return _jbig.Jbig2Image;
   }
 }));
-Object.defineProperty(exports, "setVerbosityLevel", ({
+Object.defineProperty(exports, "JpegImage", ({
   enumerable: true,
   get: function () {
-    return _util.setVerbosityLevel;
+    return _jpg.JpegImage;
   }
 }));
-Object.defineProperty(exports, "Jbig2Image", ({
+Object.defineProperty(exports, "JpxImage", ({
   enumerable: true,
   get: function () {
-    return _jbig.Jbig2Image;
+    return _jpx.JpxImage;
   }
 }));
-Object.defineProperty(exports, "JpegImage", ({
+Object.defineProperty(exports, "getVerbosityLevel", ({
   enumerable: true,
   get: function () {
-    return _jpg.JpegImage;
+    return _util.getVerbosityLevel;
   }
 }));
-Object.defineProperty(exports, "JpxImage", ({
+Object.defineProperty(exports, "setVerbosityLevel", ({
   enumerable: true,
   get: function () {
-    return _jpx.JpxImage;
+    return _util.setVerbosityLevel;
   }
 }));
 
@@ -8857,8 +8986,8 @@ var _jpg = __w_pdfjs_require__(10);
 
 var _jpx = __w_pdfjs_require__(11);
 
-const pdfjsVersion = '2.11.338';
-const pdfjsBuild = 'dedff3c98';
+const pdfjsVersion = '2.12.313';
+const pdfjsBuild = 'a2ae56f39';
 })();
 
 /******/ 	return __webpack_exports__;

Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
image_decoders/pdf.image_decoders.js.map


Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
image_decoders/pdf.image_decoders.min.js


Dosya farkı çok büyük olduğundan ihmal edildi
+ 416 - 191
legacy/build/pdf.js


Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
legacy/build/pdf.js.map


Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
legacy/build/pdf.min.js


Dosya farkı çok büyük olduğundan ihmal edildi
+ 2 - 2
legacy/build/pdf.sandbox.js


Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
legacy/build/pdf.sandbox.js.map


Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
legacy/build/pdf.sandbox.min.js


Dosya farkı çok büyük olduğundan ihmal edildi
+ 359 - 176
legacy/build/pdf.worker.js


Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
legacy/build/pdf.worker.js.map


Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
legacy/build/pdf.worker.min.js


Dosya farkı çok büyük olduğundan ihmal edildi
+ 412 - 187
legacy/image_decoders/pdf.image_decoders.js


Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
legacy/image_decoders/pdf.image_decoders.js.map


Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
legacy/image_decoders/pdf.image_decoders.min.js


+ 59 - 20
legacy/web/pdf_viewer.css

@@ -23,6 +23,9 @@
   overflow: hidden;
   opacity: 0.2;
   line-height: 1;
+  -webkit-text-size-adjust: none;
+     -moz-text-size-adjust: none;
+          text-size-adjust: none;
 }
 
 .textLayer span,
@@ -34,6 +37,13 @@
   transform-origin: 0% 0%;
 }
 
+/* Only necessary in Google Chrome, see issue 14205, and most unfortunately
+ * the problem doesn't show up in "text" reference tests. */
+.textLayer span.markedContent {
+  top: 0;
+  height: 0;
+}
+
 .textLayer .highlight {
   margin: -1px;
   padding: 1px;
@@ -115,6 +125,13 @@
   height: 100%;
 }
 
+.annotationLayer .buttonWidgetAnnotation.pushButton > canvas {
+  position: relative;
+  top: 0;
+  left: 0;
+  z-index: -1;
+}
+
 .annotationLayer .linkAnnotation > a:hover,
 .annotationLayer .buttonWidgetAnnotation.pushButton > a:hover {
   opacity: 0.2;
@@ -230,6 +247,16 @@
   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;
@@ -267,17 +294,21 @@
   display: inline-block;
 }
 
-.annotationLayer .popup span {
+.annotationLayer .popupDate {
   display: inline-block;
   margin-left: 5px;
 }
 
-.annotationLayer .popup p {
+.annotationLayer .popupContent {
   border-top: 1px solid rgba(51, 51, 51, 1);
   margin-top: 2px;
   padding-top: 2px;
 }
 
+.annotationLayer .richText > * {
+  white-space: pre-wrap;
+}
+
 .annotationLayer .highlightAnnotation,
 .annotationLayer .underlineAnnotation,
 .annotationLayer .squigglyAnnotation,
@@ -510,6 +541,9 @@
 .xfaLink {
   width: 100%;
   height: 100%;
+  position: absolute;
+  top: 0;
+  left: 0;
 }
 
 .xfaCheckbox,
@@ -620,11 +654,12 @@
 }
 
 :root {
-  --pdfViewer-padding-bottom: none;
+  --pdfViewer-padding-bottom: 0;
   --page-margin: 1px auto -8px;
   --page-border: 9px solid transparent;
   --spreadHorizontalWrapped-margin-LR: -3.5px;
   --zoom-factor: 1;
+  --viewport-scale-factor: 1;
 }
 
 @media screen and (forced-colors: active) {
@@ -658,6 +693,12 @@
   background-color: rgba(255, 255, 255, 1);
 }
 
+.pdfViewer .dummyPage {
+  position: relative;
+  width: 0;
+  /* The height is set via JS, see `BaseViewer.#ensurePageViewVisible`. */
+}
+
 .pdfViewer.removePageBorders .page {
   margin: 0 auto 10px;
   border: none;
@@ -693,6 +734,7 @@
 }
 
 .spread .page,
+.spread .dummyPage,
 .pdfViewer.scrollHorizontal .page,
 .pdfViewer.scrollWrapped .page,
 .pdfViewer.scrollHorizontal .spread,
@@ -733,29 +775,26 @@
   bottom: 0;
   background: url("images/loading-icon.gif") center no-repeat;
 }
-
-.pdfPresentationMode .pdfViewer {
-  margin-left: 0;
-  margin-right: 0;
+.pdfViewer .page .loadingIcon.notVisible {
+  background: none;
 }
 
-.pdfPresentationMode .pdfViewer .page,
-.pdfPresentationMode .pdfViewer .spread {
-  display: block;
+.pdfViewer.enablePermissions .textLayer span {
+  -webkit-user-select: none !important;
+     -moz-user-select: none !important;
+          user-select: none !important;
+  cursor: not-allowed;
 }
 
-.pdfPresentationMode .pdfViewer .page,
-.pdfPresentationMode .pdfViewer.removePageBorders .page {
-  margin-left: auto;
-  margin-right: auto;
+.pdfPresentationMode .pdfViewer {
+  padding-bottom: 0;
 }
 
-.pdfPresentationMode:-webkit-full-screen .pdfViewer .page {
-  margin-bottom: 100%;
-  border: 0;
+.pdfPresentationMode .spread {
+  margin: 0;
 }
 
-.pdfPresentationMode:fullscreen .pdfViewer .page {
-  margin-bottom: 100%;
-  border: 0;
+.pdfPresentationMode .pdfViewer .page {
+  margin: 0 auto;
+  border: 2px solid transparent;
 }

Dosya farkı çok büyük olduğundan ihmal edildi
+ 340 - 480
legacy/web/pdf_viewer.js


Dosya farkı çok büyük olduğundan ihmal edildi
+ 0 - 0
legacy/web/pdf_viewer.js.map


+ 53 - 20
lib/core/annotation.js

@@ -24,8 +24,8 @@
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
-exports.getQuadPoints = getQuadPoints;
 exports.MarkupAnnotation = exports.AnnotationFactory = exports.AnnotationBorderStyle = exports.Annotation = void 0;
+exports.getQuadPoints = getQuadPoints;
 
 var _util = require("../shared/util.js");
 
@@ -51,6 +51,8 @@ var _stream = require("./stream.js");
 
 var _writer = require("./writer.js");
 
+var _factory = require("./xfa/factory.js");
+
 class AnnotationFactory {
   static create(xref, ref, pdfManager, idFactory, collectFields) {
     return Promise.all([pdfManager.ensureCatalog("acroForm"), collectFields ? this._getPageIndex(xref, ref, pdfManager) : -1]).then(([acroForm, pageIndex]) => pdfManager.ensure(this, "_create", [xref, ref, pdfManager, idFactory, acroForm, collectFields, pageIndex]));
@@ -314,7 +316,8 @@ class Annotation {
       id: params.id,
       modificationDate: this.modificationDate,
       rect: this.rectangle,
-      subtype: params.subtype
+      subtype: params.subtype,
+      hasOwnCanvas: false
     };
 
     if (params.collectFields) {
@@ -473,7 +476,7 @@ class Annotation {
         this.borderStyle.setWidth(array[2], this.rectangle);
 
         if (array.length === 4) {
-          this.borderStyle.setDashArray(array[3]);
+          this.borderStyle.setDashArray(array[3], true);
         }
       }
     } else {
@@ -509,8 +512,8 @@ class Annotation {
     this.appearance = normalAppearanceState.get(as.name);
   }
 
-  loadResources(keys) {
-    return this.appearance.dict.getAsync("Resources").then(resources => {
+  loadResources(keys, appearance) {
+    return appearance.dict.getAsync("Resources").then(resources => {
       if (!resources) {
         return undefined;
       }
@@ -522,21 +525,28 @@ class Annotation {
     });
   }
 
-  getOperatorList(evaluator, task, renderForms, annotationStorage) {
-    if (!this.appearance) {
-      return Promise.resolve(new _operator_list.OperatorList());
+  getOperatorList(evaluator, task, intent, renderForms, annotationStorage) {
+    const data = this.data;
+    let appearance = this.appearance;
+    const isUsingOwnCanvas = data.hasOwnCanvas && intent & _util.RenderingIntentFlag.DISPLAY;
+
+    if (!appearance) {
+      if (!isUsingOwnCanvas) {
+        return Promise.resolve(new _operator_list.OperatorList());
+      }
+
+      appearance = new _stream.StringStream("");
+      appearance.dict = new _primitives.Dict();
     }
 
-    const appearance = this.appearance;
-    const data = this.data;
     const appearanceDict = appearance.dict;
-    const resourcesPromise = this.loadResources(["ExtGState", "ColorSpace", "Pattern", "Shading", "XObject", "Font"]);
+    const resourcesPromise = this.loadResources(["ExtGState", "ColorSpace", "Pattern", "Shading", "XObject", "Font"], appearance);
     const bbox = appearanceDict.getArray("BBox") || [0, 0, 1, 1];
     const matrix = appearanceDict.getArray("Matrix") || [1, 0, 0, 1, 0, 0];
     const transform = getTransformMatrix(data.rect, bbox, matrix);
     return resourcesPromise.then(resources => {
       const opList = new _operator_list.OperatorList();
-      opList.addOp(_util.OPS.beginAnnotation, [data.id, data.rect, transform, matrix]);
+      opList.addOp(_util.OPS.beginAnnotation, [data.id, data.rect, transform, matrix, isUsingOwnCanvas]);
       return evaluator.getOperatorList({
         stream: appearance,
         task,
@@ -685,7 +695,7 @@ class AnnotationBorderStyle {
     }
   }
 
-  setDashArray(dashArray) {
+  setDashArray(dashArray, forceStyle = false) {
     if (Array.isArray(dashArray) && dashArray.length > 0) {
       let isValid = true;
       let allZeros = true;
@@ -703,6 +713,10 @@ class AnnotationBorderStyle {
 
       if (isValid && !allZeros) {
         this.dashArray = dashArray;
+
+        if (forceStyle) {
+          this.setStyle(_primitives.Name.get("D"));
+        }
       } else {
         this.width = 0;
       }
@@ -778,6 +792,10 @@ class MarkupAnnotation extends Annotation {
         this.data.color = null;
       }
     }
+
+    if (dict.has("RC")) {
+      this.data.richText = _factory.XFAFactory.getRichTextAsHtml(dict.get("RC"));
+    }
   }
 
   setCreationDate(creationDate) {
@@ -968,18 +986,18 @@ class WidgetAnnotation extends Annotation {
     return !!(this.data.fieldFlags & flag);
   }
 
-  getOperatorList(evaluator, task, renderForms, annotationStorage) {
+  getOperatorList(evaluator, task, intent, renderForms, annotationStorage) {
     if (renderForms && !(this instanceof SignatureWidgetAnnotation)) {
       return Promise.resolve(new _operator_list.OperatorList());
     }
 
     if (!this._hasText) {
-      return super.getOperatorList(evaluator, task, renderForms, annotationStorage);
+      return super.getOperatorList(evaluator, task, intent, renderForms, annotationStorage);
     }
 
     return this._getAppearance(evaluator, task, annotationStorage).then(content => {
       if (this.appearance && content === null) {
-        return super.getOperatorList(evaluator, task, renderForms, annotationStorage);
+        return super.getOperatorList(evaluator, task, intent, renderForms, annotationStorage);
       }
 
       const operatorList = new _operator_list.OperatorList();
@@ -1429,15 +1447,17 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
     } else if (this.data.radioButton) {
       this._processRadioButton(params);
     } else if (this.data.pushButton) {
+      this.data.hasOwnCanvas = true;
+
       this._processPushButton(params);
     } else {
       (0, _util.warn)("Invalid field flags for button widget annotation");
     }
   }
 
-  async getOperatorList(evaluator, task, renderForms, annotationStorage) {
+  async getOperatorList(evaluator, task, intent, renderForms, annotationStorage) {
     if (this.data.pushButton) {
-      return super.getOperatorList(evaluator, task, false, annotationStorage);
+      return super.getOperatorList(evaluator, task, intent, false, annotationStorage);
     }
 
     let value = null;
@@ -1449,7 +1469,7 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
 
     if (value === null) {
       if (this.appearance) {
-        return super.getOperatorList(evaluator, task, renderForms, annotationStorage);
+        return super.getOperatorList(evaluator, task, intent, renderForms, annotationStorage);
       }
 
       if (this.data.checkBox) {
@@ -1464,7 +1484,7 @@ class ButtonWidgetAnnotation extends WidgetAnnotation {
     if (appearance) {
       const savedAppearance = this.appearance;
       this.appearance = appearance;
-      const operatorList = super.getOperatorList(evaluator, task, renderForms, annotationStorage);
+      const operatorList = super.getOperatorList(evaluator, task, intent, renderForms, annotationStorage);
       this.appearance = savedAppearance;
       return operatorList;
     }
@@ -1997,6 +2017,10 @@ class PopupAnnotation extends Annotation {
     this.data.titleObj = this._title;
     this.setContents(parentItem.get("Contents"));
     this.data.contentsObj = this._contents;
+
+    if (parentItem.has("RC")) {
+      this.data.richText = _factory.XFAFactory.getRichTextAsHtml(parentItem.get("RC"));
+    }
   }
 
 }
@@ -2071,6 +2095,10 @@ class SquareAnnotation extends MarkupAnnotation {
 
       const fillAlpha = fillColor ? strokeAlpha : null;
 
+      if (this.borderStyle.width === 0 && !fillColor) {
+        return;
+      }
+
       this._setDefaultAppearance({
         xref: parameters.xref,
         extra: `${this.borderStyle.width} w`,
@@ -2116,6 +2144,11 @@ class CircleAnnotation extends MarkupAnnotation {
       }
 
       const fillAlpha = fillColor ? strokeAlpha : null;
+
+      if (this.borderStyle.width === 0 && !fillColor) {
+        return;
+      }
+
       const controlPointsDistance = 4 / 3 * Math.tan(Math.PI / (2 * 4));
 
       this._setDefaultAppearance({

+ 1 - 1
lib/core/bidi.js

@@ -128,7 +128,7 @@ function bidi(str, startLevel = -1, vertical = false) {
   }
 
   if (startLevel === -1) {
-    if (numBidi / strLength < 0.3) {
+    if (numBidi / strLength < 0.3 && strLength > 4) {
       isLTR = true;
       startLevel = 0;
     } else {

+ 193 - 40
lib/core/catalog.js

@@ -34,6 +34,8 @@ var _util = require("../shared/util.js");
 
 var _name_number_tree = require("./name_number_tree.js");
 
+var _base_stream = require("./base_stream.js");
+
 var _colorspace = require("./colorspace.js");
 
 var _file_spec = require("./file_spec.js");
@@ -58,10 +60,12 @@ class Catalog {
     this.xref = xref;
     this._catDict = xref.getCatalogObj();
 
-    if (!(0, _primitives.isDict)(this._catDict)) {
+    if (!(this._catDict instanceof _primitives.Dict)) {
       throw new _util.FormatError("Catalog object is not a dictionary.");
     }
 
+    this.toplevelPagesDict;
+    this._actualNumPages = null;
     this.fontCache = new _primitives.RefSetCache();
     this.builtInCMapCache = new Map();
     this.standardFontDataCache = new Map();
@@ -74,21 +78,19 @@ class Catalog {
   get version() {
     const version = this._catDict.get("Version");
 
-    if (!(0, _primitives.isName)(version)) {
-      return (0, _util.shadow)(this, "version", null);
-    }
+    return (0, _util.shadow)(this, "version", version instanceof _primitives.Name ? version.name : null);
+  }
 
-    return (0, _util.shadow)(this, "version", version.name);
+  get lang() {
+    const lang = this._catDict.get("Lang");
+
+    return (0, _util.shadow)(this, "lang", typeof lang === "string" ? (0, _util.stringToPDFString)(lang) : null);
   }
 
   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);
+    return (0, _util.shadow)(this, "needsRendering", typeof needsRendering === "boolean" ? needsRendering : false);
   }
 
   get collection() {
@@ -140,33 +142,34 @@ class Catalog {
   get metadata() {
     const streamRef = this._catDict.getRaw("Metadata");
 
-    if (!(0, _primitives.isRef)(streamRef)) {
+    if (!(streamRef instanceof _primitives.Ref)) {
       return (0, _util.shadow)(this, "metadata", null);
     }
 
-    const suppressEncryption = !(this.xref.encrypt && this.xref.encrypt.encryptMetadata);
-    const stream = this.xref.fetch(streamRef, suppressEncryption);
     let metadata = null;
 
-    if ((0, _primitives.isStream)(stream) && (0, _primitives.isDict)(stream.dict)) {
-      const type = stream.dict.get("Type");
-      const subtype = stream.dict.get("Subtype");
+    try {
+      const suppressEncryption = !(this.xref.encrypt && this.xref.encrypt.encryptMetadata);
+      const stream = this.xref.fetch(streamRef, suppressEncryption);
 
-      if ((0, _primitives.isName)(type, "Metadata") && (0, _primitives.isName)(subtype, "XML")) {
-        try {
+      if (stream instanceof _base_stream.BaseStream && stream.dict instanceof _primitives.Dict) {
+        const type = stream.dict.get("Type");
+        const subtype = stream.dict.get("Subtype");
+
+        if ((0, _primitives.isName)(type, "Metadata") && (0, _primitives.isName)(subtype, "XML")) {
           const data = (0, _util.stringToUTF8String)(stream.getString());
 
           if (data) {
             metadata = new _metadata_parser.MetadataParser(data).serializable;
           }
-        } catch (e) {
-          if (e instanceof _core_utils.MissingDataException) {
-            throw e;
-          }
-
-          (0, _util.info)("Skipping invalid metadata.");
         }
       }
+    } catch (ex) {
+      if (ex instanceof _core_utils.MissingDataException) {
+        throw ex;
+      }
+
+      (0, _util.info)(`Skipping invalid Metadata: "${ex}".`);
     }
 
     return (0, _util.shadow)(this, "metadata", metadata);
@@ -568,14 +571,26 @@ class Catalog {
     };
   }
 
-  get numPages() {
+  setActualNumPages(num = null) {
+    this._actualNumPages = num;
+  }
+
+  get hasActualNumPages() {
+    return this._actualNumPages !== null;
+  }
+
+  get _pagesCount() {
     const obj = this.toplevelPagesDict.get("Count");
 
     if (!Number.isInteger(obj)) {
       throw new _util.FormatError("Page count in top-level pages dictionary is not an integer.");
     }
 
-    return (0, _util.shadow)(this, "numPages", obj);
+    return (0, _util.shadow)(this, "_pagesCount", obj);
+  }
+
+  get numPages() {
+    return this.hasActualNumPages ? this._actualNumPages : this._pagesCount;
   }
 
   get destinations() {
@@ -1152,21 +1167,27 @@ class Catalog {
 
   getPageDict(pageIndex) {
     const capability = (0, _util.createPromiseCapability)();
-    const nodesToVisit = [this._catDict.getRaw("Pages")];
+    const nodesToVisit = [this.toplevelPagesDict];
     const visitedNodes = new _primitives.RefSet();
+
+    const pagesRef = this._catDict.getRaw("Pages");
+
+    if (pagesRef instanceof _primitives.Ref) {
+      visitedNodes.put(pagesRef);
+    }
+
     const xref = this.xref,
           pageKidsCountCache = this.pageKidsCountCache;
-    let count,
-        currentPageIndex = 0;
+    let currentPageIndex = 0;
 
     function next() {
       while (nodesToVisit.length) {
         const currentNode = nodesToVisit.pop();
 
-        if ((0, _primitives.isRef)(currentNode)) {
-          count = pageKidsCountCache.get(currentNode);
+        if (currentNode instanceof _primitives.Ref) {
+          const count = pageKidsCountCache.get(currentNode);
 
-          if (count > 0 && currentPageIndex + count < pageIndex) {
+          if (count >= 0 && currentPageIndex + count <= pageIndex) {
             currentPageIndex += count;
             continue;
           }
@@ -1179,11 +1200,11 @@ class Catalog {
           visitedNodes.put(currentNode);
           xref.fetchAsync(currentNode).then(function (obj) {
             if ((0, _primitives.isDict)(obj, "Page") || (0, _primitives.isDict)(obj) && !obj.has("Kids")) {
-              if (pageIndex === currentPageIndex) {
-                if (currentNode && !pageKidsCountCache.has(currentNode)) {
-                  pageKidsCountCache.put(currentNode, 1);
-                }
+              if (currentNode && !pageKidsCountCache.has(currentNode)) {
+                pageKidsCountCache.put(currentNode, 1);
+              }
 
+              if (pageIndex === currentPageIndex) {
                 capability.resolve([obj, currentNode]);
               } else {
                 currentPageIndex++;
@@ -1199,12 +1220,20 @@ class Catalog {
           return;
         }
 
-        if (!(0, _primitives.isDict)(currentNode)) {
+        if (!(currentNode instanceof _primitives.Dict)) {
           capability.reject(new _util.FormatError("Page dictionary kid reference points to wrong type of object."));
           return;
         }
 
-        count = currentNode.get("Count");
+        let count;
+
+        try {
+          count = currentNode.get("Count");
+        } catch (ex) {
+          if (ex instanceof _core_utils.MissingDataException) {
+            throw ex;
+          }
+        }
 
         if (Number.isInteger(count) && count >= 0) {
           const objId = currentNode.objId;
@@ -1219,10 +1248,28 @@ class Catalog {
           }
         }
 
-        const kids = currentNode.get("Kids");
+        let kids;
+
+        try {
+          kids = currentNode.get("Kids");
+        } catch (ex) {
+          if (ex instanceof _core_utils.MissingDataException) {
+            throw ex;
+          }
+        }
 
         if (!Array.isArray(kids)) {
-          if ((0, _primitives.isName)(currentNode.get("Type"), "Page") || !currentNode.has("Type") && currentNode.has("Contents")) {
+          let type;
+
+          try {
+            type = currentNode.get("Type");
+          } catch (ex) {
+            if (ex instanceof _core_utils.MissingDataException) {
+              throw ex;
+            }
+          }
+
+          if ((0, _primitives.isName)(type, "Page") || !currentNode.has("Type") && currentNode.has("Contents")) {
             if (currentPageIndex === pageIndex) {
               capability.resolve([currentNode, null]);
               return;
@@ -1248,6 +1295,112 @@ class Catalog {
     return capability.promise;
   }
 
+  getAllPageDicts(recoveryMode = false) {
+    const queue = [{
+      currentNode: this.toplevelPagesDict,
+      posInKids: 0
+    }];
+    const visitedNodes = new _primitives.RefSet();
+
+    const pagesRef = this._catDict.getRaw("Pages");
+
+    if (pagesRef instanceof _primitives.Ref) {
+      visitedNodes.put(pagesRef);
+    }
+
+    const map = new Map();
+    let pageIndex = 0;
+
+    function addPageDict(pageDict, pageRef) {
+      map.set(pageIndex++, [pageDict, pageRef]);
+    }
+
+    function addPageError(error) {
+      map.set(pageIndex++, [error, null]);
+    }
+
+    while (queue.length > 0) {
+      const queueItem = queue[queue.length - 1];
+      const {
+        currentNode,
+        posInKids
+      } = queueItem;
+      let kids;
+
+      try {
+        kids = currentNode.get("Kids");
+      } catch (ex) {
+        if (ex instanceof _core_utils.MissingDataException) {
+          throw ex;
+        }
+
+        if (ex instanceof _core_utils.XRefEntryException && !recoveryMode) {
+          throw ex;
+        }
+
+        addPageError(ex);
+        break;
+      }
+
+      if (!Array.isArray(kids)) {
+        addPageError(new _util.FormatError("Page dictionary kids object is not an array."));
+        break;
+      }
+
+      if (posInKids >= kids.length) {
+        queue.pop();
+        continue;
+      }
+
+      const kidObj = kids[posInKids];
+      let obj;
+
+      if (kidObj instanceof _primitives.Ref) {
+        try {
+          obj = this.xref.fetch(kidObj);
+        } catch (ex) {
+          if (ex instanceof _core_utils.MissingDataException) {
+            throw ex;
+          }
+
+          if (ex instanceof _core_utils.XRefEntryException && !recoveryMode) {
+            throw ex;
+          }
+
+          addPageError(ex);
+          break;
+        }
+
+        if (visitedNodes.has(kidObj)) {
+          addPageError(new _util.FormatError("Pages tree contains circular reference."));
+          break;
+        }
+
+        visitedNodes.put(kidObj);
+      } else {
+        obj = kidObj;
+      }
+
+      if (!(obj instanceof _primitives.Dict)) {
+        addPageError(new _util.FormatError("Page dictionary kid reference points to wrong type of object."));
+        break;
+      }
+
+      if ((0, _primitives.isDict)(obj, "Page") || !obj.has("Kids")) {
+        addPageDict(obj, kidObj instanceof _primitives.Ref ? kidObj : null);
+      } else {
+        queue.push({
+          currentNode: obj,
+          posInKids: 0
+        });
+      }
+
+      queueItem.posInKids++;
+    }
+
+    return map;
+  }
+
   getPageIndex(pageRef) {
     const cachedPageIndex = this.pageIndexCache.get(pageRef);
 

+ 4 - 0
lib/core/ccitt.js

@@ -450,6 +450,10 @@ class CCITTFaxDecoder {
       c = 0;
 
       do {
+        if (typeof this.outputBits !== "number") {
+          throw new _util.FormatError('Invalid /CCITTFaxDecode data, "outputBits" must be a number.');
+        }
+
         if (this.outputBits > bits) {
           c <<= bits;
 

+ 11 - 3
lib/core/chunked_stream.js

@@ -105,6 +105,10 @@ class ChunkedStream extends _stream.Stream {
 
     const chunk = Math.floor(pos / this.chunkSize);
 
+    if (chunk > this.numChunks) {
+      return;
+    }
+
     if (chunk === this.lastSuccessfulEnsureByteChunk) {
       return;
     }
@@ -125,9 +129,13 @@ class ChunkedStream extends _stream.Stream {
       return;
     }
 
-    const chunkSize = this.chunkSize;
-    const beginChunk = Math.floor(begin / chunkSize);
-    const endChunk = Math.floor((end - 1) / chunkSize) + 1;
+    const beginChunk = Math.floor(begin / this.chunkSize);
+
+    if (beginChunk > this.numChunks) {
+      return;
+    }
+
+    const endChunk = Math.min(Math.floor((end - 1) / this.chunkSize) + 1, this.numChunks);
 
     for (let chunk = beginChunk; chunk < endChunk; ++chunk) {
       if (!this._loadedChunks.has(chunk)) {

+ 8 - 1
lib/core/cmap.js

@@ -74,7 +74,14 @@ class CMap {
 
     while (low <= high) {
       this._map[low++] = dstLow;
-      dstLow = dstLow.substring(0, lastByte) + String.fromCharCode(dstLow.charCodeAt(lastByte) + 1);
+      const nextCharCode = dstLow.charCodeAt(lastByte) + 1;
+
+      if (nextCharCode > 0xff) {
+        dstLow = dstLow.substring(0, lastByte - 1) + String.fromCharCode(dstLow.charCodeAt(lastByte - 1) + 1) + "\x00";
+        continue;
+      }
+
+      dstLow = dstLow.substring(0, lastByte) + String.fromCharCode(nextCharCode);
     }
   }
 

+ 8 - 8
lib/core/colorspace.js

@@ -260,16 +260,16 @@ class ColorSpace {
 
     if ((0, _primitives.isName)(cs)) {
       switch (cs.name) {
-        case "DeviceGray":
         case "G":
+        case "DeviceGray":
           return this.singletons.gray;
 
-        case "DeviceRGB":
         case "RGB":
+        case "DeviceRGB":
           return this.singletons.rgb;
 
-        case "DeviceCMYK":
         case "CMYK":
+        case "DeviceCMYK":
           return this.singletons.cmyk;
 
         case "Pattern":
@@ -302,16 +302,16 @@ class ColorSpace {
       let params, numComps, baseCS, whitePoint, blackPoint, gamma;
 
       switch (mode) {
-        case "DeviceGray":
         case "G":
+        case "DeviceGray":
           return this.singletons.gray;
 
-        case "DeviceRGB":
         case "RGB":
+        case "DeviceRGB":
           return this.singletons.rgb;
 
-        case "DeviceCMYK":
         case "CMYK":
+        case "DeviceCMYK":
           return this.singletons.cmyk;
 
         case "CalGray":
@@ -364,8 +364,8 @@ class ColorSpace {
 
           return new PatternCS(baseCS);
 
-        case "Indexed":
         case "I":
+        case "Indexed":
           baseCS = this._parse(cs[1], xref, resources, pdfFunctionFactory);
           const hiVal = xref.fetchIfRef(cs[2]) + 1;
           const lookup = xref.fetchIfRef(cs[3]);
@@ -678,7 +678,7 @@ const CalGrayCS = function CalGrayCSClosure() {
     const A = src[srcOffset] * scale;
     const AG = A ** cs.G;
     const L = cs.YW * AG;
-    const val = Math.max(295.8 * L ** 0.333333333333333333 - 40.8, 0);
+    const val = Math.max(295.8 * L ** 0.3333333333333333 - 40.8, 0);
     dest[destOffset] = val;
     dest[destOffset + 1] = val;
     dest[destOffset + 2] = val;

+ 50 - 1
lib/core/core_utils.js

@@ -24,6 +24,7 @@
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
+exports.XRefParseException = exports.XRefEntryException = exports.ParserEOFException = exports.MissingDataException = exports.DocStats = void 0;
 exports.collectActions = collectActions;
 exports.encodeToXmlString = encodeToXmlString;
 exports.escapePDFName = escapePDFName;
@@ -39,7 +40,6 @@ exports.readUint32 = readUint32;
 exports.recoverJsURL = recoverJsURL;
 exports.toRomanNumerals = toRomanNumerals;
 exports.validateCSSFont = validateCSSFont;
-exports.XRefParseException = exports.XRefEntryException = exports.ParserEOFException = exports.MissingDataException = void 0;
 
 var _util = require("../shared/util.js");
 
@@ -115,6 +115,55 @@ class XRefParseException extends _util.BaseException {
 
 exports.XRefParseException = XRefParseException;
 
+class DocStats {
+  constructor(handler) {
+    this._handler = handler;
+    this._streamTypes = new Set();
+    this._fontTypes = new Set();
+  }
+
+  _send() {
+    const streamTypes = Object.create(null),
+          fontTypes = Object.create(null);
+
+    for (const type of this._streamTypes) {
+      streamTypes[type] = true;
+    }
+
+    for (const type of this._fontTypes) {
+      fontTypes[type] = true;
+    }
+
+    this._handler.send("DocStats", {
+      streamTypes,
+      fontTypes
+    });
+  }
+
+  addStreamType(type) {
+    if (this._streamTypes.has(type)) {
+      return;
+    }
+
+    this._streamTypes.add(type);
+
+    this._send();
+  }
+
+  addFontType(type) {
+    if (this._fontTypes.has(type)) {
+      return;
+    }
+
+    this._fontTypes.add(type);
+
+    this._send();
+  }
+
+}
+
+exports.DocStats = DocStats;
+
 function getInheritableProperty({
   dict,
   key,

+ 3 - 1
lib/core/crypto.js

@@ -24,8 +24,9 @@
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
+exports.calculateSHA256 = exports.calculateMD5 = exports.PDF20 = exports.PDF17 = exports.CipherTransformFactory = exports.ARCFourCipher = exports.AES256Cipher = exports.AES128Cipher = void 0;
 exports.calculateSHA384 = calculateSHA384;
-exports.PDF20 = exports.PDF17 = exports.CipherTransformFactory = exports.calculateSHA512 = exports.calculateSHA256 = exports.calculateMD5 = exports.ARCFourCipher = exports.AES256Cipher = exports.AES128Cipher = void 0;
+exports.calculateSHA512 = void 0;
 
 var _util = require("../shared/util.js");
 
@@ -1486,6 +1487,7 @@ const CipherTransformFactory = function CipherTransformFactoryClosure() {
         throw new _util.FormatError("unknown encryption method");
       }
 
+      this.filterName = filter.name;
       this.dict = dict;
       const algorithm = dict.get("V");
 

+ 141 - 58
lib/core/document.js

@@ -24,7 +24,7 @@
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
-exports.PDFDocument = exports.Page = void 0;
+exports.Page = exports.PDFDocument = void 0;
 
 var _util = require("../shared/util.js");
 
@@ -235,13 +235,9 @@ class Page {
   }
 
   get xfaData() {
-    if (this.xfaFactory) {
-      return (0, _util.shadow)(this, "xfaData", {
-        bbox: this.xfaFactory.getBoundingBox(this.pageIndex)
-      });
-    }
-
-    return (0, _util.shadow)(this, "xfaData", null);
+    return (0, _util.shadow)(this, "xfaData", this.xfaFactory ? {
+      bbox: this.xfaFactory.getBoundingBox(this.pageIndex)
+    } : null);
   }
 
   save(handler, task, annotationStorage) {
@@ -339,7 +335,7 @@ class Page {
 
       for (const annotation of annotations) {
         if (intentAny || intentDisplay && annotation.mustBeViewed(annotationStorage) || intentPrint && annotation.mustBePrinted(annotationStorage)) {
-          opListPromises.push(annotation.getOperatorList(partialEvaluator, task, renderForms, annotationStorage).catch(function (reason) {
+          opListPromises.push(annotation.getOperatorList(partialEvaluator, task, intent, renderForms, annotationStorage).catch(function (reason) {
             (0, _util.warn)("getOperatorList - ignoring annotation data during " + `"${task.name}" task: "${reason}".`);
             return null;
           }));
@@ -543,7 +539,7 @@ class PDFDocument {
     this.pdfManager = pdfManager;
     this.stream = stream;
     this.xref = new _xref.XRef(stream, pdfManager);
-    this._pagePromises = [];
+    this._pagePromises = new Map();
     this._version = null;
     const idCounters = {
       font: 0
@@ -677,12 +673,18 @@ class PDFDocument {
   }
 
   get numPages() {
-    if (this.xfaFactory) {
-      return (0, _util.shadow)(this, "numPages", this.xfaFactory.numberPages);
+    let num = 0;
+
+    if (this.catalog.hasActualNumPages) {
+      num = this.catalog.numPages;
+    } else if (this.xfaFactory) {
+      num = this.xfaFactory.getNumPages();
+    } else if (this.linearization) {
+      num = this.linearization.numPages;
+    } else {
+      num = this.catalog.numPages;
     }
 
-    const linearization = this.linearization;
-    const num = linearization ? linearization.numPages : this.catalog.numPages;
     return (0, _util.shadow)(this, "numPages", num);
   }
 
@@ -782,24 +784,21 @@ class PDFDocument {
   }
 
   get xfaFactory() {
+    let data;
+
     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);
+      data = this.xfaData;
     }
 
-    return (0, _util.shadow)(this, "xfaFaxtory", null);
+    return (0, _util.shadow)(this, "xfaFactory", data ? new _factory.XFAFactory(data) : null);
   }
 
   get isPureXfa() {
-    return this.xfaFactory && this.xfaFactory.isValid();
+    return this.xfaFactory ? this.xfaFactory.isValid() : false;
   }
 
   get htmlForXfa() {
-    if (this.xfaFactory) {
-      return this.xfaFactory.getPages();
-    }
-
-    return null;
+    return this.xfaFactory ? this.xfaFactory.getPages() : null;
   }
 
   async loadXfaImages() {
@@ -971,11 +970,7 @@ class PDFDocument {
   }
 
   async serializeXfaData(annotationStorage) {
-    if (this.xfaFactory) {
-      return this.xfaFactory.serializeData(annotationStorage);
-    }
-
-    return null;
+    return this.xfaFactory ? this.xfaFactory.serializeData(annotationStorage) : null;
   }
 
   get formInfo() {
@@ -1036,6 +1031,8 @@ class PDFDocument {
 
     const docInfo = {
       PDFFormatVersion: version,
+      Language: this.catalog.lang,
+      EncryptFilterName: this.xref.encrypt ? this.xref.encrypt.filterName : null,
       IsLinearized: !!this.linearization,
       IsAcroFormPresent: this.formInfo.hasAcroForm,
       IsXFAPresent: this.formInfo.hasXfa,
@@ -1120,7 +1117,7 @@ class PDFDocument {
     return (0, _util.shadow)(this, "fingerprints", [hexString(hashOriginal), hashModified ? hexString(hashModified) : null]);
   }
 
-  _getLinearizationPage(pageIndex) {
+  async _getLinearizationPage(pageIndex) {
     const {
       catalog,
       linearization
@@ -1128,7 +1125,9 @@ class PDFDocument {
 
     const ref = _primitives.Ref.get(linearization.objectNumberFirst, 0);
 
-    return this.xref.fetchAsync(ref).then(obj => {
+    try {
+      const obj = await this.xref.fetchAsync(ref);
+
       if ((0, _primitives.isDict)(obj, "Page") || (0, _primitives.isDict)(obj) && !obj.has("Type") && obj.has("Contents")) {
         if (ref && !catalog.pageKidsCountCache.has(ref)) {
           catalog.pageKidsCountCache.put(ref, 1);
@@ -1137,42 +1136,36 @@ class PDFDocument {
         return [obj, ref];
       }
 
-      throw new _util.FormatError("The Linearization dictionary doesn't point " + "to a valid Page dictionary.");
-    }).catch(reason => {
+      throw new _util.FormatError("The Linearization dictionary doesn't point to a valid Page dictionary.");
+    } catch (reason) {
       (0, _util.info)(reason);
       return catalog.getPageDict(pageIndex);
-    });
+    }
   }
 
   getPage(pageIndex) {
-    if (this._pagePromises[pageIndex] !== undefined) {
-      return this._pagePromises[pageIndex];
+    const cachedPromise = this._pagePromises.get(pageIndex);
+
+    if (cachedPromise) {
+      return cachedPromise;
     }
 
     const {
       catalog,
-      linearization
+      linearization,
+      xfaFactory
     } = this;
+    let promise;
 
-    if (this.xfaFactory) {
-      return Promise.resolve(new Page({
-        pdfManager: this.pdfManager,
-        xref: this.xref,
-        pageIndex,
-        pageDict: _primitives.Dict.empty,
-        ref: null,
-        globalIdFactory: this._globalIdFactory,
-        fontCache: catalog.fontCache,
-        builtInCMapCache: catalog.builtInCMapCache,
-        standardFontDataCache: catalog.standardFontDataCache,
-        globalImageCache: catalog.globalImageCache,
-        nonBlendModesSet: catalog.nonBlendModesSet,
-        xfaFactory: this.xfaFactory
-      }));
+    if (xfaFactory) {
+      promise = Promise.resolve([_primitives.Dict.empty, null]);
+    } else if (linearization && linearization.pageFirst === pageIndex) {
+      promise = this._getLinearizationPage(pageIndex);
+    } else {
+      promise = catalog.getPageDict(pageIndex);
     }
 
-    const promise = linearization && linearization.pageFirst === pageIndex ? this._getLinearizationPage(pageIndex) : catalog.getPageDict(pageIndex);
-    return this._pagePromises[pageIndex] = promise.then(([pageDict, ref]) => {
+    promise = promise.then(([pageDict, ref]) => {
       return new Page({
         pdfManager: this.pdfManager,
         xref: this.xref,
@@ -1185,19 +1178,109 @@ class PDFDocument {
         standardFontDataCache: catalog.standardFontDataCache,
         globalImageCache: catalog.globalImageCache,
         nonBlendModesSet: catalog.nonBlendModesSet,
-        xfaFactory: null
+        xfaFactory
       });
     });
+
+    this._pagePromises.set(pageIndex, promise);
+
+    return promise;
   }
 
-  checkFirstPage() {
-    return this.getPage(0).catch(async reason => {
+  async checkFirstPage(recoveryMode = false) {
+    if (recoveryMode) {
+      return;
+    }
+
+    try {
+      await this.getPage(0);
+    } catch (reason) {
       if (reason instanceof _core_utils.XRefEntryException) {
-        this._pagePromises.length = 0;
+        this._pagePromises.delete(0);
+
         await this.cleanup();
         throw new _core_utils.XRefParseException();
       }
-    });
+    }
+  }
+
+  async checkLastPage(recoveryMode = false) {
+    const {
+      catalog,
+      pdfManager
+    } = this;
+    catalog.setActualNumPages();
+    let numPages;
+
+    try {
+      await Promise.all([pdfManager.ensureDoc("xfaFactory"), pdfManager.ensureDoc("linearization"), pdfManager.ensureCatalog("numPages")]);
+
+      if (this.xfaFactory) {
+        return;
+      } else if (this.linearization) {
+        numPages = this.linearization.numPages;
+      } else {
+        numPages = catalog.numPages;
+      }
+
+      if (!Number.isInteger(numPages)) {
+        throw new _util.FormatError("Page count is not an integer.");
+      } else if (numPages <= 1) {
+        return;
+      }
+
+      await this.getPage(numPages - 1);
+    } catch (reason) {
+      this._pagePromises.delete(numPages - 1);
+
+      await this.cleanup();
+
+      if (reason instanceof _core_utils.XRefEntryException && !recoveryMode) {
+        throw new _core_utils.XRefParseException();
+      }
+
+      (0, _util.warn)(`checkLastPage - invalid /Pages tree /Count: ${numPages}.`);
+      let pagesTree;
+
+      try {
+        pagesTree = await pdfManager.ensureCatalog("getAllPageDicts", [recoveryMode]);
+      } catch (reasonAll) {
+        if (reasonAll instanceof _core_utils.XRefEntryException && !recoveryMode) {
+          throw new _core_utils.XRefParseException();
+        }
+
+        catalog.setActualNumPages(1);
+        return;
+      }
+
+      for (const [pageIndex, [pageDict, ref]] of pagesTree) {
+        let promise;
+
+        if (pageDict instanceof Error) {
+          promise = Promise.reject(pageDict);
+          promise.catch(() => {});
+        } else {
+          promise = Promise.resolve(new Page({
+            pdfManager,
+            xref: this.xref,
+            pageIndex,
+            pageDict,
+            ref,
+            globalIdFactory: this._globalIdFactory,
+            fontCache: catalog.fontCache,
+            builtInCMapCache: catalog.builtInCMapCache,
+            standardFontDataCache: catalog.standardFontDataCache,
+            globalImageCache: catalog.globalImageCache,
+            nonBlendModesSet: catalog.nonBlendModesSet,
+            xfaFactory: null
+          }));
+        }
+
+        this._pagePromises.set(pageIndex, promise);
+      }
+
+      catalog.setActualNumPages(pagesTree.size);
+    }
   }
 
   fontFallback(id, handler) {

Dosya farkı çok büyük olduğundan ihmal edildi
+ 1 - 1
lib/core/encodings.js


+ 144 - 151
lib/core/evaluator.js

@@ -56,6 +56,8 @@ var _image_utils = require("./image_utils.js");
 
 var _stream = require("./stream.js");
 
+var _base_stream = require("./base_stream.js");
+
 var _bidi = require("./bidi.js");
 
 var _colorspace = require("./colorspace.js");
@@ -498,7 +500,8 @@ class PartialEvaluator {
       operatorList.addOp(_util.OPS.beginGroup, [groupOptions]);
     }
 
-    operatorList.addOp(_util.OPS.paintFormXObjectBegin, [matrix, bbox]);
+    const args = group ? [matrix, null] : [matrix, bbox];
+    operatorList.addOp(_util.OPS.paintFormXObjectBegin, args);
     return this.getOperatorList({
       stream: xobj,
       task,
@@ -539,8 +542,8 @@ class PartialEvaluator {
   }) {
     const dict = image.dict;
     const imageRef = dict.objId;
-    const w = dict.get("Width", "W");
-    const h = dict.get("Height", "H");
+    const w = dict.get("W", "Width");
+    const h = dict.get("H", "Height");
 
     if (!(w && (0, _util.isNum)(w)) || !(h && (0, _util.isNum)(h))) {
       (0, _util.warn)("Image dimensions are missing, or not numbers.");
@@ -564,20 +567,18 @@ class PartialEvaluator {
       operatorList.addOp(_util.OPS.beginMarkedContentProps, ["OC", optionalContent]);
     }
 
-    const imageMask = dict.get("ImageMask", "IM") || false;
-    const interpolate = dict.get("Interpolate", "I");
+    const imageMask = dict.get("IM", "ImageMask") || false;
+    const interpolate = dict.get("I", "Interpolate");
     let imgData, args;
 
     if (imageMask) {
-      const width = dict.get("Width", "W");
-      const height = dict.get("Height", "H");
-      const bitStrideLength = width + 7 >> 3;
-      const imgArray = image.getBytes(bitStrideLength * height, true);
-      const decode = dict.getArray("Decode", "D");
+      const bitStrideLength = w + 7 >> 3;
+      const imgArray = image.getBytes(bitStrideLength * h, true);
+      const decode = dict.getArray("D", "Decode");
       imgData = _image.PDFImage.createMask({
         imgArray,
-        width,
-        height,
+        width: w,
+        height: h,
         imageIsFromDecodeStream: image instanceof _decode_stream.DecodeStream,
         inverseDecode: !!decode && decode[0] > 0,
         interpolate
@@ -600,7 +601,7 @@ class PartialEvaluator {
       return;
     }
 
-    const softMask = dict.get("SMask", "SM") || false;
+    const softMask = dict.get("SM", "SMask") || false;
     const mask = dict.get("Mask") || false;
     const SMALL_IMAGE_DIMENSIONS = 200;
 
@@ -1099,8 +1100,7 @@ class PartialEvaluator {
     font.loadedName = `${this.idFactory.getDocId()}_${fontID}`;
     this.translateFont(preEvaluatedFont).then(translatedFont => {
       if (translatedFont.fontType !== undefined) {
-        const xrefFontStats = xref.stats.fontTypes;
-        xrefFontStats[translatedFont.fontType] = true;
+        xref.stats.addFontType(translatedFont.fontType);
       }
 
       fontCapability.resolve(new TranslatedFont({
@@ -1119,8 +1119,10 @@ class PartialEvaluator {
         const fontFile3 = descriptor && descriptor.get("FontFile3");
         const subtype = fontFile3 && fontFile3.get("Subtype");
         const fontType = (0, _fonts_utils.getFontType)(preEvaluatedFont.type, subtype && subtype.name);
-        const xrefFontStats = xref.stats.fontTypes;
-        xrefFontStats[fontType] = true;
+
+        if (fontType !== undefined) {
+          xref.stats.addFontType(fontType);
+        }
       } catch (ex) {}
 
       fontCapability.resolve(new TranslatedFont({
@@ -1921,6 +1923,8 @@ class PartialEvaluator {
     resources = resources || _primitives.Dict.empty;
     stateManager = stateManager || new StateManager(new TextState());
     const WhitespaceRegexp = /\s/g;
+    const DiacriticRegExp = new RegExp("^\\p{Mn}$", "u");
+    const NormalizedUnicodes = (0, _unicode.getNormalizedUnicodes)();
     const textContent = {
       items: [],
       styles: Object.create(null)
@@ -1933,21 +1937,20 @@ class PartialEvaluator {
       width: 0,
       height: 0,
       vertical: false,
-      lastCharSize: 0,
       prevTransform: null,
       textAdvanceScale: 0,
-      spaceWidth: 0,
       spaceInFlowMin: 0,
       spaceInFlowMax: 0,
       trackingSpaceMin: Infinity,
+      negativeSpaceMax: -Infinity,
       transform: null,
       fontName: null,
-      hasEOL: false,
-      isLastCharWhiteSpace: false
+      hasEOL: false
     };
-    const TRACKING_SPACE_FACTOR = 0.3;
-    const SPACE_IN_FLOW_MIN_FACTOR = 0.3;
-    const SPACE_IN_FLOW_MAX_FACTOR = 1.3;
+    const TRACKING_SPACE_FACTOR = 0.1;
+    const NEGATIVE_SPACE_FACTOR = -0.2;
+    const SPACE_IN_FLOW_MIN_FACTOR = 0.1;
+    const SPACE_IN_FLOW_MAX_FACTOR = 0.6;
     const self = this;
     const xref = this.xref;
     const showSpacedTextBuffer = [];
@@ -2006,19 +2009,10 @@ class PartialEvaluator {
       const scaleLineX = Math.hypot(textState.textLineMatrix[0], textState.textLineMatrix[1]);
       const scaleCtmX = Math.hypot(textState.ctm[0], textState.ctm[1]);
       textContentItem.textAdvanceScale = scaleCtmX * scaleLineX;
-      textContentItem.lastCharSize = textContentItem.lastCharSize || 0;
-      const spaceWidth = font.spaceWidth / 1000 * textState.fontSize;
-
-      if (spaceWidth) {
-        textContentItem.spaceWidth = spaceWidth;
-        textContentItem.trackingSpaceMin = spaceWidth * TRACKING_SPACE_FACTOR;
-        textContentItem.spaceInFlowMin = spaceWidth * SPACE_IN_FLOW_MIN_FACTOR;
-        textContentItem.spaceInFlowMax = spaceWidth * SPACE_IN_FLOW_MAX_FACTOR;
-      } else {
-        textContentItem.spaceWidth = 0;
-        textContentItem.trackingSpaceMin = Infinity;
-      }
-
+      textContentItem.trackingSpaceMin = textState.fontSize * TRACKING_SPACE_FACTOR;
+      textContentItem.negativeSpaceMax = textState.fontSize * NEGATIVE_SPACE_FACTOR;
+      textContentItem.spaceInFlowMin = textState.fontSize * SPACE_IN_FLOW_MIN_FACTOR;
+      textContentItem.spaceInFlowMax = textState.fontSize * SPACE_IN_FLOW_MAX_FACTOR;
       textContentItem.hasEOL = false;
       textContentItem.initialized = true;
       return textContentItem;
@@ -2090,38 +2084,69 @@ class PartialEvaluator {
       });
     }
 
-    function compareWithLastPosition(fontSize) {
+    function compareWithLastPosition() {
       if (!combineTextItems || !textState.font || !textContentItem.prevTransform) {
         return;
       }
 
       const currentTransform = getCurrentTextTransform();
-      const posX = currentTransform[4];
-      const posY = currentTransform[5];
-      const lastPosX = textContentItem.prevTransform[4];
-      const lastPosY = textContentItem.prevTransform[5];
+      let posX = currentTransform[4];
+      let posY = currentTransform[5];
+      let lastPosX = textContentItem.prevTransform[4];
+      let lastPosY = textContentItem.prevTransform[5];
 
       if (lastPosX === posX && lastPosY === posY) {
         return;
       }
 
-      const advanceX = (posX - lastPosX) / textContentItem.textAdvanceScale;
-      const advanceY = (posY - lastPosY) / textContentItem.textAdvanceScale;
-      const HALF_LAST_CHAR = -0.5 * textContentItem.lastCharSize;
+      let rotate = 0;
+
+      if (currentTransform[0] && currentTransform[1] === 0 && currentTransform[2] === 0) {
+        rotate = currentTransform[0] > 0 ? 0 : 180;
+      } else if (currentTransform[1] && currentTransform[0] === 0 && currentTransform[3] === 0) {
+        rotate += currentTransform[1] > 0 ? 90 : 270;
+      }
+
+      if (rotate !== 0) {
+        switch (rotate) {
+          case 90:
+            [posX, posY] = [posY, posX];
+            [lastPosX, lastPosY] = [lastPosY, lastPosX];
+            break;
+
+          case 180:
+            [posX, posY, lastPosX, lastPosY] = [-posX, -posY, -lastPosX, -lastPosY];
+            break;
+
+          case 270:
+            [posX, posY] = [-posY, -posX];
+            [lastPosX, lastPosY] = [-lastPosY, -lastPosX];
+            break;
+        }
+      }
 
       if (textState.font.vertical) {
-        if (Math.abs(advanceX) > textContentItem.width / textContentItem.textAdvanceScale) {
-          appendEOL();
+        const advanceY = (lastPosY - posY) / textContentItem.textAdvanceScale;
+        const advanceX = posX - lastPosX;
+
+        if (advanceY < textContentItem.negativeSpaceMax) {
+          if (Math.abs(advanceX) > 0.5 * textContentItem.width) {
+            appendEOL();
+            return;
+          }
+
+          flushTextContentItem();
           return;
         }
 
-        if (HALF_LAST_CHAR > advanceY) {
+        if (Math.abs(advanceX) > textContentItem.height) {
+          appendEOL();
           return;
         }
 
-        if (advanceY > textContentItem.trackingSpaceMin) {
+        if (advanceY <= textContentItem.trackingSpaceMin) {
           textContentItem.height += advanceY;
-        } else if (!addFakeSpaces(advanceY, 0, textContentItem.prevTransform)) {
+        } else if (!addFakeSpaces(advanceY, textContentItem.prevTransform)) {
           if (textContentItem.str.length === 0) {
             textContent.items.push({
               str: " ",
@@ -2132,7 +2157,6 @@ class PartialEvaluator {
               fontName: textContentItem.fontName,
               hasEOL: false
             });
-            textContentItem.isLastCharWhiteSpace = true;
           } else {
             textContentItem.height += advanceY;
           }
@@ -2141,18 +2165,27 @@ class PartialEvaluator {
         return;
       }
 
-      if (Math.abs(advanceY) > textContentItem.height / textContentItem.textAdvanceScale) {
-        appendEOL();
+      const advanceX = (posX - lastPosX) / textContentItem.textAdvanceScale;
+      const advanceY = posY - lastPosY;
+
+      if (advanceX < textContentItem.negativeSpaceMax) {
+        if (Math.abs(advanceY) > 0.5 * textContentItem.height) {
+          appendEOL();
+          return;
+        }
+
+        flushTextContentItem();
         return;
       }
 
-      if (HALF_LAST_CHAR > advanceX) {
+      if (Math.abs(advanceY) > textContentItem.height) {
+        appendEOL();
         return;
       }
 
       if (advanceX <= textContentItem.trackingSpaceMin) {
         textContentItem.width += advanceX;
-      } else if (!addFakeSpaces(advanceX, 0, textContentItem.prevTransform)) {
+      } else if (!addFakeSpaces(advanceX, textContentItem.prevTransform)) {
         if (textContentItem.str.length === 0) {
           textContent.items.push({
             str: " ",
@@ -2163,7 +2196,6 @@ class PartialEvaluator {
             fontName: textContentItem.fontName,
             hasEOL: false
           });
-          textContentItem.isLastCharWhiteSpace = true;
         } else {
           textContentItem.width += advanceX;
         }
@@ -2172,8 +2204,7 @@ class PartialEvaluator {
 
     function buildTextContentItem({
       chars,
-      extraSpacing,
-      isFirstChunk
+      extraSpacing
     }) {
       const font = textState.font;
 
@@ -2184,87 +2215,73 @@ class PartialEvaluator {
           if (!font.vertical) {
             textState.translateTextMatrix(charSpacing * textState.textHScale, 0);
           } else {
-            textState.translateTextMatrix(0, charSpacing);
+            textState.translateTextMatrix(0, -charSpacing);
           }
         }
 
         return;
       }
 
-      const NormalizedUnicodes = (0, _unicode.getNormalizedUnicodes)();
       const glyphs = font.charsToGlyphs(chars);
       const scale = textState.fontMatrix[0] * textState.fontSize;
 
-      if (isFirstChunk) {
-        compareWithLastPosition(scale);
-      }
-
-      let textChunk = ensureTextContentItem();
-      let size = 0;
-      let lastCharSize = 0;
-
       for (let i = 0, ii = glyphs.length; i < ii; i++) {
         const glyph = glyphs[i];
-        let charSpacing = textState.charSpacing + (i === ii - 1 ? extraSpacing : 0);
-        let glyphUnicode = glyph.unicode;
+        let charSpacing = textState.charSpacing + (i + 1 === ii ? extraSpacing : 0);
+        let glyphWidth = glyph.width;
 
-        if (glyph.isSpace) {
-          charSpacing += textState.wordSpacing;
-          textChunk.isLastCharWhiteSpace = true;
-        } else {
-          glyphUnicode = NormalizedUnicodes[glyphUnicode] || glyphUnicode;
-          glyphUnicode = (0, _unicode.reverseIfRtl)(glyphUnicode);
-          textChunk.isLastCharWhiteSpace = false;
+        if (font.vertical) {
+          glyphWidth = glyph.vmetric ? glyph.vmetric[0] : -glyphWidth;
         }
 
-        textChunk.str.push(glyphUnicode);
-        const glyphWidth = font.vertical && glyph.vmetric ? glyph.vmetric[0] : glyph.width;
         let scaledDim = glyphWidth * scale;
+        let glyphUnicode = glyph.unicode;
+
+        if (glyphUnicode === " " && (i === 0 || i + 1 === ii || glyphs[i - 1].unicode === " " || glyphs[i + 1].unicode === " " || extraSpacing)) {
+          if (!font.vertical) {
+            charSpacing += scaledDim + textState.wordSpacing;
+            textState.translateTextMatrix(charSpacing * textState.textHScale, 0);
+          } else {
+            charSpacing += -scaledDim + textState.wordSpacing;
+            textState.translateTextMatrix(0, -charSpacing);
+          }
+
+          continue;
+        }
+
+        compareWithLastPosition();
+        const textChunk = ensureTextContentItem();
+
+        if (DiacriticRegExp.test(glyph.unicode)) {
+          scaledDim = 0;
+        }
 
         if (!font.vertical) {
           scaledDim *= textState.textHScale;
           textState.translateTextMatrix(scaledDim, 0);
+          textChunk.width += scaledDim;
         } else {
           textState.translateTextMatrix(0, scaledDim);
           scaledDim = Math.abs(scaledDim);
+          textChunk.height += scaledDim;
         }
 
-        size += scaledDim;
-
-        if (charSpacing) {
-          if (!font.vertical) {
-            charSpacing *= textState.textHScale;
-          }
+        if (scaledDim) {
+          textChunk.prevTransform = getCurrentTextTransform();
+        }
 
-          scaledDim += charSpacing;
-          const wasSplit = charSpacing > textContentItem.trackingSpaceMin && addFakeSpaces(charSpacing, size);
+        glyphUnicode = NormalizedUnicodes[glyphUnicode] || glyphUnicode;
+        glyphUnicode = (0, _unicode.reverseIfRtl)(glyphUnicode);
+        textChunk.str.push(glyphUnicode);
 
+        if (charSpacing) {
           if (!font.vertical) {
-            textState.translateTextMatrix(charSpacing, 0);
-          } else {
-            textState.translateTextMatrix(0, charSpacing);
-          }
-
-          if (wasSplit) {
-            textChunk = ensureTextContentItem();
-            size = 0;
+            textState.translateTextMatrix(charSpacing * textState.textHScale, 0);
           } else {
-            size += charSpacing;
+            textState.translateTextMatrix(0, -charSpacing);
           }
         }
-
-        lastCharSize = scaledDim;
       }
-
-      textChunk.lastCharSize = lastCharSize;
-
-      if (!font.vertical) {
-        textChunk.width += size;
-      } else {
-        textChunk.height += size;
-      }
-
-      textChunk.prevTransform = getCurrentTextTransform();
     }
 
     function appendEOL() {
@@ -2282,16 +2299,12 @@ class PartialEvaluator {
           hasEOL: true
         });
       }
-
-      textContentItem.isLastCharWhiteSpace = false;
-      textContentItem.lastCharSize = 0;
     }
 
-    function addFakeSpaces(width, size, transf = null) {
+    function addFakeSpaces(width, transf) {
       if (textContentItem.spaceInFlowMin <= width && width <= textContentItem.spaceInFlowMax) {
         if (textContentItem.initialized) {
           textContentItem.str.push(" ");
-          textContentItem.isLastCharWhiteSpace = true;
         }
 
         return false;
@@ -2299,29 +2312,19 @@ class PartialEvaluator {
 
       const fontName = textContentItem.fontName;
       let height = 0;
-      width *= textContentItem.textAdvanceScale;
 
-      if (!textContentItem.vertical) {
-        textContentItem.width += size;
-      } else {
-        textContentItem.height += size;
+      if (textContentItem.vertical) {
         height = width;
         width = 0;
       }
 
       flushTextContentItem();
-
-      if (textContentItem.isLastCharWhiteSpace) {
-        return true;
-      }
-
-      textContentItem.isLastCharWhiteSpace = true;
       textContent.items.push({
         str: " ",
         dir: "ltr",
         width,
         height,
-        transform: transf ? transf : getCurrentTextTransform(),
+        transform: transf || getCurrentTextTransform(),
         fontName,
         hasEOL: false
       });
@@ -2407,17 +2410,14 @@ class PartialEvaluator {
             return;
 
           case _util.OPS.setTextRise:
-            flushTextContentItem();
             textState.textRise = args[0];
             break;
 
           case _util.OPS.setHScale:
-            flushTextContentItem();
             textState.textHScale = args[0] / 100;
             break;
 
           case _util.OPS.setLeading:
-            flushTextContentItem();
             textState.leading = args[0];
             break;
 
@@ -2427,14 +2427,12 @@ class PartialEvaluator {
             break;
 
           case _util.OPS.setLeadingMoveText:
-            flushTextContentItem();
             textState.leading = -args[1];
             textState.translateTextLineMatrix(args[0], args[1]);
             textState.textMatrix = textState.textLineMatrix.slice();
             break;
 
           case _util.OPS.nextLine:
-            appendEOL();
             textState.carriageReturn();
             break;
 
@@ -2453,7 +2451,6 @@ class PartialEvaluator {
             break;
 
           case _util.OPS.beginText:
-            flushTextContentItem();
             textState.textMatrix = _util.IDENTITY_MATRIX.slice();
             textState.textLineMatrix = _util.IDENTITY_MATRIX.slice();
             break;
@@ -2466,7 +2463,6 @@ class PartialEvaluator {
 
             const spaceFactor = (textState.font.vertical ? 1 : -1) * textState.fontSize / 1000;
             const elements = args[0];
-            let isFirstChunk = true;
 
             for (let i = 0, ii = elements.length; i < ii - 1; i++) {
               const item = elements[i];
@@ -2478,13 +2474,8 @@ class PartialEvaluator {
                 showSpacedTextBuffer.length = 0;
                 buildTextContentItem({
                   chars: str,
-                  extraSpacing: item * spaceFactor,
-                  isFirstChunk
+                  extraSpacing: item * spaceFactor
                 });
-
-                if (str && isFirstChunk) {
-                  isFirstChunk = false;
-                }
               }
             }
 
@@ -2499,8 +2490,7 @@ class PartialEvaluator {
               showSpacedTextBuffer.length = 0;
               buildTextContentItem({
                 chars: str,
-                extraSpacing: 0,
-                isFirstChunk
+                extraSpacing: 0
               });
             }
 
@@ -2514,8 +2504,7 @@ class PartialEvaluator {
 
             buildTextContentItem({
               chars: args[0],
-              extraSpacing: 0,
-              isFirstChunk: true
+              extraSpacing: 0
             });
             break;
 
@@ -2525,13 +2514,10 @@ class PartialEvaluator {
               continue;
             }
 
-            textContentItem.hasEOL = true;
-            flushTextContentItem();
             textState.carriageReturn();
             buildTextContentItem({
               chars: args[0],
-              extraSpacing: 0,
-              isFirstChunk: true
+              extraSpacing: 0
             });
             break;
 
@@ -2541,15 +2527,12 @@ class PartialEvaluator {
               continue;
             }
 
-            textContentItem.hasEOL = true;
-            flushTextContentItem();
             textState.wordSpacing = args[0];
             textState.charSpacing = args[1];
             textState.carriageReturn();
             buildTextContentItem({
               chars: args[2],
-              extraSpacing: 0,
-              isFirstChunk: true
+              extraSpacing: 0
             });
             break;
 
@@ -2801,7 +2784,7 @@ class PartialEvaluator {
 
       const cidToGidMap = dict.get("CIDToGIDMap");
 
-      if ((0, _primitives.isStream)(cidToGidMap)) {
+      if (cidToGidMap instanceof _base_stream.BaseStream) {
         cidToGidBytes = cidToGidMap.getBytes();
       }
     }
@@ -3401,6 +3384,16 @@ class PartialEvaluator {
 
           hash.update(widthsBuf.join());
         }
+
+        const cidToGidMap = dict.getRaw("CIDToGIDMap") || baseDict.getRaw("CIDToGIDMap");
+
+        if (cidToGidMap instanceof _primitives.Name) {
+          hash.update(cidToGidMap.name);
+        } else if (cidToGidMap instanceof _primitives.Ref) {
+          hash.update(cidToGidMap.toString());
+        } else if (cidToGidMap instanceof _base_stream.BaseStream) {
+          hash.update(cidToGidMap.peekBytes());
+        }
       }
     }
 

+ 48 - 0
lib/core/fonts.js

@@ -1195,6 +1195,54 @@ class Font {
         }
 
         hasShortCmap = true;
+      } else if (format === 2) {
+        const subHeaderKeys = [];
+        let maxSubHeaderKey = 0;
+
+        for (let i = 0; i < 256; i++) {
+          const subHeaderKey = file.getUint16() >> 3;
+          subHeaderKeys.push(subHeaderKey);
+          maxSubHeaderKey = Math.max(subHeaderKey, maxSubHeaderKey);
+        }
+
+        const subHeaders = [];
+
+        for (let i = 0; i <= maxSubHeaderKey; i++) {
+          subHeaders.push({
+            firstCode: file.getUint16(),
+            entryCount: file.getUint16(),
+            idDelta: signedInt16(file.getByte(), file.getByte()),
+            idRangePos: file.pos + file.getUint16()
+          });
+        }
+
+        for (let i = 0; i < 256; i++) {
+          if (subHeaderKeys[i] === 0) {
+            file.pos = subHeaders[0].idRangePos + 2 * i;
+            glyphId = file.getUint16();
+            mappings.push({
+              charCode: i,
+              glyphId
+            });
+          } else {
+            const s = subHeaders[subHeaderKeys[i]];
+
+            for (j = 0; j < s.entryCount; j++) {
+              const charCode = (i << 8) + j + s.firstCode;
+              file.pos = s.idRangePos + 2 * j;
+              glyphId = file.getUint16();
+
+              if (glyphId !== 0) {
+                glyphId = (glyphId + s.idDelta) % 65536;
+              }
+
+              mappings.push({
+                charCode,
+                glyphId
+              });
+            }
+          }
+        }
       } else if (format === 4) {
         const segCount = file.getUint16() >> 1;
         file.skip(6);

+ 1 - 1
lib/core/fonts_utils.js

@@ -24,11 +24,11 @@
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
+exports.SEAC_ANALYSIS_ENABLED = exports.MacStandardGlyphOrdering = exports.FontFlags = void 0;
 exports.getFontType = getFontType;
 exports.normalizeFontName = normalizeFontName;
 exports.recoverGlyphName = recoverGlyphName;
 exports.type1FontGlyphMapping = type1FontGlyphMapping;
-exports.SEAC_ANALYSIS_ENABLED = exports.MacStandardGlyphOrdering = exports.FontFlags = void 0;
 
 var _util = require("../shared/util.js");
 

+ 1 - 1
lib/core/function.js

@@ -24,8 +24,8 @@
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
-exports.isPDFFunction = isPDFFunction;
 exports.PostScriptEvaluator = exports.PostScriptCompiler = exports.PDFFunctionFactory = void 0;
+exports.isPDFFunction = isPDFFunction;
 
 var _primitives = require("./primitives.js");
 

+ 10 - 10
lib/core/image.js

@@ -102,7 +102,7 @@ class PDFImage {
   }) {
     this.image = image;
     const dict = image.dict;
-    const filter = dict.get("Filter");
+    const filter = dict.get("F", "Filter");
 
     if ((0, _primitives.isName)(filter)) {
       switch (filter.name) {
@@ -123,8 +123,8 @@ class PDFImage {
       }
     }
 
-    let width = dict.get("Width", "W");
-    let height = dict.get("Height", "H");
+    let width = dict.get("W", "Width");
+    let height = dict.get("H", "Height");
 
     if (Number.isInteger(image.width) && image.width > 0 && Number.isInteger(image.height) && image.height > 0 && (image.width !== width || image.height !== height)) {
       (0, _util.warn)("PDFImage - using the Width/Height of the image data, " + "rather than the image dictionary.");
@@ -138,13 +138,13 @@ class PDFImage {
 
     this.width = width;
     this.height = height;
-    this.interpolate = dict.get("Interpolate", "I");
-    this.imageMask = dict.get("ImageMask", "IM") || false;
+    this.interpolate = dict.get("I", "Interpolate");
+    this.imageMask = dict.get("IM", "ImageMask") || false;
     this.matte = dict.get("Matte") || false;
     let bitsPerComponent = image.bitsPerComponent;
 
     if (!bitsPerComponent) {
-      bitsPerComponent = dict.get("BitsPerComponent", "BPC");
+      bitsPerComponent = dict.get("BPC", "BitsPerComponent");
 
       if (!bitsPerComponent) {
         if (this.imageMask) {
@@ -158,7 +158,7 @@ class PDFImage {
     this.bpc = bitsPerComponent;
 
     if (!this.imageMask) {
-      let colorSpace = dict.getRaw("ColorSpace") || dict.getRaw("CS");
+      let colorSpace = dict.getRaw("CS") || dict.getRaw("ColorSpace");
 
       if (!colorSpace) {
         (0, _util.info)("JPX images (which do not require color spaces)");
@@ -177,7 +177,7 @@ class PDFImage {
             break;
 
           default:
-            throw new Error(`JPX images with ${image.numComps} ` + "color components not supported.");
+            throw new Error(`JPX images with ${image.numComps} color components not supported.`);
         }
       }
 
@@ -191,7 +191,7 @@ class PDFImage {
       this.numComps = this.colorSpace.numComps;
     }
 
-    this.decode = dict.getArray("Decode", "D");
+    this.decode = dict.getArray("D", "Decode");
     this.needsDecode = false;
 
     if (this.decode && (this.colorSpace && !this.colorSpace.isDefaultDecode(this.decode, bitsPerComponent) || isMask && !_colorspace.ColorSpace.isDefaultDecode(this.decode, 1))) {
@@ -221,7 +221,7 @@ class PDFImage {
     } else if (mask) {
       if ((0, _primitives.isStream)(mask)) {
         const maskDict = mask.dict,
-              imageMask = maskDict.get("ImageMask", "IM");
+              imageMask = maskDict.get("IM", "ImageMask");
 
         if (!imageMask) {
           (0, _util.warn)("Ignoring /Mask in image without /ImageMask.");

+ 2 - 2
lib/core/jpeg_stream.js

@@ -67,10 +67,10 @@ class JpegStream extends _decode_stream.DecodeStream {
       decodeTransform: undefined,
       colorTransform: undefined
     };
-    const decodeArr = this.dict.getArray("Decode", "D");
+    const decodeArr = this.dict.getArray("D", "Decode");
 
     if (this.forceRGB && Array.isArray(decodeArr)) {
-      const bitsPerComponent = this.dict.get("BitsPerComponent") || 8;
+      const bitsPerComponent = this.dict.get("BPC", "BitsPerComponent") || 8;
       const decodeArrLength = decodeArr.length;
       const transform = new Int32Array(decodeArrLength);
       let transformNeeded = false;

+ 1 - 1
lib/core/jpg.js

@@ -1209,7 +1209,7 @@ class JpegImage {
       y = data[i + 2];
       k = data[i + 3];
       data[offset++] = 255 + c * (-0.00006747147073602441 * c + 0.0008379262121013727 * m + 0.0002894718188643294 * y + 0.003264231057537806 * k - 1.1185611867203937) + m * (0.000026374107616089405 * m - 0.00008626949158638572 * y - 0.0002748769067499491 * k - 0.02155688794978967) + y * (-0.00003878099212869363 * y - 0.0003267808279485286 * k + 0.0686742238595345) - k * (0.0003361971776183937 * k + 0.7430659151342254);
-      data[offset++] = 255 + c * (0.00013596372813588848 * c + 0.000924537132573585 * m + 0.00010567359618683593 * y + 0.0004791864687436512 * k - 0.3109689587515875) + m * (-0.00023545346108370344 * m + 0.0002702845253534714 * y + 0.0020200308977307156 * k - 0.7488052167015494) + y * (0.00006834815998235662 * y + 0.00015168452363460973 * k - 0.09751927774728933) - k * (0.00031891311758832814 * k + 0.7364883807733168);
+      data[offset++] = 255 + c * (0.00013596372813588848 * c + 0.000924537132573585 * m + 0.00010567359618683593 * y + 0.0004791864687436512 * k - 0.3109689587515875) + m * (-0.00023545346108370344 * m + 0.0002702845253534714 * y + 0.0020200308977307156 * k - 0.7488052167015494) + y * (0.00006834815998235662 * y + 0.00015168452363460973 * k - 0.09751927774728933) - k * (0.0003189131175883281 * k + 0.7364883807733168);
       data[offset++] = 255 + c * (0.000013598650411385307 * c + 0.00012423956175490851 * m + 0.0004751985097583589 * y - 0.0000036729317476630422 * k - 0.05562186980264034) + m * (0.00016141380598724676 * m + 0.0009692239130725186 * y + 0.0007782692450036253 * k - 0.44015232367526463) + y * (5.068882914068769e-7 * y + 0.0017778369011375071 * k - 0.7591454649749609) - k * (0.0003435319965105553 * k + 0.7063770186160144);
     }
 

+ 77 - 68
lib/core/parser.js

@@ -483,7 +483,7 @@ class Parser {
       dictLength = stream.pos - lexer.beginInlineImagePos;
     }
 
-    const filter = dict.get("Filter", "F");
+    const filter = dict.get("F", "Filter");
     let filterName;
 
     if ((0, _primitives.isName)(filter)) {
@@ -499,14 +499,24 @@ class Parser {
     const startPos = stream.pos;
     let length;
 
-    if (filterName === "DCTDecode" || filterName === "DCT") {
-      length = this.findDCTDecodeInlineStreamEnd(stream);
-    } else if (filterName === "ASCII85Decode" || filterName === "A85") {
-      length = this.findASCII85DecodeInlineStreamEnd(stream);
-    } else if (filterName === "ASCIIHexDecode" || filterName === "AHx") {
-      length = this.findASCIIHexDecodeInlineStreamEnd(stream);
-    } else {
-      length = this.findDefaultInlineStreamEnd(stream);
+    switch (filterName) {
+      case "DCT":
+      case "DCTDecode":
+        length = this.findDCTDecodeInlineStreamEnd(stream);
+        break;
+
+      case "A85":
+      case "ASCII85Decode":
+        length = this.findASCII85DecodeInlineStreamEnd(stream);
+        break;
+
+      case "AHx":
+      case "ASCIIHexDecode":
+        length = this.findASCIIHexDecodeInlineStreamEnd(stream);
+        break;
+
+      default:
+        length = this.findDefaultInlineStreamEnd(stream);
     }
 
     let imageStream = stream.makeSubStream(startPos, length, dict);
@@ -594,7 +604,7 @@ class Parser {
     let length = dict.get("Length");
 
     if (!Number.isInteger(length)) {
-      (0, _util.info)(`Bad length "${length}" in stream`);
+      (0, _util.info)(`Bad length "${length && length.toString()}" in stream.`);
       length = 0;
     }
 
@@ -654,12 +664,12 @@ class Parser {
   }
 
   filter(stream, dict, length) {
-    let filter = dict.get("Filter", "F");
-    let params = dict.get("DecodeParms", "DP");
+    let filter = dict.get("F", "Filter");
+    let params = dict.get("DP", "DecodeParms");
 
     if ((0, _primitives.isName)(filter)) {
       if (Array.isArray(params)) {
-        (0, _util.warn)("/DecodeParms should not contain an Array, " + "when /Filter contains a Name.");
+        (0, _util.warn)("/DecodeParms should not be an Array, when /Filter is a Name.");
       }
 
       return this.makeFilter(stream, filter.name, length, params);
@@ -698,67 +708,68 @@ class Parser {
       return new _stream.NullStream();
     }
 
-    try {
-      const xrefStreamStats = this.xref.stats.streamTypes;
-
-      if (name === "FlateDecode" || name === "Fl") {
-        xrefStreamStats[_util.StreamType.FLATE] = true;
-
-        if (params) {
-          return new _predictor_stream.PredictorStream(new _flate_stream.FlateStream(stream, maybeLength), maybeLength, params);
-        }
+    const xrefStats = this.xref.stats;
 
-        return new _flate_stream.FlateStream(stream, maybeLength);
-      }
-
-      if (name === "LZWDecode" || name === "LZW") {
-        xrefStreamStats[_util.StreamType.LZW] = true;
-        let earlyChange = 1;
+    try {
+      switch (name) {
+        case "Fl":
+        case "FlateDecode":
+          xrefStats.addStreamType(_util.StreamType.FLATE);
 
-        if (params) {
-          if (params.has("EarlyChange")) {
-            earlyChange = params.get("EarlyChange");
+          if (params) {
+            return new _predictor_stream.PredictorStream(new _flate_stream.FlateStream(stream, maybeLength), maybeLength, params);
           }
 
-          return new _predictor_stream.PredictorStream(new _lzw_stream.LZWStream(stream, maybeLength, earlyChange), maybeLength, params);
-        }
-
-        return new _lzw_stream.LZWStream(stream, maybeLength, earlyChange);
-      }
-
-      if (name === "DCTDecode" || name === "DCT") {
-        xrefStreamStats[_util.StreamType.DCT] = true;
-        return new _jpeg_stream.JpegStream(stream, maybeLength, params);
-      }
-
-      if (name === "JPXDecode" || name === "JPX") {
-        xrefStreamStats[_util.StreamType.JPX] = true;
-        return new _jpx_stream.JpxStream(stream, maybeLength, params);
-      }
+          return new _flate_stream.FlateStream(stream, maybeLength);
 
-      if (name === "ASCII85Decode" || name === "A85") {
-        xrefStreamStats[_util.StreamType.A85] = true;
-        return new _ascii_85_stream.Ascii85Stream(stream, maybeLength);
-      }
-
-      if (name === "ASCIIHexDecode" || name === "AHx") {
-        xrefStreamStats[_util.StreamType.AHX] = true;
-        return new _ascii_hex_stream.AsciiHexStream(stream, maybeLength);
-      }
+        case "LZW":
+        case "LZWDecode":
+          xrefStats.addStreamType(_util.StreamType.LZW);
+          let earlyChange = 1;
 
-      if (name === "CCITTFaxDecode" || name === "CCF") {
-        xrefStreamStats[_util.StreamType.CCF] = true;
-        return new _ccitt_stream.CCITTFaxStream(stream, maybeLength, params);
-      }
+          if (params) {
+            if (params.has("EarlyChange")) {
+              earlyChange = params.get("EarlyChange");
+            }
 
-      if (name === "RunLengthDecode" || name === "RL") {
-        xrefStreamStats[_util.StreamType.RLX] = true;
-        return new _run_length_stream.RunLengthStream(stream, maybeLength);
-      }
+            return new _predictor_stream.PredictorStream(new _lzw_stream.LZWStream(stream, maybeLength, earlyChange), maybeLength, params);
+          }
 
-      if (name === "JBIG2Decode") {
-        xrefStreamStats[_util.StreamType.JBIG] = true;
-        return new _jbig2_stream.Jbig2Stream(stream, maybeLength, params);
+          return new _lzw_stream.LZWStream(stream, maybeLength, earlyChange);
+
+        case "DCT":
+        case "DCTDecode":
+          xrefStats.addStreamType(_util.StreamType.DCT);
+          return new _jpeg_stream.JpegStream(stream, maybeLength, params);
+
+        case "JPX":
+        case "JPXDecode":
+          xrefStats.addStreamType(_util.StreamType.JPX);
+          return new _jpx_stream.JpxStream(stream, maybeLength, params);
+
+        case "A85":
+        case "ASCII85Decode":
+          xrefStats.addStreamType(_util.StreamType.A85);
+          return new _ascii_85_stream.Ascii85Stream(stream, maybeLength);
+
+        case "AHx":
+        case "ASCIIHexDecode":
+          xrefStats.addStreamType(_util.StreamType.AHX);
+          return new _ascii_hex_stream.AsciiHexStream(stream, maybeLength);
+
+        case "CCF":
+        case "CCITTFaxDecode":
+          xrefStats.addStreamType(_util.StreamType.CCF);
+          return new _ccitt_stream.CCITTFaxStream(stream, maybeLength, params);
+
+        case "RL":
+        case "RunLengthDecode":
+          xrefStats.addStreamType(_util.StreamType.RLX);
+          return new _run_length_stream.RunLengthStream(stream, maybeLength);
+
+        case "JBIG2Decode":
+          xrefStats.addStreamType(_util.StreamType.JBIG);
+          return new _jbig2_stream.Jbig2Stream(stream, maybeLength, params);
       }
 
       (0, _util.warn)(`Filter "${name}" is not supported.`);
@@ -1069,8 +1080,6 @@ class Lexer {
 
     if (strBuf.length > 127) {
       (0, _util.warn)(`Name token is longer than allowed by the spec: ${strBuf.length}`);
-    } else if (strBuf.length === 0) {
-      (0, _util.warn)("Name token is empty.");
     }
 
     return _primitives.Name.get(strBuf.join(""));

+ 3 - 3
lib/core/pattern.js

@@ -24,8 +24,8 @@
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
-exports.getTilingPatternIR = getTilingPatternIR;
 exports.Pattern = void 0;
+exports.getTilingPatternIR = getTilingPatternIR;
 
 var _util = require("../shared/util.js");
 
@@ -110,7 +110,7 @@ class RadialAxialShading extends BaseShading {
     this.shadingType = dict.get("ShadingType");
 
     const cs = _colorspace.ColorSpace.parse({
-      cs: dict.getRaw("ColorSpace") || dict.getRaw("CS"),
+      cs: dict.getRaw("CS") || dict.getRaw("ColorSpace"),
       xref,
       resources,
       pdfFunctionFactory,
@@ -382,7 +382,7 @@ class MeshShading extends BaseShading {
     }
 
     const cs = _colorspace.ColorSpace.parse({
-      cs: dict.getRaw("ColorSpace") || dict.getRaw("CS"),
+      cs: dict.getRaw("CS") || dict.getRaw("ColorSpace"),
       xref,
       resources,
       pdfFunctionFactory,

+ 2 - 1
lib/core/pdf_manager.js

@@ -136,11 +136,12 @@ class BasePdfManager {
 }
 
 class LocalPdfManager extends BasePdfManager {
-  constructor(docId, data, password, evaluatorOptions, enableXfa, docBaseUrl) {
+  constructor(docId, data, password, msgHandler, evaluatorOptions, enableXfa, docBaseUrl) {
     super();
     this._docId = docId;
     this._password = password;
     this._docBaseUrl = parseDocBaseUrl(docBaseUrl);
+    this.msgHandler = msgHandler;
     this.evaluatorOptions = evaluatorOptions;
     this.enableXfa = enableXfa;
     const stream = new _stream.Stream(data);

+ 1 - 1
lib/core/predictor_stream.js

@@ -59,7 +59,7 @@ class PredictorStream extends _decode_stream.DecodeStream {
     this.str = str;
     this.dict = str.dict;
     const colors = this.colors = params.get("Colors") || 1;
-    const bits = this.bits = params.get("BitsPerComponent") || 8;
+    const bits = this.bits = params.get("BPC", "BitsPerComponent") || 8;
     const columns = this.columns = params.get("Columns") || 1;
     this.pixBytes = colors * bits + 7 >> 3;
     this.rowBytes = columns * colors * bits + 7 >> 3;

+ 3 - 1
lib/core/primitives.js

@@ -24,6 +24,7 @@
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
+exports.RefSetCache = exports.RefSet = exports.Ref = exports.Name = exports.EOF = exports.Dict = exports.Cmd = exports.CIRCULAR_REF = void 0;
 exports.clearPrimitiveCaches = clearPrimitiveCaches;
 exports.isCmd = isCmd;
 exports.isDict = isDict;
@@ -31,12 +32,13 @@ exports.isName = isName;
 exports.isRef = isRef;
 exports.isRefsEqual = isRefsEqual;
 exports.isStream = isStream;
-exports.RefSetCache = exports.RefSet = exports.Ref = exports.Name = exports.EOF = exports.Dict = exports.Cmd = void 0;
 
 var _util = require("../shared/util.js");
 
 var _base_stream = require("./base_stream.js");
 
+const CIRCULAR_REF = Symbol("CIRCULAR_REF");
+exports.CIRCULAR_REF = CIRCULAR_REF;
 const EOF = Symbol("EOF");
 exports.EOF = EOF;
 

+ 2 - 1
lib/core/standard_fonts.js

@@ -24,8 +24,9 @@
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
+exports.getSerifFonts = exports.getNonStdFontMap = exports.getGlyphMapForStandardFonts = exports.getFontNameToFileMap = void 0;
 exports.getStandardFontName = getStandardFontName;
-exports.getSymbolsFonts = exports.getSupplementalGlyphMapForCalibri = exports.getSupplementalGlyphMapForArialBlack = exports.getStdFontMap = exports.getSerifFonts = exports.getNonStdFontMap = exports.getGlyphMapForStandardFonts = exports.getFontNameToFileMap = void 0;
+exports.getSymbolsFonts = exports.getSupplementalGlyphMapForCalibri = exports.getSupplementalGlyphMapForArialBlack = exports.getStdFontMap = void 0;
 
 var _core_utils = require("./core_utils.js");
 

+ 6 - 0
lib/core/struct_tree.js

@@ -339,6 +339,12 @@ class StructTreePage {
         obj.alt = (0, _util.stringToPDFString)(alt);
       }
 
+      const lang = node.dict.get("Lang");
+
+      if ((0, _util.isString)(lang)) {
+        obj.lang = (0, _util.stringToPDFString)(lang);
+      }
+
       for (const kid of node.kids) {
         const kidElement = kid.type === StructElementType.ELEMENT ? kid.parentNode : null;
 

+ 1 - 1
lib/core/unicode.js

@@ -24,11 +24,11 @@
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
+exports.getNormalizedUnicodes = void 0;
 exports.getUnicodeForGlyph = getUnicodeForGlyph;
 exports.getUnicodeRangeFor = getUnicodeRangeFor;
 exports.mapSpecialUnicodeValues = mapSpecialUnicodeValues;
 exports.reverseIfRtl = reverseIfRtl;
-exports.getNormalizedUnicodes = void 0;
 
 var _core_utils = require("./core_utils.js");
 

+ 6 - 23
lib/core/worker.js

@@ -80,17 +80,7 @@ class WorkerMessageHandler {
       }
 
       testMessageProcessed = true;
-
-      if (!(data instanceof Uint8Array)) {
-        handler.send("test", null);
-        return;
-      }
-
-      const supportTransfers = data[0] === 255;
-      handler.postMessageTransfers = supportTransfers;
-      handler.send("test", {
-        supportTransfers
-      });
+      handler.send("test", data instanceof Uint8Array && data[0] === 255);
     });
     handler.on("configure", function wphConfigure(data) {
       (0, _util.setVerbosityLevel)(data.verbosity);
@@ -107,7 +97,7 @@ class WorkerMessageHandler {
     const WorkerTasks = [];
     const verbosity = (0, _util.getVerbosityLevel)();
     const apiVersion = docParams.apiVersion;
-    const workerVersion = '2.11.338';
+    const workerVersion = '2.12.313';
 
     if (apiVersion !== workerVersion) {
       throw new Error(`The API version "${apiVersion}" does not match ` + `the Worker version "${workerVersion}".`);
@@ -131,7 +121,6 @@ class WorkerMessageHandler {
     const docBaseUrl = docParams.docBaseUrl;
     const workerHandlerName = docParams.docId + "_worker";
     let handler = new _message_handler.MessageHandler(workerHandlerName, docId, port);
-    handler.postMessageTransfers = docParams.postMessageTransfers;
 
     function ensureNotTerminated() {
       if (terminated) {
@@ -153,11 +142,8 @@ class WorkerMessageHandler {
       await pdfManager.ensureDoc("checkHeader");
       await pdfManager.ensureDoc("parseStartXRef");
       await pdfManager.ensureDoc("parse", [recoveryMode]);
-
-      if (!recoveryMode) {
-        await pdfManager.ensureDoc("checkFirstPage");
-      }
-
+      await pdfManager.ensureDoc("checkFirstPage", [recoveryMode]);
+      await pdfManager.ensureDoc("checkLastPage", [recoveryMode]);
       const isPureXfa = await pdfManager.ensureDoc("isPureXfa");
 
       if (isPureXfa) {
@@ -182,7 +168,7 @@ class WorkerMessageHandler {
 
       if (source.data) {
         try {
-          newPdfManager = new _pdf_manager.LocalPdfManager(docId, source.data, source.password, evaluatorOptions, enableXfa, docBaseUrl);
+          newPdfManager = new _pdf_manager.LocalPdfManager(docId, source.data, source.password, handler, evaluatorOptions, enableXfa, docBaseUrl);
           pdfManagerCapability.resolve(newPdfManager);
         } catch (ex) {
           pdfManagerCapability.reject(ex);
@@ -237,7 +223,7 @@ class WorkerMessageHandler {
         }
 
         try {
-          newPdfManager = new _pdf_manager.LocalPdfManager(docId, pdfFile, source.password, evaluatorOptions, enableXfa, docBaseUrl);
+          newPdfManager = new _pdf_manager.LocalPdfManager(docId, pdfFile, source.password, handler, evaluatorOptions, enableXfa, docBaseUrl);
           pdfManagerCapability.resolve(newPdfManager);
         } catch (ex) {
           pdfManagerCapability.reject(ex);
@@ -450,9 +436,6 @@ class WorkerMessageHandler {
         return stream.bytes;
       });
     });
-    handler.on("GetStats", function wphSetupGetStats(data) {
-      return pdfManager.ensureXRef("stats");
-    });
     handler.on("GetAnnotations", function ({
       pageIndex,
       intent

+ 5 - 0
lib/core/xfa/bind.js

@@ -571,6 +571,11 @@ class Binder {
           match = dataNode[_xfa_object.$getRealChildrenByNameIt](child.name, false, this.emptyMerge).next().value;
 
           if (!match) {
+            if (min === 0) {
+              uselessNodes.push(child);
+              continue;
+            }
+
             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);
 

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

@@ -76,7 +76,7 @@ class Empty extends _xfa_object.XFAObject {
 }
 
 class Builder {
-  constructor() {
+  constructor(rootNameSpace = null) {
     this._namespaceStack = [];
     this._nsAgnosticLevel = 0;
     this._namespacePrefixes = new Map();
@@ -84,7 +84,7 @@ class Builder {
     this._nextNsId = Math.max(...Object.values(_namespaces.NamespaceIds).map(({
       id
     }) => id));
-    this._currentNamespace = new _unknown.UnknownNamespace(++this._nextNsId);
+    this._currentNamespace = rootNameSpace || new _unknown.UnknownNamespace(++this._nextNsId);
   }
 
   buildRoot(ids) {

+ 78 - 6
lib/core/xfa/factory.js

@@ -40,6 +40,8 @@ var _util = require("../../shared/util.js");
 
 var _parser = require("./parser.js");
 
+var _xhtml = require("./xhtml.js");
+
 class XFAFactory {
   constructor(data) {
     try {
@@ -57,9 +59,31 @@ class XFAFactory {
     return this.root && this.form;
   }
 
-  _createPages() {
+  _createPagesHelper() {
+    const iterator = this.form[_xfa_object.$toPages]();
+
+    return new Promise((resolve, reject) => {
+      const nextIteration = () => {
+        try {
+          const value = iterator.next();
+
+          if (value.done) {
+            resolve(value.value);
+          } else {
+            setTimeout(nextIteration, 0);
+          }
+        } catch (e) {
+          reject(e);
+        }
+      };
+
+      setTimeout(nextIteration, 0);
+    });
+  }
+
+  async _createPages() {
     try {
-      this.pages = this.form[_xfa_object.$toHTML]();
+      this.pages = await this._createPagesHelper();
       this.dims = this.pages.children.map(c => {
         const {
           width,
@@ -76,9 +100,9 @@ class XFAFactory {
     return this.dims[pageIndex];
   }
 
-  get numberPages() {
+  async getNumPages() {
     if (!this.pages) {
-      this._createPages();
+      await this._createPages();
     }
 
     return this.dims.length;
@@ -113,9 +137,9 @@ class XFAFactory {
     this.form[_xfa_object.$globalData].fontFinder.add(fonts, reallyMissingFonts);
   }
 
-  getPages() {
+  async getPages() {
     if (!this.pages) {
-      this._createPages();
+      await this._createPages();
     }
 
     const pages = this.pages;
@@ -135,6 +159,54 @@ class XFAFactory {
     return Object.values(data).join("");
   }
 
+  static getRichTextAsHtml(rc) {
+    if (!rc || typeof rc !== "string") {
+      return null;
+    }
+
+    try {
+      let root = new _parser.XFAParser(_xhtml.XhtmlNamespace, true).parse(rc);
+
+      if (!["body", "xhtml"].includes(root[_xfa_object.$nodeName])) {
+        const newRoot = _xhtml.XhtmlNamespace.body({});
+
+        newRoot[_xfa_object.$appendChild](root);
+
+        root = newRoot;
+      }
+
+      const result = root[_xfa_object.$toHTML]();
+
+      if (!result.success) {
+        return null;
+      }
+
+      const {
+        html
+      } = result;
+      const {
+        attributes
+      } = html;
+
+      if (attributes) {
+        if (attributes.class) {
+          attributes.class = attributes.class.filter(attr => !attr.startsWith("xfa"));
+        }
+
+        attributes.dir = "auto";
+      }
+
+      return {
+        html,
+        str: root[_xfa_object.$text]()
+      };
+    } catch (e) {
+      (0, _util.warn)(`XFA - an error occurred during parsing of rich text: ${e}`);
+    }
+
+    return null;
+  }
+
 }
 
 exports.XFAFactory = XFAFactory;

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

@@ -24,9 +24,9 @@
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
+exports.FontFinder = void 0;
 exports.getMetrics = getMetrics;
 exports.selectFont = selectFont;
-exports.FontFinder = void 0;
 
 var _xfa_object = require("./xfa_object.js");
 

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

@@ -24,7 +24,7 @@
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
-exports.TOKEN = exports.Token = exports.Lexer = void 0;
+exports.Token = exports.TOKEN = exports.Lexer = void 0;
 const KEYWORDS = new Set(["and", "break", "continue", "do", "downto", "else", "elseif", "end", "endfor", "endfunc", "endif", "endwhile", "eq", "exit", "for", "foreach", "func", "ge", "gt", "if", "in", "infinity", "le", "lt", "nan", "ne", "not", "null", "or", "return", "step", "then", "this", "throw", "upto", "var", "while"]);
 const TOKEN = {
   and: 0,

+ 7 - 2
lib/core/xfa/html_utils.js

@@ -283,7 +283,7 @@ function layoutNode(node, availableSpace) {
 
       let parent = node[_xfa_object.$getParent]();
 
-      while (parent !== root) {
+      while (parent && parent !== root) {
         if (parent.font) {
           font = parent.font;
           break;
@@ -655,9 +655,14 @@ function setPara(node, nodeStyle, value) {
 }
 
 function setFontFamily(xfaFont, node, fontFinder, style) {
+  if (!fontFinder) {
+    delete style.fontFamily;
+    return;
+  }
+
   const name = (0, _utils.stripQuotes)(xfaFont.typeface);
-  const typeface = fontFinder.find(name);
   style.fontFamily = `"${name}"`;
+  const typeface = fontFinder.find(name);
 
   if (typeface) {
     const {

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

@@ -137,7 +137,7 @@ function addHTML(node, html, bbox) {
 
     case "tb":
       {
-        extra.width = availableSpace.width;
+        extra.width = Math.min(availableSpace.width, Math.max(extra.width, w));
         extra.height += h;
         extra.children.push(html);
         break;

+ 5 - 4
lib/core/xfa/parser.js

@@ -35,9 +35,9 @@ var _builder = require("./builder.js");
 var _util = require("../../shared/util.js");
 
 class XFAParser extends _xml_parser.XMLParserBase {
-  constructor() {
+  constructor(rootNameSpace = null, richText = false) {
     super();
-    this._builder = new _builder.Builder();
+    this._builder = new _builder.Builder(rootNameSpace);
     this._stack = [];
     this._globalData = {
       usedTypefaces: new Set()
@@ -47,6 +47,7 @@ class XFAParser extends _xml_parser.XMLParserBase {
     this._errorCode = _xml_parser.XMLParserErrorCode.NoError;
     this._whiteRegex = /^\s+$/;
     this._nbsps = /\xa0+/g;
+    this._richText = richText;
   }
 
   parse(data) {
@@ -64,8 +65,8 @@ class XFAParser extends _xml_parser.XMLParserBase {
   onText(text) {
     text = text.replace(this._nbsps, match => match.slice(1) + " ");
 
-    if (this._current[_xfa_object.$acceptWhitespace]()) {
-      this._current[_xfa_object.$onText](text);
+    if (this._richText || this._current[_xfa_object.$acceptWhitespace]()) {
+      this._current[_xfa_object.$onText](text, this._richText);
 
       return;
     }

+ 24 - 10
lib/core/xfa/template.js

@@ -50,7 +50,7 @@ const MAX_ATTEMPTS_FOR_LRTB_LAYOUT = 2;
 const MAX_EMPTY_PAGES = 3;
 const DEFAULT_TAB_INDEX = 5000;
 const HEADING_PATTERN = /^H(\d+)$/;
-const MIMES = new Set(["image/gif", "image/jpeg", "image/jpg", "image/pjpeg", "image/png", "image/apng", "image/x-png", "image/bmp", "image/x-ms-bmp", "image/tiff", "image/tif"]);
+const MIMES = new Set(["image/gif", "image/jpeg", "image/jpg", "image/pjpeg", "image/png", "image/apng", "image/x-png", "image/bmp", "image/x-ms-bmp", "image/tiff", "image/tif", "application/octet-stream"]);
 const IMAGES_HEADERS = [[[0x42, 0x4d], "image/bmp"], [[0xff, 0xd8, 0xff], "image/jpeg"], [[0x49, 0x49, 0x2a, 0x00], "image/tiff"], [[0x4d, 0x4d, 0x00, 0x2a], "image/tiff"], [[0x47, 0x49, 0x46, 0x38, 0x39, 0x61], "image/gif"], [[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a], "image/png"]];
 
 function getBorderDims(node) {
@@ -285,22 +285,31 @@ function handleBreak(node) {
   const pageArea = target && target[_xfa_object.$getParent]();
 
   let index;
+  let nextPageArea = pageArea;
 
   if (node.startNew) {
     if (target) {
       const contentAreas = pageArea.contentArea.children;
-      index = contentAreas.findIndex(e => e === target) - 1;
+      const indexForCurrent = contentAreas.indexOf(currentContentArea);
+      const indexForTarget = contentAreas.indexOf(target);
+
+      if (indexForCurrent !== -1 && indexForCurrent < indexForTarget) {
+        nextPageArea = null;
+      }
+
+      index = indexForTarget - 1;
     } else {
-      index = currentPageArea.contentArea.children.findIndex(e => e === currentContentArea);
+      index = currentPageArea.contentArea.children.indexOf(currentContentArea);
     }
   } else if (target && target !== currentContentArea) {
     const contentAreas = pageArea.contentArea.children;
-    index = contentAreas.findIndex(e => e === target) - 1;
+    index = contentAreas.indexOf(target) - 1;
+    nextPageArea = pageArea === currentPageArea ? null : pageArea;
   } else {
     return false;
   }
 
-  node[_xfa_object.$extra].target = pageArea === currentPageArea ? null : pageArea;
+  node[_xfa_object.$extra].target = nextPageArea;
   node[_xfa_object.$extra].index = index;
   return true;
 }
@@ -385,7 +394,7 @@ class Arc extends _xfa_object.XFAObject {
       }
     };
 
-    if (this.startAngle === 0 && this.sweepAngle === 360) {
+    if (this.sweepAngle === 360) {
       arc = {
         name: "ellipse",
         attributes: {
@@ -400,8 +409,8 @@ class Arc extends _xfa_object.XFAObject {
     } else {
       const startAngle = this.startAngle * Math.PI / 180;
       const sweepAngle = this.sweepAngle * Math.PI / 180;
-      const largeArc = this.sweepAngle - this.startAngle > 180 ? 1 : 0;
-      const [x1, y1, x2, y2] = [50 * (1 + Math.cos(startAngle)), 50 * (1 - Math.sin(startAngle)), 50 * (1 + Math.cos(sweepAngle)), 50 * (1 - Math.sin(sweepAngle))];
+      const largeArc = this.sweepAngle > 180 ? 1 : 0;
+      const [x1, y1, x2, y2] = [50 * (1 + Math.cos(startAngle)), 50 * (1 - Math.sin(startAngle)), 50 * (1 + Math.cos(startAngle + sweepAngle)), 50 * (1 - Math.sin(startAngle + sweepAngle))];
       arc = {
         name: "path",
         attributes: {
@@ -4601,6 +4610,10 @@ class Subform extends _xfa_object.XFAObject {
       style.height = (0, _html_utils.measureToString)(height);
     }
 
+    if ((style.width === "0px" || style.height === "0px") && children.length === 0) {
+      return _utils.HTMLResult.EMPTY;
+    }
+
     const html = {
       name: "div",
       attributes,
@@ -4756,7 +4769,7 @@ class Template extends _xfa_object.XFAObject {
     return (0, _som.searchNode)(this, container, expr, true, true);
   }
 
-  [_xfa_object.$toHTML]() {
+  *[_xfa_object.$toPages]() {
     if (!this.subform.children.length) {
       return _utils.HTMLResult.success({
         name: "div",
@@ -4904,7 +4917,7 @@ class Template extends _xfa_object.XFAObject {
           if (html.html) {
             hasSomething = hasSomething || html.html.children && html.html.children.length !== 0;
             htmlContentAreas[i].children.push(html.html);
-          } else if (!hasSomething) {
+          } else if (!hasSomething && mainHtml.children.length > 1) {
             mainHtml.children.pop();
           }
 
@@ -4990,6 +5003,7 @@ class Template extends _xfa_object.XFAObject {
       }
 
       pageArea = targetPageArea || pageArea[_xfa_object.$getNextPage]();
+      yield null;
     }
   }
 

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

@@ -24,6 +24,7 @@
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
+exports.HTMLResult = void 0;
 exports.getBBox = getBBox;
 exports.getColor = getColor;
 exports.getFloat = getFloat;
@@ -34,7 +35,6 @@ exports.getRatio = getRatio;
 exports.getRelevant = getRelevant;
 exports.getStringOption = getStringOption;
 exports.stripQuotes = stripQuotes;
-exports.HTMLResult = void 0;
 
 var _util = require("../../shared/util.js");
 

+ 8 - 4
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.$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;
+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.$toPages = 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");
 
@@ -161,6 +161,8 @@ const $tabIndex = Symbol();
 exports.$tabIndex = $tabIndex;
 const $text = Symbol();
 exports.$text = $text;
+const $toPages = Symbol();
+exports.$toPages = $toPages;
 const $toHTML = Symbol();
 exports.$toHTML = $toHTML;
 const $toString = Symbol();
@@ -921,11 +923,13 @@ class XmlObject extends XFAObject {
       return;
     }
 
+    const utf8TagName = (0, _util.utf8StringToString)(tagName);
     const prefix = this[$namespaceId] === NS_DATASETS ? "xfa:" : "";
-    buf.push(`<${prefix}${tagName}`);
+    buf.push(`<${prefix}${utf8TagName}`);
 
     for (const [name, value] of this[_attributes].entries()) {
-      buf.push(` ${name}="${(0, _core_utils.encodeToXmlString)(value[$content])}"`);
+      const utf8Name = (0, _util.utf8StringToString)(name);
+      buf.push(` ${utf8Name}="${(0, _core_utils.encodeToXmlString)(value[$content])}"`);
     }
 
     if (this[_dataValue] !== null) {
@@ -955,7 +959,7 @@ class XmlObject extends XFAObject {
       }
     }
 
-    buf.push(`</${prefix}${tagName}>`);
+    buf.push(`</${prefix}${utf8TagName}>`);
   }
 
   [$onChild](child) {

+ 37 - 8
lib/core/xfa/xhtml.js

@@ -35,15 +35,17 @@ var _html_utils = require("./html_utils.js");
 var _utils = require("./utils.js");
 
 const XHTML_NS_ID = _namespaces.NamespaceIds.xhtml.id;
+const $richText = Symbol();
 const VALID_STYLES = new Set(["color", "font", "font-family", "font-size", "font-stretch", "font-style", "font-weight", "margin", "margin-bottom", "margin-left", "margin-right", "margin-top", "letter-spacing", "line-height", "orphans", "page-break-after", "page-break-before", "page-break-inside", "tab-interval", "tab-stop", "text-align", "text-decoration", "text-indent", "vertical-align", "widows", "kerning-mode", "xfa-font-horizontal-scale", "xfa-font-vertical-scale", "xfa-spacerun", "xfa-tab-stops"]);
 const StyleMapping = new Map([["page-break-after", "breakAfter"], ["page-break-before", "breakBefore"], ["page-break-inside", "breakInside"], ["kerning-mode", value => value === "none" ? "none" : "normal"], ["xfa-font-horizontal-scale", value => `scaleX(${Math.max(0, Math.min(parseInt(value) / 100)).toFixed(2)})`], ["xfa-font-vertical-scale", value => `scaleY(${Math.max(0, Math.min(parseInt(value) / 100)).toFixed(2)})`], ["xfa-spacerun", ""], ["xfa-tab-stops", ""], ["font-size", (value, original) => {
   value = original.fontSize = (0, _utils.getMeasurement)(value);
   return (0, _html_utils.measureToString)(0.99 * value);
-}], ["letter-spacing", value => (0, _html_utils.measureToString)((0, _utils.getMeasurement)(value))], ["line-height", value => (0, _html_utils.measureToString)((0, _utils.getMeasurement)(value))], ["margin", value => (0, _html_utils.measureToString)((0, _utils.getMeasurement)(value))], ["margin-bottom", value => (0, _html_utils.measureToString)((0, _utils.getMeasurement)(value))], ["margin-left", value => (0, _html_utils.measureToString)((0, _utils.getMeasurement)(value))], ["margin-right", value => (0, _html_utils.measureToString)((0, _utils.getMeasurement)(value))], ["margin-top", value => (0, _html_utils.measureToString)((0, _utils.getMeasurement)(value))], ["text-indent", value => (0, _html_utils.measureToString)((0, _utils.getMeasurement)(value))], ["font-family", value => value]]);
+}], ["letter-spacing", value => (0, _html_utils.measureToString)((0, _utils.getMeasurement)(value))], ["line-height", value => (0, _html_utils.measureToString)((0, _utils.getMeasurement)(value))], ["margin", value => (0, _html_utils.measureToString)((0, _utils.getMeasurement)(value))], ["margin-bottom", value => (0, _html_utils.measureToString)((0, _utils.getMeasurement)(value))], ["margin-left", value => (0, _html_utils.measureToString)((0, _utils.getMeasurement)(value))], ["margin-right", value => (0, _html_utils.measureToString)((0, _utils.getMeasurement)(value))], ["margin-top", value => (0, _html_utils.measureToString)((0, _utils.getMeasurement)(value))], ["text-indent", value => (0, _html_utils.measureToString)((0, _utils.getMeasurement)(value))], ["font-family", value => value], ["vertical-align", value => (0, _html_utils.measureToString)((0, _utils.getMeasurement)(value))]]);
 const spacesRegExp = /\s+/g;
 const crlfRegExp = /[\r\n]+/g;
+const crlfForRichTextRegExp = /\r\n?/g;
 
-function mapStyle(styleStr, node) {
+function mapStyle(styleStr, node, richText) {
   const style = Object.create(null);
 
   if (!styleStr) {
@@ -89,6 +91,14 @@ function mapStyle(styleStr, node) {
     }, node, node[_xfa_object.$globalData].fontFinder, style);
   }
 
+  if (richText && style.verticalAlign && style.verticalAlign !== "0px" && style.fontSize) {
+    const SUB_SUPER_SCRIPT_FACTOR = 0.583;
+    const VERTICAL_FACTOR = 0.333;
+    const fontSize = (0, _utils.getMeasurement)(style.fontSize);
+    style.fontSize = (0, _html_utils.measureToString)(fontSize * SUB_SUPER_SCRIPT_FACTOR);
+    style.verticalAlign = (0, _html_utils.measureToString)(Math.sign((0, _utils.getMeasurement)(style.verticalAlign)) * fontSize * VERTICAL_FACTOR);
+  }
+
   (0, _html_utils.fixTextIndent)(style);
   return style;
 }
@@ -112,6 +122,7 @@ const NoWhites = new Set(["body", "html"]);
 class XhtmlObject extends _xfa_object.XmlObject {
   constructor(attributes, name) {
     super(XHTML_NS_ID, name);
+    this[$richText] = false;
     this.style = attributes.style || "";
   }
 
@@ -125,11 +136,15 @@ class XhtmlObject extends _xfa_object.XmlObject {
     return !NoWhites.has(this[_xfa_object.$nodeName]);
   }
 
-  [_xfa_object.$onText](str) {
-    str = str.replace(crlfRegExp, "");
+  [_xfa_object.$onText](str, richText = false) {
+    if (!richText) {
+      str = str.replace(crlfRegExp, "");
 
-    if (!this.style.includes("xfa-spacerun:yes")) {
-      str = str.replace(spacesRegExp, " ");
+      if (!this.style.includes("xfa-spacerun:yes")) {
+        str = str.replace(spacesRegExp, " ");
+      }
+    } else {
+      this[$richText] = true;
     }
 
     if (str) {
@@ -252,14 +267,22 @@ class XhtmlObject extends _xfa_object.XmlObject {
       return _utils.HTMLResult.EMPTY;
     }
 
+    let value;
+
+    if (this[$richText]) {
+      value = this[_xfa_object.$content] ? this[_xfa_object.$content].replace(crlfForRichTextRegExp, "\n") : undefined;
+    } else {
+      value = this[_xfa_object.$content] || undefined;
+    }
+
     return _utils.HTMLResult.success({
       name: this[_xfa_object.$nodeName],
       attributes: {
         href: this.href,
-        style: mapStyle(this.style, this)
+        style: mapStyle(this.style, this, this[$richText])
       },
       children,
-      value: this[_xfa_object.$content] || ""
+      value
     });
   }
 
@@ -423,6 +446,12 @@ class P extends XhtmlObject {
   }
 
   [_xfa_object.$text]() {
+    const siblings = this[_xfa_object.$getParent]()[_xfa_object.$getChildren]();
+
+    if (siblings[siblings.length - 1] === this) {
+      return super[_xfa_object.$text]();
+    }
+
     return super[_xfa_object.$text]() + "\n";
   }
 

+ 91 - 40
lib/core/xref.js

@@ -30,9 +30,11 @@ var _util = require("../shared/util.js");
 
 var _primitives = require("./primitives.js");
 
+var _core_utils = require("./core_utils.js");
+
 var _parser = require("./parser.js");
 
-var _core_utils = require("./core_utils.js");
+var _base_stream = require("./base_stream.js");
 
 var _crypto = require("./crypto.js");
 
@@ -43,10 +45,8 @@ class XRef {
     this.entries = [];
     this.xrefstms = Object.create(null);
     this._cacheMap = new Map();
-    this.stats = {
-      streamTypes: Object.create(null),
-      fontTypes: Object.create(null)
-    };
+    this._pendingRefs = new _primitives.RefSet();
+    this.stats = new _core_utils.DocStats(pdfManager.msgHandler);
     this._newRefNum = null;
   }
 
@@ -90,7 +90,7 @@ class XRef {
       (0, _util.warn)(`XRef.parse - Invalid "Encrypt" reference: "${ex}".`);
     }
 
-    if ((0, _primitives.isDict)(encrypt)) {
+    if (encrypt instanceof _primitives.Dict) {
       const ids = trailerDict.get("ID");
       const fileId = ids && ids.length ? ids[0] : "";
       encrypt.suppressEncryption = true;
@@ -109,15 +109,28 @@ class XRef {
       (0, _util.warn)(`XRef.parse - Invalid "Root" reference: "${ex}".`);
     }
 
-    if ((0, _primitives.isDict)(root) && root.has("Pages")) {
-      this.root = root;
-    } else {
-      if (!recoveryMode) {
-        throw new _core_utils.XRefParseException();
+    if (root instanceof _primitives.Dict) {
+      try {
+        const pages = root.get("Pages");
+
+        if (pages instanceof _primitives.Dict) {
+          this.root = root;
+          return;
+        }
+      } catch (ex) {
+        if (ex instanceof _core_utils.MissingDataException) {
+          throw ex;
+        }
+
+        (0, _util.warn)(`XRef.parse - Invalid "Pages" reference: "${ex}".`);
       }
+    }
 
-      throw new _util.FormatError("Invalid root reference");
+    if (!recoveryMode) {
+      throw new _core_utils.XRefParseException();
     }
+
+    throw new _util.InvalidPDFException("Invalid Root reference.");
   }
 
   processXRefTable(parser) {
@@ -138,11 +151,11 @@ class XRef {
 
     let dict = parser.getObj();
 
-    if (!(0, _primitives.isDict)(dict) && dict.dict) {
+    if (!(dict instanceof _primitives.Dict) && dict.dict) {
       dict = dict.dict;
     }
 
-    if (!(0, _primitives.isDict)(dict)) {
+    if (!(dict instanceof _primitives.Dict)) {
       throw new _util.FormatError("Invalid XRef table: could not parse trailer dictionary");
     }
 
@@ -249,18 +262,13 @@ class XRef {
   }
 
   readXRefStream(stream) {
-    let i, j;
     const streamState = this.streamState;
     stream.pos = streamState.streamPos;
-    const byteWidths = streamState.byteWidths;
-    const typeFieldWidth = byteWidths[0];
-    const offsetFieldWidth = byteWidths[1];
-    const generationFieldWidth = byteWidths[2];
+    const [typeFieldWidth, offsetFieldWidth, generationFieldWidth] = streamState.byteWidths;
     const entryRanges = streamState.entryRanges;
 
     while (entryRanges.length > 0) {
-      const first = entryRanges[0];
-      const n = entryRanges[1];
+      const [first, n] = entryRanges;
 
       if (!Number.isInteger(first) || !Number.isInteger(n)) {
         throw new _util.FormatError(`Invalid XRef range fields: ${first}, ${n}`);
@@ -270,27 +278,45 @@ class XRef {
         throw new _util.FormatError(`Invalid XRef entry fields length: ${first}, ${n}`);
       }
 
-      for (i = streamState.entryNum; i < n; ++i) {
+      for (let i = streamState.entryNum; i < n; ++i) {
         streamState.entryNum = i;
         streamState.streamPos = stream.pos;
         let type = 0,
             offset = 0,
             generation = 0;
 
-        for (j = 0; j < typeFieldWidth; ++j) {
-          type = type << 8 | stream.getByte();
+        for (let j = 0; j < typeFieldWidth; ++j) {
+          const typeByte = stream.getByte();
+
+          if (typeByte === -1) {
+            throw new _util.FormatError("Invalid XRef byteWidths 'type'.");
+          }
+
+          type = type << 8 | typeByte;
         }
 
         if (typeFieldWidth === 0) {
           type = 1;
         }
 
-        for (j = 0; j < offsetFieldWidth; ++j) {
-          offset = offset << 8 | stream.getByte();
+        for (let j = 0; j < offsetFieldWidth; ++j) {
+          const offsetByte = stream.getByte();
+
+          if (offsetByte === -1) {
+            throw new _util.FormatError("Invalid XRef byteWidths 'offset'.");
+          }
+
+          offset = offset << 8 | offsetByte;
         }
 
-        for (j = 0; j < generationFieldWidth; ++j) {
-          generation = generation << 8 | stream.getByte();
+        for (let j = 0; j < generationFieldWidth; ++j) {
+          const generationByte = stream.getByte();
+
+          if (generationByte === -1) {
+            throw new _util.FormatError("Invalid XRef byteWidths 'generation'.");
+          }
+
+          generation = generation << 8 | generationByte;
         }
 
         const entry = {};
@@ -380,6 +406,9 @@ class XRef {
     const objBytes = new Uint8Array([111, 98, 106]);
     const xrefBytes = new Uint8Array([47, 88, 82, 101, 102]);
     this.entries.length = 0;
+
+    this._cacheMap.clear();
+
     const stream = this.stream;
     stream.pos = 0;
     const buffer = stream.getBytes(),
@@ -511,7 +540,7 @@ class XRef {
 
       const dict = parser.getObj();
 
-      if (!(0, _primitives.isDict)(dict)) {
+      if (!(dict instanceof _primitives.Dict)) {
         continue;
       }
 
@@ -548,6 +577,10 @@ class XRef {
       return trailerDict;
     }
 
+    if (this.topDict) {
+      return this.topDict;
+    }
+
     throw new _util.InvalidPDFException("Invalid PDF structure.");
   }
 
@@ -593,7 +626,7 @@ class XRef {
             }
           }
         } else if (Number.isInteger(obj)) {
-          if (!Number.isInteger(parser.getObj()) || !(0, _primitives.isCmd)(parser.getObj(), "obj") || !(0, _primitives.isStream)(obj = parser.getObj())) {
+          if (!Number.isInteger(parser.getObj()) || !(0, _primitives.isCmd)(parser.getObj(), "obj") || !((obj = parser.getObj()) instanceof _base_stream.BaseStream)) {
             throw new _util.FormatError("Invalid XRef stream");
           }
 
@@ -614,7 +647,7 @@ class XRef {
 
         if (Number.isInteger(obj)) {
           this.startXRefQueue.push(obj);
-        } else if ((0, _primitives.isRef)(obj)) {
+        } else if (obj instanceof _primitives.Ref) {
           this.startXRefQueue.push(obj.num);
         }
 
@@ -628,6 +661,7 @@ class XRef {
       }
 
       (0, _util.info)("(while reading XRef): " + e);
+      this.startXRefQueue.shift();
     }
 
     if (recoveryMode) {
@@ -680,15 +714,32 @@ class XRef {
       return xrefEntry;
     }
 
-    if (xrefEntry.uncompressed) {
-      xrefEntry = this.fetchUncompressed(ref, xrefEntry, suppressEncryption);
-    } else {
-      xrefEntry = this.fetchCompressed(ref, xrefEntry, suppressEncryption);
+    if (this._pendingRefs.has(ref)) {
+      this._pendingRefs.remove(ref);
+
+      (0, _util.warn)(`Ignoring circular reference: ${ref}.`);
+      return _primitives.CIRCULAR_REF;
+    }
+
+    this._pendingRefs.put(ref);
+
+    try {
+      if (xrefEntry.uncompressed) {
+        xrefEntry = this.fetchUncompressed(ref, xrefEntry, suppressEncryption);
+      } else {
+        xrefEntry = this.fetchCompressed(ref, xrefEntry, suppressEncryption);
+      }
+
+      this._pendingRefs.remove(ref);
+    } catch (ex) {
+      this._pendingRefs.remove(ref);
+
+      throw ex;
     }
 
-    if ((0, _primitives.isDict)(xrefEntry)) {
+    if (xrefEntry instanceof _primitives.Dict) {
       xrefEntry.objId = ref.toString();
-    } else if ((0, _primitives.isStream)(xrefEntry)) {
+    } else if (xrefEntry instanceof _base_stream.BaseStream) {
       xrefEntry.dict.objId = ref.toString();
     }
 
@@ -735,7 +786,7 @@ class XRef {
       xrefEntry = parser.getObj();
     }
 
-    if (!(0, _primitives.isStream)(xrefEntry)) {
+    if (!(xrefEntry instanceof _base_stream.BaseStream)) {
       this._cacheMap.set(num, xrefEntry);
     }
 
@@ -746,7 +797,7 @@ class XRef {
     const tableOffset = xrefEntry.offset;
     const stream = this.fetch(_primitives.Ref.get(tableOffset, 0));
 
-    if (!(0, _primitives.isStream)(stream)) {
+    if (!(stream instanceof _base_stream.BaseStream)) {
       throw new _util.FormatError("bad ObjStm stream");
     }
 
@@ -800,7 +851,7 @@ class XRef {
       const obj = parser.getObj();
       entries[i] = obj;
 
-      if ((0, _primitives.isStream)(obj)) {
+      if (obj instanceof _base_stream.BaseStream) {
         continue;
       }
 

+ 147 - 36
lib/display/annotation_layer.js

@@ -34,6 +34,8 @@ var _annotation_storage = require("./annotation_storage.js");
 
 var _scripting_utils = require("../shared/scripting_utils.js");
 
+var _xfa_layer = require("./xfa_layer.js");
+
 const DEFAULT_TAB_INDEX = 1000;
 const GetElementsByNameSet = new WeakSet();
 
@@ -164,7 +166,25 @@ class AnnotationElement {
 
     const rect = _util.Util.normalizeRect([data.rect[0], page.view[3] - data.rect[1] + page.view[1], data.rect[2], page.view[3] - data.rect[3] + page.view[1]]);
 
-    container.style.transform = `matrix(${viewport.transform.join(",")})`;
+    if (data.hasOwnCanvas) {
+      const transform = viewport.transform.slice();
+
+      const [scaleX, scaleY] = _util.Util.singularValueDecompose2dScale(transform);
+
+      width = Math.ceil(width * scaleX);
+      height = Math.ceil(height * scaleY);
+      rect[0] *= scaleX;
+      rect[1] *= scaleY;
+
+      for (let i = 0; i < 4; i++) {
+        transform[i] = Math.sign(transform[i]);
+      }
+
+      container.style.transform = `matrix(${transform.join(",")})`;
+    } else {
+      container.style.transform = `matrix(${viewport.transform.join(",")})`;
+    }
+
     container.style.transformOrigin = `${-rect[0]}px ${-rect[1]}px`;
 
     if (!ignoreBorder && data.borderStyle.width > 0) {
@@ -219,8 +239,14 @@ class AnnotationElement {
 
     container.style.left = `${rect[0]}px`;
     container.style.top = `${rect[1]}px`;
-    container.style.width = `${width}px`;
-    container.style.height = `${height}px`;
+
+    if (data.hasOwnCanvas) {
+      container.style.width = container.style.height = "auto";
+    } else {
+      container.style.width = `${width}px`;
+      container.style.height = `${height}px`;
+    }
+
     return container;
   }
 
@@ -263,6 +289,7 @@ class AnnotationElement {
       titleObj: data.titleObj,
       modificationDate: data.modificationDate,
       contentsObj: data.contentsObj,
+      richText: data.richText,
       hideWrapper: true
     });
     const popup = popupElement.render();
@@ -356,10 +383,11 @@ class AnnotationElement {
 }
 
 class LinkAnnotationElement extends AnnotationElement {
-  constructor(parameters) {
+  constructor(parameters, options = null) {
     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,
+      ignoreBorder: !!options?.ignoreBorder,
       createQuadrilaterals: true
     });
   }
@@ -596,7 +624,7 @@ class LinkAnnotationElement extends AnnotationElement {
 
 class TextAnnotationElement extends AnnotationElement {
   constructor(parameters) {
-    const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str);
+    const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str || parameters.data.richText?.str);
     super(parameters, {
       isRenderable
     });
@@ -1203,6 +1231,12 @@ class RadioButtonWidgetAnnotationElement extends WidgetAnnotationElement {
 }
 
 class PushButtonWidgetAnnotationElement extends LinkAnnotationElement {
+  constructor(parameters) {
+    super(parameters, {
+      ignoreBorder: parameters.data.hasAppearance
+    });
+  }
+
   render() {
     const container = super.render();
     container.className = "buttonWidgetAnnotation pushButton";
@@ -1456,7 +1490,7 @@ class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
 
 class PopupAnnotationElement extends AnnotationElement {
   constructor(parameters) {
-    const isRenderable = !!(parameters.data.titleObj?.str || parameters.data.contentsObj?.str);
+    const isRenderable = !!(parameters.data.titleObj?.str || parameters.data.contentsObj?.str || parameters.data.richText?.str);
     super(parameters, {
       isRenderable
     });
@@ -1483,7 +1517,8 @@ class PopupAnnotationElement extends AnnotationElement {
       color: this.data.color,
       titleObj: this.data.titleObj,
       modificationDate: this.data.modificationDate,
-      contentsObj: this.data.contentsObj
+      contentsObj: this.data.contentsObj,
+      richText: this.data.richText
     });
     const page = this.page;
 
@@ -1508,6 +1543,7 @@ class PopupElement {
     this.titleObj = parameters.titleObj;
     this.modificationDate = parameters.modificationDate;
     this.contentsObj = parameters.contentsObj;
+    this.richText = parameters.richText;
     this.hideWrapper = parameters.hideWrapper || false;
     this.pinned = false;
   }
@@ -1538,6 +1574,7 @@ class PopupElement {
 
     if (dateObject) {
       const modificationDate = document.createElement("span");
+      modificationDate.className = "popupDate";
       modificationDate.textContent = "{{date}}, {{time}}";
       modificationDate.dataset.l10nId = "annotation_date_string";
       modificationDate.dataset.l10nArgs = JSON.stringify({
@@ -1547,9 +1584,19 @@ class PopupElement {
       popup.appendChild(modificationDate);
     }
 
-    const contents = this._formatContents(this.contentsObj);
+    if (this.richText?.str && (!this.contentsObj?.str || this.contentsObj.str === this.richText.str)) {
+      _xfa_layer.XfaLayer.render({
+        xfaHtml: this.richText.html,
+        intent: "richText",
+        div: popup
+      });
 
-    popup.appendChild(contents);
+      popup.lastChild.className = "richText popupContent";
+    } else {
+      const contents = this._formatContents(this.contentsObj);
+
+      popup.appendChild(contents);
+    }
 
     if (!Array.isArray(this.trigger)) {
       this.trigger = [this.trigger];
@@ -1571,6 +1618,7 @@ class PopupElement {
     dir
   }) {
     const p = document.createElement("p");
+    p.className = "popupContent";
     p.dir = dir;
     const lines = str.split(/(?:\r\n?|\n)/);
 
@@ -1620,7 +1668,7 @@ class PopupElement {
 
 class FreeTextAnnotationElement extends AnnotationElement {
   constructor(parameters) {
-    const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str);
+    const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str || parameters.data.richText?.str);
     super(parameters, {
       isRenderable,
       ignoreBorder: true
@@ -1641,7 +1689,7 @@ class FreeTextAnnotationElement extends AnnotationElement {
 
 class LineAnnotationElement extends AnnotationElement {
   constructor(parameters) {
-    const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str);
+    const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str || parameters.data.richText?.str);
     super(parameters, {
       isRenderable,
       ignoreBorder: true
@@ -1661,6 +1709,7 @@ class LineAnnotationElement extends AnnotationElement {
     line.setAttribute("y2", data.rect[3] - data.lineCoordinates[3]);
     line.setAttribute("stroke-width", data.borderStyle.width || 1);
     line.setAttribute("stroke", "transparent");
+    line.setAttribute("fill", "transparent");
     svg.appendChild(line);
     this.container.append(svg);
 
@@ -1673,7 +1722,7 @@ class LineAnnotationElement extends AnnotationElement {
 
 class SquareAnnotationElement extends AnnotationElement {
   constructor(parameters) {
-    const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str);
+    const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str || parameters.data.richText?.str);
     super(parameters, {
       isRenderable,
       ignoreBorder: true
@@ -1694,7 +1743,7 @@ class SquareAnnotationElement extends AnnotationElement {
     square.setAttribute("height", height - borderWidth);
     square.setAttribute("stroke-width", borderWidth || 1);
     square.setAttribute("stroke", "transparent");
-    square.setAttribute("fill", "none");
+    square.setAttribute("fill", "transparent");
     svg.appendChild(square);
     this.container.append(svg);
 
@@ -1707,7 +1756,7 @@ class SquareAnnotationElement extends AnnotationElement {
 
 class CircleAnnotationElement extends AnnotationElement {
   constructor(parameters) {
-    const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str);
+    const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str || parameters.data.richText?.str);
     super(parameters, {
       isRenderable,
       ignoreBorder: true
@@ -1728,7 +1777,7 @@ class CircleAnnotationElement extends AnnotationElement {
     circle.setAttribute("ry", height / 2 - borderWidth / 2);
     circle.setAttribute("stroke-width", borderWidth || 1);
     circle.setAttribute("stroke", "transparent");
-    circle.setAttribute("fill", "none");
+    circle.setAttribute("fill", "transparent");
     svg.appendChild(circle);
     this.container.append(svg);
 
@@ -1741,7 +1790,7 @@ class CircleAnnotationElement extends AnnotationElement {
 
 class PolylineAnnotationElement extends AnnotationElement {
   constructor(parameters) {
-    const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str);
+    const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str || parameters.data.richText?.str);
     super(parameters, {
       isRenderable,
       ignoreBorder: true
@@ -1769,7 +1818,7 @@ class PolylineAnnotationElement extends AnnotationElement {
     polyline.setAttribute("points", points);
     polyline.setAttribute("stroke-width", data.borderStyle.width || 1);
     polyline.setAttribute("stroke", "transparent");
-    polyline.setAttribute("fill", "none");
+    polyline.setAttribute("fill", "transparent");
     svg.appendChild(polyline);
     this.container.append(svg);
 
@@ -1791,7 +1840,7 @@ class PolygonAnnotationElement extends PolylineAnnotationElement {
 
 class CaretAnnotationElement extends AnnotationElement {
   constructor(parameters) {
-    const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str);
+    const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str || parameters.data.richText?.str);
     super(parameters, {
       isRenderable,
       ignoreBorder: true
@@ -1812,7 +1861,7 @@ class CaretAnnotationElement extends AnnotationElement {
 
 class InkAnnotationElement extends AnnotationElement {
   constructor(parameters) {
-    const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str);
+    const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str || parameters.data.richText?.str);
     super(parameters, {
       isRenderable,
       ignoreBorder: true
@@ -1842,7 +1891,7 @@ class InkAnnotationElement extends AnnotationElement {
       polyline.setAttribute("points", points);
       polyline.setAttribute("stroke-width", data.borderStyle.width || 1);
       polyline.setAttribute("stroke", "transparent");
-      polyline.setAttribute("fill", "none");
+      polyline.setAttribute("fill", "transparent");
 
       this._createPopup(polyline, data);
 
@@ -1857,7 +1906,7 @@ class InkAnnotationElement extends AnnotationElement {
 
 class HighlightAnnotationElement extends AnnotationElement {
   constructor(parameters) {
-    const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str);
+    const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str || parameters.data.richText?.str);
     super(parameters, {
       isRenderable,
       ignoreBorder: true,
@@ -1882,7 +1931,7 @@ class HighlightAnnotationElement extends AnnotationElement {
 
 class UnderlineAnnotationElement extends AnnotationElement {
   constructor(parameters) {
-    const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str);
+    const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str || parameters.data.richText?.str);
     super(parameters, {
       isRenderable,
       ignoreBorder: true,
@@ -1907,7 +1956,7 @@ class UnderlineAnnotationElement extends AnnotationElement {
 
 class SquigglyAnnotationElement extends AnnotationElement {
   constructor(parameters) {
-    const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str);
+    const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str || parameters.data.richText?.str);
     super(parameters, {
       isRenderable,
       ignoreBorder: true,
@@ -1932,7 +1981,7 @@ class SquigglyAnnotationElement extends AnnotationElement {
 
 class StrikeOutAnnotationElement extends AnnotationElement {
   constructor(parameters) {
-    const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str);
+    const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str || parameters.data.richText?.str);
     super(parameters, {
       isRenderable,
       ignoreBorder: true,
@@ -1957,7 +2006,7 @@ class StrikeOutAnnotationElement extends AnnotationElement {
 
 class StampAnnotationElement extends AnnotationElement {
   constructor(parameters) {
-    const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str);
+    const isRenderable = !!(parameters.data.hasPopup || parameters.data.titleObj?.str || parameters.data.contentsObj?.str || parameters.data.richText?.str);
     super(parameters, {
       isRenderable,
       ignoreBorder: true
@@ -2002,7 +2051,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.titleObj?.str || this.data.contentsObj?.str)) {
+    if (!this.data.hasPopup && (this.data.titleObj?.str || this.data.contentsObj?.str || this.data.richText)) {
       this._createPopup(trigger, this.data);
     }
 
@@ -2038,10 +2087,12 @@ class AnnotationLayer {
       sortedAnnotations.push(...popupAnnotations);
     }
 
+    const div = parameters.div;
+
     for (const data of sortedAnnotations) {
       const element = AnnotationElementFactory.create({
         data,
-        layer: parameters.div,
+        layer: div,
         page: parameters.page,
         viewport: parameters.viewport,
         linkService: parameters.linkService,
@@ -2067,33 +2118,93 @@ class AnnotationLayer {
 
         if (Array.isArray(rendered)) {
           for (const renderedElement of rendered) {
-            parameters.div.appendChild(renderedElement);
+            div.appendChild(renderedElement);
           }
         } else {
           if (element instanceof PopupAnnotationElement) {
-            parameters.div.prepend(rendered);
+            div.prepend(rendered);
           } else {
-            parameters.div.appendChild(rendered);
+            div.appendChild(rendered);
           }
         }
       }
     }
+
+    this.#setAnnotationCanvasMap(div, parameters.annotationCanvasMap);
   }
 
   static update(parameters) {
-    const transform = `matrix(${parameters.viewport.transform.join(",")})`;
-
-    for (const data of parameters.annotations) {
-      const elements = parameters.div.querySelectorAll(`[data-annotation-id="${data.id}"]`);
+    const {
+      page,
+      viewport,
+      annotations,
+      annotationCanvasMap,
+      div
+    } = parameters;
+    const transform = viewport.transform;
+    const matrix = `matrix(${transform.join(",")})`;
+    let scale, ownMatrix;
+
+    for (const data of annotations) {
+      const elements = div.querySelectorAll(`[data-annotation-id="${data.id}"]`);
 
       if (elements) {
         for (const element of elements) {
-          element.style.transform = transform;
+          if (data.hasOwnCanvas) {
+            const rect = _util.Util.normalizeRect([data.rect[0], page.view[3] - data.rect[1] + page.view[1], data.rect[2], page.view[3] - data.rect[3] + page.view[1]]);
+
+            if (!ownMatrix) {
+              scale = Math.abs(transform[0] || transform[1]);
+              const ownTransform = transform.slice();
+
+              for (let i = 0; i < 4; i++) {
+                ownTransform[i] = Math.sign(ownTransform[i]);
+              }
+
+              ownMatrix = `matrix(${ownTransform.join(",")})`;
+            }
+
+            const left = rect[0] * scale;
+            const top = rect[1] * scale;
+            element.style.left = `${left}px`;
+            element.style.top = `${top}px`;
+            element.style.transformOrigin = `${-left}px ${-top}px`;
+            element.style.transform = ownMatrix;
+          } else {
+            element.style.transform = matrix;
+          }
         }
       }
     }
 
-    parameters.div.hidden = false;
+    this.#setAnnotationCanvasMap(div, annotationCanvasMap);
+    div.hidden = false;
+  }
+
+  static #setAnnotationCanvasMap(div, annotationCanvasMap) {
+    if (!annotationCanvasMap) {
+      return;
+    }
+
+    for (const [id, canvas] of annotationCanvasMap) {
+      const element = div.querySelector(`[data-annotation-id="${id}"]`);
+
+      if (!element) {
+        continue;
+      }
+
+      const {
+        firstChild
+      } = element;
+
+      if (firstChild.nodeName === "CANVAS") {
+        element.replaceChild(canvas, firstChild);
+      } else {
+        element.insertBefore(canvas, firstChild);
+      }
+    }
+
+    annotationCanvasMap.clear();
   }
 
 }

+ 136 - 128
lib/display/api.js

@@ -24,9 +24,10 @@
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
+exports.build = exports.RenderTask = exports.PDFWorker = exports.PDFPageProxy = exports.PDFDocumentProxy = exports.PDFDocumentLoadingTask = exports.PDFDataRangeTransport = exports.LoopbackPort = exports.DefaultStandardFontDataFactory = exports.DefaultCanvasFactory = exports.DefaultCMapReaderFactory = void 0;
 exports.getDocument = getDocument;
 exports.setPDFNetworkStreamFactory = setPDFNetworkStreamFactory;
-exports.version = exports.RenderTask = exports.PDFWorker = exports.PDFPageProxy = exports.PDFDocumentProxy = exports.PDFDocumentLoadingTask = exports.PDFDataRangeTransport = exports.LoopbackPort = exports.DefaultStandardFontDataFactory = exports.DefaultCMapReaderFactory = exports.DefaultCanvasFactory = exports.build = void 0;
+exports.version = void 0;
 
 var _util = require("../shared/util.js");
 
@@ -247,7 +248,6 @@ function getDocument(src) {
       }
 
       const messageHandler = new _message_handler.MessageHandler(docId, workerId, worker.port);
-      messageHandler.postMessageTransfers = worker.postMessageTransfers;
       const transport = new WorkerTransport(messageHandler, task, networkStream, params);
       task._transport = transport;
       messageHandler.send("Ready", null);
@@ -270,7 +270,7 @@ async function _fetchDocument(worker, source, pdfDataRangeTransport, docId) {
 
   const workerId = await worker.messageHandler.sendWithPromise("GetDocRequest", {
     docId,
-    apiVersion: '2.11.338',
+    apiVersion: '2.12.313',
     source: {
       data: source.data,
       url: source.url,
@@ -281,7 +281,6 @@ async function _fetchDocument(worker, source, pdfDataRangeTransport, docId) {
     },
     maxImageSize: source.maxImageSize,
     disableFontFace: source.disableFontFace,
-    postMessageTransfers: worker.postMessageTransfers,
     docBaseUrl: source.docBaseUrl,
     ignoreErrors: source.ignoreErrors,
     isEvalSupported: source.isEvalSupported,
@@ -421,6 +420,15 @@ class PDFDocumentProxy {
       }
 
     });
+    Object.defineProperty(this, "getStats", {
+      value: async () => {
+        (0, _display_utils.deprecated)("`PDFDocumentProxy.getStats`, " + "please use the `PDFDocumentProxy.stats`-getter instead.");
+        return this.stats || {
+          streamTypes: {},
+          fontTypes: {}
+        };
+      }
+    });
   }
 
   get annotationStorage() {
@@ -435,6 +443,10 @@ class PDFDocumentProxy {
     return this._pdfInfo.fingerprints;
   }
 
+  get stats() {
+    return this._transport.stats;
+  }
+
   get isPureXfa() {
     return !!this._transport._htmlForXfa;
   }
@@ -519,10 +531,6 @@ class PDFDocumentProxy {
     return this._transport.downloadInfoCapability.promise;
   }
 
-  getStats() {
-    return this._transport.getStats();
-  }
-
   cleanup(keepLoadedFonts = false) {
     return this._transport.startCleanup(keepLoadedFonts || this.isPureXfa);
   }
@@ -676,7 +684,8 @@ class PDFPageProxy {
     imageLayer = null,
     canvasFactory = null,
     background = null,
-    optionalContentConfigPromise = null
+    optionalContentConfigPromise = null,
+    annotationCanvasMap = null
   }) {
     if (arguments[0]?.renderInteractiveForms !== undefined) {
       (0, _display_utils.deprecated)("render no longer accepts the `renderInteractiveForms`-option, " + "please use the `annotationMode`-option instead.");
@@ -777,6 +786,7 @@ class PDFPageProxy {
       },
       objs: this.objs,
       commonObjs: this.commonObjs,
+      annotationCanvasMap,
       operatorList: intentState.operatorList,
       pageIndex: this._pageIndex,
       canvasFactory: canvasFactoryInstance,
@@ -909,7 +919,6 @@ class PDFPageProxy {
 
   _destroy() {
     this.destroyed = true;
-    this._transport.pageCache[this._pageIndex] = null;
     const waitOn = [];
 
     for (const intentState of this._intentStates.values()) {
@@ -1129,84 +1138,92 @@ exports.PDFPageProxy = PDFPageProxy;
 class LoopbackPort {
   constructor() {
     this._listeners = [];
-    this._deferred = Promise.resolve(undefined);
+    this._deferred = Promise.resolve();
   }
 
   postMessage(obj, transfers) {
-    function cloneValue(value) {
-      if (typeof value === "function" || typeof value === "symbol" || value instanceof URL) {
-        throw new Error(`LoopbackPort.postMessage - cannot clone: ${value?.toString()}`);
+    function cloneValue(object) {
+      if (globalThis.structuredClone) {
+        return globalThis.structuredClone(object, transfers);
       }
 
-      if (typeof value !== "object" || value === null) {
-        return value;
-      }
+      function fallbackCloneValue(value) {
+        if (typeof value === "function" || typeof value === "symbol" || value instanceof URL) {
+          throw new Error(`LoopbackPort.postMessage - cannot clone: ${value?.toString()}`);
+        }
 
-      if (cloned.has(value)) {
-        return cloned.get(value);
-      }
+        if (typeof value !== "object" || value === null) {
+          return value;
+        }
 
-      let buffer, result;
+        if (cloned.has(value)) {
+          return cloned.get(value);
+        }
 
-      if ((buffer = value.buffer) && (0, _util.isArrayBuffer)(buffer)) {
-        if (transfers?.includes(buffer)) {
-          result = new value.constructor(buffer, value.byteOffset, value.byteLength);
-        } else {
-          result = new value.constructor(value);
+        let buffer, result;
+
+        if ((buffer = value.buffer) && (0, _util.isArrayBuffer)(buffer)) {
+          if (transfers?.includes(buffer)) {
+            result = new value.constructor(buffer, value.byteOffset, value.byteLength);
+          } else {
+            result = new value.constructor(value);
+          }
+
+          cloned.set(value, result);
+          return result;
         }
 
-        cloned.set(value, result);
-        return result;
-      }
+        if (value instanceof Map) {
+          result = new Map();
+          cloned.set(value, result);
 
-      if (value instanceof Map) {
-        result = new Map();
-        cloned.set(value, result);
+          for (const [key, val] of value) {
+            result.set(key, fallbackCloneValue(val));
+          }
 
-        for (const [key, val] of value) {
-          result.set(key, cloneValue(val));
+          return result;
         }
 
-        return result;
-      }
+        if (value instanceof Set) {
+          result = new Set();
+          cloned.set(value, result);
 
-      if (value instanceof Set) {
-        result = new Set();
-        cloned.set(value, result);
+          for (const val of value) {
+            result.add(fallbackCloneValue(val));
+          }
 
-        for (const val of value) {
-          result.add(cloneValue(val));
+          return result;
         }
 
-        return result;
-      }
+        result = Array.isArray(value) ? [] : Object.create(null);
+        cloned.set(value, result);
 
-      result = Array.isArray(value) ? [] : Object.create(null);
-      cloned.set(value, result);
+        for (const i in value) {
+          let desc,
+              p = value;
 
-      for (const i in value) {
-        let desc,
-            p = value;
+          while (!(desc = Object.getOwnPropertyDescriptor(p, i))) {
+            p = Object.getPrototypeOf(p);
+          }
 
-        while (!(desc = Object.getOwnPropertyDescriptor(p, i))) {
-          p = Object.getPrototypeOf(p);
-        }
+          if (typeof desc.value === "undefined") {
+            continue;
+          }
 
-        if (typeof desc.value === "undefined") {
-          continue;
-        }
+          if (typeof desc.value === "function" && !value.hasOwnProperty?.(i)) {
+            continue;
+          }
 
-        if (typeof desc.value === "function" && !value.hasOwnProperty?.(i)) {
-          continue;
+          result[i] = fallbackCloneValue(desc.value);
         }
 
-        result[i] = cloneValue(desc.value);
+        return result;
       }
 
-      return result;
+      const cloned = new WeakMap();
+      return fallbackCloneValue(object);
     }
 
-    const cloned = new WeakMap();
     const event = {
       data: cloneValue(obj)
     };
@@ -1274,7 +1291,6 @@ class PDFWorker {
 
     this.name = name;
     this.destroyed = false;
-    this.postMessageTransfers = true;
     this.verbosity = verbosity;
     this._readyCapability = (0, _util.createPromiseCapability)();
     this._port = null;
@@ -1357,10 +1373,6 @@ class PDFWorker {
             this._port = worker;
             this._webWorker = worker;
 
-            if (!data.supportTransfers) {
-              this.postMessageTransfers = false;
-            }
-
             this._readyCapability.resolve();
 
             messageHandler.send("configure", {
@@ -1389,7 +1401,7 @@ class PDFWorker {
         });
 
         const sendTest = () => {
-          const testObj = new Uint8Array([this.postMessageTransfers ? 255 : 0]);
+          const testObj = new Uint8Array([255]);
 
           try {
             messageHandler.send("test", testObj, [testObj.buffer]);
@@ -1528,6 +1540,11 @@ exports.PDFWorker = PDFWorker;
 }
 
 class WorkerTransport {
+  #docStats = null;
+  #pageCache = new Map();
+  #pagePromises = new Map();
+  #metadataPromise = null;
+
   constructor(messageHandler, loadingTask, networkStream, params) {
     this.messageHandler = messageHandler;
     this.loadingTask = loadingTask;
@@ -1556,8 +1573,6 @@ class WorkerTransport {
     this._networkStream = networkStream;
     this._fullReader = null;
     this._lastProgress = null;
-    this.pageCache = [];
-    this.pagePromises = [];
     this.downloadInfoCapability = (0, _util.createPromiseCapability)();
     this.setupMessageHandler();
   }
@@ -1566,6 +1581,10 @@ class WorkerTransport {
     return (0, _util.shadow)(this, "annotationStorage", new _annotation_storage.AnnotationStorage());
   }
 
+  get stats() {
+    return this.#docStats;
+  }
+
   getRenderingIntent(intent, annotationMode = _util.AnnotationMode.ENABLE, isOpList = false) {
     let renderingIntent = _util.RenderingIntentFlag.DISPLAY;
     let lastModified = "";
@@ -1631,14 +1650,12 @@ class WorkerTransport {
 
     const waitOn = [];
 
-    for (const page of this.pageCache) {
-      if (page) {
-        waitOn.push(page._destroy());
-      }
+    for (const page of this.#pageCache.values()) {
+      waitOn.push(page._destroy());
     }
 
-    this.pageCache.length = 0;
-    this.pagePromises.length = 0;
+    this.#pageCache.clear();
+    this.#pagePromises.clear();
 
     if (this.hasOwnProperty("annotationStorage")) {
       this.annotationStorage.resetModified();
@@ -1649,6 +1666,7 @@ class WorkerTransport {
     Promise.all(waitOn).then(() => {
       this.commonObjs.clear();
       this.fontLoader.clear();
+      this.#metadataPromise = null;
       this._getFieldObjectsPromise = null;
       this._hasJSActionsPromise = null;
 
@@ -1716,17 +1734,15 @@ class WorkerTransport {
       const fullReader = this._fullReader;
       fullReader.headersReady.then(() => {
         if (!fullReader.isStreamingSupported || !fullReader.isRangeSupported) {
-          if (this._lastProgress && loadingTask.onProgress) {
-            loadingTask.onProgress(this._lastProgress);
+          if (this._lastProgress) {
+            loadingTask.onProgress?.(this._lastProgress);
           }
 
           fullReader.onProgress = evt => {
-            if (loadingTask.onProgress) {
-              loadingTask.onProgress({
-                loaded: evt.loaded,
-                total: evt.total
-              });
-            }
+            loadingTask.onProgress?.({
+              loaded: evt.loaded,
+              total: evt.total
+            });
           };
         }
 
@@ -1837,13 +1853,10 @@ class WorkerTransport {
       return this._passwordCapability.promise;
     });
     messageHandler.on("DataLoaded", data => {
-      if (loadingTask.onProgress) {
-        loadingTask.onProgress({
-          loaded: data.length,
-          total: data.length
-        });
-      }
-
+      loadingTask.onProgress?.({
+        loaded: data.length,
+        total: data.length
+      });
       this.downloadInfoCapability.resolve(data);
     });
     messageHandler.on("StartRenderPage", data => {
@@ -1851,17 +1864,15 @@ class WorkerTransport {
         return;
       }
 
-      const page = this.pageCache[data.pageIndex];
+      const page = this.#pageCache.get(data.pageIndex);
 
       page._startRenderPage(data.transparency, data.cacheKey);
     });
-    messageHandler.on("commonobj", data => {
+    messageHandler.on("commonobj", ([id, type, exportedData]) => {
       if (this.destroyed) {
         return;
       }
 
-      const [id, type, exportedData] = data;
-
       if (this.commonObjs.has(id)) {
         return;
       }
@@ -1917,16 +1928,15 @@ class WorkerTransport {
           throw new Error(`Got unknown common object type ${type}`);
       }
     });
-    messageHandler.on("obj", data => {
+    messageHandler.on("obj", ([id, pageIndex, type, imageData]) => {
       if (this.destroyed) {
-        return undefined;
+        return;
       }
 
-      const [id, pageIndex, type, imageData] = data;
-      const pageProxy = this.pageCache[pageIndex];
+      const pageProxy = this.#pageCache.get(pageIndex);
 
       if (pageProxy.objs.has(id)) {
-        return undefined;
+        return;
       }
 
       switch (type) {
@@ -1947,20 +1957,26 @@ class WorkerTransport {
         default:
           throw new Error(`Got unknown object type ${type}`);
       }
-
-      return undefined;
     });
     messageHandler.on("DocProgress", data => {
       if (this.destroyed) {
         return;
       }
 
-      if (loadingTask.onProgress) {
-        loadingTask.onProgress({
-          loaded: data.loaded,
-          total: data.total
-        });
+      loadingTask.onProgress?.({
+        loaded: data.loaded,
+        total: data.total
+      });
+    });
+    messageHandler.on("DocStats", data => {
+      if (this.destroyed) {
+        return;
       }
+
+      this.#docStats = Object.freeze({
+        streamTypes: Object.freeze(data.streamTypes),
+        fontTypes: Object.freeze(data.fontTypes)
+      });
     });
     messageHandler.on("UnsupportedFeature", this._onUnsupportedFeature.bind(this));
     messageHandler.on("FetchBuiltInCMap", data => {
@@ -1994,9 +2010,7 @@ class WorkerTransport {
       return;
     }
 
-    if (this.loadingTask.onUnsupportedFeature) {
-      this.loadingTask.onUnsupportedFeature(featureId);
-    }
+    this.loadingTask.onUnsupportedFeature?.(featureId);
   }
 
   getData() {
@@ -2008,10 +2022,11 @@ class WorkerTransport {
       return Promise.reject(new Error("Invalid page request"));
     }
 
-    const pageIndex = pageNumber - 1;
+    const pageIndex = pageNumber - 1,
+          cachedPromise = this.#pagePromises.get(pageIndex);
 
-    if (pageIndex in this.pagePromises) {
-      return this.pagePromises[pageIndex];
+    if (cachedPromise) {
+      return cachedPromise;
     }
 
     const promise = this.messageHandler.sendWithPromise("GetPage", {
@@ -2022,10 +2037,10 @@ class WorkerTransport {
       }
 
       const page = new PDFPageProxy(pageIndex, pageInfo, this, this._params.ownerDocument, this._params.pdfBug);
-      this.pageCache[pageIndex] = page;
+      this.#pageCache.set(pageIndex, page);
       return page;
     });
-    this.pagePromises[pageIndex] = promise;
+    this.#pagePromises.set(pageIndex, promise);
     return promise;
   }
 
@@ -2138,7 +2153,7 @@ class WorkerTransport {
   }
 
   getMetadata() {
-    return this.messageHandler.sendWithPromise("GetMetadata", null).then(results => {
+    return this.#metadataPromise ||= this.messageHandler.sendWithPromise("GetMetadata", null).then(results => {
       return {
         info: results[0],
         metadata: results[1] ? new _metadata.Metadata(results[1]) : null,
@@ -2152,10 +2167,6 @@ class WorkerTransport {
     return this.messageHandler.sendWithPromise("GetMarkInfo", null);
   }
 
-  getStats() {
-    return this.messageHandler.sendWithPromise("GetStats", null);
-  }
-
   async startCleanup(keepLoadedFonts = false) {
     await this.messageHandler.sendWithPromise("Cleanup", null);
 
@@ -2163,17 +2174,11 @@ class WorkerTransport {
       return;
     }
 
-    for (let i = 0, ii = this.pageCache.length; i < ii; i++) {
-      const page = this.pageCache[i];
-
-      if (!page) {
-        continue;
-      }
-
+    for (const page of this.#pageCache.values()) {
       const cleanupSuccessful = page.cleanup();
 
       if (!cleanupSuccessful) {
-        throw new Error(`startCleanup: Page ${i + 1} is currently rendering.`);
+        throw new Error(`startCleanup: Page ${page.pageNumber} is currently rendering.`);
       }
     }
 
@@ -2183,6 +2188,7 @@ class WorkerTransport {
       this.fontLoader.clear();
     }
 
+    this.#metadataPromise = null;
     this._getFieldObjectsPromise = null;
     this._hasJSActionsPromise = null;
   }
@@ -2277,6 +2283,7 @@ class InternalRenderTask {
     params,
     objs,
     commonObjs,
+    annotationCanvasMap,
     operatorList,
     pageIndex,
     canvasFactory,
@@ -2287,6 +2294,7 @@ class InternalRenderTask {
     this.params = params;
     this.objs = objs;
     this.commonObjs = commonObjs;
+    this.annotationCanvasMap = annotationCanvasMap;
     this.operatorListIdx = null;
     this.operatorList = operatorList;
     this._pageIndex = pageIndex;
@@ -2339,7 +2347,7 @@ class InternalRenderTask {
       imageLayer,
       background
     } = this.params;
-    this.gfx = new _canvas.CanvasGraphics(canvasContext, this.commonObjs, this.objs, this.canvasFactory, imageLayer, optionalContentConfig);
+    this.gfx = new _canvas.CanvasGraphics(canvasContext, this.commonObjs, this.objs, this.canvasFactory, imageLayer, optionalContentConfig, this.annotationCanvasMap);
     this.gfx.beginDrawing({
       transform,
       viewport,
@@ -2437,7 +2445,7 @@ class InternalRenderTask {
 
 }
 
-const version = '2.11.338';
+const version = '2.12.313';
 exports.version = version;
-const build = 'dedff3c98';
+const build = 'a2ae56f39';
 exports.build = build;

+ 1 - 1
lib/display/base_factory.js

@@ -24,7 +24,7 @@
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
-exports.BaseSVGFactory = exports.BaseStandardFontDataFactory = exports.BaseCMapReaderFactory = exports.BaseCanvasFactory = void 0;
+exports.BaseStandardFontDataFactory = exports.BaseSVGFactory = exports.BaseCanvasFactory = exports.BaseCMapReaderFactory = void 0;
 
 var _util = require("../shared/util.js");
 

+ 410 - 159
lib/display/canvas.js

@@ -35,7 +35,6 @@ 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;
@@ -43,6 +42,137 @@ const MAX_SIZE_TO_COMPILE = 1000;
 const FULL_CHUNK_HEIGHT = 16;
 const LINEWIDTH_SCALE_FACTOR = 1.000001;
 
+function mirrorContextOperations(ctx, destCtx) {
+  if (ctx._removeMirroring) {
+    throw new Error("Context is already forwarding operations.");
+  }
+
+  ctx.__originalSave = ctx.save;
+  ctx.__originalRestore = ctx.restore;
+  ctx.__originalRotate = ctx.rotate;
+  ctx.__originalScale = ctx.scale;
+  ctx.__originalTranslate = ctx.translate;
+  ctx.__originalTransform = ctx.transform;
+  ctx.__originalSetTransform = ctx.setTransform;
+  ctx.__originalResetTransform = ctx.resetTransform;
+  ctx.__originalClip = ctx.clip;
+  ctx.__originalMoveTo = ctx.moveTo;
+  ctx.__originalLineTo = ctx.lineTo;
+  ctx.__originalBezierCurveTo = ctx.bezierCurveTo;
+  ctx.__originalRect = ctx.rect;
+  ctx.__originalClosePath = ctx.closePath;
+  ctx.__originalBeginPath = ctx.beginPath;
+
+  ctx._removeMirroring = () => {
+    ctx.save = ctx.__originalSave;
+    ctx.restore = ctx.__originalRestore;
+    ctx.rotate = ctx.__originalRotate;
+    ctx.scale = ctx.__originalScale;
+    ctx.translate = ctx.__originalTranslate;
+    ctx.transform = ctx.__originalTransform;
+    ctx.setTransform = ctx.__originalSetTransform;
+    ctx.resetTransform = ctx.__originalResetTransform;
+    ctx.clip = ctx.__originalClip;
+    ctx.moveTo = ctx.__originalMoveTo;
+    ctx.lineTo = ctx.__originalLineTo;
+    ctx.bezierCurveTo = ctx.__originalBezierCurveTo;
+    ctx.rect = ctx.__originalRect;
+    ctx.closePath = ctx.__originalClosePath;
+    ctx.beginPath = ctx.__originalBeginPath;
+    delete ctx._removeMirroring;
+  };
+
+  ctx.save = function ctxSave() {
+    destCtx.save();
+
+    this.__originalSave();
+  };
+
+  ctx.restore = function ctxRestore() {
+    destCtx.restore();
+
+    this.__originalRestore();
+  };
+
+  ctx.translate = function ctxTranslate(x, y) {
+    destCtx.translate(x, y);
+
+    this.__originalTranslate(x, y);
+  };
+
+  ctx.scale = function ctxScale(x, y) {
+    destCtx.scale(x, y);
+
+    this.__originalScale(x, y);
+  };
+
+  ctx.transform = function ctxTransform(a, b, c, d, e, f) {
+    destCtx.transform(a, b, c, d, e, f);
+
+    this.__originalTransform(a, b, c, d, e, f);
+  };
+
+  ctx.setTransform = function ctxSetTransform(a, b, c, d, e, f) {
+    destCtx.setTransform(a, b, c, d, e, f);
+
+    this.__originalSetTransform(a, b, c, d, e, f);
+  };
+
+  ctx.resetTransform = function ctxResetTransform() {
+    destCtx.resetTransform();
+
+    this.__originalResetTransform();
+  };
+
+  ctx.rotate = function ctxRotate(angle) {
+    destCtx.rotate(angle);
+
+    this.__originalRotate(angle);
+  };
+
+  ctx.clip = function ctxRotate(rule) {
+    destCtx.clip(rule);
+
+    this.__originalClip(rule);
+  };
+
+  ctx.moveTo = function (x, y) {
+    destCtx.moveTo(x, y);
+
+    this.__originalMoveTo(x, y);
+  };
+
+  ctx.lineTo = function (x, y) {
+    destCtx.lineTo(x, y);
+
+    this.__originalLineTo(x, y);
+  };
+
+  ctx.bezierCurveTo = function (cp1x, cp1y, cp2x, cp2y, x, y) {
+    destCtx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
+
+    this.__originalBezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
+  };
+
+  ctx.rect = function (x, y, width, height) {
+    destCtx.rect(x, y, width, height);
+
+    this.__originalRect(x, y, width, height);
+  };
+
+  ctx.closePath = function () {
+    destCtx.closePath();
+
+    this.__originalClosePath();
+  };
+
+  ctx.beginPath = function () {
+    destCtx.beginPath();
+
+    this.__originalBeginPath();
+  };
+}
+
 function addContextCurrentTransform(ctx) {
   if (ctx.mozCurrentTransform) {
     return;
@@ -189,46 +319,6 @@ 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]);
@@ -403,7 +493,7 @@ function compileType3Glyph(imgData) {
 }
 
 class CanvasExtraState {
-  constructor() {
+  constructor(width, height) {
     this.alphaIsShape = false;
     this.fontSize = 0;
     this.fontSizeScale = 1;
@@ -427,12 +517,14 @@ class CanvasExtraState {
     this.strokeAlpha = 1;
     this.lineWidth = 1;
     this.activeSMask = null;
-    this.resumeSMaskCtx = null;
     this.transferMaps = null;
+    this.startNewPathAndClipBox([0, 0, width, height]);
   }
 
   clone() {
-    return Object.create(this);
+    const clone = Object.create(this);
+    clone.clipBox = this.clipBox.slice();
+    return clone;
   }
 
   setCurrentPoint(x, y) {
@@ -440,6 +532,60 @@ class CanvasExtraState {
     this.y = y;
   }
 
+  updatePathMinMax(transform, x, y) {
+    [x, y] = _util.Util.applyTransform([x, y], transform);
+    this.minX = Math.min(this.minX, x);
+    this.minY = Math.min(this.minY, y);
+    this.maxX = Math.max(this.maxX, x);
+    this.maxY = Math.max(this.maxY, y);
+  }
+
+  updateCurvePathMinMax(transform, x0, y0, x1, y1, x2, y2, x3, y3) {
+    const box = _util.Util.bezierBoundingBox(x0, y0, x1, y1, x2, y2, x3, y3);
+
+    this.updatePathMinMax(transform, box[0], box[1]);
+    this.updatePathMinMax(transform, box[2], box[3]);
+  }
+
+  getPathBoundingBox(pathType = _pattern_helper.PathType.FILL, transform = null) {
+    const box = [this.minX, this.minY, this.maxX, this.maxY];
+
+    if (pathType === _pattern_helper.PathType.STROKE) {
+      if (!transform) {
+        (0, _util.unreachable)("Stroke bounding box must include transform.");
+      }
+
+      const scale = _util.Util.singularValueDecompose2dScale(transform);
+
+      const xStrokePad = scale[0] * this.lineWidth / 2;
+      const yStrokePad = scale[1] * this.lineWidth / 2;
+      box[0] -= xStrokePad;
+      box[1] -= yStrokePad;
+      box[2] += xStrokePad;
+      box[3] += yStrokePad;
+    }
+
+    return box;
+  }
+
+  updateClipFromPath() {
+    const intersect = _util.Util.intersect(this.clipBox, this.getPathBoundingBox());
+
+    this.startNewPathAndClipBox(intersect || [0, 0, 0, 0]);
+  }
+
+  startNewPathAndClipBox(box) {
+    this.clipBox = box;
+    this.minX = Infinity;
+    this.minY = Infinity;
+    this.maxX = 0;
+    this.maxY = 0;
+  }
+
+  getClippedPathBoundingBox(pathType = _pattern_helper.PathType.FILL, transform = null) {
+    return _util.Util.intersect(this.clipBox, this.getPathBoundingBox(pathType, transform));
+  }
+
 }
 
 function putBinaryImageData(ctx, imgData, transferMaps = null) {
@@ -736,7 +882,7 @@ function composeSMaskLuminosity(maskData, layerData, transferMap) {
   }
 }
 
-function genericComposeSMask(maskCtx, layerCtx, width, height, subtype, backdrop, transferMap) {
+function genericComposeSMask(maskCtx, layerCtx, width, height, subtype, backdrop, transferMap, layerOffsetX, layerOffsetY, maskOffsetX, maskOffsetY) {
   const hasBackdrop = !!backdrop;
   const r0 = hasBackdrop ? backdrop[0] : 0;
   const g0 = hasBackdrop ? backdrop[1] : 0;
@@ -754,24 +900,35 @@ function genericComposeSMask(maskCtx, layerCtx, width, height, subtype, backdrop
 
   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 maskData = maskCtx.getImageData(layerOffsetX - maskOffsetX, row + (layerOffsetY - maskOffsetY), width, chunkHeight);
+    const layerData = layerCtx.getImageData(layerOffsetX, row + layerOffsetY, width, chunkHeight);
 
     if (hasBackdrop) {
       composeSMaskBackdrop(maskData.data, r0, g0, b0);
     }
 
     composeFn(maskData.data, layerData.data, transferMap);
-    maskCtx.putImageData(layerData, 0, row);
+    layerCtx.putImageData(layerData, layerOffsetX, row + layerOffsetY);
   }
 }
 
-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 composeSMask(ctx, smask, layerCtx, layerBox) {
+  const layerOffsetX = layerBox[0];
+  const layerOffsetY = layerBox[1];
+  const layerWidth = layerBox[2] - layerOffsetX;
+  const layerHeight = layerBox[3] - layerOffsetY;
+
+  if (layerWidth === 0 || layerHeight === 0) {
+    return;
+  }
+
+  genericComposeSMask(smask.context, layerCtx, layerWidth, layerHeight, smask.subtype, smask.backdrop, smask.transferMap, layerOffsetX, layerOffsetY, smask.offsetX, smask.offsetY);
+  ctx.save();
+  ctx.globalAlpha = 1;
+  ctx.globalCompositeOperation = "source-over";
+  ctx.setTransform(1, 0, 0, 1, 0, 0);
+  ctx.drawImage(layerCtx.canvas, 0, 0);
+  ctx.restore();
 }
 
 function getImageSmoothingEnabled(transform, interpolate) {
@@ -796,9 +953,9 @@ const NORMAL_CLIP = {};
 const EO_CLIP = {};
 
 class CanvasGraphics {
-  constructor(canvasCtx, commonObjs, objs, canvasFactory, imageLayer, optionalContentConfig) {
+  constructor(canvasCtx, commonObjs, objs, canvasFactory, imageLayer, optionalContentConfig, annotationCanvasMap) {
     this.ctx = canvasCtx;
-    this.current = new CanvasExtraState();
+    this.current = new CanvasExtraState(this.ctx.canvas.width, this.ctx.canvas.height);
     this.stateStack = [];
     this.pendingClip = null;
     this.pendingEOFill = false;
@@ -816,12 +973,16 @@ class CanvasGraphics {
     this.smaskStack = [];
     this.smaskCounter = 0;
     this.tempSMask = null;
+    this.suspendedCtx = 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();
+    this.annotationCanvasMap = annotationCanvasMap;
+    this.viewportScale = 1;
+    this.outputScaleX = 1;
+    this.outputScaleY = 1;
 
     if (canvasCtx) {
       addContextCurrentTransform(canvasCtx);
@@ -857,9 +1018,12 @@ class CanvasGraphics {
 
     if (transform) {
       this.ctx.transform.apply(this.ctx, transform);
+      this.outputScaleX = transform[0];
+      this.outputScaleY = transform[0];
     }
 
     this.ctx.transform.apply(this.ctx, viewport.transform);
+    this.viewportScale = viewport.scale;
     this.baseTransform = this.ctx.mozCurrentTransform.slice();
     this._combinedScaleFactor = Math.hypot(this.baseTransform[0], this.baseTransform[2]);
 
@@ -940,7 +1104,6 @@ class CanvasGraphics {
     }
 
     this.cachedCanvases.clear();
-    this.cachedCanvasPatterns.clear();
     this.cachedPatterns.clear();
 
     if (this.imageLayer) {
@@ -1027,7 +1190,7 @@ class CanvasGraphics {
 
     const inverse = _util.Util.transform(fillCtx.mozCurrentTransformInverse, [1, 0, 0, 1, -offsetX, -offsetY]);
 
-    fillCtx.fillStyle = isPatternFill ? fillColor.getPattern(ctx, this, inverse, false) : fillColor;
+    fillCtx.fillStyle = isPatternFill ? fillColor.getPattern(ctx, this, inverse, _pattern_helper.PathType.FILL) : fillColor;
     fillCtx.fillRect(0, 0, width, height);
     return {
       canvas: fillCanvas.canvas,
@@ -1119,21 +1282,9 @@ class CanvasGraphics {
           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();
-            }
-          }
-
           this.current.activeSMask = value ? this.tempSMask : null;
-
-          if (this.current.activeSMask) {
-            this.beginSMaskGroup();
-          }
-
           this.tempSMask = null;
+          this.checkSMaskState();
           break;
 
         case "TR":
@@ -1142,65 +1293,68 @@ class CanvasGraphics {
     }
   }
 
-  beginSMaskGroup() {
-    const activeSMask = this.current.activeSMask;
-    const drawnWidth = activeSMask.canvas.width;
-    const drawnHeight = activeSMask.canvas.height;
+  checkSMaskState() {
+    const inSMaskMode = !!this.suspendedCtx;
+
+    if (this.current.activeSMask && !inSMaskMode) {
+      this.beginSMaskMode();
+    } else if (!this.current.activeSMask && inSMaskMode) {
+      this.endSMaskMode();
+    }
+  }
+
+  beginSMaskMode() {
+    if (this.suspendedCtx) {
+      throw new Error("beginSMaskMode called while already in smask mode");
+    }
+
+    const drawnWidth = this.ctx.canvas.width;
+    const drawnHeight = this.ctx.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.suspendedCtx = this.ctx;
+    this.ctx = scratchCanvas.context;
+    const ctx = this.ctx;
+    ctx.setTransform.apply(ctx, this.suspendedCtx.mozCurrentTransform);
+    copyCtxState(this.suspendedCtx, ctx);
+    mirrorContextOperations(ctx, this.suspendedCtx);
     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;
-
-    const deltaTransform = _util.Util.transform(this.current.activeSMask.startTransformInverse, groupCtx.mozCurrentTransform);
+  endSMaskMode() {
+    if (!this.suspendedCtx) {
+      throw new Error("endSMaskMode called while not in smask mode");
+    }
 
-    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();
-  }
+    this.ctx._removeMirroring();
 
-  resumeSMaskGroup() {
-    const groupCtx = this.current.resumeSMaskCtx;
-    const currentCtx = this.ctx;
-    this.ctx = groupCtx;
-    this.groupStack.push(currentCtx);
-    this.groupLevel++;
+    copyCtxState(this.ctx, this.suspendedCtx);
+    this.ctx = this.suspendedCtx;
+    this.current.activeSMask = null;
+    this.suspendedCtx = null;
   }
 
-  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);
+  compose(dirtyBox) {
+    if (!this.current.activeSMask) {
+      return;
+    }
 
-    const deltaTransform = _util.Util.transform(this.current.activeSMask.startTransformInverse, groupCtx.mozCurrentTransform);
+    if (!dirtyBox) {
+      dirtyBox = [0, 0, this.ctx.canvas.width, this.ctx.canvas.height];
+    } else {
+      dirtyBox[0] = Math.floor(dirtyBox[0]);
+      dirtyBox[1] = Math.floor(dirtyBox[1]);
+      dirtyBox[2] = Math.ceil(dirtyBox[2]);
+      dirtyBox[3] = Math.ceil(dirtyBox[3]);
+    }
 
-    this.ctx.transform.apply(this.ctx, deltaTransform);
+    const smask = this.current.activeSMask;
+    const suspendedCtx = this.suspendedCtx;
+    composeSMask(suspendedCtx, smask, this.ctx, dirtyBox);
+    this.ctx.save();
+    this.ctx.setTransform(1, 0, 0, 1, 0, 0);
+    this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
+    this.ctx.restore();
   }
 
   save() {
@@ -1208,25 +1362,19 @@ class CanvasGraphics {
     const old = this.current;
     this.stateStack.push(old);
     this.current = old.clone();
-    this.current.resumeSMaskCtx = null;
   }
 
   restore() {
-    if (this.current.resumeSMaskCtx) {
-      this.resumeSMaskGroup();
-    }
-
-    if (this.current.activeSMask !== null && (this.stateStack.length === 0 || this.stateStack[this.stateStack.length - 1].activeSMask !== this.current.activeSMask)) {
-      this.endSMaskGroup();
+    if (this.stateStack.length === 0 && this.current.activeSMask) {
+      this.endSMaskMode();
     }
 
     if (this.stateStack.length !== 0) {
       this.current = this.stateStack.pop();
       this.ctx.restore();
+      this.checkSMaskState();
       this.pendingClip = null;
       this._cachedGetSinglePixelWidth = null;
-    } else {
-      this.current.activeSMask = null;
     }
   }
 
@@ -1240,6 +1388,7 @@ class CanvasGraphics {
     const current = this.current;
     let x = current.x,
         y = current.y;
+    let startX, startY;
 
     for (let i = 0, j = 0, ii = ops.length; i < ii; i++) {
       switch (ops[i] | 0) {
@@ -1260,6 +1409,8 @@ class CanvasGraphics {
             ctx.lineTo(x, yh);
           }
 
+          current.updatePathMinMax(ctx.mozCurrentTransform, x, y);
+          current.updatePathMinMax(ctx.mozCurrentTransform, xw, yh);
           ctx.closePath();
           break;
 
@@ -1267,32 +1418,43 @@ class CanvasGraphics {
           x = args[j++];
           y = args[j++];
           ctx.moveTo(x, y);
+          current.updatePathMinMax(ctx.mozCurrentTransform, x, y);
           break;
 
         case _util.OPS.lineTo:
           x = args[j++];
           y = args[j++];
           ctx.lineTo(x, y);
+          current.updatePathMinMax(ctx.mozCurrentTransform, x, y);
           break;
 
         case _util.OPS.curveTo:
+          startX = x;
+          startY = y;
           x = args[j + 4];
           y = args[j + 5];
           ctx.bezierCurveTo(args[j], args[j + 1], args[j + 2], args[j + 3], x, y);
+          current.updateCurvePathMinMax(ctx.mozCurrentTransform, startX, startY, args[j], args[j + 1], args[j + 2], args[j + 3], x, y);
           j += 6;
           break;
 
         case _util.OPS.curveTo2:
+          startX = x;
+          startY = y;
           ctx.bezierCurveTo(x, y, args[j], args[j + 1], args[j + 2], args[j + 3]);
+          current.updateCurvePathMinMax(ctx.mozCurrentTransform, startX, startY, 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:
+          startX = x;
+          startY = y;
           x = args[j + 2];
           y = args[j + 3];
           ctx.bezierCurveTo(args[j], args[j + 1], x, y, x, y);
+          current.updateCurvePathMinMax(ctx.mozCurrentTransform, startX, startY, args[j], args[j + 1], x, y, x, y);
           j += 4;
           break;
 
@@ -1319,7 +1481,7 @@ class CanvasGraphics {
       if (typeof strokeColor === "object" && strokeColor?.getPattern) {
         const lineWidth = this.getSinglePixelWidth();
         ctx.save();
-        ctx.strokeStyle = strokeColor.getPattern(ctx, this, ctx.mozCurrentTransformInverse);
+        ctx.strokeStyle = strokeColor.getPattern(ctx, this, ctx.mozCurrentTransformInverse, _pattern_helper.PathType.STROKE);
         ctx.lineWidth = Math.max(lineWidth, this.current.lineWidth);
         ctx.stroke();
         ctx.restore();
@@ -1340,7 +1502,7 @@ class CanvasGraphics {
     }
 
     if (consumePath) {
-      this.consumePath();
+      this.consumePath(this.current.getClippedPathBoundingBox());
     }
 
     ctx.globalAlpha = this.current.fillAlpha;
@@ -1360,11 +1522,13 @@ class CanvasGraphics {
 
     if (isPatternFill) {
       ctx.save();
-      ctx.fillStyle = fillColor.getPattern(ctx, this, ctx.mozCurrentTransformInverse);
+      ctx.fillStyle = fillColor.getPattern(ctx, this, ctx.mozCurrentTransformInverse, _pattern_helper.PathType.FILL);
       needRestore = true;
     }
 
-    if (this.contentVisible) {
+    const intersect = this.current.getClippedPathBoundingBox();
+
+    if (this.contentVisible && intersect !== null) {
       if (this.pendingEOFill) {
         ctx.fill("evenodd");
         this.pendingEOFill = false;
@@ -1378,7 +1542,7 @@ class CanvasGraphics {
     }
 
     if (consumePath) {
-      this.consumePath();
+      this.consumePath(intersect);
     }
   }
 
@@ -1665,16 +1829,6 @@ class CanvasGraphics {
     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();
-      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);
 
@@ -1684,6 +1838,16 @@ class CanvasGraphics {
       ctx.scale(textHScale, 1);
     }
 
+    let patternTransform;
+
+    if (current.patternFill) {
+      ctx.save();
+      const pattern = current.fillColor.getPattern(ctx, this, ctx.mozCurrentTransformInverse, _pattern_helper.PathType.FILL);
+      patternTransform = ctx.mozCurrentTransform;
+      ctx.restore();
+      ctx.fillStyle = pattern;
+    }
+
     let lineWidth = current.lineWidth;
     let resetLineWidthToOne = false;
     const scale = current.textMatrixScale;
@@ -1786,6 +1950,7 @@ class CanvasGraphics {
     }
 
     ctx.restore();
+    this.compose();
     return undefined;
   }
 
@@ -1909,7 +2074,7 @@ class CanvasGraphics {
     if (this.cachedPatterns.has(objId)) {
       pattern = this.cachedPatterns.get(objId);
     } else {
-      pattern = (0, _pattern_helper.getShadingPattern)(this.objs.get(objId), this.cachedCanvasPatterns);
+      pattern = (0, _pattern_helper.getShadingPattern)(this.objs.get(objId));
       this.cachedPatterns.set(objId, pattern);
     }
 
@@ -1930,7 +2095,7 @@ class CanvasGraphics {
 
     const pattern = this._getPattern(objId);
 
-    ctx.fillStyle = pattern.getPattern(ctx, this, ctx.mozCurrentTransformInverse, true);
+    ctx.fillStyle = pattern.getPattern(ctx, this, ctx.mozCurrentTransformInverse, _pattern_helper.PathType.SHADING);
     const inv = ctx.mozCurrentTransformInverse;
 
     if (inv) {
@@ -1955,6 +2120,7 @@ class CanvasGraphics {
       this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10);
     }
 
+    this.compose(this.current.getClippedPathBoundingBox());
     this.restore();
   }
 
@@ -1984,6 +2150,8 @@ class CanvasGraphics {
       const width = bbox[2] - bbox[0];
       const height = bbox[3] - bbox[1];
       this.ctx.rect(bbox[0], bbox[1], width, height);
+      this.current.updatePathMinMax(this.ctx.mozCurrentTransform, bbox[0], bbox[1]);
+      this.current.updatePathMinMax(this.ctx.mozCurrentTransform, bbox[2], bbox[3]);
       this.clip();
       this.endPath();
     }
@@ -2004,6 +2172,13 @@ class CanvasGraphics {
     }
 
     this.save();
+    const suspendedCtx = this.suspendedCtx;
+
+    if (this.current.activeSMask) {
+      this.suspendedCtx = null;
+      this.current.activeSMask = null;
+    }
+
     const currentCtx = this.ctx;
 
     if (!group.isolated) {
@@ -2045,6 +2220,7 @@ class CanvasGraphics {
       drawnHeight = MAX_GROUP_SIZE;
     }
 
+    this.current.startNewPathAndClipBox([0, 0, drawnWidth, drawnHeight]);
     let cacheId = "groupAt" + this.groupLevel;
 
     if (group.smask) {
@@ -2074,14 +2250,17 @@ class CanvasGraphics {
       currentCtx.setTransform(1, 0, 0, 1, 0, 0);
       currentCtx.translate(offsetX, offsetY);
       currentCtx.scale(scaleX, scaleY);
+      currentCtx.save();
     }
 
     copyCtxState(currentCtx, groupCtx);
     this.ctx = groupCtx;
     this.setGState([["BM", "source-over"], ["ca", 1], ["CA", 1]]);
-    this.groupStack.push(currentCtx);
+    this.groupStack.push({
+      ctx: currentCtx,
+      suspendedCtx
+    });
     this.groupLevel++;
-    this.current.activeSMask = null;
   }
 
   endGroup(group) {
@@ -2091,16 +2270,33 @@ class CanvasGraphics {
 
     this.groupLevel--;
     const groupCtx = this.ctx;
-    this.ctx = this.groupStack.pop();
+    const {
+      ctx,
+      suspendedCtx
+    } = this.groupStack.pop();
+    this.ctx = ctx;
     this.ctx.imageSmoothingEnabled = false;
 
+    if (suspendedCtx) {
+      this.suspendedCtx = suspendedCtx;
+    }
+
     if (group.smask) {
       this.tempSMask = this.smaskStack.pop();
+      this.restore();
     } else {
+      this.ctx.restore();
+      const currentMtx = this.ctx.mozCurrentTransform;
+      this.restore();
+      this.ctx.save();
+      this.ctx.setTransform.apply(this.ctx, currentMtx);
+
+      const dirtyBox = _util.Util.getAxialAlignedBoundingBox([0, 0, groupCtx.canvas.width, groupCtx.canvas.height], currentMtx);
+
       this.ctx.drawImage(groupCtx.canvas, 0, 0);
+      this.ctx.restore();
+      this.compose(dirtyBox);
     }
-
-    this.restore();
   }
 
   beginAnnotations() {
@@ -2115,24 +2311,62 @@ class CanvasGraphics {
     this.restore();
   }
 
-  beginAnnotation(id, rect, transform, matrix) {
+  beginAnnotation(id, rect, transform, matrix, hasOwnCanvas) {
     this.save();
-    resetCtxToDefault(this.ctx);
-    this.current = new CanvasExtraState();
 
     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 (hasOwnCanvas && this.annotationCanvasMap) {
+        transform = transform.slice();
+        transform[4] -= rect[0];
+        transform[5] -= rect[1];
+        rect = rect.slice();
+        rect[0] = rect[1] = 0;
+        rect[2] = width;
+        rect[3] = height;
+
+        const [scaleX, scaleY] = _util.Util.singularValueDecompose2dScale(this.ctx.mozCurrentTransform);
+
+        const {
+          viewportScale
+        } = this;
+        const canvasWidth = Math.ceil(width * this.outputScaleX * viewportScale);
+        const canvasHeight = Math.ceil(height * this.outputScaleY * viewportScale);
+        this.annotationCanvas = this.canvasFactory.create(canvasWidth, canvasHeight);
+        const {
+          canvas,
+          context
+        } = this.annotationCanvas;
+        canvas.style.width = `calc(${width}px * var(--viewport-scale-factor))`;
+        canvas.style.height = `calc(${height}px * var(--viewport-scale-factor))`;
+        this.annotationCanvasMap.set(id, canvas);
+        this.annotationCanvas.savedCtx = this.ctx;
+        this.ctx = context;
+        this.ctx.setTransform(scaleX, 0, 0, -scaleY, 0, height * scaleY);
+        addContextCurrentTransform(this.ctx);
+        resetCtxToDefault(this.ctx);
+      } else {
+        resetCtxToDefault(this.ctx);
+        this.ctx.rect(rect[0], rect[1], width, height);
+        this.clip();
+        this.endPath();
+      }
     }
 
+    this.current = new CanvasExtraState(this.ctx.canvas.width, this.ctx.canvas.height);
     this.transform.apply(this, transform);
     this.transform.apply(this, matrix);
   }
 
   endAnnotation() {
+    if (this.annotationCanvas) {
+      this.ctx = this.annotationCanvas.savedCtx;
+      delete this.annotationCanvas.savedCtx;
+      delete this.annotationCanvas;
+    }
+
     this.restore();
   }
 
@@ -2170,6 +2404,7 @@ class CanvasGraphics {
     ctx.setTransform(1, 0, 0, 1, 0, 0);
     ctx.drawImage(maskCanvas, mask.offsetX, mask.offsetY);
     ctx.restore();
+    this.compose();
   }
 
   paintImageMaskXObjectRepeat(imgData, scaleX, skewX = 0, skewY = 0, scaleY, positions) {
@@ -2195,6 +2430,7 @@ class CanvasGraphics {
     }
 
     ctx.restore();
+    this.compose();
   }
 
   paintImageMaskXObjectGroup(images) {
@@ -2215,7 +2451,7 @@ class CanvasGraphics {
       maskCtx.save();
       putBinaryImageMask(maskCtx, image);
       maskCtx.globalCompositeOperation = "source-in";
-      maskCtx.fillStyle = isPatternFill ? fillColor.getPattern(maskCtx, this, ctx.mozCurrentTransformInverse, false) : fillColor;
+      maskCtx.fillStyle = isPatternFill ? fillColor.getPattern(maskCtx, this, ctx.mozCurrentTransformInverse, _pattern_helper.PathType.FILL) : fillColor;
       maskCtx.fillRect(0, 0, width, height);
       maskCtx.restore();
       ctx.save();
@@ -2224,6 +2460,8 @@ class CanvasGraphics {
       ctx.drawImage(maskCanvas.canvas, 0, 0, width, height, 0, -1, 1, 1);
       ctx.restore();
     }
+
+    this.compose();
   }
 
   paintImageXObject(objId) {
@@ -2307,6 +2545,7 @@ class CanvasGraphics {
       });
     }
 
+    this.compose();
     this.restore();
   }
 
@@ -2342,6 +2581,8 @@ class CanvasGraphics {
 
       ctx.restore();
     }
+
+    this.compose();
   }
 
   paintSolidColorImageMask() {
@@ -2350,6 +2591,7 @@ class CanvasGraphics {
     }
 
     this.ctx.fillRect(0, 0, 1, 1);
+    this.compose();
   }
 
   markPoint(tag) {}
@@ -2385,7 +2627,15 @@ class CanvasGraphics {
 
   endCompat() {}
 
-  consumePath() {
+  consumePath(clipBox) {
+    if (this.pendingClip) {
+      this.current.updateClipFromPath();
+    }
+
+    if (!this.pendingClip) {
+      this.compose(clipBox);
+    }
+
     const ctx = this.ctx;
 
     if (this.pendingClip) {
@@ -2398,6 +2648,7 @@ class CanvasGraphics {
       this.pendingClip = null;
     }
 
+    this.current.startNewPathAndClipBox(this.current.clipBox);
     ctx.beginPath();
   }
 

+ 1 - 1
lib/display/display_utils.js

@@ -24,6 +24,7 @@
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
+exports.StatTimer = exports.RenderingCancelledException = exports.PixelsPerInch = exports.PageViewport = exports.PDFDateString = exports.LinkTarget = exports.DOMStandardFontDataFactory = exports.DOMSVGFactory = exports.DOMCanvasFactory = exports.DOMCMapReaderFactory = void 0;
 exports.addLinkAttributes = addLinkAttributes;
 exports.deprecated = deprecated;
 exports.getFilenameFromUrl = getFilenameFromUrl;
@@ -33,7 +34,6 @@ exports.isDataScheme = isDataScheme;
 exports.isPdfFile = isPdfFile;
 exports.isValidFetchUrl = isValidFetchUrl;
 exports.loadScript = loadScript;
-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");
 

+ 1 - 2
lib/display/font_loader.js

@@ -294,8 +294,7 @@ exports.FontLoader = FontLoader;
       this._document.body.appendChild(div);
 
       isFontReady(loadTestFontId, () => {
-        this._document.body.removeChild(div);
-
+        div.remove();
         request.complete();
       });
     }

+ 9 - 6
lib/display/metadata.js

@@ -29,28 +29,31 @@ exports.Metadata = void 0;
 var _util = require("../shared/util.js");
 
 class Metadata {
+  #metadataMap;
+  #data;
+
   constructor({
     parsedData,
     rawData
   }) {
-    this._metadataMap = parsedData;
-    this._data = rawData;
+    this.#metadataMap = parsedData;
+    this.#data = rawData;
   }
 
   getRaw() {
-    return this._data;
+    return this.#data;
   }
 
   get(name) {
-    return this._metadataMap.get(name) ?? null;
+    return this.#metadataMap.get(name) ?? null;
   }
 
   getAll() {
-    return (0, _util.objectFromMap)(this._metadataMap);
+    return (0, _util.objectFromMap)(this.#metadataMap);
   }
 
   has(name) {
-    return this._metadataMap.has(name);
+    return this.#metadataMap.has(name);
   }
 
 }

+ 1 - 1
lib/display/node_utils.js

@@ -24,7 +24,7 @@
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
-exports.NodeStandardFontDataFactory = exports.NodeCMapReaderFactory = exports.NodeCanvasFactory = void 0;
+exports.NodeStandardFontDataFactory = exports.NodeCanvasFactory = exports.NodeCMapReaderFactory = void 0;
 
 var _base_factory = require("./base_factory.js");
 

+ 39 - 36
lib/display/pattern_helper.js

@@ -24,11 +24,18 @@
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
+exports.TilingPattern = exports.PathType = void 0;
 exports.getShadingPattern = getShadingPattern;
-exports.TilingPattern = void 0;
 
 var _util = require("../shared/util.js");
 
+const PathType = {
+  FILL: "Fill",
+  STROKE: "Stroke",
+  SHADING: "Shading"
+};
+exports.PathType = PathType;
+
 function applyBoundingBox(ctx, bbox) {
   if (!bbox || typeof Path2D === "undefined") {
     return;
@@ -55,7 +62,7 @@ class BaseShadingPattern {
 }
 
 class RadialAxialShadingPattern extends BaseShadingPattern {
-  constructor(IR, cachedCanvasPatterns) {
+  constructor(IR) {
     super();
     this._type = IR[1];
     this._bbox = IR[2];
@@ -65,7 +72,6 @@ class RadialAxialShadingPattern extends BaseShadingPattern {
     this._r0 = IR[6];
     this._r1 = IR[7];
     this.matrix = null;
-    this.cachedCanvasPatterns = cachedCanvasPatterns;
   }
 
   _createGradient(ctx) {
@@ -84,36 +90,30 @@ class RadialAxialShadingPattern extends BaseShadingPattern {
     return grad;
   }
 
-  getPattern(ctx, owner, inverse, shadingFill = false) {
+  getPattern(ctx, owner, inverse, pathType) {
     let pattern;
 
-    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);
-        tmpCtx.beginPath();
-        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 (pathType === PathType.STROKE || pathType === PathType.FILL) {
+      const ownerBBox = owner.current.getClippedPathBoundingBox(pathType, ctx.mozCurrentTransform) || [0, 0, 0, 0];
+      const width = Math.ceil(ownerBBox[2] - ownerBBox[0]) || 1;
+      const height = Math.ceil(ownerBBox[3] - ownerBBox[1]) || 1;
+      const tmpCanvas = owner.cachedCanvases.getCanvas("pattern", width, height, true);
+      const tmpCtx = tmpCanvas.context;
+      tmpCtx.clearRect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
+      tmpCtx.beginPath();
+      tmpCtx.rect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
+      tmpCtx.translate(-ownerBBox[0], -ownerBBox[1]);
+      inverse = _util.Util.transform(inverse, [1, 0, 0, 1, ownerBBox[0], ownerBBox[1]]);
+      tmpCtx.transform.apply(tmpCtx, owner.baseTransform);
 
-        applyBoundingBox(tmpCtx, this._bbox);
-        tmpCtx.fillStyle = this._createGradient(tmpCtx);
-        tmpCtx.fill();
-        pattern = ctx.createPattern(tmpCanvas.canvas, "repeat");
-        this.cachedCanvasPatterns.set(this, pattern);
+      if (this.matrix) {
+        tmpCtx.transform.apply(tmpCtx, this.matrix);
       }
-    } else {
-      applyBoundingBox(ctx, this._bbox);
-      pattern = this._createGradient(ctx);
-    }
 
-    if (!shadingFill) {
+      applyBoundingBox(tmpCtx, this._bbox);
+      tmpCtx.fillStyle = this._createGradient(tmpCtx);
+      tmpCtx.fill();
+      pattern = ctx.createPattern(tmpCanvas.canvas, "no-repeat");
       const domMatrix = new DOMMatrix(inverse);
 
       try {
@@ -121,6 +121,9 @@ class RadialAxialShadingPattern extends BaseShadingPattern {
       } catch (ex) {
         (0, _util.warn)(`RadialAxialShadingPattern.getPattern: "${ex?.message}".`);
       }
+    } else {
+      applyBoundingBox(ctx, this._bbox);
+      pattern = this._createGradient(ctx);
     }
 
     return pattern;
@@ -351,11 +354,11 @@ class MeshShadingPattern extends BaseShadingPattern {
     };
   }
 
-  getPattern(ctx, owner, inverse, shadingFill = false) {
+  getPattern(ctx, owner, inverse, pathType) {
     applyBoundingBox(ctx, this._bbox);
     let scale;
 
-    if (shadingFill) {
+    if (pathType === PathType.SHADING) {
       scale = _util.Util.singularValueDecompose2dScale(ctx.mozCurrentTransform);
     } else {
       scale = _util.Util.singularValueDecompose2dScale(owner.baseTransform);
@@ -367,9 +370,9 @@ class MeshShadingPattern extends BaseShadingPattern {
       }
     }
 
-    const temporaryPatternCanvas = this._createMeshCanvas(scale, shadingFill ? null : this._background, owner.cachedCanvases);
+    const temporaryPatternCanvas = this._createMeshCanvas(scale, pathType === PathType.SHADING ? null : this._background, owner.cachedCanvases);
 
-    if (!shadingFill) {
+    if (pathType !== PathType.SHADING) {
       ctx.setTransform.apply(ctx, owner.baseTransform);
 
       if (this.matrix) {
@@ -391,10 +394,10 @@ class DummyShadingPattern extends BaseShadingPattern {
 
 }
 
-function getShadingPattern(IR, cachedCanvasPatterns) {
+function getShadingPattern(IR) {
   switch (IR[0]) {
     case "RadialAxial":
-      return new RadialAxialShadingPattern(IR, cachedCanvasPatterns);
+      return new RadialAxialShadingPattern(IR);
 
     case "Mesh":
       return new MeshShadingPattern(IR);
@@ -539,10 +542,10 @@ class TilingPattern {
     }
   }
 
-  getPattern(ctx, owner, inverse, shadingFill = false) {
+  getPattern(ctx, owner, inverse, pathType) {
     let matrix = inverse;
 
-    if (!shadingFill) {
+    if (pathType !== PathType.SHADING) {
       matrix = _util.Util.transform(matrix, owner.baseTransform);
 
       if (this.matrix) {

+ 4 - 0
lib/display/svg.js

@@ -1067,6 +1067,10 @@ exports.SVGGraphics = SVGGraphics;
     }
 
     _makeShadingPattern(args) {
+      if (typeof args === "string") {
+        args = this.objs.get(args);
+      }
+
       switch (args[0]) {
         case "RadialAxial":
           const shadingId = `shading${shadingCount++}`;

+ 1 - 1
lib/display/text_layer.js

@@ -154,7 +154,7 @@ function appendText(task, geom, styles, ctx) {
 
   if (geom.str.length > 1 || task._enhanceTextSelection && AllWhitespaceRegexp.test(geom.str)) {
     shouldScaleText = true;
-  } else if (geom.transform[0] !== geom.transform[3]) {
+  } else if (geom.str !== " " && geom.transform[0] !== geom.transform[3]) {
     const absScaleX = Math.abs(geom.transform[0]),
           absScaleY = Math.abs(geom.transform[3]);
 

+ 14 - 5
lib/display/xfa_layer.js

@@ -133,7 +133,9 @@ class XfaLayer {
         if (key === "textContent") {
           html.textContent = value;
         } else if (key === "class") {
-          html.setAttribute(key, value.join(" "));
+          if (value.length) {
+            html.setAttribute(key, value.join(" "));
+          }
         } else {
           if (isHTMLAnchorElement && (key === "href" || key === "newWindow")) {
             continue;
@@ -162,7 +164,7 @@ class XfaLayer {
   static render(parameters) {
     const storage = parameters.annotationStorage;
     const linkService = parameters.linkService;
-    const root = parameters.xfa;
+    const root = parameters.xfaHtml;
     const intent = parameters.intent || "display";
     const rootHtml = document.createElement(root.name);
 
@@ -178,9 +180,16 @@ class XfaLayer {
     const stack = [[root, -1, rootHtml]];
     const rootDiv = parameters.div;
     rootDiv.appendChild(rootHtml);
-    const transform = `matrix(${parameters.viewport.transform.join(",")})`;
-    rootDiv.style.transform = transform;
-    rootDiv.setAttribute("class", "xfaLayer xfaFont");
+
+    if (parameters.viewport) {
+      const transform = `matrix(${parameters.viewport.transform.join(",")})`;
+      rootDiv.style.transform = transform;
+    }
+
+    if (intent !== "richText") {
+      rootDiv.setAttribute("class", "xfaLayer xfaFont");
+    }
+
     const textDivs = [];
 
     while (stack.length > 0) {

+ 70 - 70
lib/pdf.js

@@ -24,34 +24,34 @@
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
-Object.defineProperty(exports, "addLinkAttributes", {
+Object.defineProperty(exports, "AnnotationLayer", {
   enumerable: true,
   get: function () {
-    return _display_utils.addLinkAttributes;
+    return _annotation_layer.AnnotationLayer;
   }
 });
-Object.defineProperty(exports, "getFilenameFromUrl", {
+Object.defineProperty(exports, "AnnotationMode", {
   enumerable: true,
   get: function () {
-    return _display_utils.getFilenameFromUrl;
+    return _util.AnnotationMode;
   }
 });
-Object.defineProperty(exports, "getPdfFilenameFromUrl", {
+Object.defineProperty(exports, "CMapCompressionType", {
   enumerable: true,
   get: function () {
-    return _display_utils.getPdfFilenameFromUrl;
+    return _util.CMapCompressionType;
   }
 });
-Object.defineProperty(exports, "getXfaPageViewport", {
+Object.defineProperty(exports, "GlobalWorkerOptions", {
   enumerable: true,
   get: function () {
-    return _display_utils.getXfaPageViewport;
+    return _worker_options.GlobalWorkerOptions;
   }
 });
-Object.defineProperty(exports, "isPdfFile", {
+Object.defineProperty(exports, "InvalidPDFException", {
   enumerable: true,
   get: function () {
-    return _display_utils.isPdfFile;
+    return _util.InvalidPDFException;
   }
 });
 Object.defineProperty(exports, "LinkTarget", {
@@ -60,130 +60,130 @@ Object.defineProperty(exports, "LinkTarget", {
     return _display_utils.LinkTarget;
   }
 });
-Object.defineProperty(exports, "loadScript", {
+Object.defineProperty(exports, "LoopbackPort", {
   enumerable: true,
   get: function () {
-    return _display_utils.loadScript;
+    return _api.LoopbackPort;
   }
 });
-Object.defineProperty(exports, "PDFDateString", {
+Object.defineProperty(exports, "MissingPDFException", {
   enumerable: true,
   get: function () {
-    return _display_utils.PDFDateString;
+    return _util.MissingPDFException;
   }
 });
-Object.defineProperty(exports, "PixelsPerInch", {
+Object.defineProperty(exports, "OPS", {
   enumerable: true,
   get: function () {
-    return _display_utils.PixelsPerInch;
+    return _util.OPS;
   }
 });
-Object.defineProperty(exports, "RenderingCancelledException", {
+Object.defineProperty(exports, "PDFDataRangeTransport", {
   enumerable: true,
   get: function () {
-    return _display_utils.RenderingCancelledException;
+    return _api.PDFDataRangeTransport;
   }
 });
-Object.defineProperty(exports, "AnnotationMode", {
+Object.defineProperty(exports, "PDFDateString", {
   enumerable: true,
   get: function () {
-    return _util.AnnotationMode;
+    return _display_utils.PDFDateString;
   }
 });
-Object.defineProperty(exports, "CMapCompressionType", {
+Object.defineProperty(exports, "PDFWorker", {
   enumerable: true,
   get: function () {
-    return _util.CMapCompressionType;
+    return _api.PDFWorker;
   }
 });
-Object.defineProperty(exports, "createObjectURL", {
+Object.defineProperty(exports, "PasswordResponses", {
   enumerable: true,
   get: function () {
-    return _util.createObjectURL;
+    return _util.PasswordResponses;
   }
 });
-Object.defineProperty(exports, "createPromiseCapability", {
+Object.defineProperty(exports, "PermissionFlag", {
   enumerable: true,
   get: function () {
-    return _util.createPromiseCapability;
+    return _util.PermissionFlag;
   }
 });
-Object.defineProperty(exports, "createValidAbsoluteUrl", {
+Object.defineProperty(exports, "PixelsPerInch", {
   enumerable: true,
   get: function () {
-    return _util.createValidAbsoluteUrl;
+    return _display_utils.PixelsPerInch;
   }
 });
-Object.defineProperty(exports, "InvalidPDFException", {
+Object.defineProperty(exports, "RenderingCancelledException", {
   enumerable: true,
   get: function () {
-    return _util.InvalidPDFException;
+    return _display_utils.RenderingCancelledException;
   }
 });
-Object.defineProperty(exports, "MissingPDFException", {
+Object.defineProperty(exports, "SVGGraphics", {
   enumerable: true,
   get: function () {
-    return _util.MissingPDFException;
+    return _svg.SVGGraphics;
   }
 });
-Object.defineProperty(exports, "OPS", {
+Object.defineProperty(exports, "UNSUPPORTED_FEATURES", {
   enumerable: true,
   get: function () {
-    return _util.OPS;
+    return _util.UNSUPPORTED_FEATURES;
   }
 });
-Object.defineProperty(exports, "PasswordResponses", {
+Object.defineProperty(exports, "UnexpectedResponseException", {
   enumerable: true,
   get: function () {
-    return _util.PasswordResponses;
+    return _util.UnexpectedResponseException;
   }
 });
-Object.defineProperty(exports, "PermissionFlag", {
+Object.defineProperty(exports, "Util", {
   enumerable: true,
   get: function () {
-    return _util.PermissionFlag;
+    return _util.Util;
   }
 });
-Object.defineProperty(exports, "removeNullCharacters", {
+Object.defineProperty(exports, "VerbosityLevel", {
   enumerable: true,
   get: function () {
-    return _util.removeNullCharacters;
+    return _util.VerbosityLevel;
   }
 });
-Object.defineProperty(exports, "shadow", {
+Object.defineProperty(exports, "XfaLayer", {
   enumerable: true,
   get: function () {
-    return _util.shadow;
+    return _xfa_layer.XfaLayer;
   }
 });
-Object.defineProperty(exports, "UnexpectedResponseException", {
+Object.defineProperty(exports, "addLinkAttributes", {
   enumerable: true,
   get: function () {
-    return _util.UnexpectedResponseException;
+    return _display_utils.addLinkAttributes;
   }
 });
-Object.defineProperty(exports, "UNSUPPORTED_FEATURES", {
+Object.defineProperty(exports, "build", {
   enumerable: true,
   get: function () {
-    return _util.UNSUPPORTED_FEATURES;
+    return _api.build;
   }
 });
-Object.defineProperty(exports, "Util", {
+Object.defineProperty(exports, "createObjectURL", {
   enumerable: true,
   get: function () {
-    return _util.Util;
+    return _util.createObjectURL;
   }
 });
-Object.defineProperty(exports, "VerbosityLevel", {
+Object.defineProperty(exports, "createPromiseCapability", {
   enumerable: true,
   get: function () {
-    return _util.VerbosityLevel;
+    return _util.createPromiseCapability;
   }
 });
-Object.defineProperty(exports, "build", {
+Object.defineProperty(exports, "createValidAbsoluteUrl", {
   enumerable: true,
   get: function () {
-    return _api.build;
+    return _util.createValidAbsoluteUrl;
   }
 });
 Object.defineProperty(exports, "getDocument", {
@@ -192,40 +192,40 @@ Object.defineProperty(exports, "getDocument", {
     return _api.getDocument;
   }
 });
-Object.defineProperty(exports, "LoopbackPort", {
+Object.defineProperty(exports, "getFilenameFromUrl", {
   enumerable: true,
   get: function () {
-    return _api.LoopbackPort;
+    return _display_utils.getFilenameFromUrl;
   }
 });
-Object.defineProperty(exports, "PDFDataRangeTransport", {
+Object.defineProperty(exports, "getPdfFilenameFromUrl", {
   enumerable: true,
   get: function () {
-    return _api.PDFDataRangeTransport;
+    return _display_utils.getPdfFilenameFromUrl;
   }
 });
-Object.defineProperty(exports, "PDFWorker", {
+Object.defineProperty(exports, "getXfaPageViewport", {
   enumerable: true,
   get: function () {
-    return _api.PDFWorker;
+    return _display_utils.getXfaPageViewport;
   }
 });
-Object.defineProperty(exports, "version", {
+Object.defineProperty(exports, "isPdfFile", {
   enumerable: true,
   get: function () {
-    return _api.version;
+    return _display_utils.isPdfFile;
   }
 });
-Object.defineProperty(exports, "AnnotationLayer", {
+Object.defineProperty(exports, "loadScript", {
   enumerable: true,
   get: function () {
-    return _annotation_layer.AnnotationLayer;
+    return _display_utils.loadScript;
   }
 });
-Object.defineProperty(exports, "GlobalWorkerOptions", {
+Object.defineProperty(exports, "removeNullCharacters", {
   enumerable: true,
   get: function () {
-    return _worker_options.GlobalWorkerOptions;
+    return _util.removeNullCharacters;
   }
 });
 Object.defineProperty(exports, "renderTextLayer", {
@@ -234,16 +234,16 @@ Object.defineProperty(exports, "renderTextLayer", {
     return _text_layer.renderTextLayer;
   }
 });
-Object.defineProperty(exports, "SVGGraphics", {
+Object.defineProperty(exports, "shadow", {
   enumerable: true,
   get: function () {
-    return _svg.SVGGraphics;
+    return _util.shadow;
   }
 });
-Object.defineProperty(exports, "XfaLayer", {
+Object.defineProperty(exports, "version", {
   enumerable: true,
   get: function () {
-    return _xfa_layer.XfaLayer;
+    return _api.version;
   }
 });
 
@@ -265,8 +265,8 @@ var _svg = require("./display/svg.js");
 
 var _xfa_layer = require("./display/xfa_layer.js");
 
-const pdfjsVersion = '2.11.338';
-const pdfjsBuild = 'dedff3c98';
+const pdfjsVersion = '2.12.313';
+const pdfjsBuild = 'a2ae56f39';
 {
   if (_is_node.isNodeJS) {
     const {

Dosya farkı çok büyük olduğundan ihmal edildi
+ 2 - 2
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.11.338';
-const pdfjsBuild = 'dedff3c98';
+const pdfjsVersion = '2.12.313';
+const pdfjsBuild = 'a2ae56f39';

+ 4 - 15
lib/shared/message_handler.js

@@ -79,7 +79,6 @@ class MessageHandler {
     this.comObj = comObj;
     this.callbackId = 1;
     this.streamId = 1;
-    this.postMessageTransfers = true;
     this.streamSinks = Object.create(null);
     this.streamControllers = Object.create(null);
     this.callbackCapabilities = Object.create(null);
@@ -173,7 +172,7 @@ class MessageHandler {
   }
 
   send(actionName, data, transfers) {
-    this._postMessage({
+    this.comObj.postMessage({
       sourceName: this.sourceName,
       targetName: this.targetName,
       action: actionName,
@@ -187,7 +186,7 @@ class MessageHandler {
     this.callbackCapabilities[callbackId] = capability;
 
     try {
-      this._postMessage({
+      this.comObj.postMessage({
         sourceName: this.sourceName,
         targetName: this.targetName,
         action: actionName,
@@ -216,8 +215,7 @@ class MessageHandler {
           cancelCall: null,
           isClosed: false
         };
-
-        this._postMessage({
+        comObj.postMessage({
           sourceName,
           targetName,
           action: actionName,
@@ -225,7 +223,6 @@ class MessageHandler {
           data,
           desiredSize: controller.desiredSize
         }, transfers);
-
         return startCapability.promise;
       },
       pull: controller => {
@@ -278,7 +275,7 @@ class MessageHandler {
           this.ready = this.sinkCapability.promise;
         }
 
-        self._postMessage({
+        comObj.postMessage({
           sourceName,
           targetName,
           stream: StreamKind.ENQUEUE,
@@ -497,14 +494,6 @@ class MessageHandler {
     delete this.streamControllers[streamId];
   }
 
-  _postMessage(message, transfers) {
-    if (transfers && this.postMessageTransfers) {
-      this.comObj.postMessage(message, transfers);
-    } else {
-      this.comObj.postMessage(message);
-    }
-  }
-
   destroy() {
     this.comObj.removeEventListener("message", this._onComObjOnMessage);
   }

+ 78 - 4
lib/shared/util.js

@@ -24,6 +24,7 @@
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
+exports.VerbosityLevel = exports.Util = exports.UnknownErrorException = exports.UnexpectedResponseException = exports.UNSUPPORTED_FEATURES = 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;
 exports.arrayByteLength = arrayByteLength;
 exports.arraysToBytes = arraysToBytes;
 exports.assert = assert;
@@ -55,7 +56,6 @@ 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.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");
 
@@ -592,14 +592,19 @@ class AbortException extends BaseException {
 }
 
 exports.AbortException = AbortException;
-const NullCharactersRegExp = /\x00/g;
+const NullCharactersRegExp = /\x00+/g;
+const InvisibleCharactersRegExp = /[\x01-\x1F]/g;
 
-function removeNullCharacters(str) {
+function removeNullCharacters(str, replaceInvisible = false) {
   if (typeof str !== "string") {
     warn("The argument for removeNullCharacters must be a string.");
     return str;
   }
 
+  if (replaceInvisible) {
+    str = str.replace(InvisibleCharactersRegExp, " ");
+  }
+
   return str.replace(NullCharactersRegExp, "");
 }
 
@@ -826,6 +831,75 @@ class Util {
     return result;
   }
 
+  static bezierBoundingBox(x0, y0, x1, y1, x2, y2, x3, y3) {
+    const tvalues = [],
+          bounds = [[], []];
+    let a, b, c, t, t1, t2, b2ac, sqrtb2ac;
+
+    for (let i = 0; i < 2; ++i) {
+      if (i === 0) {
+        b = 6 * x0 - 12 * x1 + 6 * x2;
+        a = -3 * x0 + 9 * x1 - 9 * x2 + 3 * x3;
+        c = 3 * x1 - 3 * x0;
+      } else {
+        b = 6 * y0 - 12 * y1 + 6 * y2;
+        a = -3 * y0 + 9 * y1 - 9 * y2 + 3 * y3;
+        c = 3 * y1 - 3 * y0;
+      }
+
+      if (Math.abs(a) < 1e-12) {
+        if (Math.abs(b) < 1e-12) {
+          continue;
+        }
+
+        t = -c / b;
+
+        if (0 < t && t < 1) {
+          tvalues.push(t);
+        }
+
+        continue;
+      }
+
+      b2ac = b * b - 4 * c * a;
+      sqrtb2ac = Math.sqrt(b2ac);
+
+      if (b2ac < 0) {
+        continue;
+      }
+
+      t1 = (-b + sqrtb2ac) / (2 * a);
+
+      if (0 < t1 && t1 < 1) {
+        tvalues.push(t1);
+      }
+
+      t2 = (-b - sqrtb2ac) / (2 * a);
+
+      if (0 < t2 && t2 < 1) {
+        tvalues.push(t2);
+      }
+    }
+
+    let j = tvalues.length,
+        mt;
+    const jlen = j;
+
+    while (j--) {
+      t = tvalues[j];
+      mt = 1 - t;
+      bounds[0][j] = mt * mt * mt * x0 + 3 * mt * mt * t * x1 + 3 * mt * t * t * x2 + t * t * t * x3;
+      bounds[1][j] = mt * mt * mt * y0 + 3 * mt * mt * t * y1 + 3 * mt * t * t * y2 + t * t * t * y3;
+    }
+
+    bounds[0][jlen] = x0;
+    bounds[1][jlen] = y0;
+    bounds[0][jlen + 1] = x3;
+    bounds[1][jlen + 1] = y3;
+    bounds[0].length = bounds[1].length = jlen + 2;
+    return [Math.min(...bounds[0]), Math.min(...bounds[1]), Math.max(...bounds[0]), Math.max(...bounds[1])];
+  }
+
 }
 
 exports.Util = Util;
@@ -947,7 +1021,7 @@ function createPromiseCapability() {
 }
 
 function createObjectURL(data, contentType = "", forceDataSchema = false) {
-  if (URL.createObjectURL && !forceDataSchema) {
+  if (URL.createObjectURL && typeof Blob !== "undefined" && !forceDataSchema) {
     return URL.createObjectURL(new Blob([data], {
       type: contentType
     }));

+ 18 - 18
lib/test/unit/annotation_spec.js

@@ -1460,10 +1460,10 @@ describe("annotation", function () {
       partialEvaluator.xref = xref;
       const annotation = await _annotation.AnnotationFactory.create(xref, textWidgetRef, pdfManagerMock, idFactoryMock);
       const annotationStorage = new Map();
-      const operatorList = await annotation.getOperatorList(partialEvaluator, task, false, annotationStorage);
+      const operatorList = await annotation.getOperatorList(partialEvaluator, task, _util.RenderingIntentFlag.PRINT, false, annotationStorage);
       expect(operatorList.argsArray.length).toEqual(3);
       expect(operatorList.fnArray).toEqual([_util.OPS.beginAnnotation, _util.OPS.setFillRGBColor, _util.OPS.endAnnotation]);
-      expect(operatorList.argsArray[0]).toEqual(["271R", [0, 0, 32, 10], [32, 0, 0, 10, 0, 0], [1, 0, 0, 1, 0, 0]]);
+      expect(operatorList.argsArray[0]).toEqual(["271R", [0, 0, 32, 10], [32, 0, 0, 10, 0, 0], [1, 0, 0, 1, 0, 0], false]);
       expect(operatorList.argsArray[1]).toEqual(new Uint8ClampedArray([26, 51, 76]));
     });
     it("should render auto-sized text for printing", async function () {
@@ -1872,10 +1872,10 @@ describe("annotation", function () {
       annotationStorage.set(annotation.data.id, {
         value: true
       });
-      const operatorList = await annotation.getOperatorList(checkboxEvaluator, task, false, annotationStorage);
+      const operatorList = await annotation.getOperatorList(checkboxEvaluator, task, _util.RenderingIntentFlag.PRINT, false, annotationStorage);
       expect(operatorList.argsArray.length).toEqual(5);
       expect(operatorList.fnArray).toEqual([_util.OPS.beginAnnotation, _util.OPS.dependency, _util.OPS.setFont, _util.OPS.showText, _util.OPS.endAnnotation]);
-      expect(operatorList.argsArray[0]).toEqual(["124R", [0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [1, 0, 0, 1, 0, 0]]);
+      expect(operatorList.argsArray[0]).toEqual(["124R", [0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [1, 0, 0, 1, 0, 0], false]);
       expect(operatorList.argsArray[3][0][0].unicode).toEqual("4");
     });
     it("should render checkboxes for printing", async function () {
@@ -1907,18 +1907,18 @@ describe("annotation", function () {
       annotationStorage.set(annotation.data.id, {
         value: true
       });
-      let operatorList = await annotation.getOperatorList(partialEvaluator, task, false, annotationStorage);
+      let operatorList = await annotation.getOperatorList(partialEvaluator, task, _util.RenderingIntentFlag.PRINT, false, annotationStorage);
       expect(operatorList.argsArray.length).toEqual(3);
       expect(operatorList.fnArray).toEqual([_util.OPS.beginAnnotation, _util.OPS.setFillRGBColor, _util.OPS.endAnnotation]);
-      expect(operatorList.argsArray[0]).toEqual(["124R", [0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [1, 0, 0, 1, 0, 0]]);
+      expect(operatorList.argsArray[0]).toEqual(["124R", [0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [1, 0, 0, 1, 0, 0], false]);
       expect(operatorList.argsArray[1]).toEqual(new Uint8ClampedArray([26, 51, 76]));
       annotationStorage.set(annotation.data.id, {
         value: false
       });
-      operatorList = await annotation.getOperatorList(partialEvaluator, task, false, annotationStorage);
+      operatorList = await annotation.getOperatorList(partialEvaluator, task, _util.RenderingIntentFlag.PRINT, false, annotationStorage);
       expect(operatorList.argsArray.length).toEqual(3);
       expect(operatorList.fnArray).toEqual([_util.OPS.beginAnnotation, _util.OPS.setFillRGBColor, _util.OPS.endAnnotation]);
-      expect(operatorList.argsArray[0]).toEqual(["124R", [0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [1, 0, 0, 1, 0, 0]]);
+      expect(operatorList.argsArray[0]).toEqual(["124R", [0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [1, 0, 0, 1, 0, 0], false]);
       expect(operatorList.argsArray[1]).toEqual(new Uint8ClampedArray([76, 51, 26]));
     });
     it("should render checkboxes for printing twice", async function () {
@@ -1953,10 +1953,10 @@ describe("annotation", function () {
         annotationStorage.set(annotation.data.id, {
           value: true
         });
-        const operatorList = await annotation.getOperatorList(partialEvaluator, task, false, annotationStorage);
+        const operatorList = await annotation.getOperatorList(partialEvaluator, task, _util.RenderingIntentFlag.PRINT, false, annotationStorage);
         expect(operatorList.argsArray.length).toEqual(3);
         expect(operatorList.fnArray).toEqual([_util.OPS.beginAnnotation, _util.OPS.setFillRGBColor, _util.OPS.endAnnotation]);
-        expect(operatorList.argsArray[0]).toEqual(["1249R", [0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [1, 0, 0, 1, 0, 0]]);
+        expect(operatorList.argsArray[0]).toEqual(["1249R", [0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [1, 0, 0, 1, 0, 0], false]);
         expect(operatorList.argsArray[1]).toEqual(new Uint8ClampedArray([26, 51, 76]));
       }
     });
@@ -1987,10 +1987,10 @@ describe("annotation", function () {
       const task = new _worker.WorkerTask("test print");
       const annotation = await _annotation.AnnotationFactory.create(xref, buttonWidgetRef, pdfManagerMock, idFactoryMock);
       const annotationStorage = new Map();
-      const operatorList = await annotation.getOperatorList(partialEvaluator, task, false, annotationStorage);
+      const operatorList = await annotation.getOperatorList(partialEvaluator, task, _util.RenderingIntentFlag.PRINT, false, annotationStorage);
       expect(operatorList.argsArray.length).toEqual(3);
       expect(operatorList.fnArray).toEqual([_util.OPS.beginAnnotation, _util.OPS.setFillRGBColor, _util.OPS.endAnnotation]);
-      expect(operatorList.argsArray[0]).toEqual(["124R", [0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [1, 0, 0, 1, 0, 0]]);
+      expect(operatorList.argsArray[0]).toEqual(["124R", [0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [1, 0, 0, 1, 0, 0], false]);
       expect(operatorList.argsArray[1]).toEqual(new Uint8ClampedArray([26, 51, 76]));
     });
     it("should save checkboxes", async function () {
@@ -2130,18 +2130,18 @@ describe("annotation", function () {
       annotationStorage.set(annotation.data.id, {
         value: true
       });
-      let operatorList = await annotation.getOperatorList(partialEvaluator, task, false, annotationStorage);
+      let operatorList = await annotation.getOperatorList(partialEvaluator, task, _util.RenderingIntentFlag.PRINT, false, annotationStorage);
       expect(operatorList.argsArray.length).toEqual(3);
       expect(operatorList.fnArray).toEqual([_util.OPS.beginAnnotation, _util.OPS.setFillRGBColor, _util.OPS.endAnnotation]);
-      expect(operatorList.argsArray[0]).toEqual(["124R", [0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [1, 0, 0, 1, 0, 0]]);
+      expect(operatorList.argsArray[0]).toEqual(["124R", [0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [1, 0, 0, 1, 0, 0], false]);
       expect(operatorList.argsArray[1]).toEqual(new Uint8ClampedArray([26, 51, 76]));
       annotationStorage.set(annotation.data.id, {
         value: false
       });
-      operatorList = await annotation.getOperatorList(partialEvaluator, task, false, annotationStorage);
+      operatorList = await annotation.getOperatorList(partialEvaluator, task, _util.RenderingIntentFlag.PRINT, false, annotationStorage);
       expect(operatorList.argsArray.length).toEqual(3);
       expect(operatorList.fnArray).toEqual([_util.OPS.beginAnnotation, _util.OPS.setFillRGBColor, _util.OPS.endAnnotation]);
-      expect(operatorList.argsArray[0]).toEqual(["124R", [0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [1, 0, 0, 1, 0, 0]]);
+      expect(operatorList.argsArray[0]).toEqual(["124R", [0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [1, 0, 0, 1, 0, 0], false]);
       expect(operatorList.argsArray[1]).toEqual(new Uint8ClampedArray([76, 51, 26]));
     });
     it("should render radio buttons for printing using normal appearance", async function () {
@@ -2172,10 +2172,10 @@ describe("annotation", function () {
       const task = new _worker.WorkerTask("test print");
       const annotation = await _annotation.AnnotationFactory.create(xref, buttonWidgetRef, pdfManagerMock, idFactoryMock);
       const annotationStorage = new Map();
-      const operatorList = await annotation.getOperatorList(partialEvaluator, task, false, annotationStorage);
+      const operatorList = await annotation.getOperatorList(partialEvaluator, task, _util.RenderingIntentFlag.PRINT, false, annotationStorage);
       expect(operatorList.argsArray.length).toEqual(3);
       expect(operatorList.fnArray).toEqual([_util.OPS.beginAnnotation, _util.OPS.setFillRGBColor, _util.OPS.endAnnotation]);
-      expect(operatorList.argsArray[0]).toEqual(["124R", [0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [1, 0, 0, 1, 0, 0]]);
+      expect(operatorList.argsArray[0]).toEqual(["124R", [0, 0, 0, 0], [0, 0, 0, 0, 0, 0], [1, 0, 0, 1, 0, 0], false]);
       expect(operatorList.argsArray[1]).toEqual(new Uint8ClampedArray([76, 51, 26]));
     });
     it("should save radio buttons", async function () {

+ 328 - 17
lib/test/unit/api_spec.js

@@ -58,6 +58,10 @@ describe("api", function () {
     }, WAIT_TIMEOUT);
   }
 
+  function mergeText(items) {
+    return items.map(chunk => chunk.str + (chunk.hasEOL ? "\n" : "")).join("");
+  }
+
   describe("getDocument", function () {
     it("creates pdf doc from URL-string", async function () {
       const urlStr = _test_utils.TEST_PDFS_PATH + basicApiFileName;
@@ -311,6 +315,138 @@ describe("api", function () {
       expect(+docNum1).toBeLessThan(+docNum2);
       await Promise.all([loadingTask1.destroy(), loadingTask2.destroy()]);
     });
+    it("creates pdf doc from PDF file with bad XRef entry", async function () {
+      const loadingTask = (0, _api.getDocument)((0, _test_utils.buildGetDocumentParams)("PDFBOX-4352-0.pdf", {
+        rangeChunkSize: 100
+      }));
+      expect(loadingTask instanceof _api.PDFDocumentLoadingTask).toEqual(true);
+      const pdfDocument = await loadingTask.promise;
+      expect(pdfDocument.numPages).toEqual(1);
+      const page = await pdfDocument.getPage(1);
+      expect(page instanceof _api.PDFPageProxy).toEqual(true);
+      const opList = await page.getOperatorList();
+      expect(opList.fnArray.length).toEqual(0);
+      expect(opList.argsArray.length).toEqual(0);
+      expect(opList.lastChunk).toEqual(true);
+      await loadingTask.destroy();
+    });
+    it("creates pdf doc from PDF file with bad XRef header", async function () {
+      const loadingTask = (0, _api.getDocument)((0, _test_utils.buildGetDocumentParams)("GHOSTSCRIPT-698804-1-fuzzed.pdf"));
+      expect(loadingTask instanceof _api.PDFDocumentLoadingTask).toEqual(true);
+      const pdfDocument = await loadingTask.promise;
+      expect(pdfDocument.numPages).toEqual(1);
+      const page = await pdfDocument.getPage(1);
+      expect(page instanceof _api.PDFPageProxy).toEqual(true);
+      const opList = await page.getOperatorList();
+      expect(opList.fnArray.length).toEqual(0);
+      expect(opList.argsArray.length).toEqual(0);
+      expect(opList.lastChunk).toEqual(true);
+      await loadingTask.destroy();
+    });
+    it("creates pdf doc from PDF file with bad XRef byteWidths", async function () {
+      const loadingTask = (0, _api.getDocument)((0, _test_utils.buildGetDocumentParams)("REDHAT-1531897-0.pdf"));
+      expect(loadingTask instanceof _api.PDFDocumentLoadingTask).toEqual(true);
+
+      try {
+        await loadingTask.promise;
+        expect(false).toEqual(true);
+      } catch (reason) {
+        expect(reason instanceof _util.InvalidPDFException).toEqual(true);
+        expect(reason.message).toEqual("Invalid PDF structure.");
+      }
+
+      await loadingTask.destroy();
+    });
+    it("creates pdf doc from PDF file with inaccessible /Pages tree", async function () {
+      const loadingTask = (0, _api.getDocument)((0, _test_utils.buildGetDocumentParams)("poppler-395-0-fuzzed.pdf"));
+      expect(loadingTask instanceof _api.PDFDocumentLoadingTask).toEqual(true);
+
+      try {
+        await loadingTask.promise;
+        expect(false).toEqual(true);
+      } catch (reason) {
+        expect(reason instanceof _util.InvalidPDFException).toEqual(true);
+        expect(reason.message).toEqual("Invalid Root reference.");
+      }
+
+      await loadingTask.destroy();
+    });
+    it("creates pdf doc from PDF files, with bad /Pages tree /Count", async function () {
+      const loadingTask1 = (0, _api.getDocument)((0, _test_utils.buildGetDocumentParams)("poppler-67295-0.pdf"));
+      const loadingTask2 = (0, _api.getDocument)((0, _test_utils.buildGetDocumentParams)("poppler-85140-0.pdf"));
+      expect(loadingTask1 instanceof _api.PDFDocumentLoadingTask).toEqual(true);
+      expect(loadingTask2 instanceof _api.PDFDocumentLoadingTask).toEqual(true);
+      const pdfDocument1 = await loadingTask1.promise;
+      const pdfDocument2 = await loadingTask2.promise;
+      expect(pdfDocument1.numPages).toEqual(1);
+      expect(pdfDocument2.numPages).toEqual(1);
+      const page = await pdfDocument1.getPage(1);
+      expect(page instanceof _api.PDFPageProxy).toEqual(true);
+      const opList = await page.getOperatorList();
+      expect(opList.fnArray.length).toBeGreaterThan(5);
+      expect(opList.argsArray.length).toBeGreaterThan(5);
+      expect(opList.lastChunk).toEqual(true);
+
+      try {
+        await pdfDocument2.getPage(1);
+        expect(false).toEqual(true);
+      } catch (reason) {
+        expect(reason instanceof _util.UnknownErrorException).toEqual(true);
+        expect(reason.message).toEqual("Bad (uncompressed) XRef entry: 3R");
+      }
+
+      await Promise.all([loadingTask1.destroy(), loadingTask2.destroy()]);
+    });
+    it("creates pdf doc from PDF files, with circular references", async function () {
+      const loadingTask1 = (0, _api.getDocument)((0, _test_utils.buildGetDocumentParams)("poppler-91414-0-53.pdf"));
+      const loadingTask2 = (0, _api.getDocument)((0, _test_utils.buildGetDocumentParams)("poppler-91414-0-54.pdf"));
+      expect(loadingTask1 instanceof _api.PDFDocumentLoadingTask).toEqual(true);
+      expect(loadingTask2 instanceof _api.PDFDocumentLoadingTask).toEqual(true);
+      const pdfDocument1 = await loadingTask1.promise;
+      const pdfDocument2 = await loadingTask2.promise;
+      expect(pdfDocument1.numPages).toEqual(1);
+      expect(pdfDocument2.numPages).toEqual(1);
+      const pageA = await pdfDocument1.getPage(1);
+      const pageB = await pdfDocument2.getPage(1);
+      expect(pageA instanceof _api.PDFPageProxy).toEqual(true);
+      expect(pageB instanceof _api.PDFPageProxy).toEqual(true);
+
+      for (const opList of [await pageA.getOperatorList(), await pageB.getOperatorList()]) {
+        expect(opList.fnArray.length).toBeGreaterThan(5);
+        expect(opList.argsArray.length).toBeGreaterThan(5);
+        expect(opList.lastChunk).toEqual(true);
+      }
+
+      await Promise.all([loadingTask1.destroy(), loadingTask2.destroy()]);
+    });
+    it("creates pdf doc from PDF files, with bad /Pages tree /Kids entries", async function () {
+      const loadingTask1 = (0, _api.getDocument)((0, _test_utils.buildGetDocumentParams)("poppler-742-0-fuzzed.pdf"));
+      const loadingTask2 = (0, _api.getDocument)((0, _test_utils.buildGetDocumentParams)("poppler-937-0-fuzzed.pdf"));
+      expect(loadingTask1 instanceof _api.PDFDocumentLoadingTask).toEqual(true);
+      expect(loadingTask2 instanceof _api.PDFDocumentLoadingTask).toEqual(true);
+      const pdfDocument1 = await loadingTask1.promise;
+      const pdfDocument2 = await loadingTask2.promise;
+      expect(pdfDocument1.numPages).toEqual(1);
+      expect(pdfDocument2.numPages).toEqual(1);
+
+      try {
+        await pdfDocument1.getPage(1);
+        expect(false).toEqual(true);
+      } catch (reason) {
+        expect(reason instanceof _util.UnknownErrorException).toEqual(true);
+        expect(reason.message).toEqual("Page dictionary kids object is not an array.");
+      }
+
+      try {
+        await pdfDocument2.getPage(1);
+        expect(false).toEqual(true);
+      } catch (reason) {
+        expect(reason instanceof _util.UnknownErrorException).toEqual(true);
+        expect(reason.message).toEqual("Page dictionary kids object is not an array.");
+      }
+
+      await Promise.all([loadingTask1.destroy(), loadingTask2.destroy()]);
+    });
   });
   describe("PDFWorker", function () {
     it("worker created or destroyed", async function () {
@@ -468,13 +604,23 @@ describe("api", function () {
         return pdfDoc.getPage(2).then(function (pdfPage) {
           throw new Error("shall fail for invalid page");
         }, function (reason) {
-          expect(reason instanceof Error).toEqual(true);
+          expect(reason instanceof _util.UnknownErrorException).toEqual(true);
           expect(reason.message).toEqual("Pages tree contains circular reference.");
         });
       });
       await Promise.all([page1, page2]);
       await loadingTask.destroy();
     });
+    it("gets page multiple time, with working caches", async function () {
+      const promiseA = pdfDocument.getPage(1);
+      const promiseB = pdfDocument.getPage(1);
+      expect(promiseA instanceof Promise).toEqual(true);
+      expect(promiseA).toBe(promiseB);
+      const pageA = await promiseA;
+      const pageB = await promiseB;
+      expect(pageA instanceof _api.PDFPageProxy).toEqual(true);
+      expect(pageA).toBe(pageB);
+    });
     it("gets page index", async function () {
       const ref = {
         num: 17,
@@ -493,7 +639,8 @@ describe("api", function () {
         await pdfDocument.getPageIndex(ref);
         expect(false).toEqual(true);
       } catch (reason) {
-        expect(reason instanceof Error).toEqual(true);
+        expect(reason instanceof _util.UnknownErrorException).toEqual(true);
+        expect(reason.message).toEqual("The reference does not point to a /Page dictionary.");
       }
     });
     it("gets destinations, from /Dests dictionary", async function () {
@@ -806,6 +953,16 @@ describe("api", function () {
       expect(outlineItemOne.color).toEqual(new Uint8ClampedArray([0, 0, 0]));
       await loadingTask.destroy();
     });
+    it("gets outline with non-displayable chars", async function () {
+      const loadingTask = (0, _api.getDocument)((0, _test_utils.buildGetDocumentParams)("issue14267.pdf"));
+      const pdfDoc = await loadingTask.promise;
+      const outline = await pdfDoc.getOutline();
+      expect(Array.isArray(outline)).toEqual(true);
+      expect(outline.length).toEqual(1);
+      const outlineItem = outline[0];
+      expect(outlineItem.title).toEqual("hello\x11world");
+      await loadingTask.destroy();
+    });
     it("gets non-existent permissions", async function () {
       const permissions = await pdfDocument.getPermissions();
       expect(permissions).toEqual(null);
@@ -844,6 +1001,8 @@ describe("api", function () {
       expect(info.Title).toEqual("Basic API Test");
       expect(info.Custom).toEqual(undefined);
       expect(info.PDFFormatVersion).toEqual("1.7");
+      expect(info.Language).toEqual("en");
+      expect(info.EncryptFilterName).toEqual(null);
       expect(info.IsLinearized).toEqual(false);
       expect(info.IsAcroFormPresent).toEqual(false);
       expect(info.IsXFAPresent).toEqual(false);
@@ -870,6 +1029,8 @@ describe("api", function () {
       expect(typeof custom === "object" && custom !== null).toEqual(true);
       expect(custom["PTEX.Fullbanner"]).toEqual("This is pdfeTeX, " + "Version 3.141592-1.21a-2.2 (Web2C 7.5.4) kpathsea version 3.5.6");
       expect(info.PDFFormatVersion).toEqual("1.4");
+      expect(info.Language).toEqual(null);
+      expect(info.EncryptFilterName).toEqual(null);
       expect(info.IsLinearized).toEqual(false);
       expect(info.IsAcroFormPresent).toEqual(false);
       expect(info.IsXFAPresent).toEqual(false);
@@ -889,7 +1050,10 @@ describe("api", function () {
         contentDispositionFilename,
         contentLength
       } = await pdfDoc.getMetadata();
+      expect(info.Custom).toEqual(undefined);
       expect(info.PDFFormatVersion).toEqual(null);
+      expect(info.Language).toEqual(null);
+      expect(info.EncryptFilterName).toEqual(null);
       expect(info.IsLinearized).toEqual(false);
       expect(info.IsAcroFormPresent).toEqual(false);
       expect(info.IsXFAPresent).toEqual(false);
@@ -900,6 +1064,29 @@ describe("api", function () {
       expect(contentLength).toEqual(624);
       await loadingTask.destroy();
     });
+    it("gets metadata, with corrupt /Metadata XRef entry", async function () {
+      const loadingTask = (0, _api.getDocument)((0, _test_utils.buildGetDocumentParams)("PDFBOX-3148-2-fuzzed.pdf"));
+      const pdfDoc = await loadingTask.promise;
+      const {
+        info,
+        metadata,
+        contentDispositionFilename,
+        contentLength
+      } = await pdfDoc.getMetadata();
+      expect(info.Custom).toEqual(undefined);
+      expect(info.PDFFormatVersion).toEqual("1.6");
+      expect(info.Language).toEqual(null);
+      expect(info.EncryptFilterName).toEqual(null);
+      expect(info.IsLinearized).toEqual(false);
+      expect(info.IsAcroFormPresent).toEqual(true);
+      expect(info.IsXFAPresent).toEqual(false);
+      expect(info.IsCollectionPresent).toEqual(false);
+      expect(info.IsSignaturesPresent).toEqual(false);
+      expect(metadata).toEqual(null);
+      expect(contentDispositionFilename).toEqual(null);
+      expect(contentLength).toEqual(244351);
+      await loadingTask.destroy();
+    });
     it("gets markInfo", async function () {
       const loadingTask = (0, _api.getDocument)((0, _test_utils.buildGetDocumentParams)("annotation-line.pdf"));
       const pdfDoc = await loadingTask.promise;
@@ -920,11 +1107,8 @@ describe("api", function () {
       });
     });
     it("gets document stats", async function () {
-      const stats = await pdfDocument.getStats();
-      expect(stats).toEqual({
-        streamTypes: {},
-        fontTypes: {}
-      });
+      const stats = pdfDocument.stats;
+      expect(stats).toEqual(null);
     });
     it("cleans up document resources", async function () {
       await pdfDocument.cleanup();
@@ -1191,10 +1375,15 @@ describe("api", function () {
       });
       const data = await Promise.all([defaultPromise, parametersPromise]);
       expect(!!data[0].items).toEqual(true);
-      expect(data[0].items.length).toEqual(12);
+      expect(data[0].items.length).toEqual(11);
       expect(!!data[0].styles).toEqual(true);
+      const page1 = mergeText(data[0].items);
+      expect(page1).toEqual(`Table Of Content
+Chapter 1 .......................................................... 2
+Paragraph 1.1 ...................................................... 3
+page 1 / 3`);
       expect(!!data[1].items).toEqual(true);
-      expect(data[1].items.length).toEqual(7);
+      expect(data[1].items.length).toEqual(6);
       expect(!!data[1].styles).toEqual(true);
     });
     it("gets text content, with correct properties (issue 8276)", async function () {
@@ -1225,6 +1414,113 @@ describe("api", function () {
       });
       await loadingTask.destroy();
     });
+    it("gets text content, with no extra spaces (issue 13226)", async function () {
+      const loadingTask = (0, _api.getDocument)((0, _test_utils.buildGetDocumentParams)("issue13226.pdf"));
+      const pdfDoc = await loadingTask.promise;
+      const pdfPage = await pdfDoc.getPage(1);
+      const {
+        items
+      } = await pdfPage.getTextContent();
+      const text = mergeText(items);
+      expect(text).toEqual("Mitarbeiterinnen und Mitarbeiter arbeiten in über 100 Ländern engagiert im Dienste");
+      await loadingTask.destroy();
+    });
+    it("gets text content, with merged spaces (issue 13201)", async function () {
+      const loadingTask = (0, _api.getDocument)((0, _test_utils.buildGetDocumentParams)("issue13201.pdf"));
+      const pdfDoc = await loadingTask.promise;
+      const pdfPage = await pdfDoc.getPage(1);
+      const {
+        items
+      } = await pdfPage.getTextContent();
+      const text = mergeText(items);
+      expect(text.includes("Abstract. A purely peer-to-peer version of electronic cash would allow online")).toEqual(true);
+      expect(text.includes("avoid mediating disputes. The cost of mediation increases transaction costs, limiting the")).toEqual(true);
+      expect(text.includes("system is secure as long as honest nodes collectively control more CPU power than any")).toEqual(true);
+      await loadingTask.destroy();
+    });
+    it("gets text content, with no spaces between letters of words (issue 11913)", async function () {
+      const loadingTask = (0, _api.getDocument)((0, _test_utils.buildGetDocumentParams)("issue11913.pdf"));
+      const pdfDoc = await loadingTask.promise;
+      const pdfPage = await pdfDoc.getPage(1);
+      const {
+        items
+      } = await pdfPage.getTextContent();
+      const text = mergeText(items);
+      expect(text.includes("1. The first of these cases arises from the tragic handicap which has blighted the life of the Plaintiff, and from the response of the")).toEqual(true);
+      expect(text.includes("argued in this Court the appeal raises narrower, but important, issues which may be summarised as follows:-")).toEqual(true);
+      await loadingTask.destroy();
+    });
+    it("gets text content, with merged spaces (issue 10900)", async function () {
+      const loadingTask = (0, _api.getDocument)((0, _test_utils.buildGetDocumentParams)("issue10900.pdf"));
+      const pdfDoc = await loadingTask.promise;
+      const pdfPage = await pdfDoc.getPage(1);
+      const {
+        items
+      } = await pdfPage.getTextContent();
+      const text = mergeText(items);
+      expect(text.includes(`3 3 3 3
+851.5 854.9 839.3 837.5
+633.6 727.8 789.9 796.2
+1,485.1 1,582.7 1,629.2 1,633.7
+114.2 121.7 125.3 130.7
+13.0x 13.0x 13.0x 12.5x`)).toEqual(true);
+      await loadingTask.destroy();
+    });
+    it("gets text content, with spaces (issue 10640)", async function () {
+      const loadingTask = (0, _api.getDocument)((0, _test_utils.buildGetDocumentParams)("issue10640.pdf"));
+      const pdfDoc = await loadingTask.promise;
+      const pdfPage = await pdfDoc.getPage(1);
+      const {
+        items
+      } = await pdfPage.getTextContent();
+      const text = mergeText(items);
+      expect(text.includes(`Open Sans is a humanist sans serif typeface designed by Steve Matteson.
+Open Sans was designed with an upright stress, open forms and a neu-
+tral, yet friendly appearance. It was optimized for print, web, and mobile
+interfaces, and has excellent legibility characteristics in its letterforms (see
+figure \x81 on the following page). This font is available from the Google Font
+Directory [\x81] as TrueType files licensed under the Apache License version \x82.\x80.
+This package provides support for this font in LATEX. It includes Type \x81
+versions of the fonts, converted for this package using FontForge from its
+sources, for full support with Dvips.`)).toEqual(true);
+      await loadingTask.destroy();
+    });
+    it("gets text content, with negative spaces (bug 931481)", async function () {
+      if (_is_node.isNodeJS) {
+        pending("Linked test-cases are not supported in Node.js.");
+      }
+
+      const loadingTask = (0, _api.getDocument)((0, _test_utils.buildGetDocumentParams)("bug931481.pdf"));
+      const pdfDoc = await loadingTask.promise;
+      const pdfPage = await pdfDoc.getPage(1);
+      const {
+        items
+      } = await pdfPage.getTextContent();
+      const text = mergeText(items);
+      expect(text.includes(`Kathrin Nachbaur
+Die promovierte Juristin ist 1979 in Graz geboren und aufgewachsen. Nach
+erfolgreichem Studienabschluss mit Fokus auf Europarecht absolvierte sie ein
+Praktikum bei Magna International in Kanada in der Human Resources Abteilung.
+Anschliessend wurde sie geschult in Human Resources, Arbeitsrecht und
+Kommunikation, währenddessen sie auch an ihrem Doktorat im Wirtschaftsrecht
+arbeitete. Seither arbeitete sie bei Magna International als Projekt Manager in der
+Innovationsabteilung. Seit 2009 ist sie Frank Stronachs Büroleiterin in Österreich und
+Kanada. Zusätzlich ist sie seit 2012 Vice President, Business Development der
+Stronach Group und Vizepräsidentin und Institutsleiterin des Stronach Institut für
+sozialökonomische Gerechtigkeit.`)).toEqual(true);
+      await loadingTask.destroy();
+    });
+    it("gets text content, with beginbfrange operator handled correctly (bug 1627427)", async function () {
+      const loadingTask = (0, _api.getDocument)((0, _test_utils.buildGetDocumentParams)("bug1627427_reduced.pdf"));
+      const pdfDoc = await loadingTask.promise;
+      const pdfPage = await pdfDoc.getPage(1);
+      const {
+        items
+      } = await pdfPage.getTextContent();
+      const text = mergeText(items);
+      expect(text).toEqual("침하게 흐린 품이 눈이 올 듯하더니 눈은 아니 오고 얼다가 만 비가 추");
+      await loadingTask.destroy();
+    });
     it("gets empty structure tree", async function () {
       const tree = await page.getStructTree();
       expect(tree).toEqual(null);
@@ -1238,6 +1534,7 @@ describe("api", function () {
         role: "Root",
         children: [{
           role: "Document",
+          lang: "en-US",
           children: [{
             role: "H1",
             children: [{
@@ -1379,15 +1676,29 @@ describe("api", function () {
       expect(opListAnnotEnable.fnArray.length).toBeLessThan(opListAnnotEnableStorage.fnArray.length);
       await loadingTask.destroy();
     });
+    it("gets operatorList, with page resources containing corrupt /CCITTFaxDecode data", async function () {
+      const loadingTask = (0, _api.getDocument)((0, _test_utils.buildGetDocumentParams)("poppler-90-0-fuzzed.pdf"));
+      expect(loadingTask instanceof _api.PDFDocumentLoadingTask).toEqual(true);
+      const pdfDoc = await loadingTask.promise;
+      expect(pdfDoc.numPages).toEqual(16);
+      const pdfPage = await pdfDoc.getPage(6);
+      expect(pdfPage instanceof _api.PDFPageProxy).toEqual(true);
+      const opList = await pdfPage.getOperatorList();
+      expect(opList.fnArray.length).toBeGreaterThan(25);
+      expect(opList.argsArray.length).toBeGreaterThan(25);
+      expect(opList.lastChunk).toEqual(true);
+      await loadingTask.destroy();
+    });
     it("gets document stats after parsing page", async function () {
-      const stats = await page.getOperatorList().then(function () {
-        return pdfDocument.getStats();
-      });
-      const expectedStreamTypes = {};
-      expectedStreamTypes[_util.StreamType.FLATE] = true;
-      const expectedFontTypes = {};
-      expectedFontTypes[_util.FontType.TYPE1STANDARD] = true;
-      expectedFontTypes[_util.FontType.CIDFONTTYPE2] = true;
+      await page.getOperatorList();
+      const stats = pdfDocument.stats;
+      const expectedStreamTypes = {
+        [_util.StreamType.FLATE]: true
+      };
+      const expectedFontTypes = {
+        [_util.FontType.TYPE1STANDARD]: true,
+        [_util.FontType.CIDFONTTYPE2]: true
+      };
       expect(stats).toEqual({
         streamTypes: expectedStreamTypes,
         fontTypes: expectedFontTypes

+ 108 - 0
lib/test/unit/base_viewer_spec.js

@@ -0,0 +1,108 @@
+/**
+ * @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";
+
+var _base_viewer = require("../../web/base_viewer.js");
+
+describe("BaseViewer", function () {
+  describe("PDFPageViewBuffer", function () {
+    function createViewsMap(startId, endId) {
+      const map = new Map();
+
+      for (let id = startId; id <= endId; id++) {
+        map.set(id, {
+          id,
+          destroy: () => {}
+        });
+      }
+
+      return map;
+    }
+
+    it("handles `push` correctly", function () {
+      const buffer = new _base_viewer.PDFPageViewBuffer(3);
+      const viewsMap = createViewsMap(1, 5),
+            iterator = viewsMap.values();
+
+      for (let i = 0; i < 3; i++) {
+        const view = iterator.next().value;
+        buffer.push(view);
+      }
+
+      expect([...buffer]).toEqual([viewsMap.get(1), viewsMap.get(2), viewsMap.get(3)]);
+
+      for (let i = 3; i < 5; i++) {
+        const view = iterator.next().value;
+        buffer.push(view);
+      }
+
+      expect([...buffer]).toEqual([viewsMap.get(3), viewsMap.get(4), viewsMap.get(5)]);
+    });
+    it("handles `resize` correctly", function () {
+      const buffer = new _base_viewer.PDFPageViewBuffer(5);
+      const viewsMap = createViewsMap(1, 5),
+            iterator = viewsMap.values();
+
+      for (let i = 0; i < 5; i++) {
+        const view = iterator.next().value;
+        buffer.push(view);
+      }
+
+      buffer.resize(5);
+      expect([...buffer]).toEqual([viewsMap.get(1), viewsMap.get(2), viewsMap.get(3), viewsMap.get(4), viewsMap.get(5)]);
+      buffer.resize(10);
+      expect([...buffer]).toEqual([viewsMap.get(1), viewsMap.get(2), viewsMap.get(3), viewsMap.get(4), viewsMap.get(5)]);
+      buffer.resize(3);
+      expect([...buffer]).toEqual([viewsMap.get(3), viewsMap.get(4), viewsMap.get(5)]);
+    });
+    it("handles `resize` correctly, with `idsToKeep` provided", function () {
+      const buffer = new _base_viewer.PDFPageViewBuffer(5);
+      const viewsMap = createViewsMap(1, 5),
+            iterator = viewsMap.values();
+
+      for (let i = 0; i < 5; i++) {
+        const view = iterator.next().value;
+        buffer.push(view);
+      }
+
+      buffer.resize(5, new Set([1, 2]));
+      expect([...buffer]).toEqual([viewsMap.get(3), viewsMap.get(4), viewsMap.get(5), viewsMap.get(1), viewsMap.get(2)]);
+      buffer.resize(10, new Set([3, 4, 5]));
+      expect([...buffer]).toEqual([viewsMap.get(1), viewsMap.get(2), viewsMap.get(3), viewsMap.get(4), viewsMap.get(5)]);
+      buffer.resize(3, new Set([1, 2, 5]));
+      expect([...buffer]).toEqual([viewsMap.get(1), viewsMap.get(2), viewsMap.get(5)]);
+    });
+    it("handles `has` correctly", function () {
+      const buffer = new _base_viewer.PDFPageViewBuffer(3);
+      const viewsMap = createViewsMap(1, 2),
+            iterator = viewsMap.values();
+
+      for (let i = 0; i < 1; i++) {
+        const view = iterator.next().value;
+        buffer.push(view);
+      }
+
+      expect(buffer.has(viewsMap.get(1))).toEqual(true);
+      expect(buffer.has(viewsMap.get(2))).toEqual(false);
+    });
+  });
+});

+ 18 - 0
lib/test/unit/bidi_spec.js

@@ -24,6 +24,18 @@
 var _bidi = require("../../core/bidi.js");
 
 describe("bidi", function () {
+  it("should mark text as LTR if there's only LTR-characters, " + "when the string is very short", function () {
+    const str = "foo";
+    const bidiText = (0, _bidi.bidi)(str, -1, false);
+    expect(bidiText.str).toEqual("foo");
+    expect(bidiText.dir).toEqual("ltr");
+  });
+  it("should mark text as LTR if there's only LTR-characters", function () {
+    const str = "Lorem ipsum dolor sit amet, consectetur adipisicing elit.";
+    const bidiText = (0, _bidi.bidi)(str, -1, false);
+    expect(bidiText.str).toEqual("Lorem ipsum dolor sit amet, consectetur adipisicing elit.");
+    expect(bidiText.dir).toEqual("ltr");
+  });
   it("should mark text as RTL if more than 30% of text is RTL", function () {
     const test = "\u0645\u0635\u0631 Egypt";
     const result = "Egypt \u0631\u0635\u0645";
@@ -38,4 +50,10 @@ describe("bidi", function () {
     expect(bidiText.str).toEqual(result);
     expect(bidiText.dir).toEqual("ltr");
   });
+  it("should mark text as RTL if less than 30% of text is RTL, " + "when the string is very short (issue 11656)", function () {
+    const str = "()\u05d1(";
+    const bidiText = (0, _bidi.bidi)(str, -1, false);
+    expect(bidiText.str).toEqual("(\u05d1)(");
+    expect(bidiText.dir).toEqual("rtl");
+  });
 });

+ 259 - 0
lib/test/unit/event_utils_spec.js

@@ -0,0 +1,259 @@
+/**
+ * @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";
+
+var _event_utils = require("../../web/event_utils.js");
+
+var _is_node = require("../../shared/is_node.js");
+
+describe("event_utils", function () {
+  describe("EventBus", function () {
+    it("dispatch event", function () {
+      const eventBus = new _event_utils.EventBus();
+      let count = 0;
+      eventBus.on("test", function (evt) {
+        expect(evt).toEqual(undefined);
+        count++;
+      });
+      eventBus.dispatch("test");
+      expect(count).toEqual(1);
+    });
+    it("dispatch event with arguments", function () {
+      const eventBus = new _event_utils.EventBus();
+      let count = 0;
+      eventBus.on("test", function (evt) {
+        expect(evt).toEqual({
+          abc: 123
+        });
+        count++;
+      });
+      eventBus.dispatch("test", {
+        abc: 123
+      });
+      expect(count).toEqual(1);
+    });
+    it("dispatch different event", function () {
+      const eventBus = new _event_utils.EventBus();
+      let count = 0;
+      eventBus.on("test", function () {
+        count++;
+      });
+      eventBus.dispatch("nottest");
+      expect(count).toEqual(0);
+    });
+    it("dispatch event multiple times", function () {
+      const eventBus = new _event_utils.EventBus();
+      let count = 0;
+      eventBus.dispatch("test");
+      eventBus.on("test", function () {
+        count++;
+      });
+      eventBus.dispatch("test");
+      eventBus.dispatch("test");
+      expect(count).toEqual(2);
+    });
+    it("dispatch event to multiple handlers", function () {
+      const eventBus = new _event_utils.EventBus();
+      let count = 0;
+      eventBus.on("test", function () {
+        count++;
+      });
+      eventBus.on("test", function () {
+        count++;
+      });
+      eventBus.dispatch("test");
+      expect(count).toEqual(2);
+    });
+    it("dispatch to detached", function () {
+      const eventBus = new _event_utils.EventBus();
+      let count = 0;
+
+      const listener = function () {
+        count++;
+      };
+
+      eventBus.on("test", listener);
+      eventBus.dispatch("test");
+      eventBus.off("test", listener);
+      eventBus.dispatch("test");
+      expect(count).toEqual(1);
+    });
+    it("dispatch to wrong detached", function () {
+      const eventBus = new _event_utils.EventBus();
+      let count = 0;
+      eventBus.on("test", function () {
+        count++;
+      });
+      eventBus.dispatch("test");
+      eventBus.off("test", function () {
+        count++;
+      });
+      eventBus.dispatch("test");
+      expect(count).toEqual(2);
+    });
+    it("dispatch to detached during handling", function () {
+      const eventBus = new _event_utils.EventBus();
+      let count = 0;
+
+      const listener1 = function () {
+        eventBus.off("test", listener2);
+        count++;
+      };
+
+      const listener2 = function () {
+        eventBus.off("test", listener1);
+        count++;
+      };
+
+      eventBus.on("test", listener1);
+      eventBus.on("test", listener2);
+      eventBus.dispatch("test");
+      eventBus.dispatch("test");
+      expect(count).toEqual(2);
+    });
+    it("dispatch event to handlers with/without 'once' option", function () {
+      const eventBus = new _event_utils.EventBus();
+      let multipleCount = 0,
+          onceCount = 0;
+      eventBus.on("test", function () {
+        multipleCount++;
+      });
+      eventBus.on("test", function () {
+        onceCount++;
+      }, {
+        once: true
+      });
+      eventBus.dispatch("test");
+      eventBus.dispatch("test");
+      eventBus.dispatch("test");
+      expect(multipleCount).toEqual(3);
+      expect(onceCount).toEqual(1);
+    });
+    it("should not re-dispatch to DOM", async function () {
+      if (_is_node.isNodeJS) {
+        pending("Document is not supported in Node.js.");
+      }
+
+      const eventBus = new _event_utils.EventBus();
+      let count = 0;
+      eventBus.on("test", function (evt) {
+        expect(evt).toEqual(undefined);
+        count++;
+      });
+
+      function domEventListener() {
+        expect(false).toEqual(true);
+      }
+
+      document.addEventListener("test", domEventListener);
+      eventBus.dispatch("test");
+      await Promise.resolve();
+      expect(count).toEqual(1);
+      document.removeEventListener("test", domEventListener);
+    });
+  });
+  describe("waitOnEventOrTimeout", function () {
+    let eventBus;
+    beforeAll(function () {
+      eventBus = new _event_utils.EventBus();
+    });
+    afterAll(function () {
+      eventBus = null;
+    });
+    it("should reject invalid parameters", async function () {
+      const invalidTarget = (0, _event_utils.waitOnEventOrTimeout)({
+        target: "window",
+        name: "DOMContentLoaded"
+      }).then(function () {
+        expect(false).toEqual(true);
+      }, function (reason) {
+        expect(reason instanceof Error).toEqual(true);
+      });
+      const invalidName = (0, _event_utils.waitOnEventOrTimeout)({
+        target: eventBus,
+        name: ""
+      }).then(function () {
+        expect(false).toEqual(true);
+      }, function (reason) {
+        expect(reason instanceof Error).toEqual(true);
+      });
+      const invalidDelay = (0, _event_utils.waitOnEventOrTimeout)({
+        target: eventBus,
+        name: "pagerendered",
+        delay: -1000
+      }).then(function () {
+        expect(false).toEqual(true);
+      }, function (reason) {
+        expect(reason instanceof Error).toEqual(true);
+      });
+      await Promise.all([invalidTarget, invalidName, invalidDelay]);
+    });
+    it("should resolve on event, using the DOM", async function () {
+      if (_is_node.isNodeJS) {
+        pending("Document is not supported in Node.js.");
+      }
+
+      const button = document.createElement("button");
+      const buttonClicked = (0, _event_utils.waitOnEventOrTimeout)({
+        target: button,
+        name: "click",
+        delay: 10000
+      });
+      button.click();
+      const type = await buttonClicked;
+      expect(type).toEqual(_event_utils.WaitOnType.EVENT);
+    });
+    it("should resolve on timeout, using the DOM", async function () {
+      if (_is_node.isNodeJS) {
+        pending("Document is not supported in Node.js.");
+      }
+
+      const button = document.createElement("button");
+      const buttonClicked = (0, _event_utils.waitOnEventOrTimeout)({
+        target: button,
+        name: "click",
+        delay: 10
+      });
+      const type = await buttonClicked;
+      expect(type).toEqual(_event_utils.WaitOnType.TIMEOUT);
+    });
+    it("should resolve on event, using the EventBus", async function () {
+      const pageRendered = (0, _event_utils.waitOnEventOrTimeout)({
+        target: eventBus,
+        name: "pagerendered",
+        delay: 10000
+      });
+      eventBus.dispatch("pagerendered");
+      const type = await pageRendered;
+      expect(type).toEqual(_event_utils.WaitOnType.EVENT);
+    });
+    it("should resolve on timeout, using the EventBus", async function () {
+      const pageRendered = (0, _event_utils.waitOnEventOrTimeout)({
+        target: eventBus,
+        name: "pagerendered",
+        delay: 10
+      });
+      const type = await pageRendered;
+      expect(type).toEqual(_event_utils.WaitOnType.TIMEOUT);
+    });
+  });
+});

+ 1 - 1
lib/test/unit/jasmine-boot.js

@@ -36,7 +36,7 @@ var _api = require("pdfjs/display/api.js");
 var _testreporter = require("./testreporter.js");
 
 async function initializePDFJS(callback) {
-  await Promise.all(["pdfjs-test/unit/annotation_spec.js", "pdfjs-test/unit/annotation_storage_spec.js", "pdfjs-test/unit/api_spec.js", "pdfjs-test/unit/bidi_spec.js", "pdfjs-test/unit/cff_parser_spec.js", "pdfjs-test/unit/cmap_spec.js", "pdfjs-test/unit/colorspace_spec.js", "pdfjs-test/unit/core_utils_spec.js", "pdfjs-test/unit/crypto_spec.js", "pdfjs-test/unit/custom_spec.js", "pdfjs-test/unit/default_appearance_spec.js", "pdfjs-test/unit/display_svg_spec.js", "pdfjs-test/unit/display_utils_spec.js", "pdfjs-test/unit/document_spec.js", "pdfjs-test/unit/encodings_spec.js", "pdfjs-test/unit/evaluator_spec.js", "pdfjs-test/unit/function_spec.js", "pdfjs-test/unit/fetch_stream_spec.js", "pdfjs-test/unit/message_handler_spec.js", "pdfjs-test/unit/metadata_spec.js", "pdfjs-test/unit/murmurhash3_spec.js", "pdfjs-test/unit/network_spec.js", "pdfjs-test/unit/network_utils_spec.js", "pdfjs-test/unit/parser_spec.js", "pdfjs-test/unit/pdf_find_controller_spec.js", "pdfjs-test/unit/pdf_find_utils_spec.js", "pdfjs-test/unit/pdf_history_spec.js", "pdfjs-test/unit/primitives_spec.js", "pdfjs-test/unit/scripting_spec.js", "pdfjs-test/unit/stream_spec.js", "pdfjs-test/unit/struct_tree_spec.js", "pdfjs-test/unit/type1_parser_spec.js", "pdfjs-test/unit/ui_utils_spec.js", "pdfjs-test/unit/unicode_spec.js", "pdfjs-test/unit/util_spec.js", "pdfjs-test/unit/writer_spec.js", "pdfjs-test/unit/xfa_formcalc_spec.js", "pdfjs-test/unit/xfa_parser_spec.js", "pdfjs-test/unit/xfa_serialize_data_spec.js", "pdfjs-test/unit/xfa_tohtml_spec.js", "pdfjs-test/unit/xml_spec.js"].map(function (moduleName) {
+  await Promise.all(["pdfjs-test/unit/annotation_spec.js", "pdfjs-test/unit/annotation_storage_spec.js", "pdfjs-test/unit/api_spec.js", "pdfjs-test/unit/base_viewer_spec.js", "pdfjs-test/unit/bidi_spec.js", "pdfjs-test/unit/cff_parser_spec.js", "pdfjs-test/unit/cmap_spec.js", "pdfjs-test/unit/colorspace_spec.js", "pdfjs-test/unit/core_utils_spec.js", "pdfjs-test/unit/crypto_spec.js", "pdfjs-test/unit/custom_spec.js", "pdfjs-test/unit/default_appearance_spec.js", "pdfjs-test/unit/display_svg_spec.js", "pdfjs-test/unit/display_utils_spec.js", "pdfjs-test/unit/document_spec.js", "pdfjs-test/unit/encodings_spec.js", "pdfjs-test/unit/evaluator_spec.js", "pdfjs-test/unit/event_utils_spec.js", "pdfjs-test/unit/function_spec.js", "pdfjs-test/unit/fetch_stream_spec.js", "pdfjs-test/unit/message_handler_spec.js", "pdfjs-test/unit/metadata_spec.js", "pdfjs-test/unit/murmurhash3_spec.js", "pdfjs-test/unit/network_spec.js", "pdfjs-test/unit/network_utils_spec.js", "pdfjs-test/unit/parser_spec.js", "pdfjs-test/unit/pdf_find_controller_spec.js", "pdfjs-test/unit/pdf_find_utils_spec.js", "pdfjs-test/unit/pdf_history_spec.js", "pdfjs-test/unit/primitives_spec.js", "pdfjs-test/unit/scripting_spec.js", "pdfjs-test/unit/stream_spec.js", "pdfjs-test/unit/struct_tree_spec.js", "pdfjs-test/unit/type1_parser_spec.js", "pdfjs-test/unit/ui_utils_spec.js", "pdfjs-test/unit/unicode_spec.js", "pdfjs-test/unit/util_spec.js", "pdfjs-test/unit/writer_spec.js", "pdfjs-test/unit/xfa_formcalc_spec.js", "pdfjs-test/unit/xfa_parser_spec.js", "pdfjs-test/unit/xfa_serialize_data_spec.js", "pdfjs-test/unit/xfa_tohtml_spec.js", "pdfjs-test/unit/xml_spec.js"].map(function (moduleName) {
     return import(moduleName);
   }));
 

+ 25 - 36
lib/test/unit/pdf_find_controller_spec.js

@@ -23,7 +23,7 @@
 
 var _test_utils = require("./test_utils.js");
 
-var _ui_utils = require("../../web/ui_utils.js");
+var _event_utils = require("../../web/event_utils.js");
 
 var _api = require("../../display/api.js");
 
@@ -61,7 +61,7 @@ class MockLinkService extends _pdf_link_service.SimpleLinkService {
 async function initPdfFindController(filename) {
   const loadingTask = (0, _api.getDocument)((0, _test_utils.buildGetDocumentParams)(filename || tracemonkeyFileName));
   const pdfDocument = await loadingTask.promise;
-  const eventBus = new _ui_utils.EventBus();
+  const eventBus = new _event_utils.EventBus();
   const linkService = new MockLinkService();
   linkService.setDocument(pdfDocument);
   const pdfFindController = new _pdf_find_controller.PDFFindController({
@@ -78,14 +78,23 @@ async function initPdfFindController(filename) {
 function testSearch({
   eventBus,
   pdfFindController,
-  parameters,
+  state,
   matchesPerPage,
   selectedMatch,
   pageMatches = null,
   pageMatchesLength = null
 }) {
   return new Promise(function (resolve) {
-    pdfFindController.executeCommand("find", parameters);
+    const eventState = Object.assign(Object.create(null), {
+      source: this,
+      type: "",
+      query: null,
+      caseSensitive: false,
+      entireWord: false,
+      phraseSearch: true,
+      findPrevious: false
+    }, state);
+    eventBus.dispatch("find", eventState);
     let totalPages = matchesPerPage.length;
 
     for (let i = totalPages - 1; i >= 0; i--) {
@@ -132,12 +141,8 @@ describe("pdf_find_controller", function () {
     await testSearch({
       eventBus,
       pdfFindController,
-      parameters: {
-        query: "Dynamic",
-        caseSensitive: false,
-        entireWord: false,
-        phraseSearch: true,
-        findPrevious: false
+      state: {
+        query: "Dynamic"
       },
       matchesPerPage: [11, 5, 0, 3, 0, 0, 0, 1, 1, 1, 0, 3, 4, 4],
       selectedMatch: {
@@ -154,11 +159,8 @@ describe("pdf_find_controller", function () {
     await testSearch({
       eventBus,
       pdfFindController,
-      parameters: {
+      state: {
         query: "conference",
-        caseSensitive: false,
-        entireWord: false,
-        phraseSearch: true,
         findPrevious: true
       },
       matchesPerPage: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5],
@@ -176,12 +178,9 @@ describe("pdf_find_controller", function () {
     await testSearch({
       eventBus,
       pdfFindController,
-      parameters: {
+      state: {
         query: "Dynamic",
-        caseSensitive: true,
-        entireWord: false,
-        phraseSearch: true,
-        findPrevious: false
+        caseSensitive: true
       },
       matchesPerPage: [3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3],
       selectedMatch: {
@@ -198,12 +197,9 @@ describe("pdf_find_controller", function () {
     await testSearch({
       eventBus,
       pdfFindController,
-      parameters: {
+      state: {
         query: "Government",
-        caseSensitive: false,
-        entireWord: true,
-        phraseSearch: true,
-        findPrevious: false
+        entireWord: true
       },
       matchesPerPage: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0],
       selectedMatch: {
@@ -220,12 +216,9 @@ describe("pdf_find_controller", function () {
     await testSearch({
       eventBus,
       pdfFindController,
-      parameters: {
+      state: {
         query: "alternate solution",
-        caseSensitive: false,
-        entireWord: false,
-        phraseSearch: false,
-        findPrevious: false
+        phraseSearch: false
       },
       matchesPerPage: [0, 0, 0, 0, 0, 1, 0, 0, 4, 0, 0, 0, 0, 0],
       selectedMatch: {
@@ -242,19 +235,15 @@ describe("pdf_find_controller", function () {
     await testSearch({
       eventBus,
       pdfFindController,
-      parameters: {
-        query: "fraction",
-        caseSensitive: false,
-        entireWord: false,
-        phraseSearch: true,
-        findPrevious: false
+      state: {
+        query: "fraction"
       },
       matchesPerPage: [3],
       selectedMatch: {
         pageIndex: 0,
         matchIndex: 0
       },
-      pageMatches: [[19, 48, 66]],
+      pageMatches: [[19, 46, 62]],
       pageMatchesLength: [[8, 8, 8]]
     });
   });

+ 1 - 1
lib/test/unit/primitives_spec.js

@@ -91,7 +91,7 @@ describe("primitives", function () {
     const checkInvalidKeyValues = function (dict) {
       expect(dict.get()).toBeUndefined();
       expect(dict.get("Prev")).toBeUndefined();
-      expect(dict.get("Decode", "D")).toBeUndefined();
+      expect(dict.get("D", "Decode")).toBeUndefined();
       expect(dict.get("FontFile", "FontFile2", "FontFile3")).toBeUndefined();
     };
 

+ 2 - 0
lib/test/unit/struct_tree_spec.js

@@ -28,6 +28,7 @@ var _api = require("../../display/api.js");
 function equalTrees(rootA, rootB) {
   function walk(a, b) {
     expect(a.role).toEqual(b.role);
+    expect(a.lang).toEqual(b.lang);
     expect(a.type).toEqual(b.type);
     expect("children" in a).toEqual("children" in b);
 
@@ -58,6 +59,7 @@ describe("struct tree", function () {
         role: "Root",
         children: [{
           role: "Document",
+          lang: "en-US",
           children: [{
             role: "H1",
             children: [{

+ 6 - 5
lib/test/unit/test_utils.js

@@ -24,10 +24,10 @@
 Object.defineProperty(exports, "__esModule", {
   value: true
 });
+exports.XRefMock = exports.TEST_PDFS_PATH = exports.STANDARD_FONT_DATA_URL = exports.DefaultFileReaderFactory = exports.CMAP_PARAMS = void 0;
 exports.buildGetDocumentParams = buildGetDocumentParams;
 exports.createIdFactory = createIdFactory;
 exports.isEmptyObj = isEmptyObj;
-exports.XRefMock = exports.TEST_PDFS_PATH = exports.STANDARD_FONT_DATA_URL = exports.DefaultFileReaderFactory = exports.CMAP_PARAMS = void 0;
 
 var _primitives = require("../../core/primitives.js");
 
@@ -35,6 +35,8 @@ var _document = require("../../core/document.js");
 
 var _util = require("../../shared/util.js");
 
+var _core_utils = require("../../core/core_utils.js");
+
 var _is_node = require("../../shared/is_node.js");
 
 var _stream = require("../../core/stream.js");
@@ -98,10 +100,9 @@ function buildGetDocumentParams(filename, options) {
 class XRefMock {
   constructor(array) {
     this._map = Object.create(null);
-    this.stats = {
-      streamTypes: Object.create(null),
-      fontTypes: Object.create(null)
-    };
+    this.stats = new _core_utils.DocStats({
+      send: () => {}
+    });
     this._newRefNum = null;
 
     for (const key in array) {

+ 9 - 271
lib/test/unit/ui_utils_spec.js

@@ -23,8 +23,6 @@
 
 var _ui_utils = require("../../web/ui_utils.js");
 
-var _is_node = require("../../shared/is_node.js");
-
 describe("ui_utils", function () {
   describe("binary search", function () {
     function isTrue(boolean) {
@@ -54,151 +52,6 @@ describe("ui_utils", function () {
       expect((0, _ui_utils.binarySearchFirstItem)([4, 5, 6], isGreater3)).toEqual(0);
     });
   });
-  describe("EventBus", function () {
-    it("dispatch event", function () {
-      const eventBus = new _ui_utils.EventBus();
-      let count = 0;
-      eventBus.on("test", function (evt) {
-        expect(evt).toEqual(undefined);
-        count++;
-      });
-      eventBus.dispatch("test");
-      expect(count).toEqual(1);
-    });
-    it("dispatch event with arguments", function () {
-      const eventBus = new _ui_utils.EventBus();
-      let count = 0;
-      eventBus.on("test", function (evt) {
-        expect(evt).toEqual({
-          abc: 123
-        });
-        count++;
-      });
-      eventBus.dispatch("test", {
-        abc: 123
-      });
-      expect(count).toEqual(1);
-    });
-    it("dispatch different event", function () {
-      const eventBus = new _ui_utils.EventBus();
-      let count = 0;
-      eventBus.on("test", function () {
-        count++;
-      });
-      eventBus.dispatch("nottest");
-      expect(count).toEqual(0);
-    });
-    it("dispatch event multiple times", function () {
-      const eventBus = new _ui_utils.EventBus();
-      let count = 0;
-      eventBus.dispatch("test");
-      eventBus.on("test", function () {
-        count++;
-      });
-      eventBus.dispatch("test");
-      eventBus.dispatch("test");
-      expect(count).toEqual(2);
-    });
-    it("dispatch event to multiple handlers", function () {
-      const eventBus = new _ui_utils.EventBus();
-      let count = 0;
-      eventBus.on("test", function () {
-        count++;
-      });
-      eventBus.on("test", function () {
-        count++;
-      });
-      eventBus.dispatch("test");
-      expect(count).toEqual(2);
-    });
-    it("dispatch to detached", function () {
-      const eventBus = new _ui_utils.EventBus();
-      let count = 0;
-
-      const listener = function () {
-        count++;
-      };
-
-      eventBus.on("test", listener);
-      eventBus.dispatch("test");
-      eventBus.off("test", listener);
-      eventBus.dispatch("test");
-      expect(count).toEqual(1);
-    });
-    it("dispatch to wrong detached", function () {
-      const eventBus = new _ui_utils.EventBus();
-      let count = 0;
-      eventBus.on("test", function () {
-        count++;
-      });
-      eventBus.dispatch("test");
-      eventBus.off("test", function () {
-        count++;
-      });
-      eventBus.dispatch("test");
-      expect(count).toEqual(2);
-    });
-    it("dispatch to detached during handling", function () {
-      const eventBus = new _ui_utils.EventBus();
-      let count = 0;
-
-      const listener1 = function () {
-        eventBus.off("test", listener2);
-        count++;
-      };
-
-      const listener2 = function () {
-        eventBus.off("test", listener1);
-        count++;
-      };
-
-      eventBus.on("test", listener1);
-      eventBus.on("test", listener2);
-      eventBus.dispatch("test");
-      eventBus.dispatch("test");
-      expect(count).toEqual(2);
-    });
-    it("dispatch event to handlers with/without 'once' option", function () {
-      const eventBus = new _ui_utils.EventBus();
-      let multipleCount = 0,
-          onceCount = 0;
-      eventBus.on("test", function () {
-        multipleCount++;
-      });
-      eventBus.on("test", function () {
-        onceCount++;
-      }, {
-        once: true
-      });
-      eventBus.dispatch("test");
-      eventBus.dispatch("test");
-      eventBus.dispatch("test");
-      expect(multipleCount).toEqual(3);
-      expect(onceCount).toEqual(1);
-    });
-    it("should not re-dispatch to DOM", async function () {
-      if (_is_node.isNodeJS) {
-        pending("Document is not supported in Node.js.");
-      }
-
-      const eventBus = new _ui_utils.EventBus();
-      let count = 0;
-      eventBus.on("test", function (evt) {
-        expect(evt).toEqual(undefined);
-        count++;
-      });
-
-      function domEventListener() {
-        expect(false).toEqual(true);
-      }
-
-      document.addEventListener("test", domEventListener);
-      eventBus.dispatch("test");
-      await Promise.resolve();
-      expect(count).toEqual(1);
-      document.removeEventListener("test", domEventListener);
-    });
-  });
   describe("isValidRotation", function () {
     it("should reject non-integer angles", function () {
       expect((0, _ui_utils.isValidRotation)()).toEqual(false);
@@ -267,91 +120,6 @@ describe("ui_utils", function () {
       expect(parameters.get("key2")).toEqual("Value2");
     });
   });
-  describe("waitOnEventOrTimeout", function () {
-    let eventBus;
-    beforeAll(function () {
-      eventBus = new _ui_utils.EventBus();
-    });
-    afterAll(function () {
-      eventBus = null;
-    });
-    it("should reject invalid parameters", async function () {
-      const invalidTarget = (0, _ui_utils.waitOnEventOrTimeout)({
-        target: "window",
-        name: "DOMContentLoaded"
-      }).then(function () {
-        expect(false).toEqual(true);
-      }, function (reason) {
-        expect(reason instanceof Error).toEqual(true);
-      });
-      const invalidName = (0, _ui_utils.waitOnEventOrTimeout)({
-        target: eventBus,
-        name: ""
-      }).then(function () {
-        expect(false).toEqual(true);
-      }, function (reason) {
-        expect(reason instanceof Error).toEqual(true);
-      });
-      const invalidDelay = (0, _ui_utils.waitOnEventOrTimeout)({
-        target: eventBus,
-        name: "pagerendered",
-        delay: -1000
-      }).then(function () {
-        expect(false).toEqual(true);
-      }, function (reason) {
-        expect(reason instanceof Error).toEqual(true);
-      });
-      await Promise.all([invalidTarget, invalidName, invalidDelay]);
-    });
-    it("should resolve on event, using the DOM", async function () {
-      if (_is_node.isNodeJS) {
-        pending("Document is not supported in Node.js.");
-      }
-
-      const button = document.createElement("button");
-      const buttonClicked = (0, _ui_utils.waitOnEventOrTimeout)({
-        target: button,
-        name: "click",
-        delay: 10000
-      });
-      button.click();
-      const type = await buttonClicked;
-      expect(type).toEqual(_ui_utils.WaitOnType.EVENT);
-    });
-    it("should resolve on timeout, using the DOM", async function () {
-      if (_is_node.isNodeJS) {
-        pending("Document is not supported in Node.js.");
-      }
-
-      const button = document.createElement("button");
-      const buttonClicked = (0, _ui_utils.waitOnEventOrTimeout)({
-        target: button,
-        name: "click",
-        delay: 10
-      });
-      const type = await buttonClicked;
-      expect(type).toEqual(_ui_utils.WaitOnType.TIMEOUT);
-    });
-    it("should resolve on event, using the EventBus", async function () {
-      const pageRendered = (0, _ui_utils.waitOnEventOrTimeout)({
-        target: eventBus,
-        name: "pagerendered",
-        delay: 10000
-      });
-      eventBus.dispatch("pagerendered");
-      const type = await pageRendered;
-      expect(type).toEqual(_ui_utils.WaitOnType.EVENT);
-    });
-    it("should resolve on timeout, using the EventBus", async function () {
-      const pageRendered = (0, _ui_utils.waitOnEventOrTimeout)({
-        target: eventBus,
-        name: "pagerendered",
-        delay: 10
-      });
-      const type = await pageRendered;
-      expect(type).toEqual(_ui_utils.WaitOnType.TIMEOUT);
-    });
-  });
   describe("getPageSizeInches", function () {
     it("gets page size (in inches)", function () {
       const page = {
@@ -431,7 +199,8 @@ describe("ui_utils", function () {
     }
 
     function slowGetVisibleElements(scroll, pages) {
-      const views = [];
+      const views = [],
+            ids = new Set();
       const {
         scrollLeft,
         scrollTop
@@ -462,13 +231,15 @@ describe("ui_utils", function () {
             percent,
             widthPercent: fractionWidth * 100 | 0
           });
+          ids.add(view.id);
         }
       }
 
       return {
         first: views[0],
         last: views[views.length - 1],
-        views
+        views,
+        ids
       };
     }
 
@@ -564,7 +335,8 @@ describe("ui_utils", function () {
       })).toEqual({
         first: undefined,
         last: undefined,
-        views: []
+        views: [],
+        ids: new Set()
       });
     });
     it("handles all views being hidden (without errors)", function () {
@@ -581,7 +353,8 @@ describe("ui_utils", function () {
       })).toEqual({
         first: undefined,
         last: undefined,
-        views: []
+        views: [],
+        ids: new Set()
       });
     });
     describe("backtrackBeforeAllVisibleElements", function () {
@@ -611,39 +384,4 @@ describe("ui_utils", function () {
       });
     });
   });
-  describe("moveToEndOfArray", function () {
-    it("works on empty arrays", function () {
-      const data = [];
-      (0, _ui_utils.moveToEndOfArray)(data, function () {});
-      expect(data).toEqual([]);
-    });
-    it("works when moving everything", function () {
-      const data = [1, 2, 3, 4, 5];
-      (0, _ui_utils.moveToEndOfArray)(data, function () {
-        return true;
-      });
-      expect(data).toEqual([1, 2, 3, 4, 5]);
-    });
-    it("works when moving some things", function () {
-      const data = [1, 2, 3, 4, 5];
-      (0, _ui_utils.moveToEndOfArray)(data, function (x) {
-        return x % 2 === 0;
-      });
-      expect(data).toEqual([1, 3, 5, 2, 4]);
-    });
-    it("works when moving one thing", function () {
-      const data = [1, 2, 3, 4, 5];
-      (0, _ui_utils.moveToEndOfArray)(data, function (x) {
-        return x === 1;
-      });
-      expect(data).toEqual([2, 3, 4, 5, 1]);
-    });
-    it("works when moving nothing", function () {
-      const data = [1, 2, 3, 4, 5];
-      (0, _ui_utils.moveToEndOfArray)(data, function (x) {
-        return x === 0;
-      });
-      expect(data).toEqual([1, 2, 3, 4, 5]);
-    });
-  });
 });

+ 5 - 0
lib/test/unit/util_spec.js

@@ -148,6 +148,11 @@ describe("util", function () {
       const str = "string\x00With\x00Null\x00Chars";
       expect((0, _util.removeNullCharacters)(str)).toEqual("stringWithNullChars");
     });
+    it("should modify string with non-displayable characters", function () {
+      const str = Array.from(Array(32).keys()).map(x => String.fromCharCode(x) + "a").join("");
+      const expected = "a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a";
+      expect((0, _util.removeNullCharacters)(str, true)).toEqual(expected);
+    });
   });
   describe("ReadableStream", function () {
     it("should return an Object", function () {

+ 2 - 2
lib/test/unit/xfa_parser_spec.js

@@ -51,7 +51,7 @@ describe("XFAParser", function () {
           forbidden
         </dynamicRender>
       </acrobat7>
-      <autoSave>enabled</autoSave>      
+      <autoSave>enabled</autoSave>
       <submitUrl>
                  http://d.e.f
       </submitUrl>
@@ -435,7 +435,7 @@ describe("XFAParser", function () {
       const p = root.template.extras.text.$content[_xfa_object.$getChildren]()[0];
 
       expect(p.style).toEqual("text-indent:0.5in;line-height:11px;tab-stop:left 0.5in");
-      expect(p[_xfa_object.$text]()).toEqual([" The first line of this paragraph is indented a half-inch.\n", " Successive lines are not indented.\n", " This is the last line of the paragraph.\n \n"].join(""));
+      expect(p[_xfa_object.$text]()).toEqual([" The first line of this paragraph is indented a half-inch.\n", " Successive lines are not indented.\n", " This is the last line of the paragraph.\n "].join(""));
     });
     it("should parse a xfa document and apply some prototypes with cycle", function () {
       const xml = `

+ 3 - 2
lib/test/unit/xfa_serialize_data_spec.js

@@ -44,6 +44,7 @@ describe("Data serializer", function () {
           <Units>1</Units>
           <Unit_Price>250.00</Unit_Price>
           <Total_Price>250.00</Total_Price>
+          <àé></àé>
         </Detail>
         <Page>2</Page>
         <Detail PartNo="RRB-LB">
@@ -66,14 +67,14 @@ describe("Data serializer", function () {
     const dataHandler = new _data.DataHandler(root, data);
     const storage = new Map();
 
-    for (const [path, value] of [["Receipt.Detail[0].Units", "12&3"], ["Receipt.Detail[0].Unit_Price", "456>"], ["Receipt.Detail[0].Total_Price", "789"], ["Receipt.Detail[1].PartNo", "foo-bar😀"], ["Receipt.Detail[1].Description", "hello world"]]) {
+    for (const [path, value] of [["Receipt.Detail[0].Units", "12&3"], ["Receipt.Detail[0].Unit_Price", "456>"], ["Receipt.Detail[0].Total_Price", "789"], ["Receipt.Detail[0].àé", "1011"], ["Receipt.Detail[1].PartNo", "foo-bar😀"], ["Receipt.Detail[1].Description", "hello world"]]) {
       storage.set((0, _som.searchNode)(root, data, path)[0][_xfa_object.$uid], {
         value
       });
     }
 
     const serialized = dataHandler.serialize(storage);
-    const expected = `<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/"><foo>bar</foo><bar>foo</bar><xfa:data><Receipt><Page>1</Page><Detail PartNo="GS001"><Description>Giant Slingshot</Description><Units>12&amp;3</Units><Unit_Price>456&gt;</Unit_Price><Total_Price>789</Total_Price></Detail><Page>2</Page><Detail PartNo="foo-bar&#x1F600;"><Description>hello world</Description><Units>5</Units><Unit_Price>12.00</Unit_Price><Total_Price>60.00</Total_Price></Detail><Sub_Total>310.00</Sub_Total><Tax>24.80</Tax><Total_Price>334.80</Total_Price></Receipt></xfa:data></xfa:datasets>`;
+    const expected = `<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/"><foo>bar</foo><bar>foo</bar><xfa:data><Receipt><Page>1</Page><Detail PartNo="GS001"><Description>Giant Slingshot</Description><Units>12&amp;3</Units><Unit_Price>456&gt;</Unit_Price><Total_Price>789</Total_Price><\xC3\xA0\xC3\xA9>1011</\xC3\xA0\xC3\xA9></Detail><Page>2</Page><Detail PartNo="foo-bar&#x1F600;"><Description>hello world</Description><Units>5</Units><Unit_Price>12.00</Unit_Price><Total_Price>60.00</Total_Price></Detail><Sub_Total>310.00</Sub_Total><Tax>24.80</Tax><Total_Price>334.80</Total_Price></Receipt></xfa:data></xfa:datasets>`;
     expect(serialized).toEqual(expected);
   });
 });

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor