Canvas 指令集

renderCanvas 的输出是一份平台无关的 Canvas 2D 指令脚本——这也是「网页 = 原生同一内核」的关键:桌面端与浏览器渲染走同一份代码,指令流只是另一种序列化产物。

结构

interface CanvasScript {
  width: number;    // 页面像素宽(mm × scale)
  height: number;   // 页面像素高
  images: Record<string, string>;  // id → dataURL(含印章等位图)
  commands: CanvasCommand[];       // 按序回放
}

坐标已完成 mm → px 换算与 Y 轴翻转(OFD 国标 Y 轴向下,Canvas 亦向下,翻转指将 OFD 顶点系换算到像素系)。

指令全集(24 种)

op 参数 对应 Canvas API
save — save()
restore — restore()
globalAlpha alpha globalAlpha
beginPath — beginPath()
moveTo x, y moveTo
lineTo x, y lineTo
quadraticCurveTo cpx, cpy, x, y quadraticCurveTo
bezierCurveTo cp1x, cp1y, cp2x, cp2y, x, y bezierCurveTo
closePath — closePath()
fillStyle style(颜色/渐变/图案 id) fillStyle
strokeStyle style strokeStyle
createLinearGradient id, x1, y1, x2, y2, stops[{pos,color}] 渐变对象(按 id 引用)
createRadialGradient id, x0, y0, cx, cy, r, stops 渐变对象(x0/y0 为内圆心,cx/cy/r 为外圆)
createPattern id, image, matrix? createPattern("repeat") + setTransform(平铺水印/底纹;image 为瓦片 dataURL,matrix 为像素空间仿射 [a,b,c,d,e,f])
fill — fill()
fillEvenOdd — fill("evenodd")(Rule=Even-Odd 路径)
stroke — stroke()
lineWidth width lineWidth
lineCap cap lineCap
lineJoin join lineJoin
miterLimit limit miterLimit
setLineDash segments[], offset setLineDash + lineDashOffset
clip — clip()
drawImage image(images 中的 id), x, y, w, h, transform? drawImage(transform 为可选像素空间仿射,印章随页旋转用)

播放器遇到未知指令会忽略并继续(向前兼容):未来新增指令不会让旧播放器崩溃。

播放器:playCanvasScript

npm 包内置(canvas-player.js):

import { playCanvasScript } from "rust-ofd";
await playCanvasScript(canvas, script, window.devicePixelRatio || 1);

播放器做了这些事,别自己重复处理:

  • 按 width/height × dpr 设置画布尺寸(高分屏清晰)
  • 白底填充(OFD 页面默认白纸)
  • imageSmoothingEnabled = false(与桌面端渲染一致)
  • 预加载全部 images dataURL 后逐条执行指令
  • 兼容 images 为 Map 或普通对象两种容器

为什么是 JSON 指令流而不是直接 WASM 里画

指令流可以被任何语言回放(WPF、Skia、PDF 生成器……),也可以在 Worker 中生成、主线程回放。这是把「渲染语义」与「渲染目标」解耦的关键决定。