summaryrefslogtreecommitdiff
path: root/fpdfsdk
diff options
context:
space:
mode:
Diffstat (limited to 'fpdfsdk')
-rw-r--r--fpdfsdk/fpdfannot.cpp174
-rw-r--r--fpdfsdk/fpdfannot_embeddertest.cpp119
-rw-r--r--fpdfsdk/fpdfview.cpp4
-rw-r--r--fpdfsdk/fpdfview_c_api_test.c4
-rw-r--r--fpdfsdk/fsdk_define.h3
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);