墨迹主文件
约 1140 字大约 4 分钟
墨迹主文件(filename.uink)是连续的 MessagePack 对象流。Header Extension 注册 Device 与 Workspace,后续扁平 Canvas 通过 UUID 引用两者并管理各自的 Ink/Media。
文件结构
结构规则:
- Header 必须位于文件开头。
- Header Extension 可选且最多一个;存在时必须紧跟 Header。
- Device 只存在于 Header Extension 的
devices注册表,不是顶级块。 - Canvas 管理其后的 Ink/Media,直到下一个 Canvas 或文件末尾。
- 第一个 Canvas 前不得出现 Ink/Media。
- Canvas 可以为空,用于保存空白页或空白图层。
两棵注册树与扁平 Canvas
Device 树描述空间:Display 是系统绝对显示区域,Window 是相对父 Device 的窗口或板中板区域。Workspace 树描述场景、宿主、页面序列和父子生命周期。两棵树独立,Canvas 分别用 deviceGuid 与 workspaceGuid 连接它们。
一个文件可以同时包含多个白板、屏幕批注或 PPT Workspace。子 Workspace 合成在父项之上;同级合成顺序由软件决定。Canvas 始终填满所引用的 Device,不保存自己的几何。
页面与多显示器
pageGuid 是页面的永久身份,pageIndex 是 Workspace 内可重排的当前顺序。同一 Workspace 中,跨设备或跨图层表示同一逻辑页的 Canvas 必须共享二者。
- 同步白板:多个 Device 引用同一 Workspace 和同一页面身份,只同步页序与翻页;各 Canvas 的绘制内容独立。
- 独立白板:不同 Device 使用不同 Workspace,各自维护页面序列。
- PPT:Workspace 保存宿主
hostId,Canvas 保存slideId;跨设备的同一幻灯片共享 pageGuid 和 slideId。
Header.pageNum 是各 Workspace 不重复 pageGuid 的总数。空白页计数,同页多设备或多图层不重复计数。
内容与撤回顺序
同一 Canvas 中 Ink 与 Media 按物理块顺序混合处理,contentId 按此顺序从 0 连续递增。擦除 Ink 按顺序作用于其下方内容;读取器不得按块类型重新排序。
undoId 在同一 Canvas 的 Ink/Media 间共享,从 0 开始且只允许不递减。相同 undoId 的连续块构成一次撤回操作。撤回或重做必须完整重写,重写后的文件只保存当前有效内容。
.uink.extra
Media.path 引用对应 .uink.extra ZIP 内的资源。ZIP 没有额外索引;资源顺序、几何、播放、PDF 页状态和撤回信息均由 Media 块决定。
资源包缺失时基础墨迹必须正常加载。视觉媒体保留布局占位;PDF 还可以使用可选 pageCount/pageIndex 显示页数占位信息。
容错
- 未知 Type ID:跳过当前 MessagePack 对象并继续。
- 未知 workspaceType:按通用白板加载,不执行未知宿主逻辑。
- 未知 deviceType:按临时根显示面加载。
- Device/Workspace 循环:断开问题父引用,作为临时根项加载。
- Canvas 引用缺失:构造临时 Workspace 或根 Device 并警告。
- 无效 Ink/Media:跳过单块、报告警告并继续。
- 所有临时容错结果都不得回写源文件。
多显示器 PPT 可读示例
[0, 10, "5fe30f46-be92-49b6-b921-a60706febf10", 2, 1, 1, 1700000000]
{
"type": 1,
"devices": [
{ "guid": "11111111-1111-4111-8111-111111111111", "deviceType": 0,
"x": 0, "y": 0, "width": 3840, "height": 2160 },
{ "guid": "22222222-2222-4222-8222-222222222222", "deviceType": 0,
"x": 3840, "y": 0, "width": 1920, "height": 1080 }
],
"workspaces": [
{ "guid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", "workspaceType": 2,
"hostId": "INKKEYS-PPT-7B58E2A1", "currentPageIndex": 0 }
]
}
{
"type": 2,
"workspaceGuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"deviceGuid": "11111111-1111-4111-8111-111111111111",
"pageGuid": "cccccccc-cccc-4ccc-8ccc-cccccccccccc",
"pageIndex": 0, "pageNumber": 1, "layerIndex": 0, "layerNumber": 0,
"slideId": 256
}
// 第一个 Canvas 的 Ink / Media
{
"type": 2,
"workspaceGuid": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"deviceGuid": "22222222-2222-4222-8222-222222222222",
"pageGuid": "cccccccc-cccc-4ccc-8ccc-cccccccccccc",
"pageIndex": 0, "pageNumber": 1, "layerIndex": 0, "layerNumber": 0,
"slideId": 256
}
// 第二个 Canvas 的独立 Ink / Media以上是连续 MessagePack 对象的可读表示,文件本身没有包裹这些对象的外层 Array。
其他关键场景
- Window Device 上的画布:在
devices注册 Window,并让 Canvas 引用其 GUID;Canvas 本身不写 x/y/width/height。 - 嵌套白板:子 Workspace 使用
parentWorkspaceGuid,其空间位置由 Canvas 引用的 Device 决定。 - 空白页:只写 Canvas,不跟随 Ink/Media。
- PDF 缺失:保留 Media 的 width/height/transform 以及可选页信息,继续渲染其他内容。
- HDR 回退:Ink 的未知或无效色彩空间使用 Color Map 的 fallback。