// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6cmap.html const std = @import("std"); const readInt = std.mem.readInt; const assert = std.debug.assert; // this expects a TrueType font with an interface like https://codeberg.org/andrewrk/TrueType/ const font = @import("main.zig").defaultFont; fn sort(array: []const u16) []const u16 { var tempArray: [1]u16 = undefined; @setEvalBranchQuota(10000); if(array.len == 1) { tempArray[0] = array[0]; return &tempArray; } else { const indexofMin = std.mem.indexOfMin(u16, array); tempArray[0] = array[indexofMin]; return &tempArray ++ sort(array[0..indexofMin] ++ array[indexofMin + 1..]); } } const bytes = font.ttf_bytes; const tableStart = font.index_map; const format = std.mem.readInt(u16, bytes[tableStart..][0..2], .big); // Apple: "The segCount is the number of contiguous code ranges in the font" const seg_count = readInt(u16, bytes[tableStart + 6 ..][0..2], .big) >> 1; const firstEndCode = tableStart + 14; const lastEndCode = firstEndCode + (2 * seg_count); const endCodes = eds: { var codes: [seg_count]u16 = undefined; // apple: "The segments are sorted in order of increasing endCode values." for(0..seg_count) |i| { codes[i] = readInt(u16, bytes[firstEndCode .. lastEndCode][(2 * i)..(2 * i) + 2], .big); } assert(codes[seg_count - 1] == 0xFFFF); break :eds codes; }; test "endCodes are sorted" { var last = endCodes[0]; for(endCodes[1..]) |code| { std.debug.assert(code > last); last = code; } } // 2 bytes reserved, then start codes const reservedPad = readInt(u16, bytes[lastEndCode..lastEndCode + 2], .big); const _ = blk: { assert(reservedPad == 0); break :blk; }; const firstStartCode = lastEndCode + 4; const startCodes = sts: { var codes: [seg_count]u16 = undefined; codes[0] = 0; for(1..seg_count) |i| { codes[i] = readInt(u16, bytes[firstStartCode .. firstStartCode + (2 * seg_count)][(2 * i)..(2 * i) + 2], .big); } const codesSorted = sort(&codes); break :sts codesSorted; }; test "startCodes are sorted" { var last = startCodes[0]; for(startCodes[1..]) |code| { std.debug.assert(code >= last); last = code; } } test "codepoint array lengths match" { std.debug.assert(format == 4); std.debug.assert(endCodes.len == startCodes.len); @compileLog("endCodes len", endCodes.len); @compileLog("startCodes len", startCodes.len); const altEndCodes = std.mem.bytesAsSlice(u16, bytes[firstEndCode .. lastEndCode]); std.debug.assert(!std.mem.eql(u16, &endCodes, @alignCast(@constCast(altEndCodes)))); @compileLog("altEndCodes (bytesAsSlice method) len", altEndCodes.len); } pub const codePoints = cps: { const totalCodeCount = cnt: { var count = 0; for(startCodes, endCodes) |start, end| { count += (end - start); } break :cnt count; }; const allCodes = cds: { var codes: [totalCodeCount]i32 = undefined; var codesIdx = 0; @setEvalBranchQuota(10000); for(startCodes,endCodes) |start, end| { for(start..end) |n| { codes[codesIdx] = n; codesIdx += 1; } } break :cds codes; }; break :cps allCodes; };