Add GDI renderer to pdfium_test

Adds a new GDI renderer to pdfium_test. When passed --use-renderer=gdi,
pdfium_test will use the FPDF_RenderPage() function that takes a GDI
device context handle (HDC), instead of rendering to a bitmap.

Note that the GDI renderer does not support alpha.

Bug: pdfium:2054
Change-Id: I43ba490d27470c9f03d1f4a4d98f6ff3266255fc
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/108930
Reviewed-by: Lei Zhang <thestig@chromium.org>
Commit-Queue: K. Moon <kmoon@chromium.org>
diff --git a/samples/pdfium_test.cc b/samples/pdfium_test.cc
index c979db3..f25ebfc 100644
--- a/samples/pdfium_test.cc
+++ b/samples/pdfium_test.cc
@@ -3,6 +3,8 @@
 // found in the LICENSE file.
 
 #include <locale.h>
+#include <stddef.h>
+#include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -41,11 +43,15 @@
 #include "testing/utils/hash.h"
 #include "testing/utils/path_service.h"
 #include "third_party/abseil-cpp/absl/types/optional.h"
+#include "third_party/base/check_op.h"
 
 #ifdef _WIN32
 #include <crtdbg.h>
 #include <errhandlingapi.h>
 #include <io.h>
+#include <wingdi.h>
+
+#include "third_party/base/win/scoped_select_object.h"
 #else
 #include <unistd.h>
 #endif  // _WIN32
@@ -95,6 +101,17 @@
 
 namespace {
 
+enum class RendererType {
+  kDefault,
+  kAgg,
+#ifdef _WIN32
+  kGdi,
+#endif  // _WIN32
+#if defined(PDF_ENABLE_SKIA)
+  kSkia,
+#endif  // defined(PDF_ENABLE_SKIA)
+};
+
 enum class OutputFormat {
   kNone,
   kPageInfo,
@@ -141,7 +158,7 @@
   bool save_thumbnails = false;
   bool save_thumbnails_decoded = false;
   bool save_thumbnails_raw = false;
-  absl::optional<FPDF_RENDERER_TYPE> use_renderer_type;
+  RendererType use_renderer_type = RendererType::kDefault;
 #ifdef PDF_ENABLE_V8
   bool disable_javascript = false;
   std::string js_flags;  // Extra flags to pass to v8 init.
@@ -487,15 +504,19 @@
     } else if (cur_arg == "--save-thumbs-raw") {
       options->save_thumbnails_raw = true;
     } else if (ParseSwitchKeyValue(cur_arg, "--use-renderer=", &value)) {
-      if (options->use_renderer_type.has_value()) {
+      if (options->use_renderer_type != RendererType::kDefault) {
         fprintf(stderr, "Duplicate --use-renderer argument\n");
         return false;
       }
       if (value == "agg") {
-        options->use_renderer_type = FPDF_RENDERERTYPE_AGG;
+        options->use_renderer_type = RendererType::kAgg;
+#ifdef _WIN32
+      } else if (value == "gdi") {
+        options->use_renderer_type = RendererType::kGdi;
+#endif  // _WIN32
 #if defined(PDF_ENABLE_SKIA)
       } else if (value == "skia") {
-        options->use_renderer_type = FPDF_RENDERERTYPE_SKIA;
+        options->use_renderer_type = RendererType::kSkia;
 #endif  // defined(PDF_ENABLE_SKIA)
       } else {
         fprintf(stderr, "Invalid --use-renderer argument\n");
@@ -871,19 +892,6 @@
 
   bool HasOutput() const override { return !!bitmap_; }
 
-  bool Start() override {
-    bool alpha = FPDFPage_HasTransparency(page());
-    bitmap_.reset(FPDFBitmap_Create(/*width=*/width(), /*height=*/height(),
-                                    /*alpha=*/alpha));
-    if (!bitmap_)
-      return false;
-
-    FPDF_DWORD fill_color = alpha ? 0x00000000 : 0xFFFFFFFF;
-    FPDFBitmap_FillRect(bitmap(), /*left=*/0, /*top=*/0, /*width=*/width(),
-                        /*height=*/height(), /*color=*/fill_color);
-    return true;
-  }
-
   void Finish(FPDF_FORMHANDLE form) override {
     FPDF_FFLDraw(form, bitmap(), page(), /*start_x=*/0, /*start_y=*/0,
                  /*size_x=*/width(), /*size_y=*/height(), /*rotate=*/0,
@@ -906,6 +914,22 @@
         idler_(idler),
         writer_(std::move(writer)) {}
 
+  bool InitializeBitmap(void* first_scan) {
+    bool alpha = FPDFPage_HasTransparency(page());
+    bitmap_.reset(FPDFBitmap_CreateEx(
+        /*width=*/width(), /*height=*/height(),
+        /*format=*/alpha ? FPDFBitmap_BGRA : FPDFBitmap_BGRx, first_scan,
+        /*stride=*/width() * sizeof(uint32_t)));
+    if (!bitmap()) {
+      return false;
+    }
+
+    FPDF_DWORD fill_color = alpha ? 0x00000000 : 0xFFFFFFFF;
+    FPDFBitmap_FillRect(bitmap(), /*left=*/0, /*top=*/0, /*width=*/width(),
+                        /*height=*/height(), /*color=*/fill_color);
+    return true;
+  }
+
   void Idle() const { idler_(); }
   FPDF_BITMAP bitmap() { return bitmap_.get(); }
 
@@ -932,8 +956,9 @@
                            std::move(writer)) {}
 
   bool Start() override {
-    if (!BitmapPageRenderer::Start())
+    if (!InitializeBitmap(/*first_scan=*/nullptr)) {
       return false;
+    }
 
     // Note, client programs probably want to use this method instead of the
     // progressive calls. The progressive calls are if you need to pause the
@@ -967,8 +992,9 @@
   }
 
   bool Start() override {
-    if (!BitmapPageRenderer::Start())
+    if (!InitializeBitmap(/*first_scan=*/nullptr)) {
       return false;
+    }
 
     if (FPDF_RenderPageBitmapWithColorScheme_Start(
             bitmap(), page(), /*start_x=*/0, /*start_y=*/0, /*size_x=*/width(),
@@ -999,6 +1025,117 @@
   bool to_be_continued_ = false;
 };
 
+#ifdef _WIN32
+class ScopedGdiDc final {
+ public:
+  ~ScopedGdiDc() { Reset(nullptr); }
+
+  void Reset(HDC dc) {
+    if (dc_) {
+      [[maybe_unused]] BOOL success = DeleteDC(dc_);
+      DCHECK(success);
+    }
+    dc_ = dc;
+  }
+
+  HDC Get() const { return dc_; }
+
+ private:
+  HDC dc_ = nullptr;
+};
+
+class ScopedGdiObject final {
+ public:
+  ~ScopedGdiObject() { Reset(nullptr); }
+
+  void Reset(HGDIOBJ object) {
+    if (object_) {
+      [[maybe_unused]] BOOL success = DeleteObject(object_);
+      DCHECK(success);
+    }
+    object_ = object;
+  }
+
+  HGDIOBJ Get() const { return object_; }
+
+ private:
+  HGDIOBJ object_ = nullptr;
+};
+
+class GdiDisplayPageRenderer : public BitmapPageRenderer {
+ public:
+  GdiDisplayPageRenderer(FPDF_PAGE page,
+                         int width,
+                         int height,
+                         int flags,
+                         const std::function<void()>& idler,
+                         PageWriter writer)
+      : BitmapPageRenderer(page,
+                           /*width=*/width,
+                           /*height=*/height,
+                           /*flags=*/flags,
+                           idler,
+                           std::move(writer)) {}
+
+  bool Start() override {
+    // Create an in-memory DC compatible with the display.
+    dc_.Reset(CreateCompatibleDC(/*hdc=*/nullptr));
+    if (!dc_.Get()) {
+      return false;
+    }
+
+    // Create a BGRA DIB and select it into the in-memory DC.
+    BITMAPINFO dib_info;
+    memset(&dib_info, 0, sizeof(BITMAPINFO));
+    dib_info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
+    dib_info.bmiHeader.biWidth = width();
+    dib_info.bmiHeader.biHeight = -height();  // top-down
+    dib_info.bmiHeader.biPlanes = 1;
+    dib_info.bmiHeader.biBitCount = 32;
+    dib_info.bmiHeader.biCompression = BI_RGB;
+
+    VOID* dib_pixels;
+    dib_.Reset(CreateDIBSection(dc_.Get(), &dib_info, DIB_RGB_COLORS,
+                                &dib_pixels, /*hSection=*/nullptr,
+                                /*offset=*/0));
+    if (!dib_.Get() || !InitializeBitmap(dib_pixels)) {
+      return false;
+    }
+    pdfium::base::win::ScopedSelectObject select_dib(dc_.Get(), dib_.Get());
+
+    // Render into the in-memory DC.
+    FPDF_RenderPage(dc_.Get(), page(), /*start_x=*/0, /*start_y=*/0,
+                    /*size_x=*/width(), /*size_y=*/height(), /*rotate=*/0,
+                    /*flags=*/flags());
+
+    return !!GdiFlush();
+  }
+
+  void Finish(FPDF_FORMHANDLE /*form*/) override {
+    // Note that `fpdf_formfill.h` does not support GDI.
+
+    // The GDI backend doesn't support alpha and clears the alpha component to
+    // transparent, so clear the alpha component back to opaque.
+    const int stride = FPDFBitmap_GetStride(bitmap());
+    DCHECK_EQ(width() * sizeof(uint32_t), static_cast<size_t>(stride));
+    const int pixel_stride = stride / sizeof(uint32_t);
+
+    uint32_t* scanline =
+        reinterpret_cast<uint32_t*>(FPDFBitmap_GetBuffer(bitmap()));
+    for (int row = 0; row < height(); ++row) {
+      for (int column = 0; column < width(); ++column) {
+        scanline[column] |= 0xFF000000;
+      }
+      scanline += pixel_stride;
+    }
+  }
+
+ private:
+  ScopedGdiDc dc_;
+  ScopedGdiObject dib_;
+};
+#endif  // _WIN32
+
 #ifdef PDF_ENABLE_SKIA
 class SkPicturePageRenderer : public PageRenderer {
  public:
@@ -1148,6 +1285,14 @@
       break;
   }
 
+#ifdef _WIN32
+  if (!renderer && options.use_renderer_type == RendererType::kGdi) {
+    renderer = std::make_unique<GdiDisplayPageRenderer>(
+        page, /*width=*/width, /*height=*/height, /*flags=*/flags, idler,
+        std::move(writer));
+  }
+#endif  // _WIN32
+
   if (!renderer) {
     // Use a rasterizing page renderer by default.
     if (options.render_oneshot) {
@@ -1418,11 +1563,21 @@
     "<pdf-name>.thumbnail.decoded.<page-number>.png\n"
     "  --save-thumbs-raw      - write page thumbnails' raw stream data"
     "<pdf-name>.thumbnail.raw.<page-number>.png\n"
+
 #if defined(PDF_ENABLE_SKIA)
+#ifdef _WIN32
+    "  --use-renderer         - renderer to use, one of [agg | gdi | skia]\n"
+#else
     "  --use-renderer         - renderer to use, one of [agg | skia]\n"
+#endif  // _WIN32
+#else
+#ifdef _WIN32
+    "  --use-renderer         - renderer to use, one of [agg | gdi]\n"
 #else
     "  --use-renderer         - renderer to use, one of [agg]\n"
+#endif  // _WIN32
 #endif  // defined(PDF_ENABLE_SKIA)
+
 #ifdef PDF_ENABLE_V8
     "  --disable-javascript   - do not execute JS in PDF files\n"
     "  --js-flags=<flags>     - additional flags to pass to V8\n"
@@ -1501,23 +1656,40 @@
     return 1;
   }
 
-  const FPDF_RENDERER_TYPE renderer_type =
-      options.use_renderer_type.value_or(GetDefaultRendererType());
-#if defined(PDF_ENABLE_SKIA) && defined(BUILD_WITH_CHROMIUM)
-  if (renderer_type == FPDF_RENDERERTYPE_SKIA) {
-    // Needed to support Chromium's copy of Skia, which uses a
-    // DiscardableMemoryAllocator.
-    chromium_support::InitializeDiscardableMemoryAllocator();
-  }
-#endif
-
   FPDF_LIBRARY_CONFIG config;
   config.version = 4;
   config.m_pUserFontPaths = nullptr;
   config.m_pIsolate = nullptr;
   config.m_v8EmbedderSlot = 0;
   config.m_pPlatform = nullptr;
-  config.m_RendererType = renderer_type;
+
+  switch (options.use_renderer_type) {
+    case RendererType::kDefault:
+      config.m_RendererType = GetDefaultRendererType();
+      break;
+
+    case RendererType::kAgg:
+      config.m_RendererType = FPDF_RENDERERTYPE_AGG;
+      break;
+
+#ifdef _WIN32
+    case RendererType::kGdi:
+      // GDI renderer uses `FPDF_RenderPage()`, rather than a renderer type.
+      config.m_RendererType = GetDefaultRendererType();
+      break;
+#endif  // _WIN32
+
+#if defined(PDF_ENABLE_SKIA)
+    case RendererType::kSkia:
+#if defined(BUILD_WITH_CHROMIUM)
+      // Needed to support Chromium's copy of Skia, which uses a
+      // `DiscardableMemoryAllocator`.
+      chromium_support::InitializeDiscardableMemoryAllocator();
+#endif  // defined(BUILD_WITH_CHROMIUM)
+      config.m_RendererType = FPDF_RENDERERTYPE_SKIA;
+      break;
+#endif  // defined(PDF_ENABLE_SKIA)
+  }
 
   std::function<void()> idler = []() {};
 #ifdef PDF_ENABLE_V8