@@ -280,6 +280,10 @@ fn parse(data: &[u8], index: u32) -> Result<Face<'_>> {
280280 records. push ( r. read :: < TableRecord > ( ) . ok_or ( MalformedFont ) ?) ;
281281 }
282282
283+ // `Face::table` binary-searches by tag, but fonts in the wild violate
284+ // the spec's requirement that the directory be sorted.
285+ records. sort_by_key ( |record| record. tag ) ;
286+
283287 Ok ( Face { data, records } )
284288}
285289
@@ -693,6 +697,36 @@ mod tests {
693697 assert_eq ! ( result, Err ( Error :: CFFError ) ) ;
694698 }
695699
700+ #[ test]
701+ fn unsorted_table_directory_is_searchable ( ) {
702+ // Two table records deliberately stored in non-alphabetical order
703+ // (`head` before `cmap`): the directory violates the spec's sort
704+ // requirement, but both tables must still be found.
705+ let mut font = Vec :: new ( ) ;
706+ font. extend ( 0x00010000u32 . to_be_bytes ( ) ) ;
707+ font. extend ( 2u16 . to_be_bytes ( ) ) ;
708+ font. extend ( 0u16 . to_be_bytes ( ) ) ;
709+ font. extend ( 0u16 . to_be_bytes ( ) ) ;
710+ font. extend ( 0u16 . to_be_bytes ( ) ) ;
711+ // Directory (2 records à 16 bytes; data starts at 12 + 32 = 44).
712+ font. extend ( * b"head" ) ;
713+ font. extend ( 0u32 . to_be_bytes ( ) ) ;
714+ font. extend ( 44u32 . to_be_bytes ( ) ) ;
715+ font. extend ( 4u32 . to_be_bytes ( ) ) ;
716+ font. extend ( * b"cmap" ) ;
717+ font. extend ( 0u32 . to_be_bytes ( ) ) ;
718+ font. extend ( 48u32 . to_be_bytes ( ) ) ;
719+ font. extend ( 4u32 . to_be_bytes ( ) ) ;
720+ // Table payloads.
721+ font. extend ( [ 1 , 2 , 3 , 4 ] ) ;
722+ font. extend ( [ 5 , 6 , 7 , 8 ] ) ;
723+
724+ let face = parse ( & font, 0 ) . unwrap ( ) ;
725+
726+ assert_eq ! ( face. table( Tag :: HEAD ) , Some ( [ 1 , 2 , 3 , 4 ] . as_slice( ) ) ) ;
727+ assert_eq ! ( face. table( Tag :: CMAP ) , Some ( [ 5 , 6 , 7 , 8 ] . as_slice( ) ) ) ;
728+ }
729+
696730 #[ test]
697731 fn table_record_with_invalid_bounds_returns_none ( ) {
698732 let face = Face {
0 commit comments