mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-25 05:06:24 +03:00

Font metrics realistically should be integral. Cell widths, cell heights, etc. do not make sense to be floats, since our grid is integral. There is no such thing as a "half cell" (or any point). The reason we historically had these all as f32 is simplicity mixed with history. OpenGL APIs and shaders all use f32 for their values, we originally only supported OpenGL, and all the font rendering used to be directly in the renderer code (like... a year+ ago). When we refactored the font metrics calculation to its own system and also added additional renderers like Metal (which use f64, not f32), we never updated anything. We just kept metrics as f32 and casted everywhere. With CoreText and #177 this finally reared its ugly head. By forgetting a simple rounding on cell metric calculation, our integral renderers (sprite fonts) were off by 1 pixel compared to the GPU renderers. Insidious. Let's represent font metrics with the types that actually make sense: a cell width/height, etc. is _integral_. When we get to the GPU, we now cast to floats. We also cast to floats whenever we're doing more precise math (i.e. mouse offset calculation). In this case, we're only converting to floats from a integral type which is going to be much safer and less prone to uncertain rounding than converting to an int from a float type. Fixes #177
80 lines
2.6 KiB
Zig
80 lines
2.6 KiB
Zig
const builtin = @import("builtin");
|
|
const options = @import("main.zig").options;
|
|
const freetype = @import("face/freetype.zig");
|
|
const coretext = @import("face/coretext.zig");
|
|
pub const web_canvas = @import("face/web_canvas.zig");
|
|
|
|
/// Face implementation for the compile options.
|
|
pub const Face = switch (options.backend) {
|
|
.freetype,
|
|
.fontconfig_freetype,
|
|
.coretext_freetype,
|
|
=> freetype.Face,
|
|
|
|
.coretext => coretext.Face,
|
|
.web_canvas => web_canvas.Face,
|
|
};
|
|
|
|
/// If a DPI can't be calculated, this DPI is used. This is probably
|
|
/// wrong on modern devices so it is highly recommended you get the DPI
|
|
/// using whatever platform method you can.
|
|
pub const default_dpi = if (builtin.os.tag == .macos) 72 else 96;
|
|
|
|
/// The desired size for loading a font.
|
|
pub const DesiredSize = struct {
|
|
// Desired size in points
|
|
points: u16,
|
|
|
|
// The DPI of the screen so we can convert points to pixels.
|
|
xdpi: u16 = default_dpi,
|
|
ydpi: u16 = default_dpi,
|
|
|
|
// Converts points to pixels
|
|
pub fn pixels(self: DesiredSize) u16 {
|
|
// 1 point = 1/72 inch
|
|
return (self.points * self.ydpi) / 72;
|
|
}
|
|
};
|
|
|
|
/// Metrics associated with the font that are useful for renderers to know.
|
|
pub const Metrics = struct {
|
|
/// Recommended cell width and height for a monospace grid using this font.
|
|
cell_width: u32,
|
|
cell_height: u32,
|
|
|
|
/// For monospace grids, the recommended y-value from the bottom to set
|
|
/// the baseline for font rendering. This is chosen so that things such
|
|
/// as the bottom of a "g" or "y" do not drop below the cell.
|
|
cell_baseline: u32,
|
|
|
|
/// The position of the underline from the top of the cell and the
|
|
/// thickness in pixels.
|
|
underline_position: u32,
|
|
underline_thickness: u32,
|
|
|
|
/// The position and thickness of a strikethrough. Same units/style
|
|
/// as the underline fields.
|
|
strikethrough_position: u32,
|
|
strikethrough_thickness: u32,
|
|
};
|
|
|
|
/// Additional options for rendering glyphs.
|
|
pub const RenderOptions = struct {
|
|
/// The maximum height of the glyph. If this is set, then any glyph
|
|
/// larger than this height will be shrunk to this height. The scaling
|
|
/// is typically naive, but ultimately up to the rasterizer.
|
|
max_height: ?u16 = null,
|
|
|
|
/// 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,
|
|
};
|
|
|
|
pub const Foo = if (options.backend == .coretext) coretext.Face else void;
|
|
|
|
test {
|
|
@import("std").testing.refAllDecls(@This());
|
|
}
|