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 <janeliulwq@google.com>
Reviewed-by: Lei Zhang <thestig@chromium.org>
Reviewed-by: dsinclair <dsinclair@chromium.org>
diff --git a/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp b/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp
index 369c2a8..18bcbf9 100644
--- a/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp
+++ b/core/fpdfapi/edit/cpdf_pagecontentgenerator.cpp
@@ -209,6 +209,9 @@
 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 ef18666..62b10c9 100644
--- a/core/fpdfapi/edit/cpdf_pagecontentgenerator_unittest.cpp
+++ b/core/fpdfapi/edit/cpdf_pagecontentgenerator_unittest.cpp
@@ -58,7 +58,7 @@
   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<CPDF_PathObject>();
   pPathObj->m_Path.AppendPoint(CFX_PointF(0, 0), FXPT_TYPE::MoveTo, false);
@@ -71,7 +71,7 @@
   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 @@
   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 @@
   // 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 @@
   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 @@
   pDoc->CreateNewDoc();
   auto pDict = pdfium::MakeUnique<CPDF_Dictionary>();
   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<uint8_t, FxFreeDeleter> 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 ff7f4b6..eb874b3 100644
--- a/core/fpdfdoc/cpvt_generateap.cpp
+++ b/core/fpdfdoc/cpvt_generateap.cpp
@@ -545,27 +545,6 @@
   return CFX_ByteString(sAppStream);
 }
 
-std::unique_ptr<CPDF_Dictionary> GenerateExtGStateDict(
-    const CPDF_Dictionary& pAnnotDict,
-    const CFX_ByteString& sExtGSDictName,
-    const CFX_ByteString& sBlendMode) {
-  auto pGSDict =
-      pdfium::MakeUnique<CPDF_Dictionary>(pAnnotDict.GetByteStringPool());
-  pGSDict->SetNewFor<CPDF_String>("Type", "ExtGState", false);
-
-  float fOpacity =
-      pAnnotDict.KeyExist("CA") ? pAnnotDict.GetNumberFor("CA") : 1;
-  pGSDict->SetNewFor<CPDF_Number>("CA", fOpacity);
-  pGSDict->SetNewFor<CPDF_Number>("ca", fOpacity);
-  pGSDict->SetNewFor<CPDF_Boolean>("AIS", false);
-  pGSDict->SetNewFor<CPDF_String>("BM", sBlendMode, false);
-
-  auto pExtGStateDict =
-      pdfium::MakeUnique<CPDF_Dictionary>(pAnnotDict.GetByteStringPool());
-  pExtGStateDict->SetFor(sExtGSDictName, std::move(pGSDict));
-  return pExtGStateDict;
-}
-
 std::unique_ptr<CPDF_Dictionary> GenerateResourceFontDict(
     CPDF_Document* pDoc,
     const CFX_ByteString& sFontDictName) {
@@ -582,45 +561,6 @@
   return pResourceFontDict;
 }
 
-std::unique_ptr<CPDF_Dictionary> GenerateResourceDict(
-    CPDF_Document* pDoc,
-    std::unique_ptr<CPDF_Dictionary> pExtGStateDict,
-    std::unique_ptr<CPDF_Dictionary> pResourceFontDict) {
-  auto pResourceDict =
-      pdfium::MakeUnique<CPDF_Dictionary>(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<CPDF_Dictionary> pResourceDict,
-                          bool bIsTextMarkupAnnotation) {
-  CPDF_Stream* pNormalStream = pDoc->NewIndirect<CPDF_Stream>();
-  pNormalStream->SetData(psAppStream);
-
-  CPDF_Dictionary* pAPDict = pAnnotDict->GetDictFor("AP");
-  if (!pAPDict)
-    pAPDict = pAnnotDict->SetNewFor<CPDF_Dictionary>("AP");
-
-  pAPDict->SetNewFor<CPDF_Reference>("N", pDoc, pNormalStream->GetObjNum());
-
-  CPDF_Dictionary* pStreamDict = pNormalStream->GetDict();
-  pStreamDict->SetNewFor<CPDF_Number>("FormType", 1);
-  pStreamDict->SetNewFor<CPDF_String>("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";
@@ -1322,6 +1262,70 @@
 }
 
 // Static.
+std::unique_ptr<CPDF_Dictionary> CPVT_GenerateAP::GenerateExtGStateDict(
+    const CPDF_Dictionary& pAnnotDict,
+    const CFX_ByteString& sExtGSDictName,
+    const CFX_ByteString& sBlendMode) {
+  auto pGSDict =
+      pdfium::MakeUnique<CPDF_Dictionary>(pAnnotDict.GetByteStringPool());
+  pGSDict->SetNewFor<CPDF_String>("Type", "ExtGState", false);
+
+  float fOpacity =
+      pAnnotDict.KeyExist("CA") ? pAnnotDict.GetNumberFor("CA") : 1;
+  pGSDict->SetNewFor<CPDF_Number>("CA", fOpacity);
+  pGSDict->SetNewFor<CPDF_Number>("ca", fOpacity);
+  pGSDict->SetNewFor<CPDF_Boolean>("AIS", false);
+  pGSDict->SetNewFor<CPDF_String>("BM", sBlendMode, false);
+
+  auto pExtGStateDict =
+      pdfium::MakeUnique<CPDF_Dictionary>(pAnnotDict.GetByteStringPool());
+  pExtGStateDict->SetFor(sExtGSDictName, std::move(pGSDict));
+  return pExtGStateDict;
+}
+
+// Static.
+std::unique_ptr<CPDF_Dictionary> CPVT_GenerateAP::GenerateResourceDict(
+    CPDF_Document* pDoc,
+    std::unique_ptr<CPDF_Dictionary> pExtGStateDict,
+    std::unique_ptr<CPDF_Dictionary> pResourceFontDict) {
+  auto pResourceDict =
+      pdfium::MakeUnique<CPDF_Dictionary>(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<CPDF_Dictionary> pResourceDict,
+    bool bIsTextMarkupAnnotation) {
+  CPDF_Stream* pNormalStream = pDoc->NewIndirect<CPDF_Stream>();
+  pNormalStream->SetData(psAppStream);
+
+  CPDF_Dictionary* pAPDict = pAnnotDict->GetDictFor("AP");
+  if (!pAPDict)
+    pAPDict = pAnnotDict->SetNewFor<CPDF_Dictionary>("AP");
+
+  pAPDict->SetNewFor<CPDF_Reference>("N", pDoc, pNormalStream->GetObjNum());
+
+  CPDF_Dictionary* pStreamDict = pNormalStream->GetDict();
+  pStreamDict->SetNewFor<CPDF_Number>("FormType", 1);
+  pStreamDict->SetNewFor<CPDF_String>("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,
                                                  uint16_t Word,
diff --git a/core/fpdfdoc/cpvt_generateap.h b/core/fpdfdoc/cpvt_generateap.h
index c558636..cee96cd 100644
--- a/core/fpdfdoc/cpvt_generateap.h
+++ b/core/fpdfdoc/cpvt_generateap.h
@@ -60,6 +60,20 @@
                                          const CPVT_Dash& dash);
   static CFX_ByteString GenerateColorAP(const CPVT_Color& color,
                                         PaintOperation nOperation);
+  static std::unique_ptr<CPDF_Dictionary> GenerateExtGStateDict(
+      const CPDF_Dictionary& pAnnotDict,
+      const CFX_ByteString& sExtGSDictName,
+      const CFX_ByteString& sBlendMode);
+  static std::unique_ptr<CPDF_Dictionary> GenerateResourceDict(
+      CPDF_Document* pDoc,
+      std::unique_ptr<CPDF_Dictionary> pExtGStateDict,
+      std::unique_ptr<CPDF_Dictionary> pResourceFontDict);
+  static void GenerateAndSetAPDict(
+      CPDF_Document* pDoc,
+      CPDF_Dictionary* pAnnotDict,
+      std::ostringstream* psAppStream,
+      std::unique_ptr<CPDF_Dictionary> pResourceDict,
+      bool bIsTextMarkupAnnotation);
 
   static CFX_ByteString GetPDFWordString(IPVT_FontMap* pFontMap,
                                          int32_t nFontIndex,
diff --git a/fpdfsdk/fpdfannot.cpp b/fpdfsdk/fpdfannot.cpp
index 3d003d7..4d75e6e 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 @@
     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 @@
 
   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 @@
 
 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 @@
       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 85fe132..c08460e 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 @@
   // 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 @@
   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 ccd487a..1c282a7 100644
--- a/fpdfsdk/fpdfview.cpp
+++ b/fpdfsdk/fpdfview.cpp
@@ -309,6 +309,10 @@
   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 f9a3c38..94f4051 100644
--- a/fpdfsdk/fpdfview_c_api_test.c
+++ b/fpdfsdk/fpdfview_c_api_test.c
@@ -42,6 +42,10 @@
     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 12b9b47..4cfe344 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_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 9e349bd..89edd01 100644
--- a/public/fpdf_annot.h
+++ b/public/fpdf_annot.h
@@ -55,10 +55,9 @@
   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 @@
 
 // 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 @@
 // 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_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 7e34260..55e93a3 100644
--- a/testing/embedder_test.cpp
+++ b/testing/embedder_test.cpp
@@ -255,10 +255,12 @@
 }
 
 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<int>(FPDF_GetPageWidth(page));
   int height = static_cast<int>(FPDF_GetPageHeight(page));
   int alpha = FPDFPage_HasTransparency(page) ? 1 : 0;
@@ -266,7 +268,7 @@
   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 fd48840..ba44673 100644
--- a/testing/embedder_test.h
+++ b/testing/embedder_test.h
@@ -103,7 +103,9 @@
 
   // 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 0000000..9181c96
--- /dev/null
+++ b/testing/resources/annotation_stamp_with_ap.pdf
Binary files differ