fix(meme-generator): cap auto font size, center text vertically, fix sidebar truncation

- Auto font size now capped at min(200, boxHeight/2, boxWidth/3) to prevent
  oversized text on server render
- Text vertically centered within each text box
- Sidebar color labels shortened (Text/Stroke) with min-w-0 to prevent overflow
This commit is contained in:
SnapOtter
2026-05-08 17:42:04 +08:00
parent 66dfad8122
commit 0c1980c6db
2 changed files with 17 additions and 15 deletions
+6 -4
View File
@@ -143,8 +143,9 @@ export function autoSizeFontToFit(
boxHeight: number,
maxFontSize = DEFAULT_MAX_FONT_SIZE,
): number {
const effectiveMax = Math.min(maxFontSize, Math.floor(boxHeight / 2), Math.floor(boxWidth / 3));
let lo = MIN_FONT_SIZE;
let hi = maxFontSize;
let hi = Math.max(MIN_FONT_SIZE, effectiveMax);
let best = MIN_FONT_SIZE;
while (lo <= hi) {
@@ -216,6 +217,9 @@ export function renderMemeTextSvg(opts: MemeTextOptions): Buffer {
const fillAttr = escapeXmlAttr(textColor);
const strokeAttr = escapeXmlAttr(strokeColor);
const totalTextHeight = lines.length * lineHeight;
const yOffset = by + (bh - totalTextHeight) / 2 + fontSize * 0.85;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (!line) continue;
@@ -228,12 +232,10 @@ export function renderMemeTextSvg(opts: MemeTextOptions): Buffer {
} else if (textAlign === "right") {
x = bx + bw - lineWidth;
} else {
// center
x = bx + (bw - lineWidth) / 2;
}
// Baseline y: top of box + line offset + ascent approximation
const y = by + (i + 1) * lineHeight;
const y = yOffset + i * lineHeight;
let d: string;
try {
@@ -163,46 +163,46 @@ function EditorSettings() {
</div>
{/* Colors */}
<div className="flex gap-3">
<div className="flex-1">
<div className="flex gap-2">
<div className="flex-1 min-w-0">
<label htmlFor="text-color" className="text-xs text-muted-foreground block mb-0.5">
Text Color
Text
</label>
<div className="flex items-center gap-1.5">
<div className="flex items-center gap-1">
<input
id="text-color"
data-testid="text-color"
type="color"
value={textColor}
onChange={(e) => setTextColor(e.target.value)}
className="w-8 h-8 rounded border border-border shrink-0 cursor-pointer"
className="w-7 h-7 rounded border border-border shrink-0 cursor-pointer"
/>
<input
type="text"
value={textColor}
onChange={(e) => setTextColor(e.target.value)}
className="flex-1 px-1.5 py-1 rounded border border-border bg-background text-xs text-foreground font-mono"
className="flex-1 min-w-0 px-1 py-1 rounded border border-border bg-background text-[11px] text-foreground font-mono"
/>
</div>
</div>
<div className="flex-1">
<div className="flex-1 min-w-0">
<label htmlFor="stroke-color" className="text-xs text-muted-foreground block mb-0.5">
Stroke Color
Stroke
</label>
<div className="flex items-center gap-1.5">
<div className="flex items-center gap-1">
<input
id="stroke-color"
data-testid="stroke-color"
type="color"
value={strokeColor}
onChange={(e) => setStrokeColor(e.target.value)}
className="w-8 h-8 rounded border border-border shrink-0 cursor-pointer"
className="w-7 h-7 rounded border border-border shrink-0 cursor-pointer"
/>
<input
type="text"
value={strokeColor}
onChange={(e) => setStrokeColor(e.target.value)}
className="flex-1 px-1.5 py-1 rounded border border-border bg-background text-xs text-foreground font-mono"
className="flex-1 min-w-0 px-1 py-1 rounded border border-border bg-background text-[11px] text-foreground font-mono"
/>
</div>
</div>