xref: /aosp_15_r20/external/pdfium/core/fpdfapi/edit/cpdf_pagecontentgenerator_unittest.cpp (revision 3ac0a46f773bac49fa9476ec2b1cf3f8da5ec3a4)
1 // Copyright 2017 The PDFium Authors
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 <iterator>
8 #include <memory>
9 #include <utility>
10 
11 #include "core/fpdfapi/font/cpdf_font.h"
12 #include "core/fpdfapi/page/cpdf_colorspace.h"
13 #include "core/fpdfapi/page/cpdf_docpagedata.h"
14 #include "core/fpdfapi/page/cpdf_form.h"
15 #include "core/fpdfapi/page/cpdf_page.h"
16 #include "core/fpdfapi/page/cpdf_pathobject.h"
17 #include "core/fpdfapi/page/cpdf_textobject.h"
18 #include "core/fpdfapi/page/cpdf_textstate.h"
19 #include "core/fpdfapi/page/test_with_page_module.h"
20 #include "core/fpdfapi/parser/cpdf_dictionary.h"
21 #include "core/fpdfapi/parser/cpdf_document.h"
22 #include "core/fpdfapi/parser/cpdf_name.h"
23 #include "core/fpdfapi/parser/cpdf_parser.h"
24 #include "core/fpdfapi/parser/cpdf_reference.h"
25 #include "core/fpdfapi/parser/cpdf_stream.h"
26 #include "core/fpdfapi/parser/cpdf_test_document.h"
27 #include "core/fxcrt/data_vector.h"
28 #include "core/fxge/cfx_fillrenderoptions.h"
29 #include "testing/gtest/include/gtest/gtest.h"
30 
31 class CPDF_PageContentGeneratorTest : public TestWithPageModule {
32  protected:
TestProcessPath(CPDF_PageContentGenerator * pGen,fxcrt::ostringstream * buf,CPDF_PathObject * pPathObj)33   void TestProcessPath(CPDF_PageContentGenerator* pGen,
34                        fxcrt::ostringstream* buf,
35                        CPDF_PathObject* pPathObj) {
36     pGen->ProcessPath(buf, pPathObj);
37   }
38 
TestGetResource(CPDF_PageContentGenerator * pGen,const ByteString & type,const ByteString & name)39   RetainPtr<const CPDF_Dictionary> TestGetResource(
40       CPDF_PageContentGenerator* pGen,
41       const ByteString& type,
42       const ByteString& name) {
43     RetainPtr<const CPDF_Dictionary> pResources =
44         pGen->m_pObjHolder->GetResources();
45     return pResources->GetDictFor(type)->GetDictFor(name);
46   }
47 
TestProcessText(CPDF_PageContentGenerator * pGen,fxcrt::ostringstream * buf,CPDF_TextObject * pTextObj)48   void TestProcessText(CPDF_PageContentGenerator* pGen,
49                        fxcrt::ostringstream* buf,
50                        CPDF_TextObject* pTextObj) {
51     pGen->ProcessText(buf, pTextObj);
52   }
53 };
54 
TEST_F(CPDF_PageContentGeneratorTest,ProcessRect)55 TEST_F(CPDF_PageContentGeneratorTest, ProcessRect) {
56   auto pPathObj = std::make_unique<CPDF_PathObject>();
57   pPathObj->set_stroke(true);
58   pPathObj->set_filltype(CFX_FillRenderOptions::FillType::kEvenOdd);
59   pPathObj->path().AppendRect(10, 5, 13, 30);
60 
61   auto dummy_page_dict = pdfium::MakeRetain<CPDF_Dictionary>();
62   auto pTestPage = pdfium::MakeRetain<CPDF_Page>(nullptr, dummy_page_dict);
63   CPDF_PageContentGenerator generator(pTestPage.Get());
64   fxcrt::ostringstream buf;
65   TestProcessPath(&generator, &buf, pPathObj.get());
66   EXPECT_EQ("q 1 0 0 1 0 0 cm 10 5 3 25 re B* Q\n", ByteString(buf));
67 
68   pPathObj = std::make_unique<CPDF_PathObject>();
69   pPathObj->path().AppendPoint(CFX_PointF(0, 0), CFX_Path::Point::Type::kMove);
70   pPathObj->path().AppendPoint(CFX_PointF(5.2f, 0),
71                                CFX_Path::Point::Type::kLine);
72   pPathObj->path().AppendPoint(CFX_PointF(5.2f, 3.78f),
73                                CFX_Path::Point::Type::kLine);
74   pPathObj->path().AppendPointAndClose(CFX_PointF(0, 3.78f),
75                                        CFX_Path::Point::Type::kLine);
76   buf.str("");
77   TestProcessPath(&generator, &buf, pPathObj.get());
78   EXPECT_EQ("q 1 0 0 1 0 0 cm 0 0 5.1999998 3.78 re n Q\n", ByteString(buf));
79 }
80 
TEST_F(CPDF_PageContentGeneratorTest,BUG_937)81 TEST_F(CPDF_PageContentGeneratorTest, BUG_937) {
82   static const std::vector<float> rgb = {0.000000000000000000001f, 0.7f, 0.35f};
83   RetainPtr<CPDF_ColorSpace> pCS =
84       CPDF_ColorSpace::GetStockCS(CPDF_ColorSpace::Family::kDeviceRGB);
85   {
86     auto pPathObj = std::make_unique<CPDF_PathObject>();
87     pPathObj->set_filltype(CFX_FillRenderOptions::FillType::kWinding);
88 
89     // Test code in ProcessPath that generates re operator
90     pPathObj->path().AppendRect(0.000000000000000000001,
91                                 0.000000000000000000001, 100, 100);
92 
93     pPathObj->m_ColorState.SetFillColor(pCS, rgb);
94     pPathObj->m_ColorState.SetStrokeColor(pCS, rgb);
95     pPathObj->m_GraphState.SetLineWidth(200000000000000000001.0);
96     pPathObj->Transform(CFX_Matrix(1, 0, 0, 1, 0.000000000000000000001,
97                                    200000000000000.000002));
98 
99     auto dummy_page_dict = pdfium::MakeRetain<CPDF_Dictionary>();
100     auto pTestPage = pdfium::MakeRetain<CPDF_Page>(nullptr, dummy_page_dict);
101     CPDF_PageContentGenerator generator(pTestPage.Get());
102     fxcrt::ostringstream buf;
103     TestProcessPath(&generator, &buf, pPathObj.get());
104     EXPECT_EQ(
105         "q 0 0.701961 0.34902 rg 0 0.701961 0.34902 RG 200000000000000000000 w"
106         " 1 0 0 1 .00000000000000000000099999997 200000000000000 cm .000000000"
107         "00000000000099999997 .00000000000000000000099999997 100 100 re f Q\n",
108         ByteString(buf));
109   }
110 
111   {
112     // Test code in ProcessPath that handles bezier operator
113     auto pPathObj = std::make_unique<CPDF_PathObject>();
114     pPathObj->m_ColorState.SetFillColor(pCS, rgb);
115     pPathObj->m_ColorState.SetStrokeColor(pCS, rgb);
116     pPathObj->m_GraphState.SetLineWidth(2.000000000000000000001);
117     pPathObj->Transform(CFX_Matrix(1, 0, 0, 1, 432, 500000000000000.000002));
118 
119     pPathObj->set_filltype(CFX_FillRenderOptions::FillType::kWinding);
120     pPathObj->path().AppendPoint(CFX_PointF(0.000000000000000000001f, 4.67f),
121                                  CFX_Path::Point::Type::kMove);
122     pPathObj->path().AppendPoint(
123         CFX_PointF(0.000000000000000000001, 100000000000000.000002),
124         CFX_Path::Point::Type::kLine);
125     pPathObj->path().AppendPoint(CFX_PointF(0.0000000000001f, 3.15f),
126                                  CFX_Path::Point::Type::kBezier);
127     pPathObj->path().AppendPoint(CFX_PointF(3.57f, 2.98f),
128                                  CFX_Path::Point::Type::kBezier);
129     pPathObj->path().AppendPointAndClose(
130         CFX_PointF(53.4f, 5000000000000000000.00000000000000004),
131         CFX_Path::Point::Type::kBezier);
132     auto dummy_page_dict = pdfium::MakeRetain<CPDF_Dictionary>();
133     auto pTestPage = pdfium::MakeRetain<CPDF_Page>(nullptr, dummy_page_dict);
134     CPDF_PageContentGenerator generator(pTestPage.Get());
135     fxcrt::ostringstream buf;
136 
137     TestProcessPath(&generator, &buf, pPathObj.get());
138     EXPECT_EQ(
139         "q 0 0.701961 0.34902 rg 0 0.701961 0.34902 RG 2 w 1 0 0 1 432 4999999"
140         "90000000 cm .00000000000000000000099999997 4.6700001 m .0000000000000"
141         "0000000099999997 100000000000000 l .000000000000099999998 3.1500001 3"
142         ".5699999 2.98 53.400002 5000000000000000000 c h f Q\n",
143         ByteString(buf));
144   }
145 }
146 
TEST_F(CPDF_PageContentGeneratorTest,ProcessPath)147 TEST_F(CPDF_PageContentGeneratorTest, ProcessPath) {
148   auto pPathObj = std::make_unique<CPDF_PathObject>();
149   pPathObj->set_filltype(CFX_FillRenderOptions::FillType::kWinding);
150   pPathObj->path().AppendPoint(CFX_PointF(3.102f, 4.67f),
151                                CFX_Path::Point::Type::kMove);
152   pPathObj->path().AppendPoint(CFX_PointF(5.45f, 0.29f),
153                                CFX_Path::Point::Type::kLine);
154   pPathObj->path().AppendPoint(CFX_PointF(4.24f, 3.15f),
155                                CFX_Path::Point::Type::kBezier);
156   pPathObj->path().AppendPoint(CFX_PointF(4.65f, 2.98f),
157                                CFX_Path::Point::Type::kBezier);
158   pPathObj->path().AppendPoint(CFX_PointF(3.456f, 0.24f),
159                                CFX_Path::Point::Type::kBezier);
160   pPathObj->path().AppendPoint(CFX_PointF(10.6f, 11.15f),
161                                CFX_Path::Point::Type::kLine);
162   pPathObj->path().AppendPoint(CFX_PointF(11, 12.5f),
163                                CFX_Path::Point::Type::kLine);
164   pPathObj->path().AppendPoint(CFX_PointF(11.46f, 12.67f),
165                                CFX_Path::Point::Type::kBezier);
166   pPathObj->path().AppendPoint(CFX_PointF(11.84f, 12.96f),
167                                CFX_Path::Point::Type::kBezier);
168   pPathObj->path().AppendPointAndClose(CFX_PointF(12, 13.64f),
169                                        CFX_Path::Point::Type::kBezier);
170 
171   auto dummy_page_dict = pdfium::MakeRetain<CPDF_Dictionary>();
172   auto pTestPage = pdfium::MakeRetain<CPDF_Page>(nullptr, dummy_page_dict);
173   CPDF_PageContentGenerator generator(pTestPage.Get());
174   fxcrt::ostringstream buf;
175   TestProcessPath(&generator, &buf, pPathObj.get());
176   EXPECT_EQ(
177       "q 1 0 0 1 0 0 cm 3.102 4.6700001 m 5.4499998 .28999999 l 4.2399998 "
178       "3.1500001 4.6500001 2.98 3.4560001 .23999999 c 10.6000004 11.149999"
179       "6 l 11 12.5 l 11.46 12.6700001 11.8400002 12.96 12 13.6400003 c h f"
180       " Q\n",
181       ByteString(buf));
182 }
183 
TEST_F(CPDF_PageContentGeneratorTest,ProcessGraphics)184 TEST_F(CPDF_PageContentGeneratorTest, ProcessGraphics) {
185   auto pPathObj = std::make_unique<CPDF_PathObject>();
186   pPathObj->set_stroke(true);
187   pPathObj->set_filltype(CFX_FillRenderOptions::FillType::kWinding);
188   pPathObj->path().AppendPoint(CFX_PointF(1, 2), CFX_Path::Point::Type::kMove);
189   pPathObj->path().AppendPoint(CFX_PointF(3, 4), CFX_Path::Point::Type::kLine);
190   pPathObj->path().AppendPointAndClose(CFX_PointF(5, 6),
191                                        CFX_Path::Point::Type::kLine);
192 
193   static const std::vector<float> rgb = {0.5f, 0.7f, 0.35f};
194   RetainPtr<CPDF_ColorSpace> pCS =
195       CPDF_ColorSpace::GetStockCS(CPDF_ColorSpace::Family::kDeviceRGB);
196   pPathObj->m_ColorState.SetFillColor(pCS, rgb);
197 
198   static const std::vector<float> rgb2 = {1, 0.9f, 0};
199   pPathObj->m_ColorState.SetStrokeColor(pCS, rgb2);
200   pPathObj->m_GeneralState.SetFillAlpha(0.5f);
201   pPathObj->m_GeneralState.SetStrokeAlpha(0.8f);
202 
203   auto pDoc = std::make_unique<CPDF_TestDocument>();
204   pDoc->CreateNewDoc();
205 
206   RetainPtr<CPDF_Dictionary> pPageDict(pDoc->CreateNewPage(0));
207   auto pTestPage = pdfium::MakeRetain<CPDF_Page>(pDoc.get(), pPageDict);
208   CPDF_PageContentGenerator generator(pTestPage.Get());
209   fxcrt::ostringstream buf;
210   TestProcessPath(&generator, &buf, pPathObj.get());
211   ByteString pathString(buf);
212 
213   // Color RGB values used are integers divided by 255.
214   EXPECT_EQ("q 0.501961 0.701961 0.34902 rg 1 0.901961 0 RG /",
215             pathString.First(48));
216   EXPECT_EQ(" gs 1 0 0 1 0 0 cm 1 2 m 3 4 l 5 6 l h B Q\n",
217             pathString.Last(43));
218   ASSERT_GT(pathString.GetLength(), 91U);
219   RetainPtr<const CPDF_Dictionary> externalGS =
220       TestGetResource(&generator, "ExtGState",
221                       pathString.Substr(48, pathString.GetLength() - 91));
222   ASSERT_TRUE(externalGS);
223   EXPECT_EQ(0.5f, externalGS->GetFloatFor("ca"));
224   EXPECT_EQ(0.8f, externalGS->GetFloatFor("CA"));
225 
226   // Same path, now with a stroke.
227   pPathObj->m_GraphState.SetLineWidth(10.5f);
228   buf.str("");
229   TestProcessPath(&generator, &buf, pPathObj.get());
230   ByteString pathString2(buf);
231   EXPECT_EQ("q 0.501961 0.701961 0.34902 rg 1 0.901961 0 RG 10.5 w /",
232             pathString2.First(55));
233   EXPECT_EQ(" gs 1 0 0 1 0 0 cm 1 2 m 3 4 l 5 6 l h B Q\n",
234             pathString2.Last(43));
235 
236   // Compare with the previous (should use same dictionary for gs)
237   EXPECT_EQ(pathString.GetLength() + 7, pathString2.GetLength());
238   EXPECT_EQ(pathString.Substr(48, pathString.GetLength() - 76),
239             pathString2.Substr(55, pathString2.GetLength() - 83));
240 }
241 
TEST_F(CPDF_PageContentGeneratorTest,ProcessStandardText)242 TEST_F(CPDF_PageContentGeneratorTest, ProcessStandardText) {
243   // Checking font whose font dictionary is not yet indirect object.
244   auto pDoc = std::make_unique<CPDF_TestDocument>();
245   pDoc->CreateNewDoc();
246 
247   RetainPtr<CPDF_Dictionary> pPageDict(pDoc->CreateNewPage(0));
248   auto pTestPage = pdfium::MakeRetain<CPDF_Page>(pDoc.get(), pPageDict);
249   CPDF_PageContentGenerator generator(pTestPage.Get());
250   auto pTextObj = std::make_unique<CPDF_TextObject>();
251   pTextObj->m_TextState.SetFont(
252       CPDF_Font::GetStockFont(pDoc.get(), "Times-Roman"));
253   pTextObj->m_TextState.SetFontSize(10.0f);
254 
255   static const std::vector<float> rgb = {0.5f, 0.7f, 0.35f};
256   RetainPtr<CPDF_ColorSpace> pCS =
257       CPDF_ColorSpace::GetStockCS(CPDF_ColorSpace::Family::kDeviceRGB);
258   pTextObj->m_ColorState.SetFillColor(pCS, rgb);
259 
260   static const std::vector<float> rgb2 = {1, 0.9f, 0};
261   pTextObj->m_ColorState.SetStrokeColor(pCS, rgb2);
262   pTextObj->m_GeneralState.SetFillAlpha(0.5f);
263   pTextObj->m_GeneralState.SetStrokeAlpha(0.8f);
264   pTextObj->Transform(CFX_Matrix(1, 0, 0, 1, 100, 100));
265   pTextObj->SetText("Hello World");
266   fxcrt::ostringstream buf;
267   TestProcessText(&generator, &buf, pTextObj.get());
268   ByteString textString(buf);
269   auto firstResourceAt = textString.Find('/');
270   ASSERT_TRUE(firstResourceAt.has_value());
271   firstResourceAt = firstResourceAt.value() + 1;
272   auto secondResourceAt = textString.ReverseFind('/');
273   ASSERT_TRUE(secondResourceAt.has_value());
274   secondResourceAt = secondResourceAt.value() + 1;
275   ByteString firstString = textString.First(firstResourceAt.value());
276   ByteString midString =
277       textString.Substr(firstResourceAt.value(),
278                         secondResourceAt.value() - firstResourceAt.value());
279   ByteString lastString =
280       textString.Last(textString.GetLength() - secondResourceAt.value());
281   // q and Q must be outside the BT .. ET operations
282   ByteString compareString1 =
283       "q 0.501961 0.701961 0.34902 rg 1 0.901961 0 RG /";
284   // Color RGB values used are integers divided by 255.
285   ByteString compareString2 = " gs BT 1 0 0 1 100 100 Tm /";
286   ByteString compareString3 = " 10 Tf 0 Tr <48656C6C6F20576F726C64> Tj ET Q\n";
287   EXPECT_LT(compareString1.GetLength() + compareString2.GetLength() +
288                 compareString3.GetLength(),
289             textString.GetLength());
290   EXPECT_EQ(compareString1, firstString.First(compareString1.GetLength()));
291   EXPECT_EQ(compareString2, midString.Last(compareString2.GetLength()));
292   EXPECT_EQ(compareString3, lastString.Last(compareString3.GetLength()));
293   RetainPtr<const CPDF_Dictionary> externalGS = TestGetResource(
294       &generator, "ExtGState",
295       midString.First(midString.GetLength() - compareString2.GetLength()));
296   ASSERT_TRUE(externalGS);
297   EXPECT_EQ(0.5f, externalGS->GetFloatFor("ca"));
298   EXPECT_EQ(0.8f, externalGS->GetFloatFor("CA"));
299   RetainPtr<const CPDF_Dictionary> fontDict = TestGetResource(
300       &generator, "Font",
301       lastString.First(lastString.GetLength() - compareString3.GetLength()));
302   ASSERT_TRUE(fontDict);
303   EXPECT_EQ("Font", fontDict->GetNameFor("Type"));
304   EXPECT_EQ("Type1", fontDict->GetNameFor("Subtype"));
305   EXPECT_EQ("Times-Roman", fontDict->GetNameFor("BaseFont"));
306 }
307 
TEST_F(CPDF_PageContentGeneratorTest,ProcessText)308 TEST_F(CPDF_PageContentGeneratorTest, ProcessText) {
309   // Checking font whose font dictionary is already an indirect object.
310   auto pDoc = std::make_unique<CPDF_TestDocument>();
311   pDoc->CreateNewDoc();
312 
313   RetainPtr<CPDF_Dictionary> pPageDict(pDoc->CreateNewPage(0));
314   auto pTestPage = pdfium::MakeRetain<CPDF_Page>(pDoc.get(), pPageDict);
315   CPDF_PageContentGenerator generator(pTestPage.Get());
316 
317   fxcrt::ostringstream buf;
318   {
319     // Set the text object font and text
320     auto pTextObj = std::make_unique<CPDF_TextObject>();
321     auto pDict = pDoc->NewIndirect<CPDF_Dictionary>();
322     pDict->SetNewFor<CPDF_Name>("Type", "Font");
323     pDict->SetNewFor<CPDF_Name>("Subtype", "TrueType");
324 
325     RetainPtr<CPDF_Font> pFont = CPDF_Font::GetStockFont(pDoc.get(), "Arial");
326     pDict->SetNewFor<CPDF_Name>("BaseFont", pFont->GetBaseFontName());
327 
328     auto pDesc = pDoc->NewIndirect<CPDF_Dictionary>();
329     pDesc->SetNewFor<CPDF_Name>("Type", "FontDescriptor");
330     pDesc->SetNewFor<CPDF_Name>("FontName", pFont->GetBaseFontName());
331     pDict->SetNewFor<CPDF_Reference>("FontDescriptor", pDoc.get(),
332                                      pDesc->GetObjNum());
333 
334     pTextObj->m_TextState.SetFont(
335         CPDF_DocPageData::FromDocument(pDoc.get())->GetFont(pDict));
336     pTextObj->m_TextState.SetFontSize(15.5f);
337     pTextObj->SetText("I am indirect");
338     pTextObj->SetTextRenderMode(TextRenderingMode::MODE_FILL_CLIP);
339 
340     // Add a clipping path.
341     auto pPath = std::make_unique<CPDF_Path>();
342     pPath->AppendPoint(CFX_PointF(0, 0), CFX_Path::Point::Type::kMove);
343     pPath->AppendPoint(CFX_PointF(5, 0), CFX_Path::Point::Type::kLine);
344     pPath->AppendPoint(CFX_PointF(5, 4), CFX_Path::Point::Type::kLine);
345     pPath->AppendPointAndClose(CFX_PointF(0, 4), CFX_Path::Point::Type::kLine);
346     pTextObj->m_ClipPath.Emplace();
347     pTextObj->m_ClipPath.AppendPath(*pPath,
348                                     CFX_FillRenderOptions::FillType::kEvenOdd);
349 
350     TestProcessText(&generator, &buf, pTextObj.get());
351   }
352 
353   ByteString textString(buf);
354   auto firstResourceAt = textString.Find('/');
355   ASSERT_TRUE(firstResourceAt.has_value());
356   firstResourceAt = firstResourceAt.value() + 1;
357   ByteString firstString = textString.First(firstResourceAt.value());
358   ByteString lastString =
359       textString.Last(textString.GetLength() - firstResourceAt.value());
360   // q and Q must be outside the BT .. ET operations
361   ByteString compareString1 = "q 0 0 5 4 re W* n BT 1 0 0 1 0 0 Tm /";
362   ByteString compareString2 =
363       " 15.5 Tf 4 Tr <4920616D20696E646972656374> Tj ET Q\n";
364   EXPECT_LT(compareString1.GetLength() + compareString2.GetLength(),
365             textString.GetLength());
366   EXPECT_EQ(compareString1, textString.First(compareString1.GetLength()));
367   EXPECT_EQ(compareString2, textString.Last(compareString2.GetLength()));
368   RetainPtr<const CPDF_Dictionary> fontDict = TestGetResource(
369       &generator, "Font",
370       textString.Substr(compareString1.GetLength(),
371                         textString.GetLength() - compareString1.GetLength() -
372                             compareString2.GetLength()));
373   ASSERT_TRUE(fontDict);
374   EXPECT_TRUE(fontDict->GetObjNum());
375   EXPECT_EQ("Font", fontDict->GetNameFor("Type"));
376   EXPECT_EQ("TrueType", fontDict->GetNameFor("Subtype"));
377   EXPECT_EQ("Helvetica", fontDict->GetNameFor("BaseFont"));
378   RetainPtr<const CPDF_Dictionary> fontDesc =
379       fontDict->GetDictFor("FontDescriptor");
380   ASSERT_TRUE(fontDesc);
381   EXPECT_TRUE(fontDesc->GetObjNum());
382   EXPECT_EQ("FontDescriptor", fontDesc->GetNameFor("Type"));
383   EXPECT_EQ("Helvetica", fontDesc->GetNameFor("FontName"));
384 }
385 
TEST_F(CPDF_PageContentGeneratorTest,ProcessEmptyForm)386 TEST_F(CPDF_PageContentGeneratorTest, ProcessEmptyForm) {
387   auto pDoc = std::make_unique<CPDF_TestDocument>();
388   pDoc->CreateNewDoc();
389   auto pStream =
390       pdfium::MakeRetain<CPDF_Stream>(pdfium::MakeRetain<CPDF_Dictionary>());
391 
392   // Create an empty form.
393   auto pTestForm = std::make_unique<CPDF_Form>(pDoc.get(), nullptr, pStream);
394   pTestForm->ParseContent();
395   ASSERT_EQ(CPDF_PageObjectHolder::ParseState::kParsed,
396             pTestForm->GetParseState());
397 
398   // The generated stream for the empty form should be an empty string.
399   CPDF_PageContentGenerator generator(pTestForm.get());
400   fxcrt::ostringstream buf;
401   generator.ProcessPageObjects(&buf);
402   EXPECT_EQ("", ByteString(buf));
403 }
404 
TEST_F(CPDF_PageContentGeneratorTest,ProcessFormWithPath)405 TEST_F(CPDF_PageContentGeneratorTest, ProcessFormWithPath) {
406   auto pDoc = std::make_unique<CPDF_TestDocument>();
407   pDoc->CreateNewDoc();
408   static constexpr uint8_t kContents[] =
409       "q 1 0 0 1 0 0 cm 3.102 4.6700001 m 5.4500012 .28999999 "
410       "l 4.2399998 3.1499999 4.65 2.98 3.456 0.24 c 3.102 4.6700001 l h f Q\n";
411   auto pStream = pdfium::MakeRetain<CPDF_Stream>(
412       DataVector<uint8_t>(std::begin(kContents), std::end(kContents)),
413       pdfium::MakeRetain<CPDF_Dictionary>());
414 
415   // Create a form with a non-empty stream.
416   auto pTestForm = std::make_unique<CPDF_Form>(pDoc.get(), nullptr, pStream);
417   pTestForm->ParseContent();
418   ASSERT_EQ(CPDF_PageObjectHolder::ParseState::kParsed,
419             pTestForm->GetParseState());
420 
421   CPDF_PageContentGenerator generator(pTestForm.get());
422   fxcrt::ostringstream process_buf;
423   generator.ProcessPageObjects(&process_buf);
424   EXPECT_STREQ(
425       "q 1 0 0 1 0 0 cm 3.102 4.6700001 m 5.4500012 .28999999 l 4.2399998 3.14"
426       "99999 4.6500001 2.98 3.4560001 .24000001 c 3.102 4.6700001 l h f Q\n",
427       ByteString(process_buf).c_str());
428 }
429