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