From 5970a47ac3cb1c0256d73f509ac73fa24aa4be42 Mon Sep 17 00:00:00 2001 From: Henrique Nakashima Date: Thu, 11 Jan 2018 22:40:59 +0000 Subject: Add FPDFAnnot_SetAP to public API. Change-Id: I6de3e4e158a8b0276775c0915cbe53417135eec3 Reviewed-on: https://pdfium-review.googlesource.com/22570 Reviewed-by: Lei Zhang Commit-Queue: Henrique Nakashima --- fpdfsdk/fpdfannot.cpp | 45 ++++++++++++ fpdfsdk/fpdfannot_embeddertest.cpp | 145 ++++++++++++++++++++++++++++++++++--- fpdfsdk/fpdfview_c_api_test.c | 1 + public/fpdf_annot.h | 17 +++++ 4 files changed, 196 insertions(+), 12 deletions(-) diff --git a/fpdfsdk/fpdfannot.cpp b/fpdfsdk/fpdfannot.cpp index e40e21981e..9b0cb39574 100644 --- a/fpdfsdk/fpdfannot.cpp +++ b/fpdfsdk/fpdfannot.cpp @@ -747,6 +747,51 @@ FPDFAnnot_GetStringValue(FPDF_ANNOTATION annot, buffer, buflen); } +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDFAnnot_SetAP(FPDF_ANNOTATION annot, + FPDF_ANNOT_APPEARANCEMODE appearanceMode, + FPDF_WIDESTRING value) { + if (appearanceMode < 0 || appearanceMode >= FPDF_ANNOT_APPEARANCEMODE_COUNT) + return false; + + if (!annot) + return false; + + CPDF_Dictionary* pAnnotDict = + CPDFAnnotContextFromFPDFAnnotation(annot)->GetAnnotDict(); + if (!pAnnotDict) + return false; + + constexpr const char* modeKeyForMode[] = {"N", "R", "D"}; + static_assert(FX_ArraySize(modeKeyForMode) == FPDF_ANNOT_APPEARANCEMODE_COUNT, + "length of modeKeyForMode should be equal to " + "FPDF_ANNOT_APPEARANCEMODE_COUNT"); + const char* modeKey = modeKeyForMode[appearanceMode]; + + CPDF_Dictionary* pApDict = pAnnotDict->GetDictFor("AP"); + + // If value is null, we're in remove mode. Otherwise, we're in add/update + // mode. + if (value) { + if (!pApDict) + pApDict = pAnnotDict->SetNewFor("AP"); + + ByteString newValue = CFXByteStringFromFPDFWideString(value); + auto pNewApStream = pdfium::MakeUnique(); + pNewApStream->SetData(newValue.raw_str(), newValue.GetLength()); + pApDict->SetFor(modeKey, std::move(pNewApStream)); + } else { + if (pApDict) { + if (appearanceMode == FPDF_ANNOT_APPEARANCEMODE_NORMAL) + pAnnotDict->RemoveFor("AP"); + else + pApDict->RemoveFor(modeKey); + } + } + + return true; +} + FPDF_EXPORT unsigned long FPDF_CALLCONV FPDFAnnot_GetAP(FPDF_ANNOTATION annot, FPDF_ANNOT_APPEARANCEMODE appearanceMode, diff --git a/fpdfsdk/fpdfannot_embeddertest.cpp b/fpdfsdk/fpdfannot_embeddertest.cpp index f07070a4a5..b38bd87752 100644 --- a/fpdfsdk/fpdfannot_embeddertest.cpp +++ b/fpdfsdk/fpdfannot_embeddertest.cpp @@ -889,7 +889,7 @@ TEST_F(FPDFAnnotEmbeddertest, GetSetStringValue) { CloseSavedDocument(); } -TEST_F(FPDFAnnotEmbeddertest, GetAP) { +TEST_F(FPDFAnnotEmbeddertest, GetSetAP) { // Open a file with four annotations and load its first page. ASSERT_TRUE(OpenDocument("annotation_stamp_with_ap.pdf")); FPDF_PAGE page = FPDF_LoadPage(document(), 0); @@ -900,26 +900,26 @@ TEST_F(FPDFAnnotEmbeddertest, GetAP) { ASSERT_TRUE(annot); // Check that the string value of an AP returns the expected length. - unsigned long len = + unsigned long normal_len = FPDFAnnot_GetAP(annot, FPDF_ANNOT_APPEARANCEMODE_NORMAL, nullptr, 0); - EXPECT_EQ(73970u, len); + EXPECT_EQ(73970u, normal_len); // Check that the string value of an AP is not returned if the buffer is too // small. The result buffer should be overwritten with an empty string. - std::vector buf(len - 1); + std::vector buf(normal_len - 1); // Write L"z" in the buffer to verify it's not overwritten. wcscpy(reinterpret_cast(buf.data()), L"z"); EXPECT_EQ(73970u, FPDFAnnot_GetAP(annot, FPDF_ANNOT_APPEARANCEMODE_NORMAL, - buf.data(), len - 1)); + buf.data(), buf.size())); std::string ap = BufferToString(buf); EXPECT_STREQ("z", ap.c_str()); // Check that the string value of an AP is returned through a buffer that is // the right size. buf.clear(); - buf.resize(len); + buf.resize(normal_len); EXPECT_EQ(73970u, FPDFAnnot_GetAP(annot, FPDF_ANNOT_APPEARANCEMODE_NORMAL, - buf.data(), len)); + buf.data(), buf.size())); ap = BufferToString(buf); EXPECT_THAT(ap, testing::StartsWith("q Q q 7.442786 w 2 J")); EXPECT_THAT(ap, testing::EndsWith("c 716.5381 327.7156 l S Q Q")); @@ -927,21 +927,142 @@ TEST_F(FPDFAnnotEmbeddertest, GetAP) { // Check that the string value of an AP is returned through a buffer that is // larger than necessary. buf.clear(); - buf.resize(len + 1); + buf.resize(normal_len + 1); EXPECT_EQ(73970u, FPDFAnnot_GetAP(annot, FPDF_ANNOT_APPEARANCEMODE_NORMAL, - buf.data(), len + 1)); + buf.data(), buf.size())); ap = BufferToString(buf); EXPECT_THAT(ap, testing::StartsWith("q Q q 7.442786 w 2 J")); EXPECT_THAT(ap, testing::EndsWith("c 716.5381 327.7156 l S Q Q")); // Check that getting an AP for a mode that does not have an AP returns an // empty string. + unsigned long rollover_len = + FPDFAnnot_GetAP(annot, FPDF_ANNOT_APPEARANCEMODE_ROLLOVER, nullptr, 0); + EXPECT_EQ(2u, rollover_len); + buf.clear(); - buf.resize(len); + buf.resize(1000); EXPECT_EQ(2u, FPDFAnnot_GetAP(annot, FPDF_ANNOT_APPEARANCEMODE_ROLLOVER, - buf.data(), len)); + buf.data(), buf.size())); + EXPECT_STREQ("", BufferToString(buf).c_str()); + + // Check that setting the AP for an invalid appearance mode fails. + std::unique_ptr apText = + GetFPDFWideString(L"new test ap"); + EXPECT_FALSE(FPDFAnnot_SetAP(annot, -1, apText.get())); + EXPECT_FALSE( + FPDFAnnot_SetAP(annot, FPDF_ANNOT_APPEARANCEMODE_COUNT, apText.get())); + EXPECT_FALSE(FPDFAnnot_SetAP(annot, FPDF_ANNOT_APPEARANCEMODE_COUNT + 1, + apText.get())); + + // Set the AP correctly now. + EXPECT_TRUE( + FPDFAnnot_SetAP(annot, FPDF_ANNOT_APPEARANCEMODE_ROLLOVER, apText.get())); + + // Check that the new annotation value is equal to the value we just set. + rollover_len = + FPDFAnnot_GetAP(annot, FPDF_ANNOT_APPEARANCEMODE_ROLLOVER, nullptr, 0); + EXPECT_EQ(24u, rollover_len); + buf.clear(); + buf.resize(rollover_len); + EXPECT_EQ(24u, FPDFAnnot_GetAP(annot, FPDF_ANNOT_APPEARANCEMODE_ROLLOVER, + buf.data(), buf.size())); + EXPECT_STREQ(L"new test ap", BufferToWString(buf).c_str()); + + // Check that the Normal AP was not touched when the Rollover AP was set. + buf.clear(); + buf.resize(normal_len); + EXPECT_EQ(73970u, FPDFAnnot_GetAP(annot, FPDF_ANNOT_APPEARANCEMODE_NORMAL, + buf.data(), buf.size())); ap = BufferToString(buf); - EXPECT_STREQ("", ap.c_str()); + EXPECT_THAT(ap, testing::StartsWith("q Q q 7.442786 w 2 J")); + EXPECT_THAT(ap, testing::EndsWith("c 716.5381 327.7156 l S Q Q")); + + // Save the modified document, then reopen it. + FPDFPage_CloseAnnot(annot); + EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); + FPDF_ClosePage(page); + + OpenSavedDocument(); + page = LoadSavedPage(0); + FPDF_ANNOTATION new_annot = FPDFPage_GetAnnot(page, 0); + + // Check that the new annotation value is equal to the value we set before + // saving. + rollover_len = FPDFAnnot_GetAP(new_annot, FPDF_ANNOT_APPEARANCEMODE_ROLLOVER, + nullptr, 0); + EXPECT_EQ(24u, rollover_len); + buf.clear(); + buf.resize(rollover_len); + EXPECT_EQ(24u, FPDFAnnot_GetAP(new_annot, FPDF_ANNOT_APPEARANCEMODE_ROLLOVER, + buf.data(), buf.size())); + EXPECT_STREQ(L"new test ap", BufferToWString(buf).c_str()); + + // Close saved document. + FPDFPage_CloseAnnot(new_annot); + CloseSavedPage(page); + CloseSavedDocument(); +} + +TEST_F(FPDFAnnotEmbeddertest, RemoveOptionalAP) { + // Open a file with four annotations and load its first page. + ASSERT_TRUE(OpenDocument("annotation_stamp_with_ap.pdf")); + FPDF_PAGE page = FPDF_LoadPage(document(), 0); + ASSERT_TRUE(page); + + // Retrieve the first annotation. + FPDF_ANNOTATION annot = FPDFPage_GetAnnot(page, 0); + ASSERT_TRUE(annot); + + // Set Down AP. Normal AP is already set. + std::unique_ptr apText = + GetFPDFWideString(L"new test ap"); + EXPECT_TRUE( + FPDFAnnot_SetAP(annot, FPDF_ANNOT_APPEARANCEMODE_DOWN, apText.get())); + EXPECT_EQ(73970u, FPDFAnnot_GetAP(annot, FPDF_ANNOT_APPEARANCEMODE_NORMAL, + nullptr, 0)); + EXPECT_EQ(24u, + FPDFAnnot_GetAP(annot, FPDF_ANNOT_APPEARANCEMODE_DOWN, nullptr, 0)); + + // Check that setting the Down AP to null removes the Down entry but keeps + // Normal intact. + EXPECT_TRUE(FPDFAnnot_SetAP(annot, FPDF_ANNOT_APPEARANCEMODE_DOWN, nullptr)); + EXPECT_EQ(73970u, FPDFAnnot_GetAP(annot, FPDF_ANNOT_APPEARANCEMODE_NORMAL, + nullptr, 0)); + EXPECT_EQ(2u, + FPDFAnnot_GetAP(annot, FPDF_ANNOT_APPEARANCEMODE_DOWN, nullptr, 0)); + + FPDFPage_CloseAnnot(annot); + FPDF_ClosePage(page); +} + +TEST_F(FPDFAnnotEmbeddertest, RemoveRequiredAP) { + // Open a file with four annotations and load its first page. + ASSERT_TRUE(OpenDocument("annotation_stamp_with_ap.pdf")); + FPDF_PAGE page = FPDF_LoadPage(document(), 0); + ASSERT_TRUE(page); + + // Retrieve the first annotation. + FPDF_ANNOTATION annot = FPDFPage_GetAnnot(page, 0); + ASSERT_TRUE(annot); + + // Set Down AP. Normal AP is already set. + std::unique_ptr apText = + GetFPDFWideString(L"new test ap"); + EXPECT_TRUE( + FPDFAnnot_SetAP(annot, FPDF_ANNOT_APPEARANCEMODE_DOWN, apText.get())); + EXPECT_EQ(73970u, FPDFAnnot_GetAP(annot, FPDF_ANNOT_APPEARANCEMODE_NORMAL, + nullptr, 0)); + EXPECT_EQ(24u, + FPDFAnnot_GetAP(annot, FPDF_ANNOT_APPEARANCEMODE_DOWN, nullptr, 0)); + + // Check that setting the Normal AP to null removes the whole AP dictionary. + EXPECT_TRUE( + FPDFAnnot_SetAP(annot, FPDF_ANNOT_APPEARANCEMODE_NORMAL, nullptr)); + EXPECT_EQ( + 2u, FPDFAnnot_GetAP(annot, FPDF_ANNOT_APPEARANCEMODE_NORMAL, nullptr, 0)); + EXPECT_EQ(2u, + FPDFAnnot_GetAP(annot, FPDF_ANNOT_APPEARANCEMODE_DOWN, nullptr, 0)); FPDFPage_CloseAnnot(annot); FPDF_ClosePage(page); diff --git a/fpdfsdk/fpdfview_c_api_test.c b/fpdfsdk/fpdfview_c_api_test.c index 08696c2e27..ad1d8a70ce 100644 --- a/fpdfsdk/fpdfview_c_api_test.c +++ b/fpdfsdk/fpdfview_c_api_test.c @@ -62,6 +62,7 @@ int CheckPDFiumCApi() { CHK(FPDFAnnot_GetValueType); CHK(FPDFAnnot_SetStringValue); CHK(FPDFAnnot_GetStringValue); + CHK(FPDFAnnot_SetAP); CHK(FPDFAnnot_GetAP); CHK(FPDFAnnot_GetLinkedAnnot); CHK(FPDFAnnot_GetFlags); diff --git a/public/fpdf_annot.h b/public/fpdf_annot.h index 8841963183..d5ef54cf3b 100644 --- a/public/fpdf_annot.h +++ b/public/fpdf_annot.h @@ -400,6 +400,23 @@ FPDFAnnot_GetStringValue(FPDF_ANNOTATION annot, void* buffer, unsigned long buflen); +// Experimental API. +// Set the AP (appearance string) in |annot|'s dictionary for a given +// |appearanceMode|. +// +// annot - handle to an annotation. +// appearanceMode - the appearance mode (normal, rollover or down) for which +// to get the AP. +// value - the string value to be set, encoded in UTF16-LE. If +// nullptr is passed, the AP is cleared for that mode. If the +// mode is Normal, APs for all modes are cleared. +// +// Returns true if successful. +FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV +FPDFAnnot_SetAP(FPDF_ANNOTATION annot, + FPDF_ANNOT_APPEARANCEMODE appearanceMode, + FPDF_WIDESTRING value); + // Experimental API. // Get the AP (appearance string) from |annot|'s dictionary for a given // |appearanceMode|. -- cgit v1.2.3