PDF4QT/PdfForQtLib/sources/pdfaction.cpp

510 lines
20 KiB
C++

// Copyright (C) 2019-2020 Jakub Melka
//
// This file is part of PdfForQt.
//
// PdfForQt is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// PdfForQt is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with PDFForQt. If not, see <https://www.gnu.org/licenses/>.
#include "pdfaction.h"
#include "pdfdocument.h"
#include "pdfexception.h"
#include "pdfencoding.h"
namespace pdf
{
PDFActionPtr PDFAction::parse(const PDFObjectStorage* storage, PDFObject object)
{
std::set<PDFObjectReference> usedReferences;
return parseImpl(storage, qMove(object), usedReferences);
}
void PDFAction::apply(const std::function<void (const PDFAction*)>& callback)
{
callback(this);
for (const PDFActionPtr& nextAction : m_nextActions)
{
nextAction->apply(callback);
}
}
std::vector<const PDFAction*> PDFAction::getActionList() const
{
std::vector<const PDFAction*> result;
fillActionList(result);
return result;
}
PDFActionPtr PDFAction::parseImpl(const PDFObjectStorage* storage, PDFObject object, std::set<PDFObjectReference>& usedReferences)
{
if (object.isReference())
{
PDFObjectReference reference = object.getReference();
if (usedReferences.count(reference))
{
throw PDFException(PDFTranslationContext::tr("Circular dependence in actions found."));
}
usedReferences.insert(reference);
object = storage->getObjectByReference(reference);
}
if (object.isNull())
{
return PDFActionPtr();
}
if (!object.isDictionary())
{
throw PDFException(PDFTranslationContext::tr("Invalid action."));
}
PDFDocumentDataLoaderDecorator loader(storage);
const PDFDictionary* dictionary = object.getDictionary();
QByteArray name = loader.readNameFromDictionary(dictionary, "S");
if (name == "GoTo") // Goto action
{
PDFDestination destination = PDFDestination::parse(storage, dictionary->get("D"));
PDFDestination structureDestination = PDFDestination::parse(storage, dictionary->get("SD"));
return PDFActionPtr(new PDFActionGoTo(qMove(destination), qMove(structureDestination)));
}
else if (name == "GoToR")
{
PDFDestination destination = PDFDestination::parse(storage, dictionary->get("D"));
PDFDestination structureDestination = PDFDestination::parse(storage, dictionary->get("SD"));
PDFFileSpecification fileSpecification = PDFFileSpecification::parse(storage, dictionary->get("F"));
return PDFActionPtr(new PDFActionGoToR(qMove(destination), qMove(structureDestination), qMove(fileSpecification), loader.readBooleanFromDictionary(dictionary, "NewWindow", false)));
}
else if (name == "GoToE")
{
PDFDestination destination = PDFDestination::parse(storage, dictionary->get("D"));
PDFFileSpecification fileSpecification = PDFFileSpecification::parse(storage, dictionary->get("F"));
return PDFActionPtr(new PDFActionGoToE(qMove(destination), qMove(fileSpecification), loader.readBooleanFromDictionary(dictionary, "NewWindow", false), storage->getObject(dictionary->get("T"))));
}
else if (name == "GoToDp")
{
PDFObjectReference documentPart = loader.readReferenceFromDictionary(dictionary, "Dp");
return PDFActionPtr(new PDFActionGoToDp(documentPart));
}
else if (name == "Launch")
{
PDFFileSpecification fileSpecification = PDFFileSpecification::parse(storage, dictionary->get("F"));
const bool newWindow = loader.readBooleanFromDictionary(dictionary, "NewWindow", false);
PDFActionLaunch::Win win;
const PDFObject& winDictionaryObject = storage->getObject(dictionary->get("Win"));
if (winDictionaryObject.isDictionary())
{
const PDFDictionary* winDictionary = winDictionaryObject.getDictionary();
win.file = loader.readStringFromDictionary(winDictionary, "F");
win.directory = loader.readStringFromDictionary(winDictionary, "D");
win.operation = loader.readStringFromDictionary(winDictionary, "O");
win.parameters = loader.readStringFromDictionary(winDictionary, "P");
}
return PDFActionPtr(new PDFActionLaunch(qMove(fileSpecification), newWindow, qMove(win)));
}
else if (name == "Thread")
{
PDFFileSpecification fileSpecification = PDFFileSpecification::parse(storage, dictionary->get("F"));
PDFActionThread::Thread thread;
PDFActionThread::Bead bead;
const PDFObject& threadObject = dictionary->get("D");
if (threadObject.isReference())
{
thread = threadObject.getReference();
}
else if (threadObject.isInt())
{
thread = threadObject.getInteger();
}
else if (threadObject.isString())
{
thread = PDFEncoding::convertTextString(threadObject.getString());
}
const PDFObject& beadObject = dictionary->get("B");
if (beadObject.isReference())
{
bead = beadObject.getReference();
}
else if (beadObject.isInt())
{
bead = beadObject.getInteger();
}
return PDFActionPtr(new PDFActionThread(qMove(fileSpecification), qMove(thread), qMove(bead)));
}
else if (name == "URI")
{
return PDFActionPtr(new PDFActionURI(loader.readStringFromDictionary(dictionary, "URI"), loader.readBooleanFromDictionary(dictionary, "IsMap", false)));
}
else if (name == "Sound")
{
const PDFReal volume = loader.readNumberFromDictionary(dictionary, "Volume", 1.0);
const bool isSynchronous = loader.readBooleanFromDictionary(dictionary, "Synchronous", false);
const bool isRepeat = loader.readBooleanFromDictionary(dictionary, "Repeat", false);
const bool isMix = loader.readBooleanFromDictionary(dictionary, "Mix", false);
return PDFActionPtr(new PDFActionSound(PDFSound::parse(storage, dictionary->get("Sound")), volume, isSynchronous, isRepeat, isMix));
}
else if (name == "Movie")
{
constexpr const std::array<std::pair<const char*, PDFActionMovie::Operation>, 4> operations = {
std::pair<const char*, PDFActionMovie::Operation>{ "Play", PDFActionMovie::Operation::Play },
std::pair<const char*, PDFActionMovie::Operation>{ "Stop", PDFActionMovie::Operation::Stop },
std::pair<const char*, PDFActionMovie::Operation>{ "Pause", PDFActionMovie::Operation::Pause },
std::pair<const char*, PDFActionMovie::Operation>{ "Resume", PDFActionMovie::Operation::Resume }
};
// Jakub Melka: parse the movie action
PDFObject annotationObject = dictionary->get("Annotation");
PDFObjectReference annotation = annotationObject.isReference() ? annotationObject.getReference() : PDFObjectReference();
QString title = loader.readTextStringFromDictionary(dictionary, "T", QString());
PDFActionMovie::Operation operation = loader.readEnumByName(dictionary->get("Operation"), operations.cbegin(), operations.cend(), PDFActionMovie::Operation::Play);
return PDFActionPtr(new PDFActionMovie(annotation, qMove(title), operation));
}
else if (name == "Hide")
{
std::vector<PDFObjectReference> annotations;
std::vector<QString> fieldNames;
const PDFObject& object = dictionary->get("T");
if (object.isReference())
{
annotations = { object.getReference() };
}
else if (object.isString())
{
fieldNames = { loader.readTextString(object, QString()) };
}
else if (object.isArray())
{
const PDFArray* items = object.getArray();
for (size_t i = 0; i < items->getCount(); ++i)
{
const PDFObject& itemObject = items->getItem(i);
if (itemObject.isReference())
{
annotations.push_back(itemObject.getReference());
}
else if (itemObject.isString())
{
fieldNames.push_back(loader.readTextString(itemObject, QString()));
}
}
}
const bool hide = loader.readBooleanFromDictionary(dictionary, "H", true);
return PDFActionPtr(new PDFActionHide(qMove(annotations), qMove(fieldNames), hide));
}
else if (name == "Named")
{
constexpr const std::array<std::pair<const char*, PDFActionNamed::NamedActionType>, 4> types = {
std::pair<const char*, PDFActionNamed::NamedActionType>{ "NextPage", PDFActionNamed::NamedActionType::NextPage },
std::pair<const char*, PDFActionNamed::NamedActionType>{ "PrevPage", PDFActionNamed::NamedActionType::PrevPage },
std::pair<const char*, PDFActionNamed::NamedActionType>{ "FirstPage", PDFActionNamed::NamedActionType::FirstPage },
std::pair<const char*, PDFActionNamed::NamedActionType>{ "LastPage", PDFActionNamed::NamedActionType::LastPage }
};
QByteArray name = loader.readNameFromDictionary(dictionary, "N");
PDFActionNamed::NamedActionType actionType = loader.readEnumByName(dictionary->get("N"), types.cbegin(), types.cend(), PDFActionNamed::NamedActionType::Custom);
return PDFActionPtr(new PDFActionNamed(actionType, qMove(name)));
}
else if (name == "SetOCGState")
{
const bool isRadioButtonsPreserved = loader.readBooleanFromDictionary(dictionary, "PreserveRB", true);
PDFActionSetOCGState::StateChangeItems items;
PDFObject stateArrayObject = storage->getObject(dictionary->get("State"));
if (stateArrayObject.isArray())
{
constexpr const std::array<std::pair<const char*, PDFActionSetOCGState::SwitchType>, 3> types = {
std::pair<const char*, PDFActionSetOCGState::SwitchType>{ "ON", PDFActionSetOCGState::SwitchType::ON },
std::pair<const char*, PDFActionSetOCGState::SwitchType>{ "OFF", PDFActionSetOCGState::SwitchType::OFF },
std::pair<const char*, PDFActionSetOCGState::SwitchType>{ "Toggle", PDFActionSetOCGState::SwitchType::Toggle }
};
PDFActionSetOCGState::SwitchType switchType = PDFActionSetOCGState::SwitchType::ON;
const PDFArray* stateArray = stateArrayObject.getArray();
items.reserve(stateArray->getCount());
for (size_t i = 0; i < stateArray->getCount(); ++i)
{
const PDFObject& item = stateArray->getItem(i);
if (item.isName())
{
switchType = loader.readEnumByName(item, types.cbegin(), types.cend(), PDFActionSetOCGState::SwitchType::ON);
}
else if (item.isReference())
{
items.emplace_back(switchType, item.getReference());
}
}
}
return PDFActionPtr(new PDFActionSetOCGState(qMove(items), isRadioButtonsPreserved));
}
else if (name == "Rendition")
{
PDFObject annotationObject = dictionary->get("AN");
std::optional<PDFRendition> rendition;
PDFObjectReference annotation = annotationObject.isReference() ? annotationObject.getReference() : PDFObjectReference();
PDFActionRendition::Operation operation = static_cast<PDFActionRendition::Operation>(loader.readIntegerFromDictionary(dictionary, "OP", 4));
QString javascript;
if (dictionary->hasKey("R"))
{
rendition = PDFRendition::parse(storage, dictionary->get("R"));
}
PDFObject javascriptObject = storage->getObject(dictionary->get("JS"));
if (javascriptObject.isString())
{
javascript = PDFEncoding::convertTextString(javascriptObject.getString());
}
else if (javascriptObject.isStream())
{
javascript = PDFEncoding::convertTextString(storage->getDecodedStream(javascriptObject.getStream()));
}
return PDFActionPtr(new PDFActionRendition(qMove(rendition), annotation, operation, qMove(javascript)));
}
else if (name == "Trans")
{
return PDFActionPtr(new PDFActionTransition(PDFPageTransition::parse(storage, dictionary->get("Trans"))));
}
else if (name == "GoTo3DView")
{
return PDFActionPtr(new PDFActionGoTo3DView(dictionary->get("TA"), dictionary->get("V")));
}
else if (name == "JavaScript")
{
QByteArray textJavaScript;
const PDFObject& javaScriptObject = storage->getObject(dictionary->get("JS"));
if (javaScriptObject.isString())
{
textJavaScript = javaScriptObject.getString();
}
else if (javaScriptObject.isStream())
{
textJavaScript = storage->getDecodedStream(javaScriptObject.getStream());
}
return PDFActionPtr(new PDFActionJavaScript(PDFEncoding::convertTextString(textJavaScript)));
}
else if (name == "RichMediaExecute")
{
PDFObjectReference richMediaAnnotation = loader.readReferenceFromDictionary(dictionary, "TA");
PDFObjectReference richMediaInstance = loader.readReferenceFromDictionary(dictionary, "TI");
QString command;
PDFObject arguments;
if (const PDFDictionary* commandDictionary = storage->getDictionaryFromObject(dictionary->get("CMD")))
{
command = loader.readTextStringFromDictionary(commandDictionary, "C", QString());
arguments = commandDictionary->get("A");
}
return PDFActionPtr(new PDFActionRichMediaExecute(richMediaAnnotation, richMediaInstance, qMove(command), qMove(arguments)));
}
else if (name == "SubmitForm")
{
PDFFormAction::FieldScope fieldScope = PDFFormAction::FieldScope::All;
PDFFileSpecification url = PDFFileSpecification::parse(storage, dictionary->get("F"));
PDFFormAction::FieldList fieldList = PDFFormAction::parseFieldList(storage, dictionary->get("Fields"), fieldScope);
PDFActionSubmitForm::SubmitFlags flags = static_cast<PDFActionSubmitForm::SubmitFlags>(loader.readIntegerFromDictionary(dictionary, "Flags", 0));
QByteArray charset = loader.readStringFromDictionary(dictionary, "CharSet");
if (fieldScope == PDFFormAction::FieldScope::Include &&
flags.testFlag(PDFActionSubmitForm::IncludeExclude))
{
fieldScope = PDFFormAction::FieldScope::Exclude;
}
return PDFActionPtr(new PDFActionSubmitForm(fieldScope, qMove(fieldList), qMove(url), qMove(charset), flags));
}
else if (name == "ResetForm")
{
PDFFormAction::FieldScope fieldScope = PDFFormAction::FieldScope::All;
PDFFormAction::FieldList fieldList = PDFFormAction::parseFieldList(storage, dictionary->get("Fields"), fieldScope);
PDFActionResetForm::ResetFlags flags = static_cast<PDFActionResetForm::ResetFlags>(loader.readIntegerFromDictionary(dictionary, "Flags", 0));
if (fieldScope == PDFFormAction::FieldScope::Include &&
flags.testFlag(PDFActionResetForm::IncludeExclude))
{
fieldScope = PDFFormAction::FieldScope::Exclude;
}
return PDFActionPtr(new PDFActionResetForm(fieldScope, qMove(fieldList), flags));
}
else if (name == "ImportData")
{
PDFFileSpecification file = PDFFileSpecification::parse(storage, dictionary->get("F"));
return PDFActionPtr(new PDFActionImportDataForm(qMove(file)));
}
return PDFActionPtr();
}
void PDFAction::fillActionList(std::vector<const PDFAction*>& actionList) const
{
actionList.push_back(this);
for (const PDFActionPtr& actionPointer : m_nextActions)
{
if (actionPointer)
{
actionPointer->fillActionList(actionList);
}
}
}
PDFDestination PDFDestination::parse(const PDFObjectStorage* storage, PDFObject object)
{
PDFDestination result;
object = storage->getObject(object);
if (object.isName() || object.isString())
{
QByteArray name = object.getString();
result.m_destinationType = DestinationType::Named;
result.m_name = name;
}
else if (object.isArray())
{
const PDFArray* array = object.getArray();
if (array->getCount() < 2)
{
return result;
}
PDFDocumentDataLoaderDecorator loader(storage);
// First parse page number/page index
PDFObject pageNumberObject = array->getItem(0);
if (pageNumberObject.isReference())
{
result.m_pageReference = pageNumberObject.getReference();
}
else if (pageNumberObject.isInt())
{
result.m_pageIndex = pageNumberObject.getInteger();
}
else
{
return result;
}
QByteArray name = loader.readName(array->getItem(1));
size_t currentIndex = 2;
auto readNumber = [&]()
{
if (currentIndex < array->getCount())
{
return loader.readNumber(array->getItem(currentIndex++), 0.0);
}
return 0.0;
};
if (name == "XYZ")
{
result.m_destinationType = DestinationType::XYZ;
result.m_left = readNumber();
result.m_top = readNumber();
result.m_zoom = readNumber();
}
else if (name == "Fit")
{
result.m_destinationType = DestinationType::Fit;
}
else if (name == "FitH")
{
result.m_destinationType = DestinationType::FitH;
result.m_top = readNumber();
}
else if (name == "FitV")
{
result.m_destinationType = DestinationType::FitV;
result.m_left = readNumber();
}
else if (name == "FitR")
{
result.m_destinationType = DestinationType::FitR;
result.m_left = readNumber();
result.m_bottom = readNumber();
result.m_right = readNumber();
result.m_top = readNumber();
}
else if (name == "FitB")
{
result.m_destinationType = DestinationType::FitB;
}
else if (name == "FitBH")
{
result.m_destinationType = DestinationType::FitBH;
result.m_top = readNumber();
}
else if (name == "FitBV")
{
result.m_destinationType = DestinationType::FitBV;
result.m_left = readNumber();
}
else
{
return result;
}
}
return result;
}
PDFFormAction::FieldList PDFFormAction::parseFieldList(const PDFObjectStorage* storage, PDFObject object, FieldScope& fieldScope)
{
FieldList result;
object = storage->getObject(object);
if (object.isArray())
{
PDFDocumentDataLoaderDecorator loader(storage);
const PDFArray* fieldsArray = object.getArray();
for (size_t i = 0, count = fieldsArray->getCount(); i < count; ++i)
{
PDFObject fieldObject = fieldsArray->getItem(i);
if (fieldObject.isReference())
{
result.fieldReferences.push_back(fieldObject.getReference());
}
else if (fieldObject.isString())
{
result.qualifiedNames.push_back(loader.readTextString(fieldObject, QString()));
}
}
}
if (!result.isEmpty())
{
fieldScope = FieldScope::Include;
}
return result;
}
QString PDFActionURI::getURIString() const
{
return QString::fromUtf8(m_URI);
}
} // namespace pdf