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


3、创建pdf工具类(该类负责与poppler库做对接,主要负责获取pdf的总页数,和每页的图像)

(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阅读器的主要内容,如果未能解决你的问题,请参考以下文章

发生 qt5 错误 Poppler::Document* 文档

波普勒替代品

你有 py-poppler-qt 的例子吗?

Poppler:以目标分辨率渲染

Python poppler Qt5 文档加载错误

将 pdf.js 与 Qt5.8 一起使用