pixi-effects 0.28.2 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -0
- package/README.md +10 -10
- package/ai/chat-template.html +5 -5
- package/ai/tools/check.mjs +18 -3
- package/ai/tools/pixi-effects.mjs +1 -1
- package/ai/tools/render.mjs +14 -9
- package/ai/tools/view.mjs +6 -2
- package/dist/Base-CB1_FPZD.d.cts +60 -0
- package/dist/Base-Ne74ztNl.d.ts +60 -0
- package/dist/Composition-MUKR3FYD.cjs +16 -0
- package/dist/{Composition-K6B3APM5.cjs.map → Composition-MUKR3FYD.cjs.map} +1 -1
- package/dist/Composition-RJGA7KDU.js +7 -0
- package/dist/{Composition-H7UNR5F5.js.map → Composition-RJGA7KDU.js.map} +1 -1
- package/dist/Controller.cjs +73 -45
- package/dist/Controller.cjs.map +1 -1
- package/dist/Controller.d.cts +16 -22
- package/dist/Controller.d.ts +16 -22
- package/dist/Controller.js +74 -40
- package/dist/Controller.js.map +1 -1
- package/dist/{Movie-CHcM8HNB.d.ts → Movie-C2rZFkrO.d.ts} +227 -119
- package/dist/{Movie-Cx5ozUGb.d.cts → Movie-DdnpLpw1.d.cts} +227 -119
- package/dist/{blendModes-HDOP6R7T.cjs → blendModes-ETMYWATF.cjs} +5 -5
- package/dist/{blendModes-HDOP6R7T.cjs.map → blendModes-ETMYWATF.cjs.map} +1 -1
- package/dist/{blendModes-PAVTW3BO.js → blendModes-RIVZXL5A.js} +4 -4
- package/dist/{blendModes-PAVTW3BO.js.map → blendModes-RIVZXL5A.js.map} +1 -1
- package/dist/{chunk-P4GY3Z4G.cjs → chunk-2DXPTKQZ.cjs} +6 -6
- package/dist/chunk-2DXPTKQZ.cjs.map +1 -0
- package/dist/{chunk-TYWFKCPY.js → chunk-4KB3GUEK.js} +5 -5
- package/dist/chunk-4KB3GUEK.js.map +1 -0
- package/dist/{chunk-5MZWXVTT.js → chunk-A3Q3SDSH.js} +35 -28
- package/dist/chunk-A3Q3SDSH.js.map +1 -0
- package/dist/chunk-CRQFDY7E.js +4182 -0
- package/dist/chunk-CRQFDY7E.js.map +1 -0
- package/dist/chunk-HREBQAY4.cjs +4206 -0
- package/dist/chunk-HREBQAY4.cjs.map +1 -0
- package/dist/{chunk-ZMAD3KJU.cjs → chunk-LYOKH7KE.cjs} +22 -3
- package/dist/chunk-LYOKH7KE.cjs.map +1 -0
- package/dist/{chunk-45QKLD6M.js → chunk-RCVZX2JP.js} +22 -3
- package/dist/chunk-RCVZX2JP.js.map +1 -0
- package/dist/{chunk-KBH33IQX.cjs → chunk-WHNADXXG.cjs} +121 -114
- package/dist/chunk-WHNADXXG.cjs.map +1 -0
- package/dist/{chunk-7VN2KI3U.js → chunk-ZAXYPXKW.js} +14 -6
- package/dist/chunk-ZAXYPXKW.js.map +1 -0
- package/dist/{chunk-MJW7KILM.cjs → chunk-ZVUO325O.cjs} +32 -24
- package/dist/chunk-ZVUO325O.cjs.map +1 -0
- package/dist/index.cjs +106 -4482
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -60
- package/dist/index.d.ts +15 -60
- package/dist/index.js +6 -4457
- package/dist/index.js.map +1 -1
- package/dist/loader.css +1 -1
- package/dist/{Presenter.cjs → present.cjs} +370 -31
- package/dist/present.cjs.map +1 -0
- package/dist/present.d.cts +278 -0
- package/dist/present.d.ts +278 -0
- package/dist/{Presenter.js → present.js} +358 -32
- package/dist/present.js.map +1 -0
- package/dist/three.cjs +6 -6
- package/dist/three.d.cts +2 -2
- package/dist/three.d.ts +2 -2
- package/dist/three.js +2 -2
- package/dist/{types-DpK1vwcl.d.ts → types-BzIB65Vk.d.cts} +11 -265
- package/dist/{types-DpK1vwcl.d.cts → types-BzIB65Vk.d.ts} +11 -265
- package/docs/api.md +44 -77
- package/docs/dsl.md +16 -14
- package/llms-full.txt +86 -115
- package/llms.txt +1 -1
- package/package.json +5 -5
- package/skills/pixi-effects/SKILL.md +5 -5
- package/skills/pixi-effects/reference/cheatsheet.md +9 -9
- package/skills/pixi-effects/reference/direction.md +2 -0
- package/skills/pixi-effects/reference/pitfalls.md +6 -6
- package/skills/pixi-effects/reference/recipes.md +4 -4
- package/skills/pixi-effects/template.html +7 -7
- package/dist/Base-C6f0fE8j.d.ts +0 -109
- package/dist/Base-DI1FCGcH.d.cts +0 -109
- package/dist/Composition-H7UNR5F5.js +0 -7
- package/dist/Composition-K6B3APM5.cjs +0 -16
- package/dist/Presenter.cjs.map +0 -1
- package/dist/Presenter.d.cts +0 -122
- package/dist/Presenter.d.ts +0 -122
- package/dist/Presenter.js.map +0 -1
- package/dist/chunk-45QKLD6M.js.map +0 -1
- package/dist/chunk-5MZWXVTT.js.map +0 -1
- package/dist/chunk-7VN2KI3U.js.map +0 -1
- package/dist/chunk-KBH33IQX.cjs.map +0 -1
- package/dist/chunk-MJW7KILM.cjs.map +0 -1
- package/dist/chunk-P4GY3Z4G.cjs.map +0 -1
- package/dist/chunk-TYWFKCPY.js.map +0 -1
- package/dist/chunk-ZMAD3KJU.cjs.map +0 -1
package/dist/loader.css
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* pixi-effects loader: one line of HTML on the page, a few lines of CSS, no JavaScript of your own.
|
|
3
3
|
*
|
|
4
|
-
* <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pixi-effects@0.
|
|
4
|
+
* <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pixi-effects@0.29.0/dist/loader.css">
|
|
5
5
|
* <div class="stage" style="position: relative"> <!-- any box the canvas sits in, with position: relative -->
|
|
6
6
|
* <canvas id="stage" width="1280" height="720"></canvas>
|
|
7
7
|
* <div class="pe-loader" data-label="LOADING"></div> <!-- or class="pe-loader pe-loader--bar" -->
|
|
@@ -1,8 +1,270 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
require('./chunk-HREBQAY4.cjs');
|
|
4
|
+
require('./chunk-WHNADXXG.cjs');
|
|
5
|
+
require('./chunk-ZVUO325O.cjs');
|
|
6
|
+
require('./chunk-2DXPTKQZ.cjs');
|
|
7
|
+
require('./chunk-LYOKH7KE.cjs');
|
|
3
8
|
var chunkTWZNOUM7_cjs = require('./chunk-TWZNOUM7.cjs');
|
|
4
9
|
|
|
5
|
-
// src/
|
|
10
|
+
// src/present/stops.ts
|
|
11
|
+
function nextStopAfter(stops, frame) {
|
|
12
|
+
return stops.find((s) => s.frame > frame) ?? null;
|
|
13
|
+
}
|
|
14
|
+
function previousStopBefore(stops, frame) {
|
|
15
|
+
for (let i = stops.length - 1; i >= 0; i--) if (stops[i].frame < frame) return stops[i];
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
function stopAtOrBefore(stops, frame) {
|
|
19
|
+
for (let i = stops.length - 1; i >= 0; i--) if (stops[i].frame <= frame) return stops[i];
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
function pageStarts(stops) {
|
|
23
|
+
return stops.filter((s) => s.pageStart);
|
|
24
|
+
}
|
|
25
|
+
function pictureStops(stops, opts = {}) {
|
|
26
|
+
if (opts.which === "stops") return opts.pdf ? stops.filter((s) => s.pdf !== false) : [...stops];
|
|
27
|
+
const byPage = /* @__PURE__ */ new Map();
|
|
28
|
+
for (const s of stops) byPage.set(s.pageIndex, [...byPage.get(s.pageIndex) ?? [], s]);
|
|
29
|
+
const take = (list) => opts.pick === "first" ? list[0] : list[list.length - 1];
|
|
30
|
+
const picks = [];
|
|
31
|
+
for (const list of byPage.values()) {
|
|
32
|
+
const flagged = list.filter((s) => s.pdf === true);
|
|
33
|
+
const usable = list.filter((s) => s.pdf !== false);
|
|
34
|
+
const one = take(flagged.length ? flagged : usable.length ? usable : opts.pdf ? [] : list);
|
|
35
|
+
if (one) picks.push(one);
|
|
36
|
+
}
|
|
37
|
+
return picks;
|
|
38
|
+
}
|
|
39
|
+
function changedFraction(a, b, threshold = 12) {
|
|
40
|
+
const n = Math.min(a.length, b.length) >> 2;
|
|
41
|
+
if (n === 0) return 0;
|
|
42
|
+
let changed = 0;
|
|
43
|
+
for (let i = 0; i < n; i++) {
|
|
44
|
+
const k = i << 2;
|
|
45
|
+
if (Math.abs(a[k] - b[k]) > threshold || Math.abs(a[k + 1] - b[k + 1]) > threshold || Math.abs(a[k + 2] - b[k + 2]) > threshold) changed++;
|
|
46
|
+
}
|
|
47
|
+
return changed / n;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/present/navigation.ts
|
|
51
|
+
var listeners = /* @__PURE__ */ new WeakMap();
|
|
52
|
+
var toward = /* @__PURE__ */ new WeakMap();
|
|
53
|
+
function onStop(movie, fn) {
|
|
54
|
+
let set = listeners.get(movie);
|
|
55
|
+
if (!set) {
|
|
56
|
+
set = /* @__PURE__ */ new Set();
|
|
57
|
+
listeners.set(movie, set);
|
|
58
|
+
}
|
|
59
|
+
set.add(fn);
|
|
60
|
+
return () => {
|
|
61
|
+
set.delete(fn);
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function emit(movie, stop) {
|
|
65
|
+
const e = { index: stop.index, stop, pageIndex: stop.pageIndex };
|
|
66
|
+
for (const fn of [...listeners.get(movie) ?? []]) fn(e);
|
|
67
|
+
}
|
|
68
|
+
var stopIndex = (movie) => stopAtOrBefore(movie.stops, movie.currentFrame)?.index ?? -1;
|
|
69
|
+
var currentStop = (movie) => stopAtOrBefore(movie.stops, movie.currentFrame);
|
|
70
|
+
var pageIndex = (movie) => currentStop(movie)?.pageIndex ?? -1;
|
|
71
|
+
var pageCount = (movie) => pageStarts(movie.stops).length;
|
|
72
|
+
async function next(movie) {
|
|
73
|
+
if (!movie.isReady) return;
|
|
74
|
+
const going = toward.get(movie);
|
|
75
|
+
if (going && movie.isPlaying) {
|
|
76
|
+
toward.delete(movie);
|
|
77
|
+
movie.pause();
|
|
78
|
+
await movie.gotoFrame(going.frame, true);
|
|
79
|
+
emit(movie, going);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
toward.delete(movie);
|
|
83
|
+
if (movie.currentFrame >= movie.totalFrames) return;
|
|
84
|
+
const target = nextStopAfter(movie.stops, movie.currentFrame);
|
|
85
|
+
if (!target) {
|
|
86
|
+
await movie.playUntil();
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
toward.set(movie, target);
|
|
90
|
+
const landed = await movie.playUntil(target.frame / movie.frameRate);
|
|
91
|
+
if (toward.get(movie) === target) toward.delete(movie);
|
|
92
|
+
if (landed) emit(movie, target);
|
|
93
|
+
}
|
|
94
|
+
async function prev(movie) {
|
|
95
|
+
if (!movie.isReady) return;
|
|
96
|
+
toward.delete(movie);
|
|
97
|
+
movie.pause();
|
|
98
|
+
const target = previousStopBefore(movie.stops, movie.currentFrame);
|
|
99
|
+
await movie.gotoFrame(target ? target.frame : 0, true);
|
|
100
|
+
if (target) emit(movie, target);
|
|
101
|
+
}
|
|
102
|
+
async function goToStop(movie, index) {
|
|
103
|
+
if (!movie.isReady) return;
|
|
104
|
+
const stop = movie.stops[index];
|
|
105
|
+
if (!stop) {
|
|
106
|
+
console.warn(`pixi-effects: goToStop(movie, ${index}): there are ${movie.stops.length} stops (0 to ${movie.stops.length - 1})`);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
toward.delete(movie);
|
|
110
|
+
movie.pause();
|
|
111
|
+
await movie.gotoFrame(stop.frame, true);
|
|
112
|
+
emit(movie, stop);
|
|
113
|
+
}
|
|
114
|
+
async function goToPage(movie, page) {
|
|
115
|
+
const starts = pageStarts(movie.stops);
|
|
116
|
+
const stop = starts[page];
|
|
117
|
+
if (!stop) {
|
|
118
|
+
console.warn(`pixi-effects: goToPage(movie, ${page}): there are ${starts.length} pages (0 to ${starts.length - 1})`);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
await goToStop(movie, stop.index);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// src/present/pdf.ts
|
|
125
|
+
var enc = (s) => {
|
|
126
|
+
const out = new Uint8Array(s.length);
|
|
127
|
+
for (let i = 0; i < s.length; i++) out[i] = s.charCodeAt(i) & 255;
|
|
128
|
+
return out;
|
|
129
|
+
};
|
|
130
|
+
var num = (n) => String(Math.round(n * 1e3) / 1e3);
|
|
131
|
+
var textString = (s) => {
|
|
132
|
+
if (/^[\x20-\x7e]*$/.test(s)) return `(${s.replace(/[\\()]/g, (m) => `\\${m}`)})`;
|
|
133
|
+
let hex = "FEFF";
|
|
134
|
+
for (let i = 0; i < s.length; i++) hex += s.charCodeAt(i).toString(16).toUpperCase().padStart(4, "0");
|
|
135
|
+
return `<${hex}>`;
|
|
136
|
+
};
|
|
137
|
+
function buildPdf(pages, opts = {}) {
|
|
138
|
+
if (pages.length === 0) throw new Error("pixi-effects: a PDF needs at least one page");
|
|
139
|
+
const first = (i) => 3 + i * 3;
|
|
140
|
+
const objects = [];
|
|
141
|
+
objects[1] = [enc("<< /Type /Catalog /Pages 2 0 R >>")];
|
|
142
|
+
objects[2] = [enc(`<< /Type /Pages /Kids [${pages.map((_, i) => `${first(i)} 0 R`).join(" ")}] /Count ${pages.length} >>`)];
|
|
143
|
+
pages.forEach((p, i) => {
|
|
144
|
+
const w = p.width * 0.75, h = p.height * 0.75, n = first(i);
|
|
145
|
+
objects[n] = [enc(`<< /Type /Page /Parent 2 0 R /MediaBox [0 0 ${num(w)} ${num(h)}] /Resources << /XObject << /Im0 ${n + 2} 0 R >> >> /Contents ${n + 1} 0 R >>`)];
|
|
146
|
+
const content = `q ${num(w)} 0 0 ${num(h)} 0 0 cm /Im0 Do Q`;
|
|
147
|
+
objects[n + 1] = [enc(`<< /Length ${content.length} >>
|
|
148
|
+
stream
|
|
149
|
+
${content}
|
|
150
|
+
endstream`)];
|
|
151
|
+
objects[n + 2] = [enc(`<< /Type /XObject /Subtype /Image /Width ${p.width} /Height ${p.height} /ColorSpace /DeviceRGB /BitsPerComponent 8 /Filter /DCTDecode /Length ${p.jpeg.length} >>
|
|
152
|
+
stream
|
|
153
|
+
`), p.jpeg, enc("\nendstream")];
|
|
154
|
+
});
|
|
155
|
+
const infoNo = opts.title !== void 0 ? first(pages.length) : 0;
|
|
156
|
+
if (infoNo) objects[infoNo] = [enc(`<< /Title ${textString(opts.title)} /Producer (pixi-effects) >>`)];
|
|
157
|
+
const count = infoNo || first(pages.length);
|
|
158
|
+
const chunks = [enc("%PDF-1.4\n%\xE2\xE3\xCF\xD3\n")];
|
|
159
|
+
let offset = chunks[0].length;
|
|
160
|
+
const offsets = [];
|
|
161
|
+
for (let n = 1; n < count + (infoNo ? 1 : 0); n++) {
|
|
162
|
+
const body = objects[n];
|
|
163
|
+
if (!body) continue;
|
|
164
|
+
offsets[n] = offset;
|
|
165
|
+
const parts = [enc(`${n} 0 obj
|
|
166
|
+
`), ...body, enc("\nendobj\n")];
|
|
167
|
+
for (const part of parts) {
|
|
168
|
+
chunks.push(part);
|
|
169
|
+
offset += part.length;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const size = (infoNo || first(pages.length)) + (infoNo ? 1 : 0);
|
|
173
|
+
let xref = `xref
|
|
174
|
+
0 ${size}
|
|
175
|
+
0000000000 65535 f
|
|
176
|
+
`;
|
|
177
|
+
for (let n = 1; n < size; n++) xref += `${String(offsets[n] ?? 0).padStart(10, "0")} 00000 n
|
|
178
|
+
`;
|
|
179
|
+
xref += `trailer
|
|
180
|
+
<< /Size ${size} /Root 1 0 R${infoNo ? ` /Info ${infoNo} 0 R` : ""} >>
|
|
181
|
+
startxref
|
|
182
|
+
${offset}
|
|
183
|
+
%%EOF
|
|
184
|
+
`;
|
|
185
|
+
chunks.push(enc(xref));
|
|
186
|
+
const total = chunks.reduce((sum, c) => sum + c.length, 0);
|
|
187
|
+
const out = new Uint8Array(total);
|
|
188
|
+
let at = 0;
|
|
189
|
+
for (const c of chunks) {
|
|
190
|
+
out.set(c, at);
|
|
191
|
+
at += c.length;
|
|
192
|
+
}
|
|
193
|
+
return out;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// src/present/images.ts
|
|
197
|
+
function requireReady(movie, what) {
|
|
198
|
+
if (!movie.isReady) throw new Error(`pixi-effects: ${what}() needs a ready movie \u2014 await movie.init(...) first`);
|
|
199
|
+
}
|
|
200
|
+
async function stopImages(movie, opts = {}) {
|
|
201
|
+
chunkTWZNOUM7_cjs.warnUnknownOptions("stopImages()", opts, ["which", "pick", "scale", "type", "quality", "as", "motionBlur", "pdf", "onImage"]);
|
|
202
|
+
requireReady(movie, "stopImages");
|
|
203
|
+
if (movie.stops.length === 0) return [];
|
|
204
|
+
const picks = pictureStops(movie.stops, { which: opts.which, pick: opts.pick, pdf: opts.pdf });
|
|
205
|
+
const shot = { scale: opts.scale ?? 1, type: opts.type ?? "image/png", quality: opts.quality, as: opts.as ?? "blob", motionBlur: opts.motionBlur === void 0 ? false : opts.motionBlur };
|
|
206
|
+
return movie.peek(async () => {
|
|
207
|
+
const out = [];
|
|
208
|
+
for (const stop of picks) {
|
|
209
|
+
const image = await movie.snapshot(stop.frame, shot);
|
|
210
|
+
const item = { stop, page: stop.pageIndex, image };
|
|
211
|
+
out.push(item);
|
|
212
|
+
opts.onImage?.(item);
|
|
213
|
+
}
|
|
214
|
+
return out;
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
async function inspectStops(movie, opts = {}) {
|
|
218
|
+
chunkTWZNOUM7_cjs.warnUnknownOptions("inspectStops()", opts, ["lookback", "tolerance"]);
|
|
219
|
+
requireReady(movie, "inspectStops");
|
|
220
|
+
const out = { stops: [], issues: [] };
|
|
221
|
+
if (movie.stops.length === 0) return out;
|
|
222
|
+
const lookback = Math.max(1, Math.round(opts.lookback ?? 3));
|
|
223
|
+
const tolerance = opts.tolerance ?? 4e-3;
|
|
224
|
+
const small = Math.min(1, 320 / Math.max(1, movie.width));
|
|
225
|
+
const pixels = async (frame) => (await movie.snapshot(frame, { scale: small, motionBlur: false, as: "imageData" })).data;
|
|
226
|
+
return movie.peek(async () => {
|
|
227
|
+
for (const stop of movie.stops) {
|
|
228
|
+
const before = Math.max(0, stop.frame - lookback);
|
|
229
|
+
let moving = 0;
|
|
230
|
+
if (before < stop.frame) {
|
|
231
|
+
const now = await pixels(stop.frame);
|
|
232
|
+
moving = changedFraction(now, await pixels(before));
|
|
233
|
+
}
|
|
234
|
+
const settled = moving <= tolerance;
|
|
235
|
+
out.stops.push({ index: stop.index, page: stop.page, at: stop.at, frame: stop.frame, moving, settled });
|
|
236
|
+
if (!settled) {
|
|
237
|
+
out.issues.push(`stop ${stop.index + 1}${stop.page ? ` (page "${stop.page}")` : ""} at ${stop.at.toFixed(1)} s: the picture is still changing there (${moving < 0.1 ? (moving * 100).toFixed(1) : Math.round(moving * 100)}% of it changed in the last ${stop.frame - before} frames), so the audience, the page overview and a PDF see it mid-animation. Move the stop to after the animation has finished, or flag a settled stop of the page with \`pdf: true\`.`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return out;
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
async function exportPDF(movie, opts = {}) {
|
|
244
|
+
chunkTWZNOUM7_cjs.warnUnknownOptions("exportPDF()", opts, ["which", "pick", "scale", "quality", "title", "motionBlur", "onImage"]);
|
|
245
|
+
requireReady(movie, "exportPDF");
|
|
246
|
+
if (movie.stops.length === 0) throw new Error("pixi-effects: exportPDF(): this movie has no stops, so there are no pages; add `stops` to the composition (or build it with deck())");
|
|
247
|
+
const scale = opts.scale ?? 1;
|
|
248
|
+
const images = await stopImages(movie, {
|
|
249
|
+
which: opts.which,
|
|
250
|
+
pick: opts.pick,
|
|
251
|
+
scale,
|
|
252
|
+
type: "image/jpeg",
|
|
253
|
+
quality: opts.quality ?? 0.92,
|
|
254
|
+
as: "blob",
|
|
255
|
+
motionBlur: opts.motionBlur,
|
|
256
|
+
pdf: true,
|
|
257
|
+
onImage: opts.onImage
|
|
258
|
+
});
|
|
259
|
+
const pages = await Promise.all(images.map(async (i) => ({
|
|
260
|
+
jpeg: new Uint8Array(await i.image.arrayBuffer()),
|
|
261
|
+
width: Math.max(1, Math.round(movie.width * scale)),
|
|
262
|
+
height: Math.max(1, Math.round(movie.height * scale))
|
|
263
|
+
})));
|
|
264
|
+
return new Blob([buildPdf(pages, { title: opts.title })], { type: "application/pdf" });
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// src/present/Presenter.ts
|
|
6
268
|
var OPTION_KEYS = ["canvas", "keyboard", "clickToAdvance", "swipe", "indicator", "autoAdvance", "loop", "accent", "onExit"];
|
|
7
269
|
var NEXT_KEYS = /* @__PURE__ */ new Set(["ArrowRight", "ArrowDown", " ", "Enter", "PageDown"]);
|
|
8
270
|
var PREV_KEYS = /* @__PURE__ */ new Set(["ArrowLeft", "ArrowUp", "Backspace", "PageUp"]);
|
|
@@ -129,6 +391,7 @@ var Presenter = class {
|
|
|
129
391
|
wasWrapAttr = false;
|
|
130
392
|
onKey = (e) => this.handleKey(e);
|
|
131
393
|
onStop = (e) => this.handleStop(e);
|
|
394
|
+
offStop = null;
|
|
132
395
|
onSeeked = () => this.refresh();
|
|
133
396
|
onFrame = () => this.mirror();
|
|
134
397
|
onEnded = () => this.handleEnded();
|
|
@@ -202,7 +465,7 @@ var Presenter = class {
|
|
|
202
465
|
canvas.addEventListener("pointermove", this.onPointerMove);
|
|
203
466
|
if (this.options.keyboard) document.addEventListener("keydown", this.onKey);
|
|
204
467
|
document.addEventListener("fullscreenchange", this.onFullscreenChange);
|
|
205
|
-
|
|
468
|
+
this.offStop = onStop(movie, this.onStop);
|
|
206
469
|
movie.on("seeked", this.onSeeked);
|
|
207
470
|
movie.on("ended", this.onEnded);
|
|
208
471
|
this.refresh();
|
|
@@ -226,11 +489,11 @@ var Presenter = class {
|
|
|
226
489
|
}
|
|
227
490
|
next() {
|
|
228
491
|
this.cancelAdvance();
|
|
229
|
-
return this.run(() => this.movie
|
|
492
|
+
return this.run(() => next(this.movie));
|
|
230
493
|
}
|
|
231
494
|
prev() {
|
|
232
495
|
this.cancelAdvance();
|
|
233
|
-
return this.run(() => this.movie
|
|
496
|
+
return this.run(() => prev(this.movie));
|
|
234
497
|
}
|
|
235
498
|
/** Black out (`'black'`), white out (`'white'`) or bring the picture back (`'off'`). */
|
|
236
499
|
setCover(mode) {
|
|
@@ -248,7 +511,8 @@ var Presenter = class {
|
|
|
248
511
|
canvas.removeEventListener("pointerup", this.onPointerUp);
|
|
249
512
|
canvas.removeEventListener("pointermove", this.onPointerMove);
|
|
250
513
|
this.closePresenterView();
|
|
251
|
-
this.
|
|
514
|
+
this.offStop?.();
|
|
515
|
+
this.offStop = null;
|
|
252
516
|
this.movie.off("seeked", this.onSeeked);
|
|
253
517
|
this.movie.off("ended", this.onEnded);
|
|
254
518
|
for (const t of [this.idleTimer, this.digitsTimer, this.advanceTimer, this.loopTimer]) if (t) clearTimeout(t);
|
|
@@ -321,19 +585,19 @@ var Presenter = class {
|
|
|
321
585
|
this.loopTimer = null;
|
|
322
586
|
void this.run(async () => {
|
|
323
587
|
await this.movie.gotoFrame(0, true);
|
|
324
|
-
await this.movie
|
|
588
|
+
await next(this.movie);
|
|
325
589
|
});
|
|
326
590
|
}, 800);
|
|
327
591
|
}
|
|
328
592
|
refresh() {
|
|
329
593
|
const m = this.movie;
|
|
330
|
-
const pages = m
|
|
594
|
+
const pages = pageCount(m), stops = m.stops?.length ?? 0;
|
|
331
595
|
if (this.counterEl) {
|
|
332
|
-
this.counterEl.textContent = pages > 0 ? `${m
|
|
596
|
+
this.counterEl.textContent = pages > 0 ? `${pageIndex(m) >= 0 ? pageIndex(m) + 1 : "\u2013"} / ${pages}` : "";
|
|
333
597
|
this.counterEl.style.display = pages > 0 ? "" : "none";
|
|
334
598
|
}
|
|
335
|
-
if (this.titleEl) this.titleEl.textContent = m
|
|
336
|
-
if (this.progressEl) this.progressEl.style.setProperty("--mp-fill", String(stops > 0 ? (m
|
|
599
|
+
if (this.titleEl) this.titleEl.textContent = currentStop(m)?.page ?? "";
|
|
600
|
+
if (this.progressEl) this.progressEl.style.setProperty("--mp-fill", String(stops > 0 ? (stopIndex(m) + 1) / stops : 0));
|
|
337
601
|
this.refreshView();
|
|
338
602
|
}
|
|
339
603
|
wake() {
|
|
@@ -391,7 +655,7 @@ var Presenter = class {
|
|
|
391
655
|
this.showDigits();
|
|
392
656
|
this.navigate(() => {
|
|
393
657
|
this.cancelAdvance();
|
|
394
|
-
void this.run(() => this.movie
|
|
658
|
+
void this.run(() => goToPage(this.movie, page));
|
|
395
659
|
});
|
|
396
660
|
} else if (k === "Backspace" && this.digits) {
|
|
397
661
|
this.digits = this.digits.slice(0, -1);
|
|
@@ -400,11 +664,11 @@ var Presenter = class {
|
|
|
400
664
|
else if (PREV_KEYS.has(k)) this.navigate(() => void this.prev());
|
|
401
665
|
else if (k === "Home") this.navigate(() => {
|
|
402
666
|
this.cancelAdvance();
|
|
403
|
-
void this.run(() => this.movie
|
|
667
|
+
void this.run(() => goToPage(this.movie, 0));
|
|
404
668
|
});
|
|
405
669
|
else if (k === "End") this.navigate(() => {
|
|
406
670
|
this.cancelAdvance();
|
|
407
|
-
void this.run(() => this.movie
|
|
671
|
+
void this.run(() => goToStop(this.movie, Math.max(0, (this.movie.stops?.length ?? 1) - 1)));
|
|
408
672
|
});
|
|
409
673
|
else if (k === "b" || k === "B" || k === ".") this.setCover(this.coverEl.getAttribute("data-mode") === "black" ? "off" : "black");
|
|
410
674
|
else if (k === "w" || k === "W" || k === ",") this.setCover(this.coverEl.getAttribute("data-mode") === "white" ? "off" : "white");
|
|
@@ -507,18 +771,18 @@ var Presenter = class {
|
|
|
507
771
|
if (!v || v.win.closed) return;
|
|
508
772
|
const m = this.movie;
|
|
509
773
|
const stops = m.stops ?? [];
|
|
510
|
-
const pages = m
|
|
511
|
-
const cur = m
|
|
512
|
-
v.els.count.textContent = pages > 0 ? `${m
|
|
513
|
-
v.els.title.textContent = m
|
|
514
|
-
const upTo = cur < 0 ? 0 : cur,
|
|
774
|
+
const pages = pageCount(m);
|
|
775
|
+
const cur = stopIndex(m);
|
|
776
|
+
v.els.count.textContent = pages > 0 ? `${pageIndex(m) >= 0 ? pageIndex(m) + 1 : "\u2013"} / ${pages}` : "";
|
|
777
|
+
v.els.title.textContent = currentStop(m)?.page ?? (cur < 0 ? stops[0]?.page ?? "" : "");
|
|
778
|
+
const upTo = cur < 0 ? 0 : cur, pageIndex2 = stops[upTo]?.pageIndex;
|
|
515
779
|
let notes = "";
|
|
516
|
-
for (const s of stops) if (s.pageIndex ===
|
|
780
|
+
for (const s of stops) if (s.pageIndex === pageIndex2 && s.index <= upTo && s.notes) notes = s.notes;
|
|
517
781
|
v.els.notes.textContent = notes;
|
|
518
782
|
v.els.notes.setAttribute("data-empty", String(notes === ""));
|
|
519
|
-
const
|
|
520
|
-
v.els.nextbox.setAttribute("data-end", String(!
|
|
521
|
-
const src =
|
|
783
|
+
const next2 = stops[cur + 1];
|
|
784
|
+
v.els.nextbox.setAttribute("data-end", String(!next2));
|
|
785
|
+
const src = next2 ? this.thumbs.get(next2.index) : void 0;
|
|
522
786
|
if (src) v.els.next.setAttribute("src", src);
|
|
523
787
|
else v.els.next.removeAttribute("src");
|
|
524
788
|
}
|
|
@@ -546,10 +810,11 @@ var Presenter = class {
|
|
|
546
810
|
* Make a picture of every stop, once. Taking them moves the playhead through the stops and back, which the audience would see:
|
|
547
811
|
* `start()` does it before anything is shown (behind a "Preparing" cover), and an overview opened before that does it on the spot.
|
|
548
812
|
*/
|
|
813
|
+
/** Make the page pictures now (the overview and the presenter view use them): `start()` does it before the audience sees anything. */
|
|
549
814
|
ensureThumbs() {
|
|
550
815
|
this.thumbsPromise ??= (async () => {
|
|
551
816
|
try {
|
|
552
|
-
await this.movie
|
|
817
|
+
await stopImages(this.movie, {
|
|
553
818
|
which: "stops",
|
|
554
819
|
as: "dataURL",
|
|
555
820
|
type: "image/jpeg",
|
|
@@ -572,8 +837,8 @@ var Presenter = class {
|
|
|
572
837
|
const stops = (this.movie.stops ?? []).filter((s) => s.pageIndex === page);
|
|
573
838
|
return stops.length ? this.thumbs.get(stops[stops.length - 1].index) : void 0;
|
|
574
839
|
}
|
|
575
|
-
onThumb(
|
|
576
|
-
const stop = this.movie.stops?.[
|
|
840
|
+
onThumb(stopIndex2) {
|
|
841
|
+
const stop = this.movie.stops?.[stopIndex2];
|
|
577
842
|
if (stop) this.fillThumb(stop.pageIndex);
|
|
578
843
|
this.refreshView();
|
|
579
844
|
}
|
|
@@ -584,18 +849,18 @@ var Presenter = class {
|
|
|
584
849
|
this.gridEl.style.setProperty("--mp-cols", String(cols));
|
|
585
850
|
this.gridEl.style.setProperty("--mp-ratio", `${width} / ${height}`);
|
|
586
851
|
this.gridEl.innerHTML = "";
|
|
587
|
-
const current = Math.max(0, this.movie
|
|
852
|
+
const current = Math.max(0, pageIndex(this.movie));
|
|
588
853
|
pages.forEach((s, i) => {
|
|
589
854
|
const cell = document.createElement("div");
|
|
590
855
|
cell.className = "mp-cell";
|
|
591
|
-
cell.setAttribute("data-current", String(i === this.movie
|
|
856
|
+
cell.setAttribute("data-current", String(i === pageIndex(this.movie)));
|
|
592
857
|
cell.setAttribute("data-selected", String(i === current));
|
|
593
858
|
cell.innerHTML = `<div class="mp-cell-label"><span class="mp-cell-num">${i + 1}</span><span class="mp-cell-name"></span></div>`;
|
|
594
859
|
cell.querySelector(".mp-cell-name").textContent = s.page ?? "";
|
|
595
860
|
cell.addEventListener("click", () => {
|
|
596
861
|
this.overviewEl.setAttribute("data-open", "false");
|
|
597
862
|
this.cancelAdvance();
|
|
598
|
-
void this.run(() => this.movie
|
|
863
|
+
void this.run(() => goToPage(this.movie, i));
|
|
599
864
|
});
|
|
600
865
|
this.gridEl.appendChild(cell);
|
|
601
866
|
this.fillThumb(i);
|
|
@@ -636,7 +901,7 @@ var Presenter = class {
|
|
|
636
901
|
const page = this.overviewSel;
|
|
637
902
|
this.overviewEl.setAttribute("data-open", "false");
|
|
638
903
|
this.cancelAdvance();
|
|
639
|
-
void this.run(() => this.movie
|
|
904
|
+
void this.run(() => goToPage(this.movie, page));
|
|
640
905
|
return true;
|
|
641
906
|
}
|
|
642
907
|
case "Escape":
|
|
@@ -666,6 +931,80 @@ var Presenter = class {
|
|
|
666
931
|
}
|
|
667
932
|
};
|
|
668
933
|
|
|
934
|
+
// src/present/deck.ts
|
|
935
|
+
var PAGE_KEYS = ["name", "duration", "sequences", "stops", "notes", "advance", "initial", "keyframes", "filters", "filterArea", "blendMode", "mask"];
|
|
936
|
+
var DECK_KEYS = ["pages", "transition", "width", "height", "frameRate", "background", "canvas", "assets", "poster", "motionBlur", "loader"];
|
|
937
|
+
function deck(options) {
|
|
938
|
+
if (!options || !Array.isArray(options.pages) || options.pages.length === 0) throw new Error("deck(): pages must be a list with at least one page: { pages: [{ name, duration, stops, sequences }] }");
|
|
939
|
+
chunkTWZNOUM7_cjs.warnUnknownOptions("deck()", options, DECK_KEYS);
|
|
940
|
+
const { pages, transition, ...movieOptions } = options;
|
|
941
|
+
pages.forEach((p, i) => {
|
|
942
|
+
if (!p || typeof p !== "object") throw new Error(`deck(): pages[${i}] must be an object`);
|
|
943
|
+
chunkTWZNOUM7_cjs.warnUnknownOptions(`deck() pages[${i}]`, p, PAGE_KEYS);
|
|
944
|
+
if (!(typeof p.duration === "number" && Number.isFinite(p.duration) && p.duration > 0)) throw new Error(`deck(): pages[${i}].duration must be a positive number of seconds, got ${String(p.duration)}`);
|
|
945
|
+
if (!Array.isArray(p.sequences)) throw new Error(`deck(): pages[${i}].sequences must be a list of layers (it may be empty), got ${typeof p.sequences}`);
|
|
946
|
+
});
|
|
947
|
+
let overlap = 0;
|
|
948
|
+
if (transition) {
|
|
949
|
+
if (!(typeof transition.duration === "number" && transition.duration > 0)) throw new Error("deck(): transition needs a duration in seconds: { kind: 'crossfade', duration: 0.6 }");
|
|
950
|
+
const shortest = Math.min(...pages.map((p) => p.duration));
|
|
951
|
+
if (pages.length > 1 && transition.duration >= shortest) throw new Error(`deck(): the transition (${transition.duration} s) must be shorter than every page (the shortest is ${shortest} s)`);
|
|
952
|
+
overlap = pages.length > 1 ? transition.duration : 0;
|
|
953
|
+
}
|
|
954
|
+
const sequences = [];
|
|
955
|
+
const transitions = [];
|
|
956
|
+
const stops = [];
|
|
957
|
+
let start = 0;
|
|
958
|
+
pages.forEach((page, i) => {
|
|
959
|
+
const id = `page-${i + 1}`;
|
|
960
|
+
const label = page.name ? `"${page.name}"` : `pages[${i}]`;
|
|
961
|
+
const isLast = i === pages.length - 1;
|
|
962
|
+
const entries = (page.stops ?? [isLast ? page.duration : page.duration - overlap]).map((e, k) => {
|
|
963
|
+
if (e && typeof e === "object") chunkTWZNOUM7_cjs.warnUnknownOptions(`deck() page ${label} stops[${k}]`, e, ["at", "pdf"]);
|
|
964
|
+
return typeof e === "object" && e !== null ? e : { at: e };
|
|
965
|
+
});
|
|
966
|
+
const local = entries.map((e) => e.at);
|
|
967
|
+
local.forEach((s, k) => {
|
|
968
|
+
if (!(typeof s === "number" && Number.isFinite(s) && s > 0 && s <= page.duration + 1e-9)) throw new Error(`deck(): page ${label} has stop ${String(s)}, outside the page (0 to ${page.duration} s)`);
|
|
969
|
+
if (k > 0 && s <= local[k - 1]) throw new Error(`deck(): page ${label} stops must be in order, earliest first (got ${local.join(", ")})`);
|
|
970
|
+
});
|
|
971
|
+
const { name: _n, duration: _d, sequences: inner, stops: _s, notes: _no, advance: _a, ...layerProps } = page;
|
|
972
|
+
sequences.push({ type: "composition", name: id, at: round(start), duration: page.duration, ...layerProps, sequences: inner });
|
|
973
|
+
local.forEach((s, k) => {
|
|
974
|
+
const stop = { at: round(start + s) };
|
|
975
|
+
if (k === 0) {
|
|
976
|
+
stop.page = page.name ?? true;
|
|
977
|
+
if (page.notes !== void 0) stop.notes = page.notes;
|
|
978
|
+
}
|
|
979
|
+
if (k === local.length - 1 && page.advance !== void 0) stop.advance = page.advance;
|
|
980
|
+
if (entries[k].pdf !== void 0) stop.pdf = entries[k].pdf;
|
|
981
|
+
stops.push(stop);
|
|
982
|
+
});
|
|
983
|
+
if (transition && !isLast) {
|
|
984
|
+
const next2 = start + page.duration - overlap;
|
|
985
|
+
transitions.push({ ...transition, from: id, to: `page-${i + 2}`, at: round(next2), duration: transition.duration });
|
|
986
|
+
}
|
|
987
|
+
start = start + page.duration - (isLast ? 0 : overlap);
|
|
988
|
+
});
|
|
989
|
+
const composition = { sequences, stops };
|
|
990
|
+
if (transitions.length > 0) composition.transitions = transitions;
|
|
991
|
+
return { ...movieOptions, duration: round(start), composition };
|
|
992
|
+
}
|
|
993
|
+
var round = (v) => Math.round(v * 1e6) / 1e6;
|
|
994
|
+
|
|
669
995
|
exports.Presenter = Presenter;
|
|
670
|
-
|
|
671
|
-
|
|
996
|
+
exports.currentStop = currentStop;
|
|
997
|
+
exports.deck = deck;
|
|
998
|
+
exports.exportPDF = exportPDF;
|
|
999
|
+
exports.goToPage = goToPage;
|
|
1000
|
+
exports.goToStop = goToStop;
|
|
1001
|
+
exports.inspectStops = inspectStops;
|
|
1002
|
+
exports.next = next;
|
|
1003
|
+
exports.onStop = onStop;
|
|
1004
|
+
exports.pageCount = pageCount;
|
|
1005
|
+
exports.pageIndex = pageIndex;
|
|
1006
|
+
exports.prev = prev;
|
|
1007
|
+
exports.stopImages = stopImages;
|
|
1008
|
+
exports.stopIndex = stopIndex;
|
|
1009
|
+
//# sourceMappingURL=present.cjs.map
|
|
1010
|
+
//# sourceMappingURL=present.cjs.map
|