From 20eafda108cf9b0ab336fb8ab5d83a011f7b8307 Mon Sep 17 00:00:00 2001 From: Jane Liu Date: Wed, 7 Jun 2017 10:33:24 -0400 Subject: Basic APIs and tests for creating annotations 1. Added API for adding annotations and modifying common annotation properties * Added three embedder tests covering all of the API functions. Bug=pdfium:737 Change-Id: I369d9e17f589f896f9e8c672382f082e524ae534 Reviewed-on: https://pdfium-review.googlesource.com/6351 Commit-Queue: dsinclair Reviewed-by: Lei Zhang Reviewed-by: dsinclair --- fpdfsdk/fpdfannot.cpp | 137 ++++++++++++++++++++++++++++++++ fpdfsdk/fpdfannot_embeddertest.cpp | 157 ++++++++++++++++++++++++++++++++++++- 2 files changed, 292 insertions(+), 2 deletions(-) (limited to 'fpdfsdk') diff --git a/fpdfsdk/fpdfannot.cpp b/fpdfsdk/fpdfannot.cpp index 6e5b7ba063..941a5fdd80 100644 --- a/fpdfsdk/fpdfannot.cpp +++ b/fpdfsdk/fpdfannot.cpp @@ -6,9 +6,15 @@ #include "public/fpdf_annot.h" +#include + #include "core/fpdfapi/page/cpdf_page.h" #include "core/fpdfapi/parser/cpdf_array.h" #include "core/fpdfapi/parser/cpdf_dictionary.h" +#include "core/fpdfapi/parser/cpdf_document.h" +#include "core/fpdfapi/parser/cpdf_name.h" +#include "core/fpdfapi/parser/cpdf_number.h" +#include "core/fpdfapi/parser/cpdf_string.h" #include "core/fpdfdoc/cpdf_annot.h" #include "core/fpdfdoc/cpvt_color.h" #include "core/fpdfdoc/cpvt_generateap.h" @@ -92,10 +98,45 @@ static_assert(static_cast(CPDF_Annot::Subtype::XFAWIDGET) == FPDF_ANNOT_XFAWIDGET, "CPDF_Annot::XFAWIDGET value mismatch"); +DLLEXPORT FPDF_BOOL STDCALL +FPDFAnnot_IsSupportedSubtype(FPDF_ANNOTATION_SUBTYPE subtype) { + return subtype == FPDF_ANNOT_CIRCLE || subtype == FPDF_ANNOT_HIGHLIGHT || + subtype == FPDF_ANNOT_INK || subtype == FPDF_ANNOT_POPUP || + subtype == FPDF_ANNOT_SQUARE || subtype == FPDF_ANNOT_SQUIGGLY || + subtype == FPDF_ANNOT_STRIKEOUT || subtype == FPDF_ANNOT_TEXT || + subtype == FPDF_ANNOT_UNDERLINE; +} + +DLLEXPORT FPDF_BOOL STDCALL +FPDFPage_CreateAnnot(FPDF_PAGE page, + FPDF_ANNOTATION_SUBTYPE subtype, + FPDF_ANNOTATION* annot) { + CPDF_Page* pPage = CPDFPageFromFPDFPage(page); + if (!pPage || !FPDFAnnot_IsSupportedSubtype(subtype)) + return false; + + auto pDict = pdfium::MakeUnique( + pPage->m_pDocument->GetByteStringPool()); + pDict->SetNewFor("Type", "Annot"); + pDict->SetNewFor("Subtype", + CPDF_Annot::AnnotSubtypeToString( + static_cast(subtype))); + if (annot) + *annot = FPDFAnnotationFromCPDFDictionary(pDict.get()); + + CPDF_Array* pAnnotList = pPage->m_pFormDict->GetArrayFor("Annots"); + if (!pAnnotList) + pAnnotList = pPage->m_pFormDict->SetNewFor("Annots"); + + pAnnotList->Add(std::move(pDict)); + return true; +} + DLLEXPORT int STDCALL FPDFPage_GetAnnotCount(FPDF_PAGE page) { CPDF_Page* pPage = CPDFPageFromFPDFPage(page); if (!pPage || !pPage->m_pFormDict) return 0; + CPDF_Array* pAnnots = pPage->m_pFormDict->GetArrayFor("Annots"); return pAnnots ? pAnnots->GetCount() : 0; } @@ -106,6 +147,7 @@ DLLEXPORT FPDF_BOOL STDCALL FPDFPage_GetAnnot(FPDF_PAGE page, CPDF_Page* pPage = CPDFPageFromFPDFPage(page); if (!pPage || !pPage->m_pFormDict || index < 0 || !annot) return false; + CPDF_Array* pAnnots = pPage->m_pFormDict->GetArrayFor("Annots"); if (!pAnnots || static_cast(index) >= pAnnots->GetCount()) return false; @@ -120,10 +162,39 @@ FPDFAnnot_GetSubtype(FPDF_ANNOTATION annot) { CPDF_Dictionary* pAnnotDict = CPDFDictionaryFromFPDFAnnotation(annot); if (!pAnnotDict) return FPDF_ANNOT_UNKNOWN; + return static_cast( CPDF_Annot::StringToAnnotSubtype(pAnnotDict->GetStringFor("Subtype"))); } +DLLEXPORT FPDF_BOOL STDCALL FPDFAnnot_SetColor(FPDF_ANNOTATION annot, + FPDFANNOT_COLORTYPE type, + unsigned int R, + unsigned int G, + unsigned int B, + unsigned int A) { + CPDF_Dictionary* pAnnotDict = CPDFDictionaryFromFPDFAnnotation(annot); + if (!pAnnotDict || R > 255 || G > 255 || B > 255 || A > 255) + return false; + + // Set the opacity of the annotation. + pAnnotDict->SetNewFor("CA", A / 255.f); + + // Set the color of the annotation. + CFX_ByteString key = type == FPDFANNOT_COLORTYPE_InteriorColor ? "IC" : "C"; + CPDF_Array* pColor = pAnnotDict->GetArrayFor(key); + if (pColor) + pColor->RemoveAt(0, pColor->GetCount()); + else + pColor = pAnnotDict->SetNewFor(key); + + pColor->AddNew(R / 255.f); + pColor->AddNew(G / 255.f); + pColor->AddNew(B / 255.f); + + return true; +} + DLLEXPORT FPDF_BOOL STDCALL FPDFAnnot_GetColor(FPDF_ANNOTATION annot, FPDFANNOT_COLORTYPE type, unsigned int* R, @@ -183,21 +254,48 @@ DLLEXPORT FPDF_BOOL STDCALL FPDFAnnot_HasAttachmentPoints(FPDF_ANNOTATION annot) { if (!annot) return false; + FPDF_ANNOTATION_SUBTYPE subtype = FPDFAnnot_GetSubtype(annot); return subtype == FPDF_ANNOT_LINK || subtype == FPDF_ANNOT_HIGHLIGHT || subtype == FPDF_ANNOT_UNDERLINE || subtype == FPDF_ANNOT_SQUIGGLY || subtype == FPDF_ANNOT_STRIKEOUT; } +DLLEXPORT FPDF_BOOL STDCALL +FPDFAnnot_SetAttachmentPoints(FPDF_ANNOTATION annot, + FS_QUADPOINTSF quadPoints) { + if (!annot || !FPDFAnnot_HasAttachmentPoints(annot)) + return false; + + CPDF_Dictionary* pAnnotDict = CPDFDictionaryFromFPDFAnnotation(annot); + CPDF_Array* pQuadPoints = pAnnotDict->GetArrayFor("QuadPoints"); + if (pQuadPoints) + pQuadPoints->RemoveAt(0, pQuadPoints->GetCount()); + else + pQuadPoints = pAnnotDict->SetNewFor("QuadPoints"); + + pQuadPoints->AddNew(quadPoints.x1); + pQuadPoints->AddNew(quadPoints.y1); + pQuadPoints->AddNew(quadPoints.x2); + pQuadPoints->AddNew(quadPoints.y2); + pQuadPoints->AddNew(quadPoints.x3); + pQuadPoints->AddNew(quadPoints.y3); + pQuadPoints->AddNew(quadPoints.x4); + pQuadPoints->AddNew(quadPoints.y4); + return true; +} + DLLEXPORT FPDF_BOOL STDCALL FPDFAnnot_GetAttachmentPoints(FPDF_ANNOTATION annot, FS_QUADPOINTSF* quadPoints) { if (!annot || !quadPoints || !FPDFAnnot_HasAttachmentPoints(annot)) return false; + CPDF_Array* pArray = CPDFDictionaryFromFPDFAnnotation(annot)->GetArrayFor("QuadPoints"); if (!pArray) return false; + quadPoints->x1 = pArray->GetNumberAt(0); quadPoints->y1 = pArray->GetNumberAt(1); quadPoints->x2 = pArray->GetNumberAt(2); @@ -209,12 +307,35 @@ FPDFAnnot_GetAttachmentPoints(FPDF_ANNOTATION annot, return true; } +DLLEXPORT FPDF_BOOL STDCALL FPDFAnnot_SetRect(FPDF_ANNOTATION annot, + FS_RECTF rect) { + CPDF_Dictionary* pAnnotDict = CPDFDictionaryFromFPDFAnnotation(annot); + if (!pAnnotDict) + return false; + + CPDF_Array* pRect = pAnnotDict->GetArrayFor("Rect"); + if (pRect) + pRect->RemoveAt(0, pRect->GetCount()); + else + pRect = pAnnotDict->SetNewFor("Rect"); + + pRect->AddNew(rect.left); + pRect->AddNew(rect.bottom); + pRect->AddNew(rect.right); + pRect->AddNew(rect.top); + return true; +} + DLLEXPORT FPDF_BOOL STDCALL FPDFAnnot_GetRect(FPDF_ANNOTATION annot, FS_RECTF* rect) { CPDF_Dictionary* pAnnotDict = CPDFDictionaryFromFPDFAnnotation(annot); if (!rect || !pAnnotDict) return false; + CFX_FloatRect rt = pAnnotDict->GetRectFor("Rect"); + if (rt.IsEmpty()) + return false; + rect->left = rt.left; rect->bottom = rt.bottom; rect->right = rt.right; @@ -222,6 +343,20 @@ DLLEXPORT FPDF_BOOL STDCALL FPDFAnnot_GetRect(FPDF_ANNOTATION annot, return true; } +DLLEXPORT FPDF_BOOL STDCALL FPDFAnnot_SetText(FPDF_ANNOTATION annot, + FPDFANNOT_TEXTTYPE type, + FPDF_WIDESTRING text) { + CPDF_Dictionary* pAnnotDict = CPDFDictionaryFromFPDFAnnotation(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); + return true; +} + DLLEXPORT unsigned long STDCALL FPDFAnnot_GetText(FPDF_ANNOTATION annot, FPDFANNOT_TEXTTYPE type, char* buffer, @@ -229,10 +364,12 @@ DLLEXPORT unsigned long STDCALL FPDFAnnot_GetText(FPDF_ANNOTATION annot, CPDF_Dictionary* pAnnotDict = CPDFDictionaryFromFPDFAnnotation(annot); if (!pAnnotDict) return 0; + CFX_ByteString key = type == FPDFANNOT_TEXTTYPE_Author ? "T" : "Contents"; CFX_ByteString contents = pAnnotDict->GetUnicodeTextFor(key).UTF16LE_Encode(); unsigned long len = contents.GetLength(); if (buffer && buflen >= len) memcpy(buffer, contents.c_str(), len); + return len; } diff --git a/fpdfsdk/fpdfannot_embeddertest.cpp b/fpdfsdk/fpdfannot_embeddertest.cpp index 27a76eecfa..1f56d22c28 100644 --- a/fpdfsdk/fpdfannot_embeddertest.cpp +++ b/fpdfsdk/fpdfannot_embeddertest.cpp @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include +#include #include #include "public/fpdf_annot.h" @@ -9,7 +11,7 @@ #include "testing/embedder_test.h" #include "testing/gtest/include/gtest/gtest.h" -class FPDFAnnotEmbeddertest : public EmbedderTest {}; +class FPDFAnnotEmbeddertest : public EmbedderTest, public TestSaver {}; TEST_F(FPDFAnnotEmbeddertest, ExtractHighlightLongContent) { // Open a file with one annotation and load its first page. @@ -98,7 +100,7 @@ TEST_F(FPDFAnnotEmbeddertest, ExtractInkMultiple) { // Check that there is a total of 3 annotation on its first page. EXPECT_EQ(3, FPDFPage_GetAnnotCount(page)); - // Check that the third annotation of type "ink". + // Check that the third annotation is of type "ink". FPDF_ANNOTATION annot; ASSERT_TRUE(FPDFPage_GetAnnot(page, 2, &annot)); EXPECT_EQ(FPDF_ANNOT_INK, FPDFAnnot_GetSubtype(annot)); @@ -130,3 +132,154 @@ TEST_F(FPDFAnnotEmbeddertest, ExtractInkMultiple) { UnloadPage(page); } + +TEST_F(FPDFAnnotEmbeddertest, AddIllegalSubtypeAnnotation) { + // Open a file with one annotation and load its first page. + ASSERT_TRUE(OpenDocument("annotation_highlight_long_content.pdf")); + FPDF_PAGE page = FPDF_LoadPage(document(), 0); + ASSERT_TRUE(page); + + // Add an annotation with an illegal subtype. + FPDF_ANNOTATION annot; + ASSERT_FALSE(FPDFPage_CreateAnnot(page, -1, &annot)); + + UnloadPage(page); +} + +TEST_F(FPDFAnnotEmbeddertest, AddFirstTextAnnotation) { + // Open a file with no annotation and load its first page. + ASSERT_TRUE(OpenDocument("hello_world.pdf")); + FPDF_PAGE page = FPDF_LoadPage(document(), 0); + ASSERT_TRUE(page); + EXPECT_EQ(0, FPDFPage_GetAnnotCount(page)); + + // Add an underline annotation to the page. + FPDF_ANNOTATION annot; + ASSERT_TRUE(FPDFPage_CreateAnnot(page, FPDF_ANNOT_TEXT, &annot)); + + // Check that there is now 1 annotations on this page. + EXPECT_EQ(1, FPDFPage_GetAnnotCount(page)); + + // Check that the subtype of the annotation is correct. + EXPECT_EQ(FPDF_ANNOT_TEXT, FPDFAnnot_GetSubtype(annot)); + ASSERT_TRUE(FPDFPage_GetAnnot(page, 0, &annot)); + EXPECT_EQ(FPDF_ANNOT_TEXT, FPDFAnnot_GetSubtype(annot)); + + // Set the color of the annotation. + ASSERT_TRUE( + FPDFAnnot_SetColor(annot, FPDFANNOT_COLORTYPE_Color, 51, 102, 153, 204)); + // Check that the color has been set correctly. + unsigned int R; + unsigned int G; + unsigned int B; + unsigned int A; + EXPECT_TRUE( + FPDFAnnot_GetColor(annot, FPDFANNOT_COLORTYPE_Color, &R, &G, &B, &A)); + EXPECT_EQ(51u, R); + EXPECT_EQ(102u, G); + EXPECT_EQ(153u, B); + EXPECT_EQ(204u, A); + + // Change the color of the annotation. + ASSERT_TRUE( + FPDFAnnot_SetColor(annot, FPDFANNOT_COLORTYPE_Color, 204, 153, 102, 51)); + // Check that the color has been set correctly. + EXPECT_TRUE( + FPDFAnnot_GetColor(annot, FPDFANNOT_COLORTYPE_Color, &R, &G, &B, &A)); + EXPECT_EQ(204u, R); + EXPECT_EQ(153u, G); + EXPECT_EQ(102u, B); + EXPECT_EQ(51u, A); + + // Set the annotation rectangle. + FS_RECTF rect; + EXPECT_FALSE(FPDFAnnot_GetRect(annot, &rect)); + rect.left = 35; + rect.bottom = 150; + rect.right = 53; + rect.top = 165; + ASSERT_TRUE(FPDFAnnot_SetRect(annot, rect)); + // Check that the annotation rectangle has been set correctly. + ASSERT_TRUE(FPDFAnnot_GetRect(annot, &rect)); + EXPECT_EQ(35.f, rect.left); + EXPECT_EQ(150.f, rect.bottom); + EXPECT_EQ(53.f, rect.right); + EXPECT_EQ(165.f, rect.top); + + // Set the content of the annotation. + 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())); + // Check that the content has been set correctly. + unsigned long len = + FPDFAnnot_GetText(annot, FPDFANNOT_TEXTTYPE_Contents, nullptr, 0); + std::vector buf(len); + EXPECT_EQ(74u, FPDFAnnot_GetText(annot, FPDFANNOT_TEXTTYPE_Contents, + buf.data(), len)); + EXPECT_STREQ(contents, + GetPlatformWString(reinterpret_cast(buf.data())) + .c_str()); + + UnloadPage(page); +} + +TEST_F(FPDFAnnotEmbeddertest, AddAndSaveUnderlineAnnotation) { + // Open a file with one annotation and load its first page. + ASSERT_TRUE(OpenDocument("annotation_highlight_long_content.pdf")); + FPDF_PAGE page = FPDF_LoadPage(document(), 0); + ASSERT_TRUE(page); + + // Check that there is a total of one annotation on its first page, and verify + // its quadpoints. + EXPECT_EQ(1, FPDFPage_GetAnnotCount(page)); + FPDF_ANNOTATION annot; + ASSERT_TRUE(FPDFPage_GetAnnot(page, 0, &annot)); + FS_QUADPOINTSF quadpoints; + ASSERT_TRUE(FPDFAnnot_GetAttachmentPoints(annot, &quadpoints)); + EXPECT_EQ(115.802643f, quadpoints.x1); + EXPECT_EQ(718.913940f, quadpoints.y1); + EXPECT_EQ(157.211182f, quadpoints.x4); + EXPECT_EQ(706.264465f, quadpoints.y4); + + // Add an underline annotation to the page and set its quadpoints. + ASSERT_TRUE(FPDFPage_CreateAnnot(page, FPDF_ANNOT_UNDERLINE, &annot)); + quadpoints.x1 = 140.802643f; + quadpoints.x3 = 140.802643f; + ASSERT_TRUE(FPDFAnnot_SetAttachmentPoints(annot, quadpoints)); + + // Save the document, closing the page and document. + EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0)); + FPDF_ClosePage(page); + + // Open the saved document. + std::string new_file = GetString(); + FPDF_FILEACCESS file_access; + memset(&file_access, 0, sizeof(file_access)); + file_access.m_FileLen = new_file.size(); + file_access.m_GetBlock = GetBlockFromString; + file_access.m_Param = &new_file; + FPDF_DOCUMENT new_doc = FPDF_LoadCustomDocument(&file_access, nullptr); + ASSERT_TRUE(new_doc); + FPDF_PAGE new_page = FPDF_LoadPage(new_doc, 0); + ASSERT_TRUE(new_page); + + // Check that the saved document has 2 annotations on the first page + EXPECT_EQ(2, FPDFPage_GetAnnotCount(new_page)); + + // Check that the second annotation is an underline annotation and verify + // its quadpoints. + FPDF_ANNOTATION new_annot; + ASSERT_TRUE(FPDFPage_GetAnnot(new_page, 1, &new_annot)); + EXPECT_EQ(FPDF_ANNOT_UNDERLINE, FPDFAnnot_GetSubtype(new_annot)); + FS_QUADPOINTSF new_quadpoints; + ASSERT_TRUE(FPDFAnnot_GetAttachmentPoints(new_annot, &new_quadpoints)); + EXPECT_NEAR(quadpoints.x1, new_quadpoints.x1, 0.001f); + EXPECT_NEAR(quadpoints.y1, new_quadpoints.y1, 0.001f); + EXPECT_NEAR(quadpoints.x4, new_quadpoints.x4, 0.001f); + EXPECT_NEAR(quadpoints.y4, new_quadpoints.y4, 0.001f); + + FPDF_ClosePage(new_page); + FPDF_CloseDocument(new_doc); +} -- cgit v1.2.3