390 lines
12 KiB
C++
390 lines
12 KiB
C++
// 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 <map>
|
|
#include <memory>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "core/fpdfapi/parser/cpdf_array.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_reference.h"
|
|
#include "core/fpdfapi/parser/cpdf_stream.h"
|
|
#include "core/fpdfapi/parser/cpdf_string.h"
|
|
#include "fpdfsdk/fsdk_define.h"
|
|
#include "third_party/base/ptr_util.h"
|
|
#include "third_party/base/stl_util.h"
|
|
|
|
namespace {
|
|
|
|
CPDF_Object* PageDictGetInheritableTag(CPDF_Dictionary* pDict,
|
|
const CFX_ByteString& bsSrcTag) {
|
|
if (!pDict || bsSrcTag.IsEmpty())
|
|
return nullptr;
|
|
if (!pDict->KeyExist("Parent") || !pDict->KeyExist("Type"))
|
|
return nullptr;
|
|
|
|
CPDF_Object* pType = pDict->GetObjectFor("Type")->GetDirect();
|
|
if (!ToName(pType))
|
|
return nullptr;
|
|
if (pType->GetString().Compare("Page"))
|
|
return nullptr;
|
|
|
|
CPDF_Dictionary* pp =
|
|
ToDictionary(pDict->GetObjectFor("Parent")->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("Parent"))
|
|
break;
|
|
pp = ToDictionary(pp->GetObjectFor("Parent")->GetDirect());
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool CopyInheritable(CPDF_Dictionary* pCurPageDict,
|
|
CPDF_Dictionary* pSrcPageDict,
|
|
const CFX_ByteString& key) {
|
|
if (pCurPageDict->KeyExist(key))
|
|
return true;
|
|
|
|
CPDF_Object* pInheritable = PageDictGetInheritableTag(pSrcPageDict, key);
|
|
if (!pInheritable)
|
|
return false;
|
|
|
|
pCurPageDict->SetFor(key, pInheritable->Clone());
|
|
return true;
|
|
}
|
|
|
|
bool ParserPageRangeString(CFX_ByteString rangstring,
|
|
std::vector<uint16_t>* pageArray,
|
|
int nCount) {
|
|
if (rangstring.IsEmpty())
|
|
return true;
|
|
|
|
rangstring.Remove(' ');
|
|
int nLength = rangstring.GetLength();
|
|
CFX_ByteString cbCompareString("0123456789-,");
|
|
for (int i = 0; i < nLength; ++i) {
|
|
if (cbCompareString.Find(rangstring[i]) == -1)
|
|
return false;
|
|
}
|
|
|
|
CFX_ByteString cbMidRange;
|
|
int nStringFrom = 0;
|
|
int nStringTo = 0;
|
|
while (nStringTo < nLength) {
|
|
nStringTo = rangstring.Find(',', nStringFrom);
|
|
if (nStringTo == -1)
|
|
nStringTo = nLength;
|
|
cbMidRange = rangstring.Mid(nStringFrom, nStringTo - nStringFrom);
|
|
int nMid = cbMidRange.Find('-');
|
|
if (nMid == -1) {
|
|
long lPageNum = atol(cbMidRange.c_str());
|
|
if (lPageNum <= 0 || lPageNum > nCount)
|
|
return false;
|
|
pageArray->push_back((uint16_t)lPageNum);
|
|
} else {
|
|
int nStartPageNum = atol(cbMidRange.Mid(0, nMid).c_str());
|
|
if (nStartPageNum == 0)
|
|
return false;
|
|
|
|
++nMid;
|
|
int nEnd = cbMidRange.GetLength() - nMid;
|
|
if (nEnd == 0)
|
|
return false;
|
|
|
|
int nEndPageNum = atol(cbMidRange.Mid(nMid, nEnd).c_str());
|
|
if (nStartPageNum < 0 || nStartPageNum > nEndPageNum ||
|
|
nEndPageNum > nCount) {
|
|
return false;
|
|
}
|
|
for (int i = nStartPageNum; i <= nEndPageNum; ++i) {
|
|
pageArray->push_back(i);
|
|
}
|
|
}
|
|
nStringFrom = nStringTo + 1;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
class CPDF_PageOrganizer {
|
|
public:
|
|
CPDF_PageOrganizer(CPDF_Document* pDestPDFDoc, CPDF_Document* pSrcPDFDoc);
|
|
~CPDF_PageOrganizer();
|
|
|
|
bool PDFDocInit();
|
|
bool ExportPage(const std::vector<uint16_t>& pageNums, int nIndex);
|
|
|
|
private:
|
|
using ObjectNumberMap = std::map<uint32_t, uint32_t>;
|
|
|
|
bool UpdateReference(CPDF_Object* pObj, ObjectNumberMap* pObjNumberMap);
|
|
uint32_t GetNewObjId(ObjectNumberMap* pObjNumberMap, CPDF_Reference* pRef);
|
|
|
|
CPDF_Document* m_pDestPDFDoc;
|
|
CPDF_Document* m_pSrcPDFDoc;
|
|
};
|
|
|
|
CPDF_PageOrganizer::CPDF_PageOrganizer(CPDF_Document* pDestPDFDoc,
|
|
CPDF_Document* pSrcPDFDoc)
|
|
: m_pDestPDFDoc(pDestPDFDoc), m_pSrcPDFDoc(pSrcPDFDoc) {}
|
|
|
|
CPDF_PageOrganizer::~CPDF_PageOrganizer() {}
|
|
|
|
bool CPDF_PageOrganizer::PDFDocInit() {
|
|
ASSERT(m_pDestPDFDoc);
|
|
ASSERT(m_pSrcPDFDoc);
|
|
|
|
CPDF_Dictionary* pNewRoot = m_pDestPDFDoc->GetRoot();
|
|
if (!pNewRoot)
|
|
return false;
|
|
|
|
CPDF_Dictionary* pDocInfoDict = m_pDestPDFDoc->GetInfo();
|
|
if (!pDocInfoDict)
|
|
return false;
|
|
|
|
pDocInfoDict->SetNewFor<CPDF_String>("Producer", "PDFium", false);
|
|
|
|
CFX_ByteString cbRootType = pNewRoot->GetStringFor("Type", "");
|
|
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 = m_pDestPDFDoc->NewIndirect<CPDF_Dictionary>();
|
|
pNewRoot->SetNewFor<CPDF_Reference>("Pages", m_pDestPDFDoc,
|
|
pNewPages->GetObjNum());
|
|
}
|
|
|
|
CFX_ByteString cbPageType = pNewPages->GetStringFor("Type", "");
|
|
if (cbPageType.IsEmpty())
|
|
pNewPages->SetNewFor<CPDF_Name>("Type", "Pages");
|
|
|
|
if (!pNewPages->GetArrayFor("Kids")) {
|
|
pNewPages->SetNewFor<CPDF_Number>("Count", 0);
|
|
pNewPages->SetNewFor<CPDF_Reference>(
|
|
"Kids", m_pDestPDFDoc,
|
|
m_pDestPDFDoc->NewIndirect<CPDF_Array>()->GetObjNum());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CPDF_PageOrganizer::ExportPage(const std::vector<uint16_t>& pageNums,
|
|
int nIndex) {
|
|
int curpage = nIndex;
|
|
auto pObjNumberMap = pdfium::MakeUnique<ObjectNumberMap>();
|
|
int nSize = pdfium::CollectionSize<int>(pageNums);
|
|
for (int i = 0; i < nSize; ++i) {
|
|
CPDF_Dictionary* pCurPageDict = m_pDestPDFDoc->CreateNewPage(curpage);
|
|
CPDF_Dictionary* pSrcPageDict = m_pSrcPDFDoc->GetPage(pageNums[i] - 1);
|
|
if (!pSrcPageDict || !pCurPageDict)
|
|
return false;
|
|
|
|
// Clone the page dictionary
|
|
for (const auto& it : *pSrcPageDict) {
|
|
const CFX_ByteString& cbSrcKeyStr = it.first;
|
|
if (cbSrcKeyStr == "Type" || cbSrcKeyStr == "Parent")
|
|
continue;
|
|
|
|
CPDF_Object* pObj = it.second.get();
|
|
pCurPageDict->SetFor(cbSrcKeyStr, pObj->Clone());
|
|
}
|
|
|
|
// inheritable item
|
|
// 1 MediaBox - required
|
|
if (!CopyInheritable(pCurPageDict, pSrcPageDict, "MediaBox")) {
|
|
// Search for "CropBox" in the source page dictionary,
|
|
// if it does not exists, use the default letter size.
|
|
CPDF_Object* pInheritable =
|
|
PageDictGetInheritableTag(pSrcPageDict, "CropBox");
|
|
if (pInheritable) {
|
|
pCurPageDict->SetFor("MediaBox", pInheritable->Clone());
|
|
} else {
|
|
// Make the default size to be letter size (8.5'x11')
|
|
CPDF_Array* pArray = pCurPageDict->SetNewFor<CPDF_Array>("MediaBox");
|
|
pArray->AddNew<CPDF_Number>(0);
|
|
pArray->AddNew<CPDF_Number>(0);
|
|
pArray->AddNew<CPDF_Number>(612);
|
|
pArray->AddNew<CPDF_Number>(792);
|
|
}
|
|
}
|
|
|
|
// 2 Resources - required
|
|
if (!CopyInheritable(pCurPageDict, pSrcPageDict, "Resources"))
|
|
return false;
|
|
|
|
// 3 CropBox - optional
|
|
CopyInheritable(pCurPageDict, pSrcPageDict, "CropBox");
|
|
// 4 Rotate - optional
|
|
CopyInheritable(pCurPageDict, pSrcPageDict, "Rotate");
|
|
|
|
// Update the reference
|
|
uint32_t dwOldPageObj = pSrcPageDict->GetObjNum();
|
|
uint32_t dwNewPageObj = pCurPageDict->GetObjNum();
|
|
(*pObjNumberMap)[dwOldPageObj] = dwNewPageObj;
|
|
UpdateReference(pCurPageDict, pObjNumberMap.get());
|
|
++curpage;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CPDF_PageOrganizer::UpdateReference(CPDF_Object* pObj,
|
|
ObjectNumberMap* pObjNumberMap) {
|
|
switch (pObj->GetType()) {
|
|
case CPDF_Object::REFERENCE: {
|
|
CPDF_Reference* pReference = pObj->AsReference();
|
|
uint32_t newobjnum = GetNewObjId(pObjNumberMap, pReference);
|
|
if (newobjnum == 0)
|
|
return false;
|
|
pReference->SetRef(m_pDestPDFDoc, newobjnum);
|
|
break;
|
|
}
|
|
case CPDF_Object::DICTIONARY: {
|
|
CPDF_Dictionary* pDict = pObj->AsDictionary();
|
|
auto it = pDict->begin();
|
|
while (it != pDict->end()) {
|
|
const CFX_ByteString& key = it->first;
|
|
CPDF_Object* pNextObj = it->second.get();
|
|
++it;
|
|
if (key == "Parent" || key == "Prev" || key == "First")
|
|
continue;
|
|
if (!pNextObj)
|
|
return false;
|
|
if (!UpdateReference(pNextObj, pObjNumberMap))
|
|
pDict->RemoveFor(key);
|
|
}
|
|
break;
|
|
}
|
|
case CPDF_Object::ARRAY: {
|
|
CPDF_Array* pArray = pObj->AsArray();
|
|
for (size_t i = 0; i < pArray->GetCount(); ++i) {
|
|
CPDF_Object* pNextObj = pArray->GetObjectAt(i);
|
|
if (!pNextObj)
|
|
return false;
|
|
if (!UpdateReference(pNextObj, pObjNumberMap))
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
case CPDF_Object::STREAM: {
|
|
CPDF_Stream* pStream = pObj->AsStream();
|
|
CPDF_Dictionary* pDict = pStream->GetDict();
|
|
if (!pDict)
|
|
return false;
|
|
if (!UpdateReference(pDict, pObjNumberMap))
|
|
return false;
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
uint32_t CPDF_PageOrganizer::GetNewObjId(ObjectNumberMap* pObjNumberMap,
|
|
CPDF_Reference* pRef) {
|
|
if (!pRef)
|
|
return 0;
|
|
|
|
uint32_t dwObjnum = pRef->GetRefObjNum();
|
|
uint32_t dwNewObjNum = 0;
|
|
const auto it = pObjNumberMap->find(dwObjnum);
|
|
if (it != pObjNumberMap->end())
|
|
dwNewObjNum = it->second;
|
|
if (dwNewObjNum)
|
|
return dwNewObjNum;
|
|
|
|
CPDF_Object* pDirect = pRef->GetDirect();
|
|
if (!pDirect)
|
|
return 0;
|
|
|
|
std::unique_ptr<CPDF_Object> pClone = pDirect->Clone();
|
|
if (CPDF_Dictionary* pDictClone = pClone->AsDictionary()) {
|
|
if (pDictClone->KeyExist("Type")) {
|
|
CFX_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 =
|
|
m_pDestPDFDoc->AddIndirectObject(std::move(pClone));
|
|
dwNewObjNum = pUnownedClone->GetObjNum();
|
|
(*pObjNumberMap)[dwObjnum] = dwNewObjNum;
|
|
if (!UpdateReference(pUnownedClone, pObjNumberMap))
|
|
return 0;
|
|
|
|
return dwNewObjNum;
|
|
}
|
|
|
|
DLLEXPORT FPDF_BOOL STDCALL 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<uint16_t> pageArray;
|
|
int nCount = pSrcDoc->GetPageCount();
|
|
if (pagerange) {
|
|
if (!ParserPageRangeString(pagerange, &pageArray, nCount))
|
|
return false;
|
|
} else {
|
|
for (int i = 1; i <= nCount; ++i) {
|
|
pageArray.push_back(i);
|
|
}
|
|
}
|
|
|
|
CPDF_PageOrganizer pageOrg(pDestDoc, pSrcDoc);
|
|
return pageOrg.PDFDocInit() && pageOrg.ExportPage(pageArray, index);
|
|
}
|
|
|
|
DLLEXPORT FPDF_BOOL STDCALL 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;
|
|
|
|
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;
|
|
}
|