kdjvu.cpp 33 KB
Newer Older
Pino Toscano's avatar
Pino Toscano committed
1 2 3 4 5 6 7 8 9 10 11
/***************************************************************************
 *   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"

Yuri Chornoivan's avatar
Yuri Chornoivan committed
12 13 14 15 16 17 18 19
#include <QByteArray>
#include <QDomDocument>
#include <QFile>
#include <QHash>
#include <QList>
#include <QPainter>
#include <QQueue>
#include <QString>
20

Yuri Chornoivan's avatar
Yuri Chornoivan committed
21
#include <QDebug>
Laurent Montel's avatar
Laurent Montel committed
22
#include <KLocalizedString>
Pino Toscano's avatar
Pino Toscano committed
23 24

#include <libdjvu/ddjvuapi.h>
25
#include <libdjvu/miniexp.h>
Pino Toscano's avatar
Pino Toscano committed
26

27 28
#include <stdio.h>

29
QDebug &operator<<( QDebug & s, const ddjvu_rect_t r )
Pino Toscano's avatar
Pino Toscano committed
30
{
31 32
    s.nospace() << "[" << r.x << "," << r.y << " - " << r.w << "x" << r.h << "]";
    return s.space();
Pino Toscano's avatar
Pino Toscano committed
33 34 35 36
}

static void which_ddjvu_message( const ddjvu_message_t *msg )
{
37
#ifdef KDJVU_DEBUG
38
    qDebug() << "which_djvu_message(...):" << msg->m_any.tag;
Pino Toscano's avatar
Pino Toscano committed
39 40 41
    switch( msg->m_any.tag )
    {
        case DDJVU_ERROR:
42 43 44
            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 << "'";
Pino Toscano's avatar
Pino Toscano committed
45 46
            break;
        case DDJVU_INFO:
47
            qDebug().nospace() << "INFO: '" << msg->m_info.message << "'";
Pino Toscano's avatar
Pino Toscano committed
48 49
            break;
        case DDJVU_CHUNK:
50
            qDebug().nospace() << "CHUNK: '" << QByteArray( msg->m_chunk.chunkid ) << "'";
Pino Toscano's avatar
Pino Toscano committed
51
            break;
52
        case DDJVU_PROGRESS:
53
            qDebug().nospace() << "PROGRESS: '" << msg->m_progress.percent << "'";
54
            break;
Pino Toscano's avatar
Pino Toscano committed
55 56
        default: ;
    }
57 58 59
#else
    Q_UNUSED( msg );
#endif
Pino Toscano's avatar
Pino Toscano committed
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
}

/**
 * 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;
}

100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
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;
}
Pino Toscano's avatar
Pino Toscano committed
145

146
// ImageCacheItem
Pino Toscano's avatar
Pino Toscano committed
147

148
class ImageCacheItem
Pino Toscano's avatar
Pino Toscano committed
149 150
{
    public:
151 152
        ImageCacheItem( int p, int w, int h, const QImage& i )
          : page( p ), width( w ), height( h ), img( i ) { }
Pino Toscano's avatar
Pino Toscano committed
153 154 155 156

        int page;
        int width;
        int height;
157
        QImage img;
Pino Toscano's avatar
Pino Toscano committed
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
};


// 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;
}

191 192 193 194 195 196
// KDjVu::Link

KDjVu::Link::~Link()
{
}

197 198 199 200 201
KDjVu::Link::LinkArea KDjVu::Link::areaType() const
{
    return m_area;
}

202 203 204 205 206 207 208 209 210 211
QPoint KDjVu::Link::point() const
{
    return m_point;
}

QSize KDjVu::Link::size() const
{
    return m_size;
}

212
QPolygon KDjVu::Link::polygon() const
213 214 215 216
{
    return m_poly;
}

217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243
// 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;
}

244
QString KDjVu::UrlLink::url() const
245 246 247 248
{
    return m_url;
}

249 250
// KDjVu::Annotation

251 252 253 254 255
KDjVu::Annotation::Annotation( miniexp_t anno )
    : m_anno( anno )
{
}

256 257 258 259
KDjVu::Annotation::~Annotation()
{
}

260
QPoint KDjVu::Annotation::point() const
261
{
262 263 264 265
    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 );
266 267 268 269
}

QString KDjVu::Annotation::comment() const
{
270
    return QString::fromUtf8( miniexp_to_str( miniexp_nth( 2, m_anno ) ) );
271 272
}

273 274 275 276 277
void KDjVu::Annotation::setComment( const QString &comment )
{
    miniexp_t exp = m_anno;
    exp = miniexp_cdr( exp );
    exp = miniexp_cdr( exp );
Laurent Montel's avatar
Laurent Montel committed
278
    miniexp_rplaca( exp, miniexp_string( comment.toUtf8().constData() ) );
279 280
}

281 282
QColor KDjVu::Annotation::color() const
{
283 284 285 286 287
    return QColor();
}

void KDjVu::Annotation::setColor( const QColor & )
{
288 289 290 291
}

// KDjVu::TextAnnotation

292 293
KDjVu::TextAnnotation::TextAnnotation( miniexp_t anno )
  : Annotation( anno ), m_inlineText( true )
294
{
295 296 297 298 299 300 301 302
    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 ) ) );
303
        if ( id == QLatin1String( "pushpin" ) )
304 305
            m_inlineText = false;
    }
306 307
}

308 309
QSize KDjVu::TextAnnotation::size() const
{
310 311 312 313
    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 );
314 315
}

316 317 318 319 320
int KDjVu::TextAnnotation::type() const
{
    return KDjVu::Annotation::TextAnnotation;
}

321 322 323 324 325 326 327 328 329 330 331 332
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();
Laurent Montel's avatar
Laurent Montel committed
333
    find_replace_or_add_second_in_pair( m_anno, "backclr", miniexp_symbol( col.constData() ) );
334 335
}

336 337 338 339 340 341 342
bool KDjVu::TextAnnotation::inlineText() const
{
    return m_inlineText;
}

// KDjVu::LineAnnotation

343
KDjVu::LineAnnotation::LineAnnotation( miniexp_t anno )
344
  : Annotation( anno ), m_isArrow( false ), m_width( miniexp_nil )
345
{
346 347 348 349 350 351 352 353
    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 ) ) );
354
        if ( id == QLatin1String( "arrow" ) )
355 356
            m_isArrow = true;
        else if ( id == QLatin1String( "width" ) )
357
            m_width = curelem;
358
    }
359 360 361 362 363 364 365
}

int KDjVu::LineAnnotation::type() const
{
    return KDjVu::Annotation::LineAnnotation;
}

366 367 368 369 370 371 372 373 374 375 376 377
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();
Laurent Montel's avatar
Laurent Montel committed
378
    find_replace_or_add_second_in_pair( m_anno, "lineclr", miniexp_symbol( col.constData() ) );
379 380
}

381 382
QPoint KDjVu::LineAnnotation::point2() const
{
383 384 385 386
    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 );
387 388
}

389 390 391 392 393 394 395
bool KDjVu::LineAnnotation::isArrow() const
{
    return m_isArrow;
}

int KDjVu::LineAnnotation::width() const
{
396 397 398 399 400 401 402 403 404
    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 ) );
405
}
406

407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425
// KDjVu::TextEntity

KDjVu::TextEntity::TextEntity()
{
}

KDjVu::TextEntity::~TextEntity()
{
}

QString KDjVu::TextEntity::text() const
{
    return m_text;
}

QRect KDjVu::TextEntity::rect() const
{
    return m_rect;
}
426 427


Pino Toscano's avatar
Pino Toscano committed
428 429 430 431
class KDjVu::Private
{
    public:
        Private()
432
          : m_djvu_cxt( nullptr ), m_djvu_document( nullptr ), m_format( nullptr ), m_docBookmarks( nullptr ),
433
            m_cacheEnabled( true )
Pino Toscano's avatar
Pino Toscano committed
434 435 436
        {
        }

437
        QImage generateImageTile( ddjvu_page_t *djvupage, int& res,
Pino Toscano's avatar
Pino Toscano committed
438 439
            int width, int row, int xdelta, int height, int col, int ydelta );

440 441 442 443
        void readBookmarks();
        void fillBookmarksRecurse( QDomDocument& maindoc, QDomNode& curnode,
            miniexp_t exp, int offset = -1 );

444 445
        void readMetaData( int page );

Pino Toscano's avatar
Pino Toscano committed
446 447
        int pageWithName( const QString & name );

Pino Toscano's avatar
Pino Toscano committed
448 449 450 451 452 453 454
        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;

455
        QList<ImageCacheItem*> mImgCache;
Pino Toscano's avatar
Pino Toscano committed
456

457
        QHash<QString, QVariant> m_metaData;
458
        QDomDocument * m_docBookmarks;
459

Pino Toscano's avatar
Pino Toscano committed
460 461
        QHash<QString, int> m_pageNamesCache;

462 463
        bool m_cacheEnabled;

464
        static unsigned int s_formatmask[4];
Pino Toscano's avatar
Pino Toscano committed
465 466
};

467 468
unsigned int KDjVu::Private::s_formatmask[4] = { 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 };

469
QImage KDjVu::Private::generateImageTile( ddjvu_page_t *djvupage, int& res,
Pino Toscano's avatar
Pino Toscano committed
470 471 472 473 474 475 476 477 478
    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;
479
#ifdef KDJVU_DEBUG
480
    qDebug() << "renderrect:" << renderrect;
481
#endif
Pino Toscano's avatar
Pino Toscano committed
482 483 484 485 486
    ddjvu_rect_t pagerect;
    pagerect.x = 0;
    pagerect.y = 0;
    pagerect.w = width;
    pagerect.h = height;
487
#ifdef KDJVU_DEBUG
488
    qDebug() << "pagerect:" << pagerect;
489
#endif
Pino Toscano's avatar
Pino Toscano committed
490
    handle_ddjvu_messages( m_djvu_cxt, false );
491
    QImage res_img( realwidth, realheight, QImage::Format_RGB32 );
492 493 494
    // the following line workarounds a rare crash in djvulibre;
    // it should be fixed with >= 3.5.21
    ddjvu_page_get_width( djvupage );
Pino Toscano's avatar
Pino Toscano committed
495
    res = ddjvu_page_render( djvupage, DDJVU_RENDER_COLOR,
496
                  &pagerect, &renderrect, m_format, res_img.bytesPerLine(), (char *)res_img.bits() );
497 498 499 500
    if (!res)
    {
        res_img.fill(Qt::white);
    }
501
#ifdef KDJVU_DEBUG
502
    qDebug() << "rendering result:" << res;
503
#endif
Pino Toscano's avatar
Pino Toscano committed
504 505
    handle_ddjvu_messages( m_djvu_cxt, false );

506
    return res_img;
Pino Toscano's avatar
Pino Toscano committed
507 508
}

509 510 511 512 513 514 515 516 517 518 519 520 521 522
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" ) ) )
    {
Laurent Montel's avatar
Laurent Montel committed
523
        m_docBookmarks = new QDomDocument( QStringLiteral("KDjVuBookmarks") );
524
        fillBookmarksRecurse( *m_docBookmarks, *m_docBookmarks, outline, 1 );
525
        ddjvu_miniexp_release( m_djvu_document, outline );
526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544
    }
}

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 ) ) );
Laurent Montel's avatar
Laurent Montel committed
545 546
            QDomElement el = maindoc.createElement( QStringLiteral("item") );
            el.setAttribute( QStringLiteral("title"), title );
Pino Toscano's avatar
Pino Toscano committed
547
            if ( !dest.isEmpty() )
548
            {
Pino Toscano's avatar
Pino Toscano committed
549 550 551 552 553 554 555
                if ( dest.at( 0 ) == QLatin1Char( '#' ) )
                {
                    dest.remove( 0, 1 );
                    bool isNumber = false;
                    dest.toInt( &isNumber );
                    if ( isNumber )
                    {
556 557 558 559 560
                        // 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 )
                        {
Laurent Montel's avatar
Laurent Montel committed
561
                            el.setAttribute( QStringLiteral("PageNumber"), QString::number( pageNo + 1 ) );
562 563 564
                        }
                        else
                        {
Laurent Montel's avatar
Laurent Montel committed
565
                            el.setAttribute( QStringLiteral("PageNumber"), dest );
566
                        }
Pino Toscano's avatar
Pino Toscano committed
567 568 569
                    }
                    else
                    {
Laurent Montel's avatar
Laurent Montel committed
570
                       el.setAttribute( QStringLiteral("PageName"), dest );
Pino Toscano's avatar
Pino Toscano committed
571 572 573 574
                    }
                }
                else
                {
Laurent Montel's avatar
Laurent Montel committed
575
                    el.setAttribute( QStringLiteral("URL"), dest );
Pino Toscano's avatar
Pino Toscano committed
576
                }
577
            }
Pino Toscano's avatar
Pino Toscano committed
578
            curnode.appendChild( el );
579 580 581 582 583 584 585 586
            if ( !el.isNull() && ( miniexp_length( cur ) > 2 ) )
            {
                fillBookmarksRecurse( maindoc, el, cur, 2 );
            }
        }
    }
}

587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616
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;
    }
}

Pino Toscano's avatar
Pino Toscano committed
617 618
int KDjVu::Private::pageWithName( const QString & name )
{
619 620 621 622
    const int pageNo = m_pageNamesCache.value( name, -1 );
    if ( pageNo != -1 )
        return pageNo;

Pino Toscano's avatar
Pino Toscano committed
623 624 625 626 627 628 629 630 631 632
    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 ) )
633 634
        {
            m_pageNamesCache.insert( name, info.pageno );
Pino Toscano's avatar
Pino Toscano committed
635
            return info.pageno;
636
        }
Pino Toscano's avatar
Pino Toscano committed
637 638 639 640
    }
    return -1;
}

Pino Toscano's avatar
Pino Toscano committed
641

642
KDjVu::KDjVu() : d( new Private )
Pino Toscano's avatar
Pino Toscano committed
643 644 645 646
{
    // creating the djvu context
    d->m_djvu_cxt = ddjvu_context_create( "KDjVu" );
    // creating the rendering format
647
#if DDJVUAPI_VERSION >= 18
648
    d->m_format = ddjvu_format_create( DDJVU_FORMAT_RGBMASK32, 4, Private::s_formatmask );
649
#else
650
    d->m_format = ddjvu_format_create( DDJVU_FORMAT_RGBMASK32, 3, Private::s_formatmask );
651
#endif
Pino Toscano's avatar
Pino Toscano committed
652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673
    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...
Laurent Montel's avatar
Laurent Montel committed
674
    d->m_djvu_document = ddjvu_document_create_by_filename( d->m_djvu_cxt, QFile::encodeName( fileName ).constData(), true );
Pino Toscano's avatar
Pino Toscano committed
675 676 677
    if ( !d->m_djvu_document ) return false;
    // ...and wait for its loading
    wait_for_ddjvu_message( d->m_djvu_cxt, DDJVU_DOCINFO );
678 679 680
    if ( ddjvu_document_decoding_error( d->m_djvu_document ) )
    {
        ddjvu_document_release( d->m_djvu_document );
681
        d->m_djvu_document = nullptr;
682 683
        return false;
    }
Pino Toscano's avatar
Pino Toscano committed
684

685
    qDebug() << "# of pages:" << ddjvu_document_get_pagenum( d->m_djvu_document );
Pino Toscano's avatar
Pino Toscano committed
686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715
    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() )
Laurent Montel's avatar
Laurent Montel committed
716
        d->m_metaData[ QStringLiteral("documentType") ] = doctype;
717
    // get the number of components
Laurent Montel's avatar
Laurent Montel committed
718
    d->m_metaData[ QStringLiteral("componentFile") ] = ddjvu_document_get_filenum( d->m_djvu_document );
Pino Toscano's avatar
Pino Toscano committed
719 720 721 722 723

    // read the pages
    for ( int i = 0; i < numofpages; ++i )
    {
        ddjvu_status_t sts;
724 725
        ddjvu_pageinfo_t info;
        while ( ( sts = ddjvu_document_get_pageinfo( d->m_djvu_document, i, &info ) ) < DDJVU_JOB_OK )
Pino Toscano's avatar
Pino Toscano committed
726 727 728
            handle_ddjvu_messages( d->m_djvu_cxt, true );
        if ( sts >= DDJVU_JOB_FAILED )
        {
729
            qDebug().nospace() << "\t>>> page " << i << " failed: " << sts;
730
            return false;
Pino Toscano's avatar
Pino Toscano committed
731 732 733
        }

        KDjVu::Page *p = new KDjVu::Page();
734 735 736
        p->m_width = info.width;
        p->m_height = info.height;
        p->m_dpi = info.dpi;
737
#if DDJVUAPI_VERSION >= 18
738
        p->m_orientation = flipRotation( info.rotation );
739 740 741
#else
        p->m_orientation = 0;
#endif
Pino Toscano's avatar
Pino Toscano committed
742 743 744
        d->m_pages[i] = p;
    }

745 746 747 748
    // reading the metadata from the first page only should be enough
    if ( numofpages > 0 )
        d->readMetaData( 0 );

Pino Toscano's avatar
Pino Toscano committed
749 750 751 752 753
    return true;
}

void KDjVu::closeFile()
{
754 755
    // deleting the old TOC
    delete d->m_docBookmarks;
756
    d->m_docBookmarks = nullptr;
Pino Toscano's avatar
Pino Toscano committed
757 758
    // deleting the pages
    qDeleteAll( d->m_pages );
759
    d->m_pages.clear();
Pino Toscano's avatar
Pino Toscano committed
760 761 762 763 764
    // 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();
765 766
    // clearing the image cache
    qDeleteAll( d->mImgCache );
767
    d->mImgCache.clear();
Pino Toscano's avatar
Pino Toscano committed
768 769
    // clearing the old metadata
    d->m_metaData.clear();
Yuri Chornoivan's avatar
Yuri Chornoivan committed
770
    // cleaning the page names mapping
Pino Toscano's avatar
Pino Toscano committed
771
    d->m_pageNamesCache.clear();
Pino Toscano's avatar
Pino Toscano committed
772 773 774
    // releasing the old document
    if ( d->m_djvu_document )
        ddjvu_document_release( d->m_djvu_document );
775
    d->m_djvu_document = nullptr;
Pino Toscano's avatar
Pino Toscano committed
776 777
}

778
QVariant KDjVu::metaData( const QString & key ) const
Pino Toscano's avatar
Pino Toscano committed
779
{
Laurent Montel's avatar
Laurent Montel committed
780 781
    QHash<QString, QVariant>::ConstIterator it = d->m_metaData.constFind( key );
    return it != d->m_metaData.constEnd() ? it.value() : QVariant();
Pino Toscano's avatar
Pino Toscano committed
782 783
}

784 785 786 787 788 789 790
const QDomDocument * KDjVu::documentBookmarks() const
{
    if ( !d->m_docBookmarks )
        d->readBookmarks();
    return d->m_docBookmarks;
}

791
void KDjVu::linksAndAnnotationsForPage( int pageNum, QList<KDjVu::Link*> *links, QList<KDjVu::Annotation*> *annotations ) const
792
{
793
    if ( ( pageNum < 0 ) || ( pageNum >= d->m_pages.count() ) || ( !links && !annotations ) )
794
        return;
795 796 797 798 799 800

    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 ) )
801
        return;
802

803 804 805 806
    if ( links )
        links->clear();
    if ( annotations )
        annotations->clear();
807 808 809 810 811 812

    int l = miniexp_length( annots );
    for ( int i = 0; i < l; ++i )
    {
        miniexp_t cur = miniexp_nth( i, annots );
        int num = miniexp_length( cur );
813
        if ( ( num < 4 ) || !miniexp_symbolp( miniexp_nth( 0, cur ) ) ||
814 815 816 817
             ( qstrncmp( miniexp_to_name( miniexp_nth( 0, cur ) ), "maparea", 7 ) != 0 ) )
            continue;

        QString target;
818 819 820
        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 ) ) ) );
821 822
        KDjVu::Link* link = nullptr;
        KDjVu::Annotation* ann = nullptr;
823
        miniexp_t urlexp = miniexp_nth( 1, cur );
824 825 826 827
        if ( links &&
             ( type == QLatin1String( "rect" ) ||
               type == QLatin1String( "oval" ) ||
               type == QLatin1String( "poly" ) ) )
828
        {
829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850
            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;
            }
851
        }
852 853 854
        else if ( annotations &&
                  ( type == QLatin1String( "text" ) ||
                    type == QLatin1String( "line" ) ) )
855
        {
856 857
            if ( type == QLatin1String( "text" ) )
            {
858
                KDjVu::TextAnnotation * textann = new KDjVu::TextAnnotation( cur );
859
                ann = textann;
860 861 862
            }
            else if ( type == QLatin1String( "line" ) )
            {
863
                KDjVu::LineAnnotation * lineann = new KDjVu::LineAnnotation( cur );
864
                ann = lineann;
865
            }
866
        }
867
        if ( link /* safety check */ && links )
868
        {
869
            link->m_area = KDjVu::Link::UnknownArea;
870 871
            miniexp_t area = miniexp_nth( 3, cur );
            int arealength = miniexp_length( area );
872
            if ( ( arealength == 5 ) && ( type == QLatin1String( "rect" ) || type == QLatin1String( "oval" ) ) )
873 874 875
            {
                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 ) ) );
876
                if ( type == QLatin1String( "rect" ) )
877 878 879 880 881 882 883
                {
                    link->m_area = KDjVu::Link::RectArea;
                }
                else
                {
                    link->m_area = KDjVu::Link::EllipseArea;
                }
884
            }
885
            else if ( ( arealength > 0 ) && ( arealength % 2 == 1 ) &&
886
                      type == QLatin1String( "poly" ) )
887 888 889 890 891 892 893 894 895
            {
                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;
            }
896

897
            if ( link->m_area != KDjVu::Link::UnknownArea )
898
                links->append( link );
899
        }
900
        else if ( ann /* safety check */ && annotations )
901
        {
902
            annotations->append( ann );
903
        }
904 905 906
    }
}

Pino Toscano's avatar
Pino Toscano committed
907 908 909 910 911
const QVector<KDjVu::Page*> &KDjVu::pages() const
{
    return d->m_pages;
}

912
QImage KDjVu::image( int page, int width, int height, int rotation )
Pino Toscano's avatar
Pino Toscano committed
913
{
914 915
    if ( d->m_cacheEnabled )
    {
Albert Astals Cid's avatar
Albert Astals Cid committed
916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933
        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 );
934

Albert Astals Cid's avatar
Albert Astals Cid committed
935 936
            return cur2->img;
        }
937
    }
Pino Toscano's avatar
Pino Toscano committed
938 939 940 941 942 943 944 945 946 947 948 949

    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];

950
/*
Pino Toscano's avatar
Pino Toscano committed
951 952 953 954 955 956
    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 ) );
    }
957
*/
Pino Toscano's avatar
Pino Toscano committed
958 959 960 961 962 963 964

    static const int xdelta = 1500;
    static const int ydelta = 1500;

    int xparts = width / xdelta + 1;
    int yparts = height / ydelta + 1;

965
    QImage newimg;
Pino Toscano's avatar
Pino Toscano committed
966 967 968 969

    int res = 10000;
    if ( ( xparts == 1 ) && ( yparts == 1 ) )
    {
970 971
         // only one part -- render at once with no need to auxiliary image
         newimg = d->generateImageTile( djvupage, res,
Pino Toscano's avatar
Pino Toscano committed
972 973 974 975 976 977
                 width, 0, xdelta, height, 0, ydelta );
    }
    else
    {
        // more than one part -- need to render piece-by-piece and to compose
        // the results
978
        newimg = QImage( width, height, QImage::Format_RGB32 );
Pino Toscano's avatar
Pino Toscano committed
979
        QPainter p;
980
        p.begin( &newimg );
Pino Toscano's avatar
Pino Toscano committed
981 982 983
        int parts = xparts * yparts;
        for ( int i = 0; i < parts; ++i )
        {
984 985
            const int row = i % xparts;
            const int col = i / xparts;
Pino Toscano's avatar
Pino Toscano committed
986
            int tmpres = 0;
987
            const QImage tempp = d->generateImageTile( djvupage, tmpres,
Pino Toscano's avatar
Pino Toscano committed
988
                    width, row, xdelta, height, col, ydelta );
989
            p.drawImage( row * xdelta, col * ydelta, tempp );
Pino Toscano's avatar
Pino Toscano committed
990 991 992 993 994
            res = qMin( tmpres, res );
        }
        p.end();
    }

995
    if ( res && d->m_cacheEnabled )
Pino Toscano's avatar
Pino Toscano committed
996 997 998
    {
        // delete all the cached pixmaps for the current page with a size that
        // differs no more than 35% of the new pixmap size
999
        int imgsize = newimg.width() * newimg.height();
1000
        if ( imgsize > 0 )
Pino Toscano's avatar
Pino Toscano committed
1001
        {
1002
            for( int i = 0; i < d->mImgCache.count(); )
Pino Toscano's avatar
Pino Toscano committed
1003
            {
1004
                ImageCacheItem* cur = d->mImgCache.at(i);
Pino Toscano's avatar
Pino Toscano committed
1005
                if ( ( cur->page == page ) &&
1006
                     ( abs( cur->img.width() * cur->img.height() - imgsize ) < imgsize * 0.35 ) )
Pino Toscano's avatar
Pino Toscano committed
1007
                {
1008
                    d->mImgCache.removeAt( i );
Pino Toscano's avatar
Pino Toscano committed
1009 1010 1011 1012 1013 1014 1015
                    delete cur;
                }
                else
                    ++i;
            }
        }

1016 1017
        // the image cache has too many elements, remove the last
        if ( d->mImgCache.size() >= 10 )
Pino Toscano's avatar
Pino Toscano committed
1018
        {
1019 1020
            delete d->mImgCache.last();
            d->mImgCache.removeLast();
Pino Toscano's avatar
Pino Toscano committed
1021
        }
1022 1023
        ImageCacheItem* ich = new ImageCacheItem( page, width, height, newimg );
        d->mImgCache.push_front( ich );
Pino Toscano's avatar
Pino Toscano committed
1024 1025
    }

1026
    return newimg;
Pino Toscano's avatar
Pino Toscano committed
1027 1028
}

1029 1030
bool KDjVu::exportAsPostScript( const QString & fileName, const QList<int>& pageList ) const
{
1031
    if ( !d->m_djvu_document || fileName.trimmed().isEmpty() || pageList.isEmpty() )