mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-10-05 01:16:33 +00:00
use new font.GroupCache, remove unused font structs
This commit is contained in:
131
src/Grid.zig
131
src/Grid.zig
@@ -45,7 +45,7 @@ texture_color: gl.Texture,
|
|||||||
|
|
||||||
/// The font atlas.
|
/// The font atlas.
|
||||||
font_lib: font.Library,
|
font_lib: font.Library,
|
||||||
font_set: font.FallbackSet,
|
font_group: font.GroupCache,
|
||||||
|
|
||||||
/// Whether the cursor is visible or not. This is used to control cursor
|
/// Whether the cursor is visible or not. This is used to control cursor
|
||||||
/// blinking.
|
/// blinking.
|
||||||
@@ -138,49 +138,47 @@ pub fn init(
|
|||||||
alloc: Allocator,
|
alloc: Allocator,
|
||||||
font_size: font.Face.DesiredSize,
|
font_size: font.Face.DesiredSize,
|
||||||
) !Grid {
|
) !Grid {
|
||||||
// Initialize our font atlas. We will initially populate the
|
// Build our font group
|
||||||
// font atlas with all the visible ASCII characters since they are common.
|
|
||||||
var atlas = try Atlas.init(alloc, 512, .greyscale);
|
|
||||||
errdefer atlas.deinit(alloc);
|
|
||||||
|
|
||||||
// Load our emoji font
|
|
||||||
var atlas_color = try Atlas.init(alloc, 512, .rgba);
|
|
||||||
errdefer atlas_color.deinit(alloc);
|
|
||||||
|
|
||||||
// Build our fallback set so we can look up all codepoints
|
|
||||||
var font_set: font.FallbackSet = .{};
|
|
||||||
try font_set.families.ensureTotalCapacity(alloc, 2);
|
|
||||||
errdefer font_set.deinit(alloc);
|
|
||||||
|
|
||||||
var font_lib = try font.Library.init();
|
var font_lib = try font.Library.init();
|
||||||
errdefer font_lib.deinit();
|
errdefer font_lib.deinit();
|
||||||
|
var font_group = try font.GroupCache.init(alloc, group: {
|
||||||
|
var group = try font.Group.init(alloc);
|
||||||
|
errdefer group.deinit(alloc);
|
||||||
|
|
||||||
// Regular text
|
// Our regular font
|
||||||
font_set.families.appendAssumeCapacity(fam: {
|
try group.addFace(
|
||||||
var fam = font.Family.init(font_lib, atlas);
|
alloc,
|
||||||
errdefer fam.deinit(alloc);
|
.regular,
|
||||||
try fam.loadFaceFromMemory(.regular, face_ttf, font_size);
|
try font.Face.init(font_lib, face_ttf, font_size),
|
||||||
try fam.loadFaceFromMemory(.bold, face_bold_ttf, font_size);
|
);
|
||||||
break :fam fam;
|
try group.addFace(
|
||||||
});
|
alloc,
|
||||||
|
.bold,
|
||||||
|
try font.Face.init(font_lib, face_bold_ttf, font_size),
|
||||||
|
);
|
||||||
|
|
||||||
// Emoji
|
// Emoji
|
||||||
font_set.families.appendAssumeCapacity(fam: {
|
try group.addFace(
|
||||||
var fam_emoji = font.Family.init(font_lib, atlas_color);
|
alloc,
|
||||||
errdefer fam_emoji.deinit(alloc);
|
.regular,
|
||||||
try fam_emoji.loadFaceFromMemory(.regular, face_emoji_ttf, font_size);
|
try font.Face.init(font_lib, face_emoji_ttf, font_size),
|
||||||
break :fam fam_emoji;
|
);
|
||||||
|
|
||||||
|
break :group group;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Load all visible ASCII characters and build our cell width based on
|
// Load all visible ASCII characters and build our cell width based on
|
||||||
// the widest character that we see.
|
// the widest character that we see.
|
||||||
const cell_width: f32 = cell_width: {
|
const cell_width: f32 = cell_width: {
|
||||||
var cell_width: f32 = 0;
|
var cell_width: f32 = 0;
|
||||||
var i: u8 = 32;
|
var i: u32 = 32;
|
||||||
while (i <= 126) : (i += 1) {
|
while (i <= 126) : (i += 1) {
|
||||||
const goa = try font_set.getOrAddGlyph(alloc, i, .regular);
|
const index = (try font_group.indexForCodepoint(alloc, .regular, i)).?;
|
||||||
if (goa.glyph.advance_x > cell_width) {
|
const face = font_group.group.faceFromIndex(index);
|
||||||
cell_width = @ceil(goa.glyph.advance_x);
|
const glyph_index = face.glyphIndex(i).?;
|
||||||
|
const glyph = try font_group.renderGlyph(alloc, index, glyph_index);
|
||||||
|
if (glyph.advance_x > cell_width) {
|
||||||
|
cell_width = @ceil(glyph.advance_x);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,14 +188,17 @@ pub fn init(
|
|||||||
// The cell height is the vertical height required to render underscore
|
// The cell height is the vertical height required to render underscore
|
||||||
// '_' which should live at the bottom of a cell.
|
// '_' which should live at the bottom of a cell.
|
||||||
const cell_height: f32 = cell_height: {
|
const cell_height: f32 = cell_height: {
|
||||||
const fam = &font_set.families.items[0];
|
// Get the '_' char for height
|
||||||
|
const index = (try font_group.indexForCodepoint(alloc, .regular, '_')).?;
|
||||||
|
const face = font_group.group.faceFromIndex(index);
|
||||||
|
const glyph_index = face.glyphIndex('_').?;
|
||||||
|
const glyph = try font_group.renderGlyph(alloc, index, glyph_index);
|
||||||
|
|
||||||
// This is the height reported by the font face
|
// This is the height reported by the font face
|
||||||
const face_height: i32 = fam.regular.?.unitsToPxY(fam.regular.?.face.handle.*.height);
|
const face_height: i32 = face.unitsToPxY(face.face.handle.*.height);
|
||||||
|
|
||||||
// Determine the height of the underscore char
|
// Determine the height of the underscore char
|
||||||
const glyph = font_set.families.items[0].getGlyph('_', .regular).?;
|
var res: i32 = face.unitsToPxY(face.face.handle.*.ascender);
|
||||||
var res: i32 = fam.regular.?.unitsToPxY(fam.regular.?.face.handle.*.ascender);
|
|
||||||
res -= glyph.offset_y;
|
res -= glyph.offset_y;
|
||||||
res += @intCast(i32, glyph.height);
|
res += @intCast(i32, glyph.height);
|
||||||
|
|
||||||
@@ -208,10 +209,10 @@ pub fn init(
|
|||||||
break :cell_height @intToFloat(f32, res);
|
break :cell_height @intToFloat(f32, res);
|
||||||
};
|
};
|
||||||
const cell_baseline = cell_baseline: {
|
const cell_baseline = cell_baseline: {
|
||||||
const fam = &font_set.families.items[0];
|
const face = font_group.group.faces.get(.regular).items[0];
|
||||||
break :cell_baseline cell_height - @intToFloat(
|
break :cell_baseline cell_height - @intToFloat(
|
||||||
f32,
|
f32,
|
||||||
fam.regular.?.unitsToPxY(fam.regular.?.face.handle.*.ascender),
|
face.unitsToPxY(face.face.handle.*.ascender),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
log.debug("cell dimensions w={d} h={d} baseline={d}", .{ cell_width, cell_height, cell_baseline });
|
log.debug("cell dimensions w={d} h={d} baseline={d}", .{ cell_width, cell_height, cell_baseline });
|
||||||
@@ -294,12 +295,12 @@ pub fn init(
|
|||||||
try texbind.image2D(
|
try texbind.image2D(
|
||||||
0,
|
0,
|
||||||
.Red,
|
.Red,
|
||||||
@intCast(c_int, atlas.size),
|
@intCast(c_int, font_group.atlas_greyscale.size),
|
||||||
@intCast(c_int, atlas.size),
|
@intCast(c_int, font_group.atlas_greyscale.size),
|
||||||
0,
|
0,
|
||||||
.Red,
|
.Red,
|
||||||
.UnsignedByte,
|
.UnsignedByte,
|
||||||
atlas.data.ptr,
|
font_group.atlas_greyscale.data.ptr,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,12 +316,12 @@ pub fn init(
|
|||||||
try texbind.image2D(
|
try texbind.image2D(
|
||||||
0,
|
0,
|
||||||
.RGBA,
|
.RGBA,
|
||||||
@intCast(c_int, atlas_color.size),
|
@intCast(c_int, font_group.atlas_color.size),
|
||||||
@intCast(c_int, atlas_color.size),
|
@intCast(c_int, font_group.atlas_color.size),
|
||||||
0,
|
0,
|
||||||
.BGRA,
|
.BGRA,
|
||||||
.UnsignedByte,
|
.UnsignedByte,
|
||||||
atlas_color.data.ptr,
|
font_group.atlas_color.data.ptr,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,7 +337,7 @@ pub fn init(
|
|||||||
.texture = tex,
|
.texture = tex,
|
||||||
.texture_color = tex_color,
|
.texture_color = tex_color,
|
||||||
.font_lib = font_lib,
|
.font_lib = font_lib,
|
||||||
.font_set = font_set,
|
.font_group = font_group,
|
||||||
.cursor_visible = true,
|
.cursor_visible = true,
|
||||||
.cursor_style = .box,
|
.cursor_style = .box,
|
||||||
.background = .{ .r = 0, .g = 0, .b = 0 },
|
.background = .{ .r = 0, .g = 0, .b = 0 },
|
||||||
@@ -345,11 +346,7 @@ pub fn init(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Grid) void {
|
pub fn deinit(self: *Grid) void {
|
||||||
for (self.font_set.families.items) |*family| {
|
self.font_group.deinit(self.alloc);
|
||||||
family.atlas.deinit(self.alloc);
|
|
||||||
family.deinit(self.alloc);
|
|
||||||
}
|
|
||||||
self.font_set.deinit(self.alloc);
|
|
||||||
self.font_lib.deinit();
|
self.font_lib.deinit();
|
||||||
|
|
||||||
self.texture.destroy();
|
self.texture.destroy();
|
||||||
@@ -565,10 +562,30 @@ pub fn updateCell(
|
|||||||
|
|
||||||
var mode: GPUCellMode = .fg;
|
var mode: GPUCellMode = .fg;
|
||||||
|
|
||||||
// Get our glyph. Try our normal font atlas first.
|
// Get the glyph that we're going to use. We first try what the cell
|
||||||
const goa = try self.font_set.getOrAddGlyph(self.alloc, cell.char, style);
|
// wants, then the Unicode replacement char, then finally a space.
|
||||||
if (goa.family == 1) mode = .fg_color;
|
const FontInfo = struct { index: font.Group.FontIndex, ch: u32 };
|
||||||
const glyph = goa.glyph;
|
const font_info: FontInfo = font_info: {
|
||||||
|
var chars = [_]u32{ @intCast(u32, cell.char), 0xFFFD, ' ' };
|
||||||
|
for (chars) |char| {
|
||||||
|
if (try self.font_group.indexForCodepoint(self.alloc, style, char)) |idx| {
|
||||||
|
break :font_info FontInfo{
|
||||||
|
.index = idx,
|
||||||
|
.ch = char,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@panic("all fonts require at least space");
|
||||||
|
};
|
||||||
|
|
||||||
|
// Render
|
||||||
|
const face = self.font_group.group.faceFromIndex(font_info.index);
|
||||||
|
const glyph_index = face.glyphIndex(font_info.ch).?;
|
||||||
|
const glyph = try self.font_group.renderGlyph(self.alloc, font_info.index, glyph_index);
|
||||||
|
|
||||||
|
// If we're rendering a color font, we use the color atlas
|
||||||
|
if (face.hasColor()) mode = .fg_color;
|
||||||
|
|
||||||
// If the cell is wide, we need to note that in the mode
|
// If the cell is wide, we need to note that in the mode
|
||||||
if (cell.attrs.wide) mode = mode.mask(.wide_mask);
|
if (cell.attrs.wide) mode = mode.mask(.wide_mask);
|
||||||
@@ -649,7 +666,7 @@ pub fn setScreenSize(self: *Grid, dim: ScreenSize) !void {
|
|||||||
/// Updates the font texture atlas if it is dirty.
|
/// Updates the font texture atlas if it is dirty.
|
||||||
fn flushAtlas(self: *Grid) !void {
|
fn flushAtlas(self: *Grid) !void {
|
||||||
{
|
{
|
||||||
const atlas = &self.font_set.families.items[0].atlas;
|
const atlas = &self.font_group.atlas_greyscale;
|
||||||
if (atlas.modified) {
|
if (atlas.modified) {
|
||||||
atlas.modified = false;
|
atlas.modified = false;
|
||||||
var texbind = try self.texture.bind(.@"2D");
|
var texbind = try self.texture.bind(.@"2D");
|
||||||
@@ -683,7 +700,7 @@ fn flushAtlas(self: *Grid) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const atlas = &self.font_set.families.items[1].atlas;
|
const atlas = &self.font_group.atlas_color;
|
||||||
if (atlas.modified) {
|
if (atlas.modified) {
|
||||||
atlas.modified = false;
|
atlas.modified = false;
|
||||||
var texbind = try self.texture_color.bind(.@"2D");
|
var texbind = try self.texture_color.bind(.@"2D");
|
||||||
|
@@ -102,14 +102,8 @@ pub fn hasColor(self: Face) bool {
|
|||||||
return self.face.hasColor();
|
return self.face.hasColor();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load a glyph for this face. The codepoint can be either a u8 or
|
/// Render a glyph using the glyph index. The rendered glyph is stored in the
|
||||||
/// []const u8 depending on if you know it is ASCII or must be UTF-8 decoded.
|
/// given texture atlas.
|
||||||
pub fn loadGlyph(self: Face, alloc: Allocator, atlas: *Atlas, cp: u32) !Glyph {
|
|
||||||
// We need a UTF32 codepoint for freetype
|
|
||||||
const glyph_index = self.glyphIndex(cp) orelse return error.GlyphNotFound;
|
|
||||||
return self.renderGlyph(alloc, atlas, glyph_index);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn renderGlyph(self: Face, alloc: Allocator, atlas: *Atlas, glyph_index: u32) !Glyph {
|
pub fn renderGlyph(self: Face, alloc: Allocator, atlas: *Atlas, glyph_index: u32) !Glyph {
|
||||||
// If our glyph has color, we want to render the color
|
// If our glyph has color, we want to render the color
|
||||||
try self.face.loadGlyph(glyph_index, .{
|
try self.face.loadGlyph(glyph_index, .{
|
||||||
@@ -211,7 +205,7 @@ test {
|
|||||||
// Generate all visible ASCII
|
// Generate all visible ASCII
|
||||||
var i: u8 = 32;
|
var i: u8 = 32;
|
||||||
while (i < 127) : (i += 1) {
|
while (i < 127) : (i += 1) {
|
||||||
_ = try font.loadGlyph(alloc, &atlas, i);
|
_ = try font.renderGlyph(alloc, &atlas, font.glyphIndex(i).?);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,5 +222,5 @@ test "color emoji" {
|
|||||||
var font = try init(lib, testFont, .{ .points = 12 });
|
var font = try init(lib, testFont, .{ .points = 12 });
|
||||||
defer font.deinit();
|
defer font.deinit();
|
||||||
|
|
||||||
_ = try font.loadGlyph(alloc, &atlas, '🥸');
|
_ = try font.renderGlyph(alloc, &atlas, font.glyphIndex('🥸').?);
|
||||||
}
|
}
|
||||||
|
@@ -1,159 +0,0 @@
|
|||||||
//! FallbackSet represents a set of families in priority order to load a glyph.
|
|
||||||
//! This can be used to merge multiple font families together to find a glyph
|
|
||||||
//! for a codepoint.
|
|
||||||
const FallbackSet = @This();
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
const assert = std.debug.assert;
|
|
||||||
const Allocator = std.mem.Allocator;
|
|
||||||
|
|
||||||
const Atlas = @import("../Atlas.zig");
|
|
||||||
const Family = @import("main.zig").Family;
|
|
||||||
const Library = @import("main.zig").Library;
|
|
||||||
const Glyph = @import("main.zig").Glyph;
|
|
||||||
const Style = @import("main.zig").Style;
|
|
||||||
const codepoint = @import("main.zig").codepoint;
|
|
||||||
|
|
||||||
const log = std.log.scoped(.font_fallback);
|
|
||||||
|
|
||||||
/// The families to look for in order. This should be managed directly
|
|
||||||
/// by the caller of the set. Deinit will deallocate this.
|
|
||||||
families: std.ArrayListUnmanaged(Family) = .{},
|
|
||||||
|
|
||||||
/// A quick lookup that points directly to the family that loaded a glyph.
|
|
||||||
glyphs: std.AutoHashMapUnmanaged(GlyphKey, usize) = .{},
|
|
||||||
|
|
||||||
const GlyphKey = struct {
|
|
||||||
style: Style,
|
|
||||||
codepoint: u32,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn deinit(self: *FallbackSet, alloc: Allocator) void {
|
|
||||||
self.families.deinit(alloc);
|
|
||||||
self.glyphs.deinit(alloc);
|
|
||||||
self.* = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const GetOrAdd = struct {
|
|
||||||
/// Index of the family where the glyph was loaded from
|
|
||||||
family: usize,
|
|
||||||
|
|
||||||
/// True if the glyph was found or whether it was newly loaded
|
|
||||||
found_existing: bool,
|
|
||||||
|
|
||||||
/// The glyph
|
|
||||||
glyph: *Glyph,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn getOrAddGlyph(
|
|
||||||
self: *FallbackSet,
|
|
||||||
alloc: Allocator,
|
|
||||||
v: anytype,
|
|
||||||
style: Style,
|
|
||||||
) !GetOrAdd {
|
|
||||||
assert(self.families.items.len > 0);
|
|
||||||
|
|
||||||
// We need a UTF32 codepoint
|
|
||||||
const utf32 = codepoint(v);
|
|
||||||
|
|
||||||
// If we have this already, load it directly
|
|
||||||
const glyphKey: GlyphKey = .{ .style = style, .codepoint = utf32 };
|
|
||||||
const gop = try self.glyphs.getOrPut(alloc, glyphKey);
|
|
||||||
if (gop.found_existing) {
|
|
||||||
const i = gop.value_ptr.*;
|
|
||||||
assert(i < self.families.items.len);
|
|
||||||
return GetOrAdd{
|
|
||||||
.family = i,
|
|
||||||
.found_existing = true,
|
|
||||||
.glyph = self.families.items[i].getGlyph(v, style) orelse unreachable,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
errdefer _ = self.glyphs.remove(glyphKey);
|
|
||||||
|
|
||||||
// Go through each familiy and look for a matching glyph
|
|
||||||
var fam_i: ?usize = 0;
|
|
||||||
const glyph = glyph: {
|
|
||||||
for (self.families.items) |*family, i| {
|
|
||||||
fam_i = i;
|
|
||||||
|
|
||||||
// If this family already has it loaded, return it.
|
|
||||||
if (family.getGlyph(v, style)) |glyph| break :glyph glyph;
|
|
||||||
|
|
||||||
// Try to load it.
|
|
||||||
if (family.addGlyph(alloc, v, style)) |glyph|
|
|
||||||
break :glyph glyph
|
|
||||||
else |err| switch (err) {
|
|
||||||
// TODO: this probably doesn't belong here and should
|
|
||||||
// be higher level... but how?
|
|
||||||
error.AtlasFull => {
|
|
||||||
try family.atlas.grow(alloc, family.atlas.size * 2);
|
|
||||||
break :glyph try family.addGlyph(alloc, v, style);
|
|
||||||
},
|
|
||||||
|
|
||||||
error.GlyphNotFound => {},
|
|
||||||
else => return err,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we are regular, we use a fallback character
|
|
||||||
log.warn("glyph not found, using fallback. codepoint={x}", .{utf32});
|
|
||||||
fam_i = null;
|
|
||||||
break :glyph try self.families.items[0].addGlyph(alloc, ' ', style);
|
|
||||||
};
|
|
||||||
|
|
||||||
// If we found a real value, then cache it.
|
|
||||||
// TODO: support caching fallbacks too
|
|
||||||
if (fam_i) |i|
|
|
||||||
gop.value_ptr.* = i
|
|
||||||
else
|
|
||||||
_ = self.glyphs.remove(glyphKey);
|
|
||||||
|
|
||||||
return GetOrAdd{
|
|
||||||
.family = fam_i orelse 0,
|
|
||||||
.glyph = glyph,
|
|
||||||
|
|
||||||
// Technically possible that we found this in a cache...
|
|
||||||
.found_existing = false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
test {
|
|
||||||
const fontRegular = @import("test.zig").fontRegular;
|
|
||||||
const fontEmoji = @import("test.zig").fontEmoji;
|
|
||||||
|
|
||||||
const testing = std.testing;
|
|
||||||
const alloc = testing.allocator;
|
|
||||||
|
|
||||||
var lib = try Library.init();
|
|
||||||
defer lib.deinit();
|
|
||||||
|
|
||||||
var set: FallbackSet = .{};
|
|
||||||
try set.families.append(alloc, fam: {
|
|
||||||
var fam = Family.init(lib, try Atlas.init(alloc, 512, .greyscale));
|
|
||||||
try fam.loadFaceFromMemory(.regular, fontRegular, .{ .points = 48 });
|
|
||||||
break :fam fam;
|
|
||||||
});
|
|
||||||
try set.families.append(alloc, fam: {
|
|
||||||
var fam = Family.init(lib, try Atlas.init(alloc, 512, .rgba));
|
|
||||||
try fam.loadFaceFromMemory(.regular, fontEmoji, .{ .points = 48 });
|
|
||||||
break :fam fam;
|
|
||||||
});
|
|
||||||
|
|
||||||
defer {
|
|
||||||
for (set.families.items) |*family| {
|
|
||||||
family.atlas.deinit(alloc);
|
|
||||||
family.deinit(alloc);
|
|
||||||
}
|
|
||||||
set.deinit(alloc);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate all visible ASCII
|
|
||||||
var i: u8 = 32;
|
|
||||||
while (i < 127) : (i += 1) {
|
|
||||||
_ = try set.getOrAddGlyph(alloc, i, .regular);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Emoji should work
|
|
||||||
_ = try set.getOrAddGlyph(alloc, '🥸', .regular);
|
|
||||||
_ = try set.getOrAddGlyph(alloc, '🥸', .bold);
|
|
||||||
}
|
|
@@ -1,153 +0,0 @@
|
|||||||
//! Family represents a multiple styles of a single font: regular, bold,
|
|
||||||
//! italic, etc. It is able to cache the glyphs into a single atlas.
|
|
||||||
const Family = @This();
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
const Allocator = std.mem.Allocator;
|
|
||||||
const Atlas = @import("../Atlas.zig");
|
|
||||||
const Face = @import("main.zig").Face;
|
|
||||||
const Glyph = @import("main.zig").Glyph;
|
|
||||||
const Style = @import("main.zig").Style;
|
|
||||||
const testFont = @import("test.zig").fontRegular;
|
|
||||||
const codepoint = @import("main.zig").codepoint;
|
|
||||||
const Library = @import("main.zig").Library;
|
|
||||||
|
|
||||||
const log = std.log.scoped(.font_family);
|
|
||||||
|
|
||||||
/// The texture atlas where all the font glyphs are rendered.
|
|
||||||
/// This is NOT owned by the Family, deinitialization must
|
|
||||||
/// be manually done.
|
|
||||||
atlas: Atlas,
|
|
||||||
|
|
||||||
/// The library shared state.
|
|
||||||
lib: Library,
|
|
||||||
|
|
||||||
/// The glyphs that are loaded into the atlas, keyed by codepoint.
|
|
||||||
glyphs: std.AutoHashMapUnmanaged(GlyphKey, Glyph) = .{},
|
|
||||||
|
|
||||||
/// The font faces representing all the styles in this family.
|
|
||||||
/// These should be set directly or via various loader functions.
|
|
||||||
regular: ?Face = null,
|
|
||||||
bold: ?Face = null,
|
|
||||||
|
|
||||||
/// This struct is used for the hash key for glyphs.
|
|
||||||
const GlyphKey = struct {
|
|
||||||
style: Style,
|
|
||||||
codepoint: u32,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn init(lib: Library, atlas: Atlas) Family {
|
|
||||||
return .{
|
|
||||||
.lib = lib,
|
|
||||||
.atlas = atlas,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *Family, alloc: Allocator) void {
|
|
||||||
self.glyphs.deinit(alloc);
|
|
||||||
|
|
||||||
if (self.regular) |*face| face.deinit();
|
|
||||||
if (self.bold) |*face| face.deinit();
|
|
||||||
|
|
||||||
self.* = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Loads a font to use from memory.
|
|
||||||
///
|
|
||||||
/// This can only be called if a font is not already loaded for the given style.
|
|
||||||
pub fn loadFaceFromMemory(
|
|
||||||
self: *Family,
|
|
||||||
comptime style: Style,
|
|
||||||
source: [:0]const u8,
|
|
||||||
size: Face.DesiredSize,
|
|
||||||
) !void {
|
|
||||||
var face = try Face.init(self.lib, source, size);
|
|
||||||
errdefer face.deinit();
|
|
||||||
|
|
||||||
@field(self, switch (style) {
|
|
||||||
.regular => "regular",
|
|
||||||
.bold => "bold",
|
|
||||||
.italic => unreachable,
|
|
||||||
.bold_italic => unreachable,
|
|
||||||
}) = face;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the glyph for the given codepoint and style. If the glyph hasn't
|
|
||||||
/// been loaded yet this will return null.
|
|
||||||
pub fn getGlyph(self: Family, cp: anytype, style: Style) ?*Glyph {
|
|
||||||
const utf32 = codepoint(cp);
|
|
||||||
const entry = self.glyphs.getEntry(.{
|
|
||||||
.style = style,
|
|
||||||
.codepoint = utf32,
|
|
||||||
}) orelse return null;
|
|
||||||
return entry.value_ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add a glyph. If the glyph has already been loaded this will return
|
|
||||||
/// the existing loaded glyph. If a glyph style can't be found, this will
|
|
||||||
/// fall back to the "regular" style. If a glyph can't be found in the
|
|
||||||
/// "regular" style, this will fall back to the unknown glyph character.
|
|
||||||
///
|
|
||||||
/// The codepoint can be either a u8 or []const u8 depending on if you know
|
|
||||||
/// it is ASCII or must be UTF-8 decoded.
|
|
||||||
pub fn addGlyph(self: *Family, alloc: Allocator, v: anytype, style: Style) !*Glyph {
|
|
||||||
const face = face: {
|
|
||||||
// Real is the face we SHOULD use for this style.
|
|
||||||
var real = switch (style) {
|
|
||||||
.regular => self.regular,
|
|
||||||
.bold => self.bold,
|
|
||||||
.italic => unreachable,
|
|
||||||
.bold_italic => unreachable,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fall back to regular if it is null
|
|
||||||
if (real == null) real = self.regular;
|
|
||||||
|
|
||||||
// Return our face if we have it.
|
|
||||||
if (real) |ptr| break :face ptr;
|
|
||||||
|
|
||||||
// If we reached this point, we have no font in the style we
|
|
||||||
// want OR the fallback.
|
|
||||||
return error.NoFontFallback;
|
|
||||||
};
|
|
||||||
|
|
||||||
// We need a UTF32 codepoint
|
|
||||||
const utf32 = codepoint(v);
|
|
||||||
|
|
||||||
// If we have this glyph loaded already then we're done.
|
|
||||||
const glyphKey = .{
|
|
||||||
.style = style,
|
|
||||||
.codepoint = utf32,
|
|
||||||
};
|
|
||||||
const gop = try self.glyphs.getOrPut(alloc, glyphKey);
|
|
||||||
if (gop.found_existing) return gop.value_ptr;
|
|
||||||
errdefer _ = self.glyphs.remove(glyphKey);
|
|
||||||
|
|
||||||
// Get the glyph and add it to the atlas.
|
|
||||||
gop.value_ptr.* = try face.loadGlyph(alloc, &self.atlas, utf32);
|
|
||||||
return gop.value_ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
test {
|
|
||||||
const testing = std.testing;
|
|
||||||
const alloc = testing.allocator;
|
|
||||||
|
|
||||||
var lib = try Library.init();
|
|
||||||
defer lib.deinit();
|
|
||||||
|
|
||||||
var fam = init(lib, try Atlas.init(alloc, 512, .greyscale));
|
|
||||||
defer fam.deinit(alloc);
|
|
||||||
defer fam.atlas.deinit(alloc);
|
|
||||||
try fam.loadFaceFromMemory(.regular, testFont, .{ .points = 12 });
|
|
||||||
|
|
||||||
// Generate all visible ASCII
|
|
||||||
var i: u8 = 32;
|
|
||||||
while (i < 127) : (i += 1) {
|
|
||||||
_ = try fam.addGlyph(alloc, i, .regular);
|
|
||||||
}
|
|
||||||
|
|
||||||
i = 32;
|
|
||||||
while (i < 127) : (i += 1) {
|
|
||||||
try testing.expect(fam.getGlyph(i, .regular) != null);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -40,6 +40,7 @@ const GlyphKey = struct {
|
|||||||
glyph: u32,
|
glyph: u32,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// The GroupCache takes ownership of Group and will free it.
|
||||||
pub fn init(alloc: Allocator, group: Group) !GroupCache {
|
pub fn init(alloc: Allocator, group: Group) !GroupCache {
|
||||||
var atlas_greyscale = try Atlas.init(alloc, 512, .greyscale);
|
var atlas_greyscale = try Atlas.init(alloc, 512, .greyscale);
|
||||||
errdefer atlas_greyscale.deinit(alloc);
|
errdefer atlas_greyscale.deinit(alloc);
|
||||||
@@ -65,6 +66,7 @@ pub fn deinit(self: *GroupCache, alloc: Allocator) void {
|
|||||||
self.glyphs.deinit(alloc);
|
self.glyphs.deinit(alloc);
|
||||||
self.atlas_greyscale.deinit(alloc);
|
self.atlas_greyscale.deinit(alloc);
|
||||||
self.atlas_color.deinit(alloc);
|
self.atlas_color.deinit(alloc);
|
||||||
|
self.group.deinit(alloc);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reset the cache. This should be called:
|
/// Reset the cache. This should be called:
|
||||||
@@ -146,13 +148,13 @@ test {
|
|||||||
var lib = try Library.init();
|
var lib = try Library.init();
|
||||||
defer lib.deinit();
|
defer lib.deinit();
|
||||||
|
|
||||||
var group = try Group.init(alloc);
|
var cache = try init(alloc, try Group.init(alloc));
|
||||||
defer group.deinit(alloc);
|
|
||||||
try group.addFace(alloc, .regular, try Face.init(lib, testFont, .{ .points = 12 }));
|
|
||||||
|
|
||||||
var cache = try init(alloc, group);
|
|
||||||
defer cache.deinit(alloc);
|
defer cache.deinit(alloc);
|
||||||
|
|
||||||
|
// Setup group
|
||||||
|
try cache.group.addFace(alloc, .regular, try Face.init(lib, testFont, .{ .points = 12 }));
|
||||||
|
const group = cache.group;
|
||||||
|
|
||||||
// Visible ASCII. Do it twice to verify cache.
|
// Visible ASCII. Do it twice to verify cache.
|
||||||
var i: u32 = 32;
|
var i: u32 = 32;
|
||||||
while (i < 127) : (i += 1) {
|
while (i < 127) : (i += 1) {
|
||||||
|
@@ -1,11 +1,9 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
pub const Face = @import("Face.zig");
|
pub const Face = @import("Face.zig");
|
||||||
pub const Family = @import("Family.zig");
|
|
||||||
pub const Group = @import("Group.zig");
|
pub const Group = @import("Group.zig");
|
||||||
pub const GroupCache = @import("GroupCache.zig");
|
pub const GroupCache = @import("GroupCache.zig");
|
||||||
pub const Glyph = @import("Glyph.zig");
|
pub const Glyph = @import("Glyph.zig");
|
||||||
pub const FallbackSet = @import("FallbackSet.zig");
|
|
||||||
pub const Library = @import("Library.zig");
|
pub const Library = @import("Library.zig");
|
||||||
|
|
||||||
/// The styles that a family can take.
|
/// The styles that a family can take.
|
||||||
|
Reference in New Issue
Block a user