diff --git a/generators/djvu/kdjvu.cpp b/generators/djvu/kdjvu.cpp index c2e345ce7..36ae46b19 100644 --- a/generators/djvu/kdjvu.cpp +++ b/generators/djvu/kdjvu.cpp @@ -1,1158 +1,1162 @@ /*************************************************************************** * Copyright (C) 2006 by Pino Toscano * * * * 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 #include #include #include #include #include #include #include #include #include #include #include #include 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 qDebug() << "which_djvu_message(...):" << msg->m_any.tag; switch( msg->m_any.tag ) { case DDJVU_ERROR: qDebug().nospace() << "ERROR: file " << msg->m_error.filename << ", line " << msg->m_error.lineno; qDebug().nospace() << "ERROR: function '" << msg->m_error.function << "'"; qDebug().nospace() << "ERROR: '" << msg->m_error.message << "'"; break; case DDJVU_INFO: qDebug().nospace() << "INFO: '" << msg->m_info.message << "'"; break; case DDJVU_CHUNK: qDebug().nospace() << "CHUNK: '" << QByteArray( msg->m_chunk.chunkid ) << "'"; break; case DDJVU_PROGRESS: qDebug().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().constData() ) ); } 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.constData() ) ); } 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.constData() ) ); } 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 m_pages; QVector m_pages_cache; QList mImgCache; QHash m_metaData; QDomDocument * m_docBookmarks; QHash 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 qDebug() << "renderrect:" << renderrect; #endif ddjvu_rect_t pagerect; pagerect.x = 0; pagerect.y = 0; pagerect.w = width; pagerect.h = height; #ifdef KDJVU_DEBUG qDebug() << "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() ); + if (!res) + { + res_img.fill(Qt::white); + } #ifdef KDJVU_DEBUG qDebug() << "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( QStringLiteral("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( QStringLiteral("item") ); el.setAttribute( QStringLiteral("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( QStringLiteral("PageNumber"), QString::number( pageNo + 1 ) ); } else { el.setAttribute( QStringLiteral("PageNumber"), dest ); } } else { el.setAttribute( QStringLiteral("PageName"), dest ); } } else { el.setAttribute( QStringLiteral("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 ).constData(), 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; } qDebug() << "# 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[ QStringLiteral("documentType") ] = doctype; // get the number of components d->m_metaData[ QStringLiteral("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 ) { qDebug().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::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::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 *links, QList *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::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::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 ); + bool found = false; + QList::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; - } + 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& 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& pageList ) const { if ( !d->m_djvu_document || !file || pageList.isEmpty() ) return false; FILE* f = fdopen( file->handle(), "w+" ); if ( !f ) { qDebug() << "error while getting the FILE*"; return false; } QString pl; foreach ( int p, pageList ) { if ( !pl.isEmpty() ) pl += QLatin1String( "," ); pl += QString::number( p ); } pl.prepend( QStringLiteral ( "-page=" ) ); // setting the options static const int optc = 1; const char ** optv = (const char**)malloc( 1 * sizeof( char* ) ); QByteArray plb = pl.toLatin1(); 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::textEntities( int page, const QString & granularity ) const { if ( ( page < 0 ) || ( page >= d->m_pages.count() ) ) return QList(); 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(); QList ret; int height = d->m_pages.at( page )->height(); QQueue 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 ); }