mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 15:56:13 +03:00
Bring back freetype font bitmap conversion
Monaco on Mac is mono
This commit is contained in:
23
pkg/freetype/bitmap.zig
Normal file
23
pkg/freetype/bitmap.zig
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const c = @import("c.zig");
|
||||||
|
const freetype = @import("main.zig");
|
||||||
|
const errors = @import("errors.zig");
|
||||||
|
const Error = errors.Error;
|
||||||
|
const intToError = errors.intToError;
|
||||||
|
|
||||||
|
/// Convert a bitmap object with depth 1bpp, 2bpp, 4bpp, 8bpp or 32bpp to a
|
||||||
|
/// bitmap object with depth 8bpp, making the number of used bytes per line
|
||||||
|
/// (a.k.a. the ‘pitch’) a multiple of alignment.
|
||||||
|
pub fn bitmapConvert(
|
||||||
|
lib: freetype.Library,
|
||||||
|
source: *const c.FT_Bitmap,
|
||||||
|
target: *c.FT_Bitmap,
|
||||||
|
alignment: u32,
|
||||||
|
) Error!void {
|
||||||
|
try intToError(c.FT_Bitmap_Convert(
|
||||||
|
lib.handle,
|
||||||
|
source,
|
||||||
|
target,
|
||||||
|
alignment,
|
||||||
|
));
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
pub const c = @import("c.zig");
|
pub const c = @import("c.zig");
|
||||||
pub const testing = @import("test.zig");
|
pub const testing = @import("test.zig");
|
||||||
pub const Library = @import("Library.zig");
|
pub const Library = @import("Library.zig");
|
||||||
|
pub usingnamespace @import("bitmap.zig");
|
||||||
pub usingnamespace @import("computations.zig");
|
pub usingnamespace @import("computations.zig");
|
||||||
pub usingnamespace @import("errors.zig");
|
pub usingnamespace @import("errors.zig");
|
||||||
pub usingnamespace @import("face.zig");
|
pub usingnamespace @import("face.zig");
|
||||||
|
@ -1902,7 +1902,7 @@ pub fn invokeCharset(
|
|||||||
self.terminal.invokeCharset(active, slot, single);
|
self.terminal.invokeCharset(active, slot, single);
|
||||||
}
|
}
|
||||||
|
|
||||||
const face_ttf = @embedFile("font/res/FiraCode-Regular.ttf");
|
const face_ttf = @embedFile("font/res/Monaco-Regular.ttf");
|
||||||
const face_bold_ttf = @embedFile("font/res/FiraCode-Bold.ttf");
|
const face_bold_ttf = @embedFile("font/res/FiraCode-Bold.ttf");
|
||||||
const face_emoji_ttf = @embedFile("font/res/NotoColorEmoji.ttf");
|
const face_emoji_ttf = @embedFile("font/res/NotoColorEmoji.ttf");
|
||||||
const face_emoji_text_ttf = @embedFile("font/res/NotoEmoji-Regular.ttf");
|
const face_emoji_text_ttf = @embedFile("font/res/NotoEmoji-Regular.ttf");
|
||||||
|
@ -190,6 +190,7 @@ fn loadCoreTextFreetype(
|
|||||||
|
|
||||||
// TODO: face index 0 is not correct long term and we should switch
|
// TODO: face index 0 is not correct long term and we should switch
|
||||||
// to using CoreText for rendering, too.
|
// to using CoreText for rendering, too.
|
||||||
|
//std.log.warn("path={s}", .{path_slice});
|
||||||
self.face = try Face.initFile(lib, buf[0..path_slice.len :0], 0, size);
|
self.face = try Face.initFile(lib, buf[0..path_slice.len :0], 0, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ const font = @import("../main.zig");
|
|||||||
const Glyph = font.Glyph;
|
const Glyph = font.Glyph;
|
||||||
const Library = font.Library;
|
const Library = font.Library;
|
||||||
const Presentation = font.Presentation;
|
const Presentation = font.Presentation;
|
||||||
|
const convert = @import("freetype_convert.zig");
|
||||||
|
|
||||||
const log = std.log.scoped(.font_face);
|
const log = std.log.scoped(.font_face);
|
||||||
|
|
||||||
@ -143,7 +144,8 @@ pub const Face = struct {
|
|||||||
// or color depth is as expected on the texture atlas. If format is null
|
// or color depth is as expected on the texture atlas. If format is null
|
||||||
// it means there is no native color format for our Atlas and we must try
|
// it means there is no native color format for our Atlas and we must try
|
||||||
// conversion.
|
// conversion.
|
||||||
const format: Atlas.Format = switch (bitmap_ft.pixel_mode) {
|
const format: ?Atlas.Format = switch (bitmap_ft.pixel_mode) {
|
||||||
|
freetype.c.FT_PIXEL_MODE_MONO => null,
|
||||||
freetype.c.FT_PIXEL_MODE_GRAY => .greyscale,
|
freetype.c.FT_PIXEL_MODE_GRAY => .greyscale,
|
||||||
freetype.c.FT_PIXEL_MODE_BGRA => .rgba,
|
freetype.c.FT_PIXEL_MODE_BGRA => .rgba,
|
||||||
else => {
|
else => {
|
||||||
@ -151,9 +153,26 @@ pub const Face = struct {
|
|||||||
@panic("unsupported pixel mode");
|
@panic("unsupported pixel mode");
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
assert(atlas.format == format);
|
|
||||||
|
|
||||||
const bitmap = bitmap_ft;
|
// If our atlas format doesn't match, look for conversions if possible.
|
||||||
|
const bitmap_converted = if (format == null or atlas.format != format.?) blk: {
|
||||||
|
const func = convert.map[bitmap_ft.pixel_mode].get(atlas.format) orelse {
|
||||||
|
log.warn("glyph={} pixel mode={}", .{ glyph_index, bitmap_ft.pixel_mode });
|
||||||
|
return error.UnsupportedPixelMode;
|
||||||
|
};
|
||||||
|
|
||||||
|
log.warn("converting from pixel_mode={} to atlas_format={}", .{
|
||||||
|
bitmap_ft.pixel_mode,
|
||||||
|
atlas.format,
|
||||||
|
});
|
||||||
|
break :blk try func(alloc, bitmap_ft);
|
||||||
|
} else null;
|
||||||
|
defer if (bitmap_converted) |bm| {
|
||||||
|
const len = bm.width * bm.rows * atlas.format.depth();
|
||||||
|
alloc.free(bm.buffer[0..len]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const bitmap = bitmap_converted orelse bitmap_ft;
|
||||||
const tgt_w = bitmap.width;
|
const tgt_w = bitmap.width;
|
||||||
const tgt_h = bitmap.rows;
|
const tgt_h = bitmap.rows;
|
||||||
|
|
||||||
|
100
src/font/face/freetype_convert.zig
Normal file
100
src/font/face/freetype_convert.zig
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
//! Various conversions from Freetype formats to Atlas formats. These are
|
||||||
|
//! currently implemented naively. There are definitely MUCH faster ways
|
||||||
|
//! to do this (likely using SIMD), but I started simple.
|
||||||
|
const std = @import("std");
|
||||||
|
const freetype = @import("freetype");
|
||||||
|
const Atlas = @import("../../Atlas.zig");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
/// The mapping from freetype format to atlas format.
|
||||||
|
pub const map = genMap();
|
||||||
|
|
||||||
|
/// The map type.
|
||||||
|
pub const Map = [freetype.c.FT_PIXEL_MODE_MAX]AtlasArray;
|
||||||
|
|
||||||
|
/// Conversion function type. The returning bitmap buffer is guaranteed
|
||||||
|
/// to be exactly `width * rows * depth` long for freeing it. The caller must
|
||||||
|
/// free the bitmap buffer. The depth is the depth of the atlas format in the
|
||||||
|
/// map.
|
||||||
|
pub const Func = std.meta.FnPtr(fn (Allocator, Bitmap) Allocator.Error!Bitmap);
|
||||||
|
|
||||||
|
/// Alias for the freetype FT_Bitmap type to make it easier to type.
|
||||||
|
pub const Bitmap = freetype.c.struct_FT_Bitmap_;
|
||||||
|
|
||||||
|
const AtlasArray = std.EnumArray(Atlas.Format, ?Func);
|
||||||
|
|
||||||
|
fn genMap() Map {
|
||||||
|
var result: Map = undefined;
|
||||||
|
|
||||||
|
// Initialize to no converter
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < freetype.c.FT_PIXEL_MODE_MAX) : (i += 1) {
|
||||||
|
result[i] = AtlasArray.initFill(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map our converters
|
||||||
|
result[freetype.c.FT_PIXEL_MODE_MONO].set(.greyscale, monoToGreyscale);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn monoToGreyscale(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) << @intCast(u3, 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(usize, bm.pitch);
|
||||||
|
}
|
||||||
|
|
||||||
|
var copy = bm;
|
||||||
|
copy.buffer = buf.ptr;
|
||||||
|
copy.pixel_mode = freetype.c.FT_PIXEL_MODE_GRAY;
|
||||||
|
copy.pitch = @intCast(c_int, bm.width);
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
// Force comptime to run
|
||||||
|
_ = map;
|
||||||
|
}
|
||||||
|
|
||||||
|
test "mono to greyscale" {
|
||||||
|
const testing = std.testing;
|
||||||
|
const alloc = testing.allocator;
|
||||||
|
|
||||||
|
var mono_data = [_]u8{0b1010_0101};
|
||||||
|
const source: Bitmap = .{
|
||||||
|
.rows = 1,
|
||||||
|
.width = 8,
|
||||||
|
.pitch = 1,
|
||||||
|
.buffer = @ptrCast([*c]u8, &mono_data),
|
||||||
|
.num_grays = 0,
|
||||||
|
.pixel_mode = freetype.c.FT_PIXEL_MODE_MONO,
|
||||||
|
.palette_mode = 0,
|
||||||
|
.palette = null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = try monoToGreyscale(alloc, source);
|
||||||
|
defer alloc.free(result.buffer[0..(result.width * result.rows)]);
|
||||||
|
try testing.expect(result.pixel_mode == freetype.c.FT_PIXEL_MODE_GRAY);
|
||||||
|
try testing.expectEqual(@as(u8, 255), result.buffer[0]);
|
||||||
|
}
|
Reference in New Issue
Block a user