Skip to content

Canvas 指令集 ​

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

结构 ​

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

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

指令全集(23 种) ​

op参数对应 Canvas API
save—save()
restore—restore()
globalAlphaalphaglobalAlpha
beginPath—beginPath()
moveTox, ymoveTo
lineTox, ylineTo
quadraticCurveTocpx, cpy, x, yquadraticCurveTo
bezierCurveTocp1x, cp1y, cp2x, cp2y, x, ybezierCurveTo
closePath—closePath()
fillStylestyle(颜色/渐变/图案 id)fillStyle
strokeStylestylestrokeStyle
createLinearGradientid, x1, y1, x2, y2, stops[{pos,color}]渐变对象(按 id 引用)
createRadialGradientid, x0, y0, cx, cy, r, stops渐变对象(x0/y0 为内圆心,cx/cy/r 为外圆)
createPatternid, image, matrix?createPattern("repeat") + setTransform(平铺水印/底纹;image 为瓦片 dataURL,matrix 为像素空间仿射 [a,b,c,d,e,f])
fill—fill()
stroke—stroke()
lineWidthwidthlineWidth
lineCapcaplineCap
lineJoinjoinlineJoin
miterLimitlimitmiterLimit
setLineDashsegments[], offsetsetLineDash + lineDashOffset
clip—clip()
drawImageimage(images 中的 id), x, y, w, h, transform?drawImage(transform 为可选像素空间仿射,印章随页旋转用)

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

播放器:playCanvasScript ​

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

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 中生成、主线程回放。这是把「渲染语义」与「渲染目标」解耦的关键决定。

专有软件 · 商业授权(闭源)