lib/filigree/src/layout/class.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 
  3 const binary = @import("binary.zig");
  4 const LayoutError = binary.LayoutError;
  5 const hasBytes = binary.hasBytes;
  6 const readU16 = binary.readU16;
  7 
  8 pub const ClassDef = union(enum) {
  9     format_1: ClassDefFormat1,
 10     format_2: ClassDefFormat2,
 11 
 12     pub fn init(data: []const u8, offset: usize) LayoutError!ClassDef {
 13         if (!hasBytes(data, offset, 2)) return error.InvalidLayout;
 14         return switch (try readU16(data, offset)) {
 15             1 => .{ .format_1 = try ClassDefFormat1.init(data, offset) },
 16             2 => .{ .format_2 = try ClassDefFormat2.init(data, offset) },
 17             else => error.UnsupportedClassDef,
 18         };
 19     }
 20 
 21     pub fn class(self: ClassDef, data: []const u8, glyph_id: u32) LayoutError!u16 {
 22         return switch (self) {
 23             .format_1 => |format| format.class(data, glyph_id),
 24             .format_2 => |format| format.class(data, glyph_id),
 25         };
 26     }
 27 };
 28 
 29 pub const ClassDefFormat1 = struct {
 30     start_glyph_id: u16,
 31     class_value_offset: usize,
 32     glyph_count: u16,
 33 
 34     fn init(data: []const u8, offset: usize) LayoutError!ClassDefFormat1 {
 35         if (!hasBytes(data, offset, 6)) return error.InvalidLayout;
 36         const glyph_count = try readU16(data, offset + 4);
 37         const array_offset = offset + 6;
 38         const array_len = @as(usize, glyph_count) * 2;
 39         if (!hasBytes(data, array_offset, array_len)) return error.InvalidLayout;
 40         return .{
 41             .start_glyph_id = try readU16(data, offset + 2),
 42             .class_value_offset = array_offset,
 43             .glyph_count = glyph_count,
 44         };
 45     }
 46 
 47     fn class(self: ClassDefFormat1, data: []const u8, glyph_id: u32) LayoutError!u16 {
 48         if (glyph_id > std.math.maxInt(u16)) return 0;
 49         const glyph: u16 = @intCast(glyph_id);
 50         if (glyph < self.start_glyph_id) return 0;
 51         const index = @as(usize, glyph - self.start_glyph_id);
 52         if (index >= self.glyph_count) return 0;
 53         return try readU16(data, self.class_value_offset + index * 2);
 54     }
 55 };
 56 
 57 pub const ClassDefFormat2 = struct {
 58     class_range_array_offset: usize,
 59     class_range_count: u16,
 60 
 61     fn init(data: []const u8, offset: usize) LayoutError!ClassDefFormat2 {
 62         if (!hasBytes(data, offset, 4)) return error.InvalidLayout;
 63         const range_count = try readU16(data, offset + 2);
 64         const array_offset = offset + 4;
 65         const array_len = @as(usize, range_count) * 6;
 66         if (!hasBytes(data, array_offset, array_len)) return error.InvalidLayout;
 67         var previous_end: ?u16 = null;
 68         for (0..range_count) |i| {
 69             const record = array_offset + i * 6;
 70             const start = try readU16(data, record);
 71             const end = try readU16(data, record + 2);
 72             if (start > end) return error.InvalidLayout;
 73             if (previous_end) |seen| {
 74                 if (start <= seen) return error.InvalidLayout;
 75             }
 76             previous_end = end;
 77         }
 78         return .{
 79             .class_range_array_offset = array_offset,
 80             .class_range_count = range_count,
 81         };
 82     }
 83 
 84     fn class(self: ClassDefFormat2, data: []const u8, glyph_id: u32) LayoutError!u16 {
 85         if (glyph_id > std.math.maxInt(u16)) return 0;
 86         const glyph: u16 = @intCast(glyph_id);
 87         var left: usize = 0;
 88         var right: usize = self.class_range_count;
 89         while (left < right) {
 90             const mid = left + (right - left) / 2;
 91             const record = self.class_range_array_offset + mid * 6;
 92             const start = try readU16(data, record);
 93             const end = try readU16(data, record + 2);
 94             if (glyph < start) {
 95                 right = mid;
 96             } else if (glyph > end) {
 97                 left = mid + 1;
 98             } else {
 99                 return try readU16(data, record + 4);
100             }
101         }
102         return 0;
103     }
104 };
105 
106 test "ClassDef format 1 maps consecutive glyph classes" {
107     const data = [_]u8{
108         0, 1,
109         0, 5,
110         0, 3,
111         0, 1,
112         0, 7,
113         0, 2,
114     };
115     const class_def = try ClassDef.init(&data, 0);
116     try std.testing.expectEqual(@as(u16, 0), try class_def.class(&data, 4));
117     try std.testing.expectEqual(@as(u16, 1), try class_def.class(&data, 5));
118     try std.testing.expectEqual(@as(u16, 7), try class_def.class(&data, 6));
119     try std.testing.expectEqual(@as(u16, 2), try class_def.class(&data, 7));
120     try std.testing.expectEqual(@as(u16, 0), try class_def.class(&data, 8));
121 }
122 
123 test "ClassDef format 2 maps class ranges" {
124     const data = [_]u8{
125         0, 2,
126         0, 2,
127         0, 10,
128         0, 12,
129         0, 4,
130         0, 30,
131         0, 31,
132         0, 8,
133     };
134     const class_def = try ClassDef.init(&data, 0);
135     try std.testing.expectEqual(@as(u16, 4), try class_def.class(&data, 10));
136     try std.testing.expectEqual(@as(u16, 4), try class_def.class(&data, 12));
137     try std.testing.expectEqual(@as(u16, 8), try class_def.class(&data, 31));
138     try std.testing.expectEqual(@as(u16, 0), try class_def.class(&data, 13));
139     try std.testing.expectEqual(@as(u16, 0), try class_def.class(&data, 0x1_0000));
140 }
141 
142 test "ClassDef honors nonzero table offsets" {
143     const data = [_]u8{
144         0xaa, 0xbb,
145         0,    1,
146         0,    7,
147         0,    1,
148         0,    5,
149     };
150     const class_def = try ClassDef.init(&data, 2);
151     try std.testing.expectEqual(@as(u16, 5), try class_def.class(&data, 7));
152 }
153 
154 test "ClassDef rejects unsupported format" {
155     const data = [_]u8{ 0, 3 };
156     try std.testing.expectError(error.UnsupportedClassDef, ClassDef.init(&data, 0));
157 }