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