okular/generators/djvu/generator_djvu.cpp
Alexander Kandaurov b04a2daa6b Fix DjVu links pointing to pages with different dimensions
Summary:
Currently the coordinates of a link rectangle in a DjVu document are computed with respect to the size of the page the link is pointing to. Because of that, links only appear in correct places if the dimensions of a target page are the same as of the page containing the link.

The attached file demonstrating the issue contains two pages, one in portrait orientation and one in landscape, the first pointing to the second. The active zone for the link is different from where it is in other viewers.
{F6443987}

Reviewers: #okular

Subscribers: okular-devel

Tags: #okular

Differential Revision: https://phabricator.kde.org/D17194
2018-11-28 23:11:00 +01:00

436 lines
16 KiB
C++

/***************************************************************************
* Copyright (C) 2006 by Pino Toscano <toscano.pino@tiscali.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 of the License, or *
* (at your option) any later version. *
***************************************************************************/
#include "generator_djvu.h"
#include <core/action.h>
#include <core/annotations.h>
#include <core/area.h>
#include <core/document.h>
#include <core/page.h>
#include <core/textpage.h>
#include <core/utils.h>
#include <core/fileprinter.h>
#include <QDomDocument>
#include <QMutex>
#include <QPixmap>
#include <QString>
#include <QUuid>
#include <QPrinter>
#include <KAboutData>
#include <QDebug>
#include <KLocalizedString>
#include <QTemporaryFile>
#include <QDir>
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;
}
bool DjVuGenerator::print( QPrinter& printer )
{
bool result = false;
// Create tempfile to write to
QTemporaryFile tf(QDir::tempPath() + QLatin1String("/okular_XXXXXX.ps"));
if ( !tf.open() )
return false;
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();
int ret = Okular::FilePrinter::printFile( printer, fileName, document()->orientation(),
Okular::FilePrinter::SystemDeletesFiles,
Okular::FilePrinter::ApplicationSelectsPages,
document()->bookmarkedPageRange() );
result = ( ret >=0 );
}
return result;
}
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 0
m_djvu->textEntities( page->number(), "char" );
#endif
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"