diff --git a/server/src/routes/org-chart-svg.ts b/server/src/routes/org-chart-svg.ts index d0d83159..3d7c3f5e 100644 --- a/server/src/routes/org-chart-svg.ts +++ b/server/src/routes/org-chart-svg.ts @@ -493,6 +493,10 @@ const PAPERCLIP_LOGO_SVG = ` // ── Public API ─────────────────────────────────────────────────── +// GitHub recommended social media preview dimensions +const TARGET_W = 1280; +const TARGET_H = 640; + export function renderOrgChartSvg(orgTree: OrgNode[], style: OrgChartStyle = "warmth"): string { const theme = THEMES[style] || THEMES.warmth; @@ -512,26 +516,39 @@ export function renderOrgChartSvg(orgTree: OrgNode[], style: OrgChartStyle = "wa const layout = layoutTree(root, PADDING, PADDING + 24); const bounds = treeBounds(layout); - const svgW = bounds.maxX + PADDING; - const svgH = bounds.maxY + PADDING; + const contentW = bounds.maxX + PADDING; + const contentH = bounds.maxY + PADDING; - const logoX = svgW - 110 - LOGO_PADDING; + // Scale content to fit within the fixed target dimensions + const scale = Math.min(TARGET_W / contentW, TARGET_H / contentH, 1); + const scaledW = contentW * scale; + const scaledH = contentH * scale; + // Center the scaled content within the target frame + const offsetX = (TARGET_W - scaledW) / 2; + const offsetY = (TARGET_H - scaledH) / 2; + + const logoX = TARGET_W - 110 - LOGO_PADDING; const logoY = LOGO_PADDING; - return ` - ${theme.defs(svgW, svgH)} + return ` + ${theme.defs(TARGET_W, TARGET_H)} - ${theme.bgExtras(svgW, svgH)} + ${theme.bgExtras(TARGET_W, TARGET_H)} ${PAPERCLIP_LOGO_SVG} - ${renderConnectors(layout, theme)} - ${renderCards(layout, theme)} + + ${renderConnectors(layout, theme)} + ${renderCards(layout, theme)} + `; } export async function renderOrgChartPng(orgTree: OrgNode[], style: OrgChartStyle = "warmth"): Promise { const svg = renderOrgChartSvg(orgTree, style); - // Render at 2x density for retina-quality output - return sharp(Buffer.from(svg), { density: 144 }).png().toBuffer(); + // Render at 2x density for retina quality, resize to exact target dimensions + return sharp(Buffer.from(svg), { density: 144 }) + .resize(TARGET_W, TARGET_H) + .png() + .toBuffer(); }