svelte-p5 0.2.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 +101 -0
- package/dist/P5Canvas.svelte +45 -0
- package/dist/P5Canvas.svelte.d.ts +4 -0
- package/dist/createP5Bridge.svelte.d.ts +34 -0
- package/dist/createP5Bridge.svelte.js +34 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +6 -0
- package/dist/types.d.ts +21 -0
- package/dist/types.js +1 -0
- package/dist/utils/colorCache.d.ts +26 -0
- package/dist/utils/colorCache.js +25 -0
- package/dist/utils/disableFES.d.ts +14 -0
- package/dist/utils/disableFES.js +16 -0
- package/dist/utils/fontAtlas.d.ts +30 -0
- package/dist/utils/fontAtlas.js +53 -0
- package/dist/utils/hitTest.d.ts +10 -0
- package/dist/utils/hitTest.js +14 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.js +4 -0
- package/package.json +69 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Edwin Zhao
|
|
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,101 @@
|
|
|
1
|
+
# svelte-p5
|
|
2
|
+
|
|
3
|
+
Svelte 5 bindings for p5.js. This package is the primitive layer: a canvas wrapper, a state bridge, and four small performance helpers. If you want the opinionated higher-level pieces (auto-resize, FPS readout, draggable windows), install [`svelte-p5-components`](../components) as well.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add svelte-p5 p5
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## `<P5Canvas>`
|
|
12
|
+
|
|
13
|
+
Mounts a p5 sketch in instance mode, cleans it up on unmount.
|
|
14
|
+
|
|
15
|
+
```svelte
|
|
16
|
+
<script lang="ts">
|
|
17
|
+
import { P5Canvas } from 'svelte-p5';
|
|
18
|
+
import type p5 from 'p5';
|
|
19
|
+
|
|
20
|
+
const sketch = (p: p5) => {
|
|
21
|
+
p.setup = () => p.createCanvas(400, 400);
|
|
22
|
+
p.draw = () => {
|
|
23
|
+
p.background(240);
|
|
24
|
+
p.circle(p.mouseX, p.mouseY, 40);
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<P5Canvas {sketch} />
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The p5 instance is exposed as a bindable prop. Bind it when you need to call into the sketch from Svelte:
|
|
33
|
+
|
|
34
|
+
```svelte
|
|
35
|
+
<script lang="ts">
|
|
36
|
+
import { P5Canvas } from 'svelte-p5';
|
|
37
|
+
import type p5 from 'p5';
|
|
38
|
+
|
|
39
|
+
let instance = $state<p5 | null>(null);
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<P5Canvas {sketch} bind:instance />
|
|
43
|
+
<button onclick={() => instance?.redraw()}>Redraw</button>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`instance` is `null` before mount and after unmount, so guard with `?.`.
|
|
47
|
+
|
|
48
|
+
## `createP5Bridge`
|
|
49
|
+
|
|
50
|
+
A reactive object you can read and write from both sides. No subscriptions, no manual sync.
|
|
51
|
+
|
|
52
|
+
```svelte
|
|
53
|
+
<script lang="ts">
|
|
54
|
+
import { P5Canvas, createP5Bridge } from 'svelte-p5';
|
|
55
|
+
import type p5 from 'p5';
|
|
56
|
+
|
|
57
|
+
const bridge = createP5Bridge({ radius: 40, color: '#336699' });
|
|
58
|
+
|
|
59
|
+
const sketch = (p: p5) => {
|
|
60
|
+
p.setup = () => p.createCanvas(400, 400);
|
|
61
|
+
p.draw = () => {
|
|
62
|
+
p.background(240);
|
|
63
|
+
p.fill(bridge.state.color);
|
|
64
|
+
p.circle(p.mouseX, p.mouseY, bridge.state.radius);
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
</script>
|
|
68
|
+
|
|
69
|
+
<P5Canvas {sketch} />
|
|
70
|
+
<input type="range" min="10" max="200" bind:value={bridge.state.radius} />
|
|
71
|
+
<input type="color" bind:value={bridge.state.color} />
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`bridge.state` is a `$state` proxy. Mutations on either side take effect on the next frame.
|
|
75
|
+
|
|
76
|
+
For one or two fields you could just as well close over a plain `$state` variable. Reach for `createP5Bridge` when the state becomes a small struct you want to pass around as a single object.
|
|
77
|
+
|
|
78
|
+
## Performance utilities
|
|
79
|
+
|
|
80
|
+
Exported from the main entry (and from `./utils` for explicit imports):
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import { disableFES, createColorCache, createFontAtlas, hitTest } from 'svelte-p5';
|
|
84
|
+
|
|
85
|
+
disableFES(); // call once, before creating any p5 instance
|
|
86
|
+
hitTest.rect(mouseX, mouseY, x, y, w, h);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
What each does and when to reach for it lives in the [performance recipe](../../docs/recipes/performance.md).
|
|
90
|
+
|
|
91
|
+
## Type exports
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
import type { SketchFn, P5CanvasProps, P5Bridge } from 'svelte-p5';
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`SketchFn` is `(p: p5) => void`. `SketchFn`, not `Sketch`, because the components package exports a `<Sketch>` component and the collision would force aliased imports.
|
|
98
|
+
|
|
99
|
+
## License
|
|
100
|
+
|
|
101
|
+
MIT
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount } from 'svelte';
|
|
3
|
+
import type p5Type from 'p5';
|
|
4
|
+
import type { P5CanvasProps } from './types.js';
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
sketch,
|
|
8
|
+
instance = $bindable(null),
|
|
9
|
+
class: className = '',
|
|
10
|
+
style = 'display: block; width: 100%; height: 100%;',
|
|
11
|
+
onReady
|
|
12
|
+
}: P5CanvasProps = $props();
|
|
13
|
+
|
|
14
|
+
let container: HTMLDivElement | null = null;
|
|
15
|
+
|
|
16
|
+
onMount(() => {
|
|
17
|
+
// Track the local instance so cleanup works even if `instance` is reassigned
|
|
18
|
+
// by the parent binding before the async import resolves.
|
|
19
|
+
let local: p5Type | null = null;
|
|
20
|
+
let cancelled = false;
|
|
21
|
+
|
|
22
|
+
(async () => {
|
|
23
|
+
const mod = await import('p5');
|
|
24
|
+
if (cancelled || !container) return;
|
|
25
|
+
|
|
26
|
+
const p5Ctor = mod.default;
|
|
27
|
+
local = new p5Ctor((p: p5Type) => sketch(p), container);
|
|
28
|
+
instance = local;
|
|
29
|
+
onReady?.(local);
|
|
30
|
+
})();
|
|
31
|
+
|
|
32
|
+
return () => {
|
|
33
|
+
cancelled = true;
|
|
34
|
+
try {
|
|
35
|
+
local?.remove();
|
|
36
|
+
} catch {
|
|
37
|
+
// p5.remove() can throw if the DOM is already gone during HMR; ignore.
|
|
38
|
+
}
|
|
39
|
+
local = null;
|
|
40
|
+
instance = null;
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<div bind:this={container} class={className} {style}></div>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridge reactive Svelte state into a p5 sketch closure.
|
|
3
|
+
*
|
|
4
|
+
* Mutations from either side are seen by the other. Because p5's draw loop
|
|
5
|
+
* reads fields on every frame, you don't need effects or subscriptions —
|
|
6
|
+
* just read `bridge.state.foo` inside your sketch and write to it from
|
|
7
|
+
* Svelte UI.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```svelte
|
|
11
|
+
* <script lang="ts">
|
|
12
|
+
* import { P5Canvas, createP5Bridge } from 'svelte-p5';
|
|
13
|
+
* import type p5 from 'p5';
|
|
14
|
+
*
|
|
15
|
+
* const bridge = createP5Bridge({ radius: 40, color: '#336699' });
|
|
16
|
+
*
|
|
17
|
+
* const sketch = (p: p5) => {
|
|
18
|
+
* p.setup = () => p.createCanvas(400, 400);
|
|
19
|
+
* p.draw = () => {
|
|
20
|
+
* p.background(240);
|
|
21
|
+
* p.fill(bridge.state.color);
|
|
22
|
+
* p.circle(p.mouseX, p.mouseY, bridge.state.radius);
|
|
23
|
+
* };
|
|
24
|
+
* };
|
|
25
|
+
* </script>
|
|
26
|
+
*
|
|
27
|
+
* <P5Canvas {sketch} />
|
|
28
|
+
* <input type="range" bind:value={bridge.state.radius} min="10" max="200" />
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare function createP5Bridge<T extends object>(initial: T): {
|
|
32
|
+
state: T;
|
|
33
|
+
};
|
|
34
|
+
export type P5Bridge<T extends object> = ReturnType<typeof createP5Bridge<T>>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridge reactive Svelte state into a p5 sketch closure.
|
|
3
|
+
*
|
|
4
|
+
* Mutations from either side are seen by the other. Because p5's draw loop
|
|
5
|
+
* reads fields on every frame, you don't need effects or subscriptions —
|
|
6
|
+
* just read `bridge.state.foo` inside your sketch and write to it from
|
|
7
|
+
* Svelte UI.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```svelte
|
|
11
|
+
* <script lang="ts">
|
|
12
|
+
* import { P5Canvas, createP5Bridge } from 'svelte-p5';
|
|
13
|
+
* import type p5 from 'p5';
|
|
14
|
+
*
|
|
15
|
+
* const bridge = createP5Bridge({ radius: 40, color: '#336699' });
|
|
16
|
+
*
|
|
17
|
+
* const sketch = (p: p5) => {
|
|
18
|
+
* p.setup = () => p.createCanvas(400, 400);
|
|
19
|
+
* p.draw = () => {
|
|
20
|
+
* p.background(240);
|
|
21
|
+
* p.fill(bridge.state.color);
|
|
22
|
+
* p.circle(p.mouseX, p.mouseY, bridge.state.radius);
|
|
23
|
+
* };
|
|
24
|
+
* };
|
|
25
|
+
* </script>
|
|
26
|
+
*
|
|
27
|
+
* <P5Canvas {sketch} />
|
|
28
|
+
* <input type="range" bind:value={bridge.state.radius} min="10" max="200" />
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export function createP5Bridge(initial) {
|
|
32
|
+
const state = $state(initial);
|
|
33
|
+
return { state };
|
|
34
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { default as P5Canvas } from './P5Canvas.svelte';
|
|
2
|
+
export { createP5Bridge } from './createP5Bridge.svelte.js';
|
|
3
|
+
// Re-export utils from the main entry for convenience — users doing
|
|
4
|
+
// `import { disableFES } from 'svelte-p5'` get the same
|
|
5
|
+
// helpers they'd get from `.../utils`. Tree-shakeable via `sideEffects: false`.
|
|
6
|
+
export * from './utils/index.js';
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type p5 from 'p5';
|
|
2
|
+
/**
|
|
3
|
+
* A p5 sketch function in instance mode. Called with the p5 instance;
|
|
4
|
+
* the function should assign `p.setup`, `p.draw`, etc. on it.
|
|
5
|
+
*/
|
|
6
|
+
export type SketchFn = (p: p5) => void;
|
|
7
|
+
/**
|
|
8
|
+
* Props accepted by the `<P5Canvas>` component.
|
|
9
|
+
*/
|
|
10
|
+
export interface P5CanvasProps {
|
|
11
|
+
/** Your sketch function — assigns p.setup, p.draw, etc. */
|
|
12
|
+
sketch: SketchFn;
|
|
13
|
+
/** Bindable: the p5 instance, available after mount. `null` before mount or after unmount. */
|
|
14
|
+
instance?: p5 | null;
|
|
15
|
+
/** Optional class applied to the container div. */
|
|
16
|
+
class?: string;
|
|
17
|
+
/** Optional inline style on the container div. Defaults to `display: block; width: 100%; height: 100%;`. */
|
|
18
|
+
style?: string;
|
|
19
|
+
/** Called once, synchronously, when the p5 instance has been created and the sketch function has returned. */
|
|
20
|
+
onReady?: (instance: p5) => void;
|
|
21
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lazy cache for CSS color strings.
|
|
3
|
+
*
|
|
4
|
+
* p5.color() allocates a p5.Color instance on every call (object + typed
|
|
5
|
+
* arrays + regex parse). Calling it in a draw loop — e.g. to colorize per
|
|
6
|
+
* speaker or per data-point — leaks a lot of garbage. Use a ColorCache to
|
|
7
|
+
* compute each color once and reuse the CSS string.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* const colors = createColorCache<string>();
|
|
12
|
+
* const forSpeaker = (id: string, hue: number) =>
|
|
13
|
+
* colors.get(id, () => `hsl(${hue}, 80%, 60%)`);
|
|
14
|
+
*
|
|
15
|
+
* // in sketch:
|
|
16
|
+
* p.fill(forSpeaker(point.speaker, point.hue));
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export interface ColorCache<K = string> {
|
|
20
|
+
get(key: K, compute: () => string): string;
|
|
21
|
+
set(key: K, value: string): void;
|
|
22
|
+
has(key: K): boolean;
|
|
23
|
+
clear(): void;
|
|
24
|
+
readonly size: number;
|
|
25
|
+
}
|
|
26
|
+
export declare function createColorCache<K = string>(): ColorCache<K>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export function createColorCache() {
|
|
2
|
+
const cache = new Map();
|
|
3
|
+
return {
|
|
4
|
+
get(key, compute) {
|
|
5
|
+
let v = cache.get(key);
|
|
6
|
+
if (v === undefined) {
|
|
7
|
+
v = compute();
|
|
8
|
+
cache.set(key, v);
|
|
9
|
+
}
|
|
10
|
+
return v;
|
|
11
|
+
},
|
|
12
|
+
set(key, value) {
|
|
13
|
+
cache.set(key, value);
|
|
14
|
+
},
|
|
15
|
+
has(key) {
|
|
16
|
+
return cache.has(key);
|
|
17
|
+
},
|
|
18
|
+
clear() {
|
|
19
|
+
cache.clear();
|
|
20
|
+
},
|
|
21
|
+
get size() {
|
|
22
|
+
return cache.size;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Disable p5's Friendly Error System (FES) for production.
|
|
3
|
+
*
|
|
4
|
+
* FES validates arguments on every p5 API call (map, fill, stroke, text, etc.)
|
|
5
|
+
* via deep parameter checking + Levenshtein-distance typo detection. For
|
|
6
|
+
* sketches that make thousands of calls per frame, this adds 1-5% CPU overhead.
|
|
7
|
+
*
|
|
8
|
+
* Call this once, before instantiating any p5 sketch. The flag is read by
|
|
9
|
+
* p5's FES bootstrap as a bare global (`typeof IS_MINIFIED`), which in a
|
|
10
|
+
* browser means any property on `globalThis`/`window`.
|
|
11
|
+
*
|
|
12
|
+
* @see https://github.com/processing/p5.js/blob/main/src/core/friendly_errors/fes_core.js
|
|
13
|
+
*/
|
|
14
|
+
export declare function disableFES(): void;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Disable p5's Friendly Error System (FES) for production.
|
|
3
|
+
*
|
|
4
|
+
* FES validates arguments on every p5 API call (map, fill, stroke, text, etc.)
|
|
5
|
+
* via deep parameter checking + Levenshtein-distance typo detection. For
|
|
6
|
+
* sketches that make thousands of calls per frame, this adds 1-5% CPU overhead.
|
|
7
|
+
*
|
|
8
|
+
* Call this once, before instantiating any p5 sketch. The flag is read by
|
|
9
|
+
* p5's FES bootstrap as a bare global (`typeof IS_MINIFIED`), which in a
|
|
10
|
+
* browser means any property on `globalThis`/`window`.
|
|
11
|
+
*
|
|
12
|
+
* @see https://github.com/processing/p5.js/blob/main/src/core/friendly_errors/fes_core.js
|
|
13
|
+
*/
|
|
14
|
+
export function disableFES() {
|
|
15
|
+
globalThis.IS_MINIFIED = true;
|
|
16
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type p5 from 'p5';
|
|
2
|
+
export interface FontAtlasOptions {
|
|
3
|
+
/** Font size in px. Default: 14 */
|
|
4
|
+
fontSize?: number;
|
|
5
|
+
/** Fill color (any CSS color string). Default: black */
|
|
6
|
+
fill?: string;
|
|
7
|
+
/** Padding around each glyph cell. Default: 4 */
|
|
8
|
+
padding?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface FontAtlas {
|
|
11
|
+
/** The offscreen p5.Graphics buffer the strings are rendered into. */
|
|
12
|
+
readonly buffer: p5.Graphics;
|
|
13
|
+
/** Composite the pre-rendered string at (x, y). Returns false if the string wasn't in the atlas. */
|
|
14
|
+
draw(str: string, x: number, y: number): boolean;
|
|
15
|
+
/** Release the underlying buffer. Call in cleanup. */
|
|
16
|
+
dispose(): void;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Pre-render a fixed set of strings to an offscreen buffer **once**, then
|
|
20
|
+
* composite them each frame with `p5.image()`. This bypasses the OpenType.js
|
|
21
|
+
* path-rendering slow path that fires for every `p5.text()` call when you've
|
|
22
|
+
* `loadFont()`'d an OTF/TTF file — typically a 10-100× speedup for text-heavy
|
|
23
|
+
* sketches (word clouds, axis labels, small-multiples titles).
|
|
24
|
+
*
|
|
25
|
+
* Use for a fixed vocabulary (speaker names, category labels, axis ticks).
|
|
26
|
+
* Don't use for per-frame dynamic text — that defeats the point.
|
|
27
|
+
*
|
|
28
|
+
* @see https://github.com/processing/p5.js/blob/main/src/core/p5.Renderer2D.js
|
|
29
|
+
*/
|
|
30
|
+
export declare function createFontAtlas(sk: p5, strings: readonly string[], options?: FontAtlasOptions): FontAtlas;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-render a fixed set of strings to an offscreen buffer **once**, then
|
|
3
|
+
* composite them each frame with `p5.image()`. This bypasses the OpenType.js
|
|
4
|
+
* path-rendering slow path that fires for every `p5.text()` call when you've
|
|
5
|
+
* `loadFont()`'d an OTF/TTF file — typically a 10-100× speedup for text-heavy
|
|
6
|
+
* sketches (word clouds, axis labels, small-multiples titles).
|
|
7
|
+
*
|
|
8
|
+
* Use for a fixed vocabulary (speaker names, category labels, axis ticks).
|
|
9
|
+
* Don't use for per-frame dynamic text — that defeats the point.
|
|
10
|
+
*
|
|
11
|
+
* @see https://github.com/processing/p5.js/blob/main/src/core/p5.Renderer2D.js
|
|
12
|
+
*/
|
|
13
|
+
export function createFontAtlas(sk, strings, options = {}) {
|
|
14
|
+
const { fontSize = 14, fill = '#000', padding = 4 } = options;
|
|
15
|
+
sk.textSize(fontSize);
|
|
16
|
+
const cellHeight = fontSize + padding * 2;
|
|
17
|
+
const widths = [];
|
|
18
|
+
let maxWidth = 0;
|
|
19
|
+
for (const s of strings) {
|
|
20
|
+
const w = sk.textWidth(s);
|
|
21
|
+
widths.push(w);
|
|
22
|
+
if (w > maxWidth)
|
|
23
|
+
maxWidth = w;
|
|
24
|
+
}
|
|
25
|
+
const atlasWidth = Math.max(1, Math.ceil(maxWidth + padding * 2));
|
|
26
|
+
const atlasHeight = Math.max(1, cellHeight * strings.length);
|
|
27
|
+
const buffer = sk.createGraphics(atlasWidth, atlasHeight);
|
|
28
|
+
buffer.clear();
|
|
29
|
+
buffer.textSize(fontSize);
|
|
30
|
+
buffer.fill(fill);
|
|
31
|
+
buffer.noStroke();
|
|
32
|
+
buffer.textAlign(sk.LEFT, sk.TOP);
|
|
33
|
+
const positions = new Map();
|
|
34
|
+
strings.forEach((s, i) => {
|
|
35
|
+
const y = i * cellHeight;
|
|
36
|
+
const w = (widths[i] ?? 0) + padding * 2;
|
|
37
|
+
buffer.text(s, padding, y + padding);
|
|
38
|
+
positions.set(s, { y, w, h: cellHeight });
|
|
39
|
+
});
|
|
40
|
+
return {
|
|
41
|
+
buffer,
|
|
42
|
+
draw(str, x, y) {
|
|
43
|
+
const cell = positions.get(str);
|
|
44
|
+
if (!cell)
|
|
45
|
+
return false;
|
|
46
|
+
sk.image(buffer, x, y, cell.w, cell.h, 0, cell.y, cell.w, cell.h);
|
|
47
|
+
return true;
|
|
48
|
+
},
|
|
49
|
+
dispose() {
|
|
50
|
+
buffer.remove();
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal hit-test helpers. These are tree-shakeable drop-ins for p5's
|
|
3
|
+
* built-in equivalents, with stricter types and no validation overhead.
|
|
4
|
+
*/
|
|
5
|
+
/** Is (px, py) inside the axis-aligned rectangle at (x, y, w, h)? */
|
|
6
|
+
export declare const rect: (px: number, py: number, x: number, y: number, w: number, h: number) => boolean;
|
|
7
|
+
/** Is (px, py) inside the circle centered at (cx, cy) with diameter d? */
|
|
8
|
+
export declare const circle: (px: number, py: number, cx: number, cy: number, d: number) => boolean;
|
|
9
|
+
/** Is (px, py) inside the axis-aligned ellipse at (cx, cy) with width w and height h? */
|
|
10
|
+
export declare const ellipse: (px: number, py: number, cx: number, cy: number, w: number, h: number) => boolean;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal hit-test helpers. These are tree-shakeable drop-ins for p5's
|
|
3
|
+
* built-in equivalents, with stricter types and no validation overhead.
|
|
4
|
+
*/
|
|
5
|
+
/** Is (px, py) inside the axis-aligned rectangle at (x, y, w, h)? */
|
|
6
|
+
export const rect = (px, py, x, y, w, h) => px >= x && px <= x + w && py >= y && py <= y + h;
|
|
7
|
+
/** Is (px, py) inside the circle centered at (cx, cy) with diameter d? */
|
|
8
|
+
export const circle = (px, py, cx, cy, d) => Math.hypot(px - cx, py - cy) < d / 2;
|
|
9
|
+
/** Is (px, py) inside the axis-aligned ellipse at (cx, cy) with width w and height h? */
|
|
10
|
+
export const ellipse = (px, py, cx, cy, w, h) => {
|
|
11
|
+
const dx = (px - cx) / (w / 2);
|
|
12
|
+
const dy = (py - cy) / (h / 2);
|
|
13
|
+
return dx * dx + dy * dy < 1;
|
|
14
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "svelte-p5",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Svelte 5 + p5.js integration primitives — <P5Canvas> wrapper, rune bridge, and performance utilities",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"svelte",
|
|
7
|
+
"svelte5",
|
|
8
|
+
"p5",
|
|
9
|
+
"p5.js",
|
|
10
|
+
"creative-coding",
|
|
11
|
+
"canvas",
|
|
12
|
+
"visualization",
|
|
13
|
+
"generative-art"
|
|
14
|
+
],
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/edwinzhao/svelte-p5.git",
|
|
18
|
+
"directory": "packages/core"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://github.com/edwinzhao/svelte-p5/tree/main/packages/core#readme",
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/edwinzhao/svelte-p5/issues"
|
|
23
|
+
},
|
|
24
|
+
"author": "Edwin Zhao",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"type": "module",
|
|
27
|
+
"svelte": "./dist/index.js",
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"main": "./dist/index.js",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"svelte": "./dist/index.js",
|
|
34
|
+
"default": "./dist/index.js"
|
|
35
|
+
},
|
|
36
|
+
"./utils": {
|
|
37
|
+
"types": "./dist/utils/index.d.ts",
|
|
38
|
+
"default": "./dist/utils/index.js"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"dist",
|
|
43
|
+
"!dist/**/*.test.*",
|
|
44
|
+
"README.md",
|
|
45
|
+
"LICENSE"
|
|
46
|
+
],
|
|
47
|
+
"sideEffects": false,
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build": "svelte-package -o dist && publint",
|
|
50
|
+
"typecheck": "svelte-check --tsconfig ./tsconfig.json",
|
|
51
|
+
"test": "vitest run",
|
|
52
|
+
"test:watch": "vitest"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"p5": ">=1.11.0 <3",
|
|
56
|
+
"svelte": "^5.0.0"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@sveltejs/package": "catalog:",
|
|
60
|
+
"@sveltejs/vite-plugin-svelte": "catalog:",
|
|
61
|
+
"@types/p5": "catalog:",
|
|
62
|
+
"p5": "catalog:",
|
|
63
|
+
"publint": "catalog:",
|
|
64
|
+
"svelte": "catalog:",
|
|
65
|
+
"svelte-check": "catalog:",
|
|
66
|
+
"typescript": "catalog:",
|
|
67
|
+
"vitest": "catalog:"
|
|
68
|
+
}
|
|
69
|
+
}
|