1// Copyright 2017 PDFium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "core/fpdfapi/edit/cpdf_pagecontentgenerator.h" 6 7#include <memory> 8#include <utility> 9 10#include "core/fpdfapi/cpdf_modulemgr.h" 11#include "core/fpdfapi/font/cpdf_font.h" 12#include "core/fpdfapi/page/cpdf_form.h" 13#include "core/fpdfapi/page/cpdf_page.h" 14#include "core/fpdfapi/page/cpdf_pathobject.h" 15#include "core/fpdfapi/page/cpdf_textobject.h" 16#include "core/fpdfapi/parser/cpdf_document.h" 17#include "core/fpdfapi/parser/cpdf_name.h" 18#include "core/fpdfapi/parser/cpdf_parser.h" 19#include "core/fpdfapi/parser/cpdf_reference.h" 20#include "testing/gtest/include/gtest/gtest.h" 21#include "testing/test_support.h" 22#include "third_party/base/ptr_util.h" 23 24class CPDF_PageContentGeneratorTest : public testing::Test { 25 protected: 26 void SetUp() override { CPDF_ModuleMgr::Get()->Init(); } 27 28 void TearDown() override { 29 CPDF_ModuleMgr::Destroy(); 30 } 31 32 void TestProcessPath(CPDF_PageContentGenerator* pGen, 33 std::ostringstream* buf, 34 CPDF_PathObject* pPathObj) { 35 pGen->ProcessPath(buf, pPathObj); 36 } 37 38 CPDF_Dictionary* TestGetResource(CPDF_PageContentGenerator* pGen, 39 const ByteString& type, 40 const ByteString& name) { 41 return pGen->m_pObjHolder->m_pResources->GetDictFor(type)->GetDictFor(name); 42 } 43 44 void TestProcessText(CPDF_PageContentGenerator* pGen, 45 std::ostringstream* buf, 46 CPDF_TextObject* pTextObj) { 47 pGen->ProcessText(buf, pTextObj); 48 } 49}; 50 51TEST_F(CPDF_PageContentGeneratorTest, ProcessRect) { 52 auto pPathObj = pdfium::MakeUnique<CPDF_PathObject>(); 53 pPathObj->m_Path.AppendRect(10, 5, 13, 30); 54 pPathObj->m_FillType = FXFILL_ALTERNATE; 55 pPathObj->m_bStroke = true; 56 57 auto pTestPage = pdfium::MakeUnique<CPDF_Page>(nullptr, nullptr, false); 58 CPDF_PageContentGenerator generator(pTestPage.get()); 59 std::ostringstream buf; 60 TestProcessPath(&generator, &buf, pPathObj.get()); 61 EXPECT_EQ("q 1 0 0 1 0 0 cm 10 5 3 25 re B* Q\n", ByteString(buf)); 62 63 pPathObj = pdfium::MakeUnique<CPDF_PathObject>(); 64 pPathObj->m_Path.AppendPoint(CFX_PointF(0, 0), FXPT_TYPE::MoveTo, false); 65 pPathObj->m_Path.AppendPoint(CFX_PointF(5.2f, 0), FXPT_TYPE::LineTo, false); 66 pPathObj->m_Path.AppendPoint(CFX_PointF(5.2f, 3.78f), FXPT_TYPE::LineTo, 67 false); 68 pPathObj->m_Path.AppendPoint(CFX_PointF(0, 3.78f), FXPT_TYPE::LineTo, true); 69 pPathObj->m_FillType = 0; 70 pPathObj->m_bStroke = false; 71 buf.str(""); 72 73 TestProcessPath(&generator, &buf, pPathObj.get()); 74 EXPECT_EQ("q 1 0 0 1 0 0 cm 0 0 5.2 3.78 re n Q\n", ByteString(buf)); 75} 76 77TEST_F(CPDF_PageContentGeneratorTest, ProcessPath) { 78 auto pPathObj = pdfium::MakeUnique<CPDF_PathObject>(); 79 pPathObj->m_Path.AppendPoint(CFX_PointF(3.102f, 4.67f), FXPT_TYPE::MoveTo, 80 false); 81 pPathObj->m_Path.AppendPoint(CFX_PointF(5.45f, 0.29f), FXPT_TYPE::LineTo, 82 false); 83 pPathObj->m_Path.AppendPoint(CFX_PointF(4.24f, 3.15f), FXPT_TYPE::BezierTo, 84 false); 85 pPathObj->m_Path.AppendPoint(CFX_PointF(4.65f, 2.98f), FXPT_TYPE::BezierTo, 86 false); 87 pPathObj->m_Path.AppendPoint(CFX_PointF(3.456f, 0.24f), FXPT_TYPE::BezierTo, 88 false); 89 pPathObj->m_Path.AppendPoint(CFX_PointF(10.6f, 11.15f), FXPT_TYPE::LineTo, 90 false); 91 pPathObj->m_Path.AppendPoint(CFX_PointF(11, 12.5f), FXPT_TYPE::LineTo, false); 92 pPathObj->m_Path.AppendPoint(CFX_PointF(11.46f, 12.67f), FXPT_TYPE::BezierTo, 93 false); 94 pPathObj->m_Path.AppendPoint(CFX_PointF(11.84f, 12.96f), FXPT_TYPE::BezierTo, 95 false); 96 pPathObj->m_Path.AppendPoint(CFX_PointF(12, 13.64f), FXPT_TYPE::BezierTo, 97 true); 98 pPathObj->m_FillType = FXFILL_WINDING; 99 pPathObj->m_bStroke = false; 100 101 auto pTestPage = pdfium::MakeUnique<CPDF_Page>(nullptr, nullptr, false); 102 CPDF_PageContentGenerator generator(pTestPage.get()); 103 std::ostringstream buf; 104 TestProcessPath(&generator, &buf, pPathObj.get()); 105 EXPECT_EQ( 106 "q 1 0 0 1 0 0 cm 3.102 4.67 m 5.45 0.29 l 4.24 3.15 4.65 2.98 3.456 0.24" 107 " 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", 108 ByteString(buf)); 109} 110 111TEST_F(CPDF_PageContentGeneratorTest, ProcessGraphics) { 112 auto pPathObj = pdfium::MakeUnique<CPDF_PathObject>(); 113 pPathObj->m_Path.AppendPoint(CFX_PointF(1, 2), FXPT_TYPE::MoveTo, false); 114 pPathObj->m_Path.AppendPoint(CFX_PointF(3, 4), FXPT_TYPE::LineTo, false); 115 pPathObj->m_Path.AppendPoint(CFX_PointF(5, 6), FXPT_TYPE::LineTo, true); 116 pPathObj->m_FillType = FXFILL_WINDING; 117 pPathObj->m_bStroke = true; 118 119 float rgb[3] = {0.5f, 0.7f, 0.35f}; 120 CPDF_ColorSpace* pCS = CPDF_ColorSpace::GetStockCS(PDFCS_DEVICERGB); 121 pPathObj->m_ColorState.SetFillColor(pCS, rgb, 3); 122 123 float rgb2[3] = {1, 0.9f, 0}; 124 pPathObj->m_ColorState.SetStrokeColor(pCS, rgb2, 3); 125 pPathObj->m_GeneralState.SetFillAlpha(0.5f); 126 pPathObj->m_GeneralState.SetStrokeAlpha(0.8f); 127 128 auto pDoc = pdfium::MakeUnique<CPDF_Document>(nullptr); 129 pDoc->CreateNewDoc(); 130 CPDF_Dictionary* pPageDict = pDoc->CreateNewPage(0); 131 auto pTestPage = pdfium::MakeUnique<CPDF_Page>(pDoc.get(), pPageDict, false); 132 CPDF_PageContentGenerator generator(pTestPage.get()); 133 std::ostringstream buf; 134 TestProcessPath(&generator, &buf, pPathObj.get()); 135 ByteString pathString(buf); 136 137 // Color RGB values used are integers divided by 255. 138 EXPECT_EQ("q 0.501961 0.701961 0.34902 rg 1 0.901961 0 RG /", 139 pathString.Left(48)); 140 EXPECT_EQ(" gs 1 0 0 1 0 0 cm 1 2 m 3 4 l 5 6 l h B Q\n", 141 pathString.Right(43)); 142 ASSERT_TRUE(pathString.GetLength() > 91); 143 CPDF_Dictionary* externalGS = TestGetResource( 144 &generator, "ExtGState", pathString.Mid(48, pathString.GetLength() - 91)); 145 ASSERT_TRUE(externalGS); 146 EXPECT_EQ(0.5f, externalGS->GetNumberFor("ca")); 147 EXPECT_EQ(0.8f, externalGS->GetNumberFor("CA")); 148 149 // Same path, now with a stroke. 150 pPathObj->m_GraphState.SetLineWidth(10.5f); 151 buf.str(""); 152 TestProcessPath(&generator, &buf, pPathObj.get()); 153 ByteString pathString2(buf); 154 EXPECT_EQ("q 0.501961 0.701961 0.34902 rg 1 0.901961 0 RG 10.5 w /", 155 pathString2.Left(55)); 156 EXPECT_EQ(" gs 1 0 0 1 0 0 cm 1 2 m 3 4 l 5 6 l h B Q\n", 157 pathString2.Right(43)); 158 159 // Compare with the previous (should use same dictionary for gs) 160 EXPECT_EQ(pathString.GetLength() + 7, pathString2.GetLength()); 161 EXPECT_EQ(pathString.Mid(48, pathString.GetLength() - 76), 162 pathString2.Mid(55, pathString2.GetLength() - 83)); 163} 164 165TEST_F(CPDF_PageContentGeneratorTest, ProcessStandardText) { 166 // Checking font whose font dictionary is not yet indirect object. 167 auto pDoc = pdfium::MakeUnique<CPDF_Document>(nullptr); 168 pDoc->CreateNewDoc(); 169 CPDF_Dictionary* pPageDict = pDoc->CreateNewPage(0); 170 auto pTestPage = pdfium::MakeUnique<CPDF_Page>(pDoc.get(), pPageDict, false); 171 CPDF_PageContentGenerator generator(pTestPage.get()); 172 auto pTextObj = pdfium::MakeUnique<CPDF_TextObject>(); 173 CPDF_Font* pFont = CPDF_Font::GetStockFont(pDoc.get(), "Times-Roman"); 174 pTextObj->m_TextState.SetFont(pFont); 175 pTextObj->m_TextState.SetFontSize(10.0f); 176 float rgb[3] = {0.5f, 0.7f, 0.35f}; 177 CPDF_ColorSpace* pCS = CPDF_ColorSpace::GetStockCS(PDFCS_DEVICERGB); 178 pTextObj->m_ColorState.SetFillColor(pCS, rgb, 3); 179 180 float rgb2[3] = {1, 0.9f, 0}; 181 pTextObj->m_ColorState.SetStrokeColor(pCS, rgb2, 3); 182 pTextObj->m_GeneralState.SetFillAlpha(0.5f); 183 pTextObj->m_GeneralState.SetStrokeAlpha(0.8f); 184 pTextObj->Transform(CFX_Matrix(1, 0, 0, 1, 100, 100)); 185 pTextObj->SetText("Hello World"); 186 std::ostringstream buf; 187 TestProcessText(&generator, &buf, pTextObj.get()); 188 ByteString textString(buf); 189 auto firstResourceAt = textString.Find('/'); 190 ASSERT_TRUE(firstResourceAt.has_value()); 191 firstResourceAt = firstResourceAt.value() + 1; 192 auto secondResourceAt = textString.ReverseFind('/'); 193 ASSERT_TRUE(secondResourceAt.has_value()); 194 secondResourceAt = secondResourceAt.value() + 1; 195 ByteString firstString = textString.Left(firstResourceAt.value()); 196 ByteString midString = 197 textString.Mid(firstResourceAt.value(), 198 secondResourceAt.value() - firstResourceAt.value()); 199 ByteString lastString = 200 textString.Right(textString.GetLength() - secondResourceAt.value()); 201 // q and Q must be outside the BT .. ET operations 202 ByteString compareString1 = 203 "q 0.501961 0.701961 0.34902 rg 1 0.901961 0 RG /"; 204 // Color RGB values used are integers divided by 255. 205 ByteString compareString2 = " gs BT 1 0 0 1 100 100 Tm /"; 206 ByteString compareString3 = " 10 Tf <48656C6C6F20576F726C64> Tj ET Q\n"; 207 EXPECT_LT(compareString1.GetLength() + compareString2.GetLength() + 208 compareString3.GetLength(), 209 textString.GetLength()); 210 EXPECT_EQ(compareString1, firstString.Left(compareString1.GetLength())); 211 EXPECT_EQ(compareString2, midString.Right(compareString2.GetLength())); 212 EXPECT_EQ(compareString3, lastString.Right(compareString3.GetLength())); 213 CPDF_Dictionary* externalGS = TestGetResource( 214 &generator, "ExtGState", 215 midString.Left(midString.GetLength() - compareString2.GetLength())); 216 ASSERT_TRUE(externalGS); 217 EXPECT_EQ(0.5f, externalGS->GetNumberFor("ca")); 218 EXPECT_EQ(0.8f, externalGS->GetNumberFor("CA")); 219 CPDF_Dictionary* fontDict = TestGetResource( 220 &generator, "Font", 221 lastString.Left(lastString.GetLength() - compareString3.GetLength())); 222 ASSERT_TRUE(fontDict); 223 EXPECT_EQ("Font", fontDict->GetStringFor("Type")); 224 EXPECT_EQ("Type1", fontDict->GetStringFor("Subtype")); 225 EXPECT_EQ("Times-Roman", fontDict->GetStringFor("BaseFont")); 226} 227 228TEST_F(CPDF_PageContentGeneratorTest, ProcessText) { 229 // Checking font whose font dictionary is already an indirect object. 230 auto pDoc = pdfium::MakeUnique<CPDF_Document>(nullptr); 231 pDoc->CreateNewDoc(); 232 CPDF_Dictionary* pPageDict = pDoc->CreateNewPage(0); 233 auto pTestPage = pdfium::MakeUnique<CPDF_Page>(pDoc.get(), pPageDict, false); 234 CPDF_PageContentGenerator generator(pTestPage.get()); 235 236 std::ostringstream buf; 237 { 238 // Set the text object font and text 239 auto pTextObj = pdfium::MakeUnique<CPDF_TextObject>(); 240 CPDF_Dictionary* pDict = pDoc->NewIndirect<CPDF_Dictionary>(); 241 pDict->SetNewFor<CPDF_Name>("Type", "Font"); 242 pDict->SetNewFor<CPDF_Name>("Subtype", "TrueType"); 243 CPDF_Font* pFont = CPDF_Font::GetStockFont(pDoc.get(), "Arial"); 244 pDict->SetNewFor<CPDF_Name>("BaseFont", pFont->GetBaseFont()); 245 246 CPDF_Dictionary* pDesc = pDoc->NewIndirect<CPDF_Dictionary>(); 247 pDesc->SetNewFor<CPDF_Name>("Type", "FontDescriptor"); 248 pDesc->SetNewFor<CPDF_Name>("FontName", pFont->GetBaseFont()); 249 pDict->SetNewFor<CPDF_Reference>("FontDescriptor", pDoc.get(), 250 pDesc->GetObjNum()); 251 252 CPDF_Font* loadedFont = pDoc->LoadFont(pDict); 253 pTextObj->m_TextState.SetFont(loadedFont); 254 pTextObj->m_TextState.SetFontSize(15.5f); 255 pTextObj->SetText("I am indirect"); 256 257 TestProcessText(&generator, &buf, pTextObj.get()); 258 } 259 260 ByteString textString(buf); 261 auto firstResourceAt = textString.Find('/'); 262 ASSERT_TRUE(firstResourceAt.has_value()); 263 firstResourceAt = firstResourceAt.value() + 1; 264 ByteString firstString = textString.Left(firstResourceAt.value()); 265 ByteString lastString = 266 textString.Right(textString.GetLength() - firstResourceAt.value()); 267 // q and Q must be outside the BT .. ET operations 268 ByteString compareString1 = "q BT 1 0 0 1 0 0 Tm /"; 269 ByteString compareString2 = " 15.5 Tf <4920616D20696E646972656374> Tj ET Q\n"; 270 EXPECT_LT(compareString1.GetLength() + compareString2.GetLength(), 271 textString.GetLength()); 272 EXPECT_EQ(compareString1, textString.Left(compareString1.GetLength())); 273 EXPECT_EQ(compareString2, textString.Right(compareString2.GetLength())); 274 CPDF_Dictionary* fontDict = TestGetResource( 275 &generator, "Font", 276 textString.Mid(compareString1.GetLength(), 277 textString.GetLength() - compareString1.GetLength() - 278 compareString2.GetLength())); 279 ASSERT_TRUE(fontDict); 280 EXPECT_TRUE(fontDict->GetObjNum()); 281 EXPECT_EQ("Font", fontDict->GetStringFor("Type")); 282 EXPECT_EQ("TrueType", fontDict->GetStringFor("Subtype")); 283 EXPECT_EQ("Helvetica", fontDict->GetStringFor("BaseFont")); 284 CPDF_Dictionary* fontDesc = fontDict->GetDictFor("FontDescriptor"); 285 ASSERT_TRUE(fontDesc); 286 EXPECT_TRUE(fontDesc->GetObjNum()); 287 EXPECT_EQ("FontDescriptor", fontDesc->GetStringFor("Type")); 288 EXPECT_EQ("Helvetica", fontDesc->GetStringFor("FontName")); 289} 290 291TEST_F(CPDF_PageContentGeneratorTest, ProcessEmptyForm) { 292 auto pDoc = pdfium::MakeUnique<CPDF_Document>(nullptr); 293 pDoc->CreateNewDoc(); 294 auto pDict = pdfium::MakeUnique<CPDF_Dictionary>(); 295 auto pStream = pdfium::MakeUnique<CPDF_Stream>(nullptr, 0, std::move(pDict)); 296 297 // Create an empty form. 298 auto pTestForm = 299 pdfium::MakeUnique<CPDF_Form>(pDoc.get(), nullptr, pStream.get()); 300 pTestForm->ParseContent(); 301 ASSERT_TRUE(pTestForm->IsParsed()); 302 303 // The generated stream for the empty form should be an empty string. 304 CPDF_PageContentGenerator generator(pTestForm.get()); 305 std::ostringstream buf; 306 generator.ProcessPageObjects(&buf); 307 EXPECT_EQ("", ByteString(buf)); 308} 309 310TEST_F(CPDF_PageContentGeneratorTest, ProcessFormWithPath) { 311 auto pDoc = pdfium::MakeUnique<CPDF_Document>(nullptr); 312 pDoc->CreateNewDoc(); 313 auto pDict = pdfium::MakeUnique<CPDF_Dictionary>(); 314 const char content[] = 315 "q 1 0 0 1 0 0 cm 3.102 4.67 m 5.45 0.29 l 4.24 3.15 4.65 2.98 3.456 " 316 "0.24 c 3.102 4.67 l h f Q\n"; 317 size_t buf_len = FX_ArraySize(content); 318 std::unique_ptr<uint8_t, FxFreeDeleter> buf(FX_Alloc(uint8_t, buf_len)); 319 memcpy(buf.get(), content, buf_len); 320 auto pStream = pdfium::MakeUnique<CPDF_Stream>(std::move(buf), buf_len, 321 std::move(pDict)); 322 323 // Create a form with a non-empty stream. 324 auto pTestForm = 325 pdfium::MakeUnique<CPDF_Form>(pDoc.get(), nullptr, pStream.get()); 326 pTestForm->ParseContent(); 327 ASSERT_TRUE(pTestForm->IsParsed()); 328 329 CPDF_PageContentGenerator generator(pTestForm.get()); 330 std::ostringstream process_buf; 331 generator.ProcessPageObjects(&process_buf); 332 EXPECT_EQ(content, ByteString(process_buf)); 333} 334