1
0
mirror of https://github.com/ONLYOFFICE/core.git synced 2026-10-23 13:48:59 +03:00
Files
core/PdfFile/PdfEditor.cpp
2026-10-09 14:40:44 +03:00

7013 lines
220 KiB
C++

/*
* Copyright (C) Ascensio System SIA, 2009-2026
*
* This program is a free software product. You can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License (AGPL)
* version 3 as published by the Free Software Foundation, together with the
* additional terms provided in the LICENSE file.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
* details, see the GNU AGPL at: https://www.gnu.org/licenses/agpl-3.0.html
*
* You can contact Ascensio System SIA by email at info@onlyoffice.com
* or by postal mail at 20A-6 Ernesta Birznieka-Upisha Street, Riga,
* LV-1050, Latvia, European Union.
*
* The interactive user interfaces in modified versions of the Program
* are required to display Appropriate Legal Notices in accordance with
* Section 5 of the GNU AGPL version 3.
*
* No trademark rights are granted under this License.
*
* All non-code elements of the Product, including illustrations,
* icon sets, and technical writing content, are licensed under the
* Creative Commons Attribution-ShareAlike 4.0 International License:
* https://creativecommons.org/licenses/by-sa/4.0/legalcode
*
* This license applies only to such non-code elements and does not
* modify or replace the licensing terms applicable to the Program's
* source code, which remains licensed under the GNU Affero General
* Public License v3.
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
#include "PdfEditor.h"
#include "../DesktopEditor/common/Path.h"
#include "../DesktopEditor/graphics/commands/AnnotField.h"
#include "../DesktopEditor/graphics/pro/Graphics.h"
#include "SrcReader/Adaptors.h"
#include "SrcReader/PdfAnnot.h"
#include "SrcReader/PdfFont.h"
#include "SrcReader/RendererOutputDev.h"
#include "lib/xpdf/PDFDoc.h"
#include "lib/xpdf/AcroForm.h"
#include "lib/xpdf/TextString.h"
#include "lib/xpdf/Lexer.h"
#include "lib/xpdf/Parser.h"
#include "lib/xpdf/Outline.h"
#include "lib/xpdf/Link.h"
#include "lib/xpdf/Stream.h"
#include "lib/xpdf/GfxFont.h"
#include "lib/goo/GString.h"
#include "SrcWriter/Catalog.h"
#include "SrcWriter/EncryptDictionary.h"
#include "SrcWriter/Info.h"
#include "SrcWriter/ResourcesDictionary.h"
#include "SrcWriter/Streams.h"
#include "SrcWriter/Destination.h"
#include "SrcWriter/Outline.h"
#include "SrcWriter/GState.h"
#include "SrcWriter/RedactOutputDev.h"
#include "SrcWriter/OverlapOutputDev.h"
#include "SrcWriter/Image.h"
#include "SrcWriter/Font14.h"
#include "SrcWriter/FontCidTT.h"
#include <vector>
#include <climits>
#define _UC_AddToParent(val)\
{\
if (pParent->GetType() == PdfWriter::object_type_DICT)\
dynamic_cast<PdfWriter::CDictObject*>(pParent)->Add(sKey, (val));\
else if (pParent->GetType() == PdfWriter::object_type_ARRAY)\
dynamic_cast<PdfWriter::CArrayObject*>(pParent)->Add((val));\
}
namespace
{
bool SplitSkipDict(Object* obj, CObjectsManager* pManager, int nStartRefID)
{
CObjectGuard oTemp, oTemp2;
if (obj->dictLookup("Type", oTemp.get())->isName("Page"))
return true;
else if (oTemp->isName("Annot") && obj->dictLookupNF("P", oTemp2.get())->isRef())
{
// Skip annotations that don't have page created
PdfWriter::CObjectBase* pObj = pManager->GetObj(oTemp2->getRefNum() + nStartRefID);
if (!pObj)
return true;
}
oTemp->free(); oTemp2->free();
if (obj->dictLookup("S", oTemp.get())->isName("GoTo"))
{
oTemp->free();
if (obj->dictLookup("D", oTemp.get())->isArray() && oTemp->arrayGetLength() > 1 && oTemp->arrayGetNF(0, oTemp2.get())->isRef())
{
PdfWriter::CObjectBase* pObj = pManager->GetObj(oTemp2->getRefNum() + nStartRefID);
if (!pObj)
return true;
}
}
return false;
}
PdfWriter::CAnnotation* CreateAnnot(Object* oAnnot, Object* oType, PdfWriter::CXref* pXref)
{
PdfWriter::CAnnotation* pAnnot = NULL;
if (oType->isName("Text"))
pAnnot = new PdfWriter::CTextAnnotation(pXref);
else if (oType->isName("Ink"))
pAnnot = new PdfWriter::CInkAnnotation(pXref);
else if (oType->isName("Line"))
pAnnot = new PdfWriter::CLineAnnotation(pXref);
else if (oType->isName("Highlight") || oType->isName("Underline") || oType->isName("Squiggly") || oType->isName("StrikeOut"))
pAnnot = new PdfWriter::CTextMarkupAnnotation(pXref);
else if (oType->isName("Square") || oType->isName("Circle"))
pAnnot = new PdfWriter::CSquareCircleAnnotation(pXref);
else if (oType->isName("Polygon") || oType->isName("PolyLine"))
pAnnot = new PdfWriter::CPolygonLineAnnotation(pXref);
else if (oType->isName("FreeText"))
pAnnot = new PdfWriter::CFreeTextAnnotation(pXref);
else if (oType->isName("Caret"))
pAnnot = new PdfWriter::CCaretAnnotation(pXref);
else if (oType->isName("Stamp"))
pAnnot = new PdfWriter::CStampAnnotation(pXref);
else if (oType->isName("Redact"))
pAnnot = new PdfWriter::CRedactAnnotation(pXref);
else if (oType->isName("Popup"))
pAnnot = new PdfWriter::CPopupAnnotation(pXref);
else if (oType->isName("Link"))
pAnnot = new PdfWriter::CLinkAnnotation(pXref);
else if (oType->isName("Widget"))
{
char* sName = NULL;
Object oFT;
if (oAnnot->dictLookup("FT", &oFT)->isName())
sName = oFT.getName();
if (!sName)
{
Object oParent, oParent2;
oAnnot->dictLookup("Parent", &oParent);
for (int nDepth = 0; oParent.isDict() && nDepth < 50; ++nDepth) // a broken file may loop the Parent chain
{
if (oParent.dictLookup("FT", &oFT)->isName())
{
sName = oFT.getName();
break;
}
oFT.free();
oParent.dictLookup("Parent", &oParent2);
oParent.free();
oParent = oParent2;
}
oParent.free();
}
if (!sName)
{
oFT.free();
return new PdfWriter::CWidgetAnnotation(pXref, PdfWriter::EAnnotType::AnnotWidget);
}
if (strcmp("Btn", sName) == 0)
{
bool bPushButton = false;
oFT.free();
int nFf = 0;
if (oAnnot->dictLookup("Ff", &oFT)->isInt())
nFf = oFT.getInt();
if (!nFf)
{
Object oParent, oParent2;
oAnnot->dictLookup("Parent", &oParent);
for (int nDepth = 0; oParent.isDict() && nDepth < 50; ++nDepth) // a broken file may loop the Parent chain
{
if (oParent.dictLookup("Ff", &oFT)->isInt())
{
nFf = oFT.getInt();
break;
}
oFT.free();
oParent.dictLookup("Parent", &oParent2);
oParent.free();
oParent = oParent2;
}
oParent.free();
}
bPushButton = (bool)((nFf >> 16) & 1);
if (bPushButton)
pAnnot = new PdfWriter::CPushButtonWidget(pXref);
else
pAnnot = new PdfWriter::CCheckBoxWidget(pXref);
}
else if (strcmp("Tx", sName) == 0)
pAnnot = new PdfWriter::CTextWidget(pXref);
else if (strcmp("Ch", sName) == 0)
pAnnot = new PdfWriter::CChoiceWidget(pXref);
else if (strcmp("Sig", sName) == 0)
pAnnot = new PdfWriter::CSignatureWidget(pXref);
else
pAnnot = new PdfWriter::CWidgetAnnotation(pXref, PdfWriter::EAnnotType::AnnotWidget);
oFT.free();
}
return pAnnot;
}
PdfWriter::CExtGrState* CreateExtGState(Object* oState)
{
PdfWriter::CExtGrState* pState = new PdfWriter::CExtGrState(NULL);
return pState;
}
void AddWidgetParent(PdfWriter::CDocument* pDoc, CObjectsManager* pManager, PdfWriter::CObjectBase* pObj, int nDepth = 0)
{
// NULL - a deleted kid (a cleared proxy). nDepth: a widget is not registered,
// so GetParent does not stop a Parent loop through widgets
if (!pObj || pObj->GetType() != PdfWriter::object_type_DICT || nDepth > 50)
return;
PdfWriter::CDictObject* pDict = dynamic_cast<PdfWriter::CDictObject*>(pObj);
if (!pDict)
return;
int nID = pManager->FindObj(pObj);
if (nID < 0)
return;
if (pDict->GetDictType() == PdfWriter::dict_type_UNKNOWN)
{
if (pDoc->GetParent(nID))
return;
pDoc->AddParent(nID, pDict);
}
PdfWriter::CObjectBase* pObjParent = pDict->Get("Parent");
if (pObjParent && pObjParent->GetType() == PdfWriter::object_type_DICT)
AddWidgetParent(pDoc, pManager, pObjParent, nDepth + 1);
if (pDict->GetDictType() != PdfWriter::dict_type_UNKNOWN)
return;
PdfWriter::CObjectBase* pObjKids = pDict->Get("Kids");
if (!pObjKids || pObjKids->GetType() != PdfWriter::object_type_ARRAY)
return;
PdfWriter::CArrayObject* pKids = (PdfWriter::CArrayObject*)pObjKids;
for (int i = 0; i < pKids->GetCount(); ++i)
AddWidgetParent(pDoc, pManager, pKids->Get(i), nDepth + 1);
}
PdfWriter::CDictObject* GetWidgetParent(PDFDoc* pdfDoc, PdfWriter::CDocument* pDoc, Object* pParentRef, int nStartRefID)
{
if (!pParentRef || !pParentRef->isRef() || !pdfDoc)
return NULL;
PdfWriter::CDictObject* pParent = pDoc->GetParent(pParentRef->getRefNum() + nStartRefID);
if (pParent)
return pParent;
Object oParent;
if (!pParentRef->fetch(pdfDoc->getXRef(), &oParent)->isDict())
{
oParent.free();
return pParent;
}
PdfWriter::CXref* pXref = NULL;
pParent = new PdfWriter::CDictObject();
if (nStartRefID == 0)
{
pXref = new PdfWriter::CXref(pDoc, pParentRef->getRefNum());
pXref->Add(pParent, pParentRef->getRefGen());
}
if (!pDoc->EditParent(pXref, pParent, pParentRef->getRefNum() + nStartRefID))
{
RELEASEOBJECT(pXref);
oParent.free();
return NULL;
}
SDictConvertContext ctx;
for (int i = 0; i < oParent.dictGetLength(); ++i)
{
char* chKey = oParent.dictGetKey(i);
if (strcmp("Parent", chKey) == 0)
{
Object oParentRef;
oParent.dictGetValNF(i, &oParentRef);
PdfWriter::CDictObject* pParent2 = GetWidgetParent(pdfDoc, pDoc, &oParentRef, nStartRefID);
if (pParent2)
{
pParent->Add("Parent", pParent2);
oParentRef.free();
continue;
}
oParentRef.free();
}
Object oTemp;
oParent.dictGetValNF(i, &oTemp);
DictToCDictObjectUnified(&oTemp, ctx, pParent, chKey);
oTemp.free();
}
oParent.free();
return pParent;
}
// ── ctm ─────────────────────────────────────────────────────────────────────
struct CTMSnapshot
{
double m[6];
};
void CopyCTM(const double* src, double* dst)
{
for (int i = 0; i < 6; ++i)
dst[i] = src[i];
}
void SkipInlineImage(Parser* parser)
{
Object obj;
parser->getObj(&obj);
while (!obj.isEOF() && !obj.isCmd("ID"))
{
obj.free();
parser->getObj(&obj);
}
obj.free();
Stream* str = parser->getStream();
int c1 = str->getChar();
int c2 = str->getChar();
while (true)
{
if (c1 == EOF)
break;
if (c1 == 'E' && c2 == 'I')
{
int c3 = str->lookChar();
if (c3 == EOF || isspace(c3) || c3 == 0)
{
str->getChar();
break;
}
}
c1 = c2;
c2 = str->getChar();
}
}
void GetCTM(XRef* pXref, Object* oPage, double* dCTM)
{
if (!oPage || !oPage->isDict())
return;
Object oContents;
if (!oPage->dictLookup("Contents", &oContents) || (!oContents.isArray() && !oContents.isStream()))
{
oContents.free();
return;
}
dCTM[0] = 1; dCTM[1] = 0;
dCTM[2] = 0; dCTM[3] = 1;
dCTM[4] = 0; dCTM[5] = 0;
Parser* parser = new Parser(pXref, new Lexer(pXref, &oContents), gFalse);
int nNumArgs = 0;
Object oObj;
Object pArgs[maxArgs];
std::vector<CTMSnapshot> ctmStack;
parser->getObj(&oObj);
while (!oObj.isEOF())
{
if (oObj.isCmd())
{
if (oObj.isCmd("q"))
{
CTMSnapshot snap;
CopyCTM(dCTM, snap.m);
ctmStack.push_back(snap);
}
else if (oObj.isCmd("Q"))
{
if (!ctmStack.empty())
{
CopyCTM(ctmStack.back().m, dCTM);
ctmStack.pop_back();
}
}
else if (oObj.isCmd("cm") && nNumArgs > 5)
{
double a1 = dCTM[0];
double b1 = dCTM[1];
double c1 = dCTM[2];
double d1 = dCTM[3];
dCTM[0] = pArgs[0].getNum() * a1 + pArgs[1].getNum() * c1;
dCTM[1] = pArgs[0].getNum() * b1 + pArgs[1].getNum() * d1;
dCTM[2] = pArgs[2].getNum() * a1 + pArgs[3].getNum() * c1;
dCTM[3] = pArgs[2].getNum() * b1 + pArgs[3].getNum() * d1;
dCTM[4] = pArgs[4].getNum() * a1 + pArgs[5].getNum() * c1 + dCTM[4];
dCTM[5] = pArgs[4].getNum() * b1 + pArgs[5].getNum() * d1 + dCTM[5];
}
else if (oObj.isCmd("BI"))
SkipInlineImage(parser);
oObj.free();
for (int i = 0; i < nNumArgs; ++i)
pArgs[i].free();
nNumArgs = 0;
}
else if (nNumArgs < maxArgs)
pArgs[nNumArgs++] = oObj;
else
oObj.free();
parser->getObj(&oObj);
}
oObj.free();
for (int i = 0; i < nNumArgs; ++i)
pArgs[i].free();
RELEASEOBJECT(parser);
oContents.free();
}
void GetInvertCTM(double* dCTM, double* dInvCTM)
{
double det = dCTM[0] * dCTM[3] - dCTM[1] * dCTM[2];
if (fabs(det) > 1e-10)
{
double invDet = 1.0 / det;
dInvCTM[0] = dCTM[3] * invDet;
dInvCTM[1] = -dCTM[1] * invDet;
dInvCTM[2] = -dCTM[2] * invDet;
dInvCTM[3] = dCTM[0] * invDet;
dInvCTM[4] = (dCTM[2] * dCTM[5] - dCTM[3] * dCTM[4]) * invDet;
dInvCTM[5] = (dCTM[1] * dCTM[4] - dCTM[0] * dCTM[5]) * invDet;
}
}
void DrawAnnot(Object* oAnnot, PdfWriter::CPage* pPage, PdfWriter::CDocument* pDoc, XRef* xref, CObjectsManager* pManager, int nStartRefID)
{
Object oAP, oAPN, oObj;
if (!oAnnot->dictLookup("AP", &oAP)->isDict() || !oAP.dictLookup("N", &oAPN)->isStream())
{
oAP.free();
Object oTemp;
if (!oAPN.isDict() || !oAnnot->dictLookup("AS", &oObj)->isName() || !oAPN.dictLookup(oObj.getName(), &oTemp)->isStream())
{
oTemp.free(); oAPN.free(); oObj.free();
return;
}
oAPN.free();
oAPN = oTemp;
}
oAP.free();
Object oTemp;
double m[6] = { 1, 0, 0, 1, 0, 0 }, bbox[4] = { 0, 0, 0, 0 }, rect[4] = { 0, 0, 0, 0 };
if (oAnnot->dictLookup("Rect", &oTemp)->isArray() && oTemp.arrayGetLength() == 4)
{
for (int j = 0; j < 4; ++j)
{
oTemp.arrayGet(j, &oObj);
rect[j] = oObj.isNum() ? oObj.getNum() : 0;
oObj.free();
}
if (rect[0] > rect[2])
std::swap(rect[0], rect[2]);
if (rect[1] > rect[3])
std::swap(rect[1], rect[3]);
}
oTemp.free();
PdfWriter::CXObject* pForm = pDoc->CreateForm();
pPage->GrSave();
pPage->ExecuteXObject(pForm);
pPage->GrRestore();
SDictConvertContext ctx(pDoc, xref, pManager, nStartRefID);
Dict* pODict = oAPN.streamGetDict();
for (int nIndex = 0; nIndex < pODict->getLength(); ++nIndex)
{
char* chKey = pODict->getKey(nIndex);
if (strcmp("Length", chKey) == 0 || strcmp("Filter", chKey) == 0)
{
oTemp.free();
continue;
}
else if (strcmp("BBox", chKey) == 0 && pODict->getVal(nIndex, &oTemp)->isArray() && oTemp.arrayGetLength() == 4)
{
for (int j = 0; j < 4; ++j)
{
oTemp.arrayGet(j, &oObj);
bbox[j] = oObj.isNum() ? oObj.getNum() : 0;
oObj.free();
}
if (bbox[0] > bbox[2])
std::swap(bbox[0], bbox[2]);
if (bbox[1] > bbox[3])
std::swap(bbox[1], bbox[3]);
}
else if (strcmp("Matrix", chKey) == 0 && pODict->getVal(nIndex, &oTemp)->isArray() && oTemp.arrayGetLength() == 6)
{
for (int j = 0; j < 6; ++j)
{
oTemp.arrayGet(j, &oObj);
m[j] = oObj.getNum();
oObj.free();
}
}
oTemp.free();
pODict->getValNF(nIndex, &oTemp);
PdfWriter::CObjectBase* pBase = DictToCDictObjectUnified(&oTemp, ctx);
pForm->Add(chKey, pBase);
oTemp.free();
}
double formXMin, formYMin, formXMax, formYMax, x, y, sx, sy;
x = bbox[0] * m[0] + bbox[1] * m[2] + m[4];
y = bbox[0] * m[1] + bbox[1] * m[3] + m[5];
formXMin = formXMax = x;
formYMin = formYMax = y;
x = bbox[0] * m[0] + bbox[3] * m[2] + m[4];
y = bbox[0] * m[1] + bbox[3] * m[3] + m[5];
if (x < formXMin)
formXMin = x;
else if (x > formXMax)
formXMax = x;
if (y < formYMin)
formYMin = y;
else if (y > formYMax)
formYMax = y;
x = bbox[2] * m[0] + bbox[1] * m[2] + m[4];
y = bbox[2] * m[1] + bbox[1] * m[3] + m[5];
if (x < formXMin)
formXMin = x;
else if (x > formXMax)
formXMax = x;
if (y < formYMin)
formYMin = y;
else if (y > formYMax)
formYMax = y;
x = bbox[2] * m[0] + bbox[3] * m[2] + m[4];
y = bbox[2] * m[1] + bbox[3] * m[3] + m[5];
if (x < formXMin)
formXMin = x;
else if (x > formXMax)
formXMax = x;
if (y < formYMin)
formYMin = y;
else if (y > formYMax)
formYMax = y;
if (formXMin == formXMax)
sx = 1;
else
sx = (rect[2] - rect[0]) / (formXMax - formXMin);
if (formYMin == formYMax)
sy = 1;
else
sy = (rect[3] - rect[1]) / (formYMax - formYMin);
double tx = -formXMin * sx + rect[0];
double ty = -formYMin * sy + rect[1];
m[0] *= sx;
m[1] *= sy;
m[2] *= sx;
m[3] *= sy;
m[4] = m[4] * sx + tx;
m[5] = m[5] * sy + ty;
pForm->Add("Matrix", PdfWriter::CArrayObject::CreateMatrix(m));
PdfWriter::CStream* pStream = pForm->GetStream();
Stream* pOStream = oAPN.getStream();
pOStream->reset();
int nChar = pOStream->getChar();
while (nChar != EOF)
{
pStream->WriteChar(nChar);
nChar = pOStream->getChar();
}
oAPN.free();
}
void CObjectBaseToObject(PdfWriter::CObjectBase* pObj, Object* pOut)
{
if (pObj && pObj->GetType() == PdfWriter::object_type_PROXY)
pObj = ((PdfWriter::CProxyObject*)pObj)->Get();
if (!pObj)
{
pOut->initNull();
return;
}
switch (pObj->GetType())
{
case PdfWriter::object_type_BOOLEAN:
pOut->initBool(((PdfWriter::CBoolObject*)pObj)->Get() ? gTrue : gFalse);
break;
case PdfWriter::object_type_NUMBER:
pOut->initInt(((PdfWriter::CNumberObject*)pObj)->Get());
break;
case PdfWriter::object_type_REAL:
pOut->initReal(((PdfWriter::CRealObject*)pObj)->Get());
break;
case PdfWriter::object_type_NAME:
pOut->initName(((PdfWriter::CNameObject*)pObj)->Get());
break;
case PdfWriter::object_type_STRING:
{
PdfWriter::CStringObject* pStr = (PdfWriter::CStringObject*)pObj;
pOut->initString(new GString((const char*)pStr->GetString(), (int)pStr->GetLength(), true));
break;
}
case PdfWriter::object_type_BINARY:
{
PdfWriter::CBinaryObject* pBin = (PdfWriter::CBinaryObject*)pObj;
pOut->initString(new GString((const char*)pBin->GetValue(), (int)pBin->GetLength(), true));
break;
}
case PdfWriter::object_type_ARRAY:
{
PdfWriter::CArrayObject* pArr = (PdfWriter::CArrayObject*)pObj;
pOut->initArray(NULL);
for (int i = 0; i < pArr->GetCount(); ++i)
{
Object oElem;
CObjectBaseToObject(pArr->Get(i), &oElem);
pOut->arrayAdd(&oElem);
}
break;
}
case PdfWriter::object_type_DICT:
{
PdfWriter::CDictObject* pDict = dynamic_cast<PdfWriter::CDictObject*>(pObj);
if (!pDict)
{
pOut->initNull();
break;
}
pDict->BeforeWrite();
Object oDict;
oDict.initDict((XRef*)NULL);
const std::map<std::string, PdfWriter::CObjectBase*>& mEntries = pDict->GetDict();
for (std::map<std::string, PdfWriter::CObjectBase*>::const_iterator it = mEntries.begin(); it != mEntries.end(); ++it)
{
Object oVal;
CObjectBaseToObject(it->second, &oVal);
oDict.dictAdd(copyString(it->first.c_str()), &oVal);
}
PdfWriter::CStream* pWStream = pDict->GetStream();
if (!pWStream)
{
*pOut = oDict;
break;
}
// MemStream frees the buffer itself (needFree=true below), so it must come from xpdf's
// own allocator (gmalloc/gfree), not new[]/delete[].
unsigned int unSize = pWStream->Size();
BYTE* pBuf = unSize ? (BYTE*)gmalloc((int)unSize) : NULL;
if (pBuf)
{
pWStream->Seek(0, PdfWriter::SeekSet);
unsigned int unRead = unSize;
pWStream->Read(pBuf, &unRead);
}
pOut->initStream(new MemStream((char*)pBuf, 0, unSize, &oDict, gTrue));
break;
}
default:
pOut->initNull();
break;
}
}
void DrawFormThroughRedact(Object* pReaderAppearance, const double rect[4], PdfWriter::CXObject* pForm,
PdfWriter::CPage* pPage, CPdfWriter* pWriter, PDFDoc* pPDFDocument, CObjectsManager* pManager,
int nStartRefID, int nRedactInfo, const std::vector<double>& arrRedact, bool bExecuteOnPage = true)
{
PdfWriter::CDocument* pDoc = pWriter->GetDocument();
XRef* xref = pPDFDocument->getXRef();
PdfWriter::CResourcesDict* pFormResources = dynamic_cast<PdfWriter::CResourcesDict*>(pForm->Get("Resources"));
PdfWriter::CPage* pRendererPrevPage = pWriter->GetPage();
PdfWriter::CPage* pDocPrevPage = pDoc->GetCurPage();
PdfWriter::CPage* pFakePage = new PdfWriter::CPage(pDoc);
pFakePage->SetStream(pForm->GetStream());
PdfWriter::RedactOutputDev oRedactOut(pWriter, pManager, nStartRefID, nRedactInfo);
oRedactOut.NewPDF(xref);
oRedactOut.SetRedact(arrRedact);
oRedactOut.SetOverlapInfo({}, NULL, NULL, {});
oRedactOut.StartOnPage(pFakePage, pFormResources);
double arrIdentity[6] = { 1, 0, 0, 1, 0, 0 };
oRedactOut.setDefaultCTM(arrIdentity);
PDFRectangle oBox(0, 0, pPage->GetWidth(), pPage->GetHeight());
Gfx* gfx = new Gfx(pPDFDocument, &oRedactOut, NULL, &oBox, NULL);
gfx->saveState();
gfx->drawAnnot(pReaderAppearance, NULL, rect[0], rect[1], rect[2], rect[3]);
gfx->endOfPage();
RELEASEOBJECT(gfx);
pWriter->EditPage(pRendererPrevPage);
pDoc->SetCurPage(pDocPrevPage);
RELEASEOBJECT(pFakePage);
if (!bExecuteOnPage)
return;
PdfWriter::CResourcesDict* pPageResources = pPage->GetResourcesItem();
if (!pPageResources)
{
pPage->AddResource();
pPageResources = pPage->GetResourcesItem();
}
pPage->GrSave();
pPage->ExecuteXObject(pPageResources->GetXObjectName(pForm));
pPage->GrRestore();
}
void DrawAnnotRedacted(Object* oAnnot, PdfWriter::CPage* pPage, PdfWriter::CDocument* pDoc, CPdfWriter* pWriter, PDFDoc* pPDFDocument,
XRef* xref, CObjectsManager* pManager, int nStartRefID, int nRedactInfo, const std::vector<double>& arrRedact)
{
Object oAP, oAPN, oObj;
if (!oAnnot->dictLookup("AP", &oAP)->isDict() || !oAP.dictLookup("N", &oAPN)->isStream())
{
oAP.free();
Object oTemp;
if (!oAPN.isDict() || !oAnnot->dictLookup("AS", &oObj)->isName() || !oAPN.dictLookup(oObj.getName(), &oTemp)->isStream())
{
oTemp.free(); oAPN.free(); oObj.free();
return;
}
oAPN.free();
oAPN = oTemp;
}
oAP.free();
Object oTemp;
double rect[4] = { 0, 0, 0, 0 };
if (oAnnot->dictLookup("Rect", &oTemp)->isArray() && oTemp.arrayGetLength() == 4)
{
for (int j = 0; j < 4; ++j)
{
oTemp.arrayGet(j, &oObj);
rect[j] = oObj.isNum() ? oObj.getNum() : 0;
oObj.free();
}
if (rect[0] > rect[2])
std::swap(rect[0], rect[2]);
if (rect[1] > rect[3])
std::swap(rect[1], rect[3]);
}
oTemp.free();
if (rect[0] == rect[2] || rect[1] == rect[3])
{
oAPN.free();
return;
}
std::vector<PdfWriter::CPoint> arrRectPolygon =
{
PdfWriter::CPoint(rect[0], rect[1]), PdfWriter::CPoint(rect[0], rect[3]),
PdfWriter::CPoint(rect[2], rect[3]), PdfWriter::CPoint(rect[2], rect[1])
};
if (!arrRedact.empty() && PdfWriter::CheckFullRedact(arrRedact, arrRectPolygon))
{
oAPN.free();
return;
}
PdfWriter::CXObject* pForm = pDoc->CreateForm();
pForm->Add("Type", "XObject");
pForm->Add("Subtype", "Form");
pForm->Add("FormType", 1);
pForm->Add("BBox", PdfWriter::CArrayObject::CreateBox(rect[0], rect[1], rect[2], rect[3]));
PdfWriter::CResourcesDict* pFormResources = pDoc->CreateResourcesDict(true, false);
Object oAPResources;
if (oAPN.streamGetDict()->lookup("Resources", &oAPResources)->isDict())
{
SDictConvertContext resCtx(pDoc, xref, pManager, nStartRefID);
for (int j = 0; j < oAPResources.dictGetLength(); ++j)
{
Object oResItem;
char* chKey = oAPResources.dictGetKey(j);
oAPResources.dictGetValNF(j, &oResItem);
pFormResources->Add(chKey, DictToCDictObjectUnified(&oResItem, resCtx));
oResItem.free();
}
pFormResources->Fix();
}
oAPResources.free();
pForm->Add("Resources", pFormResources);
DrawFormThroughRedact(&oAPN, rect, pForm, pPage, pWriter, pPDFDocument, pManager, nStartRefID, nRedactInfo, arrRedact);
oAPN.free();
}
void DrawAnnotAppearanceRedacted(PdfWriter::CAnnotAppearanceObject* pAPN, const double rect[4], PdfWriter::CPage* pPage,
PdfWriter::CDocument* pDoc, CPdfWriter* pWriter, PDFDoc* pPDFDocument,
CObjectsManager* pManager, int nRedactInfo, const std::vector<double>& arrRedact,
PdfWriter::CXObject* pExistingForm = NULL)
{
if (rect[0] == rect[2] || rect[1] == rect[3])
return;
PdfWriter::CXObject* pForm = pExistingForm ? pExistingForm : pDoc->CreateForm();
pForm->Add("Type", "XObject");
pForm->Add("Subtype", "Form");
pForm->Add("FormType", 1);
pForm->Add("BBox", PdfWriter::CArrayObject::CreateBox(rect[0], rect[1], rect[2], rect[3]));
pForm->Add("Resources", pAPN->Get("Resources"));
Object oReaderAPN;
CObjectBaseToObject(pAPN, &oReaderAPN);
DrawFormThroughRedact(&oReaderAPN, rect, pForm, pPage, pWriter, pPDFDocument, pManager, 0, nRedactInfo, arrRedact, pExistingForm == NULL);
oReaderAPN.free();
}
PdfWriter::CObjectBase* FillEncryptDictFromXRef(XRef* xref, Object* pTrailerDict, PdfWriter::CEncryptDict* pEncryptDict, const SDictConvertContext& ctx)
{
PdfWriter::CObjectBase* pID = NULL;
xref->offEncrypted();
{
CObjectGuard encrypt;
if (pTrailerDict->dictLookup("Encrypt", encrypt.get()) && encrypt->isDict())
{
for (int i = 0; i < encrypt->dictGetLength(); ++i)
{
CObjectGuard oTemp;
char* chKey = encrypt->dictGetKey(i);
encrypt->dictGetValNF(i, oTemp.get());
pEncryptDict->Add(chKey, DictToCDictObjectUnified(oTemp.get(), ctx));
}
}
if (!pEncryptDict->Get("Length"))
pEncryptDict->Add("Length", 40);
CObjectGuard ID;
if (pTrailerDict->dictLookup("ID", ID.get()) && ID->isArray())
{
CObjectGuard ID1;
if (ID->arrayGet(0, ID1.get()) && ID1->isString())
{
pID = DictToCDictObjectUnified(ID1.get(), ctx);
pEncryptDict->Add("ID", pID);
}
}
}
xref->onEncrypted();
return pID;
}
bool MakeDocWritable(PDFDoc* pPDFDocument, const wchar_t* wsPassword, GBool bWritable)
{
GString* owner_pswd = wsPassword ? NSStrings::CreateString(wsPassword) : NULL;
GString* user_pswd = wsPassword ? NSStrings::CreateString(wsPassword) : NULL;
GBool bOk = pPDFDocument->makeWritable(bWritable, owner_pswd, user_pswd);
RELEASEOBJECT(owner_pswd);
RELEASEOBJECT(user_pswd);
return bOk == gTrue;
}
struct SFontScanContext
{
PDFDoc* pPDFDocument;
NSFonts::IFontManager* pFontManager;
PdfWriter::CDocument* pDoc;
CPdfWriter* pWriter;
CObjectsManager* pObjManager;
PdfReader::CPdfFontList* pFontList;
int nStartRefID;
};
bool VisitAndEmbedFont(const SFontScanContext& ctx, const PdfReader::SFontVisitContext& oFontCtx)
{
XRef* xref = ctx.pPDFDocument->getXRef();
if (!oFontCtx.pFontRefNF->isRef())
return false;
Ref oFontRef = oFontCtx.pFontRefNF->getRef();
GfxFont* gfxFont = GfxFont::makeFont(xref, "F", oFontRef, oFontCtx.pFontDict);
if (!gfxFont)
return false;
Ref oEmbRef;
const std::wstring wsFontBaseName = NSStrings::GetStringFromUTF32(gfxFont->getName());
GfxFontLoc* pFontLoc = NULL;
const bool bLocatable = gfxFont->getEmbeddedFontID(&oEmbRef) ||
PdfReader::IsBaseFont(wsFontBaseName) ||
((pFontLoc = gfxFont->locateFont(xref, false)) && NSStrings::GetStringFromUTF32(pFontLoc->path).length());
if (bLocatable)
{
std::wstring wsFileName, wsFontName;
PdfReader::GetFont(xref, ctx.pFontManager, ctx.pFontList, gfxFont, wsFileName, wsFontName);
if (!PdfReader::IsBaseFont(wsFontBaseName))
{
PdfReader::TFontEntry pFontEntry;
if (ctx.pFontList->GetFont(&oFontRef, &pFontEntry))
{
PdfWriter::CFontEmbedded* pFont = ctx.pDoc->FindFontEmbedded(wsFileName, 0);
if (pFont)
{
pFont->UpdateKey(oFontCtx.sFontKey);
}
else
{
CObjectGuard oFontResolved;
xref->fetch(oFontRef.num, oFontRef.gen, oFontResolved.get());
std::map<unsigned int, unsigned int> mCodeToWidth, mCodeToUnicode, mCodeToGID;
const int nDW = PdfReader::CollectFontWidths(oFontCtx.pFontDict, mCodeToWidth);
for (int j = 0; j < (int)pFontEntry.unLenUnicode; ++j)
if (pFontEntry.pCodeToUnicode[j])
mCodeToUnicode[j] = pFontEntry.pCodeToUnicode[j];
for (int j = 0; j < (int)pFontEntry.unLenGID; ++j)
if (pFontEntry.pCodeToGID[j])
mCodeToGID[j] = pFontEntry.pCodeToGID[j];
ctx.pWriter->AddFont(L"Embedded: " + wsFontName, false, false, wsFileName, 0);
PdfWriter::CObjectBase* pObj = ctx.pObjManager->GetObj(oFontRef.num + ctx.nStartRefID);
if (!pObj)
{
pObj = new PdfWriter::CObjectBase();
pObj->SetRef(oFontRef.num, oFontRef.gen);
}
pFont = ctx.pDoc->CreateFontEmbedded(wsFileName, 0, oFontCtx.sFontKey,
static_cast<PdfWriter::EFontType>(gfxFont->getType()), pObj,
mCodeToWidth, mCodeToUnicode, mCodeToGID);
pFont->SetDW(nDW);
}
}
}
}
RELEASEOBJECT(pFontLoc);
RELEASEOBJECT(gfxFont);
return false;
}
void ScanFonts(PDFDoc* pPDFDocument, Dict* pResources, CPdfWriter* pWriter, CObjectsManager* pObjManager, CPdfReader* pReader)
{
if (!pResources)
return;
SFontScanContext ctx;
ctx.pPDFDocument = pPDFDocument;
ctx.pFontManager = pReader->GetFontManager();
ctx.pDoc = pWriter->GetDocument();
ctx.pWriter = pWriter;
ctx.pObjManager = pObjManager;
ctx.pFontList = pReader->GetFontList(pPDFDocument);
ctx.nStartRefID = pReader->GetStartRefID(pPDFDocument);
auto fnVisit = [&ctx](const PdfReader::SFontVisitContext& oFontCtx) -> bool
{
return VisitAndEmbedFont(ctx, oFontCtx);
};
std::vector<int> arrUniqueResources;
PdfReader::ScanResourceFonts(pResources, 0, arrUniqueResources, fnVisit);
pReader->SetFonts(ctx.pFontList);
}
void CreateOutlines(PDFDoc* pdfDoc, PdfWriter::CDocument* pDoc, OutlineItem* pOutlineItem, PdfWriter::COutline* pParent)
{
std::string sTitle = NSStringExt::CConverter::GetUtf8FromUTF32(pOutlineItem->getTitle(), pOutlineItem->getTitleLength());
PdfWriter::COutline* pOutline = pDoc->CreateOutline(pParent, sTitle.c_str());
PdfWriter::CDestination* pDest = NULL;
LinkAction* pLinkAction = pOutlineItem->getAction();
if (pLinkAction && pLinkAction->getKind() == actionGoTo)
{
GString* str = ((LinkGoTo*)pLinkAction)->getNamedDest();
LinkDest* pLinkDest = str ? pdfDoc->findDest(str) : ((LinkGoTo*)pLinkAction)->getDest();
if (pLinkDest)
{
int pg;
if (pLinkDest->isPageRef())
{
Ref pageRef = pLinkDest->getPageRef();
pg = pdfDoc->findPage(pageRef.num, pageRef.gen);
}
else
pg = pLinkDest->getPageNum();
if (pg == 0)
pg = 1;
Ref* pPageRef = pdfDoc->getCatalog()->getPageRef(pg);
PdfWriter::CObjectBase* pPageD = pDoc->GetEditPage(--pg);
PdfWriter::CStructureTreeRoot* pSTR = pDoc->GetStructureTreeRoot();
if (pPageD && pPageD->GetType() == PdfWriter::object_type_DICT)
{
if (pSTR)
{
PdfWriter::CDictObject* pStructAnchor = pSTR->CreateDestAnchor((PdfWriter::CDictObject*)pPageD, ((PdfWriter::CPage*)pPageD)->GetOrCreateDestAnchorMCID());
pDest = pDoc->CreateDestination(pStructAnchor, true);
}
else
pDest = pDoc->CreateDestination(pPageD, true);
}
else if (!pPageD && pPageRef->num > 0)
{
PdfWriter::CObjectBase* pBase = new PdfWriter::CObjectBase();
pBase->SetRef(pPageRef->num, pPageRef->gen);
pPageD = new PdfWriter::CProxyObject(pBase, true);
pDest = pDoc->CreateDestination(pPageD, true);
}
switch (pLinkDest->getKind())
{
case destXYZ:
{
pDest->SetXYZ(pLinkDest->getLeft(), pLinkDest->getTop(), pLinkDest->getZoom());
break;
}
case destFit:
{
pDest->SetFit();
break;
}
case destFitH:
{
pDest->SetFitH(pLinkDest->getTop());
break;
}
case destFitV:
{
pDest->SetFitV(pLinkDest->getLeft());
break;
}
case destFitR:
{
pDest->SetFitR(pLinkDest->getLeft(), pLinkDest->getBottom(), pLinkDest->getRight(), pLinkDest->getTop());
break;
}
case destFitB:
{
pDest->SetFitB();
break;
}
case destFitBH:
{
pDest->SetFitBH(pLinkDest->getTop());
break;
}
case destFitBV:
{
pDest->SetFitBV(pLinkDest->getLeft());
break;
}
}
}
if (str)
RELEASEOBJECT(pLinkDest);
}
if (pDest)
pOutline->SetDestination(pDest);
pOutlineItem->open();
GList* pList = pOutlineItem->getKids();
if (!pList)
{
pOutlineItem->close();
return;
}
for (int i = 0, num = pList->getLength(); i < num; i++)
{
OutlineItem* pOutlineItemKid = (OutlineItem*)pList->get(i);
if (pOutlineItemKid)
CreateOutlines(pdfDoc, pDoc, pOutlineItemKid, pOutline);
}
pOutlineItem->close();
}
}
HRESULT _ChangePassword(const std::wstring& wsPath, const std::wstring& wsPassword, CPdfReader* _pReader, CPdfWriter* _pWriter)
{
if (!_pReader || !_pWriter)
return S_FALSE;
PDFDoc* pPDFDocument = _pReader->GetPDFDocument(0);
if (!pPDFDocument)
return S_FALSE;
XRef* xref = pPDFDocument->getXRef();
if (!xref)
return S_FALSE;
Object* trailerDict = xref->getTrailerDict();
if (!trailerDict)
return S_FALSE;
PdfWriter::CDocument* pDoc = _pWriter->GetDocument();
PdfWriter::CXref* pXref = new PdfWriter::CXref(pDoc, 0);
PdfWriter::CXref* m_pXref = new PdfWriter::CXref(pDoc, xref->getNumObjects()); // For new objects
if (!xref || !pDoc || !pXref || !m_pXref)
{
RELEASEOBJECT(pXref);
RELEASEOBJECT(m_pXref);
return S_FALSE;
}
pXref->SetPrev(m_pXref);
SDictConvertContext ctx;
for (int i = 0; i < xref->getSize(); ++i)
{
XRefEntry* pEntry = xref->getEntry(i);
if (pEntry->type == xrefEntryFree)
continue;
if (i != pXref->GetSizeXRef())
{
PdfWriter::CXref* pXref2 = new PdfWriter::CXref(pDoc, i);
pXref2->SetPrev(pXref);
pXref = pXref2;
}
Object oTemp;
xref->fetch(i, pEntry->type == xrefEntryCompressed ? 0 : pEntry->gen, &oTemp);
PdfWriter::CObjectBase* pObj = NULL;
switch (oTemp.getType())
{
case objBool:
{
pObj = new PdfWriter::CBoolObject(oTemp.getBool());
break;
}
case objInt:
{
pObj = new PdfWriter::CNumberObject(oTemp.getInt());
break;
}
case objReal:
{
pObj = new PdfWriter::CRealObject(oTemp.getReal());
break;
}
case objString:
{
GString* str = oTemp.getString();
if (str->isBinary())
{
int nLength = str->getLength();
BYTE* arrId = new BYTE[nLength];
for (int nIndex = 0; nIndex < nLength; ++nIndex)
arrId[nIndex] = str->getChar(nIndex);
pObj = new PdfWriter::CBinaryObject(arrId, nLength, false);
}
else
{
TextString* s = new TextString(str);
std::string sValue = NSStringExt::CConverter::GetUtf8FromUTF32(s->getUnicode(), s->getLength());
pObj = new PdfWriter::CStringObject(sValue.c_str(), !s->isPDFDocEncoding());
delete s;
}
break;
}
case objName:
{
pObj = new PdfWriter::CNameObject(oTemp.getName());
break;
}
case objNull:
{
pObj = new PdfWriter::CNullObject();
break;
}
case objArray:
{
pObj = new PdfWriter::CArrayObject();
for (int nIndex = 0; nIndex < oTemp.arrayGetLength(); ++nIndex)
{
Object oT;
oTemp.arrayGetNF(nIndex, &oT);
DictToCDictObjectUnified(&oT, ctx, pObj);
oT.free();
}
break;
}
case objDict:
{
pObj = new PdfWriter::CDictObject();
for (int nIndex = 0; nIndex < oTemp.dictGetLength(); ++nIndex)
{
Object oT;
char* chKey = oTemp.dictGetKey(nIndex);
oTemp.dictGetValNF(nIndex, &oT);
DictToCDictObjectUnified(&oT, ctx, pObj, chKey);
oT.free();
}
break;
}
case objRef:
{
PdfWriter::CObjectBase* pBase = new PdfWriter::CObjectBase();
pBase->SetRef(oTemp.getRefNum(), oTemp.getRefGen());
pObj = new PdfWriter::CProxyObject(pBase, true);
break;
}
case objStream:
{
Dict* pDict = oTemp.streamGetDict();
Object oObjStm;
if (pDict->lookup("Type", &oObjStm)->isName("ObjStm"))
{
oObjStm.free();
break;
}
oObjStm.free();
PdfWriter::CDictObject* pDObj = new PdfWriter::CDictObject();
pObj = pDObj;
int nLength = 0;
for (int nIndex = 0; nIndex < pDict->getLength(); ++nIndex)
{
Object oT;
char* chKey = pDict->getKey(nIndex);
if (strcmp("Length", chKey) == 0)
{
Object oLength;
nLength = pDict->getVal(nIndex, &oLength)->isInt() ? oLength.getInt() : 0;
oLength.free();
continue;
}
pDict->getValNF(nIndex, &oT);
DictToCDictObjectUnified(&oT, ctx, pObj, chKey);
oT.free();
}
PdfWriter::CStream* pStream = new PdfWriter::CMemoryStream();
pDObj->SetStream(m_pXref, pStream, false);
Stream* pImage = oTemp.getStream()->getUndecodedStream();
pImage->reset();
for (int nI = 0; nI < nLength; ++nI)
pStream->WriteChar(pImage->getChar());
break;
}
case objNone:
case objCmd:
case objError:
case objEOF:
default:
break;
}
oTemp.free();
if (pObj)
pXref->Add(pObj);
}
PdfWriter::CDictObject* pTrailer = pXref->GetTrailer();
for (int nIndex = 0; nIndex < trailerDict->dictGetLength(); ++nIndex)
{
Object oTemp;
char* chKey = trailerDict->dictGetKey(nIndex);
if (strcmp("Root", chKey) == 0 || strcmp("Info", chKey) == 0)
{
trailerDict->dictGetValNF(nIndex, &oTemp);
DictToCDictObjectUnified(&oTemp, ctx, pTrailer, chKey);
}
oTemp.free();
}
bool bRes = pDoc->SaveNewWithPassword(pXref, m_pXref, wsPath, wsPassword, wsPassword, pTrailer);
RELEASEOBJECT(pXref);
return bRes ? S_OK : S_FALSE;
}
PdfWriter::CObjectBase* DictToCDictObjectUnified(Object* obj, SDictConvertContext ctx, PdfWriter::CObjectBase* pParent, const std::string& sKey)
{
PdfWriter::CObjectBase* pBase = NULL;
Object oTemp;
switch (obj->getType())
{
// ------------------------------------------------------------------ scalar
case objBool:
{
bool b = obj->getBool() == gTrue;
if (pParent) { _UC_AddToParent(b); return NULL; }
pBase = new PdfWriter::CBoolObject(b);
break;
}
case objInt:
{
int n = obj->getInt();
if (pParent) { _UC_AddToParent(n); return NULL; }
pBase = new PdfWriter::CNumberObject(n);
break;
}
case objReal:
{
double d = obj->getReal();
if (pParent) { _UC_AddToParent(d); return NULL; }
pBase = new PdfWriter::CRealObject(d);
break;
}
case objName:
{
char* c = obj->getName();
if (pParent) { _UC_AddToParent(c); return NULL; }
pBase = new PdfWriter::CNameObject(c);
break;
}
case objNone:
{
const char* c = "None";
if (pParent) { _UC_AddToParent(c); return NULL; }
pBase = new PdfWriter::CNameObject(c);
break;
}
case objNull:
{
PdfWriter::CNullObject* p = new PdfWriter::CNullObject();
if (pParent) { _UC_AddToParent(p); return NULL; }
pBase = p;
break;
}
// ------------------------------------------------------------------ string
case objString:
{
GString* str = obj->getString();
PdfWriter::CObjectBase* pStr = NULL;
if (str->isBinary() || ctx.bMakeBinary)
{
int nLen = str->getLength();
BYTE* arr = new BYTE[nLen];
for (int i = 0; i < nLen; ++i)
arr[i] = str->getChar(i);
pStr = new PdfWriter::CBinaryObject(arr, nLen, false);
}
else
{
TextString* s = new TextString(str);
if (s->isPDFDocEncoding())
pStr = new PdfWriter::CStringObject(str->getCString(), false);
else
{
std::string sValue = NSStringExt::CConverter::GetUtf8FromUTF32(s->getUnicode(), s->getLength());
pStr = new PdfWriter::CStringObject(sValue.c_str(), true);
}
delete s;
}
if (pParent) { _UC_AddToParent(pStr); return NULL; }
pBase = pStr;
break;
}
// ------------------------------------------------------------------ array
case objArray:
{
PdfWriter::CArrayObject* pArray = new PdfWriter::CArrayObject();
if (ctx.Has(EDictConvertFlags::eDCF_RegisterObjs) && ctx.nAddObjToXRef > 0)
{
ctx.pDoc->AddObject(pArray);
ctx.pManager->AddObj(ctx.nAddObjToXRef + ctx.nStartRefID, pArray);
ctx.nAddObjToXRef = 0;
}
if (pParent) _UC_AddToParent(pArray);
SDictConvertContext childCtx = ctx;
childCtx.nAddObjToXRef = 0;
for (int i = 0; i < obj->arrayGetLength(); ++i)
{
obj->arrayGetNF(i, &oTemp);
PdfWriter::CObjectBase* pElem = DictToCDictObjectUnified(&oTemp, childCtx);
pArray->Add(pElem);
oTemp.free();
}
pBase = pArray;
break;
}
// ------------------------------------------------------------------ dict
case objDict:
{
if (ctx.Has(EDictConvertFlags::eDCF_RegisterObjs) && SplitSkipDict(obj, ctx.pManager, ctx.nStartRefID))
return NULL;
PdfWriter::CDictObject* pDict = NULL;
bool bAnnot = false;
if (ctx.Has(EDictConvertFlags::eDCF_HandleAnnots))
{
Object oSubtype;
if (obj->dictLookup("Subtype", &oSubtype)->isName())
{
PdfWriter::CAnnotation* pAnnot = CreateAnnot(obj, &oSubtype, NULL);
if (pAnnot)
{
ctx.pDoc->AddAnnotation(ctx.nAddObjToXRef + ctx.nStartRefID, pAnnot);
pDict = pAnnot;
bAnnot = true;
}
}
oSubtype.free();
Object oType;
if (obj->dictLookup("Type", &oType)->isName("ExtGState"))
{
PdfWriter::CExtGrState* pState = CreateExtGState(obj);
if (pState)
{
ctx.pDoc->AddExtGState(pState);
pDict = pState;
}
}
oType.free();
}
if (!pDict)
pDict = new PdfWriter::CDictObject();
if (ctx.Has(EDictConvertFlags::eDCF_RegisterObjs) && ctx.nAddObjToXRef > 0)
{
ctx.pDoc->AddObject(pDict);
ctx.pManager->AddObj(ctx.nAddObjToXRef + ctx.nStartRefID, pDict);
ctx.nAddObjToXRef = 0;
}
if (pParent) _UC_AddToParent(pDict);
SDictConvertContext childCtx = ctx;
childCtx.nAddObjToXRef = 0;
for (int i = 0; i < obj->dictGetLength(); ++i)
{
char* chKey = obj->dictGetKey(i);
if (ctx.bIgnoreOO && bAnnot && strcmp("OO", chKey) == 0)
{
oTemp.free();
continue;
}
if (ctx.Has(EDictConvertFlags::eDCF_HandleResources) && strcmp("Resources", chKey) == 0)
{
Ref oResRef = { -1, -1 };
if (obj->dictGetValNF(i, &oTemp)->isRef())
oResRef = oTemp.getRef();
oTemp.free();
PdfWriter::CObjectBase* pExisting = oResRef.num > 0 ? ctx.pManager->GetObj(oResRef.num + ctx.nStartRefID) : NULL;
if (pExisting)
{
pDict->Add(chKey, pExisting);
ctx.pManager->IncRefCount(oResRef.num + ctx.nStartRefID);
continue;
}
if (obj->dictGetVal(i, &oTemp)->isDict())
{
PdfWriter::CResourcesDict* pResDict = ctx.pDoc->CreateResourcesDict(oResRef.num < 0, false);
if (oResRef.num > 0)
ctx.pManager->AddObj(oResRef.num + ctx.nStartRefID, pResDict);
pDict->Add(chKey, pResDict);
for (int j = 0; j < oTemp.dictGetLength(); ++j)
{
Object oRes;
char* chKey2 = oTemp.dictGetKey(j);
oTemp.dictGetValNF(j, &oRes);
PdfWriter::CObjectBase* pResItem = DictToCDictObjectUnified(&oRes, childCtx);
pResDict->Add(chKey2, pResItem);
oRes.free();
}
pResDict->Fix();
oTemp.free();
continue;
}
else
{
oTemp.free();
obj->dictGetValNF(i, &oTemp);
}
}
else
obj->dictGetValNF(i, &oTemp);
if (pParent)
DictToCDictObjectUnified(&oTemp, childCtx, pDict, chKey);
else
{
PdfWriter::CObjectBase* pVal = DictToCDictObjectUnified(&oTemp, childCtx);
pDict->Add(chKey, pVal);
}
oTemp.free();
}
pBase = pDict;
break;
}
// ------------------------------------------------------------------ ref
case objRef:
{
if (!ctx.Has(EDictConvertFlags::eDCF_ResolveRefs))
{
PdfWriter::CObjectBase* pRefBase = new PdfWriter::CObjectBase();
pRefBase->SetRef(obj->getRefNum(), obj->getRefGen());
PdfWriter::CProxyObject* pProxy = new PdfWriter::CProxyObject(pRefBase, true);
if (pParent) _UC_AddToParent(pProxy);
return pProxy;
}
int nObjNum = obj->getRefNum();
PdfWriter::CObjectBase* pCached = ctx.pManager->GetObj(nObjNum + ctx.nStartRefID);
if (pCached)
{
ctx.pManager->IncRefCount(nObjNum + ctx.nStartRefID);
return pCached;
}
// Deleted (m_setDeletedIDs): a later reference does not convert it from the reader again
if (ctx.pManager->m_setDeletedIDs.count(nObjNum + ctx.nStartRefID))
return NULL;
// the editor's IDs, like the manager's, carry nStartRefID
std::vector<int>::iterator it = std::find(ctx.pManager->m_arrRedactFormFieldsID.begin(), ctx.pManager->m_arrRedactFormFieldsID.end(), nObjNum + ctx.nStartRefID);
if (it != ctx.pManager->m_arrRedactFormFieldsID.end())
return NULL;
SDictConvertContext refCtx = ctx;
refCtx.nAddObjToXRef = nObjNum;
obj->fetch(ctx.xref, &oTemp);
pBase = DictToCDictObjectUnified(&oTemp, refCtx);
oTemp.free();
break;
}
// ------------------------------------------------------------------ stream
case objStream:
{
if (!ctx.Has(EDictConvertFlags::eDCF_HandleStreams))
break;
PdfWriter::CDictObject* pDict = new PdfWriter::CDictObject();
if (ctx.nAddObjToXRef > 0)
{
ctx.pDoc->AddObject(pDict);
ctx.pManager->AddObj(ctx.nAddObjToXRef + ctx.nStartRefID, pDict);
ctx.nAddObjToXRef = 0;
}
SDictConvertContext childCtx = ctx;
childCtx.nAddObjToXRef = 0;
Dict* pODict = obj->streamGetDict();
int nLength = 0;
for (int i = 0; i < pODict->getLength(); ++i)
{
char* chKey = pODict->getKey(i);
if (strcmp("Length", chKey) == 0)
{
pODict->getVal(i, &oTemp);
nLength = oTemp.getNum();
PdfWriter::CNumberObject* pLenObj = new PdfWriter::CNumberObject(nLength);
ctx.pDoc->AddObject(pLenObj);
pDict->Add(chKey, pLenObj);
oTemp.free();
continue;
}
else if (ctx.Has(EDictConvertFlags::eDCF_HandleResources) && strcmp("Resources", chKey) == 0)
{
Ref oResRef = { -1, -1 };
if (pODict->getValNF(i, &oTemp)->isRef())
oResRef = oTemp.getRef();
oTemp.free();
PdfWriter::CObjectBase* pExisting = oResRef.num > 0 ? ctx.pManager->GetObj(oResRef.num + ctx.nStartRefID) : nullptr;
if (pExisting)
{
pDict->Add(chKey, pExisting);
ctx.pManager->IncRefCount(oResRef.num + ctx.nStartRefID);
continue;
}
if (pODict->getVal(i, &oTemp)->isDict())
{
PdfWriter::CResourcesDict* pResDict = ctx.pDoc->CreateResourcesDict(oResRef.num < 0, false);
if (oResRef.num > 0)
ctx.pManager->AddObj(oResRef.num + ctx.nStartRefID, pResDict);
pDict->Add(chKey, pResDict);
for (int j = 0; j < oTemp.dictGetLength(); ++j)
{
Object oRes;
char* chKey2 = oTemp.dictGetKey(j);
oTemp.dictGetValNF(j, &oRes);
PdfWriter::CObjectBase* pResItem = DictToCDictObjectUnified(&oRes, childCtx);
pResDict->Add(chKey2, pResItem);
oRes.free();
}
pResDict->Fix();
oTemp.free();
continue;
}
else
{
oTemp.free();
pODict->getValNF(i, &oTemp);
}
}
else
pODict->getValNF(i, &oTemp);
pBase = DictToCDictObjectUnified(&oTemp, childCtx);
pDict->Add(chKey, pBase);
oTemp.free();
}
pBase = pDict;
PdfWriter::CStream* pStream = new PdfWriter::CMemoryStream(nLength);
pDict->SetStream(pStream);
Stream* pOStream = ctx.bUndecodedStream ? obj->getStream()->getUndecodedStream() : obj->getStream();
pOStream->reset();
int nChar = pOStream->getChar();
while (nChar != EOF)
{
pStream->WriteChar(nChar);
nChar = pOStream->getChar();
}
break;
}
case objCmd:
case objError:
case objEOF:
break;
}
if (ctx.Has(EDictConvertFlags::eDCF_RegisterObjs) && ctx.nAddObjToXRef > 0 && pBase)
{
ctx.pDoc->AddObject(pBase);
ctx.pManager->AddObj(ctx.nAddObjToXRef + ctx.nStartRefID, pBase);
}
return pBase;
}
// ============================================================
// CObjectsManager
// ============================================================
void CObjectsManager::AddObj(int nID, PdfWriter::CObjectBase* pObj)
{
if (m_mUniqueRef.find(nID) == m_mUniqueRef.end())
m_mUniqueRef[nID] = { pObj, 1 };
}
void CObjectsManager::RemoveObj(int nID)
{
m_mUniqueRef.erase(nID);
}
PdfWriter::CObjectBase* CObjectsManager::GetObj(int nID)
{
if (m_mUniqueRef.find(nID) != m_mUniqueRef.end())
return m_mUniqueRef[nID].pObj;
return NULL;
}
bool CObjectsManager::IncRefCount(int nID)
{
if (m_mUniqueRef.find(nID) != m_mUniqueRef.end())
{
m_mUniqueRef[nID].nRefCount++;
return true;
}
return false;
}
bool CObjectsManager::DecRefCount(int nID)
{
if (m_mUniqueRef.find(nID) != m_mUniqueRef.end())
{
if (m_pDoc)
m_pDoc->RemoveObj(m_mUniqueRef[nID].pObj);
else
m_mUniqueRef[nID].pObj->SetHidden();
return true;
}
return false;
}
int CObjectsManager::FindObj(PdfWriter::CObjectBase* pObj)
{
std::map<int, CObjectInfo>::iterator it = std::find_if(m_mUniqueRef.begin(), m_mUniqueRef.end(), [pObj](const std::pair<int, CObjectInfo>& t){ return t.second.pObj == pObj; });
if (it != m_mUniqueRef.end())
return it->first;
return -1;
}
void CObjectsManager::DeleteObjTree(Object* obj, XRef* xref, int nStartRefID)
{
Object oTemp;
switch (obj->getType())
{
case objBool:
case objInt:
case objReal:
case objString:
case objName:
case objNull:
case objNone:
case objCmd:
case objError:
case objEOF:
break;
case objArray:
{
for (int nIndex = 0; nIndex < obj->arrayGetLength(); ++nIndex)
{
obj->arrayGetNF(nIndex, &oTemp);
DeleteObjTree(&oTemp, xref, nStartRefID);
oTemp.free();
}
break;
}
case objDict:
{
if (SplitSkipDict(obj, this, nStartRefID))
return;
for (int nIndex = 0; nIndex < obj->dictGetLength(); ++nIndex)
{
obj->dictGetValNF(nIndex, &oTemp);
DeleteObjTree(&oTemp, xref, nStartRefID);
oTemp.free();
}
break;
}
case objRef:
{
int nObjNum = obj->getRefNum();
PdfWriter::CObjectBase* pObj = GetObj(nObjNum + nStartRefID);
if (pObj && DecRefCount(nObjNum + nStartRefID))
return;
obj->fetch(xref, &oTemp);
DeleteObjTree(&oTemp, xref, nStartRefID);
oTemp.free();
break;
}
case objStream:
{
Dict* pODict = obj->streamGetDict();
for (int nIndex = 0; nIndex < pODict->getLength(); ++nIndex)
{
pODict->getValNF(nIndex, &oTemp);
DeleteObjTree(&oTemp, xref, nStartRefID);
oTemp.free();
}
break;
}
}
}
void CObjectsManager::SetDoc(PdfWriter::CDocument* pDoc) { m_pDoc = pDoc; }
// ============================================================
// CPdfEditor
// ============================================================
CPdfEditor::CPdfEditor(const std::wstring& _wsSrcFile, const wchar_t* _wsPassword, const std::wstring& _wsDstFile, CPdfReader* _pReader, CPdfWriter* _pWriter, Mode nMode)
{
m_wsSrcFile = _wsSrcFile;
m_wsDstFile = _wsDstFile;
m_wsPassword = _wsPassword;
m_pReader = _pReader;
m_pWriter = _pWriter;
m_nEditPage = -1;
m_nError = 0;
m_nMode = Mode::Unknown;
m_nRedactInfo = 0;
m_nOriginIndex = 0;
PDFDoc* pPDFDocument = m_pReader->GetPDFDocument(0);
XRef* xref = pPDFDocument ? pPDFDocument->getXRef() : NULL;
PdfWriter::CDocument* pDoc = m_pWriter->GetDocument();
if (!pPDFDocument || !xref || !pDoc)
{
m_nError = 1;
return;
}
m_nOriginIndex = m_pReader->GetNumPages();
SetMode(nMode);
}
void CPdfEditor::SetMode(Mode nMode)
{
if (m_nMode != Mode::Unknown)
return;
m_nMode = nMode;
if (m_nMode == Mode::WriteAppend)
{
IncrementalUpdates();
return;
}
if (m_nMode == Mode::WriteNew)
{
PdfWriter::CDocument* pDoc = m_pWriter->GetDocument();
pDoc->CreateNew(false);
PdfWriter::CPageTree* pPageTree = pDoc->GetPageTree();
m_mObjManager.SetDoc(pDoc);
pPageTree->CreateFakePages(m_pReader->GetNumPages());
m_pWriter->SetNeedAddHelvetica(false);
SyncAcroFormFromReader();
}
}
void CPdfEditor::RedactInfo(int nFlag, const std::vector<IAdvancedCommand*>& arrForms)
{
if (m_nMode != Mode::Unknown)
return;
m_pWriter->GetDocument()->SetRedactInfo(nFlag);
m_nRedactInfo = nFlag;
// 0 - Metadata
// 1 - FileAttachments
// 2 - Bookmarks
// 3 - Embedded search index
// 4 - Comments and markups
// 5 - Form Fields
// 6 - Hidden text
// 7 - Hidden layers
// 8 - Deleted or cropped content
// 9 - Links, actions and JavaScripts
// 10 - Overlapping Objects
m_mObjManager.m_arrRedactFormFields = arrForms;
for (int i = 0; i < arrForms.size(); ++i)
{
IAdvancedCommand::AdvancedCommandType nType = arrForms[i]->GetCommandType();
if (nType == IAdvancedCommand::AdvancedCommandType::Annotation)
{
// Annotation - annotation that has changed or been created and needs to be drawn in Contents of page
CAnnotFieldInfo* command = dynamic_cast<CAnnotFieldInfo*>(arrForms[i]);
m_mObjManager.m_arrRedactFormFieldsID.push_back(command->GetID());
}
else if (nType == IAdvancedCommand::AdvancedCommandType::RedactAnnot)
{
// RedactAnnot - annotation that needs to be drawn in Contents of page without changes
CRedactAnnot* command = dynamic_cast<CRedactAnnot*>(arrForms[i]);
m_mObjManager.m_arrRedactFormFieldsID.push_back(command->GetID());
}
}
}
// ============================================================
// CPdfEditor::IncrementalUpdates
// ============================================================
namespace
{
// Returns the unique 1-based index for the next "SigN" field name
unsigned int ComputeInitialSigFieldIndex(AcroForm* form)
{
if (!form)
return 0;
const int nFormFields = form->getNumFields();
unsigned int nFormField = nFormFields + 1;
std::wstring sSig = L"Sig" + std::to_wstring(nFormField);
for (int i = 0; i < nFormFields; )
{
int nLength = 0;
Unicode* uName = form->getField(i)->getName(&nLength);
std::wstring sName = NSStringExt::CConverter::GetUnicodeFromUTF32(uName, nLength);
RELEASEMEM(uName);
if (sName == sSig)
{
++nFormField;
sSig = L"Sig" + std::to_wstring(nFormField);
i = 0; // restart scan
}
else
++i;
}
return nFormField - 1;
}
// Builds a PdfWriter::CDictObject for AcroForm, also creates DR xref/dict.
PdfWriter::CDictObject* BuildAcroForm(
Object& oAcroForm,
XRef* xref,
PdfWriter::CDocument* pDoc,
SDictConvertContext& ctx,
PdfWriter::CXref** ppDRXref, // out
PdfWriter::CResourcesDict** ppDR) // out
{
*ppDRXref = NULL;
*ppDR = NULL;
PdfWriter::CDictObject* pAcroForm = new(std::nothrow) PdfWriter::CDictObject();
if (!pAcroForm)
return NULL;
for (int i = 0, nLen = oAcroForm.dictGetLength(); i < nLen; ++i)
{
char* chKey = oAcroForm.dictGetKey(i);
if (strcmp("DR", chKey) == 0)
{
CObjectGuard oTemp2, oDR;
if (!oAcroForm.dictGetVal(i, oTemp2.get())->isDict())
continue;
oAcroForm.dictGetValNF(i, oDR.get());
const int nDRNum = oDR->isRef() ? oDR->getRefNum() : xref->getNumObjects();
const int nDRGen = oDR->isRef() ? oDR->getRefGen() : 0;
PdfWriter::CXref* pDRXref = new(std::nothrow) PdfWriter::CXref(pDoc, nDRNum);
PdfWriter::CResourcesDict* pDR = new(std::nothrow) PdfWriter::CResourcesDict(NULL, true, false);
if (!pDRXref || !pDR)
{
RELEASEOBJECT(pDRXref);
RELEASEOBJECT(pDR);
RELEASEOBJECT(pAcroForm);
return NULL;
}
pDRXref->Add(pDR, nDRGen);
pAcroForm->Add(chKey, pDR);
for (int j = 0; j < oTemp2->dictGetLength(); ++j)
{
CObjectGuard oEntry;
char* chKey2 = oTemp2->dictGetKey(j);
oTemp2->dictGetVal(j, oEntry.get());
DictToCDictObjectUnified(oEntry.get(), ctx, pDR, chKey2);
}
pDR->Fix();
*ppDRXref = pDRXref;
*ppDR = pDR;
continue;
}
CObjectGuard oTemp2;
if (strcmp("NeedAppearances", chKey) == 0)
continue;
else if (strcmp("Fields", chKey) == 0)
oAcroForm.dictGetVal(i, oTemp2.get());
else
oAcroForm.dictGetValNF(i, oTemp2.get());
DictToCDictObjectUnified(oTemp2.get(), ctx, pAcroForm, chKey);
}
if (!pAcroForm->Get("Fields"))
pAcroForm->Add("Fields", new PdfWriter::CArrayObject());
return pAcroForm;
}
// Builds writer catalog + xref from the reader's catalog dict.
// Returns false on failure; ppDRXref/ppDR may be null if no DR present.
bool BuildCatalogFromReader(
Object& catDict,
XRef* xref,
PdfWriter::CDocument* pDoc,
const Ref& catRef,
PdfWriter::CXref** ppXref, // out
PdfWriter::CCatalog** ppCatalog, // out
PdfWriter::CXref** ppDRXref, // out
PdfWriter::CResourcesDict** ppDR) // out
{
*ppXref = NULL;
*ppCatalog = NULL;
*ppDRXref = NULL;
*ppDR = NULL;
PdfWriter::CXref* pXref = new(std::nothrow) PdfWriter::CXref(pDoc, catRef.num);
PdfWriter::CCatalog* pCatalog = new(std::nothrow) PdfWriter::CCatalog();
if (!pXref || !pCatalog)
{
RELEASEOBJECT(pXref);
RELEASEOBJECT(pCatalog);
return false;
}
pXref->Add(pCatalog, catRef.gen);
SDictConvertContext ctx;
for (int i = 0, nLen = catDict.dictGetLength(); i < nLen; ++i)
{
char* chKey = catDict.dictGetKey(i);
if (strcmp("AcroForm", chKey) == 0)
{
CObjectGuard oAcroForm;
catDict.dictGetVal(i, oAcroForm.get());
PdfWriter::CXref* pDRXref = NULL;
PdfWriter::CResourcesDict* pDR = NULL;
PdfWriter::CDictObject* pAcroFormDict = BuildAcroForm(*oAcroForm.get(), xref, pDoc, ctx, &pDRXref, &pDR);
if (!pAcroFormDict)
{
RELEASEOBJECT(pXref);
RELEASEOBJECT(pCatalog);
return false;
}
pCatalog->Add(chKey, pAcroFormDict);
*ppDRXref = pDRXref;
*ppDR = pDR;
continue;
}
CObjectGuard oVal;
catDict.dictGetValNF(i, oVal.get());
DictToCDictObjectUnified(oVal.get(), ctx, pCatalog, chKey);
}
*ppXref = pXref;
*ppCatalog = pCatalog;
return true;
}
// Reads encryption parameters from xref and builds a CEncryptDict for writer.
// Returns nullptr if the PDF is not encrypted or on allocation failure.
PdfWriter::CEncryptDict* GetEncryptionFromReader(XRef* xref, const wchar_t* wsPassword, int& nCryptAlgorithm) // out
{
nCryptAlgorithm = -1;
if (!xref->isEncrypted())
return NULL;
Object* pTrailerDict = xref->getTrailerDict();
if (!pTrailerDict)
return NULL;
CryptAlgorithm encAlgorithm;
GBool ownerPasswordOk;
int permFlags, keyLength, encVersion;
xref->getEncryption(&permFlags, &ownerPasswordOk, &keyLength, &encVersion, &encAlgorithm);
nCryptAlgorithm = encAlgorithm;
PdfWriter::CEncryptDict* pEncryptDict = new(std::nothrow) PdfWriter::CEncryptDict();
if (!pEncryptDict)
return NULL;
SDictConvertContext ctx;
ctx.bMakeBinary = true;
FillEncryptDictFromXRef(xref, pTrailerDict, pEncryptDict, ctx);
pEncryptDict->SetRef(0, 0);
pEncryptDict->Fix();
std::wstring sPassword = wsPassword ? wsPassword : L"";
pEncryptDict->SetPasswords(sPassword, sPassword);
if (!pEncryptDict->UpdateKey(nCryptAlgorithm))
{
RELEASEOBJECT(pEncryptDict);
return NULL; // caller checks nullptr as failure signal
}
return pEncryptDict;
}
}
bool CPdfEditor::IncrementalUpdates()
{
if (m_nMode != Mode::Unknown)
return true;
m_nMode = Mode::WriteAppend;
// ── acquire reader state ─────────────────────────────────────────────────
PDFDoc* pPDFDocument = NULL;
PdfReader::CPdfFontList* pFontList = NULL;
int nStartRefID = 0;
m_pReader->GetPageIndex(0, &pPDFDocument, &pFontList, &nStartRefID);
XRef* xref = pPDFDocument->getXRef();
PdfWriter::CDocument* pDoc = m_pWriter->GetDocument();
pDoc->CreateNew();
// ── make dst file writable ───────────────────────────────────────────────
const std::string sPathUtf8New = U_TO_UTF8(m_wsDstFile);
const std::string sPathUtf8Old = U_TO_UTF8(m_wsSrcFile);
const bool bSameFile = sPathUtf8Old == sPathUtf8New || NSSystemPath::NormalizePath(sPathUtf8Old) == NSSystemPath::NormalizePath(sPathUtf8New);
if (bSameFile)
{
if (!MakeDocWritable(pPDFDocument, m_wsPassword, true))
return false;
}
else
{
if (!NSFile::CFileBinary::Copy(m_wsSrcFile, m_wsDstFile))
return false;
NSFile::CFileBinary oFile;
if (!oFile.OpenFile(m_wsDstFile, true))
return false;
oFile.CloseFile();
}
// ── read catalog ref and pages ref from xref ─────────────────────────────
CObjectGuard catDict, catRefObj, pagesRefObj;
if (!xref->getCatalog(catDict.get())->isDict() || !catDict->dictLookupNF("Pages", pagesRefObj.get()))
return false;
Object* trailer = xref->getTrailerDict();
if (!trailer || !trailer->isDict() || !trailer->dictLookupNF("Root", catRefObj.get())->isRef())
return false;
const Ref catRef = catRefObj->getRef();
// catRefObj goes out of scope at end of block; guards cover cleanup
// ── build writer catalog ─────────────────────────────────────────────────
PdfWriter::CXref* pXref = NULL;
PdfWriter::CCatalog* pCatalog = NULL;
PdfWriter::CXref* pDRXref = NULL;
PdfWriter::CResourcesDict* pDR = NULL;
if (!BuildCatalogFromReader(*catDict.get(), xref, pDoc, catRef, &pXref, &pCatalog, &pDRXref, &pDR))
return false;
// ── sig field index ──────────────────────────────────────────────────────
AcroForm* form = pPDFDocument->getCatalog()->getForm();
unsigned int nFormField = ComputeInitialSigFieldIndex(form);
// ── encryption ───────────────────────────────────────────────────────────
int nCryptAlgorithm = -1;
PdfWriter::CEncryptDict* pEncryptDict = GetEncryptionFromReader(xref, m_wsPassword, nCryptAlgorithm);
// GetEncryptionFromReader returns NULL both for "not encrypted" and
// for "UpdateKey failed". Distinguish the two:
if (xref->isEncrypted() && !pEncryptDict)
{
RELEASEOBJECT(pXref);
RELEASEOBJECT(pDRXref);
return false;
}
// ── apply to writer ──────────────────────────────────────────────────────
bool bRes = pDoc->EditPdf(xref->getLastXRefPos(), xref->getNumObjects() + 1, pXref, pCatalog, pEncryptDict, nFormField);
if (bRes)
{
GetPageTree(xref, pagesRefObj.get());
if (pDR && pDRXref)
bRes = pDoc->EditResources(pDRXref, pDR);
}
// ── scan fonts from AcroForm DR ──────────────────────────────────────────
if (form)
{
CObjectGuard oDR;
Object* oAcroForm = form->getAcroFormObj();
if (oAcroForm->dictLookup("DR", oDR.get())->isDict())
ScanFonts(pPDFDocument, oDR->getDict(), m_pWriter, &m_mObjManager, m_pReader);
}
return bRes;
}
// ============================================================
// CPdfEditor::SyncAcroFormFromReader
// ============================================================
namespace
{
// The gen to fetch reader object nNum with (nNum < getNumObjects()): 0 for an object in an
// object stream, whose xref entry gen is its index in that stream
int FetchGen(XRef* xref, int nNum)
{
XRefEntry* pEntry = xref->getEntry(nNum);
return pEntry->type == xrefEntryCompressed ? 0 : pEntry->gen;
}
bool IsButtonField(PdfWriter::CDictObject* pField, PdfWriter::CDictObject* pKid);
// The bytes of an Opt entry's export value (the first item of [export display]); entries with
// equal bytes are one value. false - not a string
bool OptValueBytes(PdfWriter::CObjectBase* pObj, std::string& sBytes)
{
if (pObj && pObj->GetType() == PdfWriter::object_type_ARRAY && ((PdfWriter::CArrayObject*)pObj)->GetCount() > 0)
pObj = ((PdfWriter::CArrayObject*)pObj)->Get(0);
if (pObj && pObj->GetType() == PdfWriter::object_type_STRING)
sBytes.assign((const char*)((PdfWriter::CStringObject*)pObj)->GetString(), ((PdfWriter::CStringObject*)pObj)->GetLength());
else if (pObj && pObj->GetType() == PdfWriter::object_type_BINARY)
sBytes.assign((const char*)((PdfWriter::CBinaryObject*)pObj)->GetValue(), ((PdfWriter::CBinaryObject*)pObj)->GetLength());
else
return false;
return true;
}
// Ff of a field: its own, or inherited
int FieldFlags(PdfWriter::CDictObject* pField)
{
for (int nDepth = 0; pField && nDepth < 32; ++nDepth)
{
PdfWriter::CObjectBase* pObj = pField->Get("Ff");
if (pObj && pObj->GetType() == PdfWriter::object_type_NUMBER)
return ((PdfWriter::CNumberObject*)pObj)->Get();
pObj = pField->Get("Parent");
pField = (pObj && pObj->GetType() == PdfWriter::object_type_DICT) ? (PdfWriter::CDictObject*)pObj : NULL;
}
return 0;
}
// A kid named after the first index of its export value: a checkbox, or a radio in unison.
// A kid converted from the reader is typed checkbox (CreateAnnot): Ff of the field tells a radio too
bool IsUnisonKid(int nFf, PdfWriter::CCheckBoxWidget* pKid)
{
bool bRadio = (nFf & (1 << 15)) || pKid->GetWidgetType() == PdfWriter::WidgetRadiobutton;
return !bRadio || (nFf & (1 << 25));
}
// The on state of a kid as it is: its name, else the one of its appearance. "" - none
std::string KidOnState(PdfWriter::CCheckBoxWidget* pKid)
{
return pKid->NeedAP_N_Yes() ? pKid->GetAPOnState() : pKid->GetAP_N_Yes();
}
// The Opt index V holds; -1 - V is no index (Off, a state name)
int OptIndexOfV(const std::string& sV)
{
if (sV.empty() || sV.length() >= 10)
return -1;
for (char c : sV)
{
if (!isdigit((unsigned char)c))
return -1;
}
return std::stoi(sV);
}
// Names the kids of a checkbox or radio group after their Opt index (IsUnisonKid: the first index of the
// same export value). arrKids pairs with Opt by index (NULL - not a button widget). A kid is on when its AS
// is (under any name: the old one, or Yes it was drawn with); none when V is Off. A kid with no AS is on
// as V reads: an Opt index names pVKid; a state name - the kids with that state when they share one
// export value, else the kids whose export value it is (the editor drew them Yes, it reads V so).
// V names the first kid on, AS follows V
void RenameKids(PdfWriter::CDictObject* pParent, PdfWriter::CArrayObject* pOpt, const std::vector<PdfWriter::CCheckBoxWidget*>& arrKids, PdfWriter::CObjectBase* pVKid)
{
int nFf = FieldFlags(pParent);
PdfWriter::CObjectBase* pObj = pParent->Get("V");
std::string sOldV = pObj && pObj->GetType() == PdfWriter::object_type_NAME ? ((PdfWriter::CNameObject*)pObj)->Get() : "";
std::vector<std::wstring> arrNames(arrKids.size());
std::vector<std::string> arrValues(arrKids.size());
std::map<std::string, std::wstring> mNameAP_N_Yes;
for (int i = 0; i < (int)arrKids.size(); ++i)
{
arrNames[i] = std::to_wstring(i);
if (OptValueBytes(pOpt->Get(i), arrValues[i]))
arrNames[i] = mNameAP_N_Yes.emplace(arrValues[i], arrNames[i]).first->second;
else
arrValues[i] = "#" + std::to_string(i); // no export value: a value of its own
}
// the kids with no AS that are on
std::set<PdfWriter::CCheckBoxWidget*> setOn;
if (!sOldV.empty() && sOldV != "Off")
{
if (OptIndexOfV(sOldV) >= 0)
setOn.insert(dynamic_cast<PdfWriter::CCheckBoxWidget*>(pVKid));
else
{
std::set<std::string> setValues;
for (int i = 0; i < (int)arrKids.size(); ++i)
{
if (arrKids[i] && KidOnState(arrKids[i]) == sOldV)
{
setOn.insert(arrKids[i]);
setValues.insert(arrValues[i]);
}
}
if (setValues.size() != 1)
{
setOn.clear();
for (int i = 0; i < (int)arrKids.size(); ++i)
{
if (arrKids[i] && arrValues[i] == sOldV)
setOn.insert(arrKids[i]);
}
}
}
}
std::string sV = "Off";
for (int i = 0; i < (int)arrKids.size(); ++i)
{
PdfWriter::CCheckBoxWidget* pKid = arrKids[i];
if (!pKid)
continue;
PdfWriter::CObjectBase* pAS = pKid->Get("AS");
std::string sAS = pAS && pAS->GetType() == PdfWriter::object_type_NAME ? ((PdfWriter::CNameObject*)pAS)->Get() : "";
bool bOn = sOldV != "Off" && (sAS.empty() ? setOn.count(pKid) > 0 : sAS != "Off");
pKid->RenameAP_N_Yes(IsUnisonKid(nFf, pKid) ? arrNames[i] : std::to_wstring(i));
if (bOn && sV == "Off")
sV = pKid->GetAP_N_Yes();
}
for (PdfWriter::CCheckBoxWidget* pKid : arrKids)
{
if (pKid)
pKid->Add("AS", pKid->GetAP_N_Yes() == sV ? sV.c_str() : "Off");
}
pParent->Add("V", sV.c_str());
}
// RenameKids for the kids in pParent's Kids. pVKids - the Kids V's Opt index counts: as they were before
// a cut; NULL - Kids
void RenameButtonKids(PdfWriter::CDictObject* pParent, const std::vector<PdfWriter::CObjectBase*>* pVKids = NULL)
{
PdfWriter::CObjectBase* pObj = pParent->Get("Kids");
PdfWriter::CArrayObject* pKids = (pObj && pObj->GetType() == PdfWriter::object_type_ARRAY) ? (PdfWriter::CArrayObject*)pObj : NULL;
pObj = pParent->Get("Opt");
PdfWriter::CArrayObject* pOpt = (pObj && pObj->GetType() == PdfWriter::object_type_ARRAY) ? (PdfWriter::CArrayObject*)pObj : NULL;
if (!pKids || !pOpt || pKids->GetCount() != pOpt->GetCount())
return;
std::vector<PdfWriter::CCheckBoxWidget*> arrKids;
for (int i = 0; i < pKids->GetCount(); ++i)
arrKids.push_back(dynamic_cast<PdfWriter::CCheckBoxWidget*>(pKids->Get(i)));
pObj = pParent->Get("V");
int nV = OptIndexOfV(pObj && pObj->GetType() == PdfWriter::object_type_NAME ? ((PdfWriter::CNameObject*)pObj)->Get() : "");
PdfWriter::CObjectBase* pVKid = NULL;
if (nV >= 0 && pVKids)
pVKid = nV < (int)pVKids->size() ? (*pVKids)[nV] : NULL;
else if (nV >= 0)
pVKid = nV < pKids->GetCount() ? pKids->Get(nV) : NULL;
RenameKids(pParent, pOpt, arrKids, pVKid);
}
// Is the field of pKid - a converted widget or field - another one than pParent, which lists it: the
// editor moved it there (a field renamed into another field). A kid with no Parent, or with the one
// listing it (the same object, or one under its number), is pParent's
bool KidMovedAway(PdfWriter::CDictObject* pParent, PdfWriter::CObjectBase* pKid)
{
if (!pParent || !pKid || pKid->GetType() != PdfWriter::object_type_DICT)
return false;
PdfWriter::CObjectBase* pUp = ((PdfWriter::CDictObject*)pKid)->Get("Parent");
if (!pUp || pUp->GetType() != PdfWriter::object_type_DICT || pUp == pParent)
return false;
return !(pParent->GetObjId() > 0 && pUp->GetObjId() == pParent->GetObjId());
}
// Kids of a converted field and of the fields under it: every converted kid the reader lists, in the
// reader's order (a field converted before its kids' pages lacks them - SplitSkipDict), then the kids
// the reader does not list (the editor's new widgets). Opt of a button group is cut to the kids kept
void AddMissingFieldKids(Object* pNode, int nNodeID, XRef* xref, CObjectsManager& mObjManager, int nStartRefID, int nDepth, std::set<int>& setVisited)
{
// each node once: Kids may list a node again, or its own ancestor
CObjectGuard oKids;
if (nDepth > 32 || !setVisited.insert(nNodeID).second || !pNode->dictLookup("Kids", oKids.get())->isArray())
return;
PdfWriter::CArrayObject* pKids = NULL;
PdfWriter::CObjectBase* pField = mObjManager.GetObj(nNodeID);
if (pField && pField->GetType() == PdfWriter::object_type_DICT)
{
PdfWriter::CObjectBase* pKidsObj = ((PdfWriter::CDictObject*)pField)->Get("Kids");
if (pKidsObj && pKidsObj->GetType() == PdfWriter::object_type_ARRAY)
pKids = (PdfWriter::CArrayObject*)pKidsObj;
else if (!pKidsObj)
{
pKids = new PdfWriter::CArrayObject();
((PdfWriter::CDictObject*)pField)->Add("Kids", pKids);
}
}
std::vector<PdfWriter::CObjectBase*> arrKids;
for (int i = 0; i < oKids->arrayGetLength(); ++i)
{
CObjectGuard oKidRef, oKid;
if (!oKids->arrayGetNF(i, oKidRef.get())->isRef())
continue;
int nKidID = oKidRef->getRefNum() + nStartRefID;
// NULL - deleted, or not in the output; a kid the editor moved to another field (a field renamed
// into it, ReleaseMovedKids) is that field's now, not listed here again
PdfWriter::CObjectBase* pKid = mObjManager.GetObj(nKidID);
if (pKid && pField && pField->GetType() == PdfWriter::object_type_DICT && KidMovedAway((PdfWriter::CDictObject*)pField, pKid))
pKid = NULL;
if (pKid && std::find(arrKids.begin(), arrKids.end(), pKid) == arrKids.end())
arrKids.push_back(pKid);
if (oKidRef->fetch(xref, oKid.get())->isDict())
AddMissingFieldKids(oKid.get(), nKidID, xref, mObjManager, nStartRefID, nDepth + 1, setVisited);
}
if (!pKids)
return;
for (int j = 0; j < pKids->GetCount(); ++j)
{
PdfWriter::CObjectBase* pKid = pKids->Get(j);
if (pKid && std::find(arrKids.begin(), arrKids.end(), pKid) == arrKids.end())
arrKids.push_back(pKid);
}
bool bSame = pKids->GetCount() == arrKids.size();
for (int j = 0; bSame && j < arrKids.size(); ++j)
bSame = pKids->Get(j) == arrKids[j];
// rebuilt only when every item is a proxy to a converted kid: Clear deletes a direct item
// and the placeholder of a raw reference
for (int j = 0; !bSame && j < pKids->GetCount(); ++j)
{
PdfWriter::CObjectBase* pItem = pKids->Get(j, false);
PdfWriter::CObjectBase* pKid = pKids->Get(j);
if (pItem && (pItem->GetType() != PdfWriter::object_type_PROXY || (pKid && pKid->GetType() == PdfWriter::object_type_UNKNOWN)))
bSame = true;
}
if (!bSame)
{
// the proxies are released, the kids stay
pKids->Clear();
for (PdfWriter::CObjectBase* pKid : arrKids)
pKids->Add(pKid);
}
// Opt of a checkbox or radio group pairs with Kids by index: only the entries of the kids in Kids stay
PdfWriter::CDictObject* pDict = (PdfWriter::CDictObject*)pField;
PdfWriter::CObjectBase* pOptObj = pDict->Get("Opt");
CObjectGuard oOpt;
if (!pOptObj || pOptObj->GetType() != PdfWriter::object_type_ARRAY || ((PdfWriter::CArrayObject*)pOptObj)->GetCount() == pKids->GetCount() ||
!pNode->dictLookup("Opt", oOpt.get())->isArray() || oOpt->arrayGetLength() != oKids->arrayGetLength() || !IsButtonField(pDict, NULL))
return;
PdfWriter::CArrayObject* pOpt = new PdfWriter::CArrayObject();
// the reader's Kids, which V's Opt index counts (NULL - a kid not in the output)
std::vector<PdfWriter::CObjectBase*> arrVKids;
for (int i = 0; i < oKids->arrayGetLength(); ++i)
{
CObjectGuard oKidRef, oItem;
if (!oKids->arrayGetNF(i, oKidRef.get())->isRef())
{
arrVKids.push_back(NULL);
continue;
}
PdfWriter::CObjectBase* pKid = mObjManager.GetObj(oKidRef->getRefNum() + nStartRefID);
arrVKids.push_back(pKid);
bool bIn = false;
for (int j = 0; pKid && j < pKids->GetCount() && !bIn; ++j)
bIn = pKids->Get(j) == pKid;
if (bIn)
{
oOpt->arrayGet(i, oItem.get());
pOpt->Add(DictToCDictObjectUnified(oItem.get(), SDictConvertContext()));
}
}
// the kids the reader does not list have no entry to take
if (pOpt->GetCount() != pKids->GetCount())
{
RELEASEOBJECT(pOpt);
return;
}
pDict->Add("Opt", pOpt);
RenameButtonKids(pDict, &arrVKids);
}
// AddMissingFieldKids for every field tree of the reader, from the roots of AcroForm Fields
// (Fields may list widgets: their roots are reached through Parent)
void SyncFieldKids(Object* oAcroForm, XRef* xref, CObjectsManager& mObjManager, int nStartRefID)
{
CObjectGuard oFields;
if (!oAcroForm->dictLookup("Fields", oFields.get())->isArray())
return;
std::set<int> setRoots;
for (int i = 0; i < oFields->arrayGetLength(); ++i)
{
CObjectGuard oRef;
if (!oFields->arrayGetNF(i, oRef.get())->isRef())
continue;
Ref oRoot = oRef->getRef();
Object oNode;
oRef->fetch(xref, &oNode);
for (int nDepth = 0; nDepth < 32 && oNode.isDict(); ++nDepth)
{
Object oParentRef;
if (!oNode.dictLookupNF("Parent", &oParentRef)->isRef())
{
oParentRef.free();
break;
}
oRoot = oParentRef.getRef();
oNode.free();
oParentRef.fetch(xref, &oNode);
oParentRef.free();
}
oNode.free();
setRoots.insert(oRoot.num);
}
std::set<int> setVisited;
for (int nRoot : setRoots)
{
if (nRoot < 0 || nRoot >= xref->getNumObjects())
continue;
CObjectGuard oRootRef, oRoot;
oRootRef->initRef(nRoot, FetchGen(xref, nRoot));
if (oRootRef->fetch(xref, oRoot.get())->isDict())
AddMissingFieldKids(oRoot.get(), nRoot + nStartRefID, xref, mObjManager, nStartRefID, 0, setVisited);
}
}
// Copies an indirect array (Fields or CO) from reader into pAcroForm.
// If the array already exists in pAcroForm, merges elements; otherwise creates it.
// For Fields only: registers each field tree (AddWidgetParent) and lists its root, once.
void MergeRefArray(
Object& oAcroForm,
int nIndex,
const char* chKey,
PdfWriter::CDocument* pDoc,
CObjectsManager& mObjManager,
int nStartRefID,
bool bIsFields)
{
PdfWriter::CDictObject* pAcroForm = pDoc->GetAcroForm();
CObjectGuard oRef;
Ref oArrayRef = { -1, -1 };
if (oAcroForm.dictGetValNF(nIndex, oRef.get())->isRef())
oArrayRef = oRef->getRef();
PdfWriter::CArrayObject* pArray = dynamic_cast<PdfWriter::CArrayObject*>(pAcroForm->Get(chKey));
if (!pArray)
{
// Reuse already-converted object if available
PdfWriter::CObjectBase* pObj = oArrayRef.num > 0 ? mObjManager.GetObj(oArrayRef.num + nStartRefID) : NULL;
if (pObj)
{
pAcroForm->Add(chKey, pObj);
mObjManager.IncRefCount(oArrayRef.num + nStartRefID);
return;
}
}
CObjectGuard oTemp;
if (!oAcroForm.dictGetVal(nIndex, oTemp.get())->isArray())
return;
if (!pArray)
{
pArray = new PdfWriter::CArrayObject();
if (oArrayRef.num > 0)
{
pDoc->AddObject(pArray);
mObjManager.AddObj(oArrayRef.num + nStartRefID, pArray);
}
pAcroForm->Add(chKey, pArray);
}
for (int i = 0; i < oTemp->arrayGetLength(); ++i)
{
CObjectGuard oRes;
if (!oTemp->arrayGetNF(i, oRes.get())->isRef())
continue;
PdfWriter::CObjectBase* pObj = mObjManager.GetObj(oRes->getRefNum() + nStartRefID);
if (!pObj)
continue;
if (bIsFields)
{
AddWidgetParent(pDoc, &mObjManager, pObj);
// Fields lists root fields: an entry with a Parent is replaced by its root.
// A field's Parent is a field: a widget or a page there is a broken file's
for (int nDepth = 0; nDepth < 32 && pObj->GetType() == PdfWriter::object_type_DICT; ++nDepth)
{
PdfWriter::CObjectBase* pParent = ((PdfWriter::CDictObject*)pObj)->Get("Parent");
if (!pParent || pParent->GetType() != PdfWriter::object_type_DICT || ((PdfWriter::CDictObject*)pParent)->GetDictType() != PdfWriter::dict_type_UNKNOWN)
break;
pObj = pParent;
}
}
// listed already: by EditCO, EditWidgetParents, or as the root of an earlier entry
bool bHas = false;
for (int j = 0; j < pArray->GetCount() && !bHas; ++j)
bHas = pArray->Get(j) == pObj;
if (bHas)
continue;
pArray->Add(pObj);
mObjManager.IncRefCount(oRes->getRefNum() + nStartRefID);
}
}
// Merges DR dict from reader AcroForm into pDoc's CResourcesDict.
// Adds only keys not already present; scans fonts afterwards.
void MergeDRDict(
Object& oAcroForm,
int nIndex,
PdfWriter::CDocument* pDoc,
PDFDoc* pPDFDocument,
CObjectsManager& mObjManager,
int nStartRefID,
const SDictConvertContext& ctx,
CPdfWriter* pWriter,
CPdfReader* pReader)
{
PdfWriter::CResourcesDict* pDR = pDoc->GetFieldsResources();
if (!pDR)
return;
CObjectGuard oTemp;
if (!oAcroForm.dictGetVal(nIndex, oTemp.get())->isDict())
return;
for (int j = 0; j < oTemp->dictGetLength(); ++j)
{
char* chKey2 = oTemp->dictGetKey(j);
if (strcmp("ProcSet", chKey2) == 0)
continue;
CObjectGuard oTemp2;
if (!oTemp->dictGetVal(j, oTemp2.get())->isDict())
continue;
PdfWriter::CDictObject* pDict = dynamic_cast<PdfWriter::CDictObject*>(pDR->Get(chKey2));
if (!pDict)
{
// Sub-dict not present — add whole entry
CObjectGuard oTempRef;
if (oTemp->dictGetValNF(j, oTempRef.get())->isRef())
{
PdfWriter::CObjectBase* pObj = mObjManager.GetObj(oTempRef->getRefNum() + nStartRefID);
if (pObj)
{
pDR->Add(chKey2, pObj);
mObjManager.IncRefCount(oTempRef->getRefNum() + nStartRefID);
continue;
}
}
PdfWriter::CObjectBase* pBase = DictToCDictObjectUnified(oTemp2.get(), ctx);
if (oTempRef->isRef())
pDoc->AddObject(pBase);
pDR->Add(chKey2, pBase);
}
else
{
// Sub-dict present — merge missing keys only
for (int k = 0; k < oTemp2->dictGetLength(); ++k)
{
char* chKey3 = oTemp2->dictGetKey(k);
if (pDict->Get(chKey3))
continue;
CObjectGuard oTempRef;
if (oTemp2->dictGetValNF(k, oTempRef.get())->isRef())
{
PdfWriter::CObjectBase* pObj = mObjManager.GetObj(oTempRef->getRefNum() + nStartRefID);
if (pObj)
{
pDict->Add(chKey3, pObj);
mObjManager.IncRefCount(oTempRef->getRefNum() + nStartRefID);
continue;
}
}
PdfWriter::CObjectBase* pBase = DictToCDictObjectUnified(oTemp2.get(), ctx);
if (oTempRef->isRef())
pDoc->AddObject(pBase);
pDict->Add(chKey3, pBase);
}
}
}
pDR->Fix();
ScanFonts(pPDFDocument, oTemp->getDict(), pWriter, &mObjManager, pReader);
}
}
// Copies/merges AcroForm from reader into writer document.
// Called twice: at the start of WriteNew editing (pDoc is empty — builds skeleton)
// and at the end (pDoc has edits — fills in missing Fields/CO/DR entries).
void CPdfEditor::SyncAcroFormFromReader()
{
PdfWriter::CDocument* pDoc = m_pWriter->GetDocument();
PDFDoc* pPDFDocument = NULL;
PdfReader::CPdfFontList* pFontList = NULL;
int nStartRefID = 0;
m_pReader->GetPageIndex(0, &pPDFDocument, &pFontList, &nStartRefID);
XRef* xref = pPDFDocument->getXRef();
CObjectGuard oCatalog;
if (!xref->getCatalog(oCatalog.get())->isDict())
return;
CObjectGuard oAcroFormNF;
if (!oCatalog->dictLookupNF("AcroForm", oAcroFormNF.get())->isRef() && !oAcroFormNF->isDict())
return;
// Resolve indirect ref if needed
CObjectGuard oAcroFormResolved;
Object* oAcroForm = oAcroFormNF.get();
if (oAcroFormNF->isRef())
{
if (!oCatalog->dictLookup("AcroForm", oAcroFormResolved.get())->isDict())
return;
oAcroForm = oAcroFormResolved.get();
}
// Get or create AcroForm in writer
PdfWriter::CDictObject* pAcroForm = pDoc->GetAcroForm();
if (!pAcroForm)
{
pAcroForm = new PdfWriter::CDictObject();
if (oAcroFormNF->isRef())
pDoc->AddObject(pAcroForm);
pDoc->SetAcroForm(pAcroForm);
}
else
pAcroForm->Remove("NeedAppearances");
// Kids the page-by-page conversion missed: a kid converted after its field
SyncFieldKids(oAcroForm, xref, m_mObjManager, nStartRefID);
SDictConvertContext ctx(pDoc, xref, &m_mObjManager, nStartRefID);
for (int i = 0; i < oAcroForm->dictGetLength(); ++i)
{
char* chKey = oAcroForm->dictGetKey(i);
if (strcmp("Fields", chKey) == 0)
{
MergeRefArray(*oAcroForm, i, chKey, pDoc, m_mObjManager, nStartRefID, true);
continue;
}
if (strcmp("CO", chKey) == 0)
{
MergeRefArray(*oAcroForm, i, chKey, pDoc, m_mObjManager, nStartRefID, false);
continue;
}
if (strcmp("DR", chKey) == 0)
{
MergeDRDict(*oAcroForm, i, pDoc, pPDFDocument, m_mObjManager, nStartRefID, ctx, m_pWriter, m_pReader);
// Fall through to default only if DR was not a dict (handled inside)
continue;
}
if (strcmp("SigFlags", chKey) == 0 ||
strcmp("XFA", chKey) == 0 ||
strcmp("DA", chKey) == 0 ||
strcmp("NeedAppearances", chKey) == 0)
continue;
// Default: copy value as-is
CObjectGuard oTemp;
oAcroForm->dictGetValNF(i, oTemp.get());
pAcroForm->Add(chKey, DictToCDictObjectUnified(oTemp.get(), ctx));
}
}
// ============================================================
// CPdfEditor::Close
// ============================================================
namespace
{
PdfWriter::CAnnotAppearanceObject* GetAnnotAppearanceObject(PdfWriter::CAnnotation* pAnnot)
{
PdfWriter::CDictObject* pAP = dynamic_cast<PdfWriter::CDictObject*>(pAnnot->Get("AP"));
if (!pAP)
return NULL;
PdfWriter::CDictObject* pAP_N = dynamic_cast<PdfWriter::CDictObject*>(pAP->Get("N"));
if (!pAP_N)
return NULL;
PdfWriter::CAnnotAppearanceObject* pAPN = dynamic_cast<PdfWriter::CAnnotAppearanceObject*>(pAP_N);
if (pAPN)
return pAPN;
const char* sName = "Off";
PdfWriter::CNameObject* pAPNName = dynamic_cast<PdfWriter::CNameObject*>(pAnnot->Get("AS"));
if (pAPNName)
sName = pAPNName->Get();
return dynamic_cast<PdfWriter::CAnnotAppearanceObject*>(pAP_N->Get(sName));
}
// ─── WriteNew helpers ────────────────────────────────────────────────────────
void RasterizePageOverContent(NSFonts::IFontManager* pFontManager, PdfReader::CPdfFontList* pFontList, PDFDoc* pPDFDocument, int nPageIndex, Object* pContents, Dict* pResourcesDict, PDFRectangle* box, PdfWriter::CDocument* pDoc, PdfWriter::CPage* pPage)
{
double dPageW = box->x2 - box->x1;
double dPageH = box->y2 - box->y1;
if (dPageW <= 0 || dPageH <= 0)
return;
double dDpi = 144.0;
double dMaxSidePx = 5000.0;
double dMaxSide = (dPageW > dPageH ? dPageW : dPageH) * dDpi / 72.0;
if (dMaxSide > dMaxSidePx)
dDpi *= dMaxSidePx / dMaxSide;
int nRasterW = (int)(dPageW * dDpi / 72.0 + 0.5);
int nRasterH = (int)(dPageH * dDpi / 72.0 + 0.5);
if (nRasterW <= 0 || nRasterH <= 0)
return;
BYTE* pBgraData = new(std::nothrow) BYTE[(size_t)nRasterW * nRasterH * 4];
if (!pBgraData)
return;
memset(pBgraData, 0xFF, (size_t)nRasterW * nRasterH * 4);
CBgraFrame oFrame;
oFrame.put_Data(pBgraData);
oFrame.put_Width(nRasterW);
oFrame.put_Height(nRasterH);
oFrame.put_Stride(4 * nRasterW);
NSGraphics::IGraphicsRenderer* pRenderer = NSGraphics::Create();
pRenderer->SetFontManager(pFontManager);
pRenderer->CreateFromBgraFrame(&oFrame);
pRenderer->SetSwapRGB(true);
pRenderer->put_Width (dPageW * 25.4 / 72.0);
pRenderer->put_Height(dPageH * 25.4 / 72.0);
PdfReader::RendererOutputDev oRendererOut(pRenderer, pFontManager, pFontList);
oRendererOut.NewPDF(pPDFDocument->getXRef());
Gfx* gfx = new Gfx(pPDFDocument, &oRendererOut, nPageIndex, pResourcesDict, 72.0, 72.0, box, NULL, 0);
gfx->saveState();
gfx->display(pContents);
gfx->endOfPage();
delete gfx;
delete pRenderer;
PdfWriter::CImageDict* pImageDict = pDoc->CreateImage();
pImageDict->LoadRaw(pBgraData, (unsigned int)nRasterW, (unsigned int)nRasterH);
PdfWriter::CPage* pPrevCurPage = pDoc->GetCurPage();
pDoc->SetCurPage(pPage);
pDoc->ClearPageFull();
pPage->DrawImage(pImageDict, box->x1, box->y1, dPageW, dPageH);
pDoc->SetCurPage(pPrevCurPage);
}
void CloseWriteNew_ApplyOverlappingObjects(PdfWriter::CPage* pPage, NSFonts::IFontManager* pFontManager, PdfReader::CPdfFontList* pFontList, PDFDoc* pPDFDocument, int nPageIndex, PdfWriter::CDocument* pDoc)
{
PdfWriter::CObjectBase* pContentsObj = pPage->Get("Contents");
if (!pContentsObj)
return;
Object oContents;
CObjectBaseToObject(pContentsObj, &oContents);
PdfWriter::CResourcesDict* pResources = pPage->GetResourcesItem();
Object oResources;
CObjectBaseToObject(pResources, &oResources);
Dict* pResourcesDict = oResources.isDict() ? oResources.getDict() : NULL;
PdfWriter::TBox oMediaBox = pPage->GetBox("MediaBox");
PDFRectangle box(oMediaBox.fLeft, oMediaBox.fBottom, oMediaBox.fRight, oMediaBox.fTop);
RasterizePageOverContent(pFontManager, pFontList, pPDFDocument, nPageIndex, &oContents, pResourcesDict, &box, pDoc, pPage);
oContents.free();
oResources.free();
}
void CloseWriteNew_FlushAnnotations(CPdfReader* pReader, CObjectsManager* mObjManager, PdfWriter::CDocument* pDoc, CPdfWriter* pWriter, int nRedactInfo, IAdvancedCommand* pCommand = NULL, const std::vector<double>& arrRedact = {})
{
std::vector<int> arrWidget;
if (pCommand)
arrWidget = ((CRedactMeta*)pCommand)->GetWidgetID();
std::vector<IAdvancedCommand*> arrProcessed;
for (size_t i = 0; i < mObjManager->m_arrRedactFormFields.size(); ++i)
{
IAdvancedCommand* pCmd = mObjManager->m_arrRedactFormFields[i];
IAdvancedCommand::AdvancedCommandType nType = pCmd->GetCommandType();
if (nType == IAdvancedCommand::AdvancedCommandType::Annotation)
{
// Annotation - annotation that has changed or been created and needs to be drawn in Contents of page
CAnnotFieldInfo* command = dynamic_cast<CAnnotFieldInfo*>(pCmd);
CAnnotFieldInfo& oInfo = *command;
if (!oInfo.IsWidget())
continue;
PdfWriter::CPage* pPage = NULL;
if (pCommand)
{
int nID = oInfo.GetID();
std::vector<int>::iterator it = std::find(arrWidget.begin(), arrWidget.end(), nID);
if (it == arrWidget.end())
continue;
pPage = pDoc->GetCurPage();
arrProcessed.push_back(pCmd);
}
else
{
int nPage = oInfo.GetPage(); // actual page number
pPage = pDoc->GetPage(nPage);
if (!pPage)
pPage = pDoc->GetEditPage(nPage);
if (!pPage)
continue;
}
PdfWriter::CAnnotation* pAnnot = pDoc->GetAnnot(oInfo.GetID());
if (!pAnnot)
continue;
if (pCommand)
{
PdfWriter::CXObject* pPlaceholder = pDoc->CreateForm();
PdfWriter::TRect& oRect = pAnnot->GetRect();
pPlaceholder->Add("Type", "XObject");
pPlaceholder->Add("Subtype", "Form");
pPlaceholder->Add("FormType", 1);
pPlaceholder->Add("BBox", PdfWriter::CArrayObject::CreateBox(oRect.fLeft, oRect.fBottom, oRect.fRight, oRect.fTop));
PdfWriter::CResourcesDict* pPlaceholderRes = pPage->GetResourcesItem();
if (!pPlaceholderRes)
{
pPage->AddResource();
pPlaceholderRes = pPage->GetResourcesItem();
}
pPage->GrSave();
pPage->ExecuteXObject(pPlaceholderRes->GetXObjectName(pPlaceholder));
pPage->GrRestore();
mObjManager->m_arrPendingWidgetStamps.push_back({ pAnnot, pPlaceholder, pPage, arrRedact });
continue;
}
PdfWriter::CAnnotAppearanceObject* pAPN = GetAnnotAppearanceObject(pAnnot);
if (!pAPN)
{
pDoc->RemoveObj(pAnnot);
continue;
}
std::vector<double> arrAnnotRedact = pCommand ? arrRedact : std::vector<double>();
PdfWriter::TRect& oAnnotRect = pAnnot->GetRect();
std::vector<PdfWriter::CPoint> arrRectPolygon =
{
PdfWriter::CPoint(oAnnotRect.fLeft, oAnnotRect.fBottom), PdfWriter::CPoint(oAnnotRect.fLeft, oAnnotRect.fTop),
PdfWriter::CPoint(oAnnotRect.fRight, oAnnotRect.fTop), PdfWriter::CPoint(oAnnotRect.fRight, oAnnotRect.fBottom)
};
if (!arrAnnotRedact.empty() && PdfWriter::CheckFullRedact(arrAnnotRedact, arrRectPolygon))
{
pDoc->RemoveObj(pAnnot);
continue;
}
bool bPartialRedact = !arrAnnotRedact.empty() && PdfWriter::CheckPartialRedact(arrAnnotRedact, arrRectPolygon);
PDFDoc* pPDFDocument = bPartialRedact ? pReader->GetPDFDocument(0) : NULL;
if (bPartialRedact && pPDFDocument)
{
double rect[4] = { oAnnotRect.fLeft, oAnnotRect.fBottom, oAnnotRect.fRight, oAnnotRect.fTop };
DrawAnnotAppearanceRedacted(pAPN, rect, pPage, pDoc, pWriter, pPDFDocument, mObjManager, nRedactInfo, arrAnnotRedact);
}
else
{
double m[6] = { 1, 0, 0, 1, 0, 0 };
pAPN->GetMatrix(m);
m[4] += pAnnot->GetRect().fLeft;
m[5] += pAnnot->GetRect().fBottom;
pAPN->AddMatrix(m[0], m[1], m[2], m[3], m[4], m[5]);
PdfWriter::CResourcesDict* pResources = pPage->GetResourcesItem();
if (!pResources)
{
pPage->AddResource();
pResources = pPage->GetResourcesItem();
}
pPage->GrSave();
pPage->ExecuteXObject(pResources->GetXObjectName(pAPN));
pPage->GrRestore();
}
pDoc->RemoveObj(pAnnot);
}
else if (nType == IAdvancedCommand::AdvancedCommandType::RedactAnnot)
{
// RedactAnnot - annotation that needs to be drawn in Contents of page without changes
CRedactAnnot* command = dynamic_cast<CRedactAnnot*>(pCmd);
PDFDoc* pPDFDocument = NULL;
int nStartRefID = 0;
int nRefID = pReader->FindRefNum(command->GetID(), &pPDFDocument, &nStartRefID);
if (nRefID < 0)
continue;
XRef* xref = pPDFDocument->getXRef();
XRefEntry* pEntry = xref->getEntry(nRefID);
CObjectGuard oAnnotRef, oAnnot;
oAnnotRef->initRef(nRefID, pEntry->type == xrefEntryCompressed ? 0 : pEntry->gen);
if (oAnnotRef->fetch(xref, oAnnot.get())->isNull() || !oAnnot->isDict())
continue;
CObjectGuard oP;
if (!oAnnot->dictLookupNF("P", oP.get())->isRef())
continue;
PdfWriter::CPage* pPage = NULL;
if (pCommand)
{
std::vector<int>::iterator it = std::find(arrWidget.begin(), arrWidget.end(), nRefID);
if (it == arrWidget.end())
continue;
pPage = pDoc->GetCurPage();
arrProcessed.push_back(pCmd);
}
else
pPage = dynamic_cast<PdfWriter::CPage*>(mObjManager->GetObj(oP->getRefNum() + nStartRefID));
if (!pPage)
continue;
DrawAnnotRedacted(oAnnot.get(), pPage, pDoc, pWriter, pPDFDocument, xref, mObjManager, nStartRefID, nRedactInfo, pCommand ? arrRedact : std::vector<double>());
}
}
if (!arrProcessed.empty())
{
std::vector<IAdvancedCommand*>& arrFields = mObjManager->m_arrRedactFormFields;
arrFields.erase(std::remove_if(arrFields.begin(), arrFields.end(), [&arrProcessed](IAdvancedCommand* pCmd)
{
return std::find(arrProcessed.begin(), arrProcessed.end(), pCmd) != arrProcessed.end();
}), arrFields.end());
}
}
void CloseWriteNew_FinalizeWidgetStamps(CPdfReader* pReader, CObjectsManager* mObjManager, PdfWriter::CDocument* pDoc, CPdfWriter* pWriter, int nRedactInfo)
{
std::vector<SPendingWidgetStamp> arrPending;
arrPending.swap(mObjManager->m_arrPendingWidgetStamps);
for (size_t i = 0; i < arrPending.size(); ++i)
{
SPendingWidgetStamp& oStamp = arrPending[i];
PdfWriter::CAnnotation* pAnnot = oStamp.pAnnot;
PdfWriter::CAnnotAppearanceObject* pAPN = GetAnnotAppearanceObject(pAnnot);
if (!pAPN)
{
pDoc->RemoveObj(pAnnot);
continue;
}
PdfWriter::TRect& oAnnotRect = pAnnot->GetRect();
std::vector<PdfWriter::CPoint> arrRectPolygon =
{
PdfWriter::CPoint(oAnnotRect.fLeft, oAnnotRect.fBottom), PdfWriter::CPoint(oAnnotRect.fLeft, oAnnotRect.fTop),
PdfWriter::CPoint(oAnnotRect.fRight, oAnnotRect.fTop), PdfWriter::CPoint(oAnnotRect.fRight, oAnnotRect.fBottom)
};
if (!oStamp.arrAnnotRedact.empty() && PdfWriter::CheckFullRedact(oStamp.arrAnnotRedact, arrRectPolygon))
{
pDoc->RemoveObj(pAnnot);
continue;
}
bool bPartialRedact = !oStamp.arrAnnotRedact.empty() && PdfWriter::CheckPartialRedact(oStamp.arrAnnotRedact, arrRectPolygon);
PDFDoc* pPDFDocument = bPartialRedact ? pReader->GetPDFDocument(0) : NULL;
if (bPartialRedact && pPDFDocument)
{
double rect[4] = { oAnnotRect.fLeft, oAnnotRect.fBottom, oAnnotRect.fRight, oAnnotRect.fTop };
DrawAnnotAppearanceRedacted(pAPN, rect, oStamp.pPage, pDoc, pWriter, pPDFDocument, mObjManager, nRedactInfo, oStamp.arrAnnotRedact, oStamp.pPlaceholder);
}
else
{
double m[6] = { 1, 0, 0, 1, 0, 0 };
pAPN->GetMatrix(m);
m[4] += oAnnotRect.fLeft;
m[5] += oAnnotRect.fBottom;
pAPN->AddMatrix(m[0], m[1], m[2], m[3], m[4], m[5]);
PdfWriter::CResourcesDict* pFormResources = pDoc->CreateResourcesDict(true, false);
oStamp.pPlaceholder->Add("Resources", pFormResources);
const char* sXObjectName = pFormResources->GetXObjectName(pAPN);
PdfWriter::CPage* pFakePage = new PdfWriter::CPage(pDoc);
pFakePage->SetStream(oStamp.pPlaceholder->GetStream());
pFakePage->GrSave();
pFakePage->ExecuteXObject(sXObjectName);
pFakePage->GrRestore();
RELEASEOBJECT(pFakePage);
}
pDoc->RemoveObj(pAnnot);
}
}
void CloseWriteNew_ApplyEncryption(const wchar_t* wsPassword, CObjectsManager* mObjManager, PDFDoc* pPDFDocument, PdfWriter::CDocument* pDoc)
{
XRef* xref = pPDFDocument->getXRef();
if (!xref->isEncrypted() || !xref->getTrailerDict())
return;
CryptAlgorithm encAlgorithm;
GBool ownerPasswordOk;
int permFlags, keyLength, encVersion;
xref->getEncryption(&permFlags, &ownerPasswordOk, &keyLength, &encVersion, &encAlgorithm);
const int nCryptAlgorithm = encAlgorithm;
std::wstring sPassword = wsPassword ? wsPassword : L"";
pDoc->SetPasswords(sPassword, sPassword);
PdfWriter::CEncryptDict* pEncryptDict = pDoc->GetEncrypt();
SDictConvertContext ctx(pDoc, xref, mObjManager, 0);
ctx.bMakeBinary = true;
PdfWriter::CObjectBase* pID = FillEncryptDictFromXRef(xref, xref->getTrailerDict(), pEncryptDict, ctx);
pDoc->SetEncryption(pEncryptDict, pID);
pEncryptDict->Fix();
pEncryptDict->UpdateKey(nCryptAlgorithm);
}
void CloseWriteNew_ApplyNameTree(PDFDoc* pPDFDocument, PdfWriter::CDocument* pDoc)
{
Object* pNameTree = pPDFDocument->getCatalog()->getNameTree();
if (!pNameTree || !pNameTree->isDict())
return;
CObjectGuard oNames;
if (!pNameTree->dictLookup("Names", oNames.get())->isArray())
return;
for (int i = 0, nLen = oNames->arrayGetLength(); i < nLen; i += 2)
{
CObjectGuard oName, oDest;
if (!oNames->arrayGet(i, oName.get())->isString() || !oNames->arrayGet(i + 1, oDest.get())->isArray())
continue;
TextString* s = new TextString(oName->getString());
std::string sName = NSStringExt::CConverter::GetUtf8FromUTF32(s->getUnicode(), s->getLength());
PdfWriter::CStringObject* pName = new PdfWriter::CStringObject(sName.c_str(), !s->isPDFDocEncoding());
delete s;
PdfWriter::CDestination* pDest = NULL;
LinkDest* pLinkDest = new LinkDest(oDest->getArray());
if (pLinkDest && pLinkDest->isOk())
{
int nPage = 0;
if (pLinkDest->isPageRef())
{
Ref pageRef = pLinkDest->getPageRef();
nPage = pPDFDocument->findPage(pageRef.num, pageRef.gen);
}
else
nPage = pLinkDest->getPageNum();
pDest = pDoc->CreateDestination(pDoc->GetEditPage(nPage - 1), true);
}
delete pLinkDest;
if (pName && pDest)
pDoc->AddNameTree(pName, pDest);
else
{
delete pName;
delete pDest;
}
}
}
// ─── WriteAppend helpers ─────────────────────────────────────────────────────
// Returns false on allocation failure; pXref and pTrailer are always set on success.
bool CloseWriteAppend_BuildTrailer(
PDFDoc* pPDFDocument,
PdfWriter::CDocument* pDoc,
PdfWriter::CXref** ppXref, // out
PdfWriter::CDictObject** ppTrailer) // out
{
*ppXref = NULL;
*ppTrailer = NULL;
PdfWriter::CXref* pXref = new(std::nothrow) PdfWriter::CXref(pDoc, 0, 65535);
if (!pXref)
return false;
Object* trailerDict = pPDFDocument->getXRef()->getTrailerDict();
if (trailerDict)
{
PdfWriter::CDictObject* pTrailer = pXref->GetTrailer();
SDictConvertContext ctx;
for (int i = 0; i < trailerDict->dictGetLength(); ++i)
{
CObjectGuard oTemp;
char* chKey = trailerDict->dictGetKey(i);
trailerDict->dictGetValNF(i, oTemp.get());
DictToCDictObjectUnified(oTemp.get(), ctx, pTrailer, chKey);
}
*ppTrailer = pTrailer;
}
*ppXref = pXref;
return true;
}
// Returns false on allocation failure.
bool CloseWriteAppend_UpdateInfo(
PDFDoc* pPDFDocument,
PdfWriter::CDocument* pDoc,
PdfWriter::CXref* pXref,
PdfWriter::CDictObject* pTrailer,
PdfWriter::CXref** ppInfoXref, // out
PdfWriter::CInfoDict** ppInfoDict) // out
{
*ppInfoXref = NULL;
*ppInfoDict = NULL;
CObjectGuard info;
pPDFDocument->getDocInfo(info.get());
if (!info->isDict())
return true;
PdfWriter::CObjectBase* pInfo = pTrailer ? pTrailer->Get("Info") : NULL;
PdfWriter::CXref* pInfoXref = new(std::nothrow) PdfWriter::CXref(pDoc, pInfo ? pInfo->GetObjId() : 0);
if (!pInfoXref)
return false;
PdfWriter::CInfoDict* pInfoDict = new(std::nothrow) PdfWriter::CInfoDict();
if (!pInfoDict)
{
RELEASEOBJECT(pInfoXref);
return false;
}
pInfoXref->Add(pInfoDict, pInfo ? pInfo->GetGenNo() : 0);
SDictConvertContext ctx;
for (int i = 0; i < info->dictGetLength(); ++i)
{
CObjectGuard oTemp;
char* chKey = info->dictGetKey(i);
info->dictGetValNF(i, oTemp.get());
DictToCDictObjectUnified(oTemp.get(), ctx, pInfoDict, chKey);
}
pInfoDict->SetTime(PdfWriter::InfoModaDate);
*ppInfoXref = pInfoXref;
*ppInfoDict = pInfoDict;
return true;
}
void CloseWriteAppend_FinalizeSource(CPdfReader* pReader, const wchar_t* wsPassword, const std::wstring& wsDstFile, const std::wstring& wsSrcFile)
{
PDFDoc* pPDFDocument = pReader->GetPDFDocument(0);
const std::string sPathUtf8New = U_TO_UTF8(wsDstFile);
const std::string sPathUtf8Old = U_TO_UTF8(wsSrcFile);
const bool bSameFile = sPathUtf8Old == sPathUtf8New || NSSystemPath::NormalizePath(sPathUtf8Old) == NSSystemPath::NormalizePath(sPathUtf8New);
if (!bSameFile)
return;
MakeDocWritable(pPDFDocument, wsPassword, false);
NSFile::CFileBinary oFile;
if (oFile.OpenFile(wsSrcFile))
{
pReader->ChangeLength(oFile.GetFileSize());
oFile.CloseFile();
}
}
// ─── Remove hidden layers ────────────────────────────────────────────────────
bool ContainsRef(const std::vector<Ref>& refs, const Ref& ref)
{
for (size_t i = 0; i < refs.size(); ++i)
if (refs[i].num == ref.num && refs[i].gen == ref.gen)
return true;
return false;
}
void CollectRefs(Object* pArr, std::vector<Ref>& out)
{
if (!pArr->isArray())
return;
for (int i = 0; i < pArr->arrayGetLength(); ++i)
{
CObjectGuard oElem;
if (pArr->arrayGetNF(i, oElem.get())->isRef())
out.push_back(oElem.get()->getRef());
}
}
void CollectHiddenOCGRefs(Object* pOCProperties, std::vector<Ref>& hiddenRefs)
{
CObjectGuard oD;
if (!pOCProperties->dictLookup("D", oD.get())->isDict())
return;
std::vector<Ref> allRefs, onRefs, offRefs;
CObjectGuard oOCGs;
if (pOCProperties->dictLookup("OCGs", oOCGs.get())->isArray())
CollectRefs(oOCGs.get(), allRefs);
CObjectGuard oOn;
if (oD.get()->dictLookupNF("ON", oOn.get())->isArray())
CollectRefs(oOn.get(), onRefs);
CObjectGuard oOff;
if (oD.get()->dictLookupNF("OFF", oOff.get())->isArray())
CollectRefs(oOff.get(), offRefs);
for (size_t i = 0; i < offRefs.size(); ++i)
if (!ContainsRef(hiddenRefs, offRefs[i]))
hiddenRefs.push_back(offRefs[i]);
CObjectGuard oBaseState;
if (oD.get()->dictLookup("BaseState", oBaseState.get())->isName() && !strcmp(oBaseState.get()->getName(), "OFF"))
{
for (size_t i = 0; i < allRefs.size(); ++i)
if (!ContainsRef(onRefs, allRefs[i]) && !ContainsRef(hiddenRefs, allRefs[i]))
hiddenRefs.push_back(allRefs[i]);
}
}
void FilterRefArray(Object* pSrc, const std::vector<Ref>& hiddenRefs, XRef* xref, Object* pDst)
{
pDst->initArray(xref);
if (!pSrc->isArray())
return;
for (int i = 0; i < pSrc->arrayGetLength(); ++i)
{
CObjectGuard oElem;
if (pSrc->arrayGetNF(i, oElem.get())->isRef() && ContainsRef(hiddenRefs, oElem.get()->getRef()))
continue;
Object oCopy;
oElem.get()->copy(&oCopy);
pDst->arrayAdd(&oCopy);
}
}
void FilterOrderArray(Object* pSrc, const std::vector<Ref>& hiddenRefs, XRef* xref, Object* pDst)
{
pDst->initArray(xref);
if (!pSrc->isArray())
return;
for (int i = 0; i < pSrc->arrayGetLength(); ++i)
{
CObjectGuard oElem;
pSrc->arrayGetNF(i, oElem.get());
if (oElem.get()->isRef())
{
if (ContainsRef(hiddenRefs, oElem.get()->getRef()))
continue;
Object oCopy;
oElem.get()->copy(&oCopy);
pDst->arrayAdd(&oCopy);
}
else if (oElem.get()->isArray())
{
Object oNested;
FilterOrderArray(oElem.get(), hiddenRefs, xref, &oNested);
if (oNested.arrayGetLength() > 0)
pDst->arrayAdd(&oNested);
else
oNested.free();
}
else
{
Object oCopy;
oElem.get()->copy(&oCopy);
pDst->arrayAdd(&oCopy);
}
}
}
void FilterRBGroups(Object* pSrc, const std::vector<Ref>& hiddenRefs, XRef* xref, Object* pDst)
{
pDst->initArray(xref);
if (!pSrc->isArray())
return;
for (int i = 0; i < pSrc->arrayGetLength(); ++i)
{
CObjectGuard oGroup;
if (!pSrc->arrayGet(i, oGroup.get())->isArray())
continue;
Object oFiltered;
FilterRefArray(oGroup.get(), hiddenRefs, xref, &oFiltered);
if (oFiltered.arrayGetLength() > 1)
pDst->arrayAdd(&oFiltered);
else
oFiltered.free();
}
}
void FilterOCConfig(Object* pSrcConfig, const std::vector<Ref>& hiddenRefs, XRef* xref, Object* pDstConfig)
{
pDstConfig->initDict(xref);
if (!pSrcConfig->isDict())
return;
for (int i = 0; i < pSrcConfig->dictGetLength(); ++i)
{
char* key = pSrcConfig->dictGetKey(i);
CObjectGuard oSrcVal;
pSrcConfig->dictGetValNF(i, oSrcVal.get());
if (!strcmp(key, "ON") || !strcmp(key, "OFF") || !strcmp(key, "Locked"))
{
Object oDstArr;
FilterRefArray(oSrcVal.get(), hiddenRefs, xref, &oDstArr);
pDstConfig->dictAdd(copyString(key), &oDstArr);
}
else if (!strcmp(key, "Order"))
{
Object oDstArr;
FilterOrderArray(oSrcVal.get(), hiddenRefs, xref, &oDstArr);
pDstConfig->dictAdd(copyString(key), &oDstArr);
}
else if (!strcmp(key, "RBGroups"))
{
Object oDstArr;
FilterRBGroups(oSrcVal.get(), hiddenRefs, xref, &oDstArr);
pDstConfig->dictAdd(copyString(key), &oDstArr);
}
else
{
// BaseState, Name, Creator, Intent, AS
Object oCopy;
oSrcVal.get()->copy(&oCopy);
pDstConfig->dictAdd(copyString(key), &oCopy);
}
}
}
void RemoveHiddenOCGs(Object* pOCProperties, XRef* xref)
{
if (!pOCProperties->isDict())
return;
std::vector<Ref> hiddenRefs;
CollectHiddenOCGRefs(pOCProperties, hiddenRefs);
if (hiddenRefs.empty())
return;
Object oNewOCProps;
oNewOCProps.initDict(xref);
for (int i = 0; i < pOCProperties->dictGetLength(); ++i)
{
char* key = pOCProperties->dictGetKey(i);
CObjectGuard oSrcVal;
pOCProperties->dictGetValNF(i, oSrcVal.get());
if (!strcmp(key, "OCGs"))
{
Object oDstArr;
FilterRefArray(oSrcVal.get(), hiddenRefs, xref, &oDstArr);
oNewOCProps.dictAdd(copyString(key), &oDstArr);
}
else if (!strcmp(key, "D"))
{
Object oDstD;
FilterOCConfig(oSrcVal.get(), hiddenRefs, xref, &oDstD);
oNewOCProps.dictAdd(copyString(key), &oDstD);
}
else if (!strcmp(key, "Configs"))
{
Object oDstConfigs;
oDstConfigs.initArray(xref);
if (oSrcVal.get()->isArray())
{
for (int j = 0; j < oSrcVal.get()->arrayGetLength(); ++j)
{
CObjectGuard oCfg;
oSrcVal.get()->arrayGet(j, oCfg.get());
Object oDstCfg;
FilterOCConfig(oCfg.get(), hiddenRefs, xref, &oDstCfg);
oDstConfigs.arrayAdd(&oDstCfg);
}
}
oNewOCProps.dictAdd(copyString(key), &oDstConfigs);
}
else
{
Object oCopy;
oSrcVal.get()->copy(&oCopy);
oNewOCProps.dictAdd(copyString(key), &oCopy);
}
}
pOCProperties->free();
oNewOCProps.copy(pOCProperties);
oNewOCProps.free();
}
}
namespace
{
void PruneSplitFields(CObjectsManager* pManager, PdfWriter::CDocument* pDoc);
void DeleteEmptyAppendFields(CObjectsManager* pManager, PdfWriter::CDocument* pDoc, const std::set<int>& setFields);
// A checkbox or radio group with Opt whose on states disagree with it - one name for kids of different
// values (a kid the editor sent without its state name is drawn as Yes), or AS naming a state the kid
// lacks - is renamed after Opt. A group with a kid not converted or with no state dict is left as it is
void RenameButtonKidsYes(PdfWriter::CDocument* pDoc)
{
const std::map<int, PdfWriter::CDictObject*>& mParents = pDoc->GetParents();
for (std::map<int, PdfWriter::CDictObject*>::const_iterator it = mParents.begin(); it != mParents.end(); ++it)
{
PdfWriter::CDictObject* pParent = it->second;
PdfWriter::CObjectBase* pKidsObj = pParent ? pParent->Get("Kids") : NULL;
PdfWriter::CObjectBase* pOptObj = pParent ? pParent->Get("Opt") : NULL;
if (!pKidsObj || !pOptObj || pKidsObj->GetType() != PdfWriter::object_type_ARRAY || pOptObj->GetType() != PdfWriter::object_type_ARRAY ||
((PdfWriter::CArrayObject*)pKidsObj)->GetCount() != ((PdfWriter::CArrayObject*)pOptObj)->GetCount() || !IsButtonField(pParent, NULL))
continue;
PdfWriter::CArrayObject* pKids = (PdfWriter::CArrayObject*)pKidsObj;
PdfWriter::CArrayObject* pOpt = (PdfWriter::CArrayObject*)pOptObj;
bool bAll = true, bBroken = false;
std::map<std::string, std::string> mStateValue; // on state -> the kid's export value (Opt bytes)
for (int i = 0; i < pKids->GetCount() && bAll; ++i)
{
PdfWriter::CCheckBoxWidget* pKid = dynamic_cast<PdfWriter::CCheckBoxWidget*>(pKids->Get(i));
PdfWriter::CObjectBase* pAP = pKid ? pKid->Get("AP") : NULL;
PdfWriter::CObjectBase* pAPN = pAP && pAP->GetType() == PdfWriter::object_type_DICT ? ((PdfWriter::CDictObject*)pAP)->Get("N") : NULL;
// an appearance stream, not a state dict: not renamed
if (!pKid || !pAPN || pAPN->GetType() != PdfWriter::object_type_DICT || ((PdfWriter::CDictObject*)pAPN)->GetStream())
{
bAll = false;
break;
}
PdfWriter::CDictObject* pDictAPN = (PdfWriter::CDictObject*)pAPN;
std::string sOn;
for (const std::pair<const std::string, PdfWriter::CObjectBase*>& itState : pDictAPN->GetDict())
{
if (itState.first != "Off")
{
sOn = itState.first;
break;
}
}
PdfWriter::CObjectBase* pAS = pKid->Get("AS");
if (pAS && pAS->GetType() == PdfWriter::object_type_NAME && strcmp(((PdfWriter::CNameObject*)pAS)->Get(), "Off") != 0 && !pDictAPN->Get(((PdfWriter::CNameObject*)pAS)->Get()))
bBroken = true;
// by value: radios not in unison may share a state for one export value
std::string sValue = std::to_string(i);
std::string sBytes;
if (OptValueBytes(pOpt->Get(i), sBytes))
sValue = sBytes;
if (!sOn.empty() && mStateValue.emplace(sOn, sValue).first->second != sValue)
bBroken = true;
}
if (bAll && bBroken)
RenameButtonKids(pParent);
}
}
}
void CPdfEditor::Close()
{
if (m_wsDstFile.empty() && m_nMode != Mode::WriteNew)
return;
if (m_nMode == Mode::Unknown)
{
if (m_wsDstFile != m_wsSrcFile && NSSystemPath::NormalizePath(m_wsDstFile) != NSSystemPath::NormalizePath(m_wsSrcFile))
NSFile::CFileBinary::Copy(m_wsSrcFile, m_wsDstFile);
return;
}
if (m_nMode == Mode::Split)
{
PruneSplitFields(&m_mObjManager, m_pWriter->GetDocument());
m_pWriter->EditClose();
m_pWriter->SaveToFile(m_wsDstFile);
return;
}
if (m_nMode == Mode::WriteNew)
{
PdfWriter::CDocument* pDoc = m_pWriter->GetDocument();
PDFDoc* pPDFDocument = NULL;
PdfReader::CPdfFontList* pFontList = NULL;
int nStartRefID = 0;
m_pReader->GetPageIndex(0, &pPDFDocument, &pFontList, &nStartRefID);
PdfWriter::CPageTree* pPageTree = pDoc->GetPageTree();
const int nPages = pPageTree->GetCount();
for (int i = 0; i < nPages; ++i)
{
PdfWriter::CObjectBase* pObj = pPageTree->GetObj(i);
if (!pObj)
continue;
if (pObj->GetType() != PdfWriter::object_type_DICT)
{
EditPage(i, false, true);
}
/*
else if (bOverlappingObjects)
{
PdfWriter::CPage* pEditedPage = dynamic_cast<PdfWriter::CPage*>(pObj);
if (pEditedPage)
CloseWriteNew_ApplyOverlappingObjects(pEditedPage, m_pReader->GetFontManager(), pFontList, pPDFDocument, i, pDoc);
}
*/
}
CloseWriteNew_FlushAnnotations(m_pReader, &m_mObjManager, pDoc, m_pWriter, m_nRedactInfo);
SyncAcroFormFromReader();
RenameButtonKidsYes(pDoc);
if (!pDoc->GetEncrypt())
CloseWriteNew_ApplyEncryption(m_wsPassword, &m_mObjManager, pPDFDocument, pDoc);
XRef* xref = pPDFDocument->getXRef();
CObjectGuard catDict;
xref->getCatalog(catDict.get());
SDictConvertContext ctx(pDoc, xref, &m_mObjManager, nStartRefID);
PdfWriter::CCatalog* pCatalog = pDoc->GetCatalog();
if (~m_nRedactInfo & (1 << 1))
{
CObjectGuard pNames, oEF, oNames;
if (catDict->dictLookup("Names", pNames.get())->isDict() && pNames->dictLookup("EmbeddedFiles", oEF.get())->isDict())
{
PdfWriter::CDictObject* pNames = pCatalog->GetNames();
pNames->Add("EmbeddedFiles", DictToCDictObjectUnified(oEF.get(), ctx));
}
}
if (~m_nRedactInfo & (1 << 2))
{
PDFDoc* pDocumentFirst = m_pReader->GetPDFDocument(0);
Outline* pOutlineOld = pDocumentFirst->getOutline();
GList* pListOld = NULL;
if (pOutlineOld)
pListOld = pOutlineOld->getItems();
if (!pDoc->GetOutlines() && pListOld)
{
for (int i = 0, num = pListOld->getLength(); i < num; i++)
{
OutlineItem* pOutlineItem = (OutlineItem*)pListOld->get(i);
if (pOutlineItem)
CreateOutlines(pDocumentFirst, pDoc, pOutlineItem, NULL);
}
}
}
if (~m_nRedactInfo & (1 << 3))
CloseWriteNew_ApplyNameTree(pPDFDocument, pDoc);
// Layers, Optional content
CObjectGuard oOC;
if (catDict->dictLookup("OCProperties", oOC.get())->isDict())
{
if (m_nRedactInfo & (1 << 7))
RemoveHiddenOCGs(oOC.get(), xref);
pCatalog->Add("OCProperties", DictToCDictObjectUnified(oOC.get(), ctx));
}
if (m_nRedactInfo & (1 << 8))
pDoc->RemoveUnreachableObjects();
if (~m_nRedactInfo & (1 << 9))
{
CObjectGuard pNames, oEF, oNames;
if (catDict->dictLookup("Names", pNames.get())->isDict() && pNames->dictLookup("EmbeddedFiles", oEF.get())->isDict())
{
PdfWriter::CDictObject* pNames = pCatalog->GetNames();
pNames->Add("JavaScript", DictToCDictObjectUnified(oEF.get(), ctx));
}
}
m_pWriter->EditClose();
m_pWriter->SaveToFile(m_wsDstFile);
return;
}
// m_nMode == Mode::WriteAppend
PDFDoc* pPDFDocument = m_pReader->GetPDFDocument(0);
PdfWriter::CDocument* pDoc = m_pWriter->GetDocument();
DeleteEmptyAppendFields(&m_mObjManager, pDoc, m_setLostKidFields);
RenameButtonKidsYes(pDoc);
PdfWriter::CXref* pXref = NULL;
PdfWriter::CDictObject* pTrailer = NULL;
if (!CloseWriteAppend_BuildTrailer(pPDFDocument, pDoc, &pXref, &pTrailer))
return;
PdfWriter::CXref* pInfoXref = NULL;
PdfWriter::CInfoDict* pInfoDict = NULL;
if (!CloseWriteAppend_UpdateInfo(pPDFDocument, pDoc, pXref, pTrailer, &pInfoXref, &pInfoDict))
{
RELEASEOBJECT(pXref);
return;
}
if (!m_pWriter->EditClose() || !pDoc->AddToFile(m_wsDstFile, pXref, pTrailer, pInfoXref, pInfoDict))
{
RELEASEOBJECT(pXref);
return;
}
CloseWriteAppend_FinalizeSource(m_pReader, m_wsPassword, m_wsDstFile, m_wsSrcFile);
m_pReader = NULL;
m_pWriter = NULL;
m_nEditPage = -1;
}
int CPdfEditor::GetError()
{
return m_nError;
}
void CPdfEditor::GetPageTree(XRef* xref, Object* pPagesRefObj, PdfWriter::CPageTree* pPageParent)
{
PdfWriter::CDocument* pDoc = m_pWriter->GetDocument();
if (!pPagesRefObj || !xref || !pDoc)
return;
Object pagesObj;
if (!pPagesRefObj->isRef() || !pPagesRefObj->fetch(xref, &pagesObj)->isDict("Pages"))
{
pagesObj.free();
return;
}
Ref topPagesRef = pPagesRefObj->getRef();
PdfWriter::CXref* pXref = new PdfWriter::CXref(pDoc, topPagesRef.num);
if (!pXref)
{
pagesObj.free();
return;
}
PdfWriter::CPageTree* pPageT = new PdfWriter::CPageTree();
if (!pPageT)
{
pagesObj.free();
RELEASEOBJECT(pXref);
return;
}
pXref->Add(pPageT, topPagesRef.gen);
SDictConvertContext ctx;
for (int nIndex = 0; nIndex < pagesObj.dictGetLength(); ++nIndex)
{
Object oTemp;
char* chKey = pagesObj.dictGetKey(nIndex);
if (strcmp("Resources", chKey) == 0)
{
if (pagesObj.dictGetVal(nIndex, &oTemp)->isDict())
{
PdfWriter::CResourcesDict* pDict = new PdfWriter::CResourcesDict(NULL, true, false);
pPageT->Add("Resources", pDict);
for (int nIndex = 0; nIndex < oTemp.dictGetLength(); ++nIndex)
{
Object oRes;
char* chKey2 = oTemp.dictGetKey(nIndex);
if (strcmp("Font", chKey2) == 0 || strcmp("ExtGState", chKey2) == 0 || strcmp("XObject", chKey2) == 0 || strcmp("Shading", chKey2) == 0 || strcmp("Pattern", chKey2) == 0)
oTemp.dictGetVal(nIndex, &oRes);
else
oTemp.dictGetValNF(nIndex, &oRes);
DictToCDictObjectUnified(&oRes, ctx, pDict, chKey2);
oRes.free();
}
oTemp.free();
continue;
}
else
{
oTemp.free();
pagesObj.dictGetValNF(nIndex, &oTemp);
}
}
else if (strcmp("Parent", chKey) == 0 && pPageParent)
{
pPageT->Add("Parent", pPageParent);
continue;
}
else
pagesObj.dictGetValNF(nIndex, &oTemp);
DictToCDictObjectUnified(&oTemp, ctx, pPageT, chKey);
oTemp.free();
}
pDoc->CreatePageTree(pXref, pPageT);
pPageT->Fix();
Object kidsArrObj;
if (!pagesObj.dictLookup("Kids", &kidsArrObj)->isArray())
{
pagesObj.free();
kidsArrObj.free();
return;
}
pagesObj.free();
for (int i = 0, count = kidsArrObj.arrayGetLength(); i < count; ++i)
{
Object kidRefObj;
if (kidsArrObj.arrayGetNF(i, &kidRefObj))
GetPageTree(xref, &kidRefObj, pPageT);
kidRefObj.free();
}
kidsArrObj.free();
}
bool CPdfEditor::EditPage(int _nPageIndex, bool bSet, bool bActualPos)
{
if (m_nMode == Mode::Unknown && !IncrementalUpdates())
return false;
WriteRedact({});
m_arrRedact.clear();
PDFDoc* pPDFDocument = NULL;
PdfReader::CPdfFontList* pFontList = NULL;
int nStartRefID = 0;
PdfWriter::CDocument* pDoc = m_pWriter->GetDocument();
if (!pDoc)
return false;
PdfWriter::CPageTree* pPageTree = pDoc->GetPageTree();
PdfWriter::CPage* pEditPage = NULL;
pEditPage = bActualPos ? pDoc->GetPage(_nPageIndex) : pDoc->GetEditPage(_nPageIndex);
if (m_nMode == Mode::Split && !pEditPage)
return false;
if (pEditPage)
{
if (bSet)
{
pDoc->SetCurPage(pEditPage);
m_pWriter->EditPage(pEditPage);
m_nEditPage = _nPageIndex;
}
return true;
}
int nOriginIndex = _nPageIndex;
if (m_nMode == Mode::WriteNew)
{
PdfWriter::CObjectBase* pObj = pPageTree->GetObj(_nPageIndex);
PdfWriter::CFakePage* pFakePage = NULL;
if (pObj)
pFakePage = dynamic_cast<PdfWriter::CFakePage*>(pObj);
if (pFakePage)
nOriginIndex = pFakePage->GetOriginIndex();
}
int nPageIndex = m_pReader->GetPageIndex(nOriginIndex, &pPDFDocument, &pFontList, &nStartRefID);
if (nPageIndex < 0 || !pPDFDocument)
return NULL;
if (m_nMode == Mode::WriteNew)
{
PdfWriter::CPage* pPage = new PdfWriter::CPage(pDoc);
pDoc->AddObject(pPage);
pPageTree->ReplacePage(_nPageIndex, pPage);
pDoc->AddEditPage(pPage, _nPageIndex);
// Get page object
Catalog* pCatalog = pPDFDocument->getCatalog();
XRef* xref = pPDFDocument->getXRef();
Ref* pPageRef = pCatalog->getPageRef(nPageIndex);
Object pageRefObj, pageObj;
pageRefObj.initRef(pPageRef->num, pPageRef->gen);
if (!pageRefObj.fetch(xref, &pageObj)->isDict())
{
pageObj.free(); pageRefObj.free();
return false;
}
m_mObjManager.AddObj(pPageRef->num + nStartRefID, pPage);
pageRefObj.free();
SDictConvertContext ctx(pDoc, xref, &m_mObjManager, nStartRefID);
bool bResources = false, bMediaBox = false, bCropBox = false, bRotate = false;
for (int nIndex = 0; nIndex < pageObj.dictGetLength(); ++nIndex)
{
Object oTemp;
char* chKey = pageObj.dictGetKey(nIndex);
if (strcmp("Resources", chKey) == 0)
{
bResources = true;
Ref oResourcesRef = { -1, -1 };
if (pageObj.dictGetValNF(nIndex, &oTemp)->isRef())
oResourcesRef = oTemp.getRef();
oTemp.free();
PdfWriter::CObjectBase* pObj = oResourcesRef.num > 0 ? m_mObjManager.GetObj(oResourcesRef.num + nStartRefID) : NULL;
if (pObj)
{
pPage->Add(chKey, pObj);
m_mObjManager.IncRefCount(oResourcesRef.num + nStartRefID);
continue;
}
if (pageObj.dictGetVal(nIndex, &oTemp)->isDict())
{
PdfWriter::CResourcesDict* pDict = pDoc->CreateResourcesDict(oResourcesRef.num < 0, false);
if (oResourcesRef.num > 0)
m_mObjManager.AddObj(oResourcesRef.num + nStartRefID, pDict);
pPage->Add(chKey, pDict);
for (int nIndex = 0; nIndex < oTemp.dictGetLength(); ++nIndex)
{
Object oRes;
char* chKey2 = oTemp.dictGetKey(nIndex);
oTemp.dictGetValNF(nIndex, &oRes);
PdfWriter::CObjectBase* pBase = DictToCDictObjectUnified(&oRes, ctx);
pDict->Add(chKey2, pBase);
oRes.free();
}
oTemp.free();
continue;
}
else
{
oTemp.free();
pageObj.dictGetValNF(nIndex, &oTemp);
}
}
else if (strcmp("Parent", chKey) == 0)
{
// Fields from Parent pages are transferred to the pages themselves
oTemp.free();
continue;
}
else if (strcmp("MediaBox", chKey) == 0)
{
bMediaBox = true;
pageObj.dictGetValNF(nIndex, &oTemp);
}
else if (strcmp("CropBox", chKey) == 0)
{
bCropBox = true;
pageObj.dictGetValNF(nIndex, &oTemp);
}
else if (strcmp("Rotate", chKey) == 0)
{
bRotate = true;
pageObj.dictGetValNF(nIndex, &oTemp);
}
else if (strcmp("Contents", chKey) == 0)
{
pageObj.dictGetValNF(nIndex, &oTemp);
ctx.bUndecodedStream = false;
PdfWriter::CObjectBase* pBase = DictToCDictObjectUnified(&oTemp, ctx);
ctx.bUndecodedStream = true;
pPage->Add(chKey, pBase);
continue;
}
else
pageObj.dictGetValNF(nIndex, &oTemp);
PdfWriter::CObjectBase* pBase = DictToCDictObjectUnified(&oTemp, ctx);
pPage->Add(chKey, pBase);
oTemp.free();
}
if (!bResources || !bMediaBox || !bCropBox || !bRotate)
{
Page* pOPage = pCatalog->getPage(nPageIndex);
if (!bMediaBox)
{
PDFRectangle* pRect = pOPage->getMediaBox();
pPage->Add("MediaBox", PdfWriter::CArrayObject::CreateBox(pRect->x1, pRect->y1, pRect->x2, pRect->y2));
}
if (!bCropBox && pOPage->isCropped())
{
PDFRectangle* pRect = pOPage->getCropBox();
pPage->Add("CropBox", PdfWriter::CArrayObject::CreateBox(pRect->x1, pRect->y1, pRect->x2, pRect->y2));
}
if (!bRotate)
pPage->Add("Rotate", pOPage->getRotate());
if (!bResources)
{
Dict* pResources = pOPage->getResourceDict();
PdfWriter::CResourcesDict* pDict = pDoc->CreateResourcesDict(true, false);
pPage->Add("Resources", pDict);
for (int nIndex = 0; nIndex < pResources->getLength(); ++nIndex)
{
Object oRes;
char* chKey2 = pResources->getKey(nIndex);
pResources->getValNF(nIndex, &oRes);
PdfWriter::CObjectBase* pBase = DictToCDictObjectUnified(&oRes, ctx);
pDict->Add(chKey2, pBase);
oRes.free();
}
}
}
pPage->Fix();
pDoc->FixEditPage(pPage);
bool bHiddenText = (m_nRedactInfo & (1 << 6)) != 0;
bool bHiddenLayers = (m_nRedactInfo & (1 << 7)) != 0;
bool bDeletedOrCroppedContent = (m_nRedactInfo & (1 << 8)) != 0;
bool bOverlappingObjects = (m_nRedactInfo & (1 << 10)) != 0;
if (bOverlappingObjects)
{
Page* pOPage = pCatalog->getPage(nPageIndex);
PDFRectangle* box = pOPage->getMediaBox();
Object oContents;
pOPage->getContents(&oContents);
RasterizePageOverContent(m_pReader->GetFontManager(), pFontList, pPDFDocument, _nPageIndex, &oContents, pOPage->getResourceDict(), box, pDoc, pPage);
oContents.free();
}
else if (bHiddenText || bHiddenLayers || bDeletedOrCroppedContent)
{
int nEP = m_nEditPage;
m_nEditPage = _nPageIndex;
Redact(NULL);
m_nEditPage = nEP;
}
if (bSet)
{
pDoc->SetCurPage(pPage);
m_pWriter->EditPage(pPage);
m_nEditPage = _nPageIndex;
if (bCropBox)
{
Page* pOPage = pCatalog->getPage(nPageIndex);
if (pOPage->isCropped())
{
PDFRectangle* pCropBox = pOPage->getCropBox();
PdfWriter::CStream* pStream = pPage->GetStream();
pStream->WriteStr("1 0 0 1 ");
pStream->WriteReal(pCropBox->x1);
pStream->WriteChar(' ');
pStream->WriteReal(pCropBox->y2 - pOPage->getMediaBox()->y2);
pStream->WriteStr(" cm\012");
}
}
double dCTM[6] = { 1, 0, 0, 1, 0, 0 };
double dInvCTM[6] = { 1, 0, 0, 1, 0, 0 };
GetCTM(xref, &pageObj, dCTM);
GetInvertCTM(dCTM, dInvCTM);
pageObj.free();
pPage->Transform(dInvCTM[0], dInvCTM[1], dInvCTM[2], dInvCTM[3], dInvCTM[4], dInvCTM[5]);
pPage->SetStrokeColor(0, 0, 0);
pPage->SetFillColor(0, 0, 0);
pPage->SetExtGrState(pDoc->GetExtGState(255, 255));
pPage->BeginText();
pPage->SetCharSpace(0);
pPage->SetTextRenderingMode(PdfWriter::textrenderingmode_Fill);
pPage->SetHorizontalScaling(100);
pPage->EndText();
}
return true;
}
XRef* xref = pPDFDocument->getXRef();
Catalog* pCatalog = pPDFDocument->getCatalog();
if (!xref || !pCatalog)
return false;
std::pair<int, int> pPageRef = std::make_pair(0, 0);
if (bActualPos)
pPageRef = pDoc->GetPageRef(_nPageIndex);
else
{
Ref* pRef = pPDFDocument->getCatalog()->getPageRef(nPageIndex);
if (pRef && pRef->num != 0)
pPageRef = std::make_pair(pRef->num, pRef->gen);
}
if (pPageRef.first == 0)
return false;
// Get page object
Object pageRefObj, pageObj;
pageRefObj.initRef(pPageRef.first, pPageRef.second);
if (!pageRefObj.fetch(xref, &pageObj) || !pageObj.isDict())
{
pageObj.free();
pageRefObj.free();
return false;
}
pageRefObj.free();
// Reproduce page dictionary from reader for writer
PdfWriter::CXref* pXref = new PdfWriter::CXref(pDoc, pPageRef.first);
if (!pXref)
{
pageObj.free();
return false;
}
PdfWriter::CPage* pPage = new PdfWriter::CPage(pDoc);
if (!pPage)
{
pageObj.free();
RELEASEOBJECT(pXref);
return false;
}
pXref->Add(pPage, pPageRef.second);
bool bResources = false, bMediaBox = false, bCropBox = false, bRotate = false;
SDictConvertContext ctx;
for (int nIndex = 0; nIndex < pageObj.dictGetLength(); ++nIndex)
{
Object oTemp;
char* chKey = pageObj.dictGetKey(nIndex);
if (strcmp("Resources", chKey) == 0)
{
bResources = true;
if (pageObj.dictGetVal(nIndex, &oTemp)->isDict())
{
PdfWriter::CResourcesDict* pDict = new PdfWriter::CResourcesDict(NULL, true, false);
pPage->Add("Resources", pDict);
for (int nIndex = 0; nIndex < oTemp.dictGetLength(); ++nIndex)
{
Object oRes;
char* chKey2 = oTemp.dictGetKey(nIndex);
if (strcmp("Font", chKey2) == 0 || strcmp("ExtGState", chKey2) == 0 || strcmp("XObject", chKey2) == 0 || strcmp("Shading", chKey2) == 0 || strcmp("Pattern", chKey2) == 0)
oTemp.dictGetVal(nIndex, &oRes);
else
oTemp.dictGetValNF(nIndex, &oRes);
DictToCDictObjectUnified(&oRes, ctx, pDict, chKey2);
oRes.free();
}
oTemp.free();
continue;
}
else
{
oTemp.free();
pageObj.dictGetValNF(nIndex, &oTemp);
}
}
else if (strcmp("Annots", chKey) == 0)
{
if (pageObj.dictGetVal(nIndex, &oTemp)->isArray())
{
PdfWriter::CArrayObject* pArray = new PdfWriter::CArrayObject();
pPage->Add("Annots", pArray);
for (int nIndex = 0; nIndex < oTemp.arrayGetLength(); ++nIndex)
{
Object oAnnot;
oTemp.arrayGetNF(nIndex, &oAnnot);
DictToCDictObjectUnified(&oAnnot, ctx, pArray);
oAnnot.free();
}
oTemp.free();
continue;
}
else
{
oTemp.free();
pageObj.dictGetValNF(nIndex, &oTemp);
}
}
else if (strcmp("Contents", chKey) == 0)
{
if (pageObj.dictGetVal(nIndex, &oTemp)->isArray())
{
DictToCDictObjectUnified(&oTemp, ctx, pPage, chKey);
oTemp.free();
continue;
}
else
{
oTemp.free();
pageObj.dictGetValNF(nIndex, &oTemp);
}
}
else if (strcmp("Parent", chKey) == 0)
{
pageObj.dictGetValNF(nIndex, &oTemp);
}
else if (strcmp("MediaBox", chKey) == 0)
{
bMediaBox = true;
pageObj.dictGetValNF(nIndex, &oTemp);
}
else if (strcmp("CropBox", chKey) == 0)
{
bCropBox = true;
pageObj.dictGetValNF(nIndex, &oTemp);
}
else if (strcmp("Rotate", chKey) == 0)
{
bRotate = true;
pageObj.dictGetValNF(nIndex, &oTemp);
}
else
pageObj.dictGetValNF(nIndex, &oTemp);
DictToCDictObjectUnified(&oTemp, ctx, pPage, chKey);
oTemp.free();
}
if (!bResources || !bMediaBox || !bCropBox || !bRotate)
{
Page* pOPage = pCatalog->getPage(nPageIndex);
if (!bMediaBox)
{
PDFRectangle* pRect = pOPage->getMediaBox();
pPage->Add("MediaBox", PdfWriter::CArrayObject::CreateBox(pRect->x1, pRect->y1, pRect->x2, pRect->y2));
}
if (!bCropBox && pOPage->isCropped())
{
PDFRectangle* pRect = pOPage->getCropBox();
pPage->Add("CropBox", PdfWriter::CArrayObject::CreateBox(pRect->x1, pRect->y1, pRect->x2, pRect->y2));
}
if (!bRotate)
pPage->Add("Rotate", pOPage->getRotate());
if (!bResources)
{
Dict* pResources = pOPage->getResourceDict();
PdfWriter::CResourcesDict* pDict = pDoc->CreateResourcesDict(true, false);
pPage->Add("Resources", pDict);
SDictConvertContext ctx;
ctx.bMakeBinary = false;
for (int nIndex = 0; nIndex < pResources->getLength(); ++nIndex)
{
Object oRes;
char* chKey2 = pResources->getKey(nIndex);
if (strcmp("Font", chKey2) == 0 || strcmp("ExtGState", chKey2) == 0 || strcmp("XObject", chKey2) == 0 || strcmp("Shading", chKey2) == 0 || strcmp("Pattern", chKey2) == 0)
pResources->getVal(nIndex, &oRes);
else
pResources->getValNF(nIndex, &oRes);
DictToCDictObjectUnified(&oRes, ctx, pDict, chKey2);
}
}
}
pPage->Fix();
// Apply page editing for writer
if (pDoc->EditPage(pXref, pPage, _nPageIndex))
{
if (bSet)
{
m_pWriter->EditPage(pPage);
m_nEditPage = _nPageIndex;
}
if (bCropBox)
{
Page* pOPage = pCatalog->getPage(nPageIndex);
if (pOPage->isCropped())
{
PDFRectangle* pCropBox = pOPage->getCropBox();
PdfWriter::CStream* pStream = pPage->GetStream();
pStream->WriteStr("1 0 0 1 ");
pStream->WriteReal(pCropBox->x1);
pStream->WriteChar(' ');
pStream->WriteReal(pCropBox->y2 - pOPage->getMediaBox()->y2);
pStream->WriteStr(" cm\012");
}
}
double dCTM[6] = { 1, 0, 0, 1, 0, 0 };
double dInvCTM[6] = { 1, 0, 0, 1, 0, 0 };
GetCTM(xref, &pageObj, dCTM);
GetInvertCTM(dCTM, dInvCTM);
pageObj.free();
pPage->Transform(dInvCTM[0], dInvCTM[1], dInvCTM[2], dInvCTM[3], dInvCTM[4], dInvCTM[5]);
pPage->SetStrokeColor(0, 0, 0);
pPage->SetFillColor(0, 0, 0);
pPage->SetExtGrState(pDoc->GetExtGState(255, 255));
pPage->BeginText();
pPage->SetCharSpace(0);
pPage->SetTextRenderingMode(PdfWriter::textrenderingmode_Fill);
pPage->SetHorizontalScaling(100);
pPage->EndText();
return true;
}
pageObj.free();
RELEASEOBJECT(pXref);
return false;
}
namespace
{
void RemoveOffPageWidgets(CObjectsManager* pManager, PdfWriter::CDocument* pDoc, int nStartRefID, int nEndRefID, const std::vector<PdfWriter::CPage*>& arrPages);
}
bool CPdfEditor::SplitPages(const int* arrPageIndex, unsigned int unLength, PDFDoc* _pDoc, int nStartRefID, bool bIgnoreOO)
{
if (m_nMode == Mode::Unknown)
return false;
PDFDoc* pPDFDocument = _pDoc;
XRef* xref = pPDFDocument->getXRef();
PdfWriter::CDocument* pDoc = m_pWriter->GetDocument();
PdfWriter::CPageTree* pPageTree = pDoc->GetPageTree();
int nPagesBefore = m_pReader->GetNumPagesBefore(pPDFDocument);
if (unLength == 0)
unLength = pPDFDocument->getNumPages();
SDictConvertContext ctx(pDoc, xref, &m_mObjManager, nStartRefID);
ctx.bIgnoreOO = bIgnoreOO;
// Pages must be created in advance to reference them
Catalog* pCatalog = pPDFDocument->getCatalog();
for (unsigned int i = 0; i < unLength; ++i)
{
Ref* pPageRef = pCatalog->getPageRef((arrPageIndex ? arrPageIndex[i] : i) + 1);
if (pPageRef->num == 0)
return false;
PdfWriter::CPage* pPage = new PdfWriter::CPage(pDoc);
pDoc->AddObject(pPage);
if (m_nMode == Mode::WriteAppend || m_nMode == Mode::WriteNew)
pDoc->AddPage(pDoc->GetPagesCount(), pPage);
else
pPageTree->ReplacePage(nPagesBefore + (arrPageIndex ? arrPageIndex[i] : i), pPage);
pDoc->AddEditPage(pPage, nPagesBefore + (arrPageIndex ? arrPageIndex[i] : i));
// Get page object
Object pageRefObj, pageObj;
pageRefObj.initRef(pPageRef->num, pPageRef->gen);
if (!pageRefObj.fetch(xref, &pageObj)->isDict())
{
pageObj.free(); pageRefObj.free();
return false;
}
m_mObjManager.AddObj(pPageRef->num + nStartRefID, pPage);
pageObj.free(); pageRefObj.free();
}
std::vector<PdfWriter::CPage*> arrPages;
for (unsigned int i = 0; i < unLength; ++i)
{
Ref* pPageRef = pCatalog->getPageRef((arrPageIndex ? arrPageIndex[i] : i) + 1);
if (pPageRef->num == 0)
return false;
// Get page object
PdfWriter::CPage* pPage = (PdfWriter::CPage*)m_mObjManager.GetObj(pPageRef->num + nStartRefID);
arrPages.push_back(pPage);
Object pageRefObj, pageObj;
pageRefObj.initRef(pPageRef->num, pPageRef->gen);
if (!pageRefObj.fetch(xref, &pageObj)->isDict())
{
pageObj.free();
pageRefObj.free();
return false;
}
pageRefObj.free();
bool bResources = false, bMediaBox = false, bCropBox = false, bRotate = false;
// Copy page with all resources from reader for writer
for (int nIndex = 0; nIndex < pageObj.dictGetLength(); ++nIndex)
{
Object oTemp;
char* chKey = pageObj.dictGetKey(nIndex);
if (strcmp("Resources", chKey) == 0)
{
bResources = true;
Ref oResourcesRef = { -1, -1 };
if (pageObj.dictGetValNF(nIndex, &oTemp)->isRef())
oResourcesRef = oTemp.getRef();
oTemp.free();
PdfWriter::CObjectBase* pObj = oResourcesRef.num > 0 ? m_mObjManager.GetObj(oResourcesRef.num + nStartRefID) : NULL;
if (pObj)
{
pPage->Add(chKey, pObj);
m_mObjManager.IncRefCount(oResourcesRef.num + nStartRefID);
continue;
}
if (pageObj.dictGetVal(nIndex, &oTemp)->isDict())
{
PdfWriter::CResourcesDict* pDict = pDoc->CreateResourcesDict(oResourcesRef.num < 0, false);
if (oResourcesRef.num > 0)
m_mObjManager.AddObj(oResourcesRef.num + nStartRefID, pDict);
pPage->Add(chKey, pDict);
for (int nIndex2 = 0; nIndex2 < oTemp.dictGetLength(); ++nIndex2)
{
Object oRes;
char* chKey2 = oTemp.dictGetKey(nIndex2);
oTemp.dictGetValNF(nIndex2, &oRes);
PdfWriter::CObjectBase* pBase = DictToCDictObjectUnified(&oRes, ctx);
pDict->Add(chKey2, pBase);
oRes.free();
}
oTemp.free();
continue;
}
else
{
oTemp.free();
pageObj.dictGetValNF(nIndex, &oTemp);
}
}
else if (strcmp("Parent", chKey) == 0)
{
oTemp.free();
continue;
}
else if (strcmp("MediaBox", chKey) == 0)
{
bMediaBox = true;
pageObj.dictGetValNF(nIndex, &oTemp);
}
else if (strcmp("CropBox", chKey) == 0)
{
bCropBox = true;
pageObj.dictGetValNF(nIndex, &oTemp);
}
else if (strcmp("Rotate", chKey) == 0)
{
bRotate = true;
pageObj.dictGetValNF(nIndex, &oTemp);
}
else
pageObj.dictGetValNF(nIndex, &oTemp);
PdfWriter::CObjectBase* pBase = DictToCDictObjectUnified(&oTemp, ctx);
pPage->Add(chKey, pBase);
if (strcmp("Contents", chKey) == 0)
{
if (pBase->GetType() == PdfWriter::object_type_ARRAY)
{
PdfWriter::CArrayObject* pArr = (PdfWriter::CArrayObject*)pBase;
for (int j = 0; j < pArr->GetCount(); ++j)
{
pBase = pArr->Get(j);
if (pBase->GetType() == PdfWriter::object_type_DICT)
{
PdfWriter::CDictObject* pDict = (PdfWriter::CDictObject*)pBase;
if (pDict->Get("Filter"))
pDict->SetFilter(STREAM_FILTER_ALREADY_DECODE);
}
}
}
else if (pBase->GetType() == PdfWriter::object_type_DICT)
{
PdfWriter::CDictObject* pDict = (PdfWriter::CDictObject*)pBase;
if (pDict->Get("Filter"))
pDict->SetFilter(STREAM_FILTER_ALREADY_DECODE);
}
}
oTemp.free();
}
if (!bResources || !bMediaBox || !bCropBox || !bRotate)
{
Page* pOPage = pCatalog->getPage((arrPageIndex ? arrPageIndex[i] : i) + 1);
if (!bMediaBox)
{
PDFRectangle* pRect = pOPage->getMediaBox();
pPage->Add("MediaBox", PdfWriter::CArrayObject::CreateBox(pRect->x1, pRect->y1, pRect->x2, pRect->y2));
}
if (!bCropBox && pOPage->isCropped())
{
PDFRectangle* pRect = pOPage->getCropBox();
pPage->Add("CropBox", PdfWriter::CArrayObject::CreateBox(pRect->x1, pRect->y1, pRect->x2, pRect->y2));
}
if (!bRotate)
pPage->Add("Rotate", pOPage->getRotate());
if (!bResources)
{
Dict* pResources = pOPage->getResourceDict();
PdfWriter::CResourcesDict* pDict = pDoc->CreateResourcesDict(true, false);
pPage->Add("Resources", pDict);
for (int nIndex = 0; nIndex < pResources->getLength(); ++nIndex)
{
Object oRes;
char* chKey2 = pResources->getKey(nIndex);
pResources->getValNF(nIndex, &oRes);
PdfWriter::CObjectBase* pBase = DictToCDictObjectUnified(&oRes, ctx);
pDict->Add(chKey2, pBase);
oRes.free();
}
}
}
pPage->Fix();
if (m_nMode == Mode::WriteAppend || m_nMode == Mode::WriteNew)
{
pDoc->FixEditPage(pPage);
double dCTM[6] = { 1, 0, 0, 1, 0, 0 };
double dInvCTM[6] = { 1, 0, 0, 1, 0, 0 };
GetCTM(xref, &pageObj, dCTM);
GetInvertCTM(dCTM, dInvCTM);
pPage->Transform(dInvCTM[0], dInvCTM[1], dInvCTM[2], dInvCTM[3], dInvCTM[4], dInvCTM[5]);
pPage->SetStrokeColor(0, 0, 0);
pPage->SetFillColor(0, 0, 0);
pPage->SetExtGrState(pDoc->GetExtGState(255, 255));
pPage->BeginText();
pPage->SetCharSpace(0);
pPage->SetTextRenderingMode(PdfWriter::textrenderingmode_Fill);
pPage->SetHorizontalScaling(100);
pPage->EndText();
}
else
m_pWriter->SetNeedAddHelvetica(false); // TODO adds font for proper Adobe pdf editing without text. Remove when implementing font map
pageObj.free();
}
// off-page widgets go before SyncFieldKids and the Fields below take them in
RemoveOffPageWidgets(&m_mObjManager, pDoc, nStartRefID, nStartRefID + xref->getNumObjects(), arrPages);
Object oCatalog;
if (!xref->getCatalog(&oCatalog)->isDict())
{
oCatalog.free();
return false;
}
Object oAcroForm;
if (oCatalog.dictLookupNF("AcroForm", &oAcroForm)->isRef() || oAcroForm.isDict())
{
PdfWriter::CDictObject* pAcroForm = pDoc->GetAcroForm();
if (!pAcroForm)
{
pAcroForm = new PdfWriter::CDictObject();
if (oAcroForm.isRef())
pDoc->AddObject(pAcroForm);
pDoc->SetAcroForm(pAcroForm);
}
else
pAcroForm->Remove("NeedAppearances");
if (oAcroForm.isRef())
{
oAcroForm.free();
if (!oCatalog.dictLookup("AcroForm", &oAcroForm)->isDict())
{
oAcroForm.free(); oCatalog.free();
return false;
}
}
// Kids in the reader's order; Opt of a button group is cut to the kids of the pages taken
SyncFieldKids(&oAcroForm, xref, m_mObjManager, nStartRefID);
for (int nIndex = 0; nIndex < oAcroForm.dictGetLength(); ++nIndex)
{
Object oTemp;
char* chKey = oAcroForm.dictGetKey(nIndex);
if (strcmp("Fields", chKey) == 0)
{
Ref oFieldsRef = { -1, -1 };
if (oAcroForm.dictGetValNF(nIndex, &oTemp)->isRef())
oFieldsRef = oTemp.getRef();
oTemp.free();
PdfWriter::CArrayObject* pFields = dynamic_cast<PdfWriter::CArrayObject*>(pAcroForm->Get("Fields"));
if (!pFields)
{
PdfWriter::CObjectBase* pObj = oFieldsRef.num > 0 ? m_mObjManager.GetObj(oFieldsRef.num + nStartRefID) : NULL;
if (pObj)
{
pAcroForm->Add(chKey, pObj);
m_mObjManager.IncRefCount(oFieldsRef.num + nStartRefID);
continue;
}
}
if (oAcroForm.dictGetVal(nIndex, &oTemp)->isArray())
{
if (!pFields)
{
pFields = new PdfWriter::CArrayObject();
if (oFieldsRef.num > 0)
{
pDoc->AddObject(pFields);
m_mObjManager.AddObj(oFieldsRef.num + nStartRefID, pFields);
}
pAcroForm->Add(chKey, pFields);
}
for (int nIndex = 0; nIndex < oTemp.arrayGetLength(); ++nIndex)
{
Object oRes;
PdfWriter::CObjectBase* pObj = NULL;
if (oTemp.arrayGetNF(nIndex, &oRes)->isRef())
pObj = m_mObjManager.GetObj(oRes.getRefNum() + nStartRefID);
if (pObj)
{
pFields->Add(pObj);
m_mObjManager.IncRefCount(oRes.getRefNum() + nStartRefID);
AddWidgetParent(pDoc, &m_mObjManager, pObj);
oRes.free();
continue;
}
oRes.free();
}
oTemp.free();
continue;
}
else if (!pFields)
{
oTemp.free();
oAcroForm.dictGetValNF(nIndex, &oTemp);
}
else
{
oTemp.free();
continue;
}
}
else if (strcmp("CO", chKey) == 0)
{
// only the fields the pages brought in: a field of a page split away would come with its V
MergeRefArray(oAcroForm, nIndex, chKey, pDoc, m_mObjManager, nStartRefID, false);
continue;
}
else if (strcmp("SigFlags", chKey) == 0 || strcmp("XFA", chKey) == 0 || (strcmp("DA", chKey) == 0 && pAcroForm->Get("DA")) || strcmp("NeedAppearances", chKey) == 0)
{ // Cannot guarantee their execution
oTemp.free();
continue;
}
else if (strcmp("DR", chKey) == 0)
{ // Add only unique keys
PdfWriter::CDictObject* pDR = dynamic_cast<PdfWriter::CDictObject*>(pAcroForm->Get("DR"));
if (!pDR)
{
pDR = new PdfWriter::CDictObject();
pDoc->AddObject(pDR);
pAcroForm->Add(chKey, pDR);
}
PdfWriter::CArrayObject* pProcset = new PdfWriter::CArrayObject();
pDR->Add("ProcSet", pProcset);
pProcset->Add(new PdfWriter::CNameObject("PDF"));
pProcset->Add(new PdfWriter::CNameObject("Text"));
pProcset->Add(new PdfWriter::CNameObject("ImageB"));
pProcset->Add(new PdfWriter::CNameObject("ImageC"));
pProcset->Add(new PdfWriter::CNameObject("ImageI"));
if (oAcroForm.dictGetVal(nIndex, &oTemp)->isDict())
{
Object oTemp2;
for (int nIndex2 = 0; nIndex2 < oTemp.dictGetLength(); ++nIndex2)
{
char* chKey2 = oTemp.dictGetKey(nIndex2);
if (strcmp("ProcSet", chKey2) == 0 || !oTemp.dictGetVal(nIndex2, &oTemp2)->isDict())
{
oTemp2.free();
continue;
}
PdfWriter::CDictObject* pDict = dynamic_cast<PdfWriter::CDictObject*>(pDR->Get(chKey2));
if (!pDict)
{
Object oTempRef;
if (oTemp.dictGetValNF(nIndex2, &oTempRef)->isRef())
{
PdfWriter::CObjectBase* pObj = m_mObjManager.GetObj(oTempRef.getRefNum() + nStartRefID);
if (pObj)
{
pDR->Add(chKey2, pObj);
m_mObjManager.IncRefCount(oTempRef.getRefNum() + nStartRefID);
oTemp2.free(); oTempRef.free();
continue;
}
}
PdfWriter::CObjectBase* pBase = DictToCDictObjectUnified(&oTemp2, ctx);
if (oTempRef.isRef())
pDoc->AddObject(pBase);
pDR->Add(chKey2, pBase);
oTemp2.free(); oTempRef.free();
continue;
}
else
{
for (int nIndex3 = 0; nIndex3 < oTemp2.dictGetLength(); ++nIndex3)
{
char* chKey3 = oTemp2.dictGetKey(nIndex3);
if (pDict->Get(chKey3))
continue;
Object oTempRef;
if (oTemp2.dictGetValNF(nIndex3, &oTempRef)->isRef())
{
PdfWriter::CObjectBase* pObj = m_mObjManager.GetObj(oTempRef.getRefNum() + nStartRefID);
if (pObj)
{
pDict->Add(chKey3, pObj);
m_mObjManager.IncRefCount(oTempRef.getRefNum() + nStartRefID);
oTemp2.free(); oTempRef.free();
continue;
}
}
PdfWriter::CObjectBase* pBase = DictToCDictObjectUnified(&oTemp2, ctx);
if (oTempRef.isRef())
pDoc->AddObject(pBase);
pDict->Add(chKey3, pBase);
oTemp2.free(); oTempRef.free();
continue;
}
}
}
oTemp2.free();
Dict* pResources = oTemp.getDict();
ScanFonts(pPDFDocument, pResources, m_pWriter, &m_mObjManager, m_pReader);
oTemp.free();
continue;
}
else
{
oTemp.free();
oAcroForm.dictGetValNF(nIndex, &oTemp);
}
}
else
oAcroForm.dictGetValNF(nIndex, &oTemp);
PdfWriter::CObjectBase* pBase = DictToCDictObjectUnified(&oTemp, ctx);
pAcroForm->Add(chKey, pBase);
oTemp.free();
}
}
oAcroForm.free(); oCatalog.free();
// Renaming fields
std::string sPrefix = m_pReader->GetPrefixForm(pPDFDocument);
if (!sPrefix.empty())
{
sPrefix = "_" + sPrefix;
std::vector<int> arrRename; // Vector of renamed fields
std::map<int, PdfWriter::CAnnotation*> mAnnots = m_pWriter->GetDocument()->GetAnnots();
for (auto it = mAnnots.begin(); it != mAnnots.end(); it++)
{
PdfWriter::CAnnotation* pAnnot = it->second;
if (pAnnot->GetAnnotationType() != PdfWriter::AnnotWidget || it->first < nStartRefID)
continue;
std::vector<int>::iterator it2 = std::find(arrRename.begin(), arrRename.end(), it->first);
if (it2 != arrRename.end())
continue;
PdfWriter::CObjectBase* pObjBase = pAnnot->Get("Parent");
if (!pObjBase || !ChangeFullNameParent(m_mObjManager.FindObj(pObjBase), sPrefix, arrRename))
{
pObjBase = pAnnot->Get("T");
if (pObjBase && pObjBase->GetType() == PdfWriter::object_type_STRING)
{
PdfWriter::CStringObject* pStr = (PdfWriter::CStringObject*)pObjBase;
pStr->Add(sPrefix.c_str());
}
else if (pObjBase && pObjBase->GetType() == PdfWriter::object_type_BINARY)
{
PdfWriter::CBinaryObject* pBin = (PdfWriter::CBinaryObject*)pObjBase;
pBin->Add((BYTE*)sPrefix.c_str(), sPrefix.length());
}
}
arrRename.push_back(it->first);
}
}
return true;
}
bool CPdfEditor::SplitPages(const int* arrPageIndex, unsigned int unLength)
{
if (m_nMode != Mode::Split)
return true;
PdfWriter::CDocument* pDoc = m_pWriter->GetDocument();
if (!pDoc)
return false;
PdfWriter::CPageTree* pPageTree = pDoc->GetPageTree();
if (!pPageTree)
return false;
m_mObjManager.SetDoc(pDoc);
int nPages = m_pReader->GetNumPages();
pPageTree->CreateFakePages(nPages);
int nTotalPages = 0;
int nPDFIndex = 0;
std::map<int, std::vector<int>> mFileToPages;
PDFDoc* pPDFDocument = m_pReader->GetPDFDocument(nPDFIndex);
nPages = pPDFDocument->getNumPages();
if (!arrPageIndex || unLength == 0)
{
unLength = m_pReader->GetNumPages();
arrPageIndex = NULL;
}
for (unsigned int i = 0; i < unLength; ++i)
{
int nPageIndex = arrPageIndex ? arrPageIndex[i] : i;
if (nPageIndex < nTotalPages + nPages)
mFileToPages[nPDFIndex].push_back(nPageIndex - nTotalPages);
else
{
pPDFDocument = m_pReader->GetPDFDocument(++nPDFIndex);
if (!pPDFDocument)
{
m_mObjManager.m_arrSplitAddPages.push_back(nPageIndex);
--nPDFIndex;
continue;
}
nTotalPages += nPages;
nPages = pPDFDocument->getNumPages();
--i;
}
}
for (const std::pair<const int, std::vector<int>>& it : mFileToPages)
{
pPDFDocument = m_pReader->GetPDFDocument(it.first);
if (!SplitPages(it.second.data(), it.second.size(), pPDFDocument, m_pReader->GetStartRefID(pPDFDocument), false))
return false;
}
return true;
}
void CPdfEditor::AfterSplitPages()
{
PdfWriter::CDocument* pDoc = m_pWriter->GetDocument();
if (!pDoc)
return;
PdfWriter::CPageTree* pPageTree = pDoc->GetPageTree();
if (!pPageTree)
return;
// The editor's changes are in: a field converted for them (EditWidgets) brings its kids with no P,
// those of the pages split away too; then a field with no widget left, and states named Yes
std::vector<PdfWriter::CPage*> arrPages;
for (unsigned int i = 0; i < pPageTree->GetCount(); ++i)
arrPages.push_back(pPageTree->GetPage(i)); // NULL - a page not in the part
RemoveOffPageWidgets(&m_mObjManager, pDoc, 0, INT_MAX, arrPages);
PruneSplitFields(&m_mObjManager, pDoc);
RenameButtonKidsYes(pDoc);
pPageTree->ClearFakePages();
// what nothing in the part refers to leaves it: the appearance streams of the widgets the editor sent
// (AddAnnotField removes them: they show the old values), the objects of what was taken out
pDoc->RemoveUnreachableObjects();
}
bool CPdfEditor::ChangeFullNameParent(int nParent, const std::string& sPrefix, std::vector<int>& arrRename)
{
std::vector<int>::const_iterator it2 = std::find(arrRename.begin(), arrRename.end(), nParent);
if (it2 != arrRename.end())
return true;
PdfWriter::CObjectBase* pObjBase = m_mObjManager.GetObj(nParent);
if (!pObjBase || pObjBase->GetType() != PdfWriter::object_type_DICT)
return false;
PdfWriter::CDictObject* pDict = (PdfWriter::CDictObject*)pObjBase;
// marked before the recursion: a Parent chain that loops back stops here
arrRename.push_back(nParent);
pObjBase = pDict->Get("Parent");
if (!pObjBase || !ChangeFullNameParent(m_mObjManager.FindObj(pObjBase), sPrefix, arrRename))
{
pObjBase = pDict->Get("T");
if (pObjBase && pObjBase->GetType() == PdfWriter::object_type_STRING)
{
PdfWriter::CStringObject* pStr = (PdfWriter::CStringObject*)pObjBase;
pStr->Add(sPrefix.c_str());
}
else if (pObjBase && pObjBase->GetType() == PdfWriter::object_type_BINARY)
{
PdfWriter::CBinaryObject* pBin = (PdfWriter::CBinaryObject*)pObjBase;
pBin->Add((BYTE*)sPrefix.c_str(), sPrefix.length());
}
}
return true;
}
bool CPdfEditor::MergePages(const std::string& sOutlineTitle)
{
if (m_nMode == Mode::Unknown && !IncrementalUpdates())
return false;
m_nOriginIndex = m_pReader->GetNumPages();
PDFDoc* pDocument = m_pReader->GetLastPDFDocument();
int nStartRefID = m_pReader->GetStartRefID(pDocument);
m_mObjManager.SetDoc(m_pWriter->GetDocument());
bool bRes = SplitPages(NULL, 0, pDocument, nStartRefID, true);
if (!bRes)
return false;
Outline* pOutlineAdd = pDocument->getOutline();
GList* pListAdd = NULL;
if (pOutlineAdd)
pListAdd = pOutlineAdd->getItems();
if (!pListAdd)
return bRes;
PdfWriter::CDocument* pDoc = m_pWriter->GetDocument();
PDFDoc* pDocumentFirst = m_pReader->GetPDFDocument(0);
Outline* pOutlineOld = pDocumentFirst->getOutline();
GList* pListOld = NULL;
if (pOutlineOld)
pListOld = pOutlineOld->getItems();
if (!pDoc->GetOutlines() && pListOld)
{
for (int i = 0, num = pListOld->getLength(); i < num; i++)
{
OutlineItem* pOutlineItem = (OutlineItem*)pListOld->get(i);
if (pOutlineItem)
CreateOutlines(pDocumentFirst, pDoc, pOutlineItem, NULL);
}
}
PdfWriter::COutline* pOutline = pDoc->CreateOutline(NULL, sOutlineTitle.c_str());
for (int i = 0, num = pListAdd->getLength(); i < num; i++)
{
OutlineItem* pOutlineItem = (OutlineItem*)pListAdd->get(i);
if (pOutlineItem)
CreateOutlines(pDocumentFirst, pDoc, pOutlineItem, pOutline);
}
return bRes;
}
bool CPdfEditor::PrintPages(const std::vector<bool>& arrPages, int nFlag)
{
if (m_nMode != Mode::Unknown)
return false;
PdfWriter::CDocument* pDoc = m_pWriter->GetDocument();
PdfWriter::CPageTree* pPageTree = pDoc->GetPageTree();
m_mObjManager.SetDoc(pDoc);
for (int i = 0; i < arrPages.size(); ++i)
{
if (!arrPages[i])
continue;
PdfWriter::CPage* pPage = new PdfWriter::CPage(pDoc);
pDoc->AddObject(pPage);
pPageTree->AddPage(pPage);
pDoc->AddEditPage(pPage, i);
PDFDoc* pPDFDocument = NULL;
PdfReader::CPdfFontList* pFontList = NULL;
int nStartRefID = 0;
int nPageIndex = m_pReader->GetPageIndex(i, &pPDFDocument, &pFontList, &nStartRefID);
Catalog* pCatalog = pPDFDocument->getCatalog();
XRef* xref = pPDFDocument->getXRef();
Ref* pPageRef = pCatalog->getPageRef(nPageIndex);
Object pageRefObj, pageObj;
pageRefObj.initRef(pPageRef->num, pPageRef->gen);
if (!pageRefObj.fetch(xref, &pageObj)->isDict())
{
pageObj.free(); pageRefObj.free();
continue;
}
m_mObjManager.AddObj(pPageRef->num + nStartRefID, pPage);
pageRefObj.free();
SDictConvertContext ctx(pDoc, xref, &m_mObjManager, nStartRefID);
bool bResources = false, bMediaBox = false, bCropBox = false, bRotate = false;
for (int nIndex = 0; nIndex < pageObj.dictGetLength(); ++nIndex)
{
Object oTemp;
char* chKey = pageObj.dictGetKey(nIndex);
if (strcmp("Resources", chKey) == 0)
{
bResources = true;
Ref oResourcesRef = { -1, -1 };
if (pageObj.dictGetValNF(nIndex, &oTemp)->isRef())
oResourcesRef = oTemp.getRef();
oTemp.free();
PdfWriter::CObjectBase* pObj = oResourcesRef.num > 0 ? m_mObjManager.GetObj(oResourcesRef.num + nStartRefID) : NULL;
if (pObj)
{
pPage->Add(chKey, pObj);
m_mObjManager.IncRefCount(oResourcesRef.num + nStartRefID);
continue;
}
if (pageObj.dictGetVal(nIndex, &oTemp)->isDict())
{
PdfWriter::CResourcesDict* pDict = pDoc->CreateResourcesDict(oResourcesRef.num < 0, false);
if (oResourcesRef.num > 0)
m_mObjManager.AddObj(oResourcesRef.num + nStartRefID, pDict);
pPage->Add(chKey, pDict);
for (int nIndex = 0; nIndex < oTemp.dictGetLength(); ++nIndex)
{
Object oRes;
char* chKey2 = oTemp.dictGetKey(nIndex);
oTemp.dictGetValNF(nIndex, &oRes);
PdfWriter::CObjectBase* pBase = DictToCDictObjectUnified(&oRes, ctx);
pDict->Add(chKey2, pBase);
oRes.free();
}
oTemp.free();
continue;
}
else
{
oTemp.free();
pageObj.dictGetValNF(nIndex, &oTemp);
}
}
else if (strcmp("Parent", chKey) == 0)
{
// Fields from Parent pages are transferred to the pages themselves
oTemp.free();
continue;
}
else if (strcmp("MediaBox", chKey) == 0)
{
bMediaBox = true;
pageObj.dictGetValNF(nIndex, &oTemp);
}
else if (strcmp("CropBox", chKey) == 0)
{
bCropBox = true;
pageObj.dictGetValNF(nIndex, &oTemp);
}
else if (strcmp("Rotate", chKey) == 0)
{
bRotate = true;
pageObj.dictGetValNF(nIndex, &oTemp);
}
else if (strcmp("Contents", chKey) == 0)
{
if (nFlag != 3)
{
pageObj.dictGetValNF(nIndex, &oTemp);
ctx.bUndecodedStream = false;
PdfWriter::CObjectBase* pBase = DictToCDictObjectUnified(&oTemp, ctx);
ctx.bUndecodedStream = true;
pPage->Add(chKey, pBase);
}
continue;
}
else if (strcmp("Annots", chKey) == 0)
{
oTemp.free();
continue;
}
else
pageObj.dictGetValNF(nIndex, &oTemp);
PdfWriter::CObjectBase* pBase = DictToCDictObjectUnified(&oTemp, ctx);
pPage->Add(chKey, pBase);
oTemp.free();
}
if (!bResources || !bMediaBox || !bCropBox || !bRotate)
{
Page* pOPage = pCatalog->getPage(nPageIndex);
if (!bMediaBox)
{
PDFRectangle* pRect = pOPage->getMediaBox();
pPage->Add("MediaBox", PdfWriter::CArrayObject::CreateBox(pRect->x1, pRect->y1, pRect->x2, pRect->y2));
}
if (!bCropBox && pOPage->isCropped())
{
PDFRectangle* pRect = pOPage->getCropBox();
pPage->Add("CropBox", PdfWriter::CArrayObject::CreateBox(pRect->x1, pRect->y1, pRect->x2, pRect->y2));
}
if (!bRotate)
pPage->Add("Rotate", pOPage->getRotate());
if (!bResources)
{
Dict* pResources = pOPage->getResourceDict();
PdfWriter::CResourcesDict* pDict = pDoc->CreateResourcesDict(true, false);
pPage->Add("Resources", pDict);
for (int nIndex = 0; nIndex < pResources->getLength(); ++nIndex)
{
Object oRes;
char* chKey2 = pResources->getKey(nIndex);
pResources->getValNF(nIndex, &oRes);
PdfWriter::CObjectBase* pBase = DictToCDictObjectUnified(&oRes, ctx);
pDict->Add(chKey2, pBase);
oRes.free();
}
}
}
pPage->Fix();
pDoc->FixEditPage(pPage);
double dCTM[6] = { 1, 0, 0, 1, 0, 0 };
double dInvCTM[6] = { 1, 0, 0, 1, 0, 0 };
GetCTM(xref, &pageObj, dCTM);
GetInvertCTM(dCTM, dInvCTM);
pPage->Transform(dInvCTM[0], dInvCTM[1], dInvCTM[2], dInvCTM[3], dInvCTM[4], dInvCTM[5]);
pPage->SetStrokeColor(0, 0, 0);
pPage->SetFillColor(0, 0, 0);
pPage->SetExtGrState(pDoc->GetExtGState(255, 255));
pPage->BeginText();
pPage->SetCharSpace(0);
pPage->SetTextRenderingMode(PdfWriter::textrenderingmode_Fill);
pPage->SetHorizontalScaling(100);
pPage->EndText();
Object oAnnots;
if (!pageObj.dictLookup("Annots", &oAnnots)->isArray())
{
pageObj.free();
continue;
}
for (int i = 0; i < oAnnots.arrayGetLength(); ++i)
{
Object oAnnot, oType, oObj;
if (!oAnnots.arrayGet(i, &oAnnot)->isDict() || !oAnnot.dictLookup("Subtype", &oType)->isName() || !oAnnot.dictLookup("F", &oObj)->isInt())
{
oAnnot.free(); oType.free(); oObj.free();
continue;
}
int nFlags = oObj.getInt();
oObj.free();
// if Invisible || Hidden || !Print
if ((nFlags & (1 << 0)) || (nFlags & (1 << 1)) || !(nFlags & (1 << 2)))
{
oAnnot.free(); oType.free();
continue;
}
char* sType = oType.getName();
if (((nFlag == 0 || nFlag == 3) && strcmp(sType, "Widget")) || (nFlag == 2 && strcmp(sType, "Stamp") && strcmp(sType, "Widget")))
{
oType.free(); oAnnot.free();
continue;
}
oType.free();
// TODO Should we generate appearance for those who don't have it
DrawAnnot(&oAnnot, pPage, pDoc, xref, &m_mObjManager, nStartRefID);
oAnnot.free();
}
oAnnots.free();
pageObj.free();
}
return true;
}
namespace
{
bool RemoveParentKid(PdfWriter::CDictObject* pParent, PdfWriter::CDictObject* pKid, int nKidID);
int RemoveArrayItem(PdfWriter::CArrayObject* pArray, PdfWriter::CObjectBase* pKid, int nKidID);
bool HasButtonOpt(PdfWriter::CDictObject* pField, PdfWriter::CDictObject* pKid);
// WriteAppend: the widgets of a deleted page of the original file leave Fields and their field's Kids
// (setLostKid gets the field, setDeleted the widget). A checkbox or radio group with Opt keeps its kids
// (HasButtonOpt): the editor's Opt and V index still count them
void RemovePageWidgets(PDFDoc* pPDFDocument, PdfWriter::CDocument* pDoc, PdfWriter::CObjectBase* pPageObj, std::set<int>& setLostKid, std::set<int>& setDeleted)
{
PdfWriter::CDictObject* pAcroForm = pDoc->GetAcroForm();
PdfWriter::CObjectBase* pFieldsObj = pAcroForm ? pAcroForm->Get("Fields") : NULL;
if (!pPDFDocument || !pPageObj || !pPageObj->GetObjId() || !pFieldsObj || pFieldsObj->GetType() != PdfWriter::object_type_ARRAY)
return;
PdfWriter::CArrayObject* pFields = (PdfWriter::CArrayObject*)pFieldsObj;
XRef* xref = pPDFDocument->getXRef();
CObjectGuard oPage, oAnnots;
if (!xref->fetch(pPageObj->GetObjId(), pPageObj->GetGenNo(), oPage.get())->isDict() || !oPage->dictLookup("Annots", oAnnots.get())->isArray())
return;
for (int i = 0; i < oAnnots->arrayGetLength(); ++i)
{
CObjectGuard oAnnotRef, oNode, oType;
if (!oAnnots->arrayGetNF(i, oAnnotRef.get())->isRef() || !oAnnotRef->fetch(xref, oNode.get())->isDict() || !oNode->dictLookup("Subtype", oType.get())->isName("Widget"))
continue;
int nKid = oAnnotRef->getRefNum();
RemoveArrayItem(pFields, NULL, nKid);
CObjectGuard oParentRef;
PdfWriter::CDictObject* pParent = oNode->dictLookupNF("Parent", oParentRef.get())->isRef() ? GetWidgetParent(pPDFDocument, pDoc, oParentRef.get(), 0) : NULL;
if (pParent && HasButtonOpt(pParent, NULL))
continue;
if (pParent)
{
RemoveParentKid(pParent, NULL, nKid);
setLostKid.insert(oParentRef->getRefNum());
}
setDeleted.insert(nKid);
}
}
}
bool CPdfEditor::DeletePage(int nPageIndex)
{
if (m_nMode == Mode::Unknown && !IncrementalUpdates())
return false;
WriteRedact({});
m_arrRedact.clear();
PdfWriter::CDocument* pDoc = m_pWriter->GetDocument();
PdfWriter::CPage* pPage = pDoc->GetPage(nPageIndex);
int nObjID = m_mObjManager.FindObj(pPage);
if (pPage && nObjID > 0)
{
// A converted page (a merged file's; WriteNew, Split) takes its widgets out of the field tree:
// else their field's Kids keeps them, on no page
std::vector<int> arrWidgets;
PdfWriter::CObjectBase* pAnnotsObj = pPage->Get("Annots");
if (pAnnotsObj && pAnnotsObj->GetType() == PdfWriter::object_type_ARRAY)
{
PdfWriter::CArrayObject* pAnnots = (PdfWriter::CArrayObject*)pAnnotsObj;
for (int i = 0; i < pAnnots->GetCount(); ++i)
{
PdfWriter::CAnnotation* pAnnot = dynamic_cast<PdfWriter::CAnnotation*>(pAnnots->Get(i));
int nAnnotID = (pAnnot && pAnnot->GetAnnotationType() == PdfWriter::AnnotWidget) ? m_mObjManager.FindObj(pAnnot) : -1;
if (nAnnotID >= 0)
arrWidgets.push_back(nAnnotID);
}
}
for (int nWidgetID : arrWidgets)
{
// still converted only: DeleteAnnot looks any other ID up on the edited page
PdfWriter::CObjectBase* pWidget = m_mObjManager.GetObj(nWidgetID);
if (!pWidget || pWidget->GetType() != PdfWriter::object_type_DICT)
continue;
// a kid of a checkbox or radio group with Opt stays, as in RemovePageWidgets: the editor's Opt
// and V index still count it. Split cuts it, EditWidgetParents finds the kid on by export value
PdfWriter::CObjectBase* pParent = ((PdfWriter::CDictObject*)pWidget)->Get("Parent");
if (m_nMode != Mode::Split && pParent && pParent->GetType() == PdfWriter::object_type_DICT &&
HasButtonOpt((PdfWriter::CDictObject*)pParent, (PdfWriter::CDictObject*)pWidget))
continue;
DeleteAnnot(nWidgetID, NULL);
}
}
else if (m_nMode == Mode::WriteAppend)
RemovePageWidgets(m_pReader->GetPDFDocument(0), pDoc, pDoc->GetPageObj(nPageIndex), m_setLostKidFields, m_mObjManager.m_setDeletedIDs);
bool bRes = pDoc->DeletePage(nPageIndex);
if (bRes && nObjID > 0)
{
m_mObjManager.RemoveObj(nObjID);
pDoc->RemoveObj(pPage);
}
return bRes;
}
bool CPdfEditor::AddPage(int nPageIndex)
{
if (m_nMode == Mode::Unknown && !IncrementalUpdates())
return false;
WriteRedact({});
m_arrRedact.clear();
if (m_nMode == Mode::Split)
{
std::vector<int>::iterator it = std::find(m_mObjManager.m_arrSplitAddPages.begin(), m_mObjManager.m_arrSplitAddPages.end(), m_nOriginIndex++);
if (it == m_mObjManager.m_arrSplitAddPages.end())
{
PdfWriter::CPageTree* pPageTree = m_pWriter->GetDocument()->GetPageTree();
pPageTree->CreateFakePages(1, nPageIndex);
return false;
}
m_mObjManager.m_arrSplitAddPages.erase(it);
}
// Mode WriteNew & WriteAppend
m_nEditPage = -1;
// Apply page addition for writer
if (!m_pWriter->AddPage(nPageIndex))
return false;
// By default, first page dimensions are set for new page, they can be changed later
double dPageDpiX, dPageDpiY;
double dWidth, dHeight;
m_pReader->GetPageInfo(0, &dWidth, &dHeight, &dPageDpiX, &dPageDpiY);
dWidth *= 25.4 / dPageDpiX;
dHeight *= 25.4 / dPageDpiY;
m_pWriter->put_Width(dWidth);
m_pWriter->put_Height(dHeight);
return true;
}
bool CPdfEditor::MovePage(int nPageIndex, int nPos)
{
WriteRedact({});
m_arrRedact.clear();
if (m_nMode == Mode::Split || m_nMode == Mode::WriteNew || EditPage(nPageIndex, true, true))
return m_pWriter->GetDocument()->MovePage(nPageIndex, nPos);
return false;
}
bool CPdfEditor::EditAnnot(int _nPageIndex, int nID)
{
PdfWriter::CObjectBase* pObj = m_mObjManager.GetObj(nID);
if (pObj)
return true;
PDFDoc* pPDFDocument = NULL;
PdfReader::CPdfFontList* pFontList = NULL;
int nStartRefID = 0;
int nPageIndex = m_pReader->GetPageIndex(_nPageIndex, &pPDFDocument, &pFontList, &nStartRefID);
PdfWriter::CDocument* pDoc = m_pWriter->GetDocument();
if (nPageIndex < 0 || !pPDFDocument || !pDoc)
return false;
if (pDoc->GetAnnot(nID))
return true;
if (m_nMode == Mode::Split || m_nMode == Mode::WriteNew)
return false;
XRef* xref = pPDFDocument->getXRef();
Ref* pPageRef = pPDFDocument->getCatalog()->getPageRef(nPageIndex);
if (!xref || !pPageRef || pPageRef->num == 0)
return false;
// Get annotation object
Object pageRefObj, pageObj, oAnnots;
pageRefObj.initRef(pPageRef->num, pPageRef->gen);
if (!pageRefObj.fetch(xref, &pageObj)->isDict() || !pageObj.dictLookup("Annots", &oAnnots)->isArray())
{
pageRefObj.free(); pageObj.free(); oAnnots.free();
return false;
}
pageRefObj.free(); pageObj.free();
Object oAnnotRef, oAnnot, oType;
for (int i = 0; i < oAnnots.arrayGetLength(); ++i)
{
if (oAnnots.arrayGetNF(i, &oAnnotRef)->isRef() && oAnnotRef.getRefNum() + nStartRefID == nID)
break;
oAnnotRef.free();
}
oAnnots.free();
if (!oAnnotRef.isRef() || !oAnnotRef.fetch(xref, &oAnnot)->isDict() || !oAnnot.dictLookup("Subtype", &oType)->isName())
{
oAnnotRef.free(); oAnnot.free(); oType.free();
return false;
}
if (!pDoc->GetEditPage(_nPageIndex))
EditPage(_nPageIndex, false);
// Reproduce annotation dictionary from reader for writer
PdfWriter::CXref* pXref = new PdfWriter::CXref(pDoc, oAnnotRef.getRefNum());
if (!pXref)
{
oAnnotRef.free(); oAnnot.free(); oType.free();
return false;
}
bool bIsWidget = false;
PdfWriter::CAnnotation* pAnnot = NULL;
if (oType.isName("Text"))
pAnnot = new PdfWriter::CTextAnnotation(pXref);
else if (oType.isName("Ink"))
pAnnot = new PdfWriter::CInkAnnotation(pXref);
else if (oType.isName("Line"))
pAnnot = new PdfWriter::CLineAnnotation(pXref);
else if (oType.isName("Highlight") || oType.isName("Underline") || oType.isName("Squiggly") || oType.isName("StrikeOut"))
pAnnot = new PdfWriter::CTextMarkupAnnotation(pXref);
else if (oType.isName("Square") || oType.isName("Circle"))
pAnnot = new PdfWriter::CSquareCircleAnnotation(pXref);
else if (oType.isName("Polygon") || oType.isName("PolyLine"))
pAnnot = new PdfWriter::CPolygonLineAnnotation(pXref);
else if (oType.isName("FreeText"))
{
std::vector<Ref> arrFont = PdfReader::GetFreeTextFonts(pPDFDocument, m_pReader->GetFontManager(), pFontList, &oAnnotRef);
for (int i = 0; i < arrFont.size(); ++i)
{
Ref oFontInfo = arrFont[i];
PdfReader::TFontEntry pEntry;
pFontList->GetFont(&oFontInfo, &pEntry);
m_mFonts.insert(std::make_pair(pEntry.wsFontName, pEntry.wsFilePath));
}
pAnnot = new PdfWriter::CFreeTextAnnotation(pXref);
}
else if (oType.isName("Caret"))
pAnnot = new PdfWriter::CCaretAnnotation(pXref);
else if (oType.isName("Stamp"))
pAnnot = new PdfWriter::CStampAnnotation(pXref);
else if (oType.isName("Redact"))
pAnnot = new PdfWriter::CRedactAnnotation(pXref);
else if (oType.isName("Popup"))
pAnnot = new PdfWriter::CPopupAnnotation(pXref);
else if (oType.isName("Link"))
pAnnot = new PdfWriter::CLinkAnnotation(pXref);
else if (oType.isName("Widget"))
{
bIsWidget = true;
char* sName = NULL;
Object oFT;
if (oAnnot.dictLookup("FT", &oFT)->isName())
sName = oFT.getName();
if (!sName)
{
Object oParent, oParent2;
oAnnot.dictLookup("Parent", &oParent);
for (int nDepth = 0; oParent.isDict() && nDepth < 50; ++nDepth) // a broken file may loop the Parent chain
{
if (oParent.dictLookup("FT", &oFT)->isName())
{
sName = oFT.getName();
break;
}
oFT.free();
oParent.dictLookup("Parent", &oParent2);
oParent.free();
oParent = oParent2;
}
oParent.free();
}
if (sName)
{
if (strcmp("Btn", sName) == 0)
{
bool bPushButton = false;
oFT.free();
int nFf = 0;
if (oAnnot.dictLookup("Ff", &oFT)->isInt())
nFf = oFT.getInt();
if (!nFf)
{
Object oParent, oParent2;
oAnnot.dictLookup("Parent", &oParent);
for (int nDepth = 0; oParent.isDict() && nDepth < 50; ++nDepth) // a broken file may loop the Parent chain
{
if (oParent.dictLookup("Ff", &oFT)->isInt())
{
nFf = oFT.getInt();
break;
}
oFT.free();
oParent.dictLookup("Parent", &oParent2);
oParent.free();
oParent = oParent2;
}
oParent.free();
}
bPushButton = (bool)((nFf >> 16) & 1);
bool bRadiobutton = (bool)((nFf >> 15) & 1);
if (bPushButton)
pAnnot = new PdfWriter::CPushButtonWidget(pXref);
else
pAnnot = new PdfWriter::CCheckBoxWidget(pXref, bRadiobutton ? PdfWriter::WidgetRadiobutton : PdfWriter::WidgetCheckbox);
}
else if (strcmp("Tx", sName) == 0)
pAnnot = new PdfWriter::CTextWidget(pXref);
else if (strcmp("Ch", sName) == 0)
pAnnot = new PdfWriter::CChoiceWidget(pXref);
else if (strcmp("Sig", sName) == 0)
pAnnot = new PdfWriter::CSignatureWidget(pXref);
else
pAnnot = new PdfWriter::CWidgetAnnotation(pXref, PdfWriter::EAnnotType::AnnotWidget);
}
oFT.free();
}
if (!pAnnot)
{
oAnnotRef.free(); oAnnot.free(); oType.free();
RELEASEOBJECT(pXref);
return false;
}
pXref->Add(pAnnot, oAnnotRef.getRefGen());
SDictConvertContext ctx;
for (int nIndex = 0; nIndex < oAnnot.dictGetLength(); ++nIndex)
{
char* chKey = oAnnot.dictGetKey(nIndex);
if (!strcmp("Popup", chKey))
{
Object oPopupRef;
if (oAnnot.dictGetValNF(nIndex, &oPopupRef)->isRef() && EditAnnot(nPageIndex, oPopupRef.getRefNum() + nStartRefID))
{
PdfWriter::CAnnotation* pPopup = pDoc->GetAnnot(oPopupRef.getRefNum() + nStartRefID);
if (pPopup)
{
pAnnot->Add("Popup", pPopup);
pPopup->Add("Parent", pAnnot);
}
}
continue;
}
else if (!strcmp("Parent", chKey) && bIsWidget)
{
Object oParentRef;
oAnnot.dictGetValNF(nIndex, &oParentRef);
PdfWriter::CDictObject* pParent = GetWidgetParent(pPDFDocument, pDoc, &oParentRef, nStartRefID);
if (!pParent)
{
oParentRef.free();
continue;
}
((PdfWriter::CWidgetAnnotation*)pAnnot)->SetParent(pParent);
PdfWriter::CArrayObject* pKids = dynamic_cast<PdfWriter::CArrayObject*>(pParent->Get("Kids"));
if (!pKids)
{
oParentRef.free();
continue;
}
for (int i = 0; i < pKids->GetCount(); ++i)
{
PdfWriter::CObjectBase* pKid = pKids->Get(i);
if (pKid && pKid->GetObjId() == oAnnotRef.getRefNum())
{
pKids->Insert(pKid, pAnnot, true);
break;
}
}
oParentRef.free();
continue;
}
else if (!strcmp("AP", chKey) && pAnnot->GetAnnotationType() == PdfWriter::AnnotWidget)
{
PdfWriter::EWidgetType nType = ((PdfWriter::CWidgetAnnotation*)pAnnot)->GetWidgetType();
if (nType == PdfWriter::WidgetRadiobutton || nType == PdfWriter::WidgetCheckbox)
{
PdfWriter::CCheckBoxWidget* pCAnnot = dynamic_cast<PdfWriter::CCheckBoxWidget*>(pAnnot);
Object oAP, oN;
if (oAnnot.dictGetVal(nIndex, &oAP)->isDict() && oAP.dictLookup("N", &oN)->isDict())
{
for (int j = 0, nNormLength = oN.dictGetLength(); j < nNormLength; ++j)
{
std::string sNormName(oN.dictGetKey(j));
if (sNormName != "Off")
{
pCAnnot->SetAP_N_Yes(UTF8_TO_U(sNormName));
break;
}
}
}
}
}
Object oTemp;
oAnnot.dictGetValNF(nIndex, &oTemp);
DictToCDictObjectUnified(&oTemp, ctx, pAnnot, chKey);
oTemp.free();
}
if (oType.isName("Stamp"))
{
Object oAP, oAPN;
if (oAnnot.dictLookup("AP", &oAP)->isDict() && oAP.dictLookup("N", &oAPN)->isStream())
{
Object oAPNRef;
oAP.dictLookupNF("N", &oAPNRef);
PdfWriter::CXref* pXRef = new PdfWriter::CXref(pDoc, oAPNRef.getRefNum());
pDoc->EditXref(pXRef);
PdfWriter::CDictObject* pAPN = new PdfWriter::CDictObject();
pXRef->Add(pAPN, oAPNRef.getRefGen());
((PdfWriter::CStampAnnotation*)pAnnot)->SetAPStream(pAPN);
oAPNRef.free();
Object oTemp;
SDictConvertContext ctx;
Dict* pODict = oAPN.streamGetDict();
for (int nIndex = 0; nIndex < pODict->getLength(); ++nIndex)
{
char* chKey = pODict->getKey(nIndex);
pODict->getValNF(nIndex, &oTemp);
DictToCDictObjectUnified(&oTemp, ctx, pAPN, chKey);
oTemp.free();
}
int nLength = 0;
if (pODict->lookup("Length", &oTemp)->isInt())
nLength = oTemp.getInt();
PdfWriter::CStream* pStream = new PdfWriter::CMemoryStream(nLength);
pAPN->SetStream(pStream);
pAPN->Add("Length", nLength);
if (pAPN->Get("Filter"))
pAPN->SetFilter(STREAM_FILTER_ALREADY_DECODE);
Stream* pOStream = oAPN.getStream()->getUndecodedStream();
pOStream->reset();
int nChar = pOStream->getChar();
while (nChar != EOF)
{
pStream->WriteChar(nChar);
nChar = pOStream->getChar();
}
}
oAP.free(); oAPN.free();
}
oAnnotRef.free(); oAnnot.free(); oType.free();
if (pDoc->EditAnnot(pXref, pAnnot, nID))
return true;
RELEASEOBJECT(pXref);
return false;
}
namespace
{
void RemoveObj(CObjectsManager* pManager, PdfWriter::CDocument* pDoc, PdfWriter::CObjectBase* pObj)
{
std::vector<PdfWriter::CObjectBase*> arrRemoved;
pDoc->RemoveObj(pObj, true, &arrRemoved);
for (size_t i = 0; i < arrRemoved.size(); ++i)
{
int nIDR = pManager->FindObj(arrRemoved[i]);
pManager->RemoveObj(nIDR);
}
}
// true for a button field (FT Btn on the field, an ancestor, else the kid): its Opt pairs with Kids
// by index, a choice field's Opt is its list of items
bool IsButtonField(PdfWriter::CDictObject* pField, PdfWriter::CDictObject* pKid)
{
for (int nDepth = 0; pField && nDepth < 32; ++nDepth)
{
PdfWriter::CObjectBase* pFT = pField->Get("FT");
if (pFT && pFT->GetType() == PdfWriter::object_type_NAME)
return strcmp(((PdfWriter::CNameObject*)pFT)->Get(), "Btn") == 0;
PdfWriter::CObjectBase* pParent = pField->Get("Parent");
pField = (pParent && pParent->GetType() == PdfWriter::object_type_DICT) ? (PdfWriter::CDictObject*)pParent : NULL;
}
PdfWriter::CObjectBase* pFT = pKid ? pKid->Get("FT") : NULL;
return pFT && pFT->GetType() == PdfWriter::object_type_NAME && strcmp(((PdfWriter::CNameObject*)pFT)->Get(), "Btn") == 0;
}
// A checkbox or radio group with Opt (a WriteAppend copy keeps an indirect Opt as a raw reference)
bool HasButtonOpt(PdfWriter::CDictObject* pField, PdfWriter::CDictObject* pKid)
{
PdfWriter::CObjectBase* pOpt = pField ? pField->Get("Opt") : NULL;
return pOpt && (pOpt->GetType() == PdfWriter::object_type_ARRAY || pOpt->GetType() == PdfWriter::object_type_UNKNOWN) && IsButtonField(pField, pKid);
}
// The item of pKid (the writer's object), or of object nKidID when pKid is NULL, leaves the array;
// only its proxy is deleted (CArrayObject::Remove returns the target). Its index, -1 - not there
int RemoveArrayItem(PdfWriter::CArrayObject* pArray, PdfWriter::CObjectBase* pKid, int nKidID)
{
for (int i = 0; i < pArray->GetCount(); ++i)
{
PdfWriter::CObjectBase* pItem = pArray->Get(i);
if (!pItem || (pKid ? pItem != pKid : (int)pItem->GetObjId() != nKidID))
continue;
PdfWriter::CObjectBase* pProxy = pArray->Get(i, false);
pArray->Remove(i);
if (pProxy != pItem)
delete pProxy;
return i;
}
return -1;
}
// The kid leaves the parent's Kids; in a checkbox or radio group whose Opt pairs with Kids, its Opt entry
// too (the editor writes Opt without it). pKid - the writer's kid; NULL - the item of object nKidID
// (WriteAppend: not converted). true - an Opt entry left with it
bool RemoveParentKid(PdfWriter::CDictObject* pParent, PdfWriter::CDictObject* pKid, int nKidID)
{
PdfWriter::CObjectBase* pKidsObj = pParent->Get("Kids");
if (!pKidsObj || pKidsObj->GetType() != PdfWriter::object_type_ARRAY)
return false;
PdfWriter::CArrayObject* pKids = (PdfWriter::CArrayObject*)pKidsObj;
PdfWriter::CObjectBase* pOptObj = pParent->Get("Opt");
PdfWriter::CArrayObject* pOpt = (pOptObj && pOptObj->GetType() == PdfWriter::object_type_ARRAY) ? (PdfWriter::CArrayObject*)pOptObj : NULL;
if (pOpt && (pOpt->GetCount() != pKids->GetCount() || !IsButtonField(pParent, pKid)))
pOpt = NULL;
int i = RemoveArrayItem(pKids, pKid, nKidID);
if (i < 0 || !pOpt || !pOpt->Get(i))
return false;
// the entry itself, or the proxy of an indirect one
PdfWriter::CObjectBase* pOptItem = pOpt->Get(i, false);
pOpt->Remove(i);
RELEASEOBJECT(pOptItem);
return true;
}
// pDict leaves its parent's Kids (RemoveParentKid); when its Opt entry goes too, the kids left are
// renamed after their new indexes (V's index counts the Kids before)
void RemoveFromParentKids(PdfWriter::CDictObject* pDict)
{
PdfWriter::CObjectBase* pParent = pDict->Get("Parent");
if (!pParent || pParent->GetType() != PdfWriter::object_type_DICT)
return;
std::vector<PdfWriter::CObjectBase*> arrVKids;
PdfWriter::CObjectBase* pKids = ((PdfWriter::CDictObject*)pParent)->Get("Kids");
for (int i = 0; pKids && pKids->GetType() == PdfWriter::object_type_ARRAY && i < ((PdfWriter::CArrayObject*)pKids)->GetCount(); ++i)
arrVKids.push_back(((PdfWriter::CArrayObject*)pKids)->Get(i));
if (RemoveParentKid((PdfWriter::CDictObject*)pParent, pDict, 0))
RenameButtonKids((PdfWriter::CDictObject*)pParent, &arrVKids);
}
// A field whose kids are all deleted is deleted too, up the Parent chain - else its parent's Kids keeps it,
// with no widget and the deleted kids' V. Decided by the reader's Kids: the writer's lacks the kids not converted yet
void DeleteEmptyParents(CPdfReader* pReader, CObjectsManager* pManager, PdfWriter::CDocument* pDoc, int nID)
{
PDFDoc* pPDFDocument = NULL;
int nStartRefID = 0;
int nRefNum = pReader->FindRefNum(nID, &pPDFDocument, &nStartRefID);
if (nRefNum < 0 || !pPDFDocument)
return;
XRef* xref = pPDFDocument->getXRef();
Object oNode, oType;
if (!xref->fetch(nRefNum, FetchGen(xref, nRefNum), &oNode)->isDict() || !oNode.dictLookup("Subtype", &oType)->isName("Widget"))
{
oNode.free(); oType.free();
return;
}
oType.free();
for (int nDepth = 0; nDepth < 32; ++nDepth)
{
Object oParentRef, oParent, oKids;
if (!oNode.dictLookupNF("Parent", &oParentRef)->isRef() || !oParentRef.fetch(xref, &oParent)->isDict() ||
!oParent.dictLookup("Kids", &oKids)->isArray())
{
oParentRef.free(); oParent.free(); oKids.free();
break;
}
bool bLive = false;
for (int i = 0; i < oKids.arrayGetLength() && !bLive; ++i)
{
Object oKid;
bLive = !oKids.arrayGetNF(i, &oKid)->isRef() || !pManager->m_setDeletedIDs.count(oKid.getRefNum() + nStartRefID);
oKid.free();
}
oKids.free();
if (bLive)
{
oParentRef.free(); oParent.free();
break;
}
int nParentID = oParentRef.getRefNum() + nStartRefID;
oParentRef.free();
pManager->m_setDeletedIDs.insert(nParentID);
PdfWriter::CObjectBase* pObj = pManager->GetObj(nParentID);
if (pObj && pObj->GetType() == PdfWriter::object_type_DICT)
{
RemoveFromParentKids((PdfWriter::CDictObject*)pObj);
RemoveObj(pManager, pDoc, pObj);
}
oNode.free();
oNode = oParent;
}
oNode.free();
}
// WriteAppend: a field whose kids all left in this update (DeleteAnnot, RemovePageWidgets: setFields) leaves
// it, up the Parent chain: out of Fields, CO and its parent's Kids, its object freed - else Fields lists it with
// Kids [] and its V. A field the editor gave a new widget (EditWidgetParents) has a kid again and stays
void DeleteEmptyAppendFields(CObjectsManager* pManager, PdfWriter::CDocument* pDoc, const std::set<int>& setFields)
{
PdfWriter::CDictObject* pAcroForm = pDoc->GetAcroForm();
PdfWriter::CArrayObject* pFields = pAcroForm ? dynamic_cast<PdfWriter::CArrayObject*>(pAcroForm->Get("Fields")) : NULL;
PdfWriter::CArrayObject* pCO = pAcroForm ? dynamic_cast<PdfWriter::CArrayObject*>(pAcroForm->Get("CO")) : NULL;
for (int nID : setFields)
{
PdfWriter::CDictObject* pField = pDoc->GetParent(nID);
for (int nDepth = 0; pField && nDepth < 32; ++nDepth)
{
PdfWriter::CArrayObject* pKids = dynamic_cast<PdfWriter::CArrayObject*>(pField->Get("Kids"));
bool bEmpty = pKids != NULL;
for (int i = 0; bEmpty && i < pKids->GetCount(); ++i)
bEmpty = !pKids->Get(i);
// only a field copied into the update under its number (GetWidgetParent)
int nFieldID = pField->GetObjId();
if (!bEmpty || nFieldID <= 0 || pDoc->GetParent(nFieldID) != pField)
break;
PdfWriter::CObjectBase* pUp = pField->Get("Parent");
PdfWriter::CDictObject* pUpField = pUp && pUp->GetType() == PdfWriter::object_type_DICT ? (PdfWriter::CDictObject*)pUp : NULL;
if (pUpField == pField) // a broken file: the field is its own Parent
pUpField = NULL;
if (pFields)
RemoveArrayItem(pFields, NULL, nFieldID);
if (pCO)
RemoveArrayItem(pCO, NULL, nFieldID);
if (pUpField)
RemoveParentKid(pUpField, NULL, nFieldID);
pManager->m_setDeletedIDs.insert(nFieldID);
pDoc->RemoveObj(pField, false); // out of the parents EditWidgetParents walks
PdfWriter::CXref::Free(pField);
pField = pUpField;
}
}
}
// Split: a widget SplitSkipDict does not hold back - one with no P or no Type - comes in with its field's
// Kids even when its page is left out. Removed when no page of arrPages lists it in Annots.
// [nStartRefID, nEndRefID) - the manager IDs of the widgets checked
void RemoveOffPageWidgets(CObjectsManager* pManager, PdfWriter::CDocument* pDoc, int nStartRefID, int nEndRefID, const std::vector<PdfWriter::CPage*>& arrPages)
{
std::set<PdfWriter::CObjectBase*> setOnPage;
for (PdfWriter::CPage* pPage : arrPages)
{
PdfWriter::CObjectBase* pAnnotsObj = pPage ? pPage->Get("Annots") : NULL;
if (!pAnnotsObj || pAnnotsObj->GetType() != PdfWriter::object_type_ARRAY)
continue;
PdfWriter::CArrayObject* pAnnots = (PdfWriter::CArrayObject*)pAnnotsObj;
for (int i = 0; i < pAnnots->GetCount(); ++i)
setOnPage.insert(pAnnots->Get(i));
}
std::vector<int> arrOff;
const std::map<int, PdfWriter::CAnnotation*>& mAnnots = pDoc->GetAnnots();
for (std::map<int, PdfWriter::CAnnotation*>::const_iterator it = mAnnots.begin(); it != mAnnots.end(); ++it)
{
if (it->first >= nStartRefID && it->first < nEndRefID && it->second->GetAnnotationType() == PdfWriter::AnnotWidget && !setOnPage.count(it->second))
arrOff.push_back(it->first);
}
for (int nID : arrOff)
{
PdfWriter::CObjectBase* pObj = pManager->GetObj(nID);
if (!pObj || pObj->GetType() != PdfWriter::object_type_DICT)
continue;
pManager->m_setDeletedIDs.insert(nID);
RemoveFromParentKids((PdfWriter::CDictObject*)pObj);
RemoveObj(pManager, pDoc, pObj);
}
}
// Split: a field with no widget under it (its widgets split away, or deleted) leaves the part, its V with it.
// false - pNode has none: the caller removes it
bool PruneSplitFields(CObjectsManager* pManager, PdfWriter::CDocument* pDoc, PdfWriter::CObjectBase* pNode, std::set<PdfWriter::CObjectBase*>& setSeen, int nDepth)
{
// a widget, or anything that is not a field dict, stays
if (!pNode || pNode->GetType() != PdfWriter::object_type_DICT || ((PdfWriter::CDictObject*)pNode)->GetDictType() != PdfWriter::dict_type_UNKNOWN)
return true;
// a loop in the field tree is left as it is
if (nDepth > 50 || !setSeen.insert(pNode).second)
return true;
PdfWriter::CObjectBase* pKidsObj = ((PdfWriter::CDictObject*)pNode)->Get("Kids");
if (!pKidsObj || pKidsObj->GetType() != PdfWriter::object_type_ARRAY)
return true;
PdfWriter::CArrayObject* pKids = (PdfWriter::CArrayObject*)pKidsObj;
bool bLive = false;
for (int i = 0; i < pKids->GetCount(); ++i)
{
PdfWriter::CObjectBase* pKid = pKids->Get(i);
if (!pKid)
continue; // deleted
if (PruneSplitFields(pManager, pDoc, pKid, setSeen, nDepth + 1))
{
bLive = true;
continue;
}
RemoveArrayItem(pKids, pKid, 0);
RemoveObj(pManager, pDoc, pKid);
--i;
}
return bLive;
}
void PruneSplitFields(CObjectsManager* pManager, PdfWriter::CDocument* pDoc)
{
PdfWriter::CDictObject* pAcroForm = pDoc->GetAcroForm();
PdfWriter::CObjectBase* pFieldsObj = pAcroForm ? pAcroForm->Get("Fields") : NULL;
if (!pFieldsObj || pFieldsObj->GetType() != PdfWriter::object_type_ARRAY)
return;
PdfWriter::CArrayObject* pFields = (PdfWriter::CArrayObject*)pFieldsObj;
std::set<PdfWriter::CObjectBase*> setSeen;
for (int i = 0; i < pFields->GetCount(); ++i)
{
PdfWriter::CObjectBase* pField = pFields->Get(i);
if (!pField || PruneSplitFields(pManager, pDoc, pField, setSeen, 0))
continue;
RemoveArrayItem(pFields, pField, 0);
RemoveObj(pManager, pDoc, pField);
--i;
}
}
PdfWriter::CDictObject* GetRebuildParent(CPdfReader* pReader, CObjectsManager* pManager, PdfWriter::CDocument* pDoc, int nID);
// A button kid that is on leaves its field: true when the field's V names the kid's state and no
// other kid the reader lists has that state on - V is Off then. pParentID - the field's manager ID
bool IsValueOfDeletedKid(CPdfReader* pReader, CObjectsManager* pManager, int nID, int* pParentID = NULL)
{
PDFDoc* pPDFDocument = NULL;
int nStartRefID = 0;
int nRefNum = pReader->FindRefNum(nID, &pPDFDocument, &nStartRefID);
if (nRefNum < 0 || !pPDFDocument)
return false;
XRef* xref = pPDFDocument->getXRef();
CObjectGuard oKid, oAS, oParentRef, oParent, oV, oKids;
if (!xref->fetch(nRefNum, FetchGen(xref, nRefNum), oKid.get())->isDict() || !oKid->dictLookup("AS", oAS.get())->isName() || oAS->isName("Off") ||
!oKid->dictLookupNF("Parent", oParentRef.get())->isRef() || !oParentRef->fetch(xref, oParent.get())->isDict() ||
!oParent->dictLookup("V", oV.get())->isName(oAS->getName()))
return false;
if (oParent->dictLookup("Kids", oKids.get())->isArray())
{
for (int i = 0; i < oKids->arrayGetLength(); ++i)
{
CObjectGuard oRef, oOther, oOtherAS;
if (!oKids->arrayGetNF(i, oRef.get())->isRef() || oRef->getRefNum() == nRefNum || pManager->m_setDeletedIDs.count(oRef->getRefNum() + nStartRefID))
continue;
if (oRef->fetch(xref, oOther.get())->isDict() && oOther->dictLookup("AS", oOtherAS.get())->isName(oAS->getName()))
return false;
}
}
if (pParentID)
*pParentID = oParentRef->getRefNum() + nStartRefID;
return true;
}
}
bool CPdfEditor::DeleteAnnot(int nID, Object* oAnnots)
{
PdfWriter::CDocument* pDoc = m_pWriter->GetDocument();
if (!pDoc)
return false;
// Marked deleted when converted (any mode; in WriteAppend a merged file's annotation), and always in
// Split and WriteNew, which convert page by page: a later reference must not convert it again
bool bRebuild = m_nMode == Mode::Split || m_nMode == Mode::WriteNew;
PdfWriter::CObjectBase* pObj = m_mObjManager.GetObj(nID);
if (pObj || bRebuild)
m_mObjManager.m_setDeletedIDs.insert(nID);
if (pObj)
{
if (pObj->GetType() == PdfWriter::object_type_DICT)
{
PdfWriter::CObjectBase* pParentObj = ((PdfWriter::CDictObject*)pObj)->Get("Parent");
if (pParentObj && pParentObj->GetType() == PdfWriter::object_type_DICT && IsValueOfDeletedKid(m_pReader, &m_mObjManager, nID))
((PdfWriter::CDictObject*)pParentObj)->Add("V", "Off");
// before the Popup, whose cascade may free the annotation (Popup /Parent)
RemoveFromParentKids((PdfWriter::CDictObject*)pObj);
PdfWriter::CObjectBase* pObjR = ((PdfWriter::CDictObject*)pObj)->Get("Popup");
if (pObjR)
{
int nPopupID = m_mObjManager.FindObj(pObjR);
if (nPopupID >= 0)
m_mObjManager.m_setDeletedIDs.insert(nPopupID);
RemoveObj(&m_mObjManager, pDoc, pObjR);
}
}
// freed already by the Popup's cascade: the manager no longer lists it
if (m_mObjManager.GetObj(nID) == pObj)
RemoveObj(&m_mObjManager, pDoc, pObj);
DeleteEmptyParents(m_pReader, &m_mObjManager, pDoc, nID);
return true;
}
if (bRebuild)
{
// Not converted yet: its Popup is in the page's Annots too and would be converted alone
PDFDoc* pPDFDocument = NULL;
int nStartRefID = 0;
int nRefNum = m_pReader->FindRefNum(nID, &pPDFDocument, &nStartRefID);
if (nRefNum >= 0 && pPDFDocument)
{
XRef* xref = pPDFDocument->getXRef();
CObjectGuard oAnnot, oPopupRef;
if (xref->fetch(nRefNum, FetchGen(xref, nRefNum), oAnnot.get())->isDict() && oAnnot->dictLookupNF("Popup", oPopupRef.get())->isRef())
m_mObjManager.m_setDeletedIDs.insert(oPopupRef->getRefNum() + nStartRefID);
}
DeleteEmptyParents(m_pReader, &m_mObjManager, pDoc, nID);
// WriteNew converts the field from the reader with its V; a split part has it already, or not at all
int nParentID = 0;
if (IsValueOfDeletedKid(m_pReader, &m_mObjManager, nID, &nParentID))
{
PdfWriter::CObjectBase* pParent = m_nMode == Mode::WriteNew ? GetRebuildParent(m_pReader, &m_mObjManager, pDoc, nParentID) : m_mObjManager.GetObj(nParentID);
if (pParent && pParent->GetType() == PdfWriter::object_type_DICT)
((PdfWriter::CDictObject*)pParent)->Add("V", "Off");
}
return true;
}
PDFDoc* pPDFDocument = NULL;
int nPageIndex = m_pReader->GetPageIndex(m_nEditPage, &pPDFDocument);
if (nPageIndex < 0 || !pPDFDocument)
return false;
XRef* xref = pPDFDocument->getXRef();
bool bClear = false;
if (!oAnnots)
{
PdfWriter::CPage* pPage = pDoc->GetCurPage();
std::pair<int, int> pPageRef = { pPage->GetObjId(), pPage->GetGenNo() };
if (pPageRef.first == 0)
return false;
oAnnots = new Object();
bClear = true;
// Get annotation object
Object pageRefObj, pageObj;
pageRefObj.initRef(pPageRef.first, pPageRef.second);
if (!pageRefObj.fetch(xref, &pageObj)->isDict() || !pageObj.dictLookup("Annots", oAnnots)->isArray())
{
pageRefObj.free(); pageObj.free(); oAnnots->free();
RELEASEOBJECT(oAnnots);
return false;
}
pageRefObj.free(); pageObj.free();
}
bool bRes = false;
for (int i = 0; i < oAnnots->arrayGetLength(); ++i)
{
Object oAnnotRef, oAnnot;
if (oAnnots->arrayGetNF(i, &oAnnotRef)->isRef() && oAnnotRef.getRefNum() == nID)
{
bool bNeed = false;
if (oAnnotRef.fetch(xref, &oAnnot)->isDict())
{
Object oType;
if (oAnnot.dictLookup("Subtype", &oType)->isName("Widget"))
{
char* sName = NULL;
Object oFT;
if (oAnnot.dictLookup("FT", &oFT)->isName())
sName = oFT.getName();
if (!sName)
{
Object oParent, oParent2;
oAnnot.dictLookup("Parent", &oParent);
for (int nDepth = 0; oParent.isDict() && nDepth < 50; ++nDepth) // a broken file may loop the Parent chain
{
if (oParent.dictLookup("FT", &oFT)->isName())
{
sName = oFT.getName();
break;
}
oFT.free();
oParent.dictLookup("Parent", &oParent2);
oParent.free();
oParent = oParent2;
}
oParent.free();
}
if (sName && strcmp("Btn", sName) == 0)
{
bool bPushButton = false;
oFT.free();
int nFf = 0;
if (oAnnot.dictLookup("Ff", &oFT)->isInt())
nFf = oFT.getInt();
if (!nFf)
{
Object oParent, oParent2;
oAnnot.dictLookup("Parent", &oParent);
for (int nDepth = 0; oParent.isDict() && nDepth < 50; ++nDepth) // a broken file may loop the Parent chain
{
if (oParent.dictLookup("Ff", &oFT)->isInt())
{
nFf = oFT.getInt();
break;
}
oFT.free();
oParent.dictLookup("Parent", &oParent2);
oParent.free();
oParent = oParent2;
}
oParent.free();
}
bPushButton = (bool)((nFf >> 16) & 1);
if (!bPushButton)
{
oFT.free();
bNeed = oAnnot.dictLookup("Opt", &oFT)->isArray() == gTrue;
if (!bNeed)
{
Object oParent, oParent2;
oAnnot.dictLookup("Parent", &oParent);
for (int nDepth = 0; oParent.isDict() && nDepth < 50; ++nDepth) // a broken file may loop the Parent chain
{
if (oParent.dictLookup("Opt", &oFT)->isArray())
{
bNeed = true;
break;
}
oFT.free();
oParent.dictLookup("Parent", &oParent2);
oParent.free();
oParent = oParent2;
}
oParent.free();
}
PdfWriter::CAnnotation* pAnnot = bNeed && EditAnnot(m_nEditPage, nID) ? pDoc->GetAnnot(nID) : NULL;
if (pAnnot)
{
// restored below: EditAnnot of a kid on another page makes that page current
PdfWriter::CPage* pPage = pDoc->GetCurPage();
PdfWriter::CObjectBase* pObj = pAnnot->Get("Parent");
PdfWriter::CDictObject* pParent = NULL;
if (pObj && pObj->GetType() == PdfWriter::object_type_DICT)
pParent = (PdfWriter::CDictObject*)pObj;
PdfWriter::CArrayObject* pOpt = NULL, *pKids = NULL;
// the Kids V's Opt index counts: before this kid leaves
std::vector<int> arrVKidIDs;
if (pParent)
{
pObj = pParent->Get("Kids");
if (pObj && pObj->GetType() == PdfWriter::object_type_ARRAY)
pKids = (PdfWriter::CArrayObject*)pObj;
for (int i = 0; pKids && i < pKids->GetCount(); ++i)
{
PdfWriter::CObjectBase* pItem = pKids->Get(i);
arrVKidIDs.push_back(pItem ? (int)pItem->GetObjId() : 0);
}
pObj = pParent->Get("Opt");
if (pObj && pObj->GetType() == PdfWriter::object_type_ARRAY)
pOpt = (PdfWriter::CArrayObject*)pObj;
// only the proxy goes: pAnnot is still in the page's Annots, its own xref and the document's annotations
RemoveParentKid(pParent, pAnnot, 0);
}
// Fields may list widgets (CDocument::DeleteAnnot takes them out too)
PdfWriter::CDictObject* pAcroForm = pDoc->GetAcroForm();
pObj = pAcroForm ? pAcroForm->Get("Fields") : NULL;
if (pObj && pObj->GetType() == PdfWriter::object_type_ARRAY)
RemoveArrayItem((PdfWriter::CArrayObject*)pObj, NULL, nID);
// The kids left are renamed after their new Opt indexes, the field's value follows the kid that is on
if (pKids && pOpt && pKids->GetCount() == pOpt->GetCount())
{
std::vector<PdfWriter::CCheckBoxWidget*> arrKids;
for (int i = 0; i < pKids->GetCount(); ++i)
{
pObj = pKids->Get(i);
PdfWriter::CCheckBoxWidget* pKidAnnot = NULL;
Object oKid, oSubtype, oPageRef;
if (pObj && xref->fetch(pObj->GetObjId(), pObj->GetGenNo(), &oKid)->isDict("Annot") && oKid.dictLookup("Subtype", &oSubtype)->isName("Widget") &&
oKid.dictLookupNF("P", &oPageRef)->isRef())
{
int nPage = pPDFDocument->findPage(oPageRef.getRefNum(), oPageRef.getRefGen()) - 1;
int nObjId = pObj->GetObjId();
if (nPage >= 0 && EditAnnot(nPage, nObjId))
pKidAnnot = dynamic_cast<PdfWriter::CCheckBoxWidget*>(pDoc->GetAnnot(nObjId));
}
oKid.free(); oSubtype.free(); oPageRef.free();
arrKids.push_back(pKidAnnot);
}
pObj = pParent->Get("V");
int nV = OptIndexOfV(pObj && pObj->GetType() == PdfWriter::object_type_NAME ? ((PdfWriter::CNameObject*)pObj)->Get() : "");
PdfWriter::CObjectBase* pVKid = nV >= 0 && nV < (int)arrVKidIDs.size() && arrVKidIDs[nV] > 0 ? pDoc->GetAnnot(arrVKidIDs[nV]) : NULL;
RenameKids(pParent, pOpt, arrKids, pVKid);
}
pDoc->SetCurPage(pPage);
// Out of the page's Annots and the document's annotations, which EditWidgetParents walks.
// Not freed: EditAnnot gave it an xref entry in use, a free entry would duplicate it.
// Unreferenced, it stays in the update, like an edited page that is deleted (CDocument::DeletePage)
Object oPopupRef;
if (oAnnot.dictLookupNF("Popup", &oPopupRef)->isRef())
{
PdfWriter::CAnnotation* pPopup = pDoc->GetAnnot(oPopupRef.getRefNum());
if (pPopup)
{
if (pPage)
pPage->DeleteAnnotation(oPopupRef.getRefNum());
pDoc->RemoveObj(pPopup);
}
else
pDoc->DeleteAnnot(oPopupRef.getRefNum(), oPopupRef.getRefGen());
}
oPopupRef.free();
bRes = pPage && pPage->DeleteAnnotation(nID);
// In WriteAppend pAnnot is in its own xref, not the document's one: RemoveObj does not delete it
pDoc->RemoveObj(pAnnot);
}
else
bNeed = false; // not edited: deleted like any other widget
}
}
oFT.free();
}
oType.free();
if (!bNeed)
{
Object oPopupRef;
if (oAnnot.dictLookupNF("Popup", &oPopupRef)->isRef())
pDoc->DeleteAnnot(oPopupRef.getRefNum(), oPopupRef.getRefGen());
oPopupRef.free();
}
}
if (!bNeed)
{
// The widget's field is copied now and written without it: a copy made later (EditWidgets,
// EditAnnot of another kid) would still list the freed widget
Object oParentRef, oSubtype;
if (oAnnot.isDict() && oAnnot.dictLookup("Subtype", &oSubtype)->isName("Widget") && oAnnot.dictLookupNF("Parent", &oParentRef)->isRef())
{
PdfWriter::CDictObject* pParent = GetWidgetParent(pPDFDocument, pDoc, &oParentRef, 0);
if (pParent)
{
RemoveParentKid(pParent, NULL, nID);
if (IsValueOfDeletedKid(m_pReader, &m_mObjManager, nID))
pParent->Add("V", "Off");
m_setLostKidFields.insert(oParentRef.getRefNum());
}
}
oParentRef.free(); oSubtype.free();
bRes = pDoc->DeleteAnnot(oAnnotRef.getRefNum(), oAnnotRef.getRefGen());
// a CO entry of it is left out (EditWidgets)
if (bRes)
m_mObjManager.m_setDeletedIDs.insert(nID);
}
}
else if (oAnnots->arrayGet(i, &oAnnot)->isDict())
{
Object oIRTRef;
if (oAnnot.dictLookupNF("IRT", &oIRTRef)->isRef() && oIRTRef.getRefNum() == nID)
DeleteAnnot(oAnnotRef.getRefNum(), oAnnots);
oIRTRef.free();
}
oAnnotRef.free(); oAnnot.free();
}
if (bClear)
{
oAnnots->free();
RELEASEOBJECT(oAnnots);
}
return bRes;
}
void CPdfEditor::CheckMode()
{
if (m_nMode == Mode::Unknown)
IncrementalUpdates();
}
namespace
{
// Split, WriteNew: field nID as the manager converted it (with a page, or here from the reader), registered
// with its ancestors and the fields under it - not a GetWidgetParent copy, which keeps the reader number.
// NULL - a new field, a widget, a deleted field
PdfWriter::CDictObject* GetRebuildParent(CPdfReader* pReader, CObjectsManager* pManager, PdfWriter::CDocument* pDoc, int nID)
{
PdfWriter::CDictObject* pParent = pDoc->GetParent(nID);
if (pParent)
return pParent;
// Deleted with all its kids - not converted again
if (pManager->m_setDeletedIDs.count(nID))
return NULL;
PdfWriter::CObjectBase* pObj = pManager->GetObj(nID);
if (!pObj)
{
PDFDoc* pPDFDocument = NULL;
int nStartRefID = 0;
int nRefNum = pReader->FindRefNum(nID, &pPDFDocument, &nStartRefID);
if (nRefNum < 0 || !pPDFDocument)
return NULL; // a new field
XRef* xref = pPDFDocument->getXRef();
Object oRef, oField, oType;
oRef.initRef(nRefNum, FetchGen(xref, nRefNum));
// a field, not a widget: a widget is converted with its page
bool bField = oRef.fetch(xref, &oField)->isDict() && !oField.dictLookup("Subtype", &oType)->isName("Widget");
oField.free(); oType.free();
// registered under nID; WriteNew adds the kids of the pages converted later in SyncAcroFormFromReader
if (bField)
pObj = DictToCDictObjectUnified(&oRef, SDictConvertContext(pDoc, xref, pManager, nStartRefID));
oRef.free();
}
if (!pObj || pObj->GetType() != PdfWriter::object_type_DICT || ((PdfWriter::CDictObject*)pObj)->GetDictType() != PdfWriter::dict_type_UNKNOWN)
return NULL;
// the field, its ancestors and the fields under it
AddWidgetParent(pDoc, pManager, pObj);
return pDoc->GetParent(nID);
}
// The parent of a field: as the JS sends it, as converted, or as the reader has it. 0 - a root
int GetFieldParentID(CPdfReader* pReader, CObjectsManager* pManager, const std::map<int, int>& mJSParent, int nID)
{
std::map<int, int>::const_iterator it = mJSParent.find(nID);
if (it != mJSParent.end())
return it->second;
PdfWriter::CObjectBase* pObj = pManager->GetObj(nID);
if (pObj && pObj->GetType() == PdfWriter::object_type_DICT)
{
PdfWriter::CObjectBase* pParent = ((PdfWriter::CDictObject*)pObj)->Get("Parent");
int nParentID = pParent ? pManager->FindObj(pParent) : -1;
return nParentID > 0 ? nParentID : 0;
}
PDFDoc* pPDFDocument = NULL;
int nStartRefID = 0;
int nRefNum = pReader->FindRefNum(nID, &pPDFDocument, &nStartRefID);
if (nRefNum < 0 || !pPDFDocument)
return 0;
XRef* xref = pPDFDocument->getXRef();
int nParentID = 0;
Object oNode, oParentRef;
if (xref->fetch(nRefNum, FetchGen(xref, nRefNum), &oNode)->isDict() && oNode.dictLookupNF("Parent", &oParentRef)->isRef())
nParentID = oParentRef.getRefNum() + nStartRefID;
oNode.free(); oParentRef.free();
return nParentID;
}
// Split: the fields a widget of the part lies under, up to the root. The editor sends the changed
// fields of all pages; a field not in this set is not in the part
std::set<int> GetSplitLiveFields(CPdfReader* pReader, CObjectsManager* pManager, PdfWriter::CDocument* pDoc, const std::vector<CWidgetsInfo::CParent*>& arrParents)
{
std::map<int, int> mJSParent;
for (CWidgetsInfo::CParent* pParent : arrParents)
mJSParent[pParent->nID] = (pParent->nFlags & (1 << 4)) ? pParent->nParentID : 0;
std::set<int> setLive;
const std::map<int, PdfWriter::CAnnotation*>& mAnnots = pDoc->GetAnnots();
for (std::map<int, PdfWriter::CAnnotation*>::const_iterator it = mAnnots.begin(); it != mAnnots.end(); ++it)
{
if (!it->second || it->second->GetAnnotationType() != PdfWriter::AnnotWidget)
continue;
PdfWriter::CWidgetAnnotation* pWidget = (PdfWriter::CWidgetAnnotation*)it->second;
// written by the JS, or converted with a page split out
int nID = pWidget->GetParentID();
if (nID <= 0)
{
PdfWriter::CObjectBase* pParent = pWidget->Get("Parent");
nID = pParent ? pManager->FindObj(pParent) : -1;
}
for (int nDepth = 0; nID > 0 && nDepth < 32 && setLive.insert(nID).second; ++nDepth)
nID = GetFieldParentID(pReader, pManager, mJSParent, nID);
}
return setLive;
}
// The pages (origin indexes) of the widget nID, or of the widgets under the field nID, deleted ones aside
void GetWidgetPages(CPdfReader* pReader, CObjectsManager* pManager, int nID, std::set<int>& setPages)
{
PDFDoc* pPDFDocument = NULL;
int nStartRefID = 0;
int nRefNum = pReader->FindRefNum(nID, &pPDFDocument, &nStartRefID);
if (nRefNum < 0 || !pPDFDocument)
return;
XRef* xref = pPDFDocument->getXRef();
int nPagesBefore = pReader->GetNumPagesBefore(pPDFDocument);
std::set<int> setSeen;
std::vector< std::pair<Ref, int> > arrStack;
Ref oRoot = { nRefNum, FetchGen(xref, nRefNum) };
arrStack.push_back(std::make_pair(oRoot, 0));
while (!arrStack.empty())
{
Ref oRef = arrStack.back().first;
int nDepth = arrStack.back().second;
arrStack.pop_back();
CObjectGuard oNode, oPageRef, oKids;
if (!setSeen.insert(oRef.num).second || pManager->m_setDeletedIDs.count(oRef.num + nStartRefID) || !xref->fetch(oRef.num, oRef.gen, oNode.get())->isDict())
continue;
if (oNode->dictLookupNF("P", oPageRef.get())->isRef())
{
int nPage = pPDFDocument->findPage(oPageRef->getRefNum(), oPageRef->getRefGen());
if (nPage > 0)
setPages.insert(nPagesBefore + nPage - 1);
}
if (nDepth >= 32 || !oNode->dictLookup("Kids", oKids.get())->isArray())
continue;
for (int i = 0; i < oKids->arrayGetLength(); ++i)
{
CObjectGuard oKidRef;
if (oKids->arrayGetNF(i, oKidRef.get())->isRef())
arrStack.push_back(std::make_pair(oKidRef->getRef(), nDepth + 1));
}
}
}
// Kids of the field nID and of the fields under it: every converted kid, in the reader's order
void SyncFieldKidsOf(CPdfReader* pReader, CObjectsManager* pManager, int nID)
{
PDFDoc* pPDFDocument = NULL;
int nStartRefID = 0;
int nRefNum = pReader->FindRefNum(nID, &pPDFDocument, &nStartRefID);
if (nRefNum < 0 || !pPDFDocument)
return;
XRef* xref = pPDFDocument->getXRef();
CObjectGuard oNode;
std::set<int> setVisited;
if (xref->fetch(nRefNum, FetchGen(xref, nRefNum), oNode.get())->isDict())
AddMissingFieldKids(oNode.get(), nID, xref, *pManager, nStartRefID, 0, setVisited);
}
}
bool CPdfEditor::EditWidgets(IAdvancedCommand* pCommand)
{
if (m_nMode == Mode::Unknown && !IncrementalUpdates())
return false;
WriteRedact({});
m_arrRedact.clear();
CWidgetsInfo* pFieldInfo = (CWidgetsInfo*)pCommand;
PdfWriter::CDocument* pDoc = m_pWriter->GetDocument();
for (int i = 0; i < m_mObjManager.m_arrRedactFormFields.size(); ++i)
{
IAdvancedCommand::AdvancedCommandType nType = m_mObjManager.m_arrRedactFormFields[i]->GetCommandType();
if (nType == IAdvancedCommand::AdvancedCommandType::Annotation)
{
// Annotation - annotation that has changed or been created and needs to be drawn in Contents of page
CAnnotFieldInfo* command = dynamic_cast<CAnnotFieldInfo*>(m_mObjManager.m_arrRedactFormFields[i]);
CAnnotFieldInfo& oInfo = *command;
if (!oInfo.IsWidget())
continue;
int nPage = oInfo.GetPage(); // actual page number
PdfWriter::CPage* pPage = pDoc->GetPage(nPage);
if (!pPage)
{
EditPage(nPage, false, true);
pPage = pDoc->GetPage(nPage);
}
if (!pPage)
pPage = pDoc->GetEditPage(nPage);
if (!pPage)
continue;
m_pWriter->SetPage(pPage);
pDoc->SetCurPage(pPage);
m_pWriter->AddAnnotField(NULL, command, true);
}
}
const std::vector< std::pair<int, int> >& arrCO = pFieldInfo->GetCO();
const std::vector<CWidgetsInfo::CParent*>& arrParents = pFieldInfo->GetParents();
// Split and WriteNew renumber the reader: no raw reader reference (ChangeCO),
// no parent copied with its reader number (GetWidgetParent)
bool bRebuild = m_nMode == Mode::Split || m_nMode == Mode::WriteNew;
std::set<int> setSplitLive;
if (m_nMode == Mode::Split && (!arrCO.empty() || !arrParents.empty()))
setSplitLive = GetSplitLiveFields(m_pReader, &m_mObjManager, pDoc, arrParents);
// WriteNew converts the pages not edited only in Close, after this command. The pages of a CO widget
// and of a changed field's widgets are converted here: EditCO lists the widget in the editor's order,
// EditWidgetParents gets every kid in Kids (Opt pairs with it by index)
std::map<int, int> mFakePages; // origin index -> page index, the pages not converted yet
bool bFakePages = false;
auto EditWidgetPages = [this, pDoc, &mFakePages, &bFakePages](int nID)
{
std::set<int> setPages;
GetWidgetPages(m_pReader, &m_mObjManager, nID, setPages);
if (setPages.empty())
return;
// listed once: CPageTree::GetObj walks the tree
if (!bFakePages)
{
bFakePages = true;
PdfWriter::CPageTree* pPageTree = pDoc->GetPageTree();
for (int nPage = 0; nPage < (int)pPageTree->GetCount(); ++nPage)
{
PdfWriter::CFakePage* pFakePage = dynamic_cast<PdfWriter::CFakePage*>(pPageTree->GetObj(nPage));
if (pFakePage)
mFakePages.emplace(pFakePage->GetOriginIndex(), nPage);
}
}
for (int nOrigin : setPages)
{
std::map<int, int>::iterator it = mFakePages.find(nOrigin);
if (it == mFakePages.end())
continue;
int nPage = it->second;
mFakePages.erase(it);
// the converted page takes the place of the fake one, the indexes stay
EditPage(nPage, false, true);
}
};
for (int i = 0; i < arrCO.size(); ++i)
{
int nObjNum = arrCO[i].first;
if (pDoc->GetParent(nObjNum))
continue;
if (pDoc->GetAnnot(nObjNum))
continue;
if (bRebuild)
{
// EditCO finds a field registered as a parent, a widget among the annotations,
// and leaves out the rest: its gen stays -1
if (m_mObjManager.m_setDeletedIDs.count(nObjNum) || (m_nMode == Mode::Split && !setSplitLive.count(nObjNum)))
continue;
// a widget (a field of one widget) of a page not edited
if (!GetRebuildParent(m_pReader, &m_mObjManager, pDoc, nObjNum) && m_nMode == Mode::WriteNew)
EditWidgetPages(nObjNum);
continue;
}
// a widget deleted in this update is left out (EditCO: its gen stays -1)
if (m_mObjManager.m_setDeletedIDs.count(nObjNum))
continue;
PDFDoc* pPDFDocument = NULL;
int nStartRefID = 0;
int nRefID = m_pReader->FindRefNum(nObjNum, &pPDFDocument, &nStartRefID);
if (nRefID < 0)
continue;
XRefEntry* pEntry = pPDFDocument->getXRef()->getEntry(nRefID);
pFieldInfo->ChangeCO(i, nRefID, pEntry->type == xrefEntryCompressed ? 0 : pEntry->gen);
}
for (CWidgetsInfo::CParent* pParent : arrParents)
{
if (bRebuild)
{
// The changed field and its parent are registered here for EditWidgetParents.
// Split: one no widget of the part lies under is not, and EditWidgetParents skips it
int arrID[2] = { pParent->nID, (pParent->nFlags & (1 << 4)) ? pParent->nParentID : -1 };
for (int nID : arrID)
{
if (nID < 0 || (m_nMode == Mode::Split && !setSplitLive.count(nID)))
continue;
// The changed field gets its kids in the reader's order and Opt cut to them: WriteNew all of them
// (their pages converted here), Split the ones of the part (a field converted here, after
// SplitPages synced the others)
bool bSync = nID == pParent->nID && !m_mObjManager.m_setDeletedIDs.count(nID);
if (bSync && m_nMode == Mode::WriteNew)
EditWidgetPages(nID);
if (GetRebuildParent(m_pReader, &m_mObjManager, pDoc, nID))
{
if (bSync)
SyncFieldKidsOf(m_pReader, &m_mObjManager, nID);
}
// a new field, or a deleted one the JS has a new widget under: it keeps its name only,
// nothing of what was deleted (a redacted value)
else if (m_pReader->FindRefNum(nID, NULL, NULL) < 0 || m_mObjManager.m_setDeletedIDs.count(nID))
{
PdfWriter::CDictObject* pNew = pDoc->CreateParent(nID);
PDFDoc* pPDFDocument = NULL;
int nRefNum = m_pReader->FindRefNum(nID, &pPDFDocument, NULL);
CObjectGuard oField, oT;
if (nRefNum >= 0 && pPDFDocument && pPDFDocument->getXRef()->fetch(nRefNum, FetchGen(pPDFDocument->getXRef(), nRefNum), oField.get())->isDict() &&
oField->dictLookup("T", oT.get())->isString())
pNew->Add("T", DictToCDictObjectUnified(oT.get(), SDictConvertContext()));
}
}
continue;
}
PdfWriter::CDictObject* pDParent = pDoc->GetParent(pParent->nID);
if (pDParent)
continue;
PDFDoc* pPDFDocument = NULL;
int nStartRefID = 0;
int nRefID = m_pReader->FindRefNum(pParent->nID, &pPDFDocument, &nStartRefID);
if (nRefID < 0)
continue;
// a merged file's field is converted (renumbered) with its pages, not copied under its reader number
if (nStartRefID)
{
GetRebuildParent(m_pReader, &m_mObjManager, pDoc, pParent->nID);
continue;
}
XRefEntry* pEntry = pPDFDocument->getXRef()->getEntry(nRefID);
Object oParentRef;
oParentRef.initRef(nRefID, pEntry->type == xrefEntryCompressed ? 0 : pEntry->gen);
GetWidgetParent(pPDFDocument, pDoc, &oParentRef, nStartRefID);
// TODO re-set children
oParentRef.free();
}
// A widget the editor adds to a field it does not send as changed names the field only by its ParentID,
// and EditWidgetParents links only the fields sent: such a field is linked here
std::set<int> setSent, setOrphan;
for (CWidgetsInfo::CParent* pParent : arrParents)
setSent.insert(pParent->nID);
const std::map<int, PdfWriter::CAnnotation*>& mAnnots = pDoc->GetAnnots();
for (std::map<int, PdfWriter::CAnnotation*>::const_iterator it = mAnnots.begin(); it != mAnnots.end(); ++it)
{
if (!it->second || it->second->GetAnnotationType() != PdfWriter::AnnotWidget)
continue;
PdfWriter::CWidgetAnnotation* pWidget = (PdfWriter::CWidgetAnnotation*)it->second;
int nParentID = pWidget->GetParentID();
if (nParentID > 0 && !pWidget->Get("Parent") && !setSent.count(nParentID) && !m_mObjManager.m_setDeletedIDs.count(nParentID))
setOrphan.insert(nParentID);
}
for (int nID : setOrphan)
{
PdfWriter::CDictObject* pParent = pDoc->GetParent(nID);
if (!pParent && bRebuild)
pParent = GetRebuildParent(m_pReader, &m_mObjManager, pDoc, nID);
else if (!pParent)
{
PDFDoc* pPDFDocument = NULL;
int nStartRefID = 0;
int nRefID = m_pReader->FindRefNum(nID, &pPDFDocument, &nStartRefID);
if (nRefID < 0 || !pPDFDocument)
continue;
// a merged file's field is converted (renumbered) with its pages: a copy would keep its reader numbers
if (nStartRefID)
pParent = GetRebuildParent(m_pReader, &m_mObjManager, pDoc, nID);
else
{
Object oParentRef;
oParentRef.initRef(nRefID, FetchGen(pPDFDocument->getXRef(), nRefID));
pParent = GetWidgetParent(pPDFDocument, pDoc, &oParentRef, nStartRefID);
oParentRef.free();
}
}
// Opt of a checkbox or radio group pairs with Kids by index: the editor sends no entry for the kid
if (pParent && !HasButtonOpt(pParent, NULL))
pDoc->SetParentKids(nID);
}
return true;
}
int CPdfEditor::GetPagesCount()
{
return m_pWriter->GetDocument()->GetPagesCount();
}
void CPdfEditor::GetPageInfo(int nPageIndex, double* pdWidth, double* pdHeight, double* pdDpiX, double* pdDpiY)
{
PdfWriter::CPage* pPage = m_pWriter->GetDocument()->GetPage(nPageIndex);
if (!pPage)
return;
int nRotate = pPage->GetRotate();
if (nRotate % 180 == 0)
{
*pdWidth = pPage->GetWidth();
*pdHeight = pPage->GetHeight();
}
else
{
*pdWidth = pPage->GetHeight();
*pdHeight = pPage->GetWidth();
}
*pdDpiX = 72.0;
*pdDpiY = 72.0;
}
int CPdfEditor::GetRotate(int nPageIndex)
{
PdfWriter::CPage* pPage = m_pWriter->GetDocument()->GetPage(nPageIndex);
if (!pPage)
return 0;
return pPage->GetRotate();
}
bool CPdfEditor::IsEditPage()
{
return m_nEditPage >= 0;
}
void CPdfEditor::ClearPage()
{
PDFDoc* pPDFDocument = NULL;
PdfReader::CPdfFontList* pFontList = NULL;
int nStartRefID = 0;
int nPageIndex = m_pReader->GetPageIndex(m_nEditPage, &pPDFDocument, &pFontList, &nStartRefID);
PdfWriter::CDocument* pDoc = m_pWriter->GetDocument();
if (nPageIndex < 0 || !pPDFDocument || !pDoc)
return;
XRef* xref = pPDFDocument->getXRef();
Ref* pPageRef = pPDFDocument->getCatalog()->getPageRef(nPageIndex);
// Get page object
Object pageRefObj, pageObj;
pageRefObj.initRef(pPageRef->num, pPageRef->gen);
if (!pageRefObj.fetch(xref, &pageObj)->isDict())
{
pageObj.free(); pageRefObj.free();
return;
}
pageRefObj.free();
Object oAnnots;
// TEMPORARILY delete Link annotations when editing
if (pageObj.dictLookup("Annots", &oAnnots)->isArray())
{
for (int nIndex = 0; nIndex < oAnnots.arrayGetLength(); ++nIndex)
{
Object oAnnot, oSubtype, oAnnotRef;
if (oAnnots.arrayGet(nIndex, &oAnnot)->isDict("Annot") && oAnnot.dictLookup("Subtype", &oSubtype)->isName("Link"))
{
oAnnots.arrayGetNF(nIndex, &oAnnotRef);
// DeleteAnnot takes the manager ID, the page may come from a merged file
DeleteAnnot(oAnnotRef.getRefNum() + nStartRefID, &oAnnots);
}
oAnnot.free(); oSubtype.free(); oAnnotRef.free();
}
}
pageObj.free();
if (m_nMode == Mode::Split || m_nMode == Mode::WriteNew)
{
PdfWriter::CPage* pPage = pDoc->GetCurPage();
auto removeContentObj = [&](PdfWriter::CObjectBase* pObj)
{
if (pObj->GetType() == PdfWriter::object_type_DICT)
{
PdfWriter::CObjectBase* pLength = ((PdfWriter::CDictObject*)pObj)->Get("Length");
if (pLength)
{
int nLengthID = m_mObjManager.FindObj(pLength);
m_mObjManager.RemoveObj(nLengthID);
}
}
int nObjID = m_mObjManager.FindObj(pObj);
m_mObjManager.RemoveObj(nObjID);
};
PdfWriter::CObjectBase* pObjContents = pPage->Get("Contents");
if (pObjContents && pObjContents->GetType() == PdfWriter::object_type_ARRAY)
{
PdfWriter::CArrayObject* pContents = (PdfWriter::CArrayObject*)pObjContents;
for (int i = 0; i < pContents->GetCount(); ++i)
removeContentObj(pContents->Get(i));
}
else if (pObjContents)
removeContentObj(pObjContents);
pDoc->ClearPageFull();
}
else
pDoc->ClearPage();
Page* pOPage = pPDFDocument->getCatalog()->getPage(nPageIndex);
if (pOPage->isCropped())
{
PDFRectangle* pCropBox = pOPage->getCropBox();
PdfWriter::CStream* pStream = pDoc->GetCurPage()->GetStream();
pStream->WriteStr("1 0 0 1 ");
pStream->WriteReal(pCropBox->x1);
pStream->WriteChar(' ');
pStream->WriteReal(pCropBox->y2 - pOPage->getMediaBox()->y2);
pStream->WriteStr(" cm\012");
}
if (m_nMode == Mode::Split)
{
oAnnots.free();
return;
}
// Mode::WriteAppend && Mode::WriteNew
// Reading and processing fonts from page
Dict* pResources = pOPage->getResourceDict();
ScanFonts(pPDFDocument, pResources, m_pWriter, &m_mObjManager, m_pReader);
oAnnots.free();
}
void CPdfEditor::AddShapeXML(const std::string& sXML)
{
return m_pWriter->GetDocument()->AddShapeXML(sXML);
}
void CPdfEditor::EndMarkedContent()
{
m_pWriter->GetDocument()->EndShapeXML();
}
bool CPdfEditor::IsBase14(const std::wstring& wsFontName, bool& bBold, bool& bItalic, std::wstring& wsFontPath)
{
std::map<std::wstring, std::wstring>::const_iterator it = m_mFonts.find(wsFontName);
if (it != m_mFonts.end())
wsFontPath = it->second;
if (wsFontPath.empty())
{
const std::map<std::wstring, std::wstring>& mFonts = m_pReader->GetFonts();
std::map<std::wstring, std::wstring>::const_iterator it2 = mFonts.find(wsFontName);
if (it2 != mFonts.end())
wsFontPath = it2->second;
}
if (wsFontPath.empty())
return false;
if (wsFontName == L"Helvetica")
return true;
if (wsFontName == L"Helvetica-Bold")
{
bBold = true;
return true;
}
if (wsFontName == L"Helvetica-Oblique")
{
bItalic = true;
return true;
}
if (wsFontName == L"Helvetice-BoldOblique")
{
bBold = true;
bItalic = true;
return true;
}
if (wsFontName == L"Courier")
return true;
if (wsFontName == L"Courier-Bold")
{
bBold = true;
return true;
}
if (wsFontName == L"Courier-Oblique")
{
bItalic = true;
return true;
}
if (wsFontName == L"Courier-BoldOblique")
{
bBold = true;
bItalic = true;
return true;
}
if (wsFontName == L"Times" || wsFontName == L"Times-Roman")
return true;
if (wsFontName == L"Times-Bold")
{
bBold = true;
return true;
}
if (wsFontName == L"Times-Oblique")
{
bItalic = true;
return true;
}
if (wsFontName == L"Times-BoldOblique")
{
bBold = true;
bItalic = true;
return true;
}
if (wsFontName == L"Symbol")
return true;
if (wsFontName == L"ZapfDingbats")
return true;
return false;
}
void CPdfEditor::Redact(IAdvancedCommand* _pCommand)
{
PDFDoc* pPDFDocument = NULL;
PdfReader::CPdfFontList* pFontList = NULL;
int nStartRefID = 0;
PDFRectangle* cropBox = NULL;
int nPageIndex = -1;
Page* pPage = NULL;
bool bEditPage = IsEditPage();
PdfWriter::CDocument* pDoc = m_pWriter->GetDocument();
if (bEditPage)
{
nPageIndex = m_pReader->GetPageIndex(m_nEditPage, &pPDFDocument, &pFontList, &nStartRefID);
if (nPageIndex < 0 || !pPDFDocument)
return;
pPage = pPDFDocument->getCatalog()->getPage(nPageIndex);
cropBox = pPage->getCropBox();
}
else
{
cropBox = new PDFRectangle();
PdfWriter::CPage* pWPage = pDoc->GetCurPage();
PdfWriter::TBox oBox = pWPage->GetBox("CropBox");
if (oBox.IsEmpty())
oBox = pWPage->GetBox("MediaBox");
cropBox = new PDFRectangle(oBox.fLeft, oBox.fBottom, oBox.fRight, oBox.fTop);
}
std::vector<double> arrAllQuads;
if (_pCommand)
{
CRedact* pCommand = (CRedact*)_pCommand;
std::vector<CRedact::SRedact*> arrRedacts = pCommand->GetRedact();
for (CRedact::SRedact* pRedact : arrRedacts)
{
m_arrRedact.push_back(CRedactData());
m_arrRedact.back().sID = pRedact->sID;
m_arrRedact.back().arrQuads = pRedact->arrQuadPoints;
for (int i = 0; i < pRedact->arrQuadPoints.size(); i += 2)
{
arrAllQuads.push_back(pRedact->arrQuadPoints[i + 0]);
arrAllQuads.push_back(cropBox->y2 - pRedact->arrQuadPoints[i + 1]);
}
int nFlags = pRedact->nFlag;
if (nFlags & (1 << 0))
{
m_arrRedact.back().pRender = pRedact->pRender;
m_arrRedact.back().nLenRender = pRedact->nRenderLen;
}
}
}
if (bEditPage)
{
std::set<int> arrOverlapHiddenTextIndices;
std::vector<int> arrOverlapFormTextCounts, arrOverlapFormGraphicsCounts;
std::vector<PdfWriter::SOverlapGraphicsResult> arrOverlapGraphicsResults;
bool bTextOnly = (m_nRedactInfo & (1 << 6)) != 0;
if (bTextOnly)
{
PdfWriter::OverlapOutputDev oOverlapOut(bTextOnly);
oOverlapOut.NewPDF(pPDFDocument->getXRef());
Object oContents;
pPage->getContents(&oContents);
PDFRectangle* box = pPage->getMediaBox();
Gfx* gfx = new Gfx(pPDFDocument, &oOverlapOut, m_nEditPage, pPage->getResourceDict(), 72.0, 72.0, box, NULL, 0);
gfx->saveState();
gfx->display(&oContents);
gfx->endOfPage();
oContents.free();
RELEASEOBJECT(gfx);
arrOverlapHiddenTextIndices = oOverlapOut.GetHiddenText();
arrOverlapFormTextCounts = oOverlapOut.GetFormTextCounts();
arrOverlapFormGraphicsCounts = oOverlapOut.GetFormGraphicsCounts();
arrOverlapGraphicsResults = oOverlapOut.GetHiddenGraphics();
}
PdfWriter::RedactOutputDev oRedactOut(m_pWriter, &m_mObjManager, nStartRefID, m_nRedactInfo);
oRedactOut.NewPDF(pPDFDocument->getXRef());
oRedactOut.SetRedact(arrAllQuads);
oRedactOut.SetOverlapInfo(arrOverlapHiddenTextIndices, &arrOverlapFormTextCounts, &arrOverlapFormGraphicsCounts, std::move(arrOverlapGraphicsResults));
Object oContents;
pPage->getContents(&oContents);
PDFRectangle* box = pPage->getMediaBox();
Gfx* gfx = new Gfx(pPDFDocument, &oRedactOut, m_nEditPage, pPage->getResourceDict(), 72.0, 72.0, box, NULL, 0);
gfx->saveState();
gfx->display(&oContents);
gfx->endOfPage();
oContents.free();
RELEASEOBJECT(gfx);
PdfWriter::CPage* pWPage = pDoc->GetCurPage();
pWPage->SetFontType(PdfWriter::EFontType::fontUnknownType);
}
else
{
RELEASEOBJECT(cropBox);
}
}
std::vector<double> CPdfEditor::WriteRedact(const std::vector<std::wstring>& arrID)
{
std::wstring sID;
if (!arrID.empty())
sID = arrID[0];
std::vector<double> arrRes;
for (int i = 0; i < m_arrRedact.size(); ++i)
{
if (m_arrRedact[i].sID == sID)
{
for (int j = i; j < m_arrRedact.size(); ++j)
arrRes.insert(arrRes.end(), m_arrRedact[j].arrQuads.begin(), m_arrRedact[j].arrQuads.end());
return arrRes;
}
CRedactData oRedact = m_arrRedact[i];
if (oRedact.bDraw || !oRedact.pRender || oRedact.nLenRender != oRedact.arrQuads.size() / 8 * 12)
continue;
BYTE* pMemory = oRedact.pRender;
m_pWriter->AddRedact({});
double dM1, dM2, dM3, dM4, dM5, dM6;
m_pWriter->GetTransform(&dM1, &dM2, &dM3, &dM4, &dM5, &dM6);
LONG lType, lColorB, lAlpha1, lAlpha2;
m_pWriter->get_BrushType(&lType);
m_pWriter->get_BrushColor1(&lColorB);
m_pWriter->get_BrushAlpha1(&lAlpha1);
m_pWriter->get_BrushAlpha2(&lAlpha2);
m_pWriter->SetTransform(1, 0, 0, 1, 0, 0);
m_pWriter->PathCommandEnd();
m_pWriter->put_BrushType(c_BrushTypeSolid);
m_pWriter->put_BrushAlpha1(255);
m_pWriter->put_BrushAlpha2(255);
for (int i = 0; i < oRedact.arrQuads.size(); i += 8)
{
int ret = *((int*)pMemory);
pMemory += 4;
LONG R = ret;
ret = *((int*)pMemory);
pMemory += 4;
LONG G = ret;
ret = *((int*)pMemory);
pMemory += 4;
LONG B = ret;
LONG lColor = (LONG)(R | (G << 8) | (B << 16) | ((LONG)255 << 24));
m_pWriter->put_BrushColor1(lColor);
m_pWriter->PathCommandMoveTo(PdfReader::PDFCoordsToMM(oRedact.arrQuads[i + 0]), PdfReader::PDFCoordsToMM(oRedact.arrQuads[i + 1]));
m_pWriter->PathCommandLineTo(PdfReader::PDFCoordsToMM(oRedact.arrQuads[i + 2]), PdfReader::PDFCoordsToMM(oRedact.arrQuads[i + 3]));
m_pWriter->PathCommandLineTo(PdfReader::PDFCoordsToMM(oRedact.arrQuads[i + 4]), PdfReader::PDFCoordsToMM(oRedact.arrQuads[i + 5]));
m_pWriter->PathCommandLineTo(PdfReader::PDFCoordsToMM(oRedact.arrQuads[i + 6]), PdfReader::PDFCoordsToMM(oRedact.arrQuads[i + 7]));
m_pWriter->PathCommandClose();
m_pWriter->DrawPath(NULL, L"", c_nWindingFillMode);
m_pWriter->PathCommandEnd();
}
m_pWriter->SetTransform(dM1, dM2, dM3, dM4, dM5, dM6);
m_pWriter->put_BrushType(lType);
m_pWriter->put_BrushColor1(lColorB);
m_pWriter->put_BrushAlpha1(lAlpha1);
m_pWriter->put_BrushAlpha2(lAlpha2);
// TODO Redact render should intersect with all subsequent Redacts
// TODO actually there should be render of Redact commands
/*
PdfWriter::CPage* pCurPage = m_pWriter->GetPage();
pDoc->FixEditPage(pCurPage);
PdfWriter::CPage* pFakePage = new PdfWriter::CPage(pDoc);
m_pWriter->SetPage(pFakePage);
pDoc->SetCurPage(pFakePage);
// TODO Need to reset current matrix to identity, then shift by CropBox
pFakePage->SetStream(pCurPage->GetStream());
pFakePage->Add("Resources", pCurPage->Get("Resources"));
IMetafileToRenderter* pCorrector = new IMetafileToRenderter(m_pWriter->GetRenderer());
NSOnlineOfficeBinToPdf::ConvertBufferToRenderer(pRender, nLenRender, pCorrector);
RELEASEOBJECT(pCorrector);
m_pWriter->SetPage(pCurPage);
pDoc->SetCurPage(pCurPage);
RELEASEOBJECT(pFakePage);
*/
m_arrRedact[i].bDraw = true;
}
return arrRes;
}
void CPdfEditor::RedactMeta(IAdvancedCommand* pCommand, const std::vector<double>& arrRedact)
{
CRedactMeta* pRedactMeta = (CRedactMeta*)pCommand;
std::vector<int> arrWidget = pRedactMeta->GetWidgetID();
for (int i = 0; i < m_mObjManager.m_arrRedactFormFields.size(); ++i)
{
IAdvancedCommand::AdvancedCommandType nType = m_mObjManager.m_arrRedactFormFields[i]->GetCommandType();
if (nType == IAdvancedCommand::AdvancedCommandType::Annotation)
{
// Annotation - annotation that has changed or been created and needs to be drawn in Contents of page
CAnnotFieldInfo* command = dynamic_cast<CAnnotFieldInfo*>(m_mObjManager.m_arrRedactFormFields[i]);
CAnnotFieldInfo& oInfo = *command;
if (!oInfo.IsWidget())
continue;
int nID = oInfo.GetID();
std::vector<int>::iterator it = std::find(arrWidget.begin(), arrWidget.end(), nID);
if (it == arrWidget.end())
continue;
m_pWriter->AddAnnotField(NULL, command, true);
}
}
CloseWriteNew_FlushAnnotations(m_pReader, &m_mObjManager, m_pWriter->GetDocument(), m_pWriter, m_nRedactInfo, pCommand, arrRedact);
}
// A kid the editor moved to another field - a field renamed into it: SetParentKids gives a widget its new
// Parent, EditWidgetParents a field - leaves the Kids of the field that still lists it, its Opt entry with it
// (KidMovedAway; WriteNew: AddMissingFieldKids at Close does not list it there again).
// WriteAppend: a field so left with no kid leaves the form at Close (DeleteEmptyAppendFields)
void CPdfEditor::ReleaseMovedKids()
{
PdfWriter::CDocument* pDoc = m_pWriter->GetDocument();
const std::map<int, PdfWriter::CDictObject*>& mParents = pDoc->GetParents();
for (std::map<int, PdfWriter::CDictObject*>::const_iterator it = mParents.begin(); it != mParents.end(); ++it)
{
PdfWriter::CDictObject* pParent = it->second;
PdfWriter::CObjectBase* pKidsObj = pParent ? pParent->Get("Kids") : NULL;
if (!pKidsObj || pKidsObj->GetType() != PdfWriter::object_type_ARRAY)
continue;
PdfWriter::CArrayObject* pKids = (PdfWriter::CArrayObject*)pKidsObj;
std::vector<PdfWriter::CDictObject*> arrMoved;
std::vector<int> arrMovedID;
for (int i = 0; i < pKids->GetCount(); ++i)
{
PdfWriter::CObjectBase* pKid = pKids->Get(i);
if (!pKid)
continue; // a deleted kid
if (pKid->GetType() != PdfWriter::object_type_DICT)
{
// WriteAppend: a field copied under its number (GetWidgetParent) keeps its kids as raw
// references; a kid field the editor re-parented is a copy under its number too
if (pKid->GetObjId() > 0 && it->first == (int)pParent->GetObjId())
{
PdfWriter::CDictObject* pCopy = pDoc->GetParent((int)pKid->GetObjId());
if (pCopy && pCopy->GetObjId() == pKid->GetObjId() && KidMovedAway(pParent, pCopy))
arrMovedID.push_back((int)pKid->GetObjId());
}
continue;
}
if (KidMovedAway(pParent, pKid))
arrMoved.push_back((PdfWriter::CDictObject*)pKid);
}
for (size_t i = 0; i < arrMoved.size(); ++i)
RemoveParentKid(pParent, arrMoved[i], 0);
for (size_t i = 0; i < arrMovedID.size(); ++i)
RemoveParentKid(pParent, NULL, arrMovedID[i]);
if ((!arrMoved.empty() || !arrMovedID.empty()) && m_nMode == Mode::WriteAppend)
m_setLostKidFields.insert(it->first);
}
}
void CPdfEditor::FinalizeWidgetRedactStamps()
{
CloseWriteNew_FinalizeWidgetStamps(m_pReader, &m_mObjManager, m_pWriter->GetDocument(), m_pWriter, m_nRedactInfo);
}