1004 lines
23 KiB
C++
1004 lines
23 KiB
C++
/*
|
|
|
|
Copyright 2014 S. Razi Alavizadeh
|
|
Copyright 2018 Marshall Banana
|
|
Copyright 2013-2014, 2018 Adam Reichold
|
|
Copyright 2013 Alexander Volkov
|
|
|
|
This file is part of qpdfview.
|
|
|
|
The implementation is based on KDjVu by Pino Toscano.
|
|
|
|
qpdfview 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.
|
|
|
|
qpdfview is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with qpdfview. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
#include "djvumodel.h"
|
|
|
|
#include <cstdio>
|
|
|
|
#include <QFile>
|
|
#include <qmath.h>
|
|
|
|
#if defined(Q_OS_WIN) && defined(DJVU_STATIC)
|
|
|
|
#define DDJVUAPI /**/
|
|
#define MINILISPAPI /**/
|
|
|
|
#endif // Q_OS_WIN DJVU_STATIC
|
|
|
|
#include <libdjvu/ddjvuapi.h>
|
|
#include <libdjvu/miniexp.h>
|
|
|
|
#define LOCK_PAGE QMutexLocker mutexLocker(&m_parent->m_mutex);
|
|
#define LOCK_DOCUMENT QMutexLocker mutexLocker(&m_mutex);
|
|
|
|
#if DDJVUAPI_VERSION < 23
|
|
|
|
#define LOCK_PAGE_GLOBAL QMutexLocker globalMutexLocker(m_parent->m_globalMutex);
|
|
#define LOCK_DOCUMENT_GLOBAL QMutexLocker globalMutexLocker(m_globalMutex);
|
|
|
|
#else
|
|
|
|
#define LOCK_PAGE_GLOBAL
|
|
#define LOCK_DOCUMENT_GLOBAL
|
|
|
|
#endif // DDJVUAPI_VERSION
|
|
|
|
namespace
|
|
{
|
|
|
|
using namespace qpdfview;
|
|
using namespace qpdfview::Model;
|
|
|
|
inline miniexp_t miniexp_cadddr(miniexp_t exp)
|
|
{
|
|
return miniexp_cadr(miniexp_cddr(exp));
|
|
}
|
|
|
|
inline miniexp_t miniexp_caddddr(miniexp_t exp)
|
|
{
|
|
return miniexp_caddr(miniexp_cddr(exp));
|
|
}
|
|
|
|
inline miniexp_t skip(miniexp_t exp, int offset)
|
|
{
|
|
while(offset-- > 0)
|
|
{
|
|
exp = miniexp_cdr(exp);
|
|
}
|
|
|
|
return exp;
|
|
}
|
|
|
|
void clearMessageQueue(ddjvu_context_t* context, bool wait)
|
|
{
|
|
if(wait)
|
|
{
|
|
ddjvu_message_wait(context);
|
|
}
|
|
|
|
while(true)
|
|
{
|
|
if(ddjvu_message_peek(context) != 0)
|
|
{
|
|
ddjvu_message_pop(context);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void waitForMessageTag(ddjvu_context_t* context, ddjvu_message_tag_t tag)
|
|
{
|
|
ddjvu_message_wait(context);
|
|
|
|
while(true)
|
|
{
|
|
ddjvu_message_t* message = ddjvu_message_peek(context);
|
|
|
|
if(message != 0)
|
|
{
|
|
if(message->m_any.tag == tag)
|
|
{
|
|
break;
|
|
}
|
|
|
|
ddjvu_message_pop(context);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
QPainterPath loadLinkBoundary(const QString& type, miniexp_t boundaryExp, QSizeF size)
|
|
{
|
|
QPainterPath boundary;
|
|
|
|
const int count = miniexp_length(boundaryExp);
|
|
|
|
if(count == 4 && (type == QLatin1String("rect") || type == QLatin1String("oval")))
|
|
{
|
|
QPoint p(miniexp_to_int(miniexp_car(boundaryExp)), miniexp_to_int(miniexp_cadr(boundaryExp)));
|
|
QSize s(miniexp_to_int(miniexp_caddr(boundaryExp)), miniexp_to_int(miniexp_cadddr(boundaryExp)));
|
|
|
|
p.setY(size.height() - s.height() - p.y());
|
|
|
|
const QRectF r(p, s);
|
|
|
|
if(type == QLatin1String("rect"))
|
|
{
|
|
boundary.addRect(r);
|
|
}
|
|
else
|
|
{
|
|
boundary.addEllipse(r);
|
|
}
|
|
}
|
|
else if(count > 0 && count % 2 == 0 && type == QLatin1String("poly"))
|
|
{
|
|
QPolygon polygon;
|
|
|
|
for(int index = 0; index < count; index += 2)
|
|
{
|
|
QPoint p(miniexp_to_int(miniexp_nth(index, boundaryExp)), miniexp_to_int(miniexp_nth(index + 1, boundaryExp)));
|
|
|
|
p.setY(size.height() - p.y());
|
|
|
|
polygon << p;
|
|
}
|
|
|
|
boundary.addPolygon(polygon);
|
|
}
|
|
|
|
return QTransform::fromScale(1.0 / size.width(), 1.0 / size.height()).map(boundary);
|
|
}
|
|
|
|
Link* loadLinkTarget(const QPainterPath& boundary, miniexp_t targetExp, int index, const QHash< QString, int >& pageByName)
|
|
{
|
|
QString target;
|
|
|
|
if(miniexp_stringp(targetExp))
|
|
{
|
|
target = QString::fromUtf8(miniexp_to_str(targetExp));
|
|
}
|
|
else if(miniexp_length(targetExp) == 3 && qstrcmp(miniexp_to_name(miniexp_car(targetExp)), "url") == 0)
|
|
{
|
|
target = QString::fromUtf8(miniexp_to_str(miniexp_cadr(targetExp)));
|
|
}
|
|
|
|
if(target.isEmpty())
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if(target.at(0) == QLatin1Char('#'))
|
|
{
|
|
target.remove(0, 1);
|
|
|
|
bool ok = false;
|
|
int targetPage = target.toInt(&ok);
|
|
|
|
if(!ok)
|
|
{
|
|
const int page = pageByName.value(target);
|
|
|
|
if(page != 0)
|
|
{
|
|
targetPage = page;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(target.at(0) == QLatin1Char('+') || target.at(0) == QLatin1Char('-'))
|
|
{
|
|
targetPage += index + 1;
|
|
}
|
|
}
|
|
|
|
return new Link(boundary, targetPage);
|
|
}
|
|
else
|
|
{
|
|
return new Link(boundary, target);
|
|
}
|
|
}
|
|
|
|
QList< Link* > loadLinks(miniexp_t linkExp, QSizeF size, int index, const QHash< QString, int >& pageByName)
|
|
{
|
|
QList< Link* > links;
|
|
|
|
for(miniexp_t linkItem = miniexp_nil; miniexp_consp(linkExp); linkExp = miniexp_cdr(linkExp))
|
|
{
|
|
linkItem = miniexp_car(linkExp);
|
|
|
|
if(miniexp_length(linkItem) < 4 || qstrcmp(miniexp_to_name(miniexp_car(linkItem)), "maparea") != 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
miniexp_t targetExp = miniexp_cadr(linkItem);
|
|
miniexp_t boundaryExp = miniexp_cadddr(linkItem);
|
|
|
|
if(!miniexp_symbolp(miniexp_car(boundaryExp)))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const QString type = QString::fromUtf8(miniexp_to_name(miniexp_car(boundaryExp)));
|
|
|
|
if(type == QLatin1String("rect") || type == QLatin1String("oval") || type == QLatin1String("poly"))
|
|
{
|
|
QPainterPath boundary = loadLinkBoundary(type, miniexp_cdr(boundaryExp), size);
|
|
|
|
if(!boundary.isEmpty())
|
|
{
|
|
Link* link = loadLinkTarget(boundary, targetExp, index, pageByName);
|
|
|
|
if(link != 0)
|
|
{
|
|
links.append(link);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return links;
|
|
}
|
|
|
|
QString loadText(miniexp_t textExp, QSizeF size, const QRectF& rect)
|
|
{
|
|
if(miniexp_length(textExp) < 6 && !miniexp_symbolp(miniexp_car(textExp)))
|
|
{
|
|
return QString();
|
|
}
|
|
|
|
const int xmin = miniexp_to_int(miniexp_cadr(textExp));
|
|
const int ymin = miniexp_to_int(miniexp_caddr(textExp));
|
|
const int xmax = miniexp_to_int(miniexp_cadddr(textExp));
|
|
const int ymax = miniexp_to_int(miniexp_caddddr(textExp));
|
|
|
|
if(rect.intersects(QRect(xmin, size.height() - ymax, xmax - xmin, ymax - ymin)))
|
|
{
|
|
const QString type = QString::fromUtf8(miniexp_to_name(miniexp_car(textExp)));
|
|
|
|
if(type == QLatin1String("word"))
|
|
{
|
|
return QString::fromUtf8(miniexp_to_str(miniexp_nth(5, textExp)));
|
|
}
|
|
else
|
|
{
|
|
QStringList text;
|
|
|
|
textExp = skip(textExp, 5);
|
|
|
|
for(miniexp_t textItem = miniexp_nil; miniexp_consp(textExp); textExp = miniexp_cdr(textExp))
|
|
{
|
|
textItem = miniexp_car(textExp);
|
|
|
|
text.append(loadText(textItem, size, rect));
|
|
}
|
|
|
|
return type == QLatin1String("line") ? text.join(" ") : text.join("\n");
|
|
}
|
|
}
|
|
|
|
return QString();
|
|
}
|
|
|
|
QList< QRectF > findText(miniexp_t pageTextExp, QSizeF size, const QTransform& transform, const QStringList& words, bool matchCase, bool wholeWords)
|
|
{
|
|
if(words.isEmpty())
|
|
{
|
|
return QList< QRectF >();
|
|
}
|
|
|
|
const Qt::CaseSensitivity caseSensitivity = matchCase ? Qt::CaseSensitive : Qt::CaseInsensitive;
|
|
|
|
QRectF result;
|
|
int wordIndex = 0;
|
|
|
|
QList< miniexp_t > texts;
|
|
QList< QRectF > results;
|
|
|
|
texts.append(pageTextExp);
|
|
|
|
while(!texts.isEmpty())
|
|
{
|
|
miniexp_t textExp = texts.takeFirst();
|
|
|
|
if(miniexp_length(textExp) < 6 || !miniexp_symbolp(miniexp_car(textExp)))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const QString type = QString::fromUtf8(miniexp_to_name(miniexp_car(textExp)));
|
|
|
|
if(type == QLatin1String("word"))
|
|
{
|
|
const QString text = QString::fromUtf8(miniexp_to_str(miniexp_nth(5, textExp)));
|
|
|
|
int index = 0;
|
|
|
|
while((index = text.indexOf(words.at(wordIndex), index, caseSensitivity)) != -1)
|
|
{
|
|
const int nextIndex = index + words.at(wordIndex).length();
|
|
|
|
const bool wordBegins = index == 0 || !text.at(index - 1).isLetterOrNumber();
|
|
const bool wordEnds = nextIndex == text.length() || !text.at(nextIndex).isLetterOrNumber();
|
|
|
|
if(!wholeWords || (wordBegins && wordEnds))
|
|
{
|
|
const int xmin = miniexp_to_int(miniexp_cadr(textExp));
|
|
const int ymin = miniexp_to_int(miniexp_caddr(textExp));
|
|
const int xmax = miniexp_to_int(miniexp_cadddr(textExp));
|
|
const int ymax = miniexp_to_int(miniexp_caddddr(textExp));
|
|
|
|
result = result.united(QRectF(xmin, size.height() - ymax, xmax - xmin, ymax - ymin));
|
|
|
|
// Advance after partial match
|
|
if(++wordIndex == words.size())
|
|
{
|
|
results.append(transform.mapRect(result));
|
|
|
|
// Reset after full match
|
|
result = QRectF();
|
|
wordIndex = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Reset after malformed match
|
|
result = QRectF();
|
|
wordIndex = 0;
|
|
}
|
|
|
|
if((index = nextIndex) >= text.length())
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(index < 0)
|
|
{
|
|
// Reset after empty match
|
|
result = QRectF();
|
|
wordIndex = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
textExp = skip(textExp, 5);
|
|
|
|
for(miniexp_t textItem = miniexp_nil; miniexp_consp(textExp); textExp = miniexp_cdr(textExp))
|
|
{
|
|
textItem = miniexp_car(textExp);
|
|
|
|
texts.append(textItem);
|
|
}
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
Outline loadOutline(miniexp_t outlineExp, const QHash< QString, int >& pageByName)
|
|
{
|
|
Outline outline;
|
|
|
|
for(miniexp_t outlineItem = miniexp_nil; miniexp_consp(outlineExp); outlineExp = miniexp_cdr(outlineExp))
|
|
{
|
|
outlineItem = miniexp_car(outlineExp);
|
|
|
|
if(miniexp_length(outlineItem) < 2 || !miniexp_stringp(miniexp_car(outlineItem)) || !miniexp_stringp(miniexp_cadr(outlineItem)))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const QString title = QString::fromUtf8(miniexp_to_str(miniexp_car(outlineItem)));
|
|
|
|
if(title.isEmpty())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
outline.push_back(Section());
|
|
Section& section = outline.back();
|
|
section.title = title;
|
|
|
|
QString destination = QString::fromUtf8(miniexp_to_str(miniexp_cadr(outlineItem)));
|
|
|
|
if(!destination.isEmpty() && destination.at(0) == QLatin1Char('#'))
|
|
{
|
|
destination.remove(0, 1);
|
|
|
|
bool ok = false;
|
|
int page = destination.toInt(&ok);
|
|
|
|
if(!ok)
|
|
{
|
|
const int destinationPage = pageByName.value(destination);
|
|
|
|
if(destinationPage != 0)
|
|
{
|
|
ok = true;
|
|
page = destinationPage;
|
|
}
|
|
}
|
|
|
|
if(ok)
|
|
{
|
|
section.link.page = page;
|
|
}
|
|
}
|
|
|
|
if(miniexp_length(outlineItem) > 2)
|
|
{
|
|
section.children = loadOutline(skip(outlineItem, 2), pageByName);
|
|
}
|
|
}
|
|
|
|
return outline;
|
|
}
|
|
|
|
Properties loadProperties(miniexp_t annoExp)
|
|
{
|
|
Properties properties;
|
|
|
|
for(miniexp_t annoItem = miniexp_nil; miniexp_consp(annoExp); annoExp = miniexp_cdr(annoExp))
|
|
{
|
|
annoItem = miniexp_car(annoExp);
|
|
|
|
if(miniexp_length(annoItem) < 2 || qstrcmp(miniexp_to_name(miniexp_car(annoItem)), "metadata") != 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
annoItem = miniexp_cdr(annoItem);
|
|
|
|
for(miniexp_t keyValueItem = miniexp_nil; miniexp_consp(annoItem); annoItem = miniexp_cdr(annoItem))
|
|
{
|
|
keyValueItem = miniexp_car(annoItem);
|
|
|
|
if(miniexp_length(keyValueItem) != 2)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const QString key = QString::fromUtf8(miniexp_to_name(miniexp_car(keyValueItem)));
|
|
const QString value = QString::fromUtf8(miniexp_to_str(miniexp_cadr(keyValueItem)));
|
|
|
|
if(!key.isEmpty() && !value.isEmpty())
|
|
{
|
|
properties.push_back(qMakePair(key, value));
|
|
}
|
|
}
|
|
}
|
|
|
|
return properties;
|
|
}
|
|
|
|
} // anonymous
|
|
|
|
namespace qpdfview
|
|
{
|
|
|
|
namespace Model
|
|
{
|
|
|
|
DjVuPage::DjVuPage(const DjVuDocument* parent, int index, const ddjvu_pageinfo_t& pageinfo) :
|
|
m_parent(parent),
|
|
m_index(index),
|
|
m_size(pageinfo.width, pageinfo.height),
|
|
m_resolution(pageinfo.dpi)
|
|
{
|
|
}
|
|
|
|
DjVuPage::~DjVuPage()
|
|
{
|
|
}
|
|
|
|
QSizeF DjVuPage::size() const
|
|
{
|
|
return 72.0 / m_resolution * m_size;
|
|
}
|
|
|
|
QImage DjVuPage::render(qreal horizontalResolution, qreal verticalResolution, Rotation rotation, QRect boundingRect) const
|
|
{
|
|
LOCK_PAGE
|
|
|
|
ddjvu_page_t* page = ddjvu_page_create_by_pageno(m_parent->m_document, m_index);
|
|
|
|
if(page == 0)
|
|
{
|
|
return QImage();
|
|
}
|
|
|
|
ddjvu_status_t status;
|
|
|
|
while(true)
|
|
{
|
|
status = ddjvu_page_decoding_status(page);
|
|
|
|
if(status < DDJVU_JOB_OK)
|
|
{
|
|
clearMessageQueue(m_parent->m_context, true);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(status >= DDJVU_JOB_FAILED)
|
|
{
|
|
ddjvu_page_release(page);
|
|
|
|
return QImage();
|
|
}
|
|
|
|
switch(rotation)
|
|
{
|
|
default:
|
|
case RotateBy0:
|
|
ddjvu_page_set_rotation(page, DDJVU_ROTATE_0);
|
|
break;
|
|
case RotateBy90:
|
|
ddjvu_page_set_rotation(page, DDJVU_ROTATE_270);
|
|
break;
|
|
case RotateBy180:
|
|
ddjvu_page_set_rotation(page, DDJVU_ROTATE_180);
|
|
break;
|
|
case RotateBy270:
|
|
ddjvu_page_set_rotation(page, DDJVU_ROTATE_90);
|
|
break;
|
|
}
|
|
|
|
ddjvu_rect_t pagerect;
|
|
|
|
pagerect.x = 0;
|
|
pagerect.y = 0;
|
|
|
|
switch(rotation)
|
|
{
|
|
default:
|
|
case RotateBy0:
|
|
case RotateBy180:
|
|
pagerect.w = qRound(horizontalResolution / m_resolution * m_size.width());
|
|
pagerect.h = qRound(verticalResolution / m_resolution * m_size.height());
|
|
break;
|
|
case RotateBy90:
|
|
case RotateBy270:
|
|
pagerect.w = qRound(horizontalResolution / m_resolution * m_size.height());
|
|
pagerect.h = qRound(verticalResolution / m_resolution * m_size.width());
|
|
break;
|
|
}
|
|
|
|
ddjvu_rect_t renderrect;
|
|
|
|
if(boundingRect.isNull())
|
|
{
|
|
renderrect.x = pagerect.x;
|
|
renderrect.y = pagerect.y;
|
|
renderrect.w = pagerect.w;
|
|
renderrect.h = pagerect.h;
|
|
}
|
|
else
|
|
{
|
|
renderrect.x = boundingRect.x();
|
|
renderrect.y = boundingRect.y();
|
|
renderrect.w = boundingRect.width();
|
|
renderrect.h = boundingRect.height();
|
|
}
|
|
|
|
QImage image(renderrect.w, renderrect.h, QImage::Format_RGB32);
|
|
|
|
if(!ddjvu_page_render(page, DDJVU_RENDER_COLOR, &pagerect, &renderrect, m_parent->m_format, image.bytesPerLine(), reinterpret_cast< char* >(image.bits())))
|
|
{
|
|
image = QImage();
|
|
}
|
|
|
|
clearMessageQueue(m_parent->m_context, false);
|
|
|
|
ddjvu_page_release(page);
|
|
|
|
return image;
|
|
}
|
|
|
|
QString DjVuPage::label() const
|
|
{
|
|
return m_parent->m_titleByIndex.value(m_index);
|
|
}
|
|
|
|
QList< Link* > DjVuPage::links() const
|
|
{
|
|
LOCK_PAGE
|
|
|
|
miniexp_t pageAnnoExp = miniexp_nil;
|
|
|
|
{
|
|
LOCK_PAGE_GLOBAL
|
|
|
|
while(true)
|
|
{
|
|
pageAnnoExp = ddjvu_document_get_pageanno(m_parent->m_document, m_index);
|
|
|
|
if(pageAnnoExp == miniexp_dummy)
|
|
{
|
|
clearMessageQueue(m_parent->m_context, true);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
const QList< Link* > links = loadLinks(pageAnnoExp, m_size, m_index, m_parent->m_pageByName);
|
|
|
|
{
|
|
LOCK_PAGE_GLOBAL
|
|
|
|
ddjvu_miniexp_release(m_parent->m_document, pageAnnoExp);
|
|
}
|
|
|
|
return links;
|
|
}
|
|
|
|
QString DjVuPage::text(const QRectF& rect) const
|
|
{
|
|
LOCK_PAGE
|
|
|
|
miniexp_t pageTextExp = miniexp_nil;
|
|
|
|
{
|
|
LOCK_PAGE_GLOBAL
|
|
|
|
while(true)
|
|
{
|
|
pageTextExp = ddjvu_document_get_pagetext(m_parent->m_document, m_index, "word");
|
|
|
|
if(pageTextExp == miniexp_dummy)
|
|
{
|
|
clearMessageQueue(m_parent->m_context, true);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
const QTransform transform = QTransform::fromScale(m_resolution / 72.0, m_resolution / 72.0);
|
|
|
|
const QString text = loadText(pageTextExp, m_size, transform.mapRect(rect)).simplified();
|
|
|
|
{
|
|
LOCK_PAGE_GLOBAL
|
|
|
|
ddjvu_miniexp_release(m_parent->m_document, pageTextExp);
|
|
}
|
|
|
|
return text.simplified();
|
|
}
|
|
|
|
QList< QRectF > DjVuPage::search(const QString& text, bool matchCase, bool wholeWords) const
|
|
{
|
|
LOCK_PAGE
|
|
|
|
miniexp_t pageTextExp = miniexp_nil;
|
|
|
|
{
|
|
LOCK_PAGE_GLOBAL
|
|
|
|
while(true)
|
|
{
|
|
pageTextExp = ddjvu_document_get_pagetext(m_parent->m_document, m_index, "word");
|
|
|
|
if(pageTextExp == miniexp_dummy)
|
|
{
|
|
clearMessageQueue(m_parent->m_context, true);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
const QTransform transform = QTransform::fromScale(72.0 / m_resolution, 72.0 / m_resolution);
|
|
const QStringList words = text.split(QRegExp(QLatin1String("\\W+")), QString::SkipEmptyParts);
|
|
|
|
const QList< QRectF > results = findText(pageTextExp, m_size, transform, words, matchCase, wholeWords);
|
|
|
|
{
|
|
LOCK_PAGE_GLOBAL
|
|
|
|
ddjvu_miniexp_release(m_parent->m_document, pageTextExp);
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
DjVuDocument::DjVuDocument(QMutex* globalMutex, ddjvu_context_t* context, ddjvu_document_t* document) :
|
|
m_mutex(),
|
|
m_globalMutex(globalMutex),
|
|
m_context(context),
|
|
m_document(document),
|
|
m_format(0),
|
|
m_pageByName(),
|
|
m_titleByIndex()
|
|
{
|
|
unsigned int mask[] = {0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000};
|
|
|
|
m_format = ddjvu_format_create(DDJVU_FORMAT_RGBMASK32, 4, mask);
|
|
ddjvu_format_set_row_order(m_format, 1);
|
|
ddjvu_format_set_y_direction(m_format, 1);
|
|
|
|
prepareFileInfo();
|
|
}
|
|
|
|
DjVuDocument::~DjVuDocument()
|
|
{
|
|
ddjvu_document_release(m_document);
|
|
ddjvu_context_release(m_context);
|
|
ddjvu_format_release(m_format);
|
|
}
|
|
|
|
int DjVuDocument::numberOfPages() const
|
|
{
|
|
LOCK_DOCUMENT
|
|
|
|
return ddjvu_document_get_pagenum(m_document);
|
|
}
|
|
|
|
Page* DjVuDocument::page(int index) const
|
|
{
|
|
LOCK_DOCUMENT
|
|
|
|
ddjvu_status_t status;
|
|
ddjvu_pageinfo_t pageinfo;
|
|
|
|
while(true)
|
|
{
|
|
status = ddjvu_document_get_pageinfo(m_document, index, &pageinfo);
|
|
|
|
if(status < DDJVU_JOB_OK)
|
|
{
|
|
clearMessageQueue(m_context, true);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(status >= DDJVU_JOB_FAILED)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return new DjVuPage(this, index, pageinfo);
|
|
}
|
|
|
|
QStringList DjVuDocument::saveFilter() const
|
|
{
|
|
return QStringList() << QLatin1String("DjVu (*.djvu *.djv)");
|
|
}
|
|
|
|
bool DjVuDocument::canSave() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool DjVuDocument::save(const QString& filePath, bool withChanges) const
|
|
{
|
|
Q_UNUSED(withChanges);
|
|
|
|
LOCK_DOCUMENT
|
|
|
|
#ifdef _MSC_VER
|
|
|
|
FILE* file = _wfopen(reinterpret_cast< const wchar_t* >(filePath.utf16()), L"w+");
|
|
|
|
#else
|
|
|
|
FILE* file = fopen(QFile::encodeName(filePath), "w+");
|
|
|
|
#endif // _MSC_VER
|
|
|
|
if(file == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ddjvu_job_t* job = ddjvu_document_save(m_document, file, 0, 0);
|
|
|
|
while(!ddjvu_job_done(job))
|
|
{
|
|
clearMessageQueue(m_context, true);
|
|
}
|
|
|
|
fclose(file);
|
|
|
|
return !ddjvu_job_error(job);
|
|
}
|
|
|
|
Outline DjVuDocument::outline() const
|
|
{
|
|
Outline outline;
|
|
|
|
LOCK_DOCUMENT
|
|
|
|
miniexp_t outlineExp = miniexp_nil;
|
|
|
|
{
|
|
LOCK_DOCUMENT_GLOBAL
|
|
|
|
while(true)
|
|
{
|
|
outlineExp = ddjvu_document_get_outline(m_document);
|
|
|
|
if(outlineExp == miniexp_dummy)
|
|
{
|
|
clearMessageQueue(m_context, true);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(miniexp_length(outlineExp) > 1 && qstrcmp(miniexp_to_name(miniexp_car(outlineExp)), "bookmarks") == 0)
|
|
{
|
|
outline = loadOutline(skip(outlineExp, 1), m_pageByName);
|
|
}
|
|
|
|
{
|
|
LOCK_DOCUMENT_GLOBAL
|
|
|
|
ddjvu_miniexp_release(m_document, outlineExp);
|
|
}
|
|
|
|
return outline;
|
|
}
|
|
|
|
Properties DjVuDocument::properties() const
|
|
{
|
|
Properties properties;
|
|
|
|
LOCK_DOCUMENT
|
|
|
|
miniexp_t annoExp = miniexp_nil;
|
|
|
|
{
|
|
LOCK_DOCUMENT_GLOBAL
|
|
|
|
while(true)
|
|
{
|
|
annoExp = ddjvu_document_get_anno(m_document, TRUE);
|
|
|
|
if(annoExp == miniexp_dummy)
|
|
{
|
|
clearMessageQueue(m_context, true);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
properties = loadProperties(annoExp);
|
|
|
|
{
|
|
LOCK_DOCUMENT_GLOBAL
|
|
|
|
ddjvu_miniexp_release(m_document, annoExp);
|
|
}
|
|
|
|
return properties;
|
|
}
|
|
|
|
void DjVuDocument::prepareFileInfo()
|
|
{
|
|
for(int index = 0, count = ddjvu_document_get_filenum(m_document); index < count; ++index)
|
|
{
|
|
ddjvu_fileinfo_t fileinfo;
|
|
|
|
if(ddjvu_document_get_fileinfo(m_document, index, &fileinfo) != DDJVU_JOB_OK || fileinfo.type != 'P')
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const QString id = QString::fromUtf8(fileinfo.id);
|
|
const QString name = QString::fromUtf8(fileinfo.name);
|
|
const QString title = QString::fromUtf8(fileinfo.title);
|
|
|
|
m_pageByName[id] = m_pageByName[name] = m_pageByName[title] = fileinfo.pageno + 1;
|
|
|
|
if(!title.endsWith(".djvu", Qt::CaseInsensitive) && !title.endsWith(".djv", Qt::CaseInsensitive))
|
|
{
|
|
m_titleByIndex[fileinfo.pageno] = title;
|
|
}
|
|
}
|
|
|
|
m_pageByName.squeeze();
|
|
m_titleByIndex.squeeze();
|
|
}
|
|
|
|
} // Model
|
|
|
|
DjVuPlugin::DjVuPlugin(QObject* parent) : QObject(parent),
|
|
m_globalMutex()
|
|
{
|
|
setObjectName("DjVuPlugin");
|
|
}
|
|
|
|
Model::Document* DjVuPlugin::loadDocument(const QString& filePath) const
|
|
{
|
|
ddjvu_context_t* context = ddjvu_context_create("qpdfview");
|
|
|
|
if(context == 0)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
#if DDJVUAPI_VERSION >= 19
|
|
|
|
ddjvu_document_t* document = ddjvu_document_create_by_filename_utf8(context, filePath.toUtf8(), FALSE);
|
|
|
|
#else
|
|
|
|
ddjvu_document_t* document = ddjvu_document_create_by_filename(context, QFile::encodeName(filePath), FALSE);
|
|
|
|
#endif // DDJVUAPI_VERSION
|
|
|
|
if(document == 0)
|
|
{
|
|
ddjvu_context_release(context);
|
|
|
|
return 0;
|
|
}
|
|
|
|
waitForMessageTag(context, DDJVU_DOCINFO);
|
|
|
|
if(ddjvu_document_decoding_error(document))
|
|
{
|
|
ddjvu_document_release(document);
|
|
ddjvu_context_release(context);
|
|
|
|
return 0;
|
|
}
|
|
|
|
return new Model::DjVuDocument(&m_globalMutex, context, document);
|
|
}
|
|
|
|
} // qpdfview
|
|
|
|
#if QT_VERSION < QT_VERSION_CHECK(5,0,0)
|
|
|
|
Q_EXPORT_PLUGIN2(qpdfview_djvu, qpdfview::DjVuPlugin)
|
|
|
|
#endif // QT_VERSION
|