mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 07:46:12 +03:00
font/coretext: support synthetic bold
This commit is contained in:
@ -62,7 +62,9 @@ const c = @cImport({
|
||||
/// Finally, some styles may be synthesized if they are not supported.
|
||||
/// For example, if a font does not have an italic style and no alternative
|
||||
/// italic font is specified, Ghostty will synthesize an italic style by
|
||||
/// applying a slant to the regular style.
|
||||
/// applying a slant to the regular style. If you want to disable these
|
||||
/// synthesized styles then you can use the `font-style` configurations
|
||||
/// as documented below.
|
||||
///
|
||||
/// You can disable styles completely by using the `font-style` set of
|
||||
/// configurations. See the documentation for `font-style` for more information.
|
||||
|
@ -273,9 +273,15 @@ pub fn completeStyles(self: *Collection, alloc: Allocator) CompleteError!void {
|
||||
|
||||
// If we don't have bold, use the regular font.
|
||||
const bold_list = self.faces.getPtr(.bold);
|
||||
if (bold_list.count() == 0) {
|
||||
log.warn("bold style not available, using regular font", .{});
|
||||
try bold_list.append(alloc, .{ .alias = regular_entry });
|
||||
if (bold_list.count() == 0) bold: {
|
||||
const synthetic = self.syntheticBold(regular_entry) catch |err| {
|
||||
log.warn("failed to create synthetic bold, bold style will not be available err={}", .{err});
|
||||
try bold_list.append(alloc, .{ .alias = regular_entry });
|
||||
break :bold;
|
||||
};
|
||||
|
||||
log.info("synthetic bold face created", .{});
|
||||
try bold_list.append(alloc, .{ .loaded = synthetic });
|
||||
}
|
||||
|
||||
// If we don't have bold italic, use the regular italic font.
|
||||
@ -304,6 +310,26 @@ pub fn completeStyles(self: *Collection, alloc: Allocator) CompleteError!void {
|
||||
}
|
||||
}
|
||||
|
||||
// Create an synthetic italic font face from the given entry and return it.
|
||||
fn syntheticBold(self: *Collection, entry: *Entry) !Face {
|
||||
// Not all font backends support synthetic bold.
|
||||
if (comptime !@hasDecl(Face, "syntheticBold")) return error.SyntheticBoldUnavailable;
|
||||
|
||||
// We require loading options to create a synthetic bold face.
|
||||
const opts = self.load_options orelse return error.DeferredLoadingUnavailable;
|
||||
|
||||
// Try to bold it.
|
||||
const regular = try self.getFaceFromEntry(entry);
|
||||
const face = try regular.syntheticBold(opts.faceOptions());
|
||||
|
||||
var buf: [256]u8 = undefined;
|
||||
if (face.name(&buf)) |name| {
|
||||
log.info("font synthetic bold created family={s}", .{name});
|
||||
} else |_| {}
|
||||
|
||||
return face;
|
||||
}
|
||||
|
||||
// Create an synthetic italic font face from the given entry and return it.
|
||||
fn syntheticItalic(self: *Collection, entry: *Entry) !Face {
|
||||
// Not all font backends support synthetic italicization.
|
||||
|
@ -24,6 +24,10 @@ pub const Face = struct {
|
||||
/// Set quirks.disableDefaultFontFeatures
|
||||
quirks_disable_default_font_features: bool = false,
|
||||
|
||||
/// True if this font face should be rasterized with a synthetic bold
|
||||
/// effect. This is used for fonts that don't have a bold variant.
|
||||
synthetic_bold: ?f64 = null,
|
||||
|
||||
/// If the face can possibly be colored, then this is the state
|
||||
/// used to check for color information. This is null if the font
|
||||
/// can't possibly be colored (i.e. doesn't have SVG, sbix, etc
|
||||
@ -175,6 +179,25 @@ pub const Face = struct {
|
||||
return try initFont(ct_font, opts);
|
||||
}
|
||||
|
||||
/// Return a new face that is the same as this but applies a synthetic
|
||||
/// bold effect to it. This is useful for fonts that don't have a bold
|
||||
/// variant.
|
||||
pub fn syntheticBold(self: *const Face, opts: font.face.Options) !Face {
|
||||
const ct_font = try self.font.copyWithAttributes(0.0, null, null);
|
||||
errdefer ct_font.release();
|
||||
var face = try initFont(ct_font, opts);
|
||||
|
||||
// TO determine our synthetic bold line width we get a multiplier
|
||||
// from the font size in points. This is a heuristic that is based
|
||||
// on the fact that a line width of 1 looks good to me at 12 points
|
||||
// and we want to scale that up roughly linearly with the font size.
|
||||
const points_f64: f64 = @floatCast(opts.size.points);
|
||||
const line_width = @max(points_f64 / 12.0, 1);
|
||||
face.synthetic_bold = line_width;
|
||||
|
||||
return face;
|
||||
}
|
||||
|
||||
/// Returns the font name. If allocation is required, buf will be used,
|
||||
/// but sometimes allocation isn't required and a static string is
|
||||
/// returned.
|
||||
@ -300,11 +323,23 @@ pub const Face = struct {
|
||||
.advance_x = 0,
|
||||
};
|
||||
|
||||
// If we're doing thicken, then getBoundsForGlyphs does not take
|
||||
// into account the anti-aliasing that will be added to the glyph.
|
||||
// We need to add some padding to allow that to happen. A padding of
|
||||
// 2 is usually enough for anti-aliasing.
|
||||
const padding_ctx: u32 = if (opts.thicken) 2 else 0;
|
||||
// Additional padding we need to add to the bitmap context itself
|
||||
// due to the glyph being larger than standard.
|
||||
const padding_ctx: u32 = padding_ctx: {
|
||||
// If we're doing thicken, then getBoundsForGlyphs does not take
|
||||
// into account the anti-aliasing that will be added to the glyph.
|
||||
// We need to add some padding to allow that to happen. A padding of
|
||||
// 2 is usually enough for anti-aliasing.
|
||||
var result: u32 = if (opts.thicken) 2 else 0;
|
||||
|
||||
// If we have a synthetic bold, add padding for the stroke width
|
||||
if (self.synthetic_bold) |line_width| {
|
||||
// x2 for top and bottom padding
|
||||
result += @intFromFloat(@ceil(line_width) * 2);
|
||||
}
|
||||
|
||||
break :padding_ctx result;
|
||||
};
|
||||
const padded_width: u32 = width + (padding_ctx * 2);
|
||||
const padded_height: u32 = height + (padding_ctx * 2);
|
||||
|
||||
@ -390,6 +425,13 @@ pub const Face = struct {
|
||||
context.setGrayStrokeColor(ctx, 1, 1);
|
||||
}
|
||||
|
||||
// If we are drawing with synthetic bold then use a fill stroke
|
||||
// which strokes the outlines of the glyph making a more bold look.
|
||||
if (self.synthetic_bold) |line_width| {
|
||||
context.setTextDrawingMode(ctx, .fill_stroke);
|
||||
context.setLineWidth(ctx, line_width);
|
||||
}
|
||||
|
||||
// We want to render the glyphs at (0,0), but the glyphs themselves
|
||||
// are offset by bearings, so we have to undo those bearings in order
|
||||
// to get them to 0,0. We also add the padding so that they render
|
||||
|
Reference in New Issue
Block a user