热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

Qt使用Poppler实现pdf阅读器的示例代码

下面小编就为大家分享一篇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 
#include 
#include 
#include 
#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()<

4、pdf显示类(pdf的右侧显示滚动条,①拖动滚动条翻页 ②鼠标拖动pdf到最上或最底时翻页)

注意:本文省略了页面缓存,如果是真实的项目的话,本着严谨的态度,请务必缓存页面

(1)mypdfcanvas.h(继承父类的resizeEvent是为了 ①当pdf只有1页时不显示滚动条 ②当用户拖动缩放窗口时动态改变pdf显示尺寸)

#ifndef MYPDFCANVAS_H
#define MYPDFCANVAS_H
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#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 cachedImageMap;
  //用来存储已缓存的pdf页面序号(从0开始)
//  QQueue 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 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 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() 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 

5、pdf及右侧滑块的装载容器

(1)mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include 
#include 
#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 "<rect()<<", "<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 
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阅读器的示例代码就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。


推荐阅读
  • 本文详细介绍了 Dockerfile 的编写方法及其在网络配置中的应用,涵盖基础指令、镜像构建与发布流程,并深入探讨了 Docker 的默认网络、容器互联及自定义网络的实现。 ... [详细]
  • Python 异步编程:深入理解 asyncio 库(上)
    本文介绍了 Python 3.4 版本引入的标准库 asyncio,该库为异步 IO 提供了强大的支持。我们将探讨为什么需要 asyncio,以及它如何简化并发编程的复杂性,并详细介绍其核心概念和使用方法。 ... [详细]
  • 深入解析Android自定义View面试题
    本文探讨了Android Launcher开发中自定义View的重要性,并通过一道经典的面试题,帮助开发者更好地理解自定义View的实现细节。文章不仅涵盖了基础知识,还提供了实际操作建议。 ... [详细]
  • 本文详细记录了在基于Debian的Deepin 20操作系统上安装MySQL 5.7的具体步骤,包括软件包的选择、依赖项的处理及远程访问权限的配置。 ... [详细]
  • 深入解析Spring Cloud Ribbon负载均衡机制
    本文详细介绍了Spring Cloud中的Ribbon组件如何实现服务调用的负载均衡。通过分析其工作原理、源码结构及配置方式,帮助读者理解Ribbon在分布式系统中的重要作用。 ... [详细]
  • 本文详细介绍了如何使用Spring Boot进行高效开发,涵盖了配置、实例化容器以及核心注解的使用方法。 ... [详细]
  • 本文详细介绍了W3C标准盒模型和IE传统盒模型的区别,探讨了CSS3中box-sizing属性的使用方法及其在布局中的重要性。通过实例分析,帮助读者更好地理解和应用这一关键概念。 ... [详细]
  • 题库来源:安全生产模拟考试一点通公众号小程序G3锅炉水处理报名考试是安全生产模拟考试一点通生成的,G3锅炉水处理证模拟考试题库是根据G3锅炉水处理最新 ... [详细]
  • 本文详细探讨了Netty中Future及其子类的设计与实现,包括其在并发编程中的作用和具体应用场景。我们将介绍Future的继承体系、关键方法的实现细节,并讨论如何通过监听器和回调机制来处理异步任务的结果。 ... [详细]
  • 2023年京东Android面试真题解析与经验分享
    本文由一位拥有6年Android开发经验的工程师撰写,详细解析了京东面试中常见的技术问题。涵盖引用传递、Handler机制、ListView优化、多线程控制及ANR处理等核心知识点。 ... [详细]
  • 本文介绍如何在 Unity 的 XML 配置文件中,将参数传递给自定义生命周期管理器的构造函数。我们将详细探讨 CustomLifetimeManager 类的实现及其配置方法。 ... [详细]
  • Ralph的Kubernetes进阶之旅:集群架构与对象解析
    本文深入探讨了Kubernetes集群的架构和核心对象,详细介绍了Pod、Service、Volume等基本组件,以及更高层次的抽象如Deployment、StatefulSet等,帮助读者全面理解Kubernetes的工作原理。 ... [详细]
  • Hadoop入门与核心组件详解
    本文详细介绍了Hadoop的基础知识及其核心组件,包括HDFS、MapReduce和YARN。通过本文,读者可以全面了解Hadoop的生态系统及应用场景。 ... [详细]
  • 本文详细探讨了Java中StringBuffer类在不同情况下的扩容规则,包括空参构造、带初始字符串和指定初始容量的构造方法。通过实例代码和理论分析,帮助读者更好地理解StringBuffer的内部工作原理。 ... [详细]
  • 本文探讨了领域驱动设计(DDD)的核心概念、应用场景及其实现方式,详细介绍了其在企业级软件开发中的优势和挑战。通过对比事务脚本与领域模型,展示了DDD如何提升系统的可维护性和扩展性。 ... [详细]
author-avatar
Vin-莹持_366
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有