diff --git a/pkg/freetype/face.zig b/pkg/freetype/face.zig index 171b7fe76..c7648b3ca 100644 --- a/pkg/freetype/face.zig +++ b/pkg/freetype/face.zig @@ -106,6 +106,7 @@ pub const LoadFlags = packed struct { monochrome: bool = false, linear_design: bool = false, no_autohint: bool = false, + _padding1: u1 = 0, target_normal: bool = false, target_light: bool = false, target_mono: bool = false, @@ -114,7 +115,7 @@ pub const LoadFlags = packed struct { color: bool = false, compute_metrics: bool = false, bitmap_metrics_only: bool = false, - _padding: u10 = 0, + _padding2: u9 = 0, test { // This must always be an i32 size so we can bitcast directly. @@ -124,11 +125,12 @@ pub const LoadFlags = packed struct { test "bitcast" { const testing = std.testing; - const cval: i32 = c.FT_LOAD_RENDER | c.FT_LOAD_PEDANTIC; + const cval: i32 = c.FT_LOAD_RENDER | c.FT_LOAD_PEDANTIC | c.FT_LOAD_COLOR; const flags = @bitCast(LoadFlags, cval); try testing.expect(!flags.no_hinting); try testing.expect(flags.render); try testing.expect(flags.pedantic); + try testing.expect(flags.color); } }; diff --git a/src/Grid.zig b/src/Grid.zig index b048ac8a1..4eb89e350 100644 --- a/src/Grid.zig +++ b/src/Grid.zig @@ -44,6 +44,7 @@ texture: gl.Texture, texture_color: gl.Texture, /// The font atlas. +font_lib: font.Library, font_set: font.FallbackSet, /// Whether the cursor is visible or not. This is used to control cursor @@ -151,9 +152,12 @@ pub fn init( try font_set.families.ensureTotalCapacity(alloc, 2); errdefer font_set.deinit(alloc); + var font_lib = try font.Library.init(); + errdefer font_lib.deinit(); + // Regular text font_set.families.appendAssumeCapacity(fam: { - var fam = try font.Family.init(atlas); + var fam = font.Family.init(font_lib, atlas); errdefer fam.deinit(alloc); try fam.loadFaceFromMemory(.regular, face_ttf, font_size); try fam.loadFaceFromMemory(.bold, face_bold_ttf, font_size); @@ -162,7 +166,7 @@ pub fn init( // Emoji font_set.families.appendAssumeCapacity(fam: { - var fam_emoji = try font.Family.init(atlas_color); + var fam_emoji = font.Family.init(font_lib, atlas_color); errdefer fam_emoji.deinit(alloc); try fam_emoji.loadFaceFromMemory(.regular, face_emoji_ttf, font_size); break :fam fam_emoji; @@ -189,11 +193,11 @@ pub fn init( const fam = &font_set.families.items[0]; // This is the height reported by the font face - const face_height: i32 = fam.regular.?.unitsToPxY(fam.regular.?.ft_face.*.height); + const face_height: i32 = fam.regular.?.unitsToPxY(fam.regular.?.face.?.handle.*.height); // Determine the height of the underscore char const glyph = font_set.families.items[0].getGlyph('_', .regular).?; - var res: i32 = fam.regular.?.unitsToPxY(fam.regular.?.ft_face.*.ascender); + var res: i32 = fam.regular.?.unitsToPxY(fam.regular.?.face.?.handle.*.ascender); res -= glyph.offset_y; res += @intCast(i32, glyph.height); @@ -207,7 +211,7 @@ pub fn init( const fam = &font_set.families.items[0]; break :cell_baseline cell_height - @intToFloat( f32, - fam.regular.?.unitsToPxY(fam.regular.?.ft_face.*.ascender), + fam.regular.?.unitsToPxY(fam.regular.?.face.?.handle.*.ascender), ); }; log.debug("cell dimensions w={d} h={d} baseline={d}", .{ cell_width, cell_height, cell_baseline }); @@ -331,6 +335,7 @@ pub fn init( .vbo = vbo, .texture = tex, .texture_color = tex_color, + .font_lib = font_lib, .font_set = font_set, .cursor_visible = true, .cursor_style = .box, @@ -345,6 +350,7 @@ pub fn deinit(self: *Grid) void { family.deinit(self.alloc); } self.font_set.deinit(self.alloc); + self.font_lib.deinit(); self.texture.destroy(); self.texture_color.destroy(); diff --git a/src/Window.zig b/src/Window.zig index 728c398c5..6c855928f 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -240,8 +240,8 @@ pub fn create(alloc: Allocator, loop: libuv.Loop, config: *const Config) !*Windo const window_size = try window.getSize(); var grid = try Grid.init(alloc, .{ .points = config.@"font-size", - .xdpi = @floatToInt(u32, x_dpi), - .ydpi = @floatToInt(u32, y_dpi), + .xdpi = @floatToInt(u16, x_dpi), + .ydpi = @floatToInt(u16, y_dpi), }); try grid.setScreenSize(.{ .width = window_size.width, .height = window_size.height }); grid.background = .{ diff --git a/src/font/Face.zig b/src/font/Face.zig index a1d11449c..2cd53191b 100644 --- a/src/font/Face.zig +++ b/src/font/Face.zig @@ -7,53 +7,49 @@ const Face = @This(); const std = @import("std"); const builtin = @import("builtin"); +const freetype = @import("freetype"); const assert = std.debug.assert; const testing = std.testing; const Allocator = std.mem.Allocator; -const ftc = @import("freetype").c; const Atlas = @import("../Atlas.zig"); const Glyph = @import("main.zig").Glyph; +const Library = @import("main.zig").Library; -const ftok = ftc.FT_Err_Ok; const log = std.log.scoped(.font_face); -/// The FreeType library -ft_library: ftc.FT_Library, +/// The core library +library: Library, /// Our font face. -ft_face: ftc.FT_Face = null, +face: ?freetype.Face = null, /// If a DPI can't be calculated, this DPI is used. This is probably /// wrong on modern devices so it is highly recommended you get the DPI /// using whatever platform method you can. pub const default_dpi = if (builtin.os.tag == .macos) 72 else 96; -pub fn init(lib: ftc.FT_Library) !Face { +pub fn init(lib: Library) !Face { return Face{ - .ft_library = lib, + .library = lib, }; } pub fn deinit(self: *Face) void { - if (self.ft_face != null) { - if (ftc.FT_Done_Face(self.ft_face) != ftok) - log.err("failed to clean up font face", .{}); - } - + if (self.face) |face| face.deinit(); self.* = undefined; } /// The desired size for loading a font. pub const DesiredSize = struct { // Desired size in points - points: u32, + points: u16, // The DPI of the screen so we can convert points to pixels. - xdpi: u32 = default_dpi, - ydpi: u32 = default_dpi, + xdpi: u16 = default_dpi, + ydpi: u16 = default_dpi, // Converts points to pixels - pub fn pixels(self: DesiredSize) u32 { + pub fn pixels(self: DesiredSize) u16 { // 1 point = 1/72 inch return (self.points * self.ydpi) / 72; } @@ -65,86 +61,66 @@ pub fn loadFaceFromMemory( source: [:0]const u8, size: DesiredSize, ) !void { - assert(self.ft_face == null); + assert(self.face == null); - if (ftc.FT_New_Memory_Face( - self.ft_library, - source.ptr, - @intCast(c_long, source.len), - 0, - &self.ft_face, - ) != ftok) return error.FaceLoadFailed; - errdefer { - _ = ftc.FT_Done_Face(self.ft_face); - self.ft_face = null; - } + const face = try self.library.lib.initMemoryFace(source, 0); + errdefer face.deinit(); - if (ftc.FT_Select_Charmap(self.ft_face, ftc.FT_ENCODING_UNICODE) != ftok) - return error.FaceLoadFailed; + try face.selectCharmap(.unicode); // If we have fixed sizes, we just have to try to pick the one closest // to what the user requested. Otherwise, we can choose an arbitrary // pixel size. - if (!ftc.FT_HAS_FIXED_SIZES(self.ft_face)) { - const size_26dot6 = size.points << 6; // mult by 64 - if (ftc.FT_Set_Char_Size(self.ft_face, 0, size_26dot6, size.xdpi, size.ydpi) != ftok) - return error.FaceLoadFailed; - } else try self.selectSizeNearest(size.pixels()); + if (!face.hasFixedSizes()) { + const size_26dot6 = @intCast(i32, size.points << 6); // mult by 64 + try face.setCharSize(0, size_26dot6, size.xdpi, size.ydpi); + } else try selectSizeNearest(face, size.pixels()); + + // Success, persist + self.face = face; } /// Selects the fixed size in the loaded face that is closest to the /// requested pixel size. -fn selectSizeNearest(self: *Face, size: u32) !void { - var i: usize = 0; - var best_i: usize = 0; +fn selectSizeNearest(face: freetype.Face, size: u32) !void { + var i: i32 = 0; + var best_i: i32 = 0; var best_diff: i32 = 0; - while (i < self.ft_face.*.num_fixed_sizes) : (i += 1) { - const diff = @intCast(i32, size) - @intCast(i32, self.ft_face.*.available_sizes[i].width); + while (i < face.handle.*.num_fixed_sizes) : (i += 1) { + const width = face.handle.*.available_sizes[@intCast(usize, i)].width; + const diff = @intCast(i32, size) - @intCast(i32, width); if (i == 0 or diff < best_diff) { best_diff = diff; best_i = i; } } - if (ftc.FT_Select_Size(self.ft_face, @intCast(c_int, best_i)) != ftok) - return error.FaceSelectSizeFailed; + try face.selectSize(best_i); } /// Load a glyph for this face. The codepoint can be either a u8 or /// []const u8 depending on if you know it is ASCII or must be UTF-8 decoded. pub fn loadGlyph(self: Face, alloc: Allocator, atlas: *Atlas, cp: u32) !Glyph { - assert(self.ft_face != null); + const face = self.face.?; // We need a UTF32 codepoint for freetype - const glyph_index = glyph_index: { - //log.warn("glyph load: {x}", .{cp}); - const idx = ftc.FT_Get_Char_Index(self.ft_face, cp); - if (idx > 0) break :glyph_index idx; - - // Unknown glyph. - //log.warn("glyph not found: {x}", .{cp}); - return error.GlyphNotFound; - }; + const glyph_index = face.getCharIndex(cp) orelse return error.GlyphNotFound; //log.warn("glyph index: {}", .{glyph_index}); // If our glyph has color, we want to render the color - var load_flags: c_int = ftc.FT_LOAD_RENDER; - if (ftc.FT_HAS_COLOR(self.ft_face)) load_flags |= @intCast(c_int, ftc.FT_LOAD_COLOR); + try face.loadGlyph(glyph_index, .{ + .render = true, + .color = face.hasColor(), + }); - if (ftc.FT_Load_Glyph( - self.ft_face, - glyph_index, - load_flags, - ) != ftok) return error.LoadGlyphFailed; - - const glyph = self.ft_face.*.glyph; + const glyph = face.handle.*.glyph; const bitmap = glyph.*.bitmap; // Ensure we know how to work with the font format. And assure that // or color depth is as expected on the texture atlas. const format: Atlas.Format = switch (bitmap.pixel_mode) { - ftc.FT_PIXEL_MODE_GRAY => .greyscale, - ftc.FT_PIXEL_MODE_BGRA => .rgba, + freetype.c.FT_PIXEL_MODE_GRAY => .greyscale, + freetype.c.FT_PIXEL_MODE_BGRA => .rgba, else => { log.warn("pixel mode={}", .{bitmap.pixel_mode}); @panic("unsupported pixel mode"); @@ -204,27 +180,28 @@ pub fn loadGlyph(self: Face, alloc: Allocator, atlas: *Atlas, cp: u32) !Glyph { /// Convert 16.6 pixel format to pixels based on the scale factor of the /// current font size. pub fn unitsToPxY(self: Face, units: i32) i32 { - return @intCast(i32, ftc.FT_MulFix(units, self.ft_face.*.size.*.metrics.y_scale) >> 6); + return @intCast(i32, freetype.mulFix( + units, + @intCast(i32, self.face.?.handle.*.size.*.metrics.y_scale), + ) >> 6); } /// Convert 26.6 pixel format to f32 -fn f26dot6ToFloat(v: ftc.FT_F26Dot6) f32 { +fn f26dot6ToFloat(v: freetype.c.FT_F26Dot6) f32 { return @intToFloat(f32, v >> 6); } test { const testFont = @import("test.zig").fontRegular; - - var ft_lib: ftc.FT_Library = undefined; - if (ftc.FT_Init_FreeType(&ft_lib) != ftok) - return error.FreeTypeInitFailed; - defer _ = ftc.FT_Done_FreeType(ft_lib); - const alloc = testing.allocator; + + var lib = try Library.init(); + defer lib.deinit(); + var atlas = try Atlas.init(alloc, 512, .greyscale); defer atlas.deinit(alloc); - var font = try init(ft_lib); + var font = try init(lib); defer font.deinit(); try font.loadFaceFromMemory(testFont, .{ .points = 12 }); @@ -237,18 +214,16 @@ test { } test "color emoji" { + const alloc = testing.allocator; const testFont = @import("test.zig").fontEmoji; - var ft_lib: ftc.FT_Library = undefined; - if (ftc.FT_Init_FreeType(&ft_lib) != ftok) - return error.FreeTypeInitFailed; - defer _ = ftc.FT_Done_FreeType(ft_lib); + var lib = try Library.init(); + defer lib.deinit(); - const alloc = testing.allocator; var atlas = try Atlas.init(alloc, 512, .rgba); defer atlas.deinit(alloc); - var font = try init(ft_lib); + var font = try init(lib); defer font.deinit(); try font.loadFaceFromMemory(testFont, .{ .points = 12 }); diff --git a/src/font/FallbackSet.zig b/src/font/FallbackSet.zig index bf9d2ac3c..dafe51e76 100644 --- a/src/font/FallbackSet.zig +++ b/src/font/FallbackSet.zig @@ -9,6 +9,7 @@ const Allocator = std.mem.Allocator; const Atlas = @import("../Atlas.zig"); const Family = @import("main.zig").Family; +const Library = @import("main.zig").Library; const Glyph = @import("main.zig").Glyph; const Style = @import("main.zig").Style; const codepoint = @import("main.zig").codepoint; @@ -123,14 +124,17 @@ test { const testing = std.testing; const alloc = testing.allocator; + var lib = try Library.init(); + defer lib.deinit(); + var set: FallbackSet = .{}; try set.families.append(alloc, fam: { - var fam = try Family.init(try Atlas.init(alloc, 512, .greyscale)); + var fam = Family.init(lib, try Atlas.init(alloc, 512, .greyscale)); try fam.loadFaceFromMemory(.regular, fontRegular, .{ .points = 48 }); break :fam fam; }); try set.families.append(alloc, fam: { - var fam = try Family.init(try Atlas.init(alloc, 512, .rgba)); + var fam = Family.init(lib, try Atlas.init(alloc, 512, .rgba)); try fam.loadFaceFromMemory(.regular, fontEmoji, .{ .points = 48 }); break :fam fam; }); diff --git a/src/font/Family.zig b/src/font/Family.zig index 5f28d0f01..20ea097ef 100644 --- a/src/font/Family.zig +++ b/src/font/Family.zig @@ -5,27 +5,22 @@ const Family = @This(); const std = @import("std"); const Allocator = std.mem.Allocator; const Atlas = @import("../Atlas.zig"); -const ftc = @import("freetype").c; -const ftok = ftc.FT_Err_Ok; const Face = @import("main.zig").Face; const Glyph = @import("main.zig").Glyph; const Style = @import("main.zig").Style; const testFont = @import("test.zig").fontRegular; const codepoint = @import("main.zig").codepoint; +const Library = @import("main.zig").Library; const log = std.log.scoped(.font_family); -// NOTE(mitchellh): I think eventually atlas and the freetype lib fields -// move even higher up into another struct that manages sets of font families -// in order to support fallback and so on. - /// The texture atlas where all the font glyphs are rendered. /// This is NOT owned by the Family, deinitialization must /// be manually done. atlas: Atlas, -/// The FreeType library, initialized by this init func. -ft_library: ftc.FT_Library, +/// The library shared state. +lib: Library, /// The glyphs that are loaded into the atlas, keyed by codepoint. glyphs: std.AutoHashMapUnmanaged(GlyphKey, Glyph) = .{}, @@ -41,16 +36,11 @@ const GlyphKey = struct { codepoint: u32, }; -pub fn init(atlas: Atlas) !Family { - var res = Family{ +pub fn init(lib: Library, atlas: Atlas) Family { + return .{ + .lib = lib, .atlas = atlas, - .ft_library = undefined, }; - - if (ftc.FT_Init_FreeType(&res.ft_library) != ftok) - return error.FreeTypeInitFailed; - - return res; } pub fn deinit(self: *Family, alloc: Allocator) void { @@ -59,9 +49,6 @@ pub fn deinit(self: *Family, alloc: Allocator) void { if (self.regular) |*face| face.deinit(); if (self.bold) |*face| face.deinit(); - if (ftc.FT_Done_FreeType(self.ft_library) != ftok) - log.err("failed to clean up FreeType", .{}); - self.* = undefined; } @@ -74,7 +61,7 @@ pub fn loadFaceFromMemory( source: [:0]const u8, size: Face.DesiredSize, ) !void { - var face = try Face.init(self.ft_library); + var face = try Face.init(self.lib); errdefer face.deinit(); try face.loadFaceFromMemory(source, size); @@ -145,7 +132,11 @@ pub fn addGlyph(self: *Family, alloc: Allocator, v: anytype, style: Style) !*Gly test { const testing = std.testing; const alloc = testing.allocator; - var fam = try init(try Atlas.init(alloc, 512, .greyscale)); + + var lib = try Library.init(); + defer lib.deinit(); + + var fam = init(lib, try Atlas.init(alloc, 512, .greyscale)); defer fam.deinit(alloc); defer fam.atlas.deinit(alloc); try fam.loadFaceFromMemory(.regular, testFont, .{ .points = 12 }); diff --git a/src/font/Library.zig b/src/font/Library.zig new file mode 100644 index 000000000..62a4e77d0 --- /dev/null +++ b/src/font/Library.zig @@ -0,0 +1,19 @@ +//! A library represents the shared state that the underlying font +//! library implementation(s) require per-process. +//! +//! In the future, this will be abstracted so that the underlying text +//! engine might not be Freetype and may be something like Core Text, +//! but the API will remain the same. +const Library = @This(); + +const freetype = @import("freetype"); + +lib: freetype.Library, + +pub fn init() freetype.Error!Library { + return Library{ .lib = try freetype.Library.init() }; +} + +pub fn deinit(self: *Library) void { + self.lib.deinit(); +} diff --git a/src/font/main.zig b/src/font/main.zig index d7919da28..4560d0e0f 100644 --- a/src/font/main.zig +++ b/src/font/main.zig @@ -4,10 +4,7 @@ pub const Face = @import("Face.zig"); pub const Family = @import("Family.zig"); pub const Glyph = @import("Glyph.zig"); pub const FallbackSet = @import("FallbackSet.zig"); - -/// Embedded fonts (for now) -pub const fontRegular = @import("test.zig").fontRegular; -pub const fontBold = @import("test.zig").fontBold; +pub const Library = @import("Library.zig"); /// The styles that a family can take. pub const Style = enum { @@ -29,8 +26,5 @@ pub fn codepoint(v: anytype) u32 { } test { - _ = Face; - _ = Family; - _ = Glyph; - _ = FallbackSet; + @import("std").testing.refAllDecls(@This()); }