Add public API to support forced colors mode

The CL extends support for color scheme in PDFium to a forced
color scheme which is applied when calling the new progressive
render API.
The color scheme is currently applied to only text and path.

For text rendering, the color of the text (fill and stroke) is
changed to the color mentioned in the color scheme.

Similarly colors for fill and stroke can be set for path rendering.
In addition to this there is an option to change the fill paths to
strokes, which is required since with fill paths using a single
color, a graphics with multiple adjacent fill paths will not be
decipherable.

All annotations also follow the above rules, except forms (PDF
annotation widgets like text box, combo box etc.) which
are not part of this change, and will be taken up separately.

There also needs to be a special handling for highlight since
the Multiply blend mode leads to the highlights being less visible
in some color schemes, which will be taken up separately.

The above is achieved by the following changes:

1. Adding a struct for the various graphics objects in a pdf for
which a color can be specified and honored. The
corresponding colors are set by the caller. The CL adds support
for only fill and stroke color of paths and text. Subsequent CLs
will add support for more objects.
2. Adding a new API in the progressive rendering public APIs to
pass in the forced color scheme from the caller.
3. Adding a structure in CPDF_RenderOptions to keep a cache of the
color scheme, and a setter method for the same.
4. Adding a new method CPDF_RenderOptions::TranslateOjectColor()
to translate the color of an object based on its type and
render type to the corresponding forced color.
5. Modifying CPDF_RenderStatus::GetFillArgbInternal() and
CPDF_RenderStatus::GetStrokeArgb() to call the above added method for
translating the color for stroke and fill paths.
6. Modifying CPDF_RenderStatus::ProcessPathPattern() to add code to
convert fill paths to stroke.
7. Adding embedder test for testing rendering using the forced color
scheme in public APIs with different content types.
8. Adding command line flags to pdfium_test to test forced color
scheme rendering using a sample color scheme.

Bug: pdfium:1391
Change-Id: I8b3f43eb0290d11b6a1ee2d5a36dfd1f0c0993e8
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/62270
Commit-Queue: Gourab Kundu <gourabk@microsoft.com>
Reviewed-by: Lei Zhang <thestig@chromium.org>
diff --git a/core/fpdfapi/render/BUILD.gn b/core/fpdfapi/render/BUILD.gn
index 51750ea..8fc9e53 100644
--- a/core/fpdfapi/render/BUILD.gn
+++ b/core/fpdfapi/render/BUILD.gn
@@ -76,5 +76,6 @@
     "fpdf_progressive_render_embeddertest.cpp",
     "fpdf_render_pattern_embeddertest.cpp",
   ]
+  deps = [ "../../fxge" ]
   pdfium_root_dir = "../../../"
 }
diff --git a/core/fpdfapi/render/cpdf_renderoptions.cpp b/core/fpdfapi/render/cpdf_renderoptions.cpp
index 11efe9e..80937de 100644
--- a/core/fpdfapi/render/cpdf_renderoptions.cpp
+++ b/core/fpdfapi/render/cpdf_renderoptions.cpp
@@ -42,6 +42,25 @@
   return ArgbEncode(a, gray, gray, gray);
 }
 
+FX_ARGB CPDF_RenderOptions::TranslateObjectColor(
+    FX_ARGB argb,
+    CPDF_PageObject::Type object_type,
+    RenderType render_type) const {
+  if (!ColorModeIs(kForcedColor))
+    return TranslateColor(argb);
+
+  switch (object_type) {
+    case CPDF_PageObject::Type::PATH:
+      return render_type == RenderType::kFill ? m_ColorScheme.path_fill_color
+                                              : m_ColorScheme.path_stroke_color;
+    case CPDF_PageObject::Type::TEXT:
+      return render_type == RenderType::kFill ? m_ColorScheme.text_fill_color
+                                              : m_ColorScheme.text_stroke_color;
+    default:
+      return argb;
+  }
+}
+
 uint32_t CPDF_RenderOptions::GetCacheSizeLimit() const {
   return kCacheSizeLimitBytes;
 }
diff --git a/core/fpdfapi/render/cpdf_renderoptions.h b/core/fpdfapi/render/cpdf_renderoptions.h
index 84f7e4c..322faae 100644
--- a/core/fpdfapi/render/cpdf_renderoptions.h
+++ b/core/fpdfapi/render/cpdf_renderoptions.h
@@ -8,13 +8,16 @@
 #define CORE_FPDFAPI_RENDER_CPDF_RENDEROPTIONS_H_
 
 #include "core/fpdfapi/page/cpdf_occontext.h"
+#include "core/fpdfapi/page/cpdf_pageobject.h"
 #include "core/fxcrt/fx_system.h"
 #include "core/fxcrt/retain_ptr.h"
 #include "core/fxge/fx_dib.h"
 
 class CPDF_RenderOptions {
  public:
-  enum Type : uint8_t { kNormal = 0, kGray, kAlpha };
+  enum Type : uint8_t { kNormal = 0, kGray, kAlpha, kForcedColor };
+
+  enum RenderType : uint8_t { kFill = 0, kStroke };
 
   struct Options {
     Options();
@@ -36,6 +39,14 @@
     bool bNoPathSmooth = false;
     bool bNoImageSmooth = false;
     bool bLimitedImageCache = false;
+    bool bConvertFillToStroke = false;
+  };
+
+  struct ColorScheme {
+    FX_ARGB path_fill_color;
+    FX_ARGB path_stroke_color;
+    FX_ARGB text_fill_color;
+    FX_ARGB text_stroke_color;
   };
 
   CPDF_RenderOptions();
@@ -43,6 +54,13 @@
   ~CPDF_RenderOptions();
 
   FX_ARGB TranslateColor(FX_ARGB argb) const;
+  FX_ARGB TranslateObjectColor(FX_ARGB argb,
+                               CPDF_PageObject::Type object_type,
+                               RenderType render_type) const;
+
+  void SetColorScheme(const ColorScheme& color_scheme) {
+    m_ColorScheme = color_scheme;
+  }
 
   void SetColorMode(Type mode) { m_ColorMode = mode; }
   bool ColorModeIs(Type mode) const { return m_ColorMode == mode; }
@@ -64,6 +82,7 @@
   Type m_ColorMode = kNormal;
   bool m_bDrawAnnots = false;
   Options m_Options;
+  ColorScheme m_ColorScheme;
   RetainPtr<CPDF_OCContext> m_pOCContext;
 };
 
diff --git a/core/fpdfapi/render/cpdf_renderstatus.cpp b/core/fpdfapi/render/cpdf_renderstatus.cpp
index 58ce613..2a1fae7 100644
--- a/core/fpdfapi/render/cpdf_renderstatus.cpp
+++ b/core/fpdfapi/render/cpdf_renderstatus.cpp
@@ -411,6 +411,14 @@
   if (FillType == 0 && !bStroke)
     return true;
 
+  // If the option to convert fill paths to stroke is enabled for forced color,
+  // set |FillType| to 0 and |bStroke| to true.
+  if (m_Options.ColorModeIs(CPDF_RenderOptions::Type::kForcedColor) &&
+      m_Options.GetOptions().bConvertFillToStroke && (FillType != 0)) {
+    bStroke = true;
+    FillType = 0;
+  }
+
   uint32_t fill_argb = FillType ? GetFillArgb(pPathObj) : 0;
   uint32_t stroke_argb = bStroke ? GetStrokeArgb(pPathObj) : 0;
   CFX_Matrix path_matrix = pPathObj->matrix() * mtObj2Device;
@@ -473,7 +481,9 @@
           pObj->m_GeneralState.GetTransferFunc()->TranslateColor(colorref);
     }
   }
-  return m_Options.TranslateColor(AlphaAndColorRefToArgb(alpha, colorref));
+  return m_Options.TranslateObjectColor(AlphaAndColorRefToArgb(alpha, colorref),
+                                        pObj->GetType(),
+                                        CPDF_RenderOptions::RenderType::kFill);
 }
 
 FX_ARGB CPDF_RenderStatus::GetStrokeArgb(CPDF_PageObject* pObj) const {
@@ -500,7 +510,9 @@
           pObj->m_GeneralState.GetTransferFunc()->TranslateColor(colorref);
     }
   }
-  return m_Options.TranslateColor(AlphaAndColorRefToArgb(alpha, colorref));
+  return m_Options.TranslateObjectColor(
+      AlphaAndColorRefToArgb(alpha, colorref), pObj->GetType(),
+      CPDF_RenderOptions::RenderType::kStroke);
 }
 
 void CPDF_RenderStatus::ProcessClipPath(const CPDF_ClipPath& ClipPath,
diff --git a/core/fpdfapi/render/fpdf_progressive_render_embeddertest.cpp b/core/fpdfapi/render/fpdf_progressive_render_embeddertest.cpp
index 955d699..7b27ddf 100644
--- a/core/fpdfapi/render/fpdf_progressive_render_embeddertest.cpp
+++ b/core/fpdfapi/render/fpdf_progressive_render_embeddertest.cpp
@@ -6,23 +6,64 @@
 
 #include "build/build_config.h"
 #include "core/fxcrt/fx_system.h"
+#include "core/fxge/fx_dib.h"
 #include "public/fpdf_progressive.h"
 #include "testing/embedder_test.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
+namespace {
+
+constexpr FX_ARGB kBlack = 0xFF000000;
+constexpr FX_ARGB kBlue = 0xFF0000FF;
+constexpr FX_ARGB kGreen = 0xFF00FF00;
+constexpr FX_ARGB kRed = 0xFFFF0000;
+constexpr FX_ARGB kWhite = 0xFFFFFFFF;
+
+}  // namespace
+
 class FPDFProgressiveRenderEmbedderTest : public EmbedderTest {
  public:
+  class FakePause : public IFSDK_PAUSE {
+   public:
+    explicit FakePause(bool should_pause) : should_pause_(should_pause) {
+      IFSDK_PAUSE::version = 1;
+      IFSDK_PAUSE::user = nullptr;
+      IFSDK_PAUSE::NeedToPauseNow = Pause_NeedToPauseNow;
+    }
+    ~FakePause() = default;
+    static FPDF_BOOL Pause_NeedToPauseNow(IFSDK_PAUSE* param) {
+      return static_cast<FakePause*>(param)->should_pause_;
+    }
+
+   private:
+    const bool should_pause_;
+  };
+
   // StartRenderPageWithFlags() with no flags.
   // The call returns true if the rendering is complete.
   bool StartRenderPage(FPDF_PAGE page, IFSDK_PAUSE* pause);
 
-  // Start rendering of |page| into a bitmap with the ability to pause the
+  // Start rendering of |page| into a bitmap with the ability to |pause| the
   // rendering with the specified rendering |flags|.
   // The call returns true if the rendering is complete.
   //
   // See public/fpdfview.h for a list of page rendering flags.
   bool StartRenderPageWithFlags(FPDF_PAGE page, IFSDK_PAUSE* pause, int flags);
 
+  // Start rendering of |page| into a bitmap with the ability to pause the
+  // rendering with the specified rendering |flags| and the specified
+  // |color_scheme|. This also takes in the |background_color| for the bitmap.
+  // The call returns true if the rendering is complete.
+  //
+  // See public/fpdfview.h for the list of page rendering flags and
+  // the list of colors in the scheme.
+  bool StartRenderPageWithColorSchemeAndBackground(
+      FPDF_PAGE page,
+      IFSDK_PAUSE* pause,
+      int flags,
+      const FPDF_COLORSCHEME* color_scheme,
+      uint32_t background_color);
+
   // Continue rendering of |page| into the bitmap created in
   // StartRenderPageWithFlags().
   // The call returns true if the rendering is complete.
@@ -40,6 +81,33 @@
   ScopedFPDFBitmap FinishRenderPageWithForms(FPDF_PAGE page,
                                              FPDF_FORMHANDLE handle);
 
+  // Convert the |page| into a bitmap with a |background_color|, using the
+  // color scheme render API with the specific |flags| and |color_scheme|.
+  // The form handle associated with |page| should be passed in via |handle|.
+  // If |handle| is nullptr, then forms on the page will not be rendered.
+  // This returns the bitmap generated by the progressive render calls.
+  //
+  // See public/fpdfview.h for a list of page rendering flags and
+  // the color scheme that can be applied for rendering.
+  ScopedFPDFBitmap RenderPageWithForcedColorScheme(
+      FPDF_PAGE page,
+      FPDF_FORMHANDLE handle,
+      int flags,
+      const FPDF_COLORSCHEME* color_scheme,
+      FX_ARGB background_color);
+
+ protected:
+  // Utility method to render the |page_num| of the currently loaded Pdf
+  // using RenderPageWithForcedColorScheme() passing in the render options
+  // and expected values for bitmap verification.
+  void VerifyRenderingWithColorScheme(int page_num,
+                                      int flags,
+                                      const FPDF_COLORSCHEME* color_scheme,
+                                      FX_ARGB background_color,
+                                      int bitmap_width,
+                                      int bitmap_height,
+                                      const char* md5);
+
  private:
   // Keeps the bitmap used for progressive rendering alive until
   // FPDF_RenderPage_Close() is called after which the bitmap is returned
@@ -72,6 +140,28 @@
   return rv != FPDF_RENDER_TOBECONTINUED;
 }
 
+bool FPDFProgressiveRenderEmbedderTest::
+    StartRenderPageWithColorSchemeAndBackground(
+        FPDF_PAGE page,
+        IFSDK_PAUSE* pause,
+        int flags,
+        const FPDF_COLORSCHEME* color_scheme,
+        uint32_t background_color) {
+  int width = static_cast<int>(FPDF_GetPageWidth(page));
+  int height = static_cast<int>(FPDF_GetPageHeight(page));
+  progressive_render_flags_ = flags;
+  int alpha = FPDFPage_HasTransparency(page) ? 1 : 0;
+  progressive_render_bitmap_ =
+      ScopedFPDFBitmap(FPDFBitmap_Create(width, height, alpha));
+  ASSERT(progressive_render_bitmap_);
+  FPDFBitmap_FillRect(progressive_render_bitmap_.get(), 0, 0, width, height,
+                      background_color);
+  int rv = FPDF_RenderPageBitmapWithColorScheme_Start(
+      progressive_render_bitmap_.get(), page, 0, 0, width, height, 0,
+      progressive_render_flags_, color_scheme, pause);
+  return rv != FPDF_RENDER_TOBECONTINUED;
+}
+
 bool FPDFProgressiveRenderEmbedderTest::ContinueRenderPage(FPDF_PAGE page,
                                                            IFSDK_PAUSE* pause) {
   ASSERT(progressive_render_bitmap_);
@@ -98,22 +188,24 @@
   return std::move(progressive_render_bitmap_);
 }
 
-class FakePause : public IFSDK_PAUSE {
- public:
-  explicit FakePause(bool should_pause) : should_pause_(should_pause) {
-    IFSDK_PAUSE::version = 1;
-    IFSDK_PAUSE::user = nullptr;
-    IFSDK_PAUSE::NeedToPauseNow = Pause_NeedToPauseNow;
-  }
-  ~FakePause() = default;
+ScopedFPDFBitmap
+FPDFProgressiveRenderEmbedderTest::RenderPageWithForcedColorScheme(
+    FPDF_PAGE page,
+    FPDF_FORMHANDLE handle,
+    int flags,
+    const FPDF_COLORSCHEME* color_scheme,
+    FX_ARGB background_color) {
+  FakePause pause(true);
+  bool render_done = StartRenderPageWithColorSchemeAndBackground(
+                         page, &pause, flags, color_scheme, background_color) ==
+                     FPDF_RENDER_TOBECONTINUED;
+  EXPECT_FALSE(render_done);
 
-  static FPDF_BOOL Pause_NeedToPauseNow(IFSDK_PAUSE* param) {
-    return static_cast<FakePause*>(param)->should_pause_;
+  while (!render_done) {
+    render_done = ContinueRenderPage(page, &pause);
   }
-
- private:
-  const bool should_pause_ = false;
-};
+  return FinishRenderPageWithForms(page, form_handle_);
+}
 
 // TODO(crbug.com/pdfium/11): Fix this test and enable.
 #if defined(_SKIA_SUPPORT_) || defined(_SKIA_SUPPORT_PATHS_)
@@ -243,3 +335,167 @@
   CompareBitmap(bitmap.get(), 300, 300, kMd5ContentWithForms);
   UnloadPage(page);
 }
+
+void FPDFProgressiveRenderEmbedderTest::VerifyRenderingWithColorScheme(
+    int page_num,
+    int flags,
+    const FPDF_COLORSCHEME* color_scheme,
+    FX_ARGB background_color,
+    int bitmap_width,
+    int bitmap_height,
+    const char* md5) {
+  ASSERT_TRUE(document_);
+
+  FPDF_PAGE page = LoadPage(page_num);
+  ASSERT_TRUE(page);
+
+  ScopedFPDFBitmap bitmap = RenderPageWithForcedColorScheme(
+      page, form_handle_, flags, color_scheme, background_color);
+  ASSERT_TRUE(bitmap);
+  CompareBitmap(bitmap.get(), bitmap_width, bitmap_height, md5);
+  UnloadPage(page);
+}
+
+TEST_F(FPDFProgressiveRenderEmbedderTest, RenderTextWithColorScheme) {
+// Test rendering of text with forced color scheme on.
+#if defined(OS_WIN)
+  static constexpr char kMD5ContentWithText[] =
+      "4245f32cc11748a00fd69852a5e5808d";
+#elif defined(OS_MACOSX)
+  static constexpr char kMD5ContentWithText[] =
+      "754a742f10ce0926b766dc3dd47d1f64";
+#else
+  static constexpr char kMD5ContentWithText[] =
+      "f14d3caba5a973a28be8653aac9e4df3";
+#endif
+
+  ASSERT_TRUE(OpenDocument("hello_world.pdf"));
+
+  FPDF_COLORSCHEME color_scheme{kBlack, kWhite, kWhite, kWhite};
+  VerifyRenderingWithColorScheme(/*page_num=*/0, /*flags=*/0, &color_scheme,
+                                 kBlack, 200, 200, kMD5ContentWithText);
+}
+
+TEST_F(FPDFProgressiveRenderEmbedderTest, RenderPathWithColorScheme) {
+  // Test rendering of paths with forced color scheme on.
+  static const char kMD5Rectangles[] = "249f59b0d066c4f6bd89782a80822219";
+
+  ASSERT_TRUE(OpenDocument("rectangles.pdf"));
+
+  FPDF_COLORSCHEME color_scheme{kWhite, kRed, kBlue, kBlue};
+  VerifyRenderingWithColorScheme(/*page_num=*/0, /*flags=*/0, &color_scheme,
+                                 kBlack, 200, 300, kMD5Rectangles);
+}
+
+TEST_F(FPDFProgressiveRenderEmbedderTest,
+       RenderPathWithColorSchemeAndConvertFillToStroke) {
+  // Test rendering of paths with forced color scheme on and conversion from
+  // fill to stroke enabled. The fill paths should be rendered as stroke.
+  static const char kMD5Rectangles[] = "0ebcc11e617635eca1fa9ce475383a80";
+
+  ASSERT_TRUE(OpenDocument("rectangles.pdf"));
+
+  FPDF_COLORSCHEME color_scheme{kWhite, kRed, kBlue, kBlue};
+  VerifyRenderingWithColorScheme(/*page_num=*/0, FPDF_CONVERT_FILL_TO_STROKE,
+                                 &color_scheme, kBlack, 200, 300,
+                                 kMD5Rectangles);
+}
+
+TEST_F(FPDFProgressiveRenderEmbedderTest, RenderHighlightWithColorScheme) {
+// Test rendering of highlight with forced color scheme on.
+//
+// Note: The fill color rendered for highlight is different from the normal
+// path since highlights have Multiply blend mode, while the other path has
+// Normal blend mode.
+#if defined(OS_MACOSX)
+  static constexpr char kMD5ContentWithHighlightFill[] =
+      "a820afec9b99d3d3f2e9e9382bbad7c1";
+#else  // OS_WIN & OS_LINUX
+  static constexpr char kMD5ContentWithHighlightFill[] =
+      "a08a0639f89446f66f3689ee8e08b9fe";
+#endif
+
+  ASSERT_TRUE(OpenDocument("annotation_highlight_square_with_ap.pdf"));
+
+  FPDF_COLORSCHEME color_scheme{kRed, kGreen, kWhite, kWhite};
+  VerifyRenderingWithColorScheme(/*page_num=*/0, FPDF_ANNOT, &color_scheme,
+                                 kBlue, 612, 792, kMD5ContentWithHighlightFill);
+}
+
+TEST_F(FPDFProgressiveRenderEmbedderTest,
+       RenderHighlightWithColorSchemeAndConvertFillToStroke) {
+// Test rendering of highlight with forced color and converting fill to
+// stroke. The highlight should be rendered as a stroke of the rect.
+//
+// Note: The stroke color rendered for highlight is different from the normal
+// path since highlights have Multiply blend mode, while the other path has
+// Normal blend mode.
+#if defined(OS_MACOSX)
+  static constexpr char kMD5ContentWithHighlight[] =
+      "8837bea0b3520164b1784e513c882a2d";
+#else  // OS_WIN & OS_LINUX
+  static constexpr char kMD5ContentWithHighlight[] =
+      "3dd8c02f5c06bac85e0d2c8bf37d1dc4";
+#endif
+
+  ASSERT_TRUE(OpenDocument("annotation_highlight_square_with_ap.pdf"));
+
+  FPDF_COLORSCHEME color_scheme{kRed, kGreen, kWhite, kWhite};
+  VerifyRenderingWithColorScheme(
+      /*page_num=*/0, FPDF_ANNOT | FPDF_CONVERT_FILL_TO_STROKE, &color_scheme,
+      kBlue, 612, 792, kMD5ContentWithHighlight);
+}
+
+TEST_F(FPDFProgressiveRenderEmbedderTest, RenderInkWithColorScheme) {
+// Test rendering of multiple ink with forced color scheme on.
+#if defined(OS_WIN)
+  static constexpr char kMD5ContentWithInk[] =
+      "542e5dc877a9ffdc4101ee3dc391c3b1";
+#else  // OS_MACOSX & OS_LINUX
+  static constexpr char kMD5ContentWithInk[] =
+      "797bce7dc6c50ee86b095405df9fe5aa";
+#endif
+
+  ASSERT_TRUE(OpenDocument("annotation_ink_multiple.pdf"));
+
+  FPDF_COLORSCHEME color_scheme{kBlack, kGreen, kRed, kRed};
+  VerifyRenderingWithColorScheme(/*page_num=*/0, FPDF_ANNOT, &color_scheme,
+                                 kBlack, 612, 792, kMD5ContentWithInk);
+}
+
+TEST_F(FPDFProgressiveRenderEmbedderTest, RenderStampWithColorScheme) {
+// Test rendering of static annotation with forced color scheme on.
+#if defined(OS_WIN)
+  static constexpr char kMD5ContentWithStamp[] =
+      "71dce8f1221e1d2fe59d74258c3afd54";
+#elif defined(OS_MACOSX)
+  static constexpr char kMD5ContentWithStamp[] =
+      "e2d9bef817d366021e5727d9350bde43";
+#else
+  static constexpr char kMD5ContentWithStamp[] =
+      "d5518b1d9765fa62897a24d12244080f";
+#endif
+
+  ASSERT_TRUE(OpenDocument("annotation_stamp_with_ap.pdf"));
+
+  FPDF_COLORSCHEME color_scheme{kBlue, kGreen, kRed, kRed};
+  VerifyRenderingWithColorScheme(/*page_num=*/0, FPDF_ANNOT, &color_scheme,
+                                 kWhite, 595, 842, kMD5ContentWithStamp);
+}
+
+TEST_F(FPDFProgressiveRenderEmbedderTest, RenderFormWithColorScheme) {
+  // Test rendering of form does not change with forced color scheme on.
+  static constexpr char kMD5ContentWithForm[] =
+      "080f7a4381606659301440e1b14dca35";
+
+  ASSERT_TRUE(OpenDocument("annotiter.pdf"));
+
+  FPDF_COLORSCHEME color_scheme{kGreen, kGreen, kRed, kRed};
+  VerifyRenderingWithColorScheme(/*page_num=*/0, FPDF_ANNOT, &color_scheme,
+                                 kWhite, 612, 792, kMD5ContentWithForm);
+
+  // Verify that the MD5 hash matches when rendered without |color_scheme|.
+  VerifyRenderingWithColorScheme(/*page_num=*/0, FPDF_ANNOT,
+                                 /*color_scheme=*/nullptr, kWhite, 612, 792,
+                                 kMD5ContentWithForm);
+}
diff --git a/fpdfsdk/cpdfsdk_helpers.cpp b/fpdfsdk/cpdfsdk_helpers.cpp
index 2d3a95b..33d27ad 100644
--- a/fpdfsdk/cpdfsdk_helpers.cpp
+++ b/fpdfsdk/cpdfsdk_helpers.cpp
@@ -14,6 +14,7 @@
 #include "core/fpdfapi/parser/cpdf_dictionary.h"
 #include "core/fpdfapi/parser/cpdf_document.h"
 #include "core/fpdfapi/parser/cpdf_stream_acc.h"
+#include "core/fpdfapi/render/cpdf_renderoptions.h"
 #include "core/fpdfdoc/cpdf_annot.h"
 #include "core/fpdfdoc/cpdf_interactiveform.h"
 #include "core/fpdfdoc/cpdf_metadata.h"
@@ -440,3 +441,13 @@
   }
   FXSYS_SetLastError(err_code);
 }
+
+void SetColorFromScheme(const FPDF_COLORSCHEME* pColorScheme,
+                        CPDF_RenderOptions* pRenderOptions) {
+  CPDF_RenderOptions::ColorScheme color_scheme;
+  color_scheme.path_fill_color = pColorScheme->path_fill_color;
+  color_scheme.path_stroke_color = pColorScheme->path_stroke_color;
+  color_scheme.text_fill_color = pColorScheme->text_fill_color;
+  color_scheme.text_stroke_color = pColorScheme->text_stroke_color;
+  pRenderOptions->SetColorScheme(color_scheme);
+}
diff --git a/fpdfsdk/cpdfsdk_helpers.h b/fpdfsdk/cpdfsdk_helpers.h
index 74ae582..73ca946 100644
--- a/fpdfsdk/cpdfsdk_helpers.h
+++ b/fpdfsdk/cpdfsdk_helpers.h
@@ -32,6 +32,7 @@
 class CPDF_Font;
 class CPDF_LinkExtract;
 class CPDF_PageObject;
+class CPDF_RenderOptions;
 class CPDF_Stream;
 class CPDF_StructElement;
 class CPDF_StructTree;
@@ -270,5 +271,7 @@
 void ReportUnsupportedXFA(const CPDF_Document* pDoc);
 void CheckForUnsupportedAnnot(const CPDF_Annot* pAnnot);
 void ProcessParseError(CPDF_Parser::Error err);
+void SetColorFromScheme(const FPDF_COLORSCHEME* pColorScheme,
+                        CPDF_RenderOptions* pRenderOptions);
 
 #endif  // FPDFSDK_CPDFSDK_HELPERS_H_
diff --git a/fpdfsdk/cpdfsdk_renderpage.cpp b/fpdfsdk/cpdfsdk_renderpage.cpp
index ce492e6..ae2fdfa 100644
--- a/fpdfsdk/cpdfsdk_renderpage.cpp
+++ b/fpdfsdk/cpdfsdk_renderpage.cpp
@@ -14,8 +14,8 @@
 #include "core/fpdfapi/render/cpdf_renderoptions.h"
 #include "core/fpdfdoc/cpdf_annotlist.h"
 #include "core/fxge/cfx_renderdevice.h"
+#include "fpdfsdk/cpdfsdk_helpers.h"
 #include "fpdfsdk/cpdfsdk_pauseadapter.h"
-#include "public/fpdfview.h"
 #include "third_party/base/ptr_util.h"
 
 namespace {
@@ -25,6 +25,7 @@
                     const CFX_Matrix& matrix,
                     const FX_RECT& clipping_rect,
                     int flags,
+                    const FPDF_COLORSCHEME* color_scheme,
                     bool need_to_restore,
                     CPDFSDK_PauseAdapter* pause) {
   if (!pContext->m_pOptions)
@@ -43,6 +44,12 @@
   if (flags & FPDF_GRAYSCALE)
     pContext->m_pOptions->SetColorMode(CPDF_RenderOptions::kGray);
 
+  if (color_scheme) {
+    pContext->m_pOptions->SetColorMode(CPDF_RenderOptions::kForcedColor);
+    SetColorFromScheme(color_scheme, pContext->m_pOptions.get());
+    options.bConvertFillToStroke = !!(flags & FPDF_CONVERT_FILL_TO_STROKE);
+  }
+
   const CPDF_OCContext::UsageType usage =
       (flags & FPDF_PRINTING) ? CPDF_OCContext::Print : CPDF_OCContext::View;
   pContext->m_pOptions->SetOCContext(
@@ -81,8 +88,9 @@
                         CPDF_Page* pPage,
                         const CFX_Matrix& matrix,
                         const FX_RECT& clipping_rect,
-                        int flags) {
-  RenderPageImpl(pContext, pPage, matrix, clipping_rect, flags,
+                        int flags,
+                        const FPDF_COLORSCHEME* color_scheme) {
+  RenderPageImpl(pContext, pPage, matrix, clipping_rect, flags, color_scheme,
                  /*need_to_restore=*/true, /*pause=*/nullptr);
 }
 
@@ -94,9 +102,10 @@
                                    int size_y,
                                    int rotate,
                                    int flags,
+                                   const FPDF_COLORSCHEME* color_scheme,
                                    bool need_to_restore,
                                    CPDFSDK_PauseAdapter* pause) {
   const FX_RECT rect(start_x, start_y, start_x + size_x, start_y + size_y);
   RenderPageImpl(pContext, pPage, pPage->GetDisplayMatrix(rect, rotate), rect,
-                 flags, need_to_restore, pause);
+                 flags, color_scheme, need_to_restore, pause);
 }
diff --git a/fpdfsdk/cpdfsdk_renderpage.h b/fpdfsdk/cpdfsdk_renderpage.h
index cb7a600..0f9cdec 100644
--- a/fpdfsdk/cpdfsdk_renderpage.h
+++ b/fpdfsdk/cpdfsdk_renderpage.h
@@ -7,6 +7,8 @@
 #ifndef FPDFSDK_CPDFSDK_RENDERPAGE_H_
 #define FPDFSDK_CPDFSDK_RENDERPAGE_H_
 
+#include "public/fpdfview.h"
+
 class CFX_Matrix;
 class CPDFSDK_PauseAdapter;
 class CPDF_Page;
@@ -17,7 +19,8 @@
                         CPDF_Page* pPage,
                         const CFX_Matrix& matrix,
                         const FX_RECT& clipping_rect,
-                        int flags);
+                        int flags,
+                        const FPDF_COLORSCHEME* color_scheme);
 
 // TODO(thestig): Consider giving this a better name, and make its parameters
 // more similar to those of CPDFSDK_RenderPage().
@@ -29,6 +32,7 @@
                                    int size_y,
                                    int rotate,
                                    int flags,
+                                   const FPDF_COLORSCHEME* color_scheme,
                                    bool need_to_restore,
                                    CPDFSDK_PauseAdapter* pause);
 
diff --git a/fpdfsdk/fpdf_progressive.cpp b/fpdfsdk/fpdf_progressive.cpp
index 99a9230..7221b27 100644
--- a/fpdfsdk/fpdf_progressive.cpp
+++ b/fpdfsdk/fpdf_progressive.cpp
@@ -41,15 +41,17 @@
 
 }  // namespace
 
-FPDF_EXPORT int FPDF_CALLCONV FPDF_RenderPageBitmap_Start(FPDF_BITMAP bitmap,
-                                                          FPDF_PAGE page,
-                                                          int start_x,
-                                                          int start_y,
-                                                          int size_x,
-                                                          int size_y,
-                                                          int rotate,
-                                                          int flags,
-                                                          IFSDK_PAUSE* pause) {
+FPDF_EXPORT int FPDF_CALLCONV
+FPDF_RenderPageBitmapWithColorScheme_Start(FPDF_BITMAP bitmap,
+                                           FPDF_PAGE page,
+                                           int start_x,
+                                           int start_y,
+                                           int size_x,
+                                           int size_y,
+                                           int rotate,
+                                           int flags,
+                                           const FPDF_COLORSCHEME* color_scheme,
+                                           IFSDK_PAUSE* pause) {
   if (!bitmap || !pause || pause->version != 1)
     return FPDF_RENDER_FAILED;
 
@@ -69,7 +71,7 @@
 
   CPDFSDK_PauseAdapter pause_adapter(pause);
   CPDFSDK_RenderPageWithContext(pContext, pPage, start_x, start_y, size_x,
-                                size_y, rotate, flags,
+                                size_y, rotate, flags, color_scheme,
                                 /*need_to_restore=*/false, &pause_adapter);
 
 #ifdef _SKIA_SUPPORT_PATHS_
@@ -83,6 +85,20 @@
   return ToFPDFStatus(pContext->m_pRenderer->GetStatus());
 }
 
+FPDF_EXPORT int FPDF_CALLCONV FPDF_RenderPageBitmap_Start(FPDF_BITMAP bitmap,
+                                                          FPDF_PAGE page,
+                                                          int start_x,
+                                                          int start_y,
+                                                          int size_x,
+                                                          int size_y,
+                                                          int rotate,
+                                                          int flags,
+                                                          IFSDK_PAUSE* pause) {
+  return FPDF_RenderPageBitmapWithColorScheme_Start(
+      bitmap, page, start_x, start_y, size_x, size_y, rotate, flags,
+      /*color_scheme=*/nullptr, pause);
+}
+
 FPDF_EXPORT int FPDF_CALLCONV FPDF_RenderPage_Continue(FPDF_PAGE page,
                                                        IFSDK_PAUSE* pause) {
   if (!pause || pause->version != 1)
diff --git a/fpdfsdk/fpdf_view.cpp b/fpdfsdk/fpdf_view.cpp
index 9bfd15d..613740b 100644
--- a/fpdfsdk/fpdf_view.cpp
+++ b/fpdfsdk/fpdf_view.cpp
@@ -507,6 +507,7 @@
     pContext->m_pDevice = pdfium::MakeUnique<CPDF_WindowsRenderDevice>(dc);
     CPDFSDK_RenderPageWithContext(pContext, pPage, start_x, start_y, size_x,
                                   size_y, rotate, flags,
+                                  /*color_scheme=*/nullptr,
                                   /*need_to_restore=*/true, /*pause=*/nullptr);
     return;
   }
@@ -525,7 +526,8 @@
   }
 
   CPDFSDK_RenderPageWithContext(pContext, pPage, start_x, start_y, size_x,
-                                size_y, rotate, flags, /*need_to_restore=*/true,
+                                size_y, rotate, flags, /*color_scheme=*/nullptr,
+                                /*need_to_restore=*/true,
                                 /*pause=*/nullptr);
 
   if (!bHasMask) {
@@ -567,7 +569,8 @@
   pContext->m_pOptions->GetOptions().bBreakForMasks = true;
 
   CPDFSDK_RenderPageWithContext(pContext, pPage, start_x, start_y, size_x,
-                                size_y, rotate, flags, /*need_to_restore=*/true,
+                                size_y, rotate, flags, /*color_scheme=*/nullptr,
+                                /*need_to_restore=*/true,
                                 /*pause=*/nullptr);
 
   // Render masks
@@ -609,7 +612,8 @@
   RetainPtr<CFX_DIBitmap> pBitmap(CFXDIBitmapFromFPDFBitmap(bitmap));
   pDevice->Attach(pBitmap, !!(flags & FPDF_REVERSE_BYTE_ORDER), nullptr, false);
   CPDFSDK_RenderPageWithContext(pContext, pPage, start_x, start_y, size_x,
-                                size_y, rotate, flags, /*need_to_restore=*/true,
+                                size_y, rotate, flags, /*color_scheme=*/nullptr,
+                                /*need_to_restore=*/true,
                                 /*pause=*/nullptr);
 
 #ifdef _SKIA_SUPPORT_PATHS_
@@ -652,7 +656,8 @@
   CFX_Matrix transform_matrix = pPage->GetDisplayMatrix(rect, 0);
   if (matrix)
     transform_matrix *= CFXMatrixFromFSMatrix(*matrix);
-  CPDFSDK_RenderPage(pContext, pPage, transform_matrix, clip_rect, flags);
+  CPDFSDK_RenderPage(pContext, pPage, transform_matrix, clip_rect, flags,
+                     /*color_scheme=*/nullptr);
 }
 
 #ifdef _SKIA_SUPPORT_
@@ -673,7 +678,9 @@
   pContext->m_pDevice = std::move(skDevice);
 
   CPDFSDK_RenderPageWithContext(pContext, pPage, 0, 0, size_x, size_y, 0, 0,
+                                /*color_scheme=*/nullptr,
                                 /*need_to_restore=*/true, /*pause=*/nullptr);
+
   return recorder;
 }
 #endif  // _SKIA_SUPPORT_
diff --git a/fpdfsdk/fpdf_view_c_api_test.c b/fpdfsdk/fpdf_view_c_api_test.c
index 26b433f..6d9d308 100644
--- a/fpdfsdk/fpdf_view_c_api_test.c
+++ b/fpdfsdk/fpdf_view_c_api_test.c
@@ -294,6 +294,7 @@
     CHK(FPDF_ImportPages);
 
     // fpdf_progressive.h
+    CHK(FPDF_RenderPageBitmapWithColorScheme_Start);
     CHK(FPDF_RenderPageBitmap_Start);
     CHK(FPDF_RenderPage_Close);
     CHK(FPDF_RenderPage_Continue);
diff --git a/public/fpdf_progressive.h b/public/fpdf_progressive.h
index 99803b8..7edafb2 100644
--- a/public/fpdf_progressive.h
+++ b/public/fpdf_progressive.h
@@ -46,6 +46,51 @@
   void* user;
 } IFSDK_PAUSE;
 
+// Experimental API.
+// Function: FPDF_RenderPageBitmapWithColorScheme_Start
+//          Start to render page contents to a device independent bitmap
+//          progressively with a specified color scheme for the content.
+// Parameters:
+//          bitmap       -   Handle to the device independent bitmap (as the
+//                           output buffer). Bitmap handle can be created by
+//                           FPDFBitmap_Create function.
+//          page         -   Handle to the page as returned by FPDF_LoadPage
+//                           function.
+//          start_x      -   Left pixel position of the display area in the
+//                           bitmap coordinate.
+//          start_y      -   Top pixel position of the display area in the
+//                           bitmap coordinate.
+//          size_x       -   Horizontal size (in pixels) for displaying the
+//                           page.
+//          size_y       -   Vertical size (in pixels) for displaying the page.
+//          rotate       -   Page orientation: 0 (normal), 1 (rotated 90
+//                           degrees clockwise), 2 (rotated 180 degrees),
+//                           3 (rotated 90 degrees counter-clockwise).
+//          flags        -   0 for normal display, or combination of flags
+//                           defined in fpdfview.h. With FPDF_ANNOT flag, it
+//                           renders all annotations that does not require
+//                           user-interaction, which are all annotations except
+//                           widget and popup annotations.
+//          color_scheme -   Color scheme to be used in rendering the |page|.
+//                           If null, this function will work similar to
+//                           FPDF_RenderPageBitmap_Start().
+//          pause        -   The IFSDK_PAUSE interface. A callback mechanism
+//                           allowing the page rendering process.
+// Return value:
+//          Rendering Status. See flags for progressive process status for the
+//          details.
+FPDF_EXPORT int FPDF_CALLCONV
+FPDF_RenderPageBitmapWithColorScheme_Start(FPDF_BITMAP bitmap,
+                                           FPDF_PAGE page,
+                                           int start_x,
+                                           int start_y,
+                                           int size_x,
+                                           int size_y,
+                                           int rotate,
+                                           int flags,
+                                           const FPDF_COLORSCHEME* color_scheme,
+                                           IFSDK_PAUSE* pause);
+
 // Function: FPDF_RenderPageBitmap_Start
 //          Start to render page contents to a device independent bitmap
 //          progressively.
@@ -73,7 +118,6 @@
 // Return value:
 //          Rendering Status. See flags for progressive process status for the
 //          details.
-//
 FPDF_EXPORT int FPDF_CALLCONV FPDF_RenderPageBitmap_Start(FPDF_BITMAP bitmap,
                                                           FPDF_PAGE page,
                                                           int start_x,
diff --git a/public/fpdfview.h b/public/fpdfview.h
index 96a3e0f..1e67bcb 100644
--- a/public/fpdfview.h
+++ b/public/fpdfview.h
@@ -759,6 +759,19 @@
 // Set whether to render in a reverse Byte order, this flag is only used when
 // rendering to a bitmap.
 #define FPDF_REVERSE_BYTE_ORDER 0x10
+// Set whether fill paths need to be stroked. This flag is only used when
+// FPDF_COLORSCHEME is passed in, since with a single fill color for paths the
+// boundaries of adjacent fill paths are less visible.
+#define FPDF_CONVERT_FILL_TO_STROKE 0x20
+
+// Struct for color scheme.
+// Each should be a 32-bit value specifying the color, in 8888 ARGB format.
+typedef struct FPDF_COLORSCHEME_ {
+  FPDF_DWORD path_fill_color;
+  FPDF_DWORD path_stroke_color;
+  FPDF_DWORD text_fill_color;
+  FPDF_DWORD text_stroke_color;
+} FPDF_COLORSCHEME;
 
 #ifdef _WIN32
 // Function: FPDF_RenderPage
diff --git a/samples/pdfium_test.cc b/samples/pdfium_test.cc
index 4f0ab7e..d7f211c 100644
--- a/samples/pdfium_test.cc
+++ b/samples/pdfium_test.cc
@@ -108,6 +108,8 @@
   bool lcd_text = false;
   bool no_nativetext = false;
   bool grayscale = false;
+  bool forced_color = false;
+  bool fill_to_stroke = false;
   bool limit_cache = false;
   bool force_halftone = false;
   bool printing = false;
@@ -153,6 +155,8 @@
     flags |= FPDF_NO_NATIVETEXT;
   if (options.grayscale)
     flags |= FPDF_GRAYSCALE;
+  if (options.fill_to_stroke)
+    flags |= FPDF_CONVERT_FILL_TO_STROKE;
   if (options.limit_cache)
     flags |= FPDF_RENDER_LIMITEDIMAGECACHE;
   if (options.force_halftone)
@@ -422,6 +426,10 @@
       options->no_nativetext = true;
     } else if (cur_arg == "--grayscale") {
       options->grayscale = true;
+    } else if (cur_arg == "--forced-color") {
+      options->forced_color = true;
+    } else if (cur_arg == "--fill-to-stroke") {
+      options->fill_to_stroke = true;
     } else if (cur_arg == "--limit-cache") {
       options->limit_cache = true;
     } else if (cur_arg == "--force-halftone") {
@@ -747,8 +755,16 @@
       pause.version = 1;
       pause.NeedToPauseNow = &NeedToPauseNow;
 
-      int rv = FPDF_RenderPageBitmap_Start(bitmap.get(), page, 0, 0, width,
-                                           height, 0, flags, &pause);
+      // Client programs will be setting these values when rendering.
+      // This is a sample color scheme with distinct colors.
+      // Used only when |options.forced_color| is true.
+      const FPDF_COLORSCHEME color_scheme{
+          /*path_fill_color=*/0xFFFF0000, /*path_stroke_color=*/0xFF00FF00,
+          /*text_fill_color=*/0xFF0000FF, /*text_stroke_color=*/0xFF00FFFF};
+
+      int rv = FPDF_RenderPageBitmapWithColorScheme_Start(
+          bitmap.get(), page, 0, 0, width, height, 0, flags,
+          options.forced_color ? &color_scheme : nullptr, &pause);
       while (rv == FPDF_RENDER_TOBECONTINUED)
         rv = FPDF_RenderPage_Continue(page, &pause);
     }
@@ -1032,6 +1048,8 @@
     "  --lcd-text           - render text optimized for LCD displays\n"
     "  --no-nativetext      - render without using the native text output\n"
     "  --grayscale          - render grayscale output\n"
+    "  --forced-color       - render in forced color mode\n"
+    "  --fill-to-stroke     - render fill as stroke in forced color mode\n"
     "  --limit-cache        - render limiting image cache size\n"
     "  --force-halftone     - render forcing halftone\n"
     "  --printing           - render as if for printing\n"