fix: fall back to libreoffice for docx-to-pdf when htmltopdf fails - #319
fix: fall back to libreoffice for docx-to-pdf when htmltopdf fails#319LiHua000 wants to merge 1 commit into
Conversation
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: LiHua000 The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
Reviewer's GuideAdds a fallback DOCX-to-PDF conversion path using headless LibreOffice when the existing htmltopdf/WebEngine-based converter fails, and declares LibreOffice as a recommended dependency to improve availability of the fallback converter. Sequence diagram for DOCX-to-PDF fallback using LibreOfficesequenceDiagram
actor User
participant DocumentFactory
participant htmltopdf_converter as htmltopdf_converter
participant libreoffice_fallback as libreoffice
User->>DocumentFactory: getDocument(docType_docx)
DocumentFactory->>htmltopdf_converter: start(html_to_pdf)
DocumentFactory->>htmltopdf_converter: waitForFinished()
DocumentFactory->>DocumentFactory: realFile.exists()
alt temp.pdf exists
DocumentFactory->>User: return PDFDocument
else temp.pdf missing
DocumentFactory->>DocumentFactory: qInfo htmltopdf failed
DocumentFactory->>libreoffice_fallback: fallback.start(fallbackCommand)
DocumentFactory->>libreoffice_fallback: fallback.waitForStarted()
alt waitForStarted failed
DocumentFactory->>DocumentFactory: error = ConvertFailed
DocumentFactory->>DocumentFactory: *pprocess = nullptr
DocumentFactory-->>User: return nullptr
else started
DocumentFactory->>libreoffice_fallback: fallback.waitForFinished()
alt waitForFinished failed
DocumentFactory->>DocumentFactory: error = ConvertFailed
DocumentFactory->>DocumentFactory: *pprocess = nullptr
DocumentFactory-->>User: return nullptr
else finished
DocumentFactory->>DocumentFactory: realFile.exists()
alt temp.pdf still missing
DocumentFactory->>DocumentFactory: error = ConvertFailed
DocumentFactory->>DocumentFactory: *pprocess = nullptr
DocumentFactory-->>User: return nullptr
else pdf generated
DocumentFactory->>DocumentFactory: *pprocess = nullptr
DocumentFactory-->>User: return PDFDocument
end
end
end
end
Flow diagram for updated DOCX conversion chain with LibreOffice fallbackflowchart LR
A[DOCX file] --> B[unzip]
B --> C[pandoc html]
C --> D[htmltopdf pdf]
D -->|temp.pdf exists| E[PDFDocument]
D -->|temp.pdf missing| F[libreoffice --headless --convert-to pdf]
F -->|temp.pdf exists| E
F -->|start/finish/pdf failure| G[ConvertFailed]
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
bff3360 to
fabe17b
Compare
1. 在 DOCX 渲染链路 htmltopdf 失败(temp.pdf 未生成)时,兜底使用 libreoffice 直接将 docx 转换为 pdf,绕开 sw_64 等架构下 QtWebEngine 内部崩溃导致 html→pdf 失败的问题; 2. 兜底触发条件为 htmltopdf 失败(temp.pdf 不存在),全架构通用且仅失败时触发,不影响 x86/ARM/LoongArch 等正常架构的现有渲染输出; 3. libreoffice 使用独立的 UserInstallation 配置目录,避免与已运行的 libreoffice 抢占用户配置; 4. 兜底进程通过 pprocess 暴露给调用方,转换结束/失败时及时置空,避免悬挂指针; 5. debian/control 增加 Recommends: libreoffice,确保兜底转换器默认可用,缺失时优雅降级为转换失败(不劣于现状); ===================================== 1. fall back to libreoffice to convert docx directly to pdf when htmltopdf fails (temp.pdf not generated), bypassing the QtWebEngine internal crash on sw_64 and other architectures where html-to-pdf conversion fails; 2. the fallback triggers only when htmltopdf fails (temp.pdf missing), applies to all architectures and only on failure, leaving existing rendering output on x86/ARM/LoongArch untouched; 3. libreoffice uses a dedicated UserInstallation profile dir to avoid profile-lock conflicts with an already running libreoffice; 4. the fallback process is exposed via pprocess and cleared on completion/failure to avoid dangling pointers; 5. add Recommends: libreoffice to debian/control so the fallback converter is available by default; degrades gracefully to conversion failure when absent (no worse than before); Log: 修复 sw_64 架构打开 docx 显示空白:QtWebEngine 在 sw_64 上崩溃导致 html→pdf 失败,新增 libreoffice 兜底转换路径绕开 webengine,恢复 DOCX 文档可用性 Bug: https://pms.uniontech.com/bug-view-365875.html PMS: BUG-365875
fabe17b to
0a54641
Compare
deepin pr auto review★ 总体评分:35分■ 【总体评价】
■ 【详细分析】
■ 【改进建议代码示例】 diff --git a/reader/document/Model.cpp b/reader/document/Model.cpp
index b7220d52f..f9db1ba18 100755
--- a/reader/document/Model.cpp
+++ b/reader/document/Model.cpp
@@ -137,14 +137,48 @@ deepin_reader::Document *deepin_reader::DocumentFactory::getDocument(const int &
QFile realFile(realFilePath);
if (!realFile.exists()) {
- qInfo() << "htmltopdf failed! " << realFilePath << " doesn't exist";
- error = deepin_reader::Document::ConvertFailed;
- if (!(QProcess::CrashExit == converter.exitStatus() && 9 == converter.exitCode())) {
+ // htmltopdf 依赖 QtWebEngine,在部分架构(如 sw_64)上 webengine 内部崩溃会导致 pdf 无法生成。
+ // 兜底使用 libreoffice 直接将 docx 转换为 pdf,绕开 webengine,恢复 DOCX 文档的可用性。
+ qInfo() << "htmltopdf failed!" << realFilePath << "doesn't exist, fallback to libreoffice";
+ QProcess fallback;
+ *pprocess = &fallback;
+ fallback.setWorkingDirectory(convertedFileDir);
+ // 独立的 UserInstallation 避免与已运行的 libreoffice 抢占用户配置
+ QString userInstallation = "file://" + convertedFileDir + "/lo_profile";
+ QStringList args;
+ args << "--headless"
+ << "-env:UserInstallation=" + userInstallation
+ << "--convert-to" << "pdf"
+ << "--outdir" << convertedFileDir
+ << targetDoc;
+ qDebug() << "执行命令: libreoffice" << args.join(" ");
+ fallback.start("libreoffice", args);
+ if (!fallback.waitForStarted()) {
+ qInfo() << "start libreoffice fallback failed";
+ error = deepin_reader::Document::ConvertFailed;
*pprocess = nullptr;
return nullptr;
}
- return nullptr;
+ // libreoffice 为重量级套件,每次兜底均使用全新 lo_profile 冷启动,叠加 sw_64 较慢,
+ // 大文档/带图表 docx 转换可能超过 QProcess 默认 30s 超时,此处放宽至 180s 避免误判失败。
+ if (!fallback.waitForFinished(180000)) {
+ qInfo() << "libreoffice fallback failed";
+ error = deepin_reader::Document::ConvertFailed;
+ *pprocess = nullptr;
+ return nullptr;
+ }
+ if (!realFile.exists()) {
+ qInfo() << "libreoffice fallback failed!" << realFilePath << "doesn't exist";
+ error = deepin_reader::Document::ConvertFailed;
+ // 转换过程中关闭应用,docsheet 被释放,对应的 *pprocess 已不存在;
+ // 仅当进程非被 SIGKILL(用户关闭文档)时才置空,避免对已释放内存写入。
+ if (!(QProcess::CrashExit == fallback.exitStatus() && 9 == fallback.exitCode())) {
+ *pprocess = nullptr;
+ }
+ return nullptr;
+ }
+ // 兜底成功:在 fallback 析构前置空 pprocess,避免悬挂指针。
+ // 此处与下方函数作用域的置空非冗余:此处服务块作用域的 fallback(析构前清空),
+ // 下方服务函数作用域的 converter2(此时仍存活)。
+ *pprocess = nullptr;
}
- qDebug() << "html转pdf完成";
+ qDebug() << "docx转pdf完成";
*pprocess = nullptr;
document = deepin_reader::PDFDocument::loadDocument(realFilePath, password, error); |
|
@LiHua000: The following test failed, say
Full PR test history. Your PR dashboard. DetailsInstructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. I understand the commands that are listed here. |
根因
PMS 365875:SW(申威 sw_64)架构机器在 deepin-reader 打开 DOCX 文件后界面空白,必现。
DOCX 走外挂转换链
docx → unzip → pandoc(html) → htmltopdf(pdf) → PDFDocument,其中 html→pdf 由htmltopdf(QWebEnginePage/Qt WebEngine)完成。sw_64 上QWebEnginePage::load()(htmltopdf/htmltopdfconverter.cpp:25)内部 SIGSEGV 崩溃(开发者抓取日志:--no-sandbox+--jitless仍SEGV_MAPERR,pdf 不生成),导致reader/document/Model.cpp判ConvertFailed返回 null 文档 → 界面空白。全链路仅一条 webengine 依赖路径,无降级兜底。崩溃成因高度指向 sw_64 qt5webengine(5.13) 与 Qt(5.11) ABI 不一致(符号化堆栈缺,待上游坐实,不阻塞本修复)。修复方案
为 DOCX 转换提供非 webengine 的兜底:当
htmltopdf失败(temp.pdf未生成)时,兜底用libreoffice --headless --convert-to pdf直接将 docx 转为 pdf,绕开崩溃的 webengine。temp.pdf不存在)时触发,全架构通用,不硬编码__sw_64__。x86/ARM/LoongArch 上 htmltopdf 正常 → 兜底不触发 → 现有渲染输出零变化;sw_64 上 htmltopdf 崩溃 → 兜底接管。debian/control增Recommends: libreoffice-writer(拉libreoffice-core,含libreofficeCLI,减体积)。缺失时代码侧waitForStarted失败 → 优雅降级为ConvertFailed,不劣于现状,无回归。reader/document/Model.cppDOCX 分支 +debian/control一行;不改htmltopdf/、不改 PDF/DJVU 分支、不改函数签名。Review 返工(针对 #319 首轮 review 的 Major 项)
首轮 review 等级 C,2 项 Major 已返工修复:
libreoffice跑完但temp.pdf仍不存在)补回「关闭应用竞态」SIGKILL 守卫,正确引用fallback进程(既有:75/:110同款守卫,规避~DocSheet()SIGKILL 后pprocess指向已释放m_process的 UAF):fallback.waitForFinished()→waitForFinished(180000)(180s)。libreoffice 每次兜底用全新lo_profile冷启动,叠加 sw_64 较慢,大文档/带图表 docx 可能超 QProcess 默认 30s,放宽至 180s 避免误判失败导致再次空白。非阻塞改进项(v2 一并处理,其中 m01 已在 v3 撤销 — 见下):
"html转pdf完成"改为中性"docx转pdf完成"(该行同时服务 htmltopdf 成功与 libreoffice 兜底成功两条路径)。Recommends: libreoffice→libreoffice-writer(减体积,已验证依赖libreoffice-core含 CLI)。QProcess::start单字符串分词):既有全文件模式、非本次回归,未改以保持一致。v3 返工(针对 #319 二次重审引入的 M03)
二次重审 M01/M02 已正确修复,但 v2 的 m01「移除块内置空」引入新 Major M03(UAF 悬挂指针):
QProcess fallback为块作用域,兜底成功路径上fallback析构时*pprocess仍持&fallback,存在悬挂窗口(~DocSheet()经悬挂m_process读processId()→ UAF/误杀)。v3 恢复 v1 块内置空结构(1 行 + 注释):块内(服务块作用域
fallback,析构前清空)+ 块外(服务函数作用域converter2,仍存活)两处置空非冗余,覆盖不同作用域/路径,均保留。撤销 v2 的 m01,恢复 v1 块内结构。改动安全评估
低-中风险,向后兼容。
DocumentFactory::getDocument调用者 2 处:PageRenderThread.cpp:688(主 DOCX 路径,契约不变,行为在 sw_64 上由失败转为成功)与SheetBrowser.cpp:118(传 nullptr,DOCX 首部即 early-return,不触及改动区段)。兜底进程经pprocess暴露并在结束/失败时按守卫置空,避免悬挂与 UAF。无签名变更、无公开函数删除、不撤销历史修复。测试建议
CentralDocPagetip),不崩溃。