mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 08:46:08 +03:00
fonts are presentation format aware (text vs emoji)
This commit is contained in:
@ -166,6 +166,11 @@ pub fn init(
|
|||||||
.regular,
|
.regular,
|
||||||
try font.Face.init(font_lib, face_emoji_ttf, font_size),
|
try font.Face.init(font_lib, face_emoji_ttf, font_size),
|
||||||
);
|
);
|
||||||
|
try group.addFace(
|
||||||
|
alloc,
|
||||||
|
.regular,
|
||||||
|
try font.Face.init(font_lib, face_emoji_text_ttf, font_size),
|
||||||
|
);
|
||||||
|
|
||||||
break :group group;
|
break :group group;
|
||||||
});
|
});
|
||||||
@ -830,3 +835,4 @@ test "GridSize update rounding" {
|
|||||||
const face_ttf = @embedFile("font/res/FiraCode-Regular.ttf");
|
const face_ttf = @embedFile("font/res/FiraCode-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");
|
||||||
|
@ -15,6 +15,7 @@ const Allocator = std.mem.Allocator;
|
|||||||
const Atlas = @import("../Atlas.zig");
|
const Atlas = @import("../Atlas.zig");
|
||||||
const Glyph = @import("main.zig").Glyph;
|
const Glyph = @import("main.zig").Glyph;
|
||||||
const Library = @import("main.zig").Library;
|
const Library = @import("main.zig").Library;
|
||||||
|
const Presentation = @import("main.zig").Presentation;
|
||||||
const convert = @import("convert.zig");
|
const convert = @import("convert.zig");
|
||||||
|
|
||||||
const log = std.log.scoped(.font_face);
|
const log = std.log.scoped(.font_face);
|
||||||
@ -25,6 +26,10 @@ face: freetype.Face,
|
|||||||
/// Harfbuzz font corresponding to this face.
|
/// Harfbuzz font corresponding to this face.
|
||||||
hb_font: harfbuzz.Font,
|
hb_font: harfbuzz.Font,
|
||||||
|
|
||||||
|
/// The presentation for this font. This is a heuristic since fonts don't have
|
||||||
|
/// a way to declare this. We just assume a font with color is an emoji font.
|
||||||
|
presentation: Presentation,
|
||||||
|
|
||||||
/// If a DPI can't be calculated, this DPI is used. This is probably
|
/// If a DPI can't be calculated, this DPI is used. This is probably
|
||||||
/// wrong on modern devices so it is highly recommended you get the DPI
|
/// wrong on modern devices so it is highly recommended you get the DPI
|
||||||
/// using whatever platform method you can.
|
/// using whatever platform method you can.
|
||||||
@ -56,7 +61,11 @@ pub fn init(lib: Library, source: [:0]const u8, size: DesiredSize) !Face {
|
|||||||
const hb_font = try harfbuzz.freetype.createFont(face.handle);
|
const hb_font = try harfbuzz.freetype.createFont(face.handle);
|
||||||
errdefer hb_font.destroy();
|
errdefer hb_font.destroy();
|
||||||
|
|
||||||
return Face{ .face = face, .hb_font = hb_font };
|
return Face{
|
||||||
|
.face = face,
|
||||||
|
.hb_font = hb_font,
|
||||||
|
.presentation = if (face.hasColor()) .emoji else .text,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Face) void {
|
pub fn deinit(self: *Face) void {
|
||||||
@ -241,6 +250,8 @@ test {
|
|||||||
var font = try init(lib, testFont, .{ .points = 12 });
|
var font = try init(lib, testFont, .{ .points = 12 });
|
||||||
defer font.deinit();
|
defer font.deinit();
|
||||||
|
|
||||||
|
try testing.expectEqual(Presentation.text, font.presentation);
|
||||||
|
|
||||||
// Generate all visible ASCII
|
// Generate all visible ASCII
|
||||||
var i: u8 = 32;
|
var i: u8 = 32;
|
||||||
while (i < 127) : (i += 1) {
|
while (i < 127) : (i += 1) {
|
||||||
@ -261,6 +272,8 @@ test "color emoji" {
|
|||||||
var font = try init(lib, testFont, .{ .points = 12 });
|
var font = try init(lib, testFont, .{ .points = 12 });
|
||||||
defer font.deinit();
|
defer font.deinit();
|
||||||
|
|
||||||
|
try testing.expectEqual(Presentation.emoji, font.presentation);
|
||||||
|
|
||||||
_ = try font.renderGlyph(alloc, &atlas, font.glyphIndex('🥸').?);
|
_ = try font.renderGlyph(alloc, &atlas, font.glyphIndex('🥸').?);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ const Face = @import("main.zig").Face;
|
|||||||
const Library = @import("main.zig").Library;
|
const Library = @import("main.zig").Library;
|
||||||
const Glyph = @import("main.zig").Glyph;
|
const Glyph = @import("main.zig").Glyph;
|
||||||
const Style = @import("main.zig").Style;
|
const Style = @import("main.zig").Style;
|
||||||
|
const Presentation = @import("main.zig").Presentation;
|
||||||
|
|
||||||
const log = std.log.scoped(.font_group);
|
const log = std.log.scoped(.font_group);
|
||||||
|
|
||||||
@ -86,19 +87,34 @@ pub const FontIndex = packed struct {
|
|||||||
/// The font index is valid as long as font faces aren't removed. This
|
/// The font index is valid as long as font faces aren't removed. This
|
||||||
/// isn't cached; it is expected that downstream users handle caching if
|
/// isn't cached; it is expected that downstream users handle caching if
|
||||||
/// that is important.
|
/// that is important.
|
||||||
pub fn indexForCodepoint(self: Group, style: Style, cp: u32) ?FontIndex {
|
///
|
||||||
|
/// Optionally, a presentation format can be specified. This presentation
|
||||||
|
/// format will be preferred but if it can't be found in this format,
|
||||||
|
/// any text format will be accepted. If presentation is null, any presentation
|
||||||
|
/// is allowed. This func will NOT determine the default presentation for
|
||||||
|
/// a code point.
|
||||||
|
pub fn indexForCodepoint(
|
||||||
|
self: Group,
|
||||||
|
cp: u32,
|
||||||
|
style: Style,
|
||||||
|
p: ?Presentation,
|
||||||
|
) ?FontIndex {
|
||||||
// If we can find the exact value, then return that.
|
// If we can find the exact value, then return that.
|
||||||
if (self.indexForCodepointExact(style, cp)) |value| return value;
|
if (self.indexForCodepointExact(cp, style, p)) |value| return value;
|
||||||
|
|
||||||
// If this is already regular, we're done falling back.
|
// If this is already regular, we're done falling back.
|
||||||
if (style == .regular) return null;
|
if (style == .regular and p == null) return null;
|
||||||
|
|
||||||
// For non-regular fonts, we fall back to regular.
|
// For non-regular fonts, we fall back to regular.
|
||||||
return self.indexForCodepointExact(.regular, cp);
|
return self.indexForCodepointExact(cp, .regular, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn indexForCodepointExact(self: Group, style: Style, cp: u32) ?FontIndex {
|
fn indexForCodepointExact(self: Group, cp: u32, style: Style, p: ?Presentation) ?FontIndex {
|
||||||
for (self.faces.get(style).items) |face, i| {
|
for (self.faces.get(style).items) |face, i| {
|
||||||
|
// If the presentation is null, we allow the first presentation we
|
||||||
|
// can find. Otherwise, we check for the specific one requested.
|
||||||
|
if (p != null and face.presentation != p.?) continue;
|
||||||
|
|
||||||
if (face.glyphIndex(cp) != null) {
|
if (face.glyphIndex(cp) != null) {
|
||||||
return FontIndex{
|
return FontIndex{
|
||||||
.style = style,
|
.style = style,
|
||||||
@ -143,6 +159,7 @@ test {
|
|||||||
const alloc = testing.allocator;
|
const alloc = testing.allocator;
|
||||||
const testFont = @import("test.zig").fontRegular;
|
const testFont = @import("test.zig").fontRegular;
|
||||||
const testEmoji = @import("test.zig").fontEmoji;
|
const testEmoji = @import("test.zig").fontEmoji;
|
||||||
|
const testEmojiText = @import("test.zig").fontEmojiText;
|
||||||
|
|
||||||
var atlas_greyscale = try Atlas.init(alloc, 512, .greyscale);
|
var atlas_greyscale = try Atlas.init(alloc, 512, .greyscale);
|
||||||
defer atlas_greyscale.deinit(alloc);
|
defer atlas_greyscale.deinit(alloc);
|
||||||
@ -155,11 +172,12 @@ test {
|
|||||||
|
|
||||||
try group.addFace(alloc, .regular, try Face.init(lib, testFont, .{ .points = 12 }));
|
try group.addFace(alloc, .regular, try Face.init(lib, testFont, .{ .points = 12 }));
|
||||||
try group.addFace(alloc, .regular, try Face.init(lib, testEmoji, .{ .points = 12 }));
|
try group.addFace(alloc, .regular, try Face.init(lib, testEmoji, .{ .points = 12 }));
|
||||||
|
try group.addFace(alloc, .regular, try Face.init(lib, testEmojiText, .{ .points = 12 }));
|
||||||
|
|
||||||
// Should find all visible ASCII
|
// Should find all visible ASCII
|
||||||
var i: u32 = 32;
|
var i: u32 = 32;
|
||||||
while (i < 127) : (i += 1) {
|
while (i < 127) : (i += 1) {
|
||||||
const idx = group.indexForCodepoint(.regular, i).?;
|
const idx = group.indexForCodepoint(i, .regular, null).?;
|
||||||
try testing.expectEqual(Style.regular, idx.style);
|
try testing.expectEqual(Style.regular, idx.style);
|
||||||
try testing.expectEqual(@as(FontIndex.IndexInt, 0), idx.idx);
|
try testing.expectEqual(@as(FontIndex.IndexInt, 0), idx.idx);
|
||||||
|
|
||||||
@ -176,7 +194,19 @@ test {
|
|||||||
|
|
||||||
// Try emoji
|
// Try emoji
|
||||||
{
|
{
|
||||||
const idx = group.indexForCodepoint(.regular, '🥸').?;
|
const idx = group.indexForCodepoint('🥸', .regular, null).?;
|
||||||
|
try testing.expectEqual(Style.regular, idx.style);
|
||||||
|
try testing.expectEqual(@as(FontIndex.IndexInt, 1), idx.idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try text emoji
|
||||||
|
{
|
||||||
|
const idx = group.indexForCodepoint(0x270C, .regular, .text).?;
|
||||||
|
try testing.expectEqual(Style.regular, idx.style);
|
||||||
|
try testing.expectEqual(@as(FontIndex.IndexInt, 2), idx.idx);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const idx = group.indexForCodepoint(0x270C, .regular, .emoji).?;
|
||||||
try testing.expectEqual(Style.regular, idx.style);
|
try testing.expectEqual(Style.regular, idx.style);
|
||||||
try testing.expectEqual(@as(FontIndex.IndexInt, 1), idx.idx);
|
try testing.expectEqual(@as(FontIndex.IndexInt, 1), idx.idx);
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ const Glyph = @import("main.zig").Glyph;
|
|||||||
const Style = @import("main.zig").Style;
|
const Style = @import("main.zig").Style;
|
||||||
const Group = @import("main.zig").Group;
|
const Group = @import("main.zig").Group;
|
||||||
const Metrics = @import("main.zig").Metrics;
|
const Metrics = @import("main.zig").Metrics;
|
||||||
|
const Presentation = @import("main.zig").Presentation;
|
||||||
|
|
||||||
const log = std.log.scoped(.font_groupcache);
|
const log = std.log.scoped(.font_groupcache);
|
||||||
|
|
||||||
@ -34,6 +35,7 @@ atlas_color: Atlas,
|
|||||||
const CodepointKey = struct {
|
const CodepointKey = struct {
|
||||||
style: Style,
|
style: Style,
|
||||||
codepoint: u32,
|
codepoint: u32,
|
||||||
|
presentation: ?Presentation,
|
||||||
};
|
};
|
||||||
|
|
||||||
const GlyphKey = struct {
|
const GlyphKey = struct {
|
||||||
@ -90,7 +92,7 @@ pub fn metrics(self: *GroupCache, alloc: Allocator) !Metrics {
|
|||||||
var cell_width: f32 = 0;
|
var cell_width: f32 = 0;
|
||||||
var i: u32 = 32;
|
var i: u32 = 32;
|
||||||
while (i <= 126) : (i += 1) {
|
while (i <= 126) : (i += 1) {
|
||||||
const index = (try self.indexForCodepoint(alloc, .regular, i)).?;
|
const index = (try self.indexForCodepoint(alloc, i, .regular, .text)).?;
|
||||||
const face = self.group.faceFromIndex(index);
|
const face = self.group.faceFromIndex(index);
|
||||||
const glyph_index = face.glyphIndex(i).?;
|
const glyph_index = face.glyphIndex(i).?;
|
||||||
const glyph = try self.renderGlyph(alloc, index, glyph_index);
|
const glyph = try self.renderGlyph(alloc, index, glyph_index);
|
||||||
@ -106,7 +108,7 @@ pub fn metrics(self: *GroupCache, alloc: Allocator) !Metrics {
|
|||||||
// '_' which should live at the bottom of a cell.
|
// '_' which should live at the bottom of a cell.
|
||||||
const cell_height: f32 = cell_height: {
|
const cell_height: f32 = cell_height: {
|
||||||
// Get the '_' char for height
|
// Get the '_' char for height
|
||||||
const index = (try self.indexForCodepoint(alloc, .regular, '_')).?;
|
const index = (try self.indexForCodepoint(alloc, '_', .regular, .text)).?;
|
||||||
const face = self.group.faceFromIndex(index);
|
const face = self.group.faceFromIndex(index);
|
||||||
const glyph_index = face.glyphIndex('_').?;
|
const glyph_index = face.glyphIndex('_').?;
|
||||||
const glyph = try self.renderGlyph(alloc, index, glyph_index);
|
const glyph = try self.renderGlyph(alloc, index, glyph_index);
|
||||||
@ -142,15 +144,21 @@ pub fn metrics(self: *GroupCache, alloc: Allocator) !Metrics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get the font index for a given codepoint. This is cached.
|
/// Get the font index for a given codepoint. This is cached.
|
||||||
pub fn indexForCodepoint(self: *GroupCache, alloc: Allocator, style: Style, cp: u32) !?Group.FontIndex {
|
pub fn indexForCodepoint(
|
||||||
const key: CodepointKey = .{ .style = style, .codepoint = cp };
|
self: *GroupCache,
|
||||||
|
alloc: Allocator,
|
||||||
|
cp: u32,
|
||||||
|
style: Style,
|
||||||
|
p: ?Presentation,
|
||||||
|
) !?Group.FontIndex {
|
||||||
|
const key: CodepointKey = .{ .style = style, .codepoint = cp, .presentation = p };
|
||||||
const gop = try self.codepoints.getOrPut(alloc, key);
|
const gop = try self.codepoints.getOrPut(alloc, key);
|
||||||
|
|
||||||
// If it is in the cache, use it.
|
// If it is in the cache, use it.
|
||||||
if (gop.found_existing) return gop.value_ptr.*;
|
if (gop.found_existing) return gop.value_ptr.*;
|
||||||
|
|
||||||
// Load a value and cache it. This even caches negative matches.
|
// Load a value and cache it. This even caches negative matches.
|
||||||
const value = self.group.indexForCodepoint(style, cp);
|
const value = self.group.indexForCodepoint(cp, style, null);
|
||||||
gop.value_ptr.* = value;
|
gop.value_ptr.* = value;
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
@ -219,7 +227,7 @@ test {
|
|||||||
// Visible ASCII. Do it twice to verify cache.
|
// Visible ASCII. Do it twice to verify cache.
|
||||||
var i: u32 = 32;
|
var i: u32 = 32;
|
||||||
while (i < 127) : (i += 1) {
|
while (i < 127) : (i += 1) {
|
||||||
const idx = (try cache.indexForCodepoint(alloc, .regular, i)).?;
|
const idx = (try cache.indexForCodepoint(alloc, i, .regular, null)).?;
|
||||||
try testing.expectEqual(Style.regular, idx.style);
|
try testing.expectEqual(Style.regular, idx.style);
|
||||||
try testing.expectEqual(@as(Group.FontIndex.IndexInt, 0), idx.idx);
|
try testing.expectEqual(@as(Group.FontIndex.IndexInt, 0), idx.idx);
|
||||||
|
|
||||||
@ -240,7 +248,7 @@ test {
|
|||||||
|
|
||||||
i = 32;
|
i = 32;
|
||||||
while (i < 127) : (i += 1) {
|
while (i < 127) : (i += 1) {
|
||||||
const idx = (try cache.indexForCodepoint(alloc, .regular, i)).?;
|
const idx = (try cache.indexForCodepoint(alloc, i, .regular, null)).?;
|
||||||
try testing.expectEqual(Style.regular, idx.style);
|
try testing.expectEqual(Style.regular, idx.style);
|
||||||
try testing.expectEqual(@as(Group.FontIndex.IndexInt, 0), idx.idx);
|
try testing.expectEqual(@as(Group.FontIndex.IndexInt, 0), idx.idx);
|
||||||
|
|
||||||
|
@ -179,14 +179,16 @@ pub const RunIterator = struct {
|
|||||||
// for unknown glyphs.
|
// for unknown glyphs.
|
||||||
const font_idx_opt = (try self.shaper.group.indexForCodepoint(
|
const font_idx_opt = (try self.shaper.group.indexForCodepoint(
|
||||||
alloc,
|
alloc,
|
||||||
style,
|
|
||||||
cell.char,
|
cell.char,
|
||||||
|
style,
|
||||||
|
null,
|
||||||
)) orelse (try self.shaper.group.indexForCodepoint(
|
)) orelse (try self.shaper.group.indexForCodepoint(
|
||||||
alloc,
|
alloc,
|
||||||
style,
|
|
||||||
0xFFFD,
|
0xFFFD,
|
||||||
|
style,
|
||||||
|
null,
|
||||||
)) orelse
|
)) orelse
|
||||||
try self.shaper.group.indexForCodepoint(alloc, style, ' ');
|
try self.shaper.group.indexForCodepoint(alloc, ' ', style, null);
|
||||||
const font_idx = font_idx_opt.?;
|
const font_idx = font_idx_opt.?;
|
||||||
//log.warn("char={x} idx={}", .{ cell.char, font_idx });
|
//log.warn("char={x} idx={}", .{ cell.char, font_idx });
|
||||||
if (j == self.i) current_font = font_idx;
|
if (j == self.i) current_font = font_idx;
|
||||||
@ -378,6 +380,40 @@ test "shape emoji width" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// test "shape variation selector VS15" {
|
||||||
|
// const testing = std.testing;
|
||||||
|
// const alloc = testing.allocator;
|
||||||
|
//
|
||||||
|
// var testdata = try testShaper(alloc);
|
||||||
|
// defer testdata.deinit();
|
||||||
|
//
|
||||||
|
// var buf: [32]u8 = undefined;
|
||||||
|
// var buf_idx: usize = 0;
|
||||||
|
// buf_idx += try std.unicode.utf8Encode(0x263A, buf[buf_idx..]); // White smiling face (text)
|
||||||
|
// buf_idx += try std.unicode.utf8Encode(0xFE0F, buf[buf_idx..]); // ZWJ to force color
|
||||||
|
//
|
||||||
|
// // Make a screen with some data
|
||||||
|
// var screen = try terminal.Screen.init(alloc, 3, 10, 0);
|
||||||
|
// defer screen.deinit();
|
||||||
|
// try screen.testWriteString(buf[0..buf_idx]);
|
||||||
|
//
|
||||||
|
// // Get our run iterator
|
||||||
|
// var shaper = testdata.shaper;
|
||||||
|
// var it = shaper.runIterator(screen.getRow(.{ .screen = 0 }));
|
||||||
|
// var count: usize = 0;
|
||||||
|
// while (try it.next(alloc)) |run| {
|
||||||
|
// count += 1;
|
||||||
|
// //try testing.expectEqual(@as(u32, 2), shaper.hb_buf.getLength());
|
||||||
|
//
|
||||||
|
// const cells = try shaper.shape(run);
|
||||||
|
// try testing.expectEqual(@as(usize, 2), cells.len);
|
||||||
|
// log.warn("WHAT={}", .{cells[0]});
|
||||||
|
// log.warn("WHAT={}", .{cells[1]});
|
||||||
|
// try testing.expectEqual(@as(u8, 2), cells[0].width);
|
||||||
|
// }
|
||||||
|
// try testing.expectEqual(@as(usize, 1), count);
|
||||||
|
// }
|
||||||
|
|
||||||
const TestShaper = struct {
|
const TestShaper = struct {
|
||||||
alloc: Allocator,
|
alloc: Allocator,
|
||||||
shaper: Shaper,
|
shaper: Shaper,
|
||||||
|
73
src/font/convert.zig
Normal file
73
src/font/convert.zig
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
//! 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 = 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(.rgba, monoToRGBA);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn monoToRGBA(alloc: Allocator, bm: Bitmap) Allocator.Error!Bitmap {
|
||||||
|
// NOTE: This was never tested and may not work. I wrote it to
|
||||||
|
// solve another issue where this ended up not being needed.
|
||||||
|
// TODO: test this!
|
||||||
|
|
||||||
|
const depth = Atlas.Format.rgba.depth();
|
||||||
|
var buf = try alloc.alloc(u8, bm.width * bm.rows * depth);
|
||||||
|
errdefer alloc.free(buf);
|
||||||
|
|
||||||
|
var i: usize = 0;
|
||||||
|
while (i < bm.width * bm.rows) : (i += 1) {
|
||||||
|
var bit: u3 = 0;
|
||||||
|
while (bit <= 7) : (bit += 1) {
|
||||||
|
const mask = @as(u8, 1) << (7 - bit);
|
||||||
|
const bitval: u8 = if (bm.buffer[i] & mask > 0) 0xFF else 0;
|
||||||
|
const buf_i = (i * 8 * depth) + (bit * depth);
|
||||||
|
buf[buf_i] = 0xFF - bitval;
|
||||||
|
buf[buf_i + 1] = 0xFF - bitval;
|
||||||
|
buf[buf_i + 2] = 0xFF - bitval;
|
||||||
|
buf[buf_i + 3] = bitval;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var copy = bm;
|
||||||
|
copy.buffer = buf.ptr;
|
||||||
|
copy.pixel_mode = freetype.c.FT_PIXEL_MODE_BGRA;
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
_ = map;
|
||||||
|
}
|
@ -8,13 +8,19 @@ pub const Library = @import("Library.zig");
|
|||||||
pub const Shaper = @import("Shaper.zig");
|
pub const Shaper = @import("Shaper.zig");
|
||||||
|
|
||||||
/// The styles that a family can take.
|
/// The styles that a family can take.
|
||||||
pub const Style = enum(u2) {
|
pub const Style = enum(u3) {
|
||||||
regular = 0,
|
regular = 0,
|
||||||
bold = 1,
|
bold = 1,
|
||||||
italic = 2,
|
italic = 2,
|
||||||
bold_italic = 3,
|
bold_italic = 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// The presentation for a an emoji.
|
||||||
|
pub const Presentation = enum(u1) {
|
||||||
|
text = 0, // U+FE0E
|
||||||
|
emoji = 1, // U+FEOF
|
||||||
|
};
|
||||||
|
|
||||||
/// Font metrics useful for things such as grid calculation.
|
/// Font metrics useful for things such as grid calculation.
|
||||||
pub const Metrics = struct {
|
pub const Metrics = struct {
|
||||||
/// The width and height of a monospace cell.
|
/// The width and height of a monospace cell.
|
||||||
|
BIN
src/font/res/NotoEmoji-Regular.ttf
Executable file
BIN
src/font/res/NotoEmoji-Regular.ttf
Executable file
Binary file not shown.
@ -1,3 +1,4 @@
|
|||||||
pub const fontRegular = @embedFile("res/Inconsolata-Regular.ttf");
|
pub const fontRegular = @embedFile("res/Inconsolata-Regular.ttf");
|
||||||
pub const fontBold = @embedFile("res/Inconsolata-Bold.ttf");
|
pub const fontBold = @embedFile("res/Inconsolata-Bold.ttf");
|
||||||
pub const fontEmoji = @embedFile("res/NotoColorEmoji.ttf");
|
pub const fontEmoji = @embedFile("res/NotoColorEmoji.ttf");
|
||||||
|
pub const fontEmojiText = @embedFile("res/NotoEmoji-Regular.ttf");
|
||||||
|
Reference in New Issue
Block a user