fix: v0.10.4 —— 导出改走 QPdfWriter,彻底绕开打印子系统

- 之前用 QPrinter 出 PDF:它会先初始化平台打印引擎(Windows 枚举打印机 /
  打印后台,Linux 走 CUPS),没有打印机或服务异常时就会卡住
- 改用 QPdfWriter + QTextDocument::print:纯写文件,不碰打印栈,三端一致
- 相应把 QtPrintSupport 从工程里去掉,二进制也小了一圈
- 文件对话框策略调整:Linux 强制 Qt 自带(GTK 原生在这台机器会卡),
  Windows / macOS 恢复系统原生对话框
- 端到端验证:从应用 UI 触发导出 → 生成 2 页 A4 PDF
- 版本 0.10.4
This commit is contained in:
2026-10-22 18:50:18 +08:00
parent 6723a4edea
commit 8aca7ade42
3 changed files with 39 additions and 22 deletions
+6 -5
View File
@@ -2,7 +2,7 @@
一个轻量的跨平台 Markdown 编辑器,基于 **Qt 5 / C++17** 构建。
当前版本 **0.10.3**。
当前版本 **0.10.4**。
## 界面
@@ -94,11 +94,12 @@
**文件 → 导出 PDF…**(`Ctrl+E`)直接弹保存对话框,选个位置就导出,没有中间预览。
A4 纸、16mm 页边距。
文件对话框一律用 Qt 自带的实现(`DontUseNativeDialog`),
不走系统原生选择器——GTK 原生对话框在部分桌面环境下会因为 dconf / gvfs
不可用而卡住,这也是之前「点导出就没反应」的原因。
Linux 上文件对话框强制用 Qt 自带的实现(`DontUseNativeDialog`):
GTK 原生选择器在 dconf / gvfs 不可用时会导致「点导出没反应」。
Windows / macOS 保留系统原生对话框。
导出用的是 Qt 自带的 `QPrinter` + `QTextDocument`,不需要额外的 PDF 库。
导出用的是 Qt 自带的 `QPdfWriter` + `QTextDocument`,不需要额外的 PDF 库,
也不经过任何平台的打印子系统(Windows 打印后台、CUPS),所以三端行为一致。
支持标题、列表(含任务列表)、引用、代码块、行内代码、粗体 / 斜体 / 删除线、
链接、表格;图片只输出 alt 文字。
+2 -2
View File
@@ -1,8 +1,8 @@
TEMPLATE = app
TARGET = VoiletMark
VERSION = 0.10.3
VERSION = 0.10.4
QT += core gui widgets printsupport
QT += core gui widgets
CONFIG += c++17 warn_on
+31 -15
View File
@@ -27,9 +27,9 @@
#include <QMessageBox>
#include <QPageLayout>
#include <QPageSize>
#include <QPdfWriter>
#include <QPixmap>
#include <QProcess>
#include <QPrinter>
#include <QPushButton>
#include <QSaveFile>
#include <QSettings>
@@ -52,6 +52,17 @@ namespace {
constexpr int kMaxRecentFiles = 8;
constexpr int kDefaultFontSize = 12;
// Linux 桌面上 Qt 会尝试 GTK 原生文件选择器,dconf / gvfs 不可用时它会卡住,
// 所以只在 Linux 上强制使用 Qt 自己的对话框;Windows / macOS 保留原生对话框。
QFileDialog::Options fileDialogOptions()
{
#if defined(Q_OS_LINUX)
return QFileDialog::DontUseNativeDialog;
#else
return QFileDialog::Options();
#endif
}
const char kWelcomeText[] = R"VOILETMARK(
# 欢迎使用 VoiletMark
@@ -613,7 +624,7 @@ void MainWindow::openFile()
const QString path = QFileDialog::getOpenFileName(
this, tr("打开 Markdown 文件"), startDirectory(),
tr("Markdown 文件 (*.md *.markdown *.mdown *.mkd);;文本文件 (*.txt);;所有文件 (*)"),
nullptr, QFileDialog::DontUseNativeDialog);
nullptr, fileDialogOptions());
if (path.isEmpty())
return;
openPath(path);
@@ -667,7 +678,7 @@ bool MainWindow::saveFileAs()
QString path = QFileDialog::getSaveFileName(
this, tr("另存为"), startDirectory(),
tr("Markdown 文件 (*.md);;文本文件 (*.txt);;所有文件 (*)"),
nullptr, QFileDialog::DontUseNativeDialog);
nullptr, fileDialogOptions());
if (path.isEmpty())
return false;
@@ -685,7 +696,7 @@ void MainWindow::exportPdf()
QString target = QFileDialog::getSaveFileName(
this, tr("导出 PDF"), QDir(startDirectory()).filePath(suggested),
tr("PDF 文件 (*.pdf)"), nullptr, QFileDialog::DontUseNativeDialog);
tr("PDF 文件 (*.pdf)"), nullptr, fileDialogOptions());
if (target.isEmpty())
return;
if (!target.endsWith(QStringLiteral(".pdf"), Qt::CaseInsensitive))
@@ -695,18 +706,23 @@ void MainWindow::exportPdf()
QTextDocument document;
MarkdownRenderer::render(m_editor->toPlainText(), &document);
document.setPageSize(QSizeF(672.76, 1001.57));
QPrinter printer(QPrinter::HighResolution);
printer.setOutputFormat(QPrinter::PdfFormat);
printer.setOutputFileName(target);
printer.setPageSize(QPageSize(QPageSize::A4));
printer.setPageMargins(QMarginsF(16, 16, 16, 16), QPageLayout::Millimeter);
printer.setDocName(QFileInfo(target).completeBaseName());
{
// 用 QPdfWriter 直接写 PDF,不碰任何平台的打印子系统:
// Windows 的打印后台、Linux 的 CUPS 在没有打印机或服务异常时都可能卡住。
QPdfWriter writer(target);
writer.setPageSize(QPageSize(QPageSize::A4));
writer.setPageMargins(QMarginsF(16, 16, 16, 16), QPageLayout::Millimeter);
writer.setTitle(QFileInfo(target).completeBaseName());
QApplication::setOverrideCursor(Qt::WaitCursor);
document.print(&printer);
QApplication::restoreOverrideCursor();
const QRectF paintRect = writer.pageLayout().paintRectPixels(writer.resolution());
const qreal scale = 96.0 / writer.resolution();
document.setPageSize(QSizeF(paintRect.width() * scale, paintRect.height() * scale));
QApplication::setOverrideCursor(Qt::WaitCursor);
document.print(&writer);
QApplication::restoreOverrideCursor();
}
if (!QFileInfo::exists(target) || QFileInfo(target).size() <= 0) {
QMessageBox::warning(this, tr("导出失败"),
@@ -979,7 +995,7 @@ void MainWindow::chooseFolder()
{
const QString directory = QFileDialog::getExistingDirectory(
this, tr("打开文件夹"), startDirectory(),
QFileDialog::ShowDirsOnly | QFileDialog::DontUseNativeDialog);
QFileDialog::ShowDirsOnly | fileDialogOptions());
if (!directory.isEmpty())
openFolder(directory);
}