# paperjs-offset

Paper.js 路径偏移辅助库：闭合路径偏移、描边轮廓和结果质量分析。

## Metadata

- HTML: https://glenzli.com/projects/paperjs-offset/
- Markdown: https://glenzli.com/projects/paperjs-offset.md
- Collection: Projects
- Language: zh-CN
- Published: 2026-06-29
- Status: active
- Tags: geometry, paperjs, typescript

## Content

`paperjs-offset` 是一个面向 [Paper.js](https://paperjs.org/) 的路径偏移辅助库。它接受 `paper.Path` 和 `paper.CompoundPath`，可以生成闭合路径偏移、可填充的描边轮廓，并提供用于检查结果几何质量的分析信息。

这个库刻意保持较小的 API 面：扩张或收缩一个闭合路径，把中心线描边转换成闭合轮廓，然后检查生成结果是否足够可靠。

## 闭合路径偏移

正距离会把闭合形状向外扩张，负距离会向内收缩。源路径仍由调用方控制，因此可以保留它用于对比，也可以在生成后移除。

```ts
const expanded = offset(source, 14, {
  join: "round",
  insert: false
});

const contracted = offset(source, -10, {
  join: "round",
  insert: false
});
```

这是大多数基础 demo 和 Gallery case 的核心操作。

## 描边轮廓

`offsetStroke` 把开放或闭合中心线作为独立操作处理。Join 决定拐角如何重建，cap 决定开放路径端点如何闭合。对自然形状来说，`round` 通常是最稳妥的默认值；`miter` 和 `bevel` 更适合锐角构造几何。

```ts
const outline = offsetStroke(centerline, 8, {
  join: "round",
  cap: "round",
  insert: false
});
```

## 质量分析

默认的 `algorithm: "auto"` 会先生成 adaptive 结果。质量检查没有发现自交、意外空结果、包含关系错误或负向偏移距离坍缩等结构性问题时，它会直接返回；只有发现异常，才继续比较其他策略。

同一套评分信息也可以通过 `analyze` 读取。分析时应传入实际使用的 join、cap 和 stroke 语义，避免把 bevel 连接或 butt 端点的预期形状误判为距离坍缩。

```ts
const result = offset(source, 12, {
  algorithm: "auto",
  join: "round",
  insert: false
});

const quality = analyze(source, result, 12, {
  join: "round"
});
```

## 策略选择

| 模式 | 适用情况 |
| --- | --- |
| `auto` | 推荐的应用默认值；先走 adaptive，必要时再比较其他候选。 |
| `adaptive` | 保持请求的偏移距离；激进内缩接近塌缩边界时，会比较原 join 与更安全的 round join。 |
| `robust` | 对凹角和自交描边轮廓执行额外清理。 |
| `split` | 拆分自交曲线，保留比 robust 更多的原始结构。 |
| `legacy` | 尽量维持旧版本输出，用于兼容已有结果。 |

`auto` 的目标不是让调用方猜测哪种算法适合某个图形，而是在常见输入上避免不必要的多策略计算，并在困难几何出现时才扩大搜索范围。需要复现历史输出或检查某个特定策略时，仍然可以显式选择模式。

## 交互指南

下面的例子来自项目仓库生成的 Dev Site Export Contract。详情页只读取 `manifest.json`、`data/guide.json` 和 runtime，具体示例内容仍由 `paperjs-offset` 仓库维护。

![paperjs-offset 交互指南截图](/projects/paperjs-offset/assets/guide.png)

交互指南的静态截图。启用 JavaScript 后会挂载项目仓库导出的实时 canvas 示例。

## Playground

Playground 使用同一套导出的 data 和 runtime。你可以选择预设形状，调整偏移参数，并立即观察生成路径。

![paperjs-offset Playground 截图](/projects/paperjs-offset/assets/playground.png)

Playground 的静态截图。启用 JavaScript 后会挂载可调参数的实时偏移实验区。

## Gallery

Gallery 由 `data/gallery.json` 驱动，包含 198 个确定性 case。页面默认先显示 12 个轻量精选案例，基础、进阶、边界与特殊 case 会在切换 tab 后再渲染。每个 case 都带有 SVG path、operation、distance、options 和 expected 质量信息。

除常规的星形、平滑曲线、孔洞和开放描边外，用例还覆盖嵌套岛屿轮廓、尺寸悬殊的分离组件、深度细分、包含关系泄漏、数值精度敏感的侵蚀，以及应当完全消失的负向偏移。这些输入既用于页面展示，也直接来自项目的压力测试语料。

![paperjs-offset Gallery 截图](/projects/paperjs-offset/assets/gallery.png)

Gallery 的静态截图。启用 JavaScript 后会从项目导出的 gallery 数据渲染全部确定性 case。

## 用例与验证

固定 Gallery 之外，测试还会使用固定 seed 生成 400 个 fuzz case，覆盖随机多边形、Bezier 曲线、复合路径和开放描边。失败输入可以用 seed 和 case 编号重放，再收录为新的确定性回归用例。

Benchmark 使用同一批语料比较不同算法，输出总耗时、P50、P95、最大耗时和最慢案例。它不设置容易受机器差异影响的硬性时间阈值，主要用于观察策略调整是否引入明显退化。

## 安装

使用这个库的应用需要自行安装 Paper.js：

```sh
npm install paper paperjs-offset
```

## 推荐 API

新代码推荐使用具名函数：

```ts
import paper from "paper";
import { analyze, offset, offsetStroke } from "paperjs-offset";

paper.setup(new paper.Size(400, 300));
```

核心函数包括：

```ts
offset(path, distance, options);
offsetStroke(path, distance, options);
analyze(source, result, distance, options);
```

`offset` 负责闭合路径的扩张或收缩。`offsetStroke` 把中心线描边转换成闭合轮廓。`analyze` 对生成结果打分，并报告困难几何 case 的警告信号。

## 说明

Bezier 几何偏移是近似问题，尤其在紧曲线、凹角、自交、复合路径和激进负偏移附近更明显。

`adaptive` 会保留调用方请求的偏移距离，不再通过缩小负向偏移来换取看起来更安全的图形。激进内缩接近临界区时，它会比较请求的 bevel/miter join 与 round 候选，避免退化轮廓面积重新增长，或在完全消失后再次出现。对于过度侵蚀后理应消失的形状，空结果本身可能就是正确结果；是否可接受，应结合业务语义和 `analyze` 的报告判断。
