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 <algorithm>
10 #include <map>
11 #include <memory>
12 #include <utility>
13 #include <vector>
14 
15 #include "constants/page_object.h"
16 #include "core/fpdfapi/page/cpdf_page.h"
17 #include "core/fpdfapi/page/cpdf_pageobject.h"
18 #include "core/fpdfapi/parser/cpdf_array.h"
19 #include "core/fpdfapi/parser/cpdf_dictionary.h"
20 #include "core/fpdfapi/parser/cpdf_document.h"
21 #include "core/fpdfapi/parser/cpdf_name.h"
22 #include "core/fpdfapi/parser/cpdf_number.h"
23 #include "core/fpdfapi/parser/cpdf_object.h"
24 #include "core/fpdfapi/parser/cpdf_reference.h"
25 #include "core/fpdfapi/parser/cpdf_stream.h"
26 #include "core/fpdfapi/parser/cpdf_stream_acc.h"
27 #include "core/fpdfapi/parser/cpdf_string.h"
28 #include "core/fpdfapi/render/cpdf_pagerendercache.h"
29 #include "core/fxcrt/fx_safe_types.h"
30 #include "core/fxcrt/retain_ptr.h"
31 #include "core/fxcrt/unowned_ptr.h"
32 #include "fpdfsdk/cpdfsdk_helpers.h"
33 #include "public/cpp/fpdf_scopers.h"
34 
35 namespace {
36 
37 // Struct that stores sub page origin and scale information.  When importing
38 // more than one pages onto the same page, most likely the pages will need to be
39 // scaled down, and scale is in range of (0, 1) exclusive.
40 struct NupPageSettings {
41   CFX_PointF subPageStartPoint;
42   float scale;
43 };
44 
45 // Calculates the N-up parameters.  When importing multiple pages into one page.
46 // The space of output page is evenly divided along the X axis and Y axis based
47 // on the input |nPagesOnXAxis| and |nPagesOnYAxis|.
48 class NupState {
49  public:
50   NupState(const CFX_SizeF& pagesize,
51            size_t nPagesOnXAxis,
52            size_t nPagesOnYAxis);
53 
54   // Calculate sub page origin and scale with the source page of |pagesize| and
55   // new page of |m_subPageSize|.
56   NupPageSettings CalculateNewPagePosition(const CFX_SizeF& pagesize);
57 
58  private:
59   // Helper function to get the |iSubX|, |iSubY| pair based on |m_subPageIndex|.
60   // The space of output page is evenly divided into slots along x and y axis.
61   // |iSubX| and |iSubY| are 0-based indices that indicate which allocation
62   // slot to use.
63   std::pair<size_t, size_t> ConvertPageOrder() const;
64 
65   // Given the |iSubX| and |iSubY| subpage position within a page, and a source
66   // page with dimensions of |pagesize|, calculate the sub page's origin and
67   // scale.
68   NupPageSettings CalculatePageEdit(size_t iSubX,
69                                     size_t iSubY,
70                                     const CFX_SizeF& pagesize) const;
71 
72   const CFX_SizeF m_destPageSize;
73   const size_t m_nPagesOnXAxis;
74   const size_t m_nPagesOnYAxis;
75   const size_t m_nPagesPerSheet;
76   CFX_SizeF m_subPageSize;
77 
78   // A 0-based index, in range of [0, m_nPagesPerSheet - 1).
79   size_t m_subPageIndex = 0;
80 };
81 
NupState(const CFX_SizeF & pagesize,size_t nPagesOnXAxis,size_t nPagesOnYAxis)82 NupState::NupState(const CFX_SizeF& pagesize,
83                    size_t nPagesOnXAxis,
84                    size_t nPagesOnYAxis)
85     : m_destPageSize(pagesize),
86       m_nPagesOnXAxis(nPagesOnXAxis),
87       m_nPagesOnYAxis(nPagesOnYAxis),
88       m_nPagesPerSheet(nPagesOnXAxis * nPagesOnYAxis) {
89   ASSERT(m_nPagesOnXAxis > 0);
90   ASSERT(m_nPagesOnYAxis > 0);
91   ASSERT(m_destPageSize.width > 0);
92   ASSERT(m_destPageSize.height > 0);
93 
94   m_subPageSize.width = m_destPageSize.width / m_nPagesOnXAxis;
95   m_subPageSize.height = m_destPageSize.height / m_nPagesOnYAxis;
96 }
97 
ConvertPageOrder() const98 std::pair<size_t, size_t> NupState::ConvertPageOrder() const {
99   size_t iSubX = m_subPageIndex % m_nPagesOnXAxis;
100   size_t iSubY = m_subPageIndex / m_nPagesOnXAxis;
101 
102   // Y Axis, pages start from the top of the output page.
103   iSubY = m_nPagesOnYAxis - iSubY - 1;
104 
105   return {iSubX, iSubY};
106 }
107 
CalculatePageEdit(size_t iSubX,size_t iSubY,const CFX_SizeF & pagesize) const108 NupPageSettings NupState::CalculatePageEdit(size_t iSubX,
109                                             size_t iSubY,
110                                             const CFX_SizeF& pagesize) const {
111   NupPageSettings settings;
112   settings.subPageStartPoint.x = iSubX * m_subPageSize.width;
113   settings.subPageStartPoint.y = iSubY * m_subPageSize.height;
114 
115   const float xScale = m_subPageSize.width / pagesize.width;
116   const float yScale = m_subPageSize.height / pagesize.height;
117   settings.scale = std::min(xScale, yScale);
118 
119   float subWidth = pagesize.width * settings.scale;
120   float subHeight = pagesize.height * settings.scale;
121   if (xScale > yScale)
122     settings.subPageStartPoint.x += (m_subPageSize.width - subWidth) / 2;
123   else
124     settings.subPageStartPoint.y += (m_subPageSize.height - subHeight) / 2;
125   return settings;
126 }
127 
CalculateNewPagePosition(const CFX_SizeF & pagesize)128 NupPageSettings NupState::CalculateNewPagePosition(const CFX_SizeF& pagesize) {
129   if (m_subPageIndex >= m_nPagesPerSheet)
130     m_subPageIndex = 0;
131 
132   size_t iSubX;
133   size_t iSubY;
134   std::tie(iSubX, iSubY) = ConvertPageOrder();
135   ++m_subPageIndex;
136   return CalculatePageEdit(iSubX, iSubY, pagesize);
137 }
138 
PageDictGetInheritableTag(const CPDF_Dictionary * pDict,const ByteString & bsSrcTag)139 const CPDF_Object* PageDictGetInheritableTag(const CPDF_Dictionary* pDict,
140                                              const ByteString& bsSrcTag) {
141   if (!pDict || bsSrcTag.IsEmpty())
142     return nullptr;
143   if (!pDict->KeyExist(pdfium::page_object::kParent) ||
144       !pDict->KeyExist(pdfium::page_object::kType)) {
145     return nullptr;
146   }
147 
148   const CPDF_Object* pType =
149       pDict->GetObjectFor(pdfium::page_object::kType)->GetDirect();
150   if (!ToName(pType))
151     return nullptr;
152   if (pType->GetString().Compare("Page"))
153     return nullptr;
154 
155   const CPDF_Dictionary* pp = ToDictionary(
156       pDict->GetObjectFor(pdfium::page_object::kParent)->GetDirect());
157   if (!pp)
158     return nullptr;
159 
160   if (pDict->KeyExist(bsSrcTag))
161     return pDict->GetObjectFor(bsSrcTag);
162 
163   while (pp) {
164     if (pp->KeyExist(bsSrcTag))
165       return pp->GetObjectFor(bsSrcTag);
166     if (!pp->KeyExist(pdfium::page_object::kParent))
167       break;
168     pp = ToDictionary(
169         pp->GetObjectFor(pdfium::page_object::kParent)->GetDirect());
170   }
171   return nullptr;
172 }
173 
GetMediaBox(const CPDF_Dictionary * pPageDict)174 CFX_FloatRect GetMediaBox(const CPDF_Dictionary* pPageDict) {
175   const CPDF_Object* pMediaBox =
176       PageDictGetInheritableTag(pPageDict, pdfium::page_object::kMediaBox);
177   const CPDF_Array* pArray = ToArray(pMediaBox->GetDirect());
178   if (!pArray)
179     return CFX_FloatRect();
180   return pArray->GetRect();
181 }
182 
GetCropBox(const CPDF_Dictionary * pPageDict)183 CFX_FloatRect GetCropBox(const CPDF_Dictionary* pPageDict) {
184   if (pPageDict->KeyExist(pdfium::page_object::kCropBox))
185     return pPageDict->GetRectFor(pdfium::page_object::kCropBox);
186   return GetMediaBox(pPageDict);
187 }
188 
CopyInheritable(CPDF_Dictionary * pDestPageDict,const CPDF_Dictionary * pSrcPageDict,const ByteString & key)189 bool CopyInheritable(CPDF_Dictionary* pDestPageDict,
190                      const CPDF_Dictionary* pSrcPageDict,
191                      const ByteString& key) {
192   if (pDestPageDict->KeyExist(key))
193     return true;
194 
195   const CPDF_Object* pInheritable =
196       PageDictGetInheritableTag(pSrcPageDict, key);
197   if (!pInheritable)
198     return false;
199 
200   pDestPageDict->SetFor(key, pInheritable->Clone());
201   return true;
202 }
203 
ParsePageRangeString(const ByteString & bsPageRange,uint32_t nCount,std::vector<uint32_t> * pageArray)204 bool ParsePageRangeString(const ByteString& bsPageRange,
205                           uint32_t nCount,
206                           std::vector<uint32_t>* pageArray) {
207   ByteString bsStrippedPageRange = bsPageRange;
208   bsStrippedPageRange.Remove(' ');
209   size_t nLength = bsStrippedPageRange.GetLength();
210   if (nLength == 0)
211     return true;
212 
213   static const ByteString cbCompareString("0123456789-,");
214   for (size_t i = 0; i < nLength; ++i) {
215     if (!cbCompareString.Contains(bsStrippedPageRange[i]))
216       return false;
217   }
218 
219   ByteString cbMidRange;
220   size_t nStringFrom = 0;
221   size_t nStringTo = 0;
222   while (nStringTo < nLength) {
223     nStringTo = bsStrippedPageRange.Find(',', nStringFrom).value_or(nLength);
224     cbMidRange =
225         bsStrippedPageRange.Substr(nStringFrom, nStringTo - nStringFrom);
226     Optional<size_t> nDashPosition = cbMidRange.Find('-');
227     if (nDashPosition) {
228       size_t nMid = nDashPosition.value();
229       uint32_t nStartPageNum = pdfium::base::checked_cast<uint32_t>(
230           atoi(cbMidRange.First(nMid).c_str()));
231       if (nStartPageNum == 0)
232         return false;
233 
234       ++nMid;
235       size_t nEnd = cbMidRange.GetLength() - nMid;
236       if (nEnd == 0)
237         return false;
238 
239       uint32_t nEndPageNum = pdfium::base::checked_cast<uint32_t>(
240           atoi(cbMidRange.Substr(nMid, nEnd).c_str()));
241       if (nStartPageNum < 0 || nStartPageNum > nEndPageNum ||
242           nEndPageNum > nCount) {
243         return false;
244       }
245       for (uint32_t i = nStartPageNum; i <= nEndPageNum; ++i) {
246         pageArray->push_back(i);
247       }
248     } else {
249       uint32_t nPageNum =
250           pdfium::base::checked_cast<uint32_t>(atoi(cbMidRange.c_str()));
251       if (nPageNum <= 0 || nPageNum > nCount)
252         return false;
253       pageArray->push_back(nPageNum);
254     }
255     nStringFrom = nStringTo + 1;
256   }
257   return true;
258 }
259 
GetPageNumbers(const CPDF_Document & doc,const ByteString & bsPageRange)260 std::vector<uint32_t> GetPageNumbers(const CPDF_Document& doc,
261                                      const ByteString& bsPageRange) {
262   std::vector<uint32_t> page_numbers;
263   uint32_t nCount = doc.GetPageCount();
264   if (bsPageRange.IsEmpty()) {
265     for (uint32_t i = 1; i <= nCount; ++i)
266       page_numbers.push_back(i);
267   } else {
268     if (!ParsePageRangeString(bsPageRange, nCount, &page_numbers))
269       page_numbers.clear();
270   }
271   return page_numbers;
272 }
273 
274 class CPDF_PageOrganizer {
275  protected:
276   CPDF_PageOrganizer(CPDF_Document* pDestDoc, CPDF_Document* pSrcDoc);
277   ~CPDF_PageOrganizer();
278 
279   // Must be called after construction before doing anything else.
280   bool Init();
281 
282   bool UpdateReference(CPDF_Object* pObj);
283 
dest()284   CPDF_Document* dest() { return m_pDestDoc.Get(); }
dest() const285   const CPDF_Document* dest() const { return m_pDestDoc.Get(); }
286 
src()287   CPDF_Document* src() { return m_pSrcDoc.Get(); }
src() const288   const CPDF_Document* src() const { return m_pSrcDoc.Get(); }
289 
AddObjectMapping(uint32_t dwOldPageObj,uint32_t dwNewPageObj)290   void AddObjectMapping(uint32_t dwOldPageObj, uint32_t dwNewPageObj) {
291     m_ObjectNumberMap[dwOldPageObj] = dwNewPageObj;
292   }
293 
ClearObjectNumberMap()294   void ClearObjectNumberMap() { m_ObjectNumberMap.clear(); }
295 
296  private:
297   uint32_t GetNewObjId(CPDF_Reference* pRef);
298 
299   UnownedPtr<CPDF_Document> const m_pDestDoc;
300   UnownedPtr<CPDF_Document> const m_pSrcDoc;
301 
302   // Mapping of source object number to destination object number.
303   std::map<uint32_t, uint32_t> m_ObjectNumberMap;
304 };
305 
CPDF_PageOrganizer(CPDF_Document * pDestDoc,CPDF_Document * pSrcDoc)306 CPDF_PageOrganizer::CPDF_PageOrganizer(CPDF_Document* pDestDoc,
307                                        CPDF_Document* pSrcDoc)
308     : m_pDestDoc(pDestDoc), m_pSrcDoc(pSrcDoc) {}
309 
310 CPDF_PageOrganizer::~CPDF_PageOrganizer() = default;
311 
Init()312 bool CPDF_PageOrganizer::Init() {
313   ASSERT(m_pDestDoc);
314   ASSERT(m_pSrcDoc);
315 
316   CPDF_Dictionary* pNewRoot = dest()->GetRoot();
317   if (!pNewRoot)
318     return false;
319 
320   CPDF_Dictionary* pDocInfoDict = dest()->GetInfo();
321   if (!pDocInfoDict)
322     return false;
323 
324   pDocInfoDict->SetNewFor<CPDF_String>("Producer", "PDFium", false);
325 
326   ByteString cbRootType = pNewRoot->GetStringFor("Type", ByteString());
327   if (cbRootType.IsEmpty())
328     pNewRoot->SetNewFor<CPDF_Name>("Type", "Catalog");
329 
330   CPDF_Object* pElement = pNewRoot->GetObjectFor("Pages");
331   CPDF_Dictionary* pNewPages =
332       pElement ? ToDictionary(pElement->GetDirect()) : nullptr;
333   if (!pNewPages) {
334     pNewPages = dest()->NewIndirect<CPDF_Dictionary>();
335     pNewRoot->SetNewFor<CPDF_Reference>("Pages", dest(),
336                                         pNewPages->GetObjNum());
337   }
338   ByteString cbPageType = pNewPages->GetStringFor("Type", ByteString());
339   if (cbPageType.IsEmpty())
340     pNewPages->SetNewFor<CPDF_Name>("Type", "Pages");
341 
342   if (!pNewPages->GetArrayFor("Kids")) {
343     auto* pNewArray = dest()->NewIndirect<CPDF_Array>();
344     pNewPages->SetNewFor<CPDF_Number>("Count", 0);
345     pNewPages->SetNewFor<CPDF_Reference>("Kids", dest(),
346                                          pNewArray->GetObjNum());
347   }
348   return true;
349 }
350 
UpdateReference(CPDF_Object * pObj)351 bool CPDF_PageOrganizer::UpdateReference(CPDF_Object* pObj) {
352   switch (pObj->GetType()) {
353     case CPDF_Object::kReference: {
354       CPDF_Reference* pReference = pObj->AsReference();
355       uint32_t newobjnum = GetNewObjId(pReference);
356       if (newobjnum == 0)
357         return false;
358       pReference->SetRef(dest(), newobjnum);
359       return true;
360     }
361     case CPDF_Object::kDictionary: {
362       CPDF_Dictionary* pDict = pObj->AsDictionary();
363       std::vector<ByteString> bad_keys;
364       {
365         CPDF_DictionaryLocker locker(pDict);
366         for (const auto& it : locker) {
367           const ByteString& key = it.first;
368           if (key == "Parent" || key == "Prev" || key == "First")
369             continue;
370           CPDF_Object* pNextObj = it.second.Get();
371           if (!pNextObj)
372             return false;
373           if (!UpdateReference(pNextObj))
374             bad_keys.push_back(key);
375         }
376       }
377       for (const auto& key : bad_keys)
378         pDict->RemoveFor(key);
379       return true;
380     }
381     case CPDF_Object::kArray: {
382       CPDF_Array* pArray = pObj->AsArray();
383       for (size_t i = 0; i < pArray->size(); ++i) {
384         CPDF_Object* pNextObj = pArray->GetObjectAt(i);
385         if (!pNextObj || !UpdateReference(pNextObj))
386           return false;
387       }
388       return true;
389     }
390     case CPDF_Object::kStream: {
391       CPDF_Stream* pStream = pObj->AsStream();
392       CPDF_Dictionary* pDict = pStream->GetDict();
393       return pDict && UpdateReference(pDict);
394     }
395     default:
396       return true;
397   }
398 }
399 
GetNewObjId(CPDF_Reference * pRef)400 uint32_t CPDF_PageOrganizer::GetNewObjId(CPDF_Reference* pRef) {
401   if (!pRef)
402     return 0;
403 
404   uint32_t dwObjnum = pRef->GetRefObjNum();
405   uint32_t dwNewObjNum = 0;
406   const auto it = m_ObjectNumberMap.find(dwObjnum);
407   if (it != m_ObjectNumberMap.end())
408     dwNewObjNum = it->second;
409   if (dwNewObjNum)
410     return dwNewObjNum;
411 
412   CPDF_Object* pDirect = pRef->GetDirect();
413   if (!pDirect)
414     return 0;
415 
416   RetainPtr<CPDF_Object> pClone = pDirect->Clone();
417   if (CPDF_Dictionary* pDictClone = pClone->AsDictionary()) {
418     if (pDictClone->KeyExist("Type")) {
419       ByteString strType = pDictClone->GetStringFor("Type");
420       if (!FXSYS_stricmp(strType.c_str(), "Pages"))
421         return 4;
422       if (!FXSYS_stricmp(strType.c_str(), "Page"))
423         return 0;
424     }
425   }
426   CPDF_Object* pUnownedClone = dest()->AddIndirectObject(std::move(pClone));
427   dwNewObjNum = pUnownedClone->GetObjNum();
428   AddObjectMapping(dwObjnum, dwNewObjNum);
429   if (!UpdateReference(pUnownedClone))
430     return 0;
431 
432   return dwNewObjNum;
433 }
434 
435 // Copies pages from a source document into a destination document.
436 // This class is intended to be used once via ExportPage() and then destroyed.
437 class CPDF_PageExporter final : public CPDF_PageOrganizer {
438  public:
439   CPDF_PageExporter(CPDF_Document* pDestDoc, CPDF_Document* pSrcDoc);
440   ~CPDF_PageExporter();
441 
442   // For the pages from the source document with |pageNums| as their page
443   // numbers, insert them into the destination document at page |nIndex|.
444   // |pageNums| is 1-based.
445   // |nIndex| is 0-based.
446   bool ExportPage(const std::vector<uint32_t>& pageNums, int nIndex);
447 };
448 
CPDF_PageExporter(CPDF_Document * pDestDoc,CPDF_Document * pSrcDoc)449 CPDF_PageExporter::CPDF_PageExporter(CPDF_Document* pDestDoc,
450                                      CPDF_Document* pSrcDoc)
451     : CPDF_PageOrganizer(pDestDoc, pSrcDoc) {}
452 
453 CPDF_PageExporter::~CPDF_PageExporter() = default;
454 
ExportPage(const std::vector<uint32_t> & pageNums,int nIndex)455 bool CPDF_PageExporter::ExportPage(const std::vector<uint32_t>& pageNums,
456                                    int nIndex) {
457   if (!Init())
458     return false;
459 
460   int curpage = nIndex;
461   for (size_t i = 0; i < pageNums.size(); ++i) {
462     CPDF_Dictionary* pDestPageDict = dest()->CreateNewPage(curpage);
463     auto* pSrcPageDict = src()->GetPageDictionary(pageNums[i] - 1);
464     if (!pSrcPageDict || !pDestPageDict)
465       return false;
466 
467     // Clone the page dictionary
468     CPDF_DictionaryLocker locker(pSrcPageDict);
469     for (const auto& it : locker) {
470       const ByteString& cbSrcKeyStr = it.first;
471       if (cbSrcKeyStr == pdfium::page_object::kType ||
472           cbSrcKeyStr == pdfium::page_object::kParent) {
473         continue;
474       }
475 
476       CPDF_Object* pObj = it.second.Get();
477       pDestPageDict->SetFor(cbSrcKeyStr, pObj->Clone());
478     }
479 
480     // inheritable item
481     // Even though some entries are required by the PDF spec, there exist
482     // PDFs that omit them. Set some defaults in this case.
483     // 1 MediaBox - required
484     if (!CopyInheritable(pDestPageDict, pSrcPageDict,
485                          pdfium::page_object::kMediaBox)) {
486       // Search for "CropBox" in the source page dictionary.
487       // If it does not exist, use the default letter size.
488       const CPDF_Object* pInheritable = PageDictGetInheritableTag(
489           pSrcPageDict, pdfium::page_object::kCropBox);
490       if (pInheritable) {
491         pDestPageDict->SetFor(pdfium::page_object::kMediaBox,
492                               pInheritable->Clone());
493       } else {
494         // Make the default size letter size (8.5"x11")
495         static const CFX_FloatRect kDefaultLetterRect(0, 0, 612, 792);
496         pDestPageDict->SetRectFor(pdfium::page_object::kMediaBox,
497                                   kDefaultLetterRect);
498       }
499     }
500 
501     // 2 Resources - required
502     if (!CopyInheritable(pDestPageDict, pSrcPageDict,
503                          pdfium::page_object::kResources)) {
504       // Use a default empty resources if it does not exist.
505       pDestPageDict->SetNewFor<CPDF_Dictionary>(
506           pdfium::page_object::kResources);
507     }
508 
509     // 3 CropBox - optional
510     CopyInheritable(pDestPageDict, pSrcPageDict, pdfium::page_object::kCropBox);
511     // 4 Rotate - optional
512     CopyInheritable(pDestPageDict, pSrcPageDict, pdfium::page_object::kRotate);
513 
514     // Update the reference
515     uint32_t dwOldPageObj = pSrcPageDict->GetObjNum();
516     uint32_t dwNewPageObj = pDestPageDict->GetObjNum();
517     AddObjectMapping(dwOldPageObj, dwNewPageObj);
518     UpdateReference(pDestPageDict);
519     ++curpage;
520   }
521 
522   return true;
523 }
524 
525 // Copies pages from a source document into a destination document. Creates 1
526 // page in the destination document for every N source pages. This class is
527 // intended to be used once via ExportNPagesToOne() and then destroyed.
528 class CPDF_NPageToOneExporter final : public CPDF_PageOrganizer {
529  public:
530   CPDF_NPageToOneExporter(CPDF_Document* pDestDoc, CPDF_Document* pSrcDoc);
531   ~CPDF_NPageToOneExporter();
532 
533   // For the pages from the source document with |pageNums| as their page
534   // numbers, insert them into the destination document, starting at page 0.
535   // |pageNums| is 1-based.
536   // |destPageSize| is the destination document page dimensions, measured in
537   // PDF "user space" units.
538   // |nPagesOnXAxis| and |nPagesOnXAxis| together defines how many source
539   // pages fit on one destination page.
540   bool ExportNPagesToOne(const std::vector<uint32_t>& pageNums,
541                          const CFX_SizeF& destPageSize,
542                          size_t nPagesOnXAxis,
543                          size_t nPagesOnYAxis);
544 
545  private:
546   // Map page object number to XObject object name.
547   using PageXObjectMap = std::map<uint32_t, ByteString>;
548 
549   // Creates an XObject from |pSrcPageDict|, or find an existing XObject that
550   // represents |pSrcPageDict|. The transformation matrix is specified in
551   // |settings|.
552   // Returns the XObject reference surrounded by the transformation matrix.
553   ByteString AddSubPage(const CPDF_Dictionary* pSrcPageDict,
554                         const NupPageSettings& settings);
555 
556   // Creates an XObject from |pSrcPageDict|. Updates mapping as needed.
557   // Returns the name of the newly created XObject.
558   ByteString MakeXObjectFromPage(const CPDF_Dictionary* pSrcPageDict);
559 
560   // Adds |bsContent| as the Contents key in |pDestPageDict|.
561   // Adds the objects in |m_XObjectNameToNumberMap| to the XObject dictionary in
562   // |pDestPageDict|'s Resources dictionary.
563   void FinishPage(CPDF_Dictionary* pDestPageDict, const ByteString& bsContent);
564 
565   // Counter for giving new XObjects unique names.
566   uint32_t m_nObjectNumber = 0;
567 
568   // Keeps track of created XObjects in the current page.
569   // Map XObject's object name to it's object number.
570   std::map<ByteString, uint32_t> m_XObjectNameToNumberMap;
571 
572   // Mapping of source page object number and XObject name of the entire doc.
573   // If there are multiple source pages that reference the same object number,
574   // they can also share the same created XObject.
575   PageXObjectMap m_SrcPageXObjectMap;
576 };
577 
CPDF_NPageToOneExporter(CPDF_Document * pDestDoc,CPDF_Document * pSrcDoc)578 CPDF_NPageToOneExporter::CPDF_NPageToOneExporter(CPDF_Document* pDestDoc,
579                                                  CPDF_Document* pSrcDoc)
580     : CPDF_PageOrganizer(pDestDoc, pSrcDoc) {}
581 
582 CPDF_NPageToOneExporter::~CPDF_NPageToOneExporter() = default;
583 
ExportNPagesToOne(const std::vector<uint32_t> & pageNums,const CFX_SizeF & destPageSize,size_t nPagesOnXAxis,size_t nPagesOnYAxis)584 bool CPDF_NPageToOneExporter::ExportNPagesToOne(
585     const std::vector<uint32_t>& pageNums,
586     const CFX_SizeF& destPageSize,
587     size_t nPagesOnXAxis,
588     size_t nPagesOnYAxis) {
589   if (!Init())
590     return false;
591 
592   FX_SAFE_SIZE_T nSafePagesPerSheet = nPagesOnXAxis;
593   nSafePagesPerSheet *= nPagesOnYAxis;
594   if (!nSafePagesPerSheet.IsValid())
595     return false;
596 
597   ClearObjectNumberMap();
598   m_SrcPageXObjectMap.clear();
599   size_t nPagesPerSheet = nSafePagesPerSheet.ValueOrDie();
600   NupState nupState(destPageSize, nPagesOnXAxis, nPagesOnYAxis);
601 
602   size_t curpage = 0;
603   const CFX_FloatRect destPageRect(0, 0, destPageSize.width,
604                                    destPageSize.height);
605   for (size_t iOuterPage = 0; iOuterPage < pageNums.size();
606        iOuterPage += nPagesPerSheet) {
607     m_XObjectNameToNumberMap.clear();
608 
609     // Create a new page
610     CPDF_Dictionary* pDestPageDict = dest()->CreateNewPage(curpage);
611     if (!pDestPageDict)
612       return false;
613 
614     pDestPageDict->SetRectFor(pdfium::page_object::kMediaBox, destPageRect);
615     ByteString bsContent;
616     size_t iInnerPageMax =
617         std::min(iOuterPage + nPagesPerSheet, pageNums.size());
618     for (size_t i = iOuterPage; i < iInnerPageMax; ++i) {
619       auto* pSrcPageDict = src()->GetPageDictionary(pageNums[i] - 1);
620       if (!pSrcPageDict)
621         return false;
622 
623       auto pSrcPage = pdfium::MakeRetain<CPDF_Page>(src(), pSrcPageDict);
624       pSrcPage->SetRenderCache(
625           std::make_unique<CPDF_PageRenderCache>(pSrcPage.Get()));
626       NupPageSettings settings =
627           nupState.CalculateNewPagePosition(pSrcPage->GetPageSize());
628       bsContent += AddSubPage(pSrcPageDict, settings);
629     }
630 
631     FinishPage(pDestPageDict, bsContent);
632     ++curpage;
633   }
634 
635   return true;
636 }
637 
AddSubPage(const CPDF_Dictionary * pSrcPageDict,const NupPageSettings & settings)638 ByteString CPDF_NPageToOneExporter::AddSubPage(
639     const CPDF_Dictionary* pSrcPageDict,
640     const NupPageSettings& settings) {
641   uint32_t dwSrcPageObjnum = pSrcPageDict->GetObjNum();
642   const auto it = m_SrcPageXObjectMap.find(dwSrcPageObjnum);
643   ByteString bsXObjectName = it != m_SrcPageXObjectMap.end()
644                                  ? it->second
645                                  : MakeXObjectFromPage(pSrcPageDict);
646 
647   CFX_Matrix matrix;
648   matrix.Scale(settings.scale, settings.scale);
649   matrix.Translate(settings.subPageStartPoint.x, settings.subPageStartPoint.y);
650 
651   std::ostringstream contentStream;
652   contentStream << "q\n"
653                 << matrix.a << " " << matrix.b << " " << matrix.c << " "
654                 << matrix.d << " " << matrix.e << " " << matrix.f << " cm\n"
655                 << "/" << bsXObjectName << " Do Q\n";
656   return ByteString(contentStream);
657 }
658 
MakeXObjectFromPage(const CPDF_Dictionary * pSrcPageDict)659 ByteString CPDF_NPageToOneExporter::MakeXObjectFromPage(
660     const CPDF_Dictionary* pSrcPageDict) {
661   ASSERT(pSrcPageDict);
662 
663   const CPDF_Object* pSrcContentObj =
664       pSrcPageDict->GetDirectObjectFor(pdfium::page_object::kContents);
665 
666   CPDF_Stream* pNewXObject = dest()->NewIndirect<CPDF_Stream>(
667       nullptr, 0, dest()->New<CPDF_Dictionary>());
668   CPDF_Dictionary* pNewXObjectDict = pNewXObject->GetDict();
669   static const char kResourceString[] = "Resources";
670   if (!CopyInheritable(pNewXObjectDict, pSrcPageDict, kResourceString)) {
671     // Use a default empty resources if it does not exist.
672     pNewXObjectDict->SetNewFor<CPDF_Dictionary>(kResourceString);
673   }
674   uint32_t dwSrcPageObj = pSrcPageDict->GetObjNum();
675   uint32_t dwNewXobjectObj = pNewXObjectDict->GetObjNum();
676   AddObjectMapping(dwSrcPageObj, dwNewXobjectObj);
677   UpdateReference(pNewXObjectDict);
678 
679   pNewXObjectDict->SetNewFor<CPDF_Name>("Type", "XObject");
680   pNewXObjectDict->SetNewFor<CPDF_Name>("Subtype", "Form");
681   pNewXObjectDict->SetNewFor<CPDF_Number>("FormType", 1);
682   pNewXObjectDict->SetRectFor("BBox", GetCropBox(pSrcPageDict));
683   // TODO(xlou): add matrix field to pNewXObjectDict.
684 
685   if (pSrcContentObj) {
686     ByteString bsSrcContentStream;
687     const CPDF_Array* pSrcContentArray = ToArray(pSrcContentObj);
688     if (pSrcContentArray) {
689       for (size_t i = 0; i < pSrcContentArray->size(); ++i) {
690         const CPDF_Stream* pStream = pSrcContentArray->GetStreamAt(i);
691         auto pAcc = pdfium::MakeRetain<CPDF_StreamAcc>(pStream);
692         pAcc->LoadAllDataFiltered();
693         bsSrcContentStream += ByteString(pAcc->GetSpan());
694         bsSrcContentStream += "\n";
695       }
696     } else {
697       const CPDF_Stream* pStream = pSrcContentObj->AsStream();
698       auto pAcc = pdfium::MakeRetain<CPDF_StreamAcc>(pStream);
699       pAcc->LoadAllDataFiltered();
700       bsSrcContentStream = ByteString(pAcc->GetSpan());
701     }
702     pNewXObject->SetDataAndRemoveFilter(bsSrcContentStream.raw_span());
703   }
704 
705   // TODO(xlou): A better name schema to avoid possible object name collision.
706   ByteString bsXObjectName = ByteString::Format("X%d", ++m_nObjectNumber);
707   m_XObjectNameToNumberMap[bsXObjectName] = pNewXObject->GetObjNum();
708   m_SrcPageXObjectMap[pSrcPageDict->GetObjNum()] = bsXObjectName;
709   return bsXObjectName;
710 }
711 
FinishPage(CPDF_Dictionary * pDestPageDict,const ByteString & bsContent)712 void CPDF_NPageToOneExporter::FinishPage(CPDF_Dictionary* pDestPageDict,
713                                          const ByteString& bsContent) {
714   ASSERT(pDestPageDict);
715 
716   CPDF_Dictionary* pRes =
717       pDestPageDict->GetDictFor(pdfium::page_object::kResources);
718   if (!pRes) {
719     pRes = pDestPageDict->SetNewFor<CPDF_Dictionary>(
720         pdfium::page_object::kResources);
721   }
722 
723   CPDF_Dictionary* pPageXObject = pRes->GetDictFor("XObject");
724   if (!pPageXObject)
725     pPageXObject = pRes->SetNewFor<CPDF_Dictionary>("XObject");
726 
727   for (auto& it : m_XObjectNameToNumberMap)
728     pPageXObject->SetNewFor<CPDF_Reference>(it.first, dest(), it.second);
729 
730   auto pDict = dest()->New<CPDF_Dictionary>();
731   CPDF_Stream* pStream =
732       dest()->NewIndirect<CPDF_Stream>(nullptr, 0, std::move(pDict));
733   pStream->SetData(bsContent.raw_span());
734   pDestPageDict->SetNewFor<CPDF_Reference>(pdfium::page_object::kContents,
735                                            dest(), pStream->GetObjNum());
736 }
737 
738 }  // namespace
739 
FPDF_ImportPages(FPDF_DOCUMENT dest_doc,FPDF_DOCUMENT src_doc,FPDF_BYTESTRING pagerange,int index)740 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDF_ImportPages(FPDF_DOCUMENT dest_doc,
741                                                      FPDF_DOCUMENT src_doc,
742                                                      FPDF_BYTESTRING pagerange,
743                                                      int index) {
744   CPDF_Document* pDestDoc = CPDFDocumentFromFPDFDocument(dest_doc);
745   if (!dest_doc)
746     return false;
747 
748   CPDF_Document* pSrcDoc = CPDFDocumentFromFPDFDocument(src_doc);
749   if (!pSrcDoc)
750     return false;
751 
752   std::vector<uint32_t> page_numbers = GetPageNumbers(*pSrcDoc, pagerange);
753   if (page_numbers.empty())
754     return false;
755 
756   CPDF_PageExporter exporter(pDestDoc, pSrcDoc);
757   return exporter.ExportPage(page_numbers, index);
758 }
759 
760 FPDF_EXPORT FPDF_DOCUMENT FPDF_CALLCONV
FPDF_ImportNPagesToOne(FPDF_DOCUMENT src_doc,float output_width,float output_height,size_t num_pages_on_x_axis,size_t num_pages_on_y_axis)761 FPDF_ImportNPagesToOne(FPDF_DOCUMENT src_doc,
762                        float output_width,
763                        float output_height,
764                        size_t num_pages_on_x_axis,
765                        size_t num_pages_on_y_axis) {
766   CPDF_Document* pSrcDoc = CPDFDocumentFromFPDFDocument(src_doc);
767   if (!pSrcDoc)
768     return nullptr;
769 
770   if (output_width <= 0 || output_height <= 0 || num_pages_on_x_axis <= 0 ||
771       num_pages_on_y_axis <= 0) {
772     return nullptr;
773   }
774 
775   ScopedFPDFDocument output_doc(FPDF_CreateNewDocument());
776   if (!output_doc)
777     return nullptr;
778 
779   CPDF_Document* pDestDoc = CPDFDocumentFromFPDFDocument(output_doc.get());
780   ASSERT(pDestDoc);
781 
782   std::vector<uint32_t> page_numbers = GetPageNumbers(*pSrcDoc, ByteString());
783   if (page_numbers.empty())
784     return nullptr;
785 
786   if (num_pages_on_x_axis == 1 && num_pages_on_y_axis == 1) {
787     CPDF_PageExporter exporter(pDestDoc, pSrcDoc);
788     if (!exporter.ExportPage(page_numbers, 0))
789       return nullptr;
790     return output_doc.release();
791   }
792 
793   CPDF_NPageToOneExporter exporter(pDestDoc, pSrcDoc);
794   if (!exporter.ExportNPagesToOne(page_numbers,
795                                   CFX_SizeF(output_width, output_height),
796                                   num_pages_on_x_axis, num_pages_on_y_axis)) {
797     return nullptr;
798   }
799   return output_doc.release();
800 }
801 
802 FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
FPDF_CopyViewerPreferences(FPDF_DOCUMENT dest_doc,FPDF_DOCUMENT src_doc)803 FPDF_CopyViewerPreferences(FPDF_DOCUMENT dest_doc, FPDF_DOCUMENT src_doc) {
804   CPDF_Document* pDstDoc = CPDFDocumentFromFPDFDocument(dest_doc);
805   if (!pDstDoc)
806     return false;
807 
808   CPDF_Document* pSrcDoc = CPDFDocumentFromFPDFDocument(src_doc);
809   if (!pSrcDoc)
810     return false;
811 
812   const CPDF_Dictionary* pSrcDict = pSrcDoc->GetRoot();
813   pSrcDict = pSrcDict->GetDictFor("ViewerPreferences");
814   if (!pSrcDict)
815     return false;
816 
817   CPDF_Dictionary* pDstDict = pDstDoc->GetRoot();
818   if (!pDstDict)
819     return false;
820 
821   pDstDict->SetFor("ViewerPreferences", pSrcDict->CloneDirectObject());
822   return true;
823 }
824