@ogxjs/core 0.1.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 +102 -0
- package/dist/builder.d.ts +75 -0
- package/dist/builder.d.ts.map +1 -0
- package/dist/builder.js +243 -0
- package/dist/cache.d.ts +32 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +71 -0
- package/dist/css.d.ts +83 -0
- package/dist/css.d.ts.map +1 -0
- package/dist/css.js +1 -0
- package/dist/font-registry.d.ts +34 -0
- package/dist/font-registry.d.ts.map +1 -0
- package/dist/font-registry.js +59 -0
- package/dist/fonts/inter/inter-300.ttf +0 -0
- package/dist/fonts/inter/inter-400.ttf +0 -0
- package/dist/fonts/inter/inter-500.ttf +0 -0
- package/dist/fonts/inter/inter-600.ttf +0 -0
- package/dist/fonts/inter/inter-700.ttf +0 -0
- package/dist/fonts.d.ts +29 -0
- package/dist/fonts.d.ts.map +1 -0
- package/dist/fonts.js +90 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/ogx.d.ts +16 -0
- package/dist/ogx.d.ts.map +1 -0
- package/dist/ogx.js +104 -0
- package/dist/presets/blog.d.ts +31 -0
- package/dist/presets/blog.d.ts.map +1 -0
- package/dist/presets/blog.js +98 -0
- package/dist/presets/docs.d.ts +23 -0
- package/dist/presets/docs.d.ts.map +1 -0
- package/dist/presets/docs.js +87 -0
- package/dist/presets/index.d.ts +20 -0
- package/dist/presets/index.d.ts.map +1 -0
- package/dist/presets/index.js +11 -0
- package/dist/presets/minimal.d.ts +20 -0
- package/dist/presets/minimal.d.ts.map +1 -0
- package/dist/presets/minimal.js +53 -0
- package/dist/presets/social.d.ts +29 -0
- package/dist/presets/social.d.ts.map +1 -0
- package/dist/presets/social.js +66 -0
- package/dist/render-png.d.ts +7 -0
- package/dist/render-png.d.ts.map +1 -0
- package/dist/render-png.js +19 -0
- package/dist/render-svg.d.ts +7 -0
- package/dist/render-svg.d.ts.map +1 -0
- package/dist/render-svg.js +123 -0
- package/dist/render.d.ts +10 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +180 -0
- package/dist/tailwind/colors.d.ts +6 -0
- package/dist/tailwind/colors.d.ts.map +1 -0
- package/dist/tailwind/colors.js +281 -0
- package/dist/tailwind/index.d.ts +2 -0
- package/dist/tailwind/index.d.ts.map +1 -0
- package/dist/tailwind/index.js +1 -0
- package/dist/tailwind/parser.d.ts +15 -0
- package/dist/tailwind/parser.d.ts.map +1 -0
- package/dist/tailwind/parser.js +1037 -0
- package/dist/tailwind/scales.d.ts +26 -0
- package/dist/tailwind/scales.d.ts.map +1 -0
- package/dist/tailwind/scales.js +126 -0
- package/dist/tailwind/test-parser.d.ts +2 -0
- package/dist/tailwind/test-parser.d.ts.map +1 -0
- package/dist/tailwind/test-parser.js +10 -0
- package/dist/targets.d.ts +67 -0
- package/dist/targets.d.ts.map +1 -0
- package/dist/targets.js +25 -0
- package/dist/types.d.ts +173 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +10 -0
- package/dist/utils/assets.d.ts +11 -0
- package/dist/utils/assets.d.ts.map +1 -0
- package/dist/utils/assets.js +35 -0
- package/dist/utils/color.d.ts +14 -0
- package/dist/utils/color.d.ts.map +1 -0
- package/dist/utils/color.js +68 -0
- package/dist/utils/text.d.ts +14 -0
- package/dist/utils/text.d.ts.map +1 -0
- package/dist/utils/text.js +50 -0
- package/package.json +71 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { OGXElement, Preset } from "../types";
|
|
2
|
+
export interface MinimalPresetProps {
|
|
3
|
+
/** Main text */
|
|
4
|
+
title: string;
|
|
5
|
+
/** Secondary text */
|
|
6
|
+
subtitle?: string;
|
|
7
|
+
/** Background color (Tailwind class or hex) */
|
|
8
|
+
background?: string;
|
|
9
|
+
/** Text color (Tailwind class or hex) */
|
|
10
|
+
textColor?: string;
|
|
11
|
+
/** Custom slot overrides */
|
|
12
|
+
slots?: {
|
|
13
|
+
content?: OGXElement;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Minimal preset - ultra clean, just text
|
|
18
|
+
*/
|
|
19
|
+
export declare const minimalPreset: Preset<MinimalPresetProps>;
|
|
20
|
+
//# sourceMappingURL=minimal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"minimal.d.ts","sourceRoot":"","sources":["../../src/presets/minimal.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAEnD,MAAM,WAAW,kBAAkB;IACjC,gBAAgB;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,qBAAqB;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+CAA+C;IAC/C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yCAAyC;IACzC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4BAA4B;IAC5B,KAAK,CAAC,EAAE;QACN,OAAO,CAAC,EAAE,UAAU,CAAC;KACtB,CAAC;CACH;AAED;;GAEG;AACH,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,kBAAkB,CAqEpD,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { absolute, h1, p, stack } from "../builder";
|
|
2
|
+
/**
|
|
3
|
+
* Minimal preset - ultra clean, just text
|
|
4
|
+
*/
|
|
5
|
+
export const minimalPreset = (props) => {
|
|
6
|
+
const { title, subtitle, background = "bg-zinc-950", textColor = "text-white", slots = {}, } = props;
|
|
7
|
+
const bgClass = background.startsWith("bg-")
|
|
8
|
+
? background
|
|
9
|
+
: `bg-[${background}]`;
|
|
10
|
+
const textClass = textColor.startsWith("text-")
|
|
11
|
+
? textColor
|
|
12
|
+
: `text-[${textColor}]`;
|
|
13
|
+
return stack([
|
|
14
|
+
"w-full",
|
|
15
|
+
"h-full",
|
|
16
|
+
"items-center",
|
|
17
|
+
"justify-center",
|
|
18
|
+
"p-24",
|
|
19
|
+
"relative",
|
|
20
|
+
bgClass,
|
|
21
|
+
], [
|
|
22
|
+
// Subtle Background Texture
|
|
23
|
+
absolute([
|
|
24
|
+
"inset-0 opacity-[0.03]",
|
|
25
|
+
background.includes("light") || background === "bg-white"
|
|
26
|
+
? "bg-grid-black-32"
|
|
27
|
+
: "bg-grid-white-32",
|
|
28
|
+
]),
|
|
29
|
+
absolute(["bg-grain/5"]), // Quality Boost: Dithering
|
|
30
|
+
// Content
|
|
31
|
+
stack("items-center justify-center relative", slots.content ?? [
|
|
32
|
+
h1([
|
|
33
|
+
"text-9xl",
|
|
34
|
+
"font-black",
|
|
35
|
+
"text-center",
|
|
36
|
+
"tracking-tightest", // Quality Boost
|
|
37
|
+
"leading-none",
|
|
38
|
+
textClass,
|
|
39
|
+
], title),
|
|
40
|
+
subtitle
|
|
41
|
+
? p([
|
|
42
|
+
"text-3xl",
|
|
43
|
+
"text-center",
|
|
44
|
+
"mt-10",
|
|
45
|
+
"opacity-40",
|
|
46
|
+
"font-medium",
|
|
47
|
+
"tracking-wide",
|
|
48
|
+
textClass,
|
|
49
|
+
], subtitle.toUpperCase())
|
|
50
|
+
: null,
|
|
51
|
+
]),
|
|
52
|
+
]);
|
|
53
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { OGXElement, Preset } from "../types";
|
|
2
|
+
export interface SocialPresetProps {
|
|
3
|
+
/** Main title or headline */
|
|
4
|
+
title: string;
|
|
5
|
+
/** Handle or username */
|
|
6
|
+
handle?: string;
|
|
7
|
+
/** Avatar URL */
|
|
8
|
+
avatar?: string;
|
|
9
|
+
/** Brand/app name */
|
|
10
|
+
brand?: string;
|
|
11
|
+
/** Brand logo URL */
|
|
12
|
+
logo?: string;
|
|
13
|
+
/** Gradient direction for background */
|
|
14
|
+
gradient?: "to-r" | "to-br" | "to-b" | "to-bl";
|
|
15
|
+
/** From color (hex) */
|
|
16
|
+
fromColor?: string;
|
|
17
|
+
/** To color (hex) */
|
|
18
|
+
toColor?: string;
|
|
19
|
+
/** Custom slot overrides */
|
|
20
|
+
slots?: {
|
|
21
|
+
header?: OGXElement;
|
|
22
|
+
footer?: OGXElement;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Social preset - card-style for social media
|
|
27
|
+
*/
|
|
28
|
+
export declare const socialPreset: Preset<SocialPresetProps>;
|
|
29
|
+
//# sourceMappingURL=social.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"social.d.ts","sourceRoot":"","sources":["../../src/presets/social.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAEnD,MAAM,WAAW,iBAAiB;IAChC,6BAA6B;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,yBAAyB;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iBAAiB;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qBAAqB;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qBAAqB;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wCAAwC;IACxC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;IAC/C,uBAAuB;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qBAAqB;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,4BAA4B;IAC5B,KAAK,CAAC,EAAE;QACN,MAAM,CAAC,EAAE,UAAU,CAAC;QACpB,MAAM,CAAC,EAAE,UAAU,CAAC;KACrB,CAAC;CACH;AAED;;GAEG;AACH,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,iBAAiB,CAkGlD,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { absolute, h1, img, row, span, stack } from "../builder";
|
|
2
|
+
/**
|
|
3
|
+
* Social preset - card-style for social media
|
|
4
|
+
*/
|
|
5
|
+
export const socialPreset = (props) => {
|
|
6
|
+
const { title, handle, avatar, brand, logo, gradient = "to-br", fromColor = "oklch(65% 0.25 260)", // Vibrant Indigo-ish
|
|
7
|
+
toColor = "oklch(60% 0.3 300)", // Vibrant Purple-ish
|
|
8
|
+
slots = {}, } = props;
|
|
9
|
+
const gradientStyle = `linear-gradient(${gradient === "to-r"
|
|
10
|
+
? "90deg"
|
|
11
|
+
: gradient === "to-br"
|
|
12
|
+
? "135deg"
|
|
13
|
+
: gradient === "to-b"
|
|
14
|
+
? "180deg"
|
|
15
|
+
: "225deg"}, ${fromColor}, ${toColor})`;
|
|
16
|
+
return stack(["w-full", "h-full", "p-20", "relative", "overflow-hidden"], [
|
|
17
|
+
// Background Gradient
|
|
18
|
+
absolute("", null, { style: { backgroundImage: gradientStyle } }),
|
|
19
|
+
// Subtle Background Texture (Grain/Noise-like mesh)
|
|
20
|
+
absolute(["inset-0 opacity-20", "bg-grid-white/5-32"]),
|
|
21
|
+
absolute(["bg-grain/15"]), // Quality Boost: Dithering
|
|
22
|
+
// Content Layout
|
|
23
|
+
stack("flex-1 justify-between relative", [
|
|
24
|
+
// Header
|
|
25
|
+
slots.header ??
|
|
26
|
+
row("items-center justify-between", [
|
|
27
|
+
row("items-center gap-4", [
|
|
28
|
+
logo ? img(logo, "w-12 h-12 rounded-xl bg-zinc-950") : null,
|
|
29
|
+
brand
|
|
30
|
+
? span(["text-2xl font-black text-white tracking-tighter"], brand.toUpperCase())
|
|
31
|
+
: null,
|
|
32
|
+
]),
|
|
33
|
+
// Subtle platform indicator
|
|
34
|
+
span(["text-white/30 text-xs font-bold tracking-widest"], "SOCIAL CARD"),
|
|
35
|
+
]),
|
|
36
|
+
// Main content
|
|
37
|
+
stack("gap-6 overflow-hidden", [
|
|
38
|
+
h1([
|
|
39
|
+
"text-7xl",
|
|
40
|
+
"font-black",
|
|
41
|
+
"text-white",
|
|
42
|
+
"leading-[1.05]",
|
|
43
|
+
"tracking-tight", // Quality Boost
|
|
44
|
+
], title),
|
|
45
|
+
]),
|
|
46
|
+
// Footer with user info (Clean Glass Badge)
|
|
47
|
+
slots.footer ??
|
|
48
|
+
(handle || avatar
|
|
49
|
+
? row([
|
|
50
|
+
"items-center gap-4 p-3 pr-6 rounded-full self-start shadow-premium",
|
|
51
|
+
"bg-black/10 border border-white/10 backdrop-blur-md",
|
|
52
|
+
], [
|
|
53
|
+
avatar
|
|
54
|
+
? img(avatar, "w-10 h-10 rounded-full border border-white/20 shadow-sm")
|
|
55
|
+
: null,
|
|
56
|
+
handle
|
|
57
|
+
? span([
|
|
58
|
+
"text-xl font-bold text-white/90 tracking-tight",
|
|
59
|
+
"w-fit truncate shrink-0",
|
|
60
|
+
], handle)
|
|
61
|
+
: null,
|
|
62
|
+
])
|
|
63
|
+
: null),
|
|
64
|
+
]),
|
|
65
|
+
]);
|
|
66
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { OGXElement, RenderOptions } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Render an OGX element to PNG image
|
|
4
|
+
* Node.js only: uses @resvg/resvg-js for SVG → PNG conversion
|
|
5
|
+
*/
|
|
6
|
+
export declare function render(element: OGXElement, options?: RenderOptions): Promise<Uint8Array>;
|
|
7
|
+
//# sourceMappingURL=render-png.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-png.d.ts","sourceRoot":"","sources":["../src/render-png.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAGzD;;;GAGG;AACH,wBAAsB,MAAM,CAC3B,OAAO,EAAE,UAAU,EACnB,OAAO,GAAE,aAAkB,GACzB,OAAO,CAAC,UAAU,CAAC,CAiBrB"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { renderToSVG } from "./render-svg";
|
|
2
|
+
/**
|
|
3
|
+
* Render an OGX element to PNG image
|
|
4
|
+
* Node.js only: uses @resvg/resvg-js for SVG → PNG conversion
|
|
5
|
+
*/
|
|
6
|
+
export async function render(element, options = {}) {
|
|
7
|
+
const svg = await renderToSVG(element, options);
|
|
8
|
+
const width = options.width ?? 1200;
|
|
9
|
+
const resvgPkg = "@resvg/resvg-js";
|
|
10
|
+
const { Resvg } = await import(resvgPkg);
|
|
11
|
+
const resvg = new Resvg(svg, {
|
|
12
|
+
fitTo: {
|
|
13
|
+
mode: "width",
|
|
14
|
+
value: width,
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
const pngData = resvg.render();
|
|
18
|
+
return pngData.asPng();
|
|
19
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { OGXElement, RenderOptions } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Render an OGX element to SVG string
|
|
4
|
+
* Browser-safe: uses only Satori (no Node.js dependencies)
|
|
5
|
+
*/
|
|
6
|
+
export declare function renderToSVG(element: OGXElement, options?: RenderOptions): Promise<string>;
|
|
7
|
+
//# sourceMappingURL=render-svg.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render-svg.d.ts","sourceRoot":"","sources":["../src/render-svg.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAEX,UAAU,EACV,aAAa,EAEb,MAAM,SAAS,CAAC;AAKjB;;;GAGG;AACH,wBAAsB,WAAW,CAChC,OAAO,EAAE,UAAU,EACnB,OAAO,GAAE,aAAkB,GACzB,OAAO,CAAC,MAAM,CAAC,CA2CjB"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import satori from "satori";
|
|
2
|
+
import { fontRegistry } from "./font-registry";
|
|
3
|
+
import { parseTailwind } from "./tailwind";
|
|
4
|
+
import { getPlatformDimensions } from "./targets";
|
|
5
|
+
const DEFAULT_WIDTH = 1200;
|
|
6
|
+
const DEFAULT_HEIGHT = 630;
|
|
7
|
+
/**
|
|
8
|
+
* Render an OGX element to SVG string
|
|
9
|
+
* Browser-safe: uses only Satori (no Node.js dependencies)
|
|
10
|
+
*/
|
|
11
|
+
export async function renderToSVG(element, options = {}) {
|
|
12
|
+
const platformDims = options.platform
|
|
13
|
+
? getPlatformDimensions(options.platform)
|
|
14
|
+
: null;
|
|
15
|
+
const { width = platformDims?.width ?? DEFAULT_WIDTH, height = platformDims?.height ?? DEFAULT_HEIGHT, debug = false, } = options;
|
|
16
|
+
let resolvedFonts = options.fonts ?? [];
|
|
17
|
+
if (resolvedFonts.length === 0) {
|
|
18
|
+
resolvedFonts = fontRegistry.getFonts();
|
|
19
|
+
}
|
|
20
|
+
if (resolvedFonts.length === 0) {
|
|
21
|
+
const { loadInterFromUrl } = await import("./fonts");
|
|
22
|
+
resolvedFonts = [
|
|
23
|
+
await loadInterFromUrl(300),
|
|
24
|
+
await loadInterFromUrl(400),
|
|
25
|
+
await loadInterFromUrl(500),
|
|
26
|
+
await loadInterFromUrl(600),
|
|
27
|
+
await loadInterFromUrl(700),
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
const fullOptions = {
|
|
31
|
+
width,
|
|
32
|
+
height,
|
|
33
|
+
fonts: resolvedFonts,
|
|
34
|
+
debug,
|
|
35
|
+
theme: options.theme,
|
|
36
|
+
colorScheme: options.colorScheme,
|
|
37
|
+
};
|
|
38
|
+
const transformedElement = transformElement(element, debug, fullOptions);
|
|
39
|
+
return await satori(transformedElement, {
|
|
40
|
+
width,
|
|
41
|
+
height,
|
|
42
|
+
fonts: resolvedFonts.map(fontToSatoriFont),
|
|
43
|
+
debug,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Transform OGX element tree to Satori-compatible format
|
|
48
|
+
* Processes `tw` props into inline styles
|
|
49
|
+
*/
|
|
50
|
+
function transformElement(element, debug, options) {
|
|
51
|
+
if (element === null || element === undefined) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
if (typeof element === "string" || typeof element === "number") {
|
|
55
|
+
return element;
|
|
56
|
+
}
|
|
57
|
+
const { tw, style, children, ...restProps } = element.props;
|
|
58
|
+
let mergedStyle;
|
|
59
|
+
if (tw) {
|
|
60
|
+
const twStyles = parseTailwind(tw, options.theme, options.colorScheme);
|
|
61
|
+
mergedStyle = style ? { ...twStyles, ...style } : twStyles;
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
mergedStyle = style ? { ...style } : {};
|
|
65
|
+
}
|
|
66
|
+
// Default resets for Satori (only if not already set by Tailwind)
|
|
67
|
+
if (mergedStyle.margin === undefined)
|
|
68
|
+
mergedStyle.margin = 0;
|
|
69
|
+
if (mergedStyle.padding === undefined &&
|
|
70
|
+
mergedStyle.paddingTop === undefined &&
|
|
71
|
+
mergedStyle.paddingBottom === undefined &&
|
|
72
|
+
mergedStyle.paddingLeft === undefined &&
|
|
73
|
+
mergedStyle.paddingRight === undefined) {
|
|
74
|
+
mergedStyle.padding = 0;
|
|
75
|
+
}
|
|
76
|
+
// Apply default fontFamily if not specified
|
|
77
|
+
if (!mergedStyle.fontFamily) {
|
|
78
|
+
mergedStyle.fontFamily = "Inter";
|
|
79
|
+
}
|
|
80
|
+
// h1/p tags have default margins in many browsers, reset them
|
|
81
|
+
if (element.type === "h1" || element.type === "p") {
|
|
82
|
+
mergedStyle.margin = 0;
|
|
83
|
+
}
|
|
84
|
+
// Add debug border if enabled
|
|
85
|
+
if (debug) {
|
|
86
|
+
mergedStyle.border = "1px solid rgba(255, 0, 0, 0.3)";
|
|
87
|
+
}
|
|
88
|
+
// Transform children recursively
|
|
89
|
+
let transformedChildren = null;
|
|
90
|
+
if (children !== null && children !== undefined) {
|
|
91
|
+
if (Array.isArray(children)) {
|
|
92
|
+
const filtered = children.filter((c) => c !== null && c !== undefined);
|
|
93
|
+
if (filtered.length === 1) {
|
|
94
|
+
transformedChildren = transformElement(filtered[0], debug, options);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
transformedChildren = filtered.map((c) => transformElement(c, debug, options));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
transformedChildren = transformElement(children, debug, options);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
type: element.type,
|
|
106
|
+
props: {
|
|
107
|
+
...restProps,
|
|
108
|
+
style: mergedStyle,
|
|
109
|
+
children: transformedChildren,
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Convert OGX FontConfig to Satori font format
|
|
115
|
+
*/
|
|
116
|
+
function fontToSatoriFont(font) {
|
|
117
|
+
return {
|
|
118
|
+
name: font.name,
|
|
119
|
+
data: font.data,
|
|
120
|
+
weight: font.weight ?? 400,
|
|
121
|
+
style: font.style ?? "normal",
|
|
122
|
+
};
|
|
123
|
+
}
|
package/dist/render.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { OGXElement, RenderOptions } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Render an OGX element to PNG image
|
|
4
|
+
*/
|
|
5
|
+
export declare function render(element: OGXElement, options?: RenderOptions): Promise<Uint8Array>;
|
|
6
|
+
/**
|
|
7
|
+
* Render an OGX element to SVG string
|
|
8
|
+
*/
|
|
9
|
+
export declare function renderToSVG(element: OGXElement, options?: RenderOptions): Promise<string>;
|
|
10
|
+
//# sourceMappingURL=render.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAEX,UAAU,EACV,aAAa,EAEb,MAAM,SAAS,CAAC;AAKjB;;GAEG;AACH,wBAAsB,MAAM,CAC3B,OAAO,EAAE,UAAU,EACnB,OAAO,GAAE,aAAkB,GACzB,OAAO,CAAC,UAAU,CAAC,CA6DrB;AAED;;GAEG;AACH,wBAAsB,WAAW,CAChC,OAAO,EAAE,UAAU,EACnB,OAAO,GAAE,aAAkB,GACzB,OAAO,CAAC,MAAM,CAAC,CA6CjB"}
|
package/dist/render.js
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import satori from "satori";
|
|
2
|
+
import { fontRegistry } from "./font-registry";
|
|
3
|
+
import { parseTailwind } from "./tailwind";
|
|
4
|
+
import { getPlatformDimensions } from "./targets";
|
|
5
|
+
const DEFAULT_WIDTH = 1200;
|
|
6
|
+
const DEFAULT_HEIGHT = 630;
|
|
7
|
+
/**
|
|
8
|
+
* Render an OGX element to PNG image
|
|
9
|
+
*/
|
|
10
|
+
export async function render(element, options = {}) {
|
|
11
|
+
// Resolve platform dimensions
|
|
12
|
+
const platformDims = options.platform
|
|
13
|
+
? getPlatformDimensions(options.platform)
|
|
14
|
+
: null;
|
|
15
|
+
const { width = platformDims?.width ?? DEFAULT_WIDTH, height = platformDims?.height ?? DEFAULT_HEIGHT, debug = false, } = options;
|
|
16
|
+
// Resolve fonts
|
|
17
|
+
let resolvedFonts = options.fonts ?? [];
|
|
18
|
+
if (resolvedFonts.length === 0) {
|
|
19
|
+
resolvedFonts = fontRegistry.getFonts();
|
|
20
|
+
}
|
|
21
|
+
if (resolvedFonts.length === 0) {
|
|
22
|
+
const { loadInterFromUrl } = await import("./fonts");
|
|
23
|
+
resolvedFonts = [
|
|
24
|
+
await loadInterFromUrl(300),
|
|
25
|
+
await loadInterFromUrl(400),
|
|
26
|
+
await loadInterFromUrl(500),
|
|
27
|
+
await loadInterFromUrl(600),
|
|
28
|
+
await loadInterFromUrl(700),
|
|
29
|
+
];
|
|
30
|
+
}
|
|
31
|
+
const fullOptions = {
|
|
32
|
+
width,
|
|
33
|
+
height,
|
|
34
|
+
fonts: resolvedFonts,
|
|
35
|
+
debug,
|
|
36
|
+
theme: options.theme,
|
|
37
|
+
colorScheme: options.colorScheme,
|
|
38
|
+
};
|
|
39
|
+
// Transform the element tree to process tw props
|
|
40
|
+
const transformedElement = transformElement(element, debug, fullOptions);
|
|
41
|
+
// Generate SVG with Satori
|
|
42
|
+
const svg = await satori(transformedElement, {
|
|
43
|
+
width,
|
|
44
|
+
height,
|
|
45
|
+
fonts: resolvedFonts.map(fontToSatoriFont),
|
|
46
|
+
debug,
|
|
47
|
+
});
|
|
48
|
+
// Dynamic import resvg to avoid bundler resolution issues with native binaries
|
|
49
|
+
const resvgPkg = "@resvg/resvg-js";
|
|
50
|
+
const { Resvg } = await import(resvgPkg);
|
|
51
|
+
// Convert SVG to PNG with resvg
|
|
52
|
+
const resvg = new Resvg(svg, {
|
|
53
|
+
fitTo: {
|
|
54
|
+
mode: "width",
|
|
55
|
+
value: width,
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
const pngData = resvg.render();
|
|
59
|
+
return pngData.asPng();
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Render an OGX element to SVG string
|
|
63
|
+
*/
|
|
64
|
+
export async function renderToSVG(element, options = {}) {
|
|
65
|
+
// Resolve platform dimensions
|
|
66
|
+
const platformDims = options.platform
|
|
67
|
+
? getPlatformDimensions(options.platform)
|
|
68
|
+
: null;
|
|
69
|
+
const { width = platformDims?.width ?? DEFAULT_WIDTH, height = platformDims?.height ?? DEFAULT_HEIGHT, debug = false, } = options;
|
|
70
|
+
// Resolve fonts
|
|
71
|
+
let resolvedFonts = options.fonts ?? [];
|
|
72
|
+
if (resolvedFonts.length === 0) {
|
|
73
|
+
resolvedFonts = fontRegistry.getFonts();
|
|
74
|
+
}
|
|
75
|
+
if (resolvedFonts.length === 0) {
|
|
76
|
+
const { loadInterFromUrl } = await import("./fonts");
|
|
77
|
+
resolvedFonts = [
|
|
78
|
+
await loadInterFromUrl(300),
|
|
79
|
+
await loadInterFromUrl(400),
|
|
80
|
+
await loadInterFromUrl(500),
|
|
81
|
+
await loadInterFromUrl(600),
|
|
82
|
+
await loadInterFromUrl(700),
|
|
83
|
+
];
|
|
84
|
+
}
|
|
85
|
+
const fullOptions = {
|
|
86
|
+
width,
|
|
87
|
+
height,
|
|
88
|
+
fonts: resolvedFonts,
|
|
89
|
+
debug,
|
|
90
|
+
theme: options.theme,
|
|
91
|
+
colorScheme: options.colorScheme,
|
|
92
|
+
};
|
|
93
|
+
const transformedElement = transformElement(element, debug, fullOptions);
|
|
94
|
+
return await satori(transformedElement, {
|
|
95
|
+
width,
|
|
96
|
+
height,
|
|
97
|
+
fonts: resolvedFonts.map(fontToSatoriFont),
|
|
98
|
+
debug,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Transform OGX element tree to Satori-compatible format
|
|
103
|
+
* Processes `tw` props into inline styles
|
|
104
|
+
*/
|
|
105
|
+
function transformElement(element, debug, options) {
|
|
106
|
+
// Handle primitives
|
|
107
|
+
if (element === null || element === undefined) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
if (typeof element === "string" || typeof element === "number") {
|
|
111
|
+
return element;
|
|
112
|
+
}
|
|
113
|
+
const { tw, style, children, ...restProps } = element.props;
|
|
114
|
+
// Parse Tailwind classes and merge with inline styles
|
|
115
|
+
let mergedStyle;
|
|
116
|
+
if (tw) {
|
|
117
|
+
const twStyles = parseTailwind(tw, options.theme, options.colorScheme);
|
|
118
|
+
mergedStyle = style ? { ...twStyles, ...style } : twStyles;
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
mergedStyle = style ? { ...style } : {};
|
|
122
|
+
}
|
|
123
|
+
// Default resets for Satori (only if not already set by Tailwind)
|
|
124
|
+
if (mergedStyle.margin === undefined)
|
|
125
|
+
mergedStyle.margin = 0;
|
|
126
|
+
if (mergedStyle.padding === undefined &&
|
|
127
|
+
mergedStyle.paddingTop === undefined &&
|
|
128
|
+
mergedStyle.paddingBottom === undefined &&
|
|
129
|
+
mergedStyle.paddingLeft === undefined &&
|
|
130
|
+
mergedStyle.paddingRight === undefined) {
|
|
131
|
+
mergedStyle.padding = 0;
|
|
132
|
+
}
|
|
133
|
+
// Apply default fontFamily if not specified
|
|
134
|
+
if (!mergedStyle.fontFamily) {
|
|
135
|
+
mergedStyle.fontFamily = "Inter";
|
|
136
|
+
}
|
|
137
|
+
// h1/p tags have default margins in many browsers, reset them
|
|
138
|
+
if (element.type === "h1" || element.type === "p") {
|
|
139
|
+
mergedStyle.margin = 0;
|
|
140
|
+
}
|
|
141
|
+
// Add debug border if enabled
|
|
142
|
+
if (debug) {
|
|
143
|
+
mergedStyle.border = "1px solid rgba(255, 0, 0, 0.3)";
|
|
144
|
+
}
|
|
145
|
+
// Transform children recursively
|
|
146
|
+
let transformedChildren = null;
|
|
147
|
+
if (children !== null && children !== undefined) {
|
|
148
|
+
if (Array.isArray(children)) {
|
|
149
|
+
const filtered = children.filter((c) => c !== null && c !== undefined);
|
|
150
|
+
if (filtered.length === 1) {
|
|
151
|
+
transformedChildren = transformElement(filtered[0], debug, options);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
transformedChildren = filtered.map((c) => transformElement(c, debug, options));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
transformedChildren = transformElement(children, debug, options);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
type: element.type,
|
|
163
|
+
props: {
|
|
164
|
+
...restProps,
|
|
165
|
+
style: mergedStyle,
|
|
166
|
+
children: transformedChildren,
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Convert OGX FontConfig to Satori font format
|
|
172
|
+
*/
|
|
173
|
+
function fontToSatoriFont(font) {
|
|
174
|
+
return {
|
|
175
|
+
name: font.name,
|
|
176
|
+
data: font.data,
|
|
177
|
+
weight: font.weight ?? 400,
|
|
178
|
+
style: font.style ?? "normal",
|
|
179
|
+
};
|
|
180
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"colors.d.ts","sourceRoot":"","sources":["../../src/tailwind/colors.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CA2SzC,CAAC"}
|