tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 1077HTML 1008Markdown 12TOML 83209 Definitions

⎇ 8437796cc084de78ee04e2d940086954c924cb98 src/style/compact_length.rs Rust · 539 lines · 18.6 KB History Who touched what raw

Feature flag calc values (#806) 8437796cc · Nico Burns ·

1//! A tagged-pointer abstraction that allows size styles in Taffy to be represented
2//! in just 64 bits. Wrapped by types in the `super::dimension` and `super::grid` modules.
3use super::LengthPercentage;
4use crate::style_helpers::{
5    FromFr, FromLength, FromPercent, TaffyAuto, TaffyFitContent, TaffyMaxContent, TaffyMinContent, TaffyZero,
6};
7
8/// Note: these two functions are copied directly from the std (core) library. But by duplicating them
9/// here we can reduce MSRV from 1.84 all the way down to 1.65 while retaining const constructors and
10/// strict pointer provenance
11mod compat {
12    #![allow(unsafe_code)]
13    #![allow(clippy::transmute_float_to_int)]
14
15    /// Raw transmutation from `f32` to `u32`.
16    pub const fn f32_to_bits(val: f32) -> u32 {
17        // SAFETY: `u32` is a plain old datatype so we can always transmute to it.
18        unsafe { core::mem::transmute(val) }
19    }
20    /// Raw transmutation from `u32` to `f32`.
21    pub const fn f32_from_bits(v: u32) -> f32 {
22        // SAFETY: `u32` is a plain old datatype so we can always transmute from it.
23        unsafe { core::mem::transmute(v) }
24    }
25
26    /// Tag a pointer preserving provenance (requires Rust 1.84)
27    #[inline(always)]
28    #[cfg(all(target_pointer_width = "64", feature = "strict_provenance"))]
29    pub fn tag_ptr(ptr: *const (), tag: usize) -> *const () {
30        ptr.map_addr(|a| a | tag)
31    }
32
33    /// Tag a pointer exposing provenance (works back to Rust 1.0)
34    #[inline(always)]
35    #[cfg(all(target_pointer_width = "64", not(feature = "strict_provenance")))]
36    pub fn tag_ptr(ptr: *const (), tag: usize) -> *const () {
37        (ptr as usize | tag) as *const ()
38    }
39}
40
41#[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))]
42std::compile_error!("Taffy only supports targets with a pointer width of 32 or 64 bits");
43
44/// CompactLengthInner implementation for 64 bit platforms
45#[cfg(target_pointer_width = "64")]
46mod inner {
47    use super::compat::{f32_from_bits, f32_to_bits, tag_ptr};
48
49    /// The low byte (8 bits)
50    const TAG_MASK: usize = 0b11111111;
51    /// The low 3 bits
52    const CALC_TAG_MASK: usize = 0b111;
53    // The high 63 bits
54    // const CALC_PTR_MASK: usize = usize::MAX ^ 0b111;
55
56    /// On 64 bit platforms the tag, value and pointer are packed into a single 64 bit pointer
57    ///
58    /// The tagged pointer always has a tag and may contain an f32 value or a pointer
59    /// (or neither) depending on the variant indicated by the tag.
60    #[derive(Copy, Clone, Debug, PartialEq)]
61    pub(super) struct CompactLengthInner {
62        /// The tagged pointer
63        tagged_ptr: *const (),
64    }
65    impl CompactLengthInner {
66        /// Construct a `CompactLengthInner` from a tag and pointer
67        #[inline(always)]
68        pub(super) fn from_ptr(ptr: *const (), tag: usize) -> Self {
69            let tagged_ptr = tag_ptr(ptr, tag);
70            Self { tagged_ptr }
71        }
72
73        /// Construct a `CompactLengthInner` from a tag and numeric value
74        #[inline(always)]
75        pub(super) const fn from_val(val: f32, tag: usize) -> Self {
76            let tagged_ptr = (((f32_to_bits(val) as usize) << 32) | tag) as *const ();
77            Self { tagged_ptr }
78        }
79
80        /// Construct a `CompactLengthInner` from only a tag
81        #[inline(always)]
82        pub(super) const fn from_tag(tag: usize) -> Self {
83            let tagged_ptr = tag as *const ();
84            Self { tagged_ptr }
85        }
86
87        /// Get the calc tag (low 3 bits)
88        #[inline(always)]
89        #[cfg(feature = "calc")]
90        pub(super) fn calc_tag(self) -> usize {
91            (self.tagged_ptr as usize) & CALC_TAG_MASK
92        }
93
94        /// Get the general tag (low 8 bits)
95        #[inline(always)]
96        pub(super) fn tag(self) -> usize {
97            (self.tagged_ptr as usize) & TAG_MASK
98        }
99
100        /// Get the pointer value
101        #[inline(always)]
102        pub(super) fn ptr(self) -> *const () {
103            self.tagged_ptr
104        }
105
106        /// Get the numeric value
107        #[inline(always)]
108        pub(super) fn value(self) -> f32 {
109            f32_from_bits((self.tagged_ptr as usize >> 32) as u32)
110        }
111
112        /// Get the serialized value.
113        #[inline(always)]
114        #[cfg(feature = "serde")]
115        pub(super) fn serialized(self) -> u64 {
116            (self.tagged_ptr as usize as u64).rotate_left(32)
117        }
118
119        /// Derialized from a value.
120        #[inline(always)]
121        #[cfg(feature = "serde")]
122        pub(super) fn from_serialized(value: u64) -> Self {
123            Self { tagged_ptr: value.rotate_right(32) as usize as *const () }
124        }
125    }
126}
127
128/// CompactLengthInner implementation for 32 bit platforms
129#[cfg(target_pointer_width = "32")]
130mod inner {
131    use super::compat::{f32_from_bits, f32_to_bits};
132
133    /// On 32 bit platforms the tag is stored separately.
134    /// Either an f32 value or a pointer (or neither) are packed into the ptr field
135    /// depending on the variant indicated by the tag
136    #[derive(Copy, Clone, Debug, PartialEq)]
137    pub(super) struct CompactLengthInner {
138        /// The tag indicating what kind of value we are storing
139        tag: usize,
140        /// The pointer of numeric value
141        ptr: *const (),
142    }
143
144    impl CompactLengthInner {
145        /// Construct a `CompactLengthInner` from a tag and pointer
146        #[inline(always)]
147        pub(super) fn from_ptr(ptr: *const (), tag: usize) -> Self {
148            Self { ptr, tag }
149        }
150
151        /// Construct a `CompactLengthInner` from a tag and numeric value
152        #[inline(always)]
153        pub(super) const fn from_val(val: f32, tag: usize) -> Self {
154            Self { ptr: f32_to_bits(val) as usize as *const (), tag }
155        }
156
157        /// Construct a `CompactLengthInner` from only a tag
158        #[inline(always)]
159        pub(super) const fn from_tag(tag: usize) -> Self {
160            Self { ptr: 0 as *const (), tag }
161        }
162
163        /// Get the calc tag (low 3 bits)
164        #[inline(always)]
165        #[cfg(feature = "calc")]
166        pub(super) fn calc_tag(self) -> usize {
167            self.tag
168        }
169
170        /// Get the general tag (low 8 bits)
171        #[inline(always)]
172        pub(super) fn tag(self) -> usize {
173            self.tag
174        }
175
176        /// Get the pointer value
177        #[inline(always)]
178        pub(super) fn ptr(self) -> *const () {
179            self.ptr
180        }
181
182        /// Get the numeric value
183        #[inline(always)]
184        pub(super) fn value(self) -> f32 {
185            f32_from_bits(self.ptr as u32)
186        }
187
188        /// Get the serialized value.
189        #[inline(always)]
190        #[cfg(feature = "serde")]
191        pub(super) fn serialized(self) -> u64 {
192            (self.tag as u64) << 32 | (self.ptr as u64)
193        }
194
195        /// Derialized from a value.
196        #[inline(always)]
197        #[cfg(feature = "serde")]
198        pub(super) fn from_serialized(value: u64) -> Self {
199            Self { tag: value >> 32 as usize, ptr: value & 0xFFFFFFFF as usize as *const () }
200        }
201    }
202}
203
204use inner::CompactLengthInner;
205
206/// A representation of a length as a compact 64-bit tagged pointer
207#[derive(Copy, Clone, PartialEq, Debug)]
208#[repr(transparent)]
209pub struct CompactLength(CompactLengthInner);
210
211impl CompactLength {
212    /// The tag indicating a calc() value
213    #[cfg(feature = "calc")]
214    pub const CALC_TAG: usize = 0b000;
215    /// The tag indicating a length value
216    pub const LENGTH_TAG: usize = 0b0000_0001;
217    /// The tag indicating a percentage value
218    pub const PERCENT_TAG: usize = 0b0000_0010;
219    /// The tag indicating an auto value
220    pub const AUTO_TAG: usize = 0b0000_0011;
221    /// The tag indicating an fr value
222    pub const FR_TAG: usize = 0b0000_0100;
223    /// The tag indicating a min-content value
224    pub const MIN_CONTENT_TAG: usize = 0b00000111;
225    /// The tag indicating a max-content value
226    pub const MAX_CONTENT_TAG: usize = 0b00001111;
227    /// The tag indicating a fit-content value with px limit
228    pub const FIT_CONTENT_PX_TAG: usize = 0b00010111;
229    /// The tag indicating a fit-content value with percent limit
230    pub const FIT_CONTENT_PERCENT_TAG: usize = 0b00011111;
231}
232
233impl CompactLength {
234    /// An absolute length in some abstract units. Users of Taffy may define what they correspond
235    /// to in their application (pixels, logical pixels, mm, etc) as they see fit.
236    #[inline(always)]
237    pub const fn length(val: f32) -> Self {
238        Self(CompactLengthInner::from_val(val, Self::LENGTH_TAG))
239    }
240
241    /// A percentage length relative to the size of the containing block.
242    ///
243    /// **NOTE: percentages are represented as a f32 value in the range [0.0, 1.0] NOT the range [0.0, 100.0]**
244    #[inline(always)]
245    pub const fn percent(val: f32) -> Self {
246        Self(CompactLengthInner::from_val(val, Self::PERCENT_TAG))
247    }
248
249    /// A `calc()` value. The value passed here is treated as an opaque handle to
250    /// the actual calc representation and may be a pointer, index, etc.
251    ///
252    /// The low 3 bits are used as a tag value and will be returned as 0.
253    #[inline]
254    #[cfg(feature = "calc")]
255    pub fn calc(ptr: *const ()) -> Self {
256        assert_ne!(ptr as u64, 0);
257        assert_eq!(ptr as u64 & 0b111, 0);
258        Self(CompactLengthInner::from_ptr(ptr, Self::CALC_TAG))
259    }
260
261    /// The dimension should be automatically computed according to algorithm-specific rules
262    /// regarding the default size of boxes.
263    #[inline(always)]
264    pub const fn auto() -> Self {
265        Self(CompactLengthInner::from_tag(Self::AUTO_TAG))
266    }
267
268    /// The dimension as a fraction of the total available grid space (`fr` units in CSS)
269    /// Specified value is the numerator of the fraction. Denominator is the sum of all fraction specified in that grid dimension
270    /// Spec: <https://www.w3.org/TR/css3-grid-layout/#fr-unit>
271    #[inline(always)]
272    pub const fn fr(val: f32) -> Self {
273        Self(CompactLengthInner::from_val(val, Self::FR_TAG))
274    }
275
276    /// The size should be the "min-content" size.
277    /// This is the smallest size that can fit the item's contents with ALL soft line-wrapping opportunities taken
278    #[inline(always)]
279    pub const fn min_content() -> Self {
280        Self(CompactLengthInner::from_tag(Self::MIN_CONTENT_TAG))
281    }
282
283    /// The size should be the "max-content" size.
284    /// This is the smallest size that can fit the item's contents with NO soft line-wrapping opportunities taken
285    #[inline(always)]
286    pub const fn max_content() -> Self {
287        Self(CompactLengthInner::from_tag(Self::MAX_CONTENT_TAG))
288    }
289
290    /// The size should be computed according to the "fit content" formula:
291    ///    `max(min_content, min(max_content, limit))`
292    /// where:
293    ///    - `min_content` is the [min-content](Self::min_content) size
294    ///    - `max_content` is the [max-content](Self::max_content) size
295    ///    - `limit` is a LENGTH value passed to this function
296    ///
297    /// The effect of this is that the item takes the size of `limit` clamped
298    /// by the min-content and max-content sizes.
299    #[inline(always)]
300    pub const fn fit_content_px(limit: f32) -> Self {
301        Self(CompactLengthInner::from_val(limit, Self::FIT_CONTENT_PX_TAG))
302    }
303
304    /// The size should be computed according to the "fit content" formula:
305    ///    `max(min_content, min(max_content, limit))`
306    /// where:
307    ///    - `min_content` is the [min-content](Self::min_content) size
308    ///    - `max_content` is the [max-content](Self::max_content) size
309    ///    - `limit` is a PERCENTAGE value passed to this function
310    ///
311    /// The effect of this is that the item takes the size of `limit` clamped
312    /// by the min-content and max-content sizes.
313    #[inline(always)]
314    pub const fn fit_content_percent(limit: f32) -> Self {
315        Self(CompactLengthInner::from_val(limit, Self::FIT_CONTENT_PERCENT_TAG))
316    }
317
318    /// Get the primary tag
319    #[inline(always)]
320    pub fn tag(self) -> usize {
321        self.0.tag()
322    }
323
324    /// Get the numeric value associated with the `CompactLength`
325    /// (e.g. the pixel value for a LENGTH variant)
326    #[inline(always)]
327    pub fn value(self) -> f32 {
328        self.0.value()
329    }
330
331    /// Get the calc pointer of the `CompactLength`
332    #[inline(always)]
333    #[cfg(feature = "calc")]
334    pub fn calc_value(self) -> *const () {
335        self.0.ptr()
336    }
337
338    /// Returns true if the value is 0 px
339    #[inline(always)]
340    #[cfg(feature = "calc")]
341    pub fn is_calc(self) -> bool {
342        self.0.calc_tag() == 0
343    }
344
345    /// Returns true if the value is 0 px
346    #[inline(always)]
347    pub fn is_zero(self) -> bool {
348        self.0 == Self::ZERO.0
349    }
350
351    /// Returns true if the value is a length or percentage value
352    #[inline(always)]
353    pub fn is_length_or_percentage(self) -> bool {
354        matches!(self.tag(), Self::LENGTH_TAG | Self::PERCENT_TAG)
355    }
356
357    /// Returns true if the value is auto
358    #[inline(always)]
359    pub fn is_auto(self) -> bool {
360        self.tag() == Self::AUTO_TAG
361    }
362
363    /// Returns true if the value is min-content
364    #[inline(always)]
365    pub fn is_min_content(self) -> bool {
366        matches!(self.tag(), Self::MIN_CONTENT_TAG)
367    }
368
369    /// Returns true if the value is max-content
370    #[inline(always)]
371    pub fn is_max_content(self) -> bool {
372        matches!(self.tag(), Self::MAX_CONTENT_TAG)
373    }
374
375    /// Returns true if the value is a fit-content(...) value
376    #[inline(always)]
377    pub fn is_fit_content(self) -> bool {
378        matches!(self.tag(), Self::FIT_CONTENT_PX_TAG | Self::FIT_CONTENT_PERCENT_TAG)
379    }
380
381    /// Returns true if the value is max-content or a fit-content(...) value
382    #[inline(always)]
383    pub fn is_max_or_fit_content(self) -> bool {
384        matches!(self.tag(), Self::MAX_CONTENT_TAG | Self::FIT_CONTENT_PX_TAG | Self::FIT_CONTENT_PERCENT_TAG)
385    }
386
387    /// Returns true if the max track sizing function is `MaxContent`, `FitContent` or `Auto` else false.
388    /// "In all cases, treat auto and fit-content() as max-content, except where specified otherwise for fit-content()."
389    /// See: <https://www.w3.org/TR/css-grid-1/#algo-terms>
390    #[inline(always)]
391    pub fn is_max_content_alike(&self) -> bool {
392        matches!(
393            self.tag(),
394            CompactLength::AUTO_TAG
395                | CompactLength::MAX_CONTENT_TAG
396                | CompactLength::FIT_CONTENT_PX_TAG
397                | CompactLength::FIT_CONTENT_PERCENT_TAG
398        )
399    }
400
401    /// Returns true if the min track sizing function is `MinContent` or `MaxContent`, else false.
402    #[inline(always)]
403    pub fn is_min_or_max_content(&self) -> bool {
404        matches!(self.tag(), Self::MIN_CONTENT_TAG | Self::MAX_CONTENT_TAG)
405    }
406
407    /// Returns true if the value is auto, min-content, max-content, or fit-content(...)
408    #[inline(always)]
409    pub fn is_intrinsic(self) -> bool {
410        matches!(
411            self.tag(),
412            Self::AUTO_TAG
413                | Self::MIN_CONTENT_TAG
414                | Self::MAX_CONTENT_TAG
415                | Self::FIT_CONTENT_PX_TAG
416                | Self::FIT_CONTENT_PERCENT_TAG
417        )
418    }
419
420    /// Returns true if the value is and fr value
421    #[inline(always)]
422    pub fn is_fr(self) -> bool {
423        self.tag() == Self::FR_TAG
424    }
425
426    /// Whether the track sizing functions depends on the size of the parent node
427    #[inline(always)]
428    pub fn uses_percentage(self) -> bool {
429        #[cfg(feature = "calc")]
430        {
431            matches!(self.tag(), CompactLength::PERCENT_TAG | CompactLength::FIT_CONTENT_PERCENT_TAG) || self.is_calc()
432        }
433        #[cfg(not(feature = "calc"))]
434        {
435            matches!(self.tag(), CompactLength::PERCENT_TAG | CompactLength::FIT_CONTENT_PERCENT_TAG)
436        }
437    }
438
439    /// Resolve percentage values against the passed parent_size, returning Some(value)
440    /// Non-percentage values always return None.
441    #[inline(always)]
442    pub fn resolved_percentage_size(
443        self,
444        parent_size: f32,
445        calc_resolver: impl Fn(*const (), f32) -> f32,
446    ) -> Option<f32> {
447        match self.tag() {
448            CompactLength::PERCENT_TAG => Some(self.value() * parent_size),
449            #[cfg(feature = "calc")]
450            _ if self.is_calc() => Some(calc_resolver(self.0.ptr(), parent_size)),
451            _ => None,
452        }
453    }
454}
455
456impl TaffyZero for CompactLength {
457    const ZERO: Self = Self::length(0.0);
458}
459impl TaffyAuto for CompactLength {
460    const AUTO: Self = Self::auto();
461}
462impl TaffyMinContent for CompactLength {
463    const MIN_CONTENT: Self = Self::min_content();
464}
465impl TaffyMaxContent for CompactLength {
466    const MAX_CONTENT: Self = Self::max_content();
467}
468impl FromLength for CompactLength {
469    fn from_length<Input: Into<f32> + Copy>(value: Input) -> Self {
470        Self::length(value.into())
471    }
472}
473impl FromPercent for CompactLength {
474    fn from_percent<Input: Into<f32> + Copy>(value: Input) -> Self {
475        Self::percent(value.into())
476    }
477}
478impl FromFr for CompactLength {
479    fn from_fr<Input: Into<f32> + Copy>(value: Input) -> Self {
480        Self::fr(value.into())
481    }
482}
483impl TaffyFitContent for CompactLength {
484    fn fit_content(lp: LengthPercentage) -> Self {
485        let value = lp.0.value();
486        match lp.0.tag() {
487            Self::LENGTH_TAG => Self::fit_content_px(value),
488            Self::PERCENT_TAG => Self::fit_content_percent(value),
489            _ => unreachable!(),
490        }
491    }
492}
493
494#[cfg(feature = "serde")]
495impl serde::Serialize for CompactLength {
496    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
497    where
498        S: serde::Serializer,
499    {
500        #[cfg(feature = "calc")]
501        {
502            if self.tag() == Self::CALC_TAG {
503                Err(serde::ser::Error::custom("Cannot serialize Calc value"))
504            } else {
505                serializer.serialize_u64(self.0.serialized())
506            }
507        }
508
509        #[cfg(not(feature = "calc"))]
510        serializer.serialize_u64(self.0.serialized())
511    }
512}
513
514#[cfg(feature = "serde")]
515impl<'de> serde::Deserialize<'de> for CompactLength {
516    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
517    where
518        D: serde::Deserializer<'de>,
519    {
520        let bits: u64 = u64::deserialize(deserializer)?;
521        let value = Self(CompactLengthInner::from_serialized(bits));
522        // Note: validation intentionally excludes the CALC_TAG as deserializing calc() values is not supported
523        if matches!(
524            value.tag(),
525            CompactLength::LENGTH_TAG
526                | CompactLength::PERCENT_TAG
527                | CompactLength::AUTO_TAG
528                | CompactLength::MIN_CONTENT_TAG
529                | CompactLength::MAX_CONTENT_TAG
530                | CompactLength::FIT_CONTENT_PX_TAG
531                | CompactLength::FIT_CONTENT_PERCENT_TAG
532                | CompactLength::FR_TAG
533        ) {
534            Ok(value)
535        } else {
536            Err(serde::de::Error::custom("Cannot deserialize Calc value"))
537        }
538    }
539}