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