mirror of
https://github.com/JakubMelka/PDF4QT.git
synced 2025-06-05 21:59:17 +02:00
Finishing of outline items
This commit is contained in:
@@ -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 \
|
||||
|
@@ -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;
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
|
@@ -45,7 +45,7 @@ private:
|
||||
};
|
||||
|
||||
/// File specification
|
||||
class PDFFileSpecification
|
||||
class PDFFORQTLIBSHARED_EXPORT PDFFileSpecification
|
||||
{
|
||||
public:
|
||||
explicit PDFFileSpecification() = default;
|
||||
|
94
PdfForQtLib/sources/pdfnametreeloader.h
Normal file
94
PdfForQtLib/sources/pdfnametreeloader.h
Normal 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
|
@@ -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())
|
||||
|
@@ -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())
|
||||
{
|
||||
|
@@ -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);
|
||||
|
@@ -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());
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user