Annotate fonts with ruby (pinyin/romaji) and produce modified TTF/WOFF2 outputs.
at main 106 lines 3.8 kB view raw
1use anyhow::{Context, Result}; 2use fontcull_read_fonts::{FontRef, TopLevelTable, tables::cff::Cff, types::Tag}; 3use fontcull_write_fonts::tables::{glyf::Glyf, loca::Loca}; 4use rustc_hash::FxHashMap; 5 6pub fn build_collection(fonts: &[FontRef]) -> Result<Vec<u8>> { 7 let mut out = Vec::new(); 8 9 // TTC header 10 out.extend_from_slice(b"ttcf"); // Tag 11 out.extend_from_slice(&1u16.to_be_bytes()); // Major 12 out.extend_from_slice(&0u16.to_be_bytes()); // Minor 13 out.extend_from_slice(&(fonts.len() as u32).to_be_bytes()); 14 15 let offset_table_start = out.len(); 16 17 for _ in 0..fonts.len() { 18 out.extend_from_slice(&0u32.to_be_bytes()); 19 } 20 21 let mut font_offsets = Vec::new(); 22 let mut table_cache: FxHashMap<(Tag, Vec<u8>), u32> = FxHashMap::default(); 23 let mut table_data_block = Vec::new(); 24 25 // Process and rewrite each font 26 for font in fonts { 27 font_offsets.push(out.len() as u32); 28 let records = font.table_directory().table_records(); 29 let num_tables = records.len() as u16; 30 31 // Write OffsetTable header 32 out.extend_from_slice(&0x00010000u32.to_be_bytes()); // sfntVersion 33 out.extend_from_slice(&num_tables.to_be_bytes()); 34 let entry_selector = (num_tables as f32).log2().floor() as u16; 35 let search_range = (2u16.pow(entry_selector as u32)) * 16; 36 out.extend_from_slice(&search_range.to_be_bytes()); 37 out.extend_from_slice(&entry_selector.to_be_bytes()); 38 out.extend_from_slice(&(num_tables * 16 - search_range).to_be_bytes()); 39 40 for record in records { 41 let tag = record.tag(); 42 let table_data = font 43 .table_data(tag) 44 .context("Table missing")? 45 .as_ref() 46 .to_vec(); 47 48 // Only share tables that are usually safe and heavy 49 let can_share = matches!(tag, Glyf::TAG | Cff::TAG | Loca::TAG); 50 51 let rel_offset = if can_share { 52 if let Some(&off) = table_cache.get(&(tag, table_data.clone())) { 53 off 54 } else { 55 while table_data_block.len() % 4 != 0 { 56 table_data_block.push(0); 57 } 58 59 let off = table_data_block.len() as u32; 60 table_cache.insert((tag, table_data.clone()), off); 61 table_data_block.extend(&table_data); 62 63 off 64 } 65 } else { 66 while table_data_block.len() % 4 != 0 { 67 table_data_block.push(0); 68 } 69 70 let off = table_data_block.len() as u32; 71 table_data_block.extend(&table_data); 72 73 off 74 }; 75 76 out.extend_from_slice(&tag.to_be_bytes()); 77 out.extend_from_slice(&record.checksum().to_be_bytes()); 78 out.extend_from_slice(&rel_offset.to_be_bytes()); 79 out.extend_from_slice(&(table_data.len() as u32).to_be_bytes()); 80 } 81 } 82 83 // Fix up absolute offsets 84 85 let data_block_start = out.len() as u32; 86 87 for (i, &off) in font_offsets.iter().enumerate() { 88 let pos = offset_table_start + (i * 4); 89 out[pos..pos + 4].copy_from_slice(&off.to_be_bytes()); 90 } 91 92 for &f_off in &font_offsets { 93 let num_tables = 94 u16::from_be_bytes(out[f_off as usize + 4..f_off as usize + 6].try_into()?); 95 96 for i in 0..num_tables { 97 let off_pos = (f_off as usize + 12) + (i as usize * 16) + 8; 98 let rel = u32::from_be_bytes(out[off_pos..off_pos + 4].try_into()?); 99 out[off_pos..off_pos + 4].copy_from_slice(&(data_block_start + rel).to_be_bytes()); 100 } 101 } 102 103 out.extend(table_data_block); 104 105 Ok(out) 106}