use super::LengthPercentage;
use crate::style_helpers::{
FromFr, FromLength, FromPercent, TaffyAuto, TaffyFitContent, TaffyMaxContent, TaffyMinContent, TaffyZero,
};
mod compat {
#![allow(unsafe_code)]
#![allow(unknown_lints)]
#![allow(unnecessary_transmutes)]
pub const fn f32_to_bits(val: f32) -> u32 {
unsafe { core::mem::transmute(val) }
}
pub const fn f32_from_bits(v: u32) -> f32 {
unsafe { core::mem::transmute(v) }
}
#[inline(always)]
#[cfg(all(target_pointer_width = "64", feature = "strict_provenance"))]
pub fn tag_ptr(ptr: *const (), tag: usize) -> *const () {
ptr.map_addr(|a| a | tag)
}
#[inline(always)]
#[cfg(all(target_pointer_width = "64", not(feature = "strict_provenance")))]
pub fn tag_ptr(ptr: *const (), tag: usize) -> *const () {
(ptr as usize | tag) as *const ()
}
#[inline(always)]
#[cfg(all(target_pointer_width = "64", feature = "strict_provenance"))]
pub fn untag_ptr(ptr: *const (), mask: usize) -> *const () {
ptr.map_addr(|a| a & !mask)
}
#[inline(always)]
#[cfg(all(target_pointer_width = "64", not(feature = "strict_provenance")))]
pub fn untag_ptr(ptr: *const (), mask: usize) -> *const () {
(ptr as usize & !mask) as *const ()
}
}
#[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))]
std::compile_error!("Taffy only supports targets with a pointer width of 32 or 64 bits");
#[cfg(target_pointer_width = "64")]
mod inner {
use super::compat::{f32_from_bits, f32_to_bits, tag_ptr, untag_ptr};
const TAG_MASK: usize = 0b11111111;
const CALC_TAG_MASK: usize = 0b111;
#[derive(Copy, Clone, Debug, PartialEq)]
pub(super) struct CompactLengthInner {
tagged_ptr: *const (),
}
impl CompactLengthInner {
#[inline(always)]
pub(super) fn from_ptr(ptr: *const (), tag: usize) -> Self {
let tagged_ptr = tag_ptr(ptr, tag);
Self { tagged_ptr }
}
#[inline(always)]
pub(super) const fn from_val(val: f32, tag: usize) -> Self {
let tagged_ptr = (((f32_to_bits(val) as usize) << 32) | tag) as *const ();
Self { tagged_ptr }
}
#[inline(always)]
pub(super) const fn from_tag(tag: usize) -> Self {
let tagged_ptr = tag as *const ();
Self { tagged_ptr }
}
#[inline(always)]
#[cfg(feature = "calc")]
pub(super) fn calc_tag(self) -> usize {
(self.tagged_ptr as usize) & CALC_TAG_MASK
}
#[inline(always)]
pub(super) fn tag(self) -> usize {
(self.tagged_ptr as usize) & TAG_MASK
}
#[inline(always)]
pub(super) fn ptr(self) -> *const () {
untag_ptr(self.tagged_ptr, CALC_TAG_MASK)
}
#[inline(always)]
pub(super) fn value(self) -> f32 {
f32_from_bits((self.tagged_ptr as usize >> 32) as u32)
}
#[inline(always)]
#[cfg(feature = "serde")]
pub(super) fn serialized(self) -> u64 {
(self.tagged_ptr as usize as u64).rotate_left(32)
}
#[inline(always)]
#[cfg(feature = "serde")]
pub(super) fn from_serialized(value: u64) -> Self {
Self { tagged_ptr: value.rotate_right(32) as usize as *const () }
}
}
}
#[cfg(target_pointer_width = "32")]
mod inner {
use super::compat::{f32_from_bits, f32_to_bits};
#[derive(Copy, Clone, Debug, PartialEq)]
pub(super) struct CompactLengthInner {
tag: usize,
ptr: *const (),
}
impl CompactLengthInner {
#[inline(always)]
pub(super) fn from_ptr(ptr: *const (), tag: usize) -> Self {
Self { ptr, tag }
}
#[inline(always)]
pub(super) const fn from_val(val: f32, tag: usize) -> Self {
Self { ptr: f32_to_bits(val) as usize as *const (), tag }
}
#[inline(always)]
pub(super) const fn from_tag(tag: usize) -> Self {
Self { ptr: 0 as *const (), tag }
}
#[inline(always)]
#[cfg(feature = "calc")]
pub(super) fn calc_tag(self) -> usize {
self.tag
}
#[inline(always)]
pub(super) fn tag(self) -> usize {
self.tag
}
#[inline(always)]
pub(super) fn ptr(self) -> *const () {
self.ptr
}
#[inline(always)]
pub(super) fn value(self) -> f32 {
f32_from_bits(self.ptr as u32)
}
#[inline(always)]
#[cfg(feature = "serde")]
pub(super) fn serialized(self) -> u64 {
(self.tag as u64) << 32 | (self.ptr as u64)
}
#[inline(always)]
#[cfg(feature = "serde")]
pub(super) fn from_serialized(value: u64) -> Self {
Self { tag: (value >> 32) as usize, ptr: (value & 0xFFFFFFFF) as usize as *const () }
}
}
}
use inner::CompactLengthInner;
#[derive(Copy, Clone, PartialEq, Debug)]
#[repr(transparent)]
pub struct CompactLength(CompactLengthInner);
impl CompactLength {
pub const NON_POINTER_BIT: usize = 0b0001;
pub const KEYWORD_BIT: usize = 0b0010;
pub const PERCENTAGE_BIT: usize = 0b0100;
pub const SIZING_KEYWORD_BIT: usize = 0b1000;
#[cfg(feature = "calc")]
pub const CALC_TAG: usize = 0b0000_0100;
#[cfg(feature = "calc")]
pub const FIT_CONTENT_CALC_TAG: usize = 0b0000_0110;
pub const LENGTH_TAG: usize = 0b0000_0001;
pub const PERCENT_TAG: usize = 0b0000_0101;
pub const AUTO_TAG: usize = 0b0000_0011;
pub const FR_TAG: usize = 0b0001_0011;
pub const CONTENT_TAG: usize = 0b0010_0011;
pub const MIN_CONTENT_TAG: usize = 0b0000_1011;
pub const MAX_CONTENT_TAG: usize = 0b0001_1011;
pub const FIT_CONTENT_PX_TAG: usize = 0b0010_1011;
pub const FIT_CONTENT_KEYWORD_TAG: usize = 0b0011_1011;
pub const STRETCH_TAG: usize = 0b0100_1011;
pub const FIT_CONTENT_PERCENT_TAG: usize = 0b0000_1111;
}
impl CompactLength {
#[inline(always)]
pub const fn length(val: f32) -> Self {
Self(CompactLengthInner::from_val(val, Self::LENGTH_TAG))
}
#[inline(always)]
pub const fn percent(val: f32) -> Self {
Self(CompactLengthInner::from_val(val, Self::PERCENT_TAG))
}
#[inline]
#[cfg(feature = "calc")]
pub fn calc(ptr: *const ()) -> Self {
assert_ne!(ptr as u64, 0);
assert_eq!(ptr as u64 & 0b111, 0);
Self(CompactLengthInner::from_ptr(ptr, Self::CALC_TAG))
}
#[inline]
#[cfg(feature = "calc")]
pub fn fit_content_calc(ptr: *const ()) -> Self {
assert_ne!(ptr as u64, 0);
assert_eq!(ptr as u64 & 0b111, 0);
Self(CompactLengthInner::from_ptr(ptr, Self::FIT_CONTENT_CALC_TAG))
}
#[inline(always)]
pub const fn auto() -> Self {
Self(CompactLengthInner::from_tag(Self::AUTO_TAG))
}
#[inline(always)]
pub const fn fr(val: f32) -> Self {
Self(CompactLengthInner::from_val(val, Self::FR_TAG))
}
#[inline(always)]
pub const fn min_content() -> Self {
Self(CompactLengthInner::from_tag(Self::MIN_CONTENT_TAG))
}
#[inline(always)]
pub const fn max_content() -> Self {
Self(CompactLengthInner::from_tag(Self::MAX_CONTENT_TAG))
}
#[inline(always)]
pub const fn fit_content_px(limit: f32) -> Self {
Self(CompactLengthInner::from_val(limit, Self::FIT_CONTENT_PX_TAG))
}
#[inline(always)]
pub const fn fit_content_percent(limit: f32) -> Self {
Self(CompactLengthInner::from_val(limit, Self::FIT_CONTENT_PERCENT_TAG))
}
#[inline(always)]
pub const fn fit_content_keyword() -> Self {
Self(CompactLengthInner::from_tag(Self::FIT_CONTENT_KEYWORD_TAG))
}
#[inline(always)]
pub const fn stretch() -> Self {
Self(CompactLengthInner::from_tag(Self::STRETCH_TAG))
}
#[inline(always)]
pub const fn content() -> Self {
Self(CompactLengthInner::from_tag(Self::CONTENT_TAG))
}
#[inline(always)]
pub fn tag(self) -> usize {
self.0.tag()
}
#[inline(always)]
pub fn value(self) -> f32 {
self.0.value()
}
#[inline(always)]
#[cfg(feature = "calc")]
pub fn calc_value(self) -> *const () {
self.0.ptr()
}
#[inline(always)]
#[cfg(feature = "calc")]
pub fn is_plain_calc(self) -> bool {
self.0.calc_tag() & (Self::NON_POINTER_BIT | Self::KEYWORD_BIT) == 0
}
#[inline(always)]
pub fn is_fit_content_calc(self) -> bool {
self.tag() & (Self::NON_POINTER_BIT | Self::KEYWORD_BIT) == Self::KEYWORD_BIT
}
#[inline(always)]
pub fn has_calc(self) -> bool {
self.tag() & Self::NON_POINTER_BIT == 0
}
#[inline(always)]
pub fn is_zero(self) -> bool {
self.0 == Self::ZERO.0
}
#[inline(always)]
pub fn is_length_or_percentage(self) -> bool {
self.tag() & (Self::NON_POINTER_BIT | Self::KEYWORD_BIT) == Self::NON_POINTER_BIT
}
#[inline(always)]
pub fn is_auto(self) -> bool {
self.tag() == Self::AUTO_TAG
}
#[inline(always)]
pub fn is_content(self) -> bool {
self.tag() == Self::CONTENT_TAG
}
#[inline(always)]
pub fn is_min_content(self) -> bool {
matches!(self.tag(), Self::MIN_CONTENT_TAG)
}
#[inline(always)]
pub fn is_max_content(self) -> bool {
matches!(self.tag(), Self::MAX_CONTENT_TAG)
}
#[inline(always)]
pub fn is_fit_content(self) -> bool {
matches!(self.tag(), Self::FIT_CONTENT_PX_TAG | Self::FIT_CONTENT_PERCENT_TAG) || self.is_fit_content_calc()
}
#[inline(always)]
pub fn is_sizing_keyword(self) -> bool {
const MASK: usize =
CompactLength::NON_POINTER_BIT | CompactLength::KEYWORD_BIT | CompactLength::SIZING_KEYWORD_BIT;
self.tag() & MASK == MASK || self.is_fit_content_calc()
}
#[inline(always)]
pub fn is_max_or_fit_content(self) -> bool {
matches!(self.tag(), Self::MAX_CONTENT_TAG | Self::FIT_CONTENT_PX_TAG | Self::FIT_CONTENT_PERCENT_TAG)
|| self.is_fit_content_calc()
}
#[inline(always)]
pub fn is_max_content_alike(&self) -> bool {
matches!(
self.tag(),
CompactLength::AUTO_TAG
| CompactLength::MAX_CONTENT_TAG
| CompactLength::FIT_CONTENT_PX_TAG
| CompactLength::FIT_CONTENT_PERCENT_TAG
) || self.is_fit_content_calc()
}
#[inline(always)]
pub fn is_min_or_max_content(&self) -> bool {
matches!(self.tag(), Self::MIN_CONTENT_TAG | Self::MAX_CONTENT_TAG)
}
#[inline(always)]
pub fn is_intrinsic(self) -> bool {
matches!(
self.tag(),
Self::AUTO_TAG
| Self::MIN_CONTENT_TAG
| Self::MAX_CONTENT_TAG
| Self::FIT_CONTENT_PX_TAG
| Self::FIT_CONTENT_PERCENT_TAG
) || self.is_fit_content_calc()
}
#[inline(always)]
pub fn is_fr(self) -> bool {
self.tag() == Self::FR_TAG
}
#[inline(always)]
pub fn uses_percentage(self) -> bool {
self.tag() & Self::PERCENTAGE_BIT != 0
}
#[inline(always)]
pub fn resolved_percentage_size(
self,
parent_size: f32,
calc_resolver: impl Fn(*const (), f32) -> f32,
) -> Option<f32> {
match self.tag() {
CompactLength::PERCENT_TAG => Some(self.value() * parent_size),
#[cfg(feature = "calc")]
_ if self.is_plain_calc() => Some(calc_resolver(self.0.ptr(), parent_size)),
_ => None,
}
}
}
impl TaffyZero for CompactLength {
const ZERO: Self = Self::length(0.0);
}
impl TaffyAuto for CompactLength {
const AUTO: Self = Self::auto();
}
impl TaffyMinContent for CompactLength {
const MIN_CONTENT: Self = Self::min_content();
}
impl TaffyMaxContent for CompactLength {
const MAX_CONTENT: Self = Self::max_content();
}
impl FromLength for CompactLength {
fn from_length<Input: Into<f64> + Copy>(value: Input) -> Self {
Self::length(value.into() as f32)
}
}
impl FromPercent for CompactLength {
fn from_percent<Input: Into<f64> + Copy>(value: Input) -> Self {
Self::percent(value.into() as f32)
}
}
impl FromFr for CompactLength {
fn from_fr<Input: Into<f64> + Copy>(value: Input) -> Self {
Self::fr(value.into() as f32)
}
}
impl TaffyFitContent for CompactLength {
fn fit_content(lp: LengthPercentage) -> Self {
let value = lp.0.value();
match lp.0.tag() {
Self::LENGTH_TAG => Self::fit_content_px(value),
Self::PERCENT_TAG => Self::fit_content_percent(value),
#[cfg(feature = "calc")]
_ if lp.0.is_plain_calc() => Self::fit_content_calc(lp.0.calc_value()),
_ => unreachable!(),
}
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for CompactLength {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
#[cfg(feature = "calc")]
{
if self.has_calc() {
Err(serde::ser::Error::custom("Cannot serialize Calc value"))
} else {
serializer.serialize_u64(self.0.serialized())
}
}
#[cfg(not(feature = "calc"))]
serializer.serialize_u64(self.0.serialized())
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for CompactLength {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let bits: u64 = u64::deserialize(deserializer)?;
let value = Self(CompactLengthInner::from_serialized(bits));
if matches!(
value.tag(),
CompactLength::LENGTH_TAG
| CompactLength::PERCENT_TAG
| CompactLength::AUTO_TAG
| CompactLength::MIN_CONTENT_TAG
| CompactLength::MAX_CONTENT_TAG
| CompactLength::FIT_CONTENT_KEYWORD_TAG
| CompactLength::FIT_CONTENT_PX_TAG
| CompactLength::FIT_CONTENT_PERCENT_TAG
| CompactLength::STRETCH_TAG
| CompactLength::CONTENT_TAG
| CompactLength::FR_TAG
) {
Ok(value)
} else {
Err(serde::de::Error::custom("Cannot deserialize Calc value"))
}
}
}
#[cfg(test)]
mod tests {
use super::{CompactLength, CompactLengthInner};
impl CompactLength {
fn from_raw_tag(tag: usize) -> Self {
Self(CompactLengthInner::from_tag(tag))
}
}
const NON_PERCENTAGE_TAGS: &[usize] = &[
CompactLength::LENGTH_TAG,
CompactLength::AUTO_TAG,
CompactLength::FR_TAG,
CompactLength::MIN_CONTENT_TAG,
CompactLength::MAX_CONTENT_TAG,
CompactLength::FIT_CONTENT_PX_TAG,
CompactLength::FIT_CONTENT_KEYWORD_TAG,
CompactLength::STRETCH_TAG,
CompactLength::CONTENT_TAG,
];
const PERCENTAGE_TAGS: &[usize] = &[CompactLength::PERCENT_TAG, CompactLength::FIT_CONTENT_PERCENT_TAG];
#[test]
fn tags_are_unique_and_fit_in_a_byte() {
let mut all: Vec<usize> = NON_PERCENTAGE_TAGS.iter().chain(PERCENTAGE_TAGS).copied().collect();
#[cfg(feature = "calc")]
all.push(CompactLength::CALC_TAG);
#[cfg(feature = "calc")]
all.push(CompactLength::FIT_CONTENT_CALC_TAG);
assert!(all.iter().all(|tag| *tag <= 0xFF));
all.sort_unstable();
all.dedup();
assert_eq!(all.len(), NON_PERCENTAGE_TAGS.len() + PERCENTAGE_TAGS.len() + 2 * cfg!(feature = "calc") as usize);
}
#[test]
fn non_pointer_tags_have_non_pointer_bit_set() {
for tag in NON_PERCENTAGE_TAGS.iter().chain(PERCENTAGE_TAGS) {
assert_ne!(tag & CompactLength::NON_POINTER_BIT, 0, "tag {tag:#010b}");
}
#[cfg(feature = "calc")]
assert_eq!(CompactLength::CALC_TAG & CompactLength::NON_POINTER_BIT, 0);
}
#[test]
fn keyword_bits_match_predicates() {
let all = NON_PERCENTAGE_TAGS.iter().chain(PERCENTAGE_TAGS).map(|tag| CompactLength::from_raw_tag(*tag));
for value in all {
let tag = value.tag();
let is_plain = matches!(tag, CompactLength::LENGTH_TAG | CompactLength::PERCENT_TAG);
assert_eq!(tag & CompactLength::KEYWORD_BIT == 0, is_plain, "tag {tag:#010b}");
assert_eq!(value.is_length_or_percentage(), is_plain, "tag {tag:#010b}");
let is_sizing_keyword = matches!(
tag,
CompactLength::MIN_CONTENT_TAG
| CompactLength::MAX_CONTENT_TAG
| CompactLength::FIT_CONTENT_KEYWORD_TAG
| CompactLength::FIT_CONTENT_PX_TAG
| CompactLength::FIT_CONTENT_PERCENT_TAG
| CompactLength::STRETCH_TAG
);
assert_eq!(value.is_sizing_keyword(), is_sizing_keyword, "tag {tag:#010b}");
}
#[cfg(feature = "calc")]
{
assert_eq!(CompactLength::CALC_TAG & CompactLength::KEYWORD_BIT, 0);
assert_ne!(CompactLength::FIT_CONTENT_CALC_TAG & CompactLength::KEYWORD_BIT, 0);
assert_eq!(CompactLength::FIT_CONTENT_CALC_TAG & CompactLength::NON_POINTER_BIT, 0);
assert_ne!(CompactLength::FIT_CONTENT_CALC_TAG & CompactLength::PERCENTAGE_BIT, 0);
}
}
#[test]
fn percentage_bit_matches_uses_percentage() {
for tag in NON_PERCENTAGE_TAGS {
assert_eq!(tag & CompactLength::PERCENTAGE_BIT, 0, "tag {tag:#010b}");
}
for tag in PERCENTAGE_TAGS {
assert_ne!(tag & CompactLength::PERCENTAGE_BIT, 0, "tag {tag:#010b}");
}
#[cfg(feature = "calc")]
assert_ne!(CompactLength::CALC_TAG & CompactLength::PERCENTAGE_BIT, 0);
assert!(!CompactLength::length(10.0).uses_percentage());
assert!(!CompactLength::auto().uses_percentage());
assert!(!CompactLength::fr(1.0).uses_percentage());
assert!(!CompactLength::min_content().uses_percentage());
assert!(!CompactLength::max_content().uses_percentage());
assert!(!CompactLength::fit_content_px(10.0).uses_percentage());
assert!(!CompactLength::fit_content_keyword().uses_percentage());
assert!(!CompactLength::stretch().uses_percentage());
assert!(!CompactLength::content().uses_percentage());
assert!(CompactLength::percent(0.5).uses_percentage());
assert!(CompactLength::fit_content_percent(0.5).uses_percentage());
}
#[test]
#[cfg(feature = "calc")]
fn fit_content_calc_round_trips_pointer() {
let boxed = Box::new(0u64);
let ptr = &*boxed as *const u64 as *const ();
let value = CompactLength::fit_content_calc(ptr);
assert!(value.is_fit_content_calc());
assert!(value.has_calc());
assert!(!value.is_plain_calc());
assert!(value.is_fit_content());
assert!(value.is_sizing_keyword());
assert!(value.is_intrinsic());
assert!(value.is_max_or_fit_content());
assert!(value.is_max_content_alike());
assert!(value.uses_percentage());
assert!(!value.is_length_or_percentage());
assert_eq!(value.calc_value(), ptr);
let calc = CompactLength::calc(ptr);
assert!(calc.is_plain_calc());
assert!(calc.has_calc());
assert!(!calc.is_fit_content_calc());
assert!(!calc.is_fit_content());
assert!(!calc.is_sizing_keyword());
}
#[test]
fn calc_round_trips_pointer() {
let boxed = Box::new(42u64);
let ptr = &*boxed as *const u64 as *const ();
let value = CompactLength::calc(ptr);
assert!(value.is_plain_calc());
assert!(value.uses_percentage());
assert!(!value.is_length_or_percentage());
assert!(!value.is_auto());
assert!(!value.is_intrinsic());
assert_eq!(value.calc_value(), ptr);
}
}