From e18fa3fc455aebe34a014f28d74df350bd7c1dc1 Mon Sep 17 00:00:00 2001
From: Jonas Jenwald <jonas.jenwald@gmail.com>
Date: Sat, 20 Jun 2020 11:34:41 +0200
Subject: [PATCH] Tweak the `QueueOptimizer` to recognize
 `OPS.paintImageMaskXObject` operators as *repeated* when the "skew"
 transformation matrix elements are non-zero (issue 8078)

*First of all, I should mention that my understanding of the finer details of the `QueueOptimizer` (and its related `CanvasGraphics` methods) is somewhat limited.*
Hence I'm not sure if there's actually a very good reason for *only* considering ImageMasks where the "skew" transformation matrix elements are zero as *repeated*, however simply looking at the code I just don't see why these elements cannot be non-zero as long as they are *all identical* for the ImageMasks.
Furthermore, looking at the *group* case (which is what we're currently falling back to), there's no particular limitation placed upon the transformation matrix elements.

While this patch obviously isn't enough to *completely* fix the issue, since there should be a visible Pattern rendered as well[1], it seem (at least to me) like enough of an improvement that submitting this is justified.
With these changes the referenced PDF document will no longer hang the *entire* browser, and rendering also finishes in a *reasonable* time (< 10 seconds for me) which seem fine given the *huge* number of identical inline images present.[2]

---
[1] Temporarily changing the Pattern to a solid color *does* render the correct/expected area, which suggests that the remaining problem is a pre-existing issue related to the Pattern-handling itself rather than the `QueueOptimizer` functionality.

[2] The document isn't exactly rendered immediately in e.g. Adobe Reader either.
---
 src/core/operator_list.js    | 18 ++++++++++--------
 src/display/canvas.js        | 13 +++++++++++--
 test/pdfs/issue8078.pdf.link |  1 +
 test/test_manifest.json      |  8 ++++++++
 4 files changed, 30 insertions(+), 10 deletions(-)
 create mode 100644 test/pdfs/issue8078.pdf.link

diff --git a/src/core/operator_list.js b/src/core/operator_list.js
index b3b9c045c..ca1ff2505 100644
--- a/src/core/operator_list.js
+++ b/src/core/operator_list.js
@@ -229,13 +229,13 @@ var QueueOptimizer = (function QueueOptimizerClosure() {
       var isSameImage = false;
       var iTransform, transformArgs;
       var firstPIMXOArg0 = argsArray[iFirstPIMXO][0];
-      if (
-        argsArray[iFirstTransform][1] === 0 &&
-        argsArray[iFirstTransform][2] === 0
-      ) {
+      const firstTransformArg0 = argsArray[iFirstTransform][0],
+        firstTransformArg1 = argsArray[iFirstTransform][1],
+        firstTransformArg2 = argsArray[iFirstTransform][2],
+        firstTransformArg3 = argsArray[iFirstTransform][3];
+
+      if (firstTransformArg1 === firstTransformArg2) {
         isSameImage = true;
-        var firstTransformArg0 = argsArray[iFirstTransform][0];
-        var firstTransformArg3 = argsArray[iFirstTransform][3];
         iTransform = iFirstTransform + 4;
         var iPIMXO = iFirstPIMXO + 4;
         for (q = 1; q < count; q++, iTransform += 4, iPIMXO += 4) {
@@ -243,8 +243,8 @@ var QueueOptimizer = (function QueueOptimizerClosure() {
           if (
             argsArray[iPIMXO][0] !== firstPIMXOArg0 ||
             transformArgs[0] !== firstTransformArg0 ||
-            transformArgs[1] !== 0 ||
-            transformArgs[2] !== 0 ||
+            transformArgs[1] !== firstTransformArg1 ||
+            transformArgs[2] !== firstTransformArg2 ||
             transformArgs[3] !== firstTransformArg3
           ) {
             if (q < MIN_IMAGES_IN_MASKS_BLOCK) {
@@ -272,6 +272,8 @@ var QueueOptimizer = (function QueueOptimizerClosure() {
         argsArray.splice(iFirstSave, count * 4, [
           firstPIMXOArg0,
           firstTransformArg0,
+          firstTransformArg1,
+          firstTransformArg2,
           firstTransformArg3,
           positions,
         ]);
diff --git a/src/display/canvas.js b/src/display/canvas.js
index 8cae8db99..bdfb16f40 100644
--- a/src/display/canvas.js
+++ b/src/display/canvas.js
@@ -2157,9 +2157,11 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
       this.paintInlineImageXObject(maskCanvas.canvas);
     },
 
-    paintImageMaskXObjectRepeat: function CanvasGraphics_paintImageMaskXObjectRepeat(
+    paintImageMaskXObjectRepeat(
       imgData,
       scaleX,
+      skewX = 0,
+      skewY = 0,
       scaleY,
       positions
     ) {
@@ -2190,7 +2192,14 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
       var ctx = this.ctx;
       for (var i = 0, ii = positions.length; i < ii; i += 2) {
         ctx.save();
-        ctx.transform(scaleX, 0, 0, scaleY, positions[i], positions[i + 1]);
+        ctx.transform(
+          scaleX,
+          skewX,
+          skewY,
+          scaleY,
+          positions[i],
+          positions[i + 1]
+        );
         ctx.scale(1, -1);
         ctx.drawImage(maskCanvas.canvas, 0, 0, width, height, 0, -1, 1, 1);
         ctx.restore();
diff --git a/test/pdfs/issue8078.pdf.link b/test/pdfs/issue8078.pdf.link
new file mode 100644
index 000000000..103c89331
--- /dev/null
+++ b/test/pdfs/issue8078.pdf.link
@@ -0,0 +1 @@
+https://bugs.ghostscript.com/attachment.cgi?id=7455
diff --git a/test/test_manifest.json b/test/test_manifest.json
index db8b70ee4..f724bff8c 100644
--- a/test/test_manifest.json
+++ b/test/test_manifest.json
@@ -711,6 +711,14 @@
        "type": "eq",
        "about": "Type1 font with |Ref|s in the Differences array of the Encoding dictionary."
     },
+    {  "id": "issue8078",
+       "file": "pdfs/issue8078.pdf",
+       "md5": "8b7d74bc24b4157393e4e88a511c05f1",
+       "link": true,
+       "rounds": 1,
+       "lastPage": 1,
+       "type": "eq"
+    },
     {  "id": "issue8092",
        "file": "pdfs/issue8092.pdf",
        "md5": "e4f3376b35fd132580246c3db1fbd738",