Finishing of outline items

This commit is contained in:
Jakub Melka
2019-11-30 16:26:32 +01:00
parent 39059c645e
commit 5954b7f409
21 changed files with 702 additions and 21 deletions

View File

@@ -81,6 +81,7 @@ HEADERS += \
sources/pdfjbig2decoder.h \
sources/pdfmeshqualitysettings.h \
sources/pdfmultimedia.h \
sources/pdfnametreeloader.h \
sources/pdfobject.h \
sources/pdfoptionalcontent.h \
sources/pdfoutline.h \

View File

@@ -39,6 +39,13 @@ void PDFAction::apply(const std::function<void (const PDFAction*)>& callback)
}
}
std::vector<const PDFAction*> PDFAction::getActionList() const
{
std::vector<const PDFAction*> result;
fillActionList(result);
return result;
}
PDFActionPtr PDFAction::parseImpl(const PDFDocument* document, PDFObject object, std::set<PDFObjectReference>& usedReferences)
{
if (object.isReference())
@@ -273,10 +280,37 @@ PDFActionPtr PDFAction::parseImpl(const PDFDocument* document, PDFObject object,
{
return PDFActionPtr(new PDFActionGoTo3DView(dictionary->get("TA"), dictionary->get("V")));
}
else if (name == "JavaScript")
{
QByteArray textJavaScript;
const PDFObject& javaScriptObject = document->getObject(dictionary->get("JS"));
if (javaScriptObject.isString())
{
textJavaScript = javaScriptObject.getString();
}
else if (javaScriptObject.isStream())
{
textJavaScript = document->getDecodedStream(javaScriptObject.getStream());
}
return PDFActionPtr(new PDFActionJavaScript(PDFEncoding::convertTextString(textJavaScript)));
}
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 PDFDocument* document, PDFObject object)
{
PDFDestination result;

View File

@@ -50,7 +50,8 @@ enum class ActionType
SetOCGState,
Rendition,
Transition,
GoTo3DView
GoTo3DView,
JavaScript
};
enum class DestinationType
@@ -105,7 +106,7 @@ private:
using PDFActionPtr = QSharedPointer<PDFAction>;
/// Base class for action types.
class PDFAction
class PDFFORQTLIBSHARED_EXPORT PDFAction
{
public:
explicit PDFAction() = default;
@@ -127,9 +128,14 @@ public:
/// the 'Next' entry, as described by PDF 1.7 specification.
void apply(const std::function<void(const PDFAction* action)>& callback);
/// Returns list of actions to be executed
std::vector<const PDFAction*> getActionList() const;
private:
static PDFActionPtr parseImpl(const PDFDocument* document, PDFObject object, std::set<PDFObjectReference>& usedReferences);
void fillActionList(std::vector<const PDFAction*>& actionList) const;
std::vector<PDFActionPtr> m_nextActions;
};
@@ -490,6 +496,23 @@ private:
PDFObject m_view;
};
class PDFActionJavaScript : public PDFAction
{
public:
explicit PDFActionJavaScript(const QString& javaScript) :
m_javaScript(javaScript)
{
}
virtual ActionType getType() const override { return ActionType::JavaScript; }
const QString& getJavaScript() const { return m_javaScript; }
private:
QString m_javaScript;
};
} // namespace pdf
#endif // PDFACTION_H

View File

@@ -19,6 +19,7 @@
#include "pdfdocument.h"
#include "pdfexception.h"
#include "pdfnumbertreeloader.h"
#include "pdfnametreeloader.h"
namespace pdf
{
@@ -42,6 +43,28 @@ static constexpr const char* PDF_VIEWER_PREFERENCES_PICK_TRAY_BY_PDF_SIZE = "Pic
static constexpr const char* PDF_VIEWER_PREFERENCES_NUMBER_OF_COPIES = "NumCopies";
static constexpr const char* PDF_VIEWER_PREFERENCES_PRINT_PAGE_RANGE = "PrintPageRange";
size_t PDFCatalog::getPageIndexFromPageReference(PDFObjectReference reference) const
{
auto it = std::find_if(m_pages.cbegin(), m_pages.cend(), [reference](const PDFPage& page) { return page.getPageReference() == reference; });
if (it != m_pages.cend())
{
return std::distance(m_pages.cbegin(), it);
}
return INVALID_PAGE_INDEX;
}
const PDFDestination* PDFCatalog::getDestination(const QByteArray& key) const
{
auto it = m_destinations.find(key);
if (it != m_destinations.cend())
{
return &it->second;
}
return nullptr;
}
PDFCatalog PDFCatalog::parse(const PDFObject& catalog, const PDFDocument* document)
{
if (!catalog.isDictionary())
@@ -109,6 +132,41 @@ PDFCatalog PDFCatalog::parse(const PDFObject& catalog, const PDFDocument* docume
catalogObject.m_pageMode = loader.readEnumByName(catalogDictionary->get("PageMode"), pageModes.begin(), pageModes.end(), PageMode::UseNone);
}
catalogObject.m_version = loader.readNameFromDictionary(catalogDictionary, "Version");
if (const PDFDictionary* namesDictionary = document->getDictionaryFromObject(catalogDictionary->get("Names")))
{
auto parseDestination = [](const PDFDocument* document, PDFObject object)
{
object = document->getObject(object);
if (object.isDictionary())
{
object = object.getDictionary()->get("D");
}
return PDFDestination::parse(document, qMove(object));
};
catalogObject.m_destinations = PDFNameTreeLoader<PDFDestination>::parse(document, namesDictionary->get("Dests"), parseDestination);
catalogObject.m_javaScriptActions = PDFNameTreeLoader<PDFActionPtr>::parse(document, namesDictionary->get("JavaScript"), &PDFAction::parse);
catalogObject.m_embeddedFiles = PDFNameTreeLoader<PDFFileSpecification>::parse(document, namesDictionary->get("EmbeddedFiles"), &PDFFileSpecification::parse);
}
// Examine "Dests" dictionary
if (const PDFDictionary* destsDictionary = document->getDictionaryFromObject(catalogDictionary->get("Dests")))
{
const size_t count = destsDictionary->getCount();
for (size_t i = 0; i < count; ++i)
{
catalogObject.m_destinations[destsDictionary->getKey(i)] = PDFDestination::parse(document, destsDictionary->getValue(i));
}
}
// Examine "URI" dictionary
if (const PDFDictionary* URIDictionary = document->getDictionaryFromObject(catalogDictionary->get("URI")))
{
catalogObject.m_baseURI = loader.readStringFromDictionary(URIDictionary, "Base");
}
return catalogObject;
}

View File

@@ -194,7 +194,7 @@ private:
PDFInteger m_numberOfCopies = 1;
};
class PDFCatalog
class PDFFORQTLIBSHARED_EXPORT PDFCatalog
{
public:
constexpr inline PDFCatalog() = default;
@@ -205,6 +205,8 @@ public:
constexpr inline PDFCatalog& operator=(const PDFCatalog&) = default;
constexpr inline PDFCatalog& operator=(PDFCatalog&&) = default;
static constexpr const size_t INVALID_PAGE_INDEX = std::numeric_limits<size_t>::max();
/// Returns viewer preferences of the application
const PDFViewerPreferences* getViewerPreferences() const { return &m_viewerPreferences; }
@@ -214,6 +216,9 @@ public:
/// Returns the page
const PDFPage* getPage(size_t index) const { return &m_pages.at(index); }
/// Returns page index. If page is not found, then INVALID_PAGE_INDEX is returned.
size_t getPageIndexFromPageReference(PDFObjectReference reference) const;
/// Returns optional content properties
const PDFOptionalContentProperties* getOptionalContentProperties() const { return &m_optionalContentProperties; }
@@ -223,14 +228,25 @@ public:
/// Returns action, which should be performed
const PDFAction* getOpenAction() const { return m_openAction.data(); }
/// Returns version of the PDF specification, to which the document conforms.
const QByteArray& getVersion() const { return m_version; }
PageLayout getPageLayout() const { return m_pageLayout; }
PageMode getPageMode() const { return m_pageMode; }
const QByteArray& getBaseURI() const { return m_baseURI; }
/// Returns destination using the key. If destination with the key is not found,
/// then nullptr is returned.
/// \param key Destination key
/// \returns Pointer to the destination, or nullptr
const PDFDestination* getDestination(const QByteArray& key) const;
/// Parses catalog from catalog dictionary. If object cannot be parsed, or error occurs,
/// then exception is thrown.
static PDFCatalog parse(const PDFObject& catalog, const PDFDocument* document);
private:
QByteArray m_version;
PDFViewerPreferences m_viewerPreferences;
std::vector<PDFPage> m_pages;
std::vector<PDFPageLabel> m_pageLabels;
@@ -239,6 +255,12 @@ private:
PDFActionPtr m_openAction;
PageLayout m_pageLayout = PageLayout::SinglePage;
PageMode m_pageMode = PageMode::UseNone;
QByteArray m_baseURI;
// Maps from Names dictionary
std::map<QByteArray, PDFDestination> m_destinations;
std::map<QByteArray, PDFActionPtr> m_javaScriptActions;
std::map<QByteArray, PDFFileSpecification> m_embeddedFiles;
};
} // namespace pdf

View File

@@ -18,6 +18,8 @@
#ifndef PDFENCODING_H
#define PDFENCODING_H
#include "pdfglobal.h"
#include <QString>
#include <QDateTime>
@@ -33,7 +35,7 @@ using EncodingTable = std::array<QChar, 256>;
/// This class can convert byte stream to the QString in unicode encoding.
/// PDF has several encodings, see PDF Reference 1.7, Appendix D.
class PDFEncoding
class PDFFORQTLIBSHARED_EXPORT PDFEncoding
{
public:
explicit PDFEncoding() = delete;

View File

@@ -45,7 +45,7 @@ private:
};
/// File specification
class PDFFileSpecification
class PDFFORQTLIBSHARED_EXPORT PDFFileSpecification
{
public:
explicit PDFFileSpecification() = default;

View File

@@ -0,0 +1,94 @@
// Copyright (C) 2019 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/>.
#ifndef PDFNAMETREELOADER_H
#define PDFNAMETREELOADER_H
#include "pdfdocument.h"
#include <map>
#include <functional>
namespace pdf
{
/// This class can load a number tree into the array
template<typename Type>
class PDFNameTreeLoader
{
public:
explicit PDFNameTreeLoader() = delete;
using MappedObjects = std::map<QByteArray, Type>;
using LoadMethod = std::function<Type(const PDFDocument*, const PDFObject&)>;
/// Parses the name tree and loads its items into the map. Some errors are ignored,
/// e.g. when kid is null. Objects are retrieved by \p loadMethod.
/// \param document Document
/// \param root Root of the name tree
/// \param loadMethod Parsing method, which retrieves parsed object
static MappedObjects parse(const PDFDocument* document, const PDFObject& root, const LoadMethod& loadMethod)
{
MappedObjects result;
parseImpl(result, document, root, loadMethod);
return result;
}
private:
static void parseImpl(MappedObjects& objects, const PDFDocument* document, const PDFObject& root, const LoadMethod& loadMethod)
{
if (const PDFDictionary* dictionary = document->getDictionaryFromObject(root))
{
// Jakub Melka: First, load the objects into the map
const PDFObject& namedItems = document->getObject(dictionary->get("Names"));
if (namedItems.isArray())
{
const PDFArray* namedItemsArray = namedItems.getArray();
const size_t count = namedItemsArray->getCount() / 2;
for (size_t i = 0; i < count; ++i)
{
const size_t numberIndex = 2 * i;
const size_t valueIndex = 2 * i + 1;
const PDFObject& name = document->getObject(namedItemsArray->getItem(numberIndex));
if (!name.isString())
{
continue;
}
objects[name.getString()] = loadMethod(document, namedItemsArray->getItem(valueIndex));
}
}
// Then, follow the kids
const PDFObject& kids = document->getObject(dictionary->get("Kids"));
if (kids.isArray())
{
const PDFArray* kidsArray = kids.getArray();
const size_t count = kidsArray->getCount();
for (size_t i = 0; i < count; ++i)
{
parseImpl(objects, document, kidsArray->getItem(i), loadMethod);
}
}
}
}
};
} // namespace pdf
#endif // PDFNAMETREELOADER_H

View File

@@ -53,11 +53,8 @@ public:
private:
static void parseImpl(Objects& objects, const PDFDocument* document, const PDFObject& root)
{
const PDFObject& dereferencedRoot = document->getObject(root);
if (dereferencedRoot.isDictionary())
if (const PDFDictionary* dictionary = document->getDictionaryFromObject(root))
{
const PDFDictionary* dictionary = dereferencedRoot.getDictionary();
// First, load the objects into the array
const PDFObject& numberedItems = document->getObject(dictionary->get("Nums"));
if (numberedItems.isArray())

View File

@@ -343,13 +343,13 @@ OCState PDFOptionalContentActivity::getState(PDFObjectReference ocg) const
return OCState::Unknown;
}
void PDFOptionalContentActivity::setState(PDFObjectReference ocg, OCState state)
void PDFOptionalContentActivity::setState(PDFObjectReference ocg, OCState state, bool preserveRadioButtons)
{
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.
if (state == OCState::ON)
if (state == OCState::ON && preserveRadioButtons)
{
for (const std::vector<PDFObjectReference>& radioButtonGroup : m_properties->getDefaultConfiguration().getRadioButtonGroups())
{

View File

@@ -164,18 +164,20 @@ public:
/// Gets the optional content groups state. If optional content group doesn't exist,
/// then it returns Unknown state.
/// \param ocg Optional conteng group
/// \param ocg Optional content group
OCState getState(PDFObjectReference ocg) const;
/// Sets the state of optional content group. If optional content group doesn't exist,
/// then nothing happens. If optional content group is contained in radio button group, then
/// all other optional content groups in the group are switched off, if we are
/// switching this one to ON state. If we are switching it off, then nothing happens (as all
/// optional content groups in radio button group can be switched off).
/// optional content groups in radio button group can be switched off). This behaviour can be
/// controlled via parameter \p preserveRadioButtons.
/// \param ocg Optional content group
/// \param state New state of the optional content group
/// \param preserveRadioButtons Switch off other radio buttons in group?
/// \note If something changed, then signal \p optionalContentGroupStateChanged is emitted.
void setState(PDFObjectReference ocg, OCState state);
void setState(PDFObjectReference ocg, OCState state, bool preserveRadioButtons = true);
/// Applies configuration to the current state of optional content groups
void applyConfiguration(const PDFOptionalContentConfiguration& configuration);

View File

@@ -150,6 +150,7 @@ void PDFPage::parseImpl(std::vector<PDFPage>& pages,
const PDFDocument* document)
{
// Are we in internal node, or leaf (page object)?
PDFObjectReference objectReference = root.isReference() ? root.getReference() : PDFObjectReference();
const PDFObject& dereferenced = document->getObject(root);
if (dereferenced.isDictionary())
@@ -198,6 +199,7 @@ void PDFPage::parseImpl(std::vector<PDFPage>& pages,
{
PDFPage page;
page.m_pageReference = objectReference;
page.m_mediaBox = currentInheritableAttributes.getMediaBox();
page.m_cropBox = currentInheritableAttributes.getCropBox();
page.m_resources = document->getObject(currentInheritableAttributes.getResources());

View File

@@ -94,6 +94,8 @@ public:
inline QRectF getTrimBoxMM() const { return getRectMM(m_trimBox); }
inline QRectF getArtBoxMM() const { return getRectMM(m_artBox); }
inline PDFObjectReference getPageReference() const { return m_pageReference; }
QRectF getRotatedMediaBox() const;
QRectF getRotatedCropBox() const;
@@ -120,6 +122,7 @@ private:
PageRotation m_pageRotation = PageRotation::None;
PDFObject m_resources;
PDFObject m_contents;
PDFObjectReference m_pageReference;
};
} // namespace pdf