loopwind 0.21.0 → 0.23.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 +9 -2
- package/dist/cli.js +5 -3
- package/dist/cli.js.map +1 -1
- package/dist/commands/render.d.ts +1 -0
- package/dist/commands/render.d.ts.map +1 -1
- package/dist/commands/render.js +1 -0
- package/dist/commands/render.js.map +1 -1
- package/dist/lib/encode-worker.d.ts +2 -0
- package/dist/lib/encode-worker.d.ts.map +1 -0
- package/dist/lib/encode-worker.js +29 -0
- package/dist/lib/encode-worker.js.map +1 -0
- package/dist/lib/mjpeg-muxer.d.ts +46 -0
- package/dist/lib/mjpeg-muxer.d.ts.map +1 -0
- package/dist/lib/mjpeg-muxer.js +513 -0
- package/dist/lib/mjpeg-muxer.js.map +1 -0
- package/dist/lib/render-core.d.ts +63 -0
- package/dist/lib/render-core.d.ts.map +1 -0
- package/dist/lib/render-core.js +65 -0
- package/dist/lib/render-core.js.map +1 -0
- package/dist/lib/renderer.d.ts.map +1 -1
- package/dist/lib/renderer.js +10 -7
- package/dist/lib/renderer.js.map +1 -1
- package/dist/lib/resvg-init.d.ts +15 -0
- package/dist/lib/resvg-init.d.ts.map +1 -0
- package/dist/lib/resvg-init.js +55 -0
- package/dist/lib/resvg-init.js.map +1 -0
- package/dist/lib/tailwind/colors.d.ts +8 -0
- package/dist/lib/tailwind/colors.d.ts.map +1 -0
- package/dist/lib/tailwind/colors.js +102 -0
- package/dist/lib/tailwind/colors.js.map +1 -0
- package/dist/lib/tailwind/index.d.ts +10 -0
- package/dist/lib/tailwind/index.d.ts.map +1 -0
- package/dist/lib/tailwind/index.js +9 -0
- package/dist/lib/tailwind/index.js.map +1 -0
- package/dist/lib/tailwind/resolvers.d.ts +28 -0
- package/dist/lib/tailwind/resolvers.d.ts.map +1 -0
- package/dist/lib/tailwind/resolvers.js +94 -0
- package/dist/lib/tailwind/resolvers.js.map +1 -0
- package/dist/lib/tailwind/types.d.ts +29 -0
- package/dist/lib/tailwind/types.d.ts.map +1 -0
- package/dist/lib/tailwind/types.js +8 -0
- package/dist/lib/tailwind/types.js.map +1 -0
- package/dist/lib/tailwind-config-loader.d.ts +8 -45
- package/dist/lib/tailwind-config-loader.d.ts.map +1 -1
- package/dist/lib/tailwind-config-loader.js +6 -429
- package/dist/lib/tailwind-config-loader.js.map +1 -1
- package/dist/lib/tailwind.d.ts +1 -1
- package/dist/lib/tailwind.d.ts.map +1 -1
- package/dist/lib/tailwind.js +1 -1
- package/dist/lib/tailwind.js.map +1 -1
- package/dist/lib/video-preview.d.ts.map +1 -1
- package/dist/lib/video-preview.js +28 -16
- package/dist/lib/video-preview.js.map +1 -1
- package/dist/lib/video-renderer.d.ts +19 -1
- package/dist/lib/video-renderer.d.ts.map +1 -1
- package/dist/lib/video-renderer.js +194 -70
- package/dist/lib/video-renderer.js.map +1 -1
- package/dist/sdk/edge/index.d.ts +91 -0
- package/dist/sdk/edge/index.d.ts.map +1 -0
- package/dist/sdk/edge/index.js +187 -0
- package/dist/sdk/edge/index.js.map +1 -0
- package/dist/sdk/workers/index.d.ts +135 -0
- package/dist/sdk/workers/index.d.ts.map +1 -0
- package/dist/sdk/workers/index.js +271 -0
- package/dist/sdk/workers/index.js.map +1 -0
- package/dist/sdk/workers/tailwind-config.d.ts +48 -0
- package/dist/sdk/workers/tailwind-config.d.ts.map +1 -0
- package/dist/sdk/workers/tailwind-config.js +187 -0
- package/dist/sdk/workers/tailwind-config.js.map +1 -0
- package/dist/sdk/workers/tailwind.d.ts +9 -0
- package/dist/sdk/workers/tailwind.d.ts.map +1 -0
- package/dist/sdk/workers/tailwind.js +8 -0
- package/dist/sdk/workers/tailwind.js.map +1 -0
- package/package.json +6 -2
- package/test-cloudflare-worker/README.md +64 -0
- package/test-cloudflare-worker/dist/README.md +1 -0
- package/test-cloudflare-worker/dist/index.js +23743 -0
- package/test-cloudflare-worker/dist/index.js.map +8 -0
- package/test-cloudflare-worker/package-lock.json +1773 -0
- package/test-cloudflare-worker/package.json +25 -0
- package/test-cloudflare-worker/test-sdk.mjs +75 -0
- package/test-cloudflare-worker/wrangler.toml +14 -0
- package/test-video-720p.mjs +96 -0
- package/test-video-breakdown.mjs +98 -0
- package/test-video-perf-1080.mjs +67 -0
- package/test-video-perf.mjs +56 -0
- package/test-worker-1080p.mjs +103 -0
- package/test-worker-viability.mjs +140 -0
- package/website/astro.config.mjs +4 -9
- package/website/dist/_astro/PlaygroundEditor.DzFavsm8.js +26 -0
- package/website/dist/_astro/VideoPreviewClient.BrajhYmh.js +1 -0
- package/website/dist/_astro/agents.CZXv4DCM.css +1 -0
- package/website/dist/_astro/client.BHSq4mdQ.js +33 -0
- package/website/dist/_astro/index.CTbGshLK.js +9 -0
- package/website/dist/_astro/jsx-runtime.BjG_zV1W.js +9 -0
- package/website/dist/_routes.json +1 -0
- package/website/dist/_worker.js/_@astrojs-ssr-adapter.mjs +4 -4
- package/website/dist/_worker.js/_astro-internal_middleware.mjs +2 -2
- package/website/dist/_worker.js/chunks/Logo_Cud5QvBJ.mjs +22 -0
- package/website/dist/_worker.js/chunks/_@astro-renderers_-YVK7NHa.mjs +15015 -0
- package/website/dist/_worker.js/chunks/astro/{server_Y5_QHO8v.mjs → server_CsUrSZgd.mjs} +113 -2
- package/website/dist/_worker.js/chunks/{astro-designed-error-pages_BNTLO-TA.mjs → astro-designed-error-pages_1ELXm5Tt.mjs} +1 -1
- package/website/dist/_worker.js/chunks/{index_C1UTDwYg.mjs → index_BDWR1Q-q.mjs} +2 -2
- package/website/dist/_worker.js/chunks/{noop-middleware_DlWGj5t5.mjs → noop-middleware_B8fH5jha.mjs} +1 -1
- package/website/dist/_worker.js/index.js +38 -30
- package/website/dist/_worker.js/manifest_Bk6136-u.mjs +98 -0
- package/website/dist/_worker.js/pages/_image.astro.mjs +1 -1
- package/website/dist/_worker.js/pages/api/playground/render.astro.mjs +25562 -0
- package/website/dist/_worker.js/pages/api/playground/templates.astro.mjs +92 -0
- package/website/dist/_worker.js/pages/api/raw-markdown/_---path_.astro.mjs +1 -1
- package/website/dist/_worker.js/pages/playground/_example_.astro.mjs +95 -0
- package/website/dist/_worker.js/pages/playground.astro.mjs +1 -0
- package/website/dist/_worker.js/renderers.mjs +1 -56
- package/website/dist/agents/index.html +4 -2
- package/website/dist/animation/index.html +629 -3
- package/website/dist/config/index.html +4 -2
- package/website/dist/fonts/index.html +4 -2
- package/website/dist/getting-started/index.html +4 -2
- package/website/dist/helpers/index.html +196 -10
- package/website/dist/images/index.html +4 -2
- package/website/dist/index.html +4 -3
- package/website/dist/llm.txt +870 -20
- package/website/dist/playground/index.html +6 -0
- package/website/dist/preview/index.html +4 -2
- package/website/dist/sdk/index.html +639 -127
- package/website/dist/sitemap.xml +12 -12
- package/website/dist/styling/index.html +4 -2
- package/website/dist/templates/index.html +4 -2
- package/website/dist/video/index.html +47 -12
- package/website/package-lock.json +11 -1
- package/website/package.json +3 -1
- package/website/wrangler.toml +9 -0
- package/_dsgn/templates/dashed-stroke-test/template.tsx +0 -73
- package/_dsgn/templates/path-follow-test/template.tsx +0 -176
- package/_dsgn/templates/path-simple-test/template.tsx +0 -98
- package/_dsgn/templates/stroke-dash-test/meta.json +0 -12
- package/_dsgn/templates/stroke-dash-test/template.tsx +0 -53
- package/website/dist/_astro/agents.Yx-L_igG.css +0 -1
- package/website/dist/_worker.js/manifest_CT_D-YDe.mjs +0 -98
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loopwind SDK for Edge Runtimes (Cloudflare Workers, Vercel Edge, etc.)
|
|
3
|
+
*
|
|
4
|
+
* This module provides image rendering without Node.js dependencies.
|
|
5
|
+
* Uses @resvg/resvg-wasm instead of @resvg/resvg-js.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { renderImage, defineTemplate } from 'loopwind/sdk/edge';
|
|
10
|
+
*
|
|
11
|
+
* const template = defineTemplate({
|
|
12
|
+
* name: 'og-image',
|
|
13
|
+
* size: { width: 1200, height: 630 },
|
|
14
|
+
* render: ({ tw, title }) => <div style={tw('flex')}>{title}</div>
|
|
15
|
+
* });
|
|
16
|
+
*
|
|
17
|
+
* const png = await renderImage(template, { title: 'Hello' });
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
import satori from 'satori';
|
|
21
|
+
import { Resvg, initWasm } from '@resvg/resvg-wasm';
|
|
22
|
+
// @ts-ignore - WASM import handled by bundler
|
|
23
|
+
import resvgWasm from '@resvg/resvg-wasm/index_bg.wasm?init';
|
|
24
|
+
import { tw as createTwHelper } from '../../lib/tailwind.js';
|
|
25
|
+
// Re-export template definition helpers (these don't use Node.js APIs)
|
|
26
|
+
export { defineTemplate, defineTemplateFromSchema } from '../template.js';
|
|
27
|
+
let wasmInitialized = false;
|
|
28
|
+
let wasmInitPromise = null;
|
|
29
|
+
/**
|
|
30
|
+
* Initialize WASM module (called automatically on first render)
|
|
31
|
+
*/
|
|
32
|
+
export async function initResvgWasm() {
|
|
33
|
+
if (wasmInitialized)
|
|
34
|
+
return;
|
|
35
|
+
if (wasmInitPromise) {
|
|
36
|
+
await wasmInitPromise;
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
wasmInitPromise = (async () => {
|
|
40
|
+
try {
|
|
41
|
+
// Try to initialize with the bundled WASM
|
|
42
|
+
if (typeof resvgWasm === 'function') {
|
|
43
|
+
const wasmModule = await resvgWasm();
|
|
44
|
+
await initWasm(wasmModule);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
await initWasm(resvgWasm);
|
|
48
|
+
}
|
|
49
|
+
wasmInitialized = true;
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
// If already initialized, ignore
|
|
53
|
+
if (String(error).includes('already been initialized')) {
|
|
54
|
+
wasmInitialized = true;
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
})();
|
|
61
|
+
await wasmInitPromise;
|
|
62
|
+
}
|
|
63
|
+
// Font cache
|
|
64
|
+
let fontCache = null;
|
|
65
|
+
/**
|
|
66
|
+
* Load default Inter font from CDN
|
|
67
|
+
*/
|
|
68
|
+
async function loadDefaultFonts() {
|
|
69
|
+
if (fontCache)
|
|
70
|
+
return fontCache;
|
|
71
|
+
const fonts = [];
|
|
72
|
+
try {
|
|
73
|
+
// Load Inter Regular
|
|
74
|
+
const regularResponse = await fetch('https://cdn.jsdelivr.net/npm/@fontsource/inter@5.0.18/files/inter-latin-400-normal.woff2');
|
|
75
|
+
if (regularResponse.ok) {
|
|
76
|
+
fonts.push({
|
|
77
|
+
name: 'Inter',
|
|
78
|
+
data: await regularResponse.arrayBuffer(),
|
|
79
|
+
weight: 400,
|
|
80
|
+
style: 'normal',
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
// Load Inter Bold
|
|
84
|
+
const boldResponse = await fetch('https://cdn.jsdelivr.net/npm/@fontsource/inter@5.0.18/files/inter-latin-700-normal.woff2');
|
|
85
|
+
if (boldResponse.ok) {
|
|
86
|
+
fonts.push({
|
|
87
|
+
name: 'Inter',
|
|
88
|
+
data: await boldResponse.arrayBuffer(),
|
|
89
|
+
weight: 700,
|
|
90
|
+
style: 'normal',
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
console.warn('Failed to load default fonts:', error);
|
|
96
|
+
}
|
|
97
|
+
fontCache = fonts;
|
|
98
|
+
return fonts;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Render an image from a template definition (Edge-compatible)
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```typescript
|
|
105
|
+
* const template = {
|
|
106
|
+
* name: 'og-image',
|
|
107
|
+
* size: { width: 1200, height: 630 },
|
|
108
|
+
* render: ({ tw, title }) => (
|
|
109
|
+
* <div style={tw('flex items-center justify-center w-full h-full bg-black')}>
|
|
110
|
+
* <h1 style={tw('text-6xl font-bold text-white')}>{title}</h1>
|
|
111
|
+
* </div>
|
|
112
|
+
* )
|
|
113
|
+
* };
|
|
114
|
+
*
|
|
115
|
+
* const png = await renderImage(template, { title: 'Hello World' });
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
export async function renderImage(template, props, options = {}) {
|
|
119
|
+
const { format = 'png', scale = 2, fonts: customFonts, config } = options;
|
|
120
|
+
// Merge configs
|
|
121
|
+
const mergedConfig = {
|
|
122
|
+
...template.config,
|
|
123
|
+
...config,
|
|
124
|
+
colors: { ...template.config?.colors, ...config?.colors },
|
|
125
|
+
fonts: { ...template.config?.fonts, ...config?.fonts },
|
|
126
|
+
};
|
|
127
|
+
// Create tw helper
|
|
128
|
+
const tw = (classes) => createTwHelper(classes, undefined, mergedConfig);
|
|
129
|
+
// Load fonts
|
|
130
|
+
const fonts = customFonts || await loadDefaultFonts();
|
|
131
|
+
// Render to SVG with satori
|
|
132
|
+
const svg = await satori(template.render({ ...props, tw, config: mergedConfig }), {
|
|
133
|
+
width: template.size.width,
|
|
134
|
+
height: template.size.height,
|
|
135
|
+
fonts: fonts.map(f => ({
|
|
136
|
+
name: f.name,
|
|
137
|
+
data: f.data,
|
|
138
|
+
weight: f.weight || 400,
|
|
139
|
+
style: f.style || 'normal',
|
|
140
|
+
})),
|
|
141
|
+
});
|
|
142
|
+
if (format === 'svg') {
|
|
143
|
+
return new TextEncoder().encode(svg).buffer;
|
|
144
|
+
}
|
|
145
|
+
// Initialize WASM if needed
|
|
146
|
+
await initResvgWasm();
|
|
147
|
+
// Convert to PNG using resvg-wasm
|
|
148
|
+
const resvg = new Resvg(svg, {
|
|
149
|
+
fitTo: {
|
|
150
|
+
mode: 'width',
|
|
151
|
+
value: Math.round(template.size.width * scale),
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
const pngData = resvg.render();
|
|
155
|
+
const pngBuffer = pngData.asPng();
|
|
156
|
+
return pngBuffer.buffer;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Render to SVG only (no WASM needed)
|
|
160
|
+
*/
|
|
161
|
+
export async function renderSVG(template, props, options = {}) {
|
|
162
|
+
const { fonts: customFonts, config } = options;
|
|
163
|
+
// Merge configs
|
|
164
|
+
const mergedConfig = {
|
|
165
|
+
...template.config,
|
|
166
|
+
...config,
|
|
167
|
+
colors: { ...template.config?.colors, ...config?.colors },
|
|
168
|
+
fonts: { ...template.config?.fonts, ...config?.fonts },
|
|
169
|
+
};
|
|
170
|
+
// Create tw helper
|
|
171
|
+
const tw = (classes) => createTwHelper(classes, undefined, mergedConfig);
|
|
172
|
+
// Load fonts
|
|
173
|
+
const fonts = customFonts || await loadDefaultFonts();
|
|
174
|
+
// Render to SVG with satori
|
|
175
|
+
const svg = await satori(template.render({ ...props, tw, config: mergedConfig }), {
|
|
176
|
+
width: template.size.width,
|
|
177
|
+
height: template.size.height,
|
|
178
|
+
fonts: fonts.map(f => ({
|
|
179
|
+
name: f.name,
|
|
180
|
+
data: f.data,
|
|
181
|
+
weight: f.weight || 400,
|
|
182
|
+
style: f.style || 'normal',
|
|
183
|
+
})),
|
|
184
|
+
});
|
|
185
|
+
return svg;
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/sdk/edge/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare Workers SDK for loopwind
|
|
3
|
+
*
|
|
4
|
+
* Pure JavaScript/WASM implementation for edge runtimes.
|
|
5
|
+
* - @resvg/resvg-wasm for SVG-to-PNG conversion
|
|
6
|
+
* - h264-mp4-encoder for video encoding
|
|
7
|
+
*
|
|
8
|
+
* All rendering (image and video) works in Cloudflare Workers!
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { renderImage, renderSVG, renderVideo, defineTemplate, initWasm } from 'loopwind/sdk/workers';
|
|
13
|
+
* import React from 'react';
|
|
14
|
+
*
|
|
15
|
+
* // Image template
|
|
16
|
+
* const imageTemplate = defineTemplate({
|
|
17
|
+
* name: 'og-image',
|
|
18
|
+
* size: { width: 1200, height: 630 },
|
|
19
|
+
* render: ({ tw, title }) => React.createElement(
|
|
20
|
+
* 'div',
|
|
21
|
+
* { style: tw('flex items-center justify-center w-full h-full bg-blue-500') },
|
|
22
|
+
* React.createElement('h1', { style: tw('text-6xl font-bold text-white') }, title)
|
|
23
|
+
* )
|
|
24
|
+
* });
|
|
25
|
+
*
|
|
26
|
+
* // SVG (no WASM needed)
|
|
27
|
+
* const svg = await renderSVG(imageTemplate, { title: 'Hello World' });
|
|
28
|
+
*
|
|
29
|
+
* // PNG (requires WASM)
|
|
30
|
+
* await initWasm();
|
|
31
|
+
* const png = await renderImage(imageTemplate, { title: 'Hello World' });
|
|
32
|
+
*
|
|
33
|
+
* // Video template
|
|
34
|
+
* const videoTemplate = defineTemplate({
|
|
35
|
+
* name: 'intro',
|
|
36
|
+
* type: 'video',
|
|
37
|
+
* size: { width: 1920, height: 1080 },
|
|
38
|
+
* video: { fps: 30, duration: 2 },
|
|
39
|
+
* render: ({ tw, progress }) => React.createElement(
|
|
40
|
+
* 'div',
|
|
41
|
+
* { style: tw('flex items-center justify-center w-full h-full bg-gradient-to-r from-blue-500 to-purple-500') },
|
|
42
|
+
* React.createElement('div', { style: { opacity: progress } }, 'Fade In')
|
|
43
|
+
* )
|
|
44
|
+
* });
|
|
45
|
+
*
|
|
46
|
+
* // MP4 (requires WASM)
|
|
47
|
+
* const mp4 = await renderVideo(videoTemplate, {});
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
import { type TemplateConfig, type FontConfig, type StyleConfig } from '../../lib/render-core.js';
|
|
51
|
+
export { defineTemplate } from '../../lib/render-core.js';
|
|
52
|
+
export type { TemplateConfig, FontConfig, StyleConfig };
|
|
53
|
+
export { defineTemplateFromSchema, defineTemplateFromSource } from '../template.js';
|
|
54
|
+
export type { TemplateDefinition } from '../template.js';
|
|
55
|
+
/**
|
|
56
|
+
* Render options for workers
|
|
57
|
+
*/
|
|
58
|
+
export interface WorkerRenderOptions {
|
|
59
|
+
format?: 'png' | 'svg';
|
|
60
|
+
fonts?: FontConfig[];
|
|
61
|
+
config?: StyleConfig;
|
|
62
|
+
scale?: number;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Initialize WASM (required for PNG rendering)
|
|
66
|
+
*/
|
|
67
|
+
export declare function initWasm(resvgWasm?: ArrayBuffer): Promise<void>;
|
|
68
|
+
/**
|
|
69
|
+
* Check if WASM is initialized
|
|
70
|
+
*/
|
|
71
|
+
export declare function isWasmInitialized(): boolean;
|
|
72
|
+
/**
|
|
73
|
+
* Render to SVG string
|
|
74
|
+
*/
|
|
75
|
+
export declare function renderSVG<TProps = any>(template: TemplateConfig<TProps>, props: TProps, options?: WorkerRenderOptions): Promise<string>;
|
|
76
|
+
/**
|
|
77
|
+
* Render to PNG (requires initWasm() first)
|
|
78
|
+
*/
|
|
79
|
+
export declare function renderImage<TProps = any>(template: TemplateConfig<TProps>, props: TProps, options?: WorkerRenderOptions): Promise<Uint8Array>;
|
|
80
|
+
/**
|
|
81
|
+
* Load a font from URL (helper for custom fonts)
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```typescript
|
|
85
|
+
* const fonts = await Promise.all([
|
|
86
|
+
* loadFont('https://example.com/my-font-400.woff', 'MyFont', 400),
|
|
87
|
+
* loadFont('https://example.com/my-font-700.woff', 'MyFont', 700),
|
|
88
|
+
* ]);
|
|
89
|
+
*
|
|
90
|
+
* const png = await renderImage(template, props, { fonts });
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
export declare function loadFont(url: string, name: string, weight?: number, style?: 'normal' | 'italic'): Promise<FontConfig>;
|
|
94
|
+
/**
|
|
95
|
+
* Fetch an image and convert to data URI
|
|
96
|
+
*/
|
|
97
|
+
export declare function fetchImage(url: string): Promise<string>;
|
|
98
|
+
/**
|
|
99
|
+
* Video render options for workers
|
|
100
|
+
*/
|
|
101
|
+
export interface VideoRenderOptions {
|
|
102
|
+
fonts?: FontConfig[];
|
|
103
|
+
config?: StyleConfig;
|
|
104
|
+
quality?: number;
|
|
105
|
+
onProgress?: (frame: number, total: number) => void;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Render a video template to MP4 (requires initWasm() first)
|
|
109
|
+
*
|
|
110
|
+
* Uses h264-mp4-encoder (WASM) for encoding - works in Cloudflare Workers.
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* ```typescript
|
|
114
|
+
* await initWasm();
|
|
115
|
+
*
|
|
116
|
+
* const template = defineTemplate({
|
|
117
|
+
* name: 'video',
|
|
118
|
+
* type: 'video',
|
|
119
|
+
* size: { width: 1920, height: 1080 },
|
|
120
|
+
* video: { fps: 30, duration: 2 },
|
|
121
|
+
* render: ({ tw, progress }) => React.createElement(
|
|
122
|
+
* 'div',
|
|
123
|
+
* { style: tw('flex items-center justify-center w-full h-full bg-blue-500') },
|
|
124
|
+
* React.createElement('div', {
|
|
125
|
+
* style: { opacity: progress, ...tw('text-6xl font-bold text-white') }
|
|
126
|
+
* }, 'Fade In')
|
|
127
|
+
* )
|
|
128
|
+
* });
|
|
129
|
+
*
|
|
130
|
+
* const mp4 = await renderVideo(template, {});
|
|
131
|
+
* return new Response(mp4, { headers: { 'Content-Type': 'video/mp4' } });
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
export declare function renderVideo<TProps = any>(template: TemplateConfig<TProps>, props: TProps, options?: VideoRenderOptions): Promise<Uint8Array>;
|
|
135
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/sdk/workers/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AAIH,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,WAAW,EAEjB,MAAM,0BAA0B,CAAC;AAGlC,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC;AAGxD,OAAO,EAAE,wBAAwB,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AACpF,YAAY,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEzD;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,MAAM,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IACvB,KAAK,CAAC,EAAE,UAAU,EAAE,CAAC;IACrB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAKD;;GAEG;AACH,wBAAsB,QAAQ,CAAC,SAAS,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAarE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAyCD;;GAEG;AACH,wBAAsB,SAAS,CAAC,MAAM,GAAG,GAAG,EAC1C,QAAQ,EAAE,cAAc,CAAC,MAAM,CAAC,EAChC,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,MAAM,CAAC,CAOjB;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,MAAM,GAAG,GAAG,EAC5C,QAAQ,EAAE,cAAc,CAAC,MAAM,CAAC,EAChC,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,UAAU,CAAC,CAyBrB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,QAAQ,CAC5B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,MAAM,GAAE,MAAY,EACpB,KAAK,GAAE,QAAQ,GAAG,QAAmB,GACpC,OAAO,CAAC,UAAU,CAAC,CAWrB;AAED;;GAEG;AACH,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAc7D;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,UAAU,EAAE,CAAC;IACrB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACrD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,WAAW,CAAC,MAAM,GAAG,GAAG,EAC5C,QAAQ,EAAE,cAAc,CAAC,MAAM,CAAC,EAChC,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,UAAU,CAAC,CAgErB"}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare Workers SDK for loopwind
|
|
3
|
+
*
|
|
4
|
+
* Pure JavaScript/WASM implementation for edge runtimes.
|
|
5
|
+
* - @resvg/resvg-wasm for SVG-to-PNG conversion
|
|
6
|
+
* - h264-mp4-encoder for video encoding
|
|
7
|
+
*
|
|
8
|
+
* All rendering (image and video) works in Cloudflare Workers!
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { renderImage, renderSVG, renderVideo, defineTemplate, initWasm } from 'loopwind/sdk/workers';
|
|
13
|
+
* import React from 'react';
|
|
14
|
+
*
|
|
15
|
+
* // Image template
|
|
16
|
+
* const imageTemplate = defineTemplate({
|
|
17
|
+
* name: 'og-image',
|
|
18
|
+
* size: { width: 1200, height: 630 },
|
|
19
|
+
* render: ({ tw, title }) => React.createElement(
|
|
20
|
+
* 'div',
|
|
21
|
+
* { style: tw('flex items-center justify-center w-full h-full bg-blue-500') },
|
|
22
|
+
* React.createElement('h1', { style: tw('text-6xl font-bold text-white') }, title)
|
|
23
|
+
* )
|
|
24
|
+
* });
|
|
25
|
+
*
|
|
26
|
+
* // SVG (no WASM needed)
|
|
27
|
+
* const svg = await renderSVG(imageTemplate, { title: 'Hello World' });
|
|
28
|
+
*
|
|
29
|
+
* // PNG (requires WASM)
|
|
30
|
+
* await initWasm();
|
|
31
|
+
* const png = await renderImage(imageTemplate, { title: 'Hello World' });
|
|
32
|
+
*
|
|
33
|
+
* // Video template
|
|
34
|
+
* const videoTemplate = defineTemplate({
|
|
35
|
+
* name: 'intro',
|
|
36
|
+
* type: 'video',
|
|
37
|
+
* size: { width: 1920, height: 1080 },
|
|
38
|
+
* video: { fps: 30, duration: 2 },
|
|
39
|
+
* render: ({ tw, progress }) => React.createElement(
|
|
40
|
+
* 'div',
|
|
41
|
+
* { style: tw('flex items-center justify-center w-full h-full bg-gradient-to-r from-blue-500 to-purple-500') },
|
|
42
|
+
* React.createElement('div', { style: { opacity: progress } }, 'Fade In')
|
|
43
|
+
* )
|
|
44
|
+
* });
|
|
45
|
+
*
|
|
46
|
+
* // MP4 (requires WASM)
|
|
47
|
+
* const mp4 = await renderVideo(videoTemplate, {});
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
import { Resvg, initWasm as initResvgWasm } from '@resvg/resvg-wasm';
|
|
51
|
+
import HME from 'h264-mp4-encoder';
|
|
52
|
+
import { renderTemplateToSVG, } from '../../lib/render-core.js';
|
|
53
|
+
// Re-export core types and functions
|
|
54
|
+
export { defineTemplate } from '../../lib/render-core.js';
|
|
55
|
+
// Re-export from template module for backwards compatibility
|
|
56
|
+
export { defineTemplateFromSchema, defineTemplateFromSource } from '../template.js';
|
|
57
|
+
// WASM state
|
|
58
|
+
let wasmInitialized = false;
|
|
59
|
+
/**
|
|
60
|
+
* Initialize WASM (required for PNG rendering)
|
|
61
|
+
*/
|
|
62
|
+
export async function initWasm(resvgWasm) {
|
|
63
|
+
if (wasmInitialized)
|
|
64
|
+
return;
|
|
65
|
+
if (resvgWasm) {
|
|
66
|
+
await initResvgWasm(resvgWasm);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
const wasmUrl = 'https://unpkg.com/@resvg/resvg-wasm@2.6.2/index_bg.wasm';
|
|
70
|
+
const response = await fetch(wasmUrl);
|
|
71
|
+
const wasm = await response.arrayBuffer();
|
|
72
|
+
await initResvgWasm(wasm);
|
|
73
|
+
}
|
|
74
|
+
wasmInitialized = true;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Check if WASM is initialized
|
|
78
|
+
*/
|
|
79
|
+
export function isWasmInitialized() {
|
|
80
|
+
return wasmInitialized;
|
|
81
|
+
}
|
|
82
|
+
// Font cache
|
|
83
|
+
let defaultFontsCache = null;
|
|
84
|
+
async function getDefaultFonts() {
|
|
85
|
+
if (defaultFontsCache)
|
|
86
|
+
return defaultFontsCache;
|
|
87
|
+
const fonts = [];
|
|
88
|
+
try {
|
|
89
|
+
const [regularRes, boldRes] = await Promise.all([
|
|
90
|
+
fetch('https://cdn.jsdelivr.net/npm/@fontsource/inter@5.0.18/files/inter-latin-400-normal.woff'),
|
|
91
|
+
fetch('https://cdn.jsdelivr.net/npm/@fontsource/inter@5.0.18/files/inter-latin-700-normal.woff'),
|
|
92
|
+
]);
|
|
93
|
+
if (regularRes.ok) {
|
|
94
|
+
fonts.push({
|
|
95
|
+
name: 'Inter',
|
|
96
|
+
data: await regularRes.arrayBuffer(),
|
|
97
|
+
weight: 400,
|
|
98
|
+
style: 'normal',
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
if (boldRes.ok) {
|
|
102
|
+
fonts.push({
|
|
103
|
+
name: 'Inter',
|
|
104
|
+
data: await boldRes.arrayBuffer(),
|
|
105
|
+
weight: 700,
|
|
106
|
+
style: 'normal',
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch (e) {
|
|
111
|
+
console.warn('Failed to load default fonts:', e);
|
|
112
|
+
}
|
|
113
|
+
defaultFontsCache = fonts;
|
|
114
|
+
return fonts;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Render to SVG string
|
|
118
|
+
*/
|
|
119
|
+
export async function renderSVG(template, props, options = {}) {
|
|
120
|
+
const fonts = options.fonts || await getDefaultFonts();
|
|
121
|
+
return renderTemplateToSVG(template, props, {
|
|
122
|
+
fonts,
|
|
123
|
+
config: options.config,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Render to PNG (requires initWasm() first)
|
|
128
|
+
*/
|
|
129
|
+
export async function renderImage(template, props, options = {}) {
|
|
130
|
+
const { format = 'png', scale = 2 } = options;
|
|
131
|
+
if (!wasmInitialized) {
|
|
132
|
+
throw new Error('WASM not initialized. Call initWasm() before rendering.');
|
|
133
|
+
}
|
|
134
|
+
if (template.type === 'video') {
|
|
135
|
+
throw new Error('Cannot render video template as image.');
|
|
136
|
+
}
|
|
137
|
+
const svg = await renderSVG(template, props, options);
|
|
138
|
+
if (format === 'svg') {
|
|
139
|
+
return new TextEncoder().encode(svg);
|
|
140
|
+
}
|
|
141
|
+
const resvg = new Resvg(svg, {
|
|
142
|
+
fitTo: {
|
|
143
|
+
mode: 'width',
|
|
144
|
+
value: Math.round(template.size.width * scale),
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
return resvg.render().asPng();
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Load a font from URL (helper for custom fonts)
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* ```typescript
|
|
154
|
+
* const fonts = await Promise.all([
|
|
155
|
+
* loadFont('https://example.com/my-font-400.woff', 'MyFont', 400),
|
|
156
|
+
* loadFont('https://example.com/my-font-700.woff', 'MyFont', 700),
|
|
157
|
+
* ]);
|
|
158
|
+
*
|
|
159
|
+
* const png = await renderImage(template, props, { fonts });
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
162
|
+
export async function loadFont(url, name, weight = 400, style = 'normal') {
|
|
163
|
+
const response = await fetch(url);
|
|
164
|
+
if (!response.ok) {
|
|
165
|
+
throw new Error(`Failed to load font from ${url}: ${response.statusText}`);
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
name,
|
|
169
|
+
data: await response.arrayBuffer(),
|
|
170
|
+
weight,
|
|
171
|
+
style,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Fetch an image and convert to data URI
|
|
176
|
+
*/
|
|
177
|
+
export async function fetchImage(url) {
|
|
178
|
+
try {
|
|
179
|
+
const response = await fetch(url);
|
|
180
|
+
if (!response.ok)
|
|
181
|
+
throw new Error(`Failed to fetch: ${response.statusText}`);
|
|
182
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
183
|
+
const base64 = btoa(String.fromCharCode(...new Uint8Array(arrayBuffer)));
|
|
184
|
+
const contentType = response.headers.get('content-type') || 'image/png';
|
|
185
|
+
return `data:${contentType.split(';')[0]};base64,${base64}`;
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
console.error('Image loading failed:', error);
|
|
189
|
+
return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Render a video template to MP4 (requires initWasm() first)
|
|
194
|
+
*
|
|
195
|
+
* Uses h264-mp4-encoder (WASM) for encoding - works in Cloudflare Workers.
|
|
196
|
+
*
|
|
197
|
+
* @example
|
|
198
|
+
* ```typescript
|
|
199
|
+
* await initWasm();
|
|
200
|
+
*
|
|
201
|
+
* const template = defineTemplate({
|
|
202
|
+
* name: 'video',
|
|
203
|
+
* type: 'video',
|
|
204
|
+
* size: { width: 1920, height: 1080 },
|
|
205
|
+
* video: { fps: 30, duration: 2 },
|
|
206
|
+
* render: ({ tw, progress }) => React.createElement(
|
|
207
|
+
* 'div',
|
|
208
|
+
* { style: tw('flex items-center justify-center w-full h-full bg-blue-500') },
|
|
209
|
+
* React.createElement('div', {
|
|
210
|
+
* style: { opacity: progress, ...tw('text-6xl font-bold text-white') }
|
|
211
|
+
* }, 'Fade In')
|
|
212
|
+
* )
|
|
213
|
+
* });
|
|
214
|
+
*
|
|
215
|
+
* const mp4 = await renderVideo(template, {});
|
|
216
|
+
* return new Response(mp4, { headers: { 'Content-Type': 'video/mp4' } });
|
|
217
|
+
* ```
|
|
218
|
+
*/
|
|
219
|
+
export async function renderVideo(template, props, options = {}) {
|
|
220
|
+
if (!wasmInitialized) {
|
|
221
|
+
throw new Error('WASM not initialized. Call initWasm() before rendering video.');
|
|
222
|
+
}
|
|
223
|
+
if (template.type !== 'video') {
|
|
224
|
+
throw new Error('Template must have type: "video" to render as video.');
|
|
225
|
+
}
|
|
226
|
+
if (!template.video) {
|
|
227
|
+
throw new Error('Template must have video config (fps, duration).');
|
|
228
|
+
}
|
|
229
|
+
const { fps, duration } = template.video;
|
|
230
|
+
const { width, height } = template.size;
|
|
231
|
+
const { quality = 23, onProgress } = options;
|
|
232
|
+
const totalFrames = Math.floor(fps * duration);
|
|
233
|
+
const fonts = options.fonts || await getDefaultFonts();
|
|
234
|
+
// Generate all frames as RGBA
|
|
235
|
+
const rgbaFrames = new Array(totalFrames);
|
|
236
|
+
for (let frame = 0; frame < totalFrames; frame++) {
|
|
237
|
+
const progress = frame / (totalFrames - 1);
|
|
238
|
+
const frameProps = { ...props, frame, progress };
|
|
239
|
+
// Render SVG
|
|
240
|
+
const svg = await renderTemplateToSVG(template, frameProps, {
|
|
241
|
+
fonts,
|
|
242
|
+
config: options.config,
|
|
243
|
+
});
|
|
244
|
+
// Convert SVG to RGBA using resvg
|
|
245
|
+
const resvg = new Resvg(svg, {
|
|
246
|
+
fitTo: { mode: 'width', value: width },
|
|
247
|
+
});
|
|
248
|
+
const rendered = resvg.render();
|
|
249
|
+
rgbaFrames[frame] = rendered.pixels;
|
|
250
|
+
if (onProgress) {
|
|
251
|
+
onProgress(frame + 1, totalFrames);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
// Encode to H.264 MP4
|
|
255
|
+
const encoder = await HME.createH264MP4Encoder();
|
|
256
|
+
encoder.width = width;
|
|
257
|
+
encoder.height = height;
|
|
258
|
+
encoder.frameRate = fps;
|
|
259
|
+
encoder.quantizationParameter = quality;
|
|
260
|
+
encoder.speed = 10;
|
|
261
|
+
encoder.groupOfPictures = fps;
|
|
262
|
+
encoder.initialize();
|
|
263
|
+
for (let i = 0; i < rgbaFrames.length; i++) {
|
|
264
|
+
encoder.addFrameRgba(rgbaFrames[i]);
|
|
265
|
+
}
|
|
266
|
+
encoder.finalize();
|
|
267
|
+
const output = encoder.FS.readFile(encoder.outputFilename);
|
|
268
|
+
encoder.delete();
|
|
269
|
+
return output;
|
|
270
|
+
}
|
|
271
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/sdk/workers/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tailwind config utilities for Cloudflare Workers
|
|
3
|
+
* Pure JavaScript - no Node.js dependencies
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Tailwind config interface (simplified)
|
|
7
|
+
*/
|
|
8
|
+
export interface TailwindConfig {
|
|
9
|
+
theme?: {
|
|
10
|
+
extend?: {
|
|
11
|
+
colors?: Record<string, any>;
|
|
12
|
+
spacing?: Record<string, string>;
|
|
13
|
+
fontSize?: Record<string, any>;
|
|
14
|
+
fontFamily?: Record<string, string[]>;
|
|
15
|
+
borderRadius?: Record<string, string>;
|
|
16
|
+
[key: string]: any;
|
|
17
|
+
};
|
|
18
|
+
colors?: Record<string, any>;
|
|
19
|
+
spacing?: Record<string, string>;
|
|
20
|
+
fontSize?: Record<string, any>;
|
|
21
|
+
fontFamily?: Record<string, string[]>;
|
|
22
|
+
borderRadius?: Record<string, string>;
|
|
23
|
+
[key: string]: any;
|
|
24
|
+
};
|
|
25
|
+
[key: string]: any;
|
|
26
|
+
}
|
|
27
|
+
export declare const DEFAULT_COLORS: Record<string, any>;
|
|
28
|
+
/**
|
|
29
|
+
* Resolve a color from Tailwind config
|
|
30
|
+
* Supports: primary, blue-500, slate-200, etc.
|
|
31
|
+
*/
|
|
32
|
+
export declare function resolveColor(colorName: string, tailwindConfig: TailwindConfig | null): string | null;
|
|
33
|
+
/**
|
|
34
|
+
* Resolve spacing value from Tailwind config
|
|
35
|
+
*/
|
|
36
|
+
export declare function resolveSpacing(value: string, tailwindConfig: TailwindConfig | null): string | null;
|
|
37
|
+
/**
|
|
38
|
+
* Resolve font size from Tailwind config
|
|
39
|
+
*/
|
|
40
|
+
export declare function resolveFontSize(size: string, tailwindConfig: TailwindConfig | null): {
|
|
41
|
+
fontSize: string;
|
|
42
|
+
lineHeight?: string;
|
|
43
|
+
} | null;
|
|
44
|
+
/**
|
|
45
|
+
* Resolve border radius from Tailwind config
|
|
46
|
+
*/
|
|
47
|
+
export declare function resolveBorderRadius(value: string, tailwindConfig: TailwindConfig | null): string | null;
|
|
48
|
+
//# sourceMappingURL=tailwind-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tailwind-config.d.ts","sourceRoot":"","sources":["../../../src/sdk/workers/tailwind-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE;QACN,MAAM,CAAC,EAAE;YACP,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAC7B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YACjC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YAC/B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;YACtC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YACtC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;SACpB,CAAC;QACF,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC7B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACjC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC/B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QACtC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACtC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;KACpB,CAAC;IACF,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAGD,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CA8F9C,CAAC;AAEF;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,cAAc,GAAG,IAAI,GACpC,MAAM,GAAG,IAAI,CA+Bf;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EACb,cAAc,EAAE,cAAc,GAAG,IAAI,GACpC,MAAM,GAAG,IAAI,CAWf;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,cAAc,EAAE,cAAc,GAAG,IAAI,GACpC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CA6BlD;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,EACb,cAAc,EAAE,cAAc,GAAG,IAAI,GACpC,MAAM,GAAG,IAAI,CAWf"}
|