1 /***************************************************************************
2 * Copyright (C) 2006 by Pino Toscano <toscano.pino@tiscali.it> *
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 ***************************************************************************/
12 #include <qbytearray.h>
24 #include <libdjvu/ddjvuapi.h>
25 #include <libdjvu/miniexp.h>
29 QDebug
&operator<<( QDebug
& s
, const ddjvu_rect_t
&r
)
31 s
.nospace() << "[" << r
.x
<< "," << r
.y
<< " - " << r
.w
<< "x" << r
.h
<< "]";
35 static void which_ddjvu_message( const ddjvu_message_t
*msg
)
38 kDebug() << "which_djvu_message(...):" << msg
->m_any
.tag
;
39 switch( msg
->m_any
.tag
)
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
<< "'";
47 kDebug().nospace() << "INFO: '" << msg
->m_info
.message
<< "'";
50 kDebug().nospace() << "CHUNK: '" << QByteArray( msg
->m_chunk
.chunkid
) << "'";
53 kDebug().nospace() << "PROGRESS: '" << msg
->m_progress
.percent
<< "'";
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
;
69 ddjvu_message_wait( ctx
);
70 while ( ( msg
= ddjvu_message_peek( ctx
) ) )
72 which_ddjvu_message( msg
);
73 ddjvu_message_pop( ctx
);
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
);
92 * Convert a clockwise coefficient \p r for a rotation to a counter-clockwise
95 static int flipRotation( int r
)
100 static miniexp_t
find_second_in_pair( miniexp_t theexp
, const char* which
)
102 miniexp_t exp
= theexp
;
105 miniexp_t cur
= miniexp_car( exp
);
106 if ( !miniexp_consp( cur
) || !miniexp_symbolp( miniexp_car( cur
) ) )
108 exp
= miniexp_cdr( exp
);
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
);
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
);
125 miniexp_t cur
= miniexp_car( exp
);
126 if ( !miniexp_consp( cur
) || !miniexp_symbolp( miniexp_car( cur
) ) )
128 exp
= miniexp_cdr( exp
);
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
);
140 exp
= miniexp_cdr( exp
);
142 // TODO add the new replacement ad the end of the list
151 ImageCacheItem( int p
, int w
, int h
, const QImage
& i
)
152 : page( p
), width( w
), height( h
), img( i
) { }
171 int KDjVu::Page::width() const
176 int KDjVu::Page::height() const
181 int KDjVu::Page::dpi() const
186 int KDjVu::Page::orientation() const
188 return m_orientation
;
197 KDjVu::Link::LinkArea
KDjVu::Link::areaType() const
202 QPoint
KDjVu::Link::point() const
207 QSize
KDjVu::Link::size() const
212 QPolygon
KDjVu::Link::polygon() const
219 KDjVu::PageLink::PageLink()
223 int KDjVu::PageLink::type() const
225 return KDjVu::Link::PageLink
;
228 QString
KDjVu::PageLink::page() const
235 KDjVu::UrlLink::UrlLink()
239 int KDjVu::UrlLink::type() const
241 return KDjVu::Link::UrlLink
;
244 QString
KDjVu::UrlLink::url() const
251 KDjVu::Annotation::Annotation( miniexp_t 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
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
) )
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
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
) )
353 QString id
= QString::fromUtf8( miniexp_to_name( miniexp_nth( 0, curelem
) ) );
354 if ( id
== QLatin1String( "arrow" ) )
356 else if ( id
== QLatin1String( "width" ) )
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
) )
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
394 int KDjVu::LineAnnotation::width() const
396 if ( m_width
== miniexp_nil
)
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
) );
409 KDjVu::TextEntity::TextEntity()
413 KDjVu::TextEntity::~TextEntity()
417 QString
KDjVu::TextEntity::text() const
422 QRect
KDjVu::TextEntity::rect() const
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
;
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
;
480 kDebug() << "renderrect:" << renderrect
;
482 ddjvu_rect_t pagerect
;
488 kDebug() << "pagerect:" << pagerect
;
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() );
498 kDebug() << "rendering result:" << res
;
500 handle_ddjvu_messages( m_djvu_cxt
, false );
505 void KDjVu::Private::readBookmarks()
507 if ( !m_djvu_document
)
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
) )
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( '#' ) )
548 bool isNumber
= false;
549 dest
.toInt( &isNumber
);
552 el
.setAttribute( "PageNumber", dest
);
556 el
.setAttribute( "PageName", dest
);
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
)
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 )
585 miniexp_t exp
= miniexp_nth( 0, annots
);
586 int size
= miniexp_length( exp
);
588 qstrncmp( miniexp_to_name( miniexp_nth( 0, exp
) ), "metadata", 8 ) )
591 for ( int i
= 1; i
< size
; ++i
)
593 miniexp_t cur
= miniexp_nth( i
, exp
);
594 if ( miniexp_length( cur
) != 2 )
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
) )
612 if ( info
.type
!= 'P' )
614 if ( ( utfName
== info
.id
) || ( utfName
== info
.name
) || ( utfName
== info
.title
) )
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
);
629 d
->m_format
= ddjvu_format_create( DDJVU_FORMAT_RGBMASK32
, 3, Private::s_formatmask
);
631 ddjvu_format_set_row_order( d
->m_format
, 1 );
632 ddjvu_format_set_y_direction( d
->m_format
, 1 );
640 ddjvu_format_release( d
->m_format
);
641 ddjvu_context_release( d
->m_djvu_cxt
);
646 bool KDjVu::openFile( const QString
& fileName
)
648 // first, close the old file
649 if ( d
->m_djvu_document
)
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
);
661 d
->m_pages
.resize( numofpages
);
662 d
->m_pages_cache
.clear();
663 d
->m_pages_cache
.resize( numofpages
);
665 // get the document type
667 switch ( ddjvu_document_get_type( d
->m_djvu_document
) )
669 case DDJVU_DOCTYPE_UNKNOWN
:
670 doctype
= i18nc( "Type of DjVu document", "Unknown" );
672 case DDJVU_DOCTYPE_SINGLEPAGE
:
673 doctype
= i18nc( "Type of DjVu document", "Single Page" );
675 case DDJVU_DOCTYPE_BUNDLED
:
676 doctype
= i18nc( "Type of DjVu document", "Bundled" );
678 case DDJVU_DOCTYPE_INDIRECT
:
679 doctype
= i18nc( "Type of DjVu document", "Indirect" );
681 case DDJVU_DOCTYPE_OLD_BUNDLED
:
682 doctype
= i18nc( "Type of DjVu document", "Bundled (old)" );
684 case DDJVU_DOCTYPE_OLD_INDEXED
:
685 doctype
= i18nc( "Type of DjVu document", "Indexed (old)" );
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
);
694 for ( int i
= 0; i
< numofpages
; ++i
)
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
;
706 KDjVu::Page
*p
= new KDjVu::Page();
707 p
->m_width
= info
.width
;
708 p
->m_height
= info
.height
;
710 #if DDJVUAPI_VERSION >= 18
711 p
->m_orientation
= flipRotation( info
.rotation
);
713 p
->m_orientation
= 0;
718 // reading the metadata from the first page only should be enough
719 if ( numofpages
> 0 )
720 d
->readMetaData( 0 );
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
);
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
)
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
) )
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
) )
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 ) )
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
);
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
;
820 KDjVu::UrlLink
* ulink
= new KDjVu::UrlLink();
821 ulink
->m_url
= target
;
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
);
834 else if ( type
== QLatin1String( "line" ) )
836 KDjVu::LineAnnotation
* lineann
= new KDjVu::LineAnnotation( cur
);
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
;
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
;
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
) ) );
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
885 QImage
KDjVu::image( int page
, int width
, int height
, int rotation
)
887 if ( d
->m_cacheEnabled
)
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
) &&
896 ? cur
->width
== width
&& cur
->height
== height
897 : cur
->width
== height
&& cur
->height
== width
) )
902 // taking the element and pushing to the top of the list
904 ImageCacheItem
* cur2
= *it
;
905 d
->mImgCache
.erase( it
);
906 d
->mImgCache
.push_front( cur2
);
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
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;
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
);
949 // more than one part -- need to render piece-by-piece and to compose
951 newimg
= QImage( width
, height
, QImage::Format_RGB32
);
954 int parts
= xparts
* yparts
;
955 for ( int i
= 0; i
< parts
; ++i
)
957 int row
= i
% xparts
;
958 int col
= i
/ xparts
;
960 QImage tempp
= d
->generateImageTile( djvupage
, tmpres
,
961 width
, row
, xdelta
, height
, col
, ydelta
);
964 p
.drawImage( row
* xdelta
, col
* ydelta
, tempp
);
966 res
= qMin( tmpres
, res
);
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();
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
);
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
);
1005 bool KDjVu::exportAsPostScript( const QString
& fileName
, const QList
<int>& pageList
) const
1007 if ( !d
->m_djvu_document
|| fileName
.trimmed().isEmpty() || pageList
.isEmpty() )
1010 QFile
f( fileName
);
1011 f
.open( QIODevice::ReadWrite
);
1012 bool ret
= exportAsPostScript( &f
, pageList
);
1020 bool KDjVu::exportAsPostScript( QFile
* file
, const QList
<int>& pageList
) const
1022 if ( !d
->m_djvu_document
|| !file
|| pageList
.isEmpty() )
1025 FILE* f
= fdopen( file
->handle(), "w+" );
1028 kDebug() << "error while getting the FILE*";
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 );
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
>();
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
;
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
)
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
);
1102 for ( int i
= 5; i
< size
; ++i
)
1103 queue
.enqueue( miniexp_nth( i
, cur
) );
1111 void KDjVu::setCacheEnabled( bool enable
)
1113 if ( enable
== d
->m_cacheEnabled
)
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
)
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
) );