@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 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/generate.ts","../src/fonts.ts","../src/noise.ts","../src/png-encoder.ts","../src/templates/blog.ts","../src/templates/profile.ts","../src/templates/default.ts","../src/templates/index.ts","../src/types.ts","../src/endpoint.ts","../src/svg.ts"],"sourcesContent":["/**\n * @ewanc26/og\n *\n * Dynamic OpenGraph image generator with noise backgrounds, bold typography,\n * and Satori-based rendering. Works in SvelteKit endpoints, edge runtimes,\n * and build scripts.\n *\n * @example\n * ```ts\n * import { generateOgImage, createOgEndpoint } from '@ewanc26/og';\n * import { blogTemplate } from '@ewanc26/og/templates';\n *\n * // Generate PNG\n * const png = await generateOgImage({\n * title: 'My Blog Post',\n * description: 'A description',\n * siteName: 'ewancroft.uk',\n * });\n *\n * // SvelteKit endpoint\n * export const GET = createOgEndpoint({ siteName: 'ewancroft.uk' });\n * ```\n */\n\n// Core generation\nexport { generateOgImage, generateOgImageDataUrl, generateOgResponse, OG_WIDTH, OG_HEIGHT } from './generate.js'\n\n// Types\nexport type {\n\tOgColorConfig,\n\tOgFontConfig,\n\tOgNoiseConfig,\n\tOgTemplateProps,\n\tOgTemplate,\n\tOgGenerateOptions,\n\tOgEndpointOptions,\n} from './types.js'\nexport { defaultColors } from './types.js'\n\n// Noise (for advanced customization)\nexport { generateNoiseDataUrl, generateCircleNoiseDataUrl } from './noise.js'\n\n// Fonts (for advanced customization)\nexport { loadFonts, createSatoriFonts, BUNDLED_FONTS } from './fonts.js'\n\n// Endpoint helpers\nexport { createOgEndpoint } from './endpoint.js'\n\n// SVG to PNG conversion\nexport { svgToPng, svgToPngDataUrl, svgToPngResponse } from './svg.js'\nexport type { SvgToPngOptions } from './svg.js'\n","/**\n * Core OG image generation.\n * Uses satori for JSX-to-SVG and resvg-js for SVG-to-PNG.\n */\n\nimport satori from 'satori'\nimport { Resvg } from '@resvg/resvg-js'\nimport { loadFonts, createSatoriFonts } from './fonts.js'\nimport { generateNoiseDataUrl, generateCircleNoiseDataUrl } from './noise.js'\nimport { getTemplate } from './templates/index.js'\nimport { defaultColors } from './types.js'\nimport type {\n\tOgGenerateOptions,\n\tOgColorConfig,\n\tOgTemplateProps,\n} from './types.js'\n\n// Standard OG image dimensions\nexport const OG_WIDTH = 1200\nexport const OG_HEIGHT = 630\n\n/**\n * Generate an OG image as PNG Buffer.\n */\nexport async function generateOgImage(options: OgGenerateOptions): Promise<Buffer> {\n\tconst {\n\t\ttitle,\n\t\tdescription,\n\t\tsiteName,\n\t\timage,\n\t\ttemplate = 'blog',\n\t\tcolors: colorOverrides,\n\t\tfonts: fontConfig,\n\t\tnoise: noiseConfig,\n\t\tnoiseSeed,\n\t\twidth = OG_WIDTH,\n\t\theight = OG_HEIGHT,\n\t\tdebugSvg = false,\n\t} = options\n\n\t// Merge colours\n\tconst colors: OgColorConfig = {\n\t\t...defaultColors,\n\t\t...colorOverrides,\n\t}\n\n\t// Load fonts\n\tconst fonts = await loadFonts(fontConfig)\n\tconst satoriFonts = createSatoriFonts(fonts)\n\n\t// Generate noise background\n\tconst noiseEnabled = noiseConfig?.enabled !== false\n\tconst noiseSeedValue = noiseSeed || noiseConfig?.seed || title\n\tconst noiseDataUrl = noiseEnabled\n\t\t? generateNoiseDataUrl({\n\t\t\t\tseed: noiseSeedValue,\n\t\t\t\twidth,\n\t\t\t\theight,\n\t\t\t\topacity: noiseConfig?.opacity ?? 0.4,\n\t\t\t\tcolorMode: noiseConfig?.colorMode ?? 'grayscale',\n\t\t\t})\n\t\t: undefined\n\n\t// Generate circular noise decoration\n\tconst circleNoiseDataUrl = noiseEnabled\n\t\t? generateCircleNoiseDataUrl({\n\t\t\t\tseed: `${noiseSeedValue}-circle`,\n\t\t\t\tsize: 200,\n\t\t\t\topacity: noiseConfig?.opacity ?? 0.15,\n\t\t\t\tcolorMode: noiseConfig?.colorMode ?? 'grayscale',\n\t\t\t})\n\t\t: undefined\n\n\t// Get template function\n\tconst templateFn = getTemplate(template as Parameters<typeof getTemplate>[0])\n\n\t// Build template props\n\tconst props: OgTemplateProps = {\n\t\ttitle,\n\t\tdescription,\n\t\tsiteName,\n\t\timage,\n\t\tcolors,\n\t\tnoiseDataUrl,\n\t\tcircleNoiseDataUrl,\n\t\twidth,\n\t\theight,\n\t}\n\n\t// Render template to Satori-compatible structure\n\tconst element = templateFn(props)\n\n\t// Generate SVG with satori\n\tconst svg = await satori(element as Parameters<typeof satori>[0], {\n\t\twidth,\n\t\theight,\n\t\tfonts: satoriFonts,\n\t})\n\n\t// Debug: return SVG string\n\tif (debugSvg) {\n\t\treturn Buffer.from(svg)\n\t}\n\n\t// Convert SVG to PNG with resvg-js\n\tconst resvg = new Resvg(svg, {\n\t\tfitTo: {\n\t\t\tmode: 'width',\n\t\t\tvalue: width,\n\t\t},\n\t})\n\tconst pngData = resvg.render()\n\n\treturn Buffer.from(pngData.asPng())\n}\n\n/**\n * Generate OG image and return as base64 data URL.\n */\nexport async function generateOgImageDataUrl(options: OgGenerateOptions): Promise<string> {\n\tconst png = await generateOgImage(options)\n\treturn `data:image/png;base64,${png.toString('base64')}`\n}\n\n/**\n * Generate OG image and return as Response (for SvelteKit endpoints).\n */\nexport async function generateOgResponse(options: OgGenerateOptions, cacheMaxAge = 3600): Promise<Response> {\n\tconst png = await generateOgImage(options)\n\n\treturn new Response(png, {\n\t\theaders: {\n\t\t\t'Content-Type': 'image/png',\n\t\t\t'Cache-Control': `public, max-age=${cacheMaxAge}`,\n\t\t},\n\t})\n}\n","/**\n * @ewanc26/og fonts\n *\n * Font loading utilities. Bundles Inter font by default.\n */\n\nimport { readFile } from 'node:fs/promises'\nimport { dirname, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport type { OgFontConfig } from './types.js'\n\n// Declare __dirname for CJS contexts (injected by bundlers)\ndeclare const __dirname: string | undefined\n\n// ─── Paths ────────────────────────────────────────────────────────────────────\n\n/**\n * Get the directory of the current module.\n * Works in both ESM and bundled contexts.\n */\nfunction getModuleDir(): string {\n\t// ESM context\n\tif (typeof import.meta !== 'undefined' && import.meta.url) {\n\t\treturn dirname(fileURLToPath(import.meta.url))\n\t}\n\t// Bundled CJS context - __dirname is injected by bundlers\n\tif (typeof __dirname !== 'undefined') {\n\t\treturn __dirname\n\t}\n\t// Fallback\n\treturn resolve(process.cwd(), 'node_modules/@ewanc26/og/dist')\n}\n\n/**\n * Resolve the fonts directory relative to the installed package.\n */\nfunction getFontsDir(): string {\n\treturn resolve(getModuleDir(), '../fonts')\n}\n\n/**\n * Resolve bundled font paths. Uses getters to defer resolution until runtime.\n */\nexport const BUNDLED_FONTS = {\n\tget heading() {\n\t\treturn resolve(getFontsDir(), 'Inter-Bold.ttf')\n\t},\n\tget body() {\n\t\treturn resolve(getFontsDir(), 'Inter-Regular.ttf')\n\t},\n} as const\n\n// ─── Font Loading ──────────────────────────────────────────────────────────────\n\nexport interface LoadedFonts {\n\theading: ArrayBuffer\n\tbody: ArrayBuffer\n}\n\n/**\n * Load fonts from config, falling back to bundled DM Sans.\n */\nexport async function loadFonts(config?: OgFontConfig): Promise<LoadedFonts> {\n\tconst headingPath = config?.heading ?? BUNDLED_FONTS.heading\n\tconst bodyPath = config?.body ?? BUNDLED_FONTS.body\n\n\tconst [heading, body] = await Promise.all([\n\t\tloadFontFile(headingPath),\n\t\tloadFontFile(bodyPath),\n\t])\n\n\treturn { heading, body }\n}\n\n/**\n * Load a font from file path or URL.\n */\nasync function loadFontFile(source: string): Promise<ArrayBuffer> {\n\t// Handle URLs\n\tif (source.startsWith('http://') || source.startsWith('https://')) {\n\t\tconst response = await fetch(source)\n\t\tif (!response.ok) {\n\t\t\tthrow new Error(`Failed to load font from URL: ${source}`)\n\t\t}\n\t\treturn response.arrayBuffer()\n\t}\n\n\t// Handle file paths\n\tconst buffer = await readFile(source)\n\treturn buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength)\n}\n\n// ─── Font Registration for Satori ─────────────────────────────────────────────\n\nexport type SatoriFontConfig = {\n\tname: string\n\tdata: ArrayBuffer\n\tweight: 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900\n\tstyle: 'normal' | 'italic'\n}\n\n/**\n * Create Satori-compatible font config from loaded fonts.\n * Uses Inter font family with weight 700 for headings and 400 for body.\n */\nexport function createSatoriFonts(fonts: LoadedFonts): SatoriFontConfig[] {\n\treturn [\n\t\t{\n\t\t\tname: 'Inter',\n\t\t\tdata: fonts.heading,\n\t\t\tweight: 700,\n\t\t\tstyle: 'normal',\n\t\t},\n\t\t{\n\t\t\tname: 'Inter',\n\t\t\tdata: fonts.body,\n\t\t\tweight: 400,\n\t\t\tstyle: 'normal',\n\t\t},\n\t]\n}\n","/**\n * @ewanc26/og noise\n */\n\nimport { generateNoisePixels } from '@ewanc26/noise'\nimport { PNGEncoder } from './png-encoder.js'\nimport type { OgNoiseConfig } from './types.js'\n\nexport interface NoiseOptions {\n\tseed: string\n\twidth: number\n\theight: number\n\topacity?: number\n\tcolorMode?: OgNoiseConfig['colorMode']\n}\n\nexport interface CircleNoiseOptions {\n\tseed: string\n\tsize: number\n\topacity?: number\n\tcolorMode?: OgNoiseConfig['colorMode']\n}\n\n/**\n * Generate a noise PNG as a data URL.\n */\nexport function generateNoiseDataUrl(options: NoiseOptions): string {\n\tconst { seed, width, height, opacity = 0.4, colorMode = 'grayscale' } = options\n\n\tconst pixels = generateNoisePixels(width, height, seed, {\n\t\tgridSize: 4,\n\t\toctaves: 3,\n\t\tcolorMode: colorMode === 'grayscale'\n\t\t\t? { type: 'grayscale', range: [20, 60] }\n\t\t\t: { type: 'hsl', hueRange: 40, saturationRange: [30, 50], lightnessRange: [30, 50] }\n\t})\n\n\tif (opacity < 1) {\n\t\tfor (let i = 3; i < pixels.length; i += 4) {\n\t\t\tpixels[i] = Math.round(pixels[i] * opacity)\n\t\t}\n\t}\n\n\tconst pngBuffer = PNGEncoder.encode(pixels, width, height)\n\treturn `data:image/png;base64,${pngBuffer.toString('base64')}`\n}\n\n/**\n * Generate a circular noise PNG as a data URL.\n * Creates a square image with circular transparency mask.\n */\nexport function generateCircleNoiseDataUrl(options: CircleNoiseOptions): string {\n\tconst { seed, size, opacity = 0.15, colorMode = 'grayscale' } = options\n\n\tconst pixels = generateNoisePixels(size, size, seed, {\n\t\tgridSize: 4,\n\t\toctaves: 3,\n\t\tcolorMode: colorMode === 'grayscale'\n\t\t\t? { type: 'grayscale', range: [30, 70] }\n\t\t\t: { type: 'hsl', hueRange: 40, saturationRange: [30, 50], lightnessRange: [30, 50] }\n\t})\n\n\tconst center = size / 2\n\tconst radius = size / 2\n\n\t// Apply circular mask\n\tfor (let y = 0; y < size; y++) {\n\t\tfor (let x = 0; x < size; x++) {\n\t\t\tconst idx = (y * size + x) * 4\n\t\t\tconst dx = x - center + 0.5\n\t\t\tconst dy = y - center + 0.5\n\t\t\tconst dist = Math.sqrt(dx * dx + dy * dy)\n\n\t\t\tif (dist > radius) {\n\t\t\t\t// Outside circle - fully transparent\n\t\t\t\tpixels[idx + 3] = 0\n\t\t\t} else if (dist > radius - 2) {\n\t\t\t\t// Anti-alias edge\n\t\t\t\tconst edgeOpacity = (radius - dist) / 2\n\t\t\t\tpixels[idx + 3] = Math.round(255 * edgeOpacity * opacity)\n\t\t\t} else {\n\t\t\t\t// Inside circle - apply opacity\n\t\t\t\tpixels[idx + 3] = Math.round(255 * opacity)\n\t\t\t}\n\t\t}\n\t}\n\n\tconst pngBuffer = PNGEncoder.encode(pixels, size, size)\n\treturn `data:image/png;base64,${pngBuffer.toString('base64')}`\n}\n","/**\n * Minimal PNG encoder for noise backgrounds.\n * Uses node:zlib for deflate compression.\n */\n\nimport { deflateSync } from 'node:zlib'\n\nconst PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])\n\nfunction crc32(data: Buffer): number {\n\tlet crc = 0xffffffff\n\tconst table: number[] = []\n\n\t// Build CRC table\n\tfor (let n = 0; n < 256; n++) {\n\t\tlet c = n\n\t\tfor (let k = 0; k < 8; k++) {\n\t\t\tc = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1\n\t\t}\n\t\ttable[n] = c\n\t}\n\n\t// Calculate CRC\n\tfor (let i = 0; i < data.length; i++) {\n\t\tcrc = table[(crc ^ data[i]) & 0xff] ^ (crc >>> 8)\n\t}\n\n\treturn (crc ^ 0xffffffff) >>> 0\n}\n\nfunction createChunk(type: string, data: Buffer): Buffer {\n\tconst length = Buffer.alloc(4)\n\tlength.writeUInt32BE(data.length, 0)\n\n\tconst typeBuffer = Buffer.from(type, 'ascii')\n\tconst crcData = Buffer.concat([typeBuffer, data])\n\tconst crc = Buffer.alloc(4)\n\tcrc.writeUInt32BE(crc32(crcData), 0)\n\n\treturn Buffer.concat([length, typeBuffer, data, crc])\n}\n\nfunction createIHDR(width: number, height: number): Buffer {\n\tconst data = Buffer.alloc(13)\n\tdata.writeUInt32BE(width, 0) // Width\n\tdata.writeUInt32BE(height, 4) // Height\n\tdata.writeUInt8(8, 8) // Bit depth: 8 bits\n\tdata.writeUInt8(2, 9) // Colour type: 2 (RGB)\n\tdata.writeUInt8(0, 10) // Compression method\n\tdata.writeUInt8(0, 11) // Filter method\n\tdata.writeUInt8(0, 12) // Interlace method\n\n\treturn createChunk('IHDR', data)\n}\n\nfunction createIDAT(pixels: Uint8ClampedArray, width: number, height: number): Buffer {\n\t// Apply filter (none filter = 0) per row\n\tconst rawData = Buffer.alloc(height * (width * 3 + 1))\n\n\tlet srcOffset = 0\n\tlet dstOffset = 0\n\n\tfor (let y = 0; y < height; y++) {\n\t\trawData[dstOffset++] = 0 // Filter type: none\n\t\tfor (let x = 0; x < width; x++) {\n\t\t\tconst r = pixels[srcOffset++]\n\t\t\tconst g = pixels[srcOffset++]\n\t\t\tconst b = pixels[srcOffset++]\n\t\t\tsrcOffset++ // Skip alpha\n\t\t\trawData[dstOffset++] = r\n\t\t\trawData[dstOffset++] = g\n\t\t\trawData[dstOffset++] = b\n\t\t}\n\t}\n\n\tconst compressed = deflateSync(rawData)\n\treturn createChunk('IDAT', compressed)\n}\n\nfunction createIEND(): Buffer {\n\treturn createChunk('IEND', Buffer.alloc(0))\n}\n\n/**\n * Encode raw RGBA pixel data as a PNG Buffer.\n */\nexport function encodePNG(pixels: Uint8ClampedArray, width: number, height: number): Buffer {\n\treturn Buffer.concat([\n\t\tPNG_SIGNATURE,\n\t\tcreateIHDR(width, height),\n\t\tcreateIDAT(pixels, width, height),\n\t\tcreateIEND(),\n\t])\n}\n\n/**\n * PNGEncoder namespace for cleaner imports.\n */\nexport const PNGEncoder = {\n\tencode: encodePNG,\n}\n","/**\n * Blog OG template.\n * Clean centered layout.\n */\n\nimport type { OgTemplateProps } from '../types.js'\n\nexport function blogTemplate({\n\ttitle,\n\tdescription,\n\tsiteName,\n\tcolors,\n\twidth,\n\theight,\n}: OgTemplateProps) {\n\treturn {\n\t\ttype: 'div',\n\t\tprops: {\n\t\t\tstyle: {\n\t\t\t\tdisplay: 'flex',\n\t\t\t\tflexDirection: 'column',\n\t\t\t\talignItems: 'center',\n\t\t\t\tjustifyContent: 'center',\n\t\t\t\twidth,\n\t\t\t\theight,\n\t\t\t\tbackgroundColor: colors.background,\n\t\t\t},\n\t\t\tchildren: [\n\t\t\t\t{\n\t\t\t\t\ttype: 'h1',\n\t\t\t\t\tprops: {\n\t\t\t\t\t\tstyle: {\n\t\t\t\t\t\t\tfontSize: 64,\n\t\t\t\t\t\t\tfontWeight: 700,\n\t\t\t\t\t\t\tcolor: colors.text,\n\t\t\t\t\t\t\tletterSpacing: '-0.02em',\n\t\t\t\t\t\t\tmargin: 0,\n\t\t\t\t\t\t\ttextAlign: 'center',\n\t\t\t\t\t\t\tlineHeight: 1.1,\n\t\t\t\t\t\t\tmaxWidth: 1000,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tchildren: title,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tdescription ? {\n\t\t\t\t\ttype: 'p',\n\t\t\t\t\tprops: {\n\t\t\t\t\t\tstyle: {\n\t\t\t\t\t\t\tfontSize: 28,\n\t\t\t\t\t\t\tfontWeight: 400,\n\t\t\t\t\t\t\tcolor: colors.accent,\n\t\t\t\t\t\t\tmarginTop: 28,\n\t\t\t\t\t\t\tmarginBottom: 0,\n\t\t\t\t\t\t\ttextAlign: 'center',\n\t\t\t\t\t\t\tlineHeight: 1.4,\n\t\t\t\t\t\t\tmaxWidth: 900,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tchildren: description,\n\t\t\t\t\t},\n\t\t\t\t} : null,\n\t\t\t\t{\n\t\t\t\t\ttype: 'p',\n\t\t\t\t\tprops: {\n\t\t\t\t\t\tstyle: {\n\t\t\t\t\t\t\tfontSize: 24,\n\t\t\t\t\t\t\tfontWeight: 400,\n\t\t\t\t\t\t\tcolor: colors.accent,\n\t\t\t\t\t\t\tmarginTop: 56,\n\t\t\t\t\t\t\tmarginBottom: 0,\n\t\t\t\t\t\t\ttextAlign: 'center',\n\t\t\t\t\t\t\topacity: 0.7,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tchildren: siteName,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t].filter(Boolean),\n\t\t},\n\t}\n}\n","/**\n * Profile OG template.\n * Centered layout.\n */\n\nimport type { OgTemplateProps } from '../types.js'\n\nexport function profileTemplate({\n\ttitle,\n\tdescription,\n\tsiteName,\n\timage,\n\tcolors,\n\twidth,\n\theight,\n}: OgTemplateProps) {\n\tconst children: unknown[] = []\n\n\tif (image) {\n\t\tchildren.push({\n\t\t\ttype: 'img',\n\t\t\tprops: {\n\t\t\t\tsrc: image,\n\t\t\t\twidth: 120,\n\t\t\t\theight: 120,\n\t\t\t\tstyle: {\n\t\t\t\t\tborderRadius: '50%',\n\t\t\t\t\tmarginBottom: 32,\n\t\t\t\t\tobjectFit: 'cover',\n\t\t\t\t},\n\t\t\t},\n\t\t})\n\t}\n\n\tchildren.push({\n\t\ttype: 'h1',\n\t\tprops: {\n\t\t\tstyle: {\n\t\t\t\tfontSize: 56,\n\t\t\t\tfontWeight: 700,\n\t\t\t\tcolor: colors.text,\n\t\t\t\tletterSpacing: '-0.02em',\n\t\t\t\tmargin: 0,\n\t\t\t\ttextAlign: 'center',\n\t\t\t\tlineHeight: 1.1,\n\t\t\t\tmaxWidth: 900,\n\t\t\t},\n\t\t\tchildren: title,\n\t\t},\n\t})\n\n\tif (description) {\n\t\tchildren.push({\n\t\t\ttype: 'p',\n\t\t\tprops: {\n\t\t\t\tstyle: {\n\t\t\t\t\tfontSize: 26,\n\t\t\t\t\tfontWeight: 400,\n\t\t\t\t\tcolor: colors.accent,\n\t\t\t\t\tmarginTop: 20,\n\t\t\t\t\tmarginBottom: 0,\n\t\t\t\t\ttextAlign: 'center',\n\t\t\t\t\tlineHeight: 1.4,\n\t\t\t\t\tmaxWidth: 700,\n\t\t\t\t},\n\t\t\t\tchildren: description,\n\t\t\t},\n\t\t})\n\t}\n\n\tchildren.push({\n\t\ttype: 'p',\n\t\tprops: {\n\t\t\tstyle: {\n\t\t\t\tfontSize: 24,\n\t\t\t\tfontWeight: 400,\n\t\t\t\tcolor: colors.accent,\n\t\t\t\tmarginTop: 48,\n\t\t\t\tmarginBottom: 0,\n\t\t\t\ttextAlign: 'center',\n\t\t\t\topacity: 0.7,\n\t\t\t},\n\t\t\tchildren: siteName,\n\t\t},\n\t})\n\n\treturn {\n\t\ttype: 'div',\n\t\tprops: {\n\t\t\tstyle: {\n\t\t\t\tdisplay: 'flex',\n\t\t\t\tflexDirection: 'column',\n\t\t\t\talignItems: 'center',\n\t\t\t\tjustifyContent: 'center',\n\t\t\t\twidth,\n\t\t\t\theight,\n\t\t\t\tbackgroundColor: colors.background,\n\t\t\t},\n\t\t\tchildren,\n\t\t},\n\t}\n}\n","/**\n * Default OG template.\n * Clean, centered layout.\n */\n\nimport type { OgTemplateProps } from '../types.js'\n\nexport function defaultTemplate({\n\ttitle,\n\tdescription,\n\tsiteName,\n\tcolors,\n\twidth,\n\theight,\n}: OgTemplateProps) {\n\treturn {\n\t\ttype: 'div',\n\t\tprops: {\n\t\t\tstyle: {\n\t\t\t\tdisplay: 'flex',\n\t\t\t\tflexDirection: 'column',\n\t\t\t\talignItems: 'center',\n\t\t\t\tjustifyContent: 'center',\n\t\t\t\twidth,\n\t\t\t\theight,\n\t\t\t\tbackgroundColor: colors.background,\n\t\t\t},\n\t\t\tchildren: [\n\t\t\t\t{\n\t\t\t\t\ttype: 'h1',\n\t\t\t\t\tprops: {\n\t\t\t\t\t\tstyle: {\n\t\t\t\t\t\t\tfontSize: 72,\n\t\t\t\t\t\t\tfontWeight: 700,\n\t\t\t\t\t\t\tcolor: colors.text,\n\t\t\t\t\t\t\tletterSpacing: '-0.02em',\n\t\t\t\t\t\t\tmargin: 0,\n\t\t\t\t\t\t\ttextAlign: 'center',\n\t\t\t\t\t\t},\n\t\t\t\t\t\tchildren: title,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tdescription ? {\n\t\t\t\t\ttype: 'p',\n\t\t\t\t\tprops: {\n\t\t\t\t\t\tstyle: {\n\t\t\t\t\t\t\tfontSize: 32,\n\t\t\t\t\t\t\tfontWeight: 400,\n\t\t\t\t\t\t\tcolor: colors.accent,\n\t\t\t\t\t\t\tmarginTop: 24,\n\t\t\t\t\t\t\tmarginBottom: 0,\n\t\t\t\t\t\t\ttextAlign: 'center',\n\t\t\t\t\t\t\tmaxWidth: 900,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tchildren: description,\n\t\t\t\t\t},\n\t\t\t\t} : null,\n\t\t\t\t{\n\t\t\t\t\ttype: 'p',\n\t\t\t\t\tprops: {\n\t\t\t\t\t\tstyle: {\n\t\t\t\t\t\t\tfontSize: 28,\n\t\t\t\t\t\t\tfontWeight: 400,\n\t\t\t\t\t\t\tcolor: colors.accent,\n\t\t\t\t\t\t\tmarginTop: 64,\n\t\t\t\t\t\t\tmarginBottom: 0,\n\t\t\t\t\t\t\ttextAlign: 'center',\n\t\t\t\t\t\t\topacity: 0.7,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tchildren: siteName,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t].filter(Boolean),\n\t\t},\n\t}\n}\n","/**\n * Built-in OG templates.\n */\n\nexport { blogTemplate } from './blog.js'\nexport { profileTemplate } from './profile.js'\nexport { defaultTemplate } from './default.js'\n\nimport { blogTemplate } from './blog.js'\nimport { profileTemplate } from './profile.js'\nimport { defaultTemplate } from './default.js'\nimport type { OgTemplate } from '../types.js'\n\nexport const templates = {\n\tblog: blogTemplate,\n\tprofile: profileTemplate,\n\tdefault: defaultTemplate,\n} as const\n\nexport type TemplateName = keyof typeof templates\n\nexport function getTemplate(name: TemplateName | OgTemplate): OgTemplate {\n\tif (typeof name === 'function') {\n\t\treturn name\n\t}\n\treturn templates[name]\n}\n","/**\n * @ewanc26/og types\n */\n\n// ─── Colour Configuration ─────────────────────────────────────────────────────\n\nexport interface OgColorConfig {\n\t/** Background color (very dark). @default '#0f1a15' */\n\tbackground: string\n\t/** Primary text color. @default '#e8f5e9' */\n\ttext: string\n\t/** Secondary/accent text (mint). @default '#86efac' */\n\taccent: string\n}\n\nexport const defaultColors: OgColorConfig = {\n\tbackground: '#0f1a15',\n\ttext: '#e8f5e9',\n\taccent: '#86efac',\n}\n\n// ─── Font Configuration ───────────────────────────────────────────────────────\n\nexport interface OgFontConfig {\n\theading?: string\n\tbody?: string\n}\n\n// ─── Noise Configuration ──────────────────────────────────────────────────────\n\nexport interface OgNoiseConfig {\n\tenabled?: boolean\n\tseed?: string\n\topacity?: number\n\tcolorMode?: 'grayscale' | 'hsl'\n}\n\n// ─── Template Props ────────────────────────────────────────────────────────────\n\nexport interface OgTemplateProps {\n\ttitle: string\n\tdescription?: string\n\tsiteName: string\n\timage?: string\n\tcolors: OgColorConfig\n\tnoiseDataUrl?: string\n\tcircleNoiseDataUrl?: string\n\twidth: number\n\theight: number\n}\n\nexport type OgTemplate = (props: OgTemplateProps) => unknown\n\n// ─── Generation Options ───────────────────────────────────────────────────────\n\nexport interface OgGenerateOptions {\n\ttitle: string\n\tdescription?: string\n\tsiteName: string\n\timage?: string\n\ttemplate?: 'blog' | 'profile' | 'default' | OgTemplate\n\tcolors?: Partial<OgColorConfig>\n\tfonts?: OgFontConfig\n\tnoise?: OgNoiseConfig\n\tnoiseSeed?: string\n\twidth?: number\n\theight?: number\n\tdebugSvg?: boolean\n}\n\n// ─── SvelteKit Endpoint Options ───────────────────────────────────────────────\n\nexport interface OgEndpointOptions {\n\tsiteName: string\n\tdefaultTemplate?: 'blog' | 'profile' | 'default' | OgTemplate\n\tcolors?: Partial<OgColorConfig>\n\tfonts?: OgFontConfig\n\tnoise?: OgNoiseConfig\n\tcacheMaxAge?: number\n\twidth?: number\n\theight?: number\n}\n\n// ─── Internal Types ────────────────────────────────────────────────────────────\n\nexport interface InternalGenerateContext {\n\twidth: number\n\theight: number\n\tfonts: { heading: ArrayBuffer; body: ArrayBuffer }\n\tcolors: OgColorConfig\n\tnoiseDataUrl?: string\n}\n","/**\n * SvelteKit endpoint helpers.\n */\n\nimport { generateOgResponse } from './generate.js'\nimport type { OgEndpointOptions, OgTemplate } from './types.js'\n\n/**\n * Create a SvelteKit GET handler for OG image generation.\n *\n * @example\n * ```ts\n * // src/routes/og/[title]/+server.ts\n * import { createOgEndpoint } from '@ewanc26/og';\n *\n * export const GET = createOgEndpoint({\n * siteName: 'ewancroft.uk',\n * defaultTemplate: 'blog',\n * });\n * ```\n *\n * The endpoint expects query parameters:\n * - `title` (required): Page title\n * - `description`: Optional description\n * - `image`: Optional avatar/logo URL\n * - `seed`: Optional noise seed\n */\nexport function createOgEndpoint(options: OgEndpointOptions) {\n\tconst {\n\t\tsiteName,\n\t\tdefaultTemplate: template = 'default',\n\t\tcolors,\n\t\tfonts,\n\t\tnoise,\n\t\tcacheMaxAge = 3600,\n\t\twidth,\n\t\theight,\n\t} = options\n\n\treturn async ({ url }: { url: URL }) => {\n\t\tconst title = url.searchParams.get('title')\n\t\tconst description = url.searchParams.get('description') ?? undefined\n\t\tconst image = url.searchParams.get('image') ?? undefined\n\t\tconst noiseSeed = url.searchParams.get('seed') ?? undefined\n\n\t\tif (!title) {\n\t\t\treturn new Response('Missing title parameter', { status: 400 })\n\t\t}\n\n\t\ttry {\n\t\t\treturn await generateOgResponse(\n\t\t\t\t{\n\t\t\t\t\ttitle,\n\t\t\t\t\tdescription,\n\t\t\t\t\tsiteName,\n\t\t\t\t\timage,\n\t\t\t\t\ttemplate: template as OgTemplate,\n\t\t\t\t\tcolors,\n\t\t\t\t\tfonts,\n\t\t\t\t\tnoise,\n\t\t\t\t\tnoiseSeed,\n\t\t\t\t\twidth,\n\t\t\t\t\theight,\n\t\t\t\t},\n\t\t\t\tcacheMaxAge\n\t\t\t)\n\t\t} catch (error) {\n\t\t\tconsole.error('Failed to generate OG image:', error)\n\t\t\treturn new Response('Failed to generate image', { status: 500 })\n\t\t}\n\t}\n}\n\n/**\n * Create a typed OG image URL for use in meta tags.\n */\nexport function createOgImageUrl(\n\tbaseUrl: string,\n\tparams: {\n\t\ttitle: string\n\t\tdescription?: string\n\t\timage?: string\n\t\tseed?: string\n\t}\n): string {\n\tconst url = new URL(baseUrl)\n\turl.searchParams.set('title', params.title)\n\tif (params.description) url.searchParams.set('description', params.description)\n\tif (params.image) url.searchParams.set('image', params.image)\n\tif (params.seed) url.searchParams.set('seed', params.seed)\n\treturn url.toString()\n}\n","/**\n * SVG to PNG conversion using @resvg/resvg-js.\n */\n\nimport { Resvg } from '@resvg/resvg-js'\n\nexport interface SvgToPngOptions {\n\t/** Scale to fit width in pixels */\n\tfitWidth?: number\n\t/** Background colour for transparent areas */\n\tbackgroundColor?: string\n}\n\n/**\n * Convert an SVG string to PNG Buffer.\n */\nexport function svgToPng(svg: string, options: SvgToPngOptions = {}): Buffer {\n\tconst opts = {\n\t\tfitTo: options.fitWidth\n\t\t\t? { mode: 'width' as const, value: options.fitWidth }\n\t\t\t: undefined,\n\t\tbackground: options.backgroundColor,\n\t}\n\n\tconst resvg = new Resvg(svg, opts)\n\tconst rendered = resvg.render()\n\n\treturn Buffer.from(rendered.asPng())\n}\n\n/**\n * Convert an SVG string to PNG data URL.\n */\nexport function svgToPngDataUrl(svg: string, options: SvgToPngOptions = {}): string {\n\tconst png = svgToPng(svg, options)\n\treturn `data:image/png;base64,${png.toString('base64')}`\n}\n\n/**\n * Convert an SVG string to PNG Response (for SvelteKit endpoints).\n */\nexport function svgToPngResponse(svg: string, options: SvgToPngOptions = {}, cacheMaxAge = 3600): Response {\n\tconst png = svgToPng(svg, options)\n\n\treturn new Response(png, {\n\t\theaders: {\n\t\t\t'Content-Type': 'image/png',\n\t\t\t'Cache-Control': `public, max-age=${cacheMaxAge}`,\n\t\t},\n\t})\n}\n"],"mappings":"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","names":["satori","import_resvg_js"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { O as OgGenerateOptions, a as OgNoiseConfig, b as OgFontConfig, c as OgEndpointOptions } from './types-Bn2R50Vr.cjs';
|
|
2
|
+
export { d as OgColorConfig, e as OgTemplate, f as OgTemplateProps, g as defaultColors } from './types-Bn2R50Vr.cjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Core OG image generation.
|
|
6
|
+
* Uses satori for JSX-to-SVG and resvg-js for SVG-to-PNG.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
declare const OG_WIDTH = 1200;
|
|
10
|
+
declare const OG_HEIGHT = 630;
|
|
11
|
+
/**
|
|
12
|
+
* Generate an OG image as PNG Buffer.
|
|
13
|
+
*/
|
|
14
|
+
declare function generateOgImage(options: OgGenerateOptions): Promise<Buffer>;
|
|
15
|
+
/**
|
|
16
|
+
* Generate OG image and return as base64 data URL.
|
|
17
|
+
*/
|
|
18
|
+
declare function generateOgImageDataUrl(options: OgGenerateOptions): Promise<string>;
|
|
19
|
+
/**
|
|
20
|
+
* Generate OG image and return as Response (for SvelteKit endpoints).
|
|
21
|
+
*/
|
|
22
|
+
declare function generateOgResponse(options: OgGenerateOptions, cacheMaxAge?: number): Promise<Response>;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @ewanc26/og noise
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
interface NoiseOptions {
|
|
29
|
+
seed: string;
|
|
30
|
+
width: number;
|
|
31
|
+
height: number;
|
|
32
|
+
opacity?: number;
|
|
33
|
+
colorMode?: OgNoiseConfig['colorMode'];
|
|
34
|
+
}
|
|
35
|
+
interface CircleNoiseOptions {
|
|
36
|
+
seed: string;
|
|
37
|
+
size: number;
|
|
38
|
+
opacity?: number;
|
|
39
|
+
colorMode?: OgNoiseConfig['colorMode'];
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Generate a noise PNG as a data URL.
|
|
43
|
+
*/
|
|
44
|
+
declare function generateNoiseDataUrl(options: NoiseOptions): string;
|
|
45
|
+
/**
|
|
46
|
+
* Generate a circular noise PNG as a data URL.
|
|
47
|
+
* Creates a square image with circular transparency mask.
|
|
48
|
+
*/
|
|
49
|
+
declare function generateCircleNoiseDataUrl(options: CircleNoiseOptions): string;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @ewanc26/og fonts
|
|
53
|
+
*
|
|
54
|
+
* Font loading utilities. Bundles Inter font by default.
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Resolve bundled font paths. Uses getters to defer resolution until runtime.
|
|
59
|
+
*/
|
|
60
|
+
declare const BUNDLED_FONTS: {
|
|
61
|
+
readonly heading: string;
|
|
62
|
+
readonly body: string;
|
|
63
|
+
};
|
|
64
|
+
interface LoadedFonts {
|
|
65
|
+
heading: ArrayBuffer;
|
|
66
|
+
body: ArrayBuffer;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Load fonts from config, falling back to bundled DM Sans.
|
|
70
|
+
*/
|
|
71
|
+
declare function loadFonts(config?: OgFontConfig): Promise<LoadedFonts>;
|
|
72
|
+
type SatoriFontConfig = {
|
|
73
|
+
name: string;
|
|
74
|
+
data: ArrayBuffer;
|
|
75
|
+
weight: 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900;
|
|
76
|
+
style: 'normal' | 'italic';
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Create Satori-compatible font config from loaded fonts.
|
|
80
|
+
* Uses Inter font family with weight 700 for headings and 400 for body.
|
|
81
|
+
*/
|
|
82
|
+
declare function createSatoriFonts(fonts: LoadedFonts): SatoriFontConfig[];
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* SvelteKit endpoint helpers.
|
|
86
|
+
*/
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Create a SvelteKit GET handler for OG image generation.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```ts
|
|
93
|
+
* // src/routes/og/[title]/+server.ts
|
|
94
|
+
* import { createOgEndpoint } from '@ewanc26/og';
|
|
95
|
+
*
|
|
96
|
+
* export const GET = createOgEndpoint({
|
|
97
|
+
* siteName: 'ewancroft.uk',
|
|
98
|
+
* defaultTemplate: 'blog',
|
|
99
|
+
* });
|
|
100
|
+
* ```
|
|
101
|
+
*
|
|
102
|
+
* The endpoint expects query parameters:
|
|
103
|
+
* - `title` (required): Page title
|
|
104
|
+
* - `description`: Optional description
|
|
105
|
+
* - `image`: Optional avatar/logo URL
|
|
106
|
+
* - `seed`: Optional noise seed
|
|
107
|
+
*/
|
|
108
|
+
declare function createOgEndpoint(options: OgEndpointOptions): ({ url }: {
|
|
109
|
+
url: URL;
|
|
110
|
+
}) => Promise<Response>;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* SVG to PNG conversion using @resvg/resvg-js.
|
|
114
|
+
*/
|
|
115
|
+
interface SvgToPngOptions {
|
|
116
|
+
/** Scale to fit width in pixels */
|
|
117
|
+
fitWidth?: number;
|
|
118
|
+
/** Background colour for transparent areas */
|
|
119
|
+
backgroundColor?: string;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Convert an SVG string to PNG Buffer.
|
|
123
|
+
*/
|
|
124
|
+
declare function svgToPng(svg: string, options?: SvgToPngOptions): Buffer;
|
|
125
|
+
/**
|
|
126
|
+
* Convert an SVG string to PNG data URL.
|
|
127
|
+
*/
|
|
128
|
+
declare function svgToPngDataUrl(svg: string, options?: SvgToPngOptions): string;
|
|
129
|
+
/**
|
|
130
|
+
* Convert an SVG string to PNG Response (for SvelteKit endpoints).
|
|
131
|
+
*/
|
|
132
|
+
declare function svgToPngResponse(svg: string, options?: SvgToPngOptions, cacheMaxAge?: number): Response;
|
|
133
|
+
|
|
134
|
+
export { BUNDLED_FONTS, OG_HEIGHT, OG_WIDTH, OgEndpointOptions, OgFontConfig, OgGenerateOptions, OgNoiseConfig, type SvgToPngOptions, createOgEndpoint, createSatoriFonts, generateCircleNoiseDataUrl, generateNoiseDataUrl, generateOgImage, generateOgImageDataUrl, generateOgResponse, loadFonts, svgToPng, svgToPngDataUrl, svgToPngResponse };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { O as OgGenerateOptions, a as OgNoiseConfig, b as OgFontConfig, c as OgEndpointOptions } from './types-Bn2R50Vr.js';
|
|
2
|
+
export { d as OgColorConfig, e as OgTemplate, f as OgTemplateProps, g as defaultColors } from './types-Bn2R50Vr.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Core OG image generation.
|
|
6
|
+
* Uses satori for JSX-to-SVG and resvg-js for SVG-to-PNG.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
declare const OG_WIDTH = 1200;
|
|
10
|
+
declare const OG_HEIGHT = 630;
|
|
11
|
+
/**
|
|
12
|
+
* Generate an OG image as PNG Buffer.
|
|
13
|
+
*/
|
|
14
|
+
declare function generateOgImage(options: OgGenerateOptions): Promise<Buffer>;
|
|
15
|
+
/**
|
|
16
|
+
* Generate OG image and return as base64 data URL.
|
|
17
|
+
*/
|
|
18
|
+
declare function generateOgImageDataUrl(options: OgGenerateOptions): Promise<string>;
|
|
19
|
+
/**
|
|
20
|
+
* Generate OG image and return as Response (for SvelteKit endpoints).
|
|
21
|
+
*/
|
|
22
|
+
declare function generateOgResponse(options: OgGenerateOptions, cacheMaxAge?: number): Promise<Response>;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @ewanc26/og noise
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
interface NoiseOptions {
|
|
29
|
+
seed: string;
|
|
30
|
+
width: number;
|
|
31
|
+
height: number;
|
|
32
|
+
opacity?: number;
|
|
33
|
+
colorMode?: OgNoiseConfig['colorMode'];
|
|
34
|
+
}
|
|
35
|
+
interface CircleNoiseOptions {
|
|
36
|
+
seed: string;
|
|
37
|
+
size: number;
|
|
38
|
+
opacity?: number;
|
|
39
|
+
colorMode?: OgNoiseConfig['colorMode'];
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Generate a noise PNG as a data URL.
|
|
43
|
+
*/
|
|
44
|
+
declare function generateNoiseDataUrl(options: NoiseOptions): string;
|
|
45
|
+
/**
|
|
46
|
+
* Generate a circular noise PNG as a data URL.
|
|
47
|
+
* Creates a square image with circular transparency mask.
|
|
48
|
+
*/
|
|
49
|
+
declare function generateCircleNoiseDataUrl(options: CircleNoiseOptions): string;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @ewanc26/og fonts
|
|
53
|
+
*
|
|
54
|
+
* Font loading utilities. Bundles Inter font by default.
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Resolve bundled font paths. Uses getters to defer resolution until runtime.
|
|
59
|
+
*/
|
|
60
|
+
declare const BUNDLED_FONTS: {
|
|
61
|
+
readonly heading: string;
|
|
62
|
+
readonly body: string;
|
|
63
|
+
};
|
|
64
|
+
interface LoadedFonts {
|
|
65
|
+
heading: ArrayBuffer;
|
|
66
|
+
body: ArrayBuffer;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Load fonts from config, falling back to bundled DM Sans.
|
|
70
|
+
*/
|
|
71
|
+
declare function loadFonts(config?: OgFontConfig): Promise<LoadedFonts>;
|
|
72
|
+
type SatoriFontConfig = {
|
|
73
|
+
name: string;
|
|
74
|
+
data: ArrayBuffer;
|
|
75
|
+
weight: 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900;
|
|
76
|
+
style: 'normal' | 'italic';
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Create Satori-compatible font config from loaded fonts.
|
|
80
|
+
* Uses Inter font family with weight 700 for headings and 400 for body.
|
|
81
|
+
*/
|
|
82
|
+
declare function createSatoriFonts(fonts: LoadedFonts): SatoriFontConfig[];
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* SvelteKit endpoint helpers.
|
|
86
|
+
*/
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Create a SvelteKit GET handler for OG image generation.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```ts
|
|
93
|
+
* // src/routes/og/[title]/+server.ts
|
|
94
|
+
* import { createOgEndpoint } from '@ewanc26/og';
|
|
95
|
+
*
|
|
96
|
+
* export const GET = createOgEndpoint({
|
|
97
|
+
* siteName: 'ewancroft.uk',
|
|
98
|
+
* defaultTemplate: 'blog',
|
|
99
|
+
* });
|
|
100
|
+
* ```
|
|
101
|
+
*
|
|
102
|
+
* The endpoint expects query parameters:
|
|
103
|
+
* - `title` (required): Page title
|
|
104
|
+
* - `description`: Optional description
|
|
105
|
+
* - `image`: Optional avatar/logo URL
|
|
106
|
+
* - `seed`: Optional noise seed
|
|
107
|
+
*/
|
|
108
|
+
declare function createOgEndpoint(options: OgEndpointOptions): ({ url }: {
|
|
109
|
+
url: URL;
|
|
110
|
+
}) => Promise<Response>;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* SVG to PNG conversion using @resvg/resvg-js.
|
|
114
|
+
*/
|
|
115
|
+
interface SvgToPngOptions {
|
|
116
|
+
/** Scale to fit width in pixels */
|
|
117
|
+
fitWidth?: number;
|
|
118
|
+
/** Background colour for transparent areas */
|
|
119
|
+
backgroundColor?: string;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Convert an SVG string to PNG Buffer.
|
|
123
|
+
*/
|
|
124
|
+
declare function svgToPng(svg: string, options?: SvgToPngOptions): Buffer;
|
|
125
|
+
/**
|
|
126
|
+
* Convert an SVG string to PNG data URL.
|
|
127
|
+
*/
|
|
128
|
+
declare function svgToPngDataUrl(svg: string, options?: SvgToPngOptions): string;
|
|
129
|
+
/**
|
|
130
|
+
* Convert an SVG string to PNG Response (for SvelteKit endpoints).
|
|
131
|
+
*/
|
|
132
|
+
declare function svgToPngResponse(svg: string, options?: SvgToPngOptions, cacheMaxAge?: number): Response;
|
|
133
|
+
|
|
134
|
+
export { BUNDLED_FONTS, OG_HEIGHT, OG_WIDTH, OgEndpointOptions, OgFontConfig, OgGenerateOptions, OgNoiseConfig, type SvgToPngOptions, createOgEndpoint, createSatoriFonts, generateCircleNoiseDataUrl, generateNoiseDataUrl, generateOgImage, generateOgImageDataUrl, generateOgResponse, loadFonts, svgToPng, svgToPngDataUrl, svgToPngResponse };
|