diff --git a/src/font/Collection.zig b/src/font/Collection.zig index 09e6c6c09..162ec849a 100644 --- a/src/font/Collection.zig +++ b/src/font/Collection.zig @@ -8,8 +8,10 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const font = @import("main.zig"); const DeferredFace = font.DeferredFace; +const DesiredSize = font.face.DesiredSize; const Face = font.Face; const Library = font.Library; +const Metrics = font.face.Metrics; const Presentation = font.Presentation; const Style = font.Style; @@ -17,12 +19,19 @@ const Style = font.Style; /// Instead, use the functions available on Collection. faces: StyleArray, +/// The load options for deferred faces in the face list. If this +/// is not set, then deferred faces will not be loaded. Attempting to +/// add a deferred face will result in an error. +load_options: ?LoadOptions = null, + /// Initialize an empty collection. -pub fn init(alloc: Allocator) !Collection { +pub fn init( + alloc: Allocator, +) !Collection { // Initialize our styles array, preallocating some space that is // likely to be used. var faces = StyleArray.initFill(.{}); - for (&faces.values) |*list| try list.ensureTotalCapacityPrecise(alloc, 2); + for (&faces.values) |*v| try v.ensureTotalCapacityPrecise(alloc, 2); return .{ .faces = faces }; } @@ -32,10 +41,13 @@ pub fn deinit(self: *Collection, alloc: Allocator) void { for (entry.value.items) |*item| item.deinit(); entry.value.deinit(alloc); } + + //self.load_options.deinit(alloc); } pub const AddError = Allocator.Error || error{ CollectionFull, + DeferredLoadingUnavailable, }; /// Add a face to the collection for the given style. This face will be added @@ -61,11 +73,47 @@ pub fn add( if (list.items.len >= Index.Special.start - 1) return error.CollectionFull; + // If this is deferred and we don't have load options, we can't. + if (face.isDeferred() and self.load_options == null) + return error.DeferredLoadingUnavailable; + const idx = list.items.len; try list.append(alloc, face); return .{ .style = style, .idx = @intCast(idx) }; } +/// Return the Face represented by a given Index. The returned pointer +/// is only valid as long as this collection is not modified. +/// +/// This will initialize the face if it is deferred and not yet loaded, +/// which can fail. +pub fn getFace(self: *Collection, index: Index) !*Face { + if (index.special() != null) return error.SpecialHasNoFace; + const list = self.faces.getPtr(index.style); + const item = &list.items[index.idx]; + return switch (item.*) { + inline .deferred, .fallback_deferred => |*d, tag| deferred: { + const opts = self.load_options orelse + return error.DeferredLoadingUnavailable; + const face = try d.load(opts.library, opts.faceOptions()); + d.deinit(); + item.* = switch (tag) { + .deferred => .{ .loaded = face }, + .fallback_deferred => .{ .fallback_loaded = face }, + else => unreachable, + }; + + break :deferred switch (tag) { + .deferred => &item.loaded, + .fallback_deferred => &item.fallback_loaded, + else => unreachable, + }; + }, + + .loaded, .fallback_loaded => |*f| f, + }; +} + /// Packed array of all Style enum cases mapped to a growable list of faces. /// /// We use this data structure because there aren't many styles and all @@ -74,6 +122,32 @@ pub fn add( /// small style count. const StyleArray = std.EnumArray(Style, std.ArrayListUnmanaged(Entry)); +/// Load options are used to configure all the details a Collection +/// needs to load deferred faces. +pub const LoadOptions = struct { + /// The library to use for loading faces. This is not owned by + /// the collection and can be used by multiple collections. When + /// deinitializing the collection, the library is not deinitialized. + library: Library, + + /// The desired font size for all loaded faces. + size: DesiredSize = .{ .points = 12 }, + + /// The metric modifiers to use for all loaded faces. If this is + /// set then the memory is owned by the collection and will be + /// freed when the collection is deinitialized. The modifier set + /// must use the same allocator as the collection. + metric_modifiers: Metrics.ModifierSet = .{}, + + /// The options to use for loading faces. + fn faceOptions(self: *const LoadOptions) font.face.Options { + return .{ + .size = self.size, + .metric_modifiers = &self.metric_modifiers, + }; + } +}; + /// A entry in a collection can be deferred or loaded. A deferred face /// is not yet fully loaded and only represents the font descriptor /// and usually uses less resources. A loaded face is fully parsed, @@ -114,6 +188,14 @@ pub const Entry = union(enum) { } } + /// True if the entry is deferred. + fn isDeferred(self: Entry) bool { + return switch (self) { + .deferred, .fallback_deferred => true, + .loaded, .fallback_loaded => false, + }; + } + /// True if this face satisfies the given codepoint and presentation. fn hasCodepoint(self: Entry, cp: u32, p_mode: PresentationMode) bool { return switch (self) { @@ -262,3 +344,43 @@ test "add full" { ) }, )); } + +test "add deferred without loading options" { + const testing = std.testing; + const alloc = testing.allocator; + + var c = try init(alloc); + defer c.deinit(alloc); + + try testing.expectError(error.DeferredLoadingUnavailable, c.add( + alloc, + .regular, + + // This can be undefined because it should never be accessed. + .{ .deferred = undefined }, + )); +} + +test getFace { + const testing = std.testing; + const alloc = testing.allocator; + const testFont = @import("test.zig").fontRegular; + + var lib = try Library.init(); + defer lib.deinit(); + + var c = try init(alloc); + defer c.deinit(alloc); + + const idx = try c.add(alloc, .regular, .{ .loaded = try Face.init( + lib, + testFont, + .{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } }, + ) }); + + { + const face1 = try c.getFace(idx); + const face2 = try c.getFace(idx); + try testing.expectEqual(@intFromPtr(face1), @intFromPtr(face2)); + } +}