mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-04-27 11:58:37 +03:00
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):  <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:
@ -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");
|
||||||
|
@ -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)],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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/>.
|
||||||
|
BIN
src/font/res/TerminusTTF-Regular.ttf
Normal file
BIN
src/font/res/TerminusTTF-Regular.ttf
Normal file
Binary file not shown.
Reference in New Issue
Block a user