Skip to main content

azul_css/codegen/
format.rs

1//! Generates const-compatible Rust source code from parsed CSS values.
2//!
3//! This module provides the [`FormatAsRustCode`] trait for converting CSS types
4//! into Rust source strings, [`GetHash`] for content-based deduplication, and
5//! [`VecContents`] for collecting heap-allocated CSS values that must be
6//! declared as `const` slices before use in generated code.
7//!
8//! Primary consumer: `core::xml` uses this for CSS-to-Rust code generation.
9
10use alloc::{collections::btree_map::BTreeMap, format, string::String, string::ToString, vec::Vec};
11use core::fmt::Write;
12use core::hash::Hash;
13
14// wildcard imports: the code generator references the full set of property types
15// across every css sub-module; listing them all explicitly is unmaintainable.
16#[allow(clippy::wildcard_imports)]
17use crate::{
18    corety::*,
19    css::*,
20    props::{basic::*, layout::*, property::*, style::*},
21};
22
23/// Formats a value as const-compatible Rust source code.
24pub trait FormatAsRustCode {
25    fn format_as_rust_code(&self, tabs: usize) -> String;
26}
27
28/// Returns a deterministic 64-bit hash for content-based deduplication.
29pub trait GetHash {
30    fn get_hash(&self) -> u64;
31}
32
33impl<T: Hash> GetHash for T {
34    fn get_hash(&self) -> u64 {
35        use core::hash::Hasher;
36        let mut hasher = std::hash::DefaultHasher::new();
37        self.hash(&mut hasher);
38        hasher.finish()
39    }
40}
41
42// In order to generate the Rust code, all items that implement Drop
43// have to be declared before being used.
44
45impl<T: FormatAsRustCode + 'static> FormatAsRustCode for BoxOrStatic<T> {
46    fn format_as_rust_code(&self, tabs: usize) -> String {
47        self.as_ref().format_as_rust_code(tabs)
48    }
49}
50
51/// Collects heap-allocated CSS values (strings, vecs) that must be emitted as
52/// `const` slice declarations before the generated Rust code can reference them.
53#[derive(Debug, Default)]
54pub struct VecContents {
55    // the u64 is the hash of the type (generated by string.get_hash())
56    pub strings: BTreeMap<u64, AzString>,
57    pub style_filters: BTreeMap<u64, StyleFilterVec>,
58    pub style_background_sizes: BTreeMap<u64, StyleBackgroundSizeVec>,
59    pub style_background_repeats: BTreeMap<u64, StyleBackgroundRepeatVec>,
60    pub style_background_contents: BTreeMap<u64, StyleBackgroundContentVec>,
61    pub style_background_positions: BTreeMap<u64, StyleBackgroundPositionVec>,
62    pub style_transforms: BTreeMap<u64, StyleTransformVec>,
63    pub font_families: BTreeMap<u64, StyleFontFamilyVec>,
64    pub linear_color_stops: BTreeMap<u64, NormalizedLinearColorStopVec>,
65    pub radial_color_stops: BTreeMap<u64, NormalizedRadialColorStopVec>,
66}
67
68impl VecContents {
69    pub fn format(&self, tabs: usize) -> String {
70        let mut result = String::new();
71        let t = "    ".repeat(tabs);
72        let t2 = "    ".repeat(tabs + 1);
73
74        for (key, item) in &self.strings {
75            let _ = write!(
76                result,
77                "\r\n    const STRING_{}: AzString = AzString::from_const_str(\"{}\");",
78                key,
79                item.as_str()
80            );
81        }
82
83        for (key, item) in &self.style_filters {
84            let val = item
85                .iter()
86                .map(|filter| format_style_filter(filter, tabs + 1))
87                .collect::<Vec<_>>()
88                .join(&format!(",\r\n{t}"));
89
90            let _ =
91                write!(result,
92                "\r\n    const STYLE_FILTER_{key}_ITEMS: &[StyleFilter] = &[\r\n{t2}{val}\r\n{t}];"
93            );
94        }
95
96        for (key, item) in &self.style_background_sizes {
97            let val = item
98                .iter()
99                .map(format_style_background_size)
100                .collect::<Vec<_>>()
101                .join(&format!(",\r\n{t}"));
102
103            let _ = write!(
104                result,
105                "\r\n    const STYLE_BACKGROUND_SIZE_{key}_ITEMS: &[StyleBackgroundSize] = \
106                 &[\r\n{t2}{val}\r\n{t}];"
107            );
108        }
109
110        for (key, item) in &self.style_background_repeats {
111            let val = item
112                .iter()
113                .map(|bgr| bgr.format_as_rust_code(tabs + 1))
114                .collect::<Vec<_>>()
115                .join(&format!(",\r\n{t}"));
116
117            let _ =
118                write!(result,
119                "\r\n    const STYLE_BACKGROUND_REPEAT_{key}_ITEMS: &[StyleBackgroundRepeat] = \
120                 &[\r\n{t2}{val}\r\n{t}];"
121            );
122        }
123
124        for (key, item) in &self.style_background_contents {
125            let val = item
126                .iter()
127                .map(|bgc| format_style_background_content(bgc, tabs + 1))
128                .collect::<Vec<_>>()
129                .join(&format!(",\r\n{t}"));
130
131            let _ =
132                write!(result,
133                "\r\n    const STYLE_BACKGROUND_CONTENT_{key}_ITEMS: &[StyleBackgroundContent] = \
134                 &[\r\n{t2}{val}\r\n{t}];"
135            );
136        }
137
138        for (key, item) in &self.style_background_positions {
139            let val = item
140                .iter()
141                .map(|bgp| format_style_background_position(bgp, tabs))
142                .collect::<Vec<_>>()
143                .join(&format!(",\r\n{t}"));
144
145            let _ = write!(result,
146                "\r\n    const STYLE_BACKGROUND_POSITION_{key}_ITEMS: &[StyleBackgroundPosition] = \
147                 &[\r\n{t2}{val}\r\n{t}];"
148            );
149        }
150
151        for (key, item) in &self.style_transforms {
152            let val = format_style_transforms(item.as_ref(), tabs + 1);
153
154            let _ = write!(result,
155                "\r\n    const STYLE_TRANSFORM_{key}_ITEMS: &[StyleTransform] = &[\r\n{t2}{val}\r\n{t}];"
156            );
157        }
158
159        for (key, item) in &self.font_families {
160            let val = format_font_ids(item.as_ref(), tabs + 1);
161
162            let _ = write!(result,
163                "\r\n    const STYLE_FONT_FAMILY_{key}_ITEMS: &[StyleFontFamily] = &[\r\n{t2}{val}\r\n{t}];"
164            );
165        }
166
167        for (key, item) in &self.linear_color_stops {
168            let val = format_linear_color_stops(item.as_ref(), 1);
169
170            let _ = write!(
171                result,
172                "\r\n    const LINEAR_COLOR_STOP_{key}_ITEMS: &[NormalizedLinearColorStop] = \
173                 &[\r\n{t2}{val}\r\n{t}];"
174            );
175        }
176
177        for (key, item) in &self.radial_color_stops {
178            let val = format_radial_color_stops(item.as_ref(), tabs);
179
180            let _ = write!(
181                result,
182                "\r\n    const RADIAL_COLOR_STOP_{key}_ITEMS: &[NormalizedRadialColorStop] = \
183                 &[\r\n{t2}{val}\r\n{t}];"
184            );
185        }
186
187        result
188    }
189
190    // given a CSS property, clones all the necessary strings (see class documentation)
191    // Cross-type dispatch over CssProperty variants; identical-looking arm bodies
192    // bind different value types and can't merge (clippy::match_same_arms FP).
193    #[allow(clippy::match_same_arms)]
194    pub fn insert_from_css_property(&mut self, prop: &CssProperty) {
195        match prop {
196            CssProperty::FontFamily(CssPropertyValue::Exact(v)) => {
197                for family in v {
198                    match family {
199                        StyleFontFamily::System(s) => {
200                            // if the font-family is surrounded by quotes, strip them ("Arial" ->
201                            // Arial)
202                            let s = s.as_str();
203                            let s = s.trim();
204                            let s = s.trim_start_matches('\"');
205                            let s = s.trim_end_matches('\"');
206                            let s = s.trim_start_matches('\'');
207                            let s = s.trim_end_matches('\'');
208
209                            self.strings.insert(s.get_hash(), s.to_string().into());
210                        }
211                        StyleFontFamily::File(s) => {
212                            let s = s.as_str();
213                            let s = s.trim();
214                            let s = s.trim_start_matches('\"');
215                            let s = s.trim_end_matches('\"');
216                            let s = s.trim_start_matches('\'');
217                            let s = s.trim_end_matches('\'');
218
219                            self.strings.insert(s.get_hash(), s.to_string().into());
220                        }
221                        _ => {}
222                    }
223                }
224                self.font_families.insert(v.get_hash(), v.clone());
225            }
226            CssProperty::Transform(CssPropertyValue::Exact(v)) => {
227                self.style_transforms.insert(v.get_hash(), v.clone());
228            }
229            CssProperty::BackgroundRepeat(CssPropertyValue::Exact(v)) => {
230                self.style_background_repeats
231                    .insert(v.get_hash(), v.clone());
232            }
233            CssProperty::BackgroundSize(CssPropertyValue::Exact(v)) => {
234                self.style_background_sizes.insert(v.get_hash(), v.clone());
235            }
236            CssProperty::BackgroundPosition(CssPropertyValue::Exact(v)) => {
237                self.style_background_positions
238                    .insert(v.get_hash(), v.clone());
239            }
240            CssProperty::BackgroundContent(CssPropertyValue::Exact(ref v)) => {
241                for background in v {
242                    match background {
243                        StyleBackgroundContent::Image(id) => {
244                            self.strings.insert(id.get_hash(), id.clone());
245                        }
246                        StyleBackgroundContent::LinearGradient(lg) => {
247                            self.linear_color_stops
248                                .insert(lg.stops.get_hash(), lg.stops.clone());
249                        }
250                        StyleBackgroundContent::RadialGradient(rg) => {
251                            self.linear_color_stops
252                                .insert(rg.stops.get_hash(), rg.stops.clone());
253                        }
254                        StyleBackgroundContent::ConicGradient(lg) => {
255                            self.radial_color_stops
256                                .insert(lg.stops.get_hash(), lg.stops.clone());
257                        }
258                        _ => {}
259                    }
260                }
261                self.style_background_contents
262                    .insert(v.get_hash(), v.clone());
263            }
264            CssProperty::Filter(CssPropertyValue::Exact(v)) => {
265                self.style_filters.insert(v.get_hash(), v.clone());
266            }
267            CssProperty::BackdropFilter(CssPropertyValue::Exact(v)) => {
268                self.style_filters.insert(v.get_hash(), v.clone());
269            }
270            _ => {}
271        }
272    }
273}
274
275// Helper functions for formatting values
276
277/// Formats a `PixelValue` as a const-compatible Rust constructor call.
278#[must_use]
279pub fn format_pixel_value(p: &PixelValue) -> String {
280    let value = p.number.get();
281
282    // Decompose into integer + fractional parts using the absolute value so
283    // that negative numbers are handled correctly.  Previously, floorf on a
284    // negative value (e.g. -1.5 → -2) caused the wrong reconstruction.
285    let abs_val = libm::fabsf(value);
286    let sign: isize = if value < 0.0 { -1 } else { 1 };
287    let pre_comma = f32_to_isize(libm::floorf(abs_val)) * sign;
288    let post_comma = f32_to_isize(libm::roundf((abs_val - libm::floorf(abs_val)) * 100.0));
289
290    match p.metric {
291        SizeMetric::Pt => format!(
292            "PixelValue::const_pt_fractional({pre_comma}, {post_comma})"
293        ),
294        SizeMetric::Em => format!(
295            "PixelValue::const_em_fractional({pre_comma}, {post_comma})"
296        ),
297        other => format!(
298            "PixelValue::const_from_metric_fractional(SizeMetric::{other:?}, {pre_comma}, {post_comma})"
299        ),
300    }
301}
302
303/// Truncating `f32` → `isize` for the integer / fractional parts of const CSS
304/// dimension values (always small and non-negative after `fabsf`). Rust's
305/// `as isize` saturates out-of-range floats; this isolates the one unavoidable
306/// float→int cast behind a documented attribute. Behaviour-preserving.
307#[inline]
308#[allow(clippy::cast_possible_truncation)]
309const fn f32_to_isize(v: f32) -> isize {
310    v as isize
311}
312
313/// Formats a `PixelValueNoPercent` as a const-compatible Rust constructor call.
314#[must_use]
315pub fn format_pixel_value_no_percent(p: &PixelValueNoPercent) -> String {
316    format!(
317        "PixelValueNoPercent {{ inner: {} }}",
318        format_pixel_value(&p.inner)
319    )
320}
321
322/// Formats a `FloatValue` as a const-compatible Rust constructor call.
323#[must_use]
324pub fn format_float_value(f: &FloatValue) -> String {
325    let value = f.get();
326    let abs_val = libm::fabsf(value);
327    let sign: isize = if value < 0.0 { -1 } else { 1 };
328    let pre_comma = f32_to_isize(libm::floorf(abs_val)) * sign;
329    let post_comma = f32_to_isize(libm::roundf((abs_val - libm::floorf(abs_val)) * 100.0));
330    format!("FloatValue::const_new_fractional({pre_comma}, {post_comma})")
331}
332
333/// Formats a `PercentageValue` as a const-compatible Rust constructor call.
334#[must_use]
335pub fn format_percentage_value(f: &PercentageValue) -> String {
336    let value = f.normalized() * 100.0;
337    let abs_val = libm::fabsf(value);
338    let sign: isize = if value < 0.0 { -1 } else { 1 };
339    let pre_comma = f32_to_isize(libm::floorf(abs_val)) * sign;
340    let post_comma = f32_to_isize(libm::roundf((abs_val - libm::floorf(abs_val)) * 100.0));
341    format!("PercentageValue::const_new_fractional({pre_comma}, {post_comma})")
342}
343
344/// Formats an `AngleValue` as a const-compatible Rust constructor call.
345#[must_use]
346pub fn format_angle_value(f: &AngleValue) -> String {
347    let value = f.number.get();
348    let abs_val = libm::fabsf(value);
349    let sign: isize = if value < 0.0 { -1 } else { 1 };
350    let pre_comma = f32_to_isize(libm::floorf(abs_val)) * sign;
351    let post_comma = f32_to_isize(libm::roundf((abs_val - libm::floorf(abs_val)) * 100.0));
352    format!(
353        "AngleValue::const_from_metric_fractional(AngleMetric::{:?}, {}, {})",
354        f.metric, pre_comma, post_comma
355    )
356}
357
358/// Formats a `ColorU` as a const-compatible Rust struct literal.
359#[must_use]
360pub fn format_color_value(c: &ColorU) -> String {
361    format!(
362        "ColorU {{ r: {}, g: {}, b: {}, a: {} }}",
363        c.r, c.g, c.b, c.a
364    )
365}
366
367fn format_grid_line(line: &GridLine, _tabs: usize) -> String {
368    match line {
369        GridLine::Auto => "GridLine::Auto".to_string(),
370        GridLine::Line(n) => format!("GridLine::Line({n})"),
371        GridLine::Named(named) => format!(
372            "GridLine::Named(NamedGridLine {{ grid_line_name: AzString::from_const_str({:?}), span_count: {} }})",
373            named.grid_line_name.as_ref(),
374            named.span_count,
375        ),
376        GridLine::Span(n) => format!("GridLine::Span({n})"),
377    }
378}
379
380fn format_color_or_system(c: ColorOrSystem) -> String {
381    use crate::props::basic::color::{ColorOrSystem, SystemColorRef};
382    match c {
383        ColorOrSystem::Color(color) => {
384            format!("ColorOrSystem::Color({})", format_color_value(&color))
385        }
386        ColorOrSystem::System(system_ref) => {
387            let variant = match system_ref {
388                SystemColorRef::Text => "Text",
389                SystemColorRef::Background => "Background",
390                SystemColorRef::Accent => "Accent",
391                SystemColorRef::AccentText => "AccentText",
392                SystemColorRef::ButtonFace => "ButtonFace",
393                SystemColorRef::ButtonText => "ButtonText",
394                SystemColorRef::WindowBackground => "WindowBackground",
395                SystemColorRef::SelectionBackground => "SelectionBackground",
396                SystemColorRef::SelectionText => "SelectionText",
397            };
398            format!("ColorOrSystem::System(SystemColorRef::{variant})")
399        }
400    }
401}
402
403// Macro implementations for common patterns
404
405macro_rules! impl_float_value_fmt {
406    ($struct_name:ident) => {
407        impl FormatAsRustCode for $struct_name {
408            fn format_as_rust_code(&self, _tabs: usize) -> String {
409                format!(
410                    "{} {{ inner: {} }}",
411                    stringify!($struct_name),
412                    format_float_value(&self.inner)
413                )
414            }
415        }
416    };
417}
418
419impl_float_value_fmt!(LayoutFlexGrow);
420impl_float_value_fmt!(LayoutFlexShrink);
421
422macro_rules! impl_percentage_value_fmt {
423    ($struct_name:ident) => {
424        impl FormatAsRustCode for $struct_name {
425            fn format_as_rust_code(&self, _tabs: usize) -> String {
426                format!(
427                    "{} {{ inner: {} }}",
428                    stringify!($struct_name),
429                    format_percentage_value(&self.inner)
430                )
431            }
432        }
433    };
434}
435
436impl_percentage_value_fmt!(StyleLineHeight);
437impl_percentage_value_fmt!(StyleOpacity);
438
439macro_rules! impl_pixel_value_fmt {
440    ($struct_name:ident) => {
441        impl FormatAsRustCode for $struct_name {
442            fn format_as_rust_code(&self, _tabs: usize) -> String {
443                format!(
444                    "{} {{ inner: {} }}",
445                    stringify!($struct_name),
446                    format_pixel_value(&self.inner)
447                )
448            }
449        }
450    };
451}
452
453impl_pixel_value_fmt!(StyleTabSize);
454impl_pixel_value_fmt!(StyleBorderTopLeftRadius);
455impl_pixel_value_fmt!(StyleBorderBottomLeftRadius);
456impl_pixel_value_fmt!(StyleBorderTopRightRadius);
457impl_pixel_value_fmt!(StyleBorderBottomRightRadius);
458
459impl_pixel_value_fmt!(LayoutBorderTopWidth);
460impl_pixel_value_fmt!(LayoutBorderLeftWidth);
461impl_pixel_value_fmt!(LayoutBorderRightWidth);
462impl_pixel_value_fmt!(LayoutBorderBottomWidth);
463impl_pixel_value_fmt!(StyleLetterSpacing);
464impl_pixel_value_fmt!(StyleWordSpacing);
465impl_pixel_value_fmt!(StyleFontSize);
466
467impl_pixel_value_fmt!(LayoutMarginTop);
468impl_pixel_value_fmt!(LayoutMarginBottom);
469impl_pixel_value_fmt!(LayoutMarginRight);
470impl_pixel_value_fmt!(LayoutMarginLeft);
471
472impl_pixel_value_fmt!(LayoutPaddingTop);
473impl_pixel_value_fmt!(LayoutPaddingBottom);
474impl_pixel_value_fmt!(LayoutPaddingRight);
475impl_pixel_value_fmt!(LayoutPaddingLeft);
476impl_pixel_value_fmt!(LayoutPaddingInlineStart);
477impl_pixel_value_fmt!(LayoutPaddingInlineEnd);
478
479impl FormatAsRustCode for LayoutWidth {
480    fn format_as_rust_code(&self, _tabs: usize) -> String {
481        match self {
482            Self::Auto => "LayoutWidth::Auto".to_string(),
483            Self::Px(px) => format!("LayoutWidth::Px({})", format_pixel_value(px)),
484            Self::MinContent => "LayoutWidth::MinContent".to_string(),
485            Self::MaxContent => "LayoutWidth::MaxContent".to_string(),
486            Self::FitContent(px) => format!("LayoutWidth::FitContent({})", format_pixel_value(px)),
487            Self::Calc(items) => format!("LayoutWidth::Calc(/* {} items */)", items.len()),
488        }
489    }
490}
491
492impl FormatAsRustCode for LayoutHeight {
493    fn format_as_rust_code(&self, _tabs: usize) -> String {
494        match self {
495            Self::Auto => "LayoutHeight::Auto".to_string(),
496            Self::Px(px) => format!("LayoutHeight::Px({})", format_pixel_value(px)),
497            Self::MinContent => "LayoutHeight::MinContent".to_string(),
498            Self::MaxContent => "LayoutHeight::MaxContent".to_string(),
499            Self::FitContent(px) => format!("LayoutHeight::FitContent({})", format_pixel_value(px)),
500            Self::Calc(items) => format!("LayoutHeight::Calc(/* {} items */)", items.len()),
501        }
502    }
503}
504
505impl_pixel_value_fmt!(LayoutMinHeight);
506impl_pixel_value_fmt!(LayoutMinWidth);
507impl_pixel_value_fmt!(LayoutMaxWidth);
508impl_pixel_value_fmt!(LayoutMaxHeight);
509impl_pixel_value_fmt!(LayoutTop);
510impl_pixel_value_fmt!(LayoutInsetBottom);
511
512impl_pixel_value_fmt!(LayoutRight);
513impl_pixel_value_fmt!(LayoutLeft);
514
515// LayoutFlexBasis implementation moved to `props/layout/flex.rs`.
516
517impl_pixel_value_fmt!(LayoutColumnGap);
518impl_pixel_value_fmt!(LayoutRowGap);
519
520impl FormatAsRustCode for GridTemplate {
521    fn format_as_rust_code(&self, tabs: usize) -> String {
522        let tracks: Vec<String> = self
523            .tracks
524            .as_ref()
525            .iter()
526            .map(|t| t.format_as_rust_code(tabs))
527            .collect();
528        format!(
529            "GridTemplate {{ tracks: GridTrackSizingVec::from_vec(vec![{}]) }}",
530            tracks.join(", ")
531        )
532    }
533}
534
535impl FormatAsRustCode for GridPlacement {
536    fn format_as_rust_code(&self, tabs: usize) -> String {
537        format!(
538            "GridPlacement {{ grid_start: {}, grid_end: {} }}",
539            format_grid_line(&self.grid_start, tabs),
540            format_grid_line(&self.grid_end, tabs),
541        )
542    }
543}
544
545impl_color_value_fmt!(StyleTextColor);
546impl_color_value_fmt!(StyleBorderTopColor);
547impl_color_value_fmt!(StyleBorderLeftColor);
548impl_color_value_fmt!(StyleBorderRightColor);
549impl_color_value_fmt!(StyleBorderBottomColor);
550
551impl_enum_fmt!(
552    StyleMixBlendMode,
553    Normal,
554    Multiply,
555    Screen,
556    Overlay,
557    Darken,
558    Lighten,
559    ColorDodge,
560    ColorBurn,
561    HardLight,
562    SoftLight,
563    Difference,
564    Exclusion,
565    Hue,
566    Saturation,
567    Color,
568    Luminosity
569);
570
571impl_enum_fmt!(StyleHyphens, None, Manual, Auto);
572impl_enum_fmt!(StyleWordBreak, Normal, BreakAll, KeepAll, BreakWord);
573impl_enum_fmt!(StyleOverflowWrap, Normal, Anywhere, BreakWord);
574impl_enum_fmt!(StyleLineBreak, Auto, Loose, Normal, Strict, Anywhere);
575impl_enum_fmt!(StyleObjectFit, Fill, Contain, Cover, None, ScaleDown);
576impl_enum_fmt!(StyleTextOverflow, Clip, Ellipsis);
577
578impl FormatAsRustCode for StyleObjectPosition {
579    fn format_as_rust_code(&self, _tabs: usize) -> String {
580        format!(
581            "StyleObjectPosition {{ horizontal: {}, vertical: {} }}",
582            format_background_position_horizontal(&self.horizontal),
583            format_background_position_vertical(&self.vertical)
584        )
585    }
586}
587
588impl FormatAsRustCode for StyleAspectRatio {
589    fn format_as_rust_code(&self, _tabs: usize) -> String {
590        match self {
591            Self::Auto => "StyleAspectRatio::Auto".to_string(),
592            Self::Ratio(r) => format!(
593                "StyleAspectRatio::Ratio(AspectRatioValue {{ width: {}, height: {} }})",
594                r.width, r.height
595            ),
596        }
597    }
598}
599
600impl_enum_fmt!(StyleTextOrientation, Mixed, Upright, Sideways);
601impl_enum_fmt!(
602    StyleTextAlignLast,
603    Auto,
604    Start,
605    End,
606    Left,
607    Right,
608    Center,
609    Justify
610);
611
612impl_enum_fmt!(
613    StyleTextTransform,
614    None,
615    Capitalize,
616    Uppercase,
617    Lowercase,
618    FullWidth
619);
620
621impl_enum_fmt!(StyleDirection, Ltr, Rtl);
622
623impl_enum_fmt!(
624    StyleWhiteSpace,
625    Normal,
626    Pre,
627    Nowrap,
628    PreWrap,
629    PreLine,
630    BreakSpaces
631);
632
633impl_enum_fmt!(StyleVisibility, Visible, Hidden, Collapse);
634
635impl_enum_fmt!(LayoutWritingMode, HorizontalTb, VerticalRl, VerticalLr);
636
637impl_enum_fmt!(LayoutClear, None, Left, Right, Both);
638
639impl_enum_fmt!(
640    StyleCursor,
641    Alias,
642    AllScroll,
643    Cell,
644    ColResize,
645    ContextMenu,
646    Copy,
647    Crosshair,
648    Default,
649    EResize,
650    EwResize,
651    Grab,
652    Grabbing,
653    Help,
654    Move,
655    NResize,
656    NsResize,
657    NeswResize,
658    NwseResize,
659    Pointer,
660    Progress,
661    RowResize,
662    SResize,
663    SeResize,
664    Text,
665    Unset,
666    VerticalText,
667    WResize,
668    Wait,
669    ZoomIn,
670    ZoomOut
671);
672
673impl_enum_fmt!(
674    BorderStyle,
675    None,
676    Solid,
677    Double,
678    Dotted,
679    Dashed,
680    Hidden,
681    Groove,
682    Ridge,
683    Inset,
684    Outset
685);
686
687impl_enum_fmt!(
688    StyleBackgroundRepeat,
689    NoRepeat,
690    PatternRepeat,
691    RepeatX,
692    RepeatY
693);
694
695impl_enum_fmt!(
696    LayoutDisplay,
697    None,
698    Block,
699    Inline,
700    InlineBlock,
701    Flex,
702    InlineFlex,
703    Table,
704    InlineTable,
705    TableRowGroup,
706    TableHeaderGroup,
707    TableFooterGroup,
708    TableRow,
709    TableColumnGroup,
710    TableColumn,
711    TableCell,
712    TableCaption,
713    ListItem,
714    RunIn,
715    Marker,
716    Grid,
717    InlineGrid,
718    FlowRoot,
719    Contents
720);
721
722impl_enum_fmt!(LayoutFloat, Left, Right, None);
723
724impl_enum_fmt!(LayoutBoxSizing, ContentBox, BorderBox);
725
726impl_enum_fmt!(LayoutFlexDirection, Row, RowReverse, Column, ColumnReverse);
727
728impl_enum_fmt!(LayoutFlexWrap, Wrap, NoWrap, WrapReverse);
729
730impl_enum_fmt!(
731    LayoutJustifyContent,
732    Start,
733    End,
734    FlexStart,
735    FlexEnd,
736    Center,
737    SpaceBetween,
738    SpaceAround,
739    SpaceEvenly
740);
741
742impl_enum_fmt!(LayoutAlignItems, Stretch, Center, Start, End, Baseline);
743
744impl_enum_fmt!(
745    LayoutAlignContent,
746    Start,
747    End,
748    Stretch,
749    Center,
750    SpaceBetween,
751    SpaceAround
752);
753
754impl_enum_fmt!(Shape, Circle, Ellipse);
755
756impl_enum_fmt!(LayoutOverflow, Auto, Scroll, Visible, Hidden, Clip);
757
758impl_enum_fmt!(StyleTextAlign, Center, Left, Right, Justify, Start, End);
759
760impl_enum_fmt!(StyleUserSelect, Auto, Text, None, All);
761
762impl_enum_fmt!(StyleTextDecoration, None, Underline, Overline, LineThrough);
763
764impl_enum_fmt!(
765    DirectionCorner,
766    Right,
767    Left,
768    Top,
769    Bottom,
770    TopRight,
771    TopLeft,
772    BottomRight,
773    BottomLeft
774);
775
776impl_enum_fmt!(ExtendMode, Clamp, Repeat);
777
778impl_enum_fmt!(StyleBackfaceVisibility, Visible, Hidden);
779impl_enum_fmt!(StyleAppRegion, NoDrag, Drag);
780impl_enum_fmt!(StyleSpatialNavigationAction, Auto, Focus, Scroll);
781impl_enum_fmt!(StyleSpatialNavigationContain, Auto, Contain);
782
783impl_enum_fmt!(
784    StyleUnicodeBidi,
785    Normal,
786    Embed,
787    Isolate,
788    BidiOverride,
789    IsolateOverride,
790    Plaintext
791);
792impl_enum_fmt!(StyleTextBoxTrim, None, TrimStart, TrimEnd, TrimBoth);
793impl_enum_fmt!(
794    TextBoxEdgeOver,
795    Auto,
796    Text,
797    Cap,
798    Ex,
799    Ideographic,
800    IdeographicInk
801);
802impl_enum_fmt!(
803    TextBoxEdgeUnder,
804    Auto,
805    Text,
806    Alphabetic,
807    Ideographic,
808    IdeographicInk
809);
810impl FormatAsRustCode for StyleTextBoxEdge {
811    fn format_as_rust_code(&self, tabs: usize) -> String {
812        format!(
813            "StyleTextBoxEdge {{ over: {}, under: {} }}",
814            self.over.format_as_rust_code(tabs),
815            self.under.format_as_rust_code(tabs)
816        )
817    }
818}
819impl_enum_fmt!(
820    StyleDominantBaseline,
821    Auto,
822    TextBottom,
823    Alphabetic,
824    Ideographic,
825    Middle,
826    Central,
827    Mathematical,
828    Hanging,
829    TextTop
830);
831impl_enum_fmt!(
832    StyleAlignmentBaseline,
833    Baseline,
834    TextBottom,
835    Alphabetic,
836    Ideographic,
837    Middle,
838    Central,
839    Mathematical,
840    TextTop
841);
842impl_enum_fmt!(StyleBaselineSource, Auto, First, Last);
843impl_enum_fmt!(
844    StyleLineFitEdge,
845    Leading,
846    Text,
847    Cap,
848    Ex,
849    Ideographic,
850    IdeographicInk,
851    Alphabetic
852);
853impl_enum_fmt!(
854    StyleInitialLetterAlign,
855    Auto,
856    Alphabetic,
857    Hanging,
858    Ideographic
859);
860impl_enum_fmt!(StyleInitialLetterWrap, None, First, All, Grid);
861impl_enum_fmt!(StyleScrollbarGutter, Auto, Stable, StableBothEdges);
862
863impl FormatAsRustCode for StyleOverflowClipMargin {
864    fn format_as_rust_code(&self, tabs: usize) -> String {
865        format!(
866            "StyleOverflowClipMargin {{ inner: {} }}",
867            self.inner.format_as_rust_code(tabs)
868        )
869    }
870}
871
872impl FormatAsRustCode for StyleClipRect {
873    fn format_as_rust_code(&self, _tabs: usize) -> String {
874        fn fmt_edge(o: OptionF32) -> String {
875            match o {
876                OptionF32::None => String::from("OptionF32::None"),
877                OptionF32::Some(v) => format!("OptionF32::Some({v:?})"),
878            }
879        }
880        format!(
881            "StyleClipRect {{ top: {}, right: {}, bottom: {}, left: {} }}",
882            fmt_edge(self.top),
883            fmt_edge(self.right),
884            fmt_edge(self.bottom),
885            fmt_edge(self.left),
886        )
887    }
888}
889
890// Complex type implementations
891
892fn format_style_background_size(c: &StyleBackgroundSize) -> String {
893    match c {
894        StyleBackgroundSize::Contain => String::from("StyleBackgroundSize::Contain"),
895        StyleBackgroundSize::Cover => String::from("StyleBackgroundSize::Cover"),
896        StyleBackgroundSize::ExactSize(size) => format!(
897            "StyleBackgroundSize::ExactSize(PixelValueSize {{ width: {}, height: {} }})",
898            format_pixel_value(&size.width),
899            format_pixel_value(&size.height)
900        ),
901    }
902}
903
904// Scrollbar-related impls moved to `props/style/scrollbar.rs`
905
906/// Formats a `ScrollbarInfo` as a const-compatible Rust struct literal.
907#[must_use]
908pub fn format_scrollbar_info(s: &ScrollbarInfo, tabs: usize) -> String {
909    let t = String::from("    ").repeat(tabs);
910    let t1 = String::from("    ").repeat(tabs + 1);
911    format!(
912        "ScrollbarInfo {{\r\n{}width: {},\r\n{}padding_left: {},\r\n{}padding_right: \
913         {},\r\n{}track: {},\r\n{}thumb: {},\r\n{}button: {},\r\n{}button: {},\r\n{}resizer: \
914         {},\r\n{}}}",
915        t1,
916        s.width.format_as_rust_code(tabs + 1),
917        t1,
918        s.padding_left.format_as_rust_code(tabs + 1),
919        t1,
920        s.padding_right.format_as_rust_code(tabs + 1),
921        t1,
922        format_style_background_content(&s.track, tabs + 1),
923        t1,
924        format_style_background_content(&s.thumb, tabs + 1),
925        t1,
926        format_style_background_content(&s.button, tabs + 1),
927        t1,
928        format_style_background_content(&s.corner, tabs + 1),
929        t1,
930        format_style_background_content(&s.resizer, tabs + 1),
931        t
932    )
933}
934
935// Background/filter vec impls moved to their respective modules.
936
937fn format_style_background_content(content: &StyleBackgroundContent, tabs: usize) -> String {
938    match content {
939        StyleBackgroundContent::LinearGradient(l) => format!(
940            "StyleBackgroundContent::LinearGradient({})",
941            format_linear_gradient(l, tabs)
942        ),
943        StyleBackgroundContent::RadialGradient(r) => format!(
944            "StyleBackgroundContent::RadialGradient({})",
945            format_radial_gradient(r, tabs)
946        ),
947        StyleBackgroundContent::ConicGradient(r) => format!(
948            "StyleBackgroundContent::ConicGradient({})",
949            format_conic_gradient(r, tabs)
950        ),
951        StyleBackgroundContent::Image(id) => format!("StyleBackgroundContent::Image({id:?})"),
952        StyleBackgroundContent::Color(c) => {
953            format!("StyleBackgroundContent::Color({})", format_color_value(c))
954        }
955        StyleBackgroundContent::SystemColor(s) => {
956            use crate::props::basic::color::SystemColorRef;
957            let variant = match s {
958                SystemColorRef::Text => "Text",
959                SystemColorRef::Background => "Background",
960                SystemColorRef::Accent => "Accent",
961                SystemColorRef::AccentText => "AccentText",
962                SystemColorRef::ButtonFace => "ButtonFace",
963                SystemColorRef::ButtonText => "ButtonText",
964                SystemColorRef::WindowBackground => "WindowBackground",
965                SystemColorRef::SelectionBackground => "SelectionBackground",
966                SystemColorRef::SelectionText => "SelectionText",
967            };
968            format!("StyleBackgroundContent::SystemColor(SystemColorRef::{variant})")
969        }
970    }
971}
972
973fn format_direction(d: &Direction, tabs: usize) -> String {
974    match d {
975        Direction::Angle(fv) => format!("Direction::Angle({})", format_angle_value(fv)),
976        Direction::FromTo(DirectionCorners { dir_from, dir_to }) => format!(
977            "Direction::FromTo(DirectionCorners {{ dir_from: {}, dir_to: {} }})",
978            dir_from.format_as_rust_code(tabs + 1),
979            dir_to.format_as_rust_code(tabs + 1)
980        ),
981    }
982}
983
984fn format_linear_gradient(l: &LinearGradient, tabs: usize) -> String {
985    let t = String::from("    ").repeat(tabs);
986    let t1 = String::from("    ").repeat(tabs + 1);
987    format!(
988        "LinearGradient {{\r\n{}direction: {},\r\n{}extend_mode: {},\r\n{}stops: \
989         NormalizedLinearColorStopVec::from_const_slice(LINEAR_COLOR_STOP_{}_ITEMS),\r\n{}}}",
990        t1,
991        format_direction(&l.direction, tabs + 1),
992        t1,
993        l.extend_mode.format_as_rust_code(tabs + 1),
994        t1,
995        l.stops.get_hash(),
996        t,
997    )
998}
999
1000fn format_conic_gradient(r: &ConicGradient, tabs: usize) -> String {
1001    let t = String::from("    ").repeat(tabs);
1002    let t1 = String::from("    ").repeat(tabs + 1);
1003
1004    format!(
1005        "ConicGradient {{\r\n{}extend_mode: {},\r\n{}center: {},\r\n{}angle: {},\r\n{}stops: \
1006         NormalizedRadialColorStopVec::from_const_slice(RADIAL_COLOR_STOP_{}_ITEMS),\r\n{}}}",
1007        t1,
1008        r.extend_mode.format_as_rust_code(tabs + 1),
1009        t1,
1010        format_style_background_position(&r.center, tabs + 1),
1011        t1,
1012        format_angle_value(&r.angle),
1013        t1,
1014        r.stops.get_hash(),
1015        t,
1016    )
1017}
1018
1019fn format_radial_gradient(r: &RadialGradient, tabs: usize) -> String {
1020    let t = String::from("    ").repeat(tabs);
1021    let t1 = String::from("    ").repeat(tabs + 1);
1022    format!(
1023        "RadialGradient {{\r\n{}shape: {},\r\n{}extend_mode: {},\r\n{}position: {},\r\n{}size: \
1024         RadialGradientSize::{:?},\r\n{}stops: \
1025         NormalizedLinearColorStopVec::from_const_slice(LINEAR_COLOR_STOP_{}_ITEMS),\r\n{}}}",
1026        t1,
1027        r.shape.format_as_rust_code(tabs + 1),
1028        t1,
1029        r.extend_mode.format_as_rust_code(tabs + 1),
1030        t1,
1031        format_style_background_position(&r.position, tabs + 1),
1032        t1,
1033        r.size,
1034        t1,
1035        r.stops.get_hash(),
1036        t,
1037    )
1038}
1039
1040fn format_linear_color_stops(stops: &[NormalizedLinearColorStop], tabs: usize) -> String {
1041    let t = String::from("    ").repeat(tabs);
1042    stops
1043        .iter()
1044        .map(format_linear_color_stop)
1045        .collect::<Vec<_>>()
1046        .join(&format!(",\r\n{t}"))
1047}
1048
1049fn format_linear_color_stop(g: &NormalizedLinearColorStop) -> String {
1050    format!(
1051        "NormalizedLinearColorStop {{ offset: {}, color: {} }}",
1052        format_percentage_value(&g.offset),
1053        format_color_or_system(g.color),
1054    )
1055}
1056
1057fn format_radial_color_stops(stops: &[NormalizedRadialColorStop], tabs: usize) -> String {
1058    let t = String::from("    ").repeat(tabs);
1059    stops
1060        .iter()
1061        .map(format_radial_color_stop)
1062        .collect::<Vec<_>>()
1063        .join(&format!(",\r\n{t}"))
1064}
1065
1066fn format_radial_color_stop(g: &NormalizedRadialColorStop) -> String {
1067    format!(
1068        "RadialColorStop {{ angle: {}, color: {} }}",
1069        format_angle_value(&g.angle),
1070        format_color_or_system(g.color),
1071    )
1072}
1073
1074fn format_style_filter(st: &StyleFilter, tabs: usize) -> String {
1075    match st {
1076        StyleFilter::Blend(mb) => format!("StyleFilter::Blend({})", mb.format_as_rust_code(tabs)),
1077        StyleFilter::Flood(c) => format!("StyleFilter::Flood({})", format_color_value(c)),
1078        StyleFilter::Blur(m) => format!(
1079            "StyleFilter::Blur(StyleBlur {{ width: {}, height: {} }})",
1080            format_pixel_value(&m.width),
1081            format_pixel_value(&m.height)
1082        ),
1083        StyleFilter::Opacity(pct) => {
1084            format!("StyleFilter::Opacity({})", format_percentage_value(pct))
1085        }
1086        StyleFilter::ColorMatrix(cm) => format!(
1087            "StyleFilter::ColorMatrix(StyleColorMatrix {{ m0: {}, m1: {}, m2: {}, m3: {}, m4: {}, \
1088             m5: {}, m6: {}, m7: {}, m8: {}, m9: {}, m10: {}, m11: {}, m12: {}, m13: {}, m14: {}, \
1089             m15: {}, m16: {}, m17: {}, m18: {}, m19: {} }})",
1090            format_float_value(&cm.m0),
1091            format_float_value(&cm.m1),
1092            format_float_value(&cm.m2),
1093            format_float_value(&cm.m3),
1094            format_float_value(&cm.m4),
1095            format_float_value(&cm.m5),
1096            format_float_value(&cm.m6),
1097            format_float_value(&cm.m7),
1098            format_float_value(&cm.m8),
1099            format_float_value(&cm.m9),
1100            format_float_value(&cm.m10),
1101            format_float_value(&cm.m11),
1102            format_float_value(&cm.m12),
1103            format_float_value(&cm.m13),
1104            format_float_value(&cm.m14),
1105            format_float_value(&cm.m15),
1106            format_float_value(&cm.m16),
1107            format_float_value(&cm.m17),
1108            format_float_value(&cm.m18),
1109            format_float_value(&cm.m19)
1110        ),
1111        StyleFilter::DropShadow(m) => {
1112            format!("StyleFilter::DropShadow({})", m.format_as_rust_code(tabs))
1113        }
1114        StyleFilter::ComponentTransfer => "StyleFilter::ComponentTransfer".to_string(),
1115        StyleFilter::Offset(o) => format!(
1116            "StyleFilter::Offset(StyleFilterOffset {{ x: {}, y: {} }})",
1117            format_pixel_value(&o.x),
1118            format_pixel_value(&o.y)
1119        ),
1120        StyleFilter::Composite(StyleCompositeFilter::Over) => {
1121            "StyleFilter::Composite(StyleCompositeFilter::Over)".to_string()
1122        }
1123        StyleFilter::Composite(StyleCompositeFilter::In) => {
1124            "StyleFilter::Composite(StyleCompositeFilter::In)".to_string()
1125        }
1126        StyleFilter::Composite(StyleCompositeFilter::Atop) => {
1127            "StyleFilter::Composite(StyleCompositeFilter::Atop)".to_string()
1128        }
1129        StyleFilter::Composite(StyleCompositeFilter::Out) => {
1130            "StyleFilter::Composite(StyleCompositeFilter::Out)".to_string()
1131        }
1132        StyleFilter::Composite(StyleCompositeFilter::Xor) => {
1133            "StyleFilter::Composite(StyleCompositeFilter::Xor)".to_string()
1134        }
1135        StyleFilter::Composite(StyleCompositeFilter::Lighter) => {
1136            "StyleFilter::Composite(StyleCompositeFilter::Lighter)".to_string()
1137        }
1138        StyleFilter::Composite(StyleCompositeFilter::Arithmetic(fv)) => format!(
1139            "StyleFilter::Composite(StyleCompositeFilter::Arithmetic(ArithmeticCoefficients {{ \
1140             k1: {}, k2: {}, k3: {}, k4: {} }}))",
1141            format_float_value(&fv.k1),
1142            format_float_value(&fv.k2),
1143            format_float_value(&fv.k3),
1144            format_float_value(&fv.k4)
1145        ),
1146        StyleFilter::Brightness(v) => {
1147            format!("StyleFilter::Brightness({})", format_percentage_value(v))
1148        }
1149        StyleFilter::Contrast(v) => {
1150            format!("StyleFilter::Contrast({})", format_percentage_value(v))
1151        }
1152        StyleFilter::Grayscale(v) => {
1153            format!("StyleFilter::Grayscale({})", format_percentage_value(v))
1154        }
1155        StyleFilter::HueRotate(a) => format!("StyleFilter::HueRotate({})", format_angle_value(a)),
1156        StyleFilter::Invert(v) => format!("StyleFilter::Invert({})", format_percentage_value(v)),
1157        StyleFilter::Saturate(v) => {
1158            format!("StyleFilter::Saturate({})", format_percentage_value(v))
1159        }
1160        StyleFilter::Sepia(v) => format!("StyleFilter::Sepia({})", format_percentage_value(v)),
1161    }
1162}
1163
1164fn format_style_transforms(stops: &[StyleTransform], tabs: usize) -> String {
1165    let t = String::from("    ").repeat(tabs);
1166    stops
1167        .iter()
1168        .map(|s| format_style_transform(s, tabs))
1169        .collect::<Vec<_>>()
1170        .join(&format!(",\r\n{t}"))
1171}
1172
1173#[allow(clippy::too_many_lines)] // large but cohesive: single-purpose CSS parser/formatter/dispatch table (one branch per property/variant)
1174fn format_style_transform(st: &StyleTransform, tabs: usize) -> String {
1175    let tabs_minus_one = String::from("    ").repeat(tabs);
1176    let tabs = String::from("    ").repeat(tabs + 1);
1177    match st {
1178        StyleTransform::Matrix(m) => format!(
1179            "StyleTransform::Matrix(StyleTransformMatrix2D {{ a: {}, b: {}, c: {}, d: {}, tx: {}, \
1180             ty: {} }})",
1181            format_float_value(&m.a),
1182            format_float_value(&m.b),
1183            format_float_value(&m.c),
1184            format_float_value(&m.d),
1185            format_float_value(&m.tx),
1186            format_float_value(&m.ty)
1187        ),
1188        StyleTransform::Matrix3D(m) => format!(
1189            "StyleTransform::Matrix3D(StyleTransformMatrix3D {{\r\n{tabs}m11: {},\r\n{tabs}m12: \
1190             {},\r\n{tabs}m13: {},\r\n{tabs}m14: {},\r\n{tabs}m21: {},\r\n{tabs}m22: \
1191             {},\r\n{tabs}m23: {},\r\n{tabs}m24: {},\r\n{tabs}m31: {},\r\n{tabs}m32: \
1192             {},\r\n{tabs}m33: {},\r\n{tabs}m34: {},\r\n{tabs}m41: {},\r\n{tabs}m42: \
1193             {},\r\n{tabs}m43: {},\r\n{tabs}m44: {}\r\n{tabs_minus_one}}})",
1194            format_float_value(&m.m11),
1195            format_float_value(&m.m12),
1196            format_float_value(&m.m13),
1197            format_float_value(&m.m14),
1198            format_float_value(&m.m21),
1199            format_float_value(&m.m22),
1200            format_float_value(&m.m23),
1201            format_float_value(&m.m24),
1202            format_float_value(&m.m31),
1203            format_float_value(&m.m32),
1204            format_float_value(&m.m33),
1205            format_float_value(&m.m34),
1206            format_float_value(&m.m41),
1207            format_float_value(&m.m42),
1208            format_float_value(&m.m43),
1209            format_float_value(&m.m44),
1210            tabs = tabs,
1211            tabs_minus_one = tabs_minus_one,
1212        ),
1213        StyleTransform::Translate(t) => format!(
1214            "StyleTransform::Translate(StyleTransformTranslate2D {{ x: {}, y: {} }})",
1215            format_pixel_value(&t.x),
1216            format_pixel_value(&t.y)
1217        ),
1218        StyleTransform::Translate3D(t) => format!(
1219            "StyleTransform::Translate3D(StyleTransformTranslate3D {{ x: {}, y: {}, z: {} }})",
1220            format_pixel_value(&t.x),
1221            format_pixel_value(&t.y),
1222            format_pixel_value(&t.z)
1223        ),
1224        StyleTransform::TranslateX(x) => {
1225            format!("StyleTransform::TranslateX({})", format_pixel_value(x))
1226        }
1227        StyleTransform::TranslateY(y) => {
1228            format!("StyleTransform::TranslateY({})", format_pixel_value(y))
1229        }
1230        StyleTransform::TranslateZ(z) => {
1231            format!("StyleTransform::TranslateZ({})", format_pixel_value(z))
1232        }
1233        StyleTransform::Rotate(r) => format!("StyleTransform::Rotate({})", format_angle_value(r)),
1234        StyleTransform::Rotate3D(r) => format!(
1235            "StyleTransform::Rotate3D(StyleTransformRotate3D {{ {}, {}, {}, {} }})",
1236            format_float_value(&r.x),
1237            format_float_value(&r.y),
1238            format_float_value(&r.z),
1239            format_angle_value(&r.angle)
1240        ),
1241        StyleTransform::RotateX(x) => {
1242            format!("StyleTransform::RotateX({})", format_angle_value(x))
1243        }
1244        StyleTransform::RotateY(y) => {
1245            format!("StyleTransform::RotateY({})", format_angle_value(y))
1246        }
1247        StyleTransform::RotateZ(z) => {
1248            format!("StyleTransform::RotateZ({})", format_angle_value(z))
1249        }
1250        StyleTransform::Scale(s) => format!(
1251            "StyleTransform::Scale(StyleTransformScale2D {{ x: {}, y: {} }})",
1252            format_float_value(&s.x),
1253            format_float_value(&s.y)
1254        ),
1255        StyleTransform::Scale3D(s) => format!(
1256            "StyleTransform::Scale3D(StyleTransformScale3D {{ x: {}, y: {}, z: {} }})",
1257            format_float_value(&s.x),
1258            format_float_value(&s.y),
1259            format_float_value(&s.z)
1260        ),
1261        StyleTransform::ScaleX(x) => {
1262            format!("StyleTransform::ScaleX({})", format_percentage_value(x))
1263        }
1264        StyleTransform::ScaleY(y) => {
1265            format!("StyleTransform::ScaleY({})", format_percentage_value(y))
1266        }
1267        StyleTransform::ScaleZ(z) => {
1268            format!("StyleTransform::ScaleZ({})", format_percentage_value(z))
1269        }
1270        StyleTransform::Skew(sk) => format!(
1271            "StyleTransform::Skew(StyleTransformSkew2D {{ x: {}, y: {} }})",
1272            format_angle_value(&sk.x),
1273            format_angle_value(&sk.y)
1274        ),
1275        StyleTransform::SkewX(x) => {
1276            format!("StyleTransform::SkewX({})", format_angle_value(x))
1277        }
1278        StyleTransform::SkewY(y) => {
1279            format!("StyleTransform::SkewY({})", format_angle_value(y))
1280        }
1281        StyleTransform::Perspective(dist) => {
1282            format!("StyleTransform::Perspective({})", format_pixel_value(dist))
1283        }
1284    }
1285}
1286
1287fn format_font_ids(font_ids: &[StyleFontFamily], tabs: usize) -> String {
1288    let t = String::from("    ").repeat(tabs);
1289    font_ids
1290        .iter()
1291        .map(|s| s.format_as_rust_code(tabs + 1))
1292        .collect::<Vec<_>>()
1293        .join(&format!(",\r\n{t}"))
1294}
1295
1296fn format_style_background_position(b: &StyleBackgroundPosition, tabs: usize) -> String {
1297    let t = String::from("    ").repeat(tabs);
1298    let t1 = String::from("    ").repeat(tabs + 1);
1299    format!(
1300        "StyleBackgroundPosition {{\r\n{}horizontal: {},\r\n{}vertical: {},\r\n{}}}",
1301        t1,
1302        format_background_position_horizontal(&b.horizontal),
1303        t1,
1304        format_background_position_vertical(&b.vertical),
1305        t
1306    )
1307}
1308
1309fn format_background_position_horizontal(b: &BackgroundPositionHorizontal) -> String {
1310    match b {
1311        BackgroundPositionHorizontal::Left => "BackgroundPositionHorizontal::Left".to_string(),
1312        BackgroundPositionHorizontal::Center => "BackgroundPositionHorizontal::Center".to_string(),
1313        BackgroundPositionHorizontal::Right => "BackgroundPositionHorizontal::Right".to_string(),
1314        BackgroundPositionHorizontal::Exact(p) => format!(
1315            "BackgroundPositionHorizontal::Exact({})",
1316            format_pixel_value(p)
1317        ),
1318    }
1319}
1320
1321fn format_background_position_vertical(b: &BackgroundPositionVertical) -> String {
1322    match b {
1323        BackgroundPositionVertical::Top => "BackgroundPositionVertical::Top".to_string(),
1324        BackgroundPositionVertical::Center => "BackgroundPositionVertical::Center".to_string(),
1325        BackgroundPositionVertical::Bottom => "BackgroundPositionVertical::Bottom".to_string(),
1326        BackgroundPositionVertical::Exact(p) => format!(
1327            "BackgroundPositionVertical::Exact({})",
1328            format_pixel_value(p)
1329        ),
1330    }
1331}
1332
1333// Border side style impls moved to `props/style/border.rs`.
1334
1335// Several small impls were moved to their respective modules.
1336
1337// StyleTransformOrigin impl moved to `props/style/transform.rs`.
1338
1339// Implementations for small property types were moved to their respective
1340// modules to keep the implementations next to the type definitions.