File indexing completed on 2024-05-12 16:06:34
0001 /* 0002 SPDX-FileCopyrightText: 2006 Pino Toscano <toscano.pino@tiscali.it> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "kdjvu.h" 0008 0009 #include <QByteArray> 0010 #include <QDomDocument> 0011 #include <QFile> 0012 #include <QHash> 0013 #include <QPainter> 0014 #include <QQueue> 0015 #include <QString> 0016 0017 #include <KLocalizedString> 0018 #include <QDebug> 0019 0020 #include <libdjvu/ddjvuapi.h> 0021 #include <libdjvu/miniexp.h> 0022 0023 #include <stdio.h> 0024 0025 QDebug &operator<<(QDebug &s, const ddjvu_rect_t r) 0026 { 0027 s.nospace() << "[" << r.x << "," << r.y << " - " << r.w << "x" << r.h << "]"; 0028 return s.space(); 0029 } 0030 0031 static void which_ddjvu_message(const ddjvu_message_t *msg) 0032 { 0033 #ifdef KDJVU_DEBUG 0034 qDebug() << "which_djvu_message(...):" << msg->m_any.tag; 0035 switch (msg->m_any.tag) { 0036 case DDJVU_ERROR: 0037 qDebug().nospace() << "ERROR: file " << msg->m_error.filename << ", line " << msg->m_error.lineno; 0038 qDebug().nospace() << "ERROR: function '" << msg->m_error.function << "'"; 0039 qDebug().nospace() << "ERROR: '" << msg->m_error.message << "'"; 0040 break; 0041 case DDJVU_INFO: 0042 qDebug().nospace() << "INFO: '" << msg->m_info.message << "'"; 0043 break; 0044 case DDJVU_CHUNK: 0045 qDebug().nospace() << "CHUNK: '" << QByteArray(msg->m_chunk.chunkid) << "'"; 0046 break; 0047 case DDJVU_PROGRESS: 0048 qDebug().nospace() << "PROGRESS: '" << msg->m_progress.percent << "'"; 0049 break; 0050 default:; 0051 } 0052 #else 0053 Q_UNUSED(msg); 0054 #endif 0055 } 0056 0057 /** 0058 * Explore the message queue until there are message left in it. 0059 */ 0060 static void handle_ddjvu_messages(ddjvu_context_t *ctx, int wait) 0061 { 0062 const ddjvu_message_t *msg; 0063 if (wait) { 0064 ddjvu_message_wait(ctx); 0065 } 0066 while ((msg = ddjvu_message_peek(ctx))) { 0067 which_ddjvu_message(msg); 0068 ddjvu_message_pop(ctx); 0069 } 0070 } 0071 0072 /** 0073 * Explore the message queue until the message \p mid is found. 0074 */ 0075 static void wait_for_ddjvu_message(ddjvu_context_t *ctx, ddjvu_message_tag_t mid) 0076 { 0077 ddjvu_message_wait(ctx); 0078 const ddjvu_message_t *msg; 0079 while ((msg = ddjvu_message_peek(ctx)) && msg && (msg->m_any.tag != mid)) { 0080 which_ddjvu_message(msg); 0081 ddjvu_message_pop(ctx); 0082 } 0083 } 0084 0085 /** 0086 * Convert a clockwise coefficient \p r for a rotation to a counter-clockwise 0087 * and vice versa. 0088 */ 0089 static int flipRotation(int r) 0090 { 0091 return (4 - r) % 4; 0092 } 0093 0094 static miniexp_t find_second_in_pair(miniexp_t theexp, const char *which) 0095 { 0096 miniexp_t exp = theexp; 0097 while (exp) { 0098 miniexp_t cur = miniexp_car(exp); 0099 if (!miniexp_consp(cur) || !miniexp_symbolp(miniexp_car(cur))) { 0100 exp = miniexp_cdr(exp); 0101 continue; 0102 } 0103 0104 const QString id = QString::fromUtf8(miniexp_to_name(miniexp_car(cur))); 0105 if (id == QLatin1String(which)) { 0106 return miniexp_cadr(cur); 0107 } 0108 exp = miniexp_cdr(exp); 0109 } 0110 return miniexp_nil; 0111 } 0112 0113 static bool find_replace_or_add_second_in_pair(miniexp_t theexp, const char *which, miniexp_t replacement) 0114 { 0115 miniexp_t exp = miniexp_cdddr(theexp); 0116 while (exp) { 0117 miniexp_t cur = miniexp_car(exp); 0118 if (!miniexp_consp(cur) || !miniexp_symbolp(miniexp_car(cur))) { 0119 exp = miniexp_cdr(exp); 0120 continue; 0121 } 0122 0123 const QString id = QString::fromUtf8(miniexp_to_name(miniexp_car(cur))); 0124 if (id == QLatin1String(which)) { 0125 miniexp_t reversed = miniexp_reverse(cur); 0126 miniexp_rplaca(reversed, replacement); 0127 cur = miniexp_reverse(reversed); 0128 return true; 0129 } 0130 exp = miniexp_cdr(exp); 0131 } 0132 // TODO add the new replacement ad the end of the list 0133 return false; 0134 } 0135 0136 // ImageCacheItem 0137 0138 class ImageCacheItem 0139 { 0140 public: 0141 ImageCacheItem(int p, int w, int h, const QImage &i) 0142 : page(p) 0143 , width(w) 0144 , height(h) 0145 , img(i) 0146 { 0147 } 0148 0149 int page; 0150 int width; 0151 int height; 0152 QImage img; 0153 }; 0154 0155 // KdjVu::Page 0156 0157 KDjVu::Page::Page() 0158 { 0159 } 0160 0161 KDjVu::Page::~Page() 0162 { 0163 } 0164 0165 int KDjVu::Page::width() const 0166 { 0167 return m_width; 0168 } 0169 0170 int KDjVu::Page::height() const 0171 { 0172 return m_height; 0173 } 0174 0175 int KDjVu::Page::dpi() const 0176 { 0177 return m_dpi; 0178 } 0179 0180 int KDjVu::Page::orientation() const 0181 { 0182 return m_orientation; 0183 } 0184 0185 // KDjVu::Link 0186 0187 KDjVu::Link::~Link() 0188 { 0189 } 0190 0191 KDjVu::Link::LinkArea KDjVu::Link::areaType() const 0192 { 0193 return m_area; 0194 } 0195 0196 QPoint KDjVu::Link::point() const 0197 { 0198 return m_point; 0199 } 0200 0201 QSize KDjVu::Link::size() const 0202 { 0203 return m_size; 0204 } 0205 0206 QPolygon KDjVu::Link::polygon() const 0207 { 0208 return m_poly; 0209 } 0210 0211 // KDjVu::PageLink 0212 0213 KDjVu::PageLink::PageLink() 0214 { 0215 } 0216 0217 int KDjVu::PageLink::type() const 0218 { 0219 return KDjVu::Link::PageLink; 0220 } 0221 0222 QString KDjVu::PageLink::page() const 0223 { 0224 return m_page; 0225 } 0226 0227 // KDjVu::UrlLink 0228 0229 KDjVu::UrlLink::UrlLink() 0230 { 0231 } 0232 0233 int KDjVu::UrlLink::type() const 0234 { 0235 return KDjVu::Link::UrlLink; 0236 } 0237 0238 QString KDjVu::UrlLink::url() const 0239 { 0240 return m_url; 0241 } 0242 0243 // KDjVu::Annotation 0244 0245 KDjVu::Annotation::Annotation(miniexp_t anno) 0246 : m_anno(anno) 0247 { 0248 } 0249 0250 KDjVu::Annotation::~Annotation() 0251 { 0252 } 0253 0254 QPoint KDjVu::Annotation::point() const 0255 { 0256 miniexp_t area = miniexp_nth(3, m_anno); 0257 int a = miniexp_to_int(miniexp_nth(1, area)); 0258 int b = miniexp_to_int(miniexp_nth(2, area)); 0259 return QPoint(a, b); 0260 } 0261 0262 QString KDjVu::Annotation::comment() const 0263 { 0264 return QString::fromUtf8(miniexp_to_str(miniexp_nth(2, m_anno))); 0265 } 0266 0267 void KDjVu::Annotation::setComment(const QString &comment) 0268 { 0269 miniexp_t exp = m_anno; 0270 exp = miniexp_cdr(exp); 0271 exp = miniexp_cdr(exp); 0272 miniexp_rplaca(exp, miniexp_string(comment.toUtf8().constData())); 0273 } 0274 0275 QColor KDjVu::Annotation::color() const 0276 { 0277 return QColor(); 0278 } 0279 0280 void KDjVu::Annotation::setColor(const QColor &) 0281 { 0282 } 0283 0284 // KDjVu::TextAnnotation 0285 0286 KDjVu::TextAnnotation::TextAnnotation(miniexp_t anno) 0287 : Annotation(anno) 0288 , m_inlineText(true) 0289 { 0290 const int num = miniexp_length(m_anno); 0291 for (int j = 4; j < num; ++j) { 0292 miniexp_t curelem = miniexp_nth(j, m_anno); 0293 if (!miniexp_listp(curelem)) { 0294 continue; 0295 } 0296 0297 QString id = QString::fromUtf8(miniexp_to_name(miniexp_nth(0, curelem))); 0298 if (id == QLatin1String("pushpin")) { 0299 m_inlineText = false; 0300 } 0301 } 0302 } 0303 0304 QSize KDjVu::TextAnnotation::size() const 0305 { 0306 miniexp_t area = miniexp_nth(3, m_anno); 0307 int c = miniexp_to_int(miniexp_nth(3, area)); 0308 int d = miniexp_to_int(miniexp_nth(4, area)); 0309 return QSize(c, d); 0310 } 0311 0312 int KDjVu::TextAnnotation::type() const 0313 { 0314 return KDjVu::Annotation::TextAnnotation; 0315 } 0316 0317 QColor KDjVu::TextAnnotation::color() const 0318 { 0319 miniexp_t col = find_second_in_pair(m_anno, "backclr"); 0320 if (!miniexp_symbolp(col)) { 0321 return Qt::transparent; 0322 } 0323 0324 return QColor(QString::fromUtf8(miniexp_to_name(col))); 0325 } 0326 0327 void KDjVu::TextAnnotation::setColor(const QColor &color) 0328 { 0329 const QByteArray col = color.name().toLatin1(); 0330 find_replace_or_add_second_in_pair(m_anno, "backclr", miniexp_symbol(col.constData())); 0331 } 0332 0333 bool KDjVu::TextAnnotation::inlineText() const 0334 { 0335 return m_inlineText; 0336 } 0337 0338 // KDjVu::LineAnnotation 0339 0340 KDjVu::LineAnnotation::LineAnnotation(miniexp_t anno) 0341 : Annotation(anno) 0342 , m_isArrow(false) 0343 , m_width(miniexp_nil) 0344 { 0345 const int num = miniexp_length(m_anno); 0346 for (int j = 4; j < num; ++j) { 0347 miniexp_t curelem = miniexp_nth(j, m_anno); 0348 if (!miniexp_listp(curelem)) { 0349 continue; 0350 } 0351 0352 QString id = QString::fromUtf8(miniexp_to_name(miniexp_nth(0, curelem))); 0353 if (id == QLatin1String("arrow")) { 0354 m_isArrow = true; 0355 } else if (id == QLatin1String("width")) { 0356 m_width = curelem; 0357 } 0358 } 0359 } 0360 0361 int KDjVu::LineAnnotation::type() const 0362 { 0363 return KDjVu::Annotation::LineAnnotation; 0364 } 0365 0366 QColor KDjVu::LineAnnotation::color() const 0367 { 0368 miniexp_t col = find_second_in_pair(m_anno, "lineclr"); 0369 if (!miniexp_symbolp(col)) { 0370 return Qt::black; 0371 } 0372 0373 return QColor(QString::fromUtf8(miniexp_to_name(col))); 0374 } 0375 0376 void KDjVu::LineAnnotation::setColor(const QColor &color) 0377 { 0378 const QByteArray col = color.name().toLatin1(); 0379 find_replace_or_add_second_in_pair(m_anno, "lineclr", miniexp_symbol(col.constData())); 0380 } 0381 0382 QPoint KDjVu::LineAnnotation::point2() const 0383 { 0384 miniexp_t area = miniexp_nth(3, m_anno); 0385 int c = miniexp_to_int(miniexp_nth(3, area)); 0386 int d = miniexp_to_int(miniexp_nth(4, area)); 0387 return QPoint(c, d); 0388 } 0389 0390 bool KDjVu::LineAnnotation::isArrow() const 0391 { 0392 return m_isArrow; 0393 } 0394 0395 int KDjVu::LineAnnotation::width() const 0396 { 0397 if (m_width == miniexp_nil) { 0398 return 1; 0399 } 0400 0401 return miniexp_to_int(miniexp_cadr(m_width)); 0402 } 0403 0404 void KDjVu::LineAnnotation::setWidth(int width) 0405 { 0406 find_replace_or_add_second_in_pair(m_anno, "width", miniexp_number(width)); 0407 } 0408 0409 // KDjVu::TextEntity 0410 0411 KDjVu::TextEntity::TextEntity() 0412 { 0413 } 0414 0415 KDjVu::TextEntity::~TextEntity() 0416 { 0417 } 0418 0419 QString KDjVu::TextEntity::text() const 0420 { 0421 return m_text; 0422 } 0423 0424 QRect KDjVu::TextEntity::rect() const 0425 { 0426 return m_rect; 0427 } 0428 0429 class KDjVu::Private 0430 { 0431 public: 0432 Private() 0433 : m_djvu_cxt(nullptr) 0434 , m_djvu_document(nullptr) 0435 , m_format(nullptr) 0436 , m_docBookmarks(nullptr) 0437 , m_cacheEnabled(true) 0438 { 0439 } 0440 0441 QImage generateImageTile(ddjvu_page_t *djvupage, int &res, int width, int row, int xdelta, int height, int col, int ydelta); 0442 0443 void readBookmarks(); 0444 void fillBookmarksRecurse(QDomDocument &maindoc, QDomNode &curnode, miniexp_t exp, int offset = -1); 0445 0446 void readMetaData(int page); 0447 0448 int pageWithName(const QString &name); 0449 0450 ddjvu_context_t *m_djvu_cxt; 0451 ddjvu_document_t *m_djvu_document; 0452 ddjvu_format_t *m_format; 0453 0454 QVector<KDjVu::Page *> m_pages; 0455 QVector<ddjvu_page_t *> m_pages_cache; 0456 0457 QList<ImageCacheItem *> mImgCache; 0458 0459 QHash<QString, QVariant> m_metaData; 0460 QDomDocument *m_docBookmarks; 0461 0462 QHash<QString, int> m_pageNamesCache; 0463 0464 bool m_cacheEnabled; 0465 0466 static unsigned int s_formatmask[4]; 0467 }; 0468 0469 unsigned int KDjVu::Private::s_formatmask[4] = {0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000}; 0470 0471 QImage KDjVu::Private::generateImageTile(ddjvu_page_t *djvupage, int &res, int width, int row, int xdelta, int height, int col, int ydelta) 0472 { 0473 ddjvu_rect_t renderrect; 0474 renderrect.x = row * xdelta; 0475 renderrect.y = col * ydelta; 0476 int realwidth = qMin(width - renderrect.x, xdelta); 0477 int realheight = qMin(height - renderrect.y, ydelta); 0478 renderrect.w = realwidth; 0479 renderrect.h = realheight; 0480 #ifdef KDJVU_DEBUG 0481 qDebug() << "renderrect:" << renderrect; 0482 #endif 0483 ddjvu_rect_t pagerect; 0484 pagerect.x = 0; 0485 pagerect.y = 0; 0486 pagerect.w = width; 0487 pagerect.h = height; 0488 #ifdef KDJVU_DEBUG 0489 qDebug() << "pagerect:" << pagerect; 0490 #endif 0491 handle_ddjvu_messages(m_djvu_cxt, false); 0492 QImage res_img(realwidth, realheight, QImage::Format_RGB32); 0493 // the following line workarounds a rare crash in djvulibre; 0494 // it should be fixed with >= 3.5.21 0495 ddjvu_page_get_width(djvupage); 0496 res = ddjvu_page_render(djvupage, DDJVU_RENDER_COLOR, &pagerect, &renderrect, m_format, res_img.bytesPerLine(), (char *)res_img.bits()); 0497 if (!res) { 0498 res_img.fill(Qt::white); 0499 } 0500 #ifdef KDJVU_DEBUG 0501 qDebug() << "rendering result:" << res; 0502 #endif 0503 handle_ddjvu_messages(m_djvu_cxt, false); 0504 0505 return res_img; 0506 } 0507 0508 void KDjVu::Private::readBookmarks() 0509 { 0510 if (!m_djvu_document) { 0511 return; 0512 } 0513 0514 miniexp_t outline; 0515 while ((outline = ddjvu_document_get_outline(m_djvu_document)) == miniexp_dummy) { 0516 handle_ddjvu_messages(m_djvu_cxt, true); 0517 } 0518 0519 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"))) { 0520 m_docBookmarks = new QDomDocument(QStringLiteral("KDjVuBookmarks")); 0521 fillBookmarksRecurse(*m_docBookmarks, *m_docBookmarks, outline, 1); 0522 ddjvu_miniexp_release(m_djvu_document, outline); 0523 } 0524 } 0525 0526 void KDjVu::Private::fillBookmarksRecurse(QDomDocument &maindoc, QDomNode &curnode, miniexp_t exp, int offset) 0527 { 0528 if (!miniexp_listp(exp)) { 0529 return; 0530 } 0531 0532 int l = miniexp_length(exp); 0533 for (int i = qMax(offset, 0); i < l; ++i) { 0534 miniexp_t cur = miniexp_nth(i, exp); 0535 0536 if (miniexp_consp(cur) && (miniexp_length(cur) > 0) && miniexp_stringp(miniexp_nth(0, cur)) && miniexp_stringp(miniexp_nth(1, cur))) { 0537 QString title = QString::fromUtf8(miniexp_to_str(miniexp_nth(0, cur))); 0538 QString dest = QString::fromUtf8(miniexp_to_str(miniexp_nth(1, cur))); 0539 QDomElement el = maindoc.createElement(QStringLiteral("item")); 0540 el.setAttribute(QStringLiteral("title"), title); 0541 if (!dest.isEmpty()) { 0542 if (dest.at(0) == QLatin1Char('#')) { 0543 dest.remove(0, 1); 0544 bool isNumber = false; 0545 dest.toInt(&isNumber); 0546 if (isNumber) { 0547 // it might be an actual page number, but could also be a page label 0548 // so resolve the number, and get the real page number 0549 int pageNo = pageWithName(dest); 0550 if (pageNo != -1) { 0551 el.setAttribute(QStringLiteral("PageNumber"), QString::number(pageNo + 1)); 0552 } else { 0553 el.setAttribute(QStringLiteral("PageNumber"), dest); 0554 } 0555 } else { 0556 el.setAttribute(QStringLiteral("PageName"), dest); 0557 } 0558 } else { 0559 el.setAttribute(QStringLiteral("URL"), dest); 0560 } 0561 } 0562 curnode.appendChild(el); 0563 if (!el.isNull() && (miniexp_length(cur) > 2)) { 0564 fillBookmarksRecurse(maindoc, el, cur, 2); 0565 } 0566 } 0567 } 0568 } 0569 0570 void KDjVu::Private::readMetaData(int page) 0571 { 0572 if (!m_djvu_document) { 0573 return; 0574 } 0575 0576 miniexp_t annots; 0577 while ((annots = ddjvu_document_get_pageanno(m_djvu_document, page)) == miniexp_dummy) { 0578 handle_ddjvu_messages(m_djvu_cxt, true); 0579 } 0580 0581 if (!miniexp_listp(annots) || miniexp_length(annots) == 0) { 0582 return; 0583 } 0584 0585 miniexp_t exp = miniexp_nth(0, annots); 0586 int size = miniexp_length(exp); 0587 if (size <= 1 || qstrncmp(miniexp_to_name(miniexp_nth(0, exp)), "metadata", 8)) { 0588 return; 0589 } 0590 0591 for (int i = 1; i < size; ++i) { 0592 miniexp_t cur = miniexp_nth(i, exp); 0593 if (miniexp_length(cur) != 2) { 0594 continue; 0595 } 0596 0597 QString id = QString::fromUtf8(miniexp_to_name(miniexp_nth(0, cur))); 0598 QString value = QString::fromUtf8(miniexp_to_str(miniexp_nth(1, cur))); 0599 m_metaData[id.toLower()] = value; 0600 } 0601 } 0602 0603 int KDjVu::Private::pageWithName(const QString &name) 0604 { 0605 const int pageNo = m_pageNamesCache.value(name, -1); 0606 if (pageNo != -1) { 0607 return pageNo; 0608 } 0609 0610 const QByteArray utfName = name.toUtf8(); 0611 const int fileNum = ddjvu_document_get_filenum(m_djvu_document); 0612 ddjvu_fileinfo_t info; 0613 for (int i = 0; i < fileNum; ++i) { 0614 if (DDJVU_JOB_OK != ddjvu_document_get_fileinfo(m_djvu_document, i, &info)) { 0615 continue; 0616 } 0617 if (info.type != 'P') { 0618 continue; 0619 } 0620 if ((utfName == info.id) || (utfName == info.name) || (utfName == info.title)) { 0621 m_pageNamesCache.insert(name, info.pageno); 0622 return info.pageno; 0623 } 0624 } 0625 return -1; 0626 } 0627 0628 KDjVu::KDjVu() 0629 : d(new Private) 0630 { 0631 // creating the djvu context 0632 d->m_djvu_cxt = ddjvu_context_create("KDjVu"); 0633 // creating the rendering format 0634 #if DDJVUAPI_VERSION >= 18 0635 d->m_format = ddjvu_format_create(DDJVU_FORMAT_RGBMASK32, 4, Private::s_formatmask); 0636 #else 0637 d->m_format = ddjvu_format_create(DDJVU_FORMAT_RGBMASK32, 3, Private::s_formatmask); 0638 #endif 0639 ddjvu_format_set_row_order(d->m_format, 1); 0640 ddjvu_format_set_y_direction(d->m_format, 1); 0641 } 0642 0643 KDjVu::~KDjVu() 0644 { 0645 closeFile(); 0646 0647 ddjvu_format_release(d->m_format); 0648 ddjvu_context_release(d->m_djvu_cxt); 0649 0650 delete d; 0651 } 0652 0653 bool KDjVu::openFile(const QString &fileName) 0654 { 0655 // first, close the old file 0656 if (d->m_djvu_document) { 0657 closeFile(); 0658 } 0659 0660 // load the document..., use UTF-8 variant to work on Windows, too, see bug 422500 0661 d->m_djvu_document = ddjvu_document_create_by_filename_utf8(d->m_djvu_cxt, fileName.toUtf8().constData(), true); 0662 if (!d->m_djvu_document) { 0663 return false; 0664 } 0665 // ...and wait for its loading 0666 wait_for_ddjvu_message(d->m_djvu_cxt, DDJVU_DOCINFO); 0667 if (ddjvu_document_decoding_error(d->m_djvu_document)) { 0668 ddjvu_document_release(d->m_djvu_document); 0669 d->m_djvu_document = nullptr; 0670 return false; 0671 } 0672 0673 qDebug() << "# of pages:" << ddjvu_document_get_pagenum(d->m_djvu_document); 0674 int numofpages = ddjvu_document_get_pagenum(d->m_djvu_document); 0675 d->m_pages.clear(); 0676 d->m_pages.resize(numofpages); 0677 d->m_pages_cache.clear(); 0678 d->m_pages_cache.resize(numofpages); 0679 0680 // get the document type 0681 QString doctype; 0682 switch (ddjvu_document_get_type(d->m_djvu_document)) { 0683 case DDJVU_DOCTYPE_UNKNOWN: 0684 doctype = i18nc("Type of DjVu document", "Unknown"); 0685 break; 0686 case DDJVU_DOCTYPE_SINGLEPAGE: 0687 doctype = i18nc("Type of DjVu document", "Single Page"); 0688 break; 0689 case DDJVU_DOCTYPE_BUNDLED: 0690 doctype = i18nc("Type of DjVu document", "Bundled"); 0691 break; 0692 case DDJVU_DOCTYPE_INDIRECT: 0693 doctype = i18nc("Type of DjVu document", "Indirect"); 0694 break; 0695 case DDJVU_DOCTYPE_OLD_BUNDLED: 0696 doctype = i18nc("Type of DjVu document", "Bundled (old)"); 0697 break; 0698 case DDJVU_DOCTYPE_OLD_INDEXED: 0699 doctype = i18nc("Type of DjVu document", "Indexed (old)"); 0700 break; 0701 } 0702 if (!doctype.isEmpty()) { 0703 d->m_metaData[QStringLiteral("documentType")] = doctype; 0704 } 0705 // get the number of components 0706 d->m_metaData[QStringLiteral("componentFile")] = ddjvu_document_get_filenum(d->m_djvu_document); 0707 0708 // read the pages 0709 for (int i = 0; i < numofpages; ++i) { 0710 ddjvu_status_t sts; 0711 ddjvu_pageinfo_t info; 0712 while ((sts = ddjvu_document_get_pageinfo(d->m_djvu_document, i, &info)) < DDJVU_JOB_OK) { 0713 handle_ddjvu_messages(d->m_djvu_cxt, true); 0714 } 0715 if (sts >= DDJVU_JOB_FAILED) { 0716 qDebug().nospace() << "\t>>> page " << i << " failed: " << sts; 0717 return false; 0718 } 0719 0720 KDjVu::Page *p = new KDjVu::Page(); 0721 p->m_width = info.width; 0722 p->m_height = info.height; 0723 p->m_dpi = info.dpi; 0724 #if DDJVUAPI_VERSION >= 18 0725 p->m_orientation = flipRotation(info.rotation); 0726 #else 0727 p->m_orientation = 0; 0728 #endif 0729 d->m_pages[i] = p; 0730 } 0731 0732 // reading the metadata from the first page only should be enough 0733 if (numofpages > 0) { 0734 d->readMetaData(0); 0735 } 0736 0737 return true; 0738 } 0739 0740 void KDjVu::closeFile() 0741 { 0742 // deleting the old TOC 0743 delete d->m_docBookmarks; 0744 d->m_docBookmarks = nullptr; 0745 // deleting the pages 0746 qDeleteAll(d->m_pages); 0747 d->m_pages.clear(); 0748 // releasing the djvu pages 0749 QVector<ddjvu_page_t *>::Iterator it = d->m_pages_cache.begin(), itEnd = d->m_pages_cache.end(); 0750 for (; it != itEnd; ++it) { 0751 ddjvu_page_release(*it); 0752 } 0753 d->m_pages_cache.clear(); 0754 // clearing the image cache 0755 qDeleteAll(d->mImgCache); 0756 d->mImgCache.clear(); 0757 // clearing the old metadata 0758 d->m_metaData.clear(); 0759 // cleaning the page names mapping 0760 d->m_pageNamesCache.clear(); 0761 // releasing the old document 0762 if (d->m_djvu_document) { 0763 ddjvu_document_release(d->m_djvu_document); 0764 } 0765 d->m_djvu_document = nullptr; 0766 } 0767 0768 QVariant KDjVu::metaData(const QString &key) const 0769 { 0770 QHash<QString, QVariant>::ConstIterator it = d->m_metaData.constFind(key); 0771 return it != d->m_metaData.constEnd() ? it.value() : QVariant(); 0772 } 0773 0774 const QDomDocument *KDjVu::documentBookmarks() const 0775 { 0776 if (!d->m_docBookmarks) { 0777 d->readBookmarks(); 0778 } 0779 return d->m_docBookmarks; 0780 } 0781 0782 void KDjVu::linksAndAnnotationsForPage(int pageNum, QList<KDjVu::Link *> *links, QList<KDjVu::Annotation *> *annotations) const 0783 { 0784 if ((pageNum < 0) || (pageNum >= d->m_pages.count()) || (!links && !annotations)) { 0785 return; 0786 } 0787 0788 miniexp_t annots; 0789 while ((annots = ddjvu_document_get_pageanno(d->m_djvu_document, pageNum)) == miniexp_dummy) { 0790 handle_ddjvu_messages(d->m_djvu_cxt, true); 0791 } 0792 0793 if (!miniexp_listp(annots)) { 0794 return; 0795 } 0796 0797 if (links) { 0798 links->clear(); 0799 } 0800 if (annotations) { 0801 annotations->clear(); 0802 } 0803 0804 int l = miniexp_length(annots); 0805 for (int i = 0; i < l; ++i) { 0806 miniexp_t cur = miniexp_nth(i, annots); 0807 int num = miniexp_length(cur); 0808 if ((num < 4) || !miniexp_symbolp(miniexp_nth(0, cur)) || (qstrncmp(miniexp_to_name(miniexp_nth(0, cur)), "maparea", 7) != 0)) { 0809 continue; 0810 } 0811 0812 QString target; 0813 QString type; 0814 if (miniexp_symbolp(miniexp_nth(0, miniexp_nth(3, cur)))) { 0815 type = QString::fromUtf8(miniexp_to_name(miniexp_nth(0, miniexp_nth(3, cur)))); 0816 } 0817 KDjVu::Link *link = nullptr; 0818 KDjVu::Annotation *ann = nullptr; 0819 miniexp_t urlexp = miniexp_nth(1, cur); 0820 if (links && (type == QLatin1String("rect") || type == QLatin1String("oval") || type == QLatin1String("poly"))) { 0821 if (miniexp_stringp(urlexp)) { 0822 target = QString::fromUtf8(miniexp_to_str(miniexp_nth(1, cur))); 0823 } 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)) { 0824 target = QString::fromUtf8(miniexp_to_str(miniexp_nth(1, urlexp))); 0825 } 0826 if (target.isEmpty() || ((target.length() > 0) && target.at(0) == QLatin1Char('#'))) { 0827 KDjVu::PageLink *plink = new KDjVu::PageLink(); 0828 plink->m_page = target; 0829 link = plink; 0830 } else { 0831 KDjVu::UrlLink *ulink = new KDjVu::UrlLink(); 0832 ulink->m_url = target; 0833 link = ulink; 0834 } 0835 } else if (annotations && (type == QLatin1String("text") || type == QLatin1String("line"))) { 0836 if (type == QLatin1String("text")) { 0837 KDjVu::TextAnnotation *textann = new KDjVu::TextAnnotation(cur); 0838 ann = textann; 0839 } else if (type == QLatin1String("line")) { 0840 KDjVu::LineAnnotation *lineann = new KDjVu::LineAnnotation(cur); 0841 ann = lineann; 0842 } 0843 } 0844 if (link /* safety check */ && links) { 0845 link->m_area = KDjVu::Link::UnknownArea; 0846 miniexp_t area = miniexp_nth(3, cur); 0847 int arealength = miniexp_length(area); 0848 if ((arealength == 5) && (type == QLatin1String("rect") || type == QLatin1String("oval"))) { 0849 link->m_point = QPoint(miniexp_to_int(miniexp_nth(1, area)), miniexp_to_int(miniexp_nth(2, area))); 0850 link->m_size = QSize(miniexp_to_int(miniexp_nth(3, area)), miniexp_to_int(miniexp_nth(4, area))); 0851 if (type == QLatin1String("rect")) { 0852 link->m_area = KDjVu::Link::RectArea; 0853 } else { 0854 link->m_area = KDjVu::Link::EllipseArea; 0855 } 0856 } else if ((arealength > 0) && (arealength % 2 == 1) && type == QLatin1String("poly")) { 0857 link->m_area = KDjVu::Link::PolygonArea; 0858 QPolygon poly; 0859 for (int j = 1; j < arealength; j += 2) { 0860 poly << QPoint(miniexp_to_int(miniexp_nth(j, area)), miniexp_to_int(miniexp_nth(j + 1, area))); 0861 } 0862 link->m_poly = poly; 0863 } 0864 0865 if (link->m_area != KDjVu::Link::UnknownArea) { 0866 links->append(link); 0867 } 0868 } else if (ann /* safety check */ && annotations) { 0869 annotations->append(ann); 0870 } 0871 } 0872 } 0873 0874 const QVector<KDjVu::Page *> &KDjVu::pages() const 0875 { 0876 return d->m_pages; 0877 } 0878 0879 QImage KDjVu::image(int page, int width, int height, int rotation) 0880 { 0881 if (d->m_cacheEnabled) { 0882 bool found = false; 0883 QList<ImageCacheItem *>::Iterator it = d->mImgCache.begin(), itEnd = d->mImgCache.end(); 0884 for (; (it != itEnd) && !found; ++it) { 0885 ImageCacheItem *cur = *it; 0886 if ((cur->page == page) && (rotation % 2 == 0 ? cur->width == width && cur->height == height : cur->width == height && cur->height == width)) { 0887 found = true; 0888 } 0889 } 0890 if (found) { 0891 // taking the element and pushing to the top of the list 0892 --it; 0893 ImageCacheItem *cur2 = *it; 0894 d->mImgCache.erase(it); 0895 d->mImgCache.push_front(cur2); 0896 0897 return cur2->img; 0898 } 0899 } 0900 0901 if (!d->m_pages_cache.at(page)) { 0902 ddjvu_page_t *newpage = ddjvu_page_create_by_pageno(d->m_djvu_document, page); 0903 // wait for the new page to be loaded 0904 ddjvu_status_t sts; 0905 while ((sts = ddjvu_page_decoding_status(newpage)) < DDJVU_JOB_OK) { 0906 handle_ddjvu_messages(d->m_djvu_cxt, true); 0907 } 0908 d->m_pages_cache[page] = newpage; 0909 } 0910 ddjvu_page_t *djvupage = d->m_pages_cache[page]; 0911 0912 /* 0913 if ( ddjvu_page_get_rotation( djvupage ) != flipRotation( rotation ) ) 0914 { 0915 // TODO: test documents with initial rotation != 0 0916 // ddjvu_page_set_rotation( djvupage, m_pages.at( page )->orientation() ); 0917 ddjvu_page_set_rotation( djvupage, (ddjvu_page_rotation_t)flipRotation( rotation ) ); 0918 } 0919 */ 0920 0921 static const int xdelta = 1500; 0922 static const int ydelta = 1500; 0923 0924 int xparts = width / xdelta + 1; 0925 int yparts = height / ydelta + 1; 0926 0927 QImage newimg; 0928 0929 int res = 10000; 0930 if ((xparts == 1) && (yparts == 1)) { 0931 // only one part -- render at once with no need to auxiliary image 0932 newimg = d->generateImageTile(djvupage, res, width, 0, xdelta, height, 0, ydelta); 0933 } else { 0934 // more than one part -- need to render piece-by-piece and to compose 0935 // the results 0936 newimg = QImage(width, height, QImage::Format_RGB32); 0937 QPainter p; 0938 p.begin(&newimg); 0939 int parts = xparts * yparts; 0940 for (int i = 0; i < parts; ++i) { 0941 const int row = i % xparts; 0942 const int col = i / xparts; 0943 int tmpres = 0; 0944 const QImage tempp = d->generateImageTile(djvupage, tmpres, width, row, xdelta, height, col, ydelta); 0945 p.drawImage(row * xdelta, col * ydelta, tempp); 0946 res = qMin(tmpres, res); 0947 } 0948 p.end(); 0949 } 0950 0951 if (res && d->m_cacheEnabled) { 0952 // delete all the cached pixmaps for the current page with a size that 0953 // differs no more than 35% of the new pixmap size 0954 int imgsize = newimg.width() * newimg.height(); 0955 if (imgsize > 0) { 0956 for (int i = 0; i < d->mImgCache.count();) { 0957 ImageCacheItem *cur = d->mImgCache.at(i); 0958 if ((cur->page == page) && (abs(cur->img.width() * cur->img.height() - imgsize) < imgsize * 0.35)) { 0959 d->mImgCache.removeAt(i); 0960 delete cur; 0961 } else { 0962 ++i; 0963 } 0964 } 0965 } 0966 0967 // the image cache has too many elements, remove the last 0968 if (d->mImgCache.size() >= 10) { 0969 delete d->mImgCache.last(); 0970 d->mImgCache.removeLast(); 0971 } 0972 ImageCacheItem *ich = new ImageCacheItem(page, width, height, newimg); 0973 d->mImgCache.push_front(ich); 0974 } 0975 0976 return newimg; 0977 } 0978 0979 bool KDjVu::exportAsPostScript(const QString &fileName, const QList<int> &pageList) const 0980 { 0981 if (!d->m_djvu_document || fileName.trimmed().isEmpty() || pageList.isEmpty()) { 0982 return false; 0983 } 0984 0985 QFile f(fileName); 0986 f.open(QIODevice::ReadWrite); 0987 bool ret = exportAsPostScript(&f, pageList); 0988 if (ret) { 0989 f.close(); 0990 } 0991 return ret; 0992 } 0993 0994 bool KDjVu::exportAsPostScript(QFile *file, const QList<int> &pageList) const 0995 { 0996 if (!d->m_djvu_document || !file || pageList.isEmpty()) { 0997 return false; 0998 } 0999 1000 FILE *f = fdopen(file->handle(), "w+"); 1001 if (!f) { 1002 qDebug() << "error while getting the FILE*"; 1003 return false; 1004 } 1005 1006 QString pl; 1007 for (const int p : pageList) { 1008 if (!pl.isEmpty()) { 1009 pl += QLatin1String(","); 1010 } 1011 pl += QString::number(p); 1012 } 1013 pl.prepend(QStringLiteral("-page=")); 1014 1015 // setting the options 1016 static const int optc = 1; 1017 const char **optv = (const char **)malloc(1 * sizeof(char *)); 1018 QByteArray plb = pl.toLatin1(); 1019 optv[0] = plb.constData(); 1020 1021 ddjvu_job_t *printjob = ddjvu_document_print(d->m_djvu_document, f, optc, optv); 1022 while (!ddjvu_job_done(printjob)) { 1023 handle_ddjvu_messages(d->m_djvu_cxt, true); 1024 } 1025 1026 free(optv); 1027 1028 return fclose(f) == 0; 1029 } 1030 1031 QList<KDjVu::TextEntity> KDjVu::textEntities(int page, const QString &granularity) const 1032 { 1033 if ((page < 0) || (page >= d->m_pages.count())) { 1034 return QList<KDjVu::TextEntity>(); 1035 } 1036 1037 miniexp_t r; 1038 while ((r = ddjvu_document_get_pagetext(d->m_djvu_document, page, nullptr)) == miniexp_dummy) { 1039 handle_ddjvu_messages(d->m_djvu_cxt, true); 1040 } 1041 1042 if (r == miniexp_nil) { 1043 return QList<KDjVu::TextEntity>(); 1044 } 1045 1046 QList<KDjVu::TextEntity> ret; 1047 1048 int height = d->m_pages.at(page)->height(); 1049 1050 QQueue<miniexp_t> queue; 1051 queue.enqueue(r); 1052 1053 while (!queue.isEmpty()) { 1054 miniexp_t cur = queue.dequeue(); 1055 1056 if (miniexp_listp(cur) && (miniexp_length(cur) > 0) && miniexp_symbolp(miniexp_nth(0, cur))) { 1057 int size = miniexp_length(cur); 1058 QString sym = QString::fromUtf8(miniexp_to_name(miniexp_nth(0, cur))); 1059 if (sym == granularity) { 1060 if (size >= 6) { 1061 int xmin = miniexp_to_int(miniexp_nth(1, cur)); 1062 int ymin = miniexp_to_int(miniexp_nth(2, cur)); 1063 int xmax = miniexp_to_int(miniexp_nth(3, cur)); 1064 int ymax = miniexp_to_int(miniexp_nth(4, cur)); 1065 QRect rect(xmin, height - ymax, xmax - xmin, ymax - ymin); 1066 KDjVu::TextEntity entity; 1067 entity.m_rect = rect; 1068 entity.m_text = QString::fromUtf8(miniexp_to_str(miniexp_nth(5, cur))); 1069 ret.append(entity); 1070 } 1071 } else { 1072 for (int i = 5; i < size; ++i) { 1073 queue.enqueue(miniexp_nth(i, cur)); 1074 } 1075 } 1076 } 1077 } 1078 1079 return ret; 1080 } 1081 1082 void KDjVu::setCacheEnabled(bool enable) 1083 { 1084 if (enable == d->m_cacheEnabled) { 1085 return; 1086 } 1087 1088 d->m_cacheEnabled = enable; 1089 if (!d->m_cacheEnabled) { 1090 qDeleteAll(d->mImgCache); 1091 d->mImgCache.clear(); 1092 } 1093 } 1094 1095 bool KDjVu::isCacheEnabled() const 1096 { 1097 return d->m_cacheEnabled; 1098 } 1099 1100 int KDjVu::pageNumber(const QString &name) const 1101 { 1102 if (!d->m_djvu_document) { 1103 return -1; 1104 } 1105 1106 return d->pageWithName(name); 1107 }