/****************************************************************************
** This file is part of the taborca project hosted at
** sf.net/projects/taborca
** Copyright (C) 2013 Shawn Rutledge
** Contact: s@ecloud.org
**
** 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.
**
** This program 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 this program in a file called LICENSE; if not, write to the
** Free Software Foundation, Inc.,
** 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
****************************************************************************/
#include "pdfpager.h"
#include "common.h"
#include <QThreadPool>
PDFPager::PDFPager() :
pdf(NULL),
pageRange(this),
m_pageNum(-1),
m_renderSize(600, 600),
m_thumbnailRenderSize(72, 72)
{
setAutoDelete(false);
}
void PDFPager::openPDF(QString fpath)
{
m_path = fpath;
m_nullThumbnails.clear();
pdf = Poppler::Document::load(fpath);
if (!pdf || pdf->isLocked())
{
/// @todo ... error message ....
if (pdf)
delete pdf;
pdf = NULL;
return;
}
qDebug("open pdf success");
int pages = pdf->numPages();
m_cache.resize(pages);
emit opened(fpath, pages);
emit numPages(pages);
for (int p = 0; p < pages; ++p)
{
Poppler::Page* pdfPage = pdf->page(p);
if (pdfPage)
emit thumbnail(p, pdfPage->thumbnail(), pdfPage->label());
if (pdfPage)
{
QImage thumbnail = pdfPage->thumbnail();
if (thumbnail.isNull())
{
qDebug("null thumbnail for page %d", p);
m_nullThumbnails.append(p);
}
// Doesn't work. WTF...
// emit thumbnail(p, pdfPage->thumbnail(), pdfPage->label());
}
}
page(0);
pageRange.setRange(0, pages - 1);
pdf->setRenderHint(Poppler::Document::Antialiasing);
pdf->setRenderHint(Poppler::Document::TextAntialiasing);
pdf->setRenderHint(Poppler::Document::TextHinting);
// Start rendering thumbnails (if necessary) and pages in the background
QThreadPool::globalInstance()->start(this, QThread::LowPriority);
}
void PDFPager::closePDF()
{
if (!m_path.isEmpty() && pdf)
{
emit closed(m_path);
delete pdf;
pdf = NULL;
page(-1);
}
}
void PDFPager::page(int pnum)
{
if (pnum < 0)
return;
if (pnum >= m_cache.size())
return;
m_pageNum = pnum;
// Page fromCache = m_cache[pnum];
// if (fromCache.isNull())
render(pnum);
// else
// {
// emit rendered(pnum, fromCache);
// emit pageText(fromCache.textStrings);
// emit pageChanged(pnum);
// emit pageChanged(fromCache.label);
// }
}
void PDFPager::nextPage(bool forward)
{
if (forward)
page(m_pageNum + 1);
else
page(m_pageNum - 1);
}
void PDFPager::renderSize(QSize s, bool reRender)
{
qDebug("renderSize(%d x %d)", s.width(), s.height());
m_renderSize = s;
if (reRender)
page(m_pageNum);
}
void PDFPager::render(int pnum, QSize renderSize, bool emitImage)
{
m_renderMutex.lock();
qDebug("render(%d, %d x %d)", pnum, renderSize.width(), renderSize.height());
if (pnum >= pdf->numPages())
return;
if (!renderSize.isValid())
renderSize = m_renderSize;
emit rendering(true);
// Access page of the PDF file
if (!pdf)
return;
Poppler::Page* pdfPage = pdf->page(pnum); // Document starts at page 0
if (!pdfPage)
{
// ... error message ...
emit rendering(false);
return;
}
// Generate a QImage of the rendered page
double res = MIN(((double)(renderSize.width())) / // dots
(pdfPage->pageSizeF().width() / 72.0), // inches
((double)(renderSize.height())) / // dots
(pdfPage->pageSizeF().height() / 72.0)); // inches
qDebug("res would be %lf\n", res);
//qDebug("renderToImage: res %f to fit %d x %d", res, renderSize.width(), renderSize.height());
// @hack For now, fix the res at 72, because that way 1 point = 1 model unit,
// which helps the text items to match the image.
/// @todo define model units in terms of points:
/// on load, choose an appropriate resolution and
/// then convert TextBox point units to model units.
// if (res > 8) // e.g. it's not a thumbnail
// res = 144; // this will break text items matchup
// res = 72.0;
QImage image = pdfPage->renderToImage(res, res);//, 0, 0, 1000, 1000);
if (image.isNull())
{
// ... error message ...
emit rendering(false);
return;
}
//qDebug("renderDone: %d x %d", image.width(), image.height());
QString label = pdfPage->label();
if (label.isEmpty())
label = QString::number(pnum);
Page pcache = Page(label, image, pdfPage->textList());
if (res < 8) // a thumbnail
m_cache[pnum] = pcache;
if (emitImage)
{
emit pageChanged(pnum);
emit pageChanged(label);
renderDone(pnum, image);
emit pageText(pcache.textStrings);
}
// after reading everything of interest, the page can be deleted
// (pdf->page(pnum) resulted in ownership of that copy of the data,
// so this function is responsible to delete it)
delete pdfPage;
m_renderMutex.unlock();
}
void PDFPager::renderDone(int pnum, QImage pm)
{
emit rendered(pnum, pm);
emit rendering(false);
}
void PDFPager::run()
{
qDebug("PDFPager::run()");
int pages = pdf->numPages();
for (int i = 0; i < pages; ++i)
{
if (m_nullThumbnails.contains(i))
{
render(i, m_thumbnailRenderSize, false);
// @hack The cache happens to have a thumbnail right now,
// if no other rendering happened between the line above and this one.
qDebug("for page %d, emitting thumbnail with width %d; requested size was %d", i, m_cache[i].width(), m_thumbnailRenderSize.width());
emit thumbnail(i, m_cache[i], QString::number(i));
}
emit statusMessage(QString("pre-rendering page %1").arg(i));
// Put the full-size image in the cache
if (m_pageNum - 10 < i && i < m_pageNum + 10)
{
qDebug("pre-rendering page %d, near current page %d", i, m_pageNum);
render(i, m_renderSize, false);
}
}
emit statusClear();
}