aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/font/face.zig
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-07-16 23:28:05 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-07-16 23:29:28 +0300
commit4a3302d19a2baa3a33e910e0088c24e780905b90 (patch)
treedcaf76fae1c82102d933c9225b17ca7ac5d3a847 /vendor/libghostty-vt/src/font/face.zig
parent550e9f8cc085f428429f5cf2da0bb2dba897e8b8 (diff)
chore: update vendored libghostty-vt
refs #276
Diffstat (limited to 'vendor/libghostty-vt/src/font/face.zig')
-rw-r--r--vendor/libghostty-vt/src/font/face.zig596
1 files changed, 0 insertions, 596 deletions
diff --git a/vendor/libghostty-vt/src/font/face.zig b/vendor/libghostty-vt/src/font/face.zig
index d77253ad..aeb7280c 100644
--- a/vendor/libghostty-vt/src/font/face.zig
+++ b/vendor/libghostty-vt/src/font/face.zig
@@ -2,7 +2,6 @@ const std = @import("std");
const builtin = @import("builtin");
const build_config = @import("../build_config.zig");
const options = @import("main.zig").options;
-const Metrics = @import("main.zig").Metrics;
const config = @import("../config.zig");
const freetype = @import("face/freetype.zig");
const coretext = @import("face/coretext.zig");
@@ -94,407 +93,6 @@ pub const Variation = struct {
};
};
-/// The size and position of a glyph.
-pub const GlyphSize = struct {
- width: f64,
- height: f64,
- x: f64,
- y: f64,
-};
-
-/// Additional options for rendering glyphs.
-pub const RenderOptions = struct {
- /// The metrics that are defining the grid layout. These are usually
- /// the metrics of the primary font face. The grid metrics are used
- /// by the font face to better layout the glyph in situations where
- /// the font is not exactly the same size as the grid.
- grid_metrics: Metrics,
-
- /// The number of grid cells this glyph will take up. This can be used
- /// optionally by the rasterizer to better layout the glyph.
- cell_width: ?u2 = null,
-
- /// Constraint and alignment properties for the glyph. The rasterizer
- /// should call the `constrain` function on this with the original size
- /// and bearings of the glyph to get remapped values that the glyph
- /// should be scaled/moved to.
- constraint: Constraint = .none,
-
- /// The number of cells, horizontally that the glyph is free to take up
- /// when resized and aligned by `constraint`. This is usually 1, but if
- /// there's whitespace to the right of the cell then it can be 2.
- constraint_width: u2 = 1,
-
- /// Thicken the glyph. This draws the glyph with a thicker stroke width.
- /// This is purely an aesthetic setting.
- ///
- /// This only works with CoreText currently.
- thicken: bool = false,
-
- /// "Strength" of the thickening, between `0` and `255`.
- /// Only has an effect when `thicken` is enabled.
- ///
- /// `0` does not correspond to *no* thickening,
- /// just the *lightest* thickening available.
- ///
- /// CoreText only.
- thicken_strength: u8 = 255,
-
- /// See the `constraint` field.
- pub const Constraint = struct {
- /// Don't constrain the glyph in any way.
- pub const none: Constraint = .{};
-
- /// Sizing rule.
- size: Size = .none,
-
- /// Vertical alignment rule.
- align_vertical: Align = .none,
- /// Horizontal alignment rule.
- align_horizontal: Align = .none,
-
- /// Top padding when resizing.
- pad_top: f64 = 0.0,
- /// Left padding when resizing.
- pad_left: f64 = 0.0,
- /// Right padding when resizing.
- pad_right: f64 = 0.0,
- /// Bottom padding when resizing.
- pad_bottom: f64 = 0.0,
-
- // Size and bearings of the glyph relative
- // to the bounding box of its scale group.
- relative_width: f64 = 1.0,
- relative_height: f64 = 1.0,
- relative_x: f64 = 0.0,
- relative_y: f64 = 0.0,
-
- /// Maximum aspect ratio (width/height) to allow when stretching.
- max_xy_ratio: ?f64 = null,
-
- /// Maximum number of cells horizontally to use.
- max_constraint_width: u2 = 2,
-
- /// What to use as the height metric when constraining the glyph and
- /// the constraint width is 1,
- height: Height = .cell,
-
- pub const Size = enum {
- /// Don't change the size of this glyph.
- none,
- /// Scale the glyph down if needed to fit within the bounds,
- /// preserving aspect ratio.
- fit,
- /// Scale the glyph up or down to exactly match the bounds,
- /// preserving aspect ratio.
- cover,
- /// Scale the glyph down if needed to fit within the bounds,
- /// preserving aspect ratio. If the glyph doesn't cover a
- /// single cell, scale up. If the glyph exceeds a single
- /// cell but is within the bounds, do nothing.
- /// (Nerd Font specific rule.)
- fit_cover1,
- /// Stretch the glyph to exactly fit the bounds in both
- /// directions, disregarding aspect ratio.
- stretch,
- };
-
- pub const Align = enum {
- /// Don't move the glyph on this axis.
- none,
- /// Move the glyph so that its leading (bottom/left)
- /// edge aligns with the leading edge of the axis.
- start,
- /// Move the glyph so that its trailing (top/right)
- /// edge aligns with the trailing edge of the axis.
- end,
- /// Move the glyph so that it is centered on this axis.
- center,
- /// Move the glyph so that it is centered on this axis,
- /// but always with respect to the first cell even for
- /// multi-cell constraints. (Nerd Font specific rule.)
- center1,
- };
-
- pub const Height = enum {
- /// Use the full line height of the primary face for
- /// constraining this glyph.
- cell,
- /// Use the icon height from the grid metrics for
- /// constraining this glyph. Unlike `cell`, the value of
- /// this height depends on both the constraint width and the
- /// affected by the `adjust-icon-height` config option.
- icon,
- };
-
- /// Returns true if the constraint does anything. If it doesn't,
- /// because it neither sizes nor positions the glyph, then this
- /// returns false.
- pub inline fn doesAnything(self: Constraint) bool {
- return self.size != .none or
- self.align_horizontal != .none or
- self.align_vertical != .none;
- }
-
- /// Apply this constraint to the provided glyph
- /// size, given the available width and height.
- pub fn constrain(
- self: Constraint,
- glyph: GlyphSize,
- metrics: Metrics,
- /// Number of cells horizontally available for this glyph.
- constraint_width: u2,
- ) GlyphSize {
- if (!self.doesAnything()) return glyph;
-
- switch (self.size) {
- .stretch => {
- // Stretched glyphs are usually meant to align across cell
- // boundaries, which works best if they're scaled and
- // aligned to the grid rather than the face. This is most
- // easily done by inserting this little fib in the metrics.
- var m = metrics;
- m.face_width = @floatFromInt(m.cell_width);
- m.face_height = @floatFromInt(m.cell_height);
- m.face_y = 0.0;
-
- // Negative padding for stretched glyphs is a band-aid to
- // avoid gaps due to pixel rounding, but at the cost of
- // unsightly overlap artifacts. Since we scale and align to
- // the grid rather than the face, we don't need it.
- var c = self;
- c.pad_bottom = @max(0, c.pad_bottom);
- c.pad_top = @max(0, c.pad_top);
- c.pad_left = @max(0, c.pad_left);
- c.pad_right = @max(0, c.pad_right);
-
- return c.constrainInner(glyph, m, constraint_width);
- },
- else => return self.constrainInner(glyph, metrics, constraint_width),
- }
- }
-
- fn constrainInner(
- self: Constraint,
- glyph: GlyphSize,
- metrics: Metrics,
- constraint_width: u2,
- ) GlyphSize {
- // For extra wide font faces, never stretch glyphs across two cells.
- // This mirrors font_patcher.
- const min_constraint_width: u2 = if ((self.size == .stretch) and (metrics.face_width > 0.9 * metrics.face_height))
- 1
- else
- @min(self.max_constraint_width, constraint_width);
-
- // The bounding box for the glyph's scale group.
- // Scaling and alignment rules are calculated for
- // this box and then applied to the glyph.
- var group: GlyphSize = group: {
- const group_width = glyph.width / self.relative_width;
- const group_height = glyph.height / self.relative_height;
- break :group .{
- .width = group_width,
- .height = group_height,
- .x = glyph.x - (group_width * self.relative_x),
- .y = glyph.y - (group_height * self.relative_y),
- };
- };
-
- // Apply prescribed scaling, preserving the
- // center bearings of the group bounding box
- const width_factor, const height_factor = self.scale_factors(group, metrics, min_constraint_width);
- const center_x = group.x + (group.width / 2);
- const center_y = group.y + (group.height / 2);
- group.width *= width_factor;
- group.height *= height_factor;
- group.x = center_x - (group.width / 2);
- group.y = center_y - (group.height / 2);
-
- // NOTE: font_patcher jumps through a lot of hoops at this
- // point to ensure that the glyph remains within the target
- // bounding box after rounding to font definition units.
- // This is irrelevant here as we're not rounding, we're
- // staying in f64 and heading straight to rendering.
-
- // Apply prescribed alignment
- group.y = self.aligned_y(group, metrics);
- group.x = self.aligned_x(group, metrics, min_constraint_width);
-
- // Transfer the scaling and alignment back to the glyph and return.
- return .{
- .width = width_factor * glyph.width,
- .height = height_factor * glyph.height,
- .x = group.x + (group.width * self.relative_x),
- .y = group.y + (group.height * self.relative_y),
- };
- }
-
- /// Return width and height scaling factors for this scaling group.
- fn scale_factors(
- self: Constraint,
- group: GlyphSize,
- metrics: Metrics,
- min_constraint_width: u2,
- ) struct { f64, f64 } {
- if (self.size == .none) {
- return .{ 1.0, 1.0 };
- }
-
- const multi_cell = (min_constraint_width > 1);
-
- const pad_width_factor = @as(f64, @floatFromInt(min_constraint_width)) - (self.pad_left + self.pad_right);
- const pad_height_factor = 1 - (self.pad_bottom + self.pad_top);
-
- const target_width = pad_width_factor * metrics.face_width;
- const target_height = pad_height_factor * switch (self.height) {
- .cell => metrics.face_height,
- // Like font-patcher, the icon constraint height depends on the
- // constraint width. Unlike font-patcher, the multi-cell
- // icon_height may be different from face_height due to the
- // `adjust-icon-height` config option.
- .icon => if (multi_cell)
- metrics.icon_height
- else
- metrics.icon_height_single,
- };
-
- var width_factor = target_width / group.width;
- var height_factor = target_height / group.height;
-
- switch (self.size) {
- .none => unreachable,
- .fit => {
- // Scale down to fit if needed
- height_factor = @min(1, width_factor, height_factor);
- width_factor = height_factor;
- },
- .cover => {
- // Scale to cover
- height_factor = @min(width_factor, height_factor);
- width_factor = height_factor;
- },
- .fit_cover1 => {
- // Scale down to fit or up to cover at least one cell
- // NOTE: This is similar to font_patcher's "pa" mode,
- // however, font_patcher will only do the upscaling
- // part if the constraint width is 1, resulting in
- // some icons becoming smaller when the constraint
- // width increases. You'd see icons shrinking when
- // opening up a space after them. This makes no
- // sense, so we've fixed the rule such that these
- // icons are scaled to the same size for multi-cell
- // constraints as they would be for single-cell.
- height_factor = @min(width_factor, height_factor);
- if (multi_cell and (height_factor > 1)) {
- // Call back into this function with
- // constraint width 1 to get single-cell scale
- // factors. We use the height factor as width
- // could have been modified by max_xy_ratio.
- _, const single_height_factor = self.scale_factors(group, metrics, 1);
- height_factor = @max(1, single_height_factor);
- }
- width_factor = height_factor;
- },
- .stretch => {},
- }
-
- // Reduce aspect ratio if required
- if (self.max_xy_ratio) |ratio| {
- if (group.width * width_factor > group.height * height_factor * ratio) {
- width_factor = group.height * height_factor * ratio / group.width;
- }
- }
-
- return .{ width_factor, height_factor };
- }
-
- /// Return vertical bearing for aligning this group
- fn aligned_y(
- self: Constraint,
- group: GlyphSize,
- metrics: Metrics,
- ) f64 {
- if ((self.size == .none) and (self.align_vertical == .none)) {
- // If we don't have any constraints affecting the vertical axis,
- // we don't touch vertical alignment.
- return group.y;
- }
- // We use face_height and offset by face_y, rather than
- // using cell_height directly, to account for the asymmetry
- // of the pixel cell around the face (a consequence of
- // aligning the baseline with a pixel boundary rather than
- // vertically centering the face).
- const pad_bottom_dy = self.pad_bottom * metrics.face_height;
- const pad_top_dy = self.pad_top * metrics.face_height;
- const start_y = metrics.face_y + pad_bottom_dy;
- const end_y = metrics.face_y + (metrics.face_height - group.height - pad_top_dy);
- const center_y = (start_y + end_y) / 2;
- return switch (self.align_vertical) {
- // NOTE: Even if there is no prescribed alignment, we ensure
- // that the group doesn't protrude outside the padded cell,
- // since this is implied by every available size constraint. If
- // the group is too high we fall back to centering, though if we
- // hit the .none prong we always have self.size != .none, so
- // this should never happen.
- .none => if (end_y < start_y)
- center_y
- else
- @max(start_y, @min(group.y, end_y)),
- .start => start_y,
- .end => end_y,
- .center, .center1 => center_y,
- };
- }
-
- /// Return horizontal bearing for aligning this group
- fn aligned_x(
- self: Constraint,
- group: GlyphSize,
- metrics: Metrics,
- min_constraint_width: u2,
- ) f64 {
- if ((self.size == .none) and (self.align_horizontal == .none)) {
- // If we don't have any constraints affecting the horizontal
- // axis, we don't touch horizontal alignment.
- return group.x;
- }
- // For multi-cell constraints, we align relative to the span
- // from the left edge of the first cell to the right edge of
- // the last face cell assuming it's left-aligned within the
- // rounded and adjusted pixel cell. Any horizontal offset to
- // center the face within the grid cell is the responsibility
- // of the backend-specific rendering code, and should be done
- // after applying constraints.
- const full_face_span = metrics.face_width + @as(f64, @floatFromInt((min_constraint_width - 1) * metrics.cell_width));
- const pad_left_dx = self.pad_left * metrics.face_width;
- const pad_right_dx = self.pad_right * metrics.face_width;
- const start_x = pad_left_dx;
- const end_x = full_face_span - group.width - pad_right_dx;
- return switch (self.align_horizontal) {
- // NOTE: Even if there is no prescribed alignment, we ensure
- // that the glyph doesn't protrude outside the padded cell,
- // since this is implied by every available size constraint. The
- // left-side bound has priority if the group is too wide, though
- // if we hit the .none prong we always have self.size != .none,
- // so this should never happen.
- .none => @max(start_x, @min(group.x, end_x)),
- .start => start_x,
- .end => @max(start_x, end_x),
- .center => @max(start_x, (start_x + end_x) / 2),
- // NOTE: .center1 implements the font_patcher rule of centering
- // in the first cell even for multi-cell constraints. Since glyphs
- // are not allowed to protrude to the left, this results in the
- // left-alignment like .start when the glyph is wider than a cell.
- .center1 => center1: {
- const end1_x = metrics.face_width - group.width - pad_right_dx;
- break :center1 @max(start_x, (start_x + end1_x) / 2);
- },
- };
- }
- };
-};
-
test {
@import("std").testing.refAllDecls(@This());
}
@@ -512,197 +110,3 @@ test "Variation.Id: slnt should be 1936486004" {
try testing.expectEqual(@as(u32, 1936486004), @as(u32, @bitCast(id)));
try testing.expectEqualStrings("slnt", &(id.str()));
}
-
-test "Constraints" {
- const comparison = @import("../datastruct/comparison.zig");
- const getConstraint = @import("nerd_font_attributes.zig").getConstraint;
-
- // Hardcoded data matches metrics from CoreText at size 12 and DPI 96.
-
- // Define grid metrics (matches font-family = JetBrains Mono)
- const metrics: Metrics = .{
- .cell_width = 10,
- .cell_height = 22,
- .cell_baseline = 5,
- .underline_position = 19,
- .underline_thickness = 1,
- .strikethrough_position = 12,
- .strikethrough_thickness = 1,
- .overline_position = 0,
- .overline_thickness = 1,
- .box_thickness = 1,
- .cursor_thickness = 1,
- .cursor_height = 22,
- .icon_height = 21.12,
- .icon_height_single = 44.48 / 3.0,
- .face_width = 9.6,
- .face_height = 21.12,
- .face_y = 0.2,
- };
-
- // ASCII (no constraint).
- {
- const constraint: RenderOptions.Constraint = .none;
-
- // BBox of 'x' from JetBrains Mono.
- const glyph_x: GlyphSize = .{
- .width = 6.784,
- .height = 15.28,
- .x = 1.408,
- .y = 4.84,
- };
-
- // Any constraint width: do nothing.
- inline for (.{ 1, 2 }) |constraint_width| {
- try comparison.expectApproxEqual(
- glyph_x,
- constraint.constrain(glyph_x, metrics, constraint_width),
- );
- }
- }
-
- // Symbol (same constraint as hardcoded in Renderer.addGlyph).
- {
- const constraint: RenderOptions.Constraint = .{ .size = .fit };
-
- // BBox of '■' (0x25A0 black square) from Iosevka.
- // NOTE: This glyph is designed to span two cells.
- const glyph_25A0: GlyphSize = .{
- .width = 10.272,
- .height = 10.272,
- .x = 2.864,
- .y = 5.304,
- };
-
- // Constraint width 1: scale down and shift to fit a single cell.
- try comparison.expectApproxEqual(
- GlyphSize{
- .width = metrics.face_width,
- .height = metrics.face_width,
- .x = 0,
- .y = 5.64,
- },
- constraint.constrain(glyph_25A0, metrics, 1),
- );
-
- // Constraint width 2: do nothing.
- try comparison.expectApproxEqual(
- glyph_25A0,
- constraint.constrain(glyph_25A0, metrics, 2),
- );
- }
-
- // Emoji (same constraint as hardcoded in SharedGrid.renderGlyph).
- {
- const constraint: RenderOptions.Constraint = .{
- .size = .cover,
- .align_horizontal = .center,
- .align_vertical = .center,
- .pad_left = 0.025,
- .pad_right = 0.025,
- };
-
- // BBox of '🥸' (0x1F978) from Apple Color Emoji.
- const glyph_1F978: GlyphSize = .{
- .width = 20,
- .height = 20,
- .x = 0.46,
- .y = 1,
- };
-
- // Constraint width 2: scale to cover two cells with padding, center;
- try comparison.expectApproxEqual(
- GlyphSize{
- .width = 18.72,
- .height = 18.72,
- .x = 0.44,
- .y = 1.4,
- },
- constraint.constrain(glyph_1F978, metrics, 2),
- );
- }
-
- // Nerd Font default.
- {
- const constraint = getConstraint(0xea61).?;
-
- // Verify that this is the constraint we expect.
- try std.testing.expectEqual(.fit_cover1, constraint.size);
- try std.testing.expectEqual(.icon, constraint.height);
- try std.testing.expectEqual(.center1, constraint.align_horizontal);
- try std.testing.expectEqual(.center1, constraint.align_vertical);
-
- // BBox of '' (0xEA61 nf-cod-lightbulb) from Symbols Only.
- // NOTE: This icon is part of a group, so the
- // constraint applies to a larger bounding box.
- const glyph_EA61: GlyphSize = .{
- .width = 9.015625,
- .height = 13.015625,
- .x = 3.015625,
- .y = 3.76525,
- };
-
- // Constraint width 1: scale and shift group to fit a single cell.
- try comparison.expectApproxEqual(
- GlyphSize{
- .width = 7.2125,
- .height = 10.4125,
- .x = 0.8125,
- .y = 5.950695224719102,
- },
- constraint.constrain(glyph_EA61, metrics, 1),
- );
-
- // Constraint width 2: no scaling; left-align and vertically center group.
- try comparison.expectApproxEqual(
- GlyphSize{
- .width = glyph_EA61.width,
- .height = glyph_EA61.height,
- .x = 1.015625,
- .y = 4.7483690308988775,
- },
- constraint.constrain(glyph_EA61, metrics, 2),
- );
- }
-
- // Nerd Font stretch.
- {
- const constraint = getConstraint(0xe0c0).?;
-
- // Verify that this is the constraint we expect.
- try std.testing.expectEqual(.stretch, constraint.size);
- try std.testing.expectEqual(.cell, constraint.height);
- try std.testing.expectEqual(.start, constraint.align_horizontal);
- try std.testing.expectEqual(.center1, constraint.align_vertical);
-
- // BBox of ' ' (0xE0C0 nf-ple-flame_thick) from Symbols Only.
- const glyph_E0C0: GlyphSize = .{
- .width = 16.796875,
- .height = 16.46875,
- .x = -0.796875,
- .y = 1.7109375,
- };
-
- // Constraint width 1: stretch and position to exactly cover one cell.
- try comparison.expectApproxEqual(
- GlyphSize{
- .width = @floatFromInt(metrics.cell_width),
- .height = @floatFromInt(metrics.cell_height),
- .x = 0,
- .y = 0,
- },
- constraint.constrain(glyph_E0C0, metrics, 1),
- );
-
- // Constraint width 1: stretch and position to exactly cover two cells.
- try comparison.expectApproxEqual(
- GlyphSize{
- .width = @floatFromInt(2 * metrics.cell_width),
- .height = @floatFromInt(metrics.cell_height),
- .x = 0,
- .y = 0,
- },
- constraint.constrain(glyph_E0C0, metrics, 2),
- );
- }
-}