Skip to main content

fop_core/properties/
property_value.rs

1//! Property value types
2
3use fop_types::{Color, EvalContext, Expression, Gradient, Length, Percentage};
4use std::borrow::Cow;
5
6/// Relative font-size values (keywords)
7///
8/// Based on CSS Fonts Module Level 3 specification section 3.5
9#[derive(Debug, Clone, Copy, PartialEq)]
10pub enum RelativeFontSize {
11    /// larger - multiply parent font size by 1.2
12    Larger,
13    /// smaller - divide parent font size by 1.2
14    Smaller,
15    /// xx-small - absolute keyword (9pt)
16    XxSmall,
17    /// x-small - absolute keyword (10pt)
18    XSmall,
19    /// small - absolute keyword (13pt)
20    Small,
21    /// medium - absolute keyword (16pt, default)
22    Medium,
23    /// large - absolute keyword (18pt)
24    Large,
25    /// x-large - absolute keyword (24pt)
26    XLarge,
27    /// xx-large - absolute keyword (32pt)
28    XxLarge,
29}
30
31impl RelativeFontSize {
32    /// Parse a string into a RelativeFontSize
33    pub fn parse(s: &str) -> Option<Self> {
34        match s {
35            "larger" => Some(RelativeFontSize::Larger),
36            "smaller" => Some(RelativeFontSize::Smaller),
37            "xx-small" => Some(RelativeFontSize::XxSmall),
38            "x-small" => Some(RelativeFontSize::XSmall),
39            "small" => Some(RelativeFontSize::Small),
40            "medium" => Some(RelativeFontSize::Medium),
41            "large" => Some(RelativeFontSize::Large),
42            "x-large" => Some(RelativeFontSize::XLarge),
43            "xx-large" => Some(RelativeFontSize::XxLarge),
44            _ => None,
45        }
46    }
47
48    /// Resolve to an absolute font size
49    ///
50    /// For larger/smaller, requires parent font size
51    /// For absolute keywords, ignores parent and returns fixed size
52    pub fn resolve(&self, parent_size: Length) -> Length {
53        match self {
54            RelativeFontSize::Larger => {
55                // Multiply by 1.2
56                Length::from_millipoints((parent_size.millipoints() as f64 * 1.2) as i32)
57            }
58            RelativeFontSize::Smaller => {
59                // Divide by 1.2
60                Length::from_millipoints((parent_size.millipoints() as f64 / 1.2) as i32)
61            }
62            // Absolute size keywords
63            RelativeFontSize::XxSmall => Length::from_pt(9.0),
64            RelativeFontSize::XSmall => Length::from_pt(10.0),
65            RelativeFontSize::Small => Length::from_pt(13.0),
66            RelativeFontSize::Medium => Length::from_pt(16.0),
67            RelativeFontSize::Large => Length::from_pt(18.0),
68            RelativeFontSize::XLarge => Length::from_pt(24.0),
69            RelativeFontSize::XxLarge => Length::from_pt(32.0),
70        }
71    }
72
73    /// Check if this is a relative size (larger/smaller)
74    pub fn is_relative(&self) -> bool {
75        matches!(self, RelativeFontSize::Larger | RelativeFontSize::Smaller)
76    }
77
78    /// Check if this is an absolute keyword size
79    pub fn is_absolute_keyword(&self) -> bool {
80        !self.is_relative()
81    }
82}
83
84/// A property value that can be stored in a PropertyList
85#[derive(Debug, Clone, PartialEq)]
86pub enum PropertyValue {
87    /// A length value (e.g., "10pt", "2cm")
88    Length(Length),
89
90    /// A color value (e.g., "#FF0000", "red")
91    Color(Color),
92
93    /// A gradient value (e.g., "linear-gradient(red, blue)")
94    Gradient(Gradient),
95
96    /// An enumerated value (stored as u16 for compactness)
97    Enum(u16),
98
99    /// A string value (borrowed or owned)
100    String(Cow<'static, str>),
101
102    /// A percentage value (e.g., 50%, 100%)
103    Percentage(Percentage),
104
105    /// An integer value
106    Integer(i32),
107
108    /// A floating-point value
109    Number(f64),
110
111    /// A boolean value
112    Boolean(bool),
113
114    /// A list of values
115    List(Vec<PropertyValue>),
116
117    /// A space-separated pair of values
118    Pair(Box<PropertyValue>, Box<PropertyValue>),
119
120    /// A relative font-size value (larger, smaller, or size keywords)
121    RelativeFontSize(RelativeFontSize),
122
123    /// A CSS calc() expression
124    Expression(Expression),
125
126    /// The special value "auto"
127    Auto,
128
129    /// The special value "none"
130    None,
131
132    /// The special value "inherit"
133    Inherit,
134}
135
136impl PropertyValue {
137    /// Check if this is an auto value
138    #[inline]
139    pub fn is_auto(&self) -> bool {
140        matches!(self, PropertyValue::Auto)
141    }
142
143    /// Check if this is a none value
144    #[inline]
145    pub fn is_none(&self) -> bool {
146        matches!(self, PropertyValue::None)
147    }
148
149    /// Check if this is an inherit value
150    #[inline]
151    pub fn is_inherit(&self) -> bool {
152        matches!(self, PropertyValue::Inherit)
153    }
154
155    /// Try to get a length value
156    pub fn as_length(&self) -> Option<Length> {
157        match self {
158            PropertyValue::Length(len) => Some(*len),
159            _ => None,
160        }
161    }
162
163    /// Try to get a relative font size value
164    pub fn as_relative_font_size(&self) -> Option<RelativeFontSize> {
165        match self {
166            PropertyValue::RelativeFontSize(rfs) => Some(*rfs),
167            _ => None,
168        }
169    }
170
171    /// Resolve a font size value to an absolute length
172    ///
173    /// If the value is a relative font size (larger/smaller/keywords),
174    /// it will be resolved using the parent font size.
175    pub fn resolve_font_size(&self, parent_size: Length) -> Option<Length> {
176        match self {
177            PropertyValue::Length(len) => Some(*len),
178            PropertyValue::RelativeFontSize(rfs) => Some(rfs.resolve(parent_size)),
179            _ => None,
180        }
181    }
182
183    /// Try to get a color value
184    pub fn as_color(&self) -> Option<Color> {
185        match self {
186            PropertyValue::Color(color) => Some(*color),
187            _ => None,
188        }
189    }
190
191    /// Try to get a gradient value
192    pub fn as_gradient(&self) -> Option<&Gradient> {
193        match self {
194            PropertyValue::Gradient(gradient) => Some(gradient),
195            _ => None,
196        }
197    }
198
199    /// Try to get an enum value
200    pub fn as_enum(&self) -> Option<u16> {
201        match self {
202            PropertyValue::Enum(e) => Some(*e),
203            _ => None,
204        }
205    }
206
207    /// Try to get a string value
208    pub fn as_string(&self) -> Option<&str> {
209        match self {
210            PropertyValue::String(s) => Some(s.as_ref()),
211            _ => None,
212        }
213    }
214
215    /// Try to get an integer value
216    pub fn as_integer(&self) -> Option<i32> {
217        match self {
218            PropertyValue::Integer(i) => Some(*i),
219            _ => None,
220        }
221    }
222
223    /// Try to get a number value
224    pub fn as_number(&self) -> Option<f64> {
225        match self {
226            PropertyValue::Number(n) => Some(*n),
227            _ => None,
228        }
229    }
230
231    /// Try to get a boolean value
232    pub fn as_boolean(&self) -> Option<bool> {
233        match self {
234            PropertyValue::Boolean(b) => Some(*b),
235            _ => None,
236        }
237    }
238
239    /// Try to get a percentage value
240    pub fn as_percentage(&self) -> Option<Percentage> {
241        match self {
242            PropertyValue::Percentage(p) => Some(*p),
243            _ => None,
244        }
245    }
246
247    /// Resolve a value to a length, applying percentage if needed
248    pub fn resolve_length(&self, base: Length) -> Option<Length> {
249        match self {
250            PropertyValue::Length(len) => Some(*len),
251            PropertyValue::Percentage(pct) => Some(pct.of(base)),
252            _ => None,
253        }
254    }
255
256    /// Try to get an expression value
257    pub fn as_expression(&self) -> Option<&Expression> {
258        match self {
259            PropertyValue::Expression(expr) => Some(expr),
260            _ => None,
261        }
262    }
263
264    /// Resolve a value to a length, evaluating expressions if needed
265    pub fn resolve_with_context(&self, context: &EvalContext) -> Option<Length> {
266        match self {
267            PropertyValue::Length(len) => Some(*len),
268            PropertyValue::Percentage(pct) => context.base_width.map(|base| pct.of(base)),
269            PropertyValue::Expression(expr) => expr.evaluate(context).ok(),
270            _ => None,
271        }
272    }
273}
274
275#[cfg(test)]
276mod tests {
277    use super::*;
278
279    #[test]
280    fn test_length_value() {
281        let val = PropertyValue::Length(Length::from_pt(12.0));
282        assert!(val.as_length().is_some());
283        assert_eq!(
284            val.as_length().expect("test: should succeed"),
285            Length::from_pt(12.0)
286        );
287    }
288
289    #[test]
290    fn test_color_value() {
291        let val = PropertyValue::Color(Color::RED);
292        assert!(val.as_color().is_some());
293        assert_eq!(val.as_color().expect("test: should succeed"), Color::RED);
294    }
295
296    #[test]
297    fn test_enum_value() {
298        let val = PropertyValue::Enum(42);
299        assert_eq!(val.as_enum(), Some(42));
300    }
301
302    #[test]
303    fn test_string_value() {
304        let val = PropertyValue::String(Cow::Borrowed("test"));
305        assert_eq!(val.as_string(), Some("test"));
306    }
307
308    #[test]
309    fn test_special_values() {
310        assert!(PropertyValue::Auto.is_auto());
311        assert!(PropertyValue::None.is_none());
312        assert!(PropertyValue::Inherit.is_inherit());
313    }
314
315    #[test]
316    fn test_pair_value() {
317        let val = PropertyValue::Pair(
318            Box::new(PropertyValue::Length(Length::from_pt(10.0))),
319            Box::new(PropertyValue::Length(Length::from_pt(20.0))),
320        );
321
322        match val {
323            PropertyValue::Pair(first, second) => {
324                assert_eq!(first.as_length(), Some(Length::from_pt(10.0)));
325                assert_eq!(second.as_length(), Some(Length::from_pt(20.0)));
326            }
327            _ => panic!("Expected Pair"),
328        }
329    }
330
331    #[test]
332    fn test_relative_font_size_parsing() {
333        assert_eq!(
334            RelativeFontSize::parse("larger"),
335            Some(RelativeFontSize::Larger)
336        );
337        assert_eq!(
338            RelativeFontSize::parse("smaller"),
339            Some(RelativeFontSize::Smaller)
340        );
341        assert_eq!(
342            RelativeFontSize::parse("xx-small"),
343            Some(RelativeFontSize::XxSmall)
344        );
345        assert_eq!(
346            RelativeFontSize::parse("x-small"),
347            Some(RelativeFontSize::XSmall)
348        );
349        assert_eq!(
350            RelativeFontSize::parse("small"),
351            Some(RelativeFontSize::Small)
352        );
353        assert_eq!(
354            RelativeFontSize::parse("medium"),
355            Some(RelativeFontSize::Medium)
356        );
357        assert_eq!(
358            RelativeFontSize::parse("large"),
359            Some(RelativeFontSize::Large)
360        );
361        assert_eq!(
362            RelativeFontSize::parse("x-large"),
363            Some(RelativeFontSize::XLarge)
364        );
365        assert_eq!(
366            RelativeFontSize::parse("xx-large"),
367            Some(RelativeFontSize::XxLarge)
368        );
369        assert_eq!(RelativeFontSize::parse("invalid"), None);
370    }
371
372    #[test]
373    fn test_relative_font_size_larger() {
374        let parent = Length::from_pt(12.0);
375        let larger = RelativeFontSize::Larger;
376        let result = larger.resolve(parent);
377        // 12 * 1.2 = 14.4
378        assert_eq!(result, Length::from_pt(14.4));
379    }
380
381    #[test]
382    fn test_relative_font_size_smaller() {
383        let parent = Length::from_pt(12.0);
384        let smaller = RelativeFontSize::Smaller;
385        let result = smaller.resolve(parent);
386        // 12 / 1.2 = 10
387        assert_eq!(result, Length::from_pt(10.0));
388    }
389
390    #[test]
391    fn test_relative_font_size_absolute_keywords() {
392        assert_eq!(
393            RelativeFontSize::XxSmall.resolve(Length::from_pt(100.0)),
394            Length::from_pt(9.0)
395        );
396        assert_eq!(
397            RelativeFontSize::XSmall.resolve(Length::from_pt(100.0)),
398            Length::from_pt(10.0)
399        );
400        assert_eq!(
401            RelativeFontSize::Small.resolve(Length::from_pt(100.0)),
402            Length::from_pt(13.0)
403        );
404        assert_eq!(
405            RelativeFontSize::Medium.resolve(Length::from_pt(100.0)),
406            Length::from_pt(16.0)
407        );
408        assert_eq!(
409            RelativeFontSize::Large.resolve(Length::from_pt(100.0)),
410            Length::from_pt(18.0)
411        );
412        assert_eq!(
413            RelativeFontSize::XLarge.resolve(Length::from_pt(100.0)),
414            Length::from_pt(24.0)
415        );
416        assert_eq!(
417            RelativeFontSize::XxLarge.resolve(Length::from_pt(100.0)),
418            Length::from_pt(32.0)
419        );
420    }
421
422    #[test]
423    fn test_relative_font_size_checks() {
424        assert!(RelativeFontSize::Larger.is_relative());
425        assert!(RelativeFontSize::Smaller.is_relative());
426        assert!(!RelativeFontSize::Medium.is_relative());
427        assert!(RelativeFontSize::Medium.is_absolute_keyword());
428        assert!(RelativeFontSize::Large.is_absolute_keyword());
429        assert!(!RelativeFontSize::Larger.is_absolute_keyword());
430    }
431
432    #[test]
433    fn test_property_value_relative_font_size() {
434        let val = PropertyValue::RelativeFontSize(RelativeFontSize::Larger);
435        assert!(val.as_relative_font_size().is_some());
436        assert_eq!(val.as_relative_font_size(), Some(RelativeFontSize::Larger));
437    }
438
439    #[test]
440    fn test_resolve_font_size_with_length() {
441        let val = PropertyValue::Length(Length::from_pt(14.0));
442        let parent = Length::from_pt(12.0);
443        assert_eq!(val.resolve_font_size(parent), Some(Length::from_pt(14.0)));
444    }
445
446    #[test]
447    fn test_resolve_font_size_with_larger() {
448        let val = PropertyValue::RelativeFontSize(RelativeFontSize::Larger);
449        let parent = Length::from_pt(12.0);
450        // 12 * 1.2 = 14.4
451        assert_eq!(val.resolve_font_size(parent), Some(Length::from_pt(14.4)));
452    }
453
454    #[test]
455    fn test_resolve_font_size_with_smaller() {
456        let val = PropertyValue::RelativeFontSize(RelativeFontSize::Smaller);
457        let parent = Length::from_pt(12.0);
458        // 12 / 1.2 = 10
459        assert_eq!(val.resolve_font_size(parent), Some(Length::from_pt(10.0)));
460    }
461
462    #[test]
463    fn test_resolve_font_size_with_medium_keyword() {
464        let val = PropertyValue::RelativeFontSize(RelativeFontSize::Medium);
465        let parent = Length::from_pt(12.0);
466        // medium is always 16pt regardless of parent
467        assert_eq!(val.resolve_font_size(parent), Some(Length::from_pt(16.0)));
468    }
469
470    #[test]
471    fn test_resolve_font_size_with_invalid_type() {
472        let val = PropertyValue::String(Cow::Borrowed("test"));
473        let parent = Length::from_pt(12.0);
474        assert_eq!(val.resolve_font_size(parent), None);
475    }
476
477    #[test]
478    fn test_expression_value() {
479        let expr = Expression::parse("calc(100% - 20pt)").expect("test: should succeed");
480        let val = PropertyValue::Expression(expr);
481        assert!(val.as_expression().is_some());
482    }
483
484    #[test]
485    fn test_resolve_with_context_expression() {
486        let expr = Expression::parse("calc(100% - 20pt)").expect("test: should succeed");
487        let val = PropertyValue::Expression(expr);
488        let ctx = EvalContext::with_width(Length::from_pt(200.0), Length::from_pt(12.0));
489        let result = val
490            .resolve_with_context(&ctx)
491            .expect("test: should succeed");
492        assert_eq!(result, Length::from_pt(180.0));
493    }
494
495    #[test]
496    fn test_resolve_with_context_length() {
497        let val = PropertyValue::Length(Length::from_pt(50.0));
498        let ctx = EvalContext::with_width(Length::from_pt(200.0), Length::from_pt(12.0));
499        let result = val
500            .resolve_with_context(&ctx)
501            .expect("test: should succeed");
502        assert_eq!(result, Length::from_pt(50.0));
503    }
504
505    #[test]
506    fn test_resolve_with_context_percentage() {
507        let val = PropertyValue::Percentage(Percentage::from_percent(50.0));
508        let ctx = EvalContext::with_width(Length::from_pt(200.0), Length::from_pt(12.0));
509        let result = val
510            .resolve_with_context(&ctx)
511            .expect("test: should succeed");
512        assert_eq!(result, Length::from_pt(100.0));
513    }
514}
515
516// ===== ADDITIONAL TESTS =====
517#[cfg(test)]
518mod additional_tests {
519    use super::*;
520    use fop_types::{Color, Length, Percentage};
521
522    // ===== PropertyValue type checks =====
523
524    #[test]
525    fn test_is_auto() {
526        assert!(PropertyValue::Auto.is_auto());
527        assert!(!PropertyValue::None.is_auto());
528        assert!(!PropertyValue::Inherit.is_auto());
529        assert!(!PropertyValue::Length(Length::from_pt(0.0)).is_auto());
530    }
531
532    #[test]
533    fn test_is_none() {
534        assert!(PropertyValue::None.is_none());
535        assert!(!PropertyValue::Auto.is_none());
536        assert!(!PropertyValue::Inherit.is_none());
537        assert!(!PropertyValue::Integer(0).is_none());
538    }
539
540    #[test]
541    fn test_is_inherit() {
542        assert!(PropertyValue::Inherit.is_inherit());
543        assert!(!PropertyValue::Auto.is_inherit());
544        assert!(!PropertyValue::None.is_inherit());
545        assert!(!PropertyValue::Boolean(false).is_inherit());
546    }
547
548    #[test]
549    fn test_as_length_returns_none_for_auto() {
550        assert!(PropertyValue::Auto.as_length().is_none());
551    }
552
553    #[test]
554    fn test_as_length_returns_none_for_none() {
555        assert!(PropertyValue::None.as_length().is_none());
556    }
557
558    #[test]
559    fn test_as_length_returns_none_for_integer() {
560        assert!(PropertyValue::Integer(42).as_length().is_none());
561    }
562
563    #[test]
564    fn test_as_color_returns_none_for_length() {
565        assert!(PropertyValue::Length(Length::from_pt(10.0))
566            .as_color()
567            .is_none());
568    }
569
570    #[test]
571    fn test_as_enum_returns_none_for_string() {
572        assert!(PropertyValue::String(std::borrow::Cow::Borrowed("foo"))
573            .as_enum()
574            .is_none());
575    }
576
577    #[test]
578    #[allow(clippy::approx_constant)]
579    fn test_as_integer_returns_none_for_number() {
580        assert!(PropertyValue::Number(3.14).as_integer().is_none());
581    }
582
583    #[test]
584    fn test_as_number_returns_none_for_integer() {
585        assert!(PropertyValue::Integer(42).as_number().is_none());
586    }
587
588    #[test]
589    fn test_as_boolean_returns_none_for_enum() {
590        assert!(PropertyValue::Enum(1).as_boolean().is_none());
591    }
592
593    #[test]
594    fn test_as_percentage_returns_none_for_length() {
595        assert!(PropertyValue::Length(Length::from_pt(50.0))
596            .as_percentage()
597            .is_none());
598    }
599
600    #[test]
601    fn test_as_string_returns_none_for_color() {
602        assert!(PropertyValue::Color(Color::RED).as_string().is_none());
603    }
604
605    // ===== PropertyValue construction and retrieval =====
606
607    #[test]
608    fn test_integer_value_positive() {
609        let v = PropertyValue::Integer(42);
610        assert_eq!(v.as_integer(), Some(42));
611    }
612
613    #[test]
614    fn test_integer_value_negative() {
615        let v = PropertyValue::Integer(-7);
616        assert_eq!(v.as_integer(), Some(-7));
617    }
618
619    #[test]
620    fn test_integer_value_zero() {
621        let v = PropertyValue::Integer(0);
622        assert_eq!(v.as_integer(), Some(0));
623    }
624
625    #[test]
626    #[allow(clippy::approx_constant)]
627    fn test_number_value_positive() {
628        let v = PropertyValue::Number(3.14);
629        assert_eq!(v.as_number(), Some(3.14));
630    }
631
632    #[test]
633    fn test_number_value_zero() {
634        let v = PropertyValue::Number(0.0);
635        assert_eq!(v.as_number(), Some(0.0));
636    }
637
638    #[test]
639    fn test_boolean_true() {
640        let v = PropertyValue::Boolean(true);
641        assert_eq!(v.as_boolean(), Some(true));
642    }
643
644    #[test]
645    fn test_boolean_false() {
646        let v = PropertyValue::Boolean(false);
647        assert_eq!(v.as_boolean(), Some(false));
648    }
649
650    #[test]
651    fn test_percentage_value() {
652        let v = PropertyValue::Percentage(Percentage::from_percent(75.0));
653        assert_eq!(v.as_percentage(), Some(Percentage::from_percent(75.0)));
654    }
655
656    #[test]
657    fn test_pair_value_access() {
658        let v = PropertyValue::Pair(
659            Box::new(PropertyValue::Length(Length::from_pt(10.0))),
660            Box::new(PropertyValue::Length(Length::from_pt(20.0))),
661        );
662        match v {
663            PropertyValue::Pair(a, b) => {
664                assert_eq!(a.as_length(), Some(Length::from_pt(10.0)));
665                assert_eq!(b.as_length(), Some(Length::from_pt(20.0)));
666            }
667            _ => panic!("Expected Pair"),
668        }
669    }
670
671    #[test]
672    fn test_list_value_empty() {
673        let v = PropertyValue::List(vec![]);
674        match v {
675            PropertyValue::List(items) => assert!(items.is_empty()),
676            _ => panic!("Expected List"),
677        }
678    }
679
680    #[test]
681    fn test_list_value_with_items() {
682        let v = PropertyValue::List(vec![
683            PropertyValue::Integer(1),
684            PropertyValue::Integer(2),
685            PropertyValue::Integer(3),
686        ]);
687        match v {
688            PropertyValue::List(items) => {
689                assert_eq!(items.len(), 3);
690                assert_eq!(items[0].as_integer(), Some(1));
691                assert_eq!(items[2].as_integer(), Some(3));
692            }
693            _ => panic!("Expected List"),
694        }
695    }
696
697    // ===== resolve_length tests =====
698
699    #[test]
700    fn test_resolve_length_with_length() {
701        let v = PropertyValue::Length(Length::from_pt(30.0));
702        let result = v.resolve_length(Length::from_pt(100.0));
703        assert_eq!(result, Some(Length::from_pt(30.0)));
704    }
705
706    #[test]
707    fn test_resolve_length_with_percentage() {
708        let v = PropertyValue::Percentage(Percentage::from_percent(25.0));
709        let result = v.resolve_length(Length::from_pt(200.0));
710        assert_eq!(result, Some(Length::from_pt(50.0)));
711    }
712
713    #[test]
714    fn test_resolve_length_with_auto_returns_none() {
715        let v = PropertyValue::Auto;
716        let result = v.resolve_length(Length::from_pt(100.0));
717        assert!(result.is_none());
718    }
719
720    #[test]
721    fn test_resolve_length_with_none_returns_none() {
722        let v = PropertyValue::None;
723        let result = v.resolve_length(Length::from_pt(100.0));
724        assert!(result.is_none());
725    }
726
727    // ===== RelativeFontSize edge cases =====
728
729    #[test]
730    fn test_relative_font_size_parse_all_keywords() {
731        assert!(RelativeFontSize::parse("larger").is_some());
732        assert!(RelativeFontSize::parse("smaller").is_some());
733        assert!(RelativeFontSize::parse("xx-small").is_some());
734        assert!(RelativeFontSize::parse("x-small").is_some());
735        assert!(RelativeFontSize::parse("small").is_some());
736        assert!(RelativeFontSize::parse("medium").is_some());
737        assert!(RelativeFontSize::parse("large").is_some());
738        assert!(RelativeFontSize::parse("x-large").is_some());
739        assert!(RelativeFontSize::parse("xx-large").is_some());
740    }
741
742    #[test]
743    fn test_relative_font_size_parse_invalid_returns_none() {
744        assert!(RelativeFontSize::parse("").is_none());
745        assert!(RelativeFontSize::parse("LARGE").is_none());
746        assert!(RelativeFontSize::parse("12pt").is_none());
747        assert!(RelativeFontSize::parse("unknown").is_none());
748    }
749
750    #[test]
751    fn test_relative_font_size_is_relative_for_larger_smaller() {
752        assert!(RelativeFontSize::Larger.is_relative());
753        assert!(RelativeFontSize::Smaller.is_relative());
754        assert!(!RelativeFontSize::Medium.is_relative());
755        assert!(!RelativeFontSize::Large.is_relative());
756    }
757
758    #[test]
759    fn test_relative_font_size_is_absolute_keyword() {
760        assert!(RelativeFontSize::XxSmall.is_absolute_keyword());
761        assert!(RelativeFontSize::XSmall.is_absolute_keyword());
762        assert!(RelativeFontSize::Small.is_absolute_keyword());
763        assert!(RelativeFontSize::Medium.is_absolute_keyword());
764        assert!(RelativeFontSize::Large.is_absolute_keyword());
765        assert!(RelativeFontSize::XLarge.is_absolute_keyword());
766        assert!(RelativeFontSize::XxLarge.is_absolute_keyword());
767        assert!(!RelativeFontSize::Larger.is_absolute_keyword());
768        assert!(!RelativeFontSize::Smaller.is_absolute_keyword());
769    }
770
771    #[test]
772    fn test_relative_font_size_resolve_xx_small() {
773        let result = RelativeFontSize::XxSmall.resolve(Length::from_pt(16.0));
774        assert_eq!(result, Length::from_pt(9.0));
775    }
776
777    #[test]
778    fn test_relative_font_size_resolve_xx_large() {
779        let result = RelativeFontSize::XxLarge.resolve(Length::from_pt(16.0));
780        assert_eq!(result, Length::from_pt(32.0));
781    }
782
783    #[test]
784    fn test_relative_font_size_larger_increases() {
785        let parent = Length::from_pt(10.0);
786        let result = RelativeFontSize::Larger.resolve(parent);
787        assert!(result.millipoints() > parent.millipoints());
788    }
789
790    #[test]
791    fn test_relative_font_size_smaller_decreases() {
792        let parent = Length::from_pt(10.0);
793        let result = RelativeFontSize::Smaller.resolve(parent);
794        assert!(result.millipoints() < parent.millipoints());
795    }
796
797    // ===== PropertyValue equality =====
798
799    #[test]
800    fn test_property_value_equality_auto() {
801        assert_eq!(PropertyValue::Auto, PropertyValue::Auto);
802    }
803
804    #[test]
805    fn test_property_value_equality_none() {
806        assert_eq!(PropertyValue::None, PropertyValue::None);
807    }
808
809    #[test]
810    fn test_property_value_equality_inherit() {
811        assert_eq!(PropertyValue::Inherit, PropertyValue::Inherit);
812    }
813
814    #[test]
815    fn test_property_value_equality_integer() {
816        assert_eq!(PropertyValue::Integer(42), PropertyValue::Integer(42));
817        assert_ne!(PropertyValue::Integer(42), PropertyValue::Integer(43));
818    }
819
820    #[test]
821    fn test_property_value_inequality_different_types() {
822        assert_ne!(PropertyValue::Auto, PropertyValue::None);
823        assert_ne!(PropertyValue::None, PropertyValue::Inherit);
824        assert_ne!(PropertyValue::Integer(0), PropertyValue::Boolean(false));
825    }
826}