qpdfviewsb/sources/fitzmodel.cpp

451 lines
11 KiB
C++

/*
Copyright 2018 S. Razi Alavizadeh
Copyright 2015 Martin Banky
Copyright 2014-2015, 2018 Adam Reichold
This file is part of qpdfview.
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 "fitzmodel.h"
#include <QFile>
#include <qmath.h>
extern "C"
{
#include <mupdf/fitz/stream.h>
#include <mupdf/fitz/bidi.h>
#include <mupdf/fitz/output.h>
#include <mupdf/fitz/display-list.h>
#include <mupdf/fitz/document.h>
#include <mupdf/fitz/pool.h>
#include <mupdf/fitz/structured-text.h>
typedef struct pdf_document_s pdf_document;
pdf_document* pdf_specifics(fz_context*, fz_document*);
}
namespace
{
using namespace qpdfview;
using namespace qpdfview::Model;
QString removeFilePrefix(const char* uri)
{
QString url = QString::fromUtf8(uri);
if(url.startsWith("file://", Qt::CaseInsensitive))
{
url = url.mid(7);
}
return url;
}
Outline loadOutline(fz_outline* item)
{
Outline outline;
for(; item; item = item->next)
{
outline.push_back(Section());
Section& section = outline.back();
section.title = QString::fromUtf8(item->title);
if(item->page != -1)
{
section.link.page = item->page + 1;
}
else if (item->uri != 0)
{
section.link.urlOrFileName = removeFilePrefix(item->uri);
}
if(fz_outline* childItem = item->down)
{
section.children = loadOutline(childItem);
}
}
return outline;
}
} // anonymous
namespace qpdfview
{
namespace Model
{
FitzPage::FitzPage(const FitzDocument* parent, fz_page* page) :
m_parent(parent),
m_page(page)
{
}
FitzPage::~FitzPage()
{
fz_drop_page(m_parent->m_context, m_page);
}
QSizeF FitzPage::size() const
{
QMutexLocker mutexLocker(&m_parent->m_mutex);
fz_rect rect;
fz_bound_page(m_parent->m_context, m_page, &rect);
return QSizeF(rect.x1 - rect.x0, rect.y1 - rect.y0);
}
QImage FitzPage::render(qreal horizontalResolution, qreal verticalResolution, Rotation rotation, QRect boundingRect) const
{
QMutexLocker mutexLocker(&m_parent->m_mutex);
fz_matrix matrix;
fz_scale(&matrix, horizontalResolution / 72.0f, verticalResolution / 72.0f);
switch(rotation)
{
default:
case RotateBy0:
fz_pre_rotate(&matrix, 0.0);
break;
case RotateBy90:
fz_pre_rotate(&matrix, 90.0);
break;
case RotateBy180:
fz_pre_rotate(&matrix, 180.0);
break;
case RotateBy270:
fz_pre_rotate(&matrix, 270.0);
break;
}
fz_rect rect;
fz_bound_page(m_parent->m_context, m_page, &rect);
fz_transform_rect(&rect, &matrix);
fz_irect irect;
fz_round_rect(&irect, &rect);
fz_context* context = fz_clone_context(m_parent->m_context);
fz_display_list* display_list = fz_new_display_list(context, &rect);
fz_device* device = fz_new_list_device(context, display_list);
fz_run_page(m_parent->m_context, m_page, device, &matrix, 0);
fz_close_device(m_parent->m_context, device);
fz_drop_device(m_parent->m_context, device);
mutexLocker.unlock();
fz_matrix tileMatrix;
fz_translate(&tileMatrix, -rect.x0, -rect.y0);
fz_rect tileRect = fz_infinite_rect;
int tileWidth = irect.x1 - irect.x0;
int tileHeight = irect.y1 - irect.y0;
if(!boundingRect.isNull())
{
fz_pre_translate(&tileMatrix, -boundingRect.x(), -boundingRect.y());
tileRect.x0 = boundingRect.x();
tileRect.y0 = boundingRect.y();
tileRect.x1 = boundingRect.right();
tileRect.y1 = boundingRect.bottom();
tileWidth = boundingRect.width();
tileHeight = boundingRect.height();
}
QImage image(tileWidth, tileHeight, QImage::Format_RGB32);
image.fill(m_parent->m_paperColor);
fz_pixmap* pixmap = fz_new_pixmap_with_data(context, fz_device_bgr(context), image.width(), image.height(), 0, 1, image.bytesPerLine(), image.bits());
device = fz_new_draw_device(context, &tileMatrix, pixmap);
fz_run_display_list(context, display_list, device, &fz_identity, &tileRect, 0);
fz_close_device(context, device);
fz_drop_device(context, device);
fz_drop_pixmap(context, pixmap);
fz_drop_display_list(context, display_list);
fz_drop_context(context);
return image;
}
QList< Link* > FitzPage::links() const
{
QMutexLocker mutexLocker(&m_parent->m_mutex);
QList< Link* > links;
fz_rect rect;
fz_bound_page(m_parent->m_context, m_page, &rect);
const qreal width = qAbs(rect.x1 - rect.x0);
const qreal height = qAbs(rect.y1 - rect.y0);
fz_link* first_link = fz_load_links(m_parent->m_context, m_page);
for(fz_link* link = first_link; link != 0; link = link->next)
{
const QRectF boundary = QRectF(link->rect.x0 / width, link->rect.y0 / height, (link->rect.x1 - link->rect.x0) / width, (link->rect.y1 - link->rect.y0) / height).normalized();
if (link->uri != 0)
{
if (fz_is_external_link(m_parent->m_context, link->uri) == 0)
{
float left;
float top;
const int page = fz_resolve_link(m_parent->m_context, m_parent->m_document, link->uri, &left, &top);
if (page != -1)
{
links.append(new Link(boundary, page + 1, left / width, top / height));
}
}
else
{
links.append(new Link(boundary, removeFilePrefix(link->uri)));
}
}
}
fz_drop_link(m_parent->m_context, first_link);
return links;
}
QString FitzPage::text(const QRectF &rect) const
{
QMutexLocker mutexLocker(&m_parent->m_mutex);
fz_rect mediaBox;
mediaBox.x0 = rect.x();
mediaBox.y0 = rect.y();
mediaBox.x1 = rect.right();
mediaBox.y1 = rect.bottom();
fz_stext_page* textPage = fz_new_stext_page(m_parent->m_context, &mediaBox);
fz_device* device = fz_new_stext_device(m_parent->m_context, textPage, 0);
fz_run_page(m_parent->m_context, m_page, device, &fz_identity, 0);
fz_close_device(m_parent->m_context, device);
fz_drop_device(m_parent->m_context, device);
fz_point topLeft;
topLeft.x = rect.x();
topLeft.y = rect.y();
fz_point bottomRight;
bottomRight.x = rect.right();
bottomRight.y = rect.bottom();
char* selection = fz_copy_selection(m_parent->m_context, textPage, topLeft, bottomRight, 0);
QString text = QString::fromUtf8(selection);
::free(selection);
fz_drop_stext_page(m_parent->m_context, textPage);
return text;
}
QList<QRectF> FitzPage::search(const QString& text, bool matchCase, bool wholeWords) const
{
Q_UNUSED(matchCase);
Q_UNUSED(wholeWords);
QMutexLocker mutexLocker(&m_parent->m_mutex);
fz_rect rect;
fz_bound_page(m_parent->m_context, m_page, &rect);
fz_stext_page* textPage = fz_new_stext_page(m_parent->m_context, &rect);
fz_device* device = fz_new_stext_device(m_parent->m_context, textPage, 0);
fz_run_page(m_parent->m_context, m_page, device, &fz_identity, 0);
fz_close_device(m_parent->m_context, device);
fz_drop_device(m_parent->m_context, device);
const QByteArray needle = text.toUtf8();
QVector< fz_rect > hits(32);
int numberOfHits = fz_search_stext_page(m_parent->m_context, textPage, needle.constData(), hits.data(), hits.size());
while(numberOfHits == hits.size())
{
hits.resize(2 * hits.size());
numberOfHits = fz_search_stext_page(m_parent->m_context, textPage, needle.constData(), hits.data(), hits.size());
}
hits.resize(numberOfHits);
fz_drop_stext_page(m_parent->m_context, textPage);
QList< QRectF > results;
results.reserve(hits.size());
foreach(fz_rect rect, hits)
{
results.append(QRectF(rect.x0, rect.y0, rect.x1 - rect.x0, rect.y1 - rect.y0));
}
return results;
}
FitzDocument::FitzDocument(fz_context* context, fz_document* document) :
m_mutex(),
m_context(context),
m_document(document),
m_paperColor(Qt::white)
{
}
FitzDocument::~FitzDocument()
{
fz_drop_document(m_context, m_document);
fz_drop_context(m_context);
}
int FitzDocument::numberOfPages() const
{
QMutexLocker mutexLocker(&m_mutex);
return fz_count_pages(m_context, m_document);
}
Page* FitzDocument::page(int index) const
{
QMutexLocker mutexLocker(&m_mutex);
if(fz_page* page = fz_load_page(m_context, m_document, index))
{
return new FitzPage(this, page);
}
return 0;
}
bool FitzDocument::canBePrintedUsingCUPS() const
{
QMutexLocker mutexLocker(&m_mutex);
return pdf_specifics(m_context, m_document) != 0;
}
void FitzDocument::setPaperColor(const QColor& paperColor)
{
m_paperColor = paperColor;
}
Outline FitzDocument::outline() const
{
Outline outline;
QMutexLocker mutexLocker(&m_mutex);
if(fz_outline* rootItem = fz_load_outline(m_context, m_document))
{
outline = loadOutline(rootItem);
fz_drop_outline(m_context, rootItem);
}
return outline;
}
} // Model
FitzPlugin::FitzPlugin(QObject* parent) : QObject(parent)
{
setObjectName("FitzPlugin");
m_locks_context.user = this;
m_locks_context.lock = FitzPlugin::lock;
m_locks_context.unlock = FitzPlugin::unlock;
m_context = fz_new_context(0, &m_locks_context, FZ_STORE_DEFAULT);
fz_register_document_handlers(m_context);
}
FitzPlugin::~FitzPlugin()
{
fz_drop_context(m_context);
}
Model::Document* FitzPlugin::loadDocument(const QString& filePath) const
{
fz_context* context = fz_clone_context(m_context);
if(context == 0)
{
return 0;
}
#ifdef _MSC_VER
fz_document* document = fz_open_document(context, filePath.toUtf8());
#else
fz_document* document = fz_open_document(context, QFile::encodeName(filePath));
#endif // _MSC_VER
if(document == 0)
{
fz_drop_context(context);
return 0;
}
return new Model::FitzDocument(context, document);
}
void FitzPlugin::lock(void* user, int lock)
{
static_cast< FitzPlugin* >(user)->m_mutex[lock].lock();
}
void FitzPlugin::unlock(void* user, int lock)
{
static_cast< FitzPlugin* >(user)->m_mutex[lock].unlock();
}
} // qpdfview
#if QT_VERSION < QT_VERSION_CHECK(5,0,0)
Q_EXPORT_PLUGIN2(qpdfview_fitz, qpdfview::FitzPlugin)
#endif // QT_VERSION