font: turn rasterization options into a struct, add thicken

This commit is contained in:
Mitchell Hashimoto
2023-07-01 09:55:19 -07:00
parent e99376cac1
commit 3795cd6c2d
9 changed files with 63 additions and 39 deletions

View File

@ -34,6 +34,10 @@ pub const Config = struct {
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 = .{ .r = 0x28, .g = 0x2C, .b = 0x34 },

View File

@ -269,7 +269,7 @@ pub fn renderGlyph(
atlas: *font.Atlas,
index: FontIndex,
glyph_index: u32,
max_height: ?u16,
opts: font.face.RenderOptions,
) !Glyph {
// Special-case fonts are rendered directly.
if (index.special()) |sp| switch (sp) {
@ -282,7 +282,7 @@ pub fn renderGlyph(
const face = &self.faces.get(index.style).items[@intCast(index.idx)];
try face.load(self.lib, self.size);
const glyph = 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;
}
@ -383,7 +383,9 @@ pub const Wasm = struct {
) !*Glyph {
const idx = @as(FontIndex, @bitCast(@as(u8, @intCast(idx_))));
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);
errdefer alloc.destroy(result);
@ -427,7 +429,7 @@ test {
&atlas_greyscale,
idx,
glyph_index,
null,
.{},
);
}
@ -483,7 +485,7 @@ test "box glyph" {
&atlas_greyscale,
idx,
0x2500,
null,
.{},
);
try testing.expectEqual(@as(u32, 36), glyph.height);
}
@ -514,7 +516,7 @@ test "resize" {
&atlas_greyscale,
idx,
glyph_index,
null,
.{},
);
try testing.expectEqual(@as(u32, 11), glyph.height);
@ -531,7 +533,7 @@ test "resize" {
&atlas_greyscale,
idx,
glyph_index,
null,
.{},
);
try testing.expectEqual(@as(u32, 21), glyph.height);
@ -574,7 +576,7 @@ test "discover monospace with fontconfig and freetype" {
&atlas_greyscale,
idx,
glyph_index,
null,
.{},
);
}
}

View File

@ -122,7 +122,7 @@ pub fn renderGlyph(
alloc: Allocator,
index: Group.FontIndex,
glyph_index: u32,
max_height: ?u16,
opts: font.face.RenderOptions,
) !Glyph {
const key: GlyphKey = .{ .index = index, .glyph = glyph_index };
const gop = try self.glyphs.getOrPut(alloc, key);
@ -140,7 +140,7 @@ pub fn renderGlyph(
atlas,
index,
glyph_index,
max_height,
opts,
) catch |err| switch (err) {
// If the atlas is full, we resize it
error.AtlasFull => blk: {
@ -150,7 +150,7 @@ pub fn renderGlyph(
atlas,
index,
glyph_index,
max_height,
opts,
);
},
@ -203,7 +203,7 @@ test {
alloc,
idx,
glyph_index,
null,
.{},
);
}
@ -225,7 +225,7 @@ test {
alloc,
idx,
glyph_index,
null,
.{},
);
}
}
@ -300,7 +300,9 @@ pub const Wasm = struct {
) !*Glyph {
const idx = @as(Group.FontIndex, @bitCast(@as(u8, @intCast(idx_))));
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);
errdefer alloc.destroy(result);
@ -352,7 +354,7 @@ test "resize" {
alloc,
idx,
glyph_index,
null,
.{},
);
try testing.expectEqual(@as(u32, 11), glyph.height);
@ -368,7 +370,7 @@ test "resize" {
alloc,
idx,
glyph_index,
null,
.{},
);
try testing.expectEqual(@as(u32, 21), glyph.height);

View File

@ -58,6 +58,20 @@ pub const Metrics = struct {
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;
test {

View File

@ -102,10 +102,8 @@ pub const Face = struct {
alloc: Allocator,
atlas: *font.Atlas,
glyph_index: u32,
max_height: ?u16,
opts: font.face.RenderOptions,
) !font.Glyph {
_ = max_height;
var glyphs = [_]macos.graphics.Glyph{@intCast(glyph_index)};
// Get the bounding rect for rendering this glyph.
@ -205,7 +203,7 @@ pub const Face = struct {
});
ctx.setAllowsFontSmoothing(true);
ctx.setShouldSmoothFonts(false); // The amadeus "enthicken"
ctx.setShouldSmoothFonts(opts.thicken); // The amadeus "enthicken"
ctx.setAllowsFontSubpixelQuantization(true);
ctx.setShouldSubpixelQuantizeFonts(true);
ctx.setAllowsFontSubpixelPositioning(true);
@ -479,7 +477,7 @@ test {
var i: u8 = 32;
while (i < 127) : (i += 1) {
try testing.expect(face.glyphIndex(i) != null);
_ = try face.renderGlyph(alloc, &atlas, face.glyphIndex(i).?, null);
_ = try face.renderGlyph(alloc, &atlas, face.glyphIndex(i).?, .{});
}
}
@ -523,6 +521,6 @@ test "in-memory" {
var i: u8 = 32;
while (i < 127) : (i += 1) {
try testing.expect(face.glyphIndex(i) != null);
_ = try face.renderGlyph(alloc, &atlas, face.glyphIndex(i).?, null);
_ = try face.renderGlyph(alloc, &atlas, face.glyphIndex(i).?, .{});
}
}

View File

@ -124,7 +124,7 @@ pub const Face = struct {
alloc: Allocator,
atlas: *font.Atlas,
glyph_index: u32,
max_height: ?u16,
opts: font.face.RenderOptions,
) !Glyph {
// If our glyph has color, we want to render the color
try self.face.loadGlyph(glyph_index, .{
@ -188,7 +188,7 @@ pub const Face = struct {
// and copy the atlas.
const bitmap_original = bitmap_converted orelse bitmap_ft;
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;
if (bm.rows <= max) break :resized null;
@ -522,16 +522,16 @@ test {
// Generate all visible ASCII
var i: u8 = 32;
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
{
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 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);
}
}
@ -551,11 +551,13 @@ test "color emoji" {
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
{
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);
}
}
@ -610,5 +612,5 @@ test "mono to rgba" {
defer ft_font.deinit();
// glyph 3 is mono in Noto
_ = try ft_font.renderGlyph(alloc, &atlas, 3, null);
_ = try ft_font.renderGlyph(alloc, &atlas, 3, .{});
}

View File

@ -190,9 +190,9 @@ pub const Face = struct {
alloc: Allocator,
atlas: *font.Atlas,
glyph_index: u32,
max_height: ?u16,
opts: font.face.RenderOptions,
) !font.Glyph {
_ = max_height;
_ = opts;
var render = try self.renderGlyphInternal(alloc, glyph_index);
defer render.deinit();
@ -551,7 +551,7 @@ pub const Wasm = struct {
}
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);
errdefer alloc.destroy(result);

View File

@ -996,7 +996,9 @@ pub fn updateCell(
self.alloc,
shaper_run.font_index,
shaper_cell.glyph_index,
@intFromFloat(@ceil(self.cell_size.height)),
.{
.max_height = @intFromFloat(@ceil(self.cell_size.height)),
},
);
// If we're rendering a color font, we use the color atlas
@ -1031,7 +1033,7 @@ pub fn updateCell(
self.alloc,
font.sprite_index,
@intFromEnum(sprite),
null,
.{},
);
const color = if (cell.attrs.underline_color) cell.underline_fg else colors.fg;
@ -1083,7 +1085,7 @@ fn addCursor(self: *Metal, screen: *terminal.Screen) void {
self.alloc,
font.sprite_index,
@intFromEnum(sprite),
null,
.{},
) catch |err| {
log.warn("error rendering cursor glyph err={}", .{err});
return;

View File

@ -991,7 +991,7 @@ fn addCursor(self: *OpenGL, screen: *terminal.Screen) void {
self.alloc,
font.sprite_index,
@intFromEnum(sprite),
null,
.{},
) catch |err| {
log.warn("error rendering cursor glyph err={}", .{err});
return;
@ -1144,7 +1144,7 @@ pub fn updateCell(
self.alloc,
shaper_run.font_index,
shaper_cell.glyph_index,
@intFromFloat(@ceil(self.cell_size.height)),
.{ .max_height = @intFromFloat(@ceil(self.cell_size.height)) },
);
// If we're rendering a color font, we use the color atlas
@ -1190,7 +1190,7 @@ pub fn updateCell(
self.alloc,
font.sprite_index,
@intFromEnum(sprite),
null,
.{},
);
const color = if (cell.attrs.underline_color) cell.underline_fg else colors.fg;