foster-ts-shapes 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 +242 -0
- package/dist/canonicalize.d.ts +2 -0
- package/dist/canonicalize.js +125 -0
- package/dist/fmt.d.ts +1 -0
- package/dist/fmt.js +3 -0
- package/dist/generate.d.ts +2 -0
- package/dist/generate.js +140 -0
- package/dist/id.d.ts +1 -0
- package/dist/id.js +4 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/mask.d.ts +2 -0
- package/dist/mask.js +142 -0
- package/dist/prng.d.ts +22 -0
- package/dist/prng.js +36 -0
- package/dist/shapes/blob.d.ts +5 -0
- package/dist/shapes/blob.js +35 -0
- package/dist/shapes/circle.d.ts +5 -0
- package/dist/shapes/circle.js +23 -0
- package/dist/shapes/octagon.d.ts +5 -0
- package/dist/shapes/octagon.js +24 -0
- package/dist/shapes/oval.d.ts +5 -0
- package/dist/shapes/oval.js +31 -0
- package/dist/shapes/polygon.d.ts +5 -0
- package/dist/shapes/polygon.js +23 -0
- package/dist/shapes/rectangle.d.ts +5 -0
- package/dist/shapes/rectangle.js +34 -0
- package/dist/shapes/square.d.ts +5 -0
- package/dist/shapes/square.js +34 -0
- package/dist/shapes/trapezoid.d.ts +5 -0
- package/dist/shapes/trapezoid.js +29 -0
- package/dist/shapes/triangle.d.ts +5 -0
- package/dist/shapes/triangle.js +28 -0
- package/dist/styling.d.ts +3 -0
- package/dist/styling.js +65 -0
- package/dist/svg.d.ts +2 -0
- package/dist/svg.js +22 -0
- package/dist/types.d.ts +121 -0
- package/dist/types.js +1 -0
- package/dist/validate.d.ts +2 -0
- package/dist/validate.js +266 -0
- package/dist/variation.d.ts +11 -0
- package/dist/variation.js +244 -0
- package/package.json +41 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 VisualFinesse
|
|
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
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# foster-ts-shapes
|
|
2
|
+
|
|
3
|
+
> Deterministic SVG shape generation — zero dependencies, fully typed, endlessly composable.
|
|
4
|
+
|
|
5
|
+
Drop in a seed and a shape list, get back a pixel-perfect SVG every time. No canvas, no DOM, no runtime deps — just TypeScript and math.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install foster-ts-shapes
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { generate } from "foster-ts-shapes";
|
|
21
|
+
|
|
22
|
+
const result = generate({
|
|
23
|
+
seed: 42,
|
|
24
|
+
canvas: { width: 400, height: 400 },
|
|
25
|
+
shapes: [{ type: "circle", x: 200, y: 200, size: 100, fill: "steelblue" }],
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
console.log(result.svg); // <svg ...>...</svg>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Same seed → same SVG. Every time. On any machine.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Examples
|
|
36
|
+
|
|
37
|
+
 
|
|
38
|
+
|
|
39
|
+
> **Note:** These animations are external visualizations created to showcase the PRNG-driven variation system — cycling through different seeds to demonstrate deterministic shape generation. `foster-ts-shapes` outputs static SVG strings only. It does not animate shapes or produce GIFs.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Shapes
|
|
44
|
+
|
|
45
|
+
Nine primitives, all sharing a common base interface:
|
|
46
|
+
|
|
47
|
+
| Type | Required fields |
|
|
48
|
+
| ----------- | ------------------------------------- |
|
|
49
|
+
| `square` | `x, y, size` |
|
|
50
|
+
| `rectangle` | `x, y, width, height` |
|
|
51
|
+
| `circle` | `x, y, size` |
|
|
52
|
+
| `triangle` | `x, y, size` |
|
|
53
|
+
| `trapezoid` | `x, y, topWidth, bottomWidth, height` |
|
|
54
|
+
| `octagon` | `x, y, size` |
|
|
55
|
+
| `polygon` | `x, y, sides, size` |
|
|
56
|
+
| `oval` | `x, y, width, height` |
|
|
57
|
+
| `blob` | `x, y, size, points?` |
|
|
58
|
+
|
|
59
|
+
Every shape also accepts `rotation?` (degrees).
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Features
|
|
64
|
+
|
|
65
|
+
### Styling
|
|
66
|
+
|
|
67
|
+
Every shape accepts fill, stroke, and opacity fields:
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
{
|
|
71
|
+
type: "square",
|
|
72
|
+
x: 100, y: 100, size: 80,
|
|
73
|
+
fill: "tomato",
|
|
74
|
+
stroke: "darkred",
|
|
75
|
+
strokeWidth: 3,
|
|
76
|
+
opacity: 0.85,
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Gradients
|
|
81
|
+
|
|
82
|
+
Linear and radial gradients on fill and/or stroke:
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
{
|
|
86
|
+
type: "circle",
|
|
87
|
+
x: 150, y: 150, size: 100,
|
|
88
|
+
fillGradient: {
|
|
89
|
+
type: "linear",
|
|
90
|
+
x1: 0, y1: 0, x2: 1, y2: 1,
|
|
91
|
+
stops: [
|
|
92
|
+
{ offset: 0, color: "royalblue" },
|
|
93
|
+
{ offset: 1, color: "mediumpurple" },
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Bezier Rounding
|
|
100
|
+
|
|
101
|
+
Smooth out any shape's corners with a single field:
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
{
|
|
105
|
+
type: "polygon",
|
|
106
|
+
x: 200, y: 200, sides: 6, size: 100,
|
|
107
|
+
bezier: 0.4, // 0–1: rounding amount
|
|
108
|
+
bezierDirection: "in", // "out" (convex, default) | "in" (concave)
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Variation / Distortion
|
|
113
|
+
|
|
114
|
+
Seeded randomness that distorts vertices — great for organic, hand-drawn aesthetics:
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
{
|
|
118
|
+
type: "blob",
|
|
119
|
+
x: 150, y: 150, size: 100, points: 8,
|
|
120
|
+
fill: "goldenrod",
|
|
121
|
+
distort: 0.3, // 0–1: per-vertex displacement
|
|
122
|
+
sizeVariance: 0.2, // 0–1: overall scale jitter
|
|
123
|
+
clamp: { width: 200, height: 200 }, // keep shape within bounds
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Layers
|
|
128
|
+
|
|
129
|
+
Control z-order with `layer` — lower values render behind higher ones:
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
shapes: [
|
|
133
|
+
{
|
|
134
|
+
type: "circle",
|
|
135
|
+
x: 200,
|
|
136
|
+
y: 200,
|
|
137
|
+
size: 150,
|
|
138
|
+
fill: "cornflowerblue",
|
|
139
|
+
layer: 0,
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
type: "oval",
|
|
143
|
+
x: 200,
|
|
144
|
+
y: 200,
|
|
145
|
+
width: 80,
|
|
146
|
+
height: 40,
|
|
147
|
+
fill: "white",
|
|
148
|
+
layer: 1,
|
|
149
|
+
},
|
|
150
|
+
];
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Masks
|
|
154
|
+
|
|
155
|
+
Clip any shape using one or more mask shapes. Masks accept the full variation and bezier API:
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
// Circle clipped to a triangle window
|
|
159
|
+
{
|
|
160
|
+
type: "circle",
|
|
161
|
+
x: 150, y: 150, size: 100,
|
|
162
|
+
fill: "steelblue",
|
|
163
|
+
mask: { type: "triangle", x: 150, y: 150, size: 90 },
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Polygon revealed through two circular windows
|
|
167
|
+
{
|
|
168
|
+
type: "polygon",
|
|
169
|
+
x: 200, y: 150, sides: 8, size: 130,
|
|
170
|
+
fill: "mediumseagreen",
|
|
171
|
+
mask: [
|
|
172
|
+
{ type: "circle", x: 140, y: 130, size: 60 },
|
|
173
|
+
{ type: "circle", x: 260, y: 170, size: 60 },
|
|
174
|
+
],
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Donut: punch a hole in a shape by compositing black into the mask
|
|
178
|
+
{
|
|
179
|
+
type: "square",
|
|
180
|
+
x: 150, y: 150, size: 160,
|
|
181
|
+
fill: "steelblue",
|
|
182
|
+
mask: [
|
|
183
|
+
{ type: "square", x: 150, y: 150, size: 160, fill: "white" }, // reveal
|
|
184
|
+
{ type: "square", x: 150, y: 150, size: 80, fill: "black" }, // punch hole
|
|
185
|
+
],
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## API
|
|
192
|
+
|
|
193
|
+
### `generate(input: GeneratorInput): GeneratorOutput`
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
interface GeneratorInput {
|
|
197
|
+
seed: number; // any integer — determines all randomness
|
|
198
|
+
canvas: { width: number; height: number };
|
|
199
|
+
shapes: Shape[];
|
|
200
|
+
outputMode?: "semantic" | "path"; // default: "semantic"
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
interface GeneratorOutput {
|
|
204
|
+
svg: string;
|
|
205
|
+
metadata: { shapeCount: number };
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
`outputMode: "semantic"` emits native SVG primitives (`<rect>`, `<circle>`, etc.) where possible.
|
|
210
|
+
`outputMode: "path"` forces all shapes to `<path>` elements — useful when distortion or masking is applied.
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## Determinism
|
|
215
|
+
|
|
216
|
+
The seed drives a [mulberry32](https://gist.github.com/tommyettinger/46a874533244883189143505d203312c) PRNG. Given the same seed and input, `generate()` returns byte-for-byte identical SVG output across environments and versions. This makes the library suitable as a stable foundation for generative art pipelines, procedural asset systems, or any context where reproducibility matters.
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## TypeScript
|
|
221
|
+
|
|
222
|
+
Full strict-mode types are included. No `@types/` package needed.
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
import type { GeneratorInput, Shape, GeneratorOutput } from "foster-ts-shapes";
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
███████╗ ██████╗ ███████╗████████╗███████╗██████╗
|
|
231
|
+
██╔════╝██╔═══██╗██╔════╝╚══██╔══╝██╔════╝██╔══██╗
|
|
232
|
+
█████╗ ██║ ██║███████╗ ██║ █████╗ ██████╔╝
|
|
233
|
+
██╔══╝ ██║ ██║╚════██║ ██║ ██╔══╝ ██╔══██╗
|
|
234
|
+
██║ ╚██████╔╝███████║ ██║ ███████╗██║ ██║
|
|
235
|
+
╚═╝ ╚═════╝ ╚══════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## License
|
|
241
|
+
|
|
242
|
+
MIT © 2026 VisualFinesse
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
function canonicalizeCanvas(canvas) {
|
|
2
|
+
return {
|
|
3
|
+
width: canvas.width,
|
|
4
|
+
height: canvas.height,
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
// isMaskShape = true when canonicalizing a shape inside a <mask> element:
|
|
8
|
+
// mask and layer fields on nested shapes are silently dropped (no nesting).
|
|
9
|
+
function canonicalizeShape(shape, isMaskShape = false) {
|
|
10
|
+
// Canonicalize mask shapes (only for top-level shapes; mask nesting not supported)
|
|
11
|
+
const canonicalMask = [];
|
|
12
|
+
if (!isMaskShape && shape.mask !== undefined) {
|
|
13
|
+
const rawArr = Array.isArray(shape.mask) ? shape.mask : [shape.mask];
|
|
14
|
+
for (const ms of rawArr) {
|
|
15
|
+
canonicalMask.push(canonicalizeShape(ms, true));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const base = {
|
|
19
|
+
x: shape.x,
|
|
20
|
+
y: shape.y,
|
|
21
|
+
...(shape.rotation !== undefined ? { rotation: shape.rotation } : {}),
|
|
22
|
+
...(shape.distort !== undefined ? { distort: shape.distort } : {}),
|
|
23
|
+
...(shape.sizeVariance !== undefined ? { sizeVariance: shape.sizeVariance } : {}),
|
|
24
|
+
...(shape.clamp !== undefined ? { clamp: shape.clamp } : {}),
|
|
25
|
+
...(shape.fill !== undefined ? { fill: shape.fill } : {}),
|
|
26
|
+
...(shape.stroke !== undefined ? { stroke: shape.stroke } : {}),
|
|
27
|
+
...(shape.strokeWidth !== undefined ? { strokeWidth: shape.strokeWidth } : {}),
|
|
28
|
+
...(shape.opacity !== undefined ? { opacity: shape.opacity } : {}),
|
|
29
|
+
...(shape.fillGradient !== undefined ? { fillGradient: shape.fillGradient } : {}),
|
|
30
|
+
...(shape.strokeGradient !== undefined ? { strokeGradient: shape.strokeGradient } : {}),
|
|
31
|
+
...(shape.bezier !== undefined ? { bezier: shape.bezier } : {}),
|
|
32
|
+
...(shape.bezierDirection !== undefined ? { bezierDirection: shape.bezierDirection } : {}),
|
|
33
|
+
...(!isMaskShape && shape.layer !== undefined ? { layer: shape.layer } : {}),
|
|
34
|
+
...(canonicalMask.length > 0 ? { mask: canonicalMask } : {}),
|
|
35
|
+
};
|
|
36
|
+
switch (shape.type) {
|
|
37
|
+
case "square": {
|
|
38
|
+
const s = shape;
|
|
39
|
+
const result = { type: "square", ...base, size: s.size };
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
case "rectangle": {
|
|
43
|
+
const s = shape;
|
|
44
|
+
const result = {
|
|
45
|
+
type: "rectangle",
|
|
46
|
+
...base,
|
|
47
|
+
width: s.width,
|
|
48
|
+
height: s.height,
|
|
49
|
+
};
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
case "circle": {
|
|
53
|
+
const s = shape;
|
|
54
|
+
const result = { type: "circle", ...base, size: s.size };
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
case "triangle": {
|
|
58
|
+
const s = shape;
|
|
59
|
+
const result = {
|
|
60
|
+
type: "triangle",
|
|
61
|
+
...base,
|
|
62
|
+
size: s.size,
|
|
63
|
+
};
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
case "trapezoid": {
|
|
67
|
+
const s = shape;
|
|
68
|
+
const result = {
|
|
69
|
+
type: "trapezoid",
|
|
70
|
+
...base,
|
|
71
|
+
topWidth: s.topWidth,
|
|
72
|
+
bottomWidth: s.bottomWidth,
|
|
73
|
+
height: s.height,
|
|
74
|
+
};
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
case "octagon": {
|
|
78
|
+
const s = shape;
|
|
79
|
+
const result = { type: "octagon", ...base, size: s.size };
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
case "polygon": {
|
|
83
|
+
const s = shape;
|
|
84
|
+
const result = {
|
|
85
|
+
type: "polygon",
|
|
86
|
+
...base,
|
|
87
|
+
sides: s.sides,
|
|
88
|
+
size: s.size,
|
|
89
|
+
};
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
case "oval": {
|
|
93
|
+
const s = shape;
|
|
94
|
+
const result = {
|
|
95
|
+
type: "oval",
|
|
96
|
+
...base,
|
|
97
|
+
width: s.width,
|
|
98
|
+
height: s.height,
|
|
99
|
+
};
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
case "blob": {
|
|
103
|
+
const s = shape;
|
|
104
|
+
const result = {
|
|
105
|
+
type: "blob",
|
|
106
|
+
...base,
|
|
107
|
+
size: s.size,
|
|
108
|
+
...(s.points !== undefined ? { points: s.points } : {}),
|
|
109
|
+
};
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
default:
|
|
113
|
+
// Unknown type — pass through with only base fields; validate() will catch it
|
|
114
|
+
return { type: shape.type, ...base };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
export function canonicalize(input) {
|
|
118
|
+
const result = {
|
|
119
|
+
seed: input.seed,
|
|
120
|
+
canvas: canonicalizeCanvas(input.canvas),
|
|
121
|
+
shapes: input.shapes.map((s) => canonicalizeShape(s)),
|
|
122
|
+
...(input.outputMode !== undefined ? { outputMode: input.outputMode } : {}),
|
|
123
|
+
};
|
|
124
|
+
return result;
|
|
125
|
+
}
|
package/dist/fmt.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function fmt(n: number): string;
|
package/dist/fmt.js
ADDED
package/dist/generate.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { shapeId } from "./id.js";
|
|
2
|
+
import { assembleSvg } from "./svg.js";
|
|
3
|
+
import { renderSquare } from "./shapes/square.js";
|
|
4
|
+
import { renderRectangle } from "./shapes/rectangle.js";
|
|
5
|
+
import { renderCircle } from "./shapes/circle.js";
|
|
6
|
+
import { renderTriangle } from "./shapes/triangle.js";
|
|
7
|
+
import { renderTrapezoid } from "./shapes/trapezoid.js";
|
|
8
|
+
import { renderOctagon } from "./shapes/octagon.js";
|
|
9
|
+
import { renderPolygon } from "./shapes/polygon.js";
|
|
10
|
+
import { renderOval } from "./shapes/oval.js";
|
|
11
|
+
import { renderBlob } from "./shapes/blob.js";
|
|
12
|
+
import { canonicalize } from "./canonicalize.js";
|
|
13
|
+
import { validate } from "./validate.js";
|
|
14
|
+
import { createPrng, varSeed } from "./prng.js";
|
|
15
|
+
import { applySizeVariance, renderVaried, extractVertices, verticesToPath } from "./variation.js";
|
|
16
|
+
import { buildGradientDef, buildStylingAttrs } from "./styling.js";
|
|
17
|
+
import { buildMaskDef } from "./mask.js";
|
|
18
|
+
export function generate(input) {
|
|
19
|
+
const canonical = canonicalize(input);
|
|
20
|
+
validate(canonical);
|
|
21
|
+
const mode = canonical.outputMode ?? "semantic";
|
|
22
|
+
const defs = [];
|
|
23
|
+
// Stage 4: stable sort by layer (lower = rendered first = further back)
|
|
24
|
+
// Capture original index before sort so shapeId / varSeed remain deterministic
|
|
25
|
+
const indexed = canonical.shapes.map((shape, i) => ({ shape, i }));
|
|
26
|
+
indexed.sort((a, b) => (a.shape.layer ?? 0) - (b.shape.layer ?? 0));
|
|
27
|
+
const elements = indexed.map(({ shape, i }) => {
|
|
28
|
+
const sv = shape.sizeVariance ?? 0;
|
|
29
|
+
const dt = shape.distort ?? 0;
|
|
30
|
+
const bz = shape.bezier ?? 0;
|
|
31
|
+
// Create variation PRNG only when needed for sizeVariance or distort
|
|
32
|
+
const varPrng = (sv > 0 || dt > 0) ? createPrng(varSeed(canonical.seed, i)) : null;
|
|
33
|
+
// 1. Apply sizeVariance (scale size dims, consumes one prng draw)
|
|
34
|
+
const workShape = (sv > 0 && varPrng) ? applySizeVariance(shape, varPrng) : shape;
|
|
35
|
+
let tag;
|
|
36
|
+
let attrs;
|
|
37
|
+
if (dt > 0 || bz > 0) {
|
|
38
|
+
// Path pipeline: distort and/or bezier corner rounding
|
|
39
|
+
if (dt > 0 && varPrng) {
|
|
40
|
+
// Distortion (with optional bezier rounding passed through)
|
|
41
|
+
const r = renderVaried(workShape, varPrng, canonical.seed, i, bz, workShape.bezierDirection ?? "out");
|
|
42
|
+
tag = r.tag;
|
|
43
|
+
attrs = r.attrs;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
// Bezier only — extract vertices and apply corner rounding
|
|
47
|
+
const verts = extractVertices(workShape, canonical.seed, i);
|
|
48
|
+
const d = verticesToPath(verts, bz, workShape.bezierDirection ?? "out");
|
|
49
|
+
tag = "path";
|
|
50
|
+
attrs = { d };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
// Normal render pipeline
|
|
55
|
+
switch (workShape.type) {
|
|
56
|
+
case "square": {
|
|
57
|
+
const r = renderSquare(workShape, mode);
|
|
58
|
+
tag = r.tag;
|
|
59
|
+
attrs = r.attrs;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
case "rectangle": {
|
|
63
|
+
const r = renderRectangle(workShape, mode);
|
|
64
|
+
tag = r.tag;
|
|
65
|
+
attrs = r.attrs;
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
case "circle": {
|
|
69
|
+
const r = renderCircle(workShape, mode);
|
|
70
|
+
tag = r.tag;
|
|
71
|
+
attrs = r.attrs;
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
case "triangle": {
|
|
75
|
+
const r = renderTriangle(workShape, mode);
|
|
76
|
+
tag = r.tag;
|
|
77
|
+
attrs = r.attrs;
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
case "trapezoid": {
|
|
81
|
+
const r = renderTrapezoid(workShape, mode);
|
|
82
|
+
tag = r.tag;
|
|
83
|
+
attrs = r.attrs;
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
case "octagon": {
|
|
87
|
+
const r = renderOctagon(workShape, mode);
|
|
88
|
+
tag = r.tag;
|
|
89
|
+
attrs = r.attrs;
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
case "polygon": {
|
|
93
|
+
const r = renderPolygon(workShape, mode);
|
|
94
|
+
tag = r.tag;
|
|
95
|
+
attrs = r.attrs;
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
case "oval": {
|
|
99
|
+
const r = renderOval(workShape, mode);
|
|
100
|
+
tag = r.tag;
|
|
101
|
+
attrs = r.attrs;
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
case "blob": {
|
|
105
|
+
const r = renderBlob(workShape, mode, canonical.seed, i);
|
|
106
|
+
tag = r.tag;
|
|
107
|
+
attrs = r.attrs;
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
default: throw new Error(`Unsupported shape type: ${workShape.type}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Compute shape ID (needed for gradient IDs)
|
|
114
|
+
const id = shapeId(canonical.seed, shape.type, i);
|
|
115
|
+
// Gradient defs accumulation
|
|
116
|
+
let fillGradId;
|
|
117
|
+
if (shape.fillGradient) {
|
|
118
|
+
fillGradId = `grad-${id}-fill`;
|
|
119
|
+
defs.push(buildGradientDef(fillGradId, shape.fillGradient));
|
|
120
|
+
}
|
|
121
|
+
let strokeGradId;
|
|
122
|
+
if (shape.strokeGradient) {
|
|
123
|
+
strokeGradId = `grad-${id}-stroke`;
|
|
124
|
+
defs.push(buildGradientDef(strokeGradId, shape.strokeGradient));
|
|
125
|
+
}
|
|
126
|
+
// Apply all styling attrs (fill, stroke, stroke-width, opacity)
|
|
127
|
+
Object.assign(attrs, buildStylingAttrs(shape, fillGradId, strokeGradId));
|
|
128
|
+
// Stage 4: mask processing — build <mask> def and reference it on the shape
|
|
129
|
+
if (shape.mask !== undefined) {
|
|
130
|
+
const maskShapes = Array.isArray(shape.mask) ? shape.mask : [shape.mask];
|
|
131
|
+
if (maskShapes.length > 0) {
|
|
132
|
+
const maskId = `mask-${id}`;
|
|
133
|
+
defs.push(buildMaskDef(maskId, maskShapes, mode, canonical.seed, i, defs));
|
|
134
|
+
attrs["mask"] = `url(#${maskId})`;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return { tag, attrs, id, rotation: shape.rotation, cx: shape.x, cy: shape.y };
|
|
138
|
+
});
|
|
139
|
+
return { svg: assembleSvg(canonical.canvas, elements, defs), metadata: { shapeCount: canonical.shapes.length } };
|
|
140
|
+
}
|
package/dist/id.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function shapeId(seed: number, type: string, index: number): string;
|
package/dist/id.js
ADDED
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { generate } from "./generate.js";
|
|
2
|
+
export type { GeneratorInput, Canvas, Shape, ShapeBase, SquareShape, RectangleShape, CircleShape, TriangleShape, TrapezoidShape, OctagonShape, PolygonShape, OvalShape, BlobShape, MaskShape, VariationFields, StylingFields, BezierFields, LayerFields, MaskFields, Gradient, LinearGradient, RadialGradient, ColorStop, GeneratorOutput, } from "./types.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { generate } from "./generate.js";
|
package/dist/mask.d.ts
ADDED