@ewanc26/og 0.1.1
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/LICENSE +661 -0
- package/README.md +145 -0
- package/dist/chunk-EPPJ2HBS.js +258 -0
- package/dist/chunk-EPPJ2HBS.js.map +1 -0
- package/dist/fonts/Inter-Bold.ttf +0 -0
- package/dist/fonts/Inter-Regular.ttf +0 -0
- package/dist/index.cjs +663 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +134 -0
- package/dist/index.d.ts +134 -0
- package/dist/index.js +365 -0
- package/dist/index.js.map +1 -0
- package/dist/templates/index.cjs +288 -0
- package/dist/templates/index.cjs.map +1 -0
- package/dist/templates/index.d.cts +183 -0
- package/dist/templates/index.d.ts +183 -0
- package/dist/templates/index.js +15 -0
- package/dist/templates/index.js.map +1 -0
- package/dist/types-Bn2R50Vr.d.cts +60 -0
- package/dist/types-Bn2R50Vr.d.ts +60 -0
- package/fonts/Inter-Bold.ttf +0 -0
- package/fonts/Inter-Regular.ttf +0 -0
- package/package.json +63 -0
- package/src/endpoint.ts +92 -0
- package/src/fonts.ts +121 -0
- package/src/generate.ts +137 -0
- package/src/index.ts +51 -0
- package/src/noise.ts +90 -0
- package/src/png-encoder.ts +101 -0
- package/src/svg.ts +51 -0
- package/src/templates/blog.ts +79 -0
- package/src/templates/default.ts +76 -0
- package/src/templates/index.ts +27 -0
- package/src/templates/profile.ts +102 -0
- package/src/types.ts +92 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal PNG encoder for noise backgrounds.
|
|
3
|
+
* Uses node:zlib for deflate compression.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { deflateSync } from 'node:zlib'
|
|
7
|
+
|
|
8
|
+
const PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])
|
|
9
|
+
|
|
10
|
+
function crc32(data: Buffer): number {
|
|
11
|
+
let crc = 0xffffffff
|
|
12
|
+
const table: number[] = []
|
|
13
|
+
|
|
14
|
+
// Build CRC table
|
|
15
|
+
for (let n = 0; n < 256; n++) {
|
|
16
|
+
let c = n
|
|
17
|
+
for (let k = 0; k < 8; k++) {
|
|
18
|
+
c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1
|
|
19
|
+
}
|
|
20
|
+
table[n] = c
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Calculate CRC
|
|
24
|
+
for (let i = 0; i < data.length; i++) {
|
|
25
|
+
crc = table[(crc ^ data[i]) & 0xff] ^ (crc >>> 8)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return (crc ^ 0xffffffff) >>> 0
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function createChunk(type: string, data: Buffer): Buffer {
|
|
32
|
+
const length = Buffer.alloc(4)
|
|
33
|
+
length.writeUInt32BE(data.length, 0)
|
|
34
|
+
|
|
35
|
+
const typeBuffer = Buffer.from(type, 'ascii')
|
|
36
|
+
const crcData = Buffer.concat([typeBuffer, data])
|
|
37
|
+
const crc = Buffer.alloc(4)
|
|
38
|
+
crc.writeUInt32BE(crc32(crcData), 0)
|
|
39
|
+
|
|
40
|
+
return Buffer.concat([length, typeBuffer, data, crc])
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function createIHDR(width: number, height: number): Buffer {
|
|
44
|
+
const data = Buffer.alloc(13)
|
|
45
|
+
data.writeUInt32BE(width, 0) // Width
|
|
46
|
+
data.writeUInt32BE(height, 4) // Height
|
|
47
|
+
data.writeUInt8(8, 8) // Bit depth: 8 bits
|
|
48
|
+
data.writeUInt8(2, 9) // Colour type: 2 (RGB)
|
|
49
|
+
data.writeUInt8(0, 10) // Compression method
|
|
50
|
+
data.writeUInt8(0, 11) // Filter method
|
|
51
|
+
data.writeUInt8(0, 12) // Interlace method
|
|
52
|
+
|
|
53
|
+
return createChunk('IHDR', data)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function createIDAT(pixels: Uint8ClampedArray, width: number, height: number): Buffer {
|
|
57
|
+
// Apply filter (none filter = 0) per row
|
|
58
|
+
const rawData = Buffer.alloc(height * (width * 3 + 1))
|
|
59
|
+
|
|
60
|
+
let srcOffset = 0
|
|
61
|
+
let dstOffset = 0
|
|
62
|
+
|
|
63
|
+
for (let y = 0; y < height; y++) {
|
|
64
|
+
rawData[dstOffset++] = 0 // Filter type: none
|
|
65
|
+
for (let x = 0; x < width; x++) {
|
|
66
|
+
const r = pixels[srcOffset++]
|
|
67
|
+
const g = pixels[srcOffset++]
|
|
68
|
+
const b = pixels[srcOffset++]
|
|
69
|
+
srcOffset++ // Skip alpha
|
|
70
|
+
rawData[dstOffset++] = r
|
|
71
|
+
rawData[dstOffset++] = g
|
|
72
|
+
rawData[dstOffset++] = b
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const compressed = deflateSync(rawData)
|
|
77
|
+
return createChunk('IDAT', compressed)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function createIEND(): Buffer {
|
|
81
|
+
return createChunk('IEND', Buffer.alloc(0))
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Encode raw RGBA pixel data as a PNG Buffer.
|
|
86
|
+
*/
|
|
87
|
+
export function encodePNG(pixels: Uint8ClampedArray, width: number, height: number): Buffer {
|
|
88
|
+
return Buffer.concat([
|
|
89
|
+
PNG_SIGNATURE,
|
|
90
|
+
createIHDR(width, height),
|
|
91
|
+
createIDAT(pixels, width, height),
|
|
92
|
+
createIEND(),
|
|
93
|
+
])
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* PNGEncoder namespace for cleaner imports.
|
|
98
|
+
*/
|
|
99
|
+
export const PNGEncoder = {
|
|
100
|
+
encode: encodePNG,
|
|
101
|
+
}
|
package/src/svg.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SVG to PNG conversion using @resvg/resvg-js.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Resvg } from '@resvg/resvg-js'
|
|
6
|
+
|
|
7
|
+
export interface SvgToPngOptions {
|
|
8
|
+
/** Scale to fit width in pixels */
|
|
9
|
+
fitWidth?: number
|
|
10
|
+
/** Background colour for transparent areas */
|
|
11
|
+
backgroundColor?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Convert an SVG string to PNG Buffer.
|
|
16
|
+
*/
|
|
17
|
+
export function svgToPng(svg: string, options: SvgToPngOptions = {}): Buffer {
|
|
18
|
+
const opts = {
|
|
19
|
+
fitTo: options.fitWidth
|
|
20
|
+
? { mode: 'width' as const, value: options.fitWidth }
|
|
21
|
+
: undefined,
|
|
22
|
+
background: options.backgroundColor,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const resvg = new Resvg(svg, opts)
|
|
26
|
+
const rendered = resvg.render()
|
|
27
|
+
|
|
28
|
+
return Buffer.from(rendered.asPng())
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Convert an SVG string to PNG data URL.
|
|
33
|
+
*/
|
|
34
|
+
export function svgToPngDataUrl(svg: string, options: SvgToPngOptions = {}): string {
|
|
35
|
+
const png = svgToPng(svg, options)
|
|
36
|
+
return `data:image/png;base64,${png.toString('base64')}`
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Convert an SVG string to PNG Response (for SvelteKit endpoints).
|
|
41
|
+
*/
|
|
42
|
+
export function svgToPngResponse(svg: string, options: SvgToPngOptions = {}, cacheMaxAge = 3600): Response {
|
|
43
|
+
const png = svgToPng(svg, options)
|
|
44
|
+
|
|
45
|
+
return new Response(png, {
|
|
46
|
+
headers: {
|
|
47
|
+
'Content-Type': 'image/png',
|
|
48
|
+
'Cache-Control': `public, max-age=${cacheMaxAge}`,
|
|
49
|
+
},
|
|
50
|
+
})
|
|
51
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blog OG template.
|
|
3
|
+
* Clean centered layout.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { OgTemplateProps } from '../types.js'
|
|
7
|
+
|
|
8
|
+
export function blogTemplate({
|
|
9
|
+
title,
|
|
10
|
+
description,
|
|
11
|
+
siteName,
|
|
12
|
+
colors,
|
|
13
|
+
width,
|
|
14
|
+
height,
|
|
15
|
+
}: OgTemplateProps) {
|
|
16
|
+
return {
|
|
17
|
+
type: 'div',
|
|
18
|
+
props: {
|
|
19
|
+
style: {
|
|
20
|
+
display: 'flex',
|
|
21
|
+
flexDirection: 'column',
|
|
22
|
+
alignItems: 'center',
|
|
23
|
+
justifyContent: 'center',
|
|
24
|
+
width,
|
|
25
|
+
height,
|
|
26
|
+
backgroundColor: colors.background,
|
|
27
|
+
},
|
|
28
|
+
children: [
|
|
29
|
+
{
|
|
30
|
+
type: 'h1',
|
|
31
|
+
props: {
|
|
32
|
+
style: {
|
|
33
|
+
fontSize: 64,
|
|
34
|
+
fontWeight: 700,
|
|
35
|
+
color: colors.text,
|
|
36
|
+
letterSpacing: '-0.02em',
|
|
37
|
+
margin: 0,
|
|
38
|
+
textAlign: 'center',
|
|
39
|
+
lineHeight: 1.1,
|
|
40
|
+
maxWidth: 1000,
|
|
41
|
+
},
|
|
42
|
+
children: title,
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
description ? {
|
|
46
|
+
type: 'p',
|
|
47
|
+
props: {
|
|
48
|
+
style: {
|
|
49
|
+
fontSize: 28,
|
|
50
|
+
fontWeight: 400,
|
|
51
|
+
color: colors.accent,
|
|
52
|
+
marginTop: 28,
|
|
53
|
+
marginBottom: 0,
|
|
54
|
+
textAlign: 'center',
|
|
55
|
+
lineHeight: 1.4,
|
|
56
|
+
maxWidth: 900,
|
|
57
|
+
},
|
|
58
|
+
children: description,
|
|
59
|
+
},
|
|
60
|
+
} : null,
|
|
61
|
+
{
|
|
62
|
+
type: 'p',
|
|
63
|
+
props: {
|
|
64
|
+
style: {
|
|
65
|
+
fontSize: 24,
|
|
66
|
+
fontWeight: 400,
|
|
67
|
+
color: colors.accent,
|
|
68
|
+
marginTop: 56,
|
|
69
|
+
marginBottom: 0,
|
|
70
|
+
textAlign: 'center',
|
|
71
|
+
opacity: 0.7,
|
|
72
|
+
},
|
|
73
|
+
children: siteName,
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
].filter(Boolean),
|
|
77
|
+
},
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default OG template.
|
|
3
|
+
* Clean, centered layout.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { OgTemplateProps } from '../types.js'
|
|
7
|
+
|
|
8
|
+
export function defaultTemplate({
|
|
9
|
+
title,
|
|
10
|
+
description,
|
|
11
|
+
siteName,
|
|
12
|
+
colors,
|
|
13
|
+
width,
|
|
14
|
+
height,
|
|
15
|
+
}: OgTemplateProps) {
|
|
16
|
+
return {
|
|
17
|
+
type: 'div',
|
|
18
|
+
props: {
|
|
19
|
+
style: {
|
|
20
|
+
display: 'flex',
|
|
21
|
+
flexDirection: 'column',
|
|
22
|
+
alignItems: 'center',
|
|
23
|
+
justifyContent: 'center',
|
|
24
|
+
width,
|
|
25
|
+
height,
|
|
26
|
+
backgroundColor: colors.background,
|
|
27
|
+
},
|
|
28
|
+
children: [
|
|
29
|
+
{
|
|
30
|
+
type: 'h1',
|
|
31
|
+
props: {
|
|
32
|
+
style: {
|
|
33
|
+
fontSize: 72,
|
|
34
|
+
fontWeight: 700,
|
|
35
|
+
color: colors.text,
|
|
36
|
+
letterSpacing: '-0.02em',
|
|
37
|
+
margin: 0,
|
|
38
|
+
textAlign: 'center',
|
|
39
|
+
},
|
|
40
|
+
children: title,
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
description ? {
|
|
44
|
+
type: 'p',
|
|
45
|
+
props: {
|
|
46
|
+
style: {
|
|
47
|
+
fontSize: 32,
|
|
48
|
+
fontWeight: 400,
|
|
49
|
+
color: colors.accent,
|
|
50
|
+
marginTop: 24,
|
|
51
|
+
marginBottom: 0,
|
|
52
|
+
textAlign: 'center',
|
|
53
|
+
maxWidth: 900,
|
|
54
|
+
},
|
|
55
|
+
children: description,
|
|
56
|
+
},
|
|
57
|
+
} : null,
|
|
58
|
+
{
|
|
59
|
+
type: 'p',
|
|
60
|
+
props: {
|
|
61
|
+
style: {
|
|
62
|
+
fontSize: 28,
|
|
63
|
+
fontWeight: 400,
|
|
64
|
+
color: colors.accent,
|
|
65
|
+
marginTop: 64,
|
|
66
|
+
marginBottom: 0,
|
|
67
|
+
textAlign: 'center',
|
|
68
|
+
opacity: 0.7,
|
|
69
|
+
},
|
|
70
|
+
children: siteName,
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
].filter(Boolean),
|
|
74
|
+
},
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in OG templates.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export { blogTemplate } from './blog.js'
|
|
6
|
+
export { profileTemplate } from './profile.js'
|
|
7
|
+
export { defaultTemplate } from './default.js'
|
|
8
|
+
|
|
9
|
+
import { blogTemplate } from './blog.js'
|
|
10
|
+
import { profileTemplate } from './profile.js'
|
|
11
|
+
import { defaultTemplate } from './default.js'
|
|
12
|
+
import type { OgTemplate } from '../types.js'
|
|
13
|
+
|
|
14
|
+
export const templates = {
|
|
15
|
+
blog: blogTemplate,
|
|
16
|
+
profile: profileTemplate,
|
|
17
|
+
default: defaultTemplate,
|
|
18
|
+
} as const
|
|
19
|
+
|
|
20
|
+
export type TemplateName = keyof typeof templates
|
|
21
|
+
|
|
22
|
+
export function getTemplate(name: TemplateName | OgTemplate): OgTemplate {
|
|
23
|
+
if (typeof name === 'function') {
|
|
24
|
+
return name
|
|
25
|
+
}
|
|
26
|
+
return templates[name]
|
|
27
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Profile OG template.
|
|
3
|
+
* Centered layout.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { OgTemplateProps } from '../types.js'
|
|
7
|
+
|
|
8
|
+
export function profileTemplate({
|
|
9
|
+
title,
|
|
10
|
+
description,
|
|
11
|
+
siteName,
|
|
12
|
+
image,
|
|
13
|
+
colors,
|
|
14
|
+
width,
|
|
15
|
+
height,
|
|
16
|
+
}: OgTemplateProps) {
|
|
17
|
+
const children: unknown[] = []
|
|
18
|
+
|
|
19
|
+
if (image) {
|
|
20
|
+
children.push({
|
|
21
|
+
type: 'img',
|
|
22
|
+
props: {
|
|
23
|
+
src: image,
|
|
24
|
+
width: 120,
|
|
25
|
+
height: 120,
|
|
26
|
+
style: {
|
|
27
|
+
borderRadius: '50%',
|
|
28
|
+
marginBottom: 32,
|
|
29
|
+
objectFit: 'cover',
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
children.push({
|
|
36
|
+
type: 'h1',
|
|
37
|
+
props: {
|
|
38
|
+
style: {
|
|
39
|
+
fontSize: 56,
|
|
40
|
+
fontWeight: 700,
|
|
41
|
+
color: colors.text,
|
|
42
|
+
letterSpacing: '-0.02em',
|
|
43
|
+
margin: 0,
|
|
44
|
+
textAlign: 'center',
|
|
45
|
+
lineHeight: 1.1,
|
|
46
|
+
maxWidth: 900,
|
|
47
|
+
},
|
|
48
|
+
children: title,
|
|
49
|
+
},
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
if (description) {
|
|
53
|
+
children.push({
|
|
54
|
+
type: 'p',
|
|
55
|
+
props: {
|
|
56
|
+
style: {
|
|
57
|
+
fontSize: 26,
|
|
58
|
+
fontWeight: 400,
|
|
59
|
+
color: colors.accent,
|
|
60
|
+
marginTop: 20,
|
|
61
|
+
marginBottom: 0,
|
|
62
|
+
textAlign: 'center',
|
|
63
|
+
lineHeight: 1.4,
|
|
64
|
+
maxWidth: 700,
|
|
65
|
+
},
|
|
66
|
+
children: description,
|
|
67
|
+
},
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
children.push({
|
|
72
|
+
type: 'p',
|
|
73
|
+
props: {
|
|
74
|
+
style: {
|
|
75
|
+
fontSize: 24,
|
|
76
|
+
fontWeight: 400,
|
|
77
|
+
color: colors.accent,
|
|
78
|
+
marginTop: 48,
|
|
79
|
+
marginBottom: 0,
|
|
80
|
+
textAlign: 'center',
|
|
81
|
+
opacity: 0.7,
|
|
82
|
+
},
|
|
83
|
+
children: siteName,
|
|
84
|
+
},
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
type: 'div',
|
|
89
|
+
props: {
|
|
90
|
+
style: {
|
|
91
|
+
display: 'flex',
|
|
92
|
+
flexDirection: 'column',
|
|
93
|
+
alignItems: 'center',
|
|
94
|
+
justifyContent: 'center',
|
|
95
|
+
width,
|
|
96
|
+
height,
|
|
97
|
+
backgroundColor: colors.background,
|
|
98
|
+
},
|
|
99
|
+
children,
|
|
100
|
+
},
|
|
101
|
+
}
|
|
102
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ewanc26/og types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// ─── Colour Configuration ─────────────────────────────────────────────────────
|
|
6
|
+
|
|
7
|
+
export interface OgColorConfig {
|
|
8
|
+
/** Background color (very dark). @default '#0f1a15' */
|
|
9
|
+
background: string
|
|
10
|
+
/** Primary text color. @default '#e8f5e9' */
|
|
11
|
+
text: string
|
|
12
|
+
/** Secondary/accent text (mint). @default '#86efac' */
|
|
13
|
+
accent: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const defaultColors: OgColorConfig = {
|
|
17
|
+
background: '#0f1a15',
|
|
18
|
+
text: '#e8f5e9',
|
|
19
|
+
accent: '#86efac',
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ─── Font Configuration ───────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
export interface OgFontConfig {
|
|
25
|
+
heading?: string
|
|
26
|
+
body?: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ─── Noise Configuration ──────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
export interface OgNoiseConfig {
|
|
32
|
+
enabled?: boolean
|
|
33
|
+
seed?: string
|
|
34
|
+
opacity?: number
|
|
35
|
+
colorMode?: 'grayscale' | 'hsl'
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ─── Template Props ────────────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
export interface OgTemplateProps {
|
|
41
|
+
title: string
|
|
42
|
+
description?: string
|
|
43
|
+
siteName: string
|
|
44
|
+
image?: string
|
|
45
|
+
colors: OgColorConfig
|
|
46
|
+
noiseDataUrl?: string
|
|
47
|
+
circleNoiseDataUrl?: string
|
|
48
|
+
width: number
|
|
49
|
+
height: number
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type OgTemplate = (props: OgTemplateProps) => unknown
|
|
53
|
+
|
|
54
|
+
// ─── Generation Options ───────────────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
export interface OgGenerateOptions {
|
|
57
|
+
title: string
|
|
58
|
+
description?: string
|
|
59
|
+
siteName: string
|
|
60
|
+
image?: string
|
|
61
|
+
template?: 'blog' | 'profile' | 'default' | OgTemplate
|
|
62
|
+
colors?: Partial<OgColorConfig>
|
|
63
|
+
fonts?: OgFontConfig
|
|
64
|
+
noise?: OgNoiseConfig
|
|
65
|
+
noiseSeed?: string
|
|
66
|
+
width?: number
|
|
67
|
+
height?: number
|
|
68
|
+
debugSvg?: boolean
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ─── SvelteKit Endpoint Options ───────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
export interface OgEndpointOptions {
|
|
74
|
+
siteName: string
|
|
75
|
+
defaultTemplate?: 'blog' | 'profile' | 'default' | OgTemplate
|
|
76
|
+
colors?: Partial<OgColorConfig>
|
|
77
|
+
fonts?: OgFontConfig
|
|
78
|
+
noise?: OgNoiseConfig
|
|
79
|
+
cacheMaxAge?: number
|
|
80
|
+
width?: number
|
|
81
|
+
height?: number
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ─── Internal Types ────────────────────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
export interface InternalGenerateContext {
|
|
87
|
+
width: number
|
|
88
|
+
height: number
|
|
89
|
+
fonts: { heading: ArrayBuffer; body: ArrayBuffer }
|
|
90
|
+
colors: OgColorConfig
|
|
91
|
+
noiseDataUrl?: string
|
|
92
|
+
}
|