Set default graphics before generating page contents

In this CL, the content generator sets some default graphics states
before processing the page objects. In particular, a default ExtGState
is now set before processing, and the last CTM is now stored right
after parsing finishes: the only command to change matrix is ctm, and
it concatenates, so inverting requires knowing the current value.

Bug: pdfium:779
Change-Id: I35b1c07550ce91839fb0e20fbf717e3e80c9b9d6
Reviewed-on: https://pdfium-review.googlesource.com/7070
Commit-Queue: Nicolás Peña <npm@chromium.org>
Reviewed-by: dsinclair <dsinclair@chromium.org>
diff --git a/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp b/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp
index eeb6f81..10b3933 100644
--- a/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp
+++ b/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp
@@ -65,9 +65,24 @@
 
   CPDF_Document* pDoc = m_pDocument.Get();
   std::ostringstream buf;
+
+  // Set the default graphic state values
+  buf << "q\n";
+  if (!m_pObjHolder->GetLastCTM().IsIdentity()) {
+    CFX_Matrix reverse;
+    reverse.SetReverse(m_pObjHolder->GetLastCTM());
+    buf << reverse << " cm\n";
+  }
+  ProcessDefaultGraphics(&buf);
+
+  // Process the page objects
   if (!ProcessPageObjects(&buf))
     return;
 
+  // Return graphics to original state
+  buf << "Q\n";
+
+  // Add buffer to a stream in page's 'Contents'
   CPDF_Dictionary* pPageDict = m_pObjHolder->m_pFormDict.Get();
   CPDF_Object* pContent =
       pPageDict ? pPageDict->GetObjectFor("Contents") : nullptr;
@@ -142,6 +157,7 @@
   for (auto& pPageObj : m_pageObjects) {
     if (m_pObjHolder->IsPage() && !pPageObj->IsDirty())
       continue;
+
     bDirty = true;
     if (CPDF_ImageObject* pImageObject = pPageObj->AsImage())
       ProcessImage(buf, pImageObject);
@@ -304,6 +320,32 @@
   *buf << "/" << PDF_NameEncode(name) << " gs ";
 }
 
+void CPDF_PageContentGenerator::ProcessDefaultGraphics(
+    std::ostringstream* buf) {
+  *buf << "0 0 0 RG 0 0 0 rg 1 w "
+       << static_cast<int>(CFX_GraphStateData::LineCapButt) << " J "
+       << static_cast<int>(CFX_GraphStateData::LineJoinMiter) << " j\n";
+  GraphicsData defaultGraphics;
+  defaultGraphics.fillAlpha = 1.0f;
+  defaultGraphics.strokeAlpha = 1.0f;
+  defaultGraphics.blendType = FXDIB_BLEND_NORMAL;
+  auto it = m_pObjHolder->m_GraphicsMap.find(defaultGraphics);
+  CFX_ByteString name;
+  if (it != m_pObjHolder->m_GraphicsMap.end()) {
+    name = it->second;
+  } else {
+    auto gsDict = pdfium::MakeUnique<CPDF_Dictionary>();
+    gsDict->SetNewFor<CPDF_Number>("ca", defaultGraphics.fillAlpha);
+    gsDict->SetNewFor<CPDF_Number>("CA", defaultGraphics.strokeAlpha);
+    gsDict->SetNewFor<CPDF_Name>("BM", "Normal");
+    CPDF_Object* pDict = m_pDocument->AddIndirectObject(std::move(gsDict));
+    uint32_t dwObjNum = pDict->GetObjNum();
+    name = RealizeResource(dwObjNum, "ExtGState");
+    m_pObjHolder->m_GraphicsMap[defaultGraphics] = name;
+  }
+  *buf << "/" << PDF_NameEncode(name).c_str() << " gs ";
+}
+
 // This method adds text to the buffer, BT begins the text object, ET ends it.
 // Tm sets the text matrix (allows positioning and transforming text).
 // Tf sets the font name (from Font in Resources) and font size.
diff --git a/core/fpdfapi/edit/cpdf_pagecontentgenerator.h b/core/fpdfapi/edit/cpdf_pagecontentgenerator.h
index 2d90eb4..5295d87 100644
--- a/core/fpdfapi/edit/cpdf_pagecontentgenerator.h
+++ b/core/fpdfapi/edit/cpdf_pagecontentgenerator.h
@@ -35,6 +35,7 @@
   void ProcessPath(std::ostringstream* buf, CPDF_PathObject* pPathObj);
   void ProcessImage(std::ostringstream* buf, CPDF_ImageObject* pImageObj);
   void ProcessGraphics(std::ostringstream* buf, CPDF_PageObject* pPageObj);
+  void ProcessDefaultGraphics(std::ostringstream* buf);
   void ProcessText(std::ostringstream* buf, CPDF_TextObject* pTextObj);
   CFX_ByteString RealizeResource(uint32_t dwResourceObjNum,
                                  const CFX_ByteString& bsType);
diff --git a/core/fpdfapi/page/cpdf_contentparser.h b/core/fpdfapi/page/cpdf_contentparser.h
index 58a9773..b18e070 100644
--- a/core/fpdfapi/page/cpdf_contentparser.h
+++ b/core/fpdfapi/page/cpdf_contentparser.h
@@ -29,6 +29,7 @@
   ~CPDF_ContentParser();
 
   ParseStatus GetStatus() const { return m_Status; }
+  CPDF_StreamContentParser* GetParser() const { return m_pParser.get(); }
   void Start(CPDF_Page* pPage);
   void Start(CPDF_Form* pForm,
              CPDF_AllStates* pGraphicStates,
diff --git a/core/fpdfapi/page/cpdf_pageobjectholder.cpp b/core/fpdfapi/page/cpdf_pageobjectholder.cpp
index 50ef780..614fa34 100644
--- a/core/fpdfapi/page/cpdf_pageobjectholder.cpp
+++ b/core/fpdfapi/page/cpdf_pageobjectholder.cpp
@@ -8,6 +8,7 @@
 
 #include <algorithm>
 
+#include "core/fpdfapi/page/cpdf_allstates.h"
 #include "core/fpdfapi/page/cpdf_contentparser.h"
 #include "core/fpdfapi/page/cpdf_pageobject.h"
 #include "core/fpdfapi/parser/cpdf_dictionary.h"
@@ -38,6 +39,8 @@
     return;
 
   m_ParseState = CONTENT_PARSED;
+  if (m_pParser->GetParser() && m_pParser->GetParser()->GetCurStates())
+    m_LastCTM = m_pParser->GetParser()->GetCurStates()->m_CTM;
   m_pParser.reset();
 }
 
diff --git a/core/fpdfapi/page/cpdf_pageobjectholder.h b/core/fpdfapi/page/cpdf_pageobjectholder.h
index 4733e06..87ebbc8 100644
--- a/core/fpdfapi/page/cpdf_pageobjectholder.h
+++ b/core/fpdfapi/page/cpdf_pageobjectholder.h
@@ -55,6 +55,7 @@
   const CPDF_PageObjectList* GetPageObjectList() const {
     return &m_PageObjectList;
   }
+  const CFX_Matrix& GetLastCTM() const { return m_LastCTM; }
 
   bool BackgroundAlphaNeeded() const { return m_bBackgroundAlphaNeeded; }
   void SetBackgroundAlphaNeeded(bool needed) {
@@ -89,6 +90,7 @@
   ParseState m_ParseState;
   std::unique_ptr<CPDF_ContentParser> m_pParser;
   CPDF_PageObjectList m_PageObjectList;
+  CFX_Matrix m_LastCTM;
 };
 
 #endif  // CORE_FPDFAPI_PAGE_CPDF_PAGEOBJECTHOLDER_H_
diff --git a/fpdfsdk/fpdfedit_embeddertest.cpp b/fpdfsdk/fpdfedit_embeddertest.cpp
index ad3e0d1..727306a 100644
--- a/fpdfsdk/fpdfedit_embeddertest.cpp
+++ b/fpdfsdk/fpdfedit_embeddertest.cpp
@@ -139,24 +139,29 @@
     "<</CreationDate\\(D:.*\\)/Creator\\(PDFium\\)>>\r\n"
     "endobj\r\n"
     "4 0 obj\r\n"
-    "<</MediaBox\\[ 0 0 640 480\\]/Parent 2 0 R /Resources<<>>"
+    "<</MediaBox\\[ 0 0 640 480\\]/Parent 2 0 R "
+    "/Resources<</ExtGState<</FXE1 5 0 R >>>>"
     "/Rotate 0/Type/Page"
     ">>\r\n"
     "endobj\r\n"
+    "5 0 obj\r\n"
+    "<</BM/Normal/CA 1/ca 1>>\r\n"
+    "endobj\r\n"
     "xref\r\n"
-    "0 5\r\n"
+    "0 6\r\n"
     "0000000000 65535 f\r\n"
     "0000000017 00000 n\r\n"
     "0000000066 00000 n\r\n"
     "0000000122 00000 n\r\n"
     "0000000192 00000 n\r\n"
+    "0000000311 00000 n\r\n"
     "trailer\r\n"
     "<<\r\n"
     "/Root 1 0 R\r\n"
     "/Info 3 0 R\r\n"
-    "/Size 5/ID\\[<.*><.*>\\]>>\r\n"
+    "/Size 6/ID\\[<.*><.*>\\]>>\r\n"
     "startxref\r\n"
-    "285\r\n"
+    "354\r\n"
     "%%EOF\r\n";
 
 }  // namespace
@@ -572,7 +577,7 @@
   CPDF_Dictionary* graphics_dict =
       the_page->m_pResources->GetDictFor("ExtGState");
   ASSERT_TRUE(graphics_dict);
-  EXPECT_EQ(1, static_cast<int>(graphics_dict->GetCount()));
+  EXPECT_EQ(2, static_cast<int>(graphics_dict->GetCount()));
 
   // Add a text object causing no change to the graphics dictionary
   FPDF_PAGEOBJECT text1 = FPDFPageObj_NewTextObj(document(), "Arial", 12.0f);
@@ -581,7 +586,7 @@
   EXPECT_TRUE(FPDFText_SetFillColor(text1, 100, 100, 100, 255));
   FPDFPage_InsertObject(page.get(), text1);
   EXPECT_TRUE(FPDFPage_GenerateContent(page.get()));
-  EXPECT_EQ(1, static_cast<int>(graphics_dict->GetCount()));
+  EXPECT_EQ(2, static_cast<int>(graphics_dict->GetCount()));
 
   // Add a text object increasing the size of the graphics dictionary
   FPDF_PAGEOBJECT text2 =
@@ -590,7 +595,7 @@
   FPDFPageObj_SetBlendMode(text2, "Darken");
   EXPECT_TRUE(FPDFText_SetFillColor(text2, 0, 0, 255, 150));
   EXPECT_TRUE(FPDFPage_GenerateContent(page.get()));
-  EXPECT_EQ(2, static_cast<int>(graphics_dict->GetCount()));
+  EXPECT_EQ(3, static_cast<int>(graphics_dict->GetCount()));
 
   // Add a path that should reuse graphics
   FPDF_PAGEOBJECT path = FPDFPageObj_CreateNewPath(400, 100);
@@ -598,7 +603,7 @@
   EXPECT_TRUE(FPDFPath_SetFillColor(path, 200, 200, 100, 150));
   FPDFPage_InsertObject(page.get(), path);
   EXPECT_TRUE(FPDFPage_GenerateContent(page.get()));
-  EXPECT_EQ(2, static_cast<int>(graphics_dict->GetCount()));
+  EXPECT_EQ(3, static_cast<int>(graphics_dict->GetCount()));
 
   // Add a rect increasing the size of the graphics dictionary
   FPDF_PAGEOBJECT rect2 = FPDFPageObj_CreateNewRect(10, 10, 100, 100);
@@ -607,7 +612,7 @@
   EXPECT_TRUE(FPDFPath_SetStrokeColor(rect2, 0, 0, 0, 200));
   FPDFPage_InsertObject(page.get(), rect2);
   EXPECT_TRUE(FPDFPage_GenerateContent(page.get()));
-  EXPECT_EQ(3, static_cast<int>(graphics_dict->GetCount()));
+  EXPECT_EQ(4, static_cast<int>(graphics_dict->GetCount()));
 }
 
 TEST_F(FPDFEditEmbeddertest, DoubleGenerating) {
@@ -626,7 +631,7 @@
   CPDF_Dictionary* graphics_dict =
       the_page->m_pResources->GetDictFor("ExtGState");
   ASSERT_TRUE(graphics_dict);
-  EXPECT_EQ(1, static_cast<int>(graphics_dict->GetCount()));
+  EXPECT_EQ(2, static_cast<int>(graphics_dict->GetCount()));
 
   // Check the bitmap
   FPDF_BITMAP page_bitmap = RenderPage(page);
@@ -636,7 +641,7 @@
   // Never mind, my new favorite color is blue, increase alpha
   EXPECT_TRUE(FPDFPath_SetFillColor(rect, 0, 0, 255, 180));
   EXPECT_TRUE(FPDFPage_GenerateContent(page));
-  EXPECT_EQ(2, static_cast<int>(graphics_dict->GetCount()));
+  EXPECT_EQ(3, static_cast<int>(graphics_dict->GetCount()));
 
   // Check that bitmap displays changed content
   page_bitmap = RenderPage(page);
@@ -645,7 +650,7 @@
 
   // And now generate, without changes
   EXPECT_TRUE(FPDFPage_GenerateContent(page));
-  EXPECT_EQ(2, static_cast<int>(graphics_dict->GetCount()));
+  EXPECT_EQ(3, static_cast<int>(graphics_dict->GetCount()));
   page_bitmap = RenderPage(page);
   CompareBitmap(page_bitmap, 612, 792, "2e51656f5073b0bee611d9cd086aa09c");
   FPDFBitmap_Destroy(page_bitmap);
@@ -665,7 +670,7 @@
 
   // Generate yet again, check dicts are reasonably sized
   EXPECT_TRUE(FPDFPage_GenerateContent(page));
-  EXPECT_EQ(2, static_cast<int>(graphics_dict->GetCount()));
+  EXPECT_EQ(3, static_cast<int>(graphics_dict->GetCount()));
   EXPECT_EQ(1, static_cast<int>(font_dict->GetCount()));
   FPDF_ClosePage(page);
 }
@@ -984,3 +989,51 @@
   FPDF_CloseDocument(new_doc);
 }
 #endif  // _FXM_PLATFORM_ == _FXM_PLATFORM_LINUX_
+
+TEST_F(FPDFEditEmbeddertest, SaveAndRender) {
+  const char embMD5[] = "3c20472b0552c0c22b88ab1ed8c6202b";
+  {
+    EXPECT_TRUE(OpenDocument("bug_779.pdf"));
+    FPDF_PAGE page = LoadPage(0);
+    ASSERT_NE(nullptr, page);
+
+    // Now add a more complex blue path.
+    FPDF_PAGEOBJECT green_path = FPDFPageObj_CreateNewPath(20, 20);
+    EXPECT_TRUE(FPDFPath_SetFillColor(green_path, 0, 255, 0, 200));
+    // TODO(npm): stroking will cause the MD5s to differ.
+    EXPECT_TRUE(FPDFPath_SetDrawMode(green_path, FPDF_FILLMODE_WINDING, 0));
+    EXPECT_TRUE(FPDFPath_LineTo(green_path, 20, 63));
+    EXPECT_TRUE(FPDFPath_BezierTo(green_path, 55, 55, 78, 78, 90, 90));
+    EXPECT_TRUE(FPDFPath_LineTo(green_path, 133, 133));
+    EXPECT_TRUE(FPDFPath_LineTo(green_path, 133, 33));
+    EXPECT_TRUE(FPDFPath_BezierTo(green_path, 38, 33, 39, 36, 40, 40));
+    EXPECT_TRUE(FPDFPath_Close(green_path));
+    FPDFPage_InsertObject(page, green_path);
+    FPDF_BITMAP page_bitmap = RenderPage(page);
+    CompareBitmap(page_bitmap, 612, 792, embMD5);
+    FPDFBitmap_Destroy(page_bitmap);
+
+    // Now save the result, closing the page and document
+    EXPECT_TRUE(FPDFPage_GenerateContent(page));
+    EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0));
+    UnloadPage(page);
+  }
+
+  // Render the saved result
+  std::string new_file = GetString();
+  FPDF_FILEACCESS file_access;
+  memset(&file_access, 0, sizeof(file_access));
+  file_access.m_FileLen = new_file.size();
+  file_access.m_GetBlock = GetBlockFromString;
+  file_access.m_Param = &new_file;
+  FPDF_DOCUMENT new_doc = FPDF_LoadCustomDocument(&file_access, nullptr);
+  ASSERT_NE(nullptr, new_doc);
+  EXPECT_EQ(1, FPDF_GetPageCount(new_doc));
+  FPDF_PAGE new_page = FPDF_LoadPage(new_doc, 0);
+  ASSERT_NE(nullptr, new_page);
+  FPDF_BITMAP new_bitmap = RenderPage(new_page);
+  CompareBitmap(new_bitmap, 612, 792, embMD5);
+  FPDFBitmap_Destroy(new_bitmap);
+  FPDF_ClosePage(new_page);
+  FPDF_CloseDocument(new_doc);
+}
diff --git a/testing/resources/bug_779.in b/testing/resources/bug_779.in
new file mode 100644
index 0000000..f8e21a0
--- /dev/null
+++ b/testing/resources/bug_779.in
@@ -0,0 +1,50 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /MediaBox [ 0 0 612 792 ]
+  /Count 1
+  /Kids [ 3 0 R ]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /Resources 5 0 R
+>>
+endobj
+{{object 4 0}} <<
+>>
+stream
+1 0 0 -1 0 792 cm
+0 0 255 RG
+255 0 0 rg
+5 w
+/GS1 gs
+q
+100 100 m
+200 200 l
+200 300 l
+b*
+endstream
+endobj
+{{object 5 0}} <<
+  /ExtGState <<
+    /GS1 6 0 R
+  >>
+>>
+endobj
+{{object 6 0}} <<
+  /Type /ExtGState
+  /CA 0.3
+  /ca 0.3
+>>
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
\ No newline at end of file
diff --git a/testing/resources/bug_779.pdf b/testing/resources/bug_779.pdf
new file mode 100644
index 0000000..2d60904
--- /dev/null
+++ b/testing/resources/bug_779.pdf
@@ -0,0 +1,60 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /MediaBox [ 0 0 612 792 ]
+  /Count 1
+  /Kids [ 3 0 R ]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+  /Resources 5 0 R
+>>
+endobj
+4 0 obj <<
+>>
+stream
+1 0 0 -1 0 792 cm
+0 0 255 RG
+255 0 0 rg
+5 w
+/GS1 gs
+q
+100 100 m
+200 200 l
+200 300 l
+b*
+endstream
+endobj
+5 0 obj <<
+  /ExtGState <<
+    /GS1 6 0 R
+  >>
+>>
+endobj
+6 0 obj <<
+  /Type /ExtGState
+  /CA 0.3
+  /ca 0.3
+>>
+xref
+0 7
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000161 00000 n 
+0000000249 00000 n 
+0000000374 00000 n 
+0000000431 00000 n 
+trailer<< /Root 1 0 R /Size 7 >>
+startxref
+484
+%%EOF
\ No newline at end of file