From 0c2d309f2af7e66fdb99bb5dac2a2237635151aa Mon Sep 17 00:00:00 2001 From: IntegratedQuantum Date: Sat, 21 Oct 2023 10:35:39 +0200 Subject: [PATCH] Ditch mach-freetype: now building freetype and harfbuzz directly. Having zig bindings was certainly nice to have, but overall the code isn't too different really. One nice advantage of building freetype and harfbuzz directly is that we can get rid of some unnecessary dependencies, like brotli. Also, since that was the only zig dependency outside the standard library, this finally gives me the freedom to update zig whenever I want. This is a necessary step for #117. Resolves #139 --- build.zig | 85 +++++++++++++++++++++++++++++++++++++----- build.zig.zon | 10 +++-- src/graphics.zig | 96 ++++++++++++++++++++++++++++++------------------ 3 files changed, 143 insertions(+), 48 deletions(-) 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); }