mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-21 03:06:15 +03:00
font: start SharedGrid
This commit is contained in:
120
src/font/SharedGrid.zig
Normal file
120
src/font/SharedGrid.zig
Normal file
@ -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,
|
||||||
|
};
|
@ -16,6 +16,7 @@ pub const GroupCacheSet = @import("GroupCacheSet.zig");
|
|||||||
pub const Glyph = @import("Glyph.zig");
|
pub const Glyph = @import("Glyph.zig");
|
||||||
pub const shape = @import("shape.zig");
|
pub const shape = @import("shape.zig");
|
||||||
pub const Shaper = shape.Shaper;
|
pub const Shaper = shape.Shaper;
|
||||||
|
pub const SharedGrid = @import("SharedGrid.zig");
|
||||||
pub const sprite = @import("sprite.zig");
|
pub const sprite = @import("sprite.zig");
|
||||||
pub const Sprite = sprite.Sprite;
|
pub const Sprite = sprite.Sprite;
|
||||||
pub const SpriteFace = sprite.Face;
|
pub const SpriteFace = sprite.Face;
|
||||||
|
Reference in New Issue
Block a user