@oh-just-another/renderer-core 0.57.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/CHANGELOG.md +15 -0
- package/LICENSE +21 -0
- package/README.md +40 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/animation-adapter.d.ts +91 -0
- package/dist/animation-adapter.d.ts.map +1 -0
- package/dist/animation-adapter.js +90 -0
- package/dist/animation-adapter.js.map +1 -0
- package/dist/built-in-renderers.d.ts +6 -0
- package/dist/built-in-renderers.d.ts.map +1 -0
- package/dist/built-in-renderers.js +517 -0
- package/dist/built-in-renderers.js.map +1 -0
- package/dist/constants.d.ts +97 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +115 -0
- package/dist/constants.js.map +1 -0
- package/dist/edge-cache-bitmap.d.ts +34 -0
- package/dist/edge-cache-bitmap.d.ts.map +1 -0
- package/dist/edge-cache-bitmap.js +42 -0
- package/dist/edge-cache-bitmap.js.map +1 -0
- package/dist/edge-cache.d.ts +32 -0
- package/dist/edge-cache.d.ts.map +1 -0
- package/dist/edge-cache.js +90 -0
- package/dist/edge-cache.js.map +1 -0
- package/dist/edge-renderer.d.ts +73 -0
- package/dist/edge-renderer.d.ts.map +1 -0
- package/dist/edge-renderer.js +554 -0
- package/dist/edge-renderer.js.map +1 -0
- package/dist/grid-renderer.d.ts +55 -0
- package/dist/grid-renderer.d.ts.map +1 -0
- package/dist/grid-renderer.js +173 -0
- package/dist/grid-renderer.js.map +1 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/js-rasterizer.d.ts +8 -0
- package/dist/js-rasterizer.d.ts.map +1 -0
- package/dist/js-rasterizer.js +86 -0
- package/dist/js-rasterizer.js.map +1 -0
- package/dist/layer-cache-composite.d.ts +39 -0
- package/dist/layer-cache-composite.d.ts.map +1 -0
- package/dist/layer-cache-composite.js +47 -0
- package/dist/layer-cache-composite.js.map +1 -0
- package/dist/layers.d.ts +14 -0
- package/dist/layers.d.ts.map +1 -0
- package/dist/layers.js +2 -0
- package/dist/layers.js.map +1 -0
- package/dist/rasterizer.d.ts +27 -0
- package/dist/rasterizer.d.ts.map +1 -0
- package/dist/rasterizer.js +10 -0
- package/dist/rasterizer.js.map +1 -0
- package/dist/render-target.d.ts +94 -0
- package/dist/render-target.d.ts.map +1 -0
- package/dist/render-target.js +2 -0
- package/dist/render-target.js.map +1 -0
- package/dist/scene-renderer.d.ts +126 -0
- package/dist/scene-renderer.d.ts.map +1 -0
- package/dist/scene-renderer.js +149 -0
- package/dist/scene-renderer.js.map +1 -0
- package/dist/shape-cache-bitmap.d.ts +42 -0
- package/dist/shape-cache-bitmap.d.ts.map +1 -0
- package/dist/shape-cache-bitmap.js +61 -0
- package/dist/shape-cache-bitmap.js.map +1 -0
- package/dist/shape-cache.d.ts +40 -0
- package/dist/shape-cache.d.ts.map +1 -0
- package/dist/shape-cache.js +67 -0
- package/dist/shape-cache.js.map +1 -0
- package/dist/shape-renderer.d.ts +36 -0
- package/dist/shape-renderer.d.ts.map +1 -0
- package/dist/shape-renderer.js +15 -0
- package/dist/shape-renderer.js.map +1 -0
- package/dist/text-editing.d.ts +81 -0
- package/dist/text-editing.d.ts.map +1 -0
- package/dist/text-editing.js +191 -0
- package/dist/text-editing.js.map +1 -0
- package/dist/text-layout.d.ts +44 -0
- package/dist/text-layout.d.ts.map +1 -0
- package/dist/text-layout.js +57 -0
- package/dist/text-layout.js.map +1 -0
- package/dist/text-shaper.d.ts +53 -0
- package/dist/text-shaper.d.ts.map +1 -0
- package/dist/text-shaper.js +19 -0
- package/dist/text-shaper.js.map +1 -0
- package/dist/tile-renderer.d.ts +126 -0
- package/dist/tile-renderer.d.ts.map +1 -0
- package/dist/tile-renderer.js +192 -0
- package/dist/tile-renderer.js.map +1 -0
- package/dist/worker-render.d.ts +54 -0
- package/dist/worker-render.d.ts.map +1 -0
- package/dist/worker-render.js +8 -0
- package/dist/worker-render.js.map +1 -0
- package/package.json +56 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-target.d.ts","sourceRoot":"","sources":["../src/render-target.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-target.js","sourceRoot":"","sources":["../src/render-target.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { type Scene, type ElementBase, type SpatialGrid } from "@oh-just-another/scene";
|
|
2
|
+
import type { Bounds, LayerId, ElementId } from "@oh-just-another/types";
|
|
3
|
+
import type { RenderTarget } from "./render-target.js";
|
|
4
|
+
import { ElementCache } from "./shape-cache.js";
|
|
5
|
+
import type { LayerCompositeCache } from "./layer-cache-composite.js";
|
|
6
|
+
/**
|
|
7
|
+
* Zoom-based level-of-detail thresholds. Each threshold turns on a
|
|
8
|
+
* cheaper render path when the scene zoom drops below it:
|
|
9
|
+
*
|
|
10
|
+
* - **placeholder** → draw a flat fill at the shape's world AABB and
|
|
11
|
+
* skip the registered renderer entirely. Highest savings, biggest
|
|
12
|
+
* visual fidelity loss; reserve for very-zoomed-out overviews.
|
|
13
|
+
* - **hideText** → skip text shapes (their wrap+measure cost is high).
|
|
14
|
+
*
|
|
15
|
+
* Thresholds compare against `scene.viewport.zoom` (1.0 = 1:1 pixels).
|
|
16
|
+
* Omit a threshold to disable that level.
|
|
17
|
+
*/
|
|
18
|
+
export interface LodOptions {
|
|
19
|
+
readonly placeholder?: number;
|
|
20
|
+
readonly hideText?: number;
|
|
21
|
+
}
|
|
22
|
+
export interface RenderSceneOptions {
|
|
23
|
+
/** Skip clearing the target before drawing. Default: false. */
|
|
24
|
+
readonly skipClear?: boolean;
|
|
25
|
+
/** Called for shapes whose `type` has no registered renderer. Default: ignore. */
|
|
26
|
+
readonly onUnknownElement?: (shape: ElementBase) => void;
|
|
27
|
+
/**
|
|
28
|
+
* World-space viewport bounds. When provided, shapes whose AABB does
|
|
29
|
+
* not intersect this rectangle are skipped (viewport culling). Pass
|
|
30
|
+
* a slightly inflated rect to avoid pop-in during pan.
|
|
31
|
+
*/
|
|
32
|
+
readonly viewport?: Bounds;
|
|
33
|
+
/**
|
|
34
|
+
* Persistent bounds cache. When omitted a fresh per-render cache is
|
|
35
|
+
* created — fine for hot paths because lookups inside one frame still
|
|
36
|
+
* amortize. Pass a long-lived cache from `Editor` to share work across
|
|
37
|
+
* frames, hit-test, and overlay.
|
|
38
|
+
*/
|
|
39
|
+
readonly boundsCache?: ElementCache<Bounds>;
|
|
40
|
+
/**
|
|
41
|
+
* Pre-built spatial index. When provided together with `viewport`, the
|
|
42
|
+
* renderer picks candidate shapes from the index and skips full layer
|
|
43
|
+
* scans — pays off around ~10k shapes.
|
|
44
|
+
*/
|
|
45
|
+
readonly spatialIndex?: SpatialGrid;
|
|
46
|
+
/**
|
|
47
|
+
* Zoom thresholds for cheaper render paths. See {@link LodOptions}.
|
|
48
|
+
*/
|
|
49
|
+
readonly lod?: LodOptions;
|
|
50
|
+
/**
|
|
51
|
+
* Placeholder fill colour. Defaults to `#bbb`. Pick something close
|
|
52
|
+
* to the average shape colour so the transition is unobtrusive.
|
|
53
|
+
*/
|
|
54
|
+
readonly placeholderFill?: string;
|
|
55
|
+
/**
|
|
56
|
+
* Optional dirty rectangle in **world** coords. When set:
|
|
57
|
+
* • the renderer clears only the corresponding screen region;
|
|
58
|
+
* • shapes whose world AABB doesn't intersect the dirty rect are
|
|
59
|
+
* skipped entirely.
|
|
60
|
+
* Combined with shape-identity tracking by the host this drops most
|
|
61
|
+
* of the per-frame work for "single shape moves on otherwise static
|
|
62
|
+
* scene".
|
|
63
|
+
*/
|
|
64
|
+
readonly dirtyWorld?: Bounds;
|
|
65
|
+
/**
|
|
66
|
+
* Shapes to render with reduced alpha (modern-style group isolation).
|
|
67
|
+
* For each shape whose `id` appears in this set, the renderer sets
|
|
68
|
+
* `globalAlpha = dimOpacity` for the per-shape draw pass before
|
|
69
|
+
* dispatching to the registered renderer.
|
|
70
|
+
*
|
|
71
|
+
* Caveat: shapes whose own `style.opacity` is explicitly set will
|
|
72
|
+
* have their renderer call `setOpacity` again and override the
|
|
73
|
+
* dim — the dim affects only the common case where shapes don't
|
|
74
|
+
* carry an explicit opacity. Acceptable for the isolation UX
|
|
75
|
+
* because outsiders are usually plain opaque shapes.
|
|
76
|
+
*/
|
|
77
|
+
readonly dimElements?: ReadonlySet<ElementId>;
|
|
78
|
+
/**
|
|
79
|
+
* Alpha to use for `dimElements`. Default 1 (no-op). Hosts using the
|
|
80
|
+
* isolation feature should pass their `ISOLATION_DIM_OPACITY`
|
|
81
|
+
* constant.
|
|
82
|
+
*/
|
|
83
|
+
readonly dimOpacity?: number;
|
|
84
|
+
/**
|
|
85
|
+
* Element ids that should NOT render this pass. Used by hosts that
|
|
86
|
+
* implement per-shape / group hide propagation: the editor walks
|
|
87
|
+
* the parent chain to compute which shapes are effectively hidden
|
|
88
|
+
* and forwards the set here. Hidden shapes are also skipped in
|
|
89
|
+
* hit-test on the editor side, so they read as "absent" entirely.
|
|
90
|
+
*/
|
|
91
|
+
readonly hideElements?: ReadonlySet<ElementId>;
|
|
92
|
+
/**
|
|
93
|
+
* Per-layer composite bitmap cache. When supplied along with
|
|
94
|
+
* `compositeLayerBitmap`, unchanged layers (i.e. not present in
|
|
95
|
+
* `dirtyLayerIds`) are drawn from a single cached `drawImage` call
|
|
96
|
+
* instead of walking every shape.
|
|
97
|
+
*
|
|
98
|
+
* Pass `dirtyLayerIds` so the renderer knows which layers to
|
|
99
|
+
* re-rasterise. Without it the cache is treated as cold every
|
|
100
|
+
* frame (defensive — better stale work than a stale visual).
|
|
101
|
+
*/
|
|
102
|
+
readonly layerCompositeCache?: LayerCompositeCache;
|
|
103
|
+
readonly dirtyLayerIds?: ReadonlySet<LayerId>;
|
|
104
|
+
/**
|
|
105
|
+
* Host-side layer rasteriser. Receives the layer id, the active
|
|
106
|
+
* zoom bucket, and the scene; returns the bitmap to cache or
|
|
107
|
+
* `null` to opt out. The kernel doesn't ship one — `Offscreen
|
|
108
|
+
* Canvas` import lives in `@renderer-canvas`.
|
|
109
|
+
*/
|
|
110
|
+
readonly compositeLayerBitmap?: (layerId: LayerId, zoomBucket: number, scene: Scene) => unknown;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Renders the `main` z-stack of a scene onto a single target.
|
|
114
|
+
*
|
|
115
|
+
* Order of operations:
|
|
116
|
+
* 1. Optionally clear the surface.
|
|
117
|
+
* 2. Apply the scene's world-to-screen transform.
|
|
118
|
+
* 3. For each visible layer (bottom → top): for each shape (bottom → top):
|
|
119
|
+
* save state, push the shape's local TRS, invoke its registered renderer.
|
|
120
|
+
*
|
|
121
|
+
* This function does not draw edges, selection handles, or grids — those
|
|
122
|
+
* either live on different layers (`background` / `overlay`) or are added by
|
|
123
|
+
* higher-level packages.
|
|
124
|
+
*/
|
|
125
|
+
export declare const renderScene: (scene: Scene, target: RenderTarget, options?: RenderSceneOptions) => void;
|
|
126
|
+
//# sourceMappingURL=scene-renderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scene-renderer.d.ts","sourceRoot":"","sources":["../src/scene-renderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,KAAK,EACV,KAAK,WAAW,EAChB,KAAK,WAAW,EACjB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAEzE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,OAAO,EAAqB,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAEnE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAGtE;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,kBAAkB;IACjC,+DAA+D;IAC/D,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,kFAAkF;IAClF,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,CAAC;IACzD;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC;IAC5C;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,WAAW,CAAC;IACpC;;OAEG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,UAAU,CAAC;IAC1B;;;OAGG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC;;;;;;;;OAQG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC,SAAS,CAAC,CAAC;IAC9C;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;;;OAMG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,WAAW,CAAC,SAAS,CAAC,CAAC;IAC/C;;;;;;;;;OASG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;IACnD,QAAQ,CAAC,aAAa,CAAC,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC;IAC9C;;;;;OAKG;IACH,QAAQ,CAAC,oBAAoB,CAAC,EAAE,CAC9B,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,KAAK,KACT,OAAO,CAAC;CACd;AAED;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,WAAW,GACtB,OAAO,KAAK,EACZ,QAAQ,YAAY,EACpB,UAAS,kBAAuB,KAC/B,IAkIF,CAAC"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { getLayersInOrder, getElementsInLayer, getWorldToScreen, } from "@oh-just-another/scene";
|
|
2
|
+
import { bounds as B, matrix } from "@oh-just-another/math";
|
|
3
|
+
import { getElementRenderer } from "./shape-renderer.js";
|
|
4
|
+
import { cachedWorldBounds, ElementCache } from "./shape-cache.js";
|
|
5
|
+
import { DEFAULT_PLACEHOLDER_FILL } from "./constants.js";
|
|
6
|
+
import { zoomBucket as bucketFor } from "./shape-cache-bitmap.js";
|
|
7
|
+
/**
|
|
8
|
+
* Renders the `main` z-stack of a scene onto a single target.
|
|
9
|
+
*
|
|
10
|
+
* Order of operations:
|
|
11
|
+
* 1. Optionally clear the surface.
|
|
12
|
+
* 2. Apply the scene's world-to-screen transform.
|
|
13
|
+
* 3. For each visible layer (bottom → top): for each shape (bottom → top):
|
|
14
|
+
* save state, push the shape's local TRS, invoke its registered renderer.
|
|
15
|
+
*
|
|
16
|
+
* This function does not draw edges, selection handles, or grids — those
|
|
17
|
+
* either live on different layers (`background` / `overlay`) or are added by
|
|
18
|
+
* higher-level packages.
|
|
19
|
+
*/
|
|
20
|
+
export const renderScene = (scene, target, options = {}) => {
|
|
21
|
+
const w2s = getWorldToScreen(scene.viewport);
|
|
22
|
+
const dirtyWorld = options.dirtyWorld;
|
|
23
|
+
if (!options.skipClear) {
|
|
24
|
+
if (dirtyWorld) {
|
|
25
|
+
// Project the dirty rect to screen pixels, inflate by a few
|
|
26
|
+
// pixels to cover anti-aliased stroke fuzz.
|
|
27
|
+
const corners = [
|
|
28
|
+
matrix.applyToPoint(w2s, { x: dirtyWorld.x, y: dirtyWorld.y }),
|
|
29
|
+
matrix.applyToPoint(w2s, {
|
|
30
|
+
x: dirtyWorld.x + dirtyWorld.width,
|
|
31
|
+
y: dirtyWorld.y + dirtyWorld.height,
|
|
32
|
+
}),
|
|
33
|
+
];
|
|
34
|
+
const screen = B.expand(B.fromPoints(corners), 2);
|
|
35
|
+
target.clear(screen);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
target.clear();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
target.save();
|
|
42
|
+
target.setTransform(w2s);
|
|
43
|
+
const boundsCache = options.boundsCache ?? new ElementCache();
|
|
44
|
+
const viewport = options.viewport;
|
|
45
|
+
// Spatial-index candidate set: when present, restricts the per-layer
|
|
46
|
+
// walk to shapes the index considers possibly-visible. Without it the
|
|
47
|
+
// per-shape AABB check on a cached bounds is still cheap (~50ns), so
|
|
48
|
+
// the index is only worth the build cost for very large scenes.
|
|
49
|
+
let candidates = null;
|
|
50
|
+
if (viewport && options.spatialIndex) {
|
|
51
|
+
candidates = options.spatialIndex.query(viewport);
|
|
52
|
+
}
|
|
53
|
+
const zoom = scene.viewport.zoom;
|
|
54
|
+
const lod = options.lod;
|
|
55
|
+
const usePlaceholder = lod?.placeholder !== undefined && zoom < lod.placeholder;
|
|
56
|
+
const dropText = lod?.hideText !== undefined && zoom < lod.hideText;
|
|
57
|
+
const placeholderFill = options.placeholderFill ?? DEFAULT_PLACEHOLDER_FILL;
|
|
58
|
+
const layerCache = options.layerCompositeCache;
|
|
59
|
+
const dirtyLayers = options.dirtyLayerIds;
|
|
60
|
+
const compositeLayerBitmap = options.compositeLayerBitmap;
|
|
61
|
+
const zoomBucket = bucketFor(zoom);
|
|
62
|
+
const layerBoundsFor = (layerId) => {
|
|
63
|
+
let acc = null;
|
|
64
|
+
for (const shape of getElementsInLayer(scene, layerId)) {
|
|
65
|
+
const bb = cachedWorldBounds(boundsCache, shape);
|
|
66
|
+
acc = acc ? B.union(acc, bb) : bb;
|
|
67
|
+
}
|
|
68
|
+
return acc;
|
|
69
|
+
};
|
|
70
|
+
for (const layer of getLayersInOrder(scene)) {
|
|
71
|
+
if (!layer.visible)
|
|
72
|
+
continue;
|
|
73
|
+
// Per-layer composite cache fast path. Only fires when the
|
|
74
|
+
// host plugged a cache + a layer rasteriser; the kernel
|
|
75
|
+
// doesn't ship a default rasteriser because OffscreenCanvas
|
|
76
|
+
// creation lives in renderer-canvas. Drop dirty layers + ones
|
|
77
|
+
// touched by a dirty rect from the cache so the bitmap isn't
|
|
78
|
+
// re-used after a mutation.
|
|
79
|
+
if (layerCache && compositeLayerBitmap) {
|
|
80
|
+
if (dirtyLayers?.has(layer.id))
|
|
81
|
+
layerCache.invalidateLayer(layer.id);
|
|
82
|
+
let bitmap = layerCache.get(layer.id, zoomBucket);
|
|
83
|
+
if (bitmap === undefined) {
|
|
84
|
+
const fresh = compositeLayerBitmap(layer.id, zoomBucket, scene);
|
|
85
|
+
if (fresh !== null) {
|
|
86
|
+
layerCache.set(layer.id, zoomBucket, fresh);
|
|
87
|
+
bitmap = fresh;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (bitmap !== undefined) {
|
|
91
|
+
const bb = layerBoundsFor(layer.id);
|
|
92
|
+
if (bb)
|
|
93
|
+
target.drawImage(bitmap, bb.x, bb.y, bb.width, bb.height);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
for (const shape of getElementsInLayer(scene, layer.id)) {
|
|
98
|
+
if (options.hideElements?.has(shape.id))
|
|
99
|
+
continue;
|
|
100
|
+
if (candidates && !candidates.has(shape.id))
|
|
101
|
+
continue;
|
|
102
|
+
if (viewport) {
|
|
103
|
+
const bb = cachedWorldBounds(boundsCache, shape);
|
|
104
|
+
if (!B.intersects(bb, viewport))
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (dirtyWorld) {
|
|
108
|
+
const bb = cachedWorldBounds(boundsCache, shape);
|
|
109
|
+
if (!B.intersects(bb, dirtyWorld))
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (dropText && shape.type === "text")
|
|
113
|
+
continue;
|
|
114
|
+
if (usePlaceholder) {
|
|
115
|
+
// Draw the AABB directly in world coords — skip the renderer
|
|
116
|
+
// entirely. The shape's TRS is folded into the cached bounds.
|
|
117
|
+
const bb = cachedWorldBounds(boundsCache, shape);
|
|
118
|
+
target.setFill(placeholderFill);
|
|
119
|
+
target.setStrokeWidth(0);
|
|
120
|
+
target.beginPath();
|
|
121
|
+
target.rect(bb.x, bb.y, bb.width, bb.height);
|
|
122
|
+
target.fill();
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const renderer = getElementRenderer(shape.type);
|
|
126
|
+
if (!renderer) {
|
|
127
|
+
options.onUnknownElement?.(shape);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
target.save();
|
|
131
|
+
// Isolation dim — set BEFORE the renderer runs so any
|
|
132
|
+
// shape-style-specific setOpacity inside the renderer can
|
|
133
|
+
// override (see RenderSceneOptions.dimElements).
|
|
134
|
+
if (options.dimElements?.has(shape.id) && options.dimOpacity !== undefined) {
|
|
135
|
+
target.setOpacity(options.dimOpacity);
|
|
136
|
+
}
|
|
137
|
+
target.translate(shape.position.x, shape.position.y);
|
|
138
|
+
if (shape.rotation !== 0)
|
|
139
|
+
target.rotate(shape.rotation);
|
|
140
|
+
if (shape.scale.x !== 1 || shape.scale.y !== 1) {
|
|
141
|
+
target.scale(shape.scale.x, shape.scale.y);
|
|
142
|
+
}
|
|
143
|
+
renderer(shape, target, { zoom });
|
|
144
|
+
target.restore();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
target.restore();
|
|
148
|
+
};
|
|
149
|
+
//# sourceMappingURL=scene-renderer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scene-renderer.js","sourceRoot":"","sources":["../src/scene-renderer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ElementBase } from "@oh-just-another/scene";
|
|
2
|
+
/**
|
|
3
|
+
* Per-shape rasterised cache. Keyed by the shape's identity reference —
|
|
4
|
+
* since scene mutations always replace the shape object (`apply(scene,
|
|
5
|
+
* patch)` produces fresh references), a cache hit is guaranteed to reflect
|
|
6
|
+
* the exact rendered output of the cached version. Pan / zoom invalidation
|
|
7
|
+
* is the host's job: keep zoom in a small "bucket" (e.g. round to 0.1) and
|
|
8
|
+
* include it in the key.
|
|
9
|
+
*
|
|
10
|
+
* LRU-by-insertion-order with a count cap. Hosts can replace with their own
|
|
11
|
+
* cache by implementing the same `get` / `set` / `delete` surface.
|
|
12
|
+
*/
|
|
13
|
+
export interface ElementBitmapCache<V = unknown> {
|
|
14
|
+
get(shape: ElementBase, zoomBucket: number): V | undefined;
|
|
15
|
+
set(shape: ElementBase, zoomBucket: number, value: V): void;
|
|
16
|
+
delete(shape: ElementBase, zoomBucket: number): void;
|
|
17
|
+
clear(): void;
|
|
18
|
+
readonly size: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* In-memory LRU cache. Operates on shape identity (reference) —
|
|
22
|
+
* a stale shape reference for the same id is a miss because the
|
|
23
|
+
* cached entry's `shapeRef !== shape`. That is the invalidation
|
|
24
|
+
* mechanism — no version field needed.
|
|
25
|
+
*/
|
|
26
|
+
export declare class InMemoryElementBitmapCache<V> implements ElementBitmapCache<V> {
|
|
27
|
+
private readonly entries;
|
|
28
|
+
private readonly cap;
|
|
29
|
+
constructor(cap?: number);
|
|
30
|
+
get size(): number;
|
|
31
|
+
get(shape: ElementBase, zoomBucket: number): V | undefined;
|
|
32
|
+
set(shape: ElementBase, zoomBucket: number, value: V): void;
|
|
33
|
+
delete(shape: ElementBase, zoomBucket: number): void;
|
|
34
|
+
clear(): void;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Quantise a continuous zoom value to a bucket. Buckets within a
|
|
38
|
+
* power-of-two range share a cache entry so small zoom adjustments
|
|
39
|
+
* don't blow the cache. `bucket = 2 ^ round(log2(zoom))`.
|
|
40
|
+
*/
|
|
41
|
+
export declare const zoomBucket: (zoom: number) => number;
|
|
42
|
+
//# sourceMappingURL=shape-cache-bitmap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shape-cache-bitmap.d.ts","sourceRoot":"","sources":["../src/shape-cache-bitmap.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAE1D;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,kBAAkB,CAAC,CAAC,GAAG,OAAO;IAC7C,GAAG,CAAC,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS,CAAC;IAC3D,GAAG,CAAC,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC;IAC5D,MAAM,CAAC,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACrD,KAAK,IAAI,IAAI,CAAC;IACd,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAUD;;;;;GAKG;AACH,qBAAa,0BAA0B,CAAC,CAAC,CAAE,YAAW,kBAAkB,CAAC,CAAC,CAAC;IACzE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA+B;IACvD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;gBAEjB,GAAG,SAAM;IAIrB,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,GAAG,CAAC,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS;IAe1D,GAAG,CAAC,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI;IAW3D,MAAM,CAAC,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IAIpD,KAAK,IAAI,IAAI;CAGd;AAED;;;;GAIG;AACH,eAAO,MAAM,UAAU,GAAI,MAAM,MAAM,KAAG,MAGzC,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const keyFor = (shape, zoomBucket) => `${shape.id}@${zoomBucket}`;
|
|
2
|
+
/**
|
|
3
|
+
* In-memory LRU cache. Operates on shape identity (reference) —
|
|
4
|
+
* a stale shape reference for the same id is a miss because the
|
|
5
|
+
* cached entry's `shapeRef !== shape`. That is the invalidation
|
|
6
|
+
* mechanism — no version field needed.
|
|
7
|
+
*/
|
|
8
|
+
export class InMemoryElementBitmapCache {
|
|
9
|
+
entries = new Map();
|
|
10
|
+
cap;
|
|
11
|
+
constructor(cap = 512) {
|
|
12
|
+
this.cap = cap;
|
|
13
|
+
}
|
|
14
|
+
get size() {
|
|
15
|
+
return this.entries.size;
|
|
16
|
+
}
|
|
17
|
+
get(shape, zoomBucket) {
|
|
18
|
+
const key = keyFor(shape, zoomBucket);
|
|
19
|
+
const e = this.entries.get(key);
|
|
20
|
+
if (!e)
|
|
21
|
+
return undefined;
|
|
22
|
+
if (e.shapeRef !== shape) {
|
|
23
|
+
// Reference changed → stale; evict so the slot is free.
|
|
24
|
+
this.entries.delete(key);
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
// Re-insert to mark as recently-used (Map preserves insert order).
|
|
28
|
+
this.entries.delete(key);
|
|
29
|
+
this.entries.set(key, e);
|
|
30
|
+
return e.value;
|
|
31
|
+
}
|
|
32
|
+
set(shape, zoomBucket, value) {
|
|
33
|
+
const key = keyFor(shape, zoomBucket);
|
|
34
|
+
if (this.entries.has(key))
|
|
35
|
+
this.entries.delete(key);
|
|
36
|
+
this.entries.set(key, { shapeRef: shape, value });
|
|
37
|
+
if (this.entries.size > this.cap) {
|
|
38
|
+
// Evict the oldest entry (first key in insertion order).
|
|
39
|
+
const oldest = this.entries.keys().next().value;
|
|
40
|
+
if (oldest !== undefined)
|
|
41
|
+
this.entries.delete(oldest);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
delete(shape, zoomBucket) {
|
|
45
|
+
this.entries.delete(keyFor(shape, zoomBucket));
|
|
46
|
+
}
|
|
47
|
+
clear() {
|
|
48
|
+
this.entries.clear();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Quantise a continuous zoom value to a bucket. Buckets within a
|
|
53
|
+
* power-of-two range share a cache entry so small zoom adjustments
|
|
54
|
+
* don't blow the cache. `bucket = 2 ^ round(log2(zoom))`.
|
|
55
|
+
*/
|
|
56
|
+
export const zoomBucket = (zoom) => {
|
|
57
|
+
if (zoom <= 0)
|
|
58
|
+
return 1;
|
|
59
|
+
return 2 ** Math.round(Math.log2(zoom));
|
|
60
|
+
};
|
|
61
|
+
//# sourceMappingURL=shape-cache-bitmap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shape-cache-bitmap.js","sourceRoot":"","sources":["../src/shape-cache-bitmap.ts"],"names":[],"mappings":"AAsBA,MAAM,MAAM,GAAG,CAAC,KAAkB,EAAE,UAAkB,EAAU,EAAE,CAChE,GAAG,KAAK,CAAC,EAAE,IAAI,UAAU,EAAE,CAAC;AAO9B;;;;;GAKG;AACH,MAAM,OAAO,0BAA0B;IACpB,OAAO,GAAG,IAAI,GAAG,EAAoB,CAAC;IACtC,GAAG,CAAS;IAE7B,YAAY,GAAG,GAAG,GAAG;QACnB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;IACjB,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;IAC3B,CAAC;IAED,GAAG,CAAC,KAAkB,EAAE,UAAkB;QACxC,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;QACtC,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAChC,IAAI,CAAC,CAAC;YAAE,OAAO,SAAS,CAAC;QACzB,IAAI,CAAC,CAAC,QAAQ,KAAK,KAAK,EAAE,CAAC;YACzB,wDAAwD;YACxD,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACzB,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,mEAAmE;QACnE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACzB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACzB,OAAO,CAAC,CAAC,KAAK,CAAC;IACjB,CAAC;IAED,GAAG,CAAC,KAAkB,EAAE,UAAkB,EAAE,KAAQ;QAClD,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;QACtC,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACpD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;QAClD,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACjC,yDAAyD;YACzD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC;YAChD,IAAI,MAAM,KAAK,SAAS;gBAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACxD,CAAC;IACH,CAAC;IAED,MAAM,CAAC,KAAkB,EAAE,UAAkB;QAC3C,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC;IACjD,CAAC;IAED,KAAK;QACH,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,IAAY,EAAU,EAAE;IACjD,IAAI,IAAI,IAAI,CAAC;QAAE,OAAO,CAAC,CAAC;IACxB,OAAO,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AAC1C,CAAC,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Bounds, ElementId } from "@oh-just-another/types";
|
|
2
|
+
import { type Scene, type ElementBase } from "@oh-just-another/scene";
|
|
3
|
+
/**
|
|
4
|
+
* Per-shape memo with object-identity invalidation. Cached value sticks
|
|
5
|
+
* until the underlying shape reference changes — and because every scene
|
|
6
|
+
* op (`updateElement` / `moveElement` / ...) returns a new shape object, the
|
|
7
|
+
* cache invalidates automatically without the caller threading versions
|
|
8
|
+
* through.
|
|
9
|
+
*
|
|
10
|
+
* Caches survive across frames; pair with `prune(scene)` after large
|
|
11
|
+
* deletions if memory matters. For ephemeral, single-render memos use
|
|
12
|
+
* a fresh `ElementCache` instance (cheap to construct).
|
|
13
|
+
*/
|
|
14
|
+
export declare class ElementCache<T> {
|
|
15
|
+
private readonly entries;
|
|
16
|
+
get(shape: ElementBase): T | undefined;
|
|
17
|
+
set(shape: ElementBase, value: T): T;
|
|
18
|
+
/**
|
|
19
|
+
* Lazy memo. Returns the cached value if `shape` is the same reference
|
|
20
|
+
* as the one we cached against; otherwise runs `compute`, stores the
|
|
21
|
+
* result, and returns it.
|
|
22
|
+
*/
|
|
23
|
+
getOrCompute(shape: ElementBase, compute: (s: ElementBase) => T): T;
|
|
24
|
+
invalidate(id: ElementId): void;
|
|
25
|
+
clear(): void;
|
|
26
|
+
/** Drop entries whose shape is no longer in the scene. */
|
|
27
|
+
prune(scene: Scene): void;
|
|
28
|
+
get size(): number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Shared module-level cache for world-space bounds. Used by `renderScene`
|
|
32
|
+
* for viewport culling and reusable from outside (hit-test, overlay) so
|
|
33
|
+
* a single computation amortizes across passes.
|
|
34
|
+
*
|
|
35
|
+
* `getElementWorldBounds` is pure — same shape ref → same bounds — so a
|
|
36
|
+
* by-identity cache is sound.
|
|
37
|
+
*/
|
|
38
|
+
export declare const sharedBoundsCache: ElementCache<Bounds>;
|
|
39
|
+
export declare const cachedWorldBounds: (cache: ElementCache<Bounds>, shape: ElementBase) => Bounds;
|
|
40
|
+
//# sourceMappingURL=shape-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shape-cache.d.ts","sourceRoot":"","sources":["../src/shape-cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,EAAyB,KAAK,KAAK,EAAE,KAAK,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAE7F;;;;;;;;;;GAUG;AACH,qBAAa,YAAY,CAAC,CAAC;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiE;IAEzF,GAAG,CAAC,KAAK,EAAE,WAAW,GAAG,CAAC,GAAG,SAAS;IAUtC,GAAG,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC;IAKpC;;;;OAIG;IACH,YAAY,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC,EAAE,WAAW,KAAK,CAAC,GAAG,CAAC;IAMnE,UAAU,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI;IAI/B,KAAK,IAAI,IAAI;IAIb,0DAA0D;IAC1D,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAMzB,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,EAAE,YAAY,CAAC,MAAM,CAA8B,CAAC;AAElF,eAAO,MAAM,iBAAiB,GAAI,OAAO,YAAY,CAAC,MAAM,CAAC,EAAE,OAAO,WAAW,KAAG,MAClC,CAAC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { getElementWorldBounds } from "@oh-just-another/scene";
|
|
2
|
+
/**
|
|
3
|
+
* Per-shape memo with object-identity invalidation. Cached value sticks
|
|
4
|
+
* until the underlying shape reference changes — and because every scene
|
|
5
|
+
* op (`updateElement` / `moveElement` / ...) returns a new shape object, the
|
|
6
|
+
* cache invalidates automatically without the caller threading versions
|
|
7
|
+
* through.
|
|
8
|
+
*
|
|
9
|
+
* Caches survive across frames; pair with `prune(scene)` after large
|
|
10
|
+
* deletions if memory matters. For ephemeral, single-render memos use
|
|
11
|
+
* a fresh `ElementCache` instance (cheap to construct).
|
|
12
|
+
*/
|
|
13
|
+
export class ElementCache {
|
|
14
|
+
entries = new Map();
|
|
15
|
+
get(shape) {
|
|
16
|
+
const entry = this.entries.get(shape.id);
|
|
17
|
+
if (!entry)
|
|
18
|
+
return undefined;
|
|
19
|
+
if (entry.ref !== shape) {
|
|
20
|
+
this.entries.delete(shape.id);
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
return entry.value;
|
|
24
|
+
}
|
|
25
|
+
set(shape, value) {
|
|
26
|
+
this.entries.set(shape.id, { ref: shape, value });
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Lazy memo. Returns the cached value if `shape` is the same reference
|
|
31
|
+
* as the one we cached against; otherwise runs `compute`, stores the
|
|
32
|
+
* result, and returns it.
|
|
33
|
+
*/
|
|
34
|
+
getOrCompute(shape, compute) {
|
|
35
|
+
const cached = this.get(shape);
|
|
36
|
+
if (cached !== undefined)
|
|
37
|
+
return cached;
|
|
38
|
+
return this.set(shape, compute(shape));
|
|
39
|
+
}
|
|
40
|
+
invalidate(id) {
|
|
41
|
+
this.entries.delete(id);
|
|
42
|
+
}
|
|
43
|
+
clear() {
|
|
44
|
+
this.entries.clear();
|
|
45
|
+
}
|
|
46
|
+
/** Drop entries whose shape is no longer in the scene. */
|
|
47
|
+
prune(scene) {
|
|
48
|
+
for (const id of this.entries.keys()) {
|
|
49
|
+
if (!scene.elements.has(id))
|
|
50
|
+
this.entries.delete(id);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
get size() {
|
|
54
|
+
return this.entries.size;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Shared module-level cache for world-space bounds. Used by `renderScene`
|
|
59
|
+
* for viewport culling and reusable from outside (hit-test, overlay) so
|
|
60
|
+
* a single computation amortizes across passes.
|
|
61
|
+
*
|
|
62
|
+
* `getElementWorldBounds` is pure — same shape ref → same bounds — so a
|
|
63
|
+
* by-identity cache is sound.
|
|
64
|
+
*/
|
|
65
|
+
export const sharedBoundsCache = new ElementCache();
|
|
66
|
+
export const cachedWorldBounds = (cache, shape) => cache.getOrCompute(shape, getElementWorldBounds);
|
|
67
|
+
//# sourceMappingURL=shape-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shape-cache.js","sourceRoot":"","sources":["../src/shape-cache.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAgC,MAAM,wBAAwB,CAAC;AAE7F;;;;;;;;;;GAUG;AACH,MAAM,OAAO,YAAY;IACN,OAAO,GAAG,IAAI,GAAG,EAAsD,CAAC;IAEzF,GAAG,CAAC,KAAkB;QACpB,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACzC,IAAI,CAAC,KAAK;YAAE,OAAO,SAAS,CAAC;QAC7B,IAAI,KAAK,CAAC,GAAG,KAAK,KAAK,EAAE,CAAC;YACxB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAC9B,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,OAAO,KAAK,CAAC,KAAK,CAAC;IACrB,CAAC;IAED,GAAG,CAAC,KAAkB,EAAE,KAAQ;QAC9B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;QAClD,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;;;OAIG;IACH,YAAY,CAAC,KAAkB,EAAE,OAA8B;QAC7D,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAC/B,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO,MAAM,CAAC;QACxC,OAAO,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;IACzC,CAAC;IAED,UAAU,CAAC,EAAa;QACtB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAC1B,CAAC;IAED,KAAK;QACH,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;IAED,0DAA0D;IAC1D,KAAK,CAAC,KAAY;QAChB,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;YACrC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;gBAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACvD,CAAC;IACH,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;IAC3B,CAAC;CACF;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAyB,IAAI,YAAY,EAAU,CAAC;AAElF,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,KAA2B,EAAE,KAAkB,EAAU,EAAE,CAC3F,KAAK,CAAC,YAAY,CAAC,KAAK,EAAE,qBAAqB,CAAC,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ElementBase } from "@oh-just-another/scene";
|
|
2
|
+
import type { RenderTarget } from "./render-target.js";
|
|
3
|
+
/**
|
|
4
|
+
* Optional draw context passed to an {@link ElementRenderer}. Carries the
|
|
5
|
+
* current view `zoom` so a renderer can draw screen-constant features (e.g. a
|
|
6
|
+
* 1px hairline border that does NOT scale with zoom): a local stroke width of
|
|
7
|
+
* `1 / (zoom * shape.scale)` lands at one device pixel. Optional and additive —
|
|
8
|
+
* renderers that don't need it ignore the third argument, and callers that
|
|
9
|
+
* can't supply it (preview / export at 1:1) may omit it.
|
|
10
|
+
*/
|
|
11
|
+
export interface ElementRenderContext {
|
|
12
|
+
/** Current view scale (1.0 = 1:1). `world × zoom = screen px`. */
|
|
13
|
+
readonly zoom: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Draws a single shape onto `target`. The shape's `position` / `rotation` /
|
|
17
|
+
* `scale` have already been applied to the target — implementations draw in
|
|
18
|
+
* the shape's *local* coordinate space.
|
|
19
|
+
*
|
|
20
|
+
* Implementations should also apply style (fill / stroke / etc.) themselves;
|
|
21
|
+
* the renderer-core does not push styles globally because some shapes (e.g.
|
|
22
|
+
* `text`) extend the base `Style` with overlays.
|
|
23
|
+
*/
|
|
24
|
+
export type ElementRenderer<S extends ElementBase = ElementBase> = (shape: S, target: RenderTarget, ctx?: ElementRenderContext) => void;
|
|
25
|
+
/**
|
|
26
|
+
* Register a renderer for a shape type. Plugins call this at module load.
|
|
27
|
+
* The kernel ships renderers for every built-in shape from `@oh-just-another/scene`
|
|
28
|
+
* — they are installed by `@oh-just-another/renderer-canvas` (and any other
|
|
29
|
+
* backend) on import.
|
|
30
|
+
*/
|
|
31
|
+
export declare const registerElementRenderer: <S extends ElementBase>(type: S["type"], renderer: ElementRenderer<S>) => void;
|
|
32
|
+
/** Look up a registered renderer. Returns `undefined` for unknown types. */
|
|
33
|
+
export declare const getElementRenderer: (type: string) => ElementRenderer | undefined;
|
|
34
|
+
/** True if a renderer is registered for `type`. */
|
|
35
|
+
export declare const hasElementRenderer: (type: string) => boolean;
|
|
36
|
+
//# sourceMappingURL=shape-renderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shape-renderer.d.ts","sourceRoot":"","sources":["../src/shape-renderer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD;;;;;;;GAOG;AACH,MAAM,WAAW,oBAAoB;IACnC,kEAAkE;IAClE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,WAAW,GAAG,WAAW,IAAI,CACjE,KAAK,EAAE,CAAC,EACR,MAAM,EAAE,YAAY,EACpB,GAAG,CAAC,EAAE,oBAAoB,KACvB,IAAI,CAAC;AAIV;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GAAI,CAAC,SAAS,WAAW,EAC3D,MAAM,CAAC,CAAC,MAAM,CAAC,EACf,UAAU,eAAe,CAAC,CAAC,CAAC,KAC3B,IAEF,CAAC;AAEF,4EAA4E;AAC5E,eAAO,MAAM,kBAAkB,GAAI,MAAM,MAAM,KAAG,eAAe,GAAG,SAA+B,CAAC;AAEpG,mDAAmD;AACnD,eAAO,MAAM,kBAAkB,GAAI,MAAM,MAAM,KAAG,OAA6B,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const registry = new Map();
|
|
2
|
+
/**
|
|
3
|
+
* Register a renderer for a shape type. Plugins call this at module load.
|
|
4
|
+
* The kernel ships renderers for every built-in shape from `@oh-just-another/scene`
|
|
5
|
+
* — they are installed by `@oh-just-another/renderer-canvas` (and any other
|
|
6
|
+
* backend) on import.
|
|
7
|
+
*/
|
|
8
|
+
export const registerElementRenderer = (type, renderer) => {
|
|
9
|
+
registry.set(type, renderer);
|
|
10
|
+
};
|
|
11
|
+
/** Look up a registered renderer. Returns `undefined` for unknown types. */
|
|
12
|
+
export const getElementRenderer = (type) => registry.get(type);
|
|
13
|
+
/** True if a renderer is registered for `type`. */
|
|
14
|
+
export const hasElementRenderer = (type) => registry.has(type);
|
|
15
|
+
//# sourceMappingURL=shape-renderer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shape-renderer.js","sourceRoot":"","sources":["../src/shape-renderer.ts"],"names":[],"mappings":"AA+BA,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA2B,CAAC;AAEpD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CACrC,IAAe,EACf,QAA4B,EACtB,EAAE;IACR,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,QAA2B,CAAC,CAAC;AAClD,CAAC,CAAC;AAEF,4EAA4E;AAC5E,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,IAAY,EAA+B,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AAEpG,mDAAmD;AACnD,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,IAAY,EAAW,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC"}
|