lib/filigree/src/font/names.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 readU16 = binary.readU16;
8
9 pub const NameRecordBytes = struct {
10 platform_id: u16,
11 encoding_id: u16,
12 language_id: u16,
13 name_id: u16,
14 bytes: []const u8,
15 };
16
17 pub fn bestNameRecord(data: []const u8, name_table: Table, name_id: u16) FontError!?NameRecordBytes {
18 if (name_table.len < 6) return error.InvalidFont;
19 const version = try readU16(data, name_table.offset);
20 if (version != 0 and version != 1) return error.InvalidFont;
21 const count = try readU16(data, name_table.offset + 2);
22 const storage_offset = try readU16(data, name_table.offset + 4);
23 const records_offset = name_table.offset + 6;
24 if (@as(u64, count) * 12 > @as(u64, name_table.len - 6)) return error.InvalidFont;
25 if (storage_offset > name_table.len) return error.InvalidFont;
26
27 var best_score: u16 = 0;
28 var best: ?NameRecordBytes = null;
29 for (0..count) |record_index| {
30 const record_offset = records_offset + record_index * 12;
31 const platform_id = try readU16(data, record_offset);
32 const encoding_id = try readU16(data, record_offset + 2);
33 const language_id = try readU16(data, record_offset + 4);
34 const current_name_id = try readU16(data, record_offset + 6);
35 if (current_name_id != name_id) continue;
36 const len = try readU16(data, record_offset + 8);
37 const relative = try readU16(data, record_offset + 10);
38 if (!nameRecordIsUtf16(platform_id, encoding_id)) continue;
39 if (relative > name_table.len - storage_offset) return error.InvalidFont;
40 const storage_start = name_table.offset + storage_offset;
41 const start = storage_start + @as(usize, relative);
42 const storage_remaining = name_table.len - storage_offset - @as(usize, relative);
43 if (@as(usize, len) > storage_remaining) return error.InvalidFont;
44 if (len % 2 != 0) return error.InvalidFont;
45 const score = nameRecordScore(platform_id, encoding_id, language_id);
46 if (score <= best_score) continue;
47 best_score = score;
48 best = .{
49 .platform_id = platform_id,
50 .encoding_id = encoding_id,
51 .language_id = language_id,
52 .name_id = current_name_id,
53 .bytes = data[start..][0..len],
54 };
55 }
56 return best;
57 }
58
59 pub fn utf16BeToUtf8Alloc(allocator: std.mem.Allocator, bytes: []const u8) ![]u8 {
60 var utf8_len: usize = 0;
61 var read_index: usize = 0;
62 while (read_index < bytes.len) {
63 const codepoint = try readUtf16BeScalar(bytes, &read_index);
64 utf8_len += try std.unicode.utf8CodepointSequenceLength(codepoint);
65 }
66
67 const result = try allocator.alloc(u8, utf8_len);
68 errdefer allocator.free(result);
69 var write_index: usize = 0;
70 read_index = 0;
71 while (read_index < bytes.len) {
72 const codepoint = try readUtf16BeScalar(bytes, &read_index);
73 write_index += try std.unicode.utf8Encode(codepoint, result[write_index..]);
74 }
75 return result;
76 }
77
78 fn nameRecordIsUtf16(platform_id: u16, encoding_id: u16) bool {
79 return platform_id == 0 or (platform_id == 3 and (encoding_id == 1 or encoding_id == 10));
80 }
81
82 fn nameRecordScore(platform_id: u16, encoding_id: u16, language_id: u16) u16 {
83 if (platform_id == 3 and encoding_id == 10 and language_id == 0x0409) return 500;
84 if (platform_id == 3 and encoding_id == 1 and language_id == 0x0409) return 450;
85 if (platform_id == 3 and encoding_id == 10) return 400;
86 if (platform_id == 3 and encoding_id == 1) return 350;
87 if (platform_id == 0) return 300;
88 return 0;
89 }
90
91 fn readUtf16BeScalar(bytes: []const u8, index: *usize) FontError!u21 {
92 const unit = try readUtf16BeUnit(bytes, index.*);
93 index.* += 2;
94 if (unit >= 0xd800 and unit <= 0xdbff) {
95 if (index.* >= bytes.len) return error.InvalidFont;
96 const low = try readUtf16BeUnit(bytes, index.*);
97 if (low < 0xdc00 or low > 0xdfff) return error.InvalidFont;
98 index.* += 2;
99 return @intCast(0x10000 + (((@as(u32, unit) - 0xd800) << 10) | (@as(u32, low) - 0xdc00)));
100 }
101 if (unit >= 0xdc00 and unit <= 0xdfff) return error.InvalidFont;
102 return @intCast(unit);
103 }
104
105 fn readUtf16BeUnit(bytes: []const u8, offset: usize) FontError!u16 {
106 if (offset > bytes.len or bytes.len - offset < 2) return error.InvalidFont;
107 return std.mem.readInt(u16, bytes[offset..][0..2], .big);
108 }