From 3795cd6c2d8864e846c24a63f2f720244c79d783 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 1 Jul 2023 09:55:19 -0700 Subject: [PATCH] font: turn rasterization options into a struct, add thicken --- src/config.zig | 4 ++++ src/font/Group.zig | 18 ++++++++++-------- src/font/GroupCache.zig | 18 ++++++++++-------- src/font/face.zig | 14 ++++++++++++++ src/font/face/coretext.zig | 10 ++++------ src/font/face/freetype.zig | 18 ++++++++++-------- src/font/face/web_canvas.zig | 6 +++--- src/renderer/Metal.zig | 8 +++++--- src/renderer/OpenGL.zig | 6 +++--- 9 files changed, 63 insertions(+), 39 deletions(-) diff --git a/src/config.zig b/src/config.zig index a6c7c3b59..a5de9afca 100644 --- a/src/config.zig +++ b/src/config.zig @@ -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 }, diff --git a/src/font/Group.zig b/src/font/Group.zig index f7f9e271f..f32395608 100644 --- a/src/font/Group.zig +++ b/src/font/Group.zig @@ -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, + .{}, ); } } diff --git a/src/font/GroupCache.zig b/src/font/GroupCache.zig index 57a4f1acb..af73aac58 100644 --- a/src/font/GroupCache.zig +++ b/src/font/GroupCache.zig @@ -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); diff --git a/src/font/face.zig b/src/font/face.zig index b6d7465cf..24f0f5368 100644 --- a/src/font/face.zig +++ b/src/font/face.zig @@ -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 { diff --git a/src/font/face/coretext.zig b/src/font/face/coretext.zig index 2380cc555..1b4ec2142 100644 --- a/src/font/face/coretext.zig +++ b/src/font/face/coretext.zig @@ -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).?, .{}); } } diff --git a/src/font/face/freetype.zig b/src/font/face/freetype.zig index eab180d62..a09a523ef 100644 --- a/src/font/face/freetype.zig +++ b/src/font/face/freetype.zig @@ -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, .{}); } diff --git a/src/font/face/web_canvas.zig b/src/font/face/web_canvas.zig index 821b5f422..458e018c1 100644 --- a/src/font/face/web_canvas.zig +++ b/src/font/face/web_canvas.zig @@ -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); diff --git a/src/renderer/Metal.zig b/src/renderer/Metal.zig index 7d41f2bc2..61e12a55b 100644 --- a/src/renderer/Metal.zig +++ b/src/renderer/Metal.zig @@ -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; diff --git a/src/renderer/OpenGL.zig b/src/renderer/OpenGL.zig index 26dfc015f..f130fc6ab 100644 --- a/src/renderer/OpenGL.zig +++ b/src/renderer/OpenGL.zig @@ -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;