QT虽然有PDF模块,但是需要5.15以及6之后的版本,并且只支持加载文档进行阅览,不支持修改PDF文件。
本篇博客使用QT5,不使用QT的PDF模块,通过两百行代码编写一个支持阅览与任意位置插入图片的PDF编辑器,并且还可以拓展其他功能
PDF文档的显示:我们使用poppler这个pdf库,poppler是一个用于渲染pdf文件的开源库,遵循GPL协议。
对于qt,poppler专门有一个适配的库:poppler-qt
poppler下载链接:Poppler
关于qt使用poppler,我们来看一下官方示例的代码:
加载一个指定的文档:
- QString filename;
-
- Poppler::Document* document = Poppler::Document::load(filename);
- if (!document || document->isLocked()) {
-
- // ... error message ....
-
- delete document;
- return;
- }
函数说明:
Poppler::Document::load(QStirng);
返回一个从磁盘上的文件加载了指定文档的document对象。
另外还有从数据流加载PDF的函数
static Document * Poppler::Document::loadFromData ( const QByteArray & fileContents, const QByteArray & ownerPassword = QByteArray(), const QByteArray & userPassword = QByteArray() )
将文档中的某个页面,渲染到QImage上:
- // Paranoid safety check
- if (document == 0) {
- // ... error message ...
- return;
- }
-
- // Access page of the PDF file
- Poppler::Page* pdfPage = document->page(pageNumber); // Document starts at page 0
- if (pdfPage == 0) {
- // ... error message ...
- return;
- }
-
- // Generate a QImage of the rendered page
- QImage image = pdfPage->renderToImage(xres, yres, x, y, width, height);
- if (image.isNull()) {
- // ... error message ...
- return;
- }
-
- // ... use image ...
-
- // after the usage, the page must be deleted
- delete pdfPage;
函数说明:
Page * Poppler::Document::page (int index) const
获取加载文档的某一页。 这个页码时从0开始的,如果加载第一页,那么index的值应该为0。
QImage Poppler::Page::renderToImage ( double xres = 72.0, double yres = 72.0, int x = -1, int y = -1, int w = -1, int h = -1, Rotation rotate = Rotate0 )
将加载的页面,渲染为QImage形式,以便展示在窗口上
参数:
x | 指定框的左侧 x 坐标(以像素为单位)。 |
y | 指定框的顶部 y 坐标(以像素为单位)。 |
w | 指定框的宽度(以像素为单位)。 |
h | 指定框的高度(以像素为单位)。 |
Xres | 图形设备的水平分辨率,以每英寸点数为单位 |
yres | 图形设备的垂直分辨率,以每英寸点数表示 |
rotate | 如何旋转页面,竖向展示、横向展示等 |
当xywh几个值偶读设置为-1的时候,会根据Xres和yres中指定的水平和垂直分辨率自动计算图像的大小
所有操作完毕,记得释放文档
delete document;
编辑PDF文档:使用PDF-Writer来编辑PDF文档
PDF-Writer的github链接:https://github.com/galkahana/PDF-Writer,原名好像是pdfhummus,遵循Apache-2.0 license协议。 wiki上的文档很丰富很详细,编译过程都有说明,这里就不详述了
对于这个库的一个小测试也可以看我这个博文C++编辑修改PDF-CSDN博客
linux下使用
首先在两个库的github中下载最新的源码包,拷贝到linux系统中,按照github上描述的编译步骤进行编译和安装。然后就能在QT中引用相关库了
windows下可能有一些已经编译好的库,需要注意使用msvc编译器和mingw编译器时,引用的库是否是对应的,否则会报错
使用QtCreator新建一个Widget项目
pro文件添加:
头文件
INCLUDEPATH += /home/ubuntu/pro/pdf/pdfwriter/PDF-Writer-master/install/include/PDFWriter
INCLUDEPATH += /home/ubuntu/pro/pdf/pdfwriter/PDF-Writer-master/install/include
库
LIBS += -lpoppler
LIBS += -lpoppler-qt5
LIBS+=-L/home/ubuntu/pro/pdf/pdfwriter/PDF-Writer-master/install/lib -lPDFWriter -lFreeType -lLibPng -lLibJpeg -lLibTiff -lLibAesgm -lZlib
这里我的poppler是直接装到默认目录了,如果指定了安装目录,像PDFWriter一样使用-L指定路径就可以了
如果是使用的动态库,系统中有重复的库,记得加上-rpath或者-runpath的后缀指明运行时查找动态库的目录。
打开widget.ui界面,拖动两个按钮到窗体上
分别命名为打开文件和保存文件,用于之后打开要渲染展示的文件和对文件编辑修改后的保存
从工具栏拖动一个scroll Area到窗体上,移动到两个按钮下方。
将scrollArea大小设置为800*800,将其中的widget设置为784*842(一张A4纸的分辨率通常是842×595,所以显示和插入图像时需要进行一下换算) ,用于显示PDF文件
添加一个继承于QWidget的类,用于渲染PDF中的单张页面
在PageWidget中,添加函数insertImg()和deleteQImage(),用于后续插入图片的显示和删除。添加函数SetPageNum()和GetPageNum()用于记录页码与获取页码。重载QWidget的mouseDoubleClickEvent(QMouseEvent *event)函数,用于在鼠标双击的位置获取坐标,插入图片
PageWidget.h:
- #ifndef PAGEWIDGET_H
- #define PAGEWIDGET_H
-
- #include
-
- class PageWidget : public QWidget
- {
- Q_OBJECT
- public:
- explicit PageWidget(QImage img,QWidget *parent = nullptr);
- void insertQImage(int x,int y,QPixmap img);
- void deleteQImage(int x,int y);
- void SetPageNum(int num){m_num=num;}
- int GetPageNum(){return m_num;}
-
- signals:
- void doubleclicked(int x,int y,int pages);
-
- protected:
- void mouseDoubleClickEvent(QMouseEvent *event) override;
-
- private:
- int m_num;
- };
-
- #endif // PAGEWIDGET_H
-
PageWidget.cpp
- PageWidget::PageWidget(QImage img,QWidget *parent) : QWidget(parent)
- {
- // 设置背景图片
- setAutoFillBackground(true); // 这句要加上, 否则可能显示不出背景图.
- QPalette palette = this->palette();
- palette.setBrush(QPalette::Window,QBrush(QPixmap::fromImage(img))); // 使用平滑的缩放方式
- this->setPalette(palette);
- }
-
- void PageWidget::insertQImage(int x, int y, QPixmap img)
- {
- qDebug()<<"insertQImage";
- QLabel *label = new QLabel(this);
- label->move(x,y);
- label->setPixmap(img);
- label->show();
- }
-
- void PageWidget::deleteQImage(int x, int y)
- {
- QWidget* wdt=this->childAt(x,y);
- if(!wdt)
- wdt->close();//delete wdt
- }
-
- void PageWidget::mouseDoubleClickEvent(QMouseEvent *event)
- {
- qDebug()<<"x:"<<event->x()<<"y"<<event->y();
- emit doubleclicked(event->x(),event->y(),m_num);
- }
进入widget.ui,右键单击之前创建的打开图片按钮,选择“转到槽”创建该按钮的槽函数。添加功能,在单击该按钮时,弹出一个文件选择窗口选择要打开的pdf文件
void Widget::on_button_Open_clicked()
{
QString fileName = QFileDialog::getOpenFileName(this, "open", "./", "document Files (*.pdf)");
if(!fileName.isEmpty())
renderPage(fileName);
}
创建一个渲染函数renderPage(QStinrg),用于渲染PDF文档到窗口上。
- void Widget::renderPage(QString filePath)
- {
- QString filename=filePath;
- Poppler::Document* document = Poppler::Document::load(filename);
- if(!document||document->isLocked())
- {
- delete document;
- QMessageBox::warning(this,"错误","获取PDF文档出错");
- return;
- }
- int PageNum = document->numPages();
-
- //计算窗体大小和比率
- Poppler::Page* pdfPageInit = document->page(0);
- if(pdfPageInit==0)
- {
- QMessageBox::warning(this,"错误","获取PDF文件页面出错");
- return;
- }
- QSize pageSize=pdfPageInit->pageSize();
- qDebug()<<"pagesize"<<pageSize.width()<<pageSize.height();
- int pdfwwidth=781;//
- double p_rate = ((double)pdfwwidth/(double)pageSize.width()) ;
- m_prate=p_rate;
- qDebug()<<p_rate<<pageSize.height()<<pageSize.width();
- ui->scrollAreaWidgetContents->setGeometry(0,0,pdfwwidth,pageSize.height()*p_rate);
- ui->scrollAreaWidgetContents->setFixedHeight(pageSize.height()*p_rate*PageNum);
- delete pdfPageInit;
-
- for(int i=0;i<PageNum;i++)
- {
- Poppler::Page* pdfPage = document->page(i);
- if(pdfPage==0)
- {
- QMessageBox::warning(this,"错误","获取PDF文件页面出错");
- return;
- }
-
- QImage image = pdfPage->renderToImage(72*p_rate,72*p_rate);
- PageWidget *widget=new PageWidget(image);
- widget->setGeometry(0,0,pdfwwidth,pageSize.height()*p_rate);
- widget->SetPageNum(i+1);
- vlayout->addWidget(widget);
- delete pdfPage;
- }
- delete document;
- }
效果:
再添加PDF编辑的部分:
创建一个PDFWriter类,添加初始化PDF文档函数int initPDFWriter(QString filename);和指定位置插入的函数 int insertImg(int page,int px,int py,int height,int width,QString imgpath);
pdfwriter.h
- #ifndef PDFWRITE_H
- #define PDFWRITE_H
-
- #include "PDFWriter.h"//
- #include "PDFPage.h"//
- #include "PageContentContext.h"//
- #include "PDFModifiedPage.h"
- #include
-
- class PDFWrite : public QObject
- {
- Q_OBJECT
- public:
- explicit PDFWrite(QObject *parent = nullptr);
- ~PDFWrite();
- int initPDFWriter(QString filename);
- int DeinitPDFWriter();
- int insertImg(int page,int px,int py,int height,int width,QString imgpath);
-
- signals:
-
- private:
- PDFWriter* pdfWriter;
-
- };
-
- #endif // PDFWRITE_H
pdfwriter.cpp
- #include "pdfwrite.h"
- #include <QDebug>
- PDFWrite::PDFWrite(QObject *parent) : QObject(parent)
- {
- pdfWriter=NULL;
- }
-
- PDFWrite::~PDFWrite()
- {
- DeinitPDFWriter();
- }
-
- int PDFWrite::initPDFWriter(QString filename)
- {
- if(!pdfWriter)
- pdfWriter=new PDFWriter;
- qDebug()<<filename<<filename.toLocal8Bit().data();
- int ret=pdfWriter->ModifyPDF(filename.toStdString(), ePDFVersion13, "");
- qDebug()<<ret;
- return 0;
- }
-
- int PDFWrite::DeinitPDFWriter()
- {
- if(pdfWriter)
- {
- pdfWriter->EndPDF();
- pdfWriter=NULL;
- }
- return 0;
- }
-
- int PDFWrite::insertImg(int page, int px, int py, int height, int width, QString imgpath)
- {
- if(!pdfWriter)
- return -1;
- qDebug()<<"create modifiedPage"<<"page"<<page;
- PDFModifiedPage modifiedPage(pdfWriter,page-1);
-
- AbstractContentContext* contentContext = modifiedPage.StartContentContext();
- AbstractContentContext::ImageOptions opt;
- opt.transformationMethod = AbstractContentContext::eFit;
- opt.boundingBoxHeight=height;
- opt.boundingBoxWidth=width;
- opt.fitProportional = true;
- qDebug()<<imgpath<<px<<py;
- contentContext->DrawImage(px,py,imgpath.toStdString(),opt);
- int ret=modifiedPage.WritePage();
- qDebug()<<"writepage:"<<ret;
- modifiedPage.EndContentContext();
-
- return 0;
- }
然后,将Pagewidget中双击页面与图片插入关联起来。
在widget中添加connect(widget,&PageWidget::doubleclicked,this,&Widget::pages_doublecliked);
添加pages_doublecliked()槽函数
- void Widget::pages_doublecliked(int x,int y,int page)
- {
- PageWidget *p_page = qobject_cast<PageWidget *>(sender());
- QString fileName = QFileDialog::getOpenFileName(this, tr("打开一个图像文件"), "./", tr("document Files (*.bmp *.png *.jpg)"));
- if(!fileName.isEmpty())
- {
- QImage img;
- if(img.load(fileName))
- {
- float rate=0.25;
- int scalew=img.width()*rate;
- int scaleh=img.height()*rate;//该值用于pdfwriter插入时限定大小,阅读器上显示还需再进行缩放
-
- QPixmap pix = QPixmap::fromImage(img).scaled(scalew*m_prate,scaleh*m_prate, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
- p_page->insertQImage(x,y,pix);
-
- if(p_pdfwrite!=NULL)
- {
- qDebug()<<"write img";
- p_pdfwrite->insertImg(p_page->GetPageNum(),(x/m_prate),((p_page->height()-(y+pix.height()))/m_prate))),scaleh,scalew,fileName);
- }
- }
- }
- }
这里要注意一点,PDF文件的坐标系和QT的坐标系是不同的,在PDF标准协议中,x,y是以左下角为原点的,而在QT中的坐标是以左上角为原点的。所以在插入图片时要稍微转换一下(由于时间和篇幅问题我这里只是简单转换了下)。
图片插入的演示(彦卿图片来源于网络,侵删):
由于时间和篇幅问题,未修改一些bug和完善其他功能,之后有时间再补上
其他待完善的扩展:
创建略缩图,点击略缩图则跳转到对应的页面
制作一个功能栏,添加保存(替换),另存为;添加在指定位置编辑文本的功能;添加页码跳转和打印功能,增加撤销、回撤功能
添加水印
单页面插入以及删除