vintage-frames 0.16.2 → 0.17.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 CHANGED
@@ -39,6 +39,8 @@ createShell(document.querySelector('vf-desktop'), {
39
39
 
40
40
  See [docs/SHELL.md](https://github.com/aportilla/vintage-frames/blob/main/docs/SHELL.md). Its API may change in any minor release while it is experimental.
41
41
 
42
+ An application on the shell can ship as one PNG, its app file. `vintage-frames/build` holds the Vite plugins that write app files and build them into a site; it runs in the build only. See [docs/APP-FILES.md](https://github.com/aportilla/vintage-frames/blob/main/docs/APP-FILES.md).
43
+
42
44
 
43
45
  ## Sizing
44
46
 
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Writing app files. The box's own chunks are copied as they are, so the
3
+ * picture is never re-encoded; any app chunks already there are dropped,
4
+ * and the manifest's and the code's go in before IEND.
5
+ */
6
+ import type { AppManifest } from '../shell/app-file.js';
7
+ /** What {@link packApp} packs. */
8
+ export interface PackAppInput {
9
+ /** The manifest's fields but its format and icon, which the packer fills in. */
10
+ manifest: Omit<AppManifest, 'format' | 'icon'>;
11
+ /** The icon's PNG, 32 × 32. */
12
+ icon: Uint8Array;
13
+ /** The application's code: one ES module whose default export is its factory. */
14
+ code: string;
15
+ /** The artwork's PNG, for the box. Without it the icon is stamped there. */
16
+ artwork?: Uint8Array;
17
+ }
18
+ /** `box` with `manifest` and `code` in its chunks. */
19
+ export declare function writeAppFile(box: Uint8Array, manifest: AppManifest, code: string): Promise<Uint8Array>;
20
+ /** The whole app file: the box in the kit's frame, then the manifest and the code. */
21
+ export declare function packApp({ manifest, icon, code, artwork }: PackAppInput): Promise<Uint8Array>;
@@ -0,0 +1,41 @@
1
+ import { CODE_KEYWORD as e, MANIFEST_KEYWORD as t, PNG_SIGNATURE as n, deflate as r, itxtKeyword as i, manifestOf as a, pngChunks as o } from "../shell/app-file.js";
2
+ import { concat as s, pngChunk as c } from "./png.js";
3
+ import { composeBox as l } from "./box.js";
4
+ //#region src/build/app-file.ts
5
+ var u = new TextEncoder();
6
+ function d(e, t, n) {
7
+ let r = Uint8Array.from([...e].map((e) => e.charCodeAt(0)));
8
+ return c("iTXt", s([
9
+ r,
10
+ Uint8Array.of(0, +!!n, 0, 0, 0),
11
+ t
12
+ ]));
13
+ }
14
+ async function f(c, l, f) {
15
+ let p = o(c);
16
+ if (!p) throw Error("vintage-frames/build: the box is not a whole PNG");
17
+ let m = a(l);
18
+ if (typeof m == "string") throw Error(`vintage-frames/build: the manifest won't read back: ${m}`);
19
+ let h = [d(t, u.encode(JSON.stringify(m)), !1), d(e, await r(u.encode(f)), !0)], g = [n];
20
+ for (let e of p) e.type === "iTXt" && ["vintage-frames.app", "vintage-frames.code"].includes(i(e.data)) || (e.type === "IEND" && g.push(...h), g.push(c.subarray(e.start, e.end)));
21
+ return s(g);
22
+ }
23
+ async function p({ manifest: e, icon: t, code: n, artwork: r }) {
24
+ let i = {
25
+ ...e,
26
+ format: 1,
27
+ icon: `data:image/png;base64,${m(t)}`
28
+ };
29
+ return f(await l({
30
+ manifest: i,
31
+ icon: t,
32
+ artwork: r
33
+ }), i, n);
34
+ }
35
+ function m(e) {
36
+ let t = "";
37
+ for (let n = 0; n < e.length; n += 32768) t += String.fromCharCode(...e.subarray(n, n + 32768));
38
+ return btoa(t);
39
+ }
40
+ //#endregion
41
+ export { p as packApp, f as writeAppFile };
@@ -0,0 +1,55 @@
1
+ /**
2
+ * The box: the picture an app file shows. The kit's frame, with the
3
+ * application's artwork in the artwork slot, or its icon stamped large on
4
+ * the slot's backdrop when there is none, and lines of text set from the
5
+ * kit's strikes with the manifest's fields in them. Artwork and the stamp
6
+ * are pasted by their alpha. Nothing is drawn outside the slots.
7
+ */
8
+ import type { AppManifest } from '../shell/app-file.js';
9
+ import type { Strike } from '../styles/strike.js';
10
+ import type { Pixels } from './png.js';
11
+ /** A box in a frame's picture, in its px. */
12
+ export interface Slot {
13
+ left: number;
14
+ top: number;
15
+ width: number;
16
+ height: number;
17
+ }
18
+ /** A frame: a picture with everything fixed drawn in, and its slots. */
19
+ export interface BoxFrame {
20
+ /** The picture, a PNG. */
21
+ picture: Uint8Array;
22
+ /** Where the artwork goes. Without artwork the icon goes there, `stamp` times its size and centered. */
23
+ artwork: Slot & {
24
+ stamp: number;
25
+ };
26
+ /** The lines of text. */
27
+ text: readonly TextSlot[];
28
+ }
29
+ /** A line of text on the box. */
30
+ export interface TextSlot {
31
+ /** The top-left of its em box, and the width it fits in. */
32
+ left: number;
33
+ top: number;
34
+ width: number;
35
+ strike: Strike;
36
+ /** Picture px per design px. */
37
+ scale: number;
38
+ /** `#rrggbb`. */
39
+ ink: string;
40
+ align?: 'left' | 'center' | 'right';
41
+ /** The line, with manifest fields in braces: `{name}`, `Version {version}`, `by {author}`, or fixed text. */
42
+ text: string;
43
+ }
44
+ /** What a box is composed from. */
45
+ export interface BoxInput {
46
+ manifest: AppManifest;
47
+ /** The icon's PNG, 32 × 32. */
48
+ icon: Uint8Array;
49
+ /** The artwork's PNG: the artwork slot's size, or that divided by a whole number. */
50
+ artwork?: Uint8Array;
51
+ }
52
+ /** The box in the kit's frame, as a PNG. */
53
+ export declare function composeBox(input: BoxInput): Promise<Uint8Array>;
54
+ /** The box in `frame`, as pixels. */
55
+ export declare function drawBox(frame: BoxFrame, { manifest, icon, artwork }: BoxInput): Promise<Pixels>;
@@ -0,0 +1,90 @@
1
+ import { FRAME as e } from "./frame.js";
2
+ import { decodePng as t, encodePng as n } from "./png.js";
3
+ //#region src/build/box.ts
4
+ var r = [
5
+ "id",
6
+ "name",
7
+ "version",
8
+ "requires",
9
+ "author"
10
+ ];
11
+ async function i(t) {
12
+ return n(await a(e, t));
13
+ }
14
+ async function a(e, { manifest: n, icon: r, artwork: i }) {
15
+ let a = await t(e.picture, "the frame"), c = await t(r, "the icon");
16
+ if (c.width !== 32 || c.height !== 32) throw Error(`the icon is ${c.width} × ${c.height}, and an icon is 32 × 32`);
17
+ let u = e.artwork;
18
+ if (i) {
19
+ let e = await t(i, "the artwork"), n = u.width / e.width;
20
+ if (!Number.isInteger(n) || e.height * n !== u.height) throw Error(`the artwork is ${e.width} × ${e.height}: draw it at ${u.width} × ${u.height}, or that divided by a whole number (${o(u)})`);
21
+ s(a, e, u.left, u.top, n, u);
22
+ } else {
23
+ let e = 32 * u.stamp;
24
+ s(a, c, u.left + Math.floor((u.width - e) / 2), u.top + Math.floor((u.height - e) / 2), u.stamp, u);
25
+ }
26
+ for (let t of e.text) p(a, t, l(t.text, n));
27
+ return a;
28
+ }
29
+ function o({ width: e, height: t }) {
30
+ let n = [];
31
+ for (let r = 2; r <= e && n.length < 3; r++) e % r === 0 && t % r === 0 && n.push(`${e / r} × ${t / r}`);
32
+ return [...n, "…"].join(", ");
33
+ }
34
+ function s(e, t, n, r, i, a) {
35
+ let o = Math.max(n, a.left, 0), s = Math.max(r, a.top, 0), l = Math.min(n + t.width * i, a.left + a.width, e.width), u = Math.min(r + t.height * i, a.top + a.height, e.height);
36
+ for (let a = s; a < u; a++) for (let s = o; s < l; s++) {
37
+ let o = (Math.floor((a - r) / i) * t.width + Math.floor((s - n) / i)) * 4;
38
+ c(e, s, a, t.data.subarray(o, o + 4));
39
+ }
40
+ }
41
+ function c(e, t, n, [r, i, a, o]) {
42
+ if (!o) return;
43
+ let s = e.data, c = (n * e.width + t) * 4;
44
+ if (o === 255) {
45
+ s[c] = r, s[c + 1] = i, s[c + 2] = a, s[c + 3] = 255;
46
+ return;
47
+ }
48
+ let l = s[c + 3] * (255 - o) / 255, u = o + l;
49
+ s[c] = Math.round((r * o + s[c] * l) / u), s[c + 1] = Math.round((i * o + s[c + 1] * l) / u), s[c + 2] = Math.round((a * o + s[c + 2] * l) / u), s[c + 3] = Math.round(u);
50
+ }
51
+ function l(e, t) {
52
+ return e.replace(/\{(\w+)\}/g, (n, i) => {
53
+ let a = r.find((e) => e === i);
54
+ if (!a) throw Error(`the frame's line "${e}" names {${i}}, which a manifest doesn't have`);
55
+ return t[a];
56
+ });
57
+ }
58
+ var u = (e) => `"${e}" (U+${e.codePointAt(0).toString(16).toUpperCase().padStart(4, "0")})`, d = (e, t) => [...t].reduce((t, n) => t + e.glyphs[n].advance, 0);
59
+ function f(e, t, n) {
60
+ if (d(e, t) <= n) return t;
61
+ if (!e.glyphs["…"]) throw Error(`${e.family} has no ${u("…")} to shorten "${t}" with`);
62
+ let r = [...t];
63
+ for (; r.length && d(e, r.join("").trimEnd() + "…") > n;) r.pop();
64
+ return r.join("").trimEnd() + "…";
65
+ }
66
+ function p(e, t, n) {
67
+ let { strike: r, scale: i } = t;
68
+ for (let e of n) if (!r.glyphs[e]) throw Error(`${r.family} has no ${u(e)}, in "${n}"`);
69
+ let a = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(t.ink);
70
+ if (!a) throw Error(`the frame's ink "${t.ink}" is not #rrggbb`);
71
+ let o = Uint8Array.of(parseInt(a[1], 16), parseInt(a[2], 16), parseInt(a[3], 16), 255), s = f(r, n, t.width / i), l = d(r, s) * i, p = t.align === "center" ? Math.floor((t.width - l) / 2 / i) * i : t.align === "right" ? t.width - l : 0, m = {
72
+ left: t.left,
73
+ top: t.top,
74
+ width: t.width,
75
+ height: (r.ascent + r.descent) * i
76
+ }, h = t.top + r.ascent * i, g = t.left + p;
77
+ for (let t of s) {
78
+ let n = r.glyphs[t];
79
+ n.rows.forEach((t, r) => {
80
+ let a = h - (n.y0 + n.height - r) * i;
81
+ for (let r = 0; r < t.length; r++) {
82
+ if (t[r] !== "#") continue;
83
+ let s = g + (n.x0 + r) * i;
84
+ for (let t = Math.max(a, m.top, 0); t < Math.min(a + i, m.top + m.height, e.height); t++) for (let n = Math.max(s, m.left, 0); n < Math.min(s + i, m.left + m.width, e.width); n++) c(e, n, t, o);
85
+ }
86
+ }), g += n.advance * i;
87
+ }
88
+ }
89
+ //#endregion
90
+ export { i as composeBox, a as drawBox };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * `vintage-frames/build/client`: the types of a `*.png?app` import, which
3
+ * `appFiles()` serves. A site adds it to tsconfig's `types` beside
4
+ * `vite/client`.
5
+ */
6
+ declare module '*.png?app' {
7
+ /** The application's factory: its definition, given the manifest's icon. */
8
+ const app: (...args: any[]) => import('../shell/index.js').AppDefinition<any>;
9
+ export default app;
10
+ /** The app file's manifest. */
11
+ export const manifest: import('../shell/pure.js').AppManifest;
12
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * The kit's frame's picture: box/frame.png, 480 × 615, 3095 bytes.
3
+ * GENERATED by `npm run embed:frame`: redraw box/frame.png and re-run rather
4
+ * than editing this file. Its slots are src/build/frame.ts's.
5
+ */
6
+ export declare const FRAME_PNG: Uint8Array;
@@ -0,0 +1,4 @@
1
+ //#region src/build/frame-picture.ts
2
+ var e = Uint8Array.from(atob("iVBORw0KGgoAAAANSUhEUgAAAeAAAAJnCAYAAABPiYBoAAAL3klEQVR42u3VwQkAIAwEwfRfW3rSBnwJBuFmYQpQAldVtQCAcT4BAAwwAKQNsCRJepcBliTJAEuSZIAlSZIBliTJAEuSJAMsSZIBliRJBliSJAMsSZIMsCRJBliSJAMsSZIMsCRJBliSJBlgSZIMsCRJMsCSJBlgSZJkgCVJMsCSJBlgAyxJkgGWJMkAS5IkAyxJkgGWJEkGWJIkAyxJkgywJEkGWJIkA2yAJUkywJIkGWBJkmSAJUkywJIkyQBLkmSAJUmSAZYkyQBLkmSADbAkSQZYkiQDLEmSDPDxAQCEM8AGGAADbIABMMAG+NEDuhuAQAbYAANggA0wAAbYABtgAAywAQbAABtgAwyAATbAABhgA2yAATDABhgAA2yADTAABtgAA2CADTAABtgAG2AADLABBsAAG2ADDIABNsAAGGADbIABMMAGGAADbIANMAAG2AADYIANMAAG2AAbYAAMsAEGwAAbYAMMgAE2wAAYYANsgAEwwAYYAANsgA0wAAbYAANggA0wAAbYABtgAAywAQbAABtgAwyAATbAABhgA2yAATDABhgAA2yADTAABtgAA2CADTAABtgAG2AADLABBsAAG2ADDIABNsAAGGADbIABMMAGGAADbIANMAAG2AADYIANsCMEMMAG2AADYIANMAAG2AAbYAAMsAEGwAAbYAMMgAE2wAAYYANsgAEwwAYYAANsgA0wgAE2wAYYAANsgAEwwAbYAANggA0wAAbYABtgAAywAQbAABtgAwyAATbAABhgA2yAAQywATbAABhgAwyAATbABhgAA2yAATDABtgAA2CADTAABtgAG2AADLABBsAAG2ADDGCADbABBsAAG2AADLABNsAAGGADDIABNsAGGAADbIABMMAG2AADYIANMAAG2AAbYAADbIANMAAG2AADYIANsAEGwAAbYAAMsAE2wAAYYAMMgAE2wAYYAANsgAEwwAbYAANggA0wAAbYAANggA2wAQbAABtgAAywATbAABhgAwyAATbABhgAA2yAATDABtgAA2CADTAABtgAA2CADbABBsAAG2AADLABNsAAGGADDIABNsAGGAADbIABMMAG2AADYIANMAAG2AADYIANsAEGwAAbYAAMsAE2wAAYYAMMgAE2wAYYAANsgAEwwAbYAANggA0wAAbYAANggA2wAQbAABtgAAywATbAABhgAwyAATbABhgAA2yAATDABtgAA2CADTAABtgAO0IAA2yADTAABtgAA2CADbABBsAAG2AADLABNsAAGGADDIABNsAGGAADbIABMMAG2BECGGADbIABMMAGGAADbIANMAAG2AADYIANsAEGwAAbYAAMsAE2wAAYYAMMgAE2wAYYwAAbYAMMgAE2wAAYYANsgAEwwAYYAANsgA0wAAbYAANggA2wAQbAABtgAAywATbAAAbYABtgAAywAQbAABtgAwyAATbAABhgA2yAATDABhgAA2yADTAABtgAA2CADbABBjDABtgAA2CADTAABtgAG2AADLABBsAAG2ADDIABNsAAGGADbIABMMAGGAADbIANMAAG2AADYIANMAAG2AAbYAAMsAEGwAAbYAMMgAE2wAAYYANsgAEwwAYYAANsgA0wAAbYAANggA0wAAbYABtgAAywAQbAABtgAwyAATbAABhgA2yAATDABhgAA2yADTAABtgAA2CADTAABtgAG2AADLABBsAAG2ADDIABNsAAGGADbIABMMAGGAADbIANMAAG2AADYIANMAAG2AAbYAAMsAEGwAAbYAMMgAE2wAAYYANsgAEwwAYYAANsgA0wAAbYAANggA0wAAbYABtgAAywAQbAABtgAwyAATbAABhgA2yAATDABhgAA2yADTAABtgAA2CADbAjBDDABtgAA2CADTAABtgAG2AADLABBsAAG2ADDIABNsAAGGADbIABMMAGGAADbIANMIABNsAGGAADbIABMMAG2AADYIANMAAG2AAbYAAMsAEGwAAbYAMMgAE2wAAYYANsgAEMsAE2wAAYYAMMgAE2wAYYAANsgAEwwAbYAANggA0wAAbYABtgAAywAQbAABtgAwxggA2wAQbAABtgAAywATbAABhgAwyAATbABhgAA2yAATDABtgAA2CADTAABtgAG2AADLABBsAAG2AADLABNsAAGGADDIABNsAGGAADbIABMMAG2AADYIANMAAG2AAbYAAMsAEGwAAbYAAMsAE2wAAYYAMMgAE2wAYYAANsgAEwwAbYAANggA0wAAbYABtgAAywAQbAABtgAAywATbAABhgAwyAATbABhgAA2yAATDABtgAA2CADTAABtgAG2AADLABBsAAG2AADLABNsAAGGADDIABNsAGGAADbIABMMAG2AADYIANMAAG2AAbYAAMsAEGwAAbYAAMsAE2wAAYYAMMgAE2wAYYAANsgAEwwAbYAANggA0wAAbYABtgAAywAQbAABtgRwhggA2wAQbAABtgAAywATbAABhgAwyAATbABhgAA2yAATDABtgAA2CADTAABtgAG2AAA2yADTAABtgAA2CADbABBsAAG2AADLABNsAAGGADDIABNsAGGAADbIABMMAG2AADGGADbIABMMAGGAADbIANMAAG2AADYIANsAEGwAAbYAAMsAE2wAAYYAMMgAE2wAYYwAAbYAMMgAE2wAAYYANsgAEwwAYYAANsgA0wAAbYAANggA2wAQbAABtgAAywATbAAAbYABtgAAywAQbAABtgAwyAATbAABhgA2yAATDABhgAA2yADTAABtgAA2CADbABBsAAG2AADLABBsAAG2ADDIABNsAAGGADbIABMMAGGAADbIANMAAG2AADYIANsAEGwAAbYAAMsAEGwAAbYAMMgAE2wAAYYANsgAEwwAYYAANsgA0wAAbYAANggA2wAQbAABtgAAywAQbAABtgAwyAATbAABhgA2yAATDABhgAA2yADTAABtgAA2CADbABBsAAG2AADLABBsAAG2ADDIABNsAAGGADbIABMMAGGAADbICvHwBAOANsgAEwwAYYAANsgCVJCs8AS5JkgCVJMsCSJMkAS5JkgCVJkgGWJMkAS5IkAyxJkgGWJEkGWJIkAyxJkgGWJEkGWJIkAyxJkgywJEkGWJIkGWBJkgywJEkywJIkGWBJkgywAZYkyQBLkmSAJUmSAZYkyQBLkiQDLEmSAZYkSQZYkiQDLEmSATbAkiQZYEmSDLAkSTLAkiQZYEmSZIAlSTLAkiTJAEuSZIAlSTLABliSJAMsSZIBliRJBliSJAMsSZIMsCRJBliSJBlgSZIMsCRJBtgAS5JkgCVJMsCSJMkAS5JkgCVJkgGWJMkAS5IkAyxJkgGWJMkAG2BJkgywJEkGWJIkGWBJkgywJEkywJIkGWBJkmSAJUkywJIkGWADLEmSAZYkyQBLkiQDLEmSAZYkSQZYkiQDLEmSDLAkSQZYkiQDbIAlSTLAkiQZYEmSZIAlSTLAkiTJAEuSZIAlSZIBliTJAEuSZIANsCRJBliSJAMsSZIMsCRJBliSJBlgSZIMsCRJMsCSJBlgSZJkgCVJMsCSJBlgSZJkgCVJMsCSJMkAS5JkgCVJkgGWJMkAS5IkAyxJkgGWJMkAG2BJkgywJEkGWJIkGWBJkgywJEkywJIkGWBJkmSAJUkywJIkGWADLEmSAZYkyQBLkiQDLEmSAZYkSQZYkiQDLEmSDLAkSQZYkiQDbIAlSTLAkiQZYEmSZIAlSTLAkiTJAEuSZIAlSZIBliTJAEuSZIANsCRJBliSJAMsSZIMsCRJBliSJBlgSZIMsCRJMsCSJBlgSZIMsAGWJMkAS5JkgCVJkgGWJMkAS5IkAyxJkgGWJEkGWJIkAyxJkgE2wJIkGWBJkgywJEkywJIkGWBJkmSAJUkywJIkyQBLkmSAJUkywAZYkiQDLEmSAZYkSQZYkiQDLEmSDLAkSQZYkiQZYEmSDLAkSQbYAEuSZIAlSTLAkiTJAEuSZIAlSZIBliTJAEuSJAMsSZIBliRJBliSpK8GGAAY4xMAwAADQIANOM+x56UXUawAAAAASUVORK5CYII="), (e) => e.charCodeAt(0));
3
+ //#endregion
4
+ export { e as FRAME_PNG };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The kit's frame: the picture every app file's box is drawn on, and its
3
+ * slots, in the picture's px. A placeholder: a white card on 160 × 205
4
+ * system px drawn at 3×, the artwork's slot on a gray backdrop inside a
5
+ * black rule, then the name, the version and the author, centered.
6
+ *
7
+ * The picture is box/frame.png (`npm run embed:frame`). A redrawn picture
8
+ * moves its slots here.
9
+ */
10
+ import type { BoxFrame } from './box.js';
11
+ export declare const FRAME: BoxFrame;
@@ -0,0 +1,42 @@
1
+ import { VF_BODY_STRIKE as e } from "../styles/body-strike.js";
2
+ import { VF_DISPLAY_STRIKE as t } from "../styles/display-strike.js";
3
+ import { FRAME_PNG as n } from "./frame-picture.js";
4
+ //#region src/build/frame.ts
5
+ var r = {
6
+ left: 48,
7
+ width: 384,
8
+ scale: 3,
9
+ ink: "#000000",
10
+ align: "center"
11
+ }, i = {
12
+ picture: n,
13
+ artwork: {
14
+ left: 48,
15
+ top: 48,
16
+ width: 384,
17
+ height: 384,
18
+ stamp: 8
19
+ },
20
+ text: [
21
+ {
22
+ ...r,
23
+ top: 456,
24
+ strike: t,
25
+ text: "{name}"
26
+ },
27
+ {
28
+ ...r,
29
+ top: 510,
30
+ strike: e,
31
+ text: "Version {version}"
32
+ },
33
+ {
34
+ ...r,
35
+ top: 546,
36
+ strike: e,
37
+ text: "by {author}"
38
+ }
39
+ ]
40
+ };
41
+ //#endregion
42
+ export { i as FRAME };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * `vintage-frames/build` — app files, written: the Vite plugin that makes an
3
+ * application's build write its app file, the one that builds app files
4
+ * into a site, and the pieces under them: the packer, the writer and the box
5
+ * in the kit's frame. Build-only: it runs under Node, and no page imports
6
+ * it. Reading an app file is `vintage-frames/shell/pure`'s.
7
+ * docs/APP-FILES.md is the guide.
8
+ */
9
+ export { appFile, appFiles } from './plugins.js';
10
+ export type { AppFileOptions } from './plugins.js';
11
+ export { packApp, writeAppFile } from './app-file.js';
12
+ export type { PackAppInput } from './app-file.js';
13
+ export { composeBox } from './box.js';
14
+ export type { BoxInput } from './box.js';
@@ -0,0 +1,4 @@
1
+ import { composeBox as e } from "./box.js";
2
+ import { packApp as t, writeAppFile as n } from "./app-file.js";
3
+ import { appFile as r, appFiles as i } from "./plugins.js";
4
+ export { r as appFile, i as appFiles, e as composeBox, t as packApp, n as writeAppFile };
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The Vite plugins. `appFile()` makes an application's build write its app
3
+ * file; `appFiles()` builds app files into a site.
4
+ */
5
+ import type { Plugin } from 'vite';
6
+ export interface AppFileOptions {
7
+ /** The application's id, name, version and author: its `app.ts`. */
8
+ app: {
9
+ id: string;
10
+ name: string;
11
+ version: string;
12
+ author: string;
13
+ };
14
+ /** The module whose default export is the application's factory. */
15
+ entry: string;
16
+ /** Its 32 × 32 icon, a PNG. */
17
+ icon: string;
18
+ /** Its artwork for the box: a PNG at the artwork slot's size, or that divided by a whole number. */
19
+ artwork?: string;
20
+ /** A directory to write the app file to as well, such as a site's `apps/`. */
21
+ copyTo?: string;
22
+ }
23
+ /**
24
+ * Makes an application's build write its app file, `dist/<name>.png`. It
25
+ * builds the entry as a library, one ES module with the kit's entries left
26
+ * as imports, and fails the build when the bundle breaks a rule: CSS or any
27
+ * other file beside the code, more than one module, Lit or the kit bundled
28
+ * in, or an import of anything but the kit's three entries. `npm run dev`
29
+ * is untouched.
30
+ */
31
+ export declare function appFile(options: AppFileOptions): Plugin;
32
+ /**
33
+ * Builds app files into a site. An import of `*.png?app` reads the file and
34
+ * refuses one that isn't an app file, or whose `requires` this kit's
35
+ * version doesn't meet. Its default export is the application's factory,
36
+ * its definition given the manifest's icon, and `manifest` is the manifest.
37
+ * The code imports the kit by name, so the page keeps one copy of it. A new
38
+ * file reloads the dev server's page.
39
+ */
40
+ export declare function appFiles(): Plugin;
@@ -0,0 +1,129 @@
1
+ import { inspectAppFile as e, satisfies as t } from "../shell/app-file.js";
2
+ import { VERSION as n } from "../shell/version.js";
3
+ import { packApp as r } from "./app-file.js";
4
+ import { mkdir as i, readFile as a, writeFile as o } from "node:fs/promises";
5
+ import { join as s, relative as c, resolve as l } from "node:path";
6
+ //#region src/build/plugins.ts
7
+ var u = [
8
+ "vintage-frames",
9
+ "vintage-frames/shell",
10
+ "vintage-frames/shell/pure"
11
+ ], d = "app-code";
12
+ function f(e) {
13
+ let t = e.match(/(?<=[\\/]node_modules[\\/])(?:@[^\\/]+[\\/])?[^\\/]+/g)?.at(-1)?.replace("\\", "/");
14
+ return t === "vintage-frames" ? "a copy of vintage-frames" : t && /^(?:lit|lit-html|lit-element|@lit\/.+|@lit-labs\/.+)$/.test(t) ? `Lit (${t})` : null;
15
+ }
16
+ async function p(e) {
17
+ let t = JSON.parse(await a(s(e, "package.json"), "utf8")), r = t.devDependencies?.["vintage-frames"] ?? t.dependencies?.["vintage-frames"];
18
+ if (typeof r != "string") throw Error("package.json has no vintage-frames dev dependency");
19
+ if (!/^\^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(r)) throw Error(`package.json's vintage-frames range is "${r}", and an app file requires a caret range: ^${n}`);
20
+ return r;
21
+ }
22
+ function m(e) {
23
+ let { app: c, entry: d, icon: m, artwork: h, copyTo: g } = e;
24
+ for (let e of [
25
+ "id",
26
+ "name",
27
+ "version",
28
+ "author"
29
+ ]) if (typeof c?.[e] != "string" || !c[e]) throw Error(`vintage-frames appFile(): app.${e} is missing`);
30
+ if (!d || !m) throw Error(`vintage-frames appFile(): ${d ? "icon" : "entry"} is missing`);
31
+ let _ = `${c.name.replace(/[\\/:*?"<>|]/g, "-")}.png`, v = "", y = null;
32
+ return {
33
+ name: "vintage-frames:app-file",
34
+ apply: "build",
35
+ config: () => ({ build: {
36
+ lib: {
37
+ entry: d,
38
+ formats: ["es"],
39
+ fileName: "app"
40
+ },
41
+ copyPublicDir: !1,
42
+ rolldownOptions: {
43
+ external: /^vintage-frames(?:\/|$)/,
44
+ output: { codeSplitting: !1 }
45
+ }
46
+ } }),
47
+ configResolved(e) {
48
+ v = e.root;
49
+ },
50
+ buildStart() {
51
+ for (let e of [
52
+ m,
53
+ h,
54
+ "package.json"
55
+ ]) e && this.addWatchFile(l(v, e));
56
+ },
57
+ generateBundle: {
58
+ order: "post",
59
+ async handler(e, i) {
60
+ let o = Object.values(i), s = o.filter((e) => e.type === "chunk"), d = o.filter((e) => e.type === "asset").map((e) => e.fileName);
61
+ d.length && this.error(`${c.name} emits ${d.join(", ")} beside its code, and an app file holds one module: no CSS import, no source map file, its art inlined.`);
62
+ let [g] = s;
63
+ (!g || s.length > 1) && this.error(`${c.name} builds to ${s.length} modules, and an app file holds one.`);
64
+ for (let e of Object.keys(g.modules)) {
65
+ let t = f(e);
66
+ t && this.error(`${c.name} bundles ${t}. It composes the page's kit and brings none of its own.`);
67
+ }
68
+ let b = [...g.imports, ...g.dynamicImports].filter((e) => !u.includes(e));
69
+ b.length && this.error(`${c.name} imports ${b.join(", ")}. An application imports ${u.join(", ")} and its own files.`);
70
+ try {
71
+ let e = await p(v);
72
+ if (!t("0.17.0", e)) throw Error(`package.json requires vintage-frames ${e}, and the build is on ${n}: move the range`);
73
+ y = await r({
74
+ manifest: {
75
+ id: c.id,
76
+ name: c.name,
77
+ version: c.version,
78
+ requires: e,
79
+ author: c.author
80
+ },
81
+ icon: new Uint8Array(await a(l(v, m))),
82
+ code: g.code,
83
+ artwork: h ? new Uint8Array(await a(l(v, h))) : void 0
84
+ });
85
+ } catch (e) {
86
+ this.error(`${c.name}: ${e.message}`);
87
+ }
88
+ delete i[g.fileName], this.emitFile({
89
+ type: "asset",
90
+ fileName: _,
91
+ source: y
92
+ });
93
+ }
94
+ },
95
+ async writeBundle() {
96
+ if (!g || !y) return;
97
+ let e = l(v, g);
98
+ await i(e, { recursive: !0 }), await o(s(e, _), y);
99
+ }
100
+ };
101
+ }
102
+ function h() {
103
+ let r = "";
104
+ return {
105
+ name: "vintage-frames:app-files",
106
+ enforce: "pre",
107
+ configResolved(e) {
108
+ r = e.root;
109
+ },
110
+ async load(i) {
111
+ let o = i.indexOf("?");
112
+ if (o < 0) return null;
113
+ let s = i.slice(0, o), l = new URLSearchParams(i.slice(o + 1));
114
+ if (!s.endsWith(".png") || !(l.has("app") || l.has(d))) return null;
115
+ this.addWatchFile(s);
116
+ let u = c(r, s), f = await e(new Uint8Array(await a(s)));
117
+ typeof f == "string" && this.error(`${u} is not an app file: ${f}.`);
118
+ let { manifest: p, code: m } = f;
119
+ return l.has(d) ? m : (t("0.17.0", p.requires) || this.error(`${u}: ${p.name} ${p.version} requires vintage-frames ${p.requires}, and this site has ${n}. Build ${p.name} on ${n} and copy its app file in.`), [
120
+ `import factory from ${JSON.stringify(`${s}?${d}`)}`,
121
+ `export const manifest = ${JSON.stringify(p)}`,
122
+ "export default (...args) => ({ ...factory(...args), icon: manifest.icon })",
123
+ ""
124
+ ].join("\n"));
125
+ }
126
+ };
127
+ }
128
+ //#endregion
129
+ export { m as appFile, h as appFiles };
@@ -0,0 +1,24 @@
1
+ /**
2
+ * PNG pixels, with no canvas and no dependency: a decoder for the forms art
3
+ * comes in, and an encoder for the box.
4
+ *
5
+ * - Decoding takes 8-bit gray, RGB, gray with alpha and RGBA, and indexed
6
+ * or gray at 1, 2, 4 or 8 bits, non-interlaced, with their transparency.
7
+ * 16-bit and interlaced files are refused, naming what to save them as.
8
+ * - Encoding writes 8-bit RGBA, each row filtered the way that compresses
9
+ * it best.
10
+ */
11
+ /** Straight RGBA, 8 bits a channel, rows top first. */
12
+ export interface Pixels {
13
+ width: number;
14
+ height: number;
15
+ data: Uint8Array;
16
+ }
17
+ /** `parts`, end to end. */
18
+ export declare function concat(parts: readonly Uint8Array[]): Uint8Array;
19
+ /** One chunk, with its length and CRC. */
20
+ export declare function pngChunk(type: string, data: Uint8Array): Uint8Array;
21
+ /** A PNG's pixels. `what` names it in an error: "the artwork". */
22
+ export declare function decodePng(bytes: Uint8Array, what?: string): Promise<Pixels>;
23
+ /** Pixels as an 8-bit RGBA PNG. */
24
+ export declare function encodePng({ width, height, data }: Pixels): Promise<Uint8Array>;
@@ -0,0 +1,116 @@
1
+ import { PNG_SIGNATURE as e, crc32 as t, deflate as n, inflate as r, pngChunks as i } from "../shell/app-file.js";
2
+ //#region src/build/png.ts
3
+ var a = {
4
+ 0: 1,
5
+ 2: 3,
6
+ 3: 1,
7
+ 4: 2,
8
+ 6: 4
9
+ };
10
+ function o(e) {
11
+ let t = new Uint8Array(e.reduce((e, t) => e + t.length, 0)), n = 0;
12
+ for (let r of e) t.set(r, n), n += r.length;
13
+ return t;
14
+ }
15
+ function s(e, n) {
16
+ let r = new Uint8Array(12 + n.length), i = new DataView(r.buffer);
17
+ i.setUint32(0, n.length);
18
+ for (let t = 0; t < 4; t++) r[4 + t] = e.charCodeAt(t);
19
+ return r.set(n, 8), i.setUint32(8 + n.length, t(r.subarray(4, 8 + n.length))), r;
20
+ }
21
+ async function c(e, t = "the PNG") {
22
+ let n = i(e), s = n?.[0];
23
+ if (!n || s?.type !== "IHDR" || s.data.length !== 13) throw Error(`${t} is not a whole PNG`);
24
+ let c = new DataView(s.data.buffer, s.data.byteOffset, 13), l = c.getUint32(0), u = c.getUint32(4), [f, p, m, h, g] = s.data.subarray(8);
25
+ if (f === 16) throw Error(`${t} is a 16-bit PNG: save it as an 8-bit PNG`);
26
+ if (g) throw Error(`${t} is interlaced: save it as a non-interlaced PNG`);
27
+ let _ = a[p], v = p === 0 || p === 3 ? [
28
+ 1,
29
+ 2,
30
+ 4,
31
+ 8
32
+ ].includes(f) : f === 8;
33
+ if (!_ || !v || m || h) throw Error(`${t} is a kind of PNG this kit doesn't read (color type ${p}, ${f} bits): save it as an 8-bit PNG`);
34
+ let y = null, b = null, x = [];
35
+ for (let { type: e, data: t } of n) e === "PLTE" ? y = t : e === "tRNS" ? b = t : e === "IDAT" && x.push(t);
36
+ if (p === 3 && !y) throw Error(`${t} is indexed and has no palette`);
37
+ let S = _ * f, C = Math.ceil(l * S / 8), w = await r(o(x)).catch(() => {
38
+ throw Error(`${t} is not a whole PNG`);
39
+ });
40
+ if (w.length < u * (C + 1)) throw Error(`${t} is not a whole PNG`);
41
+ let T = d(w, u, C, Math.max(1, S >> 3), t), E = (1 << f) - 1, D = (e, t) => {
42
+ let n = t * f;
43
+ return T[e + (n >> 3)] >> 8 - f - (n & 7) & E;
44
+ }, O = (e) => b && b.length >= e * 2 + 2 ? b[e * 2] << 8 | b[e * 2 + 1] : -1, k = new Uint8Array(l * u * 4);
45
+ for (let e = 0; e < u; e++) {
46
+ let n = e * C;
47
+ for (let r = 0; r < l; r++) {
48
+ let i = (e * l + r) * 4;
49
+ if (p === 0) {
50
+ let e = f === 8 ? T[n + r] : D(n, r);
51
+ k[i] = k[i + 1] = k[i + 2] = Math.round(e * 255 / E), k[i + 3] = e === O(0) ? 0 : 255;
52
+ } else if (p === 3) {
53
+ let e = f === 8 ? T[n + r] : D(n, r);
54
+ if (e * 3 + 2 >= y.length) throw Error(`${t} uses a color its palette lacks`);
55
+ k[i] = y[e * 3], k[i + 1] = y[e * 3 + 1], k[i + 2] = y[e * 3 + 2], k[i + 3] = b?.[e] ?? 255;
56
+ } else if (p === 2) {
57
+ let e = n + r * 3;
58
+ k.set(T.subarray(e, e + 3), i);
59
+ let t = T[e] !== O(0) || T[e + 1] !== O(1) || T[e + 2] !== O(2);
60
+ k[i + 3] = t ? 255 : 0;
61
+ } else if (p === 4) {
62
+ let e = n + r * 2;
63
+ k[i] = k[i + 1] = k[i + 2] = T[e], k[i + 3] = T[e + 1];
64
+ } else k.set(T.subarray(n + r * 4, n + r * 4 + 4), i);
65
+ }
66
+ }
67
+ return {
68
+ width: l,
69
+ height: u,
70
+ data: k
71
+ };
72
+ }
73
+ function l(e, t, n) {
74
+ let r = e + t - n, i = Math.abs(r - e), a = Math.abs(r - t), o = Math.abs(r - n);
75
+ return i <= a && i <= o ? e : a <= o ? t : n;
76
+ }
77
+ function u(e, t, n, r) {
78
+ return e === 1 ? t : e === 2 ? n : e === 3 ? t + n >> 1 : e === 4 ? l(t, n, r) : 0;
79
+ }
80
+ function d(e, t, n, r, i) {
81
+ let a = new Uint8Array(t * n);
82
+ for (let o = 0; o < t; o++) {
83
+ let t = e[o * (n + 1)];
84
+ if (t > 4) throw Error(`${i} is not a whole PNG`);
85
+ let s = o * (n + 1) + 1, c = o * n;
86
+ for (let i = 0; i < n; i++) {
87
+ let l = i >= r ? a[c + i - r] : 0, d = o ? a[c - n + i] : 0, f = i >= r && o ? a[c - n + i - r] : 0;
88
+ a[c + i] = e[s + i] + u(t, l, d, f) & 255;
89
+ }
90
+ }
91
+ return a;
92
+ }
93
+ async function f({ width: t, height: r, data: i }) {
94
+ let a = t * 4, c = new Uint8Array(r * (a + 1)), l = Array.from({ length: 5 }, () => new Uint8Array(a));
95
+ for (let e = 0; e < r; e++) {
96
+ let t = e * a, n = 0, r = Infinity;
97
+ for (let o = 0; o < 5; o++) {
98
+ let s = l[o], c = 0;
99
+ for (let n = 0; n < a; n++) {
100
+ let r = n >= 4 ? i[t + n - 4] : 0, l = e ? i[t - a + n] : 0, d = n >= 4 && e ? i[t - a + n - 4] : 0, f = i[t + n] - u(o, r, l, d) & 255;
101
+ s[n] = f, c += f < 128 ? f : 256 - f;
102
+ }
103
+ c < r && (r = c, n = o);
104
+ }
105
+ c[e * (a + 1)] = n, c.set(l[n], e * (a + 1) + 1);
106
+ }
107
+ let d = /* @__PURE__ */ new Uint8Array(13), f = new DataView(d.buffer);
108
+ return f.setUint32(0, t), f.setUint32(4, r), d[8] = 8, d[9] = 6, o([
109
+ e,
110
+ s("IHDR", d),
111
+ s("IDAT", await n(c)),
112
+ s("IEND", /* @__PURE__ */ new Uint8Array())
113
+ ]);
114
+ }
115
+ //#endregion
116
+ export { o as concat, c as decodePng, f as encodePng, s as pngChunk };