mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
core: move color parsing functions into RGB namespace
This commit is contained in:
@ -147,6 +147,112 @@ pub const RGB = struct {
|
|||||||
try std.testing.expectEqual(@as(usize, 24), @bitSizeOf(RGB));
|
try std.testing.expectEqual(@as(usize, 24), @bitSizeOf(RGB));
|
||||||
try std.testing.expectEqual(@as(usize, 3), @sizeOf(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" {
|
test "palette: default" {
|
||||||
@ -158,3 +264,23 @@ test "palette: default" {
|
|||||||
try testing.expectEqual(Name.default(@as(Name, @enumFromInt(i))), default[i]);
|
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"));
|
||||||
|
}
|
||||||
|
@ -2201,107 +2201,12 @@ const StreamHandler = struct {
|
|||||||
self.messageWriter(msg);
|
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(
|
pub fn setDefaultColor(
|
||||||
self: *StreamHandler,
|
self: *StreamHandler,
|
||||||
kind: terminal.osc.Command.DefaultColorKind,
|
kind: terminal.osc.Command.DefaultColorKind,
|
||||||
value: []const u8,
|
value: []const u8,
|
||||||
) !void {
|
) !void {
|
||||||
const color = try parseColorSpec(value);
|
const color = try terminal.color.RGB.parse(value);
|
||||||
|
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
.foreground => {
|
.foreground => {
|
||||||
|
Reference in New Issue
Block a user