QGraphicsScene导出为PDF
QGraphicsScene导出为PDF
完整例程
方案一:QPrinter + QPainter
#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文件。核心流程包括:
- 选择保存目录:用户通过对话框指定输出路径。
- 获取场景范围:提取场景的矩形边界(
sceneRect
)。 - 配置PDF打印机:设置页面大小、边距、分辨率等参数。
- 渲染场景到PDF:将场景内容绘制到PDF页面。
- 反馈与自动打开:提示导出结果并自动打开生成的PDF文件。
2. 关键代码分析
(1) 保存路径与文件名
QString saveDir = QFileDialog::getExistingDirectory(...);
QString filePath = QDir(saveDir).filePath("output_yyyyMMdd_hhmmss.pdf");
- 动态文件名:使用时间戳避免重复,格式为
output_20250723_0830.pdf
。 - 目录选择:
QFileDialog
提供系统原生路径选择界面,提升用户体验。
(2) 场景范围获取
QRectF sceneRect = scene->sceneRect();
- 重要性:
sceneRect
决定PDF页面尺寸,确保内容完整显示。 - 潜在风险:若场景未显式设置
sceneRect
,可能返回无效值(如itemsBoundingRect()
动态计算),导致内容裁剪。
(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(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) 渲染场景
scene->render(&painter, QRectF(0, 0, sceneRect.width(), sceneRect.height()), // 目标区域sceneRect // 源区域
);
- 坐标系对齐:源区域(
sceneRect
)映射到目标区域(PDF页面),保证左上角(0,0)对齐。 - 渲染效率:
QGraphicsScene::render()
自动处理所有子项的绘制,支持复杂图形和文本。
(5) 结果反馈
QMessageBox::information(...); // 成功提示
QDesktopServices::openUrl(...); // 自动打开PDF
- 提示保存路径并用系统默认应用打开PDF。
3. 潜在问题与优化建议
(1) 场景矩形有效性
-
问题:若场景未初始化
sceneRect
,可能返回无效值(如未添加任何图形项时为空矩形)。 -
解决方案:
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()
。 -
扩展建议:
if (!printer.setOutputFileName(filePath)) { // 检查文件可写性QMessageBox::warning("错误", "文件路径不可写"); }
4. 替代方案对比
方法 | 优点 | 缺点 |
---|---|---|
QPrinter | 兼容Qt4/Qt5,支持打印预览 | 高级布局(如页眉页脚)需手动实现 |
QPdfWriter | Qt5专属,更轻量,直接控制PDF属性 | 无打印预览功能,需完全手动绘制 |
第三方库 | 支持高级功能(表格、水印) | 增加依赖和复杂度 |
当前函数选择
QPrinter
是平衡功能与复杂性的合理方案。
5. 总结
此函数实现了高效、准确的场景到PDF导出,核心优势包括:
- 精准坐标系匹配:基于72 DPI和点单位确保1:1输出。
- 用户交互友好:动态路径选择、时间戳命名、自动打开文件。
- 边距控制严谨:通过
setPageMargins(0)
+setFullPage(true)
避免留白。
推荐优化点:
- 增加
sceneRect
有效性校验和动态计算。 - 补充文件可写性检查和分页渲染逻辑。
- 强化错误处理分支(如打印机初始化失败)。
方案二:QPdfWriter + QPainter
注意:
QPdfWriter::setPageSize()
在 Qt 5.15 之前(包括 5.12 LTS)确实不会生效于自定义尺寸,原因不是 SizeMatchPolicy
,而是 Qt 的 bug / 未实现 —— 它只对预定义的 QPageSize::A4
、QPageSize::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已经没有问题了
#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));// 渲染场景到PDFQPainter 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 没有出现上面的问题