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 }