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}; } };