⎇ bd0150f5fca7eadd62553898d3b064ccc3c54897
src/style/compact_length.rs
Ñawpa willay
Pi imata llamkarqan
ch'uya
Validate tag when deserializing values (#805)
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// /// Error serializing
45// enum SerializeErr {
46// /// Serializing calc values is not supported
47// CalcValue,
48// }
49
50/// CompactLengthInner implementation for 64 bit platforms
51#[cfg(target_pointer_width = "64")]
52mod inner {
53 use super::compat::{f32_from_bits, f32_to_bits, tag_ptr};
54
55 /// The low byte (8 bits)
56 const TAG_MASK: usize = 0b11111111;
57 /// The low 3 bits
58 const CALC_TAG_MASK: usize = 0b111;
59 // The high 63 bits
60 // const CALC_PTR_MASK: usize = usize::MAX ^ 0b111;
61
62 /// On 64 bit platforms the tag, value and pointer are packed into a single 64 bit pointer
63 ///
64 /// The tagged pointer always has a tag and may contain an f32 value or a pointer
65 /// (or neither) depending on the variant indicated by the tag.
66 #[derive(Copy, Clone, Debug, PartialEq)]
67 pub(super) struct CompactLengthInner {
68 /// The tagged pointer
69 tagged_ptr: *const (),
70 }
71 impl CompactLengthInner {
72 /// Construct a `CompactLengthInner` from a tag and pointer
73 #[inline(always)]
74 pub(super) fn from_ptr(ptr: *const (), tag: usize) -> Self {
75 let tagged_ptr = tag_ptr(ptr, tag);
76 Self { tagged_ptr }
77 }
78
79 /// Construct a `CompactLengthInner` from a tag and numeric value
80 #[inline(always)]
81 pub(super) const fn from_val(val: f32, tag: usize) -> Self {
82 let tagged_ptr = (((f32_to_bits(val) as usize) << 32) | tag) as *const ();
83 Self { tagged_ptr }
84 }
85
86 /// Construct a `CompactLengthInner` from only a tag
87 #[inline(always)]
88 pub(super) const fn from_tag(tag: usize) -> Self {
89 let tagged_ptr = tag as *const ();
90 Self { tagged_ptr }
91 }
92
93 /// Get the calc tag (low 3 bits)
94 #[inline(always)]
95 pub(super) fn calc_tag(self) -> usize {
96 (self.tagged_ptr as usize) & CALC_TAG_MASK
97 }
98
99 /// Get the general tag (low 8 bits)
100 #[inline(always)]
101 pub(super) fn tag(self) -> usize {
102 (self.tagged_ptr as usize) & TAG_MASK
103 }
104
105 /// Get the pointer value
106 #[inline(always)]
107 pub(super) fn ptr(self) -> *const () {
108 self.tagged_ptr
109 }
110
111 /// Get the numeric value
112 #[inline(always)]
113 pub(super) fn value(self) -> f32 {
114 f32_from_bits((self.tagged_ptr as usize >> 32) as u32)
115 }
116
117 /// Get the serialized value.
118 #[inline(always)]
119 #[cfg(feature = "serde")]
120 pub(super) fn serialized(self) -> u64 {
121 (self.tagged_ptr as usize as u64).rotate_left(32)
122 }
123
124 /// Derialized from a value.
125 #[inline(always)]
126 #[cfg(feature = "serde")]
127 pub(super) fn from_serialized(value: u64) -> Self {
128 Self { tagged_ptr: value.rotate_right(32) as usize as *const () }
129 }
130 }
131}
132
133/// CompactLengthInner implementation for 32 bit platforms
134#[cfg(target_pointer_width = "32")]
135mod inner {
136 use super::compat::{f32_from_bits, f32_to_bits};
137
138 /// On 32 bit platforms the tag is stored separately.
139 /// Either an f32 value or a pointer (or neither) are packed into the ptr field
140 /// depending on the variant indicated by the tag
141 #[derive(Copy, Clone, Debug, PartialEq)]
142 pub(super) struct CompactLengthInner {
143 /// The tag indicating what kind of value we are storing
144 tag: usize,
145 /// The pointer of numeric value
146 ptr: *const (),
147 }
148
149 impl CompactLengthInner {
150 /// Construct a `CompactLengthInner` from a tag and pointer
151 #[inline(always)]
152 pub(super) fn from_ptr(ptr: *const (), tag: usize) -> Self {
153 Self { ptr, tag }
154 }
155
156 /// Construct a `CompactLengthInner` from a tag and numeric value
157 #[inline(always)]
158 pub(super) const fn from_val(val: f32, tag: usize) -> Self {
159 Self { ptr: f32_to_bits(val) as usize as *const (), tag }
160 }
161
162 /// Construct a `CompactLengthInner` from only a tag
163 #[inline(always)]
164 pub(super) const fn from_tag(tag: usize) -> Self {
165 Self { ptr: 0 as *const (), tag }
166 }
167
168 /// Get the calc tag (low 3 bits)
169 #[inline(always)]
170 pub(super) fn calc_tag(self) -> usize {
171 self.tag
172 }
173
174 /// Get the general tag (low 8 bits)
175 #[inline(always)]
176 pub(super) fn tag(self) -> usize {
177 self.tag
178 }
179
180 /// Get the pointer value
181 #[inline(always)]
182 pub(super) fn ptr(self) -> *const () {
183 self.ptr
184 }
185
186 /// Get the numeric value
187 #[inline(always)]
188 pub(super) fn value(self) -> f32 {
189 f32_from_bits(self.ptr as u32)
190 }
191
192 /// Get the serialized value.
193 #[inline(always)]
194 #[cfg(feature = "serde")]
195 pub(super) fn serialized(self) -> u64 {
196 (self.tag as u64) << 32 | (self.ptr as u64)
197 }
198
199 /// Derialized from a value.
200 #[inline(always)]
201 #[cfg(feature = "serde")]
202 pub(super) fn from_serialized(value: u64) -> Self {
203 Self { tag: value >> 32 as usize, ptr: value & 0xFFFFFFFF as usize as *const () }
204 }
205 }
206}
207
208use inner::CompactLengthInner;
209
210/// A representation of a length as a compact 64-bit tagged pointer
211#[derive(Copy, Clone, PartialEq, Debug)]
212#[repr(transparent)]
213pub struct CompactLength(CompactLengthInner);
214
215impl CompactLength {
216 /// The tag indicating a calc() value
217 pub const CALC_TAG: usize = 0b000;
218 /// The tag indicating a length value
219 pub const LENGTH_TAG: usize = 0b0000_0001;
220 /// The tag indicating a percentage value
221 pub const PERCENT_TAG: usize = 0b0000_0010;
222 /// The tag indicating an auto value
223 pub const AUTO_TAG: usize = 0b0000_0011;
224 /// The tag indicating an fr value
225 pub const FR_TAG: usize = 0b0000_0100;
226 /// The tag indicating a min-content value
227 pub const MIN_CONTENT_TAG: usize = 0b00000111;
228 /// The tag indicating a max-content value
229 pub const MAX_CONTENT_TAG: usize = 0b00001111;
230 /// The tag indicating a fit-content value with px limit
231 pub const FIT_CONTENT_PX_TAG: usize = 0b00010111;
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 pub fn calc(ptr: *const ()) -> Self {
258 assert_ne!(ptr as u64, 0);
259 assert_eq!(ptr as u64 & 0b111, 0);
260 Self(CompactLengthInner::from_ptr(ptr, Self::CALC_TAG))
261 }
262
263 /// The dimension should be automatically computed according to algorithm-specific rules
264 /// regarding the default size of boxes.
265 #[inline(always)]
266 pub const fn auto() -> Self {
267 Self(CompactLengthInner::from_tag(Self::AUTO_TAG))
268 }
269
270 /// The dimension as a fraction of the total available grid space (`fr` units in CSS)
271 /// Specified value is the numerator of the fraction. Denominator is the sum of all fraction specified in that grid dimension
272 /// Spec: <https://www.w3.org/TR/css3-grid-layout/#fr-unit>
273 #[inline(always)]
274 pub const fn fr(val: f32) -> Self {
275 Self(CompactLengthInner::from_val(val, Self::FR_TAG))
276 }
277
278 /// The size should be the "min-content" size.
279 /// This is the smallest size that can fit the item's contents with ALL soft line-wrapping opportunities taken
280 #[inline(always)]
281 pub const fn min_content() -> Self {
282 Self(CompactLengthInner::from_tag(Self::MIN_CONTENT_TAG))
283 }
284
285 /// The size should be the "max-content" size.
286 /// This is the smallest size that can fit the item's contents with NO soft line-wrapping opportunities taken
287 #[inline(always)]
288 pub const fn max_content() -> Self {
289 Self(CompactLengthInner::from_tag(Self::MAX_CONTENT_TAG))
290 }
291
292 /// The size should be computed according to the "fit content" formula:
293 /// `max(min_content, min(max_content, limit))`
294 /// where:
295 /// - `min_content` is the [min-content](Self::min_content) size
296 /// - `max_content` is the [max-content](Self::max_content) size
297 /// - `limit` is a LENGTH value passed to this function
298 ///
299 /// The effect of this is that the item takes the size of `limit` clamped
300 /// by the min-content and max-content sizes.
301 #[inline(always)]
302 pub const fn fit_content_px(limit: f32) -> Self {
303 Self(CompactLengthInner::from_val(limit, Self::FIT_CONTENT_PX_TAG))
304 }
305
306 /// The size should be computed according to the "fit content" formula:
307 /// `max(min_content, min(max_content, limit))`
308 /// where:
309 /// - `min_content` is the [min-content](Self::min_content) size
310 /// - `max_content` is the [max-content](Self::max_content) size
311 /// - `limit` is a PERCENTAGE value passed to this function
312 ///
313 /// The effect of this is that the item takes the size of `limit` clamped
314 /// by the min-content and max-content sizes.
315 #[inline(always)]
316 pub const fn fit_content_percent(limit: f32) -> Self {
317 Self(CompactLengthInner::from_val(limit, Self::FIT_CONTENT_PERCENT_TAG))
318 }
319
320 /// Get the primary tag
321 #[inline(always)]
322 pub fn tag(self) -> usize {
323 self.0.tag()
324 }
325
326 /// Get the numeric value associated with the `CompactLength`
327 /// (e.g. the pixel value for a LENGTH variant)
328 #[inline(always)]
329 pub fn value(self) -> f32 {
330 self.0.value()
331 }
332
333 /// Get the calc pointer of the `CompactLength`
334 #[inline(always)]
335 pub fn calc_value(self) -> *const () {
336 self.0.ptr()
337 }
338
339 /// Returns true if the value is 0 px
340 #[inline(always)]
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 // TODO: handle calc() values
430 matches!(self.tag(), CompactLength::PERCENT_TAG | CompactLength::FIT_CONTENT_PERCENT_TAG) || self.is_calc()
431 }
432
433 /// Resolve percentage values against the passed parent_size, returning Some(value)
434 /// Non-percentage values always return None.
435 #[inline(always)]
436 pub fn resolved_percentage_size(
437 self,
438 parent_size: f32,
439 calc_resolver: impl Fn(*const (), f32) -> f32,
440 ) -> Option<f32> {
441 match self.tag() {
442 CompactLength::PERCENT_TAG => Some(self.value() * parent_size),
443 _ if self.is_calc() => Some(calc_resolver(self.0.ptr(), parent_size)),
444 _ => None,
445 }
446 }
447}
448
449impl TaffyZero for CompactLength {
450 const ZERO: Self = Self::length(0.0);
451}
452impl TaffyAuto for CompactLength {
453 const AUTO: Self = Self::auto();
454}
455impl TaffyMinContent for CompactLength {
456 const MIN_CONTENT: Self = Self::min_content();
457}
458impl TaffyMaxContent for CompactLength {
459 const MAX_CONTENT: Self = Self::max_content();
460}
461impl FromLength for CompactLength {
462 fn from_length<Input: Into<f32> + Copy>(value: Input) -> Self {
463 Self::length(value.into())
464 }
465}
466impl FromPercent for CompactLength {
467 fn from_percent<Input: Into<f32> + Copy>(value: Input) -> Self {
468 Self::percent(value.into())
469 }
470}
471impl FromFr for CompactLength {
472 fn from_fr<Input: Into<f32> + Copy>(value: Input) -> Self {
473 Self::fr(value.into())
474 }
475}
476impl TaffyFitContent for CompactLength {
477 fn fit_content(lp: LengthPercentage) -> Self {
478 let value = lp.0.value();
479 match lp.0.tag() {
480 Self::LENGTH_TAG => Self::fit_content_px(value),
481 Self::PERCENT_TAG => Self::fit_content_percent(value),
482 _ => unreachable!(),
483 }
484 }
485}
486
487#[cfg(feature = "serde")]
488impl serde::Serialize for CompactLength {
489 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
490 where
491 S: serde::Serializer,
492 {
493 if self.tag() == Self::CALC_TAG {
494 Err(serde::ser::Error::custom("Cannot serialize Calc value"))
495 } else {
496 serializer.serialize_u64(self.0.serialized())
497 }
498 }
499}
500
501#[cfg(feature = "serde")]
502impl<'de> serde::Deserialize<'de> for CompactLength {
503 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
504 where
505 D: serde::Deserializer<'de>,
506 {
507 let bits: u64 = u64::deserialize(deserializer)?;
508 let value = Self(CompactLengthInner::from_serialized(bits));
509 // Note: validation intentionally excludes the CALC_TAG as deserializing calc() values is not supported
510 if matches!(
511 value.tag(),
512 CompactLength::LENGTH_TAG
513 | CompactLength::PERCENT_TAG
514 | CompactLength::AUTO_TAG
515 | CompactLength::MIN_CONTENT_TAG
516 | CompactLength::MAX_CONTENT_TAG
517 | CompactLength::FIT_CONTENT_PX_TAG
518 | CompactLength::FIT_CONTENT_PERCENT_TAG
519 | CompactLength::FR_TAG
520 ) {
521 Ok(value)
522 } else {
523 Err(serde::de::Error::custom("Cannot deserialize Calc value"))
524 }
525 }
526}