Shape 块
约 1916 字大约 6 分钟
- Type ID: 5
- Type: Map
Shape 表示一条可编辑的参数化图形。它与 Ink、Media 并列存在于 Canvas 内容流中,保存几何、单色填充和单色描边。Shape 不是 Ink 的 inkType,也不复用 Ink 的轨迹点。
字段
| 字段 | 类型 | 要求 | 说明 |
|---|---|---|---|
type | uint16 | Required | 固定为 5 |
contentId | uint32 | Required | Canvas 内 Ink/Shape/Media 共享的连续内容编号 |
undoId | uint32 | Required | Canvas 内非递减的撤回操作分组编号 |
shapeType | int32 | Required | 几何类型 |
geometry | Map | Required | 由 shapeType 决定的几何数据 |
stroke | Map | Conditional | 描边样式;Line/Polyline 必填 |
fill | Map | Conditional | 闭合 Shape 的填充样式 |
extra | Map | Optional | 软件私有扩展 |
Shape 至少必须有 stroke 或 fill 之一。Line/Polyline 必须有 stroke,不得有 fill。
shapeType
| 值 | 名称 | 语义 |
|---|---|---|
0 | Line | 由两个端点定义的单个线条对象 |
1 | Polyline | 由至少两个点定义的单个折线对象 |
2 | Rectangle | 可非等比缩放的矩形 |
3 | Square | 保持等比语义的正方形 |
4 | Ellipse | 可非等比缩放的椭圆 |
5 | Circle | 保持等比语义的圆 |
6 | Polygon | 至少三个点的闭合多边形 |
7–127 | Reserved | UInk 后续版本保留 |
128 及以上 | Private | 软件私有图形类型 |
读取器遇到未知 shapeType 时,应跳过该完整 Shape,报告警告并继续读取后续内容。不能把未知几何安全地回退为其他几何时,不得伪造一个不同的 Shape。
shapeType 只描述底层几何,不描述线条是否连续。Line/Polyline 的实线、虚线、点线等外观由同一个 Stroke Map 的 dashArray 控制,始终作为一个 Shape、一个 contentId 和一个可整体编辑的对象保存,不得把可见短线拆成多个 Shape。
几何
Point Map
Polyline 和 Polygon 的每个点使用 Map:
| 字段 | 类型 | 要求 | 说明 |
|---|---|---|---|
x | float32 | Required | Canvas 绝对 X 坐标 |
y | float32 | Required | Canvas 绝对 Y 坐标 |
坐标必须是有限数。Shape 点全部使用绝对坐标,不采用 Ink 的首点绝对、后续点相对编码。
Line、Polyline 与 Polygon
这三种几何使用 points:
- Line 必须恰好有两个点;
- Polyline 至少有两个点;
- Polygon 至少有三个点,读取器按顺序连接并隐式连接末点与首点;写入器不得重复保存首点。
Polygon 用于三角形等基础多边形,不注册独立 Triangle 类型。Polygon 的填充使用非零环绕规则;软件不得依赖自交多边形获得未定义的填充结果。
Rectangle 与 Ellipse
Rectangle 和 Ellipse 的 geometry 字段为:
| 字段 | 类型 | 要求 | 说明 |
|---|---|---|---|
centerX | float32 | Required | 几何中心 X |
centerY | float32 | Required | 几何中心 Y |
width | float32 | Required | 最终宽度,必须大于 0 |
height | float32 | Required | 最终高度,必须大于 0 |
rotation | float32 | Optional | 弧度,Canvas 坐标系正值顺时针,缺失为 0 |
Rectangle 和 Ellipse 允许 width 与 height 不相等。旋转后仍以这组最终 Canvas 几何显示,不使用通用仿射矩阵。
Square
Square 的 geometry 字段为:
| 字段 | 类型 | 要求 | 说明 |
|---|---|---|---|
centerX | float32 | Required | 几何中心 X |
centerY | float32 | Required | 几何中心 Y |
size | float32 | Required | 正方形边长,必须大于 0 |
rotation | float32 | Optional | 弧度,缺失为 0 |
Square 使用独立类型,以便编辑器在缩放时保持宽高比例。
Circle
Circle 的 geometry 字段为:
| 字段 | 类型 | 要求 | 说明 |
|---|---|---|---|
centerX | float32 | Required | 圆心 X |
centerY | float32 | Required | 圆心 Y |
radius | float32 | Required | 半径,必须大于 0 |
Circle 不保存 rotation,因为旋转不会改变圆的几何。Circle 使用独立类型,以便编辑器在缩放时保持等比。
Stroke Map
Stroke 在同一个 Shape 内使用一种颜色、透明度和固定宽度,不支持逐点宽度或颜色。
| 字段 | 类型 | 要求 | 说明 |
|---|---|---|---|
color | Color Map | Required | 描边颜色,支持 SDR/HDR 回退 |
opacity | float32 | Required | 描边透明度,范围 0–1 |
width | float32 | Required | 最终可见的 Canvas 描边宽度,必须大于 0 |
dashArray | Array<float32> | Optional | 实际 dash/gap 长度,缺失或空数组为实线 |
dashOffset | float32 | Optional | dash 起始偏移,缺失为 0 |
startMarker | int32 | Optional | 起点端头,缺失为 0 |
endMarker | int32 | Optional | 终点端头,缺失为 0 |
width、dashArray 和 dashOffset 使用保存时最终可见的 Canvas 长度。软件缩放 Shape 时可以自行决定是否改变它们,但写入器必须保存换算后的实际结果。读取器不得通过额外几何变换再次缩放这些值。
非空 dashArray 必须包含偶数个非负有限值,且总和大于 0。非法 dashArray 或非法 dashOffset 时,读取器按实线处理。具体线帽、连接处和抗锯齿外观由软件决定,UInk 不注册 lineCap、lineJoin 或 miterLimit。
Marker
| 值 | 名称 | 语义 |
|---|---|---|
0 | None | 无端头 |
1 | OpenArrow | 开放式箭头语义 |
2–127 | Reserved | UInk 后续版本保留 |
128 及以上 | Private | 软件私有端头 |
Marker 只保存类型编号,不保存 Path、尺寸或固定比例。端头的具体外形由软件自行决定。未知 Marker 必须按 None 回退,不得丢弃主体 Line/Polyline。
软件内部可以用主线加两条端头线绘制箭头;写入 UInk 时应归一化为一个 Line/Polyline 及其 Marker。Marker 不产生额外的 Shape、contentId 或 undoId。
Fill Map
Fill 首版只支持单色填充,渐变、纹理和图片填充留待后续版本或私有扩展。
| 字段 | 类型 | 要求 | 说明 |
|---|---|---|---|
fillType | int32 | Required | 0 为 Solid,1–127 保留,128+ 私有 |
color | Color Map | Required | 填充颜色,支持 SDR/HDR 回退 |
opacity | float32 | Required | 填充透明度,范围 0–1 |
未知 fillType 时,读取器使用 color 和 opacity 按 Solid 回退。Line/Polyline 不得保存 Fill;闭合 Shape 可以只保存 Fill、只保存 Stroke 或同时保存两者。
内容流、撤回与增量写入
Shape 与 Ink、Media 按物理块顺序混合处理。三者共享当前 Canvas 的连续 contentId;undoId 从 0 开始且只允许不递减,相同 undoId 的连续内容块构成一次撤回操作。
完整 Shape 可以追加到文件末尾最后一个 Canvas。修改、移动、缩放或删除既有 Shape 必须完整重写,不得通过追加重复旧 Shape。完整重写后重新整理各 Canvas 的 contentId 和 undoId。
Erase Ink 按物理顺序作用于其下方的 Shape;具体裁剪算法、透明效果和背景效果由软件决定。读取器不得因为块类型不同而重新排序。
renderOnlyWhenLatest = true 的原始 Ink 在后续连续内容中遇到 Ink 或 Shape 结果时按现有条件渲染规则隐藏;Media 不会使该组失去“最新”状态。
容错与兼容
- 缺少
type、contentId、undoId、shapeType、geometry或必要样式时跳过该 Shape,报告警告并继续读取。 - 坐标、尺寸、rotation、宽度、dash 或 opacity 含 NaN/Infinity 时,按字段回退;无法构造有效几何时跳过整块。
- Shape 的未知
shapeType:跳过整块。 - Stroke 的未知 Marker:按 None 回退并保留主体几何。
- Fill 的未知
fillType:使用 Color Map 和 opacity 按 Solid 回退。 - 旧读取器遇到未知 Type ID
5时,可以跳过当前完整 MessagePack 对象并继续读取后续块。 - 不能原样保留未知块的读取器不得无警告覆盖源文件,以免旧软件保存时静默删除 Shape。
示例
虚线双向箭头
{
"type": 5,
"contentId": 2,
"undoId": 1,
"shapeType": 0,
"geometry": {
"points": [
{ "x": 120.0, "y": 240.0 },
{ "x": 420.0, "y": 240.0 }
]
},
"stroke": {
"color": { "fallback": 255 },
"opacity": 1.0,
"width": 4.0,
"dashArray": [16.0, 8.0],
"startMarker": 1,
"endMarker": 1
}
}旋转正方形与 HDR 填充
{
"type": 5,
"contentId": 3,
"undoId": 2,
"shapeType": 3,
"geometry": {
"centerX": 320.0,
"centerY": 220.0,
"size": 180.0,
"rotation": 0.3926991
},
"stroke": {
"color": { "fallback": 255 },
"opacity": 1.0,
"width": 5.0
},
"fill": {
"fillType": 0,
"color": {
"fallback": 16763904,
"space": "scrgb",
"components": [1.8, 0.65, 0.1]
},
"opacity": 0.5
}
}