QGraphicsScene导出为PDF

QGraphicsScene导出为PDF

完整例程

方案一:QPrinter + QPainter

cpp 复制代码
#include <QFileDialog>
#include <QPrinter> 
#include <QPainter>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QMessageBox>
#include <QPageSize>
#include <QDir>
#include <QDesktopServices>
#include <QScreen>

void exportSceneToPdf(QGraphicsView* view)
{
	if (!view) return;

	QGraphicsScene* scene = view->scene();
	if (!scene) return;

	// 1. 选择保存目录
	QString saveDir = QFileDialog::getExistingDirectory(nullptr,
		QObject::tr("选择保存目录"),
		QDir::currentPath());
	if (saveDir.isEmpty()) return;
	
	QString filePath = QDir(saveDir).filePath(QString("output_%1.pdf").arg(QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss")));

	// 2. 获取场景的矩形范围
	QRectF sceneRect = scene->sceneRect();   
	
	// 3. 配置打印机
	QPrinter printer(QPrinter::PrinterResolution);// PrinterResolution是 72 dpi。
	printer.setOutputFormat(QPrinter::PdfFormat);
	printer.setOutputFileName(filePath);
	/*
	PDF 内部用 1/72 inch = 1 point 作为坐标系。
	Qt的QPrinter / QPdfWriter  默认也是 72 dpi。
	*/
	//printer.setResolution(72);             // 必须 72,否则会缩放

	QPageSize pageSizePoint(QSizeF(sceneRect.width(), sceneRect.height()), QPageSize::Point);
	printer.setPageSize(pageSizePoint);
	printer.setPageMargins(QMarginsF(0, 0, 0, 0));   // 不留边

	// 4. 渲染
	QPainter painter(&printer);
	if (!painter.isActive()) {
		QMessageBox::warning(nullptr, QObject::tr("错误"),
			QObject::tr("无法创建 PDF 文件"));
		return;
	}

	// 场景左上角 (0,0) 对应 PDF 页面左上角
	scene->render(&painter,
		QRectF(0, 0, sceneRect.width(), sceneRect.height()),
		sceneRect);          // 源矩形
	painter.end();

	QMessageBox::information(nullptr, QObject::tr("成功"),
		QObject::tr("PDF 已保存至: %1").arg(filePath));

	QDesktopServices::openUrl(QUrl::fromLocalFile(filePath));
}

1. 功能概述

此函数将 QGraphicsView 中的场景内容(包括图形项、文本、表格等)导出为PDF文件。核心流程包括:

  1. 选择保存目录:用户通过对话框指定输出路径。
  2. 获取场景范围 :提取场景的矩形边界(sceneRect)。
  3. 配置PDF打印机:设置页面大小、边距、分辨率等参数。
  4. 渲染场景到PDF:将场景内容绘制到PDF页面。
  5. 反馈与自动打开:提示导出结果并自动打开生成的PDF文件。

2. 关键代码分析

(1) 保存路径与文件名
cpp 复制代码
QString saveDir = QFileDialog::getExistingDirectory(...);
QString filePath = QDir(saveDir).filePath("output_yyyyMMdd_hhmmss.pdf");
  • 动态文件名 :使用时间戳避免重复,格式为 output_20250723_0830.pdf
  • 目录选择QFileDialog 提供系统原生路径选择界面,提升用户体验。
(2) 场景范围获取
cpp 复制代码
QRectF sceneRect = scene->sceneRect();
  • 重要性sceneRect 决定PDF页面尺寸,确保内容完整显示。
  • 潜在风险 :若场景未显式设置 sceneRect,可能返回无效值(如 itemsBoundingRect() 动态计算),导致内容裁剪。
(3) 打印机配置
cpp 复制代码
QPrinter printer(QPrinter::PrinterResolution);// PrinterResolution是 72 dpi。
printer.setOutputFormat(QPrinter::PdfFormat);
printer.setOutputFileName(filePath);
/*
PDF 内部用 1/72 inch = 1 point 作为坐标系。
Qt的QPrinter / QPdfWriter  默认也是 72 dpi。
*/
//printer.setResolution(72);             // 必须 72,否则会缩放

QPageSize pageSizePoint(sceneRect.size(), QPageSize::Point);
printer.setPageSize(pageSizePoint);
printer.setPageMargins(QMarginsF(0, 0, 0, 0));
  • 分辨率设置:
    • 注释 printer.setResolution(72) ,因Qt默认使用 72 DPI(1点=1/72英寸),与PDF内部坐标系一致。
    • 若强制设置其他DPI,会导致内容缩放失真。
  • 页面尺寸与边距:
    • 直接按场景尺寸(单位:点)设置页面大小,确保1:1输出。
    • setPageMargins(0) 移除边距,避免空白区域占用内容空间。
(4) 渲染场景
cpp 复制代码
scene->render(&painter, 
    QRectF(0, 0, sceneRect.width(), sceneRect.height()), // 目标区域
    sceneRect // 源区域
);
  • 坐标系对齐 :源区域(sceneRect)映射到目标区域(PDF页面),保证左上角(0,0)对齐。
  • 渲染效率QGraphicsScene::render() 自动处理所有子项的绘制,支持复杂图形和文本。
(5) 结果反馈
cpp 复制代码
QMessageBox::information(...); // 成功提示
QDesktopServices::openUrl(...); // 自动打开PDF
  • 提示保存路径并用系统默认应用打开PDF。

3. 潜在问题与优化建议

(1) 场景矩形有效性
  • 问题 :若场景未初始化 sceneRect,可能返回无效值(如未添加任何图形项时为空矩形)。

  • 解决方案:

    cpp 复制代码
    if (sceneRect.isEmpty()) {
        sceneRect = scene->itemsBoundingRect(); // 动态计算所有项边界
    }
(2) 边距设置不生效
  • 问题 :某些系统下 setPageMargins(0) 可能因打印机驱动保留默认边距。
  • 解决方案 :添加 printer.setFullPage(true) 强制忽略驱动边距。
(3) 大场景导出性能
  • 问题:复杂场景(如万级图形项)渲染可能卡顿。
  • 优化方向:
    • 分页渲染 :将大场景分割为多个页面,通过循环调用 printer.newPage() 绘制。
    • 增量绘制 :仅渲染可视区域,但需调整 sceneRect 逻辑。
(4) 分辨率与缩放风险
  • 关键点 :避免主动设置 setResolution(),因Qt默认72 DPI与PDF点单位天然兼容,强制修改会导致坐标缩放。
(5) 错误处理增强
  • 当前逻辑 :仅检查 painter.isActive()

  • 扩展建议:

    cpp 复制代码
    if (!printer.setOutputFileName(filePath)) { // 检查文件可写性
        QMessageBox::warning("错误", "文件路径不可写");
    }

4. 替代方案对比

方法 优点 缺点
QPrinter 兼容Qt4/Qt5,支持打印预览 高级布局(如页眉页脚)需手动实现
QPdfWriter Qt5专属,更轻量,直接控制PDF属性 无打印预览功能,需完全手动绘制
第三方库 支持高级功能(表格、水印) 增加依赖和复杂度

当前函数选择 QPrinter 是平衡功能与复杂性的合理方案。


5. 总结

此函数实现了高效、准确的场景到PDF导出,核心优势包括:

  1. 精准坐标系匹配:基于72 DPI和点单位确保1:1输出。
  2. 用户交互友好:动态路径选择、时间戳命名、自动打开文件。
  3. 边距控制严谨 :通过 setPageMargins(0) + setFullPage(true) 避免留白。

推荐优化点

  • 增加 sceneRect 有效性校验和动态计算。
  • 补充文件可写性检查和分页渲染逻辑。
  • 强化错误处理分支(如打印机初始化失败)。

方案二:QPdfWriter + QPainter

注意:

QPdfWriter::setPageSize() 在 Qt 5.15 之前(包括 5.12 LTS)确实不会生效于自定义尺寸,原因不是 SizeMatchPolicy,而是 Qt 的 bug / 未实现 ------ 它只对预定义的 QPageSize::A4QPageSize::Letter 等枚举值有效,自定义 QPageSize(QSizeF, QPageSize::Millimeter) 会被 silently 忽略。

这是 Qt 官方 bug tracker 里已确认的问题(QTBUG-31443 Combine Multiple Images Into a Single PDF)。

如何判断你是否命中了该 bug

  • 现象:pdfWriter.setPageSize(customSize) 后,pdfWriter.width()/height() 仍是 210×297 mm(A4)。

目前Qt5.9.8的确有问题,Qt5.15.2已经没有问题了

cpp 复制代码
#include <QPdfWriter>
void exportGraphicsViewToPdf(QGraphicsView* graphicsView) {

	if (!graphicsView) return;

	QGraphicsScene* scene = graphicsView->scene();
	if (!scene) return;

	// 1.选择保存目录
	QString saveDir = QFileDialog::getExistingDirectory(nullptr, "选择保存目录", QDir::currentPath());
	if (saveDir.isEmpty()) return;
	QString filePath = QDir(saveDir).filePath(QString("output_%1.pdf").arg(QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss")));

	// 2. 获取场景的矩形范围
	QRectF sceneRect = scene->sceneRect();
	QPageSize pageSizePoint(QSizeF(sceneRect.width(), sceneRect.height()), QPageSize::Point);

	// 初始化PDF写入器
	QPdfWriter pdfWriter(filePath);
	pdfWriter.setPageSize(pageSizePoint);
	/*
	PDF 内部用 1/72 inch = 1 point 作为坐标系。
	Qt的QPrinter / QPdfWriter  默认也是 72 dpi。
	*/
	pdfWriter.setResolution(72); // 必须 72,否则会缩放

	pdfWriter.setPageMargins(QMarginsF(0, 0, 0, 0));

	// 渲染场景到PDF
	QPainter painter(&pdfWriter);        
	if (!painter.isActive()) return;

	scene->render(&painter,
		QRectF(0, 0, sceneRect.width(), sceneRect.height()),
		sceneRect);          // 源矩形
	painter.end();

	QMessageBox::information(nullptr, "成功", "PDF已保存至: " + filePath);
	QDesktopServices::openUrl(QUrl::fromLocalFile(filePath));
}

方案一缺陷

方案一:QPrinter + QPainter

其中左侧使用QGraphicsProxyWidget::setWidget(QLabel())失帧,左侧改用QGraphicsEllipseItem+QGraphicsTextItem

方案二:QPdfWriter + QPainter 没有出现上面的问题

相关推荐
小比卡丘5 分钟前
【C++进阶】第7课—红黑树
java·开发语言·c++
序属秋秋秋3 小时前
《C++初阶之STL》【vector容器:详解 + 实现】
开发语言·c++·笔记·学习·stl
lixzest6 小时前
快速梳理遗留项目
java·c++·python
郝学胜-神的一滴7 小时前
建造者模式:构建复杂对象的优雅方式
开发语言·c++·程序人生·建造者模式
啊我不会诶9 小时前
CF每日5题(1500-1600)
c++·学习·算法
程序员编程指南10 小时前
Qt容器类:QList、QMap等的高效使用
c语言·开发语言·c++·qt
点云SLAM10 小时前
C++中std::string和std::string_view使用详解和示例
开发语言·c++·算法·字符串·string·c++标准库算法·string_view
DY009J10 小时前
C++基础学习——文件操作详解
c++·学习·cocoa·visual studio code
原来是猿10 小时前
list 介绍 及 底层
数据结构·c++·list
程序员编程指南11 小时前
Qt 元对象系统(Meta-Object System)解析
c语言·开发语言·c++·qt