mirror of
https://bitbucket.org/smil3y/kde-extraapps.git
synced 2025-02-24 19:02:53 +00:00
1158 lines
33 KiB
C++
1158 lines
33 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 "kdjvu.h"
|
|
|
|
#include <qbytearray.h>
|
|
#include <qdom.h>
|
|
#include <qfile.h>
|
|
#include <qhash.h>
|
|
#include <qlist.h>
|
|
#include <qpainter.h>
|
|
#include <qqueue.h>
|
|
#include <qstring.h>
|
|
|
|
#include <kdebug.h>
|
|
#include <klocale.h>
|
|
|
|
#include <libdjvu/ddjvuapi.h>
|
|
#include <libdjvu/miniexp.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
QDebug &operator<<( QDebug & s, const ddjvu_rect_t &r )
|
|
{
|
|
s.nospace() << "[" << r.x << "," << r.y << " - " << r.w << "x" << r.h << "]";
|
|
return s.space();
|
|
}
|
|
|
|
static void which_ddjvu_message( const ddjvu_message_t *msg )
|
|
{
|
|
#ifdef KDJVU_DEBUG
|
|
kDebug() << "which_djvu_message(...):" << msg->m_any.tag;
|
|
switch( msg->m_any.tag )
|
|
{
|
|
case DDJVU_ERROR:
|
|
kDebug().nospace() << "ERROR: file " << msg->m_error.filename << ", line " << msg->m_error.lineno;
|
|
kDebug().nospace() << "ERROR: function '" << msg->m_error.function << "'";
|
|
kDebug().nospace() << "ERROR: '" << msg->m_error.message << "'";
|
|
break;
|
|
case DDJVU_INFO:
|
|
kDebug().nospace() << "INFO: '" << msg->m_info.message << "'";
|
|
break;
|
|
case DDJVU_CHUNK:
|
|
kDebug().nospace() << "CHUNK: '" << QByteArray( msg->m_chunk.chunkid ) << "'";
|
|
break;
|
|
case DDJVU_PROGRESS:
|
|
kDebug().nospace() << "PROGRESS: '" << msg->m_progress.percent << "'";
|
|
break;
|
|
default: ;
|
|
}
|
|
#else
|
|
Q_UNUSED( msg );
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Explore the message queue until there are message left in it.
|
|
*/
|
|
static void handle_ddjvu_messages( ddjvu_context_t *ctx, int wait )
|
|
{
|
|
const ddjvu_message_t *msg;
|
|
if ( wait )
|
|
ddjvu_message_wait( ctx );
|
|
while ( ( msg = ddjvu_message_peek( ctx ) ) )
|
|
{
|
|
which_ddjvu_message( msg );
|
|
ddjvu_message_pop( ctx );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Explore the message queue until the message \p mid is found.
|
|
*/
|
|
static void wait_for_ddjvu_message( ddjvu_context_t *ctx, ddjvu_message_tag_t mid )
|
|
{
|
|
ddjvu_message_wait( ctx );
|
|
const ddjvu_message_t *msg;
|
|
while ( ( msg = ddjvu_message_peek( ctx ) ) && msg && ( msg->m_any.tag != mid ) )
|
|
{
|
|
which_ddjvu_message( msg );
|
|
ddjvu_message_pop( ctx );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convert a clockwise coefficient \p r for a rotation to a counter-clockwise
|
|
* and vice versa.
|
|
*/
|
|
static int flipRotation( int r )
|
|
{
|
|
return ( 4 - r ) % 4;
|
|
}
|
|
|
|
static miniexp_t find_second_in_pair( miniexp_t theexp, const char* which )
|
|
{
|
|
miniexp_t exp = theexp;
|
|
while ( exp )
|
|
{
|
|
miniexp_t cur = miniexp_car( exp );
|
|
if ( !miniexp_consp( cur ) || !miniexp_symbolp( miniexp_car( cur ) ) )
|
|
{
|
|
exp = miniexp_cdr( exp );
|
|
continue;
|
|
}
|
|
|
|
const QString id = QString::fromUtf8( miniexp_to_name( miniexp_car( cur ) ) );
|
|
if ( id == QLatin1String( which ) )
|
|
return miniexp_cadr( cur );
|
|
exp = miniexp_cdr( exp );
|
|
}
|
|
return miniexp_nil;
|
|
}
|
|
|
|
static bool find_replace_or_add_second_in_pair( miniexp_t theexp, const char* which, miniexp_t replacement )
|
|
{
|
|
miniexp_t exp = miniexp_cdddr( theexp );
|
|
while ( exp )
|
|
{
|
|
miniexp_t cur = miniexp_car( exp );
|
|
if ( !miniexp_consp( cur ) || !miniexp_symbolp( miniexp_car( cur ) ) )
|
|
{
|
|
exp = miniexp_cdr( exp );
|
|
continue;
|
|
}
|
|
|
|
const QString id = QString::fromUtf8( miniexp_to_name( miniexp_car( cur ) ) );
|
|
if ( id == QLatin1String( which ) )
|
|
{
|
|
miniexp_t reversed = miniexp_reverse( cur );
|
|
miniexp_rplaca( reversed, replacement );
|
|
cur = miniexp_reverse( reversed );
|
|
return true;
|
|
}
|
|
exp = miniexp_cdr( exp );
|
|
}
|
|
// TODO add the new replacement ad the end of the list
|
|
return false;
|
|
}
|
|
|
|
// ImageCacheItem
|
|
|
|
class ImageCacheItem
|
|
{
|
|
public:
|
|
ImageCacheItem( int p, int w, int h, const QImage& i )
|
|
: page( p ), width( w ), height( h ), img( i ) { }
|
|
|
|
int page;
|
|
int width;
|
|
int height;
|
|
QImage img;
|
|
};
|
|
|
|
|
|
// KdjVu::Page
|
|
|
|
KDjVu::Page::Page()
|
|
{
|
|
}
|
|
|
|
KDjVu::Page::~Page()
|
|
{
|
|
}
|
|
|
|
int KDjVu::Page::width() const
|
|
{
|
|
return m_width;
|
|
}
|
|
|
|
int KDjVu::Page::height() const
|
|
{
|
|
return m_height;
|
|
}
|
|
|
|
int KDjVu::Page::dpi() const
|
|
{
|
|
return m_dpi;
|
|
}
|
|
|
|
int KDjVu::Page::orientation() const
|
|
{
|
|
return m_orientation;
|
|
}
|
|
|
|
// KDjVu::Link
|
|
|
|
KDjVu::Link::~Link()
|
|
{
|
|
}
|
|
|
|
KDjVu::Link::LinkArea KDjVu::Link::areaType() const
|
|
{
|
|
return m_area;
|
|
}
|
|
|
|
QPoint KDjVu::Link::point() const
|
|
{
|
|
return m_point;
|
|
}
|
|
|
|
QSize KDjVu::Link::size() const
|
|
{
|
|
return m_size;
|
|
}
|
|
|
|
QPolygon KDjVu::Link::polygon() const
|
|
{
|
|
return m_poly;
|
|
}
|
|
|
|
// KDjVu::PageLink
|
|
|
|
KDjVu::PageLink::PageLink()
|
|
{
|
|
}
|
|
|
|
int KDjVu::PageLink::type() const
|
|
{
|
|
return KDjVu::Link::PageLink;
|
|
}
|
|
|
|
QString KDjVu::PageLink::page() const
|
|
{
|
|
return m_page;
|
|
}
|
|
|
|
// KDjVu::UrlLink
|
|
|
|
KDjVu::UrlLink::UrlLink()
|
|
{
|
|
}
|
|
|
|
int KDjVu::UrlLink::type() const
|
|
{
|
|
return KDjVu::Link::UrlLink;
|
|
}
|
|
|
|
QString KDjVu::UrlLink::url() const
|
|
{
|
|
return m_url;
|
|
}
|
|
|
|
// KDjVu::Annotation
|
|
|
|
KDjVu::Annotation::Annotation( miniexp_t anno )
|
|
: m_anno( anno )
|
|
{
|
|
}
|
|
|
|
KDjVu::Annotation::~Annotation()
|
|
{
|
|
}
|
|
|
|
QPoint KDjVu::Annotation::point() const
|
|
{
|
|
miniexp_t area = miniexp_nth( 3, m_anno );
|
|
int a = miniexp_to_int( miniexp_nth( 1, area ) );
|
|
int b = miniexp_to_int( miniexp_nth( 2, area ) );
|
|
return QPoint( a, b );
|
|
}
|
|
|
|
QString KDjVu::Annotation::comment() const
|
|
{
|
|
return QString::fromUtf8( miniexp_to_str( miniexp_nth( 2, m_anno ) ) );
|
|
}
|
|
|
|
void KDjVu::Annotation::setComment( const QString &comment )
|
|
{
|
|
miniexp_t exp = m_anno;
|
|
exp = miniexp_cdr( exp );
|
|
exp = miniexp_cdr( exp );
|
|
miniexp_rplaca( exp, miniexp_string( comment.toUtf8() ) );
|
|
}
|
|
|
|
QColor KDjVu::Annotation::color() const
|
|
{
|
|
return QColor();
|
|
}
|
|
|
|
void KDjVu::Annotation::setColor( const QColor & )
|
|
{
|
|
}
|
|
|
|
// KDjVu::TextAnnotation
|
|
|
|
KDjVu::TextAnnotation::TextAnnotation( miniexp_t anno )
|
|
: Annotation( anno ), m_inlineText( true )
|
|
{
|
|
const int num = miniexp_length( m_anno );
|
|
for ( int j = 4; j < num; ++j )
|
|
{
|
|
miniexp_t curelem = miniexp_nth( j, m_anno );
|
|
if ( !miniexp_listp( curelem ) )
|
|
continue;
|
|
|
|
QString id = QString::fromUtf8( miniexp_to_name( miniexp_nth( 0, curelem ) ) );
|
|
if ( id == QLatin1String( "pushpin" ) )
|
|
m_inlineText = false;
|
|
}
|
|
}
|
|
|
|
QSize KDjVu::TextAnnotation::size() const
|
|
{
|
|
miniexp_t area = miniexp_nth( 3, m_anno );
|
|
int c = miniexp_to_int( miniexp_nth( 3, area ) );
|
|
int d = miniexp_to_int( miniexp_nth( 4, area ) );
|
|
return QSize( c, d );
|
|
}
|
|
|
|
int KDjVu::TextAnnotation::type() const
|
|
{
|
|
return KDjVu::Annotation::TextAnnotation;
|
|
}
|
|
|
|
QColor KDjVu::TextAnnotation::color() const
|
|
{
|
|
miniexp_t col = find_second_in_pair( m_anno, "backclr" );
|
|
if ( !miniexp_symbolp( col ) )
|
|
return Qt::transparent;
|
|
|
|
return QColor( QString::fromUtf8( miniexp_to_name( col ) ) );
|
|
}
|
|
|
|
void KDjVu::TextAnnotation::setColor( const QColor &color )
|
|
{
|
|
const QByteArray col = color.name().toLatin1();
|
|
find_replace_or_add_second_in_pair( m_anno, "backclr", miniexp_symbol( col ) );
|
|
}
|
|
|
|
bool KDjVu::TextAnnotation::inlineText() const
|
|
{
|
|
return m_inlineText;
|
|
}
|
|
|
|
// KDjVu::LineAnnotation
|
|
|
|
KDjVu::LineAnnotation::LineAnnotation( miniexp_t anno )
|
|
: Annotation( anno ), m_isArrow( false ), m_width( miniexp_nil )
|
|
{
|
|
const int num = miniexp_length( m_anno );
|
|
for ( int j = 4; j < num; ++j )
|
|
{
|
|
miniexp_t curelem = miniexp_nth( j, m_anno );
|
|
if ( !miniexp_listp( curelem ) )
|
|
continue;
|
|
|
|
QString id = QString::fromUtf8( miniexp_to_name( miniexp_nth( 0, curelem ) ) );
|
|
if ( id == QLatin1String( "arrow" ) )
|
|
m_isArrow = true;
|
|
else if ( id == QLatin1String( "width" ) )
|
|
m_width = curelem;
|
|
}
|
|
}
|
|
|
|
int KDjVu::LineAnnotation::type() const
|
|
{
|
|
return KDjVu::Annotation::LineAnnotation;
|
|
}
|
|
|
|
QColor KDjVu::LineAnnotation::color() const
|
|
{
|
|
miniexp_t col = find_second_in_pair( m_anno, "lineclr" );
|
|
if ( !miniexp_symbolp( col ) )
|
|
return Qt::black;
|
|
|
|
return QColor( QString::fromUtf8( miniexp_to_name( col ) ) );
|
|
}
|
|
|
|
void KDjVu::LineAnnotation::setColor( const QColor &color )
|
|
{
|
|
const QByteArray col = color.name().toLatin1();
|
|
find_replace_or_add_second_in_pair( m_anno, "lineclr", miniexp_symbol( col ) );
|
|
}
|
|
|
|
QPoint KDjVu::LineAnnotation::point2() const
|
|
{
|
|
miniexp_t area = miniexp_nth( 3, m_anno );
|
|
int c = miniexp_to_int( miniexp_nth( 3, area ) );
|
|
int d = miniexp_to_int( miniexp_nth( 4, area ) );
|
|
return QPoint( c, d );
|
|
}
|
|
|
|
bool KDjVu::LineAnnotation::isArrow() const
|
|
{
|
|
return m_isArrow;
|
|
}
|
|
|
|
int KDjVu::LineAnnotation::width() const
|
|
{
|
|
if ( m_width == miniexp_nil )
|
|
return 1;
|
|
|
|
return miniexp_to_int( miniexp_cadr( m_width ) );
|
|
}
|
|
|
|
void KDjVu::LineAnnotation::setWidth( int width )
|
|
{
|
|
find_replace_or_add_second_in_pair( m_anno, "width", miniexp_number( width ) );
|
|
}
|
|
|
|
// KDjVu::TextEntity
|
|
|
|
KDjVu::TextEntity::TextEntity()
|
|
{
|
|
}
|
|
|
|
KDjVu::TextEntity::~TextEntity()
|
|
{
|
|
}
|
|
|
|
QString KDjVu::TextEntity::text() const
|
|
{
|
|
return m_text;
|
|
}
|
|
|
|
QRect KDjVu::TextEntity::rect() const
|
|
{
|
|
return m_rect;
|
|
}
|
|
|
|
|
|
class KDjVu::Private
|
|
{
|
|
public:
|
|
Private()
|
|
: m_djvu_cxt( 0 ), m_djvu_document( 0 ), m_format( 0 ), m_docBookmarks( 0 ),
|
|
m_cacheEnabled( true )
|
|
{
|
|
}
|
|
|
|
QImage generateImageTile( ddjvu_page_t *djvupage, int& res,
|
|
int width, int row, int xdelta, int height, int col, int ydelta );
|
|
|
|
void readBookmarks();
|
|
void fillBookmarksRecurse( QDomDocument& maindoc, QDomNode& curnode,
|
|
miniexp_t exp, int offset = -1 );
|
|
|
|
void readMetaData( int page );
|
|
|
|
int pageWithName( const QString & name );
|
|
|
|
ddjvu_context_t *m_djvu_cxt;
|
|
ddjvu_document_t *m_djvu_document;
|
|
ddjvu_format_t *m_format;
|
|
|
|
QVector<KDjVu::Page*> m_pages;
|
|
QVector<ddjvu_page_t *> m_pages_cache;
|
|
|
|
QList<ImageCacheItem*> mImgCache;
|
|
|
|
QHash<QString, QVariant> m_metaData;
|
|
QDomDocument * m_docBookmarks;
|
|
|
|
QHash<QString, int> m_pageNamesCache;
|
|
|
|
bool m_cacheEnabled;
|
|
|
|
static unsigned int s_formatmask[4];
|
|
};
|
|
|
|
unsigned int KDjVu::Private::s_formatmask[4] = { 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 };
|
|
|
|
QImage KDjVu::Private::generateImageTile( ddjvu_page_t *djvupage, int& res,
|
|
int width, int row, int xdelta, int height, int col, int ydelta )
|
|
{
|
|
ddjvu_rect_t renderrect;
|
|
renderrect.x = row * xdelta;
|
|
renderrect.y = col * ydelta;
|
|
int realwidth = qMin( width - renderrect.x, xdelta );
|
|
int realheight = qMin( height - renderrect.y, ydelta );
|
|
renderrect.w = realwidth;
|
|
renderrect.h = realheight;
|
|
#ifdef KDJVU_DEBUG
|
|
kDebug() << "renderrect:" << renderrect;
|
|
#endif
|
|
ddjvu_rect_t pagerect;
|
|
pagerect.x = 0;
|
|
pagerect.y = 0;
|
|
pagerect.w = width;
|
|
pagerect.h = height;
|
|
#ifdef KDJVU_DEBUG
|
|
kDebug() << "pagerect:" << pagerect;
|
|
#endif
|
|
handle_ddjvu_messages( m_djvu_cxt, false );
|
|
QImage res_img( realwidth, realheight, QImage::Format_RGB32 );
|
|
// the following line workarounds a rare crash in djvulibre;
|
|
// it should be fixed with >= 3.5.21
|
|
ddjvu_page_get_width( djvupage );
|
|
res = ddjvu_page_render( djvupage, DDJVU_RENDER_COLOR,
|
|
&pagerect, &renderrect, m_format, res_img.bytesPerLine(), (char *)res_img.bits() );
|
|
#ifdef KDJVU_DEBUG
|
|
kDebug() << "rendering result:" << res;
|
|
#endif
|
|
handle_ddjvu_messages( m_djvu_cxt, false );
|
|
|
|
return res_img;
|
|
}
|
|
|
|
void KDjVu::Private::readBookmarks()
|
|
{
|
|
if ( !m_djvu_document )
|
|
return;
|
|
|
|
miniexp_t outline;
|
|
while ( ( outline = ddjvu_document_get_outline( m_djvu_document ) ) == miniexp_dummy )
|
|
handle_ddjvu_messages( m_djvu_cxt, true );
|
|
|
|
if ( miniexp_listp( outline ) &&
|
|
( miniexp_length( outline ) > 0 ) &&
|
|
miniexp_symbolp( miniexp_nth( 0, outline ) ) &&
|
|
( QString::fromUtf8( miniexp_to_name( miniexp_nth( 0, outline ) ) ) == QLatin1String( "bookmarks" ) ) )
|
|
{
|
|
m_docBookmarks = new QDomDocument( "KDjVuBookmarks" );
|
|
fillBookmarksRecurse( *m_docBookmarks, *m_docBookmarks, outline, 1 );
|
|
ddjvu_miniexp_release( m_djvu_document, outline );
|
|
}
|
|
}
|
|
|
|
void KDjVu::Private::fillBookmarksRecurse( QDomDocument& maindoc, QDomNode& curnode,
|
|
miniexp_t exp, int offset )
|
|
{
|
|
if ( !miniexp_listp( exp ) )
|
|
return;
|
|
|
|
int l = miniexp_length( exp );
|
|
for ( int i = qMax( offset, 0 ); i < l; ++i )
|
|
{
|
|
miniexp_t cur = miniexp_nth( i, exp );
|
|
|
|
if ( miniexp_consp( cur ) && ( miniexp_length( cur ) > 0 ) &&
|
|
miniexp_stringp( miniexp_nth( 0, cur ) ) && miniexp_stringp( miniexp_nth( 1, cur ) ) )
|
|
{
|
|
QString title = QString::fromUtf8( miniexp_to_str( miniexp_nth( 0, cur ) ) );
|
|
QString dest = QString::fromUtf8( miniexp_to_str( miniexp_nth( 1, cur ) ) );
|
|
QDomElement el = maindoc.createElement( "item" );
|
|
el.setAttribute( "title", title );
|
|
if ( !dest.isEmpty() )
|
|
{
|
|
if ( dest.at( 0 ) == QLatin1Char( '#' ) )
|
|
{
|
|
dest.remove( 0, 1 );
|
|
bool isNumber = false;
|
|
dest.toInt( &isNumber );
|
|
if ( isNumber )
|
|
{
|
|
// it might be an actual page number, but could also be a page label
|
|
// so resolve the number, and get the real page number
|
|
int pageNo = pageWithName( dest );
|
|
if ( pageNo != -1 )
|
|
{
|
|
el.setAttribute( "PageNumber", QString::number( pageNo + 1 ) );
|
|
}
|
|
else
|
|
{
|
|
el.setAttribute( "PageNumber", dest );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
el.setAttribute( "PageName", dest );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
el.setAttribute( "URL", dest );
|
|
}
|
|
}
|
|
curnode.appendChild( el );
|
|
if ( !el.isNull() && ( miniexp_length( cur ) > 2 ) )
|
|
{
|
|
fillBookmarksRecurse( maindoc, el, cur, 2 );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void KDjVu::Private::readMetaData( int page )
|
|
{
|
|
if ( !m_djvu_document )
|
|
return;
|
|
|
|
miniexp_t annots;
|
|
while ( ( annots = ddjvu_document_get_pageanno( m_djvu_document, page ) ) == miniexp_dummy )
|
|
handle_ddjvu_messages( m_djvu_cxt, true );
|
|
|
|
if ( !miniexp_listp( annots ) || miniexp_length( annots ) == 0 )
|
|
return;
|
|
|
|
miniexp_t exp = miniexp_nth( 0, annots );
|
|
int size = miniexp_length( exp );
|
|
if ( size <= 1 ||
|
|
qstrncmp( miniexp_to_name( miniexp_nth( 0, exp ) ), "metadata", 8 ) )
|
|
return;
|
|
|
|
for ( int i = 1; i < size; ++i )
|
|
{
|
|
miniexp_t cur = miniexp_nth( i, exp );
|
|
if ( miniexp_length( cur ) != 2 )
|
|
continue;
|
|
|
|
QString id = QString::fromUtf8( miniexp_to_name( miniexp_nth( 0, cur ) ) );
|
|
QString value = QString::fromUtf8( miniexp_to_str( miniexp_nth( 1, cur ) ) );
|
|
m_metaData[ id.toLower() ] = value;
|
|
}
|
|
}
|
|
|
|
int KDjVu::Private::pageWithName( const QString & name )
|
|
{
|
|
const int pageNo = m_pageNamesCache.value( name, -1 );
|
|
if ( pageNo != -1 )
|
|
return pageNo;
|
|
|
|
const QByteArray utfName = name.toUtf8();
|
|
const int fileNum = ddjvu_document_get_filenum( m_djvu_document );
|
|
ddjvu_fileinfo_t info;
|
|
for ( int i = 0; i < fileNum; ++i )
|
|
{
|
|
if ( DDJVU_JOB_OK != ddjvu_document_get_fileinfo( m_djvu_document, i, &info ) )
|
|
continue;
|
|
if ( info.type != 'P' )
|
|
continue;
|
|
if ( ( utfName == info.id ) || ( utfName == info.name ) || ( utfName == info.title ) )
|
|
{
|
|
m_pageNamesCache.insert( name, info.pageno );
|
|
return info.pageno;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
KDjVu::KDjVu() : d( new Private )
|
|
{
|
|
// creating the djvu context
|
|
d->m_djvu_cxt = ddjvu_context_create( "KDjVu" );
|
|
// creating the rendering format
|
|
#if DDJVUAPI_VERSION >= 18
|
|
d->m_format = ddjvu_format_create( DDJVU_FORMAT_RGBMASK32, 4, Private::s_formatmask );
|
|
#else
|
|
d->m_format = ddjvu_format_create( DDJVU_FORMAT_RGBMASK32, 3, Private::s_formatmask );
|
|
#endif
|
|
ddjvu_format_set_row_order( d->m_format, 1 );
|
|
ddjvu_format_set_y_direction( d->m_format, 1 );
|
|
}
|
|
|
|
|
|
KDjVu::~KDjVu()
|
|
{
|
|
closeFile();
|
|
|
|
ddjvu_format_release( d->m_format );
|
|
ddjvu_context_release( d->m_djvu_cxt );
|
|
|
|
delete d;
|
|
}
|
|
|
|
bool KDjVu::openFile( const QString & fileName )
|
|
{
|
|
// first, close the old file
|
|
if ( d->m_djvu_document )
|
|
closeFile();
|
|
|
|
// load the document...
|
|
d->m_djvu_document = ddjvu_document_create_by_filename( d->m_djvu_cxt, QFile::encodeName( fileName ), true );
|
|
if ( !d->m_djvu_document ) return false;
|
|
// ...and wait for its loading
|
|
wait_for_ddjvu_message( d->m_djvu_cxt, DDJVU_DOCINFO );
|
|
if ( ddjvu_document_decoding_error( d->m_djvu_document ) )
|
|
{
|
|
ddjvu_document_release( d->m_djvu_document );
|
|
d->m_djvu_document = 0;
|
|
return false;
|
|
}
|
|
|
|
kDebug() << "# of pages:" << ddjvu_document_get_pagenum( d->m_djvu_document );
|
|
int numofpages = ddjvu_document_get_pagenum( d->m_djvu_document );
|
|
d->m_pages.clear();
|
|
d->m_pages.resize( numofpages );
|
|
d->m_pages_cache.clear();
|
|
d->m_pages_cache.resize( numofpages );
|
|
|
|
// get the document type
|
|
QString doctype;
|
|
switch ( ddjvu_document_get_type( d->m_djvu_document ) )
|
|
{
|
|
case DDJVU_DOCTYPE_UNKNOWN:
|
|
doctype = i18nc( "Type of DjVu document", "Unknown" );
|
|
break;
|
|
case DDJVU_DOCTYPE_SINGLEPAGE:
|
|
doctype = i18nc( "Type of DjVu document", "Single Page" );
|
|
break;
|
|
case DDJVU_DOCTYPE_BUNDLED:
|
|
doctype = i18nc( "Type of DjVu document", "Bundled" );
|
|
break;
|
|
case DDJVU_DOCTYPE_INDIRECT:
|
|
doctype = i18nc( "Type of DjVu document", "Indirect" );
|
|
break;
|
|
case DDJVU_DOCTYPE_OLD_BUNDLED:
|
|
doctype = i18nc( "Type of DjVu document", "Bundled (old)" );
|
|
break;
|
|
case DDJVU_DOCTYPE_OLD_INDEXED:
|
|
doctype = i18nc( "Type of DjVu document", "Indexed (old)" );
|
|
break;
|
|
}
|
|
if ( !doctype.isEmpty() )
|
|
d->m_metaData[ "documentType" ] = doctype;
|
|
// get the number of components
|
|
d->m_metaData[ "componentFile" ] = ddjvu_document_get_filenum( d->m_djvu_document );
|
|
|
|
// read the pages
|
|
for ( int i = 0; i < numofpages; ++i )
|
|
{
|
|
ddjvu_status_t sts;
|
|
ddjvu_pageinfo_t info;
|
|
while ( ( sts = ddjvu_document_get_pageinfo( d->m_djvu_document, i, &info ) ) < DDJVU_JOB_OK )
|
|
handle_ddjvu_messages( d->m_djvu_cxt, true );
|
|
if ( sts >= DDJVU_JOB_FAILED )
|
|
{
|
|
kDebug().nospace() << "\t>>> page " << i << " failed: " << sts;
|
|
return false;
|
|
}
|
|
|
|
KDjVu::Page *p = new KDjVu::Page();
|
|
p->m_width = info.width;
|
|
p->m_height = info.height;
|
|
p->m_dpi = info.dpi;
|
|
#if DDJVUAPI_VERSION >= 18
|
|
p->m_orientation = flipRotation( info.rotation );
|
|
#else
|
|
p->m_orientation = 0;
|
|
#endif
|
|
d->m_pages[i] = p;
|
|
}
|
|
|
|
// reading the metadata from the first page only should be enough
|
|
if ( numofpages > 0 )
|
|
d->readMetaData( 0 );
|
|
|
|
return true;
|
|
}
|
|
|
|
void KDjVu::closeFile()
|
|
{
|
|
// deleting the old TOC
|
|
delete d->m_docBookmarks;
|
|
d->m_docBookmarks = 0;
|
|
// deleting the pages
|
|
qDeleteAll( d->m_pages );
|
|
d->m_pages.clear();
|
|
// releasing the djvu pages
|
|
QVector<ddjvu_page_t *>::Iterator it = d->m_pages_cache.begin(), itEnd = d->m_pages_cache.end();
|
|
for ( ; it != itEnd; ++it )
|
|
ddjvu_page_release( *it );
|
|
d->m_pages_cache.clear();
|
|
// clearing the image cache
|
|
qDeleteAll( d->mImgCache );
|
|
d->mImgCache.clear();
|
|
// clearing the old metadata
|
|
d->m_metaData.clear();
|
|
// cleaing the page names mapping
|
|
d->m_pageNamesCache.clear();
|
|
// releasing the old document
|
|
if ( d->m_djvu_document )
|
|
ddjvu_document_release( d->m_djvu_document );
|
|
d->m_djvu_document = 0;
|
|
}
|
|
|
|
QVariant KDjVu::metaData( const QString & key ) const
|
|
{
|
|
QHash<QString, QVariant>::ConstIterator it = d->m_metaData.constFind( key );
|
|
return it != d->m_metaData.constEnd() ? it.value() : QVariant();
|
|
}
|
|
|
|
const QDomDocument * KDjVu::documentBookmarks() const
|
|
{
|
|
if ( !d->m_docBookmarks )
|
|
d->readBookmarks();
|
|
return d->m_docBookmarks;
|
|
}
|
|
|
|
void KDjVu::linksAndAnnotationsForPage( int pageNum, QList<KDjVu::Link*> *links, QList<KDjVu::Annotation*> *annotations ) const
|
|
{
|
|
if ( ( pageNum < 0 ) || ( pageNum >= d->m_pages.count() ) || ( !links && !annotations ) )
|
|
return;
|
|
|
|
miniexp_t annots;
|
|
while ( ( annots = ddjvu_document_get_pageanno( d->m_djvu_document, pageNum ) ) == miniexp_dummy )
|
|
handle_ddjvu_messages( d->m_djvu_cxt, true );
|
|
|
|
if ( !miniexp_listp( annots ) )
|
|
return;
|
|
|
|
if ( links )
|
|
links->clear();
|
|
if ( annotations )
|
|
annotations->clear();
|
|
|
|
int l = miniexp_length( annots );
|
|
for ( int i = 0; i < l; ++i )
|
|
{
|
|
miniexp_t cur = miniexp_nth( i, annots );
|
|
int num = miniexp_length( cur );
|
|
if ( ( num < 4 ) || !miniexp_symbolp( miniexp_nth( 0, cur ) ) ||
|
|
( qstrncmp( miniexp_to_name( miniexp_nth( 0, cur ) ), "maparea", 7 ) != 0 ) )
|
|
continue;
|
|
|
|
QString target;
|
|
QString type;
|
|
if ( miniexp_symbolp( miniexp_nth( 0, miniexp_nth( 3, cur ) ) ) )
|
|
type = QString::fromUtf8( miniexp_to_name( miniexp_nth( 0, miniexp_nth( 3, cur ) ) ) );
|
|
KDjVu::Link* link = 0;
|
|
KDjVu::Annotation* ann = 0;
|
|
miniexp_t urlexp = miniexp_nth( 1, cur );
|
|
if ( links &&
|
|
( type == QLatin1String( "rect" ) ||
|
|
type == QLatin1String( "oval" ) ||
|
|
type == QLatin1String( "poly" ) ) )
|
|
{
|
|
if ( miniexp_stringp( urlexp ) )
|
|
{
|
|
target = QString::fromUtf8( miniexp_to_str( miniexp_nth( 1, cur ) ) );
|
|
}
|
|
else if ( miniexp_listp( urlexp ) && ( miniexp_length( urlexp ) == 3 ) &&
|
|
miniexp_symbolp( miniexp_nth( 0, urlexp ) ) &&
|
|
( qstrncmp( miniexp_to_name( miniexp_nth( 0, urlexp ) ), "url", 3 ) == 0 ) )
|
|
{
|
|
target = QString::fromUtf8( miniexp_to_str( miniexp_nth( 1, urlexp ) ) );
|
|
}
|
|
if ( target.isEmpty() || ( ( target.length() > 0 ) && target.at(0) == QLatin1Char( '#' ) ) )
|
|
{
|
|
KDjVu::PageLink* plink = new KDjVu::PageLink();
|
|
plink->m_page = target;
|
|
link = plink;
|
|
}
|
|
else
|
|
{
|
|
KDjVu::UrlLink* ulink = new KDjVu::UrlLink();
|
|
ulink->m_url = target;
|
|
link = ulink;
|
|
}
|
|
}
|
|
else if ( annotations &&
|
|
( type == QLatin1String( "text" ) ||
|
|
type == QLatin1String( "line" ) ) )
|
|
{
|
|
if ( type == QLatin1String( "text" ) )
|
|
{
|
|
KDjVu::TextAnnotation * textann = new KDjVu::TextAnnotation( cur );
|
|
ann = textann;
|
|
}
|
|
else if ( type == QLatin1String( "line" ) )
|
|
{
|
|
KDjVu::LineAnnotation * lineann = new KDjVu::LineAnnotation( cur );
|
|
ann = lineann;
|
|
}
|
|
}
|
|
if ( link /* safety check */ && links )
|
|
{
|
|
link->m_area = KDjVu::Link::UnknownArea;
|
|
miniexp_t area = miniexp_nth( 3, cur );
|
|
int arealength = miniexp_length( area );
|
|
if ( ( arealength == 5 ) && ( type == QLatin1String( "rect" ) || type == QLatin1String( "oval" ) ) )
|
|
{
|
|
link->m_point = QPoint( miniexp_to_int( miniexp_nth( 1, area ) ), miniexp_to_int( miniexp_nth( 2, area ) ) );
|
|
link->m_size = QSize( miniexp_to_int( miniexp_nth( 3, area ) ), miniexp_to_int( miniexp_nth( 4, area ) ) );
|
|
if ( type == QLatin1String( "rect" ) )
|
|
{
|
|
link->m_area = KDjVu::Link::RectArea;
|
|
}
|
|
else
|
|
{
|
|
link->m_area = KDjVu::Link::EllipseArea;
|
|
}
|
|
}
|
|
else if ( ( arealength > 0 ) && ( arealength % 2 == 1 ) &&
|
|
type == QLatin1String( "poly" ) )
|
|
{
|
|
link->m_area = KDjVu::Link::PolygonArea;
|
|
QPolygon poly;
|
|
for ( int j = 1; j < arealength; j += 2 )
|
|
{
|
|
poly << QPoint( miniexp_to_int( miniexp_nth( j, area ) ), miniexp_to_int( miniexp_nth( j + 1, area ) ) );
|
|
}
|
|
link->m_poly = poly;
|
|
}
|
|
|
|
if ( link->m_area != KDjVu::Link::UnknownArea )
|
|
links->append( link );
|
|
}
|
|
else if ( ann /* safety check */ && annotations )
|
|
{
|
|
annotations->append( ann );
|
|
}
|
|
}
|
|
}
|
|
|
|
const QVector<KDjVu::Page*> &KDjVu::pages() const
|
|
{
|
|
return d->m_pages;
|
|
}
|
|
|
|
QImage KDjVu::image( int page, int width, int height, int rotation )
|
|
{
|
|
if ( d->m_cacheEnabled )
|
|
{
|
|
bool found = false;
|
|
QList<ImageCacheItem*>::Iterator it = d->mImgCache.begin(), itEnd = d->mImgCache.end();
|
|
for ( ; ( it != itEnd ) && !found; ++it )
|
|
{
|
|
ImageCacheItem* cur = *it;
|
|
if ( ( cur->page == page ) &&
|
|
( rotation % 2 == 0
|
|
? cur->width == width && cur->height == height
|
|
: cur->width == height && cur->height == width ) )
|
|
found = true;
|
|
}
|
|
if ( found )
|
|
{
|
|
// taking the element and pushing to the top of the list
|
|
--it;
|
|
ImageCacheItem* cur2 = *it;
|
|
d->mImgCache.erase( it );
|
|
d->mImgCache.push_front( cur2 );
|
|
|
|
return cur2->img;
|
|
}
|
|
}
|
|
|
|
if ( !d->m_pages_cache.at( page ) )
|
|
{
|
|
ddjvu_page_t *newpage = ddjvu_page_create_by_pageno( d->m_djvu_document, page );
|
|
// wait for the new page to be loaded
|
|
ddjvu_status_t sts;
|
|
while ( ( sts = ddjvu_page_decoding_status( newpage ) ) < DDJVU_JOB_OK )
|
|
handle_ddjvu_messages( d->m_djvu_cxt, true );
|
|
d->m_pages_cache[page] = newpage;
|
|
}
|
|
ddjvu_page_t *djvupage = d->m_pages_cache[page];
|
|
|
|
/*
|
|
if ( ddjvu_page_get_rotation( djvupage ) != flipRotation( rotation ) )
|
|
{
|
|
// TODO: test documents with initial rotation != 0
|
|
// ddjvu_page_set_rotation( djvupage, m_pages.at( page )->orientation() );
|
|
ddjvu_page_set_rotation( djvupage, (ddjvu_page_rotation_t)flipRotation( rotation ) );
|
|
}
|
|
*/
|
|
|
|
static const int xdelta = 1500;
|
|
static const int ydelta = 1500;
|
|
|
|
int xparts = width / xdelta + 1;
|
|
int yparts = height / ydelta + 1;
|
|
|
|
QImage newimg;
|
|
|
|
int res = 10000;
|
|
if ( ( xparts == 1 ) && ( yparts == 1 ) )
|
|
{
|
|
// only one part -- render at once with no need to auxiliary image
|
|
newimg = d->generateImageTile( djvupage, res,
|
|
width, 0, xdelta, height, 0, ydelta );
|
|
}
|
|
else
|
|
{
|
|
// more than one part -- need to render piece-by-piece and to compose
|
|
// the results
|
|
newimg = QImage( width, height, QImage::Format_RGB32 );
|
|
QPainter p;
|
|
p.begin( &newimg );
|
|
int parts = xparts * yparts;
|
|
for ( int i = 0; i < parts; ++i )
|
|
{
|
|
int row = i % xparts;
|
|
int col = i / xparts;
|
|
int tmpres = 0;
|
|
QImage tempp = d->generateImageTile( djvupage, tmpres,
|
|
width, row, xdelta, height, col, ydelta );
|
|
if ( tmpres )
|
|
{
|
|
p.drawImage( row * xdelta, col * ydelta, tempp );
|
|
}
|
|
res = qMin( tmpres, res );
|
|
}
|
|
p.end();
|
|
}
|
|
|
|
if ( res && d->m_cacheEnabled )
|
|
{
|
|
// delete all the cached pixmaps for the current page with a size that
|
|
// differs no more than 35% of the new pixmap size
|
|
int imgsize = newimg.width() * newimg.height();
|
|
if ( imgsize > 0 )
|
|
{
|
|
for( int i = 0; i < d->mImgCache.count(); )
|
|
{
|
|
ImageCacheItem* cur = d->mImgCache.at(i);
|
|
if ( ( cur->page == page ) &&
|
|
( abs( cur->img.width() * cur->img.height() - imgsize ) < imgsize * 0.35 ) )
|
|
{
|
|
d->mImgCache.removeAt( i );
|
|
delete cur;
|
|
}
|
|
else
|
|
++i;
|
|
}
|
|
}
|
|
|
|
// the image cache has too many elements, remove the last
|
|
if ( d->mImgCache.size() >= 10 )
|
|
{
|
|
delete d->mImgCache.last();
|
|
d->mImgCache.removeLast();
|
|
}
|
|
ImageCacheItem* ich = new ImageCacheItem( page, width, height, newimg );
|
|
d->mImgCache.push_front( ich );
|
|
}
|
|
|
|
return newimg;
|
|
}
|
|
|
|
bool KDjVu::exportAsPostScript( const QString & fileName, const QList<int>& pageList ) const
|
|
{
|
|
if ( !d->m_djvu_document || fileName.trimmed().isEmpty() || pageList.isEmpty() )
|
|
return false;
|
|
|
|
QFile f( fileName );
|
|
f.open( QIODevice::ReadWrite );
|
|
bool ret = exportAsPostScript( &f, pageList );
|
|
if ( ret )
|
|
{
|
|
f.close();
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
bool KDjVu::exportAsPostScript( QFile* file, const QList<int>& pageList ) const
|
|
{
|
|
if ( !d->m_djvu_document || !file || pageList.isEmpty() )
|
|
return false;
|
|
|
|
FILE* f = fdopen( file->handle(), "w+" );
|
|
if ( !f )
|
|
{
|
|
kDebug() << "error while getting the FILE*";
|
|
return false;
|
|
}
|
|
|
|
QString pl;
|
|
foreach ( int p, pageList )
|
|
{
|
|
if ( !pl.isEmpty() )
|
|
pl += QString::fromLatin1( "," );
|
|
pl += QString::number( p );
|
|
}
|
|
pl.prepend( "-page=" );
|
|
|
|
// setting the options
|
|
static const int optc = 1;
|
|
const char ** optv = (const char**)malloc( 1 * sizeof( char* ) );
|
|
QByteArray plb = pl.toAscii();
|
|
optv[0] = plb.constData();
|
|
|
|
ddjvu_job_t *printjob = ddjvu_document_print( d->m_djvu_document, f, optc, optv );
|
|
while ( !ddjvu_job_done( printjob ) )
|
|
handle_ddjvu_messages( d->m_djvu_cxt, true );
|
|
|
|
free( optv );
|
|
|
|
return fclose( f ) == 0;
|
|
}
|
|
|
|
QList<KDjVu::TextEntity> KDjVu::textEntities( int page, const QString & granularity ) const
|
|
{
|
|
if ( ( page < 0 ) || ( page >= d->m_pages.count() ) )
|
|
return QList<KDjVu::TextEntity>();
|
|
|
|
miniexp_t r;
|
|
while ( ( r = ddjvu_document_get_pagetext( d->m_djvu_document, page, 0 ) ) == miniexp_dummy )
|
|
handle_ddjvu_messages( d->m_djvu_cxt, true );
|
|
|
|
if ( r == miniexp_nil )
|
|
return QList<KDjVu::TextEntity>();
|
|
|
|
QList<KDjVu::TextEntity> ret;
|
|
|
|
int height = d->m_pages.at( page )->height();
|
|
|
|
QQueue<miniexp_t> queue;
|
|
queue.enqueue( r );
|
|
|
|
while ( !queue.isEmpty() )
|
|
{
|
|
miniexp_t cur = queue.dequeue();
|
|
|
|
if ( miniexp_listp( cur )
|
|
&& ( miniexp_length( cur ) > 0 )
|
|
&& miniexp_symbolp( miniexp_nth( 0, cur ) ) )
|
|
{
|
|
int size = miniexp_length( cur );
|
|
QString sym = QString::fromUtf8( miniexp_to_name( miniexp_nth( 0, cur ) ) );
|
|
if ( sym == granularity )
|
|
{
|
|
if ( size >= 6 )
|
|
{
|
|
int xmin = miniexp_to_int( miniexp_nth( 1, cur ) );
|
|
int ymin = miniexp_to_int( miniexp_nth( 2, cur ) );
|
|
int xmax = miniexp_to_int( miniexp_nth( 3, cur ) );
|
|
int ymax = miniexp_to_int( miniexp_nth( 4, cur ) );
|
|
QRect rect( xmin, height - ymax, xmax - xmin, ymax - ymin );
|
|
KDjVu::TextEntity entity;
|
|
entity.m_rect = rect;
|
|
entity.m_text = QString::fromUtf8( miniexp_to_str( miniexp_nth( 5, cur ) ) );
|
|
ret.append( entity );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for ( int i = 5; i < size; ++i )
|
|
queue.enqueue( miniexp_nth( i, cur ) );
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void KDjVu::setCacheEnabled( bool enable )
|
|
{
|
|
if ( enable == d->m_cacheEnabled )
|
|
return;
|
|
|
|
d->m_cacheEnabled = enable;
|
|
if ( !d->m_cacheEnabled )
|
|
{
|
|
qDeleteAll( d->mImgCache );
|
|
d->mImgCache.clear();
|
|
}
|
|
}
|
|
|
|
bool KDjVu::isCacheEnabled() const
|
|
{
|
|
return d->m_cacheEnabled;
|
|
}
|
|
|
|
int KDjVu::pageNumber( const QString & name ) const
|
|
{
|
|
if ( !d->m_djvu_document )
|
|
return -1;
|
|
|
|
return d->pageWithName( name );
|
|
}
|