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_ppo.h"
8
9#include <memory>
10
11#include "fpdfsdk/include/fsdk_define.h"
12
13class CPDF_PageOrganizer {
14 public:
15  using ObjectNumberMap = std::map<FX_DWORD, FX_DWORD>;
16  CPDF_PageOrganizer();
17  ~CPDF_PageOrganizer();
18
19  FX_BOOL PDFDocInit(CPDF_Document* pDestPDFDoc, CPDF_Document* pSrcPDFDoc);
20  FX_BOOL ExportPage(CPDF_Document* pSrcPDFDoc,
21                     CFX_WordArray* nPageNum,
22                     CPDF_Document* pDestPDFDoc,
23                     int nIndex);
24  CPDF_Object* PageDictGetInheritableTag(CPDF_Dictionary* pDict,
25                                         CFX_ByteString nSrctag);
26  FX_BOOL UpdateReference(CPDF_Object* pObj,
27                          CPDF_Document* pDoc,
28                          ObjectNumberMap* pObjNumberMap);
29  FX_DWORD GetNewObjId(CPDF_Document* pDoc,
30                       ObjectNumberMap* pObjNumberMap,
31                       CPDF_Reference* pRef);
32};
33
34CPDF_PageOrganizer::CPDF_PageOrganizer() {}
35
36CPDF_PageOrganizer::~CPDF_PageOrganizer() {}
37
38FX_BOOL CPDF_PageOrganizer::PDFDocInit(CPDF_Document* pDestPDFDoc,
39                                       CPDF_Document* pSrcPDFDoc) {
40  if (!pDestPDFDoc || !pSrcPDFDoc)
41    return FALSE;
42
43  CPDF_Dictionary* pNewRoot = pDestPDFDoc->GetRoot();
44  if (!pNewRoot)
45    return FALSE;
46
47  // Set the document information
48  CPDF_Dictionary* DInfoDict = pDestPDFDoc->GetInfo();
49  if (!DInfoDict)
50    return FALSE;
51
52  CFX_ByteString producerstr;
53  producerstr.Format("PDFium");
54  DInfoDict->SetAt("Producer", new CPDF_String(producerstr, FALSE));
55
56  // Set type
57  CFX_ByteString cbRootType = pNewRoot->GetString("Type", "");
58  if (cbRootType.Equal("")) {
59    pNewRoot->SetAt("Type", new CPDF_Name("Catalog"));
60  }
61
62  CPDF_Object* pElement = pNewRoot->GetElement("Pages");
63  CPDF_Dictionary* pNewPages =
64      pElement ? ToDictionary(pElement->GetDirect()) : nullptr;
65  if (!pNewPages) {
66    pNewPages = new CPDF_Dictionary;
67    FX_DWORD NewPagesON = pDestPDFDoc->AddIndirectObject(pNewPages);
68    pNewRoot->SetAt("Pages", new CPDF_Reference(pDestPDFDoc, NewPagesON));
69  }
70
71  CFX_ByteString cbPageType = pNewPages->GetString("Type", "");
72  if (cbPageType.Equal("")) {
73    pNewPages->SetAt("Type", new CPDF_Name("Pages"));
74  }
75
76  CPDF_Array* pKeysArray = pNewPages->GetArray("Kids");
77  if (!pKeysArray) {
78    CPDF_Array* pNewKids = new CPDF_Array;
79    FX_DWORD Kidsobjnum = -1;
80    Kidsobjnum = pDestPDFDoc->AddIndirectObject(pNewKids);
81
82    pNewPages->SetAt("Kids", new CPDF_Reference(pDestPDFDoc, Kidsobjnum));
83    pNewPages->SetAt("Count", new CPDF_Number(0));
84  }
85
86  return TRUE;
87}
88
89FX_BOOL CPDF_PageOrganizer::ExportPage(CPDF_Document* pSrcPDFDoc,
90                                       CFX_WordArray* nPageNum,
91                                       CPDF_Document* pDestPDFDoc,
92                                       int nIndex) {
93  int curpage = nIndex;
94
95  std::unique_ptr<ObjectNumberMap> pObjNumberMap(new ObjectNumberMap);
96
97  for (int i = 0; i < nPageNum->GetSize(); ++i) {
98    CPDF_Dictionary* pCurPageDict = pDestPDFDoc->CreateNewPage(curpage);
99    CPDF_Dictionary* pSrcPageDict = pSrcPDFDoc->GetPage(nPageNum->GetAt(i) - 1);
100    if (!pSrcPageDict || !pCurPageDict)
101      return FALSE;
102
103    // Clone the page dictionary
104    for (const auto& it : *pSrcPageDict) {
105      const CFX_ByteString& cbSrcKeyStr = it.first;
106      CPDF_Object* pObj = it.second;
107      if (cbSrcKeyStr.Compare(("Type")) && cbSrcKeyStr.Compare(("Parent"))) {
108        if (pCurPageDict->KeyExist(cbSrcKeyStr))
109          pCurPageDict->RemoveAt(cbSrcKeyStr);
110        pCurPageDict->SetAt(cbSrcKeyStr, pObj->Clone());
111      }
112    }
113
114    // inheritable item
115    CPDF_Object* pInheritable = nullptr;
116    // 1 MediaBox  //required
117    if (!pCurPageDict->KeyExist("MediaBox")) {
118      pInheritable = PageDictGetInheritableTag(pSrcPageDict, "MediaBox");
119      if (!pInheritable) {
120        // Search the "CropBox" from source page dictionary,
121        // if not exists,we take the letter size.
122        pInheritable = PageDictGetInheritableTag(pSrcPageDict, "CropBox");
123        if (pInheritable) {
124          pCurPageDict->SetAt("MediaBox", pInheritable->Clone());
125        } else {
126          // Make the default size to be letter size (8.5'x11')
127          CPDF_Array* pArray = new CPDF_Array;
128          pArray->AddNumber(0);
129          pArray->AddNumber(0);
130          pArray->AddNumber(612);
131          pArray->AddNumber(792);
132          pCurPageDict->SetAt("MediaBox", pArray);
133        }
134      } else {
135        pCurPageDict->SetAt("MediaBox", pInheritable->Clone());
136      }
137    }
138    // 2 Resources //required
139    if (!pCurPageDict->KeyExist("Resources")) {
140      pInheritable = PageDictGetInheritableTag(pSrcPageDict, "Resources");
141      if (!pInheritable)
142        return FALSE;
143      pCurPageDict->SetAt("Resources", pInheritable->Clone());
144    }
145    // 3 CropBox  //Optional
146    if (!pCurPageDict->KeyExist("CropBox")) {
147      pInheritable = PageDictGetInheritableTag(pSrcPageDict, "CropBox");
148      if (pInheritable)
149        pCurPageDict->SetAt("CropBox", pInheritable->Clone());
150    }
151    // 4 Rotate  //Optional
152    if (!pCurPageDict->KeyExist("Rotate")) {
153      pInheritable = PageDictGetInheritableTag(pSrcPageDict, "Rotate");
154      if (pInheritable)
155        pCurPageDict->SetAt("Rotate", pInheritable->Clone());
156    }
157
158    // Update the reference
159    FX_DWORD dwOldPageObj = pSrcPageDict->GetObjNum();
160    FX_DWORD dwNewPageObj = pCurPageDict->GetObjNum();
161
162    (*pObjNumberMap)[dwOldPageObj] = dwNewPageObj;
163
164    UpdateReference(pCurPageDict, pDestPDFDoc, pObjNumberMap.get());
165    ++curpage;
166  }
167
168  return TRUE;
169}
170
171CPDF_Object* CPDF_PageOrganizer::PageDictGetInheritableTag(
172    CPDF_Dictionary* pDict,
173    CFX_ByteString nSrctag) {
174  if (!pDict || nSrctag.IsEmpty())
175    return nullptr;
176  if (!pDict->KeyExist("Parent") || !pDict->KeyExist("Type"))
177    return nullptr;
178
179  CPDF_Object* pType = pDict->GetElement("Type")->GetDirect();
180  if (!ToName(pType))
181    return nullptr;
182  if (pType->GetString().Compare("Page"))
183    return nullptr;
184
185  CPDF_Dictionary* pp = ToDictionary(pDict->GetElement("Parent")->GetDirect());
186  if (!pp)
187    return nullptr;
188
189  if (pDict->KeyExist((const char*)nSrctag))
190    return pDict->GetElement((const char*)nSrctag);
191
192  while (pp) {
193    if (pp->KeyExist((const char*)nSrctag))
194      return pp->GetElement((const char*)nSrctag);
195    if (!pp->KeyExist("Parent"))
196      break;
197    pp = ToDictionary(pp->GetElement("Parent")->GetDirect());
198  }
199  return nullptr;
200}
201
202FX_BOOL CPDF_PageOrganizer::UpdateReference(CPDF_Object* pObj,
203                                            CPDF_Document* pDoc,
204                                            ObjectNumberMap* pObjNumberMap) {
205  switch (pObj->GetType()) {
206    case PDFOBJ_REFERENCE: {
207      CPDF_Reference* pReference = pObj->AsReference();
208      FX_DWORD newobjnum = GetNewObjId(pDoc, pObjNumberMap, pReference);
209      if (newobjnum == 0)
210        return FALSE;
211      pReference->SetRef(pDoc, newobjnum);
212      break;
213    }
214    case PDFOBJ_DICTIONARY: {
215      CPDF_Dictionary* pDict = pObj->AsDictionary();
216      auto it = pDict->begin();
217      while (it != pDict->end()) {
218        const CFX_ByteString& key = it->first;
219        CPDF_Object* pNextObj = it->second;
220        ++it;
221        if (!FXSYS_strcmp(key, "Parent") || !FXSYS_strcmp(key, "Prev") ||
222            !FXSYS_strcmp(key, "First")) {
223          continue;
224        }
225        if (pNextObj) {
226          if (!UpdateReference(pNextObj, pDoc, pObjNumberMap))
227            pDict->RemoveAt(key);
228        } else {
229          return FALSE;
230        }
231      }
232      break;
233    }
234    case PDFOBJ_ARRAY: {
235      CPDF_Array* pArray = pObj->AsArray();
236      FX_DWORD count = pArray->GetCount();
237      for (FX_DWORD i = 0; i < count; ++i) {
238        CPDF_Object* pNextObj = pArray->GetElement(i);
239        if (!pNextObj)
240          return FALSE;
241        if (!UpdateReference(pNextObj, pDoc, pObjNumberMap))
242          return FALSE;
243      }
244      break;
245    }
246    case PDFOBJ_STREAM: {
247      CPDF_Stream* pStream = pObj->AsStream();
248      CPDF_Dictionary* pDict = pStream->GetDict();
249      if (pDict) {
250        if (!UpdateReference(pDict, pDoc, pObjNumberMap))
251          return FALSE;
252      } else {
253        return FALSE;
254      }
255      break;
256    }
257    default:
258      break;
259  }
260
261  return TRUE;
262}
263
264FX_DWORD CPDF_PageOrganizer::GetNewObjId(CPDF_Document* pDoc,
265                                         ObjectNumberMap* pObjNumberMap,
266                                         CPDF_Reference* pRef) {
267  if (!pRef)
268    return 0;
269
270  FX_DWORD dwObjnum = pRef->GetRefObjNum();
271  FX_DWORD dwNewObjNum = 0;
272  const auto it = pObjNumberMap->find(dwObjnum);
273  if (it != pObjNumberMap->end())
274    dwNewObjNum = it->second;
275  if (dwNewObjNum)
276    return dwNewObjNum;
277
278  CPDF_Object* pDirect = pRef->GetDirect();
279  if (!pDirect)
280    return 0;
281
282  CPDF_Object* pClone = pDirect->Clone();
283  if (!pClone)
284    return 0;
285
286  if (CPDF_Dictionary* pDictClone = pClone->AsDictionary()) {
287    if (pDictClone->KeyExist("Type")) {
288      CFX_ByteString strType = pDictClone->GetString("Type");
289      if (!FXSYS_stricmp(strType, "Pages")) {
290        pDictClone->Release();
291        return 4;
292      }
293      if (!FXSYS_stricmp(strType, "Page")) {
294        pDictClone->Release();
295        return 0;
296      }
297    }
298  }
299  dwNewObjNum = pDoc->AddIndirectObject(pClone);
300  (*pObjNumberMap)[dwObjnum] = dwNewObjNum;
301  if (!UpdateReference(pClone, pDoc, pObjNumberMap)) {
302    pClone->Release();
303    return 0;
304  }
305  return dwNewObjNum;
306}
307
308FPDF_BOOL ParserPageRangeString(CFX_ByteString rangstring,
309                                CFX_WordArray* pageArray,
310                                int nCount) {
311  if (rangstring.GetLength() != 0) {
312    rangstring.Remove(' ');
313    int nLength = rangstring.GetLength();
314    CFX_ByteString cbCompareString("0123456789-,");
315    for (int i = 0; i < nLength; ++i) {
316      if (cbCompareString.Find(rangstring[i]) == -1)
317        return FALSE;
318    }
319    CFX_ByteString cbMidRange;
320    int nStringFrom = 0;
321    int nStringTo = 0;
322    while (nStringTo < nLength) {
323      nStringTo = rangstring.Find(',', nStringFrom);
324      if (nStringTo == -1)
325        nStringTo = nLength;
326      cbMidRange = rangstring.Mid(nStringFrom, nStringTo - nStringFrom);
327      int nMid = cbMidRange.Find('-');
328      if (nMid == -1) {
329        long lPageNum = atol(cbMidRange);
330        if (lPageNum <= 0 || lPageNum > nCount)
331          return FALSE;
332        pageArray->Add((FX_WORD)lPageNum);
333      } else {
334        int nStartPageNum = atol(cbMidRange.Mid(0, nMid));
335        if (nStartPageNum == 0)
336          return FALSE;
337
338        ++nMid;
339        int nEnd = cbMidRange.GetLength() - nMid;
340        if (nEnd == 0)
341          return FALSE;
342
343        int nEndPageNum = atol(cbMidRange.Mid(nMid, nEnd));
344        if (nStartPageNum < 0 || nStartPageNum > nEndPageNum ||
345            nEndPageNum > nCount) {
346          return FALSE;
347        }
348        for (int i = nStartPageNum; i <= nEndPageNum; ++i) {
349          pageArray->Add(i);
350        }
351      }
352      nStringFrom = nStringTo + 1;
353    }
354  }
355  return TRUE;
356}
357
358DLLEXPORT FPDF_BOOL STDCALL FPDF_ImportPages(FPDF_DOCUMENT dest_doc,
359                                             FPDF_DOCUMENT src_doc,
360                                             FPDF_BYTESTRING pagerange,
361                                             int index) {
362  CPDF_Document* pDestDoc = CPDFDocumentFromFPDFDocument(dest_doc);
363  if (!dest_doc)
364    return FALSE;
365
366  CPDF_Document* pSrcDoc = CPDFDocumentFromFPDFDocument(src_doc);
367  if (!pSrcDoc)
368    return FALSE;
369
370  CFX_WordArray pageArray;
371  int nCount = pSrcDoc->GetPageCount();
372  if (pagerange) {
373    if (!ParserPageRangeString(pagerange, &pageArray, nCount))
374      return FALSE;
375  } else {
376    for (int i = 1; i <= nCount; ++i) {
377      pageArray.Add(i);
378    }
379  }
380
381  CPDF_PageOrganizer pageOrg;
382  pageOrg.PDFDocInit(pDestDoc, pSrcDoc);
383  return pageOrg.ExportPage(pSrcDoc, &pageArray, pDestDoc, index);
384}
385
386DLLEXPORT FPDF_BOOL STDCALL FPDF_CopyViewerPreferences(FPDF_DOCUMENT dest_doc,
387                                                       FPDF_DOCUMENT src_doc) {
388  CPDF_Document* pDstDoc = CPDFDocumentFromFPDFDocument(dest_doc);
389  if (!pDstDoc)
390    return FALSE;
391
392  CPDF_Document* pSrcDoc = CPDFDocumentFromFPDFDocument(src_doc);
393  if (!pSrcDoc)
394    return FALSE;
395
396  CPDF_Dictionary* pSrcDict = pSrcDoc->GetRoot();
397  pSrcDict = pSrcDict->GetDict("ViewerPreferences");
398  if (!pSrcDict)
399    return FALSE;
400
401  CPDF_Dictionary* pDstDict = pDstDoc->GetRoot();
402  if (!pDstDict)
403    return FALSE;
404
405  pDstDict->SetAt("ViewerPreferences", pSrcDict->Clone(TRUE));
406  return TRUE;
407}
408