@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
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,663 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
BUNDLED_FONTS: () => BUNDLED_FONTS,
|
|
34
|
+
OG_HEIGHT: () => OG_HEIGHT,
|
|
35
|
+
OG_WIDTH: () => OG_WIDTH,
|
|
36
|
+
createOgEndpoint: () => createOgEndpoint,
|
|
37
|
+
createSatoriFonts: () => createSatoriFonts,
|
|
38
|
+
defaultColors: () => defaultColors,
|
|
39
|
+
generateCircleNoiseDataUrl: () => generateCircleNoiseDataUrl,
|
|
40
|
+
generateNoiseDataUrl: () => generateNoiseDataUrl,
|
|
41
|
+
generateOgImage: () => generateOgImage,
|
|
42
|
+
generateOgImageDataUrl: () => generateOgImageDataUrl,
|
|
43
|
+
generateOgResponse: () => generateOgResponse,
|
|
44
|
+
loadFonts: () => loadFonts,
|
|
45
|
+
svgToPng: () => svgToPng,
|
|
46
|
+
svgToPngDataUrl: () => svgToPngDataUrl,
|
|
47
|
+
svgToPngResponse: () => svgToPngResponse
|
|
48
|
+
});
|
|
49
|
+
module.exports = __toCommonJS(index_exports);
|
|
50
|
+
|
|
51
|
+
// src/generate.ts
|
|
52
|
+
var import_satori = __toESM(require("satori"), 1);
|
|
53
|
+
var import_resvg_js = require("@resvg/resvg-js");
|
|
54
|
+
|
|
55
|
+
// src/fonts.ts
|
|
56
|
+
var import_promises = require("fs/promises");
|
|
57
|
+
var import_node_path = require("path");
|
|
58
|
+
var import_node_url = require("url");
|
|
59
|
+
var import_meta = {};
|
|
60
|
+
function getModuleDir() {
|
|
61
|
+
if (typeof import_meta !== "undefined" && import_meta.url) {
|
|
62
|
+
return (0, import_node_path.dirname)((0, import_node_url.fileURLToPath)(import_meta.url));
|
|
63
|
+
}
|
|
64
|
+
if (typeof __dirname !== "undefined") {
|
|
65
|
+
return __dirname;
|
|
66
|
+
}
|
|
67
|
+
return (0, import_node_path.resolve)(process.cwd(), "node_modules/@ewanc26/og/dist");
|
|
68
|
+
}
|
|
69
|
+
function getFontsDir() {
|
|
70
|
+
return (0, import_node_path.resolve)(getModuleDir(), "../fonts");
|
|
71
|
+
}
|
|
72
|
+
var BUNDLED_FONTS = {
|
|
73
|
+
get heading() {
|
|
74
|
+
return (0, import_node_path.resolve)(getFontsDir(), "Inter-Bold.ttf");
|
|
75
|
+
},
|
|
76
|
+
get body() {
|
|
77
|
+
return (0, import_node_path.resolve)(getFontsDir(), "Inter-Regular.ttf");
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
async function loadFonts(config) {
|
|
81
|
+
const headingPath = config?.heading ?? BUNDLED_FONTS.heading;
|
|
82
|
+
const bodyPath = config?.body ?? BUNDLED_FONTS.body;
|
|
83
|
+
const [heading, body] = await Promise.all([
|
|
84
|
+
loadFontFile(headingPath),
|
|
85
|
+
loadFontFile(bodyPath)
|
|
86
|
+
]);
|
|
87
|
+
return { heading, body };
|
|
88
|
+
}
|
|
89
|
+
async function loadFontFile(source) {
|
|
90
|
+
if (source.startsWith("http://") || source.startsWith("https://")) {
|
|
91
|
+
const response = await fetch(source);
|
|
92
|
+
if (!response.ok) {
|
|
93
|
+
throw new Error(`Failed to load font from URL: ${source}`);
|
|
94
|
+
}
|
|
95
|
+
return response.arrayBuffer();
|
|
96
|
+
}
|
|
97
|
+
const buffer = await (0, import_promises.readFile)(source);
|
|
98
|
+
return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
|
99
|
+
}
|
|
100
|
+
function createSatoriFonts(fonts) {
|
|
101
|
+
return [
|
|
102
|
+
{
|
|
103
|
+
name: "Inter",
|
|
104
|
+
data: fonts.heading,
|
|
105
|
+
weight: 700,
|
|
106
|
+
style: "normal"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: "Inter",
|
|
110
|
+
data: fonts.body,
|
|
111
|
+
weight: 400,
|
|
112
|
+
style: "normal"
|
|
113
|
+
}
|
|
114
|
+
];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/noise.ts
|
|
118
|
+
var import_noise = require("@ewanc26/noise");
|
|
119
|
+
|
|
120
|
+
// src/png-encoder.ts
|
|
121
|
+
var import_node_zlib = require("zlib");
|
|
122
|
+
var PNG_SIGNATURE = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
|
|
123
|
+
function crc32(data) {
|
|
124
|
+
let crc = 4294967295;
|
|
125
|
+
const table = [];
|
|
126
|
+
for (let n = 0; n < 256; n++) {
|
|
127
|
+
let c = n;
|
|
128
|
+
for (let k = 0; k < 8; k++) {
|
|
129
|
+
c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
130
|
+
}
|
|
131
|
+
table[n] = c;
|
|
132
|
+
}
|
|
133
|
+
for (let i = 0; i < data.length; i++) {
|
|
134
|
+
crc = table[(crc ^ data[i]) & 255] ^ crc >>> 8;
|
|
135
|
+
}
|
|
136
|
+
return (crc ^ 4294967295) >>> 0;
|
|
137
|
+
}
|
|
138
|
+
function createChunk(type, data) {
|
|
139
|
+
const length = Buffer.alloc(4);
|
|
140
|
+
length.writeUInt32BE(data.length, 0);
|
|
141
|
+
const typeBuffer = Buffer.from(type, "ascii");
|
|
142
|
+
const crcData = Buffer.concat([typeBuffer, data]);
|
|
143
|
+
const crc = Buffer.alloc(4);
|
|
144
|
+
crc.writeUInt32BE(crc32(crcData), 0);
|
|
145
|
+
return Buffer.concat([length, typeBuffer, data, crc]);
|
|
146
|
+
}
|
|
147
|
+
function createIHDR(width, height) {
|
|
148
|
+
const data = Buffer.alloc(13);
|
|
149
|
+
data.writeUInt32BE(width, 0);
|
|
150
|
+
data.writeUInt32BE(height, 4);
|
|
151
|
+
data.writeUInt8(8, 8);
|
|
152
|
+
data.writeUInt8(2, 9);
|
|
153
|
+
data.writeUInt8(0, 10);
|
|
154
|
+
data.writeUInt8(0, 11);
|
|
155
|
+
data.writeUInt8(0, 12);
|
|
156
|
+
return createChunk("IHDR", data);
|
|
157
|
+
}
|
|
158
|
+
function createIDAT(pixels, width, height) {
|
|
159
|
+
const rawData = Buffer.alloc(height * (width * 3 + 1));
|
|
160
|
+
let srcOffset = 0;
|
|
161
|
+
let dstOffset = 0;
|
|
162
|
+
for (let y = 0; y < height; y++) {
|
|
163
|
+
rawData[dstOffset++] = 0;
|
|
164
|
+
for (let x = 0; x < width; x++) {
|
|
165
|
+
const r = pixels[srcOffset++];
|
|
166
|
+
const g = pixels[srcOffset++];
|
|
167
|
+
const b = pixels[srcOffset++];
|
|
168
|
+
srcOffset++;
|
|
169
|
+
rawData[dstOffset++] = r;
|
|
170
|
+
rawData[dstOffset++] = g;
|
|
171
|
+
rawData[dstOffset++] = b;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const compressed = (0, import_node_zlib.deflateSync)(rawData);
|
|
175
|
+
return createChunk("IDAT", compressed);
|
|
176
|
+
}
|
|
177
|
+
function createIEND() {
|
|
178
|
+
return createChunk("IEND", Buffer.alloc(0));
|
|
179
|
+
}
|
|
180
|
+
function encodePNG(pixels, width, height) {
|
|
181
|
+
return Buffer.concat([
|
|
182
|
+
PNG_SIGNATURE,
|
|
183
|
+
createIHDR(width, height),
|
|
184
|
+
createIDAT(pixels, width, height),
|
|
185
|
+
createIEND()
|
|
186
|
+
]);
|
|
187
|
+
}
|
|
188
|
+
var PNGEncoder = {
|
|
189
|
+
encode: encodePNG
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
// src/noise.ts
|
|
193
|
+
function generateNoiseDataUrl(options) {
|
|
194
|
+
const { seed, width, height, opacity = 0.4, colorMode = "grayscale" } = options;
|
|
195
|
+
const pixels = (0, import_noise.generateNoisePixels)(width, height, seed, {
|
|
196
|
+
gridSize: 4,
|
|
197
|
+
octaves: 3,
|
|
198
|
+
colorMode: colorMode === "grayscale" ? { type: "grayscale", range: [20, 60] } : { type: "hsl", hueRange: 40, saturationRange: [30, 50], lightnessRange: [30, 50] }
|
|
199
|
+
});
|
|
200
|
+
if (opacity < 1) {
|
|
201
|
+
for (let i = 3; i < pixels.length; i += 4) {
|
|
202
|
+
pixels[i] = Math.round(pixels[i] * opacity);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const pngBuffer = PNGEncoder.encode(pixels, width, height);
|
|
206
|
+
return `data:image/png;base64,${pngBuffer.toString("base64")}`;
|
|
207
|
+
}
|
|
208
|
+
function generateCircleNoiseDataUrl(options) {
|
|
209
|
+
const { seed, size, opacity = 0.15, colorMode = "grayscale" } = options;
|
|
210
|
+
const pixels = (0, import_noise.generateNoisePixels)(size, size, seed, {
|
|
211
|
+
gridSize: 4,
|
|
212
|
+
octaves: 3,
|
|
213
|
+
colorMode: colorMode === "grayscale" ? { type: "grayscale", range: [30, 70] } : { type: "hsl", hueRange: 40, saturationRange: [30, 50], lightnessRange: [30, 50] }
|
|
214
|
+
});
|
|
215
|
+
const center = size / 2;
|
|
216
|
+
const radius = size / 2;
|
|
217
|
+
for (let y = 0; y < size; y++) {
|
|
218
|
+
for (let x = 0; x < size; x++) {
|
|
219
|
+
const idx = (y * size + x) * 4;
|
|
220
|
+
const dx = x - center + 0.5;
|
|
221
|
+
const dy = y - center + 0.5;
|
|
222
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
223
|
+
if (dist > radius) {
|
|
224
|
+
pixels[idx + 3] = 0;
|
|
225
|
+
} else if (dist > radius - 2) {
|
|
226
|
+
const edgeOpacity = (radius - dist) / 2;
|
|
227
|
+
pixels[idx + 3] = Math.round(255 * edgeOpacity * opacity);
|
|
228
|
+
} else {
|
|
229
|
+
pixels[idx + 3] = Math.round(255 * opacity);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const pngBuffer = PNGEncoder.encode(pixels, size, size);
|
|
234
|
+
return `data:image/png;base64,${pngBuffer.toString("base64")}`;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// src/templates/blog.ts
|
|
238
|
+
function blogTemplate({
|
|
239
|
+
title,
|
|
240
|
+
description,
|
|
241
|
+
siteName,
|
|
242
|
+
colors,
|
|
243
|
+
width,
|
|
244
|
+
height
|
|
245
|
+
}) {
|
|
246
|
+
return {
|
|
247
|
+
type: "div",
|
|
248
|
+
props: {
|
|
249
|
+
style: {
|
|
250
|
+
display: "flex",
|
|
251
|
+
flexDirection: "column",
|
|
252
|
+
alignItems: "center",
|
|
253
|
+
justifyContent: "center",
|
|
254
|
+
width,
|
|
255
|
+
height,
|
|
256
|
+
backgroundColor: colors.background
|
|
257
|
+
},
|
|
258
|
+
children: [
|
|
259
|
+
{
|
|
260
|
+
type: "h1",
|
|
261
|
+
props: {
|
|
262
|
+
style: {
|
|
263
|
+
fontSize: 64,
|
|
264
|
+
fontWeight: 700,
|
|
265
|
+
color: colors.text,
|
|
266
|
+
letterSpacing: "-0.02em",
|
|
267
|
+
margin: 0,
|
|
268
|
+
textAlign: "center",
|
|
269
|
+
lineHeight: 1.1,
|
|
270
|
+
maxWidth: 1e3
|
|
271
|
+
},
|
|
272
|
+
children: title
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
description ? {
|
|
276
|
+
type: "p",
|
|
277
|
+
props: {
|
|
278
|
+
style: {
|
|
279
|
+
fontSize: 28,
|
|
280
|
+
fontWeight: 400,
|
|
281
|
+
color: colors.accent,
|
|
282
|
+
marginTop: 28,
|
|
283
|
+
marginBottom: 0,
|
|
284
|
+
textAlign: "center",
|
|
285
|
+
lineHeight: 1.4,
|
|
286
|
+
maxWidth: 900
|
|
287
|
+
},
|
|
288
|
+
children: description
|
|
289
|
+
}
|
|
290
|
+
} : null,
|
|
291
|
+
{
|
|
292
|
+
type: "p",
|
|
293
|
+
props: {
|
|
294
|
+
style: {
|
|
295
|
+
fontSize: 24,
|
|
296
|
+
fontWeight: 400,
|
|
297
|
+
color: colors.accent,
|
|
298
|
+
marginTop: 56,
|
|
299
|
+
marginBottom: 0,
|
|
300
|
+
textAlign: "center",
|
|
301
|
+
opacity: 0.7
|
|
302
|
+
},
|
|
303
|
+
children: siteName
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
].filter(Boolean)
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// src/templates/profile.ts
|
|
312
|
+
function profileTemplate({
|
|
313
|
+
title,
|
|
314
|
+
description,
|
|
315
|
+
siteName,
|
|
316
|
+
image,
|
|
317
|
+
colors,
|
|
318
|
+
width,
|
|
319
|
+
height
|
|
320
|
+
}) {
|
|
321
|
+
const children = [];
|
|
322
|
+
if (image) {
|
|
323
|
+
children.push({
|
|
324
|
+
type: "img",
|
|
325
|
+
props: {
|
|
326
|
+
src: image,
|
|
327
|
+
width: 120,
|
|
328
|
+
height: 120,
|
|
329
|
+
style: {
|
|
330
|
+
borderRadius: "50%",
|
|
331
|
+
marginBottom: 32,
|
|
332
|
+
objectFit: "cover"
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
children.push({
|
|
338
|
+
type: "h1",
|
|
339
|
+
props: {
|
|
340
|
+
style: {
|
|
341
|
+
fontSize: 56,
|
|
342
|
+
fontWeight: 700,
|
|
343
|
+
color: colors.text,
|
|
344
|
+
letterSpacing: "-0.02em",
|
|
345
|
+
margin: 0,
|
|
346
|
+
textAlign: "center",
|
|
347
|
+
lineHeight: 1.1,
|
|
348
|
+
maxWidth: 900
|
|
349
|
+
},
|
|
350
|
+
children: title
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
if (description) {
|
|
354
|
+
children.push({
|
|
355
|
+
type: "p",
|
|
356
|
+
props: {
|
|
357
|
+
style: {
|
|
358
|
+
fontSize: 26,
|
|
359
|
+
fontWeight: 400,
|
|
360
|
+
color: colors.accent,
|
|
361
|
+
marginTop: 20,
|
|
362
|
+
marginBottom: 0,
|
|
363
|
+
textAlign: "center",
|
|
364
|
+
lineHeight: 1.4,
|
|
365
|
+
maxWidth: 700
|
|
366
|
+
},
|
|
367
|
+
children: description
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
children.push({
|
|
372
|
+
type: "p",
|
|
373
|
+
props: {
|
|
374
|
+
style: {
|
|
375
|
+
fontSize: 24,
|
|
376
|
+
fontWeight: 400,
|
|
377
|
+
color: colors.accent,
|
|
378
|
+
marginTop: 48,
|
|
379
|
+
marginBottom: 0,
|
|
380
|
+
textAlign: "center",
|
|
381
|
+
opacity: 0.7
|
|
382
|
+
},
|
|
383
|
+
children: siteName
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
return {
|
|
387
|
+
type: "div",
|
|
388
|
+
props: {
|
|
389
|
+
style: {
|
|
390
|
+
display: "flex",
|
|
391
|
+
flexDirection: "column",
|
|
392
|
+
alignItems: "center",
|
|
393
|
+
justifyContent: "center",
|
|
394
|
+
width,
|
|
395
|
+
height,
|
|
396
|
+
backgroundColor: colors.background
|
|
397
|
+
},
|
|
398
|
+
children
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// src/templates/default.ts
|
|
404
|
+
function defaultTemplate({
|
|
405
|
+
title,
|
|
406
|
+
description,
|
|
407
|
+
siteName,
|
|
408
|
+
colors,
|
|
409
|
+
width,
|
|
410
|
+
height
|
|
411
|
+
}) {
|
|
412
|
+
return {
|
|
413
|
+
type: "div",
|
|
414
|
+
props: {
|
|
415
|
+
style: {
|
|
416
|
+
display: "flex",
|
|
417
|
+
flexDirection: "column",
|
|
418
|
+
alignItems: "center",
|
|
419
|
+
justifyContent: "center",
|
|
420
|
+
width,
|
|
421
|
+
height,
|
|
422
|
+
backgroundColor: colors.background
|
|
423
|
+
},
|
|
424
|
+
children: [
|
|
425
|
+
{
|
|
426
|
+
type: "h1",
|
|
427
|
+
props: {
|
|
428
|
+
style: {
|
|
429
|
+
fontSize: 72,
|
|
430
|
+
fontWeight: 700,
|
|
431
|
+
color: colors.text,
|
|
432
|
+
letterSpacing: "-0.02em",
|
|
433
|
+
margin: 0,
|
|
434
|
+
textAlign: "center"
|
|
435
|
+
},
|
|
436
|
+
children: title
|
|
437
|
+
}
|
|
438
|
+
},
|
|
439
|
+
description ? {
|
|
440
|
+
type: "p",
|
|
441
|
+
props: {
|
|
442
|
+
style: {
|
|
443
|
+
fontSize: 32,
|
|
444
|
+
fontWeight: 400,
|
|
445
|
+
color: colors.accent,
|
|
446
|
+
marginTop: 24,
|
|
447
|
+
marginBottom: 0,
|
|
448
|
+
textAlign: "center",
|
|
449
|
+
maxWidth: 900
|
|
450
|
+
},
|
|
451
|
+
children: description
|
|
452
|
+
}
|
|
453
|
+
} : null,
|
|
454
|
+
{
|
|
455
|
+
type: "p",
|
|
456
|
+
props: {
|
|
457
|
+
style: {
|
|
458
|
+
fontSize: 28,
|
|
459
|
+
fontWeight: 400,
|
|
460
|
+
color: colors.accent,
|
|
461
|
+
marginTop: 64,
|
|
462
|
+
marginBottom: 0,
|
|
463
|
+
textAlign: "center",
|
|
464
|
+
opacity: 0.7
|
|
465
|
+
},
|
|
466
|
+
children: siteName
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
].filter(Boolean)
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// src/templates/index.ts
|
|
475
|
+
var templates = {
|
|
476
|
+
blog: blogTemplate,
|
|
477
|
+
profile: profileTemplate,
|
|
478
|
+
default: defaultTemplate
|
|
479
|
+
};
|
|
480
|
+
function getTemplate(name) {
|
|
481
|
+
if (typeof name === "function") {
|
|
482
|
+
return name;
|
|
483
|
+
}
|
|
484
|
+
return templates[name];
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// src/types.ts
|
|
488
|
+
var defaultColors = {
|
|
489
|
+
background: "#0f1a15",
|
|
490
|
+
text: "#e8f5e9",
|
|
491
|
+
accent: "#86efac"
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
// src/generate.ts
|
|
495
|
+
var OG_WIDTH = 1200;
|
|
496
|
+
var OG_HEIGHT = 630;
|
|
497
|
+
async function generateOgImage(options) {
|
|
498
|
+
const {
|
|
499
|
+
title,
|
|
500
|
+
description,
|
|
501
|
+
siteName,
|
|
502
|
+
image,
|
|
503
|
+
template = "blog",
|
|
504
|
+
colors: colorOverrides,
|
|
505
|
+
fonts: fontConfig,
|
|
506
|
+
noise: noiseConfig,
|
|
507
|
+
noiseSeed,
|
|
508
|
+
width = OG_WIDTH,
|
|
509
|
+
height = OG_HEIGHT,
|
|
510
|
+
debugSvg = false
|
|
511
|
+
} = options;
|
|
512
|
+
const colors = {
|
|
513
|
+
...defaultColors,
|
|
514
|
+
...colorOverrides
|
|
515
|
+
};
|
|
516
|
+
const fonts = await loadFonts(fontConfig);
|
|
517
|
+
const satoriFonts = createSatoriFonts(fonts);
|
|
518
|
+
const noiseEnabled = noiseConfig?.enabled !== false;
|
|
519
|
+
const noiseSeedValue = noiseSeed || noiseConfig?.seed || title;
|
|
520
|
+
const noiseDataUrl = noiseEnabled ? generateNoiseDataUrl({
|
|
521
|
+
seed: noiseSeedValue,
|
|
522
|
+
width,
|
|
523
|
+
height,
|
|
524
|
+
opacity: noiseConfig?.opacity ?? 0.4,
|
|
525
|
+
colorMode: noiseConfig?.colorMode ?? "grayscale"
|
|
526
|
+
}) : void 0;
|
|
527
|
+
const circleNoiseDataUrl = noiseEnabled ? generateCircleNoiseDataUrl({
|
|
528
|
+
seed: `${noiseSeedValue}-circle`,
|
|
529
|
+
size: 200,
|
|
530
|
+
opacity: noiseConfig?.opacity ?? 0.15,
|
|
531
|
+
colorMode: noiseConfig?.colorMode ?? "grayscale"
|
|
532
|
+
}) : void 0;
|
|
533
|
+
const templateFn = getTemplate(template);
|
|
534
|
+
const props = {
|
|
535
|
+
title,
|
|
536
|
+
description,
|
|
537
|
+
siteName,
|
|
538
|
+
image,
|
|
539
|
+
colors,
|
|
540
|
+
noiseDataUrl,
|
|
541
|
+
circleNoiseDataUrl,
|
|
542
|
+
width,
|
|
543
|
+
height
|
|
544
|
+
};
|
|
545
|
+
const element = templateFn(props);
|
|
546
|
+
const svg = await (0, import_satori.default)(element, {
|
|
547
|
+
width,
|
|
548
|
+
height,
|
|
549
|
+
fonts: satoriFonts
|
|
550
|
+
});
|
|
551
|
+
if (debugSvg) {
|
|
552
|
+
return Buffer.from(svg);
|
|
553
|
+
}
|
|
554
|
+
const resvg = new import_resvg_js.Resvg(svg, {
|
|
555
|
+
fitTo: {
|
|
556
|
+
mode: "width",
|
|
557
|
+
value: width
|
|
558
|
+
}
|
|
559
|
+
});
|
|
560
|
+
const pngData = resvg.render();
|
|
561
|
+
return Buffer.from(pngData.asPng());
|
|
562
|
+
}
|
|
563
|
+
async function generateOgImageDataUrl(options) {
|
|
564
|
+
const png = await generateOgImage(options);
|
|
565
|
+
return `data:image/png;base64,${png.toString("base64")}`;
|
|
566
|
+
}
|
|
567
|
+
async function generateOgResponse(options, cacheMaxAge = 3600) {
|
|
568
|
+
const png = await generateOgImage(options);
|
|
569
|
+
return new Response(png, {
|
|
570
|
+
headers: {
|
|
571
|
+
"Content-Type": "image/png",
|
|
572
|
+
"Cache-Control": `public, max-age=${cacheMaxAge}`
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// src/endpoint.ts
|
|
578
|
+
function createOgEndpoint(options) {
|
|
579
|
+
const {
|
|
580
|
+
siteName,
|
|
581
|
+
defaultTemplate: template = "default",
|
|
582
|
+
colors,
|
|
583
|
+
fonts,
|
|
584
|
+
noise,
|
|
585
|
+
cacheMaxAge = 3600,
|
|
586
|
+
width,
|
|
587
|
+
height
|
|
588
|
+
} = options;
|
|
589
|
+
return async ({ url }) => {
|
|
590
|
+
const title = url.searchParams.get("title");
|
|
591
|
+
const description = url.searchParams.get("description") ?? void 0;
|
|
592
|
+
const image = url.searchParams.get("image") ?? void 0;
|
|
593
|
+
const noiseSeed = url.searchParams.get("seed") ?? void 0;
|
|
594
|
+
if (!title) {
|
|
595
|
+
return new Response("Missing title parameter", { status: 400 });
|
|
596
|
+
}
|
|
597
|
+
try {
|
|
598
|
+
return await generateOgResponse(
|
|
599
|
+
{
|
|
600
|
+
title,
|
|
601
|
+
description,
|
|
602
|
+
siteName,
|
|
603
|
+
image,
|
|
604
|
+
template,
|
|
605
|
+
colors,
|
|
606
|
+
fonts,
|
|
607
|
+
noise,
|
|
608
|
+
noiseSeed,
|
|
609
|
+
width,
|
|
610
|
+
height
|
|
611
|
+
},
|
|
612
|
+
cacheMaxAge
|
|
613
|
+
);
|
|
614
|
+
} catch (error) {
|
|
615
|
+
console.error("Failed to generate OG image:", error);
|
|
616
|
+
return new Response("Failed to generate image", { status: 500 });
|
|
617
|
+
}
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// src/svg.ts
|
|
622
|
+
var import_resvg_js2 = require("@resvg/resvg-js");
|
|
623
|
+
function svgToPng(svg, options = {}) {
|
|
624
|
+
const opts = {
|
|
625
|
+
fitTo: options.fitWidth ? { mode: "width", value: options.fitWidth } : void 0,
|
|
626
|
+
background: options.backgroundColor
|
|
627
|
+
};
|
|
628
|
+
const resvg = new import_resvg_js2.Resvg(svg, opts);
|
|
629
|
+
const rendered = resvg.render();
|
|
630
|
+
return Buffer.from(rendered.asPng());
|
|
631
|
+
}
|
|
632
|
+
function svgToPngDataUrl(svg, options = {}) {
|
|
633
|
+
const png = svgToPng(svg, options);
|
|
634
|
+
return `data:image/png;base64,${png.toString("base64")}`;
|
|
635
|
+
}
|
|
636
|
+
function svgToPngResponse(svg, options = {}, cacheMaxAge = 3600) {
|
|
637
|
+
const png = svgToPng(svg, options);
|
|
638
|
+
return new Response(png, {
|
|
639
|
+
headers: {
|
|
640
|
+
"Content-Type": "image/png",
|
|
641
|
+
"Cache-Control": `public, max-age=${cacheMaxAge}`
|
|
642
|
+
}
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
646
|
+
0 && (module.exports = {
|
|
647
|
+
BUNDLED_FONTS,
|
|
648
|
+
OG_HEIGHT,
|
|
649
|
+
OG_WIDTH,
|
|
650
|
+
createOgEndpoint,
|
|
651
|
+
createSatoriFonts,
|
|
652
|
+
defaultColors,
|
|
653
|
+
generateCircleNoiseDataUrl,
|
|
654
|
+
generateNoiseDataUrl,
|
|
655
|
+
generateOgImage,
|
|
656
|
+
generateOgImageDataUrl,
|
|
657
|
+
generateOgResponse,
|
|
658
|
+
loadFonts,
|
|
659
|
+
svgToPng,
|
|
660
|
+
svgToPngDataUrl,
|
|
661
|
+
svgToPngResponse
|
|
662
|
+
});
|
|
663
|
+
//# sourceMappingURL=index.cjs.map
|