Qt 使用Poppler實(shí)現(xiàn)pdf閱讀器的示例代碼
開(kāi)發(fā)環(huán)境 Qt5.5.1、Qt Creator 3.5.1
Qt實(shí)現(xiàn)pdf閱讀器和MFC實(shí)現(xiàn)pdf閱讀器,其實(shí)原理都是差不多的。
需要用到Poppler開(kāi)源庫(kù),下載地址如下 https://poppler.freedesktop.org/
如果只是要在window的gcc下運(yùn)行的話,可以下載已經(jīng)編譯好的庫(kù) https://sourceforge.net/projects/poppler-win32/
注意:這個(gè)是MinGW版本的Qt,也就是運(yùn)行在GCC環(huán)境下的庫(kù),里面只包含 *.dll 和 *.a 。如果是Vistual Studio版本的Qt ,那么很不幸里面沒(méi)有 *.lib文件。
1、新建項(xiàng)目,在項(xiàng)目的根目錄新建一個(gè)“poppler”文件夾,將poppler中qt5目錄下的文件都丟進(jìn)去(*.h頭文件,另外再將編譯好的2個(gè)*.a文件和2個(gè)*.dll丟進(jìn)去,我這里多丟了實(shí)現(xiàn)的*.cc文件,因?yàn)?.cc已經(jīng)被編譯成動(dòng)態(tài)庫(kù)了,所以可以不用包含在代碼中)
2、在項(xiàng)目的pro配置文件中添加以下內(nèi)容,引用poppler的頭文件和庫(kù)文件(注意:我這里是win32,所以前面加了win32前綴)
INCLUDEPATH += $$PWD/poppler win32: LIBS += -L$$PWD/poppler -llibpoppler win32: LIBS += -L$$PWD/poppler -llibpoppler-qt5
3、創(chuàng)建pdf工具類(lèi)(該類(lèi)負(fù)責(zé)與poppler庫(kù)做對(duì)接,主要負(fù)責(zé)獲取pdf的總頁(yè)數(shù),和每頁(yè)的圖像)
(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();
//獲取指定頁(yè)pdf圖像(頁(yè)碼從0開(kāi)始)
QImage getPdfImage(int pageNumber);
//獲取pdf總頁(yè)碼
int getNumPages();
//獲取pdf頁(yè)面大小
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顯示類(lèi)(pdf的右側(cè)顯示滾動(dòng)條,①拖動(dòng)滾動(dòng)條翻頁(yè) ②鼠標(biāo)拖動(dòng)pdf到最上或最底時(shí)翻頁(yè))
注意:本文省略了頁(yè)面緩存,如果是真實(shí)的項(xiàng)目的話,本著嚴(yán)謹(jǐn)?shù)膽B(tài)度,請(qǐng)務(wù)必緩存頁(yè)面
(1)mypdfcanvas.h(繼承父類(lèi)的resizeEvent是為了 ①當(dāng)pdf只有1頁(yè)時(shí)不顯示滾動(dòng)條 ②當(dāng)用戶(hù)拖動(dòng)縮放窗口時(shí)動(dòng)態(tài)改變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);
//頁(yè)碼從0開(kāi)始
bool setPage(int pageNumber);
//獲取頁(yè)數(shù)
int getNumPages();
float getScaledRatio();
//顯示裁剪后的圖片
bool showClipImage(int pageNumber, int x, int y, int w, int h);
//取消顯示裁剪圖片,恢復(fù)正常顯示
void cancelClip();
//實(shí)際獲取的pdf寬高度
QSize pdfActualSize;
signals:
void pageChanged(int currentPage);
private:
PdfUtils* pdfUtils;
QString pdfPath;
//最大緩存圖片數(shù)量
int maxCachedNum;
//用來(lái)緩存pdf的每一個(gè)頁(yè)面的圖像(從0開(kāi)始)
QMap<int, QImage> cachedImageMap;
//用來(lái)存儲(chǔ)已緩存的pdf頁(yè)面序號(hào)(從0開(kāi)始)
// QQueue<int> cachedPageQueue;
//當(dāng)前頁(yè)碼(從0開(kāi)始)
int currentPage;
//總頁(yè)碼(從0開(kāi)始)
int numPages;
bool isMouseDown;
int lastMouseY;
//當(dāng)前pdf頁(yè)面的圖像
QImage image;
int imageX;
int imageY;
int imageMinY;
//是否是剪裁狀態(tài)
bool isClip;
//獲取指定頁(yè)的圖片
bool getPdfImage(int pageNumber);
void reachTop();
void reachBottom();
//判斷是否需要發(fā)送重定位簽名框的信號(hào)
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及右側(cè)滑塊的裝載容器
(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);
//重新調(diào)整pdf界面大小
void resizeCanvas();
void setWidgetVisible(bool pdfCanvasVisible, bool scrollbarVisible);
public slots:
//當(dāng)拖動(dòng)pdf上滑到頂(或下滑到底)時(shí)觸發(fā)該方法
onPageChange(int currentPage);
//當(dāng)滑動(dòng)條的滑塊被滑動(dòng)時(shí),會(huì)調(diào)用該方法
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、調(diào)用方式
(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、實(shí)際效果圖
更新于2016-08-03
8、項(xiàng)目下載地址(使用當(dāng)前最新的庫(kù)poppler-0.45.0、poppler-0.39.0-win32)
http://download.csdn.net/detail/chy555chy/9593364
該項(xiàng)目在win7(Qt5.1)、win10(Qt5.7)下測(cè)試過(guò)了,均可正常運(yùn)行。
下圖為項(xiàng)目目錄中的poppler文件夾(已經(jīng)刪去所有.cc文件),因?yàn)橹挥脦?kù)和頭文件,Qt便可隱式調(diào)用dll中的函數(shù)了。
更新于2016-08-22
你們?cè)u(píng)論中遇到的加載庫(kù)的時(shí)候就奔潰現(xiàn)象我還真沒(méi)遇到過(guò)。
下面是測(cè)試情況:
(1)當(dāng)PDF文件未找到的情況,會(huì)輸出錯(cuò)誤日志,但是并不會(huì)崩潰。
(2)當(dāng)路徑中包含”中文“,且包含"空格"的情況,poppler是可以正常打開(kāi)的。

以上這篇Qt 使用Poppler實(shí)現(xiàn)pdf閱讀器的示例代碼就是小編分享給大家的全部?jī)?nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
C++ socket實(shí)現(xiàn)miniFTP
這篇文章主要為大家詳細(xì)介紹了C++ socket實(shí)現(xiàn)miniFTP的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11
利用C語(yǔ)言模擬實(shí)現(xiàn)qsort,strcpy,strcat,strcmp函數(shù)
C語(yǔ)言學(xué)生信息管理系統(tǒng)小項(xiàng)目

