mirror of https://github.com/JakubMelka/PDF4QT.git
Signature plugin: Electronic signature
This commit is contained in:
parent
b2a26ada19
commit
c6cfb4cdc8
|
@ -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())
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue