djvu toc:
[kdegraphics.git] / okular / generators / djvu / kdjvu.cpp
blobdb8d82b9557659f42afaa44b7b4c66100ca50cf8
1 /***************************************************************************
2 * Copyright (C) 2006 by Pino Toscano <toscano.pino@tiscali.it> *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 ***************************************************************************/
10 #include "kdjvu.h"
12 #include <qbytearray.h>
13 #include <qdom.h>
14 #include <qfile.h>
15 #include <qhash.h>
16 #include <qlist.h>
17 #include <qpainter.h>
18 #include <qqueue.h>
19 #include <qstring.h>
21 #include <kdebug.h>
22 #include <klocale.h>
24 #include <libdjvu/ddjvuapi.h>
25 #include <libdjvu/miniexp.h>
27 #include <stdio.h>
29 QDebug &operator<<( QDebug & s, const ddjvu_rect_t &r )
31 s.nospace() << "[" << r.x << "," << r.y << " - " << r.w << "x" << r.h << "]";
32 return s.space();
35 static void which_ddjvu_message( const ddjvu_message_t *msg )
37 #ifdef KDJVU_DEBUG
38 kDebug() << "which_djvu_message(...):" << msg->m_any.tag;
39 switch( msg->m_any.tag )
41 case DDJVU_ERROR:
42 kDebug().nospace() << "ERROR: file " << msg->m_error.filename << ", line " << msg->m_error.lineno;
43 kDebug().nospace() << "ERROR: function '" << msg->m_error.function << "'";
44 kDebug().nospace() << "ERROR: '" << msg->m_error.message << "'";
45 break;
46 case DDJVU_INFO:
47 kDebug().nospace() << "INFO: '" << msg->m_info.message << "'";
48 break;
49 case DDJVU_CHUNK:
50 kDebug().nospace() << "CHUNK: '" << QByteArray( msg->m_chunk.chunkid ) << "'";
51 break;
52 case DDJVU_PROGRESS:
53 kDebug().nospace() << "PROGRESS: '" << msg->m_progress.percent << "'";
54 break;
55 default: ;
57 #else
58 Q_UNUSED( msg );
59 #endif
62 /**
63 * Explore the message queue until there are message left in it.
65 static void handle_ddjvu_messages( ddjvu_context_t *ctx, int wait )
67 const ddjvu_message_t *msg;
68 if ( wait )
69 ddjvu_message_wait( ctx );
70 while ( ( msg = ddjvu_message_peek( ctx ) ) )
72 which_ddjvu_message( msg );
73 ddjvu_message_pop( ctx );
77 /**
78 * Explore the message queue until the message \p mid is found.
80 static void wait_for_ddjvu_message( ddjvu_context_t *ctx, ddjvu_message_tag_t mid )
82 ddjvu_message_wait( ctx );
83 const ddjvu_message_t *msg;
84 while ( ( msg = ddjvu_message_peek( ctx ) ) && msg && ( msg->m_any.tag != mid ) )
86 which_ddjvu_message( msg );
87 ddjvu_message_pop( ctx );
91 /**
92 * Convert a clockwise coefficient \p r for a rotation to a counter-clockwise
93 * and vice versa.
95 static int flipRotation( int r )
97 return ( 4 - r ) % 4;
100 static miniexp_t find_second_in_pair( miniexp_t theexp, const char* which )
102 miniexp_t exp = theexp;
103 while ( exp )
105 miniexp_t cur = miniexp_car( exp );
106 if ( !miniexp_consp( cur ) || !miniexp_symbolp( miniexp_car( cur ) ) )
108 exp = miniexp_cdr( exp );
109 continue;
112 const QString id = QString::fromUtf8( miniexp_to_name( miniexp_car( cur ) ) );
113 if ( id == QLatin1String( which ) )
114 return miniexp_cadr( cur );
115 exp = miniexp_cdr( exp );
117 return miniexp_nil;
120 static bool find_replace_or_add_second_in_pair( miniexp_t theexp, const char* which, miniexp_t replacement )
122 miniexp_t exp = miniexp_cdddr( theexp );
123 while ( exp )
125 miniexp_t cur = miniexp_car( exp );
126 if ( !miniexp_consp( cur ) || !miniexp_symbolp( miniexp_car( cur ) ) )
128 exp = miniexp_cdr( exp );
129 continue;
132 const QString id = QString::fromUtf8( miniexp_to_name( miniexp_car( cur ) ) );
133 if ( id == QLatin1String( which ) )
135 miniexp_t reversed = miniexp_reverse( cur );
136 miniexp_rplaca( reversed, replacement );
137 cur = miniexp_reverse( reversed );
138 return true;
140 exp = miniexp_cdr( exp );
142 // TODO add the new replacement ad the end of the list
143 return false;
146 // ImageCacheItem
148 class ImageCacheItem
150 public:
151 ImageCacheItem( int p, int w, int h, const QImage& i )
152 : page( p ), width( w ), height( h ), img( i ) { }
154 int page;
155 int width;
156 int height;
157 QImage img;
161 // KdjVu::Page
163 KDjVu::Page::Page()
167 KDjVu::Page::~Page()
171 int KDjVu::Page::width() const
173 return m_width;
176 int KDjVu::Page::height() const
178 return m_height;
181 int KDjVu::Page::dpi() const
183 return m_dpi;
186 int KDjVu::Page::orientation() const
188 return m_orientation;
191 // KDjVu::Link
193 KDjVu::Link::~Link()
197 KDjVu::Link::LinkArea KDjVu::Link::areaType() const
199 return m_area;
202 QPoint KDjVu::Link::point() const
204 return m_point;
207 QSize KDjVu::Link::size() const
209 return m_size;
212 QPolygon KDjVu::Link::polygon() const
214 return m_poly;
217 // KDjVu::PageLink
219 KDjVu::PageLink::PageLink()
223 int KDjVu::PageLink::type() const
225 return KDjVu::Link::PageLink;
228 QString KDjVu::PageLink::page() const
230 return m_page;
233 // KDjVu::UrlLink
235 KDjVu::UrlLink::UrlLink()
239 int KDjVu::UrlLink::type() const
241 return KDjVu::Link::UrlLink;
244 QString KDjVu::UrlLink::url() const
246 return m_url;
249 // KDjVu::Annotation
251 KDjVu::Annotation::Annotation( miniexp_t anno )
252 : m_anno( anno )
256 KDjVu::Annotation::~Annotation()
260 QPoint KDjVu::Annotation::point() const
262 miniexp_t area = miniexp_nth( 3, m_anno );
263 int a = miniexp_to_int( miniexp_nth( 1, area ) );
264 int b = miniexp_to_int( miniexp_nth( 2, area ) );
265 return QPoint( a, b );
268 QString KDjVu::Annotation::comment() const
270 return QString::fromUtf8( miniexp_to_str( miniexp_nth( 2, m_anno ) ) );
273 void KDjVu::Annotation::setComment( const QString &comment )
275 miniexp_t exp = m_anno;
276 exp = miniexp_cdr( exp );
277 exp = miniexp_cdr( exp );
278 miniexp_rplaca( exp, miniexp_string( comment.toUtf8() ) );
281 QColor KDjVu::Annotation::color() const
283 return QColor();
286 void KDjVu::Annotation::setColor( const QColor & )
290 // KDjVu::TextAnnotation
292 KDjVu::TextAnnotation::TextAnnotation( miniexp_t anno )
293 : Annotation( anno ), m_inlineText( true )
295 const int num = miniexp_length( m_anno );
296 for ( int j = 4; j < num; ++j )
298 miniexp_t curelem = miniexp_nth( j, m_anno );
299 if ( !miniexp_listp( curelem ) )
300 continue;
302 QString id = QString::fromUtf8( miniexp_to_name( miniexp_nth( 0, curelem ) ) );
303 if ( id == QLatin1String( "pushpin" ) )
304 m_inlineText = false;
308 QSize KDjVu::TextAnnotation::size() const
310 miniexp_t area = miniexp_nth( 3, m_anno );
311 int c = miniexp_to_int( miniexp_nth( 3, area ) );
312 int d = miniexp_to_int( miniexp_nth( 4, area ) );
313 return QSize( c, d );
316 int KDjVu::TextAnnotation::type() const
318 return KDjVu::Annotation::TextAnnotation;
321 QColor KDjVu::TextAnnotation::color() const
323 miniexp_t col = find_second_in_pair( m_anno, "backclr" );
324 if ( !miniexp_symbolp( col ) )
325 return Qt::transparent;
327 return QColor( QString::fromUtf8( miniexp_to_name( col ) ) );
330 void KDjVu::TextAnnotation::setColor( const QColor &color )
332 const QByteArray col = color.name().toLatin1();
333 find_replace_or_add_second_in_pair( m_anno, "backclr", miniexp_symbol( col ) );
336 bool KDjVu::TextAnnotation::inlineText() const
338 return m_inlineText;
341 // KDjVu::LineAnnotation
343 KDjVu::LineAnnotation::LineAnnotation( miniexp_t anno )
344 : Annotation( anno ), m_isArrow( false ), m_width( miniexp_nil )
346 const int num = miniexp_length( m_anno );
347 for ( int j = 4; j < num; ++j )
349 miniexp_t curelem = miniexp_nth( j, m_anno );
350 if ( !miniexp_listp( curelem ) )
351 continue;
353 QString id = QString::fromUtf8( miniexp_to_name( miniexp_nth( 0, curelem ) ) );
354 if ( id == QLatin1String( "arrow" ) )
355 m_isArrow = true;
356 else if ( id == QLatin1String( "width" ) )
357 m_width = curelem;
361 int KDjVu::LineAnnotation::type() const
363 return KDjVu::Annotation::LineAnnotation;
366 QColor KDjVu::LineAnnotation::color() const
368 miniexp_t col = find_second_in_pair( m_anno, "lineclr" );
369 if ( !miniexp_symbolp( col ) )
370 return Qt::black;
372 return QColor( QString::fromUtf8( miniexp_to_name( col ) ) );
375 void KDjVu::LineAnnotation::setColor( const QColor &color )
377 const QByteArray col = color.name().toLatin1();
378 find_replace_or_add_second_in_pair( m_anno, "lineclr", miniexp_symbol( col ) );
381 QPoint KDjVu::LineAnnotation::point2() const
383 miniexp_t area = miniexp_nth( 3, m_anno );
384 int c = miniexp_to_int( miniexp_nth( 3, area ) );
385 int d = miniexp_to_int( miniexp_nth( 4, area ) );
386 return QPoint( c, d );
389 bool KDjVu::LineAnnotation::isArrow() const
391 return m_isArrow;
394 int KDjVu::LineAnnotation::width() const
396 if ( m_width == miniexp_nil )
397 return 1;
399 return miniexp_to_int( miniexp_cadr( m_width ) );
402 void KDjVu::LineAnnotation::setWidth( int width )
404 find_replace_or_add_second_in_pair( m_anno, "width", miniexp_number( width ) );
407 // KDjVu::TextEntity
409 KDjVu::TextEntity::TextEntity()
413 KDjVu::TextEntity::~TextEntity()
417 QString KDjVu::TextEntity::text() const
419 return m_text;
422 QRect KDjVu::TextEntity::rect() const
424 return m_rect;
428 class KDjVu::Private
430 public:
431 Private()
432 : m_djvu_cxt( 0 ), m_djvu_document( 0 ), m_format( 0 ), m_docBookmarks( 0 ),
433 m_cacheEnabled( true )
437 QImage generateImageTile( ddjvu_page_t *djvupage, int& res,
438 int width, int row, int xdelta, int height, int col, int ydelta );
440 void readBookmarks();
441 void fillBookmarksRecurse( QDomDocument& maindoc, QDomNode& curnode,
442 miniexp_t exp, int offset = -1 );
444 void readMetaData( int page );
446 int pageWithName( const QString & name );
448 ddjvu_context_t *m_djvu_cxt;
449 ddjvu_document_t *m_djvu_document;
450 ddjvu_format_t *m_format;
452 QVector<KDjVu::Page*> m_pages;
453 QVector<ddjvu_page_t *> m_pages_cache;
455 QList<ImageCacheItem*> mImgCache;
457 QHash<QString, QVariant> m_metaData;
458 QDomDocument * m_docBookmarks;
460 QHash<QString, int> m_pageNamesCache;
462 bool m_cacheEnabled;
464 static unsigned int s_formatmask[4];
467 unsigned int KDjVu::Private::s_formatmask[4] = { 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 };
469 QImage KDjVu::Private::generateImageTile( ddjvu_page_t *djvupage, int& res,
470 int width, int row, int xdelta, int height, int col, int ydelta )
472 ddjvu_rect_t renderrect;
473 renderrect.x = row * xdelta;
474 renderrect.y = col * ydelta;
475 int realwidth = qMin( width - renderrect.x, xdelta );
476 int realheight = qMin( height - renderrect.y, ydelta );
477 renderrect.w = realwidth;
478 renderrect.h = realheight;
479 #ifdef KDJVU_DEBUG
480 kDebug() << "renderrect:" << renderrect;
481 #endif
482 ddjvu_rect_t pagerect;
483 pagerect.x = 0;
484 pagerect.y = 0;
485 pagerect.w = width;
486 pagerect.h = height;
487 #ifdef KDJVU_DEBUG
488 kDebug() << "pagerect:" << pagerect;
489 #endif
490 handle_ddjvu_messages( m_djvu_cxt, false );
491 QImage res_img( realwidth, realheight, QImage::Format_RGB32 );
492 // the following line workarounds a rare crash in djvulibre;
493 // it should be fixed with >= 3.5.21
494 ddjvu_page_get_width( djvupage );
495 res = ddjvu_page_render( djvupage, DDJVU_RENDER_COLOR,
496 &pagerect, &renderrect, m_format, res_img.bytesPerLine(), (char *)res_img.bits() );
497 #ifdef KDJVU_DEBUG
498 kDebug() << "rendering result:" << res;
499 #endif
500 handle_ddjvu_messages( m_djvu_cxt, false );
502 return res_img;
505 void KDjVu::Private::readBookmarks()
507 if ( !m_djvu_document )
508 return;
510 miniexp_t outline;
511 while ( ( outline = ddjvu_document_get_outline( m_djvu_document ) ) == miniexp_dummy )
512 handle_ddjvu_messages( m_djvu_cxt, true );
514 if ( miniexp_listp( outline ) &&
515 ( miniexp_length( outline ) > 0 ) &&
516 miniexp_symbolp( miniexp_nth( 0, outline ) ) &&
517 ( QString::fromUtf8( miniexp_to_name( miniexp_nth( 0, outline ) ) ) == QLatin1String( "bookmarks" ) ) )
519 m_docBookmarks = new QDomDocument( "KDjVuBookmarks" );
520 fillBookmarksRecurse( *m_docBookmarks, *m_docBookmarks, outline, 1 );
521 ddjvu_miniexp_release( m_djvu_document, outline );
525 void KDjVu::Private::fillBookmarksRecurse( QDomDocument& maindoc, QDomNode& curnode,
526 miniexp_t exp, int offset )
528 if ( !miniexp_listp( exp ) )
529 return;
531 int l = miniexp_length( exp );
532 for ( int i = qMax( offset, 0 ); i < l; ++i )
534 miniexp_t cur = miniexp_nth( i, exp );
536 if ( miniexp_consp( cur ) && ( miniexp_length( cur ) > 0 ) &&
537 miniexp_stringp( miniexp_nth( 0, cur ) ) && miniexp_stringp( miniexp_nth( 1, cur ) ) )
539 QString title = QString::fromUtf8( miniexp_to_str( miniexp_nth( 0, cur ) ) );
540 QString dest = QString::fromUtf8( miniexp_to_str( miniexp_nth( 1, cur ) ) );
541 QDomElement el = maindoc.createElement( "item" );
542 el.setAttribute( "title", title );
543 if ( !dest.isEmpty() )
545 if ( dest.at( 0 ) == QLatin1Char( '#' ) )
547 dest.remove( 0, 1 );
548 bool isNumber = false;
549 dest.toInt( &isNumber );
550 if ( isNumber )
552 el.setAttribute( "PageNumber", dest );
554 else
556 el.setAttribute( "PageName", dest );
559 else
561 el.setAttribute( "URL", dest );
564 curnode.appendChild( el );
565 if ( !el.isNull() && ( miniexp_length( cur ) > 2 ) )
567 fillBookmarksRecurse( maindoc, el, cur, 2 );
573 void KDjVu::Private::readMetaData( int page )
575 if ( !m_djvu_document )
576 return;
578 miniexp_t annots;
579 while ( ( annots = ddjvu_document_get_pageanno( m_djvu_document, page ) ) == miniexp_dummy )
580 handle_ddjvu_messages( m_djvu_cxt, true );
582 if ( !miniexp_listp( annots ) || miniexp_length( annots ) == 0 )
583 return;
585 miniexp_t exp = miniexp_nth( 0, annots );
586 int size = miniexp_length( exp );
587 if ( size <= 1 ||
588 qstrncmp( miniexp_to_name( miniexp_nth( 0, exp ) ), "metadata", 8 ) )
589 return;
591 for ( int i = 1; i < size; ++i )
593 miniexp_t cur = miniexp_nth( i, exp );
594 if ( miniexp_length( cur ) != 2 )
595 continue;
597 QString id = QString::fromUtf8( miniexp_to_name( miniexp_nth( 0, cur ) ) );
598 QString value = QString::fromUtf8( miniexp_to_str( miniexp_nth( 1, cur ) ) );
599 m_metaData[ id.toLower() ] = value;
603 int KDjVu::Private::pageWithName( const QString & name )
605 const QByteArray utfName = name.toUtf8();
606 const int fileNum = ddjvu_document_get_filenum( m_djvu_document );
607 ddjvu_fileinfo_t info;
608 for ( int i = 0; i < fileNum; ++i )
610 if ( DDJVU_JOB_OK != ddjvu_document_get_fileinfo( m_djvu_document, i, &info ) )
611 continue;
612 if ( info.type != 'P' )
613 continue;
614 if ( ( utfName == info.id ) || ( utfName == info.name ) || ( utfName == info.title ) )
615 return info.pageno;
617 return -1;
621 KDjVu::KDjVu() : d( new Private )
623 // creating the djvu context
624 d->m_djvu_cxt = ddjvu_context_create( "KDjVu" );
625 // creating the rendering format
626 #if DDJVUAPI_VERSION >= 18
627 d->m_format = ddjvu_format_create( DDJVU_FORMAT_RGBMASK32, 4, Private::s_formatmask );
628 #else
629 d->m_format = ddjvu_format_create( DDJVU_FORMAT_RGBMASK32, 3, Private::s_formatmask );
630 #endif
631 ddjvu_format_set_row_order( d->m_format, 1 );
632 ddjvu_format_set_y_direction( d->m_format, 1 );
636 KDjVu::~KDjVu()
638 closeFile();
640 ddjvu_format_release( d->m_format );
641 ddjvu_context_release( d->m_djvu_cxt );
643 delete d;
646 bool KDjVu::openFile( const QString & fileName )
648 // first, close the old file
649 if ( d->m_djvu_document )
650 closeFile();
652 // load the document...
653 d->m_djvu_document = ddjvu_document_create_by_filename( d->m_djvu_cxt, QFile::encodeName( fileName ), true );
654 if ( !d->m_djvu_document ) return false;
655 // ...and wait for its loading
656 wait_for_ddjvu_message( d->m_djvu_cxt, DDJVU_DOCINFO );
658 kDebug() << "# of pages:" << ddjvu_document_get_pagenum( d->m_djvu_document );
659 int numofpages = ddjvu_document_get_pagenum( d->m_djvu_document );
660 d->m_pages.clear();
661 d->m_pages.resize( numofpages );
662 d->m_pages_cache.clear();
663 d->m_pages_cache.resize( numofpages );
665 // get the document type
666 QString doctype;
667 switch ( ddjvu_document_get_type( d->m_djvu_document ) )
669 case DDJVU_DOCTYPE_UNKNOWN:
670 doctype = i18nc( "Type of DjVu document", "Unknown" );
671 break;
672 case DDJVU_DOCTYPE_SINGLEPAGE:
673 doctype = i18nc( "Type of DjVu document", "Single Page" );
674 break;
675 case DDJVU_DOCTYPE_BUNDLED:
676 doctype = i18nc( "Type of DjVu document", "Bundled" );
677 break;
678 case DDJVU_DOCTYPE_INDIRECT:
679 doctype = i18nc( "Type of DjVu document", "Indirect" );
680 break;
681 case DDJVU_DOCTYPE_OLD_BUNDLED:
682 doctype = i18nc( "Type of DjVu document", "Bundled (old)" );
683 break;
684 case DDJVU_DOCTYPE_OLD_INDEXED:
685 doctype = i18nc( "Type of DjVu document", "Indexed (old)" );
686 break;
688 if ( !doctype.isEmpty() )
689 d->m_metaData[ "documentType" ] = doctype;
690 // get the number of components
691 d->m_metaData[ "componentFile" ] = ddjvu_document_get_filenum( d->m_djvu_document );
693 // read the pages
694 for ( int i = 0; i < numofpages; ++i )
696 ddjvu_status_t sts;
697 ddjvu_pageinfo_t info;
698 while ( ( sts = ddjvu_document_get_pageinfo( d->m_djvu_document, i, &info ) ) < DDJVU_JOB_OK )
699 handle_ddjvu_messages( d->m_djvu_cxt, true );
700 if ( sts >= DDJVU_JOB_FAILED )
702 kDebug().nospace() << "\t>>> page " << i << " failed: " << sts;
703 return false;
706 KDjVu::Page *p = new KDjVu::Page();
707 p->m_width = info.width;
708 p->m_height = info.height;
709 p->m_dpi = info.dpi;
710 #if DDJVUAPI_VERSION >= 18
711 p->m_orientation = flipRotation( info.rotation );
712 #else
713 p->m_orientation = 0;
714 #endif
715 d->m_pages[i] = p;
718 // reading the metadata from the first page only should be enough
719 if ( numofpages > 0 )
720 d->readMetaData( 0 );
722 return true;
725 void KDjVu::closeFile()
727 // deleting the old TOC
728 delete d->m_docBookmarks;
729 d->m_docBookmarks = 0;
730 // deleting the pages
731 qDeleteAll( d->m_pages );
732 d->m_pages.clear();
733 // releasing the djvu pages
734 QVector<ddjvu_page_t *>::Iterator it = d->m_pages_cache.begin(), itEnd = d->m_pages_cache.end();
735 for ( ; it != itEnd; ++it )
736 ddjvu_page_release( *it );
737 d->m_pages_cache.clear();
738 // clearing the image cache
739 qDeleteAll( d->mImgCache );
740 d->mImgCache.clear();
741 // clearing the old metadata
742 d->m_metaData.clear();
743 // cleaing the page names mapping
744 d->m_pageNamesCache.clear();
745 // releasing the old document
746 if ( d->m_djvu_document )
747 ddjvu_document_release( d->m_djvu_document );
748 d->m_djvu_document = 0;
751 QVariant KDjVu::metaData( const QString & key ) const
753 QHash<QString, QVariant>::ConstIterator it = d->m_metaData.constFind( key );
754 return it != d->m_metaData.constEnd() ? it.value() : QVariant();
757 const QDomDocument * KDjVu::documentBookmarks() const
759 if ( !d->m_docBookmarks )
760 d->readBookmarks();
761 return d->m_docBookmarks;
764 void KDjVu::linksAndAnnotationsForPage( int pageNum, QList<KDjVu::Link*> *links, QList<KDjVu::Annotation*> *annotations ) const
766 if ( ( pageNum < 0 ) || ( pageNum >= d->m_pages.count() ) || ( !links && !annotations ) )
767 return;
769 miniexp_t annots;
770 while ( ( annots = ddjvu_document_get_pageanno( d->m_djvu_document, pageNum ) ) == miniexp_dummy )
771 handle_ddjvu_messages( d->m_djvu_cxt, true );
773 if ( !miniexp_listp( annots ) )
774 return;
776 if ( links )
777 links->clear();
778 if ( annotations )
779 annotations->clear();
781 int l = miniexp_length( annots );
782 for ( int i = 0; i < l; ++i )
784 miniexp_t cur = miniexp_nth( i, annots );
785 int num = miniexp_length( cur );
786 if ( ( num < 4 ) || !miniexp_symbolp( miniexp_nth( 0, cur ) ) ||
787 ( qstrncmp( miniexp_to_name( miniexp_nth( 0, cur ) ), "maparea", 7 ) != 0 ) )
788 continue;
790 QString target;
791 QString type;
792 if ( miniexp_symbolp( miniexp_nth( 0, miniexp_nth( 3, cur ) ) ) )
793 type = QString::fromUtf8( miniexp_to_name( miniexp_nth( 0, miniexp_nth( 3, cur ) ) ) );
794 KDjVu::Link* link = 0;
795 KDjVu::Annotation* ann = 0;
796 miniexp_t urlexp = miniexp_nth( 1, cur );
797 if ( links &&
798 ( type == QLatin1String( "rect" ) ||
799 type == QLatin1String( "oval" ) ||
800 type == QLatin1String( "poly" ) ) )
802 if ( miniexp_stringp( urlexp ) )
804 target = QString::fromUtf8( miniexp_to_str( miniexp_nth( 1, cur ) ) );
806 else if ( miniexp_listp( urlexp ) && ( miniexp_length( urlexp ) == 3 ) &&
807 miniexp_symbolp( miniexp_nth( 0, urlexp ) ) &&
808 ( qstrncmp( miniexp_to_name( miniexp_nth( 0, urlexp ) ), "url", 3 ) == 0 ) )
810 target = QString::fromUtf8( miniexp_to_str( miniexp_nth( 1, urlexp ) ) );
812 if ( target.isEmpty() || ( ( target.length() > 0 ) && target.at(0) == QLatin1Char( '#' ) ) )
814 KDjVu::PageLink* plink = new KDjVu::PageLink();
815 plink->m_page = target;
816 link = plink;
818 else
820 KDjVu::UrlLink* ulink = new KDjVu::UrlLink();
821 ulink->m_url = target;
822 link = ulink;
825 else if ( annotations &&
826 ( type == QLatin1String( "text" ) ||
827 type == QLatin1String( "line" ) ) )
829 if ( type == QLatin1String( "text" ) )
831 KDjVu::TextAnnotation * textann = new KDjVu::TextAnnotation( cur );
832 ann = textann;
834 else if ( type == QLatin1String( "line" ) )
836 KDjVu::LineAnnotation * lineann = new KDjVu::LineAnnotation( cur );
837 ann = lineann;
840 if ( link /* safety check */ && links )
842 link->m_area = KDjVu::Link::UnknownArea;
843 miniexp_t area = miniexp_nth( 3, cur );
844 int arealength = miniexp_length( area );
845 if ( ( arealength == 5 ) && ( type == QLatin1String( "rect" ) || type == QLatin1String( "oval" ) ) )
847 link->m_point = QPoint( miniexp_to_int( miniexp_nth( 1, area ) ), miniexp_to_int( miniexp_nth( 2, area ) ) );
848 link->m_size = QSize( miniexp_to_int( miniexp_nth( 3, area ) ), miniexp_to_int( miniexp_nth( 4, area ) ) );
849 if ( type == QLatin1String( "rect" ) )
851 link->m_area = KDjVu::Link::RectArea;
853 else
855 link->m_area = KDjVu::Link::EllipseArea;
858 else if ( ( arealength > 0 ) && ( arealength % 2 == 1 ) &&
859 type == QLatin1String( "poly" ) )
861 link->m_area = KDjVu::Link::PolygonArea;
862 QPolygon poly;
863 for ( int j = 1; j < arealength; j += 2 )
865 poly << QPoint( miniexp_to_int( miniexp_nth( j, area ) ), miniexp_to_int( miniexp_nth( j + 1, area ) ) );
867 link->m_poly = poly;
870 if ( link->m_area != KDjVu::Link::UnknownArea )
871 links->append( link );
873 else if ( ann /* safety check */ && annotations )
875 annotations->append( ann );
880 const QVector<KDjVu::Page*> &KDjVu::pages() const
882 return d->m_pages;
885 QImage KDjVu::image( int page, int width, int height, int rotation )
887 if ( d->m_cacheEnabled )
889 bool found = false;
890 QList<ImageCacheItem*>::Iterator it = d->mImgCache.begin(), itEnd = d->mImgCache.end();
891 for ( ; ( it != itEnd ) && !found; ++it )
893 ImageCacheItem* cur = *it;
894 if ( ( cur->page == page ) &&
895 ( rotation % 2 == 0
896 ? cur->width == width && cur->height == height
897 : cur->width == height && cur->height == width ) )
898 found = true;
900 if ( found )
902 // taking the element and pushing to the top of the list
903 --it;
904 ImageCacheItem* cur2 = *it;
905 d->mImgCache.erase( it );
906 d->mImgCache.push_front( cur2 );
908 return cur2->img;
912 if ( !d->m_pages_cache.at( page ) )
914 ddjvu_page_t *newpage = ddjvu_page_create_by_pageno( d->m_djvu_document, page );
915 // wait for the new page to be loaded
916 ddjvu_status_t sts;
917 while ( ( sts = ddjvu_page_decoding_status( newpage ) ) < DDJVU_JOB_OK )
918 handle_ddjvu_messages( d->m_djvu_cxt, true );
919 d->m_pages_cache[page] = newpage;
921 ddjvu_page_t *djvupage = d->m_pages_cache[page];
924 if ( ddjvu_page_get_rotation( djvupage ) != flipRotation( rotation ) )
926 // TODO: test documents with initial rotation != 0
927 // ddjvu_page_set_rotation( djvupage, m_pages.at( page )->orientation() );
928 ddjvu_page_set_rotation( djvupage, (ddjvu_page_rotation_t)flipRotation( rotation ) );
932 static const int xdelta = 1500;
933 static const int ydelta = 1500;
935 int xparts = width / xdelta + 1;
936 int yparts = height / ydelta + 1;
938 QImage newimg;
940 int res = 10000;
941 if ( ( xparts == 1 ) && ( yparts == 1 ) )
943 // only one part -- render at once with no need to auxiliary image
944 newimg = d->generateImageTile( djvupage, res,
945 width, 0, xdelta, height, 0, ydelta );
947 else
949 // more than one part -- need to render piece-by-piece and to compose
950 // the results
951 newimg = QImage( width, height, QImage::Format_RGB32 );
952 QPainter p;
953 p.begin( &newimg );
954 int parts = xparts * yparts;
955 for ( int i = 0; i < parts; ++i )
957 int row = i % xparts;
958 int col = i / xparts;
959 int tmpres = 0;
960 QImage tempp = d->generateImageTile( djvupage, tmpres,
961 width, row, xdelta, height, col, ydelta );
962 if ( tmpres )
964 p.drawImage( row * xdelta, col * ydelta, tempp );
966 res = qMin( tmpres, res );
968 p.end();
971 if ( res && d->m_cacheEnabled )
973 // delete all the cached pixmaps for the current page with a size that
974 // differs no more than 35% of the new pixmap size
975 int imgsize = newimg.width() * newimg.height();
976 if ( imgsize > 0 )
978 for( int i = 0; i < d->mImgCache.count(); )
980 ImageCacheItem* cur = d->mImgCache.at(i);
981 if ( ( cur->page == page ) &&
982 ( abs( cur->img.width() * cur->img.height() - imgsize ) < imgsize * 0.35 ) )
984 d->mImgCache.removeAt( i );
985 delete cur;
987 else
988 ++i;
992 // the image cache has too many elements, remove the last
993 if ( d->mImgCache.size() >= 10 )
995 delete d->mImgCache.last();
996 d->mImgCache.removeLast();
998 ImageCacheItem* ich = new ImageCacheItem( page, width, height, newimg );
999 d->mImgCache.push_front( ich );
1002 return newimg;
1005 bool KDjVu::exportAsPostScript( const QString & fileName, const QList<int>& pageList ) const
1007 if ( !d->m_djvu_document || fileName.trimmed().isEmpty() || pageList.isEmpty() )
1008 return false;
1010 QFile f( fileName );
1011 f.open( QIODevice::ReadWrite );
1012 bool ret = exportAsPostScript( &f, pageList );
1013 if ( ret )
1015 f.close();
1017 return ret;
1020 bool KDjVu::exportAsPostScript( QFile* file, const QList<int>& pageList ) const
1022 if ( !d->m_djvu_document || !file || pageList.isEmpty() )
1023 return false;
1025 FILE* f = fdopen( file->handle(), "w+" );
1026 if ( !f )
1028 kDebug() << "error while getting the FILE*";
1029 return false;
1032 QString pl;
1033 foreach ( int p, pageList )
1035 if ( !pl.isEmpty() )
1036 pl += QString::fromLatin1( "," );
1037 pl += QString::number( p );
1039 pl.prepend( "-page=" );
1041 // setting the options
1042 static const int optc = 1;
1043 const char ** optv = (const char**)malloc( 1 * sizeof( char* ) );
1044 QByteArray plb = pl.toAscii();
1045 optv[0] = plb.constData();
1047 ddjvu_job_t *printjob = ddjvu_document_print( d->m_djvu_document, f, optc, optv );
1048 while ( !ddjvu_job_done( printjob ) )
1049 handle_ddjvu_messages( d->m_djvu_cxt, true );
1051 free( optv );
1053 return fclose( f ) == 0;
1056 QList<KDjVu::TextEntity> KDjVu::textEntities( int page, const QString & granularity ) const
1058 if ( ( page < 0 ) || ( page >= d->m_pages.count() ) )
1059 return QList<KDjVu::TextEntity>();
1061 miniexp_t r;
1062 while ( ( r = ddjvu_document_get_pagetext( d->m_djvu_document, page, 0 ) ) == miniexp_dummy )
1063 handle_ddjvu_messages( d->m_djvu_cxt, true );
1065 if ( r == miniexp_nil )
1066 return QList<KDjVu::TextEntity>();
1068 QList<KDjVu::TextEntity> ret;
1070 int height = d->m_pages.at( page )->height();
1072 QQueue<miniexp_t> queue;
1073 queue.enqueue( r );
1075 while ( !queue.isEmpty() )
1077 miniexp_t cur = queue.dequeue();
1079 if ( miniexp_listp( cur )
1080 && ( miniexp_length( cur ) > 0 )
1081 && miniexp_symbolp( miniexp_nth( 0, cur ) ) )
1083 int size = miniexp_length( cur );
1084 QString sym = QString::fromUtf8( miniexp_to_name( miniexp_nth( 0, cur ) ) );
1085 if ( sym == granularity )
1087 if ( size >= 6 )
1089 int xmin = miniexp_to_int( miniexp_nth( 1, cur ) );
1090 int ymin = miniexp_to_int( miniexp_nth( 2, cur ) );
1091 int xmax = miniexp_to_int( miniexp_nth( 3, cur ) );
1092 int ymax = miniexp_to_int( miniexp_nth( 4, cur ) );
1093 QRect rect( xmin, height - ymax, xmax - xmin, ymax - ymin );
1094 KDjVu::TextEntity entity;
1095 entity.m_rect = rect;
1096 entity.m_text = QString::fromUtf8( miniexp_to_str( miniexp_nth( 5, cur ) ) );
1097 ret.append( entity );
1100 else
1102 for ( int i = 5; i < size; ++i )
1103 queue.enqueue( miniexp_nth( i, cur ) );
1108 return ret;
1111 void KDjVu::setCacheEnabled( bool enable )
1113 if ( enable == d->m_cacheEnabled )
1114 return;
1116 d->m_cacheEnabled = enable;
1117 if ( !d->m_cacheEnabled )
1119 qDeleteAll( d->mImgCache );
1120 d->mImgCache.clear();
1124 bool KDjVu::isCacheEnabled() const
1126 return d->m_cacheEnabled;
1129 int KDjVu::pageNumber( const QString & name ) const
1131 if ( !d->m_djvu_document )
1132 return -1;
1134 QHash< QString, int >::iterator it = d->m_pageNamesCache.find( name );
1135 if ( it == d->m_pageNamesCache.end() )
1137 it = d->m_pageNamesCache.insert( name, d->pageWithName( name ) );
1139 return it.value();