Merge remote-tracking branch 'remotes/origin/signdoc'

This commit is contained in:
Jakub Melka
2022-05-29 17:32:39 +02:00
102 changed files with 12961 additions and 2158 deletions

View File

@@ -15,7 +15,7 @@
# You should have received a copy of the GNU Lesser General Public License
# along with PDF4QT. If not, see <https://www.gnu.org/licenses/>.
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

View File

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

View File

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

View File

@@ -501,10 +501,11 @@ public:
{
None = 0x0000, ///< No flag
Reset = 0x0001, ///< Whole document content is changed (for example, new document is being set)
Annotation = 0x0002, ///< Annotations changed
FormField = 0x0004, ///< Form field content changed
Authorization = 0x0008, ///< Authorization has changed (for example, old document is granted user access, but for new, owner access)
XFA_Pagination = 0x0010, ///< XFA pagination has been performed (this flag can be set only when Reset flag has been set and not any other flag)
PageContents = 0x0002, ///< Page contents changed (page graphics, not annotations)
Annotation = 0x0004, ///< Annotations changed
FormField = 0x0008, ///< Form field content changed
Authorization = 0x0010, ///< Authorization has changed (for example, old document is granted user access, but for new, owner access)
XFA_Pagination = 0x0020, ///< XFA pagination has been performed (this flag can be set only when Reset flag has been set and not any other flag)
};
Q_DECLARE_FLAGS(ModificationFlags, ModificationFlag)
@@ -550,6 +551,7 @@ public:
ModificationFlags getFlags() const { return m_flags; }
bool hasReset() const { return m_flags.testFlag(Reset); }
bool hasPageContentsChanged() const { return m_flags.testFlag(PageContents); }
bool hasFlag(ModificationFlag flag) const { return m_flags.testFlag(flag); }
operator PDFDocument*() const { return m_document; }

View File

@@ -22,6 +22,8 @@
#include "pdfobjectutils.h"
#include "pdfnametreeloader.h"
#include "pdfdbgheap.h"
#include "pdfparser.h"
#include "pdfstreamfilters.h"
#include <QBuffer>
#include <QPainter>
@@ -698,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<PDFReal, 4> PDFDocumentBuilder::getAnnotationReductionRectangle(const QRectF& boundingRect, const QRectF& innerRect) const
{
return { qAbs(innerRect.left() - boundingRect.left()), qAbs(boundingRect.bottom() - innerRect.bottom()), qAbs(boundingRect.right() - innerRect.right()), qAbs(boundingRect.top() - innerRect.top()) };
}
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<PDFObjectReference> 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<std::pair<QByteArray, QByteArray>> renamings;
if (oldResources.isDictionary() && newResources.isDictionary())
{
PDFObjectFactory renamedResourcesDictionary;
renamedResourcesDictionary.beginDictionary();
const PDFDictionary* oldResourcesDictionary = oldResources.getDictionary();
const PDFDictionary* newResourcesDictionary = newResources.getDictionary();
const size_t count = newResourcesDictionary->getCount();
for (size_t i = 0; i < count; ++i)
{
const PDFInplaceOrMemoryString& key = newResourcesDictionary->getKey(i);
QByteArray keyString = key.getString();
// Process current resource key
renamedResourcesDictionary.beginDictionaryItem(keyString);
if (oldResourcesDictionary->hasKey(keyString))
{
const PDFObject& newResourcesSubdictionaryObject = m_documentBuilder->getObject(newResourcesDictionary->getValue(i));
const PDFObject& oldResourcesSubdictionaryObject = m_documentBuilder->getObject(oldResourcesDictionary->get(keyString));
if (oldResourcesSubdictionaryObject.isDictionary() && newResourcesSubdictionaryObject.isDictionary())
{
// Jakub Melka: Rename items, which are in both dictionaries
const PDFDictionary* oldSd = oldResourcesSubdictionaryObject.getDictionary();
const PDFDictionary* newSd = newResourcesSubdictionaryObject.getDictionary();
renamedResourcesDictionary.beginDictionary();
const size_t subcount = newSd->getCount();
for (size_t j = 0; j < subcount; ++j)
{
const PDFInplaceOrMemoryString& subkey = newSd->getKey(j);
QByteArray subkeyString = subkey.getString();
if (oldSd->hasKey(subkeyString))
{
// Jakub Melka: we must rename the item
QByteArray newSubkeyString = subkeyString;
PDFInteger k = 0;
while (oldSd->hasKey(newSubkeyString))
{
newSubkeyString = subkeyString + "_" + QByteArray::number(++k);
}
renamings.emplace_back(std::make_pair(subkeyString, newSubkeyString));
subkeyString = newSubkeyString;
}
renamedResourcesDictionary.beginDictionaryItem(subkeyString);
renamedResourcesDictionary << newSd->getValue(j);
renamedResourcesDictionary.endDictionaryItem();
}
renamedResourcesDictionary.endDictionary();
}
else
{
renamedResourcesDictionary << newResourcesDictionary->getValue(i);
}
}
else
{
renamedResourcesDictionary << newResourcesDictionary->getValue(i);
}
renamedResourcesDictionary.endDictionaryItem();
}
renamedResourcesDictionary.endDictionary();
m_documentBuilder->setObject(resourcesReference, renamedResourcesDictionary.takeObject());
}
if (contentStreamObject.isStream())
{
QByteArray decodedStream = m_documentBuilder->getDecodedStream(contentStreamObject.getStream());
decodedStream = decodedStream.trimmed();
// Append save/restore state
if (!decodedStream.startsWith("q "))
{
decodedStream.prepend("q ");
decodedStream.append(" Q");
}
// Replace all occurences in the stream
for (const auto& item : renamings)
{
QByteArray oldName = item.first;
QByteArray newName = item.second;
oldName.prepend('/');
newName.prepend('/');
int currentPos = 0;
int oldNameIndex = decodedStream.indexOf(oldName, currentPos);
while (oldNameIndex != -1)
{
const int whiteSpacePosition = oldNameIndex + oldName.size();
if (whiteSpacePosition < decodedStream.size() && !PDFLexicalAnalyzer::isWhitespace(decodedStream[whiteSpacePosition]))
{
currentPos = oldNameIndex + 1;
oldNameIndex = decodedStream.indexOf(oldName, currentPos);
continue;
}
decodedStream.replace(oldNameIndex, oldName.length(), newName);
currentPos = oldNameIndex + 1;
oldNameIndex = decodedStream.indexOf(oldName, currentPos);
}
}
PDFArray array;
array.appendItem(PDFObject::createName("FlateDecode"));
// Compress the content stream
QByteArray compressedData = PDFFlateDecodeFilter::compress(decodedStream);
PDFDictionary updatedDictionary = *contentStreamObject.getStream()->getDictionary();
updatedDictionary.setEntry(PDFInplaceOrMemoryString("Length"), PDFObject::createInteger(compressedData.size()));
updatedDictionary.setEntry(PDFInplaceOrMemoryString("Filters"), PDFObject::createArray(std::make_shared<PDFArray>(qMove(array))));
PDFObject newContentStream = PDFObject::createStream(std::make_shared<PDFStream>(qMove(updatedDictionary), qMove(compressedData)));
m_documentBuilder->setObject(contentStreamReference, std::move(newContentStream));
}
}
void PDFDocumentBuilder::updateAnnotationAppearanceStreams(PDFObjectReference annotationReference)
@@ -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;

View File

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

View File

@@ -192,7 +192,10 @@ PDFOperationResult PDFDocumentWriter::write(const QString& fileName, const PDFDo
PDFOperationResult result = write(&file, document);
if (result)
{
file.commit();
if (!file.commit())
{
return tr("File '%1' can't be opened for writing. %2").arg(fileName, file.errorString());
}
}
else
{

View File

@@ -488,8 +488,8 @@ void PDFDrawWidgetProxy::setDocument(const PDFModifiedDocument& document)
if (getDocument() != document)
{
m_cacheClearTimer->stop();
m_compiler->stop(document.hasReset());
m_textLayoutCompiler->stop(document.hasReset());
m_compiler->stop(document.hasReset() || document.hasPageContentsChanged());
m_textLayoutCompiler->stop(document.hasReset() || document.hasPageContentsChanged());
m_controller->setDocument(document);
if (PDFOptionalContentActivity* optionalContentActivity = document.getOptionalContentActivity())

View File

@@ -1836,7 +1836,7 @@ void PDFFontCache::setDocument(const PDFModifiedDocument& document)
// Jakub Melka: If document has not reset flag, then fonts of the
// document remains the same. So it is not needed to clear font cache.
if (document.hasReset())
if (document.hasReset() || document.hasPageContentsChanged())
{
m_fontCache.clear();
m_realizedFontCache.clear();

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
// Copyright (C) 2020-2021 Jakub Melka
// Copyright (C) 2020-2022 Jakub Melka
//
// This file is part of PDF4QT.
//

View File

@@ -738,7 +738,7 @@ void PDFThumbnailsItemModel::setDocument(const PDFModifiedDocument& document)
{
if (m_document != document)
{
if (document.hasReset() || document.hasFlag(PDFModifiedDocument::Annotation))
if (document.hasReset() || document.hasPageContentsChanged() || document.hasFlag(PDFModifiedDocument::Annotation))
{
beginResetModel();
m_thumbnailCache.clear();

View File

@@ -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 <https://www.gnu.org/licenses/>.
#include "pdfpagecontenteditorstylesettings.h"
#include "ui_pdfpagecontenteditorstylesettings.h"
#include "pdfwidgetutils.h"
#include "pdfpagecontentelements.h"
#include <QFontDialog>
#include <QColorDialog>
#include <QLineEdit>
#include <QVBoxLayout>
#include <QGroupBox>
#include <QDialogButtonBox>
#include <QTextEdit>
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<double>::of(&QDoubleSpinBox::valueChanged), this, &PDFPageContentEditorStyleSettings::onPenWidthChanged);
connect(ui->penStyleCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &PDFPageContentEditorStyleSettings::onPenStyleChanged);
connect(ui->brushStyleCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &PDFPageContentEditorStyleSettings::onBrushStyleChanged);
connect(ui->textAngleEdit, QOverload<double>::of(&QDoubleSpinBox::valueChanged), this, &PDFPageContentEditorStyleSettings::onTextAngleChanged);
connect(ui->penColorCombo->lineEdit(), &QLineEdit::editingFinished, this, &PDFPageContentEditorStyleSettings::onPenColorComboTextChanged);
connect(ui->penColorCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &PDFPageContentEditorStyleSettings::onPenColorComboIndexChanged);
connect(ui->brushColorCombo->lineEdit(), &QLineEdit::editingFinished, this, &PDFPageContentEditorStyleSettings::onBrushColorComboTextChanged);
connect(ui->brushColorCombo, QOverload<int>::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<QRadioButton*>())
{
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<const PDFPageContentStyledElement*>(element);
const PDFPageContentElementTextBox* textElement = dynamic_cast<const PDFPageContentElementTextBox*>(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<QRadioButton*>())
{
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<QRadioButton*>())
{
radioButton->setChecked(false);
}
m_alignment = alignment;
QRadioButton* radioButton = qobject_cast<QRadioButton*>(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<PDFPageContentStyledElement*>(element);
PDFPageContentElementTextBox* textElement = dynamic_cast<PDFPageContentElementTextBox*>(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<Qt::Alignment>(alignment);
if (m_alignment != alignmentValue)
{
m_alignment = alignmentValue;
emit alignmentChanged(m_alignment);
}
}
void PDFPageContentEditorStyleSettings::onPenStyleChanged()
{
Qt::PenStyle penStyle = static_cast<Qt::PenStyle>(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<Qt::BrushStyle>(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<QColor>();
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<QColor>();
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

View File

@@ -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 <https://www.gnu.org/licenses/>.
#ifndef PDFPAGECONTENTEDITORSTYLESETTINGS_H
#define PDFPAGECONTENTEDITORSTYLESETTINGS_H
#include "pdfglobal.h"
#include <QPen>
#include <QIcon>
#include <QFont>
#include <QBrush>
#include <QWidget>
#include <QSignalMapper>
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

View File

@@ -0,0 +1,230 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PDFPageContentEditorStyleSettings</class>
<widget class="QWidget" name="PDFPageContentEditorStyleSettings">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>344</width>
<height>310</height>
</rect>
</property>
<property name="windowTitle">
<string>Style Settings</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="7" column="0">
<widget class="QLabel" name="textAlignmentLabel">
<property name="text">
<string>Text Alignment</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="penStyleLabel">
<property name="text">
<string>Pen Style</string>
</property>
</widget>
</item>
<item row="8" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::MinimumExpanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="4" column="1">
<widget class="QComboBox" name="brushColorCombo">
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="1" colspan="2">
<widget class="QComboBox" name="penStyleCombo"/>
</item>
<item row="0" column="1" colspan="2">
<widget class="QDoubleSpinBox" name="penWidthEdit"/>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="penColorCombo">
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="penWidthLabel">
<property name="text">
<string>Pen Width</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="brushColorLabel">
<property name="text">
<string>Brush Color</string>
</property>
</widget>
</item>
<item row="7" column="1" colspan="2">
<layout class="QGridLayout" name="textAlignmentLayout">
<item row="1" column="0">
<widget class="QRadioButton" name="al21Button">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QRadioButton" name="al13Button">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QRadioButton" name="al32Button">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QRadioButton" name="al22Button">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QRadioButton" name="al33Button">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QRadioButton" name="al31Button">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QRadioButton" name="al23Button">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QRadioButton" name="al11Button">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QRadioButton" name="al12Button">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="1" column="3">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="6" column="1" colspan="2">
<widget class="QDoubleSpinBox" name="textAngleEdit">
<property name="minimum">
<double>-90.000000000000000</double>
</property>
<property name="maximum">
<double>90.000000000000000</double>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="QToolButton" name="selectBrushColorButton">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="penColorLabel">
<property name="text">
<string>Pen Color</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="fontLabel">
<property name="text">
<string>Font</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QFontComboBox" name="fontComboBox"/>
</item>
<item row="6" column="0">
<widget class="QLabel" name="textAngleLabel">
<property name="text">
<string>Text Angle</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QToolButton" name="selectPenColorButton">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="5" column="2">
<widget class="QToolButton" name="selectFontButton">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="brushStyleLabel">
<property name="text">
<string>Brush Style</string>
</property>
</widget>
</item>
<item row="3" column="1" colspan="2">
<widget class="QComboBox" name="brushStyleCombo"/>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -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 <https://www.gnu.org/licenses/>.
#include "pdfpagecontenteditortools.h"
#include "pdfpagecontentelements.h"
#include "pdfpainterutils.h"
#include "pdftexteditpseudowidget.h"
#include "pdfdrawwidget.h"
#include <QPen>
#include <QPainter>
#include <QMouseEvent>
#include <QFileDialog>
#include <QImageReader>
#include <QGuiApplication>
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<PDFPageContentStyledElement*>(getElement()))
{
styledElement->setPen(pen);
emit getProxy()->repaintNeeded();
}
}
void PDFCreatePCElementTool::setBrush(const QBrush& brush)
{
if (PDFPageContentStyledElement* styledElement = dynamic_cast<PDFPageContentStyledElement*>(getElement()))
{
styledElement->setBrush(brush);
emit getProxy()->repaintNeeded();
}
}
void PDFCreatePCElementTool::setFont(const QFont& font)
{
if (PDFPageContentElementTextBox* textBoxElement = dynamic_cast<PDFPageContentElementTextBox*>(getElement()))
{
textBoxElement->setFont(font);
emit getProxy()->repaintNeeded();
}
}
void PDFCreatePCElementTool::setAlignment(Qt::Alignment alignment)
{
if (PDFPageContentElementTextBox* textBoxElement = dynamic_cast<PDFPageContentElementTextBox*>(getElement()))
{
textBoxElement->setAlignment(alignment);
emit getProxy()->repaintNeeded();
}
}
void PDFCreatePCElementTool::setTextAngle(PDFReal angle)
{
if (PDFPageContentElementTextBox* textBoxElement = dynamic_cast<PDFPageContentElementTextBox*>(getElement()))
{
textBoxElement->setAngle(angle);
emit getProxy()->repaintNeeded();
}
}
QRectF PDFCreatePCElementTool::getRectangleFromPickTool(PDFPickTool* pickTool,
const QMatrix& pagePointToDevicePointMatrix)
{
const std::vector<QPointF>& 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<PDFRenderError>& 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<PDFRenderError>& 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<PDFRenderError>& 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<QByteArray> 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<PDFRenderError>& 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<PDFRenderError>& 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<PDFRenderError>& 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<int>::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<QPointF> 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<QPointF> 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<QPointF> 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<QPointF> 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<int>::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<int>::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<int>::max(), pen.color());
emit getProxy()->repaintNeeded();
}
} // namespace pdf

View File

@@ -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 <https://www.gnu.org/licenses/>.
#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<PDFRenderError>& 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<PDFRenderError>& 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<PDFRenderError>& 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<QPointF> 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<PDFRenderError>& 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<PDFRenderError>& 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<PDFRenderError>& 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<QPointF> getPagePointUnderMouse(QMouseEvent* event) const;
bool isEditing() const;
PDFPickTool* m_pickTool;
PDFPageContentElementTextBox* m_element;
PDFTextEditPseudowidget* m_textEditWidget;
};
} // namespace pdf
#endif // PDFPAGECONTENTEDITORTOOLS_H

View File

@@ -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 <https://www.gnu.org/licenses/>.
#include "pdfpagecontenteditorwidget.h"
#include "pdfpagecontenteditorstylesettings.h"
#include "ui_pdfpagecontenteditorwidget.h"
#include "pdfwidgetutils.h"
#include "pdfpagecontentelements.h"
#include "pdfutils.h"
#include <QAction>
#include <QToolButton>
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<QToolButton*>())
{
button->setIconSize(m_toolButtonIconSize);
}
m_operationMapper.setMapping(ui->alignVertTopButton, static_cast<int>(PDFPageContentElementManipulator::Operation::AlignTop));
m_operationMapper.setMapping(ui->alignVertMiddleButton, static_cast<int>(PDFPageContentElementManipulator::Operation::AlignCenterVertically));
m_operationMapper.setMapping(ui->alignVertBottomButton, static_cast<int>(PDFPageContentElementManipulator::Operation::AlignBottom));
m_operationMapper.setMapping(ui->alignHorLeftButton, static_cast<int>(PDFPageContentElementManipulator::Operation::AlignLeft));
m_operationMapper.setMapping(ui->alignHorMiddleButton, static_cast<int>(PDFPageContentElementManipulator::Operation::AlignCenterHorizontally));
m_operationMapper.setMapping(ui->alignHorRightButton, static_cast<int>(PDFPageContentElementManipulator::Operation::AlignRight));
m_operationMapper.setMapping(ui->setSameWidthButton, static_cast<int>(PDFPageContentElementManipulator::Operation::SetSameWidth));
m_operationMapper.setMapping(ui->setSameHeightButton, static_cast<int>(PDFPageContentElementManipulator::Operation::SetSameHeight));
m_operationMapper.setMapping(ui->setSameSizeButton, static_cast<int>(PDFPageContentElementManipulator::Operation::SetSameSize));
m_operationMapper.setMapping(ui->centerHorizontallyButton, static_cast<int>(PDFPageContentElementManipulator::Operation::CenterHorizontally));
m_operationMapper.setMapping(ui->centerVerticallyButton, static_cast<int>(PDFPageContentElementManipulator::Operation::CenterVertically));
m_operationMapper.setMapping(ui->centerRectButton, static_cast<int>(PDFPageContentElementManipulator::Operation::CenterHorAndVert));
m_operationMapper.setMapping(ui->layoutHorizontallyButton, static_cast<int>(PDFPageContentElementManipulator::Operation::LayoutHorizontally));
m_operationMapper.setMapping(ui->layoutVerticallyButton, static_cast<int>(PDFPageContentElementManipulator::Operation::LayoutVertically));
m_operationMapper.setMapping(ui->layoutFormButton, static_cast<int>(PDFPageContentElementManipulator::Operation::LayoutForm));
m_operationMapper.setMapping(ui->layoutGridButton, static_cast<int>(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<QToolButton*>(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<PDFInteger> presentElementIds;
std::set<PDFInteger> 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<QAction*>(actionObject);
Q_ASSERT(action);
action->trigger();
}
void PDFPageContentEditorWidget::onActionChanged()
{
QAction* action = qobject_cast<QAction*>(sender());
QToolButton* button = qobject_cast<QToolButton*>(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<PDFInteger> PDFPageContentEditorWidget::getSelection() const
{
std::set<PDFInteger> 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<PDFInteger>& 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

View File

@@ -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 <https://www.gnu.org/licenses/>.
#ifndef PDFPAGECONTENTEDITORWIDGET_H
#define PDFPAGECONTENTEDITORWIDGET_H
#include "pdfglobal.h"
#include <QDockWidget>
#include <QSignalMapper>
#include <set>
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<PDFInteger> getSelection() const;
void setSelection(const std::set<PDFInteger>& 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

View File

@@ -0,0 +1,215 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PDFPageContentEditorWidget</class>
<widget class="QDockWidget" name="PDFPageContentEditorWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>333</width>
<height>607</height>
</rect>
</property>
<property name="allowedAreas">
<set>Qt::LeftDockWidgetArea|Qt::RightDockWidgetArea</set>
</property>
<property name="windowTitle">
<string>Content editor</string>
</property>
<widget class="QWidget" name="dockWidgetContents">
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,0,0,1">
<item>
<widget class="QGroupBox" name="toolGroupBox">
<property name="title">
<string>Toolbox</string>
</property>
<layout class="QGridLayout" name="toolGroupBoxLayout"/>
</widget>
</item>
<item>
<widget class="QGroupBox" name="adjustBox">
<property name="title">
<string>Geometry Tools</string>
</property>
<layout class="QGridLayout" name="geometryToolsGroupBoxLayout">
<item row="0" column="0">
<widget class="QToolButton" name="alignVertTopButton">
<property name="toolTip">
<string>Align to Top</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QToolButton" name="alignVertMiddleButton">
<property name="toolTip">
<string>Align to Vertical Center</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QToolButton" name="alignVertBottomButton">
<property name="toolTip">
<string>Align to Bottom</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QToolButton" name="setSameWidthButton">
<property name="toolTip">
<string>Set Same Width</string>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="QToolButton" name="setSameHeightButton">
<property name="toolTip">
<string>Set Same Height</string>
</property>
</widget>
</item>
<item row="0" column="5">
<widget class="QToolButton" name="setSameSizeButton">
<property name="toolTip">
<string>Set Same Size</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QToolButton" name="alignHorLeftButton">
<property name="toolTip">
<string>Align to Left</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QToolButton" name="alignHorMiddleButton">
<property name="toolTip">
<string>Align to Horizontal Center</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QToolButton" name="alignHorRightButton">
<property name="toolTip">
<string>Align to Right</string>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QToolButton" name="centerHorizontallyButton">
<property name="toolTip">
<string>Center Horizontally</string>
</property>
</widget>
</item>
<item row="1" column="4">
<widget class="QToolButton" name="centerVerticallyButton">
<property name="toolTip">
<string>Center Vertically</string>
</property>
</widget>
</item>
<item row="1" column="5">
<widget class="QToolButton" name="centerRectButton">
<property name="toolTip">
<string>Center to Page Media Box</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="layoutGroupBox">
<property name="title">
<string>Layout Tools</string>
</property>
<layout class="QHBoxLayout" name="layoutToolBoxLayout">
<item>
<widget class="QToolButton" name="layoutHorizontallyButton">
<property name="toolTip">
<string>Make Horizontal Layout</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="layoutVerticallyButton">
<property name="toolTip">
<string>Make Vertical Layout</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="layoutFormButton">
<property name="toolTip">
<string>Make Form Layout</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="layoutGridButton">
<property name="toolTip">
<string>Make Grid Layout</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="appearanceGroupBox">
<property name="title">
<string>Appearance</string>
</property>
<layout class="QVBoxLayout" name="appearanceLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="pdf::PDFPageContentEditorStyleSettings" name="appearanceSettingsWidget" native="true"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="contentGroupBox">
<property name="title">
<string>Content</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QListWidget" name="itemsListWidget">
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>pdf::PDFPageContentEditorStyleSettings</class>
<extends>QWidget</extends>
<header>pdfpagecontenteditorstylesettings.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

File diff suppressed because it is too large Load Diff

View File

@@ -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 <https://www.gnu.org/licenses/>.
#ifndef PDFPAGECONTENTELEMENTS_H
#define PDFPAGECONTENTELEMENTS_H
#include "pdfdocumentdrawinterface.h"
#include <QPen>
#include <QFont>
#include <QBrush>
#include <QCursor>
#include <QPainterPath>
#include <set>
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<PDFRenderError>& 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<PDFRenderError>& 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<PDFRenderError>& 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<PDFRenderError>& 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<PDFRenderError>& 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<PDFRenderError>& 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<QSvgRenderer> 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<PDFRenderError>& 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<PDFInteger>& 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<PDFInteger>& ids, SelectionModes modes);
void select(PDFInteger id);
void select(const std::set<PDFInteger>& ids);
void selectNew(PDFInteger id);
void selectNew(const std::set<PDFInteger>& ids);
void deselect(PDFInteger id);
void deselect(const std::set<PDFInteger>& 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<PDFRenderError>& 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<PDFInteger> m_selection;
bool m_isManipulationInProgress;
std::vector<std::unique_ptr<PDFPageContentElement>> m_manipulatedElements;
std::map<PDFInteger, uint> 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<PDFInteger> getElementIds() const;
/// Returns set of selected element ids
std::set<PDFInteger> getSelectedElementIds() const;
/// Returns set of involved pages
std::set<PDFInteger> getPageIndices() const;
/// Returns bounding box of elements on page
QRectF getBoundingBox(PDFInteger pageIndex) const;
/// Set selected items
void setSelectedElementIds(const std::set<PDFInteger>& selectedElementIds);
/// Removes elements specified in selection
/// \param selection Items to be removed
void removeElementsById(const std::vector<PDFInteger>& 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<QCursor>& getCursor() const override;
virtual int getInputPriority() const override;
virtual void drawPage(QPainter* painter,
PDFInteger pageIndex,
const PDFPrecompiledPage* compiledPage,
PDFTextLayoutGetter& layoutGetter,
const QMatrix& pagePointToDevicePointMatrix,
QList<PDFRenderError>& 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<PDFRenderError>& 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<PDFInteger>& elements);
private:
struct MouseEventInfo
{
std::set<PDFInteger> 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<std::unique_ptr<PDFPageContentElement>> m_elements;
std::optional<QCursor> m_cursor;
PDFPageContentElementManipulator m_manipulator;
MouseGrabInfo m_mouseGrabInfo;
};
} // namespace pdf
Q_DECLARE_OPERATORS_FOR_FLAGS(pdf::PDFPageContentElementManipulator::SelectionModes)
#endif // PDFPAGECONTENTELEMENTS_H

View File

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

View File

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

View File

@@ -383,10 +383,9 @@ QByteArray PDFFlateDecodeFilter::apply(const QByteArray& data,
return predictor.apply(uncompress(data));
}
QByteArray PDFFlateDecodeFilter::recompress(const QByteArray& data)
QByteArray PDFFlateDecodeFilter::compress(const QByteArray& decompressedData)
{
QByteArray result;
QByteArray decompressedData = uncompress(data);
z_stream stream = { };
stream.next_in = const_cast<Bytef*>(convertByteArrayToUcharPtr(decompressedData));
@@ -431,13 +430,19 @@ QByteArray PDFFlateDecodeFilter::recompress(const QByteArray& data)
errorMessage = PDFTranslationContext::tr("zlib code: %1").arg(error);
}
throw PDFException(PDFTranslationContext::tr("Error decompressing by flate method: %1").arg(errorMessage));
throw PDFException(PDFTranslationContext::tr("Error compressing by flate method: %1").arg(errorMessage));
}
}
return result;
}
QByteArray PDFFlateDecodeFilter::recompress(const QByteArray& data)
{
QByteArray decompressedData = uncompress(data);
return compress(decompressedData);
}
PDFInteger PDFFlateDecodeFilter::getStreamDataLength(const QByteArray& data, PDFInteger offset) const
{
if (offset < 0 || offset >= data.size())

View File

@@ -215,6 +215,10 @@ public:
/// Recompresses data. So, first, data are decompressed, and then
/// recompressed again with maximal compress ratio possible.
/// \param data Uncompressed data to be compressed
static QByteArray compress(const QByteArray& decompressedData);
/// Compress data with maximal compress ratio possible.
/// \param data Compressed data to be recompressed
static QByteArray recompress(const QByteArray& data);

View File

@@ -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 <https://www.gnu.org/licenses/>.
#include "pdftexteditpseudowidget.h"
#include "pdfpainterutils.h"
#include <QStyle>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QApplication>
#include <QClipboard>
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<int> PDFTextEditPseudowidget::getCursorPositions() const
{
std::vector<int> 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 = [&parameters](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<int> 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<QTextLayout::FormatRange> 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<int> 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

View File

@@ -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 <https://www.gnu.org/licenses/>.
#ifndef PDFTEXTEDITPSEUDOWIDGET_H
#define PDFTEXTEDITPSEUDOWIDGET_H
#include "pdfglobal.h"
#include "pdfform.h"
#include <QRectF>
#include <QColor>
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<int> 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

View File

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

View File

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

View File

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