2020-01-18 11:38:54 +01:00
|
|
|
// Copyright (C) 2019-2020 Jakub Melka
|
2019-06-23 18:35:32 +02:00
|
|
|
//
|
|
|
|
// 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 "pdfoptionalcontent.h"
|
|
|
|
#include "pdfdocument.h"
|
|
|
|
#include "pdfexception.h"
|
|
|
|
|
|
|
|
namespace pdf
|
|
|
|
{
|
|
|
|
|
|
|
|
PDFOptionalContentProperties PDFOptionalContentProperties::create(const PDFDocument* document, const PDFObject& object)
|
|
|
|
{
|
|
|
|
PDFOptionalContentProperties properties;
|
|
|
|
|
|
|
|
const PDFObject& dereferencedObject = document->getObject(object);
|
|
|
|
if (dereferencedObject.isDictionary())
|
|
|
|
{
|
|
|
|
const PDFDictionary* dictionary = dereferencedObject.getDictionary();
|
|
|
|
PDFDocumentDataLoaderDecorator loader(document);
|
|
|
|
properties.m_allOptionalContentGroups = loader.readReferenceArrayFromDictionary(dictionary, "OCGs");
|
|
|
|
|
2019-07-01 19:53:38 +02:00
|
|
|
for (const PDFObjectReference& reference : properties.m_allOptionalContentGroups)
|
|
|
|
{
|
|
|
|
const PDFObject& object = document->getStorage().getObject(reference);
|
|
|
|
if (!object.isNull())
|
|
|
|
{
|
|
|
|
properties.m_optionalContentGroups[reference] = PDFOptionalContentGroup::create(document, object);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-23 18:35:32 +02:00
|
|
|
if (dictionary->hasKey("D"))
|
|
|
|
{
|
|
|
|
properties.m_defaultConfiguration = PDFOptionalContentConfiguration::create(document, dictionary->get("D"));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dictionary->hasKey("Configs"))
|
|
|
|
{
|
|
|
|
const PDFObject& configsObject = document->getObject(dictionary->get("Configs"));
|
|
|
|
if (configsObject.isArray())
|
|
|
|
{
|
|
|
|
const PDFArray* configsArray = configsObject.getArray();
|
|
|
|
properties.m_configurations.reserve(configsArray->getCount());
|
|
|
|
|
|
|
|
for (size_t i = 0, count = configsArray->getCount(); i < count; ++i)
|
|
|
|
{
|
|
|
|
properties.m_configurations.emplace_back(PDFOptionalContentConfiguration::create(document, configsArray->getItem(i)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (!configsObject.isNull())
|
|
|
|
{
|
2019-09-27 18:41:56 +02:00
|
|
|
throw PDFException(PDFTranslationContext::tr("Invalid optional content properties."));
|
2019-06-23 18:35:32 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (!dereferencedObject.isNull())
|
|
|
|
{
|
2019-09-27 18:41:56 +02:00
|
|
|
throw PDFException(PDFTranslationContext::tr("Invalid optional content properties."));
|
2019-06-23 18:35:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return properties;
|
|
|
|
}
|
|
|
|
|
|
|
|
PDFOptionalContentConfiguration PDFOptionalContentConfiguration::create(const PDFDocument* document, const PDFObject& object)
|
|
|
|
{
|
|
|
|
PDFOptionalContentConfiguration configuration;
|
|
|
|
|
|
|
|
const PDFObject& dereferencedObject = document->getObject(object);
|
|
|
|
if (dereferencedObject.isDictionary())
|
|
|
|
{
|
|
|
|
const PDFDictionary* dictionary = dereferencedObject.getDictionary();
|
|
|
|
PDFDocumentDataLoaderDecorator loader(document);
|
|
|
|
configuration.m_name = loader.readTextStringFromDictionary(dictionary, "Name", QString());
|
|
|
|
configuration.m_creator = loader.readTextStringFromDictionary(dictionary, "Creator", QString());
|
|
|
|
|
|
|
|
constexpr const std::array<std::pair<const char*, BaseState>, 3> baseStateEnumValues = {
|
|
|
|
std::pair<const char*, BaseState>{ "ON", BaseState::ON },
|
|
|
|
std::pair<const char*, BaseState>{ "OFF", BaseState::OFF },
|
|
|
|
std::pair<const char*, BaseState>{ "Unchanged", BaseState::Unchanged }
|
|
|
|
};
|
|
|
|
configuration.m_baseState = loader.readEnumByName(dictionary->get("BaseState"), baseStateEnumValues.cbegin(), baseStateEnumValues.cend(), BaseState::ON);
|
|
|
|
configuration.m_OnArray = loader.readReferenceArrayFromDictionary(dictionary, "ON");
|
|
|
|
configuration.m_OffArray = loader.readReferenceArrayFromDictionary(dictionary, "OFF");
|
|
|
|
|
|
|
|
if (dictionary->hasKey("Intent"))
|
|
|
|
{
|
|
|
|
const PDFObject& nameOrNames = document->getObject(dictionary->get("Intent"));
|
|
|
|
|
|
|
|
if (nameOrNames.isName())
|
|
|
|
{
|
|
|
|
configuration.m_intents = { loader.readName(nameOrNames) };
|
|
|
|
}
|
|
|
|
else if (nameOrNames.isArray())
|
|
|
|
{
|
|
|
|
configuration.m_intents = loader.readNameArray(nameOrNames);
|
|
|
|
}
|
|
|
|
else if (!nameOrNames.isNull())
|
|
|
|
{
|
2019-09-27 18:41:56 +02:00
|
|
|
throw PDFException(PDFTranslationContext::tr("Invalid optional content configuration."));
|
2019-06-23 18:35:32 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dictionary->hasKey("AS"))
|
|
|
|
{
|
|
|
|
const PDFObject& asArrayObject = document->getObject(dictionary->get("AS"));
|
|
|
|
if (asArrayObject.isArray())
|
|
|
|
{
|
|
|
|
const PDFArray* asArray = asArrayObject.getArray();
|
|
|
|
configuration.m_usageApplications.reserve(asArray->getCount());
|
|
|
|
|
|
|
|
for (size_t i = 0, count = asArray->getCount(); i < count; ++i)
|
|
|
|
{
|
|
|
|
configuration.m_usageApplications.emplace_back(createUsageApplication(document, asArray->getItem(i)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (!asArrayObject.isNull())
|
|
|
|
{
|
2019-09-27 18:41:56 +02:00
|
|
|
throw PDFException(PDFTranslationContext::tr("Invalid optional content configuration."));
|
2019-06-23 18:35:32 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
configuration.m_order = document->getObject(dictionary->get("Order"));
|
|
|
|
if (!configuration.m_order.isArray() && !configuration.m_order.isNull())
|
|
|
|
{
|
2019-09-27 18:41:56 +02:00
|
|
|
throw PDFException(PDFTranslationContext::tr("Invalid optional content configuration."));
|
2019-06-23 18:35:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
constexpr const std::array<std::pair<const char*, ListMode>, 3> listModeEnumValues = {
|
|
|
|
std::pair<const char*, ListMode>{ "AllPages", ListMode::AllPages },
|
|
|
|
std::pair<const char*, ListMode>{ "VisiblePages", ListMode::VisiblePages }
|
|
|
|
};
|
|
|
|
configuration.m_listMode = loader.readEnumByName(dictionary->get("ListMode"), listModeEnumValues.cbegin(), listModeEnumValues.cend(), ListMode::AllPages);
|
|
|
|
|
|
|
|
if (dictionary->hasKey("RBGroups"))
|
|
|
|
{
|
|
|
|
const PDFObject& rbGroupsObject = document->getObject(dictionary->get("RBGroups"));
|
|
|
|
if (rbGroupsObject.isArray())
|
|
|
|
{
|
|
|
|
const PDFArray* rbGroupsArray = rbGroupsObject.getArray();
|
|
|
|
configuration.m_radioButtonGroups.reserve(rbGroupsArray->getCount());
|
|
|
|
|
|
|
|
for (size_t i = 0, count = rbGroupsArray->getCount(); i < count; ++i)
|
|
|
|
{
|
|
|
|
configuration.m_radioButtonGroups.emplace_back(loader.readReferenceArray(rbGroupsArray->getItem(i)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (!rbGroupsObject.isNull())
|
|
|
|
{
|
2019-09-27 18:41:56 +02:00
|
|
|
throw PDFException(PDFTranslationContext::tr("Invalid optional content configuration."));
|
2019-06-23 18:35:32 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
configuration.m_locked = loader.readReferenceArrayFromDictionary(dictionary, "Locked");
|
|
|
|
}
|
|
|
|
|
|
|
|
return configuration;
|
|
|
|
}
|
|
|
|
|
2019-07-02 16:20:12 +02:00
|
|
|
OCUsage PDFOptionalContentConfiguration::getUsageFromName(const QByteArray& name)
|
|
|
|
{
|
|
|
|
if (name == "View")
|
|
|
|
{
|
|
|
|
return OCUsage::View;
|
|
|
|
}
|
|
|
|
else if (name == "Print")
|
|
|
|
{
|
|
|
|
return OCUsage::Print;
|
|
|
|
}
|
|
|
|
else if (name == "Export")
|
|
|
|
{
|
|
|
|
return OCUsage::Export;
|
|
|
|
}
|
|
|
|
|
|
|
|
return OCUsage::Invalid;
|
|
|
|
}
|
|
|
|
|
2019-06-23 18:35:32 +02:00
|
|
|
PDFOptionalContentConfiguration::UsageApplication PDFOptionalContentConfiguration::createUsageApplication(const PDFDocument* document, const PDFObject& object)
|
|
|
|
{
|
|
|
|
UsageApplication result;
|
|
|
|
|
|
|
|
const PDFObject& dereferencedObject = document->getObject(object);
|
|
|
|
if (dereferencedObject.isDictionary())
|
|
|
|
{
|
|
|
|
PDFDocumentDataLoaderDecorator loader(document);
|
|
|
|
const PDFDictionary* dictionary = dereferencedObject.getDictionary();
|
|
|
|
result.event = loader.readNameFromDictionary(dictionary, "Event");
|
|
|
|
result.optionalContengGroups = loader.readReferenceArrayFromDictionary(dictionary, "OCGs");
|
|
|
|
result.categories = loader.readNameArrayFromDictionary(dictionary, "Category");
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2019-07-01 19:53:38 +02:00
|
|
|
PDFOptionalContentGroup::PDFOptionalContentGroup() :
|
|
|
|
m_usageZoomMin(0),
|
|
|
|
m_usageZoomMax(std::numeric_limits<PDFReal>::infinity()),
|
|
|
|
m_usagePrintState(OCState::Unknown),
|
|
|
|
m_usageViewState(OCState::Unknown),
|
|
|
|
m_usageExportState(OCState::Unknown)
|
|
|
|
{
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
PDFOptionalContentGroup PDFOptionalContentGroup::create(const PDFDocument* document, const PDFObject& object)
|
|
|
|
{
|
|
|
|
PDFOptionalContentGroup result;
|
|
|
|
|
|
|
|
const PDFObject& dereferencedObject = document->getObject(object);
|
|
|
|
if (!dereferencedObject.isDictionary())
|
|
|
|
{
|
2019-09-27 18:41:56 +02:00
|
|
|
throw PDFException(PDFTranslationContext::tr("Invalid optional content group."));
|
2019-07-01 19:53:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
PDFDocumentDataLoaderDecorator loader(document);
|
|
|
|
|
|
|
|
const PDFDictionary* dictionary = dereferencedObject.getDictionary();
|
|
|
|
result.m_name = loader.readTextStringFromDictionary(dictionary, "Name", QString());
|
|
|
|
|
|
|
|
if (dictionary->hasKey("Intent"))
|
|
|
|
{
|
|
|
|
const PDFObject& nameOrNames = document->getObject(dictionary->get("Intent"));
|
|
|
|
|
|
|
|
if (nameOrNames.isName())
|
|
|
|
{
|
|
|
|
result.m_intents = { loader.readName(nameOrNames) };
|
|
|
|
}
|
|
|
|
else if (nameOrNames.isArray())
|
|
|
|
{
|
|
|
|
result.m_intents = loader.readNameArray(nameOrNames);
|
|
|
|
}
|
|
|
|
else if (!nameOrNames.isNull())
|
|
|
|
{
|
2019-09-27 18:41:56 +02:00
|
|
|
throw PDFException(PDFTranslationContext::tr("Invalid optional content group."));
|
2019-07-01 19:53:38 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const PDFObject& usageDictionaryObject = dictionary->get("Usage");
|
|
|
|
if (usageDictionaryObject.isDictionary())
|
|
|
|
{
|
|
|
|
const PDFDictionary* usageDictionary = usageDictionaryObject.getDictionary();
|
|
|
|
|
|
|
|
result.m_creatorInfo = document->getObject(usageDictionary->get("CreatorInfo"));
|
|
|
|
result.m_language = document->getObject(usageDictionary->get("Language"));
|
|
|
|
|
|
|
|
const PDFObject& zoomDictionary = document->getObject(usageDictionary->get("Zoom"));
|
|
|
|
if (zoomDictionary.isDictionary())
|
|
|
|
{
|
|
|
|
result.m_usageZoomMin = loader.readNumberFromDictionary(usageDictionary, "min", result.m_usageZoomMin);
|
|
|
|
result.m_usageZoomMax = loader.readNumberFromDictionary(usageDictionary, "max", result.m_usageZoomMax);
|
|
|
|
}
|
|
|
|
|
|
|
|
auto readState = [document, usageDictionary, &loader](const char* dictionaryKey, const char* key) -> OCState
|
|
|
|
{
|
|
|
|
const PDFObject& stateDictionaryObject = document->getObject(usageDictionary->get(dictionaryKey));
|
|
|
|
if (stateDictionaryObject.isDictionary())
|
|
|
|
{
|
|
|
|
const PDFDictionary* stateDictionary = stateDictionaryObject.getDictionary();
|
|
|
|
QByteArray stateName = loader.readNameFromDictionary(stateDictionary, key);
|
|
|
|
|
|
|
|
if (stateName == "ON")
|
|
|
|
{
|
|
|
|
return OCState::ON;
|
|
|
|
}
|
|
|
|
if (stateName == "OFF")
|
|
|
|
{
|
|
|
|
return OCState::OFF;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return OCState::Unknown;
|
|
|
|
};
|
|
|
|
|
|
|
|
result.m_usageViewState = readState("View", "ViewState");
|
|
|
|
result.m_usagePrintState = readState("Print", "PrintState");
|
|
|
|
result.m_usageExportState = readState("Export", "ExportState");
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2019-07-02 16:20:12 +02:00
|
|
|
OCState PDFOptionalContentGroup::getUsageState(OCUsage usage) const
|
|
|
|
{
|
|
|
|
switch (usage)
|
|
|
|
{
|
|
|
|
case OCUsage::View:
|
|
|
|
return getUsageViewState();
|
|
|
|
|
|
|
|
case OCUsage::Print:
|
|
|
|
return getUsagePrintState();
|
|
|
|
|
|
|
|
case OCUsage::Export:
|
|
|
|
return getUsageExportState();
|
|
|
|
|
|
|
|
case OCUsage::Invalid:
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return OCState::Unknown;
|
|
|
|
}
|
|
|
|
|
|
|
|
PDFOptionalContentActivity::PDFOptionalContentActivity(const PDFDocument* document, OCUsage usage, QObject* parent) :
|
|
|
|
QObject(parent),
|
|
|
|
m_document(document),
|
|
|
|
m_properties(document->getCatalog()->getOptionalContentProperties()),
|
|
|
|
m_usage(usage)
|
|
|
|
{
|
|
|
|
if (m_properties->isValid())
|
|
|
|
{
|
|
|
|
for (const PDFObjectReference& reference : m_properties->getAllOptionalContentGroups())
|
|
|
|
{
|
|
|
|
m_states[reference] = OCState::Unknown;
|
|
|
|
}
|
|
|
|
|
|
|
|
applyConfiguration(m_properties->getDefaultConfiguration());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
OCState PDFOptionalContentActivity::getState(PDFObjectReference ocg) const
|
|
|
|
{
|
|
|
|
auto it = m_states.find(ocg);
|
|
|
|
if (it != m_states.cend())
|
|
|
|
{
|
|
|
|
return it->second;
|
|
|
|
}
|
|
|
|
|
|
|
|
return OCState::Unknown;
|
|
|
|
}
|
|
|
|
|
2019-11-30 16:26:32 +01:00
|
|
|
void PDFOptionalContentActivity::setState(PDFObjectReference ocg, OCState state, bool preserveRadioButtons)
|
2019-07-02 16:20:12 +02:00
|
|
|
{
|
|
|
|
auto it = m_states.find(ocg);
|
|
|
|
if (it != m_states.cend() && it->second != state)
|
|
|
|
{
|
|
|
|
// We are changing the state. If new state is ON, then we must check radio button groups.
|
2019-11-30 16:26:32 +01:00
|
|
|
if (state == OCState::ON && preserveRadioButtons)
|
2019-07-02 16:20:12 +02:00
|
|
|
{
|
|
|
|
for (const std::vector<PDFObjectReference>& radioButtonGroup : m_properties->getDefaultConfiguration().getRadioButtonGroups())
|
|
|
|
{
|
|
|
|
if (std::find(radioButtonGroup.cbegin(), radioButtonGroup.cend(), ocg) != radioButtonGroup.cend())
|
|
|
|
{
|
|
|
|
// We must set all states of this radio button group to OFF
|
|
|
|
for (const PDFObjectReference& ocgRadioButtonGroup : radioButtonGroup)
|
|
|
|
{
|
|
|
|
setState(ocgRadioButtonGroup, OCState::OFF);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
it->second = state;
|
|
|
|
emit optionalContentGroupStateChanged(ocg, state);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void PDFOptionalContentActivity::applyConfiguration(const PDFOptionalContentConfiguration& configuration)
|
|
|
|
{
|
|
|
|
// Step 1: Apply base state to all states
|
|
|
|
if (configuration.getBaseState() != PDFOptionalContentConfiguration::BaseState::Unchanged)
|
|
|
|
{
|
|
|
|
const OCState newState = (configuration.getBaseState() == PDFOptionalContentConfiguration::BaseState::ON) ? OCState::ON : OCState::OFF;
|
|
|
|
for (auto& item : m_states)
|
|
|
|
{
|
|
|
|
item.second = newState;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
auto setOCGState = [this](PDFObjectReference ocg, OCState state)
|
|
|
|
{
|
|
|
|
auto it = m_states.find(ocg);
|
|
|
|
if (it != m_states.cend())
|
|
|
|
{
|
|
|
|
it->second = state;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Step 2: Process 'ON' entry
|
|
|
|
for (PDFObjectReference ocg : configuration.getOnArray())
|
|
|
|
{
|
|
|
|
setOCGState(ocg, OCState::ON);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Step 3: Process 'OFF' entry
|
|
|
|
for (PDFObjectReference ocg : configuration.getOffArray())
|
|
|
|
{
|
|
|
|
setOCGState(ocg, OCState::OFF);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Step 4: Apply usage
|
|
|
|
for (const PDFOptionalContentConfiguration::UsageApplication& usageApplication : configuration.getUsageApplications())
|
|
|
|
{
|
|
|
|
// We will use usage from the events name. We ignore category, as it should duplicate the events name.
|
|
|
|
const OCUsage usage = PDFOptionalContentConfiguration::getUsageFromName(usageApplication.event);
|
|
|
|
|
|
|
|
if (usage == m_usage)
|
|
|
|
{
|
|
|
|
for (PDFObjectReference ocg : usageApplication.optionalContengGroups)
|
|
|
|
{
|
|
|
|
if (!m_properties->hasOptionalContentGroup(ocg))
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const PDFOptionalContentGroup& optionalContentGroup = m_properties->getOptionalContentGroup(ocg);
|
|
|
|
const OCState newState = optionalContentGroup.getUsageState(usage);
|
|
|
|
setOCGState(ocg, newState);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-04 17:52:38 +02:00
|
|
|
PDFOptionalContentMembershipObject PDFOptionalContentMembershipObject::create(const PDFDocument* document, const PDFObject& object)
|
|
|
|
{
|
|
|
|
PDFOptionalContentMembershipObject result;
|
|
|
|
const PDFObject& dereferencedObject = document->getObject(object);
|
|
|
|
if (dereferencedObject.isDictionary())
|
|
|
|
{
|
|
|
|
const PDFDictionary* dictionary = dereferencedObject.getDictionary();
|
|
|
|
if (dictionary->hasKey("VE"))
|
|
|
|
{
|
|
|
|
// Parse visibility expression
|
|
|
|
|
|
|
|
std::set<PDFObjectReference> usedReferences;
|
|
|
|
std::function<std::unique_ptr<Node>(const PDFObject&)> parseNode = [document, &parseNode, &usedReferences](const PDFObject& nodeObject) -> std::unique_ptr<Node>
|
|
|
|
{
|
|
|
|
const PDFObject& dereferencedNodeObject = document->getObject(nodeObject);
|
|
|
|
if (dereferencedNodeObject.isArray())
|
|
|
|
{
|
|
|
|
// It is probably array. We must check, if we doesn't have cyclic reference.
|
|
|
|
if (nodeObject.isReference())
|
|
|
|
{
|
|
|
|
if (usedReferences.count(nodeObject.getReference()))
|
|
|
|
{
|
2019-09-27 18:41:56 +02:00
|
|
|
throw PDFException(PDFTranslationContext::tr("Cyclic reference error in optional content visibility expression."));
|
2019-07-04 17:52:38 +02:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
usedReferences.insert(nodeObject.getReference());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check the array
|
|
|
|
const PDFArray* array = dereferencedNodeObject.getArray();
|
|
|
|
if (array->getCount() < 2)
|
|
|
|
{
|
2019-09-27 18:41:56 +02:00
|
|
|
throw PDFException(PDFTranslationContext::tr("Invalid optional content visibility expression."));
|
2019-07-04 17:52:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Read the operator
|
|
|
|
const PDFObject& dereferencedNameObject = document->getObject(array->getItem(0));
|
|
|
|
QByteArray operatorName;
|
|
|
|
if (dereferencedNameObject.isName())
|
|
|
|
{
|
|
|
|
operatorName = dereferencedNameObject.getString();
|
|
|
|
}
|
|
|
|
|
|
|
|
Operator operatorType = Operator::And;
|
|
|
|
if (operatorName == "And")
|
|
|
|
{
|
|
|
|
operatorType = Operator::And;
|
|
|
|
} else if (operatorName == "Or")
|
|
|
|
{
|
|
|
|
operatorType = Operator::Or;
|
|
|
|
}
|
|
|
|
else if (operatorName == "Not")
|
|
|
|
{
|
|
|
|
operatorType = Operator::Not;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2019-09-27 18:41:56 +02:00
|
|
|
throw PDFException(PDFTranslationContext::tr("Invalid optional content visibility expression."));
|
2019-07-04 17:52:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Read the operands
|
|
|
|
std::vector<std::unique_ptr<Node>> operands;
|
|
|
|
operands.reserve(array->getCount());
|
|
|
|
for (size_t i = 1, count = array->getCount(); i < count; ++i)
|
|
|
|
{
|
|
|
|
operands.push_back(parseNode(array->getItem(i)));
|
|
|
|
}
|
|
|
|
|
|
|
|
return std::unique_ptr<Node>(new OperatorNode(operatorType, qMove(operands)));
|
|
|
|
}
|
|
|
|
else if (nodeObject.isReference())
|
|
|
|
{
|
|
|
|
// Treat is as an optional content group
|
|
|
|
return std::unique_ptr<Node>(new OptionalContentGroupNode(nodeObject.getReference()));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Something strange occured - either we should have an array, or we should have a reference to the OCG
|
2019-09-27 18:41:56 +02:00
|
|
|
throw PDFException(PDFTranslationContext::tr("Invalid optional content visibility expression."));
|
2019-07-04 17:52:38 +02:00
|
|
|
return std::unique_ptr<Node>(nullptr);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
result.m_expression = parseNode(dictionary->get("VE"));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// First, scan all optional content groups
|
|
|
|
PDFDocumentDataLoaderDecorator loader(document);
|
2020-01-18 17:53:06 +01:00
|
|
|
std::vector<PDFObjectReference> ocgs;
|
|
|
|
|
|
|
|
PDFObject singleOCG = dictionary->get("OCGs");
|
|
|
|
if (singleOCG.isReference())
|
|
|
|
{
|
|
|
|
ocgs = { singleOCG.getReference() };
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ocgs = loader.readReferenceArrayFromDictionary(dictionary, "OCGs");
|
|
|
|
}
|
2019-07-04 17:52:38 +02:00
|
|
|
|
|
|
|
if (!ocgs.empty())
|
|
|
|
{
|
|
|
|
auto createOperatorOnOcgs = [&ocgs](Operator operator_)
|
|
|
|
{
|
|
|
|
std::vector<std::unique_ptr<Node>> operands;
|
|
|
|
operands.reserve(ocgs.size());
|
|
|
|
for (PDFObjectReference reference : ocgs)
|
|
|
|
{
|
|
|
|
operands.push_back(std::unique_ptr<Node>(new OptionalContentGroupNode(reference)));
|
|
|
|
}
|
|
|
|
return std::unique_ptr<Node>(new OperatorNode(operator_, qMove(operands)));
|
|
|
|
};
|
|
|
|
|
|
|
|
// Parse 'P' mode
|
|
|
|
QByteArray type = loader.readNameFromDictionary(dictionary, "P");
|
|
|
|
if (type == "AllOn")
|
|
|
|
{
|
|
|
|
// All of entries in OCGS are turned on
|
|
|
|
result.m_expression = createOperatorOnOcgs(Operator::And);
|
|
|
|
}
|
|
|
|
else if (type == "AnyOn")
|
|
|
|
{
|
|
|
|
// Any of entries in OCGS is turned on
|
|
|
|
result.m_expression = createOperatorOnOcgs(Operator::Or);
|
|
|
|
}
|
|
|
|
else if (type == "AnyOff")
|
|
|
|
{
|
|
|
|
// Any of entries are turned off. It is negation of 'AllOn'.
|
|
|
|
std::vector<std::unique_ptr<Node>> subexpression;
|
|
|
|
subexpression.push_back(createOperatorOnOcgs(Operator::And));
|
|
|
|
result.m_expression = std::unique_ptr<Node>(new OperatorNode(Operator::Not, qMove(subexpression)));
|
|
|
|
}
|
|
|
|
else if (type == "AllOff")
|
|
|
|
{
|
|
|
|
// All of entries are turned off. It is negation of 'AnyOn'
|
|
|
|
std::vector<std::unique_ptr<Node>> subexpression;
|
|
|
|
subexpression.push_back(createOperatorOnOcgs(Operator::Or));
|
|
|
|
result.m_expression = std::unique_ptr<Node>(new OperatorNode(Operator::Not, qMove(subexpression)));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Default value is AnyOn according to the PDF reference
|
|
|
|
result.m_expression = createOperatorOnOcgs(Operator::Or);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
OCState PDFOptionalContentMembershipObject::evaluate(const PDFOptionalContentActivity* activity) const
|
|
|
|
{
|
|
|
|
return m_expression ? m_expression->evaluate(activity) : OCState::Unknown;
|
|
|
|
}
|
|
|
|
|
|
|
|
OCState PDFOptionalContentMembershipObject::OptionalContentGroupNode::evaluate(const PDFOptionalContentActivity* activity) const
|
|
|
|
{
|
|
|
|
return activity->getState(m_optionalContentGroup);
|
|
|
|
}
|
|
|
|
|
|
|
|
OCState PDFOptionalContentMembershipObject::OperatorNode::evaluate(const PDFOptionalContentActivity* activity) const
|
|
|
|
{
|
|
|
|
OCState result = OCState::Unknown;
|
|
|
|
|
|
|
|
switch (m_operator)
|
|
|
|
{
|
|
|
|
case Operator::And:
|
|
|
|
{
|
|
|
|
for (const auto& child : m_children)
|
|
|
|
{
|
|
|
|
result = result & child->evaluate(activity);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case Operator::Or:
|
|
|
|
{
|
|
|
|
for (const auto& child : m_children)
|
|
|
|
{
|
|
|
|
result = result | child->evaluate(activity);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case Operator::Not:
|
|
|
|
{
|
|
|
|
// We must handle case, when we have zero or more expressions (Not operator requires exactly one).
|
|
|
|
// If this case occurs, then we return Unknown state.
|
|
|
|
if (m_children.size() == 1)
|
|
|
|
{
|
|
|
|
OCState childState = m_children.front()->evaluate(activity);
|
|
|
|
|
|
|
|
switch (childState)
|
|
|
|
{
|
|
|
|
case OCState::ON:
|
|
|
|
result = OCState::OFF;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case OCState::OFF:
|
|
|
|
result = OCState::ON;
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
Q_ASSERT(result == OCState::Unknown);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
default:
|
|
|
|
{
|
|
|
|
Q_ASSERT(false);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2019-06-23 18:35:32 +02:00
|
|
|
} // namespace pdf
|