taffy/style/
compact_length.rs1use super::LengthPercentage;
4use crate::style_helpers::{
5 FromFr, FromLength, FromPercent, TaffyAuto, TaffyFitContent, TaffyMaxContent, TaffyMinContent, TaffyZero,
6};
7
8mod compat {
12 #![allow(unsafe_code)]
13 #![allow(unknown_lints)]
14 #![allow(unnecessary_transmutes)]
15
16 pub const fn f32_to_bits(val: f32) -> u32 {
18 unsafe { core::mem::transmute(val) }
20 }
21 pub const fn f32_from_bits(v: u32) -> f32 {
23 unsafe { core::mem::transmute(v) }
25 }
26
27 #[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 #[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#[cfg(target_pointer_width = "64")]
47mod inner {
48 use super::compat::{f32_from_bits, f32_to_bits, tag_ptr};
49
50 const TAG_MASK: usize = 0b11111111;
52 const CALC_TAG_MASK: usize = 0b111;
54 #[derive(Copy, Clone, Debug, PartialEq)]
62 pub(super) struct CompactLengthInner {
63 tagged_ptr: *const (),
65 }
66 impl CompactLengthInner {
67 #[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 #[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 #[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 #[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 #[inline(always)]
97 pub(super) fn tag(self) -> usize {
98 (self.tagged_ptr as usize) & TAG_MASK
99 }
100
101 #[inline(always)]
103 pub(super) fn ptr(self) -> *const () {
104 self.tagged_ptr
105 }
106
107 #[inline(always)]
109 pub(super) fn value(self) -> f32 {
110 f32_from_bits((self.tagged_ptr as usize >> 32) as u32)
111 }
112
113 #[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 #[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#[cfg(target_pointer_width = "32")]
131mod inner {
132 use super::compat::{f32_from_bits, f32_to_bits};
133
134 #[derive(Copy, Clone, Debug, PartialEq)]
138 pub(super) struct CompactLengthInner {
139 tag: usize,
141 ptr: *const (),
143 }
144
145 impl CompactLengthInner {
146 #[inline(always)]
148 pub(super) fn from_ptr(ptr: *const (), tag: usize) -> Self {
149 Self { ptr, tag }
150 }
151
152 #[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 #[inline(always)]
160 pub(super) const fn from_tag(tag: usize) -> Self {
161 Self { ptr: 0 as *const (), tag }
162 }
163
164 #[inline(always)]
166 #[cfg(feature = "calc")]
167 pub(super) fn calc_tag(self) -> usize {
168 self.tag
169 }
170
171 #[inline(always)]
173 pub(super) fn tag(self) -> usize {
174 self.tag
175 }
176
177 #[inline(always)]
179 pub(super) fn ptr(self) -> *const () {
180 self.ptr
181 }
182
183 #[inline(always)]
185 pub(super) fn value(self) -> f32 {
186 f32_from_bits(self.ptr as u32)
187 }
188
189 #[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 #[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#[derive(Copy, Clone, PartialEq, Debug)]
209#[repr(transparent)]
210pub struct CompactLength(CompactLengthInner);
211
212impl CompactLength {
213 #[cfg(feature = "calc")]
215 pub const CALC_TAG: usize = 0b000;
216 pub const LENGTH_TAG: usize = 0b0000_0001;
218 pub const PERCENT_TAG: usize = 0b0000_0010;
220 pub const AUTO_TAG: usize = 0b0000_0011;
222 pub const FR_TAG: usize = 0b0000_0100;
224 pub const MIN_CONTENT_TAG: usize = 0b00000111;
226 pub const MAX_CONTENT_TAG: usize = 0b00001111;
228 pub const FIT_CONTENT_PX_TAG: usize = 0b00010111;
230 pub const FIT_CONTENT_PERCENT_TAG: usize = 0b00011111;
232 pub const FIT_CONTENT_KEYWORD_TAG: usize = 0b00100111;
234 pub const STRETCH_TAG: usize = 0b00101111;
236 pub const CONTENT_TAG: usize = 0b00110111;
238}
239
240impl CompactLength {
241 #[inline(always)]
244 pub const fn length(val: f32) -> Self {
245 Self(CompactLengthInner::from_val(val, Self::LENGTH_TAG))
246 }
247
248 #[inline(always)]
252 pub const fn percent(val: f32) -> Self {
253 Self(CompactLengthInner::from_val(val, Self::PERCENT_TAG))
254 }
255
256 #[inline]
261 #[cfg(feature = "calc")]
262 pub fn calc(ptr: *const ()) -> Self {
263 assert_ne!(ptr as u64, 0);
264 assert_eq!(ptr as u64 & 0b111, 0);
265 Self(CompactLengthInner::from_ptr(ptr, Self::CALC_TAG))
266 }
267
268 #[inline(always)]
271 pub const fn auto() -> Self {
272 Self(CompactLengthInner::from_tag(Self::AUTO_TAG))
273 }
274
275 #[inline(always)]
279 pub const fn fr(val: f32) -> Self {
280 Self(CompactLengthInner::from_val(val, Self::FR_TAG))
281 }
282
283 #[inline(always)]
286 pub const fn min_content() -> Self {
287 Self(CompactLengthInner::from_tag(Self::MIN_CONTENT_TAG))
288 }
289
290 #[inline(always)]
293 pub const fn max_content() -> Self {
294 Self(CompactLengthInner::from_tag(Self::MAX_CONTENT_TAG))
295 }
296
297 #[inline(always)]
307 pub const fn fit_content_px(limit: f32) -> Self {
308 Self(CompactLengthInner::from_val(limit, Self::FIT_CONTENT_PX_TAG))
309 }
310
311 #[inline(always)]
321 pub const fn fit_content_percent(limit: f32) -> Self {
322 Self(CompactLengthInner::from_val(limit, Self::FIT_CONTENT_PERCENT_TAG))
323 }
324
325 #[inline(always)]
332 pub const fn fit_content_keyword() -> Self {
333 Self(CompactLengthInner::from_tag(Self::FIT_CONTENT_KEYWORD_TAG))
334 }
335
336 #[inline(always)]
340 pub const fn stretch() -> Self {
341 Self(CompactLengthInner::from_tag(Self::STRETCH_TAG))
342 }
343
344 #[inline(always)]
349 pub const fn content() -> Self {
350 Self(CompactLengthInner::from_tag(Self::CONTENT_TAG))
351 }
352
353 #[inline(always)]
355 pub fn tag(self) -> usize {
356 self.0.tag()
357 }
358
359 #[inline(always)]
362 pub fn value(self) -> f32 {
363 self.0.value()
364 }
365
366 #[inline(always)]
368 #[cfg(feature = "calc")]
369 pub fn calc_value(self) -> *const () {
370 self.0.ptr()
371 }
372
373 #[inline(always)]
375 #[cfg(feature = "calc")]
376 pub fn is_calc(self) -> bool {
377 self.0.calc_tag() == 0
378 }
379
380 #[inline(always)]
382 pub fn is_zero(self) -> bool {
383 self.0 == Self::ZERO.0
384 }
385
386 #[inline(always)]
388 pub fn is_length_or_percentage(self) -> bool {
389 matches!(self.tag(), Self::LENGTH_TAG | Self::PERCENT_TAG)
390 }
391
392 #[inline(always)]
394 pub fn is_auto(self) -> bool {
395 self.tag() == Self::AUTO_TAG
396 }
397
398 #[inline(always)]
400 pub fn is_content(self) -> bool {
401 self.tag() == Self::CONTENT_TAG
402 }
403
404 #[inline(always)]
406 pub fn is_min_content(self) -> bool {
407 matches!(self.tag(), Self::MIN_CONTENT_TAG)
408 }
409
410 #[inline(always)]
412 pub fn is_max_content(self) -> bool {
413 matches!(self.tag(), Self::MAX_CONTENT_TAG)
414 }
415
416 #[inline(always)]
418 pub fn is_fit_content(self) -> bool {
419 matches!(self.tag(), Self::FIT_CONTENT_PX_TAG | Self::FIT_CONTENT_PERCENT_TAG)
420 }
421
422 #[inline(always)]
424 pub fn is_sizing_keyword(self) -> bool {
425 matches!(
426 self.tag(),
427 Self::MIN_CONTENT_TAG
428 | Self::MAX_CONTENT_TAG
429 | Self::FIT_CONTENT_KEYWORD_TAG
430 | Self::FIT_CONTENT_PX_TAG
431 | Self::FIT_CONTENT_PERCENT_TAG
432 | Self::STRETCH_TAG
433 )
434 }
435
436 #[inline(always)]
438 pub fn is_max_or_fit_content(self) -> bool {
439 matches!(self.tag(), Self::MAX_CONTENT_TAG | Self::FIT_CONTENT_PX_TAG | Self::FIT_CONTENT_PERCENT_TAG)
440 }
441
442 #[inline(always)]
446 pub fn is_max_content_alike(&self) -> bool {
447 matches!(
448 self.tag(),
449 CompactLength::AUTO_TAG
450 | CompactLength::MAX_CONTENT_TAG
451 | CompactLength::FIT_CONTENT_PX_TAG
452 | CompactLength::FIT_CONTENT_PERCENT_TAG
453 )
454 }
455
456 #[inline(always)]
458 pub fn is_min_or_max_content(&self) -> bool {
459 matches!(self.tag(), Self::MIN_CONTENT_TAG | Self::MAX_CONTENT_TAG)
460 }
461
462 #[inline(always)]
464 pub fn is_intrinsic(self) -> bool {
465 matches!(
466 self.tag(),
467 Self::AUTO_TAG
468 | Self::MIN_CONTENT_TAG
469 | Self::MAX_CONTENT_TAG
470 | Self::FIT_CONTENT_PX_TAG
471 | Self::FIT_CONTENT_PERCENT_TAG
472 )
473 }
474
475 #[inline(always)]
477 pub fn is_fr(self) -> bool {
478 self.tag() == Self::FR_TAG
479 }
480
481 #[inline(always)]
483 pub fn uses_percentage(self) -> bool {
484 #[cfg(feature = "calc")]
485 {
486 matches!(self.tag(), CompactLength::PERCENT_TAG | CompactLength::FIT_CONTENT_PERCENT_TAG) || self.is_calc()
487 }
488 #[cfg(not(feature = "calc"))]
489 {
490 matches!(self.tag(), CompactLength::PERCENT_TAG | CompactLength::FIT_CONTENT_PERCENT_TAG)
491 }
492 }
493
494 #[inline(always)]
497 pub fn resolved_percentage_size(
498 self,
499 parent_size: f32,
500 calc_resolver: impl Fn(*const (), f32) -> f32,
501 ) -> Option<f32> {
502 match self.tag() {
503 CompactLength::PERCENT_TAG => Some(self.value() * parent_size),
504 #[cfg(feature = "calc")]
505 _ if self.is_calc() => Some(calc_resolver(self.0.ptr(), parent_size)),
506 _ => None,
507 }
508 }
509}
510
511impl TaffyZero for CompactLength {
512 const ZERO: Self = Self::length(0.0);
513}
514impl TaffyAuto for CompactLength {
515 const AUTO: Self = Self::auto();
516}
517impl TaffyMinContent for CompactLength {
518 const MIN_CONTENT: Self = Self::min_content();
519}
520impl TaffyMaxContent for CompactLength {
521 const MAX_CONTENT: Self = Self::max_content();
522}
523impl FromLength for CompactLength {
524 fn from_length<Input: Into<f64> + Copy>(value: Input) -> Self {
525 Self::length(value.into() as f32)
526 }
527}
528impl FromPercent for CompactLength {
529 fn from_percent<Input: Into<f64> + Copy>(value: Input) -> Self {
530 Self::percent(value.into() as f32)
531 }
532}
533impl FromFr for CompactLength {
534 fn from_fr<Input: Into<f64> + Copy>(value: Input) -> Self {
535 Self::fr(value.into() as f32)
536 }
537}
538impl TaffyFitContent for CompactLength {
539 fn fit_content(lp: LengthPercentage) -> Self {
540 let value = lp.0.value();
541 match lp.0.tag() {
542 Self::LENGTH_TAG => Self::fit_content_px(value),
543 Self::PERCENT_TAG => Self::fit_content_percent(value),
544 _ => unreachable!(),
545 }
546 }
547}
548
549#[cfg(feature = "serde")]
550impl serde::Serialize for CompactLength {
551 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
552 where
553 S: serde::Serializer,
554 {
555 #[cfg(feature = "calc")]
556 {
557 if self.tag() == Self::CALC_TAG {
558 Err(serde::ser::Error::custom("Cannot serialize Calc value"))
559 } else {
560 serializer.serialize_u64(self.0.serialized())
561 }
562 }
563
564 #[cfg(not(feature = "calc"))]
565 serializer.serialize_u64(self.0.serialized())
566 }
567}
568
569#[cfg(feature = "serde")]
570impl<'de> serde::Deserialize<'de> for CompactLength {
571 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
572 where
573 D: serde::Deserializer<'de>,
574 {
575 let bits: u64 = u64::deserialize(deserializer)?;
576 let value = Self(CompactLengthInner::from_serialized(bits));
577 if matches!(
579 value.tag(),
580 CompactLength::LENGTH_TAG
581 | CompactLength::PERCENT_TAG
582 | CompactLength::AUTO_TAG
583 | CompactLength::MIN_CONTENT_TAG
584 | CompactLength::MAX_CONTENT_TAG
585 | CompactLength::FIT_CONTENT_KEYWORD_TAG
586 | CompactLength::FIT_CONTENT_PX_TAG
587 | CompactLength::FIT_CONTENT_PERCENT_TAG
588 | CompactLength::STRETCH_TAG
589 | CompactLength::CONTENT_TAG
590 | CompactLength::FR_TAG
591 ) {
592 Ok(value)
593 } else {
594 Err(serde::de::Error::custom("Cannot deserialize Calc value"))
595 }
596 }
597}