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