iOfficeAI/OfficeCLI · error · ArgumentException
Invalid color value: '{original}'. HSL saturation/lightness
Error message
Invalid color value: '{original}'. HSL saturation/lightness must be expressed as a percentage (e.g. 50%). What it means
Thrown by ParsePercent01 when an HSL saturation or lightness token does not end with '%'. The library deliberately requires the percent sign for HSL S/L so that the meaning is unambiguous (CSS requires it too). A bare fraction like 0.5 is rejected even though it is numerically valid.
Source
Thrown at src/officecli/Core/ParseHelpers.cs:210
{
token = token.Trim();
// Strip an optional `deg` suffix (CSS allows it; other angle units
// — turn/rad/grad — are out of scope for the input vocabulary we care
// about and would just need their own multipliers if asked for).
if (token.EndsWith("deg", StringComparison.OrdinalIgnoreCase))
token = token[..^3].Trim();
if (!double.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out var h))
throw new ArgumentException($"Invalid color value: '{original}'. Hue must be a number in degrees.");
h %= 360;
if (h < 0) h += 360;
return h;
}
private static double ParsePercent01(string token, string original)
{
token = token.Trim();
if (!token.EndsWith('%'))
throw new ArgumentException($"Invalid color value: '{original}'. HSL saturation/lightness must be expressed as a percentage (e.g. 50%).");
if (!double.TryParse(token[..^1], NumberStyles.Float, CultureInfo.InvariantCulture, out var pct)
|| pct < 0 || pct > 100)
throw new ArgumentException($"Invalid color value: '{original}'. HSL saturation/lightness must be 0%-100%.");
return pct / 100.0;
}
private static (byte R, byte G, byte B) HslToRgb(double h, double s, double l)
{
// Standard HSL → RGB (CSS Color Module Level 3).
double c = (1 - Math.Abs(2 * l - 1)) * s;
double hp = h / 60.0;
double x = c * (1 - Math.Abs(hp % 2 - 1));
double r1 = 0, g1 = 0, b1 = 0;
if (hp < 1) { r1 = c; g1 = x; b1 = 0; }
else if (hp < 2) { r1 = x; g1 = c; b1 = 0; }
else if (hp < 3) { r1 = 0; g1 = c; b1 = x; }
else if (hp < 4) { r1 = 0; g1 = x; b1 = c; }
else if (hp < 5) { r1 = x; g1 = 0; b1 = c; }View on GitHub (pinned to 1ced45e900)
Solutions
- Always append % to saturation and lightness: hsl(120,50%,50%).
- When interpolating, format with the suffix: $"hsl({h},{s}%,{l}%".
- If you hold values as 0-1 fractions, multiply by 100 and add %.
Example fix
// before
var c = $"hsl({h},{s},{l}")); // s,l are fractions 0..1
ParseColor(c); // throws 289
// after
var c = $"hsl({h},{s*100}%,{l*100}%");
ParseColor(c); Defensive patterns
Strategy: validation
Validate before calling
// ensure S/L always carry '%'
static string Pct(double f01) => $"{Math.Clamp(f01,0,1)*100}%";
// build: $"hsl({h},{Pct(s)},{Pct(l)})" Type guard
static bool IsPercentToken(string t) => t.Trim().EndsWith('%'); Try / catch
try { color = ParseColor(spec); }
catch (ArgumentException ex) when (ex.Message.Contains("expressed as a percentage"))
{ /* append % to S/L tokens, retry */ } Prevention
- Always append '%' to saturation and lightness.
- When holding 0-1 fractions, multiply by 100 and add '%'.
- Centralize hsl string formatting in one helper.
When it happens
Trigger: Passing hsl(120,0.5,0.5) (omitting %); passing hsl(120,50,50) (treating S/L as 0-100 integers without a suffix).
Common situations: Programmers used to 0-1 fraction notation; building the hsl string from numeric variables without appending %; copy-paste from a source that used the fraction form.
Related errors
- Invalid color value: '{original}'. Hue must be a number in d
- Invalid color value: '{original}'. HSL saturation/lightness
- Invalid color value: '{original}'. RGB percentage components
- Invalid color value: '{original}'. RGB components must be 0-
- Invalid color value: '{original}'. Alpha percentage must be
AI-assisted analysis of iOfficeAI/OfficeCLI@1ced45e900 (2026-08-13).
Data as JSON: /api/errors/71e511f90e1f808b.
Report an issue: GitHub.