lib/filigree/src/font/kern.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

 1 const std = @import("std");
 2 const binary = @import("binary.zig");
 3 const model = @import("model.zig");
 4 
 5 const FontError = model.FontError;
 6 const Table = binary.Table;
 7 const readI16 = binary.readI16;
 8 const readU16 = binary.readU16;
 9 
10 pub const Kern = struct {
11     subtable: ?KernSubtable,
12 
13     pub fn init(data: []const u8, kern_table: Table) FontError!Kern {
14         if (kern_table.len < 4) return error.InvalidKern;
15         const version = try readU16(data, kern_table.offset);
16         if (version != 0) return error.InvalidKern;
17         const count = try readU16(data, kern_table.offset + 2);
18         var offset = kern_table.offset + 4;
19         const end = kern_table.offset + kern_table.len;
20         var first: ?KernSubtable = null;
21 
22         for (0..count) |_| {
23             if (offset > end or end - offset < 6) return error.InvalidKern;
24             const subtable_start = offset;
25             const subtable_version = try readU16(data, offset);
26             const len: usize = try readU16(data, offset + 2);
27             const coverage = try readU16(data, offset + 4);
28             if (len < 6 or len > end - offset) return error.InvalidKern;
29             offset += len;
30 
31             if (subtable_version != 0) continue;
32             const format: u8 = @intCast(coverage >> 8);
33             const horizontal = (coverage & 0x0001) != 0;
34             const minimum = (coverage & 0x0002) != 0;
35             const cross_stream = (coverage & 0x0004) != 0;
36             if (format != 0 or !horizontal or minimum or cross_stream) continue;
37             const subtable = try validateKernFormat0(data, .{ .offset = subtable_start, .len = len });
38             if (first == null) first = subtable;
39         }
40 
41         return .{ .subtable = first };
42     }
43 
44     pub fn value(self: Kern, data: []const u8, left_glyph: u32, right_glyph: u32) FontError!i16 {
45         if (self.subtable) |subtable| {
46             if (try subtable.value(data, left_glyph, right_glyph)) |pair_value| {
47                 return pair_value;
48             }
49         }
50         return 0;
51     }
52 };
53 
54 const KernSubtable = struct {
55     pairs_offset: usize,
56     pair_count: u16,
57 
58     fn value(self: KernSubtable, data: []const u8, left_glyph: u32, right_glyph: u32) FontError!?i16 {
59         if (left_glyph > std.math.maxInt(u16) or right_glyph > std.math.maxInt(u16)) return null;
60         const key = (@as(u32, @intCast(left_glyph)) << 16) | @as(u32, @intCast(right_glyph));
61 
62         var left: usize = 0;
63         var right: usize = self.pair_count;
64         while (left < right) {
65             const mid = left + (right - left) / 2;
66             const record = self.pairs_offset + mid * 6;
67             const candidate = (@as(u32, try readU16(data, record)) << 16) | @as(u32, try readU16(data, record + 2));
68             if (key < candidate) {
69                 right = mid;
70             } else if (key > candidate) {
71                 left = mid + 1;
72             } else {
73                 return try readI16(data, record + 4);
74             }
75         }
76         return null;
77     }
78 };
79 
80 fn validateKernFormat0(data: []const u8, subtable: Table) FontError!KernSubtable {
81     if (subtable.len < 14) return error.InvalidKern;
82     const pairs_offset = subtable.offset + 14;
83     const pair_count = try readU16(data, subtable.offset + 6);
84     const pairs_len = @as(usize, pair_count) * 6;
85     if (pairs_len > subtable.len - 14) return error.InvalidKern;
86     return .{
87         .pairs_offset = pairs_offset,
88         .pair_count = pair_count,
89     };
90 }