diff --git a/Pdf4QtEditorPlugins/EditorPlugin/editorplugin.cpp b/Pdf4QtEditorPlugins/EditorPlugin/editorplugin.cpp index e4c1d54..6f8b257 100644 --- a/Pdf4QtEditorPlugins/EditorPlugin/editorplugin.cpp +++ b/Pdf4QtEditorPlugins/EditorPlugin/editorplugin.cpp @@ -23,6 +23,8 @@ #include "pdfdocumentbuilder.h" #include "pdfcertificatemanagerdialog.h" #include "pdfdocumentwriter.h" +#include "pdfpagecontenteditorprocessor.h" +#include "pdfstreamfilters.h" #include #include @@ -180,6 +182,138 @@ QString EditorPlugin::getPluginMenuName() const 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 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.
%1").arg(errors.join("
")).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(qMove(array)))); + pdf::PDFObject contentObject = pdf::PDFObject::createStream(std::make_shared(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) { if (!graphicsOnly) diff --git a/Pdf4QtEditorPlugins/EditorPlugin/editorplugin.h b/Pdf4QtEditorPlugins/EditorPlugin/editorplugin.h index d2a07ce..76e6d10 100644 --- a/Pdf4QtEditorPlugins/EditorPlugin/editorplugin.h +++ b/Pdf4QtEditorPlugins/EditorPlugin/editorplugin.h @@ -49,6 +49,8 @@ public: virtual std::vector getActions() const override; virtual QString getPluginMenuName() const override; + bool save(); + private: void onSceneChanged(bool graphicsOnly); void onSceneSelectionChanged(); diff --git a/Pdf4QtLibCore/sources/pdfdocumentbuilder.cpp b/Pdf4QtLibCore/sources/pdfdocumentbuilder.cpp index 3f679bf..be73df7 100644 --- a/Pdf4QtLibCore/sources/pdfdocumentbuilder.cpp +++ b/Pdf4QtLibCore/sources/pdfdocumentbuilder.cpp @@ -285,6 +285,12 @@ PDFObjectFactory& PDFObjectFactory::operator<<(AnnotationBorderStyle style) return *this; } +PDFObjectFactory& PDFObjectFactory::operator<<(PDFDictionary dictionary) +{ + *this << PDFObject::createDictionary(std::make_shared(std::move(dictionary))); + return *this; +} + PDFObjectFactory& PDFObjectFactory::operator<<(const QDateTime& dateTime) { addObject(PDFObject::createString(PDFEncoding::convertDateTimeToString(dateTime))); @@ -708,6 +714,19 @@ PDFDocument PDFDocumentBuilder::build() 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 { return m_storage.getDecodedStream(stream); diff --git a/Pdf4QtLibCore/sources/pdfdocumentbuilder.h b/Pdf4QtLibCore/sources/pdfdocumentbuilder.h index 3a5de42..276e4b7 100644 --- a/Pdf4QtLibCore/sources/pdfdocumentbuilder.h +++ b/Pdf4QtLibCore/sources/pdfdocumentbuilder.h @@ -133,6 +133,7 @@ public: PDFObjectFactory& operator<<(const PDFDestination& destination); PDFObjectFactory& operator<<(PageRotation pageRotation); PDFObjectFactory& operator<<(PDFFormSubmitFlags flags); + PDFObjectFactory& operator<<(PDFDictionary dictionary); /// Treat containers - write them as array template()))> @@ -343,6 +344,9 @@ public: /// if document being built was invalid. PDFDocument build(); + /// Replaces all objects by references in the dictionary + void replaceObjectsByReferences(PDFDictionary& dictionary); + /// If object is reference, the dereference attempt is performed /// and object is returned. If it is not a reference, then self /// is returned. If dereference attempt fails, then null object diff --git a/Pdf4QtLibCore/sources/pdfobject.h b/Pdf4QtLibCore/sources/pdfobject.h index df28851..a55a27c 100644 --- a/Pdf4QtLibCore/sources/pdfobject.h +++ b/Pdf4QtLibCore/sources/pdfobject.h @@ -427,6 +427,8 @@ public: /// Removes null objects from dictionary void removeNullObjects(); + bool isEmpty() const { return getCount() == 0; } + /// Optimizes the dictionary for memory consumption virtual void optimize() override; diff --git a/Pdf4QtLibCore/sources/pdfpagecontenteditorprocessor.cpp b/Pdf4QtLibCore/sources/pdfpagecontenteditorprocessor.cpp index d0e9a3d..b3342bd 100644 --- a/Pdf4QtLibCore/sources/pdfpagecontenteditorprocessor.cpp +++ b/Pdf4QtLibCore/sources/pdfpagecontenteditorprocessor.cpp @@ -778,6 +778,11 @@ void PDFEditedPageContentElementText::setItemsAsText(const QString& newItemsAsTe m_itemsAsText = newItemsAsText; } +PDFPageContentEditorContentStreamBuilder::PDFPageContentEditorContentStreamBuilder() +{ + +} + void PDFPageContentEditorContentStreamBuilder::writeStateDifference(QTextStream& stream, const PDFPageContentProcessorState& state) { m_currentState.setState(state); @@ -1065,6 +1070,11 @@ void PDFPageContentEditorContentStreamBuilder::writeElement(const PDFEditedPageC stream << Qt::endl; } +const QByteArray& PDFPageContentEditorContentStreamBuilder::getOutputContent() const +{ + return m_outputContent; +} + void PDFPageContentEditorContentStreamBuilder::writePainterPath(QTextStream& stream, const QPainterPath& path, bool isStroking, @@ -1455,4 +1465,9 @@ void PDFPageContentEditorContentStreamBuilder::addError(const QString& error) m_errors << error; } +void PDFPageContentEditorContentStreamBuilder::setFontDictionary(const PDFDictionary& newFontDictionary) +{ + m_fontDictionary = newFontDictionary; +} + } // namespace pdf diff --git a/Pdf4QtLibCore/sources/pdfpagecontenteditorprocessor.h b/Pdf4QtLibCore/sources/pdfpagecontenteditorprocessor.h index 0bcff07..50fbb9a 100644 --- a/Pdf4QtLibCore/sources/pdfpagecontenteditorprocessor.h +++ b/Pdf4QtLibCore/sources/pdfpagecontenteditorprocessor.h @@ -218,6 +218,15 @@ public: 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: void writePainterPath(QTextStream& stream, const QPainterPath& path, diff --git a/Pdf4QtLibWidgets/sources/pdfpagecontentelements.cpp b/Pdf4QtLibWidgets/sources/pdfpagecontentelements.cpp index aacf90e..4287706 100644 --- a/Pdf4QtLibWidgets/sources/pdfpagecontentelements.cpp +++ b/Pdf4QtLibWidgets/sources/pdfpagecontentelements.cpp @@ -904,6 +904,18 @@ std::set PDFPageContentScene::getPageIndices() const return result; } +std::map> PDFPageContentScene::getElementsByPage() const +{ + std::map> result; + + for (const auto& elementHandle : m_elements) + { + result[elementHandle->getPageIndex()].push_back(elementHandle.get()); + } + + return result; +} + QRectF PDFPageContentScene::getBoundingBox(PDFInteger pageIndex) const { QRectF rect; diff --git a/Pdf4QtLibWidgets/sources/pdfpagecontentelements.h b/Pdf4QtLibWidgets/sources/pdfpagecontentelements.h index 7502ed8..442ba63 100644 --- a/Pdf4QtLibWidgets/sources/pdfpagecontentelements.h +++ b/Pdf4QtLibWidgets/sources/pdfpagecontentelements.h @@ -38,6 +38,7 @@ class PDFWidget; class PDFDocument; class PDFPageContentScene; class PDFEditedPageContentElement; +class PDFPageContentElementEdited; class PDF4QTLIBWIDGETSSHARED_EXPORT PDFPageContentElement { @@ -104,6 +105,8 @@ public: Pt2 }; + virtual const PDFPageContentElementEdited* asElementEdited() const { return nullptr; } + protected: uint getRectangleManipulationMode(const QRectF& rectangle, const QPointF& point, @@ -368,6 +371,7 @@ public: virtual QRectF getBoundingBox() const override; virtual void setSize(QSizeF size) override; virtual QString getDescription() const override; + virtual const PDFPageContentElementEdited* asElementEdited() const { return this; } const PDFEditedPageContentElement* getElement() const { return m_element.get(); } PDFEditedPageContentElement* getElement() { return m_element.get(); } @@ -532,6 +536,8 @@ public: /// Returns set of involved pages std::set getPageIndices() const; + std::map> getElementsByPage() const; + /// Returns bounding box of elements on page QRectF getBoundingBox(PDFInteger pageIndex) const;