/* SPDX-FileCopyrightText: 2006 Pino Toscano SPDX-License-Identifier: GPL-2.0-or-later */ #include "kdjvu.h" #include #include #include #include #include #include #include #include #include #include #include #include QDebug &operator<<(QDebug &s, const ddjvu_rect_t r) { s.nospace() << "[" << r.x << "," << r.y << " - " << r.w << "x" << r.h << "]"; return s.space(); } static void which_ddjvu_message(const ddjvu_message_t *msg) { #ifdef KDJVU_DEBUG qDebug() << "which_djvu_message(...):" << msg->m_any.tag; switch (msg->m_any.tag) { case DDJVU_ERROR: qDebug().nospace() << "ERROR: file " << msg->m_error.filename << ", line " << msg->m_error.lineno; qDebug().nospace() << "ERROR: function '" << msg->m_error.function << "'"; qDebug().nospace() << "ERROR: '" << msg->m_error.message << "'"; break; case DDJVU_INFO: qDebug().nospace() << "INFO: '" << msg->m_info.message << "'"; break; case DDJVU_CHUNK: qDebug().nospace() << "CHUNK: '" << QByteArray(msg->m_chunk.chunkid) << "'"; break; case DDJVU_PROGRESS: qDebug().nospace() << "PROGRESS: '" << msg->m_progress.percent << "'"; break; default:; } #else Q_UNUSED(msg); #endif } /** * Explore the message queue until there are message left in it. */ static void handle_ddjvu_messages(ddjvu_context_t *ctx, int wait) { const ddjvu_message_t *msg; if (wait) { ddjvu_message_wait(ctx); } while ((msg = ddjvu_message_peek(ctx))) { which_ddjvu_message(msg); ddjvu_message_pop(ctx); } } /** * Explore the message queue until the message \p mid is found. */ static void wait_for_ddjvu_message(ddjvu_context_t *ctx, ddjvu_message_tag_t mid) { ddjvu_message_wait(ctx); const ddjvu_message_t *msg; while ((msg = ddjvu_message_peek(ctx)) && msg && (msg->m_any.tag != mid)) { which_ddjvu_message(msg); ddjvu_message_pop(ctx); } } /** * Convert a clockwise coefficient \p r for a rotation to a counter-clockwise * and vice versa. */ static int flipRotation(int r) { return (4 - r) % 4; } static miniexp_t find_second_in_pair(miniexp_t theexp, const char *which) { miniexp_t exp = theexp; while (exp) { miniexp_t cur = miniexp_car(exp); if (!miniexp_consp(cur) || !miniexp_symbolp(miniexp_car(cur))) { exp = miniexp_cdr(exp); continue; } const QString id = QString::fromUtf8(miniexp_to_name(miniexp_car(cur))); if (id == QLatin1String(which)) { return miniexp_cadr(cur); } exp = miniexp_cdr(exp); } return miniexp_nil; } static bool find_replace_or_add_second_in_pair(miniexp_t theexp, const char *which, miniexp_t replacement) { miniexp_t exp = miniexp_cdddr(theexp); while (exp) { miniexp_t cur = miniexp_car(exp); if (!miniexp_consp(cur) || !miniexp_symbolp(miniexp_car(cur))) { exp = miniexp_cdr(exp); continue; } const QString id = QString::fromUtf8(miniexp_to_name(miniexp_car(cur))); if (id == QLatin1String(which)) { miniexp_t reversed = miniexp_reverse(cur); miniexp_rplaca(reversed, replacement); cur = miniexp_reverse(reversed); return true; } exp = miniexp_cdr(exp); } // TODO add the new replacement ad the end of the list return false; } // ImageCacheItem class ImageCacheItem { public: ImageCacheItem(int p, int w, int h, const QImage &i) : page(p) , width(w) , height(h) , img(i) { } int page; int width; int height; QImage img; }; // KdjVu::Page KDjVu::Page::Page() { } KDjVu::Page::~Page() { } int KDjVu::Page::width() const { return m_width; } int KDjVu::Page::height() const { return m_height; } int KDjVu::Page::dpi() const { return m_dpi; } int KDjVu::Page::orientation() const { return m_orientation; } // KDjVu::Link KDjVu::Link::~Link() { } KDjVu::Link::LinkArea KDjVu::Link::areaType() const { return m_area; } QPoint KDjVu::Link::point() const { return m_point; } QSize KDjVu::Link::size() const { return m_size; } QPolygon KDjVu::Link::polygon() const { return m_poly; } // KDjVu::PageLink KDjVu::PageLink::PageLink() { } int KDjVu::PageLink::type() const { return KDjVu::Link::PageLink; } QString KDjVu::PageLink::page() const { return m_page; } // KDjVu::UrlLink KDjVu::UrlLink::UrlLink() { } int KDjVu::UrlLink::type() const { return KDjVu::Link::UrlLink; } QString KDjVu::UrlLink::url() const { return m_url; } // KDjVu::Annotation KDjVu::Annotation::Annotation(miniexp_t anno) : m_anno(anno) { } KDjVu::Annotation::~Annotation() { } QPoint KDjVu::Annotation::point() const { miniexp_t area = miniexp_nth(3, m_anno); int a = miniexp_to_int(miniexp_nth(1, area)); int b = miniexp_to_int(miniexp_nth(2, area)); return QPoint(a, b); } QString KDjVu::Annotation::comment() const { return QString::fromUtf8(miniexp_to_str(miniexp_nth(2, m_anno))); } void KDjVu::Annotation::setComment(const QString &comment) { miniexp_t exp = m_anno; exp = miniexp_cdr(exp); exp = miniexp_cdr(exp); miniexp_rplaca(exp, miniexp_string(comment.toUtf8().constData())); } QColor KDjVu::Annotation::color() const { return QColor(); } void KDjVu::Annotation::setColor(const QColor &) { } // KDjVu::TextAnnotation KDjVu::TextAnnotation::TextAnnotation(miniexp_t anno) : Annotation(anno) , m_inlineText(true) { const int num = miniexp_length(m_anno); for (int j = 4; j < num; ++j) { miniexp_t curelem = miniexp_nth(j, m_anno); if (!miniexp_listp(curelem)) { continue; } QString id = QString::fromUtf8(miniexp_to_name(miniexp_nth(0, curelem))); if (id == QLatin1String("pushpin")) { m_inlineText = false; } } } QSize KDjVu::TextAnnotation::size() const { miniexp_t area = miniexp_nth(3, m_anno); int c = miniexp_to_int(miniexp_nth(3, area)); int d = miniexp_to_int(miniexp_nth(4, area)); return QSize(c, d); } int KDjVu::TextAnnotation::type() const { return KDjVu::Annotation::TextAnnotation; } QColor KDjVu::TextAnnotation::color() const { miniexp_t col = find_second_in_pair(m_anno, "backclr"); if (!miniexp_symbolp(col)) { return Qt::transparent; } return QColor(QString::fromUtf8(miniexp_to_name(col))); } void KDjVu::TextAnnotation::setColor(const QColor &color) { const QByteArray col = color.name().toLatin1(); find_replace_or_add_second_in_pair(m_anno, "backclr", miniexp_symbol(col.constData())); } bool KDjVu::TextAnnotation::inlineText() const { return m_inlineText; } // KDjVu::LineAnnotation KDjVu::LineAnnotation::LineAnnotation(miniexp_t anno) : Annotation(anno) , m_isArrow(false) , m_width(miniexp_nil) { const int num = miniexp_length(m_anno); for (int j = 4; j < num; ++j) { miniexp_t curelem = miniexp_nth(j, m_anno); if (!miniexp_listp(curelem)) { continue; } QString id = QString::fromUtf8(miniexp_to_name(miniexp_nth(0, curelem))); if (id == QLatin1String("arrow")) { m_isArrow = true; } else if (id == QLatin1String("width")) { m_width = curelem; } } } int KDjVu::LineAnnotation::type() const { return KDjVu::Annotation::LineAnnotation; } QColor KDjVu::LineAnnotation::color() const { miniexp_t col = find_second_in_pair(m_anno, "lineclr"); if (!miniexp_symbolp(col)) { return Qt::black; } return QColor(QString::fromUtf8(miniexp_to_name(col))); } void KDjVu::LineAnnotation::setColor(const QColor &color) { const QByteArray col = color.name().toLatin1(); find_replace_or_add_second_in_pair(m_anno, "lineclr", miniexp_symbol(col.constData())); } QPoint KDjVu::LineAnnotation::point2() const { miniexp_t area = miniexp_nth(3, m_anno); int c = miniexp_to_int(miniexp_nth(3, area)); int d = miniexp_to_int(miniexp_nth(4, area)); return QPoint(c, d); } bool KDjVu::LineAnnotation::isArrow() const { return m_isArrow; } int KDjVu::LineAnnotation::width() const { if (m_width == miniexp_nil) { return 1; } return miniexp_to_int(miniexp_cadr(m_width)); } void KDjVu::LineAnnotation::setWidth(int width) { find_replace_or_add_second_in_pair(m_anno, "width", miniexp_number(width)); } // KDjVu::TextEntity KDjVu::TextEntity::TextEntity() { } KDjVu::TextEntity::~TextEntity() { } QString KDjVu::TextEntity::text() const { return m_text; } QRect KDjVu::TextEntity::rect() const { return m_rect; } class KDjVu::Private { public: Private() : m_djvu_cxt(nullptr) , m_djvu_document(nullptr) , m_format(nullptr) , m_docBookmarks(nullptr) , m_cacheEnabled(true) { } QImage generateImageTile(ddjvu_page_t *djvupage, int &res, int width, int row, int xdelta, int height, int col, int ydelta); void readBookmarks(); void fillBookmarksRecurse(QDomDocument &maindoc, QDomNode &curnode, miniexp_t exp, int offset = -1); void readMetaData(int page); int pageWithName(const QString &name); ddjvu_context_t *m_djvu_cxt; ddjvu_document_t *m_djvu_document; ddjvu_format_t *m_format; QVector m_pages; QVector m_pages_cache; QList mImgCache; QHash m_metaData; QDomDocument *m_docBookmarks; QHash m_pageNamesCache; bool m_cacheEnabled; static unsigned int s_formatmask[4]; }; unsigned int KDjVu::Private::s_formatmask[4] = {0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000}; QImage KDjVu::Private::generateImageTile(ddjvu_page_t *djvupage, int &res, int width, int row, int xdelta, int height, int col, int ydelta) { ddjvu_rect_t renderrect; renderrect.x = row * xdelta; renderrect.y = col * ydelta; int realwidth = qMin(width - renderrect.x, xdelta); int realheight = qMin(height - renderrect.y, ydelta); renderrect.w = realwidth; renderrect.h = realheight; #ifdef KDJVU_DEBUG qDebug() << "renderrect:" << renderrect; #endif ddjvu_rect_t pagerect; pagerect.x = 0; pagerect.y = 0; pagerect.w = width; pagerect.h = height; #ifdef KDJVU_DEBUG qDebug() << "pagerect:" << pagerect; #endif handle_ddjvu_messages(m_djvu_cxt, false); QImage res_img(realwidth, realheight, QImage::Format_RGB32); // the following line workarounds a rare crash in djvulibre; // it should be fixed with >= 3.5.21 ddjvu_page_get_width(djvupage); res = ddjvu_page_render(djvupage, DDJVU_RENDER_COLOR, &pagerect, &renderrect, m_format, res_img.bytesPerLine(), (char *)res_img.bits()); if (!res) { res_img.fill(Qt::white); } #ifdef KDJVU_DEBUG qDebug() << "rendering result:" << res; #endif handle_ddjvu_messages(m_djvu_cxt, false); return res_img; } void KDjVu::Private::readBookmarks() { if (!m_djvu_document) { return; } miniexp_t outline; while ((outline = ddjvu_document_get_outline(m_djvu_document)) == miniexp_dummy) { handle_ddjvu_messages(m_djvu_cxt, true); } if (miniexp_listp(outline) && (miniexp_length(outline) > 0) && miniexp_symbolp(miniexp_nth(0, outline)) && (QString::fromUtf8(miniexp_to_name(miniexp_nth(0, outline))) == QLatin1String("bookmarks"))) { m_docBookmarks = new QDomDocument(QStringLiteral("KDjVuBookmarks")); fillBookmarksRecurse(*m_docBookmarks, *m_docBookmarks, outline, 1); ddjvu_miniexp_release(m_djvu_document, outline); } } void KDjVu::Private::fillBookmarksRecurse(QDomDocument &maindoc, QDomNode &curnode, miniexp_t exp, int offset) { if (!miniexp_listp(exp)) { return; } int l = miniexp_length(exp); for (int i = qMax(offset, 0); i < l; ++i) { miniexp_t cur = miniexp_nth(i, exp); if (miniexp_consp(cur) && (miniexp_length(cur) > 0) && miniexp_stringp(miniexp_nth(0, cur)) && miniexp_stringp(miniexp_nth(1, cur))) { QString title = QString::fromUtf8(miniexp_to_str(miniexp_nth(0, cur))); QString dest = QString::fromUtf8(miniexp_to_str(miniexp_nth(1, cur))); QDomElement el = maindoc.createElement(QStringLiteral("item")); el.setAttribute(QStringLiteral("title"), title); if (!dest.isEmpty()) { if (dest.at(0) == QLatin1Char('#')) { dest.remove(0, 1); bool isNumber = false; dest.toInt(&isNumber); if (isNumber) { // it might be an actual page number, but could also be a page label // so resolve the number, and get the real page number int pageNo = pageWithName(dest); if (pageNo != -1) { el.setAttribute(QStringLiteral("PageNumber"), QString::number(pageNo + 1)); } else { el.setAttribute(QStringLiteral("PageNumber"), dest); } } else { el.setAttribute(QStringLiteral("PageName"), dest); } } else { el.setAttribute(QStringLiteral("URL"), dest); } } curnode.appendChild(el); if (!el.isNull() && (miniexp_length(cur) > 2)) { fillBookmarksRecurse(maindoc, el, cur, 2); } } } } void KDjVu::Private::readMetaData(int page) { if (!m_djvu_document) { return; } miniexp_t annots; while ((annots = ddjvu_document_get_pageanno(m_djvu_document, page)) == miniexp_dummy) { handle_ddjvu_messages(m_djvu_cxt, true); } if (!miniexp_listp(annots) || miniexp_length(annots) == 0) { return; } miniexp_t exp = miniexp_nth(0, annots); int size = miniexp_length(exp); if (size <= 1 || qstrncmp(miniexp_to_name(miniexp_nth(0, exp)), "metadata", 8)) { return; } for (int i = 1; i < size; ++i) { miniexp_t cur = miniexp_nth(i, exp); if (miniexp_length(cur) != 2) { continue; } QString id = QString::fromUtf8(miniexp_to_name(miniexp_nth(0, cur))); QString value = QString::fromUtf8(miniexp_to_str(miniexp_nth(1, cur))); m_metaData[id.toLower()] = value; } } int KDjVu::Private::pageWithName(const QString &name) { const int pageNo = m_pageNamesCache.value(name, -1); if (pageNo != -1) { return pageNo; } const QByteArray utfName = name.toUtf8(); const int fileNum = ddjvu_document_get_filenum(m_djvu_document); ddjvu_fileinfo_t info; for (int i = 0; i < fileNum; ++i) { if (DDJVU_JOB_OK != ddjvu_document_get_fileinfo(m_djvu_document, i, &info)) { continue; } if (info.type != 'P') { continue; } if ((utfName == info.id) || (utfName == info.name) || (utfName == info.title)) { m_pageNamesCache.insert(name, info.pageno); return info.pageno; } } return -1; } KDjVu::KDjVu() : d(new Private) { // creating the djvu context d->m_djvu_cxt = ddjvu_context_create("KDjVu"); // creating the rendering format #if DDJVUAPI_VERSION >= 18 d->m_format = ddjvu_format_create(DDJVU_FORMAT_RGBMASK32, 4, Private::s_formatmask); #else d->m_format = ddjvu_format_create(DDJVU_FORMAT_RGBMASK32, 3, Private::s_formatmask); #endif ddjvu_format_set_row_order(d->m_format, 1); ddjvu_format_set_y_direction(d->m_format, 1); } KDjVu::~KDjVu() { closeFile(); ddjvu_format_release(d->m_format); ddjvu_context_release(d->m_djvu_cxt); delete d; } bool KDjVu::openFile(const QString &fileName) { // first, close the old file if (d->m_djvu_document) { closeFile(); } // load the document..., use UTF-8 variant to work on Windows, too, see bug 422500 d->m_djvu_document = ddjvu_document_create_by_filename_utf8(d->m_djvu_cxt, fileName.toUtf8().constData(), true); if (!d->m_djvu_document) { return false; } // ...and wait for its loading wait_for_ddjvu_message(d->m_djvu_cxt, DDJVU_DOCINFO); if (ddjvu_document_decoding_error(d->m_djvu_document)) { ddjvu_document_release(d->m_djvu_document); d->m_djvu_document = nullptr; return false; } qDebug() << "# of pages:" << ddjvu_document_get_pagenum(d->m_djvu_document); int numofpages = ddjvu_document_get_pagenum(d->m_djvu_document); d->m_pages.clear(); d->m_pages.resize(numofpages); d->m_pages_cache.clear(); d->m_pages_cache.resize(numofpages); // get the document type QString doctype; switch (ddjvu_document_get_type(d->m_djvu_document)) { case DDJVU_DOCTYPE_UNKNOWN: doctype = i18nc("Type of DjVu document", "Unknown"); break; case DDJVU_DOCTYPE_SINGLEPAGE: doctype = i18nc("Type of DjVu document", "Single Page"); break; case DDJVU_DOCTYPE_BUNDLED: doctype = i18nc("Type of DjVu document", "Bundled"); break; case DDJVU_DOCTYPE_INDIRECT: doctype = i18nc("Type of DjVu document", "Indirect"); break; case DDJVU_DOCTYPE_OLD_BUNDLED: doctype = i18nc("Type of DjVu document", "Bundled (old)"); break; case DDJVU_DOCTYPE_OLD_INDEXED: doctype = i18nc("Type of DjVu document", "Indexed (old)"); break; } if (!doctype.isEmpty()) { d->m_metaData[QStringLiteral("documentType")] = doctype; } // get the number of components d->m_metaData[QStringLiteral("componentFile")] = ddjvu_document_get_filenum(d->m_djvu_document); // read the pages for (int i = 0; i < numofpages; ++i) { ddjvu_status_t sts; ddjvu_pageinfo_t info; while ((sts = ddjvu_document_get_pageinfo(d->m_djvu_document, i, &info)) < DDJVU_JOB_OK) { handle_ddjvu_messages(d->m_djvu_cxt, true); } if (sts >= DDJVU_JOB_FAILED) { qDebug().nospace() << "\t>>> page " << i << " failed: " << sts; return false; } KDjVu::Page *p = new KDjVu::Page(); p->m_width = info.width; p->m_height = info.height; p->m_dpi = info.dpi; #if DDJVUAPI_VERSION >= 18 p->m_orientation = flipRotation(info.rotation); #else p->m_orientation = 0; #endif d->m_pages[i] = p; } // reading the metadata from the first page only should be enough if (numofpages > 0) { d->readMetaData(0); } return true; } void KDjVu::closeFile() { // deleting the old TOC delete d->m_docBookmarks; d->m_docBookmarks = nullptr; // deleting the pages qDeleteAll(d->m_pages); d->m_pages.clear(); // releasing the djvu pages QVector::Iterator it = d->m_pages_cache.begin(), itEnd = d->m_pages_cache.end(); for (; it != itEnd; ++it) { ddjvu_page_release(*it); } d->m_pages_cache.clear(); // clearing the image cache qDeleteAll(d->mImgCache); d->mImgCache.clear(); // clearing the old metadata d->m_metaData.clear(); // cleaning the page names mapping d->m_pageNamesCache.clear(); // releasing the old document if (d->m_djvu_document) { ddjvu_document_release(d->m_djvu_document); } d->m_djvu_document = nullptr; } QVariant KDjVu::metaData(const QString &key) const { QHash::ConstIterator it = d->m_metaData.constFind(key); return it != d->m_metaData.constEnd() ? it.value() : QVariant(); } const QDomDocument *KDjVu::documentBookmarks() const { if (!d->m_docBookmarks) { d->readBookmarks(); } return d->m_docBookmarks; } void KDjVu::linksAndAnnotationsForPage(int pageNum, QList *links, QList *annotations) const { if ((pageNum < 0) || (pageNum >= d->m_pages.count()) || (!links && !annotations)) { return; } miniexp_t annots; while ((annots = ddjvu_document_get_pageanno(d->m_djvu_document, pageNum)) == miniexp_dummy) { handle_ddjvu_messages(d->m_djvu_cxt, true); } if (!miniexp_listp(annots)) { return; } if (links) { links->clear(); } if (annotations) { annotations->clear(); } int l = miniexp_length(annots); for (int i = 0; i < l; ++i) { miniexp_t cur = miniexp_nth(i, annots); int num = miniexp_length(cur); if ((num < 4) || !miniexp_symbolp(miniexp_nth(0, cur)) || (qstrncmp(miniexp_to_name(miniexp_nth(0, cur)), "maparea", 7) != 0)) { continue; } QString target; QString type; if (miniexp_symbolp(miniexp_nth(0, miniexp_nth(3, cur)))) { type = QString::fromUtf8(miniexp_to_name(miniexp_nth(0, miniexp_nth(3, cur)))); } KDjVu::Link *link = nullptr; KDjVu::Annotation *ann = nullptr; miniexp_t urlexp = miniexp_nth(1, cur); if (links && (type == QLatin1String("rect") || type == QLatin1String("oval") || type == QLatin1String("poly"))) { if (miniexp_stringp(urlexp)) { target = QString::fromUtf8(miniexp_to_str(miniexp_nth(1, cur))); } else if (miniexp_listp(urlexp) && (miniexp_length(urlexp) == 3) && miniexp_symbolp(miniexp_nth(0, urlexp)) && (qstrncmp(miniexp_to_name(miniexp_nth(0, urlexp)), "url", 3) == 0)) { target = QString::fromUtf8(miniexp_to_str(miniexp_nth(1, urlexp))); } if (target.isEmpty() || ((target.length() > 0) && target.at(0) == QLatin1Char('#'))) { KDjVu::PageLink *plink = new KDjVu::PageLink(); plink->m_page = target; link = plink; } else { KDjVu::UrlLink *ulink = new KDjVu::UrlLink(); ulink->m_url = target; link = ulink; } } else if (annotations && (type == QLatin1String("text") || type == QLatin1String("line"))) { if (type == QLatin1String("text")) { KDjVu::TextAnnotation *textann = new KDjVu::TextAnnotation(cur); ann = textann; } else if (type == QLatin1String("line")) { KDjVu::LineAnnotation *lineann = new KDjVu::LineAnnotation(cur); ann = lineann; } } if (link /* safety check */ && links) { link->m_area = KDjVu::Link::UnknownArea; miniexp_t area = miniexp_nth(3, cur); int arealength = miniexp_length(area); if ((arealength == 5) && (type == QLatin1String("rect") || type == QLatin1String("oval"))) { link->m_point = QPoint(miniexp_to_int(miniexp_nth(1, area)), miniexp_to_int(miniexp_nth(2, area))); link->m_size = QSize(miniexp_to_int(miniexp_nth(3, area)), miniexp_to_int(miniexp_nth(4, area))); if (type == QLatin1String("rect")) { link->m_area = KDjVu::Link::RectArea; } else { link->m_area = KDjVu::Link::EllipseArea; } } else if ((arealength > 0) && (arealength % 2 == 1) && type == QLatin1String("poly")) { link->m_area = KDjVu::Link::PolygonArea; QPolygon poly; for (int j = 1; j < arealength; j += 2) { poly << QPoint(miniexp_to_int(miniexp_nth(j, area)), miniexp_to_int(miniexp_nth(j + 1, area))); } link->m_poly = poly; } if (link->m_area != KDjVu::Link::UnknownArea) { links->append(link); } } else if (ann /* safety check */ && annotations) { annotations->append(ann); } } } const QVector &KDjVu::pages() const { return d->m_pages; } QImage KDjVu::image(int page, int width, int height, int rotation) { if (d->m_cacheEnabled) { bool found = false; QList::Iterator it = d->mImgCache.begin(), itEnd = d->mImgCache.end(); for (; (it != itEnd) && !found; ++it) { ImageCacheItem *cur = *it; if ((cur->page == page) && (rotation % 2 == 0 ? cur->width == width && cur->height == height : cur->width == height && cur->height == width)) { found = true; } } if (found) { // taking the element and pushing to the top of the list --it; ImageCacheItem *cur2 = *it; d->mImgCache.erase(it); d->mImgCache.push_front(cur2); return cur2->img; } } if (!d->m_pages_cache.at(page)) { ddjvu_page_t *newpage = ddjvu_page_create_by_pageno(d->m_djvu_document, page); // wait for the new page to be loaded ddjvu_status_t sts; while ((sts = ddjvu_page_decoding_status(newpage)) < DDJVU_JOB_OK) { handle_ddjvu_messages(d->m_djvu_cxt, true); } d->m_pages_cache[page] = newpage; } ddjvu_page_t *djvupage = d->m_pages_cache[page]; /* if ( ddjvu_page_get_rotation( djvupage ) != flipRotation( rotation ) ) { // TODO: test documents with initial rotation != 0 // ddjvu_page_set_rotation( djvupage, m_pages.at( page )->orientation() ); ddjvu_page_set_rotation( djvupage, (ddjvu_page_rotation_t)flipRotation( rotation ) ); } */ static const int xdelta = 1500; static const int ydelta = 1500; int xparts = width / xdelta + 1; int yparts = height / ydelta + 1; QImage newimg; int res = 10000; if ((xparts == 1) && (yparts == 1)) { // only one part -- render at once with no need to auxiliary image newimg = d->generateImageTile(djvupage, res, width, 0, xdelta, height, 0, ydelta); } else { // more than one part -- need to render piece-by-piece and to compose // the results newimg = QImage(width, height, QImage::Format_RGB32); QPainter p; p.begin(&newimg); int parts = xparts * yparts; for (int i = 0; i < parts; ++i) { const int row = i % xparts; const int col = i / xparts; int tmpres = 0; const QImage tempp = d->generateImageTile(djvupage, tmpres, width, row, xdelta, height, col, ydelta); p.drawImage(row * xdelta, col * ydelta, tempp); res = qMin(tmpres, res); } p.end(); } if (res && d->m_cacheEnabled) { // delete all the cached pixmaps for the current page with a size that // differs no more than 35% of the new pixmap size int imgsize = newimg.width() * newimg.height(); if (imgsize > 0) { for (int i = 0; i < d->mImgCache.count();) { ImageCacheItem *cur = d->mImgCache.at(i); if ((cur->page == page) && (abs(cur->img.width() * cur->img.height() - imgsize) < imgsize * 0.35)) { d->mImgCache.removeAt(i); delete cur; } else { ++i; } } } // the image cache has too many elements, remove the last if (d->mImgCache.size() >= 10) { delete d->mImgCache.last(); d->mImgCache.removeLast(); } ImageCacheItem *ich = new ImageCacheItem(page, width, height, newimg); d->mImgCache.push_front(ich); } return newimg; } bool KDjVu::exportAsPostScript(const QString &fileName, const QList &pageList) const { if (!d->m_djvu_document || fileName.trimmed().isEmpty() || pageList.isEmpty()) { return false; } QFile f(fileName); f.open(QIODevice::ReadWrite); bool ret = exportAsPostScript(&f, pageList); if (ret) { f.close(); } return ret; } bool KDjVu::exportAsPostScript(QFile *file, const QList &pageList) const { if (!d->m_djvu_document || !file || pageList.isEmpty()) { return false; } FILE *f = fdopen(file->handle(), "w+"); if (!f) { qDebug() << "error while getting the FILE*"; return false; } QString pl; for (const int p : pageList) { if (!pl.isEmpty()) { pl += QLatin1String(","); } pl += QString::number(p); } pl.prepend(QStringLiteral("-page=")); // setting the options static const int optc = 1; const char **optv = (const char **)malloc(1 * sizeof(char *)); QByteArray plb = pl.toLatin1(); optv[0] = plb.constData(); ddjvu_job_t *printjob = ddjvu_document_print(d->m_djvu_document, f, optc, optv); while (!ddjvu_job_done(printjob)) { handle_ddjvu_messages(d->m_djvu_cxt, true); } free(optv); return fclose(f) == 0; } QList KDjVu::textEntities(int page, const QString &granularity) const { if ((page < 0) || (page >= d->m_pages.count())) { return QList(); } miniexp_t r; while ((r = ddjvu_document_get_pagetext(d->m_djvu_document, page, nullptr)) == miniexp_dummy) { handle_ddjvu_messages(d->m_djvu_cxt, true); } if (r == miniexp_nil) { return QList(); } QList ret; int height = d->m_pages.at(page)->height(); QQueue queue; queue.enqueue(r); while (!queue.isEmpty()) { miniexp_t cur = queue.dequeue(); if (miniexp_listp(cur) && (miniexp_length(cur) > 0) && miniexp_symbolp(miniexp_nth(0, cur))) { int size = miniexp_length(cur); QString sym = QString::fromUtf8(miniexp_to_name(miniexp_nth(0, cur))); if (sym == granularity) { if (size >= 6) { int xmin = miniexp_to_int(miniexp_nth(1, cur)); int ymin = miniexp_to_int(miniexp_nth(2, cur)); int xmax = miniexp_to_int(miniexp_nth(3, cur)); int ymax = miniexp_to_int(miniexp_nth(4, cur)); QRect rect(xmin, height - ymax, xmax - xmin, ymax - ymin); KDjVu::TextEntity entity; entity.m_rect = rect; entity.m_text = QString::fromUtf8(miniexp_to_str(miniexp_nth(5, cur))); ret.append(entity); } } else { for (int i = 5; i < size; ++i) { queue.enqueue(miniexp_nth(i, cur)); } } } } return ret; } void KDjVu::setCacheEnabled(bool enable) { if (enable == d->m_cacheEnabled) { return; } d->m_cacheEnabled = enable; if (!d->m_cacheEnabled) { qDeleteAll(d->mImgCache); d->mImgCache.clear(); } } bool KDjVu::isCacheEnabled() const { return d->m_cacheEnabled; } int KDjVu::pageNumber(const QString &name) const { if (!d->m_djvu_document) { return -1; } return d->pageWithName(name); }