blob: 39a5a4f8b64f1b86c5fb37437e92dd9e38079720 [file] [log] [blame]
/* poppler-annotation.cc: qt interface to poppler
* Copyright (C) 2006, 2009, 2012-2015, 2018-2022 Albert Astals Cid <aacid@kde.org>
* Copyright (C) 2006, 2008, 2010 Pino Toscano <pino@kde.org>
* Copyright (C) 2012, Guillermo A. Amaral B. <gamaral@kde.org>
* Copyright (C) 2012-2014 Fabio D'Urso <fabiodurso@hotmail.it>
* Copyright (C) 2012, 2015, Tobias Koenig <tobias.koenig@kdab.com>
* Copyright (C) 2018 Adam Reichold <adam.reichold@t-online.de>
* Copyright (C) 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, <info@kdab.com>. Work sponsored by the LiMux project of the city of Munich
* Copyright (C) 2018 Intevation GmbH <intevation@intevation.de>
* Copyright (C) 2018 Dileep Sankhla <sankhla.dileep96@gmail.com>
* Copyright (C) 2018, 2019 Tobias Deiminger <haxtibal@posteo.de>
* Copyright (C) 2018 Carlos Garcia Campos <carlosgc@gnome.org>
* Copyright (C) 2020-2022 Oliver Sander <oliver.sander@tu-dresden.de>
* Copyright (C) 2020 Katarina Behrens <Katarina.Behrens@cib.de>
* Copyright (C) 2020 Thorsten Behrens <Thorsten.Behrens@CIB.de>
* Copyright (C) 2020, 2024 Klarälvdalens Datakonsult AB, a KDAB Group company, <info@kdab.com>. Work sponsored by Technische Universität Dresden
* Copyright (C) 2021 Mahmoud Ahmed Khalil <mahmoudkhalil11@gmail.com>
* Adapting code from
* Copyright (C) 2004 by Enrico Ros <eros.kde@email.it>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
*/
// qt/kde includes
#include <QtCore/QtAlgorithms>
#include <QtGui/QColor>
#include <QtGui/QTransform>
#include <QImage>
// local includes
#include "poppler-annotation.h"
#include "poppler-link.h"
#include "poppler-qt6.h"
#include "poppler-annotation-helper.h"
#include "poppler-annotation-private.h"
#include "poppler-page-private.h"
#include "poppler-private.h"
#include "poppler-form.h"
// poppler includes
#include <Page.h>
#include <Annot.h>
#include <Gfx.h>
#include <Error.h>
#include <FileSpec.h>
#include <Link.h>
#include <DateInfo.h>
/* Almost all getters directly query the underlying poppler annotation, with
* the exceptions of link, file attachment, sound, movie and screen annotations,
* Whose data retrieval logic has not been moved yet. Their getters return
* static data set at creation time by findAnnotations
*/
namespace Poppler {
// BEGIN AnnotationAppearancePrivate implementation
AnnotationAppearancePrivate::AnnotationAppearancePrivate(Annot *annot)
{
if (annot) {
appearance = annot->getAppearance();
} else {
appearance.setToNull();
}
}
// END AnnotationAppearancePrivate implementation
// BEGIN AnnotationAppearance implementation
AnnotationAppearance::AnnotationAppearance(AnnotationAppearancePrivate *annotationAppearancePrivate) : d(annotationAppearancePrivate) { }
AnnotationAppearance::~AnnotationAppearance()
{
delete d;
}
// END AnnotationAppearance implementation
// BEGIN Annotation implementation
AnnotationPrivate::AnnotationPrivate() : revisionScope(Annotation::Root), revisionType(Annotation::None), pdfAnnot(nullptr), pdfPage(nullptr), parentDoc(nullptr) { }
void getRawDataFromQImage(const QImage &qimg, int bitsPerPixel, QByteArray *data, QByteArray *sMaskData)
{
const int height = qimg.height();
const int width = qimg.width();
switch (bitsPerPixel) {
case 1:
for (int line = 0; line < height; line++) {
const char *lineData = reinterpret_cast<const char *>(qimg.scanLine(line));
for (int offset = 0; offset < (width + 7) / 8; offset++) {
data->append(lineData[offset]);
}
}
break;
case 8:
case 24:
data->append((const char *)qimg.bits(), static_cast<int>(qimg.sizeInBytes()));
break;
case 32:
for (int line = 0; line < height; line++) {
const QRgb *lineData = reinterpret_cast<const QRgb *>(qimg.scanLine(line));
for (int offset = 0; offset < width; offset++) {
char a = (char)qAlpha(lineData[offset]);
char r = (char)qRed(lineData[offset]);
char g = (char)qGreen(lineData[offset]);
char b = (char)qBlue(lineData[offset]);
data->append(r);
data->append(g);
data->append(b);
sMaskData->append(a);
}
}
break;
}
}
void AnnotationPrivate::addRevision(Annotation *ann, Annotation::RevScope scope, Annotation::RevType type)
{
/* Since ownership stays with the caller, create an alias of ann */
revisions.append(ann->d_ptr->makeAlias());
/* Set revision properties */
revisionScope = scope;
revisionType = type;
}
AnnotationPrivate::~AnnotationPrivate()
{
// Delete all children revisions
qDeleteAll(revisions);
// Release Annot object
if (pdfAnnot) {
pdfAnnot->decRefCnt();
}
}
void AnnotationPrivate::tieToNativeAnnot(Annot *ann, ::Page *page, Poppler::DocumentData *doc)
{
if (pdfAnnot) {
error(errIO, -1, "Annotation is already tied");
return;
}
pdfAnnot = ann;
pdfPage = page;
parentDoc = doc;
pdfAnnot->incRefCnt();
}
/* This method is called when a new annotation is created, after pdfAnnot and
* pdfPage have been set */
void AnnotationPrivate::flushBaseAnnotationProperties()
{
Q_ASSERT(pdfPage);
Annotation *q = makeAlias(); // Setters are defined in the public class
// Since pdfAnnot has been set, this calls will write in the Annot object
q->setAuthor(author);
q->setContents(contents);
q->setUniqueName(uniqueName);
q->setModificationDate(modDate);
q->setCreationDate(creationDate);
q->setFlags(flags);
// q->setBoundary(boundary); -- already set by subclass-specific code
q->setStyle(style);
q->setPopup(popup);
// Flush revisions
foreach (Annotation *r, revisions) {
// TODO: Flush revision
delete r; // Object is no longer needed
}
delete q;
// Clear some members to save memory
author.clear();
contents.clear();
uniqueName.clear();
revisions.clear();
}
// Returns matrix to convert from user space coords (oriented according to the
// specified rotation) to normalized coords
static void fillNormalizationMTX(::Page *pdfPage, double MTX[6], int pageRotation)
{
Q_ASSERT(pdfPage);
// build a normalized transform matrix for this page at 100% scale
GfxState *gfxState = new GfxState(72.0, 72.0, pdfPage->getCropBox(), pageRotation, true);
const double *gfxCTM = gfxState->getCTM();
double w = pdfPage->getCropWidth();
double h = pdfPage->getCropHeight();
// Swap width and height if the page is rotated landscape or seascape
if (pageRotation == 90 || pageRotation == 270) {
double t = w;
w = h;
h = t;
}
for (int i = 0; i < 6; i += 2) {
MTX[i] = gfxCTM[i] / w;
MTX[i + 1] = gfxCTM[i + 1] / h;
}
delete gfxState;
}
// Returns matrix to convert from user space coords (i.e. those that are stored
// in the PDF file) to normalized coords (i.e. those that we expose to clients).
// This method also applies a rotation around the top-left corner if the
// FixedRotation flag is set.
void AnnotationPrivate::fillTransformationMTX(double MTX[6]) const
{
Q_ASSERT(pdfPage);
Q_ASSERT(pdfAnnot);
const int pageRotate = pdfPage->getRotate();
if (pageRotate == 0 || (pdfAnnot->getFlags() & Annot::flagNoRotate) == 0) {
// Use the normalization matrix for this page's rotation
fillNormalizationMTX(pdfPage, MTX, pageRotate);
} else {
// Clients expect coordinates relative to this page's rotation, but
// FixedRotation annotations internally use unrotated coordinates:
// construct matrix to both normalize and rotate coordinates using the
// top-left corner as rotation pivot
double MTXnorm[6];
fillNormalizationMTX(pdfPage, MTXnorm, pageRotate);
QTransform transform(MTXnorm[0], MTXnorm[1], MTXnorm[2], MTXnorm[3], MTXnorm[4], MTXnorm[5]);
transform.translate(+pdfAnnot->getXMin(), +pdfAnnot->getYMax());
transform.rotate(pageRotate);
transform.translate(-pdfAnnot->getXMin(), -pdfAnnot->getYMax());
MTX[0] = transform.m11();
MTX[1] = transform.m12();
MTX[2] = transform.m21();
MTX[3] = transform.m22();
MTX[4] = transform.dx();
MTX[5] = transform.dy();
}
}
QRectF AnnotationPrivate::fromPdfRectangle(const PDFRectangle &r) const
{
double swp, MTX[6];
fillTransformationMTX(MTX);
QPointF p1, p2;
XPDFReader::transform(MTX, r.x1, r.y1, p1);
XPDFReader::transform(MTX, r.x2, r.y2, p2);
double tl_x = p1.x();
double tl_y = p1.y();
double br_x = p2.x();
double br_y = p2.y();
if (tl_x > br_x) {
swp = tl_x;
tl_x = br_x;
br_x = swp;
}
if (tl_y > br_y) {
swp = tl_y;
tl_y = br_y;
br_y = swp;
}
return QRectF(QPointF(tl_x, tl_y), QPointF(br_x, br_y));
}
// This function converts a boundary QRectF in normalized coords to a
// PDFRectangle in user coords. If the FixedRotation flag is set, this function
// also applies a rotation around the top-left corner: it's the inverse of
// the transformation produced by fillTransformationMTX, but we can't use
// fillTransformationMTX here because it relies on the native annotation
// object's boundary rect to be already set up.
PDFRectangle boundaryToPdfRectangle(::Page *pdfPage, const QRectF &r, int rFlags)
{
Q_ASSERT(pdfPage);
const int pageRotate = pdfPage->getRotate();
double MTX[6];
fillNormalizationMTX(pdfPage, MTX, pageRotate);
double tl_x, tl_y, br_x, br_y, swp;
XPDFReader::invTransform(MTX, r.topLeft(), tl_x, tl_y);
XPDFReader::invTransform(MTX, r.bottomRight(), br_x, br_y);
if (tl_x > br_x) {
swp = tl_x;
tl_x = br_x;
br_x = swp;
}
if (tl_y > br_y) {
swp = tl_y;
tl_y = br_y;
br_y = swp;
}
const int rotationFixUp = (rFlags & Annotation::FixedRotation) ? pageRotate : 0;
const double width = br_x - tl_x;
const double height = br_y - tl_y;
if (rotationFixUp == 0) {
return PDFRectangle(tl_x, tl_y, br_x, br_y);
} else if (rotationFixUp == 90) {
return PDFRectangle(tl_x, tl_y - width, tl_x + height, tl_y);
} else if (rotationFixUp == 180) {
return PDFRectangle(br_x, tl_y - height, br_x + width, tl_y);
} else { // rotationFixUp == 270
return PDFRectangle(br_x, br_y - width, br_x + height, br_y);
}
}
PDFRectangle AnnotationPrivate::boundaryToPdfRectangle(const QRectF &r, int rFlags) const
{
return Poppler::boundaryToPdfRectangle(pdfPage, r, rFlags);
}
AnnotPath *AnnotationPrivate::toAnnotPath(const QVector<QPointF> &list) const
{
const int count = list.size();
std::vector<AnnotCoord> ac;
ac.reserve(count);
double MTX[6];
fillTransformationMTX(MTX);
foreach (const QPointF &p, list) {
double x, y;
XPDFReader::invTransform(MTX, p, x, y);
ac.emplace_back(x, y);
}
return new AnnotPath(std::move(ac));
}
std::vector<std::unique_ptr<Annotation>> AnnotationPrivate::findAnnotations(::Page *pdfPage, DocumentData *doc, const QSet<Annotation::SubType> &subtypes, int parentID)
{
Annots *annots = pdfPage->getAnnots();
const bool wantTextAnnotations = subtypes.isEmpty() || subtypes.contains(Annotation::AText);
const bool wantLineAnnotations = subtypes.isEmpty() || subtypes.contains(Annotation::ALine);
const bool wantGeomAnnotations = subtypes.isEmpty() || subtypes.contains(Annotation::AGeom);
const bool wantHighlightAnnotations = subtypes.isEmpty() || subtypes.contains(Annotation::AHighlight);
const bool wantStampAnnotations = subtypes.isEmpty() || subtypes.contains(Annotation::AStamp);
const bool wantInkAnnotations = subtypes.isEmpty() || subtypes.contains(Annotation::AInk);
const bool wantLinkAnnotations = subtypes.isEmpty() || subtypes.contains(Annotation::ALink);
const bool wantCaretAnnotations = subtypes.isEmpty() || subtypes.contains(Annotation::ACaret);
const bool wantFileAttachmentAnnotations = subtypes.isEmpty() || subtypes.contains(Annotation::AFileAttachment);
const bool wantSoundAnnotations = subtypes.isEmpty() || subtypes.contains(Annotation::ASound);
const bool wantMovieAnnotations = subtypes.isEmpty() || subtypes.contains(Annotation::AMovie);
const bool wantScreenAnnotations = subtypes.isEmpty() || subtypes.contains(Annotation::AScreen);
const bool wantWidgetAnnotations = subtypes.isEmpty() || subtypes.contains(Annotation::AWidget);
// Create Annotation objects and tie to their native Annot
std::vector<std::unique_ptr<Annotation>> res;
for (Annot *ann : annots->getAnnots()) {
if (!ann) {
error(errInternal, -1, "Annot is null");
continue;
}
// Check parent annotation
AnnotMarkup *markupann = dynamic_cast<AnnotMarkup *>(ann);
if (!markupann) {
// Assume it's a root annotation, and skip if user didn't request it
if (parentID != -1) {
continue;
}
} else if (markupann->getInReplyToID() != parentID) {
continue;
}
/* Create Annotation of the right subclass */
std::unique_ptr<Annotation> annotation;
Annot::AnnotSubtype subType = ann->getType();
switch (subType) {
case Annot::typeText:
if (!wantTextAnnotations) {
continue;
}
annotation = std::make_unique<TextAnnotation>(TextAnnotation::Linked);
break;
case Annot::typeFreeText:
if (!wantTextAnnotations) {
continue;
}
annotation = std::make_unique<TextAnnotation>(TextAnnotation::InPlace);
break;
case Annot::typeLine:
if (!wantLineAnnotations) {
continue;
}
annotation = std::make_unique<LineAnnotation>(LineAnnotation::StraightLine);
break;
case Annot::typePolygon:
case Annot::typePolyLine:
if (!wantLineAnnotations) {
continue;
}
annotation = std::make_unique<LineAnnotation>(LineAnnotation::Polyline);
break;
case Annot::typeSquare:
case Annot::typeCircle:
if (!wantGeomAnnotations) {
continue;
}
annotation = std::make_unique<GeomAnnotation>();
break;
case Annot::typeHighlight:
case Annot::typeUnderline:
case Annot::typeSquiggly:
case Annot::typeStrikeOut:
if (!wantHighlightAnnotations) {
continue;
}
annotation = std::make_unique<HighlightAnnotation>();
break;
case Annot::typeStamp:
if (!wantStampAnnotations) {
continue;
}
annotation = std::make_unique<StampAnnotation>();
break;
case Annot::typeInk:
if (!wantInkAnnotations) {
continue;
}
annotation = std::make_unique<InkAnnotation>();
break;
case Annot::typeLink: /* TODO: Move logic to getters */
{
if (!wantLinkAnnotations) {
continue;
}
// parse Link params
AnnotLink *linkann = static_cast<AnnotLink *>(ann);
LinkAnnotation *l = new LinkAnnotation();
// -> hlMode
l->setLinkHighlightMode((LinkAnnotation::HighlightMode)linkann->getLinkEffect());
// -> link region
// TODO
// reading link action
if (linkann->getAction()) {
std::unique_ptr<Link> popplerLink = PageData::convertLinkActionToLink(linkann->getAction(), doc, QRectF());
if (popplerLink) {
l->setLinkDestination(std::move(popplerLink));
}
}
annotation.reset(l);
break;
}
case Annot::typeCaret:
if (!wantCaretAnnotations) {
continue;
}
annotation = std::make_unique<CaretAnnotation>();
break;
case Annot::typeFileAttachment: /* TODO: Move logic to getters */
{
if (!wantFileAttachmentAnnotations) {
continue;
}
AnnotFileAttachment *attachann = static_cast<AnnotFileAttachment *>(ann);
FileAttachmentAnnotation *f = new FileAttachmentAnnotation();
// -> fileIcon
f->setFileIconName(QString::fromLatin1(attachann->getName()->c_str()));
// -> embeddedFile
auto filespec = std::make_unique<FileSpec>(attachann->getFile());
f->setEmbeddedFile(new EmbeddedFile(*new EmbeddedFileData(std::move(filespec))));
annotation.reset(f);
break;
}
case Annot::typeSound: /* TODO: Move logic to getters */
{
if (!wantSoundAnnotations) {
continue;
}
AnnotSound *soundann = static_cast<AnnotSound *>(ann);
SoundAnnotation *s = new SoundAnnotation();
// -> soundIcon
s->setSoundIconName(QString::fromLatin1(soundann->getName()->c_str()));
// -> sound
s->setSound(new SoundObject(soundann->getSound()));
annotation.reset(s);
break;
}
case Annot::typeMovie: /* TODO: Move logic to getters */
{
if (!wantMovieAnnotations) {
continue;
}
AnnotMovie *movieann = static_cast<AnnotMovie *>(ann);
MovieAnnotation *m = new MovieAnnotation();
// -> movie
MovieObject *movie = new MovieObject(movieann);
m->setMovie(movie);
// -> movieTitle
const GooString *movietitle = movieann->getTitle();
if (movietitle) {
m->setMovieTitle(QString::fromLatin1(movietitle->c_str()));
}
annotation.reset(m);
break;
}
case Annot::typeScreen: {
if (!wantScreenAnnotations) {
continue;
}
AnnotScreen *screenann = static_cast<AnnotScreen *>(ann);
// TODO Support other link types than Link::Rendition in ScreenAnnotation
if (!screenann->getAction() || screenann->getAction()->getKind() != actionRendition) {
continue;
}
ScreenAnnotation *s = new ScreenAnnotation();
// -> screen
std::unique_ptr<Link> popplerLink = PageData::convertLinkActionToLink(screenann->getAction(), doc, QRectF());
s->setAction(static_cast<Poppler::LinkRendition *>(popplerLink.release()));
// -> screenTitle
const GooString *screentitle = screenann->getTitle();
if (screentitle) {
s->setScreenTitle(UnicodeParsedString(screentitle));
}
annotation.reset(s);
break;
}
case Annot::typePopup:
continue; // popups are parsed by Annotation's window() getter
case Annot::typeUnknown:
continue; // special case for ignoring unknown annotations
case Annot::typeWidget:
if (!wantWidgetAnnotations) {
continue;
}
annotation.reset(new WidgetAnnotation());
break;
case Annot::typeRichMedia: {
const AnnotRichMedia *annotRichMedia = static_cast<AnnotRichMedia *>(ann);
RichMediaAnnotation *richMediaAnnotation = new RichMediaAnnotation;
const AnnotRichMedia::Settings *annotSettings = annotRichMedia->getSettings();
if (annotSettings) {
RichMediaAnnotation::Settings *settings = new RichMediaAnnotation::Settings;
if (annotSettings->getActivation()) {
RichMediaAnnotation::Activation *activation = new RichMediaAnnotation::Activation;
switch (annotSettings->getActivation()->getCondition()) {
case AnnotRichMedia::Activation::conditionPageOpened:
activation->setCondition(RichMediaAnnotation::Activation::PageOpened);
break;
case AnnotRichMedia::Activation::conditionPageVisible:
activation->setCondition(RichMediaAnnotation::Activation::PageVisible);
break;
case AnnotRichMedia::Activation::conditionUserAction:
activation->setCondition(RichMediaAnnotation::Activation::UserAction);
break;
}
settings->setActivation(activation);
}
if (annotSettings->getDeactivation()) {
RichMediaAnnotation::Deactivation *deactivation = new RichMediaAnnotation::Deactivation;
switch (annotSettings->getDeactivation()->getCondition()) {
case AnnotRichMedia::Deactivation::conditionPageClosed:
deactivation->setCondition(RichMediaAnnotation::Deactivation::PageClosed);
break;
case AnnotRichMedia::Deactivation::conditionPageInvisible:
deactivation->setCondition(RichMediaAnnotation::Deactivation::PageInvisible);
break;
case AnnotRichMedia::Deactivation::conditionUserAction:
deactivation->setCondition(RichMediaAnnotation::Deactivation::UserAction);
break;
}
settings->setDeactivation(deactivation);
}
richMediaAnnotation->setSettings(settings);
}
const AnnotRichMedia::Content *annotContent = annotRichMedia->getContent();
if (annotContent) {
RichMediaAnnotation::Content *content = new RichMediaAnnotation::Content;
const int configurationsCount = annotContent->getConfigurationsCount();
if (configurationsCount > 0) {
QList<RichMediaAnnotation::Configuration *> configurations;
for (int i = 0; i < configurationsCount; ++i) {
const AnnotRichMedia::Configuration *annotConfiguration = annotContent->getConfiguration(i);
if (!annotConfiguration) {
continue;
}
RichMediaAnnotation::Configuration *configuration = new RichMediaAnnotation::Configuration;
if (annotConfiguration->getName()) {
configuration->setName(UnicodeParsedString(annotConfiguration->getName()));
}
switch (annotConfiguration->getType()) {
case AnnotRichMedia::Configuration::type3D:
configuration->setType(RichMediaAnnotation::Configuration::Type3D);
break;
case AnnotRichMedia::Configuration::typeFlash:
configuration->setType(RichMediaAnnotation::Configuration::TypeFlash);
break;
case AnnotRichMedia::Configuration::typeSound:
configuration->setType(RichMediaAnnotation::Configuration::TypeSound);
break;
case AnnotRichMedia::Configuration::typeVideo:
configuration->setType(RichMediaAnnotation::Configuration::TypeVideo);
break;
}
const int instancesCount = annotConfiguration->getInstancesCount();
if (instancesCount > 0) {
QList<RichMediaAnnotation::Instance *> instances;
for (int j = 0; j < instancesCount; ++j) {
const AnnotRichMedia::Instance *annotInstance = annotConfiguration->getInstance(j);
if (!annotInstance) {
continue;
}
RichMediaAnnotation::Instance *instance = new RichMediaAnnotation::Instance;
switch (annotInstance->getType()) {
case AnnotRichMedia::Instance::type3D:
instance->setType(RichMediaAnnotation::Instance::Type3D);
break;
case AnnotRichMedia::Instance::typeFlash:
instance->setType(RichMediaAnnotation::Instance::TypeFlash);
break;
case AnnotRichMedia::Instance::typeSound:
instance->setType(RichMediaAnnotation::Instance::TypeSound);
break;
case AnnotRichMedia::Instance::typeVideo:
instance->setType(RichMediaAnnotation::Instance::TypeVideo);
break;
}
const AnnotRichMedia::Params *annotParams = annotInstance->getParams();
if (annotParams) {
RichMediaAnnotation::Params *params = new RichMediaAnnotation::Params;
if (annotParams->getFlashVars()) {
params->setFlashVars(UnicodeParsedString(annotParams->getFlashVars()));
}
instance->setParams(params);
}
instances.append(instance);
}
configuration->setInstances(instances);
}
configurations.append(configuration);
}
content->setConfigurations(configurations);
}
const int assetsCount = annotContent->getAssetsCount();
if (assetsCount > 0) {
QList<RichMediaAnnotation::Asset *> assets;
for (int i = 0; i < assetsCount; ++i) {
const AnnotRichMedia::Asset *annotAsset = annotContent->getAsset(i);
if (!annotAsset) {
continue;
}
RichMediaAnnotation::Asset *asset = new RichMediaAnnotation::Asset;
if (annotAsset->getName()) {
asset->setName(UnicodeParsedString(annotAsset->getName()));
}
auto fileSpec = std::make_unique<FileSpec>(annotAsset->getFileSpec());
asset->setEmbeddedFile(new EmbeddedFile(*new EmbeddedFileData(std::move(fileSpec))));
assets.append(asset);
}
content->setAssets(assets);
}
richMediaAnnotation->setContent(content);
}
annotation.reset(richMediaAnnotation);
break;
}
default: {
#define CASE_FOR_TYPE(thetype) \
case Annot::type##thetype: \
error(errUnimplemented, -1, "Annotation " #thetype " not supported"); \
break;
switch (subType) {
CASE_FOR_TYPE(PrinterMark)
CASE_FOR_TYPE(TrapNet)
CASE_FOR_TYPE(Watermark)
CASE_FOR_TYPE(3D)
default:
error(errUnimplemented, -1, "Annotation {0:d} not supported", subType);
}
continue;
#undef CASE_FOR_TYPE
}
}
annotation->d_ptr->tieToNativeAnnot(ann, pdfPage, doc);
res.push_back(std::move(annotation));
}
return res;
}
Ref AnnotationPrivate::pdfObjectReference() const
{
if (pdfAnnot == nullptr) {
return Ref::INVALID();
}
return pdfAnnot->getRef();
}
std::unique_ptr<Link> AnnotationPrivate::additionalAction(Annotation::AdditionalActionType type) const
{
if (pdfAnnot->getType() != Annot::typeScreen && pdfAnnot->getType() != Annot::typeWidget) {
return {};
}
const Annot::AdditionalActionsType actionType = toPopplerAdditionalActionType(type);
std::unique_ptr<::LinkAction> linkAction;
if (pdfAnnot->getType() == Annot::typeScreen) {
linkAction = static_cast<AnnotScreen *>(pdfAnnot)->getAdditionalAction(actionType);
} else {
linkAction = static_cast<AnnotWidget *>(pdfAnnot)->getAdditionalAction(actionType);
}
if (linkAction) {
return PageData::convertLinkActionToLink(linkAction.get(), parentDoc, QRectF());
}
return {};
}
void AnnotationPrivate::addAnnotationToPage(::Page *pdfPage, DocumentData *doc, const Annotation *ann)
{
if (ann->d_ptr->pdfAnnot != nullptr) {
error(errIO, -1, "Annotation is already tied");
return;
}
// Unimplemented annotations can't be created by the user because their ctor
// is private. Therefore, createNativeAnnot will never return 0
Annot *nativeAnnot = ann->d_ptr->createNativeAnnot(pdfPage, doc);
Q_ASSERT(nativeAnnot);
if (ann->d_ptr->annotationAppearance.isStream()) {
nativeAnnot->setNewAppearance(ann->d_ptr->annotationAppearance.copy());
}
pdfPage->addAnnot(nativeAnnot);
}
void AnnotationPrivate::removeAnnotationFromPage(::Page *pdfPage, const Annotation *ann)
{
if (ann->d_ptr->pdfAnnot == nullptr) {
error(errIO, -1, "Annotation is not tied");
return;
}
if (ann->d_ptr->pdfPage != pdfPage) {
error(errIO, -1, "Annotation doesn't belong to the specified page");
return;
}
// Remove annotation
pdfPage->removeAnnot(ann->d_ptr->pdfAnnot);
// Destroy object
delete ann;
}
class TextAnnotationPrivate : public AnnotationPrivate
{
public:
TextAnnotationPrivate();
Annotation *makeAlias() override;
Annot *createNativeAnnot(::Page *destPage, DocumentData *doc) override;
void setDefaultAppearanceToNative();
std::unique_ptr<DefaultAppearance> getDefaultAppearanceFromNative() const;
// data fields
TextAnnotation::TextType textType;
QString textIcon;
std::optional<QFont> textFont;
QColor textColor = Qt::black;
TextAnnotation::InplaceAlignPosition inplaceAlign;
QVector<QPointF> inplaceCallout;
TextAnnotation::InplaceIntent inplaceIntent;
};
class Annotation::Style::Private : public QSharedData
{
public:
Private() : opacity(1.0), width(1.0), lineStyle(Solid), xCorners(0.0), yCorners(0.0), lineEffect(NoEffect), effectIntensity(1.0)
{
dashArray.resize(1);
dashArray[0] = 3;
}
QColor color;
double opacity;
double width;
Annotation::LineStyle lineStyle;
double xCorners;
double yCorners;
QVector<double> dashArray;
Annotation::LineEffect lineEffect;
double effectIntensity;
};
Annotation::Style::Style() : d(new Private) { }
Annotation::Style::Style(const Style &other) : d(other.d) { }
Annotation::Style &Annotation::Style::operator=(const Style &other)
{
if (this != &other) {
d = other.d;
}
return *this;
}
Annotation::Style::~Style() { }
QColor Annotation::Style::color() const
{
return d->color;
}
void Annotation::Style::setColor(const QColor &color)
{
d->color = color;
}
double Annotation::Style::opacity() const
{
return d->opacity;
}
void Annotation::Style::setOpacity(double opacity)
{
d->opacity = opacity;
}
double Annotation::Style::width() const
{
return d->width;
}
void Annotation::Style::setWidth(double width)
{
d->width = width;
}
Annotation::LineStyle Annotation::Style::lineStyle() const
{
return d->lineStyle;
}
void Annotation::Style::setLineStyle(Annotation::LineStyle style)
{
d->lineStyle = style;
}
double Annotation::Style::xCorners() const
{
return d->xCorners;
}
void Annotation::Style::setXCorners(double radius)
{
d->xCorners = radius;
}
double Annotation::Style::yCorners() const
{
return d->yCorners;
}
void Annotation::Style::setYCorners(double radius)
{
d->yCorners = radius;
}
const QVector<double> &Annotation::Style::dashArray() const
{
return d->dashArray;
}
void Annotation::Style::setDashArray(const QVector<double> &array)
{
d->dashArray = array;
}
Annotation::LineEffect Annotation::Style::lineEffect() const
{
return d->lineEffect;
}
void Annotation::Style::setLineEffect(Annotation::LineEffect effect)
{
d->lineEffect = effect;
}
double Annotation::Style::effectIntensity() const
{
return d->effectIntensity;
}
void Annotation::Style::setEffectIntensity(double intens)
{
d->effectIntensity = intens;
}
class Annotation::Popup::Private : public QSharedData
{
public:
Private() : flags(-1) { }
int flags;
QRectF geometry;
QString title;
QString summary;
QString text;
};
Annotation::Popup::Popup() : d(new Private) { }
Annotation::Popup::Popup(const Popup &other) : d(other.d) { }
Annotation::Popup &Annotation::Popup::operator=(const Popup &other)
{
if (this != &other) {
d = other.d;
}
return *this;
}
Annotation::Popup::~Popup() { }
int Annotation::Popup::flags() const
{
return d->flags;
}
void Annotation::Popup::setFlags(int flags)
{
d->flags = flags;
}
QRectF Annotation::Popup::geometry() const
{
return d->geometry;
}
void Annotation::Popup::setGeometry(const QRectF &geom)
{
d->geometry = geom;
}
QString Annotation::Popup::title() const
{
return d->title;
}
void Annotation::Popup::setTitle(const QString &title)
{
d->title = title;
}
QString Annotation::Popup::summary() const
{
return d->summary;
}
void Annotation::Popup::setSummary(const QString &summary)
{
d->summary = summary;
}
QString Annotation::Popup::text() const
{
return d->text;
}
void Annotation::Popup::setText(const QString &text)
{
d->text = text;
}
Annotation::Annotation(AnnotationPrivate &dd) : d_ptr(&dd) { }
Annotation::~Annotation() { }
QString Annotation::author() const
{
Q_D(const Annotation);
if (!d->pdfAnnot) {
return d->author;
}
const AnnotMarkup *markupann = dynamic_cast<const AnnotMarkup *>(d->pdfAnnot);
return markupann ? UnicodeParsedString(markupann->getLabel()) : QString();
}
void Annotation::setAuthor(const QString &author)
{
Q_D(Annotation);
if (!d->pdfAnnot) {
d->author = author;
return;
}
AnnotMarkup *markupann = dynamic_cast<AnnotMarkup *>(d->pdfAnnot);
if (markupann) {
markupann->setLabel(std::unique_ptr<GooString>(QStringToUnicodeGooString(author)));
}
}
QString Annotation::contents() const
{
Q_D(const Annotation);
if (!d->pdfAnnot) {
return d->contents;
}
return UnicodeParsedString(d->pdfAnnot->getContents());
}
void Annotation::setContents(const QString &contents)
{
Q_D(Annotation);
if (!d->pdfAnnot) {
d->contents = contents;
return;
}
d->pdfAnnot->setContents(std::unique_ptr<GooString>(QStringToUnicodeGooString(contents)));
TextAnnotationPrivate *textAnnotD = dynamic_cast<TextAnnotationPrivate *>(d);
if (textAnnotD) {
textAnnotD->setDefaultAppearanceToNative();
}
}
QString Annotation::uniqueName() const
{
Q_D(const Annotation);
if (!d->pdfAnnot) {
return d->uniqueName;
}
return UnicodeParsedString(d->pdfAnnot->getName());
}
void Annotation::setUniqueName(const QString &uniqueName)
{
Q_D(Annotation);
if (!d->pdfAnnot) {
d->uniqueName = uniqueName;
return;
}
QByteArray ascii = uniqueName.toLatin1();
GooString s(ascii.constData());
d->pdfAnnot->setName(&s);
}
QDateTime Annotation::modificationDate() const
{
Q_D(const Annotation);
if (!d->pdfAnnot) {
return d->modDate;
}
if (d->pdfAnnot->getModified()) {
return convertDate(d->pdfAnnot->getModified()->c_str());
} else {
return QDateTime();
}
}
void Annotation::setModificationDate(const QDateTime &date)
{
Q_D(Annotation);
if (!d->pdfAnnot) {
d->modDate = date;
return;
}
if (d->pdfAnnot) {
if (date.isValid()) {
const time_t t = date.toSecsSinceEpoch();
GooString *s = timeToDateString(&t);
d->pdfAnnot->setModified(s);
delete s;
} else {
d->pdfAnnot->setModified(nullptr);
}
}
}
QDateTime Annotation::creationDate() const
{
Q_D(const Annotation);
if (!d->pdfAnnot) {
return d->creationDate;
}
const AnnotMarkup *markupann = dynamic_cast<const AnnotMarkup *>(d->pdfAnnot);
if (markupann && markupann->getDate()) {
return convertDate(markupann->getDate()->c_str());
}
return modificationDate();
}
void Annotation::setCreationDate(const QDateTime &date)
{
Q_D(Annotation);
if (!d->pdfAnnot) {
d->creationDate = date;
return;
}
AnnotMarkup *markupann = dynamic_cast<AnnotMarkup *>(d->pdfAnnot);
if (markupann) {
if (date.isValid()) {
const time_t t = date.toSecsSinceEpoch();
GooString *s = timeToDateString(&t);
markupann->setDate(s);
delete s;
} else {
markupann->setDate(nullptr);
}
}
}
static Annotation::Flags fromPdfFlags(int flags)
{
Annotation::Flags qtflags;
if (flags & Annot::flagHidden) {
qtflags |= Annotation::Hidden;
}
if (flags & Annot::flagNoZoom) {
qtflags |= Annotation::FixedSize;
}
if (flags & Annot::flagNoRotate) {
qtflags |= Annotation::FixedRotation;
}
if (!(flags & Annot::flagPrint)) {
qtflags |= Annotation::DenyPrint;
}
if (flags & Annot::flagReadOnly) {
qtflags |= Annotation::DenyWrite;
qtflags |= Annotation::DenyDelete;
}
if (flags & Annot::flagLocked) {
qtflags |= Annotation::DenyDelete;
}
if (flags & Annot::flagToggleNoView) {
qtflags |= Annotation::ToggleHidingOnMouse;
}
return qtflags;
}
static int toPdfFlags(Annotation::Flags qtflags)
{
int flags = 0;
if (qtflags & Annotation::Hidden) {
flags |= Annot::flagHidden;
}
if (qtflags & Annotation::FixedSize) {
flags |= Annot::flagNoZoom;
}
if (qtflags & Annotation::FixedRotation) {
flags |= Annot::flagNoRotate;
}
if (!(qtflags & Annotation::DenyPrint)) {
flags |= Annot::flagPrint;
}
if (qtflags & Annotation::DenyWrite) {
flags |= Annot::flagReadOnly;
}
if (qtflags & Annotation::DenyDelete) {
flags |= Annot::flagLocked;
}
if (qtflags & Annotation::ToggleHidingOnMouse) {
flags |= Annot::flagToggleNoView;
}
return flags;
}
Annotation::Flags Annotation::flags() const
{
Q_D(const Annotation);
if (!d->pdfAnnot) {
return d->flags;
}
return fromPdfFlags(d->pdfAnnot->getFlags());
}
void Annotation::setFlags(Annotation::Flags flags)
{
Q_D(Annotation);
if (!d->pdfAnnot) {
d->flags = flags;
return;
}
d->pdfAnnot->setFlags(toPdfFlags(flags));
}
QRectF Annotation::boundary() const
{
Q_D(const Annotation);
if (!d->pdfAnnot) {
return d->boundary;
}
const PDFRectangle &rect = d->pdfAnnot->getRect();
return d->fromPdfRectangle(rect);
}
void Annotation::setBoundary(const QRectF &boundary)
{
Q_D(Annotation);
if (!d->pdfAnnot) {
d->boundary = boundary;
return;
}
const PDFRectangle rect = d->boundaryToPdfRectangle(boundary, flags());
if (rect == d->pdfAnnot->getRect()) {
return;
}
d->pdfAnnot->setRect(&rect);
}
Annotation::Style Annotation::style() const
{
Q_D(const Annotation);
if (!d->pdfAnnot) {
return d->style;
}
Style s;
s.setColor(convertAnnotColor(d->pdfAnnot->getColor()));
const AnnotMarkup *markupann = dynamic_cast<const AnnotMarkup *>(d->pdfAnnot);
if (markupann) {
s.setOpacity(markupann->getOpacity());
}
const AnnotBorder *border = d->pdfAnnot->getBorder();
if (border) {
if (border->getType() == AnnotBorder::typeArray) {
const AnnotBorderArray *border_array = static_cast<const AnnotBorderArray *>(border);
s.setXCorners(border_array->getHorizontalCorner());
s.setYCorners(border_array->getVerticalCorner());
}
s.setWidth(border->getWidth());
s.setLineStyle((Annotation::LineStyle)(1 << border->getStyle()));
const std::vector<double> &dashArray = border->getDash();
s.setDashArray(QVector<double>(dashArray.begin(), dashArray.end()));
}
AnnotBorderEffect *border_effect;
switch (d->pdfAnnot->getType()) {
case Annot::typeFreeText:
border_effect = static_cast<AnnotFreeText *>(d->pdfAnnot)->getBorderEffect();
break;
case Annot::typeSquare:
case Annot::typeCircle:
border_effect = static_cast<AnnotGeometry *>(d->pdfAnnot)->getBorderEffect();
break;
default:
border_effect = nullptr;
}
if (border_effect) {
s.setLineEffect((Annotation::LineEffect)border_effect->getEffectType());
s.setEffectIntensity(border_effect->getIntensity());
}
return s;
}
void Annotation::setStyle(const Annotation::Style &style)
{
Q_D(Annotation);
if (!d->pdfAnnot) {
d->style = style;
return;
}
d->pdfAnnot->setColor(convertQColor(style.color()));
AnnotMarkup *markupann = dynamic_cast<AnnotMarkup *>(d->pdfAnnot);
if (markupann) {
markupann->setOpacity(style.opacity());
}
auto border = std::make_unique<AnnotBorderArray>();
border->setWidth(style.width());
border->setHorizontalCorner(style.xCorners());
border->setVerticalCorner(style.yCorners());
d->pdfAnnot->setBorder(std::move(border));
}
Annotation::Popup Annotation::popup() const
{
Q_D(const Annotation);
if (!d->pdfAnnot) {
return d->popup;
}
Popup w;
AnnotPopup *popup = nullptr;
int flags = -1; // Not initialized
const AnnotMarkup *markupann = dynamic_cast<const AnnotMarkup *>(d->pdfAnnot);
if (markupann) {
popup = markupann->getPopup();
w.setSummary(UnicodeParsedString(markupann->getSubject()));
}
if (popup) {
flags = fromPdfFlags(popup->getFlags()) & (Annotation::Hidden | Annotation::FixedSize | Annotation::FixedRotation);
if (!popup->getOpen()) {
flags |= Annotation::Hidden;
}
const PDFRectangle &rect = popup->getRect();
w.setGeometry(d->fromPdfRectangle(rect));
}
if (d->pdfAnnot->getType() == Annot::typeText) {
const AnnotText *textann = static_cast<const AnnotText *>(d->pdfAnnot);
// Text annotations default to same rect as annotation
if (flags == -1) {
flags = 0;
w.setGeometry(boundary());
}
// If text is not 'opened', force window hiding. if the window
// was parsed from popup, the flag should already be set
if (!textann->getOpen() && flags != -1) {
flags |= Annotation::Hidden;
}
}
w.setFlags(flags);
return w;
}
void Annotation::setPopup(const Annotation::Popup &popup)
{
Q_D(Annotation);
if (!d->pdfAnnot) {
d->popup = popup;
return;
}
#if 0 /* TODO: Remove old popup and add AnnotPopup to page */
AnnotMarkup *markupann = dynamic_cast<AnnotMarkup*>(d->pdfAnnot);
if (!markupann)
return;
// Create a new AnnotPopup and assign it to pdfAnnot
PDFRectangle rect = d->toPdfRectangle( popup.geometry() );
AnnotPopup * p = new AnnotPopup( d->pdfPage->getDoc(), &rect );
p->setOpen( !(popup.flags() & Annotation::Hidden) );
if (!popup.summary().isEmpty())
{
GooString *s = QStringToUnicodeGooString(popup.summary());
markupann->setLabel(s);
delete s;
}
markupann->setPopup(p);
#endif
}
Annotation::RevScope Annotation::revisionScope() const
{
Q_D(const Annotation);
if (!d->pdfAnnot) {
return d->revisionScope;
}
const AnnotMarkup *markupann = dynamic_cast<const AnnotMarkup *>(d->pdfAnnot);
if (markupann && markupann->isInReplyTo()) {
switch (markupann->getReplyTo()) {
case AnnotMarkup::replyTypeR:
return Annotation::Reply;
case AnnotMarkup::replyTypeGroup:
return Annotation::Group;
}
}
return Annotation::Root; // It's not a revision
}
Annotation::RevType Annotation::revisionType() const
{
Q_D(const Annotation);
if (!d->pdfAnnot) {
return d->revisionType;
}
const AnnotText *textann = dynamic_cast<const AnnotText *>(d->pdfAnnot);
if (textann && textann->isInReplyTo()) {
switch (textann->getState()) {
case AnnotText::stateMarked:
return Annotation::Marked;
case AnnotText::stateUnmarked:
return Annotation::Unmarked;
case AnnotText::stateAccepted:
return Annotation::Accepted;
case AnnotText::stateRejected:
return Annotation::Rejected;
case AnnotText::stateCancelled:
return Annotation::Cancelled;
case AnnotText::stateCompleted:
return Annotation::Completed;
default:
break;
}
}
return Annotation::None;
}
std::vector<std::unique_ptr<Annotation>> Annotation::revisions() const
{
Q_D(const Annotation);
if (!d->pdfAnnot) {
/* Return aliases, whose ownership goes to the caller */
std::vector<std::unique_ptr<Annotation>> res;
foreach (Annotation *rev, d->revisions)
res.push_back(std::unique_ptr<Annotation>(rev->d_ptr->makeAlias()));
return res;
}
/* If the annotation doesn't live in a object on its own (eg bug51361), it
* has no ref, therefore it can't have revisions */
if (!d->pdfAnnot->getHasRef()) {
return std::vector<std::unique_ptr<Annotation>>();
}
return AnnotationPrivate::findAnnotations(d->pdfPage, d->parentDoc, QSet<Annotation::SubType>(), d->pdfAnnot->getId());
}
std::unique_ptr<AnnotationAppearance> Annotation::annotationAppearance() const
{
Q_D(const Annotation);
return std::make_unique<AnnotationAppearance>(new AnnotationAppearancePrivate(d->pdfAnnot));
}
void Annotation::setAnnotationAppearance(const AnnotationAppearance &annotationAppearance)
{
Q_D(Annotation);
if (!d->pdfAnnot) {
d->annotationAppearance = annotationAppearance.d->appearance.copy();
return;
}
// Moving the appearance object using std::move would result
// in the object being completed moved from the AnnotationAppearancePrivate
// class. So, we'll not be able to retrieve the stamp's original AP stream
d->pdfAnnot->setNewAppearance(annotationAppearance.d->appearance.copy());
}
// END Annotation implementation
/** TextAnnotation [Annotation] */
TextAnnotationPrivate::TextAnnotationPrivate() : AnnotationPrivate(), textType(TextAnnotation::Linked), textIcon(QStringLiteral("Note")), inplaceAlign(TextAnnotation::InplaceAlignLeft), inplaceIntent(TextAnnotation::Unknown) { }
Annotation *TextAnnotationPrivate::makeAlias()
{
return new TextAnnotation(*this);
}
Annot *TextAnnotationPrivate::createNativeAnnot(::Page *destPage, DocumentData *doc)
{
// Setters are defined in the public class
TextAnnotation *q = static_cast<TextAnnotation *>(makeAlias());
// Set page and contents
pdfPage = destPage;
parentDoc = doc;
// Set pdfAnnot
PDFRectangle rect = boundaryToPdfRectangle(boundary, flags);
if (textType == TextAnnotation::Linked) {
pdfAnnot = new AnnotText { destPage->getDoc(), &rect };
} else {
const double pointSize = textFont ? textFont->pointSizeF() : AnnotFreeText::undefinedFontPtSize;
if (pointSize < 0) {
qWarning() << "TextAnnotationPrivate::createNativeAnnot: font pointSize < 0";
}
pdfAnnot = new AnnotFreeText { destPage->getDoc(), &rect };
}
// Set properties
flushBaseAnnotationProperties();
q->setTextIcon(textIcon);
q->setInplaceAlign(inplaceAlign);
q->setCalloutPoints(inplaceCallout);
q->setInplaceIntent(inplaceIntent);
delete q;
inplaceCallout.clear(); // Free up memory
setDefaultAppearanceToNative();
return pdfAnnot;
}
void TextAnnotationPrivate::setDefaultAppearanceToNative()
{
if (pdfAnnot && pdfAnnot->getType() == Annot::typeFreeText) {
AnnotFreeText *ftextann = static_cast<AnnotFreeText *>(pdfAnnot);
const double pointSize = textFont ? textFont->pointSizeF() : AnnotFreeText::undefinedFontPtSize;
if (pointSize < 0) {
qWarning() << "TextAnnotationPrivate::createNativeAnnot: font pointSize < 0";
}
std::string fontName = "Invalid_font";
if (textFont) {
Form *form = pdfPage->getDoc()->getCatalog()->getCreateForm();
if (form) {
fontName = form->findFontInDefaultResources(textFont->family().toStdString(), textFont->styleName().toStdString());
if (fontName.empty()) {
fontName = form->addFontToDefaultResources(textFont->family().toStdString(), textFont->styleName().toStdString()).fontName;
}
if (!fontName.empty()) {
form->ensureFontsForAllCharacters(pdfAnnot->getContents(), fontName);
} else {
fontName = "Invalid_font";
}
}
}
DefaultAppearance da { { objName, fontName.c_str() }, pointSize, convertQColor(textColor) };
ftextann->setDefaultAppearance(da);
}
}
std::unique_ptr<DefaultAppearance> TextAnnotationPrivate::getDefaultAppearanceFromNative() const
{
if (pdfAnnot && pdfAnnot->getType() == Annot::typeFreeText) {
AnnotFreeText *ftextann = static_cast<AnnotFreeText *>(pdfAnnot);
return ftextann->getDefaultAppearance();
} else {
return {};
}
}
TextAnnotation::TextAnnotation(TextAnnotation::TextType type) : Annotation(*new TextAnnotationPrivate())
{
setTextType(type);
}
TextAnnotation::TextAnnotation(TextAnnotationPrivate &dd) : Annotation(dd) { }
TextAnnotation::~TextAnnotation() { }
Annotation::SubType TextAnnotation::subType() const
{
return AText;
}
TextAnnotation::TextType TextAnnotation::textType() const
{
Q_D(const TextAnnotation);
if (!d->pdfAnnot) {
return d->textType;
}
return d->pdfAnnot->getType() == Annot::typeText ? TextAnnotation::Linked : TextAnnotation::InPlace;
}
void TextAnnotation::setTextType(TextAnnotation::TextType type)
{
Q_D(TextAnnotation);
if (!d->pdfAnnot) {
d->textType = type;
return;
}
// Type cannot be changed if annotation is already tied
qWarning() << "You can't change the type of a TextAnnotation that is already in a page";
}
QString TextAnnotation::textIcon() const
{
Q_D(const TextAnnotation);
if (!d->pdfAnnot) {
return d->textIcon;
}
if (d->pdfAnnot->getType() == Annot::typeText) {
const AnnotText *textann = static_cast<const AnnotText *>(d->pdfAnnot);
return QString::fromLatin1(textann->getIcon()->c_str());
}
return QString();
}
void TextAnnotation::setTextIcon(const QString &icon)
{
Q_D(TextAnnotation);
if (!d->pdfAnnot) {
d->textIcon = icon;
return;
}
if (d->pdfAnnot->getType() == Annot::typeText) {
AnnotText *textann = static_cast<AnnotText *>(d->pdfAnnot);
QByteArray encoded = icon.toLatin1();
GooString s(encoded.constData());
textann->setIcon(&s);
}
}
QFont TextAnnotation::textFont() const
{
Q_D(const TextAnnotation);
if (d->textFont) {
return *d->textFont;
}
double fontSize { AnnotFreeText::undefinedFontPtSize };
if (d->pdfAnnot->getType() == Annot::typeFreeText) {
std::unique_ptr<DefaultAppearance> da { d->getDefaultAppearanceFromNative() };
if (da && da->getFontPtSize() > 0) {
fontSize = da->getFontPtSize();
}
}
QFont font;
font.setPointSizeF(fontSize);
return font;
}
void TextAnnotation::setTextFont(const QFont &font)
{
Q_D(TextAnnotation);
if (font == d->textFont) {
return;
}
d->textFont = font;
d->setDefaultAppearanceToNative();
}
QColor TextAnnotation::textColor() const
{
Q_D(const TextAnnotation);
if (!d->pdfAnnot) {
return d->textColor;
}
if (std::unique_ptr<DefaultAppearance> da { d->getDefaultAppearanceFromNative() }) {
return convertAnnotColor(da->getFontColor());
}
return {};
}
void TextAnnotation::setTextColor(const QColor &color)
{
Q_D(TextAnnotation);
if (color == d->textColor) {
return;
}
d->textColor = color;
d->setDefaultAppearanceToNative();
}
TextAnnotation::InplaceAlignPosition TextAnnotation::inplaceAlign() const
{
Q_D(const TextAnnotation);
if (!d->pdfAnnot) {
return d->inplaceAlign;
}
if (d->pdfAnnot->getType() == Annot::typeFreeText) {
const AnnotFreeText *ftextann = static_cast<const AnnotFreeText *>(d->pdfAnnot);
switch (ftextann->getQuadding()) {
case VariableTextQuadding::leftJustified:
return InplaceAlignLeft;
case VariableTextQuadding::centered:
return InplaceAlignCenter;
case VariableTextQuadding::rightJustified:
return InplaceAlignRight;
}
}
return InplaceAlignLeft;
}
static VariableTextQuadding alignToQuadding(TextAnnotation::InplaceAlignPosition align)
{
switch (align) {
case TextAnnotation::InplaceAlignLeft:
return VariableTextQuadding::leftJustified;
case TextAnnotation::InplaceAlignCenter:
return VariableTextQuadding::centered;
case TextAnnotation::InplaceAlignRight:
return VariableTextQuadding::rightJustified;
}
return VariableTextQuadding::leftJustified;
}
void TextAnnotation::setInplaceAlign(InplaceAlignPosition align)
{
Q_D(TextAnnotation);
if (!d->pdfAnnot) {
d->inplaceAlign = align;
return;
}
if (d->pdfAnnot->getType() == Annot::typeFreeText) {
AnnotFreeText *ftextann = static_cast<AnnotFreeText *>(d->pdfAnnot);
ftextann->setQuadding(alignToQuadding(align));
}
}
QPointF TextAnnotation::calloutPoint(int id) const
{
const QVector<QPointF> points = calloutPoints();
if (id < 0 || id >= points.size()) {
return QPointF();
} else {
return points[id];
}
}
QVector<QPointF> TextAnnotation::calloutPoints() const
{
Q_D(const TextAnnotation);
if (!d->pdfAnnot) {
return d->inplaceCallout;
}
if (d->pdfAnnot->getType() == Annot::typeText) {
return QVector<QPointF>();
}
const AnnotFreeText *ftextann = static_cast<const AnnotFreeText *>(d->pdfAnnot);
const AnnotCalloutLine *callout = ftextann->getCalloutLine();
if (!callout) {
return QVector<QPointF>();
}
double MTX[6];
d->fillTransformationMTX(MTX);
const AnnotCalloutMultiLine *callout_v6 = dynamic_cast<const AnnotCalloutMultiLine *>(callout);
QVector<QPointF> res(callout_v6 ? 3 : 2);
XPDFReader::transform(MTX, callout->getX1(), callout->getY1(), res[0]);
XPDFReader::transform(MTX, callout->getX2(), callout->getY2(), res[1]);
if (callout_v6) {
XPDFReader::transform(MTX, callout_v6->getX3(), callout_v6->getY3(), res[2]);
}
return res;
}
void TextAnnotation::setCalloutPoints(const QVector<QPointF> &points)
{
Q_D(TextAnnotation);
if (!d->pdfAnnot) {
d->inplaceCallout = points;
return;
}
if (d->pdfAnnot->getType() != Annot::typeFreeText) {
return;
}
AnnotFreeText *ftextann = static_cast<AnnotFreeText *>(d->pdfAnnot);
const int count = points.size();
if (count == 0) {
ftextann->setCalloutLine(nullptr);
return;
}
if (count != 2 && count != 3) {
error(errSyntaxError, -1, "Expected zero, two or three points for callout");
return;
}
AnnotCalloutLine *callout;
double x1, y1, x2, y2;
double MTX[6];
d->fillTransformationMTX(MTX);
XPDFReader::invTransform(MTX, points[0], x1, y1);
XPDFReader::invTransform(MTX, points[1], x2, y2);
if (count == 3) {
double x3, y3;
XPDFReader::invTransform(MTX, points[2], x3, y3);
callout = new AnnotCalloutMultiLine(x1, y1, x2, y2, x3, y3);
} else {
callout = new AnnotCalloutLine(x1, y1, x2, y2);
}
ftextann->setCalloutLine(callout);
delete callout;
}
TextAnnotation::InplaceIntent TextAnnotation::inplaceIntent() const
{
Q_D(const TextAnnotation);
if (!d->pdfAnnot) {
return d->inplaceIntent;
}
if (d->pdfAnnot->getType() == Annot::typeFreeText) {
const AnnotFreeText *ftextann = static_cast<const AnnotFreeText *>(d->pdfAnnot);
return (TextAnnotation::InplaceIntent)ftextann->getIntent();
}
return TextAnnotation::Unknown;
}
void TextAnnotation::setInplaceIntent(TextAnnotation::InplaceIntent intent)
{
Q_D(TextAnnotation);
if (!d->pdfAnnot) {
d->inplaceIntent = intent;
return;
}
if (d->pdfAnnot->getType() == Annot::typeFreeText) {
AnnotFreeText *ftextann = static_cast<AnnotFreeText *>(d->pdfAnnot);
ftextann->setIntent((AnnotFreeText::AnnotFreeTextIntent)intent);
}
}
/** LineAnnotation [Annotation] */
class LineAnnotationPrivate : public AnnotationPrivate
{
public:
LineAnnotationPrivate();
Annotation *makeAlias() override;
Annot *createNativeAnnot(::Page *destPage, DocumentData *doc) override;
// data fields (note uses border for rendering style)
QVector<QPointF> linePoints;
LineAnnotation::TermStyle lineStartStyle;
LineAnnotation::TermStyle lineEndStyle;
bool lineClosed : 1; // (if true draw close shape)
bool lineShowCaption : 1;
LineAnnotation::LineType lineType;
QColor lineInnerColor;
double lineLeadingFwdPt;
double lineLeadingBackPt;
LineAnnotation::LineIntent lineIntent;
};
LineAnnotationPrivate::LineAnnotationPrivate()
: AnnotationPrivate(), lineStartStyle(LineAnnotation::None), lineEndStyle(LineAnnotation::None), lineClosed(false), lineShowCaption(false), lineLeadingFwdPt(0), lineLeadingBackPt(0), lineIntent(LineAnnotation::Unknown)
{
}
Annotation *LineAnnotationPrivate::makeAlias()
{
return new LineAnnotation(*this);
}
Annot *LineAnnotationPrivate::createNativeAnnot(::Page *destPage, DocumentData *doc)
{
// Setters are defined in the public class
LineAnnotation *q = static_cast<LineAnnotation *>(makeAlias());
// Set page and document
pdfPage = destPage;
parentDoc = doc;
// Set pdfAnnot
PDFRectangle rect = boundaryToPdfRectangle(boundary, flags);
if (lineType == LineAnnotation::StraightLine) {
pdfAnnot = new AnnotLine(doc->doc, &rect);
} else {
pdfAnnot = new AnnotPolygon(doc->doc, &rect, lineClosed ? Annot::typePolygon : Annot::typePolyLine);
}
// Set properties
flushBaseAnnotationProperties();
q->setLinePoints(linePoints);
q->setLineStartStyle(lineStartStyle);
q->setLineEndStyle(lineEndStyle);
q->setLineInnerColor(lineInnerColor);
q->setLineLeadingForwardPoint(lineLeadingFwdPt);
q->setLineLeadingBackPoint(lineLeadingBackPt);
q->setLineShowCaption(lineShowCaption);
q->setLineIntent(lineIntent);
delete q;
linePoints.clear(); // Free up memory
return pdfAnnot;
}
LineAnnotation::LineAnnotation(LineAnnotation::LineType type) : Annotation(*new LineAnnotationPrivate())
{
setLineType(type);
}
LineAnnotation::LineAnnotation(LineAnnotationPrivate &dd) : Annotation(dd) { }
LineAnnotation::~LineAnnotation() { }
Annotation::SubType LineAnnotation::subType() const
{
return ALine;
}
LineAnnotation::LineType LineAnnotation::lineType() const
{
Q_D(const LineAnnotation);
if (!d->pdfAnnot) {
return d->lineType;
}
return (d->pdfAnnot->getType() == Annot::typeLine) ? LineAnnotation::StraightLine : LineAnnotation::Polyline;
}
void LineAnnotation::setLineType(LineAnnotation::LineType type)
{
Q_D(LineAnnotation);
if (!d->pdfAnnot) {
d->lineType = type;
return;
}
// Type cannot be changed if annotation is already tied
qWarning() << "You can't change the type of a LineAnnotation that is already in a page";
}
QVector<QPointF> LineAnnotation::linePoints() const
{
Q_D(const LineAnnotation);
if (!d->pdfAnnot) {
return d->linePoints;
}
double MTX[6];
d->fillTransformationMTX(MTX);
QVector<QPointF> res;
if (d->pdfAnnot->getType() == Annot::typeLine) {
const AnnotLine *lineann = static_cast<const AnnotLine *>(d->pdfAnnot);
QPointF p;
XPDFReader::transform(MTX, lineann->getX1(), lineann->getY1(), p);
res.append(p);
XPDFReader::transform(MTX, lineann->getX2(), lineann->getY2(), p);
res.append(p);
} else {
const AnnotPolygon *polyann = static_cast<const AnnotPolygon *>(d->pdfAnnot);
const AnnotPath *vertices = polyann->getVertices();
for (int i = 0; i < vertices->getCoordsLength(); ++i) {
QPointF p;
XPDFReader::transform(MTX, vertices->getX(i), vertices->getY(i), p);
res.append(p);
}
}
return res;
}
void LineAnnotation::setLinePoints(const QVector<QPointF> &points)
{
Q_D(LineAnnotation);
if (!d->pdfAnnot) {
d->linePoints = points;
return;
}
if (d->pdfAnnot->getType() == Annot::typeLine) {
AnnotLine *lineann = static_cast<AnnotLine *>(d->pdfAnnot);
if (points.size() != 2) {
error(errSyntaxError, -1, "Expected two points for a straight line");
return;
}
double x1, y1, x2, y2;
double MTX[6];
d->fillTransformationMTX(MTX);
XPDFReader::invTransform(MTX, points.first(), x1, y1);
XPDFReader::invTransform(MTX, points.last(), x2, y2);
lineann->setVertices(x1, y1, x2, y2);
} else {
AnnotPolygon *polyann = static_cast<AnnotPolygon *>(d->pdfAnnot);
AnnotPath *p = d->toAnnotPath(points);
polyann->setVertices(p);
delete p;
}
}
LineAnnotation::TermStyle LineAnnotation::lineStartStyle() const
{
Q_D(const LineAnnotation);
if (!d->pdfAnnot) {
return d->lineStartStyle;
}
if (d->pdfAnnot->getType() == Annot::typeLine) {
const AnnotLine *lineann = static_cast<const AnnotLine *>(d->pdfAnnot);
return (LineAnnotation::TermStyle)lineann->getStartStyle();
} else {
const AnnotPolygon *polyann = static_cast<const AnnotPolygon *>(d->pdfAnnot);
return (LineAnnotation::TermStyle)polyann->getStartStyle();
}
}
void LineAnnotation::setLineStartStyle(LineAnnotation::TermStyle style)
{
Q_D(LineAnnotation);
if (!d->pdfAnnot) {
d->lineStartStyle = style;
return;
}
if (d->pdfAnnot->getType() == Annot::typeLine) {
AnnotLine *lineann = static_cast<AnnotLine *>(d->pdfAnnot);
lineann->setStartEndStyle((AnnotLineEndingStyle)style, lineann->getEndStyle());
} else {
AnnotPolygon *polyann = static_cast<AnnotPolygon *>(d->pdfAnnot);
polyann->setStartEndStyle((AnnotLineEndingStyle)style, polyann->getEndStyle());
}
}
LineAnnotation::TermStyle LineAnnotation::lineEndStyle() const
{
Q_D(const LineAnnotation);
if (!d->pdfAnnot) {
return d->lineEndStyle;
}
if (d->pdfAnnot->getType() == Annot::typeLine) {
const AnnotLine *lineann = static_cast<const AnnotLine *>(d->pdfAnnot);
return (LineAnnotation::TermStyle)lineann->getEndStyle();
} else {
const AnnotPolygon *polyann = static_cast<const AnnotPolygon *>(d->pdfAnnot);
return (LineAnnotation::TermStyle)polyann->getEndStyle();
}
}
void LineAnnotation::setLineEndStyle(LineAnnotation::TermStyle style)
{
Q_D(LineAnnotation);
if (!d->pdfAnnot) {
d->lineEndStyle = style;
return;
}
if (d->pdfAnnot->getType() == Annot::typeLine) {
AnnotLine *lineann = static_cast<AnnotLine *>(d->pdfAnnot);
lineann->setStartEndStyle(lineann->getStartStyle(), (AnnotLineEndingStyle)style);
} else {
AnnotPolygon *polyann = static_cast<AnnotPolygon *>(d->pdfAnnot);
polyann->setStartEndStyle(polyann->getStartStyle(), (AnnotLineEndingStyle)style);
}
}
bool LineAnnotation::isLineClosed() const
{
Q_D(const LineAnnotation);
if (!d->pdfAnnot) {
return d->lineClosed;
}
return d->pdfAnnot->getType() == Annot::typePolygon;
}
void LineAnnotation::setLineClosed(bool closed)
{
Q_D(LineAnnotation);
if (!d->pdfAnnot) {
d->lineClosed = closed;
return;
}
if (d->pdfAnnot->getType() != Annot::typeLine) {
AnnotPolygon *polyann = static_cast<AnnotPolygon *>(d->pdfAnnot);
// Set new subtype and switch intent if necessary
if (closed) {
polyann->setType(Annot::typePolygon);
if (polyann->getIntent() == AnnotPolygon::polylineDimension) {
polyann->setIntent(AnnotPolygon::polygonDimension);
}
} else {
polyann->setType(Annot::typePolyLine);
if (polyann->getIntent() == AnnotPolygon::polygonDimension) {
polyann->setIntent(AnnotPolygon::polylineDimension);
}
}
}
}
QColor LineAnnotation::lineInnerColor() const
{
Q_D(const LineAnnotation);
if (!d->pdfAnnot) {
return d->lineInnerColor;
}
AnnotColor *c;
if (d->pdfAnnot->getType() == Annot::typeLine) {
const AnnotLine *lineann = static_cast<const AnnotLine *>(d->pdfAnnot);
c = lineann->getInteriorColor();
} else {
const AnnotPolygon *polyann = static_cast<const AnnotPolygon *>(d->pdfAnnot);
c = polyann->getInteriorColor();
}
return convertAnnotColor(c);
}
void LineAnnotation::setLineInnerColor(const QColor &color)
{
Q_D(LineAnnotation);
if (!d->pdfAnnot) {
d->lineInnerColor = color;
return;
}
auto c = convertQColor(color);
if (d->pdfAnnot->getType() == Annot::typeLine) {
AnnotLine *lineann = static_cast<AnnotLine *>(d->pdfAnnot);
lineann->setInteriorColor(std::move(c));
} else {
AnnotPolygon *polyann = static_cast<AnnotPolygon *>(d->pdfAnnot);
polyann->setInteriorColor(std::move(c));
}
}
double LineAnnotation::lineLeadingForwardPoint() const
{
Q_D(const LineAnnotation);
if (!d->pdfAnnot) {
return d->lineLeadingFwdPt;
}
if (d->pdfAnnot->getType() == Annot::typeLine) {
const AnnotLine *lineann = static_cast<const AnnotLine *>(d->pdfAnnot);
return lineann->getLeaderLineLength();
}
return 0;
}
void LineAnnotation::setLineLeadingForwardPoint(double point)
{
Q_D(LineAnnotation);
if (!d->pdfAnnot) {
d->lineLeadingFwdPt = point;
return;
}
if (d->pdfAnnot->getType() == Annot::typeLine) {
AnnotLine *lineann = static_cast<AnnotLine *>(d->pdfAnnot);
lineann->setLeaderLineLength(point);
}
}
double LineAnnotation::lineLeadingBackPoint() const
{
Q_D(const LineAnnotation);
if (!d->pdfAnnot) {
return d->lineLeadingBackPt;
}
if (d->pdfAnnot->getType() == Annot::typeLine) {
const AnnotLine *lineann = static_cast<const AnnotLine *>(d->pdfAnnot);
return lineann->getLeaderLineExtension();
}
return 0;
}
void LineAnnotation::setLineLeadingBackPoint(double point)
{
Q_D(LineAnnotation);
if (!d->pdfAnnot) {
d->lineLeadingBackPt = point;
return;
}
if (d->pdfAnnot->getType() == Annot::typeLine) {
AnnotLine *lineann = static_cast<AnnotLine *>(d->pdfAnnot);
lineann->setLeaderLineExtension(point);
}
}
bool LineAnnotation::lineShowCaption() const
{
Q_D(const LineAnnotation);
if (!d->pdfAnnot) {
return d->lineShowCaption;
}
if (d->pdfAnnot->getType() == Annot::typeLine) {
const AnnotLine *lineann = static_cast<const AnnotLine *>(d->pdfAnnot);
return lineann->getCaption();
}
return false;
}
void LineAnnotation::setLineShowCaption(bool show)
{
Q_D(LineAnnotation);
if (!d->pdfAnnot) {
d->lineShowCaption = show;
return;
}
if (d->pdfAnnot->getType() == Annot::typeLine) {
AnnotLine *lineann = static_cast<AnnotLine *>(d->pdfAnnot);
lineann->setCaption(show);
}
}
LineAnnotation::LineIntent LineAnnotation::lineIntent() const
{
Q_D(const LineAnnotation);
if (!d->pdfAnnot) {
return d->lineIntent;
}
if (d->pdfAnnot->getType() == Annot::typeLine) {
const AnnotLine *lineann = static_cast<const AnnotLine *>(d->pdfAnnot);
return (LineAnnotation::LineIntent)(lineann->getIntent() + 1);
} else {
const AnnotPolygon *polyann = static_cast<const AnnotPolygon *>(d->pdfAnnot);
if (polyann->getIntent() == AnnotPolygon::polygonCloud) {
return LineAnnotation::PolygonCloud;
} else { // AnnotPolygon::polylineDimension, AnnotPolygon::polygonDimension
return LineAnnotation::Dimension;
}
}
}
void LineAnnotation::setLineIntent(LineAnnotation::LineIntent intent)
{
Q_D(LineAnnotation);
if (!d->pdfAnnot) {
d->lineIntent = intent;
return;
}
if (intent == LineAnnotation::Unknown) {
return; // Do not set (actually, it should clear the property)
}
if (d->pdfAnnot->getType() == Annot::typeLine) {
AnnotLine *lineann = static_cast<AnnotLine *>(d->pdfAnnot);
lineann->setIntent((AnnotLine::AnnotLineIntent)(intent - 1));
} else {
AnnotPolygon *polyann = static_cast<AnnotPolygon *>(d->pdfAnnot);
if (intent == LineAnnotation::PolygonCloud) {
polyann->setIntent(AnnotPolygon::polygonCloud);
} else // LineAnnotation::Dimension
{
if (d->pdfAnnot->getType() == Annot::typePolygon) {
polyann->setIntent(AnnotPolygon::polygonDimension);
} else { // Annot::typePolyLine
polyann->setIntent(AnnotPolygon::polylineDimension);
}
}
}
}
/** GeomAnnotation [Annotation] */
class GeomAnnotationPrivate : public AnnotationPrivate
{
public:
GeomAnnotationPrivate();
Annotation *makeAlias() override;
Annot *createNativeAnnot(::Page *destPage, DocumentData *doc) override;
// data fields (note uses border for rendering style)
GeomAnnotation::GeomType geomType;
QColor geomInnerColor;
};
GeomAnnotationPrivate::GeomAnnotationPrivate() : AnnotationPrivate(), geomType(GeomAnnotation::InscribedSquare) { }
Annotation *GeomAnnotationPrivate::makeAlias()
{
return new GeomAnnotation(*this);
}
Annot *GeomAnnotationPrivate::createNativeAnnot(::Page *destPage, DocumentData *doc)
{
// Setters are defined in the public class
GeomAnnotation *q = static_cast<GeomAnnotation *>(makeAlias());
// Set page and document
pdfPage = destPage;
parentDoc = doc;
Annot::AnnotSubtype type;
if (geomType == GeomAnnotation::InscribedSquare) {
type = Annot::typeSquare;
} else { // GeomAnnotation::InscribedCircle
type = Annot::typeCircle;
}
// Set pdfAnnot
PDFRectangle rect = boundaryToPdfRectangle(boundary, flags);
pdfAnnot = new AnnotGeometry(destPage->getDoc(), &rect, type);
// Set properties
flushBaseAnnotationProperties();
q->setGeomInnerColor(geomInnerColor);
delete q;
return pdfAnnot;
}
GeomAnnotation::GeomAnnotation() : Annotation(*new GeomAnnotationPrivate()) { }
GeomAnnotation::GeomAnnotation(GeomAnnotationPrivate &dd) : Annotation(dd) { }
GeomAnnotation::~GeomAnnotation() { }
Annotation::SubType GeomAnnotation::subType() const
{
return AGeom;
}
GeomAnnotation::GeomType GeomAnnotation::geomType() const
{
Q_D(const GeomAnnotation);
if (!d->pdfAnnot) {
return d->geomType;
}
if (d->pdfAnnot->getType() == Annot::typeSquare) {
return GeomAnnotation::InscribedSquare;
} else { // Annot::typeCircle
return GeomAnnotation::InscribedCircle;
}
}
void GeomAnnotation::setGeomType(GeomAnnotation::GeomType type)
{
Q_D(GeomAnnotation);
if (!d->pdfAnnot) {
d->geomType = type;
return;
}
AnnotGeometry *geomann = static_cast<AnnotGeometry *>(d->pdfAnnot);
if (type == GeomAnnotation::InscribedSquare) {
geomann->setType(Annot::typeSquare);
} else { // GeomAnnotation::InscribedCircle
geomann->setType(Annot::typeCircle);
}
}
QColor GeomAnnotation::geomInnerColor() const
{
Q_D(const GeomAnnotation);
if (!d->pdfAnnot) {
return d->geomInnerColor;
}
const AnnotGeometry *geomann = static_cast<const AnnotGeometry *>(d->pdfAnnot);
return convertAnnotColor(geomann->getInteriorColor());
}
void GeomAnnotation::setGeomInnerColor(const QColor &color)
{
Q_D(GeomAnnotation);
if (!d->pdfAnnot) {
d->geomInnerColor = color;
return;
}
AnnotGeometry *geomann = static_cast<AnnotGeometry *>(d->pdfAnnot);
geomann->setInteriorColor(convertQColor(color));
}
/** HighlightAnnotation [Annotation] */
class HighlightAnnotationPrivate : public AnnotationPrivate
{
public:
HighlightAnnotationPrivate();
Annotation *makeAlias() override;
Annot *createNativeAnnot(::Page *destPage, DocumentData *doc) override;
// data fields
HighlightAnnotation::HighlightType highlightType;
QList<HighlightAnnotation::Quad> highlightQuads; // not empty
// helpers
static Annot::AnnotSubtype toAnnotSubType(HighlightAnnotation::HighlightType type);
QList<HighlightAnnotation::Quad> fromQuadrilaterals(AnnotQuadrilaterals *quads) const;
AnnotQuadrilaterals *toQuadrilaterals(const QList<HighlightAnnotation::Quad> &quads) const;
};
HighlightAnnotationPrivate::HighlightAnnotationPrivate() : AnnotationPrivate(), highlightType(HighlightAnnotation::Highlight) { }
Annotation *HighlightAnnotationPrivate::makeAlias()
{
return new HighlightAnnotation(*this);
}
Annot::AnnotSubtype HighlightAnnotationPrivate::toAnnotSubType(HighlightAnnotation::HighlightType type)
{
switch (type) {
default: // HighlightAnnotation::Highlight:
return Annot::typeHighlight;
case HighlightAnnotation::Underline:
return Annot::typeUnderline;
case HighlightAnnotation::Squiggly:
return Annot::typeSquiggly;
case HighlightAnnotation::StrikeOut:
return Annot::typeStrikeOut;
}
}
QList<HighlightAnnotation::Quad> HighlightAnnotationPrivate::fromQuadrilaterals(AnnotQuadrilaterals *hlquads) const
{
QList<HighlightAnnotation::Quad> quads;
if (!hlquads || !hlquads->getQuadrilateralsLength()) {
return quads;
}
const int quadsCount = hlquads->getQuadrilateralsLength();
double MTX[6];
fillTransformationMTX(MTX);
quads.reserve(quadsCount);
for (int q = 0; q < quadsCount; ++q) {
HighlightAnnotation::Quad quad;
XPDFReader::transform(MTX, hlquads->getX1(q), hlquads->getY1(q), quad.points[0]);
XPDFReader::transform(MTX, hlquads->getX2(q), hlquads->getY2(q), quad.points[1]);
XPDFReader::transform(MTX, hlquads->getX3(q), hlquads->getY3(q), quad.points[2]);
XPDFReader::transform(MTX, hlquads->getX4(q), hlquads->getY4(q), quad.points[3]);
// ### PDF1.6 specs says that point are in ccw order, but in fact
// points 3 and 4 are swapped in every PDF around!
QPointF tmpPoint = quad.points[2];
quad.points[2] = quad.points[3];
quad.points[3] = tmpPoint;
// initialize other properties and append quad
quad.capStart = true; // unlinked quads are always capped
quad.capEnd = true; // unlinked quads are always capped
quad.feather = 0.1; // default feather
quads.append(quad);
}
return quads;
}
AnnotQuadrilaterals *HighlightAnnotationPrivate::toQuadrilaterals(const QList<HighlightAnnotation::Quad> &quads) const
{
const int count = quads.size();
auto ac = std::make_unique<AnnotQuadrilaterals::AnnotQuadrilateral[]>(count);
double MTX[6];
fillTransformationMTX(MTX);
int pos = 0;
foreach (const HighlightAnnotation::Quad &q, quads) {
double x1, y1, x2, y2, x3, y3, x4, y4;
XPDFReader::invTransform(MTX, q.points[0], x1, y1);
XPDFReader::invTransform(MTX, q.points[1], x2, y2);
// Swap points 3 and 4 (see HighlightAnnotationPrivate::fromQuadrilaterals)
XPDFReader::invTransform(MTX, q.points[3], x3, y3);
XPDFReader::invTransform(MTX, q.points[2], x4, y4);
ac[pos++] = AnnotQuadrilaterals::AnnotQuadrilateral(x1, y1, x2, y2, x3, y3, x4, y4);
}
return new AnnotQuadrilaterals(std::move(ac), count);
}
Annot *HighlightAnnotationPrivate::createNativeAnnot(::Page *destPage, DocumentData *doc)
{
// Setters are defined in the public class
HighlightAnnotation *q = static_cast<HighlightAnnotation *>(makeAlias());
// Set page and document
pdfPage = destPage;
parentDoc = doc;
// Set pdfAnnot
PDFRectangle rect = boundaryToPdfRectangle(boundary, flags);
pdfAnnot = new AnnotTextMarkup(destPage->getDoc(), &rect, toAnnotSubType(highlightType));
// Set properties
flushBaseAnnotationProperties();
q->setHighlightQuads(highlightQuads);
highlightQuads.clear(); // Free up memory
delete q;
return pdfAnnot;
}
HighlightAnnotation::HighlightAnnotation() : Annotation(*new HighlightAnnotationPrivate()) { }
HighlightAnnotation::HighlightAnnotation(HighlightAnnotationPrivate &dd) : Annotation(dd) { }
HighlightAnnotation::~HighlightAnnotation() { }
Annotation::SubType HighlightAnnotation::subType() const
{
return AHighlight;
}
HighlightAnnotation::HighlightType HighlightAnnotation::highlightType() const
{
Q_D(const HighlightAnnotation);
if (!d->pdfAnnot) {
return d->highlightType;
}
Annot::AnnotSubtype subType = d->pdfAnnot->getType();
if (subType == Annot::typeHighlight) {
return HighlightAnnotation::Highlight;
} else if (subType == Annot::typeUnderline) {
return HighlightAnnotation::Underline;
} else if (subType == Annot::typeSquiggly) {
return HighlightAnnotation::Squiggly;
} else { // Annot::typeStrikeOut
return HighlightAnnotation::StrikeOut;
}
}
void HighlightAnnotation::setHighlightType(HighlightAnnotation::HighlightType type)
{
Q_D(HighlightAnnotation);
if (!d->pdfAnnot) {
d->highlightType = type;
return;
}
AnnotTextMarkup *hlann = static_cast<AnnotTextMarkup *>(d->pdfAnnot);
hlann->setType(HighlightAnnotationPrivate::toAnnotSubType(type));
}
QList<HighlightAnnotation::Quad> HighlightAnnotation::highlightQuads() const
{
Q_D(const HighlightAnnotation);
if (!d->pdfAnnot) {
return d->highlightQuads;
}
const AnnotTextMarkup *hlann = static_cast<AnnotTextMarkup *>(d->pdfAnnot);
return d->fromQuadrilaterals(hlann->getQuadrilaterals());
}
void HighlightAnnotation::setHighlightQuads(const QList<HighlightAnnotation::Quad> &quads)
{
Q_D(HighlightAnnotation);
if (!d->pdfAnnot) {
d->highlightQuads = quads;
return;
}
AnnotTextMarkup *hlann = static_cast<AnnotTextMarkup *>(d->pdfAnnot);
AnnotQuadrilaterals *quadrilaterals = d->toQuadrilaterals(quads);
hlann->setQuadrilaterals(quadrilaterals);
delete quadrilaterals;
}
/** StampAnnotation [Annotation] */
class StampAnnotationPrivate : public AnnotationPrivate
{
public:
StampAnnotationPrivate();
Annotation *makeAlias() override;
Annot *createNativeAnnot(::Page *destPage, DocumentData *doc) override;
AnnotStampImageHelper *convertQImageToAnnotStampImageHelper(const QImage &qimg);
// data fields
QString stampIconName;
QImage stampCustomImage;
};
StampAnnotationPrivate::StampAnnotationPrivate() : AnnotationPrivate(), stampIconName(QStringLiteral("Draft")) { }
Annotation *StampAnnotationPrivate::makeAlias()
{
return new StampAnnotation(*this);
}
Annot *StampAnnotationPrivate::createNativeAnnot(::Page *destPage, DocumentData *doc)
{
StampAnnotation *q = static_cast<StampAnnotation *>(makeAlias());
// Set page and document
pdfPage = destPage;
parentDoc = doc;
// Set pdfAnnot
PDFRectangle rect = boundaryToPdfRectangle(boundary, flags);
pdfAnnot = new AnnotStamp(destPage->getDoc(), &rect);
// Set properties
flushBaseAnnotationProperties();
q->setStampIconName(stampIconName);
q->setStampCustomImage(stampCustomImage);
delete q;
stampIconName.clear(); // Free up memory
return pdfAnnot;
}
AnnotStampImageHelper *StampAnnotationPrivate::convertQImageToAnnotStampImageHelper(const QImage &qimg)
{
QImage convertedQImage = qimg;
QByteArray data;
QByteArray sMaskData;
const int width = convertedQImage.width();
const int height = convertedQImage.height();
int bitsPerComponent = 1;
ColorSpace colorSpace = ColorSpace::DeviceGray;
switch (convertedQImage.format()) {
case QImage::Format_MonoLSB:
if (!convertedQImage.allGray()) {
convertedQImage = convertedQImage.convertToFormat(QImage::Format_RGB888);
colorSpace = ColorSpace::DeviceRGB;
bitsPerComponent = 8;
} else {
convertedQImage = convertedQImage.convertToFormat(QImage::Format_Mono);
}
break;
case QImage::Format_Mono:
if (!convertedQImage.allGray()) {
convertedQImage = convertedQImage.convertToFormat(QImage::Format_RGB888);
colorSpace = ColorSpace::DeviceRGB;
bitsPerComponent = 8;
}
break;
case QImage::Format_RGB32:
case QImage::Format_ARGB32_Premultiplied:
case QImage::Format_ARGB8565_Premultiplied:
case QImage::Format_ARGB6666_Premultiplied:
case QImage::Format_ARGB8555_Premultiplied:
case QImage::Format_ARGB4444_Premultiplied:
case QImage::Format_Alpha8:
convertedQImage = convertedQImage.convertToFormat(QImage::Format_ARGB32);
colorSpace = ColorSpace::DeviceRGB;
bitsPerComponent = 8;
break;
case QImage::Format_RGBA8888:
case QImage::Format_RGBA8888_Premultiplied:
case QImage::Format_RGBX8888:
case QImage::Format_ARGB32:
colorSpace = ColorSpace::DeviceRGB;
bitsPerComponent = 8;
break;
case QImage::Format_Grayscale8:
bitsPerComponent = 8;
break;
case QImage::Format_Grayscale16:
convertedQImage = convertedQImage.convertToFormat(QImage::Format_Grayscale8);
colorSpace = ColorSpace::DeviceGray;
bitsPerComponent = 8;
break;
case QImage::Format_RGB16:
case QImage::Format_RGB666:
case QImage::Format_RGB555:
case QImage::Format_RGB444:
convertedQImage = convertedQImage.convertToFormat(QImage::Format_RGB888);
colorSpace = ColorSpace::DeviceRGB;
bitsPerComponent = 8;
break;
case QImage::Format_RGB888:
colorSpace = ColorSpace::DeviceRGB;
bitsPerComponent = 8;
break;
default:
convertedQImage = convertedQImage.convertToFormat(QImage::Format_ARGB32);
colorSpace = ColorSpace::DeviceRGB;
bitsPerComponent = 8;
break;
}
getRawDataFromQImage(convertedQImage, convertedQImage.depth(), &data, &sMaskData);
AnnotStampImageHelper *annotImg;
if (sMaskData.size() > 0) {
AnnotStampImageHelper sMask(parentDoc->doc, width, height, ColorSpace::DeviceGray, 8, sMaskData.data(), sMaskData.size());
annotImg = new AnnotStampImageHelper(parentDoc->doc, width, height, colorSpace, bitsPerComponent, data.data(), data.size(), sMask.getRef());
} else {
annotImg = new AnnotStampImageHelper(parentDoc->doc, width, height, colorSpace, bitsPerComponent, data.data(), data.size());
}
return annotImg;
}
StampAnnotation::StampAnnotation() : Annotation(*new StampAnnotationPrivate()) { }
StampAnnotation::StampAnnotation(StampAnnotationPrivate &dd) : Annotation(dd) { }
StampAnnotation::~StampAnnotation() { }
Annotation::SubType StampAnnotation::subType() const
{
return AStamp;
}
QString StampAnnotation::stampIconName() const
{
Q_D(const StampAnnotation);
if (!d->pdfAnnot) {
return d->stampIconName;
}
const AnnotStamp *stampann = static_cast<const AnnotStamp *>(d->pdfAnnot);
return QString::fromLatin1(stampann->getIcon()->c_str());
}
void StampAnnotation::setStampIconName(const QString &name)
{
Q_D(StampAnnotation);
if (!d->pdfAnnot) {
d->stampIconName = name;
return;
}