fundus 0.12.2 → 0.14.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/build/client/_app/immutable/assets/AssetSourceMenu.IjDdjx3o.css +1 -0
- package/build/client/_app/immutable/assets/AssetSourceMenu.IjDdjx3o.css.br +0 -0
- package/build/client/_app/immutable/assets/AssetSourceMenu.IjDdjx3o.css.gz +0 -0
- package/build/client/_app/immutable/chunks/77p2oEJ1.js +1 -0
- package/build/client/_app/immutable/chunks/77p2oEJ1.js.br +0 -0
- package/build/client/_app/immutable/chunks/77p2oEJ1.js.gz +0 -0
- package/build/client/_app/immutable/chunks/B_PWcB6g.js +108 -0
- package/build/client/_app/immutable/chunks/B_PWcB6g.js.br +0 -0
- package/build/client/_app/immutable/chunks/B_PWcB6g.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{D-dMdMUj.js → RgXSPQ_h.js} +1 -1
- package/build/client/_app/immutable/chunks/RgXSPQ_h.js.br +0 -0
- package/build/client/_app/immutable/chunks/RgXSPQ_h.js.gz +0 -0
- package/build/client/_app/immutable/entry/{app.CmTIRtCw.js → app.BsvwTApt.js} +2 -2
- package/build/client/_app/immutable/entry/app.BsvwTApt.js.br +0 -0
- package/build/client/_app/immutable/entry/app.BsvwTApt.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.BdjLMMsY.js +1 -0
- package/build/client/_app/immutable/entry/start.BdjLMMsY.js.br +0 -0
- package/build/client/_app/immutable/entry/start.BdjLMMsY.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{0.CNULKexJ.js → 0.CjQfQrD6.js} +1 -1
- package/build/client/_app/immutable/nodes/0.CjQfQrD6.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.CjQfQrD6.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{1.Cq-r1S-6.js → 1.DyPcUKIh.js} +1 -1
- package/build/client/_app/immutable/nodes/1.DyPcUKIh.js.br +2 -0
- package/build/client/_app/immutable/nodes/1.DyPcUKIh.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{2.B4CjEZCb.js → 2.CworwA2R.js} +1 -1
- package/build/client/_app/immutable/nodes/2.CworwA2R.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.CworwA2R.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.BKqbML1T.js +1 -0
- package/build/client/_app/immutable/nodes/3.BKqbML1T.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.BKqbML1T.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{4.BkqPmamr.js → 4.Cmigd8CT.js} +1 -1
- package/build/client/_app/immutable/nodes/4.Cmigd8CT.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.Cmigd8CT.js.gz +0 -0
- package/build/client/_app/version.json +1 -1
- package/build/client/_app/version.json.br +0 -0
- package/build/client/_app/version.json.gz +0 -0
- package/build/handler.js +7 -7
- package/build/index.js +7 -7
- package/build/server/chunks/chunks/{context.js-6YdbScn_.js → context.js-CVuTSPjH.js} +2 -2
- package/build/server/chunks/chunks/{context.js-6YdbScn_.js.map → context.js-CVuTSPjH.js.map} +1 -1
- package/build/server/chunks/chunks/{contract.js-BHQbpsFk.js → contract.js-itFN0Gon.js} +6 -2
- package/build/server/chunks/chunks/contract.js-itFN0Gon.js.map +1 -0
- package/build/server/chunks/chunks/{exports.js-ku1LJuiG.js → exports.js-B13PkE0J.js} +2 -2
- package/build/server/chunks/chunks/{exports.js-ku1LJuiG.js.map → exports.js-B13PkE0J.js.map} +1 -1
- package/build/server/chunks/chunks/{http.js-dbT0ifQ9.js → http.js-ChQRRKfZ.js} +2 -2
- package/build/server/chunks/chunks/{http.js-dbT0ifQ9.js.map → http.js-ChQRRKfZ.js.map} +1 -1
- package/build/server/chunks/chunks/{internal.js-BSRzKMSE.js → internal.js-DlKoWKM-.js} +3 -3
- package/build/server/chunks/chunks/{internal.js-BSRzKMSE.js.map → internal.js-DlKoWKM-.js.map} +1 -1
- package/build/server/chunks/chunks/internal2.js-BlXt2rfE.js.map +1 -1
- package/build/server/chunks/chunks/{shared.js-DgL50WRD.js → shared.js-DKt4pwWi.js} +2 -2
- package/build/server/chunks/chunks/{shared.js-DgL50WRD.js.map → shared.js-DKt4pwWi.js.map} +1 -1
- package/build/server/chunks/chunks/{utils.js-D4p4YSNW.js → utils.js-BYMC-4Wt.js} +3 -3
- package/build/server/chunks/chunks/{utils.js-D4p4YSNW.js.map → utils.js-BYMC-4Wt.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/_...path_/{_server.ts.js-BaMGnbi8.js → _server.ts.js-z988EDp2.js} +3 -3
- package/build/server/chunks/entries/endpoints/api/_...path_/{_server.ts.js-BaMGnbi8.js.map → _server.ts.js-z988EDp2.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/_id_/{_server.ts.js-B6YhmqYd.js → _server.ts.js-C68ru6gS.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/assets/_id_/{_server.ts.js-B6YhmqYd.js.map → _server.ts.js-C68ru6gS.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/_id_/raw/{_server.ts.js-qtGGwjZ4.js → _server.ts.js-C0tk6RFk.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/assets/_id_/raw/{_server.ts.js-qtGGwjZ4.js.map → _server.ts.js-C0tk6RFk.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/_id_/reimport/{_server.ts.js-98vjovDS.js → _server.ts.js-I03oZuo8.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/assets/_id_/reimport/{_server.ts.js-98vjovDS.js.map → _server.ts.js-I03oZuo8.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/{_server.ts.js-CCzQEr--.js → _server.ts.js-BsQB8YDq.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/assets/{_server.ts.js-CCzQEr--.js.map → _server.ts.js-BsQB8YDq.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/delete/{_server.ts.js-Ca7G_5IJ.js → _server.ts.js-CJuFE6Fb.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/assets/delete/{_server.ts.js-Ca7G_5IJ.js.map → _server.ts.js-CJuFE6Fb.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/move/{_server.ts.js-DRw4ICSn.js → _server.ts.js-DuOZRRiA.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/assets/move/{_server.ts.js-DRw4ICSn.js.map → _server.ts.js-DuOZRRiA.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/folders/_path_/{_server.ts.js-Ai46jTgw.js → _server.ts.js-DGzYpQbv.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/folders/_path_/{_server.ts.js-Ai46jTgw.js.map → _server.ts.js-DGzYpQbv.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/folders/{_server.ts.js-NQKYbtiU.js → _server.ts.js-B045QoCA.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/folders/{_server.ts.js-NQKYbtiU.js.map → _server.ts.js-B045QoCA.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/imports/{_server.ts.js-_vJ2Npqd.js → _server.ts.js-HUkVdz19.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/imports/{_server.ts.js-_vJ2Npqd.js.map → _server.ts.js-HUkVdz19.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/manifests/_name_/{_server.ts.js-DIn5srjK.js → _server.ts.js-ry509mom.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/manifests/_name_/{_server.ts.js-DIn5srjK.js.map → _server.ts.js-ry509mom.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/manifests/{_server.ts.js-CqFLRTP-.js → _server.ts.js-DU0J7U25.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/manifests/{_server.ts.js-CqFLRTP-.js.map → _server.ts.js-DU0J7U25.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/raw/adopt/{_server.ts.js-BYn_v3VS.js → _server.ts.js-AZIV2PIn.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/raw/adopt/{_server.ts.js-BYn_v3VS.js.map → _server.ts.js-AZIV2PIn.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/raw/edited/accept/{_server.ts.js-sxVzkX1S.js → _server.ts.js-CDL0hCfM.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/raw/edited/accept/{_server.ts.js-sxVzkX1S.js.map → _server.ts.js-CDL0hCfM.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/raw/edited/dismiss/{_server.ts.js-E3B6VyAs.js → _server.ts.js-B13c17fq.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/raw/edited/dismiss/{_server.ts.js-E3B6VyAs.js.map → _server.ts.js-B13c17fq.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/reconcile/{_server.ts.js-ClgS9ghS.js → _server.ts.js-BCxjyAGC.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/reconcile/{_server.ts.js-ClgS9ghS.js.map → _server.ts.js-BCxjyAGC.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/reveal/{_server.ts.js-BPUzWMNL.js → _server.ts.js-BveeMZc2.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/reveal/{_server.ts.js-BPUzWMNL.js.map → _server.ts.js-BveeMZc2.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/state/{_server.ts.js-_G6pURnx.js → _server.ts.js-BZjTd9a0.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/state/{_server.ts.js-_G6pURnx.js.map → _server.ts.js-BZjTd9a0.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/usage/{_server.ts.js-as3GcjaI.js → _server.ts.js-BbTfPvC9.js} +4 -4
- package/build/server/chunks/entries/endpoints/api/usage/{_server.ts.js-as3GcjaI.js.map → _server.ts.js-BbTfPvC9.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/files/_...path_/{_server.ts.js-CR58wymO.js → _server.ts.js-8VPJMLBk.js} +3 -3
- package/build/server/chunks/entries/endpoints/files/_...path_/{_server.ts.js-CR58wymO.js.map → _server.ts.js-8VPJMLBk.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/files/proxy/_...path_/{_server.ts.js-Do2LOf04.js → _server.ts.js-BqR2J2sB.js} +4 -4
- package/build/server/chunks/entries/endpoints/files/proxy/_...path_/{_server.ts.js-Do2LOf04.js.map → _server.ts.js-BqR2J2sB.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/files/raw/_...path_/{_server.ts.js-Ckp6_SaE.js → _server.ts.js-DbA28dCY.js} +4 -4
- package/build/server/chunks/entries/endpoints/files/raw/_...path_/{_server.ts.js-Ckp6_SaE.js.map → _server.ts.js-DbA28dCY.js.map} +1 -1
- package/build/server/chunks/entries/{hooks.server.js-CL1tg0CT.js → hooks.server.js-9PBc_3Jh.js} +3 -3
- package/build/server/chunks/entries/{hooks.server.js-CL1tg0CT.js.map → hooks.server.js-9PBc_3Jh.js.map} +1 -1
- package/build/server/chunks/{handler-BRtsY9uB.js → handler-XxBNCHhb.js} +5 -5
- package/build/server/chunks/{handler-BRtsY9uB.js.map → handler-XxBNCHhb.js.map} +1 -1
- package/build/server/chunks/{index.js-g3JzUW3v.js → index.js-B_76Gq5Y.js} +5 -5
- package/build/server/chunks/{index.js-g3JzUW3v.js.map → index.js-B_76Gq5Y.js.map} +1 -1
- package/build/server/chunks/{manifest.js-D51Bb0-d.js → manifest.js-C2MZZt3o.js} +29 -29
- package/build/server/chunks/{manifest.js-D51Bb0-d.js.map → manifest.js-C2MZZt3o.js.map} +1 -1
- package/build/server/chunks/nodes/{0.js-B1bRRxWY.js → 0.js-BLURyWmd.js} +2 -2
- package/build/server/chunks/nodes/{0.js-B1bRRxWY.js.map → 0.js-BLURyWmd.js.map} +1 -1
- package/build/server/chunks/nodes/1.js-CITI7d3d.js +7 -0
- package/build/server/chunks/nodes/{1.js-BIjbx0GP.js.map → 1.js-CITI7d3d.js.map} +1 -1
- package/build/server/chunks/nodes/2.js-DAls5TjF.js +7 -0
- package/build/server/chunks/nodes/{2.js-B6X0GbFS.js.map → 2.js-DAls5TjF.js.map} +1 -1
- package/build/server/chunks/nodes/3.js-CesAmg_E.js +7 -0
- package/build/server/chunks/nodes/{3.js-CQVJ114p.js.map → 3.js-CesAmg_E.js.map} +1 -1
- package/build/server/chunks/nodes/{4.js-Bt9pLOHE.js → 4.js-BRlAiVnn.js} +2 -2
- package/build/server/chunks/nodes/{4.js-Bt9pLOHE.js.map → 4.js-BRlAiVnn.js.map} +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/manifest-entry.d.ts +14 -5
- package/dist/runtime/components/ChromaKeyVideo.svelte +3 -0
- package/dist/runtime/components/Image.svelte +3 -0
- package/dist/runtime/components/Slice.svelte +215 -18
- package/dist/runtime/components/Slice.svelte.d.ts +3 -4
- package/dist/runtime/components/Video.svelte +3 -0
- package/dist/runtime/geometry.d.ts +15 -9
- package/dist/runtime/geometry.js +21 -14
- package/dist/runtime/index.d.ts +3 -0
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/kind-handlers.d.ts +22 -10
- package/dist/runtime/kind-handlers.js +58 -21
- package/dist/runtime/logging.d.ts +34 -0
- package/dist/runtime/logging.js +27 -0
- package/dist/runtime/manifest.d.ts +16 -5
- package/dist/runtime/manifest.js +19 -8
- package/dist/runtime/observe-border-box-size.d.ts +1 -1
- package/dist/runtime/observe-border-box-size.js +2 -2
- package/dist/runtime/preload-registry.d.ts +20 -1
- package/dist/runtime/preload-registry.js +23 -2
- package/dist/runtime/slice-source-lifecycle.d.ts +11 -2
- package/dist/runtime/slice-source-lifecycle.js +21 -2
- package/dist/server/codegen.js +5 -1
- package/package.json +1 -1
- package/build/client/_app/immutable/assets/AssetSourceMenu.DOu9eU7n.css +0 -1
- package/build/client/_app/immutable/assets/AssetSourceMenu.DOu9eU7n.css.br +0 -0
- package/build/client/_app/immutable/assets/AssetSourceMenu.DOu9eU7n.css.gz +0 -0
- package/build/client/_app/immutable/chunks/BR73V5PG.js +0 -1
- package/build/client/_app/immutable/chunks/BR73V5PG.js.br +0 -2
- package/build/client/_app/immutable/chunks/BR73V5PG.js.gz +0 -0
- package/build/client/_app/immutable/chunks/D-dMdMUj.js.br +0 -0
- package/build/client/_app/immutable/chunks/D-dMdMUj.js.gz +0 -0
- package/build/client/_app/immutable/chunks/PGG-LyPT.js +0 -108
- package/build/client/_app/immutable/chunks/PGG-LyPT.js.br +0 -0
- package/build/client/_app/immutable/chunks/PGG-LyPT.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.CmTIRtCw.js.br +0 -0
- package/build/client/_app/immutable/entry/app.CmTIRtCw.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.M4xqAoBc.js +0 -1
- package/build/client/_app/immutable/entry/start.M4xqAoBc.js.br +0 -1
- package/build/client/_app/immutable/entry/start.M4xqAoBc.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.CNULKexJ.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.CNULKexJ.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.Cq-r1S-6.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.Cq-r1S-6.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.B4CjEZCb.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.B4CjEZCb.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.DzWxKBWe.js +0 -1
- package/build/client/_app/immutable/nodes/3.DzWxKBWe.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.DzWxKBWe.js.gz +0 -0
- package/build/client/_app/immutable/nodes/4.BkqPmamr.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.BkqPmamr.js.gz +0 -0
- package/build/server/chunks/chunks/contract.js-BHQbpsFk.js.map +0 -1
- package/build/server/chunks/nodes/1.js-BIjbx0GP.js +0 -7
- package/build/server/chunks/nodes/2.js-B6X0GbFS.js +0 -7
- package/build/server/chunks/nodes/3.js-CQVJ114p.js +0 -7
- package/dist/runtime/components/SliceCanvas.svelte +0 -173
- package/dist/runtime/components/SliceCanvas.svelte.d.ts +0 -10
- package/dist/runtime/components/SliceDom.svelte +0 -161
- package/dist/runtime/components/SliceDom.svelte.d.ts +0 -10
|
@@ -1,31 +1,58 @@
|
|
|
1
1
|
<!--
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
Slice renderer using the entry-based API (slicing arrives inline on the
|
|
3
|
+
`SliceManifestEntry`). Owns the source lifecycle (load, decode, latest-wins
|
|
4
|
+
commit). A one-slice snapshot renders as a regular image; sliced snapshots
|
|
5
|
+
draw each cell with one `drawImage` into a backing canvas. A ResizeObserver
|
|
6
|
+
triggers a redraw on resize; window `resize` and DPR changes (browser zoom,
|
|
7
|
+
moving between displays) redraw too, and a reactive effect redraws when the
|
|
8
|
+
entry changes — so it also works live inside the editor.
|
|
9
|
+
|
|
10
|
+
The backing-store ratio is the devicePixelRatio FLOORED to an integer and
|
|
11
|
+
capped at `maxDpr` (default 2), never the exact ratio. Cell edges are
|
|
12
|
+
integer virtual CSS pixels, so an integer ratio lands every drawImage edge on
|
|
13
|
+
integer backing pixels — abutting cells cannot open an antialiased gap. On
|
|
14
|
+
displays with a fractional devicePixelRatio (2.625-class Androids, browser
|
|
15
|
+
zoom, scaled desktops) those gaps are exactly the hairlines this avoids;
|
|
16
|
+
the compositor applies the remainder to the finished canvas as one
|
|
17
|
+
upscale, where slice boundaries are plain content. Floor + cap
|
|
18
|
+
deliberately favor seam-freeness and backing memory over sharpness.
|
|
19
|
+
|
|
20
|
+
The measured element is the root div, not its painted child: with overdraw
|
|
21
|
+
the canvas or image is enlarged by the overdraw's destination size and
|
|
22
|
+
offset negatively (see `computeCanvasPaintLayout`), so the baked shadow/glow
|
|
23
|
+
paints outside the layout box while the root (and therefore the layout)
|
|
24
|
+
keeps the box size.
|
|
7
25
|
-->
|
|
8
26
|
<script lang="ts">
|
|
9
27
|
import { onDestroy } from 'svelte';
|
|
10
28
|
import type { SliceManifestEntry } from '../../core/index.js';
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
29
|
+
import {
|
|
30
|
+
computeCanvasPaintLayout,
|
|
31
|
+
computeRenderCells,
|
|
32
|
+
type CanvasPaintLayout
|
|
33
|
+
} from '../geometry.js';
|
|
34
|
+
import { observeBorderBoxSize } from '../observe-border-box-size.js';
|
|
35
|
+
import {
|
|
36
|
+
SliceSourceLifecycle,
|
|
37
|
+
isDetachedSliceSource,
|
|
38
|
+
type LoadedSliceSnapshot
|
|
39
|
+
} from '../slice-source-lifecycle.js';
|
|
40
|
+
import { logRender } from '../logging.js';
|
|
14
41
|
|
|
15
42
|
interface Props {
|
|
16
43
|
src: SliceManifestEntry;
|
|
17
44
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* with a device-pixel-ratio-sized backing store.
|
|
45
|
+
* Upper bound of the canvas backing-store ratio (floored itself). The
|
|
46
|
+
* default of 2 is rarely distinguishable and keeps backing memory bounded.
|
|
21
47
|
*/
|
|
22
|
-
|
|
48
|
+
maxDpr?: number;
|
|
23
49
|
/** Accessible label for the rendered graphic. */
|
|
24
50
|
alt?: string;
|
|
25
51
|
class?: string;
|
|
26
52
|
}
|
|
27
53
|
|
|
28
|
-
let { src,
|
|
54
|
+
let { src, maxDpr = 2, alt = '', class: className = '' }: Props = $props();
|
|
55
|
+
|
|
29
56
|
let snapshot = $state.raw<LoadedSliceSnapshot | null>(null);
|
|
30
57
|
const sourceLifecycle = new SliceSourceLifecycle((loaded) => {
|
|
31
58
|
snapshot = loaded;
|
|
@@ -35,11 +62,181 @@
|
|
|
35
62
|
sourceLifecycle.request(src);
|
|
36
63
|
});
|
|
37
64
|
|
|
65
|
+
$effect(() => logRender(src));
|
|
66
|
+
|
|
38
67
|
onDestroy(() => sourceLifecycle.dispose());
|
|
68
|
+
|
|
69
|
+
let root = $state<HTMLDivElement>();
|
|
70
|
+
let canvas = $state<HTMLCanvasElement>();
|
|
71
|
+
let boxWidth = $state(0);
|
|
72
|
+
let boxHeight = $state(0);
|
|
73
|
+
let devicePixelRatio = $state(1);
|
|
74
|
+
// Bumped by window `resize` so the draw effect re-runs even when neither the
|
|
75
|
+
// box nor the entry changed (belt and braces alongside the ResizeObserver).
|
|
76
|
+
let redrawTick = $state(0);
|
|
77
|
+
|
|
78
|
+
// Track the display's devicePixelRatio: a resolution media query fires
|
|
79
|
+
// exactly when the DPR leaves the tracked value (browser zoom, moving the
|
|
80
|
+
// window to another display). The tracked state must be READ first — a
|
|
81
|
+
// state the effect writes before reading is deliberately not a dependency
|
|
82
|
+
// in Svelte, so a write-first version would never re-arm the query after
|
|
83
|
+
// the first transition. Reconciling via update() converges in one extra
|
|
84
|
+
// run when the real DPR drifted (mount, or a change event).
|
|
85
|
+
$effect(() => {
|
|
86
|
+
const trackedRatio = devicePixelRatio;
|
|
87
|
+
const query = window.matchMedia(`(resolution: ${trackedRatio}dppx)`);
|
|
88
|
+
const update = () => {
|
|
89
|
+
devicePixelRatio = window.devicePixelRatio || 1;
|
|
90
|
+
};
|
|
91
|
+
update();
|
|
92
|
+
query.addEventListener('change', update);
|
|
93
|
+
return () => query.removeEventListener('change', update);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
$effect(() => {
|
|
97
|
+
const element = root;
|
|
98
|
+
if (!element) return;
|
|
99
|
+
return observeBorderBoxSize(element, (width, height) => {
|
|
100
|
+
boxWidth = width;
|
|
101
|
+
boxHeight = height;
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const emptyLayout: CanvasPaintLayout = {
|
|
106
|
+
renderWidth: 0,
|
|
107
|
+
renderHeight: 0,
|
|
108
|
+
cssLeft: 0,
|
|
109
|
+
cssTop: 0,
|
|
110
|
+
cssWidth: 0,
|
|
111
|
+
cssHeight: 0,
|
|
112
|
+
backingWidth: 0,
|
|
113
|
+
backingHeight: 0,
|
|
114
|
+
originX: 0,
|
|
115
|
+
originY: 0
|
|
116
|
+
};
|
|
117
|
+
// The ratio the backing store and every draw use; the css placement is
|
|
118
|
+
// unaffected, so the compositor bridges any difference on the whole texture.
|
|
119
|
+
const renderRatio = $derived.by(() => {
|
|
120
|
+
const cap = Math.max(1, Math.floor(maxDpr));
|
|
121
|
+
return Math.min(cap, Math.max(1, Math.floor(devicePixelRatio)));
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const layout = $derived.by(() => {
|
|
125
|
+
const current = snapshot;
|
|
126
|
+
return current
|
|
127
|
+
? computeCanvasPaintLayout(current.entry, boxWidth, boxHeight, renderRatio)
|
|
128
|
+
: emptyLayout;
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Redraw only when a committed snapshot, the box, DPR, or window changes.
|
|
132
|
+
$effect(() => {
|
|
133
|
+
const current = snapshot;
|
|
134
|
+
const currentLayout = layout;
|
|
135
|
+
void redrawTick;
|
|
136
|
+
draw(current, currentLayout);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
function draw(current: LoadedSliceSnapshot | null, currentLayout: CanvasPaintLayout): void {
|
|
140
|
+
const element = canvas;
|
|
141
|
+
if (!element || current?.entry.slicing.mode === 'one') return;
|
|
142
|
+
if (!current || boxWidth === 0 || boxHeight === 0) {
|
|
143
|
+
element.width = 0;
|
|
144
|
+
element.height = 0;
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (isDetachedSliceSource(current.image)) {
|
|
148
|
+
// A preload release closed the retained bitmap while this slice stayed
|
|
149
|
+
// mounted. Keep the last painted pixels and reload asynchronously.
|
|
150
|
+
sourceLifecycle.request(src);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
element.width = currentLayout.backingWidth;
|
|
155
|
+
element.height = currentLayout.backingHeight;
|
|
156
|
+
|
|
157
|
+
const context = element.getContext('2d');
|
|
158
|
+
if (!context) return;
|
|
159
|
+
// Origin at the layout box's top-left, so the (possibly negative) cell
|
|
160
|
+
// coordinates from computeRenderCells apply directly.
|
|
161
|
+
context.setTransform(
|
|
162
|
+
renderRatio,
|
|
163
|
+
0,
|
|
164
|
+
0,
|
|
165
|
+
renderRatio,
|
|
166
|
+
currentLayout.originX,
|
|
167
|
+
currentLayout.originY
|
|
168
|
+
);
|
|
169
|
+
context.clearRect(
|
|
170
|
+
currentLayout.cssLeft,
|
|
171
|
+
currentLayout.cssTop,
|
|
172
|
+
currentLayout.cssWidth,
|
|
173
|
+
currentLayout.cssHeight
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
for (const cell of computeRenderCells(
|
|
177
|
+
current.entry,
|
|
178
|
+
currentLayout.renderWidth,
|
|
179
|
+
currentLayout.renderHeight
|
|
180
|
+
)) {
|
|
181
|
+
context.drawImage(
|
|
182
|
+
current.image,
|
|
183
|
+
cell.sx,
|
|
184
|
+
cell.sy,
|
|
185
|
+
cell.sw,
|
|
186
|
+
cell.sh,
|
|
187
|
+
cell.dx,
|
|
188
|
+
cell.dy,
|
|
189
|
+
cell.dw,
|
|
190
|
+
cell.dh
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
39
194
|
</script>
|
|
40
195
|
|
|
41
|
-
{
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
{
|
|
196
|
+
<svelte:window onresize={() => (redrawTick += 1)} />
|
|
197
|
+
|
|
198
|
+
<div
|
|
199
|
+
bind:this={root}
|
|
200
|
+
class="fundus-slice {className}"
|
|
201
|
+
role={snapshot?.entry.slicing.mode === 'one' ? undefined : alt ? 'img' : undefined}
|
|
202
|
+
aria-label={snapshot?.entry.slicing.mode === 'one' ? undefined : alt || undefined}
|
|
203
|
+
>
|
|
204
|
+
{#if snapshot?.entry.slicing.mode === 'one'}
|
|
205
|
+
<img
|
|
206
|
+
src={snapshot.entry.src}
|
|
207
|
+
{alt}
|
|
208
|
+
style:left="{layout.cssLeft}px"
|
|
209
|
+
style:top="{layout.cssTop}px"
|
|
210
|
+
style:width="{layout.cssWidth}px"
|
|
211
|
+
style:height="{layout.cssHeight}px"
|
|
212
|
+
/>
|
|
213
|
+
{:else if snapshot}
|
|
214
|
+
<canvas
|
|
215
|
+
bind:this={canvas}
|
|
216
|
+
style:left="{layout.cssLeft}px"
|
|
217
|
+
style:top="{layout.cssTop}px"
|
|
218
|
+
style:width="{layout.cssWidth}px"
|
|
219
|
+
style:height="{layout.cssHeight}px"
|
|
220
|
+
></canvas>
|
|
221
|
+
{/if}
|
|
222
|
+
</div>
|
|
223
|
+
|
|
224
|
+
<style>
|
|
225
|
+
.fundus-slice {
|
|
226
|
+
position: relative;
|
|
227
|
+
width: 100%;
|
|
228
|
+
height: 100%;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
canvas,
|
|
232
|
+
img {
|
|
233
|
+
position: absolute;
|
|
234
|
+
display: block;
|
|
235
|
+
pointer-events: none;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
img {
|
|
239
|
+
max-width: none;
|
|
240
|
+
max-height: none;
|
|
241
|
+
}
|
|
242
|
+
</style>
|
|
@@ -2,11 +2,10 @@ import type { SliceManifestEntry } from '../../core/index.js';
|
|
|
2
2
|
interface Props {
|
|
3
3
|
src: SliceManifestEntry;
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* with a device-pixel-ratio-sized backing store.
|
|
5
|
+
* Upper bound of the canvas backing-store ratio (floored itself). The
|
|
6
|
+
* default of 2 is rarely distinguishable and keeps backing memory bounded.
|
|
8
7
|
*/
|
|
9
|
-
|
|
8
|
+
maxDpr?: number;
|
|
10
9
|
/** Accessible label for the rendered graphic. */
|
|
11
10
|
alt?: string;
|
|
12
11
|
class?: string;
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
<script lang="ts">
|
|
8
8
|
import type { VideoManifestEntry } from '../../core/index.js';
|
|
9
9
|
import { resolveRetainedSource } from '../preload-registry.js';
|
|
10
|
+
import { logRender } from '../logging.js';
|
|
10
11
|
|
|
11
12
|
interface Props {
|
|
12
13
|
src: VideoManifestEntry;
|
|
@@ -35,6 +36,8 @@
|
|
|
35
36
|
|
|
36
37
|
const source = $derived(resolveRetainedSource(src.src));
|
|
37
38
|
|
|
39
|
+
$effect(() => logRender(src));
|
|
40
|
+
|
|
38
41
|
$effect(() => {
|
|
39
42
|
const video = videoElement;
|
|
40
43
|
if (!video) return;
|
|
@@ -58,22 +58,27 @@ export declare function effectiveOverdraw(entry: SliceManifestEntry): SliceInset
|
|
|
58
58
|
*/
|
|
59
59
|
export declare function computeOverdrawDestination(entry: SliceManifestEntry, boxWidth: number, boxHeight: number): SliceInsets;
|
|
60
60
|
/**
|
|
61
|
-
* How the
|
|
61
|
+
* How the slice renderer's `<canvas>` relates to the layout box: with overdraw
|
|
62
62
|
* the canvas is enlarged by the overdraw's destination size and offset
|
|
63
63
|
* negatively, so the baked shadow/glow paints outside the layout box while the
|
|
64
64
|
* wrapper (and therefore the layout) keeps the box size. The backing store is
|
|
65
|
-
* the CSS size ×
|
|
66
|
-
*
|
|
67
|
-
*
|
|
65
|
+
* the rounded virtual CSS size × the integer render ratio. This matches the
|
|
66
|
+
* integer final edges from `computeRenderCells`; the browser scales the
|
|
67
|
+
* completed texture to the possibly fractional CSS size. The paint origin sits
|
|
68
|
+
* at the layout box's top-left in backing pixels — so the (possibly negative)
|
|
69
|
+
* cell coordinates from `computeRenderCells` apply directly.
|
|
68
70
|
*/
|
|
69
71
|
export interface CanvasPaintLayout {
|
|
72
|
+
/** Integer virtual layout-box size used by `computeRenderCells`. */
|
|
73
|
+
renderWidth: number;
|
|
74
|
+
renderHeight: number;
|
|
70
75
|
/** CSS offset of the canvas within the wrapper (0 or negative — overdraw). */
|
|
71
76
|
cssLeft: number;
|
|
72
77
|
cssTop: number;
|
|
73
|
-
/** CSS size of the canvas
|
|
78
|
+
/** CSS size of the scaled canvas, including overdraw. */
|
|
74
79
|
cssWidth: number;
|
|
75
80
|
cssHeight: number;
|
|
76
|
-
/** Backing-store size in
|
|
81
|
+
/** Backing-store size in pixels (rounded virtual CSS size × render ratio). */
|
|
77
82
|
backingWidth: number;
|
|
78
83
|
backingHeight: number;
|
|
79
84
|
/** Paint origin (the layout box's top-left) in device pixels. */
|
|
@@ -82,10 +87,11 @@ export interface CanvasPaintLayout {
|
|
|
82
87
|
}
|
|
83
88
|
/**
|
|
84
89
|
* The canvas geometry for a slice filling a `boxWidth × boxHeight` layout box
|
|
85
|
-
* at the given
|
|
86
|
-
* scale). Pure — the
|
|
90
|
+
* at the given integer render ratio (the floored/capped display DPR, not the
|
|
91
|
+
* asset's export scale). Pure — the slice renderer derives every size and
|
|
92
|
+
* offset from this.
|
|
87
93
|
*/
|
|
88
|
-
export declare function computeCanvasPaintLayout(entry: SliceManifestEntry, boxWidth: number, boxHeight: number,
|
|
94
|
+
export declare function computeCanvasPaintLayout(entry: SliceManifestEntry, boxWidth: number, boxHeight: number, renderRatio: number): CanvasPaintLayout;
|
|
89
95
|
/**
|
|
90
96
|
* Compute the cells to paint for a slice filling a `boxWidth × boxHeight` box.
|
|
91
97
|
* Returns an empty array for a zero-size box. With overdraw the cells cover the
|
package/dist/runtime/geometry.js
CHANGED
|
@@ -85,23 +85,30 @@ export function computeOverdrawDestination(entry, boxWidth, boxHeight) {
|
|
|
85
85
|
}
|
|
86
86
|
/**
|
|
87
87
|
* The canvas geometry for a slice filling a `boxWidth × boxHeight` layout box
|
|
88
|
-
* at the given
|
|
89
|
-
* scale). Pure — the
|
|
88
|
+
* at the given integer render ratio (the floored/capped display DPR, not the
|
|
89
|
+
* asset's export scale). Pure — the slice renderer derives every size and
|
|
90
|
+
* offset from this.
|
|
90
91
|
*/
|
|
91
|
-
export function computeCanvasPaintLayout(entry, boxWidth, boxHeight,
|
|
92
|
-
const
|
|
93
|
-
const
|
|
94
|
-
const
|
|
92
|
+
export function computeCanvasPaintLayout(entry, boxWidth, boxHeight, renderRatio) {
|
|
93
|
+
const renderWidth = boxWidth > 0 ? Math.max(1, Math.round(boxWidth)) : 0;
|
|
94
|
+
const renderHeight = boxHeight > 0 ? Math.max(1, Math.round(boxHeight)) : 0;
|
|
95
|
+
const overdrawDestination = computeOverdrawDestination(entry, renderWidth, renderHeight);
|
|
96
|
+
const virtualWidth = renderWidth + overdrawDestination.left + overdrawDestination.right;
|
|
97
|
+
const virtualHeight = renderHeight + overdrawDestination.top + overdrawDestination.bottom;
|
|
98
|
+
const horizontalScale = renderWidth > 0 ? boxWidth / renderWidth : 1;
|
|
99
|
+
const verticalScale = renderHeight > 0 ? boxHeight / renderHeight : 1;
|
|
95
100
|
return {
|
|
101
|
+
renderWidth,
|
|
102
|
+
renderHeight,
|
|
96
103
|
// `+ 0` normalizes the negative zero a zero overdraw would produce.
|
|
97
|
-
cssLeft: -overdrawDestination.left + 0,
|
|
98
|
-
cssTop: -overdrawDestination.top + 0,
|
|
99
|
-
cssWidth,
|
|
100
|
-
cssHeight,
|
|
101
|
-
backingWidth:
|
|
102
|
-
backingHeight:
|
|
103
|
-
originX: overdrawDestination.left *
|
|
104
|
-
originY: overdrawDestination.top *
|
|
104
|
+
cssLeft: -overdrawDestination.left * horizontalScale + 0,
|
|
105
|
+
cssTop: -overdrawDestination.top * verticalScale + 0,
|
|
106
|
+
cssWidth: virtualWidth * horizontalScale,
|
|
107
|
+
cssHeight: virtualHeight * verticalScale,
|
|
108
|
+
backingWidth: virtualWidth * renderRatio,
|
|
109
|
+
backingHeight: virtualHeight * renderRatio,
|
|
110
|
+
originX: overdrawDestination.left * renderRatio,
|
|
111
|
+
originY: overdrawDestination.top * renderRatio
|
|
105
112
|
};
|
|
106
113
|
}
|
|
107
114
|
/** Cumulative, rounded edges so adjacent cells abut exactly on integer pixels. */
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -5,5 +5,8 @@ export { default as Video } from './components/Video.svelte';
|
|
|
5
5
|
export { default as ChromaKeyVideo } from './components/ChromaKeyVideo.svelte';
|
|
6
6
|
export { resolveRetainedSource } from './preload-registry.js';
|
|
7
7
|
export { audioSourceOf } from './kind-handlers.js';
|
|
8
|
+
export { setFundusLogger } from './logging.js';
|
|
9
|
+
export type { FundusLogger, RenderedSource } from './logging.js';
|
|
10
|
+
export type { SourceStatus } from './preload-registry.js';
|
|
8
11
|
export type { ImageManifestEntry, SliceManifestEntry, VideoManifestEntry, ChromaKeyVideoManifestEntry, AudioManifestEntry, ManifestEntry, ChromaKeyParameters, ImagePreloadOptions, SlicePreloadOptions, VideoPreloadOptions, ChromaKeyVideoPreloadOptions, AudioPreloadOptions, PreloadOptions } from '../core/index.js';
|
|
9
12
|
export { DEFAULT_CHROMA_KEY_PARAMETERS } from '../core/index.js';
|
package/dist/runtime/index.js
CHANGED
|
@@ -6,4 +6,5 @@ export { default as Video } from './components/Video.svelte';
|
|
|
6
6
|
export { default as ChromaKeyVideo } from './components/ChromaKeyVideo.svelte';
|
|
7
7
|
export { resolveRetainedSource } from './preload-registry.js';
|
|
8
8
|
export { audioSourceOf } from './kind-handlers.js';
|
|
9
|
+
export { setFundusLogger } from './logging.js';
|
|
9
10
|
export { DEFAULT_CHROMA_KEY_PARAMETERS } from '../core/index.js';
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The runtime part of the type-plugin contract: per-kind preload
|
|
3
3
|
* strategy and its inverse release strategy. Each first-party kind registers
|
|
4
|
-
* a handler that knows how its delivered file is loaded —
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* a handler that knows how its delivered file is loaded — images fetch +
|
|
5
|
+
* optionally decode, slices fetch and retain a decoded form (an ImageBitmap
|
|
6
|
+
* for multi-cell slices, the decoded element for one-slices), media kinds
|
|
7
|
+
* (video/audio) fetch fully and mint an object URL — and how what it loaded
|
|
8
|
+
* is disposed (revoke the object URL, close the bitmap; images have nothing
|
|
9
|
+
* to dispose). An unknown kind falls back to a plain full fetch: no decode,
|
|
10
|
+
* no retention.
|
|
9
11
|
*
|
|
10
12
|
* Handlers are pure strategy: refcounting, cross-manifest dedup, and
|
|
11
13
|
* in-flight bookkeeping live in the module-wide registry
|
|
@@ -16,12 +18,20 @@ import type { AudioManifestEntry, PreloadOptions } from '../core/index.js';
|
|
|
16
18
|
export declare function audioSourceOf(entry: AudioManifestEntry): string;
|
|
17
19
|
/**
|
|
18
20
|
* What a handler's `preload` acquired and its `release` must dispose. For
|
|
19
|
-
* retained media kinds this is the minted object URL
|
|
20
|
-
* (image,
|
|
21
|
+
* retained media kinds this is the minted object URL, for slices the decoded
|
|
22
|
+
* bitmap; cache-backed kinds (image, unknown) hold no disposable resources.
|
|
21
23
|
*/
|
|
22
24
|
export interface KindHandlerResources {
|
|
23
25
|
/** Object URL minted over the fully fetched file (retained media kinds). */
|
|
24
26
|
objectUrl?: string;
|
|
27
|
+
/** Decoded pixels of a multi-cell slice source, pinned against cache eviction. */
|
|
28
|
+
imageBitmap?: ImageBitmap;
|
|
29
|
+
/**
|
|
30
|
+
* Decoded element of a one-slice source: the synchronous-commit token for
|
|
31
|
+
* `SliceSourceLifecycle`, whose decode also warms the (evictable) browser
|
|
32
|
+
* cache the mounted `<img>` paints from. Nothing to dispose.
|
|
33
|
+
*/
|
|
34
|
+
image?: HTMLImageElement;
|
|
25
35
|
}
|
|
26
36
|
/** How one asset kind preloads — and releases — its delivered file. */
|
|
27
37
|
export interface KindRuntimeHandler {
|
|
@@ -29,12 +39,14 @@ export interface KindRuntimeHandler {
|
|
|
29
39
|
* Fully load one source of this kind — the promise resolves only once the
|
|
30
40
|
* file is completely transferred (plus the kind's strategy: decoded /
|
|
31
41
|
* retained as an object URL), its body matches the build-time `byteSize`,
|
|
32
|
-
* and return the resources `release` disposes.
|
|
42
|
+
* and return the resources `release` disposes. `entry` is the manifest
|
|
43
|
+
* entry the source came from, for strategies keyed on entry metadata.
|
|
33
44
|
*/
|
|
34
|
-
preload(url: string, byteSize: number, options: PreloadOptions): Promise<KindHandlerResources>;
|
|
45
|
+
preload(url: string, byteSize: number, options: PreloadOptions, entry?: Record<string, unknown>): Promise<KindHandlerResources>;
|
|
35
46
|
/**
|
|
36
47
|
* The inverse of `preload`: dispose the resources it returned (revoke the
|
|
37
|
-
* object URL). Omitted = the kind acquires nothing
|
|
48
|
+
* object URL, close the bitmap). Omitted = the kind acquires nothing
|
|
49
|
+
* disposable.
|
|
38
50
|
*/
|
|
39
51
|
release?(resources: KindHandlerResources): void;
|
|
40
52
|
}
|
|
@@ -66,28 +66,65 @@ function verifyByteSize(url, expected, actual) {
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
/**
|
|
69
|
-
* The image
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
69
|
+
* The image strategy: fetch the full file, then decode by default. Decoding
|
|
70
|
+
* goes via an HTMLImageElement where a DOM exists (the browser serves the
|
|
71
|
+
* bytes from cache); headless environments (Node tests, SSR) skip decoding.
|
|
72
|
+
* Nothing to release — the browser cache owns the bytes, and the mounted
|
|
73
|
+
* `<img>` decodes from it.
|
|
73
74
|
*/
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
75
|
+
const imageHandler = {
|
|
76
|
+
async preload(url, byteSize, options) {
|
|
77
|
+
const response = await fetchCompletely(url);
|
|
78
|
+
// Read the body fully so the file is actually transferred (and
|
|
79
|
+
// cached), not just the response headers.
|
|
80
|
+
const bytes = await response.arrayBuffer();
|
|
81
|
+
verifyByteSize(url, byteSize, bytes.byteLength);
|
|
82
|
+
if (shouldDecode('image', options) && typeof globalThis.Image === 'function') {
|
|
83
|
+
const image = new globalThis.Image();
|
|
84
|
+
image.src = url;
|
|
85
|
+
await image.decode();
|
|
86
|
+
}
|
|
87
|
+
return {};
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* The slice strategy: fetch the full file and retain a decoded form while
|
|
92
|
+
* held, chosen by the entry's slicing mode. Multi-cell slices (nine,
|
|
93
|
+
* three-*) draw through a canvas and retain the pixels as an ImageBitmap —
|
|
94
|
+
* unlike a throwaway decode into the browser's evictable image cache, the
|
|
95
|
+
* bitmap survives memory pressure and lets `SliceSourceLifecycle` commit
|
|
96
|
+
* synchronously, so a preloaded slice paints in its mount frame. One-slice
|
|
97
|
+
* sources render as a plain `<img>`, which cannot paint from a bitmap: they
|
|
98
|
+
* retain the decoded HTMLImageElement instead — the same synchronous commit,
|
|
99
|
+
* and its decode warms the (evictable) cache the mounted `<img>` paints
|
|
100
|
+
* from. Release closes the bitmap; a retained element has nothing to
|
|
101
|
+
* dispose. Slice sources are small (the insets cover every rendered size),
|
|
102
|
+
* so retention is cheap. Environments without `createImageBitmap` retain the
|
|
103
|
+
* decoded element for every mode.
|
|
104
|
+
*/
|
|
105
|
+
const sliceHandler = {
|
|
106
|
+
async preload(url, byteSize, options, entry) {
|
|
107
|
+
const response = await fetchCompletely(url);
|
|
108
|
+
const blob = await response.blob();
|
|
109
|
+
verifyByteSize(url, byteSize, blob.size);
|
|
110
|
+
if (!shouldDecode('slice', options))
|
|
87
111
|
return {};
|
|
112
|
+
const oneSlice = entry?.slicing?.mode === 'one';
|
|
113
|
+
if (!oneSlice && typeof globalThis.createImageBitmap === 'function') {
|
|
114
|
+
return { imageBitmap: await globalThis.createImageBitmap(blob) };
|
|
88
115
|
}
|
|
89
|
-
|
|
90
|
-
|
|
116
|
+
if (typeof globalThis.Image === 'function') {
|
|
117
|
+
const image = new globalThis.Image();
|
|
118
|
+
image.src = url;
|
|
119
|
+
await image.decode();
|
|
120
|
+
return { image };
|
|
121
|
+
}
|
|
122
|
+
return {};
|
|
123
|
+
},
|
|
124
|
+
release(resources) {
|
|
125
|
+
resources.imageBitmap?.close();
|
|
126
|
+
}
|
|
127
|
+
};
|
|
91
128
|
/**
|
|
92
129
|
* The retained-media strategy (video, chroma-key-video, audio): fetch the
|
|
93
130
|
* full file and retain it as an object URL so later playback does not touch
|
|
@@ -118,8 +155,8 @@ const unknownKindHandler = {
|
|
|
118
155
|
}
|
|
119
156
|
};
|
|
120
157
|
const runtimeHandlerCatalogue = {
|
|
121
|
-
image:
|
|
122
|
-
slice:
|
|
158
|
+
image: imageHandler,
|
|
159
|
+
slice: sliceHandler,
|
|
123
160
|
video: retainedMediaHandler,
|
|
124
161
|
'chroma-key-video': retainedMediaHandler,
|
|
125
162
|
audio: retainedMediaHandler
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opt-in render-time diagnostics: a host registers one logger and every Fundus
|
|
3
|
+
* render component reports, on mount and whenever its displayed asset changes,
|
|
4
|
+
* which sources it needs and whether each was preloaded. Purely additive and
|
|
5
|
+
* gated — with no logger registered this is a single null-check, no walk, no
|
|
6
|
+
* cost. It never participates in rendering: `logRender` swallows any error
|
|
7
|
+
* (a malformed entry, a throwing logger) so diagnostics can't break the view.
|
|
8
|
+
*/
|
|
9
|
+
import type { ManifestEntry } from '../core/index.js';
|
|
10
|
+
import { type SourceStatus } from './preload-registry.js';
|
|
11
|
+
/** One rendered source and its preload state at the moment of the render. */
|
|
12
|
+
export interface RenderedSource {
|
|
13
|
+
/** The asset id the source belongs to — an inlined mask/fallback carries its own. */
|
|
14
|
+
id: string;
|
|
15
|
+
kind: string;
|
|
16
|
+
url: string;
|
|
17
|
+
status: SourceStatus;
|
|
18
|
+
}
|
|
19
|
+
export interface FundusLogger {
|
|
20
|
+
/**
|
|
21
|
+
* Reported once per component render (mount and each in-place asset swap),
|
|
22
|
+
* listing every source the component displays. A single-file kind yields one
|
|
23
|
+
* source; a chroma-key-video yields its video plus the inlined mask/fallback.
|
|
24
|
+
* Sources are not deduped — a url shared by two of them appears twice.
|
|
25
|
+
*/
|
|
26
|
+
onRender(sources: RenderedSource[]): void;
|
|
27
|
+
}
|
|
28
|
+
/** Register (or clear, with `null`) the process-wide render logger. */
|
|
29
|
+
export declare function setFundusLogger(logger: FundusLogger | null): void;
|
|
30
|
+
/**
|
|
31
|
+
* Report the sources a component is rendering and their current preload state.
|
|
32
|
+
* A no-op without a registered logger; never throws.
|
|
33
|
+
*/
|
|
34
|
+
export declare function logRender(entry: ManifestEntry): void;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { collectEntrySources } from './manifest.js';
|
|
2
|
+
import { sourceStatus } from './preload-registry.js';
|
|
3
|
+
let activeLogger = null;
|
|
4
|
+
/** Register (or clear, with `null`) the process-wide render logger. */
|
|
5
|
+
export function setFundusLogger(logger) {
|
|
6
|
+
activeLogger = logger;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Report the sources a component is rendering and their current preload state.
|
|
10
|
+
* A no-op without a registered logger; never throws.
|
|
11
|
+
*/
|
|
12
|
+
export function logRender(entry) {
|
|
13
|
+
if (!activeLogger)
|
|
14
|
+
return;
|
|
15
|
+
try {
|
|
16
|
+
activeLogger.onRender(collectEntrySources(entry).map((source) => ({
|
|
17
|
+
id: source.id,
|
|
18
|
+
kind: source.kind,
|
|
19
|
+
url: source.url,
|
|
20
|
+
status: sourceStatus(source.url)
|
|
21
|
+
})));
|
|
22
|
+
}
|
|
23
|
+
catch (cause) {
|
|
24
|
+
// Diagnostics must never break rendering.
|
|
25
|
+
console.warn('fundus: render logging failed', cause);
|
|
26
|
+
}
|
|
27
|
+
}
|