Xournal++ 插件深度定制实战:用 Lua 给手写笔记注入一条专属批改流水线
【免费下载链接】xournalppXournal++ is a handwriting notetaking software with PDF annotation support. Written in C++ with GTK3, supporting Linux (e.g. Ubuntu, Debian, Arch, SUSE), macOS and Windows 10. Supports pen input from devices such as Wacom Tablets.项目地址: https://gitcode.com/gh_mirrors/xo/xournalpp
Xournal++ 是一个用 C++ 与 GTK3 打造的手写笔记与 PDF 标注工具,很多人用它在平板上批改作业、记录课堂。但默认功能再丰富,也总有"我想要一个官方没给"的操作——比如一键把整页红色批注统计出来并统一换色。Xournal++ 的插件系统(Lua)就是为这种"官方没做、但你需要"的定制场景准备的,本文带你从零写一个能真正跑起来的插件,顺带避开几个会让插件"假装没加载"的深坑。
场景:批改笔记时的"手动地狱"
想象一下:你刚用 Wacom 数位板批完 40 页学生作业,每页都有大量红色圈注。现在导师要求"所有批注统一改成深蓝、加粗 20%"——如果一个个选中再改色,一晚上就搭进去了。
我当初就是在这样一个深夜打开 Xournal++,发现没有"批量改笔迹属性"的功能,才第一次去翻它的plugins/目录。结果发现:这个项目早就内置了一整套 Lua 脚本接口,官方只写了少量示例,剩下的潜力全等着你自己挖。下面是 Xournal++ 主界面,正文后面我们写的插件就会挂在这片画布上:
机制打比方:插件是"遥控器",app是万能 API
把 Xournal++ 想象成一个成熟的厨房:厨房本身(C++ 核心)功能齐全但不许你乱动灶台;而 Lua 插件是一把"万能遥控器",它不能拆厨房,却能精确按下每一个按钮。
这把遥控器的按键清单,全部声明在项目源码的 plugins/luapi_application.def.lua 里,从app.getStrokes()(读取笔画数据)、app.addStrokes()(批量写入笔画)到app.changeToolColor()、app.export(),一应俱全。插件的生命周期则由 src/core/plugin/Plugin.h 定义:启动时加载脚本 → 调用initUi()注册菜单与按钮 → 用户点击后触发你写的回调函数。
和 SFBAudioEngine 播放器要"在安全上下文里改音频图"类似,Xournal++ 也有自己的潜规则:所有 UI 注册必须发生在initUi()里,所有文档修改后必须调用app.refreshPage()通知重绘——漏掉后者,你的插件"看起来没生效",其实改了但没刷新,这是最常见的隐性故障。
实战清单:4 步写出第一个真插件
目标:写一个BulkStyle插件,把当前图层所有笔迹复制一份并加粗 20%,同时统计红色笔迹数量并弹窗汇报。整个过程约 20 行代码。
第 1 步:搭骨架(plugin.ini)
在plugins/目录(或用户配置目录的plugins/子目录)下新建BulkStyle/文件夹,放入plugin.ini:
[about] author=Your Name description=Batch restyle the strokes on current layer. version=1.0 [default] enabled=false [plugin] mainfile=main.lua为什么enabled=false?因为新插件默认关闭,这是故意的——官方示例插件 plugins/Example/plugin.ini 也这么做,避免用户升级后突然多出一堆菜单。你装好后需要手动去"插件管理"里打开它,或直接改配置。
第 2 步:用initUi()注册入口(main.lua)
-- 只允许在这里注册菜单项和工具栏按钮 function initUi() app.registerUi({ ["menu"] = "复制并加粗当前层笔迹", ["callback"] = "bulkRestyle", ["accelerator"] = "<Alt>b" }) endapp.registerUi是整个定制的关键 API,声明就在 plugins/luapi_application.def.lua 的第 97 行附近。它会把菜单项挂到"插件"菜单下,<Alt>b是快捷键。注意回调必须写字符串函数名,而不是函数本身——因为回调是在 C++ 侧按名字查找执行的。
第 3 步:实现真正的"重排"逻辑
function bulkRestyle() local strokes = app.getStrokes("layer") -- 读取当前图层全部笔迹 local red, copies = 0, {} for _, s in ipairs(strokes) do if s.color == 0xff0000 then red = red + 1 end table.insert(copies, { x = s.x, y = s.y, pressure = s.pressure, tool = s.tool, width = s.width * 1.2, -- 加粗 20% color = s.color, fill = s.fill, lineStyle = s.lineStyle }) end if #copies > 0 then app.addStrokes({strokes = copies, allowUndoRedoAction = "grouped"}) app.refreshPage() -- 千万不能省,见下文"隐藏坑点" end app.openDialog("复制了 " .. #copies .. " 条笔迹,其中红色 " .. red .. " 条", {"OK"}, "") end这段代码同时演示了插件最强大的双向能力:getStrokes("layer")把画布上的笔迹读成 Lua 表(逆操作是app.addStrokes),之后你可以任意改宽度、颜色、线型再写回去——相当于给画布加了一条可编程的"处理链"。
第 4 步:安装、启用、跑起来
克隆仓库后插件的源码位置在 plugins/,但实际加载路径有两个:程序安装目录旁的plugins/,以及用户配置目录下的plugins/(具体逻辑见 src/core/plugin/PluginController.cpp)。把自己的插件放进用户配置目录的plugins/即可,无需重新编译。启动 Xournal++ → 菜单"插件 → 插件管理"勾选BulkStyle→ 按<Alt>b验证。
调试与验证:别猜,打印出来
插件不生效时,90% 的情况靠日志就能定位。几个立即可用的排查手段:
- 看日志:Xournal++ 在加载插件时会打印
Loading plugins from: ...(见PluginController.cpp)。如果这一行都没出现,说明插件目录路径错了。 - 用
print()埋点:在initUi()和回调开头各放一句print("BulkStyle init"),日志里能看到生命周期是否正常走到回调。 - 用
app.getFolder()确认落盘位置:local dir = app.getFolder("config")能拿到插件专属配置目录,适合存放你自己的状态文件,避免污染全局配置。
判断标准:能读到
initUi的 print,说明加载成功;能读到回调的 print,说明菜单注册成功;两者都有但画布没变,那就是漏了app.refreshPage()。
高危操作与避坑清单
| 操作 | 后果 | 正确做法 |
|---|---|---|
修改文档后不调app.refreshPage() | 界面不刷新,误以为没生效 | 任何addStrokes/addTexts/addImages之后立即刷新 |
调用addStrokes时省略allowUndoRedoAction | 撤销栈混乱,Ctrl+Z 一步撤回整页 | 显式传"grouped",把一次批量操作归为一个撤销动作 |
在initUi()之外调用registerUi | 菜单项不出现 | 所有注册集中在initUi() |
| 在回调里做超长循环 | UI 卡死,看起来像崩溃 | 拆分批次,或改用app.openDialog让用户确认后再跑 |
用app.C时写死魔法数字 | 版本升级后枚举值对不上 | 优先用 luapi_application.def.lua 里的app.C.Tool_pen这类常量 |
另外一条"禁止事项"和标杆文章的引擎控制同理:不要试图用os.execute去改文档文件本身,也别绕过插件 API 直接操作底层对象——一切读写都走app接口,否则内部状态不一致,轻则撤销失灵,重则崩溃。
进阶思路与行动号召
你的"处理链"可以越搭越长:用app.getTexts()批量扫描文字框做词频统计、用app.export()把批改结果按图层逐层导出成 SVG 给课件复用、用app.getDocumentStructure()做整份作业的"体检报告"。甚至可以把上面三步拆成三个独立菜单项,做成一条完整的"批改流水线"。
想要跑通这个示例,最快的路径是git clone https://gitcode.com/gh_mirrors/xo/xournalpp后先跑一遍plugins/ColorCycle这个官方示例(它展示了菜单注册与颜色切换的完整写法),再回来替换成你自己的逻辑。一个能改笔迹、能统计、能导出的插件,从零到跑通只要一个晚上——而它从此会让你的批改效率翻倍。动手吧,把画布变成你的代码可以指挥的舞台。🎨
【免费下载链接】xournalppXournal++ is a handwriting notetaking software with PDF annotation support. Written in C++ with GTK3, supporting Linux (e.g. Ubuntu, Debian, Arch, SUSE), macOS and Windows 10. Supports pen input from devices such as Wacom Tablets.项目地址: https://gitcode.com/gh_mirrors/xo/xournalpp
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考