1// Copyright 2014 PDFium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
6
7#include "public/fpdf_doc.h"
8
9#include <memory>
10#include <set>
11
12#include "core/fpdfapi/page/cpdf_page.h"
13#include "core/fpdfapi/parser/cpdf_array.h"
14#include "core/fpdfapi/parser/cpdf_document.h"
15#include "core/fpdfdoc/cpdf_bookmark.h"
16#include "core/fpdfdoc/cpdf_bookmarktree.h"
17#include "core/fpdfdoc/cpdf_dest.h"
18#include "core/fpdfdoc/cpdf_pagelabel.h"
19#include "fpdfsdk/fsdk_define.h"
20#include "third_party/base/ptr_util.h"
21#include "third_party/base/stl_util.h"
22
23namespace {
24
25CPDF_Bookmark FindBookmark(const CPDF_BookmarkTree& tree,
26                           CPDF_Bookmark bookmark,
27                           const WideString& title,
28                           std::set<CPDF_Dictionary*>* visited) {
29  // Return if already checked to avoid circular calling.
30  if (pdfium::ContainsKey(*visited, bookmark.GetDict()))
31    return CPDF_Bookmark();
32  visited->insert(bookmark.GetDict());
33
34  if (bookmark.GetDict() &&
35      bookmark.GetTitle().CompareNoCase(title.c_str()) == 0) {
36    // First check this item.
37    return bookmark;
38  }
39
40  // Go into children items.
41  CPDF_Bookmark child = tree.GetFirstChild(bookmark);
42  while (child.GetDict() && !pdfium::ContainsKey(*visited, child.GetDict())) {
43    // Check this item and its children.
44    CPDF_Bookmark found = FindBookmark(tree, child, title, visited);
45    if (found.GetDict())
46      return found;
47    child = tree.GetNextSibling(child);
48  }
49  return CPDF_Bookmark();
50}
51
52CPDF_LinkList* GetLinkList(CPDF_Page* page) {
53  if (!page)
54    return nullptr;
55
56  CPDF_Document* pDoc = page->m_pDocument.Get();
57  std::unique_ptr<CPDF_LinkList>* pHolder = pDoc->LinksContext();
58  if (!pHolder->get())
59    *pHolder = pdfium::MakeUnique<CPDF_LinkList>();
60  return pHolder->get();
61}
62
63}  // namespace
64
65FPDF_EXPORT FPDF_BOOKMARK FPDF_CALLCONV
66FPDFBookmark_GetFirstChild(FPDF_DOCUMENT document, FPDF_BOOKMARK pDict) {
67  CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document);
68  if (!pDoc)
69    return nullptr;
70  CPDF_BookmarkTree tree(pDoc);
71  CPDF_Bookmark bookmark =
72      CPDF_Bookmark(ToDictionary(static_cast<CPDF_Object*>(pDict)));
73  return tree.GetFirstChild(bookmark).GetDict();
74}
75
76FPDF_EXPORT FPDF_BOOKMARK FPDF_CALLCONV
77FPDFBookmark_GetNextSibling(FPDF_DOCUMENT document, FPDF_BOOKMARK pDict) {
78  if (!pDict)
79    return nullptr;
80  CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document);
81  if (!pDoc)
82    return nullptr;
83  CPDF_BookmarkTree tree(pDoc);
84  CPDF_Bookmark bookmark =
85      CPDF_Bookmark(ToDictionary(static_cast<CPDF_Object*>(pDict)));
86  return tree.GetNextSibling(bookmark).GetDict();
87}
88
89FPDF_EXPORT unsigned long FPDF_CALLCONV
90FPDFBookmark_GetTitle(FPDF_BOOKMARK pDict, void* buffer, unsigned long buflen) {
91  if (!pDict)
92    return 0;
93  CPDF_Bookmark bookmark(ToDictionary(static_cast<CPDF_Object*>(pDict)));
94  WideString title = bookmark.GetTitle();
95  return Utf16EncodeMaybeCopyAndReturnLength(title, buffer, buflen);
96}
97
98FPDF_EXPORT FPDF_BOOKMARK FPDF_CALLCONV
99FPDFBookmark_Find(FPDF_DOCUMENT document, FPDF_WIDESTRING title) {
100  if (!title || title[0] == 0)
101    return nullptr;
102  CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document);
103  if (!pDoc)
104    return nullptr;
105  CPDF_BookmarkTree tree(pDoc);
106  size_t len = WideString::WStringLength(title);
107  WideString encodedTitle = WideString::FromUTF16LE(title, len);
108  std::set<CPDF_Dictionary*> visited;
109  return FindBookmark(tree, CPDF_Bookmark(), encodedTitle, &visited).GetDict();
110}
111
112FPDF_EXPORT FPDF_DEST FPDF_CALLCONV FPDFBookmark_GetDest(FPDF_DOCUMENT document,
113                                                         FPDF_BOOKMARK pDict) {
114  if (!pDict)
115    return nullptr;
116  CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document);
117  if (!pDoc)
118    return nullptr;
119  CPDF_Bookmark bookmark(ToDictionary(static_cast<CPDF_Object*>(pDict)));
120  CPDF_Dest dest = bookmark.GetDest(pDoc);
121  if (dest.GetObject())
122    return dest.GetObject();
123  // If this bookmark is not directly associated with a dest, we try to get
124  // action
125  CPDF_Action action = bookmark.GetAction();
126  if (!action.GetDict())
127    return nullptr;
128  return action.GetDest(pDoc).GetObject();
129}
130
131FPDF_EXPORT FPDF_ACTION FPDF_CALLCONV
132FPDFBookmark_GetAction(FPDF_BOOKMARK pDict) {
133  if (!pDict)
134    return nullptr;
135  CPDF_Bookmark bookmark(ToDictionary(static_cast<CPDF_Object*>(pDict)));
136  return bookmark.GetAction().GetDict();
137}
138
139FPDF_EXPORT unsigned long FPDF_CALLCONV FPDFAction_GetType(FPDF_ACTION pDict) {
140  if (!pDict)
141    return PDFACTION_UNSUPPORTED;
142
143  CPDF_Action action(ToDictionary(static_cast<CPDF_Object*>(pDict)));
144  CPDF_Action::ActionType type = action.GetType();
145  switch (type) {
146    case CPDF_Action::GoTo:
147      return PDFACTION_GOTO;
148    case CPDF_Action::GoToR:
149      return PDFACTION_REMOTEGOTO;
150    case CPDF_Action::URI:
151      return PDFACTION_URI;
152    case CPDF_Action::Launch:
153      return PDFACTION_LAUNCH;
154    default:
155      return PDFACTION_UNSUPPORTED;
156  }
157}
158
159FPDF_EXPORT FPDF_DEST FPDF_CALLCONV FPDFAction_GetDest(FPDF_DOCUMENT document,
160                                                       FPDF_ACTION pDict) {
161  if (!pDict)
162    return nullptr;
163  CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document);
164  if (!pDoc)
165    return nullptr;
166  CPDF_Action action(ToDictionary(static_cast<CPDF_Object*>(pDict)));
167  return action.GetDest(pDoc).GetObject();
168}
169
170FPDF_EXPORT unsigned long FPDF_CALLCONV
171FPDFAction_GetFilePath(FPDF_ACTION pDict, void* buffer, unsigned long buflen) {
172  unsigned long type = FPDFAction_GetType(pDict);
173  if (type != PDFACTION_REMOTEGOTO && type != PDFACTION_LAUNCH)
174    return 0;
175
176  CPDF_Action action(ToDictionary(static_cast<CPDF_Object*>(pDict)));
177  ByteString path = action.GetFilePath().UTF8Encode();
178  unsigned long len = path.GetLength() + 1;
179  if (buffer && len <= buflen)
180    memcpy(buffer, path.c_str(), len);
181  return len;
182}
183
184FPDF_EXPORT unsigned long FPDF_CALLCONV
185FPDFAction_GetURIPath(FPDF_DOCUMENT document,
186                      FPDF_ACTION pDict,
187                      void* buffer,
188                      unsigned long buflen) {
189  if (!pDict)
190    return 0;
191  CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document);
192  if (!pDoc)
193    return 0;
194  CPDF_Action action(ToDictionary(static_cast<CPDF_Object*>(pDict)));
195  ByteString path = action.GetURI(pDoc);
196  unsigned long len = path.GetLength() + 1;
197  if (buffer && len <= buflen)
198    memcpy(buffer, path.c_str(), len);
199  return len;
200}
201
202FPDF_EXPORT unsigned long FPDF_CALLCONV
203FPDFDest_GetPageIndex(FPDF_DOCUMENT document, FPDF_DEST pDict) {
204  if (!pDict)
205    return 0;
206  CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document);
207  if (!pDoc)
208    return 0;
209  CPDF_Dest dest(static_cast<CPDF_Array*>(pDict));
210  return dest.GetPageIndex(pDoc);
211}
212
213FPDF_EXPORT unsigned long FPDF_CALLCONV
214FPDFDest_GetView(FPDF_DEST pDict,
215                 unsigned long* pNumParams,
216                 FS_FLOAT* pParams) {
217  if (!pDict) {
218    *pNumParams = 0;
219    return 0;
220  }
221
222  CPDF_Dest dest(static_cast<CPDF_Array*>(pDict));
223  unsigned long nParams = dest.GetNumParams();
224  ASSERT(nParams <= 4);
225  *pNumParams = nParams;
226  for (unsigned long i = 0; i < nParams; ++i)
227    pParams[i] = dest.GetParam(i);
228  return dest.GetZoomMode();
229}
230
231FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
232FPDFDest_GetLocationInPage(FPDF_DEST pDict,
233                           FPDF_BOOL* hasXVal,
234                           FPDF_BOOL* hasYVal,
235                           FPDF_BOOL* hasZoomVal,
236                           FS_FLOAT* x,
237                           FS_FLOAT* y,
238                           FS_FLOAT* zoom) {
239  if (!pDict)
240    return false;
241
242  auto dest = pdfium::MakeUnique<CPDF_Dest>(static_cast<CPDF_Object*>(pDict));
243
244  // FPDF_BOOL is an int, GetXYZ expects bools.
245  bool bHasX;
246  bool bHasY;
247  bool bHasZoom;
248  if (!dest->GetXYZ(&bHasX, &bHasY, &bHasZoom, x, y, zoom))
249    return false;
250
251  *hasXVal = bHasX;
252  *hasYVal = bHasY;
253  *hasZoomVal = bHasZoom;
254  return true;
255}
256
257FPDF_EXPORT FPDF_LINK FPDF_CALLCONV FPDFLink_GetLinkAtPoint(FPDF_PAGE page,
258                                                            double x,
259                                                            double y) {
260  CPDF_Page* pPage = CPDFPageFromFPDFPage(page);
261  if (!pPage)
262    return nullptr;
263
264  CPDF_LinkList* pLinkList = GetLinkList(pPage);
265  if (!pLinkList)
266    return nullptr;
267
268  return pLinkList
269      ->GetLinkAtPoint(pPage,
270                       CFX_PointF(static_cast<float>(x), static_cast<float>(y)),
271                       nullptr)
272      .GetDict();
273}
274
275FPDF_EXPORT int FPDF_CALLCONV FPDFLink_GetLinkZOrderAtPoint(FPDF_PAGE page,
276                                                            double x,
277                                                            double y) {
278  CPDF_Page* pPage = CPDFPageFromFPDFPage(page);
279  if (!pPage)
280    return -1;
281
282  CPDF_LinkList* pLinkList = GetLinkList(pPage);
283  if (!pLinkList)
284    return -1;
285
286  int z_order = -1;
287  pLinkList->GetLinkAtPoint(
288      pPage, CFX_PointF(static_cast<float>(x), static_cast<float>(y)),
289      &z_order);
290  return z_order;
291}
292
293FPDF_EXPORT FPDF_DEST FPDF_CALLCONV FPDFLink_GetDest(FPDF_DOCUMENT document,
294                                                     FPDF_LINK pDict) {
295  if (!pDict)
296    return nullptr;
297  CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document);
298  if (!pDoc)
299    return nullptr;
300  CPDF_Link link(ToDictionary(static_cast<CPDF_Object*>(pDict)));
301  FPDF_DEST dest = link.GetDest(pDoc).GetObject();
302  if (dest)
303    return dest;
304  // If this link is not directly associated with a dest, we try to get action
305  CPDF_Action action = link.GetAction();
306  if (!action.GetDict())
307    return nullptr;
308  return action.GetDest(pDoc).GetObject();
309}
310
311FPDF_EXPORT FPDF_ACTION FPDF_CALLCONV FPDFLink_GetAction(FPDF_LINK pDict) {
312  if (!pDict)
313    return nullptr;
314
315  CPDF_Link link(ToDictionary(static_cast<CPDF_Object*>(pDict)));
316  return link.GetAction().GetDict();
317}
318
319FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFLink_Enumerate(FPDF_PAGE page,
320                                                       int* startPos,
321                                                       FPDF_LINK* linkAnnot) {
322  if (!startPos || !linkAnnot)
323    return false;
324  CPDF_Page* pPage = CPDFPageFromFPDFPage(page);
325  if (!pPage || !pPage->m_pFormDict)
326    return false;
327  CPDF_Array* pAnnots = pPage->m_pFormDict->GetArrayFor("Annots");
328  if (!pAnnots)
329    return false;
330  for (size_t i = *startPos; i < pAnnots->GetCount(); i++) {
331    CPDF_Dictionary* pDict =
332        ToDictionary(static_cast<CPDF_Object*>(pAnnots->GetDirectObjectAt(i)));
333    if (!pDict)
334      continue;
335    if (pDict->GetStringFor("Subtype") == "Link") {
336      *startPos = static_cast<int>(i + 1);
337      *linkAnnot = static_cast<FPDF_LINK>(pDict);
338      return true;
339    }
340  }
341  return false;
342}
343
344FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDFLink_GetAnnotRect(FPDF_LINK linkAnnot,
345                                                          FS_RECTF* rect) {
346  if (!linkAnnot || !rect)
347    return false;
348  CPDF_Dictionary* pAnnotDict =
349      ToDictionary(static_cast<CPDF_Object*>(linkAnnot));
350  FSRECTFFromCFXFloatRect(pAnnotDict->GetRectFor("Rect"), rect);
351  return true;
352}
353
354FPDF_EXPORT int FPDF_CALLCONV FPDFLink_CountQuadPoints(FPDF_LINK linkAnnot) {
355  if (!linkAnnot)
356    return 0;
357  CPDF_Dictionary* pAnnotDict =
358      ToDictionary(static_cast<CPDF_Object*>(linkAnnot));
359  CPDF_Array* pArray = pAnnotDict->GetArrayFor("QuadPoints");
360  if (!pArray)
361    return 0;
362  return static_cast<int>(pArray->GetCount() / 8);
363}
364
365FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
366FPDFLink_GetQuadPoints(FPDF_LINK linkAnnot,
367                       int quadIndex,
368                       FS_QUADPOINTSF* quadPoints) {
369  if (!linkAnnot || !quadPoints)
370    return false;
371  CPDF_Dictionary* pAnnotDict =
372      ToDictionary(static_cast<CPDF_Object*>(linkAnnot));
373  CPDF_Array* pArray = pAnnotDict->GetArrayFor("QuadPoints");
374  if (!pArray)
375    return false;
376
377  if (quadIndex < 0 ||
378      static_cast<size_t>(quadIndex) >= pArray->GetCount() / 8 ||
379      (static_cast<size_t>(quadIndex * 8 + 7) >= pArray->GetCount())) {
380    return false;
381  }
382
383  quadPoints->x1 = pArray->GetNumberAt(quadIndex * 8);
384  quadPoints->y1 = pArray->GetNumberAt(quadIndex * 8 + 1);
385  quadPoints->x2 = pArray->GetNumberAt(quadIndex * 8 + 2);
386  quadPoints->y2 = pArray->GetNumberAt(quadIndex * 8 + 3);
387  quadPoints->x3 = pArray->GetNumberAt(quadIndex * 8 + 4);
388  quadPoints->y3 = pArray->GetNumberAt(quadIndex * 8 + 5);
389  quadPoints->x4 = pArray->GetNumberAt(quadIndex * 8 + 6);
390  quadPoints->y4 = pArray->GetNumberAt(quadIndex * 8 + 7);
391  return true;
392}
393
394FPDF_EXPORT unsigned long FPDF_CALLCONV FPDF_GetMetaText(FPDF_DOCUMENT document,
395                                                         FPDF_BYTESTRING tag,
396                                                         void* buffer,
397                                                         unsigned long buflen) {
398  if (!tag)
399    return 0;
400  CPDF_Document* pDoc = CPDFDocumentFromFPDFDocument(document);
401  if (!pDoc)
402    return 0;
403  pDoc->LoadDocumentInfo();
404  const CPDF_Dictionary* pInfo = pDoc->GetInfo();
405  if (!pInfo)
406    return 0;
407  WideString text = pInfo->GetUnicodeTextFor(tag);
408  return Utf16EncodeMaybeCopyAndReturnLength(text, buffer, buflen);
409}
410
411FPDF_EXPORT unsigned long FPDF_CALLCONV
412FPDF_GetPageLabel(FPDF_DOCUMENT document,
413                  int page_index,
414                  void* buffer,
415                  unsigned long buflen) {
416  if (page_index < 0)
417    return 0;
418
419  // CPDF_PageLabel can deal with NULL |document|.
420  CPDF_PageLabel label(CPDFDocumentFromFPDFDocument(document));
421  Optional<WideString> str = label.GetLabel(page_index);
422  return str.has_value()
423             ? Utf16EncodeMaybeCopyAndReturnLength(str.value(), buffer, buflen)
424             : 0;
425}
426