Qt中使用QPdfWriter类结合QPainter类绘制并输出PDF文件

一.类的介绍

1.QPdfWriter介绍

Qt中提供了一个直接可以处理PDF的类,这就是QPdfWriter类。
(1)PDF文件生成

支持创建新的PDF文件或覆盖已有文件,通过构造函数直接绑定文件路径或QFile对象;

默认生成矢量图形PDF,支持高分辨率输出(可设置DPI);

2)页面属性配置

页面方向:通过setPageOrientation(QPageLayout::Orientation)设置纵向(Portrait)或横向(Landscape);

页面尺寸:使用setPageSize(QPageSize::A4)定义纸张大小,支持ISO标准尺寸(如A4、A3);

页边距:通过setPageMargins()调整内容区域与页边的距离;
(3)内容绘制

与QPainter深度集成,支持所有标准绘图操作:

图形:线段、矩形、椭圆、多边形等;

文本:多字体样式、对齐方式、旋转文字;

图像:支持PNG、JPG、SVG等格式的嵌入;
(4)多页面管理

通过QPainter::begin()和QPainter::end()控制绘制流程;

使用QPrinter::newPage()或手动分页逻辑实现多页文档;

2.QPainter介绍

(1)QPainter类功能

QPainter是Qt框架中用于2D图形绘制的核心类,提供高度优化的绘图功能,支持在QWidget、QImage、QPixmap、QPrinter等设备上进行绘制。其主要特性包括:

  • 支持矢量图形(直线/曲线/几何图形)和位图操作;
  • 提供坐标变换、复合模式、抗锯齿等高级特性;
  • 集成字体渲染、图像合成等专业级功能;
  • 必须通过paintEvent()事件或在继承自QPaintDevice的类中使用;
    (2)QPainter类接口
  1. 基础绘图操作
  2. 文本与图像处理
  3. 状态控制与高级特性

    QPainter类还有很多接口函数,尤其是跟绘制有关的,很多重载的接口方便不同情况的使用,具体可以参考官网的介绍QPainter类

二.开发生成PDF文件

下面开始用上文中的两个类来封装一个专门用来绘制PDF文件的类。

1.使用前要注意:

  • 坐标系系统:PDF坐标系原点在页面左上角,Y轴向下延伸,X轴向右延伸;
  • 单位换算:使用QPageLayout::Millimeter设置毫米单位,绘制时默认使用像素单位;
  • 图像缩放:推荐使用QRect参数控制图片显示尺寸,避免直接缩放;
  • 字体嵌入:中文字体需通过QFontDatabase加载系统字体;
  • 多页处理:通过QPdfWriter的newPage()创建新页面;

2.绘制流程梳理

  • 想要操作PDF文件,首先得有个文件,使用QFile的对象指向文件,然后创建QPdfWriter类的对象,并将QPdfWriter绑定在该文件上,然后用QPdfWriter对象设定PDF的一些参数,比如DPI,绘制页面大小等。
  • 其次,想要绘制得打开文件,调用QFile对象打开绑定的pdf格式的文件;
  • 再次,创建QPainter类对象,用该对象的各个规制接口来绘制各种图形文字等,如果你要设计绘制的接口很多的话,这里其实是最耗时的;
  • 最后,正确的释放资源,关闭文件;

3.代码说明

现在实操

  • QtCreator上创建一个简单的应用程序项目,先编译下,确保原始项目没问题;
  • 在程序界面上添加一个按钮,命名"btCreatePdf",连接好按钮对应的点击信号槽;
  • 添加新的C++类,继承自QObject,类命名"PdfGenerator",这就是我们准备开发的一个专门操作PDF的自定义类,也就是我们所有对PDF的操作都在这个类里边完成;
  • "PdfGenerator"类的设计开发,包含QPdfWriter,QPainter,QFont,QImage,QPageSize, QFile等类;创建QPdfWriter类的对象,QPainter类的对象,QFile类的对象;调用这些对象的接口实现PDF的绘制。
  • 最后,在主程序中包含上面自定义类,在界面按钮"btCreatePdf"中调用其实现PDF绘制。
    不多说,直接上代码:

PdfGenerator 类的头文件:

cpp 复制代码
// pdfgenerator.h 
#include <QObject>
#include <QPdfWriter>
#include <QPainter>
#include <QFont>
#include <QImage>
#include <QPageSize>
#include <QFile>

class PdfGenerator : public QObject {
    Q_OBJECT
public:
    explicit PdfGenerator(const QString &fileName, QPagedPaintDevice::PageSize size = QPagedPaintDevice::PageSize::A4);
    ~PdfGenerator();
 
    // 基础设置 
    void setMargins(qreal left, qreal top, qreal right, qreal bottom);
    void setResolution(int dpi);
    void newPage();
	bool beginPage();
	bool endPage();
 
// 绘制接口
    //绘制线段
    void drawLine(const QPointF &start, const QPointF &end, const QColor &color, qreal width);
    //绘制文字
    void drawText(const QRectF &rect, const QString &text, const QFont &font, const QColor &color, Qt::Alignment align);
    //绘制图片
    void drawImage(const QRectF &rect, const QString &imagePath, bool keepAspectRatio);
    //绘制矩形
    void drawRect(const QRectF &rect, const QColor &fillColor, const QColor &borderColor, qreal borderWidth);
    //绘制椭圆
    void drawEllipse(const QRectF &rect, const QColor &fillColor, const QColor &borderColor, qreal borderWidth);
   //想设计其他绘制接口继续往下加	
  
private:
    QPdfWriter *m_writer = nullptr;
    QPainter *m_painter = nullptr;
    QRect m_pageRect;
    QFile m_pdfFile;
};

PdfGenerator 类的cpp文件

cpp 复制代码
#include "PdfGenerator.h"
#include <QtDebug>
// pdfgenerator.cpp  
PdfGenerator::PdfGenerator(const QString &fileName, QPagedPaintDevice::PageSize size)
{
    m_pdfFile.setFileName(fileName);
    m_writer = new QPdfWriter(&m_pdfFile);
    m_writer->setPageSize(size);
    m_writer->setResolution(300);
    m_writer->setPageMargins(QMarginsF(20, 20, 20, 20), QPageLayout::Millimeter);
    m_pageRect = m_writer->pageLayout().paintRectPixels(m_writer->resolution());
    // 计算可绘制区域 
    m_pageRect = QRect(0, 0, m_writer->width(), m_writer->height());
    if(!m_pdfFile.open(QIODevice::WriteOnly))
        return ;

}
 
PdfGenerator::~PdfGenerator()
{
    if (m_painter->isActive())
    {
        m_painter->end();
    }
    delete m_painter;
    delete m_writer;
}
 
void PdfGenerator::setMargins(qreal left, qreal top, qreal right, qreal bottom)
{
    m_writer->setPageMargins(QMarginsF(left, top, right, bottom), QPageLayout::Millimeter);
    m_pageRect = m_writer->pageLayout().paintRectPixels(m_writer->resolution()); // 更新绘制区域[5]()
}
 
void PdfGenerator::newPage()
{
     // 创建新页
    m_writer->newPage();
}

 bool PdfGenerator::beginPage()
 {
     bool bRet = false;
     if(nullptr == m_painter)
     {
        m_painter = new QPainter(m_writer);
     }
     //启用抗锯齿
     m_painter->setRenderHint(QPainter::Antialiasing);
    if (nullptr != m_painter)
    {
        m_painter->begin(m_writer);
        //m_painter->reset(new  QPainter(m_writer.data()));
        bRet = m_painter->isActive();
    }
    qDebug() << "beginPage bRet is " << bRet;
    return bRet;
}
 
bool PdfGenerator::endPage() {

    if (m_painter && m_painter->isActive())
    {
        m_painter->end();
        m_writer->deleteLater();
        m_pdfFile.close();
        return true;
    }
    m_pdfFile.close();
    return false;
}

// 绘制线段 
void PdfGenerator::drawLine(const QPointF &start, const QPointF &end, const QColor &color, qreal width)
{
    if (!m_painter->isActive())
		return;
 
    m_painter->save();
    m_painter->setPen(QPen(color, width));
    m_painter->drawLine(start, end);
    m_painter->restore();
}
 
// 绘制文本(支持对齐)
void PdfGenerator::drawText(const QRectF &rect, const QString &text, const QFont &font, const QColor &color, Qt::Alignment align)
{
    if (!m_painter->isActive()) 
    	return;
 
    m_painter->save();
    m_painter->setFont(font);
    m_painter->setPen(color);
    m_painter->drawText(rect, static_cast<int>(align), text);
    m_painter->restore();
}
 
// 绘制图片(自动缩放)
void PdfGenerator::drawImage(const QRectF &rect, const QString &imagePath, bool keepAspectRatio)
{
    if (!m_painter->isActive())
    	return;
 
    QPixmap pixmap(imagePath);
    if (pixmap.isNull())  return;
 
    QRectF targetRect = rect;
    if (keepAspectRatio) {
        QSizeF scaled = pixmap.size().scaled(rect.size().toSize(),  Qt::KeepAspectRatio);
        targetRect.setSize(scaled); 
    }
 
    m_painter->drawPixmap(targetRect, pixmap, pixmap.rect()); 
}
 
// 绘制矩形(支持填充)
void PdfGenerator::drawRect(const QRectF &rect, const QColor &fillColor, const QColor &borderColor, qreal borderWidth)
{
	if (!m_painter->isActive())
		return;
 
    m_painter->save();
    m_painter->setBrush(QBrush(fillColor));
    m_painter->setPen(QPen(borderColor, borderWidth));
    m_painter->drawRect(rect);
    m_painter->restore();
}
 
// 绘制椭圆 
void PdfGenerator::drawEllipse(const QRectF &rect, const QColor &fillColor, const QColor &borderColor, qreal borderWidth)
{
    if (!m_painter->isActive()) 
    	return;
    m_painter->save();
    m_painter->setBrush(QBrush(fillColor));
    m_painter->setPen(QPen(borderColor, borderWidth));
    m_painter->drawEllipse(rect);
    m_painter->restore();
}

主程序按钮调用PdfGenerator类绘制PDF

cpp 复制代码
//创建Pdf
void MainWindow::on_btCreatePdf_clicked()
{
    qDebug() << "into on_btCreatePdf_clicked";
    PdfGenerator doc("E:/test/output.pdf");
    if (doc.beginPage())
    {
        qDebug() << "beginPage success";
        // 绘制灰色线段
        doc.drawLine(QPointF(20,  60), QPointF(150, 200), Qt::lightGray, 2.0);

        // 添加图片(保持比例),例子的资源里没有添加这张图片,所以下面PDF里没有绘制出来图片
        doc.drawImage(QRectF(100,  100, 100, 100), "logo.png", false);

        // 绘制蓝色文字
        QFont font("Arial", 12, QFont::Bold);
        doc.drawText(QRectF(70,  270, 270, 50), "Hello PDF!", font, Qt::blue, Qt::AlignVCenter | Qt::AlignHCenter);

        // 绘制绿色填充矩形
        doc.drawRect(QRectF(300,  150, 100, 40), Qt::green, Qt::black, 1.5);

        // 绘制黄色边框椭圆
        doc.drawEllipse(QRectF(250,  200, 100, 80), Qt::transparent, Qt::yellow, 2.0);

        doc.endPage();
    }

}

执行后,展示结果:

以此自定义PdfGenerator类作为基础,后续可以根据QPainter类本身带有的各种图形绘制功能,封装你想做的绘制接口,实际项目应用中,就以你封装的接口进行各种布局绘制操作,来完成项目要求。

相关推荐
忆源2 小时前
【Qt】之音视频编程1:QtAV的背景和安装篇
开发语言·qt·音视频
apcipot_rain3 小时前
【应用密码学】实验五 公钥密码2——ECC
前端·数据库·python
辛一一5 小时前
neo4j图数据库基本概念和向量使用
数据库·neo4j
巨龙之路6 小时前
什么是时序数据库?
数据库·时序数据库
蔡蓝7 小时前
binlog日志以及MySQL的数据同步
数据库·mysql
teacher伟大光荣且正确7 小时前
Qt Creator 配置 Android 编译环境
android·开发语言·qt
是店小二呀7 小时前
【金仓数据库征文】金融行业中的国产化数据库替代应用实践
数据库·金融·数据库平替用金仓·金仓数据库2025征文
炒空心菜菜8 小时前
SparkSQL 连接 MySQL 并添加新数据:实战指南
大数据·开发语言·数据库·后端·mysql·spark
多多*8 小时前
算法竞赛相关 Java 二分模版
java·开发语言·数据结构·数据库·sql·算法·oracle
爱喝酸奶的桃酥8 小时前
MYSQL数据库集群高可用和数据监控平台
java·数据库·mysql