blob: b5d38228d7b0edf154b169836377346e9b3b99fd [file] [log] [blame]
// 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,
const ByteString& type,
const ByteString& name) {
RetainPtr<const CPDF_Dictionary> pResources =
pGen->m_pObjHolder->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 0 .7019608 .34901962 rg 0 .7019608 .34901962 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 0 .7019608 .34901962 rg 0 .7019608 .34901962 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 pDoc = std::make_unique<CPDF_TestDocument>();
pDoc->CreateNewDoc();
RetainPtr<CPDF_Dictionary> pPageDict(pDoc->CreateNewPage(0));
auto pTestPage = pdfium::MakeRetain<CPDF_Page>(pDoc.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 expected_string_start =
"q .50196081 .7019608 .34901962 rg 1 .90196079 0 RG /";
const ByteStringView expected_string_end = " gs 1 2 m 3 4 l 5 6 l h B Q\n";
const size_t expected_string_min_length =
expected_string_start.GetLength() + expected_string_end.GetLength();
EXPECT_EQ(expected_string_start,
path_string.First(expected_string_start.GetLength()));
EXPECT_EQ(expected_string_end,
path_string.Last(expected_string_end.GetLength()));
ASSERT_GT(path_string.GetLength(), expected_string_min_length);
RetainPtr<const CPDF_Dictionary> external_gs = TestGetResource(
&generator, "ExtGState",
path_string.Substr(expected_string_start.GetLength(),
path_string.GetLength() - expected_string_min_length));
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 expected_string_start2 =
"q .50196081 .7019608 .34901962 rg 1 .90196079 0 RG 10.5 w /";
ByteString path_string2(buf);
EXPECT_EQ(expected_string_start2,
path_string2.First(expected_string_start2.GetLength()));
EXPECT_EQ(expected_string_end,
path_string2.Last(expected_string_end.GetLength()));
// Compare with the previous (should use same dictionary for gs)
const size_t expected_strings_length_difference =
expected_string_start2.GetLength() - expected_string_start.GetLength();
EXPECT_EQ(path_string.GetLength() + expected_strings_length_difference,
path_string2.GetLength());
EXPECT_EQ(
path_string.Substr(expected_string_start.GetLength(),
path_string.GetLength() - expected_string_min_length),
path_string2.Substr(expected_string_start2.GetLength(),
path_string2.GetLength() -
expected_string_min_length -
expected_strings_length_difference));
}
TEST_F(CPDFPageContentGeneratorTest, ProcessStandardText) {
// Checking font whose font dictionary is not yet indirect object.
auto pDoc = std::make_unique<CPDF_TestDocument>();
pDoc->CreateNewDoc();
RetainPtr<CPDF_Dictionary> pPageDict(pDoc->CreateNewPage(0));
auto pTestPage = pdfium::MakeRetain<CPDF_Page>(pDoc.get(), pPageDict);
CPDF_PageContentGenerator generator(pTestPage.Get());
auto pTextObj = std::make_unique<CPDF_TextObject>();
pTextObj->mutable_text_state().SetFont(
CPDF_Font::GetStockFont(pDoc.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.Last(text_string.GetLength() - second_resource_at.value());
// q and Q must be outside the BT .. ET operations
ByteString compare_string1 =
"q .50196081 .7019608 .34901962 rg 1 .90196079 0 RG /";
// Color RGB values used are integers divided by 255.
ByteString compare_string2 = " gs BT 1 0 0 1 100 100 Tm /";
ByteString compare_string3 = " 10 Tf 0 Tr <48656C6C6F20576F726C64> Tj ET Q\n";
EXPECT_LT(compare_string1.GetLength() + compare_string2.GetLength() +
compare_string3.GetLength(),
text_string.GetLength());
EXPECT_EQ(compare_string1, first_string.First(compare_string1.GetLength()));
EXPECT_EQ(compare_string2, mid_string.Last(compare_string2.GetLength()));
EXPECT_EQ(compare_string3, last_string.Last(compare_string3.GetLength()));
RetainPtr<const CPDF_Dictionary> external_gs = TestGetResource(
&generator, "ExtGState",
mid_string.First(mid_string.GetLength() - compare_string2.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.First(last_string.GetLength() - compare_string3.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 pDoc = std::make_unique<CPDF_TestDocument>();
pDoc->CreateNewDoc();
RetainPtr<CPDF_Dictionary> pPageDict(pDoc->CreateNewPage(0));
auto pTestPage = pdfium::MakeRetain<CPDF_Page>(pDoc.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 pDict = pDoc->NewIndirect<CPDF_Dictionary>();
pDict->SetNewFor<CPDF_Name>("Type", "Font");
pDict->SetNewFor<CPDF_Name>("Subtype", "TrueType");
RetainPtr<CPDF_Font> pFont = CPDF_Font::GetStockFont(pDoc.get(), "Arial");
pDict->SetNewFor<CPDF_Name>("BaseFont", pFont->GetBaseFontName());
auto pDesc = pDoc->NewIndirect<CPDF_Dictionary>();
pDesc->SetNewFor<CPDF_Name>("Type", "FontDescriptor");
pDesc->SetNewFor<CPDF_Name>("FontName", pFont->GetBaseFontName());
pDict->SetNewFor<CPDF_Reference>("FontDescriptor", pDoc.get(),
pDesc->GetObjNum());
pTextObj->mutable_text_state().SetFont(
CPDF_DocPageData::FromDocument(pDoc.get())->GetFont(pDict));
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.Last(text_string.GetLength() - 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.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 pDoc = std::make_unique<CPDF_TestDocument>();
pDoc->CreateNewDoc();
auto pStream =
pdfium::MakeRetain<CPDF_Stream>(pdfium::MakeRetain<CPDF_Dictionary>());
// Create an empty form.
auto pTestForm = std::make_unique<CPDF_Form>(pDoc.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;
generator.ProcessPageObjects(&buf);
EXPECT_EQ("", ByteString(buf));
}
TEST_F(CPDFPageContentGeneratorTest, ProcessFormWithPath) {
auto pDoc = std::make_unique<CPDF_TestDocument>();
pDoc->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>(pDoc.get(), nullptr, pStream);
pTestForm->ParseContent();
ASSERT_EQ(CPDF_PageObjectHolder::ParseState::kParsed,
pTestForm->GetParseState());
CPDF_PageContentGenerator generator(pTestForm.get());
fxcrt::ostringstream process_buf;
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 .24000001 c 3.102 4.6700001 l h f Q\n",
ByteString(process_buf));
}