@zyrab/domo-og
Version:
An OG (Open Graph) image generator config based SVG templates and WASM engine.
145 lines (117 loc) • 4.51 kB
JavaScript
import { createHash } from "crypto";
const logOnceFlags = new Set();
export function logOnce(key, msg) {
if (!logOnceFlags.has(key)) {
console.info(msg);
logOnceFlags.add(key);
}
}
export function hash(input) {
return createHash("sha256").update(input).digest("hex").slice(0, 10);
}
function escapeXml(str) {
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
}
// replacec {{dynamic}} tag insde template content ex: content: Hello {{name}} freind . {{name}} will be replaced from route content name
export function injectData(input, data) {
if (typeof input === "string") {
let result = input;
for (const [key, value] of Object.entries(data)) {
if (typeof value === "string" || typeof value === "number") {
result = result.split(`{{${key}}}`).join(value);
}
}
return result;
}
if (Array.isArray(input)) return input.map((v) => injectData(v, data));
if (input && typeof input === "object") {
const out = {};
for (const key in input) out[key] = injectData(input[key], data);
return out;
}
return input;
}
export function calculateLayout(el, canvasWidth, canvasHeight) {
const padding = el.padding || 0;
// Initialize with the numeric value if it's a number
let py1 = 0,
px1 = 0,
py2 = 0,
px2 = 0;
if (typeof padding === "number") {
py1 = px1 = py2 = px2 = padding;
} else if (typeof padding === "string") {
const p = padding.trim().split(/\s+/).map(Number);
py1 = p[0] || 0;
px2 = p.length > 1 ? p[1] : py1; // Right
py2 = p.length > 2 ? p[2] : py1; // Bottom
px1 = p.length > 3 ? p[3] : px2; // Left
}
let x;
if (el.type === "image") {
const w = el.width || 100;
if (el.horizontalAlign === "left" || !el.horizontalAlign) x = px1;
else if (el.horizontalAlign === "center") x = (canvasWidth - w) / 2;
else if (el.horizontalAlign === "right") x = canvasWidth - w - px2;
else x = el.x ?? 0;
} else {
if (el.horizontalAlign === "left" || !el.horizontalAlign) x = px1;
else if (el.horizontalAlign === "center") x = canvasWidth / 2;
else if (el.horizontalAlign === "right") x = canvasWidth - px2;
else x = el.x ?? 0;
}
let y;
if (el.type === "image") {
const h = el.height || 100;
if (el.verticalAlign === "top" || !el.verticalAlign) y = py1;
else if (el.verticalAlign === "middle") y = (canvasHeight - h) / 2;
else if (el.verticalAlign === "bottom") y = canvasHeight - h - py2;
else y = el.y ?? 0;
} else if (el.type === "text") {
const fontSize = el.fontSize || 32;
if (el.verticalAlign === "top" || !el.verticalAlign) y = py1 + fontSize;
else if (el.verticalAlign === "middle") y = canvasHeight / 2 + fontSize * 0.35;
else if (el.verticalAlign === "bottom") y = canvasHeight - py2 - fontSize * 0.25;
else y = el.y ?? fontSize;
} else {
y = el.y ?? 0;
}
return { x, y };
}
function splitText(text, maxLength) {
if (!text) return [];
const words = text.trim().split(/\s+/);
const lines = [];
let current = "";
for (const word of words) {
if ((current + (current ? " " : "") + word).trim().length <= maxLength) {
current += (current ? " " : "") + word;
} else {
if (current) lines.push(current);
current = word;
}
}
if (current) lines.push(current);
return lines;
}
export function formatTextLines(text, options = {}) {
const { maxLength = 25, x = 0, lineHeightEm = 1.2, verticalAlign = "top", fontSize = 32 } = options;
const lines = splitText(text, maxLength);
if (!lines.length) return { svg: "", textWidth: 0, textHeight: 0 };
const totalExtraHeight = (lines.length - 1) * lineHeightEm;
const longestLineLength = Math.max(...lines.map((l) => l.length));
const textWidth = longestLineLength * (fontSize * 0.6);
const textHeight = lines.length * fontSize * lineHeightEm;
const svg = lines
.map((line, i) => {
let dy = 0;
if (i === 0) {
if (verticalAlign === "bottom") dy = -totalExtraHeight;
else if (verticalAlign === "middle") dy = -totalExtraHeight / 2;
} else dy = lineHeightEm;
const dyStr = dy === 0 ? "0" : `${dy}em`;
return `<tspan x="${x}" dy="${dyStr}">${escapeXml(line)}</tspan>`;
})
.join("");
return { svg, textWidth, textHeight };
}