blob: e15523cfa3efa36d734046d59a383b7d969a0354 [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 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/QRegExp>
#include <QtCore/QtAlgorithms>
#include <QtXml/QDomElement>
#include <QtGui/QColor>
#include <QtGui/QTransform>
#include <QImage>
// local includes
#include "poppler-annotation.h"
#include "poppler-link.h"
#include "poppler-qt5.h"
#include "poppler-annotation-helper.h"
#include "poppler-annotation-private.h"
#include "poppler-page-private.h"
#include "poppler-private.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 AnnotationUtils implementation
Annotation *AnnotationUtils::createAnnotation(const QDomElement &annElement)
{
// safety check on annotation element
if (!annElement.hasAttribute(QStringLiteral("type"))) {
return nullptr;
}
// build annotation of given type
Annotation *annotation = nullptr;
int typeNumber = annElement.attribute(QStringLiteral("type")).toInt();
switch (typeNumber) {
case Annotation::AText:
annotation = new TextAnnotation(annElement);
break;
case Annotation::ALine:
annotation = new LineAnnotation(annElement);
break;
case Annotation::AGeom:
annotation = new GeomAnnotation(annElement);
break;
case Annotation::AHighlight:
annotation = new HighlightAnnotation(annElement);
break;
case Annotation::AStamp:
annotation = new StampAnnotation(annElement);
break;
case Annotation::AInk:
annotation = new InkAnnotation(annElement);
break;
case Annotation::ACaret:
annotation = new CaretAnnotation(annElement);
break;
}
// return created annotation
return annotation;
}
void AnnotationUtils::storeAnnotation(const Annotation *ann, QDomElement &annElement, QDomDocument &document)
{
// save annotation's type as element's attribute
annElement.setAttribute(QStringLiteral("type"), (uint)ann->subType());
// append all annotation data as children of this node
ann->store(annElement, document);
}
QDomElement AnnotationUtils::findChildElement(const QDomNode &parentNode, const QString &name)
{
// loop through the whole children and return a 'name' named element
QDomNode subNode = parentNode.firstChild();
while (subNode.isElement()) {
QDomElement element = subNode.toElement();
if (element.tagName() == name) {
return element;
}
subNode = subNode.nextSibling();
}
// if the name can't be found, return a dummy null element
return QDomElement();
}
// END AnnotationUtils implementation
// 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() : flags(0), 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:
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
data->append((const char *)qimg.bits(), static_cast<int>(qimg.sizeInBytes()));
#else
data->append((const char *)qimg.bits(), qimg.byteCount());
#endif
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 double w = pdfPage->getCropWidth();
const double h = pdfPage->getCropHeight();
if (w == 0 || h == 0) {
// page is broken, there's nothing to transform
return {};
}
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 QLinkedList<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));
}
QList<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
QList<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 */
Annotation *annotation = nullptr;
Annot::AnnotSubtype subType = ann->getType();
switch (subType) {
case Annot::typeText:
if (!wantTextAnnotations) {
continue;
}
annotation = new TextAnnotation(TextAnnotation::Linked);
break;
case Annot::typeFreeText:
if (!wantTextAnnotations) {
continue;
}
annotation = new TextAnnotation(TextAnnotation::InPlace);
break;
case Annot::typeLine:
if (!wantLineAnnotations) {
continue;
}
annotation = new LineAnnotation(LineAnnotation::StraightLine);
break;
case Annot::typePolygon:
case Annot::typePolyLine:
if (!wantLineAnnotations) {
continue;
}
annotation = new LineAnnotation(LineAnnotation::Polyline);
break;
case Annot::typeSquare:
case Annot::typeCircle:
if (!wantGeomAnnotations) {
continue;
}
annotation = new GeomAnnotation();
break;
case Annot::typeHighlight:
case Annot::typeUnderline:
case Annot::typeSquiggly:
case Annot::typeStrikeOut:
if (!wantHighlightAnnotations) {
continue;
}
annotation = new HighlightAnnotation();
break;
case Annot::typeStamp:
if (!wantStampAnnotations) {
continue;
}
annotation = new StampAnnotation();
break;
case Annot::typeInk:
if (!wantInkAnnotations) {
continue;
}
annotation = new 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();
annotation = l;
// -> hlMode
l->setLinkHighlightMode((LinkAnnotation::HighlightMode)linkann->getLinkEffect());
// -> link region
// TODO
// reading link action
if (linkann->getAction()) {
Link *popplerLink = PageData::convertLinkActionToLink(linkann->getAction(), doc, QRectF());
if (popplerLink) {
l->setLinkDestination(popplerLink);
}
}
break;
}
case Annot::typeCaret:
if (!wantCaretAnnotations) {
continue;
}
annotation = new CaretAnnotation();
break;
case Annot::typeFileAttachment: /* TODO: Move logic to getters */
{
if (!wantFileAttachmentAnnotations) {
continue;
}
AnnotFileAttachment *attachann = static_cast<AnnotFileAttachment *>(ann);
FileAttachmentAnnotation *f = new FileAttachmentAnnotation();
annotation = f;
// -> 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))));
break;
}
case Annot::typeSound: /* TODO: Move logic to getters */
{
if (!wantSoundAnnotations) {
continue;
}
AnnotSound *soundann = static_cast<AnnotSound *>(ann);
SoundAnnotation *s = new SoundAnnotation();
annotation = s;
// -> soundIcon
s->setSoundIconName(QString::fromLatin1(soundann->getName()->c_str()));
// -> sound
s->setSound(new SoundObject(soundann->getSound()));
break;
}
case Annot::typeMovie: /* TODO: Move logic to getters */
{
if (!wantMovieAnnotations) {
continue;
}
AnnotMovie *movieann = static_cast<AnnotMovie *>(ann);
MovieAnnotation *m = new MovieAnnotation();
annotation = m;
// -> movie
MovieObject *movie = new MovieObject(movieann);
m->setMovie(movie);
// -> movieTitle
const GooString *movietitle = movieann->getTitle();
if (movietitle) {
m->setMovieTitle(QString::fromLatin1(movietitle->c_str()));
}
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();
annotation = s;
// -> screen
Link *popplerLink = PageData::convertLinkActionToLink(screenann->getAction(), doc, QRectF());
s->setAction(static_cast<Poppler::LinkRendition *>(popplerLink));
// -> screenTitle
const GooString *screentitle = screenann->getTitle();
if (screentitle) {
s->setScreenTitle(UnicodeParsedString(screentitle));
}
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 = 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 = 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.append(annotation);
}
return res;
}
Ref AnnotationPrivate::pdfObjectReference() const
{
if (pdfAnnot == nullptr) {
return Ref::INVALID();
}
return pdfAnnot->getRef();
}
Link *AnnotationPrivate::additionalAction(Annotation::AdditionalActionType type) const
{
if (pdfAnnot->getType() != Annot::typeScreen && pdfAnnot->getType() != Annot::typeWidget) {
return nullptr;
}
const Annot::AdditionalActionsType actionType = toPopplerAdditionalActionType(type);
std::unique_ptr<::LinkAction> linkAction = nullptr;
if (pdfAnnot->getType() == Annot::typeScreen) {
linkAction = static_cast<AnnotScreen *>(pdfAnnot)->getAdditionalAction(actionType);
} else {
linkAction = static_cast<AnnotWidget *>(pdfAnnot)->getAdditionalAction(actionType);
}
Link *link = nullptr;
if (linkAction) {
link = PageData::convertLinkActionToLink(linkAction.get(), parentDoc, QRectF());
}
return link;
}
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;
int inplaceAlign; // 0:left, 1:center, 2:right
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() { }
Annotation::Annotation(AnnotationPrivate &dd, const QDomNode &annNode) : d_ptr(&dd)
{
Q_D(Annotation);
// get the [base] element of the annotation node
QDomElement e = AnnotationUtils::findChildElement(annNode, QStringLiteral("base"));
if (e.isNull()) {
return;
}
Style s;
Popup w;
// parse -contents- attributes
if (e.hasAttribute(QStringLiteral("author"))) {
setAuthor(e.attribute(QStringLiteral("author")));
}
if (e.hasAttribute(QStringLiteral("contents"))) {
setContents(e.attribute(QStringLiteral("contents")));
}
if (e.hasAttribute(QStringLiteral("uniqueName"))) {
setUniqueName(e.attribute(QStringLiteral("uniqueName")));
}
if (e.hasAttribute(QStringLiteral("modifyDate"))) {
QDateTime dt = QDateTime::fromString(e.attribute(QStringLiteral("modifyDate")));
if (!dt.isValid()) {
dt = QDateTime::fromString(e.attribute(QStringLiteral("modifyDate")), Qt::ISODate);
}
setModificationDate(dt);
}
if (e.hasAttribute(QStringLiteral("creationDate"))) {
QDateTime dt = QDateTime::fromString(e.attribute(QStringLiteral("creationDate")));
if (!dt.isValid()) {
dt = QDateTime::fromString(e.attribute(QStringLiteral("creationDate")), Qt::ISODate);
}
setCreationDate(dt);
}
// parse -other- attributes
if (e.hasAttribute(QStringLiteral("flags"))) {
setFlags(e.attribute(QStringLiteral("flags")).toInt());
}
if (e.hasAttribute(QStringLiteral("color"))) {
s.setColor(QColor(e.attribute(QStringLiteral("color"))));
}
if (e.hasAttribute(QStringLiteral("opacity"))) {
s.setOpacity(e.attribute(QStringLiteral("opacity")).toDouble());
}
// parse -the-subnodes- (describing Style, Window, Revision(s) structures)
// Note: all subnodes if present must be 'attributes complete'
QDomNode eSubNode = e.firstChild();
while (eSubNode.isElement()) {
QDomElement ee = eSubNode.toElement();
eSubNode = eSubNode.nextSibling();
// parse boundary
if (ee.tagName() == QLatin1String("boundary")) {
QRectF brect;
brect.setLeft(ee.attribute(QStringLiteral("l")).toDouble());
brect.setTop(ee.attribute(QStringLiteral("t")).toDouble());
brect.setRight(ee.attribute(QStringLiteral("r")).toDouble());
brect.setBottom(ee.attribute(QStringLiteral("b")).toDouble());
setBoundary(brect);
}
// parse penStyle if not default
else if (ee.tagName() == QLatin1String("penStyle")) {
s.setWidth(ee.attribute(QStringLiteral("width")).toDouble());
s.setLineStyle((LineStyle)ee.attribute(QStringLiteral("style")).toInt());
s.setXCorners(ee.attribute(QStringLiteral("xcr")).toDouble());
s.setYCorners(ee.attribute(QStringLiteral("ycr")).toDouble());
// Try to parse dash array (new format)
QVector<double> dashArray;
QDomNode eeSubNode = ee.firstChild();
while (eeSubNode.isElement()) {
QDomElement eee = eeSubNode.toElement();
eeSubNode = eeSubNode.nextSibling();
if (eee.tagName() != QLatin1String("dashsegm")) {
continue;
}
dashArray.append(eee.attribute(QStringLiteral("len")).toDouble());
}
// If no segments were found use marks/spaces (old format)
if (dashArray.size() == 0) {
dashArray.append(ee.attribute(QStringLiteral("marks")).toDouble());
dashArray.append(ee.attribute(QStringLiteral("spaces")).toDouble());
}
s.setDashArray(dashArray);
}
// parse effectStyle if not default
else if (ee.tagName() == QLatin1String("penEffect")) {
s.setLineEffect((LineEffect)ee.attribute(QStringLiteral("effect")).toInt());
s.setEffectIntensity(ee.attribute(QStringLiteral("intensity")).toDouble());
}
// parse window if present
else if (ee.tagName() == QLatin1String("window")) {
QRectF geom;
geom.setX(ee.attribute(QStringLiteral("top")).toDouble());
geom.setY(ee.attribute(QStringLiteral("left")).toDouble());
if (ee.hasAttribute(QStringLiteral("widthDouble"))) {
geom.setWidth(ee.attribute(QStringLiteral("widthDouble")).toDouble());
} else {
geom.setWidth(ee.attribute(QStringLiteral("width")).toDouble());
}
if (ee.hasAttribute(QStringLiteral("widthDouble"))) {
geom.setHeight(ee.attribute(QStringLiteral("heightDouble")).toDouble());
} else {
geom.setHeight(ee.attribute(QStringLiteral("height")).toDouble());
}
w.setGeometry(geom);
w.setFlags(ee.attribute(QStringLiteral("flags")).toInt());
w.setTitle(ee.attribute(QStringLiteral("title")));
w.setSummary(ee.attribute(QStringLiteral("summary")));
// parse window subnodes
QDomNode winNode = ee.firstChild();
for (; winNode.isElement(); winNode = winNode.nextSibling()) {
QDomElement winElement = winNode.toElement();
if (winElement.tagName() == QLatin1String("text")) {
w.setText(winElement.firstChild().toCDATASection().data());
}
}
}
}
setStyle(s); // assign parsed style
setPopup(w); // assign parsed window
// get the [revisions] element of the annotation node
QDomNode revNode = annNode.firstChild();
for (; revNode.isElement(); revNode = revNode.nextSibling()) {
QDomElement revElement = revNode.toElement();
if (revElement.tagName() != QLatin1String("revision")) {
continue;
}
Annotation *reply = AnnotationUtils::createAnnotation(revElement);
if (reply) // if annotation is valid, add as a revision of this annotation
{
RevScope scope = (RevScope)revElement.attribute(QStringLiteral("revScope")).toInt();
RevType type = (RevType)revElement.attribute(QStringLiteral("revType")).toInt();
d->addRevision(reply, scope, type);
delete reply;
}
}
}
void Annotation::storeBaseAnnotationProperties(QDomNode &annNode, QDomDocument &document) const
{
// create [base] element of the annotation node
QDomElement e = document.createElement(QStringLiteral("base"));
annNode.appendChild(e);
const Style s = style();
const Popup w = popup();
// store -contents- attributes
if (!author().isEmpty()) {
e.setAttribute(QStringLiteral("author"), author());
}
if (!contents().isEmpty()) {
e.setAttribute(QStringLiteral("contents"), contents());
}
if (!uniqueName().isEmpty()) {
e.setAttribute(QStringLiteral("uniqueName"), uniqueName());
}
if (modificationDate().isValid()) {
e.setAttribute(QStringLiteral("modifyDate"), modificationDate().toString());
}
if (creationDate().isValid()) {
e.setAttribute(QStringLiteral("creationDate"), creationDate().toString());
}
// store -other- attributes
if (flags()) {
e.setAttribute(QStringLiteral("flags"), flags());
}
if (s.color().isValid()) {
e.setAttribute(QStringLiteral("color"), s.color().name());
}
if (s.opacity() != 1.0) {
e.setAttribute(QStringLiteral("opacity"), QString::number(s.opacity()));
}
// Sub-Node-1 - boundary
const QRectF brect = boundary();
QDomElement bE = document.createElement(QStringLiteral("boundary"));
e.appendChild(bE);
bE.setAttribute(QStringLiteral("l"), QString::number((double)brect.left()));
bE.setAttribute(QStringLiteral("t"), QString::number((double)brect.top()));
bE.setAttribute(QStringLiteral("r"), QString::number((double)brect.right()));
bE.setAttribute(QStringLiteral("b"), QString::number((double)brect.bottom()));
// Sub-Node-2 - penStyle
const QVector<double> &dashArray = s.dashArray();
if (s.width() != 1 || s.lineStyle() != Solid || s.xCorners() != 0 || s.yCorners() != 0.0 || dashArray.size() != 1 || dashArray[0] != 3) {
QDomElement psE = document.createElement(QStringLiteral("penStyle"));
e.appendChild(psE);
psE.setAttribute(QStringLiteral("width"), QString::number(s.width()));
psE.setAttribute(QStringLiteral("style"), (int)s.lineStyle());
psE.setAttribute(QStringLiteral("xcr"), QString::number(s.xCorners()));
psE.setAttribute(QStringLiteral("ycr"), QString::number(s.yCorners()));
int marks = 3, spaces = 0; // Do not break code relying on marks/spaces
if (dashArray.size() != 0) {
marks = (int)dashArray[0];
}
if (dashArray.size() > 1) {
spaces = (int)dashArray[1];
}
psE.setAttribute(QStringLiteral("marks"), marks);
psE.setAttribute(QStringLiteral("spaces"), spaces);
foreach (double segm, dashArray) {
QDomElement pattE = document.createElement(QStringLiteral("dashsegm"));
pattE.setAttribute(QStringLiteral("len"), QString::number(segm));
psE.appendChild(pattE);
}
}
// Sub-Node-3 - penEffect
if (s.lineEffect() != NoEffect || s.effectIntensity() != 1.0) {
QDomElement peE = document.createElement(QStringLiteral("penEffect"));
e.appendChild(peE);
peE.setAttribute(QStringLiteral("effect"), (int)s.lineEffect());
peE.setAttribute(QStringLiteral("intensity"), QString::number(s.effectIntensity()));
}
// Sub-Node-4 - window
if (w.flags() != -1 || !w.title().isEmpty() || !w.summary().isEmpty() || !w.text().isEmpty()) {
QDomElement wE = document.createElement(QStringLiteral("window"));
const QRectF geom = w.geometry();
e.appendChild(wE);
wE.setAttribute(QStringLiteral("flags"), w.flags());
wE.setAttribute(QStringLiteral("top"), QString::number(geom.x()));
wE.setAttribute(QStringLiteral("left"), QString::number(geom.y()));
wE.setAttribute(QStringLiteral("width"), (int)geom.width());
wE.setAttribute(QStringLiteral("height"), (int)geom.height());
wE.setAttribute(QStringLiteral("widthDouble"), QString::number(geom.width()));
wE.setAttribute(QStringLiteral("heightDouble"), QString::number(geom.height()));
wE.setAttribute(QStringLiteral("title"), w.title());
wE.setAttribute(QStringLiteral("summary"), w.summary());
// store window.text as a subnode, because we need escaped data
if (!w.text().isEmpty()) {
QDomElement escapedText = document.createElement(QStringLiteral("text"));
wE.appendChild(escapedText);
QDomCDATASection textCData = document.createCDATASection(w.text());
escapedText.appendChild(textCData);
}
}
const QList<Annotation *> revs = revisions();
// create [revision] element of the annotation node (if any)
if (revs.isEmpty()) {
return;
}
// add all revisions as children of revisions element
foreach (const Annotation *rev, revs) {
QDomElement r = document.createElement(QStringLiteral("revision"));
annNode.appendChild(r);
// set element attributes
r.setAttribute(QStringLiteral("revScope"), (int)rev->revisionScope());
r.setAttribute(QStringLiteral("revType"), (int)rev->revisionType());
// use revision as the annotation element, so fill it up
AnnotationUtils::storeAnnotation(rev, r, document);
delete rev;
}
}
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 int fromPdfFlags(int flags)
{
int qtflags = 0;
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 | Annotation::DenyDelete);
}
if (flags & Annot::flagLocked) {
qtflags |= Annotation::DenyDelete;
}
if (flags & Annot::flagToggleNoView) {
qtflags |= Annotation::ToggleHidingOnMouse;
}
return qtflags;
}
static int toPdfFlags(int 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;
}
int Annotation::flags() const
{
Q_D(const Annotation);
if (!d->pdfAnnot) {
return d->flags;
}
return fromPdfFlags(d->pdfAnnot->getFlags());
}
void Annotation::setFlags(int 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();
#if QT_VERSION <= QT_VERSION_CHECK(5, 14, 0)
s.setDashArray(QVector<double>::fromStdVector(dashArray));
#else
s.setDashArray(QVector<double>(dashArray.begin(), dashArray.end()));
#endif
}
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;
}
QList<Annotation *> Annotation::revisions() const
{
Q_D(const Annotation);
if (!d->pdfAnnot) {
/* Return aliases, whose ownership goes to the caller */
QList<Annotation *> res;
foreach (Annotation *rev, d->revisions)
res.append(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 QList<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(0), 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(const QDomNode &node) : Annotation(*new TextAnnotationPrivate, node)
{
// loop through the whole children looking for a 'text' element
QDomNode subNode = node.firstChild();
while (subNode.isElement()) {
QDomElement e = subNode.toElement();
subNode = subNode.nextSibling();
if (e.tagName() != QLatin1String("text")) {
continue;
}
// parse the attributes
if (e.hasAttribute(QStringLiteral("type"))) {
setTextType((TextAnnotation::TextType)e.attribute(QStringLiteral("type")).toInt());
}
if (e.hasAttribute(QStringLiteral("icon"))) {
setTextIcon(e.attribute(QStringLiteral("icon")));
}
if (e.hasAttribute(QStringLiteral("font"))) {
QFont font;
font.fromString(e.attribute(QStringLiteral("font")));
setTextFont(font);
if (e.hasAttribute(QStringLiteral("fontColor"))) {
const QColor color = QColor(e.attribute(QStringLiteral("fontColor")));
setTextColor(color);
}
}
if (e.hasAttribute(QStringLiteral("align"))) {
setInplaceAlign(e.attribute(QStringLiteral("align")).toInt());
}
if (e.hasAttribute(QStringLiteral("intent"))) {
setInplaceIntent((TextAnnotation::InplaceIntent)e.attribute(QStringLiteral("intent")).toInt());
}
// parse the subnodes
QDomNode eSubNode = e.firstChild();
while (eSubNode.isElement()) {
QDomElement ee = eSubNode.toElement();
eSubNode = eSubNode.nextSibling();
if (ee.tagName() == QLatin1String("escapedText")) {
setContents(ee.firstChild().toCDATASection().data());
} else if (ee.tagName() == QLatin1String("callout")) {
QVector<QPointF> points(3);
points[0] = QPointF(ee.attribute(QStringLiteral("ax")).toDouble(), ee.attribute(QStringLiteral("ay")).toDouble());
points[1] = QPointF(ee.attribute(QStringLiteral("bx")).toDouble(), ee.attribute(QStringLiteral("by")).toDouble());
points[2] = QPointF(ee.attribute(QStringLiteral("cx")).toDouble(), ee.attribute(QStringLiteral("cy")).toDouble());
setCalloutPoints(points);
}
}
// loading complete
break;
}
}
TextAnnotation::~TextAnnotation() { }
void TextAnnotation::store(QDomNode &node, QDomDocument &document) const
{
// store base annotation properties
storeBaseAnnotationProperties(node, document);
// create [text] element
QDomElement textElement = document.createElement(QStringLiteral("text"));
node.appendChild(textElement);
// store the optional attributes
if (textType() != Linked) {
textElement.setAttribute(QStringLiteral("type"), (int)textType());
}
if (textIcon() != QLatin1String("Note")) {
textElement.setAttribute(QStringLiteral("icon"), textIcon());
}
if (inplaceAlign()) {
textElement.setAttribute(QStringLiteral("align"), inplaceAlign());
}
if (inplaceIntent() != Unknown) {
textElement.setAttribute(QStringLiteral("intent"), (int)inplaceIntent());
}
textElement.setAttribute(QStringLiteral("font"), textFont().toString());
textElement.setAttribute(QStringLiteral("fontColor"), textColor().name());
// Sub-Node-1 - escapedText
if (!contents().isEmpty()) {
QDomElement escapedText = document.createElement(QStringLiteral("escapedText"));
textElement.appendChild(escapedText);
QDomCDATASection textCData = document.createCDATASection(contents());
escapedText.appendChild(textCData);
}
// Sub-Node-2 - callout
if (calloutPoint(0).x() != 0.0) {
QDomElement calloutElement = document.createElement(QStringLiteral("callout"));
textElement.appendChild(calloutElement);
calloutElement.setAttribute(QStringLiteral("ax"), QString::number(calloutPoint(0).x()));
calloutElement.setAttribute(QStringLiteral("ay"), QString::number(calloutPoint(0).y()));
calloutElement.setAttribute(QStringLiteral("bx"), QString::number(calloutPoint(1).x()));
calloutElement.setAttribute(QStringLiteral("by"), QString::number(calloutPoint(1).y()));
calloutElement.setAttribute(QStringLiteral("cx"), QString::number(calloutPoint(2).x()));
calloutElement.setAttribute(QStringLiteral("cy"), QString::number(calloutPoint(2).y()));
}
}
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();
}
int 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);
return static_cast<int>(ftextann->getQuadding());
}
return 0;
}
void TextAnnotation::setInplaceAlign(int 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((VariableTextQuadding)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)
QLinkedList<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(const QDomNode &node) : Annotation(*new LineAnnotationPrivate(), node)
{
// loop through the whole children looking for a 'line' element
QDomNode subNode = node.firstChild();
while (subNode.isElement()) {
QDomElement e = subNode.toElement();
subNode = subNode.nextSibling();
if (e.tagName() != QLatin1String("line")) {
continue;
}
// parse the attributes
if (e.hasAttribute(QStringLiteral("startStyle"))) {
setLineStartStyle((LineAnnotation::TermStyle)e.attribute(QStringLiteral("startStyle")).toInt());
}
if (e.hasAttribute(QStringLiteral("endStyle"))) {
setLineEndStyle((LineAnnotation::TermStyle)e.attribute(QStringLiteral("endStyle")).toInt());
}
if (e.hasAttribute(QStringLiteral("closed"))) {
setLineClosed(e.attribute(QStringLiteral("closed")).toInt());
}
if (e.hasAttribute(QStringLiteral("innerColor"))) {
setLineInnerColor(QColor(e.attribute(QStringLiteral("innerColor"))));
}
if (e.hasAttribute(QStringLiteral("leadFwd"))) {
setLineLeadingForwardPoint(e.attribute(QStringLiteral("leadFwd")).toDouble());
}
if (e.hasAttribute(QStringLiteral("leadBack"))) {
setLineLeadingBackPoint(e.attribute(QStringLiteral("leadBack")).toDouble());
}
if (e.hasAttribute(QStringLiteral("showCaption"))) {
setLineShowCaption(e.attribute(QStringLiteral("showCaption")).toInt());
}
if (e.hasAttribute(QStringLiteral("intent"))) {
setLineIntent((LineAnnotation::LineIntent)e.attribute(QStringLiteral("intent")).toInt());
}
// parse all 'point' subnodes
QLinkedList<QPointF> points;
QDomNode pointNode = e.firstChild();
while (pointNode.isElement()) {
QDomElement pe = pointNode.toElement();
pointNode = pointNode.nextSibling();
if (pe.tagName() != QLatin1String("point")) {
continue;
}
QPointF p(pe.attribute(QStringLiteral("x"), QStringLiteral("0.0")).toDouble(), pe.attribute(QStringLiteral("y"), QStringLiteral("0.0")).toDouble());
points.append(p);
}
setLinePoints(points);
setLineType(points.size() == 2 ? StraightLine : Polyline);
// loading complete
break;
}
}
LineAnnotation::~LineAnnotation() { }
void LineAnnotation::store(QDomNode &node, QDomDocument &document) const
{
// store base annotation properties
storeBaseAnnotationProperties(node, document);
// create [line] element
QDomElement lineElement = document.createElement(QStringLiteral("line"));
node.appendChild(lineElement);
// store the attributes
if (lineStartStyle() != None) {
lineElement.setAttribute(QStringLiteral("startStyle"), (int)lineStartStyle());
}
if (lineEndStyle() != None) {
lineElement.setAttribute(QStringLiteral("endStyle"), (int)lineEndStyle());
}
if (isLineClosed()) {
lineElement.setAttribute(QStringLiteral("closed"), isLineClosed());
}
if (lineInnerColor().isValid()) {
lineElement.setAttribute(QStringLiteral("innerColor"), lineInnerColor().name());
}
if (lineLeadingForwardPoint() != 0.0) {
lineElement.setAttribute(QStringLiteral("leadFwd"), QString::number(lineLeadingForwardPoint()));
}
if (lineLeadingBackPoint() != 0.0) {
lineElement.setAttribute(QStringLiteral("leadBack"), QString::number(lineLeadingBackPoint()));
}
if (lineShowCaption()) {
lineElement.setAttribute(QStringLiteral("showCaption"), lineShowCaption());
}
if (lineIntent() != Unknown) {
lineElement.setAttribute(QStringLiteral("intent"), lineIntent());
}
// append the list of points
const QLinkedList<QPointF> points = linePoints();
if (points.count() > 1) {
QLinkedList<QPointF>::const_iterator it = points.begin(), end = points.end();
while (it != end) {
const QPointF &p = *it;
QDomElement pElement = document.createElement(QStringLiteral("point"));
lineElement.appendChild(pElement);
pElement.setAttribute(QStringLiteral("x"), QString::number(p.x()));
pElement.setAttribute(QStringLiteral("y"), QString::number(p.y()));
++it;
}
}
}
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";
}
QLinkedList<QPointF> LineAnnotation::linePoints() const
{
Q_D(const LineAnnotation);
if (!d->pdfAnnot) {
return d->linePoints;
}
double MTX[6];
d->fillTransformationMTX(MTX);
QLinkedList<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 QLinkedList<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