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, boxHeight: number,
maxFontSize = DEFAULT_MAX_FONT_SIZE, maxFontSize = DEFAULT_MAX_FONT_SIZE,
): number { ): number {
const effectiveMax = Math.min(maxFontSize, Math.floor(boxHeight / 2), Math.floor(boxWidth / 3));
let lo = MIN_FONT_SIZE; let lo = MIN_FONT_SIZE;
let hi = maxFontSize; let hi = Math.max(MIN_FONT_SIZE, effectiveMax);
let best = MIN_FONT_SIZE; let best = MIN_FONT_SIZE;
while (lo <= hi) { while (lo <= hi) {
@@ -216,6 +217,9 @@ export function renderMemeTextSvg(opts: MemeTextOptions): Buffer {
const fillAttr = escapeXmlAttr(textColor); const fillAttr = escapeXmlAttr(textColor);
const strokeAttr = escapeXmlAttr(strokeColor); 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++) { for (let i = 0; i < lines.length; i++) {
const line = lines[i]; const line = lines[i];
if (!line) continue; if (!line) continue;
@@ -228,12 +232,10 @@ export function renderMemeTextSvg(opts: MemeTextOptions): Buffer {
} else if (textAlign === "right") { } else if (textAlign === "right") {
x = bx + bw - lineWidth; x = bx + bw - lineWidth;
} else { } else {
// center
x = bx + (bw - lineWidth) / 2; x = bx + (bw - lineWidth) / 2;
} }
// Baseline y: top of box + line offset + ascent approximation const y = yOffset + i * lineHeight;
const y = by + (i + 1) * lineHeight;
let d: string; let d: string;
try { try {
@@ -163,46 +163,46 @@ function EditorSettings() {
</div> </div>
{/* Colors */} {/* Colors */}
<div className="flex gap-3"> <div className="flex gap-2">
<div className="flex-1"> <div className="flex-1 min-w-0">
<label htmlFor="text-color" className="text-xs text-muted-foreground block mb-0.5"> <label htmlFor="text-color" className="text-xs text-muted-foreground block mb-0.5">
Text Color Text
</label> </label>
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1">
<input <input
id="text-color" id="text-color"
data-testid="text-color" data-testid="text-color"
type="color" type="color"
value={textColor} value={textColor}
onChange={(e) => setTextColor(e.target.value)} 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 <input
type="text" type="text"
value={textColor} value={textColor}
onChange={(e) => setTextColor(e.target.value)} 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> </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"> <label htmlFor="stroke-color" className="text-xs text-muted-foreground block mb-0.5">
Stroke Color Stroke
</label> </label>
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1">
<input <input
id="stroke-color" id="stroke-color"
data-testid="stroke-color" data-testid="stroke-color"
type="color" type="color"
value={strokeColor} value={strokeColor}
onChange={(e) => setStrokeColor(e.target.value)} 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 <input
type="text" type="text"
value={strokeColor} value={strokeColor}
onChange={(e) => setStrokeColor(e.target.value)} 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>
</div> </div>