From 2e1a32bc49f2b7b871cf0d04f25ec45b337f06fb Mon Sep 17 00:00:00 2001 From: Jane Liu Date: Thu, 6 Jul 2017 12:01:25 -0400 Subject: Added APIs for getting/setting string pairs in annotation dictionaries 1. Added APIs for getting/setting arbitary key + value string pairs in annotation dictionaries. * Added an embedder test testing all the new functions. Bug=pdfium:737 Change-Id: I93c9ca6fccf787028e106607ef8cf549ebca95d8 Reviewed-on: https://pdfium-review.googlesource.com/7150 Commit-Queue: Jane Liu Reviewed-by: dsinclair --- fpdfsdk/fpdfannot.cpp | 83 ++++++++++++++++++++++---- fpdfsdk/fpdfannot_embeddertest.cpp | 119 +++++++++++++++++++++++++++++++++---- fpdfsdk/fpdfview_c_api_test.c | 5 +- public/fpdf_annot.h | 73 ++++++++++++++++------- public/fpdfview.h | 3 + samples/pdfium_test.cc | 12 ++-- 6 files changed, 244 insertions(+), 51 deletions(-) diff --git a/fpdfsdk/fpdfannot.cpp b/fpdfsdk/fpdfannot.cpp index 024c52bc10..b8043b0a66 100644 --- a/fpdfsdk/fpdfannot.cpp +++ b/fpdfsdk/fpdfannot.cpp @@ -104,6 +104,31 @@ static_assert(static_cast(CPDF_Annot::Subtype::XFAWIDGET) == FPDF_ANNOT_XFAWIDGET, "CPDF_Annot::XFAWIDGET value mismatch"); +// These checks ensure the consistency of dictionary value types across core/ +// and public/. +static_assert(static_cast(CPDF_Object::Type::BOOLEAN) == + FPDF_OBJECT_BOOLEAN, + "CPDF_Object::BOOLEAN value mismatch"); +static_assert(static_cast(CPDF_Object::Type::NUMBER) == FPDF_OBJECT_NUMBER, + "CPDF_Object::NUMBER value mismatch"); +static_assert(static_cast(CPDF_Object::Type::STRING) == FPDF_OBJECT_STRING, + "CPDF_Object::STRING value mismatch"); +static_assert(static_cast(CPDF_Object::Type::NAME) == FPDF_OBJECT_NAME, + "CPDF_Object::NAME value mismatch"); +static_assert(static_cast(CPDF_Object::Type::ARRAY) == FPDF_OBJECT_ARRAY, + "CPDF_Object::ARRAY value mismatch"); +static_assert(static_cast(CPDF_Object::Type::DICTIONARY) == + FPDF_OBJECT_DICTIONARY, + "CPDF_Object::DICTIONARY value mismatch"); +static_assert(static_cast(CPDF_Object::Type::STREAM) == FPDF_OBJECT_STREAM, + "CPDF_Object::STREAM value mismatch"); +static_assert(static_cast(CPDF_Object::Type::NULLOBJ) == + FPDF_OBJECT_NULLOBJ, + "CPDF_Object::NULLOBJ value mismatch"); +static_assert(static_cast(CPDF_Object::Type::REFERENCE) == + FPDF_OBJECT_REFERENCE, + "CPDF_Object::REFERENCE value mismatch"); + class CPDF_AnnotContext { public: CPDF_AnnotContext(CPDF_Dictionary* pAnnotDict, @@ -147,6 +172,11 @@ bool HasAPStream(const CPDF_Dictionary* pAnnotDict) { return !!FPDFDOC_GetAnnotAP(pAnnotDict, CPDF_Annot::AppearanceMode::Normal); } +CFX_ByteString CFXByteStringFromFPDFWideString(FPDF_WIDESTRING text) { + return CFX_WideString::FromUTF16LE(text, CFX_WideString::WStringLength(text)) + .UTF8Encode(); +} + } // namespace DLLEXPORT FPDF_BOOL STDCALL @@ -645,9 +675,36 @@ DLLEXPORT FS_RECTF STDCALL FPDFAnnot_GetRect(FPDF_ANNOTATION annot) { return rect; } -DLLEXPORT FPDF_BOOL STDCALL FPDFAnnot_SetText(FPDF_ANNOTATION annot, - FPDFANNOT_TEXTTYPE type, - FPDF_WIDESTRING text) { +DLLEXPORT FPDF_BOOL STDCALL FPDFAnnot_HasKey(FPDF_ANNOTATION annot, + FPDF_WIDESTRING key) { + if (!annot) + return false; + + CPDF_Dictionary* pAnnotDict = + CPDFAnnotContextFromFPDFAnnotation(annot)->GetAnnotDict(); + if (!pAnnotDict) + return false; + + return pAnnotDict->KeyExist(CFXByteStringFromFPDFWideString(key)); +} + +DLLEXPORT FPDF_OBJECT_TYPE STDCALL FPDFAnnot_GetValueType(FPDF_ANNOTATION annot, + FPDF_WIDESTRING key) { + if (!FPDFAnnot_HasKey(annot, key)) + return FPDF_OBJECT_UNKNOWN; + + CPDF_Object* pObj = + CPDFAnnotContextFromFPDFAnnotation(annot)->GetAnnotDict()->GetObjectFor( + CFXByteStringFromFPDFWideString(key)); + if (!pObj) + return FPDF_OBJECT_UNKNOWN; + + return pObj->GetType(); +} + +DLLEXPORT FPDF_BOOL STDCALL FPDFAnnot_SetStringValue(FPDF_ANNOTATION annot, + FPDF_WIDESTRING key, + FPDF_WIDESTRING value) { if (!annot) return false; @@ -656,17 +713,16 @@ DLLEXPORT FPDF_BOOL STDCALL FPDFAnnot_SetText(FPDF_ANNOTATION annot, if (!pAnnotDict) return false; - CFX_ByteString key = type == FPDFANNOT_TEXTTYPE_Author ? "T" : "Contents"; - FX_STRSIZE len = CFX_WideString::WStringLength(text); - CFX_WideString encodedText = CFX_WideString::FromUTF16LE(text, len); - pAnnotDict->SetNewFor(key, encodedText.UTF8Encode(), false); + pAnnotDict->SetNewFor(CFXByteStringFromFPDFWideString(key), + CFXByteStringFromFPDFWideString(value), + false); return true; } -DLLEXPORT unsigned long STDCALL FPDFAnnot_GetText(FPDF_ANNOTATION annot, - FPDFANNOT_TEXTTYPE type, - void* buffer, - unsigned long buflen) { +DLLEXPORT unsigned long STDCALL FPDFAnnot_GetStringValue(FPDF_ANNOTATION annot, + FPDF_WIDESTRING key, + void* buffer, + unsigned long buflen) { if (!annot) return 0; @@ -675,8 +731,9 @@ DLLEXPORT unsigned long STDCALL FPDFAnnot_GetText(FPDF_ANNOTATION annot, if (!pAnnotDict) return 0; - CFX_ByteString key = type == FPDFANNOT_TEXTTYPE_Author ? "T" : "Contents"; - CFX_ByteString contents = pAnnotDict->GetUnicodeTextFor(key).UTF16LE_Encode(); + CFX_ByteString contents = + pAnnotDict->GetUnicodeTextFor(CFXByteStringFromFPDFWideString(key)) + .UTF16LE_Encode(); unsigned long len = contents.GetLength(); if (buffer && buflen >= len) memcpy(buffer, contents.c_str(), len); diff --git a/fpdfsdk/fpdfannot_embeddertest.cpp b/fpdfsdk/fpdfannot_embeddertest.cpp index 7d174ff11d..e62d7c5d45 100644 --- a/fpdfsdk/fpdfannot_embeddertest.cpp +++ b/fpdfsdk/fpdfannot_embeddertest.cpp @@ -59,21 +59,29 @@ TEST_F(FPDFAnnotEmbeddertest, ExtractHighlightLongContent) { EXPECT_EQ(255u, A); // Check that the author is correct. + std::unique_ptr author_key = + GetFPDFWideString(L"T"); + EXPECT_EQ(FPDF_OBJECT_STRING, + FPDFAnnot_GetValueType(annot, author_key.get())); unsigned long len = - FPDFAnnot_GetText(annot, FPDFANNOT_TEXTTYPE_Author, nullptr, 0); + FPDFAnnot_GetStringValue(annot, author_key.get(), nullptr, 0); std::vector buf(len); - EXPECT_EQ(28u, FPDFAnnot_GetText(annot, FPDFANNOT_TEXTTYPE_Author, buf.data(), - len)); + EXPECT_EQ(28u, + FPDFAnnot_GetStringValue(annot, author_key.get(), buf.data(), len)); EXPECT_STREQ(L"Jae Hyun Park", GetPlatformWString(reinterpret_cast(buf.data())) .c_str()); // Check that the content is correct. - len = FPDFAnnot_GetText(annot, FPDFANNOT_TEXTTYPE_Contents, nullptr, 0); + std::unique_ptr contents_key = + GetFPDFWideString(L"Contents"); + EXPECT_EQ(FPDF_OBJECT_STRING, + FPDFAnnot_GetValueType(annot, contents_key.get())); + len = FPDFAnnot_GetStringValue(annot, contents_key.get(), nullptr, 0); buf.clear(); buf.resize(len); - EXPECT_EQ(2690u, FPDFAnnot_GetText(annot, FPDFANNOT_TEXTTYPE_Contents, - buf.data(), len)); + EXPECT_EQ(2690u, FPDFAnnot_GetStringValue(annot, contents_key.get(), + buf.data(), len)); const wchar_t contents[] = L"This is a note for that highlight annotation. Very long highlight " "annotation. Long long long Long long longLong long longLong long " @@ -137,8 +145,10 @@ TEST_F(FPDFAnnotEmbeddertest, ExtractInkMultiple) { EXPECT_EQ(76u, A); // Check that there is no content. + std::unique_ptr contents_key = + GetFPDFWideString(L"Contents"); EXPECT_EQ(2u, - FPDFAnnot_GetText(annot, FPDFANNOT_TEXTTYPE_Contents, nullptr, 0)); + FPDFAnnot_GetStringValue(annot, contents_key.get(), nullptr, 0)); // Check that the rectange coordinates are correct. // Note that upon rendering, the rectangle coordinates will be adjusted. @@ -229,17 +239,18 @@ TEST_F(FPDFAnnotEmbeddertest, AddFirstTextAnnotation) { EXPECT_EQ(165.f, rect.top); // Set the content of the annotation. + std::unique_ptr contents_key = + GetFPDFWideString(L"Contents"); const wchar_t contents[] = L"Hello! This is a customized content."; std::unique_ptr text = GetFPDFWideString(contents); - ASSERT_TRUE( - FPDFAnnot_SetText(annot, FPDFANNOT_TEXTTYPE_Contents, text.get())); + ASSERT_TRUE(FPDFAnnot_SetStringValue(annot, contents_key.get(), text.get())); // Check that the content has been set correctly. unsigned long len = - FPDFAnnot_GetText(annot, FPDFANNOT_TEXTTYPE_Contents, nullptr, 0); + FPDFAnnot_GetStringValue(annot, contents_key.get(), nullptr, 0); std::vector buf(len); - EXPECT_EQ(74u, FPDFAnnot_GetText(annot, FPDFANNOT_TEXTTYPE_Contents, - buf.data(), len)); + EXPECT_EQ(74u, FPDFAnnot_GetStringValue(annot, contents_key.get(), buf.data(), + len)); EXPECT_STREQ(contents, GetPlatformWString(reinterpret_cast(buf.data())) .c_str()); @@ -766,3 +777,87 @@ TEST_F(FPDFAnnotEmbeddertest, AddAndModifyText) { UnloadPage(page); } + +TEST_F(FPDFAnnotEmbeddertest, GetSetStringValue) { + // 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 a non-existent key does not exist. + EXPECT_FALSE(FPDFAnnot_HasKey(annot, GetFPDFWideString(L"none").get())); + + // Check that the string value of a non-string dictionary entry is empty. + std::unique_ptr ap_key = + GetFPDFWideString(L"AP"); + EXPECT_TRUE(FPDFAnnot_HasKey(annot, ap_key.get())); + EXPECT_EQ(FPDF_OBJECT_REFERENCE, FPDFAnnot_GetValueType(annot, ap_key.get())); + EXPECT_EQ(2u, FPDFAnnot_GetStringValue(annot, ap_key.get(), nullptr, 0)); + + // Check that the string value of the hash is correct. + std::unique_ptr hash_key = + GetFPDFWideString(L"AAPL:Hash"); + EXPECT_EQ(FPDF_OBJECT_NAME, FPDFAnnot_GetValueType(annot, hash_key.get())); + unsigned long len = + FPDFAnnot_GetStringValue(annot, hash_key.get(), nullptr, 0); + std::vector buf(len); + EXPECT_EQ(66u, + FPDFAnnot_GetStringValue(annot, hash_key.get(), buf.data(), len)); + EXPECT_STREQ(L"395fbcb98d558681742f30683a62a2ad", + GetPlatformWString(reinterpret_cast(buf.data())) + .c_str()); + + // Check that the string value of the modified date is correct. + std::unique_ptr date_key = + GetFPDFWideString(L"M"); + EXPECT_EQ(FPDF_OBJECT_NAME, FPDFAnnot_GetValueType(annot, hash_key.get())); + len = FPDFAnnot_GetStringValue(annot, date_key.get(), nullptr, 0); + buf.clear(); + buf.resize(len); + EXPECT_EQ(44u, + FPDFAnnot_GetStringValue(annot, date_key.get(), buf.data(), len)); + EXPECT_STREQ(L"D:201706071721Z00'00'", + GetPlatformWString(reinterpret_cast(buf.data())) + .c_str()); + + // Update the date entry for the annotation. + const wchar_t new_date[] = L"D:201706282359Z00'00'"; + std::unique_ptr text = + GetFPDFWideString(new_date); + EXPECT_TRUE(FPDFAnnot_SetStringValue(annot, date_key.get(), text.get())); + + // Save the document, closing the page and document. + FPDFPage_CloseAnnot(annot); + EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); + FPDF_ClosePage(page); + + // Open the saved annotation. +#if _FXM_PLATFORM_ == _FXM_PLATFORM_APPLE_ + const char md5[] = "c35408717759562d1f8bf33d317483d2"; +#elif _FXM_PLATFORM_ == _FXM_PLATFORM_WINDOWS_ + const char md5[] = "bdf96279ab82d9f484874db3f0c03429"; +#else + const char md5[] = "07d4168715553b4294525f840c40aa1c"; +#endif + TestSaved(595, 842, md5); + FPDF_ANNOTATION new_annot = FPDFPage_GetAnnot(m_SavedPage, 0); + + // Check that the string value of the modified date is the newly-set value. + EXPECT_EQ(FPDF_OBJECT_STRING, + FPDFAnnot_GetValueType(new_annot, date_key.get())); + len = FPDFAnnot_GetStringValue(new_annot, date_key.get(), nullptr, 0); + buf.clear(); + buf.resize(len); + EXPECT_EQ(44u, FPDFAnnot_GetStringValue(new_annot, date_key.get(), buf.data(), + len)); + EXPECT_STREQ(new_date, + GetPlatformWString(reinterpret_cast(buf.data())) + .c_str()); + + FPDFPage_CloseAnnot(new_annot); + CloseSaved(); +} diff --git a/fpdfsdk/fpdfview_c_api_test.c b/fpdfsdk/fpdfview_c_api_test.c index 0027aa4b52..e6a58734d2 100644 --- a/fpdfsdk/fpdfview_c_api_test.c +++ b/fpdfsdk/fpdfview_c_api_test.c @@ -53,8 +53,9 @@ int CheckPDFiumCApi() { CHK(FPDFAnnot_GetAttachmentPoints); CHK(FPDFAnnot_SetRect); CHK(FPDFAnnot_GetRect); - CHK(FPDFAnnot_SetText); - CHK(FPDFAnnot_GetText); + CHK(FPDFAnnot_HasKey); + CHK(FPDFAnnot_SetStringValue); + CHK(FPDFAnnot_GetStringValue); CHK(FPDFAnnot_GetFlags); CHK(FPDFAnnot_SetFlags); diff --git a/public/fpdf_annot.h b/public/fpdf_annot.h index 7c3b7247c8..31779a5cd8 100644 --- a/public/fpdf_annot.h +++ b/public/fpdf_annot.h @@ -57,16 +57,22 @@ extern "C" { #define FPDF_ANNOT_FLAG_LOCKED (1 << 7) #define FPDF_ANNOT_FLAG_TOGGLENOVIEW (1 << 8) +#define FPDF_OBJECT_UNKNOWN 0 +#define FPDF_OBJECT_BOOLEAN 1 +#define FPDF_OBJECT_NUMBER 2 +#define FPDF_OBJECT_STRING 3 +#define FPDF_OBJECT_NAME 4 +#define FPDF_OBJECT_ARRAY 5 +#define FPDF_OBJECT_DICTIONARY 6 +#define FPDF_OBJECT_STREAM 7 +#define FPDF_OBJECT_NULLOBJ 8 +#define FPDF_OBJECT_REFERENCE 9 + typedef enum FPDFANNOT_COLORTYPE { FPDFANNOT_COLORTYPE_Color = 0, FPDFANNOT_COLORTYPE_InteriorColor } FPDFANNOT_COLORTYPE; -typedef enum FPDFANNOT_TEXTTYPE { - FPDFANNOT_TEXTTYPE_Contents = 0, - FPDFANNOT_TEXTTYPE_Author -} FPDFANNOT_TEXTTYPE; - // Experimental API. // Check if an annotation subtype is currently supported for creation. // Currently supported subtypes: circle, highlight, ink, popup, square, @@ -281,31 +287,58 @@ DLLEXPORT FPDF_BOOL STDCALL FPDFAnnot_SetRect(FPDF_ANNOTATION annot, DLLEXPORT FS_RECTF STDCALL FPDFAnnot_GetRect(FPDF_ANNOTATION annot); // Experimental API. -// Set the contents of an annotation. +// Check if |annot|'s dictionary has |key| as a key. +// +// annot - handle to an annotation. +// key - the key to look for. +// +// Returns true if |key| exists. +DLLEXPORT FPDF_BOOL STDCALL FPDFAnnot_HasKey(FPDF_ANNOTATION annot, + FPDF_WIDESTRING key); + +// Experimental API. +// Get the type of the value corresponding to |key| in |annot|'s dictioanry. +// +// annot - handle to an annotation. +// key - the key to look for. +// +// Returns the type of the dictionary value. +DLLEXPORT FPDF_OBJECT_TYPE STDCALL FPDFAnnot_GetValueType(FPDF_ANNOTATION annot, + FPDF_WIDESTRING key); + +// Experimental API. +// Set the string value corresponding to |key| in |annot|'s dictionary, +// overwriting the existing value if any. The value type would be +// FPDF_OBJECT_STRING after this function call. // // annot - handle to an annotation. -// type - type of the text to be set. -// text - the text to be set. +// key - the key to the dictionary entry to be set, encoded in UTF16-LE. +// value - the string value to be set, encoded in UTF16-LE. // // Returns true if successful. -DLLEXPORT FPDF_BOOL STDCALL FPDFAnnot_SetText(FPDF_ANNOTATION annot, - FPDFANNOT_TEXTTYPE type, - FPDF_WIDESTRING text); +DLLEXPORT FPDF_BOOL STDCALL FPDFAnnot_SetStringValue(FPDF_ANNOTATION annot, + FPDF_WIDESTRING key, + FPDF_WIDESTRING value); // Experimental API. -// Get the contents of an annotation. |buffer| is only modified if |buflen| -// is longer than the length of contents. +// Get the string value corresponding to |key| in |annot|'s dictionary. |buffer| +// is only modified if |buflen| is longer than the length of contents. Note that +// if |key| does not exist in the dictionary or if |key|'s corresponding value +// in the dictionary is not a string (i.e. the value is not of type +// FPDF_OBJECT_STRING or FPDF_OBJECT_NAME), then an empty string would be copied +// to |buffer| and the return value would be 2. On other errors, nothing would +// be added to |buffer| and the return value would be 0. // // annot - handle to an annotation. -// type - type of the text requested. -// buffer - buffer for holding the contents string, encoded in UTF16-LE. +// key - the key to the requested dictionary entry. +// buffer - buffer for holding the value string, encoded in UTF16-LE. // buflen - length of the buffer. // -// Returns the length of the contents. -DLLEXPORT unsigned long STDCALL FPDFAnnot_GetText(FPDF_ANNOTATION annot, - FPDFANNOT_TEXTTYPE type, - void* buffer, - unsigned long buflen); +// Returns the length of the string value. +DLLEXPORT unsigned long STDCALL FPDFAnnot_GetStringValue(FPDF_ANNOTATION annot, + FPDF_WIDESTRING key, + void* buffer, + unsigned long buflen); // Experimental API. // Get the annotation flags of |annot|. diff --git a/public/fpdfview.h b/public/fpdfview.h index ba5cb4ff66..1ff0aeb267 100644 --- a/public/fpdfview.h +++ b/public/fpdfview.h @@ -126,6 +126,9 @@ typedef const FS_RECTF* FS_LPCRECTF; // Annotation subtype. typedef int FPDF_ANNOTATION_SUBTYPE; +// Dictionary value types. +typedef int FPDF_OBJECT_TYPE; + #if defined(_WIN32) && defined(FPDFSDK_EXPORTS) // On Windows system, functions are exported in a DLL #define DLLEXPORT __declspec(dllexport) diff --git a/samples/pdfium_test.cc b/samples/pdfium_test.cc index 5a3668f6ce..5eccf22554 100644 --- a/samples/pdfium_test.cc +++ b/samples/pdfium_test.cc @@ -303,17 +303,21 @@ void WriteAnnot(FPDF_PAGE page, const char* pdf_name, int num) { } // Retrieve the annotation's contents and author. + std::unique_ptr contents_key = + GetFPDFWideString(L"Contents"); unsigned long len = - FPDFAnnot_GetText(annot, FPDFANNOT_TEXTTYPE_Contents, nullptr, 0); + FPDFAnnot_GetStringValue(annot, contents_key.get(), nullptr, 0); std::vector buf(len); - FPDFAnnot_GetText(annot, FPDFANNOT_TEXTTYPE_Contents, buf.data(), len); + FPDFAnnot_GetStringValue(annot, contents_key.get(), buf.data(), len); fprintf(fp, "Content: %ls\n", GetPlatformWString(reinterpret_cast(buf.data())) .c_str()); - len = FPDFAnnot_GetText(annot, FPDFANNOT_TEXTTYPE_Author, nullptr, 0); + std::unique_ptr author_key = + GetFPDFWideString(L"T"); + len = FPDFAnnot_GetStringValue(annot, author_key.get(), nullptr, 0); buf.clear(); buf.resize(len); - FPDFAnnot_GetText(annot, FPDFANNOT_TEXTTYPE_Author, buf.data(), len); + FPDFAnnot_GetStringValue(annot, author_key.get(), buf.data(), len); fprintf(fp, "Author: %ls\n", GetPlatformWString(reinterpret_cast(buf.data())) .c_str()); -- cgit v1.2.3