Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions fpdfsdk/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ source_set("fpdfsdk") {
"epdf_capture.cpp",
# EmbedPDF: the catalog's metadata stream (public/epdf_catalog.h).
"epdf_catalog.cpp",
"epdf_pageobject.cpp",
# EmbedPDF: implements EPDFFont_* runtime font registration APIs.
"epdf_font.cpp",
# EmbedPDF: session-free AcroForm model snapshot (EPDFForm_* APIs).
Expand Down Expand Up @@ -257,6 +258,7 @@ pdfium_embeddertest_source_set("embeddertests") {
"epdf_capture_embeddertest.cpp",
# EmbedPDF: covers the catalog's metadata stream (EPDFCatalog_*Metadata).
"epdf_catalog_embeddertest.cpp",
"epdf_pageobject_embeddertest.cpp",
# EmbedPDF: covers appearance states and modes, widgets in page renders,
# and the check box and radio button symbols.
"epdf_appearance_state_embeddertest.cpp",
Expand Down
6 changes: 6 additions & 0 deletions fpdfsdk/epdf_c_api_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -267,8 +267,14 @@ int CheckEmbedPDFCApi(void) {
CHK(EPDF_SetMetaTrapped);

// epdf_edit.h
CHK(EPDFFormObj_GetTransparencyGroup);
CHK(EPDFImageObj_SetJpeg);
CHK(EPDFImageObj_SetPng);
CHK(EPDFPageObj_GetBlendMode);
CHK(EPDFPageObj_GetFillAlpha);
CHK(EPDFPageObj_GetMiterLimit);
CHK(EPDFPageObj_GetStrokeAlpha);
CHK(EPDFPageObj_HasSoftMask);
CHK(EPDFPage_InsertBlankRaw);
CHK(EPDFText_RedactInQuads);
CHK(EPDFText_RedactInRect);
Expand Down
77 changes: 77 additions & 0 deletions fpdfsdk/epdf_pageobject.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// SPDX-License-Identifier: Apache-2.0

#include "public/epdf_edit.h"

#include "core/fpdfapi/page/cpdf_form.h"
#include "core/fpdfapi/page/cpdf_formobject.h"
#include "core/fpdfapi/page/cpdf_pageobject.h"
#include "core/fxcrt/compiler_specific.h"
#include "core/fxcrt/span.h"
#include "fpdfsdk/cpdfsdk_helpers.h"

FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
EPDFPageObj_GetFillAlpha(FPDF_PAGEOBJECT page_object, float* alpha) {
const auto* object = CPDFPageObjectFromFPDFPageObject(page_object);
if (!object || !alpha) {
return false;
}
*alpha = object->general_state().GetFillAlpha();
return true;
}

FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
EPDFPageObj_GetStrokeAlpha(FPDF_PAGEOBJECT page_object, float* alpha) {
const auto* object = CPDFPageObjectFromFPDFPageObject(page_object);
if (!object || !alpha) {
return false;
}
*alpha = object->general_state().GetStrokeAlpha();
return true;
}

FPDF_EXPORT unsigned long FPDF_CALLCONV
EPDFPageObj_GetBlendMode(FPDF_PAGEOBJECT page_object,
char* buffer,
unsigned long buflen) {
const auto* object = CPDFPageObjectFromFPDFPageObject(page_object);
if (!object) {
return 0;
}
if (!buffer) {
buflen = 0;
}
// SAFETY: the caller provides a writable buffer of buflen bytes.
return NulTerminateMaybeCopyAndReturnLength(
object->general_state().GetBlendMode(),
UNSAFE_BUFFERS(pdfium::span(buffer, static_cast<size_t>(buflen))));
}

FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
EPDFPageObj_HasSoftMask(FPDF_PAGEOBJECT page_object) {
const auto* object = CPDFPageObjectFromFPDFPageObject(page_object);
return object && object->general_state().GetSoftMask();
}

FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
EPDFFormObj_GetTransparencyGroup(FPDF_PAGEOBJECT form_object,
FPDF_BOOL* is_group,
FPDF_BOOL* is_isolated) {
const auto* object = CPDFPageObjectFromFPDFPageObject(form_object);
if (!object || !object->IsForm() || !is_group || !is_isolated) {
return false;
}
const auto& transparency = object->AsForm()->form()->GetTransparency();
*is_group = transparency.IsGroup();
*is_isolated = transparency.IsGroup() && transparency.IsIsolated();
return true;
}

FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
EPDFPageObj_GetMiterLimit(FPDF_PAGEOBJECT page_object, float* miter_limit) {
const auto* object = CPDFPageObjectFromFPDFPageObject(page_object);
if (!object || !miter_limit) {
return false;
}
*miter_limit = object->graph_state().GetMiterLimit();
return true;
}
101 changes: 101 additions & 0 deletions fpdfsdk/epdf_pageobject_embeddertest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// SPDX-License-Identifier: Apache-2.0

#include "public/epdf_edit.h"

#include <array>
#include <cstring>

#include "public/cpp/fpdf_scopers.h"
#include "testing/embedder_test.h"

class EPDFPageObjectEmbedderTest : public EmbedderTest {};

TEST_F(EPDFPageObjectEmbedderTest, DefaultsAndInvalidArguments) {
ScopedFPDFPageObject path(FPDFPageObj_CreateNewRect(0, 0, 10, 10));
ASSERT_TRUE(path);
float value = -1;
EXPECT_TRUE(EPDFPageObj_GetFillAlpha(path.get(), &value));
EXPECT_FLOAT_EQ(1, value);
EXPECT_TRUE(EPDFPageObj_GetStrokeAlpha(path.get(), &value));
EXPECT_FLOAT_EQ(1, value);
EXPECT_TRUE(EPDFPageObj_GetMiterLimit(path.get(), &value));
EXPECT_FLOAT_EQ(10, value);
EXPECT_FALSE(EPDFPageObj_HasSoftMask(path.get()));
char mode[7] = {};
EXPECT_EQ(7u, EPDFPageObj_GetBlendMode(path.get(), mode, sizeof(mode)));
EXPECT_STREQ("Normal", mode);

value = -1;
EXPECT_FALSE(EPDFPageObj_GetFillAlpha(nullptr, &value));
EXPECT_FALSE(EPDFPageObj_GetStrokeAlpha(nullptr, &value));
EXPECT_FALSE(EPDFPageObj_GetMiterLimit(nullptr, &value));
EXPECT_FLOAT_EQ(-1, value);
EXPECT_FALSE(EPDFPageObj_GetFillAlpha(path.get(), nullptr));
EXPECT_FALSE(EPDFPageObj_GetStrokeAlpha(path.get(), nullptr));
EXPECT_FALSE(EPDFPageObj_GetMiterLimit(path.get(), nullptr));
EXPECT_FALSE(EPDFPageObj_HasSoftMask(nullptr));
EXPECT_EQ(0u, EPDFPageObj_GetBlendMode(nullptr, mode, sizeof(mode)));
EXPECT_STREQ("Normal", mode);
FPDF_BOOL group = -1;
FPDF_BOOL isolated = -1;
EXPECT_FALSE(EPDFFormObj_GetTransparencyGroup(nullptr, &group, &isolated));
EXPECT_FALSE(EPDFFormObj_GetTransparencyGroup(path.get(), &group, &isolated));
EXPECT_EQ(-1, group);
EXPECT_EQ(-1, isolated);
}

TEST_F(EPDFPageObjectEmbedderTest, ReadsGraphicsStateWithoutChangingRendering) {
ASSERT_TRUE(OpenDocument("epdf_pageobject_state.pdf"));
FPDF_PAGE page = LoadPage(0);
ASSERT_TRUE(page);
ASSERT_EQ(7, FPDFPage_CountObjects(page));
const auto before = HashBitmap(RenderPage(page).get());

// Images and shadings carry alpha without a color state.
const std::array<int, 3> types = {FPDF_PAGEOBJ_PATH, FPDF_PAGEOBJ_IMAGE,
FPDF_PAGEOBJ_SHADING};
for (int i = 0; i < 3; ++i) {
FPDF_PAGEOBJECT object = FPDFPage_GetObject(page, i);
EXPECT_EQ(types[i], FPDFPageObj_GetType(object));
float alpha = -1;
EXPECT_TRUE(EPDFPageObj_GetFillAlpha(object, &alpha));
EXPECT_FLOAT_EQ(0.25f, alpha);
EXPECT_TRUE(EPDFPageObj_GetStrokeAlpha(object, &alpha));
EXPECT_FLOAT_EQ(0.75f, alpha);
char mode[9] = {};
EXPECT_EQ(9u, EPDFPageObj_GetBlendMode(object, nullptr, 0));
EXPECT_EQ(9u, EPDFPageObj_GetBlendMode(object, nullptr, sizeof(mode)));
EXPECT_EQ(9u, EPDFPageObj_GetBlendMode(object, mode, sizeof(mode)));
EXPECT_STREQ("Multiply", mode);
char small[4] = {'k', 'e', 'e', 'p'};
EXPECT_EQ(9u, EPDFPageObj_GetBlendMode(object, small, sizeof(small)));
EXPECT_EQ(0, std::memcmp(small, "keep", sizeof(small)));
EXPECT_FALSE(EPDFPageObj_HasSoftMask(object));
if (i != 0) {
unsigned int r = 0, g = 0, b = 0, a = 0;
EXPECT_FALSE(FPDFPageObj_GetFillColor(object, &r, &g, &b, &a));
}
}
float miter = 0;
EXPECT_TRUE(EPDFPageObj_GetMiterLimit(FPDFPage_GetObject(page, 0), &miter));
EXPECT_FLOAT_EQ(7, miter);
EXPECT_EQ(FPDF_LINEJOIN_ROUND,
FPDFPageObj_GetLineJoin(FPDFPage_GetObject(page, 0)));

for (int i = 3; i < 6; ++i) {
FPDF_PAGEOBJECT object = FPDFPage_GetObject(page, i);
FPDF_BOOL group = -1;
FPDF_BOOL isolated = -1;
EXPECT_TRUE(EPDFFormObj_GetTransparencyGroup(object, &group, &isolated));
EXPECT_EQ(i != 3, group);
EXPECT_EQ(i == 5, isolated);
group = isolated = -1;
EXPECT_FALSE(EPDFFormObj_GetTransparencyGroup(object, nullptr, &isolated));
EXPECT_FALSE(EPDFFormObj_GetTransparencyGroup(object, &group, nullptr));
EXPECT_EQ(-1, group);
EXPECT_EQ(-1, isolated);
}
EXPECT_TRUE(EPDFPageObj_HasSoftMask(FPDFPage_GetObject(page, 6)));
EXPECT_EQ(before, HashBitmap(RenderPage(page).get()));
UnloadPage(page);
}
72 changes: 71 additions & 1 deletion public/epdf_edit.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,77 @@ extern "C" {
#endif // __cplusplus

// EmbedPDF's additions to public/fpdf_edit.h: blank pages without loading the
// page, PNG and JPEG image objects, and text redaction.
// page, PNG and JPEG image objects, text redaction, and page-object state.

// Experimental EmbedPDF Extension API.
// Read the object's non-stroking alpha, including for images and shadings
// that have no color state. This is the object's graphics-state value, not
// a composited opacity after soft masks or enclosing transparency groups.
//
// page_object - handle to a page object.
// alpha - receives the alpha (0 = transparent, 1 = opaque).
//
// Returns TRUE on success. Returns FALSE for a NULL handle or output pointer,
// leaving the output unchanged. Does not modify the object.
FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
EPDFPageObj_GetFillAlpha(FPDF_PAGEOBJECT page_object, float* alpha);

// Experimental EmbedPDF Extension API.
// Read the object's stroking alpha. The output, failure, and ownership
// semantics are the same as EPDFPageObj_GetFillAlpha(). The value does not
// imply that the object actually strokes any content.
FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
EPDFPageObj_GetStrokeAlpha(FPDF_PAGEOBJECT page_object, float* alpha);

// Experimental EmbedPDF Extension API.
// Read the object's resolved blend-mode name, using the names accepted by
// FPDFPageObj_SetBlendMode(). The default is "Normal".
//
// page_object - handle to a page object.
// buffer - caller-owned buffer, or NULL to query the required size.
// buflen - size of |buffer| in bytes.
//
// Returns the required byte count including the terminating NUL, or 0 for a
// NULL object. Copies only when |buffer| is non-NULL and large enough;
// otherwise leaves it unchanged. Does not modify the object.
FPDF_EXPORT unsigned long FPDF_CALLCONV
EPDFPageObj_GetBlendMode(FPDF_PAGEOBJECT page_object,
char* buffer,
unsigned long buflen);

// Experimental EmbedPDF Extension API.
// Return TRUE if the object's graphics state contains a soft mask, FALSE
// otherwise (including a NULL object). This does not report an image's own
// /SMask or masks applied by enclosing objects. Does not modify the object.
FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
EPDFPageObj_HasSoftMask(FPDF_PAGEOBJECT page_object);

// Experimental EmbedPDF Extension API.
// Read a Form XObject's transparency-group state.
//
// form_object - handle to a form page object.
// is_group - receives TRUE for a transparency group, FALSE otherwise.
// is_isolated - receives TRUE for an isolated transparency group, FALSE
// otherwise, including an ordinary form.
//
// Returns TRUE on success. Returns FALSE for a NULL/non-form object or NULL
// output pointer, leaving both outputs unchanged. Does not modify the form.
FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
EPDFFormObj_GetTransparencyGroup(FPDF_PAGEOBJECT form_object,
FPDF_BOOL* is_group,
FPDF_BOOL* is_isolated);

// Experimental EmbedPDF Extension API.
// Read the object's miter limit: the maximum ratio of miter length to stroke
// width. The PDF default is 10. The value does not imply a miter join is used.
//
// page_object - handle to a page object.
// miter_limit - receives the graphics-state miter limit.
//
// Returns TRUE on success. Returns FALSE for a NULL handle or output pointer,
// leaving the output unchanged. Does not modify the object.
FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
EPDFPageObj_GetMiterLimit(FPDF_PAGEOBJECT page_object, float* miter_limit);

// Experimental EmbedPDF Extension API.
// Insert a blank page without loading it: FPDFPage_New() for a caller that
Expand Down
77 changes: 77 additions & 0 deletions testing/resources/epdf_pageobject_state.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
{{header}}
{{object 1 0}} << /Type /Catalog /Pages 2 0 R >>
endobj
{{object 2 0}} << /Type /Pages /Kids [3 0 R] /Count 1 >>
endobj
{{object 3 0}} <<
/Type /Page /Parent 2 0 R /MediaBox [0 0 100 100]
/Resources <<
/ExtGState << /Half 5 0 R /Mask 11 0 R >>
/XObject << /Im 6 0 R /Plain 8 0 R /Group 9 0 R /Isolated 10 0 R >>
/Shading << /Sh 7 0 R >>
>>
/Contents 4 0 R
>>
endobj
{{object 4 0}} << {{streamlen}} >>
stream
q /Half gs 4 w 1 j 7 M 10 10 20 20 re B Q
q /Half gs 10 0 0 10 40 10 cm /Im Do Q
q /Half gs /Sh sh Q
/Plain Do
/Group Do
/Isolated Do
q /Mask gs 10 60 20 20 re f Q
endstream
endobj
{{object 5 0}} << /Type /ExtGState /ca 0.25 /CA 0.75 /BM /Multiply >>
endobj
{{object 6 0}} <<
/Type /XObject /Subtype /Image /Width 1 /Height 1
/ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /ASCIIHexDecode
{{streamlen}}
>>
stream
FF0000>
endstream
endobj
{{object 7 0}} <<
/ShadingType 2 /ColorSpace /DeviceRGB /Coords [0 0 100 0]
/Function << /FunctionType 2 /Domain [0 1] /C0 [1 0 0] /C1 [0 0 1] /N 1 >>
/Extend [true true]
>>
endobj
{{object 8 0}} <<
/Type /XObject /Subtype /Form /BBox [0 0 100 100] /Resources << >>
{{streamlen}}
>>
stream
10 40 20 20 re f
endstream
endobj
{{object 9 0}} <<
/Type /XObject /Subtype /Form /BBox [0 0 100 100] /Resources << >>
/Group << /S /Transparency /I false >>
{{streamlen}}
>>
stream
40 40 20 20 re f
endstream
endobj
{{object 10 0}} <<
/Type /XObject /Subtype /Form /BBox [0 0 100 100] /Resources << >>
/Group << /S /Transparency /I true >>
{{streamlen}}
>>
stream
70 40 20 20 re f
endstream
endobj
{{object 11 0}} <<
/Type /ExtGState /SMask << /S /Alpha /G 10 0 R >>
>>
endobj
{{xref}}
{{trailer}}
{{startxref}}
%%EOF