diff options
Diffstat (limited to 'fpdfsdk')
-rw-r--r-- | fpdfsdk/fpdfannot.cpp | 174 | ||||
-rw-r--r-- | fpdfsdk/fpdfannot_embeddertest.cpp | 119 | ||||
-rw-r--r-- | fpdfsdk/fpdfview.cpp | 4 | ||||
-rw-r--r-- | fpdfsdk/fpdfview_c_api_test.c | 4 | ||||
-rw-r--r-- | fpdfsdk/fsdk_define.h | 3 |
5 files changed, 301 insertions, 3 deletions
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 <memory> #include <utility> +#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<CPDF_Form>( 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<CPDF_Form> 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<CPDF_PageObject>& 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<const uint8_t*>(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<CPDF_PageObject>& candidate) { + return candidate.get() == pPathObj; + }); + if (it != pObjList->end()) + return false; + + // Append the path object to the object list. + std::unique_ptr<CPDF_PageObject> 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<const uint8_t*>(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<size_t>(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 <string> #include <vector> +#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<CPDF_PathObject*>(page_object); } +CPDF_PageObject* CPDFPageObjectFromFPDFPageObject(FPDF_PAGEOBJECT page_object) { + return static_cast<CPDF_PageObject*>(page_object); +} + CFX_DIBitmap* CFXBitmapFromFPDFBitmap(FPDF_BITMAP bitmap) { return static_cast<CFX_DIBitmap*>(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); |