[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-105265":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":16,"subscribersCount":16,"size":16,"stars1d":16,"stars7d":16,"stars30d":16,"stars90d":16,"forks30d":16,"starsTrendScore":16,"compositeScore":17,"rankGlobal":10,"rankLanguage":10,"license":18,"archived":19,"fork":19,"defaultBranch":20,"hasWiki":21,"hasPages":19,"topics":22,"createdAt":10,"pushedAt":10,"updatedAt":37,"readmeContent":38,"aiSummary":10,"trendingCount":16,"starSnapshotCount":16,"syncStatus":39,"lastSyncTime":40,"discoverSource":41},105265,"HuiwenMCP","xristosstarnetgames439-del\u002FHuiwenMCP","xristosstarnetgames439-del","Qt + OpenHarmony \u002F HarmonyOS PC 文档阅读、编辑与格式转换项目，集成 ArkTS、CEF 浏览器内核、PDFium 与 Python HNP。已支持 PDF\u002FMarkdown 双栏预览、同步滚动、PDF 转 Markdown 与最近工作区恢复；规划支持 Word 文档预览与编辑、PDF 编辑、Markdown 编辑、OCR 文字识别及多格式批量转换。源码公开，供学习交流与教学参考，欢迎提交 Issue、通过 PR 贡献代码，共同完善鸿蒙 PC 文档工具。自有代码采用 PolyForm Noncommercial 1.0.0 许可。","",null,"C++",236,45,28,1,0,40.96,"Other",false,"main",true,[23,24,25,26,27,28,29,30,31,32,33,34,35,36],"arkts","cef","chromium","educational","embedded-browser","harmonyos","harmonyos-pc","hnp","learning-resource","markdown-viewer","openharmony","pdf-reader","pdf-to-markdown","qt","2026-11-01 00:13:02","# 汇文MCP · HuiwenMCP for Harmony\n\n鸿蒙 PC 上的 PDF 与 Markdown 阅读、转换应用。本阶段先把文字型 PDF 转成沙箱内的 MD 文件。\n\n## 许可证\n\n本项目自有代码采用 [PolyForm Noncommercial License 1.0.0](LICENSE)，官方原文见 [PolyForm 网站](https:\u002F\u002Fpolyformproject.org\u002Flicenses\u002Fnoncommercial\u002F1.0.0)。这是源码公开的非商业使用许可。\n\n允许在许可规定的用途范围内使用、学习、研究、修改和分发。商业产品开发、销售、商业服务等超出许可范围的用途，需要另行取得相关权利人的授权。许可对教育机构等组织另有允许用途规定，以 `LICENSE` 原文为准。\n\n第三方源码、依赖和资源仍遵循各自的许可证及版权声明，不受本项目许可证替代。贡献代码前，请确认你有权将该贡献按本项目许可证授权。\n\n## 当前状态\n\n- 首页保持 ArkTS；银灰介绍区与主区域按约 30% \u002F 70% 分配宽度，主区域提供文档预览、文档编辑、文档转化三个小正方形入口，普通字重的名称放在方块外下方。点击入口打开独立格式选择子窗口；预览和编辑仅展示 Word、PDF、MD 格式，转化窗口中的 PDF→MD 入口启动独立 Qt 工作区。\n- 左侧介绍区上下内边距为 50vp、左右为 32vp；标题置顶，两段介绍组成间隔 20vp 的底部内容组，外层通过 `SpaceBetween` 分布并保持左对齐。\n- 介绍区背景与黑色 ∞ 使用独立 SVG：银灰底图包含左上、右下的淡化文档及斜向柔光，∞ 的反光从中心向两侧连续衰减；底图等比例铺满，符号等比例居中，文字独立叠加，整体由 24vp 圆角裁切。本次素材与布局仅做静态检查和设计渲染，尚未编译、打包或设备验收。\n- 首页右侧入口改为顶部排列，下方“最近工作区”默认展示三条任务目录记录；当前可恢复 PDF→MD 转化任务，加载原 PDF 和已有 MD，并在 Qt 载入成功后更新任务目录最近使用时间。本次最近列表、恢复与时间更新仅做格式检查，尚未编译、打包或真机验收。\n- ArkTS 首页左侧有 60vp 半透明窄侧栏，底部 36vp 三横线 SVG 按钮可打开“关于、外观、截图、文件管理、设置”菜单。菜单通过实际布局坐标，让菜单底部与按钮底部对齐，菜单左边与按钮右边保持 8vp 间距；外观二级菜单提供浅色、深色、跟随系统，选择持久保存并应用于 ArkTS 首页和弹窗；Qt 工作区仍使用自身配色。关于弹窗读取实际应用版本号；截图、文件管理、设置本阶段只保留入口并提示暂未开放。首页内容背景与窗口容器分别设置透明，焦点态容器透明，非焦点态使用实色，右侧主内容保持不透明。\n- Qt 开发依赖保存在 `third_party\u002Fqt\u002F`，运行库从 `entry\u002Flibs\u002Farm64-v8a\u002F` 链接并随 HAP 分发；PDFium fork 保存在 `third_party\u002Fpdfium\u002F`，Qt 左侧输入区域已接入其公开 C API 实现正文预览；已完成鸿蒙编译、HAP 打包；系统目录跳转沿用原有实现。\n- DevEco Run 构建前执行 `PrepareNativeDependencies`，按当前 PDFium 源码增量生成 `libpdfium.so`；Qt\u002FNAPI 的 C++ 编译后同步新库到 `libs`。没有保存源码自动构建、应用内动态替换库或保持进程状态的热重载。\n- Qt 工作区默认最大化并保留系统标题栏和窗口边框，输入、输出区域解耦；中部分割线可拖动，悬浮“转化”按钮随分隔线移动。\n- 两侧区域共用 `DocumentView : QStackedWidget`，分别注入 PDF 和 Markdown 渲染器；文件卡片、预览切换、忙碌状态与对外信号集中维护。PDF 继承公共 `RasterDocumentViewport`，复用独立绘图窗口、滚动条和惯性能力；CEF 保留浏览器自身滚动。Word 渲染器尚未接入。本次通用化重构仅完成格式检查，尚未编译、打包或设备验收。\n- 输入文件卡片通过 DocumentStore 获取系统选取授权，再经 NAPI 调用 C++ harmony_sandbox 完成沙箱复制和路径安排。两侧操作统一到顶部“文件”菜单：查看模式默认预览，可切换为占位图；左侧由 PDFium 显示多页 PDF，右侧已有结果时由 CEF 显示 MD。转换成功自动切到预览并隐藏转化按钮；更换文件保留当前查看方式，清空旧结果并恢复按钮，取消选择保留原状态；尚未生成 MD 时仍显示等待提示。PDFium 后台串行渲染可见页，连续尺寸变化合并请求；位图缓存上限为 64 MiB，超长页面降低采样分辨率。Qt ConversionService 仍复用 PythonRunner 转换。本次默认预览及按钮显隐调整仅做格式检查，尚未编译、打包或真机验收。\n- 原有 Python HNP 自检与 PDF 转换已由用户在真机验证成功。新增 Qt 窗口交接、选择与结果位置操作需要真机验收。\n- 汇文 Python 包只公开 `pdf_to_md()`；后续格式在依赖和设备转换通过后再增加。\n- Python 部署拆成稳定环境 HNP 与业务 `.pyz`：修改汇文 API、MarkItDown 源码或业务补丁只更新 pyz；修改解释器、目标依赖及环境打包配置才重建 HNP。本次拆分仅做语法和格式检查，首次迁移及设备转换尚待验证。\n\n## 工程结构\n\n以下以当前仓库根目录为基准。`已有`表示工作区中存在，`生成`表示本机打包产物，`规划`表示后续阶段的目标目录或文件；规划项尚未创建。\n\n```text\nHuiWenMCP\u002F\n├── AppScope\u002F                          [已有] 应用名称、图标和版本\n├── entry\u002F                             [已有] 鸿蒙 HAP 模块\n│   ├── src\u002Fmain\u002F\n│   │   ├── module.json5               [已有] 设备类型、Ability 与私有 HNP 声明\n│   │   ├── ets\u002F\n│   │   │   ├── abilitystage\u002FQtAbilityStage.ets [已有] Qt 平台初始化\n│   │   │   ├── entryability\u002FEntryAbility.ets  [已有] ArkTS 启动入口\n│   │   │   ├── entryability\u002FQtWorkspaceAbility.ets [已有] Qt 窗口及系统文件操作\n│   │   │   ├── components\u002FFunctionSidebar.ets [已有] 首页半透明窄栏与底部菜单按钮\n│   │   │   ├── components\u002FHomeFeatureCard.ets [已有] 正方形功能卡片与 SVG 图标\n│   │   │   ├── components\u002FHomeFormatDialog.ets [已有] 独立格式选择子窗口与 PDF→MD 跳转事件\n│   │   │   ├── components\u002FRecentWorkspaceList.ets [已有] 固定列、文件名中部省略与最近记录点击\n│   │   │   ├── pages\u002FHomePage.ets      [已有] 首页功能切换与 Qt 窗口交接\n│   │   │   ├── pages\u002FIndex.ets         [已有] QPA 固定加载的 Qt XComponent 承载页\n│   │   │   ├── services\u002FConversionService.ets [已有] 页面调用原生转换的统一入口\n│   │   │   ├── services\u002FRecentWorkspaceService.ets [已有] 最近数量、模式文案、时间和恢复前校验\n│   │   │   └── utils\u002FDocumentStore.ets [已有] 系统选取授权与 NAPI 适配\n│   │   └── cpp\u002F\n│   │       ├── CMakeLists.txt          [已有] 原生库构建配置\n│   │       ├── napi_bridge.cpp        [已有] ArkTS → C++ 的 NAPI 接口\n│   │       ├── python_runner.cpp\u002F.h   [已有] 部署内置 pyz 到缓存，使用 HNP 解释器执行\n│   │       └── types\u002F                 [已有] 原生模块的 ArkTS 类型声明\n│   ├── libs\u002Farm64-v8a\u002F                [生成] Qt、PDFium 与本项目动态库，不进入 Git\n│   │   └── libQt5{Core,Gui,Widgets,Concurrent}.so、QPA、libpdfium.so、libhuiwen_{native,qt}.so\n│   ├── build-profile.json5            [已有] HAP 构建与 ABI 配置\n│   ├── hvigorfile.ts                   [已有] 按输入准备 Python HNP，并在 HAP 签名前注入\n│   └── oh-package.json5               [已有] 模块依赖\n├── converter\u002F                         [已有] 可供桌面端复用的 Python 转换逻辑\n│   ├── huiwen_converter\u002F              [已有] 汇文公开 Python API\n│   │   ├── __init__.py                 [已有] 仅导出已实现的 pdf_to_md()\n│   │   ├── api.py                      [已有] 路径校验、写入 MD、返回输出路径\n│   │   └── _markitdown_adapter.py      [已有] 唯一直接调用 MarkItDown 的适配文件\n│   └── pdf2md\u002Fconvert.py              [已有] pyz 的 __main__，也可供桌面直接执行\n├── qt_app\u002F                            [已有] Qt Widgets 工作区，鸿蒙共享库\u002F桌面可执行入口\n│   ├── CMakeLists.txt                 构建 huiwen_qt，链接 Qt、PDFium 与鸿蒙 CEF\n│   ├── main.cpp                       默认最大化并保留系统边框，显示成功后通知结束原首页\n│   ├── resources\u002F\n│   │   ├── branding\u002F、file-icons\u002F     应用图标及共用文件格式图标\n│   │   └── file-icons.qrc、markdown.qrc 图标与离线 Markdown 页面资源\n│   ├── tests\u002F\n│   │   ├── CMakeLists.txt、pdf_preview_check.cpp 通用 View、PDF 加载与滚动检查\n│   │   └── sandbox_startup\u002F           沙箱启动检查；检查程序均需单独构建运行\n│   └── src\u002F\n│       ├── workspace\u002FConversionWindow.h\u002F.cpp 主窗口、两侧渲染器注入与业务接线\n│       ├── documentView\u002F              不区分输入／输出角色的文档区域\n│       │   ├── DocumentView.h\u002F.cpp    共用卡片、预览切换、忙碌状态与信号\n│       │   ├── DocumentRenderer.h    渲染契约、滚动归属及可选能力配置\n│       │   └── renderers\u002F\n│       │       ├── pdf\u002FPdfPreviewWidget.h\u002F.cpp PDFium、页面排版、后台渲染与缓存\n│       │       └── markdown\u002FMarkdownPreviewWidget.h\u002F.cpp 离线 MD 页面、CEF 画面与生命周期\n│       ├── components\u002F\n│       │   ├── FileCard.h\u002F.cpp         共用文件占位卡片，图标与名称由调用方传入\n│       │   ├── ConvertButton.h\u002F.cpp    独立悬浮转化按钮\n│       │   ├── rendering\u002F\n│       │   │   ├── RasterDocumentViewport.h\u002F.cpp 栅格文档共用视口，组合原生窗口与滚动能力\n│       │   │   └── RasterRenderWindow.h\u002F.cpp 独立 QWindow／QBackingStore、脏区与输入转发\n│       │   ├── scrolling\u002FDocumentScrollController.h\u002F.cpp Qt 平滑滚动、触控板采样及可选惯性\n│       │   ├── splitter\u002FWorkspaceSplitter.h\u002F.cpp  分隔线与拖动位置指示条\n│       │   ├── scrollBar\u002FDocumentScrollBar.h\u002F.cpp 文档竖向滚动条样式\n│       │   └── functionBar\u002F\n│       │       ├── FunctionBar.h\u002F.cpp 顶部“文件”和“滚动设置”入口\n│       │       ├── FileMenu.h\u002F.cpp    查看方式、任务目录及当前业务模式菜单\n│       │       └── SelectionMenu.h\u002F.cpp 共用菜单样式、右侧蓝点与子菜单箭头\n│       ├── services\u002FConversionService.h\u002F.cpp    异步 PDF 转 MD API\n│       ├── sandbox\u002F\n│       │   ├── DocumentItem.h         格式、路径、归档目录、缓存打开路径和使用时间\n│       │   ├── SandboxStore.h\u002F.cpp    三类目录、最近记录、编辑缓存和路径校验\n│       │   └── SandboxService.h\u002F.cpp  请求系统选择、接收沙箱路径和打开输出位置\n│       └── platform\u002Fharmony\u002F\n│           ├── qt_bridge.h\u002F.cpp              Qt ↔ ArkTS 线程桥\n│           ├── harmony_sandbox.h\u002F.cpp        鸿蒙沙箱规则与文件操作\n│           ├── harmony_sandbox_napi.cpp      异步导入与输出校验 NAPI\n│           └── CefSubprocess.cpp            CEF 子进程入口，不承担文档区域布局\n├── hnp\u002Farm64-v8a\u002F                     [生成] 鸿蒙 Python 运行环境，不进入 Git\n│   └── huiwen_python.hnp               私有 HNP 归档，内部包含：\n│       ├── bin\u002Fpython3                 Python 解释器\n│       ├── lib\u002Fpython3.14\u002Fsite-packages\u002F PDF 依赖及原生 .so，不含汇文 API 和 MarkItDown\n│       └── share\u002Fhuiwen\u002Fpython_smoke.py 固定的解释器自检入口\n├── entry\u002Fsrc\u002Fmain\u002Fresources\u002Frawfile\u002Fpython\u002F [生成] 独立业务包，不进入 Git\n│   └── huiwen_converter.pyz\n│       ├── __main__.py                来自 converter\u002Fpdf2md\u002Fconvert.py\n│       ├── huiwen_converter\u002F          汇文公开 API 与适配实现\n│       ├── markitdown\u002F                已应用业务补丁的第三方 Python 源码及包内资源\n│       └── licenses\u002Fmarkitdown\u002F       MIT 许可与第三方声明\n├── packaging\u002F                         [部分已有] 各平台打包脚本和配置\n│   ├── ohos\u002F                           [已有] HNP 打包脚本、依赖锁定文件及上游补丁\n│   │   └── build_python_bundle.py      生成确定性业务 pyz，不变则保留成品与时间戳\n│   ├── windows\u002F                        [规划] Windows 发布配置\n│   └── macos\u002F                          [规划] macOS 发布配置\n├── tests\u002Fpdf_samples\u002F                 [规划] 文字、双栏、表格等转换样本\n├── third_party\u002F\n│   ├── markitdown\u002F                   [子模块] MarkItDown fork\n│   ├── pdfium\u002F                       [子模块] PDFium fork 与鸿蒙构建补丁\n│   ├── markdown\u002F                     [已有] 离线 Markdown 页面源码与依赖锁定\n│   ├── cef\u002F                          [已有] CEF 适配与说明，SDK 由固定依赖包准备\n│   ├── prebuilt\u002F                     [已有] Qt\u002FCEF 固定依赖清单与准备说明\n│   └── qt\u002F                           [本机依赖] Qt 头文件、CMake 配置及宿主工具\n├── entry\u002Fsrc\u002Fmain\u002Fresources\u002Frawfile\u002Fthird_party_licenses\u002F [生成] Qt\u002FPDFium 许可材料，随包分发\n├── CMakeLists.txt                     [规划] Qt 桌面工程入口\n├── build-profile.example.json5       [已有] 不含签名信息的全局构建配置模板\n├── build-profile.json5               [本机] 签名配置，已被 Git 忽略\n└── README.md                          [已有] 项目状态与维护说明\n```\n\n当前 Qt 转换链为 `ConvertButton → Qt ConversionService.convertPdfToMd() → PythonRunner → HNP Python 执行缓存 pyz\u002F__main__.py → huiwen_converter.pdf_to_md() → MarkItDown → convert\u002F\u003C时间戳_文件名>\u002Foutput\u002F*.md`。Python API 与第三方库适配保持原有边界。原 ArkTS 转换接口仍保留；首页通过“文档转化 → PDF→MD”进入 Qt，不再选文件或直接执行转换。\n\n鸿蒙跳转由 `HomePage.ets → startAbility(QtWorkspaceAbility) → QPA.startQtApplication() → libhuiwen_qt.so\u002Fmain()` 完成。Qt 窗口实际显示后通过 NAPI 线程安全回调通知新 Ability；新 Ability 通过应用 EventHub 通知首页，只有发起跳转的首页消费一次通知并调用 `terminateSelf()`。这只结束首页 Ability，不结束应用进程。\n\n返回链为 `ConversionWindow::closeEvent() → QtBridge.request(\"close\") → QtWorkspaceAbility.returnHome()`：先启动首页，收到 `EntryAbility` 页面加载且进入前台的 `huiwen.home.ready` 后，经 `workspaceReply({action:\"close\"})` 通知 Qt 隐藏窗体并关闭浏览器实例；全部实例关闭后 Qt 回传 `closed`，工作区 Ability 才结束。QPA 已把系统标题栏关闭转为 Qt `closeEvent`，不重复注册关闭监听。CEF 平台的关闭请求也复用 `returnHome()`。\n\nQPA 在进程内只启动一次 Qt 主循环，CEF 引擎也保留到应用退出。每次工作区注册\u002F解除现有 NAPI 回调时，Qt 创建\u002F释放对应的 `ConversionWindow`，下次进入仍使用 `showMaximized()`；关闭工作区不调用 `CefShutdown()`。窗口管理暂放 `main.cpp` 与 `closeEvent()`，后续再迁入窗口基类。本次仅做格式检查，连续往返和 CEF 展示仍需真机验收。\n\n当前 QPA 固定加载 `pages\u002FIndex`，不能让该路由同时作为 ArkTS 首页。`EntryAbility` 加载独立的 `pages\u002FHomePage`；`Index.ets` 仅创建 Qt 的 NODE 类型 XComponent。`QtWorkspaceAbility.newLocalStorage()` 提供存储，QPA 写入 `idName` 并传给承载页。若在承载页放首页事件监听，会在 Qt 就绪时把 Qt Ability 也关闭；缺少存储方法则会产生 `newLocalStorage not found`、`Need object` 等日志。\n\n文件选择链为 `DocumentView::cardActivated → ConversionWindow::selectDocument() → SandboxService → QtBridge → QtWorkspaceAbility → DocumentStore`。Qt 请求进入 ArkTS 线程处理；结果经 NAPI 投递到 Qt 线程，不跨线程直接操作界面。新 Ability 使用自己的 Context，首页关闭后文件选择仍可继续。DocumentStore 只保留 Picker、URI 授权打开及关闭；目录、时间戳、命名、最近使用和缓存规则由通用 C++ `SandboxStore` 负责；鸿蒙授权 fd 复制与适配由 `HarmonySandbox` 负责。\n\n仓库中的 `third_party\u002Fmarkitdown` 固定源码版本，**不会把整个第三方 Git 仓库装进应用**。`pack_python_hnp.sh` 只部署 Python 解释器、目标依赖及其原生 `.so` 和自检脚本；`build_python_bundle.py` 把 `markitdown\u002F`、`huiwen_converter\u002F`、固定转换入口及许可材料装入独立 `.pyz`，应用 `markitdown-no-magika.patch`，不修改子模块源码。两个产物随同一个 HAP 分发；NAPI、Qt 应用和最小 Qt 运行库仍按原有方式部署。\n\n`EntryAbility` 和 `QtWorkspaceAbility` 在启动期调用 `configurePython(resourceManager, cacheDir)`。C++ 只读取固定的 `rawfile\u002Fpython\u002Fhuiwen_converter.pyz`，通过系统 ResourceManager 取出字节，以 SHA-256 命名保存到 `\u003C应用 cacheDir>\u002Fhuiwen-python\u002F\u003Csha256>.pyz`；缓存字节相同时不重写，缺失或内容不符时先写临时文件再替换。旧任务持有的旧版本文件不会在初始化新包时被覆盖，缓存可随应用缓存一起清理。\n\n转换继续使用 `convertPdf(inputPath, outputPath)` \u002F `RunPdfConversionResult()`，由 HNP 的 `bin\u002Fpython3 -B -u \u003C缓存 pyz> \u003CPDF> \u003CMD>` 启动固定业务入口。Python 从 pyz 导入业务模块，从 HNP 的 site-packages 加载依赖和原生扩展；不回退到 HNP 中旧的转换源码，不接受外部脚本路径。`pdf_to_md()`、stdout 最后一行 JSON 协议、错误码和页面调用方式保持原有边界。自检仍执行 HNP 的 `python_smoke.py`。\n\n### 接口边界\n\n| 位置 | 项目接口 | 调用者与职责 |\n| --- | --- | --- |\n| `converter\u002Fhuiwen_converter\u002F__init__.py` | `pdf_to_md(input_path, output_path) -> Path` | Python 公开业务接口；生成非空 MD 后返回路径，失败抛出异常。后续格式实现并验证后，才新增对应的显式接口。 |\n| `converter\u002Fhuiwen_converter\u002F_markitdown_adapter.py` | 内部 `pdf_to_markdown()` | 唯一直接导入 MarkItDown 的文件；不作为业务 API。 |\n| `entry\u002Fsrc\u002Fmain\u002Fets\u002Fservices\u002FConversionService.ets` | `pdfToMd(inputPath, outputPath): Promise\u003Cstring>` | ArkTS 页面调用；只转发到 NAPI，成功返回 MD 路径。 |\n| `entry\u002Fsrc\u002Fmain\u002Fcpp\u002Fnapi_bridge.cpp` | `configurePython(resourceManager, cacheDir)` | 启动期部署应用内置 pyz；两个 Ability 都提供自己的 Context，不接受外部脚本路径。 |\n| `entry\u002Fsrc\u002Fmain\u002Fcpp\u002Fnapi_bridge.cpp` | `convertPdf(inputPath, outputPath)` | 原生边界；异步用 HNP 解释器执行当前业务 pyz，不接受任意 Shell 命令。 |\n\n## 沙箱平台分层\n\n- `sandbox\u002FSandboxStore.h\u002F.cpp` 是 Qt Core 通用实现，负责 `preview`、`edit`、`convert` 三类目录、任务命名、最近记录、编辑缓存查询和目录归属校验，不引入 JSON、数据库或额外索引文件。鸿蒙把它编入 `libhuiwen_native.so`，Qt 工作区链接复用；桌面直接编译此实现。\n- `SandboxService` 仍是 Qt 界面入口；鸿蒙通过 ArkTS Picker 获得授权，桌面使用 Qt 选择器。输入统一原子保存，失败只清理本次输入及空任务目录，不覆盖其他任务。\n- `platform\u002Fharmony\u002Fharmony_sandbox.h\u002F.cpp` 负责鸿蒙 fd 复制；NAPI 同步复制授权 fd 并设置子进程不继承，在工作线程以独立偏移分块读取，成功、失败和排队失败都释放 fd。`harmony_sandbox_napi.cpp` 的目录扫描也在工作线程运行。\n- `harmonyImportDocument(filesDir, name, fd, kind, mode)` 返回 `Promise\u003CHarmonySandboxDocument>`；`mode` 为 `preview`、`edit`、`convert`，取代原有布尔转换标记。支持 PDF、MD\u002FMarkdown、Word 导入归档；当前正式转换接口仍仅接 PDF→MD，归档支持 Word 不表示已经接入 Word 预览、编辑或转换。\n- `harmonyValidateOutput(filesDir, path)` 接受新任务的 `convert|edit\u002F\u003C任务>\u002Foutput\u002F*.md`，兼容历史 `outputs\u002F` 下的文件；仍检查真实路径、目录归属及非空可读 MD。历史数据保留原位，不批量迁移。\n- `harmonyValidateTaskDirectory(filesDir, path)` 直接复用 `SandboxStore::validateTaskDirectory()`，只接受真实可读且不含符号链接或路径别名的 `edit|convert\u002F\u003C任务>\u002Finput|output` 目录。与 MD 文件校验分开，因此选取 PDF 后即可请求打开空输出目录；鸿蒙经 `openDirectory` 工作区事件调用系统文件管理器，桌面使用 `QDesktopServices`。\n\n## 窗口包裹图：Qt 工作区\n\n下图对应当前源码中两侧均显示正文预览时的结构。外层表示平台承载，内层表示 Qt 控件与画面的视觉包裹；`*` 标注的 CEF Surface 只表示画面位置，其实际原生父窗口见图后说明。本次通用化重构仅完成格式检查，不代表已重新完成编译、打包或设备验收。\n\n```text\n┌─ 鸿蒙系统窗口：QtWorkspaceAbility \u002F WindowStage ────────────────────────────────────────────────────────────────┐\n│ ┌─ Index.ets：XComponent NODE \u002F QPA ──────────────────────────────────────────────────────────────────────────┐ │\n│ │ ┌─ ConversionWindow : QWidget ────────────────────────────────────────────────────────────────────────────┐ │ │\n│ │ │ FunctionBar：[ 文件 ] [ 滚动设置 ]                                                                      │ │ │\n│ │ │ ┌─ WorkspaceSplitter ─────────────────────────────────────────────────────────────────────────────────┐ │ │ │\n│ │ │ │ ┌─ DocumentView A : QStackedWidget ────────────┐ ║ ┌─ DocumentView B : QStackedWidget ────────────┐ │ │ │ │\n│ │ │ │ │ 当前页：PDF 预览                             │ ║ │ 当前页：MD 预览                              │ │ │ │ │\n│ │ │ │ │ ┌─ PdfPreviewWidget ───────────────────────┐ │ ║ │ ┌─ MarkdownPreviewWidget ──────────────────┐ │ │ │ │ │\n│ │ │ │ │ │ 继承 RasterDocumentViewport（同一控件）  │ │ ║ │ │ 位置、尺寸及生命周期控制                 │ │ │ │ │ │\n│ │ │ │ │ │ ┌─ viewport : QWidget ───────────────┐ ▐ │ │ ║ │ │ ┌─ CEF 原生 Surface* ────────────────┐   │ │ │ │ │ │\n│ │ │ │ │ │ │ ┌─ createWindowContainer ────────┐ │ ▐ │ │ ║ │ │ │ MD 正文                            │   │ │ │ │ │ │\n│ │ │ │ │ │ │ │ ┌─ RasterRenderWindow ───────┐ │ │ ▐ │ │ ║ │ │ │ 浏览器绘制、滚动及惯性             │   │ │ │ │ │ │\n│ │ │ │ │ │ │ │ │ PDF 正文                   │ │ │ ▐ │ │ ║ │ │ │                                    │   │ │ │ │ │ │\n│ │ │ │ │ │ │ │ │ 独立 QBackingStore         │ │ │ ▐ │ │ ║ │ │ │                                    │   │ │ │ │ │ │\n│ │ │ │ │ │ │ │ └────────────────────────────┘ │ │ ▐ │ │ ║ │ │ │                                    │   │ │ │ │ │ │\n│ │ │ │ │ │ │ └────────────────────────────────┘ │ ▐ │ │ ║ │ │ │                                    │   │ │ │ │ │ │\n│ │ │ │ │ │ └────────────────────────────────────┘ ▐ │ │ ║ │ │ └────────────────────────────────────┘   │ │ │ │ │ │\n│ │ │ │ │ └──────────────────────────────────────────┘ │ ║ │ └──────────────────────────────────────────┘ │ │ │ │ │\n│ │ │ │ └──────────────────────────────────────────────┘ ║ └──────────────────────────────────────────────┘ │ │ │ │\n│ │ │ │                                              [ 转化 ] ← 悬浮于分隔条下部                            │ │ │ │\n│ │ │ └─────────────────────────────────────────────────────────────────────────────────────────────────────┘ │ │ │\n│ │ │ 状态提示 QLabel                                                                                         │ │ │\n│ │ └─────────────────────────────────────────────────────────────────────────────────────────────────────────┘ │ │\n│ └─────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ │\n└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘\n```\n\n- **平台承载**：`QtWorkspaceAbility` 管理鸿蒙系统窗口；`Index.ets` 的 `XComponent NODE` 通过 QPA 承载 Qt。ArkTS 首页属于另一个 Ability，不是工作区下面的一层背景。\n- **工作区**：`ConversionWindow` 组装 `FunctionBar`、`WorkspaceSplitter` 和状态提示。`ConvertButton` 是主窗口的直接子控件，图中只标出悬浮位置；它不占分栏布局空间，按需显示并位于正文和分隔条之上。\n- **共用区域**：两侧都是 `DocumentView : QStackedWidget`，本身就是页面切换容器，不再额外包一层业务 QWidget。工作区分别注入 PDF 和 Markdown 渲染器，并接收文件卡片、打开完成与滚动信号；两侧不互相调用。\n- **PDF 预览**：`PdfPreviewWidget` 继承 `RasterDocumentViewport`，两者是同一个控件，不是父子包裹的两层。其 `viewport` 内的 `createWindowContainer()` 嵌入 `RasterRenderWindow`，后者使用独立 `QBackingStore` 绘制。图中 `▐` 是位于正文视口外侧的 `DocumentScrollBar`。\n- **Markdown 预览**：`MarkdownPreviewWidget` 是 Qt 中的占位及控制控件。CEF 的实际原生父窗口是 `ConversionWindow` 对应的 Qt 顶层窗口，按占位区域映射位置与尺寸，并使用自己的 Surface；它不是普通 QWidget 子画布。\n- **非可视能力**：`DocumentRenderer` 是 C++ 渲染接口；`DocumentScrollController` 是 Qt 滚动能力对象；`QBackingStore` 和页面缓存是内部数据／绘图对象。这些都不构成额外可视壳层。\n- **能力配置**：`DocumentView::setViewOptions()` 接收 `DocumentViewOptions`，默认启用 `inertiaEnabled` 和 `scrollBarVisible`。前者只控制 Qt 侧追加的惯性；CEF 保留引擎自身的手势处理，只映射滚动条显隐。`scrollOwner()` 区分 View 管理滚动与 Backend 管理滚动，不在 CEF 外再加 Qt 滚动区域。\n- **复用边界**：卡片与预览切换不依赖输入／输出角色。后续 Word 渲染器可实现同一契约后注入；Word 引擎尚未接入，卡片的 `kind` 参数只选择图标，不会自动创建对应格式的渲染器。\n- **画面隔离与通信**：PDF 和 MD 正文分别使用独立画面缓冲。此前独立 PDF 缓冲方案获得用户真机无拖动残影的反馈，本次重构仍需重新验收；这也不代表模拟器 CEF GPU 崩溃已解决。QPA 负责窗口、绘制和输入适配，NAPI／`QtBridge` 负责文件选择、任务恢复与返回首页等业务消息。\n\n切到占位图模式或尚无文件时，`DocumentView` 显示另一页；两页互斥显示，卡片的滚动区域不会包在 PDF 或 CEF 正文外侧：\n\n```text\n┌─ DocumentView：卡片页（两侧共用） ─────────────────────────────────────┐\n│ QScrollArea → viewport → 内容 QWidget → FileCard                       │\n│ 显示图标、文件名或选择提示；正文预览页隐藏                             │\n└────────────────────────────────────────────────────────────────────────┘\n```\n\n## Qt 文件菜单\n\n顶部灰色功能条由 `FunctionBar` 组装，`FileMenu` 只维护菜单状态、发出请求，`ConversionWindow` 负责协调预览和沙箱服务；两侧区域不持有“预览／返回文件、更换文件、打开所在目录”工具按钮。`SelectionMenu` 统一绘制右侧 `#3983F7` 蓝点（直径 14px，圆心距菜单项右边缘 24px），子菜单箭头放在更靠右的位置，文件菜单与滚动设置保持对齐。\n\n```text\n文件\n├── 查看模式\n│   ├── 占位图模式\n│   ├── 预览模式（默认）\n│   └── 编辑模式（禁用，TODO）\n├── 文件目录\n│   ├── 输入目录\n│   └── 输出目录\n└── 切换模式\n    ├── 预览模式（禁用，TODO）\n    ├── 编辑模式（禁用，TODO）\n    └── 转化模式（当前，右侧蓝点）\n        └── 更换文件\n```\n\n- 查看模式同时控制左右区域，进入工作区和恢复任务时默认选中预览；未选择 PDF 时仍显示选择入口，未生成 MD 时右侧继续显示等待提示。选择新文件和开始转换保留当前查看方式，转换成功自动切回预览，并同步菜单蓝点；“切换模式”中的业务模式始终为转化。\n- 仅当前“转化模式”拥有更换入口；取消保留原任务、结果、查看方式和按钮状态，选取成功创建新任务、清空旧结果并恢复转化按钮。按钮显隐直接读取 `DocumentView::hasDocument()`（结果区域实例），不依赖 CEF 加载完成，不重复维护完成状态。选择或转换期间禁用查看方式切换与更换文件，避免任务串线。其他业务模式和编辑视图只保留 TODO，没有接入业务切换。\n- 输入、输出目录均定位当前任务下的目录，未选择 PDF 时禁用；系统文件管理器是否准确定位仍需真机确认。原 MD 文件卡片的目录跳转继续使用非空文件校验。\n- “滚动设置”的菜单选中项直接控制联动，不再保留隐藏复选框作为第二份状态。默认“同步滚动”：左右预览均就绪时，任意一侧滚动带动另一侧，优先按 PDF 页码对齐，无页码时按总进度同步；切回同步时按当前 PDF 位置对齐一次。“单向滚动”：只滚动当前操作区域，不向另一侧转发位置。更换文件、恢复任务和切换查看方式沿用当前滚动设置。本次接线仅做格式检查，切换效果仍需设备验收。\n\n分隔区和滚动条外观放在独立组件中：\n\n- `WorkspaceSplitter` 独立封装分隔区，沿用 Qt 原生拖动。普通分隔线为 1px；按住拖动时主线为 5px，颜色 `#546987`，鼠标 Y 坐标处显示宽 10px、长 180px、圆角半径 5px 的同色指示条，中心随鼠标上下移动，松开后恢复普通状态。命中区为 11px，容纳加宽的指示条；按下或松开时不因线条变粗改变布局。\n- 鸿蒙分隔条使用独立原生子节点；分隔条显示、开始拖动和 PDF／MD 就绪时显式提升到 CEF 之上，移动时沿用该层级，避免每个鼠标事件都等待一次 ArkUI 置顶调用。覆盖范围仅限自身 11px 区域；悬浮转化按钮仍可覆盖分隔条。其他平台沿用 Qt 兄弟控件层级。\n- `DocumentScrollBar` 只维护文档竖向滚动条外观：宽 24px、滑块最小高度 30px、常态 `#c6c6c6`、无圆角、透明轨道且无上下箭头。公共 `RasterDocumentViewport` 安装滚动条后，再由 `DocumentScrollController` 绑定动画；PDF 后端处理页码定位，两侧同步由工作区协调。MD 继续使用 CEF 滚动条。\n\nPDF 查看器继续使用 PDFium 与 Qt Widgets；`PdfPreviewWidget` 负责页面、缓存和同步定位，公共栅格视口负责绘图承载，`DocumentScrollController` 负责 Qt 输入与惯性：\n\n- 有像素位移时即时跟手；鸿蒙触控板的物理像素按窗口像素比换算为逻辑像素，并修正方向，避免高分屏放大滑动速度；鼠标不取反，Windows\u002FmacOS 沿用 Qt 像素方向。换算后的不足一像素位移累计到下次，不因取整丢失慢速输入。只有角度位移的滚轮沿用 100ms 短动画。\n- 触控板记录近期位移与时间，松手后按末速度匀减速续滑，最长 600ms；慢速、停顿或到达边界时不续滑。零位移的 `ScrollEnd` 也会处理，并等待 32ms 接收系统惯性；收到 `ScrollMomentum` 时取消自有惯性，避免叠加。再次触碰、反向滑动、操作滚动条、对侧同步定位、换文件、调整尺寸或隐藏预览会中止旧运动。\n- `components\u002Fscrolling\u002FDocumentScrollController.cpp` 中的 `touchpadSpeed`（默认 1.0）和 `touchpadDeceleration`（默认 0.005 逻辑像素\u002Fms²）集中控制速度与减速。当前已统一位移单位；与右侧 CEF\u002FMD 的实际续滑距离、时长及速度仍需真机对比校准，不能将默认参数视为与浏览器手感完全一致。\n- PDF 正文通过通用 `RasterRenderWindow` 的独立 `QWindow + QBackingStore` 绘制，由 `RasterDocumentViewport` 使用 `createWindowContainer()` 嵌入视口。原生窗口将滚轮、触控板阶段和键盘事件转交公共视口；Qt 滚动能力由 `DocumentScrollController` 提供，PDFium、页面缓存与页码定位留在 PDF 后端。滚动复用独立缓冲中的像素，仅补画新露出的条带；后台页面图片完成后仍只刷新对应区域。关闭时先解除绘制和输入回调，再等待后台任务完成。\n- PDF 原生容器只创建自身节点，避免连带把整个分栏提升为不透明原生窗口；PDF 或 MD 原生预览显示后，工作区统一恢复分隔条和悬浮转化按钮的层级。鸿蒙日志 `HUIWEN_RASTER_SURFACE backend=independent-raster created` 直接写入 HiLog，用于确认新容器已构造。2026-10-30 用户反馈此前独立缓冲方案真机拖动已无残影；本次通用化重构仅做格式检查，未新增编译、打包或设备验收。\n- 拖动分隔条或调整窗口时，PDF 每约 16ms 合并一次最新布局与滚动同步请求，实时绘制缓存位图并临时关闭平滑缩放；尺寸停止变化 150ms 后恢复平滑绘制并请求高清页面。换文件或隐藏预览时取消待处理的布局与缩放任务。MD 保持原有实时更新，未增加人为延迟。调整尺寸期间不重新解析 PDF。\n- CEF 原生窗口的几何更新只保留一个待处理任务，尚未执行的请求合并为最新位置和尺寸；实际应用时跳过重复状态与空尺寸，关闭后的任务不再操作原生节点。仅变化的属性交给 ArkUI\u002FXComponent 更新，不再调用离屏渲染专用的 `WasResized()`、`WasHidden()` 或限 Windows\u002FLinux 的 `NotifyMoveOrResizeStarted()`。此调整用于收敛窗口更新；模拟器之后仍出现同地址的 CEF GPU 崩溃，不能将其视为崩溃已修复。\n- 保留必要错误日志：`HUIWEN_CEF stage=geometry_failed` 表示 CEF 原生节点更新失败，`HuiwenCEF` 记录引擎及页面生命周期；公共栅格绘图窗口构造时输出一次 `HUIWEN_RASTER_SURFACE`。临时逐次拖动和绘制采样日志已移除。\n- 优先后台串行渲染当前可见页，再在 64 MiB 缓存预算允许时预取下一页、上一页；没有余量时跳过预取。每页采样预算按布局中可能同时可见的最多页数确定，不随滚动位置变化，避免跨页导致缓存尺寸反复失效。超长页仍降采样，不做整份文档预加载或分块渲染。\n- 触控板、鼠标滚轮、跨页及同步／单向滚动需按设备逐项验收；真机残影通过的反馈不等于 Windows\u002FmacOS 或模拟器验收通过。\n\n## 沙箱文件路径\n\n鸿蒙根目录取自 `getApplicationContext().filesDir`；Windows\u002FmacOS 使用 Qt 的 `AppDataLocation`。设备实际物理映射仍以运行时为准，以下仅展示 `files\u002F` 下的规则：\n\n```text\nfiles\u002F\n├── preview\u002F\n│   └── 20261024_173015_报告\u002F\n│       └── 报告.pdf\n├── edit\u002F\n│   └── 20261024_173015_报告\u002F\n│       ├── input\u002F\n│       │   └── 报告.docx\n│       └── output\u002F\n│           └── 报告.docx\n└── convert\u002F\n    └── 20261024_173015_报告\u002F\n        ├── input\u002F\n        │   └── 报告.pdf\n        └── output\u002F\n            └── 报告.md\n```\n\n- 任务目录名使用设备本地时间 `yyyyMMdd_HHmmss_文件名`（不含扩展名）；同秒同名以 ` (2)`、` (3)` 区分，输入输出共用此目录。普通文件名保留原样；非法路径字符拒绝，跨平台文件名特殊字符替换为下划线，过长文件名明确报错；仅目录中的文件名部分按 UTF-8 长度截短。\n- 预览直接保存文件；编辑、转化创建 `input\u002F` 和 `output\u002F`。导入时仅生成输入副本及预定输出路径，输出文件在实际保存或转化时生成。失败重试沿用当前任务目录，重新选取文件创建新目录。\n- 目录名时间表示创建时间，任务目录的修改时间表示最近使用时间。导入完成、PDF 成功打开、转换开始及结束都会显式更新任务目录修改时间；时间写入失败记录 `HUIWEN_SANDBOX stage=touch_failed`，不会把已经成功的转换改判失败。扫描列表和查询缓存本身不更新时间，也不修改目录名。\n- 最近任务只扫描三类目录的一层任务子目录，合并后按 `lastUsedAt`（毫秒时间戳）降序，时间相同时按任务目录名排序。跳过无输入、失效目录、越界符号链接或不可读缓存；旧目录继续支持输出定位，但不伪造旧任务记录。\n- 按文件名查询编辑缓存会返回全部同名候选，按最近使用排序。`openPath` 优先指向已有、可读的 `output\u002F原文件名`，尚无输出时指向 `input\u002F原文件名`；输出已存在但无效时跳过该候选。调用页面可对唯一结果直接打开、多个结果展示选择，成功打开或保存后再标记使用。\n\n| ArkTS 入口（`utils\u002FDocumentStore.ets`） | 用途 |\n| --- | --- |\n| `pickDocumentToSandbox(context, kind, DocumentMode.Preview|Edit|Convert)` | 系统选取、授权读取并按类型归档；取消不创建任务 |\n| `getRecentDocuments(context, limit = 5, mode = 'all')` | 后台读取最近任务，数量为 1–100；可筛选任一类型 |\n| `findEditCaches(context, name)` | 按原文件名查找所有同名编辑缓存，返回候选与 `openPath` |\n| `markDocumentUsed(context, taskPath)` | 未来预览\u002F编辑页面在实际打开或保存后更新时间 |\n\n这些接口通过 NAPI 调用同一份 `SandboxStore`，返回名称、格式、模式、`taskPath`、`inputPath`、`outputPath`、`openPath` 和 `lastUsedAt`。Qt 侧可直接使用 `SandboxStore::recentDocuments()`、`findEditDocuments()`、`markUsed()`。首页已接最近工作区列表；同名候选选择 UI 和独立编辑工作区仍未接入，首页预览和编辑入口仍只展示格式选项。\n\n## 首页最近工作区\n\n- 右侧主区顶部保留“开始使用”和三个模式入口，顶部留白 44vp；提示文案为“请选择转化模式”，与三个按钮区域的间隔为 84vp（原 24vp 间距加按钮区顶部 60vp）。上区使用普通 `Column` 按内容高度排列，不使用 `Scroll` 或纵向 `layoutWeight(1)`；父容器顶部排列且间距为 0，最近工作区通过自身顶部外边距与上区保持 50vp 距离，不再贴底。16vp 标题在记录滚动区外固定显示；窗口大小及左侧介绍区比例不变。\n- `RecentWorkspaceService.listRecentWorkspaces(context, limit = DEFAULT_RECENT_WORKSPACE_LIMIT)` 默认取三条，数量常量为 `3`，底层仍支持 1–100。扫描 `preview\u002Fedit\u002Fconvert` 三类任务，按目录修改时间降序排列，不创建 JSON、数据库或新任务目录。\n- 每行高度为 48vp，模式图标底框 28vp、内部 SVG 18vp；文件名、格式、时间字号分别为 14、12、11，加载、空列表及失败提示为 12。格式列为 104vp、时间列为 152vp，格式与时间间隔 8vp。文件名使用 `layoutWeight(1)` 占满剩余宽度，单行中部省略，鼠标悬停显示全名；固定列不参与收缩。时间显示设备本地 `yyyy-MM-dd HH:mm`，使用灰色文字。\n- 仅记录区域使用 `Scroll`，标题不进入滚动容器；列表高度按条数计算并以 `maxListHeight` 限制，默认最大 240vp，可由调用方传参调整。默认三条时高度为 160vp，单条为 48vp，不补空行；更多记录超过最大高度后在内部滚动。\n- 行点击直接通过 Want 的 `huiwenTaskPath` 传任务标识，跳过格式弹窗和选择器。首页先检查任务，Qt Ability 再经异步 `harmonyLoadTask(filesDir, taskPath)` 调用 `SandboxStore::loadTask()` 校验；只允许已接入的 PDF→MD，重新读取原输入和预定输出路径，不信任页面缓存的文件路径。其他模式显示对应图标和标签，但暂不开放恢复。\n- `SandboxService::restorationRequested()` 通知 `ConversionWindow::restoreDocument()`；窗口正在选择或转换时拒绝替换，原 PDF 失效时也不覆盖当前任务。恢复时切到预览，加载原 PDF 和非空的已有 MD，并隐藏转化按钮；MD 不存在或为空时继续显示等待提示并保留按钮，不自动重新转换。\n- Qt 载入后调用 `SandboxService::finishRestoration()` 更新时间，通过 `restoreResult` 回执通知 Qt Ability，成功后才发出首页关闭通知。读取列表、恢复前校验及恢复失败不更新时间；目录名的创建时间不变。新启动及 `onNewWant` 复用窗口都走同一恢复链。日志前缀为 `HUIWEN_QT_RESTORE_OK` \u002F `HUIWEN_QT_RESTORE_FAILED`；系统实际交接与显示仍需真机验收。\n- 首页查询和恢复前校验可能先于 `QApplication` 执行；任务命名校验使用字符检查，PDF 预读使用 C++ 标准文件流，避免在 NAPI 工作线程通过 `QFile`（`QObject`）或正则 JIT 提前初始化 Qt 线程数据，导致工作区鼠标事件触发主线程断言。\n- 启动顺序回归检查独立于 PDFium：`cmake -S qt_app\u002Ftests\u002Fsandbox_startup -B \u002Ftmp\u002Fhuiwen-sandbox-startup -DCMAKE_PREFIX_PATH=\u003C宿主Qt5路径>`，然后 `cmake --build \u002Ftmp\u002Fhuiwen-sandbox-startup`，最后 `QT_QPA_PLATFORM=offscreen \u002Ftmp\u002Fhuiwen-sandbox-startup\u002Fsandbox_startup_check`。检查先在线程中查询、校验任务，再创建 `QApplication` 并分发鼠标事件；它不能替代鸿蒙真机恢复验收。\n\n当前 Qt PDF→MD 选择链改为传递 `DocumentMode.Convert`，使用 `convert\u002F\u003C任务>\u002Finput` 与 `output`。转换成功才展示 MD 卡片，扫描件仍按原有错误提示处理；Python 继续接收具体输入、输出路径。点击 MD 卡片仍通过 `openLink('filemanager:\u002F\u002FopenDirectory')` 传入校验后的结果 URI，打开真实输出目录，不创建应用内文件浏览器。\n\n## 应用图标\n\n统一使用 `.native-tools\u002Fdesign\u002Fhuiwen-icon-rounded-final.png` 的最终版图标，原图为 1024×1024 PNG，保留透明圆角。正式资源直接随仓库保存，构建使用以下文件：\n\n- `AppScope\u002Fresources\u002Fbase\u002Fmedia\u002Fapp_icon.png`：应用级图标，由 `AppScope\u002Fapp.json5` 引用。\n- `entry\u002Fsrc\u002Fmain\u002Fresources\u002Fbase\u002Fmedia\u002Fapp_icon.png`：ArkTS 首页与 Qt 工作区 Ability 图标，以及各自的启动页图标。\n- `qt_app\u002Fresources\u002Fbranding\u002Fapp_icon.png`：Qt 窗口图标，通过现有 QRC 注册为 `:\u002Fbranding\u002Fapp_icon.png`，在 `main.cpp` 中统一设置。\n\n三份资源均直接复制最终版 PNG；更新品牌图标时同步替换这三份文件。\n\n## 本地构建\n\n工程当前配置为 HarmonyOS PC、`arm64-v8a`，目标 SDK 为 `6.1.1(24)`。首次克隆后，将 `build-profile.example.json5` 复制为本机的 `build-profile.json5`，需要安装到设备时再在 DevEco Studio 配置签名。\n\nmacOS ARM64 开发机首次 Run 会按 `third_party\u002Fprebuilt\u002Fmanifest.json` 下载、校验并恢复固定的\nQt\u002FCEF `.so`、头文件、CMake 配置、宿主工具和浏览器核心资源；后续 Run 校验本地文件后复用。\n下载地址对应的依赖包须先发布，离线机器可通过 `HUIWEN_PREBUILT_ARCHIVE` 指定同一压缩包。\n包哈希或本地依赖不匹配会停止构建，不覆盖已修改的库。详见 [固定依赖包](third_party\u002Fprebuilt\u002FREADME.md)。\n\n维护者准备或升级 Qt SDK 时，仍可导入同一套鸿蒙 Qt 5.15.12 开发文件和五个运行库：\n\n```sh\npython3 packaging\u002Fohos\u002Fprepare_qt.py \\\n  --source \u002F绝对路径\u002FQt5.15.12-ohos17-arm64-v8a \\\n  --license-source \u002F对应的\u002Fqtbase源码目录\n```\n\n`--source` 也可由原有 `qt.sdk.dir` 或 `QT_PREFIX` 指定，但它们只用于准备\u002F升级依赖，正常 CMake 构建使用项目内 `third_party\u002Fqt\u002F`。库的唯一部署位置是 `entry\u002Flibs\u002Farm64-v8a\u002F`，Qt 开发包内通过相对符号链接引用；宿主工具必须匹配构建电脑。修改 Qt 内核后需先编译新的 SDK，再执行准备命令同步整套依赖。\n\n当前 PDF 残影修复使用应用内独立 `QBackingStore`，不要求修改或重新编译 Qt SDK。\n\nPDFium fork 为 `xristosstarnetgames439-del\u002Fpdfium` 的 `huiwen-dev` 分支，首次同步提交记录官方源码 SHA；主项目固定子模块提交。首次构建会准备 DEPS 锁定的 Git 依赖、GN 和 Clang，之后由 Ninja 增量编译当前工作树，不重置用户的源码修改：\n\n```sh\ngit submodule update --init third_party\u002Fpdfium\npython3 packaging\u002Fohos\u002Fbuild_pdfium.py --sync\n```\n\n脚本使用鸿蒙 NDK sysroot、归档器和链接驱动，生成 `aarch64-linux-ohos` 动态库；内部 libc++ 静态链接，关闭 V8、XFA、Skia 和 PartitionAlloc，保留公开渲染、编辑及保存接口。通过 ELF 架构、必要导出和动态依赖检查后，原子更新 `entry\u002Flibs\u002Farm64-v8a\u002Flibpdfium.so`，同时生成源码\u002F构建参数\u002F校验值记录及分发许可材料。构建失败不会覆盖有效旧库，但会阻止新 HAP 生成。可设置 `OHOS_NDK_HOME` 指向 `native\u002F`。\n\nDevEco Run 的顺序为 `PreBuild → PrepareNativeDependencies（恢复固定 Qt\u002FCEF，再增量构建 PDFium）→ PrepareMarkdownResources → ConfigureCmake \u002F CompileResource → BuildNativeWithNinja → ProcessLibs → PackageHap → RepackHapWithHnp → SignHap`。PDFium 编译和 Qt\u002FNAPI 库同步在收集原生库之前完成；未改动的源码复用产物。这里只实现运行构建时更新，没有文件监听器，也没有在运行进程内卸载和替换 C++ 库。\n\nMarkdown 另由 `PrepareMarkdownResources` 在 `PreBuild` 后、`ConfigureCmake`、`CompileResource` 和 `PackageHap` 前准备。\n同一资源准备任务还使用宿主 Python 执行 `build_python_bundle.py`：收集当前转换代码与许可、应用补丁、固定 zip 成员顺序\u002F时间\u002F权限；新旧 pyz 字节相同则输出 `[HUIWEN_PY_BUNDLE] CACHED` 并保留时间戳，否则原子替换并输出 `GENERATED`。不复制运行环境或目标 wheel，也不将本机字节码和原生扩展放入 pyz。\n它复用 Hvigor 的 Node\u002Fnpm，首次 Run 联网按锁文件自动安装依赖，之后每次构建并比对\n`entry\u002Fsrc\u002Fmain\u002Fresources\u002Frawfile\u002Fmarkdown\u002F` 下的 JS\u002FCSS、页面、许可证和校验清单；仅原子替换内容变化的文件，其余保留时间戳，避免 Qt RCC 无意义重编。全部相同时输出 `[HUIWEN_MD] CACHED`，有更新时输出 `GENERATED`，资源随 HAP 收集；\n安装或构建失败会停止打包。通用预览模板还经 Qt qrc 编入工作区动态库，直接接收 MD 原文，\n不为每篇 MD 另存 HTML。CEF 运行库使用项目内预编译版本，仅其包装层参与 C++ 编译；\n轻量 HAR 模块已固定在 `third_party\u002Fcef\u002Fweb_engine\u002F`，OHPM 首次同步无需等候大依赖下载。\nPython HNP 的运行时路径仍按下文配置，本次 Qt\u002FCEF 自动准备不替代该配置。详见 [Markdown 构建说明](third_party\u002Fmarkdown\u002FREADME.md)\n和 [CEF 环境说明](third_party\u002Fcef\u002FREADME.md)。\n\n需要核对更新链路时，可运行 `python3 packaging\u002Fohos\u002Fcheck_run_update.py`。它临时向 PDFium 加入两版构建探针，分别执行 HAP 构建并核对 `libs` 与 HAP 中的实际库内容，最后移除探针并重建最终包；不会安装或启动设备应用。该检查需要已配置可用的 HAP 签名，也会执行原有 Python HNP 打包。\n\n首次克隆时连同固定版本的 MarkItDown 子模块一起下载：\n\n```sh\ngit clone --recurse-submodules https:\u002F\u002Fgithub.com\u002Fxristosstarnetgames439-del\u002FHuiwenMCP.git HuiWenMCP\ncd HuiWenMCP\ngit submodule status third_party\u002Fmarkitdown\n```\n\n已经克隆了主仓库的开发者，在仓库根目录补拉子模块：\n\n```sh\ngit submodule update --init third_party\u002Fmarkitdown\ngit submodule status third_party\u002Fmarkitdown\n```\n\n再取得鸿蒙 arm64 Python 3.14 的已解压安装目录，要求包含 `bin\u002Fpython3` 和 `lib\u002Fpython3.14\u002Fsite-packages\u002F`。原有自检使用 [Harmonybrew 的预编译 Python 3.14.6](https:\u002F\u002Fgithub.com\u002FHarmonybrew\u002Fohos-python\u002Freleases\u002Ftag\u002F3.14.6) 通过。在被 Git 忽略的 `local.properties` 中配置本机路径：\n\n```properties\npython.runtime.dir=\u002F绝对路径\u002Fpython-3.14.6-ohos-arm64\n# 可选：指定本机带 pip 的 Python，避免 DevEco 的 PATH 找不到解释器。\npython.host.path=\u002F绝对路径\u002F宿主\u002Fpython3\n```\n\n也可用 `HUIWEN_PYTHON_RUNTIME` 和 `HOST_PYTHON` 环境变量覆盖上述配置。相对配置路径按项目根目录解析。正常点击 DevEco Run 并执行 HAP 构建安装时，签名前的 `RepackHapWithHnp` 任务先检查 HNP 输入，未变化时复用成品；首次准备、输入变化或产物缺失\u002F校验不符时重新生成。仅热重载不保证执行此任务。也可手动执行同一检查和打包流程：\n\n```sh\npython3 packaging\u002Fohos\u002Fbuild_python_bundle.py\nbash packaging\u002Fohos\u002Fpack_python_hnp.sh \u002F绝对路径\u002Fpython-3.14.6-ohos-arm64\n```\n\n脚本使用本机 Python 的 pip 下载 `requirements-pdf.txt` 中锁定的鸿蒙 arm64\u002Fmusl wheel。目标依赖按清单 SHA-256 缓存在被 Git 忽略的 `.hnp-cache\u002F`，清单变化时重新安装。环境成品指纹保存在 `.hnp-cache\u002Fhnp-build.sha256`，只覆盖运行时目录、目标依赖清单、自检脚本、`hnp.json`、环境打包脚本和 `hnpcli`；按文件内容、路径、权限及符号链接目标计算，并校验已有 HNP 的 SHA-256。汇文 API、MarkItDown、转换入口和业务补丁的修改只影响独立 pyz；ArkTS、Qt 和 PDFium 修改也不会使环境 HNP 失效。\n\n需要重建时只复制运行时与目标依赖，清除运行时目录中可能残留的汇文 API、MarkItDown 和旧转换入口，再用 SDK 的 `hnpcli` 打包；成功后原子替换 `hnp\u002Farm64-v8a\u002Fhuiwen_python.hnp`，再写入新指纹。任一步失败仍停止构建。删除 `.hnp-cache\u002Fhnp-build.sha256` 或成品 HNP 可强制重打包；删除整个 `.hnp-cache\u002F` 可连同目标依赖一起重新准备。首次迁移到环境\u002F业务拆分会重建一次 HNP，之后仅修改业务代码不会重建环境。若 SDK 工具不在默认位置，可设置 `HNPCLI=\u002F绝对路径\u002Fhnpcli`。HNP 的 `name\u002Fversion` 固定为 `huiwen_python\u002F1.0.0`。\n\nMarkItDown 官方源码保持在子模块的 `v0.1.8` 提交；它采用 MIT 许可，可修改并商用，但分发时需保留许可与第三方声明。其官方包强制依赖 Magika\u002FONNX Runtime，而当前目标架构无法直接解析 ONNX Runtime wheel。仓库中的补丁只让 Magika 可选；本阶段输入已由选择器限定为 `.pdf`，按扩展名交给 PDF 转换器。补丁不改动子模块原始源码。pip 能解析目标架构 wheel 只说明可获取包，不代表鸿蒙设备能加载其中的原生 `.so`；需在设备上完成导入及实际 PDF 转换验收。\n\nHvigor 的默认 `PackageHap` 不会自动带入根目录的 HNP。`entry\u002Fhvigorfile.ts` 在 `PackageHap` 后、`SignHap` 前先准备 HNP，再调用 SDK `app_packing_tool.jar --hnp-path` 重打未签名 HAP，并检查环境 HNP 和 `resources\u002Frawfile\u002Fpython\u002Fhuiwen_converter.pyz` 均存在；复用环境不跳过注入和签名。构建日志中的 `[HUIWEN_HNP] CACHED\u002FGENERATED` 与 `[HUIWEN_PY_BUNDLE] CACHED\u002FGENERATED` 分别表示两个产物的状态。SDK 不在默认位置时设置 `DEVECO_SDK_HOME` 或 `OHOS_SDK_HOME`。设备日志筛选 `HuiwenPython`：`PYTHON_BUNDLE` 显示缓存业务包哈希及路径，`PYTHON_START` 显示 HNP 解释器与当前 pyz，`RUNTIME` 显示实际导入模块及算法标识。\n\n本次拆分仅做语法和格式检查，未执行构建、打包或设备运行。后续验收：首次迁移允许环境 HNP 和业务 pyz 各生成一次；原样复跑应同时命中缓存；只改 Python API 或 MarkItDown 时应仅见业务包 `GENERATED`、环境 `CACHED`；改目标依赖清单才应重建环境。安装后确认 `PYTHON_START` 的脚本来自缓存 pyz，实际完成一次 PDF 转换；可再运行 `packaging\u002Fohos\u002Fcheck_workspace_hap.py \u003Csigned.hap>` 检查 pyz 入口和许可。对比构建耗时，安装和无线传输另行计量；HAP 仍携带完整环境，并非设备差量安装。\n\n```sh\ncd entry && ohpm install\ncd ..\nhvigorw assembleHap --mode module -p product=default -p module=entry@default\n```\n\n也可以在 DevEco Studio 中使用 **Build Hap(s)\u002FAPP(s)**。修改代码后至少执行 `git diff --check`；需要验证设备行为时，再安装生成的签名 HAP 并启动 `EntryAbility`。Python 运行时自检已在真机通过；后续若更换运行时或 HNP 内容，可临时调用保留的 `checkPython()` 再验证。本机签名文件已被 Git 忽略，推送前用 `git status` 确认它没有进入提交。\n\n## 维护约定\n\n- 新增功能时更新“当前状态”，区分已运行的能力与尚未接入的入口。\n- 调整模块、原生库或打包方式时更新“工程结构”和“本地构建”。\n- 不把生成的 HAP、构建缓存或本机 SDK 路径写成跨机器通用的依赖。\n- 不提交 `entry\u002Flibs\u002F` 和 `third_party\u002Fqt\u002F` 中的本机开发依赖；NAPI、Qt 应用与 PDFium `.so` 按源码增量生成，Qt 运行库由准备脚本导入。主仓库只固定 PDFium 子模块提交，fork 修改在其独立仓库维护。\n\n## ArkTS 首页功能\n\n- 窗口大小和窄侧栏保持原有设置。除侧栏、外边距及分区间距外，介绍区与主区域使用 `layoutWeight(3)` \u002F `layoutWeight(7)` 分配宽度，主区域取消固定 360vp 宽度。\n- “开始使用”下三个入口保持等宽列布局，浅灰正方形边长为所在列宽的 50%，即上一版方块边长的一半；方块内只放居中的 SVG，文档预览、文档编辑、文档转化名称以普通字重显示在方块外下方。`HomeFeatureCard` 只负责显示和点击事件，`HomePage` 保存当前功能选项。\n- 点击入口后，按 `docs\u002F自定义弹窗模版.md` 使用 `getUIContext().getPromptAction().openCustomDialog()` 打开居中的独立格式选择子窗口，采用 `showInSubWindow: true`、`isModal: false` 的组合（该接口禁止子窗口同时启用模态，也不支持 `CustomDialogController` 的 `customStyle` 参数）；首页保存返回的弹窗 ID，通过 `closeCustomDialog(id)` 统一关闭，并处理打开失败、外部取消及首页退出期间的异步清理。系统背板与内容组件 `HomeFormatDialog` 共用 `HOME_FORMAT_DIALOG_WIDTH = 360`，内外宽度统一固定为 360vp，不再使用 90% 限制；高度随内容。只由系统背板绘制浅灰背景、16vp 圆角和阴影，关闭背景模糊，内部保持透明，避免宽度不一致留下透明空白。三个弹窗共用内容布局，内容居中；左右及底部内边距为 24vp，顶部为 40vp，为右上角关闭按钮留位；格式选项自身内边距为 12vp，转换入口使用透明背景，不再额外叠加白底和边框。弹窗不在首页内展开或预留面板高度，右上角仅保留 28×28vp 关闭按钮，内含 18×18vp 的 × SVG，也可点击外部取消。预览和编辑窗口仅展示 Word、PDF、MD，暂不选择文件或跳转；转化窗口展示 PDF→MD 组合图标入口。点击该入口后由 `HomeFormatDialog.onConvertRequested` 通知首页，关闭子窗口成功后再启动现有 Qt 工作区，仍在 Qt 窗口就绪后结束首页 Ability。启动期间禁用重复操作，失败显示错误。\n- `HomeFormatDialog` 通过 `title` 参数接收首页传入的标题，预览为“请选择要预览的格式”，编辑为“请选择要编辑的格式”，转化为“请选择要转化的格式”。标题以 18vp 普通字重显示在内容左上方，与格式选项间隔 16vp；保留右上角关闭按钮、360vp 内外统一宽度及原有背景设置。\n- 功能 SVG 使用 Feather `book-open`、`edit-3`、`repeat`，转化方向使用 `arrow-right`，弹窗关闭使用 `x`，许可随 `rawfile\u002Flicenses\u002Ffeather-icons-LICENSE.txt` 分发。格式 PNG 原样复用 Qt 已有图标，来源见 [文件格式图标说明](qt_app\u002Fresources\u002Ffile-icons\u002FREADME.md)。所有图标从应用资源加载，无需运行时下载。\n\n## ArkTS 首页侧栏\n\n- 侧栏固定在启动首页左侧，宽度 60vp；底部仅一个三横线按钮，尺寸 36×36vp，距底部 20vp。图标为 `resources\u002Fbase\u002Fmedia\u002Fsidebar_menu.svg`，来自 [Feather Menu](https:\u002F\u002Fgithub.com\u002Ffeathericons\u002Ffeather\u002Fblob\u002Fmain\u002Ficons\u002Fmenu.svg)，保留三横线几何形状，颜色跟随首页外观，以 24×24vp 的 `Image` 显示；[上游 MIT 许可](entry\u002Fsrc\u002Fmain\u002Fresources\u002Frawfile\u002Flicenses\u002Ffeather-icons-LICENSE.txt) 随 rawfile 分发。按钮保留零内边距，不依赖字符字体或文本省略行为。功能卡片显示在侧栏右侧的主区域。\n- `EntryAbility` 在页面加载后，先设置 `setWindowBackgroundColor('#00000000')`，再独立设置 `setWindowContainerColor('#00000000', '#FFF5F7FB')`；后者需要 `ohos.permission.SET_WINDOW_TRANSPARENT`，已在模块中声明。焦点态容器透明，非焦点态使用不透明底色。两个步骤各自处理异常，设置失败时保留必要错误日志，不阻断首页使用。根布局透明，侧栏背景为 `#fae5e9ec`（约 98% 不透明度），主内容背景仍为实色；只调整背景透明度，按钮保持清晰，不额外增加模糊效果。\n- 侧栏组件通过 `FunctionSidebar.onMenuRequested` 打开首页菜单，并通过 `onMenuAnchorChanged` 同步按钮锚点；Qt 转换工作区不增加侧栏。\n\n## Qt 工作区验收\n\n本阶段页面入口为 `qt_app\u002Fsrc\u002Fworkspace\u002FConversionWindow.cpp`。未创建后续侧栏目录；新增侧栏时使用 `sidebars\u002F`，避免与输入\u002F输出区域混淆。\n\n1. 点击 ArkTS 首页“文档转化”，再点击独立格式选择窗口中的“PDF → MD”，格式子窗口关闭后打开 Qt；Qt 默认最大化并保留系统标题栏、窗口边框及窗口控制按钮。查看 `QtWorkspace` 标签下的 `HUIWEN_QT_START`、`HUIWEN_QT_READY`，确认只有 `EntryAbility` 随后结束，`QtWorkspaceAbility` 保持运行；不应再出现 `newLocalStorage not found`、`Need object`。\n2. 左右文档区域占满状态栏上方的可用高度，底部状态栏保持紧凑；还原窗口并调整大小时布局应同步变化。悬停中间细线出现水平分割光标；拖动改变两侧宽度，底部小型“转化”按钮保持居中在线上。\n3. 进入工作区应默认选中“文件 → 查看模式 → 预览模式”，业务仍是“转化模式”。点击左侧虚线加号选择 PDF，默认直接显示 PDF 预览；手动切到占位图时显示文件卡片。拖动分隔线或缩放窗口后，页面应按当前可用宽度重排。“文件 → 切换模式 → 转化模式 → 更换文件”应保留查看方式、清空旧 MD、恢复转化按钮，预览方式下从新 PDF 第一页开始。取消选择应保留原文件、MD、查看方式及按钮状态。选择或转换期间不得重复更换；损坏、加密文件应提示原因。两侧不再显示旧工具按钮。\n4. 点击“转化”，操作期间不能重复选择或提交。即使转换前处于占位图模式，成功后也自动切到预览并更新菜单蓝点，直接展示 MD、隐藏转化按钮，底部显示 `code=0`、成功提示和完整路径。失败显示错误码及处理建议，保留“重新转化”按钮；重试成功后同样隐藏。选择新文件后按钮恢复，取消选择保留原状态；切换查看方式、拖动分隔线及缩放窗口不能重新显示已有结果的转化按钮。\n5. 转换成功后底部显示完整 MD 路径；切到占位图模式后点击 MD 卡片，沿用系统文件管理器打开文件夹，文件夹内能看到真实 MD。不存在、为空或不可读取的结果应在跳转前明确报错，不能误报转换成功。\n6. 日志搜索 `HUIWEN_OUTPUT`：`import_success` 为导入落盘路径，`convert_start` 为输入\u002F输出路径，`verified` 为磁盘验证及字节数，`convert_success` 为成功通知，`locate_start` 为点击时重新检查，`locate_requested` 为鸿蒙桥接已提交。系统跳转结果继续查看原有 `HUIWEN_QT_OPEN_REQUEST`、`HUIWEN_QT_OPEN_REQUEST_ACCEPTED` 或 `HUIWEN_QT_OPEN_FAILED`。\n7. 新导入生成 `convert\u002FyyyyMMdd_HHmmss_文件名\u002Finput\u002F原文件.pdf` 与 `output\u002F原文件.md`；同秒同名只对任务目录追加编号，文件名保持一致。首页默认展示最近三条记录，长文件名中部省略且不挤压固定列；点击应复用原任务、载入已有结果，不弹选择器、不新建目录。检查成功恢复、PDF 打开、转换开始及结束后任务目录修改时间会更新；查询和失败不更新时间。分别验证全新 Qt 窗口、已有窗口、转换中拒绝替换、PDF 被删除、尚无 MD 的任务。同一任务重试路径不变，历史 `outputs\u002Fmd\u002F` 继续支持原有目录跳转。\n8. 从最近工作区进入，应直接预览 PDF 与已有非空 MD，转化按钮隐藏；MD 不存在或为空时显示等待提示并保留按钮。点击系统标题栏关闭应回到首页并结束工作区 Ability。连续重复进入和关闭，包含 MD 仍在加载时关闭，确认无残留白窗、整体退出或 CEF 失效；每次进入仍最大化，首页在工作区载入成功后结束。\n\nPython 固定入口在 stdout 最后一行返回 `HUIWEN_CONVERT_RESULT {\"code\":0,\"message\":\"转化成功，Markdown 文件已生成。\",\"outputPath\":\"\u002F沙箱\u002Fconvert\u002F时间戳_文件名\u002Foutput\u002F文件.md\"}`。失败时 `code` 非零、`outputPath` 为空、进程退出码为 1；详细异常写入日志，界面展示中文原因和处理建议。C++ 独立保存完整结果行，日志尾部缓存仍限制为 4096 字节，结果不受日志截断影响。成功仍保留 `HUIWEN_CONVERT_OK` 标记，原 ArkTS 接口继续成功返回路径、失败拒绝 Promise。\n\n错误码：`1001` 参数或格式错误，`1002` 输入不存在，`1003` 加密或禁止提取，`1004` PDF 损坏或不支持，`1005` 无可提取文字（暂不支持 OCR），`1006` 读写权限不足，`1007` 存储不足，`1008` 组件缺失，`1099` 其他转换异常；`2001` 原生进程或结果协议异常，`2002` 输出文件校验失败。C++ 同时验证字段类型、退出码、预期输出路径及真实非空文件，不能仅凭成功字段通知界面。\n\nHAP 包结构校验：\n\n```sh\npython3 packaging\u002Fohos\u002Fcheck_workspace_hap.py entry\u002Fbuild\u002Fdefault\u002Foutputs\u002Fdefault\u002Fentry-default-signed.hap\n```\n\nWindows\u002FmacOS 的同一 Widgets 源码提供桌面入口，尚未编译验证和制作安装包；CMake 需通过 `PDFIUM_LIBRARY` 指定与宿主架构匹配的 PDFium 动态库。桌面转换需安装同一 Python 依赖、设置 `PYTHONPATH` 为仓库的 `converter\u002F`，`HUIWEN_CONVERT_SCRIPT` 为 `converter\u002Fpdf2md\u002Fconvert.py` 的绝对路径；可用 `HUIWEN_PYTHON` 指定解释器。\n\nPDF 预览桌面回归检查（独立工程，不加入 HAP；需要桌面 Qt 5.15 和对应宿主架构 PDFium）：\n\n```sh\ncmake -S qt_app\u002Ftests -B \u002Ftmp\u002Fhuiwen-preview-check \\\n  -DCMAKE_PREFIX_PATH=\u002F桌面Qt目录 -DPDFIUM_LIBRARY=\u002F本机PDFium动态库\ncmake --build \u002Ftmp\u002Fhuiwen-preview-check\nQT_QPA_PLATFORM=offscreen \u002Ftmp\u002Fhuiwen-preview-check\u002Fpdf_preview_check\n```\n\n该检查生成两页彩色 PDF，验证导入后按钮、真实页面像素、多页滚动、调整宽度、更换文档与忙碌状态。鸿蒙 C++ 编译及 HAP 打包已通过。\n",2,"2026-10-31 04:26:16","CREATED_QUERY"]