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:
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
一个轻量的跨平台 Markdown 编辑器,基于 **Qt 5 / C++17** 构建。
|
一个轻量的跨平台 Markdown 编辑器,基于 **Qt 5 / C++17** 构建。
|
||||||
|
|
||||||
当前版本 **0.10.3**。
|
当前版本 **0.10.4**。
|
||||||
|
|
||||||
## 界面
|
## 界面
|
||||||
|
|
||||||
@@ -94,11 +94,12 @@
|
|||||||
**文件 → 导出 PDF…**(`Ctrl+E`)直接弹保存对话框,选个位置就导出,没有中间预览。
|
**文件 → 导出 PDF…**(`Ctrl+E`)直接弹保存对话框,选个位置就导出,没有中间预览。
|
||||||
A4 纸、16mm 页边距。
|
A4 纸、16mm 页边距。
|
||||||
|
|
||||||
文件对话框一律用 Qt 自带的实现(`DontUseNativeDialog`),
|
Linux 上文件对话框强制用 Qt 自带的实现(`DontUseNativeDialog`):
|
||||||
不走系统原生选择器——GTK 原生对话框在部分桌面环境下会因为 dconf / gvfs
|
GTK 原生选择器在 dconf / gvfs 不可用时会导致「点导出没反应」。
|
||||||
不可用而卡住,这也是之前「点导出就没反应」的原因。
|
Windows / macOS 保留系统原生对话框。
|
||||||
|
|
||||||
导出用的是 Qt 自带的 `QPrinter` + `QTextDocument`,不需要额外的 PDF 库。
|
导出用的是 Qt 自带的 `QPdfWriter` + `QTextDocument`,不需要额外的 PDF 库,
|
||||||
|
也不经过任何平台的打印子系统(Windows 打印后台、CUPS),所以三端行为一致。
|
||||||
支持标题、列表(含任务列表)、引用、代码块、行内代码、粗体 / 斜体 / 删除线、
|
支持标题、列表(含任务列表)、引用、代码块、行内代码、粗体 / 斜体 / 删除线、
|
||||||
链接、表格;图片只输出 alt 文字。
|
链接、表格;图片只输出 alt 文字。
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
TEMPLATE = app
|
TEMPLATE = app
|
||||||
TARGET = VoiletMark
|
TARGET = VoiletMark
|
||||||
VERSION = 0.10.3
|
VERSION = 0.10.4
|
||||||
|
|
||||||
QT += core gui widgets printsupport
|
QT += core gui widgets
|
||||||
|
|
||||||
CONFIG += c++17 warn_on
|
CONFIG += c++17 warn_on
|
||||||
|
|
||||||
|
|||||||
+29
-13
@@ -27,9 +27,9 @@
|
|||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QPageLayout>
|
#include <QPageLayout>
|
||||||
#include <QPageSize>
|
#include <QPageSize>
|
||||||
|
#include <QPdfWriter>
|
||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
#include <QPrinter>
|
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QSaveFile>
|
#include <QSaveFile>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
@@ -52,6 +52,17 @@ namespace {
|
|||||||
constexpr int kMaxRecentFiles = 8;
|
constexpr int kMaxRecentFiles = 8;
|
||||||
constexpr int kDefaultFontSize = 12;
|
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(
|
const char kWelcomeText[] = R"VOILETMARK(
|
||||||
# 欢迎使用 VoiletMark
|
# 欢迎使用 VoiletMark
|
||||||
|
|
||||||
@@ -613,7 +624,7 @@ void MainWindow::openFile()
|
|||||||
const QString path = QFileDialog::getOpenFileName(
|
const QString path = QFileDialog::getOpenFileName(
|
||||||
this, tr("打开 Markdown 文件"), startDirectory(),
|
this, tr("打开 Markdown 文件"), startDirectory(),
|
||||||
tr("Markdown 文件 (*.md *.markdown *.mdown *.mkd);;文本文件 (*.txt);;所有文件 (*)"),
|
tr("Markdown 文件 (*.md *.markdown *.mdown *.mkd);;文本文件 (*.txt);;所有文件 (*)"),
|
||||||
nullptr, QFileDialog::DontUseNativeDialog);
|
nullptr, fileDialogOptions());
|
||||||
if (path.isEmpty())
|
if (path.isEmpty())
|
||||||
return;
|
return;
|
||||||
openPath(path);
|
openPath(path);
|
||||||
@@ -667,7 +678,7 @@ bool MainWindow::saveFileAs()
|
|||||||
QString path = QFileDialog::getSaveFileName(
|
QString path = QFileDialog::getSaveFileName(
|
||||||
this, tr("另存为"), startDirectory(),
|
this, tr("另存为"), startDirectory(),
|
||||||
tr("Markdown 文件 (*.md);;文本文件 (*.txt);;所有文件 (*)"),
|
tr("Markdown 文件 (*.md);;文本文件 (*.txt);;所有文件 (*)"),
|
||||||
nullptr, QFileDialog::DontUseNativeDialog);
|
nullptr, fileDialogOptions());
|
||||||
if (path.isEmpty())
|
if (path.isEmpty())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -685,7 +696,7 @@ void MainWindow::exportPdf()
|
|||||||
|
|
||||||
QString target = QFileDialog::getSaveFileName(
|
QString target = QFileDialog::getSaveFileName(
|
||||||
this, tr("导出 PDF"), QDir(startDirectory()).filePath(suggested),
|
this, tr("导出 PDF"), QDir(startDirectory()).filePath(suggested),
|
||||||
tr("PDF 文件 (*.pdf)"), nullptr, QFileDialog::DontUseNativeDialog);
|
tr("PDF 文件 (*.pdf)"), nullptr, fileDialogOptions());
|
||||||
if (target.isEmpty())
|
if (target.isEmpty())
|
||||||
return;
|
return;
|
||||||
if (!target.endsWith(QStringLiteral(".pdf"), Qt::CaseInsensitive))
|
if (!target.endsWith(QStringLiteral(".pdf"), Qt::CaseInsensitive))
|
||||||
@@ -695,18 +706,23 @@ void MainWindow::exportPdf()
|
|||||||
|
|
||||||
QTextDocument document;
|
QTextDocument document;
|
||||||
MarkdownRenderer::render(m_editor->toPlainText(), &document);
|
MarkdownRenderer::render(m_editor->toPlainText(), &document);
|
||||||
document.setPageSize(QSizeF(672.76, 1001.57));
|
|
||||||
|
|
||||||
QPrinter printer(QPrinter::HighResolution);
|
{
|
||||||
printer.setOutputFormat(QPrinter::PdfFormat);
|
// 用 QPdfWriter 直接写 PDF,不碰任何平台的打印子系统:
|
||||||
printer.setOutputFileName(target);
|
// Windows 的打印后台、Linux 的 CUPS 在没有打印机或服务异常时都可能卡住。
|
||||||
printer.setPageSize(QPageSize(QPageSize::A4));
|
QPdfWriter writer(target);
|
||||||
printer.setPageMargins(QMarginsF(16, 16, 16, 16), QPageLayout::Millimeter);
|
writer.setPageSize(QPageSize(QPageSize::A4));
|
||||||
printer.setDocName(QFileInfo(target).completeBaseName());
|
writer.setPageMargins(QMarginsF(16, 16, 16, 16), QPageLayout::Millimeter);
|
||||||
|
writer.setTitle(QFileInfo(target).completeBaseName());
|
||||||
|
|
||||||
|
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);
|
QApplication::setOverrideCursor(Qt::WaitCursor);
|
||||||
document.print(&printer);
|
document.print(&writer);
|
||||||
QApplication::restoreOverrideCursor();
|
QApplication::restoreOverrideCursor();
|
||||||
|
}
|
||||||
|
|
||||||
if (!QFileInfo::exists(target) || QFileInfo(target).size() <= 0) {
|
if (!QFileInfo::exists(target) || QFileInfo(target).size() <= 0) {
|
||||||
QMessageBox::warning(this, tr("导出失败"),
|
QMessageBox::warning(this, tr("导出失败"),
|
||||||
@@ -979,7 +995,7 @@ void MainWindow::chooseFolder()
|
|||||||
{
|
{
|
||||||
const QString directory = QFileDialog::getExistingDirectory(
|
const QString directory = QFileDialog::getExistingDirectory(
|
||||||
this, tr("打开文件夹"), startDirectory(),
|
this, tr("打开文件夹"), startDirectory(),
|
||||||
QFileDialog::ShowDirsOnly | QFileDialog::DontUseNativeDialog);
|
QFileDialog::ShowDirsOnly | fileDialogOptions());
|
||||||
if (!directory.isEmpty())
|
if (!directory.isEmpty())
|
||||||
openFolder(directory);
|
openFolder(directory);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user