@@ -8,6 +8,8 @@ const Allocator = std.mem.Allocator;
88
99const Parser = parser .Parser ;
1010
11+ const UTF8 = std .unicode .Utf8View ;
12+
1113const Writer = byte_writer .ByteWriter ;
1214
1315const AutoHashMap = std .AutoHashMap ;
@@ -102,11 +104,16 @@ pub const BoundingBox = struct {
102104 }
103105};
104106
107+ const GlyphInfo = struct {
108+ bbox : BoundingBox ,
109+ is_composite : bool ,
110+ };
111+
105112pub const Glyph = struct {
106113 id : u16 = 0 ,
107114 advance_width : u16 = 0 ,
108115 left_side_bearing : i16 = 0 ,
109- data : [] const u8 = &[ _ ] u8 {} ,
116+ is_composite : bool = false ,
110117 bbox : BoundingBox = BoundingBox .empty (),
111118 has_outline : bool = false ,
112119 _initialized : bool = false ,
@@ -120,6 +127,11 @@ pub const Glyph = struct {
120127 }
121128};
122129
130+ pub const BuildSubsetterOptions = struct {
131+ modified_time : ? i64 = null ,
132+ input_text : []const u8 = &[_ ]u8 {},
133+ };
134+
123135const Reader = struct {
124136 const Self = @This ();
125137
@@ -201,34 +213,40 @@ const Reader = struct {
201213 const hmtx = hmtx_table .cast (table .Hmtx );
202214 const metrics = hmtx .get_metrics (gid );
203215
204- const bbox = blk : {
205- const loca_table = self .t .parser .parsed_tables .loca .? ;
206- const loca = loca_table .cast (table .Loca );
216+ const loca_table = self .t .parser .parsed_tables .loca .? ;
217+ const loca = loca_table .cast (table .Loca );
218+ const has_outline = loca .has_glyph_data (gid );
219+
220+ const glyph_info = if (has_outline ) info : {
207221 const glyf_table = self .t .parser .parsed_tables .glyf .? ;
208222 const glyf = glyf_table .cast (table .Glyf );
209223 const offset = loca .get_glyph_offset (gid ).? ;
210224 const parsed_glyf = try glyf .parse_glyph (offset );
211225 defer parsed_glyf .deinit ();
212226 const header = parsed_glyf .get_header ();
213- break : blk BoundingBox {
227+ const bbox = BoundingBox {
214228 .x_min = header .x_min ,
215229 .y_min = header .y_min ,
216230 .x_max = header .x_max ,
217231 .y_max = header .y_max ,
218232 };
233+ const is_composite = header .is_composite ();
234+ break :info GlyphInfo {
235+ .bbox = bbox ,
236+ .is_composite = is_composite ,
237+ };
238+ } else GlyphInfo {
239+ .bbox = BoundingBox .empty (),
240+ .is_composite = false ,
219241 };
220242
221- const has_outline = blk : {
222- const loca_table = self .t .parser .parsed_tables .loca .? ;
223- const loca = loca_table .cast (table .Loca );
224- break :blk loca .has_glyph_data (gid );
225- };
226243 var glyh = Glyph {
227244 .id = gid ,
228245 .advance_width = metrics .advance_width ,
229246 .left_side_bearing = metrics .left_side_bearing ,
230247 .has_outline = has_outline ,
231- .bbox = bbox ,
248+ .bbox = glyph_info .bbox ,
249+ .is_composite = glyph_info .is_composite ,
232250 };
233251 glyh .mark_as_done ();
234252 try self .glyph_cache .put (code_point , glyh );
@@ -243,9 +261,10 @@ const Subsetter = struct {
243261 allocator : Allocator ,
244262 const Self = @This ();
245263
246- pub fn init (t : * ttf ) Subsetter {
264+ pub fn init (t : * ttf ) ! Subsetter {
247265 return Self {
248266 .t = t ,
267+ .r = try t .reader (),
249268 .allocator = t .allocator ,
250269 };
251270 }
@@ -254,23 +273,151 @@ const Subsetter = struct {
254273
255274 }
256275
257- pub fn build_subset (self : * Self ) ! []u8 {
276+ pub fn build_subset (self : * Self , options : BuildSubsetterOptions ) ! void {
277+ var buffer = Writer (u8 ).init (self .allocator );
278+ errdefer buffer .deinit ();
279+ var required_glyphs = AutoHashMap (u16 , Glyph ).init (self .allocator );
280+ defer required_glyphs .deinit ();
281+
282+ var utf8_view = try UTF8 .init (options .input_text );
283+ var iterator = utf8_view .iterator ();
284+
285+ while (iterator .nextCodepoint ()) | codepoint | {
286+ const glyph = try self .r .get_glyph_info (codepoint );
287+ if (glyph .id != 0 ) {
288+ try required_glyphs .put (glyph .id , glyph );
289+ }
290+ }
291+ try required_glyphs .put (0 , Glyph {});
292+ try self .collect_glyph_ids_recursive (& required_glyphs );
293+ var glyph_ids = try self .allocator .alloc (u16 , required_glyphs .count ());
294+ defer self .allocator .free (glyph_ids );
295+ var iter = required_glyphs .iterator ();
296+ var i : usize = 0 ;
297+ while (iter .next ()) | entry | {
298+ glyph_ids [i ] = entry .key_ptr .* ;
299+ i += 1 ;
300+ }
301+ std .sort .heap (u16 , glyph_ids , {}, std .sort .asc (u16 ));
302+ }
303+
304+ fn collect_glyph_ids_recursive (self : * Self , required_glyphs : * AutoHashMap (u16 , Glyph )) ! void {
305+ var new_glyphs = AutoHashMap (u16 , Glyph ).init (self .allocator );
306+ defer new_glyphs .deinit ();
307+
308+ var iter = required_glyphs .iterator ();
309+ while (iter .next ()) | entry | {
310+ const glyph_id = entry .key_ptr .* ;
311+ const glyph = entry .value_ptr .* ;
312+
313+ if (glyph .is_composite ) {
314+ const glyf_table = self .t .parser .parsed_tables .glyf .? ;
315+ const glyf = glyf_table .cast (table .Glyf );
316+ const loca_table = self .t .parser .parsed_tables .loca .? ;
317+ const loca = loca_table .cast (table .Loca );
318+ const glyph_offset = loca .get_glyph_offset (glyph_id ).? ;
319+ var parsed_glyph = try glyf .parse_glyph (glyph_offset );
320+ defer parsed_glyph .deinit ();
321+ const composite_glyph = parsed_glyph .composite ;
322+
323+ for (composite_glyph .components ) | component | {
324+ const component_glyph_id = component .glyph_index ;
325+ if (! required_glyphs .contains (component_glyph_id )) {
326+ const component_glyph = try self .r .get_glyph_info (component_glyph_id );
327+ try new_glyphs .put (component_glyph_id , component_glyph );
328+ }
329+ }
330+ }
331+ }
332+
333+ if (new_glyphs .count () > 0 ) {
334+ var new_iter = new_glyphs .iterator ();
335+ while (new_iter .next ()) | entry | {
336+ try required_glyphs .put (entry .key_ptr .* , entry .value_ptr .* );
337+ }
338+ try self .collect_glyph_ids_recursive (required_glyphs );
339+ }
340+ }
341+
342+ fn build_name_table (self : * Self ) []const u8 {
343+ var name_pos : usize = 0 ;
344+
345+ for (self .t .parser .table_records .items , 0.. ) | record , i | {
346+ if (record .tag == .name ) {
347+ name_pos = i ;
348+ break ;
349+ }
350+ }
351+
352+ const name_table_offset = self .parser .table_records .items [name_pos ].offset ;
353+ const name_table_size = self .t .parser .table_records .items [name_pos ].length ;
354+
355+ const name_table = self .t .parser .buffer [name_table_offset .. name_table_offset + name_table_size ];
356+
357+ return name_table ;
358+ }
359+
360+ fn build_post_table (self : * Self , glyph_ids : []u16 ) ! []u8 {
361+ var post_table = self .t .parser .parsed_tables .post .? ;
362+ const post = post_table .cast (table .Post );
363+
258364 var buffer = Writer (u8 ).init (self .allocator );
365+
259366 errdefer buffer .deinit ();
367+
368+ try buffer .write (u32 , post .version , .big );
369+ try buffer .write (i32 , post .italic_angle , .big );
370+ try buffer .write (i16 , post .underline_position , .big );
371+ try buffer .write (i16 , post .underline_thickness , .big );
372+ try buffer .write (u32 , post .is_fixed_pitch , .big );
373+ try buffer .write (u32 , post .min_mem_type42 , .big );
374+ try buffer .write (u32 , post .max_mem_type42 , .big );
375+ try buffer .write (u32 , post .min_mem_type1 , .big );
376+ try buffer .write (u32 , post .max_mem_type1 , .big );
377+
378+ if (post .v2_data ) | _ | {
379+ try buffer .write (u16 , @intCast (glyph_ids .len ), .big );
380+
381+ var has_custom_names = false ;
382+ for (glyph_ids ) | glyph_id | {
383+ if (post .get_glyph_index (glyph_id )) | glyph_index | {
384+ try buffer .write (u16 , glyph_index , .big );
385+ if (glyph_index >= 258 ) {
386+ has_custom_names = true ;
387+ }
388+ }
389+ }
390+ if (has_custom_names ) {
391+ for (glyph_ids ) | glyph_id | {
392+ if (post .get_glyph_index (glyph_id )) | glyph_index | {
393+ if (glyph_index >= 258 ) {
394+ if (post .get_glyph_name (glyph_id )) | glyph_name | {
395+ try buffer .write_u8 (@intCast (glyph_name .len ));
396+ try buffer .write_bytes (glyph_name );
397+ }
398+ }
399+ }
400+ }
401+ }
402+ }
403+
404+ return buffer .to_owned_slice ();
260405 }
261406};
262407
263408test "ttf.zig" {
264409 const fs = std .fs ;
265410 const allocator = std .testing .allocator ;
266- const font_file_path = fs .path .join (allocator , &.{ "./" , "fonts" , "sub5 .ttf" }) catch unreachable ;
411+ const font_file_path = fs .path .join (allocator , &.{ "./" , "fonts" , "LXGWBright-Light .ttf" }) catch unreachable ;
267412 defer allocator .free (font_file_path );
268413 const file_content = try fs .cwd ().readFileAlloc (allocator , font_file_path , std .math .maxInt (usize ));
269414 defer allocator .free (file_content );
270415 var font = try ttf .init (allocator , file_content );
271416 defer font .deinit ();
272- var reader = try font .reader ();
273- const code_point : u32 = 'a' ;
274- const e = try reader .get_glyph_info (code_point );
275- std .debug .print ("glyph: {any}\n " , .{e });
417+ var subbsetter = try font .subsetter ();
418+ try subbsetter .build_subset (BuildSubsetterOptions { .input_text = "绪方理奈" });
419+ // var reader = try font.reader();
420+ // const code_point: u32 = 'a';
421+ // const e = try reader.get_glyph_info(code_point);
422+ // std.debug.print("glyph: {any}\n", .{e});
276423}
0 commit comments