fundus 0.26.0 → 0.28.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/README.md +18 -4
- package/build/client/_app/immutable/assets/{3.OulZ54CG.css → 3.Dfl33dj0.css} +1 -1
- package/build/client/_app/immutable/assets/3.Dfl33dj0.css.br +0 -0
- package/build/client/_app/immutable/assets/3.Dfl33dj0.css.gz +0 -0
- package/build/client/_app/immutable/chunks/{B_4qwt4o.js → BSzEzOBI.js} +1 -1
- package/build/client/_app/immutable/chunks/BSzEzOBI.js.br +0 -0
- package/build/client/_app/immutable/chunks/BSzEzOBI.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CRFKGnag.js +121 -0
- package/build/client/_app/immutable/chunks/CRFKGnag.js.br +0 -0
- package/build/client/_app/immutable/chunks/CRFKGnag.js.gz +0 -0
- package/build/client/_app/immutable/chunks/apKHI4vq.js +2 -0
- package/build/client/_app/immutable/chunks/apKHI4vq.js.br +0 -0
- package/build/client/_app/immutable/chunks/apKHI4vq.js.gz +0 -0
- package/build/client/_app/immutable/chunks/g2rma9Ym.js +1 -0
- package/build/client/_app/immutable/chunks/g2rma9Ym.js.br +0 -0
- package/build/client/_app/immutable/chunks/g2rma9Ym.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{DS1jTxvm.js → rtFCvv3j.js} +1 -1
- package/build/client/_app/immutable/chunks/rtFCvv3j.js.br +0 -0
- package/build/client/_app/immutable/chunks/rtFCvv3j.js.gz +0 -0
- package/build/client/_app/immutable/entry/{app.DgW6Eg19.js → app.BiYJKHq5.js} +2 -2
- package/build/client/_app/immutable/entry/app.BiYJKHq5.js.br +0 -0
- package/build/client/_app/immutable/entry/app.BiYJKHq5.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.BGYYpgAJ.js +1 -0
- package/build/client/_app/immutable/entry/start.BGYYpgAJ.js.br +0 -0
- package/build/client/_app/immutable/entry/start.BGYYpgAJ.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.kL9OGUbr.js +1 -0
- package/build/client/_app/immutable/nodes/0.kL9OGUbr.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.kL9OGUbr.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{1.CoM7ng4c.js → 1.DQGC5ZQg.js} +1 -1
- package/build/client/_app/immutable/nodes/1.DQGC5ZQg.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.DQGC5ZQg.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{2.CV-vWxFI.js → 2.DXo6NZ8U.js} +2 -2
- package/build/client/_app/immutable/nodes/2.DXo6NZ8U.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.DXo6NZ8U.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.DkyIyoNd.js +1 -0
- package/build/client/_app/immutable/nodes/3.DkyIyoNd.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.DkyIyoNd.js.gz +0 -0
- package/build/client/_app/immutable/nodes/4.D1JY4Ttk.js +2 -0
- package/build/client/_app/immutable/nodes/4.D1JY4Ttk.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.D1JY4Ttk.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-mE8mFnsg.js → context.js-BZSZNuAd.js} +5 -5
- package/build/server/chunks/chunks/{context.js-mE8mFnsg.js.map → context.js-BZSZNuAd.js.map} +1 -1
- package/build/server/chunks/chunks/{contract.js-B6KBK8D9.js → contract.js-B4JdXFgm.js} +33 -38
- package/build/server/chunks/chunks/contract.js-B4JdXFgm.js.map +1 -0
- package/build/server/chunks/chunks/{core.js-Bp-ReC60.js → core.js-BHxa3VBo.js} +40 -5
- package/build/server/chunks/chunks/core.js-BHxa3VBo.js.map +1 -0
- package/build/server/chunks/chunks/{exports.js-CDl9URFG.js → exports.js-Cncv1qoG.js} +2 -2
- package/build/server/chunks/chunks/{exports.js-CDl9URFG.js.map → exports.js-Cncv1qoG.js.map} +1 -1
- package/build/server/chunks/chunks/{http.js-BTq44nR5.js → http.js-DYYKvppy.js} +2 -2
- package/build/server/chunks/chunks/{http.js-BTq44nR5.js.map → http.js-DYYKvppy.js.map} +1 -1
- package/build/server/chunks/chunks/{internal.js-Bz3sw0lq.js → internal.js-C2FKw_9R.js} +3 -3
- package/build/server/chunks/chunks/{internal.js-Bz3sw0lq.js.map → internal.js-C2FKw_9R.js.map} +1 -1
- package/build/server/chunks/chunks/internal2.js-BlXt2rfE.js.map +1 -1
- package/build/server/chunks/chunks/{manifest-membership.js-Dj3WwZgz.js → manifest-membership.js-BkrPsM_r.js} +63 -4
- package/build/server/chunks/chunks/manifest-membership.js-BkrPsM_r.js.map +1 -0
- package/build/server/chunks/chunks/{raw-reconciliation.js-mWOAOdgL.js → raw-reconciliation.js-DPnNXe0b.js} +3 -3
- package/build/server/chunks/chunks/raw-reconciliation.js-DPnNXe0b.js.map +1 -0
- package/build/server/chunks/chunks/{raw-reconciliation2.js-Bo1jJgvd.js → raw-reconciliation2.js-DKOpJ8if.js} +2 -2
- package/build/server/chunks/chunks/{raw-reconciliation2.js-Bo1jJgvd.js.map → raw-reconciliation2.js-DKOpJ8if.js.map} +1 -1
- package/build/server/chunks/chunks/{shared.js-DwJl8ZNw.js → shared.js-BGsWd7z-.js} +2 -2
- package/build/server/chunks/chunks/{shared.js-DwJl8ZNw.js.map → shared.js-BGsWd7z-.js.map} +1 -1
- package/build/server/chunks/chunks/{utils.js-DoU5Jolh.js → utils.js-C0ADATvf.js} +3 -3
- package/build/server/chunks/chunks/{utils.js-DoU5Jolh.js.map → utils.js-C0ADATvf.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/_...path_/{_server.ts.js-B_aa_zh7.js → _server.ts.js-Dw2MW5Tm.js} +6 -6
- package/build/server/chunks/entries/endpoints/api/_...path_/{_server.ts.js-B_aa_zh7.js.map → _server.ts.js-Dw2MW5Tm.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/_id_/{_server.ts.js-CM6PedpD.js → _server.ts.js-GQDuJGug.js} +7 -7
- package/build/server/chunks/entries/endpoints/api/assets/_id_/{_server.ts.js-CM6PedpD.js.map → _server.ts.js-GQDuJGug.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/_id_/raw/{_server.ts.js-BpfOlmKN.js → _server.ts.js-JbLSv7Xx.js} +7 -7
- package/build/server/chunks/entries/endpoints/api/assets/_id_/raw/{_server.ts.js-BpfOlmKN.js.map → _server.ts.js-JbLSv7Xx.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/_id_/reimport/{_server.ts.js-UvDSeD5O.js → _server.ts.js-BPae-BYF.js} +7 -7
- package/build/server/chunks/entries/endpoints/api/assets/_id_/reimport/{_server.ts.js-UvDSeD5O.js.map → _server.ts.js-BPae-BYF.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/_id_/retag/{_server.ts.js-D4kq3W1-.js → _server.ts.js-BafrrUxu.js} +7 -7
- package/build/server/chunks/entries/endpoints/api/assets/_id_/retag/{_server.ts.js-D4kq3W1-.js.map → _server.ts.js-BafrrUxu.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/{_server.ts.js-CcV2BTCH.js → _server.ts.js-UOlSNEi9.js} +7 -7
- package/build/server/chunks/entries/endpoints/api/assets/{_server.ts.js-CcV2BTCH.js.map → _server.ts.js-UOlSNEi9.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/delete/{_server.ts.js-Cb0Dn_a2.js → _server.ts.js-QE1vUuAx.js} +7 -7
- package/build/server/chunks/entries/endpoints/api/assets/delete/{_server.ts.js-Cb0Dn_a2.js.map → _server.ts.js-QE1vUuAx.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/move/{_server.ts.js-ChlfFRqR.js → _server.ts.js-DIVfppGt.js} +7 -7
- package/build/server/chunks/entries/endpoints/api/assets/move/{_server.ts.js-ChlfFRqR.js.map → _server.ts.js-DIVfppGt.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/events/{_server.ts.js-DnrgWGff.js → _server.ts.js-B6R_kqP8.js} +7 -7
- package/build/server/chunks/entries/endpoints/api/events/{_server.ts.js-DnrgWGff.js.map → _server.ts.js-B6R_kqP8.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/folders/_path_/{_server.ts.js-CFM83bne.js → _server.ts.js-CertbDsH.js} +7 -7
- package/build/server/chunks/entries/endpoints/api/folders/_path_/{_server.ts.js-CFM83bne.js.map → _server.ts.js-CertbDsH.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/folders/{_server.ts.js-BtJIovEG.js → _server.ts.js-DEIF3Hu2.js} +7 -7
- package/build/server/chunks/entries/endpoints/api/folders/{_server.ts.js-BtJIovEG.js.map → _server.ts.js-DEIF3Hu2.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/imports/{_server.ts.js-BAHE77r2.js → _server.ts.js-DNFsNGr4.js} +7 -7
- package/build/server/chunks/entries/endpoints/api/imports/{_server.ts.js-BAHE77r2.js.map → _server.ts.js-DNFsNGr4.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/manifests/_name_/{_server.ts.js-Cypw-CjT.js → _server.ts.js-DDW8XRPe.js} +7 -7
- package/build/server/chunks/entries/endpoints/api/manifests/_name_/{_server.ts.js-Cypw-CjT.js.map → _server.ts.js-DDW8XRPe.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/manifests/{_server.ts.js-FrntF4Gn.js → _server.ts.js-C222-xli.js} +7 -7
- package/build/server/chunks/entries/endpoints/api/manifests/{_server.ts.js-FrntF4Gn.js.map → _server.ts.js-C222-xli.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/raw/adopt/{_server.ts.js-BeEHDBep.js → _server.ts.js-CI1P1uyd.js} +7 -7
- package/build/server/chunks/entries/endpoints/api/raw/adopt/{_server.ts.js-BeEHDBep.js.map → _server.ts.js-CI1P1uyd.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/raw/edited/accept/{_server.ts.js-Di1kfrl2.js → _server.ts.js-DpiUwaSh.js} +7 -7
- package/build/server/chunks/entries/endpoints/api/raw/edited/accept/{_server.ts.js-Di1kfrl2.js.map → _server.ts.js-DpiUwaSh.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/raw/edited/dismiss/{_server.ts.js-CrLHlnap.js → _server.ts.js-DT0VPqPT.js} +7 -7
- package/build/server/chunks/entries/endpoints/api/raw/edited/dismiss/{_server.ts.js-CrLHlnap.js.map → _server.ts.js-DT0VPqPT.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/reconcile/{_server.ts.js-BcqKQTDk.js → _server.ts.js-DBpHM42g.js} +7 -7
- package/build/server/chunks/entries/endpoints/api/reconcile/{_server.ts.js-BcqKQTDk.js.map → _server.ts.js-DBpHM42g.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/reveal/{_server.ts.js-oFUf-T_H.js → _server.ts.js-Cxu5b6Au.js} +7 -7
- package/build/server/chunks/entries/endpoints/api/reveal/{_server.ts.js-oFUf-T_H.js.map → _server.ts.js-Cxu5b6Au.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/state/{_server.ts.js-D46dsJCB.js → _server.ts.js-W4dhMo8Y.js} +7 -7
- package/build/server/chunks/entries/endpoints/api/state/{_server.ts.js-D46dsJCB.js.map → _server.ts.js-W4dhMo8Y.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/usage/{_server.ts.js-BlKjsU-v.js → _server.ts.js-DJ_adxbT.js} +7 -7
- package/build/server/chunks/entries/endpoints/api/usage/{_server.ts.js-BlKjsU-v.js.map → _server.ts.js-DJ_adxbT.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/files/_...path_/{_server.ts.js-DkYQlG9U.js → _server.ts.js-BisoHihD.js} +6 -6
- package/build/server/chunks/entries/endpoints/files/_...path_/{_server.ts.js-DkYQlG9U.js.map → _server.ts.js-BisoHihD.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/files/proxy/_...path_/{_server.ts.js-DafDgMis.js → _server.ts.js-D4jLLuko.js} +7 -7
- package/build/server/chunks/entries/endpoints/files/proxy/_...path_/{_server.ts.js-DafDgMis.js.map → _server.ts.js-D4jLLuko.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/files/raw/_...path_/{_server.ts.js-6Vn2UuF6.js → _server.ts.js-Dfv2ItjT.js} +7 -7
- package/build/server/chunks/entries/endpoints/files/raw/_...path_/{_server.ts.js-6Vn2UuF6.js.map → _server.ts.js-Dfv2ItjT.js.map} +1 -1
- package/build/server/chunks/entries/{hooks.server.js-CSypLuCa.js → hooks.server.js-oHJX5INm.js} +6 -6
- package/build/server/chunks/entries/{hooks.server.js-CSypLuCa.js.map → hooks.server.js-oHJX5INm.js.map} +1 -1
- package/build/server/chunks/{handler-C0NXVVDu.js → handler-uc4GLvWI.js} +5 -5
- package/build/server/chunks/{handler-C0NXVVDu.js.map → handler-uc4GLvWI.js.map} +1 -1
- package/build/server/chunks/{index.js-D3rzqgKi.js → index.js-BTlSiK4q.js} +5 -5
- package/build/server/chunks/{index.js-D3rzqgKi.js.map → index.js-BTlSiK4q.js.map} +1 -1
- package/build/server/chunks/{manifest.js-Dw3Fvt_0.js → manifest.js-BB8iIVbZ.js} +31 -31
- package/build/server/chunks/{manifest.js-Dw3Fvt_0.js.map → manifest.js-BB8iIVbZ.js.map} +1 -1
- package/build/server/chunks/nodes/{0.js-D1uRRjFL.js → 0.js-CccZDEdX.js} +2 -2
- package/build/server/chunks/nodes/{0.js-D1uRRjFL.js.map → 0.js-CccZDEdX.js.map} +1 -1
- package/build/server/chunks/nodes/1.js-_Ms77u64.js +7 -0
- package/build/server/chunks/nodes/{1.js-sYnc9kZu.js.map → 1.js-_Ms77u64.js.map} +1 -1
- package/build/server/chunks/nodes/{2.js-B3OCWo8p.js → 2.js-BpyFsilO.js} +2 -2
- package/build/server/chunks/nodes/{2.js-B3OCWo8p.js.map → 2.js-BpyFsilO.js.map} +1 -1
- package/build/server/chunks/nodes/3.js-D7RUEviu.js +7 -0
- package/build/server/chunks/nodes/{3.js-BWkr9ibc.js.map → 3.js-D7RUEviu.js.map} +1 -1
- package/build/server/chunks/nodes/4.js-Cfc3G2Gv.js +7 -0
- package/build/server/chunks/nodes/{4.js-gujLHNff.js.map → 4.js-Cfc3G2Gv.js.map} +1 -1
- package/dist/core/asset-id.d.ts +3 -2
- package/dist/core/asset-id.js +63 -2
- package/dist/core/chroma-key.d.ts +11 -5
- package/dist/core/chroma-key.js +11 -3
- package/dist/core/figma-tag-script.d.ts +19 -6
- package/dist/core/figma-tag-script.js +35 -23
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.js +1 -1
- package/dist/core/manifest-entry.d.ts +7 -0
- package/dist/core/plugins/slice.d.ts +12 -0
- package/dist/core/plugins/slice.js +26 -0
- package/dist/runtime/chroma-key-gl.d.ts +12 -4
- package/dist/runtime/chroma-key-gl.js +63 -6
- package/dist/runtime/components/ChromaKeyVideo.svelte +2 -1
- package/dist/runtime/geometry.d.ts +4 -2
- package/dist/runtime/geometry.js +14 -12
- package/dist/server/cli-figma-tags.js +12 -5
- package/dist/server/figma-tags.js +32 -6
- package/dist/server/plugins/contract.d.ts +5 -2
- package/dist/server/plugins/slice.js +14 -31
- package/dist/server/processing/processor.d.ts +1 -1
- package/dist/server/processing/processor.js +3 -3
- package/package.json +1 -1
- package/build/client/_app/immutable/assets/3.OulZ54CG.css.br +0 -0
- package/build/client/_app/immutable/assets/3.OulZ54CG.css.gz +0 -0
- package/build/client/_app/immutable/chunks/B_4qwt4o.js.br +0 -0
- package/build/client/_app/immutable/chunks/B_4qwt4o.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Cml0-AYv.js +0 -2
- package/build/client/_app/immutable/chunks/Cml0-AYv.js.br +0 -0
- package/build/client/_app/immutable/chunks/Cml0-AYv.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DGYcFVZ3.js +0 -1
- package/build/client/_app/immutable/chunks/DGYcFVZ3.js.br +0 -0
- package/build/client/_app/immutable/chunks/DGYcFVZ3.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DS1jTxvm.js.br +0 -2
- package/build/client/_app/immutable/chunks/DS1jTxvm.js.gz +0 -0
- package/build/client/_app/immutable/chunks/p2q3f3Ml.js +0 -108
- package/build/client/_app/immutable/chunks/p2q3f3Ml.js.br +0 -0
- package/build/client/_app/immutable/chunks/p2q3f3Ml.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.DgW6Eg19.js.br +0 -0
- package/build/client/_app/immutable/entry/app.DgW6Eg19.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.D3J5OM6w.js +0 -1
- package/build/client/_app/immutable/entry/start.D3J5OM6w.js.br +0 -1
- package/build/client/_app/immutable/entry/start.D3J5OM6w.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.DdcKoZu5.js +0 -1
- package/build/client/_app/immutable/nodes/0.DdcKoZu5.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.DdcKoZu5.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.CoM7ng4c.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.CoM7ng4c.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.CV-vWxFI.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.CV-vWxFI.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.B9mDsYGS.js +0 -1
- package/build/client/_app/immutable/nodes/3.B9mDsYGS.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.B9mDsYGS.js.gz +0 -0
- package/build/client/_app/immutable/nodes/4.CFfIs-6D.js +0 -2
- package/build/client/_app/immutable/nodes/4.CFfIs-6D.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.CFfIs-6D.js.gz +0 -0
- package/build/server/chunks/chunks/contract.js-B6KBK8D9.js.map +0 -1
- package/build/server/chunks/chunks/core.js-Bp-ReC60.js.map +0 -1
- package/build/server/chunks/chunks/manifest-membership.js-Dj3WwZgz.js.map +0 -1
- package/build/server/chunks/chunks/raw-reconciliation.js-mWOAOdgL.js.map +0 -1
- package/build/server/chunks/nodes/1.js-sYnc9kZu.js +0 -7
- package/build/server/chunks/nodes/3.js-BWkr9ibc.js +0 -7
- package/build/server/chunks/nodes/4.js-gujLHNff.js +0 -7
|
@@ -121,20 +121,32 @@ export async function loadTaggableNode(figma, nodeId) {
|
|
|
121
121
|
return node;
|
|
122
122
|
}
|
|
123
123
|
/**
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
124
|
+
* Every other node carrying `tag`, across every page. Scripts start with only
|
|
125
|
+
* the first page loaded, and `findAll` on an unloaded page silently misses
|
|
126
|
+
* content, so each page is loaded first.
|
|
127
127
|
*/
|
|
128
|
-
export async function
|
|
128
|
+
export async function findTagHolders(figma, tag, exceptNodeId) {
|
|
129
|
+
const holders = [];
|
|
129
130
|
for (const page of figma.root.children) {
|
|
130
131
|
await page.loadAsync();
|
|
131
|
-
const
|
|
132
|
+
const found = page.findAll((candidate) => candidate.id !== exceptNodeId &&
|
|
132
133
|
!isInstanceSublayerId(candidate.id) &&
|
|
133
134
|
candidate.getSharedPluginData(FIGMA_TAG_NAMESPACE, FIGMA_TAG_KEY) === tag);
|
|
134
|
-
|
|
135
|
-
|
|
135
|
+
for (const node of found)
|
|
136
|
+
holders.push({ node, page });
|
|
136
137
|
}
|
|
137
|
-
return
|
|
138
|
+
return holders;
|
|
139
|
+
}
|
|
140
|
+
/** Clear the node's tag and its Fundus annotation; returns whether an annotation was removed. */
|
|
141
|
+
export function clearFundusTag(node) {
|
|
142
|
+
node.setSharedPluginData(FIGMA_TAG_NAMESPACE, FIGMA_TAG_KEY, '');
|
|
143
|
+
if (!supportsAnnotations(node)) {
|
|
144
|
+
node.setSharedPluginData(FIGMA_TAG_NAMESPACE, FIGMA_ANNOTATION_LABEL_KEY, '');
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
const before = node.annotations.length;
|
|
148
|
+
stripFundusAnnotation(node);
|
|
149
|
+
return node.annotations.length < before;
|
|
138
150
|
}
|
|
139
151
|
/**
|
|
140
152
|
* Tag a node, re-checking the live document right before writing: the CLI's
|
|
@@ -149,10 +161,13 @@ export async function setFundusTag(figma, input) {
|
|
|
149
161
|
if (!input.force && previous !== '') {
|
|
150
162
|
throw new Error(`Node "${node.name}" (${node.id}) already carries tag "${previous}"; rerun prepare-set-tag with --force to replace it.`);
|
|
151
163
|
}
|
|
152
|
-
const
|
|
153
|
-
if (
|
|
154
|
-
|
|
164
|
+
const holders = await findTagHolders(figma, input.tag, node.id);
|
|
165
|
+
if (!input.force && holders.length > 0) {
|
|
166
|
+
const [holder] = holders;
|
|
167
|
+
throw new Error(`Tag "${input.tag}" is already used by "${holder.node.name}" (${holder.node.id}) on page "${holder.page.name}"; choose another tag, or rerun prepare-set-tag with --force to move it here.`);
|
|
155
168
|
}
|
|
169
|
+
for (const holder of holders)
|
|
170
|
+
clearFundusTag(holder.node);
|
|
156
171
|
node.setSharedPluginData(FIGMA_TAG_NAMESPACE, FIGMA_TAG_KEY, input.tag);
|
|
157
172
|
const annotated = supportsAnnotations(node);
|
|
158
173
|
if (annotated)
|
|
@@ -163,7 +178,12 @@ export async function setFundusTag(figma, input) {
|
|
|
163
178
|
nodeName: node.name,
|
|
164
179
|
tag: input.tag,
|
|
165
180
|
previousTag: previous === '' ? null : previous,
|
|
166
|
-
annotated
|
|
181
|
+
annotated,
|
|
182
|
+
movedFrom: holders.map((holder) => ({
|
|
183
|
+
nodeId: holder.node.id,
|
|
184
|
+
nodeName: holder.node.name,
|
|
185
|
+
page: holder.page.name
|
|
186
|
+
}))
|
|
167
187
|
};
|
|
168
188
|
}
|
|
169
189
|
export async function deleteFundusTag(figma, input) {
|
|
@@ -174,16 +194,7 @@ export async function deleteFundusTag(figma, input) {
|
|
|
174
194
|
? `Node "${node.name}" (${node.id}) carries no Fundus tag.`
|
|
175
195
|
: `Node "${node.name}" (${node.id}) carries tag "${current}", not "${input.tag}"; rerun prepare-delete-tag.`);
|
|
176
196
|
}
|
|
177
|
-
|
|
178
|
-
let annotationRemoved = false;
|
|
179
|
-
if (supportsAnnotations(node)) {
|
|
180
|
-
const before = node.annotations.length;
|
|
181
|
-
stripFundusAnnotation(node);
|
|
182
|
-
annotationRemoved = node.annotations.length < before;
|
|
183
|
-
}
|
|
184
|
-
else {
|
|
185
|
-
node.setSharedPluginData(FIGMA_TAG_NAMESPACE, FIGMA_ANNOTATION_LABEL_KEY, '');
|
|
186
|
-
}
|
|
197
|
+
const annotationRemoved = clearFundusTag(node);
|
|
187
198
|
return {
|
|
188
199
|
ok: true,
|
|
189
200
|
nodeId: node.id,
|
|
@@ -208,7 +219,8 @@ const SCRIPT_FUNCTIONS = [
|
|
|
208
219
|
stripFundusAnnotation,
|
|
209
220
|
pageOfNode,
|
|
210
221
|
loadTaggableNode,
|
|
211
|
-
|
|
222
|
+
findTagHolders,
|
|
223
|
+
clearFundusTag,
|
|
212
224
|
setFundusTag,
|
|
213
225
|
deleteFundusTag
|
|
214
226
|
];
|
package/dist/core/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export { createEmptyLibrary, parseLibrary, serializeLibrary } from './library.js
|
|
|
3
3
|
export { rawFileNameError, rawFilePathError, portableRawPathKey, isFundusTransactionName } from './raw-path.js';
|
|
4
4
|
export { ROOT_FOLDER, FOLDER_SENTINEL, folderNameError, folderPathError, normalizeFolderPath, portableFolderPathKey, parentFolderPath, folderNameOf, joinFolderPath, folderOfRawPath, rawFileNameOfPath, isFolderOrDescendant, replaceFolderPrefix } from './folder-path.js';
|
|
5
5
|
export { RESERVED_ASSET_IDS, isValidAssetId, assetIdError, manifestNameError } from './asset-id.js';
|
|
6
|
-
export type { SetFigmaTagInput, SetFigmaTagResult, DeleteFigmaTagInput, DeleteFigmaTagResult } from './figma-tag-script.js';
|
|
6
|
+
export type { SetFigmaTagInput, SetFigmaTagResult, FigmaTagHolder, DeleteFigmaTagInput, DeleteFigmaTagResult } from './figma-tag-script.js';
|
|
7
7
|
export { buildSetTagScript, buildDeleteTagScript, isInstanceSublayerId } from './figma-tag-script.js';
|
|
8
8
|
export type { ManifestColor } from './manifest-appearance.js';
|
|
9
9
|
export { MANIFEST_COLORS, MANIFEST_EMOJIS, isManifestColor, isManifestEmoji, resolveManifestColor, manifestColorError, manifestEmojiError } from './manifest-appearance.js';
|
|
@@ -31,7 +31,7 @@ export type { AssetTypePluginDescriptor } from './plugins/contract.js';
|
|
|
31
31
|
export type { ImageFormat, ImagePreset, ImagePluginConfig, ImageParameters } from './plugins/image.js';
|
|
32
32
|
export { imagePlugin, validateImagePreset, IMAGE_BUILT_IN_PRESETS, RASTER_IMAGE_EXTENSIONS } from './plugins/image.js';
|
|
33
33
|
export type { SliceCompression, SliceParameters, SlicePluginConfig } from './plugins/slice.js';
|
|
34
|
-
export { processedSliceInsets, slicePlugin, SLICE_BUILT_IN_PRESETS } from './plugins/slice.js';
|
|
34
|
+
export { processedSliceInsets, sliceEntryGeometry, slicePlugin, SLICE_BUILT_IN_PRESETS } from './plugins/slice.js';
|
|
35
35
|
export type { CustomFfmpegVideoPreset, FfmpegArgument, FfmpegArgumentTokens, FfmpegCommandPaths, FfmpegPathToken, ResolvedCustomFfmpegVideoPreset, ResolvedVideoPreset, StructuredVideoPreset, VideoPreset, VideoPluginConfig, VideoParameters } from './plugins/video.js';
|
|
36
36
|
export { isCustomFfmpegVideoPreset, materializeFfmpegCommand, videoPlugin, validateVideoPreset, validateVideoParameters, VIDEO_BUILT_IN_PRESETS, VIDEO_EXTENSIONS } from './plugins/video.js';
|
|
37
37
|
export type { ChromaKeyVideoParameters } from './plugins/chroma-key-video.js';
|
package/dist/core/index.js
CHANGED
|
@@ -15,7 +15,7 @@ export { isAssetReference } from './asset-reference.js';
|
|
|
15
15
|
export { collectAllReferences, referenceErrors, referenceClosure, topologicalReferenceOrder } from './reference-graph.js';
|
|
16
16
|
export { computeLibraryMembership, allDeliveredAssetIds } from './manifest-membership.js';
|
|
17
17
|
export { imagePlugin, validateImagePreset, IMAGE_BUILT_IN_PRESETS, RASTER_IMAGE_EXTENSIONS } from './plugins/image.js';
|
|
18
|
-
export { processedSliceInsets, slicePlugin, SLICE_BUILT_IN_PRESETS } from './plugins/slice.js';
|
|
18
|
+
export { processedSliceInsets, sliceEntryGeometry, slicePlugin, SLICE_BUILT_IN_PRESETS } from './plugins/slice.js';
|
|
19
19
|
export { isCustomFfmpegVideoPreset, materializeFfmpegCommand, videoPlugin, validateVideoPreset, validateVideoParameters, VIDEO_BUILT_IN_PRESETS, VIDEO_EXTENSIONS } from './plugins/video.js';
|
|
20
20
|
export { chromaKeyVideoPlugin } from './plugins/chroma-key-video.js';
|
|
21
21
|
export { audioPlugin, validateAudioPreset, AUDIO_BUILT_IN_PRESETS, AUDIO_EXTENSIONS } from './plugins/audio.js';
|
|
@@ -43,6 +43,13 @@ export interface SliceManifestEntry extends AssetIdentified {
|
|
|
43
43
|
/** Pixel dimensions of the delivered image the insets are measured in. */
|
|
44
44
|
sourceWidth: number;
|
|
45
45
|
sourceHeight: number;
|
|
46
|
+
/**
|
|
47
|
+
* Pixel dimensions before stretch compression; present only when compression
|
|
48
|
+
* shrank the delivered image. Layout sizing uses these, painting samples the
|
|
49
|
+
* delivered `sourceWidth` × `sourceHeight`.
|
|
50
|
+
*/
|
|
51
|
+
uncompressedWidth?: number;
|
|
52
|
+
uncompressedHeight?: number;
|
|
46
53
|
}
|
|
47
54
|
export interface VideoManifestEntry extends AssetIdentified {
|
|
48
55
|
kind: 'video';
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* preset). No `targetWidth`: fixed caps keep their authored source-pixel size.
|
|
6
6
|
*/
|
|
7
7
|
import type { AssetTypePluginDescriptor } from './contract.js';
|
|
8
|
+
import type { SliceManifestEntry } from '../manifest-entry.js';
|
|
8
9
|
import { type SliceInsets, type SliceMode } from '../slice-data.js';
|
|
9
10
|
import type { ImagePluginConfig, ImagePreset } from './image.js';
|
|
10
11
|
export interface SliceParameters {
|
|
@@ -39,8 +40,19 @@ export declare function processedSliceInsets(options: {
|
|
|
39
40
|
width: number;
|
|
40
41
|
height: number;
|
|
41
42
|
}): SliceInsets;
|
|
43
|
+
interface Dimensions {
|
|
44
|
+
width: number;
|
|
45
|
+
height: number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* The geometry fields of a slice entry delivered at `delivered` pixels from a
|
|
49
|
+
* `raw` original — shared by codegen and the editor preview so both describe
|
|
50
|
+
* the image identically.
|
|
51
|
+
*/
|
|
52
|
+
export declare function sliceEntryGeometry(parameters: SliceParameters, delivered: Dimensions, raw: Dimensions): Pick<SliceManifestEntry, 'slicing' | 'sourceWidth' | 'sourceHeight' | 'uncompressedWidth' | 'uncompressedHeight'>;
|
|
42
53
|
/** Same shape as the image plugin's config: named output-format presets. */
|
|
43
54
|
export type SlicePluginConfig = ImagePluginConfig;
|
|
44
55
|
/** Built-in presets, always available; host config merges over them. */
|
|
45
56
|
export declare const SLICE_BUILT_IN_PRESETS: Record<string, ImagePreset>;
|
|
46
57
|
export declare const slicePlugin: AssetTypePluginDescriptor<SliceParameters, SlicePluginConfig>;
|
|
58
|
+
export {};
|
|
@@ -30,6 +30,32 @@ export function processedSliceInsets(options) {
|
|
|
30
30
|
const bottom = clamp(desiredBottom, 0, options.height - top);
|
|
31
31
|
return { top, right, bottom, left };
|
|
32
32
|
}
|
|
33
|
+
function hasAnySide(insets) {
|
|
34
|
+
if (!insets)
|
|
35
|
+
return false;
|
|
36
|
+
return insets.top > 0 || insets.right > 0 || insets.bottom > 0 || insets.left > 0;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The geometry fields of a slice entry delivered at `delivered` pixels from a
|
|
40
|
+
* `raw` original — shared by codegen and the editor preview so both describe
|
|
41
|
+
* the image identically.
|
|
42
|
+
*/
|
|
43
|
+
export function sliceEntryGeometry(parameters, delivered, raw) {
|
|
44
|
+
// Slices are never resized, so differing dimensions mean compression shrank the image.
|
|
45
|
+
const compressed = delivered.width !== raw.width || delivered.height !== raw.height;
|
|
46
|
+
return {
|
|
47
|
+
slicing: {
|
|
48
|
+
mode: parameters.mode,
|
|
49
|
+
insets: processedSliceInsets({ ...parameters, ...delivered }),
|
|
50
|
+
pixelRatio: parameters.pixelRatio,
|
|
51
|
+
// An all-zero overdraw is semantically "none" — keep the entry minimal.
|
|
52
|
+
...(hasAnySide(parameters.overdraw) ? { overdraw: parameters.overdraw } : {})
|
|
53
|
+
},
|
|
54
|
+
sourceWidth: delivered.width,
|
|
55
|
+
sourceHeight: delivered.height,
|
|
56
|
+
...(compressed ? { uncompressedWidth: raw.width, uncompressedHeight: raw.height } : {})
|
|
57
|
+
};
|
|
58
|
+
}
|
|
33
59
|
/** Built-in presets, always available; host config merges over them. */
|
|
34
60
|
// Deliberately separate from image presets: each namespace feeds its own job hash.
|
|
35
61
|
export const SLICE_BUILT_IN_PRESETS = {
|
|
@@ -6,13 +6,21 @@
|
|
|
6
6
|
* The keying algorithm follows the upstream shader: the key color and each
|
|
7
7
|
* pixel are converted to YCbCr chroma (Cb/Cr), the squared chroma distance is mapped
|
|
8
8
|
* through a cutoff/feather window (`colorclose`), the resulting mask is blurred with a
|
|
9
|
-
* 3×3 neighbourhood + smoothstep-sharpened, the keyed color is despilled (
|
|
10
|
-
*
|
|
9
|
+
* 3×3 neighbourhood + smoothstep-sharpened, the keyed color is despilled (lost
|
|
10
|
+
* luminance re-added), and the output is
|
|
11
11
|
* `vec4(color * mask, mask)` — the upstream output formula (tint fixed at white; tinting
|
|
12
12
|
* is host styling, not asset data) — emitted premultiplied (rgb × alpha) for the canvas
|
|
13
13
|
* compositor (see the fragColor note).
|
|
14
14
|
*
|
|
15
|
-
* Fundus
|
|
15
|
+
* Fundus replaces the upstream despill, which hard-codes greenscreen (green pulled toward
|
|
16
|
+
* (2b + r) / 4 — it misses any other key's spill, and tints white and gray pink even for
|
|
17
|
+
* green keys). Fundus's despill follows the key color: the key's dominant channels
|
|
18
|
+
* (`spillChannels`) are clamped down to the pixel's strongest remaining channel, so
|
|
19
|
+
* only excess in the key's direction is removed and neutral colors stay exact. For a
|
|
20
|
+
* green key that is the classic "clamp g to max(r, b)". `despillCoverage` fades the
|
|
21
|
+
* despill out over opaque pixels, so artwork colored like the key survives.
|
|
22
|
+
*
|
|
23
|
+
* Fundus also adds an optional mask pass (`setMask`): a grayscale image
|
|
16
24
|
* sampled at the same UVs whose luminance multiplies the final alpha — white keeps a
|
|
17
25
|
* pixel, black drops it, gray fades it (no alpha clip). Absent a mask, a 1×1 white
|
|
18
26
|
* fallback texture makes the pass a no-op.
|
|
@@ -20,7 +28,7 @@
|
|
|
20
28
|
* Owns its own GL context on the given canvas. `create` returns null when WebGL2 is
|
|
21
29
|
* unavailable. Browser-only; the caller drives it per frame with the playing <video>.
|
|
22
30
|
*/
|
|
23
|
-
import type
|
|
31
|
+
import { type ChromaKeyParameters } from '../core/index.js';
|
|
24
32
|
export declare class ChromaKeyGl {
|
|
25
33
|
#private;
|
|
26
34
|
/** Returns null when WebGL2 is unavailable or the shader fails to compile. */
|
|
@@ -1,3 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebGL2 chroma-key video renderer based on the public UnityChromakey shader
|
|
3
|
+
* (https://github.com/otdavies/UnityChromakey, `Shaders/ChromaKey.shader`) so a
|
|
4
|
+
* greenscreen video plays with a transparent background.
|
|
5
|
+
*
|
|
6
|
+
* The keying algorithm follows the upstream shader: the key color and each
|
|
7
|
+
* pixel are converted to YCbCr chroma (Cb/Cr), the squared chroma distance is mapped
|
|
8
|
+
* through a cutoff/feather window (`colorclose`), the resulting mask is blurred with a
|
|
9
|
+
* 3×3 neighbourhood + smoothstep-sharpened, the keyed color is despilled (lost
|
|
10
|
+
* luminance re-added), and the output is
|
|
11
|
+
* `vec4(color * mask, mask)` — the upstream output formula (tint fixed at white; tinting
|
|
12
|
+
* is host styling, not asset data) — emitted premultiplied (rgb × alpha) for the canvas
|
|
13
|
+
* compositor (see the fragColor note).
|
|
14
|
+
*
|
|
15
|
+
* Fundus replaces the upstream despill, which hard-codes greenscreen (green pulled toward
|
|
16
|
+
* (2b + r) / 4 — it misses any other key's spill, and tints white and gray pink even for
|
|
17
|
+
* green keys). Fundus's despill follows the key color: the key's dominant channels
|
|
18
|
+
* (`spillChannels`) are clamped down to the pixel's strongest remaining channel, so
|
|
19
|
+
* only excess in the key's direction is removed and neutral colors stay exact. For a
|
|
20
|
+
* green key that is the classic "clamp g to max(r, b)". `despillCoverage` fades the
|
|
21
|
+
* despill out over opaque pixels, so artwork colored like the key survives.
|
|
22
|
+
*
|
|
23
|
+
* Fundus also adds an optional mask pass (`setMask`): a grayscale image
|
|
24
|
+
* sampled at the same UVs whose luminance multiplies the final alpha — white keeps a
|
|
25
|
+
* pixel, black drops it, gray fades it (no alpha clip). Absent a mask, a 1×1 white
|
|
26
|
+
* fallback texture makes the pass a no-op.
|
|
27
|
+
*
|
|
28
|
+
* Owns its own GL context on the given canvas. `create` returns null when WebGL2 is
|
|
29
|
+
* unavailable. Browser-only; the caller drives it per frame with the playing <video>.
|
|
30
|
+
*/
|
|
31
|
+
import { DEFAULT_CHROMA_KEY_PARAMETERS } from '../core/index.js';
|
|
1
32
|
const VERTEX_SHADER = `#version 300 es
|
|
2
33
|
in vec2 a_pos;
|
|
3
34
|
out vec2 v_uv;
|
|
@@ -9,8 +40,8 @@ void main() {
|
|
|
9
40
|
}`;
|
|
10
41
|
// The upstream fragment, translated HLSL → GLSL. Kept structurally identical (same
|
|
11
42
|
// function names, same sample pattern, same constants) so it can be diffed against
|
|
12
|
-
// the original; only the texel-size plumbing
|
|
13
|
-
//
|
|
43
|
+
// the original; only the texel-size plumbing (u_texelSize is (1/w, 1/h), Unity's
|
|
44
|
+
// _MainTex_TexelSize packs (1/w, 1/h, w, h)) and the despill block differ.
|
|
14
45
|
const FRAGMENT_SHADER = `#version 300 es
|
|
15
46
|
precision highp float;
|
|
16
47
|
|
|
@@ -28,6 +59,8 @@ uniform float u_maskFeathering;
|
|
|
28
59
|
uniform float u_sharpening;
|
|
29
60
|
uniform float u_despill;
|
|
30
61
|
uniform float u_despillLuminanceAdd;
|
|
62
|
+
uniform vec3 u_spillChannels;
|
|
63
|
+
uniform float u_despillCoverage;
|
|
31
64
|
|
|
32
65
|
float rgb2y(vec3 c) {
|
|
33
66
|
return 0.299 * c.r + 0.587 * c.g + 0.114 * c.b;
|
|
@@ -85,9 +118,20 @@ void main() {
|
|
|
85
118
|
float smoothedMask = smoothstep(u_sharpening, 1.0, mix(c, blurContribution, u_maskFeathering));
|
|
86
119
|
vec4 result = color * smoothedMask;
|
|
87
120
|
|
|
88
|
-
// Despill
|
|
89
|
-
|
|
90
|
-
|
|
121
|
+
// Despill (Fundus, see the header): clamp the spill channels to the strongest other
|
|
122
|
+
// channel. Non-spill channels read as 1.0 in the min and 0.0 in the max (result is
|
|
123
|
+
// in 0–1); a key without spill channels has an all-zero mask and subtracts nothing.
|
|
124
|
+
vec3 spillValues = mix(vec3(1.0), result.rgb, u_spillChannels);
|
|
125
|
+
vec3 otherValues = result.rgb * (1.0 - u_spillChannels);
|
|
126
|
+
float spillExcess = max(
|
|
127
|
+
min(spillValues.r, min(spillValues.g, spillValues.b)) -
|
|
128
|
+
max(otherValues.r, max(otherValues.g, otherValues.b)),
|
|
129
|
+
0.0
|
|
130
|
+
);
|
|
131
|
+
// Full despill where the mask is at most 0.8 (the keyed edge), coverage-scaled
|
|
132
|
+
// above it.
|
|
133
|
+
float coverage = mix(1.0 - smoothstep(0.8, 1.0, smoothedMask), 1.0, u_despillCoverage);
|
|
134
|
+
result.rgb -= u_spillChannels * spillExcess * u_despill * coverage;
|
|
91
135
|
vec4 dif = color - result;
|
|
92
136
|
float desaturatedDif = rgb2y(dif.xyz);
|
|
93
137
|
result += mix(0.0, desaturatedDif, u_despillLuminanceAdd);
|
|
@@ -107,6 +151,15 @@ void main() {
|
|
|
107
151
|
// whole canvas rect on iOS.
|
|
108
152
|
fragColor = vec4(keyed.rgb * keyed.a, keyed.a);
|
|
109
153
|
}`;
|
|
154
|
+
/**
|
|
155
|
+
* The key color's spill channels as a 0/1 mask: each channel above the key's channel
|
|
156
|
+
* mean (green → g; magenta → r, b; cyan → g, b). A gray key has none, so no despill.
|
|
157
|
+
* Compared as 2c > a + b, which is exact for equal channels.
|
|
158
|
+
*/
|
|
159
|
+
function spillChannels(keyColor) {
|
|
160
|
+
const [r, g, b] = keyColor;
|
|
161
|
+
return [2 * r > g + b ? 1 : 0, 2 * g > r + b ? 1 : 0, 2 * b > r + g ? 1 : 0];
|
|
162
|
+
}
|
|
110
163
|
function compileProgram(gl) {
|
|
111
164
|
const compile = (type, source) => {
|
|
112
165
|
const shader = gl.createShader(type);
|
|
@@ -202,7 +255,9 @@ export class ChromaKeyGl {
|
|
|
202
255
|
maskFeathering: location('u_maskFeathering'),
|
|
203
256
|
sharpening: location('u_sharpening'),
|
|
204
257
|
despill: location('u_despill'),
|
|
205
|
-
despillLuminanceAdd: location('u_despillLuminanceAdd')
|
|
258
|
+
despillLuminanceAdd: location('u_despillLuminanceAdd'),
|
|
259
|
+
spillChannels: location('u_spillChannels'),
|
|
260
|
+
despillCoverage: location('u_despillCoverage')
|
|
206
261
|
};
|
|
207
262
|
gl.uniform1i(gl.getUniformLocation(this.#program, 'u_texture'), 0);
|
|
208
263
|
gl.uniform1i(gl.getUniformLocation(this.#program, 'u_mask'), 1);
|
|
@@ -280,6 +335,8 @@ export class ChromaKeyGl {
|
|
|
280
335
|
gl.uniform1f(this.#uniforms.sharpening, parameters.sharpening);
|
|
281
336
|
gl.uniform1f(this.#uniforms.despill, parameters.despill);
|
|
282
337
|
gl.uniform1f(this.#uniforms.despillLuminanceAdd, parameters.despillLuminanceAdd);
|
|
338
|
+
gl.uniform3fv(this.#uniforms.spillChannels, spillChannels(parameters.keyColor));
|
|
339
|
+
gl.uniform1f(this.#uniforms.despillCoverage, parameters.despillCoverage ?? DEFAULT_CHROMA_KEY_PARAMETERS.despillCoverage);
|
|
283
340
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
284
341
|
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
285
342
|
}
|
|
@@ -103,7 +103,8 @@
|
|
|
103
103
|
maskFeathering: key.maskFeathering,
|
|
104
104
|
sharpening: key.sharpening,
|
|
105
105
|
despill: key.despill,
|
|
106
|
-
despillLuminanceAdd: key.despillLuminanceAdd
|
|
106
|
+
despillLuminanceAdd: key.despillLuminanceAdd,
|
|
107
|
+
despillCoverage: key.despillCoverage
|
|
107
108
|
};
|
|
108
109
|
lifecycle?.setChromaKey(snapshot);
|
|
109
110
|
});
|
|
@@ -27,7 +27,7 @@ import type { SliceInsets, SliceManifestEntry } from '../core/index.js';
|
|
|
27
27
|
* The manifest-entry fields the pure layout math needs — a structural subset
|
|
28
28
|
* of `SliceManifestEntry`, so hand-built fixtures stay minimal.
|
|
29
29
|
*/
|
|
30
|
-
export type SliceLayoutEntry = Pick<SliceManifestEntry, 'sourceWidth' | 'sourceHeight' | 'slicing'>;
|
|
30
|
+
export type SliceLayoutEntry = Pick<SliceManifestEntry, 'sourceWidth' | 'sourceHeight' | 'uncompressedWidth' | 'uncompressedHeight' | 'slicing'>;
|
|
31
31
|
/**
|
|
32
32
|
* The insets that actually apply for the entry's mode: a 3-slice mode zeroes out
|
|
33
33
|
* the axis it does not use (and `one` uses none at all), so callers never have
|
|
@@ -53,7 +53,9 @@ export interface SliceLayoutGeometry {
|
|
|
53
53
|
* image minus its baked-in overdraw, divided by the export `pixelRatio`. The
|
|
54
54
|
* core is what fills the layout box when `Slice` renders the entry; the
|
|
55
55
|
* overdraw band paints outside the box (like a CSS `box-shadow`), so these are
|
|
56
|
-
* the sizes to lay the element out at.
|
|
56
|
+
* the sizes to lay the element out at. Stretch compression is a delivery
|
|
57
|
+
* optimization, so the authored `uncompressedWidth` / `uncompressedHeight`
|
|
58
|
+
* take precedence over the delivered source size.
|
|
57
59
|
*
|
|
58
60
|
* The math is identical for all modes; the mode decides how a consumer should
|
|
59
61
|
* use the result:
|
package/dist/runtime/geometry.js
CHANGED
|
@@ -35,15 +35,15 @@ export function effectiveOverdraw(entry) {
|
|
|
35
35
|
return { top, right, bottom, left };
|
|
36
36
|
}
|
|
37
37
|
/**
|
|
38
|
-
* The layout box —
|
|
39
|
-
* Never smaller than one
|
|
40
|
-
* a remaining box).
|
|
38
|
+
* The layout box — an image of `width × height` pixels minus the entry's
|
|
39
|
+
* effective overdraw — in those pixels. Never smaller than one pixel per axis
|
|
40
|
+
* (`effectiveOverdraw` guarantees a remaining box).
|
|
41
41
|
*/
|
|
42
|
-
function
|
|
42
|
+
function coreSize(entry, width, height) {
|
|
43
43
|
const overdraw = effectiveOverdraw(entry);
|
|
44
44
|
return {
|
|
45
|
-
width: Math.max(1,
|
|
46
|
-
height: Math.max(1,
|
|
45
|
+
width: Math.max(1, width - overdraw.left - overdraw.right),
|
|
46
|
+
height: Math.max(1, height - overdraw.top - overdraw.bottom)
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
49
|
/**
|
|
@@ -51,7 +51,9 @@ function sourceLayoutSize(entry) {
|
|
|
51
51
|
* image minus its baked-in overdraw, divided by the export `pixelRatio`. The
|
|
52
52
|
* core is what fills the layout box when `Slice` renders the entry; the
|
|
53
53
|
* overdraw band paints outside the box (like a CSS `box-shadow`), so these are
|
|
54
|
-
* the sizes to lay the element out at.
|
|
54
|
+
* the sizes to lay the element out at. Stretch compression is a delivery
|
|
55
|
+
* optimization, so the authored `uncompressedWidth` / `uncompressedHeight`
|
|
56
|
+
* take precedence over the delivered source size.
|
|
55
57
|
*
|
|
56
58
|
* The math is identical for all modes; the mode decides how a consumer should
|
|
57
59
|
* use the result:
|
|
@@ -72,12 +74,12 @@ function sourceLayoutSize(entry) {
|
|
|
72
74
|
* no float artifacts leak into style output.
|
|
73
75
|
*/
|
|
74
76
|
export function sliceLayoutGeometry(entry) {
|
|
75
|
-
const
|
|
77
|
+
const core = coreSize(entry, entry.uncompressedWidth ?? entry.sourceWidth, entry.uncompressedHeight ?? entry.sourceHeight);
|
|
76
78
|
const pixelRatio = entry.slicing.pixelRatio > 0 ? entry.slicing.pixelRatio : 1;
|
|
77
79
|
return {
|
|
78
|
-
width:
|
|
79
|
-
height:
|
|
80
|
-
aspectRatio: `${
|
|
80
|
+
width: core.width / pixelRatio,
|
|
81
|
+
height: core.height / pixelRatio,
|
|
82
|
+
aspectRatio: `${core.width} / ${core.height}`
|
|
81
83
|
};
|
|
82
84
|
}
|
|
83
85
|
/**
|
|
@@ -94,7 +96,7 @@ export function computeOverdrawDestination(entry, boxWidth, boxHeight) {
|
|
|
94
96
|
return overdraw;
|
|
95
97
|
}
|
|
96
98
|
const pixelRatio = entry.slicing.pixelRatio > 0 ? entry.slicing.pixelRatio : 1;
|
|
97
|
-
const sourceLayout =
|
|
99
|
+
const sourceLayout = coreSize(entry, entry.sourceWidth, entry.sourceHeight);
|
|
98
100
|
let horizontalScale;
|
|
99
101
|
let verticalScale;
|
|
100
102
|
if (entry.slicing.mode === 'three-horizontal') {
|
|
@@ -32,16 +32,23 @@ const PREPARE_SET_TAG_HELP = `fundus figma prepare-set-tag — prepare a use_fig
|
|
|
32
32
|
Usage: fundus figma prepare-set-tag --node-id <id> --tag <tag>
|
|
33
33
|
[--file <key|link>] [--force] [--json]
|
|
34
34
|
|
|
35
|
-
Refuses, listing the file's existing tags, when another node
|
|
36
|
-
the tag
|
|
37
|
-
|
|
35
|
+
Refuses unless --force, listing the file's existing tags, when another node
|
|
36
|
+
already carries the tag; --force moves the tag (and its annotation) from those
|
|
37
|
+
nodes to this one, so it stays unique. Also refuses unless --force when the
|
|
38
|
+
node already carries a tag (the same one or another) or a Fundus asset with
|
|
39
|
+
that id exists from another source. An asset imported from this very node by
|
|
40
|
+
node id is not a conflict: tagging prepares its move to the tag
|
|
41
|
+
(\`fundus asset replace <tag> --from figma --figma-tag <tag>\` afterwards keeps
|
|
42
|
+
its id, parameters, and manifests).
|
|
38
43
|
|
|
39
44
|
Flags:
|
|
40
45
|
--node-id <id> the node to tag: 12:34 or 12-34
|
|
41
46
|
--tag <tag> the Fundus asset id to store on the node (camelCase)
|
|
42
47
|
--file <key|link> Figma file key or link; default:
|
|
43
48
|
import.figma.defaultFileKey from fundus.config.ts
|
|
44
|
-
--force replace the node's current tag,
|
|
49
|
+
--force always set the tag: replace the node's current tag,
|
|
50
|
+
rewrite the same tag, move it from other nodes, or tag
|
|
51
|
+
despite a Fundus asset from another source
|
|
45
52
|
--json print {"ok": true, "fileKey", "nodeId", "tag",
|
|
46
53
|
"previousTag", "warnings", "next", "code"}; a refusal
|
|
47
54
|
prints {"ok": false, "error": {"message"}, "existingTags"}
|
|
@@ -50,7 +57,7 @@ Flags:
|
|
|
50
57
|
|
|
51
58
|
Without --json the script goes to stdout, everything else to stderr.
|
|
52
59
|
The script returns {"ok": true, "nodeId", "nodeName", "tag", "previousTag",
|
|
53
|
-
"annotated"}. Afterwards: \`fundus asset ingest --from figma --figma-tag <tag>\`.
|
|
60
|
+
"annotated", "movedFrom": [{"nodeId", "nodeName", "page"}]}. Afterwards: \`fundus asset ingest --from figma --figma-tag <tag>\`.
|
|
54
61
|
|
|
55
62
|
Example:
|
|
56
63
|
fundus figma prepare-set-tag --file 'https://www.figma.com/design/abc/UI' \\
|
|
@@ -37,6 +37,13 @@ function tagListing(nodes) {
|
|
|
37
37
|
function describeNode(node) {
|
|
38
38
|
return `"${node.nodeName}" (${node.nodeId}) on page "${node.page}"`;
|
|
39
39
|
}
|
|
40
|
+
/** Whether `record` is a node-mode Figma import of `nodeId` in `fileKey`. */
|
|
41
|
+
function isNodeImportOf(record, fileKey, nodeId) {
|
|
42
|
+
if (record.importSource?.importer !== 'figma')
|
|
43
|
+
return false;
|
|
44
|
+
const source = validateFigmaSourceParameters(record.importSource.parameters);
|
|
45
|
+
return source.mode !== 'tag' && source.fileKey === fileKey && source.nodeId === nodeId;
|
|
46
|
+
}
|
|
40
47
|
/** The library asset imported from `tag` in `fileKey`, if any. */
|
|
41
48
|
function tagImportedAsset(library, fileKey, tag) {
|
|
42
49
|
const record = library.assets[tag];
|
|
@@ -55,12 +62,31 @@ export function planSetFigmaTag(request, { library, document }) {
|
|
|
55
62
|
const nodes = figmaDocumentNodes(document);
|
|
56
63
|
const existingTags = tagListing(nodes);
|
|
57
64
|
const warnings = [];
|
|
58
|
-
const
|
|
59
|
-
if (
|
|
60
|
-
|
|
65
|
+
const holders = nodes.filter((node) => node.tag === tag && node.nodeId !== nodeId);
|
|
66
|
+
if (holders.length > 0) {
|
|
67
|
+
const described = holders.map(describeNode).join(', ');
|
|
68
|
+
if (!force) {
|
|
69
|
+
throw new FigmaTagConflictError(`Tag "${tag}" is already used by ${described} — choose another tag, or pass --force to move it to node ${nodeId} (existing tags are listed).`, existingTags);
|
|
70
|
+
}
|
|
71
|
+
warnings.push(`Moves tag "${tag}" from ${described} to node ${nodeId}.`);
|
|
61
72
|
}
|
|
62
|
-
|
|
63
|
-
|
|
73
|
+
const existing = Object.hasOwn(library.assets, tag) ? library.assets[tag] : null;
|
|
74
|
+
if (existing && tagImportedAsset(library, fileKey, tag) === null) {
|
|
75
|
+
// A replace without --scale falls back to the config default and resets a
|
|
76
|
+
// slice's pixelRatio to it, so keep the scale a Figma asset was imported at.
|
|
77
|
+
const savedScale = existing.importSource?.importer === 'figma'
|
|
78
|
+
? ` --scale ${validateFigmaSourceParameters(existing.importSource.parameters).scale}`
|
|
79
|
+
: '';
|
|
80
|
+
const replace = `\`fundus asset replace ${tag} --from figma --figma-tag ${tag} --figma-file ${fileKey}${savedScale}\``;
|
|
81
|
+
if (isNodeImportOf(existing, fileKey, nodeId)) {
|
|
82
|
+
warnings.push(`Fundus asset "${tag}" is imported from this node by id — afterwards run ${replace} to move it to the tag (id, parameters, folder, and manifests stay).`);
|
|
83
|
+
}
|
|
84
|
+
else if (!force) {
|
|
85
|
+
throw new FigmaTagConflictError(`A Fundus asset "${tag}" already exists and is imported neither from this tag nor from this node in file "${fileKey}" — choose another tag, or pass --force to tag the node anyway and then run ${replace}, which replaces the asset's current source.`, existingTags);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
warnings.push(`Fundus asset "${tag}" already exists from another source — afterwards run ${replace} to switch it to this tag; that replaces its current source.`);
|
|
89
|
+
}
|
|
64
90
|
}
|
|
65
91
|
const node = nodes.find((candidate) => candidate.nodeId === nodeId) ?? null;
|
|
66
92
|
if (!node) {
|
|
@@ -83,7 +109,7 @@ export function planSetFigmaTag(request, { library, document }) {
|
|
|
83
109
|
}
|
|
84
110
|
warnings.push(`Replaces tag "${previousTag}" on node ${nodeId}.${followUp}`);
|
|
85
111
|
}
|
|
86
|
-
if (tagImportedAsset(library, fileKey, tag) !== null &&
|
|
112
|
+
if (tagImportedAsset(library, fileKey, tag) !== null && previousTag !== tag) {
|
|
87
113
|
warnings.push(`Fundus asset "${tag}" is imported from this tag; its next \`fundus asset reimport ${tag}\` resolves to this node.`);
|
|
88
114
|
}
|
|
89
115
|
return {
|
|
@@ -15,6 +15,9 @@ export interface ProxyOutputInfo {
|
|
|
15
15
|
height?: number;
|
|
16
16
|
/** Media duration; present for video and audio proxies. */
|
|
17
17
|
durationMs?: number;
|
|
18
|
+
/** Raw source pixel dimensions; present for slice proxies (pre-compression layout size). */
|
|
19
|
+
rawWidth?: number;
|
|
20
|
+
rawHeight?: number;
|
|
18
21
|
}
|
|
19
22
|
/** One delivered asset's current proxy: file identity plus output metadata. */
|
|
20
23
|
export interface AssetProxyInfo extends ProxyOutputInfo {
|
|
@@ -101,7 +104,7 @@ export interface ServerAssetTypePlugin<TParameters = unknown, TPreset = unknown>
|
|
|
101
104
|
/** Run the processing job (sharp/ffmpeg): write the proxy, report its metadata. */
|
|
102
105
|
process(job: ServerProcessingJob<TParameters, TPreset>): Promise<ProxyOutputInfo>;
|
|
103
106
|
/** Probe an existing proxy file's output metadata (the up-to-date path). */
|
|
104
|
-
readProxyInfo(proxyPath: string, config: ResolvedFundusConfig): Promise<ProxyOutputInfo>;
|
|
107
|
+
readProxyInfo(proxyPath: string, config: ResolvedFundusConfig, rawPath: string): Promise<ProxyOutputInfo>;
|
|
105
108
|
/**
|
|
106
109
|
* Build the generated manifest entry. Reference fields stay
|
|
107
110
|
* `{ $asset }` markers — codegen's generic inline walk resolves them.
|
|
@@ -113,4 +116,4 @@ export type ServerPluginRegistry = Record<string, ServerAssetTypePlugin>;
|
|
|
113
116
|
/** Resolve a named preset out of a plugin's merged preset map, or throw listing the known names. */
|
|
114
117
|
export declare function resolveNamedPreset<TPreset>(label: string, presetName: string, presets: Record<string, TPreset>): TPreset;
|
|
115
118
|
/** A proxy-info field a given entry kind cannot do without. */
|
|
116
|
-
export declare function requireProxyField(proxy: AssetProxyInfo, field:
|
|
119
|
+
export declare function requireProxyField(proxy: AssetProxyInfo, field: keyof ProxyOutputInfo): number;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* redundant pixels from the stretchable regions. The slicing data itself is
|
|
5
5
|
* entry-only.
|
|
6
6
|
*/
|
|
7
|
-
import { SLICE_BUILT_IN_PRESETS, defaultInsetsForSize,
|
|
7
|
+
import { SLICE_BUILT_IN_PRESETS, defaultInsetsForSize, sliceEntryGeometry, slicePlugin, validateImagePreset } from '../../core/index.js';
|
|
8
8
|
import { readImageDimensions } from '../processing/image-processing.js';
|
|
9
9
|
import { processSliceImageFile } from '../processing/slice-processing.js';
|
|
10
10
|
import { extensionForImageFormat } from './image.js';
|
|
@@ -21,10 +21,10 @@ export const sliceConfigDefinition = {
|
|
|
21
21
|
})
|
|
22
22
|
})
|
|
23
23
|
};
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return
|
|
24
|
+
/** Proxy info plus the raw dimensions the entry's pre-compression layout size needs. */
|
|
25
|
+
async function withRawDimensions(output, rawPath) {
|
|
26
|
+
const [info, raw] = await Promise.all([output, readImageDimensions(rawPath)]);
|
|
27
|
+
return { ...info, rawWidth: raw.width, rawHeight: raw.height };
|
|
28
28
|
}
|
|
29
29
|
/**
|
|
30
30
|
* Best-effort guess of the asset's export scale from a `@2x` / `@3x` suffix in
|
|
@@ -53,7 +53,7 @@ export const sliceServerPlugin = {
|
|
|
53
53
|
pixelRatio: detectPixelRatio(rawFileName)
|
|
54
54
|
};
|
|
55
55
|
},
|
|
56
|
-
process: (job) => processSliceImageFile({
|
|
56
|
+
process: (job) => withRawDimensions(processSliceImageFile({
|
|
57
57
|
sourcePath: job.sourcePath,
|
|
58
58
|
targetPath: job.targetPath,
|
|
59
59
|
preset: job.preset,
|
|
@@ -61,29 +61,12 @@ export const sliceServerPlugin = {
|
|
|
61
61
|
insets: job.parameters.insets,
|
|
62
62
|
overdraw: job.parameters.overdraw,
|
|
63
63
|
compression: job.parameters.compression
|
|
64
|
-
}),
|
|
65
|
-
readProxyInfo: (proxyPath) => readImageDimensions(proxyPath),
|
|
66
|
-
buildManifestEntry: ({ parameters, src, proxy }) => {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
...parameters,
|
|
73
|
-
width: sourceWidth,
|
|
74
|
-
height: sourceHeight
|
|
75
|
-
}),
|
|
76
|
-
pixelRatio: parameters.pixelRatio,
|
|
77
|
-
// An all-zero overdraw is semantically "none" — keep the entry minimal.
|
|
78
|
-
...(hasAnySide(parameters.overdraw) ? { overdraw: parameters.overdraw } : {})
|
|
79
|
-
};
|
|
80
|
-
return {
|
|
81
|
-
kind: 'slice',
|
|
82
|
-
src,
|
|
83
|
-
byteSize: proxy.fileSize,
|
|
84
|
-
slicing,
|
|
85
|
-
sourceWidth,
|
|
86
|
-
sourceHeight
|
|
87
|
-
};
|
|
88
|
-
}
|
|
64
|
+
}), job.sourcePath),
|
|
65
|
+
readProxyInfo: (proxyPath, _config, rawPath) => withRawDimensions(readImageDimensions(proxyPath), rawPath),
|
|
66
|
+
buildManifestEntry: ({ parameters, src, proxy }) => ({
|
|
67
|
+
kind: 'slice',
|
|
68
|
+
src,
|
|
69
|
+
byteSize: proxy.fileSize,
|
|
70
|
+
...sliceEntryGeometry(parameters, { width: requireProxyField(proxy, 'width'), height: requireProxyField(proxy, 'height') }, { width: requireProxyField(proxy, 'rawWidth'), height: requireProxyField(proxy, 'rawHeight') })
|
|
71
|
+
})
|
|
89
72
|
};
|