mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-14 11:35:48 +00:00
font: add bitmap font tests for BDF, PCF, and OTB formats (#10193)
Adds test coverage for bitmap font rendering in the FreeType backend using the Spleen 8x16 font in three formats: - BDF (Bitmap Distribution Format) - PCF (Portable Compiled Format) - OTB (OpenType Bitmap) Tests validate glyph dimensions and pixel-perfect rendering against expected patterns, following the model established by the existing TerminusTTF test. Spleen was chosen because it ships in all three required formats from a single source, providing consistent test data across formats. Its BSD 2-Clause license is compatible with the existing font licenses in the repo (OFL, MIT). No existing embedded fonts could be used since they are all TTF/OTF files that use the TrueType loader rather than the BDF/PCF/OTB loaders being tested. Addresses #8524. --- This PR was written with assistance from Claude Code.
This commit is contained in:
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -10,4 +10,5 @@ pkg/libintl/libintl.h linguist-generated=true
|
||||
pkg/simdutf/vendor/** linguist-vendored
|
||||
src/font/nerd_font_attributes.zig linguist-generated=true
|
||||
src/font/nerd_font_codepoint_tables.py linguist-generated=true
|
||||
src/font/res/** linguist-vendored
|
||||
src/terminal/res/** linguist-vendored
|
||||
|
||||
@@ -47,3 +47,9 @@ 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");
|
||||
|
||||
/// Spleen is a monospaced bitmap font available in multiple formats.
|
||||
/// Used for testing bitmap font support across different file formats.
|
||||
pub const spleen_bdf = @embedFile("res/spleen-8x16.bdf");
|
||||
pub const spleen_pcf = @embedFile("res/spleen-8x16.pcf");
|
||||
pub const spleen_otb = @embedFile("res/spleen-8x16.otb");
|
||||
|
||||
@@ -1284,3 +1284,153 @@ test "bitmap glyph" {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Expected pixel pattern for Spleen 8x16 'A' (glyph index from char 'A')
|
||||
// Derived from BDF BITMAP data: 00,00,7C,C6,C6,C6,FE,C6,C6,C6,C6,C6,00,00,00,00
|
||||
const spleen_A =
|
||||
\\........
|
||||
\\........
|
||||
\\.#####..
|
||||
\\##...##.
|
||||
\\##...##.
|
||||
\\##...##.
|
||||
\\#######.
|
||||
\\##...##.
|
||||
\\##...##.
|
||||
\\##...##.
|
||||
\\##...##.
|
||||
\\##...##.
|
||||
\\........
|
||||
\\........
|
||||
\\........
|
||||
\\........
|
||||
;
|
||||
// Including the newline
|
||||
const spleen_A_pitch = 9;
|
||||
// Test parameters for bitmap font tests
|
||||
const spleen_test_point_size = 12;
|
||||
const spleen_test_dpi = 96;
|
||||
|
||||
test "bitmap glyph BDF" {
|
||||
const alloc = testing.allocator;
|
||||
const testFont = font.embedded.spleen_bdf;
|
||||
|
||||
var lib = try Library.init(alloc);
|
||||
defer lib.deinit();
|
||||
|
||||
var atlas = try font.Atlas.init(alloc, 512, .grayscale);
|
||||
defer atlas.deinit(alloc);
|
||||
|
||||
// Spleen 8x16 is a pure bitmap font at 16px height
|
||||
var ft_font = try Face.init(lib, testFont, .{ .size = .{
|
||||
.points = spleen_test_point_size,
|
||||
.xdpi = spleen_test_dpi,
|
||||
.ydpi = spleen_test_dpi,
|
||||
} });
|
||||
defer ft_font.deinit();
|
||||
|
||||
// Get glyph index for 'A'
|
||||
const glyph_index = ft_font.glyphIndex('A') orelse return error.GlyphNotFound;
|
||||
|
||||
const glyph = try ft_font.renderGlyph(
|
||||
alloc,
|
||||
&atlas,
|
||||
glyph_index,
|
||||
.{ .grid_metrics = font.Metrics.calc(ft_font.getMetrics()) },
|
||||
);
|
||||
|
||||
// Verify dimensions match Spleen 8x16
|
||||
try testing.expectEqual(8, glyph.width);
|
||||
try testing.expectEqual(16, glyph.height);
|
||||
|
||||
// Verify pixel-perfect rendering
|
||||
for (0..glyph.height) |y| {
|
||||
for (0..glyph.width) |x| {
|
||||
const pixel = spleen_A[y * spleen_A_pitch + x];
|
||||
try testing.expectEqual(
|
||||
@as(u8, if (pixel == '#') 255 else 0),
|
||||
atlas.data[(glyph.atlas_y + y) * atlas.size + (glyph.atlas_x + x)],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test "bitmap glyph PCF" {
|
||||
const alloc = testing.allocator;
|
||||
const testFont = font.embedded.spleen_pcf;
|
||||
|
||||
var lib = try Library.init(alloc);
|
||||
defer lib.deinit();
|
||||
|
||||
var atlas = try font.Atlas.init(alloc, 512, .grayscale);
|
||||
defer atlas.deinit(alloc);
|
||||
|
||||
var ft_font = try Face.init(lib, testFont, .{ .size = .{
|
||||
.points = spleen_test_point_size,
|
||||
.xdpi = spleen_test_dpi,
|
||||
.ydpi = spleen_test_dpi,
|
||||
} });
|
||||
defer ft_font.deinit();
|
||||
|
||||
const glyph_index = ft_font.glyphIndex('A') orelse return error.GlyphNotFound;
|
||||
|
||||
const glyph = try ft_font.renderGlyph(
|
||||
alloc,
|
||||
&atlas,
|
||||
glyph_index,
|
||||
.{ .grid_metrics = font.Metrics.calc(ft_font.getMetrics()) },
|
||||
);
|
||||
|
||||
try testing.expectEqual(8, glyph.width);
|
||||
try testing.expectEqual(16, glyph.height);
|
||||
|
||||
for (0..glyph.height) |y| {
|
||||
for (0..glyph.width) |x| {
|
||||
const pixel = spleen_A[y * spleen_A_pitch + x];
|
||||
try testing.expectEqual(
|
||||
@as(u8, if (pixel == '#') 255 else 0),
|
||||
atlas.data[(glyph.atlas_y + y) * atlas.size + (glyph.atlas_x + x)],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test "bitmap glyph OTB" {
|
||||
const alloc = testing.allocator;
|
||||
const testFont = font.embedded.spleen_otb;
|
||||
|
||||
var lib = try Library.init(alloc);
|
||||
defer lib.deinit();
|
||||
|
||||
var atlas = try font.Atlas.init(alloc, 512, .grayscale);
|
||||
defer atlas.deinit(alloc);
|
||||
|
||||
var ft_font = try Face.init(lib, testFont, .{ .size = .{
|
||||
.points = spleen_test_point_size,
|
||||
.xdpi = spleen_test_dpi,
|
||||
.ydpi = spleen_test_dpi,
|
||||
} });
|
||||
defer ft_font.deinit();
|
||||
|
||||
const glyph_index = ft_font.glyphIndex('A') orelse return error.GlyphNotFound;
|
||||
|
||||
const glyph = try ft_font.renderGlyph(
|
||||
alloc,
|
||||
&atlas,
|
||||
glyph_index,
|
||||
.{ .grid_metrics = font.Metrics.calc(ft_font.getMetrics()) },
|
||||
);
|
||||
|
||||
try testing.expectEqual(8, glyph.width);
|
||||
try testing.expectEqual(16, glyph.height);
|
||||
|
||||
for (0..glyph.height) |y| {
|
||||
for (0..glyph.width) |x| {
|
||||
const pixel = spleen_A[y * spleen_A_pitch + x];
|
||||
try testing.expectEqual(
|
||||
@as(u8, if (pixel == '#') 255 else 0),
|
||||
atlas.data[(glyph.atlas_y + y) * atlas.size + (glyph.atlas_x + x)],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
24
src/font/res/BSD-2-Clause.txt
vendored
Normal file
24
src/font/res/BSD-2-Clause.txt
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
Copyright (c) 2018-2024, Frederic Cambus
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
|
||||
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
6
src/font/res/README.md
vendored
6
src/font/res/README.md
vendored
@@ -1,6 +1,6 @@
|
||||
# Fonts and Licenses
|
||||
|
||||
This project uses several fonts which fall under the SIL Open Font License (OFL-1.1) and MIT License:
|
||||
This project uses several fonts which fall under the SIL Open Font License (OFL-1.1), MIT License, and BSD 2-Clause License:
|
||||
|
||||
- Code New Roman (OFL-1.1)
|
||||
- [© 2014 Sam Radian. All Rights Reserved.](https://github.com/chrissimpkins/codeface/blob/master/fonts/code-new-roman/license.txt)
|
||||
@@ -28,8 +28,12 @@ This project uses several fonts which fall under the SIL Open Font License (OFL-
|
||||
- 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/)
|
||||
- Spleen (BSD 2-Clause)
|
||||
- [Copyright (c) 2018-2024, Frederic Cambus](https://github.com/fcambus/spleen)
|
||||
|
||||
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/>.
|
||||
|
||||
A full copy of the MIT license can be found at [MIT.txt](./MIT.txt).
|
||||
|
||||
A full copy of the BSD 2-Clause license can be found at [BSD-2-Clause.txt](./BSD-2-Clause.txt).
|
||||
|
||||
22328
src/font/res/spleen-8x16.bdf
vendored
Normal file
22328
src/font/res/spleen-8x16.bdf
vendored
Normal file
File diff suppressed because it is too large
Load Diff
BIN
src/font/res/spleen-8x16.otb
vendored
Normal file
BIN
src/font/res/spleen-8x16.otb
vendored
Normal file
Binary file not shown.
BIN
src/font/res/spleen-8x16.pcf
vendored
Normal file
BIN
src/font/res/spleen-8x16.pcf
vendored
Normal file
Binary file not shown.
@@ -19,6 +19,7 @@ extend-exclude = [
|
||||
# Fonts
|
||||
"*.ttf",
|
||||
"*.otf",
|
||||
"*.bdf",
|
||||
# Images
|
||||
"*.png",
|
||||
"*.ico",
|
||||
|
||||
Reference in New Issue
Block a user