Canvas 块
约 1605 字大约 5 分钟
- Type ID: 2
- Type: Map
- Required
Canvas 是扁平内容流中的页面图层记录。使用显式注册表时,它通过 GUID 分别引用 Workspace 与 Device;使用隐式单例时,它省略对应 GUID。Canvas 后面的 Ink、Shape 和 Media 归属该 Canvas,作用域在下一个 Canvas 或文件末尾结束。
字段
| 字段 | 类型 | 要求 | 说明 |
|---|---|---|---|
type | uint16 | Required | 固定为 2 |
workspaceGuid | string(36) | Conditional | 使用显式 Workspace 注册表时必填 |
deviceGuid | string(36) | Conditional | 使用显式 Device 注册表时必填 |
pageGuid | string(36) | Required | 逻辑页面的永久 UUID |
pageIndex | uint32 | Required | Workspace 内从 0 连续的当前页序 |
pageNumber | uint32 | Required | 用户界面显示页码,不参与排序 |
layerIndex | uint32 | Required | 同一设备页面内从 0 连续的图层索引 |
layerNumber | uint32 | Required | 用户界面显示图层号,不参与排序 |
slideId | int32 | Conditional | PPT Workspace 必填的 PowerPoint COM SlideID |
viewport | Map | Conditional | 仅 layerIndex = 0 可以保存;Device 可见区域对应的 Canvas 世界坐标与统一缩放 |
extra | Map | Optional | 私有扩展 |
Canvas 不保存自身边界几何,其显示视口始终填满对应的显式或隐式 Device。Ink、Shape 与 Media 使用平台无关的 Canvas 逻辑像素;viewport 只决定这些世界坐标如何映射到 Device 局部逻辑像素,不修改内容本身的坐标。
Viewport Map
| 字段 | 类型 | 要求 | 说明 |
|---|---|---|---|
x | float32 | Required | Device 左上角对应的 Canvas 逻辑 X,可为负数 |
y | float32 | Required | Device 左上角对应的 Canvas 逻辑 Y,可为负数 |
scale | float32 | Required | Device 逻辑像素 / Canvas 逻辑像素,必须为有限正数 |
viewport.x/y 使用 Canvas 世界坐标。它们与 Display Device 的系统绝对 x/y、Window Device 的父级相对 x/y 没有继承或换算关系。
Canvas 世界坐标到 Device 局部坐标的映射为:
deviceX = (canvasX - viewport.x) * viewport.scale
deviceY = (canvasY - viewport.y) * viewport.scale反向映射为:
canvasX = deviceX / viewport.scale + viewport.x
canvasY = deviceY / viewport.scale + viewport.yscale = 1 表示一 Canvas 逻辑像素对应一 Device 逻辑像素,2 表示放大两倍,0.5 表示缩小到一半。Device 当前可见的 Canvas 世界宽高分别为 Device.width / scale 与 Device.height / scale。物理像素和 DPI 不参与该公式。Device 或窗口尺寸改变时保持 viewport 左上角不变,并向右下改变可见范围。
viewport 缺失时默认 { x: 0, y: 0, scale: 1 }。UInk 1.0 不注册视口旋转、错切或 X/Y 非等比缩放。
页面归属与权威层
为避免显式 GUID 与隐式单例产生歧义,本文使用以下逻辑键:workspaceKey 是显式 workspaceGuid 或文件内隐式 Workspace 单例,deviceKey 是显式 deviceGuid 或文件内隐式 Device 单例。viewport 归属于 (workspaceKey, deviceKey, pageGuid)。同一页面在不同 Device 上可以使用不同 viewport。
只有同页同 Device 的 layerIndex = 0 Canvas 可以保存 viewport,其他图层必须省略并继承第 0 层。第 0 层缺失 viewport 或 viewport 无效时,所有图层统一使用默认值。读取器遇到非第 0 层的 viewport 时忽略该字段并警告,不得用它覆盖第 0 层状态。
viewport 适用于所有 Workspace 类型。软件可以禁止用户在屏幕批注、白板或 PPT 中平移缩放,但合法的非默认 viewport 必须按照相同公式解释。
页面身份、排序与唯一性
pageGuid在整个 UInk 文件中永久唯一,在完整保存或页面重排时保持不变;复制为新页面时必须生成新 UUID。- 复制页面时,新页面继承源页面的
viewport;软件可以在复制后调整并保存新的最终值。 - 同一逻辑页跨设备、跨图层共享
pageGuid。 - 同一 Workspace 内,
pageGuid与pageIndex严格一一对应,pageIndex从 0 开始且无空洞。 - 每个
(workspaceKey, deviceKey, pageGuid)下的layerIndex从 0 开始且无空洞;不同 Device 允许具有不同图层数量。 (workspaceKey, deviceKey, pageGuid, layerIndex)是 Canvas 唯一键,在同一文件内不得重复。- 同页同 Device 中
layerIndex越大越靠前;该合成顺序不依赖 Canvas 的物理排列。 pageNumber与layerNumber仅供显示,允许跳号或重复。- Canvas 块在对象流中的排列不决定合成顺序。为便于流式读取和人工检查,写入器应依次按 Workspace 注册顺序、
pageIndex、Device 注册顺序和layerIndex排列 Canvas;隐式单例只有一个注册顺位。
多显示器白板
同步翻页使用同一个 Workspace:同一页在多个 Device 上具有相同 pageGuid 和 pageIndex,但每个 Device 使用独立 Canvas、独立 Ink/Shape/Media、独立 contentId 与 undoId。UInk 不同步不同屏幕上的绘制内容。
同一逻辑页在不同 Device 上可以保存不同 viewport;每个 Device 内由第 0 层保存唯一 viewport。
各屏幕独立翻页时使用多个 Workspace,每个 Workspace 维护自己的页面序列。
PPT 锚定
workspaceType = 2 时,Canvas 必须保存 slideId。读取器使用 Slides.FindBySlideID 定位幻灯片。定位失败时,读取器必须保留该 Canvas,将其标记为未绑定并提示宿主绑定失效;不得按 pageIndex、当前页或其他幻灯片自动重新附着。用户或宿主可以通过显式重新绑定流程选择新幻灯片,随后执行完整保存。
同一 PPT 页面跨设备显示时共享 pageGuid 和 slideId,但仍使用独立 Canvas 内容。
示例
{
"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,
"viewport": {
"x": -320.0,
"y": 180.0,
"scale": 1.5
},
"extra": {}
}该 Canvas 可以不包含任何 Ink/Shape/Media,用于保存用户已创建但尚未绘制的空白页或图层。每个显式 Workspace 至少应包含一个 Canvas。
容错
使用显式注册表时,如果 workspaceGuid 或 deviceGuid 缺失或无法解析,读取器可以构造仅供本次加载使用的临时 Workspace 或根 Device,并报告警告。页面身份或 pageIndex 缺失或无效时,读取器可以按 Canvas 在对象流中的先后顺序生成临时独立页面;layerIndex 缺失或无效时,应在对应临时页面下按对象流顺序生成临时图层。上述临时身份只用于本次加载。
第 0 层 viewport Map 缺少任一必填字段、x/y 包含 NaN/Infinity,或 scale 非正、包含 NaN/Infinity 时,整个 viewport 无效并回退默认值。非第 0 层 viewport 一律忽略。所有默认值、继承和容错结果均不得自动回写源文件。