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 }