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