Qt 使用Poppler实现pdf阅读器
Posted 福州-司马懿
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Qt 使用Poppler实现pdf阅读器相关的知识,希望对你有一定的参考价值。
开发环境 Qt5.5.1、Qt Creator 3.5.1
Qt实现pdf阅读器和MFC实现pdf阅读器,其实原理都是差不多的。
需要用到Poppler开源库,下载地址如下 https://poppler.freedesktop.org/
如果只是要在window的gcc下运行的话,可以下载已经编译好的库 https://sourceforge.net/projects/poppler-win32/
注意:这个是MinGW版本的Qt,也就是运行在GCC环境下的库,里面只包含 *.dll 和 *.a 。如果是Vistual Studio版本的Qt ,那么很不幸里面没有 *.lib文件。
1、新建项目,在项目的根目录新建一个“poppler”文件夹,将poppler中qt5目录下的文件都丢进去(*.h头文件,另外再将编译好的2个*.a文件和2个*.dll丢进去,我这里多丢了实现的*.cc文件,因为*.cc已经被编译成动态库了,所以可以不用包含在代码中)
2、在项目的pro配置文件中添加以下内容,引用poppler的头文件和库文件(注意:我这里是win32,所以前面加了win32前缀)
INCLUDEPATH += $$PWD/poppler
win32: LIBS += -L$$PWD/poppler -llibpoppler
win32: LIBS += -L$$PWD/poppler -llibpoppler-qt5
(1)pdfutils.h
#ifndef PDFUTILS_H
#define PDFUTILS_H
#include <QObject>
#include <QImage>
#include <QSize>
#include <QDebug>
#include "poppler-qt5.h"
class PdfUtils
public:
explicit PdfUtils(QString filePath);
~PdfUtils();
//获取指定页pdf图像(页码从0开始)
QImage getPdfImage(int pageNumber);
//获取pdf总页码
int getNumPages();
//获取pdf页面大小
QSize getPageSize();
private:
QString filePath;
int numPages;
QSize pageSize;
void getPdfInfo();
;
#endif // PDFUTILS_H
(2)pdfutils.cpp
#include "pdfutils.h"
PdfUtils::PdfUtils(QString filePath)
this->filePath = filePath;
getPdfInfo();
PdfUtils::~PdfUtils()
QImage PdfUtils::getPdfImage(int pageNumber)
QImage image;
Poppler::Document* document = Poppler::Document::load(filePath);
if (!document || document->isLocked())
// ... error message ....
delete document;
return image;
// Document starts at page 0
Poppler::Page* pdfPage = document->page(pageNumber);
if (pdfPage == 0)
// ... error message ...
return image;
// Generate a QImage of the rendered page
image = pdfPage->renderToImage(72, 72, -1, -1, -1, -1);
if (image.isNull())
// ... error message ...
return image;
// after the usage, the page must be deleted
delete pdfPage;
delete document;
return image;
int PdfUtils::getNumPages()
return numPages;
QSize PdfUtils::getPageSize()
return pageSize;
void PdfUtils::getPdfInfo()
numPages = 0;
Poppler::Document* document = Poppler::Document::load(filePath);
if (!document || document->isLocked())
// ... error message ....
delete document;
return;
numPages = document->numPages();
Poppler::Page* pdfPage = document->page(0);
pageSize = pdfPage->pageSize();
qDebug()<<pageSize;
delete pdfPage;
delete document;
4、pdf显示类(pdf的右侧显示滚动条,①拖动滚动条翻页 ②鼠标拖动pdf到最上或最底时翻页)
注意:本文省略了页面缓存,如果是真实的项目的话,本着严谨的态度,请务必缓存页面
(1)mypdfcanvas.h(继承父类的resizeEvent是为了 ①当pdf只有1页时不显示滚动条 ②当用户拖动缩放窗口时动态改变pdf显示尺寸)
#ifndef MYPDFCANVAS_H
#define MYPDFCANVAS_H
#include <QWidget>
#include <QVector>
#include <QMouseEvent>
#include <QPaintEvent>
#include <QPainter>
#include <QPaintEvent>
#include <QMap>
#include <QPalette>
#include <QResizeEvent>
#include "pdfutils.h"
class MyPdfCanvas : public QWidget
Q_OBJECT
public:
explicit MyPdfCanvas(QWidget *parent = 0);
~MyPdfCanvas();
void resizeEvent(QResizeEvent* e);
void paintEvent(QPaintEvent *e);
void mousePressEvent(QMouseEvent *e);
void mouseReleaseEvent(QMouseEvent *e);
void mouseMoveEvent(QMouseEvent *e);
void setMaxCachedNum(int maxCachedNum);
//如果不能解析pdf返回false
bool setPath(QString pdfPath);
//页码从0开始
bool setPage(int pageNumber);
//获取页数
int getNumPages();
float getScaledRatio();
//显示裁剪后的图片
bool showClipImage(int pageNumber, int x, int y, int w, int h);
//取消显示裁剪图片,恢复正常显示
void cancelClip();
//实际获取的pdf宽高度
QSize pdfActualSize;
signals:
void pageChanged(int currentPage);
private:
PdfUtils* pdfUtils;
QString pdfPath;
//最大缓存图片数量
int maxCachedNum;
//用来缓存pdf的每一个页面的图像(从0开始)
QMap<int, QImage> cachedImageMap;
//用来存储已缓存的pdf页面序号(从0开始)
// QQueue<int> cachedPageQueue;
//当前页码(从0开始)
int currentPage;
//总页码(从0开始)
int numPages;
bool isMouseDown;
int lastMouseY;
//当前pdf页面的图像
QImage image;
int imageX;
int imageY;
int imageMinY;
//是否是剪裁状态
bool isClip;
//获取指定页的图片
bool getPdfImage(int pageNumber);
void reachTop();
void reachBottom();
//判断是否需要发送重定位签名框的信号
void needLocateSignArea();
;
#endif // MYPDFCANVAS_H
(2)pdfcanvas.cpp
#include "mypdfcanvas.h"
MyPdfCanvas::MyPdfCanvas(QWidget *parent) : QWidget(parent)
pdfUtils = NULL;
imageX = 0;
imageY = 0;
isClip = false;
setAutoFillBackground(true);
MyPdfCanvas::~MyPdfCanvas()
if(pdfUtils != NULL) delete pdfUtils;
void MyPdfCanvas::resizeEvent(QResizeEvent *e)
image = this->cachedImageMap[currentPage];
if(!image.isNull())
float radio = (float)e->size().width()/(float)e->oldSize().width();
int imageHeight = image.height()* e->size().width()/image.width();
image = image.scaled(e->size().width(), imageHeight);
if(imageHeight < this->height())
imageY = (this->height()-imageHeight)/2;
//如果图片高度小于控件高度,则图片居中
// imageMinY = imageY;
imageMinY = 0;
imageY = imageMinY;
else
if(radio>0)
imageY = (int)(imageY*radio);
if(imageY > 0)
imageY = 0;
else
imageY = 0;
void MyPdfCanvas::paintEvent(QPaintEvent *e)
QPainter* painter = new QPainter(this);
if(image.isNull())
painter->fillRect(this->rect(), Qt::transparent);
return;
painter->drawImage(0, imageY, image);
delete painter;
void MyPdfCanvas::mousePressEvent(QMouseEvent *e)
isMouseDown = true;
lastMouseY = e->y();
void MyPdfCanvas::mouseReleaseEvent(QMouseEvent *e)
isMouseDown = false;
void MyPdfCanvas::mouseMoveEvent(QMouseEvent *e)
if(!isMouseDown || image.isNull())
return;
int distance = e->y() - lastMouseY;
lastMouseY = e->y();
imageY += distance;
if(imageY > 0)
imageY = 0;
reachTop();
return;
else if(imageY < imageMinY)
imageY = imageMinY;
reachBottom();
return;
update();
void MyPdfCanvas::setMaxCachedNum(int maxCachedNum)
this->maxCachedNum = maxCachedNum;
bool MyPdfCanvas::setPath(QString pdfPath)
this->pdfPath = pdfPath;
if(pdfUtils != NULL) delete pdfUtils;
pdfUtils = new PdfUtils(pdfPath);
numPages = pdfUtils->getNumPages();
if(numPages > 0)
isClip = false;
pdfActualSize = pdfUtils->getPageSize();
cachedImageMap.clear();
currentPage = 0;
imageY = 0;
lastMouseY = 0;
return numPages > 0;
bool MyPdfCanvas::setPage(int pageNumber)
if(!getPdfImage(pageNumber))
return false;
isClip = false;
isMouseDown = false;
image = image.scaledToWidth(this->width());
imageMinY = this->height() - image.height();
if(image.height() < this->height())
//如果图片高度小于控件高度,则图片居中
// imageMinY /= 2;
imageMinY = 0;
imageY = imageMinY;
else
imageY = 0;
update();
return true;
int MyPdfCanvas::getNumPages()
return numPages;
float MyPdfCanvas::getScaledRatio()
int pdfWidth = pdfUtils->getPageSize().width();
return (float)this->width()/(float)pdfWidth;
bool MyPdfCanvas::showClipImage(int pageNumber, int x, int y, int w, int h)
if(!getPdfImage(pageNumber))
return false;
isClip = true;
imageY = 0;
image = image.copy(x, y, w, h).scaled(this->size());
update();
void MyPdfCanvas::cancelClip()
isClip = false;
setPage(currentPage);
bool MyPdfCanvas::getPdfImage(int pageNumber)
if(pageNumber<0 || pageNumber >= numPages)
return false;
if(cachedImageMap.contains(pageNumber))
image = cachedImageMap.value(pageNumber);
else
image = pdfUtils->getPdfImage(pageNumber);
if(!image.isNull())
cachedImageMap[pageNumber] = image;
pdfActualSize = image.size();
if(image.isNull())
return false;
currentPage = pageNumber;
return true;
void MyPdfCanvas::reachTop()
if(currentPage > 0)
emit pageChanged(currentPage-1);
void MyPdfCanvas::reachBottom()
if(currentPage < numPages-1)
emit pageChanged(currentPage+1);
5、pdf及右侧滑块的装载容器
(1)mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QScrollBar>
#include "mypdfcanvas.h"
#define SCROLLBAR_WIDTH 30
class MainWindow : public QMainWindow
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
void resizeEvent(QResizeEvent* e);
bool setPdfPath(QString path);
//重新调整pdf界面大小
void resizeCanvas();
void setWidgetVisible(bool pdfCanvasVisible, bool scrollbarVisible);
public slots:
//当拖动pdf上滑到顶(或下滑到底)时触发该方法
onPageChange(int currentPage);
//当滑动条的滑块被滑动时,会调用该方法
onScrollBarValueChange();
private:
MyPdfCanvas *pdfCanvas;
QScrollBar *scrollbar;
;
#endif // MAINWINDOW_H
(2)mainwindow.cpp
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
pdfCanvas = new MyPdfCanvas(this);
scrollbar = new QScrollBar(Qt::Vertical, this);
setWidgetVisible(false, false);
connect(pdfCanvas, SIGNAL(pageChanged(int)), this, SLOT(onPageChange(int)));
connect(scrollbar, SIGNAL(valueChanged(int)), this, SLOT(onScrollBarValueChange()));
MainWindow::~MainWindow()
void MainWindow::resizeEvent(QResizeEvent *e)
resizeCanvas();
bool MainWindow::setPdfPath(QString path)
bool result = pdfCanvas->setPath(path);
if(result)
int numPages = pdfCanvas->getNumPages();
if(numPages>1)
scrollbar->setMaximum(numPages-1);
scrollbar->setValue(0);
pdfCanvas->setPage(0);
resizeCanvas();
return result;
void MainWindow::resizeCanvas()
qDebug()<<"resize "<<this->rect()<<", "<<pdfCanvas->rect();
int numPages = pdfCanvas->getNumPages();
if(numPages == 1)
pdfCanvas->setGeometry(this->rect());
setWidgetVisible(true, false);
else if(numPages > 1)
pdfCanvas->setGeometry(0, 0, this->width()-SCROLLBAR_WIDTH, this->height());
scrollbar->setGeometry(this->width()-SCROLLBAR_WIDTH, 0, this->width()-SCROLLBAR_WIDTH, this->height());
setWidgetVisible(true, true);
else
//numPages <= 0
setWidgetVisible(false, false);
void MainWindow::setWidgetVisible(bool pdfCanvasVisible, bool scrollbarVisible)
pdfCanvas->setVisible(pdfCanvasVisible);
scrollbar->setVisible(scrollbarVisible);
MainWindow::onPageChange(int currentPage)
pdfCanvas->setPage(currentPage);
MainWindow::onScrollBarValueChange()
pdfCanvas->setPage(scrollbar->value());
6、调用方式
(1)main.cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
QApplication a(argc, argv);
MainWindow w;
w.resize(500, 500);
w.show();
QString path = "D://test.pdf";
w.setPdfPath(path);
w.setWindowTitle(path);
return a.exec();
7、实际效果图
更新于2016-08-03
8、项目下载地址(使用当前最新的库poppler-0.45.0、poppler-0.39.0-win32)
http://download.csdn.net/detail/chy555chy/9593364
该项目在win7(Qt5.1)、win10(Qt5.7)下测试过了,均可正常运行。
下图为项目目录中的poppler文件夹(已经删去所有.cc文件),因为只用库和头文件,Qt便可隐式调用dll中的函数了。
更新于2016-08-22
你们评论中遇到的加载库的时候就奔溃现象我还真没遇到过。
下面是测试情况:
(1)当PDF文件未找到的情况,会输出错误日志,但是并不会崩溃。
(2)当路径中包含”中文“,且包含"空格"的情况,poppler是可以正常打开的。
以上是关于Qt 使用Poppler实现pdf阅读器的主要内容,如果未能解决你的问题,请参考以下文章