mirror of
https://github.com/JakubMelka/PDF4QT.git
synced 2025-06-05 21:59:17 +02:00
Editor Plugin: Save modified document
This commit is contained in:
@ -23,6 +23,8 @@
|
|||||||
#include "pdfdocumentbuilder.h"
|
#include "pdfdocumentbuilder.h"
|
||||||
#include "pdfcertificatemanagerdialog.h"
|
#include "pdfcertificatemanagerdialog.h"
|
||||||
#include "pdfdocumentwriter.h"
|
#include "pdfdocumentwriter.h"
|
||||||
|
#include "pdfpagecontenteditorprocessor.h"
|
||||||
|
#include "pdfstreamfilters.h"
|
||||||
|
|
||||||
#include <QAction>
|
#include <QAction>
|
||||||
#include <QToolButton>
|
#include <QToolButton>
|
||||||
@ -180,6 +182,138 @@ QString EditorPlugin::getPluginMenuName() const
|
|||||||
return tr("Edi&tor");
|
return tr("Edi&tor");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool EditorPlugin::save()
|
||||||
|
{
|
||||||
|
if (QMessageBox::question(m_dataExchangeInterface->getMainWindow(), tr("Confirm Changes"), tr("The changes to the page content will be written to the document. Do you want to continue?"), QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes)
|
||||||
|
{
|
||||||
|
pdf::PDFDocumentModifier modifier(m_document);
|
||||||
|
|
||||||
|
std::set<pdf::PDFInteger> pageIndices = m_scene.getPageIndices();
|
||||||
|
auto elementsByPage = m_scene.getElementsByPage();
|
||||||
|
for (pdf::PDFInteger pageIndex : pageIndices)
|
||||||
|
{
|
||||||
|
if (m_editedPageContent.count(pageIndex) == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pdf::PDFPage* page = m_document->getCatalog()->getPage(pageIndex);
|
||||||
|
const pdf::PDFEditedPageContent& editedPageContent = m_editedPageContent.at(pageIndex);
|
||||||
|
|
||||||
|
pdf::PDFPageContentEditorContentStreamBuilder contentStreamBuilder;
|
||||||
|
contentStreamBuilder.setFontDictionary(editedPageContent.getFontDictionary());
|
||||||
|
|
||||||
|
auto it = elementsByPage.find(pageIndex);
|
||||||
|
if (it != elementsByPage.cend())
|
||||||
|
{
|
||||||
|
for (const pdf::PDFPageContentElement* element : it->second)
|
||||||
|
{
|
||||||
|
const pdf::PDFPageContentElementEdited* editedElement = element->asElementEdited();
|
||||||
|
|
||||||
|
if (editedElement)
|
||||||
|
{
|
||||||
|
contentStreamBuilder.writeElement(editedElement->getElement());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TODO: Implement other elements
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList errors = contentStreamBuilder.getErrors();
|
||||||
|
contentStreamBuilder.clearErrors();
|
||||||
|
|
||||||
|
if (!errors.empty())
|
||||||
|
{
|
||||||
|
const int errorCount = errors.size();
|
||||||
|
if (errors.size() > 3)
|
||||||
|
{
|
||||||
|
errors.resize(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString message = tr("Errors (%2) occured while creating content stream on page %3.<br>%1").arg(errors.join("<br>")).arg(errorCount).arg(pageIndex + 1);
|
||||||
|
if (QMessageBox::question(m_dataExchangeInterface->getMainWindow(), tr("Error"), message, QMessageBox::Abort, QMessageBox::Ignore) == QMessageBox::Abort)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pdf::PDFDocumentBuilder* builder = modifier.getBuilder();
|
||||||
|
|
||||||
|
pdf::PDFDictionary fontDictionary = contentStreamBuilder.getFontDictionary();
|
||||||
|
pdf::PDFDictionary xobjectDictionary = contentStreamBuilder.getXObjectDictionary();
|
||||||
|
pdf::PDFDictionary graphicStateDictionary = contentStreamBuilder.getGraphicStateDictionary();
|
||||||
|
|
||||||
|
builder->replaceObjectsByReferences(fontDictionary);
|
||||||
|
builder->replaceObjectsByReferences(xobjectDictionary);
|
||||||
|
builder->replaceObjectsByReferences(graphicStateDictionary);
|
||||||
|
|
||||||
|
pdf::PDFArray array;
|
||||||
|
array.appendItem(pdf::PDFObject::createName("FlateDecode"));
|
||||||
|
|
||||||
|
// Compress the content stream
|
||||||
|
QByteArray compressedData = pdf::PDFFlateDecodeFilter::compress(contentStreamBuilder.getOutputContent());
|
||||||
|
pdf::PDFDictionary contentDictionary;
|
||||||
|
contentDictionary.setEntry(pdf::PDFInplaceOrMemoryString("Length"), pdf::PDFObject::createInteger(compressedData.size()));
|
||||||
|
contentDictionary.setEntry(pdf::PDFInplaceOrMemoryString("Filter"), pdf::PDFObject::createArray(std::make_shared<pdf::PDFArray>(qMove(array))));
|
||||||
|
pdf::PDFObject contentObject = pdf::PDFObject::createStream(std::make_shared<pdf::PDFStream>(qMove(contentDictionary), qMove(compressedData)));
|
||||||
|
|
||||||
|
pdf::PDFObject pageObject = builder->getObjectByReference(page->getPageReference());
|
||||||
|
|
||||||
|
pdf::PDFObjectFactory factory;
|
||||||
|
factory.beginDictionary();
|
||||||
|
factory.beginDictionaryItem("Resources");
|
||||||
|
factory.beginDictionary();
|
||||||
|
|
||||||
|
if (!fontDictionary.isEmpty())
|
||||||
|
{
|
||||||
|
factory.beginDictionaryItem("Font");
|
||||||
|
factory << fontDictionary;
|
||||||
|
factory.endDictionaryItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!xobjectDictionary.isEmpty())
|
||||||
|
{
|
||||||
|
factory.beginDictionaryItem("XObject");
|
||||||
|
factory << xobjectDictionary;
|
||||||
|
factory.endDictionaryItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!graphicStateDictionary.isEmpty())
|
||||||
|
{
|
||||||
|
factory.beginDictionaryItem("ExtGState");
|
||||||
|
factory << graphicStateDictionary;
|
||||||
|
factory.endDictionaryItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
factory.endDictionary();
|
||||||
|
factory.endDictionaryItem();
|
||||||
|
|
||||||
|
factory.beginDictionaryItem("Content");
|
||||||
|
factory << builder->addObject(std::move(contentObject));
|
||||||
|
factory.endDictionaryItem();
|
||||||
|
|
||||||
|
factory.endDictionary();
|
||||||
|
|
||||||
|
pageObject = pdf::PDFObjectManipulator::merge(pageObject, factory.takeObject(), pdf::PDFObjectManipulator::RemoveNullObjects);
|
||||||
|
builder->setObject(page->getPageReference(), std::move(pageObject));
|
||||||
|
|
||||||
|
modifier.markReset();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_scene.clear();
|
||||||
|
m_editedPageContent.clear();
|
||||||
|
|
||||||
|
if (modifier.finalize())
|
||||||
|
{
|
||||||
|
Q_EMIT m_widget->getToolManager()->documentModified(pdf::PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void EditorPlugin::onSceneChanged(bool graphicsOnly)
|
void EditorPlugin::onSceneChanged(bool graphicsOnly)
|
||||||
{
|
{
|
||||||
if (!graphicsOnly)
|
if (!graphicsOnly)
|
||||||
|
@ -49,6 +49,8 @@ public:
|
|||||||
virtual std::vector<QAction*> getActions() const override;
|
virtual std::vector<QAction*> getActions() const override;
|
||||||
virtual QString getPluginMenuName() const override;
|
virtual QString getPluginMenuName() const override;
|
||||||
|
|
||||||
|
bool save();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void onSceneChanged(bool graphicsOnly);
|
void onSceneChanged(bool graphicsOnly);
|
||||||
void onSceneSelectionChanged();
|
void onSceneSelectionChanged();
|
||||||
|
@ -285,6 +285,12 @@ PDFObjectFactory& PDFObjectFactory::operator<<(AnnotationBorderStyle style)
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PDFObjectFactory& PDFObjectFactory::operator<<(PDFDictionary dictionary)
|
||||||
|
{
|
||||||
|
*this << PDFObject::createDictionary(std::make_shared<pdf::PDFDictionary>(std::move(dictionary)));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
PDFObjectFactory& PDFObjectFactory::operator<<(const QDateTime& dateTime)
|
PDFObjectFactory& PDFObjectFactory::operator<<(const QDateTime& dateTime)
|
||||||
{
|
{
|
||||||
addObject(PDFObject::createString(PDFEncoding::convertDateTimeToString(dateTime)));
|
addObject(PDFObject::createString(PDFEncoding::convertDateTimeToString(dateTime)));
|
||||||
@ -708,6 +714,19 @@ PDFDocument PDFDocumentBuilder::build()
|
|||||||
return PDFDocument(PDFObjectStorage(m_storage), m_version, QByteArray());
|
return PDFDocument(PDFObjectStorage(m_storage), m_version, QByteArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PDFDocumentBuilder::replaceObjectsByReferences(PDFDictionary& dictionary)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < dictionary.getCount(); ++i)
|
||||||
|
{
|
||||||
|
const PDFObject& object = dictionary.getValue(i);
|
||||||
|
if (!object.isReference())
|
||||||
|
{
|
||||||
|
auto key = dictionary.getKey(i);
|
||||||
|
dictionary.setEntry(key, PDFObject::createReference(addObject(object)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QByteArray PDFDocumentBuilder::getDecodedStream(const PDFStream* stream) const
|
QByteArray PDFDocumentBuilder::getDecodedStream(const PDFStream* stream) const
|
||||||
{
|
{
|
||||||
return m_storage.getDecodedStream(stream);
|
return m_storage.getDecodedStream(stream);
|
||||||
|
@ -133,6 +133,7 @@ public:
|
|||||||
PDFObjectFactory& operator<<(const PDFDestination& destination);
|
PDFObjectFactory& operator<<(const PDFDestination& destination);
|
||||||
PDFObjectFactory& operator<<(PageRotation pageRotation);
|
PDFObjectFactory& operator<<(PageRotation pageRotation);
|
||||||
PDFObjectFactory& operator<<(PDFFormSubmitFlags flags);
|
PDFObjectFactory& operator<<(PDFFormSubmitFlags flags);
|
||||||
|
PDFObjectFactory& operator<<(PDFDictionary dictionary);
|
||||||
|
|
||||||
/// Treat containers - write them as array
|
/// Treat containers - write them as array
|
||||||
template<typename Container, typename ValueType = decltype(*std::begin(std::declval<Container>()))>
|
template<typename Container, typename ValueType = decltype(*std::begin(std::declval<Container>()))>
|
||||||
@ -343,6 +344,9 @@ public:
|
|||||||
/// if document being built was invalid.
|
/// if document being built was invalid.
|
||||||
PDFDocument build();
|
PDFDocument build();
|
||||||
|
|
||||||
|
/// Replaces all objects by references in the dictionary
|
||||||
|
void replaceObjectsByReferences(PDFDictionary& dictionary);
|
||||||
|
|
||||||
/// If object is reference, the dereference attempt is performed
|
/// If object is reference, the dereference attempt is performed
|
||||||
/// and object is returned. If it is not a reference, then self
|
/// and object is returned. If it is not a reference, then self
|
||||||
/// is returned. If dereference attempt fails, then null object
|
/// is returned. If dereference attempt fails, then null object
|
||||||
|
@ -427,6 +427,8 @@ public:
|
|||||||
/// Removes null objects from dictionary
|
/// Removes null objects from dictionary
|
||||||
void removeNullObjects();
|
void removeNullObjects();
|
||||||
|
|
||||||
|
bool isEmpty() const { return getCount() == 0; }
|
||||||
|
|
||||||
/// Optimizes the dictionary for memory consumption
|
/// Optimizes the dictionary for memory consumption
|
||||||
virtual void optimize() override;
|
virtual void optimize() override;
|
||||||
|
|
||||||
|
@ -778,6 +778,11 @@ void PDFEditedPageContentElementText::setItemsAsText(const QString& newItemsAsTe
|
|||||||
m_itemsAsText = newItemsAsText;
|
m_itemsAsText = newItemsAsText;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PDFPageContentEditorContentStreamBuilder::PDFPageContentEditorContentStreamBuilder()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void PDFPageContentEditorContentStreamBuilder::writeStateDifference(QTextStream& stream, const PDFPageContentProcessorState& state)
|
void PDFPageContentEditorContentStreamBuilder::writeStateDifference(QTextStream& stream, const PDFPageContentProcessorState& state)
|
||||||
{
|
{
|
||||||
m_currentState.setState(state);
|
m_currentState.setState(state);
|
||||||
@ -1065,6 +1070,11 @@ void PDFPageContentEditorContentStreamBuilder::writeElement(const PDFEditedPageC
|
|||||||
stream << Qt::endl;
|
stream << Qt::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const QByteArray& PDFPageContentEditorContentStreamBuilder::getOutputContent() const
|
||||||
|
{
|
||||||
|
return m_outputContent;
|
||||||
|
}
|
||||||
|
|
||||||
void PDFPageContentEditorContentStreamBuilder::writePainterPath(QTextStream& stream,
|
void PDFPageContentEditorContentStreamBuilder::writePainterPath(QTextStream& stream,
|
||||||
const QPainterPath& path,
|
const QPainterPath& path,
|
||||||
bool isStroking,
|
bool isStroking,
|
||||||
@ -1455,4 +1465,9 @@ void PDFPageContentEditorContentStreamBuilder::addError(const QString& error)
|
|||||||
m_errors << error;
|
m_errors << error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PDFPageContentEditorContentStreamBuilder::setFontDictionary(const PDFDictionary& newFontDictionary)
|
||||||
|
{
|
||||||
|
m_fontDictionary = newFontDictionary;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace pdf
|
} // namespace pdf
|
||||||
|
@ -218,6 +218,15 @@ public:
|
|||||||
|
|
||||||
const QByteArray& getOutputContent() const;
|
const QByteArray& getOutputContent() const;
|
||||||
|
|
||||||
|
const PDFDictionary& getFontDictionary() const { return m_fontDictionary; }
|
||||||
|
const PDFDictionary& getXObjectDictionary() const { return m_xobjectDictionary; }
|
||||||
|
const PDFDictionary& getGraphicStateDictionary() const { return m_graphicStateDictionary; }
|
||||||
|
|
||||||
|
void setFontDictionary(const PDFDictionary& newFontDictionary);
|
||||||
|
|
||||||
|
const QStringList& getErrors() const { return m_errors; }
|
||||||
|
void clearErrors() { m_errors.clear(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void writePainterPath(QTextStream& stream,
|
void writePainterPath(QTextStream& stream,
|
||||||
const QPainterPath& path,
|
const QPainterPath& path,
|
||||||
|
@ -904,6 +904,18 @@ std::set<PDFInteger> PDFPageContentScene::getPageIndices() const
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::map<PDFInteger, std::vector<const PDFPageContentElement*>> PDFPageContentScene::getElementsByPage() const
|
||||||
|
{
|
||||||
|
std::map<PDFInteger, std::vector<const PDFPageContentElement*>> result;
|
||||||
|
|
||||||
|
for (const auto& elementHandle : m_elements)
|
||||||
|
{
|
||||||
|
result[elementHandle->getPageIndex()].push_back(elementHandle.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
QRectF PDFPageContentScene::getBoundingBox(PDFInteger pageIndex) const
|
QRectF PDFPageContentScene::getBoundingBox(PDFInteger pageIndex) const
|
||||||
{
|
{
|
||||||
QRectF rect;
|
QRectF rect;
|
||||||
|
@ -38,6 +38,7 @@ class PDFWidget;
|
|||||||
class PDFDocument;
|
class PDFDocument;
|
||||||
class PDFPageContentScene;
|
class PDFPageContentScene;
|
||||||
class PDFEditedPageContentElement;
|
class PDFEditedPageContentElement;
|
||||||
|
class PDFPageContentElementEdited;
|
||||||
|
|
||||||
class PDF4QTLIBWIDGETSSHARED_EXPORT PDFPageContentElement
|
class PDF4QTLIBWIDGETSSHARED_EXPORT PDFPageContentElement
|
||||||
{
|
{
|
||||||
@ -104,6 +105,8 @@ public:
|
|||||||
Pt2
|
Pt2
|
||||||
};
|
};
|
||||||
|
|
||||||
|
virtual const PDFPageContentElementEdited* asElementEdited() const { return nullptr; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
uint getRectangleManipulationMode(const QRectF& rectangle,
|
uint getRectangleManipulationMode(const QRectF& rectangle,
|
||||||
const QPointF& point,
|
const QPointF& point,
|
||||||
@ -368,6 +371,7 @@ public:
|
|||||||
virtual QRectF getBoundingBox() const override;
|
virtual QRectF getBoundingBox() const override;
|
||||||
virtual void setSize(QSizeF size) override;
|
virtual void setSize(QSizeF size) override;
|
||||||
virtual QString getDescription() const override;
|
virtual QString getDescription() const override;
|
||||||
|
virtual const PDFPageContentElementEdited* asElementEdited() const { return this; }
|
||||||
|
|
||||||
const PDFEditedPageContentElement* getElement() const { return m_element.get(); }
|
const PDFEditedPageContentElement* getElement() const { return m_element.get(); }
|
||||||
PDFEditedPageContentElement* getElement() { return m_element.get(); }
|
PDFEditedPageContentElement* getElement() { return m_element.get(); }
|
||||||
@ -532,6 +536,8 @@ public:
|
|||||||
/// Returns set of involved pages
|
/// Returns set of involved pages
|
||||||
std::set<PDFInteger> getPageIndices() const;
|
std::set<PDFInteger> getPageIndices() const;
|
||||||
|
|
||||||
|
std::map<PDFInteger, std::vector<const PDFPageContentElement*>> getElementsByPage() const;
|
||||||
|
|
||||||
/// Returns bounding box of elements on page
|
/// Returns bounding box of elements on page
|
||||||
QRectF getBoundingBox(PDFInteger pageIndex) const;
|
QRectF getBoundingBox(PDFInteger pageIndex) const;
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user