mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-21 19:26:09 +03:00
Merge pull request #333 from mitchellh/face-rework
font: Improve quirks mode performance, DeferredFace, Group refactor This was motivated by #331. I realized with #331 that this added a lot of overhead on every render frame, because for each text run being rendered, we were (1) reloading the font name (unknown performance cost on macOS due to closed source, looks like [it ain't exactly free in Freetype](https://sourcegraph.com/github.com/freetype/freetype@4d8db130ea4342317581bab65fc96365ce806b77/-/blob/src/base/ftsnames.c?L44)) (2) doing multiple string compares (3) multiple function call overhead. And realistically, quirks mode will be _rare_, so paying a very active cost for a rare thing is 💩 . In investigating that I finally shaved some yaks I've been wanting to shave on this path... the end result is that overall we should be doing [very] slightly less work no matter what and using slightly less memory. And on the renderer path, we're doing _way_ less work. - All loaded font `Face` structures must also check and cache their quirks mode settings. This makes #331 a single boolean check per text run which is effectively free compared to the actual shaping task. The cost is one additional byte on the face structure (well, whatever the alignment is). - `DeferredFace` now only represents an _unloaded_ face (that can be loaded multiple times). This lowers memory usage significantly (`Face` was large) per deferred face. - `DeferredFace.name()` now takes a buffer for output instead of just failing on no static space, weird decision. No performance impact but makes this function more robust. - `Group.addFace` now takes a tagged union representing either a deferred or loaded face, this gets rid of all the weird `DeferredFace.initLoaded` wrappers that were unnecessary and dumb. No real performance impact. - When a font face is loaded in `Group` (lots of things trigger this), the DeferredFace it came from is now deinitialized immediately. This should save on memory significantly because we free all the font discovery information. We can do this because a deferred face is only ever loaded _once_ when its part of a `Group`, and the group owns the deferred face. - Auto-italicize moves into `Group` and is no longer duplicated between Deferred and normal Face. No performance change, less code.
This commit is contained in:
@ -222,6 +222,9 @@ pub fn init(
|
|||||||
};
|
};
|
||||||
group.discover = disco;
|
group.discover = disco;
|
||||||
|
|
||||||
|
// A buffer we use to store the font names for logging.
|
||||||
|
var name_buf: [256]u8 = undefined;
|
||||||
|
|
||||||
if (config.@"font-family") |family| {
|
if (config.@"font-family") |family| {
|
||||||
var disco_it = try disco.discover(.{
|
var disco_it = try disco.discover(.{
|
||||||
.family = family,
|
.family = family,
|
||||||
@ -229,8 +232,8 @@ pub fn init(
|
|||||||
});
|
});
|
||||||
defer disco_it.deinit();
|
defer disco_it.deinit();
|
||||||
if (try disco_it.next()) |face| {
|
if (try disco_it.next()) |face| {
|
||||||
log.info("font regular: {s}", .{try face.name()});
|
log.info("font regular: {s}", .{try face.name(&name_buf)});
|
||||||
try group.addFace(alloc, .regular, face);
|
try group.addFace(.regular, .{ .deferred = face });
|
||||||
} else log.warn("font-family not found: {s}", .{family});
|
} else log.warn("font-family not found: {s}", .{family});
|
||||||
}
|
}
|
||||||
if (config.@"font-family-bold") |family| {
|
if (config.@"font-family-bold") |family| {
|
||||||
@ -241,8 +244,8 @@ pub fn init(
|
|||||||
});
|
});
|
||||||
defer disco_it.deinit();
|
defer disco_it.deinit();
|
||||||
if (try disco_it.next()) |face| {
|
if (try disco_it.next()) |face| {
|
||||||
log.info("font bold: {s}", .{try face.name()});
|
log.info("font bold: {s}", .{try face.name(&name_buf)});
|
||||||
try group.addFace(alloc, .bold, face);
|
try group.addFace(.bold, .{ .deferred = face });
|
||||||
} else log.warn("font-family-bold not found: {s}", .{family});
|
} else log.warn("font-family-bold not found: {s}", .{family});
|
||||||
}
|
}
|
||||||
if (config.@"font-family-italic") |family| {
|
if (config.@"font-family-italic") |family| {
|
||||||
@ -253,8 +256,8 @@ pub fn init(
|
|||||||
});
|
});
|
||||||
defer disco_it.deinit();
|
defer disco_it.deinit();
|
||||||
if (try disco_it.next()) |face| {
|
if (try disco_it.next()) |face| {
|
||||||
log.info("font italic: {s}", .{try face.name()});
|
log.info("font italic: {s}", .{try face.name(&name_buf)});
|
||||||
try group.addFace(alloc, .italic, face);
|
try group.addFace(.italic, .{ .deferred = face });
|
||||||
} else log.warn("font-family-italic not found: {s}", .{family});
|
} else log.warn("font-family-italic not found: {s}", .{family});
|
||||||
}
|
}
|
||||||
if (config.@"font-family-bold-italic") |family| {
|
if (config.@"font-family-bold-italic") |family| {
|
||||||
@ -266,56 +269,35 @@ pub fn init(
|
|||||||
});
|
});
|
||||||
defer disco_it.deinit();
|
defer disco_it.deinit();
|
||||||
if (try disco_it.next()) |face| {
|
if (try disco_it.next()) |face| {
|
||||||
log.info("font bold+italic: {s}", .{try face.name()});
|
log.info("font bold+italic: {s}", .{try face.name(&name_buf)});
|
||||||
try group.addFace(alloc, .bold_italic, face);
|
try group.addFace(.bold_italic, .{ .deferred = face });
|
||||||
} else log.warn("font-family-bold-italic not found: {s}", .{family});
|
} else log.warn("font-family-bold-italic not found: {s}", .{family});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Our built-in font will be used as a backup
|
// Our built-in font will be used as a backup
|
||||||
try group.addFace(
|
try group.addFace(
|
||||||
alloc,
|
|
||||||
.regular,
|
.regular,
|
||||||
font.DeferredFace.initLoaded(try font.Face.init(font_lib, face_ttf, font_size)),
|
.{ .loaded = try font.Face.init(font_lib, face_ttf, font_size) },
|
||||||
);
|
);
|
||||||
try group.addFace(
|
try group.addFace(
|
||||||
alloc,
|
|
||||||
.bold,
|
.bold,
|
||||||
font.DeferredFace.initLoaded(try font.Face.init(font_lib, face_bold_ttf, font_size)),
|
.{ .loaded = try font.Face.init(font_lib, face_bold_ttf, font_size) },
|
||||||
);
|
);
|
||||||
|
|
||||||
// If we support auto-italicization and we don't have an italic face,
|
// Auto-italicize if we have to.
|
||||||
// then we can try to auto-italicize our regular face.
|
try group.italicize();
|
||||||
if (comptime font.DeferredFace.canItalicize()) {
|
|
||||||
if (group.getFace(.italic) == null) {
|
|
||||||
if (group.getFace(.regular)) |regular| {
|
|
||||||
if (try regular.italicize()) |face| {
|
|
||||||
log.info("font auto-italicized: {s}", .{try face.name()});
|
|
||||||
try group.addFace(alloc, .italic, face);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// We don't support auto-italics. If we don't have an italic font
|
|
||||||
// face let the user know so they aren't surprised (if they look
|
|
||||||
// at logs).
|
|
||||||
if (group.getFace(.italic) == null) {
|
|
||||||
log.warn("no italic font face available, italics will not render", .{});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Emoji fallback. We don't include this on Mac since Mac is expected
|
// Emoji fallback. We don't include this on Mac since Mac is expected
|
||||||
// to always have the Apple Emoji available.
|
// to always have the Apple Emoji available.
|
||||||
if (builtin.os.tag != .macos or font.Discover == void) {
|
if (builtin.os.tag != .macos or font.Discover == void) {
|
||||||
try group.addFace(
|
try group.addFace(
|
||||||
alloc,
|
|
||||||
.regular,
|
.regular,
|
||||||
font.DeferredFace.initLoaded(try font.Face.init(font_lib, face_emoji_ttf, font_size)),
|
.{ .loaded = try font.Face.init(font_lib, face_emoji_ttf, font_size) },
|
||||||
);
|
);
|
||||||
try group.addFace(
|
try group.addFace(
|
||||||
alloc,
|
|
||||||
.regular,
|
.regular,
|
||||||
font.DeferredFace.initLoaded(try font.Face.init(font_lib, face_emoji_text_ttf, font_size)),
|
.{ .loaded = try font.Face.init(font_lib, face_emoji_text_ttf, font_size) },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -328,8 +310,9 @@ pub fn init(
|
|||||||
});
|
});
|
||||||
defer disco_it.deinit();
|
defer disco_it.deinit();
|
||||||
if (try disco_it.next()) |face| {
|
if (try disco_it.next()) |face| {
|
||||||
log.info("font emoji: {s}", .{try face.name()});
|
var name_buf: [256]u8 = undefined;
|
||||||
try group.addFace(alloc, .regular, face);
|
log.info("font emoji: {s}", .{try face.name(&name_buf)});
|
||||||
|
try group.addFace(.regular, .{ .deferred = face });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,9 +30,6 @@ const FaceState = switch (options.backend) {
|
|||||||
.web_canvas => WebCanvas,
|
.web_canvas => WebCanvas,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The loaded face (once loaded).
|
|
||||||
face: ?Face = null,
|
|
||||||
|
|
||||||
/// Fontconfig
|
/// Fontconfig
|
||||||
fc: if (options.backend == .fontconfig_freetype) ?Fontconfig else void =
|
fc: if (options.backend == .fontconfig_freetype) ?Fontconfig else void =
|
||||||
if (options.backend == .fontconfig_freetype) null else {},
|
if (options.backend == .fontconfig_freetype) null else {},
|
||||||
@ -72,13 +69,6 @@ pub const CoreText = struct {
|
|||||||
self.font.release();
|
self.font.release();
|
||||||
self.* = undefined;
|
self.* = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Auto-italicize the font by applying a skew.
|
|
||||||
pub fn italicize(self: *const CoreText) !CoreText {
|
|
||||||
const ct_font = try self.font.copyWithAttributes(0.0, &Face.italic_skew, null);
|
|
||||||
errdefer ct_font.release();
|
|
||||||
return .{ .font = ct_font };
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// WebCanvas specific data. This is only present when building with canvas.
|
/// WebCanvas specific data. This is only present when building with canvas.
|
||||||
@ -98,14 +88,7 @@ pub const WebCanvas = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Initialize a deferred face that is already pre-loaded. The deferred face
|
|
||||||
/// takes ownership over the loaded face, deinit will deinit the loaded face.
|
|
||||||
pub fn initLoaded(face: Face) DeferredFace {
|
|
||||||
return .{ .face = face };
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *DeferredFace) void {
|
pub fn deinit(self: *DeferredFace) void {
|
||||||
if (self.face) |*face| face.deinit();
|
|
||||||
switch (options.backend) {
|
switch (options.backend) {
|
||||||
.fontconfig_freetype => if (self.fc) |*fc| fc.deinit(),
|
.fontconfig_freetype => if (self.fc) |*fc| fc.deinit(),
|
||||||
.coretext, .coretext_freetype => if (self.ct) |*ct| ct.deinit(),
|
.coretext, .coretext_freetype => if (self.ct) |*ct| ct.deinit(),
|
||||||
@ -115,14 +98,9 @@ pub fn deinit(self: *DeferredFace) void {
|
|||||||
self.* = undefined;
|
self.* = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if the face has been loaded.
|
|
||||||
pub inline fn loaded(self: DeferredFace) bool {
|
|
||||||
return self.face != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the name of this face. The memory is always owned by the
|
/// Returns the name of this face. The memory is always owned by the
|
||||||
/// face so it doesn't have to be freed.
|
/// face so it doesn't have to be freed.
|
||||||
pub fn name(self: DeferredFace) ![:0]const u8 {
|
pub fn name(self: DeferredFace, buf: []u8) ![]const u8 {
|
||||||
switch (options.backend) {
|
switch (options.backend) {
|
||||||
.freetype => {},
|
.freetype => {},
|
||||||
|
|
||||||
@ -136,22 +114,15 @@ pub fn name(self: DeferredFace) ![:0]const u8 {
|
|||||||
// this to be returned efficiently." In this case, we need
|
// this to be returned efficiently." In this case, we need
|
||||||
// to allocate. But we can't return an allocated string because
|
// to allocate. But we can't return an allocated string because
|
||||||
// we don't have an allocator. Let's use the stack and log it.
|
// we don't have an allocator. Let's use the stack and log it.
|
||||||
var buf: [1024]u8 = undefined;
|
break :unsupported display_name.cstring(buf, .utf8) orelse
|
||||||
const buf_name = display_name.cstring(&buf, .utf8) orelse
|
return error.OutOfMemory;
|
||||||
"<not enough internal storage space>";
|
|
||||||
|
|
||||||
log.info(
|
|
||||||
"CoreText font required too much space to copy, value = {s}",
|
|
||||||
.{buf_name},
|
|
||||||
);
|
|
||||||
break :unsupported "<CoreText internal storage limited, see logs>";
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
.web_canvas => if (self.wc) |wc| return wc.font_str,
|
.web_canvas => if (self.wc) |wc| return wc.font_str,
|
||||||
}
|
}
|
||||||
|
|
||||||
return "TODO: built-in font names";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load the deferred font face. This does nothing if the face is loaded.
|
/// Load the deferred font face. This does nothing if the face is loaded.
|
||||||
@ -159,69 +130,48 @@ pub fn load(
|
|||||||
self: *DeferredFace,
|
self: *DeferredFace,
|
||||||
lib: Library,
|
lib: Library,
|
||||||
size: font.face.DesiredSize,
|
size: font.face.DesiredSize,
|
||||||
) !void {
|
) !Face {
|
||||||
// No-op if we already loaded
|
return switch (options.backend) {
|
||||||
if (self.face != null) return;
|
.fontconfig_freetype => try self.loadFontconfig(lib, size),
|
||||||
|
.coretext => try self.loadCoreText(lib, size),
|
||||||
switch (options.backend) {
|
.coretext_freetype => try self.loadCoreTextFreetype(lib, size),
|
||||||
.fontconfig_freetype => {
|
.web_canvas => try self.loadWebCanvas(size),
|
||||||
try self.loadFontconfig(lib, size);
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
|
|
||||||
.coretext => {
|
|
||||||
try self.loadCoreText(lib, size);
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
|
|
||||||
.coretext_freetype => {
|
|
||||||
try self.loadCoreTextFreetype(lib, size);
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
|
|
||||||
.web_canvas => {
|
|
||||||
try self.loadWebCanvas(size);
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Unreachable because we must be already loaded or have the
|
// Unreachable because we must be already loaded or have the
|
||||||
// proper configuration for one of the other deferred mechanisms.
|
// proper configuration for one of the other deferred mechanisms.
|
||||||
.freetype => unreachable,
|
.freetype => unreachable,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn loadFontconfig(
|
fn loadFontconfig(
|
||||||
self: *DeferredFace,
|
self: *DeferredFace,
|
||||||
lib: Library,
|
lib: Library,
|
||||||
size: font.face.DesiredSize,
|
size: font.face.DesiredSize,
|
||||||
) !void {
|
) !Face {
|
||||||
assert(self.face == null);
|
|
||||||
const fc = self.fc.?;
|
const fc = self.fc.?;
|
||||||
|
|
||||||
// Filename and index for our face so we can load it
|
// Filename and index for our face so we can load it
|
||||||
const filename = (try fc.pattern.get(.file, 0)).string;
|
const filename = (try fc.pattern.get(.file, 0)).string;
|
||||||
const face_index = (try fc.pattern.get(.index, 0)).integer;
|
const face_index = (try fc.pattern.get(.index, 0)).integer;
|
||||||
|
|
||||||
self.face = try Face.initFile(lib, filename, face_index, size);
|
return try Face.initFile(lib, filename, face_index, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn loadCoreText(
|
fn loadCoreText(
|
||||||
self: *DeferredFace,
|
self: *DeferredFace,
|
||||||
lib: Library,
|
lib: Library,
|
||||||
size: font.face.DesiredSize,
|
size: font.face.DesiredSize,
|
||||||
) !void {
|
) !Face {
|
||||||
_ = lib;
|
_ = lib;
|
||||||
assert(self.face == null);
|
|
||||||
const ct = self.ct.?;
|
const ct = self.ct.?;
|
||||||
self.face = try Face.initFontCopy(ct.font, size);
|
return try Face.initFontCopy(ct.font, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn loadCoreTextFreetype(
|
fn loadCoreTextFreetype(
|
||||||
self: *DeferredFace,
|
self: *DeferredFace,
|
||||||
lib: Library,
|
lib: Library,
|
||||||
size: font.face.DesiredSize,
|
size: font.face.DesiredSize,
|
||||||
) !void {
|
) !Face {
|
||||||
assert(self.face == null);
|
|
||||||
const ct = self.ct.?;
|
const ct = self.ct.?;
|
||||||
|
|
||||||
// Get the URL for the font so we can get the filepath
|
// Get the URL for the font so we can get the filepath
|
||||||
@ -253,16 +203,15 @@ fn loadCoreTextFreetype(
|
|||||||
// TODO: face index 0 is not correct long term and we should switch
|
// TODO: face index 0 is not correct long term and we should switch
|
||||||
// to using CoreText for rendering, too.
|
// to using CoreText for rendering, too.
|
||||||
//std.log.warn("path={s}", .{path_slice});
|
//std.log.warn("path={s}", .{path_slice});
|
||||||
self.face = try Face.initFile(lib, buf[0..path_slice.len :0], 0, size);
|
return try Face.initFile(lib, buf[0..path_slice.len :0], 0, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn loadWebCanvas(
|
fn loadWebCanvas(
|
||||||
self: *DeferredFace,
|
self: *DeferredFace,
|
||||||
size: font.face.DesiredSize,
|
size: font.face.DesiredSize,
|
||||||
) !void {
|
) !Face {
|
||||||
assert(self.face == null);
|
|
||||||
const wc = self.wc.?;
|
const wc = self.wc.?;
|
||||||
self.face = try Face.initNamed(wc.alloc, wc.font_str, size, wc.presentation);
|
return try Face.initNamed(wc.alloc, wc.font_str, size, wc.presentation);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if this face can satisfy the given codepoint and
|
/// Returns true if this face can satisfy the given codepoint and
|
||||||
@ -273,12 +222,6 @@ fn loadWebCanvas(
|
|||||||
/// discovery mechanism (i.e. fontconfig). If no discovery is used,
|
/// discovery mechanism (i.e. fontconfig). If no discovery is used,
|
||||||
/// the face is always expected to be loaded.
|
/// the face is always expected to be loaded.
|
||||||
pub fn hasCodepoint(self: DeferredFace, cp: u32, p: ?Presentation) bool {
|
pub fn hasCodepoint(self: DeferredFace, cp: u32, p: ?Presentation) bool {
|
||||||
// If we have the face, use the face.
|
|
||||||
if (self.face) |face| {
|
|
||||||
if (p) |desired| if (face.presentation != desired) return false;
|
|
||||||
return face.glyphIndex(cp) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (options.backend) {
|
switch (options.backend) {
|
||||||
.fontconfig_freetype => {
|
.fontconfig_freetype => {
|
||||||
// If we are using fontconfig, use the fontconfig metadata to
|
// If we are using fontconfig, use the fontconfig metadata to
|
||||||
@ -351,40 +294,6 @@ pub fn hasCodepoint(self: DeferredFace, cp: u32, p: ?Presentation) bool {
|
|||||||
unreachable;
|
unreachable;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if our deferred font implementation supports auto-itacilization.
|
|
||||||
pub fn canItalicize() bool {
|
|
||||||
return @hasDecl(FaceState, "italicize") and @hasDecl(Face, "italicize");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a new deferred face with the italicized version of this face
|
|
||||||
/// by applying a skew. This is NOT TRUE italics. You should use the discovery
|
|
||||||
/// mechanism to try to find an italic font. This is a fallback for when
|
|
||||||
/// that fails.
|
|
||||||
pub fn italicize(self: *const DeferredFace) !?DeferredFace {
|
|
||||||
if (comptime !canItalicize()) return null;
|
|
||||||
|
|
||||||
var result: DeferredFace = .{};
|
|
||||||
|
|
||||||
if (self.face) |face| {
|
|
||||||
result.face = try face.italicize();
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (options.backend) {
|
|
||||||
.freetype => {},
|
|
||||||
.fontconfig_freetype => if (self.fc) |*fc| {
|
|
||||||
result.fc = try fc.italicize();
|
|
||||||
},
|
|
||||||
.coretext, .coretext_freetype => if (self.ct) |*ct| {
|
|
||||||
result.ct = try ct.italicize();
|
|
||||||
},
|
|
||||||
.web_canvas => if (self.wc) |*wc| {
|
|
||||||
result.wc = try wc.italicize();
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The wasm-compatible API.
|
/// The wasm-compatible API.
|
||||||
pub const Wasm = struct {
|
pub const Wasm = struct {
|
||||||
const wasm = @import("../os/wasm.zig");
|
const wasm = @import("../os/wasm.zig");
|
||||||
@ -429,30 +338,8 @@ pub const Wasm = struct {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Caller should not free this, the face is owned by the deferred face.
|
|
||||||
export fn deferred_face_face(self: *DeferredFace) ?*Face {
|
|
||||||
assert(self.loaded());
|
|
||||||
return &self.face.?;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
test "preloaded" {
|
|
||||||
const testing = std.testing;
|
|
||||||
const testFont = @import("test.zig").fontRegular;
|
|
||||||
|
|
||||||
var lib = try Library.init();
|
|
||||||
defer lib.deinit();
|
|
||||||
|
|
||||||
var face = try Face.init(lib, testFont, .{ .points = 12 });
|
|
||||||
errdefer face.deinit();
|
|
||||||
|
|
||||||
var def = initLoaded(face);
|
|
||||||
defer def.deinit();
|
|
||||||
|
|
||||||
try testing.expect(def.hasCodepoint(' ', null));
|
|
||||||
}
|
|
||||||
|
|
||||||
test "fontconfig" {
|
test "fontconfig" {
|
||||||
if (options.backend != .fontconfig_freetype) return error.SkipZigTest;
|
if (options.backend != .fontconfig_freetype) return error.SkipZigTest;
|
||||||
|
|
||||||
@ -471,16 +358,15 @@ test "fontconfig" {
|
|||||||
break :def (try it.next()).?;
|
break :def (try it.next()).?;
|
||||||
};
|
};
|
||||||
defer def.deinit();
|
defer def.deinit();
|
||||||
try testing.expect(!def.loaded());
|
|
||||||
|
|
||||||
// Verify we can get the name
|
// Verify we can get the name
|
||||||
const n = try def.name();
|
var buf: [1024]u8 = undefined;
|
||||||
|
const n = try def.name(&buf);
|
||||||
try testing.expect(n.len > 0);
|
try testing.expect(n.len > 0);
|
||||||
|
|
||||||
// Load it and verify it works
|
// Load it and verify it works
|
||||||
try def.load(lib, .{ .points = 12 });
|
const face = try def.load(lib, .{ .points = 12 });
|
||||||
try testing.expect(def.hasCodepoint(' ', null));
|
try testing.expect(face.glyphIndex(' ') != null);
|
||||||
try testing.expect(def.face.?.glyphIndex(' ') != null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test "coretext" {
|
test "coretext" {
|
||||||
@ -501,15 +387,14 @@ test "coretext" {
|
|||||||
break :def (try it.next()).?;
|
break :def (try it.next()).?;
|
||||||
};
|
};
|
||||||
defer def.deinit();
|
defer def.deinit();
|
||||||
try testing.expect(!def.loaded());
|
|
||||||
try testing.expect(def.hasCodepoint(' ', null));
|
try testing.expect(def.hasCodepoint(' ', null));
|
||||||
|
|
||||||
// Verify we can get the name
|
// Verify we can get the name
|
||||||
const n = try def.name();
|
var buf: [1024]u8 = undefined;
|
||||||
|
const n = try def.name(&buf);
|
||||||
try testing.expect(n.len > 0);
|
try testing.expect(n.len > 0);
|
||||||
|
|
||||||
// Load it and verify it works
|
// Load it and verify it works
|
||||||
try def.load(lib, .{ .points = 12 });
|
const face = try def.load(lib, .{ .points = 12 });
|
||||||
try testing.expect(def.hasCodepoint(' ', null));
|
try testing.expect(face.glyphIndex(' ') != null);
|
||||||
try testing.expect(def.face.?.glyphIndex(' ') != null);
|
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ const Glyph = @import("main.zig").Glyph;
|
|||||||
const Style = @import("main.zig").Style;
|
const Style = @import("main.zig").Style;
|
||||||
const Presentation = @import("main.zig").Presentation;
|
const Presentation = @import("main.zig").Presentation;
|
||||||
const options = @import("main.zig").options;
|
const options = @import("main.zig").options;
|
||||||
|
const quirks = @import("../quirks.zig");
|
||||||
|
|
||||||
const log = std.log.scoped(.font_group);
|
const log = std.log.scoped(.font_group);
|
||||||
|
|
||||||
@ -30,8 +31,7 @@ const log = std.log.scoped(.font_group);
|
|||||||
// usually only one font group for the entire process so this isn't the
|
// usually only one font group for the entire process so this isn't the
|
||||||
// most important memory efficiency we can look for. This is totally opaque
|
// most important memory efficiency we can look for. This is totally opaque
|
||||||
// to the user so we can change this later.
|
// to the user so we can change this later.
|
||||||
const StyleArray = std.EnumArray(Style, std.ArrayListUnmanaged(DeferredFace));
|
const StyleArray = std.EnumArray(Style, std.ArrayListUnmanaged(GroupFace));
|
||||||
|
|
||||||
/// The allocator for this group
|
/// The allocator for this group
|
||||||
alloc: Allocator,
|
alloc: Allocator,
|
||||||
|
|
||||||
@ -55,6 +55,19 @@ discover: ?*font.Discover = null,
|
|||||||
/// terminal rendering will look wrong.
|
/// terminal rendering will look wrong.
|
||||||
sprite: ?font.sprite.Face = null,
|
sprite: ?font.sprite.Face = null,
|
||||||
|
|
||||||
|
/// A face in a group can be deferred or loaded.
|
||||||
|
pub const GroupFace = union(enum) {
|
||||||
|
deferred: DeferredFace, // Not loaded
|
||||||
|
loaded: Face, // Loaded
|
||||||
|
|
||||||
|
pub fn deinit(self: *GroupFace) void {
|
||||||
|
switch (self.*) {
|
||||||
|
.deferred => |*v| v.deinit(),
|
||||||
|
.loaded => |*v| v.deinit(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
pub fn init(
|
pub fn init(
|
||||||
alloc: Allocator,
|
alloc: Allocator,
|
||||||
lib: Library,
|
lib: Library,
|
||||||
@ -86,22 +99,55 @@ pub fn deinit(self: *Group) void {
|
|||||||
///
|
///
|
||||||
/// The group takes ownership of the face. The face will be deallocated when
|
/// The group takes ownership of the face. The face will be deallocated when
|
||||||
/// the group is deallocated.
|
/// the group is deallocated.
|
||||||
pub fn addFace(self: *Group, alloc: Allocator, style: Style, face: DeferredFace) !void {
|
pub fn addFace(self: *Group, style: Style, face: GroupFace) !void {
|
||||||
const list = self.faces.getPtr(style);
|
const list = self.faces.getPtr(style);
|
||||||
|
|
||||||
// We have some special indexes so we must never pass those.
|
// We have some special indexes so we must never pass those.
|
||||||
if (list.items.len >= FontIndex.Special.start - 1) return error.GroupFull;
|
if (list.items.len >= FontIndex.Special.start - 1) return error.GroupFull;
|
||||||
|
|
||||||
try list.append(alloc, face);
|
try list.append(self.alloc, face);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the face for the given style. This will always return the first
|
/// Returns true if we have a face for the given style, though the face may
|
||||||
/// face (if it exists). The returned pointer is only valid as long as
|
/// not be loaded yet.
|
||||||
/// the faces do not change.
|
pub fn hasFaceForStyle(self: Group, style: Style) bool {
|
||||||
pub fn getFace(self: *Group, style: Style) ?*DeferredFace {
|
const list = self.faces.get(style);
|
||||||
const list = self.faces.getPtr(style);
|
return list.items.len > 0;
|
||||||
if (list.items.len == 0) return null;
|
}
|
||||||
return &list.items[0];
|
|
||||||
|
/// This will automatically create an italicized font from the regular
|
||||||
|
/// font face if we don't have any italicized fonts.
|
||||||
|
pub fn italicize(self: *Group) !void {
|
||||||
|
// If we have an italic font, do nothing.
|
||||||
|
const italic_list = self.faces.getPtr(.italic);
|
||||||
|
if (italic_list.items.len > 0) return;
|
||||||
|
|
||||||
|
// Not all font backends support auto-italicization.
|
||||||
|
if (comptime !@hasDecl(Face, "italicize")) {
|
||||||
|
log.warn("no italic font face available, italics will not render", .{});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Our regular font. If we have no regular font we also do nothing.
|
||||||
|
const regular = regular: {
|
||||||
|
const list = self.faces.get(.regular);
|
||||||
|
if (list.items.len == 0) return;
|
||||||
|
|
||||||
|
// The font must be loaded.
|
||||||
|
break :regular try self.faceFromIndex(.{
|
||||||
|
.style = .regular,
|
||||||
|
.idx = 0,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Try to italicize it.
|
||||||
|
const face = try regular.italicize();
|
||||||
|
try italic_list.append(self.alloc, .{ .loaded = face });
|
||||||
|
|
||||||
|
var buf: [128]u8 = undefined;
|
||||||
|
if (face.name(&buf)) |name| {
|
||||||
|
log.info("font auto-italicized: {s}", .{name});
|
||||||
|
} else |_| {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resize the fonts to the desired size.
|
/// Resize the fonts to the desired size.
|
||||||
@ -113,10 +159,10 @@ pub fn setSize(self: *Group, size: font.face.DesiredSize) !void {
|
|||||||
// Resize all our faces that are loaded
|
// Resize all our faces that are loaded
|
||||||
var it = self.faces.iterator();
|
var it = self.faces.iterator();
|
||||||
while (it.next()) |entry| {
|
while (it.next()) |entry| {
|
||||||
for (entry.value.items) |*deferred| {
|
for (entry.value.items) |*elem| switch (elem.*) {
|
||||||
if (!deferred.loaded()) continue;
|
.deferred => continue,
|
||||||
try deferred.face.?.setSize(size);
|
.loaded => |*f| try f.setSize(size),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set our size for future loads
|
// Set our size for future loads
|
||||||
@ -213,11 +259,12 @@ pub fn indexForCodepoint(
|
|||||||
defer disco_it.deinit();
|
defer disco_it.deinit();
|
||||||
|
|
||||||
if (disco_it.next() catch break :discover) |face| {
|
if (disco_it.next() catch break :discover) |face| {
|
||||||
|
var buf: [256]u8 = undefined;
|
||||||
log.info("found codepoint 0x{x} in fallback face={s}", .{
|
log.info("found codepoint 0x{x} in fallback face={s}", .{
|
||||||
cp,
|
cp,
|
||||||
face.name() catch "<error>",
|
face.name(&buf) catch "<error>",
|
||||||
});
|
});
|
||||||
self.addFace(self.alloc, style, face) catch break :discover;
|
self.addFace(style, .{ .deferred = face }) catch break :discover;
|
||||||
if (self.indexForCodepointExact(cp, style, p)) |value| return value;
|
if (self.indexForCodepointExact(cp, style, p)) |value| return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -231,8 +278,16 @@ pub fn indexForCodepoint(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn indexForCodepointExact(self: Group, cp: u32, style: Style, p: ?Presentation) ?FontIndex {
|
fn indexForCodepointExact(self: Group, cp: u32, style: Style, p: ?Presentation) ?FontIndex {
|
||||||
for (self.faces.get(style).items, 0..) |deferred, i| {
|
for (self.faces.get(style).items, 0..) |elem, i| {
|
||||||
if (deferred.hasCodepoint(cp, p)) {
|
const has_cp = switch (elem) {
|
||||||
|
.deferred => |v| v.hasCodepoint(cp, p),
|
||||||
|
.loaded => |face| loaded: {
|
||||||
|
if (p) |desired| if (face.presentation != desired) break :loaded false;
|
||||||
|
break :loaded face.glyphIndex(cp) != null;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (has_cp) {
|
||||||
return FontIndex{
|
return FontIndex{
|
||||||
.style = style,
|
.style = style,
|
||||||
.idx = @intCast(i),
|
.idx = @intCast(i),
|
||||||
@ -246,7 +301,7 @@ fn indexForCodepointExact(self: Group, cp: u32, style: Style, p: ?Presentation)
|
|||||||
|
|
||||||
/// Returns the presentation for a specific font index. This is useful for
|
/// Returns the presentation for a specific font index. This is useful for
|
||||||
/// determining what atlas is needed.
|
/// determining what atlas is needed.
|
||||||
pub fn presentationFromIndex(self: Group, index: FontIndex) !font.Presentation {
|
pub fn presentationFromIndex(self: *Group, index: FontIndex) !font.Presentation {
|
||||||
if (index.special()) |sp| switch (sp) {
|
if (index.special()) |sp| switch (sp) {
|
||||||
.sprite => return .text,
|
.sprite => return .text,
|
||||||
};
|
};
|
||||||
@ -256,12 +311,22 @@ pub fn presentationFromIndex(self: Group, index: FontIndex) !font.Presentation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Return the Face represented by a given FontIndex. Note that special
|
/// Return the Face represented by a given FontIndex. Note that special
|
||||||
/// fonts (i.e. box glyphs) do not have a face.
|
/// fonts (i.e. box glyphs) do not have a face. The returned face pointer is
|
||||||
pub fn faceFromIndex(self: Group, index: FontIndex) !*Face {
|
/// only valid until the set of faces change.
|
||||||
|
pub fn faceFromIndex(self: *Group, index: FontIndex) !*Face {
|
||||||
if (index.special() != null) return error.SpecialHasNoFace;
|
if (index.special() != null) return error.SpecialHasNoFace;
|
||||||
const deferred = &self.faces.get(index.style).items[@intCast(index.idx)];
|
const list = self.faces.getPtr(index.style);
|
||||||
try deferred.load(self.lib, self.size);
|
const item = &list.items[@intCast(index.idx)];
|
||||||
return &deferred.face.?;
|
return switch (item.*) {
|
||||||
|
.deferred => |*d| deferred: {
|
||||||
|
const face = try d.load(self.lib, self.size);
|
||||||
|
d.deinit();
|
||||||
|
item.* = .{ .loaded = face };
|
||||||
|
break :deferred &item.loaded;
|
||||||
|
},
|
||||||
|
|
||||||
|
.loaded => |*f| f,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Render a glyph by glyph index into the given font atlas and return
|
/// Render a glyph by glyph index into the given font atlas and return
|
||||||
@ -276,7 +341,7 @@ pub fn faceFromIndex(self: Group, index: FontIndex) !*Face {
|
|||||||
/// doing text shaping, the text shaping library (i.e. HarfBuzz) will automatically
|
/// doing text shaping, the text shaping library (i.e. HarfBuzz) will automatically
|
||||||
/// determine glyph indexes for a text run.
|
/// determine glyph indexes for a text run.
|
||||||
pub fn renderGlyph(
|
pub fn renderGlyph(
|
||||||
self: Group,
|
self: *Group,
|
||||||
alloc: Allocator,
|
alloc: Allocator,
|
||||||
atlas: *font.Atlas,
|
atlas: *font.Atlas,
|
||||||
index: FontIndex,
|
index: FontIndex,
|
||||||
@ -292,9 +357,8 @@ pub fn renderGlyph(
|
|||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
const face = &self.faces.get(index.style).items[@intCast(index.idx)];
|
const face = try self.faceFromIndex(index);
|
||||||
try face.load(self.lib, self.size);
|
const glyph = try face.renderGlyph(alloc, atlas, glyph_index, opts);
|
||||||
const glyph = try face.face.?.renderGlyph(alloc, atlas, glyph_index, opts);
|
|
||||||
// log.warn("GLYPH={}", .{glyph});
|
// log.warn("GLYPH={}", .{glyph});
|
||||||
return glyph;
|
return glyph;
|
||||||
}
|
}
|
||||||
@ -349,7 +413,7 @@ pub const Wasm = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export fn group_add_face(self: *Group, style: u16, face: *font.DeferredFace) void {
|
export fn group_add_face(self: *Group, style: u16, face: *font.DeferredFace) void {
|
||||||
return self.addFace(alloc, @enumFromInt(style), face.*) catch |err| {
|
return self.addFace(@enumFromInt(style), face.*) catch |err| {
|
||||||
log.warn("error adding face to group err={}", .{err});
|
log.warn("error adding face to group err={}", .{err});
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@ -422,13 +486,13 @@ test {
|
|||||||
var group = try init(alloc, lib, .{ .points = 12 });
|
var group = try init(alloc, lib, .{ .points = 12 });
|
||||||
defer group.deinit();
|
defer group.deinit();
|
||||||
|
|
||||||
try group.addFace(alloc, .regular, DeferredFace.initLoaded(try Face.init(lib, testFont, .{ .points = 12 })));
|
try group.addFace(.regular, .{ .loaded = try Face.init(lib, testFont, .{ .points = 12 }) });
|
||||||
|
|
||||||
if (font.options.backend != .coretext) {
|
if (font.options.backend != .coretext) {
|
||||||
// Coretext doesn't support Noto's format
|
// Coretext doesn't support Noto's format
|
||||||
try group.addFace(alloc, .regular, DeferredFace.initLoaded(try Face.init(lib, testEmoji, .{ .points = 12 })));
|
try group.addFace(.regular, .{ .loaded = try Face.init(lib, testEmoji, .{ .points = 12 }) });
|
||||||
}
|
}
|
||||||
try group.addFace(alloc, .regular, DeferredFace.initLoaded(try Face.init(lib, testEmojiText, .{ .points = 12 })));
|
try group.addFace(.regular, .{ .loaded = try Face.init(lib, testEmojiText, .{ .points = 12 }) });
|
||||||
|
|
||||||
// Should find all visible ASCII
|
// Should find all visible ASCII
|
||||||
var i: u32 = 32;
|
var i: u32 = 32;
|
||||||
@ -521,7 +585,7 @@ test "resize" {
|
|||||||
var group = try init(alloc, lib, .{ .points = 12, .xdpi = 96, .ydpi = 96 });
|
var group = try init(alloc, lib, .{ .points = 12, .xdpi = 96, .ydpi = 96 });
|
||||||
defer group.deinit();
|
defer group.deinit();
|
||||||
|
|
||||||
try group.addFace(alloc, .regular, DeferredFace.initLoaded(try Face.init(lib, testFont, .{ .points = 12, .xdpi = 96, .ydpi = 96 })));
|
try group.addFace(.regular, .{ .loaded = try Face.init(lib, testFont, .{ .points = 12, .xdpi = 96, .ydpi = 96 }) });
|
||||||
|
|
||||||
// Load a letter
|
// Load a letter
|
||||||
{
|
{
|
||||||
@ -574,7 +638,7 @@ test "discover monospace with fontconfig and freetype" {
|
|||||||
defer lib.deinit();
|
defer lib.deinit();
|
||||||
var group = try init(alloc, lib, .{ .points = 12 });
|
var group = try init(alloc, lib, .{ .points = 12 });
|
||||||
defer group.deinit();
|
defer group.deinit();
|
||||||
try group.addFace(alloc, .regular, (try it.next()).?);
|
try group.addFace(.regular, .{ .deferred = (try it.next()).? });
|
||||||
|
|
||||||
// Should find all visible ASCII
|
// Should find all visible ASCII
|
||||||
var atlas_greyscale = try font.Atlas.init(alloc, 512, .greyscale);
|
var atlas_greyscale = try font.Atlas.init(alloc, 512, .greyscale);
|
||||||
@ -612,7 +676,7 @@ test "faceFromIndex returns pointer" {
|
|||||||
var group = try init(alloc, lib, .{ .points = 12, .xdpi = 96, .ydpi = 96 });
|
var group = try init(alloc, lib, .{ .points = 12, .xdpi = 96, .ydpi = 96 });
|
||||||
defer group.deinit();
|
defer group.deinit();
|
||||||
|
|
||||||
try group.addFace(alloc, .regular, DeferredFace.initLoaded(try Face.init(lib, testFont, .{ .points = 12, .xdpi = 96, .ydpi = 96 })));
|
try group.addFace(.regular, .{ .loaded = try Face.init(lib, testFont, .{ .points = 12, .xdpi = 96, .ydpi = 96 }) });
|
||||||
|
|
||||||
{
|
{
|
||||||
const idx = group.indexForCodepoint('A', .regular, null).?;
|
const idx = group.indexForCodepoint('A', .regular, null).?;
|
||||||
|
@ -183,11 +183,10 @@ test {
|
|||||||
|
|
||||||
// Setup group
|
// Setup group
|
||||||
try cache.group.addFace(
|
try cache.group.addFace(
|
||||||
alloc,
|
|
||||||
.regular,
|
.regular,
|
||||||
DeferredFace.initLoaded(try Face.init(lib, testFont, .{ .points = 12 })),
|
.{ .loaded = try Face.init(lib, testFont, .{ .points = 12 }) },
|
||||||
);
|
);
|
||||||
const group = cache.group;
|
var 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;
|
||||||
@ -340,9 +339,8 @@ test "resize" {
|
|||||||
|
|
||||||
// Setup group
|
// Setup group
|
||||||
try cache.group.addFace(
|
try cache.group.addFace(
|
||||||
alloc,
|
|
||||||
.regular,
|
.regular,
|
||||||
DeferredFace.initLoaded(try Face.init(lib, testFont, .{ .points = 12, .xdpi = 96, .ydpi = 96 })),
|
.{ .loaded = try Face.init(lib, testFont, .{ .points = 12, .xdpi = 96, .ydpi = 96 }) },
|
||||||
);
|
);
|
||||||
|
|
||||||
// Load a letter
|
// Load a letter
|
||||||
|
@ -217,7 +217,6 @@ pub const Fontconfig = struct {
|
|||||||
defer self.i += 1;
|
defer self.i += 1;
|
||||||
|
|
||||||
return DeferredFace{
|
return DeferredFace{
|
||||||
.face = null,
|
|
||||||
.fc = .{
|
.fc = .{
|
||||||
.pattern = font_pattern,
|
.pattern = font_pattern,
|
||||||
.charset = (try font_pattern.get(.charset, 0)).char_set,
|
.charset = (try font_pattern.get(.charset, 0)).char_set,
|
||||||
@ -301,7 +300,6 @@ pub const CoreText = struct {
|
|||||||
defer self.i += 1;
|
defer self.i += 1;
|
||||||
|
|
||||||
return DeferredFace{
|
return DeferredFace{
|
||||||
.face = null,
|
|
||||||
.ct = .{ .font = font },
|
.ct = .{ .font = font },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -311,14 +309,9 @@ pub const CoreText = struct {
|
|||||||
test "fontconfig" {
|
test "fontconfig" {
|
||||||
if (options.backend != .fontconfig_freetype) return error.SkipZigTest;
|
if (options.backend != .fontconfig_freetype) return error.SkipZigTest;
|
||||||
|
|
||||||
const testing = std.testing;
|
|
||||||
|
|
||||||
var fc = Fontconfig.init();
|
var fc = Fontconfig.init();
|
||||||
var it = try fc.discover(.{ .family = "monospace", .size = 12 });
|
var it = try fc.discover(.{ .family = "monospace", .size = 12 });
|
||||||
defer it.deinit();
|
defer it.deinit();
|
||||||
while (try it.next()) |face| {
|
|
||||||
try testing.expect(!face.loaded());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
test "fontconfig codepoint" {
|
test "fontconfig codepoint" {
|
||||||
@ -333,7 +326,6 @@ test "fontconfig codepoint" {
|
|||||||
// The first result should have the codepoint. Later ones may not
|
// The first result should have the codepoint. Later ones may not
|
||||||
// because fontconfig returns all fonts sorted.
|
// because fontconfig returns all fonts sorted.
|
||||||
const face = (try it.next()).?;
|
const face = (try it.next()).?;
|
||||||
try testing.expect(!face.loaded());
|
|
||||||
try testing.expect(face.hasCodepoint('A', null));
|
try testing.expect(face.hasCodepoint('A', null));
|
||||||
|
|
||||||
// Should have other codepoints too
|
// Should have other codepoints too
|
||||||
@ -351,9 +343,8 @@ test "coretext" {
|
|||||||
var it = try ct.discover(.{ .family = "Monaco", .size = 12 });
|
var it = try ct.discover(.{ .family = "Monaco", .size = 12 });
|
||||||
defer it.deinit();
|
defer it.deinit();
|
||||||
var count: usize = 0;
|
var count: usize = 0;
|
||||||
while (try it.next()) |face| {
|
while (try it.next()) |_| {
|
||||||
count += 1;
|
count += 1;
|
||||||
try testing.expect(!face.loaded());
|
|
||||||
}
|
}
|
||||||
try testing.expect(count > 0);
|
try testing.expect(count > 0);
|
||||||
}
|
}
|
||||||
@ -372,7 +363,6 @@ test "coretext codepoint" {
|
|||||||
// The first result should have the codepoint. Later ones may not
|
// The first result should have the codepoint. Later ones may not
|
||||||
// because fontconfig returns all fonts sorted.
|
// because fontconfig returns all fonts sorted.
|
||||||
const face = (try it.next()).?;
|
const face = (try it.next()).?;
|
||||||
try testing.expect(!face.loaded());
|
|
||||||
try testing.expect(face.hasCodepoint('A', null));
|
try testing.expect(face.hasCodepoint('A', null));
|
||||||
|
|
||||||
// Should have other codepoints too
|
// Should have other codepoints too
|
||||||
|
@ -4,6 +4,7 @@ const Allocator = std.mem.Allocator;
|
|||||||
const macos = @import("macos");
|
const macos = @import("macos");
|
||||||
const harfbuzz = @import("harfbuzz");
|
const harfbuzz = @import("harfbuzz");
|
||||||
const font = @import("../main.zig");
|
const font = @import("../main.zig");
|
||||||
|
const quirks = @import("../../quirks.zig");
|
||||||
|
|
||||||
const log = std.log.scoped(.font_face);
|
const log = std.log.scoped(.font_face);
|
||||||
|
|
||||||
@ -20,6 +21,9 @@ pub const Face = struct {
|
|||||||
/// Metrics for this font face. These are useful for renderers.
|
/// Metrics for this font face. These are useful for renderers.
|
||||||
metrics: font.face.Metrics,
|
metrics: font.face.Metrics,
|
||||||
|
|
||||||
|
/// Set quirks.disableDefaultFontFeatures
|
||||||
|
quirks_disable_default_font_features: bool = false,
|
||||||
|
|
||||||
/// The matrix applied to a regular font to auto-italicize it.
|
/// The matrix applied to a regular font to auto-italicize it.
|
||||||
pub const italic_skew = macos.graphics.AffineTransform{
|
pub const italic_skew = macos.graphics.AffineTransform{
|
||||||
.a = 1,
|
.a = 1,
|
||||||
@ -65,12 +69,14 @@ pub const Face = struct {
|
|||||||
|
|
||||||
const traits = ct_font.getSymbolicTraits();
|
const traits = ct_font.getSymbolicTraits();
|
||||||
|
|
||||||
return Face{
|
var result: Face = .{
|
||||||
.font = ct_font,
|
.font = ct_font,
|
||||||
.hb_font = hb_font,
|
.hb_font = hb_font,
|
||||||
.presentation = if (traits.color_glyphs) .emoji else .text,
|
.presentation = if (traits.color_glyphs) .emoji else .text,
|
||||||
.metrics = try calcMetrics(ct_font),
|
.metrics = try calcMetrics(ct_font),
|
||||||
};
|
};
|
||||||
|
result.quirks_disable_default_font_features = quirks.disableDefaultFontFeatures(&result);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Face) void {
|
pub fn deinit(self: *Face) void {
|
||||||
|
@ -18,6 +18,7 @@ const Library = font.Library;
|
|||||||
const Presentation = font.Presentation;
|
const Presentation = font.Presentation;
|
||||||
const convert = @import("freetype_convert.zig");
|
const convert = @import("freetype_convert.zig");
|
||||||
const fastmem = @import("../../fastmem.zig");
|
const fastmem = @import("../../fastmem.zig");
|
||||||
|
const quirks = @import("../../quirks.zig");
|
||||||
|
|
||||||
const log = std.log.scoped(.font_face);
|
const log = std.log.scoped(.font_face);
|
||||||
|
|
||||||
@ -35,6 +36,9 @@ pub const Face = struct {
|
|||||||
/// Metrics for this font face. These are useful for renderers.
|
/// Metrics for this font face. These are useful for renderers.
|
||||||
metrics: font.face.Metrics,
|
metrics: font.face.Metrics,
|
||||||
|
|
||||||
|
/// Set quirks.disableDefaultFontFeatures
|
||||||
|
quirks_disable_default_font_features: bool = false,
|
||||||
|
|
||||||
/// Initialize a new font face with the given source in-memory.
|
/// Initialize a new font face with the given source in-memory.
|
||||||
pub fn initFile(lib: Library, path: [:0]const u8, index: i32, size: font.face.DesiredSize) !Face {
|
pub fn initFile(lib: Library, path: [:0]const u8, index: i32, size: font.face.DesiredSize) !Face {
|
||||||
const face = try lib.lib.initFace(path, index);
|
const face = try lib.lib.initFace(path, index);
|
||||||
@ -56,12 +60,14 @@ pub const Face = struct {
|
|||||||
const hb_font = try harfbuzz.freetype.createFont(face.handle);
|
const hb_font = try harfbuzz.freetype.createFont(face.handle);
|
||||||
errdefer hb_font.destroy();
|
errdefer hb_font.destroy();
|
||||||
|
|
||||||
return Face{
|
var result: Face = .{
|
||||||
.face = face,
|
.face = face,
|
||||||
.hb_font = hb_font,
|
.hb_font = hb_font,
|
||||||
.presentation = if (face.hasColor()) .emoji else .text,
|
.presentation = if (face.hasColor()) .emoji else .text,
|
||||||
.metrics = calcMetrics(face),
|
.metrics = calcMetrics(face),
|
||||||
};
|
};
|
||||||
|
result.quirks_disable_default_font_features = quirks.disableDefaultFontFeatures(&result);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Face) void {
|
pub fn deinit(self: *Face) void {
|
||||||
|
@ -12,7 +12,6 @@ const Library = font.Library;
|
|||||||
const Style = font.Style;
|
const Style = font.Style;
|
||||||
const Presentation = font.Presentation;
|
const Presentation = font.Presentation;
|
||||||
const terminal = @import("../../terminal/main.zig");
|
const terminal = @import("../../terminal/main.zig");
|
||||||
const quirks = @import("../../quirks.zig");
|
|
||||||
|
|
||||||
const log = std.log.scoped(.font_shaper);
|
const log = std.log.scoped(.font_shaper);
|
||||||
|
|
||||||
@ -108,7 +107,7 @@ pub const Shaper = struct {
|
|||||||
// fonts, the codepoint == glyph_index so we don't need to run any shaping.
|
// fonts, the codepoint == glyph_index so we don't need to run any shaping.
|
||||||
if (run.font_index.special() == null) {
|
if (run.font_index.special() == null) {
|
||||||
const face = try run.group.group.faceFromIndex(run.font_index);
|
const face = try run.group.group.faceFromIndex(run.font_index);
|
||||||
const i = if (!quirks.disableDefaultFontFeatures(face)) 0 else i: {
|
const i = if (!face.quirks_disable_default_font_features) 0 else i: {
|
||||||
// If we are disabling default font features we just offset
|
// If we are disabling default font features we just offset
|
||||||
// our features by the hardcoded items because always
|
// our features by the hardcoded items because always
|
||||||
// add those at the beginning.
|
// add those at the beginning.
|
||||||
@ -795,12 +794,13 @@ fn testShaper(alloc: Allocator) !TestShaper {
|
|||||||
errdefer cache_ptr.*.deinit(alloc);
|
errdefer cache_ptr.*.deinit(alloc);
|
||||||
|
|
||||||
// Setup group
|
// Setup group
|
||||||
try cache_ptr.group.addFace(alloc, .regular, DeferredFace.initLoaded(try Face.init(lib, testFont, .{ .points = 12 })));
|
try cache_ptr.group.addFace(.regular, .{ .loaded = try Face.init(lib, testFont, .{ .points = 12 }) });
|
||||||
|
|
||||||
if (font.options.backend != .coretext) {
|
if (font.options.backend != .coretext) {
|
||||||
// Coretext doesn't support Noto's format
|
// Coretext doesn't support Noto's format
|
||||||
try cache_ptr.group.addFace(alloc, .regular, DeferredFace.initLoaded(try Face.init(lib, testEmoji, .{ .points = 12 })));
|
try cache_ptr.group.addFace(.regular, .{ .loaded = try Face.init(lib, testEmoji, .{ .points = 12 }) });
|
||||||
}
|
}
|
||||||
try cache_ptr.group.addFace(alloc, .regular, DeferredFace.initLoaded(try Face.init(lib, testEmojiText, .{ .points = 12 })));
|
try cache_ptr.group.addFace(.regular, .{ .loaded = try Face.init(lib, testEmojiText, .{ .points = 12 }) });
|
||||||
|
|
||||||
var cell_buf = try alloc.alloc(font.shape.Cell, 80);
|
var cell_buf = try alloc.alloc(font.shape.Cell, 80);
|
||||||
errdefer alloc.free(cell_buf);
|
errdefer alloc.free(cell_buf);
|
||||||
|
Reference in New Issue
Block a user