Add support for fill and stroke RGBA in CPDF_PageContentGenerator

CPDF_PageObject is a CPDF_GraphicStates, which allows us to add graphics
information to the object itself. The RGB can be added easily in the stream.
The alpha parameter needs to be in a dictionary contained in the ExtGState,
which should be part of the page's resources.

BUG=pdfium:661

Change-Id: Id99b2ece7aa201e3550c765ac2b5eeff4b43fc48
Reviewed-on: https://pdfium-review.googlesource.com/2530
Reviewed-by: dsinclair <dsinclair@chromium.org>
Reviewed-by: Tom Sepez <tsepez@chromium.org>
Commit-Queue: Nicolás Peña <npm@chromium.org>
diff --git a/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp b/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp
index e2354e1..9dba6e0 100644
--- a/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp
+++ b/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp
@@ -6,6 +6,9 @@
 
 #include "core/fpdfapi/edit/cpdf_pagecontentgenerator.h"
 
+#include <tuple>
+#include <utility>
+
 #include "core/fpdfapi/page/cpdf_docpagedata.h"
 #include "core/fpdfapi/page/cpdf_image.h"
 #include "core/fpdfapi/page/cpdf_imageobject.h"
@@ -15,6 +18,7 @@
 #include "core/fpdfapi/parser/cpdf_dictionary.h"
 #include "core/fpdfapi/parser/cpdf_document.h"
 #include "core/fpdfapi/parser/cpdf_name.h"
+#include "core/fpdfapi/parser/cpdf_number.h"
 #include "core/fpdfapi/parser/cpdf_reference.h"
 #include "core/fpdfapi/parser/cpdf_stream.h"
 #include "core/fpdfapi/parser/fpdf_parser_decode.h"
@@ -27,6 +31,19 @@
   return ar;
 }
 
+bool GetColor(const CPDF_Color* pColor, FX_FLOAT* rgb) {
+  int intRGB[3];
+  if (!pColor ||
+      pColor->GetColorSpace() != CPDF_ColorSpace::GetStockCS(PDFCS_DEVICERGB) ||
+      !pColor->GetRGB(intRGB[0], intRGB[1], intRGB[2])) {
+    return false;
+  }
+  rgb[0] = intRGB[0] / 255.0f;
+  rgb[1] = intRGB[1] / 255.0f;
+  rgb[2] = intRGB[2] / 255.0f;
+  return true;
+}
+
 }  // namespace
 
 CPDF_PageContentGenerator::CPDF_PageContentGenerator(CPDF_Page* pPage)
@@ -123,8 +140,10 @@
 // "h" closes the subpath (appends a line from current to starting point)
 // Path painting operators: "S", "n", "B", "f", "B*", "f*", depending on
 // the filling mode and whether we want stroking the path or not.
+// "Q" restores the graphics state imposed by the ProcessGraphics method.
 void CPDF_PageContentGenerator::ProcessPath(CFX_ByteTextBuf* buf,
                                             CPDF_PathObject* pPathObj) {
+  ProcessGraphics(buf, pPathObj);
   const FX_PATHPOINT* pPoints = pPathObj->m_Path.GetPoints();
   if (pPathObj->m_Path.IsRect()) {
     *buf << pPoints[0].m_PointX << " " << pPoints[0].m_PointY << " "
@@ -166,5 +185,55 @@
     *buf << (pPathObj->m_bStroke ? " B" : " f");
   else if (pPathObj->m_FillType == FXFILL_ALTERNATE)
     *buf << (pPathObj->m_bStroke ? " B*" : " f*");
-  *buf << "\n";
+  *buf << " Q\n";
+}
+
+// This method supports color operators rg and RGB from Table 4.24 of PDF spec
+// 1.7. A color will not be set if the colorspace is not DefaultRGB or the RGB
+// values cannot be obtained. The method also adds an external graphics
+// dictionary, as described in Section 4.3.4.
+// "rg" sets the fill color, "RG" sets the stroke color (using DefaultRGB)
+// "ca" sets the fill alpha, "CA" sets the stroke alpha.
+// "q" saves the graphics state, so that the settings can later be reversed
+void CPDF_PageContentGenerator::ProcessGraphics(CFX_ByteTextBuf* buf,
+                                                CPDF_PageObject* pPageObj) {
+  *buf << "q ";
+  FX_FLOAT fillColor[3];
+  if (GetColor(pPageObj->m_ColorState.GetFillColor(), fillColor)) {
+    *buf << fillColor[0] << " " << fillColor[1] << " " << fillColor[2]
+         << " rg ";
+  }
+  FX_FLOAT strokeColor[3];
+  if (GetColor(pPageObj->m_ColorState.GetStrokeColor(), strokeColor)) {
+    *buf << strokeColor[0] << " " << strokeColor[1] << " " << strokeColor[2]
+         << " RG ";
+  }
+
+  GraphicsData graphD;
+  graphD.fillAlpha = pPageObj->m_GeneralState.GetFillAlpha();
+  graphD.strokeAlpha = pPageObj->m_GeneralState.GetStrokeAlpha();
+  if (graphD.fillAlpha == 1.0f && graphD.strokeAlpha == 1.0f)
+    return;
+
+  CFX_ByteString name;
+  auto it = m_GraphicsMap.find(graphD);
+  if (it != m_GraphicsMap.end()) {
+    name = it->second;
+  } else {
+    auto gsDict = pdfium::MakeUnique<CPDF_Dictionary>();
+    gsDict->SetNewFor<CPDF_Number>("ca", graphD.fillAlpha);
+    gsDict->SetNewFor<CPDF_Number>("CA", graphD.strokeAlpha);
+    CPDF_Object* pDict = m_pDocument->AddIndirectObject(std::move(gsDict));
+    uint32_t dwObjNum = pDict->GetObjNum();
+    name = RealizeResource(dwObjNum, "ExtGState");
+    m_GraphicsMap[graphD] = name;
+  }
+  *buf << "/" << PDF_NameEncode(name) << " gs ";
+}
+
+bool CPDF_PageContentGenerator::GraphicsData::operator<(
+    const GraphicsData& other) const {
+  if (fillAlpha != other.fillAlpha)
+    return fillAlpha < other.fillAlpha;
+  return strokeAlpha < other.strokeAlpha;
 }
diff --git a/core/fpdfapi/edit/cpdf_pagecontentgenerator.h b/core/fpdfapi/edit/cpdf_pagecontentgenerator.h
index c74652e..e48ea4a 100644
--- a/core/fpdfapi/edit/cpdf_pagecontentgenerator.h
+++ b/core/fpdfapi/edit/cpdf_pagecontentgenerator.h
@@ -7,6 +7,7 @@
 #ifndef CORE_FPDFAPI_EDIT_CPDF_PAGECONTENTGENERATOR_H_
 #define CORE_FPDFAPI_EDIT_CPDF_PAGECONTENTGENERATOR_H_
 
+#include <map>
 #include <vector>
 
 #include "core/fxcrt/fx_basic.h"
@@ -26,14 +27,21 @@
   void GenerateContent();
 
  private:
-  friend class cpdf_pagecontentgenerator_ProcessRect_Test;
-  friend class cpdf_pagecontentgenerator_ProcessPath_Test;
+  friend class CPDF_PageContentGeneratorTest;
 
   void ProcessPath(CFX_ByteTextBuf* buf, CPDF_PathObject* pPathObj);
   void ProcessImage(CFX_ByteTextBuf* buf, CPDF_ImageObject* pImageObj);
+  void ProcessGraphics(CFX_ByteTextBuf* buf, CPDF_PageObject* pPageObj);
   CFX_ByteString RealizeResource(uint32_t dwResourceObjNum,
                                  const CFX_ByteString& bsType);
 
+  struct GraphicsData {
+    FX_FLOAT fillAlpha;
+    FX_FLOAT strokeAlpha;
+    bool operator<(const GraphicsData& other) const;
+  };
+
+  std::map<GraphicsData, CFX_ByteString> m_GraphicsMap;
   CPDF_Page* const m_pPage;
   CPDF_Document* const m_pDocument;
   std::vector<CPDF_PageObject*> m_pageObjects;
diff --git a/core/fpdfapi/edit/cpdf_pagecontentgenerator_unittest.cpp b/core/fpdfapi/edit/cpdf_pagecontentgenerator_unittest.cpp
index 8812c0e..41e61b3 100644
--- a/core/fpdfapi/edit/cpdf_pagecontentgenerator_unittest.cpp
+++ b/core/fpdfapi/edit/cpdf_pagecontentgenerator_unittest.cpp
@@ -4,12 +4,34 @@
 
 #include "core/fpdfapi/edit/cpdf_pagecontentgenerator.h"
 
+#include "core/fpdfapi/cpdf_modulemgr.h"
 #include "core/fpdfapi/page/cpdf_page.h"
 #include "core/fpdfapi/page/cpdf_pathobject.h"
+#include "core/fpdfapi/parser/cpdf_document.h"
+#include "core/fpdfapi/parser/cpdf_parser.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "third_party/base/ptr_util.h"
 
-TEST(cpdf_pagecontentgenerator, ProcessRect) {
+class CPDF_PageContentGeneratorTest : public testing::Test {
+ protected:
+  void SetUp() override { CPDF_ModuleMgr::Get()->InitPageModule(); }
+
+  void TearDown() override { CPDF_ModuleMgr::Destroy(); }
+
+  void TestProcessPath(CPDF_PageContentGenerator* pGen,
+                       CFX_ByteTextBuf* buf,
+                       CPDF_PathObject* pPathObj) {
+    pGen->ProcessPath(buf, pPathObj);
+  }
+
+  CPDF_Dictionary* TestGetGS(CPDF_PageContentGenerator* pGen,
+                             const CFX_ByteString& name) {
+    return pGen->m_pPage->m_pResources->GetDictFor("ExtGState")
+        ->GetDictFor(name);
+  }
+};
+
+TEST_F(CPDF_PageContentGeneratorTest, ProcessRect) {
   auto pPathObj = pdfium::MakeUnique<CPDF_PathObject>();
   pPathObj->m_Path.AppendRect(10, 5, 13, 30);
   pPathObj->m_FillType = FXFILL_ALTERNATE;
@@ -17,8 +39,8 @@
   auto pTestPage = pdfium::MakeUnique<CPDF_Page>(nullptr, nullptr, false);
   CPDF_PageContentGenerator generator(pTestPage.get());
   CFX_ByteTextBuf buf;
-  generator.ProcessPath(&buf, pPathObj.get());
-  EXPECT_EQ("10 5 3 25 re B*\n", buf.MakeString());
+  TestProcessPath(&generator, &buf, pPathObj.get());
+  EXPECT_EQ("q 10 5 3 25 re B* Q\n", buf.MakeString());
 
   pPathObj = pdfium::MakeUnique<CPDF_PathObject>();
   pPathObj->m_Path.SetPointCount(4);
@@ -38,11 +60,11 @@
   pPathObj->m_FillType = 0;
   pPathObj->m_bStroke = false;
   buf.Clear();
-  generator.ProcessPath(&buf, pPathObj.get());
-  EXPECT_EQ("0 0 5.2 3.78 re n\n", buf.MakeString());
+  TestProcessPath(&generator, &buf, pPathObj.get());
+  EXPECT_EQ("q 0 0 5.2 3.78 re n Q\n", buf.MakeString());
 }
 
-TEST(cpdf_pagecontentgenerator, ProcessPath) {
+TEST_F(CPDF_PageContentGeneratorTest, ProcessPath) {
   auto pPathObj = pdfium::MakeUnique<CPDF_PathObject>();
   pPathObj->m_Path.SetPointCount(10);
   FX_PATHPOINT* pPoints = pPathObj->m_Path.GetMutablePoints();
@@ -81,9 +103,51 @@
   auto pTestPage = pdfium::MakeUnique<CPDF_Page>(nullptr, nullptr, false);
   CPDF_PageContentGenerator generator(pTestPage.get());
   CFX_ByteTextBuf buf;
-  generator.ProcessPath(&buf, pPathObj.get());
+  TestProcessPath(&generator, &buf, pPathObj.get());
   EXPECT_EQ(
-      "3.102 4.67 m 5.45 0.29 l 4.24 3.15 4.65 2.98 3.456 0.24 c 10.6 11.15 l "
-      "11 12.5 l 11.46 12.67 11.84 12.96 12 13.64 c h f\n",
+      "q 3.102 4.67 m 5.45 0.29 l 4.24 3.15 4.65 2.98 3.456 0.24 c 10.6 11.15 "
+      "l 11 12.5 l 11.46 12.67 11.84 12.96 12 13.64 c h f Q\n",
       buf.MakeString());
 }
+
+TEST_F(CPDF_PageContentGeneratorTest, ProcessGraphics) {
+  auto pPathObj = pdfium::MakeUnique<CPDF_PathObject>();
+  pPathObj->m_Path.SetPointCount(3);
+  FX_PATHPOINT* pPoints = pPathObj->m_Path.GetMutablePoints();
+  pPoints[0].m_PointX = 1;
+  pPoints[0].m_PointY = 2;
+  pPoints[0].m_Flag = FXPT_MOVETO;
+  pPoints[1].m_PointX = 3;
+  pPoints[1].m_PointY = 4;
+  pPoints[1].m_Flag = FXPT_LINETO;
+  pPoints[2].m_PointX = 5;
+  pPoints[2].m_PointY = 6;
+  pPoints[2].m_Flag = FXPT_LINETO | FXPT_CLOSEFIGURE;
+  pPathObj->m_FillType = FXFILL_WINDING;
+  pPathObj->m_bStroke = true;
+  FX_FLOAT rgb[3] = {0.5f, 0.7f, 0.35f};
+  CPDF_ColorSpace* pCS = CPDF_ColorSpace::GetStockCS(PDFCS_DEVICERGB);
+  pPathObj->m_ColorState.SetFillColor(pCS, rgb, 3);
+  FX_FLOAT rgb2[3] = {1, 0.9f, 0};
+  pPathObj->m_ColorState.SetStrokeColor(pCS, rgb2, 3);
+  pPathObj->m_GeneralState.SetFillAlpha(0.5f);
+  pPathObj->m_GeneralState.SetStrokeAlpha(0.8f);
+  auto pDoc = pdfium::MakeUnique<CPDF_Document>(nullptr);
+  pDoc->CreateNewDoc();
+  CPDF_Dictionary* pPageDict = pDoc->CreateNewPage(0);
+  auto pTestPage = pdfium::MakeUnique<CPDF_Page>(pDoc.get(), pPageDict, false);
+  CPDF_PageContentGenerator generator(pTestPage.get());
+  CFX_ByteTextBuf buf;
+  TestProcessPath(&generator, &buf, pPathObj.get());
+  CFX_ByteString pathString = buf.MakeString();
+  // Color RGB values used are integers divided by 255.
+  EXPECT_EQ("q 0.501961 0.701961 0.34902 rg 1 0.901961 0 RG /",
+            pathString.Left(48));
+  EXPECT_EQ(" gs 1 2 m 3 4 l 5 6 l h B Q\n", pathString.Right(28));
+  ASSERT_TRUE(pathString.GetLength() > 76);
+  CPDF_Dictionary* externalGS =
+      TestGetGS(&generator, pathString.Mid(48, pathString.GetLength() - 76));
+  ASSERT_TRUE(externalGS);
+  EXPECT_EQ(0.5f, externalGS->GetNumberFor("ca"));
+  EXPECT_EQ(0.8f, externalGS->GetNumberFor("CA"));
+}