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 @@
+
+
+
+
\ 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
+
+
+