mirror of
				https://github.com/JakubMelka/PDF4QT.git
				synced 2025-06-05 21:59:17 +02:00 
			
		
		
		
	Signature plugin: Electronic signature
This commit is contained in:
		| @@ -501,10 +501,11 @@ public: | ||||
|     { | ||||
|         None            = 0x0000,   ///< No flag | ||||
|         Reset           = 0x0001,   ///< Whole document content is changed (for example, new document is being set) | ||||
|         Annotation      = 0x0002,   ///< Annotations changed | ||||
|         FormField       = 0x0004,   ///< Form field content changed | ||||
|         Authorization   = 0x0008,   ///< Authorization has changed (for example, old document is granted user access, but for new, owner access) | ||||
|         XFA_Pagination  = 0x0010,   ///< XFA pagination has been performed (this flag can be set only when Reset flag has been set and not any other flag) | ||||
|         PageContents    = 0x0002,   ///< Page contents changed (page graphics, not annotations) | ||||
|         Annotation      = 0x0004,   ///< Annotations changed | ||||
|         FormField       = 0x0008,   ///< Form field content changed | ||||
|         Authorization   = 0x0010,   ///< Authorization has changed (for example, old document is granted user access, but for new, owner access) | ||||
|         XFA_Pagination  = 0x0020,   ///< XFA pagination has been performed (this flag can be set only when Reset flag has been set and not any other flag) | ||||
|     }; | ||||
|  | ||||
|     Q_DECLARE_FLAGS(ModificationFlags, ModificationFlag) | ||||
| @@ -550,6 +551,7 @@ public: | ||||
|     ModificationFlags getFlags() const { return m_flags; } | ||||
|  | ||||
|     bool hasReset() const { return m_flags.testFlag(Reset); } | ||||
|     bool hasPageContentsChanged() const { return m_flags.testFlag(PageContents); } | ||||
|     bool hasFlag(ModificationFlag flag) const { return m_flags.testFlag(flag); } | ||||
|  | ||||
|     operator PDFDocument*() const { return m_document; } | ||||
|   | ||||
| @@ -22,6 +22,8 @@ | ||||
| #include "pdfobjectutils.h" | ||||
| #include "pdfnametreeloader.h" | ||||
| #include "pdfdbgheap.h" | ||||
| #include "pdfparser.h" | ||||
| #include "pdfstreamfilters.h" | ||||
|  | ||||
| #include <QBuffer> | ||||
| #include <QPainter> | ||||
| @@ -698,6 +700,11 @@ PDFDocument PDFDocumentBuilder::build() | ||||
|     return PDFDocument(PDFObjectStorage(m_storage), m_version); | ||||
| } | ||||
|  | ||||
| QByteArray PDFDocumentBuilder::getDecodedStream(const PDFStream* stream) const | ||||
| { | ||||
|     return m_storage.getDecodedStream(stream); | ||||
| } | ||||
|  | ||||
| std::array<PDFReal, 4> PDFDocumentBuilder::getAnnotationReductionRectangle(const QRectF& boundingRect, const QRectF& innerRect) const | ||||
| { | ||||
|     return { qAbs(innerRect.left() - boundingRect.left()), qAbs(boundingRect.bottom() - innerRect.bottom()), qAbs(boundingRect.right() - innerRect.right()), qAbs(boundingRect.top() - innerRect.top()) }; | ||||
| @@ -813,6 +820,10 @@ void PDFPageContentStreamBuilder::end(QPainter* painter) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             PDFObject oldResourcesObject = pageDictionary->get("Resources"); | ||||
|             replaceResources(contentsReference, resourcesReference, oldResourcesObject); | ||||
|             m_documentBuilder->mergeTo(resourcesReference, m_documentBuilder->getObject(oldResourcesObject)); | ||||
|         } | ||||
|  | ||||
|         switch (m_mode) | ||||
| @@ -848,6 +859,139 @@ void PDFPageContentStreamBuilder::end(QPainter* painter) | ||||
|     } | ||||
| } | ||||
|  | ||||
| void PDFPageContentStreamBuilder::replaceResources(PDFObjectReference contentStreamReference, | ||||
|                                                    PDFObjectReference resourcesReference, | ||||
|                                                    PDFObject oldResources) | ||||
| { | ||||
|     PDFObject newResources = m_documentBuilder->getObjectByReference(resourcesReference); | ||||
|     oldResources = m_documentBuilder->getObject(oldResources); | ||||
|     PDFObject contentStreamObject = m_documentBuilder->getObjectByReference(contentStreamReference); | ||||
|  | ||||
|     std::vector<std::pair<QByteArray, QByteArray>> renamings; | ||||
|  | ||||
|     if (oldResources.isDictionary() && newResources.isDictionary()) | ||||
|     { | ||||
|         PDFObjectFactory renamedResourcesDictionary; | ||||
|  | ||||
|         renamedResourcesDictionary.beginDictionary(); | ||||
|  | ||||
|         const PDFDictionary* oldResourcesDictionary = oldResources.getDictionary(); | ||||
|         const PDFDictionary* newResourcesDictionary = newResources.getDictionary(); | ||||
|         const size_t count = newResourcesDictionary->getCount(); | ||||
|         for (size_t i = 0; i < count; ++i) | ||||
|         { | ||||
|             const PDFInplaceOrMemoryString& key = newResourcesDictionary->getKey(i); | ||||
|             QByteArray keyString = key.getString(); | ||||
|  | ||||
|             // Process current resource key | ||||
|             renamedResourcesDictionary.beginDictionaryItem(keyString); | ||||
|  | ||||
|             if (oldResourcesDictionary->hasKey(keyString)) | ||||
|             { | ||||
|                 const PDFObject& newResourcesSubdictionaryObject = m_documentBuilder->getObject(newResourcesDictionary->getValue(i)); | ||||
|                 const PDFObject& oldResourcesSubdictionaryObject = m_documentBuilder->getObject(oldResourcesDictionary->get(keyString)); | ||||
|                 if (oldResourcesSubdictionaryObject.isDictionary() && newResourcesSubdictionaryObject.isDictionary()) | ||||
|                 { | ||||
|                     // Jakub Melka: Rename items, which are in both dictionaries | ||||
|                     const PDFDictionary* oldSd = oldResourcesSubdictionaryObject.getDictionary(); | ||||
|                     const PDFDictionary* newSd = newResourcesSubdictionaryObject.getDictionary(); | ||||
|  | ||||
|                     renamedResourcesDictionary.beginDictionary(); | ||||
|  | ||||
|                     const size_t subcount = newSd->getCount(); | ||||
|                     for (size_t j = 0; j < subcount; ++j) | ||||
|                     { | ||||
|                         const PDFInplaceOrMemoryString& subkey = newSd->getKey(j); | ||||
|                         QByteArray subkeyString = subkey.getString(); | ||||
|                         if (oldSd->hasKey(subkeyString)) | ||||
|                         { | ||||
|                             // Jakub Melka: we must rename the item | ||||
|                             QByteArray newSubkeyString = subkeyString; | ||||
|                             PDFInteger k = 0; | ||||
|                             while (oldSd->hasKey(newSubkeyString)) | ||||
|                             { | ||||
|                                 newSubkeyString = subkeyString + "_" + QByteArray::number(++k); | ||||
|                             } | ||||
|                             renamings.emplace_back(std::make_pair(subkeyString, newSubkeyString)); | ||||
|                             subkeyString = newSubkeyString; | ||||
|                         } | ||||
|  | ||||
|                         renamedResourcesDictionary.beginDictionaryItem(subkeyString); | ||||
|                         renamedResourcesDictionary << newSd->getValue(j); | ||||
|                         renamedResourcesDictionary.endDictionaryItem(); | ||||
|                     } | ||||
|  | ||||
|                     renamedResourcesDictionary.endDictionary(); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     renamedResourcesDictionary << newResourcesDictionary->getValue(i); | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 renamedResourcesDictionary << newResourcesDictionary->getValue(i); | ||||
|             } | ||||
|  | ||||
|             renamedResourcesDictionary.endDictionaryItem(); | ||||
|         } | ||||
|  | ||||
|         renamedResourcesDictionary.endDictionary(); | ||||
|         m_documentBuilder->setObject(resourcesReference, renamedResourcesDictionary.takeObject()); | ||||
|     } | ||||
|  | ||||
|     if (contentStreamObject.isStream()) | ||||
|     { | ||||
|         QByteArray decodedStream = m_documentBuilder->getDecodedStream(contentStreamObject.getStream()); | ||||
|         decodedStream = decodedStream.trimmed(); | ||||
|  | ||||
|         // Append save/restore state | ||||
|         if (!decodedStream.startsWith("q ")) | ||||
|         { | ||||
|             decodedStream.prepend("q "); | ||||
|             decodedStream.append(" Q"); | ||||
|         } | ||||
|  | ||||
|         // Replace all occurences in the stream | ||||
|         for (const auto& item : renamings) | ||||
|         { | ||||
|             QByteArray oldName = item.first; | ||||
|             QByteArray newName = item.second; | ||||
|  | ||||
|             oldName.prepend('/'); | ||||
|             newName.prepend('/'); | ||||
|  | ||||
|             int currentPos = 0; | ||||
|             int oldNameIndex = decodedStream.indexOf(oldName, currentPos); | ||||
|             while (oldNameIndex != -1) | ||||
|             { | ||||
|                 const int whiteSpacePosition = oldNameIndex + oldName.size(); | ||||
|                 if (whiteSpacePosition < decodedStream.size() && !PDFLexicalAnalyzer::isWhitespace(decodedStream[whiteSpacePosition])) | ||||
|                 { | ||||
|                     currentPos = oldNameIndex + 1; | ||||
|                     oldNameIndex = decodedStream.indexOf(oldName, currentPos); | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 decodedStream.replace(oldNameIndex, oldName.length(), newName); | ||||
|                 currentPos = oldNameIndex + 1; | ||||
|                 oldNameIndex = decodedStream.indexOf(oldName, currentPos); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         PDFArray array; | ||||
|         array.appendItem(PDFObject::createName("FlateDecode")); | ||||
|  | ||||
|         // Compress the content stream | ||||
|         QByteArray compressedData = PDFFlateDecodeFilter::compress(decodedStream); | ||||
|         PDFDictionary updatedDictionary = *contentStreamObject.getStream()->getDictionary(); | ||||
|         updatedDictionary.setEntry(PDFInplaceOrMemoryString("Length"), PDFObject::createInteger(compressedData.size())); | ||||
|         updatedDictionary.setEntry(PDFInplaceOrMemoryString("Filters"), PDFObject::createArray(std::make_shared<PDFArray>(qMove(array)))); | ||||
|         PDFObject newContentStream = PDFObject::createStream(std::make_shared<PDFStream>(qMove(updatedDictionary), qMove(compressedData))); | ||||
|         m_documentBuilder->setObject(contentStreamReference, std::move(newContentStream)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| void PDFDocumentBuilder::updateAnnotationAppearanceStreams(PDFObjectReference annotationReference) | ||||
| { | ||||
|     PDFAnnotationPtr annotation = PDFAnnotation::parse(&m_storage, annotationReference); | ||||
|   | ||||
| @@ -295,6 +295,10 @@ public: | ||||
|     void end(QPainter* painter); | ||||
|  | ||||
| private: | ||||
|     void replaceResources(PDFObjectReference contentStreamReference, | ||||
|                           PDFObjectReference resourcesReference, | ||||
|                           PDFObject oldResources); | ||||
|  | ||||
|     PDFDocumentBuilder* m_documentBuilder; | ||||
|     PDFContentStreamBuilder* m_contentStreamBuilder; | ||||
|     PDFObjectReference m_pageReference; | ||||
| @@ -341,6 +345,11 @@ public: | ||||
|     /// is returned (no exception is thrown). | ||||
|     const PDFObject& getObjectByReference(PDFObjectReference reference) const; | ||||
|  | ||||
|     /// Returns the decoded stream. If stream data cannot be decoded, | ||||
|     /// then empty byte array is returned. | ||||
|     /// \param stream Stream to be decoded | ||||
|     QByteArray getDecodedStream(const PDFStream* stream) const; | ||||
|  | ||||
|     /// Returns annotation bounding rectangle | ||||
|     std::array<PDFReal, 4> getAnnotationReductionRectangle(const QRectF& boundingRect, const QRectF& innerRect) const; | ||||
|  | ||||
| @@ -1526,6 +1535,7 @@ public: | ||||
|     PDFModifiedDocument::ModificationFlags getFlags() const { return m_modificationFlags; } | ||||
|  | ||||
|     void markReset() { m_modificationFlags.setFlag(PDFModifiedDocument::Reset); } | ||||
|     void markPageContentsChanged() { m_modificationFlags.setFlag(PDFModifiedDocument::PageContents); } | ||||
|     void markAnnotationsChanged() { m_modificationFlags.setFlag(PDFModifiedDocument::Annotation); } | ||||
|     void markFormFieldChanged() { m_modificationFlags.setFlag(PDFModifiedDocument::FormField); } | ||||
|     void markXFAPagination() { m_modificationFlags.setFlag(PDFModifiedDocument::XFA_Pagination); } | ||||
|   | ||||
| @@ -192,7 +192,10 @@ PDFOperationResult PDFDocumentWriter::write(const QString& fileName, const PDFDo | ||||
|             PDFOperationResult result = write(&file, document); | ||||
|             if (result) | ||||
|             { | ||||
|                 file.commit(); | ||||
|                 if (!file.commit()) | ||||
|                 { | ||||
|                     return tr("File '%1' can't be opened for writing. %2").arg(fileName, file.errorString()); | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|   | ||||
| @@ -488,8 +488,8 @@ void PDFDrawWidgetProxy::setDocument(const PDFModifiedDocument& document) | ||||
|     if (getDocument() != document) | ||||
|     { | ||||
|         m_cacheClearTimer->stop(); | ||||
|         m_compiler->stop(document.hasReset()); | ||||
|         m_textLayoutCompiler->stop(document.hasReset()); | ||||
|         m_compiler->stop(document.hasReset() || document.hasPageContentsChanged()); | ||||
|         m_textLayoutCompiler->stop(document.hasReset() || document.hasPageContentsChanged()); | ||||
|         m_controller->setDocument(document); | ||||
|  | ||||
|         if (PDFOptionalContentActivity* optionalContentActivity = document.getOptionalContentActivity()) | ||||
|   | ||||
| @@ -1836,7 +1836,7 @@ void PDFFontCache::setDocument(const PDFModifiedDocument& document) | ||||
|  | ||||
|         // Jakub Melka: If document has not reset flag, then fonts of the | ||||
|         // document remains the same. So it is not needed to clear font cache. | ||||
|         if (document.hasReset()) | ||||
|         if (document.hasReset() || document.hasPageContentsChanged()) | ||||
|         { | ||||
|             m_fontCache.clear(); | ||||
|             m_realizedFontCache.clear(); | ||||
|   | ||||
| @@ -738,7 +738,7 @@ void PDFThumbnailsItemModel::setDocument(const PDFModifiedDocument& document) | ||||
| { | ||||
|     if (m_document != document) | ||||
|     { | ||||
|         if (document.hasReset() || document.hasFlag(PDFModifiedDocument::Annotation)) | ||||
|         if (document.hasReset() || document.hasPageContentsChanged() || document.hasFlag(PDFModifiedDocument::Annotation)) | ||||
|         { | ||||
|             beginResetModel(); | ||||
|             m_thumbnailCache.clear(); | ||||
|   | ||||
| @@ -2363,12 +2363,18 @@ void PDFPageContentElementTextBox::drawPage(QPainter* painter, | ||||
|     } | ||||
|  | ||||
|     QRectF rect = getRectangle(); | ||||
|     QFont font = getFont(); | ||||
|     font.setHintingPreference(QFont::PreferNoHinting); | ||||
|     if (font.pointSizeF() > 0.0) | ||||
|     { | ||||
|         font.setPixelSize(qCeil(font.pointSizeF())); | ||||
|     } | ||||
|  | ||||
|     PDFPainterStateGuard guard(painter); | ||||
|     painter->setWorldMatrix(pagePointToDevicePointMatrix, true); | ||||
|     painter->setPen(getPen()); | ||||
|     painter->setBrush(getBrush()); | ||||
|     painter->setFont(getFont()); | ||||
|     painter->setFont(font); | ||||
|     painter->setRenderHint(QPainter::Antialiasing); | ||||
|     painter->setClipRect(rect, Qt::IntersectClip); | ||||
|     painter->translate(rect.center()); | ||||
|   | ||||
| @@ -383,10 +383,9 @@ QByteArray PDFFlateDecodeFilter::apply(const QByteArray& data, | ||||
|     return predictor.apply(uncompress(data)); | ||||
| } | ||||
|  | ||||
| QByteArray PDFFlateDecodeFilter::recompress(const QByteArray& data) | ||||
| QByteArray PDFFlateDecodeFilter::compress(const QByteArray& decompressedData) | ||||
| { | ||||
|     QByteArray result; | ||||
|     QByteArray decompressedData = uncompress(data); | ||||
|  | ||||
|     z_stream stream = { }; | ||||
|     stream.next_in = const_cast<Bytef*>(convertByteArrayToUcharPtr(decompressedData)); | ||||
| @@ -431,13 +430,19 @@ QByteArray PDFFlateDecodeFilter::recompress(const QByteArray& data) | ||||
|                 errorMessage = PDFTranslationContext::tr("zlib code: %1").arg(error); | ||||
|             } | ||||
|  | ||||
|             throw PDFException(PDFTranslationContext::tr("Error decompressing by flate method: %1").arg(errorMessage)); | ||||
|             throw PDFException(PDFTranslationContext::tr("Error compressing by flate method: %1").arg(errorMessage)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return result; | ||||
| } | ||||
|  | ||||
| QByteArray PDFFlateDecodeFilter::recompress(const QByteArray& data) | ||||
| { | ||||
|     QByteArray decompressedData = uncompress(data); | ||||
|     return compress(decompressedData); | ||||
| } | ||||
|  | ||||
| PDFInteger PDFFlateDecodeFilter::getStreamDataLength(const QByteArray& data, PDFInteger offset) const | ||||
| { | ||||
|     if (offset < 0 || offset >= data.size()) | ||||
|   | ||||
| @@ -215,6 +215,10 @@ public: | ||||
|  | ||||
|     /// Recompresses data. So, first, data are decompressed, and then | ||||
|     /// recompressed again with maximal compress ratio possible. | ||||
|     /// \param data Uncompressed data to be compressed | ||||
|     static QByteArray compress(const QByteArray& decompressedData); | ||||
|  | ||||
|     /// Compress data with maximal compress ratio possible. | ||||
|     /// \param data Compressed data to be recompressed | ||||
|     static QByteArray recompress(const QByteArray& data); | ||||
|  | ||||
|   | ||||
| @@ -58,7 +58,7 @@ void PDFAdvancedFindWidget::setDocument(const pdf::PDFModifiedDocument& document | ||||
|  | ||||
|         // If document is not being reset, then page text should remain the same, | ||||
|         // so, there is no need to clear the results. | ||||
|         if (document.hasReset()) | ||||
|         if (document.hasReset() || document.hasPageContentsChanged()) | ||||
|         { | ||||
|             m_findResults.clear(); | ||||
|             updateUI(); | ||||
|   | ||||
| @@ -292,7 +292,9 @@ void SignaturePlugin::onSignElectronically() | ||||
|             pdf::PDFTextLayoutGetter nullGetter(nullptr, pageIndex); | ||||
|             m_scene.drawPage(painter, pageIndex, nullptr, nullGetter, QMatrix(), errors); | ||||
|             pageContentStreamBuilder.end(painter); | ||||
|             modifier.markPageContentsChanged(); | ||||
|         } | ||||
|         m_scene.clear(); | ||||
|  | ||||
|         if (modifier.finalize()) | ||||
|         { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user