You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
387 lines
14 KiB
387 lines
14 KiB
/* |
|
SPDX-FileCopyrightText: 2006 Pino Toscano <toscano.pino@tiscali.it> |
|
|
|
SPDX-License-Identifier: GPL-2.0-or-later |
|
*/ |
|
|
|
#include "generator_djvu.h" |
|
|
|
#include <core/action.h> |
|
#include <core/annotations.h> |
|
#include <core/area.h> |
|
#include <core/document.h> |
|
#include <core/fileprinter.h> |
|
#include <core/page.h> |
|
#include <core/textpage.h> |
|
#include <core/utils.h> |
|
|
|
#include <QDomDocument> |
|
#include <QMutex> |
|
#include <QPixmap> |
|
#include <QPrinter> |
|
#include <QString> |
|
#include <QUuid> |
|
|
|
#include <KAboutData> |
|
#include <KLocalizedString> |
|
#include <QDebug> |
|
#include <QDir> |
|
#include <QTemporaryFile> |
|
|
|
static void recurseCreateTOC(QDomDocument &maindoc, const QDomNode &parent, QDomNode &parentDestination, KDjVu *djvu) |
|
{ |
|
QDomNode n = parent.firstChild(); |
|
while (!n.isNull()) { |
|
QDomElement el = n.toElement(); |
|
|
|
QDomElement newel = maindoc.createElement(el.attribute(QStringLiteral("title"))); |
|
parentDestination.appendChild(newel); |
|
|
|
QString dest; |
|
if (!(dest = el.attribute(QStringLiteral("PageNumber"))).isEmpty()) { |
|
Okular::DocumentViewport vp; |
|
vp.pageNumber = dest.toInt() - 1; |
|
newel.setAttribute(QStringLiteral("Viewport"), vp.toString()); |
|
} else if (!(dest = el.attribute(QStringLiteral("PageName"))).isEmpty()) { |
|
Okular::DocumentViewport vp; |
|
vp.pageNumber = djvu->pageNumber(dest); |
|
newel.setAttribute(QStringLiteral("Viewport"), vp.toString()); |
|
} else if (!(dest = el.attribute(QStringLiteral("URL"))).isEmpty()) { |
|
newel.setAttribute(QStringLiteral("URL"), dest); |
|
} |
|
|
|
if (el.hasChildNodes()) { |
|
recurseCreateTOC(maindoc, n, newel, djvu); |
|
} |
|
n = n.nextSibling(); |
|
} |
|
} |
|
|
|
OKULAR_EXPORT_PLUGIN(DjVuGenerator, "libokularGenerator_djvu.json") |
|
|
|
DjVuGenerator::DjVuGenerator(QObject *parent, const QVariantList &args) |
|
: Okular::Generator(parent, args) |
|
, m_docSyn(nullptr) |
|
{ |
|
setFeature(TextExtraction); |
|
setFeature(Threaded); |
|
setFeature(PrintPostscript); |
|
if (Okular::FilePrinter::ps2pdfAvailable()) |
|
setFeature(PrintToFile); |
|
|
|
m_djvu = new KDjVu(); |
|
m_djvu->setCacheEnabled(false); |
|
} |
|
|
|
DjVuGenerator::~DjVuGenerator() |
|
{ |
|
delete m_djvu; |
|
} |
|
|
|
bool DjVuGenerator::loadDocument(const QString &fileName, QVector<Okular::Page *> &pagesVector) |
|
{ |
|
QMutexLocker locker(userMutex()); |
|
if (!m_djvu->openFile(fileName)) |
|
return false; |
|
|
|
locker.unlock(); |
|
|
|
loadPages(pagesVector, 0); |
|
|
|
return true; |
|
} |
|
|
|
bool DjVuGenerator::doCloseDocument() |
|
{ |
|
userMutex()->lock(); |
|
m_djvu->closeFile(); |
|
userMutex()->unlock(); |
|
|
|
delete m_docSyn; |
|
m_docSyn = nullptr; |
|
|
|
return true; |
|
} |
|
|
|
QImage DjVuGenerator::image(Okular::PixmapRequest *request) |
|
{ |
|
userMutex()->lock(); |
|
QImage img = m_djvu->image(request->pageNumber(), request->width(), request->height(), request->page()->rotation()); |
|
userMutex()->unlock(); |
|
return img; |
|
} |
|
|
|
Okular::DocumentInfo DjVuGenerator::generateDocumentInfo(const QSet<Okular::DocumentInfo::Key> &keys) const |
|
{ |
|
Okular::DocumentInfo docInfo; |
|
|
|
if (keys.contains(Okular::DocumentInfo::MimeType)) |
|
docInfo.set(Okular::DocumentInfo::MimeType, QStringLiteral("image/vnd.djvu")); |
|
|
|
if (m_djvu) { |
|
// compile internal structure reading properties from KDjVu |
|
if (keys.contains(Okular::DocumentInfo::Author)) |
|
docInfo.set(Okular::DocumentInfo::Title, m_djvu->metaData(QStringLiteral("title")).toString()); |
|
if (keys.contains(Okular::DocumentInfo::Author)) |
|
docInfo.set(Okular::DocumentInfo::Author, m_djvu->metaData(QStringLiteral("author")).toString()); |
|
if (keys.contains(Okular::DocumentInfo::CreationDate)) |
|
docInfo.set(Okular::DocumentInfo::CreationDate, m_djvu->metaData(QStringLiteral("year")).toString()); |
|
if (keys.contains(Okular::DocumentInfo::CustomKeys)) { |
|
docInfo.set(QStringLiteral("editor"), m_djvu->metaData(QStringLiteral("editor")).toString(), i18n("Editor")); |
|
docInfo.set(QStringLiteral("publisher"), m_djvu->metaData(QStringLiteral("publisher")).toString(), i18n("Publisher")); |
|
docInfo.set(QStringLiteral("volume"), m_djvu->metaData(QStringLiteral("volume")).toString(), i18n("Volume")); |
|
docInfo.set(QStringLiteral("documentType"), m_djvu->metaData(QStringLiteral("documentType")).toString(), i18n("Type of document")); |
|
QVariant numcomponents = m_djvu->metaData(QStringLiteral("componentFile")); |
|
docInfo.set(QStringLiteral("componentFile"), numcomponents.type() != QVariant::Int ? i18nc("Unknown number of component files", "Unknown") : numcomponents.toString(), i18n("Component Files")); |
|
} |
|
} |
|
|
|
return docInfo; |
|
} |
|
|
|
const Okular::DocumentSynopsis *DjVuGenerator::generateDocumentSynopsis() |
|
{ |
|
QMutexLocker locker(userMutex()); |
|
if (m_docSyn) |
|
return m_docSyn; |
|
|
|
const QDomDocument *doc = m_djvu->documentBookmarks(); |
|
if (doc) { |
|
m_docSyn = new Okular::DocumentSynopsis(); |
|
recurseCreateTOC(*m_docSyn, *doc, *m_docSyn, m_djvu); |
|
} |
|
locker.unlock(); |
|
|
|
return m_docSyn; |
|
} |
|
|
|
Okular::Document::PrintError DjVuGenerator::print(QPrinter &printer) |
|
{ |
|
// Create tempfile to write to |
|
QTemporaryFile tf(QDir::tempPath() + QLatin1String("/okular_XXXXXX.ps")); |
|
if (!tf.open()) |
|
return Okular::Document::TemporaryFileOpenPrintError; |
|
const QString fileName = tf.fileName(); |
|
|
|
QMutexLocker locker(userMutex()); |
|
QList<int> pageList = Okular::FilePrinter::pageList(printer, m_djvu->pages().count(), document()->currentPage() + 1, document()->bookmarkedPageList()); |
|
|
|
if (m_djvu->exportAsPostScript(&tf, pageList)) { |
|
tf.setAutoRemove(false); |
|
tf.close(); |
|
return Okular::FilePrinter::printFile(printer, fileName, document()->orientation(), Okular::FilePrinter::SystemDeletesFiles, Okular::FilePrinter::ApplicationSelectsPages, document()->bookmarkedPageRange()); |
|
} |
|
|
|
return Okular::Document::UnknownPrintError; |
|
} |
|
|
|
QVariant DjVuGenerator::metaData(const QString &key, const QVariant &option) const |
|
{ |
|
Q_UNUSED(option) |
|
if (key == QLatin1String("DocumentTitle")) { |
|
return m_djvu->metaData(QStringLiteral("title")); |
|
} |
|
return QVariant(); |
|
} |
|
|
|
Okular::TextPage *DjVuGenerator::textPage(Okular::TextRequest *request) |
|
{ |
|
userMutex()->lock(); |
|
const Okular::Page *page = request->page(); |
|
QList<KDjVu::TextEntity> te; |
|
if (te.isEmpty()) |
|
te = m_djvu->textEntities(page->number(), QStringLiteral("word")); |
|
if (te.isEmpty()) |
|
te = m_djvu->textEntities(page->number(), QStringLiteral("line")); |
|
userMutex()->unlock(); |
|
QList<KDjVu::TextEntity>::ConstIterator it = te.constBegin(); |
|
QList<KDjVu::TextEntity>::ConstIterator itEnd = te.constEnd(); |
|
QList<Okular::TextEntity *> words; |
|
const KDjVu::Page *djvupage = m_djvu->pages().at(page->number()); |
|
for (; it != itEnd; ++it) { |
|
const KDjVu::TextEntity &cur = *it; |
|
words.append(new Okular::TextEntity(cur.text(), new Okular::NormalizedRect(cur.rect(), djvupage->width(), djvupage->height()))); |
|
} |
|
Okular::TextPage *textpage = new Okular::TextPage(words); |
|
return textpage; |
|
} |
|
|
|
void DjVuGenerator::loadPages(QVector<Okular::Page *> &pagesVector, int rotation) |
|
{ |
|
const QVector<KDjVu::Page *> &djvu_pages = m_djvu->pages(); |
|
int numofpages = djvu_pages.count(); |
|
pagesVector.resize(numofpages); |
|
|
|
for (int i = 0; i < numofpages; ++i) { |
|
const KDjVu::Page *p = djvu_pages.at(i); |
|
if (pagesVector[i]) |
|
delete pagesVector[i]; |
|
int w = p->width(); |
|
int h = p->height(); |
|
if (rotation % 2 == 1) |
|
qSwap(w, h); |
|
Okular::Page *page = new Okular::Page(i, w, h, (Okular::Rotation)(p->orientation() + rotation)); |
|
pagesVector[i] = page; |
|
|
|
QList<KDjVu::Annotation *> annots; |
|
QList<KDjVu::Link *> links; |
|
userMutex()->lock(); |
|
m_djvu->linksAndAnnotationsForPage(i, &links, &annots); |
|
userMutex()->unlock(); |
|
if (!links.isEmpty()) { |
|
QLinkedList<Okular::ObjectRect *> rects; |
|
QList<KDjVu::Link *>::ConstIterator it = links.constBegin(); |
|
QList<KDjVu::Link *>::ConstIterator itEnd = links.constEnd(); |
|
for (; it != itEnd; ++it) { |
|
KDjVu::Link *curlink = (*it); |
|
Okular::ObjectRect *newrect = convertKDjVuLink(i, curlink); |
|
if (newrect) |
|
rects.append(newrect); |
|
// delete the links as soon as we process them |
|
delete curlink; |
|
} |
|
if (rects.count() > 0) |
|
page->setObjectRects(rects); |
|
} |
|
if (!annots.isEmpty()) { |
|
QList<KDjVu::Annotation *>::ConstIterator it = annots.constBegin(); |
|
QList<KDjVu::Annotation *>::ConstIterator itEnd = annots.constEnd(); |
|
for (; it != itEnd; ++it) { |
|
KDjVu::Annotation *ann = (*it); |
|
Okular::Annotation *newann = convertKDjVuAnnotation(w, h, ann); |
|
if (newann) |
|
page->addAnnotation(newann); |
|
// delete the annotations as soon as we process them |
|
delete ann; |
|
} |
|
} |
|
} |
|
} |
|
|
|
Okular::ObjectRect *DjVuGenerator::convertKDjVuLink(int page, KDjVu::Link *link) const |
|
{ |
|
Okular::Action *newlink = nullptr; |
|
Okular::ObjectRect *newrect = nullptr; |
|
switch (link->type()) { |
|
case KDjVu::Link::PageLink: { |
|
KDjVu::PageLink *l = static_cast<KDjVu::PageLink *>(link); |
|
bool ok = true; |
|
QString target = l->page(); |
|
if ((target.length() > 0) && target.at(0) == QLatin1Char('#')) |
|
target.remove(0, 1); |
|
int tmppage = target.toInt(&ok); |
|
if (ok || target.isEmpty()) { |
|
Okular::DocumentViewport vp; |
|
if (!target.isEmpty()) |
|
vp.pageNumber = (target.at(0) == QLatin1Char('+') || target.at(0) == QLatin1Char('-')) ? page + tmppage : tmppage - 1; |
|
newlink = new Okular::GotoAction(QString(), vp); |
|
} |
|
break; |
|
} |
|
case KDjVu::Link::UrlLink: { |
|
KDjVu::UrlLink *l = static_cast<KDjVu::UrlLink *>(link); |
|
QString url = l->url(); |
|
newlink = new Okular::BrowseAction(QUrl(url)); |
|
break; |
|
} |
|
} |
|
if (newlink) { |
|
const KDjVu::Page *p = m_djvu->pages().at(page); |
|
int width = p->width(); |
|
int height = p->height(); |
|
bool scape_orientation = false; // hack by tokoe, should always create default page |
|
if (scape_orientation) |
|
qSwap(width, height); |
|
switch (link->areaType()) { |
|
case KDjVu::Link::RectArea: |
|
case KDjVu::Link::EllipseArea: { |
|
QRect r(QPoint(link->point().x(), p->height() - link->point().y() - link->size().height()), link->size()); |
|
bool ellipse = (link->areaType() == KDjVu::Link::EllipseArea); |
|
newrect = new Okular::ObjectRect(Okular::NormalizedRect(Okular::Utils::rotateRect(r, width, height, 0), width, height), ellipse, Okular::ObjectRect::Action, newlink); |
|
break; |
|
} |
|
case KDjVu::Link::PolygonArea: { |
|
QPolygon poly = link->polygon(); |
|
QPolygonF newpoly; |
|
for (int i = 0; i < poly.count(); ++i) { |
|
int x = poly.at(i).x(); |
|
int y = poly.at(i).y(); |
|
if (scape_orientation) |
|
qSwap(x, y); |
|
else { |
|
y = height - y; |
|
} |
|
newpoly << QPointF((double)(x) / width, (double)(y) / height); |
|
} |
|
if (!newpoly.isEmpty()) { |
|
newpoly << newpoly.first(); |
|
newrect = new Okular::ObjectRect(newpoly, Okular::ObjectRect::Action, newlink); |
|
} |
|
break; |
|
} |
|
default:; |
|
} |
|
if (!newrect) { |
|
delete newlink; |
|
} |
|
} |
|
return newrect; |
|
} |
|
|
|
Okular::Annotation *DjVuGenerator::convertKDjVuAnnotation(int w, int h, KDjVu::Annotation *ann) const |
|
{ |
|
Okular::Annotation *newann = nullptr; |
|
switch (ann->type()) { |
|
case KDjVu::Annotation::TextAnnotation: { |
|
KDjVu::TextAnnotation *txtann = static_cast<KDjVu::TextAnnotation *>(ann); |
|
Okular::TextAnnotation *newtxtann = new Okular::TextAnnotation(); |
|
// boundary |
|
QRect rect(QPoint(txtann->point().x(), h - txtann->point().y() - txtann->size().height()), txtann->size()); |
|
newtxtann->setBoundingRectangle(Okular::NormalizedRect(Okular::Utils::rotateRect(rect, w, h, 0), w, h)); |
|
// type |
|
newtxtann->setTextType(txtann->inlineText() ? Okular::TextAnnotation::InPlace : Okular::TextAnnotation::Linked); |
|
newtxtann->style().setOpacity(txtann->color().alphaF()); |
|
// FIXME remove once the annotation text handling is fixed |
|
newtxtann->setContents(ann->comment()); |
|
newann = newtxtann; |
|
break; |
|
} |
|
case KDjVu::Annotation::LineAnnotation: { |
|
KDjVu::LineAnnotation *lineann = static_cast<KDjVu::LineAnnotation *>(ann); |
|
Okular::LineAnnotation *newlineann = new Okular::LineAnnotation(); |
|
// boundary |
|
QPoint a(lineann->point().x(), h - lineann->point().y()); |
|
QPoint b(lineann->point2().x(), h - lineann->point2().y()); |
|
QRect rect = QRect(a, b).normalized(); |
|
newlineann->setBoundingRectangle(Okular::NormalizedRect(Okular::Utils::rotateRect(rect, w, h, 0), w, h)); |
|
// line points |
|
QLinkedList<Okular::NormalizedPoint> points; |
|
points.append(Okular::NormalizedPoint(a.x(), a.y(), w, h)); |
|
points.append(Okular::NormalizedPoint(b.x(), b.y(), w, h)); |
|
newlineann->setLinePoints(points); |
|
// arrow? |
|
if (lineann->isArrow()) |
|
newlineann->setLineEndStyle(Okular::LineAnnotation::OpenArrow); |
|
// width |
|
newlineann->style().setWidth(lineann->width()); |
|
newann = newlineann; |
|
break; |
|
} |
|
} |
|
if (newann) { |
|
// setting the common parameters |
|
newann->style().setColor(ann->color()); |
|
newann->setContents(ann->comment()); |
|
// creating an id as name for the annotation |
|
QString uid = QUuid::createUuid().toString(); |
|
uid.remove(0, 1); |
|
uid.chop(1); |
|
uid.remove(QLatin1Char('-')); |
|
newann->setUniqueName(uid); |
|
// is external |
|
newann->setFlags(newann->flags() | Okular::Annotation::External); |
|
} |
|
return newann; |
|
} |
|
|
|
#include "generator_djvu.moc"
|
|
|