core: move color parsing functions into RGB namespace

This commit is contained in:
Gregory Anders
2023-11-09 14:06:06 -06:00
parent 006e93bd08
commit 49feaedef6
2 changed files with 127 additions and 96 deletions

View File

@ -147,6 +147,112 @@ pub const RGB = struct {
try std.testing.expectEqual(@as(usize, 24), @bitSizeOf(RGB));
try std.testing.expectEqual(@as(usize, 3), @sizeOf(RGB));
}
/// Parse a color from a floating point intensity value.
///
/// The value should be between 0.0 and 1.0, inclusive.
fn fromIntensity(value: []const u8) !u8 {
const i = std.fmt.parseFloat(f64, value) catch return error.InvalidFormat;
if (i < 0.0 or i > 1.0) {
return error.InvalidFormat;
}
return @intFromFloat(i * std.math.maxInt(u8));
}
/// Parse a color from a string of hexadecimal digits
///
/// The string can contain 1, 2, 3, or 4 characters and represents the color
/// value scaled in 4, 8, 12, or 16 bits, respectively.
fn fromHex(value: []const u8) !u8 {
if (value.len == 0 or value.len > 4) {
return error.InvalidFormat;
}
const color = std.fmt.parseUnsigned(u16, value, 16) catch return error.InvalidFormat;
const divisor: usize = switch (value.len) {
1 => std.math.maxInt(u4),
2 => std.math.maxInt(u8),
3 => std.math.maxInt(u12),
4 => std.math.maxInt(u16),
else => unreachable,
};
return @intCast(@as(usize, color) * std.math.maxInt(u8) / divisor);
}
/// Parse a color specification of the form
///
/// rgb:<red>/<green>/<blue>
///
/// <red>, <green>, <blue> := h | hh | hhh | hhhh
///
/// where `h` is a single hexadecimal digit.
///
/// Alternatively, the form
///
/// rgbi:<red>/<green>/<blue>
///
/// where <red>, <green>, and <blue> are floating point values between 0.0
/// and 1.0 (inclusive) is also accepted.
pub fn parse(value: []const u8) !RGB {
const minimum_length = "rgb:a/a/a".len;
if (value.len < minimum_length or !std.mem.eql(u8, value[0..3], "rgb")) {
return error.InvalidFormat;
}
var i: usize = 3;
const use_intensity = if (value[i] == 'i') blk: {
i += 1;
break :blk true;
} else false;
if (value[i] != ':') {
return error.InvalidFormat;
}
i += 1;
const r = r: {
const slice = if (std.mem.indexOfScalarPos(u8, value, i, '/')) |end|
value[i..end]
else
return error.InvalidFormat;
i += slice.len + 1;
break :r if (use_intensity)
try RGB.fromIntensity(slice)
else
try RGB.fromHex(slice);
};
const g = g: {
const slice = if (std.mem.indexOfScalarPos(u8, value, i, '/')) |end|
value[i..end]
else
return error.InvalidFormat;
i += slice.len + 1;
break :g if (use_intensity)
try RGB.fromIntensity(slice)
else
try RGB.fromHex(slice);
};
const b = if (use_intensity)
try RGB.fromIntensity(value[i..])
else
try RGB.fromHex(value[i..]);
return RGB{
.r = r,
.g = g,
.b = b,
};
}
};
test "palette: default" {
@ -158,3 +264,23 @@ test "palette: default" {
try testing.expectEqual(Name.default(@as(Name, @enumFromInt(i))), default[i]);
}
}
test "RGB.parse" {
const testing = std.testing;
try testing.expectEqual(RGB{ .r = 255, .g = 0, .b = 0 }, try RGB.parse("rgbi:1.0/0/0"));
try testing.expectEqual(RGB{ .r = 127, .g = 160, .b = 0 }, try RGB.parse("rgb:7f/a0a0/0"));
try testing.expectEqual(RGB{ .r = 255, .g = 255, .b = 255 }, try RGB.parse("rgb:f/ff/fff"));
// Invalid format
try testing.expectError(error.InvalidFormat, RGB.parse("rgb;"));
try testing.expectError(error.InvalidFormat, RGB.parse("rgb:"));
try testing.expectError(error.InvalidFormat, RGB.parse(":a/a/a"));
try testing.expectError(error.InvalidFormat, RGB.parse("a/a/a"));
try testing.expectError(error.InvalidFormat, RGB.parse("rgb:a/a/a/"));
try testing.expectError(error.InvalidFormat, RGB.parse("rgb:00000///"));
try testing.expectError(error.InvalidFormat, RGB.parse("rgb:000/"));
try testing.expectError(error.InvalidFormat, RGB.parse("rgbi:a/a/a"));
try testing.expectError(error.InvalidFormat, RGB.parse("rgb:0.5/0.0/1.0"));
try testing.expectError(error.InvalidFormat, RGB.parse("rgb:not/hex/zz"));
}

View File

@ -2201,107 +2201,12 @@ const StreamHandler = struct {
self.messageWriter(msg);
}
/// Parse a color from a string of hexadecimal digits or a floating point
/// intensity value.
///
/// If `intensity` is false, the string can contain 1, 2, 3, or 4 characters
/// and represents the color value scaled in 4, 8, 12, or 16 bits,
/// respectively.
///
/// If `intensity` is true, the string should contain a floating point value
/// between 0.0 and 1.0, inclusive.
fn parseColor(value: []const u8, intensity: bool) !u8 {
if (intensity) {
const i = try std.fmt.parseFloat(f64, value);
if (i < 0.0 or i > 1.0) {
return error.InvalidValue;
}
return @intFromFloat(i * std.math.maxInt(u8));
}
if (value.len == 0 or value.len > 4) {
return error.InvalidValue;
}
const color = try std.fmt.parseUnsigned(u16, value, 16);
const divisor: usize = switch (value.len) {
1 => std.math.maxInt(u4),
2 => std.math.maxInt(u8),
3 => std.math.maxInt(u12),
4 => std.math.maxInt(u16),
else => unreachable,
};
return @intCast(color * std.math.maxInt(u8) / divisor);
}
/// Parse a color specification of the form
///
/// rgb:<red>/<green>/<blue>
///
/// <red>, <green>, <blue> := h | hh | hhh | hhhh
///
/// where `h` is a single hexadecimal digit.
///
/// Alternatively, the form
///
/// rgbi:<red>/<green>/<blue>
///
/// where <red>, <green>, and <blue> are floating point values between 0.0
/// and 1.0 (inclusive) is also accepted.
fn parseColorSpec(value: []const u8) !terminal.color.RGB {
const minimum_length = "rgb:a/a/a".len;
if (value.len < minimum_length or !std.mem.eql(u8, value[0..3], "rgb")) {
return error.InvalidFormat;
}
var i: usize = 3;
const use_intensity = if (value[i] == 'i') blk: {
i += 1;
break :blk true;
} else false;
if (value[i] != ':') {
return error.InvalidFormat;
}
i += 1;
const r = r: {
const slash_i = std.mem.indexOfScalarPos(u8, value, i, '/') orelse
return error.InvalidFormat;
const r = try parseColor(value[i..slash_i], use_intensity);
i = slash_i + 1;
break :r r;
};
const g = g: {
const slash_i = std.mem.indexOfScalarPos(u8, value, i, '/') orelse
return error.InvalidFormat;
const g = try parseColor(value[i..slash_i], use_intensity);
i = slash_i + 1;
break :g g;
};
const b = try parseColor(value[i..], use_intensity);
return terminal.color.RGB{
.r = r,
.g = g,
.b = b,
};
}
pub fn setDefaultColor(
self: *StreamHandler,
kind: terminal.osc.Command.DefaultColorKind,
value: []const u8,
) !void {
const color = try parseColorSpec(value);
const color = try terminal.color.RGB.parse(value);
switch (kind) {
.foreground => {