【QT】C++编辑修改PDF文件,C++渲染显示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,我们来看一下官方示例的代码:

加载一个指定的文档:

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的时候,会根据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:

#ifndef PAGEWIDGET_H
#define PAGEWIDGET_H

#include <QWidget>

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 <QObject>

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和完善其他功能,之后有时间再补上

其他待完善的扩展:

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

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

添加水印

单页面插入以及删除

  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值