Add public APIs to create and use Form XObjects.
Add FPDF_NewXObjectFromPage() and FPDF_CloseXObject() to make and
destroy FPDF_XOBJECT handles. FPDF_XOBJECT handles can be used with
FPDF_NewFormObjectFromXObject() to create new FPDF_PAGEOBJECT handles of
type FPDF_PAGEOBJ_FORM.
This is based a prototype by feinberg@google.com.
Bug: pdfium:977
Change-Id: I3dd27e28121defef483321750c9a43ccbb750bc7
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/82072
Commit-Queue: Lei Zhang <thestig@chromium.org>
Reviewed-by: Tom Sepez <tsepez@chromium.org>
diff --git a/fpdfsdk/fpdf_ppo_embeddertest.cpp b/fpdfsdk/fpdf_ppo_embeddertest.cpp
index 85751c1..dc5130c 100644
--- a/fpdfsdk/fpdf_ppo_embeddertest.cpp
+++ b/fpdfsdk/fpdf_ppo_embeddertest.cpp
@@ -5,12 +5,16 @@
#include <memory>
#include <string>
+#include "core/fpdfapi/page/cpdf_form.h"
+#include "core/fpdfapi/page/cpdf_formobject.h"
+#include "fpdfsdk/cpdfsdk_helpers.h"
#include "public/cpp/fpdf_scopers.h"
#include "public/fpdf_edit.h"
#include "public/fpdf_ppo.h"
#include "public/fpdf_save.h"
#include "public/fpdfview.h"
#include "testing/embedder_test.h"
+#include "testing/embedder_test_constants.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/base/cxx17_backports.h"
@@ -143,6 +147,160 @@
}
}
+TEST_F(FPDFPPOEmbedderTest, ImportPageToXObject) {
+#if defined(_SKIA_SUPPORT_) || defined(_SKIA_SUPPORT_PATHS_)
+ static const char kChecksum[] = "d6ebc0a8afc22fe0137f54ce54e1a19c";
+#else
+ static const char kChecksum[] = "2d88d180af7109eb346439f7c855bb29";
+#endif
+
+ ASSERT_TRUE(OpenDocument("rectangles.pdf"));
+
+ {
+ ScopedFPDFDocument output_doc(FPDF_CreateNewDocument());
+ ASSERT_TRUE(output_doc);
+
+ FPDF_XOBJECT xobject =
+ FPDF_NewXObjectFromPage(output_doc.get(), document(), 0);
+ ASSERT_TRUE(xobject);
+
+ for (int i = 0; i < 2; ++i) {
+ ScopedFPDFPage page(FPDFPage_New(output_doc.get(), 0, 612, 792));
+ ASSERT_TRUE(page);
+
+ FPDF_PAGEOBJECT page_object = FPDF_NewFormObjectFromXObject(xobject);
+ ASSERT_TRUE(page_object);
+ EXPECT_EQ(FPDF_PAGEOBJ_FORM, FPDFPageObj_GetType(page_object));
+ FPDFPage_InsertObject(page.get(), page_object);
+ EXPECT_TRUE(FPDFPage_GenerateContent(page.get()));
+
+ // TODO(thestig): This should have `kChecksum`.
+ ScopedFPDFBitmap page_bitmap = RenderPage(page.get());
+ CompareBitmap(page_bitmap.get(), 612, 792,
+ pdfium::kBlankPage612By792Checksum);
+ }
+
+ EXPECT_TRUE(FPDF_SaveAsCopy(output_doc.get(), this, 0));
+
+ FPDF_CloseXObject(xobject);
+ }
+
+ constexpr int kExpectedPageCount = 2;
+ ASSERT_TRUE(OpenSavedDocument());
+
+ FPDF_PAGE saved_pages[kExpectedPageCount];
+ FPDF_PAGEOBJECT xobjects[kExpectedPageCount];
+ for (int i = 0; i < kExpectedPageCount; ++i) {
+ saved_pages[i] = LoadSavedPage(i);
+ ASSERT_TRUE(saved_pages[i]);
+
+ EXPECT_EQ(1, FPDFPage_CountObjects(saved_pages[i]));
+ xobjects[i] = FPDFPage_GetObject(saved_pages[i], 0);
+ ASSERT_TRUE(xobjects[i]);
+ ASSERT_EQ(FPDF_PAGEOBJ_FORM, FPDFPageObj_GetType(xobjects[i]));
+ EXPECT_EQ(8, FPDFFormObj_CountObjects(xobjects[i]));
+
+ {
+ ScopedFPDFBitmap page_bitmap = RenderPage(saved_pages[i]);
+ CompareBitmap(page_bitmap.get(), 612, 792, kChecksum);
+ }
+ }
+
+ // Peek at object internals to make sure the two XObjects use the same stream.
+ EXPECT_NE(xobjects[0], xobjects[1]);
+ CPDF_PageObject* obj1 = CPDFPageObjectFromFPDFPageObject(xobjects[0]);
+ ASSERT_TRUE(obj1->AsForm());
+ ASSERT_TRUE(obj1->AsForm()->form());
+ ASSERT_TRUE(obj1->AsForm()->form()->GetStream());
+ CPDF_PageObject* obj2 = CPDFPageObjectFromFPDFPageObject(xobjects[1]);
+ ASSERT_TRUE(obj2->AsForm());
+ ASSERT_TRUE(obj2->AsForm()->form());
+ ASSERT_TRUE(obj2->AsForm()->form()->GetStream());
+ EXPECT_EQ(obj1->AsForm()->form()->GetStream(),
+ obj2->AsForm()->form()->GetStream());
+
+ for (FPDF_PAGE saved_page : saved_pages)
+ CloseSavedPage(saved_page);
+
+ CloseSavedDocument();
+}
+
+TEST_F(FPDFPPOEmbedderTest, ImportPageToXObjectWithSameDoc) {
+#if defined(_SKIA_SUPPORT_) || defined(_SKIA_SUPPORT_PATHS_)
+ static const char kChecksum[] = "8e7d672f49f9ca98fb9157824cefc204";
+#else
+ static const char kChecksum[] = "4d5ca14827b7707f8283e639b33c121a";
+#endif
+
+ ASSERT_TRUE(OpenDocument("rectangles.pdf"));
+
+ FPDF_XOBJECT xobject = FPDF_NewXObjectFromPage(document(), document(), 0);
+ ASSERT_TRUE(xobject);
+
+ FPDF_PAGE page = LoadPage(0);
+ ASSERT_TRUE(page);
+
+ {
+ ScopedFPDFBitmap bitmap = RenderLoadedPage(page);
+ CompareBitmap(bitmap.get(), 200, 300, pdfium::kRectanglesChecksum);
+ }
+
+ FPDF_PAGEOBJECT page_object = FPDF_NewFormObjectFromXObject(xobject);
+ ASSERT_TRUE(page_object);
+ ASSERT_EQ(FPDF_PAGEOBJ_FORM, FPDFPageObj_GetType(page_object));
+
+ // Access the CPDF_FormObject underneath, as there is no public API to set
+ // the matrix for form objects. (yet)
+ static constexpr FS_MATRIX kMatrix = {0.5f, 0.0f, 0.0f, 0.5f, 0.0f, 0.0f};
+ CPDF_FormObject* pFormObj =
+ CPDFPageObjectFromFPDFPageObject(page_object)->AsForm();
+ pFormObj->Transform(CFXMatrixFromFSMatrix(kMatrix));
+ pFormObj->SetDirty(true);
+
+ FPDFPage_InsertObject(page, page_object);
+ EXPECT_TRUE(FPDFPage_GenerateContent(page));
+
+ {
+ // TODO(thestig): This should have `kChecksum`.
+ ScopedFPDFBitmap bitmap = RenderLoadedPage(page);
+ CompareBitmap(bitmap.get(), 200, 300, pdfium::kRectanglesChecksum);
+ }
+
+ FPDF_CloseXObject(xobject);
+
+ EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0));
+ VerifySavedDocument(200, 300, kChecksum);
+
+ UnloadPage(page);
+}
+
+TEST_F(FPDFPPOEmbedderTest, XObjectNullParams) {
+ ASSERT_TRUE(OpenDocument("rectangles.pdf"));
+ ASSERT_EQ(1, FPDF_GetPageCount(document()));
+
+ EXPECT_FALSE(FPDF_NewXObjectFromPage(nullptr, nullptr, -1));
+ EXPECT_FALSE(FPDF_NewXObjectFromPage(nullptr, nullptr, 0));
+ EXPECT_FALSE(FPDF_NewXObjectFromPage(nullptr, nullptr, 1));
+ EXPECT_FALSE(FPDF_NewXObjectFromPage(document(), nullptr, -1));
+ EXPECT_FALSE(FPDF_NewXObjectFromPage(document(), nullptr, 0));
+ EXPECT_FALSE(FPDF_NewXObjectFromPage(document(), nullptr, 1));
+ EXPECT_FALSE(FPDF_NewXObjectFromPage(nullptr, document(), -1));
+ EXPECT_FALSE(FPDF_NewXObjectFromPage(nullptr, document(), 0));
+ EXPECT_FALSE(FPDF_NewXObjectFromPage(nullptr, document(), 1));
+
+ {
+ ScopedFPDFDocument output_doc(FPDF_CreateNewDocument());
+ ASSERT_TRUE(output_doc);
+ EXPECT_FALSE(FPDF_NewXObjectFromPage(output_doc.get(), document(), -1));
+ EXPECT_FALSE(FPDF_NewXObjectFromPage(output_doc.get(), document(), 1));
+ }
+
+ // Should be a no-op.
+ FPDF_CloseXObject(nullptr);
+
+ EXPECT_FALSE(FPDF_NewFormObjectFromXObject(nullptr));
+}
+
TEST_F(FPDFPPOEmbedderTest, BUG_925981) {
ASSERT_TRUE(OpenDocument("bug_925981.pdf"));
ScopedFPDFDocument output_doc_2up(