diff --git a/Pdf4QtDocDiff/app-icon.ico b/Pdf4QtDocDiff/app-icon.ico index e6d9d63..b5a62d6 100644 Binary files a/Pdf4QtDocDiff/app-icon.ico and b/Pdf4QtDocDiff/app-icon.ico differ diff --git a/Pdf4QtDocDiff/app-icon.svg b/Pdf4QtDocDiff/app-icon.svg new file mode 100644 index 0000000..3c05198 --- /dev/null +++ b/Pdf4QtDocDiff/app-icon.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + diff --git a/Pdf4QtLib/Pdf4QtLib.pro b/Pdf4QtLib/Pdf4QtLib.pro index 7df0cd0..2c65d67 100644 --- a/Pdf4QtLib/Pdf4QtLib.pro +++ b/Pdf4QtLib/Pdf4QtLib.pro @@ -15,7 +15,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with PDF4QT. If not, see . -QT += gui widgets xml +QT += gui widgets xml svg TARGET = Pdf4QtLib TEMPLATE = lib @@ -72,6 +72,10 @@ SOURCES += \ sources/pdfoptimizer.cpp \ sources/pdfoptionalcontent.cpp \ sources/pdfoutline.cpp \ + sources/pdfpagecontenteditorstylesettings.cpp \ + sources/pdfpagecontenteditortools.cpp \ + sources/pdfpagecontenteditorwidget.cpp \ + sources/pdfpagecontentelements.cpp \ sources/pdfpagenavigation.cpp \ sources/pdfpagetransition.cpp \ sources/pdfpainterutils.cpp \ @@ -87,6 +91,7 @@ SOURCES += \ sources/pdfsignaturehandler.cpp \ sources/pdfsnapper.cpp \ sources/pdfstructuretree.cpp \ + sources/pdftexteditpseudowidget.cpp \ sources/pdftextlayout.cpp \ sources/pdftransparencyrenderer.cpp \ sources/pdfutils.cpp \ @@ -147,6 +152,10 @@ HEADERS += \ sources/pdfoptimizer.h \ sources/pdfoptionalcontent.h \ sources/pdfoutline.h \ + sources/pdfpagecontenteditorstylesettings.h \ + sources/pdfpagecontenteditortools.h \ + sources/pdfpagecontenteditorwidget.h \ + sources/pdfpagecontentelements.h \ sources/pdfpagenavigation.h \ sources/pdfpagetransition.h \ sources/pdfpainterutils.h \ @@ -165,6 +174,7 @@ HEADERS += \ sources/pdfsignaturehandler_impl.h \ sources/pdfsnapper.h \ sources/pdfstructuretree.h \ + sources/pdftexteditpseudowidget.h \ sources/pdftextlayout.h \ sources/pdftransparencyrenderer.h \ sources/pdfwidgettool.h \ @@ -194,6 +204,8 @@ HEADERS += \ sources/pdfimage.h FORMS += \ + sources/pdfpagecontenteditorstylesettings.ui \ + sources/pdfpagecontenteditorwidget.ui \ sources/pdfrenderingerrorswidget.ui \ sources/pdfselectpagesdialog.ui diff --git a/Pdf4QtLib/sources/pdfadvancedtools.cpp b/Pdf4QtLib/sources/pdfadvancedtools.cpp index e69769a..895f93e 100644 --- a/Pdf4QtLib/sources/pdfadvancedtools.cpp +++ b/Pdf4QtLib/sources/pdfadvancedtools.cpp @@ -659,7 +659,7 @@ void PDFCreateFreehandCurveTool::mousePressEvent(QWidget* widget, QMouseEvent* e resetTool(); } - getProxy()->repaintNeeded(); + emit getProxy()->repaintNeeded(); } void PDFCreateFreehandCurveTool::mouseReleaseEvent(QWidget* widget, QMouseEvent* event) @@ -1063,7 +1063,7 @@ void PDFCreateHighlightTextTool::setSelection(PDFTextSelection&& textSelection) if (m_textSelection != textSelection) { m_textSelection = qMove(textSelection); - getProxy()->repaintNeeded(); + emit getProxy()->repaintNeeded(); } } diff --git a/Pdf4QtLib/sources/pdfadvancedtools.h b/Pdf4QtLib/sources/pdfadvancedtools.h index 888a7d3..16e69a0 100644 --- a/Pdf4QtLib/sources/pdfadvancedtools.h +++ b/Pdf4QtLib/sources/pdfadvancedtools.h @@ -166,7 +166,8 @@ private: public: explicit PDFCreateEllipseTool(PDFDrawWidgetProxy* proxy, PDFToolManager* toolManager, QAction* action, QObject* parent); - virtual void drawPage(QPainter* painter, PDFInteger pageIndex, + virtual void drawPage(QPainter* painter, + PDFInteger pageIndex, const PDFPrecompiledPage* compiledPage, PDFTextLayoutGetter& layoutGetter, const QMatrix& pagePointToDevicePointMatrix, diff --git a/Pdf4QtLib/sources/pdfdocument.h b/Pdf4QtLib/sources/pdfdocument.h index d476ece..3d529bb 100644 --- a/Pdf4QtLib/sources/pdfdocument.h +++ b/Pdf4QtLib/sources/pdfdocument.h @@ -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; } diff --git a/Pdf4QtLib/sources/pdfdocumentbuilder.cpp b/Pdf4QtLib/sources/pdfdocumentbuilder.cpp index a56ebf2..13266f1 100644 --- a/Pdf4QtLib/sources/pdfdocumentbuilder.cpp +++ b/Pdf4QtLib/sources/pdfdocumentbuilder.cpp @@ -22,6 +22,8 @@ #include "pdfobjectutils.h" #include "pdfnametreeloader.h" #include "pdfdbgheap.h" +#include "pdfparser.h" +#include "pdfstreamfilters.h" #include #include @@ -698,14 +700,23 @@ PDFDocument PDFDocumentBuilder::build() return PDFDocument(PDFObjectStorage(m_storage), m_version); } +QByteArray PDFDocumentBuilder::getDecodedStream(const PDFStream* stream) const +{ + return m_storage.getDecodedStream(stream); +} + std::array 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()) }; } -PDFPageContentStreamBuilder::PDFPageContentStreamBuilder(PDFDocumentBuilder* builder) : +PDFPageContentStreamBuilder::PDFPageContentStreamBuilder(PDFDocumentBuilder* builder, + PDFContentStreamBuilder::CoordinateSystem coordinateSystem, + Mode mode) : m_documentBuilder(builder), - m_contentStreamBuilder(nullptr) + m_contentStreamBuilder(nullptr), + m_coordinateSystem(coordinateSystem), + m_mode(mode) { } @@ -733,7 +744,7 @@ QPainter* PDFPageContentStreamBuilder::begin(PDFObjectReference page) } m_pageReference = page; - m_contentStreamBuilder = new PDFContentStreamBuilder(mediaBox.size(), PDFContentStreamBuilder::CoordinateSystem::Qt); + m_contentStreamBuilder = new PDFContentStreamBuilder(mediaBox.size(), m_coordinateSystem); return m_contentStreamBuilder->begin(); } @@ -762,21 +773,223 @@ void PDFPageContentStreamBuilder::end(QPainter* painter) PDFObjectReference resourcesReference = copiedObjects[0].getReference(); PDFObjectReference contentsReference = copiedObjects[1].getReference(); - PDFObjectFactory pageUpdateFactory; + if (m_mode == Mode::Replace) + { + PDFObjectFactory pageUpdateFactory; - pageUpdateFactory.beginDictionary(); + pageUpdateFactory.beginDictionary(); - pageUpdateFactory.beginDictionaryItem("Contents"); - pageUpdateFactory << contentsReference; - pageUpdateFactory.endDictionaryItem(); + pageUpdateFactory.beginDictionaryItem("Contents"); + pageUpdateFactory << contentsReference; + pageUpdateFactory.endDictionaryItem(); - pageUpdateFactory.beginDictionaryItem("Resources"); - pageUpdateFactory << resourcesReference; - pageUpdateFactory.endDictionaryItem(); + pageUpdateFactory.beginDictionaryItem("Resources"); + pageUpdateFactory << resourcesReference; + pageUpdateFactory.endDictionaryItem(); - pageUpdateFactory.endDictionary(); + pageUpdateFactory.endDictionary(); - m_documentBuilder->mergeTo(m_pageReference, pageUpdateFactory.takeObject()); + m_documentBuilder->mergeTo(m_pageReference, pageUpdateFactory.takeObject()); + } + else + { + std::vector contentReferences; + PDFObject pageObject = m_documentBuilder->getObjectByReference(m_pageReference); + + if (pageObject.isDictionary()) + { + const PDFDictionary* pageDictionary = pageObject.getDictionary(); + const PDFObject& oldContents = pageDictionary->get("Contents"); + const PDFObject& oldContentsObject = m_documentBuilder->getObject(oldContents); + + if (oldContentsObject.isStream()) + { + if (oldContents.isReference()) + { + contentReferences.push_back(oldContents.getReference()); + } + } + else if (oldContentsObject.isArray()) + { + const PDFArray* contentsArray = oldContentsObject.getArray(); + for (const PDFObject& object : *contentsArray) + { + if (object.isReference()) + { + contentReferences.push_back(object.getReference()); + } + } + } + + PDFObject oldResourcesObject = pageDictionary->get("Resources"); + replaceResources(contentsReference, resourcesReference, oldResourcesObject); + m_documentBuilder->mergeTo(resourcesReference, m_documentBuilder->getObject(oldResourcesObject)); + } + + switch (m_mode) + { + case Mode::PlaceBefore: + contentReferences.insert(contentReferences.begin(), contentsReference); + break; + + case Mode::PlaceAfter: + contentReferences.push_back(contentsReference); + break; + + default: + Q_ASSERT(false); + break; + } + + PDFObjectFactory pageUpdateFactory; + + pageUpdateFactory.beginDictionary(); + + pageUpdateFactory.beginDictionaryItem("Contents"); + pageUpdateFactory << contentReferences; + pageUpdateFactory.endDictionaryItem(); + + pageUpdateFactory.beginDictionaryItem("Resources"); + pageUpdateFactory << resourcesReference; + pageUpdateFactory.endDictionaryItem(); + + pageUpdateFactory.endDictionary(); + + m_documentBuilder->mergeTo(m_pageReference, pageUpdateFactory.takeObject()); + } +} + +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> 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(qMove(array)))); + PDFObject newContentStream = PDFObject::createStream(std::make_shared(qMove(updatedDictionary), qMove(compressedData))); + m_documentBuilder->setObject(contentStreamReference, std::move(newContentStream)); + } } void PDFDocumentBuilder::updateAnnotationAppearanceStreams(PDFObjectReference annotationReference) @@ -1715,6 +1928,30 @@ PDFObjectReference PDFDocumentBuilder::appendPage(QRectF mediaBox) } +PDFObjectReference PDFDocumentBuilder::createAcroForm(PDFObjectReferenceVector fields) +{ + PDFObjectFactory objectBuilder; + + objectBuilder.beginDictionary(); + objectBuilder.beginDictionaryItem("Fields"); + objectBuilder << fields; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("NeedAppearances"); + objectBuilder << false; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("SigFlags"); + objectBuilder << 0; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("XFA"); + objectBuilder << PDFObject(); + objectBuilder.endDictionaryItem(); + objectBuilder.endDictionary(); + PDFObjectReference acroForm = addObject(objectBuilder.takeObject()); + setCatalogAcroForm(acroForm); + return acroForm; +} + + PDFObjectReference PDFDocumentBuilder::createActionGoTo(PDFDestination destination) { PDFObjectFactory objectBuilder; @@ -2479,88 +2716,6 @@ PDFObjectReference PDFDocumentBuilder::createAnnotationFileAttachment(PDFObjectR } -PDFObjectReference PDFDocumentBuilder::createAnnotationFreeText(PDFObjectReference page, - QRectF boundingRectangle, - QRectF textRectangle, - QString title, - QString subject, - QString contents, - TextAlignment textAlignment, - QPointF startPoint, - QPointF endPoint, - AnnotationLineEnding startLineType, - AnnotationLineEnding endLineType) -{ - PDFObjectFactory objectBuilder; - - objectBuilder.beginDictionary(); - objectBuilder.beginDictionaryItem("Type"); - objectBuilder << WrapName("Annot"); - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("Subtype"); - objectBuilder << WrapName("FreeText"); - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("Rect"); - objectBuilder << boundingRectangle; - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("F"); - objectBuilder << 4; - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("P"); - objectBuilder << page; - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("M"); - objectBuilder << WrapCurrentDateTime(); - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("CreationDate"); - objectBuilder << WrapCurrentDateTime(); - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("T"); - objectBuilder << title; - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("Contents"); - objectBuilder << contents; - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("Subj"); - objectBuilder << subject; - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("Q"); - objectBuilder << WrapFreeTextAlignment(textAlignment); - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("DA"); - objectBuilder << WrapString("/Arial 10 Tf"); - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("RD"); - objectBuilder << getAnnotationReductionRectangle(boundingRectangle, textRectangle); - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("CL"); - objectBuilder.beginArray(); - objectBuilder << startPoint; - objectBuilder << endPoint; - objectBuilder.endArray(); - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("LE"); - objectBuilder.beginArray(); - objectBuilder << startLineType; - objectBuilder << endLineType; - objectBuilder.endArray(); - objectBuilder.endDictionaryItem(); - objectBuilder.endDictionary(); - PDFObjectReference annotationObject = addObject(objectBuilder.takeObject()); - objectBuilder.beginDictionary(); - objectBuilder.beginDictionaryItem("Annots"); - objectBuilder.beginArray(); - objectBuilder << annotationObject; - objectBuilder.endArray(); - objectBuilder.endDictionaryItem(); - objectBuilder.endDictionary(); - PDFObject pageAnnots = objectBuilder.takeObject(); - appendTo(page, pageAnnots); - updateAnnotationAppearanceStreams(annotationObject); - return annotationObject; -} - - PDFObjectReference PDFDocumentBuilder::createAnnotationFreeText(PDFObjectReference page, QRectF boundingRectangle, QRectF textRectangle, @@ -2707,6 +2862,88 @@ PDFObjectReference PDFDocumentBuilder::createAnnotationFreeText(PDFObjectReferen } +PDFObjectReference PDFDocumentBuilder::createAnnotationFreeText(PDFObjectReference page, + QRectF boundingRectangle, + QRectF textRectangle, + QString title, + QString subject, + QString contents, + TextAlignment textAlignment, + QPointF startPoint, + QPointF endPoint, + AnnotationLineEnding startLineType, + AnnotationLineEnding endLineType) +{ + PDFObjectFactory objectBuilder; + + objectBuilder.beginDictionary(); + objectBuilder.beginDictionaryItem("Type"); + objectBuilder << WrapName("Annot"); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Subtype"); + objectBuilder << WrapName("FreeText"); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Rect"); + objectBuilder << boundingRectangle; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("F"); + objectBuilder << 4; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("P"); + objectBuilder << page; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("M"); + objectBuilder << WrapCurrentDateTime(); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("CreationDate"); + objectBuilder << WrapCurrentDateTime(); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("T"); + objectBuilder << title; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Contents"); + objectBuilder << contents; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Subj"); + objectBuilder << subject; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Q"); + objectBuilder << WrapFreeTextAlignment(textAlignment); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("DA"); + objectBuilder << WrapString("/Arial 10 Tf"); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("RD"); + objectBuilder << getAnnotationReductionRectangle(boundingRectangle, textRectangle); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("CL"); + objectBuilder.beginArray(); + objectBuilder << startPoint; + objectBuilder << endPoint; + objectBuilder.endArray(); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("LE"); + objectBuilder.beginArray(); + objectBuilder << startLineType; + objectBuilder << endLineType; + objectBuilder.endArray(); + objectBuilder.endDictionaryItem(); + objectBuilder.endDictionary(); + PDFObjectReference annotationObject = addObject(objectBuilder.takeObject()); + objectBuilder.beginDictionary(); + objectBuilder.beginDictionaryItem("Annots"); + objectBuilder.beginArray(); + objectBuilder << annotationObject; + objectBuilder.endArray(); + objectBuilder.endDictionaryItem(); + objectBuilder.endDictionary(); + PDFObject pageAnnots = objectBuilder.takeObject(); + appendTo(page, pageAnnots); + updateAnnotationAppearanceStreams(annotationObject); + return annotationObject; +} + + PDFObjectReference PDFDocumentBuilder::createAnnotationHighlight(PDFObjectReference page, QRectF rectangle, QColor color, @@ -3632,6 +3869,47 @@ PDFObjectReference PDFDocumentBuilder::createAnnotationSquiggly(PDFObjectReferen } +PDFObjectReference PDFDocumentBuilder::createAnnotationSquiggly(PDFObjectReference page, + QPolygonF quadrilaterals, + QColor color) +{ + PDFObjectFactory objectBuilder; + + objectBuilder.beginDictionary(); + objectBuilder.beginDictionaryItem("Type"); + objectBuilder << WrapName("Annot"); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Subtype"); + objectBuilder << WrapName("Squiggly"); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("P"); + objectBuilder << page; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("CreationDate"); + objectBuilder << WrapCurrentDateTime(); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("C"); + objectBuilder << color; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("QuadPoints"); + objectBuilder << quadrilaterals; + objectBuilder.endDictionaryItem(); + objectBuilder.endDictionary(); + PDFObjectReference annotationObject = addObject(objectBuilder.takeObject()); + objectBuilder.beginDictionary(); + objectBuilder.beginDictionaryItem("Annots"); + objectBuilder.beginArray(); + objectBuilder << annotationObject; + objectBuilder.endArray(); + objectBuilder.endDictionaryItem(); + objectBuilder.endDictionary(); + PDFObject pageAnnots = objectBuilder.takeObject(); + appendTo(page, pageAnnots); + updateAnnotationAppearanceStreams(annotationObject); + return annotationObject; +} + + PDFObjectReference PDFDocumentBuilder::createAnnotationSquiggly(PDFObjectReference page, QRectF rectangle, QColor color, @@ -3696,47 +3974,6 @@ PDFObjectReference PDFDocumentBuilder::createAnnotationSquiggly(PDFObjectReferen } -PDFObjectReference PDFDocumentBuilder::createAnnotationSquiggly(PDFObjectReference page, - QPolygonF quadrilaterals, - QColor color) -{ - PDFObjectFactory objectBuilder; - - objectBuilder.beginDictionary(); - objectBuilder.beginDictionaryItem("Type"); - objectBuilder << WrapName("Annot"); - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("Subtype"); - objectBuilder << WrapName("Squiggly"); - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("P"); - objectBuilder << page; - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("CreationDate"); - objectBuilder << WrapCurrentDateTime(); - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("C"); - objectBuilder << color; - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("QuadPoints"); - objectBuilder << quadrilaterals; - objectBuilder.endDictionaryItem(); - objectBuilder.endDictionary(); - PDFObjectReference annotationObject = addObject(objectBuilder.takeObject()); - objectBuilder.beginDictionary(); - objectBuilder.beginDictionaryItem("Annots"); - objectBuilder.beginArray(); - objectBuilder << annotationObject; - objectBuilder.endArray(); - objectBuilder.endDictionaryItem(); - objectBuilder.endDictionary(); - PDFObject pageAnnots = objectBuilder.takeObject(); - appendTo(page, pageAnnots); - updateAnnotationAppearanceStreams(annotationObject); - return annotationObject; -} - - PDFObjectReference PDFDocumentBuilder::createAnnotationStamp(PDFObjectReference page, QRectF rectangle, Stamp stampType, @@ -4020,70 +4257,6 @@ PDFObjectReference PDFDocumentBuilder::createAnnotationText(PDFObjectReference p } -PDFObjectReference PDFDocumentBuilder::createAnnotationUnderline(PDFObjectReference page, - QRectF rectangle, - QColor color, - QString title, - QString subject, - QString contents) -{ - PDFObjectFactory objectBuilder; - - objectBuilder.beginDictionary(); - objectBuilder.beginDictionaryItem("Type"); - objectBuilder << WrapName("Annot"); - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("Subtype"); - objectBuilder << WrapName("Underline"); - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("Rect"); - objectBuilder << rectangle; - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("P"); - objectBuilder << page; - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("M"); - objectBuilder << WrapCurrentDateTime(); - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("CreationDate"); - objectBuilder << WrapCurrentDateTime(); - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("C"); - objectBuilder << color; - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("T"); - objectBuilder << title; - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("Contents"); - objectBuilder << contents; - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("Subj"); - objectBuilder << subject; - objectBuilder.endDictionaryItem(); - objectBuilder.beginDictionaryItem("QuadPoints"); - objectBuilder.beginArray(); - objectBuilder << rectangle.bottomLeft(); - objectBuilder << rectangle.bottomRight(); - objectBuilder << rectangle.topLeft(); - objectBuilder << rectangle.topRight(); - objectBuilder.endArray(); - objectBuilder.endDictionaryItem(); - objectBuilder.endDictionary(); - PDFObjectReference annotationObject = addObject(objectBuilder.takeObject()); - objectBuilder.beginDictionary(); - objectBuilder.beginDictionaryItem("Annots"); - objectBuilder.beginArray(); - objectBuilder << annotationObject; - objectBuilder.endArray(); - objectBuilder.endDictionaryItem(); - objectBuilder.endDictionary(); - PDFObject pageAnnots = objectBuilder.takeObject(); - appendTo(page, pageAnnots); - updateAnnotationAppearanceStreams(annotationObject); - return annotationObject; -} - - PDFObjectReference PDFDocumentBuilder::createAnnotationUnderline(PDFObjectReference page, QRectF rectangle, QColor color) @@ -4174,6 +4347,70 @@ PDFObjectReference PDFDocumentBuilder::createAnnotationUnderline(PDFObjectRefere } +PDFObjectReference PDFDocumentBuilder::createAnnotationUnderline(PDFObjectReference page, + QRectF rectangle, + QColor color, + QString title, + QString subject, + QString contents) +{ + PDFObjectFactory objectBuilder; + + objectBuilder.beginDictionary(); + objectBuilder.beginDictionaryItem("Type"); + objectBuilder << WrapName("Annot"); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Subtype"); + objectBuilder << WrapName("Underline"); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Rect"); + objectBuilder << rectangle; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("P"); + objectBuilder << page; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("M"); + objectBuilder << WrapCurrentDateTime(); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("CreationDate"); + objectBuilder << WrapCurrentDateTime(); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("C"); + objectBuilder << color; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("T"); + objectBuilder << title; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Contents"); + objectBuilder << contents; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Subj"); + objectBuilder << subject; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("QuadPoints"); + objectBuilder.beginArray(); + objectBuilder << rectangle.bottomLeft(); + objectBuilder << rectangle.bottomRight(); + objectBuilder << rectangle.topLeft(); + objectBuilder << rectangle.topRight(); + objectBuilder.endArray(); + objectBuilder.endDictionaryItem(); + objectBuilder.endDictionary(); + PDFObjectReference annotationObject = addObject(objectBuilder.takeObject()); + objectBuilder.beginDictionary(); + objectBuilder.beginDictionaryItem("Annots"); + objectBuilder.beginArray(); + objectBuilder << annotationObject; + objectBuilder.endArray(); + objectBuilder.endDictionaryItem(); + objectBuilder.endDictionary(); + PDFObject pageAnnots = objectBuilder.takeObject(); + appendTo(page, pageAnnots); + updateAnnotationAppearanceStreams(annotationObject); + return annotationObject; +} + + PDFObjectReference PDFDocumentBuilder::createCatalog() { PDFObjectFactory objectBuilder; @@ -4305,6 +4542,135 @@ PDFObjectReference PDFDocumentBuilder::createFileSpecification(QString fileName) } +PDFObjectReference PDFDocumentBuilder::createFormFieldSignature(QString fieldName, + PDFObjectReferenceVector kids, + PDFObjectReference signatureValue) +{ + PDFObjectFactory objectBuilder; + + objectBuilder.beginDictionary(); + objectBuilder.beginDictionaryItem("FT"); + objectBuilder << WrapName("Sig"); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Kids"); + objectBuilder << kids; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("T"); + objectBuilder << fieldName; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("V"); + objectBuilder << signatureValue; + objectBuilder.endDictionaryItem(); + objectBuilder.endDictionary(); + PDFObjectReference formFieldSignature = addObject(objectBuilder.takeObject()); + return formFieldSignature; +} + + +void PDFDocumentBuilder::createFormFieldWidget(PDFObjectReference formField, + PDFObjectReference page, + PDFObjectReference appearanceStream, + QRectF rect) +{ + PDFObjectFactory objectBuilder; + + objectBuilder.beginDictionary(); + objectBuilder.beginDictionaryItem("Type"); + objectBuilder << WrapName("Annot"); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Subtype"); + objectBuilder << WrapName("Widget"); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("P"); + objectBuilder << page; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Rect"); + objectBuilder << rect; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("AP"); + objectBuilder.beginDictionary(); + objectBuilder.beginDictionaryItem("N"); + objectBuilder << appearanceStream; + objectBuilder.endDictionaryItem(); + objectBuilder.endDictionary(); + objectBuilder.endDictionaryItem(); + objectBuilder.endDictionary(); + PDFObject widgetObject = objectBuilder.takeObject(); + objectBuilder.beginDictionary(); + objectBuilder.beginDictionaryItem("Annots"); + objectBuilder << std::array{ formField }; + objectBuilder.endDictionaryItem(); + objectBuilder.endDictionary(); + PDFObject pageObject = objectBuilder.takeObject(); + mergeTo(formField, widgetObject); + appendTo(page, pageObject); +} + + +void PDFDocumentBuilder::createInvisibleFormFieldWidget(PDFObjectReference formField, + PDFObjectReference page) +{ + PDFObjectFactory objectBuilder; + + objectBuilder.beginDictionary(); + objectBuilder.beginDictionaryItem("P"); + objectBuilder << page; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Rect"); + objectBuilder << std::array{ 0.0, 0.0, 0.0, 0.0 }; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Subtype"); + objectBuilder << WrapName("Widget"); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Type"); + objectBuilder << WrapName("Annot"); + objectBuilder.endDictionaryItem(); + objectBuilder.endDictionary(); + PDFObject widgetObject = objectBuilder.takeObject(); + objectBuilder.beginDictionary(); + objectBuilder.beginDictionaryItem("Annots"); + objectBuilder << std::array{ formField }; + objectBuilder.endDictionaryItem(); + objectBuilder.endDictionary(); + PDFObject pageObject = objectBuilder.takeObject(); + mergeTo(formField, widgetObject); + appendTo(page, pageObject); +} + + +PDFObjectReference PDFDocumentBuilder::createSignatureDictionary(QByteArray filter, + QByteArray subfilter, + QByteArray contents, + QDateTime signingTime, + PDFInteger byteRangeItem) +{ + PDFObjectFactory objectBuilder; + + objectBuilder.beginDictionary(); + objectBuilder.beginDictionaryItem("Type"); + objectBuilder << WrapName("Sig"); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Filter"); + objectBuilder << WrapName(filter); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("SubFilter"); + objectBuilder << WrapName(subfilter); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("ByteRange"); + objectBuilder << std::array{ byteRangeItem, byteRangeItem, byteRangeItem, byteRangeItem }; + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("Contents"); + objectBuilder << WrapString(contents); + objectBuilder.endDictionaryItem(); + objectBuilder.beginDictionaryItem("M"); + objectBuilder << signingTime; + objectBuilder.endDictionaryItem(); + objectBuilder.endDictionary(); + PDFObjectReference signatureDictionary = addObject(objectBuilder.takeObject()); + return signatureDictionary; +} + + PDFObject PDFDocumentBuilder::createTrailerDictionary(PDFObjectReference catalog) { PDFObjectFactory objectBuilder; @@ -4805,6 +5171,14 @@ void PDFDocumentBuilder::setFormFieldValue(PDFObjectReference formField, } +void PDFDocumentBuilder::setLanguage(QLocale locale) +{ + PDFObjectFactory objectBuilder; + + setLanguage(locale.name()); +} + + void PDFDocumentBuilder::setLanguage(QString language) { PDFObjectFactory objectBuilder; @@ -4819,14 +5193,6 @@ void PDFDocumentBuilder::setLanguage(QString language) } -void PDFDocumentBuilder::setLanguage(QLocale locale) -{ - PDFObjectFactory objectBuilder; - - setLanguage(locale.name()); -} - - void PDFDocumentBuilder::setOutline(PDFObjectReference outline) { PDFObjectFactory objectBuilder; @@ -4961,6 +5327,36 @@ void PDFDocumentBuilder::setPageUserUnit(PDFObjectReference page, } +void PDFDocumentBuilder::setSignatureContactInfo(PDFObjectReference signatureDictionary, + QString contactInfoText) +{ + PDFObjectFactory objectBuilder; + + objectBuilder.beginDictionary(); + objectBuilder.beginDictionaryItem("ContactInfo"); + objectBuilder << contactInfoText; + objectBuilder.endDictionaryItem(); + objectBuilder.endDictionary(); + PDFObject updatedSignatureDictionary = objectBuilder.takeObject(); + mergeTo(signatureDictionary, updatedSignatureDictionary); +} + + +void PDFDocumentBuilder::setSignatureReason(PDFObjectReference signatureDictionary, + QString reasonText) +{ + PDFObjectFactory objectBuilder; + + objectBuilder.beginDictionary(); + objectBuilder.beginDictionaryItem("Reason"); + objectBuilder << reasonText; + objectBuilder.endDictionaryItem(); + objectBuilder.endDictionary(); + PDFObject updatedSignatureDictionary = objectBuilder.takeObject(); + mergeTo(signatureDictionary, updatedSignatureDictionary); +} + + void PDFDocumentBuilder::updateTrailerDictionary(PDFInteger objectCount) { PDFObjectFactory objectBuilder; diff --git a/Pdf4QtLib/sources/pdfdocumentbuilder.h b/Pdf4QtLib/sources/pdfdocumentbuilder.h index 845bad1..a7fe950 100644 --- a/Pdf4QtLib/sources/pdfdocumentbuilder.h +++ b/Pdf4QtLib/sources/pdfdocumentbuilder.h @@ -39,6 +39,12 @@ struct WrapName } + WrapName(QByteArray name) : + name(std::move(name)) + { + + } + QByteArray name; }; @@ -86,7 +92,7 @@ struct WrapEmptyArray { }; /// Factory for creating various PDF objects, such as simple objects, /// dictionaries, arrays etc. -class PDFObjectFactory +class PDF4QTLIBSHARED_EXPORT PDFObjectFactory { public: inline explicit PDFObjectFactory() = default; @@ -261,9 +267,20 @@ private: class PDF4QTLIBSHARED_EXPORT PDFPageContentStreamBuilder { public: - PDFPageContentStreamBuilder(PDFDocumentBuilder* builder); - /// Starts painting onto the page. Old page content is erased. This + enum class Mode + { + Replace, + PlaceBefore, + PlaceAfter + }; + + /// Vytvoří nový builder, který vytváří obsah stránek. + PDFPageContentStreamBuilder(PDFDocumentBuilder* builder, + PDFContentStreamBuilder::CoordinateSystem coordinateSystem = PDFContentStreamBuilder::CoordinateSystem::Qt, + Mode mode = Mode::Replace); + + /// Starts painting onto the page. Old page content is erased (in Replace mode). This /// function returns painter, onto which can be graphics drawn. Painter /// uses Qt's coordinate system. Calling begin multiple times, without /// subsequent calls to end function, is invalid and can result @@ -284,9 +301,15 @@ public: void end(QPainter* painter); private: + void replaceResources(PDFObjectReference contentStreamReference, + PDFObjectReference resourcesReference, + PDFObject oldResources); + PDFDocumentBuilder* m_documentBuilder; PDFContentStreamBuilder* m_contentStreamBuilder; PDFObjectReference m_pageReference; + PDFContentStreamBuilder::CoordinateSystem m_coordinateSystem; + Mode m_mode; }; class PDF4QTLIBSHARED_EXPORT PDFDocumentBuilder @@ -328,6 +351,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 getAnnotationReductionRectangle(const QRectF& boundingRect, const QRectF& innerRect) const; @@ -446,6 +474,11 @@ public: PDFObjectReference appendPage(QRectF mediaBox); + /// Creates AcroForm dictionary. Erases XFA form if present. + /// \param fields Fields + PDFObjectReference createAcroForm(PDFObjectReferenceVector fields); + + /// Creates GoTo action. This action changes view to a specific destination in the same document. /// \param destination Destination PDFObjectReference createActionGoTo(PDFDestination destination); @@ -662,36 +695,6 @@ public: QString description); - /// Free text annotation displays text directly on a page. Text appears directly on the page, in the - /// same way, as standard text in PDF document. Free text annotations are usually used to comment - /// the document. Free text annotation can also have callout line, with, or without a knee. Specify - /// start/end point parameters of this function to get callout line. - /// \param page Page to which is annotation added - /// \param boundingRectangle Bounding rectangle of free text annotation. It must contain both - /// callout line and text rectangle. - /// \param textRectangle Rectangle with text, in absolute coordinates. They are then recomputed to - /// match bounding rectangle. - /// \param title Title - /// \param subject Subject - /// \param contents Contents (text displayed) - /// \param textAlignment Text alignment. Only horizontal alignment flags are valid. - /// \param startPoint Start point of callout line - /// \param endPoint End point of callout line - /// \param startLineType Line ending at the start point - /// \param endLineType Line ending at the end point - PDFObjectReference createAnnotationFreeText(PDFObjectReference page, - QRectF boundingRectangle, - QRectF textRectangle, - QString title, - QString subject, - QString contents, - TextAlignment textAlignment, - QPointF startPoint, - QPointF endPoint, - AnnotationLineEnding startLineType, - AnnotationLineEnding endLineType); - - /// Free text annotation displays text directly on a page. Text appears directly on the page, in the /// same way, as standard text in PDF document. Free text annotations are usually used to comment /// the document. Free text annotation can also have callout line, with, or without a knee. Specify @@ -741,6 +744,36 @@ public: TextAlignment textAlignment); + /// Free text annotation displays text directly on a page. Text appears directly on the page, in the + /// same way, as standard text in PDF document. Free text annotations are usually used to comment + /// the document. Free text annotation can also have callout line, with, or without a knee. Specify + /// start/end point parameters of this function to get callout line. + /// \param page Page to which is annotation added + /// \param boundingRectangle Bounding rectangle of free text annotation. It must contain both + /// callout line and text rectangle. + /// \param textRectangle Rectangle with text, in absolute coordinates. They are then recomputed to + /// match bounding rectangle. + /// \param title Title + /// \param subject Subject + /// \param contents Contents (text displayed) + /// \param textAlignment Text alignment. Only horizontal alignment flags are valid. + /// \param startPoint Start point of callout line + /// \param endPoint End point of callout line + /// \param startLineType Line ending at the start point + /// \param endLineType Line ending at the end point + PDFObjectReference createAnnotationFreeText(PDFObjectReference page, + QRectF boundingRectangle, + QRectF textRectangle, + QString title, + QString subject, + QString contents, + TextAlignment textAlignment, + QPointF startPoint, + QPointF endPoint, + AnnotationLineEnding startLineType, + AnnotationLineEnding endLineType); + + /// Text markup annotation is used to highlight text. It is a markup annotation, so it can contain /// window to be opened (and commented). This annotation is usually used to highlight text, but can /// also highlight other things, such as images, or other graphics. @@ -1024,6 +1057,16 @@ public: QColor color); + /// Text markup annotation is used to squiggly underline text. It is a markup annotation, so it can + /// contain window to be opened (and commented). + /// \param page Page to which is annotation added + /// \param quadrilaterals Area in which is markup displayed + /// \param color Color + PDFObjectReference createAnnotationSquiggly(PDFObjectReference page, + QPolygonF quadrilaterals, + QColor color); + + /// Text markup annotation is used to squiggly underline text. It is a markup annotation, so it can /// contain window to be opened (and commented). /// \param page Page to which is annotation added @@ -1040,16 +1083,6 @@ public: QString contents); - /// Text markup annotation is used to squiggly underline text. It is a markup annotation, so it can - /// contain window to be opened (and commented). - /// \param page Page to which is annotation added - /// \param quadrilaterals Area in which is markup displayed - /// \param color Color - PDFObjectReference createAnnotationSquiggly(PDFObjectReference page, - QPolygonF quadrilaterals, - QColor color); - - /// Stamp annotation /// \param page Page to which is annotation added /// \param rectangle Stamp area @@ -1122,22 +1155,6 @@ public: bool open); - /// Text markup annotation is used to underline text. It is a markup annotation, so it can contain - /// window to be opened (and commented). - /// \param page Page to which is annotation added - /// \param rectangle Area in which is markup displayed - /// \param color Color - /// \param title Title - /// \param subject Subject - /// \param contents Contents - PDFObjectReference createAnnotationUnderline(PDFObjectReference page, - QRectF rectangle, - QColor color, - QString title, - QString subject, - QString contents); - - /// Text markup annotation is used to underline text. It is a markup annotation, so it can contain /// window to be opened (and commented). /// \param page Page to which is annotation added @@ -1158,6 +1175,22 @@ public: QColor color); + /// Text markup annotation is used to underline text. It is a markup annotation, so it can contain + /// window to be opened (and commented). + /// \param page Page to which is annotation added + /// \param rectangle Area in which is markup displayed + /// \param color Color + /// \param title Title + /// \param subject Subject + /// \param contents Contents + PDFObjectReference createAnnotationUnderline(PDFObjectReference page, + QRectF rectangle, + QColor color, + QString title, + QString subject, + QString contents); + + /// Creates empty catalog. This function is used, when a new document is being created. Do not call /// this function manually. PDFObjectReference createCatalog(); @@ -1193,6 +1226,48 @@ public: PDFObjectReference createFileSpecification(QString fileName); + /// Creates form field of type signature. + /// \param fieldName Field name + /// \param kids Kids of the signature field. + /// \param signatureValue Signature value + PDFObjectReference createFormFieldSignature(QString fieldName, + PDFObjectReferenceVector kids, + PDFObjectReference signatureValue); + + + /// Creates visible form field widget without contents. + /// \param formField Form field reference + /// \param page Page reference + /// \param appearanceStream Appearance stream + /// \param rect Widget rectangle + void createFormFieldWidget(PDFObjectReference formField, + PDFObjectReference page, + PDFObjectReference appearanceStream, + QRectF rect); + + + /// + /// \param formField Form field reference + /// \param page Page reference + void createInvisibleFormFieldWidget(PDFObjectReference formField, + PDFObjectReference page); + + + /// Creates signature dictionary used for preparation in signing process. Can define parameters of the + /// signature. + /// \param filter Filter (for example, Adobe.PPKLite, Entrust.PPKEF, CiCi.SignIt, ...) + /// \param subfilter Subfilter (for example, adbe.pkcs7.detached, adbe.pkcs7.sha1, + /// ETSI.CAdES.detached, ...) + /// \param contents Contents (reserved data for signature). + /// \param signingTime Signing date/time + /// \param byteRangeItem Item which will fill byte range array. + PDFObjectReference createSignatureDictionary(QByteArray filter, + QByteArray subfilter, + QByteArray contents, + QDateTime signingTime, + PDFInteger byteRangeItem); + + /// This function is used to create a new trailer dictionary, when blank document is created. Do not /// call this function manually. /// \param catalog Reference to document catalog @@ -1393,6 +1468,11 @@ public: PDFObject value); + /// Set document language. + /// \param locale Locale, from which is language determined + void setLanguage(QLocale locale); + + /// Set document language. /// \param language Document language. It should be a language identifier, as defined in ISO 639 /// and ISO 3166. For example, "en-US", where first two letter means language code (en = @@ -1400,11 +1480,6 @@ public: void setLanguage(QString language); - /// Set document language. - /// \param locale Locale, from which is language determined - void setLanguage(QLocale locale); - - /// Set document outline. /// \param outline Document outline root void setOutline(PDFObjectReference outline); @@ -1470,6 +1545,20 @@ public: PDFReal unit); + /// Sets signature contact info field. + /// \param signatureDictionary Signature dictionary reference + /// \param contactInfoText Contact info text + void setSignatureContactInfo(PDFObjectReference signatureDictionary, + QString contactInfoText); + + + /// Sets signature reason field. + /// \param signatureDictionary Signature dictionary reference + /// \param reasonText Reason text + void setSignatureReason(PDFObjectReference signatureDictionary, + QString reasonText); + + /// This function is used to update trailer dictionary. Must be called each time the final document is /// being built. /// \param objectCount Number of objects (including empty ones) @@ -1513,6 +1602,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); } diff --git a/Pdf4QtLib/sources/pdfdocumentwriter.cpp b/Pdf4QtLib/sources/pdfdocumentwriter.cpp index a9e67e0..df1d6fb 100644 --- a/Pdf4QtLib/sources/pdfdocumentwriter.cpp +++ b/Pdf4QtLib/sources/pdfdocumentwriter.cpp @@ -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 { diff --git a/Pdf4QtLib/sources/pdfdrawspacecontroller.cpp b/Pdf4QtLib/sources/pdfdrawspacecontroller.cpp index 9900839..d1bbeb4 100644 --- a/Pdf4QtLib/sources/pdfdrawspacecontroller.cpp +++ b/Pdf4QtLib/sources/pdfdrawspacecontroller.cpp @@ -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()) diff --git a/Pdf4QtLib/sources/pdffont.cpp b/Pdf4QtLib/sources/pdffont.cpp index 98c15aa..d47025c 100644 --- a/Pdf4QtLib/sources/pdffont.cpp +++ b/Pdf4QtLib/sources/pdffont.cpp @@ -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(); diff --git a/Pdf4QtLib/sources/pdfform.cpp b/Pdf4QtLib/sources/pdfform.cpp index 23c64b0..50868dc 100644 --- a/Pdf4QtLib/sources/pdfform.cpp +++ b/Pdf4QtLib/sources/pdfform.cpp @@ -17,6 +17,7 @@ #include "pdfform.h" #include "pdfdocument.h" +#include "pdftexteditpseudowidget.h" #include "pdfdrawspacecontroller.h" #include "pdfdrawwidget.h" #include "pdfdocumentbuilder.h" @@ -33,175 +34,6 @@ namespace pdf { -/// "Pseudo" widget, which is emulating text editor, which can be single line, or multiline. -/// Passwords can also be edited and editor can be read only. -class PDFTextEditPseudowidget -{ -public: - explicit PDFTextEditPseudowidget(PDFFormField::FieldFlags flags); - - void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event); - void keyPressEvent(QWidget* widget, QKeyEvent* event); - - inline bool isReadonly() const { return m_flags.testFlag(PDFFormField::ReadOnly); } - inline bool isMultiline() const { return m_flags.testFlag(PDFFormField::Multiline); } - inline bool isPassword() const { return m_flags.testFlag(PDFFormField::Password); } - inline bool isFileSelect() const { return m_flags.testFlag(PDFFormField::FileSelect); } - inline bool isComb() const { return m_flags.testFlag(PDFFormField::Comb); } - - inline bool isEmpty() const { return m_editText.isEmpty(); } - inline bool isTextSelected() const { return !isEmpty() && getSelectionLength() > 0; } - inline bool isWholeTextSelected() const { return !isEmpty() && getSelectionLength() == m_editText.length(); } - - inline int getTextLength() const { return m_editText.length(); } - inline int getSelectionLength() const { return m_selectionEnd - m_selectionStart; } - - inline int getPositionCursor() const { return m_positionCursor; } - inline int getPositionStart() const { return 0; } - inline int getPositionEnd() const { return getTextLength(); } - - inline void clearSelection() { m_selectionStart = m_selectionEnd = 0; } - - inline const QString& getText() const { return m_editText; } - inline QString getSelectedText() const { return m_editText.mid(m_selectionStart, getSelectionLength()); } - - /// Sets (updates) text selection - /// \param startPosition From where we are selecting text - /// \param selectionLength Selection length (positive - to the right, negative - to the left) - void setSelection(int startPosition, int selectionLength); - - /// Moves cursor position. It behaves as usual in text boxes, - /// when user moves the cursor. If \p select is true, then - /// selection is updated. - /// \param position New position of the cursor - /// \param select Select text when moving the cursor? - void setCursorPosition(int position, bool select); - - /// Sets text content of the widget. This functions sets the text, - /// even if widget is readonly. - /// \param text Text to be set - void setText(const QString& text); - - /// Sets widget appearance, such as font, font size, color, text alignment, - /// and rectangle, in which widget resides on page (in page coordinates) - /// \param appearance Appearance - /// \param textAlignment Text alignment - /// \param rect Widget rectangle in page coordinates - /// \param maxTextLength Maximal text length - void setAppearance(const PDFAnnotationDefaultAppearance& appearance, - Qt::Alignment textAlignment, - QRectF rect, - int maxTextLength); - - void performCut(); - void performCopy(); - void performPaste(); - void performClear(); - void performSelectAll(); - void performBackspace(); - void performDelete(); - void performRemoveSelectedText(); - void performInsertText(const QString& text); - - /// Draw text edit using given parameters - /// \param parameters Parameters - void draw(AnnotationDrawParameters& parameters, bool edit) const; - - /// Returns valid cursor position retrieved from position in the widget. - /// \param point Point in page coordinate space - /// \param edit Are we using edit transformations? - /// \returns Cursor position - int getCursorPositionFromWidgetPosition(const QPointF& point, bool edit) const; - - inline int getCursorForward(QTextLayout::CursorMode mode) const { return getNextPrevCursorPosition(getSingleStepForward(), mode); } - inline int getCursorBackward(QTextLayout::CursorMode mode) const { return getNextPrevCursorPosition(getSingleStepBackward(), mode); } - inline int getCursorCharacterForward() const { return getCursorForward(QTextLayout::SkipCharacters); } - inline int getCursorCharacterBackward() const { return getCursorBackward(QTextLayout::SkipCharacters); } - inline int getCursorWordForward() const { return getCursorForward(QTextLayout::SkipWords); } - inline int getCursorWordBackward() const { return getCursorBackward(QTextLayout::SkipWords); } - inline int getCursorDocumentStart() const { return (getSingleStepForward() > 0) ? getPositionStart() : getPositionEnd(); } - inline int getCursorDocumentEnd() const { return (getSingleStepForward() > 0) ? getPositionEnd() : getPositionStart(); } - inline int getCursorLineStart() const { return (getSingleStepForward() > 0) ? getCurrentLineTextStart() : getCurrentLineTextEnd(); } - inline int getCursorLineEnd() const { return (getSingleStepForward() > 0) ? getCurrentLineTextEnd() : getCurrentLineTextStart(); } - inline int getCursorNextLine() const { return getCurrentLineTextEnd(); } - inline int getCursorPreviousLine() const { return getNextPrevCursorPosition(getCurrentLineTextStart(), -1, QTextLayout::SkipCharacters); } - - const QRectF& getWidgetRect() const { return m_widgetRect; } - QFont getFont() const { return m_textLayout.font(); } - QColor getFontColor() const { return m_textColor; } - -private: - /// This function does following things: - /// 1) Clamps edit text to fit maximum length - /// 2) Creates display string from edit string - /// 3) Updates text layout - void updateTextLayout(); - - /// Returns single step forward, which is determined - /// by cursor move style and layout direction. - int getSingleStepForward() const; - - /// Returns single step backward, which is determined - /// by cursor move style and layout direction. - int getSingleStepBackward() const { return -getSingleStepForward(); } - - /// Returns next/previous position, by number of steps, - /// using given cursor mode (skipping characters or whole words). - /// \param steps Number of steps to proceed (can be negative number) - /// \param mode Skip mode - letters or words? - int getNextPrevCursorPosition(int steps, QTextLayout::CursorMode mode) const { return getNextPrevCursorPosition(m_positionCursor, steps, mode); } - - /// Returns next/previous position from reference cursor position, by number of steps, - /// using given cursor mode (skipping characters or whole words). - /// \param referencePosition Reference cursor position - /// \param steps Number of steps to proceed (can be negative number) - /// \param mode Skip mode - letters or words? - int getNextPrevCursorPosition(int referencePosition, int steps, QTextLayout::CursorMode mode) const; - - /// Returns current line text start position - int getCurrentLineTextStart() const; - - /// Returns current line text end position - int getCurrentLineTextEnd() const; - - /// Creates text box transform matrix, which transforms from - /// widget space to page space. - /// \param edit Create matrix for text editing? - QMatrix createTextBoxTransformMatrix(bool edit) const; - - /// Returns vector of cursor positions (which may be not equal - /// to edit string length, because edit string can contain surrogates, - /// or graphemes, which are single glyphs, but represented by more - /// 16-bit unicode codes). - std::vector getCursorPositions() const; - - int getCursorLineUp() const; - int getCursorLineDown() const; - - PDFFormField::FieldFlags m_flags; - - /// Text edited by the user - QString m_editText; - - /// Text, which is displayed. It can differ from text - /// edited by user, in case password is being entered. - QString m_displayText; - - /// Text layout - QTextLayout m_textLayout; - - /// Character for displaying passwords - QChar m_passwordReplacementCharacter; - - int m_selectionStart; - int m_selectionEnd; - int m_positionCursor; - int m_maxTextLength; - - QRectF m_widgetRect; - QColor m_textColor; -}; - /// Editor for button-like editors class PDFFormFieldAbstractButtonEditor : public PDFFormFieldWidgetEditor { @@ -2643,893 +2475,7 @@ void PDFFormFieldTextBoxEditor::draw(AnnotationDrawParameters& parameters, bool } } -PDFTextEditPseudowidget::PDFTextEditPseudowidget(PDFFormField::FieldFlags flags) : - m_flags(flags), - m_selectionStart(0), - m_selectionEnd(0), - m_positionCursor(0), - m_maxTextLength(0) -{ - m_textLayout.setCacheEnabled(true); - m_passwordReplacementCharacter = QApplication::style()->styleHint(QStyle::SH_LineEdit_PasswordCharacter); -} -void PDFTextEditPseudowidget::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) -{ - Q_UNUSED(widget); - constexpr QKeySequence::StandardKey acceptedKeys[] = { QKeySequence::Delete, QKeySequence::Cut, QKeySequence::Copy, QKeySequence::Paste, - QKeySequence::SelectAll, QKeySequence::MoveToNextChar, QKeySequence::MoveToPreviousChar, - QKeySequence::MoveToNextWord, QKeySequence::MoveToPreviousWord, QKeySequence::MoveToNextLine, - QKeySequence::MoveToPreviousLine, QKeySequence::MoveToStartOfLine, QKeySequence::MoveToEndOfLine, - QKeySequence::MoveToStartOfBlock, QKeySequence::MoveToEndOfBlock, QKeySequence::MoveToStartOfDocument, - QKeySequence::MoveToEndOfDocument, QKeySequence::SelectNextChar, QKeySequence::SelectPreviousChar, - QKeySequence::SelectNextWord, QKeySequence::SelectPreviousWord, QKeySequence::SelectNextLine, - QKeySequence::SelectPreviousLine, QKeySequence::SelectStartOfLine, QKeySequence::SelectEndOfLine, - QKeySequence::SelectStartOfBlock, QKeySequence::SelectEndOfBlock, QKeySequence::SelectStartOfDocument, - QKeySequence::SelectEndOfDocument, QKeySequence::DeleteStartOfWord, QKeySequence::DeleteEndOfWord, - QKeySequence::DeleteEndOfLine, QKeySequence::Deselect, QKeySequence::DeleteCompleteLine, QKeySequence::Backspace }; - - if (std::any_of(std::begin(acceptedKeys), std::end(acceptedKeys), [event](QKeySequence::StandardKey standardKey) { return event == standardKey; })) - { - event->accept(); - return; - } - - switch (event->key()) - { - case Qt::Key_Direction_L: - case Qt::Key_Direction_R: - case Qt::Key_Up: - case Qt::Key_Down: - case Qt::Key_Left: - case Qt::Key_Right: - event->accept(); - break; - - default: - break; - } - - if (!event->text().isEmpty()) - { - event->accept(); - for (const QChar& character : event->text()) - { - if (!character.isPrint()) - { - event->ignore(); - break; - } - } - } -} - -void PDFTextEditPseudowidget::keyPressEvent(QWidget* widget, QKeyEvent* event) -{ - Q_UNUSED(widget); - - /* - We will support following key sequences: - Delete - Cut, - Copy, - Paste, - SelectAll, - MoveToNextChar, - MoveToPreviousChar, - MoveToNextWord, - MoveToPreviousWord, - MoveToNextLine, - MoveToPreviousLine, - MoveToStartOfLine, - MoveToEndOfLine, - MoveToStartOfBlock, - MoveToEndOfBlock, - MoveToStartOfDocument, - MoveToEndOfDocument, - SelectNextChar, - SelectPreviousChar, - SelectNextWord, - SelectPreviousWord, - SelectNextLine, - SelectPreviousLine, - SelectStartOfLine, - SelectEndOfLine, - SelectStartOfBlock, - SelectEndOfBlock, - SelectStartOfDocument, - SelectEndOfDocument, - DeleteStartOfWord, - DeleteEndOfWord, - DeleteEndOfLine, - Deselect, - DeleteCompleteLine, - Backspace, - * */ - - event->accept(); - - if (event == QKeySequence::Delete) - { - performDelete(); - } - else if (event == QKeySequence::Cut) - { - performCut(); - } - else if (event == QKeySequence::Copy) - { - performCopy(); - } - else if (event == QKeySequence::Paste) - { - performPaste(); - } - else if (event == QKeySequence::SelectAll) - { - setSelection(0, getTextLength()); - } - else if (event == QKeySequence::MoveToNextChar) - { - setCursorPosition(getCursorCharacterForward(), false); - } - else if (event == QKeySequence::MoveToPreviousChar) - { - setCursorPosition(getCursorCharacterBackward(), false); - } - else if (event == QKeySequence::MoveToNextWord) - { - setCursorPosition(getCursorWordForward(), false); - } - else if (event == QKeySequence::MoveToPreviousWord) - { - setCursorPosition(getCursorWordBackward(), false); - } - else if (event == QKeySequence::MoveToNextLine) - { - setCursorPosition(getCursorLineDown(), false); - } - else if (event == QKeySequence::MoveToPreviousLine) - { - setCursorPosition(getCursorLineUp(), false); - } - else if (event == QKeySequence::MoveToStartOfLine || event == QKeySequence::MoveToStartOfBlock) - { - setCursorPosition(getCursorLineStart(), false); - } - else if (event == QKeySequence::MoveToEndOfLine || event == QKeySequence::MoveToEndOfBlock) - { - setCursorPosition(getCursorLineEnd(), false); - } - else if (event == QKeySequence::MoveToStartOfDocument) - { - setCursorPosition(getCursorDocumentStart(), false); - } - else if (event == QKeySequence::MoveToEndOfDocument) - { - setCursorPosition(getCursorDocumentEnd(), false); - } - else if (event == QKeySequence::SelectNextChar) - { - setCursorPosition(getCursorCharacterForward(), true); - } - else if (event == QKeySequence::SelectPreviousChar) - { - setCursorPosition(getCursorCharacterBackward(), true); - } - else if (event == QKeySequence::SelectNextWord) - { - setCursorPosition(getCursorWordForward(), true); - } - else if (event == QKeySequence::SelectPreviousWord) - { - setCursorPosition(getCursorWordBackward(), true); - } - else if (event == QKeySequence::SelectNextLine) - { - setCursorPosition(getCursorLineDown(), true); - } - else if (event == QKeySequence::SelectPreviousLine) - { - setCursorPosition(getCursorLineUp(), true); - } - else if (event == QKeySequence::SelectStartOfLine || event == QKeySequence::SelectStartOfBlock) - { - setCursorPosition(getCursorLineStart(), true); - } - else if (event == QKeySequence::SelectEndOfLine || event == QKeySequence::SelectEndOfBlock) - { - setCursorPosition(getCursorLineEnd(), true); - } - else if (event == QKeySequence::SelectStartOfDocument) - { - setCursorPosition(getCursorDocumentStart(), true); - } - else if (event == QKeySequence::SelectEndOfDocument) - { - setCursorPosition(getCursorDocumentEnd(), true); - } - else if (event == QKeySequence::DeleteStartOfWord) - { - if (!isReadonly()) - { - setCursorPosition(getCursorWordBackward(), true); - performRemoveSelectedText(); - } - } - else if (event == QKeySequence::DeleteEndOfWord) - { - if (!isReadonly()) - { - setCursorPosition(getCursorWordForward(), true); - performRemoveSelectedText(); - } - } - else if (event == QKeySequence::DeleteEndOfLine) - { - if (!isReadonly()) - { - setCursorPosition(getCursorLineEnd(), true); - performRemoveSelectedText(); - } - } - else if (event == QKeySequence::Deselect) - { - clearSelection(); - } - else if (event == QKeySequence::DeleteCompleteLine) - { - if (!isReadonly()) - { - m_selectionStart = getCurrentLineTextStart(); - m_selectionEnd = getCurrentLineTextEnd(); - performRemoveSelectedText(); - } - } - else if (event == QKeySequence::Backspace || event->key() == Qt::Key_Backspace) - { - performBackspace(); - } - else if (event->key() == Qt::Key_Direction_L) - { - QTextOption option = m_textLayout.textOption(); - option.setTextDirection(Qt::LeftToRight); - m_textLayout.setTextOption(qMove(option)); - updateTextLayout(); - } - else if (event->key() == Qt::Key_Direction_R) - { - QTextOption option = m_textLayout.textOption(); - option.setTextDirection(Qt::LeftToRight); - m_textLayout.setTextOption(qMove(option)); - updateTextLayout(); - } - else if (event->key() == Qt::Key_Up) - { - setCursorPosition(getCursorLineUp(), event->modifiers().testFlag(Qt::ShiftModifier)); - } - else if (event->key() == Qt::Key_Down) - { - setCursorPosition(getCursorLineDown(), event->modifiers().testFlag(Qt::ShiftModifier)); - } - else if (event->key() == Qt::Key_Left) - { - const int position = (event->modifiers().testFlag(Qt::ControlModifier)) ? getCursorWordBackward() : getCursorCharacterBackward(); - setCursorPosition(position, event->modifiers().testFlag(Qt::ShiftModifier)); - } - else if (event->key() == Qt::Key_Right) - { - const int position = (event->modifiers().testFlag(Qt::ControlModifier)) ? getCursorWordForward() : getCursorCharacterForward(); - setCursorPosition(position, event->modifiers().testFlag(Qt::ShiftModifier)); - } - else if (isMultiline() && (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return)) - { - performInsertText(QString::fromUtf16(u"\u2028")); - } - else - { - QString text = event->text(); - if (!text.isEmpty()) - { - performInsertText(text); - } - else - { - event->ignore(); - } - } -} - -void PDFTextEditPseudowidget::setSelection(int startPosition, int selectionLength) -{ - if (selectionLength > 0) - { - // We are selecting to the right - m_selectionStart = startPosition; - m_selectionEnd = qMin(startPosition + selectionLength, getTextLength()); - m_positionCursor = m_selectionEnd; - } - else if (selectionLength < 0) - { - // We are selecting to the left - m_selectionStart = qMax(startPosition + selectionLength, 0); - m_selectionEnd = startPosition; - m_positionCursor = m_selectionStart; - } - else - { - // Clear the selection - m_selectionStart = 0; - m_selectionEnd = 0; - m_positionCursor = startPosition; - } -} - -void PDFTextEditPseudowidget::setCursorPosition(int position, bool select) -{ - if (select) - { - const bool isTextSelected = this->isTextSelected(); - const bool isCursorAtStartOfSelection = isTextSelected && m_selectionStart == m_positionCursor; - const bool isCursorAtEndOfSelection = isTextSelected && m_selectionEnd == m_positionCursor; - - // Do we have selected text, and cursor is at the end of selected text? - // In this case, we must preserve start of the selection (we are manipulating - // with the end of selection. - if (isCursorAtEndOfSelection) - { - m_selectionStart = qMin(m_selectionStart, position); - m_selectionEnd = qMax(m_selectionStart, position); - } - else if (isCursorAtStartOfSelection) - { - // We must preserve end of the text selection, because we are manipulating - // with start of text selection. - m_selectionStart = qMin(m_selectionEnd, position); - m_selectionEnd = qMax(m_selectionEnd, position); - } - else - { - // Otherwise we are manipulating with cursor - m_selectionStart = qMin(m_positionCursor, position); - m_selectionEnd = qMax(m_positionCursor, position); - } - } - - // Why we are clearing text selection, even if we doesn't have it? - // We can have, for example m_selectionStart == m_selectionEnd == 3, - // and we want to have it both zero. - if (!select || !isTextSelected()) - { - clearSelection(); - } - - m_positionCursor = position; -} - -void PDFTextEditPseudowidget::setText(const QString& text) -{ - clearSelection(); - m_editText = text; - setCursorPosition(getPositionEnd(), false); - updateTextLayout(); -} - -void PDFTextEditPseudowidget::setAppearance(const PDFAnnotationDefaultAppearance& appearance, Qt::Alignment textAlignment, QRectF rect, int maxTextLength) -{ - // Set appearance - qreal fontSize = appearance.getFontSize(); - if (qFuzzyIsNull(fontSize)) - { - fontSize = rect.height(); - } - - QFont font(appearance.getFontName()); - font.setHintingPreference(QFont::PreferNoHinting); - font.setPixelSize(qCeil(fontSize)); - font.setStyleStrategy(QFont::ForceOutline); - m_textLayout.setFont(font); - - QTextOption option = m_textLayout.textOption(); - option.setWrapMode(isMultiline() ? QTextOption::WrapAtWordBoundaryOrAnywhere : QTextOption::NoWrap); - option.setAlignment(textAlignment); - option.setUseDesignMetrics(true); - m_textLayout.setTextOption(option); - - m_textColor = appearance.getFontColor(); - if (!m_textColor.isValid()) - { - m_textColor = Qt::black; - } - - m_maxTextLength = maxTextLength; - m_widgetRect = rect; -} - -void PDFTextEditPseudowidget::performCut() -{ - if (isReadonly()) - { - return; - } - - performCopy(); - performRemoveSelectedText(); -} - -void PDFTextEditPseudowidget::performCopy() -{ - if (isTextSelected() && !isPassword()) - { - QApplication::clipboard()->setText(getSelectedText(), QClipboard::Clipboard); - } -} - -void PDFTextEditPseudowidget::performPaste() -{ - // We always insert text, even if it is empty. Because we want - // to erase selected text. - performInsertText(QApplication::clipboard()->text(QClipboard::Clipboard)); -} - -void PDFTextEditPseudowidget::performClear() -{ - if (isReadonly()) - { - return; - } - - performSelectAll(); - performRemoveSelectedText(); -} - -void PDFTextEditPseudowidget::performSelectAll() -{ - m_selectionStart = getPositionStart(); - m_selectionEnd = getPositionEnd(); -} - -void PDFTextEditPseudowidget::performBackspace() -{ - if (isReadonly()) - { - return; - } - - // If we have selection, then delete selected text. If we do not have - // selection, then we delete previous character. - if (!isTextSelected()) - { - setCursorPosition(m_textLayout.previousCursorPosition(m_positionCursor, QTextLayout::SkipCharacters), true); - } - - performRemoveSelectedText(); -} - -void PDFTextEditPseudowidget::performDelete() -{ - if (isReadonly()) - { - return; - } - - // If we have selection, then delete selected text. If we do not have - // selection, then we delete previous character. - if (!isTextSelected()) - { - setCursorPosition(m_textLayout.nextCursorPosition(m_positionCursor, QTextLayout::SkipCharacters), true); - } - - performRemoveSelectedText(); -} - -void PDFTextEditPseudowidget::performRemoveSelectedText() -{ - if (isTextSelected()) - { - m_editText.remove(m_selectionStart, getSelectionLength()); - setCursorPosition(m_selectionStart, false); - clearSelection(); - updateTextLayout(); - } -} - -void PDFTextEditPseudowidget::performInsertText(const QString& text) -{ - if (isReadonly()) - { - return; - } - - // Insert text at the cursor - performRemoveSelectedText(); - m_editText.insert(m_positionCursor, text); - setCursorPosition(m_positionCursor + text.length(), false); - updateTextLayout(); -} - -QMatrix PDFTextEditPseudowidget::createTextBoxTransformMatrix(bool edit) const -{ - QMatrix matrix; - - matrix.translate(m_widgetRect.left(), m_widgetRect.bottom()); - matrix.scale(1.0, -1.0); - - if (edit && !isComb() && m_textLayout.isValidCursorPosition(m_positionCursor)) - { - // Jakub Melka: we must scroll the control, so cursor is always - // visible in the widget area. If we are not editing, this not the - // case, because we always show the text from the beginning. - - const QTextLine line = m_textLayout.lineForTextPosition(m_positionCursor); - if (line.isValid()) - { - const qreal xCursorPosition = line.cursorToX(m_positionCursor); - if (xCursorPosition >= m_widgetRect.width()) - { - const qreal delta = xCursorPosition - m_widgetRect.width(); - matrix.translate(-delta, 0.0); - } - - // Check, if we aren't outside of y position - const qreal lineSpacing = line.leadingIncluded() ? line.height() : line.leading() + line.height(); - const qreal lineBottom = lineSpacing * (line.lineNumber() + 1); - - if (lineBottom >= m_widgetRect.height()) - { - const qreal delta = lineBottom - m_widgetRect.height(); - matrix.translate(0.0, -delta); - } - } - } - - if (!isMultiline() && !isComb()) - { - // If text is single line, then adjust text position to the vertical center - QTextLine textLine = m_textLayout.lineAt(0); - if (textLine.isValid()) - { - const qreal lineSpacing = textLine.leadingIncluded() ? textLine.height() : textLine.leading() + textLine.height(); - const qreal textBoxHeight = m_widgetRect.height(); - - if (lineSpacing < textBoxHeight) - { - const qreal delta = (textBoxHeight - lineSpacing) * 0.5; - matrix.translate(0.0, delta); - } - } - } - - return matrix; -} - -std::vector PDFTextEditPseudowidget::getCursorPositions() const -{ - std::vector result; - result.reserve(m_editText.length()); - result.push_back(0); - - int currentPos = 0; - while (currentPos < m_editText.length()) - { - currentPos = m_textLayout.nextCursorPosition(currentPos, QTextLayout::SkipCharacters); - result.push_back(currentPos); - } - - return result; -} - -void PDFTextEditPseudowidget::draw(AnnotationDrawParameters& parameters, bool edit) const -{ - pdf::PDFPainterStateGuard guard(parameters.painter); - parameters.boundingRectangle = parameters.annotation->getRectangle(); - - QPalette palette = QApplication::palette(); - - auto getAdjustedColor = [¶meters](QColor color) - { - if (parameters.invertColors) - { - return invertColor(color); - } - - return color; - }; - - QPainter* painter = parameters.painter; - - if (edit) - { - pdf::PDFPainterStateGuard guard(painter); - painter->setPen(getAdjustedColor(Qt::black)); - painter->setBrush(Qt::NoBrush); - painter->drawRect(parameters.boundingRectangle); - } - - painter->setClipRect(parameters.boundingRectangle, Qt::IntersectClip); - painter->setWorldMatrix(createTextBoxTransformMatrix(edit), true); - painter->setPen(getAdjustedColor(Qt::black)); - - if (isComb()) - { - const qreal combCount = qMax(m_maxTextLength, 1); - qreal combWidth = m_widgetRect.width() / combCount; - QRectF combRect(0.0, 0.0, combWidth, m_widgetRect.height()); - painter->setFont(m_textLayout.font()); - - QColor textColor = getAdjustedColor(m_textColor); - QColor highlightTextColor = getAdjustedColor(palette.color(QPalette::HighlightedText)); - QColor highlightColor = getAdjustedColor(palette.color(QPalette::Highlight)); - - std::vector positions = getCursorPositions(); - for (size_t i = 1; i < positions.size(); ++i) - { - if (positions[i - 1] >= m_selectionStart && positions[i] - 1 < m_selectionEnd) - { - // We are in text selection - painter->fillRect(combRect, highlightColor); - painter->setPen(highlightTextColor); - } - else - { - // We are not in text selection - painter->setPen(textColor); - } - - int length = positions[i] - positions[i - 1]; - QString text = m_displayText.mid(positions[i - 1], length); - painter->drawText(combRect, Qt::AlignCenter, text); - - // Draw the cursor? - if (edit && m_positionCursor >= positions[i - 1] && m_positionCursor < positions[i]) - { - QRectF cursorRect(combRect.left(), combRect.bottom() - 1, combRect.width(), 1); - painter->fillRect(cursorRect, textColor); - } - - combRect.translate(combWidth, 0.0); - } - - // Draw the cursor onto next unfilled cell? - if (edit && m_positionCursor == getPositionEnd()) - { - QRectF cursorRect(combRect.left(), combRect.bottom() - 1, combRect.width(), 1); - painter->fillRect(cursorRect, textColor); - } - } - else - { - QVector selections; - - QTextLayout::FormatRange defaultFormat; - defaultFormat.start = getPositionStart(); - defaultFormat.length = getTextLength(); - defaultFormat.format.clearBackground(); - defaultFormat.format.setForeground(QBrush(getAdjustedColor(m_textColor), Qt::SolidPattern)); - - // If we are editing, draw selections - if (edit && isTextSelected()) - { - QTextLayout::FormatRange before = defaultFormat; - QTextLayout::FormatRange after = defaultFormat; - - before.start = getPositionStart(); - before.length = m_selectionStart; - after.start = m_selectionEnd; - after.length = getTextLength() - m_selectionEnd; - - QTextLayout::FormatRange selectedFormat = defaultFormat; - selectedFormat.start = m_selectionStart; - selectedFormat.length = getSelectionLength(); - selectedFormat.format.setForeground(QBrush(getAdjustedColor(palette.color(QPalette::HighlightedText)), Qt::SolidPattern)); - selectedFormat.format.setBackground(QBrush(getAdjustedColor(palette.color(QPalette::Highlight)), Qt::SolidPattern)); - - selections = { before, selectedFormat, after}; - } - else - { - selections.push_back(defaultFormat); - } - - // Draw text - m_textLayout.draw(painter, QPointF(0.0, 0.0), selections, QRectF()); - - // If we are editing, also draw text - if (edit && !isReadonly()) - { - m_textLayout.drawCursor(painter, QPointF(0.0, 0.0), m_positionCursor); - } - } -} - -int PDFTextEditPseudowidget::getCursorPositionFromWidgetPosition(const QPointF& point, bool edit) const -{ - QMatrix textBoxSpaceToPageSpace = createTextBoxTransformMatrix(edit); - QMatrix pageSpaceToTextBoxSpace = textBoxSpaceToPageSpace.inverted(); - - QPointF textBoxPoint = pageSpaceToTextBoxSpace.map(point); - - if (isComb()) - { - // If it is comb, then characters are spaced equidistantly - const qreal x = qBound(0.0, textBoxPoint.x(), m_widgetRect.width()); - const size_t position = qFloor(x * qreal(m_maxTextLength) / qreal(m_widgetRect.width())); - std::vector positions = getCursorPositions(); - if (position < positions.size()) - { - return positions[position]; - } - - return positions.back(); - } - else if (m_textLayout.lineCount() > 0) - { - QTextLine line; - qreal yPos = 0.0; - - // Find line under cursor - for (int i = 0; i < m_textLayout.lineCount(); ++i) - { - QTextLine currentLine = m_textLayout.lineAt(i); - const qreal lineSpacing = currentLine.leadingIncluded() ? currentLine.height() : currentLine.leading() + currentLine.height(); - const qreal yNextPos = yPos + lineSpacing; - - if (textBoxPoint.y() >= yPos && textBoxPoint.y() < yNextPos) - { - line = currentLine; - break; - } - - yPos = yNextPos; - } - - // If no line is found, select last line - if (!line.isValid()) - { - if (textBoxPoint.y() < 0.0) - { - line = m_textLayout.lineAt(0); - } - else - { - line = m_textLayout.lineAt(m_textLayout.lineCount() - 1); - } - } - - return line.xToCursor(textBoxPoint.x(), QTextLine::CursorBetweenCharacters); - } - - return 0; -} - -void PDFTextEditPseudowidget::updateTextLayout() -{ - // Prepare display text - if (isPassword()) - { - m_displayText.resize(m_editText.length(), m_passwordReplacementCharacter); - } - else - { - m_displayText = m_editText; - } - - // Perform text layout - m_textLayout.clearLayout(); - m_textLayout.setText(m_displayText); - m_textLayout.beginLayout(); - - QPointF textLinePosition(0.0, 0.0); - - while (true) - { - QTextLine textLine = m_textLayout.createLine(); - if (!textLine.isValid()) - { - // We are finished with layout - break; - } - - textLinePosition.ry() += textLine.leading(); - textLine.setLineWidth(m_widgetRect.width()); - textLine.setPosition(textLinePosition); - textLinePosition.ry() += textLine.height(); - } - m_textLayout.endLayout(); - - // Check length - if (m_maxTextLength > 0) - { - int length = 0; - int currentPos = 0; - - while (currentPos < m_editText.length()) - { - currentPos = m_textLayout.nextCursorPosition(currentPos, QTextLayout::SkipCharacters); - ++length; - - if (length == m_maxTextLength) - { - break; - } - } - - if (currentPos < m_editText.length()) - { - m_editText = m_editText.left(currentPos); - m_positionCursor = qBound(getPositionStart(), m_positionCursor, getPositionEnd()); - updateTextLayout(); - } - } -} - -int PDFTextEditPseudowidget::getSingleStepForward() const -{ - // If direction is right-to-left, then move backward (because - // text is painted from right to left. - return (m_textLayout.textOption().textDirection() == Qt::RightToLeft) ? -1 : 1; -} - -int PDFTextEditPseudowidget::getNextPrevCursorPosition(int referencePosition, int steps, QTextLayout::CursorMode mode) const -{ - int cursor = referencePosition; - - if (steps > 0) - { - for (int i = 0; i < steps; ++i) - { - cursor = m_textLayout.nextCursorPosition(cursor, mode); - } - } - else if (steps < 0) - { - for (int i = 0; i < -steps; ++i) - { - cursor = m_textLayout.previousCursorPosition(cursor, mode); - } - } - - return cursor; -} - -int PDFTextEditPseudowidget::getCurrentLineTextStart() const -{ - return m_textLayout.lineForTextPosition(m_positionCursor).textStart(); -} - -int PDFTextEditPseudowidget::getCurrentLineTextEnd() const -{ - QTextLine textLine = m_textLayout.lineForTextPosition(m_positionCursor); - return textLine.textStart() + textLine.textLength(); -} - -int PDFTextEditPseudowidget::getCursorLineUp() const -{ - QTextLine line = m_textLayout.lineForTextPosition(m_positionCursor); - const int lineIndex = line.lineNumber() - 1; - - if (lineIndex >= 0) - { - QTextLine upLine = m_textLayout.lineAt(lineIndex); - return upLine.xToCursor(line.cursorToX(m_positionCursor), QTextLine::CursorBetweenCharacters); - } - - return m_positionCursor; -} - -int PDFTextEditPseudowidget::getCursorLineDown() const -{ - QTextLine line = m_textLayout.lineForTextPosition(m_positionCursor); - const int lineIndex = line.lineNumber() + 1; - - if (lineIndex < m_textLayout.lineCount()) - { - QTextLine downLine = m_textLayout.lineAt(lineIndex); - return downLine.xToCursor(line.cursorToX(m_positionCursor), QTextLine::CursorBetweenCharacters); - } - - return m_positionCursor; -} bool PDFFormFieldText::setValue(const SetValueParameters& parameters) { diff --git a/Pdf4QtLib/sources/pdfform.h b/Pdf4QtLib/sources/pdfform.h index 0297edb..8c40a96 100644 --- a/Pdf4QtLib/sources/pdfform.h +++ b/Pdf4QtLib/sources/pdfform.h @@ -1,4 +1,4 @@ -// Copyright (C) 2020-2021 Jakub Melka +// Copyright (C) 2020-2022 Jakub Melka // // This file is part of PDF4QT. // diff --git a/Pdf4QtLib/sources/pdfitemmodels.cpp b/Pdf4QtLib/sources/pdfitemmodels.cpp index 279373e..2a89f40 100644 --- a/Pdf4QtLib/sources/pdfitemmodels.cpp +++ b/Pdf4QtLib/sources/pdfitemmodels.cpp @@ -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(); diff --git a/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.cpp b/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.cpp new file mode 100644 index 0000000..7ec14cd --- /dev/null +++ b/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.cpp @@ -0,0 +1,548 @@ +// Copyright (C) 2022 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#include "pdfpagecontenteditorstylesettings.h" +#include "ui_pdfpagecontenteditorstylesettings.h" + +#include "pdfwidgetutils.h" +#include "pdfpagecontentelements.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace pdf +{ + +PDFPageContentEditorStyleSettings::PDFPageContentEditorStyleSettings(QWidget* parent) : + QWidget(parent), + ui(new Ui::PDFPageContentEditorStyleSettings) +{ + ui->setupUi(this); + + for (QString colorName : QColor::colorNames()) + { + QColor color(colorName); + QIcon icon = getIconForColor(color); + + ui->penColorCombo->addItem(icon, colorName, color); + ui->brushColorCombo->addItem(icon, colorName, color); + } + + ui->penStyleCombo->addItem(tr("None"), int(Qt::NoPen)); + ui->penStyleCombo->addItem(tr("Solid"), int(Qt::SolidLine)); + ui->penStyleCombo->addItem(tr("Dashed"), int(Qt::DashLine)); + ui->penStyleCombo->addItem(tr("Dotted"), int(Qt::DotLine)); + ui->penStyleCombo->addItem(tr("Dash-dot"), int(Qt::DashDotLine)); + ui->penStyleCombo->addItem(tr("Dash-dot-dot"), int(Qt::DashDotDotLine)); + + ui->brushStyleCombo->addItem(tr("None"), int(Qt::NoBrush)); + ui->brushStyleCombo->addItem(tr("Solid"), int(Qt::SolidPattern)); + ui->brushStyleCombo->addItem(tr("Horizontal"), int(Qt::HorPattern)); + ui->brushStyleCombo->addItem(tr("Vertical"), int(Qt::VerPattern)); + ui->brushStyleCombo->addItem(tr("B-Diagonal"), int(Qt::BDiagPattern)); + ui->brushStyleCombo->addItem(tr("F-Diagonal"), int(Qt::FDiagPattern)); + ui->brushStyleCombo->addItem(tr("Cross"), int(Qt::CrossPattern)); + + connect(ui->fontComboBox, &QFontComboBox::currentFontChanged, this, &PDFPageContentEditorStyleSettings::onFontChanged); + connect(ui->selectPenColorButton, &QToolButton::clicked, this, &PDFPageContentEditorStyleSettings::onSelectPenColorButtonClicked); + connect(ui->selectBrushColorButton, &QToolButton::clicked, this, &PDFPageContentEditorStyleSettings::onSelectBrushColorButtonClicked); + connect(ui->selectFontButton, &QToolButton::clicked, this, &PDFPageContentEditorStyleSettings::onSelectFontButtonClicked); + connect(ui->penWidthEdit, QOverload::of(&QDoubleSpinBox::valueChanged), this, &PDFPageContentEditorStyleSettings::onPenWidthChanged); + connect(ui->penStyleCombo, QOverload::of(&QComboBox::currentIndexChanged), this, &PDFPageContentEditorStyleSettings::onPenStyleChanged); + connect(ui->brushStyleCombo, QOverload::of(&QComboBox::currentIndexChanged), this, &PDFPageContentEditorStyleSettings::onBrushStyleChanged); + connect(ui->textAngleEdit, QOverload::of(&QDoubleSpinBox::valueChanged), this, &PDFPageContentEditorStyleSettings::onTextAngleChanged); + connect(ui->penColorCombo->lineEdit(), &QLineEdit::editingFinished, this, &PDFPageContentEditorStyleSettings::onPenColorComboTextChanged); + connect(ui->penColorCombo, QOverload::of(&QComboBox::currentIndexChanged), this, &PDFPageContentEditorStyleSettings::onPenColorComboIndexChanged); + connect(ui->brushColorCombo->lineEdit(), &QLineEdit::editingFinished, this, &PDFPageContentEditorStyleSettings::onBrushColorComboTextChanged); + connect(ui->brushColorCombo, QOverload::of(&QComboBox::currentIndexChanged), this, &PDFPageContentEditorStyleSettings::onBrushColorComboIndexChanged); + + m_alignmentMapper.setMapping(ui->al11Button, int(Qt::AlignLeft | Qt::AlignTop)); + m_alignmentMapper.setMapping(ui->al12Button, int(Qt::AlignHCenter | Qt::AlignTop)); + m_alignmentMapper.setMapping(ui->al13Button, int(Qt::AlignRight | Qt::AlignTop)); + m_alignmentMapper.setMapping(ui->al21Button, int(Qt::AlignLeft | Qt::AlignVCenter)); + m_alignmentMapper.setMapping(ui->al22Button, int(Qt::AlignCenter)); + m_alignmentMapper.setMapping(ui->al23Button, int(Qt::AlignRight | Qt::AlignVCenter)); + m_alignmentMapper.setMapping(ui->al31Button, int(Qt::AlignLeft | Qt::AlignBottom)); + m_alignmentMapper.setMapping(ui->al32Button, int(Qt::AlignHCenter | Qt::AlignBottom)); + m_alignmentMapper.setMapping(ui->al33Button, int(Qt::AlignRight | Qt::AlignBottom)); + + for (QRadioButton* radioButton : findChildren()) + { + connect(radioButton, &QRadioButton::clicked, &m_alignmentMapper, QOverload<>::of(&QSignalMapper::map)); + } + connect(&m_alignmentMapper, &QSignalMapper::mappedInt, this, &PDFPageContentEditorStyleSettings::onAlignmentRadioButtonClicked); + + loadFromElement(nullptr, true); +} + +PDFPageContentEditorStyleSettings::~PDFPageContentEditorStyleSettings() +{ + delete ui; +} + +void PDFPageContentEditorStyleSettings::loadFromElement(const PDFPageContentElement* element, bool forceUpdate) +{ + const PDFPageContentStyledElement* styledElement = dynamic_cast(element); + const PDFPageContentElementTextBox* textElement = dynamic_cast(element); + + StyleFeatures features = None; + + if (styledElement) + { + features.setFlag(Pen); + features.setFlag(PenColor); + features.setFlag(Brush); + } + + if (textElement) + { + features.setFlag(PenColor); + features.setFlag(Text); + } + + const bool hasPen = features.testFlag(Pen); + const bool hasPenColor = features.testFlag(PenColor); + const bool hasBrush = features.testFlag(Brush); + const bool hasText = features.testFlag(Text); + + ui->penWidthEdit->setEnabled(hasPen); + ui->penWidthLabel->setEnabled(hasPen); + + ui->penStyleCombo->setEnabled(hasPen); + ui->penStyleLabel->setEnabled(hasPen); + + ui->penColorCombo->setEnabled(hasPenColor); + ui->penColorLabel->setEnabled(hasPenColor); + ui->selectPenColorButton->setEnabled(hasPenColor); + + ui->brushStyleLabel->setEnabled(hasBrush); + ui->brushStyleCombo->setEnabled(hasBrush); + + ui->brushColorCombo->setEnabled(hasBrush); + ui->brushColorLabel->setEnabled(hasBrush); + ui->selectBrushColorButton->setEnabled(hasBrush); + + ui->fontComboBox->setEnabled(hasText); + ui->fontLabel->setEnabled(hasText); + ui->selectFontButton->setEnabled(hasText); + + for (QRadioButton* radioButton : findChildren()) + { + radioButton->setEnabled(hasText); + } + ui->textAlignmentLabel->setEnabled(hasText); + + ui->textAngleLabel->setEnabled(hasText); + ui->textAngleEdit->setEnabled(hasText); + + QPen pen(Qt::SolidLine); + QBrush brush(Qt::transparent); + QFont font = QGuiApplication::font(); + Qt::Alignment alignment = Qt::AlignCenter; + PDFReal textAngle = 0.0; + + if (styledElement) + { + pen = styledElement->getPen(); + brush = styledElement->getBrush(); + } + + if (textElement) + { + font = textElement->getFont(); + alignment = textElement->getAlignment(); + textAngle = textElement->getAngle(); + } + + setPen(pen, forceUpdate); + setBrush(brush, forceUpdate); + setFont(font, forceUpdate); + setFontAlignment(alignment, forceUpdate); + setTextAngle(textAngle, forceUpdate); +} + +void PDFPageContentEditorStyleSettings::setPen(const QPen& pen, bool forceUpdate) +{ + if (m_pen != pen || forceUpdate) + { + const bool oldBlockSignals = blockSignals(true); + + m_pen = pen; + ui->penWidthEdit->setValue(pen.widthF()); + ui->penStyleCombo->setCurrentIndex(ui->penStyleCombo->findData(int(pen.style()))); + setColorToComboBox(ui->penColorCombo, pen.color()); + + blockSignals(oldBlockSignals); + emit penChanged(m_pen); + } +} + +void PDFPageContentEditorStyleSettings::setBrush(const QBrush& brush, bool forceUpdate) +{ + if (m_brush != brush || forceUpdate) + { + const bool oldBlockSignals = blockSignals(true); + + m_brush = brush; + ui->brushStyleCombo->setCurrentIndex(ui->brushStyleCombo->findData(int(brush.style()))); + setColorToComboBox(ui->brushColorCombo, brush.color()); + + blockSignals(oldBlockSignals); + emit brushChanged(m_brush); + } +} + +void PDFPageContentEditorStyleSettings::setFont(const QFont& font, bool forceUpdate) +{ + if (m_font != font || forceUpdate) + { + const bool oldBlockSignals = blockSignals(true); + + m_font = font; + ui->fontComboBox->setCurrentFont(m_font); + + blockSignals(oldBlockSignals); + emit fontChanged(m_font); + } +} + +void PDFPageContentEditorStyleSettings::setFontAlignment(Qt::Alignment alignment, bool forceUpdate) +{ + if (m_alignment != alignment || forceUpdate) + { + const bool oldBlockSignals = blockSignals(true); + + for (QRadioButton* radioButton : findChildren()) + { + radioButton->setChecked(false); + } + + m_alignment = alignment; + QRadioButton* radioButton = qobject_cast(m_alignmentMapper.mapping(int(alignment))); + radioButton->setChecked(true); + + blockSignals(oldBlockSignals); + emit alignmentChanged(m_alignment); + } +} + +void PDFPageContentEditorStyleSettings::setTextAngle(PDFReal angle, bool forceUpdate) +{ + if (ui->textAngleEdit->value() != angle || forceUpdate) + { + const bool oldBlockSignals = blockSignals(true); + ui->textAngleEdit->setValue(angle); + blockSignals(oldBlockSignals); + emit textAngleChanged(ui->textAngleEdit->value()); + } +} + +bool PDFPageContentEditorStyleSettings::showEditElementStyleDialog(QWidget* parent, + PDFPageContentElement* element) +{ + QDialog dialog(parent); + dialog.setWindowTitle(tr("Edit Item")); + dialog.setLayout(new QVBoxLayout()); + + QTextEdit* textEdit = nullptr; + PDFPageContentStyledElement* styledElement = dynamic_cast(element); + PDFPageContentElementTextBox* textElement = dynamic_cast(element); + if (textElement) + { + QGroupBox* contentGroupBox = new QGroupBox(&dialog); + textEdit = new QTextEdit(textElement->getText(), contentGroupBox); + textEdit->setFont(textElement->getFont()); + textEdit->setAlignment(textElement->getAlignment()); + textEdit->setTextColor(textElement->getPen().color()); + contentGroupBox->setTitle(tr("Content")); + contentGroupBox->setLayout(new QVBoxLayout()); + contentGroupBox->layout()->addWidget(textEdit); + dialog.layout()->addWidget(contentGroupBox); + } + + PDFPageContentEditorStyleSettings* appearanceWidget = new PDFPageContentEditorStyleSettings(&dialog); + appearanceWidget->loadFromElement(element, true); + if (textEdit) + { + connect(appearanceWidget, &PDFPageContentEditorStyleSettings::alignmentChanged, textEdit, &QTextEdit::setAlignment); + connect(appearanceWidget, &PDFPageContentEditorStyleSettings::fontChanged, textEdit, &QTextEdit::setFont); + connect(appearanceWidget, &PDFPageContentEditorStyleSettings::penChanged, textEdit, [textEdit](const QPen& pen) { textEdit->setTextColor(pen.color()); }); + } + + QGroupBox* appearanceGroupBox = new QGroupBox(&dialog); + appearanceGroupBox->setTitle(tr("Appearance")); + appearanceGroupBox->setLayout(new QVBoxLayout()); + appearanceGroupBox->layout()->addWidget(appearanceWidget); + dialog.layout()->addWidget(appearanceGroupBox); + + QDialogButtonBox* dialogButtonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dialog); + connect(dialogButtonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); + connect(dialogButtonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); + dialog.layout()->addWidget(dialogButtonBox); + + if (dialog.exec() == QDialog::Accepted) + { + if (styledElement) + { + styledElement->setPen(appearanceWidget->getPen()); + styledElement->setBrush(appearanceWidget->getBrush()); + } + + if (textElement) + { + textElement->setText(textEdit->toPlainText()); + textElement->setFont(appearanceWidget->getFont()); + textElement->setAlignment(appearanceWidget->getAlignment()); + textElement->setAngle(appearanceWidget->getTextAngle()); + } + + return true; + } + + return false; +} + +QIcon PDFPageContentEditorStyleSettings::getIconForColor(QColor color) const +{ + QIcon icon; + + QSize iconSize = PDFWidgetUtils::scaleDPI(this, QSize(16, 16)); + + QPixmap pixmap(iconSize.width(), iconSize.height()); + pixmap.fill(color); + icon.addPixmap(pixmap); + + return icon; +} + +void PDFPageContentEditorStyleSettings::setColorToComboBox(QComboBox* comboBox, QColor color) +{ + if (!color.isValid()) + { + return; + } + + QString name = color.name(QColor::HexArgb); + + int index = comboBox->findData(color, Qt::UserRole, Qt::MatchExactly); + + if (index == -1) + { + // Jakub Melka: try to find text (color name) + index = comboBox->findText(name); + } + + if (index != -1) + { + comboBox->setCurrentIndex(index); + } + else + { + comboBox->addItem(getIconForColor(color), name, color); + comboBox->setCurrentIndex(comboBox->count() - 1); + } +} + +void PDFPageContentEditorStyleSettings::onSelectFontButtonClicked() +{ + bool ok = false; + QFont font = QFontDialog::getFont(&ok, m_font, this, tr("Select Font")); + + if (ok && m_font != font) + { + m_font = font; + ui->fontComboBox->setCurrentFont(m_font); + emit fontChanged(m_font); + } +} + +void PDFPageContentEditorStyleSettings::setPenColor(QColor color) +{ + if (color.isValid() && m_pen.color() != color) + { + m_pen.setColor(color); + setColorToComboBox(ui->penColorCombo, color); + emit penChanged(m_pen); + } +} + +void PDFPageContentEditorStyleSettings::onSelectPenColorButtonClicked() +{ + QColor color = QColorDialog::getColor(m_pen.color(), this, tr("Select Color for Pen"), QColorDialog::ShowAlphaChannel); + setPenColor(color); +} + +void PDFPageContentEditorStyleSettings::setBrushColor(QColor color) +{ + if (color.isValid() && m_brush.color() != color) + { + m_brush.setColor(color); + setColorToComboBox(ui->brushColorCombo, color); + emit brushChanged(m_brush); + } +} + +Qt::Alignment PDFPageContentEditorStyleSettings::getAlignment() const +{ + return m_alignment; +} + +PDFReal PDFPageContentEditorStyleSettings::getTextAngle() const +{ + return ui->textAngleEdit->value(); +} + +const QFont& PDFPageContentEditorStyleSettings::getFont() const +{ + return m_font; +} + +const QBrush& PDFPageContentEditorStyleSettings::getBrush() const +{ + return m_brush; +} + +const QPen& PDFPageContentEditorStyleSettings::getPen() const +{ + return m_pen; +} + +void PDFPageContentEditorStyleSettings::onSelectBrushColorButtonClicked() +{ + QColor color = QColorDialog::getColor(m_pen.color(), this, tr("Select Color for Brush"), QColorDialog::ShowAlphaChannel); + setBrushColor(color); +} + +void PDFPageContentEditorStyleSettings::onPenWidthChanged(double value) +{ + if (m_pen.widthF() != value) + { + m_pen.setWidthF(value); + emit penChanged(m_pen); + } +} + +void PDFPageContentEditorStyleSettings::onTextAngleChanged(double value) +{ + emit textAngleChanged(value); +} + +void PDFPageContentEditorStyleSettings::onAlignmentRadioButtonClicked(int alignment) +{ + Qt::Alignment alignmentValue = static_cast(alignment); + if (m_alignment != alignmentValue) + { + m_alignment = alignmentValue; + emit alignmentChanged(m_alignment); + } +} + +void PDFPageContentEditorStyleSettings::onPenStyleChanged() +{ + Qt::PenStyle penStyle = static_cast(ui->penStyleCombo->currentData().toInt()); + if (m_pen.style() != penStyle) + { + m_pen.setStyle(penStyle); + emit penChanged(m_pen); + } +} + +void PDFPageContentEditorStyleSettings::onBrushStyleChanged() +{ + Qt::BrushStyle brushStyle = static_cast(ui->brushStyleCombo->currentData().toInt()); + if (m_brush.style() != brushStyle) + { + m_brush.setStyle(brushStyle); + emit brushChanged(m_brush); + } +} + +void PDFPageContentEditorStyleSettings::onPenColorComboTextChanged() +{ + QColor color(ui->penColorCombo->currentText()); + if (color.isValid()) + { + setColorToComboBox(ui->penColorCombo, color); + + if (m_pen.color() != color) + { + m_pen.setColor(color); + emit penChanged(m_pen); + } + } + else if (ui->penColorCombo->currentIndex() != -1) + { + ui->penColorCombo->setEditText(ui->penColorCombo->itemText(ui->penColorCombo->currentIndex())); + } +} + +void PDFPageContentEditorStyleSettings::onPenColorComboIndexChanged() +{ + const int index = ui->penColorCombo->currentIndex(); + QColor color = ui->penColorCombo->itemData(index, Qt::UserRole).value(); + if (color.isValid() && m_pen.color() != color) + { + m_pen.setColor(color); + emit penChanged(m_pen); + } +} + +void PDFPageContentEditorStyleSettings::onBrushColorComboTextChanged() +{ + QColor color(ui->brushColorCombo->currentText()); + if (color.isValid()) + { + setColorToComboBox(ui->brushColorCombo, color); + + if (m_brush.color() != color) + { + m_brush.setColor(color); + emit brushChanged(m_brush); + } + } + else if (ui->brushColorCombo->currentIndex() != -1) + { + ui->brushColorCombo->setEditText(ui->brushColorCombo->itemText(ui->brushColorCombo->currentIndex())); + } +} + +void PDFPageContentEditorStyleSettings::onBrushColorComboIndexChanged() +{ + const int index = ui->brushColorCombo->currentIndex(); + QColor color = ui->brushColorCombo->itemData(index, Qt::UserRole).value(); + if (color.isValid() && m_brush.color() != color) + { + m_brush.setColor(color); + emit brushChanged(m_brush); + } +} + +void PDFPageContentEditorStyleSettings::onFontChanged(const QFont& font) +{ + if (m_font != font) + { + m_font = font; + emit fontChanged(m_font); + } +} + +} // namespace pdf diff --git a/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.h b/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.h new file mode 100644 index 0000000..04b0a61 --- /dev/null +++ b/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.h @@ -0,0 +1,118 @@ +// Copyright (C) 2022 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef PDFPAGECONTENTEDITORSTYLESETTINGS_H +#define PDFPAGECONTENTEDITORSTYLESETTINGS_H + +#include "pdfglobal.h" + +#include +#include +#include +#include +#include +#include + +namespace Ui +{ +class PDFPageContentEditorStyleSettings; +} + +class QComboBox; + +namespace pdf +{ +class PDFPageContentElement; + +class PDF4QTLIBSHARED_EXPORT PDFPageContentEditorStyleSettings : public QWidget +{ + Q_OBJECT + +public: + + enum StyleFeature + { + None = 0, + Pen = 1 << 0, + PenColor = 1 << 1, + Brush = 1 << 2, + Text = 1 << 3 + }; + Q_DECLARE_FLAGS(StyleFeatures, StyleFeature) + + explicit PDFPageContentEditorStyleSettings(QWidget* parent); + virtual ~PDFPageContentEditorStyleSettings() override; + + /// Loads data from element, element can be nullptr + /// \param element Element + void loadFromElement(const PDFPageContentElement* element, bool forceUpdate); + + void setPen(const QPen& pen, bool forceUpdate); + void setBrush(const QBrush& brush, bool forceUpdate); + void setFont(const QFont& font, bool forceUpdate); + void setFontAlignment(Qt::Alignment alignment, bool forceUpdate); + void setTextAngle(PDFReal angle, bool forceUpdate); + + static bool showEditElementStyleDialog(QWidget* parent, PDFPageContentElement* element); + + const QPen& getPen() const; + const QBrush& getBrush() const; + const QFont& getFont() const; + Qt::Alignment getAlignment() const; + PDFReal getTextAngle() const; + +signals: + void penChanged(const QPen& pen); + void brushChanged(const QBrush& brush); + void fontChanged(const QFont& font); + void alignmentChanged(Qt::Alignment alignment); + void textAngleChanged(pdf::PDFReal angle); + +private slots: + void onSelectFontButtonClicked(); + void onSelectPenColorButtonClicked(); + void onSelectBrushColorButtonClicked(); + void onPenWidthChanged(double value); + void onTextAngleChanged(double value); + void onAlignmentRadioButtonClicked(int alignment); + void onPenStyleChanged(); + void onBrushStyleChanged(); + void onPenColorComboTextChanged(); + void onPenColorComboIndexChanged(); + void onBrushColorComboTextChanged(); + void onBrushColorComboIndexChanged(); + +private: + Ui::PDFPageContentEditorStyleSettings* ui; + + void onFontChanged(const QFont& font); + void setColorToComboBox(QComboBox* comboBox, QColor color); + QIcon getIconForColor(QColor color) const; + + void setPenColor(QColor color); + void setBrushColor(QColor color); + + QPen m_pen; + QBrush m_brush; + QFont m_font; + Qt::Alignment m_alignment = Qt::AlignCenter; + QSignalMapper m_alignmentMapper; +}; + +} // namespace pdf + +#endif // PDFPAGECONTENTEDITORSTYLESETTINGS_H diff --git a/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.ui b/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.ui new file mode 100644 index 0000000..ccab31d --- /dev/null +++ b/Pdf4QtLib/sources/pdfpagecontenteditorstylesettings.ui @@ -0,0 +1,230 @@ + + + PDFPageContentEditorStyleSettings + + + + 0 + 0 + 344 + 310 + + + + Style Settings + + + + + + Text Alignment + + + + + + + Pen Style + + + + + + + Qt::Vertical + + + QSizePolicy::MinimumExpanding + + + + 20 + 40 + + + + + + + + true + + + + + + + + + + + + + true + + + + + + + Pen Width + + + + + + + Brush Color + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + -90.000000000000000 + + + 90.000000000000000 + + + + + + + ... + + + + + + + Pen Color + + + + + + + Font + + + + + + + + + + Text Angle + + + + + + + ... + + + + + + + ... + + + + + + + Brush Style + + + + + + + + + + + diff --git a/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp b/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp new file mode 100644 index 0000000..232ffde --- /dev/null +++ b/Pdf4QtLib/sources/pdfpagecontenteditortools.cpp @@ -0,0 +1,968 @@ +// Copyright (C) 2022 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#include "pdfpagecontenteditortools.h" +#include "pdfpagecontentelements.h" +#include "pdfpainterutils.h" +#include "pdftexteditpseudowidget.h" +#include "pdfdrawwidget.h" + +#include +#include +#include +#include +#include +#include + +namespace pdf +{ + +PDFCreatePCElementTool::PDFCreatePCElementTool(PDFDrawWidgetProxy* proxy, + PDFPageContentScene* scene, + QAction* action, + QObject* parent) : + PDFWidgetTool(proxy, action, parent), + m_scene(scene) +{ + +} + +void PDFCreatePCElementTool::setPen(const QPen& pen) +{ + if (PDFPageContentStyledElement* styledElement = dynamic_cast(getElement())) + { + styledElement->setPen(pen); + emit getProxy()->repaintNeeded(); + } +} + +void PDFCreatePCElementTool::setBrush(const QBrush& brush) +{ + if (PDFPageContentStyledElement* styledElement = dynamic_cast(getElement())) + { + styledElement->setBrush(brush); + emit getProxy()->repaintNeeded(); + } +} + +void PDFCreatePCElementTool::setFont(const QFont& font) +{ + if (PDFPageContentElementTextBox* textBoxElement = dynamic_cast(getElement())) + { + textBoxElement->setFont(font); + emit getProxy()->repaintNeeded(); + } +} + +void PDFCreatePCElementTool::setAlignment(Qt::Alignment alignment) +{ + if (PDFPageContentElementTextBox* textBoxElement = dynamic_cast(getElement())) + { + textBoxElement->setAlignment(alignment); + emit getProxy()->repaintNeeded(); + } +} + +void PDFCreatePCElementTool::setTextAngle(PDFReal angle) +{ + if (PDFPageContentElementTextBox* textBoxElement = dynamic_cast(getElement())) + { + textBoxElement->setAngle(angle); + emit getProxy()->repaintNeeded(); + } +} + +QRectF PDFCreatePCElementTool::getRectangleFromPickTool(PDFPickTool* pickTool, + const QMatrix& pagePointToDevicePointMatrix) +{ + const std::vector& points = pickTool->getPickedPoints(); + if (points.empty()) + { + return QRectF(); + } + + QPointF mousePoint = pagePointToDevicePointMatrix.inverted().map(pickTool->getSnappedPoint()); + QPointF point = points.front(); + qreal xMin = qMin(point.x(), mousePoint.x()); + qreal xMax = qMax(point.x(), mousePoint.x()); + qreal yMin = qMin(point.y(), mousePoint.y()); + qreal yMax = qMax(point.y(), mousePoint.y()); + qreal width = xMax - xMin; + qreal height = yMax - yMin; + + if (!qFuzzyIsNull(width) && !qFuzzyIsNull(height)) + { + return QRectF(xMin, yMin, width, height); + } + + return QRectF(); +} + +PDFCreatePCElementRectangleTool::PDFCreatePCElementRectangleTool(PDFDrawWidgetProxy* proxy, + PDFPageContentScene* scene, + QAction* action, + bool isRounded, + QObject* parent) : + BaseClass(proxy, scene, action, parent), + m_pickTool(nullptr), + m_element(nullptr) +{ + m_pickTool = new PDFPickTool(proxy, PDFPickTool::Mode::Rectangles, this); + m_pickTool->setDrawSelectionRectangle(false); + addTool(m_pickTool); + connect(m_pickTool, &PDFPickTool::rectanglePicked, this, &PDFCreatePCElementRectangleTool::onRectanglePicked); + + QPen pen(Qt::SolidLine); + pen.setWidthF(1.0); + + m_element = new PDFPageContentElementRectangle(); + m_element->setBrush(Qt::NoBrush); + m_element->setPen(std::move(pen)); + m_element->setRounded(isRounded); + + updateActions(); +} + +PDFCreatePCElementRectangleTool::~PDFCreatePCElementRectangleTool() +{ + delete m_element; +} + +void PDFCreatePCElementRectangleTool::drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const +{ + BaseClass::drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); + + if (pageIndex != m_pickTool->getPageIndex()) + { + return; + } + + QRectF rectangle = getRectangleFromPickTool(m_pickTool, pagePointToDevicePointMatrix); + if (!rectangle.isValid()) + { + return; + } + + m_element->setPageIndex(pageIndex); + m_element->setRectangle(rectangle); + + m_element->drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); +} + +const PDFPageContentElement* PDFCreatePCElementRectangleTool::getElement() const +{ + return m_element; +} + +PDFPageContentElement* PDFCreatePCElementRectangleTool::getElement() +{ + return m_element; +} + +void PDFCreatePCElementRectangleTool::onRectanglePicked(PDFInteger pageIndex, QRectF pageRectangle) +{ + if (pageRectangle.isEmpty()) + { + return; + } + + m_element->setPageIndex(pageIndex); + m_element->setRectangle(pageRectangle); + m_scene->addElement(m_element->clone()); + + setActive(false); +} + +PDFCreatePCElementLineTool::PDFCreatePCElementLineTool(PDFDrawWidgetProxy* proxy, + PDFPageContentScene* scene, + QAction* action, + bool isHorizontal, + bool isVertical, + QObject* parent) : + BaseClass(proxy, scene, action, parent), + m_pickTool(nullptr), + m_element(nullptr) +{ + m_pickTool = new PDFPickTool(proxy, PDFPickTool::Mode::Points, this); + m_pickTool->setDrawSelectionRectangle(false); + addTool(m_pickTool); + connect(m_pickTool, &PDFPickTool::pointPicked, this, &PDFCreatePCElementLineTool::onPointPicked); + + QPen pen(Qt::SolidLine); + pen.setWidthF(2.0); + pen.setCapStyle(Qt::RoundCap); + + PDFPageContentElementLine::LineGeometry geometry = PDFPageContentElementLine::LineGeometry::General; + + if (isHorizontal) + { + geometry = PDFPageContentElementLine::LineGeometry::Horizontal; + } + + if (isVertical) + { + geometry = PDFPageContentElementLine::LineGeometry::Vertical; + } + + m_element = new PDFPageContentElementLine(); + m_element->setBrush(Qt::NoBrush); + m_element->setPen(std::move(pen)); + m_element->setGeometry(geometry); + + updateActions(); +} + +PDFCreatePCElementLineTool::~PDFCreatePCElementLineTool() +{ + delete m_element; +} + +void PDFCreatePCElementLineTool::drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const +{ + BaseClass::drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); + + if (pageIndex != m_pickTool->getPageIndex() || !m_startPoint) + { + return; + } + + m_element->setPageIndex(pageIndex); + + QPointF startPoint = *m_startPoint; + QPointF endPoint = pagePointToDevicePointMatrix.inverted().map(m_pickTool->getSnappedPoint()); + QLineF line(startPoint, endPoint); + + if (!qFuzzyIsNull(line.length())) + { + m_element->setLine(line); + } + + m_element->drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); +} + +const PDFPageContentElement* PDFCreatePCElementLineTool::getElement() const +{ + return m_element; +} + +PDFPageContentElement* PDFCreatePCElementLineTool::getElement() +{ + return m_element; +} + +void PDFCreatePCElementLineTool::clear() +{ + m_startPoint = std::nullopt; +} + +void PDFCreatePCElementLineTool::onPointPicked(PDFInteger pageIndex, QPointF pagePoint) +{ + if (!m_startPoint || m_element->getPageIndex() != pageIndex) + { + m_startPoint = pagePoint; + m_element->setPageIndex(pageIndex); + m_element->setLine(QLineF(pagePoint, pagePoint)); + return; + } + + if (qFuzzyCompare(m_startPoint.value().x(), pagePoint.x()) && + qFuzzyCompare(m_startPoint.value().y(), pagePoint.y())) + { + // Jakub Melka: Point is same as the start point + clear(); + return; + } + + QLineF line = m_element->getLine(); + line.setP2(pagePoint); + m_element->setLine(line); + m_scene->addElement(m_element->clone()); + clear(); + + setActive(false); +} + +PDFCreatePCElementImageTool::PDFCreatePCElementImageTool(PDFDrawWidgetProxy* proxy, + PDFPageContentScene* scene, + QAction* action, + QByteArray content, + bool askSelectImage, + QObject* parent) : + BaseClass(proxy, scene, action, parent), + m_pickTool(nullptr), + m_element(nullptr), + m_askSelectImage(askSelectImage) +{ + m_pickTool = new PDFPickTool(proxy, PDFPickTool::Mode::Rectangles, this); + m_pickTool->setDrawSelectionRectangle(false); + addTool(m_pickTool); + connect(m_pickTool, &PDFPickTool::rectanglePicked, this, &PDFCreatePCElementImageTool::onRectanglePicked); + + m_element = new PDFPageContentImageElement(); + m_element->setContent(content); + + updateActions(); +} + +PDFCreatePCElementImageTool::~PDFCreatePCElementImageTool() +{ + delete m_element; +} + +void PDFCreatePCElementImageTool::drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const +{ + BaseClass::drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); + + if (pageIndex != m_pickTool->getPageIndex()) + { + return; + } + + QRectF rectangle = getRectangleFromPickTool(m_pickTool, pagePointToDevicePointMatrix); + if (!rectangle.isValid()) + { + return; + } + + m_element->setPageIndex(pageIndex); + m_element->setRectangle(rectangle); + + { + PDFPainterStateGuard guard(painter); + painter->setWorldMatrix(pagePointToDevicePointMatrix, true); + painter->setRenderHint(QPainter::Antialiasing); + painter->setPen(Qt::DotLine); + painter->setBrush(Qt::NoBrush); + painter->drawRect(rectangle); + } + + m_element->drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); +} + +const PDFPageContentElement* PDFCreatePCElementImageTool::getElement() const +{ + return m_element; +} + +PDFPageContentElement* PDFCreatePCElementImageTool::getElement() +{ + return m_element; +} + +void PDFCreatePCElementImageTool::setActiveImpl(bool active) +{ + BaseClass::setActiveImpl(active); + + if (active && m_askSelectImage) + { + QTimer::singleShot(0, this, &PDFCreatePCElementImageTool::selectImage); + } +} + +void PDFCreatePCElementImageTool::selectImage() +{ + if (m_imageDirectory.isEmpty()) + { + QStringList pictureDirectiories = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation); + if (!pictureDirectiories.isEmpty()) + { + m_imageDirectory = pictureDirectiories.last(); + } + else + { + m_imageDirectory = QDir::currentPath(); + } + } + + QList mimeTypes = QImageReader::supportedMimeTypes(); + QStringList mimeTypeFilters; + for (const QByteArray& mimeType : mimeTypes) + { + mimeTypeFilters.append(mimeType); + } + + QFileDialog dialog(getProxy()->getWidget(), tr("Select Image")); + dialog.setDirectory(m_imageDirectory); + dialog.setMimeTypeFilters(mimeTypeFilters); + dialog.selectMimeTypeFilter("image/svg+xml"); + dialog.setAcceptMode(QFileDialog::AcceptOpen); + dialog.setFileMode(QFileDialog::ExistingFile); + + if (dialog.exec() == QFileDialog::Accepted) + { + QString fileName = dialog.selectedFiles().constFirst(); + QFile file(fileName); + if (file.open(QFile::ReadOnly)) + { + m_element->setContent(file.readAll()); + file.close(); + } + else + { + setActive(false); + } + } + else + { + setActive(false); + } +} + +void PDFCreatePCElementImageTool::onRectanglePicked(PDFInteger pageIndex, QRectF pageRectangle) +{ + if (pageRectangle.isEmpty()) + { + return; + } + + m_element->setPageIndex(pageIndex); + m_element->setRectangle(pageRectangle); + m_scene->addElement(m_element->clone()); + + setActive(false); +} + +PDFCreatePCElementDotTool::PDFCreatePCElementDotTool(PDFDrawWidgetProxy* proxy, + PDFPageContentScene* scene, + QAction* action, + QObject* parent) : + BaseClass(proxy, scene, action, parent), + m_pickTool(nullptr), + m_element(nullptr) +{ + m_pickTool = new PDFPickTool(proxy, PDFPickTool::Mode::Points, this); + m_pickTool->setDrawSelectionRectangle(false); + addTool(m_pickTool); + connect(m_pickTool, &PDFPickTool::pointPicked, this, &PDFCreatePCElementDotTool::onPointPicked); + + QPen pen(Qt::SolidLine); + pen.setWidthF(5.0); + pen.setCapStyle(Qt::RoundCap); + + m_element = new PDFPageContentElementDot(); + m_element->setBrush(Qt::NoBrush); + m_element->setPen(std::move(pen)); + + updateActions(); +} + +PDFCreatePCElementDotTool::~PDFCreatePCElementDotTool() +{ + delete m_element; +} + +void PDFCreatePCElementDotTool::drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const +{ + BaseClass::drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); + + QPointF point = pagePointToDevicePointMatrix.inverted().map(m_pickTool->getSnappedPoint()); + + PDFPainterStateGuard guard(painter); + painter->setWorldMatrix(pagePointToDevicePointMatrix, true); + painter->setRenderHint(QPainter::Antialiasing); + painter->setPen(m_element->getPen()); + painter->setBrush(m_element->getBrush()); + painter->drawPoint(point); +} + +const PDFPageContentElement* PDFCreatePCElementDotTool::getElement() const +{ + return m_element; +} + +PDFPageContentElement* PDFCreatePCElementDotTool::getElement() +{ + return m_element; +} + +void PDFCreatePCElementDotTool::onPointPicked(PDFInteger pageIndex, QPointF pagePoint) +{ + m_element->setPageIndex(pageIndex); + m_element->setPoint(pagePoint); + + m_scene->addElement(m_element->clone()); + m_element->setPageIndex(-1); + + setActive(false); +} + +PDFCreatePCElementFreehandCurveTool::PDFCreatePCElementFreehandCurveTool(PDFDrawWidgetProxy* proxy, + PDFPageContentScene* scene, + QAction* action, + QObject* parent) : + BaseClass(proxy, scene, action, parent), + m_element(nullptr) +{ + QPen pen(Qt::SolidLine); + pen.setWidthF(2.0); + pen.setCapStyle(Qt::RoundCap); + + m_element = new PDFPageContentElementFreehandCurve(); + m_element->setBrush(Qt::NoBrush); + m_element->setPen(std::move(pen)); +} + +PDFCreatePCElementFreehandCurveTool::~PDFCreatePCElementFreehandCurveTool() +{ + delete m_element; +} + +void PDFCreatePCElementFreehandCurveTool::drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const +{ + BaseClass::drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); + + if (pageIndex != m_element->getPageIndex() || m_element->isEmpty()) + { + return; + } + + m_element->drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); +} + +const PDFPageContentElement* PDFCreatePCElementFreehandCurveTool::getElement() const +{ + return m_element; +} + +PDFPageContentElement* PDFCreatePCElementFreehandCurveTool::getElement() +{ + return m_element; +} + +void PDFCreatePCElementFreehandCurveTool::mousePressEvent(QWidget* widget, QMouseEvent* event) +{ + Q_UNUSED(widget); + event->accept(); + + if (event->button() == Qt::LeftButton) + { + // Try to perform pick point + QPointF pagePoint; + PDFInteger pageIndex = getProxy()->getPageUnderPoint(event->pos(), &pagePoint); + if (pageIndex != -1 && // We have picked some point on page + (m_element->getPageIndex() == -1 || m_element->getPageIndex() == pageIndex)) // We are under current page + { + m_element->setPageIndex(pageIndex); + m_element->addStartPoint(pagePoint); + } + } + else if (event->button() == Qt::RightButton) + { + resetTool(); + } + + emit getProxy()->repaintNeeded(); +} + +void PDFCreatePCElementFreehandCurveTool::mouseReleaseEvent(QWidget* widget, QMouseEvent* event) +{ + Q_UNUSED(widget); + event->accept(); + + if (event->button() == Qt::LeftButton) + { + // Try to perform pick point + QPointF pagePoint; + PDFInteger pageIndex = getProxy()->getPageUnderPoint(event->pos(), &pagePoint); + if (pageIndex != -1 && // We have picked some point on page + (m_element->getPageIndex() == pageIndex)) // We are under current page + { + m_element->setPageIndex(pageIndex); + m_element->addPoint(pagePoint); + + if (!m_element->isEmpty()) + { + m_scene->addElement(m_element->clone()); + } + } + + resetTool(); + } + + emit getProxy()->repaintNeeded(); +} + +void PDFCreatePCElementFreehandCurveTool::mouseMoveEvent(QWidget* widget, QMouseEvent* event) +{ + Q_UNUSED(widget); + event->accept(); + + if (event->buttons() & Qt::LeftButton && m_element->getPageIndex() != -1) + { + // Try to add point to the path + QPointF pagePoint; + PDFInteger pageIndex = getProxy()->getPageUnderPoint(event->pos(), &pagePoint); + if (pageIndex == m_element->getPageIndex()) + { + m_element->addPoint(pagePoint); + } + + emit getProxy()->repaintNeeded(); + } +} + +void PDFCreatePCElementFreehandCurveTool::setActiveImpl(bool active) +{ + BaseClass::setActiveImpl(active); + + if (!active) + { + resetTool(); + } +} + +void PDFCreatePCElementFreehandCurveTool::resetTool() +{ + m_element->clear(); +} + +PDFCreatePCElementTextTool::PDFCreatePCElementTextTool(PDFDrawWidgetProxy* proxy, + PDFPageContentScene* scene, + QAction* action, + QObject* parent) : + BaseClass(proxy, scene, action, parent), + m_pickTool(nullptr), + m_element(nullptr), + m_textEditWidget(nullptr) +{ + m_pickTool = new PDFPickTool(proxy, PDFPickTool::Mode::Rectangles, this); + m_pickTool->setDrawSelectionRectangle(true); + connect(m_pickTool, &PDFPickTool::rectanglePicked, this, &PDFCreatePCElementTextTool::onRectanglePicked); + + QFont font = QGuiApplication::font(); + font.setPixelSize(16.0); + + m_element = new PDFPageContentElementTextBox(); + m_element->setBrush(Qt::NoBrush); + m_element->setPen(QPen(Qt::SolidLine)); + m_element->setFont(font); + + m_textEditWidget = new PDFTextEditPseudowidget(PDFFormField::Multiline); +} + +PDFCreatePCElementTextTool::~PDFCreatePCElementTextTool() +{ + delete m_textEditWidget; + delete m_element; +} + +void PDFCreatePCElementTextTool::drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const +{ + BaseClass::drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); + + if (pageIndex != m_element->getPageIndex()) + { + return; + } + + if (isEditing()) + { + PDFPainterStateGuard guard(painter); + AnnotationDrawParameters parameters; + parameters.painter = painter; + parameters.boundingRectangle = m_element->getRectangle(); + parameters.key.first = PDFAppeareanceStreams::Appearance::Normal; + parameters.invertColors = getProxy()->getFeatures().testFlag(PDFRenderer::InvertColors); + + painter->setWorldMatrix(pagePointToDevicePointMatrix, true); + m_textEditWidget->draw(parameters, true); + } +} + +const PDFPageContentElement* PDFCreatePCElementTextTool::getElement() const +{ + return m_element; +} + +PDFPageContentElement* PDFCreatePCElementTextTool::getElement() +{ + return m_element; +} + +void PDFCreatePCElementTextTool::resetTool() +{ + m_textEditWidget->setText(QString()); + m_element->setText(QString()); + m_element->setPageIndex(-1); + + if (getTopToolstackTool()) + { + removeTool(); + } +} + +void PDFCreatePCElementTextTool::setActiveImpl(bool active) +{ + BaseClass::setActiveImpl(active); + + if (active) + { + Q_ASSERT(!getTopToolstackTool()); + addTool(m_pickTool); + } + else + { + resetTool(); + } + + m_pickTool->setActive(active); +} + +void PDFCreatePCElementTextTool::onRectanglePicked(PDFInteger pageIndex, QRectF pageRectangle) +{ + if (pageRectangle.isEmpty()) + { + return; + } + + m_element->setPageIndex(pageIndex); + m_element->setRectangle(pageRectangle); + + m_textEditWidget->setAppearance(m_element->getFont(), + m_element->getAlignment(), + m_element->getRectangle(), + std::numeric_limits::max(), + m_element->getPen().color()); + + removeTool(); +} + +void PDFCreatePCElementTextTool::finishEditing() +{ + m_element->setText(m_textEditWidget->getText()); + + if (!m_element->getText().isEmpty()) + { + m_scene->addElement(m_element->clone()); + } + + resetTool(); + setActive(false); +} + +std::optional PDFCreatePCElementTextTool::getPagePointUnderMouse(QMouseEvent* event) const +{ + QPointF pagePoint; + PDFInteger pageIndex = getProxy()->getPageUnderPoint(event->pos(), &pagePoint); + if (pageIndex == m_element->getPageIndex() && + m_element->getRectangle().contains(pagePoint)) + { + return pagePoint; + } + + return std::nullopt; +} + +bool PDFCreatePCElementTextTool::isEditing() const +{ + return isActive() && !getTopToolstackTool(); +} + +void PDFCreatePCElementTextTool::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) +{ + Q_UNUSED(widget); + + if (isEditing()) + { + m_textEditWidget->shortcutOverrideEvent(widget, event); + } +} + +void PDFCreatePCElementTextTool::keyPressEvent(QWidget* widget, QKeyEvent* event) +{ + event->ignore(); + + if (!isEditing()) + { + BaseClass::keyPressEvent(widget, event); + return; + } + + if (event->key() == Qt::Key_Escape) + { + return; + } + + if (!m_textEditWidget->isMultiline() && (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return)) + { + // Commit the editor and create element + finishEditing(); + event->accept(); + return; + } + + m_textEditWidget->keyPressEvent(widget, event); + + if (event->isAccepted()) + { + widget->update(); + } +} + +void PDFCreatePCElementTextTool::mousePressEvent(QWidget* widget, QMouseEvent* event) +{ + if (isEditing()) + { + if (event->button() == Qt::LeftButton) + { + std::optional pagePoint = getPagePointUnderMouse(event); + if (pagePoint) + { + const int cursorPosition = m_textEditWidget->getCursorPositionFromWidgetPosition(pagePoint.value(), true); + m_textEditWidget->setCursorPosition(cursorPosition, event->modifiers() & Qt::ShiftModifier); + } + else + { + finishEditing(); + } + + event->accept(); + widget->update(); + } + } + else + { + BaseClass::mousePressEvent(widget, event); + } +} + +void PDFCreatePCElementTextTool::mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event) +{ + if (isEditing()) + { + if (event->button() == Qt::LeftButton) + { + std::optional pagePoint = getPagePointUnderMouse(event); + if (pagePoint) + { + const int cursorPosition = m_textEditWidget->getCursorPositionFromWidgetPosition(pagePoint.value(), true); + m_textEditWidget->setCursorPosition(cursorPosition, false); + m_textEditWidget->setCursorPosition(m_textEditWidget->getCursorWordBackward(), false); + m_textEditWidget->setCursorPosition(m_textEditWidget->getCursorWordForward(), true); + } + else + { + finishEditing(); + } + + event->accept(); + widget->update(); + } + } + else + { + BaseClass::mousePressEvent(widget, event); + } +} + +void PDFCreatePCElementTextTool::mouseMoveEvent(QWidget* widget, QMouseEvent* event) +{ + if (isEditing()) + { + std::optional pagePoint = getPagePointUnderMouse(event); + if (pagePoint) + { + // We must test, if left mouse button is pressed while + // we are moving the mouse - if yes, then select the text. + if (event->buttons() & Qt::LeftButton) + { + const int cursorPosition = m_textEditWidget->getCursorPositionFromWidgetPosition(pagePoint.value(), true); + m_textEditWidget->setCursorPosition(cursorPosition, true); + + event->accept(); + widget->update(); + } + } + } + else + { + BaseClass::mouseMoveEvent(widget, event); + } +} + +void PDFCreatePCElementTextTool::wheelEvent(QWidget* widget, QWheelEvent* event) +{ + if (isEditing()) + { + event->ignore(); + } + else + { + BaseClass::wheelEvent(widget, event); + } +} + +void PDFCreatePCElementTextTool::setFont(const QFont& font) +{ + BaseClass::setFont(font); + m_textEditWidget->setAppearance(font, m_element->getAlignment(), m_element->getRectangle(), std::numeric_limits::max(), m_element->getPen().color()); + emit getProxy()->repaintNeeded(); +} + +void PDFCreatePCElementTextTool::setAlignment(Qt::Alignment alignment) +{ + BaseClass::setAlignment(alignment); + m_textEditWidget->setAppearance(m_element->getFont(), alignment, m_element->getRectangle(), std::numeric_limits::max(), m_element->getPen().color()); + emit getProxy()->repaintNeeded(); +} + +void PDFCreatePCElementTextTool::setPen(const QPen& pen) +{ + BaseClass::setPen(pen); + + QFont font = m_element->getFont(); + font.setHintingPreference(QFont::PreferNoHinting); + if (font.pointSizeF() > 0.0) + { + font.setPixelSize(qRound(font.pointSizeF())); + } + + m_textEditWidget->setAppearance(font, m_element->getAlignment(), m_element->getRectangle(), std::numeric_limits::max(), pen.color()); + emit getProxy()->repaintNeeded(); +} + +} // namespace pdf diff --git a/Pdf4QtLib/sources/pdfpagecontenteditortools.h b/Pdf4QtLib/sources/pdfpagecontenteditortools.h new file mode 100644 index 0000000..568887b --- /dev/null +++ b/Pdf4QtLib/sources/pdfpagecontenteditortools.h @@ -0,0 +1,292 @@ +// Copyright (C) 2022 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef PDFPAGECONTENTEDITORTOOLS_H +#define PDFPAGECONTENTEDITORTOOLS_H + +#include "pdfwidgettool.h" + +namespace pdf +{ + +class PDFPageContentScene; +class PDFPageContentElement; +class PDFPageContentImageElement; +class PDFPageContentElementDot; +class PDFPageContentElementLine; +class PDFPageContentElementTextBox; +class PDFPageContentElementRectangle; +class PDFPageContentElementFreehandCurve; +class PDFTextEditPseudowidget; + +class PDF4QTLIBSHARED_EXPORT PDFCreatePCElementTool : public PDFWidgetTool +{ + Q_OBJECT +public: + PDFCreatePCElementTool(PDFDrawWidgetProxy* proxy, + PDFPageContentScene* scene, + QAction* action, + QObject* parent); + + virtual const PDFPageContentElement* getElement() const = 0; + virtual PDFPageContentElement* getElement() = 0; + + virtual void setPen(const QPen& pen); + virtual void setBrush(const QBrush& brush); + virtual void setFont(const QFont& font); + virtual void setAlignment(Qt::Alignment alignment); + virtual void setTextAngle(pdf::PDFReal angle); + +protected: + static QRectF getRectangleFromPickTool(PDFPickTool* pickTool, const QMatrix& pagePointToDevicePointMatrix); + + PDFPageContentScene* m_scene; +}; + +/// Tool that creates rectangle element. +class PDF4QTLIBSHARED_EXPORT PDFCreatePCElementRectangleTool : public PDFCreatePCElementTool +{ + Q_OBJECT + +private: + using BaseClass = PDFCreatePCElementTool; + +public: + explicit PDFCreatePCElementRectangleTool(PDFDrawWidgetProxy* proxy, + PDFPageContentScene* scene, + QAction* action, + bool isRounded, + QObject* parent); + virtual ~PDFCreatePCElementRectangleTool() override; + + virtual void drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const override; + + virtual const PDFPageContentElement* getElement() const override; + virtual PDFPageContentElement* getElement() override; + +private: + void onRectanglePicked(pdf::PDFInteger pageIndex, QRectF pageRectangle); + + PDFPickTool* m_pickTool; + PDFPageContentElementRectangle* m_element; +}; + +/// Tool that displays SVG image (or raster image) +class PDF4QTLIBSHARED_EXPORT PDFCreatePCElementImageTool : public PDFCreatePCElementTool +{ + Q_OBJECT + +private: + using BaseClass = PDFCreatePCElementTool; + +public: + explicit PDFCreatePCElementImageTool(PDFDrawWidgetProxy* proxy, + PDFPageContentScene* scene, + QAction* action, + QByteArray content, + bool askSelectImage, + QObject* parent); + virtual ~PDFCreatePCElementImageTool() override; + + virtual void drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const override; + + virtual const PDFPageContentElement* getElement() const override; + virtual PDFPageContentElement* getElement() override; + +protected: + virtual void setActiveImpl(bool active) override; + +private: + void selectImage(); + void onRectanglePicked(pdf::PDFInteger pageIndex, QRectF pageRectangle); + + PDFPickTool* m_pickTool; + PDFPageContentImageElement* m_element; + bool m_askSelectImage; + QString m_imageDirectory; +}; + +/// Tool that creates line element. +class PDF4QTLIBSHARED_EXPORT PDFCreatePCElementLineTool : public PDFCreatePCElementTool +{ + Q_OBJECT + +private: + using BaseClass = PDFCreatePCElementTool; + +public: + explicit PDFCreatePCElementLineTool(PDFDrawWidgetProxy* proxy, + PDFPageContentScene* scene, + QAction* action, + bool isHorizontal, + bool isVertical, + QObject* parent); + virtual ~PDFCreatePCElementLineTool() override; + + virtual void drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const override; + + virtual const PDFPageContentElement* getElement() const override; + virtual PDFPageContentElement* getElement() override; + +private: + void clear(); + void onPointPicked(pdf::PDFInteger pageIndex, QPointF pagePoint); + + PDFPickTool* m_pickTool; + PDFPageContentElementLine* m_element; + std::optional m_startPoint; +}; + +/// Tool that creates dot element. +class PDF4QTLIBSHARED_EXPORT PDFCreatePCElementDotTool : public PDFCreatePCElementTool +{ + Q_OBJECT + +private: + using BaseClass = PDFCreatePCElementTool; + +public: + explicit PDFCreatePCElementDotTool(PDFDrawWidgetProxy* proxy, + PDFPageContentScene* scene, + QAction* action, + QObject* parent); + virtual ~PDFCreatePCElementDotTool() override; + + virtual void drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const override; + + virtual const PDFPageContentElement* getElement() const override; + virtual PDFPageContentElement* getElement() override; + +private: + void onPointPicked(pdf::PDFInteger pageIndex, QPointF pagePoint); + + PDFPickTool* m_pickTool; + PDFPageContentElementDot* m_element; +}; + +/// Tool that creates freehand curve element. +class PDF4QTLIBSHARED_EXPORT PDFCreatePCElementFreehandCurveTool : public PDFCreatePCElementTool +{ + Q_OBJECT + +private: + using BaseClass = PDFCreatePCElementTool; + +public: + explicit PDFCreatePCElementFreehandCurveTool(PDFDrawWidgetProxy* proxy, + PDFPageContentScene* scene, + QAction* action, + QObject* parent); + virtual ~PDFCreatePCElementFreehandCurveTool() override; + + virtual void drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const override; + + virtual const PDFPageContentElement* getElement() const override; + virtual PDFPageContentElement* getElement() override; + + virtual void mousePressEvent(QWidget* widget, QMouseEvent* event) override; + virtual void mouseReleaseEvent(QWidget* widget, QMouseEvent* event) override; + virtual void mouseMoveEvent(QWidget* widget, QMouseEvent* event) override; + +protected: + virtual void setActiveImpl(bool active); + +private: + void resetTool(); + + PDFPageContentElementFreehandCurve* m_element; +}; + +/// Tool that displays SVG image +class PDF4QTLIBSHARED_EXPORT PDFCreatePCElementTextTool : public PDFCreatePCElementTool +{ + Q_OBJECT + +private: + using BaseClass = PDFCreatePCElementTool; + +public: + explicit PDFCreatePCElementTextTool(PDFDrawWidgetProxy* proxy, + PDFPageContentScene* scene, + QAction* action, + QObject* parent); + virtual ~PDFCreatePCElementTextTool() override; + + virtual void drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const override; + + virtual const PDFPageContentElement* getElement() const override; + virtual PDFPageContentElement* getElement() override; + + virtual void setPen(const QPen& pen); + virtual void setFont(const QFont& font) override; + virtual void setAlignment(Qt::Alignment alignment) override; + + virtual void setActiveImpl(bool active) override; + virtual void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) override; + virtual void keyPressEvent(QWidget* widget, QKeyEvent* event) override; + virtual void mousePressEvent(QWidget* widget, QMouseEvent* event) override; + virtual void mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event) override; + virtual void mouseMoveEvent(QWidget* widget, QMouseEvent* event) override; + virtual void wheelEvent(QWidget* widget, QWheelEvent* event) override; + +private: + void onRectanglePicked(pdf::PDFInteger pageIndex, QRectF pageRectangle); + + void finishEditing(); + void resetTool(); + std::optional getPagePointUnderMouse(QMouseEvent* event) const; + + bool isEditing() const; + + PDFPickTool* m_pickTool; + PDFPageContentElementTextBox* m_element; + PDFTextEditPseudowidget* m_textEditWidget; +}; + +} // namespace pdf + +#endif // PDFPAGECONTENTEDITORTOOLS_H diff --git a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.cpp b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.cpp new file mode 100644 index 0000000..78cf64d --- /dev/null +++ b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.cpp @@ -0,0 +1,273 @@ +// Copyright (C) 2022 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#include "pdfpagecontenteditorwidget.h" +#include "pdfpagecontenteditorstylesettings.h" +#include "ui_pdfpagecontenteditorwidget.h" +#include "pdfwidgetutils.h" +#include "pdfpagecontentelements.h" +#include "pdfutils.h" + +#include +#include + +namespace pdf +{ + +PDFPageContentEditorWidget::PDFPageContentEditorWidget(QWidget* parent) : + QDockWidget(parent), + ui(new Ui::PDFPageContentEditorWidget), + m_toolBoxColumnCount(6), + m_scene(nullptr), + m_selectionChangeEnabled(true), + m_updatesEnabled(true) +{ + ui->setupUi(this); + + m_toolButtonIconSize = PDFWidgetUtils::scaleDPI(this, QSize(32, 32)); + + for (QToolButton* button : findChildren()) + { + button->setIconSize(m_toolButtonIconSize); + } + + m_operationMapper.setMapping(ui->alignVertTopButton, static_cast(PDFPageContentElementManipulator::Operation::AlignTop)); + m_operationMapper.setMapping(ui->alignVertMiddleButton, static_cast(PDFPageContentElementManipulator::Operation::AlignCenterVertically)); + m_operationMapper.setMapping(ui->alignVertBottomButton, static_cast(PDFPageContentElementManipulator::Operation::AlignBottom)); + m_operationMapper.setMapping(ui->alignHorLeftButton, static_cast(PDFPageContentElementManipulator::Operation::AlignLeft)); + m_operationMapper.setMapping(ui->alignHorMiddleButton, static_cast(PDFPageContentElementManipulator::Operation::AlignCenterHorizontally)); + m_operationMapper.setMapping(ui->alignHorRightButton, static_cast(PDFPageContentElementManipulator::Operation::AlignRight)); + m_operationMapper.setMapping(ui->setSameWidthButton, static_cast(PDFPageContentElementManipulator::Operation::SetSameWidth)); + m_operationMapper.setMapping(ui->setSameHeightButton, static_cast(PDFPageContentElementManipulator::Operation::SetSameHeight)); + m_operationMapper.setMapping(ui->setSameSizeButton, static_cast(PDFPageContentElementManipulator::Operation::SetSameSize)); + m_operationMapper.setMapping(ui->centerHorizontallyButton, static_cast(PDFPageContentElementManipulator::Operation::CenterHorizontally)); + m_operationMapper.setMapping(ui->centerVerticallyButton, static_cast(PDFPageContentElementManipulator::Operation::CenterVertically)); + m_operationMapper.setMapping(ui->centerRectButton, static_cast(PDFPageContentElementManipulator::Operation::CenterHorAndVert)); + m_operationMapper.setMapping(ui->layoutHorizontallyButton, static_cast(PDFPageContentElementManipulator::Operation::LayoutHorizontally)); + m_operationMapper.setMapping(ui->layoutVerticallyButton, static_cast(PDFPageContentElementManipulator::Operation::LayoutVertically)); + m_operationMapper.setMapping(ui->layoutFormButton, static_cast(PDFPageContentElementManipulator::Operation::LayoutForm)); + m_operationMapper.setMapping(ui->layoutGridButton, static_cast(PDFPageContentElementManipulator::Operation::LayoutGrid)); + + connect(ui->alignVertTopButton, &QToolButton::clicked, &m_operationMapper, QOverload<>::of(&QSignalMapper::map)); + connect(ui->alignVertMiddleButton, &QToolButton::clicked, &m_operationMapper, QOverload<>::of(&QSignalMapper::map)); + connect(ui->alignVertBottomButton, &QToolButton::clicked, &m_operationMapper, QOverload<>::of(&QSignalMapper::map)); + connect(ui->alignHorLeftButton, &QToolButton::clicked, &m_operationMapper, QOverload<>::of(&QSignalMapper::map)); + connect(ui->alignHorMiddleButton, &QToolButton::clicked, &m_operationMapper, QOverload<>::of(&QSignalMapper::map)); + connect(ui->alignHorRightButton, &QToolButton::clicked, &m_operationMapper, QOverload<>::of(&QSignalMapper::map)); + connect(ui->setSameWidthButton, &QToolButton::clicked, &m_operationMapper, QOverload<>::of(&QSignalMapper::map)); + connect(ui->setSameHeightButton, &QToolButton::clicked, &m_operationMapper, QOverload<>::of(&QSignalMapper::map)); + connect(ui->setSameSizeButton, &QToolButton::clicked, &m_operationMapper, QOverload<>::of(&QSignalMapper::map)); + connect(ui->centerHorizontallyButton, &QToolButton::clicked, &m_operationMapper, QOverload<>::of(&QSignalMapper::map)); + connect(ui->centerVerticallyButton, &QToolButton::clicked, &m_operationMapper, QOverload<>::of(&QSignalMapper::map)); + connect(ui->centerRectButton, &QToolButton::clicked, &m_operationMapper, QOverload<>::of(&QSignalMapper::map)); + connect(ui->layoutHorizontallyButton, &QToolButton::clicked, &m_operationMapper, QOverload<>::of(&QSignalMapper::map)); + connect(ui->layoutVerticallyButton, &QToolButton::clicked, &m_operationMapper, QOverload<>::of(&QSignalMapper::map)); + connect(ui->layoutFormButton, &QToolButton::clicked, &m_operationMapper, QOverload<>::of(&QSignalMapper::map)); + connect(ui->layoutGridButton, &QToolButton::clicked, &m_operationMapper, QOverload<>::of(&QSignalMapper::map)); + + connect(&m_actionMapper, &QSignalMapper::mappedObject, this, &PDFPageContentEditorWidget::onActionTriggerRequest); + connect(&m_operationMapper, &QSignalMapper::mappedInt, this, &PDFPageContentEditorWidget::operationTriggered); + connect(ui->itemsListWidget->selectionModel(), &QItemSelectionModel::selectionChanged, this, &PDFPageContentEditorWidget::onItemSelectionChanged); + + connect(ui->appearanceSettingsWidget, &PDFPageContentEditorStyleSettings::penChanged, this, &PDFPageContentEditorWidget::penChanged); + connect(ui->appearanceSettingsWidget, &PDFPageContentEditorStyleSettings::brushChanged, this, &PDFPageContentEditorWidget::brushChanged); + connect(ui->appearanceSettingsWidget, &PDFPageContentEditorStyleSettings::fontChanged, this, &PDFPageContentEditorWidget::fontChanged); + connect(ui->appearanceSettingsWidget, &PDFPageContentEditorStyleSettings::alignmentChanged, this, &PDFPageContentEditorWidget::alignmentChanged); + connect(ui->appearanceSettingsWidget, &PDFPageContentEditorStyleSettings::textAngleChanged, this, &PDFPageContentEditorWidget::textAngleChanged); +} + +PDFPageContentEditorWidget::~PDFPageContentEditorWidget() +{ + delete ui; +} + +void PDFPageContentEditorWidget::addAction(QAction* action) +{ + // First, find position for our action + int row = 0; + int column = 0; + + while (true) + { + if (!ui->toolGroupBoxLayout->itemAtPosition(row, column)) + { + break; + } + + ++column; + + if (column == m_toolBoxColumnCount) + { + column = 0; + ++row; + } + } + + QToolButton* button = new QToolButton(this); + button->setIcon(action->icon()); + button->setText(action->text()); + button->setToolTip(action->toolTip()); + button->setCheckable(action->isCheckable()); + button->setChecked(action->isChecked()); + button->setEnabled(action->isEnabled()); + button->setShortcut(action->shortcut()); + button->setIconSize(m_toolButtonIconSize); + m_actionMapper.setMapping(button, action); + connect(button, &QToolButton::clicked, &m_actionMapper, QOverload<>::of(&QSignalMapper::map)); + connect(action, &QAction::changed, this, &PDFPageContentEditorWidget::onActionChanged); + + ui->toolGroupBoxLayout->addWidget(button, row, column, Qt::AlignCenter); +} + +QToolButton* PDFPageContentEditorWidget::getToolButtonForOperation(int operation) const +{ + return qobject_cast(m_operationMapper.mapping(operation)); +} + +void PDFPageContentEditorWidget::updateItemsInListWidget() +{ + if (!m_updatesEnabled) + { + return; + } + + pdf::PDFTemporaryValueChange guard(&m_updatesEnabled, false); + ui->itemsListWidget->setUpdatesEnabled(false); + + if (m_scene) + { + std::set presentElementIds; + std::set elementIds = m_scene->getElementIds(); + + // Remove items which are not here + for (int i = 0; i < ui->itemsListWidget->count();) + { + QListWidgetItem* item = ui->itemsListWidget->item(i); + const PDFInteger elementId = item->data(Qt::UserRole).toLongLong(); + if (!elementIds.count(elementId)) + { + delete ui->itemsListWidget->takeItem(i); + } + else + { + presentElementIds.insert(elementId); + ++i; + } + } + + // Add items which are here + for (PDFInteger elementId : elementIds) + { + if (presentElementIds.count(elementId)) + { + continue; + } + + const PDFPageContentElement* element = m_scene->getElementById(elementId); + Q_ASSERT(element); + + QListWidgetItem* item = new QListWidgetItem(element->getDescription()); + item->setData(Qt::UserRole, elementId); + + ui->itemsListWidget->addItem(item); + } + } + else + { + ui->itemsListWidget->clear(); + } + + ui->itemsListWidget->setUpdatesEnabled(true); +} + +void PDFPageContentEditorWidget::onActionTriggerRequest(QObject* actionObject) +{ + QAction* action = qobject_cast(actionObject); + Q_ASSERT(action); + + action->trigger(); +} + +void PDFPageContentEditorWidget::onActionChanged() +{ + QAction* action = qobject_cast(sender()); + QToolButton* button = qobject_cast(m_actionMapper.mapping(action)); + + Q_ASSERT(action); + Q_ASSERT(button); + + button->setChecked(action->isChecked()); + button->setEnabled(action->isEnabled()); +} + +void PDFPageContentEditorWidget::onItemSelectionChanged() +{ + if (m_selectionChangeEnabled) + { + emit itemSelectionChangedByUser(); + } +} + +PDFPageContentScene* PDFPageContentEditorWidget::scene() const +{ + return m_scene; +} + +void PDFPageContentEditorWidget::setScene(PDFPageContentScene* newScene) +{ + if (m_scene != newScene) + { + m_scene = newScene; + updateItemsInListWidget(); + } +} + +std::set PDFPageContentEditorWidget::getSelection() const +{ + std::set result; + + for (int i = 0; i < ui->itemsListWidget->count(); ++i) + { + QListWidgetItem* item = ui->itemsListWidget->item(i); + if (item->isSelected()) + { + const PDFInteger elementId = item->data(Qt::UserRole).toLongLong(); + result.insert(elementId); + } + } + + return result; +} + +void PDFPageContentEditorWidget::setSelection(const std::set& selection) +{ + pdf::PDFTemporaryValueChange guard(&m_selectionChangeEnabled, false); + + for (int i = 0; i < ui->itemsListWidget->count(); ++i) + { + QListWidgetItem* item = ui->itemsListWidget->item(i); + const PDFInteger elementId = item->data(Qt::UserRole).toLongLong(); + item->setSelected(selection.count(elementId)); + } +} + +void PDFPageContentEditorWidget::loadStyleFromElement(const PDFPageContentElement* element) +{ + ui->appearanceSettingsWidget->loadFromElement(element, false); +} + +} // namespace pdf diff --git a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.h b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.h new file mode 100644 index 0000000..4e60b7a --- /dev/null +++ b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.h @@ -0,0 +1,93 @@ +// Copyright (C) 2022 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef PDFPAGECONTENTEDITORWIDGET_H +#define PDFPAGECONTENTEDITORWIDGET_H + +#include "pdfglobal.h" + +#include +#include + +#include + +class QToolButton; + +namespace Ui +{ +class PDFPageContentEditorWidget; +} + +namespace pdf +{ +class PDFPageContentScene; +class PDFPageContentElement; + +class PDF4QTLIBSHARED_EXPORT PDFPageContentEditorWidget : public QDockWidget +{ + Q_OBJECT + +public: + explicit PDFPageContentEditorWidget(QWidget* parent); + virtual ~PDFPageContentEditorWidget() override; + + /// Adds external action to the tool box + void addAction(QAction* action); + + QToolButton* getToolButtonForOperation(int operation) const; + + /// Update items in list widget + void updateItemsInListWidget(); + + PDFPageContentScene* scene() const; + void setScene(PDFPageContentScene* newScene); + + std::set getSelection() const; + void setSelection(const std::set& selection); + + /// Loads style from element, element can be nullptr + /// \param element Element + void loadStyleFromElement(const PDFPageContentElement* element); + +signals: + void operationTriggered(int operation); + void itemSelectionChangedByUser(); + + void penChanged(const QPen& pen); + void brushChanged(const QBrush& brush); + void fontChanged(const QFont& font); + void alignmentChanged(Qt::Alignment alignment); + void textAngleChanged(pdf::PDFReal angle); + +private: + void onActionTriggerRequest(QObject* actionObject); + void onActionChanged(); + void onItemSelectionChanged(); + + Ui::PDFPageContentEditorWidget* ui; + QSignalMapper m_actionMapper; + QSignalMapper m_operationMapper; + int m_toolBoxColumnCount; + QSize m_toolButtonIconSize; + PDFPageContentScene* m_scene; + bool m_selectionChangeEnabled; + bool m_updatesEnabled; +}; + +} // namespace pdf + +#endif // PDFPAGECONTENTEDITORWIDGET_H diff --git a/Pdf4QtLib/sources/pdfpagecontenteditorwidget.ui b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.ui new file mode 100644 index 0000000..f9bdde7 --- /dev/null +++ b/Pdf4QtLib/sources/pdfpagecontenteditorwidget.ui @@ -0,0 +1,215 @@ + + + PDFPageContentEditorWidget + + + + 0 + 0 + 333 + 607 + + + + Qt::LeftDockWidgetArea|Qt::RightDockWidgetArea + + + Content editor + + + + + + + Toolbox + + + + + + + + Geometry Tools + + + + + + Align to Top + + + + + + + Align to Vertical Center + + + + + + + Align to Bottom + + + + + + + Set Same Width + + + + + + + Set Same Height + + + + + + + Set Same Size + + + + + + + Align to Left + + + + + + + Align to Horizontal Center + + + + + + + Align to Right + + + + + + + Center Horizontally + + + + + + + Center Vertically + + + + + + + Center to Page Media Box + + + + + + + + + + Layout Tools + + + + + + Make Horizontal Layout + + + + + + + Make Vertical Layout + + + + + + + Make Form Layout + + + + + + + Make Grid Layout + + + + + + + + + + Appearance + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + Content + + + + + + QAbstractItemView::ExtendedSelection + + + + + + + + + + + + pdf::PDFPageContentEditorStyleSettings + QWidget +
pdfpagecontenteditorstylesettings.h
+ 1 +
+
+ + +
diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.cpp b/Pdf4QtLib/sources/pdfpagecontentelements.cpp new file mode 100644 index 0000000..48d8607 --- /dev/null +++ b/Pdf4QtLib/sources/pdfpagecontentelements.cpp @@ -0,0 +1,2535 @@ +// Copyright (C) 2022 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#include "pdfpagecontentelements.h" +#include "pdfpainterutils.h" +#include "pdfdrawwidget.h" +#include "pdfdrawspacecontroller.h" +#include "pdfwidgetutils.h" +#include "pdfutils.h" + +#include +#include +#include +#include +#include +#include + +namespace pdf +{ + +PDFInteger PDFPageContentElement::getPageIndex() const +{ + return m_pageIndex; +} + +void PDFPageContentElement::setPageIndex(PDFInteger newPageIndex) +{ + m_pageIndex = newPageIndex; +} + +PDFInteger PDFPageContentElement::getElementId() const +{ + return m_elementId; +} + +void PDFPageContentElement::setElementId(PDFInteger newElementId) +{ + m_elementId = newElementId; +} + +Qt::CursorShape PDFPageContentElement::getCursorShapeForManipulationMode(uint mode) +{ + switch (mode) + { + case None: + case Pt1: + case Pt2: + case Translate: + return Qt::ArrowCursor; + + case Top: + case Bottom: + return Qt::SizeVerCursor; + + case Left: + case Right: + return Qt::SizeHorCursor; + + case TopLeft: + case BottomRight: + return Qt::SizeBDiagCursor; + + case TopRight: + case BottomLeft: + return Qt::SizeFDiagCursor; + + default: + Q_ASSERT(false); + break; + } + + return Qt::ArrowCursor; +} + +uint PDFPageContentElement::getRectangleManipulationMode(const QRectF& rectangle, + const QPointF& point, + PDFReal snapPointDistanceThreshold) const +{ + if ((rectangle.topLeft() - point).manhattanLength() < snapPointDistanceThreshold) + { + return TopLeft; + } + + if ((rectangle.topRight() - point).manhattanLength() < snapPointDistanceThreshold) + { + return TopRight; + } + + if ((rectangle.bottomLeft() - point).manhattanLength() < snapPointDistanceThreshold) + { + return BottomLeft; + } + + if ((rectangle.bottomRight() - point).manhattanLength() < snapPointDistanceThreshold) + { + return BottomRight; + } + + if (rectangle.left() <= point.x() && + point.x() <= rectangle.right() && + (qAbs(rectangle.top() - point.y()) < snapPointDistanceThreshold)) + { + return Top; + } + + if (rectangle.left() <= point.x() && + point.x() <= rectangle.right() && + (qAbs(rectangle.bottom() - point.y()) < snapPointDistanceThreshold)) + { + return Bottom; + } + + if (rectangle.top() <= point.y() && + point.y() <= rectangle.bottom() && + (qAbs(rectangle.left() - point.x()) < snapPointDistanceThreshold)) + { + return Left; + } + + if (rectangle.top() <= point.y() && + point.y() <= rectangle.bottom() && + (qAbs(rectangle.right() - point.x()) < snapPointDistanceThreshold)) + { + return Right; + } + + if (rectangle.contains(point)) + { + return Translate; + } + + return None; +} + +void PDFPageContentElement::performRectangleManipulation(QRectF& rectangle, + uint mode, + const QPointF& offset) +{ + switch (mode) + { + case None: + break; + + case Translate: + rectangle.translate(offset); + break; + + case Top: + rectangle.setTop(qMin(rectangle.bottom(), rectangle.top() + offset.y())); + break; + + case Left: + rectangle.setLeft(qMin(rectangle.right(), rectangle.left() + offset.x())); + break; + + case Right: + rectangle.setRight(qMax(rectangle.left(), rectangle.right() + offset.x())); + break; + + case Bottom: + rectangle.setBottom(qMax(rectangle.top(), rectangle.bottom() + offset.y())); + break; + + case TopLeft: + rectangle.setTop(qMin(rectangle.bottom(), rectangle.top() + offset.y())); + rectangle.setLeft(qMin(rectangle.right(), rectangle.left() + offset.x())); + break; + + case TopRight: + rectangle.setTop(qMin(rectangle.bottom(), rectangle.top() + offset.y())); + rectangle.setRight(qMax(rectangle.left(), rectangle.right() + offset.x())); + break; + + case BottomLeft: + rectangle.setBottom(qMax(rectangle.top(), rectangle.bottom() + offset.y())); + rectangle.setLeft(qMin(rectangle.right(), rectangle.left() + offset.x())); + break; + + case BottomRight: + rectangle.setBottom(qMax(rectangle.top(), rectangle.bottom() + offset.y())); + rectangle.setRight(qMax(rectangle.left(), rectangle.right() + offset.x())); + break; + + default: + Q_ASSERT(false); + break; + } +} + +void PDFPageContentElement::performRectangleSetSize(QRectF& rectangle, QSizeF size) +{ + const qreal offset = rectangle.size().height() - size.height(); + rectangle.setSize(size); + rectangle.translate(0, offset); +} + +QString PDFPageContentElement::formatDescription(const QString& description) const +{ + return PDFTranslationContext::tr("#%1: %2").arg(getElementId()).arg(description); +} + +const QPen& PDFPageContentStyledElement::getPen() const +{ + return m_pen; +} + +void PDFPageContentStyledElement::setPen(const QPen& newPen) +{ + m_pen = newPen; +} + +const QBrush& PDFPageContentStyledElement::getBrush() const +{ + return m_brush; +} + +void PDFPageContentStyledElement::setBrush(const QBrush& newBrush) +{ + m_brush = newBrush; +} + +PDFPageContentElementRectangle* PDFPageContentElementRectangle::clone() const +{ + PDFPageContentElementRectangle* copy = new PDFPageContentElementRectangle(); + copy->setElementId(getElementId()); + copy->setPageIndex(getPageIndex()); + copy->setPen(getPen()); + copy->setBrush(getBrush()); + copy->setRectangle(getRectangle()); + copy->setRounded(isRounded()); + return copy; +} + +bool PDFPageContentElementRectangle::isRounded() const +{ + return m_rounded; +} + +void PDFPageContentElementRectangle::setRounded(bool newRounded) +{ + m_rounded = newRounded; +} + +const QRectF& PDFPageContentElementRectangle::getRectangle() const +{ + return m_rectangle; +} + +void PDFPageContentElementRectangle::setRectangle(const QRectF& newRectangle) +{ + m_rectangle = newRectangle; +} + +void PDFPageContentElementRectangle::drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const +{ + Q_UNUSED(compiledPage); + Q_UNUSED(layoutGetter); + Q_UNUSED(errors); + + if (pageIndex != getPageIndex()) + { + return; + } + + PDFPainterStateGuard guard(painter); + painter->setWorldMatrix(pagePointToDevicePointMatrix, true); + painter->setPen(getPen()); + painter->setBrush(getBrush()); + painter->setRenderHint(QPainter::Antialiasing); + + QRectF rect = getRectangle(); + if (isRounded()) + { + qreal radius = qMin(rect.width(), rect.height()) * 0.25; + painter->drawRoundedRect(rect, radius, radius, Qt::AbsoluteSize); + } + else + { + painter->drawRect(rect); + } +} + +uint PDFPageContentElementRectangle::getManipulationMode(const QPointF& point, + PDFReal snapPointDistanceThreshold) const +{ + return getRectangleManipulationMode(getRectangle(), point, snapPointDistanceThreshold); +} + +void PDFPageContentElementRectangle::performManipulation(uint mode, const QPointF& offset) +{ + performRectangleManipulation(m_rectangle, mode, offset); +} + +QRectF PDFPageContentElementRectangle::getBoundingBox() const +{ + return getRectangle(); +} + +void PDFPageContentElementRectangle::setSize(QSizeF size) +{ + performRectangleSetSize(m_rectangle, size); +} + +QString PDFPageContentElementRectangle::getDescription() const +{ + return formatDescription(isRounded() ? PDFTranslationContext::tr("Rounded rectangle") : PDFTranslationContext::tr("Rectangle")); +} + +PDFPageContentScene::PDFPageContentScene(QObject* parent) : + QObject(parent), + m_firstFreeId(1), + m_isActive(false), + m_widget(nullptr), + m_manipulator(this, nullptr) +{ + connect(&m_manipulator, &PDFPageContentElementManipulator::selectionChanged, this, &PDFPageContentScene::onSelectionChanged); +} + +PDFPageContentScene::~PDFPageContentScene() +{ + +} + +void PDFPageContentScene::addElement(PDFPageContentElement* element) +{ + element->setElementId(m_firstFreeId++); + m_elements.emplace_back(element); + emit sceneChanged(false); +} + +void PDFPageContentScene::replaceElement(PDFPageContentElement* element) +{ + std::unique_ptr elementPtr(element); + + for (size_t i = 0; i < m_elements.size(); ++i) + { + if (m_elements[i]->getElementId() == element->getElementId()) + { + m_elements[i] = std::move(elementPtr); + emit sceneChanged(false); + break; + } + } +} + +PDFPageContentElement* PDFPageContentScene::getElementById(PDFInteger id) const +{ + auto it = std::find_if(m_elements.cbegin(), m_elements.cend(), [id](const auto& element) { return element->getElementId() == id; }); + if (it != m_elements.cend()) + { + return it->get(); + } + + return nullptr; +} + +void PDFPageContentScene::clear() +{ + if (!m_elements.empty()) + { + m_manipulator.reset(); + m_elements.clear(); + emit sceneChanged(false); + } +} + +void PDFPageContentScene::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) +{ + Q_UNUSED(widget); + + constexpr QKeySequence::StandardKey acceptedKeys[] = { QKeySequence::Delete, + QKeySequence::SelectAll, + QKeySequence::Deselect, + QKeySequence::Cancel }; + + if (std::any_of(std::begin(acceptedKeys), std::end(acceptedKeys), [event](QKeySequence::StandardKey standardKey) { return event == standardKey; })) + { + event->accept(); + return; + } +} + +void PDFPageContentScene::keyPressEvent(QWidget* widget, QKeyEvent* event) +{ + Q_UNUSED(widget); + event->ignore(); + + if (event == QKeySequence::Delete) + { + if (!m_manipulator.isSelectionEmpty()) + { + m_manipulator.performDeleteSelection(); + event->accept(); + } + } + else if (event == QKeySequence::SelectAll) + { + if (!isEmpty()) + { + m_manipulator.selectAll(); + event->accept(); + } + } + else if (event == QKeySequence::Deselect) + { + if (!m_manipulator.isSelectionEmpty()) + { + m_manipulator.deselectAll(); + event->accept(); + } + } + else if (event == QKeySequence::Cancel) + { + if (m_manipulator.isManipulationInProgress()) + { + m_manipulator.cancelManipulation(); + m_manipulator.deselectAll(); + event->accept(); + } + } +} + +void PDFPageContentScene::keyReleaseEvent(QWidget* widget, QKeyEvent* event) +{ + Q_UNUSED(widget); + event->ignore(); +} + +void PDFPageContentScene::mousePressEvent(QWidget* widget, QMouseEvent* event) +{ + if (!isActive()) + { + return; + } + + MouseEventInfo info = getMouseEventInfo(widget, event->pos()); + if (info.isValid() || isMouseGrabbed()) + { + // We to handle selecting/deselecting the active + // item. After that, accept the item. + if (info.isValid() && event->button() == Qt::LeftButton) + { + info.widgetMouseStartPos = event->pos(); + info.timer.start(); + + if (!m_manipulator.isManipulationInProgress()) + { + Qt::KeyboardModifiers keyboardModifiers = QApplication::keyboardModifiers(); + const bool isCtrl = keyboardModifiers.testFlag(Qt::ControlModifier); + const bool isShift = keyboardModifiers.testFlag(Qt::ShiftModifier); + + if (isCtrl && !isShift) + { + m_manipulator.select(info.hoveredElementIds); + } + else if (!isCtrl && isShift) + { + m_manipulator.deselect(info.hoveredElementIds); + } + else if (!m_manipulator.isAllSelected(info.hoveredElementIds)) + { + m_manipulator.selectNew(info.hoveredElementIds); + } + } + + event->accept(); + } + + grabMouse(info, event); + } + else if (event->button() == Qt::LeftButton) + { + m_manipulator.deselectAll(); + } + + updateMouseCursor(info, getSnapPointDistanceThreshold()); +} + +void PDFPageContentScene::mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event) +{ + Q_UNUSED(widget); + + if (!isActive()) + { + return; + } + + MouseEventInfo info = getMouseEventInfo(widget, event->pos()); + if (info.isValid()) + { + emit editElementRequest(info.hoveredElementIds); + } + + // If mouse is grabbed, then event is accepted always (because + // we get Press event, when we grabbed the mouse, then we will + // wait for corresponding release event while all mouse move events + // will be accepted, even if editor doesn't accept them. + if (isMouseGrabbed()) + { + event->accept(); + } +} + +void PDFPageContentScene::mouseReleaseEvent(QWidget* widget, QMouseEvent* event) +{ + Q_UNUSED(widget); + + if (!isActive()) + { + return; + } + + if (isMouseGrabbed()) + { + if (event->button() == Qt::LeftButton) + { + event->accept(); + + if (m_manipulator.isManipulationInProgress()) + { + QPointF pagePoint; + const PDFInteger pageIndex = m_widget->getDrawWidgetProxy()->getPageUnderPoint(event->pos(), &pagePoint); + m_mouseGrabInfo.info.widgetMouseCurrentPos = event->pos(); + bool isCopyCreated = QApplication::keyboardModifiers().testFlag(Qt::ControlModifier); + m_manipulator.finishManipulation(pageIndex, m_mouseGrabInfo.info.pagePos, pagePoint, isCopyCreated); + } + } + + ungrabMouse(getMouseEventInfo(widget, event->pos()), event); + } + + MouseEventInfo info = getMouseEventInfo(widget, event->pos()); + updateMouseCursor(info, getSnapPointDistanceThreshold()); +} + +void PDFPageContentScene::mouseMoveEvent(QWidget* widget, QMouseEvent* event) +{ + Q_UNUSED(widget); + + if (!isActive()) + { + return; + } + + const PDFReal threshold = getSnapPointDistanceThreshold(); + + QPointF pagePoint; + const PDFInteger pageIndex = m_widget->getDrawWidgetProxy()->getPageUnderPoint(event->pos(), &pagePoint); + if (m_mouseGrabInfo.info.isValid() && + event->buttons().testFlag(Qt::LeftButton) && + m_mouseGrabInfo.info.pageIndex == pageIndex && + m_manipulator.isManipulationAllowed(pageIndex)) + { + // Jakub Melka: Start grab? + m_mouseGrabInfo.info.widgetMouseCurrentPos = event->pos(); + + if (!m_manipulator.isManipulationInProgress()) + { + QPoint vector = m_mouseGrabInfo.info.widgetMouseCurrentPos - m_mouseGrabInfo.info.widgetMouseStartPos; + if (vector.manhattanLength() > QApplication::startDragDistance() || + m_mouseGrabInfo.info.timer.hasExpired(QApplication::startDragTime())) + { + m_manipulator.startManipulation(pageIndex, m_mouseGrabInfo.info.pagePos, pagePoint, threshold); + } + } + else + { + m_manipulator.updateManipulation(pageIndex, m_mouseGrabInfo.info.pagePos, pagePoint); + } + } + + MouseEventInfo info = getMouseEventInfo(widget, event->pos()); + updateMouseCursor(info, threshold); + + // If mouse is grabbed, then event is accepted always (because + // we get Press event, when we grabbed the mouse, then we will + // wait for corresponding release event while all mouse move events + // will be accepted, even if editor doesn't accept them. + if (isMouseGrabbed()) + { + event->accept(); + } + + if (m_manipulator.isManipulationInProgress()) + { + emit sceneChanged(true); + } +} + +void PDFPageContentScene::wheelEvent(QWidget* widget, QWheelEvent* event) +{ + Q_UNUSED(widget); + + if (!isActive()) + { + return; + } + + // We will accept mouse wheel events, if we are grabbing the mouse. + // We do not want to zoom in/zoom out while grabbing. + if (isMouseGrabbed()) + { + event->accept(); + } +} + +QString PDFPageContentScene::getTooltip() const +{ + return QString(); +} + +const std::optional& PDFPageContentScene::getCursor() const +{ + return m_cursor; +} + +int PDFPageContentScene::getInputPriority() const +{ + return ToolPriority + 1; +} + +void PDFPageContentScene::drawElements(QPainter* painter, + PDFInteger pageIndex, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + const PDFPrecompiledPage* compiledPage, + QList& errors) const +{ + for (const auto& element : m_elements) + { + if (element->getPageIndex() != pageIndex) + { + continue; + } + + element->drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); + } +} + +void PDFPageContentScene::drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const +{ + if (!m_isActive) + { + return; + } + + drawElements(painter, pageIndex, layoutGetter, pagePointToDevicePointMatrix, compiledPage, errors); + m_manipulator.drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); +} + +PDFPageContentScene::MouseEventInfo PDFPageContentScene::getMouseEventInfo(QWidget* widget, QPoint point) +{ + MouseEventInfo result; + + Q_UNUSED(widget); + Q_ASSERT(isActive()); + + if (isMouseGrabbed()) + { + result = m_mouseGrabInfo.info; + result.widgetMouseCurrentPos = point; + return result; + } + + result.widgetMouseStartPos = point; + result.widgetMouseCurrentPos = point; + result.timer = m_mouseGrabInfo.info.timer; + result.pageIndex = m_widget->getDrawWidgetProxy()->getPageUnderPoint(point, &result.pagePos); + + const PDFReal threshold = getSnapPointDistanceThreshold(); + for (const auto& elementItem : m_elements) + { + PDFPageContentElement* element = elementItem.get(); + + if (element->getPageIndex() != result.pageIndex) + { + // Different page + continue; + } + + if (element->getManipulationMode(result.pagePos, threshold) != 0) + { + result.hoveredElementIds.insert(element->getElementId()); + } + } + + return result; +} + +PDFReal PDFPageContentScene::getSnapPointDistanceThreshold() const +{ + const PDFReal snapPointDistanceThresholdPixels = PDFWidgetUtils::scaleDPI_x(m_widget, 6.0); + const PDFReal snapPointDistanceThreshold = m_widget->getDrawWidgetProxy()->transformPixelToDeviceSpace(snapPointDistanceThresholdPixels); + return snapPointDistanceThreshold; +} + +void PDFPageContentScene::grabMouse(const MouseEventInfo& info, QMouseEvent* event) +{ + Q_ASSERT(isActive()); + + if (event->type() == QEvent::MouseButtonDblClick) + { + // Double clicks doesn't grab the mouse + return; + } + + Q_ASSERT(event->type() == QEvent::MouseButtonPress); + + if (isMouseGrabbed()) + { + // If mouse is already grabbed, then when new mouse button is pressed, + // we just increase nesting level and accept the mouse event. We are + // accepting all mouse events, if mouse is grabbed. + ++m_mouseGrabInfo.mouseGrabNesting; + event->accept(); + } + else if (event->isAccepted()) + { + // Event is accepted and we are not grabbing the mouse. We must start + // grabbing the mouse. + Q_ASSERT(m_mouseGrabInfo.mouseGrabNesting == 0); + ++m_mouseGrabInfo.mouseGrabNesting; + m_mouseGrabInfo.info = info; + } +} + +void PDFPageContentScene::ungrabMouse(const MouseEventInfo& info, QMouseEvent* event) +{ + Q_UNUSED(info); + Q_ASSERT(isActive()); + Q_ASSERT(event->type() == QEvent::MouseButtonRelease); + + if (isMouseGrabbed()) + { + // Mouse is being grabbed, decrease nesting level. We must also accept + // mouse release event, because mouse is being grabbed. + --m_mouseGrabInfo.mouseGrabNesting; + event->accept(); + + if (!isMouseGrabbed()) + { + m_mouseGrabInfo.info = MouseEventInfo(); + } + } + + Q_ASSERT(m_mouseGrabInfo.mouseGrabNesting >= 0); +} + +void PDFPageContentScene::updateMouseCursor(const MouseEventInfo& info, PDFReal snapPointDistanceThreshold) +{ + std::optional cursorShapeValue; + + for (const PDFInteger id : info.hoveredElementIds) + { + PDFPageContentElement* element = getElementById(id); + uint manipulationMode = element->getManipulationMode(info.pagePos, snapPointDistanceThreshold); + + if (manipulationMode > 0) + { + Qt::CursorShape cursorShape = PDFPageContentElement::getCursorShapeForManipulationMode(manipulationMode); + + if (!cursorShapeValue) + { + cursorShapeValue = cursorShape; + } + else if (cursorShapeValue.value() != cursorShape) + { + cursorShapeValue = Qt::ArrowCursor; + break; + } + } + } + + if (cursorShapeValue && cursorShapeValue.value() == Qt::ArrowCursor && + m_manipulator.isManipulationInProgress()) + { + const bool isCopy = QApplication::keyboardModifiers().testFlag(Qt::ControlModifier); + cursorShapeValue = isCopy ? Qt::DragCopyCursor : Qt::DragMoveCursor; + } + + // Update cursor shape + if (!cursorShapeValue) + { + m_cursor = std::nullopt; + } + else if (!m_cursor.has_value() || m_cursor.value().shape() != cursorShapeValue.value()) + { + m_cursor = QCursor(cursorShapeValue.value()); + } +} + +void PDFPageContentScene::onSelectionChanged() +{ + emit sceneChanged(true); + emit selectionChanged(); +} + +PDFWidget* PDFPageContentScene::widget() const +{ + return m_widget; +} + +void PDFPageContentScene::setWidget(PDFWidget* newWidget) +{ + m_widget = newWidget; +} + +bool PDFPageContentScene::isActive() const +{ + return m_isActive; +} + +void PDFPageContentScene::setActive(bool newIsActive) +{ + if (m_isActive != newIsActive) + { + m_isActive = newIsActive; + + if (!newIsActive) + { + m_mouseGrabInfo = MouseGrabInfo(); + m_manipulator.reset(); + } + + emit sceneChanged(false); + } +} + +std::set PDFPageContentScene::getElementIds() const +{ + std::set result; + + for (const auto& element : m_elements) + { + result.insert(element->getElementId()); + } + + return result; +} + +std::set PDFPageContentScene::getSelectedElementIds() const +{ + std::set result; + + for (const auto& element : m_elements) + { + if (m_manipulator.isSelected(element->getElementId())) + { + result.insert(element->getElementId()); + } + } + + return result; +} + +std::set PDFPageContentScene::getPageIndices() const +{ + std::set result; + + for (const auto& element : m_elements) + { + result.insert(element->getPageIndex()); + } + + return result; +} + +QRectF PDFPageContentScene::getBoundingBox(PDFInteger pageIndex) const +{ + QRectF rect; + + for (const auto& element : m_elements) + { + if (element->getPageIndex() == pageIndex) + { + rect = rect.united(element->getBoundingBox()); + } + } + + return rect; +} + +void PDFPageContentScene::setSelectedElementIds(const std::set& selectedElementIds) +{ + m_manipulator.selectNew(selectedElementIds); +} + +void PDFPageContentScene::removeElementsById(const std::vector& selection) +{ + const size_t oldSize = m_elements.size(); + m_elements.erase(std::remove_if(m_elements.begin(), m_elements.end(), [&selection](const auto& element){ return std::find(selection.cbegin(), selection.cend(), element->getElementId()) != selection.cend(); }), m_elements.end()); + const size_t newSize = m_elements.size(); + + if (newSize < oldSize) + { + emit sceneChanged(false); + } +} + +void PDFPageContentScene::performOperation(int operation) +{ + m_manipulator.performOperation(static_cast(operation)); +} + +const PDFDocument* PDFPageContentScene::getDocument() const +{ + if (m_widget) + { + return m_widget->getDrawWidgetProxy()->getDocument(); + } + + return nullptr; +} + +PDFPageContentElementLine* PDFPageContentElementLine::clone() const +{ + PDFPageContentElementLine* copy = new PDFPageContentElementLine(); + copy->setElementId(getElementId()); + copy->setPageIndex(getPageIndex()); + copy->setPen(getPen()); + copy->setBrush(getBrush()); + copy->setGeometry(getGeometry()); + copy->setLine(getLine()); + return copy; +} + +void PDFPageContentElementLine::drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const +{ + Q_UNUSED(compiledPage); + Q_UNUSED(layoutGetter); + Q_UNUSED(errors); + + if (pageIndex != getPageIndex()) + { + return; + } + + PDFPainterStateGuard guard(painter); + painter->setWorldMatrix(pagePointToDevicePointMatrix, true); + painter->setPen(getPen()); + painter->setBrush(getBrush()); + painter->setRenderHint(QPainter::Antialiasing); + + painter->drawLine(getLine()); +} + +uint PDFPageContentElementLine::getManipulationMode(const QPointF& point, + PDFReal snapPointDistanceThreshold) const +{ + if ((m_line.p1() - point).manhattanLength() < snapPointDistanceThreshold) + { + return Pt1; + } + + if ((m_line.p2() - point).manhattanLength() < snapPointDistanceThreshold) + { + return Pt2; + } + + QPointF vl = m_line.p2() - m_line.p1(); + QPointF vp = point - m_line.p1(); + + const qreal lengthSquared = QPointF::dotProduct(vl, vl); + + if (qFuzzyIsNull(lengthSquared)) + { + return None; + } + + const qreal t = QPointF::dotProduct(vl, vp) / lengthSquared; + if (t >= 0.0 && t <= 1.0) + { + QPointF projectedPoint = m_line.p1() + t * vl; + if ((point - projectedPoint).manhattanLength() < snapPointDistanceThreshold) + { + return Translate; + } + } + + return None; +} + +void PDFPageContentElementLine::performManipulation(uint mode, const QPointF& offset) +{ + switch (mode) + { + case None: + break; + + case Pt1: + m_line.setP1(m_line.p1() + offset); + break; + + case Pt2: + m_line.setP2(m_line.p2() + offset); + break; + + case Translate: + m_line.translate(offset); + break; + + default: + Q_ASSERT(false); + break; + } +} + +QRectF PDFPageContentElementLine::getBoundingBox() const +{ + if (!qFuzzyIsNull(m_line.length())) + { + const qreal xMin = qMin(m_line.p1().x(), m_line.p2().x()); + const qreal xMax = qMax(m_line.p1().x(), m_line.p2().x()); + const qreal yMin = qMin(m_line.p1().y(), m_line.p2().y()); + const qreal yMax = qMax(m_line.p1().y(), m_line.p2().y()); + return QRectF(xMin, yMin, xMax - xMin, yMax - yMin); + } + + return QRectF(); +} + +void PDFPageContentElementLine::setSize(QSizeF size) +{ + QPointF p1 = m_line.p1(); + QPointF p2 = m_line.p2(); + + if (p1.x() < p2.x()) + { + p2.setX(p1.x() + size.width()); + } + else + { + p1.setX(p2.x() + size.width()); + } + + if (p1.y() < p2.y()) + { + p1.setY(p2.y() - size.height()); + } + else + { + p2.setY(p1.y() - size.height()); + } + + m_line.setPoints(p1, p2); +} + +QString PDFPageContentElementLine::getDescription() const +{ + return formatDescription(PDFTranslationContext::tr("Line")); +} + +PDFPageContentElementLine::LineGeometry PDFPageContentElementLine::getGeometry() const +{ + return m_geometry; +} + +void PDFPageContentElementLine::setGeometry(LineGeometry newGeometry) +{ + m_geometry = newGeometry; +} + +const QLineF& PDFPageContentElementLine::getLine() const +{ + return m_line; +} + +void PDFPageContentElementLine::setLine(const QLineF& newLine) +{ + m_line = newLine; + + if (m_geometry == LineGeometry::Horizontal) + { + m_line.setP2(QPointF(newLine.p2().x(), newLine.p1().y())); + } + + if (m_geometry == LineGeometry::Vertical) + { + m_line.setP2(QPointF(newLine.p1().x(), newLine.p2().y())); + } +} + +PDFPageContentImageElement::PDFPageContentImageElement() : + m_renderer(std::make_unique()) +{ + +} + +PDFPageContentImageElement::~PDFPageContentImageElement() +{ + +} + +PDFPageContentImageElement* PDFPageContentImageElement::clone() const +{ + PDFPageContentImageElement* copy = new PDFPageContentImageElement(); + copy->setElementId(getElementId()); + copy->setPageIndex(getPageIndex()); + copy->setRectangle(getRectangle()); + copy->setContent(getContent()); + return copy; +} + +void PDFPageContentImageElement::drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const +{ + Q_UNUSED(compiledPage); + Q_UNUSED(layoutGetter); + Q_UNUSED(errors); + + if (pageIndex != getPageIndex() || !getRectangle().isValid()) + { + return; + } + + PDFPainterStateGuard guard(painter); + painter->setWorldMatrix(pagePointToDevicePointMatrix, true); + painter->setRenderHint(QPainter::Antialiasing); + + if (m_renderer->isValid()) + { + QRectF viewBox = m_renderer->viewBoxF(); + if (!viewBox.isValid()) + { + return; + } + + QRectF renderBox = getRectangle(); + QSizeF viewBoxSize = viewBox.size(); + QSizeF renderBoxSize = viewBoxSize.scaled(renderBox.size(), Qt::KeepAspectRatio); + QRectF targetRenderBox = QRectF(QPointF(), renderBoxSize); + targetRenderBox.moveCenter(renderBox.center()); + + painter->translate(targetRenderBox.bottomLeft()); + painter->scale(1.0, -1.0); + targetRenderBox.moveTopLeft(QPointF(0, 0)); + + m_renderer->render(painter, targetRenderBox); + } + else if (!m_image.isNull()) + { + QRectF viewBox(QPointF(0, 0), m_image.size()); + + QRectF renderBox = getRectangle(); + QSizeF viewBoxSize = viewBox.size(); + QSizeF renderBoxSize = viewBoxSize.scaled(renderBox.size(), Qt::KeepAspectRatio); + QRectF targetRenderBox = QRectF(QPointF(), renderBoxSize); + targetRenderBox.moveCenter(renderBox.center()); + + painter->translate(targetRenderBox.bottomLeft()); + painter->scale(1.0, -1.0); + targetRenderBox.moveTopLeft(QPointF(0, 0)); + + painter->drawImage(targetRenderBox, m_image); + } +} + +uint PDFPageContentImageElement::getManipulationMode(const QPointF& point, PDFReal snapPointDistanceThreshold) const +{ + return getRectangleManipulationMode(getRectangle(), point, snapPointDistanceThreshold); +} + +void PDFPageContentImageElement::performManipulation(uint mode, const QPointF& offset) +{ + performRectangleManipulation(m_rectangle, mode, offset); +} + +QRectF PDFPageContentImageElement::getBoundingBox() const +{ + return getRectangle(); +} + +void PDFPageContentImageElement::setSize(QSizeF size) +{ + performRectangleSetSize(m_rectangle, size); +} + +QString PDFPageContentImageElement::getDescription() const +{ + return formatDescription(PDFTranslationContext::tr("SVG image")); +} + +const QByteArray& PDFPageContentImageElement::getContent() const +{ + return m_content; +} + +void PDFPageContentImageElement::setContent(const QByteArray& newContent) +{ + if (m_content != newContent) + { + m_content = newContent; + if (!m_renderer->load(m_content)) + { + QByteArray imageData = m_content; + QBuffer buffer(&imageData); + buffer.open(QBuffer::ReadOnly); + + QImageReader reader(&buffer); + m_image = reader.read(); + buffer.close();; + } + } +} + +const QRectF& PDFPageContentImageElement::getRectangle() const +{ + return m_rectangle; +} + +void PDFPageContentImageElement::setRectangle(const QRectF& newRectangle) +{ + m_rectangle = newRectangle; +} + +PDFPageContentElementDot* PDFPageContentElementDot::clone() const +{ + PDFPageContentElementDot* copy = new PDFPageContentElementDot(); + copy->setElementId(getElementId()); + copy->setPageIndex(getPageIndex()); + copy->setPen(getPen()); + copy->setBrush(getBrush()); + copy->setPoint(getPoint()); + return copy; +} + +void PDFPageContentElementDot::drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const +{ + Q_UNUSED(compiledPage); + Q_UNUSED(layoutGetter); + Q_UNUSED(errors); + + if (pageIndex != getPageIndex()) + { + return; + } + + PDFPainterStateGuard guard(painter); + painter->setWorldMatrix(pagePointToDevicePointMatrix, true); + painter->setRenderHint(QPainter::Antialiasing); + painter->setPen(getPen()); + painter->setBrush(getBrush()); + painter->drawPoint(m_point); +} + +uint PDFPageContentElementDot::getManipulationMode(const QPointF& point, + PDFReal snapPointDistanceThreshold) const +{ + if ((m_point - point).manhattanLength() < snapPointDistanceThreshold) + { + return Translate; + } + + return None; +} + +void PDFPageContentElementDot::performManipulation(uint mode, const QPointF& offset) +{ + switch (mode) + { + case None: + break; + + case Translate: + m_point += offset; + break; + + default: + Q_ASSERT(false); + break; + } +} + +QRectF PDFPageContentElementDot::getBoundingBox() const +{ + return QRectF(m_point, QSizeF(0.001, 0.001)); +} + +void PDFPageContentElementDot::setSize(QSizeF size) +{ + Q_UNUSED(size); +} + +QString PDFPageContentElementDot::getDescription() const +{ + return formatDescription(PDFTranslationContext::tr("Dot")); +} + +QPointF PDFPageContentElementDot::getPoint() const +{ + return m_point; +} + +void PDFPageContentElementDot::setPoint(QPointF newPoint) +{ + m_point = newPoint; +} + +PDFPageContentElementFreehandCurve* PDFPageContentElementFreehandCurve::clone() const +{ + PDFPageContentElementFreehandCurve* copy = new PDFPageContentElementFreehandCurve(); + copy->setElementId(getElementId()); + copy->setPageIndex(getPageIndex()); + copy->setPen(getPen()); + copy->setBrush(getBrush()); + copy->setCurve(getCurve()); + return copy; +} + +void PDFPageContentElementFreehandCurve::drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const +{ + Q_UNUSED(compiledPage); + Q_UNUSED(layoutGetter); + Q_UNUSED(errors); + + if (pageIndex != getPageIndex()) + { + return; + } + + PDFPainterStateGuard guard(painter); + painter->setWorldMatrix(pagePointToDevicePointMatrix, true); + painter->setPen(getPen()); + painter->setBrush(getBrush()); + painter->setRenderHint(QPainter::Antialiasing); + + painter->drawPath(getCurve()); +} + +uint PDFPageContentElementFreehandCurve::getManipulationMode(const QPointF& point, PDFReal snapPointDistanceThreshold) const +{ + Q_UNUSED(snapPointDistanceThreshold); + + if (m_curve.isEmpty()) + { + return None; + } + + if (m_curve.controlPointRect().contains(point)) + { + return Translate; + } + + return None; +} + +void PDFPageContentElementFreehandCurve::performManipulation(uint mode, const QPointF& offset) +{ + switch (mode) + { + case None: + break; + + case Translate: + m_curve.translate(offset); + break; + + default: + Q_ASSERT(false); + break; + } +} + +QRectF PDFPageContentElementFreehandCurve::getBoundingBox() const +{ + return m_curve.controlPointRect(); +} + +void PDFPageContentElementFreehandCurve::setSize(QSizeF size) +{ + Q_UNUSED(size); +} + +QString PDFPageContentElementFreehandCurve::getDescription() const +{ + return formatDescription(PDFTranslationContext::tr("Freehand curve")); +} + +QPainterPath PDFPageContentElementFreehandCurve::getCurve() const +{ + return m_curve; +} + +void PDFPageContentElementFreehandCurve::setCurve(QPainterPath newCurve) +{ + m_curve = newCurve; +} + +void PDFPageContentElementFreehandCurve::addStartPoint(const QPointF& point) +{ + m_curve.moveTo(point); +} + +void PDFPageContentElementFreehandCurve::addPoint(const QPointF& point) +{ + m_curve.lineTo(point); +} + +void PDFPageContentElementFreehandCurve::clear() +{ + setPageIndex(-1); + m_curve = QPainterPath(); +} + +PDFPageContentElementManipulator::PDFPageContentElementManipulator(PDFPageContentScene* scene, QObject* parent) : + QObject(parent), + m_scene(scene), + m_isManipulationInProgress(false) +{ + +} + +bool PDFPageContentElementManipulator::isSelected(PDFInteger id) const +{ + return std::find(m_selection.cbegin(), m_selection.cend(), id) != m_selection.cend(); +} + +bool PDFPageContentElementManipulator::isAllSelected(const std::set& elementIds) const +{ + return std::all_of(elementIds.cbegin(), elementIds.cend(), [this](PDFInteger id) { return isSelected(id); }); +} + +void PDFPageContentElementManipulator::reset() +{ + cancelManipulation(); + deselectAll(); +} + +void PDFPageContentElementManipulator::update(PDFInteger id, SelectionModes modes) +{ + bool modified = false; + + if (modes.testFlag(Clear)) + { + modified = !m_selection.empty(); + m_selection.clear(); + } + + // Is id valid? + if (id > 0) + { + if (modes.testFlag(Select)) + { + if (!isSelected(id)) + { + modified = true; + m_selection.push_back(id); + } + } + + if (modes.testFlag(Deselect)) + { + if (isSelected(id)) + { + modified = true; + eraseSelectedElementById(id); + } + } + + if (modes.testFlag(Toggle)) + { + if (isSelected(id)) + { + eraseSelectedElementById(id); + } + else + { + m_selection.push_back(id); + } + + // When toggle is performed, selection is always changed + modified = true; + } + } + + if (modified) + { + emit selectionChanged(); + } +} + +void PDFPageContentElementManipulator::update(const std::set& ids, SelectionModes modes) +{ + bool modified = false; + + if (modes.testFlag(Clear)) + { + modified = !m_selection.empty(); + m_selection.clear(); + } + + // Is id valid? + if (!ids.empty()) + { + if (modes.testFlag(Select)) + { + for (auto id : ids) + { + if (!isSelected(id)) + { + modified = true; + m_selection.push_back(id); + } + } + } + + if (modes.testFlag(Deselect)) + { + for (auto id : ids) + { + if (isSelected(id)) + { + modified = true; + eraseSelectedElementById(id); + } + } + } + + if (modes.testFlag(Toggle)) + { + for (auto id : ids) + { + if (isSelected(id)) + { + eraseSelectedElementById(id); + } + else + { + m_selection.push_back(id); + } + + // When toggle is performed, selection is always changed + modified = true; + } + } + } + + if (modified) + { + emit selectionChanged(); + } +} + +void PDFPageContentElementManipulator::select(PDFInteger id) +{ + update(id, Select); +} + +void PDFPageContentElementManipulator::select(const std::set& ids) +{ + update(ids, Select); +} + +void PDFPageContentElementManipulator::selectNew(PDFInteger id) +{ + update(id, SelectionModes(Select | Clear)); +} + +void PDFPageContentElementManipulator::selectNew(const std::set& ids) +{ + update(ids, SelectionModes(Select | Clear)); +} + +void PDFPageContentElementManipulator::deselect(PDFInteger id) +{ + update(id, Deselect); +} + +void PDFPageContentElementManipulator::deselect(const std::set& ids) +{ + update(ids, Deselect); +} + +void PDFPageContentElementManipulator::selectAll() +{ + std::set ids = m_scene->getElementIds(); + update(ids, Select); +} + +void PDFPageContentElementManipulator::deselectAll() +{ + update(-1, Clear); +} + +bool PDFPageContentElementManipulator::isManipulationAllowed(PDFInteger pageIndex) const +{ + for (const PDFInteger id : m_selection) + { + if (const PDFPageContentElement* element = m_scene->getElementById(id)) + { + if (element->getPageIndex() == pageIndex) + { + return true; + } + } + } + + return false; +} + +void PDFPageContentElementManipulator::performOperation(Operation operation) +{ + if (isSelectionEmpty()) + { + // Jakub Melka: nothing selected + return; + } + + QRectF representativeRect = getSelectionBoundingRect(); + std::vector manipulatedElements; + for (const PDFInteger id : m_selection) + { + const PDFPageContentElement* element = m_scene->getElementById(id); + manipulatedElements.push_back(element->clone()); + } + + switch (operation) + { + case Operation::AlignTop: + { + for (PDFPageContentElement* element : manipulatedElements) + { + QRectF boundingBox = element->getBoundingBox(); + const qreal offset = representativeRect.bottom() - boundingBox.bottom(); + element->performManipulation(PDFPageContentElement::Translate, QPointF(0.0, offset)); + } + break; + } + + case Operation::AlignCenterVertically: + { + for (PDFPageContentElement* element : manipulatedElements) + { + QRectF boundingBox = element->getBoundingBox(); + const qreal offset = representativeRect.center().y() - boundingBox.center().y(); + element->performManipulation(PDFPageContentElement::Translate, QPointF(0.0, offset)); + } + break; + } + + case Operation::AlignBottom: + { + for (PDFPageContentElement* element : manipulatedElements) + { + QRectF boundingBox = element->getBoundingBox(); + const qreal offset = representativeRect.top() - boundingBox.top(); + element->performManipulation(PDFPageContentElement::Translate, QPointF(0.0, offset)); + } + break; + } + + case Operation::AlignLeft: + { + for (PDFPageContentElement* element : manipulatedElements) + { + QRectF boundingBox = element->getBoundingBox(); + const qreal offset = representativeRect.left() - boundingBox.left(); + element->performManipulation(PDFPageContentElement::Translate, QPointF(offset, 0.0)); + } + break; + } + + case Operation::AlignCenterHorizontally: + { + for (PDFPageContentElement* element : manipulatedElements) + { + QRectF boundingBox = element->getBoundingBox(); + const qreal offset = representativeRect.center().x() - boundingBox.center().x(); + element->performManipulation(PDFPageContentElement::Translate, QPointF(offset, 0.0)); + } + break; + } + + case Operation::AlignRight: + { + for (PDFPageContentElement* element : manipulatedElements) + { + QRectF boundingBox = element->getBoundingBox(); + const qreal offset = representativeRect.right() - boundingBox.right(); + element->performManipulation(PDFPageContentElement::Translate, QPointF(offset, 0.0)); + } + break; + } + + case Operation::SetSameHeight: + { + QSizeF size = manipulatedElements.front()->getBoundingBox().size(); + + for (PDFPageContentElement* element : manipulatedElements) + { + size.setWidth(element->getBoundingBox().width()); + element->setSize(size); + } + break; + } + + case Operation::SetSameWidth: + { + QSizeF size = manipulatedElements.front()->getBoundingBox().size(); + + for (PDFPageContentElement* element : manipulatedElements) + { + size.setHeight(element->getBoundingBox().height()); + element->setSize(size); + } + break; + } + + case Operation::SetSameSize: + { + QSizeF size = manipulatedElements.front()->getBoundingBox().size(); + + for (PDFPageContentElement* element : manipulatedElements) + { + element->setSize(size); + } + break; + } + + case Operation::CenterHorizontally: + { + const PDFInteger pageIndex = manipulatedElements.front()->getPageIndex(); + QRectF pageMediaBox = getPageMediaBox(pageIndex); + const qreal offset = pageMediaBox.center().x() - representativeRect.center().x(); + + for (PDFPageContentElement* element : manipulatedElements) + { + element->performManipulation(PDFPageContentElement::Translate, QPointF(offset, 0.0)); + } + break; + } + + case Operation::CenterVertically: + { + const PDFInteger pageIndex = manipulatedElements.front()->getPageIndex(); + QRectF pageMediaBox = getPageMediaBox(pageIndex); + const qreal offset = pageMediaBox.center().y() - representativeRect.center().y(); + + for (PDFPageContentElement* element : manipulatedElements) + { + element->performManipulation(PDFPageContentElement::Translate, QPointF(0.0, offset)); + } + break; + } + + case Operation::CenterHorAndVert: + { + const PDFInteger pageIndex = manipulatedElements.front()->getPageIndex(); + QRectF pageMediaBox = getPageMediaBox(pageIndex); + const qreal offsetX = pageMediaBox.center().x() - representativeRect.center().x(); + const qreal offsetY = pageMediaBox.center().y() - representativeRect.center().y(); + QPointF offset(offsetX, offsetY); + + for (PDFPageContentElement* element : manipulatedElements) + { + element->performManipulation(PDFPageContentElement::Translate, offset); + } + break; + } + + case Operation::LayoutVertically: + { + auto comparator = [](PDFPageContentElement* left, PDFPageContentElement* right) + { + QRectF r1 = left->getBoundingBox(); + QRectF r2 = right->getBoundingBox(); + + return r1.y() > r2.y(); + }; + std::stable_sort(manipulatedElements.begin(), manipulatedElements.end(), comparator); + + qreal yTop = representativeRect.bottom(); + for (PDFPageContentElement* element : manipulatedElements) + { + QRectF boundingBox = element->getBoundingBox(); + QPointF offset(0.0, yTop - boundingBox.bottom()); + element->performManipulation(PDFPageContentElement::Translate, offset); + yTop -= boundingBox.height(); + } + + break; + } + + case Operation::LayoutHorizontally: + { + auto comparator = [](PDFPageContentElement* left, PDFPageContentElement* right) + { + QRectF r1 = left->getBoundingBox(); + QRectF r2 = right->getBoundingBox(); + + return r1.x() < r2.x(); + }; + std::stable_sort(manipulatedElements.begin(), manipulatedElements.end(), comparator); + + qreal x = representativeRect.left(); + for (PDFPageContentElement* element : manipulatedElements) + { + QRectF boundingBox = element->getBoundingBox(); + QPointF offset(x - boundingBox.left(), 0.0); + element->performManipulation(PDFPageContentElement::Translate, offset); + x += boundingBox.width(); + } + + break; + } + + case Operation::LayoutForm: + { + std::vector> formLayout; + + // Divide elements to left/right side + std::vector elementsLeft; + std::vector elementsRight; + + for (PDFPageContentElement* element : manipulatedElements) + { + QRectF boundingBox = element->getBoundingBox(); + + const qreal distanceLeft = boundingBox.left() - representativeRect.left(); + const qreal distanceRight = representativeRect.right() - boundingBox.right(); + + if (distanceLeft < distanceRight) + { + elementsLeft.push_back(element); + } + else + { + elementsRight.push_back(element); + } + } + + // Create pairs of left/right elements + while (!elementsLeft.empty() && !elementsRight.empty()) + { + PDFPageContentElement* elementRight = nullptr; + PDFPageContentElement* elementLeft = nullptr; + + qreal overlap = 0.0; + + // Iterate trough element on the left + for (PDFPageContentElement* elementLeftCurrent : elementsLeft) + { + QRectF leftBoundingBox = elementLeftCurrent->getBoundingBox(); + + // Find matching element on the right + for (PDFPageContentElement* elementRightCurrent : elementsRight) + { + QRectF rightBoundingBox = elementRightCurrent->getBoundingBox(); + if (isRectangleVerticallyOverlapped(leftBoundingBox, rightBoundingBox)) + { + QRectF unitedRect = leftBoundingBox.united(rightBoundingBox); + qreal currentOverlap = leftBoundingBox.height() + rightBoundingBox.height() - unitedRect.height(); + + if (currentOverlap > overlap) + { + overlap = currentOverlap; + elementRight = elementRightCurrent; + elementLeft = elementLeftCurrent; + } + } + } + } + + Q_ASSERT((elementLeft != nullptr) == (elementRight != nullptr)); + + if (elementLeft && elementRight) + { + auto itLeft = std::find(elementsLeft.begin(), elementsLeft.end(), elementLeft); + elementsLeft.erase(itLeft); + + auto itRight = std::find(elementsRight.begin(), elementsRight.end(), elementRight); + elementsRight.erase(itRight); + + formLayout.emplace_back(elementLeft, elementRight); + continue; + } + else + { + break; + } + } + + for (PDFPageContentElement* leftElement : elementsLeft) + { + formLayout.emplace_back(leftElement, nullptr); + } + + for (PDFPageContentElement* rightElement : elementsRight) + { + formLayout.emplace_back(nullptr, rightElement); + } + + // Sort elements vertically + auto comparator = [](const auto& left, const auto& right) + { + const PDFPageContentElement* l1 = left.first ? left.first : left.second; + const PDFPageContentElement* l2 = left.second ? left.second : left.first; + + const PDFPageContentElement* r1 = right.first ? right.first : right.second; + const PDFPageContentElement* r2 = right.second ? right.second : right.first; + + QRectF lbb1 = l1->getBoundingBox(); + QRectF lbb2 = l2->getBoundingBox(); + QRectF rbb1 = r1->getBoundingBox(); + QRectF rbb2 = r2->getBoundingBox(); + + const qreal ly = (lbb1.center().y() + lbb2.center().y()) * 0.5; + const qreal ry = (rbb1.center().y() + rbb2.center().y()) * 0.5; + + return ly > ry; + }; + std::stable_sort(formLayout.begin(), formLayout.end(), comparator); + + // Calculate width + qreal leftWidth = 0.0; + qreal rightWidth = 0.0; + + for (const auto& row : formLayout) + { + PDFPageContentElement* elementLeft = row.first; + PDFPageContentElement* elementRight = row.second; + + if (elementLeft) + { + qreal width = elementLeft->getBoundingBox().width(); + leftWidth = qMax(leftWidth, width); + } + + if (elementRight) + { + qreal width = elementRight->getBoundingBox().width(); + rightWidth = qMax(rightWidth, width); + } + } + + // Now, perform layout + qreal yTop = representativeRect.bottom(); + qreal xLeft = representativeRect.left(); + qreal xRight = representativeRect.right() - rightWidth; + + for (const auto& row : formLayout) + { + PDFPageContentElement* elementLeft = row.first; + PDFPageContentElement* elementRight = row.second; + + qreal yHeight = 0.0; + + if (elementLeft) + { + QRectF boundingBoxLeft = elementLeft->getBoundingBox(); + QPointF offsetLeft(xLeft - boundingBoxLeft.x(), yTop - boundingBoxLeft.bottom()); + elementLeft->performManipulation(PDFPageContentElement::Translate, offsetLeft); + yHeight = qMax(yHeight, boundingBoxLeft.height()); + } + + if (elementRight) + { + QRectF boundingBoxRight = elementRight->getBoundingBox(); + QPointF offsetRight(xRight - boundingBoxRight.x(), yTop - boundingBoxRight.bottom()); + elementRight->performManipulation(PDFPageContentElement::Translate, offsetRight); + yHeight = qMax(yHeight, boundingBoxRight.height()); + } + + yTop -= yHeight; + } + + break; + } + + case Operation::LayoutGrid: + { + std::map rowHeights; + std::map columnWidths; + std::map elementToRow; + std::map elementToColumn; + + // Detect rows + auto comparatorRow = [](PDFPageContentElement* left, PDFPageContentElement* right) + { + QRectF r1 = left->getBoundingBox(); + QRectF r2 = right->getBoundingBox(); + + return r1.top() < r2.top(); + }; + std::stable_sort(manipulatedElements.begin(), manipulatedElements.end(), comparatorRow); + + PDFInteger row = 0; + std::vector rowElementsToProcess = manipulatedElements; + while (!rowElementsToProcess.empty()) + { + PDFPageContentElement* sampleElement = rowElementsToProcess.back(); + elementToRow[sampleElement] = row; + rowElementsToProcess.pop_back(); + QRectF boundingBox = sampleElement->getBoundingBox(); + qreal maxHeight = boundingBox.height(); + + for (auto it = rowElementsToProcess.begin(); it != rowElementsToProcess.end();) + { + QRectF currentBoundingBox = (*it)->getBoundingBox(); + if (isRectangleVerticallyOverlapped(boundingBox, currentBoundingBox)) + { + QRectF unitedRect = boundingBox.united(currentBoundingBox); + qreal currentOverlap = boundingBox.height() + currentBoundingBox.height() - unitedRect.height(); + + if (qFuzzyIsNull(currentOverlap)) + { + ++it; + continue; + } + + elementToRow[*it] = row; + maxHeight = qMax(currentBoundingBox.height(), maxHeight); + it = rowElementsToProcess.erase(it); + } + else + { + ++it; + } + } + + rowHeights[row] = qMax(rowHeights[row], maxHeight); + + ++row; + } + + // Detect columns + auto comparatorColumn = [](PDFPageContentElement* left, PDFPageContentElement* right) + { + QRectF r1 = left->getBoundingBox(); + QRectF r2 = right->getBoundingBox(); + + return r1.left() > r2.left(); + }; + std::stable_sort(manipulatedElements.begin(), manipulatedElements.end(), comparatorColumn); + + PDFInteger column = 0; + std::vector columnElementsToProcess = manipulatedElements; + while (!columnElementsToProcess.empty()) + { + PDFPageContentElement* sampleElement = columnElementsToProcess.back(); + elementToColumn[sampleElement] = column; + columnElementsToProcess.pop_back(); + QRectF boundingBox = sampleElement->getBoundingBox(); + qreal maxWidth = boundingBox.width(); + + for (auto it = columnElementsToProcess.begin(); it != columnElementsToProcess.end();) + { + QRectF currentBoundingBox = (*it)->getBoundingBox(); + if (isRectangleHorizontallyOverlapped(boundingBox, currentBoundingBox)) + { + QRectF unitedRect = boundingBox.united(currentBoundingBox); + qreal currentOverlap = boundingBox.width() + currentBoundingBox.width() - unitedRect.width(); + + if (qFuzzyIsNull(currentOverlap)) + { + ++it; + continue; + } + + elementToColumn[*it] = column; + maxWidth = qMax(currentBoundingBox.width(), maxWidth); + it = columnElementsToProcess.erase(it); + } + else + { + ++it; + } + } + + columnWidths[column] = qMax(columnWidths[column], maxWidth); + + ++column; + } + + // Calculate cell offsets + std::vector cellOffsetX(column, 0.0); + std::vector cellOffsetY(row, 0.0); + + for (size_t i = 1; i < size_t(column); ++i) + { + cellOffsetX[i] = cellOffsetX[i - 1] + columnWidths[i - 1]; + } + for (size_t i = 1; i < size_t(row); ++i) + { + cellOffsetY[i] = cellOffsetY[i - 1] + rowHeights[i - 1]; + } + + // Move elements + qreal xLeft = representativeRect.left(); + qreal yTop = representativeRect.bottom(); + for (PDFPageContentElement* element : manipulatedElements) + { + const PDFInteger row = elementToRow[element]; + const PDFInteger column = elementToColumn[element]; + + const qreal xOffset = cellOffsetX[column]; + const qreal yOffset = cellOffsetY[row]; + + QRectF boundingBox = element->getBoundingBox(); + QPointF offset(xLeft + xOffset - boundingBox.left(), yTop - yOffset - boundingBox.bottom()); + element->performManipulation(PDFPageContentElement::Translate, offset); + } + + break; + } + } + + for (PDFPageContentElement* element : manipulatedElements) + { + m_scene->replaceElement(element); + } +} + +void PDFPageContentElementManipulator::performDeleteSelection() +{ + cancelManipulation(); + m_scene->removeElementsById(m_selection); + deselectAll(); +} + +void PDFPageContentElementManipulator::startManipulation(PDFInteger pageIndex, + const QPointF& startPoint, + const QPointF& currentPoint, + PDFReal snapPointDistanceThreshold) +{ + Q_ASSERT(!isManipulationInProgress()); + + // Collect elements to be manipulated + for (const PDFInteger id : m_selection) + { + const PDFPageContentElement* element = m_scene->getElementById(id); + + if (!element || element->getPageIndex() != pageIndex) + { + continue; + } + + uint manipulationMode = element->getManipulationMode(startPoint, snapPointDistanceThreshold); + + if (!manipulationMode) + { + manipulationMode = PDFPageContentElement::Translate; + } + + // Jakub Melka: manipulate this element + m_manipulatedElements.emplace_back(element->clone()); + m_manipulationModes[id] = manipulationMode; + } + + if (!m_manipulatedElements.empty()) + { + m_isManipulationInProgress = true; + m_lastUpdatedPoint = startPoint; + updateManipulation(pageIndex, startPoint, currentPoint); + emit stateChanged(); + } +} + +void PDFPageContentElementManipulator::updateManipulation(PDFInteger pageIndex, + const QPointF& startPoint, + const QPointF& currentPoint) +{ + Q_UNUSED(startPoint); + + QPointF offset = currentPoint - m_lastUpdatedPoint; + + for (const auto& element : m_manipulatedElements) + { + if (element->getPageIndex() == pageIndex) + { + element->performManipulation(m_manipulationModes[element->getElementId()], offset); + } + } + + m_lastUpdatedPoint = currentPoint; + emit stateChanged(); +} + +void PDFPageContentElementManipulator::finishManipulation(PDFInteger pageIndex, + const QPointF& startPoint, + const QPointF& currentPoint, + bool createCopy) +{ + Q_ASSERT(isManipulationInProgress()); + updateManipulation(pageIndex, startPoint, currentPoint); + + if (createCopy) + { + for (const auto& element : m_manipulatedElements) + { + m_scene->addElement(element->clone()); + } + } + else + { + for (const auto& element : m_manipulatedElements) + { + m_scene->replaceElement(element->clone()); + } + } + + cancelManipulation(); +} + +void PDFPageContentElementManipulator::cancelManipulation() +{ + if (isManipulationInProgress()) + { + m_isManipulationInProgress = false; + m_manipulatedElements.clear(); + m_manipulationModes.clear(); + emit stateChanged(); + } + + Q_ASSERT(!m_isManipulationInProgress); + Q_ASSERT(m_manipulatedElements.empty()); + Q_ASSERT(m_manipulationModes.empty()); +} + +void PDFPageContentElementManipulator::drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const +{ + // Draw selection + if (!isSelectionEmpty()) + { + QPainterPath selectionPath; + for (const PDFInteger id : m_selection) + { + if (PDFPageContentElement* element = m_scene->getElementById(id)) + { + QPainterPath tempPath; + tempPath.addRect(element->getBoundingBox()); + selectionPath = selectionPath.united(tempPath); + } + } + + if (!selectionPath.isEmpty()) + { + PDFPainterStateGuard guard(painter); + QPen pen(Qt::SolidLine); + pen.setWidthF(2.0); + pen.setColor(QColor::fromRgbF(0.8, 0.8, 0.1, 0.7)); + QBrush brush(Qt::SolidPattern); + brush.setColor(QColor::fromRgbF(1.0, 1.0, 0.0, 0.2)); + + painter->setPen(std::move(pen)); + painter->setBrush(std::move(brush)); + + selectionPath = pagePointToDevicePointMatrix.map(selectionPath); + painter->drawPath(selectionPath); + } + } + + // Draw dragged items + if (isManipulationInProgress()) + { + PDFPainterStateGuard guard(painter); + painter->setOpacity(0.2); + + for (const auto& manipulatedElement : m_manipulatedElements) + { + manipulatedElement->drawPage(painter, pageIndex, compiledPage, layoutGetter, pagePointToDevicePointMatrix, errors); + } + } +} + +QRectF PDFPageContentElementManipulator::getSelectionBoundingRect() const +{ + QRectF rect; + + for (const PDFInteger elementId : m_selection) + { + if (const PDFPageContentElement* element = m_scene->getElementById(elementId)) + { + rect = rect.united(element->getBoundingBox()); + } + } + + return rect; +} + +QRectF PDFPageContentElementManipulator::getPageMediaBox(PDFInteger pageIndex) const +{ + if (pageIndex < 0) + { + return QRectF(); + } + + if (const PDFDocument* document = m_scene->getDocument()) + { + size_t pageCount = document->getCatalog()->getPageCount(); + if (size_t(pageIndex) < pageCount) + { + const PDFPage* page = document->getCatalog()->getPage(pageIndex); + return page->getMediaBox(); + } + } + + return QRectF(); +} + +void PDFPageContentElementManipulator::eraseSelectedElementById(PDFInteger id) +{ + auto it = std::find(m_selection.begin(), m_selection.end(), id); + if (it != m_selection.end()) + { + m_selection.erase(it); + } +} + +PDFPageContentElementTextBox* PDFPageContentElementTextBox::clone() const +{ + PDFPageContentElementTextBox* copy = new PDFPageContentElementTextBox(); + copy->setElementId(getElementId()); + copy->setPageIndex(getPageIndex()); + copy->setPen(getPen()); + copy->setBrush(getBrush()); + copy->setRectangle(getRectangle()); + copy->setText(getText()); + copy->setFont(getFont()); + copy->setAngle(getAngle()); + copy->setAlignment(getAlignment()); + return copy; +} + +void PDFPageContentElementTextBox::drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const +{ + Q_UNUSED(compiledPage); + Q_UNUSED(layoutGetter); + Q_UNUSED(errors); + + if (pageIndex != getPageIndex()) + { + return; + } + + QRectF rect = getRectangle(); + QFont font = getFont(); + font.setHintingPreference(QFont::PreferNoHinting); + if (font.pointSizeF() > 0.0) + { + font.setPixelSize(qRound(font.pointSizeF())); + } + + PDFPainterStateGuard guard(painter); + painter->setWorldMatrix(pagePointToDevicePointMatrix, true); + painter->setPen(getPen()); + painter->setBrush(getBrush()); + painter->setFont(font); + painter->setRenderHint(QPainter::Antialiasing); + painter->setClipRect(rect, Qt::IntersectClip); + painter->translate(rect.center()); + painter->scale(1.0, -1.0); + + QTextOption option; + option.setAlignment(getAlignment()); + + QRectF textRect(-rect.width() * 0.5, -rect.height() * 0.5, rect.width(), rect.height()); + if (qFuzzyIsNull(getAngle())) + { + painter->drawText(textRect, getText(), option); + } + else + { + QRectF textBoundingRect = painter->boundingRect(textRect, getText(), option); + + QMatrix matrix; + matrix.rotate(getAngle()); + QRectF mappedTextBoundingRect = matrix.mapRect(textBoundingRect); + + switch (getAlignment() & Qt::AlignHorizontal_Mask) + { + case Qt::AlignLeft: + mappedTextBoundingRect.moveLeft(textRect.left()); + break; + + case Qt::AlignHCenter: + mappedTextBoundingRect.moveLeft(textRect.left() + textRect.width() * 0.5 - mappedTextBoundingRect.width() * 0.5); + break; + + case Qt::AlignRight: + mappedTextBoundingRect.moveRight(textRect.right()); + break; + + default: + Q_ASSERT(false); + break; + } + + switch (getAlignment() & Qt::AlignVertical_Mask) + { + case Qt::AlignTop: + mappedTextBoundingRect.moveTop(textRect.top()); + break; + + case Qt::AlignVCenter: + mappedTextBoundingRect.moveTop(textRect.top() + textRect.height() * 0.5 - mappedTextBoundingRect.height() * 0.5); + break; + + case Qt::AlignBottom: + mappedTextBoundingRect.moveBottom(textRect.bottom()); + break; + + default: + Q_ASSERT(false); + break; + } + + painter->translate(mappedTextBoundingRect.center()); + painter->rotate(getAngle()); + + textBoundingRect.moveCenter(QPointF(0, 0)); + painter->drawText(textBoundingRect, getText(), option); + } +} + +uint PDFPageContentElementTextBox::getManipulationMode(const QPointF& point, + PDFReal snapPointDistanceThreshold) const +{ + return getRectangleManipulationMode(getRectangle(), point, snapPointDistanceThreshold); +} + +void PDFPageContentElementTextBox::performManipulation(uint mode, const QPointF& offset) +{ + performRectangleManipulation(m_rectangle, mode, offset); +} + +QRectF PDFPageContentElementTextBox::getBoundingBox() const +{ + return m_rectangle; +} + +void PDFPageContentElementTextBox::setSize(QSizeF size) +{ + performRectangleSetSize(m_rectangle, size); +} + +QString PDFPageContentElementTextBox::getDescription() const +{ + return formatDescription(PDFTranslationContext::tr("Text box '%1'").arg(getText())); +} + +const QString& PDFPageContentElementTextBox::getText() const +{ + return m_text; +} + +void PDFPageContentElementTextBox::setText(const QString& newText) +{ + m_text = newText; +} + +const QFont& PDFPageContentElementTextBox::getFont() const +{ + return m_font; +} + +void PDFPageContentElementTextBox::setFont(const QFont& newFont) +{ + m_font = newFont; +} + +PDFReal PDFPageContentElementTextBox::getAngle() const +{ + return m_angle; +} + +void PDFPageContentElementTextBox::setAngle(PDFReal newAngle) +{ + m_angle = newAngle; +} + +const Qt::Alignment& PDFPageContentElementTextBox::getAlignment() const +{ + return m_alignment; +} + +void PDFPageContentElementTextBox::setAlignment(const Qt::Alignment& newAlignment) +{ + m_alignment = newAlignment; +} + +} // namespace pdf diff --git a/Pdf4QtLib/sources/pdfpagecontentelements.h b/Pdf4QtLib/sources/pdfpagecontentelements.h new file mode 100644 index 0000000..4e5143f --- /dev/null +++ b/Pdf4QtLib/sources/pdfpagecontentelements.h @@ -0,0 +1,626 @@ +// Copyright (C) 2022 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef PDFPAGECONTENTELEMENTS_H +#define PDFPAGECONTENTELEMENTS_H + +#include "pdfdocumentdrawinterface.h" + +#include +#include +#include +#include +#include + +#include + +class QSvgRenderer; + +namespace pdf +{ +class PDFWidget; +class PDFDocument; +class PDFPageContentScene; + +class PDF4QTLIBSHARED_EXPORT PDFPageContentElement +{ +public: + explicit PDFPageContentElement() = default; + virtual ~PDFPageContentElement() = default; + + virtual PDFPageContentElement* clone() const = 0; + + virtual void drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const = 0; + + /// Returns manipulation mode. If manipulation mode is zero, then element + /// cannot be manipulated. If it is nonzero, then element can be manipulated + /// in some way. + /// \param point Point on page + /// \param snapPointDistanceTreshold Snap point threshold + virtual uint getManipulationMode(const QPointF& point, PDFReal snapPointDistanceThreshold) const = 0; + + /// Performs manipulation of given mode. Mode must be the one returned + /// from function getManipulationMode. \sa getManipulationMode + /// \param mode Mode + /// \param offset Offset + virtual void performManipulation(uint mode, const QPointF& offset) = 0; + + /// Returns bounding box of the element + virtual QRectF getBoundingBox() const = 0; + + /// Sets size to the elements that supports it. Does + /// nothing for elements, which does not support it. + virtual void setSize(QSizeF size) = 0; + + /// Returns description of the element + virtual QString getDescription() const = 0; + + PDFInteger getPageIndex() const; + void setPageIndex(PDFInteger newPageIndex); + + PDFInteger getElementId() const; + void setElementId(PDFInteger newElementId); + + /// Returns cursor shape for manipulation mode + /// \param mode Manipulation mode + static Qt::CursorShape getCursorShapeForManipulationMode(uint mode); + + enum ManipulationModes : uint + { + None = 0, + Translate, + Top, + Left, + Right, + Bottom, + TopLeft, + TopRight, + BottomLeft, + BottomRight, + Pt1, + Pt2 + }; + +protected: + uint getRectangleManipulationMode(const QRectF& rectangle, + const QPointF& point, + PDFReal snapPointDistanceThreshold) const; + + void performRectangleManipulation(QRectF& rectangle, + uint mode, + const QPointF& offset); + + void performRectangleSetSize(QRectF& rectangle, QSizeF size); + + QString formatDescription(const QString& description) const; + + PDFInteger m_elementId = -1; + PDFInteger m_pageIndex = -1; +}; + +class PDF4QTLIBSHARED_EXPORT PDFPageContentStyledElement : public PDFPageContentElement +{ +public: + explicit PDFPageContentStyledElement() = default; + virtual ~PDFPageContentStyledElement() = default; + + const QPen& getPen() const; + void setPen(const QPen& newPen); + + const QBrush& getBrush() const; + void setBrush(const QBrush& newBrush); + +protected: + QPen m_pen; + QBrush m_brush; +}; + +class PDF4QTLIBSHARED_EXPORT PDFPageContentElementRectangle : public PDFPageContentStyledElement +{ +public: + virtual ~PDFPageContentElementRectangle() = default; + + virtual PDFPageContentElementRectangle* clone() const override; + + bool isRounded() const; + void setRounded(bool newRounded); + + const QRectF& getRectangle() const; + void setRectangle(const QRectF& newRectangle); + + virtual void drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const override; + + virtual uint getManipulationMode(const QPointF& point, + PDFReal snapPointDistanceThreshold) const override; + + virtual void performManipulation(uint mode, const QPointF& offset) override; + virtual QRectF getBoundingBox() const override; + virtual void setSize(QSizeF size); + virtual QString getDescription() const override; + +private: + bool m_rounded = false; + QRectF m_rectangle; +}; + +class PDF4QTLIBSHARED_EXPORT PDFPageContentElementLine : public PDFPageContentStyledElement +{ +public: + virtual ~PDFPageContentElementLine() = default; + + virtual PDFPageContentElementLine* clone() const override; + + enum class LineGeometry + { + General, + Horizontal, + Vertical + }; + + virtual void drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const override; + + virtual uint getManipulationMode(const QPointF& point, + PDFReal snapPointDistanceThreshold) const override; + + virtual void performManipulation(uint mode, const QPointF& offset) override; + virtual QRectF getBoundingBox() const override; + virtual void setSize(QSizeF size); + virtual QString getDescription() const override; + + LineGeometry getGeometry() const; + void setGeometry(LineGeometry newGeometry); + + const QLineF& getLine() const; + void setLine(const QLineF& newLine); + +private: + LineGeometry m_geometry = LineGeometry::General; + QLineF m_line; +}; + +class PDF4QTLIBSHARED_EXPORT PDFPageContentElementDot : public PDFPageContentStyledElement +{ +public: + virtual ~PDFPageContentElementDot() = default; + + virtual PDFPageContentElementDot* clone() const override; + + virtual void drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const override; + + virtual uint getManipulationMode(const QPointF& point, + PDFReal snapPointDistanceThreshold) const override; + + virtual void performManipulation(uint mode, const QPointF& offset) override; + virtual QRectF getBoundingBox() const override; + virtual void setSize(QSizeF size); + virtual QString getDescription() const override; + + QPointF getPoint() const; + void setPoint(QPointF newPoint); + +private: + QPointF m_point; +}; + +class PDF4QTLIBSHARED_EXPORT PDFPageContentElementFreehandCurve : public PDFPageContentStyledElement +{ +public: + virtual ~PDFPageContentElementFreehandCurve() = default; + + virtual PDFPageContentElementFreehandCurve* clone() const override; + + virtual void drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const override; + + virtual uint getManipulationMode(const QPointF& point, + PDFReal snapPointDistanceThreshold) const override; + + virtual void performManipulation(uint mode, const QPointF& offset) override; + virtual QRectF getBoundingBox() const override; + virtual void setSize(QSizeF size); + virtual QString getDescription() const override; + + QPainterPath getCurve() const; + void setCurve(QPainterPath newCurve); + + bool isEmpty() const { return m_curve.isEmpty(); } + void addStartPoint(const QPointF& point); + void addPoint(const QPointF& point); + void clear(); + +private: + QPainterPath m_curve; +}; + +class PDF4QTLIBSHARED_EXPORT PDFPageContentImageElement : public PDFPageContentElement +{ +public: + PDFPageContentImageElement(); + virtual ~PDFPageContentImageElement(); + + virtual PDFPageContentImageElement* clone() const override; + + virtual void drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const override; + + virtual uint getManipulationMode(const QPointF& point, + PDFReal snapPointDistanceThreshold) const override; + + virtual void performManipulation(uint mode, const QPointF& offset) override; + virtual QRectF getBoundingBox() const override; + virtual void setSize(QSizeF size); + virtual QString getDescription() const override; + + const QByteArray& getContent() const; + void setContent(const QByteArray& newContent); + + const QRectF& getRectangle() const; + void setRectangle(const QRectF& newRectangle); + +private: + QRectF m_rectangle; + QByteArray m_content; + QImage m_image; + std::unique_ptr m_renderer; +}; + +class PDF4QTLIBSHARED_EXPORT PDFPageContentElementTextBox : public PDFPageContentStyledElement +{ +public: + virtual ~PDFPageContentElementTextBox() = default; + + virtual PDFPageContentElementTextBox* clone() const override; + + const QRectF& getRectangle() const { return m_rectangle; } + void setRectangle(const QRectF& newRectangle) { m_rectangle = newRectangle; } + + virtual void drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const override; + + virtual uint getManipulationMode(const QPointF& point, + PDFReal snapPointDistanceThreshold) const override; + + virtual void performManipulation(uint mode, const QPointF& offset) override; + virtual QRectF getBoundingBox() const override; + virtual void setSize(QSizeF size); + virtual QString getDescription() const override; + + const QString& getText() const; + void setText(const QString& newText); + + const QFont& getFont() const; + void setFont(const QFont& newFont); + + PDFReal getAngle() const; + void setAngle(PDFReal newAngle); + + const Qt::Alignment& getAlignment() const; + void setAlignment(const Qt::Alignment& newAlignment); + +private: + QString m_text; + QRectF m_rectangle; + QFont m_font; + PDFReal m_angle = 0.0; + Qt::Alignment m_alignment = Qt::AlignCenter; +}; + +class PDF4QTLIBSHARED_EXPORT PDFPageContentElementManipulator : public QObject +{ + Q_OBJECT + +public: + PDFPageContentElementManipulator(PDFPageContentScene* scene, QObject* parent); + + enum class Operation + { + AlignTop, + AlignCenterVertically, + AlignBottom, + AlignLeft, + AlignCenterHorizontally, + AlignRight, + SetSameHeight, + SetSameWidth, + SetSameSize, + CenterHorizontally, + CenterVertically, + CenterHorAndVert, + LayoutVertically, + LayoutHorizontally, + LayoutForm, + LayoutGrid + }; + + enum SelectionMode + { + NoUpdate = 0x0000, + Clear = 0x0001, ///< Clears current selection + Select = 0x0002, ///< Selects item + Deselect = 0x0004, ///< Deselects item + Toggle = 0x0008, ///< Toggles selection of the item + }; + Q_DECLARE_FLAGS(SelectionModes, SelectionMode) + + /// Returns true, if element with given id is selected + /// \param id Element id + bool isSelected(PDFInteger id) const; + + /// Returns true, if all elements are selected + /// \param ids Element ids + bool isAllSelected(const std::set& elementIds) const; + + /// Returns true, if selection is empty + bool isSelectionEmpty() const { return m_selection.empty(); } + + /// Clear all selection, stop manipulation + void reset(); + + void update(PDFInteger id, SelectionModes modes); + void update(const std::set& ids, SelectionModes modes); + void select(PDFInteger id); + void select(const std::set& ids); + void selectNew(PDFInteger id); + void selectNew(const std::set& ids); + void deselect(PDFInteger id); + void deselect(const std::set& ids); + void selectAll(); + void deselectAll(); + + bool isManipulationAllowed(PDFInteger pageIndex) const; + bool isManipulationInProgress() const { return m_isManipulationInProgress; } + + void performOperation(Operation operation); + void performDeleteSelection(); + + void startManipulation(PDFInteger pageIndex, + const QPointF& startPoint, + const QPointF& currentPoint, + PDFReal snapPointDistanceThreshold); + + void updateManipulation(PDFInteger pageIndex, + const QPointF& startPoint, + const QPointF& currentPoint); + + void finishManipulation(PDFInteger pageIndex, + const QPointF& startPoint, + const QPointF& currentPoint, + bool createCopy); + + void cancelManipulation(); + + void drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const; + + /// Returns bounding box of whole selection + QRectF getSelectionBoundingRect() const; + + /// Returns page rectangle for the page + QRectF getPageMediaBox(PDFInteger pageIndex) const; + +signals: + void selectionChanged(); + void stateChanged(); + +private: + void eraseSelectedElementById(PDFInteger id); + + PDFPageContentScene* m_scene; + std::vector m_selection; + bool m_isManipulationInProgress; + std::vector> m_manipulatedElements; + std::map m_manipulationModes; + QPointF m_lastUpdatedPoint; +}; + +class PDF4QTLIBSHARED_EXPORT PDFPageContentScene : public QObject, + public IDocumentDrawInterface, + public IDrawWidgetInputInterface +{ + Q_OBJECT + +public: + explicit PDFPageContentScene(QObject* parent); + virtual ~PDFPageContentScene(); + + static constexpr PDFInteger INVALID_ELEMENT_ID = 0; + + /// Add new element to page content scene, scene + /// takes ownership over the element. + /// \param element Element + void addElement(PDFPageContentElement* element); + + /// Replaces element in the page content scene, scene + /// takes ownership over the element. + /// \param element Element + void replaceElement(PDFPageContentElement* element); + + /// Returns element by its id (identifier number) + /// \param id Element id + PDFPageContentElement* getElementById(PDFInteger id) const; + + /// Clear whole scene - remove all page content elements + void clear(); + + /// Returns true, if scene is empty + bool isEmpty() const { return m_elements.empty(); } + + bool isActive() const; + void setActive(bool newIsActive); + + /// Returns set of all element ids + std::set getElementIds() const; + + /// Returns set of selected element ids + std::set getSelectedElementIds() const; + + /// Returns set of involved pages + std::set getPageIndices() const; + + /// Returns bounding box of elements on page + QRectF getBoundingBox(PDFInteger pageIndex) const; + + /// Set selected items + void setSelectedElementIds(const std::set& selectedElementIds); + + /// Removes elements specified in selection + /// \param selection Items to be removed + void removeElementsById(const std::vector& selection); + + /// Performs manipulation of selected elements with manipulator + void performOperation(int operation); + + /// Returns document (or nullptr) + const PDFDocument* getDocument() const; + + // IDrawWidgetInputInterface interface +public: + virtual void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) override; + virtual void keyPressEvent(QWidget* widget, QKeyEvent* event) override; + virtual void keyReleaseEvent(QWidget* widget, QKeyEvent* event) override; + virtual void mousePressEvent(QWidget* widget, QMouseEvent* event) override; + virtual void mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event) override; + virtual void mouseReleaseEvent(QWidget* widget, QMouseEvent* event) override; + virtual void mouseMoveEvent(QWidget* widget, QMouseEvent* event) override; + virtual void wheelEvent(QWidget* widget, QWheelEvent* event) override; + virtual QString getTooltip() const override; + virtual const std::optional& getCursor() const override; + virtual int getInputPriority() const override; + + virtual void drawPage(QPainter* painter, + PDFInteger pageIndex, + const PDFPrecompiledPage* compiledPage, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + QList& errors) const override; + + PDFWidget* widget() const; + void setWidget(PDFWidget* newWidget); + + void drawElements(QPainter* painter, + PDFInteger pageIndex, + PDFTextLayoutGetter& layoutGetter, + const QMatrix& pagePointToDevicePointMatrix, + const PDFPrecompiledPage* compiledPage, + QList& errors) const; + +signals: + /// This signal is emitted when scene has changed (including graphics) + void sceneChanged(bool graphicsOnly); + + void selectionChanged(); + + /// Request to edit the elements + void editElementRequest(const std::set& elements); + +private: + + struct MouseEventInfo + { + std::set hoveredElementIds; + QPoint widgetMouseStartPos; + QPoint widgetMouseCurrentPos; + QElapsedTimer timer; + PDFInteger pageIndex = -1; + QPointF pagePos; + + bool isValid() const { return !hoveredElementIds.empty(); } + }; + + MouseEventInfo getMouseEventInfo(QWidget* widget, QPoint point); + + struct MouseGrabInfo + { + MouseEventInfo info; + int mouseGrabNesting = 0; + + bool isMouseGrabbed() const { return mouseGrabNesting > 0; } + }; + + PDFReal getSnapPointDistanceThreshold() const; + + bool isMouseGrabbed() const { return m_mouseGrabInfo.isMouseGrabbed(); } + + /// Grabs mouse input, if mouse is already grabbed, or if event + /// is accepted. + /// \param info Mouse event info + /// \param event Mouse event + void grabMouse(const MouseEventInfo& info, QMouseEvent* event); + + /// Release mouse input + /// \param info Mouse event info + /// \param event Mouse event + void ungrabMouse(const MouseEventInfo& info, QMouseEvent* event); + + /// Updates mouse cursor + /// \param info Mouse event info + /// \param snapPointDistanceTreshold Snap point threshold + void updateMouseCursor(const MouseEventInfo& info, PDFReal snapPointDistanceThreshold); + + /// Reaction on selection changed + void onSelectionChanged(); + + PDFInteger m_firstFreeId; + bool m_isActive; + PDFWidget* m_widget; + std::vector> m_elements; + std::optional m_cursor; + PDFPageContentElementManipulator m_manipulator; + MouseGrabInfo m_mouseGrabInfo; +}; + +} // namespace pdf + +Q_DECLARE_OPERATORS_FOR_FLAGS(pdf::PDFPageContentElementManipulator::SelectionModes) + +#endif // PDFPAGECONTENTELEMENTS_H diff --git a/Pdf4QtLib/sources/pdfsignaturehandler.cpp b/Pdf4QtLib/sources/pdfsignaturehandler.cpp index 154eb13..2464c06 100644 --- a/Pdf4QtLib/sources/pdfsignaturehandler.cpp +++ b/Pdf4QtLib/sources/pdfsignaturehandler.cpp @@ -348,6 +348,15 @@ void PDFSignatureVerificationResult::addCertificateQualifiedStatementNotVerified } } +void PDFSignatureVerificationResult::addCertificateUnableToGetCRLWarning() +{ + if (!m_flags.testFlag(Warning_Certificate_UnableToGetCRL)) + { + m_flags.setFlag(Warning_Certificate_UnableToGetCRL); + m_warnings << PDFTranslationContext::tr("Unable to get CRL."); + } +} + void PDFSignatureVerificationResult::setSignatureFieldQualifiedName(const QString& signatureFieldQualifiedName) { m_signatureFieldQualifiedName = signatureFieldQualifiedName; @@ -977,6 +986,15 @@ int PDFSignatureHandler_ETSI_base::verifyCallback(int ok, X509_STORE_CTX* contex return 1; } + case X509_V_ERR_UNABLE_TO_GET_CRL: + { + // We will treat this as only warning. It means that + // CRL cannot be downloaded or other error occured. + s_ETSI_currentResult->addCertificateUnableToGetCRLWarning(); + X509_STORE_CTX_set_error(context, X509_V_OK); + return 1; + } + case X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION: { // We must handle all critical extensions manually diff --git a/Pdf4QtLib/sources/pdfsignaturehandler.h b/Pdf4QtLib/sources/pdfsignaturehandler.h index f6db0e5..d93044e 100644 --- a/Pdf4QtLib/sources/pdfsignaturehandler.h +++ b/Pdf4QtLib/sources/pdfsignaturehandler.h @@ -319,6 +319,7 @@ public: Warning_Signature_NotCoveredBytes = 0x00200000, ///< Some bytes in source data are not covered by signature Warning_Certificate_CRLValidityTimeExpired = 0x00400000, ///< Certificate revocation list was not checked, because it's validity expired Warning_Certificate_QualifiedStatement = 0x00800000, ///< Qualified certificate statement not verified + Warning_Certificate_UnableToGetCRL = 0x01000000, ///< Unable to get CRL Error_Certificates_Mask = Error_Certificate_Invalid | Error_Certificate_NoSignatures | Error_Certificate_Missing | Error_Certificate_Generic | Error_Certificate_Expired | Error_Certificate_SelfSigned | Error_Certificate_SelfSignedChain | Error_Certificate_TrustedNotFound | @@ -327,7 +328,7 @@ public: Error_Signatures_Mask = Error_Signature_Invalid | Error_Signature_SourceCertificateMissing | Error_Signature_NoSignaturesFound | Error_Signature_DigestFailure | Error_Signature_DataOther | Error_Signature_DataCoveredBySignatureMissing, - Warning_Certificates_Mask = Warning_Certificate_CRLValidityTimeExpired | Warning_Certificate_QualifiedStatement, + Warning_Certificates_Mask = Warning_Certificate_CRLValidityTimeExpired | Warning_Certificate_QualifiedStatement | Warning_Certificate_UnableToGetCRL, Warning_Signatures_Mask = Warning_Signature_NotCoveredBytes, Warnings_Mask = Warning_Certificates_Mask | Warning_Signatures_Mask @@ -361,6 +362,7 @@ public: void addSignatureNotCoveredBytesWarning(PDFInteger count); void addCertificateCRLValidityTimeExpiredWarning(); void addCertificateQualifiedStatementNotVerifiedWarning(); + void addCertificateUnableToGetCRLWarning(); bool isValid() const { return hasFlag(OK); } bool isCertificateValid() const { return hasFlag(Certificate_OK); } diff --git a/Pdf4QtLib/sources/pdfstreamfilters.cpp b/Pdf4QtLib/sources/pdfstreamfilters.cpp index 22d48d6..c25cc63 100644 --- a/Pdf4QtLib/sources/pdfstreamfilters.cpp +++ b/Pdf4QtLib/sources/pdfstreamfilters.cpp @@ -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(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()) diff --git a/Pdf4QtLib/sources/pdfstreamfilters.h b/Pdf4QtLib/sources/pdfstreamfilters.h index 3e23bdc..e89c740 100644 --- a/Pdf4QtLib/sources/pdfstreamfilters.h +++ b/Pdf4QtLib/sources/pdfstreamfilters.h @@ -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); diff --git a/Pdf4QtLib/sources/pdftexteditpseudowidget.cpp b/Pdf4QtLib/sources/pdftexteditpseudowidget.cpp new file mode 100644 index 0000000..668dfb8 --- /dev/null +++ b/Pdf4QtLib/sources/pdftexteditpseudowidget.cpp @@ -0,0 +1,949 @@ +// Copyright (C) 2022 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#include "pdftexteditpseudowidget.h" +#include "pdfpainterutils.h" + +#include +#include +#include +#include +#include + +namespace pdf +{ + +PDFTextEditPseudowidget::PDFTextEditPseudowidget(PDFFormField::FieldFlags flags) : + m_flags(flags), + m_selectionStart(0), + m_selectionEnd(0), + m_positionCursor(0), + m_maxTextLength(0) +{ + m_textLayout.setCacheEnabled(true); + m_passwordReplacementCharacter = QApplication::style()->styleHint(QStyle::SH_LineEdit_PasswordCharacter); +} + +void PDFTextEditPseudowidget::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) +{ + Q_UNUSED(widget); + constexpr QKeySequence::StandardKey acceptedKeys[] = { QKeySequence::Delete, QKeySequence::Cut, QKeySequence::Copy, QKeySequence::Paste, + QKeySequence::SelectAll, QKeySequence::MoveToNextChar, QKeySequence::MoveToPreviousChar, + QKeySequence::MoveToNextWord, QKeySequence::MoveToPreviousWord, QKeySequence::MoveToNextLine, + QKeySequence::MoveToPreviousLine, QKeySequence::MoveToStartOfLine, QKeySequence::MoveToEndOfLine, + QKeySequence::MoveToStartOfBlock, QKeySequence::MoveToEndOfBlock, QKeySequence::MoveToStartOfDocument, + QKeySequence::MoveToEndOfDocument, QKeySequence::SelectNextChar, QKeySequence::SelectPreviousChar, + QKeySequence::SelectNextWord, QKeySequence::SelectPreviousWord, QKeySequence::SelectNextLine, + QKeySequence::SelectPreviousLine, QKeySequence::SelectStartOfLine, QKeySequence::SelectEndOfLine, + QKeySequence::SelectStartOfBlock, QKeySequence::SelectEndOfBlock, QKeySequence::SelectStartOfDocument, + QKeySequence::SelectEndOfDocument, QKeySequence::DeleteStartOfWord, QKeySequence::DeleteEndOfWord, + QKeySequence::DeleteEndOfLine, QKeySequence::Deselect, QKeySequence::DeleteCompleteLine, QKeySequence::Backspace }; + + if (std::any_of(std::begin(acceptedKeys), std::end(acceptedKeys), [event](QKeySequence::StandardKey standardKey) { return event == standardKey; })) + { + event->accept(); + return; + } + + switch (event->key()) + { + case Qt::Key_Direction_L: + case Qt::Key_Direction_R: + case Qt::Key_Up: + case Qt::Key_Down: + case Qt::Key_Left: + case Qt::Key_Right: + event->accept(); + break; + + default: + break; + } + + if (!event->text().isEmpty()) + { + event->accept(); + for (const QChar& character : event->text()) + { + if (!character.isPrint()) + { + event->ignore(); + break; + } + } + } +} + +void PDFTextEditPseudowidget::keyPressEvent(QWidget* widget, QKeyEvent* event) +{ + Q_UNUSED(widget); + + /* + We will support following key sequences: + Delete + Cut, + Copy, + Paste, + SelectAll, + MoveToNextChar, + MoveToPreviousChar, + MoveToNextWord, + MoveToPreviousWord, + MoveToNextLine, + MoveToPreviousLine, + MoveToStartOfLine, + MoveToEndOfLine, + MoveToStartOfBlock, + MoveToEndOfBlock, + MoveToStartOfDocument, + MoveToEndOfDocument, + SelectNextChar, + SelectPreviousChar, + SelectNextWord, + SelectPreviousWord, + SelectNextLine, + SelectPreviousLine, + SelectStartOfLine, + SelectEndOfLine, + SelectStartOfBlock, + SelectEndOfBlock, + SelectStartOfDocument, + SelectEndOfDocument, + DeleteStartOfWord, + DeleteEndOfWord, + DeleteEndOfLine, + Deselect, + DeleteCompleteLine, + Backspace, + * */ + + event->accept(); + + if (event == QKeySequence::Delete) + { + performDelete(); + } + else if (event == QKeySequence::Cut) + { + performCut(); + } + else if (event == QKeySequence::Copy) + { + performCopy(); + } + else if (event == QKeySequence::Paste) + { + performPaste(); + } + else if (event == QKeySequence::SelectAll) + { + setSelection(0, getTextLength()); + } + else if (event == QKeySequence::MoveToNextChar) + { + setCursorPosition(getCursorCharacterForward(), false); + } + else if (event == QKeySequence::MoveToPreviousChar) + { + setCursorPosition(getCursorCharacterBackward(), false); + } + else if (event == QKeySequence::MoveToNextWord) + { + setCursorPosition(getCursorWordForward(), false); + } + else if (event == QKeySequence::MoveToPreviousWord) + { + setCursorPosition(getCursorWordBackward(), false); + } + else if (event == QKeySequence::MoveToNextLine) + { + setCursorPosition(getCursorLineDown(), false); + } + else if (event == QKeySequence::MoveToPreviousLine) + { + setCursorPosition(getCursorLineUp(), false); + } + else if (event == QKeySequence::MoveToStartOfLine || event == QKeySequence::MoveToStartOfBlock) + { + setCursorPosition(getCursorLineStart(), false); + } + else if (event == QKeySequence::MoveToEndOfLine || event == QKeySequence::MoveToEndOfBlock) + { + setCursorPosition(getCursorLineEnd(), false); + } + else if (event == QKeySequence::MoveToStartOfDocument) + { + setCursorPosition(getCursorDocumentStart(), false); + } + else if (event == QKeySequence::MoveToEndOfDocument) + { + setCursorPosition(getCursorDocumentEnd(), false); + } + else if (event == QKeySequence::SelectNextChar) + { + setCursorPosition(getCursorCharacterForward(), true); + } + else if (event == QKeySequence::SelectPreviousChar) + { + setCursorPosition(getCursorCharacterBackward(), true); + } + else if (event == QKeySequence::SelectNextWord) + { + setCursorPosition(getCursorWordForward(), true); + } + else if (event == QKeySequence::SelectPreviousWord) + { + setCursorPosition(getCursorWordBackward(), true); + } + else if (event == QKeySequence::SelectNextLine) + { + setCursorPosition(getCursorLineDown(), true); + } + else if (event == QKeySequence::SelectPreviousLine) + { + setCursorPosition(getCursorLineUp(), true); + } + else if (event == QKeySequence::SelectStartOfLine || event == QKeySequence::SelectStartOfBlock) + { + setCursorPosition(getCursorLineStart(), true); + } + else if (event == QKeySequence::SelectEndOfLine || event == QKeySequence::SelectEndOfBlock) + { + setCursorPosition(getCursorLineEnd(), true); + } + else if (event == QKeySequence::SelectStartOfDocument) + { + setCursorPosition(getCursorDocumentStart(), true); + } + else if (event == QKeySequence::SelectEndOfDocument) + { + setCursorPosition(getCursorDocumentEnd(), true); + } + else if (event == QKeySequence::DeleteStartOfWord) + { + if (!isReadonly()) + { + setCursorPosition(getCursorWordBackward(), true); + performRemoveSelectedText(); + } + } + else if (event == QKeySequence::DeleteEndOfWord) + { + if (!isReadonly()) + { + setCursorPosition(getCursorWordForward(), true); + performRemoveSelectedText(); + } + } + else if (event == QKeySequence::DeleteEndOfLine) + { + if (!isReadonly()) + { + setCursorPosition(getCursorLineEnd(), true); + performRemoveSelectedText(); + } + } + else if (event == QKeySequence::Deselect) + { + clearSelection(); + } + else if (event == QKeySequence::DeleteCompleteLine) + { + if (!isReadonly()) + { + m_selectionStart = getCurrentLineTextStart(); + m_selectionEnd = getCurrentLineTextEnd(); + performRemoveSelectedText(); + } + } + else if (event == QKeySequence::Backspace || event->key() == Qt::Key_Backspace) + { + performBackspace(); + } + else if (event->key() == Qt::Key_Direction_L) + { + QTextOption option = m_textLayout.textOption(); + option.setTextDirection(Qt::LeftToRight); + m_textLayout.setTextOption(qMove(option)); + updateTextLayout(); + } + else if (event->key() == Qt::Key_Direction_R) + { + QTextOption option = m_textLayout.textOption(); + option.setTextDirection(Qt::LeftToRight); + m_textLayout.setTextOption(qMove(option)); + updateTextLayout(); + } + else if (event->key() == Qt::Key_Up) + { + setCursorPosition(getCursorLineUp(), event->modifiers().testFlag(Qt::ShiftModifier)); + } + else if (event->key() == Qt::Key_Down) + { + setCursorPosition(getCursorLineDown(), event->modifiers().testFlag(Qt::ShiftModifier)); + } + else if (event->key() == Qt::Key_Left) + { + const int position = (event->modifiers().testFlag(Qt::ControlModifier)) ? getCursorWordBackward() : getCursorCharacterBackward(); + setCursorPosition(position, event->modifiers().testFlag(Qt::ShiftModifier)); + } + else if (event->key() == Qt::Key_Right) + { + const int position = (event->modifiers().testFlag(Qt::ControlModifier)) ? getCursorWordForward() : getCursorCharacterForward(); + setCursorPosition(position, event->modifiers().testFlag(Qt::ShiftModifier)); + } + else if (isMultiline() && (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return)) + { + performInsertText(QString::fromUtf16(u"\u2028")); + } + else + { + QString text = event->text(); + if (!text.isEmpty()) + { + performInsertText(text); + } + else + { + event->ignore(); + } + } +} + +void PDFTextEditPseudowidget::setSelection(int startPosition, int selectionLength) +{ + if (selectionLength > 0) + { + // We are selecting to the right + m_selectionStart = startPosition; + m_selectionEnd = qMin(startPosition + selectionLength, getTextLength()); + m_positionCursor = m_selectionEnd; + } + else if (selectionLength < 0) + { + // We are selecting to the left + m_selectionStart = qMax(startPosition + selectionLength, 0); + m_selectionEnd = startPosition; + m_positionCursor = m_selectionStart; + } + else + { + // Clear the selection + m_selectionStart = 0; + m_selectionEnd = 0; + m_positionCursor = startPosition; + } +} + +void PDFTextEditPseudowidget::setCursorPosition(int position, bool select) +{ + if (select) + { + const bool isTextSelected = this->isTextSelected(); + const bool isCursorAtStartOfSelection = isTextSelected && m_selectionStart == m_positionCursor; + const bool isCursorAtEndOfSelection = isTextSelected && m_selectionEnd == m_positionCursor; + + // Do we have selected text, and cursor is at the end of selected text? + // In this case, we must preserve start of the selection (we are manipulating + // with the end of selection. + if (isCursorAtEndOfSelection) + { + m_selectionStart = qMin(m_selectionStart, position); + m_selectionEnd = qMax(m_selectionStart, position); + } + else if (isCursorAtStartOfSelection) + { + // We must preserve end of the text selection, because we are manipulating + // with start of text selection. + m_selectionStart = qMin(m_selectionEnd, position); + m_selectionEnd = qMax(m_selectionEnd, position); + } + else + { + // Otherwise we are manipulating with cursor + m_selectionStart = qMin(m_positionCursor, position); + m_selectionEnd = qMax(m_positionCursor, position); + } + } + + // Why we are clearing text selection, even if we doesn't have it? + // We can have, for example m_selectionStart == m_selectionEnd == 3, + // and we want to have it both zero. + if (!select || !isTextSelected()) + { + clearSelection(); + } + + m_positionCursor = position; +} + +void PDFTextEditPseudowidget::setText(const QString& text) +{ + clearSelection(); + m_editText = text; + setCursorPosition(getPositionEnd(), false); + updateTextLayout(); +} + +void PDFTextEditPseudowidget::setAppearance(const PDFAnnotationDefaultAppearance& appearance, Qt::Alignment textAlignment, QRectF rect, int maxTextLength) +{ + // Set appearance + qreal fontSize = appearance.getFontSize(); + if (qFuzzyIsNull(fontSize)) + { + fontSize = rect.height(); + } + + QFont font(appearance.getFontName()); + font.setHintingPreference(QFont::PreferNoHinting); + font.setPixelSize(qCeil(fontSize)); + font.setStyleStrategy(QFont::ForceOutline); + m_textLayout.setFont(font); + + QTextOption option = m_textLayout.textOption(); + option.setWrapMode(isMultiline() ? QTextOption::WrapAtWordBoundaryOrAnywhere : QTextOption::NoWrap); + option.setAlignment(textAlignment); + option.setUseDesignMetrics(true); + m_textLayout.setTextOption(option); + + m_textColor = appearance.getFontColor(); + if (!m_textColor.isValid()) + { + m_textColor = Qt::black; + } + + m_maxTextLength = maxTextLength; + m_widgetRect = rect; + updateTextLayout(); +} + +void PDFTextEditPseudowidget::setAppearance(const QFont& font, + Qt::Alignment textAlignment, + QRectF rect, + int maxTextLength, + QColor textColor) +{ + // Set appearance + m_textLayout.setFont(font); + + QTextOption option = m_textLayout.textOption(); + option.setWrapMode(isMultiline() ? QTextOption::WrapAtWordBoundaryOrAnywhere : QTextOption::NoWrap); + option.setAlignment(textAlignment); + option.setUseDesignMetrics(true); + m_textLayout.setTextOption(option); + + m_textColor = textColor; + if (!m_textColor.isValid()) + { + m_textColor = Qt::black; + } + + m_maxTextLength = maxTextLength; + m_widgetRect = rect; + updateTextLayout(); +} + +void PDFTextEditPseudowidget::performCut() +{ + if (isReadonly()) + { + return; + } + + performCopy(); + performRemoveSelectedText(); +} + +void PDFTextEditPseudowidget::performCopy() +{ + if (isTextSelected() && !isPassword()) + { + QApplication::clipboard()->setText(getSelectedText(), QClipboard::Clipboard); + } +} + +void PDFTextEditPseudowidget::performPaste() +{ + // We always insert text, even if it is empty. Because we want + // to erase selected text. + performInsertText(QApplication::clipboard()->text(QClipboard::Clipboard)); +} + +void PDFTextEditPseudowidget::performClear() +{ + if (isReadonly()) + { + return; + } + + performSelectAll(); + performRemoveSelectedText(); +} + +void PDFTextEditPseudowidget::performSelectAll() +{ + m_selectionStart = getPositionStart(); + m_selectionEnd = getPositionEnd(); +} + +void PDFTextEditPseudowidget::performBackspace() +{ + if (isReadonly()) + { + return; + } + + // If we have selection, then delete selected text. If we do not have + // selection, then we delete previous character. + if (!isTextSelected()) + { + setCursorPosition(m_textLayout.previousCursorPosition(m_positionCursor, QTextLayout::SkipCharacters), true); + } + + performRemoveSelectedText(); +} + +void PDFTextEditPseudowidget::performDelete() +{ + if (isReadonly()) + { + return; + } + + // If we have selection, then delete selected text. If we do not have + // selection, then we delete previous character. + if (!isTextSelected()) + { + setCursorPosition(m_textLayout.nextCursorPosition(m_positionCursor, QTextLayout::SkipCharacters), true); + } + + performRemoveSelectedText(); +} + +void PDFTextEditPseudowidget::performRemoveSelectedText() +{ + if (isTextSelected()) + { + m_editText.remove(m_selectionStart, getSelectionLength()); + setCursorPosition(m_selectionStart, false); + clearSelection(); + updateTextLayout(); + } +} + +void PDFTextEditPseudowidget::performInsertText(const QString& text) +{ + if (isReadonly()) + { + return; + } + + // Insert text at the cursor + performRemoveSelectedText(); + m_editText.insert(m_positionCursor, text); + setCursorPosition(m_positionCursor + text.length(), false); + updateTextLayout(); +} + +QMatrix PDFTextEditPseudowidget::createTextBoxTransformMatrix(bool edit) const +{ + QMatrix matrix; + + matrix.translate(m_widgetRect.left(), m_widgetRect.bottom()); + matrix.scale(1.0, -1.0); + + if (edit && !isComb() && m_textLayout.isValidCursorPosition(m_positionCursor)) + { + // Jakub Melka: we must scroll the control, so cursor is always + // visible in the widget area. If we are not editing, this not the + // case, because we always show the text from the beginning. + + const QTextLine line = m_textLayout.lineForTextPosition(m_positionCursor); + if (line.isValid()) + { + const qreal xCursorPosition = line.cursorToX(m_positionCursor); + if (xCursorPosition >= m_widgetRect.width()) + { + const qreal delta = xCursorPosition - m_widgetRect.width(); + matrix.translate(-delta, 0.0); + } + + // Check, if we aren't outside of y position + const qreal lineSpacing = line.leadingIncluded() ? line.height() : line.leading() + line.height(); + const qreal lineBottom = lineSpacing * (line.lineNumber() + 1); + + if (lineBottom >= m_widgetRect.height()) + { + const qreal delta = lineBottom - m_widgetRect.height(); + matrix.translate(0.0, -delta); + } + } + } + + if (!isMultiline() && !isComb()) + { + // If text is single line, then adjust text position to the vertical center + QTextLine textLine = m_textLayout.lineAt(0); + if (textLine.isValid()) + { + const qreal lineSpacing = textLine.leadingIncluded() ? textLine.height() : textLine.leading() + textLine.height(); + const qreal textBoxHeight = m_widgetRect.height(); + + if (lineSpacing < textBoxHeight) + { + const qreal delta = (textBoxHeight - lineSpacing) * 0.5; + matrix.translate(0.0, delta); + } + } + } + + return matrix; +} + +std::vector PDFTextEditPseudowidget::getCursorPositions() const +{ + std::vector result; + result.reserve(m_editText.length()); + result.push_back(0); + + int currentPos = 0; + while (currentPos < m_editText.length()) + { + currentPos = m_textLayout.nextCursorPosition(currentPos, QTextLayout::SkipCharacters); + result.push_back(currentPos); + } + + return result; +} + +void PDFTextEditPseudowidget::draw(AnnotationDrawParameters& parameters, bool edit) const +{ + pdf::PDFPainterStateGuard guard(parameters.painter); + + if (parameters.annotation) + { + parameters.boundingRectangle = parameters.annotation->getRectangle(); + } + + QPalette palette = QApplication::palette(); + + auto getAdjustedColor = [¶meters](QColor color) + { + if (parameters.invertColors) + { + return invertColor(color); + } + + return color; + }; + + QPainter* painter = parameters.painter; + + if (edit) + { + pdf::PDFPainterStateGuard guard(painter); + painter->setPen(getAdjustedColor(Qt::black)); + painter->setBrush(Qt::NoBrush); + painter->drawRect(parameters.boundingRectangle); + } + + painter->setClipRect(parameters.boundingRectangle, Qt::IntersectClip); + painter->setWorldMatrix(createTextBoxTransformMatrix(edit), true); + painter->setPen(getAdjustedColor(Qt::black)); + + if (isComb()) + { + const qreal combCount = qMax(m_maxTextLength, 1); + qreal combWidth = m_widgetRect.width() / combCount; + QRectF combRect(0.0, 0.0, combWidth, m_widgetRect.height()); + painter->setFont(m_textLayout.font()); + + QColor textColor = getAdjustedColor(m_textColor); + QColor highlightTextColor = getAdjustedColor(palette.color(QPalette::HighlightedText)); + QColor highlightColor = getAdjustedColor(palette.color(QPalette::Highlight)); + + std::vector positions = getCursorPositions(); + for (size_t i = 1; i < positions.size(); ++i) + { + if (positions[i - 1] >= m_selectionStart && positions[i] - 1 < m_selectionEnd) + { + // We are in text selection + painter->fillRect(combRect, highlightColor); + painter->setPen(highlightTextColor); + } + else + { + // We are not in text selection + painter->setPen(textColor); + } + + int length = positions[i] - positions[i - 1]; + QString text = m_displayText.mid(positions[i - 1], length); + painter->drawText(combRect, Qt::AlignCenter, text); + + // Draw the cursor? + if (edit && m_positionCursor >= positions[i - 1] && m_positionCursor < positions[i]) + { + QRectF cursorRect(combRect.left(), combRect.bottom() - 1, combRect.width(), 1); + painter->fillRect(cursorRect, textColor); + } + + combRect.translate(combWidth, 0.0); + } + + // Draw the cursor onto next unfilled cell? + if (edit && m_positionCursor == getPositionEnd()) + { + QRectF cursorRect(combRect.left(), combRect.bottom() - 1, combRect.width(), 1); + painter->fillRect(cursorRect, textColor); + } + } + else + { + QVector selections; + + QTextLayout::FormatRange defaultFormat; + defaultFormat.start = getPositionStart(); + defaultFormat.length = getTextLength(); + defaultFormat.format.clearBackground(); + defaultFormat.format.setForeground(QBrush(getAdjustedColor(m_textColor), Qt::SolidPattern)); + + // If we are editing, draw selections + if (edit && isTextSelected()) + { + QTextLayout::FormatRange before = defaultFormat; + QTextLayout::FormatRange after = defaultFormat; + + before.start = getPositionStart(); + before.length = m_selectionStart; + after.start = m_selectionEnd; + after.length = getTextLength() - m_selectionEnd; + + QTextLayout::FormatRange selectedFormat = defaultFormat; + selectedFormat.start = m_selectionStart; + selectedFormat.length = getSelectionLength(); + selectedFormat.format.setForeground(QBrush(getAdjustedColor(palette.color(QPalette::HighlightedText)), Qt::SolidPattern)); + selectedFormat.format.setBackground(QBrush(getAdjustedColor(palette.color(QPalette::Highlight)), Qt::SolidPattern)); + + selections = { before, selectedFormat, after}; + } + else + { + selections.push_back(defaultFormat); + } + + // Draw text + m_textLayout.draw(painter, QPointF(0.0, 0.0), selections, QRectF()); + + // If we are editing, also draw text + if (edit && !isReadonly()) + { + m_textLayout.drawCursor(painter, QPointF(0.0, 0.0), m_positionCursor); + } + } +} + +int PDFTextEditPseudowidget::getCursorPositionFromWidgetPosition(const QPointF& point, bool edit) const +{ + QMatrix textBoxSpaceToPageSpace = createTextBoxTransformMatrix(edit); + QMatrix pageSpaceToTextBoxSpace = textBoxSpaceToPageSpace.inverted(); + + QPointF textBoxPoint = pageSpaceToTextBoxSpace.map(point); + + if (isComb()) + { + // If it is comb, then characters are spaced equidistantly + const qreal x = qBound(0.0, textBoxPoint.x(), m_widgetRect.width()); + const size_t position = qFloor(x * qreal(m_maxTextLength) / qreal(m_widgetRect.width())); + std::vector positions = getCursorPositions(); + if (position < positions.size()) + { + return positions[position]; + } + + return positions.back(); + } + else if (m_textLayout.lineCount() > 0) + { + QTextLine line; + qreal yPos = 0.0; + + // Find line under cursor + for (int i = 0; i < m_textLayout.lineCount(); ++i) + { + QTextLine currentLine = m_textLayout.lineAt(i); + const qreal lineSpacing = currentLine.leadingIncluded() ? currentLine.height() : currentLine.leading() + currentLine.height(); + const qreal yNextPos = yPos + lineSpacing; + + if (textBoxPoint.y() >= yPos && textBoxPoint.y() < yNextPos) + { + line = currentLine; + break; + } + + yPos = yNextPos; + } + + // If no line is found, select last line + if (!line.isValid()) + { + if (textBoxPoint.y() < 0.0) + { + line = m_textLayout.lineAt(0); + } + else + { + line = m_textLayout.lineAt(m_textLayout.lineCount() - 1); + } + } + + return line.xToCursor(textBoxPoint.x(), QTextLine::CursorBetweenCharacters); + } + + return 0; +} + +void PDFTextEditPseudowidget::updateTextLayout() +{ + // Prepare display text + if (isPassword()) + { + m_displayText.resize(m_editText.length(), m_passwordReplacementCharacter); + } + else + { + m_displayText = m_editText; + } + + // Perform text layout + m_textLayout.clearLayout(); + m_textLayout.setText(m_displayText); + m_textLayout.beginLayout(); + + QPointF textLinePosition(0.0, 0.0); + + while (true) + { + QTextLine textLine = m_textLayout.createLine(); + if (!textLine.isValid()) + { + // We are finished with layout + break; + } + + textLinePosition.ry() += textLine.leading(); + textLine.setLineWidth(m_widgetRect.width()); + textLine.setPosition(textLinePosition); + textLinePosition.ry() += textLine.height(); + } + m_textLayout.endLayout(); + + // Check length + if (m_maxTextLength > 0) + { + int length = 0; + int currentPos = 0; + + while (currentPos < m_editText.length()) + { + currentPos = m_textLayout.nextCursorPosition(currentPos, QTextLayout::SkipCharacters); + ++length; + + if (length == m_maxTextLength) + { + break; + } + } + + if (currentPos < m_editText.length()) + { + m_editText = m_editText.left(currentPos); + m_positionCursor = qBound(getPositionStart(), m_positionCursor, getPositionEnd()); + updateTextLayout(); + } + } +} + +int PDFTextEditPseudowidget::getSingleStepForward() const +{ + // If direction is right-to-left, then move backward (because + // text is painted from right to left. + return (m_textLayout.textOption().textDirection() == Qt::RightToLeft) ? -1 : 1; +} + +int PDFTextEditPseudowidget::getNextPrevCursorPosition(int referencePosition, int steps, QTextLayout::CursorMode mode) const +{ + int cursor = referencePosition; + + if (steps > 0) + { + for (int i = 0; i < steps; ++i) + { + cursor = m_textLayout.nextCursorPosition(cursor, mode); + } + } + else if (steps < 0) + { + for (int i = 0; i < -steps; ++i) + { + cursor = m_textLayout.previousCursorPosition(cursor, mode); + } + } + + return cursor; +} + +int PDFTextEditPseudowidget::getCurrentLineTextStart() const +{ + return m_textLayout.lineForTextPosition(m_positionCursor).textStart(); +} + +int PDFTextEditPseudowidget::getCurrentLineTextEnd() const +{ + QTextLine textLine = m_textLayout.lineForTextPosition(m_positionCursor); + return textLine.textStart() + textLine.textLength(); +} + +int PDFTextEditPseudowidget::getCursorLineUp() const +{ + QTextLine line = m_textLayout.lineForTextPosition(m_positionCursor); + const int lineIndex = line.lineNumber() - 1; + + if (lineIndex >= 0) + { + QTextLine upLine = m_textLayout.lineAt(lineIndex); + return upLine.xToCursor(line.cursorToX(m_positionCursor), QTextLine::CursorBetweenCharacters); + } + + return m_positionCursor; +} + +int PDFTextEditPseudowidget::getCursorLineDown() const +{ + QTextLine line = m_textLayout.lineForTextPosition(m_positionCursor); + const int lineIndex = line.lineNumber() + 1; + + if (lineIndex < m_textLayout.lineCount()) + { + QTextLine downLine = m_textLayout.lineAt(lineIndex); + return downLine.xToCursor(line.cursorToX(m_positionCursor), QTextLine::CursorBetweenCharacters); + } + + return m_positionCursor; +} + +} // namespace pdf diff --git a/Pdf4QtLib/sources/pdftexteditpseudowidget.h b/Pdf4QtLib/sources/pdftexteditpseudowidget.h new file mode 100644 index 0000000..19d00d7 --- /dev/null +++ b/Pdf4QtLib/sources/pdftexteditpseudowidget.h @@ -0,0 +1,217 @@ +// Copyright (C) 2022 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef PDFTEXTEDITPSEUDOWIDGET_H +#define PDFTEXTEDITPSEUDOWIDGET_H + +#include "pdfglobal.h" +#include "pdfform.h" + +#include +#include + +class QWidget; +class QKeyEvent; + +namespace pdf +{ + +/// "Pseudo" widget, which is emulating text editor, which can be single line, or multiline. +/// Passwords can also be edited and editor can be read only. +class PDFTextEditPseudowidget +{ +public: + explicit PDFTextEditPseudowidget(PDFFormField::FieldFlags flags); + + void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event); + void keyPressEvent(QWidget* widget, QKeyEvent* event); + + inline bool isReadonly() const { return m_flags.testFlag(PDFFormField::ReadOnly); } + inline bool isMultiline() const { return m_flags.testFlag(PDFFormField::Multiline); } + inline bool isPassword() const { return m_flags.testFlag(PDFFormField::Password); } + inline bool isFileSelect() const { return m_flags.testFlag(PDFFormField::FileSelect); } + inline bool isComb() const { return m_flags.testFlag(PDFFormField::Comb); } + + inline bool isEmpty() const { return m_editText.isEmpty(); } + inline bool isTextSelected() const { return !isEmpty() && getSelectionLength() > 0; } + inline bool isWholeTextSelected() const { return !isEmpty() && getSelectionLength() == m_editText.length(); } + + inline int getTextLength() const { return m_editText.length(); } + inline int getSelectionLength() const { return m_selectionEnd - m_selectionStart; } + + inline int getPositionCursor() const { return m_positionCursor; } + inline int getPositionStart() const { return 0; } + inline int getPositionEnd() const { return getTextLength(); } + + inline void clearSelection() { m_selectionStart = m_selectionEnd = 0; } + + inline const QString& getText() const { return m_editText; } + inline QString getSelectedText() const { return m_editText.mid(m_selectionStart, getSelectionLength()); } + + /// Sets (updates) text selection + /// \param startPosition From where we are selecting text + /// \param selectionLength Selection length (positive - to the right, negative - to the left) + void setSelection(int startPosition, int selectionLength); + + /// Moves cursor position. It behaves as usual in text boxes, + /// when user moves the cursor. If \p select is true, then + /// selection is updated. + /// \param position New position of the cursor + /// \param select Select text when moving the cursor? + void setCursorPosition(int position, bool select); + + /// Sets text content of the widget. This functions sets the text, + /// even if widget is readonly. + /// \param text Text to be set + void setText(const QString& text); + + /// Sets widget appearance, such as font, font size, color, text alignment, + /// and rectangle, in which widget resides on page (in page coordinates) + /// \param appearance Appearance + /// \param textAlignment Text alignment + /// \param rect Widget rectangle in page coordinates + /// \param maxTextLength Maximal text length + void setAppearance(const PDFAnnotationDefaultAppearance& appearance, + Qt::Alignment textAlignment, + QRectF rect, + int maxTextLength); + + /// Sets widget appearance, such as font, font size, color, text alignment, + /// and rectangle, in which widget resides on page (in page coordinates) + /// \param font Font + /// \param textAlignment Text alignment + /// \param rect Widget rectangle in page coordinates + /// \param maxTextLength Maximal text length + /// \param textColor Color + void setAppearance(const QFont& font, + Qt::Alignment textAlignment, + QRectF rect, + int maxTextLength, + QColor textColor); + + void performCut(); + void performCopy(); + void performPaste(); + void performClear(); + void performSelectAll(); + void performBackspace(); + void performDelete(); + void performRemoveSelectedText(); + void performInsertText(const QString& text); + + /// Draw text edit using given parameters + /// \param parameters Parameters + void draw(AnnotationDrawParameters& parameters, bool edit) const; + + /// Returns valid cursor position retrieved from position in the widget. + /// \param point Point in page coordinate space + /// \param edit Are we using edit transformations? + /// \returns Cursor position + int getCursorPositionFromWidgetPosition(const QPointF& point, bool edit) const; + + inline int getCursorForward(QTextLayout::CursorMode mode) const { return getNextPrevCursorPosition(getSingleStepForward(), mode); } + inline int getCursorBackward(QTextLayout::CursorMode mode) const { return getNextPrevCursorPosition(getSingleStepBackward(), mode); } + inline int getCursorCharacterForward() const { return getCursorForward(QTextLayout::SkipCharacters); } + inline int getCursorCharacterBackward() const { return getCursorBackward(QTextLayout::SkipCharacters); } + inline int getCursorWordForward() const { return getCursorForward(QTextLayout::SkipWords); } + inline int getCursorWordBackward() const { return getCursorBackward(QTextLayout::SkipWords); } + inline int getCursorDocumentStart() const { return (getSingleStepForward() > 0) ? getPositionStart() : getPositionEnd(); } + inline int getCursorDocumentEnd() const { return (getSingleStepForward() > 0) ? getPositionEnd() : getPositionStart(); } + inline int getCursorLineStart() const { return (getSingleStepForward() > 0) ? getCurrentLineTextStart() : getCurrentLineTextEnd(); } + inline int getCursorLineEnd() const { return (getSingleStepForward() > 0) ? getCurrentLineTextEnd() : getCurrentLineTextStart(); } + inline int getCursorNextLine() const { return getCurrentLineTextEnd(); } + inline int getCursorPreviousLine() const { return getNextPrevCursorPosition(getCurrentLineTextStart(), -1, QTextLayout::SkipCharacters); } + + const QRectF& getWidgetRect() const { return m_widgetRect; } + QFont getFont() const { return m_textLayout.font(); } + QColor getFontColor() const { return m_textColor; } + +private: + /// This function does following things: + /// 1) Clamps edit text to fit maximum length + /// 2) Creates display string from edit string + /// 3) Updates text layout + void updateTextLayout(); + + /// Returns single step forward, which is determined + /// by cursor move style and layout direction. + int getSingleStepForward() const; + + /// Returns single step backward, which is determined + /// by cursor move style and layout direction. + int getSingleStepBackward() const { return -getSingleStepForward(); } + + /// Returns next/previous position, by number of steps, + /// using given cursor mode (skipping characters or whole words). + /// \param steps Number of steps to proceed (can be negative number) + /// \param mode Skip mode - letters or words? + int getNextPrevCursorPosition(int steps, QTextLayout::CursorMode mode) const { return getNextPrevCursorPosition(m_positionCursor, steps, mode); } + + /// Returns next/previous position from reference cursor position, by number of steps, + /// using given cursor mode (skipping characters or whole words). + /// \param referencePosition Reference cursor position + /// \param steps Number of steps to proceed (can be negative number) + /// \param mode Skip mode - letters or words? + int getNextPrevCursorPosition(int referencePosition, int steps, QTextLayout::CursorMode mode) const; + + /// Returns current line text start position + int getCurrentLineTextStart() const; + + /// Returns current line text end position + int getCurrentLineTextEnd() const; + + /// Creates text box transform matrix, which transforms from + /// widget space to page space. + /// \param edit Create matrix for text editing? + QMatrix createTextBoxTransformMatrix(bool edit) const; + + /// Returns vector of cursor positions (which may be not equal + /// to edit string length, because edit string can contain surrogates, + /// or graphemes, which are single glyphs, but represented by more + /// 16-bit unicode codes). + std::vector getCursorPositions() const; + + int getCursorLineUp() const; + int getCursorLineDown() const; + + PDFFormField::FieldFlags m_flags; + + /// Text edited by the user + QString m_editText; + + /// Text, which is displayed. It can differ from text + /// edited by user, in case password is being entered. + QString m_displayText; + + /// Text layout + QTextLayout m_textLayout; + + /// Character for displaying passwords + QChar m_passwordReplacementCharacter; + + int m_selectionStart; + int m_selectionEnd; + int m_positionCursor; + int m_maxTextLength; + + QRectF m_widgetRect; + QColor m_textColor; +}; + +} // namespace pdf + +#endif // PDFTEXTEDITPSEUDOWIDGET_H diff --git a/Pdf4QtLib/sources/pdfutils.h b/Pdf4QtLib/sources/pdfutils.h index 9aaf0fb..afa864c 100644 --- a/Pdf4QtLib/sources/pdfutils.h +++ b/Pdf4QtLib/sources/pdfutils.h @@ -465,6 +465,11 @@ constexpr bool isRectangleHorizontallyOverlapped(const QRectF& r1, const QRectF& return isIntervalOverlap(r1.left(), r1.right(), r2.left(), r2.right()); } +constexpr bool isRectangleVerticallyOverlapped(const QRectF& r1, const QRectF& r2) +{ + return isIntervalOverlap(r1.top(), r1.bottom(), r2.top(), r2.bottom()); +} + inline QColor invertColor(QColor color) { qreal r = 0.0; diff --git a/Pdf4QtLib/sources/pdfwidgettool.cpp b/Pdf4QtLib/sources/pdfwidgettool.cpp index 7f823b6..06871b5 100644 --- a/Pdf4QtLib/sources/pdfwidgettool.cpp +++ b/Pdf4QtLib/sources/pdfwidgettool.cpp @@ -101,11 +101,19 @@ void PDFWidgetTool::setActive(bool active) setActiveImpl(active); updateActions(); - m_proxy->repaintNeeded(); + emit m_proxy->repaintNeeded(); emit toolActivityChanged(active); } } +void PDFWidgetTool::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) +{ + if (PDFWidgetTool* tool = getTopToolstackTool()) + { + tool->shortcutOverrideEvent(widget, event); + } +} + void PDFWidgetTool::keyPressEvent(QWidget* widget, QKeyEvent* event) { if (PDFWidgetTool* tool = getTopToolstackTool()) @@ -130,6 +138,14 @@ void PDFWidgetTool::mousePressEvent(QWidget* widget, QMouseEvent* event) } } +void PDFWidgetTool::mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event) +{ + if (PDFWidgetTool* tool = getTopToolstackTool()) + { + tool->mouseDoubleClickEvent(widget, event); + } +} + void PDFWidgetTool::mouseReleaseEvent(QWidget* widget, QMouseEvent* event) { if (PDFWidgetTool* tool = getTopToolstackTool()) @@ -194,11 +210,13 @@ PDFWidgetTool* PDFWidgetTool::getTopToolstackTool() const void PDFWidgetTool::addTool(PDFWidgetTool* tool) { + tool->setActive(isActive()); m_toolStack.push_back(tool); } void PDFWidgetTool::removeTool() { + m_toolStack.back()->setActive(false); m_toolStack.pop_back(); } @@ -809,8 +827,12 @@ PDFMagnifierTool* PDFToolManager::getMagnifierTool() const void PDFToolManager::shortcutOverrideEvent(QWidget* widget, QKeyEvent* event) { - Q_UNUSED(widget); - Q_UNUSED(event); + event->ignore(); + + if (PDFWidgetTool* activeTool = getActiveTool()) + { + activeTool->shortcutOverrideEvent(widget, event); + } } void PDFToolManager::keyPressEvent(QWidget* widget, QKeyEvent* event) @@ -854,8 +876,12 @@ void PDFToolManager::mousePressEvent(QWidget* widget, QMouseEvent* event) void PDFToolManager::mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event) { - Q_UNUSED(widget); - Q_UNUSED(event); + event->ignore(); + + if (PDFWidgetTool* activeTool = getActiveTool()) + { + activeTool->mouseDoubleClickEvent(widget, event); + } } void PDFToolManager::mouseReleaseEvent(QWidget* widget, QMouseEvent* event) @@ -1070,6 +1096,11 @@ void PDFPickTool::drawPage(QPainter* painter, Q_UNUSED(layoutGetter); Q_UNUSED(errors); + if (!isActive()) + { + return; + } + // If we are picking rectangles, then draw current selection rectangle if (m_mode == Mode::Rectangles && m_drawSelectionRectangle && m_pageIndex == pageIndex && !m_pickedPoints.empty()) { @@ -1097,6 +1128,11 @@ void PDFPickTool::drawPage(QPainter* painter, void PDFPickTool::drawPostRendering(QPainter* painter, QRect rect) const { + if (!isActive()) + { + return; + } + if (m_mode != Mode::Images) { m_snapper.drawSnapPoints(painter); @@ -1233,7 +1269,7 @@ void PDFPickTool::resetTool() m_snapper.clearReferencePoint(); buildSnapData(); - getProxy()->repaintNeeded(); + emit getProxy()->repaintNeeded(); } void PDFPickTool::buildSnapData() diff --git a/Pdf4QtLib/sources/pdfwidgettool.h b/Pdf4QtLib/sources/pdfwidgettool.h index 52f7c68..c85e78d 100644 --- a/Pdf4QtLib/sources/pdfwidgettool.h +++ b/Pdf4QtLib/sources/pdfwidgettool.h @@ -62,6 +62,11 @@ public: /// Returns action for activating/deactivating this tool QAction* getAction() const { return m_action; } + /// Handles shortcut override event from widget, over which tool operates + /// \param widget Widget, over which tool operates + /// \param event Event + virtual void shortcutOverrideEvent(QWidget* widget, QKeyEvent* event); + /// Handles key press event from widget, over which tool operates /// \param widget Widget, over which tool operates /// \param event Event @@ -77,6 +82,11 @@ public: /// \param event Event virtual void mousePressEvent(QWidget* widget, QMouseEvent* event); + /// Handles mouse double click event from widget, over which tool operates + /// \param widget Widget, over which tool operates + /// \param event Event + virtual void mouseDoubleClickEvent(QWidget* widget, QMouseEvent* event); + /// Handles mouse release event from widget, over which tool operates /// \param widget Widget, over which tool operates /// \param event Event @@ -344,8 +354,8 @@ public: void setSelectionRectangleColor(QColor selectionRectangleColor); signals: - void pointPicked(PDFInteger pageIndex, QPointF pagePoint); - void rectanglePicked(PDFInteger pageIndex, QRectF pageRectangle); + void pointPicked(pdf::PDFInteger pageIndex, QPointF pagePoint); + void rectanglePicked(pdf::PDFInteger pageIndex, QRectF pageRectangle); void imagePicked(const QImage& image); protected: diff --git a/Pdf4QtViewer/pdf4qtviewer.qrc b/Pdf4QtViewer/pdf4qtviewer.qrc index 985f175..6f2a227 100644 --- a/Pdf4QtViewer/pdf4qtviewer.qrc +++ b/Pdf4QtViewer/pdf4qtviewer.qrc @@ -74,5 +74,21 @@ resources/save.svg resources/save-as.svg resources/select-all-text.svg + resources/pce-align-bottom.svg + resources/pce-align-h-center.svg + resources/pce-align-left.svg + resources/pce-align-right.svg + resources/pce-align-top.svg + resources/pce-align-v-center.svg + resources/pce-center-h.svg + resources/pce-center-v.svg + resources/pce-center-vh.svg + resources/pce-layout-form.svg + resources/pce-layout-grid.svg + resources/pce-layout-h.svg + resources/pce-layout-v.svg + resources/pce-same-height.svg + resources/pce-same-size.svg + resources/pce-same-width.svg diff --git a/Pdf4QtViewer/pdfadvancedfindwidget.cpp b/Pdf4QtViewer/pdfadvancedfindwidget.cpp index e5ac8a2..f2c082a 100644 --- a/Pdf4QtViewer/pdfadvancedfindwidget.cpp +++ b/Pdf4QtViewer/pdfadvancedfindwidget.cpp @@ -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(); diff --git a/Pdf4QtViewer/pdfviewermainwindow.cpp b/Pdf4QtViewer/pdfviewermainwindow.cpp index 905a4fd..2a63550 100644 --- a/Pdf4QtViewer/pdfviewermainwindow.cpp +++ b/Pdf4QtViewer/pdfviewermainwindow.cpp @@ -104,6 +104,7 @@ PDFViewerMainWindow::PDFViewerMainWindow(QWidget* parent) : // Initialize toolbar icon size adjustToolbar(ui->mainToolBar); + ui->mainToolBar->setWindowTitle(tr("Standard")); // Initialize task bar progress #ifdef Q_OS_WIN diff --git a/Pdf4QtViewer/pdfviewermainwindowlite.cpp b/Pdf4QtViewer/pdfviewermainwindowlite.cpp index 68d0b8f..fb1638c 100644 --- a/Pdf4QtViewer/pdfviewermainwindowlite.cpp +++ b/Pdf4QtViewer/pdfviewermainwindowlite.cpp @@ -102,6 +102,7 @@ PDFViewerMainWindowLite::PDFViewerMainWindowLite(QWidget* parent) : // Initialize toolbar icon size adjustToolbar(ui->mainToolBar); + ui->mainToolBar->setWindowTitle(tr("Standard")); // Initialize task bar progress #ifdef Q_OS_WIN diff --git a/Pdf4QtViewer/resources/pce-align-bottom.svg b/Pdf4QtViewer/resources/pce-align-bottom.svg new file mode 100644 index 0000000..9b67948 --- /dev/null +++ b/Pdf4QtViewer/resources/pce-align-bottom.svg @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/Pdf4QtViewer/resources/pce-align-h-center.svg b/Pdf4QtViewer/resources/pce-align-h-center.svg new file mode 100644 index 0000000..3c55698 --- /dev/null +++ b/Pdf4QtViewer/resources/pce-align-h-center.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/Pdf4QtViewer/resources/pce-align-left.svg b/Pdf4QtViewer/resources/pce-align-left.svg new file mode 100644 index 0000000..100ab7c --- /dev/null +++ b/Pdf4QtViewer/resources/pce-align-left.svg @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/Pdf4QtViewer/resources/pce-align-right.svg b/Pdf4QtViewer/resources/pce-align-right.svg new file mode 100644 index 0000000..9dcd851 --- /dev/null +++ b/Pdf4QtViewer/resources/pce-align-right.svg @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/Pdf4QtViewer/resources/pce-align-top.svg b/Pdf4QtViewer/resources/pce-align-top.svg new file mode 100644 index 0000000..5eac6ef --- /dev/null +++ b/Pdf4QtViewer/resources/pce-align-top.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/Pdf4QtViewer/resources/pce-align-v-center.svg b/Pdf4QtViewer/resources/pce-align-v-center.svg new file mode 100644 index 0000000..609e684 --- /dev/null +++ b/Pdf4QtViewer/resources/pce-align-v-center.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/Pdf4QtViewer/resources/pce-apply-changes.svg b/Pdf4QtViewer/resources/pce-apply-changes.svg new file mode 100644 index 0000000..44dc6a6 --- /dev/null +++ b/Pdf4QtViewer/resources/pce-apply-changes.svg @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + diff --git a/Pdf4QtViewer/resources/pce-center-h.svg b/Pdf4QtViewer/resources/pce-center-h.svg new file mode 100644 index 0000000..2a4907c --- /dev/null +++ b/Pdf4QtViewer/resources/pce-center-h.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + diff --git a/Pdf4QtViewer/resources/pce-center-v.svg b/Pdf4QtViewer/resources/pce-center-v.svg new file mode 100644 index 0000000..316a762 --- /dev/null +++ b/Pdf4QtViewer/resources/pce-center-v.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + diff --git a/Pdf4QtViewer/resources/pce-center-vh.svg b/Pdf4QtViewer/resources/pce-center-vh.svg new file mode 100644 index 0000000..6ec89fc --- /dev/null +++ b/Pdf4QtViewer/resources/pce-center-vh.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + diff --git a/Pdf4QtViewer/resources/pce-layout-form.svg b/Pdf4QtViewer/resources/pce-layout-form.svg new file mode 100644 index 0000000..e4550fe --- /dev/null +++ b/Pdf4QtViewer/resources/pce-layout-form.svg @@ -0,0 +1,20 @@ + + + + + + + + + + diff --git a/Pdf4QtViewer/resources/pce-layout-grid.svg b/Pdf4QtViewer/resources/pce-layout-grid.svg new file mode 100644 index 0000000..6001bba --- /dev/null +++ b/Pdf4QtViewer/resources/pce-layout-grid.svg @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Pdf4QtViewer/resources/pce-layout-h.svg b/Pdf4QtViewer/resources/pce-layout-h.svg new file mode 100644 index 0000000..059febe --- /dev/null +++ b/Pdf4QtViewer/resources/pce-layout-h.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + diff --git a/Pdf4QtViewer/resources/pce-layout-v.svg b/Pdf4QtViewer/resources/pce-layout-v.svg new file mode 100644 index 0000000..91a63b2 --- /dev/null +++ b/Pdf4QtViewer/resources/pce-layout-v.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + diff --git a/Pdf4QtViewer/resources/pce-same-height.svg b/Pdf4QtViewer/resources/pce-same-height.svg new file mode 100644 index 0000000..0c4ec18 --- /dev/null +++ b/Pdf4QtViewer/resources/pce-same-height.svg @@ -0,0 +1,19 @@ + + + + + + + + + + diff --git a/Pdf4QtViewer/resources/pce-same-size.svg b/Pdf4QtViewer/resources/pce-same-size.svg new file mode 100644 index 0000000..a43a441 --- /dev/null +++ b/Pdf4QtViewer/resources/pce-same-size.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + diff --git a/Pdf4QtViewer/resources/pce-same-width.svg b/Pdf4QtViewer/resources/pce-same-width.svg new file mode 100644 index 0000000..e4525f0 --- /dev/null +++ b/Pdf4QtViewer/resources/pce-same-width.svg @@ -0,0 +1,19 @@ + + + + + + + + + + diff --git a/Pdf4QtViewer/resources/pce-select-brush.svg b/Pdf4QtViewer/resources/pce-select-brush.svg new file mode 100644 index 0000000..6a450c1 --- /dev/null +++ b/Pdf4QtViewer/resources/pce-select-brush.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + diff --git a/Pdf4QtViewer/resources/pce-select-color.svg b/Pdf4QtViewer/resources/pce-select-color.svg new file mode 100644 index 0000000..df8183d --- /dev/null +++ b/Pdf4QtViewer/resources/pce-select-color.svg @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/Pdf4QtViewer/resources/pce-select-font.svg b/Pdf4QtViewer/resources/pce-select-font.svg new file mode 100644 index 0000000..f2979d1 --- /dev/null +++ b/Pdf4QtViewer/resources/pce-select-font.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + diff --git a/Pdf4QtViewer/resources/pce-select-pen.svg b/Pdf4QtViewer/resources/pce-select-pen.svg new file mode 100644 index 0000000..fb5fece --- /dev/null +++ b/Pdf4QtViewer/resources/pce-select-pen.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + diff --git a/Pdf4QtViewer/resources/placeholder.svg b/Pdf4QtViewer/resources/placeholder.svg new file mode 100644 index 0000000..d2a3670 --- /dev/null +++ b/Pdf4QtViewer/resources/placeholder.svg @@ -0,0 +1,57 @@ + + + +image/svg+xml + +? + \ No newline at end of file diff --git a/Pdf4QtViewerLite/app-icon.ico b/Pdf4QtViewerLite/app-icon.ico index dedae02..06c4c45 100644 Binary files a/Pdf4QtViewerLite/app-icon.ico and b/Pdf4QtViewerLite/app-icon.ico differ diff --git a/Pdf4QtViewerLite/app-icon.svg b/Pdf4QtViewerLite/app-icon.svg new file mode 100644 index 0000000..3921568 --- /dev/null +++ b/Pdf4QtViewerLite/app-icon.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + diff --git a/Pdf4QtViewerPlugins/DimensionsPlugin/DimensionsPlugin.qbs b/Pdf4QtViewerPlugins/DimensionsPlugin/DimensionsPlugin.qbs index 64b84b8..64e7f7f 100644 --- a/Pdf4QtViewerPlugins/DimensionsPlugin/DimensionsPlugin.qbs +++ b/Pdf4QtViewerPlugins/DimensionsPlugin/DimensionsPlugin.qbs @@ -10,6 +10,6 @@ Pdf4QtPlugin { ] Properties { condition: qbs.hostOS.contains("windows") - cpp.defines: "DIMENTIONPLUGIN_LIBRARY" + cpp.defines: "DIMENSIONPLUGIN_LIBRARY" } } diff --git a/Pdf4QtViewerPlugins/Pdf4QtViewerPlugins.pro b/Pdf4QtViewerPlugins/Pdf4QtViewerPlugins.pro index b9b11bb..0d1fd7b 100644 --- a/Pdf4QtViewerPlugins/Pdf4QtViewerPlugins.pro +++ b/Pdf4QtViewerPlugins/Pdf4QtViewerPlugins.pro @@ -24,6 +24,7 @@ SUBDIRS += \ RedactPlugin \ OutputPreviewPlugin \ ObjectInspectorPlugin \ - AudioBookPlugin + AudioBookPlugin \ + SignaturePlugin diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/SignaturePlugin.json b/Pdf4QtViewerPlugins/SignaturePlugin/SignaturePlugin.json new file mode 100644 index 0000000..2c97fe9 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/SignaturePlugin.json @@ -0,0 +1,7 @@ +{ + "Name" : "Signature", + "Author" : "Jakub Melka", + "Version" : "1.0.0", + "License" : "LGPL v3", + "Description" : "Electronically or digitally sign PDF document." +} diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/SignaturePlugin.pro b/Pdf4QtViewerPlugins/SignaturePlugin/SignaturePlugin.pro new file mode 100644 index 0000000..c08e5d6 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/SignaturePlugin.pro @@ -0,0 +1,68 @@ +# Copyright (C) 2022 Jakub Melka +# +# This file is part of PDF4QT. +# +# PDF4QT is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# with the written consent of the copyright owner, any later version. +# +# PDF4QT is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with PDF4QT. If not, see . + +TEMPLATE = lib +DEFINES += SIGNATUREPLUGIN_LIBRARY + +QT += gui widgets + +LIBS += -L$$OUT_PWD/../.. + +LIBS += -lPdf4QtLib + +QMAKE_CXXFLAGS += /std:c++latest /utf-8 + +INCLUDEPATH += $$PWD/../../Pdf4QtLib/Sources + +DESTDIR = $$OUT_PWD/../../pdfplugins + +CONFIG += c++11 + +Pdf4Qt_OPENSSL_PATH = $$absolute_path(../../Tools, $$[QT_INSTALL_PREFIX]) + +# Link OpenSSL +LIBS += -L$$Pdf4Qt_OPENSSL_PATH/OpenSSL/Win_x64/bin -L$$Pdf4Qt_OPENSSL_PATH/OpenSSL/Win_x64/lib -llibcrypto -llibssl +INCLUDEPATH += $$Pdf4Qt_OPENSSL_PATH/OpenSSL/Win_x64/include +DEPENDPATH += $$Pdf4Qt_OPENSSL_PATH/OpenSSL/Win_x64/include + +SOURCES += \ + certificatemanager.cpp \ + certificatemanagerdialog.cpp \ + createcertificatedialog.cpp \ + signatureplugin.cpp \ + signdialog.cpp + +HEADERS += \ + certificatemanager.h \ + certificatemanagerdialog.h \ + createcertificatedialog.h \ + signatureplugin.h \ + signdialog.h + +CONFIG += force_debug_info + +DISTFILES += \ + SignaturePlugin.json + +RESOURCES += \ + icons.qrc + +FORMS += \ + certificatemanagerdialog.ui \ + createcertificatedialog.ui \ + signdialog.ui + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/SignaturePlugin.qbs b/Pdf4QtViewerPlugins/SignaturePlugin/SignaturePlugin.qbs new file mode 100644 index 0000000..4004868 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/SignaturePlugin.qbs @@ -0,0 +1,15 @@ +import qbs + +Pdf4QtPlugin { + name: "SignaturePlugin" + files: [ + "*.h", + "*.cpp", + "*.ui", + "icons.qrc", + ] + Properties { + condition: qbs.hostOS.contains("windows") + cpp.defines: "SIGNATUREPLUGIN_LIBRARY" + } +} diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/accept-mark.svg b/Pdf4QtViewerPlugins/SignaturePlugin/accept-mark.svg new file mode 100644 index 0000000..d577d96 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/accept-mark.svg @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/activate.svg b/Pdf4QtViewerPlugins/SignaturePlugin/activate.svg new file mode 100644 index 0000000..4869237 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/activate.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.cpp new file mode 100644 index 0000000..2fd5c08 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.cpp @@ -0,0 +1,250 @@ +// Copyright (C) 2022 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#include "certificatemanager.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace pdfplugin +{ + +CertificateManager::CertificateManager() +{ + +} + +template +using openssl_ptr = std::unique_ptr; + +void CertificateManager::createCertificate(const NewCertificateInfo& info) +{ + openssl_ptr pksBuffer(BIO_new(BIO_s_mem()), &BIO_free_all); + + if (pksBuffer) + { + openssl_ptr bignumber(BN_new(), &BN_free); + openssl_ptr rsaKey(RSA_new(), &RSA_free); + + BN_set_word(bignumber.get(), RSA_F4); + const int rsaResult = RSA_generate_key_ex(rsaKey.get(), info.rsaKeyLength, bignumber.get(), nullptr); + if (rsaResult) + { + openssl_ptr certificate(X509_new(), &X509_free); + openssl_ptr privateKey(EVP_PKEY_new(), &EVP_PKEY_free); + + EVP_PKEY_set1_RSA(privateKey.get(), rsaKey.get()); + ASN1_INTEGER* serialNumber = X509_get_serialNumber(certificate.get()); + ASN1_INTEGER_set(serialNumber, info.serialNumber); + + // Set validity of the certificate + X509_gmtime_adj(X509_getm_notBefore(certificate.get()), 0); + X509_gmtime_adj(X509_getm_notAfter(certificate.get()), info.validityInSeconds); + + // Set name + X509_NAME* name = X509_get_subject_name(certificate.get()); + + auto addString = [name](const char* identifier, QString string) + { + if (string.isEmpty()) + { + return; + } + + QByteArray stringUtf8 = string.toUtf8(); + X509_NAME_add_entry_by_txt(name, identifier, MBSTRING_UTF8, reinterpret_cast(stringUtf8.constData()), stringUtf8.length(), -1, 0); + }; + addString("C", info.certCountryCode); + addString("O", info.certOrganization); + addString("OU", info.certOrganizationUnit); + addString("CN", info.certCommonName); + addString("E", info.certEmail); + + X509_EXTENSION* extension = nullptr; + X509V3_CTX context = { }; + X509V3_set_ctx_nodb(&context); + X509V3_set_ctx(&context, certificate.get(), certificate.get(), nullptr, nullptr, 0); + extension = X509V3_EXT_conf_nid (NULL, &context, NID_key_usage, "digitalSignature, keyAgreement"); + X509_add_ext(certificate.get(), extension, -1); + X509_EXTENSION_free(extension); + + X509_set_issuer_name(certificate.get(), name); + + // Set public key + X509_set_pubkey(certificate.get(), privateKey.get()); + X509_sign(certificate.get(), privateKey.get(), EVP_sha512()); + + // Private key password + QByteArray privateKeyPaswordUtf8 = info.privateKeyPasword.toUtf8(); + + // Write the data + openssl_ptr pkcs12(PKCS12_create(privateKeyPaswordUtf8.constData(), + nullptr, + privateKey.get(), + certificate.get(), + nullptr, + 0, + 0, + PKCS12_DEFAULT_ITER, + PKCS12_DEFAULT_ITER, + 0), &PKCS12_free); + i2d_PKCS12_bio(pksBuffer.get(), pkcs12.get()); + + BUF_MEM* pksMemoryBuffer = nullptr; + BIO_get_mem_ptr(pksBuffer.get(), &pksMemoryBuffer); + + if (!info.fileName.isEmpty()) + { + QFile file(info.fileName); + if (file.open(QFile::WriteOnly | QFile::Truncate)) + { + file.write(pksMemoryBuffer->data, pksMemoryBuffer->length); + file.close(); + } + } + } + } +} + +QFileInfoList CertificateManager::getCertificates() +{ + QDir directory(getCertificateDirectory()); + return directory.entryInfoList(QStringList() << "*.pfx", QDir::Files | QDir::NoDotAndDotDot | QDir::Readable, QDir::Name); +} + +QString CertificateManager::getCertificateDirectory() +{ + QDir directory(QStandardPaths::standardLocations(QStandardPaths::AppDataLocation).front() + "/certificates/"); + return directory.absolutePath(); +} + +QString CertificateManager::generateCertificateFileName() +{ + QString directoryString = getCertificateDirectory(); + QDir directory(directoryString); + + int certificateIndex = 1; + while (true) + { + QString fileName = directory.absoluteFilePath(QString("cert_%1.pfx").arg(certificateIndex++)); + if (!QFile::exists(fileName)) + { + return fileName; + } + } + + return QString(); +} + +bool CertificateManager::isCertificateValid(QString fileName, QString password) +{ + QFile file(fileName); + if (file.open(QFile::ReadOnly)) + { + QByteArray data = file.readAll(); + file.close(); + + openssl_ptr pksBuffer(BIO_new(BIO_s_mem()), &BIO_free_all); + BIO_write(pksBuffer.get(), data.constData(), data.length()); + + openssl_ptr pkcs12(d2i_PKCS12_bio(pksBuffer.get(), nullptr), &PKCS12_free); + if (pkcs12) + { + const char* passwordPointer = nullptr; + QByteArray passwordByteArray = password.isEmpty() ? QByteArray() : password.toUtf8(); + if (!passwordByteArray.isEmpty()) + { + passwordPointer = passwordByteArray.constData(); + } + + return PKCS12_parse(pkcs12.get(), passwordPointer, nullptr, nullptr, nullptr) == 1; + } + } + + return false; +} + +bool SignatureFactory::sign(QString certificateName, QString password, QByteArray data, QByteArray& result) +{ + QFile file(certificateName); + if (file.open(QFile::ReadOnly)) + { + QByteArray certificateData = file.readAll(); + file.close(); + + openssl_ptr certificateBuffer(BIO_new(BIO_s_mem()), &BIO_free_all); + BIO_write(certificateBuffer.get(), certificateData.constData(), certificateData.length()); + + openssl_ptr pkcs12(d2i_PKCS12_bio(certificateBuffer.get(), nullptr), &PKCS12_free); + if (pkcs12) + { + const char* passwordPointer = nullptr; + QByteArray passwordByteArray = password.isEmpty() ? QByteArray() : password.toUtf8(); + if (!passwordByteArray.isEmpty()) + { + passwordPointer = passwordByteArray.constData(); + } + + EVP_PKEY* key = nullptr; + X509* certificate = nullptr; + STACK_OF(X509)* certificates = nullptr; + if (PKCS12_parse(pkcs12.get(), passwordPointer, &key, &certificate, &certificates) == 1) + { + openssl_ptr signedDataBuffer(BIO_new(BIO_s_mem()), &BIO_free_all); + BIO_write(signedDataBuffer.get(), data.constData(), data.length()); + + PKCS7* signature = PKCS7_sign(certificate, key, certificates, signedDataBuffer.get(), PKCS7_DETACHED | PKCS7_BINARY); + if (signature) + { + openssl_ptr outputBuffer(BIO_new(BIO_s_mem()), &BIO_free_all); + i2d_PKCS7_bio(outputBuffer.get(), signature); + + BUF_MEM* pksMemoryBuffer = nullptr; + BIO_get_mem_ptr(outputBuffer.get(), &pksMemoryBuffer); + + result = QByteArray(pksMemoryBuffer->data, int(pksMemoryBuffer->length)); + + EVP_PKEY_free(key); + X509_free(certificate); + sk_X509_free(certificates); + return true; + } + + EVP_PKEY_free(key); + X509_free(certificate); + sk_X509_free(certificates); + return false; + } + } + } + + return false; +} + +} // namespace pdfplugin diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.h b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.h new file mode 100644 index 0000000..94de089 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanager.h @@ -0,0 +1,64 @@ +// Copyright (C) 2022 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef CERTIFICATEMANAGER_H +#define CERTIFICATEMANAGER_H + +#include +#include + +namespace pdfplugin +{ + +class CertificateManager +{ +public: + CertificateManager(); + + struct NewCertificateInfo + { + QString fileName; + QString privateKeyPasword; + + QString certCountryCode; + QString certOrganization; + QString certOrganizationUnit; + QString certCommonName; + QString certEmail; + + int rsaKeyLength = 1024; + int validityInSeconds = 2 * 365 * 24 * 3600; + long serialNumber = 1; + }; + + void createCertificate(const NewCertificateInfo& info); + + static QFileInfoList getCertificates(); + static QString getCertificateDirectory(); + static QString generateCertificateFileName(); + static bool isCertificateValid(QString fileName, QString password); +}; + +class SignatureFactory +{ +public: + static bool sign(QString certificateName, QString password, QByteArray data, QByteArray& result); +}; + +} // namespace pdfplugin + +#endif // CERTIFICATEMANAGER_H diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanagerdialog.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanagerdialog.cpp new file mode 100644 index 0000000..a8c1954 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanagerdialog.cpp @@ -0,0 +1,133 @@ +// Copyright (C) 2022 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#include "certificatemanagerdialog.h" +#include "ui_certificatemanagerdialog.h" +#include "createcertificatedialog.h" + +#include "pdfwidgetutils.h" + +#include +#include +#include +#include +#include +#include + +namespace pdfplugin +{ + +CertificateManagerDialog::CertificateManagerDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::CertificateManagerDialog), + m_newCertificateButton(nullptr), + m_openCertificateDirectoryButton(nullptr), + m_deleteCertificateButton(nullptr), + m_importCertificateButton(nullptr), + m_certificateFileModel(nullptr) +{ + ui->setupUi(this); + + QDir::root().mkpath(CertificateManager::getCertificateDirectory()); + + m_certificateFileModel = new QFileSystemModel(this); + QModelIndex rootIndex = m_certificateFileModel->setRootPath(CertificateManager::getCertificateDirectory()); + ui->fileView->setModel(m_certificateFileModel); + ui->fileView->setRootIndex(rootIndex); + + m_newCertificateButton = ui->buttonBox->addButton(tr("Create"), QDialogButtonBox::ActionRole); + m_openCertificateDirectoryButton = ui->buttonBox->addButton(tr("Open Directory"), QDialogButtonBox::ActionRole); + m_deleteCertificateButton = ui->buttonBox->addButton(tr("Delete"), QDialogButtonBox::ActionRole); + m_importCertificateButton = ui->buttonBox->addButton(tr("Import"), QDialogButtonBox::ActionRole); + + connect(m_newCertificateButton, &QPushButton::clicked, this, &CertificateManagerDialog::onNewCertificateClicked); + connect(m_openCertificateDirectoryButton, &QPushButton::clicked, this, &CertificateManagerDialog::onOpenCertificateDirectoryClicked); + connect(m_deleteCertificateButton, &QPushButton::clicked, this, &CertificateManagerDialog::onDeleteCertificateClicked); + connect(m_importCertificateButton, &QPushButton::clicked, this, &CertificateManagerDialog::onImportCertificateClicked); + + setMinimumSize(pdf::PDFWidgetUtils::scaleDPI(this, QSize(640, 480))); +} + +CertificateManagerDialog::~CertificateManagerDialog() +{ + delete ui; +} + +void CertificateManagerDialog::onNewCertificateClicked() +{ + CreateCertificateDialog dialog(this); + if (dialog.exec() == CreateCertificateDialog::Accepted) + { + const CertificateManager::NewCertificateInfo info = dialog.getNewCertificateInfo(); + m_certificateManager.createCertificate(info); + } +} + +void CertificateManagerDialog::onOpenCertificateDirectoryClicked() +{ + QDesktopServices::openUrl(QString("file:///%1").arg(CertificateManager::getCertificateDirectory(), QUrl::TolerantMode)); +} + +void CertificateManagerDialog::onDeleteCertificateClicked() +{ + QFileInfo fileInfo = m_certificateFileModel->fileInfo(ui->fileView->currentIndex()); + if (fileInfo.exists()) + { + if (QMessageBox::question(this, tr("Confirm delete"), tr("Do you want to delete certificate '%1'?").arg(fileInfo.fileName()), QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) + { + QFile file(fileInfo.filePath()); + if (!file.remove()) + { + QMessageBox::critical(this, tr("Error"), tr("Cannot delete certificate '%1'").arg(fileInfo.fileName())); + } + } + } +} + +void CertificateManagerDialog::onImportCertificateClicked() +{ + QString selectedFile = QFileDialog::getOpenFileName(this, tr("Import Certificate"), QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation), tr("Certificate file (*.pfx);;All files (*.*)")); + + if (selectedFile.isEmpty()) + { + return; + } + + QFile file(selectedFile); + if (file.exists()) + { + QString path = CertificateManager::getCertificateDirectory(); + QString targetFile = QString("%1/%2").arg(path, QFileInfo(file).fileName()); + if (QFile::exists(targetFile)) + { + QMessageBox::critical(this, tr("Error"), tr("Target file exists. Please rename the certificate file to import.")); + } + else + { + if (file.copy(targetFile)) + { + QMessageBox::information(this, tr("Import Certificate"), tr("Certificate '%1' was successfully imported.").arg(file.fileName())); + } + else + { + QMessageBox::critical(this, tr("Import Certificate"), tr("Error occured during certificate '%1' import.").arg(file.fileName())); + } + } + } +} + +} // namespace pdfplugin diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanagerdialog.h b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanagerdialog.h new file mode 100644 index 0000000..1fcdfb5 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanagerdialog.h @@ -0,0 +1,61 @@ +// Copyright (C) 2022 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef CERTIFICATEMANAGERDIALOG_H +#define CERTIFICATEMANAGERDIALOG_H + +#include "certificatemanager.h" + +#include + +class QAction; +class QFileSystemModel; + +namespace Ui +{ +class CertificateManagerDialog; +} + +namespace pdfplugin +{ + +class CertificateManagerDialog : public QDialog +{ + Q_OBJECT + +public: + explicit CertificateManagerDialog(QWidget* parent); + virtual ~CertificateManagerDialog() override; + +private: + void onNewCertificateClicked(); + void onOpenCertificateDirectoryClicked(); + void onDeleteCertificateClicked(); + void onImportCertificateClicked(); + + Ui::CertificateManagerDialog* ui; + CertificateManager m_certificateManager; + QPushButton* m_newCertificateButton; + QPushButton* m_openCertificateDirectoryButton; + QPushButton* m_deleteCertificateButton; + QPushButton* m_importCertificateButton; + QFileSystemModel* m_certificateFileModel; +}; + +} // namespace pdfplugin + +#endif // CERTIFICATEMANAGERDIALOG_H diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanagerdialog.ui b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanagerdialog.ui new file mode 100644 index 0000000..8fb0941 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/certificatemanagerdialog.ui @@ -0,0 +1,76 @@ + + + CertificateManagerDialog + + + + 0 + 0 + 789 + 511 + + + + Certificate Manager + + + + + + Certificates + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + + + buttonBox + accepted() + CertificateManagerDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + CertificateManagerDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/certificates.svg b/Pdf4QtViewerPlugins/SignaturePlugin/certificates.svg new file mode 100644 index 0000000..c534a8b --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/certificates.svg @@ -0,0 +1,80 @@ + + + + + + + + + + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/clear.svg b/Pdf4QtViewerPlugins/SignaturePlugin/clear.svg new file mode 100644 index 0000000..9fbb320 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/clear.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/create-dot.svg b/Pdf4QtViewerPlugins/SignaturePlugin/create-dot.svg new file mode 100644 index 0000000..97427e1 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/create-dot.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/create-freehand-curve.svg b/Pdf4QtViewerPlugins/SignaturePlugin/create-freehand-curve.svg new file mode 100644 index 0000000..3b8e3b4 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/create-freehand-curve.svg @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/create-horizontal-line.svg b/Pdf4QtViewerPlugins/SignaturePlugin/create-horizontal-line.svg new file mode 100644 index 0000000..34272e6 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/create-horizontal-line.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/create-line.svg b/Pdf4QtViewerPlugins/SignaturePlugin/create-line.svg new file mode 100644 index 0000000..ff400fb --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/create-line.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/create-no-mark.svg b/Pdf4QtViewerPlugins/SignaturePlugin/create-no-mark.svg new file mode 100644 index 0000000..a5f20ea --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/create-no-mark.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/create-rectangle.svg b/Pdf4QtViewerPlugins/SignaturePlugin/create-rectangle.svg new file mode 100644 index 0000000..5b3c056 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/create-rectangle.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/create-rounded-rectangle.svg b/Pdf4QtViewerPlugins/SignaturePlugin/create-rounded-rectangle.svg new file mode 100644 index 0000000..397628a --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/create-rounded-rectangle.svg @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/create-svg-image.svg b/Pdf4QtViewerPlugins/SignaturePlugin/create-svg-image.svg new file mode 100644 index 0000000..0d46519 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/create-svg-image.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/create-text.svg b/Pdf4QtViewerPlugins/SignaturePlugin/create-text.svg new file mode 100644 index 0000000..8e4173a --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/create-text.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/create-vertical-line.svg b/Pdf4QtViewerPlugins/SignaturePlugin/create-vertical-line.svg new file mode 100644 index 0000000..6b4239f --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/create-vertical-line.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/create-yes-mark.svg b/Pdf4QtViewerPlugins/SignaturePlugin/create-yes-mark.svg new file mode 100644 index 0000000..d9c53c8 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/create-yes-mark.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/createcertificatedialog.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/createcertificatedialog.cpp new file mode 100644 index 0000000..4015502 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/createcertificatedialog.cpp @@ -0,0 +1,162 @@ +// Copyright (C) 2022 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#include "createcertificatedialog.h" +#include "ui_createcertificatedialog.h" + +#include "certificatemanager.h" + +#include +#include +#include +#include + +namespace pdfplugin +{ + +CreateCertificateDialog::CreateCertificateDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::CreateCertificateDialog) +{ + ui->setupUi(this); + + ui->fileNameEdit->setReadOnly(true); + ui->fileNameEdit->setText(CertificateManager::generateCertificateFileName()); + + ui->keyLengthCombo->addItem(tr("1024 bits"), 1024); + ui->keyLengthCombo->addItem(tr("2048 bits"), 2048); + ui->keyLengthCombo->addItem(tr("4096 bits"), 4096); + ui->keyLengthCombo->setCurrentIndex(ui->keyLengthCombo->findData(2048)); + + QList locales = QLocale::matchingLocales(QLocale::AnyLanguage, QLocale::AnyScript, QLocale::AnyCountry); + std::sort(locales.begin(), locales.end(), [](const QLocale& left, const QLocale& right) { return QString::compare(left.nativeCountryName(), right.nativeCountryName(), Qt::CaseInsensitive) < 0; }); + + int currentIndex = 0; + QLocale currentLocale = QLocale::system(); + + for (const QLocale& locale : locales) + { + if (locale.country() == QLocale::AnyCountry) + { + continue; + } + + if (locale.nativeCountryName().isEmpty()) + { + continue; + } + + QString localeName = locale.name(); + QString countryCode = localeName.split(QChar('_')).back(); + QString text = QString("%1 | %2").arg(countryCode, locale.nativeCountryName()); + + if (ui->countryCombo->findText(text) >= 0) + { + continue; + } + + if (locale.bcp47Name() == currentLocale.bcp47Name()) + { + currentIndex = ui->countryCombo->count(); + } + + ui->countryCombo->addItem(text, countryCode); + } + + ui->countryCombo->setCurrentIndex(currentIndex); + ui->countryCombo->setMaxVisibleItems(25); + + QDate minDate = QDate::currentDate(); + ui->validTillEdit->setMinimumDate(minDate); + + QDate selectedDate = minDate; + selectedDate = selectedDate.addYears(5, ui->validTillEdit->calendar()); + ui->validTillEdit->setSelectedDate(selectedDate); +} + +CreateCertificateDialog::~CreateCertificateDialog() +{ + delete ui; +} + +void CreateCertificateDialog::accept() +{ + if (validate()) + { + bool ok = false; + QString password1 = QInputDialog::getText(this, tr("Certificate Protection"), tr("Enter password to protect your certificate."), QLineEdit::Password, QString(), &ok); + + if (!ok) + { + return; + } + + QString password2 = QInputDialog::getText(this, tr("Certificate Protection"), tr("Enter password again to verify password text."), QLineEdit::Password, QString(), &ok); + + if (password1 != password2) + { + QMessageBox::critical(this, tr("Error"), tr("Reentered password is not equal to the first one!")); + return; + } + + QDate date = ui->validTillEdit->selectedDate(); + QDate currentDate = QDate::currentDate(); + int days = currentDate.daysTo(date); + + // Fill certificate info + m_newCertificateInfo.fileName = ui->fileNameEdit->text(); + m_newCertificateInfo.privateKeyPasword = password1; + m_newCertificateInfo.certCountryCode = ui->countryCombo->currentData().toString(); + m_newCertificateInfo.certOrganization = ui->organizationEdit->text(); + m_newCertificateInfo.certOrganizationUnit = ui->organizationUnitEdit->text(); + m_newCertificateInfo.certCommonName = ui->commonNameEdit->text(); + m_newCertificateInfo.certEmail = ui->emailEdit->text(); + m_newCertificateInfo.rsaKeyLength = ui->keyLengthCombo->currentData().toInt(); + m_newCertificateInfo.validityInSeconds = days * 24 * 3600; + + BaseClass::accept(); + } +} + +bool CreateCertificateDialog::validate() +{ + // validate empty text fields + if (ui->commonNameEdit->text().isEmpty()) + { + QMessageBox::critical(this, tr("Error"), tr("Please enter a name!")); + ui->commonNameEdit->setFocus(); + return false; + } + + if (ui->organizationEdit->text().isEmpty()) + { + QMessageBox::critical(this, tr("Error"), tr("Please enter an organization name!")); + ui->organizationEdit->setFocus(); + return false; + } + + if (ui->emailEdit->text().isEmpty()) + { + QMessageBox::critical(this, tr("Error"), tr("Please enter an email address!")); + ui->emailEdit->setFocus(); + return false; + } + + return true; +} + +} // namespace plugin diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/createcertificatedialog.h b/Pdf4QtViewerPlugins/SignaturePlugin/createcertificatedialog.h new file mode 100644 index 0000000..173323f --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/createcertificatedialog.h @@ -0,0 +1,59 @@ +// Copyright (C) 2022 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef CREATECERTIFICATEDIALOG_H +#define CREATECERTIFICATEDIALOG_H + +#include "certificatemanager.h" + +#include + +namespace Ui +{ +class CreateCertificateDialog; +} + +namespace pdfplugin +{ + +class CreateCertificateDialog : public QDialog +{ + Q_OBJECT + +private: + using BaseClass = QDialog; + +public: + explicit CreateCertificateDialog(QWidget* parent); + virtual ~CreateCertificateDialog() override; + + const CertificateManager::NewCertificateInfo& getNewCertificateInfo() const { return m_newCertificateInfo; } + +public slots: + virtual void accept() override; + +private: + bool validate(); + + CertificateManager::NewCertificateInfo m_newCertificateInfo; + + Ui::CreateCertificateDialog* ui; +}; + +} // namespace plugin + +#endif // CREATECERTIFICATEDIALOG_H diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/createcertificatedialog.ui b/Pdf4QtViewerPlugins/SignaturePlugin/createcertificatedialog.ui new file mode 100644 index 0000000..6ec52d9 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/createcertificatedialog.ui @@ -0,0 +1,182 @@ + + + CreateCertificateDialog + + + + 0 + 0 + 514 + 488 + + + + Create Certificate + + + + + + Create Self Signed Certificate + + + + + + Name + + + + + + + true + + + + + + + Organization + + + + + + + Organization Unit + + + + + + + Email + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + true + + + + + + + true + + + + + + + true + + + + + + + Country + + + + + + + Key length + + + + + + + Valid till + + + + + + + + + + + + + + + + + + + Certificate file + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + CreateCertificateDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + CreateCertificateDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/icons.qrc b/Pdf4QtViewerPlugins/SignaturePlugin/icons.qrc new file mode 100644 index 0000000..c53a915 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/icons.qrc @@ -0,0 +1,22 @@ + + + accept-mark.svg + reject-mark.svg + activate.svg + certificates.svg + clear.svg + create-dot.svg + create-freehand-curve.svg + create-horizontal-line.svg + create-line.svg + create-no-mark.svg + create-rectangle.svg + create-rounded-rectangle.svg + create-svg-image.svg + create-text.svg + create-vertical-line.svg + create-yes-mark.svg + sign-digitally.svg + sign-electronically.svg + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/reject-mark.svg b/Pdf4QtViewerPlugins/SignaturePlugin/reject-mark.svg new file mode 100644 index 0000000..017bbf4 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/reject-mark.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/sign-digitally.svg b/Pdf4QtViewerPlugins/SignaturePlugin/sign-digitally.svg new file mode 100644 index 0000000..5fdaee5 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/sign-digitally.svg @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/sign-electronically.svg b/Pdf4QtViewerPlugins/SignaturePlugin/sign-electronically.svg new file mode 100644 index 0000000..e1286f9 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/sign-electronically.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp new file mode 100644 index 0000000..16f1234 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.cpp @@ -0,0 +1,666 @@ +// Copyright (C) 2022 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#include "signatureplugin.h" +#include "pdfdrawwidget.h" +#include "pdfutils.h" +#include "pdfpagecontenteditorwidget.h" +#include "pdfpagecontenteditorstylesettings.h" +#include "pdfdocumentbuilder.h" +#include "certificatemanagerdialog.h" +#include "signdialog.h" +#include "pdfdocumentwriter.h" + +#include +#include +#include +#include +#include + +namespace pdfplugin +{ + +SignaturePlugin::SignaturePlugin() : + pdf::PDFPlugin(nullptr), + m_actions({ }), + m_tools({ }), + m_editorWidget(nullptr), + m_scene(nullptr), + m_sceneSelectionChangeEnabled(true) +{ + +} + +void SignaturePlugin::setWidget(pdf::PDFWidget* widget) +{ + Q_ASSERT(!m_widget); + + BaseClass::setWidget(widget); + + QAction* activateAction = new QAction(QIcon(":/pdfplugins/signatureplugin/activate.svg"), tr("Activate signature creator"), this); + QAction* createTextAction = new QAction(QIcon(":/pdfplugins/signatureplugin/create-text.svg"), tr("Create Text Label"), this); + QAction* createFreehandCurveAction = new QAction(QIcon(":/pdfplugins/signatureplugin/create-freehand-curve.svg"), tr("Create Freehand Curve"), this); + QAction* createAcceptMarkAction = new QAction(QIcon(":/pdfplugins/signatureplugin/create-yes-mark.svg"), tr("Create Accept Mark"), this); + QAction* createRejectMarkAction = new QAction(QIcon(":/pdfplugins/signatureplugin/create-no-mark.svg"), tr("Create Reject Mark"), this); + QAction* createRectangleAction = new QAction(QIcon(":/pdfplugins/signatureplugin/create-rectangle.svg"), tr("Create Rectangle"), this); + QAction* createRoundedRectangleAction = new QAction(QIcon(":/pdfplugins/signatureplugin/create-rounded-rectangle.svg"), tr("Create Rounded Rectangle"), this); + QAction* createHorizontalLineAction = new QAction(QIcon(":/pdfplugins/signatureplugin/create-horizontal-line.svg"), tr("Create Horizontal Line"), this); + QAction* createVerticalLineAction = new QAction(QIcon(":/pdfplugins/signatureplugin/create-vertical-line.svg"), tr("Create Vertical Line"), this); + QAction* createLineAction = new QAction(QIcon(":/pdfplugins/signatureplugin/create-line.svg"), tr("Create Line"), this); + QAction* createDotAction = new QAction(QIcon(":/pdfplugins/signatureplugin/create-dot.svg"), tr("Create Dot"), this); + QAction* createSvgImageAction = new QAction(QIcon(":/pdfplugins/signatureplugin/create-svg-image.svg"), tr("Create SVG Image"), this); + QAction* clearAction = new QAction(QIcon(":/pdfplugins/signatureplugin/clear.svg"), tr("Clear All Graphics"), this); + QAction* signElectronicallyAction = new QAction(QIcon(":/pdfplugins/signatureplugin/sign-electronically.svg"), tr("Sign Electronically"), this); + QAction* signDigitallyAction = new QAction(QIcon(":/pdfplugins/signatureplugin/sign-digitally.svg"), tr("Sign Digitally With Certificate"), this); + QAction* certificatesAction = new QAction(QIcon(":/pdfplugins/signatureplugin/certificates.svg"), tr("Certificates Manager"), this); + + activateAction->setObjectName("signaturetool_activateAction"); + createTextAction->setObjectName("signaturetool_createTextAction"); + createFreehandCurveAction->setObjectName("signaturetool_createFreehandCurveAction"); + createAcceptMarkAction->setObjectName("signaturetool_createAcceptMarkAction"); + createRejectMarkAction->setObjectName("signaturetool_createRejectMarkAction"); + createRectangleAction->setObjectName("signaturetool_createRectangleAction"); + createRoundedRectangleAction->setObjectName("signaturetool_createRoundedRectangleAction"); + createHorizontalLineAction->setObjectName("signaturetool_createHorizontalLineAction"); + createVerticalLineAction->setObjectName("signaturetool_createVerticalLineAction"); + createLineAction->setObjectName("signaturetool_createLineAction"); + createDotAction->setObjectName("signaturetool_createDotAction"); + createSvgImageAction->setObjectName("signaturetool_createSvgImageAction"); + clearAction->setObjectName("signaturetool_clearAction"); + signElectronicallyAction->setObjectName("signaturetool_signElectronicallyAction"); + signDigitallyAction->setObjectName("signaturetool_signDigitallyAction"); + certificatesAction->setObjectName("signaturetool_certificatesAction"); + + activateAction->setCheckable(true); + createTextAction->setCheckable(true); + createFreehandCurveAction->setCheckable(true); + createAcceptMarkAction->setCheckable(true); + createRejectMarkAction->setCheckable(true); + createRectangleAction->setCheckable(true); + createRoundedRectangleAction->setCheckable(true); + createHorizontalLineAction->setCheckable(true); + createVerticalLineAction->setCheckable(true); + createLineAction->setCheckable(true); + createDotAction->setCheckable(true); + createSvgImageAction->setCheckable(true); + + m_actions[Activate] = activateAction; + m_actions[Text] = createTextAction; + m_actions[FreehandCurve] = createFreehandCurveAction; + m_actions[AcceptMark] = createAcceptMarkAction; + m_actions[RejectMark] = createRejectMarkAction; + m_actions[Rectangle] = createRectangleAction; + m_actions[RoundedRectangle] = createRoundedRectangleAction; + m_actions[HorizontalLine] = createHorizontalLineAction; + m_actions[VerticalLine] = createVerticalLineAction; + m_actions[Line] = createLineAction; + m_actions[Dot] = createDotAction; + m_actions[SvgImage] = createSvgImageAction; + m_actions[Clear] = clearAction; + m_actions[SignElectronically] = signElectronicallyAction; + m_actions[SignDigitally] = signDigitallyAction; + m_actions[Certificates] = certificatesAction; + + QFile acceptMarkFile(":/pdfplugins/signatureplugin/accept-mark.svg"); + QByteArray acceptMarkContent; + if (acceptMarkFile.open(QFile::ReadOnly)) + { + acceptMarkContent = acceptMarkFile.readAll(); + acceptMarkFile.close(); + } + + QFile rejectMarkFile(":/pdfplugins/signatureplugin/reject-mark.svg"); + QByteArray rejectMarkContent; + if (rejectMarkFile.open(QFile::ReadOnly)) + { + rejectMarkContent = rejectMarkFile.readAll(); + rejectMarkFile.close(); + } + + m_tools[TextTool] = new pdf::PDFCreatePCElementTextTool(widget->getDrawWidgetProxy(), &m_scene, createTextAction, this); + m_tools[FreehandCurveTool] = new pdf::PDFCreatePCElementFreehandCurveTool(widget->getDrawWidgetProxy(), &m_scene, createFreehandCurveAction, this); + m_tools[AcceptMarkTool] = new pdf::PDFCreatePCElementImageTool(widget->getDrawWidgetProxy(), &m_scene, createAcceptMarkAction, acceptMarkContent, false, this); + m_tools[RejectMarkTool] = new pdf::PDFCreatePCElementImageTool(widget->getDrawWidgetProxy(), &m_scene, createRejectMarkAction, rejectMarkContent, false, this); + m_tools[RectangleTool] = new pdf::PDFCreatePCElementRectangleTool(widget->getDrawWidgetProxy(), &m_scene, createRectangleAction, false, this); + m_tools[RoundedRectangleTool] = new pdf::PDFCreatePCElementRectangleTool(widget->getDrawWidgetProxy(), &m_scene, createRoundedRectangleAction, true, this); + m_tools[HorizontalLineTool] = new pdf::PDFCreatePCElementLineTool(widget->getDrawWidgetProxy(), &m_scene, createHorizontalLineAction, true, false, this); + m_tools[VerticalLineTool] = new pdf::PDFCreatePCElementLineTool(widget->getDrawWidgetProxy(), &m_scene, createVerticalLineAction, false, true, this); + m_tools[LineTool] = new pdf::PDFCreatePCElementLineTool(widget->getDrawWidgetProxy(), &m_scene, createLineAction, false, false, this); + m_tools[DotTool] = new pdf::PDFCreatePCElementDotTool(widget->getDrawWidgetProxy(), &m_scene, createDotAction, this); + m_tools[ImageTool] = new pdf::PDFCreatePCElementImageTool(widget->getDrawWidgetProxy(), &m_scene, createSvgImageAction, QByteArray(), true, this); + + pdf::PDFToolManager* toolManager = widget->getToolManager(); + for (pdf::PDFWidgetTool* tool : m_tools) + { + toolManager->addTool(tool); + connect(tool, &pdf::PDFWidgetTool::toolActivityChanged, this, &SignaturePlugin::onToolActivityChanged); + } + + m_widget->addInputInterface(&m_scene); + m_widget->getDrawWidgetProxy()->registerDrawInterface(&m_scene); + m_scene.setWidget(m_widget); + connect(&m_scene, &pdf::PDFPageContentScene::sceneChanged, this, &SignaturePlugin::onSceneChanged); + connect(&m_scene, &pdf::PDFPageContentScene::selectionChanged, this, &SignaturePlugin::onSceneSelectionChanged); + connect(&m_scene, &pdf::PDFPageContentScene::editElementRequest, this, &SignaturePlugin::onSceneEditElement); + connect(clearAction, &QAction::triggered, &m_scene, &pdf::PDFPageContentScene::clear); + connect(activateAction, &QAction::triggered, this, &SignaturePlugin::setActive); + connect(signElectronicallyAction, &QAction::triggered, this, &SignaturePlugin::onSignElectronically); + connect(signDigitallyAction, &QAction::triggered, this, &SignaturePlugin::onSignDigitally); + connect(certificatesAction, &QAction::triggered, this, &SignaturePlugin::onOpenCertificatesManager); + + updateActions(); +} + +void SignaturePlugin::setDocument(const pdf::PDFModifiedDocument& document) +{ + BaseClass::setDocument(document); + + if (document.hasReset()) + { + setActive(false); + updateActions(); + } +} + +std::vector SignaturePlugin::getActions() const +{ + std::vector result; + + result.push_back(m_actions[Activate]); + result.push_back(m_actions[SignElectronically]); + result.push_back(m_actions[SignDigitally]); + result.push_back(m_actions[Certificates]); + + return result; +} + +void SignaturePlugin::onSceneChanged(bool graphicsOnly) +{ + if (!graphicsOnly) + { + updateActions(); + } + + if (m_editorWidget) + { + m_editorWidget->updateItemsInListWidget(); + } + + updateGraphics(); +} + +void SignaturePlugin::onSceneSelectionChanged() +{ + if (m_editorWidget && m_sceneSelectionChangeEnabled) + { + m_editorWidget->setSelection(m_scene.getSelectedElementIds()); + } +} + +void SignaturePlugin::onWidgetSelectionChanged() +{ + Q_ASSERT(m_editorWidget); + + pdf::PDFTemporaryValueChange guard(&m_sceneSelectionChangeEnabled, false); + m_scene.setSelectedElementIds(m_editorWidget->getSelection()); +} + +pdf::PDFWidgetTool* SignaturePlugin::getActiveTool() +{ + for (pdf::PDFWidgetTool* currentTool : m_tools) + { + if (currentTool->isActive()) + { + return currentTool; + } + } + + return nullptr; +} + +void SignaturePlugin::onToolActivityChanged() +{ + if (m_editorWidget) + { + pdf::PDFWidgetTool* activeTool = getActiveTool(); + + const pdf::PDFPageContentElement* element = nullptr; + pdf::PDFCreatePCElementTool* tool = qobject_cast(activeTool); + if (tool) + { + element = tool->getElement(); + } + + m_editorWidget->loadStyleFromElement(element); + } +} + +void SignaturePlugin::onSceneEditElement(const std::set& elements) +{ + if (elements.empty()) + { + return; + } + + pdf::PDFPageContentElement* element = nullptr; + for (pdf::PDFInteger id : elements) + { + element = m_scene.getElementById(id); + if (element) + { + break; + } + } + + if (!element) + { + return; + } + + if (pdf::PDFPageContentEditorStyleSettings::showEditElementStyleDialog(m_dataExchangeInterface->getMainWindow(), element)) + { + updateGraphics(); + } +} + +void SignaturePlugin::onSignElectronically() +{ + Q_ASSERT(m_document); + Q_ASSERT(!m_scene.isEmpty()); + + if (QMessageBox::question(m_dataExchangeInterface->getMainWindow(), tr("Confirm Signature"), tr("Document will be signed electronically. Do you want to continue?"), QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) + { + pdf::PDFDocumentModifier modifier(m_document); + + std::set pageIndices = m_scene.getPageIndices(); + for (pdf::PDFInteger pageIndex : pageIndices) + { + const pdf::PDFPage* page = m_document->getCatalog()->getPage(pageIndex); + pdf::PDFPageContentStreamBuilder pageContentStreamBuilder(modifier.getBuilder(), + pdf::PDFContentStreamBuilder::CoordinateSystem::PDF, + pdf::PDFPageContentStreamBuilder::Mode::PlaceAfter); + QPainter* painter = pageContentStreamBuilder.begin(page->getPageReference()); + QList errors; + pdf::PDFTextLayoutGetter nullGetter(nullptr, pageIndex); + m_scene.drawElements(painter, pageIndex, nullGetter, QMatrix(), nullptr, errors); + pageContentStreamBuilder.end(painter); + modifier.markPageContentsChanged(); + } + m_scene.clear(); + + if (modifier.finalize()) + { + emit m_widget->getToolManager()->documentModified(pdf::PDFModifiedDocument(modifier.getDocument(), nullptr, modifier.getFlags())); + } + } +} + +void SignaturePlugin::onSignDigitally() +{ + // Jakub Melka: do we have certificates? If not, + // open certificate dialog, so the user can create + // a new one. + if (CertificateManager::getCertificates().isEmpty()) + { + onOpenCertificatesManager(); + + if (CertificateManager::getCertificates().isEmpty()) + { + return; + } + } + + SignDialog dialog(m_dataExchangeInterface->getMainWindow(), m_scene.isEmpty()); + if (dialog.exec() == SignDialog::Accepted) + { + QByteArray data = "SampleDataToBeSigned" + QByteArray::number(QDateTime::currentMSecsSinceEpoch()); + QByteArray signature; + if (!SignatureFactory::sign(dialog.getCertificatePath(), dialog.getPassword(), data, signature)) + { + QMessageBox::critical(m_widget, tr("Error"), tr("Failed to create digital signature.")); + return; + } + + QString signatureName = QString("pdf4qt_signature_%1").arg(QString::number(QDateTime::currentMSecsSinceEpoch())); + + pdf::PDFInteger offsetMark = 123456789123; + constexpr const char* offsetMarkString = "123456789123"; + const auto offsetMarkStringLength = std::strlen(offsetMarkString); + + pdf::PDFDocumentBuilder builder(m_document); + pdf::PDFObjectReference signatureDictionary = builder.createSignatureDictionary("Adobe.PPKLite", "adbe.pkcs7.detached", signature, QDateTime::currentDateTime(), offsetMark); + pdf::PDFObjectReference formField = builder.createFormFieldSignature(signatureName, { }, signatureDictionary); + builder.createAcroForm({ formField }); + + const pdf::PDFCatalog* catalog = m_document->getCatalog(); + if (dialog.getSignMethod() == SignDialog::SignDigitallyInvisible) + { + if (catalog->getPageCount() > 0) + { + const pdf::PDFObjectReference pageReference = catalog->getPage(0)->getPageReference(); + builder.createInvisibleFormFieldWidget(formField, pageReference); + } + } + else if (dialog.getSignMethod() == SignDialog::SignDigitally) + { + Q_ASSERT(!m_scene.isEmpty()); + const pdf::PDFInteger pageIndex = *m_scene.getPageIndices().begin(); + const pdf::PDFPage* page = catalog->getPage(pageIndex); + + pdf::PDFContentStreamBuilder contentBuilder(page->getMediaBox().size(), pdf::PDFContentStreamBuilder::CoordinateSystem::PDF); + QPainter* painter = contentBuilder.begin(); + QList errors; + pdf::PDFTextLayoutGetter nullGetter(nullptr, pageIndex); + m_scene.drawPage(painter, pageIndex, nullptr, nullGetter, QMatrix(), errors); + pdf::PDFContentStreamBuilder::ContentStream contentStream = contentBuilder.end(painter); + + QRectF boundingRect = m_scene.getBoundingBox(pageIndex); + std::vector copiedObjects = builder.copyFrom({ contentStream.resources, contentStream.contents }, contentStream.document.getStorage(), true); + Q_ASSERT(copiedObjects.size() == 2); + + pdf::PDFObjectReference resourcesReference = copiedObjects[0].getReference(); + pdf::PDFObjectReference formReference = copiedObjects[1].getReference(); + + // Create form object + pdf::PDFObjectFactory formFactory; + + formFactory.beginDictionary(); + + formFactory.beginDictionaryItem("Type"); + formFactory << pdf::WrapName("XObject"); + formFactory.endDictionaryItem(); + + formFactory.beginDictionaryItem("Subtype"); + formFactory << pdf::WrapName("Form"); + formFactory.endDictionaryItem(); + + formFactory.beginDictionaryItem("BBox"); + formFactory << boundingRect; + formFactory.endDictionaryItem(); + + formFactory.beginDictionaryItem("Resources"); + formFactory << resourcesReference; + formFactory.endDictionaryItem(); + + formFactory.endDictionary(); + + builder.mergeTo(formReference, formFactory.takeObject()); + + builder.createFormFieldWidget(formField, page->getPageReference(), formReference, boundingRect); + } + + QString reasonText = dialog.getReasonText(); + if (!reasonText.isEmpty()) + { + builder.setSignatureReason(signatureDictionary, reasonText); + } + + QString contactInfoText = dialog.getContactInfoText(); + if (!contactInfoText.isEmpty()) + { + builder.setSignatureContactInfo(signatureDictionary, contactInfoText); + } + + pdf::PDFDocument signedDocument = builder.build(); + + // 1) Save the document with incorrect signature + QBuffer buffer; + pdf::PDFDocumentWriter writer(m_widget->getDrawWidgetProxy()->getProgress()); + buffer.open(QBuffer::ReadWrite); + writer.write(&buffer, &signedDocument); + + const int indexOfSignature = buffer.data().indexOf(signature.toHex()); + if (indexOfSignature == -1) + { + QMessageBox::critical(m_widget, tr("Error"), tr("Failed to create digital signature.")); + buffer.close(); + return; + } + + // 2) Write ranges to be checked + const pdf::PDFInteger i1 = 0; + const pdf::PDFInteger i2 = indexOfSignature - 1; + const pdf::PDFInteger i3 = i2 + signature.size() * 2 + 2; + const pdf::PDFInteger i4 = buffer.data().size() - i3; + + auto writeInt = [&](pdf::PDFInteger offset) + { + QString offsetString = QString::number(offset); + offsetString = offsetString.leftJustified(static_cast(offsetMarkStringLength), ' ', true); + const auto index = buffer.data().lastIndexOf(QString(offsetMarkString), indexOfSignature); + buffer.seek(index); + buffer.write(offsetString.toLocal8Bit()); + }; + + writeInt(i4); + writeInt(i3); + writeInt(i2); + writeInt(i1); + + // 3) Sign the data + QByteArray dataToBeSigned; + buffer.seek(i1); + dataToBeSigned.append(buffer.read(i2)); + buffer.seek(i3); + dataToBeSigned.append(buffer.read(i4)); + + if (!SignatureFactory::sign(dialog.getCertificatePath(), dialog.getPassword(), dataToBeSigned, signature)) + { + QMessageBox::critical(m_widget, tr("Error"), tr("Failed to create digital signature.")); + buffer.close(); + return; + } + + buffer.seek(i2 + 1); + buffer.write(signature.toHex()); + + buffer.close(); + + QString fileName = QFileDialog::getSaveFileName(m_dataExchangeInterface->getMainWindow(), tr("Save Signed Document"), getSignedFileName(), tr("Portable Document (*.pdf);;All files (*.*)")); + if (!fileName.isEmpty()) + { + QFile signedFile(fileName); + if (signedFile.open(QFile::WriteOnly | QFile::Truncate)) + { + signedFile.write(buffer.data()); + signedFile.close(); + } + } + } +} + +QString SignaturePlugin::getSignedFileName() const +{ + QFileInfo fileInfo(m_dataExchangeInterface->getOriginalFileName()); + + return fileInfo.path() + "/" + fileInfo.baseName() + "_SIGNED.pdf"; +} + +void SignaturePlugin::onOpenCertificatesManager() +{ + CertificateManagerDialog dialog(m_dataExchangeInterface->getMainWindow()); + dialog.exec(); +} + +void SignaturePlugin::onPenChanged(const QPen& pen) +{ + if (pdf::PDFCreatePCElementTool* activeTool = qobject_cast(getActiveTool())) + { + activeTool->setPen(pen); + } +} + +void SignaturePlugin::onBrushChanged(const QBrush& brush) +{ + if (pdf::PDFCreatePCElementTool* activeTool = qobject_cast(getActiveTool())) + { + activeTool->setBrush(brush); + } +} + +void SignaturePlugin::onFontChanged(const QFont& font) +{ + if (pdf::PDFCreatePCElementTool* activeTool = qobject_cast(getActiveTool())) + { + activeTool->setFont(font); + } +} + +void SignaturePlugin::onAlignmentChanged(Qt::Alignment alignment) +{ + if (pdf::PDFCreatePCElementTool* activeTool = qobject_cast(getActiveTool())) + { + activeTool->setAlignment(alignment); + } +} + +void SignaturePlugin::onTextAngleChanged(pdf::PDFReal angle) +{ + if (pdf::PDFCreatePCElementTool* activeTool = qobject_cast(getActiveTool())) + { + activeTool->setTextAngle(angle); + } +} + +void SignaturePlugin::setActive(bool active) +{ + if (m_scene.isActive() != active) + { + // Abort active tool, if we are deactivating the plugin + if (!active) + { + if (pdf::PDFWidgetTool* tool = m_widget->getToolManager()->getActiveTool()) + { + auto it = std::find(m_tools.cbegin(), m_tools.cend(), tool); + if (it != m_tools.cend()) + { + m_widget->getToolManager()->setActiveTool(nullptr); + } + } + } + + m_scene.setActive(active); + if (!active) + { + m_scene.clear(); + } + else + { + updateDockWidget(); + } + + m_actions[Activate]->setChecked(active); + updateActions(); + } +} + +void SignaturePlugin::updateActions() +{ + m_actions[Activate]->setEnabled(m_document); + + if (!m_scene.isActive() || !m_document) + { + // Inactive scene - disable all except activate action and certificates + for (QAction* action : m_actions) + { + if (action == m_actions[Activate] || + action == m_actions[Certificates]) + { + continue; + } + + action->setEnabled(false); + } + + return; + } + + const bool isSceneNonempty = !m_scene.isEmpty(); + + // Tool actions + for (auto actionId : { Text, FreehandCurve, AcceptMark, RejectMark, + Rectangle, RoundedRectangle, HorizontalLine, + VerticalLine, Line, Dot, SvgImage }) + { + m_actions[actionId]->setEnabled(true); + } + + // Clear action + QAction* clearAction = m_actions[Clear]; + clearAction->setEnabled(isSceneNonempty); + + // Sign actions + QAction* signElectronicallyAction = m_actions[SignElectronically]; + signElectronicallyAction->setEnabled(isSceneNonempty); + QAction* signDigitallyAction = m_actions[SignDigitally]; + signDigitallyAction->setEnabled(m_document); +} + +void SignaturePlugin::updateGraphics() +{ + if (m_widget) + { + m_widget->getDrawWidget()->getWidget()->update(); + } +} + +void SignaturePlugin::updateDockWidget() +{ + if (m_editorWidget) + { + return; + } + + m_editorWidget = new pdf::PDFPageContentEditorWidget(m_dataExchangeInterface->getMainWindow()); + m_editorWidget->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); + m_dataExchangeInterface->getMainWindow()->addDockWidget(Qt::RightDockWidgetArea, m_editorWidget, Qt::Vertical); + m_editorWidget->setFloating(false); + m_editorWidget->setWindowTitle(tr("Signature Toolbox")); + m_editorWidget->setScene(&m_scene); + connect(m_editorWidget, &pdf::PDFPageContentEditorWidget::operationTriggered, &m_scene, &pdf::PDFPageContentScene::performOperation); + connect(m_editorWidget, &pdf::PDFPageContentEditorWidget::itemSelectionChangedByUser, this, &SignaturePlugin::onWidgetSelectionChanged); + + m_editorWidget->getToolButtonForOperation(static_cast(pdf::PDFPageContentElementManipulator::Operation::AlignTop))->setIcon(QIcon(":/resources/pce-align-top.svg")); + m_editorWidget->getToolButtonForOperation(static_cast(pdf::PDFPageContentElementManipulator::Operation::AlignCenterVertically))->setIcon(QIcon(":/resources/pce-align-v-center.svg")); + m_editorWidget->getToolButtonForOperation(static_cast(pdf::PDFPageContentElementManipulator::Operation::AlignBottom))->setIcon(QIcon(":/resources/pce-align-bottom.svg")); + m_editorWidget->getToolButtonForOperation(static_cast(pdf::PDFPageContentElementManipulator::Operation::AlignLeft))->setIcon(QIcon(":/resources/pce-align-left.svg")); + m_editorWidget->getToolButtonForOperation(static_cast(pdf::PDFPageContentElementManipulator::Operation::AlignCenterHorizontally))->setIcon(QIcon(":/resources/pce-align-h-center.svg")); + m_editorWidget->getToolButtonForOperation(static_cast(pdf::PDFPageContentElementManipulator::Operation::AlignRight))->setIcon(QIcon(":/resources/pce-align-right.svg")); + m_editorWidget->getToolButtonForOperation(static_cast(pdf::PDFPageContentElementManipulator::Operation::SetSameHeight))->setIcon(QIcon(":/resources/pce-same-height.svg")); + m_editorWidget->getToolButtonForOperation(static_cast(pdf::PDFPageContentElementManipulator::Operation::SetSameWidth))->setIcon(QIcon(":/resources/pce-same-width.svg")); + m_editorWidget->getToolButtonForOperation(static_cast(pdf::PDFPageContentElementManipulator::Operation::SetSameSize))->setIcon(QIcon(":/resources/pce-same-size.svg")); + m_editorWidget->getToolButtonForOperation(static_cast(pdf::PDFPageContentElementManipulator::Operation::CenterHorizontally))->setIcon(QIcon(":/resources/pce-center-h.svg")); + m_editorWidget->getToolButtonForOperation(static_cast(pdf::PDFPageContentElementManipulator::Operation::CenterVertically))->setIcon(QIcon(":/resources/pce-center-v.svg")); + m_editorWidget->getToolButtonForOperation(static_cast(pdf::PDFPageContentElementManipulator::Operation::CenterHorAndVert))->setIcon(QIcon(":/resources/pce-center-vh.svg")); + m_editorWidget->getToolButtonForOperation(static_cast(pdf::PDFPageContentElementManipulator::Operation::LayoutVertically))->setIcon(QIcon(":/resources/pce-layout-v.svg")); + m_editorWidget->getToolButtonForOperation(static_cast(pdf::PDFPageContentElementManipulator::Operation::LayoutHorizontally))->setIcon(QIcon(":/resources/pce-layout-h.svg")); + m_editorWidget->getToolButtonForOperation(static_cast(pdf::PDFPageContentElementManipulator::Operation::LayoutForm))->setIcon(QIcon(":/resources/pce-layout-form.svg")); + m_editorWidget->getToolButtonForOperation(static_cast(pdf::PDFPageContentElementManipulator::Operation::LayoutGrid))->setIcon(QIcon(":/resources/pce-layout-grid.svg")); + + for (QAction* action : m_actions) + { + m_editorWidget->addAction(action); + } + + connect(m_editorWidget, &pdf::PDFPageContentEditorWidget::penChanged, this, &SignaturePlugin::onPenChanged); + connect(m_editorWidget, &pdf::PDFPageContentEditorWidget::brushChanged, this, &SignaturePlugin::onBrushChanged); + connect(m_editorWidget, &pdf::PDFPageContentEditorWidget::fontChanged, this, &SignaturePlugin::onFontChanged); + connect(m_editorWidget, &pdf::PDFPageContentEditorWidget::alignmentChanged, this, &SignaturePlugin::onAlignmentChanged); + connect(m_editorWidget, &pdf::PDFPageContentEditorWidget::textAngleChanged, this, &SignaturePlugin::onTextAngleChanged); +} + +} diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h new file mode 100644 index 0000000..ea1cd86 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signatureplugin.h @@ -0,0 +1,128 @@ +// Copyright (C) 2022 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef SIGNATURESPLUGIN_H +#define SIGNATURESPLUGIN_H + +#include "pdfplugin.h" +#include "pdfpagecontentelements.h" +#include "pdfpagecontenteditortools.h" + +#include + +namespace pdf +{ +class PDFPageContentEditorWidget; +} + +namespace pdfplugin +{ + +class SignaturePlugin : public pdf::PDFPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "PDF4QT.SignaturePlugin" FILE "SignaturePlugin.json") + +private: + using BaseClass = pdf::PDFPlugin; + +public: + SignaturePlugin(); + + virtual void setWidget(pdf::PDFWidget* widget) override; + virtual void setDocument(const pdf::PDFModifiedDocument& document) override; + virtual std::vector getActions() const override; + +private: + void onSceneChanged(bool graphicsOnly); + void onSceneSelectionChanged(); + void onWidgetSelectionChanged(); + void onToolActivityChanged(); + void onSceneEditElement(const std::set& elements); + void onSignElectronically(); + void onSignDigitally(); + void onOpenCertificatesManager(); + + void onPenChanged(const QPen& pen); + void onBrushChanged(const QBrush& brush); + void onFontChanged(const QFont& font); + void onAlignmentChanged(Qt::Alignment alignment); + void onTextAngleChanged(pdf::PDFReal angle); + + enum Action + { + // Activate action + Activate, + + // Create graphics actions + Text, + FreehandCurve, + AcceptMark, + RejectMark, + Rectangle, + RoundedRectangle, + HorizontalLine, + VerticalLine, + Line, + Dot, + SvgImage, + Clear, + + // Sign actions + SignElectronically, + SignDigitally, + Certificates, + + LastAction + }; + + enum Tools + { + TextTool, + FreehandCurveTool, + AcceptMarkTool, + RejectMarkTool, + RectangleTool, + RoundedRectangleTool, + HorizontalLineTool, + VerticalLineTool, + LineTool, + DotTool, + ImageTool, + LastTool + }; + + void setActive(bool active); + + void updateActions(); + void updateGraphics(); + void updateDockWidget(); + + QString getSignedFileName() const; + + std::array m_actions; + std::array m_tools; + pdf::PDFPageContentEditorWidget* m_editorWidget; + + pdf::PDFPageContentScene m_scene; + bool m_sceneSelectionChangeEnabled; + pdf::PDFWidgetTool* getActiveTool(); +}; + +} // namespace pdfplugin + +#endif // SIGNATURESPLUGIN_H diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signdialog.cpp b/Pdf4QtViewerPlugins/SignaturePlugin/signdialog.cpp new file mode 100644 index 0000000..5f36158 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signdialog.cpp @@ -0,0 +1,105 @@ +// Copyright (C) 2022 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#include "signdialog.h" +#include "ui_signdialog.h" + +#include "certificatemanager.h" + +#include + +#include + +namespace pdfplugin +{ + +SignDialog::SignDialog(QWidget* parent, bool isSceneEmpty) : + QDialog(parent), + ui(new Ui::SignDialog) +{ + ui->setupUi(this); + + if (!isSceneEmpty) + { + ui->methodCombo->addItem(tr("Sign digitally"), SignDigitally); + } + + ui->methodCombo->addItem(tr("Sign digitally (invisible signature)"), SignDigitallyInvisible); + ui->methodCombo->setCurrentIndex(0); + + QFileInfoList certificates = CertificateManager::getCertificates(); + for (const QFileInfo& certificateFileInfo : certificates) + { + ui->certificateCombo->addItem(certificateFileInfo.fileName(), certificateFileInfo.absoluteFilePath()); + } +} + +SignDialog::~SignDialog() +{ + delete ui; +} + +SignDialog::SignMethod SignDialog::getSignMethod() const +{ + return static_cast(ui->methodCombo->currentData().toInt()); +} + +QString SignDialog::getCertificatePath() const +{ + return ui->certificateCombo->currentData().toString(); +} + +QString SignDialog::getPassword() const +{ + return ui->certificatePasswordEdit->text(); +} + +QString SignDialog::getReasonText() const +{ + return ui->reasonEdit->text(); +} + +QString SignDialog::getContactInfoText() const +{ + return ui->contactInfoEdit->text(); +} + +void SignDialog::accept() +{ + // Check certificate + if (!QFile::exists(getCertificatePath())) + { + QMessageBox::critical(this, tr("Error"), tr("Certificate does not exist.")); + ui->certificateCombo->setFocus(); + return; + } + + // Check we can access the certificate + if (!CertificateManager::isCertificateValid(getCertificatePath(), ui->certificatePasswordEdit->text())) + { + QMessageBox::critical(this, tr("Error"), tr("Password to open certificate is invalid.")); + ui->certificatePasswordEdit->setFocus(); + return; + } + + QDialog::accept(); +} + +} // namespace pdfplugin + + + diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signdialog.h b/Pdf4QtViewerPlugins/SignaturePlugin/signdialog.h new file mode 100644 index 0000000..6546b58 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signdialog.h @@ -0,0 +1,59 @@ +// Copyright (C) 2022 Jakub Melka +// +// This file is part of PDF4QT. +// +// PDF4QT is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// with the written consent of the copyright owner, any later version. +// +// PDF4QT is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with PDF4QT. If not, see . + +#ifndef SIGNDIALOG_H +#define SIGNDIALOG_H + +#include + +namespace Ui +{ +class SignDialog; +} + +namespace pdfplugin +{ + +class SignDialog : public QDialog +{ + Q_OBJECT + +public: + explicit SignDialog(QWidget* parent, bool isSceneEmpty); + virtual ~SignDialog() override; + + virtual void accept() override; + + enum SignMethod + { + SignDigitally, + SignDigitallyInvisible + }; + + SignMethod getSignMethod() const; + QString getCertificatePath() const; + QString getPassword() const; + QString getReasonText() const; + QString getContactInfoText() const; + +private: + Ui::SignDialog* ui; +}; + +} // namespace pdfplugin + +#endif // SIGNDIALOG_H diff --git a/Pdf4QtViewerPlugins/SignaturePlugin/signdialog.ui b/Pdf4QtViewerPlugins/SignaturePlugin/signdialog.ui new file mode 100644 index 0000000..d8c4466 --- /dev/null +++ b/Pdf4QtViewerPlugins/SignaturePlugin/signdialog.ui @@ -0,0 +1,160 @@ + + + SignDialog + + + + 0 + 0 + 501 + 332 + + + + Dialog + + + + + + Sign Method + + + + + + Method + + + + + + + + + + Certificate + + + + + + + + + + Password + + + + + + + QLineEdit::Password + + + true + + + + + + + + + + Parameters + + + + + + Reason + + + + + + + true + + + + + + + Contact Info + + + + + + + true + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + SignDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + SignDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/Pdf4QtViewerProfi/app-icon.ico b/Pdf4QtViewerProfi/app-icon.ico index dedae02..d4f4e1f 100644 Binary files a/Pdf4QtViewerProfi/app-icon.ico and b/Pdf4QtViewerProfi/app-icon.ico differ diff --git a/Pdf4QtViewerProfi/app-icon.svg b/Pdf4QtViewerProfi/app-icon.svg new file mode 100644 index 0000000..c325e3a --- /dev/null +++ b/Pdf4QtViewerProfi/app-icon.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + diff --git a/generated_code_definition.xml b/generated_code_definition.xml index 0429049..fa6ec83 100644 --- a/generated_code_definition.xml +++ b/generated_code_definition.xml @@ -103,7 +103,7 @@ Code _void - appendTo(getPageTreeRoot(), updatedTreeRoot); + appendTo(getPageTreeRoot(), updatedTreeRoot); return pageReference; @@ -112,6 +112,85 @@ return pageReference; Appends a new page after last page. _PDFObjectReference + + + + + + + + + + fields + _PDFObjectReferenceVector + Fields + + + Parameters + + _void + + + + + + + + + + + + Fields + DictionaryItemSimple + fields + + + + + NeedAppearances + DictionaryItemSimple + false + + + + + SigFlags + DictionaryItemSimple + 0 + + + + + XFA + DictionaryItemSimple + PDFObject() + + + + Dictionary + + + + CreateObject + acroForm + _PDFObjectReference + + + + + + Code + + _void + setCatalogAcroForm(acroForm); +return acroForm; + + + Structure + createAcroForm + Creates AcroForm dictionary. Erases XFA form if present. + _PDFObjectReference + @@ -2307,8 +2386,8 @@ return pageReference; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -2531,8 +2610,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -2706,8 +2785,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationReference); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationReference); return annotationReference; @@ -2716,281 +2795,6 @@ return annotationReference; Creates a new file attachment annotation. This annotation needs file specification as parameter. _PDFObjectReference - - - - - - - - - - page - _PDFObjectReference - Page to which is annotation added - - - - - boundingRectangle - _QRectF - Bounding rectangle of free text annotation. It must contain both callout line and text rectangle. - - - - - textRectangle - _QRectF - Rectangle with text, in absolute coordinates. They are then recomputed to match bounding rectangle. - - - - - title - _QString - Title - - - - - subject - _QString - Subject - - - - - contents - _QString - Contents (text displayed) - - - - - textAlignment - _TextAlignment - Text alignment. Only horizontal alignment flags are valid. - - - - - startPoint - _QPointF - Start point of callout line - - - - - endPoint - _QPointF - End point of callout line - - - - - startLineType - _AnnotationLineEnding - Line ending at the start point - - - - - endLineType - _AnnotationLineEnding - Line ending at the end point - - - Parameters - - _void - - - - - - - - - - - - Type - DictionaryItemSimple - WrapName("Annot") - - - - - Subtype - DictionaryItemSimple - WrapName("FreeText") - - - - - Rect - DictionaryItemSimple - boundingRectangle - - - - - F - DictionaryItemSimple - 4 - - - - - P - DictionaryItemSimple - page - - - - - M - DictionaryItemSimple - WrapCurrentDateTime() - - - - - CreationDate - DictionaryItemSimple - WrapCurrentDateTime() - - - - - T - DictionaryItemSimple - title - - - - - Contents - DictionaryItemSimple - contents - - - - - Subj - DictionaryItemSimple - subject - - - - - Q - DictionaryItemSimple - WrapFreeTextAlignment(textAlignment) - - - - - DA - DictionaryItemSimple - WrapString("/Arial 10 Tf") - - - - - RD - DictionaryItemSimple - getAnnotationReductionRectangle(boundingRectangle, textRectangle) - - - - - - - - - ArraySimple - startPoint;endPoint - - - CL - DictionaryItemComplex - - - - - - - - - - ArraySimple - startLineType;endLineType - - - LE - DictionaryItemComplex - - - - - Dictionary - - - - CreateObject - annotationObject - _PDFObjectReference - - - - - - - - - - - - - - - - ArraySimple - annotationObject - - - Annots - DictionaryItemComplex - - - - - Dictionary - - - - CreateObject - pageAnnots - _PDFObject - - - - - - Code - - _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); -return annotationObject; - - - Annotations - createAnnotationFreeText - Free text annotation displays text directly on a page. Text appears directly on the page, in the same way, as standard text in PDF document. Free text annotations are usually used to comment the document. Free text annotation can also have callout line, with, or without a knee. Specify start/end point parameters of this function to get callout line. - _PDFObjectReference - @@ -3263,8 +3067,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -3466,8 +3270,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -3476,6 +3280,281 @@ return annotationObject; Free text annotation displays text directly on a page. Text appears directly on the page, in the same way, as standard text in PDF document. Free text annotations are usually used to comment the document. Free text annotation can also have callout line, with, or without a knee. _PDFObjectReference + + + + + + + + + + page + _PDFObjectReference + Page to which is annotation added + + + + + boundingRectangle + _QRectF + Bounding rectangle of free text annotation. It must contain both callout line and text rectangle. + + + + + textRectangle + _QRectF + Rectangle with text, in absolute coordinates. They are then recomputed to match bounding rectangle. + + + + + title + _QString + Title + + + + + subject + _QString + Subject + + + + + contents + _QString + Contents (text displayed) + + + + + textAlignment + _TextAlignment + Text alignment. Only horizontal alignment flags are valid. + + + + + startPoint + _QPointF + Start point of callout line + + + + + endPoint + _QPointF + End point of callout line + + + + + startLineType + _AnnotationLineEnding + Line ending at the start point + + + + + endLineType + _AnnotationLineEnding + Line ending at the end point + + + Parameters + + _void + + + + + + + + + + + + Type + DictionaryItemSimple + WrapName("Annot") + + + + + Subtype + DictionaryItemSimple + WrapName("FreeText") + + + + + Rect + DictionaryItemSimple + boundingRectangle + + + + + F + DictionaryItemSimple + 4 + + + + + P + DictionaryItemSimple + page + + + + + M + DictionaryItemSimple + WrapCurrentDateTime() + + + + + CreationDate + DictionaryItemSimple + WrapCurrentDateTime() + + + + + T + DictionaryItemSimple + title + + + + + Contents + DictionaryItemSimple + contents + + + + + Subj + DictionaryItemSimple + subject + + + + + Q + DictionaryItemSimple + WrapFreeTextAlignment(textAlignment) + + + + + DA + DictionaryItemSimple + WrapString("/Arial 10 Tf") + + + + + RD + DictionaryItemSimple + getAnnotationReductionRectangle(boundingRectangle, textRectangle) + + + + + + + + + ArraySimple + startPoint;endPoint + + + CL + DictionaryItemComplex + + + + + + + + + + ArraySimple + startLineType;endLineType + + + LE + DictionaryItemComplex + + + + + Dictionary + + + + CreateObject + annotationObject + _PDFObjectReference + + + + + + + + + + + + + + + + ArraySimple + annotationObject + + + Annots + DictionaryItemComplex + + + + + Dictionary + + + + CreateObject + pageAnnots + _PDFObject + + + + + + Code + + _void + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); +return annotationObject; + + + Annotations + createAnnotationFreeText + Free text annotation displays text directly on a page. Text appears directly on the page, in the same way, as standard text in PDF document. Free text annotations are usually used to comment the document. Free text annotation can also have callout line, with, or without a knee. Specify start/end point parameters of this function to get callout line. + _PDFObjectReference + @@ -3670,8 +3749,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -3825,8 +3904,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -3965,8 +4044,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -4190,8 +4269,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -4407,8 +4486,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -4689,8 +4768,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -5041,8 +5120,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -5242,8 +5321,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationReference); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationReference); return annotationReference; @@ -5473,8 +5552,8 @@ return annotationReference; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -5733,8 +5812,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -5872,8 +5951,8 @@ return annotationObject; Code _void - mergeTo(parentAnnotation, upgradedParentAnnotation); -updateAnnotationAppearanceStreams(popupAnnotation); + mergeTo(parentAnnotation, upgradedParentAnnotation); +updateAnnotationAppearanceStreams(popupAnnotation); return popupAnnotation; @@ -6027,8 +6106,8 @@ return popupAnnotation; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -6167,8 +6246,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -6391,8 +6470,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -6546,8 +6625,148 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); +return annotationObject; + + + Annotations + createAnnotationSquiggly + Text markup annotation is used to squiggly underline text. It is a markup annotation, so it can contain window to be opened (and commented). + _PDFObjectReference + + + + + + + + + + + page + _PDFObjectReference + Page to which is annotation added + + + + + quadrilaterals + _QPolygonF + Area in which is markup displayed + + + + + color + _QColor + Color + + + Parameters + + _void + + + + + + + + + + + + Type + DictionaryItemSimple + WrapName("Annot") + + + + + Subtype + DictionaryItemSimple + WrapName("Squiggly") + + + + + P + DictionaryItemSimple + page + + + + + CreationDate + DictionaryItemSimple + WrapCurrentDateTime() + + + + + C + DictionaryItemSimple + color + + + + + QuadPoints + DictionaryItemSimple + quadrilaterals + + + + Dictionary + + + + CreateObject + annotationObject + _PDFObjectReference + + + + + + + + + + + + + + + + ArraySimple + annotationObject + + + Annots + DictionaryItemComplex + + + + + Dictionary + + + + CreateObject + pageAnnots + _PDFObject + + + + + + Code + + _void + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -6750,148 +6969,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); -return annotationObject; - - - Annotations - createAnnotationSquiggly - Text markup annotation is used to squiggly underline text. It is a markup annotation, so it can contain window to be opened (and commented). - _PDFObjectReference - - - - - - - - - - - page - _PDFObjectReference - Page to which is annotation added - - - - - quadrilaterals - _QPolygonF - Area in which is markup displayed - - - - - color - _QColor - Color - - - Parameters - - _void - - - - - - - - - - - - Type - DictionaryItemSimple - WrapName("Annot") - - - - - Subtype - DictionaryItemSimple - WrapName("Squiggly") - - - - - P - DictionaryItemSimple - page - - - - - CreationDate - DictionaryItemSimple - WrapCurrentDateTime() - - - - - C - DictionaryItemSimple - color - - - - - QuadPoints - DictionaryItemSimple - quadrilaterals - - - - Dictionary - - - - CreateObject - annotationObject - _PDFObjectReference - - - - - - - - - - - - - - - - ArraySimple - annotationObject - - - Annots - DictionaryItemComplex - - - - - Dictionary - - - - CreateObject - pageAnnots - _PDFObject - - - - - - Code - - _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -7079,8 +7158,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -7283,8 +7362,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -7438,8 +7517,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -7578,8 +7657,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -7820,9 +7899,9 @@ return annotationObject; Code _void - mergeTo(annotationObject, updateAnnotationPopup); -appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + mergeTo(annotationObject, updateAnnotationPopup); +appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -7831,6 +7910,301 @@ return annotationObject; Creates text annotation. Text annotation is "sticky note" attached to a point in the PDF document. When closed, it is displayed as icon, if opened, widget appears with attached text. Text annotations do not scale or rotate, they appear independent of zoom/rotate. So, they behave as if flags NoZoom or NoRotate to the annotations are being set. Popup annotation is automatically created for this annotation. _PDFObjectReference + + + + + + + + + + page + _PDFObjectReference + Page to which is annotation added + + + + + rectangle + _QRectF + Area in which is markup displayed + + + + + color + _QColor + Color + + + Parameters + + _void + + + + + + + + + + + + Type + DictionaryItemSimple + WrapName("Annot") + + + + + Subtype + DictionaryItemSimple + WrapName("Underline") + + + + + Rect + DictionaryItemSimple + rectangle + + + + + P + DictionaryItemSimple + page + + + + + CreationDate + DictionaryItemSimple + WrapCurrentDateTime() + + + + + C + DictionaryItemSimple + color + + + + + + + + + ArraySimple + rectangle.bottomLeft();rectangle.bottomRight();rectangle.topLeft();rectangle.topRight() + + + QuadPoints + DictionaryItemComplex + + + + + Dictionary + + + + CreateObject + annotationObject + _PDFObjectReference + + + + + + + + + + + + + + + + ArraySimple + annotationObject + + + Annots + DictionaryItemComplex + + + + + Dictionary + + + + CreateObject + pageAnnots + _PDFObject + + + + + + Code + + _void + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); +return annotationObject; + + + Annotations + createAnnotationUnderline + Text markup annotation is used to underline text. It is a markup annotation, so it can contain window to be opened (and commented). + _PDFObjectReference + + + + + + + + + + + page + _PDFObjectReference + Page to which is annotation added + + + + + quadrilaterals + _QPolygonF + Area in which is markup displayed + + + + + color + _QColor + Color + + + Parameters + + _void + + + + + + + + + + + + Type + DictionaryItemSimple + WrapName("Annot") + + + + + Subtype + DictionaryItemSimple + WrapName("Underline") + + + + + P + DictionaryItemSimple + page + + + + + CreationDate + DictionaryItemSimple + WrapCurrentDateTime() + + + + + C + DictionaryItemSimple + color + + + + + QuadPoints + DictionaryItemSimple + quadrilaterals + + + + Dictionary + + + + CreateObject + annotationObject + _PDFObjectReference + + + + + + + + + + + + + + + + ArraySimple + annotationObject + + + Annots + DictionaryItemComplex + + + + + Dictionary + + + + CreateObject + pageAnnots + _PDFObject + + + + + + Code + + _void + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); +return annotationObject; + + + Annotations + createAnnotationUnderline + Text markup annotation is used to underline text. It is a markup annotation, so it can contain window to be opened (and commented). + _PDFObjectReference + @@ -8025,303 +8399,8 @@ return annotationObject; Code _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); -return annotationObject; - - - Annotations - createAnnotationUnderline - Text markup annotation is used to underline text. It is a markup annotation, so it can contain window to be opened (and commented). - _PDFObjectReference - - - - - - - - - - - page - _PDFObjectReference - Page to which is annotation added - - - - - rectangle - _QRectF - Area in which is markup displayed - - - - - color - _QColor - Color - - - Parameters - - _void - - - - - - - - - - - - Type - DictionaryItemSimple - WrapName("Annot") - - - - - Subtype - DictionaryItemSimple - WrapName("Underline") - - - - - Rect - DictionaryItemSimple - rectangle - - - - - P - DictionaryItemSimple - page - - - - - CreationDate - DictionaryItemSimple - WrapCurrentDateTime() - - - - - C - DictionaryItemSimple - color - - - - - - - - - ArraySimple - rectangle.bottomLeft();rectangle.bottomRight();rectangle.topLeft();rectangle.topRight() - - - QuadPoints - DictionaryItemComplex - - - - - Dictionary - - - - CreateObject - annotationObject - _PDFObjectReference - - - - - - - - - - - - - - - - ArraySimple - annotationObject - - - Annots - DictionaryItemComplex - - - - - Dictionary - - - - CreateObject - pageAnnots - _PDFObject - - - - - - Code - - _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); -return annotationObject; - - - Annotations - createAnnotationUnderline - Text markup annotation is used to underline text. It is a markup annotation, so it can contain window to be opened (and commented). - _PDFObjectReference - - - - - - - - - - - page - _PDFObjectReference - Page to which is annotation added - - - - - quadrilaterals - _QPolygonF - Area in which is markup displayed - - - - - color - _QColor - Color - - - Parameters - - _void - - - - - - - - - - - - Type - DictionaryItemSimple - WrapName("Annot") - - - - - Subtype - DictionaryItemSimple - WrapName("Underline") - - - - - P - DictionaryItemSimple - page - - - - - CreationDate - DictionaryItemSimple - WrapCurrentDateTime() - - - - - C - DictionaryItemSimple - color - - - - - QuadPoints - DictionaryItemSimple - quadrilaterals - - - - Dictionary - - - - CreateObject - annotationObject - _PDFObjectReference - - - - - - - - - - - - - - - - ArraySimple - annotationObject - - - Annots - DictionaryItemComplex - - - - - Dictionary - - - - CreateObject - pageAnnots - _PDFObject - - - - - - Code - - _void - appendTo(page, pageAnnots); -updateAnnotationAppearanceStreams(annotationObject); + appendTo(page, pageAnnots); +updateAnnotationAppearanceStreams(annotationObject); return annotationObject; @@ -8637,8 +8716,8 @@ return annotationObject; Code _void - mergeTo(rootNodeReference, updatedRootNode); -mergeTo(getCatalogReference(), updatedCatalog); + mergeTo(rootNodeReference, updatedRootNode); +mergeTo(getCatalogReference(), updatedCatalog); return rootNodeReference; @@ -8775,6 +8854,475 @@ return rootNodeReference; Creates file specification for external file. _PDFObjectReference + + + + + + + + + + fieldName + _QString + Field name + + + + + kids + _PDFObjectReferenceVector + Kids of the signature field. + + + + + signatureValue + _PDFObjectReference + Signature value + + + Parameters + + _void + + + + + + + + + + + + FT + DictionaryItemSimple + WrapName("Sig") + + + + + Kids + DictionaryItemSimple + kids + + + + + T + DictionaryItemSimple + fieldName + + + + + V + DictionaryItemSimple + signatureValue + + + + Dictionary + + + + CreateObject + formFieldSignature + _PDFObjectReference + + + + + + Code + + _void + return formFieldSignature; + + + Structure + createFormFieldSignature + Creates form field of type signature. + _PDFObjectReference + + + + + + + + + + + formField + _PDFObjectReference + Form field reference + + + + + page + _PDFObjectReference + Page reference + + + + + appearanceStream + _PDFObjectReference + Appearance stream + + + + + rect + _QRectF + Widget rectangle + + + Parameters + + _void + + + + + + + + + + + + Type + DictionaryItemSimple + WrapName("Annot") + + + + + Subtype + DictionaryItemSimple + WrapName("Widget") + + + + + P + DictionaryItemSimple + page + + + + + Rect + DictionaryItemSimple + rect + + + + + + + + + + + N + DictionaryItemSimple + appearanceStream + + + + Dictionary + + + + AP + DictionaryItemComplex + + + + + Dictionary + + + + CreateObject + widgetObject + _PDFObject + + + + + + + + + + + + Annots + DictionaryItemSimple + std::array{ formField } + + + + Dictionary + + + + CreateObject + pageObject + _PDFObject + + + + + + Code + + _void + mergeTo(formField, widgetObject); +appendTo(page, pageObject); + + + Structure + createFormFieldWidget + Creates visible form field widget without contents. + _void + + + + + + + + + + + formField + _PDFObjectReference + Form field reference + + + + + page + _PDFObjectReference + Page reference + + + Parameters + + _void + + + + + + + + + + + + P + DictionaryItemSimple + page + + + + + Rect + DictionaryItemSimple + std::array{ 0.0, 0.0, 0.0, 0.0 } + + + + + Subtype + DictionaryItemSimple + WrapName("Widget") + + + + + Type + DictionaryItemSimple + WrapName("Annot") + + + + Dictionary + + + + CreateObject + widgetObject + _PDFObject + + + + + + + + + + + + Annots + DictionaryItemSimple + std::array{ formField } + + + + Dictionary + + + + CreateObject + pageObject + _PDFObject + + + + + + Code + + _void + mergeTo(formField, widgetObject); +appendTo(page, pageObject); + + + Structure + createInvisibleFormFieldWidget + + _void + + + + + + + + + + + filter + _QByteArray + Filter (for example, Adobe.PPKLite, Entrust.PPKEF, CiCi.SignIt, ...) + + + + + subfilter + _QByteArray + Subfilter (for example, adbe.pkcs7.detached, adbe.pkcs7.sha1, ETSI.CAdES.detached, ...) + + + + + contents + _QByteArray + Contents (reserved data for signature). + + + + + signingTime + _QDateTime + Signing date/time + + + + + byteRangeItem + _PDFInteger + Item which will fill byte range array. + + + Parameters + + _void + + + + + + + + + + + + Type + DictionaryItemSimple + WrapName("Sig") + + + + + Filter + DictionaryItemSimple + WrapName(filter) + + + + + SubFilter + DictionaryItemSimple + WrapName(subfilter) + + + + + ByteRange + DictionaryItemSimple + std::array{ byteRangeItem, byteRangeItem, byteRangeItem, byteRangeItem } + + + + + Contents + DictionaryItemSimple + WrapString(contents) + + + + + M + DictionaryItemSimple + signingTime + + + + Dictionary + + + + CreateObject + signatureDictionary + _PDFObjectReference + + + + + + Code + + _void + return signatureDictionary; + + + Structure + createSignatureDictionary + Creates signature dictionary used for preparation in signing process. Can define parameters of the signature. + _PDFObjectReference + @@ -10739,6 +11287,39 @@ return rootNodeReference; Sets form field value. Value must be correct for this form field, no checking is performed. Also, if you use this function, annotation widgets, which are attached to this form field, should also be updated (for example, appearance state and sometimes appearance streams). _void + + + + + + + + + + locale + _QLocale + Locale, from which is language determined + + + Parameters + + _void + + + + + + Code + + _void + setLanguage(locale.name()); + + + Structure + setLanguage + Set document language. + _void + @@ -10796,39 +11377,6 @@ return rootNodeReference; Set document language. _void - - - - - - - - - - locale - _QLocale - Locale, from which is language determined - - - Parameters - - _void - - - - - - Code - - _void - setLanguage(locale.name()); - - - Structure - setLanguage - Set document language. - _void - @@ -11398,6 +11946,134 @@ return rootNodeReference; Sets page's user unit. It specifies user space unit, in multiples of 1 / 72 inch. _void + + + + + + + + + + signatureDictionary + _PDFObjectReference + Signature dictionary reference + + + + + contactInfoText + _QString + Contact info text + + + Parameters + + _void + + + + + + + + + + + + ContactInfo + DictionaryItemSimple + contactInfoText + + + + Dictionary + + + + CreateObject + updatedSignatureDictionary + _PDFObject + + + + + + Code + + _void + mergeTo(signatureDictionary, updatedSignatureDictionary); + + + Structure + setSignatureContactInfo + Sets signature contact info field. + _void + + + + + + + + + + + signatureDictionary + _PDFObjectReference + Signature dictionary reference + + + + + reasonText + _QString + Reason text + + + Parameters + + _void + + + + + + + + + + + + Reason + DictionaryItemSimple + reasonText + + + + Dictionary + + + + CreateObject + updatedSignatureDictionary + _PDFObject + + + + + + Code + + _void + mergeTo(signatureDictionary, updatedSignatureDictionary); + + + Structure + setSignatureReason + Sets signature reason field. + _void + @@ -11478,7 +12154,7 @@ return rootNodeReference; Code _void - m_storage.updateTrailerDictionary(qMove(trailerDictionary)); + m_storage.updateTrailerDictionary(qMove(trailerDictionary)); updateDocumentInfo(qMove(updatedInfoDictionary)); diff --git a/qt.conf b/qt.conf new file mode 100644 index 0000000..5cfc332 --- /dev/null +++ b/qt.conf @@ -0,0 +1,2 @@ +[Platforms] +WindowsArguments = fontengine=freetype \ No newline at end of file