From 9ba8fbc02e86a6b7cb5c4142d9481ae3238c1ec4 Mon Sep 17 00:00:00 2001 From: Nicolas Pena Date: Wed, 28 Jun 2017 15:31:56 -0400 Subject: Set default graphics before generating page contents MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Reviewed-by: dsinclair --- core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp | 42 +++++++++++++ core/fpdfapi/edit/cpdf_pagecontentgenerator.h | 1 + core/fpdfapi/page/cpdf_contentparser.h | 1 + core/fpdfapi/page/cpdf_pageobjectholder.cpp | 3 + core/fpdfapi/page/cpdf_pageobjectholder.h | 2 + fpdfsdk/fpdfedit_embeddertest.cpp | 79 +++++++++++++++++++++---- testing/resources/bug_779.in | 50 ++++++++++++++++ testing/resources/bug_779.pdf | 60 +++++++++++++++++++ 8 files changed, 225 insertions(+), 13 deletions(-) create mode 100644 testing/resources/bug_779.in create mode 100644 testing/resources/bug_779.pdf diff --git a/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp b/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp index eeb6f81721..10b3933742 100644 --- a/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp +++ b/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp @@ -65,9 +65,24 @@ void CPDF_PageContentGenerator::GenerateContent() { 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 @@ bool CPDF_PageContentGenerator::ProcessPageObjects(std::ostringstream* buf) { 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 @@ void CPDF_PageContentGenerator::ProcessGraphics(std::ostringstream* buf, *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(CFX_GraphStateData::LineCapButt) << " J " + << static_cast(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(); + gsDict->SetNewFor("ca", defaultGraphics.fillAlpha); + gsDict->SetNewFor("CA", defaultGraphics.strokeAlpha); + gsDict->SetNewFor("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 2d90eb4465..5295d8747b 100644 --- a/core/fpdfapi/edit/cpdf_pagecontentgenerator.h +++ b/core/fpdfapi/edit/cpdf_pagecontentgenerator.h @@ -35,6 +35,7 @@ class CPDF_PageContentGenerator { 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 58a977301f..b18e07036c 100644 --- a/core/fpdfapi/page/cpdf_contentparser.h +++ b/core/fpdfapi/page/cpdf_contentparser.h @@ -29,6 +29,7 @@ class CPDF_ContentParser { ~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 50ef780396..614fa34d2b 100644 --- a/core/fpdfapi/page/cpdf_pageobjectholder.cpp +++ b/core/fpdfapi/page/cpdf_pageobjectholder.cpp @@ -8,6 +8,7 @@ #include +#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 @@ void CPDF_PageObjectHolder::ContinueParse(IFX_Pause* pPause) { 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 4733e06b2c..87ebbc8460 100644 --- a/core/fpdfapi/page/cpdf_pageobjectholder.h +++ b/core/fpdfapi/page/cpdf_pageobjectholder.h @@ -55,6 +55,7 @@ class CPDF_PageObjectHolder { 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 @@ class CPDF_PageObjectHolder { ParseState m_ParseState; std::unique_ptr 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 ad3e0d15a8..727306a96e 100644 --- a/fpdfsdk/fpdfedit_embeddertest.cpp +++ b/fpdfsdk/fpdfedit_embeddertest.cpp @@ -139,24 +139,29 @@ const char kExpectedPDF[] = "<>\r\n" "endobj\r\n" "4 0 obj\r\n" - "<>" + "<>>>" "/Rotate 0/Type/Page" ">>\r\n" "endobj\r\n" + "5 0 obj\r\n" + "<>\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 @@ TEST_F(FPDFEditEmbeddertest, GraphicsData) { CPDF_Dictionary* graphics_dict = the_page->m_pResources->GetDictFor("ExtGState"); ASSERT_TRUE(graphics_dict); - EXPECT_EQ(1, static_cast(graphics_dict->GetCount())); + EXPECT_EQ(2, static_cast(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 @@ TEST_F(FPDFEditEmbeddertest, GraphicsData) { 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(graphics_dict->GetCount())); + EXPECT_EQ(2, static_cast(graphics_dict->GetCount())); // Add a text object increasing the size of the graphics dictionary FPDF_PAGEOBJECT text2 = @@ -590,7 +595,7 @@ TEST_F(FPDFEditEmbeddertest, GraphicsData) { FPDFPageObj_SetBlendMode(text2, "Darken"); EXPECT_TRUE(FPDFText_SetFillColor(text2, 0, 0, 255, 150)); EXPECT_TRUE(FPDFPage_GenerateContent(page.get())); - EXPECT_EQ(2, static_cast(graphics_dict->GetCount())); + EXPECT_EQ(3, static_cast(graphics_dict->GetCount())); // Add a path that should reuse graphics FPDF_PAGEOBJECT path = FPDFPageObj_CreateNewPath(400, 100); @@ -598,7 +603,7 @@ TEST_F(FPDFEditEmbeddertest, GraphicsData) { 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(graphics_dict->GetCount())); + EXPECT_EQ(3, static_cast(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 @@ TEST_F(FPDFEditEmbeddertest, GraphicsData) { 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(graphics_dict->GetCount())); + EXPECT_EQ(4, static_cast(graphics_dict->GetCount())); } TEST_F(FPDFEditEmbeddertest, DoubleGenerating) { @@ -626,7 +631,7 @@ TEST_F(FPDFEditEmbeddertest, DoubleGenerating) { CPDF_Dictionary* graphics_dict = the_page->m_pResources->GetDictFor("ExtGState"); ASSERT_TRUE(graphics_dict); - EXPECT_EQ(1, static_cast(graphics_dict->GetCount())); + EXPECT_EQ(2, static_cast(graphics_dict->GetCount())); // Check the bitmap FPDF_BITMAP page_bitmap = RenderPage(page); @@ -636,7 +641,7 @@ TEST_F(FPDFEditEmbeddertest, DoubleGenerating) { // 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(graphics_dict->GetCount())); + EXPECT_EQ(3, static_cast(graphics_dict->GetCount())); // Check that bitmap displays changed content page_bitmap = RenderPage(page); @@ -645,7 +650,7 @@ TEST_F(FPDFEditEmbeddertest, DoubleGenerating) { // And now generate, without changes EXPECT_TRUE(FPDFPage_GenerateContent(page)); - EXPECT_EQ(2, static_cast(graphics_dict->GetCount())); + EXPECT_EQ(3, static_cast(graphics_dict->GetCount())); page_bitmap = RenderPage(page); CompareBitmap(page_bitmap, 612, 792, "2e51656f5073b0bee611d9cd086aa09c"); FPDFBitmap_Destroy(page_bitmap); @@ -665,7 +670,7 @@ TEST_F(FPDFEditEmbeddertest, DoubleGenerating) { // Generate yet again, check dicts are reasonably sized EXPECT_TRUE(FPDFPage_GenerateContent(page)); - EXPECT_EQ(2, static_cast(graphics_dict->GetCount())); + EXPECT_EQ(3, static_cast(graphics_dict->GetCount())); EXPECT_EQ(1, static_cast(font_dict->GetCount())); FPDF_ClosePage(page); } @@ -984,3 +989,51 @@ TEST_F(FPDFEditEmbeddertest, AddCIDFontText) { 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 0000000000..f8e21a0aad --- /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 0000000000..2d609045cc --- /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 -- cgit v1.2.3