Skip to content

fix(print): run PDF print conversion asynchronously with a wait tip - #421

Open
Resurgamz wants to merge 1 commit into
linuxdeepin:release/snipefrom
Resurgamz:agent/pms-bug-bot/7de3b1406a7e
Open

Resurgamz wants to merge 1 commit into
linuxdeepin:release/snipefrom
Resurgamz:agent/pms-bug-bot/7de3b1406a7e

Conversation

@Resurgamz

@Resurgamz Resurgamz commented Oct 19, 2026 •

Copy link
Copy Markdown

Problem

Clicking Print / Ctrl+P on a PDF ran pdftocairo synchronously on the UI thread in DocSheet::onPopPrintDialog() before preview->open(). For PDFs with many pages the conversion can take a long time, so the print preview dialog could not pop up and the whole UI froze. A prior fix even raised the timeout to 5 minutes, which made the freeze worse.

Change

  • Run the pdftocairo conversion asynchronously in a background QProcess; the UI thread is never blocked.
  • Immediately show a persistent lightweight tip ("Preparing the content for printing…") using the existing DFloatingMessage/DMessageManager style (close button hidden), and open the print preview once the conversion finishes.
  • If the conversion fails or pdftocairo is unavailable, fall back to the original PDF and continue printing (same behaviour as before this change).
  • The conversion runs on every print (no caching), so the printed content always matches the current document.

Translations for the new string are submitted separately in #422.

Regression constraint

The conversion is intentionally not gated by the Linearized flag. The bug-348017 reproducers are non-linearized PDFs with non-embedded CJK fonts; skipping the conversion for them would reintroduce blank/garbled printing. Every PDF is still converted exactly as before — only the timing/threading changed.

Testing

  • cmake --build of deepin-reader passes (Qt5/DTK5).

Related

  • Fixes: bug-379067 (print dialog blocked by synchronous pdftocairo)
  • Regression guard: bug-348017 (blank/garbled PDF printing) — hence the conversion is not gated by Linearized

@deepin-ci-robot

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: Resurgamz

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@sourcery-ai

sourcery-ai Bot commented Oct 19, 2026

Copy link
Copy Markdown

Reviewer's Guide

PDF printing now performs the required pdftocairo conversion asynchronously with a visible non-dismissible wait tip, validates and caches the converted output before opening preview, aborts cleanly on failures or timeout, and adds the required dependency, translations, and unit coverage.

Sequence diagram for asynchronous PDF print preparation

sequenceDiagram
    actor User
    participant DocSheet
    participant WaitTip as DFloatingMessage
    participant Process as QProcess
    participant Preview as DPrintPreviewDialog

    User->>DocSheet: onPopPrintDialog()
    DocSheet->>WaitTip: showPrintWaitTip()
    DocSheet->>Process: start(pdftocairo)
    Process-->>DocSheet: finished(exitCode, exitStatus)
    DocSheet->>DocSheet: isConvertedPdfValid()
    alt conversion succeeds and output is valid
        DocSheet->>WaitTip: closePrintWaitTip()
        DocSheet->>Preview: showPrintPreviewWithPath()
        Preview-->>User: open()
    else failure, timeout, or invalid output
        DocSheet->>WaitTip: closePrintWaitTip()
        DocSheet->>User: showPrintFailedTip()
    end
Loading

Flow diagram for cached PDF print conversion

flowchart TD
    A[PDF print requested] --> B{Conversion already running?}
    B -- Yes --> C[Ignore repeated request]
    B -- No --> D{Valid cached PDF exists?}
    D -- Yes --> E[showPrintPreviewWithPath]
    D -- No --> F[showPrintWaitTip]
    F --> G[startPdfPrintConversion]
    G --> H{Conversion succeeds and output is valid?}
    H -- Yes --> I[Cache converted PDF]
    I --> E
    H -- No --> J[Abort printing]
    J --> K[showPrintFailedTip]
Loading

File-Level Changes

Change Details Files
Moves PDF print conversion off the UI thread and coordinates preview display through asynchronous process completion.
  • Starts pdftocairo via a parented QProcess with a five-minute QTimer timeout.
  • Defers process startup to the next event-loop iteration so the wait message can render immediately.
  • Validates exit status and PDF tail markers before opening preview; failure aborts printing and reports an error.
  • Adds re-entry protection and destructor cleanup for the process, timer, and wait state.
reader/uiframe/DocSheet.cpp
reader/uiframe/DocSheet.h
Adds a persistent print-preparation UX and session-level converted-PDF reuse.
  • Shows a resident floating wait message with its close button hidden while conversion runs.
  • Closes the wait message on completion and displays a failure message when conversion cannot produce valid output.
  • Reuses a valid pdftocairoPrint.pdf cache and routes DOCX, PDF, and other document types through a shared preview-opening helper.
  • Retains PDF conversion regardless of the Linearized flag.
reader/uiframe/DocSheet.cpp
reader/uiframe/DocSheet.h
Updates packaging, localization, and regression coverage for the new print flow.
  • Adds the poppler-utils runtime dependency and changelog entry.
  • Adds English/base and Simplified Chinese strings for wait and failure messages.
  • Adds unit coverage for entering the asynchronous wait state and handling conversion failure.
debian/control
debian/changelog
translations/deepin-reader.ts
translations/deepin-reader_zh_CN.ts
tests/uiframe/ut_docsheet.cpp

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@github-actions

Copy link
Copy Markdown

TAG Bot

TAG: 6.5.67
EXISTED: no
DISTRIBUTION: unstable

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="reader/uiframe/DocSheet.cpp" line_range="2122-2123" />
<code_context>
+    if (m_printConvertTimer)
+        m_printConvertTimer->stop();
+    if (m_printConvertProcess && m_printConvertProcess->state() != QProcess::NotRunning)
+        m_printConvertProcess->kill();
+
     // 排空引用本 sheet 的所有待处理渲染任务(仅优化,避免为将死文档做无用渲染);
</code_context>
<issue_to_address>
**Retries fail after conversion timeouts**

When the user retries printing before the killed process has finished, the timeout handler calls `finishPdfPrintConversion(false)`, clearing the in-progress guard while `m_printConvertProcess` is still running; the retry’s `start()` does not launch a new conversion, and the old process’s callback can finish the retry as a failure or use its output.

Keep retries from starting until the timed-out process has stopped, and prevent its callbacks from completing a later request.

Also at `reader/uiframe/DocSheet.cpp:2098-2105`, `reader/uiframe/DocSheet.cpp:2129-2130`.
</issue_to_address>

### Comment 2
<location path="tests/uiframe/ut_docsheet.cpp" line_range="1729-1732" />
<code_context>
+TEST_F(TestDocSheet, UT_DocSheet_finishPdfPrintConversion_failure)
+{
+    // 处理失败:退出等待状态并给出失败提示,且不进行后续打印
+    m_tester->m_printConvertInProgress = true;
+    m_tester->finishPdfPrintConversion(false);
+    EXPECT_FALSE(m_tester->m_printConvertInProgress);
+}
+
 TEST_F(TestDocSheet, UT_DocSheet_onPrintRequested_singleArg)
</code_context>
<issue_to_address>
**Failure behavior goes untested**

When the failure branch stops showing the error tip or incorrectly opens a preview, `UT_DocSheet_finishPdfPrintConversion_failure` checks only that the in-progress flag is cleared, so it still passes without failure feedback or with a preview opened.

Assert that the failure tip is shown and that no print preview is opened.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment on lines +2122 to +2123
m_printConvertProcess->kill();
finishPdfPrintConversion(false);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium · Retries fail after conversion timeouts

When the user retries printing before the killed process has finished, the timeout handler calls finishPdfPrintConversion(false), clearing the in-progress guard while m_printConvertProcess is still running; the retry’s start() does not launch a new conversion, and the old process’s callback can finish the retry as a failure or use its output.

Keep retries from starting until the timed-out process has stopped, and prevent its callbacks from completing a later request.

Also at reader/uiframe/DocSheet.cpp:2098-2105, reader/uiframe/DocSheet.cpp:2129-2130.

Prompt for AI agents
In `reader/uiframe/DocSheet.cpp` at lines 2122-2123:

**Retries fail after conversion timeouts**

When the user retries printing before the killed process has finished, the timeout handler calls `finishPdfPrintConversion(false)`, clearing the in-progress guard while `m_printConvertProcess` is still running; the retry’s `start()` does not launch a new conversion, and the old process’s callback can finish the retry as a failure or use its output.

Keep retries from starting until the timed-out process has stopped, and prevent its callbacks from completing a later request.

Also at `reader/uiframe/DocSheet.cpp:2098-2105`, `reader/uiframe/DocSheet.cpp:2129-2130`.

Comment thread tests/uiframe/ut_docsheet.cpp Outdated
Comment on lines +1729 to +1732
m_tester->m_printConvertInProgress = true;
m_tester->finishPdfPrintConversion(false);
EXPECT_FALSE(m_tester->m_printConvertInProgress);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium · Failure behavior goes untested

When the failure branch stops showing the error tip or incorrectly opens a preview, UT_DocSheet_finishPdfPrintConversion_failure checks only that the in-progress flag is cleared, so it still passes without failure feedback or with a preview opened.

Assert that the failure tip is shown and that no print preview is opened.

Prompt for AI agents
In `tests/uiframe/ut_docsheet.cpp` at lines 1729-1732:

**Failure behavior goes untested**

When the failure branch stops showing the error tip or incorrectly opens a preview, `UT_DocSheet_finishPdfPrintConversion_failure` checks only that the in-progress flag is cleared, so it still passes without failure feedback or with a preview opened.

Assert that the failure tip is shown and that no print preview is opened.

@Resurgamz
Resurgamz force-pushed the agent/pms-bug-bot/7de3b1406a7e branch 3 times, most recently from 9f8b8d8 to a5e6ec7 Compare October 19, 2026 09:38
@Resurgamz
Resurgamz force-pushed the agent/pms-bug-bot/7de3b1406a7e branch from a5e6ec7 to f5df335 Compare October 19, 2026 10:01
pdftocairo was executed synchronously on the UI thread before the print
preview was shown, so large PDFs blocked the print dialog from popping
up. Now a persistent lightweight tip is shown immediately, the
conversion runs in a background process, and the preview opens once it
finishes. If the conversion fails or pdftocairo is unavailable, it
falls back to the original PDF so printing still proceeds (same
behaviour as before). The conversion is performed on every print.

The conversion is still performed for every PDF (not gated by the
Linearized flag) so that bug-348017 (non-linearized PDFs with
non-embedded CJK fonts printing blank/garbled) is not regressed.

Log: 打印 PDF 时异步执行 pdftocairo 转化并先显示等待提示,避免大文件阻塞打印界面

Bug: https://pms.uniontech.com/bug-view-379067.html
@Resurgamz
Resurgamz force-pushed the agent/pms-bug-bot/7de3b1406a7e branch from f5df335 to b4e123b Compare October 20, 2026 13:16
@deepin-ci-robot

Copy link
Copy Markdown

deepin pr auto review

🤖 AI 代码审查报告

总体评分: 87 分 (通过阈值: 70分)

Pass


📊 总体评价

项目 结果
审查结论 代码审查通过,建议修复打印提示窗口定位问题
评分详情 本次提交将 PDF 打印转化由同步阻塞(waitForFinished 最长 5 分钟)改为异步 QProcess + 超时定时器 + 常驻等待提示,显著改善了主线程阻塞问题,核心异步逻辑、重入保护、超时与失败回退处理正确,注释完整。 代码安全维度无漏洞(pdftocairo 以参数列表方式调用,无 shell 注入;无硬编码凭据/敏感信息)。 主要不足在于 printTipParent 选取承载窗口时遍历 topLevelWidgets 取最后一个 QMainWindow, 多窗口场景下等待提示会显示在错误窗口(逻辑缺陷);另有图标语义、调试日志位置等轻微质量问题。 总体评分 87 分,审查通过。

🔍 详细分析

1. 语法逻辑 ❌

评价: 存在逻辑缺陷 ❌ 不通过

潜在问题:
✅ 未发现明显问题

建议: ['printTipParent 直接返回 this->window(),避免遍历全部顶层窗口并消除多窗口错位问题', '析构 kill() 前显式 disconnect(m_printConvertProcess/timer, nullptr, this, nullptr),作为防御性加固']


2. 代码质量 ✅

评价: 代码结构清晰,注释完整 ✅ 通过

潜在问题:
✅ 未发现明显问题

建议: ['等待提示使用加载中/旋转图标(如 view-refresh 或 DSpinner)替代 dr_ok', '将 qCDebug 日志移入各退出路径,或直接移除', '失败回退时向用户展示简短警告(如另起一条 DFloatingMessage 或状态栏提示)']


3. 代码性能 ✅

评价: 性能良好,资源使用合理 ✅ 通过

潜在问题:
✅ 未发现明显问题

建议: ['使用 QStringLiteral("dr_ok") 替代运行期字符串拼接']


4. 代码安全 🔒

评价: 存在0个安全漏洞 ✅ 通过

🔐 发现 0 个安全漏洞

安全漏洞详情:
✅ 未发现安全漏洞

建议: []


💡 改进建议代码示例

// DocSheet.cpp —— 修正 printTipParent 窗口定位,并优化图标与日志
QWidget *DocSheet::printTipParent() const
{
    // 直接取当前 sheet 所在的顶层窗口,避免多窗口时提示显示到错误窗口
    QWidget *parent = this->window();
    if (!parent)
        parent = qApp->activeWindow();
    if (!parent)
        parent = const_cast<DocSheet *>(this);
    return parent;
}

void DocSheet::showPrintWaitTip()
{
    if (m_printWaitTip)
        return;

    QWidget *parent = printTipParent();
    if (!parent)
        return;

    DFloatingMessage *tip = new DFloatingMessage(DFloatingMessage::ResidentType);
    tip->setAttribute(Qt::WA_DeleteOnClose);
    // 使用加载中图标,语义与“准备中请等待”一致
    tip->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
    tip->setMessage(tr("Preparing the content for printing. This may take a while. Please wait…"));
    if (DDialogCloseButton *closeBtn = tip->findChild<DDialogCloseButton *>())
        closeBtn->hide();

    m_printWaitTip = tip;
    DMessageManager::instance()->sendMessage(parent, tip);
    qCDebug(appLog) << "print wait tip shown";
}

本报告由 AI 代码审查工具自动生成

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants