11const std = @import ("std" );
2- const c = @import ("ft.zig" ).c ;
2+ const ft = @import ("ft.zig" ).c ;
3+ const kb = @import ("ft.zig" ).kb ;
34const TextRun = @import ("TextRun.zig" );
45const px_per_pt = @import ("../main.zig" ).px_per_pt ;
56const RenderedGlyph = @import ("../main.zig" ).RenderedGlyph ;
@@ -10,75 +11,64 @@ const Font = @This();
1011
1112var freetype_ready_mu : std.Thread.Mutex = .{};
1213var freetype_ready : bool = false ;
13- var ft_library : c .FT_Library = null ;
14+ var ft_library : ft .FT_Library = null ;
1415
15- face : c.FT_Face ,
16+ face : ft.FT_Face ,
17+ font_bytes : []const u8 ,
1618bitmap : std .ArrayListUnmanaged (RGBA32 ) = .{},
1719
1820pub fn initFreetype () ! void {
1921 freetype_ready_mu .lock ();
2022 defer freetype_ready_mu .unlock ();
2123 if (! freetype_ready ) {
22- if (c .FT_Init_FreeType (& ft_library ) != 0 ) return error .FreetypeInitFailed ;
24+ if (ft .FT_Init_FreeType (& ft_library ) != 0 ) return error .FreetypeInitFailed ;
2325 freetype_ready = true ;
2426 }
2527}
2628
29+ /// font_bytes must remain valid until .deinit() is called.
2730pub fn initBytes (font_bytes : []const u8 ) anyerror ! Font {
2831 try initFreetype ();
29- var face : c .FT_Face = null ;
30- if (c .FT_New_Memory_Face (ft_library , font_bytes .ptr , @intCast (font_bytes .len ), 0 , & face ) != 0 )
32+ var face : ft .FT_Face = null ;
33+ if (ft .FT_New_Memory_Face (ft_library , font_bytes .ptr , @intCast (font_bytes .len ), 0 , & face ) != 0 )
3134 return error .FreetypeError ;
32- return .{ .face = face };
35+ return .{ .face = face , . font_bytes = font_bytes };
3336}
3437
35- pub fn shape (f : * const Font , r : * TextRun ) anyerror ! void {
36- // Guess text segment properties.
37- c .hb_buffer_guess_segment_properties (r .buffer );
38- // TODO: Optionally override specific text segment properties?
39- // hb_buffer_set_direction(r.buffer, ...);
40- // hb_buffer_set_script(r.buffer, ...);
41- // hb_buffer_set_language(r.buffer, hb_language_from_string("en", -1));
42-
38+ pub fn shape (f : * Font , r : * TextRun ) anyerror ! void {
39+ const kb_font = kb .kbts_ShapePushFontFromMemory (
40+ r .context ,
41+ @constCast (f .font_bytes .ptr ),
42+ @intCast (f .font_bytes .len ),
43+ 0 ,
44+ ) orelse return error .RenderError ;
45+
46+ // Get font metrics to know UnitsPerEm for scaling.
47+ var info : kb.kbts_font_info2_1 = std .mem .zeroes (kb .kbts_font_info2_1 );
48+ info .Base .Size = @sizeOf (kb .kbts_font_info2_1 );
49+ kb .kbts_GetFontInfo2 (kb_font , @ptrCast (& info ));
50+ if (info .UnitsPerEm == 0 ) return error .InvalidFont ;
51+ r .units_per_em = @floatFromInt (info .UnitsPerEm );
52+
53+ // Set FreeType face size for glyph rendering (still needed for rasterization).
4354 const font_size_pt = r .font_size_px / px_per_pt ;
4455 const font_size_pt_frac : i32 = @intFromFloat (font_size_pt * 64.0 );
45- if (c .FT_Set_Char_Size (f .face , font_size_pt_frac , font_size_pt_frac , 0 , 0 ) != 0 )
56+ if (ft .FT_Set_Char_Size (f .face , font_size_pt_frac , font_size_pt_frac , 0 , 0 ) != 0 )
4657 return error .RenderError ;
4758
48- const hb_face = c .hb_ft_face_create_referenced (f .face ) orelse return error .RenderError ;
49- const hb_font = c .hb_font_create (hb_face ) orelse return error .RenderError ;
50- defer c .hb_font_destroy (hb_font );
51-
52- c .hb_font_set_scale (hb_font , font_size_pt_frac , font_size_pt_frac );
53- c .hb_font_set_ptem (hb_font , font_size_pt );
54-
55- // TODO: optionally pass shaping features?
56- c .hb_shape (hb_font , r .buffer , null , 0 );
57-
58- r .index = 0 ;
59- var info_count : u32 = 0 ;
60- const infos_ptr = c .hb_buffer_get_glyph_infos (r .buffer , & info_count );
61- r .infos = if (infos_ptr ) | p | p [0.. info_count ] else return error .OutOfMemory ;
62-
63- var pos_count : u32 = 0 ;
64- const pos_ptr = c .hb_buffer_get_glyph_positions (r .buffer , & pos_count );
65- r .positions = if (pos_ptr ) | p | p [0.. pos_count ] else return error .OutOfMemory ;
66-
67- for (r .positions , r .infos ) | * pos , info | {
68- const glyph_index = info .codepoint ;
69- if (c .FT_Load_Glyph (f .face , glyph_index , c .FT_LOAD_DEFAULT ) != 0 )
70- return error .RenderError ;
71- const glyph = f .face .* .glyph ;
72- const metrics = glyph .* .metrics ;
73- pos .* .x_offset += @intCast (metrics .horiBearingX );
74- pos .* .y_offset += @intCast (metrics .horiBearingY );
75- // TODO: use vertBearingX / vertBearingY for vertical layouts
76- }
59+ // Store FreeType face for bearing lookups during glyph iteration.
60+ r .ft_face = f .face ;
61+
62+ // Perform the full shaping pass: begin → add text → end.
63+ // TODO(font): allow configuration of direction/language by user
64+ kb .kbts_ShapeBegin (r .context , kb .KBTS_DIRECTION_DONT_KNOW , kb .KBTS_LANGUAGE_DONT_KNOW );
65+ kb .kbts_ShapeUtf8 (r .context , r .utf8_text .ptr , @intCast (r .utf8_text .len ), kb .KBTS_USER_ID_GENERATION_MODE_CODEPOINT_INDEX );
66+ kb .kbts_ShapeEnd (r .context );
7767}
7868
7969pub fn render (f : * Font , allocator : std.mem.Allocator , glyph_index : u32 , opt : RenderOptions ) anyerror ! RenderedGlyph {
8070 _ = opt ;
81- if (c .FT_Load_Glyph (f .face , glyph_index , c .FT_LOAD_RENDER ) != 0 )
71+ if (ft .FT_Load_Glyph (f .face , glyph_index , ft .FT_LOAD_RENDER ) != 0 )
8272 return error .RenderError ;
8373
8474 const glyph = f .face .* .glyph ;
@@ -120,6 +110,6 @@ pub fn render(f: *Font, allocator: std.mem.Allocator, glyph_index: u32, opt: Ren
120110}
121111
122112pub fn deinit (f : * Font , allocator : std.mem.Allocator ) void {
123- _ = c .FT_Done_Face (f .face );
113+ _ = ft .FT_Done_Face (f .face );
124114 f .bitmap .deinit (allocator );
125115}
0 commit comments