From f9baca8709472f71fe0e5bcae95717b7f712b4a5 Mon Sep 17 00:00:00 2001 From: Nicolas Pena Date: Mon, 6 Feb 2017 12:01:21 -0500 Subject: Add support for fill and stroke RGBA in CPDF_PageContentGenerator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Reviewed-by: Tom Sepez Commit-Queue: Nicolás Peña --- core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp | 71 ++++++++++++++++++- core/fpdfapi/edit/cpdf_pagecontentgenerator.h | 12 +++- .../edit/cpdf_pagecontentgenerator_unittest.cpp | 82 +++++++++++++++++++--- 3 files changed, 153 insertions(+), 12 deletions(-) diff --git a/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp b/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp index e2354e1b50..9dba6e0ceb 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 +#include + #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 @@ CFX_ByteTextBuf& operator<<(CFX_ByteTextBuf& ar, const CFX_Matrix& matrix) { 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 @@ void CPDF_PageContentGenerator::ProcessImage(CFX_ByteTextBuf* buf, // "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 @@ void CPDF_PageContentGenerator::ProcessPath(CFX_ByteTextBuf* buf, *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(); + gsDict->SetNewFor("ca", graphD.fillAlpha); + gsDict->SetNewFor("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 c74652e206..e48ea4a7c9 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 #include #include "core/fxcrt/fx_basic.h" @@ -26,14 +27,21 @@ class CPDF_PageContentGenerator { 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 m_GraphicsMap; CPDF_Page* const m_pPage; CPDF_Document* const m_pDocument; std::vector m_pageObjects; diff --git a/core/fpdfapi/edit/cpdf_pagecontentgenerator_unittest.cpp b/core/fpdfapi/edit/cpdf_pagecontentgenerator_unittest.cpp index 8812c0e2b6..41e61b3bab 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(); pPathObj->m_Path.AppendRect(10, 5, 13, 30); pPathObj->m_FillType = FXFILL_ALTERNATE; @@ -17,8 +39,8 @@ TEST(cpdf_pagecontentgenerator, ProcessRect) { auto pTestPage = pdfium::MakeUnique(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(); pPathObj->m_Path.SetPointCount(4); @@ -38,11 +60,11 @@ TEST(cpdf_pagecontentgenerator, ProcessRect) { 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(); pPathObj->m_Path.SetPointCount(10); FX_PATHPOINT* pPoints = pPathObj->m_Path.GetMutablePoints(); @@ -81,9 +103,51 @@ TEST(cpdf_pagecontentgenerator, ProcessPath) { auto pTestPage = pdfium::MakeUnique(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(); + 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(nullptr); + pDoc->CreateNewDoc(); + CPDF_Dictionary* pPageDict = pDoc->CreateNewPage(0); + auto pTestPage = pdfium::MakeUnique(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")); +} -- cgit v1.2.3