| | |
| | | import { googleFontHref } from "../util/theme" |
| | | import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types" |
| | | import satori, { SatoriOptions } from "satori" |
| | | import { loadEmoji, getIconCode } from "../util/emoji" |
| | | import fs from "fs" |
| | | import sharp from "sharp" |
| | | import { ImageOptions, SocialImageOptions, getSatoriFont, defaultImage } from "../util/og" |
| | |
| | | // JSX that will be used to generate satori svg |
| | | const imageComponent = userOpts.imageStructure(cfg, userOpts, title, description, fonts, fileData) |
| | | |
| | | const svg = await satori(imageComponent, { width, height, fonts }) |
| | | const svg = await satori(imageComponent, { |
| | | width, |
| | | height, |
| | | fonts, |
| | | loadAdditionalAsset: async (languageCode: string, segment: string) => { |
| | | if (languageCode === "emoji") { |
| | | return `data:image/svg+xml;base64,${btoa(await loadEmoji(getIconCode(segment)))}` |
| | | } |
| | | |
| | | return languageCode |
| | | }, |
| | | }) |
| | | |
| | | // Convert svg directly to webp (with additional compression) |
| | | const compressed = await sharp(Buffer.from(svg)).webp({ quality: 40 }).toBuffer() |
| | |
| | | } |
| | | } |
| | | |
| | | const { css, js } = externalResources |
| | | const { css, js, additionalHead } = externalResources |
| | | |
| | | const url = new URL(`https://${cfg.baseUrl ?? "example.com"}`) |
| | | const path = url.pathname as FullSlug |
| | |
| | | <link rel="stylesheet" href={googleFontHref(cfg.theme)} /> |
| | | </> |
| | | )} |
| | | <link rel="preconnect" href="https://cdnjs.cloudflare.com" crossOrigin={"anonymous"} /> |
| | | <link rel="preconnect" href="https://cdnjs.cloudflare.com" crossOrigin="anonymous" /> |
| | | <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
| | | {/* OG/Twitter meta tags */} |
| | | <meta name="og:site_name" content={cfg.pageTitle}></meta> |
| | |
| | | {js |
| | | .filter((resource) => resource.loadTime === "beforeDOMReady") |
| | | .map((res) => JSResourceToScriptElement(res, true))} |
| | | {additionalHead.map((resource) => { |
| | | if (typeof resource === "function") { |
| | | return resource(fileData) |
| | | } else { |
| | | return resource |
| | | } |
| | | })} |
| | | </head> |
| | | ) |
| | | } |