Skip to content

Commit 9dcaa87

Browse files
Impl bytemuck for GenericFamily.
We then use that to get the max value for the GenericFamilyMap in fontique.
1 parent 79ab65f commit 9dcaa87

File tree

6 files changed

+107
-4
lines changed

6 files changed

+107
-4
lines changed

Cargo.lock

+4-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
@@ -79,6 +79,7 @@ clippy.wildcard_dependencies = "warn"
7979

8080
[workspace.dependencies]
8181
accesskit = "0.17"
82+
bytemuck = { version = "1.20.0", default-features = false }
8283
fontique = { version = "0.2.0", default-features = false, path = "fontique" }
8384
hashbrown = "0.15.2"
8485
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
styled_text = { workspace = true }

fontique/src/generic.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@
44
//! Generic font families.
55
66
use super::FamilyId;
7+
use bytemuck::Contiguous; // For GenericFamily::MAX_VALUE
78
use smallvec::SmallVec;
89
use styled_text::GenericFamily;
910

1011
type FamilyVec = SmallVec<[FamilyId; 2]>;
1112

12-
// FIXME(style): This should be done better.
13-
const COUNT: usize = GenericFamily::FangSong as usize + 1;
13+
const COUNT: usize = GenericFamily::MAX_VALUE as usize + 1;
1414

1515
/// Maps generic families to family identifiers.
1616
#[derive(Clone, Default, Debug)]

styled_text/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ libm = ["dep:core_maths"]
2121

2222
[dependencies]
2323
core_maths = { version = "0.1.0", optional = true }
24+
bytemuck = { workspace = true }

styled_text/src/generic_family.rs

+98
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,101 @@ impl fmt::Display for GenericFamily {
117117
write!(f, "{}", name)
118118
}
119119
}
120+
121+
// Safety: The enum is `repr(u8)` and has only fieldless variants.
122+
unsafe impl bytemuck::NoUninit for GenericFamily {}
123+
124+
// Safety: The enum is `repr(u8)` and `0` is a valid value.
125+
unsafe impl bytemuck::Zeroable for GenericFamily {}
126+
127+
// Safety: The enum is `repr(u8)`.
128+
unsafe impl bytemuck::checked::CheckedBitPattern for GenericFamily {
129+
type Bits = u8;
130+
131+
fn is_valid_bit_pattern(bits: &u8) -> bool {
132+
use bytemuck::Contiguous;
133+
// Don't need to compare against MIN_VALUE as this is u8 and 0 is the MIN_VALUE.
134+
*bits <= Self::MAX_VALUE
135+
}
136+
}
137+
138+
// Safety: The enum is `repr(u8)`. All values are `u8` and fall within
139+
// the min and max values.
140+
unsafe impl bytemuck::Contiguous for GenericFamily {
141+
type Int = u8;
142+
const MIN_VALUE: u8 = Self::Serif as u8;
143+
const MAX_VALUE: u8 = Self::FangSong as u8;
144+
}
145+
146+
#[cfg(test)]
147+
mod tests {
148+
use crate::GenericFamily;
149+
use bytemuck::{checked::try_from_bytes, Contiguous, Zeroable};
150+
use core::ptr;
151+
152+
#[test]
153+
fn checked_bit_pattern() {
154+
let valid = bytemuck::bytes_of(&2_u8);
155+
let invalid = bytemuck::bytes_of(&200_u8);
156+
157+
assert_eq!(
158+
Ok(&GenericFamily::Monospace),
159+
try_from_bytes::<GenericFamily>(valid)
160+
);
161+
162+
assert!(try_from_bytes::<GenericFamily>(invalid).is_err());
163+
}
164+
165+
#[test]
166+
fn contiguous() {
167+
let hd1 = GenericFamily::SansSerif;
168+
let hd2 = GenericFamily::from_integer(hd1.into_integer());
169+
assert_eq!(Some(hd1), hd2);
170+
171+
assert_eq!(None, GenericFamily::from_integer(255));
172+
}
173+
174+
#[test]
175+
fn zeroable() {
176+
let hd = GenericFamily::zeroed();
177+
assert_eq!(hd, GenericFamily::Serif);
178+
}
179+
180+
/// Tests that the [`Contiguous`] impl for [`GenericFamily`] is not trivially incorrect.
181+
const _: () = {
182+
let mut value = 0;
183+
while value <= GenericFamily::MAX_VALUE {
184+
// Safety: In a const context, therefore if this makes an invalid GenericFamily, that will be detected.
185+
let it: GenericFamily = unsafe { ptr::read((&raw const value).cast()) };
186+
// Evaluate the enum value to ensure it actually has a valid tag
187+
if it as u8 != value {
188+
unreachable!();
189+
}
190+
value += 1;
191+
}
192+
};
193+
}
194+
195+
#[cfg(doctest)]
196+
/// Doctests aren't collected under `cfg(test)`; we can use `cfg(doctest)` instead
197+
mod doctests {
198+
/// Validates that any new variants in `GenericFamily` has led to a change in the `Contiguous` impl.
199+
/// Note that to test this robustly, we'd need 256 tests, which is impractical.
200+
/// We make the assumption that all new variants will maintain contiguousness.
201+
///
202+
/// ```compile_fail,E0080
203+
/// use bytemuck::Contiguous;
204+
/// use styled_text::GenericFamily;
205+
/// const {
206+
/// let value = GenericFamily::MAX_VALUE + 1;
207+
/// // Safety: In a const context, therefore if this makes an invalid GenericFamily, that will be detected.
208+
/// // (Indeed, we rely upon that)
209+
/// let it: GenericFamily = unsafe { core::ptr::read((&raw const value).cast()) };
210+
/// // Evaluate the enum value to ensure it actually has an invalid tag
211+
/// if it as u8 != value {
212+
/// unreachable!();
213+
/// }
214+
/// }
215+
/// ```
216+
const _GENERIC_FAMILY: () = {};
217+
}

0 commit comments

Comments
 (0)