mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-25 13:16:11 +03:00
@ -26,6 +26,27 @@ pub fn Context(comptime T: type) type {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn setAllowsFontSmoothing(self: *T, v: bool) void {
|
||||||
|
c.CGContextSetAllowsFontSmoothing(
|
||||||
|
@ptrCast(self),
|
||||||
|
v,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setAllowsFontSubpixelPositioning(self: *T, v: bool) void {
|
||||||
|
c.CGContextSetAllowsFontSubpixelPositioning(
|
||||||
|
@ptrCast(self),
|
||||||
|
v,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setAllowsFontSubpixelQuantization(self: *T, v: bool) void {
|
||||||
|
c.CGContextSetAllowsFontSubpixelQuantization(
|
||||||
|
@ptrCast(self),
|
||||||
|
v,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn setShouldAntialias(self: *T, v: bool) void {
|
pub fn setShouldAntialias(self: *T, v: bool) void {
|
||||||
c.CGContextSetShouldAntialias(
|
c.CGContextSetShouldAntialias(
|
||||||
@ptrCast(self),
|
@ptrCast(self),
|
||||||
@ -40,6 +61,20 @@ pub fn Context(comptime T: type) type {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn setShouldSubpixelPositionFonts(self: *T, v: bool) void {
|
||||||
|
c.CGContextSetShouldSubpixelPositionFonts(
|
||||||
|
@ptrCast(self),
|
||||||
|
v,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setShouldSubpixelQuantizeFonts(self: *T, v: bool) void {
|
||||||
|
c.CGContextSetShouldSubpixelQuantizeFonts(
|
||||||
|
@ptrCast(self),
|
||||||
|
v,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn setGrayFillColor(self: *T, gray: f64, alpha: f64) void {
|
pub fn setGrayFillColor(self: *T, gray: f64, alpha: f64) void {
|
||||||
c.CGContextSetGrayFillColor(
|
c.CGContextSetGrayFillColor(
|
||||||
@ptrCast(self),
|
@ptrCast(self),
|
||||||
@ -66,6 +101,16 @@ pub fn Context(comptime T: type) type {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn setRGBStrokeColor(self: *T, r: f64, g: f64, b: f64, alpha: f64) void {
|
||||||
|
c.CGContextSetRGBStrokeColor(
|
||||||
|
@ptrCast(self),
|
||||||
|
r,
|
||||||
|
g,
|
||||||
|
b,
|
||||||
|
alpha,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn setTextDrawingMode(self: *T, mode: TextDrawingMode) void {
|
pub fn setTextDrawingMode(self: *T, mode: TextDrawingMode) void {
|
||||||
c.CGContextSetTextDrawingMode(
|
c.CGContextSetTextDrawingMode(
|
||||||
@ptrCast(self),
|
@ptrCast(self),
|
||||||
|
@ -130,6 +130,14 @@ pub const Font = opaque {
|
|||||||
pub fn getUnderlineThickness(self: *Font) f64 {
|
pub fn getUnderlineThickness(self: *Font) f64 {
|
||||||
return c.CTFontGetUnderlineThickness(@ptrCast(self));
|
return c.CTFontGetUnderlineThickness(@ptrCast(self));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn getUnitsPerEm(self: *Font) u32 {
|
||||||
|
return c.CTFontGetUnitsPerEm(@ptrCast(self));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getSize(self: *Font) f64 {
|
||||||
|
return c.CTFontGetSize(@ptrCast(self));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const FontOrientation = enum(c_uint) {
|
pub const FontOrientation = enum(c_uint) {
|
||||||
|
@ -30,30 +30,12 @@ pub const Line = opaque {
|
|||||||
self: *Line,
|
self: *Line,
|
||||||
opts: LineBoundsOptions,
|
opts: LineBoundsOptions,
|
||||||
) graphics.Rect {
|
) graphics.Rect {
|
||||||
// return @bitCast(c.CGRect, c.CTLineGetBoundsWithOptions(
|
return @bitCast(c.CTLineGetBoundsWithOptions(
|
||||||
// @ptrCast(c.CTLineRef, self),
|
|
||||||
// opts.cval(),
|
|
||||||
// ));
|
|
||||||
|
|
||||||
// We have to use a custom C wrapper here because there is some
|
|
||||||
// C ABI issue happening.
|
|
||||||
var result: graphics.Rect = undefined;
|
|
||||||
zig_cabi_CTLineGetBoundsWithOptions(
|
|
||||||
@ptrCast(self),
|
@ptrCast(self),
|
||||||
opts.cval(),
|
opts.cval(),
|
||||||
@ptrCast(&result),
|
));
|
||||||
);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// See getBoundsWithOptions
|
|
||||||
extern "c" fn zig_cabi_CTLineGetBoundsWithOptions(
|
|
||||||
c.CTLineRef,
|
|
||||||
c.CTLineBoundsOptions,
|
|
||||||
*c.CGRect,
|
|
||||||
) void;
|
|
||||||
|
|
||||||
pub fn getTypographicBounds(
|
pub fn getTypographicBounds(
|
||||||
self: *Line,
|
self: *Line,
|
||||||
ascent: ?*f64,
|
ascent: ?*f64,
|
||||||
|
@ -231,7 +231,7 @@ pub fn init(
|
|||||||
if (try disco_it.next()) |face| {
|
if (try disco_it.next()) |face| {
|
||||||
log.info("font regular: {s}", .{try face.name()});
|
log.info("font regular: {s}", .{try face.name()});
|
||||||
try group.addFace(alloc, .regular, face);
|
try group.addFace(alloc, .regular, face);
|
||||||
}
|
} else std.log.warn("font-family not found: {s}", .{family});
|
||||||
}
|
}
|
||||||
if (config.@"font-family-bold") |family| {
|
if (config.@"font-family-bold") |family| {
|
||||||
var disco_it = try disco.discover(.{
|
var disco_it = try disco.discover(.{
|
||||||
@ -243,7 +243,7 @@ pub fn init(
|
|||||||
if (try disco_it.next()) |face| {
|
if (try disco_it.next()) |face| {
|
||||||
log.info("font bold: {s}", .{try face.name()});
|
log.info("font bold: {s}", .{try face.name()});
|
||||||
try group.addFace(alloc, .bold, face);
|
try group.addFace(alloc, .bold, face);
|
||||||
}
|
} else std.log.warn("font-family-bold not found: {s}", .{family});
|
||||||
}
|
}
|
||||||
if (config.@"font-family-italic") |family| {
|
if (config.@"font-family-italic") |family| {
|
||||||
var disco_it = try disco.discover(.{
|
var disco_it = try disco.discover(.{
|
||||||
@ -255,7 +255,7 @@ pub fn init(
|
|||||||
if (try disco_it.next()) |face| {
|
if (try disco_it.next()) |face| {
|
||||||
log.info("font italic: {s}", .{try face.name()});
|
log.info("font italic: {s}", .{try face.name()});
|
||||||
try group.addFace(alloc, .italic, face);
|
try group.addFace(alloc, .italic, face);
|
||||||
}
|
} else std.log.warn("font-family-italic not found: {s}", .{family});
|
||||||
}
|
}
|
||||||
if (config.@"font-family-bold-italic") |family| {
|
if (config.@"font-family-bold-italic") |family| {
|
||||||
var disco_it = try disco.discover(.{
|
var disco_it = try disco.discover(.{
|
||||||
@ -268,7 +268,7 @@ pub fn init(
|
|||||||
if (try disco_it.next()) |face| {
|
if (try disco_it.next()) |face| {
|
||||||
log.info("font bold+italic: {s}", .{try face.name()});
|
log.info("font bold+italic: {s}", .{try face.name()});
|
||||||
try group.addFace(alloc, .bold_italic, face);
|
try group.addFace(alloc, .bold_italic, face);
|
||||||
}
|
} else std.log.warn("font-family-bold-italic not found: {s}", .{family});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,6 +34,10 @@ pub const Config = struct {
|
|||||||
else => 12,
|
else => 12,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// Draw fonts with a thicker stroke, if supported. This is only supported
|
||||||
|
/// currently on macOS.
|
||||||
|
@"font-thicken": bool = false,
|
||||||
|
|
||||||
/// Background color for the window.
|
/// Background color for the window.
|
||||||
background: Color = .{ .r = 0x28, .g = 0x2C, .b = 0x34 },
|
background: Color = .{ .r = 0x28, .g = 0x2C, .b = 0x34 },
|
||||||
|
|
||||||
|
@ -269,7 +269,7 @@ pub fn renderGlyph(
|
|||||||
atlas: *font.Atlas,
|
atlas: *font.Atlas,
|
||||||
index: FontIndex,
|
index: FontIndex,
|
||||||
glyph_index: u32,
|
glyph_index: u32,
|
||||||
max_height: ?u16,
|
opts: font.face.RenderOptions,
|
||||||
) !Glyph {
|
) !Glyph {
|
||||||
// Special-case fonts are rendered directly.
|
// Special-case fonts are rendered directly.
|
||||||
if (index.special()) |sp| switch (sp) {
|
if (index.special()) |sp| switch (sp) {
|
||||||
@ -282,7 +282,9 @@ pub fn renderGlyph(
|
|||||||
|
|
||||||
const face = &self.faces.get(index.style).items[@intCast(index.idx)];
|
const face = &self.faces.get(index.style).items[@intCast(index.idx)];
|
||||||
try face.load(self.lib, self.size);
|
try face.load(self.lib, self.size);
|
||||||
return try face.face.?.renderGlyph(alloc, atlas, glyph_index, max_height);
|
const glyph = try face.face.?.renderGlyph(alloc, atlas, glyph_index, opts);
|
||||||
|
// log.warn("GLYPH={}", .{glyph});
|
||||||
|
return glyph;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The wasm-compatible API.
|
/// The wasm-compatible API.
|
||||||
@ -381,7 +383,9 @@ pub const Wasm = struct {
|
|||||||
) !*Glyph {
|
) !*Glyph {
|
||||||
const idx = @as(FontIndex, @bitCast(@as(u8, @intCast(idx_))));
|
const idx = @as(FontIndex, @bitCast(@as(u8, @intCast(idx_))));
|
||||||
const max_height = if (max_height_ <= 0) null else max_height_;
|
const max_height = if (max_height_ <= 0) null else max_height_;
|
||||||
const glyph = try self.renderGlyph(alloc, atlas, idx, cp, max_height);
|
const glyph = try self.renderGlyph(alloc, atlas, idx, cp, .{
|
||||||
|
.max_height = max_height,
|
||||||
|
});
|
||||||
|
|
||||||
var result = try alloc.create(Glyph);
|
var result = try alloc.create(Glyph);
|
||||||
errdefer alloc.destroy(result);
|
errdefer alloc.destroy(result);
|
||||||
@ -425,7 +429,7 @@ test {
|
|||||||
&atlas_greyscale,
|
&atlas_greyscale,
|
||||||
idx,
|
idx,
|
||||||
glyph_index,
|
glyph_index,
|
||||||
null,
|
.{},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -481,7 +485,7 @@ test "box glyph" {
|
|||||||
&atlas_greyscale,
|
&atlas_greyscale,
|
||||||
idx,
|
idx,
|
||||||
0x2500,
|
0x2500,
|
||||||
null,
|
.{},
|
||||||
);
|
);
|
||||||
try testing.expectEqual(@as(u32, 36), glyph.height);
|
try testing.expectEqual(@as(u32, 36), glyph.height);
|
||||||
}
|
}
|
||||||
@ -512,7 +516,7 @@ test "resize" {
|
|||||||
&atlas_greyscale,
|
&atlas_greyscale,
|
||||||
idx,
|
idx,
|
||||||
glyph_index,
|
glyph_index,
|
||||||
null,
|
.{},
|
||||||
);
|
);
|
||||||
|
|
||||||
try testing.expectEqual(@as(u32, 11), glyph.height);
|
try testing.expectEqual(@as(u32, 11), glyph.height);
|
||||||
@ -529,7 +533,7 @@ test "resize" {
|
|||||||
&atlas_greyscale,
|
&atlas_greyscale,
|
||||||
idx,
|
idx,
|
||||||
glyph_index,
|
glyph_index,
|
||||||
null,
|
.{},
|
||||||
);
|
);
|
||||||
|
|
||||||
try testing.expectEqual(@as(u32, 21), glyph.height);
|
try testing.expectEqual(@as(u32, 21), glyph.height);
|
||||||
@ -572,7 +576,7 @@ test "discover monospace with fontconfig and freetype" {
|
|||||||
&atlas_greyscale,
|
&atlas_greyscale,
|
||||||
idx,
|
idx,
|
||||||
glyph_index,
|
glyph_index,
|
||||||
null,
|
.{},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,7 +122,7 @@ pub fn renderGlyph(
|
|||||||
alloc: Allocator,
|
alloc: Allocator,
|
||||||
index: Group.FontIndex,
|
index: Group.FontIndex,
|
||||||
glyph_index: u32,
|
glyph_index: u32,
|
||||||
max_height: ?u16,
|
opts: font.face.RenderOptions,
|
||||||
) !Glyph {
|
) !Glyph {
|
||||||
const key: GlyphKey = .{ .index = index, .glyph = glyph_index };
|
const key: GlyphKey = .{ .index = index, .glyph = glyph_index };
|
||||||
const gop = try self.glyphs.getOrPut(alloc, key);
|
const gop = try self.glyphs.getOrPut(alloc, key);
|
||||||
@ -140,7 +140,7 @@ pub fn renderGlyph(
|
|||||||
atlas,
|
atlas,
|
||||||
index,
|
index,
|
||||||
glyph_index,
|
glyph_index,
|
||||||
max_height,
|
opts,
|
||||||
) catch |err| switch (err) {
|
) catch |err| switch (err) {
|
||||||
// If the atlas is full, we resize it
|
// If the atlas is full, we resize it
|
||||||
error.AtlasFull => blk: {
|
error.AtlasFull => blk: {
|
||||||
@ -150,7 +150,7 @@ pub fn renderGlyph(
|
|||||||
atlas,
|
atlas,
|
||||||
index,
|
index,
|
||||||
glyph_index,
|
glyph_index,
|
||||||
max_height,
|
opts,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -203,7 +203,7 @@ test {
|
|||||||
alloc,
|
alloc,
|
||||||
idx,
|
idx,
|
||||||
glyph_index,
|
glyph_index,
|
||||||
null,
|
.{},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,7 +225,7 @@ test {
|
|||||||
alloc,
|
alloc,
|
||||||
idx,
|
idx,
|
||||||
glyph_index,
|
glyph_index,
|
||||||
null,
|
.{},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -300,7 +300,9 @@ pub const Wasm = struct {
|
|||||||
) !*Glyph {
|
) !*Glyph {
|
||||||
const idx = @as(Group.FontIndex, @bitCast(@as(u8, @intCast(idx_))));
|
const idx = @as(Group.FontIndex, @bitCast(@as(u8, @intCast(idx_))));
|
||||||
const max_height = if (max_height_ <= 0) null else max_height_;
|
const max_height = if (max_height_ <= 0) null else max_height_;
|
||||||
const glyph = try self.renderGlyph(alloc, idx, cp, max_height);
|
const glyph = try self.renderGlyph(alloc, idx, cp, .{
|
||||||
|
.max_height = max_height,
|
||||||
|
});
|
||||||
|
|
||||||
var result = try alloc.create(Glyph);
|
var result = try alloc.create(Glyph);
|
||||||
errdefer alloc.destroy(result);
|
errdefer alloc.destroy(result);
|
||||||
@ -352,7 +354,7 @@ test "resize" {
|
|||||||
alloc,
|
alloc,
|
||||||
idx,
|
idx,
|
||||||
glyph_index,
|
glyph_index,
|
||||||
null,
|
.{},
|
||||||
);
|
);
|
||||||
|
|
||||||
try testing.expectEqual(@as(u32, 11), glyph.height);
|
try testing.expectEqual(@as(u32, 11), glyph.height);
|
||||||
@ -368,7 +370,7 @@ test "resize" {
|
|||||||
alloc,
|
alloc,
|
||||||
idx,
|
idx,
|
||||||
glyph_index,
|
glyph_index,
|
||||||
null,
|
.{},
|
||||||
);
|
);
|
||||||
|
|
||||||
try testing.expectEqual(@as(u32, 21), glyph.height);
|
try testing.expectEqual(@as(u32, 21), glyph.height);
|
||||||
|
@ -58,6 +58,20 @@ pub const Metrics = struct {
|
|||||||
strikethrough_thickness: f32,
|
strikethrough_thickness: f32,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// 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;
|
pub const Foo = if (options.backend == .coretext) coretext.Face else void;
|
||||||
|
|
||||||
test {
|
test {
|
||||||
|
@ -5,6 +5,8 @@ const macos = @import("macos");
|
|||||||
const harfbuzz = @import("harfbuzz");
|
const harfbuzz = @import("harfbuzz");
|
||||||
const font = @import("../main.zig");
|
const font = @import("../main.zig");
|
||||||
|
|
||||||
|
const log = std.log.scoped(.font_face);
|
||||||
|
|
||||||
pub const Face = struct {
|
pub const Face = struct {
|
||||||
/// Our font face
|
/// Our font face
|
||||||
font: *macos.text.Font,
|
font: *macos.text.Font,
|
||||||
@ -41,8 +43,10 @@ pub const Face = struct {
|
|||||||
/// because the font is loaded at a default size during discovery, and then
|
/// because the font is loaded at a default size during discovery, and then
|
||||||
/// adjusted to the final size for final load.
|
/// adjusted to the final size for final load.
|
||||||
pub fn initFontCopy(base: *macos.text.Font, size: font.face.DesiredSize) !Face {
|
pub fn initFontCopy(base: *macos.text.Font, size: font.face.DesiredSize) !Face {
|
||||||
// Create a copy
|
// Create a copy. The copyWithAttributes docs say the size is in points,
|
||||||
const ct_font = try base.copyWithAttributes(@floatFromInt(size.points), null);
|
// but we need to scale the points by the DPI and to do that we use our
|
||||||
|
// function called "pixels".
|
||||||
|
const ct_font = try base.copyWithAttributes(@floatFromInt(size.pixels()), null);
|
||||||
errdefer ct_font.release();
|
errdefer ct_font.release();
|
||||||
|
|
||||||
var hb_font = try harfbuzz.coretext.createFont(ct_font);
|
var hb_font = try harfbuzz.coretext.createFont(ct_font);
|
||||||
@ -93,39 +97,41 @@ pub const Face = struct {
|
|||||||
return @intCast(glyphs[0]);
|
return @intCast(glyphs[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Render a glyph using the glyph index. The rendered glyph is stored in the
|
|
||||||
/// given texture atlas.
|
|
||||||
pub fn renderGlyph(
|
pub fn renderGlyph(
|
||||||
self: Face,
|
self: Face,
|
||||||
alloc: Allocator,
|
alloc: Allocator,
|
||||||
atlas: *font.Atlas,
|
atlas: *font.Atlas,
|
||||||
glyph_index: u32,
|
glyph_index: u32,
|
||||||
max_height: ?u16,
|
opts: font.face.RenderOptions,
|
||||||
) !font.Glyph {
|
) !font.Glyph {
|
||||||
// We add a small pixel padding around the edge of our glyph so that
|
|
||||||
// anti-aliasing and smoothing doesn't cause us to pick up the pixels
|
|
||||||
// of another glyph when packed into the atlas.
|
|
||||||
const padding = 1;
|
|
||||||
|
|
||||||
_ = max_height;
|
|
||||||
|
|
||||||
var glyphs = [_]macos.graphics.Glyph{@intCast(glyph_index)};
|
var glyphs = [_]macos.graphics.Glyph{@intCast(glyph_index)};
|
||||||
|
|
||||||
// Get the bounding rect for this glyph to determine the width/height
|
// Get the bounding rect for rendering this glyph.
|
||||||
// of the bitmap. We use the rounded up width/height of the bounding rect.
|
const rect = self.font.getBoundingRectForGlyphs(.horizontal, &glyphs, null);
|
||||||
var bounding: [1]macos.graphics.Rect = undefined;
|
|
||||||
_ = self.font.getBoundingRectForGlyphs(.horizontal, &glyphs, &bounding);
|
|
||||||
const glyph_width = @as(u32, @intFromFloat(@ceil(bounding[0].size.width)));
|
|
||||||
const glyph_height = @as(u32, @intFromFloat(@ceil(bounding[0].size.height)));
|
|
||||||
|
|
||||||
// Width and height. Note the padding doubling is because we want
|
// The x/y that we render the glyph at. The Y value has to be flipped
|
||||||
// the padding on both sides (top/bottom, left/right).
|
// because our coordinates in 3D space are (0, 0) bottom left with
|
||||||
const width = glyph_width + (padding * 2);
|
// +y being up.
|
||||||
const height = glyph_height + (padding * 2);
|
const render_x = @floor(rect.origin.x);
|
||||||
|
const render_y = @ceil(-rect.origin.y);
|
||||||
|
|
||||||
|
// The ascent is the amount of pixels above the baseline this glyph
|
||||||
|
// is rendered. The ascent can be calculated by adding the full
|
||||||
|
// glyph height to the origin.
|
||||||
|
const glyph_ascent = @ceil(rect.size.height + rect.origin.y);
|
||||||
|
|
||||||
|
// The glyph height is basically rect.size.height but we do the
|
||||||
|
// ascent plus the descent because both are rounded elements that
|
||||||
|
// will make us more accurate.
|
||||||
|
const height: u32 = @intFromFloat(glyph_ascent + render_y);
|
||||||
|
|
||||||
|
// The glyph width is our advertised bounding with plus the rounding
|
||||||
|
// difference from our rendering X.
|
||||||
|
const width: u32 = @intFromFloat(@ceil(rect.size.width + (rect.origin.x - render_x)));
|
||||||
|
|
||||||
// This bitmap is blank. I've seen it happen in a font, I don't know why.
|
// This bitmap is blank. I've seen it happen in a font, I don't know why.
|
||||||
// If it is empty, we just return a valid glyph struct that does nothing.
|
// If it is empty, we just return a valid glyph struct that does nothing.
|
||||||
if (glyph_width == 0) return font.Glyph{
|
if (width == 0 or height == 0) return font.Glyph{
|
||||||
.width = 0,
|
.width = 0,
|
||||||
.height = 0,
|
.height = 0,
|
||||||
.offset_x = 0,
|
.offset_x = 0,
|
||||||
@ -135,77 +141,157 @@ pub const Face = struct {
|
|||||||
.advance_x = 0,
|
.advance_x = 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get the advance that we need for the glyph
|
// Settings that are specific to if we are rendering text or emoji.
|
||||||
var advances: [1]macos.graphics.Size = undefined;
|
const color: struct {
|
||||||
_ = self.font.getAdvancesForGlyphs(.horizontal, &glyphs, &advances);
|
color: bool,
|
||||||
|
depth: u32,
|
||||||
|
space: *macos.graphics.ColorSpace,
|
||||||
|
context_opts: c_uint,
|
||||||
|
} = if (self.presentation == .text) .{
|
||||||
|
.color = false,
|
||||||
|
.depth = 1,
|
||||||
|
.space = try macos.graphics.ColorSpace.createDeviceGray(),
|
||||||
|
.context_opts = @intFromEnum(macos.graphics.BitmapInfo.alpha_mask) &
|
||||||
|
@intFromEnum(macos.graphics.ImageAlphaInfo.none),
|
||||||
|
} else .{
|
||||||
|
.color = true,
|
||||||
|
.depth = 4,
|
||||||
|
.space = try macos.graphics.ColorSpace.createDeviceRGB(),
|
||||||
|
.context_opts = @intFromEnum(macos.graphics.BitmapInfo.byte_order_32_little) |
|
||||||
|
@intFromEnum(macos.graphics.ImageAlphaInfo.premultiplied_first),
|
||||||
|
};
|
||||||
|
defer color.space.release();
|
||||||
|
|
||||||
// Our buffer for rendering
|
// This is just a safety check.
|
||||||
// TODO(perf): cache this buffer
|
if (atlas.format.depth() != color.depth) {
|
||||||
// TODO(mitchellh): color is going to require a depth here
|
log.warn("font atlas color depth doesn't equal font color depth atlas={} font={}", .{
|
||||||
var buf = try alloc.alloc(u8, width * height);
|
atlas.format.depth(),
|
||||||
|
color.depth,
|
||||||
|
});
|
||||||
|
return error.InvalidAtlasFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Our buffer for rendering. We could cache this but glyph rasterization
|
||||||
|
// usually stabilizes pretty quickly and is very infrequent so I think
|
||||||
|
// the allocation overhead is acceptable compared to the cost of
|
||||||
|
// caching it forever or having to deal with a cache lifetime.
|
||||||
|
var buf = try alloc.alloc(u8, width * height * color.depth);
|
||||||
defer alloc.free(buf);
|
defer alloc.free(buf);
|
||||||
std.mem.set(u8, buf, 0);
|
@memset(buf, 0);
|
||||||
|
|
||||||
const space = try macos.graphics.ColorSpace.createDeviceGray();
|
|
||||||
defer space.release();
|
|
||||||
|
|
||||||
const ctx = try macos.graphics.BitmapContext.create(
|
const ctx = try macos.graphics.BitmapContext.create(
|
||||||
buf,
|
buf,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
8,
|
8,
|
||||||
width,
|
width * color.depth,
|
||||||
space,
|
color.space,
|
||||||
@intFromEnum(macos.graphics.BitmapInfo.alpha_mask) &
|
color.context_opts,
|
||||||
@intFromEnum(macos.graphics.ImageAlphaInfo.none),
|
|
||||||
);
|
);
|
||||||
defer ctx.release();
|
defer ctx.release();
|
||||||
|
|
||||||
|
// Perform an initial fill. This ensures that we don't have any
|
||||||
|
// uninitialized pixels in the bitmap.
|
||||||
|
if (color.color)
|
||||||
|
ctx.setRGBFillColor(1, 1, 1, 0)
|
||||||
|
else
|
||||||
|
ctx.setGrayFillColor(0, 0);
|
||||||
|
ctx.fillRect(.{
|
||||||
|
.origin = .{ .x = 0, .y = 0 },
|
||||||
|
.size = .{
|
||||||
|
.width = @floatFromInt(width),
|
||||||
|
.height = @floatFromInt(height),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.setAllowsFontSmoothing(true);
|
||||||
|
ctx.setShouldSmoothFonts(opts.thicken); // The amadeus "enthicken"
|
||||||
|
ctx.setAllowsFontSubpixelQuantization(true);
|
||||||
|
ctx.setShouldSubpixelQuantizeFonts(true);
|
||||||
|
ctx.setAllowsFontSubpixelPositioning(true);
|
||||||
|
ctx.setShouldSubpixelPositionFonts(true);
|
||||||
ctx.setAllowsAntialiasing(true);
|
ctx.setAllowsAntialiasing(true);
|
||||||
ctx.setShouldAntialias(true);
|
ctx.setShouldAntialias(true);
|
||||||
ctx.setShouldSmoothFonts(true);
|
|
||||||
|
// Set our color for drawing
|
||||||
|
if (color.color) {
|
||||||
|
ctx.setRGBFillColor(1, 1, 1, 1);
|
||||||
|
ctx.setRGBStrokeColor(1, 1, 1, 1);
|
||||||
|
} else {
|
||||||
ctx.setGrayFillColor(1, 1);
|
ctx.setGrayFillColor(1, 1);
|
||||||
ctx.setGrayStrokeColor(1, 1);
|
ctx.setGrayStrokeColor(1, 1);
|
||||||
ctx.setTextDrawingMode(.fill_stroke);
|
}
|
||||||
ctx.setTextMatrix(macos.graphics.AffineTransform.identity());
|
|
||||||
ctx.setTextPosition(0, 0);
|
|
||||||
|
|
||||||
// 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.
|
// to get them to 0,0. We also add the padding so that they render
|
||||||
var pos = [_]macos.graphics.Point{.{
|
// slightly off the edge of the bitmap.
|
||||||
.x = padding + (-1 * bounding[0].origin.x),
|
self.font.drawGlyphs(&glyphs, &.{
|
||||||
.y = padding + (-1 * bounding[0].origin.y),
|
.{
|
||||||
}};
|
.x = -1 * render_x,
|
||||||
self.font.drawGlyphs(&glyphs, &pos, ctx);
|
.y = render_y,
|
||||||
|
},
|
||||||
|
}, ctx);
|
||||||
|
|
||||||
const region = try atlas.reserve(alloc, width, height);
|
const region = region: {
|
||||||
|
// We need to add a 1px padding to the font so that we don't
|
||||||
|
// get fuzzy issues when blending textures.
|
||||||
|
const padding = 1;
|
||||||
|
|
||||||
|
// Get the full padded region
|
||||||
|
var region = try atlas.reserve(
|
||||||
|
alloc,
|
||||||
|
width + (padding * 2), // * 2 because left+right
|
||||||
|
height + (padding * 2), // * 2 because top+bottom
|
||||||
|
);
|
||||||
|
|
||||||
|
// Modify the region so that we remove the padding so that
|
||||||
|
// we write to the non-zero location. The data in an Altlas
|
||||||
|
// is always initialized to zero (Atlas.clear) so we don't
|
||||||
|
// need to worry about zero-ing that.
|
||||||
|
region.x += padding;
|
||||||
|
region.y += padding;
|
||||||
|
region.width -= padding * 2;
|
||||||
|
region.height -= padding * 2;
|
||||||
|
break :region region;
|
||||||
|
};
|
||||||
atlas.set(region, buf);
|
atlas.set(region, buf);
|
||||||
|
|
||||||
const offset_y = offset_y: {
|
const offset_y: i32 = offset_y: {
|
||||||
// Our Y coordinate in 3D is (0, 0) bottom left, +y is UP.
|
// Our Y coordinate in 3D is (0, 0) bottom left, +y is UP.
|
||||||
// We need to calculate our baseline from the bottom of a cell.
|
// We need to calculate our baseline from the bottom of a cell.
|
||||||
const baseline_from_bottom = self.metrics.cell_height - self.metrics.cell_baseline;
|
const baseline_from_bottom = self.metrics.cell_height - self.metrics.cell_baseline;
|
||||||
|
|
||||||
// Next we offset our baseline by the bearing in the font. We
|
// Next we offset our baseline by the bearing in the font. We
|
||||||
// ADD here because CoreText y is UP.
|
// ADD here because CoreText y is UP.
|
||||||
const baseline_with_offset = baseline_from_bottom + bounding[0].origin.y;
|
const baseline_with_offset = baseline_from_bottom + glyph_ascent;
|
||||||
|
|
||||||
// Finally, since we're rendering at (0, 0), the glyph will render
|
break :offset_y @intFromFloat(@ceil(baseline_with_offset));
|
||||||
// by default below the line. We have to add height (glyph height)
|
|
||||||
// so that we shift the glyph UP to be on the line, then we add our
|
|
||||||
// baseline offset to move the glyph further UP to match the baseline.
|
|
||||||
break :offset_y @as(i32, @intCast(height)) + @as(i32, @intFromFloat(@ceil(baseline_with_offset)));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return font.Glyph{
|
// log.warn("renderGlyph rect={} width={} height={} render_x={} render_y={} offset_y={} ascent={} cell_height={} cell_baseline={}", .{
|
||||||
.width = glyph_width,
|
// rect,
|
||||||
.height = glyph_height,
|
// width,
|
||||||
.offset_x = @intFromFloat(@ceil(bounding[0].origin.x)),
|
// height,
|
||||||
|
// render_x,
|
||||||
|
// render_y,
|
||||||
|
// offset_y,
|
||||||
|
// glyph_ascent,
|
||||||
|
// self.metrics.cell_height,
|
||||||
|
// self.metrics.cell_baseline,
|
||||||
|
// });
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.width = width,
|
||||||
|
.height = height,
|
||||||
|
.offset_x = @intFromFloat(render_x),
|
||||||
.offset_y = offset_y,
|
.offset_y = offset_y,
|
||||||
.atlas_x = region.x + padding,
|
.atlas_x = region.x,
|
||||||
.atlas_y = region.y + padding,
|
.atlas_y = region.y,
|
||||||
.advance_x = @floatCast(advances[0].width),
|
|
||||||
|
// This is not used, so we don't bother calculating it. If we
|
||||||
|
// ever need it, we can calculate it using getAdvancesForGlyph.
|
||||||
|
.advance_x = 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,8 +359,32 @@ pub const Face = struct {
|
|||||||
defer fs.release();
|
defer fs.release();
|
||||||
|
|
||||||
// Create a rectangle to fit all of this and create a frame of it.
|
// Create a rectangle to fit all of this and create a frame of it.
|
||||||
|
// The rectangle needs to fit all of our text so we use some
|
||||||
|
// heuristics based on cell_width to calculate it. We are
|
||||||
|
// VERY generous with our rect here because the text must fit.
|
||||||
|
const path_rect = rect: {
|
||||||
|
// The cell width at this point is valid, so let's make it
|
||||||
|
// fit 50 characters wide.
|
||||||
|
const width = cell_width * 50;
|
||||||
|
|
||||||
|
// We are trying to calculate height so we don't know how
|
||||||
|
// high to make our frame. Well-behaved fonts will probably
|
||||||
|
// not have a height greater than 4x the width, so let's just
|
||||||
|
// generously use that metric to ensure we fit the frame.
|
||||||
|
const big_cell_height = cell_width * 4;
|
||||||
|
|
||||||
|
// If we are fitting about ~50 characters per row, we need
|
||||||
|
// unit.len / 50 rows to fit all of our text.
|
||||||
|
const rows = (unit.len / 50) * 2;
|
||||||
|
|
||||||
|
// Our final height is the number of rows times our generous height.
|
||||||
|
const height = rows * big_cell_height;
|
||||||
|
|
||||||
|
break :rect macos.graphics.Rect.init(10, 10, width, height);
|
||||||
|
};
|
||||||
|
|
||||||
const path = try macos.graphics.MutablePath.create();
|
const path = try macos.graphics.MutablePath.create();
|
||||||
path.addRect(null, macos.graphics.Rect.init(10, 10, 200, 200));
|
path.addRect(null, path_rect);
|
||||||
defer path.release();
|
defer path.release();
|
||||||
const frame = try fs.createFrame(
|
const frame = try fs.createFrame(
|
||||||
macos.foundation.Range.init(0, 0),
|
macos.foundation.Range.init(0, 0),
|
||||||
@ -292,38 +402,53 @@ pub const Face = struct {
|
|||||||
const lines = frame.getLines();
|
const lines = frame.getLines();
|
||||||
const line = lines.getValueAtIndex(macos.text.Line, 0);
|
const line = lines.getValueAtIndex(macos.text.Line, 0);
|
||||||
|
|
||||||
// NOTE(mitchellh): For some reason, CTLineGetBoundsWithOptions
|
// Get the bounds of the line to determine the ascent.
|
||||||
// returns garbage and I can't figure out why... so we use the
|
const bounds = line.getBoundsWithOptions(.{ .exclude_leading = true });
|
||||||
// raw ascender.
|
const bounds_ascent = bounds.size.height + bounds.origin.y;
|
||||||
|
const baseline = @floor(bounds_ascent + 0.5);
|
||||||
|
|
||||||
var ascent: f64 = 0;
|
// This is an alternate approach to the above to calculate the
|
||||||
var descent: f64 = 0;
|
// baseline by simply using the ascender. Using this approach led
|
||||||
var leading: f64 = 0;
|
// to less accurate results, but I'm leaving it here for reference.
|
||||||
_ = line.getTypographicBounds(&ascent, &descent, &leading);
|
// var ascent: f64 = 0;
|
||||||
|
// var descent: f64 = 0;
|
||||||
|
// var leading: f64 = 0;
|
||||||
|
// _ = line.getTypographicBounds(&ascent, &descent, &leading);
|
||||||
//std.log.warn("ascent={} descent={} leading={}", .{ ascent, descent, leading });
|
//std.log.warn("ascent={} descent={} leading={}", .{ ascent, descent, leading });
|
||||||
|
|
||||||
break :metrics .{
|
break :metrics .{
|
||||||
.height = @floatCast(points[0].y - points[1].y),
|
.height = @floatCast(points[0].y - points[1].y),
|
||||||
.ascent = @floatCast(ascent),
|
.ascent = @floatCast(baseline),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// All of these metrics are based on our layout above.
|
// All of these metrics are based on our layout above.
|
||||||
const cell_height = layout_metrics.height;
|
const cell_height = layout_metrics.height;
|
||||||
const cell_baseline = layout_metrics.ascent;
|
const cell_baseline = layout_metrics.ascent;
|
||||||
const underline_position = @ceil(layout_metrics.ascent -
|
|
||||||
@as(f32, @floatCast(ct_font.getUnderlinePosition())));
|
|
||||||
const underline_thickness = @ceil(@as(f32, @floatCast(ct_font.getUnderlineThickness())));
|
const underline_thickness = @ceil(@as(f32, @floatCast(ct_font.getUnderlineThickness())));
|
||||||
const strikethrough_position = cell_baseline * 0.6;
|
const strikethrough_position = cell_baseline * 0.6;
|
||||||
const strikethrough_thickness = underline_thickness;
|
const strikethrough_thickness = underline_thickness;
|
||||||
|
|
||||||
// std.log.warn("width={d}, height={d} baseline={d} underline_pos={d} underline_thickness={d}", .{
|
// Underline position is based on our baseline because the font advertised
|
||||||
|
// underline position is based on a zero baseline. We add a small amount
|
||||||
|
// to the underline position to make it look better.
|
||||||
|
const underline_position = @ceil(cell_baseline -
|
||||||
|
@as(f32, @floatCast(ct_font.getUnderlinePosition())) +
|
||||||
|
1);
|
||||||
|
|
||||||
|
// Note: is this useful?
|
||||||
|
// const units_per_em = ct_font.getUnitsPerEm();
|
||||||
|
// const units_per_point = @intToFloat(f64, units_per_em) / ct_font.getSize();
|
||||||
|
|
||||||
|
// std.log.warn("font size size={d}", .{ct_font.getSize()});
|
||||||
|
// std.log.warn("font metrics width={d}, height={d} baseline={d} underline_pos={d} underline_thickness={d}", .{
|
||||||
// cell_width,
|
// cell_width,
|
||||||
// cell_height,
|
// cell_height,
|
||||||
// cell_baseline,
|
// cell_baseline,
|
||||||
// underline_position,
|
// underline_position,
|
||||||
// underline_thickness,
|
// underline_thickness,
|
||||||
// });
|
// });
|
||||||
|
|
||||||
return font.face.Metrics{
|
return font.face.Metrics{
|
||||||
.cell_width = cell_width,
|
.cell_width = cell_width,
|
||||||
.cell_height = cell_height,
|
.cell_height = cell_height,
|
||||||
@ -359,7 +484,7 @@ 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).?, null);
|
_ = try face.renderGlyph(alloc, &atlas, face.glyphIndex(i).?, .{});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -403,6 +528,6 @@ 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).?, null);
|
_ = try face.renderGlyph(alloc, &atlas, face.glyphIndex(i).?, .{});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -124,7 +124,7 @@ pub const Face = struct {
|
|||||||
alloc: Allocator,
|
alloc: Allocator,
|
||||||
atlas: *font.Atlas,
|
atlas: *font.Atlas,
|
||||||
glyph_index: u32,
|
glyph_index: u32,
|
||||||
max_height: ?u16,
|
opts: font.face.RenderOptions,
|
||||||
) !Glyph {
|
) !Glyph {
|
||||||
// If our glyph has color, we want to render the color
|
// If our glyph has color, we want to render the color
|
||||||
try self.face.loadGlyph(glyph_index, .{
|
try self.face.loadGlyph(glyph_index, .{
|
||||||
@ -188,7 +188,7 @@ pub const Face = struct {
|
|||||||
// and copy the atlas.
|
// and copy the atlas.
|
||||||
const bitmap_original = bitmap_converted orelse bitmap_ft;
|
const bitmap_original = bitmap_converted orelse bitmap_ft;
|
||||||
const bitmap_resized: ?freetype.c.struct_FT_Bitmap_ = resized: {
|
const bitmap_resized: ?freetype.c.struct_FT_Bitmap_ = resized: {
|
||||||
const max = max_height orelse break :resized null;
|
const max = opts.max_height orelse break :resized null;
|
||||||
const bm = bitmap_original;
|
const bm = bitmap_original;
|
||||||
if (bm.rows <= max) break :resized null;
|
if (bm.rows <= max) break :resized null;
|
||||||
|
|
||||||
@ -328,6 +328,14 @@ pub const Face = struct {
|
|||||||
break :offset_y glyph_metrics.bitmap_top + @as(c_int, @intFromFloat(self.metrics.cell_baseline));
|
break :offset_y glyph_metrics.bitmap_top + @as(c_int, @intFromFloat(self.metrics.cell_baseline));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// log.warn("renderGlyph width={} height={} offset_x={} offset_y={} glyph_metrics={}", .{
|
||||||
|
// tgt_w,
|
||||||
|
// tgt_h,
|
||||||
|
// glyph_metrics.bitmap_left,
|
||||||
|
// offset_y,
|
||||||
|
// glyph_metrics,
|
||||||
|
// });
|
||||||
|
|
||||||
// Store glyph metadata
|
// Store glyph metadata
|
||||||
return Glyph{
|
return Glyph{
|
||||||
.width = tgt_w,
|
.width = tgt_w,
|
||||||
@ -514,16 +522,16 @@ 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).?, null);
|
_ = try ft_font.renderGlyph(alloc, &atlas, ft_font.glyphIndex(i).?, .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test resizing
|
// Test resizing
|
||||||
{
|
{
|
||||||
const g1 = try ft_font.renderGlyph(alloc, &atlas, ft_font.glyphIndex('A').?, null);
|
const g1 = try ft_font.renderGlyph(alloc, &atlas, ft_font.glyphIndex('A').?, .{});
|
||||||
try testing.expectEqual(@as(u32, 11), g1.height);
|
try testing.expectEqual(@as(u32, 11), g1.height);
|
||||||
|
|
||||||
try ft_font.setSize(.{ .points = 24, .xdpi = 96, .ydpi = 96 });
|
try ft_font.setSize(.{ .points = 24, .xdpi = 96, .ydpi = 96 });
|
||||||
const g2 = try ft_font.renderGlyph(alloc, &atlas, ft_font.glyphIndex('A').?, null);
|
const g2 = try ft_font.renderGlyph(alloc, &atlas, ft_font.glyphIndex('A').?, .{});
|
||||||
try testing.expectEqual(@as(u32, 21), g2.height);
|
try testing.expectEqual(@as(u32, 21), g2.height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -543,11 +551,13 @@ test "color emoji" {
|
|||||||
|
|
||||||
try testing.expectEqual(Presentation.emoji, ft_font.presentation);
|
try testing.expectEqual(Presentation.emoji, ft_font.presentation);
|
||||||
|
|
||||||
_ = try ft_font.renderGlyph(alloc, &atlas, ft_font.glyphIndex('🥸').?, null);
|
_ = try ft_font.renderGlyph(alloc, &atlas, ft_font.glyphIndex('🥸').?, .{});
|
||||||
|
|
||||||
// resize
|
// resize
|
||||||
{
|
{
|
||||||
const glyph = try ft_font.renderGlyph(alloc, &atlas, ft_font.glyphIndex('🥸').?, 24);
|
const glyph = try ft_font.renderGlyph(alloc, &atlas, ft_font.glyphIndex('🥸').?, .{
|
||||||
|
.max_height = 24,
|
||||||
|
});
|
||||||
try testing.expectEqual(@as(u32, 24), glyph.height);
|
try testing.expectEqual(@as(u32, 24), glyph.height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -602,5 +612,5 @@ test "mono to rgba" {
|
|||||||
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, null);
|
_ = try ft_font.renderGlyph(alloc, &atlas, 3, .{});
|
||||||
}
|
}
|
||||||
|
@ -190,9 +190,9 @@ pub const Face = struct {
|
|||||||
alloc: Allocator,
|
alloc: Allocator,
|
||||||
atlas: *font.Atlas,
|
atlas: *font.Atlas,
|
||||||
glyph_index: u32,
|
glyph_index: u32,
|
||||||
max_height: ?u16,
|
opts: font.face.RenderOptions,
|
||||||
) !font.Glyph {
|
) !font.Glyph {
|
||||||
_ = max_height;
|
_ = opts;
|
||||||
|
|
||||||
var render = try self.renderGlyphInternal(alloc, glyph_index);
|
var render = try self.renderGlyphInternal(alloc, glyph_index);
|
||||||
defer render.deinit();
|
defer render.deinit();
|
||||||
@ -551,7 +551,7 @@ pub const Wasm = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn face_render_glyph_(face: *Face, atlas: *font.Atlas, codepoint: u32) !*font.Glyph {
|
fn face_render_glyph_(face: *Face, atlas: *font.Atlas, codepoint: u32) !*font.Glyph {
|
||||||
const glyph = try face.renderGlyph(alloc, atlas, codepoint, null);
|
const glyph = try face.renderGlyph(alloc, atlas, codepoint, .{});
|
||||||
|
|
||||||
const result = try alloc.create(font.Glyph);
|
const result = try alloc.create(font.Glyph);
|
||||||
errdefer alloc.destroy(result);
|
errdefer alloc.destroy(result);
|
||||||
|
@ -71,12 +71,10 @@ pub const Backend = enum {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return if (target.isDarwin()) darwin: {
|
// macOS also supports "coretext_freetype" but there is no scenario
|
||||||
// On macOS right now, the coretext renderer is still pretty buggy
|
// that is the default. It is only used by people who want to
|
||||||
// so we default to coretext for font discovery and freetype for
|
// self-compile Ghostty and prefer the freetype aesthetic.
|
||||||
// rasterization.
|
return if (target.isDarwin()) .coretext else .fontconfig_freetype;
|
||||||
break :darwin .coretext_freetype;
|
|
||||||
} else .fontconfig_freetype;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// All the functions below can be called at comptime or runtime to
|
// All the functions below can be called at comptime or runtime to
|
||||||
|
@ -340,7 +340,31 @@ const PixmanImpl = struct {
|
|||||||
|
|
||||||
const width = @as(u32, @intCast(self.image.getWidth()));
|
const width = @as(u32, @intCast(self.image.getWidth()));
|
||||||
const height = @as(u32, @intCast(self.image.getHeight()));
|
const height = @as(u32, @intCast(self.image.getHeight()));
|
||||||
const region = try atlas.reserve(alloc, width, height);
|
|
||||||
|
// Allocate our texture atlas region
|
||||||
|
const region = region: {
|
||||||
|
// We need to add a 1px padding to the font so that we don't
|
||||||
|
// get fuzzy issues when blending textures.
|
||||||
|
const padding = 1;
|
||||||
|
|
||||||
|
// Get the full padded region
|
||||||
|
var region = try atlas.reserve(
|
||||||
|
alloc,
|
||||||
|
width + (padding * 2), // * 2 because left+right
|
||||||
|
height + (padding * 2), // * 2 because top+bottom
|
||||||
|
);
|
||||||
|
|
||||||
|
// Modify the region so that we remove the padding so that
|
||||||
|
// we write to the non-zero location. The data in an Altlas
|
||||||
|
// is always initialized to zero (Atlas.clear) so we don't
|
||||||
|
// need to worry about zero-ing that.
|
||||||
|
region.x += padding;
|
||||||
|
region.y += padding;
|
||||||
|
region.width -= padding * 2;
|
||||||
|
region.height -= padding * 2;
|
||||||
|
break :region region;
|
||||||
|
};
|
||||||
|
|
||||||
if (region.width > 0 and region.height > 0) {
|
if (region.width > 0 and region.height > 0) {
|
||||||
const depth = atlas.format.depth();
|
const depth = atlas.format.depth();
|
||||||
|
|
||||||
|
@ -129,6 +129,7 @@ const GPUCellMode = enum(u8) {
|
|||||||
/// configuration. This must be exported so that we don't need to
|
/// configuration. This must be exported so that we don't need to
|
||||||
/// pass around Config pointers which makes memory management a pain.
|
/// pass around Config pointers which makes memory management a pain.
|
||||||
pub const DerivedConfig = struct {
|
pub const DerivedConfig = struct {
|
||||||
|
font_thicken: bool,
|
||||||
cursor_color: ?terminal.color.RGB,
|
cursor_color: ?terminal.color.RGB,
|
||||||
background: terminal.color.RGB,
|
background: terminal.color.RGB,
|
||||||
foreground: terminal.color.RGB,
|
foreground: terminal.color.RGB,
|
||||||
@ -142,6 +143,8 @@ pub const DerivedConfig = struct {
|
|||||||
_ = alloc_gpa;
|
_ = alloc_gpa;
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
|
.font_thicken = config.@"font-thicken",
|
||||||
|
|
||||||
.cursor_color = if (config.@"cursor-color") |col|
|
.cursor_color = if (config.@"cursor-color") |col|
|
||||||
col.toTerminalRGB()
|
col.toTerminalRGB()
|
||||||
else
|
else
|
||||||
@ -738,6 +741,14 @@ fn drawCells(
|
|||||||
|
|
||||||
/// Update the configuration.
|
/// Update the configuration.
|
||||||
pub fn changeConfig(self: *Metal, config: *DerivedConfig) !void {
|
pub fn changeConfig(self: *Metal, config: *DerivedConfig) !void {
|
||||||
|
// If font thickening settings change, we need to reset our
|
||||||
|
// font texture completely because we need to re-render the glyphs.
|
||||||
|
if (self.config.font_thicken != config.font_thicken) {
|
||||||
|
self.font_group.reset();
|
||||||
|
self.font_group.atlas_greyscale.clear();
|
||||||
|
self.font_group.atlas_color.clear();
|
||||||
|
}
|
||||||
|
|
||||||
self.config = config.*;
|
self.config = config.*;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -996,7 +1007,10 @@ pub fn updateCell(
|
|||||||
self.alloc,
|
self.alloc,
|
||||||
shaper_run.font_index,
|
shaper_run.font_index,
|
||||||
shaper_cell.glyph_index,
|
shaper_cell.glyph_index,
|
||||||
@intFromFloat(@ceil(self.cell_size.height)),
|
.{
|
||||||
|
.max_height = @intFromFloat(@ceil(self.cell_size.height)),
|
||||||
|
.thicken = self.config.font_thicken,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// If we're rendering a color font, we use the color atlas
|
// If we're rendering a color font, we use the color atlas
|
||||||
@ -1031,7 +1045,7 @@ pub fn updateCell(
|
|||||||
self.alloc,
|
self.alloc,
|
||||||
font.sprite_index,
|
font.sprite_index,
|
||||||
@intFromEnum(sprite),
|
@intFromEnum(sprite),
|
||||||
null,
|
.{},
|
||||||
);
|
);
|
||||||
|
|
||||||
const color = if (cell.attrs.underline_color) cell.underline_fg else colors.fg;
|
const color = if (cell.attrs.underline_color) cell.underline_fg else colors.fg;
|
||||||
@ -1083,7 +1097,7 @@ fn addCursor(self: *Metal, screen: *terminal.Screen) void {
|
|||||||
self.alloc,
|
self.alloc,
|
||||||
font.sprite_index,
|
font.sprite_index,
|
||||||
@intFromEnum(sprite),
|
@intFromEnum(sprite),
|
||||||
null,
|
.{},
|
||||||
) catch |err| {
|
) catch |err| {
|
||||||
log.warn("error rendering cursor glyph err={}", .{err});
|
log.warn("error rendering cursor glyph err={}", .{err});
|
||||||
return;
|
return;
|
||||||
|
@ -226,6 +226,7 @@ const GPUCellMode = enum(u8) {
|
|||||||
/// configuration. This must be exported so that we don't need to
|
/// configuration. This must be exported so that we don't need to
|
||||||
/// pass around Config pointers which makes memory management a pain.
|
/// pass around Config pointers which makes memory management a pain.
|
||||||
pub const DerivedConfig = struct {
|
pub const DerivedConfig = struct {
|
||||||
|
font_thicken: bool,
|
||||||
cursor_color: ?terminal.color.RGB,
|
cursor_color: ?terminal.color.RGB,
|
||||||
background: terminal.color.RGB,
|
background: terminal.color.RGB,
|
||||||
foreground: terminal.color.RGB,
|
foreground: terminal.color.RGB,
|
||||||
@ -239,6 +240,8 @@ pub const DerivedConfig = struct {
|
|||||||
_ = alloc_gpa;
|
_ = alloc_gpa;
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
|
.font_thicken = config.@"font-thicken",
|
||||||
|
|
||||||
.cursor_color = if (config.@"cursor-color") |col|
|
.cursor_color = if (config.@"cursor-color") |col|
|
||||||
col.toTerminalRGB()
|
col.toTerminalRGB()
|
||||||
else
|
else
|
||||||
@ -991,7 +994,7 @@ fn addCursor(self: *OpenGL, screen: *terminal.Screen) void {
|
|||||||
self.alloc,
|
self.alloc,
|
||||||
font.sprite_index,
|
font.sprite_index,
|
||||||
@intFromEnum(sprite),
|
@intFromEnum(sprite),
|
||||||
null,
|
.{},
|
||||||
) catch |err| {
|
) catch |err| {
|
||||||
log.warn("error rendering cursor glyph err={}", .{err});
|
log.warn("error rendering cursor glyph err={}", .{err});
|
||||||
return;
|
return;
|
||||||
@ -1144,7 +1147,10 @@ pub fn updateCell(
|
|||||||
self.alloc,
|
self.alloc,
|
||||||
shaper_run.font_index,
|
shaper_run.font_index,
|
||||||
shaper_cell.glyph_index,
|
shaper_cell.glyph_index,
|
||||||
@intFromFloat(@ceil(self.cell_size.height)),
|
.{
|
||||||
|
.max_height = @intFromFloat(@ceil(self.cell_size.height)),
|
||||||
|
.thicken = self.config.font_thicken,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// If we're rendering a color font, we use the color atlas
|
// If we're rendering a color font, we use the color atlas
|
||||||
@ -1190,7 +1196,7 @@ pub fn updateCell(
|
|||||||
self.alloc,
|
self.alloc,
|
||||||
font.sprite_index,
|
font.sprite_index,
|
||||||
@intFromEnum(sprite),
|
@intFromEnum(sprite),
|
||||||
null,
|
.{},
|
||||||
);
|
);
|
||||||
|
|
||||||
const color = if (cell.attrs.underline_color) cell.underline_fg else colors.fg;
|
const color = if (cell.attrs.underline_color) cell.underline_fg else colors.fg;
|
||||||
@ -1254,6 +1260,14 @@ fn gridSize(self: *const OpenGL, screen_size: renderer.ScreenSize) renderer.Grid
|
|||||||
|
|
||||||
/// Update the configuration.
|
/// Update the configuration.
|
||||||
pub fn changeConfig(self: *OpenGL, config: *DerivedConfig) !void {
|
pub fn changeConfig(self: *OpenGL, config: *DerivedConfig) !void {
|
||||||
|
// If font thickening settings change, we need to reset our
|
||||||
|
// font texture completely because we need to re-render the glyphs.
|
||||||
|
if (self.config.font_thicken != config.font_thicken) {
|
||||||
|
self.font_group.reset();
|
||||||
|
self.font_group.atlas_greyscale.clear();
|
||||||
|
self.font_group.atlas_color.clear();
|
||||||
|
}
|
||||||
|
|
||||||
self.config = config.*;
|
self.config = config.*;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user