44//! Generic font families.
55
66use super :: FamilyId ;
7+ use bytemuck:: { checked:: CheckedBitPattern , Contiguous , NoUninit , Zeroable } ;
78use core:: fmt;
89use smallvec:: SmallVec ;
910
@@ -50,6 +51,7 @@ pub enum GenericFamily {
5051 /// Song and cursive-style Kai forms. This style is often used for
5152 /// government documents.
5253 FangSong = 12 ,
54+ // NOTICE: If a new value is added, be sure to add modify `MAX_VALUE` in the bytemuck impl.
5355}
5456
5557impl GenericFamily {
@@ -122,7 +124,31 @@ impl fmt::Display for GenericFamily {
122124 }
123125}
124126
125- const COUNT : usize = GenericFamily :: FangSong as usize + 1 ;
127+ // Safety: The enum is `repr(u8)` and has only fieldless variants.
128+ unsafe impl NoUninit for GenericFamily { }
129+
130+ // Safety: The enum is `repr(u8)` and `0` is a valid value.
131+ unsafe impl Zeroable for GenericFamily { }
132+
133+ // Safety: The enum is `repr(u8)`.
134+ unsafe impl CheckedBitPattern for GenericFamily {
135+ type Bits = u8 ;
136+
137+ fn is_valid_bit_pattern ( bits : & u8 ) -> bool {
138+ // Don't need to compare against MIN_VALUE as this is u8 and 0 is the MIN_VALUE.
139+ * bits <= Self :: MAX_VALUE
140+ }
141+ }
142+
143+ // Safety: The enum is `repr(u8)`. All values are `u8` and fall within
144+ // the min and max values.
145+ unsafe impl Contiguous for GenericFamily {
146+ type Int = u8 ;
147+ const MIN_VALUE : u8 = Self :: Serif as u8 ;
148+ const MAX_VALUE : u8 = Self :: FangSong as u8 ;
149+ }
150+
151+ const COUNT : usize = GenericFamily :: MAX_VALUE as usize + 1 ;
126152
127153/// Maps generic families to family identifiers.
128154#[ derive( Clone , Default , Debug ) ]
@@ -148,3 +174,78 @@ impl GenericFamilyMap {
148174 self . map [ generic as usize ] . extend ( families) ;
149175 }
150176}
177+
178+ #[ cfg( test) ]
179+ mod tests {
180+ use crate :: GenericFamily ;
181+ use bytemuck:: { checked:: try_from_bytes, Contiguous , Zeroable } ;
182+ use core:: ptr;
183+
184+ #[ test]
185+ fn checked_bit_pattern ( ) {
186+ let valid = bytemuck:: bytes_of ( & 2_u8 ) ;
187+ let invalid = bytemuck:: bytes_of ( & 200_u8 ) ;
188+
189+ assert_eq ! (
190+ Ok ( & GenericFamily :: Monospace ) ,
191+ try_from_bytes:: <GenericFamily >( valid)
192+ ) ;
193+
194+ assert ! ( try_from_bytes:: <GenericFamily >( invalid) . is_err( ) ) ;
195+ }
196+
197+ #[ test]
198+ fn contiguous ( ) {
199+ let hd1 = GenericFamily :: SansSerif ;
200+ let hd2 = GenericFamily :: from_integer ( hd1. into_integer ( ) ) ;
201+ assert_eq ! ( Some ( hd1) , hd2) ;
202+
203+ assert_eq ! ( None , GenericFamily :: from_integer( 255 ) ) ;
204+ }
205+
206+ #[ test]
207+ fn zeroable ( ) {
208+ let hd = GenericFamily :: zeroed ( ) ;
209+ assert_eq ! ( hd, GenericFamily :: Serif ) ;
210+ }
211+
212+ /// Tests that the [`Contiguous`] impl for [`GenericFamily`] is not trivially incorrect.
213+ const _: ( ) = {
214+ let mut value = 0 ;
215+ while value <= GenericFamily :: MAX_VALUE {
216+ // Safety: In a const context, therefore if this makes an invalid GenericFamily, that will be detected.
217+ // When updating the MSRV to 1.82 or later, this can use `&raw const value` instead of the addr_of!
218+ let it: GenericFamily = unsafe { ptr:: read ( ( core:: ptr:: addr_of!( value) ) . cast ( ) ) } ;
219+ // Evaluate the enum value to ensure it actually has a valid tag
220+ if it as u8 != value {
221+ unreachable ! ( ) ;
222+ }
223+ value += 1 ;
224+ }
225+ } ;
226+ }
227+
228+ #[ cfg( doctest) ]
229+ /// Doctests aren't collected under `cfg(test)`; we can use `cfg(doctest)` instead
230+ mod doctests {
231+ /// Validates that any new variants in `GenericFamily` has led to a change in the `Contiguous` impl.
232+ /// Note that to test this robustly, we'd need 256 tests, which is impractical.
233+ /// We make the assumption that all new variants will maintain contiguousness.
234+ ///
235+ /// ```compile_fail,E0080
236+ /// use bytemuck::Contiguous;
237+ /// use fontique::GenericFamily;
238+ /// const {
239+ /// let value = GenericFamily::MAX_VALUE + 1;
240+ /// // Safety: In a const context, therefore if this makes an invalid GenericFamily, that will be detected.
241+ /// // (Indeed, we rely upon that)
242+ /// // When updating the MSRV to 1.82 or later, this can use `&raw const value` instead of the addr_of!
243+ /// let it: GenericFamily = unsafe { core::ptr::read((core::ptr::addr_of!(value)).cast()) };
244+ /// // Evaluate the enum value to ensure it actually has an invalid tag
245+ /// if it as u8 != value {
246+ /// unreachable!();
247+ /// }
248+ /// }
249+ /// ```
250+ const _GENERIC_FAMILY: ( ) = { } ;
251+ }
0 commit comments