Open FPDFDest_GetView API.

FPDFDest_GetView returns the view fit type and the parameters of the
view for a given destination.

This is useful to have more precise internal links and bookmarks that
are able to manipulate the viewport position and zoom level to focus
on the part of the PDF that it links to.

Bug: 55776, 535978, 748852
Change-Id: Ibf7df40a852030d75ec78cec7662380319569850
Reviewed-on: https://pdfium-review.googlesource.com/21790
Commit-Queue: Henrique Nakashima <hnakashima@chromium.org>
Reviewed-by: Ryan Harrison <rharrison@chromium.org>
diff --git a/core/fpdfdoc/cpdf_dest.cpp b/core/fpdfdoc/cpdf_dest.cpp
index ebe3834..cb15bf1 100644
--- a/core/fpdfdoc/cpdf_dest.cpp
+++ b/core/fpdfdoc/cpdf_dest.cpp
@@ -6,6 +6,8 @@
 
 #include "core/fpdfdoc/cpdf_dest.h"
 
+#include <algorithm>
+
 #include "core/fpdfapi/parser/cpdf_array.h"
 #include "core/fpdfapi/parser/cpdf_document.h"
 #include "core/fpdfapi/parser/cpdf_name.h"
@@ -13,8 +15,14 @@
 
 namespace {
 
-const char* const g_sZoomModes[] = {"XYZ",  "Fit",   "FitH",  "FitV", "FitR",
-                                    "FitB", "FitBH", "FitBV", nullptr};
+// These arrays are indexed by the PDFDEST_VIEW_* constants.
+
+// Last element is a sentinel.
+const char* const g_sZoomModes[] = {"Unknown", "XYZ",  "Fit",  "FitH",
+                                    "FitV",    "FitR", "FitB", "FitBH",
+                                    "FitBV",   nullptr};
+
+const int g_sZoomModeMaxParamCount[] = {0, 3, 0, 1, 1, 4, 0, 1, 1, 0};
 
 }  // namespace
 
@@ -66,9 +74,9 @@
     return 0;
 
   ByteString mode = pObj->GetString();
-  for (int i = 0; g_sZoomModes[i]; ++i) {
+  for (int i = 1; g_sZoomModes[i]; ++i) {
     if (mode == g_sZoomModes[i])
-      return i + 1;
+      return i;
   }
 
   return 0;
@@ -121,6 +129,16 @@
   return true;
 }
 
+unsigned int CPDF_Dest::GetNumParams() {
+  CPDF_Array* pArray = ToArray(m_pObj.Get());
+  if (!pArray || pArray->GetCount() < 2)
+    return 0;
+
+  size_t maxParamsForFitType = g_sZoomModeMaxParamCount[GetZoomMode()];
+  size_t numParamsInArray = pArray->GetCount() - 2;
+  return std::min(maxParamsForFitType, numParamsInArray);
+}
+
 float CPDF_Dest::GetParam(int index) {
   CPDF_Array* pArray = ToArray(m_pObj.Get());
   return pArray ? pArray->GetNumberAt(2 + index) : 0;
diff --git a/core/fpdfdoc/cpdf_dest.h b/core/fpdfdoc/cpdf_dest.h
index 2836ebb..4959901 100644
--- a/core/fpdfdoc/cpdf_dest.h
+++ b/core/fpdfdoc/cpdf_dest.h
@@ -25,7 +25,11 @@
   ByteString GetRemoteName();
   int GetPageIndex(CPDF_Document* pDoc);
   uint32_t GetPageObjNum();
+
+  // Returns the zoom mode, as one of the PDFDEST_VIEW_* values in fpdf_doc.h.
   int GetZoomMode();
+
+  unsigned int GetNumParams();
   float GetParam(int index);
 
   bool GetXYZ(bool* pHasX,
diff --git a/fpdfsdk/fpdfdoc.cpp b/fpdfsdk/fpdfdoc.cpp
index 4d2942f..51a1c61 100644
--- a/fpdfsdk/fpdfdoc.cpp
+++ b/fpdfsdk/fpdfdoc.cpp
@@ -210,6 +210,30 @@
   return dest.GetPageIndex(pDoc);
 }
 
+FPDF_EXPORT unsigned long FPDF_CALLCONV
+FPDFDest_GetView(FPDF_DOCUMENT document,
+                 FPDF_DEST pDict,
+                 unsigned long* outNumParams,
+                 FS_FLOAT* outParams) {
+  if (!pDict) {
+    *outNumParams = 0;
+    return 0;
+  }
+
+  CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document);
+  if (!pDoc) {
+    *outNumParams = 0;
+    return 0;
+  }
+
+  CPDF_Dest dest(static_cast<CPDF_Array*>(pDict));
+
+  *outNumParams = dest.GetNumParams();
+  for (unsigned long i = 0; i < *outNumParams; ++i)
+    outParams[i] = dest.GetParam(i);
+  return dest.GetZoomMode();
+}
+
 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
 FPDFDest_GetLocationInPage(FPDF_DEST pDict,
                            FPDF_BOOL* hasXVal,
diff --git a/fpdfsdk/fpdfdoc_embeddertest.cpp b/fpdfsdk/fpdfdoc_embeddertest.cpp
index c691a17..24414a1 100644
--- a/fpdfsdk/fpdfdoc_embeddertest.cpp
+++ b/fpdfsdk/fpdfdoc_embeddertest.cpp
@@ -42,6 +42,65 @@
   EXPECT_EQ(0U, FPDFDest_GetPageIndex(document(), dest));
 }
 
+TEST_F(FPDFDocEmbeddertest, DestGetView) {
+  EXPECT_TRUE(OpenDocument("named_dests.pdf"));
+
+  unsigned long numParams;
+  FS_FLOAT params[4];
+
+  numParams = 42;
+  std::fill_n(params, 4, 42.4242f);
+  EXPECT_EQ(static_cast<unsigned long>(PDFDEST_VIEW_UNKNOWN_MODE),
+            FPDFDest_GetView(document(), nullptr, &numParams, params));
+  EXPECT_EQ(0U, numParams);
+  EXPECT_FLOAT_EQ(42.4242f, params[0]);
+
+  numParams = 42;
+  std::fill_n(params, 4, 42.4242f);
+  FPDF_DEST dest = FPDF_GetNamedDestByName(document(), "First");
+  EXPECT_TRUE(dest);
+  EXPECT_EQ(static_cast<unsigned long>(PDFDEST_VIEW_XYZ),
+            FPDFDest_GetView(document(), dest, &numParams, params));
+  EXPECT_EQ(3U, numParams);
+  EXPECT_FLOAT_EQ(0, params[0]);
+  EXPECT_FLOAT_EQ(0, params[1]);
+  EXPECT_FLOAT_EQ(1, params[2]);
+  EXPECT_FLOAT_EQ(42.4242f, params[3]);
+
+  numParams = 42;
+  std::fill_n(params, 4, 42.4242f);
+  dest = FPDF_GetNamedDestByName(document(), "Next");
+  EXPECT_TRUE(dest);
+  EXPECT_EQ(static_cast<unsigned long>(PDFDEST_VIEW_FIT),
+            FPDFDest_GetView(document(), dest, &numParams, params));
+  EXPECT_EQ(0U, numParams);
+  EXPECT_FLOAT_EQ(42.4242f, params[0]);
+
+  numParams = 42;
+  std::fill_n(params, 4, 42.4242f);
+  dest = FPDF_GetNamedDestByName(document(), "FirstAlternate");
+  EXPECT_TRUE(dest);
+  EXPECT_EQ(static_cast<unsigned long>(PDFDEST_VIEW_XYZ),
+            FPDFDest_GetView(document(), dest, &numParams, params));
+  EXPECT_EQ(3U, numParams);
+  EXPECT_FLOAT_EQ(200, params[0]);
+  EXPECT_FLOAT_EQ(400, params[1]);
+  EXPECT_FLOAT_EQ(800, params[2]);
+  EXPECT_FLOAT_EQ(42.4242f, params[3]);
+
+  numParams = 42;
+  std::fill_n(params, 4, 42.4242f);
+  dest = FPDF_GetNamedDestByName(document(), "LastAlternate");
+  EXPECT_TRUE(dest);
+  EXPECT_EQ(static_cast<unsigned long>(PDFDEST_VIEW_XYZ),
+            FPDFDest_GetView(document(), dest, &numParams, params));
+  EXPECT_EQ(3U, numParams);
+  EXPECT_FLOAT_EQ(0, params[0]);
+  EXPECT_FLOAT_EQ(0, params[1]);
+  EXPECT_FLOAT_EQ(-200, params[2]);
+  EXPECT_FLOAT_EQ(42.4242f, params[3]);
+}
+
 TEST_F(FPDFDocEmbeddertest, DestGetLocationInPage) {
   EXPECT_TRUE(OpenDocument("named_dests.pdf"));
 
diff --git a/fpdfsdk/fpdfview_c_api_test.c b/fpdfsdk/fpdfview_c_api_test.c
index de4fa1c..199c383 100644
--- a/fpdfsdk/fpdfview_c_api_test.c
+++ b/fpdfsdk/fpdfview_c_api_test.c
@@ -107,6 +107,7 @@
     CHK(FPDFAction_GetURIPath);
     CHK(FPDFDest_GetPageIndex);
     CHK(FPDFDest_GetLocationInPage);
+    CHK(FPDFDest_GetView);
     CHK(FPDFLink_GetLinkAtPoint);
     CHK(FPDFLink_GetLinkZOrderAtPoint);
     CHK(FPDFLink_GetDest);
diff --git a/public/fpdf_doc.h b/public/fpdf_doc.h
index 0f87361..93efa7a 100644
--- a/public/fpdf_doc.h
+++ b/public/fpdf_doc.h
@@ -25,6 +25,17 @@
 // Launch an application or open a file.
 #define PDFACTION_LAUNCH 4
 
+// View destination fit types. See pdfmark reference v9, page 48.
+#define PDFDEST_VIEW_UNKNOWN_MODE 0
+#define PDFDEST_VIEW_XYZ 1
+#define PDFDEST_VIEW_FIT 2
+#define PDFDEST_VIEW_FITH 3
+#define PDFDEST_VIEW_FITV 4
+#define PDFDEST_VIEW_FITR 5
+#define PDFDEST_VIEW_FITB 6
+#define PDFDEST_VIEW_FITBH 7
+#define PDFDEST_VIEW_FITBV 8
+
 typedef struct _FS_QUADPOINTSF {
   FS_FLOAT x1;
   FS_FLOAT y1;
@@ -175,6 +186,22 @@
 FPDF_EXPORT unsigned long FPDF_CALLCONV
 FPDFDest_GetPageIndex(FPDF_DOCUMENT document, FPDF_DEST dest);
 
+// Get the view (fit type) specified by |dest|.
+// Experimental API. Subject to change.
+//
+//   document     - handle to the document.
+//   dest         - handle to the destination.
+//   outNumParams - buffer to write the number of view parameters.
+//   outParams -    buffer to write the view parameters. Must be at least 4
+//                  FS_FLOATs long.
+// Returns one of the PDFDEST_VIEW_* constants, PDFDEST_VIEW_UNKNOWN_MODE if
+// |dest| does not specify a view.
+FPDF_EXPORT unsigned long FPDF_CALLCONV
+FPDFDest_GetView(FPDF_DOCUMENT document,
+                 FPDF_DEST dest,
+                 unsigned long* outNumParams,
+                 FS_FLOAT* outParams);
+
 // Get the (x, y, zoom) location of |dest| in the destination page, if the
 // destination is in [page /XYZ x y zoom] syntax.
 //