Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
fpdf_ppo.cpp
Go to the documentation of this file.
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/containers/span.h"
42
47
48namespace {
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.
53struct 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|.
61class 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
95NupState::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
111std::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
121NupPageSettings 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
141NupPageSettings 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
152RetainPtr<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
186bool 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
201std::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
212class 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
222 CPDF_Document* dest() { return m_pDestDoc; }
223 const CPDF_Document* dest() const { return m_pDestDoc; }
224
225 CPDF_Document* src() { return m_pSrcDoc; }
226 const CPDF_Document* src() const { return m_pSrcDoc; }
227
228 void AddObjectMapping(uint32_t dwOldPageObj, uint32_t dwNewPageObj) {
229 m_ObjectNumberMap[dwOldPageObj] = dwNewPageObj;
230 }
231
232 void ClearObjectNumberMap() { m_ObjectNumberMap.clear(); }
233
234 private:
235 bool InitDestDoc();
236
237 uint32_t GetNewObjId(CPDF_Reference* pRef);
238
239 UnownedPtr<CPDF_Document> const m_pDestDoc;
240 UnownedPtr<CPDF_Document> const m_pSrcDoc;
241
242 // Mapping of source object number to destination object number.
243 std::map<uint32_t, uint32_t> m_ObjectNumberMap;
244};
245
246CPDF_PageOrganizer::CPDF_PageOrganizer(CPDF_Document* pDestDoc,
247 CPDF_Document* pSrcDoc)
248 : m_pDestDoc(pDestDoc), m_pSrcDoc(pSrcDoc) {}
249
250CPDF_PageOrganizer::~CPDF_PageOrganizer() = default;
251
252bool CPDF_PageOrganizer::Init() {
253 DCHECK(m_pDestDoc);
254 DCHECK(m_pSrcDoc);
255
256 return InitDestDoc();
257}
258
259bool CPDF_PageOrganizer::InitDestDoc() {
260 RetainPtr<CPDF_Dictionary> root = dest()->GetMutableRoot();
261 if (!root) {
262 return false;
263 }
264
265 RetainPtr<CPDF_Dictionary> info = dest()->GetInfo();
266 if (info) {
267 info->SetNewFor<CPDF_String>("Producer", "PDFium", false);
268 }
269
270 if (root->GetByteStringFor("Type", ByteString()).IsEmpty()) {
271 root->SetNewFor<CPDF_Name>("Type", "Catalog");
272 }
273
274 RetainPtr<CPDF_Dictionary> pages;
275 if (RetainPtr<CPDF_Object> current_pages = root->GetMutableObjectFor("Pages");
276 current_pages) {
277 pages = ToDictionary(current_pages->GetMutableDirect());
278 }
279 if (!pages) {
280 pages = dest()->NewIndirect<CPDF_Dictionary>();
281 root->SetNewFor<CPDF_Reference>("Pages", dest(), pages->GetObjNum());
282 }
283 if (pages->GetByteStringFor("Type", ByteString()).IsEmpty()) {
284 pages->SetNewFor<CPDF_Name>("Type", "Pages");
285 }
286
287 if (!pages->GetArrayFor("Kids")) {
288 auto kids_array = dest()->NewIndirect<CPDF_Array>();
289 pages->SetNewFor<CPDF_Number>("Count", 0);
290 pages->SetNewFor<CPDF_Reference>("Kids", dest(), kids_array->GetObjNum());
291 }
292 return true;
293}
294
295bool CPDF_PageOrganizer::UpdateReference(RetainPtr<CPDF_Object> pObj) {
296 switch (pObj->GetType()) {
298 CPDF_Reference* pReference = pObj->AsMutableReference();
299 uint32_t newobjnum = GetNewObjId(pReference);
300 if (newobjnum == 0)
301 return false;
302 pReference->SetRef(dest(), newobjnum);
303 return true;
304 }
306 CPDF_Dictionary* pDict = pObj->AsMutableDictionary();
307 std::vector<ByteString> bad_keys;
308 {
309 CPDF_DictionaryLocker locker(pDict);
310 for (const auto& it : locker) {
311 const ByteString& key = it.first;
312 if (key == "Parent" || key == "Prev" || key == "First")
313 continue;
314 RetainPtr<CPDF_Object> pNextObj = it.second;
315 if (!UpdateReference(pNextObj))
316 bad_keys.push_back(key);
317 }
318 }
319 for (const auto& key : bad_keys)
320 pDict->RemoveFor(key.AsStringView());
321 return true;
322 }
323 case CPDF_Object::kArray: {
324 CPDF_Array* pArray = pObj->AsMutableArray();
325 for (size_t i = 0; i < pArray->size(); ++i) {
326 if (!UpdateReference(pArray->GetMutableObjectAt(i)))
327 return false;
328 }
329 return true;
330 }
332 CPDF_Stream* pStream = pObj->AsMutableStream();
333 RetainPtr<CPDF_Dictionary> pDict = pStream->GetMutableDict();
334 return pDict && UpdateReference(std::move(pDict));
335 }
336 default:
337 return true;
338 }
339}
340
341uint32_t CPDF_PageOrganizer::GetNewObjId(CPDF_Reference* pRef) {
342 if (!pRef)
343 return 0;
344
345 uint32_t dwObjnum = pRef->GetRefObjNum();
346 uint32_t dwNewObjNum = 0;
347 const auto it = m_ObjectNumberMap.find(dwObjnum);
348 if (it != m_ObjectNumberMap.end())
349 dwNewObjNum = it->second;
350 if (dwNewObjNum)
351 return dwNewObjNum;
352
353 RetainPtr<const CPDF_Object> pDirect = pRef->GetDirect();
354 if (!pDirect)
355 return 0;
356
357 RetainPtr<CPDF_Object> pClone = pDirect->Clone();
358 const CPDF_Dictionary* pDictClone = pClone->AsDictionary();
359 if (pDictClone && pDictClone->KeyExist("Type")) {
360 ByteString strType = pDictClone->GetByteStringFor("Type");
361 if (strType.EqualNoCase("Pages"))
362 return 4;
363 if (strType.EqualNoCase("Page"))
364 return 0;
365 }
366
367 dwNewObjNum = dest()->AddIndirectObject(pClone);
368 AddObjectMapping(dwObjnum, dwNewObjNum);
369 if (!UpdateReference(std::move(pClone)))
370 return 0;
371
372 return dwNewObjNum;
373}
374
375// Copies pages from a source document into a destination document.
376// This class is intended to be used once via ExportPage() and then destroyed.
377class CPDF_PageExporter final : public CPDF_PageOrganizer {
378 public:
379 CPDF_PageExporter(CPDF_Document* pDestDoc, CPDF_Document* pSrcDoc);
380 ~CPDF_PageExporter();
381
382 // For the pages from the source document with |pageIndices| as their page
383 // indices, insert them into the destination document at page |nIndex|.
384 // |pageIndices| and |nIndex| are 0-based.
385 bool ExportPage(pdfium::span<const uint32_t> pageIndices, int nIndex);
386};
387
388CPDF_PageExporter::CPDF_PageExporter(CPDF_Document* pDestDoc,
389 CPDF_Document* pSrcDoc)
390 : CPDF_PageOrganizer(pDestDoc, pSrcDoc) {}
391
392CPDF_PageExporter::~CPDF_PageExporter() = default;
393
394bool CPDF_PageExporter::ExportPage(pdfium::span<const uint32_t> pageIndices,
395 int nIndex) {
396 if (!Init())
397 return false;
398
399 int curpage = nIndex;
400 for (uint32_t pageIndex : pageIndices) {
401 RetainPtr<CPDF_Dictionary> pDestPageDict = dest()->CreateNewPage(curpage);
402 RetainPtr<const CPDF_Dictionary> pSrcPageDict =
403 src()->GetPageDictionary(pageIndex);
404 if (!pSrcPageDict || !pDestPageDict)
405 return false;
406
407 // Clone the page dictionary
408 CPDF_DictionaryLocker locker(pSrcPageDict);
409 for (const auto& it : locker) {
410 const ByteString& cbSrcKeyStr = it.first;
411 const RetainPtr<CPDF_Object>& pObj = it.second;
412 if (cbSrcKeyStr == pdfium::page_object::kType ||
413 cbSrcKeyStr == pdfium::page_object::kParent) {
414 continue;
415 }
416 pDestPageDict->SetFor(cbSrcKeyStr, pObj->Clone());
417 }
418
419 // inheritable item
420 // Even though some entries are required by the PDF spec, there exist
421 // PDFs that omit them. Set some defaults in this case.
422 // 1 MediaBox - required
423 if (!CopyInheritable(pDestPageDict, pSrcPageDict,
424 pdfium::page_object::kMediaBox)) {
425 // Search for "CropBox" in the source page dictionary.
426 // If it does not exist, use the default letter size.
427 RetainPtr<const CPDF_Object> pInheritable = PageDictGetInheritableTag(
428 pSrcPageDict, pdfium::page_object::kCropBox);
429 if (pInheritable) {
430 pDestPageDict->SetFor(pdfium::page_object::kMediaBox,
431 pInheritable->Clone());
432 } else {
433 // Make the default size letter size (8.5"x11")
434 static const CFX_FloatRect kDefaultLetterRect(0, 0, 612, 792);
435 pDestPageDict->SetRectFor(pdfium::page_object::kMediaBox,
436 kDefaultLetterRect);
437 }
438 }
439
440 // 2 Resources - required
441 if (!CopyInheritable(pDestPageDict, pSrcPageDict,
442 pdfium::page_object::kResources)) {
443 // Use a default empty resources if it does not exist.
444 pDestPageDict->SetNewFor<CPDF_Dictionary>(
445 pdfium::page_object::kResources);
446 }
447
448 // 3 CropBox - optional
449 CopyInheritable(pDestPageDict, pSrcPageDict, pdfium::page_object::kCropBox);
450 // 4 Rotate - optional
451 CopyInheritable(pDestPageDict, pSrcPageDict, pdfium::page_object::kRotate);
452
453 // Update the reference
454 uint32_t dwOldPageObj = pSrcPageDict->GetObjNum();
455 uint32_t dwNewPageObj = pDestPageDict->GetObjNum();
456 AddObjectMapping(dwOldPageObj, dwNewPageObj);
457 UpdateReference(pDestPageDict);
458 ++curpage;
459 }
460
461 return true;
462}
463
464// Copies pages from a source document into a destination document. Creates 1
465// page in the destination document for every N source pages. This class is
466// intended to be used once via ExportNPagesToOne() and then destroyed.
467class CPDF_NPageToOneExporter final : public CPDF_PageOrganizer {
468 public:
469 CPDF_NPageToOneExporter(CPDF_Document* pDestDoc, CPDF_Document* pSrcDoc);
470 ~CPDF_NPageToOneExporter();
471
472 // For the pages from the source document with |pageIndices| as their page
473 // indices, insert them into the destination document, starting at page index
474 // 0.
475 // |pageIndices| is 0-based.
476 // |destPageSize| is the destination document page dimensions, measured in
477 // PDF "user space" units.
478 // |nPagesOnXAxis| and |nPagesOnXAxis| together defines how many source
479 // pages fit on one destination page.
480 bool ExportNPagesToOne(pdfium::span<const uint32_t> pageIndices,
481 const CFX_SizeF& destPageSize,
482 size_t nPagesOnXAxis,
483 size_t nPagesOnYAxis);
484
485 std::unique_ptr<XObjectContext> CreateXObjectContextFromPage(
486 int src_page_index);
487
488 private:
489 // Map page object number to XObject object name.
490 using PageXObjectMap = std::map<uint32_t, ByteString>;
491
492 // Creates an XObject from |pSrcPage|, or find an existing XObject that
493 // represents |pSrcPage|. The transformation matrix is specified in
494 // |settings|.
495 // Returns the XObject reference surrounded by the transformation matrix.
496 ByteString AddSubPage(const RetainPtr<CPDF_Page>& pSrcPage,
497 const NupPageSettings& settings);
498
499 // Creates an XObject from |pSrcPage|. Updates mapping as needed.
500 // Returns the name of the newly created XObject.
501 ByteString MakeXObjectFromPage(RetainPtr<CPDF_Page> pSrcPage);
502 RetainPtr<CPDF_Stream> MakeXObjectFromPageRaw(RetainPtr<CPDF_Page> pSrcPage);
503
504 // Adds |bsContent| as the Contents key in |pDestPageDict|.
505 // Adds the objects in |m_XObjectNameToNumberMap| to the XObject dictionary in
506 // |pDestPageDict|'s Resources dictionary.
507 void FinishPage(RetainPtr<CPDF_Dictionary> pDestPageDict,
508 const ByteString& bsContent);
509
510 // Counter for giving new XObjects unique names.
511 uint32_t m_nObjectNumber = 0;
512
513 // Keeps track of created XObjects in the current page.
514 // Map XObject's object name to it's object number.
515 std::map<ByteString, uint32_t> m_XObjectNameToNumberMap;
516
517 // Mapping of source page object number and XObject name of the entire doc.
518 // If there are multiple source pages that reference the same object number,
519 // they can also share the same created XObject.
520 PageXObjectMap m_SrcPageXObjectMap;
521};
522
523CPDF_NPageToOneExporter::CPDF_NPageToOneExporter(CPDF_Document* pDestDoc,
524 CPDF_Document* pSrcDoc)
525 : CPDF_PageOrganizer(pDestDoc, pSrcDoc) {}
526
527CPDF_NPageToOneExporter::~CPDF_NPageToOneExporter() = default;
528
529bool CPDF_NPageToOneExporter::ExportNPagesToOne(
530 pdfium::span<const uint32_t> pageIndices,
531 const CFX_SizeF& destPageSize,
532 size_t nPagesOnXAxis,
533 size_t nPagesOnYAxis) {
534 if (!Init())
535 return false;
536
537 FX_SAFE_SIZE_T nSafePagesPerSheet = nPagesOnXAxis;
538 nSafePagesPerSheet *= nPagesOnYAxis;
539 if (!nSafePagesPerSheet.IsValid())
540 return false;
541
542 ClearObjectNumberMap();
543 m_SrcPageXObjectMap.clear();
544 size_t nPagesPerSheet = nSafePagesPerSheet.ValueOrDie();
545 NupState nupState(destPageSize, nPagesOnXAxis, nPagesOnYAxis);
546
547 FX_SAFE_INT32 curpage = 0;
548 const CFX_FloatRect destPageRect(0, 0, destPageSize.width,
549 destPageSize.height);
550 for (size_t iOuterPage = 0; iOuterPage < pageIndices.size();
551 iOuterPage += nPagesPerSheet) {
552 m_XObjectNameToNumberMap.clear();
553
554 RetainPtr<CPDF_Dictionary> pDestPageDict =
555 dest()->CreateNewPage(curpage.ValueOrDie());
556 if (!pDestPageDict)
557 return false;
558
559 pDestPageDict->SetRectFor(pdfium::page_object::kMediaBox, destPageRect);
560 ByteString bsContent;
561 size_t iInnerPageMax =
562 std::min(iOuterPage + nPagesPerSheet, pageIndices.size());
563 for (size_t i = iOuterPage; i < iInnerPageMax; ++i) {
564 RetainPtr<CPDF_Dictionary> pSrcPageDict =
565 src()->GetMutablePageDictionary(pageIndices[i]);
566 if (!pSrcPageDict)
567 return false;
568
569 auto pSrcPage = pdfium::MakeRetain<CPDF_Page>(src(), pSrcPageDict);
570 pSrcPage->AddPageImageCache();
571 NupPageSettings settings =
572 nupState.CalculateNewPagePosition(pSrcPage->GetPageSize());
573 bsContent += AddSubPage(pSrcPage, settings);
574 }
575
576 FinishPage(pDestPageDict, bsContent);
577 ++curpage;
578 }
579
580 return true;
581}
582
583ByteString CPDF_NPageToOneExporter::AddSubPage(
584 const RetainPtr<CPDF_Page>& pSrcPage,
585 const NupPageSettings& settings) {
586 uint32_t dwSrcPageObjnum = pSrcPage->GetDict()->GetObjNum();
587 const auto it = m_SrcPageXObjectMap.find(dwSrcPageObjnum);
588 ByteString bsXObjectName = it != m_SrcPageXObjectMap.end()
589 ? it->second
590 : MakeXObjectFromPage(pSrcPage);
591
592 CFX_Matrix matrix;
593 matrix.Scale(settings.scale, settings.scale);
594 matrix.Translate(settings.subPageStartPoint.x, settings.subPageStartPoint.y);
595
596 fxcrt::ostringstream contentStream;
597 contentStream << "q\n"
598 << matrix.a << " " << matrix.b << " " << matrix.c << " "
599 << matrix.d << " " << matrix.e << " " << matrix.f << " cm\n"
600 << "/" << bsXObjectName << " Do Q\n";
601 return ByteString(contentStream);
602}
603
604RetainPtr<CPDF_Stream> CPDF_NPageToOneExporter::MakeXObjectFromPageRaw(
605 RetainPtr<CPDF_Page> pSrcPage) {
606 RetainPtr<const CPDF_Dictionary> pSrcPageDict = pSrcPage->GetDict();
607 RetainPtr<const CPDF_Object> pSrcContentObj =
608 pSrcPageDict->GetDirectObjectFor(pdfium::page_object::kContents);
609
610 auto pNewXObject =
611 dest()->NewIndirect<CPDF_Stream>(dest()->New<CPDF_Dictionary>());
612 RetainPtr<CPDF_Dictionary> pNewXObjectDict = pNewXObject->GetMutableDict();
613 static const char kResourceString[] = "Resources";
614 if (!CopyInheritable(pNewXObjectDict, pSrcPageDict, kResourceString)) {
615 // Use a default empty resources if it does not exist.
616 pNewXObjectDict->SetNewFor<CPDF_Dictionary>(kResourceString);
617 }
618 uint32_t dwSrcPageObj = pSrcPageDict->GetObjNum();
619 uint32_t dwNewXobjectObj = pNewXObjectDict->GetObjNum();
620 AddObjectMapping(dwSrcPageObj, dwNewXobjectObj);
621 UpdateReference(pNewXObjectDict);
622 pNewXObjectDict->SetNewFor<CPDF_Name>("Type", "XObject");
623 pNewXObjectDict->SetNewFor<CPDF_Name>("Subtype", "Form");
624 pNewXObjectDict->SetNewFor<CPDF_Number>("FormType", 1);
625 pNewXObjectDict->SetRectFor("BBox", pSrcPage->GetBBox());
626 pNewXObjectDict->SetMatrixFor("Matrix", pSrcPage->GetPageMatrix());
627
628 if (pSrcContentObj) {
629 ByteString bsSrcContentStream;
630 const CPDF_Array* pSrcContentArray = pSrcContentObj->AsArray();
631 if (pSrcContentArray) {
632 for (size_t i = 0; i < pSrcContentArray->size(); ++i) {
633 RetainPtr<const CPDF_Stream> pStream = pSrcContentArray->GetStreamAt(i);
634 auto pAcc = pdfium::MakeRetain<CPDF_StreamAcc>(std::move(pStream));
635 pAcc->LoadAllDataFiltered();
636 bsSrcContentStream += ByteString(pAcc->GetSpan());
637 bsSrcContentStream += "\n";
638 }
639 } else {
640 RetainPtr<const CPDF_Stream> pStream(pSrcContentObj->AsStream());
641 auto pAcc = pdfium::MakeRetain<CPDF_StreamAcc>(std::move(pStream));
642 pAcc->LoadAllDataFiltered();
643 bsSrcContentStream = ByteString(pAcc->GetSpan());
644 }
645 pNewXObject->SetDataAndRemoveFilter(bsSrcContentStream.raw_span());
646 }
647 return pNewXObject;
648}
649
650ByteString CPDF_NPageToOneExporter::MakeXObjectFromPage(
651 RetainPtr<CPDF_Page> pSrcPage) {
652 RetainPtr<CPDF_Stream> pNewXObject = MakeXObjectFromPageRaw(pSrcPage);
653
654 // TODO(xlou): A better name schema to avoid possible object name collision.
655 ByteString bsXObjectName = ByteString::Format("X%d", ++m_nObjectNumber);
656 m_XObjectNameToNumberMap[bsXObjectName] = pNewXObject->GetObjNum();
657 m_SrcPageXObjectMap[pSrcPage->GetDict()->GetObjNum()] = bsXObjectName;
658 return bsXObjectName;
659}
660
661std::unique_ptr<XObjectContext>
662CPDF_NPageToOneExporter::CreateXObjectContextFromPage(int src_page_index) {
663 RetainPtr<CPDF_Dictionary> src_page_dict =
664 src()->GetMutablePageDictionary(src_page_index);
665 if (!src_page_dict)
666 return nullptr;
667
668 auto src_page = pdfium::MakeRetain<CPDF_Page>(src(), src_page_dict);
669 auto xobject = std::make_unique<XObjectContext>();
670 xobject->dest_doc = dest();
671 xobject->xobject.Reset(MakeXObjectFromPageRaw(src_page));
672 return xobject;
673}
674
675void CPDF_NPageToOneExporter::FinishPage(
676 RetainPtr<CPDF_Dictionary> pDestPageDict,
677 const ByteString& bsContent) {
678 RetainPtr<CPDF_Dictionary> pRes =
679 pDestPageDict->GetOrCreateDictFor(pdfium::page_object::kResources);
680 RetainPtr<CPDF_Dictionary> pPageXObject = pRes->GetOrCreateDictFor("XObject");
681 for (auto& it : m_XObjectNameToNumberMap)
682 pPageXObject->SetNewFor<CPDF_Reference>(it.first, dest(), it.second);
683
684 auto pStream =
685 dest()->NewIndirect<CPDF_Stream>(dest()->New<CPDF_Dictionary>());
686 pStream->SetData(bsContent.raw_span());
687 pDestPageDict->SetNewFor<CPDF_Reference>(pdfium::page_object::kContents,
688 dest(), pStream->GetObjNum());
689}
690
691// Make sure arrays only contain objects of basic types.
692bool IsValidViewerPreferencesArray(const CPDF_Array* array) {
693 CPDF_ArrayLocker locker(array);
694 for (const auto& obj : locker) {
695 if (obj->IsArray() || obj->IsDictionary() || obj->IsReference() ||
696 obj->IsStream()) {
697 return false;
698 }
699 }
700 return true;
701}
702
703bool IsValidViewerPreferencesObject(const CPDF_Object* obj) {
704 // Per spec, there are no valid entries of these types.
705 if (obj->IsDictionary() || obj->IsNull() || obj->IsReference() ||
706 obj->IsStream()) {
707 return false;
708 }
709
710 const CPDF_Array* array = obj->AsArray();
711 if (!array) {
712 return true;
713 }
714
715 return IsValidViewerPreferencesArray(array);
716}
717
718} // namespace
719
721FPDF_ImportPagesByIndex(FPDF_DOCUMENT dest_doc,
722 FPDF_DOCUMENT src_doc,
723 const int* page_indices,
724 unsigned long length,
725 int index) {
727 if (!dest_doc)
728 return false;
729
731 if (!pSrcDoc)
732 return false;
733
734 CPDF_PageExporter exporter(pDestDoc, pSrcDoc);
735
736 if (!page_indices) {
737 std::vector<uint32_t> page_indices_vec(pSrcDoc->GetPageCount());
738 std::iota(page_indices_vec.begin(), page_indices_vec.end(), 0);
739 return exporter.ExportPage(page_indices_vec, index);
740 }
741
742 if (length == 0)
743 return false;
744
745 return exporter.ExportPage(
746 pdfium::make_span(reinterpret_cast<const uint32_t*>(page_indices),
747 length),
748 index);
749}
750
751FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDF_ImportPages(FPDF_DOCUMENT dest_doc,
752 FPDF_DOCUMENT src_doc,
753 FPDF_BYTESTRING pagerange,
754 int index) {
756 if (!dest_doc)
757 return false;
758
760 if (!pSrcDoc)
761 return false;
762
763 std::vector<uint32_t> page_indices = GetPageIndices(*pSrcDoc, pagerange);
764 if (page_indices.empty())
765 return false;
766
767 CPDF_PageExporter exporter(pDestDoc, pSrcDoc);
768 return exporter.ExportPage(page_indices, index);
769}
770
771FPDF_EXPORT FPDF_DOCUMENT FPDF_CALLCONV
772FPDF_ImportNPagesToOne(FPDF_DOCUMENT src_doc,
773 float output_width,
774 float output_height,
775 size_t num_pages_on_x_axis,
776 size_t num_pages_on_y_axis) {
778 if (!pSrcDoc)
779 return nullptr;
780
781 if (output_width <= 0 || output_height <= 0 || num_pages_on_x_axis <= 0 ||
782 num_pages_on_y_axis <= 0) {
783 return nullptr;
784 }
785
786 ScopedFPDFDocument output_doc(FPDF_CreateNewDocument());
787 if (!output_doc)
788 return nullptr;
789
790 CPDF_Document* pDestDoc = CPDFDocumentFromFPDFDocument(output_doc.get());
791 DCHECK(pDestDoc);
792
793 std::vector<uint32_t> page_indices = GetPageIndices(*pSrcDoc, ByteString());
794 if (page_indices.empty())
795 return nullptr;
796
797 if (num_pages_on_x_axis == 1 && num_pages_on_y_axis == 1) {
798 CPDF_PageExporter exporter(pDestDoc, pSrcDoc);
799 if (!exporter.ExportPage(page_indices, 0))
800 return nullptr;
801 return output_doc.release();
802 }
803
804 CPDF_NPageToOneExporter exporter(pDestDoc, pSrcDoc);
805 if (!exporter.ExportNPagesToOne(page_indices,
806 CFX_SizeF(output_width, output_height),
807 num_pages_on_x_axis, num_pages_on_y_axis)) {
808 return nullptr;
809 }
810 return output_doc.release();
811}
812
813FPDF_EXPORT FPDF_XOBJECT FPDF_CALLCONV
814FPDF_NewXObjectFromPage(FPDF_DOCUMENT dest_doc,
815 FPDF_DOCUMENT src_doc,
816 int src_page_index) {
818 if (!dest)
819 return nullptr;
820
822 if (!src)
823 return nullptr;
824
825 CPDF_NPageToOneExporter exporter(dest, src);
826 std::unique_ptr<XObjectContext> xobject =
827 exporter.CreateXObjectContextFromPage(src_page_index);
828 return FPDFXObjectFromXObjectContext(xobject.release());
829}
830
831FPDF_EXPORT void FPDF_CALLCONV FPDF_CloseXObject(FPDF_XOBJECT xobject) {
832 std::unique_ptr<XObjectContext> xobject_deleter(
833 XObjectContextFromFPDFXObject(xobject));
834}
835
836FPDF_EXPORT FPDF_PAGEOBJECT FPDF_CALLCONV
837FPDF_NewFormObjectFromXObject(FPDF_XOBJECT xobject) {
839 if (!xobj)
840 return nullptr;
841
842 auto form = std::make_unique<CPDF_Form>(xobj->dest_doc, nullptr,
843 xobj->xobject, nullptr);
844 form->ParseContent(nullptr, nullptr, nullptr);
845 auto form_object = std::make_unique<CPDF_FormObject>(
847 return FPDFPageObjectFromCPDFPageObject(form_object.release());
848}
849
851FPDF_CopyViewerPreferences(FPDF_DOCUMENT dest_doc, FPDF_DOCUMENT src_doc) {
853 if (!pDstDoc)
854 return false;
855
857 if (!pSrcDoc)
858 return false;
859
860 RetainPtr<const CPDF_Dictionary> pPrefDict =
861 pSrcDoc->GetRoot()->GetDictFor("ViewerPreferences");
862 if (!pPrefDict)
863 return false;
864
865 RetainPtr<CPDF_Dictionary> pDstDict = pDstDoc->GetMutableRoot();
866 if (!pDstDict)
867 return false;
868
869 auto cloned_dict = pdfium::MakeRetain<CPDF_Dictionary>();
870 CPDF_DictionaryLocker locker(pPrefDict);
871 for (const auto& it : locker) {
872 if (IsValidViewerPreferencesObject(it.second)) {
873 cloned_dict->SetFor(it.first, it.second->Clone());
874 }
875 }
876
877 pDstDict->SetFor("ViewerPreferences", std::move(cloned_dict));
878 return true;
879}
void Scale(float sx, float sy)
CFX_Matrix()=default
CPDF_ArrayLocker(const CPDF_Array *pArray)
CPDF_DictionaryLocker(const CPDF_Dictionary *pDictionary)
bool KeyExist(const ByteString &key) const
ByteString GetByteStringFor(const ByteString &key) const
int GetPageCount() const
uint32_t AddIndirectObject(RetainPtr< CPDF_Object > pObj)
bool IsDictionary() const
bool IsStream() const
const CPDF_Array * AsArray() const
bool IsNull() const
bool IsReference() const
static constexpr int32_t kNoContentStream
void SetRef(CPDF_IndirectObjectHolder *pDoc, uint32_t objnum)
uint32_t GetRefObjNum() const
bool EqualNoCase(ByteStringView str) const
static ByteString Format(const char *pFormat,...)
ByteString & operator+=(const ByteString &str)
ByteString & operator+=(const char *str)
ByteString & operator=(ByteString &&that) noexcept
bool IsEmpty() const
Definition bytestring.h:119
XObjectContext * XObjectContextFromFPDFXObject(FPDF_XOBJECT xobject)
CPDF_Document * CPDFDocumentFromFPDFDocument(FPDF_DOCUMENT doc)
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)
Definition fpdf_ppo.cpp:772
FPDF_EXPORT void FPDF_CALLCONV FPDF_CloseXObject(FPDF_XOBJECT xobject)
Definition fpdf_ppo.cpp:831
FPDF_EXPORT FPDF_PAGEOBJECT FPDF_CALLCONV FPDF_NewFormObjectFromXObject(FPDF_XOBJECT xobject)
Definition fpdf_ppo.cpp:837
FPDF_EXPORT FPDF_XOBJECT FPDF_CALLCONV FPDF_NewXObjectFromPage(FPDF_DOCUMENT dest_doc, FPDF_DOCUMENT src_doc, int src_page_index)
Definition fpdf_ppo.cpp:814
FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDF_CopyViewerPreferences(FPDF_DOCUMENT dest_doc, FPDF_DOCUMENT src_doc)
Definition fpdf_ppo.cpp:851
FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDF_ImportPages(FPDF_DOCUMENT dest_doc, FPDF_DOCUMENT src_doc, FPDF_BYTESTRING pagerange, int index)
Definition fpdf_ppo.cpp:751
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)
Definition fpdf_ppo.cpp:721
#define FPDF_CALLCONV
Definition fpdfview.h:227
#define FPDF_EXPORT
Definition fpdfview.h:221
const char kMediaBox[]
const char kParent[]
const char kContents[]
UnownedPtr< CPDF_Document > dest_doc
Definition fpdf_ppo.cpp:44
RetainPtr< CPDF_Stream > xobject
Definition fpdf_ppo.cpp:45