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