From f4db716e2eb75195ae1935f4a164cb5c85761fed Mon Sep 17 00:00:00 2001
From: Matt Gros <3311227+mpge@users.noreply.github.com>
Date: Sun, 5 Apr 2026 14:38:02 -0400
Subject: [PATCH] fix: clip color name text to swatch boundaries
Add clipPath elements to each swatch so text never overflows into
adjacent swatches. Also scale down the name font size when the
estimated text width exceeds the available swatch width, with a
minimum font size of 8px.
Closes #1
---
src/render/svg.ts | 38 ++++++++++++++++++++++++++++++++------
1 file changed, 32 insertions(+), 6 deletions(-)
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)
);
}
});