Hugo Machet: 3 Add rgb and 256 colours mode support Add a string parser to return a Colour Add a function to get RGB from HTML name 8 files changed, 353 insertions(+), 9 deletions(-)
zig-spoon/patches/alpine.yml: SUCCESS in 34s [Add rgb and 256 colours mode support][0] from [Hugo Machet][1] [0]: https://lists.sr.ht/~leon_plickat/public-inbox/patches/32345 [1]: mailto:mail@hmachet.com ✓ #760273 SUCCESS zig-spoon/patches/alpine.yml https://builds.sr.ht/~leon_plickat/job/760273
Thanks for working on this!
Copy & paste the following snippet into your terminal to import this patchset into git:
curl -s https://lists.sr.ht/~leon_plickat/public-inbox/patches/32345/mbox | git am -3Learn more about email & git
Implements: https://todo.sr.ht/~leon_plickat/zig-spoon/4 --- example/colours.zig | 4 ++-- lib/Attribute.zig | 35 ++++++++++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/example/colours.zig b/example/colours.zig index 431a3a6073fa..43a0854746ca 100644 --- a/example/colours.zig +++ b/example/colours.zig @@ -8,8 +8,8 @@ const spoon = @import("spoon"); const red = spoon.Attribute{ .fg = .red, .italic = true }; const green = spoon.Attribute{ .fg = .green, .blinking = true }; -const blue = spoon.Attribute{ .fg = .blue, .bold = true }; -const cyan = spoon.Attribute{ .fg = .cyan, .reverse = true }; +const blue = spoon.Attribute{ .fg = .{ .rgb = .{ 137, 207, 240 } }, .bold = true }; +const cyan = spoon.Attribute{ .fg = .{ .@"256" = 123 }, .reverse = true }; const reset = spoon.Attribute{}; pub fn main() !void { diff --git a/lib/Attribute.zig b/lib/Attribute.zig index 2aa3d17c2c4a..c75468de1e30 100644 --- a/lib/Attribute.zig +++ b/lib/Attribute.zig @@ -6,7 +6,7 @@ const Self = @This(); -const Colour = enum { +const Colour = union(enum) { none, black, red, @@ -24,6 +24,11 @@ const Colour = enum { bright_magenta, bright_cyan, bright_white, + + /// 0-255 colours + @"256": u8, + /// u8{<red 0-255>, <green 0-255>, <blue 0-255>} + rgb: [3]u8, }; fg: Colour = .white, @@ -75,6 +80,20 @@ pub fn dump(self: Self, writer: anytype) !void { .bright_magenta => try writer.writeAll(";95"), .bright_cyan => try writer.writeAll(";96"), .bright_white => try writer.writeAll(";97"), + .@"256" => { + try writer.writeAll("m"); + try writer.writeAll("\x1b[38;5;"); + try writer.print("{d}", .{self.fg.@"256"}); + }, + .rgb => { + try writer.writeAll("m"); + try writer.writeAll("\x1b[38;2;"); + try writer.print("{d};{d};{d}", .{ + self.fg.rgb[0], + self.fg.rgb[1], + self.fg.rgb[2], + }); + }, } switch (self.bg) { .none => {}, @@ -94,6 +113,20 @@ pub fn dump(self: Self, writer: anytype) !void { .bright_magenta => try writer.writeAll(";105"), .bright_cyan => try writer.writeAll(";106"), .bright_white => try writer.writeAll(";107"), + .@"256" => { + try writer.writeAll("m"); + try writer.writeAll("\x1b[48;5;"); + try writer.print("{d}", .{self.fg.@"256"}); + }, + .rgb => { + try writer.writeAll("m"); + try writer.writeAll("\x1b[48;2;"); + try writer.print("{d};{d};{d}", .{ + self.fg.rgb[0], + self.fg.rgb[1], + self.fg.rgb[2], + }); + }, } try writer.writeAll("m"); } -- 2.36.1
Examples: var rgb = try parseColoursString("0x2b45a7"); var 256 = try parseColoursString("234"); var ansi = try parseColoursString("red"); --- example/colours.zig | 13 +++-- lib/Attribute.zig | 134 ++++++++++++++++++++++++++++++++++++++++++++ test_main.zig | 1 + 3 files changed, 142 insertions(+), 6 deletions(-) diff --git a/example/colours.zig b/example/colours.zig index 43a0854746ca..4be9e63f0aaf 100644 --- a/example/colours.zig +++ b/example/colours.zig @@ -6,13 +6,14 @@ const io = std.io; const spoon = @import("spoon"); -const red = spoon.Attribute{ .fg = .red, .italic = true }; -const green = spoon.Attribute{ .fg = .green, .blinking = true }; -const blue = spoon.Attribute{ .fg = .{ .rgb = .{ 137, 207, 240 } }, .bold = true }; -const cyan = spoon.Attribute{ .fg = .{ .@"256" = 123 }, .reverse = true }; -const reset = spoon.Attribute{}; - pub fn main() !void { + const red = spoon.Attribute{ .fg = .red, .italic = true }; + const green_str = try spoon.Attribute.parseColoursString("green"); + const green = spoon.Attribute{ .fg = green_str, .blinking = true }; + const blue = spoon.Attribute{ .fg = .{ .rgb = .{ 137, 207, 240 } }, .bold = true }; + const cyan = spoon.Attribute{ .fg = .{ .@"256" = 123 }, .reverse = true }; + const reset = spoon.Attribute{}; + const writer = io.getStdOut().writer(); try red.dump(writer); diff --git a/lib/Attribute.zig b/lib/Attribute.zig index c75468de1e30..af4f7ff0055f 100644 --- a/lib/Attribute.zig +++ b/lib/Attribute.zig @@ -4,6 +4,10 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +const std = @import("std"); +const fmt = std.fmt; +const mem = std.mem; + const Self = @This(); const Colour = union(enum) { @@ -130,3 +134,133 @@ pub fn dump(self: Self, writer: anytype) !void { } try writer.writeAll("m"); } + +/// RGB colours: "0xRRGGBB" +/// 256 colours: "154" +/// ANSI colours: a string equal to one of Colour fields (case sensitive) +/// For Example: +/// var rgb = try parseColoursString("0x2b45a7"); +/// var 256 = try parseColoursString("234"); +/// var ansi = try parseColoursString("red"); +pub fn parseColoursString(s: []const u8) !Colour { + var ret: Colour = .none; + + switch (s[0]) { + '0'...'9' => { + if (s[0] == '0' and s[1] == 'x') { + const color = try hexToRgb(s); + ret = .{ .rgb = color }; + } else { + const color = fmt.parseUnsigned(u8, s, 10) catch |err| switch (err) { + error.Overflow => return error.BadColoursFormat, + else => return err, + }; + ret = .{ .@"256" = color }; + } + }, + 'a'...'z' => { + if (mem.eql(u8, s, "none")) { + ret = .none; + } else if (mem.eql(u8, s, "black")) { + ret = .black; + } else if (mem.eql(u8, s, "red")) { + ret = .red; + } else if (mem.eql(u8, s, "green")) { + ret = .green; + } else if (mem.eql(u8, s, "yellow")) { + ret = .yellow; + } else if (mem.eql(u8, s, "blue")) { + ret = .blue; + } else if (mem.eql(u8, s, "magenta")) { + ret = .magenta; + } else if (mem.eql(u8, s, "cyan")) { + ret = .cyan; + } else if (mem.eql(u8, s, "white")) { + ret = .white; + } else if (mem.eql(u8, s, "bright_black")) { + ret = .bright_black; + } else if (mem.eql(u8, s, "bright_red")) { + ret = .bright_red; + } else if (mem.eql(u8, s, "bright_green")) { + ret = .bright_green; + } else if (mem.eql(u8, s, "bright_yellow")) { + ret = .bright_yellow; + } else if (mem.eql(u8, s, "bright_blue")) { + ret = .bright_blue; + } else if (mem.eql(u8, s, "bright_magenta")) { + ret = .bright_magenta; + } else if (mem.eql(u8, s, "bright_cyan")) { + ret = .bright_cyan; + } else if (mem.eql(u8, s, "bright_white")) { + ret = .bright_white; + } else { + return error.BadColoursFormat; + } + }, + else => return error.BadColoursFormat, + } + + return ret; +} + +/// Convert a string in the format "0xRRGGBB" to [3]u8. +fn hexToRgb(s: []const u8) ![3]u8 { + if (s.len != 8) return error.BadRgbFormat; + if (s[0] != '0' or s[1] != 'x') return error.BadRgbFormat; + + var color = try fmt.parseUnsigned(u32, s[2..], 16); + color <<= 8; + color |= 0xff; + + const bytes = @bitCast([4]u8, color); + return [3]u8{ + @truncate(u8, @as(u16, bytes[3]) * 0x101), + @truncate(u8, @as(u16, bytes[2]) * 0x101), + @truncate(u8, @as(u16, bytes[1]) * 0x101), + }; +} + +test "parse colours string (good input)" { + const testing = std.testing; + + try testing.expectEqual( + Colour{ .rgb = .{ 255, 255, 255 } }, + try parseColoursString("0xffffff"), + ); + try testing.expectEqual( + Colour{ .rgb = .{ 0, 169, 143 } }, + try parseColoursString("0x00a98f"), + ); + try testing.expectEqual( + Colour{ .@"256" = 54 }, + try parseColoursString("54"), + ); + try testing.expectEqual( + Colour{ .@"256" = 007 }, + try parseColoursString("7"), + ); + try testing.expectEqual( + Colour{ .@"256" = 237 }, + try parseColoursString("237"), + ); + try testing.expectEqual( + Colour.bright_black, + try parseColoursString("bright_black"), + ); + try testing.expectEqual( + Colour.blue, + try parseColoursString("blue"), + ); +} + +test "parse colours string (bad input)" { + const testing = std.testing; + + try testing.expectError(error.BadRgbFormat, parseColoursString("0xfff")); + + try testing.expectError(error.BadColoursFormat, parseColoursString("xffffff")); + try testing.expectError(error.BadColoursFormat, parseColoursString("ffffff")); + try testing.expectError(error.BadColoursFormat, parseColoursString("blu")); + try testing.expectError(error.BadColoursFormat, parseColoursString("bLue")); + try testing.expectError(error.BadColoursFormat, parseColoursString("256")); +} diff --git a/test_main.zig b/test_main.zig index 4dba7ce47076..ed8aba1a74f4 100644 --- a/test_main.zig +++ b/test_main.zig @@ -1,4 +1,5 @@ test { _ = @import("lib/input.zig"); _ = @import("lib/input_description.zig"); + _ = @import("lib/Attribute.zig"); } -- 2.36.1
--- Feel free to just ignore this one if that not something that you are interested in. example/colours.zig | 4 ++ lib/Attribute.zig | 7 ++ lib/colours_name.zig | 164 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 175 insertions(+) create mode 100644 lib/colours_name.zig diff --git a/example/colours.zig b/example/colours.zig index 4be9e63f0aaf..8a029854cc44 100644 --- a/example/colours.zig +++ b/example/colours.zig @@ -11,6 +11,8 @@ pub fn main() !void { const green_str = try spoon.Attribute.parseColoursString("green"); const green = spoon.Attribute{ .fg = green_str, .blinking = true }; const blue = spoon.Attribute{ .fg = .{ .rgb = .{ 137, 207, 240 } }, .bold = true }; + const hotpink = try spoon.Attribute.rgbFromName("hotpink"); + const pink = spoon.Attribute{ .fg = .{ .rgb = hotpink } }; const cyan = spoon.Attribute{ .fg = .{ .@"256" = 123 }, .reverse = true }; const reset = spoon.Attribute{};
Instead of changing these colours, I'd prefer a second line below. Or, maybe create yet another example program, that prints a colour table of all thejust 256 colours. > bright_white, > + > + /// 0-255 colours > + @"256": u8, > + /// u8{<red 0-255>, <green 0-255>, <blue 0-255>} > + rgb: [3]u8, > }; These comments aren't really needed. > + const red = spoon.Attribute{ .fg = .red, .italic = true }; > + const green_str = try spoon.Attribute.parseColoursString("green"); > + const green = spoon.Attribute{ .fg = green_str, .blinking = true }; > + const blue = spoon.Attribute{ .fg = .{ .rgb = .{ 137, 207, 240 } }, .bold = true }; > + const cyan = spoon.Attribute{ .fg = .{ .@"256" = 123 }, .reverse = true }; > + const reset = spoon.Attribute{}; The colour description parser should work at comptime. To demonstrate that, I think these consts should not be moved inside main.
@@ -22,6 +24,8 @@ pub fn main() !void { try writer.writeAll("bar "); try blue.dump(writer); try writer.writeAll("baz "); + try pink.dump(writer); + try writer.writeAll("zig "); try cyan.dump(writer); try writer.writeAll("spoon\n"); diff --git a/lib/Attribute.zig b/lib/Attribute.zig index af4f7ff0055f..b1c0c3b36d25 100644 --- a/lib/Attribute.zig +++ b/lib/Attribute.zig @@ -8,6 +8,8 @@ const std = @import("std"); const fmt = std.fmt; const mem = std.mem; +const colours_name = @import("colours_name.zig"); +
I would prefer the parser living in it's own file, colour_description.zig. Also it should be called parseColourDescription(). I'll take a closer look at the parser itself in the next review, although I think right now it will blow up if you pass an empty string. > Feel free to just ignore this one if that not something that you are > interested in. Not sure yet. Initially I was against it, but I'll think about it. However, if this is going to be included, then it will need to be integrated into parseColourDescription(), with a format like `html(hotpink)`. That way, we could eventually also support other colour namespaces, like `x11(grey)`. Again, I am not sure about this yet. I don't think any other TUI library - or any TUI program whatsoever - has these options, so I do kinda want to have is just for the sake of it. Anyway, looking forward to the v2. Friendly greetings, Leon Henrik Plickat
const Self = @This(); const Colour = union(enum) { @@ -135,6 +137,11 @@ pub fn dump(self: Self, writer: anytype) !void { try writer.writeAll("m"); } +/// Return a RGB version from a name of a HTML colour. +pub fn rgbFromName(name: []const u8) ![3]u8 { + return colours_name.name.get(name) orelse return error.UnknowColourName; +} + /// RGB colours: "0xRRGGBB" /// 256 colours: "154" /// ANSI colours: a string equal to one of Colour fields (case sensitive) diff --git a/lib/colours_name.zig b/lib/colours_name.zig new file mode 100644 index 000000000000..7e346c73e087 --- /dev/null +++ b/lib/colours_name.zig @@ -0,0 +1,164 @@ +// Copyright © 2022 Hugo Machet +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +const std = @import("std"); + +/// REF: https://www.w3.org/TR/css-color-3/#svg-color +pub const name = blk: { + @setEvalBranchQuota(4000); + break :blk std.ComptimeStringMap( + [3]u8, + .{ + .{ "aliceblue", .{ 240, 248, 255 } }, + .{ "antiquewhite", .{ 250, 235, 215 } }, + .{ "aqua", .{ 0, 255, 255 } }, + .{ "aquamarine", .{ 127, 255, 212 } }, + .{ "azure", .{ 240, 255, 255 } }, + .{ "beige", .{ 245, 245, 220 } }, + .{ "bisque", .{ 255, 228, 196 } }, + .{ "black", .{ 0, 0, 0 } }, + .{ "blanchedalmond", .{ 255, 235, 205 } }, + .{ "blue", .{ 0, 0, 255 } }, + .{ "blueviolet", .{ 138, 43, 226 } }, + .{ "brown", .{ 165, 42, 42 } }, + .{ "burlywood", .{ 222, 184, 135 } }, + .{ "cadetblue", .{ 95, 158, 160 } }, + .{ "chartreuse", .{ 127, 255, 0 } }, + .{ "chocolate", .{ 210, 105, 30 } }, + .{ "coral", .{ 255, 127, 80 } }, + .{ "cornflowerblue", .{ 100, 149, 237 } }, + .{ "cornsilk", .{ 255, 248, 220 } }, + .{ "crimson", .{ 220, 20, 60 } }, + .{ "cyan", .{ 0, 255, 255 } }, + .{ "darkblue", .{ 0, 0, 139 } }, + .{ "darkcyan", .{ 0, 139, 139 } }, + .{ "darkgoldenrod", .{ 184, 134, 11 } }, + .{ "darkgray", .{ 169, 169, 169 } }, + .{ "darkgreen", .{ 0, 100, 0 } }, + .{ "darkgrey", .{ 169, 169, 169 } }, + .{ "darkkhaki", .{ 189, 183, 107 } }, + .{ "darkmagenta", .{ 139, 0, 139 } }, + .{ "darkolivegreen", .{ 85, 107, 47 } }, + .{ "darkorange", .{ 255, 140, 0 } }, + .{ "darkorchid", .{ 153, 50, 204 } }, + .{ "darkred", .{ 139, 0, 0 } }, + .{ "darksalmon", .{ 233, 150, 122 } }, + .{ "darkseagreen", .{ 143, 188, 143 } }, + .{ "darkslateblue", .{ 72, 61, 139 } }, + .{ "darkslategray", .{ 47, 79, 79 } }, + .{ "darkslategrey", .{ 47, 79, 79 } }, + .{ "darkturquoise", .{ 0, 206, 209 } }, + .{ "darkviolet", .{ 148, 0, 211 } }, + .{ "deeppink", .{ 255, 20, 147 } }, + .{ "deepskyblue", .{ 0, 191, 255 } }, + .{ "dimgray", .{ 105, 105, 105 } }, + .{ "dimgrey", .{ 105, 105, 105 } }, + .{ "dodgerblue", .{ 30, 144, 255 } }, + .{ "firebrick", .{ 178, 34, 34 } }, + .{ "floralwhite", .{ 255, 250, 240 } }, + .{ "forestgreen", .{ 34, 139, 34 } }, + .{ "fuchsia", .{ 255, 0, 255 } }, + .{ "gainsboro", .{ 220, 220, 220 } }, + .{ "ghostwhite", .{ 248, 248, 255 } }, + .{ "gold", .{ 255, 215, 0 } }, + .{ "goldenrod", .{ 218, 165, 32 } }, + .{ "gray", .{ 128, 128, 128 } }, + .{ "green", .{ 0, 128, 0 } }, + .{ "greenyellow", .{ 173, 255, 47 } }, + .{ "grey", .{ 128, 128, 128 } }, + .{ "honeydew", .{ 240, 255, 240 } }, + .{ "hotpink", .{ 255, 105, 180 } }, + .{ "indianred", .{ 205, 92, 92 } }, + .{ "indigo", .{ 75, 0, 130 } }, + .{ "ivory", .{ 255, 255, 240 } }, + .{ "khaki", .{ 240, 230, 140 } }, + .{ "lavender", .{ 230, 230, 250 } }, + .{ "lavenderblush", .{ 255, 240, 245 } }, + .{ "lawngreen", .{ 124, 252, 0 } }, + .{ "lemonchiffon", .{ 255, 250, 205 } }, + .{ "lightblue", .{ 173, 216, 230 } }, + .{ "lightcoral", .{ 240, 128, 128 } }, + .{ "lightcyan", .{ 224, 255, 255 } }, + .{ "lightgoldenrodyellow", .{ 250, 250, 210 } }, + .{ "lightgray", .{ 211, 211, 211 } }, + .{ "lightgreen", .{ 144, 238, 144 } }, + .{ "lightgrey", .{ 211, 211, 211 } }, + .{ "lightpink", .{ 255, 182, 193 } }, + .{ "lightsalmon", .{ 255, 160, 122 } }, + .{ "lightseagreen", .{ 32, 178, 170 } }, + .{ "lightskyblue", .{ 135, 206, 250 } }, + .{ "lightslategray", .{ 119, 136, 153 } }, + .{ "lightslategrey", .{ 119, 136, 153 } }, + .{ "lightsteelblue", .{ 176, 196, 222 } }, + .{ "lightyellow", .{ 255, 255, 224 } }, + .{ "lime", .{ 0, 255, 0 } }, + .{ "limegreen", .{ 50, 205, 50 } }, + .{ "linen", .{ 250, 240, 230 } }, + .{ "magenta", .{ 255, 0, 255 } }, + .{ "maroon", .{ 128, 0, 0 } }, + .{ "mediumaquamarine", .{ 102, 205, 170 } }, + .{ "mediumblue", .{ 0, 0, 205 } }, + .{ "mediumorchid", .{ 186, 85, 211 } }, + .{ "mediumpurple", .{ 147, 112, 219 } }, + .{ "mediumseagreen", .{ 60, 179, 113 } }, + .{ "mediumslateblue", .{ 123, 104, 238 } }, + .{ "mediumspringgreen", .{ 0, 250, 154 } }, + .{ "mediumturquoise", .{ 72, 209, 204 } }, + .{ "mediumvioletred", .{ 199, 21, 133 } }, + .{ "midnightblue", .{ 25, 25, 112 } }, + .{ "mintcream", .{ 245, 255, 250 } }, + .{ "mistyrose", .{ 255, 228, 225 } }, + .{ "moccasin", .{ 255, 228, 181 } }, + .{ "navajowhite", .{ 255, 222, 173 } }, + .{ "navy", .{ 0, 0, 128 } }, + .{ "oldlace", .{ 253, 245, 230 } }, + .{ "olive", .{ 128, 128, 0 } }, + .{ "olivedrab", .{ 107, 142, 35 } }, + .{ "orange", .{ 255, 165, 0 } }, + .{ "orangered", .{ 255, 69, 0 } }, + .{ "orchid", .{ 218, 112, 214 } }, + .{ "palegoldenrod", .{ 238, 232, 170 } }, + .{ "palegreen", .{ 152, 251, 152 } }, + .{ "paleturquoise", .{ 175, 238, 238 } }, + .{ "palevioletred", .{ 219, 112, 147 } }, + .{ "papayawhip", .{ 255, 239, 213 } }, + .{ "peachpuff", .{ 255, 218, 185 } }, + .{ "peru", .{ 205, 133, 63 } }, + .{ "pink", .{ 255, 192, 203 } }, + .{ "plum", .{ 221, 160, 221 } }, + .{ "powderblue", .{ 176, 224, 230 } }, + .{ "purple", .{ 128, 0, 128 } }, + .{ "red", .{ 255, 0, 0 } }, + .{ "rosybrown", .{ 188, 143, 143 } }, + .{ "royalblue", .{ 65, 105, 225 } }, + .{ "saddlebrown", .{ 139, 69, 19 } }, + .{ "salmon", .{ 250, 128, 114 } }, + .{ "sandybrown", .{ 244, 164, 96 } }, + .{ "seagreen", .{ 46, 139, 87 } }, + .{ "seashell", .{ 255, 245, 238 } }, + .{ "sienna", .{ 160, 82, 45 } }, + .{ "silver", .{ 192, 192, 192 } }, + .{ "skyblue", .{ 135, 206, 235 } }, + .{ "slateblue", .{ 106, 90, 205 } }, + .{ "slategray", .{ 112, 128, 144 } }, + .{ "slategrey", .{ 112, 128, 144 } }, + .{ "snow", .{ 255, 250, 250 } }, + .{ "springgreen", .{ 0, 255, 127 } }, + .{ "steelblue", .{ 70, 130, 180 } }, + .{ "tan", .{ 210, 180, 140 } }, + .{ "teal", .{ 0, 128, 128 } }, + .{ "thistle", .{ 216, 191, 216 } }, + .{ "tomato", .{ 255, 99, 71 } }, + .{ "turquoise", .{ 64, 224, 208 } }, + .{ "violet", .{ 238, 130, 238 } }, + .{ "wheat", .{ 245, 222, 179 } }, + .{ "white", .{ 255, 255, 255 } }, + .{ "whitesmoke", .{ 245, 245, 245 } }, + .{ "yellow", .{ 255, 255, 0 } }, + .{ "yellowgreen", .{ 154, 205, 50 } }, + }, + ); +}; -- 2.36.1
builds.sr.ht <builds@sr.ht>zig-spoon/patches/alpine.yml: SUCCESS in 34s [Add rgb and 256 colours mode support][0] from [Hugo Machet][1] [0]: https://lists.sr.ht/~leon_plickat/public-inbox/patches/32345 [1]: mailto:mail@hmachet.com ✓ #760273 SUCCESS zig-spoon/patches/alpine.yml https://builds.sr.ht/~leon_plickat/job/760273
Thanks for working on this!