• 【QT】不使用QT6PDF模块,200行代码编写一个可编辑PDF的阅读器


    QT虽然有PDF模块,但是需要5.15以及6之后的版本,并且只支持加载文档进行阅览,不支持修改PDF文件。

    本篇博客使用QT5,不使用QT的PDF模块,通过两百行代码编写一个支持阅览与任意位置插入图片的PDF编辑器,并且还可以拓展其他功能

    一、准备工作

    PDF文档的显示:我们使用poppler这个pdf库,poppler是一个用于渲染pdf文件的开源库,遵循GPL协议。

    对于qt,poppler专门有一个适配的库:poppler-qt

    poppler下载链接:Poppler

    关于qt使用poppler,我们来看一下官方示例的代码:

    加载一个指定的文档:

    1. QString filename;
    2. Poppler::Document* document = Poppler::Document::load(filename);
    3. if (!document || document->isLocked()) {
    4. // ... error message ....
    5. delete document;
    6. return;
    7. }

    函数说明:

    Poppler::Document::load(QStirng);

    返回一个从磁盘上的文件加载了指定文档的document对象。

    另外还有从数据流加载PDF的函数

    static Document * Poppler::Document::loadFromData ( const QByteArray & fileContents, const QByteArray & ownerPassword = QByteArray(), const QByteArray & userPassword = QByteArray() )

    将文档中的某个页面,渲染到QImage上:

    1. // Paranoid safety check
    2. if (document == 0) {
    3. // ... error message ...
    4. return;
    5. }
    6. // Access page of the PDF file
    7. Poppler::Page* pdfPage = document->page(pageNumber); // Document starts at page 0
    8. if (pdfPage == 0) {
    9. // ... error message ...
    10. return;
    11. }
    12. // Generate a QImage of the rendered page
    13. QImage image = pdfPage->renderToImage(xres, yres, x, y, width, height);
    14. if (image.isNull()) {
    15. // ... error message ...
    16. return;
    17. }
    18. // ... use image ...
    19. // after the usage, the page must be deleted
    20. 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的时候,会根据Xresyres中指定的水平和垂直分辨率自动计算图像的大小

    所有操作完毕,记得释放文档

    delete document;

    编辑PDF文档:使用PDF-Writer来编辑PDF文档

    PDF-Writer的github链接:https://github.com/galkahana/PDF-Writer,原名好像是pdfhummus,遵循Apache-2.0 license协议。 wiki上的文档很丰富很详细,编译过程都有说明,这里就不详述了

    对于这个库的一个小测试也可以看我这个博文C++编辑修改PDF-CSDN博客

    二、使用QT编写PDF编辑器

    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:

    1. #ifndef PAGEWIDGET_H
    2. #define PAGEWIDGET_H
    3. #include
    4. class PageWidget : public QWidget
    5. {
    6. Q_OBJECT
    7. public:
    8. explicit PageWidget(QImage img,QWidget *parent = nullptr);
    9. void insertQImage(int x,int y,QPixmap img);
    10. void deleteQImage(int x,int y);
    11. void SetPageNum(int num){m_num=num;}
    12. int GetPageNum(){return m_num;}
    13. signals:
    14. void doubleclicked(int x,int y,int pages);
    15. protected:
    16. void mouseDoubleClickEvent(QMouseEvent *event) override;
    17. private:
    18. int m_num;
    19. };
    20. #endif // PAGEWIDGET_H

    PageWidget.cpp

    1. PageWidget::PageWidget(QImage img,QWidget *parent) : QWidget(parent)
    2. {
    3. // 设置背景图片
    4. setAutoFillBackground(true); // 这句要加上, 否则可能显示不出背景图.
    5. QPalette palette = this->palette();
    6. palette.setBrush(QPalette::Window,QBrush(QPixmap::fromImage(img))); // 使用平滑的缩放方式
    7. this->setPalette(palette);
    8. }
    9. void PageWidget::insertQImage(int x, int y, QPixmap img)
    10. {
    11. qDebug()<<"insertQImage";
    12. QLabel *label = new QLabel(this);
    13. label->move(x,y);
    14. label->setPixmap(img);
    15. label->show();
    16. }
    17. void PageWidget::deleteQImage(int x, int y)
    18. {
    19. QWidget* wdt=this->childAt(x,y);
    20. if(!wdt)
    21. wdt->close();//delete wdt
    22. }
    23. void PageWidget::mouseDoubleClickEvent(QMouseEvent *event)
    24. {
    25. qDebug()<<"x:"<<event->x()<<"y"<<event->y();
    26. emit doubleclicked(event->x(),event->y(),m_num);
    27. }

    进入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文档到窗口上。

    1. void Widget::renderPage(QString filePath)
    2. {
    3. QString filename=filePath;
    4. Poppler::Document* document = Poppler::Document::load(filename);
    5. if(!document||document->isLocked())
    6. {
    7. delete document;
    8. QMessageBox::warning(this,"错误","获取PDF文档出错");
    9. return;
    10. }
    11. int PageNum = document->numPages();
    12. //计算窗体大小和比率
    13. Poppler::Page* pdfPageInit = document->page(0);
    14. if(pdfPageInit==0)
    15. {
    16. QMessageBox::warning(this,"错误","获取PDF文件页面出错");
    17. return;
    18. }
    19. QSize pageSize=pdfPageInit->pageSize();
    20. qDebug()<<"pagesize"<<pageSize.width()<<pageSize.height();
    21. int pdfwwidth=781;//
    22. double p_rate = ((double)pdfwwidth/(double)pageSize.width()) ;
    23. m_prate=p_rate;
    24. qDebug()<<p_rate<<pageSize.height()<<pageSize.width();
    25. ui->scrollAreaWidgetContents->setGeometry(0,0,pdfwwidth,pageSize.height()*p_rate);
    26. ui->scrollAreaWidgetContents->setFixedHeight(pageSize.height()*p_rate*PageNum);
    27. delete pdfPageInit;
    28. for(int i=0;i<PageNum;i++)
    29. {
    30. Poppler::Page* pdfPage = document->page(i);
    31. if(pdfPage==0)
    32. {
    33. QMessageBox::warning(this,"错误","获取PDF文件页面出错");
    34. return;
    35. }
    36. QImage image = pdfPage->renderToImage(72*p_rate,72*p_rate);
    37. PageWidget *widget=new PageWidget(image);
    38. widget->setGeometry(0,0,pdfwwidth,pageSize.height()*p_rate);
    39. widget->SetPageNum(i+1);
    40. vlayout->addWidget(widget);
    41. delete pdfPage;
    42. }
    43. delete document;
    44. }

    效果:

    再添加PDF编辑的部分:

    创建一个PDFWriter类,添加初始化PDF文档函数int initPDFWriter(QString filename);和指定位置插入的函数 int insertImg(int page,int px,int py,int height,int width,QString imgpath);

    pdfwriter.h

    1. #ifndef PDFWRITE_H
    2. #define PDFWRITE_H
    3. #include "PDFWriter.h"//
    4. #include "PDFPage.h"//
    5. #include "PageContentContext.h"//
    6. #include "PDFModifiedPage.h"
    7. #include
    8. class PDFWrite : public QObject
    9. {
    10. Q_OBJECT
    11. public:
    12. explicit PDFWrite(QObject *parent = nullptr);
    13. ~PDFWrite();
    14. int initPDFWriter(QString filename);
    15. int DeinitPDFWriter();
    16. int insertImg(int page,int px,int py,int height,int width,QString imgpath);
    17. signals:
    18. private:
    19. PDFWriter* pdfWriter;
    20. };
    21. #endif // PDFWRITE_H

    pdfwriter.cpp

    1. #include "pdfwrite.h"
    2. #include <QDebug>
    3. PDFWrite::PDFWrite(QObject *parent) : QObject(parent)
    4. {
    5. pdfWriter=NULL;
    6. }
    7. PDFWrite::~PDFWrite()
    8. {
    9. DeinitPDFWriter();
    10. }
    11. int PDFWrite::initPDFWriter(QString filename)
    12. {
    13. if(!pdfWriter)
    14. pdfWriter=new PDFWriter;
    15. qDebug()<<filename<<filename.toLocal8Bit().data();
    16. int ret=pdfWriter->ModifyPDF(filename.toStdString(), ePDFVersion13, "");
    17. qDebug()<<ret;
    18. return 0;
    19. }
    20. int PDFWrite::DeinitPDFWriter()
    21. {
    22. if(pdfWriter)
    23. {
    24. pdfWriter->EndPDF();
    25. pdfWriter=NULL;
    26. }
    27. return 0;
    28. }
    29. int PDFWrite::insertImg(int page, int px, int py, int height, int width, QString imgpath)
    30. {
    31. if(!pdfWriter)
    32. return -1;
    33. qDebug()<<"create modifiedPage"<<"page"<<page;
    34. PDFModifiedPage modifiedPage(pdfWriter,page-1);
    35. AbstractContentContext* contentContext = modifiedPage.StartContentContext();
    36. AbstractContentContext::ImageOptions opt;
    37. opt.transformationMethod = AbstractContentContext::eFit;
    38. opt.boundingBoxHeight=height;
    39. opt.boundingBoxWidth=width;
    40. opt.fitProportional = true;
    41. qDebug()<<imgpath<<px<<py;
    42. contentContext->DrawImage(px,py,imgpath.toStdString(),opt);
    43. int ret=modifiedPage.WritePage();
    44. qDebug()<<"writepage:"<<ret;
    45. modifiedPage.EndContentContext();
    46. return 0;
    47. }

    然后,将Pagewidget中双击页面与图片插入关联起来。

    在widget中添加connect(widget,&PageWidget::doubleclicked,this,&Widget::pages_doublecliked);

    添加pages_doublecliked()槽函数

    1. void Widget::pages_doublecliked(int x,int y,int page)
    2. {
    3. PageWidget *p_page = qobject_cast<PageWidget *>(sender());
    4. QString fileName = QFileDialog::getOpenFileName(this, tr("打开一个图像文件"), "./", tr("document Files (*.bmp *.png *.jpg)"));
    5. if(!fileName.isEmpty())
    6. {
    7. QImage img;
    8. if(img.load(fileName))
    9. {
    10. float rate=0.25;
    11. int scalew=img.width()*rate;
    12. int scaleh=img.height()*rate;//该值用于pdfwriter插入时限定大小,阅读器上显示还需再进行缩放
    13. QPixmap pix = QPixmap::fromImage(img).scaled(scalew*m_prate,scaleh*m_prate, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
    14. p_page->insertQImage(x,y,pix);
    15. if(p_pdfwrite!=NULL)
    16. {
    17. qDebug()<<"write img";
    18. p_pdfwrite->insertImg(p_page->GetPageNum(),(x/m_prate),((p_page->height()-(y+pix.height()))/m_prate))),scaleh,scalew,fileName);
    19. }
    20. }
    21. }
    22. }

    这里要注意一点,PDF文件的坐标系和QT的坐标系是不同的,在PDF标准协议中,x,y是以左下角为原点的,而在QT中的坐标是以左上角为原点的。所以在插入图片时要稍微转换一下(由于时间和篇幅问题我这里只是简单转换了下)。

    图片插入的演示(彦卿图片来源于网络,侵删):

    三、待完善

    由于时间和篇幅问题,未修改一些bug和完善其他功能,之后有时间再补上

    其他待完善的扩展:

    创建略缩图,点击略缩图则跳转到对应的页面

    制作一个功能栏,添加保存(替换),另存为;添加在指定位置编辑文本的功能;添加页码跳转和打印功能,增加撤销、回撤功能

    添加水印

    单页面插入以及删除

  • 相关阅读:
    【infiniband】用udaddy测试RDMA_CM API通过GID连接
    自制宏正(ATEN)KVM CS1708i固件升级线
    基于静电放电算法优化概率神经网络PNN的分类预测 - 附代码
    【无标题】
    vite下,修改node_modules源码后,在浏览器源码中看不到改动的内容
    Dubbo的前世今生
    Linux内核源码分析 (B.3) 深入理解 Linux 物理内存分配全链路实现
    数据结构-ArrayList解析和实现代码
    物联网开发笔记(45)- 使用Micropython开发ESP32开发板之控制红外传感器
    重新定义分析 - EventBridge 实时事件分析平台发布
  • 原文地址:https://blog.csdn.net/Flywithdawn/article/details/134017965