lib/ui/src/asset/embedded.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const alloc_phase = @import("alloc_phase");
3 const font_bytes = @import("ui_fonts");
4
5 const model = @import("model.zig");
6 const registry = @import("registry.zig");
7
8 const noto_bytes = font_bytes.noto;
9 const iosevka_bytes = font_bytes.iosevka;
10
11 pub const Face = struct {
12 family: []const u8,
13 descriptor: model.FontDescriptor,
14 bytes: []const u8,
15 };
16
17 pub const faces = [_]Face{
18 .{
19 .family = "Noto Sans",
20 .descriptor = .{
21 .family_len = "Noto Sans".len,
22 .units_per_em = 1000,
23 .weight = 400,
24 .width = 5,
25 .ascent = 1.069,
26 .descent = -0.293,
27 .coverage = .{ 0xa000020f, 0x00000002, 0, 0, 0, 0, 0, 0 },
28 },
29 .bytes = noto_bytes,
30 },
31 .{
32 .family = "Iosevka",
33 .descriptor = .{
34 .family_len = "Iosevka".len,
35 .units_per_em = 1000,
36 .weight = 400,
37 .width = 5,
38 .monospace = 1,
39 .ascent = 0.965,
40 .descent = -0.285,
41 .coverage = .{ 0xa000020f, 0x00000002, 0, 0, 0, 0, 0, 0 },
42 },
43 .bytes = iosevka_bytes,
44 },
45 };
46
47 pub const Embedded = struct {
48 pub const Limits = struct {
49 font_bytes: usize,
50 descriptor_bytes: usize,
51 family_bytes: usize,
52 };
53
54 pub const Capacity = struct {
55 retained_bytes: usize,
56
57 pub fn derive(limits: Limits) error{CapacityOverflow}!Capacity {
58 const fonts = std.math.add(usize, limits.font_bytes, limits.descriptor_bytes) catch return error.CapacityOverflow;
59 return .{ .retained_bytes = std.math.add(usize, fonts, limits.family_bytes) catch return error.CapacityOverflow };
60 }
61 };
62
63 pub fn defaultLimits() Limits {
64 return .{
65 .font_bytes = noto_bytes.len + iosevka_bytes.len + faces.len,
66 .descriptor_bytes = @sizeOf(@TypeOf(faces)),
67 .family_bytes = faces[0].family.len + faces[1].family.len + faces.len,
68 };
69 }
70
71 pub const claim: alloc_phase.capacity.Declaration = .{
72 .source = .{
73 .id = "ui.asset_registry",
74 .kind = .startup_static,
75 .limit_source = .application_default,
76 .storage = .{
77 .covered = &.{
78 .{ .id = "embedded_font_bytes", .lifetime = .steady, .detail = "the two pinned TrueType subsets in executable read-only storage" },
79 .{ .id = "embedded_descriptors", .lifetime = .steady, .detail = "the constant font traits and family names registered before UI readiness" },
80 },
81 .excluded = &.{ "caller-owned registry table slots and owned payload pool", "registered system faces" },
82 },
83 .capacity = .{
84 .inputs = &.{
85 alloc_phase.capacity.bindInput(Limits, "font_bytes", "font_bytes"),
86 alloc_phase.capacity.bindInput(Limits, "descriptor_bytes", "descriptor_bytes"),
87 alloc_phase.capacity.bindInput(Limits, "family_bytes", "family_bytes"),
88 },
89 .type_selectors = &.{},
90 .nodes = &.{
91 .{ .input = 0 },
92 .{ .input = 1 },
93 .{ .input = 2 },
94 .{ .add = .{ .left = 0, .right = 1 } },
95 .{ .add = .{ .left = 3, .right = 2 } },
96 },
97 .assertions = &.{.{
98 .scope = .closure_total,
99 .measure = .retained,
100 .relation = .upper_bound,
101 .expression = 4,
102 }},
103 },
104 .overload = .{ .kind = .not_applicable, .detail = "the two read-only faces have a fixed executable image size" },
105 .risks = .{
106 .transitive = .{ .status = .excluded, .detail = "the embedded font bytes and descriptors require no allocator" },
107 .foreign = .{ .status = .excluded, .detail = "the startup corpus reads no host font service" },
108 },
109 .obligations = &.{
110 .{ .key = "ui_registry_startup", .role = .acquisition },
111 .{ .key = "ui_registry_descriptors", .role = .integration },
112 .{ .key = "ui_registry_teardown", .role = .teardown },
113 },
114 },
115 .bindings = .{
116 .owner = @This(),
117 .default_limits = alloc_phase.capacity.selector(@This().defaultLimits),
118 },
119 };
120
121 /// Registers the two regular faces before a UI session begins publishing.
122 /// A bold request matches the regular face until a bold face is registered.
123 pub fn register(table: *registry.Registry) model.Error![faces.len]model.AssetHandle {
124 std.debug.assert(table.phase == .steady);
125 if (table.limits.fonts - table.font_used < faces.len) return error.FontCapacityExceeded;
126 var handles: [faces.len]model.AssetHandle = undefined;
127 for (faces, 0..) |face, index| {
128 handles[index] = try table.registerFont(face.family, face.descriptor, .{ .borrowed = face.bytes });
129 }
130 return handles;
131 }
132
133 /// Returns a ready registry with both embedded faces already registered.
134 pub fn open(allocator: std.mem.Allocator, limits: registry.Registry.Limits) (registry.Registry.InitError || model.Error)!registry.Registry {
135 if (limits.fonts < faces.len) return error.FontCapacityExceeded;
136 var table = try registry.Registry.init(allocator, limits);
137 errdefer table.deinit(allocator);
138 table.activate();
139 _ = try register(&table);
140 return table;
141 }
142 };