109 lines
3.8 KiB
Zig
109 lines
3.8 KiB
Zig
const std = @import("std");
|
|
const ft = @import("mach-freetype");
|
|
const harfbuzz = @import("mach-harfbuzz");
|
|
const TextRun = @import("TextRun.zig");
|
|
const px_per_pt = @import("../main.zig").px_per_pt;
|
|
const RenderedGlyph = @import("../main.zig").RenderedGlyph;
|
|
const RenderOptions = @import("../main.zig").RenderOptions;
|
|
const RGBA32 = @import("../main.zig").RGBA32;
|
|
|
|
const Font = @This();
|
|
|
|
var freetype_ready_mu: std.Thread.Mutex = .{};
|
|
var freetype_ready: bool = false;
|
|
var freetype: ft.Library = undefined;
|
|
|
|
face: ft.Face,
|
|
bitmap: std.ArrayListUnmanaged(RGBA32) = .{},
|
|
|
|
pub fn initFreetype() !void {
|
|
freetype_ready_mu.lock();
|
|
defer freetype_ready_mu.unlock();
|
|
if (!freetype_ready) {
|
|
freetype = try ft.Library.init();
|
|
freetype_ready = true;
|
|
}
|
|
}
|
|
|
|
pub fn initBytes(font_bytes: []const u8) anyerror!Font {
|
|
try initFreetype();
|
|
return .{
|
|
.face = try freetype.createFaceMemory(font_bytes, 0),
|
|
};
|
|
}
|
|
|
|
pub fn shape(f: *const Font, r: *TextRun) anyerror!void {
|
|
// Guess text segment properties.
|
|
r.buffer.guessSegmentProps();
|
|
// TODO: Optionally override specific text segment properties?
|
|
// buffer.setDirection(.ltr);
|
|
// buffer.setScript(.latin);
|
|
// buffer.setLanguage(harfbuzz.Language.fromString("en"));
|
|
|
|
const hb_face = harfbuzz.Face.fromFreetypeFace(f.face);
|
|
const hb_font = harfbuzz.Font.init(hb_face);
|
|
defer hb_font.deinit();
|
|
|
|
const font_size_pt = r.font_size_px / px_per_pt;
|
|
const font_size_pt_frac: i32 = @intFromFloat(font_size_pt * 256.0);
|
|
hb_font.setScale(font_size_pt_frac, font_size_pt_frac);
|
|
hb_font.setPTEM(font_size_pt);
|
|
|
|
// TODO: optionally pass shaping features?
|
|
hb_font.shape(r.buffer, null);
|
|
|
|
r.index = 0;
|
|
r.infos = r.buffer.getGlyphInfos();
|
|
r.positions = r.buffer.getGlyphPositions() orelse return error.OutOfMemory;
|
|
}
|
|
|
|
pub fn render(f: *Font, allocator: std.mem.Allocator, glyph_index: u32, opt: RenderOptions) anyerror!RenderedGlyph {
|
|
// TODO: DPI configuration
|
|
const dpi = 72;
|
|
const font_size_pt = opt.font_size_px / px_per_pt;
|
|
const font_size_pt_frac: i32 = @intFromFloat(font_size_pt * 64.0);
|
|
f.face.setCharSize(font_size_pt_frac, font_size_pt_frac, dpi, dpi) catch return error.RenderError;
|
|
f.face.loadGlyph(glyph_index, .{ .render = true }) catch return error.RenderError;
|
|
|
|
const glyph = f.face.glyph();
|
|
const glyph_bitmap = glyph.bitmap();
|
|
const buffer = glyph_bitmap.buffer();
|
|
const width = glyph_bitmap.width();
|
|
const height = glyph_bitmap.rows();
|
|
const margin = 1;
|
|
|
|
if (buffer == null) return RenderedGlyph{
|
|
.bitmap = null,
|
|
.width = width + (margin * 2),
|
|
.height = height + (margin * 2),
|
|
};
|
|
|
|
// Add 1 pixel padding to texture to avoid bleeding over other textures. This is part of the
|
|
// render() API contract.
|
|
f.bitmap.clearRetainingCapacity();
|
|
const num_pixels = (width + (margin * 2)) * (height + (margin * 2));
|
|
// TODO: handle OOM here
|
|
f.bitmap.ensureTotalCapacity(allocator, num_pixels) catch return error.RenderError;
|
|
f.bitmap.resize(allocator, num_pixels) catch return error.RenderError;
|
|
for (f.bitmap.items, 0..) |*data, i| {
|
|
const x = i % (width + (margin * 2));
|
|
const y = i / (width + (margin * 2));
|
|
if (x < margin or x > (width + margin) or y < margin or y > (height + margin)) {
|
|
data.* = RGBA32{ .r = 0, .g = 0, .b = 0, .a = 0 };
|
|
} else {
|
|
const alpha = buffer.?[((y - margin) * width + (x - margin)) % buffer.?.len];
|
|
data.* = RGBA32{ .r = 0, .g = 0, .b = 0, .a = alpha };
|
|
}
|
|
}
|
|
|
|
return RenderedGlyph{
|
|
.bitmap = f.bitmap.items,
|
|
.width = width + (margin * 2),
|
|
.height = height + (margin * 2),
|
|
};
|
|
}
|
|
|
|
pub fn deinit(f: *Font, allocator: std.mem.Allocator) void {
|
|
f.face.deinit();
|
|
f.bitmap.deinit(allocator);
|
|
}
|