stroke-outline 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +96 -2
- package/THIRD_PARTY_NOTICES +29 -0
- package/dist/index.d.ts +102 -0
- package/dist/index.js +212 -0
- package/dist/wasm/stroke_outline.d.ts +96 -0
- package/dist/wasm/stroke_outline.js +4140 -0
- package/package.json +51 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yuki Kishino
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,97 @@
|
|
|
1
|
-
#
|
|
1
|
+
# stroke-outline
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Turn vector paths into the filled outline of their stroke — the geometry a
|
|
4
|
+
canvas draws for `stroke()`, returned as a path you can fill, cache, hit-test
|
|
5
|
+
or tessellate. A WebAssembly build of a C++ library; works in browsers, workers
|
|
6
|
+
and Node.
|
|
7
|
+
|
|
8
|
+
```js
|
|
9
|
+
import { init } from 'stroke-outline';
|
|
10
|
+
|
|
11
|
+
const so = await init(); // loads the WASM once; reuse the result
|
|
12
|
+
|
|
13
|
+
// stroke SVG path data
|
|
14
|
+
const outline = so.buildOutline('M 10 10 H 90 A 20 20 0 0 1 90 50', { lineWidth: 8, lineCap: 'round' });
|
|
15
|
+
outline.toSVG(); // SVG path data — <path d>, new Path2D(...), a file
|
|
16
|
+
outline.commands; // [{ type: 'moveTo', x, y }, { type: 'cubicTo', c1x, ... }, ...]
|
|
17
|
+
outline.error; // null, or { message } if the SVG data was malformed
|
|
18
|
+
|
|
19
|
+
// parse SVG once, then stroke it as often as you like
|
|
20
|
+
const icon = so.pathFromSVG('M 0 0 H 100 V 100 H 0 Z M 30 30 V 70 H 70 V 30 Z');
|
|
21
|
+
const thin = so.buildOutline(icon, { lineWidth: 2 });
|
|
22
|
+
const thick = so.buildOutline(icon, { lineWidth: 12, align: 'inside' });
|
|
23
|
+
thinEl.setAttribute('d', thin.toSVG());
|
|
24
|
+
thickEl.setAttribute('d', thick.toSVG());
|
|
25
|
+
|
|
26
|
+
// or build paths in code
|
|
27
|
+
const path = new so.Path().moveTo(0, 0).lineTo(100, 0).arcTo(30, 30, 0, false, true, 100, 60);
|
|
28
|
+
const dashed = so.buildOutline(path, { lineWidth: 6, align: 'inside', dash: [12, 6] });
|
|
29
|
+
ctx.fill(new Path2D(dashed.toSVG())); // each dash is its own subpath
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Fill outlines with the nonzero rule** (the default for canvas `fill()` and
|
|
33
|
+
SVG). Outlines may overlap themselves; nonzero renders that correctly. With
|
|
34
|
+
`clipToShape: true`, closed paths come out as simple contours that also fill
|
|
35
|
+
correctly with even-odd.
|
|
36
|
+
|
|
37
|
+
## API
|
|
38
|
+
|
|
39
|
+
- `init()` → `Promise<StrokeOutline>`. Loads the WASM module; call it once and
|
|
40
|
+
reuse the result.
|
|
41
|
+
- `so.buildOutline(pathOrSvg, options?)` → the outline as a `Path`. Takes a `Path` or
|
|
42
|
+
SVG path data; malformed data is stroked up to the first error, and the
|
|
43
|
+
outline's `error` says why (as for `pathFromSVG`, below).
|
|
44
|
+
- `new so.Path()` with `moveTo`, `lineTo`, `quadTo`, `cubicTo`,
|
|
45
|
+
`arcTo(rx, ry, xAxisRotationDeg, largeArc, sweep, x, y)` and `close`, all
|
|
46
|
+
chainable; `.commands` and `.toSVG(precision = 3)`.
|
|
47
|
+
- `so.pathFromSVG(d)` → `Path`. Malformed path data keeps everything before
|
|
48
|
+
the first error (as SVG renders it), and `path.error` is
|
|
49
|
+
`{ message }`, saying what went wrong and where
|
|
50
|
+
(`'expected a number at position 11'`, an index into `d`). `error` is `null`
|
|
51
|
+
for valid data and for paths built in code. Use it to parse once and stroke
|
|
52
|
+
many times, or to read the input's commands.
|
|
53
|
+
- `precision` is a minimum number of decimals; small paths get more, so
|
|
54
|
+
rounding stays within ~1e-4 of their size.
|
|
55
|
+
- `so.module` is the raw Embind module, for anything not wrapped here.
|
|
56
|
+
|
|
57
|
+
Nothing needs freeing: the wrapper releases every WASM-side object itself.
|
|
58
|
+
|
|
59
|
+
### Options
|
|
60
|
+
|
|
61
|
+
All optional; omitted ones take these defaults.
|
|
62
|
+
|
|
63
|
+
| Option | Default | |
|
|
64
|
+
| --- | --- | --- |
|
|
65
|
+
| `lineWidth` | `1` | must be a positive finite number, else nothing is stroked |
|
|
66
|
+
| `lineJoin` | `'miter'` | `'miter'`, `'bevel'`, `'round'` |
|
|
67
|
+
| `lineCap` | `'butt'` | `'butt'`, `'square'`, `'round'` |
|
|
68
|
+
| `miterLimit` | `4` | below 1 behaves as 1 (always bevel) |
|
|
69
|
+
| `align` | `'center'` | `'center'`, `'inside'`, `'outside'` — closed subpaths, relative to the whole fill (holes included) |
|
|
70
|
+
| `fillRule` | `'nonzero'` | `'nonzero'`, `'evenodd'` — how subpaths form the fill, for `align` and `clipToShape` |
|
|
71
|
+
| `clipToShape` | `false` | resolve over-wide or overlapping strokes into simple contours |
|
|
72
|
+
| `dash` | none | e.g. `[10, 5]`; `[0, gap]` with round caps draws dots |
|
|
73
|
+
| `dashOffset` | `0` | |
|
|
74
|
+
|
|
75
|
+
An unknown option value (e.g. `lineJoin: 'rounded'`) throws a `TypeError`.
|
|
76
|
+
|
|
77
|
+
## Bundlers
|
|
78
|
+
|
|
79
|
+
The WebAssembly binary is embedded in the package's JavaScript, so there is no
|
|
80
|
+
`.wasm` file to serve or configure: it works as is in Vite (dev and build),
|
|
81
|
+
webpack, Rollup, esbuild and Node.
|
|
82
|
+
|
|
83
|
+
## Building from source
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
# from the repository root: build the WASM module (publishes it into js/src/wasm)
|
|
87
|
+
cmake --preset wasm && cmake --build --preset wasm-release
|
|
88
|
+
cd js
|
|
89
|
+
npm install
|
|
90
|
+
npm test # builds dist/ and runs the tests in Node
|
|
91
|
+
npm pack # the tarball that would be published
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## License
|
|
95
|
+
|
|
96
|
+
MIT — see `LICENSE`. Includes code ported from svg-arc-to-cubic-bezier
|
|
97
|
+
(Colin Meinke, ISC license); see `THIRD_PARTY_NOTICES`.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Third-party notices
|
|
2
|
+
===================
|
|
3
|
+
|
|
4
|
+
This project includes code derived from the following third-party software.
|
|
5
|
+
|
|
6
|
+
-------------------------------------------------------------------------------
|
|
7
|
+
svg-arc-to-cubic-bezier
|
|
8
|
+
https://github.com/colinmeinke/svg-arc-to-cubic-bezier
|
|
9
|
+
|
|
10
|
+
Used in: src/io/svg_arc.cpp (SVG elliptical arc to cubic bézier conversion),
|
|
11
|
+
ported to C++. Also compiled into the WebAssembly module.
|
|
12
|
+
|
|
13
|
+
Internet Systems Consortium license
|
|
14
|
+
===================================
|
|
15
|
+
|
|
16
|
+
Copyright (c) `2017`, `Colin Meinke`
|
|
17
|
+
|
|
18
|
+
Permission to use, copy, modify, and/or distribute this software for any purpose
|
|
19
|
+
with or without fee is hereby granted, provided that the above copyright notice
|
|
20
|
+
and this permission notice appear in all copies.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
23
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
|
24
|
+
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
25
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
|
26
|
+
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
|
27
|
+
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
|
28
|
+
THIS SOFTWARE.
|
|
29
|
+
-------------------------------------------------------------------------------
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { MainModule } from './wasm/stroke_outline.js';
|
|
2
|
+
export type LineJoin = 'miter' | 'bevel' | 'round';
|
|
3
|
+
export type LineCap = 'butt' | 'square' | 'round';
|
|
4
|
+
export type StrokeAlign = 'center' | 'inside' | 'outside';
|
|
5
|
+
export type FillRule = 'nonzero' | 'evenodd';
|
|
6
|
+
export interface StrokeOptions {
|
|
7
|
+
/** Stroke width; must be a positive finite number. Default 1. */
|
|
8
|
+
lineWidth?: number;
|
|
9
|
+
/** Default 'miter'. */
|
|
10
|
+
lineJoin?: LineJoin;
|
|
11
|
+
/** Default 'butt'. */
|
|
12
|
+
lineCap?: LineCap;
|
|
13
|
+
/** Miter length / line width beyond which a miter is beveled. Default 4. */
|
|
14
|
+
miterLimit?: number;
|
|
15
|
+
/** Inside/outside apply to closed subpaths, relative to the whole fill. Default 'center'. */
|
|
16
|
+
align?: StrokeAlign;
|
|
17
|
+
/** How subpaths combine into a fill (for align and clipToShape). Default 'nonzero'. */
|
|
18
|
+
fillRule?: FillRule;
|
|
19
|
+
/** Resolve over-wide/overlapping strokes into simple contours. Default false. */
|
|
20
|
+
clipToShape?: boolean;
|
|
21
|
+
/** Dash pattern, e.g. [10, 5]. Empty or omitted: solid. [0, gap] with round caps: dots. */
|
|
22
|
+
dash?: number[];
|
|
23
|
+
/** Where the dash pattern starts. Default 0. */
|
|
24
|
+
dashOffset?: number;
|
|
25
|
+
}
|
|
26
|
+
/** One path command; coordinates are absolute. */
|
|
27
|
+
export type PathCommand = {
|
|
28
|
+
type: 'moveTo';
|
|
29
|
+
x: number;
|
|
30
|
+
y: number;
|
|
31
|
+
} | {
|
|
32
|
+
type: 'lineTo';
|
|
33
|
+
x: number;
|
|
34
|
+
y: number;
|
|
35
|
+
} | {
|
|
36
|
+
type: 'quadTo';
|
|
37
|
+
cx: number;
|
|
38
|
+
cy: number;
|
|
39
|
+
x: number;
|
|
40
|
+
y: number;
|
|
41
|
+
} | {
|
|
42
|
+
type: 'cubicTo';
|
|
43
|
+
c1x: number;
|
|
44
|
+
c1y: number;
|
|
45
|
+
c2x: number;
|
|
46
|
+
c2y: number;
|
|
47
|
+
x: number;
|
|
48
|
+
y: number;
|
|
49
|
+
} | {
|
|
50
|
+
type: 'close';
|
|
51
|
+
};
|
|
52
|
+
/** Why SVG path data stopped parsing. */
|
|
53
|
+
export interface SvgError {
|
|
54
|
+
/**
|
|
55
|
+
* What went wrong, and where (an index into the string):
|
|
56
|
+
* `'expected a number at position 11'`, or, when the data ends too early,
|
|
57
|
+
* `'expected a number at position 10 (end of data)'`.
|
|
58
|
+
*/
|
|
59
|
+
message: string;
|
|
60
|
+
}
|
|
61
|
+
export interface Path {
|
|
62
|
+
moveTo(x: number, y: number): Path;
|
|
63
|
+
lineTo(x: number, y: number): Path;
|
|
64
|
+
quadTo(cx: number, cy: number, x: number, y: number): Path;
|
|
65
|
+
cubicTo(c1x: number, c1y: number, c2x: number, c2y: number, x: number, y: number): Path;
|
|
66
|
+
/** SVG elliptical arc to (x, y), expanded to cubics. */
|
|
67
|
+
arcTo(rx: number, ry: number, xAxisRotationDeg: number, largeArc: boolean, sweep: boolean, x: number, y: number): Path;
|
|
68
|
+
close(): Path;
|
|
69
|
+
/** The commands so far (absolute coordinates). */
|
|
70
|
+
readonly commands: readonly PathCommand[];
|
|
71
|
+
/**
|
|
72
|
+
* For a path parsed from malformed SVG data, or the outline of one: why and
|
|
73
|
+
* where parsing stopped (the path holds everything before it, as SVG
|
|
74
|
+
* renders it). Otherwise null.
|
|
75
|
+
*/
|
|
76
|
+
readonly error: SvgError | null;
|
|
77
|
+
/** SVG path data; `precision` is the minimum number of decimals (default 3). */
|
|
78
|
+
toSVG(precision?: number): string;
|
|
79
|
+
}
|
|
80
|
+
export interface StrokeOutline {
|
|
81
|
+
/** A new, empty path. */
|
|
82
|
+
Path: new () => Path;
|
|
83
|
+
/**
|
|
84
|
+
* Parse SVG path data. Malformed data keeps everything before the first
|
|
85
|
+
* error, as SVG renders it; `path.error` says whether, why and where.
|
|
86
|
+
*/
|
|
87
|
+
pathFromSVG(d: string): Path;
|
|
88
|
+
/**
|
|
89
|
+
* Stroke a path, or SVG path data; returns the outline as a new path (fill
|
|
90
|
+
* it with nonzero). Malformed SVG data is stroked up to the first error, as
|
|
91
|
+
* SVG renders it, and the outline's `error` says why and where.
|
|
92
|
+
*/
|
|
93
|
+
buildOutline(path: Path | string, options?: StrokeOptions): Path;
|
|
94
|
+
/** The underlying Embind module, for anything not wrapped here. */
|
|
95
|
+
readonly module: MainModule;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Load the WASM module and return the API. Call once and reuse the result.
|
|
99
|
+
* The WebAssembly binary is embedded in the package, so there is no file to
|
|
100
|
+
* serve or locate.
|
|
101
|
+
*/
|
|
102
|
+
export declare function init(): Promise<StrokeOutline>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
// The stroke-outline JavaScript API: a thin, typed wrapper over the WASM module.
|
|
2
|
+
//
|
|
3
|
+
// import { init } from 'stroke-outline';
|
|
4
|
+
// const so = await init();
|
|
5
|
+
// const outline = so.buildOutline('M 0 0 L 100 0', { lineWidth: 8, lineCap: 'round' });
|
|
6
|
+
// ctx.fill(new Path2D(outline.toSVG()));
|
|
7
|
+
//
|
|
8
|
+
// Options are partial: anything omitted takes the C++ default. Nothing here
|
|
9
|
+
// needs freeing; the wrapper releases every WASM-side object itself.
|
|
10
|
+
import createModule from './wasm/stroke_outline.js';
|
|
11
|
+
/**
|
|
12
|
+
* Load the WASM module and return the API. Call once and reuse the result.
|
|
13
|
+
* The WebAssembly binary is embedded in the package, so there is no file to
|
|
14
|
+
* serve or locate.
|
|
15
|
+
*/
|
|
16
|
+
export async function init() {
|
|
17
|
+
return createApi(await createModule());
|
|
18
|
+
}
|
|
19
|
+
function createApi(module) {
|
|
20
|
+
const ZERO = { x: 0, y: 0 };
|
|
21
|
+
const joins = { miter: module.LineJoin.Miter, bevel: module.LineJoin.Bevel, round: module.LineJoin.Round };
|
|
22
|
+
const caps = { butt: module.LineCap.Butt, square: module.LineCap.Square, round: module.LineCap.Round };
|
|
23
|
+
const aligns = {
|
|
24
|
+
center: module.StrokeAlign.Center,
|
|
25
|
+
inside: module.StrokeAlign.Inside,
|
|
26
|
+
outside: module.StrokeAlign.Outside,
|
|
27
|
+
};
|
|
28
|
+
const fillRules = { nonzero: module.FillRule.NonZero, evenodd: module.FillRule.EvenOdd };
|
|
29
|
+
function pick(table, value, name, fallback) {
|
|
30
|
+
if (value === undefined)
|
|
31
|
+
return fallback;
|
|
32
|
+
if (!(value in table))
|
|
33
|
+
throw new TypeError(`stroke-outline: unknown ${name} '${value}'`);
|
|
34
|
+
return table[value];
|
|
35
|
+
}
|
|
36
|
+
function settings(options = {}) {
|
|
37
|
+
const d = module.defaultStrokeSettings();
|
|
38
|
+
return {
|
|
39
|
+
lineWidth: options.lineWidth ?? d.lineWidth,
|
|
40
|
+
lineJoin: pick(joins, options.lineJoin, 'lineJoin', d.lineJoin),
|
|
41
|
+
lineCap: pick(caps, options.lineCap, 'lineCap', d.lineCap),
|
|
42
|
+
miterLimit: options.miterLimit ?? d.miterLimit,
|
|
43
|
+
align: pick(aligns, options.align, 'align', d.align),
|
|
44
|
+
fillRule: pick(fillRules, options.fillRule, 'fillRule', d.fillRule),
|
|
45
|
+
clipToShape: options.clipToShape ?? d.clipToShape,
|
|
46
|
+
dashPattern: options.dash ?? d.dashPattern,
|
|
47
|
+
dashOffset: options.dashOffset ?? d.dashOffset,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function toWasm(commands) {
|
|
51
|
+
const vec = new module.PathCommandVector();
|
|
52
|
+
for (const c of commands) {
|
|
53
|
+
let command;
|
|
54
|
+
switch (c.type) {
|
|
55
|
+
case 'moveTo':
|
|
56
|
+
command = { verb: module.Verb.MoveTo, ctrl1: ZERO, ctrl2: ZERO, to: { x: c.x, y: c.y } };
|
|
57
|
+
break;
|
|
58
|
+
case 'lineTo':
|
|
59
|
+
command = { verb: module.Verb.LineTo, ctrl1: ZERO, ctrl2: ZERO, to: { x: c.x, y: c.y } };
|
|
60
|
+
break;
|
|
61
|
+
case 'quadTo':
|
|
62
|
+
command = {
|
|
63
|
+
verb: module.Verb.QuadraticTo,
|
|
64
|
+
ctrl1: { x: c.cx, y: c.cy },
|
|
65
|
+
ctrl2: ZERO,
|
|
66
|
+
to: { x: c.x, y: c.y },
|
|
67
|
+
};
|
|
68
|
+
break;
|
|
69
|
+
case 'cubicTo':
|
|
70
|
+
command = {
|
|
71
|
+
verb: module.Verb.CubicTo,
|
|
72
|
+
ctrl1: { x: c.c1x, y: c.c1y },
|
|
73
|
+
ctrl2: { x: c.c2x, y: c.c2y },
|
|
74
|
+
to: { x: c.x, y: c.y },
|
|
75
|
+
};
|
|
76
|
+
break;
|
|
77
|
+
case 'close':
|
|
78
|
+
command = { verb: module.Verb.Close, ctrl1: ZERO, ctrl2: ZERO, to: ZERO };
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
vec.push_back(command);
|
|
82
|
+
}
|
|
83
|
+
return vec;
|
|
84
|
+
}
|
|
85
|
+
// Read a vector into plain commands and free it.
|
|
86
|
+
function fromWasm(vec) {
|
|
87
|
+
try {
|
|
88
|
+
const out = [];
|
|
89
|
+
for (let i = 0; i < vec.size(); i++) {
|
|
90
|
+
const c = vec.get(i);
|
|
91
|
+
switch (c.verb.value) {
|
|
92
|
+
case module.Verb.MoveTo.value:
|
|
93
|
+
out.push({ type: 'moveTo', x: c.to.x, y: c.to.y });
|
|
94
|
+
break;
|
|
95
|
+
case module.Verb.LineTo.value:
|
|
96
|
+
out.push({ type: 'lineTo', x: c.to.x, y: c.to.y });
|
|
97
|
+
break;
|
|
98
|
+
case module.Verb.QuadraticTo.value:
|
|
99
|
+
out.push({ type: 'quadTo', cx: c.ctrl1.x, cy: c.ctrl1.y, x: c.to.x, y: c.to.y });
|
|
100
|
+
break;
|
|
101
|
+
case module.Verb.CubicTo.value:
|
|
102
|
+
out.push({
|
|
103
|
+
type: 'cubicTo',
|
|
104
|
+
c1x: c.ctrl1.x,
|
|
105
|
+
c1y: c.ctrl1.y,
|
|
106
|
+
c2x: c.ctrl2.x,
|
|
107
|
+
c2y: c.ctrl2.y,
|
|
108
|
+
x: c.to.x,
|
|
109
|
+
y: c.to.y,
|
|
110
|
+
});
|
|
111
|
+
break;
|
|
112
|
+
case module.Verb.Close.value:
|
|
113
|
+
out.push({ type: 'close' });
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
vec.delete();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function withWasm(commands, use) {
|
|
124
|
+
const vec = toWasm(commands);
|
|
125
|
+
try {
|
|
126
|
+
return use(vec);
|
|
127
|
+
}
|
|
128
|
+
finally {
|
|
129
|
+
vec.delete();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
class PathImpl {
|
|
133
|
+
list = [];
|
|
134
|
+
// Current point and subpath start, for arcs and close.
|
|
135
|
+
x = 0;
|
|
136
|
+
y = 0;
|
|
137
|
+
startX = 0;
|
|
138
|
+
startY = 0;
|
|
139
|
+
error;
|
|
140
|
+
constructor(commands = [], error = null) {
|
|
141
|
+
for (const c of commands)
|
|
142
|
+
this.push(c);
|
|
143
|
+
this.error = error;
|
|
144
|
+
}
|
|
145
|
+
push(c) {
|
|
146
|
+
this.list.push(c);
|
|
147
|
+
if (c.type === 'close') {
|
|
148
|
+
this.x = this.startX;
|
|
149
|
+
this.y = this.startY;
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
this.x = c.x;
|
|
153
|
+
this.y = c.y;
|
|
154
|
+
if (c.type === 'moveTo') {
|
|
155
|
+
this.startX = c.x;
|
|
156
|
+
this.startY = c.y;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return this;
|
|
160
|
+
}
|
|
161
|
+
get commands() {
|
|
162
|
+
return this.list;
|
|
163
|
+
}
|
|
164
|
+
moveTo(x, y) {
|
|
165
|
+
return this.push({ type: 'moveTo', x, y });
|
|
166
|
+
}
|
|
167
|
+
lineTo(x, y) {
|
|
168
|
+
return this.push({ type: 'lineTo', x, y });
|
|
169
|
+
}
|
|
170
|
+
quadTo(cx, cy, x, y) {
|
|
171
|
+
return this.push({ type: 'quadTo', cx, cy, x, y });
|
|
172
|
+
}
|
|
173
|
+
cubicTo(c1x, c1y, c2x, c2y, x, y) {
|
|
174
|
+
return this.push({ type: 'cubicTo', c1x, c1y, c2x, c2y, x, y });
|
|
175
|
+
}
|
|
176
|
+
arcTo(rx, ry, rotation, largeArc, sweep, x, y) {
|
|
177
|
+
// From the current point. A degenerate arc comes back as a straight line.
|
|
178
|
+
for (const c of fromWasm(module.arcToCommands(this.x, this.y, rx, ry, rotation, largeArc, sweep, x, y)))
|
|
179
|
+
this.push(c);
|
|
180
|
+
return this;
|
|
181
|
+
}
|
|
182
|
+
close() {
|
|
183
|
+
return this.push({ type: 'close' });
|
|
184
|
+
}
|
|
185
|
+
toSVG(precision = 3) {
|
|
186
|
+
return withWasm(this.list, vec => module.pathToSvg(vec, precision));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
Path: PathImpl,
|
|
191
|
+
pathFromSVG: d => {
|
|
192
|
+
const parsed = module.parseSvgPathChecked(d);
|
|
193
|
+
const error = parsed.ok ? null : { message: String(parsed.message) };
|
|
194
|
+
return new PathImpl(fromWasm(parsed.commands), error);
|
|
195
|
+
},
|
|
196
|
+
buildOutline: (path, options) => {
|
|
197
|
+
const s = settings(options);
|
|
198
|
+
if (typeof path !== 'string')
|
|
199
|
+
return new PathImpl(withWasm(path.commands, vec => fromWasm(module.buildStrokeOutline(vec, s))));
|
|
200
|
+
// SVG data: stroke the parsed commands where they are, in WASM memory.
|
|
201
|
+
const parsed = module.parseSvgPathChecked(path);
|
|
202
|
+
try {
|
|
203
|
+
const error = parsed.ok ? null : { message: String(parsed.message) };
|
|
204
|
+
return new PathImpl(fromWasm(module.buildStrokeOutline(parsed.commands, s)), error);
|
|
205
|
+
}
|
|
206
|
+
finally {
|
|
207
|
+
parsed.commands.delete();
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
module,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// TypeScript bindings for emscripten-generated code. Automatically generated at compile time.
|
|
2
|
+
interface WasmModule {
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
type EmbindString = ArrayBuffer|Uint8Array|Uint8ClampedArray|Int8Array|string;
|
|
6
|
+
export interface ClassHandle {
|
|
7
|
+
isAliasOf(other: ClassHandle): boolean;
|
|
8
|
+
delete(): void;
|
|
9
|
+
deleteLater(): this;
|
|
10
|
+
isDeleted(): boolean;
|
|
11
|
+
// @ts-ignore - If targeting lower than ESNext, this symbol might not exist.
|
|
12
|
+
[Symbol.dispose](): void;
|
|
13
|
+
clone(): this;
|
|
14
|
+
}
|
|
15
|
+
export type Vec2 = {
|
|
16
|
+
x: number,
|
|
17
|
+
y: number
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export interface VerbValue<T extends number> {
|
|
21
|
+
value: T;
|
|
22
|
+
}
|
|
23
|
+
export type Verb = VerbValue<0>|VerbValue<1>|VerbValue<2>|VerbValue<3>|VerbValue<4>;
|
|
24
|
+
|
|
25
|
+
export interface LineJoinValue<T extends number> {
|
|
26
|
+
value: T;
|
|
27
|
+
}
|
|
28
|
+
export type LineJoin = LineJoinValue<0>|LineJoinValue<1>|LineJoinValue<2>;
|
|
29
|
+
|
|
30
|
+
export interface LineCapValue<T extends number> {
|
|
31
|
+
value: T;
|
|
32
|
+
}
|
|
33
|
+
export type LineCap = LineCapValue<0>|LineCapValue<1>|LineCapValue<2>;
|
|
34
|
+
|
|
35
|
+
export interface StrokeAlignValue<T extends number> {
|
|
36
|
+
value: T;
|
|
37
|
+
}
|
|
38
|
+
export type StrokeAlign = StrokeAlignValue<0>|StrokeAlignValue<1>|StrokeAlignValue<2>;
|
|
39
|
+
|
|
40
|
+
export interface FillRuleValue<T extends number> {
|
|
41
|
+
value: T;
|
|
42
|
+
}
|
|
43
|
+
export type FillRule = FillRuleValue<0>|FillRuleValue<1>;
|
|
44
|
+
|
|
45
|
+
export type PathCommand = {
|
|
46
|
+
verb: Verb,
|
|
47
|
+
ctrl1: Vec2,
|
|
48
|
+
ctrl2: Vec2,
|
|
49
|
+
to: Vec2
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export type StrokeSettings = {
|
|
53
|
+
lineWidth: number,
|
|
54
|
+
lineJoin: LineJoin,
|
|
55
|
+
lineCap: LineCap,
|
|
56
|
+
miterLimit: number,
|
|
57
|
+
align: StrokeAlign,
|
|
58
|
+
fillRule: FillRule,
|
|
59
|
+
clipToShape: boolean,
|
|
60
|
+
dashPattern: any,
|
|
61
|
+
dashOffset: number
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export interface PathCommandVector extends ClassHandle, Iterable<PathCommand> {
|
|
65
|
+
push_back(_0: PathCommand): void;
|
|
66
|
+
resize(_0: number, _1: PathCommand): void;
|
|
67
|
+
size(): number;
|
|
68
|
+
get(_0: number): PathCommand | undefined;
|
|
69
|
+
set(_0: number, _1: PathCommand): boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export type SvgParse = {
|
|
73
|
+
commands: PathCommandVector,
|
|
74
|
+
ok: boolean,
|
|
75
|
+
message: EmbindString
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
interface EmbindModule {
|
|
79
|
+
Verb: {MoveTo: VerbValue<0>, LineTo: VerbValue<1>, QuadraticTo: VerbValue<2>, CubicTo: VerbValue<3>, Close: VerbValue<4>};
|
|
80
|
+
LineJoin: {Miter: LineJoinValue<0>, Bevel: LineJoinValue<1>, Round: LineJoinValue<2>};
|
|
81
|
+
LineCap: {Butt: LineCapValue<0>, Square: LineCapValue<1>, Round: LineCapValue<2>};
|
|
82
|
+
StrokeAlign: {Center: StrokeAlignValue<0>, Inside: StrokeAlignValue<1>, Outside: StrokeAlignValue<2>};
|
|
83
|
+
FillRule: {NonZero: FillRuleValue<0>, EvenOdd: FillRuleValue<1>};
|
|
84
|
+
PathCommandVector: {
|
|
85
|
+
new(): PathCommandVector;
|
|
86
|
+
};
|
|
87
|
+
buildStrokeOutline(_0: PathCommandVector, _1: StrokeSettings): PathCommandVector;
|
|
88
|
+
defaultStrokeSettings(): StrokeSettings;
|
|
89
|
+
parseSvgPath(_0: EmbindString): PathCommandVector;
|
|
90
|
+
parseSvgPathChecked(_0: EmbindString): SvgParse;
|
|
91
|
+
pathToSvg(_0: PathCommandVector, _1: number): string;
|
|
92
|
+
arcToCommands(_0: number, _1: number, _2: number, _3: number, _4: number, _5: boolean, _6: boolean, _7: number, _8: number): PathCommandVector;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export type MainModule = WasmModule & EmbindModule;
|
|
96
|
+
export default function MainModuleFactory (options?: unknown): Promise<MainModule>;
|