diff --git a/src/font/SharedGrid.zig b/src/font/SharedGrid.zig new file mode 100644 index 000000000..bfb8f411f --- /dev/null +++ b/src/font/SharedGrid.zig @@ -0,0 +1,120 @@ +//! This structure represents the state required to render a terminal +//! grid using the font subsystem. It is "shared" because it is able to +//! be shared across multiple surfaces. +//! +//! It is desirable for the grid state to be shared because the font +//! configuration for a set of surfaces is almost always the same and +//! font data is relatively memory intensive. Further, the font subsystem +//! should be read-heavy compared to write-heavy, so it handles concurrent +//! reads well. Going even further, the font subsystem should be very rarely +//! read at all since it should only be necessary when the grid actively +//! changes. +const SharedGrid = @This(); + +// TODO(fontmem): +// - consider config changes and how they affect the shared grid. + +const std = @import("std"); +const assert = std.debug.assert; +const Allocator = std.mem.Allocator; +const font = @import("main.zig"); +const Atlas = font.Atlas; +const CodepointResolver = font.CodepointResolver; +const Collection = font.Collection; +const Glyph = font.Glyph; +const Metrics = font.face.Metrics; +const Presentation = font.Presentation; +const Style = font.Style; +const RenderOptions = font.face.RenderOptions; + +const log = std.log.scoped(.font_shared_grid); + +/// Cache for codepoints to font indexes in a group. +codepoints: std.AutoHashMapUnmanaged(CodepointKey, ?Collection.Index) = .{}, + +/// Cache for glyph renders into the atlas. +glyphs: std.AutoHashMapUnmanaged(GlyphKey, Glyph) = .{}, + +/// The texture atlas to store renders in. The Glyph data in the glyphs +/// cache is dependent on the atlas matching. +atlas_greyscale: Atlas, +atlas_color: Atlas, + +/// The underlying resolver for font data, fallbacks, etc. The shared +/// grid takes ownership of the resolver and will free it. +resolver: CodepointResolver, + +/// The currently active grid metrics dictating the layout of the grid. +/// This is calculated based on the resolver and current fonts. +metrics: Metrics, + +pub fn init( + alloc: Allocator, + resolver: CodepointResolver, + thicken: bool, +) !SharedGrid { + // We need to support loading options since we use the size data + assert(resolver.collection.load_options != null); + + var atlas_greyscale = try Atlas.init(alloc, 512, .greyscale); + errdefer atlas_greyscale.deinit(alloc); + var atlas_color = try Atlas.init(alloc, 512, .rgba); + errdefer atlas_color.deinit(alloc); + + var result: SharedGrid = .{ + .resolver = resolver, + .atlas_greyscale = atlas_greyscale, + .atlas_color = atlas_color, + }; + + // We set an initial capacity that can fit a good number of characters. + // This number was picked empirically based on my own terminal usage. + try result.codepoints.ensureTotalCapacity(alloc, 128); + try result.glyphs.ensureTotalCapacity(alloc, 128); + + // Initialize our metrics. + try result.reloadMetrics(thicken); + + return result; +} + +pub fn deinit(self: *SharedGrid, alloc: Allocator) void { + self.codepoints.deinit(alloc); + self.glyphs.deinit(alloc); + self.atlas_greyscale.deinit(alloc); + self.atlas_color.deinit(alloc); + self.resolver.deinit(alloc); +} + +fn reloadMetrics(self: *SharedGrid, thicken: bool) !void { + // Get our cell metrics based on a regular font ascii 'M'. Why 'M'? + // Doesn't matter, any normal ASCII will do we're just trying to make + // sure we use the regular font. + // We don't go through our caching layer because we want to minimize + // possible failures. + const collection = &self.resolver.collection; + const index = collection.getIndex('M', .regular, .{ .any = {} }).?; + const face = try collection.getFace(index); + self.metrics = face.metrics; + + // Setup our sprite font. + self.resolver.sprite = .{ + .width = self.metrics.cell_width, + .height = self.metrics.cell_height, + .thickness = self.metrics.underline_thickness * + @as(u32, if (thicken) 2 else 1), + .underline_position = self.metrics.underline_position, + }; +} + +const CodepointKey = struct { + style: Style, + codepoint: u32, + presentation: ?Presentation, +}; + +const GlyphKey = struct { + index: Collection.Index, + glyph: u32, + opts: RenderOptions, +}; diff --git a/src/font/main.zig b/src/font/main.zig index 0798bae05..0932ad4e3 100644 --- a/src/font/main.zig +++ b/src/font/main.zig @@ -16,6 +16,7 @@ pub const GroupCacheSet = @import("GroupCacheSet.zig"); pub const Glyph = @import("Glyph.zig"); pub const shape = @import("shape.zig"); pub const Shaper = shape.Shaper; +pub const SharedGrid = @import("SharedGrid.zig"); pub const sprite = @import("sprite.zig"); pub const Sprite = sprite.Sprite; pub const SpriteFace = sprite.Face;