font/freetype: Enable bitmap glyphs for non-color faces (#3837)

This allows for crisp bitmap font rendering once again. Tested with
Terminus (TTF), and at both 1x and 2x DPI the font renders perfectly
(click for 1:1 size):

![Terminus (TTF), size 12, 96
DPI](https://github.com/user-attachments/assets/181ba561-ebe4-49df-aa41-68fc782f92b8)

<img alt="Terminus (TTF), size 12, 192 DPI"
src="https://github.com/user-attachments/assets/d34804fe-4966-42e8-bf9e-bd10570ad443"
width="384" height="50" />
This commit is contained in:
Mitchell Hashimoto
2024-12-30 11:16:17 -08:00
committed by GitHub
5 changed files with 66 additions and 29 deletions

View File

@ -34,3 +34,6 @@ pub const cozette = @embedFile("res/CozetteVector.ttf");
/// Monaspace has weird ligature behaviors we want to test in our shapers /// Monaspace has weird ligature behaviors we want to test in our shapers
/// so we embed it here. /// so we embed it here.
pub const monaspace_neon = @embedFile("res/MonaspaceNeon-Regular.otf"); 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");

View File

@ -288,7 +288,6 @@ pub const Face = struct {
self.face.loadGlyph(glyph_id, .{ self.face.loadGlyph(glyph_id, .{
.render = true, .render = true,
.color = self.face.hasColor(), .color = self.face.hasColor(),
.no_bitmap = !self.face.hasColor(),
}) catch return false; }) catch return false;
// If the glyph is SVG we assume colorized // 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. // glyph properties before render so we don't render here.
.render = !self.synthetic.bold, .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 // use options from config
.no_hinting = !self.load_flags.hinting, .no_hinting = !self.load_flags.hinting,
.force_autohint = !self.load_flags.@"force-autohint", .force_autohint = !self.load_flags.@"force-autohint",
@ -385,7 +376,7 @@ pub const Face = struct {
return error.UnsupportedPixelMode; 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, bitmap_ft.pixel_mode,
atlas.format, atlas.format,
}); });
@ -1005,3 +996,55 @@ test "svg font table" {
try testing.expectEqual(430, table.len); 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)],
);
}
}
}

View File

@ -43,26 +43,14 @@ pub fn monoToGrayscale(alloc: Allocator, bm: Bitmap) Allocator.Error!Bitmap {
var buf = try alloc.alloc(u8, bm.width * bm.rows); var buf = try alloc.alloc(u8, bm.width * bm.rows);
errdefer alloc.free(buf); errdefer alloc.free(buf);
// width divided by 8 because each byte has 8 pixels. This is therefore for (0..bm.rows) |y| {
// the number of bytes in each row. const row_offset = y * @as(usize, @intCast(bm.pitch));
const bytes_per_row = bm.width >> 3; for (0..bm.width) |x| {
const byte_offset = row_offset + @divTrunc(x, 8);
var source_i: usize = 0; const mask = @as(u8, 1) << @intCast(7 - (x % 8));
var target_i: usize = 0; const bit: u8 = @intFromBool((bm.buffer[byte_offset] & mask) != 0);
var i: usize = bm.rows; buf[y * bm.width + x] = bit * 255;
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;
}
} }
source_i += @intCast(bm.pitch);
} }
var copy = bm; var copy = bm;

View File

@ -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) - [Copyright 2013 Google LLC](https://github.com/googlefonts/noto-emoji/blob/main/LICENSE)
- Cozette (MIT) - Cozette (MIT)
- [Copyright (c) 2020, Slavfox](https://github.com/slavfox/Cozette/blob/main/LICENSE) - [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). A full copy of the OFL license can be found at [OFL.txt](./OFL.txt).
An accompanying FAQ is also available at <https://openfontlicense.org/>. An accompanying FAQ is also available at <https://openfontlicense.org/>.

Binary file not shown.