diff options
-rw-r--r-- | core/fpdfdoc/cpdf_annot.cpp | 75 | ||||
-rw-r--r-- | core/fpdfdoc/cpdf_annot.h | 10 | ||||
-rw-r--r-- | fpdfsdk/fpdfannot.cpp | 39 | ||||
-rw-r--r-- | fpdfsdk/fpdfannot_embeddertest.cpp | 91 | ||||
-rw-r--r-- | fpdfsdk/fpdfview_c_api_test.c | 1 | ||||
-rw-r--r-- | public/fpdf_annot.h | 28 | ||||
-rw-r--r-- | public/fpdfview.h | 3 |
7 files changed, 196 insertions, 51 deletions
diff --git a/core/fpdfdoc/cpdf_annot.cpp b/core/fpdfdoc/cpdf_annot.cpp index d1baef5052..02df3cff60 100644 --- a/core/fpdfdoc/cpdf_annot.cpp +++ b/core/fpdfdoc/cpdf_annot.cpp @@ -60,6 +60,43 @@ CPDF_Form* AnnotGetMatrix(const CPDF_Page* pPage, return pForm; } +CPDF_Stream* FPDFDOC_GetAnnotAPInternal(const CPDF_Dictionary* pAnnotDict, + CPDF_Annot::AppearanceMode eMode, + bool bFallbackToNormal) { + CPDF_Dictionary* pAP = pAnnotDict->GetDictFor("AP"); + if (!pAP) + return nullptr; + + const char* ap_entry = "N"; + if (eMode == CPDF_Annot::Down) + ap_entry = "D"; + else if (eMode == CPDF_Annot::Rollover) + ap_entry = "R"; + if (bFallbackToNormal && !pAP->KeyExist(ap_entry)) + ap_entry = "N"; + + CPDF_Object* psub = pAP->GetDirectObjectFor(ap_entry); + if (!psub) + return nullptr; + if (CPDF_Stream* pStream = psub->AsStream()) + return pStream; + + CPDF_Dictionary* pDict = psub->AsDictionary(); + if (!pDict) + return nullptr; + + ByteString as = pAnnotDict->GetStringFor("AS"); + if (as.IsEmpty()) { + ByteString value = pAnnotDict->GetStringFor("V"); + if (value.IsEmpty()) { + CPDF_Dictionary* pParentDict = pAnnotDict->GetDictFor("Parent"); + value = pParentDict ? pParentDict->GetStringFor("V") : ByteString(); + } + as = (!value.IsEmpty() && pDict->KeyExist(value)) ? value : "Off"; + } + return pDict->GetStreamFor(as); +} + } // namespace CPDF_Annot::CPDF_Annot(std::unique_ptr<CPDF_Dictionary> pDict, @@ -140,39 +177,13 @@ uint32_t CPDF_Annot::GetFlags() const { } CPDF_Stream* FPDFDOC_GetAnnotAP(const CPDF_Dictionary* pAnnotDict, - CPDF_Annot::AppearanceMode mode) { - CPDF_Dictionary* pAP = pAnnotDict->GetDictFor("AP"); - if (!pAP) - return nullptr; - - const char* ap_entry = "N"; - if (mode == CPDF_Annot::Down) - ap_entry = "D"; - else if (mode == CPDF_Annot::Rollover) - ap_entry = "R"; - if (!pAP->KeyExist(ap_entry)) - ap_entry = "N"; - - CPDF_Object* psub = pAP->GetDirectObjectFor(ap_entry); - if (!psub) - return nullptr; - if (CPDF_Stream* pStream = psub->AsStream()) - return pStream; - - CPDF_Dictionary* pDict = psub->AsDictionary(); - if (!pDict) - return nullptr; + CPDF_Annot::AppearanceMode eMode) { + return FPDFDOC_GetAnnotAPInternal(pAnnotDict, eMode, true); +} - ByteString as = pAnnotDict->GetStringFor("AS"); - if (as.IsEmpty()) { - ByteString value = pAnnotDict->GetStringFor("V"); - if (value.IsEmpty()) { - CPDF_Dictionary* pParentDict = pAnnotDict->GetDictFor("Parent"); - value = pParentDict ? pParentDict->GetStringFor("V") : ByteString(); - } - as = (!value.IsEmpty() && pDict->KeyExist(value)) ? value : "Off"; - } - return pDict->GetStreamFor(as); +CPDF_Stream* FPDFDOC_GetAnnotAPNoFallback(const CPDF_Dictionary* pAnnotDict, + CPDF_Annot::AppearanceMode eMode) { + return FPDFDOC_GetAnnotAPInternal(pAnnotDict, eMode, false); } CPDF_Form* CPDF_Annot::GetAPForm(const CPDF_Page* pPage, AppearanceMode mode) { diff --git a/core/fpdfdoc/cpdf_annot.h b/core/fpdfdoc/cpdf_annot.h index 39448ba209..499c62dcba 100644 --- a/core/fpdfdoc/cpdf_annot.h +++ b/core/fpdfdoc/cpdf_annot.h @@ -118,7 +118,15 @@ class CPDF_Annot { CPDF_Annot* m_pPopupAnnot = nullptr; }; +// Get the AP in an annotation dict for a given appearance mode. +// If |eMode| is not Normal and there is not AP for that mode, falls back to +// the Normal AP. CPDF_Stream* FPDFDOC_GetAnnotAP(const CPDF_Dictionary* pAnnotDict, - CPDF_Annot::AppearanceMode mode); + CPDF_Annot::AppearanceMode eMode); + +// Get the AP in an annotation dict for a given appearance mode. +// No fallbacks to Normal like in FPDFDOC_GetAnnotAP. +CPDF_Stream* FPDFDOC_GetAnnotAPNoFallback(const CPDF_Dictionary* pAnnotDict, + CPDF_Annot::AppearanceMode eMode); #endif // CORE_FPDFDOC_CPDF_ANNOT_H_ diff --git a/fpdfsdk/fpdfannot.cpp b/fpdfsdk/fpdfannot.cpp index ec541f55d0..c7433d03d8 100644 --- a/fpdfsdk/fpdfannot.cpp +++ b/fpdfsdk/fpdfannot.cpp @@ -104,6 +104,18 @@ static_assert(static_cast<int>(CPDF_Annot::Subtype::XFAWIDGET) == FPDF_ANNOT_XFAWIDGET, "CPDF_Annot::XFAWIDGET value mismatch"); +// These checks ensure the consistency of annotation appearance mode values +// across core/ and public. +static_assert(static_cast<int>(CPDF_Annot::AppearanceMode::Normal) == + FPDF_ANNOT_APPEARANCEMODE_NORMAL, + "CPDF_Annot::AppearanceMode::Normal value mismatch"); +static_assert(static_cast<int>(CPDF_Annot::AppearanceMode::Rollover) == + FPDF_ANNOT_APPEARANCEMODE_ROLLOVER, + "CPDF_Annot::AppearanceMode::Rollover value mismatch"); +static_assert(static_cast<int>(CPDF_Annot::AppearanceMode::Down) == + FPDF_ANNOT_APPEARANCEMODE_DOWN, + "CPDF_Annot::AppearanceMode::Down value mismatch"); + // These checks ensure the consistency of dictionary value types across core/ // and public/. static_assert(static_cast<int>(CPDF_Object::Type::BOOLEAN) == @@ -735,6 +747,33 @@ FPDFAnnot_GetStringValue(FPDF_ANNOTATION annot, buffer, buflen); } +FPDF_EXPORT unsigned long FPDF_CALLCONV +FPDFAnnot_GetAP(FPDF_ANNOTATION annot, + FPDF_ANNOT_APPEARANCEMODE appearanceMode, + void* buffer, + unsigned long buflen) { + if (appearanceMode < 0 || appearanceMode >= FPDF_ANNOT_APPEARANCEMODE_COUNT) + return 0; + + if (!annot) + return 0; + + CPDF_Dictionary* pAnnotDict = + CPDFAnnotContextFromFPDFAnnotation(annot)->GetAnnotDict(); + if (!pAnnotDict) + return 0; + + CPDF_Annot::AppearanceMode mode = + static_cast<CPDF_Annot::AppearanceMode>(appearanceMode); + + CPDF_Stream* pStream = FPDFDOC_GetAnnotAPNoFallback(pAnnotDict, mode); + if (!pStream) + return Utf16EncodeMaybeCopyAndReturnLength(L"", buffer, buflen); + + return Utf16EncodeMaybeCopyAndReturnLength(pStream->GetUnicodeText(), buffer, + buflen); +} + FPDF_EXPORT FPDF_ANNOTATION FPDF_CALLCONV FPDFAnnot_GetLinkedAnnot(FPDF_ANNOTATION annot, FPDF_BYTESTRING key) { CPDF_AnnotContext* pAnnot = CPDFAnnotContextFromFPDFAnnotation(annot); diff --git a/fpdfsdk/fpdfannot_embeddertest.cpp b/fpdfsdk/fpdfannot_embeddertest.cpp index dce658201c..6d4521f26e 100644 --- a/fpdfsdk/fpdfannot_embeddertest.cpp +++ b/fpdfsdk/fpdfannot_embeddertest.cpp @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <cwchar> #include <memory> #include <string> #include <vector> @@ -11,12 +12,21 @@ #include "public/fpdf_edit.h" #include "public/fpdfview.h" #include "testing/embedder_test.h" +#include "testing/gmock/include/gmock/gmock-matchers.h" #include "testing/gtest/include/gtest/gtest.h" static constexpr char kContentsKey[] = "Contents"; class FPDFAnnotEmbeddertest : public EmbedderTest {}; +const std::wstring BufferToWString(std::vector<char>& buf) { + return GetPlatformWString(reinterpret_cast<unsigned short*>(buf.data())); +} + +const std::string BufferToString(std::vector<char>& buf) { + return GetPlatformString(reinterpret_cast<unsigned short*>(buf.data())); +} + TEST_F(FPDFAnnotEmbeddertest, RenderAnnotWithOnlyRolloverAP) { // Open a file with one annotation and load its first page. ASSERT_TRUE(OpenDocument("annotation_highlight_rollover_ap.pdf")); @@ -66,9 +76,7 @@ TEST_F(FPDFAnnotEmbeddertest, ExtractHighlightLongContent) { unsigned long len = FPDFAnnot_GetStringValue(annot, kAuthorKey, nullptr, 0); std::vector<char> buf(len); EXPECT_EQ(28u, FPDFAnnot_GetStringValue(annot, kAuthorKey, buf.data(), len)); - EXPECT_STREQ(L"Jae Hyun Park", - GetPlatformWString(reinterpret_cast<unsigned short*>(buf.data())) - .c_str()); + EXPECT_STREQ(L"Jae Hyun Park", BufferToWString(buf).c_str()); // Check that the content is correct. EXPECT_EQ(FPDF_OBJECT_STRING, FPDFAnnot_GetValueType(annot, kContentsKey)); @@ -98,9 +106,7 @@ TEST_F(FPDFAnnotEmbeddertest, ExtractHighlightLongContent) { "longLong long longLong long longLong long longLong long longLong long " "longLong long longLong long longLong long longLong long longLong long " "longLong long long. END"; - EXPECT_STREQ(contents, - GetPlatformWString(reinterpret_cast<unsigned short*>(buf.data())) - .c_str()); + EXPECT_STREQ(contents, BufferToWString(buf).c_str()); // Check that the quadpoints are correct. FS_QUADPOINTSF quadpoints; @@ -243,9 +249,7 @@ TEST_F(FPDFAnnotEmbeddertest, AddFirstTextAnnotation) { std::vector<char> buf(len); EXPECT_EQ(74u, FPDFAnnot_GetStringValue(annot, kContentsKey, buf.data(), len)); - EXPECT_STREQ(contents, - GetPlatformWString(reinterpret_cast<unsigned short*>(buf.data())) - .c_str()); + EXPECT_STREQ(contents, BufferToWString(buf).c_str()); FPDFPage_CloseAnnot(annot); UnloadPage(page); @@ -838,8 +842,7 @@ TEST_F(FPDFAnnotEmbeddertest, GetSetStringValue) { std::vector<char> buf(len); EXPECT_EQ(66u, FPDFAnnot_GetStringValue(annot, kHashKey, buf.data(), len)); EXPECT_STREQ(L"395fbcb98d558681742f30683a62a2ad", - GetPlatformWString(reinterpret_cast<unsigned short*>(buf.data())) - .c_str()); + BufferToWString(buf).c_str()); // Check that the string value of the modified date is correct. static constexpr char kDateKey[] = "M"; @@ -848,9 +851,7 @@ TEST_F(FPDFAnnotEmbeddertest, GetSetStringValue) { buf.clear(); buf.resize(len); EXPECT_EQ(44u, FPDFAnnot_GetStringValue(annot, kDateKey, buf.data(), len)); - EXPECT_STREQ(L"D:201706071721Z00'00'", - GetPlatformWString(reinterpret_cast<unsigned short*>(buf.data())) - .c_str()); + EXPECT_STREQ(L"D:201706071721Z00'00'", BufferToWString(buf).c_str()); // Update the date entry for the annotation. const wchar_t new_date[] = L"D:201706282359Z00'00'"; @@ -881,15 +882,71 @@ TEST_F(FPDFAnnotEmbeddertest, GetSetStringValue) { buf.resize(len); EXPECT_EQ(44u, FPDFAnnot_GetStringValue(new_annot, kDateKey, buf.data(), len)); - EXPECT_STREQ(new_date, - GetPlatformWString(reinterpret_cast<unsigned short*>(buf.data())) - .c_str()); + EXPECT_STREQ(new_date, BufferToWString(buf).c_str()); FPDFPage_CloseAnnot(new_annot); CloseSavedPage(page); CloseSavedDocument(); } +TEST_F(FPDFAnnotEmbeddertest, GetAP) { + // 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); + + // Check that the string value of an AP returns the expected length. + unsigned long len = + FPDFAnnot_GetAP(annot, FPDF_ANNOT_APPEARANCEMODE_NORMAL, nullptr, 0); + EXPECT_EQ(73970u, 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<char> buf(len - 1); + // Write L"z" in the buffer to verify it's not overwritten. + wcscpy(reinterpret_cast<wchar_t*>(buf.data()), L"z"); + EXPECT_EQ(73970u, FPDFAnnot_GetAP(annot, FPDF_ANNOT_APPEARANCEMODE_NORMAL, + buf.data(), len - 1)); + 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); + EXPECT_EQ(73970u, FPDFAnnot_GetAP(annot, FPDF_ANNOT_APPEARANCEMODE_NORMAL, + buf.data(), len)); + 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 the string value of an AP is returned through a buffer that is + // larger than necessary. + buf.clear(); + buf.resize(len + 1); + EXPECT_EQ(73970u, FPDFAnnot_GetAP(annot, FPDF_ANNOT_APPEARANCEMODE_NORMAL, + buf.data(), len + 1)); + 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. + buf.clear(); + buf.resize(len); + EXPECT_EQ(2u, FPDFAnnot_GetAP(annot, FPDF_ANNOT_APPEARANCEMODE_ROLLOVER, + buf.data(), len)); + ap = BufferToString(buf); + EXPECT_STREQ("", ap.c_str()); + + FPDFPage_CloseAnnot(annot); + FPDF_ClosePage(page); +} + TEST_F(FPDFAnnotEmbeddertest, ExtractLinkedAnnotations) { // Open a file with annotations and load its first page. ASSERT_TRUE(OpenDocument("annotation_highlight_square_with_ap.pdf")); diff --git a/fpdfsdk/fpdfview_c_api_test.c b/fpdfsdk/fpdfview_c_api_test.c index 5f935fc665..08696c2e27 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_GetAP); CHK(FPDFAnnot_GetLinkedAnnot); CHK(FPDFAnnot_GetFlags); CHK(FPDFAnnot_SetFlags); diff --git a/public/fpdf_annot.h b/public/fpdf_annot.h index 1fbc366872..8841963183 100644 --- a/public/fpdf_annot.h +++ b/public/fpdf_annot.h @@ -58,6 +58,11 @@ extern "C" { #define FPDF_ANNOT_FLAG_LOCKED (1 << 7) #define FPDF_ANNOT_FLAG_TOGGLENOVIEW (1 << 8) +#define FPDF_ANNOT_APPEARANCEMODE_NORMAL 0 +#define FPDF_ANNOT_APPEARANCEMODE_ROLLOVER 1 +#define FPDF_ANNOT_APPEARANCEMODE_DOWN 2 +#define FPDF_ANNOT_APPEARANCEMODE_COUNT 3 + #define FPDF_OBJECT_UNKNOWN 0 #define FPDF_OBJECT_BOOLEAN 1 #define FPDF_OBJECT_NUMBER 2 @@ -396,6 +401,29 @@ FPDFAnnot_GetStringValue(FPDF_ANNOTATION annot, unsigned long buflen); // Experimental API. +// Get the AP (appearance string) from |annot|'s dictionary for a given +// |appearanceMode|. +// |buffer| is only modified if |buflen| is large enough to hold the whole AP +// string. If |buflen| is smaller, the total size of the AP is still returned, +// but nothing is copied. +// If there is no appearance stream for |annot| in |appearanceMode|, an empty +// string is written to |buf| and 2 is returned. +// On other errors, nothing is written to |buffer| and 0 is returned. +// +// annot - handle to an annotation. +// appearanceMode - the appearance mode (normal, rollover or down) for which +// to get the AP. +// buffer - buffer for holding the value string, encoded in UTF16-LE. +// buflen - length of the buffer. +// +// Returns the length of the string value. +FPDF_EXPORT unsigned long FPDF_CALLCONV +FPDFAnnot_GetAP(FPDF_ANNOTATION annot, + FPDF_ANNOT_APPEARANCEMODE appearanceMode, + void* buffer, + unsigned long buflen); + +// Experimental API. // Get the annotation corresponding to |key| in |annot|'s dictionary. Common // keys for linking annotations include "IRT" and "Popup". Must call // FPDFPage_CloseAnnot() when the annotation returned by this function is no diff --git a/public/fpdfview.h b/public/fpdfview.h index 370b84ed0f..1cd3d6d194 100644 --- a/public/fpdfview.h +++ b/public/fpdfview.h @@ -137,8 +137,9 @@ typedef struct _FS_RECTF_ { // Const Pointer to FS_RECTF structure. typedef const FS_RECTF* FS_LPCRECTF; -// Annotation subtype. +// Annotation enums. typedef int FPDF_ANNOTATION_SUBTYPE; +typedef int FPDF_ANNOT_APPEARANCEMODE; // Dictionary value types. typedef int FPDF_OBJECT_TYPE; |