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