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 
13 class 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 
CPDF_PageOrganizer()34 CPDF_PageOrganizer::CPDF_PageOrganizer() {}
35 
~CPDF_PageOrganizer()36 CPDF_PageOrganizer::~CPDF_PageOrganizer() {}
37 
PDFDocInit(CPDF_Document * pDestPDFDoc,CPDF_Document * pSrcPDFDoc)38 FX_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 
ExportPage(CPDF_Document * pSrcPDFDoc,CFX_WordArray * nPageNum,CPDF_Document * pDestPDFDoc,int nIndex)89 FX_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 
PageDictGetInheritableTag(CPDF_Dictionary * pDict,CFX_ByteString nSrctag)171 CPDF_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 
UpdateReference(CPDF_Object * pObj,CPDF_Document * pDoc,ObjectNumberMap * pObjNumberMap)202 FX_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 
GetNewObjId(CPDF_Document * pDoc,ObjectNumberMap * pObjNumberMap,CPDF_Reference * pRef)264 FX_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 
ParserPageRangeString(CFX_ByteString rangstring,CFX_WordArray * pageArray,int nCount)308 FPDF_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 
FPDF_ImportPages(FPDF_DOCUMENT dest_doc,FPDF_DOCUMENT src_doc,FPDF_BYTESTRING pagerange,int index)358 DLLEXPORT 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 
FPDF_CopyViewerPreferences(FPDF_DOCUMENT dest_doc,FPDF_DOCUMENT src_doc)386 DLLEXPORT 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