mirror of
https://invent.kde.org/graphics/okular
synced 2024-11-05 18:34:53 +00:00
b04a2daa6b
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
436 lines
16 KiB
C++
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"
|