Files
Aaru/Aaru.Helpers/AnsiColorParser.cs

153 lines
5.3 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Author(s) : Natalia Portillo <claunia@claunia.com>
//
// --[ License ] --------------------------------------------------------------
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2025 Natalia Portillo
// ****************************************************************************/
using System;
using System.Drawing;
using System.Linq;
using System.Text.RegularExpressions;
namespace Aaru.Helpers;
public static partial class AnsiColorParser
{
// Standard ANSI 16-color palette (indexes 015)
private static readonly Color[] _basicPalette =
[
Color.FromArgb(0, 0, 0), // 0: black
Color.FromArgb(128, 0, 0), // 1: red
Color.FromArgb(0, 128, 0), // 2: green
Color.FromArgb(128, 128, 0), // 3: yellow
Color.FromArgb(0, 0, 128), // 4: blue
Color.FromArgb(128, 0, 128), // 5: magenta
Color.FromArgb(0, 128, 128), // 6: cyan
Color.FromArgb(192, 192, 192), // 7: white
Color.FromArgb(128, 128, 128), // 8: bright black (gray)
Color.FromArgb(255, 0, 0), // 9: bright red
Color.FromArgb(0, 255, 0), // 10: bright green
Color.FromArgb(255, 255, 0), // 11: bright yellow
Color.FromArgb(0, 0, 255), // 12: bright blue
Color.FromArgb(255, 0, 255), // 13: bright magenta
Color.FromArgb(0, 255, 255), // 14: bright cyan
Color.FromArgb(255, 255, 255) // 15: bright white
];
// Matches ESC [ params m
private static readonly Regex _sequenceRegex = AnsiRegex();
public static Color Parse(string input)
{
Match m = _sequenceRegex.Match(input);
if(!m.Success) throw new ArgumentException("No ANSI SGR sequence found.", nameof(input));
int[] parts = m.Groups["params"]
.Value.Split([';'], StringSplitOptions.RemoveEmptyEntries)
.Select(int.Parse)
.ToArray();
bool isBold = parts.Contains(1);
bool isDim = parts.Contains(2);
// True-color: ESC[38;2;<r>;<g>;<b>m
int idx38 = Array.IndexOf(parts, 38);
switch(idx38)
{
case >= 0 when parts.Length > idx38 + 4 && parts[idx38 + 1] == 2:
{
int r = parts[idx38 + 2], g = parts[idx38 + 3], b = parts[idx38 + 4];
return Color.FromArgb(r, g, b);
}
// 256-color: ESC[38;5;<n>m
case >= 0 when parts.Length > idx38 + 2 && parts[idx38 + 1] == 5:
return ColorFrom256(parts[idx38 + 2]);
}
// 3037 and 9097 color codes
foreach(int code in parts)
{
switch(code)
{
// 3037 => palette[07]
case >= 30 and <= 37:
{
int baseIndex = code - 30;
// Bold takes precedence
if(isBold) return _basicPalette[baseIndex + 8];
return isDim ? DimColor(_basicPalette[baseIndex]) : _basicPalette[baseIndex];
}
// 9097 => palette[815]
case >= 90 and <= 97:
{
int brightIndex = code - 90 + 8;
return isDim ? DimColor(_basicPalette[brightIndex]) : _basicPalette[brightIndex];
}
}
}
// Fallback
return Color.White;
}
private static Color ColorFrom256(int index)
{
switch(index)
{
case < 16:
return _basicPalette[index];
case <= 231:
{
// 6×6×6 color cube
int ci = index - 16;
int r = ci / 36, g = ci / 6 % 6, b = ci % 6;
int[] levels = [0, 95, 135, 175, 215, 255];
return Color.FromArgb(levels[r], levels[g], levels[b]);
}
default:
{
// Grayscale 232255
int gray = 8 + (index - 232) * 10;
return Color.FromArgb(gray, gray, gray);
}
}
}
private static Color DimColor(Color c) =>
// Halve each channel to simulate faint intensity
Color.FromArgb(c.R / 2, c.G / 2, c.B / 2);
[GeneratedRegex(@"\e\[(?<params>[0-9;]*)m", RegexOptions.Compiled)]
private static partial Regex AnsiRegex();
}