440 lines
18 KiB
Zig
440 lines
18 KiB
Zig
const std = @import("std");
|
|
const c = @import("c.zig").c;
|
|
// const rl = @import("raylib");
|
|
const kb = @import("kb");
|
|
const ft = @import("freetype");
|
|
const rp = @import("stb_rect_pack");
|
|
|
|
pub var ft_lib : ft.Library = undefined;
|
|
pub var shader : c.Shader = undefined;
|
|
// pub var shader : rl.Shader = undefined;
|
|
|
|
const ATLAS_SIZE = 4096;
|
|
|
|
const Glyph = struct {
|
|
index : u32,
|
|
|
|
x : i16,
|
|
y : i16,
|
|
width : i32,
|
|
height : i32,
|
|
dpi_width : f32,
|
|
dpi_height : f32,
|
|
// rwidth : i16,
|
|
// rheight : i16,
|
|
|
|
bearing_x : i32,
|
|
bearing_y : i32,
|
|
dpi_bearing_x : f32,
|
|
dpi_bearing_y : f32,
|
|
|
|
// y_max : i16,
|
|
// y_min : i16,
|
|
|
|
descent : i32,
|
|
ascent : i32,
|
|
dpi_descent : f32,
|
|
dpi_ascent : f32,
|
|
// advance : i16,
|
|
|
|
st0 : c.Vector2,
|
|
st1 : c.Vector2,
|
|
// st0 : rl.Vector2,
|
|
// st1 : rl.Vector2,
|
|
};
|
|
|
|
pub const Font = struct {
|
|
face : ft.Face,
|
|
glyphs : std.AutoHashMap(u32, Glyph),
|
|
kb : kb.kbts_font,
|
|
texture : c.Texture2D,
|
|
// texture : rl.Texture2D,
|
|
|
|
pub fn init(filename : []const u8, size : i32, allocator: std.mem.Allocator) !Font {
|
|
|
|
|
|
const font_data = try std.fs.cwd().readFileAlloc(allocator, filename, 10 * 1024 * 1024);
|
|
|
|
var face = try ft_lib.createFaceMemory(font_data, 0);
|
|
var glyphs = std.AutoHashMap(u32, Glyph).init(allocator);
|
|
try face.setCharSize(0, size * 64, 0, 96 * @as(u16, @intFromFloat(c.GetWindowScaleDPI().y)));
|
|
// try face.setPixelSizes(0, @intCast(size * @as(u16, @intFromFloat(c.GetWindowScaleDPI().y))));
|
|
// try face.setCharSize(0, size * 64, 0, 72 * @as(u16, @intFromFloat(rl.getWindowScaleDPI().y)));
|
|
// try face.setCharSize(0, size * 64, 0, 0);
|
|
try face.selectCharmap(.unicode);
|
|
|
|
var rects = try std.ArrayList(rp.stbrp_rect).initCapacity(allocator, 1024);
|
|
defer rects.deinit(allocator);
|
|
|
|
var nodes : [ATLAS_SIZE]rp.stbrp_node = std.mem.zeroes([ATLAS_SIZE]rp.stbrp_node);
|
|
|
|
var stbrpcontext : rp.stbrp_context = .{};
|
|
rp.stbrp_init_target(&stbrpcontext, ATLAS_SIZE, ATLAS_SIZE, &nodes, nodes.len);
|
|
|
|
const _atlas : []u8 = try allocator.alloc(u8, ATLAS_SIZE * ATLAS_SIZE);
|
|
defer allocator.free(_atlas);
|
|
@memset(_atlas, 0);
|
|
|
|
const atlas : c.Image = .{
|
|
// const atlas : rl.Image = .{
|
|
.data = _atlas.ptr,
|
|
.width = ATLAS_SIZE,
|
|
.height = ATLAS_SIZE,
|
|
.mipmaps = 1,
|
|
.format = c.PIXELFORMAT_UNCOMPRESSED_GRAYSCALE,
|
|
};
|
|
|
|
// var iter = face.iterateCharmap();
|
|
// while (iter.next()) |char| {
|
|
for (0..face.numGlyphs()) |index| {
|
|
// const i = iter.index;
|
|
|
|
// for (0..face.numGlyphs()) |i| {
|
|
try face.loadGlyph(@intCast(index), .{});
|
|
const bmp = face.glyph().bitmap();
|
|
|
|
try rects.append(allocator, .{
|
|
.id = @intCast(index),//@intCast(face.glyph().glyphIndex()),
|
|
.w = @max(1, @as(c_int, @intCast(bmp.width()))),
|
|
.h = @max(1, @as(c_int, @intCast(bmp.rows()))),
|
|
.x = 0,
|
|
.y = 0,
|
|
.was_packed = 0,
|
|
});
|
|
}
|
|
|
|
_ = rp.stbrp_pack_rects(&stbrpcontext, rects.items.ptr, @intCast(rects.items.len));
|
|
|
|
for (rects.items) |rect| {
|
|
const index : u32 = @intCast(rect.id);
|
|
if (rect.was_packed == 0) continue;
|
|
try face.loadGlyph(index, .{ .render = true });
|
|
|
|
const bmp = face.glyph().bitmap();
|
|
|
|
const pitch: usize = @intCast(bmp.pitch());
|
|
const w: usize = @intCast(bmp.width());
|
|
const h: usize = @intCast(bmp.rows());
|
|
|
|
const dst_x: usize = @intCast(rect.x);
|
|
const dst_y: usize = @intCast(rect.y);
|
|
|
|
if (bmp.buffer()) |buf| {
|
|
for (0..h) |y| {
|
|
const src = buf[y * pitch .. y * pitch + w];
|
|
|
|
const dst_index = (dst_y + y) * ATLAS_SIZE + dst_x;
|
|
const dst = _atlas[dst_index .. dst_index + w];
|
|
|
|
@memcpy(dst, src);
|
|
}
|
|
}
|
|
|
|
// std.log.info("{}", .{bmp.width()});
|
|
const width : i32 = @intCast(bmp.width());
|
|
// std.log.info("{}", .{bmp.rows()});
|
|
const height : i32 = @intCast(bmp.rows());
|
|
// const dpi_width : i32 = @intFromFloat(@as(f32, @floatFromInt(width)) / rl.getWindowScaleDPI().x);
|
|
// const dpi_height : i32 = @intFromFloat(@as(f32, @floatFromInt(height)) / rl.getWindowScaleDPI().y);
|
|
// std.log.info("{}", .{@as(f32, @floatFromInt(width)) / c.GetWindowScaleDPI().x});
|
|
const dpi_width : f32 = @as(f32, @floatFromInt(width)) / c.GetWindowScaleDPI().x;
|
|
// std.log.info("{}", .{@as(f32, @floatFromInt(height)) / c.GetWindowScaleDPI().y});
|
|
const dpi_height : f32 = @as(f32, @floatFromInt(height)) / c.GetWindowScaleDPI().y;
|
|
// std.log.info("{}", .{face.glyph().bitmapLeft()});
|
|
const bearing_x : i32 = face.glyph().bitmapLeft();
|
|
// std.log.info("{}", .{face.glyph().bitmapTop()});
|
|
const bearing_y : i32 = face.glyph().bitmapTop();
|
|
// std.log.info("{}", .{@as(f32, @floatFromInt(bearing_x)) / c.GetWindowScaleDPI().x});
|
|
const dpi_bearing_x : f32 = @as(f32, @floatFromInt(bearing_x)) / c.GetWindowScaleDPI().x;
|
|
// std.log.info("{}", .{@as(f32, @floatFromInt(bearing_y)) / c.GetWindowScaleDPI().y});
|
|
const dpi_bearing_y : f32 = @as(f32, @floatFromInt(bearing_y)) / c.GetWindowScaleDPI().y;
|
|
// std.log.info("{}", .{height - bearing_y});
|
|
const descent : i32 = height - bearing_y;
|
|
// std.log.info("{}", .{height - descent});
|
|
const ascent : i32 = height - descent;
|
|
// std.log.info("{}", .{dpi_height - dpi_bearing_y});
|
|
const dpi_descent : f32 = dpi_height - dpi_bearing_y;
|
|
// std.log.info("{}", .{dpi_height - dpi_descent});
|
|
const dpi_ascent : f32 = dpi_height - dpi_descent;
|
|
|
|
const x : i16 = @intCast(rect.x);
|
|
const y : i16 = @as(i16, @intCast(rect.y)) + @as(i16, @intCast(height));
|
|
|
|
const fx = @as(f32, @floatFromInt(x));
|
|
const fy = @as(f32, @floatFromInt(y));
|
|
const fw = @as(f32, @floatFromInt(width));
|
|
const fh = @as(f32, @floatFromInt(height));
|
|
const fs = @as(f32, ATLAS_SIZE);
|
|
|
|
const glyph = Glyph{
|
|
.x = x,
|
|
.y = y,
|
|
.index = index,
|
|
.bearing_x = bearing_x,
|
|
.bearing_y = bearing_y,
|
|
.dpi_bearing_x = dpi_bearing_x,
|
|
.dpi_bearing_y = dpi_bearing_y,
|
|
.width = width,
|
|
.height = height,
|
|
.dpi_width = dpi_width,
|
|
.dpi_height = dpi_height,
|
|
// .rwidth = @intCast(face.glyph().metrics().width >> 6),
|
|
// .rheight = @intCast(face.glyph().metrics().height >> 6),
|
|
.descent = descent,
|
|
.ascent = ascent,
|
|
.dpi_descent = dpi_descent,
|
|
.dpi_ascent = dpi_ascent,
|
|
// .advance = @intCast(face.glyph().advance().x >> 6),
|
|
.st0 = .{.x = (fx + 0.5) / fs, .y = (fy - fh + 0.5) / fs},
|
|
.st1 = .{.x = (fx + fw - 0.5) / fs, .y = (fy - 0.5) / fs},
|
|
};
|
|
|
|
try glyphs.put(glyph.index, glyph);
|
|
}
|
|
|
|
const texture = c.LoadTextureFromImage(atlas);
|
|
// const texture = try rl.Texture.fromImage(atlas);
|
|
|
|
var _kb = kb.kbts_FontFromMemory(font_data.ptr, @intCast(font_data.len), 0, null, null);
|
|
|
|
if (kb.kbts_FontIsValid(&_kb) != 0) {
|
|
std.log.info("[Error] [kb_text_shape] Failed to load the font.", .{});
|
|
}
|
|
|
|
allocator.free(font_data);
|
|
|
|
return .{
|
|
.face = face,
|
|
.glyphs = glyphs,
|
|
.kb = _kb,
|
|
.texture = texture,
|
|
};
|
|
}
|
|
|
|
pub fn deinit(self: *Font) void {
|
|
// var it = self.glyphs.valueIterator();
|
|
// while (it.next()) |g| {
|
|
// allocator.free(g.bitmap);
|
|
// }
|
|
self.glyphs.deinit();
|
|
self.face.deinit();
|
|
kb.kbts_FreeFont(&self.kb);
|
|
c.UnloadTexture(self.texture);
|
|
// self.texture.unload();
|
|
}
|
|
|
|
pub fn render_text(
|
|
self: *Font,
|
|
text: []const u8,
|
|
pos: c.Vector2,
|
|
// pos: rl.Vector2,
|
|
window_space: bool,
|
|
colour: c.Color,
|
|
background: c.Color,
|
|
// colour: rl.Color,
|
|
// background: rl.Color,
|
|
nice_background: bool,
|
|
count_descent: bool
|
|
) void {
|
|
if (text.len == 0)
|
|
return;
|
|
|
|
if (nice_background) {
|
|
self.render_text(
|
|
text,
|
|
c.Vector2Add(pos, .{.x = 3.0, .y = -3.0}),
|
|
// pos.add(.{.x = 3.0, .y = -3.0}),
|
|
window_space,
|
|
c.Color{ .r = 0, .g = 0, .b = 0, .a = 255 },
|
|
c.Color{ .r = 0, .g = 0, .b = 0, .a = 0 },
|
|
// rl.Color.init(0, 0, 0, 255),
|
|
// rl.Color.init(0, 0, 0, 0),
|
|
false,
|
|
false
|
|
);
|
|
}
|
|
|
|
var render_pos = pos;
|
|
_ = count_descent;
|
|
//_ = colour;
|
|
_ = background;
|
|
|
|
// const draw_size, const max_ascent, const max_descent = self.size_row(text, 0, 0);
|
|
// _ = max_ascent;
|
|
// _ = max_descent;
|
|
const font_ascent = @as(f32, @floatFromInt(self.face.size().metrics().ascender >> 6));
|
|
// const dpi_font_ascent = font_ascent / rl.getWindowScaleDPI().y;
|
|
|
|
render_pos.y += font_ascent;
|
|
|
|
// c.DrawLine(@intFromFloat(render_pos.x), @intFromFloat(render_pos.y), c.GetScreenWidth(), @intFromFloat(render_pos.y), c.RED);
|
|
// rl.drawLine(@intFromFloat(render_pos.x), @intFromFloat(render_pos.y), rl.getScreenWidth(), @intFromFloat(render_pos.y), .red);
|
|
|
|
const Context = kb.kbts_CreateShapeContext(null, null);
|
|
const kb_font = kb.kbts_ShapePushFont(Context, &self.kb);
|
|
if (kb_font == null) {
|
|
std.log.info("Could not open font!", .{});
|
|
return;
|
|
}
|
|
|
|
kb.kbts_ShapeBegin(Context, kb.KBTS_DIRECTION_DONT_KNOW, kb.KBTS_LANGUAGE_DONT_KNOW);
|
|
kb.kbts_ShapeUtf8(Context, text.ptr, @intCast(text.len), kb.KBTS_USER_ID_GENERATION_MODE_CODEPOINT_INDEX);
|
|
kb.kbts_ShapeEnd(Context);
|
|
|
|
var x_offset : f32 = 0;
|
|
var y_offset : f32 = 0;
|
|
|
|
c.BeginShaderMode(shader);
|
|
c.rlSetTexture(self.texture.id);
|
|
c.rlBegin(c.RL_QUADS);
|
|
// rl.beginShaderMode(shader);
|
|
// rl.gl.rlSetTexture(self.texture.id);
|
|
// rl.gl.rlBegin(rl.gl.rl_quads);
|
|
|
|
// Layout runs naively left to right.
|
|
var Run = kb.kbts_run{};
|
|
while (kb.kbts_ShapeRun(Context, &Run) != 0) {
|
|
if ((Run.Flags & kb.KBTS_BREAK_FLAG_LINE_HARD) != 0) {
|
|
|
|
}
|
|
|
|
var g : [*c]kb.kbts_glyph = null;
|
|
while (kb.kbts_GlyphIteratorNext(&Run.Glyphs, &g) != 0) {
|
|
const RunGlyph : *kb.kbts_glyph = g.?; // assert non-null
|
|
const CodepointIndex : i32 = RunGlyph.UserIdOrCodepointIndex;
|
|
var ShapeCodepoint : kb.kbts_shape_codepoint = undefined;
|
|
if (kb.kbts_ShapeGetShapeCodepoint(Context, CodepointIndex, &ShapeCodepoint) == 0) {
|
|
std.log.info("aah it's wrong!! idk how to handle the error rn!!", .{});
|
|
}
|
|
|
|
const advance_x = @as(f32, @floatFromInt(ft.mulFix(RunGlyph.AdvanceX, @intCast(self.face.size().metrics().x_scale)) >> 6));
|
|
const advance_y = @as(f32, @floatFromInt(ft.mulFix(RunGlyph.AdvanceY, @intCast(self.face.size().metrics().y_scale)) >> 6));
|
|
const dpi_advance_x = advance_x / c.GetWindowScaleDPI().x;
|
|
const dpi_advance_y = advance_y / c.GetWindowScaleDPI().y;
|
|
// const dpi_advance_x = advance_x / rl.getWindowScaleDPI().x;
|
|
// const dpi_advance_y = advance_y / rl.getWindowScaleDPI().y;
|
|
|
|
// glyph : *Glyph = table_find_pointer(*text.font.glyphs, RunGlyph.Id);
|
|
if (self.glyphs.getPtr(RunGlyph.Id)) |glyph| {
|
|
var v0 = c.Vector2{};
|
|
var v1 = c.Vector2{};
|
|
// const bx = @as(f32, @floatFromInt(glyph.bearing_x));
|
|
const by = glyph.dpi_bearing_y;
|
|
// const height = @as(f32, @floatFromInt(glyph.height));
|
|
// const descent = @as(f32, @floatFromInt(glyph.descent));
|
|
// const ascent = @as(f32, @floatFromInt(glyph.ascent));
|
|
// if (count_descent) {
|
|
v0 = c.Vector2Add(render_pos, .{ .x = x_offset,// + glyph.bearing_x,
|
|
.y = y_offset - by });// + draw_size.y });// /*- max_descent*/};
|
|
// v0 = render_pos.add(.{ .x = x_offset,// + glyph.bearing_x,
|
|
// .y = y_offset - by });// + draw_size.y });// /*- max_descent*/};
|
|
// } else {
|
|
// v0 = render_pos.add(.{
|
|
// .x = x_offset,// + glyph.bearing_x,
|
|
// .y = y_offset - @as(f32, @floatFromInt(glyph.height - glyph.bearing_y))
|
|
// });//* - glyph.height + draw_size.y*/};
|
|
// }
|
|
|
|
v1 = c.Vector2Add(v0, c.Vector2{ .x = glyph.dpi_width, .y = glyph.dpi_height });
|
|
// v1 = v0.add(rl.Vector2{ .x = @floatFromInt(glyph.width), .y = @floatFromInt(glyph.height) });
|
|
const p0 : c.Vector4 = .{ .x = v0.x, .y = v0.y, .z = 0.0, .w = 1.0 };
|
|
const p1 : c.Vector4 = .{ .x = v1.x, .y = v1.y, .z = 0.0, .w = 1.0 };
|
|
// const p0 : rl.Vector4 = .{ .x = v0.x, .y = v0.y, .z = 0.0, .w = 1.0 };
|
|
// const p1 : rl.Vector4 = .{ .x = v1.x, .y = v1.y, .z = 0.0, .w = 1.0 };
|
|
x_offset += dpi_advance_x;
|
|
y_offset += dpi_advance_y;
|
|
|
|
// #if Y_IS_UP {
|
|
// t0 := Vector2.{
|
|
// cast(float, glyph.x) / cast(float, ATLAS_SIZE),
|
|
// cast(float, glyph.y) / cast(float, ATLAS_SIZE)
|
|
// };
|
|
// t1 := t0 + Vector2.{
|
|
// cast(float, glyph.width) / cast(float, ATLAS_SIZE),
|
|
// -cast(float, glyph.height) / cast(float, ATLAS_SIZE)
|
|
// };
|
|
const st0 = glyph.st0;
|
|
const st1 = glyph.st1;
|
|
// } else {
|
|
// t0 := Vector2.{cast(float, glyph.x / ATLAS_SIZE), cast(float, glyph.y / ATLAS_SIZE)};
|
|
// t1 := t0 + .{cast(float, glyph.width / ATLAS_SIZE), cast(float, glyph.height / ATLAS_SIZE)};
|
|
// }
|
|
|
|
c.rlColor4ub(colour.r, colour.g, colour.b, colour.a);
|
|
c.rlNormal3f(0.0, 0.0, 1.0);
|
|
|
|
c.rlTexCoord2f(st0.x, st0.y); c.rlVertex2f(p0.x, p0.y);
|
|
c.rlTexCoord2f(st0.x, st1.y); c.rlVertex2f(p0.x, p1.y);
|
|
c.rlTexCoord2f(st1.x, st1.y); c.rlVertex2f(p1.x, p1.y);
|
|
c.rlTexCoord2f(st1.x, st0.y); c.rlVertex2f(p1.x, p0.y);
|
|
// rl.gl.rlColor4ub(colour.r, colour.g, colour.b, colour.a);
|
|
// rl.gl.rlNormal3f(0.0, 0.0, 1.0);
|
|
|
|
// rl.gl.rlTexCoord2f(st0.x, st0.y); rl.gl.rlVertex2f(p0.x, p0.y);
|
|
// rl.gl.rlTexCoord2f(st0.x, st1.y); rl.gl.rlVertex2f(p0.x, p1.y);
|
|
// rl.gl.rlTexCoord2f(st1.x, st1.y); rl.gl.rlVertex2f(p1.x, p1.y);
|
|
// rl.gl.rlTexCoord2f(st1.x, st0.y); rl.gl.rlVertex2f(p1.x, p0.y);
|
|
} else {
|
|
std.log.warn("kb_text_shape found the glyph, but we didn't load it from the font. index {}", .{RunGlyph.Id});
|
|
std.log.warn("advance_x {}", .{advance_x});
|
|
x_offset += dpi_advance_x;
|
|
y_offset += dpi_advance_y;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
c.rlEnd();
|
|
c.rlSetTexture(0);
|
|
c.EndShaderMode();
|
|
// rl.gl.rlEnd();
|
|
// rl.gl.rlSetTexture(0);
|
|
// rl.endShaderMode();
|
|
}
|
|
|
|
pub fn size_row(self: *Font, str: []const u8, n: i32, max_width: f32) struct {c.Vector2, f32, i32} {
|
|
_ = max_width;
|
|
_ = n;
|
|
const Context = kb.kbts_CreateShapeContext(null, null);
|
|
const kb_font = kb.kbts_ShapePushFont(Context, &self.kb);
|
|
if (kb_font == null) {
|
|
std.log.info("Could not open font!", .{});
|
|
return .{ c.Vector2.zero(), 0, 0 };
|
|
}
|
|
|
|
kb.kbts_ShapeBegin(Context, kb.KBTS_DIRECTION_DONT_KNOW, kb.KBTS_LANGUAGE_DONT_KNOW);
|
|
kb.kbts_ShapeUtf8(Context, str.ptr, @intCast(str.len), kb.KBTS_USER_ID_GENERATION_MODE_CODEPOINT_INDEX);
|
|
kb.kbts_ShapeEnd(Context);
|
|
|
|
var size = c.Vector2.zero();
|
|
var max_descent : f32 = 0;
|
|
|
|
// Layout runs naively left to right.
|
|
var Run = kb.kbts_run{};
|
|
while(kb.kbts_ShapeRun(Context, &Run) != 0) {
|
|
if ((Run.Flags & kb.KBTS_BREAK_FLAG_LINE_HARD) != 0) {
|
|
|
|
}
|
|
|
|
var g : [*c]kb.kbts_glyph = null;
|
|
while (kb.kbts_GlyphIteratorNext(&Run.Glyphs, &g) != 0) {
|
|
const RunGlyph : *kb.kbts_glyph = g.?; // assert non-null
|
|
const CodepointIndex : i32 = RunGlyph.UserIdOrCodepointIndex;
|
|
var ShapeCodepoint : kb.kbts_shape_codepoint = undefined;
|
|
if (kb.kbts_ShapeGetShapeCodepoint(Context, CodepointIndex, &ShapeCodepoint) == 0) {
|
|
std.log.info("aah it's wrong!! idk how to handle the error rn!!", .{});
|
|
}
|
|
|
|
if (self.glyphs.getPtr(@intCast(RunGlyph.Id))) |glyph| {
|
|
size.y = @max(size.y, @as(f32, @floatFromInt(glyph.dpi_height)));
|
|
// size.x += (ft.mulFix(RunGlyph.AdvanceX, self.face.size().metrics().x_scale) >> 6);
|
|
size.x += @floatFromInt(ft.mulFix(RunGlyph.AdvanceX, @intCast(self.face.size().metrics().x_scale)) >> 6);
|
|
max_descent = @max(max_descent, @as(f32, @floatFromInt(glyph.descent)));
|
|
}
|
|
}
|
|
}
|
|
|
|
return .{ size, max_descent, 0};
|
|
}
|
|
};
|