iOfficeAI/OfficeCLI · error · ArgumentException

Invalid color value: '{original}'. HSL saturation/lightness

Error message

Invalid color value: '{original}'. HSL saturation/lightness must be 0%-100%.

What it means

Thrown by ParsePercent01 when an HSL saturation or lightness percentage parses as a number but is outside 0%-100%. Both channels are clamped-free in CSS terms here — the library rejects out-of-range rather than silently clipping, to surface bad color math.

Source

Thrown at src/officecli/Core/ParseHelpers.cs:213

        // — 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; }
        else             { r1 = c; g1 = 0; b1 = x; }
        double m = l - c / 2;
        return (

View on GitHub (pinned to 1ced45e900)

Solutions

  1. Clamp S and L to [0,100] before formatting.
  2. Audit color-space conversion code for unbounded outputs.
  3. Round and clamp in one step: Math.Clamp(Math.Round(v),0,100).

Example fix

// before
var c = $"hsl({h},{s}%,{l}%"); // s == 150
ParseColor(c); // throws 290

// after
int C(double v) => (int)Math.Clamp(Math.Round(v),0,100);
var c = $"hsl({h},{C(s)}%,{C(l)}%)");
ParseColor(c);
Defensive patterns

Strategy: validation

Validate before calling

static string ClampSl(double v) => $"{Math.Clamp(Math.Round(v),0,100)}%";
// build: $"hsl({h},{ClampSl(s)},{ClampSl(l)})"

Type guard

static bool IsValidSlPct(string token)
    => token.EndsWith('%')
       && double.TryParse(token[..^1], CultureInfo.InvariantCulture, out var v)
       && v >= 0 && v <= 100;

Try / catch

try { color = ParseColor(spec); }
catch (ArgumentException ex) when (ex.Message.Contains("must be 0%-100%"))
{ /* clamp S/L and retry */ }

Prevention

When it happens

Trigger: Passing hsl(120,150%,50%) or hsl(120,50%,-10%). A saturation/lightness computed from a gradient or blend that exceeded bounds.

Common situations: HSL manipulation code that adds saturation without clamping; UI sliders allowing >100%; converting from another color space whose gamut exceeds HSL.

Related errors


AI-assisted analysis of iOfficeAI/OfficeCLI@1ced45e900 (2026-08-13). Data as JSON: /api/errors/10165ae32773a3ef. Report an issue: GitHub.