1
//! CSS length and pixel value types, parsing, and unit resolution.
2
//!
3
//! Defines `PixelValue` (a numeric value + CSS unit like px, em, rem, %),
4
//! `ResolutionContext` (contextual information for resolving relative units),
5
//! and `PropertyContext` (which property is being resolved, affecting % and em semantics).
6
//!
7
//! **Resolution paths:**
8
//! - `resolve_with_context()` — the correct method for new code; properly distinguishes
9
//!   em vs rem, and resolves % based on property type per the CSS spec.
10
//! - `to_pixels_internal()` — legacy fallback used by `prop_cache.rs`; does not
11
//!   distinguish rem from em. Marked `#[doc(hidden)]`.
12

            
13
use core::fmt;
14
use std::num::ParseFloatError;
15
use crate::corety::AzString;
16

            
17
use crate::props::{
18
    basic::{error::ParseFloatErrorWithInput, FloatValue, SizeMetric},
19
    formatter::FormatAsCssValue,
20
};
21

            
22
/// Default font size in pixels (16px), matching the CSS "medium" keyword
23
/// and all major browser defaults (CSS 2.1 §15.7).
24
pub const DEFAULT_FONT_SIZE: f32 = 16.0;
25

            
26
/// Conversion factor from points to pixels (1pt = 1/72 inch, 1in = 96px, therefore 1pt = 96/72 px)
27
pub const PT_TO_PX: f32 = 96.0 / 72.0;
28

            
29
/// A normalized percentage value (0.0 = 0%, 1.0 = 100%)
30
///
31
/// This type prevents double-division bugs by making it explicit that the value
32
/// is already normalized to the 0.0-1.0 range. When you have a `NormalizedPercentage`,
33
/// you should multiply it directly with the containing block size, NOT divide by 100 again.
34
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
35
#[repr(transparent)]
36
pub struct NormalizedPercentage(f32);
37

            
38
impl NormalizedPercentage {
39
    /// Create a new percentage value from a normalized float (0.0-1.0)
40
    ///
41
    /// # Arguments
42
    /// * `value` - A normalized percentage where 0.0 = 0% and 1.0 = 100%
43
    #[inline]
44
    pub const fn new(value: f32) -> Self {
45
        Self(value)
46
    }
47

            
48
    /// Create a percentage from an unnormalized value (0-100 scale)
49
    ///
50
    /// This divides by 100 internally, so you should use this when converting
51
    /// from CSS percentage syntax like "50%" which is stored as 50.0.
52
    #[inline]
53
3033
    pub fn from_unnormalized(value: f32) -> Self {
54
3033
        Self(value / 100.0)
55
3033
    }
56

            
57
    /// Get the raw normalized value (0.0-1.0)
58
    #[inline]
59
2345
    pub const fn get(self) -> f32 {
60
2345
        self.0
61
2345
    }
62

            
63
    /// Resolve this percentage against a containing block size
64
    ///
65
    /// This multiplies the normalized percentage by the containing block size.
66
    /// For example, 50% (0.5) of 640px = 320px.
67
    #[inline]
68
85
    pub fn resolve(self, containing_block_size: f32) -> f32 {
69
85
        self.0 * containing_block_size
70
85
    }
71
}
72

            
73
impl fmt::Display for NormalizedPercentage {
74
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
75
        write!(f, "{}%", self.0 * 100.0)
76
    }
77
}
78

            
79
/// Logical size in CSS logical coordinate system
80
#[derive(Debug, Copy, Clone, PartialEq)]
81
#[repr(C)]
82
pub struct CssLogicalSize {
83
    /// Inline-axis size (width in horizontal writing mode)
84
    pub inline_size: f32,
85
    /// Block-axis size (height in horizontal writing mode)
86
    pub block_size: f32,
87
}
88

            
89
impl CssLogicalSize {
90
    #[inline]
91
    pub const fn new(inline_size: f32, block_size: f32) -> Self {
92
        Self {
93
            inline_size,
94
            block_size,
95
        }
96
    }
97

            
98
    /// Convert to physical size (width, height) in horizontal writing mode
99
    #[inline]
100
    pub const fn to_physical(self) -> PhysicalSize {
101
        PhysicalSize {
102
            width: self.inline_size,
103
            height: self.block_size,
104
        }
105
    }
106
}
107

            
108
/// Physical size (always width x height, regardless of writing mode)
109
#[derive(Debug, Copy, Clone, PartialEq)]
110
#[repr(C)]
111
pub struct PhysicalSize {
112
    pub width: f32,
113
    pub height: f32,
114
}
115

            
116
impl PhysicalSize {
117
    #[inline]
118
200342
    pub const fn new(width: f32, height: f32) -> Self {
119
200342
        Self { width, height }
120
200342
    }
121

            
122
    /// Convert to logical size in horizontal writing mode
123
    #[inline]
124
    pub const fn to_logical(self) -> CssLogicalSize {
125
        CssLogicalSize {
126
            inline_size: self.width,
127
            block_size: self.height,
128
        }
129
    }
130
}
131

            
132
/// Context information needed to properly resolve CSS units (em, rem, %) to pixels.
133
///
134
/// This struct contains all the contextual information that `PixelValue::resolve()`
135
/// needs to correctly convert relative units according to the CSS specification:
136
///
137
/// - **em** units: For most properties, em refers to the element's own computed font-size. For the
138
///   font-size property itself, em refers to the parent's computed font-size.
139
///
140
/// - **rem** units: Always refer to the root element's computed font-size.
141
///
142
/// - **%** units: Percentage resolution depends on the property:
143
///   - Width/height: relative to containing block dimensions
144
///   - Margin/padding: relative to containing block width (even top/bottom!)
145
///   - Border-radius: relative to element's own border box dimensions
146
///   - Font-size: relative to parent's font-size
147
#[derive(Debug, Copy, Clone)]
148
pub struct ResolutionContext {
149
    /// The computed font-size of the current element (for em in non-font properties)
150
    pub element_font_size: f32,
151

            
152
    /// The computed font-size of the parent element (for em in font-size property)
153
    pub parent_font_size: f32,
154

            
155
    /// The computed font-size of the root element (for rem units)
156
    pub root_font_size: f32,
157

            
158
    /// The containing block dimensions (for % in width/height/margins/padding)
159
    pub containing_block_size: PhysicalSize,
160

            
161
    /// The element's own border box size (for % in border-radius, transforms)
162
    /// May be None during first layout pass before size is determined
163
    pub element_size: Option<PhysicalSize>,
164

            
165
    /// The viewport size in CSS pixels (for vw, vh, vmin, vmax units)
166
    /// This is the layout viewport size, not physical screen size
167
    pub viewport_size: PhysicalSize,
168
}
169

            
170
impl Default for ResolutionContext {
171
2
    fn default() -> Self {
172
2
        Self {
173
2
            element_font_size: 16.0,
174
2
            parent_font_size: 16.0,
175
2
            root_font_size: 16.0,
176
2
            containing_block_size: PhysicalSize::new(0.0, 0.0),
177
2
            element_size: None,
178
2
            viewport_size: PhysicalSize::new(0.0, 0.0),
179
2
        }
180
2
    }
181
}
182

            
183
impl ResolutionContext {
184
    /// Create a minimal context for testing or default resolution
185
    #[inline]
186
    pub const fn default_const() -> Self {
187
        Self {
188
            element_font_size: 16.0,
189
            parent_font_size: 16.0,
190
            root_font_size: 16.0,
191
            containing_block_size: PhysicalSize {
192
                width: 0.0,
193
                height: 0.0,
194
            },
195
            element_size: None,
196
            viewport_size: PhysicalSize {
197
                width: 0.0,
198
                height: 0.0,
199
            },
200
        }
201
    }
202

            
203
}
204

            
205
/// Specifies which property context we're resolving for, to determine correct reference values
206
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
207
pub enum PropertyContext {
208
    /// Resolving for the font-size property itself (em refers to parent)
209
    FontSize,
210
    /// Resolving for margin properties (% refers to containing block width)
211
    Margin,
212
    /// Resolving for padding properties (% refers to containing block width)
213
    Padding,
214
    /// Resolving for width or horizontal properties (% refers to containing block width)
215
    Width,
216
    /// Resolving for height or vertical properties (% refers to containing block height)
217
    Height,
218
    /// Resolving for border-width properties (only absolute lengths + em/rem, no % support)
219
    BorderWidth,
220
    /// Resolving for border-radius (% refers to element's own dimensions)
221
    BorderRadius,
222
    /// Resolving for transforms (% refers to element's own dimensions)
223
    Transform,
224
    /// Resolving for other properties (em refers to element font-size)
225
    Other,
226
}
227

            
228
/// A CSS length value consisting of a numeric value and a unit (px, em, rem, %, etc.).
229
#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
230
#[repr(C)]
231
pub struct PixelValue {
232
    pub metric: SizeMetric,
233
    pub number: FloatValue,
234
}
235

            
236
impl PixelValue {
237
    pub fn scale_for_dpi(&mut self, scale_factor: f32) {
238
        self.number = FloatValue::new(self.number.get() * scale_factor);
239
    }
240
}
241

            
242
impl FormatAsCssValue for PixelValue {
243
    fn format_as_css_value(&self, f: &mut fmt::Formatter) -> fmt::Result {
244
        write!(f, "{}{}", self.number, self.metric)
245
    }
246
}
247

            
248
impl crate::css::PrintAsCssValue for PixelValue {
249
28
    fn print_as_css_value(&self) -> String {
250
28
        format!("{}{}", self.number, self.metric)
251
28
    }
252
}
253

            
254
impl crate::format_rust_code::FormatAsRustCode for PixelValue {
255
    fn format_as_rust_code(&self, _tabs: usize) -> String {
256
        format!(
257
            "PixelValue {{ metric: {:?}, number: FloatValue::new({}) }}",
258
            self.metric,
259
            self.number.get()
260
        )
261
    }
262
}
263

            
264
// Manual Debug implementation, because the auto-generated one is nearly unreadable
265
impl fmt::Debug for PixelValue {
266
30280
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
267
30280
        write!(f, "{}{}", self.number, self.metric)
268
30280
    }
269
}
270

            
271
impl fmt::Display for PixelValue {
272
152
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
273
152
        write!(f, "{}{}", self.number, self.metric)
274
152
    }
275
}
276

            
277
impl PixelValue {
278
    #[inline]
279
8022
    pub const fn zero() -> Self {
280
        const ZERO_PX: PixelValue = PixelValue::const_px(0);
281
8022
        ZERO_PX
282
8022
    }
283

            
284
    /// Same as `PixelValue::px()`, but only accepts whole numbers,
285
    /// since using `f32` in const fn is not yet stabilized.
286
    #[inline]
287
128128
    pub const fn const_px(value: isize) -> Self {
288
128128
        Self::const_from_metric(SizeMetric::Px, value)
289
128128
    }
290

            
291
    /// Same as `PixelValue::em()`, but only accepts whole numbers,
292
    /// since using `f32` in const fn is not yet stabilized.
293
    #[inline]
294
    pub const fn const_em(value: isize) -> Self {
295
        Self::const_from_metric(SizeMetric::Em, value)
296
    }
297

            
298
    /// Creates an em value from a fractional number in const context.
299
    ///
300
    /// # Arguments
301
    /// * `pre_comma` - The integer part (e.g., 1 for 1.5em)
302
    /// * `post_comma` - The fractional part as digits (e.g., 5 for 0.5em, 83 for 0.83em)
303
    ///
304
    /// # Examples
305
    /// ```
306
    /// // 1.5em = const_em_fractional(1, 5)
307
    /// // 0.83em = const_em_fractional(0, 83)
308
    /// // 1.17em = const_em_fractional(1, 17)
309
    /// ```
310
    #[inline]
311
    pub const fn const_em_fractional(pre_comma: isize, post_comma: isize) -> Self {
312
        Self::const_from_metric_fractional(SizeMetric::Em, pre_comma, post_comma)
313
    }
314

            
315
    /// Same as `PixelValue::pt()`, but only accepts whole numbers,
316
    /// since using `f32` in const fn is not yet stabilized.
317
    #[inline]
318
    pub const fn const_pt(value: isize) -> Self {
319
        Self::const_from_metric(SizeMetric::Pt, value)
320
    }
321

            
322
    /// Creates a pt value from a fractional number in const context.
323
    #[inline]
324
    pub const fn const_pt_fractional(pre_comma: isize, post_comma: isize) -> Self {
325
        Self::const_from_metric_fractional(SizeMetric::Pt, pre_comma, post_comma)
326
    }
327

            
328
    /// Same as `PixelValue::percent()`, but only accepts whole numbers,
329
    /// since using `f32` in const fn is not yet stabilized.
330
    #[inline]
331
5460
    pub const fn const_percent(value: isize) -> Self {
332
5460
        Self::const_from_metric(SizeMetric::Percent, value)
333
5460
    }
334

            
335
    /// Same as `PixelValue::in()`, but only accepts whole numbers,
336
    /// since using `f32` in const fn is not yet stabilized.
337
    #[inline]
338
    pub const fn const_in(value: isize) -> Self {
339
        Self::const_from_metric(SizeMetric::In, value)
340
    }
341

            
342
    /// Same as `PixelValue::cm()`, but only accepts whole numbers,
343
    /// since using `f32` in const fn is not yet stabilized.
344
    #[inline]
345
    pub const fn const_cm(value: isize) -> Self {
346
        Self::const_from_metric(SizeMetric::Cm, value)
347
    }
348

            
349
    /// Same as `PixelValue::mm()`, but only accepts whole numbers,
350
    /// since using `f32` in const fn is not yet stabilized.
351
    #[inline]
352
    pub const fn const_mm(value: isize) -> Self {
353
        Self::const_from_metric(SizeMetric::Mm, value)
354
    }
355

            
356
    #[inline]
357
133588
    pub const fn const_from_metric(metric: SizeMetric, value: isize) -> Self {
358
133588
        Self {
359
133588
            metric,
360
133588
            number: FloatValue::const_new(value),
361
133588
        }
362
133588
    }
363

            
364
    /// Creates a PixelValue from a fractional number in const context.
365
    ///
366
    /// # Arguments
367
    /// * `metric` - The size metric (Px, Em, Pt, etc.)
368
    /// * `pre_comma` - The integer part
369
    /// * `post_comma` - The fractional part as digits
370
    #[inline]
371
    pub const fn const_from_metric_fractional(
372
        metric: SizeMetric,
373
        pre_comma: isize,
374
        post_comma: isize,
375
    ) -> Self {
376
        Self {
377
            metric,
378
            number: FloatValue::const_new_fractional(pre_comma, post_comma),
379
        }
380
    }
381

            
382
    #[inline]
383
860875
    pub fn px(value: f32) -> Self {
384
860875
        Self::from_metric(SizeMetric::Px, value)
385
860875
    }
386

            
387
    #[inline]
388
904
    pub fn em(value: f32) -> Self {
389
904
        Self::from_metric(SizeMetric::Em, value)
390
904
    }
391

            
392
    #[inline]
393
2
    pub fn inch(value: f32) -> Self {
394
2
        Self::from_metric(SizeMetric::In, value)
395
2
    }
396

            
397
    #[inline]
398
2
    pub fn cm(value: f32) -> Self {
399
2
        Self::from_metric(SizeMetric::Cm, value)
400
2
    }
401

            
402
    #[inline]
403
2
    pub fn mm(value: f32) -> Self {
404
2
        Self::from_metric(SizeMetric::Mm, value)
405
2
    }
406

            
407
    #[inline]
408
6
    pub fn pt(value: f32) -> Self {
409
6
        Self::from_metric(SizeMetric::Pt, value)
410
6
    }
411

            
412
    #[inline]
413
199
    pub fn percent(value: f32) -> Self {
414
199
        Self::from_metric(SizeMetric::Percent, value)
415
199
    }
416

            
417
    #[inline]
418
7
    pub fn rem(value: f32) -> Self {
419
7
        Self::from_metric(SizeMetric::Rem, value)
420
7
    }
421

            
422
    #[inline]
423
921751
    pub fn from_metric(metric: SizeMetric, value: f32) -> Self {
424
921751
        Self {
425
921751
            metric,
426
921751
            number: FloatValue::new(value),
427
921751
        }
428
921751
    }
429

            
430
    #[inline]
431
    pub fn interpolate(&self, other: &Self, t: f32) -> Self {
432
        if self.metric == other.metric {
433
            Self {
434
                metric: self.metric,
435
                number: self.number.interpolate(&other.number, t),
436
            }
437
        } else {
438
            // Interpolate between different metrics by converting to px
439
            // Note: Uses DEFAULT_FONT_SIZE for em/rem - acceptable for animation fallback
440
            let self_px_interp = self.to_pixels_internal(0.0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE);
441
            let other_px_interp = other.to_pixels_internal(0.0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE);
442
            Self::from_metric(
443
                SizeMetric::Px,
444
                self_px_interp + (other_px_interp - self_px_interp) * t,
445
            )
446
        }
447
    }
448

            
449
    /// Returns the value of the SizeMetric as a normalized percentage (0.0 = 0%, 1.0 = 100%)
450
    ///
451
    /// Returns `Some(NormalizedPercentage)` if this is a percentage value, `None` otherwise.
452
    /// The returned `NormalizedPercentage` is already normalized to 0.0-1.0 range,
453
    /// so you should multiply it directly with the containing block size.
454
    #[inline]
455
2345
    pub fn to_percent(&self) -> Option<NormalizedPercentage> {
456
2345
        match self.metric {
457
2345
            SizeMetric::Percent => Some(NormalizedPercentage::from_unnormalized(self.number.get())),
458
            _ => None,
459
        }
460
2345
    }
461

            
462
    /// Internal fallback method for converting to pixels with manual % resolution.
463
    ///
464
    /// Used internally by prop_cache.rs resolve_property_dependency().
465
    ///
466
    /// **DO NOT USE directly!** Use `resolve_with_context()` instead for new code.
467
    #[doc(hidden)]
468
    #[inline]
469
23686
    pub fn to_pixels_internal(&self, percent_resolve: f32, em_resolve: f32, rem_resolve: f32) -> f32 {
470
23686
        match self.metric {
471
23502
            SizeMetric::Px => self.number.get(),
472
8
            SizeMetric::Pt => self.number.get() * PT_TO_PX,
473
8
            SizeMetric::In => self.number.get() * 96.0,
474
8
            SizeMetric::Cm => self.number.get() * 96.0 / 2.54,
475
8
            SizeMetric::Mm => self.number.get() * 96.0 / 25.4,
476
72
            SizeMetric::Em => self.number.get() * em_resolve,
477
8
            SizeMetric::Rem => self.number.get() * rem_resolve,
478
            SizeMetric::Percent => {
479
40
                NormalizedPercentage::from_unnormalized(self.number.get()).resolve(percent_resolve)
480
            }
481
            // Viewport units: Cannot resolve without viewport context, return 0
482
            // These should use resolve_with_context() instead
483
32
            SizeMetric::Vw | SizeMetric::Vh | SizeMetric::Vmin | SizeMetric::Vmax => 0.0,
484
        }
485
23686
    }
486

            
487
    /// Resolve this value to pixels using proper CSS context.
488
    ///
489
    /// This is the **CORRECT** way to resolve CSS units. It properly handles:
490
    /// - em units: Uses element's own font-size (or parent's for font-size property)
491
    /// - rem units: Uses root element's font-size
492
    /// - % units: Uses property-appropriate reference (containing block width/height, element size,
493
    ///   etc.)
494
    /// - Absolute units: px, pt, in, cm, mm (already correct)
495
    ///
496
    /// # Arguments
497
    /// * `context` - Resolution context with font sizes and dimensions
498
    /// * `property_context` - Which property we're resolving for (affects % and em resolution)
499
    #[inline]
500
303853
    pub fn resolve_with_context(
501
303853
        &self,
502
303853
        context: &ResolutionContext,
503
303853
        property_context: PropertyContext,
504
303853
    ) -> f32 {
505
303853
        match self.metric {
506
            // Absolute units - already correct
507
302936
            SizeMetric::Px => self.number.get(),
508
            SizeMetric::Pt => self.number.get() * PT_TO_PX,
509
            SizeMetric::In => self.number.get() * 96.0,
510
            SizeMetric::Cm => self.number.get() * 96.0 / 2.54,
511
            SizeMetric::Mm => self.number.get() * 96.0 / 25.4,
512

            
513
            // Em units - CRITICAL: different resolution for font-size vs other properties
514
            SizeMetric::Em => {
515
724
                let reference_font_size = if property_context == PropertyContext::FontSize {
516
                    // Em on font-size refers to parent's font-size (CSS 2.1 §15.7)
517
305
                    context.parent_font_size
518
                } else {
519
                    // Em on other properties refers to element's own font-size (CSS 2.1 §10.5)
520
419
                    context.element_font_size
521
                };
522
724
                self.number.get() * reference_font_size
523
            }
524

            
525
            // Rem units - ALWAYS refer to root font-size (CSS Values 3)
526
78
            SizeMetric::Rem => self.number.get() * context.root_font_size,
527

            
528
            // Viewport units - refer to viewport dimensions (CSS Values 3 §6.2)
529
            // 1vw = 1% of viewport width, 1vh = 1% of viewport height
530
            SizeMetric::Vw => self.number.get() * context.viewport_size.width / 100.0,
531
76
            SizeMetric::Vh => self.number.get() * context.viewport_size.height / 100.0,
532
            // vmin = smaller of vw or vh
533
            SizeMetric::Vmin => {
534
                let min_dimension = context
535
                    .viewport_size
536
                    .width
537
                    .min(context.viewport_size.height);
538
                self.number.get() * min_dimension / 100.0
539
            }
540
            // vmax = larger of vw or vh
541
            SizeMetric::Vmax => {
542
                let max_dimension = context
543
                    .viewport_size
544
                    .width
545
                    .max(context.viewport_size.height);
546
                self.number.get() * max_dimension / 100.0
547
            }
548

            
549
            // Percent units - reference depends on property type
550
            SizeMetric::Percent => {
551
39
                let reference = match property_context {
552
                    // Font-size %: refers to parent's font-size (CSS 2.1 §15.7)
553
                    PropertyContext::FontSize => context.parent_font_size,
554

            
555
                    // Width and horizontal properties: containing block width (CSS 2.1 §10.3)
556
                    PropertyContext::Width => context.containing_block_size.width,
557

            
558
                    // Height and vertical properties: containing block height (CSS 2.1 §10.5)
559
                    PropertyContext::Height => context.containing_block_size.height,
560

            
561
                    // +spec:box-model:66e123 - margin/padding % resolved against inline size (= width in horizontal-tb)
562
                    // +spec:width-calculation:bef810 - margin percentages refer to containing block width (even top/bottom)
563
                    // Margins: ALWAYS containing block WIDTH, even for top/bottom! (CSS 2.1 §8.3)
564
                    // +spec:width-calculation:d78514 - margin percentages refer to width of containing block
565
                    // Padding: ALWAYS containing block WIDTH, even for top/bottom! (CSS 2.1 §8.4)
566
                    PropertyContext::Margin | PropertyContext::Padding => {
567
39
                        context.containing_block_size.width
568
                    }
569

            
570
                    // Border-width: % is NOT valid per CSS spec (CSS Backgrounds 3 §4.1)
571
                    // Return 0.0 if someone tries to use % on border-width
572
                    PropertyContext::BorderWidth => 0.0,
573

            
574
                    // Border-radius: element's own dimensions (CSS Backgrounds 3 §5.1)
575
                    // Note: More complex - horizontal % uses width, vertical % uses height
576
                    // For now, use width as default
577
                    PropertyContext::BorderRadius => {
578
                        context.element_size.map(|s| s.width).unwrap_or(0.0)
579
                    }
580

            
581
                    // Transforms: element's own dimensions (CSS Transforms §20.1)
582
                    PropertyContext::Transform => {
583
                        context.element_size.map(|s| s.width).unwrap_or(0.0)
584
                    }
585

            
586
                    // Other properties: default to containing block width
587
                    PropertyContext::Other => context.containing_block_size.width,
588
                };
589

            
590
39
                NormalizedPercentage::from_unnormalized(self.number.get()).resolve(reference)
591
            }
592
        }
593
303853
    }
594
}
595

            
596
// border-width: thin / medium / thick keyword values
597
// These are the canonical CSS definitions and should be used consistently
598
// across parsing and resolution.
599

            
600
/// border-width: thin = 1px (per CSS spec)
601
pub const THIN_BORDER_THICKNESS: PixelValue = PixelValue {
602
    metric: SizeMetric::Px,
603
    number: FloatValue { number: 1000 },
604
};
605

            
606
/// border-width: medium = 3px (per CSS spec, default)
607
pub const MEDIUM_BORDER_THICKNESS: PixelValue = PixelValue {
608
    metric: SizeMetric::Px,
609
    number: FloatValue { number: 3000 },
610
};
611

            
612
/// border-width: thick = 5px (per CSS spec)
613
pub const THICK_BORDER_THICKNESS: PixelValue = PixelValue {
614
    metric: SizeMetric::Px,
615
    number: FloatValue { number: 5000 },
616
};
617

            
618
/// Same as PixelValue, but doesn't allow a "%" sign
619
#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
620
#[repr(C)]
621
pub struct PixelValueNoPercent {
622
    pub inner: PixelValue,
623
}
624

            
625
impl PixelValueNoPercent {
626
    pub fn scale_for_dpi(&mut self, scale_factor: f32) {
627
        self.inner.scale_for_dpi(scale_factor);
628
    }
629
}
630

            
631
impl_option!(
632
    PixelValueNoPercent,
633
    OptionPixelValueNoPercent,
634
    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
635
);
636

            
637
impl_option!(
638
    PixelValue,
639
    OptionPixelValue,
640
    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
641
);
642

            
643
impl fmt::Display for PixelValueNoPercent {
644
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
645
        write!(f, "{}", self.inner)
646
    }
647
}
648

            
649
impl ::core::fmt::Debug for PixelValueNoPercent {
650
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
651
        write!(f, "{}", self)
652
    }
653
}
654

            
655
impl PixelValueNoPercent {
656
    /// Internal conversion to pixels (no percent support).
657
    ///
658
    /// Used internally by prop_cache.rs.
659
    ///
660
    /// **DO NOT USE directly!** Use `resolve_with_context()` on inner value instead.
661
    #[doc(hidden)]
662
    #[inline]
663
    pub fn to_pixels_internal(&self, em_resolve: f32, rem_resolve: f32) -> f32 {
664
        self.inner.to_pixels_internal(0.0, em_resolve, rem_resolve)
665
    }
666

            
667
    #[inline]
668
    pub const fn zero() -> Self {
669
        const ZERO_PXNP: PixelValueNoPercent = PixelValueNoPercent {
670
            inner: PixelValue::zero(),
671
        };
672
        ZERO_PXNP
673
    }
674
}
675
impl From<PixelValue> for PixelValueNoPercent {
676
    fn from(e: PixelValue) -> Self {
677
        Self { inner: e }
678
    }
679
}
680

            
681
#[derive(Clone, PartialEq)]
682
pub enum CssPixelValueParseError<'a> {
683
    EmptyString,
684
    NoValueGiven(&'a str, SizeMetric),
685
    ValueParseErr(ParseFloatError, &'a str),
686
    InvalidPixelValue(&'a str),
687
}
688

            
689
impl_debug_as_display!(CssPixelValueParseError<'a>);
690

            
691
impl_display! { CssPixelValueParseError<'a>, {
692
    EmptyString => format!("Missing [px / pt / em / %] value"),
693
    NoValueGiven(input, metric) => format!("Expected floating-point pixel value, got: \"{}{}\"", input, metric),
694
    ValueParseErr(err, number_str) => format!("Could not parse \"{}\" as floating-point value: \"{}\"", number_str, err),
695
    InvalidPixelValue(s) => format!("Invalid pixel value: \"{}\"", s),
696
}}
697

            
698
/// Wrapper for NoValueGiven error in pixel value parsing.
699
#[derive(Debug, Clone, PartialEq)]
700
#[repr(C)]
701
pub struct PixelNoValueGivenError {
702
    pub value: AzString,
703
    pub metric: SizeMetric,
704
}
705

            
706
/// Owned version of CssPixelValueParseError.
707
#[derive(Debug, Clone, PartialEq)]
708
#[repr(C, u8)]
709
pub enum CssPixelValueParseErrorOwned {
710
    EmptyString,
711
    NoValueGiven(PixelNoValueGivenError),
712
    ValueParseErr(ParseFloatErrorWithInput),
713
    InvalidPixelValue(AzString),
714
}
715

            
716
impl<'a> CssPixelValueParseError<'a> {
717
    pub fn to_contained(&self) -> CssPixelValueParseErrorOwned {
718
        match self {
719
            CssPixelValueParseError::EmptyString => CssPixelValueParseErrorOwned::EmptyString,
720
            CssPixelValueParseError::NoValueGiven(s, metric) => {
721
                CssPixelValueParseErrorOwned::NoValueGiven(PixelNoValueGivenError { value: s.to_string().into(), metric: *metric })
722
            }
723
            CssPixelValueParseError::ValueParseErr(err, s) => {
724
                CssPixelValueParseErrorOwned::ValueParseErr(ParseFloatErrorWithInput { error: err.clone().into(), input: s.to_string().into() })
725
            }
726
            CssPixelValueParseError::InvalidPixelValue(s) => {
727
                CssPixelValueParseErrorOwned::InvalidPixelValue(s.to_string().into())
728
            }
729
        }
730
    }
731
}
732

            
733
impl CssPixelValueParseErrorOwned {
734
    pub fn to_shared<'a>(&'a self) -> CssPixelValueParseError<'a> {
735
        match self {
736
            CssPixelValueParseErrorOwned::EmptyString => CssPixelValueParseError::EmptyString,
737
            CssPixelValueParseErrorOwned::NoValueGiven(e) => {
738
                CssPixelValueParseError::NoValueGiven(e.value.as_str(), e.metric)
739
            }
740
            CssPixelValueParseErrorOwned::ValueParseErr(e) => {
741
                CssPixelValueParseError::ValueParseErr(e.error.to_std(), e.input.as_str())
742
            }
743
            CssPixelValueParseErrorOwned::InvalidPixelValue(s) => {
744
                CssPixelValueParseError::InvalidPixelValue(s.as_str())
745
            }
746
        }
747
    }
748
}
749

            
750
/// parses an angle value like `30deg`, `1.64rad`, `100%`, etc.
751
25066
fn parse_pixel_value_inner<'a>(
752
25066
    input: &'a str,
753
25066
    match_values: &[(&'static str, SizeMetric)],
754
25066
) -> Result<PixelValue, CssPixelValueParseError<'a>> {
755
25066
    let input = input.trim();
756

            
757
25066
    if input.is_empty() {
758
2
        return Err(CssPixelValueParseError::EmptyString);
759
25064
    }
760

            
761
115987
    for (match_val, metric) in match_values {
762
108890
        if input.ends_with(match_val) {
763
17967
            let value = &input[..input.len() - match_val.len()];
764
17967
            let value = value.trim();
765
17967
            if value.is_empty() {
766
2
                return Err(CssPixelValueParseError::NoValueGiven(input, *metric));
767
17965
            }
768
17965
            match value.parse::<f32>() {
769
17964
                Ok(o) => {
770
17964
                    return Ok(PixelValue::from_metric(*metric, o));
771
                }
772
1
                Err(e) => {
773
1
                    return Err(CssPixelValueParseError::ValueParseErr(e, value));
774
                }
775
            }
776
90923
        }
777
    }
778

            
779
7097
    match input.trim().parse::<f32>() {
780
7013
        Ok(o) => Ok(PixelValue::px(o)),
781
84
        Err(_) => Err(CssPixelValueParseError::InvalidPixelValue(input)),
782
    }
783
25066
}
784

            
785
24913
pub fn parse_pixel_value<'a>(input: &'a str) -> Result<PixelValue, CssPixelValueParseError<'a>> {
786
24913
    parse_pixel_value_inner(
787
24913
        input,
788
24913
        &[
789
24913
            ("px", SizeMetric::Px),
790
24913
            ("rem", SizeMetric::Rem), // Must be before "em" to match correctly
791
24913
            ("em", SizeMetric::Em),
792
24913
            ("pt", SizeMetric::Pt),
793
24913
            ("in", SizeMetric::In),
794
24913
            ("mm", SizeMetric::Mm),
795
24913
            ("cm", SizeMetric::Cm),
796
24913
            ("vmax", SizeMetric::Vmax), // Must be before "vw" to match correctly
797
24913
            ("vmin", SizeMetric::Vmin), // Must be before "vw" to match correctly
798
24913
            ("vw", SizeMetric::Vw),
799
24913
            ("vh", SizeMetric::Vh),
800
24913
            ("%", SizeMetric::Percent),
801
24913
        ],
802
    )
803
24913
}
804

            
805
153
pub fn parse_pixel_value_no_percent<'a>(
806
153
    input: &'a str,
807
153
) -> Result<PixelValueNoPercent, CssPixelValueParseError<'a>> {
808
    Ok(PixelValueNoPercent {
809
153
        inner: parse_pixel_value_inner(
810
153
            input,
811
153
            &[
812
153
                ("px", SizeMetric::Px),
813
153
                ("rem", SizeMetric::Rem), // Must be before "em" to match correctly
814
153
                ("em", SizeMetric::Em),
815
153
                ("pt", SizeMetric::Pt),
816
153
                ("in", SizeMetric::In),
817
153
                ("mm", SizeMetric::Mm),
818
153
                ("cm", SizeMetric::Cm),
819
153
                ("vmax", SizeMetric::Vmax), // Must be before "vw" to match correctly
820
153
                ("vmin", SizeMetric::Vmin), // Must be before "vw" to match correctly
821
153
                ("vw", SizeMetric::Vw),
822
153
                ("vh", SizeMetric::Vh),
823
153
            ],
824
3
        )?,
825
    })
826
153
}
827

            
828
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
829
pub enum PixelValueWithAuto {
830
    None,
831
    Initial,
832
    Inherit,
833
    Auto,
834
    Exact(PixelValue),
835
}
836

            
837
/// Parses a pixel value, but also tries values like "auto", "initial", "inherit" and "none"
838
8899
pub fn parse_pixel_value_with_auto<'a>(
839
8899
    input: &'a str,
840
8899
) -> Result<PixelValueWithAuto, CssPixelValueParseError<'a>> {
841
8899
    let input = input.trim();
842
8899
    match input {
843
8899
        "none" => Ok(PixelValueWithAuto::None),
844
8898
        "initial" => Ok(PixelValueWithAuto::Initial),
845
8897
        "inherit" => Ok(PixelValueWithAuto::Inherit),
846
8896
        "auto" => Ok(PixelValueWithAuto::Auto),
847
8851
        e => Ok(PixelValueWithAuto::Exact(parse_pixel_value(e)?)),
848
    }
849
8899
}
850

            
851
// ============================================================================
852
// System Metric References (system:button-padding, system:button-radius, etc.)
853
// ============================================================================
854

            
855
/// Reference to a specific system metric value.
856
/// These are resolved at runtime based on the user's system preferences.
857
/// 
858
/// CSS syntax: `system:button-padding`, `system:button-radius`, `system:titlebar-height`, etc.
859
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
860
#[repr(C)]
861
#[derive(Default)]
862
pub enum SystemMetricRef {
863
    /// Button corner radius (system:button-radius)
864
    #[default]
865
    ButtonRadius,
866
    /// Button horizontal padding (system:button-padding-horizontal)
867
    ButtonPaddingHorizontal,
868
    /// Button vertical padding (system:button-padding-vertical)
869
    ButtonPaddingVertical,
870
    /// Button border width (system:button-border-width)
871
    ButtonBorderWidth,
872
    /// Titlebar height (system:titlebar-height)
873
    TitlebarHeight,
874
    /// Titlebar button area width (system:titlebar-button-width)
875
    TitlebarButtonWidth,
876
    /// Titlebar horizontal padding (system:titlebar-padding)
877
    TitlebarPadding,
878
    /// Safe area top inset for notched devices (system:safe-area-top)
879
    SafeAreaTop,
880
    /// Safe area bottom inset (system:safe-area-bottom)
881
    SafeAreaBottom,
882
    /// Safe area left inset (system:safe-area-left)
883
    SafeAreaLeft,
884
    /// Safe area right inset (system:safe-area-right)
885
    SafeAreaRight,
886
}
887

            
888

            
889
impl SystemMetricRef {
890
    /// Resolve this system metric reference against actual system metrics.
891
    pub fn resolve(&self, metrics: &crate::system::SystemMetrics) -> Option<PixelValue> {
892
        match self {
893
            SystemMetricRef::ButtonRadius => metrics.corner_radius.as_option().copied(),
894
            SystemMetricRef::ButtonPaddingHorizontal => metrics.button_padding_horizontal.as_option().copied(),
895
            SystemMetricRef::ButtonPaddingVertical => metrics.button_padding_vertical.as_option().copied(),
896
            SystemMetricRef::ButtonBorderWidth => metrics.border_width.as_option().copied(),
897
            SystemMetricRef::TitlebarHeight => metrics.titlebar.height.as_option().copied(),
898
            SystemMetricRef::TitlebarButtonWidth => metrics.titlebar.button_area_width.as_option().copied(),
899
            SystemMetricRef::TitlebarPadding => metrics.titlebar.padding_horizontal.as_option().copied(),
900
            SystemMetricRef::SafeAreaTop => metrics.titlebar.safe_area.top.as_option().copied(),
901
            SystemMetricRef::SafeAreaBottom => metrics.titlebar.safe_area.bottom.as_option().copied(),
902
            SystemMetricRef::SafeAreaLeft => metrics.titlebar.safe_area.left.as_option().copied(),
903
            SystemMetricRef::SafeAreaRight => metrics.titlebar.safe_area.right.as_option().copied(),
904
        }
905
    }
906

            
907
    /// Returns the CSS string representation of this system metric reference.
908
    pub fn as_css_str(&self) -> &'static str {
909
        match self {
910
            SystemMetricRef::ButtonRadius => "system:button-radius",
911
            SystemMetricRef::ButtonPaddingHorizontal => "system:button-padding-horizontal",
912
            SystemMetricRef::ButtonPaddingVertical => "system:button-padding-vertical",
913
            SystemMetricRef::ButtonBorderWidth => "system:button-border-width",
914
            SystemMetricRef::TitlebarHeight => "system:titlebar-height",
915
            SystemMetricRef::TitlebarButtonWidth => "system:titlebar-button-width",
916
            SystemMetricRef::TitlebarPadding => "system:titlebar-padding",
917
            SystemMetricRef::SafeAreaTop => "system:safe-area-top",
918
            SystemMetricRef::SafeAreaBottom => "system:safe-area-bottom",
919
            SystemMetricRef::SafeAreaLeft => "system:safe-area-left",
920
            SystemMetricRef::SafeAreaRight => "system:safe-area-right",
921
        }
922
    }
923

            
924
    /// Parse a system metric reference from a CSS string (without the "system:" prefix).
925
    pub fn from_css_str(s: &str) -> Option<Self> {
926
        match s {
927
            "button-radius" => Some(SystemMetricRef::ButtonRadius),
928
            "button-padding-horizontal" => Some(SystemMetricRef::ButtonPaddingHorizontal),
929
            "button-padding-vertical" => Some(SystemMetricRef::ButtonPaddingVertical),
930
            "button-border-width" => Some(SystemMetricRef::ButtonBorderWidth),
931
            "titlebar-height" => Some(SystemMetricRef::TitlebarHeight),
932
            "titlebar-button-width" => Some(SystemMetricRef::TitlebarButtonWidth),
933
            "titlebar-padding" => Some(SystemMetricRef::TitlebarPadding),
934
            "safe-area-top" => Some(SystemMetricRef::SafeAreaTop),
935
            "safe-area-bottom" => Some(SystemMetricRef::SafeAreaBottom),
936
            "safe-area-left" => Some(SystemMetricRef::SafeAreaLeft),
937
            "safe-area-right" => Some(SystemMetricRef::SafeAreaRight),
938
            _ => None,
939
        }
940
    }
941
}
942

            
943
impl fmt::Display for SystemMetricRef {
944
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
945
        write!(f, "{}", self.as_css_str())
946
    }
947
}
948

            
949
impl FormatAsCssValue for SystemMetricRef {
950
    fn format_as_css_value(&self, f: &mut fmt::Formatter) -> fmt::Result {
951
        write!(f, "{}", self.as_css_str())
952
    }
953
}
954

            
955
/// A pixel value reference that can be either a concrete value or a system metric.
956
/// System metrics are lazily evaluated at runtime based on the user's system theme.
957
/// 
958
/// CSS syntax: `10px`, `1.5em`, `system:button-padding`, etc.
959
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
960
#[repr(C, u8)]
961
pub enum PixelValueOrSystem {
962
    /// A concrete pixel value.
963
    Value(PixelValue),
964
    /// A reference to a system metric, resolved at runtime.
965
    System(SystemMetricRef),
966
}
967

            
968
impl Default for PixelValueOrSystem {
969
    fn default() -> Self {
970
        PixelValueOrSystem::Value(PixelValue::zero())
971
    }
972
}
973

            
974
impl From<PixelValue> for PixelValueOrSystem {
975
    fn from(value: PixelValue) -> Self {
976
        PixelValueOrSystem::Value(value)
977
    }
978
}
979

            
980
impl PixelValueOrSystem {
981
    /// Create a new PixelValueOrSystem from a concrete value.
982
    pub const fn value(v: PixelValue) -> Self {
983
        PixelValueOrSystem::Value(v)
984
    }
985
    
986
    /// Create a new PixelValueOrSystem from a system metric reference.
987
    pub const fn system(s: SystemMetricRef) -> Self {
988
        PixelValueOrSystem::System(s)
989
    }
990
    
991
    /// Resolve the pixel value against a SystemMetrics struct.
992
    /// Returns the system metric if available, or falls back to the provided default.
993
    pub fn resolve(&self, system_metrics: &crate::system::SystemMetrics, fallback: PixelValue) -> PixelValue {
994
        match self {
995
            PixelValueOrSystem::Value(v) => *v,
996
            PixelValueOrSystem::System(ref_type) => ref_type.resolve(system_metrics).unwrap_or(fallback),
997
        }
998
    }
999
    
}
impl fmt::Display for PixelValueOrSystem {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            PixelValueOrSystem::Value(v) => write!(f, "{}", v),
            PixelValueOrSystem::System(s) => write!(f, "{}", s),
        }
    }
}
impl FormatAsCssValue for PixelValueOrSystem {
    fn format_as_css_value(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            PixelValueOrSystem::Value(v) => v.format_as_css_value(f),
            PixelValueOrSystem::System(s) => s.format_as_css_value(f),
        }
    }
}
/// Parse a pixel value that may include system metric references.
/// 
/// Accepts: `10px`, `1.5em`, `system:button-padding`, etc.
#[cfg(feature = "parser")]
pub fn parse_pixel_value_or_system<'a>(
    input: &'a str,
) -> Result<PixelValueOrSystem, CssPixelValueParseError<'a>> {
    let input = input.trim();
    // Check for system metric reference
    if let Some(metric_name) = input.strip_prefix("system:") {
        if let Some(metric_ref) = SystemMetricRef::from_css_str(metric_name) {
            return Ok(PixelValueOrSystem::System(metric_ref));
        } else {
            return Err(CssPixelValueParseError::InvalidPixelValue(input));
        }
    }
    // Parse as regular pixel value
    Ok(PixelValueOrSystem::Value(parse_pixel_value(input)?))
}
#[cfg(all(test, feature = "parser"))]
mod tests {
    use super::*;
    #[test]
1
    fn test_parse_pixel_value() {
1
        assert_eq!(parse_pixel_value("10px").unwrap(), PixelValue::px(10.0));
1
        assert_eq!(parse_pixel_value("1.5em").unwrap(), PixelValue::em(1.5));
1
        assert_eq!(parse_pixel_value("2rem").unwrap(), PixelValue::rem(2.0));
1
        assert_eq!(parse_pixel_value("-20pt").unwrap(), PixelValue::pt(-20.0));
1
        assert_eq!(parse_pixel_value("50%").unwrap(), PixelValue::percent(50.0));
1
        assert_eq!(parse_pixel_value("1in").unwrap(), PixelValue::inch(1.0));
1
        assert_eq!(parse_pixel_value("2.54cm").unwrap(), PixelValue::cm(2.54));
1
        assert_eq!(parse_pixel_value("10mm").unwrap(), PixelValue::mm(10.0));
1
        assert_eq!(parse_pixel_value("  0  ").unwrap(), PixelValue::px(0.0));
1
    }
    #[test]
1
    fn test_resolve_with_context_em() {
        // Element has font-size: 32px, margin: 0.67em
1
        let context = ResolutionContext {
1
            element_font_size: 32.0,
1
            parent_font_size: 16.0,
1
            ..Default::default()
1
        };
        // Margin em uses element's own font-size
1
        let margin = PixelValue::em(0.67);
1
        assert!(
1
            (margin.resolve_with_context(&context, PropertyContext::Margin) - 21.44).abs() < 0.01
        );
        // Font-size em uses parent's font-size
1
        let font_size = PixelValue::em(2.0);
1
        assert_eq!(
1
            font_size.resolve_with_context(&context, PropertyContext::FontSize),
            32.0
        );
1
    }
    #[test]
1
    fn test_resolve_with_context_rem() {
        // Root has font-size: 18px
1
        let context = ResolutionContext {
1
            element_font_size: 32.0,
1
            parent_font_size: 16.0,
1
            root_font_size: 18.0,
1
            ..Default::default()
1
        };
        // Rem always uses root font-size, regardless of property
1
        let margin = PixelValue::rem(2.0);
1
        assert_eq!(
1
            margin.resolve_with_context(&context, PropertyContext::Margin),
            36.0
        );
1
        let font_size = PixelValue::rem(1.5);
1
        assert_eq!(
1
            font_size.resolve_with_context(&context, PropertyContext::FontSize),
            27.0
        );
1
    }
    #[test]
1
    fn test_resolve_with_context_percent_margin() {
        // Margin % uses containing block WIDTH (even for top/bottom!)
1
        let context = ResolutionContext {
1
            element_font_size: 16.0,
1
            parent_font_size: 16.0,
1
            root_font_size: 16.0,
1
            containing_block_size: PhysicalSize::new(800.0, 600.0),
1
            element_size: None,
1
            viewport_size: PhysicalSize::new(1920.0, 1080.0),
1
        };
1
        let margin = PixelValue::percent(10.0); // 10%
1
        assert_eq!(
1
            margin.resolve_with_context(&context, PropertyContext::Margin),
            80.0
        ); // 10% of 800
1
    }
    #[test]
1
    fn test_parse_pixel_value_no_percent() {
1
        assert_eq!(
1
            parse_pixel_value_no_percent("10px").unwrap().inner,
1
            PixelValue::px(10.0)
        );
1
        assert!(parse_pixel_value_no_percent("50%").is_err());
1
    }
    #[test]
1
    fn test_parse_pixel_value_with_auto() {
1
        assert_eq!(
1
            parse_pixel_value_with_auto("10px").unwrap(),
1
            PixelValueWithAuto::Exact(PixelValue::px(10.0))
        );
1
        assert_eq!(
1
            parse_pixel_value_with_auto("auto").unwrap(),
            PixelValueWithAuto::Auto
        );
1
        assert_eq!(
1
            parse_pixel_value_with_auto("initial").unwrap(),
            PixelValueWithAuto::Initial
        );
1
        assert_eq!(
1
            parse_pixel_value_with_auto("inherit").unwrap(),
            PixelValueWithAuto::Inherit
        );
1
        assert_eq!(
1
            parse_pixel_value_with_auto("none").unwrap(),
            PixelValueWithAuto::None
        );
1
    }
    #[test]
1
    fn test_parse_pixel_value_errors() {
1
        assert!(parse_pixel_value("").is_err());
        // Modern CSS parsers can be liberal - unitless numbers treated as px
1
        assert!(parse_pixel_value("10").is_ok()); // Parsed as 10px
                                                  // This parser is liberal and trims whitespace, so "10 px" is accepted
1
        assert!(parse_pixel_value("10 px").is_ok()); // Liberal parsing accepts this
1
        assert!(parse_pixel_value("px").is_err());
1
        assert!(parse_pixel_value("ten-px").is_err());
1
    }
}