diff --git a/src/font/embedded.zig b/src/font/embedded.zig index 098aa3eb4..31b07ff31 100644 --- a/src/font/embedded.zig +++ b/src/font/embedded.zig @@ -34,3 +34,6 @@ pub const cozette = @embedFile("res/CozetteVector.ttf"); /// Monaspace has weird ligature behaviors we want to test in our shapers /// so we embed it here. pub const monaspace_neon = @embedFile("res/MonaspaceNeon-Regular.otf"); + +/// Terminus TTF is a scalable font with bitmap glyphs at various sizes. +pub const terminus_ttf = @embedFile("res/TerminusTTF-Regular.ttf"); diff --git a/src/font/face/freetype.zig b/src/font/face/freetype.zig index bc503a3af..d63cf99f1 100644 --- a/src/font/face/freetype.zig +++ b/src/font/face/freetype.zig @@ -288,7 +288,6 @@ pub const Face = struct { self.face.loadGlyph(glyph_id, .{ .render = true, .color = self.face.hasColor(), - .no_bitmap = !self.face.hasColor(), }) catch return false; // If the glyph is SVG we assume colorized @@ -323,14 +322,6 @@ pub const Face = struct { // glyph properties before render so we don't render here. .render = !self.synthetic.bold, - // Disable bitmap strikes for now since it causes issues with - // our cell metrics and rasterization. In the future, this is - // all fixable so we can enable it. - // - // This must be enabled for color faces though because those are - // often colored bitmaps, which we support. - .no_bitmap = !self.face.hasColor(), - // use options from config .no_hinting = !self.load_flags.hinting, .force_autohint = !self.load_flags.@"force-autohint", @@ -385,7 +376,7 @@ pub const Face = struct { return error.UnsupportedPixelMode; }; - log.warn("converting from pixel_mode={} to atlas_format={}", .{ + log.debug("converting from pixel_mode={} to atlas_format={}", .{ bitmap_ft.pixel_mode, atlas.format, }); @@ -1005,3 +996,55 @@ test "svg font table" { try testing.expectEqual(430, table.len); } + +const terminus_i = + \\........ + \\........ + \\...#.... + \\...#.... + \\........ + \\..##.... + \\...#.... + \\...#.... + \\...#.... + \\...#.... + \\...#.... + \\..###... + \\........ + \\........ + \\........ + \\........ +; +// Including the newline +const terminus_i_pitch = 9; + +test "bitmap glyph" { + const alloc = testing.allocator; + const testFont = font.embedded.terminus_ttf; + + var lib = try Library.init(); + defer lib.deinit(); + + var atlas = try font.Atlas.init(alloc, 512, .grayscale); + defer atlas.deinit(alloc); + + // Any glyph at 12pt @ 96 DPI is a bitmap + var ft_font = try Face.init(lib, testFont, .{ .size = .{ .points = 12 } }); + defer ft_font.deinit(); + + // glyph 77 = 'i' + const glyph = try ft_font.renderGlyph(alloc, &atlas, 77, .{}); + + // should render crisp + try testing.expectEqual(8, glyph.width); + try testing.expectEqual(16, glyph.height); + for (0..glyph.height) |y| { + for (0..glyph.width) |x| { + const pixel = terminus_i[y * terminus_i_pitch + x]; + try testing.expectEqual( + @as(u8, if (pixel == '#') 255 else 0), + atlas.data[(glyph.atlas_y + y) * atlas.size + (glyph.atlas_x + x)], + ); + } + } +} diff --git a/src/font/face/freetype_convert.zig b/src/font/face/freetype_convert.zig index 298aad8a0..6df350bfa 100644 --- a/src/font/face/freetype_convert.zig +++ b/src/font/face/freetype_convert.zig @@ -43,26 +43,14 @@ pub fn monoToGrayscale(alloc: Allocator, bm: Bitmap) Allocator.Error!Bitmap { var buf = try alloc.alloc(u8, bm.width * bm.rows); errdefer alloc.free(buf); - // width divided by 8 because each byte has 8 pixels. This is therefore - // the number of bytes in each row. - const bytes_per_row = bm.width >> 3; - - var source_i: usize = 0; - var target_i: usize = 0; - var i: usize = bm.rows; - while (i > 0) : (i -= 1) { - var j: usize = bytes_per_row; - while (j > 0) : (j -= 1) { - var bit: u4 = 8; - while (bit > 0) : (bit -= 1) { - const mask = @as(u8, 1) << @as(u3, @intCast(bit - 1)); - const bitval: u8 = if (bm.buffer[source_i + (j - 1)] & mask > 0) 0xFF else 0; - buf[target_i] = bitval; - target_i += 1; - } + for (0..bm.rows) |y| { + const row_offset = y * @as(usize, @intCast(bm.pitch)); + for (0..bm.width) |x| { + const byte_offset = row_offset + @divTrunc(x, 8); + const mask = @as(u8, 1) << @intCast(7 - (x % 8)); + const bit: u8 = @intFromBool((bm.buffer[byte_offset] & mask) != 0); + buf[y * bm.width + x] = bit * 255; } - - source_i += @intCast(bm.pitch); } var copy = bm; diff --git a/src/font/res/README.md b/src/font/res/README.md index 3195a8916..5ad4b274f 100644 --- a/src/font/res/README.md +++ b/src/font/res/README.md @@ -25,6 +25,9 @@ This project uses several fonts which fall under the SIL Open Font License (OFL- - [Copyright 2013 Google LLC](https://github.com/googlefonts/noto-emoji/blob/main/LICENSE) - Cozette (MIT) - [Copyright (c) 2020, Slavfox](https://github.com/slavfox/Cozette/blob/main/LICENSE) +- Terminus TTF (OFL-1.1) + - [Copyright (c) 2010-2020 Dimitar Toshkov Zhekov with Reserved Font Name "Terminus Font"](https://sourceforge.net/projects/terminus-font/) + - [Copyright (c) 2011-2023 Tilman Blumenbach with Reserved Font Name "Terminus (TTF)"](https://files.ax86.net/terminus-ttf/) A full copy of the OFL license can be found at [OFL.txt](./OFL.txt). An accompanying FAQ is also available at . diff --git a/src/font/res/TerminusTTF-Regular.ttf b/src/font/res/TerminusTTF-Regular.ttf new file mode 100644 index 000000000..d125e6347 Binary files /dev/null and b/src/font/res/TerminusTTF-Regular.ttf differ