PDF: Add Stamps to the file properly

By using Poppler 20.10 new custom image stamps APIs

Instead of the incompatible hack we used before that only worked for Okular.

This is done by modifying the update function used by PopplerAnnotationProxy in order to load the image in
the correct dimensions and send it to the poppler-Qt5 frontend.

We temporarily store the stamp annotation appearance when deleting it so that we can set it again when doing an undo undo.
This commit is contained in:
Mahmoud Khalil 2021-10-01 16:04:50 +00:00 committed by Albert Astals Cid
parent 84c9f3ea2a
commit 8b3dfcb3f4
16 changed files with 174 additions and 85 deletions

View File

@ -313,6 +313,7 @@ PRIVATE
KF5::ThreadWeaver
KF5::Bookmarks
Phonon::phonon4qt5
Qt5::Svg
${MATH_LIB}
ZLIB::ZLIB
PUBLIC # these are included from the installed headers
@ -504,6 +505,10 @@ install(FILES
install(EXPORT Okular5Targets DESTINATION "${CMAKECONFIG_INSTALL_DIR}" FILE Okular5Targets.cmake NAMESPACE Okular::)
install(FILES
core/stamps.svg
DESTINATION ${KDE_INSTALL_DATADIR}/okular/pics)
########### summary #################
feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)

View File

@ -10,6 +10,10 @@
// qt/kde includes
#include <QApplication>
#include <QColor>
#include <QIcon>
#include <QPainter>
#include <QStandardPaths>
#include <QSvgRenderer>
// DBL_MAX
#include <float.h>
@ -156,6 +160,37 @@ QRect AnnotationUtils::annotationGeometry(const Annotation *annotation, double s
return rect;
}
QPixmap AnnotationUtils::loadStamp(const QString &nameOrPath, int size, bool keepAspectRatio)
{
const QString name = nameOrPath.toLower();
const QString stampFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("okular/pics/stamps.svg"));
if (!stampFile.isEmpty()) {
QSvgRenderer r(stampFile);
if (r.isValid() && r.elementExists(name)) {
const QSize stampSize = r.boundsOnElement(name).size().toSize();
const QSize pixmapSize = stampSize.scaled(size, size, keepAspectRatio ? Qt::KeepAspectRatioByExpanding : Qt::IgnoreAspectRatio);
QPixmap pixmap(pixmapSize);
pixmap.fill(Qt::transparent);
QPainter p(&pixmap);
r.render(&p, name);
p.end();
return pixmap;
}
}
// _name is a path (do this before loading as icon name to avoid some rare weirdness )
QPixmap pixmap;
pixmap.load(nameOrPath);
if (!pixmap.isNull()) {
pixmap = pixmap.scaled(size, size, keepAspectRatio ? Qt::KeepAspectRatioByExpanding : Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
return pixmap;
}
// _name is an icon name
return QIcon::fromTheme(name).pixmap(size);
}
// END AnnotationUtils implementation
AnnotationProxy::AnnotationProxy()
@ -509,6 +544,11 @@ AnnotationPrivate::~AnnotationPrivate()
delete (*it).annotation();
}
AnnotationPrivate *AnnotationPrivate::get(Annotation *a)
{
return a ? a->d_ptr : nullptr;
}
Annotation::Annotation(AnnotationPrivate &dd)
: d_ptr(&dd)
{

View File

@ -74,6 +74,15 @@ public:
* @p scaleX and @p scaleY.
*/
static QRect annotationGeometry(const Annotation *annotation, double scaleX, double scaleY);
/**
* Returns a pixmap for a stamp symbol
*
* @p name Name of a Okular stamp symbol, icon or path to an image
* @p size Size of the pixmap (ignore aspect ratio). Takes precedence over @p iconSize
* @p iconSize Maximum size of the pixmap (keep aspect ratio)
*/
static QPixmap loadStamp(const QString &nameOrPath, int size, bool keepAspectRatio = true);
};
/**

View File

@ -27,6 +27,8 @@ class AnnotationPrivate
public:
AnnotationPrivate();
OKULARCORE_EXPORT static AnnotationPrivate *get(Annotation *a);
virtual ~AnnotationPrivate();
AnnotationPrivate(const AnnotationPrivate &) = delete;

View File

Before

Width:  |  Height:  |  Size: 208 KiB

After

Width:  |  Height:  |  Size: 208 KiB

View File

@ -11,6 +11,7 @@
#include "annots.h"
// qt/kde includes
#include <QFileInfo>
#include <QLoggingCategory>
#include <QVariant>
@ -279,6 +280,19 @@ static void setSharedAnnotationPropertiesToPopplerAnnotation(const Okular::Annot
popplerAnnotation->setModificationDate(okularAnnotation->modificationDate());
}
#ifdef HAVE_POPPLER_21_10
static void setPopplerStampAnnotationCustomImage(const Poppler::Page *page, Poppler::StampAnnotation *pStampAnnotation, const Okular::StampAnnotation *oStampAnnotation)
{
const QSize size = page->pageSize();
const QRect rect = Okular::AnnotationUtils::annotationGeometry(oStampAnnotation, size.width(), size.height());
QImage image = Okular::AnnotationUtils::loadStamp(oStampAnnotation->stampIconName(), qMax(rect.width(), rect.height())).toImage();
if (!image.isNull())
pStampAnnotation->setStampCustomImage(image);
}
#endif
static void updatePopplerAnnotationFromOkularAnnotation(const Okular::TextAnnotation *oTextAnnotation, Poppler::TextAnnotation *pTextAnnotation)
{
pTextAnnotation->setTextIcon(oTextAnnotation->textIcon());
@ -333,10 +347,17 @@ static void updatePopplerAnnotationFromOkularAnnotation(const Okular::HighlightA
pHighlightAnnotation->setHighlightQuads(pQuads);
}
#ifdef HAVE_POPPLER_21_10
static void updatePopplerAnnotationFromOkularAnnotation(const Okular::StampAnnotation *oStampAnnotation, Poppler::StampAnnotation *pStampAnnotation, const Poppler::Page *page)
{
setPopplerStampAnnotationCustomImage(page, pStampAnnotation, oStampAnnotation);
}
#else
static void updatePopplerAnnotationFromOkularAnnotation(const Okular::StampAnnotation *oStampAnnotation, Poppler::StampAnnotation *pStampAnnotation)
{
pStampAnnotation->setStampIconName(oStampAnnotation->stampIconName());
}
#endif
static void updatePopplerAnnotationFromOkularAnnotation(const Okular::InkAnnotation *oInkAnnotation, Poppler::InkAnnotation *pInkAnnotation)
{
@ -398,6 +419,17 @@ static Poppler::Annotation *createPopplerAnnotationFromOkularAnnotation(const Ok
return pHighlightAnnotation;
}
#ifdef HAVE_POPPLER_21_10
static Poppler::Annotation *createPopplerAnnotationFromOkularAnnotation(const Okular::StampAnnotation *oStampAnnotation, Poppler::Page *page)
{
Poppler::StampAnnotation *pStampAnnotation = new Poppler::StampAnnotation();
setSharedAnnotationPropertiesToPopplerAnnotation(oStampAnnotation, pStampAnnotation);
updatePopplerAnnotationFromOkularAnnotation(oStampAnnotation, pStampAnnotation, page);
return pStampAnnotation;
}
#else
static Poppler::Annotation *createPopplerAnnotationFromOkularAnnotation(const Okular::StampAnnotation *oStampAnnotation)
{
Poppler::StampAnnotation *pStampAnnotation = new Poppler::StampAnnotation();
@ -407,6 +439,7 @@ static Poppler::Annotation *createPopplerAnnotationFromOkularAnnotation(const Ok
return pStampAnnotation;
}
#endif
static Poppler::Annotation *createPopplerAnnotationFromOkularAnnotation(const Okular::InkAnnotation *oInkAnnotation)
{
@ -431,6 +464,8 @@ void PopplerAnnotationProxy::notifyAddition(Okular::Annotation *okl_ann, int pag
{
QMutexLocker ml(mutex);
Poppler::Page *ppl_page = ppl_doc->page(page);
// Create poppler annotation
Poppler::Annotation *ppl_ann = nullptr;
switch (okl_ann->subType()) {
@ -447,8 +482,26 @@ void PopplerAnnotationProxy::notifyAddition(Okular::Annotation *okl_ann, int pag
ppl_ann = createPopplerAnnotationFromOkularAnnotation(static_cast<Okular::HighlightAnnotation *>(okl_ann));
break;
case Okular::Annotation::AStamp:
#ifdef HAVE_POPPLER_21_10
{
bool wasDenyWriteEnabled = okl_ann->flags() & Okular::Annotation::DenyWrite;
if (wasDenyWriteEnabled)
okl_ann->setFlags(okl_ann->flags() & ~Okular::Annotation::DenyWrite);
ppl_ann = createPopplerAnnotationFromOkularAnnotation(static_cast<Okular::StampAnnotation *>(okl_ann), ppl_page);
if (deletedStampsAnnotationAppearance.find(static_cast<Okular::StampAnnotation *>(okl_ann)) != deletedStampsAnnotationAppearance.end()) {
ppl_ann->setAnnotationAppearance(*deletedStampsAnnotationAppearance[static_cast<Okular::StampAnnotation *>(okl_ann)].get());
deletedStampsAnnotationAppearance.erase(static_cast<Okular::StampAnnotation *>(okl_ann));
if (wasDenyWriteEnabled)
okl_ann->setFlags(okl_ann->flags() | Okular::Annotation::DenyWrite);
}
}
#else
ppl_ann = createPopplerAnnotationFromOkularAnnotation(static_cast<Okular::StampAnnotation *>(okl_ann));
break;
#endif
break;
case Okular::Annotation::AInk:
ppl_ann = createPopplerAnnotationFromOkularAnnotation(static_cast<Okular::InkAnnotation *>(okl_ann));
break;
@ -460,12 +513,15 @@ void PopplerAnnotationProxy::notifyAddition(Okular::Annotation *okl_ann, int pag
return;
}
#ifdef HAVE_POPPLER_21_10
okl_ann->setFlags(okl_ann->flags() | Okular::Annotation::ExternallyDrawn);
#else
// Poppler doesn't render StampAnnotations yet
if (ppl_ann->subType() != Poppler::Annotation::AStamp)
okl_ann->setFlags(okl_ann->flags() | Okular::Annotation::ExternallyDrawn);
#endif
// Bind poppler object to page
Poppler::Page *ppl_page = ppl_doc->page(page);
ppl_page->addAnnotation(ppl_ann);
delete ppl_page;
@ -534,7 +590,13 @@ void PopplerAnnotationProxy::notifyModification(const Okular::Annotation *okl_an
case Poppler::Annotation::AStamp: {
const Okular::StampAnnotation *okl_stampann = static_cast<const Okular::StampAnnotation *>(okl_ann);
Poppler::StampAnnotation *ppl_stampann = static_cast<Poppler::StampAnnotation *>(ppl_ann);
#ifdef HAVE_POPPLER_21_10
Poppler::Page *ppl_page = ppl_doc->page(page);
updatePopplerAnnotationFromOkularAnnotation(okl_stampann, ppl_stampann, ppl_page);
delete ppl_page;
#else
updatePopplerAnnotationFromOkularAnnotation(okl_stampann, ppl_stampann);
#endif
break;
}
case Poppler::Annotation::AInk: {
@ -562,6 +624,10 @@ void PopplerAnnotationProxy::notifyRemoval(Okular::Annotation *okl_ann, int page
Poppler::Page *ppl_page = ppl_doc->page(page);
annotationsOnOpenHash->remove(okl_ann);
#ifdef HAVE_POPPLER_21_10
if (okl_ann->subType() == Okular::Annotation::AStamp)
deletedStampsAnnotationAppearance[static_cast<Okular::StampAnnotation *>(okl_ann)] = std::move(ppl_ann->annotationAppearance());
#endif
ppl_page->removeAnnotation(ppl_ann); // Also destroys ppl_ann
delete ppl_page;
@ -997,6 +1063,9 @@ Okular::Annotation *createAnnotationFromPopplerAnnotation(Poppler::Annotation *p
break;
}
case Poppler::Annotation::AStamp:
#ifdef HAVE_POPPLER_21_10
externallyDrawn = true;
#endif
tieToOkularAnn = true;
*doDelete = false;
okularAnnotation = createAnnotationFromPopplerAnnotation(static_cast<Poppler::StampAnnotation *>(popplerAnnotation));
@ -1020,6 +1089,18 @@ Okular::Annotation *createAnnotationFromPopplerAnnotation(Poppler::Annotation *p
if (externallyDrawn)
okularAnnotation->setFlags(okularAnnotation->flags() | Okular::Annotation::ExternallyDrawn);
#ifdef HAVE_POPPLER_21_10
if (okularAnnotation->subType() == Okular::Annotation::SubType::AStamp) {
Okular::StampAnnotation *oStampAnn = static_cast<Okular::StampAnnotation *>(okularAnnotation);
Poppler::StampAnnotation *pStampAnn = static_cast<Poppler::StampAnnotation *>(popplerAnnotation);
QFileInfo stampIconFile = oStampAnn->stampIconName();
if (stampIconFile.exists() && stampIconFile.isFile()) {
setPopplerStampAnnotationCustomImage(&popplerPage, pStampAnn, oStampAnn);
}
oStampAnn->setFlags(okularAnnotation->flags() | Okular::Annotation::Flag::DenyWrite);
}
#endif
// Convert the poppler annotation style to Okular annotation style
Okular::Annotation::Style &okularStyle = okularAnnotation->style();

View File

@ -15,6 +15,8 @@
#include <QMutex>
#include <unordered_map>
#include "config-okular-poppler.h"
#include "core/annotations.h"
@ -35,6 +37,9 @@ private:
Poppler::Document *ppl_doc;
QMutex *mutex;
QHash<Okular::Annotation *, Poppler::Annotation *> *annotationsOnOpenHash;
#ifdef HAVE_POPPLER_21_10
std::unordered_map<Okular::StampAnnotation *, std::unique_ptr<Poppler::AnnotationAppearance>> deletedStampsAnnotationAppearance;
#endif
};
#endif

View File

@ -1464,7 +1464,11 @@ QVariant PDFGenerator::metaData(const QString &key, const QVariant &option) cons
return i18n("Using Poppler %1\n\nBuilt against Poppler %2", Poppler::Version::string(), POPPLER_VERSION);
}
} else if (key == QLatin1String("ShowStampsWarning")) {
#ifdef HAVE_POPPLER_21_10
return QStringLiteral("no");
#else
return QStringLiteral("yes");
#endif
}
return QVariant();
}

View File

@ -19,6 +19,7 @@
#include <QBitArray>
#include <QPointer>
#include <core/annotations.h>
#include <core/document.h>
#include <core/generator.h>
#include <core/printoptionswidget.h>
@ -26,6 +27,8 @@
#include <interfaces/printinterface.h>
#include <interfaces/saveinterface.h>
#include <unordered_map>
class PDFOptionsPage;
class PopplerAnnotationProxy;

View File

@ -27,6 +27,7 @@
// local includes
#include "actionbar.h"
#include "annotationwidgets.h"
#include "core/annotations.h"
#include "guiutils.h"
#include "pageview.h"
#include "pageviewannotator.h"
@ -453,7 +454,7 @@ const QIcon AnnotationActionHandlerPrivate::widthIcon(double width)
const QIcon AnnotationActionHandlerPrivate::stampIcon(const QString &stampIconName)
{
QPixmap stampPix = GuiUtils::loadStamp(stampIconName, 32);
QPixmap stampPix = Okular::AnnotationUtils::loadStamp(stampIconName, 32);
if (stampPix.width() == stampPix.height())
return QIcon(stampPix);
else

View File

@ -30,7 +30,11 @@
#include <QSpinBox>
#include <QVariant>
#include "core/annotations.h"
#include "core/annotations_p.h"
#include "core/document.h"
#include "core/document_p.h"
#include "core/page_p.h"
#include "guiutils.h"
#include "pagepainter.h"
@ -136,7 +140,7 @@ void PixmapPreviewSelector::iconComboChanged(const QString &icon)
m_icon = icon;
}
QPixmap pixmap = GuiUtils::loadStamp(m_icon, m_previewSize);
QPixmap pixmap = Okular::AnnotationUtils::loadStamp(m_icon, m_previewSize);
const QRect cr = m_iconLabel->contentsRect();
if (pixmap.width() > cr.width() || pixmap.height() > cr.height())
pixmap = pixmap.scaled(cr.size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
@ -149,7 +153,7 @@ void PixmapPreviewSelector::selectCustomStamp()
{
const QString customStampFile = QFileDialog::getOpenFileName(this, i18nc("@title:window file chooser", "Select custom stamp symbol"), QString(), i18n("*.ico *.png *.xpm *.svg *.svgz | Icon Files (*.ico *.png *.xpm *.svg *.svgz)"));
if (!customStampFile.isEmpty()) {
QPixmap pixmap = GuiUtils::loadStamp(customStampFile, m_previewSize);
QPixmap pixmap = Okular::AnnotationUtils::loadStamp(customStampFile, m_previewSize);
if (pixmap.isNull()) {
KMessageBox::sorry(this, xi18nc("@info", "Could not load the file <filename>%1</filename>", customStampFile), i18nc("@title:window", "Invalid file"));
} else {
@ -435,14 +439,17 @@ void StampAnnotationWidget::createStyleWidget(QFormLayout *formlayout)
{
QWidget *widget = qobject_cast<QWidget *>(formlayout->parent());
KMessageWidget *brokenStampSupportWarning = new KMessageWidget(widget);
brokenStampSupportWarning->setText(xi18nc("@info",
"<warning>experimental feature.<nl/>"
"Stamps inserted in PDF documents are not visible in PDF readers other than Okular.</warning>"));
brokenStampSupportWarning->setMessageType(KMessageWidget::Warning);
brokenStampSupportWarning->setWordWrap(true);
brokenStampSupportWarning->setCloseButtonVisible(false);
formlayout->insertRow(0, brokenStampSupportWarning);
Okular::Document *doc = Okular::AnnotationPrivate::get(m_stampAnn)->m_page->m_doc->m_parent;
if (doc->metaData(QStringLiteral("ShowStampsWarning")).toString() == QLatin1String("yes")) {
KMessageWidget *brokenStampSupportWarning = new KMessageWidget(widget);
brokenStampSupportWarning->setText(xi18nc("@info",
"<warning>experimental feature.<nl/>"
"Stamps inserted in PDF documents are not visible in PDF readers other than Okular.</warning>"));
brokenStampSupportWarning->setMessageType(KMessageWidget::Warning);
brokenStampSupportWarning->setWordWrap(true);
brokenStampSupportWarning->setCloseButtonVisible(false);
formlayout->insertRow(0, brokenStampSupportWarning);
}
addOpacitySpinBox(widget, formlayout);
addVerticalSpacer(formlayout);

View File

@ -42,8 +42,4 @@ install(FILES
uparrow.png
upleftarrow.png
DESTINATION ${KDE_INSTALL_DATADIR}/okular/pics)
# install annotation stamps
install(FILES
stamps.svg
DESTINATION ${KDE_INSTALL_DATADIR}/okular/pics)
# install misc images

View File

@ -13,7 +13,6 @@
#include <QFileDialog>
#include <QPainter>
#include <QStandardPaths>
#include <QSvgRenderer>
#include <QTextDocument>
// local includes
@ -23,32 +22,6 @@
#include <memory>
struct GuiUtilsHelper {
GuiUtilsHelper()
{
}
QSvgRenderer *svgStamps();
std::unique_ptr<QSvgRenderer> svgStampFile;
};
QSvgRenderer *GuiUtilsHelper::svgStamps()
{
if (!svgStampFile.get()) {
const QString stampFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("okular/pics/stamps.svg"));
if (!stampFile.isEmpty()) {
svgStampFile = std::make_unique<QSvgRenderer>(stampFile);
if (!svgStampFile->isValid()) {
svgStampFile.reset();
}
}
}
return svgStampFile.get();
}
Q_GLOBAL_STATIC(GuiUtilsHelper, s_data)
namespace GuiUtils
{
QString captionForAnnotation(const Okular::Annotation *ann)
@ -157,35 +130,6 @@ QString prettyToolTip(const Okular::Annotation *ann)
return tooltip;
}
QPixmap loadStamp(const QString &nameOrPath, int size, bool keepAspectRatio)
{
const QString name = nameOrPath.toLower();
// _name is the name of an Okular stamp symbols ( multiple symbols in a single *.svg file)
QSvgRenderer *r = nullptr;
if ((r = s_data->svgStamps()) && r->elementExists(name)) {
const QSize stampSize = r->boundsOnElement(name).size().toSize();
const QSize pixmapSize = stampSize.scaled(size, size, keepAspectRatio ? Qt::KeepAspectRatioByExpanding : Qt::IgnoreAspectRatio);
QPixmap pixmap(pixmapSize);
pixmap.fill(Qt::transparent);
QPainter p(&pixmap);
r->render(&p, name);
p.end();
return pixmap;
}
// _name is a path (do this before loading as icon name to avoid some rare weirdness )
QPixmap pixmap;
pixmap.load(nameOrPath);
if (!pixmap.isNull()) {
pixmap = pixmap.scaled(size, size, keepAspectRatio ? Qt::KeepAspectRatioByExpanding : Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
return pixmap;
}
// _name is an icon name
return QIcon::fromTheme(name).pixmap(size);
}
void saveEmbeddedFile(Okular::EmbeddedFile *ef, QWidget *parent)
{
const QString caption = i18n("Where do you want to save %1?", ef->name());

View File

@ -37,15 +37,6 @@ QString contentsHtml(const Okular::Annotation *annotation);
QString prettyToolTip(const Okular::Annotation *annotation);
/**
* Returns a pixmap for a stamp symbol
*
* @p name Name of a Okular stamp symbol, icon or path to an image
* @p size Size of the pixmap (ignore aspect ratio). Takes precedence over @p iconSize
* @p iconSize Maximum size of the pixmap (keep aspect ratio)
*/
QPixmap loadStamp(const QString &nameOrPath, int size, bool keepAspectRatio = true);
void saveEmbeddedFile(Okular::EmbeddedFile *ef, QWidget *parent);
void writeEmbeddedFile(Okular::EmbeddedFile *ef, QWidget *parent, QFile &targetFile);

View File

@ -22,6 +22,7 @@
#include <math.h>
// local includes
#include "core/annotations.h"
#include "core/observer.h"
#include "core/page.h"
#include "core/page_p.h"
@ -592,7 +593,7 @@ void PagePainter::paintCroppedPageOnPainter(QPainter *destPainter,
Okular::StampAnnotation *stamp = (Okular::StampAnnotation *)a;
// get pixmap and alpha blend it if needed
QPixmap pixmap = GuiUtils::loadStamp(stamp->stampIconName(), qMax(annotBoundary.width(), annotBoundary.height()) * dpr);
QPixmap pixmap = Okular::AnnotationUtils::loadStamp(stamp->stampIconName(), qMax(annotBoundary.width(), annotBoundary.height()) * dpr);
if (!pixmap.isNull()) // should never happen but can happen on huge sizes
{
QPixmap scaledCroppedPixmap = pixmap.scaled(annotBoundary.width() * dpr, annotBoundary.height() * dpr).copy(dInnerRect.toAlignedRect());

View File

@ -70,7 +70,7 @@ public:
// create engine objects
if (!hoverIconName.simplified().isEmpty())
pixmap = GuiUtils::loadStamp(hoverIconName, size);
pixmap = Okular::AnnotationUtils::loadStamp(hoverIconName, size);
}
QRect event(EventType type, Button button, Modifiers modifiers, double nX, double nY, double xScale, double yScale, const Okular::Page *page) override
@ -1450,7 +1450,7 @@ QPixmap PageViewAnnotator::makeToolPixmap(const QDomElement &toolElement)
p.drawLine(0, 20, 19, 20);
p.drawLine(1, 21, 18, 21);
} else if (annotType == QLatin1String("stamp")) {
QPixmap stamp = GuiUtils::loadStamp(icon, 16, false /* keepAspectRatio */);
QPixmap stamp = Okular::AnnotationUtils::loadStamp(icon, 16, false /* keepAspectRatio */);
p.setRenderHint(QPainter::Antialiasing);
p.drawPixmap(16, 14, stamp);
} else if (annotType == QLatin1String("straight-line")) {