mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +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.
|
/// 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
|
/// 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
|
/// 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
|
/// You can disable styles completely by using the `font-style` set of
|
||||||
/// configurations. See the documentation for `font-style` for more information.
|
/// 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.
|
// If we don't have bold, use the regular font.
|
||||||
const bold_list = self.faces.getPtr(.bold);
|
const bold_list = self.faces.getPtr(.bold);
|
||||||
if (bold_list.count() == 0) {
|
if (bold_list.count() == 0) bold: {
|
||||||
log.warn("bold style not available, using regular font", .{});
|
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 });
|
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.
|
// 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.
|
// Create an synthetic italic font face from the given entry and return it.
|
||||||
fn syntheticItalic(self: *Collection, entry: *Entry) !Face {
|
fn syntheticItalic(self: *Collection, entry: *Entry) !Face {
|
||||||
// Not all font backends support synthetic italicization.
|
// Not all font backends support synthetic italicization.
|
||||||
|
@ -24,6 +24,10 @@ pub const Face = struct {
|
|||||||
/// Set quirks.disableDefaultFontFeatures
|
/// Set quirks.disableDefaultFontFeatures
|
||||||
quirks_disable_default_font_features: bool = false,
|
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
|
/// If the face can possibly be colored, then this is the state
|
||||||
/// used to check for color information. This is null if the font
|
/// 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
|
/// 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 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,
|
/// Returns the font name. If allocation is required, buf will be used,
|
||||||
/// but sometimes allocation isn't required and a static string is
|
/// but sometimes allocation isn't required and a static string is
|
||||||
/// returned.
|
/// returned.
|
||||||
@ -300,11 +323,23 @@ pub const Face = struct {
|
|||||||
.advance_x = 0,
|
.advance_x = 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
|
// If we're doing thicken, then getBoundsForGlyphs does not take
|
||||||
// into account the anti-aliasing that will be added to the glyph.
|
// 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
|
// We need to add some padding to allow that to happen. A padding of
|
||||||
// 2 is usually enough for anti-aliasing.
|
// 2 is usually enough for anti-aliasing.
|
||||||
const padding_ctx: u32 = if (opts.thicken) 2 else 0;
|
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_width: u32 = width + (padding_ctx * 2);
|
||||||
const padded_height: u32 = height + (padding_ctx * 2);
|
const padded_height: u32 = height + (padding_ctx * 2);
|
||||||
|
|
||||||
@ -390,6 +425,13 @@ pub const Face = struct {
|
|||||||
context.setGrayStrokeColor(ctx, 1, 1);
|
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
|
// 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
|
// 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
|
// to get them to 0,0. We also add the padding so that they render
|
||||||
|
Reference in New Issue
Block a user