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