feat: unified composition renderer for clipboard/export

Replace the split snapshot/native renderer paths with a single
composition pipeline (compositionRenderer.ts) that:

- Loads actual source images and uses naturalWidth/Height for
  correct full-resolution sampling (fixes top-left-corner-only bug
  caused by data.w/h being capped to 600px display dimensions)
- Routes image-only selections through native Canvas 2D composition
- Falls back to viewport snapshot for mixed/unsupported selections
  with explicit warnings instead of silent degradation
- Resolves group children via itemResolver for proper group export
- Rejects group children from native composition (local coords
  incompatible with world-space drawing)
- Adds canvas size safety limits with auto-downscale
- Guards VideoSprite._drawFrame against null texture source race
  condition during zoom-triggered culling

New files:
- compositionRenderer.ts — unified composition module
- compositionRenderer.test.ts — 16 tests for entry flattening,
  bounds, dimensions, group handling

Modified:
- clipboard.ts — uses composeSelection() instead of direct renderers
- export.ts — uses composeSelection() + getCompositionDimensions()
- Editor.tsx, useShortcutHandler.ts — pass scene for group resolution
- VideoSprite.ts — null guard on texture source in frame loop
This commit is contained in:
Hiren Kangad
2026-03-13 16:59:11 +05:30
parent 1ceafec67f
commit 05118330d6
7 changed files with 805 additions and 72 deletions
+2 -1
View File
@@ -80,7 +80,8 @@ export function useShortcutHandler(deps: ShortcutHandlerDeps) {
writeCanvasToClipboard: async (items?: SceneItem[]) => {
try {
const app = canvasRef.current?.getApp() ?? null;
await writeCanvasToClipboard(app, viewport, items);
const scene = canvasRef.current?.getScene() ?? undefined;
await writeCanvasToClipboard(app, viewport, items, scene);
} catch (err: any) {
showToast('Copy failed: ' + (err.message || 'clipboard not available'));
}