Add FPDF_PRINTMODE_EMF_IMAGE_MASKS print mode.

This is a new Windows-only print mode for use with FPDF_SetPrintMode().
It outputs EMF, like FPDF_PRINTMODE_EMF, but can process image masks
more efficiently under certain conditions.

By adding this additional print mode, embedders can choose whether to
use it at run time. This in turn allows embedders to roll out this
improvement, which has been known to cause regressions in the past, in a
controlled manner.

Bug: chromium:674771
Change-Id: Id4a16aa70a785415d2195c6503b35ff7267bc911
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/69810
Commit-Queue: Lei Zhang <thestig@chromium.org>
Reviewed-by: Rebekah Potter <rbpotter@chromium.org>
diff --git a/core/fxge/cfx_windowsrenderdevice.h b/core/fxge/cfx_windowsrenderdevice.h
index 3d96207..96ae1ee 100644
--- a/core/fxge/cfx_windowsrenderdevice.h
+++ b/core/fxge/cfx_windowsrenderdevice.h
@@ -18,6 +18,7 @@
   kModePostScript3 = 3,
   kModePostScript2PassThrough = 4,
   kModePostScript3PassThrough = 5,
+  kModeEmfImageMasks = 6,
 };
 
 class RenderDeviceDriverIface;
diff --git a/core/fxge/win32/fx_win32_device.cpp b/core/fxge/win32/fx_win32_device.cpp
index 5fabf87..e1a046f 100644
--- a/core/fxge/win32/fx_win32_device.cpp
+++ b/core/fxge/win32/fx_win32_device.cpp
@@ -1375,8 +1375,10 @@
   if (!use_printer)
     return new CGdiDisplayDriver(hDC);
 
-  if (g_pdfium_print_mode == WindowsPrintMode::kModeEmf)
+  if (g_pdfium_print_mode == WindowsPrintMode::kModeEmf ||
+      g_pdfium_print_mode == WindowsPrintMode::kModeEmfImageMasks) {
     return new CGdiPrinterDriver(hDC);
+  }
 
   if (g_pdfium_print_mode == WindowsPrintMode::kModeTextOnly)
     return new CTextOnlyPrinterDriver(hDC);
diff --git a/fpdfsdk/fpdf_view.cpp b/fpdfsdk/fpdf_view.cpp
index e7e2735..eddd0db 100644
--- a/fpdfsdk/fpdf_view.cpp
+++ b/fpdfsdk/fpdf_view.cpp
@@ -73,6 +73,9 @@
 static_assert(WindowsPrintMode::kModePostScript3PassThrough ==
                   FPDF_PRINTMODE_POSTSCRIPT3_PASSTHROUGH,
               "WindowsPrintMode::kModePostScript3PassThrough value mismatch");
+static_assert(WindowsPrintMode::kModeEmfImageMasks ==
+                  FPDF_PRINTMODE_EMF_IMAGE_MASKS,
+              "WindowsPrintMode::kModeEmfImageMasks value mismatch");
 #endif  // defined(OS_WIN)
 
 namespace {
@@ -158,10 +161,9 @@
 #endif  // PDFIUM_PRINT_TEXT_WITH_GDI
 
 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDF_SetPrintMode(int mode) {
-  if (mode < FPDF_PRINTMODE_EMF ||
-      mode > FPDF_PRINTMODE_POSTSCRIPT3_PASSTHROUGH) {
+  if (mode < FPDF_PRINTMODE_EMF || mode > FPDF_PRINTMODE_EMF_IMAGE_MASKS)
     return FALSE;
-  }
+
   g_pdfium_print_mode = static_cast<WindowsPrintMode>(mode);
   return TRUE;
 }
@@ -446,9 +448,9 @@
   // of masks. Full page bitmaps result in large spool sizes, so they should
   // only be used when necessary. For large numbers of masks, rendering each
   // individually is inefficient and unlikely to significantly improve spool
-  // size. TODO(rbpotter): Find out why this still breaks printing for some
-  // PDFs (see crbug.com/777837).
-  const bool bEnableImageMasks = false;
+  // size.
+  const bool bEnableImageMasks =
+      g_pdfium_print_mode == WindowsPrintMode::kModeEmfImageMasks;
   const bool bNewBitmap = pPage->BackgroundAlphaNeeded() ||
                           (pPage->HasImageMask() && !bEnableImageMasks) ||
                           pPage->GetMaskBoundingBoxes().size() > 100;
diff --git a/fpdfsdk/fpdf_view_embeddertest.cpp b/fpdfsdk/fpdf_view_embeddertest.cpp
index 888e940..d3b1c10 100644
--- a/fpdfsdk/fpdf_view_embeddertest.cpp
+++ b/fpdfsdk/fpdf_view_embeddertest.cpp
@@ -1463,4 +1463,22 @@
 
   UnloadPage(page);
 }
+
+TEST_F(FPDFViewEmbedderTest, ImageMask) {
+  ASSERT_TRUE(OpenDocument("bug_674771.pdf"));
+  FPDF_PAGE page = LoadPage(0);
+  ASSERT_TRUE(page);
+
+  // Render the page with more efficient processing of image masks.
+  FPDF_SetPrintMode(FPDF_PRINTMODE_EMF_IMAGE_MASKS);
+  std::vector<uint8_t> emf_image_masks = RenderPageWithFlagsToEmf(page, 0);
+
+  // Render the page normally.
+  FPDF_SetPrintMode(FPDF_PRINTMODE_EMF);
+  std::vector<uint8_t> emf_normal = RenderPageWithFlagsToEmf(page, 0);
+
+  EXPECT_LT(emf_image_masks.size(), emf_normal.size());
+
+  UnloadPage(page);
+}
 #endif  // defined(OS_WIN)
diff --git a/public/fpdf_edit.h b/public/fpdf_edit.h
index 8e0d234..f842869 100644
--- a/public/fpdf_edit.h
+++ b/public/fpdf_edit.h
@@ -63,12 +63,14 @@
 #define FPDF_LINEJOIN_ROUND 1
 #define FPDF_LINEJOIN_BEVEL 2
 
+// See FPDF_SetPrintMode() for descriptions.
 #define FPDF_PRINTMODE_EMF 0
 #define FPDF_PRINTMODE_TEXTONLY 1
 #define FPDF_PRINTMODE_POSTSCRIPT2 2
 #define FPDF_PRINTMODE_POSTSCRIPT3 3
 #define FPDF_PRINTMODE_POSTSCRIPT2_PASSTHROUGH 4
 #define FPDF_PRINTMODE_POSTSCRIPT3_PASSTHROUGH 5
+#define FPDF_PRINTMODE_EMF_IMAGE_MASKS 6
 
 typedef struct FPDF_IMAGEOBJ_METADATA {
   // The image width in pixels.
diff --git a/public/fpdfview.h b/public/fpdfview.h
index 1e67bcb..2bc459f 100644
--- a/public/fpdfview.h
+++ b/public/fpdfview.h
@@ -325,6 +325,8 @@
 //                 PostScript via ExtEscape() in PASSTHROUGH mode.
 //                 FPDF_PRINTMODE_POSTSCRIPT3_PASSTHROUGH to output level 3
 //                 PostScript via ExtEscape() in PASSTHROUGH mode.
+//                 FPDF_PRINTMODE_EMF_IMAGE_MASKS to output EMF, with more
+//                 efficient processing of documents containing image masks.
 // Return value:
 //          True if successful, false if unsuccessful (typically invalid input).
 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDF_SetPrintMode(int mode);
diff --git a/testing/resources/bug_674771.in b/testing/resources/bug_674771.in
new file mode 100644
index 0000000..3352f30
--- /dev/null
+++ b/testing/resources/bug_674771.in
@@ -0,0 +1,56 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents [4 0 R]
+  /MediaBox [0 0 792 612]
+  /Resources <<
+    /ProcSet [/PDF /ImageB]
+    /XObject <<
+      /Im0 5 0 R
+    >>
+  >>
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+400 0 0 300 60 100 cm
+/Im0 Do
+Q
+endstream
+endobj
+{{object 5 0}} <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 1
+  /Decode [0.0 1.0]
+  /Filter [/ASCIIHexDecode /JBIG2Decode]
+  /Height 400
+  /ImageMask true
+  /Width 400
+  {{streamlen}}
+>>
+stream
+000000003000010000001300000dea00000353000017110000171151000000000001260001000000
+3500000dea000003530000000000000000020003fffdff02fefefeff7f86530fb6c922cfff7fff7f
+ff7fff7fff7fff7fff7fff7fffac
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/bug_674771.pdf b/testing/resources/bug_674771.pdf
new file mode 100644
index 0000000..6b2dab8
--- /dev/null
+++ b/testing/resources/bug_674771.pdf
@@ -0,0 +1,68 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents [4 0 R]
+  /MediaBox [0 0 792 612]
+  /Resources <<
+    /ProcSet [/PDF /ImageB]
+    /XObject <<
+      /Im0 5 0 R
+    >>
+  >>
+>>
+endobj
+4 0 obj <<
+  /Length 34
+>>
+stream
+q
+400 0 0 300 60 100 cm
+/Im0 Do
+Q
+endstream
+endobj
+5 0 obj <<
+  /Type /XObject
+  /Subtype /Image
+  /BitsPerComponent 1
+  /Decode [0.0 1.0]
+  /Filter [/ASCIIHexDecode /JBIG2Decode]
+  /Height 400
+  /ImageMask true
+  /Width 400
+  /Length 191
+>>
+stream
+000000003000010000001300000dea00000353000017110000171151000000000001260001000000
+3500000dea000003530000000000000000020003fffdff02fefefeff7f86530fb6c922cfff7fff7f
+ff7fff7fff7fff7fff7fff7fffac
+endstream
+endobj
+xref
+0 6
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000131 00000 n 
+0000000317 00000 n 
+0000000402 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 6
+>>
+startxref
+808
+%%EOF