Create FDPFPageObj_AddExistingMark() CPDF_PageContentGenerator::ProcessContentMarks() already operates in such a way that consecutive page objects with the same CPDF_ContentMarkItem pointer get merged into a single BMC/EMC group. So this new API allows embedders to insert marks that span multiple page objects. Bug: 409021827, 408926609 Change-Id: I139d279f0fd59ccd6ed05d0c1c79c560bfebb153 Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/148370 Reviewed-by: Andy Phan <andyphan@chromium.org> Commit-Queue: Lei Zhang <thestig@chromium.org> Reviewed-by: Lei Zhang <thestig@chromium.org>
diff --git a/core/fpdfapi/page/cpdf_contentmarks.cpp b/core/fpdfapi/page/cpdf_contentmarks.cpp index fa81a18..77580f2 100644 --- a/core/fpdfapi/page/cpdf_contentmarks.cpp +++ b/core/fpdfapi/page/cpdf_contentmarks.cpp
@@ -49,6 +49,11 @@ mark_data_->AddMark(std::move(name)); } +void CPDF_ContentMarks::AddExistingMark(RetainPtr<CPDF_ContentMarkItem> mark) { + EnsureMarkDataExists(); + mark_data_->AddExistingMark(std::move(mark)); +} + void CPDF_ContentMarks::AddMarkWithDirectDict(ByteString name, RetainPtr<CPDF_Dictionary> dict) { EnsureMarkDataExists(); @@ -136,6 +141,11 @@ marks_.push_back(pItem); } +void CPDF_ContentMarks::MarkData::AddExistingMark( + RetainPtr<CPDF_ContentMarkItem> mark) { + marks_.push_back(std::move(mark)); +} + void CPDF_ContentMarks::MarkData::AddMarkWithDirectDict( ByteString name, RetainPtr<CPDF_Dictionary> dict) {
diff --git a/core/fpdfapi/page/cpdf_contentmarks.h b/core/fpdfapi/page/cpdf_contentmarks.h index 7a9ca10..10a7c23 100644 --- a/core/fpdfapi/page/cpdf_contentmarks.h +++ b/core/fpdfapi/page/cpdf_contentmarks.h
@@ -32,6 +32,7 @@ const CPDF_ContentMarkItem* GetItem(size_t index) const; void AddMark(ByteString name); + void AddExistingMark(RetainPtr<CPDF_ContentMarkItem> mark); void AddMarkWithDirectDict(ByteString name, RetainPtr<CPDF_Dictionary> dict); void AddMarkWithPropertiesHolder(const ByteString& name, RetainPtr<CPDF_Dictionary> dict, @@ -51,6 +52,7 @@ int GetMarkedContentID() const; void AddMark(ByteString name); + void AddExistingMark(RetainPtr<CPDF_ContentMarkItem> mark); void AddMarkWithDirectDict(ByteString name, RetainPtr<CPDF_Dictionary> dict); void AddMarkWithPropertiesHolder(const ByteString& name,
diff --git a/fpdfsdk/fpdf_edit_embeddertest.cpp b/fpdfsdk/fpdf_edit_embeddertest.cpp index 8bf352c..6e66cf7 100644 --- a/fpdfsdk/fpdf_edit_embeddertest.cpp +++ b/fpdfsdk/fpdf_edit_embeddertest.cpp
@@ -4134,6 +4134,81 @@ CheckMarkCounts(saved_page.get(), 0, 2, 0, 0, 0, 1); } +TEST_F(FPDFEditEmbedderTest, AddExistingMarkBadInputs) { + EXPECT_FALSE(FPDFPageObj_AddExistingMark(nullptr, nullptr)); + + ASSERT_TRUE(OpenDocument("hello_world.pdf")); + ScopedPage page = LoadScopedPage(0); + ASSERT_TRUE(page); + FPDF_PAGEOBJECT page_object1 = FPDFPage_GetObject(page.get(), 0); + + // Null mark should error + EXPECT_FALSE(FPDFPageObj_AddExistingMark(page_object1, nullptr)); + + FPDF_PAGEOBJECTMARK mark = FPDFPageObj_AddMark(page_object1, "Prime"); + EXPECT_TRUE(mark); + EXPECT_TRUE(FPDFPageObjMark_SetStringParam(document(), page_object1, mark, + "Test", "Hello")); + + // Null object valid mark should error + EXPECT_FALSE(FPDFPageObj_AddExistingMark(nullptr, mark)); +} + +TEST_F(FPDFEditEmbedderTest, AddExistingMarkCompressedStream) { + // Load document with some text in a compressed stream. + ASSERT_TRUE(OpenDocument("hello_world_compressed_stream.pdf")); + ScopedPage page = LoadScopedPage(0); + ASSERT_TRUE(page); + + // Render and check there are no marks. + { + ScopedFPDFBitmap page_bitmap = RenderPage(page.get()); + CompareBitmapWithExpectationSuffix(page_bitmap.get(), kHelloWorldPng); + } + CheckMarkCounts(page.get(), 0, 2, 0, 0, 0, 0); + + // Add a mark to the first page object + FPDF_PAGEOBJECT page_object1 = FPDFPage_GetObject(page.get(), 0); + FPDF_PAGEOBJECTMARK mark = FPDFPageObj_AddMark(page_object1, "Prime"); + EXPECT_TRUE(mark); + EXPECT_TRUE(FPDFPageObjMark_SetStringParam(document(), page_object1, mark, + "Test", "Hello")); + + // Render and check there is 1 mark. + { + ScopedFPDFBitmap page_bitmap = RenderPage(page.get()); + CompareBitmapWithExpectationSuffix(page_bitmap.get(), kHelloWorldPng); + } + CheckMarkCounts(page.get(), 0, 2, 1, 0, 0, 0); + + // Add the same bounds mark to the second object. + FPDF_PAGEOBJECT page_object2 = FPDFPage_GetObject(page.get(), 1); + EXPECT_TRUE(FPDFPageObj_AddExistingMark(page_object2, mark)); + + // Render and check there are 2 marks. + { + ScopedFPDFBitmap page_bitmap = RenderPage(page.get()); + CompareBitmapWithExpectationSuffix(page_bitmap.get(), kHelloWorldPng); + } + CheckMarkCounts(page.get(), 0, 2, 2, 0, 0, 0); + + // Save the file. + EXPECT_TRUE(FPDFPage_GenerateContent(page.get())); + EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); + + // Re-open the file and check the new mark is present. + ScopedSavedDoc saved_document = OpenScopedSavedDocument(); + ASSERT_TRUE(saved_document); + ScopedSavedPage saved_page = LoadScopedSavedPage(0); + ASSERT_TRUE(saved_page); + + { + ScopedFPDFBitmap page_bitmap = RenderPage(saved_page.get()); + CompareBitmapWithExpectationSuffix(page_bitmap.get(), kHelloWorldPng); + } + CheckMarkCounts(saved_page.get(), 0, 2, 2, 0, 0, 0); +} + TEST_F(FPDFEditEmbedderTest, SetMarkParam) { // Load document with some text. ASSERT_TRUE(OpenDocument("text_in_page_marked.pdf"));
diff --git a/fpdfsdk/fpdf_editpage.cpp b/fpdfsdk/fpdf_editpage.cpp index 0a0d115..7b5613e 100644 --- a/fpdfsdk/fpdf_editpage.cpp +++ b/fpdfsdk/fpdf_editpage.cpp
@@ -36,6 +36,7 @@ #include "core/fxcrt/fx_extension.h" #include "core/fxcrt/notreached.h" #include "core/fxcrt/numerics/safe_conversions.h" +#include "core/fxcrt/retain_ptr.h" #include "core/fxcrt/span.h" #include "core/fxcrt/span_util.h" #include "core/fxcrt/stl_util.h" @@ -395,6 +396,22 @@ return FPDFPageObjectMarkFromCPDFContentMarkItem(pMarks->GetItem(index)); } +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDFPageObj_AddExistingMark(FPDF_PAGEOBJECT page_object, + FPDF_PAGEOBJECTMARK mark) { + CPDF_PageObject* page = CPDFPageObjectFromFPDFPageObject(page_object); + CPDF_ContentMarkItem* mark_item = + CPDFContentMarkItemFromFPDFPageObjectMark(mark); + if (!page || !mark_item) { + return false; + } + + CPDF_ContentMarks* marks = page->GetContentMarks(); + marks->AddExistingMark(pdfium::WrapRetain(mark_item)); + page->SetDirty(true); + return true; +} + FPDF_EXPORT FPDF_PAGEOBJECTMARK FPDF_CALLCONV FPDFPageObj_AddMark(FPDF_PAGEOBJECT page_object, FPDF_BYTESTRING name) { CPDF_PageObject* pPageObj = CPDFPageObjectFromFPDFPageObject(page_object);
diff --git a/fpdfsdk/fpdf_view_c_api_test.c b/fpdfsdk/fpdf_view_c_api_test.c index 85b6219..0de97ff 100644 --- a/fpdfsdk/fpdf_view_c_api_test.c +++ b/fpdfsdk/fpdf_view_c_api_test.c
@@ -209,6 +209,7 @@ CHK(FPDFPageObjMark_SetFloatParam); CHK(FPDFPageObjMark_SetIntParam); CHK(FPDFPageObjMark_SetStringParam); + CHK(FPDFPageObj_AddExistingMark); CHK(FPDFPageObj_AddMark); CHK(FPDFPageObj_CountMarks); CHK(FPDFPageObj_CreateNewPath);
diff --git a/public/fpdf_edit.h b/public/fpdf_edit.h index eb89d01..8fac863 100644 --- a/public/fpdf_edit.h +++ b/public/fpdf_edit.h
@@ -465,6 +465,22 @@ FPDFPageObj_AddMark(FPDF_PAGEOBJECT page_object, FPDF_BYTESTRING name); // Experimental API. +// Add an existing content mark to a |page_object|. If consecutive page objects +// have the same |mark|, the generated PDF will contain a single mark that spans +// all of them. If the page objects are not consecutive, multiple copies of the +// |mark| are inserted in the PDF. +// +// page_object - handle to a page object. +// mark - handle to a mark object. +// +// Returns true on success, or false on failure. The handles are all owned by +// the library. The |page_object| and |mark| params must be associated with the +// same document. +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDFPageObj_AddExistingMark(FPDF_PAGEOBJECT page_object, + FPDF_PAGEOBJECTMARK mark); + +// Experimental API. // Removes a content |mark| from a |page_object|. // The mark handle will be invalid after the removal. //