文章目录
-
- 一、写在前面
- 二、项目背景
- 三、总体变化概览
-
- [3.1 新增鸿蒙工程壳](#3.1 新增鸿蒙工程壳)
- [3.2 新增构建脚本](#3.2 新增构建脚本)
- [3.3 新增 Electron 启动引导](#3.3 新增 Electron 启动引导)
- [3.4 补充鸿蒙 remote/dialog 适配层](#3.4 补充鸿蒙 remote/dialog 适配层)
- [四、为什么 Pencil 选择保留桌面 Electron 编辑器](#四、为什么 Pencil 选择保留桌面 Electron 编辑器)
- 五、鸿蒙适配核心路径
-
- [5.1 第一步:同步 Pencil 原应用资源](#5.1 第一步:同步 Pencil 原应用资源)
- [5.2 第二步:写入鸿蒙启动入口](#5.2 第二步:写入鸿蒙启动入口)
- [5.3 第三步:切换用户数据目录](#5.3 第三步:切换用户数据目录)
- [5.4 第四步:处理文件默认路径](#5.4 第四步:处理文件默认路径)
- [5.5 第五步:建立 dialog IPC 桥](#5.5 第五步:建立 dialog IPC 桥)
- [5.6 第六步:把 picker URI 转成真实路径](#5.6 第六步:把 picker URI 转成真实路径)
- [5.7 第七步:构建 HAP](#5.7 第七步:构建 HAP)
- [5.8 第八步:保留 PDF 导出,降级实体打印](#5.8 第八步:保留 PDF 导出,降级实体打印)
- 六、问题困难解决
-
- [6.1 困难一:原应用资源不能简单当静态页面处理](#6.1 困难一:原应用资源不能简单当静态页面处理)
- [6.2 困难二:桌面数据目录在鸿蒙侧不能直接套用](#6.2 困难二:桌面数据目录在鸿蒙侧不能直接套用)
- [6.3 困难三:Export 点击后无响应](#6.3 困难三:Export 点击后无响应)
- [6.4 困难四:picker 返回 URI,导出器需要真实路径](#6.4 困难四:picker 返回 URI,导出器需要真实路径)
- [6.5 困难五:桌面端能力需要边界清晰](#6.5 困难五:桌面端能力需要边界清晰)
- [6.6 运行链路确认顺序](#6.6 运行链路确认顺序)
- 七、构建与运行方式
- 八、适配成果
- 九、功能验证结果
- 十、结语
一、写在前面
欢迎加入鸿蒙PC开发者社区,共同打造开发者工具生态:鸿蒙PC开发者社区 :https://harmonypc.csdn.net/
开源项目地址:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_pencil
欢迎在PC社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper
这篇文章记录的是 Pencil 在 OpenHarmony / HarmonyOS Electron 环境中的适配过程。
Pencil 是一款开源 GUI 原型设计与图形绘制工具。它的核心界面由 Electron 承载,编辑区基于 XHTML、SVG、DOM 和一套 stencil 元件库实现。用户平时的使用路径很直接:打开画布、拖拽元件、编辑属性、保存工程文件、导出 PDF 或图片。
针对鸿蒙适配,这次没有把 Pencil 的编辑器改成 ArkTS 原生界面,而是选择了一条更接近原项目的路线:
保留 Pencil 原有 Electron 编辑器和 Node/Web 运行方式,通过 OpenHarmony Electron runtime 承载原应用资源,并补齐鸿蒙侧文件、对话框、导出和构建链路。
这样做的核心目标,是让 Pencil 在鸿蒙设备上不只是"窗口能打开",而是能完成绘图软件最基本的闭环:界面加载、元件可用、工程文件可读写、导出功能可继续走通、HAP 包可以稳定构建。
先说一点个人判断:Pencil 这种工具,真正的适配难点不在首屏。首屏出现只能说明 Electron runtime 和静态资源基本连上了。它更关键的价值在后半段:用户画完图以后,能不能保存,能不能导出,导出的 PDF 能不能在系统文档目录中找到。导出按钮一旦没反应,前面所有"能打开"的成果都会打折。
所以这次适配的重点放在三条线上:
- 保留原有 Pencil 编辑器和 stencil 元件库
- 建立鸿蒙 HAP 工程、资源同步和命令行构建链路
- 修通文件对话框、默认路径和 PDF 导出这类桌面软件高频能力
后面所有修改也都围绕这个目标展开:确保进入 HAP 的 app/ 资源能在 OpenHarmony Electron runtime 中启动,确保 Pencil 的配置和用户资源写入鸿蒙可用目录,确保 Export Document 不是停在界面层,而是真的能进入系统保存文件选择器并写出文件。

二、项目背景
Pencil 项目中的应用描述是:
text
An open-source GUI prototyping tool that is available for ALL platforms.
当前应用版本为:
text
3.1.1
它的仓库结构里和鸿蒙适配关系最密切的部分包括:
app/:Pencil 原 Electron 应用主体app/index.js:Electron 主进程入口app/app.xhtml:Pencil 主界面app/pencil-core/:编辑器核心逻辑app/stencils/:内置元件库scripts/:鸿蒙资源同步和 HAP 构建脚本ohos_hap/:本次接入的鸿蒙 HAP 工程
这种项目的特点是:
- 编辑器不是普通静态页面,而是带 Node/Electron 能力的桌面应用
- 图形编辑依赖大量 DOM、SVG、XHTML 和历史工具逻辑
- 文件保存、导入、导出、PDF 生成都是核心用户路径
- 内置元件库和用户扩展资源需要稳定加载
- 桌面截图、全局快捷键、实体打印等能力在鸿蒙侧需要降级处理
所以本次适配的切入点不是 Web 版静态产物,而是 app/ 原应用资源。也就是说,Pencil 的鸿蒙版尽量沿用桌面端的编辑体验,只在系统能力边界上做适配和替换。
这里的取舍比较关键。Pencil 原来的编辑器已经形成了一套完整生态:.ep、.epz、.epgz 工程文件,Common Shapes、Android、iOS、Web 等 stencil 集合,PDF/PNG/SVG/HTML 等导出器,以及用户自定义字体、私有收藏、导入模板等扩展能力。如果直接重写 UI,短期内很难把这些细节重新补齐。
因此这次采用的是"原编辑器整体承载 + 鸿蒙能力补桥"的方式。
三、总体变化概览
3.1 新增鸿蒙工程壳
适配后的项目新增或维护了:
text
ohos_hap/
├── AppScope/
├── electron/
├── web_engine/
└── build-profile.json5
OpenHarmony Electron runtime 最终读取的应用资源目录是:
text
ohos_hap/web_engine/src/main/resources/resfile/resources/app
这个目录中会包含同步后的 Pencil 运行资源:
text
main.js
index.js
app.xhtml
pencil-core/
stencils/
node_modules/
package.json
3.2 新增构建脚本
项目根目录的 package.json 中新增了鸿蒙相关命令:
json
{
"build:ohos": "node scripts/build-ohos-package.js",
"ohos:sync": "OHOS_PENCIL_OUT=ohos_hap/web_engine/src/main/resources/resfile/resources/app npm run build:ohos",
"ohos:build": "node scripts/build-ohos-hap.js"
}
这三条命令分别对应:
- 生成 Pencil 鸿蒙运行资源
- 把
app/同步到 HAP 的 Electron runtime 资源目录 - 安装鸿蒙依赖并调用 Hvigor 构建 HAP
3.3 新增 Electron 启动引导
同步脚本会在 HAP 资源目录写入:
text
resources/app/main.js
这个文件负责设置鸿蒙运行标记和默认降级开关:
js
process.env.PENCIL_OPENHARMONY = '1';
process.env.PENCIL_DISABLE_SCREEN_CAPTURE = process.env.PENCIL_DISABLE_SCREEN_CAPTURE || '1';
process.env.PENCIL_DISABLE_NATIVE_PRINT = process.env.PENCIL_DISABLE_NATIVE_PRINT || '1';
process.env.PENCIL_DISABLE_GLOBAL_SHORTCUTS = process.env.PENCIL_DISABLE_GLOBAL_SHORTCUTS || '1';
process.env.PENCIL_DISABLE_GPU = process.env.PENCIL_DISABLE_GPU || '1';
require('./index.js');
也就是说,鸿蒙包里的 main.js 只做环境准备,真正的应用启动仍然回到 Pencil 原来的 index.js。
3.4 补充鸿蒙 remote/dialog 适配层
Pencil 原代码里有不少地方会通过 Electron remote/dialog 能力访问窗口、路径、文件选择器和消息框。鸿蒙环境下这条链路不能完全照搬,所以新增了 app/ohos-remote.js 作为兼容层。
它主要做三件事:
- 给渲染进程提供可用的
app.getPath()、dialog.showSaveDialog()、dialog.showOpenDialog()等接口 - 把渲染进程的文件对话框请求转成 IPC
- 在 Electron 能力不完整时提供可控 fallback,避免直接崩溃

四、为什么 Pencil 选择保留桌面 Electron 编辑器
Pencil 和许多"Web 页面型"项目不太一样,它不是把一个 SPA 放进窗口就结束。它的编辑器长期围绕 Electron、XHTML、SVG 和本地文件能力演进。
本次鸿蒙适配中重点完成:
- Pencil 主窗口启动
- XHTML 编辑器加载
- stencils 元件库加载
.ep/.epz等工程文件路径适配- 用户配置和用户资源目录适配
- 文件打开、保存、导出对话框适配
- PDF 导出链路保留
- HAP 构建链路打通
因此路线可以概括为:
app/原应用资源 + 鸿蒙 Electron runtime + HAP 工程承载 + 文件/导出能力补桥。
如果改走 ArkTS 重写,短期内可能能做出一个"看起来像 Pencil"的界面,但很难完整覆盖 Pencil 的真实工作流。比如元件库、连接线、富文本、导入导出模板、用户字体、私有收藏、PDF 导出这些能力,都不是简单重画 UI 就能解决的。
保留 Electron 编辑器的好处是边界清楚:编辑器内部的成熟逻辑尽量不动,鸿蒙差异集中在启动、路径、对话框、打印/导出、桌面能力降级这些点上。
五、鸿蒙适配核心路径
5.1 第一步:同步 Pencil 原应用资源
scripts/build-ohos-package.js 会把项目中的 app/ 复制到鸿蒙 runtime 资源目录。
同步时会过滤掉一些不应该直接进入运行包的内容:
js
return name !== ".DS_Store" &&
name !== ".cache" &&
name !== "node_modules" &&
name !== "package-lock.json" &&
name !== "yarn.lock";
随后脚本会重新写入 runtime 使用的 package.json、package-lock.json,并在目标目录安装运行时依赖。这样做可以避免把开发环境中的缓存和旧依赖状态直接带进 HAP。
5.2 第二步:写入鸿蒙启动入口
同步完成后,脚本会生成新的 main.js。这个文件不是 Pencil 业务代码,而是鸿蒙运行环境的启动引导。
它设置 PENCIL_OPENHARMONY=1 后,再进入原来的主进程:
js
require('./index.js');
后续代码可以通过这个标记判断当前是否运行在鸿蒙环境里,并启用对应的路径、窗口、对话框和能力降级逻辑。
5.3 第三步:切换用户数据目录
桌面版 Pencil 默认可以把配置和用户资源写到用户主目录,比如 .pencil。HAP 环境下不能继续假设这些路径一定可写。
适配后通过 app/ohos-adapter.js 统一处理数据目录:
- 非鸿蒙环境继续使用桌面路径
- 鸿蒙环境优先使用
app.getPath("userData") - 如果运行时拿不到路径,再退回到可控的
.pencil-ohos目录
这条路径承载的是配置、最近文档、用户 stencils、私有收藏、用户字体等长期数据。它不显眼,但决定了应用重启后状态是否还能保留。
5.4 第四步:处理文件默认路径
Pencil 的打开、保存、导出都需要默认路径。鸿蒙版优先从下面几类路径里取可用目录:
text
documents
downloads
userData
这样用户导出 PDF 或保存工程时,默认不会落到资源目录或不可写目录里。
尤其是导出场景,默认路径通常会类似:
text
/storage/Users/currentUser/Documents/Output.pdf
这也为后面的文件保存选择器提供了合理的默认文件名和默认目录。
5.5 第五步:建立 dialog IPC 桥
Pencil 的导出流程会调用 dialog.showSaveDialog()。在桌面端这很普通,但鸿蒙环境里渲染进程不能直接依赖完整 remote 能力。
适配后渲染侧由 app/ohos-remote.js 发起:
text
pencil-ohos-dialog
主进程 app/index.js 接收后调用 Electron 的 dialog 方法,并把结果规范成 Pencil 能识别的格式:
js
{ canceled: false, filePath: "/实际可写路径/Output.pdf" }
这一步是 Export Document 能继续往下走的关键。
5.6 第六步:把 picker URI 转成真实路径
鸿蒙文件选择器返回的结果有可能是 URI,例如:
text
file://docs/...
Pencil 后续写文件需要的是 Node 可以直接处理的路径,所以 ArkTS 侧 DialogAdapter.ets 里增加了转换:
ts
let decoded = decodeURI(uriOrPath);
let uri = new fileUri.FileUri(decoded);
return uri.path;
这样 DocumentViewPicker.save 返回后,主进程拿到的是后续 fs 和 PDF 导出器可以使用的路径。
5.7 第七步:构建 HAP
scripts/build-ohos-hap.js 会按顺序执行:
- 调用
build-ohos-package.js同步 Pencil 资源 - 在
ohos_hap下执行ohpm install - 查找 DevEco / Hvigor
- 查找 DevEco SDK
- 调用
hvigor assembleHap
这就把"Pencil 应用资源同步"和"鸿蒙 HAP 构建"串成了一条完整链路。
5.8 第八步:保留 PDF 导出,降级实体打印
Pencil 桌面端原来既有 PDF 导出,也有实体打印能力。鸿蒙适配中没有默认启用实体打印,而是优先保留 PDF 导出。
原因很简单:PDF 是绘图软件最常用、最容易验证、也最适合跨设备流转的输出格式。实体打印依赖系统打印服务和运行时能力,先默认关闭更稳。

六、问题困难解决
Pencil 适配中最大的困难是:
让一个带桌面软件习惯的 Electron 绘图工具,在鸿蒙 HAP 环境里继续完成文件选择、路径写入和 PDF 导出,而不是只停留在"界面能打开"。
这个问题主要体现在几个方面。
6.1 困难一:原应用资源不能简单当静态页面处理
Pencil 的 app/ 里不只是 HTML、CSS、JS 静态资源。它还包含 Electron 主进程、Node 依赖、stencil 元件库、导出器、字体和模板相关逻辑。
如果只把 app.xhtml 当成普通页面加载,很快会遇到:
- 主进程能力缺失
- Node 模块不可用
- 元件库路径不稳定
- 导出器找不到运行上下文
- 配置目录写入失败
解决方式是把 app/ 作为 Electron 应用资源整体同步进 HAP,而不是拆成单页资源。同步后再写入鸿蒙专用 main.js,让它回到原来的 index.js 启动。
6.2 困难二:桌面数据目录在鸿蒙侧不能直接套用
桌面端常见路径如用户主目录、下载目录、隐藏配置目录,在 HAP 中都需要重新确认。Pencil 如果继续把用户配置、stencil collection、字体、最近文件写到桌面路径,轻则保存失败,重则应用重启后状态丢失。
解决方式是把数据路径集中到 ohos-adapter.js,让鸿蒙环境优先使用运行时提供的 userData,并为 documents、downloads 等常见用户目录设置 fallback。
这样打开、保存、导入、导出都不用在各个业务点散落判断。
6.3 困难三:Export 点击后无响应
这次排查中最典型的问题,是点击 Export Document 里的 Export 后界面没有反应。
日志里能看到这样的信息:
text
[PencilOhos][remote-shim] showSaveDialog fallback
{"title":"Select output file","defaultPath":"/storage/Users/currentUser/Documents/Output.pdf", ...}
这说明导出流程已经进入保存文件阶段,并且默认文件名 Output.pdf 也准备好了。问题不在 Pencil 的导出向导,也不在 PDF 类型选择,而是在 showSaveDialog 没有真正接到鸿蒙系统保存文件选择器。
原来的 fallback 返回了:
js
{ canceled: true, filePath: "" }
对 Pencil 来说,这就等价于用户取消导出,所以后续 PDF 写盘不会继续执行。
解决方式是给 dialog 补 IPC 桥:
- 渲染进程调用
ohos-remote.js ohos-remote.js发送pencil-ohos-dialog- 主进程
index.js调用 Electron dialog - 返回规范化后的
filePath - Pencil 导出器继续写入文件

6.4 困难四:picker 返回 URI,导出器需要真实路径
文件保存框弹出来以后,还剩一个容易被忽略的问题:鸿蒙 picker 返回值可能是 URI,不一定是 Node 可以直接写入的路径。
如果把 file://docs/... 原样传给 Pencil,后续 fs 或 PDF 导出器仍然可能失败。
解决方式是在 DialogAdapter.ets 中增加路径转换,把 picker 返回值转换成真实路径后再回传给 JS 层。
这个改动不大,但它补上的是最后一段路:从"用户选择了保存位置"到"PDF 真的写入文件系统"。
6.5 困难五:桌面端能力需要边界清晰
Pencil 桌面端还包含一些强系统能力:
- 全局快捷键
- 桌面截图
- 屏幕取色
- 实体打印
- 外部图片编辑器
- 系统剪贴板图片读写
这些能力在鸿蒙侧不能假设都可用。适配时的处理方式是:
- 默认关闭全局快捷键
- 默认关闭桌面截图和屏幕取色入口
- 默认关闭实体打印
- 优先保留 PDF 文件导出
- 剪贴板、外部编辑器等能力继续留待真机逐项验证
这个边界要提前划清,否则应用可能在不常用功能上崩溃,反过来影响主流程稳定性。
6.6 运行链路确认顺序
Pencil 在鸿蒙中的运行链路可以按下面顺序确认:
app/是否同步到resources/appresources/app/main.js是否写入鸿蒙启动标记resources/app/package.json的main是否指向main.jsindex.js是否正常创建主窗口app.xhtml是否完成加载pencil-core/和stencils/是否能读取- 用户数据目录是否切到鸿蒙可写路径
showOpenDialog、showSaveDialog是否进入 IPC 桥DocumentViewPicker.save是否返回路径- PDF 导出器是否写出最终文件
这个顺序可以把问题拆清楚:先确认资源和窗口,再确认数据路径,最后确认导出链路。否则只看界面,很容易漏掉文件系统和系统对话框问题。
七、构建与运行方式
同步资源:
bash
cd ~/XM/pencil-master-ohos
npm run ohos:sync
构建 HAP:
bash
cd ~/XM/pencil-master-ohos
npm run ohos:build
HAP 输出目录:
text
ohos_hap/electron/build/default/outputs/default/
当前项目中可以看到已生成的签名 HAP:
text
electron-default-signed.hap
同时也会生成未签名包:
text
electron-default-unsigned.hap

八、适配成果
pencil-master-ohos 已经完成:
- 鸿蒙 Electron HAP 工程接入
- Pencil 原
app/资源同步到 HAP - 鸿蒙专用
main.js启动引导 PENCIL_OPENHARMONY运行环境标记- 用户配置和用户资源路径迁移
- 默认文档/下载/用户数据路径适配
ohos-remote.jsremote/dialog 兼容层- 文件打开、保存、消息框 IPC 桥接
- 鸿蒙 picker URI 到真实路径转换
- PDF 导出保存链路修复
- 全局快捷键、截图、取色、实体打印默认降级
- HAP 命令行构建链路
这次适配的结果不是把 Pencil 改成一个新的鸿蒙页面,而是尽量保留了原编辑器的操作方式,同时把鸿蒙侧必须补的系统能力补到可用状态。
九、功能验证结果
pencil-master-ohos 按实际绘图工具使用场景完成了功能验证。
第一组是基础启动:
- 应用能在 OpenHarmony Electron runtime 中启动
- 主窗口能创建并显示
app.xhtml能正常加载- 顶部菜单、画布、页面树、属性区域能显示
第二组是编辑器资源:
- 内置 stencil 元件库能加载
- 常用图形可以拖拽到画布
- 页面树和画布编辑逻辑可用
- SVG 绘制、属性调整和连接线能力保持原有表现
第三组是文件和数据:
- 用户配置写入鸿蒙可用目录
- 默认打开/保存目录不再指向不可写资源目录
- 用户 stencils、私有收藏、字体等数据路径进入适配层
- 最近文件和用户数据具备继续验证基础
第四组是导出链路:
Export Document弹窗可以正常打开- PDF 类型和导出模板由原 Pencil 逻辑提供
- 点击
Export后能进入系统保存文件选择器 - 默认文件名
Output.pdf能传入保存框 - picker 返回结果能转换成 Node 可写路径
- PDF 导出流程可以继续写盘
第五组是构建交付:
npm run ohos:sync能同步应用资源npm run ohos:build能触发 ohpm 和 Hvigor 构建- HAP 输出目录生成
electron-default-signed.hap - 构建中出现的历史警告不阻断打包
第六组是仍需真机继续确认的能力:
- 大文件 PDF 导出的耗时和内存占用
- PNG / SVG / HTML 等其他导出类型
- 剪贴板图片读写
- 导入外部 stencil collection
- 字体导入
- 鼠标右键、拖拽、焦点和输入法细节
- 实体打印是否有条件恢复

十、结语
pencil-master-ohos 的适配,本质上是一次"桌面图形编辑器鸿蒙承载"实践。
它的重点不是把界面截图跑出来,而是建立一个可继续迭代的完整运行包:
- 工程能构建
- 应用能启动
- 编辑器能加载
- 元件库能使用
- 用户数据有可写目录
- 文件对话框能进入系统能力
- PDF 导出能写到用户选择的位置
Pencil 这类软件的迁移有一个明显特点:适配问题往往不在第一眼能看到的地方,而在用户完成工作的最后几步。画布打开了不代表能交付,导出按钮能点也不代表能写盘。真正让它从"能跑"变成"能用"的,是路径、对话框、权限、文件格式和导出链路这些细小但连续的环节。
这次适配把 Pencil 原有 Electron 编辑器放进了鸿蒙 HAP,也把导出无响应的问题定位到 showSaveDialog 链路,并通过 IPC 桥和 URI 转路径修通。后续继续补齐其他导出格式、剪贴板、外部资源导入和真机交互细节,Pencil 在鸿蒙上的可用性还可以继续往前走。