Add FPDFDest_GetLocationInPage API

Add an API to get the value of the /XYZ destination parameter.

This CL was originally from https://codereview.chromium.org/1960193003/ by
halcanary@.

Review-Url: https://codereview.chromium.org/2481743004
diff --git a/BUILD.gn b/BUILD.gn
index 9a8bf07..ca9cb7b 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -1652,6 +1652,7 @@
     "core/fpdfapi/parser/cpdf_simple_parser_unittest.cpp",
     "core/fpdfapi/parser/cpdf_syntax_parser_unittest.cpp",
     "core/fpdfapi/parser/fpdf_parser_decode_unittest.cpp",
+    "core/fpdfdoc/cpdf_dest_unittest.cpp",
     "core/fpdfdoc/cpdf_filespec_unittest.cpp",
     "core/fpdfdoc/cpdf_formfield_unittest.cpp",
     "core/fpdftext/fpdf_text_int_unittest.cpp",
diff --git a/core/fpdfdoc/cpdf_dest.cpp b/core/fpdfdoc/cpdf_dest.cpp
index 312ef87..51e2d0b 100644
--- a/core/fpdfdoc/cpdf_dest.cpp
+++ b/core/fpdfdoc/cpdf_dest.cpp
@@ -8,6 +8,8 @@
 
 #include "core/fpdfapi/parser/cpdf_array.h"
 #include "core/fpdfapi/parser/cpdf_document.h"
+#include "core/fpdfapi/parser/cpdf_name.h"
+#include "core/fpdfapi/parser/cpdf_number.h"
 
 namespace {
 
@@ -64,6 +66,53 @@
   return 0;
 }
 
+bool CPDF_Dest::GetXYZ(bool* pHasX,
+                       bool* pHasY,
+                       bool* pHasZoom,
+                       float* pX,
+                       float* pY,
+                       float* pZoom) const {
+  *pHasX = false;
+  *pHasY = false;
+  *pHasZoom = false;
+
+  CPDF_Array* pArray = ToArray(m_pObj);
+  if (!pArray)
+    return false;
+
+  if (pArray->GetCount() < 5)
+    return false;
+
+  const CPDF_Name* xyz = ToName(pArray->GetDirectObjectAt(1));
+  if (!xyz || xyz->GetString() != "XYZ")
+    return false;
+
+  const CPDF_Number* numX = ToNumber(pArray->GetDirectObjectAt(2));
+  const CPDF_Number* numY = ToNumber(pArray->GetDirectObjectAt(3));
+  const CPDF_Number* numZoom = ToNumber(pArray->GetDirectObjectAt(4));
+
+  // If the value is a CPDF_Null then ToNumber will return nullptr.
+  *pHasX = !!numX;
+  *pHasY = !!numY;
+  *pHasZoom = !!numZoom;
+
+  if (numX)
+    *pX = numX->GetNumber();
+  if (numY)
+    *pY = numY->GetNumber();
+
+  // A zoom value of 0 is equivalent to a null value, so treat it as a null.
+  if (numZoom) {
+    float num = numZoom->GetNumber();
+    if (num == 0.0)
+      *pHasZoom = false;
+    else
+      *pZoom = num;
+  }
+
+  return true;
+}
+
 FX_FLOAT CPDF_Dest::GetParam(int index) {
   CPDF_Array* pArray = ToArray(m_pObj);
   return pArray ? pArray->GetNumberAt(2 + index) : 0;
diff --git a/core/fpdfdoc/cpdf_dest.h b/core/fpdfdoc/cpdf_dest.h
index 8f38192..527d1dc 100644
--- a/core/fpdfdoc/cpdf_dest.h
+++ b/core/fpdfdoc/cpdf_dest.h
@@ -25,6 +25,13 @@
   int GetZoomMode();
   FX_FLOAT GetParam(int index);
 
+  bool GetXYZ(bool* pHasX,
+              bool* pHasY,
+              bool* pHasZoom,
+              float* pX,
+              float* pY,
+              float* pZoom) const;
+
  private:
   CPDF_Object* m_pObj;
 };
diff --git a/core/fpdfdoc/cpdf_dest_unittest.cpp b/core/fpdfdoc/cpdf_dest_unittest.cpp
new file mode 100644
index 0000000..d427ab6
--- /dev/null
+++ b/core/fpdfdoc/cpdf_dest_unittest.cpp
@@ -0,0 +1,60 @@
+// Copyright 2016 PDFium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "core/fpdfapi/parser/cpdf_array.h"
+#include "core/fpdfapi/parser/cpdf_null.h"
+#include "core/fpdfapi/parser/cpdf_number.h"
+#include "core/fpdfdoc/cpdf_dest.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/test_support.h"
+#include "third_party/base/ptr_util.h"
+
+TEST(cpdf_dest, GetXYZ) {
+  bool hasX;
+  bool hasY;
+  bool hasZoom;
+  float x;
+  float y;
+  float zoom;
+
+  auto dest = pdfium::MakeUnique<CPDF_Dest>();
+  EXPECT_FALSE(dest->GetXYZ(&hasX, &hasY, &hasZoom, &x, &y, &zoom));
+
+  auto array = pdfium::MakeUnique<CPDF_Array>();
+  array->AddInteger(0);  // Page Index.
+  array->AddName("XYZ");
+  array->AddNumber(4);  // X
+
+  // Not enough entries.
+  dest = pdfium::MakeUnique<CPDF_Dest>(array.get());
+  EXPECT_FALSE(dest->GetXYZ(&hasX, &hasY, &hasZoom, &x, &y, &zoom));
+
+  array->AddNumber(5);  // Y
+  array->AddNumber(6);  // Zoom.
+
+  dest = pdfium::MakeUnique<CPDF_Dest>(array.get());
+  EXPECT_TRUE(dest->GetXYZ(&hasX, &hasY, &hasZoom, &x, &y, &zoom));
+  EXPECT_TRUE(hasX);
+  EXPECT_TRUE(hasY);
+  EXPECT_TRUE(hasZoom);
+  EXPECT_EQ(4, x);
+  EXPECT_EQ(5, y);
+  EXPECT_EQ(6, zoom);
+
+  // Set zoom to 0.
+  array->SetAt(4, new CPDF_Number(0));
+  dest = pdfium::MakeUnique<CPDF_Dest>(array.get());
+  EXPECT_TRUE(dest->GetXYZ(&hasX, &hasY, &hasZoom, &x, &y, &zoom));
+  EXPECT_FALSE(hasZoom);
+
+  // Set values to null.
+  array->SetAt(2, new CPDF_Null);
+  array->SetAt(3, new CPDF_Null);
+  array->SetAt(4, new CPDF_Null);
+  dest = pdfium::MakeUnique<CPDF_Dest>(array.get());
+  EXPECT_TRUE(dest->GetXYZ(&hasX, &hasY, &hasZoom, &x, &y, &zoom));
+  EXPECT_FALSE(hasX);
+  EXPECT_FALSE(hasY);
+  EXPECT_FALSE(hasZoom);
+}
diff --git a/fpdfsdk/fpdfdoc.cpp b/fpdfsdk/fpdfdoc.cpp
index 5d11359..401b3e4 100644
--- a/fpdfsdk/fpdfdoc.cpp
+++ b/fpdfsdk/fpdfdoc.cpp
@@ -13,6 +13,7 @@
 #include "core/fpdfapi/parser/cpdf_document.h"
 #include "core/fpdfdoc/cpdf_bookmark.h"
 #include "core/fpdfdoc/cpdf_bookmarktree.h"
+#include "core/fpdfdoc/cpdf_dest.h"
 #include "fpdfsdk/fsdk_define.h"
 #include "third_party/base/stl_util.h"
 
@@ -211,6 +212,32 @@
   return dest.GetPageIndex(pDoc);
 }
 
+DLLEXPORT FPDF_BOOL STDCALL FPDFDest_GetLocationInPage(FPDF_DEST pDict,
+                                                       FPDF_BOOL* hasXVal,
+                                                       FPDF_BOOL* hasYVal,
+                                                       FPDF_BOOL* hasZoomVal,
+                                                       FS_FLOAT* x,
+                                                       FS_FLOAT* y,
+                                                       FS_FLOAT* zoom) {
+  if (!pDict)
+    return false;
+
+  std::unique_ptr<CPDF_Dest> dest(
+      new CPDF_Dest(static_cast<CPDF_Object*>(pDict)));
+
+  // FPDF_BOOL is an int, GetXYZ expects bools.
+  bool bHasX;
+  bool bHasY;
+  bool bHasZoom;
+  if (!dest->GetXYZ(&bHasX, &bHasY, &bHasZoom, x, y, zoom))
+    return false;
+
+  *hasXVal = bHasX;
+  *hasYVal = bHasY;
+  *hasZoomVal = bHasZoom;
+  return true;
+}
+
 DLLEXPORT FPDF_LINK STDCALL FPDFLink_GetLinkAtPoint(FPDF_PAGE page,
                                                     double x,
                                                     double y) {
diff --git a/fpdfsdk/fpdfdoc_embeddertest.cpp b/fpdfsdk/fpdfdoc_embeddertest.cpp
index 39b36c4..1616093 100644
--- a/fpdfsdk/fpdfdoc_embeddertest.cpp
+++ b/fpdfsdk/fpdfdoc_embeddertest.cpp
@@ -42,6 +42,31 @@
   EXPECT_EQ(0U, FPDFDest_GetPageIndex(document(), dest));
 }
 
+TEST_F(FPDFDocEmbeddertest, DestGetLocationInPage) {
+  EXPECT_TRUE(OpenDocument("named_dests.pdf"));
+
+  // NULL FPDF_DEST case.
+  EXPECT_EQ(0U, FPDFDest_GetPageIndex(document(), nullptr));
+
+  FPDF_DEST dest = FPDF_GetNamedDestByName(document(), "First");
+  EXPECT_TRUE(dest);
+
+  FPDF_BOOL hasX;
+  FPDF_BOOL hasY;
+  FPDF_BOOL hasZoom;
+  FS_FLOAT x;
+  FS_FLOAT y;
+  FS_FLOAT zoom;
+  EXPECT_TRUE(
+      FPDFDest_GetLocationInPage(dest, &hasX, &hasY, &hasZoom, &x, &y, &zoom));
+  EXPECT_TRUE(hasX);
+  EXPECT_TRUE(hasY);
+  EXPECT_TRUE(hasZoom);
+  EXPECT_EQ(0, x);
+  EXPECT_EQ(0, y);
+  EXPECT_EQ(1, zoom);
+}
+
 TEST_F(FPDFDocEmbeddertest, ActionGetFilePath) {
   EXPECT_TRUE(OpenDocument("launch_action.pdf"));
 
diff --git a/fpdfsdk/fpdfdoc_unittest.cpp b/fpdfsdk/fpdfdoc_unittest.cpp
index 39e81d5..fc90257 100644
--- a/fpdfsdk/fpdfdoc_unittest.cpp
+++ b/fpdfsdk/fpdfdoc_unittest.cpp
@@ -8,12 +8,15 @@
 #include <vector>
 
 #include "core/fpdfapi/cpdf_modulemgr.h"
+#include "core/fpdfapi/parser/cpdf_array.h"
 #include "core/fpdfapi/parser/cpdf_document.h"
 #include "core/fpdfapi/parser/cpdf_name.h"
+#include "core/fpdfapi/parser/cpdf_null.h"
 #include "core/fpdfapi/parser/cpdf_number.h"
 #include "core/fpdfapi/parser/cpdf_parser.h"
 #include "core/fpdfapi/parser/cpdf_reference.h"
 #include "core/fpdfapi/parser/cpdf_string.h"
+#include "core/fpdfdoc/cpdf_dest.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "testing/test_support.h"
 #include "third_party/base/ptr_util.h"
@@ -230,3 +233,41 @@
     EXPECT_EQ(bookmarks[3].obj, FPDFBookmark_Find(m_pDoc.get(), title.get()));
   }
 }
+
+TEST_F(PDFDocTest, GetLocationInPage) {
+  auto array = pdfium::MakeUnique<CPDF_Array>();
+  array->AddInteger(0);  // Page Index.
+  array->AddName("XYZ");
+  array->AddNumber(4);  // X
+  array->AddNumber(5);  // Y
+  array->AddNumber(6);  // Zoom.
+
+  FPDF_BOOL hasX;
+  FPDF_BOOL hasY;
+  FPDF_BOOL hasZoom;
+  FS_FLOAT x;
+  FS_FLOAT y;
+  FS_FLOAT zoom;
+
+  EXPECT_TRUE(FPDFDest_GetLocationInPage(array.get(), &hasX, &hasY, &hasZoom,
+                                         &x, &y, &zoom));
+  EXPECT_TRUE(hasX);
+  EXPECT_TRUE(hasY);
+  EXPECT_TRUE(hasZoom);
+  EXPECT_EQ(4, x);
+  EXPECT_EQ(5, y);
+  EXPECT_EQ(6, zoom);
+
+  array->SetAt(2, new CPDF_Null);
+  array->SetAt(3, new CPDF_Null);
+  array->SetAt(4, new CPDF_Null);
+  EXPECT_TRUE(FPDFDest_GetLocationInPage(array.get(), &hasX, &hasY, &hasZoom,
+                                         &x, &y, &zoom));
+  EXPECT_FALSE(hasX);
+  EXPECT_FALSE(hasY);
+  EXPECT_FALSE(hasZoom);
+
+  array = pdfium::MakeUnique<CPDF_Array>();
+  EXPECT_FALSE(FPDFDest_GetLocationInPage(array.get(), &hasX, &hasY, &hasZoom,
+                                          &x, &y, &zoom));
+}
diff --git a/fpdfsdk/fpdfview_c_api_test.c b/fpdfsdk/fpdfview_c_api_test.c
index 4847180..a48ddb9 100644
--- a/fpdfsdk/fpdfview_c_api_test.c
+++ b/fpdfsdk/fpdfview_c_api_test.c
@@ -54,6 +54,7 @@
     CHK(FPDFAction_GetFilePath);
     CHK(FPDFAction_GetURIPath);
     CHK(FPDFDest_GetPageIndex);
+    CHK(FPDFDest_GetLocationInPage);
     CHK(FPDFLink_GetLinkAtPoint);
     CHK(FPDFLink_GetLinkZOrderAtPoint);
     CHK(FPDFLink_GetDest);
diff --git a/public/fpdf_doc.h b/public/fpdf_doc.h
index c3be0e0..206dc37 100644
--- a/public/fpdf_doc.h
+++ b/public/fpdf_doc.h
@@ -171,6 +171,28 @@
 DLLEXPORT unsigned long STDCALL FPDFDest_GetPageIndex(FPDF_DOCUMENT document,
                                                       FPDF_DEST dest);
 
+// Get the (x, y, zoom) location of |dest| in the destination page, if the
+// destination is in [page /XYZ x y zoom] syntax.
+//
+//   dest       - handle to the destination.
+//   hasXVal    - out parameter; true if the x value is not null
+//   hasYVal    - out parameter; true if the y value is not null
+//   hasZoomVal - out parameter; true if the zoom value is not null
+//   x          - out parameter; the x coordinate, in page coordinates.
+//   y          - out parameter; the y coordinate, in page coordinates.
+//   zoom       - out parameter; the zoom value.
+// Returns TRUE on successfully reading the /XYZ value.
+//
+// Note the [x, y, zoom] values are only set if the corresponding hasXVal,
+// hasYVal or hasZoomVal flags are true.
+DLLEXPORT FPDF_BOOL STDCALL FPDFDest_GetLocationInPage(FPDF_DEST dest,
+                                                       FPDF_BOOL* hasXCoord,
+                                                       FPDF_BOOL* hasYCoord,
+                                                       FPDF_BOOL* hasZoom,
+                                                       FS_FLOAT* x,
+                                                       FS_FLOAT* y,
+                                                       FS_FLOAT* zoom);
+
 // Find a link at point (|x|,|y|) on |page|.
 //
 //   page - handle to the document page.