Skip to content

Commit 53ceac3

Browse files
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 linebender#209 where the separation between `GenericFamily` and `GenericFamilyMap` has grown to span crates. The testing here borrows from (copies) what is done for similar testing of `bytemuck` traits in the `color` crate. These crates already depend on `bytemuck`, so this isn't a new dependency. A decision is made here though to not use the `derive` feature as when we move this type to a vocabulary crate, we will want to avoid the `derive` feature there to avoid a dependency on proc macros.
1 parent ffea1e6 commit 53ceac3

File tree

4 files changed

+106
-3
lines changed

4 files changed

+106
-3
lines changed

Cargo.lock

+3-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ clippy.wildcard_dependencies = "warn"
7878

7979
[workspace.dependencies]
8080
accesskit = "0.17"
81+
bytemuck = { version = "1.20.0", default-features = false }
8182
fontique = { version = "0.2.0", default-features = false, path = "fontique" }
8283
hashbrown = "0.15.2"
8384
parley = { version = "0.2.0", default-features = false, path = "parley" }

fontique/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ system = [
3030
]
3131

3232
[dependencies]
33+
bytemuck = { workspace = true }
3334
read-fonts = { workspace = true }
3435
peniko = { workspace = true }
3536
smallvec = "1.13.2"

fontique/src/generic.rs

+101-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
//! Generic font families.
55
66
use super::FamilyId;
7+
use bytemuck::{checked::CheckedBitPattern, Contiguous, NoUninit, Zeroable};
78
use core::fmt;
89
use smallvec::SmallVec;
910

@@ -122,7 +123,31 @@ impl fmt::Display for GenericFamily {
122123
}
123124
}
124125

125-
const COUNT: usize = GenericFamily::FangSong as usize + 1;
126+
// Safety: The enum is `repr(u8)` and has only fieldless variants.
127+
unsafe impl NoUninit for GenericFamily {}
128+
129+
// Safety: The enum is `repr(u8)` and `0` is a valid value.
130+
unsafe impl Zeroable for GenericFamily {}
131+
132+
// Safety: The enum is `repr(u8)`.
133+
unsafe impl CheckedBitPattern for GenericFamily {
134+
type Bits = u8;
135+
136+
fn is_valid_bit_pattern(bits: &u8) -> bool {
137+
// Don't need to compare against MIN_VALUE as this is u8 and 0 is the MIN_VALUE.
138+
*bits <= Self::MAX_VALUE
139+
}
140+
}
141+
142+
// Safety: The enum is `repr(u8)`. All values are `u8` and fall within
143+
// the min and max values.
144+
unsafe impl Contiguous for GenericFamily {
145+
type Int = u8;
146+
const MIN_VALUE: u8 = Self::Serif as u8;
147+
const MAX_VALUE: u8 = Self::FangSong as u8;
148+
}
149+
150+
const COUNT: usize = GenericFamily::MAX_VALUE as usize + 1;
126151

127152
/// Maps generic families to family identifiers.
128153
#[derive(Clone, Default, Debug)]
@@ -148,3 +173,78 @@ impl GenericFamilyMap {
148173
self.map[generic as usize].extend(families);
149174
}
150175
}
176+
177+
#[cfg(test)]
178+
mod tests {
179+
use crate::GenericFamily;
180+
use bytemuck::{checked::try_from_bytes, Contiguous, Zeroable};
181+
use core::ptr;
182+
183+
#[test]
184+
fn checked_bit_pattern() {
185+
let valid = bytemuck::bytes_of(&2_u8);
186+
let invalid = bytemuck::bytes_of(&200_u8);
187+
188+
assert_eq!(
189+
Ok(&GenericFamily::Monospace),
190+
try_from_bytes::<GenericFamily>(valid)
191+
);
192+
193+
assert!(try_from_bytes::<GenericFamily>(invalid).is_err());
194+
}
195+
196+
#[test]
197+
fn contiguous() {
198+
let hd1 = GenericFamily::SansSerif;
199+
let hd2 = GenericFamily::from_integer(hd1.into_integer());
200+
assert_eq!(Some(hd1), hd2);
201+
202+
assert_eq!(None, GenericFamily::from_integer(255));
203+
}
204+
205+
#[test]
206+
fn zeroable() {
207+
let hd = GenericFamily::zeroed();
208+
assert_eq!(hd, GenericFamily::Serif);
209+
}
210+
211+
/// Tests that the [`Contiguous`] impl for [`GenericFamily`] is not trivially incorrect.
212+
const _: () = {
213+
let mut value = 0;
214+
while value <= GenericFamily::MAX_VALUE {
215+
// Safety: In a const context, therefore if this makes an invalid GenericFamily, that will be detected.
216+
// When updating the MSRV to 1.82 or later, this can use `&raw const value` instead of the addr_of!
217+
let it: GenericFamily = unsafe { ptr::read((core::ptr::addr_of!(value)).cast()) };
218+
// Evaluate the enum value to ensure it actually has a valid tag
219+
if it as u8 != value {
220+
unreachable!();
221+
}
222+
value += 1;
223+
}
224+
};
225+
}
226+
227+
#[cfg(doctest)]
228+
/// Doctests aren't collected under `cfg(test)`; we can use `cfg(doctest)` instead
229+
mod doctests {
230+
/// Validates that any new variants in `GenericFamily` has led to a change in the `Contiguous` impl.
231+
/// Note that to test this robustly, we'd need 256 tests, which is impractical.
232+
/// We make the assumption that all new variants will maintain contiguousness.
233+
///
234+
/// ```compile_fail,E0080
235+
/// use bytemuck::Contiguous;
236+
/// use fontique::GenericFamily;
237+
/// const {
238+
/// let value = GenericFamily::MAX_VALUE + 1;
239+
/// // Safety: In a const context, therefore if this makes an invalid GenericFamily, that will be detected.
240+
/// // (Indeed, we rely upon that)
241+
/// // When updating the MSRV to 1.82 or later, this can use `&raw const value` instead of the addr_of!
242+
/// let it: GenericFamily = unsafe { core::ptr::read((core::ptr::addr_of!(value)).cast()) };
243+
/// // Evaluate the enum value to ensure it actually has an invalid tag
244+
/// if it as u8 != value {
245+
/// unreachable!();
246+
/// }
247+
/// }
248+
/// ```
249+
const _GENERIC_FAMILY: () = {};
250+
}

0 commit comments

Comments
 (0)