@@ -85,7 +85,7 @@ use allsorts::tables::os2::Os2;
8585use allsorts:: tables:: { FontTableProvider , HheaTable , HmtxTable , MaxpTable } ;
8686#[ cfg( all( feature = "std" , feature = "parsing" ) ) ]
8787use allsorts:: tag;
88- #[ cfg( all ( feature = "std" , feature = "parsing" ) ) ]
88+ #[ cfg( feature = "std" ) ]
8989use std:: path:: PathBuf ;
9090
9191pub mod utils;
@@ -1360,16 +1360,40 @@ impl FcFontCache {
13601360 }
13611361 }
13621362
1363- /// Builds a new font cache
1364- #[ cfg( not( all( feature = "std" , feature = "parsing" ) ) ) ]
1365- pub fn build ( ) -> Self {
1366- Self :: default ( )
1367- }
1363+ /// Returns an empty font cache (no_std / no filesystem).
1364+ #[ cfg( not( feature = "std" ) ) ]
1365+ pub fn build ( ) -> Self { Self :: default ( ) }
1366+
1367+ /// Scans system font directories using filename heuristics (no allsorts).
1368+ #[ cfg( all( feature = "std" , not( feature = "parsing" ) ) ) ]
1369+ pub fn build ( ) -> Self { Self :: build_from_filenames ( ) }
13681370
1369- /// Builds a new font cache from all fonts discovered on the system
1371+ /// Scans and parses all system fonts via allsorts for full metadata.
13701372 #[ cfg( all( feature = "std" , feature = "parsing" ) ) ]
1371- pub fn build ( ) -> Self {
1372- Self :: build_inner ( None )
1373+ pub fn build ( ) -> Self { Self :: build_inner ( None ) }
1374+
1375+ /// Filename-only scan: discovers fonts on disk, guesses metadata from
1376+ /// the filename using [`config::tokenize_font_stem`].
1377+ #[ cfg( all( feature = "std" , not( feature = "parsing" ) ) ) ]
1378+ fn build_from_filenames ( ) -> Self {
1379+ let mut cache = Self :: default ( ) ;
1380+ for dir in crate :: config:: font_directories ( OperatingSystem :: current ( ) ) {
1381+ for path in FcCollectFontFilesRecursive ( dir) {
1382+ let pattern = match pattern_from_filename ( & path) {
1383+ Some ( p) => p,
1384+ None => continue ,
1385+ } ;
1386+ let id = FontId :: new ( ) ;
1387+ cache. disk_fonts . insert ( id, FcFontPath {
1388+ path : path. to_string_lossy ( ) . to_string ( ) ,
1389+ font_index : 0 ,
1390+ } ) ;
1391+ cache. index_pattern_tokens ( & pattern, id) ;
1392+ cache. metadata . insert ( id, pattern. clone ( ) ) ;
1393+ cache. patterns . insert ( pattern, id) ;
1394+ }
1395+ }
1396+ cache
13731397 }
13741398
13751399 /// Builds a font cache with only specific font families (and their fallbacks).
@@ -3559,42 +3583,41 @@ fn FcScanDirectoriesInner(paths: &[(Option<String>, String)]) -> Vec<(FcPattern,
35593583 }
35603584}
35613585
3562- #[ cfg( all( feature = "std" , feature = "parsing" ) ) ]
3563- fn FcScanSingleDirectoryRecursive ( dir : PathBuf ) -> Vec < ( FcPattern , FcFontPath ) > {
3564- let mut files_to_parse = Vec :: new ( ) ;
3586+ /// Recursively collect all files from a directory (no parsing, no allsorts).
3587+ #[ cfg( feature = "std" ) ]
3588+ fn FcCollectFontFilesRecursive ( dir : PathBuf ) -> Vec < PathBuf > {
3589+ let mut files = Vec :: new ( ) ;
35653590 let mut dirs_to_parse = vec ! [ dir] ;
35663591
3567- ' outer: loop {
3568- let mut new_dirs_to_parse = Vec :: new ( ) ;
3569-
3570- ' inner: for dir in dirs_to_parse. clone ( ) {
3571- let dir = match std:: fs:: read_dir ( dir) {
3592+ loop {
3593+ let mut new_dirs = Vec :: new ( ) ;
3594+ for dir in & dirs_to_parse {
3595+ let entries = match std:: fs:: read_dir ( dir) {
35723596 Ok ( o) => o,
3573- Err ( _) => continue ' inner ,
3597+ Err ( _) => continue ,
35743598 } ;
3575-
3576- for ( path, pathbuf) in dir. filter_map ( |entry| {
3577- let entry = entry. ok ( ) ?;
3599+ for entry in entries. flatten ( ) {
35783600 let path = entry. path ( ) ;
3579- let pathbuf = path. to_path_buf ( ) ;
3580- Some ( ( path, pathbuf) )
3581- } ) {
35823601 if path. is_dir ( ) {
3583- new_dirs_to_parse . push ( pathbuf ) ;
3602+ new_dirs . push ( path ) ;
35843603 } else {
3585- files_to_parse . push ( pathbuf ) ;
3604+ files . push ( path ) ;
35863605 }
35873606 }
35883607 }
3589-
3590- if new_dirs_to_parse. is_empty ( ) {
3591- break ' outer;
3592- } else {
3593- dirs_to_parse = new_dirs_to_parse;
3608+ if new_dirs. is_empty ( ) {
3609+ break ;
35943610 }
3611+ dirs_to_parse = new_dirs;
35953612 }
35963613
3597- FcParseFontFiles ( & files_to_parse)
3614+ files
3615+ }
3616+
3617+ #[ cfg( all( feature = "std" , feature = "parsing" ) ) ]
3618+ fn FcScanSingleDirectoryRecursive ( dir : PathBuf ) -> Vec < ( FcPattern , FcFontPath ) > {
3619+ let files = FcCollectFontFilesRecursive ( dir) ;
3620+ FcParseFontFiles ( & files)
35983621}
35993622
36003623#[ cfg( all( feature = "std" , feature = "parsing" ) ) ]
@@ -3621,7 +3644,7 @@ fn FcParseFontFiles(files_to_parse: &[PathBuf]) -> Vec<(FcPattern, FcFontPath)>
36213644 result. into_iter ( ) . flat_map ( |f| f. into_iter ( ) ) . collect ( )
36223645}
36233646
3624- #[ cfg( all ( feature = "std" , feature = "parsing" ) ) ]
3647+ #[ cfg( feature = "std" ) ]
36253648/// Takes a path & prefix and resolves them to a usable path, or `None` if they're unsupported/unavailable.
36263649///
36273650/// Behaviour is based on: https://www.freedesktop.org/software/fontconfig/fontconfig-user.html
@@ -4065,3 +4088,47 @@ fn detect_monospace(
40654088
40664089 Some ( monospace)
40674090}
4091+
4092+ /// Guess font metadata from a filename using the existing tokenizer.
4093+ ///
4094+ /// Uses [`config::tokenize_font_stem`] and [`config::FONT_STYLE_TOKENS`]
4095+ /// to extract the family name and detect style hints from the filename.
4096+ #[ cfg( feature = "std" ) ]
4097+ fn pattern_from_filename ( path : & std:: path:: Path ) -> Option < FcPattern > {
4098+ let ext = path. extension ( ) ?. to_str ( ) ?. to_lowercase ( ) ;
4099+ match ext. as_str ( ) {
4100+ "ttf" | "otf" | "ttc" | "woff" | "woff2" => { }
4101+ _ => return None ,
4102+ }
4103+
4104+ let stem = path. file_stem ( ) ?. to_str ( ) ?;
4105+ let all_tokens = crate :: config:: tokenize_lowercase ( stem) ;
4106+
4107+ // Style detection: check if any token matches a known style keyword
4108+ let has_token = |kw : & str | all_tokens. iter ( ) . any ( |t| t == kw) ;
4109+ let is_bold = has_token ( "bold" ) || has_token ( "heavy" ) ;
4110+ let is_italic = has_token ( "italic" ) ;
4111+ let is_oblique = has_token ( "oblique" ) ;
4112+ let is_mono = has_token ( "mono" ) || has_token ( "monospace" ) ;
4113+ let is_condensed = has_token ( "condensed" ) ;
4114+
4115+ // Family = non-style tokens joined
4116+ let family_tokens = crate :: config:: tokenize_font_stem ( stem) ;
4117+ if family_tokens. is_empty ( ) { return None ; }
4118+ let family = family_tokens. join ( " " ) ;
4119+
4120+ Some ( FcPattern {
4121+ name : Some ( stem. to_string ( ) ) ,
4122+ family : Some ( family) ,
4123+ bold : if is_bold { PatternMatch :: True } else { PatternMatch :: False } ,
4124+ italic : if is_italic { PatternMatch :: True } else { PatternMatch :: False } ,
4125+ oblique : if is_oblique { PatternMatch :: True } else { PatternMatch :: DontCare } ,
4126+ monospace : if is_mono { PatternMatch :: True } else { PatternMatch :: DontCare } ,
4127+ condensed : if is_condensed { PatternMatch :: True } else { PatternMatch :: DontCare } ,
4128+ weight : if is_bold { FcWeight :: Bold } else { FcWeight :: Normal } ,
4129+ stretch : if is_condensed { FcStretch :: Condensed } else { FcStretch :: Normal } ,
4130+ unicode_ranges : Vec :: new ( ) ,
4131+ metadata : FcFontMetadata :: default ( ) ,
4132+ render_config : FcFontRenderConfig :: default ( ) ,
4133+ } )
4134+ }
0 commit comments