From 49feaedef6e32b0d9cb5220e93d37ef7eb5079a3 Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Thu, 9 Nov 2023 14:06:06 -0600 Subject: [PATCH] core: move color parsing functions into RGB namespace --- src/terminal/color.zig | 126 +++++++++++++++++++++++++++++++++++++++++ src/termio/Exec.zig | 97 +------------------------------ 2 files changed, 127 insertions(+), 96 deletions(-) diff --git a/src/terminal/color.zig b/src/terminal/color.zig index 29e3f39de..eb2fecf5a 100644 --- a/src/terminal/color.zig +++ b/src/terminal/color.zig @@ -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:// + /// + /// , , := h | hh | hhh | hhhh + /// + /// where `h` is a single hexadecimal digit. + /// + /// Alternatively, the form + /// + /// rgbi:// + /// + /// where , , and 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")); +} diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index e3c1a395c..d875c6bfe 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -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:// - /// - /// , , := h | hh | hhh | hhhh - /// - /// where `h` is a single hexadecimal digit. - /// - /// Alternatively, the form - /// - /// rgbi:// - /// - /// where , , and 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 => {