Signature plugin: Electronic signature

This commit is contained in:
Jakub Melka 2022-04-23 20:16:22 +02:00
parent b2a26ada19
commit c6cfb4cdc8
12 changed files with 190 additions and 14 deletions

View File

@ -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; }

View File

@ -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);

View File

@ -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); }

View File

@ -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
{

View File

@ -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())

View File

@ -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();

View File

@ -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();

View File

@ -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());

View File

@ -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())

View File

@ -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);

View File

@ -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();

View File

@ -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())
{