Expose DashingArray and Phase in the public API

Access to dashing info was available in the original Foxit API, but
not publicly accessible in pdfium. This provides getters and setters
for them in fpdf_edit.h. Additionally, CFX_GraphState was extended to
enable access. In contrast with the Foxit API, the dashing array getter
copies to an array directly instead of passing in an index, like in
FPDFPath_GetPathSegment(), to reduce the need for copying.

R=tsepez@chromium.org

Bug: pdfium:1187
Change-Id: If4b376e164dc8c8a97268ff380683b5d6a9f4be4
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/82030
Reviewed-by: Lei Zhang <thestig@chromium.org>
Reviewed-by: Tom Sepez <tsepez@chromium.org>
Commit-Queue: Lei Zhang <thestig@chromium.org>
diff --git a/AUTHORS b/AUTHORS
index 02fab7d..f37b91e 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -24,6 +24,7 @@
 Miklos Vajna <vmiklos@vmiklos.hu>
 Minh Trần <myoki.crystal@gmail.com>
 Ralf Sippl <ralf.sippl@gmail.com>
+Robert Collyer <rcollyer99@gmail.com>
 Ryan Wiley <wileyrr@gmail.com>
 Tibor Dusnoki <tdusnoki@inf.u-szeged.hu>
 Wang Qing <wangqing-hf@loongson.cn>
diff --git a/core/fxge/cfx_graphstate.cpp b/core/fxge/cfx_graphstate.cpp
index 726c0c6..9631712 100644
--- a/core/fxge/cfx_graphstate.cpp
+++ b/core/fxge/cfx_graphstate.cpp
@@ -29,8 +29,30 @@
   pData->m_DashArray = std::move(dashes);
 }
 
+void CFX_GraphState::SetLineDashPhase(float phase) {
+  CFX_GraphStateData* pData = m_Ref.GetPrivateCopy();
+  pData->m_DashPhase = phase;
+}
+
+std::vector<float> CFX_GraphState::GetLineDashArray() const {
+  std::vector<float> ret;
+
+  if (m_Ref.GetObject())
+    ret = m_Ref.GetObject()->m_DashArray;
+
+  return ret;
+}
+
+int CFX_GraphState::GetLineDashSize() const {
+  return m_Ref.GetObject() ? m_Ref.GetObject()->m_DashArray.size() : 0;
+}
+
+float CFX_GraphState::GetLineDashPhase() const {
+  return m_Ref.GetObject() ? m_Ref.GetObject()->m_DashPhase : 1.0f;
+}
+
 float CFX_GraphState::GetLineWidth() const {
-  return m_Ref.GetObject() ? m_Ref.GetObject()->m_LineWidth : 1.f;
+  return m_Ref.GetObject() ? m_Ref.GetObject()->m_LineWidth : 1.0f;
 }
 
 void CFX_GraphState::SetLineWidth(float width) {
diff --git a/core/fxge/cfx_graphstate.h b/core/fxge/cfx_graphstate.h
index 20955f8..d6b37e6 100644
--- a/core/fxge/cfx_graphstate.h
+++ b/core/fxge/cfx_graphstate.h
@@ -21,6 +21,10 @@
   void Emplace();
 
   void SetLineDash(std::vector<float> dashes, float phase, float scale);
+  void SetLineDashPhase(float phase);
+  std::vector<float> GetLineDashArray() const;
+  int GetLineDashSize() const;
+  float GetLineDashPhase() const;
 
   float GetLineWidth() const;
   void SetLineWidth(float width);
diff --git a/fpdfsdk/fpdf_editpage.cpp b/fpdfsdk/fpdf_editpage.cpp
index 286beb9..1026c98 100644
--- a/fpdfsdk/fpdf_editpage.cpp
+++ b/fpdfsdk/fpdf_editpage.cpp
@@ -838,6 +838,73 @@
   return true;
 }
 
+FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
+FPDFPageObj_GetDashPhase(FPDF_PAGEOBJECT page_object, float* phase) {
+  auto* pPageObj = CPDFPageObjectFromFPDFPageObject(page_object);
+  if (!pPageObj || !phase)
+    return false;
+
+  *phase = pPageObj->m_GraphState.GetLineDashPhase();
+  return true;
+}
+
+FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
+FPDFPageObj_SetDashPhase(FPDF_PAGEOBJECT page_object, float phase) {
+  auto* pPageObj = CPDFPageObjectFromFPDFPageObject(page_object);
+  if (!pPageObj)
+    return false;
+
+  pPageObj->m_GraphState.SetLineDashPhase(phase);
+  pPageObj->SetDirty(true);
+  return true;
+}
+
+FPDF_EXPORT int FPDF_CALLCONV
+FPDFPageObj_GetDashCount(FPDF_PAGEOBJECT page_object) {
+  auto* pPageObj = CPDFPageObjectFromFPDFPageObject(page_object);
+  return pPageObj ? pPageObj->m_GraphState.GetLineDashSize() : -1;
+}
+
+FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
+FPDFPageObj_GetDashArray(FPDF_PAGEOBJECT page_object,
+                         float* dash_array,
+                         size_t dash_count) {
+  auto* pPageObj = CPDFPageObjectFromFPDFPageObject(page_object);
+  if (!pPageObj || !dash_array)
+    return false;
+
+  auto dash_vector = pPageObj->m_GraphState.GetLineDashArray();
+  if (dash_vector.size() > dash_count)
+    return false;
+
+  memcpy(dash_array, dash_vector.data(), dash_vector.size() * sizeof(float));
+  return true;
+}
+
+FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
+FPDFPageObj_SetDashArray(FPDF_PAGEOBJECT page_object,
+                         const float* dash_array,
+                         size_t dash_count,
+                         float phase) {
+  if (dash_count > 0 && !dash_array)
+    return false;
+
+  auto* pPageObj = CPDFPageObjectFromFPDFPageObject(page_object);
+  if (!pPageObj)
+    return false;
+
+  std::vector<float> dashes;
+  if (dash_count > 0) {
+    dashes.reserve(dash_count);
+    dashes.assign(dash_array, dash_array + dash_count);
+  }
+
+  pPageObj->m_GraphState.SetLineDash(dashes, phase, 1.0f);
+
+  pPageObj->SetDirty(true);
+  return true;
+}
+
 FPDF_EXPORT int FPDF_CALLCONV
 FPDFFormObj_CountObjects(FPDF_PAGEOBJECT form_object) {
   const auto* pObjectList = CPDFPageObjHolderFromFPDFFormObject(form_object);
diff --git a/fpdfsdk/fpdf_editpage_embeddertest.cpp b/fpdfsdk/fpdf_editpage_embeddertest.cpp
index a630721..e18c23e 100644
--- a/fpdfsdk/fpdf_editpage_embeddertest.cpp
+++ b/fpdfsdk/fpdf_editpage_embeddertest.cpp
@@ -154,3 +154,136 @@
 
   UnloadPage(page);
 }
+
+TEST_F(FPDFEditPageEmbedderTest, DashingArrayAndPhase) {
+  {
+    EXPECT_FALSE(FPDFPageObj_GetDashPhase(nullptr, nullptr));
+
+    float phase = -1123.5f;
+    EXPECT_FALSE(FPDFPageObj_GetDashPhase(nullptr, &phase));
+    EXPECT_FLOAT_EQ(-1123.5f, phase);
+
+    EXPECT_EQ(-1, FPDFPageObj_GetDashCount(nullptr));
+
+    EXPECT_FALSE(FPDFPageObj_GetDashArray(nullptr, nullptr, 3));
+
+    float get_array[] = {-1.0f, -1.0f, -1.0f};
+    EXPECT_FALSE(FPDFPageObj_GetDashArray(nullptr, get_array, 3));
+    for (int i = 0; i < 3; i++)
+      EXPECT_FLOAT_EQ(-1.0f, get_array[i]);
+
+    EXPECT_FALSE(FPDFPageObj_SetDashPhase(nullptr, 5.0f));
+    EXPECT_FALSE(FPDFPageObj_SetDashArray(nullptr, nullptr, 3, 5.0f));
+
+    float set_array[] = {1.0f, 2.0f, 3.0f};
+    EXPECT_FALSE(FPDFPageObj_SetDashArray(nullptr, set_array, 3, 5.0f));
+  }
+
+  constexpr int kExpectedObjectCount = 3;
+  ASSERT_TRUE(OpenDocument("dashed_lines.pdf"));
+  FPDF_PAGE page = LoadPage(0);
+  ASSERT_TRUE(page);
+
+  ASSERT_EQ(kExpectedObjectCount, FPDFPage_CountObjects(page));
+
+  {
+    FPDF_PAGEOBJECT path = FPDFPage_GetObject(page, 0);
+    ASSERT_TRUE(path);
+    EXPECT_EQ(FPDF_PAGEOBJ_PATH, FPDFPageObj_GetType(path));
+
+    EXPECT_FALSE(FPDFPageObj_GetDashPhase(path, nullptr));
+    EXPECT_FALSE(FPDFPageObj_GetDashArray(path, nullptr, 3));
+    EXPECT_FALSE(FPDFPageObj_SetDashArray(path, nullptr, 3, 5.0f));
+
+    float phase = -1123.5f;
+    EXPECT_TRUE(FPDFPageObj_GetDashPhase(path, &phase));
+    EXPECT_FLOAT_EQ(0.0f, phase);
+    EXPECT_EQ(0, FPDFPageObj_GetDashCount(path));
+
+    float get_array[] = {-1.0f, -1.0f, -1.0f};
+    EXPECT_TRUE(FPDFPageObj_GetDashArray(path, get_array, 3));
+    for (int i = 0; i < 3; i++)
+      EXPECT_FLOAT_EQ(-1.0f, get_array[i]);
+  }
+
+  {
+    FPDF_PAGEOBJECT path = FPDFPage_GetObject(page, 1);
+    ASSERT_TRUE(path);
+    EXPECT_EQ(FPDF_PAGEOBJ_PATH, FPDFPageObj_GetType(path));
+
+    float phase = -1123.5f;
+    EXPECT_TRUE(FPDFPageObj_GetDashPhase(path, &phase));
+    EXPECT_LT(0.0f, phase);
+    ASSERT_EQ(6, FPDFPageObj_GetDashCount(path));
+
+    float dash_array[] = {-1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f};
+    ASSERT_TRUE(FPDFPageObj_GetDashArray(path, dash_array, 6));
+
+    for (int i = 0; i < 6; i++)
+      EXPECT_LT(0.0f, dash_array[i]);
+
+    // the array is decreasing in value.
+    for (int i = 0; i < 5; i++)
+      EXPECT_GT(dash_array[i], dash_array[i + 1]);
+
+    // modify phase
+    EXPECT_TRUE(FPDFPageObj_SetDashPhase(path, 1.0f));
+
+    phase = -1123.5f;
+    EXPECT_TRUE(FPDFPageObj_GetDashPhase(path, &phase));
+    EXPECT_FLOAT_EQ(1.0f, phase);
+
+    // clear array
+    EXPECT_TRUE(FPDFPageObj_SetDashArray(path, nullptr, 0, 0.0f));
+    EXPECT_EQ(0, FPDFPageObj_GetDashCount(path));
+
+    phase = -1123.5f;
+    EXPECT_TRUE(FPDFPageObj_GetDashPhase(path, &phase));
+    EXPECT_FLOAT_EQ(0.0f, phase);
+  }
+
+  {
+    FPDF_PAGEOBJECT path = FPDFPage_GetObject(page, 2);
+    ASSERT_TRUE(path);
+    EXPECT_EQ(FPDF_PAGEOBJ_PATH, FPDFPageObj_GetType(path));
+
+    float phase = -1123.5f;
+    EXPECT_TRUE(FPDFPageObj_GetDashPhase(path, &phase));
+    EXPECT_FLOAT_EQ(0.0f, phase);
+
+    EXPECT_EQ(0, FPDFPageObj_GetDashCount(path));
+
+    // `get_array` should be unmodified
+    float get_array[] = {-1.0f, -1.0f, -1.0f, -1.0f};
+    EXPECT_TRUE(FPDFPageObj_GetDashArray(path, get_array, 4));
+    for (int i = 0; i < 4; i++)
+      EXPECT_FLOAT_EQ(-1.0f, get_array[i]);
+
+    // modify dash_array and phase
+    const float set_array[] = {1.0f, 2.0f, 3.0f};
+    EXPECT_TRUE(FPDFPageObj_SetDashArray(path, set_array, 3, 5.0f));
+
+    phase = -1123.5f;
+    EXPECT_TRUE(FPDFPageObj_GetDashPhase(path, &phase));
+    EXPECT_FLOAT_EQ(5.0f, phase);
+    ASSERT_EQ(3, FPDFPageObj_GetDashCount(path));
+
+    ASSERT_TRUE(FPDFPageObj_GetDashArray(path, get_array, 4));
+
+    // `get_array` should be modified only up to dash_count
+    for (int i = 0; i < 3; i++)
+      EXPECT_FLOAT_EQ(static_cast<float>(i + 1), get_array[i]);
+
+    EXPECT_FLOAT_EQ(-1.0f, get_array[3]);
+
+    // clear array
+    EXPECT_TRUE(FPDFPageObj_SetDashArray(path, set_array, 0, 4.0f));
+    EXPECT_EQ(0, FPDFPageObj_GetDashCount(path));
+
+    phase = -1123.5f;
+    EXPECT_TRUE(FPDFPageObj_GetDashPhase(path, &phase));
+    EXPECT_FLOAT_EQ(4.0f, phase);
+  }
+
+  UnloadPage(page);
+}
diff --git a/fpdfsdk/fpdf_view_c_api_test.c b/fpdfsdk/fpdf_view_c_api_test.c
index 3924088..3748fb5 100644
--- a/fpdfsdk/fpdf_view_c_api_test.c
+++ b/fpdfsdk/fpdf_view_c_api_test.c
@@ -189,6 +189,9 @@
     CHK(FPDFPageObj_CreateTextObj);
     CHK(FPDFPageObj_Destroy);
     CHK(FPDFPageObj_GetBounds);
+    CHK(FPDFPageObj_GetDashArray);
+    CHK(FPDFPageObj_GetDashCount);
+    CHK(FPDFPageObj_GetDashPhase);
     CHK(FPDFPageObj_GetFillColor);
     CHK(FPDFPageObj_GetLineCap);
     CHK(FPDFPageObj_GetLineJoin);
@@ -201,6 +204,8 @@
     CHK(FPDFPageObj_NewTextObj);
     CHK(FPDFPageObj_RemoveMark);
     CHK(FPDFPageObj_SetBlendMode);
+    CHK(FPDFPageObj_SetDashArray);
+    CHK(FPDFPageObj_SetDashPhase);
     CHK(FPDFPageObj_SetFillColor);
     CHK(FPDFPageObj_SetLineCap);
     CHK(FPDFPageObj_SetLineJoin);
diff --git a/public/fpdf_edit.h b/public/fpdf_edit.h
index 3c9642b..2290cd5 100644
--- a/public/fpdf_edit.h
+++ b/public/fpdf_edit.h
@@ -896,6 +896,63 @@
                          unsigned int* B,
                          unsigned int* A);
 
+// Experimental API.
+// Get the line dash |phase| of |page_object|.
+//
+// page_object - handle to a page object.
+// phase - pointer where the dashing phase will be stored.
+//
+// Returns TRUE on success.
+FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
+FPDFPageObj_GetDashPhase(FPDF_PAGEOBJECT page_object, float* phase);
+
+// Experimental API.
+// Set the line dash phase of |page_object|.
+//
+// page_object - handle to a page object.
+// phase - line dash phase.
+//
+// Returns TRUE on success.
+FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
+FPDFPageObj_SetDashPhase(FPDF_PAGEOBJECT page_object, float phase);
+
+// Experimental API.
+// Get the line dash array of |page_object|.
+//
+// page_object - handle to a page object.
+//
+// Returns the line dash array size or -1 on failure.
+FPDF_EXPORT int FPDF_CALLCONV
+FPDFPageObj_GetDashCount(FPDF_PAGEOBJECT page_object);
+
+// Experimental API.
+// Get the line dash array of |page_object|.
+//
+// page_object - handle to a page object.
+// dash_array - pointer where the dashing array will be stored.
+// dash_count - number of elements in |dash_array|.
+//
+// Returns TRUE on success.
+FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
+FPDFPageObj_GetDashArray(FPDF_PAGEOBJECT page_object,
+                         float* dash_array,
+                         size_t dash_count);
+
+// Experimental API.
+// Set the line dash array of |page_object|.
+//
+// page_object - handle to a page object.
+// dash_array - the dash array.
+// dash_count - number of elements in |dash_array|.
+// phase - the line dash phase.
+//
+// Returns TRUE on success.
+FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
+FPDFPageObj_SetDashArray(FPDF_PAGEOBJECT page_object,
+                         const float* dash_array,
+                         size_t dash_count,
+                         float phase);
+
 // Get number of segments inside |path|.
 //
 //   path - handle to a path.
diff --git a/testing/resources/dashed_lines.in b/testing/resources/dashed_lines.in
new file mode 100644
index 0000000..b4f74bb
--- /dev/null
+++ b/testing/resources/dashed_lines.in
@@ -0,0 +1,37 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /MediaBox [0 0 200 100]
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+>>
+endobj
+{{object 4 0}} <<
+  {{streamlen}}
+>>
+stream
+q
+0 0 0 rg
+10 25 m 190 25 l S
+[6 5 4 3 2 1] 5 d
+10 50 m 190 50 l S
+[] 0 d
+10 75 m 190 75 l S
+Q
+endstream
+endobj
+{{xref}}
+{{trailer}}
+{{startxref}}
+%%EOF
diff --git a/testing/resources/dashed_lines.pdf b/testing/resources/dashed_lines.pdf
new file mode 100644
index 0000000..f40a591
--- /dev/null
+++ b/testing/resources/dashed_lines.pdf
@@ -0,0 +1,48 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /MediaBox [0 0 200 100]
+  /Count 1
+  /Kids [3 0 R]
+>>
+endobj
+3 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /Contents 4 0 R
+>>
+endobj
+4 0 obj <<
+  /Length 95
+>>
+stream
+q
+0 0 0 rg
+10 25 m 190 25 l S
+[6 5 4 3 2 1] 5 d
+10 50 m 190 50 l S
+[] 0 d
+10 75 m 190 75 l S
+Q
+endstream
+endobj
+xref
+0 5
+0000000000 65535 f 
+0000000015 00000 n 
+0000000068 00000 n 
+0000000157 00000 n 
+0000000226 00000 n 
+trailer <<
+  /Root 1 0 R
+  /Size 5
+>>
+startxref
+372
+%%EOF