Add a flag for selecting renderer at runtime

Introduce an experimental config flag in FPDF_LIBRARY_CONFIG to allow
for changing the renderer at runtime. This flag only takes effect if the
config version is 4 or higher. When using a config version of 4, it is
necessary to specify a valid renderer type that will be used to specify
the renderer type at runtime. This is required even if a build does not
include Skia definitions and only has AGG as the available renderer
type.

Expose this flag in the pdfium_test sample, via the --use-renderer
command line option. This option is only available for builds which
are Skia enabled.

Bug: pdfium:1878
Change-Id: I971f8f7d420015a919316b1b3d57fe3d7bea4ce4
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/96904
Reviewed-by: Nigi <nigi@chromium.org>
Commit-Queue: Alan Screen <awscreen@chromium.org>
Reviewed-by: Lei Zhang <thestig@chromium.org>
diff --git a/build_overrides/pdfium.gni b/build_overrides/pdfium.gni
index 6037a63..8553398 100644
--- a/build_overrides/pdfium.gni
+++ b/build_overrides/pdfium.gni
@@ -21,9 +21,11 @@
 # Default: Use PartitionAlloc unless it is a MSVC build.
 pdf_use_partition_alloc_override = !is_win || is_clang
 
-# Build PDFium against skia (experimental) rather than agg, replacing all PDFium
-# graphics.
-# Default: Use agg.
+# Build PDFium to use Skia (experimental) for all PDFium graphics.
+# If enabled, coexists in build with AGG graphics and the default
+# renderer is selectable at runtime.
+# The default is to use AGG when both `pdf_use_skia_override` and
+# `pdf_use_skia_paths_override` are set to their defaults of false.
 pdf_use_skia_override = false
 
 # Build PDFium against skia (experimental) rather than agg, adding only path
diff --git a/core/fxge/cfx_defaultrenderdevice.cpp b/core/fxge/cfx_defaultrenderdevice.cpp
index 90ee030..d50d4a1 100644
--- a/core/fxge/cfx_defaultrenderdevice.cpp
+++ b/core/fxge/cfx_defaultrenderdevice.cpp
@@ -8,12 +8,32 @@
 
 #include "core/fxge/dib/cfx_dibitmap.h"
 
+namespace {
+
+// When build variant is Skia then it is assumed as the default, but might be
+// overridden at runtime.
+#if defined(_SKIA_SUPPORT_)
+CFX_DefaultRenderDevice::RendererType g_default_renderer_type =
+    CFX_DefaultRenderDevice::RendererType::kSkia;
+#endif
+
+#if defined(_SKIA_SUPPORT_) || defined(_SKIA_SUPPORT_PATHS_)
+bool IsSkiaVariant() {
+#if defined(_SKIA_SUPPORT_)
+  return g_default_renderer_type ==
+         CFX_DefaultRenderDevice::RendererType::kSkia;
+#elif defined(_SKIA_SUPPORT_PATHS_)
+  return true;
+#endif
+}
+#endif  // defined(_SKIA_SUPPORT_) || defined(_SKIA_SUPPORT_PATHS_)
+
+}  // namespace
+
 // static
 bool CFX_DefaultRenderDevice::SkiaIsDefaultRenderer() {
 #if defined(_SKIA_SUPPORT_)
-  // TODO(crbug.com/pdfium/1878) This will become variable-based once a method
-  // is provided to set the default at runtime.
-  return true;
+  return g_default_renderer_type == RendererType::kSkia;
 #else
   return false;
 #endif
@@ -22,19 +42,25 @@
 // static
 bool CFX_DefaultRenderDevice::SkiaPathsIsDefaultRenderer() {
 #if defined(_SKIA_SUPPORT_PATHS_)
-  // TODO(crbug.com/pdfium/1878) This will become variable-based once a method
-  // is provided to set the default at runtime.
   return true;
 #else
   return false;
 #endif
 }
 
+#if defined(_SKIA_SUPPORT_)
+// static
+void CFX_DefaultRenderDevice::SetDefaultRenderer(RendererType renderer_type) {
+  g_default_renderer_type = renderer_type;
+}
+#endif
+
 CFX_DefaultRenderDevice::CFX_DefaultRenderDevice() = default;
 
 CFX_DefaultRenderDevice::~CFX_DefaultRenderDevice() {
 #if defined(_SKIA_SUPPORT_) || defined(_SKIA_SUPPORT_PATHS_)
-  Flush(true);
+  if (IsSkiaVariant())
+    Flush(true);
 #endif
 }
 
@@ -62,11 +88,12 @@
     RetainPtr<CFX_DIBitmap> pBackdropBitmap,
     bool bGroupKnockout) {
 #if defined(_SKIA_SUPPORT_) || defined(_SKIA_SUPPORT_PATHS_)
-  return AttachSkiaImpl(pBitmap, bRgbByteOrder, pBackdropBitmap,
-                        bGroupKnockout);
-#else
-  return AttachAggImpl(pBitmap, bRgbByteOrder, pBackdropBitmap, bGroupKnockout);
+  if (IsSkiaVariant()) {
+    return AttachSkiaImpl(pBitmap, bRgbByteOrder, pBackdropBitmap,
+                          bGroupKnockout);
+  }
 #endif
+  return AttachAggImpl(pBitmap, bRgbByteOrder, pBackdropBitmap, bGroupKnockout);
 }
 
 bool CFX_DefaultRenderDevice::Create(int width,
@@ -74,8 +101,8 @@
                                      FXDIB_Format format,
                                      RetainPtr<CFX_DIBitmap> pBackdropBitmap) {
 #if defined(_SKIA_SUPPORT_) || defined(_SKIA_SUPPORT_PATHS_)
-  return CreateSkia(width, height, format, pBackdropBitmap);
-#else
-  return CreateAgg(width, height, format, pBackdropBitmap);
+  if (IsSkiaVariant())
+    return CreateSkia(width, height, format, pBackdropBitmap);
 #endif
+  return CreateAgg(width, height, format, pBackdropBitmap);
 }
diff --git a/core/fxge/cfx_defaultrenderdevice.h b/core/fxge/cfx_defaultrenderdevice.h
index 3ffbe04..0e7d77f 100644
--- a/core/fxge/cfx_defaultrenderdevice.h
+++ b/core/fxge/cfx_defaultrenderdevice.h
@@ -49,6 +49,20 @@
   // Runtime check to see if SkiaPaths is the renderer variant in use.
   static bool SkiaPathsIsDefaultRenderer();
 
+#if defined(_SKIA_SUPPORT_)
+  // This internal definition of renderer types must stay updated with respect
+  // to the public definition of `FPDF_RENDERER_TYPE`, so that all public
+  // definition values can be mapped to a value in
+  // `CFX_DefaultRenderDevice::RendererType`.
+  enum class RendererType {
+    kAgg = 0,
+    kSkia = 1,
+  };
+
+  // Update default renderer.
+  static void SetDefaultRenderer(RendererType renderer_type);
+#endif  // defined(_SKIA_SUPPORT_)
+
  private:
   bool AttachImpl(RetainPtr<CFX_DIBitmap> pBitmap,
                   bool bRgbByteOrder,
diff --git a/fpdfsdk/fpdf_view.cpp b/fpdfsdk/fpdf_view.cpp
index 90d0f68..62b96ca 100644
--- a/fpdfsdk/fpdf_view.cpp
+++ b/fpdfsdk/fpdf_view.cpp
@@ -47,6 +47,7 @@
 #include "fpdfsdk/cpdfsdk_renderpage.h"
 #include "fxjs/ijs_runtime.h"
 #include "public/fpdf_formfill.h"
+#include "third_party/base/check_op.h"
 #include "third_party/base/numerics/safe_conversions.h"
 #include "third_party/base/ptr_util.h"
 #include "third_party/base/span.h"
@@ -96,10 +97,45 @@
     "WindowsPrintMode::kPostScript3Type42PassThrough value mismatch");
 #endif  // BUILDFLAG(IS_WIN)
 
+#if defined(_SKIA_SUPPORT_)
+// These checks are here because core/ and public/ cannot depend on each other.
+static_assert(static_cast<int>(CFX_DefaultRenderDevice::RendererType::kAgg) ==
+                  FPDF_RENDERERTYPE_AGG,
+              "CFX_DefaultRenderDevice::RendererType::kAGG value mismatch");
+static_assert(static_cast<int>(CFX_DefaultRenderDevice::RendererType::kSkia) ==
+                  FPDF_RENDERERTYPE_SKIA,
+              "CFX_DefaultRenderDevice::RendererType::kSkia value mismatch");
+#endif  // defined(_SKIA_SUPPORT_)
+
 namespace {
 
 bool g_bLibraryInitialized = false;
 
+void UseRendererType(FPDF_RENDERER_TYPE public_type) {
+  // Internal definition of renderer types must stay updated with respect to
+  // the public definition, such that all public definitions can be mapped to
+  // an internal definition in `CFX_DefaultRenderDevice`. A public definition
+  // value might not be meaningful for a particular build configuration, which
+  // would mean use of that value is an error for that build.
+
+  // AGG is always present in a build. |FPDF_RENDERERTYPE_SKIA| is valid to use
+  // only if it is included in the build.
+#if defined(_SKIA_SUPPORT_)
+  // This build configuration has the option for runtime renderer selection.
+  if (public_type == FPDF_RENDERERTYPE_AGG ||
+      public_type == FPDF_RENDERERTYPE_SKIA) {
+    CFX_DefaultRenderDevice::SetDefaultRenderer(
+        static_cast<CFX_DefaultRenderDevice::RendererType>(public_type));
+    return;
+  }
+  CHECK(false);
+#else
+  // `FPDF_RENDERERTYPE_AGG` is used for fully AGG builds as well as for the
+  // _SKIA_SUPPORT_PATHS_ build configuration.
+  CHECK_EQ(public_type, FPDF_RENDERERTYPE_AGG);
+#endif
+}
+
 const CPDF_Object* GetXFAEntryFromDocument(const CPDF_Document* doc) {
   const CPDF_Dictionary* root = doc->GetRoot();
   if (!root)
@@ -196,6 +232,9 @@
     void* platform = config->version >= 3 ? config->m_pPlatform : nullptr;
     IJS_Runtime::Initialize(config->m_v8EmbedderSlot, config->m_pIsolate,
                             platform);
+
+    if (config->version >= 4)
+      UseRendererType(config->m_RendererType);
   }
   g_bLibraryInitialized = true;
 }
diff --git a/pdfium.gni b/pdfium.gni
index 43800d5..b571a33 100644
--- a/pdfium.gni
+++ b/pdfium.gni
@@ -39,8 +39,9 @@
   # Build PDFium with PartitionAlloc as the memory allocator.
   pdf_use_partition_alloc = pdf_use_partition_alloc_override
 
-  # Build PDFium against Skia (experimental) rather than AGG. Use Skia to draw
-  # everything.
+  # Build PDFium to use Skia (experimental) for all PDFium graphics.
+  # If enabled, coexists in build with AGG graphics and the default
+  # renderer is selectable at runtime.
   pdf_use_skia = pdf_use_skia_override
 
   # Build PDFium against Skia (experimental) rather than AGG. Use Skia to draw
diff --git a/public/fpdfview.h b/public/fpdfview.h
index da4fdc3..9a540e4 100644
--- a/public/fpdfview.h
+++ b/public/fpdfview.h
@@ -234,6 +234,15 @@
 //          future.
 FPDF_EXPORT void FPDF_CALLCONV FPDF_InitLibrary();
 
+// PDF renderer types - Experimental.
+// Selection of 2D graphics library to use for rendering to FPDF_BITMAPs.
+typedef enum {
+  // Anti-Grain Geometry - https://sourceforge.net/projects/agg/
+  FPDF_RENDERERTYPE_AGG = 0,
+  // Skia - https://skia.org/
+  FPDF_RENDERERTYPE_SKIA = 1,
+} FPDF_RENDERER_TYPE;
+
 // Process-wide options for initializing the library.
 typedef struct FPDF_LIBRARY_CONFIG_ {
   // Version number of the interface. Currently must be 2.
@@ -257,11 +266,21 @@
   // embedders.
   unsigned int m_v8EmbedderSlot;
 
-  // Version 3 - Experimantal,
+  // Version 3 - Experimental.
 
   // Pointer to the V8::Platform to use.
   void* m_pPlatform;
 
+  // Version 4 - Experimental.
+
+  // Explicit specification of core renderer to use. |m_RendererType| must be
+  // a valid value for |FPDF_LIBRARY_CONFIG| versions of this level or higher,
+  // or else the initialization will fail with an immediate crash.
+  // Note that use of a specified |FPDF_RENDERER_TYPE| value for which the
+  // corresponding render library is not included in the build will similarly
+  // fail with an immediate crash.
+  FPDF_RENDERER_TYPE m_RendererType;
+
 } FPDF_LIBRARY_CONFIG;
 
 // Function: FPDF_InitLibraryWithConfig
diff --git a/samples/pdfium_test.cc b/samples/pdfium_test.cc
index b69eedb..e35db65 100644
--- a/samples/pdfium_test.cc
+++ b/samples/pdfium_test.cc
@@ -128,6 +128,7 @@
   bool save_thumbnails = false;
   bool save_thumbnails_decoded = false;
   bool save_thumbnails_raw = false;
+  absl::optional<FPDF_RENDERER_TYPE> use_renderer_type;
 #ifdef PDF_ENABLE_V8
   bool disable_javascript = false;
   std::string js_flags;  // Extra flags to pass to v8 init.
@@ -182,6 +183,14 @@
   return flags;
 }
 
+FPDF_RENDERER_TYPE BuildDefaultRendererType() {
+#if defined(_SKIA_SUPPORT_)
+  return FPDF_RENDERERTYPE_SKIA;
+#else
+  return FPDF_RENDERERTYPE_AGG;
+#endif
+}
+
 absl::optional<std::string> ExpandDirectoryPath(const std::string& path) {
 #if defined(WORDEXP_AVAILABLE)
   wordexp_t expansion;
@@ -483,6 +492,23 @@
       options->save_thumbnails_decoded = true;
     } else if (cur_arg == "--save-thumbs-raw") {
       options->save_thumbnails_raw = true;
+#if defined(_SKIA_SUPPORT_)
+    } else if (ParseSwitchKeyValue(cur_arg, "--use-renderer=", &value)) {
+      if (options->use_renderer_type.has_value()) {
+        fprintf(stderr, "Duplicate --use-renderer argument\n");
+        return false;
+      }
+      if (value == "agg") {
+        options->use_renderer_type = FPDF_RENDERERTYPE_AGG;
+      } else if (value == "skia") {
+        options->use_renderer_type = FPDF_RENDERERTYPE_SKIA;
+      } else {
+        fprintf(stderr,
+                "Invalid --use-renderer argument, value must be one of agg or "
+                "skia\n");
+        return false;
+      }
+#endif  // defined(_SKIA_SUPPORT_)
 #ifdef PDF_ENABLE_V8
     } else if (cur_arg == "--disable-javascript") {
       options->disable_javascript = true;
@@ -1133,9 +1159,12 @@
     "<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(_SKIA_SUPPORT_)
+    "  --use-renderer         - renderer to use, one of [agg | skia]\n"
+#endif
 #ifdef PDF_ENABLE_V8
     "  --disable-javascript   - do not execute JS in PDF files\n"
-    "  --js-flags=<flags>     - additional flags to pas to V8"
+    "  --js-flags=<flags>     - additional flags to pass to V8\n"
 #ifdef PDF_ENABLE_XFA
     "  --disable-xfa          - do not process XFA forms\n"
 #endif  // PDF_ENABLE_XFA
@@ -1198,11 +1227,13 @@
   }
 
   FPDF_LIBRARY_CONFIG config;
-  config.version = 3;
+  config.version = 4;
   config.m_pUserFontPaths = nullptr;
   config.m_pIsolate = nullptr;
   config.m_v8EmbedderSlot = 0;
   config.m_pPlatform = nullptr;
+  config.m_RendererType =
+      options.use_renderer_type.value_or(BuildDefaultRendererType());
 
   std::function<void()> idler = []() {};
 #ifdef PDF_ENABLE_V8