diff --git a/build.zig b/build.zig index afd300ee..4595e076 100644 --- a/build.zig +++ b/build.zig @@ -6,6 +6,78 @@ fn addPackageCSourceFiles(exe: *std.Build.Step.Compile, dep: *std.Build.Dependen } } +const freetypeSources = [_][]const u8{ + "src/autofit/autofit.c", + "src/base/ftbase.c", + "src/base/ftsystem.c", + "src/base/ftdebug.c", + "src/base/ftbbox.c", + "src/base/ftbdf.c", + "src/base/ftbitmap.c", + "src/base/ftcid.c", + "src/base/ftfstype.c", + "src/base/ftgasp.c", + "src/base/ftglyph.c", + "src/base/ftgxval.c", + "src/base/ftinit.c", + "src/base/ftmm.c", + "src/base/ftotval.c", + "src/base/ftpatent.c", + "src/base/ftpfr.c", + "src/base/ftstroke.c", + "src/base/ftsynth.c", + "src/base/fttype1.c", + "src/base/ftwinfnt.c", + "src/bdf/bdf.c", + "src/bzip2/ftbzip2.c", + "src/cache/ftcache.c", + "src/cff/cff.c", + "src/cid/type1cid.c", + "src/gzip/ftgzip.c", + "src/lzw/ftlzw.c", + "src/pcf/pcf.c", + "src/pfr/pfr.c", + "src/psaux/psaux.c", + "src/pshinter/pshinter.c", + "src/psnames/psnames.c", + "src/raster/raster.c", + "src/sdf/sdf.c", + "src/sfnt/sfnt.c", + "src/smooth/smooth.c", + "src/svg/svg.c", + "src/truetype/truetype.c", + "src/type1/type1.c", + "src/type42/type42.c", + "src/winfonts/winfnt.c", +}; + +pub fn addFreetypeAndHarfbuzz(b: *std.Build, exe: *std.build.Step.Compile, c_lib: *std.build.Step.Compile, target: anytype, optimize: std.builtin.OptimizeMode, flags: []const []const u8) void { + const freetype = b.dependency("freetype", .{ + .target = target, + .optimize = optimize, + }); + const harfbuzz = b.dependency("harfbuzz", .{ + .target = target, + .optimize = optimize, + }); + + c_lib.defineCMacro("FT2_BUILD_LIBRARY", "1"); + c_lib.defineCMacro("HAVE_UNISTD_H", "1"); + c_lib.addIncludePath(freetype.path("include")); + exe.addIncludePath(freetype.path("include")); + addPackageCSourceFiles(c_lib, freetype, &freetypeSources, flags); + if (target.toTarget().os.tag == .macos) c_lib.addCSourceFile(.{ + .file = freetype.path("src/base/ftmac.c"), + .flags = &.{}, + }); + + c_lib.addIncludePath(harfbuzz.path("src")); + exe.addIncludePath(harfbuzz.path("src")); + c_lib.defineCMacro("HAVE_FREETYPE", "1"); + c_lib.addCSourceFile(.{.file = harfbuzz.path("src/harfbuzz.cc"), .flags = flags}); + c_lib.linkLibCpp(); +} + pub fn build(b: *std.build.Builder) !void { // Standard target options allows the person running `zig build` to choose // what target to build for. Here we do not override the defaults, which @@ -27,7 +99,7 @@ pub fn build(b: *std.build.Builder) !void { .target = target, .optimize = optimize, }); - const c_flags = &[_][]const u8{"-g", "-O3"}; + const c_flags = &[_][]const u8{"-g"}; c_lib.addIncludePath(.{.path = "include"}); exe.addIncludePath(.{.path = "include"}); c_lib.linkLibC(); @@ -98,15 +170,8 @@ pub fn build(b: *std.build.Builder) !void { c_lib.addCSourceFiles(.{.files = &[_][]const u8{"lib/glad.c", "lib/stb_image.c", "lib/stb_image_write.c", "lib/stb_vorbis.c"}, .flags = c_flags}); exe.addAnonymousModule("gui", .{.source_file = .{.path = "src/gui/gui.zig"}}); exe.addAnonymousModule("server", .{.source_file = .{.path = "src/server/server.zig"}}); - - const mach_freetype_dep = b.dependency("mach_freetype", .{ - .target = target, - .optimize = optimize, - }); - exe.addModule("freetype", mach_freetype_dep.module("mach-freetype")); - exe.addModule("harfbuzz", mach_freetype_dep.module("mach-harfbuzz")); - @import("mach_freetype").linkFreetype(mach_freetype_dep.builder, exe); - @import("mach_freetype").linkHarfbuzz(mach_freetype_dep.builder, exe); + + addFreetypeAndHarfbuzz(b, exe, c_lib, target, optimize, c_flags); //exe.strip = true; // Improves compile-time //exe.sanitize_thread = true; diff --git a/build.zig.zon b/build.zig.zon index b92df3b4..030d928c 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -3,9 +3,13 @@ .version = "0.0.0", .paths = .{""}, .dependencies = .{ - .mach_freetype = .{ - .url = "https://pkg.machengine.org/mach-freetype/f152278c6ccc6dcf6dcf4308bbe027a7598ffe63.tar.gz", - .hash = "1220e0de43cacb583b8f9efddcbe359398cfca17a39e265b56c8f2a10314eb8f7a5f" + .harfbuzz = .{ + .url = "https://github.com/harfbuzz/harfbuzz/archive/refs/tags/8.2.2.tar.gz", + .hash = "1220d27d0e3ddd47705cbe1505076058cb41649336d35ea51369ec8f042c35991e0f", + }, + .freetype = .{ + .url = "https://github.com/freetype/freetype/archive/refs/tags/VER-2-13-2.tar.gz", + .hash = "1220b81f6ecfb3fd222f76cf9106fecfa6554ab07ec7fdc4124b9bb063ae2adf969d", }, .portaudio = .{ .url = "https://github.com/PortAudio/portaudio/archive/refs/tags/v19.7.0.tar.gz", diff --git a/src/graphics.zig b/src/graphics.zig index 972893b5..8f6af3a5 100644 --- a/src/graphics.zig +++ b/src/graphics.zig @@ -3,8 +3,19 @@ const std = @import("std"); -const freetype = @import("freetype"); -const harfbuzz = @import("harfbuzz"); +pub const hbft = @cImport({ + @cInclude("freetype/ftadvanc.h"); + @cInclude("freetype/ftbbox.h"); + @cInclude("freetype/ftbitmap.h"); + @cInclude("freetype/ftcolor.h"); + @cInclude("freetype/ftlcdfil.h"); + @cInclude("freetype/ftsizes.h"); + @cInclude("freetype/ftstroke.h"); + @cInclude("freetype/fttrigon.h"); + @cInclude("freetype/ftsynth.h"); + @cInclude("hb.h"); + @cInclude("hb-ft.h"); +}); const vec = @import("vec.zig"); const Mat4f = vec.Mat4f; @@ -435,7 +446,7 @@ pub const TextBuffer = struct { alignment: Alignment, width: f32, - buffer: harfbuzz.Buffer, + buffer: ?*hbft.hb_buffer_t, glyphs: []GlyphData, lines: std.ArrayList(Line), lineBreaks: std.ArrayList(LineBreak), @@ -572,15 +583,22 @@ pub const TextBuffer = struct { } // Let harfbuzz do its thing: - var buffer = harfbuzz.Buffer.init() orelse return error.OutOfMemory; - defer buffer.deinit(); - buffer.addUTF32(parser.parsedText.items, 0, null); - buffer.setDirection(.ltr); - buffer.setScript(.common); - buffer.setLanguage(harfbuzz.Language.getDefault()); - TextRendering.harfbuzzFont.shape(buffer, null); - const glyphInfos = buffer.getGlyphInfos(); - const glyphPositions = buffer.getGlyphPositions().?; + var buffer = hbft.hb_buffer_create() orelse return error.OutOfMemory; + defer hbft.hb_buffer_destroy(buffer); + hbft.hb_buffer_add_utf32(buffer, parser.parsedText.items.ptr, @intCast(parser.parsedText.items.len), 0, @intCast(parser.parsedText.items.len)); + hbft.hb_buffer_set_direction(buffer, hbft.HB_DIRECTION_LTR); + hbft.hb_buffer_set_script(buffer, hbft.HB_SCRIPT_COMMON); + hbft.hb_buffer_set_language(buffer, hbft.hb_language_get_default()); + hbft.hb_shape(TextRendering.harfbuzzFont, buffer, null, 0); + var glyphInfos: []hbft.hb_glyph_info_t = undefined; + var glyphPositions: []hbft.hb_glyph_position_t = undefined; + { + var len: c_uint = 0; + glyphInfos.ptr = hbft.hb_buffer_get_glyph_infos(buffer, &len).?; + glyphPositions.ptr = hbft.hb_buffer_get_glyph_positions(buffer, &len).?; + glyphInfos.len = len; + glyphPositions.len = len; + } // Guess the text index from the given cluster indices. Only works if the number of glyphs and the number of characters in a cluster is the same. var textIndexGuess = try stackFallbackAllocator.alloc(u32, glyphInfos.len); @@ -893,27 +911,35 @@ const TextRendering = struct { alpha: c_int, } = undefined; - var freetypeLib: freetype.Library = undefined; - var freetypeFace: freetype.Face = undefined; - var harfbuzzFace: harfbuzz.Face = undefined; - var harfbuzzFont: harfbuzz.Font = undefined; + var freetypeLib: hbft.FT_Library = undefined; + var freetypeFace: hbft.FT_Face = undefined; + var harfbuzzFace: ?*hbft.hb_face_t = undefined; + var harfbuzzFont: ?*hbft.hb_font_t = undefined; var glyphMapping: std.ArrayList(u31) = undefined; var glyphData: std.ArrayList(Glyph) = undefined; var glyphTexture: [2]c_uint = undefined; var textureWidth: i32 = 1024; const textureHeight: i32 = 16; var textureOffset: i32 = 0; + + fn ftError(errorCode: hbft.FT_Error) !void { + if(errorCode == 0) return; + const errorString = hbft.FT_Error_String(errorCode); + std.log.err("Got freetype error {s}", .{errorString}); + return error.freetype; + } + fn init() !void { shader = try Shader.initAndGetUniforms("assets/cubyz/shaders/graphics/Text.vs", "assets/cubyz/shaders/graphics/Text.fs", &uniforms); shader.bind(); c.glUniform1i(uniforms.texture_sampler, 0); c.glUniform1f(uniforms.alpha, 1.0); c.glUniform2f(uniforms.fontSize, @floatFromInt(textureWidth), @floatFromInt(textureHeight)); - freetypeLib = try freetype.Library.init(); - freetypeFace = try freetypeLib.createFace("assets/cubyz/fonts/unscii-16-full.ttf", 0); - try freetypeFace.setPixelSizes(0, textureHeight); - harfbuzzFace = harfbuzz.Face.fromFreetypeFace(freetypeFace); - harfbuzzFont = harfbuzz.Font.init(harfbuzzFace); + try ftError(hbft.FT_Init_FreeType(&freetypeLib)); + try ftError(hbft.FT_New_Face(freetypeLib, "assets/cubyz/fonts/unscii-16-full.ttf", 0, &freetypeFace)); + try ftError(hbft.FT_Set_Pixel_Sizes(freetypeFace, 0, textureHeight)); + harfbuzzFace = hbft.hb_ft_face_create_referenced(freetypeFace); + harfbuzzFont = hbft.hb_font_create(harfbuzzFace); glyphMapping = std.ArrayList(u31).init(main.globalAllocator); glyphData = std.ArrayList(Glyph).init(main.globalAllocator); @@ -934,11 +960,11 @@ const TextRendering = struct { fn deinit() void { shader.deinit(); - freetypeLib.deinit(); + ftError(hbft.FT_Done_FreeType(freetypeLib)) catch {}; glyphMapping.deinit(); glyphData.deinit(); c.glDeleteTextures(2, &glyphTexture); - harfbuzzFont.deinit(); + hbft.hb_font_destroy(harfbuzzFont); } fn resizeTexture(newWidth: i32) !void { @@ -958,15 +984,15 @@ const TextRendering = struct { c.glUniform2f(uniforms.fontSize, @floatFromInt(textureWidth), @floatFromInt(textureHeight)); } - fn uploadData(bitmap: freetype.Bitmap) !void { - const width: i32 = @bitCast(bitmap.width()); - const height: i32 = @bitCast(bitmap.rows()); - const buffer = bitmap.buffer() orelse return; + fn uploadData(bitmap: hbft.FT_Bitmap) !void { + const width: i32 = @bitCast(bitmap.width); + const height: i32 = @bitCast(bitmap.rows); + const buffer = bitmap.buffer orelse return; if(textureOffset + width > textureWidth) { try resizeTexture(textureWidth*2); } c.glPixelStorei(c.GL_UNPACK_ALIGNMENT, 1); - c.glTexSubImage2D(c.GL_TEXTURE_2D, 0, textureOffset, 0, width, height, c.GL_RED, c.GL_UNSIGNED_BYTE, buffer.ptr); + c.glTexSubImage2D(c.GL_TEXTURE_2D, 0, textureOffset, 0, width, height, c.GL_RED, c.GL_UNSIGNED_BYTE, buffer); textureOffset += width; } @@ -975,17 +1001,17 @@ const TextRendering = struct { try glyphMapping.appendNTimes(0, index - glyphMapping.items.len + 1); } if(glyphMapping.items[index] == 0) {// glyph was not initialized yet. - try freetypeFace.loadGlyph(index, freetype.LoadFlags{.render = true}); - const glyph = freetypeFace.glyph(); - const bitmap = glyph.bitmap(); - const width = bitmap.width(); - const height = bitmap.rows(); + try ftError(hbft.FT_Load_Glyph(freetypeFace, index, hbft.FT_LOAD_RENDER)); + const glyph = freetypeFace.*.glyph; + const bitmap = glyph.*.bitmap; + const width = bitmap.width; + const height = bitmap.rows; glyphMapping.items[index] = @intCast(glyphData.items.len); (try glyphData.addOne()).* = Glyph { .textureX = textureOffset, .size = Vec2i{@intCast(width), @intCast(height)}, - .bearing = Vec2i{glyph.bitmapLeft(), 16 - glyph.bitmapTop()}, - .advance = @as(f32, @floatFromInt(glyph.advance().x))/@as(f32, 1 << 6), + .bearing = Vec2i{glyph.*.bitmap_left, 16 - glyph.*.bitmap_top}, + .advance = @as(f32, @floatFromInt(glyph.*.advance.x))/@as(f32, 1 << 6), }; try uploadData(bitmap); }