summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/fpdfdoc/cpdf_annot.cpp75
-rw-r--r--core/fpdfdoc/cpdf_annot.h10
-rw-r--r--fpdfsdk/fpdfannot.cpp39
-rw-r--r--fpdfsdk/fpdfannot_embeddertest.cpp91
-rw-r--r--fpdfsdk/fpdfview_c_api_test.c1
-rw-r--r--public/fpdf_annot.h28
-rw-r--r--public/fpdfview.h3
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;