项目需要展示PDF,环境为VS 2015 + Qt 5.6.3,所以新的库无法使用,另一个库qpdf虽然更易使用,但是需要Qt5.9以上,多番查找,未发现更好的库,只能自行编译poppler。
编译过程参考了以下两篇文章:
https://blog.csdn.net/u012509849/article/details/103121755
https://blog.csdn.net/pascadores/article/details/137591143
1. 下载poppler
官网:https://poppler.freedesktop.org/
测试了多个版本,发现只有0.69能编译成功
2. 下载cmake
官网:https://cmake.org/download/
这里使用的版本是:cmake-3.29.2-windows-i386
最开始用的其他版本,但是vcpkg会报错,按其提示使用了此版本
3. 下载vcpkg
https://github.com/microsoft/vcpkg
下载之后放到合适位置,命令行执行:bootstrap-vcpkg.bat
我这里只需要编译32位库,且只使用VS 2015,所以做以下两个更改:
添加环境变量:
VCPKG_DEFAULT_TRIPLET
=x86-windows
修改vcpkg目录中的文件:triplets/x86-windows.cmake,在最后一行添加:
set(VCPKG_PLATFORM_TOOLSET v140)
命令行执行vcpkg install $pkg_name
安装依赖库
大概安装了以下包:brotli、bzip2、cairo、curl、devil、dirent、egl-registry、expat、freeglut、freetype、libiconv、libjpeg-turbo、liblzma、libpng、openexr、opengl、openjpeg、tiff、zlib
4. 编译poppler-data
需要先编译poppler-data,同样在官网下载
双击之前安装的cmake-gui.exe,源码选择poppler-data目录,新建一个目录用于编译
依次点击Configure、Generate、Open Project,在VS中进行编译
将编译好的share文件夹下的文件拷贝到vcpkg的目录中:installed/x86-windows/share/
5. 编译poppler
修改poppler-0.69.0下的CMakeLists.txt,在前面配置vcpkg目录,修改后的前面几行如下:
cmake_minimum_required(VERSION 3.5.0 FATAL_ERROR)
set(CMAKE_TOOLCHAIN_FILE "D:/Programs/vcpkg/scripts/buildsystems/vcpkg.cmake")
project(poppler)
set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules)
include(PopplerDefaults)
include(PopplerMacros)
include(MacroOptionalFindPackage)
find_package(PkgConfig)
include(MacroEnsureVersion)
include(MacroBoolTo01)
第一次会报错没有test目录,按提示用git下载,放到poppler-0.69.0同一级目录
根据cmake提示调整配置,取消勾选不需要的,目录不对的修改下
最后在VS中选择poppler-qt
进行编译,注意选择Debug或Release,完成后可在目录qt5/src/Debug
,qt5/src/Release
找到编译好的文件
头文件直接使用poppler-0.69.0/qt5/src
整个编译过程中可能会遇到其他一些问题,通过搜索和询问AI一般都能解决。
6. 测试代码
使用QGraphicsView展示,由于大文件加载很慢,用QTimer依次加载
首先在pro中添加头文件和库配置:
INCLUDEPATH += $$PWD/poppler
LIBS += -lpoppler-qt5
将库拷贝到编译目录
新建的类如下:
pdfviewer.h
#ifndef PDFVIEWER_H
#define PDFVIEWER_H
#include <QGraphicsView>
#include <QPixmap>
#include <QWheelEvent>
#include "poppler-qt5.h"
class PDFViewer : public QGraphicsView {
Q_OBJECT
public:
explicit PDFViewer(QWidget *parent = nullptr);
~PDFViewer();
bool loadFile(const QString &iPath);
void clearData();
signals:
void pixChanged();
public slots:
void timeOutSlot();
void pixChangedSlot();
protected:
void wheelEvent(QWheelEvent *event) override;
private:
QList<QPixmap> mPixmapList;
QGraphicsScene *mScene = nullptr;
Poppler::Document *mDocument = nullptr;
qreal mYOffset = 0;
qreal mSceneWidth = 0;
int mLoadedCount = 3;
QTimer *mTimer = nullptr;
};
#endif // PDFVIEWER_H
pdfviewer.cpp
#include "pdfviewer.h"
#include <QTimer>
#include <QImage>
#include <QScrollBar>
#include <QtConcurrent>
#include <QFileInfo>
#include <QGraphicsScene>
#include <QGraphicsPixmapItem>
PDFViewer::PDFViewer(QWidget *iParent)
: QGraphicsView(iParent)
{
mScene = new QGraphicsScene(this);
setScene(mScene);
// 启用拖动功能
setDragMode(QGraphicsView::ScrollHandDrag);
// 设置视图属性
setRenderHints(QPainter::Antialiasing|QPainter::TextAntialiasing|QPainter::SmoothPixmapTransform);
setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
setResizeAnchor(QGraphicsView::AnchorUnderMouse);
mTimer = new QTimer(this);
mTimer->setSingleShot(true);
mTimer->setInterval(10);
connect(mTimer, SIGNAL(timeout()), this, SLOT(timeOutSlot()));
connect(this, SIGNAL(pixChanged()), this, SLOT(pixChangedSlot()), Qt::QueuedConnection);
}
PDFViewer::~PDFViewer()
{
mTimer->stop();
if (mDocument) {
delete mDocument;
mDocument = nullptr;
}
}
bool PDFViewer::loadFile(const QString &iPath)
{
clearData();
// 加载PDF文件
mDocument = Poppler::Document::load(iPath);
if (!mDocument || mDocument->isLocked()) {
qDebug()<<"load pdf file failed!";
return false;
}
mDocument->setRenderHint(Poppler::Document::Antialiasing, true);
mDocument->setRenderHint(Poppler::Document::TextAntialiasing, true);
// 用于垂直排列页面的偏移量
const double resX = physicalDpiX() * 2.0;
const double resY = physicalDpiY() * 2.0;
for (int i = 0; i < mLoadedCount; ++i) {
Poppler::Page *page = mDocument->page(i);
if (page) {
QImage image = page->renderToImage(resX, resY);
if (!image.isNull()) {
QPixmap pixmap = QPixmap::fromImage(image);
QGraphicsPixmapItem *item = new QGraphicsPixmapItem(pixmap);
mScene->addItem(item);
// 设置页面位置
item->setPos(0, mYOffset);
mYOffset += pixmap.height(); // 更新偏移量
if (pixmap.width() > mSceneWidth) {
mSceneWidth = pixmap.width();
}
}
}
}
mScene->setSceneRect(0, 0, mSceneWidth, mYOffset);
resetTransform();
centerOn(0, 0);
mTimer->start();
return true;
}
void PDFViewer::clearData()
{
mTimer->stop();
if (mDocument) {
delete mDocument;
mDocument = nullptr;
}
mScene->clear();
mScene->setSceneRect(0,0,0,0);
fitInView(mScene->sceneRect(), Qt::KeepAspectRatio);
resetTransform();
mYOffset = 0;
mSceneWidth = 0;
mLoadedCount = 3;
mPixmapList.clear();
}
void PDFViewer::timeOutSlot()
{
QtConcurrent::run([this](){
const double resX = physicalDpiX() * 2.0;
const double resY = physicalDpiY() * 2.0;
if (mLoadedCount < mDocument->numPages()) {
Poppler::Page *page = mDocument->page(mLoadedCount);
if (page) {
mLoadedCount += 1;
QImage image = page->renderToImage(resX, resY);
if (!image.isNull()) {
QPixmap pix = QPixmap::fromImage(image);
mPixmapList.append(pix);
emit pixChanged();
}
}
}
});
}
void PDFViewer::pixChangedSlot()
{
if (mPixmapList.size() > 0) {
for (QPixmap &pix: mPixmapList) {
QGraphicsPixmapItem *item = new QGraphicsPixmapItem(pix);
mScene->addItem(item);
item->setPos(0, mYOffset);
mYOffset += pix.height();
if (pix.width() > mSceneWidth) {
mSceneWidth = pix.width();
}
}
mScene->setSceneRect(0, 0, mSceneWidth, mYOffset);
mPixmapList.clear();
mTimer->start();
}
}
void PDFViewer::wheelEvent(QWheelEvent *event)
{
if (event->modifiers() & Qt::ControlModifier) {
// 按住Ctrl键时缩放
double scaleFactor = 1.15;
if (event->delta() > 0) {
scale(scaleFactor, scaleFactor);
} else {
scale(1 / scaleFactor, 1 / scaleFactor);
}
} else {
QGraphicsView::wheelEvent(event);
}
}
效果如下:
评论区