A MoonBit port of rough.js 4.6.6: hand-drawn, sketchy shapes rendered to backend-agnostic op sets and SVG.
fn main {
let gen = @moon_rough.generator()
let rect = gen.rectangle(
10.0, 10.0, 200.0, 100.0,
options=@moon_rough.Options::new(
seed=42,
fill="#e66",
fill_style=CrossHatch,
roughness=1.5,
),
)
// 和后端无关的数据:OpSet 列表,每个 op 是 Move / LineTo / BCurveTo
for set in rect.sets {
println("\{set.kind.name()}: \{set.ops.length()} ops")
}
// 转成 SVG:每个 OpSet 一个 <path>,整个 Drawable 一个 <g>
println(@moon_rough.svg_document(220.0, 120.0, @moon_rough.drawable_to_svg(rect)))
}| 包 | 内容 | 移植来源 |
|---|---|---|
| sayoi7799/moon_rough | 门面:generator()、常用类型、drawable_to_svg | — |
| .../core | Generator、Options、Drawable / OpSet / Op、renderer、7 种填充 | roughjs generator.ts、renderer.ts、fillers/* |
| .../svg | drawable_to_svg、svg_document | roughjs svg.ts(输出字符串,不操作 DOM) |
| .../random | Random(Park–Miller)、RandomSource | roughjs math.ts |
| .../path_data | parse_path、absolutize、normalize(含弧线转贝塞尔)、serialize | path-data-parser 0.1.0 |
| .../curve | points_on_bezier_curves、simplify、curve_to_bezier、points_on_path | points-on-curve 0.2.0、points-on-path 0.2.1 |
| .../hachure | hachure_lines(扫描线填充) | hachure-fill 0.5.2 |
| .../geometry | 可变的 Point | roughjs geometry.ts |
| .../jsmath | 和 V8 逐位一致的 sin / cos / tan / asin / atan、Math.round、toFixed、Number#toString、parseFloat、Math.min/max | — |
| rough.js | moon_rough |
|---|---|
| rough.generator({ options }) | @moon_rough.generator(options=…, source=…) |
| gen.line(x1, y1, x2, y2, o) | gen.line(x1, y1, x2, y2, options=o) |
| gen.rectangle / ellipse / circle | 同名方法 |
| gen.linearPath(points, o) / gen.polygon(points, o) | gen.linear_path(points) / gen.polygon(points),points : Array[(Double, Double)] |
| gen.arc(x, y, w, h, start, stop, closed, o) | gen.arc(x, y, w, h, start, stop, closed=true) |
| gen.curve(Point[]) / gen.curve(Point[][]) | gen.curve(points) / gen.curves(points_list) |
| gen.path(d, o) | gen.path(d),非法路径 raise PathError |
| gen.opsToPath(set, digits) / gen.toPaths(d) | ops_to_path(set, fixed_decimals=…) / gen.to_paths(d) |
| RoughSVG.draw(drawable) | drawable_to_svg(drawable)(属性及顺序与 setAttribute 一致) |
| { roughness: 2, fillStyle: 'zigzag' } | Options::new(roughness=2.0, fill_style=Zigzag) |
| 普通版(diago) | 手绘版(moon_rough) |
|---|---|
let svg = @diago.compile(source)
let sketch = @diago_sketch.sketchify(svg, seed=7)// js 目标。ctx 是 JavaScript 传进来的 canvas.getContext("2d")
pub fn paint(ctx : @canvas.JsContext) -> Unit {
let rc = @canvas.RoughCanvas::new(ctx, options=@moon_rough.Options::new(seed=42))
ignore(
rc.rectangle(
10.0, 10.0, 200.0, 100.0,
options=@moon_rough.Options::new(fill="#e76f51", fill_style=CrossHatch),
),
)
}node examples/web/build.mjsnode examples/web/serve.mjs| 功能 | 状态 |
|---|---|
| 图形:line、rectangle、ellipse、circle、linearPath、polygon、arc(开 / 闭)、curve(单条 / 多条)、path | ✅ 全部 |
| 填充:hachure、solid、zigzag、cross-hatch、dots、dashed、zigzag-line | ✅ 全部 |
| 选项:maxRandomnessOffset、roughness、bowing、stroke、strokeWidth、curveFitting、curveTightness、curveStepCount、fill、fillStyle、fillWeight、hachureAngle、hachureGap、simplification、dashOffset、dashGap、zigzagOffset、seed、strokeLineDash、strokeLineDashOffset、fillLineDash、fillLineDashOffset、disableMultiStroke、disableMultiStrokeFill、preserveVertices、fixedDecimalPlaceDigits、fillShapeRoughnessGain | ✅ 全部(默认值照 generator.ts) |
| SVG 路径:M L H V C S Q T A Z 及其相对形式、隐式重复、紧凑数字写法、弧线转三次贝塞尔 | ✅ |
| 输出:Drawable(OpSet 列表)、SVG 字符串、opsToPath、toPaths | ✅ |
| 给 diago 的流程图加手绘风格 | ✅ 示例模块 examples/diago_sketch,见上文「给 diago 的流程图加手绘风格」 |
| Canvas 后端(RoughCanvas) | ✅ sayoi7799/moon_rough/canvas:绘制逻辑在所有目标上编译,js 目标有真正的 canvas 绑定,wasm-gc 和 native 暂无(见上文「Canvas 后端和浏览器演示页」) |
| 浏览器演示页:和官方 rough.js 并排对比 | ✅ examples/web,见上文「Canvas 后端和浏览器演示页」 |
| 动画:抖动线条(每帧换 seed)、描线(逐步画出前 k 个 op) | ✅ examples/web/animation.html,每一帧和官方 rough.js 对比,见上文 |
| 性能测试:和 Node 上的 rough.js 对比 | ✅ src/perf,结果见下文「性能」 |
| 答辩材料 | ✅ docs/defense.md |
moon test --target jsmoon test --target wasm-gcmoon test --target native| 类别 | 内容 |
|---|---|
| 底层 | Random 13 个 seed(含 −2³¹、2³¹−1、cloneOptionsAlterSeed 产生的非 int32 的 2³¹)各前 1000 个输出、seed 0 的回退;约 2.9 万条数学 / toFixed / toString / parseFloat 用例;456 条路径的 parse / absolutize / normalize / serialize;Bézier 采样、简化、curveToBezier、pointsOnPath(含点对象的身份);600 组 hachure-fill(含旋转回来后的多边形坐标) |
| 图形 × 填充 | 642 个用例:所有图形 × 描边 / solid / hachure × 多组选项;415 个用例:8 种可填充图形 × 7 种填充 × 多组选项 |
| 随机组合 | 600 组随机图形 + 随机选项(roughness、bowing、角度、间距、preserveVertices、disableMultiStroke(Fill)、曲线参数、虚线 / 锯齿参数、simplification、各种 fill 值和填充样式) |
| 边界情况 | 宽高为 0、长度为 0、roughness 0、单点 / 两点 / 空的 polygon、linearPath 和 curve、空路径、只有 M 的路径、带相对命令和弧线的复杂路径、非法路径(返回错误)、无穷坐标、curveStepCount 1 和 2 等 321 个用例;同一生成器连续调用的序列;以及 rough.js 会死循环或抛异常的输入(只检查能正常结束) |
| suite | values | js | wasm-gc | native |
|---|---|---|---|---|
| core edge cases | 69890 | 0 (0 inexact) | 0 (0 inexact) | 0 (0 inexact) |
| core fills | 309900 | 0 (0 inexact) | 0 (0 inexact) | 0 (0 inexact) |
| core random | 249504 | 0 (0 inexact) | 0 (0 inexact) | 0 (0 inexact) |
| core sequences | 3248 | 0 (0 inexact) | 0 (0 inexact) | 0 (0 inexact) |
| core shapes | 196962 | 0 (0 inexact) | 0 (0 inexact) | 0 (0 inexact) |
| curve curve_to_bezier | 10614 | 0 (0 inexact) | 0 (0 inexact) | 0 (0 inexact) |
| curve points_on_bezier_curves | 51712 | 0 (0 inexact) | 0 (0 inexact) | 0 (0 inexact) |
| curve points_on_path | 67786 | 0 (0 inexact) | 0 (0 inexact) | 0 (0 inexact) |
| curve simplify | 7796 | 0 (0 inexact) | 0 (0 inexact) | 0 (0 inexact) |
| hachure | 994388 | 0 (0 inexact) | 0 (0 inexact) | 0 (0 inexact) |
| jsmath trig | 28085 | 0 (0 inexact) | 0 (0 inexact) | 0 (0 inexact) |
| jsmath trig edge cases | 12200 | 0 (0 inexact) | 0 (0 inexact) | 0 (0 inexact) |
| path_data | 43038 | 0 (0 inexact) | 0 (0 inexact) | 0 (0 inexact) |
| random | 13000 | 0 (0 inexact) | 0 (0 inexact) | 0 (0 inexact) |
| 项目 | rough.js(Node) | moon_rough js | wasm-gc | native |
|---|---|---|---|---|
| 10,000 个随机图形(生成) | 610 ms | 395 ms(1.5× 快) | 233 ms(2.6× 快) | 708 ms(1.2× 慢) |
| 500 个 dots 填充的图形 | 371 ms | 786 ms(2.1× 慢) | 269 ms(1.4× 快) | 666 ms(1.8× 慢) |
| 复杂路径:解析、绝对化、规范化 | 427 µs | 300 µs(1.4× 快) | 127 µs(3.4× 快) | 365 µs(1.2× 快) |
| 复杂路径:曲线采样和简化 | 754 µs | 750 µs(持平) | 392 µs(1.9× 快) | 1.8 ms(2.4× 慢) |
| 复杂路径:hachure 填充 | 2.7 ms | 2.2 ms(1.2× 快) | 1.4 ms(2.0× 快) | 3.8 ms(1.4× 慢) |
| 复杂路径:solid 填充 | 1.4 ms | 1.3 ms(1.1× 快) | 655 µs(2.2× 快) | 2.5 ms(1.7× 慢) |
| 复杂路径:cross-hatch 填充 | 4.0 ms | 3.2 ms(1.2× 快) | 2.1 ms(2.0× 快) | 5.2 ms(1.3× 慢) |
| 10,000 个图形转 SVG 字符串 | 1.10 s | 2.28 s(2.1× 慢) | 2.36 s(2.2× 慢) | 2.63 s(2.4× 慢) |
| 同上,保留两位小数 | 1.77 s | 4.06 s(2.3× 慢) | 3.72 s(2.1× 慢) | 12.12 s(6.9× 慢) |
node scripts/bench.mjsmoon bench -p sayoi7799/moon_rough/perf --target wasm-gc --releasecd scripts && npm ci && npm run gen| 项目 | 版本 | 许可证 | 作者 |
|---|---|---|---|
| roughjs | 4.6.6 | MIT | Preet Shihn |
| hachure-fill | 0.5.2 | MIT | Preet Shihn |
| path-data-parser | 0.1.0 | MIT | Preet Shihn |
| points-on-curve | 0.2.0 | MIT | Preet Shihn |
| points-on-path | 0.2.1 | MIT | Preet Shihn |
fn svg_document(width : Double, height : Double, content : String, background? : String) -> StringInstall
Download zipA MoonBit port of rough.js 4.6.6: hand-drawn, sketchy shapes rendered to backend-agnostic op sets and SVG.