diff --git a/src/render/svg.ts b/src/render/svg.ts index 026bb2f..ea7abe9 100644 --- a/src/render/svg.ts +++ b/src/render/svg.ts @@ -14,7 +14,17 @@ function escapeXml(str: string): string { } /** - * Render a single swatch with color, name, and hex + * Estimate text width using average character width ratios. + * This is an approximation since SVG doesn't have built-in text measurement. + */ +function estimateTextWidth(text: string, fontSize: number): number { + // Average character width is roughly 0.6x the font size for sans-serif + return text.length * fontSize * 0.6; +} + +/** + * Render a single swatch with color, name, and hex. + * Uses clipPath to prevent text from overflowing swatch boundaries. */ function renderSwatch( color: PaletteColor, @@ -23,12 +33,21 @@ function renderSwatch( showName: boolean; showHex: boolean; font: string; - } + }, + swatchIndex: number ): string { const { x, y, width, height, radius, nameFontSize, hexFontSize } = swatch; const { showName, showHex, font } = options; const lines: string[] = []; + const clipId = `swatch-clip-${swatchIndex}`; + + // Define a clipPath for this swatch so text never overflows + lines.push( + ` `, + ` `, + ` ` + ); // Swatch background lines.push( @@ -38,20 +57,27 @@ function renderSwatch( // Calculate text positions - bottom-left aligned with padding const textPadding = Math.min(12, width * 0.08); const textX = x + textPadding; + const availableTextWidth = width - textPadding * 2; let textY = y + height - textPadding; // Hex code (bottom line) if (showHex) { lines.push( - ` ${color.hex}` + ` ${color.hex}` ); textY -= hexFontSize + 4; } - // Color name (above hex) + // Color name (above hex) - scale down font if name is too wide if (showName && color.name) { + let adjustedNameSize = nameFontSize; + const estimatedWidth = estimateTextWidth(color.name, adjustedNameSize); + if (estimatedWidth > availableTextWidth && availableTextWidth > 0) { + adjustedNameSize = Math.max(8, adjustedNameSize * (availableTextWidth / estimatedWidth)); + } + lines.push( - ` ${escapeXml(color.name)}` + ` ${escapeXml(color.name)}` ); } @@ -152,7 +178,7 @@ export function renderSvg( showName: opts.showName, showHex: opts.showHex, font, - }) + }, i) ); } });