mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
refactor(font): move Metrics
from Face
to Collection
(#4739)
This refactor enables two very significant improvements to our font
handling, which I will be implementing next:
1. Automatically adjust size of fallback faces to better align with the
primary face, a la CSS
[`font-size-adjust`](https://developer.mozilla.org/en-US/docs/Web/CSS/font-size-adjust).
[^1]
2. Move glyph resizing/positioning out of GPU-land and in to
`renderGlyph` and apply alignment/resizing rules from the nerd fonts
patcher[^2] to the glyphs at rasterization time, so that we can ensure
exact cell fits and swap out our embedded JB Mono with an unpatched
version with a separate dedicated symbols-only nerd font.
In addition to being necessary prep work for those two changes, this PR
is also a minor but real stand-alone improvement. By only computing the
cell metrics for our primary font, we avoid a *lot* of wasted work when
loading fallback fonts, and also avoid that being a source of load
errors, which we don't yet handle gracefully[^3].
To validate this PR I've run the full set of font backend tests locally
on my Mac with no failures, and did a sanity check of running Ghostty
with both renderers and with CoreText and FreeType font backends and
then `cat`ing a file that requires fallback fonts to render, and
everything looks correct.
[^1]: #3029
[^2]: The alignment and resizing rules for the nerd font symbols are
defined in the patcher
[here](6d0b8ba05a/font-patcher (L866-L1151)
)
[^3]: #2991
This commit is contained in:
@ -32,7 +32,7 @@ const url = @import("url.zig");
|
|||||||
const Key = @import("key.zig").Key;
|
const Key = @import("key.zig").Key;
|
||||||
const KeyValue = @import("key.zig").Value;
|
const KeyValue = @import("key.zig").Value;
|
||||||
const ErrorList = @import("ErrorList.zig");
|
const ErrorList = @import("ErrorList.zig");
|
||||||
const MetricModifier = fontpkg.face.Metrics.Modifier;
|
const MetricModifier = fontpkg.Metrics.Modifier;
|
||||||
const help_strings = @import("help_strings");
|
const help_strings = @import("help_strings");
|
||||||
|
|
||||||
const log = std.log.scoped(.config);
|
const log = std.log.scoped(.config);
|
||||||
|
@ -25,7 +25,7 @@ const DeferredFace = font.DeferredFace;
|
|||||||
const DesiredSize = font.face.DesiredSize;
|
const DesiredSize = font.face.DesiredSize;
|
||||||
const Face = font.Face;
|
const Face = font.Face;
|
||||||
const Library = font.Library;
|
const Library = font.Library;
|
||||||
const Metrics = font.face.Metrics;
|
const Metrics = font.Metrics;
|
||||||
const Presentation = font.Presentation;
|
const Presentation = font.Presentation;
|
||||||
const Style = font.Style;
|
const Style = font.Style;
|
||||||
|
|
||||||
@ -35,6 +35,17 @@ const log = std.log.scoped(.font_collection);
|
|||||||
/// Instead, use the functions available on Collection.
|
/// Instead, use the functions available on Collection.
|
||||||
faces: StyleArray,
|
faces: StyleArray,
|
||||||
|
|
||||||
|
/// The metric modifiers to use for this collection. The memory
|
||||||
|
/// for this is owned by the user and is not freed by the collection.
|
||||||
|
///
|
||||||
|
/// Call `Collection.updateMetrics` to recompute the
|
||||||
|
/// collection's metrics after making changes to these.
|
||||||
|
metric_modifiers: Metrics.ModifierSet = .{},
|
||||||
|
|
||||||
|
/// Metrics for this collection. Call `Collection.updateMetrics` to (re)compute
|
||||||
|
/// these after adding a primary font or making changes to `metric_modifiers`.
|
||||||
|
metrics: ?Metrics = null,
|
||||||
|
|
||||||
/// The load options for deferred faces in the face list. If this
|
/// The load options for deferred faces in the face list. If this
|
||||||
/// is not set, then deferred faces will not be loaded. Attempting to
|
/// is not set, then deferred faces will not be loaded. Attempting to
|
||||||
/// add a deferred face will result in an error.
|
/// add a deferred face will result in an error.
|
||||||
@ -421,6 +432,28 @@ pub fn setSize(self: *Collection, size: DesiredSize) !void {
|
|||||||
.alias => continue,
|
.alias => continue,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try self.updateMetrics();
|
||||||
|
}
|
||||||
|
|
||||||
|
const UpdateMetricsError = font.Face.GetMetricsError || error{
|
||||||
|
CannotLoadPrimaryFont,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Update the cell metrics for this collection, based on
|
||||||
|
/// the primary font and the modifiers in `metric_modifiers`.
|
||||||
|
///
|
||||||
|
/// This requires a primary font (index `0`) to be present.
|
||||||
|
pub fn updateMetrics(self: *Collection) UpdateMetricsError!void {
|
||||||
|
const primary_face = self.getFace(.{ .idx = 0 }) catch return error.CannotLoadPrimaryFont;
|
||||||
|
|
||||||
|
const face_metrics = try primary_face.getMetrics();
|
||||||
|
|
||||||
|
var metrics = Metrics.calc(face_metrics);
|
||||||
|
|
||||||
|
metrics.apply(self.metric_modifiers);
|
||||||
|
|
||||||
|
self.metrics = metrics;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Packed array of all Style enum cases mapped to a growable list of faces.
|
/// Packed array of all Style enum cases mapped to a growable list of faces.
|
||||||
@ -448,10 +481,6 @@ pub const LoadOptions = struct {
|
|||||||
/// The desired font size for all loaded faces.
|
/// The desired font size for all loaded faces.
|
||||||
size: DesiredSize = .{ .points = 12 },
|
size: DesiredSize = .{ .points = 12 },
|
||||||
|
|
||||||
/// The metric modifiers to use for all loaded faces. The memory
|
|
||||||
/// for this is owned by the user and is not freed by the collection.
|
|
||||||
metric_modifiers: Metrics.ModifierSet = .{},
|
|
||||||
|
|
||||||
/// Freetype Load Flags to use when loading glyphs. This is a list of
|
/// Freetype Load Flags to use when loading glyphs. This is a list of
|
||||||
/// bitfield constants that controls operations to perform during glyph
|
/// bitfield constants that controls operations to perform during glyph
|
||||||
/// loading. Only a subset is exposed for configuration, for the whole set
|
/// loading. Only a subset is exposed for configuration, for the whole set
|
||||||
@ -467,7 +496,6 @@ pub const LoadOptions = struct {
|
|||||||
pub fn faceOptions(self: *const LoadOptions) font.face.Options {
|
pub fn faceOptions(self: *const LoadOptions) font.face.Options {
|
||||||
return .{
|
return .{
|
||||||
.size = self.size,
|
.size = self.size,
|
||||||
.metric_modifiers = &self.metric_modifiers,
|
|
||||||
.freetype_load_flags = self.freetype_load_flags,
|
.freetype_load_flags = self.freetype_load_flags,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -864,3 +892,66 @@ test "hasCodepoint emoji default graphical" {
|
|||||||
try testing.expect(c.hasCodepoint(idx, '🥸', .{ .any = {} }));
|
try testing.expect(c.hasCodepoint(idx, '🥸', .{ .any = {} }));
|
||||||
// TODO(fontmem): test explicit/implicit
|
// TODO(fontmem): test explicit/implicit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "metrics" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
const testFont = font.embedded.inconsolata;
|
||||||
|
|
||||||
|
var lib = try Library.init();
|
||||||
|
defer lib.deinit();
|
||||||
|
|
||||||
|
var c = init();
|
||||||
|
defer c.deinit(alloc);
|
||||||
|
c.load_options = .{ .library = lib };
|
||||||
|
|
||||||
|
_ = try c.add(alloc, .regular, .{ .loaded = try Face.init(
|
||||||
|
lib,
|
||||||
|
testFont,
|
||||||
|
.{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } },
|
||||||
|
) });
|
||||||
|
|
||||||
|
try c.updateMetrics();
|
||||||
|
|
||||||
|
try std.testing.expectEqual(font.Metrics{
|
||||||
|
.cell_width = 8,
|
||||||
|
// The cell height is 17 px because the calculation is
|
||||||
|
//
|
||||||
|
// ascender - descender + gap
|
||||||
|
//
|
||||||
|
// which, for inconsolata is
|
||||||
|
//
|
||||||
|
// 859 - -190 + 0
|
||||||
|
//
|
||||||
|
// font units, at 1000 units per em that works out to 1.049 em,
|
||||||
|
// and 1em should be the point size * dpi scale, so 12 * (96/72)
|
||||||
|
// which is 16, and 16 * 1.049 = 16.784, which finally is rounded
|
||||||
|
// to 17.
|
||||||
|
.cell_height = 17,
|
||||||
|
.cell_baseline = 3,
|
||||||
|
.underline_position = 17,
|
||||||
|
.underline_thickness = 1,
|
||||||
|
.strikethrough_position = 10,
|
||||||
|
.strikethrough_thickness = 1,
|
||||||
|
.overline_position = 0,
|
||||||
|
.overline_thickness = 1,
|
||||||
|
.box_thickness = 1,
|
||||||
|
.cursor_height = 17,
|
||||||
|
}, c.metrics);
|
||||||
|
|
||||||
|
// Resize should change metrics
|
||||||
|
try c.setSize(.{ .points = 24, .xdpi = 96, .ydpi = 96 });
|
||||||
|
try std.testing.expectEqual(font.Metrics{
|
||||||
|
.cell_width = 16,
|
||||||
|
.cell_height = 34,
|
||||||
|
.cell_baseline = 6,
|
||||||
|
.underline_position = 34,
|
||||||
|
.underline_thickness = 2,
|
||||||
|
.strikethrough_position = 19,
|
||||||
|
.strikethrough_thickness = 2,
|
||||||
|
.overline_position = 0,
|
||||||
|
.overline_thickness = 2,
|
||||||
|
.box_thickness = 2,
|
||||||
|
.cursor_height = 34,
|
||||||
|
}, c.metrics);
|
||||||
|
}
|
||||||
|
@ -52,7 +52,12 @@ const Minimums = struct {
|
|||||||
const cursor_height = 1;
|
const cursor_height = 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
const CalcOpts = struct {
|
/// Metrics extracted from a font face, based on
|
||||||
|
/// the metadata tables and glyph measurements.
|
||||||
|
pub const FaceMetrics = struct {
|
||||||
|
/// The minimum cell width that can contain any glyph in the ASCII range.
|
||||||
|
///
|
||||||
|
/// Determined by measuring all printable glyphs in the ASCII range.
|
||||||
cell_width: f64,
|
cell_width: f64,
|
||||||
|
|
||||||
/// The typographic ascent metric from the font.
|
/// The typographic ascent metric from the font.
|
||||||
@ -110,45 +115,45 @@ const CalcOpts = struct {
|
|||||||
/// do not round them before using them for this function.
|
/// do not round them before using them for this function.
|
||||||
///
|
///
|
||||||
/// For any nullable options that are not provided, estimates will be used.
|
/// For any nullable options that are not provided, estimates will be used.
|
||||||
pub fn calc(opts: CalcOpts) Metrics {
|
pub fn calc(face: FaceMetrics) Metrics {
|
||||||
// We use the ceiling of the provided cell width and height to ensure
|
// We use the ceiling of the provided cell width and height to ensure
|
||||||
// that the cell is large enough for the provided size, since we cast
|
// that the cell is large enough for the provided size, since we cast
|
||||||
// it to an integer later.
|
// it to an integer later.
|
||||||
const cell_width = @ceil(opts.cell_width);
|
const cell_width = @ceil(face.cell_width);
|
||||||
const cell_height = @ceil(opts.ascent - opts.descent + opts.line_gap);
|
const cell_height = @ceil(face.ascent - face.descent + face.line_gap);
|
||||||
|
|
||||||
// We split our line gap in two parts, and put half of it on the top
|
// We split our line gap in two parts, and put half of it on the top
|
||||||
// of the cell and the other half on the bottom, so that our text never
|
// of the cell and the other half on the bottom, so that our text never
|
||||||
// bumps up against either edge of the cell vertically.
|
// bumps up against either edge of the cell vertically.
|
||||||
const half_line_gap = opts.line_gap / 2;
|
const half_line_gap = face.line_gap / 2;
|
||||||
|
|
||||||
// Unlike all our other metrics, `cell_baseline` is relative to the
|
// Unlike all our other metrics, `cell_baseline` is relative to the
|
||||||
// BOTTOM of the cell.
|
// BOTTOM of the cell.
|
||||||
const cell_baseline = @round(half_line_gap - opts.descent);
|
const cell_baseline = @round(half_line_gap - face.descent);
|
||||||
|
|
||||||
// We calculate a top_to_baseline to make following calculations simpler.
|
// We calculate a top_to_baseline to make following calculations simpler.
|
||||||
const top_to_baseline = cell_height - cell_baseline;
|
const top_to_baseline = cell_height - cell_baseline;
|
||||||
|
|
||||||
// If we don't have a provided cap height,
|
// If we don't have a provided cap height,
|
||||||
// we estimate it as 75% of the ascent.
|
// we estimate it as 75% of the ascent.
|
||||||
const cap_height = opts.cap_height orelse opts.ascent * 0.75;
|
const cap_height = face.cap_height orelse face.ascent * 0.75;
|
||||||
|
|
||||||
// If we don't have a provided ex height,
|
// If we don't have a provided ex height,
|
||||||
// we estimate it as 75% of the cap height.
|
// we estimate it as 75% of the cap height.
|
||||||
const ex_height = opts.ex_height orelse cap_height * 0.75;
|
const ex_height = face.ex_height orelse cap_height * 0.75;
|
||||||
|
|
||||||
// If we don't have a provided underline thickness,
|
// If we don't have a provided underline thickness,
|
||||||
// we estimate it as 15% of the ex height.
|
// we estimate it as 15% of the ex height.
|
||||||
const underline_thickness = @max(1, @ceil(opts.underline_thickness orelse 0.15 * ex_height));
|
const underline_thickness = @max(1, @ceil(face.underline_thickness orelse 0.15 * ex_height));
|
||||||
|
|
||||||
// If we don't have a provided strikethrough thickness
|
// If we don't have a provided strikethrough thickness
|
||||||
// then we just use the underline thickness for it.
|
// then we just use the underline thickness for it.
|
||||||
const strikethrough_thickness = @max(1, @ceil(opts.strikethrough_thickness orelse underline_thickness));
|
const strikethrough_thickness = @max(1, @ceil(face.strikethrough_thickness orelse underline_thickness));
|
||||||
|
|
||||||
// If we don't have a provided underline position then
|
// If we don't have a provided underline position then
|
||||||
// we place it 1 underline-thickness below the baseline.
|
// we place it 1 underline-thickness below the baseline.
|
||||||
const underline_position = @round(top_to_baseline -
|
const underline_position = @round(top_to_baseline -
|
||||||
(opts.underline_position orelse
|
(face.underline_position orelse
|
||||||
-underline_thickness));
|
-underline_thickness));
|
||||||
|
|
||||||
// If we don't have a provided strikethrough position
|
// If we don't have a provided strikethrough position
|
||||||
@ -156,7 +161,7 @@ pub fn calc(opts: CalcOpts) Metrics {
|
|||||||
// ex height, so that it's perfectly centered on lower
|
// ex height, so that it's perfectly centered on lower
|
||||||
// case text.
|
// case text.
|
||||||
const strikethrough_position = @round(top_to_baseline -
|
const strikethrough_position = @round(top_to_baseline -
|
||||||
(opts.strikethrough_position orelse
|
(face.strikethrough_position orelse
|
||||||
ex_height * 0.5 + strikethrough_thickness * 0.5));
|
ex_height * 0.5 + strikethrough_thickness * 0.5));
|
||||||
|
|
||||||
var result: Metrics = .{
|
var result: Metrics = .{
|
||||||
@ -355,7 +360,7 @@ pub const Modifier = union(enum) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "formatConfig percent" {
|
test "formatConfig percent" {
|
||||||
const configpkg = @import("../../config.zig");
|
const configpkg = @import("../config.zig");
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
var buf = std.ArrayList(u8).init(testing.allocator);
|
var buf = std.ArrayList(u8).init(testing.allocator);
|
||||||
defer buf.deinit();
|
defer buf.deinit();
|
||||||
@ -366,7 +371,7 @@ pub const Modifier = union(enum) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
test "formatConfig absolute" {
|
test "formatConfig absolute" {
|
||||||
const configpkg = @import("../../config.zig");
|
const configpkg = @import("../config.zig");
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
var buf = std.ArrayList(u8).init(testing.allocator);
|
var buf = std.ArrayList(u8).init(testing.allocator);
|
||||||
defer buf.deinit();
|
defer buf.deinit();
|
@ -29,7 +29,7 @@ const Collection = font.Collection;
|
|||||||
const Face = font.Face;
|
const Face = font.Face;
|
||||||
const Glyph = font.Glyph;
|
const Glyph = font.Glyph;
|
||||||
const Library = font.Library;
|
const Library = font.Library;
|
||||||
const Metrics = font.face.Metrics;
|
const Metrics = font.Metrics;
|
||||||
const Presentation = font.Presentation;
|
const Presentation = font.Presentation;
|
||||||
const Style = font.Style;
|
const Style = font.Style;
|
||||||
const RenderOptions = font.face.RenderOptions;
|
const RenderOptions = font.face.RenderOptions;
|
||||||
@ -111,15 +111,10 @@ pub fn deinit(self: *SharedGrid, alloc: Allocator) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn reloadMetrics(self: *SharedGrid) !void {
|
fn reloadMetrics(self: *SharedGrid) !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 collection = &self.resolver.collection;
|
||||||
const index = collection.getIndex('M', .regular, .{ .any = {} }).?;
|
try collection.updateMetrics();
|
||||||
const face = try collection.getFace(index);
|
|
||||||
self.metrics = face.metrics;
|
self.metrics = collection.metrics.?;
|
||||||
|
|
||||||
// Setup our sprite font.
|
// Setup our sprite font.
|
||||||
self.resolver.sprite = .{ .metrics = self.metrics };
|
self.resolver.sprite = .{ .metrics = self.metrics };
|
||||||
|
@ -20,7 +20,7 @@ const Collection = font.Collection;
|
|||||||
const Discover = font.Discover;
|
const Discover = font.Discover;
|
||||||
const Style = font.Style;
|
const Style = font.Style;
|
||||||
const Library = font.Library;
|
const Library = font.Library;
|
||||||
const Metrics = font.face.Metrics;
|
const Metrics = font.Metrics;
|
||||||
const CodepointMap = font.CodepointMap;
|
const CodepointMap = font.CodepointMap;
|
||||||
const DesiredSize = font.face.DesiredSize;
|
const DesiredSize = font.face.DesiredSize;
|
||||||
const Face = font.Face;
|
const Face = font.Face;
|
||||||
@ -167,13 +167,13 @@ fn collection(
|
|||||||
const load_options: Collection.LoadOptions = .{
|
const load_options: Collection.LoadOptions = .{
|
||||||
.library = self.font_lib,
|
.library = self.font_lib,
|
||||||
.size = size,
|
.size = size,
|
||||||
.metric_modifiers = key.metric_modifiers,
|
|
||||||
.freetype_load_flags = key.freetype_load_flags,
|
.freetype_load_flags = key.freetype_load_flags,
|
||||||
};
|
};
|
||||||
|
|
||||||
var c = Collection.init();
|
var c = Collection.init();
|
||||||
errdefer c.deinit(self.alloc);
|
errdefer c.deinit(self.alloc);
|
||||||
c.load_options = load_options;
|
c.load_options = load_options;
|
||||||
|
c.metric_modifiers = key.metric_modifiers;
|
||||||
|
|
||||||
// Search for fonts
|
// Search for fonts
|
||||||
if (Discover != void) discover: {
|
if (Discover != void) discover: {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
const options = @import("main.zig").options;
|
const options = @import("main.zig").options;
|
||||||
pub const Metrics = @import("face/Metrics.zig");
|
const Metrics = @import("main.zig").Metrics;
|
||||||
const config = @import("../config.zig");
|
const config = @import("../config.zig");
|
||||||
const freetype = @import("face/freetype.zig");
|
const freetype = @import("face/freetype.zig");
|
||||||
const coretext = @import("face/coretext.zig");
|
const coretext = @import("face/coretext.zig");
|
||||||
@ -38,7 +38,6 @@ pub const freetype_load_flags_default = if (FreetypeLoadFlags != void) .{} else
|
|||||||
/// Options for initializing a font face.
|
/// Options for initializing a font face.
|
||||||
pub const Options = struct {
|
pub const Options = struct {
|
||||||
size: DesiredSize,
|
size: DesiredSize,
|
||||||
metric_modifiers: ?*const Metrics.ModifierSet = null,
|
|
||||||
freetype_load_flags: FreetypeLoadFlags = freetype_load_flags_default,
|
freetype_load_flags: FreetypeLoadFlags = freetype_load_flags_default,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -89,7 +88,7 @@ pub const RenderOptions = struct {
|
|||||||
/// the metrics of the primary font face. The grid metrics are used
|
/// the metrics of the primary font face. The grid metrics are used
|
||||||
/// by the font face to better layout the glyph in situations where
|
/// by the font face to better layout the glyph in situations where
|
||||||
/// the font is not exactly the same size as the grid.
|
/// the font is not exactly the same size as the grid.
|
||||||
grid_metrics: ?Metrics = null,
|
grid_metrics: Metrics,
|
||||||
|
|
||||||
/// The number of grid cells this glyph will take up. This can be used
|
/// The number of grid cells this glyph will take up. This can be used
|
||||||
/// optionally by the rasterizer to better layout the glyph.
|
/// optionally by the rasterizer to better layout the glyph.
|
||||||
|
@ -18,9 +18,6 @@ pub const Face = struct {
|
|||||||
/// if we're using Harfbuzz.
|
/// if we're using Harfbuzz.
|
||||||
hb_font: if (harfbuzz_shaper) harfbuzz.Font else void,
|
hb_font: if (harfbuzz_shaper) harfbuzz.Font else void,
|
||||||
|
|
||||||
/// Metrics for this font face. These are useful for renderers.
|
|
||||||
metrics: font.face.Metrics,
|
|
||||||
|
|
||||||
/// Set quirks.disableDefaultFontFeatures
|
/// Set quirks.disableDefaultFontFeatures
|
||||||
quirks_disable_default_font_features: bool = false,
|
quirks_disable_default_font_features: bool = false,
|
||||||
|
|
||||||
@ -87,11 +84,6 @@ pub const Face = struct {
|
|||||||
/// the CTFont. This does NOT copy or retain the CTFont.
|
/// the CTFont. This does NOT copy or retain the CTFont.
|
||||||
pub fn initFont(ct_font: *macos.text.Font, opts: font.face.Options) !Face {
|
pub fn initFont(ct_font: *macos.text.Font, opts: font.face.Options) !Face {
|
||||||
const traits = ct_font.getSymbolicTraits();
|
const traits = ct_font.getSymbolicTraits();
|
||||||
const metrics = metrics: {
|
|
||||||
var metrics = try calcMetrics(ct_font);
|
|
||||||
if (opts.metric_modifiers) |v| metrics.apply(v.*);
|
|
||||||
break :metrics metrics;
|
|
||||||
};
|
|
||||||
|
|
||||||
var hb_font = if (comptime harfbuzz_shaper) font: {
|
var hb_font = if (comptime harfbuzz_shaper) font: {
|
||||||
var hb_font = try harfbuzz.coretext.createFont(ct_font);
|
var hb_font = try harfbuzz.coretext.createFont(ct_font);
|
||||||
@ -109,7 +101,6 @@ pub const Face = struct {
|
|||||||
var result: Face = .{
|
var result: Face = .{
|
||||||
.font = ct_font,
|
.font = ct_font,
|
||||||
.hb_font = hb_font,
|
.hb_font = hb_font,
|
||||||
.metrics = metrics,
|
|
||||||
.color = color,
|
.color = color,
|
||||||
};
|
};
|
||||||
result.quirks_disable_default_font_features = quirks.disableDefaultFontFeatures(&result);
|
result.quirks_disable_default_font_features = quirks.disableDefaultFontFeatures(&result);
|
||||||
@ -463,7 +454,7 @@ pub const Face = struct {
|
|||||||
};
|
};
|
||||||
atlas.set(region, buf);
|
atlas.set(region, buf);
|
||||||
|
|
||||||
const metrics = opts.grid_metrics orelse self.metrics;
|
const metrics = opts.grid_metrics;
|
||||||
|
|
||||||
// This should be the distance from the bottom of
|
// This should be the distance from the bottom of
|
||||||
// the cell to the top of the glyph's bounding box.
|
// the cell to the top of the glyph's bounding box.
|
||||||
@ -506,14 +497,17 @@ pub const Face = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const CalcMetricsError = error{
|
pub const GetMetricsError = error{
|
||||||
CopyTableError,
|
CopyTableError,
|
||||||
InvalidHeadTable,
|
InvalidHeadTable,
|
||||||
InvalidPostTable,
|
InvalidPostTable,
|
||||||
InvalidHheaTable,
|
InvalidHheaTable,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn calcMetrics(ct_font: *macos.text.Font) CalcMetricsError!font.face.Metrics {
|
/// Get the `FaceMetrics` for this face.
|
||||||
|
pub fn getMetrics(self: *Face) GetMetricsError!font.Metrics.FaceMetrics {
|
||||||
|
const ct_font = self.font;
|
||||||
|
|
||||||
// Read the 'head' table out of the font data.
|
// Read the 'head' table out of the font data.
|
||||||
const head: opentype.Head = head: {
|
const head: opentype.Head = head: {
|
||||||
// macOS bitmap-only fonts use a 'bhed' tag rather than 'head', but
|
// macOS bitmap-only fonts use a 'bhed' tag rather than 'head', but
|
||||||
@ -731,7 +725,7 @@ pub const Face = struct {
|
|||||||
break :cell_width max;
|
break :cell_width max;
|
||||||
};
|
};
|
||||||
|
|
||||||
return font.face.Metrics.calc(.{
|
return .{
|
||||||
.cell_width = cell_width,
|
.cell_width = cell_width,
|
||||||
.ascent = ascent,
|
.ascent = ascent,
|
||||||
.descent = descent,
|
.descent = descent,
|
||||||
@ -742,7 +736,7 @@ pub const Face = struct {
|
|||||||
.strikethrough_thickness = strikethrough_thickness,
|
.strikethrough_thickness = strikethrough_thickness,
|
||||||
.cap_height = cap_height,
|
.cap_height = cap_height,
|
||||||
.ex_height = ex_height,
|
.ex_height = ex_height,
|
||||||
});
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Copy the font table data for the given tag.
|
/// Copy the font table data for the given tag.
|
||||||
@ -866,7 +860,12 @@ test {
|
|||||||
var i: u8 = 32;
|
var i: u8 = 32;
|
||||||
while (i < 127) : (i += 1) {
|
while (i < 127) : (i += 1) {
|
||||||
try testing.expect(face.glyphIndex(i) != null);
|
try testing.expect(face.glyphIndex(i) != null);
|
||||||
_ = try face.renderGlyph(alloc, &atlas, face.glyphIndex(i).?, .{});
|
_ = try face.renderGlyph(
|
||||||
|
alloc,
|
||||||
|
&atlas,
|
||||||
|
face.glyphIndex(i).?,
|
||||||
|
.{ .grid_metrics = font.Metrics.calc(try face.getMetrics()) },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -926,7 +925,12 @@ test "in-memory" {
|
|||||||
var i: u8 = 32;
|
var i: u8 = 32;
|
||||||
while (i < 127) : (i += 1) {
|
while (i < 127) : (i += 1) {
|
||||||
try testing.expect(face.glyphIndex(i) != null);
|
try testing.expect(face.glyphIndex(i) != null);
|
||||||
_ = try face.renderGlyph(alloc, &atlas, face.glyphIndex(i).?, .{});
|
_ = try face.renderGlyph(
|
||||||
|
alloc,
|
||||||
|
&atlas,
|
||||||
|
face.glyphIndex(i).?,
|
||||||
|
.{ .grid_metrics = font.Metrics.calc(try face.getMetrics()) },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -948,7 +952,12 @@ test "variable" {
|
|||||||
var i: u8 = 32;
|
var i: u8 = 32;
|
||||||
while (i < 127) : (i += 1) {
|
while (i < 127) : (i += 1) {
|
||||||
try testing.expect(face.glyphIndex(i) != null);
|
try testing.expect(face.glyphIndex(i) != null);
|
||||||
_ = try face.renderGlyph(alloc, &atlas, face.glyphIndex(i).?, .{});
|
_ = try face.renderGlyph(
|
||||||
|
alloc,
|
||||||
|
&atlas,
|
||||||
|
face.glyphIndex(i).?,
|
||||||
|
.{ .grid_metrics = font.Metrics.calc(try face.getMetrics()) },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -974,7 +983,12 @@ test "variable set variation" {
|
|||||||
var i: u8 = 32;
|
var i: u8 = 32;
|
||||||
while (i < 127) : (i += 1) {
|
while (i < 127) : (i += 1) {
|
||||||
try testing.expect(face.glyphIndex(i) != null);
|
try testing.expect(face.glyphIndex(i) != null);
|
||||||
_ = try face.renderGlyph(alloc, &atlas, face.glyphIndex(i).?, .{});
|
_ = try face.renderGlyph(
|
||||||
|
alloc,
|
||||||
|
&atlas,
|
||||||
|
face.glyphIndex(i).?,
|
||||||
|
.{ .grid_metrics = font.Metrics.calc(try face.getMetrics()) },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1017,60 +1031,3 @@ test "glyphIndex colored vs text" {
|
|||||||
try testing.expect(face.isColorGlyph(glyph));
|
try testing.expect(face.isColorGlyph(glyph));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test "coretext: metrics" {
|
|
||||||
const testFont = font.embedded.inconsolata;
|
|
||||||
const alloc = std.testing.allocator;
|
|
||||||
|
|
||||||
var atlas = try font.Atlas.init(alloc, 512, .grayscale);
|
|
||||||
defer atlas.deinit(alloc);
|
|
||||||
|
|
||||||
var ct_font = try Face.init(
|
|
||||||
undefined,
|
|
||||||
testFont,
|
|
||||||
.{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } },
|
|
||||||
);
|
|
||||||
defer ct_font.deinit();
|
|
||||||
|
|
||||||
try std.testing.expectEqual(font.face.Metrics{
|
|
||||||
.cell_width = 8,
|
|
||||||
// The cell height is 17 px because the calculation is
|
|
||||||
//
|
|
||||||
// ascender - descender + gap
|
|
||||||
//
|
|
||||||
// which, for inconsolata is
|
|
||||||
//
|
|
||||||
// 859 - -190 + 0
|
|
||||||
//
|
|
||||||
// font units, at 1000 units per em that works out to 1.049 em,
|
|
||||||
// and 1em should be the point size * dpi scale, so 12 * (96/72)
|
|
||||||
// which is 16, and 16 * 1.049 = 16.784, which finally is rounded
|
|
||||||
// to 17.
|
|
||||||
.cell_height = 17,
|
|
||||||
.cell_baseline = 3,
|
|
||||||
.underline_position = 17,
|
|
||||||
.underline_thickness = 1,
|
|
||||||
.strikethrough_position = 10,
|
|
||||||
.strikethrough_thickness = 1,
|
|
||||||
.overline_position = 0,
|
|
||||||
.overline_thickness = 1,
|
|
||||||
.box_thickness = 1,
|
|
||||||
.cursor_height = 17,
|
|
||||||
}, ct_font.metrics);
|
|
||||||
|
|
||||||
// Resize should change metrics
|
|
||||||
try ct_font.setSize(.{ .size = .{ .points = 24, .xdpi = 96, .ydpi = 96 } });
|
|
||||||
try std.testing.expectEqual(font.face.Metrics{
|
|
||||||
.cell_width = 16,
|
|
||||||
.cell_height = 34,
|
|
||||||
.cell_baseline = 6,
|
|
||||||
.underline_position = 34,
|
|
||||||
.underline_thickness = 2,
|
|
||||||
.strikethrough_position = 19,
|
|
||||||
.strikethrough_thickness = 2,
|
|
||||||
.overline_position = 0,
|
|
||||||
.overline_thickness = 2,
|
|
||||||
.box_thickness = 2,
|
|
||||||
.cursor_height = 34,
|
|
||||||
}, ct_font.metrics);
|
|
||||||
}
|
|
||||||
|
@ -38,9 +38,6 @@ pub const Face = struct {
|
|||||||
/// Harfbuzz font corresponding to this face.
|
/// Harfbuzz font corresponding to this face.
|
||||||
hb_font: harfbuzz.Font,
|
hb_font: harfbuzz.Font,
|
||||||
|
|
||||||
/// Metrics for this font face. These are useful for renderers.
|
|
||||||
metrics: font.face.Metrics,
|
|
||||||
|
|
||||||
/// Freetype load flags for this font face.
|
/// Freetype load flags for this font face.
|
||||||
load_flags: font.face.FreetypeLoadFlags,
|
load_flags: font.face.FreetypeLoadFlags,
|
||||||
|
|
||||||
@ -86,7 +83,6 @@ pub const Face = struct {
|
|||||||
.lib = lib.lib,
|
.lib = lib.lib,
|
||||||
.face = face,
|
.face = face,
|
||||||
.hb_font = hb_font,
|
.hb_font = hb_font,
|
||||||
.metrics = try calcMetrics(face, opts.metric_modifiers),
|
|
||||||
.load_flags = opts.freetype_load_flags,
|
.load_flags = opts.freetype_load_flags,
|
||||||
};
|
};
|
||||||
result.quirks_disable_default_font_features = quirks.disableDefaultFontFeatures(&result);
|
result.quirks_disable_default_font_features = quirks.disableDefaultFontFeatures(&result);
|
||||||
@ -186,7 +182,6 @@ pub const Face = struct {
|
|||||||
/// for clearing any glyph caches, font atlas data, etc.
|
/// for clearing any glyph caches, font atlas data, etc.
|
||||||
pub fn setSize(self: *Face, opts: font.face.Options) !void {
|
pub fn setSize(self: *Face, opts: font.face.Options) !void {
|
||||||
try setSize_(self.face, opts.size);
|
try setSize_(self.face, opts.size);
|
||||||
self.metrics = try calcMetrics(self.face, opts.metric_modifiers);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setSize_(face: freetype.Face, size: font.face.DesiredSize) !void {
|
fn setSize_(face: freetype.Face, size: font.face.DesiredSize) !void {
|
||||||
@ -224,6 +219,8 @@ pub const Face = struct {
|
|||||||
vs: []const font.face.Variation,
|
vs: []const font.face.Variation,
|
||||||
opts: font.face.Options,
|
opts: font.face.Options,
|
||||||
) !void {
|
) !void {
|
||||||
|
_ = opts;
|
||||||
|
|
||||||
// If this font doesn't support variations, we can't do anything.
|
// If this font doesn't support variations, we can't do anything.
|
||||||
if (!self.face.hasMultipleMasters() or vs.len == 0) return;
|
if (!self.face.hasMultipleMasters() or vs.len == 0) return;
|
||||||
|
|
||||||
@ -257,9 +254,6 @@ pub const Face = struct {
|
|||||||
|
|
||||||
// Set them!
|
// Set them!
|
||||||
try self.face.setVarDesignCoordinates(coords);
|
try self.face.setVarDesignCoordinates(coords);
|
||||||
|
|
||||||
// We need to recalculate font metrics which may have changed.
|
|
||||||
self.metrics = try calcMetrics(self.face, opts.metric_modifiers);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the glyph index for the given Unicode code point. If this
|
/// Returns the glyph index for the given Unicode code point. If this
|
||||||
@ -306,7 +300,7 @@ pub const Face = struct {
|
|||||||
glyph_index: u32,
|
glyph_index: u32,
|
||||||
opts: font.face.RenderOptions,
|
opts: font.face.RenderOptions,
|
||||||
) !Glyph {
|
) !Glyph {
|
||||||
const metrics = opts.grid_metrics orelse self.metrics;
|
const metrics = opts.grid_metrics;
|
||||||
|
|
||||||
// If we have synthetic italic, then we apply a transformation matrix.
|
// If we have synthetic italic, then we apply a transformation matrix.
|
||||||
// We have to undo this because synthetic italic works by increasing
|
// We have to undo this because synthetic italic works by increasing
|
||||||
@ -589,23 +583,14 @@ pub const Face = struct {
|
|||||||
return @as(opentype.sfnt.F26Dot6, @bitCast(@as(u32, @intCast(v)))).to(f64);
|
return @as(opentype.sfnt.F26Dot6, @bitCast(@as(u32, @intCast(v)))).to(f64);
|
||||||
}
|
}
|
||||||
|
|
||||||
const CalcMetricsError = error{
|
pub const GetMetricsError = error{
|
||||||
CopyTableError,
|
CopyTableError,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Calculate the metrics associated with a face. This is not public because
|
/// Get the `FaceMetrics` for this face.
|
||||||
/// the metrics are calculated for every face and cached since they're
|
pub fn getMetrics(self: *Face) GetMetricsError!font.Metrics.FaceMetrics {
|
||||||
/// frequently required for renderers and take up next to little memory space
|
const face = self.face;
|
||||||
/// in the grand scheme of things.
|
|
||||||
///
|
|
||||||
/// An aside: the proper way to limit memory usage due to faces is to limit
|
|
||||||
/// the faces with DeferredFaces and reload on demand. A Face can't be converted
|
|
||||||
/// into a DeferredFace but a Face that comes from a DeferredFace can be
|
|
||||||
/// deinitialized anytime and reloaded with the deferred face.
|
|
||||||
fn calcMetrics(
|
|
||||||
face: freetype.Face,
|
|
||||||
modifiers: ?*const font.face.Metrics.ModifierSet,
|
|
||||||
) CalcMetricsError!font.face.Metrics {
|
|
||||||
const size_metrics = face.handle.*.size.*.metrics;
|
const size_metrics = face.handle.*.size.*.metrics;
|
||||||
|
|
||||||
// This code relies on this assumption, and it should always be
|
// This code relies on this assumption, and it should always be
|
||||||
@ -793,7 +778,7 @@ pub const Face = struct {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = font.face.Metrics.calc(.{
|
return .{
|
||||||
.cell_width = cell_width,
|
.cell_width = cell_width,
|
||||||
|
|
||||||
.ascent = ascent,
|
.ascent = ascent,
|
||||||
@ -808,13 +793,7 @@ pub const Face = struct {
|
|||||||
|
|
||||||
.cap_height = cap_height,
|
.cap_height = cap_height,
|
||||||
.ex_height = ex_height,
|
.ex_height = ex_height,
|
||||||
});
|
};
|
||||||
|
|
||||||
if (modifiers) |m| result.apply(m.*);
|
|
||||||
|
|
||||||
// std.log.warn("font metrics={}", .{result});
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Copy the font table data for the given tag.
|
/// Copy the font table data for the given tag.
|
||||||
@ -843,16 +822,31 @@ 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 ft_font.renderGlyph(alloc, &atlas, ft_font.glyphIndex(i).?, .{});
|
_ = try ft_font.renderGlyph(
|
||||||
|
alloc,
|
||||||
|
&atlas,
|
||||||
|
ft_font.glyphIndex(i).?,
|
||||||
|
.{ .grid_metrics = font.Metrics.calc(try ft_font.getMetrics()) },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test resizing
|
// Test resizing
|
||||||
{
|
{
|
||||||
const g1 = try ft_font.renderGlyph(alloc, &atlas, ft_font.glyphIndex('A').?, .{});
|
const g1 = try ft_font.renderGlyph(
|
||||||
|
alloc,
|
||||||
|
&atlas,
|
||||||
|
ft_font.glyphIndex('A').?,
|
||||||
|
.{ .grid_metrics = font.Metrics.calc(try ft_font.getMetrics()) },
|
||||||
|
);
|
||||||
try testing.expectEqual(@as(u32, 11), g1.height);
|
try testing.expectEqual(@as(u32, 11), g1.height);
|
||||||
|
|
||||||
try ft_font.setSize(.{ .size = .{ .points = 24, .xdpi = 96, .ydpi = 96 } });
|
try ft_font.setSize(.{ .size = .{ .points = 24, .xdpi = 96, .ydpi = 96 } });
|
||||||
const g2 = try ft_font.renderGlyph(alloc, &atlas, ft_font.glyphIndex('A').?, .{});
|
const g2 = try ft_font.renderGlyph(
|
||||||
|
alloc,
|
||||||
|
&atlas,
|
||||||
|
ft_font.glyphIndex('A').?,
|
||||||
|
.{ .grid_metrics = font.Metrics.calc(try ft_font.getMetrics()) },
|
||||||
|
);
|
||||||
try testing.expectEqual(@as(u32, 20), g2.height);
|
try testing.expectEqual(@as(u32, 20), g2.height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -874,7 +868,12 @@ test "color emoji" {
|
|||||||
);
|
);
|
||||||
defer ft_font.deinit();
|
defer ft_font.deinit();
|
||||||
|
|
||||||
_ = try ft_font.renderGlyph(alloc, &atlas, ft_font.glyphIndex('🥸').?, .{});
|
_ = try ft_font.renderGlyph(
|
||||||
|
alloc,
|
||||||
|
&atlas,
|
||||||
|
ft_font.glyphIndex('🥸').?,
|
||||||
|
.{ .grid_metrics = font.Metrics.calc(try ft_font.getMetrics()) },
|
||||||
|
);
|
||||||
|
|
||||||
// Make sure this glyph has color
|
// Make sure this glyph has color
|
||||||
{
|
{
|
||||||
@ -885,8 +884,11 @@ test "color emoji" {
|
|||||||
|
|
||||||
// resize
|
// resize
|
||||||
{
|
{
|
||||||
const glyph = try ft_font.renderGlyph(alloc, &atlas, ft_font.glyphIndex('🥸').?, .{
|
const glyph = try ft_font.renderGlyph(
|
||||||
.grid_metrics = .{
|
alloc,
|
||||||
|
&atlas,
|
||||||
|
ft_font.glyphIndex('🥸').?,
|
||||||
|
.{ .grid_metrics = .{
|
||||||
.cell_width = 10,
|
.cell_width = 10,
|
||||||
.cell_height = 24,
|
.cell_height = 24,
|
||||||
.cell_baseline = 0,
|
.cell_baseline = 0,
|
||||||
@ -898,72 +900,12 @@ test "color emoji" {
|
|||||||
.overline_thickness = 0,
|
.overline_thickness = 0,
|
||||||
.box_thickness = 0,
|
.box_thickness = 0,
|
||||||
.cursor_height = 0,
|
.cursor_height = 0,
|
||||||
},
|
} },
|
||||||
});
|
);
|
||||||
try testing.expectEqual(@as(u32, 24), glyph.height);
|
try testing.expectEqual(@as(u32, 24), glyph.height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test "metrics" {
|
|
||||||
const testFont = font.embedded.inconsolata;
|
|
||||||
const alloc = testing.allocator;
|
|
||||||
|
|
||||||
var lib = try Library.init();
|
|
||||||
defer lib.deinit();
|
|
||||||
|
|
||||||
var atlas = try font.Atlas.init(alloc, 512, .grayscale);
|
|
||||||
defer atlas.deinit(alloc);
|
|
||||||
|
|
||||||
var ft_font = try Face.init(
|
|
||||||
lib,
|
|
||||||
testFont,
|
|
||||||
.{ .size = .{ .points = 12, .xdpi = 96, .ydpi = 96 } },
|
|
||||||
);
|
|
||||||
defer ft_font.deinit();
|
|
||||||
|
|
||||||
try testing.expectEqual(font.face.Metrics{
|
|
||||||
.cell_width = 8,
|
|
||||||
// The cell height is 17 px because the calculation is
|
|
||||||
//
|
|
||||||
// ascender - descender + gap
|
|
||||||
//
|
|
||||||
// which, for inconsolata is
|
|
||||||
//
|
|
||||||
// 859 - -190 + 0
|
|
||||||
//
|
|
||||||
// font units, at 1000 units per em that works out to 1.049 em,
|
|
||||||
// and 1em should be the point size * dpi scale, so 12 * (96/72)
|
|
||||||
// which is 16, and 16 * 1.049 = 16.784, which finally is rounded
|
|
||||||
// to 17.
|
|
||||||
.cell_height = 17,
|
|
||||||
.cell_baseline = 3,
|
|
||||||
.underline_position = 17,
|
|
||||||
.underline_thickness = 1,
|
|
||||||
.strikethrough_position = 10,
|
|
||||||
.strikethrough_thickness = 1,
|
|
||||||
.overline_position = 0,
|
|
||||||
.overline_thickness = 1,
|
|
||||||
.box_thickness = 1,
|
|
||||||
.cursor_height = 17,
|
|
||||||
}, ft_font.metrics);
|
|
||||||
|
|
||||||
// Resize should change metrics
|
|
||||||
try ft_font.setSize(.{ .size = .{ .points = 24, .xdpi = 96, .ydpi = 96 } });
|
|
||||||
try testing.expectEqual(font.face.Metrics{
|
|
||||||
.cell_width = 16,
|
|
||||||
.cell_height = 34,
|
|
||||||
.cell_baseline = 6,
|
|
||||||
.underline_position = 34,
|
|
||||||
.underline_thickness = 2,
|
|
||||||
.strikethrough_position = 19,
|
|
||||||
.strikethrough_thickness = 2,
|
|
||||||
.overline_position = 0,
|
|
||||||
.overline_thickness = 2,
|
|
||||||
.box_thickness = 2,
|
|
||||||
.cursor_height = 34,
|
|
||||||
}, ft_font.metrics);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "mono to rgba" {
|
test "mono to rgba" {
|
||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
const testFont = font.embedded.emoji;
|
const testFont = font.embedded.emoji;
|
||||||
@ -974,11 +916,16 @@ test "mono to rgba" {
|
|||||||
var atlas = try font.Atlas.init(alloc, 512, .rgba);
|
var atlas = try font.Atlas.init(alloc, 512, .rgba);
|
||||||
defer atlas.deinit(alloc);
|
defer atlas.deinit(alloc);
|
||||||
|
|
||||||
var ft_font = try Face.init(lib, testFont, .{ .size = .{ .points = 12 } });
|
var ft_font = try Face.init(lib, testFont, .{ .size = .{ .points = 12, .xdpi = 72, .ydpi = 72 } });
|
||||||
defer ft_font.deinit();
|
defer ft_font.deinit();
|
||||||
|
|
||||||
// glyph 3 is mono in Noto
|
// glyph 3 is mono in Noto
|
||||||
_ = try ft_font.renderGlyph(alloc, &atlas, 3, .{});
|
_ = try ft_font.renderGlyph(
|
||||||
|
alloc,
|
||||||
|
&atlas,
|
||||||
|
3,
|
||||||
|
.{ .grid_metrics = font.Metrics.calc(try ft_font.getMetrics()) },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "svg font table" {
|
test "svg font table" {
|
||||||
@ -988,7 +935,7 @@ test "svg font table" {
|
|||||||
var lib = try font.Library.init();
|
var lib = try font.Library.init();
|
||||||
defer lib.deinit();
|
defer lib.deinit();
|
||||||
|
|
||||||
var face = try Face.init(lib, testFont, .{ .size = .{ .points = 12 } });
|
var face = try Face.init(lib, testFont, .{ .size = .{ .points = 12, .xdpi = 72, .ydpi = 72 } });
|
||||||
defer face.deinit();
|
defer face.deinit();
|
||||||
|
|
||||||
const table = (try face.copyTable(alloc, "SVG ")).?;
|
const table = (try face.copyTable(alloc, "SVG ")).?;
|
||||||
@ -1037,7 +984,12 @@ test "bitmap glyph" {
|
|||||||
defer ft_font.deinit();
|
defer ft_font.deinit();
|
||||||
|
|
||||||
// glyph 77 = 'i'
|
// glyph 77 = 'i'
|
||||||
const glyph = try ft_font.renderGlyph(alloc, &atlas, 77, .{});
|
const glyph = try ft_font.renderGlyph(
|
||||||
|
alloc,
|
||||||
|
&atlas,
|
||||||
|
77,
|
||||||
|
.{ .grid_metrics = font.Metrics.calc(try ft_font.getMetrics()) },
|
||||||
|
);
|
||||||
|
|
||||||
// should render crisp
|
// should render crisp
|
||||||
try testing.expectEqual(8, glyph.width);
|
try testing.expectEqual(8, glyph.width);
|
||||||
|
@ -27,7 +27,7 @@ pub const Face = struct {
|
|||||||
presentation: font.Presentation,
|
presentation: font.Presentation,
|
||||||
|
|
||||||
/// 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.Metrics,
|
||||||
|
|
||||||
/// The canvas element that we will reuse to render glyphs
|
/// The canvas element that we will reuse to render glyphs
|
||||||
canvas: js.Object,
|
canvas: js.Object,
|
||||||
@ -273,7 +273,7 @@ pub const Face = struct {
|
|||||||
const underline_position = cell_height - 1;
|
const underline_position = cell_height - 1;
|
||||||
const underline_thickness: f32 = 1;
|
const underline_thickness: f32 = 1;
|
||||||
|
|
||||||
const result = font.face.Metrics{
|
const result = font.Metrics{
|
||||||
.cell_width = @intFromFloat(cell_width),
|
.cell_width = @intFromFloat(cell_width),
|
||||||
.cell_height = @intFromFloat(cell_height),
|
.cell_height = @intFromFloat(cell_height),
|
||||||
.cell_baseline = @intFromFloat(cell_baseline),
|
.cell_baseline = @intFromFloat(cell_baseline),
|
||||||
|
@ -14,7 +14,7 @@ pub const Collection = @import("Collection.zig");
|
|||||||
pub const DeferredFace = @import("DeferredFace.zig");
|
pub const DeferredFace = @import("DeferredFace.zig");
|
||||||
pub const Face = face.Face;
|
pub const Face = face.Face;
|
||||||
pub const Glyph = @import("Glyph.zig");
|
pub const Glyph = @import("Glyph.zig");
|
||||||
pub const Metrics = face.Metrics;
|
pub const Metrics = @import("Metrics.zig");
|
||||||
pub const opentype = @import("opentype.zig");
|
pub const opentype = @import("opentype.zig");
|
||||||
pub const shape = @import("shape.zig");
|
pub const shape = @import("shape.zig");
|
||||||
pub const Shaper = shape.Shaper;
|
pub const Shaper = shape.Shaper;
|
||||||
|
@ -52,7 +52,7 @@ pub fn renderGlyph(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const metrics = opts.grid_metrics orelse self.metrics;
|
const metrics = self.metrics;
|
||||||
|
|
||||||
// We adjust our sprite width based on the cell width.
|
// We adjust our sprite width based on the cell width.
|
||||||
const width = switch (opts.cell_width orelse 1) {
|
const width = switch (opts.cell_width orelse 1) {
|
||||||
|
@ -68,7 +68,7 @@ config: DerivedConfig,
|
|||||||
surface_mailbox: apprt.surface.Mailbox,
|
surface_mailbox: apprt.surface.Mailbox,
|
||||||
|
|
||||||
/// Current font metrics defining our grid.
|
/// Current font metrics defining our grid.
|
||||||
grid_metrics: font.face.Metrics,
|
grid_metrics: font.Metrics,
|
||||||
|
|
||||||
/// The size of everything.
|
/// The size of everything.
|
||||||
size: renderer.Size,
|
size: renderer.Size,
|
||||||
|
@ -49,7 +49,7 @@ alloc: std.mem.Allocator,
|
|||||||
config: DerivedConfig,
|
config: DerivedConfig,
|
||||||
|
|
||||||
/// Current font metrics defining our grid.
|
/// Current font metrics defining our grid.
|
||||||
grid_metrics: font.face.Metrics,
|
grid_metrics: font.Metrics,
|
||||||
|
|
||||||
/// The size of everything.
|
/// The size of everything.
|
||||||
size: renderer.Size,
|
size: renderer.Size,
|
||||||
@ -231,7 +231,7 @@ const SetScreenSize = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const SetFontSize = struct {
|
const SetFontSize = struct {
|
||||||
metrics: font.face.Metrics,
|
metrics: font.Metrics,
|
||||||
|
|
||||||
fn apply(self: SetFontSize, r: *const OpenGL) !void {
|
fn apply(self: SetFontSize, r: *const OpenGL) !void {
|
||||||
const gl_state = r.gl_state orelse return error.OpenGLUninitialized;
|
const gl_state = r.gl_state orelse return error.OpenGLUninitialized;
|
||||||
|
Reference in New Issue
Block a user