lib/xkb/src/keysym/unicode.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const keysym = @import("root.zig");
3 const data = @import("generated/root.zig").unicode;
4
5 const Keysym = keysym.Keysym;
6
7 pub fn codepoint(value: Keysym) ?u21 {
8 const raw = @backingInt(value);
9 if (raw >= keysym.unicode_offset and raw <= keysym.unicode_max) {
10 const direct = raw - keysym.unicode_offset;
11 if (direct == 0 or isSurrogate(direct)) return null;
12 return @intCast(direct);
13 }
14 var low: usize = 0;
15 var high: usize = data.entries.len;
16 while (low < high) {
17 const middle = low + (high - low) / 2;
18 const entry = data.entries[middle];
19 if (raw < entry.keysym) {
20 high = middle;
21 } else if (raw > entry.keysym) {
22 low = middle + 1;
23 } else {
24 return entry.codepoint;
25 }
26 }
27 return null;
28 }
29
30 fn isSurrogate(value: u32) bool {
31 return value >= 0xd800 and value <= 0xdfff;
32 }
33
34 test "every pinned keysym Unicode mapping resolves" {
35 try std.testing.expectEqual(@as(usize, data.count), data.entries.len);
36 for (data.entries) |entry| {
37 try std.testing.expectEqual(@as(?u21, entry.codepoint), codepoint(@fromBackingInt(@intCast(entry.keysym))));
38 }
39 }
40
41 test "legacy special and direct keysyms resolve to Unicode scalars" {
42 const cases = [_]struct { value: u32, expected: ?u21 }{
43 .{ .value = 0x000001a1, .expected = 0x0104 },
44 .{ .value = 0x0000ff08, .expected = 0x0008 },
45 .{ .value = 0x0000ffb7, .expected = 0x0037 },
46 .{ .value = 0x10081200, .expected = 0x0030 },
47 .{ .value = 0x1008120a, .expected = 0x002a },
48 .{ .value = 0x01000080, .expected = 0x0080 },
49 .{ .value = 0x0101f600, .expected = 0x1f600 },
50 .{ .value = 0x0110ffff, .expected = 0x10ffff },
51 .{ .value = 0x0000ffff, .expected = 0x007f },
52 .{ .value = 0x0000ffbe, .expected = null },
53 .{ .value = 0x01000000, .expected = null },
54 .{ .value = 0x0100d800, .expected = null },
55 .{ .value = 0x0100dfff, .expected = null },
56 .{ .value = 0x01110000, .expected = null },
57 };
58 for (cases) |case| {
59 try std.testing.expectEqual(case.expected, codepoint(@fromBackingInt(@intCast(case.value))));
60 }
61 }