From a77b81f091df4edf5ba7dbb35888b5741b125e44 Mon Sep 17 00:00:00 2001 From: Bruce Mitchener Date: Mon, 9 Dec 2024 12:44:59 +0700 Subject: [PATCH] Impl bytemuck traits for `GenericFamily` This can be used in the future as part of FFI work, but for now, the `Contiguous` trait is used to provide a `MAX_VALUE` used by `GenericFamilyMap`. This is a backport of part of #211 where the separation between `GenericFamily` and `GenericFamilyMap` has grown to span crates. --- Cargo.lock | 5 +- Cargo.toml | 1 + fontique/Cargo.toml | 1 + fontique/src/generic.rs | 103 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 107 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5b4c1386..0e227938 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -480,9 +480,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" -version = "1.18.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" +checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a" dependencies = [ "bytemuck_derive", ] @@ -963,6 +963,7 @@ dependencies = [ name = "fontique" version = "0.2.0" dependencies = [ + "bytemuck", "core-foundation", "core-text", "core_maths", diff --git a/Cargo.toml b/Cargo.toml index 82633df3..9c2f2b5b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,6 +78,7 @@ clippy.wildcard_dependencies = "warn" [workspace.dependencies] accesskit = "0.17" +bytemuck = { version = "1.20.0", default-features = false } fontique = { version = "0.2.0", default-features = false, path = "fontique" } hashbrown = "0.15.2" parley = { version = "0.2.0", default-features = false, path = "parley" } diff --git a/fontique/Cargo.toml b/fontique/Cargo.toml index f1b348bb..4086d1f1 100644 --- a/fontique/Cargo.toml +++ b/fontique/Cargo.toml @@ -30,6 +30,7 @@ system = [ ] [dependencies] +bytemuck = { workspace = true } read-fonts = { workspace = true } peniko = { workspace = true } smallvec = "1.13.2" diff --git a/fontique/src/generic.rs b/fontique/src/generic.rs index afbdc4ca..fdf30be3 100644 --- a/fontique/src/generic.rs +++ b/fontique/src/generic.rs @@ -4,6 +4,7 @@ //! Generic font families. use super::FamilyId; +use bytemuck::Contiguous; use core::fmt; use smallvec::SmallVec; @@ -122,7 +123,32 @@ impl fmt::Display for GenericFamily { } } -const COUNT: usize = GenericFamily::FangSong as usize + 1; +// Safety: The enum is `repr(u8)` and has only fieldless variants. +unsafe impl bytemuck::NoUninit for GenericFamily {} + +// Safety: The enum is `repr(u8)` and `0` is a valid value. +unsafe impl bytemuck::Zeroable for GenericFamily {} + +// Safety: The enum is `repr(u8)`. +unsafe impl bytemuck::checked::CheckedBitPattern for GenericFamily { + type Bits = u8; + + fn is_valid_bit_pattern(bits: &u8) -> bool { + use bytemuck::Contiguous; + // Don't need to compare against MIN_VALUE as this is u8 and 0 is the MIN_VALUE. + *bits <= Self::MAX_VALUE + } +} + +// Safety: The enum is `repr(u8)`. All values are `u8` and fall within +// the min and max values. +unsafe impl Contiguous for GenericFamily { + type Int = u8; + const MIN_VALUE: u8 = Self::Serif as u8; + const MAX_VALUE: u8 = Self::FangSong as u8; +} + +const COUNT: usize = GenericFamily::MAX_VALUE as usize + 1; /// Maps generic families to family identifiers. #[derive(Clone, Default, Debug)] @@ -148,3 +174,78 @@ impl GenericFamilyMap { self.map[generic as usize].extend(families); } } + +#[cfg(test)] +mod tests { + use crate::GenericFamily; + use bytemuck::{checked::try_from_bytes, Contiguous, Zeroable}; + use core::ptr; + + #[test] + fn checked_bit_pattern() { + let valid = bytemuck::bytes_of(&2_u8); + let invalid = bytemuck::bytes_of(&200_u8); + + assert_eq!( + Ok(&GenericFamily::Monospace), + try_from_bytes::(valid) + ); + + assert!(try_from_bytes::(invalid).is_err()); + } + + #[test] + fn contiguous() { + let hd1 = GenericFamily::SansSerif; + let hd2 = GenericFamily::from_integer(hd1.into_integer()); + assert_eq!(Some(hd1), hd2); + + assert_eq!(None, GenericFamily::from_integer(255)); + } + + #[test] + fn zeroable() { + let hd = GenericFamily::zeroed(); + assert_eq!(hd, GenericFamily::Serif); + } + + /// Tests that the [`Contiguous`] impl for [`GenericFamily`] is not trivially incorrect. + const _: () = { + let mut value = 0; + while value <= GenericFamily::MAX_VALUE { + // Safety: In a const context, therefore if this makes an invalid GenericFamily, that will be detected. + // When updating the MSRV to 1.82 or later, this can use `&raw const value` instead of the addr_of! + let it: GenericFamily = unsafe { ptr::read((core::ptr::addr_of!(value)).cast()) }; + // Evaluate the enum value to ensure it actually has a valid tag + if it as u8 != value { + unreachable!(); + } + value += 1; + } + }; +} + +#[cfg(doctest)] +/// Doctests aren't collected under `cfg(test)`; we can use `cfg(doctest)` instead +mod doctests { + /// Validates that any new variants in `GenericFamily` has led to a change in the `Contiguous` impl. + /// Note that to test this robustly, we'd need 256 tests, which is impractical. + /// We make the assumption that all new variants will maintain contiguousness. + /// + /// ```compile_fail,E0080 + /// use bytemuck::Contiguous; + /// use styled_text::GenericFamily; + /// const { + /// let value = GenericFamily::MAX_VALUE + 1; + /// // Safety: In a const context, therefore if this makes an invalid GenericFamily, that will be detected. + /// // (Indeed, we rely upon that) + /// // When updating the MSRV to 1.82 or later, this can use `&raw const value` instead of the addr_of! + /// let it: GenericFamily = unsafe { core::ptr::read((core::ptr::addr_of!(value)).cast()) }; + /// // Evaluate the enum value to ensure it actually has an invalid tag + /// if it as u8 != value { + /// unreachable!(); + /// } + /// } + /// ``` + const _GENERIC_FAMILY: () = {}; +}