Skip to content

Commit 1d9d344

Browse files
Impl bytemuck traits for GenericFamily (#213)
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 #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 1d9d344

File tree

4 files changed

+107
-3
lines changed

4 files changed

+107
-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

+102-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

@@ -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

5557
impl 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

Comments
 (0)