From baa7ff4390b8b36c2d58c1b1f8d9775c76da656a Mon Sep 17 00:00:00 2001 From: Jane Liu Date: Thu, 29 Jun 2017 19:18:23 -0400 Subject: Basic APIs and tests for extracting and setting annotation paths 1. Added APIs for retrieving existing annotation paths and setting annotation paths. * Added an embedder test testing all the new functions. Bug=pdfium:737 Change-Id: Ic451bcd3be488261baf2182549c4238b887b219e Reviewed-on: https://pdfium-review.googlesource.com/6676 Commit-Queue: Jane Liu Reviewed-by: Lei Zhang Reviewed-by: dsinclair --- core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp | 3 + .../edit/cpdf_pagecontentgenerator_unittest.cpp | 22 +-- core/fpdfdoc/cpvt_generateap.cpp | 124 ++++++++------- core/fpdfdoc/cpvt_generateap.h | 14 ++ fpdfsdk/fpdfannot.cpp | 174 ++++++++++++++++++++- fpdfsdk/fpdfannot_embeddertest.cpp | 119 +++++++++++++- fpdfsdk/fpdfview.cpp | 4 + fpdfsdk/fpdfview_c_api_test.c | 4 + fpdfsdk/fsdk_define.h | 3 + public/fpdf_annot.h | 55 ++++++- testing/embedder_test.cpp | 8 +- testing/embedder_test.h | 4 +- testing/resources/annotation_stamp_with_ap.pdf | Bin 0 -> 101719 bytes 13 files changed, 452 insertions(+), 82 deletions(-) create mode 100644 testing/resources/annotation_stamp_with_ap.pdf diff --git a/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp b/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp index 369c2a81ff..18bcbf909d 100644 --- a/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp +++ b/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp @@ -209,6 +209,9 @@ void CPDF_PageContentGenerator::ProcessImage(std::ostringstream* buf, void CPDF_PageContentGenerator::ProcessPath(std::ostringstream* buf, CPDF_PathObject* pPathObj) { ProcessGraphics(buf, pPathObj); + + *buf << pPathObj->m_Matrix << " cm "; + auto& pPoints = pPathObj->m_Path.GetPoints(); if (pPathObj->m_Path.IsRect()) { CFX_PointF diff = pPoints[2].m_Point - pPoints[0].m_Point; diff --git a/core/fpdfapi/edit/cpdf_pagecontentgenerator_unittest.cpp b/core/fpdfapi/edit/cpdf_pagecontentgenerator_unittest.cpp index ef186666f1..62b10c9149 100644 --- a/core/fpdfapi/edit/cpdf_pagecontentgenerator_unittest.cpp +++ b/core/fpdfapi/edit/cpdf_pagecontentgenerator_unittest.cpp @@ -58,7 +58,7 @@ TEST_F(CPDF_PageContentGeneratorTest, ProcessRect) { CPDF_PageContentGenerator generator(pTestPage.get()); std::ostringstream buf; TestProcessPath(&generator, &buf, pPathObj.get()); - EXPECT_EQ("q 10 5 3 25 re B* Q\n", CFX_ByteString(buf)); + EXPECT_EQ("q 1 0 0 1 0 0 cm 10 5 3 25 re B* Q\n", CFX_ByteString(buf)); pPathObj = pdfium::MakeUnique(); pPathObj->m_Path.AppendPoint(CFX_PointF(0, 0), FXPT_TYPE::MoveTo, false); @@ -71,7 +71,7 @@ TEST_F(CPDF_PageContentGeneratorTest, ProcessRect) { buf.str(""); TestProcessPath(&generator, &buf, pPathObj.get()); - EXPECT_EQ("q 0 0 5.2 3.78 re n Q\n", CFX_ByteString(buf)); + EXPECT_EQ("q 1 0 0 1 0 0 cm 0 0 5.2 3.78 re n Q\n", CFX_ByteString(buf)); } TEST_F(CPDF_PageContentGeneratorTest, ProcessPath) { @@ -103,8 +103,8 @@ TEST_F(CPDF_PageContentGeneratorTest, ProcessPath) { std::ostringstream buf; TestProcessPath(&generator, &buf, pPathObj.get()); EXPECT_EQ( - "q 3.102 4.67 m 5.45 0.29 l 4.24 3.15 4.65 2.98 3.456 0.24 c 10.6 11.15 " - "l 11 12.5 l 11.46 12.67 11.84 12.96 12 13.64 c h f Q\n", + "q 1 0 0 1 0 0 cm 3.102 4.67 m 5.45 0.29 l 4.24 3.15 4.65 2.98 3.456 0.24" + " c 10.6 11.15 l 11 12.5 l 11.46 12.67 11.84 12.96 12 13.64 c h f Q\n", CFX_ByteString(buf)); } @@ -137,10 +137,11 @@ TEST_F(CPDF_PageContentGeneratorTest, ProcessGraphics) { // Color RGB values used are integers divided by 255. EXPECT_EQ("q 0.501961 0.701961 0.34902 rg 1 0.901961 0 RG /", pathString.Left(48)); - EXPECT_EQ(" gs 1 2 m 3 4 l 5 6 l h B Q\n", pathString.Right(28)); - ASSERT_TRUE(pathString.GetLength() > 76); + EXPECT_EQ(" gs 1 0 0 1 0 0 cm 1 2 m 3 4 l 5 6 l h B Q\n", + pathString.Right(43)); + ASSERT_TRUE(pathString.GetLength() > 91); CPDF_Dictionary* externalGS = TestGetResource( - &generator, "ExtGState", pathString.Mid(48, pathString.GetLength() - 76)); + &generator, "ExtGState", pathString.Mid(48, pathString.GetLength() - 91)); ASSERT_TRUE(externalGS); EXPECT_EQ(0.5f, externalGS->GetNumberFor("ca")); EXPECT_EQ(0.8f, externalGS->GetNumberFor("CA")); @@ -152,7 +153,8 @@ TEST_F(CPDF_PageContentGeneratorTest, ProcessGraphics) { CFX_ByteString pathString2(buf); EXPECT_EQ("q 0.501961 0.701961 0.34902 rg 1 0.901961 0 RG 10.5 w /", pathString2.Left(55)); - EXPECT_EQ(" gs 1 2 m 3 4 l 5 6 l h B Q\n", pathString2.Right(28)); + EXPECT_EQ(" gs 1 0 0 1 0 0 cm 1 2 m 3 4 l 5 6 l h B Q\n", + pathString2.Right(43)); // Compare with the previous (should use same dictionary for gs) EXPECT_EQ(pathString.GetLength() + 7, pathString2.GetLength()); @@ -302,8 +304,8 @@ TEST_F(CPDF_PageContentGeneratorTest, ProcessFormWithPath) { pDoc->CreateNewDoc(); auto pDict = pdfium::MakeUnique(); const char content[] = - "q 3.102 4.67 m 5.45 0.29 l 4.24 3.15 4.65 2.98 3.456 0.24 c 3.102 4.67 " - "l h f Q\n"; + "q 1 0 0 1 0 0 cm 3.102 4.67 m 5.45 0.29 l 4.24 3.15 4.65 2.98 3.456 " + "0.24 c 3.102 4.67 l h f Q\n"; size_t buf_len = FX_ArraySize(content); std::unique_ptr buf(FX_Alloc(uint8_t, buf_len)); memcpy(buf.get(), content, buf_len); diff --git a/core/fpdfdoc/cpvt_generateap.cpp b/core/fpdfdoc/cpvt_generateap.cpp index ff7f4b68ee..eb874b3352 100644 --- a/core/fpdfdoc/cpvt_generateap.cpp +++ b/core/fpdfdoc/cpvt_generateap.cpp @@ -545,27 +545,6 @@ CFX_ByteString GetPopupContentsString(CPDF_Document* pDoc, return CFX_ByteString(sAppStream); } -std::unique_ptr GenerateExtGStateDict( - const CPDF_Dictionary& pAnnotDict, - const CFX_ByteString& sExtGSDictName, - const CFX_ByteString& sBlendMode) { - auto pGSDict = - pdfium::MakeUnique(pAnnotDict.GetByteStringPool()); - pGSDict->SetNewFor("Type", "ExtGState", false); - - float fOpacity = - pAnnotDict.KeyExist("CA") ? pAnnotDict.GetNumberFor("CA") : 1; - pGSDict->SetNewFor("CA", fOpacity); - pGSDict->SetNewFor("ca", fOpacity); - pGSDict->SetNewFor("AIS", false); - pGSDict->SetNewFor("BM", sBlendMode, false); - - auto pExtGStateDict = - pdfium::MakeUnique(pAnnotDict.GetByteStringPool()); - pExtGStateDict->SetFor(sExtGSDictName, std::move(pGSDict)); - return pExtGStateDict; -} - std::unique_ptr GenerateResourceFontDict( CPDF_Document* pDoc, const CFX_ByteString& sFontDictName) { @@ -582,45 +561,6 @@ std::unique_ptr GenerateResourceFontDict( return pResourceFontDict; } -std::unique_ptr GenerateResourceDict( - CPDF_Document* pDoc, - std::unique_ptr pExtGStateDict, - std::unique_ptr pResourceFontDict) { - auto pResourceDict = - pdfium::MakeUnique(pDoc->GetByteStringPool()); - if (pExtGStateDict) - pResourceDict->SetFor("ExtGState", std::move(pExtGStateDict)); - if (pResourceFontDict) - pResourceDict->SetFor("Font", std::move(pResourceFontDict)); - return pResourceDict; -} - -void GenerateAndSetAPDict(CPDF_Document* pDoc, - CPDF_Dictionary* pAnnotDict, - std::ostringstream* psAppStream, - std::unique_ptr pResourceDict, - bool bIsTextMarkupAnnotation) { - CPDF_Stream* pNormalStream = pDoc->NewIndirect(); - pNormalStream->SetData(psAppStream); - - CPDF_Dictionary* pAPDict = pAnnotDict->GetDictFor("AP"); - if (!pAPDict) - pAPDict = pAnnotDict->SetNewFor("AP"); - - pAPDict->SetNewFor("N", pDoc, pNormalStream->GetObjNum()); - - CPDF_Dictionary* pStreamDict = pNormalStream->GetDict(); - pStreamDict->SetNewFor("FormType", 1); - pStreamDict->SetNewFor("Subtype", "Form", false); - pStreamDict->SetMatrixFor("Matrix", CFX_Matrix()); - - CFX_FloatRect rect = bIsTextMarkupAnnotation - ? CPDF_Annot::RectFromQuadPoints(pAnnotDict) - : pAnnotDict->GetRectFor("Rect"); - pStreamDict->SetRectFor("BBox", rect); - pStreamDict->SetFor("Resources", std::move(pResourceDict)); -} - CFX_ByteString GetPaintOperatorString(bool bIsStrokeRect, bool bIsFillRect) { if (bIsStrokeRect) return bIsFillRect ? "b" : "s"; @@ -1321,6 +1261,70 @@ CFX_ByteString CPVT_GenerateAP::GenerateColorAP(const CPVT_Color& color, return CFX_ByteString(sColorStream); } +// Static. +std::unique_ptr CPVT_GenerateAP::GenerateExtGStateDict( + const CPDF_Dictionary& pAnnotDict, + const CFX_ByteString& sExtGSDictName, + const CFX_ByteString& sBlendMode) { + auto pGSDict = + pdfium::MakeUnique(pAnnotDict.GetByteStringPool()); + pGSDict->SetNewFor("Type", "ExtGState", false); + + float fOpacity = + pAnnotDict.KeyExist("CA") ? pAnnotDict.GetNumberFor("CA") : 1; + pGSDict->SetNewFor("CA", fOpacity); + pGSDict->SetNewFor("ca", fOpacity); + pGSDict->SetNewFor("AIS", false); + pGSDict->SetNewFor("BM", sBlendMode, false); + + auto pExtGStateDict = + pdfium::MakeUnique(pAnnotDict.GetByteStringPool()); + pExtGStateDict->SetFor(sExtGSDictName, std::move(pGSDict)); + return pExtGStateDict; +} + +// Static. +std::unique_ptr CPVT_GenerateAP::GenerateResourceDict( + CPDF_Document* pDoc, + std::unique_ptr pExtGStateDict, + std::unique_ptr pResourceFontDict) { + auto pResourceDict = + pdfium::MakeUnique(pDoc->GetByteStringPool()); + if (pExtGStateDict) + pResourceDict->SetFor("ExtGState", std::move(pExtGStateDict)); + if (pResourceFontDict) + pResourceDict->SetFor("Font", std::move(pResourceFontDict)); + return pResourceDict; +} + +// Static. +void CPVT_GenerateAP::GenerateAndSetAPDict( + CPDF_Document* pDoc, + CPDF_Dictionary* pAnnotDict, + std::ostringstream* psAppStream, + std::unique_ptr pResourceDict, + bool bIsTextMarkupAnnotation) { + CPDF_Stream* pNormalStream = pDoc->NewIndirect(); + pNormalStream->SetData(psAppStream); + + CPDF_Dictionary* pAPDict = pAnnotDict->GetDictFor("AP"); + if (!pAPDict) + pAPDict = pAnnotDict->SetNewFor("AP"); + + pAPDict->SetNewFor("N", pDoc, pNormalStream->GetObjNum()); + + CPDF_Dictionary* pStreamDict = pNormalStream->GetDict(); + pStreamDict->SetNewFor("FormType", 1); + pStreamDict->SetNewFor("Subtype", "Form", false); + pStreamDict->SetMatrixFor("Matrix", CFX_Matrix()); + + CFX_FloatRect rect = bIsTextMarkupAnnotation + ? CPDF_Annot::RectFromQuadPoints(pAnnotDict) + : pAnnotDict->GetRectFor("Rect"); + pStreamDict->SetRectFor("BBox", rect); + pStreamDict->SetFor("Resources", std::move(pResourceDict)); +} + // Static. CFX_ByteString CPVT_GenerateAP::GetPDFWordString(IPVT_FontMap* pFontMap, int32_t nFontIndex, diff --git a/core/fpdfdoc/cpvt_generateap.h b/core/fpdfdoc/cpvt_generateap.h index c558636255..cee96cda50 100644 --- a/core/fpdfdoc/cpvt_generateap.h +++ b/core/fpdfdoc/cpvt_generateap.h @@ -60,6 +60,20 @@ class CPVT_GenerateAP { const CPVT_Dash& dash); static CFX_ByteString GenerateColorAP(const CPVT_Color& color, PaintOperation nOperation); + static std::unique_ptr GenerateExtGStateDict( + const CPDF_Dictionary& pAnnotDict, + const CFX_ByteString& sExtGSDictName, + const CFX_ByteString& sBlendMode); + static std::unique_ptr GenerateResourceDict( + CPDF_Document* pDoc, + std::unique_ptr pExtGStateDict, + std::unique_ptr pResourceFontDict); + static void GenerateAndSetAPDict( + CPDF_Document* pDoc, + CPDF_Dictionary* pAnnotDict, + std::ostringstream* psAppStream, + std::unique_ptr pResourceDict, + bool bIsTextMarkupAnnotation); static CFX_ByteString GetPDFWordString(IPVT_FontMap* pFontMap, int32_t nFontIndex, diff --git a/fpdfsdk/fpdfannot.cpp b/fpdfsdk/fpdfannot.cpp index 3d003d738e..4d75e6e236 100644 --- a/fpdfsdk/fpdfannot.cpp +++ b/fpdfsdk/fpdfannot.cpp @@ -9,8 +9,10 @@ #include #include +#include "core/fpdfapi/edit/cpdf_pagecontentgenerator.h" #include "core/fpdfapi/page/cpdf_form.h" #include "core/fpdfapi/page/cpdf_page.h" +#include "core/fpdfapi/page/cpdf_pageobject.h" #include "core/fpdfapi/parser/cpdf_array.h" #include "core/fpdfapi/parser/cpdf_dictionary.h" #include "core/fpdfapi/parser/cpdf_document.h" @@ -118,6 +120,10 @@ class CPDF_AnnotContext { if (!pStream) return; + // Reset the annotation matrix to be the identity matrix, since the + // appearance stream already takes matrix into account. + pStream->GetDict()->SetMatrixFor("Matrix", CFX_Matrix()); + m_pAnnotForm = pdfium::MakeUnique( m_pPage->m_pDocument.Get(), m_pPage->m_pResources.Get(), pStream); m_pAnnotForm->ParseContent(nullptr, nullptr, nullptr); @@ -125,6 +131,7 @@ class CPDF_AnnotContext { CPDF_Form* GetForm() const { return m_pAnnotForm.get(); } CPDF_Dictionary* GetAnnotDict() const { return m_pAnnotDict.Get(); } + CPDF_Page* GetPage() const { return m_pPage.Get(); } private: std::unique_ptr m_pAnnotForm; @@ -144,11 +151,12 @@ bool HasAPStream(const CPDF_Dictionary* pAnnotDict) { DLLEXPORT FPDF_BOOL STDCALL FPDFAnnot_IsSupportedSubtype(FPDF_ANNOTATION_SUBTYPE subtype) { + // The supported subtypes must also be communicated in the user doc. 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; + subtype == FPDF_ANNOT_STAMP || subtype == FPDF_ANNOT_STRIKEOUT || + subtype == FPDF_ANNOT_TEXT || subtype == FPDF_ANNOT_UNDERLINE; } DLLEXPORT FPDF_ANNOTATION STDCALL @@ -228,6 +236,168 @@ FPDFAnnot_GetSubtype(FPDF_ANNOTATION annot) { CPDF_Annot::StringToAnnotSubtype(pAnnotDict->GetStringFor("Subtype"))); } +DLLEXPORT FPDF_BOOL STDCALL FPDFAnnot_UpdatePathObject(FPDF_ANNOTATION annot, + FPDF_PAGEOBJECT path) { + CPDF_AnnotContext* pAnnot = CPDFAnnotContextFromFPDFAnnotation(annot); + CPDF_PageObject* pPathObj = CPDFPageObjectFromFPDFPageObject(path); + if (!pAnnot || !pAnnot->GetAnnotDict() || !pAnnot->HasForm() || !pPathObj || + !pPathObj->IsPath()) + return false; + + // Check that the annotation type is supported by this method. + FPDF_ANNOTATION_SUBTYPE subtype = FPDFAnnot_GetSubtype(annot); + if (subtype != FPDF_ANNOT_INK && subtype != FPDF_ANNOT_STAMP) + return false; + + // Check that the annotation already has an appearance stream, since an + // existing path object is to be updated. + CPDF_Stream* pStream = FPDFDOC_GetAnnotAP(pAnnot->GetAnnotDict(), + CPDF_Annot::AppearanceMode::Normal); + if (!pStream) + return false; + + // Check that the path object is already in this annotation's object list. + CPDF_PageObjectList* pObjList = pAnnot->GetForm()->GetPageObjectList(); + auto it = std::find_if( + pObjList->begin(), pObjList->end(), + [pPathObj](const std::unique_ptr& candidate) { + return candidate.get() == pPathObj; + }); + if (it == pObjList->end()) + return false; + + // Update the content stream data in the annotation's AP stream. + CPDF_PageContentGenerator generator(pAnnot->GetForm()); + std::ostringstream buf; + generator.ProcessPageObjects(&buf); + pStream->SetData(reinterpret_cast(buf.str().c_str()), + buf.tellp()); + return true; +} + +DLLEXPORT FPDF_BOOL STDCALL FPDFAnnot_AppendPathObject(FPDF_ANNOTATION annot, + FPDF_PAGEOBJECT path) { + CPDF_AnnotContext* pAnnot = CPDFAnnotContextFromFPDFAnnotation(annot); + CPDF_PageObject* pPathObj = CPDFPageObjectFromFPDFPageObject(path); + if (!pAnnot || !pPathObj || !pPathObj->IsPath()) + return false; + + CPDF_Dictionary* pAnnotDict = pAnnot->GetAnnotDict(); + CPDF_Page* pPage = pAnnot->GetPage(); + if (!pAnnotDict || !pPage) + return false; + + // Check that the annotation type is supported by this method. + FPDF_ANNOTATION_SUBTYPE subtype = FPDFAnnot_GetSubtype(annot); + if (subtype != FPDF_ANNOT_INK && subtype != FPDF_ANNOT_STAMP) + return false; + + // If the annotation does not have an AP stream yet, generate and set it. + CPDF_Stream* pStream = FPDFDOC_GetAnnotAP(pAnnot->GetAnnotDict(), + CPDF_Annot::AppearanceMode::Normal); + if (!pStream) { + auto pExtGStateDict = + CPVT_GenerateAP::GenerateExtGStateDict(*pAnnotDict, "GS", "Normal"); + auto pResourceDict = CPVT_GenerateAP::GenerateResourceDict( + pPage->m_pDocument.Get(), std::move(pExtGStateDict), nullptr); + std::ostringstream sStream; + CPVT_GenerateAP::GenerateAndSetAPDict(pPage->m_pDocument.Get(), pAnnotDict, + &sStream, std::move(pResourceDict), + false); + pStream = + FPDFDOC_GetAnnotAP(pAnnotDict, CPDF_Annot::AppearanceMode::Normal); + if (!pStream) + return false; + } + + // Get the annotation's corresponding form object for parsing its AP stream. + if (!pAnnot->HasForm()) + pAnnot->SetForm(pStream); + + CPDF_Form* pForm = pAnnot->GetForm(); + + // Check that the path object did not come from the same annotation. If this + // check succeeds, then it is assumed that the path object came from + // FPDFPageObj_CreateNewPath(). Note that a path object that came from a + // different annotation must not be passed here, since a path object cannot + // belong to more than one annotation. + CPDF_PageObjectList* pObjList = pForm->GetPageObjectList(); + auto it = std::find_if( + pObjList->begin(), pObjList->end(), + [pPathObj](const std::unique_ptr& candidate) { + return candidate.get() == pPathObj; + }); + if (it != pObjList->end()) + return false; + + // Append the path object to the object list. + std::unique_ptr pPageObjHolder(pPathObj); + pObjList->push_back(std::move(pPageObjHolder)); + + // Set the content stream data in the annotation's AP stream. + CPDF_PageContentGenerator generator(pForm); + std::ostringstream buf; + generator.ProcessPageObjects(&buf); + pStream->SetData(reinterpret_cast(buf.str().c_str()), + buf.tellp()); + return true; +} + +DLLEXPORT int STDCALL FPDFAnnot_GetPathObjectCount(FPDF_ANNOTATION annot) { + CPDF_AnnotContext* pAnnot = CPDFAnnotContextFromFPDFAnnotation(annot); + if (!pAnnot) + return 0; + + if (!pAnnot->HasForm()) { + CPDF_Stream* pStream = FPDFDOC_GetAnnotAP( + pAnnot->GetAnnotDict(), CPDF_Annot::AppearanceMode::Normal); + if (!pStream) + return 0; + + pAnnot->SetForm(pStream); + } + + int pathCount = 0; + for (const auto& pObj : *pAnnot->GetForm()->GetPageObjectList()) { + if (pObj && pObj->IsPath()) + ++pathCount; + } + return pathCount; +} + +DLLEXPORT FPDF_PAGEOBJECT STDCALL FPDFAnnot_GetPathObject(FPDF_ANNOTATION annot, + int index) { + CPDF_AnnotContext* pAnnot = CPDFAnnotContextFromFPDFAnnotation(annot); + if (!pAnnot || index < 0) + return nullptr; + + if (!pAnnot->HasForm()) { + CPDF_Stream* pStream = FPDFDOC_GetAnnotAP( + pAnnot->GetAnnotDict(), CPDF_Annot::AppearanceMode::Normal); + if (!pStream) + return nullptr; + + pAnnot->SetForm(pStream); + } + + const CPDF_PageObjectList* pObjList = pAnnot->GetForm()->GetPageObjectList(); + if (static_cast(index) >= pObjList->size()) + return nullptr; + + // Retrieve the path object located at |index| in the list of path objects. + // Note that the list of path objects is a sublist of the page object list, + // consisting of only path objects specifically. + int pathCount = -1; + for (const auto& pObj : *pObjList) { + if (pObj && pObj->IsPath()) { + ++pathCount; + if (pathCount == index) + return pObj.get(); + } + } + return nullptr; +} + DLLEXPORT FPDF_BOOL STDCALL FPDFAnnot_SetColor(FPDF_ANNOTATION annot, FPDFANNOT_COLORTYPE type, unsigned int R, diff --git a/fpdfsdk/fpdfannot_embeddertest.cpp b/fpdfsdk/fpdfannot_embeddertest.cpp index 85fe1326bc..c08460e745 100644 --- a/fpdfsdk/fpdfannot_embeddertest.cpp +++ b/fpdfsdk/fpdfannot_embeddertest.cpp @@ -6,7 +6,9 @@ #include #include +#include "core/fxcrt/fx_system.h" #include "public/fpdf_annot.h" +#include "public/fpdf_edit.h" #include "public/fpdfview.h" #include "testing/embedder_test.h" #include "testing/gtest/include/gtest/gtest.h" @@ -23,7 +25,7 @@ TEST_F(FPDFAnnotEmbeddertest, RenderAnnotWithOnlyRolloverAP) { // normal appearance defined, only its rollover appearance. In this case, its // normal appearance should be generated, allowing the highlight annotation to // still display. - FPDF_BITMAP bitmap = RenderPageWithFlags(page, FPDF_ANNOT); + FPDF_BITMAP bitmap = RenderPageWithFlags(page, form_handle_, FPDF_ANNOT); CompareBitmap(bitmap, 612, 792, "dc98f06da047bd8aabfa99562d2cbd1e"); FPDFBitmap_Destroy(bitmap); @@ -457,7 +459,122 @@ TEST_F(FPDFAnnotEmbeddertest, RemoveAnnotation) { rect = FPDFAnnot_GetRect(annot); EXPECT_NEAR(351.8204f, rect.left, 0.001f); FPDFPage_CloseAnnot(annot); + FPDF_ClosePage(new_page); + FPDF_CloseDocument(new_doc); +} + +TEST_F(FPDFAnnotEmbeddertest, AddAndModifyPath) { +#if _FXM_PLATFORM_ == _FXM_PLATFORM_APPLE_ + const char md5[] = "c35408717759562d1f8bf33d317483d2"; + const char md5_2[] = "cf3cea74bd46497520ff6c4d1ea228c8"; + const char md5_3[] = "ee5372b31fede117fc83b9384598aa25"; +#elif _FXM_PLATFORM_ == _FXM_PLATFORM_WINDOWS_ + const char md5[] = "bdf96279ab82d9f484874db3f0c03429"; + const char md5_2[] = "5f2b32b7aa93bc1e62a7a7971f54bdd7"; + const char md5_3[] = "272661f3e5c9516aac4b5beb3ae1b36a"; +#else + const char md5[] = "07d4168715553b4294525f840c40aa1c"; + const char md5_2[] = "dd5ba8996af67d0e5add418195e4d61b"; + const char md5_3[] = "c60c2cc2c4e7b13be90bd77cc4502f97"; +#endif + + // Open a file with two annotations and load its first page. + ASSERT_TRUE(OpenDocument("annotation_stamp_with_ap.pdf")); + FPDF_PAGE page = FPDF_LoadPage(document(), 0); + ASSERT_TRUE(page); + EXPECT_EQ(2, FPDFPage_GetAnnotCount(page)); + + // Check that the page renders correctly. + FPDF_BITMAP bitmap = RenderPageWithFlags(page, form_handle_, FPDF_ANNOT); + CompareBitmap(bitmap, 595, 842, md5); + FPDFBitmap_Destroy(bitmap); + + // Retrieve the stamp annotation which has its AP stream already defined. + FPDF_ANNOTATION annot = FPDFPage_GetAnnot(page, 0); + ASSERT_TRUE(annot); + + // Check that this annotation has one path object and retrieve it. + EXPECT_EQ(1, FPDFAnnot_GetPathObjectCount(annot)); + FPDF_PAGEOBJECT path = FPDFAnnot_GetPathObject(annot, 1); + EXPECT_FALSE(path); + path = FPDFAnnot_GetPathObject(annot, 0); + EXPECT_TRUE(path); + + // Modify the color of the path object. + EXPECT_TRUE(FPDFPath_SetStrokeColor(path, 0, 0, 0, 255)); + EXPECT_TRUE(FPDFAnnot_UpdatePathObject(annot, path)); + FPDFPage_CloseAnnot(annot); + + // Check that the page with the modified annotation renders correctly. + bitmap = RenderPageWithFlags(page, form_handle_, FPDF_ANNOT); + CompareBitmap(bitmap, 595, 842, md5_2); + FPDFBitmap_Destroy(bitmap); + + // Create another stamp annotation and set its annotation rectangle. + annot = FPDFPage_CreateAnnot(page, FPDF_ANNOT_STAMP); + ASSERT_TRUE(annot); + FS_RECTF rect; + rect.left = 200.f; + rect.bottom = 400.f; + rect.right = 500.f; + rect.top = 600.f; + EXPECT_TRUE(FPDFAnnot_SetRect(annot, &rect)); + + // Add a new path to the annotation. + FPDF_PAGEOBJECT check = FPDFPageObj_CreateNewPath(200, 500); + EXPECT_TRUE(FPDFPath_LineTo(check, 300, 400)); + EXPECT_TRUE(FPDFPath_LineTo(check, 500, 600)); + EXPECT_TRUE(FPDFPath_MoveTo(check, 350, 550)); + EXPECT_TRUE(FPDFPath_LineTo(check, 450, 450)); + EXPECT_TRUE(FPDFPath_SetStrokeColor(check, 0, 255, 255, 180)); + EXPECT_TRUE(FPDFPath_SetStrokeWidth(check, 8.35f)); + EXPECT_TRUE(FPDFPath_SetDrawMode(check, 0, 1)); + EXPECT_TRUE(FPDFAnnot_AppendPathObject(annot, check)); + EXPECT_EQ(1, FPDFAnnot_GetPathObjectCount(annot)); + + // Check that the annotation's bounding box came from its rectangle. + FS_RECTF new_rect = FPDFAnnot_GetRect(annot); + EXPECT_EQ(rect.left, new_rect.left); + EXPECT_EQ(rect.bottom, new_rect.bottom); + EXPECT_EQ(rect.right, new_rect.right); + EXPECT_EQ(rect.top, new_rect.top); + + // Save the document, closing the page and document. + FPDFPage_CloseAnnot(annot); + 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 a correct count of annotations and paths. + EXPECT_EQ(3, FPDFPage_GetAnnotCount(new_page)); + annot = FPDFPage_GetAnnot(new_page, 2); + ASSERT_TRUE(annot); + EXPECT_EQ(1, FPDFAnnot_GetPathObjectCount(annot)); + + // Check that the new annotation's rectangle is as defined. + new_rect = FPDFAnnot_GetRect(annot); + EXPECT_EQ(rect.left, new_rect.left); + EXPECT_EQ(rect.bottom, new_rect.bottom); + EXPECT_EQ(rect.right, new_rect.right); + EXPECT_EQ(rect.top, new_rect.top); + + // Check that the saved page renders correctly. + bitmap = RenderPageWithFlags(new_page, nullptr, FPDF_ANNOT); + CompareBitmap(bitmap, 595, 842, md5_3); + FPDFBitmap_Destroy(bitmap); + + FPDFPage_CloseAnnot(annot); FPDF_ClosePage(new_page); FPDF_CloseDocument(new_doc); } diff --git a/fpdfsdk/fpdfview.cpp b/fpdfsdk/fpdfview.cpp index ccd487aa4d..1c282a744d 100644 --- a/fpdfsdk/fpdfview.cpp +++ b/fpdfsdk/fpdfview.cpp @@ -309,6 +309,10 @@ CPDF_PathObject* CPDFPathObjectFromFPDFPageObject(FPDF_PAGEOBJECT page_object) { return static_cast(page_object); } +CPDF_PageObject* CPDFPageObjectFromFPDFPageObject(FPDF_PAGEOBJECT page_object) { + return static_cast(page_object); +} + CFX_DIBitmap* CFXBitmapFromFPDFBitmap(FPDF_BITMAP bitmap) { return static_cast(bitmap); } diff --git a/fpdfsdk/fpdfview_c_api_test.c b/fpdfsdk/fpdfview_c_api_test.c index f9a3c387c2..94f4051bcd 100644 --- a/fpdfsdk/fpdfview_c_api_test.c +++ b/fpdfsdk/fpdfview_c_api_test.c @@ -42,6 +42,10 @@ int CheckPDFiumCApi() { CHK(FPDFPage_CloseAnnot); CHK(FPDFPage_RemoveAnnot); CHK(FPDFAnnot_GetSubtype); + CHK(FPDFAnnot_UpdatePathObject); + CHK(FPDFAnnot_AppendPathObject); + CHK(FPDFAnnot_GetPathObjectCount); + CHK(FPDFAnnot_GetPathObject); CHK(FPDFAnnot_SetColor); CHK(FPDFAnnot_GetColor); CHK(FPDFAnnot_HasAttachmentPoints); diff --git a/fpdfsdk/fsdk_define.h b/fpdfsdk/fsdk_define.h index 12b9b4771c..4cfe3442ac 100644 --- a/fpdfsdk/fsdk_define.h +++ b/fpdfsdk/fsdk_define.h @@ -23,6 +23,7 @@ class CPDF_Annot; class CPDF_Page; +class CPDF_PageObject; class CPDF_PageRenderContext; class CPDF_PathObject; class IFSDK_PAUSE_Adapter; @@ -64,6 +65,8 @@ CPDF_Page* CPDFPageFromFPDFPage(FPDF_PAGE page); CPDF_PathObject* CPDFPathObjectFromFPDFPageObject(FPDF_PAGEOBJECT page_object); +CPDF_PageObject* CPDFPageObjectFromFPDFPageObject(FPDF_PAGEOBJECT page_object); + CFX_DIBitmap* CFXBitmapFromFPDFBitmap(FPDF_BITMAP bitmap); void FSDK_SetSandBoxPolicy(FPDF_DWORD policy, FPDF_BOOL enable); diff --git a/public/fpdf_annot.h b/public/fpdf_annot.h index 9e349bdd5e..89edd01613 100644 --- a/public/fpdf_annot.h +++ b/public/fpdf_annot.h @@ -55,10 +55,9 @@ typedef enum FPDFANNOT_TEXTTYPE { FPDFANNOT_TEXTTYPE_Author } FPDFANNOT_TEXTTYPE; -// Check if an annotation subtype is currently supported for creating and -// displaying. The supported subtypes must be consistent with the ones supported -// by AP generation - see the list of calls to CPVT_GenerateAP::Generate*AP() in -// CPDF_Annot::GenerateAPIfNeeded(). +// Check if an annotation subtype is currently supported for creation. +// Currently supported subtypes: circle, highlight, ink, popup, square, +// squiggly, stamp, strikeout, text, and underline. // // subtype - the subtype to be checked. // @@ -68,6 +67,8 @@ FPDFAnnot_IsSupportedSubtype(FPDF_ANNOTATION_SUBTYPE subtype); // Create an annotation in |page| of the subtype |subtype|. If the specified // subtype is illegal or unsupported, then a new annotation will not be created. +// Must call FPDFPage_CloseAnnot() when the annotation returned by this +// function is no longer needed. // // page - handle to a page. // subtype - the subtype of the new annotation. @@ -83,7 +84,8 @@ FPDFPage_CreateAnnot(FPDF_PAGE page, FPDF_ANNOTATION_SUBTYPE subtype); // Returns the number of annotations in |page|. DLLEXPORT int STDCALL FPDFPage_GetAnnotCount(FPDF_PAGE page); -// Get annotation in |page| at |index|. +// Get annotation in |page| at |index|. Must call FPDFPage_CloseAnnot() when the +// annotation returned by this function is no longer needed. // // page - handle to a page. // index - the index of the annotation. @@ -114,6 +116,49 @@ DLLEXPORT FPDF_BOOL STDCALL FPDFPage_RemoveAnnot(FPDF_PAGE page, int index); DLLEXPORT FPDF_ANNOTATION_SUBTYPE STDCALL FPDFAnnot_GetSubtype(FPDF_ANNOTATION annot); +// Experimental API. +// Update |path| in |annot|. |path| must be in |annot| already and must have +// been retrieved by FPDFAnnot_GetPathObject(). Only ink and stamp annotations +// are supported currently. +// +// annot - handle to an annotation. +// path - handle to the path that |annot| needs to update. +// +// Return true if successful. +DLLEXPORT FPDF_BOOL STDCALL FPDFAnnot_UpdatePathObject(FPDF_ANNOTATION annot, + FPDF_PAGEOBJECT path); + +// Experimental API. +// Add |path| to |annot|. |path| must have been created by +// FPDFPageObj_CreateNewPath(), and will be owned by |annot|. Note that a |path| +// cannot belong to more than one |annot|. Only ink and stamp annotations +// are supported currently. +// +// annot - handle to an annotation. +// path - handle to the path that is to be added to |annot|. +// +// Return true if successful. +DLLEXPORT FPDF_BOOL STDCALL FPDFAnnot_AppendPathObject(FPDF_ANNOTATION annot, + FPDF_PAGEOBJECT path); + +// Experimental API. +// Get the number of path objects in |annot|. +// +// annot - handle to an annotation. +// +// Returns the number of path objects in |annot|. +DLLEXPORT int STDCALL FPDFAnnot_GetPathObjectCount(FPDF_ANNOTATION annot); + +// Experimental API. +// Get the path object in |annot| at |index|. +// +// annot - handle to an annotation. +// index - the index of the path object. +// +// Return a handle to the path object, or NULL on failure. +DLLEXPORT FPDF_PAGEOBJECT STDCALL FPDFAnnot_GetPathObject(FPDF_ANNOTATION annot, + int index); + // Set the color of an annotation. Fails when called on annotations with // appearance streams already defined; instead use // FPDFPath_Set{Stroke|Fill}Color(). diff --git a/testing/embedder_test.cpp b/testing/embedder_test.cpp index 7e34260e60..55e93a3d32 100644 --- a/testing/embedder_test.cpp +++ b/testing/embedder_test.cpp @@ -255,10 +255,12 @@ FPDF_PAGE EmbedderTest::LoadPage(int page_number) { } FPDF_BITMAP EmbedderTest::RenderPage(FPDF_PAGE page) { - return RenderPageWithFlags(page, 0); + return RenderPageWithFlags(page, form_handle_, 0); } -FPDF_BITMAP EmbedderTest::RenderPageWithFlags(FPDF_PAGE page, int flags) { +FPDF_BITMAP EmbedderTest::RenderPageWithFlags(FPDF_PAGE page, + FPDF_FORMHANDLE handle, + int flags) { int width = static_cast(FPDF_GetPageWidth(page)); int height = static_cast(FPDF_GetPageHeight(page)); int alpha = FPDFPage_HasTransparency(page) ? 1 : 0; @@ -266,7 +268,7 @@ FPDF_BITMAP EmbedderTest::RenderPageWithFlags(FPDF_PAGE page, int flags) { FPDF_DWORD fill_color = alpha ? 0x00000000 : 0xFFFFFFFF; FPDFBitmap_FillRect(bitmap, 0, 0, width, height, fill_color); FPDF_RenderPageBitmap(bitmap, page, 0, 0, width, height, 0, flags); - FPDF_FFLDraw(form_handle_, bitmap, page, 0, 0, width, height, 0, flags); + FPDF_FFLDraw(handle, bitmap, page, 0, 0, width, height, 0, flags); return bitmap; } diff --git a/testing/embedder_test.h b/testing/embedder_test.h index fd48840d28..ba44673004 100644 --- a/testing/embedder_test.h +++ b/testing/embedder_test.h @@ -103,7 +103,9 @@ class EmbedderTest : public ::testing::Test, // Convert a loaded page into a bitmap with page rendering flags specified. // See public/fpdfview.h for a list of page rendering flags. - virtual FPDF_BITMAP RenderPageWithFlags(FPDF_PAGE page, int flags); + virtual FPDF_BITMAP RenderPageWithFlags(FPDF_PAGE page, + FPDF_FORMHANDLE handle, + int flags); // Relese the resources obtained from LoadPage(). Further use of |page| // is prohibited after this call is made. diff --git a/testing/resources/annotation_stamp_with_ap.pdf b/testing/resources/annotation_stamp_with_ap.pdf new file mode 100644 index 0000000000..9181c96af5 Binary files /dev/null and b/testing/resources/annotation_stamp_with_ap.pdf differ -- cgit v1.2.3