You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
825 lines
28 KiB
825 lines
28 KiB
// Copyright 2014 PDFium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
|
|
|
|
#include "public/fpdf_ppo.h"
|
|
|
|
#include <algorithm>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "constants/page_object.h"
|
|
#include "core/fpdfapi/page/cpdf_page.h"
|
|
#include "core/fpdfapi/page/cpdf_pageobject.h"
|
|
#include "core/fpdfapi/parser/cpdf_array.h"
|
|
#include "core/fpdfapi/parser/cpdf_dictionary.h"
|
|
#include "core/fpdfapi/parser/cpdf_document.h"
|
|
#include "core/fpdfapi/parser/cpdf_name.h"
|
|
#include "core/fpdfapi/parser/cpdf_number.h"
|
|
#include "core/fpdfapi/parser/cpdf_object.h"
|
|
#include "core/fpdfapi/parser/cpdf_reference.h"
|
|
#include "core/fpdfapi/parser/cpdf_stream.h"
|
|
#include "core/fpdfapi/parser/cpdf_stream_acc.h"
|
|
#include "core/fpdfapi/parser/cpdf_string.h"
|
|
#include "core/fpdfapi/render/cpdf_pagerendercache.h"
|
|
#include "core/fxcrt/fx_safe_types.h"
|
|
#include "core/fxcrt/retain_ptr.h"
|
|
#include "core/fxcrt/unowned_ptr.h"
|
|
#include "fpdfsdk/cpdfsdk_helpers.h"
|
|
#include "public/cpp/fpdf_scopers.h"
|
|
#include "third_party/base/ptr_util.h"
|
|
|
|
namespace {
|
|
|
|
// Struct that stores sub page origin and scale information. When importing
|
|
// more than one pages onto the same page, most likely the pages will need to be
|
|
// scaled down, and scale is in range of (0, 1) exclusive.
|
|
struct NupPageSettings {
|
|
CFX_PointF subPageStartPoint;
|
|
float scale;
|
|
};
|
|
|
|
// Calculates the N-up parameters. When importing multiple pages into one page.
|
|
// The space of output page is evenly divided along the X axis and Y axis based
|
|
// on the input |nPagesOnXAxis| and |nPagesOnYAxis|.
|
|
class NupState {
|
|
public:
|
|
NupState(const CFX_SizeF& pagesize,
|
|
size_t nPagesOnXAxis,
|
|
size_t nPagesOnYAxis);
|
|
|
|
// Calculate sub page origin and scale with the source page of |pagesize| and
|
|
// new page of |m_subPageSize|.
|
|
NupPageSettings CalculateNewPagePosition(const CFX_SizeF& pagesize);
|
|
|
|
private:
|
|
// Helper function to get the |iSubX|, |iSubY| pair based on |m_subPageIndex|.
|
|
// The space of output page is evenly divided into slots along x and y axis.
|
|
// |iSubX| and |iSubY| are 0-based indices that indicate which allocation
|
|
// slot to use.
|
|
std::pair<size_t, size_t> ConvertPageOrder() const;
|
|
|
|
// Given the |iSubX| and |iSubY| subpage position within a page, and a source
|
|
// page with dimensions of |pagesize|, calculate the sub page's origin and
|
|
// scale.
|
|
NupPageSettings CalculatePageEdit(size_t iSubX,
|
|
size_t iSubY,
|
|
const CFX_SizeF& pagesize) const;
|
|
|
|
const CFX_SizeF m_destPageSize;
|
|
const size_t m_nPagesOnXAxis;
|
|
const size_t m_nPagesOnYAxis;
|
|
const size_t m_nPagesPerSheet;
|
|
CFX_SizeF m_subPageSize;
|
|
|
|
// A 0-based index, in range of [0, m_nPagesPerSheet - 1).
|
|
size_t m_subPageIndex = 0;
|
|
};
|
|
|
|
NupState::NupState(const CFX_SizeF& pagesize,
|
|
size_t nPagesOnXAxis,
|
|
size_t nPagesOnYAxis)
|
|
: m_destPageSize(pagesize),
|
|
m_nPagesOnXAxis(nPagesOnXAxis),
|
|
m_nPagesOnYAxis(nPagesOnYAxis),
|
|
m_nPagesPerSheet(nPagesOnXAxis * nPagesOnYAxis) {
|
|
ASSERT(m_nPagesOnXAxis > 0);
|
|
ASSERT(m_nPagesOnYAxis > 0);
|
|
ASSERT(m_destPageSize.width > 0);
|
|
ASSERT(m_destPageSize.height > 0);
|
|
|
|
m_subPageSize.width = m_destPageSize.width / m_nPagesOnXAxis;
|
|
m_subPageSize.height = m_destPageSize.height / m_nPagesOnYAxis;
|
|
}
|
|
|
|
std::pair<size_t, size_t> NupState::ConvertPageOrder() const {
|
|
size_t iSubX = m_subPageIndex % m_nPagesOnXAxis;
|
|
size_t iSubY = m_subPageIndex / m_nPagesOnXAxis;
|
|
|
|
// Y Axis, pages start from the top of the output page.
|
|
iSubY = m_nPagesOnYAxis - iSubY - 1;
|
|
|
|
return {iSubX, iSubY};
|
|
}
|
|
|
|
NupPageSettings NupState::CalculatePageEdit(size_t iSubX,
|
|
size_t iSubY,
|
|
const CFX_SizeF& pagesize) const {
|
|
NupPageSettings settings;
|
|
settings.subPageStartPoint.x = iSubX * m_subPageSize.width;
|
|
settings.subPageStartPoint.y = iSubY * m_subPageSize.height;
|
|
|
|
const float xScale = m_subPageSize.width / pagesize.width;
|
|
const float yScale = m_subPageSize.height / pagesize.height;
|
|
settings.scale = std::min(xScale, yScale);
|
|
|
|
float subWidth = pagesize.width * settings.scale;
|
|
float subHeight = pagesize.height * settings.scale;
|
|
if (xScale > yScale)
|
|
settings.subPageStartPoint.x += (m_subPageSize.width - subWidth) / 2;
|
|
else
|
|
settings.subPageStartPoint.y += (m_subPageSize.height - subHeight) / 2;
|
|
return settings;
|
|
}
|
|
|
|
NupPageSettings NupState::CalculateNewPagePosition(const CFX_SizeF& pagesize) {
|
|
if (m_subPageIndex >= m_nPagesPerSheet)
|
|
m_subPageIndex = 0;
|
|
|
|
size_t iSubX;
|
|
size_t iSubY;
|
|
std::tie(iSubX, iSubY) = ConvertPageOrder();
|
|
++m_subPageIndex;
|
|
return CalculatePageEdit(iSubX, iSubY, pagesize);
|
|
}
|
|
|
|
const CPDF_Object* PageDictGetInheritableTag(const CPDF_Dictionary* pDict,
|
|
const ByteString& bsSrcTag) {
|
|
if (!pDict || bsSrcTag.IsEmpty())
|
|
return nullptr;
|
|
if (!pDict->KeyExist(pdfium::page_object::kParent) ||
|
|
!pDict->KeyExist(pdfium::page_object::kType)) {
|
|
return nullptr;
|
|
}
|
|
|
|
const CPDF_Object* pType =
|
|
pDict->GetObjectFor(pdfium::page_object::kType)->GetDirect();
|
|
if (!ToName(pType))
|
|
return nullptr;
|
|
if (pType->GetString().Compare("Page"))
|
|
return nullptr;
|
|
|
|
const CPDF_Dictionary* pp = ToDictionary(
|
|
pDict->GetObjectFor(pdfium::page_object::kParent)->GetDirect());
|
|
if (!pp)
|
|
return nullptr;
|
|
|
|
if (pDict->KeyExist(bsSrcTag))
|
|
return pDict->GetObjectFor(bsSrcTag);
|
|
|
|
while (pp) {
|
|
if (pp->KeyExist(bsSrcTag))
|
|
return pp->GetObjectFor(bsSrcTag);
|
|
if (!pp->KeyExist(pdfium::page_object::kParent))
|
|
break;
|
|
pp = ToDictionary(
|
|
pp->GetObjectFor(pdfium::page_object::kParent)->GetDirect());
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
CFX_FloatRect GetMediaBox(const CPDF_Dictionary* pPageDict) {
|
|
const CPDF_Object* pMediaBox =
|
|
PageDictGetInheritableTag(pPageDict, pdfium::page_object::kMediaBox);
|
|
const CPDF_Array* pArray = ToArray(pMediaBox->GetDirect());
|
|
if (!pArray)
|
|
return CFX_FloatRect();
|
|
return pArray->GetRect();
|
|
}
|
|
|
|
CFX_FloatRect GetCropBox(const CPDF_Dictionary* pPageDict) {
|
|
if (pPageDict->KeyExist(pdfium::page_object::kCropBox))
|
|
return pPageDict->GetRectFor(pdfium::page_object::kCropBox);
|
|
return GetMediaBox(pPageDict);
|
|
}
|
|
|
|
bool CopyInheritable(CPDF_Dictionary* pDestPageDict,
|
|
const CPDF_Dictionary* pSrcPageDict,
|
|
const ByteString& key) {
|
|
if (pDestPageDict->KeyExist(key))
|
|
return true;
|
|
|
|
const CPDF_Object* pInheritable =
|
|
PageDictGetInheritableTag(pSrcPageDict, key);
|
|
if (!pInheritable)
|
|
return false;
|
|
|
|
pDestPageDict->SetFor(key, pInheritable->Clone());
|
|
return true;
|
|
}
|
|
|
|
bool ParsePageRangeString(const ByteString& bsPageRange,
|
|
uint32_t nCount,
|
|
std::vector<uint32_t>* pageArray) {
|
|
ByteString bsStrippedPageRange = bsPageRange;
|
|
bsStrippedPageRange.Remove(' ');
|
|
size_t nLength = bsStrippedPageRange.GetLength();
|
|
if (nLength == 0)
|
|
return true;
|
|
|
|
static const ByteString cbCompareString("0123456789-,");
|
|
for (size_t i = 0; i < nLength; ++i) {
|
|
if (!cbCompareString.Contains(bsStrippedPageRange[i]))
|
|
return false;
|
|
}
|
|
|
|
ByteString cbMidRange;
|
|
size_t nStringFrom = 0;
|
|
size_t nStringTo = 0;
|
|
while (nStringTo < nLength) {
|
|
nStringTo = bsStrippedPageRange.Find(',', nStringFrom).value_or(nLength);
|
|
cbMidRange =
|
|
bsStrippedPageRange.Substr(nStringFrom, nStringTo - nStringFrom);
|
|
Optional<size_t> nDashPosition = cbMidRange.Find('-');
|
|
if (nDashPosition) {
|
|
size_t nMid = nDashPosition.value();
|
|
uint32_t nStartPageNum = pdfium::base::checked_cast<uint32_t>(
|
|
atoi(cbMidRange.First(nMid).c_str()));
|
|
if (nStartPageNum == 0)
|
|
return false;
|
|
|
|
++nMid;
|
|
size_t nEnd = cbMidRange.GetLength() - nMid;
|
|
if (nEnd == 0)
|
|
return false;
|
|
|
|
uint32_t nEndPageNum = pdfium::base::checked_cast<uint32_t>(
|
|
atoi(cbMidRange.Substr(nMid, nEnd).c_str()));
|
|
if (nStartPageNum < 0 || nStartPageNum > nEndPageNum ||
|
|
nEndPageNum > nCount) {
|
|
return false;
|
|
}
|
|
for (uint32_t i = nStartPageNum; i <= nEndPageNum; ++i) {
|
|
pageArray->push_back(i);
|
|
}
|
|
} else {
|
|
uint32_t nPageNum =
|
|
pdfium::base::checked_cast<uint32_t>(atoi(cbMidRange.c_str()));
|
|
if (nPageNum <= 0 || nPageNum > nCount)
|
|
return false;
|
|
pageArray->push_back(nPageNum);
|
|
}
|
|
nStringFrom = nStringTo + 1;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
std::vector<uint32_t> GetPageNumbers(const CPDF_Document& doc,
|
|
const ByteString& bsPageRange) {
|
|
std::vector<uint32_t> page_numbers;
|
|
uint32_t nCount = doc.GetPageCount();
|
|
if (bsPageRange.IsEmpty()) {
|
|
for (uint32_t i = 1; i <= nCount; ++i)
|
|
page_numbers.push_back(i);
|
|
} else {
|
|
if (!ParsePageRangeString(bsPageRange, nCount, &page_numbers))
|
|
page_numbers.clear();
|
|
}
|
|
return page_numbers;
|
|
}
|
|
|
|
class CPDF_PageOrganizer {
|
|
protected:
|
|
CPDF_PageOrganizer(CPDF_Document* pDestDoc, CPDF_Document* pSrcDoc);
|
|
~CPDF_PageOrganizer();
|
|
|
|
// Must be called after construction before doing anything else.
|
|
bool Init();
|
|
|
|
bool UpdateReference(CPDF_Object* pObj);
|
|
|
|
CPDF_Document* dest() { return m_pDestDoc.Get(); }
|
|
const CPDF_Document* dest() const { return m_pDestDoc.Get(); }
|
|
|
|
CPDF_Document* src() { return m_pSrcDoc.Get(); }
|
|
const CPDF_Document* src() const { return m_pSrcDoc.Get(); }
|
|
|
|
void AddObjectMapping(uint32_t dwOldPageObj, uint32_t dwNewPageObj) {
|
|
m_ObjectNumberMap[dwOldPageObj] = dwNewPageObj;
|
|
}
|
|
|
|
void ClearObjectNumberMap() { m_ObjectNumberMap.clear(); }
|
|
|
|
private:
|
|
uint32_t GetNewObjId(CPDF_Reference* pRef);
|
|
|
|
UnownedPtr<CPDF_Document> const m_pDestDoc;
|
|
UnownedPtr<CPDF_Document> const m_pSrcDoc;
|
|
|
|
// Mapping of source object number to destination object number.
|
|
std::map<uint32_t, uint32_t> m_ObjectNumberMap;
|
|
};
|
|
|
|
CPDF_PageOrganizer::CPDF_PageOrganizer(CPDF_Document* pDestDoc,
|
|
CPDF_Document* pSrcDoc)
|
|
: m_pDestDoc(pDestDoc), m_pSrcDoc(pSrcDoc) {}
|
|
|
|
CPDF_PageOrganizer::~CPDF_PageOrganizer() = default;
|
|
|
|
bool CPDF_PageOrganizer::Init() {
|
|
ASSERT(m_pDestDoc);
|
|
ASSERT(m_pSrcDoc);
|
|
|
|
CPDF_Dictionary* pNewRoot = dest()->GetRoot();
|
|
if (!pNewRoot)
|
|
return false;
|
|
|
|
CPDF_Dictionary* pDocInfoDict = dest()->GetInfo();
|
|
if (!pDocInfoDict)
|
|
return false;
|
|
|
|
pDocInfoDict->SetNewFor<CPDF_String>("Producer", "PDFium", false);
|
|
|
|
ByteString cbRootType = pNewRoot->GetStringFor("Type", ByteString());
|
|
if (cbRootType.IsEmpty())
|
|
pNewRoot->SetNewFor<CPDF_Name>("Type", "Catalog");
|
|
|
|
CPDF_Object* pElement = pNewRoot->GetObjectFor("Pages");
|
|
CPDF_Dictionary* pNewPages =
|
|
pElement ? ToDictionary(pElement->GetDirect()) : nullptr;
|
|
if (!pNewPages) {
|
|
pNewPages = dest()->NewIndirect<CPDF_Dictionary>();
|
|
pNewRoot->SetNewFor<CPDF_Reference>("Pages", dest(),
|
|
pNewPages->GetObjNum());
|
|
}
|
|
ByteString cbPageType = pNewPages->GetStringFor("Type", ByteString());
|
|
if (cbPageType.IsEmpty())
|
|
pNewPages->SetNewFor<CPDF_Name>("Type", "Pages");
|
|
|
|
if (!pNewPages->GetArrayFor("Kids")) {
|
|
auto* pNewArray = dest()->NewIndirect<CPDF_Array>();
|
|
pNewPages->SetNewFor<CPDF_Number>("Count", 0);
|
|
pNewPages->SetNewFor<CPDF_Reference>("Kids", dest(),
|
|
pNewArray->GetObjNum());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool CPDF_PageOrganizer::UpdateReference(CPDF_Object* pObj) {
|
|
switch (pObj->GetType()) {
|
|
case CPDF_Object::kReference: {
|
|
CPDF_Reference* pReference = pObj->AsReference();
|
|
uint32_t newobjnum = GetNewObjId(pReference);
|
|
if (newobjnum == 0)
|
|
return false;
|
|
pReference->SetRef(dest(), newobjnum);
|
|
return true;
|
|
}
|
|
case CPDF_Object::kDictionary: {
|
|
CPDF_Dictionary* pDict = pObj->AsDictionary();
|
|
std::vector<ByteString> bad_keys;
|
|
{
|
|
CPDF_DictionaryLocker locker(pDict);
|
|
for (const auto& it : locker) {
|
|
const ByteString& key = it.first;
|
|
if (key == "Parent" || key == "Prev" || key == "First")
|
|
continue;
|
|
CPDF_Object* pNextObj = it.second.Get();
|
|
if (!pNextObj)
|
|
return false;
|
|
if (!UpdateReference(pNextObj))
|
|
bad_keys.push_back(key);
|
|
}
|
|
}
|
|
for (const auto& key : bad_keys)
|
|
pDict->RemoveFor(key);
|
|
return true;
|
|
}
|
|
case CPDF_Object::kArray: {
|
|
CPDF_Array* pArray = pObj->AsArray();
|
|
for (size_t i = 0; i < pArray->size(); ++i) {
|
|
CPDF_Object* pNextObj = pArray->GetObjectAt(i);
|
|
if (!pNextObj || !UpdateReference(pNextObj))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
case CPDF_Object::kStream: {
|
|
CPDF_Stream* pStream = pObj->AsStream();
|
|
CPDF_Dictionary* pDict = pStream->GetDict();
|
|
return pDict && UpdateReference(pDict);
|
|
}
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
uint32_t CPDF_PageOrganizer::GetNewObjId(CPDF_Reference* pRef) {
|
|
if (!pRef)
|
|
return 0;
|
|
|
|
uint32_t dwObjnum = pRef->GetRefObjNum();
|
|
uint32_t dwNewObjNum = 0;
|
|
const auto it = m_ObjectNumberMap.find(dwObjnum);
|
|
if (it != m_ObjectNumberMap.end())
|
|
dwNewObjNum = it->second;
|
|
if (dwNewObjNum)
|
|
return dwNewObjNum;
|
|
|
|
CPDF_Object* pDirect = pRef->GetDirect();
|
|
if (!pDirect)
|
|
return 0;
|
|
|
|
RetainPtr<CPDF_Object> pClone = pDirect->Clone();
|
|
if (CPDF_Dictionary* pDictClone = pClone->AsDictionary()) {
|
|
if (pDictClone->KeyExist("Type")) {
|
|
ByteString strType = pDictClone->GetStringFor("Type");
|
|
if (!FXSYS_stricmp(strType.c_str(), "Pages"))
|
|
return 4;
|
|
if (!FXSYS_stricmp(strType.c_str(), "Page"))
|
|
return 0;
|
|
}
|
|
}
|
|
CPDF_Object* pUnownedClone = dest()->AddIndirectObject(std::move(pClone));
|
|
dwNewObjNum = pUnownedClone->GetObjNum();
|
|
AddObjectMapping(dwObjnum, dwNewObjNum);
|
|
if (!UpdateReference(pUnownedClone))
|
|
return 0;
|
|
|
|
return dwNewObjNum;
|
|
}
|
|
|
|
// Copies pages from a source document into a destination document.
|
|
// This class is intended to be used once via ExportPage() and then destroyed.
|
|
class CPDF_PageExporter final : public CPDF_PageOrganizer {
|
|
public:
|
|
CPDF_PageExporter(CPDF_Document* pDestDoc, CPDF_Document* pSrcDoc);
|
|
~CPDF_PageExporter();
|
|
|
|
// For the pages from the source document with |pageNums| as their page
|
|
// numbers, insert them into the destination document at page |nIndex|.
|
|
// |pageNums| is 1-based.
|
|
// |nIndex| is 0-based.
|
|
bool ExportPage(const std::vector<uint32_t>& pageNums, int nIndex);
|
|
};
|
|
|
|
CPDF_PageExporter::CPDF_PageExporter(CPDF_Document* pDestDoc,
|
|
CPDF_Document* pSrcDoc)
|
|
: CPDF_PageOrganizer(pDestDoc, pSrcDoc) {}
|
|
|
|
CPDF_PageExporter::~CPDF_PageExporter() = default;
|
|
|
|
bool CPDF_PageExporter::ExportPage(const std::vector<uint32_t>& pageNums,
|
|
int nIndex) {
|
|
if (!Init())
|
|
return false;
|
|
|
|
int curpage = nIndex;
|
|
for (size_t i = 0; i < pageNums.size(); ++i) {
|
|
CPDF_Dictionary* pDestPageDict = dest()->CreateNewPage(curpage);
|
|
auto* pSrcPageDict = src()->GetPageDictionary(pageNums[i] - 1);
|
|
if (!pSrcPageDict || !pDestPageDict)
|
|
return false;
|
|
|
|
// Clone the page dictionary
|
|
CPDF_DictionaryLocker locker(pSrcPageDict);
|
|
for (const auto& it : locker) {
|
|
const ByteString& cbSrcKeyStr = it.first;
|
|
if (cbSrcKeyStr == pdfium::page_object::kType ||
|
|
cbSrcKeyStr == pdfium::page_object::kParent) {
|
|
continue;
|
|
}
|
|
|
|
CPDF_Object* pObj = it.second.Get();
|
|
pDestPageDict->SetFor(cbSrcKeyStr, pObj->Clone());
|
|
}
|
|
|
|
// inheritable item
|
|
// Even though some entries are required by the PDF spec, there exist
|
|
// PDFs that omit them. Set some defaults in this case.
|
|
// 1 MediaBox - required
|
|
if (!CopyInheritable(pDestPageDict, pSrcPageDict,
|
|
pdfium::page_object::kMediaBox)) {
|
|
// Search for "CropBox" in the source page dictionary.
|
|
// If it does not exist, use the default letter size.
|
|
const CPDF_Object* pInheritable = PageDictGetInheritableTag(
|
|
pSrcPageDict, pdfium::page_object::kCropBox);
|
|
if (pInheritable) {
|
|
pDestPageDict->SetFor(pdfium::page_object::kMediaBox,
|
|
pInheritable->Clone());
|
|
} else {
|
|
// Make the default size letter size (8.5"x11")
|
|
static const CFX_FloatRect kDefaultLetterRect(0, 0, 612, 792);
|
|
pDestPageDict->SetRectFor(pdfium::page_object::kMediaBox,
|
|
kDefaultLetterRect);
|
|
}
|
|
}
|
|
|
|
// 2 Resources - required
|
|
if (!CopyInheritable(pDestPageDict, pSrcPageDict,
|
|
pdfium::page_object::kResources)) {
|
|
// Use a default empty resources if it does not exist.
|
|
pDestPageDict->SetNewFor<CPDF_Dictionary>(
|
|
pdfium::page_object::kResources);
|
|
}
|
|
|
|
// 3 CropBox - optional
|
|
CopyInheritable(pDestPageDict, pSrcPageDict, pdfium::page_object::kCropBox);
|
|
// 4 Rotate - optional
|
|
CopyInheritable(pDestPageDict, pSrcPageDict, pdfium::page_object::kRotate);
|
|
|
|
// Update the reference
|
|
uint32_t dwOldPageObj = pSrcPageDict->GetObjNum();
|
|
uint32_t dwNewPageObj = pDestPageDict->GetObjNum();
|
|
AddObjectMapping(dwOldPageObj, dwNewPageObj);
|
|
UpdateReference(pDestPageDict);
|
|
++curpage;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Copies pages from a source document into a destination document. Creates 1
|
|
// page in the destination document for every N source pages. This class is
|
|
// intended to be used once via ExportNPagesToOne() and then destroyed.
|
|
class CPDF_NPageToOneExporter final : public CPDF_PageOrganizer {
|
|
public:
|
|
CPDF_NPageToOneExporter(CPDF_Document* pDestDoc, CPDF_Document* pSrcDoc);
|
|
~CPDF_NPageToOneExporter();
|
|
|
|
// For the pages from the source document with |pageNums| as their page
|
|
// numbers, insert them into the destination document, starting at page 0.
|
|
// |pageNums| is 1-based.
|
|
// |destPageSize| is the destination document page dimensions, measured in
|
|
// PDF "user space" units.
|
|
// |nPagesOnXAxis| and |nPagesOnXAxis| together defines how many source
|
|
// pages fit on one destination page.
|
|
bool ExportNPagesToOne(const std::vector<uint32_t>& pageNums,
|
|
const CFX_SizeF& destPageSize,
|
|
size_t nPagesOnXAxis,
|
|
size_t nPagesOnYAxis);
|
|
|
|
private:
|
|
// Map page object number to XObject object name.
|
|
using PageXObjectMap = std::map<uint32_t, ByteString>;
|
|
|
|
// Creates an XObject from |pSrcPageDict|, or find an existing XObject that
|
|
// represents |pSrcPageDict|. The transformation matrix is specified in
|
|
// |settings|.
|
|
// Returns the XObject reference surrounded by the transformation matrix.
|
|
ByteString AddSubPage(const CPDF_Dictionary* pSrcPageDict,
|
|
const NupPageSettings& settings);
|
|
|
|
// Creates an XObject from |pSrcPageDict|. Updates mapping as needed.
|
|
// Returns the name of the newly created XObject.
|
|
ByteString MakeXObjectFromPage(const CPDF_Dictionary* pSrcPageDict);
|
|
|
|
// Adds |bsContent| as the Contents key in |pDestPageDict|.
|
|
// Adds the objects in |m_XObjectNameToNumberMap| to the XObject dictionary in
|
|
// |pDestPageDict|'s Resources dictionary.
|
|
void FinishPage(CPDF_Dictionary* pDestPageDict, const ByteString& bsContent);
|
|
|
|
// Counter for giving new XObjects unique names.
|
|
uint32_t m_nObjectNumber = 0;
|
|
|
|
// Keeps track of created XObjects in the current page.
|
|
// Map XObject's object name to it's object number.
|
|
std::map<ByteString, uint32_t> m_XObjectNameToNumberMap;
|
|
|
|
// Mapping of source page object number and XObject name of the entire doc.
|
|
// If there are multiple source pages that reference the same object number,
|
|
// they can also share the same created XObject.
|
|
PageXObjectMap m_SrcPageXObjectMap;
|
|
};
|
|
|
|
CPDF_NPageToOneExporter::CPDF_NPageToOneExporter(CPDF_Document* pDestDoc,
|
|
CPDF_Document* pSrcDoc)
|
|
: CPDF_PageOrganizer(pDestDoc, pSrcDoc) {}
|
|
|
|
CPDF_NPageToOneExporter::~CPDF_NPageToOneExporter() = default;
|
|
|
|
bool CPDF_NPageToOneExporter::ExportNPagesToOne(
|
|
const std::vector<uint32_t>& pageNums,
|
|
const CFX_SizeF& destPageSize,
|
|
size_t nPagesOnXAxis,
|
|
size_t nPagesOnYAxis) {
|
|
if (!Init())
|
|
return false;
|
|
|
|
FX_SAFE_SIZE_T nSafePagesPerSheet = nPagesOnXAxis;
|
|
nSafePagesPerSheet *= nPagesOnYAxis;
|
|
if (!nSafePagesPerSheet.IsValid())
|
|
return false;
|
|
|
|
ClearObjectNumberMap();
|
|
m_SrcPageXObjectMap.clear();
|
|
size_t nPagesPerSheet = nSafePagesPerSheet.ValueOrDie();
|
|
NupState nupState(destPageSize, nPagesOnXAxis, nPagesOnYAxis);
|
|
|
|
size_t curpage = 0;
|
|
const CFX_FloatRect destPageRect(0, 0, destPageSize.width,
|
|
destPageSize.height);
|
|
for (size_t iOuterPage = 0; iOuterPage < pageNums.size();
|
|
iOuterPage += nPagesPerSheet) {
|
|
m_XObjectNameToNumberMap.clear();
|
|
|
|
// Create a new page
|
|
CPDF_Dictionary* pDestPageDict = dest()->CreateNewPage(curpage);
|
|
if (!pDestPageDict)
|
|
return false;
|
|
|
|
pDestPageDict->SetRectFor(pdfium::page_object::kMediaBox, destPageRect);
|
|
ByteString bsContent;
|
|
size_t iInnerPageMax =
|
|
std::min(iOuterPage + nPagesPerSheet, pageNums.size());
|
|
for (size_t i = iOuterPage; i < iInnerPageMax; ++i) {
|
|
auto* pSrcPageDict = src()->GetPageDictionary(pageNums[i] - 1);
|
|
if (!pSrcPageDict)
|
|
return false;
|
|
|
|
auto pSrcPage = pdfium::MakeRetain<CPDF_Page>(src(), pSrcPageDict);
|
|
pSrcPage->SetRenderCache(
|
|
pdfium::MakeUnique<CPDF_PageRenderCache>(pSrcPage.Get()));
|
|
NupPageSettings settings =
|
|
nupState.CalculateNewPagePosition(pSrcPage->GetPageSize());
|
|
bsContent += AddSubPage(pSrcPageDict, settings);
|
|
}
|
|
|
|
FinishPage(pDestPageDict, bsContent);
|
|
++curpage;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
ByteString CPDF_NPageToOneExporter::AddSubPage(
|
|
const CPDF_Dictionary* pSrcPageDict,
|
|
const NupPageSettings& settings) {
|
|
uint32_t dwSrcPageObjnum = pSrcPageDict->GetObjNum();
|
|
const auto it = m_SrcPageXObjectMap.find(dwSrcPageObjnum);
|
|
ByteString bsXObjectName = it != m_SrcPageXObjectMap.end()
|
|
? it->second
|
|
: MakeXObjectFromPage(pSrcPageDict);
|
|
|
|
CFX_Matrix matrix;
|
|
matrix.Scale(settings.scale, settings.scale);
|
|
matrix.Translate(settings.subPageStartPoint.x, settings.subPageStartPoint.y);
|
|
|
|
std::ostringstream contentStream;
|
|
contentStream << "q\n"
|
|
<< matrix.a << " " << matrix.b << " " << matrix.c << " "
|
|
<< matrix.d << " " << matrix.e << " " << matrix.f << " cm\n"
|
|
<< "/" << bsXObjectName << " Do Q\n";
|
|
return ByteString(contentStream);
|
|
}
|
|
|
|
ByteString CPDF_NPageToOneExporter::MakeXObjectFromPage(
|
|
const CPDF_Dictionary* pSrcPageDict) {
|
|
ASSERT(pSrcPageDict);
|
|
|
|
const CPDF_Object* pSrcContentObj =
|
|
pSrcPageDict->GetDirectObjectFor(pdfium::page_object::kContents);
|
|
|
|
CPDF_Stream* pNewXObject = dest()->NewIndirect<CPDF_Stream>(
|
|
nullptr, 0, dest()->New<CPDF_Dictionary>());
|
|
CPDF_Dictionary* pNewXObjectDict = pNewXObject->GetDict();
|
|
static const char kResourceString[] = "Resources";
|
|
if (!CopyInheritable(pNewXObjectDict, pSrcPageDict, kResourceString)) {
|
|
// Use a default empty resources if it does not exist.
|
|
pNewXObjectDict->SetNewFor<CPDF_Dictionary>(kResourceString);
|
|
}
|
|
uint32_t dwSrcPageObj = pSrcPageDict->GetObjNum();
|
|
uint32_t dwNewXobjectObj = pNewXObjectDict->GetObjNum();
|
|
AddObjectMapping(dwSrcPageObj, dwNewXobjectObj);
|
|
UpdateReference(pNewXObjectDict);
|
|
|
|
pNewXObjectDict->SetNewFor<CPDF_Name>("Type", "XObject");
|
|
pNewXObjectDict->SetNewFor<CPDF_Name>("Subtype", "Form");
|
|
pNewXObjectDict->SetNewFor<CPDF_Number>("FormType", 1);
|
|
pNewXObjectDict->SetRectFor("BBox", GetCropBox(pSrcPageDict));
|
|
// TODO(xlou): add matrix field to pNewXObjectDict.
|
|
|
|
if (pSrcContentObj) {
|
|
ByteString bsSrcContentStream;
|
|
const CPDF_Array* pSrcContentArray = ToArray(pSrcContentObj);
|
|
if (pSrcContentArray) {
|
|
for (size_t i = 0; i < pSrcContentArray->size(); ++i) {
|
|
const CPDF_Stream* pStream = pSrcContentArray->GetStreamAt(i);
|
|
auto pAcc = pdfium::MakeRetain<CPDF_StreamAcc>(pStream);
|
|
pAcc->LoadAllDataFiltered();
|
|
bsSrcContentStream += ByteString(pAcc->GetSpan());
|
|
bsSrcContentStream += "\n";
|
|
}
|
|
} else {
|
|
const CPDF_Stream* pStream = pSrcContentObj->AsStream();
|
|
auto pAcc = pdfium::MakeRetain<CPDF_StreamAcc>(pStream);
|
|
pAcc->LoadAllDataFiltered();
|
|
bsSrcContentStream = ByteString(pAcc->GetSpan());
|
|
}
|
|
pNewXObject->SetDataAndRemoveFilter(bsSrcContentStream.raw_span());
|
|
}
|
|
|
|
// TODO(xlou): A better name schema to avoid possible object name collision.
|
|
ByteString bsXObjectName = ByteString::Format("X%d", ++m_nObjectNumber);
|
|
m_XObjectNameToNumberMap[bsXObjectName] = pNewXObject->GetObjNum();
|
|
m_SrcPageXObjectMap[pSrcPageDict->GetObjNum()] = bsXObjectName;
|
|
return bsXObjectName;
|
|
}
|
|
|
|
void CPDF_NPageToOneExporter::FinishPage(CPDF_Dictionary* pDestPageDict,
|
|
const ByteString& bsContent) {
|
|
ASSERT(pDestPageDict);
|
|
|
|
CPDF_Dictionary* pRes =
|
|
pDestPageDict->GetDictFor(pdfium::page_object::kResources);
|
|
if (!pRes) {
|
|
pRes = pDestPageDict->SetNewFor<CPDF_Dictionary>(
|
|
pdfium::page_object::kResources);
|
|
}
|
|
|
|
CPDF_Dictionary* pPageXObject = pRes->GetDictFor("XObject");
|
|
if (!pPageXObject)
|
|
pPageXObject = pRes->SetNewFor<CPDF_Dictionary>("XObject");
|
|
|
|
for (auto& it : m_XObjectNameToNumberMap)
|
|
pPageXObject->SetNewFor<CPDF_Reference>(it.first, dest(), it.second);
|
|
|
|
auto pDict = dest()->New<CPDF_Dictionary>();
|
|
CPDF_Stream* pStream =
|
|
dest()->NewIndirect<CPDF_Stream>(nullptr, 0, std::move(pDict));
|
|
pStream->SetData(bsContent.raw_span());
|
|
pDestPageDict->SetNewFor<CPDF_Reference>(pdfium::page_object::kContents,
|
|
dest(), pStream->GetObjNum());
|
|
}
|
|
|
|
} // namespace
|
|
|
|
FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV FPDF_ImportPages(FPDF_DOCUMENT dest_doc,
|
|
FPDF_DOCUMENT src_doc,
|
|
FPDF_BYTESTRING pagerange,
|
|
int index) {
|
|
CPDF_Document* pDestDoc = CPDFDocumentFromFPDFDocument(dest_doc);
|
|
if (!dest_doc)
|
|
return false;
|
|
|
|
CPDF_Document* pSrcDoc = CPDFDocumentFromFPDFDocument(src_doc);
|
|
if (!pSrcDoc)
|
|
return false;
|
|
|
|
std::vector<uint32_t> page_numbers = GetPageNumbers(*pSrcDoc, pagerange);
|
|
if (page_numbers.empty())
|
|
return false;
|
|
|
|
CPDF_PageExporter exporter(pDestDoc, pSrcDoc);
|
|
return exporter.ExportPage(page_numbers, index);
|
|
}
|
|
|
|
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) {
|
|
CPDF_Document* pSrcDoc = CPDFDocumentFromFPDFDocument(src_doc);
|
|
if (!pSrcDoc)
|
|
return nullptr;
|
|
|
|
if (output_width <= 0 || output_height <= 0 || num_pages_on_x_axis <= 0 ||
|
|
num_pages_on_y_axis <= 0) {
|
|
return nullptr;
|
|
}
|
|
|
|
ScopedFPDFDocument output_doc(FPDF_CreateNewDocument());
|
|
if (!output_doc)
|
|
return nullptr;
|
|
|
|
CPDF_Document* pDestDoc = CPDFDocumentFromFPDFDocument(output_doc.get());
|
|
ASSERT(pDestDoc);
|
|
|
|
std::vector<uint32_t> page_numbers = GetPageNumbers(*pSrcDoc, ByteString());
|
|
if (page_numbers.empty())
|
|
return nullptr;
|
|
|
|
if (num_pages_on_x_axis == 1 && num_pages_on_y_axis == 1) {
|
|
CPDF_PageExporter exporter(pDestDoc, pSrcDoc);
|
|
if (!exporter.ExportPage(page_numbers, 0))
|
|
return nullptr;
|
|
return output_doc.release();
|
|
}
|
|
|
|
CPDF_NPageToOneExporter exporter(pDestDoc, pSrcDoc);
|
|
if (!exporter.ExportNPagesToOne(page_numbers,
|
|
CFX_SizeF(output_width, output_height),
|
|
num_pages_on_x_axis, num_pages_on_y_axis)) {
|
|
return nullptr;
|
|
}
|
|
return output_doc.release();
|
|
}
|
|
|
|
FPDF_EXPORT FPDF_BOOL FPDF_CALLCONV
|
|
FPDF_CopyViewerPreferences(FPDF_DOCUMENT dest_doc, FPDF_DOCUMENT src_doc) {
|
|
CPDF_Document* pDstDoc = CPDFDocumentFromFPDFDocument(dest_doc);
|
|
if (!pDstDoc)
|
|
return false;
|
|
|
|
CPDF_Document* pSrcDoc = CPDFDocumentFromFPDFDocument(src_doc);
|
|
if (!pSrcDoc)
|
|
return false;
|
|
|
|
const CPDF_Dictionary* pSrcDict = pSrcDoc->GetRoot();
|
|
pSrcDict = pSrcDict->GetDictFor("ViewerPreferences");
|
|
if (!pSrcDict)
|
|
return false;
|
|
|
|
CPDF_Dictionary* pDstDict = pDstDoc->GetRoot();
|
|
if (!pDstDict)
|
|
return false;
|
|
|
|
pDstDict->SetFor("ViewerPreferences", pSrcDict->CloneDirectObject());
|
|
return true;
|
|
}
|