Add tests for FPDF_SetPrintMode().

Set the mode to FPDF_PRINTMODE_POSTSCRIPT2 and
FPDF_PRINTMODE_POSTSCRIPT3, and try to generate some EMFs
that encapsulate PS data.

Add EmbedderTest::GetPostScriptFromEmf(), which is a simplified version
of Chromium code with similar functions, and use it to extract out the
PS data for comparison in tests.

Change-Id: I5cff4ebf0fbac60f63f07b98490a45b8ee3515df
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/55091
Commit-Queue: Lei Zhang <thestig@chromium.org>
Reviewed-by: Tom Sepez <tsepez@chromium.org>
diff --git a/fpdfsdk/fpdf_view_embeddertest.cpp b/fpdfsdk/fpdf_view_embeddertest.cpp
index d8eef2c..077be7a 100644
--- a/fpdfsdk/fpdf_view_embeddertest.cpp
+++ b/fpdfsdk/fpdf_view_embeddertest.cpp
@@ -21,6 +21,55 @@
 
 namespace {
 
+#if defined(OS_WIN)
+const char kExpectedRectanglePostScript[] = R"(
+save
+/im/initmatrix load def
+/n/newpath load def/m/moveto load def/l/lineto load def/c/curveto load def/h/closepath load def
+/f/fill load def/F/eofill load def/s/stroke load def/W/clip load def/W*/eoclip load def
+/rg/setrgbcolor load def/k/setcmykcolor load def
+/J/setlinecap load def/j/setlinejoin load def/w/setlinewidth load def/M/setmiterlimit load def/d/setdash load def
+/q/gsave load def/Q/grestore load def/iM/imagemask load def
+/Tj/show load def/Ff/findfont load def/Fs/scalefont load def/Sf/setfont load def
+/cm/concat load def/Cm/currentmatrix load def/mx/matrix load def/sm/setmatrix load def
+0 300 m 0 0 l 200 0 l 200 300 l 0 300 l h W n
+q
+0 300 m 0 0 l 200 0 l 200 300 l 0 300 l h W n
+q
+0 J
+[]0 d
+0 j
+1 w
+10 M
+mx Cm [1 0 0 -1 0 300]cm 0 290 m 10 290 l 10 300 l 0 300 l 0 290 l h 0 0 0 rg
+q F Q s sm
+mx Cm [1 0 0 -1 0 300]cm 10 150 m 60 150 l 60 180 l 10 180 l 10 150 l h q F Q s sm
+mx Cm [1 0 0 -1 0 300]cm 190 290 m 200 290 l 200 300 l 190 300 l 190 290 l h 0 0 1 rg
+q F Q 0 0 0 rg
+s sm
+mx Cm [1 0 0 -1 0 300]cm 70 232 m 120 232 l 120 262 l 70 262 l 70 232 l h 0 0 1 rg
+q F Q 0 0 0 rg
+s sm
+mx Cm [1 0 0 -1 0 300]cm 190 0 m 200 0 l 200 10 l 190 10 l 190 0 l h 0 1 0 rg
+q F Q 0 0 0 rg
+s sm
+mx Cm [1 0 0 -1 0 300]cm 130 150 m 180 150 l 180 180 l 130 180 l 130 150 l h 0 1 0 rg
+q F Q 0 0 0 rg
+s sm
+mx Cm [1 0 0 -1 0 300]cm 0 0 m 10 0 l 10 10 l 0 10 l 0 0 l h 1 0 0 rg
+q F Q 0 0 0 rg
+s sm
+mx Cm [1 0 0 -1 0 300]cm 70 67 m 120 67 l 120 97 l 70 97 l 70 67 l h 1 0 0 rg
+q F Q 0 0 0 rg
+s sm
+Q
+Q
+Q
+
+restore
+)";
+#endif  // defined(OS_WIN)
+
 class MockDownloadHints final : public FX_DOWNLOADHINTS {
  public:
   static void SAddSegment(FX_DOWNLOADHINTS* pThis, size_t offset, size_t size) {
@@ -908,7 +957,7 @@
 }
 
 #if defined(OS_WIN)
-TEST_F(FPDFViewEmbedderTest, FPDF_RenderPage) {
+TEST_F(FPDFViewEmbedderTest, FPDFRenderPageEmf) {
   ASSERT_TRUE(OpenDocument("rectangles.pdf"));
   FPDF_PAGE page = LoadPage(0);
   ASSERT_TRUE(page);
@@ -923,4 +972,75 @@
 
   UnloadPage(page);
 }
-#endif
+
+class PostScriptRenderEmbedderTestBase : public FPDFViewEmbedderTest {
+ protected:
+  ~PostScriptRenderEmbedderTestBase() override = default;
+
+  // FPDFViewEmbedderTest:
+  void TearDown() override {
+    FPDF_SetPrintMode(FPDF_PRINTMODE_EMF);
+    FPDFViewEmbedderTest::TearDown();
+  }
+};
+
+class PostScriptLevel2EmbedderTest : public PostScriptRenderEmbedderTestBase {
+ public:
+  PostScriptLevel2EmbedderTest() = default;
+  ~PostScriptLevel2EmbedderTest() override = default;
+
+ protected:
+  // FPDFViewEmbedderTest:
+  void SetUp() override {
+    FPDFViewEmbedderTest::SetUp();
+    FPDF_SetPrintMode(FPDF_PRINTMODE_POSTSCRIPT2);
+  }
+};
+
+class PostScriptLevel3EmbedderTest : public PostScriptRenderEmbedderTestBase {
+ public:
+  PostScriptLevel3EmbedderTest() = default;
+  ~PostScriptLevel3EmbedderTest() override = default;
+
+ protected:
+  // FPDFViewEmbedderTest:
+  void SetUp() override {
+    FPDFViewEmbedderTest::SetUp();
+    FPDF_SetPrintMode(FPDF_PRINTMODE_POSTSCRIPT3);
+  }
+};
+
+TEST_F(PostScriptLevel2EmbedderTest, Rectangles) {
+  ASSERT_TRUE(OpenDocument("rectangles.pdf"));
+  FPDF_PAGE page = LoadPage(0);
+  ASSERT_TRUE(page);
+
+  std::vector<uint8_t> emf_normal = RenderPageWithFlagsToEmf(page, 0);
+  std::string ps_data = GetPostScriptFromEmf(emf_normal);
+  EXPECT_STREQ(kExpectedRectanglePostScript, ps_data.c_str());
+
+  // FPDF_REVERSE_BYTE_ORDER is ignored since PostScript is not bitmap-based.
+  std::vector<uint8_t> emf_reverse_byte_order =
+      RenderPageWithFlagsToEmf(page, FPDF_REVERSE_BYTE_ORDER);
+  EXPECT_EQ(emf_normal, emf_reverse_byte_order);
+
+  UnloadPage(page);
+}
+
+TEST_F(PostScriptLevel3EmbedderTest, Rectangles) {
+  ASSERT_TRUE(OpenDocument("rectangles.pdf"));
+  FPDF_PAGE page = LoadPage(0);
+  ASSERT_TRUE(page);
+
+  std::vector<uint8_t> emf_normal = RenderPageWithFlagsToEmf(page, 0);
+  std::string ps_data = GetPostScriptFromEmf(emf_normal);
+  EXPECT_STREQ(kExpectedRectanglePostScript, ps_data.c_str());
+
+  // FPDF_REVERSE_BYTE_ORDER is ignored since PostScript is not bitmap-based.
+  std::vector<uint8_t> emf_reverse_byte_order =
+      RenderPageWithFlagsToEmf(page, FPDF_REVERSE_BYTE_ORDER);
+  EXPECT_EQ(emf_normal, emf_reverse_byte_order);
+
+  UnloadPage(page);
+}
+#endif  // defined(OS_WIN)
diff --git a/testing/embedder_test.cpp b/testing/embedder_test.cpp
index 9e64ee5..b092bc6 100644
--- a/testing/embedder_test.cpp
+++ b/testing/embedder_test.cpp
@@ -52,6 +52,18 @@
   }
 }
 
+#if defined(OS_WIN)
+int CALLBACK GetRecordProc(HDC hdc,
+                           HANDLETABLE* handle_table,
+                           const ENHMETARECORD* record,
+                           int objects_count,
+                           LPARAM param) {
+  auto& records = *reinterpret_cast<std::vector<const ENHMETARECORD*>*>(param);
+  records.push_back(record);
+  return 1;
+}
+#endif  // defined(OS_WIN)
+
 }  // namespace
 
 EmbedderTest::EmbedderTest()
@@ -405,6 +417,40 @@
   DeleteEnhMetaFile(emf);
   return buffer;
 }
+
+// static
+std::string EmbedderTest::GetPostScriptFromEmf(
+    const std::vector<uint8_t>& emf_data) {
+  // This comes from Emf::InitFromData() in Chromium.
+  HENHMETAFILE emf = SetEnhMetaFileBits(emf_data.size(), emf_data.data());
+  if (!emf)
+    return std::string();
+
+  // This comes from Emf::Enumerator::Enumerator() in Chromium.
+  std::vector<const ENHMETARECORD*> records;
+  if (!EnumEnhMetaFile(nullptr, emf, &GetRecordProc, &records, nullptr)) {
+    DeleteEnhMetaFile(emf);
+    return std::string();
+  }
+
+  // This comes from PostScriptMetaFile::SafePlayback() in Chromium.
+  std::string ps_data;
+  for (const auto* record : records) {
+    if (record->iType != EMR_GDICOMMENT)
+      continue;
+
+    // PostScript data is encapsulated inside EMF comment records.
+    // The first two bytes of the comment indicate the string length. The rest
+    // is the actual string data.
+    const auto* comment = reinterpret_cast<const EMRGDICOMMENT*>(record);
+    const char* data = reinterpret_cast<const char*>(comment->Data);
+    uint16_t size = *reinterpret_cast<const uint16_t*>(data);
+    data += 2;
+    ps_data.append(data, size);
+  }
+  DeleteEnhMetaFile(emf);
+  return ps_data;
+}
 #endif  // defined(OS_WIN)
 
 FPDF_DOCUMENT EmbedderTest::OpenSavedDocument() {
diff --git a/testing/embedder_test.h b/testing/embedder_test.h
index 56bf61e..a088577 100644
--- a/testing/embedder_test.h
+++ b/testing/embedder_test.h
@@ -166,6 +166,9 @@
   // Convert |page| into EMF with the specified page rendering |flags|.
   static std::vector<uint8_t> RenderPageWithFlagsToEmf(FPDF_PAGE page,
                                                        int flags);
+
+  // Get the PostScript data from |emf_data|.
+  static std::string GetPostScriptFromEmf(const std::vector<uint8_t>& emf_data);
 #endif
 
  protected: