| // Copyright 2017 The PDFium Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "core/fpdfapi/edit/cpdf_pagecontentgenerator.h" | |
| #include <iterator> | |
| #include <memory> | |
| #include <utility> | |
| #include "core/fpdfapi/font/cpdf_font.h" | |
| #include "core/fpdfapi/page/cpdf_colorspace.h" | |
| #include "core/fpdfapi/page/cpdf_docpagedata.h" | |
| #include "core/fpdfapi/page/cpdf_form.h" | |
| #include "core/fpdfapi/page/cpdf_page.h" | |
| #include "core/fpdfapi/page/cpdf_pathobject.h" | |
| #include "core/fpdfapi/page/cpdf_textobject.h" | |
| #include "core/fpdfapi/page/cpdf_textstate.h" | |
| #include "core/fpdfapi/page/test_with_page_module.h" | |
| #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_parser.h" | |
| #include "core/fpdfapi/parser/cpdf_reference.h" | |
| #include "core/fpdfapi/parser/cpdf_stream.h" | |
| #include "core/fpdfapi/parser/cpdf_test_document.h" | |
| #include "core/fxcrt/data_vector.h" | |
| #include "core/fxge/cfx_fillrenderoptions.h" | |
| #include "testing/gtest/include/gtest/gtest.h" | |
| class CPDFPageContentGeneratorTest : public TestWithPageModule { | |
| protected: | |
| void TestProcessPath(CPDF_PageContentGenerator* pGen, | |
| fxcrt::ostringstream* buf, | |
| CPDF_PathObject* pPathObj) { | |
| pGen->ProcessPath(buf, pPathObj); | |
| } | |
| RetainPtr<const CPDF_Dictionary> TestGetResource( | |
| CPDF_PageContentGenerator* pGen, | |
| ByteStringView type, | |
| ByteStringView name) { | |
| RetainPtr<const CPDF_Dictionary> pResources = | |
| pGen->obj_holder_->GetResources(); | |
| return pResources->GetDictFor(type)->GetDictFor(name); | |
| } | |
| void TestProcessText(CPDF_PageContentGenerator* pGen, | |
| fxcrt::ostringstream* buf, | |
| CPDF_TextObject* pTextObj) { | |
| pGen->ProcessText(buf, pTextObj); | |
| } | |
| }; | |
| TEST_F(CPDFPageContentGeneratorTest, ProcessRect) { | |
| auto pPathObj = std::make_unique<CPDF_PathObject>(); | |
| pPathObj->set_stroke(true); | |
| pPathObj->set_filltype(CFX_FillRenderOptions::FillType::kEvenOdd); | |
| pPathObj->path().AppendRect(10, 5, 13, 30); | |
| auto dummy_page_dict = pdfium::MakeRetain<CPDF_Dictionary>(); | |
| auto pTestPage = pdfium::MakeRetain<CPDF_Page>(nullptr, dummy_page_dict); | |
| CPDF_PageContentGenerator generator(pTestPage.Get()); | |
| fxcrt::ostringstream buf; | |
| TestProcessPath(&generator, &buf, pPathObj.get()); | |
| EXPECT_EQ("q 10 5 3 25 re B* Q\n", ByteString(buf)); | |
| pPathObj = std::make_unique<CPDF_PathObject>(); | |
| pPathObj->path().AppendPoint(CFX_PointF(0, 0), CFX_Path::Point::Type::kMove); | |
| pPathObj->path().AppendPoint(CFX_PointF(5.2f, 0), | |
| CFX_Path::Point::Type::kLine); | |
| pPathObj->path().AppendPoint(CFX_PointF(5.2f, 3.78f), | |
| CFX_Path::Point::Type::kLine); | |
| pPathObj->path().AppendPointAndClose(CFX_PointF(0, 3.78f), | |
| CFX_Path::Point::Type::kLine); | |
| buf.str(""); | |
| TestProcessPath(&generator, &buf, pPathObj.get()); | |
| EXPECT_EQ("q 0 0 5.1999998 3.78 re n Q\n", ByteString(buf)); | |
| } | |
| TEST_F(CPDFPageContentGeneratorTest, Bug937) { | |
| static const std::vector<float> rgb = {0.000000000000000000001f, 0.7f, 0.35f}; | |
| RetainPtr<CPDF_ColorSpace> pCS = | |
| CPDF_ColorSpace::GetStockCS(CPDF_ColorSpace::Family::kDeviceRGB); | |
| { | |
| auto pPathObj = std::make_unique<CPDF_PathObject>(); | |
| pPathObj->set_filltype(CFX_FillRenderOptions::FillType::kWinding); | |
| // Test code in ProcessPath that generates re operator | |
| pPathObj->path().AppendRect(0.000000000000000000001, | |
| 0.000000000000000000001, 100, 100); | |
| pPathObj->mutable_color_state().SetFillColor(pCS, rgb); | |
| pPathObj->mutable_color_state().SetStrokeColor(pCS, rgb); | |
| pPathObj->mutable_graph_state().SetLineWidth(200000000000000000001.0); | |
| pPathObj->Transform(CFX_Matrix(1, 0, 0, 1, 0.000000000000000000001, | |
| 200000000000000.000002)); | |
| auto dummy_page_dict = pdfium::MakeRetain<CPDF_Dictionary>(); | |
| auto pTestPage = pdfium::MakeRetain<CPDF_Page>(nullptr, dummy_page_dict); | |
| CPDF_PageContentGenerator generator(pTestPage.Get()); | |
| fxcrt::ostringstream buf; | |
| TestProcessPath(&generator, &buf, pPathObj.get()); | |
| EXPECT_EQ( | |
| "q .00000000000000000000099999997 .69999999 .34999999 rg " | |
| ".00000000000000000000099999997 .69999999 .34999999 RG " | |
| "200000000000000000000 w 1 0 0 1 .00000000000000000000099999997 " | |
| "200000000000000 cm .00000000000000000000099999997 " | |
| ".00000000000000000000099999997 100 100 re f Q\n", | |
| ByteString(buf)); | |
| } | |
| { | |
| // Test code in ProcessPath that handles bezier operator | |
| auto pPathObj = std::make_unique<CPDF_PathObject>(); | |
| pPathObj->mutable_color_state().SetFillColor(pCS, rgb); | |
| pPathObj->mutable_color_state().SetStrokeColor(pCS, rgb); | |
| pPathObj->mutable_graph_state().SetLineWidth(2.000000000000000000001); | |
| pPathObj->Transform(CFX_Matrix(1, 0, 0, 1, 432, 500000000000000.000002)); | |
| pPathObj->set_filltype(CFX_FillRenderOptions::FillType::kWinding); | |
| pPathObj->path().AppendPoint(CFX_PointF(0.000000000000000000001f, 4.67f), | |
| CFX_Path::Point::Type::kMove); | |
| pPathObj->path().AppendPoint( | |
| CFX_PointF(0.000000000000000000001, 100000000000000.000002), | |
| CFX_Path::Point::Type::kLine); | |
| pPathObj->path().AppendPoint(CFX_PointF(0.0000000000001f, 3.15f), | |
| CFX_Path::Point::Type::kBezier); | |
| pPathObj->path().AppendPoint(CFX_PointF(3.57f, 2.98f), | |
| CFX_Path::Point::Type::kBezier); | |
| pPathObj->path().AppendPointAndClose( | |
| CFX_PointF(53.4f, 5000000000000000000.00000000000000004), | |
| CFX_Path::Point::Type::kBezier); | |
| auto dummy_page_dict = pdfium::MakeRetain<CPDF_Dictionary>(); | |
| auto pTestPage = pdfium::MakeRetain<CPDF_Page>(nullptr, dummy_page_dict); | |
| CPDF_PageContentGenerator generator(pTestPage.Get()); | |
| fxcrt::ostringstream buf; | |
| TestProcessPath(&generator, &buf, pPathObj.get()); | |
| EXPECT_EQ( | |
| "q .00000000000000000000099999997 .69999999 .34999999 rg " | |
| ".00000000000000000000099999997 .69999999 .34999999 RG 2 w 1 0 0 1 432 " | |
| "499999990000000 cm .00000000000000000000099999997 4.6700001 m " | |
| ".00000000000000000000099999997 100000000000000 l " | |
| ".000000000000099999998 3.1500001 3.5699999 2.98 53.400002 " | |
| "5000000000000000000 c h f Q\n", | |
| ByteString(buf)); | |
| } | |
| } | |
| TEST_F(CPDFPageContentGeneratorTest, ProcessPath) { | |
| auto pPathObj = std::make_unique<CPDF_PathObject>(); | |
| pPathObj->set_filltype(CFX_FillRenderOptions::FillType::kWinding); | |
| pPathObj->path().AppendPoint(CFX_PointF(3.102f, 4.67f), | |
| CFX_Path::Point::Type::kMove); | |
| pPathObj->path().AppendPoint(CFX_PointF(5.45f, 0.29f), | |
| CFX_Path::Point::Type::kLine); | |
| pPathObj->path().AppendPoint(CFX_PointF(4.24f, 3.15f), | |
| CFX_Path::Point::Type::kBezier); | |
| pPathObj->path().AppendPoint(CFX_PointF(4.65f, 2.98f), | |
| CFX_Path::Point::Type::kBezier); | |
| pPathObj->path().AppendPoint(CFX_PointF(3.456f, 0.24f), | |
| CFX_Path::Point::Type::kBezier); | |
| pPathObj->path().AppendPoint(CFX_PointF(10.6f, 11.15f), | |
| CFX_Path::Point::Type::kLine); | |
| pPathObj->path().AppendPoint(CFX_PointF(11, 12.5f), | |
| CFX_Path::Point::Type::kLine); | |
| pPathObj->path().AppendPoint(CFX_PointF(11.46f, 12.67f), | |
| CFX_Path::Point::Type::kBezier); | |
| pPathObj->path().AppendPoint(CFX_PointF(11.84f, 12.96f), | |
| CFX_Path::Point::Type::kBezier); | |
| pPathObj->path().AppendPointAndClose(CFX_PointF(12, 13.64f), | |
| CFX_Path::Point::Type::kBezier); | |
| auto dummy_page_dict = pdfium::MakeRetain<CPDF_Dictionary>(); | |
| auto pTestPage = pdfium::MakeRetain<CPDF_Page>(nullptr, dummy_page_dict); | |
| CPDF_PageContentGenerator generator(pTestPage.Get()); | |
| fxcrt::ostringstream buf; | |
| TestProcessPath(&generator, &buf, pPathObj.get()); | |
| EXPECT_EQ( | |
| "q 3.102 4.6700001 m 5.4499998 .28999999 l 4.2399998 " | |
| "3.1500001 4.6500001 2.98 3.4560001 .23999999 c 10.6000004 11.149999" | |
| "6 l 11 12.5 l 11.46 12.6700001 11.8400002 12.96 12 13.6400003 c h f" | |
| " Q\n", | |
| ByteString(buf)); | |
| } | |
| TEST_F(CPDFPageContentGeneratorTest, ProcessGraphics) { | |
| auto pPathObj = std::make_unique<CPDF_PathObject>(); | |
| pPathObj->set_stroke(true); | |
| pPathObj->set_filltype(CFX_FillRenderOptions::FillType::kWinding); | |
| pPathObj->path().AppendPoint(CFX_PointF(1, 2), CFX_Path::Point::Type::kMove); | |
| pPathObj->path().AppendPoint(CFX_PointF(3, 4), CFX_Path::Point::Type::kLine); | |
| pPathObj->path().AppendPointAndClose(CFX_PointF(5, 6), | |
| CFX_Path::Point::Type::kLine); | |
| static const std::vector<float> rgb = {0.5f, 0.7f, 0.35f}; | |
| RetainPtr<CPDF_ColorSpace> pCS = | |
| CPDF_ColorSpace::GetStockCS(CPDF_ColorSpace::Family::kDeviceRGB); | |
| pPathObj->mutable_color_state().SetFillColor(pCS, rgb); | |
| static const std::vector<float> rgb2 = {1, 0.9f, 0}; | |
| pPathObj->mutable_color_state().SetStrokeColor(pCS, rgb2); | |
| pPathObj->mutable_general_state().SetFillAlpha(0.5f); | |
| pPathObj->mutable_general_state().SetStrokeAlpha(0.8f); | |
| auto doc = std::make_unique<CPDF_TestDocument>(); | |
| doc->CreateNewDoc(); | |
| RetainPtr<CPDF_Dictionary> pPageDict(doc->CreateNewPage(0)); | |
| auto pTestPage = pdfium::MakeRetain<CPDF_Page>(doc.get(), pPageDict); | |
| CPDF_PageContentGenerator generator(pTestPage.Get()); | |
| fxcrt::ostringstream buf; | |
| TestProcessPath(&generator, &buf, pPathObj.get()); | |
| ByteString path_string(buf); | |
| // Color RGB values used are integers divided by 255. | |
| const ByteStringView kExpectedStringStart = | |
| "q .5 .69999999 .34999999 rg 1 .89999998 0 RG /"; | |
| const ByteStringView kExpectedStringEnd = " gs 1 2 m 3 4 l 5 6 l h B Q\n"; | |
| const size_t kExpectedStringMinLength = | |
| kExpectedStringStart.GetLength() + kExpectedStringEnd.GetLength(); | |
| EXPECT_EQ(kExpectedStringStart, | |
| path_string.First(kExpectedStringStart.GetLength())); | |
| EXPECT_EQ(kExpectedStringEnd, | |
| path_string.Last(kExpectedStringEnd.GetLength())); | |
| ASSERT_GT(path_string.GetLength(), kExpectedStringMinLength); | |
| RetainPtr<const CPDF_Dictionary> external_gs = | |
| TestGetResource(&generator, "ExtGState", | |
| path_string.AsStringView().Substr( | |
| kExpectedStringStart.GetLength(), | |
| path_string.GetLength() - kExpectedStringMinLength)); | |
| ASSERT_TRUE(external_gs); | |
| EXPECT_EQ(0.5f, external_gs->GetFloatFor("ca")); | |
| EXPECT_EQ(0.8f, external_gs->GetFloatFor("CA")); | |
| // Same path, now with a stroke. | |
| pPathObj->mutable_graph_state().SetLineWidth(10.5f); | |
| buf.str(""); | |
| TestProcessPath(&generator, &buf, pPathObj.get()); | |
| const ByteStringView kExpectedStringStart2 = | |
| "q .5 .69999999 .34999999 rg 1 .89999998 0 RG 10.5 w /"; | |
| ByteString path_string2(buf); | |
| EXPECT_EQ(kExpectedStringStart2, | |
| path_string2.First(kExpectedStringStart2.GetLength())); | |
| EXPECT_EQ(kExpectedStringEnd, | |
| path_string2.Last(kExpectedStringEnd.GetLength())); | |
| // Compare with the previous (should use same dictionary for gs) | |
| const size_t kExpectedStringsLengthDifference = | |
| kExpectedStringStart2.GetLength() - kExpectedStringStart.GetLength(); | |
| EXPECT_EQ(path_string.GetLength() + kExpectedStringsLengthDifference, | |
| path_string2.GetLength()); | |
| EXPECT_EQ( | |
| path_string.Substr(kExpectedStringStart.GetLength(), | |
| path_string.GetLength() - kExpectedStringMinLength), | |
| path_string2.Substr(kExpectedStringStart2.GetLength(), | |
| path_string2.GetLength() - kExpectedStringMinLength - | |
| kExpectedStringsLengthDifference)); | |
| } | |
| TEST_F(CPDFPageContentGeneratorTest, ProcessStandardText) { | |
| // Checking font whose font dictionary is not yet indirect object. | |
| auto doc = std::make_unique<CPDF_TestDocument>(); | |
| doc->CreateNewDoc(); | |
| RetainPtr<CPDF_Dictionary> pPageDict(doc->CreateNewPage(0)); | |
| auto pTestPage = pdfium::MakeRetain<CPDF_Page>(doc.get(), pPageDict); | |
| CPDF_PageContentGenerator generator(pTestPage.Get()); | |
| auto pTextObj = std::make_unique<CPDF_TextObject>(); | |
| pTextObj->mutable_text_state().SetFont( | |
| CPDF_Font::GetStockFont(doc.get(), "Times-Roman")); | |
| pTextObj->mutable_text_state().SetFontSize(10.0f); | |
| static const std::vector<float> rgb = {0.5f, 0.7f, 0.35f}; | |
| RetainPtr<CPDF_ColorSpace> pCS = | |
| CPDF_ColorSpace::GetStockCS(CPDF_ColorSpace::Family::kDeviceRGB); | |
| pTextObj->mutable_color_state().SetFillColor(pCS, rgb); | |
| static const std::vector<float> rgb2 = {1, 0.9f, 0}; | |
| pTextObj->mutable_color_state().SetStrokeColor(pCS, rgb2); | |
| pTextObj->mutable_general_state().SetFillAlpha(0.5f); | |
| pTextObj->mutable_general_state().SetStrokeAlpha(0.8f); | |
| pTextObj->Transform(CFX_Matrix(1, 0, 0, 1, 100, 100)); | |
| pTextObj->SetText("Hello World"); | |
| fxcrt::ostringstream buf; | |
| TestProcessText(&generator, &buf, pTextObj.get()); | |
| ByteString text_string(buf); | |
| auto first_resource_at = text_string.Find('/'); | |
| ASSERT_TRUE(first_resource_at.has_value()); | |
| first_resource_at = first_resource_at.value() + 1; | |
| auto second_resource_at = text_string.ReverseFind('/'); | |
| ASSERT_TRUE(second_resource_at.has_value()); | |
| second_resource_at = second_resource_at.value() + 1; | |
| ByteString first_string = text_string.First(first_resource_at.value()); | |
| ByteString mid_string = text_string.Substr( | |
| first_resource_at.value(), | |
| second_resource_at.value() - first_resource_at.value()); | |
| ByteString last_string = text_string.Substr(second_resource_at.value()); | |
| // q and Q must be outside the BT .. ET operations | |
| const ByteString kCompareString1 = | |
| "q .5 .69999999 .34999999 rg 1 .89999998 0 RG /"; | |
| // Color RGB values used are integers divided by 255. | |
| const ByteString kCompareString2 = " gs BT 1 0 0 1 100 100 Tm /"; | |
| const ByteString kCompareString3 = | |
| " 10 Tf 0 Tr [<48656C6C6F20576F726C64>] TJ ET Q\n"; | |
| EXPECT_LT(kCompareString1.GetLength() + kCompareString2.GetLength() + | |
| kCompareString3.GetLength(), | |
| text_string.GetLength()); | |
| EXPECT_EQ(kCompareString1, first_string.First(kCompareString1.GetLength())); | |
| EXPECT_EQ(kCompareString2, mid_string.Last(kCompareString2.GetLength())); | |
| EXPECT_EQ(kCompareString3, last_string.Last(kCompareString3.GetLength())); | |
| RetainPtr<const CPDF_Dictionary> external_gs = TestGetResource( | |
| &generator, "ExtGState", | |
| mid_string.AsStringView().First(mid_string.GetLength() - | |
| kCompareString2.GetLength())); | |
| ASSERT_TRUE(external_gs); | |
| EXPECT_EQ(0.5f, external_gs->GetFloatFor("ca")); | |
| EXPECT_EQ(0.8f, external_gs->GetFloatFor("CA")); | |
| RetainPtr<const CPDF_Dictionary> font_dict = TestGetResource( | |
| &generator, "Font", | |
| last_string.AsStringView().First(last_string.GetLength() - | |
| kCompareString3.GetLength())); | |
| ASSERT_TRUE(font_dict); | |
| EXPECT_EQ("Font", font_dict->GetNameFor("Type")); | |
| EXPECT_EQ("Type1", font_dict->GetNameFor("Subtype")); | |
| EXPECT_EQ("Times-Roman", font_dict->GetNameFor("BaseFont")); | |
| } | |
| TEST_F(CPDFPageContentGeneratorTest, ProcessText) { | |
| // Checking font whose font dictionary is already an indirect object. | |
| auto doc = std::make_unique<CPDF_TestDocument>(); | |
| doc->CreateNewDoc(); | |
| RetainPtr<CPDF_Dictionary> pPageDict(doc->CreateNewPage(0)); | |
| auto pTestPage = pdfium::MakeRetain<CPDF_Page>(doc.get(), pPageDict); | |
| CPDF_PageContentGenerator generator(pTestPage.Get()); | |
| fxcrt::ostringstream buf; | |
| { | |
| // Set the text object font and text | |
| auto pTextObj = std::make_unique<CPDF_TextObject>(); | |
| auto dict = doc->NewIndirect<CPDF_Dictionary>(); | |
| dict->SetNewFor<CPDF_Name>("Type", "Font"); | |
| dict->SetNewFor<CPDF_Name>("Subtype", "TrueType"); | |
| RetainPtr<CPDF_Font> font = CPDF_Font::GetStockFont(doc.get(), "Arial"); | |
| dict->SetNewFor<CPDF_Name>("BaseFont", font->GetBaseFontName()); | |
| auto pDesc = doc->NewIndirect<CPDF_Dictionary>(); | |
| pDesc->SetNewFor<CPDF_Name>("Type", "FontDescriptor"); | |
| pDesc->SetNewFor<CPDF_Name>("FontName", font->GetBaseFontName()); | |
| dict->SetNewFor<CPDF_Reference>("FontDescriptor", doc.get(), | |
| pDesc->GetObjNum()); | |
| pTextObj->mutable_text_state().SetFont( | |
| CPDF_DocPageData::FromDocument(doc.get())->GetFont(dict)); | |
| pTextObj->mutable_text_state().SetFontSize(15.5f); | |
| pTextObj->SetText("I am indirect"); | |
| pTextObj->SetTextRenderMode(TextRenderingMode::MODE_FILL_CLIP); | |
| // Add a clipping path. | |
| auto pPath = std::make_unique<CPDF_Path>(); | |
| pPath->AppendPoint(CFX_PointF(0, 0), CFX_Path::Point::Type::kMove); | |
| pPath->AppendPoint(CFX_PointF(5, 0), CFX_Path::Point::Type::kLine); | |
| pPath->AppendPoint(CFX_PointF(5, 4), CFX_Path::Point::Type::kLine); | |
| pPath->AppendPointAndClose(CFX_PointF(0, 4), CFX_Path::Point::Type::kLine); | |
| CPDF_ClipPath& clip_path = pTextObj->mutable_clip_path(); | |
| clip_path.Emplace(); | |
| clip_path.AppendPath(*pPath, CFX_FillRenderOptions::FillType::kEvenOdd); | |
| TestProcessText(&generator, &buf, pTextObj.get()); | |
| } | |
| ByteString text_string(buf); | |
| auto first_resource_at = text_string.Find('/'); | |
| ASSERT_TRUE(first_resource_at.has_value()); | |
| first_resource_at = first_resource_at.value() + 1; | |
| ByteString first_string = text_string.First(first_resource_at.value()); | |
| ByteString last_string = text_string.Substr(first_resource_at.value()); | |
| // q and Q must be outside the BT .. ET operations | |
| ByteString compare_string1 = "q 0 0 5 4 re W* n BT /"; | |
| ByteString compare_string2 = | |
| " 15.5 Tf 4 Tr [<4920616D20696E646972656374>] TJ ET Q\n"; | |
| EXPECT_LT(compare_string1.GetLength() + compare_string2.GetLength(), | |
| text_string.GetLength()); | |
| EXPECT_EQ(compare_string1, text_string.First(compare_string1.GetLength())); | |
| EXPECT_EQ(compare_string2, text_string.Last(compare_string2.GetLength())); | |
| RetainPtr<const CPDF_Dictionary> font_dict = TestGetResource( | |
| &generator, "Font", | |
| text_string.AsStringView().Substr(compare_string1.GetLength(), | |
| text_string.GetLength() - | |
| compare_string1.GetLength() - | |
| compare_string2.GetLength())); | |
| ASSERT_TRUE(font_dict); | |
| EXPECT_TRUE(font_dict->GetObjNum()); | |
| EXPECT_EQ("Font", font_dict->GetNameFor("Type")); | |
| EXPECT_EQ("TrueType", font_dict->GetNameFor("Subtype")); | |
| EXPECT_EQ("Helvetica", font_dict->GetNameFor("BaseFont")); | |
| RetainPtr<const CPDF_Dictionary> font_desc = | |
| font_dict->GetDictFor("FontDescriptor"); | |
| ASSERT_TRUE(font_desc); | |
| EXPECT_TRUE(font_desc->GetObjNum()); | |
| EXPECT_EQ("FontDescriptor", font_desc->GetNameFor("Type")); | |
| EXPECT_EQ("Helvetica", font_desc->GetNameFor("FontName")); | |
| } | |
| TEST_F(CPDFPageContentGeneratorTest, ProcessEmptyForm) { | |
| auto doc = std::make_unique<CPDF_TestDocument>(); | |
| doc->CreateNewDoc(); | |
| auto pStream = | |
| pdfium::MakeRetain<CPDF_Stream>(pdfium::MakeRetain<CPDF_Dictionary>()); | |
| // Create an empty form. | |
| auto pTestForm = std::make_unique<CPDF_Form>(doc.get(), nullptr, pStream); | |
| pTestForm->ParseContent(); | |
| ASSERT_EQ(CPDF_PageObjectHolder::ParseState::kParsed, | |
| pTestForm->GetParseState()); | |
| // The generated stream for the empty form should be an empty string. | |
| CPDF_PageContentGenerator generator(pTestForm.get()); | |
| fxcrt::ostringstream buf; | |
| EXPECT_FALSE(generator.ProcessPageObjects(&buf)); | |
| EXPECT_EQ("", ByteString(buf)); | |
| } | |
| TEST_F(CPDFPageContentGeneratorTest, ProcessFormWithPath) { | |
| auto doc = std::make_unique<CPDF_TestDocument>(); | |
| doc->CreateNewDoc(); | |
| static constexpr uint8_t kContents[] = | |
| "q 3.102 4.6700001 m 5.4500012 .28999999 " | |
| "l 4.2399998 3.1499999 4.65 2.98 3.456 0.24 c 3.102 4.6700001 l h f Q\n"; | |
| auto pStream = pdfium::MakeRetain<CPDF_Stream>( | |
| DataVector<uint8_t>(std::begin(kContents), std::end(kContents)), | |
| pdfium::MakeRetain<CPDF_Dictionary>()); | |
| // Create a form with a non-empty stream. | |
| auto pTestForm = std::make_unique<CPDF_Form>(doc.get(), nullptr, pStream); | |
| pTestForm->ParseContent(); | |
| ASSERT_EQ(CPDF_PageObjectHolder::ParseState::kParsed, | |
| pTestForm->GetParseState()); | |
| CPDF_PageContentGenerator generator(pTestForm.get()); | |
| fxcrt::ostringstream process_buf; | |
| ASSERT_TRUE(generator.ProcessPageObjects(&process_buf)); | |
| EXPECT_EQ( | |
| "q 3.102 4.6700001 m 5.4500012 .28999999 l 4.2399998 3.14" | |
| "99999 4.6500001 2.98 3.4560001 .23999999 c 3.102 4.6700001 l h f Q\n", | |
| ByteString(process_buf)); | |
| } | |
| TEST_F(CPDFPageContentGeneratorTest, ProcessContentMarksWithProperties) { | |
| auto doc = std::make_unique<CPDF_TestDocument>(); | |
| doc->CreateNewDoc(); | |
| RetainPtr<CPDF_Dictionary> page_dict(doc->CreateNewPage(0)); | |
| auto test_page = pdfium::MakeRetain<CPDF_Page>(doc.get(), page_dict); | |
| auto path_obj = std::make_unique<CPDF_PathObject>(); | |
| path_obj->set_filltype(CFX_FillRenderOptions::FillType::kWinding); | |
| path_obj->path().AppendRect(0, 0, 10, 10); | |
| path_obj->SetDirty(true); | |
| auto marks_dict = pdfium::MakeRetain<CPDF_Dictionary>(); | |
| path_obj->GetContentMarks()->AddMarkWithPropertiesHolder( | |
| "M1", marks_dict, "Property Name With Space"); | |
| test_page->AppendPageObject(std::move(path_obj)); | |
| CPDF_PageContentGenerator generator(test_page.Get()); | |
| fxcrt::ostringstream buf; | |
| EXPECT_TRUE(generator.ProcessPageObjects(&buf)); | |
| ByteString content(buf); | |
| EXPECT_TRUE(content.Contains("/M1 /Property#20Name#20With#20Space BDC")); | |
| } |