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!(TextBoxEdgeOver, Auto, Text, Cap, Ex, Ideographic, IdeographicInk);
743impl_enum_fmt!(TextBoxEdgeUnder, Auto, Text, Alphabetic, Ideographic, IdeographicInk);
744impl FormatAsRustCode for StyleTextBoxEdge {
745    fn format_as_rust_code(&self, tabs: usize) -> String {
746        format!(
747            "StyleTextBoxEdge {{ over: {}, under: {} }}",
748            self.over.format_as_rust_code(tabs),
749            self.under.format_as_rust_code(tabs)
750        )
751    }
752}
753impl_enum_fmt!(StyleDominantBaseline, Auto, TextBottom, Alphabetic, Ideographic, Middle, Central, Mathematical, Hanging, TextTop);
754impl_enum_fmt!(StyleAlignmentBaseline, Baseline, TextBottom, Alphabetic, Ideographic, Middle, Central, Mathematical, TextTop);
755impl_enum_fmt!(StyleBaselineSource, Auto, First, Last);
756impl_enum_fmt!(StyleLineFitEdge, Leading, Text, Cap, Ex, Ideographic, IdeographicInk, Alphabetic);
757impl_enum_fmt!(StyleInitialLetterAlign, Auto, Alphabetic, Hanging, Ideographic);
758impl_enum_fmt!(StyleInitialLetterWrap, None, First, All, Grid);
759impl_enum_fmt!(StyleScrollbarGutter, Auto, Stable, StableBothEdges);
760
761impl FormatAsRustCode for StyleOverflowClipMargin {
762    fn format_as_rust_code(&self, tabs: usize) -> String {
763        format!("StyleOverflowClipMargin {{ inner: {} }}", self.inner.format_as_rust_code(tabs))
764    }
765}
766
767impl FormatAsRustCode for StyleClipRect {
768    fn format_as_rust_code(&self, _tabs: usize) -> String {
769        fn fmt_edge(o: OptionF32) -> String {
770            match o {
771                OptionF32::None => String::from("OptionF32::None"),
772                OptionF32::Some(v) => format!("OptionF32::Some({v:?})"),
773            }
774        }
775        format!(
776            "StyleClipRect {{ top: {}, right: {}, bottom: {}, left: {} }}",
777            fmt_edge(self.top),
778            fmt_edge(self.right),
779            fmt_edge(self.bottom),
780            fmt_edge(self.left),
781        )
782    }
783}
784
785// Complex type implementations
786
787fn format_style_background_size(c: &StyleBackgroundSize) -> String {
788    match c {
789        StyleBackgroundSize::Contain => String::from("StyleBackgroundSize::Contain"),
790        StyleBackgroundSize::Cover => String::from("StyleBackgroundSize::Cover"),
791        StyleBackgroundSize::ExactSize(size) => format!(
792            "StyleBackgroundSize::ExactSize(PixelValueSize {{ width: {}, height: {} }})",
793            format_pixel_value(&size.width),
794            format_pixel_value(&size.height)
795        ),
796    }
797}
798
799// Scrollbar-related impls moved to `props/style/scrollbar.rs`
800
801/// Formats a `ScrollbarInfo` as a const-compatible Rust struct literal.
802#[must_use] pub fn format_scrollbar_info(s: &ScrollbarInfo, tabs: usize) -> String {
803    let t = String::from("    ").repeat(tabs);
804    let t1 = String::from("    ").repeat(tabs + 1);
805    format!(
806        "ScrollbarInfo {{\r\n{}width: {},\r\n{}padding_left: {},\r\n{}padding_right: \
807         {},\r\n{}track: {},\r\n{}thumb: {},\r\n{}button: {},\r\n{}button: {},\r\n{}resizer: \
808         {},\r\n{}}}",
809        t1,
810        s.width.format_as_rust_code(tabs + 1),
811        t1,
812        s.padding_left.format_as_rust_code(tabs + 1),
813        t1,
814        s.padding_right.format_as_rust_code(tabs + 1),
815        t1,
816        format_style_background_content(&s.track, tabs + 1),
817        t1,
818        format_style_background_content(&s.thumb, tabs + 1),
819        t1,
820        format_style_background_content(&s.button, tabs + 1),
821        t1,
822        format_style_background_content(&s.corner, tabs + 1),
823        t1,
824        format_style_background_content(&s.resizer, tabs + 1),
825        t
826    )
827}
828
829// Background/filter vec impls moved to their respective modules.
830
831fn format_style_background_content(content: &StyleBackgroundContent, tabs: usize) -> String {
832    match content {
833        StyleBackgroundContent::LinearGradient(l) => format!(
834            "StyleBackgroundContent::LinearGradient({})",
835            format_linear_gradient(l, tabs)
836        ),
837        StyleBackgroundContent::RadialGradient(r) => format!(
838            "StyleBackgroundContent::RadialGradient({})",
839            format_radial_gradient(r, tabs)
840        ),
841        StyleBackgroundContent::ConicGradient(r) => format!(
842            "StyleBackgroundContent::ConicGradient({})",
843            format_conic_gradient(r, tabs)
844        ),
845        StyleBackgroundContent::Image(id) => format!("StyleBackgroundContent::Image({id:?})"),
846        StyleBackgroundContent::Color(c) => {
847            format!("StyleBackgroundContent::Color({})", format_color_value(c))
848        }
849        StyleBackgroundContent::SystemColor(s) => {
850            use crate::props::basic::color::SystemColorRef;
851            let variant = match s {
852                SystemColorRef::Text => "Text",
853                SystemColorRef::Background => "Background",
854                SystemColorRef::Accent => "Accent",
855                SystemColorRef::AccentText => "AccentText",
856                SystemColorRef::ButtonFace => "ButtonFace",
857                SystemColorRef::ButtonText => "ButtonText",
858                SystemColorRef::WindowBackground => "WindowBackground",
859                SystemColorRef::SelectionBackground => "SelectionBackground",
860                SystemColorRef::SelectionText => "SelectionText",
861            };
862            format!("StyleBackgroundContent::SystemColor(SystemColorRef::{variant})")
863        }
864    }
865}
866
867fn format_direction(d: &Direction, tabs: usize) -> String {
868    match d {
869        Direction::Angle(fv) => format!("Direction::Angle({})", format_angle_value(fv)),
870        Direction::FromTo(DirectionCorners { dir_from, dir_to }) => format!(
871            "Direction::FromTo(DirectionCorners {{ dir_from: {}, dir_to: {} }})",
872            dir_from.format_as_rust_code(tabs + 1),
873            dir_to.format_as_rust_code(tabs + 1)
874        ),
875    }
876}
877
878fn format_linear_gradient(l: &LinearGradient, tabs: usize) -> String {
879    let t = String::from("    ").repeat(tabs);
880    let t1 = String::from("    ").repeat(tabs + 1);
881    format!(
882        "LinearGradient {{\r\n{}direction: {},\r\n{}extend_mode: {},\r\n{}stops: \
883         NormalizedLinearColorStopVec::from_const_slice(LINEAR_COLOR_STOP_{}_ITEMS),\r\n{}}}",
884        t1,
885        format_direction(&l.direction, tabs + 1),
886        t1,
887        l.extend_mode.format_as_rust_code(tabs + 1),
888        t1,
889        l.stops.get_hash(),
890        t,
891    )
892}
893
894fn format_conic_gradient(r: &ConicGradient, tabs: usize) -> String {
895    let t = String::from("    ").repeat(tabs);
896    let t1 = String::from("    ").repeat(tabs + 1);
897
898    format!(
899        "ConicGradient {{\r\n{}extend_mode: {},\r\n{}center: {},\r\n{}angle: {},\r\n{}stops: \
900         NormalizedRadialColorStopVec::from_const_slice(RADIAL_COLOR_STOP_{}_ITEMS),\r\n{}}}",
901        t1,
902        r.extend_mode.format_as_rust_code(tabs + 1),
903        t1,
904        format_style_background_position(&r.center, tabs + 1),
905        t1,
906        format_angle_value(&r.angle),
907        t1,
908        r.stops.get_hash(),
909        t,
910    )
911}
912
913fn format_radial_gradient(r: &RadialGradient, tabs: usize) -> String {
914    let t = String::from("    ").repeat(tabs);
915    let t1 = String::from("    ").repeat(tabs + 1);
916    format!(
917        "RadialGradient {{\r\n{}shape: {},\r\n{}extend_mode: {},\r\n{}position: {},\r\n{}size: \
918         RadialGradientSize::{:?},\r\n{}stops: \
919         NormalizedLinearColorStopVec::from_const_slice(LINEAR_COLOR_STOP_{}_ITEMS),\r\n{}}}",
920        t1,
921        r.shape.format_as_rust_code(tabs + 1),
922        t1,
923        r.extend_mode.format_as_rust_code(tabs + 1),
924        t1,
925        format_style_background_position(&r.position, tabs + 1),
926        t1,
927        r.size,
928        t1,
929        r.stops.get_hash(),
930        t,
931    )
932}
933
934fn format_linear_color_stops(stops: &[NormalizedLinearColorStop], tabs: usize) -> String {
935    let t = String::from("    ").repeat(tabs);
936    stops
937        .iter()
938        .map(format_linear_color_stop)
939        .collect::<Vec<_>>()
940        .join(&format!(",\r\n{t}"))
941}
942
943fn format_linear_color_stop(g: &NormalizedLinearColorStop) -> String {
944    format!(
945        "NormalizedLinearColorStop {{ offset: {}, color: {} }}",
946        format_percentage_value(&g.offset),
947        format_color_or_system(g.color),
948    )
949}
950
951fn format_radial_color_stops(stops: &[NormalizedRadialColorStop], tabs: usize) -> String {
952    let t = String::from("    ").repeat(tabs);
953    stops
954        .iter()
955        .map(format_radial_color_stop)
956        .collect::<Vec<_>>()
957        .join(&format!(",\r\n{t}"))
958}
959
960fn format_radial_color_stop(g: &NormalizedRadialColorStop) -> String {
961    format!(
962        "RadialColorStop {{ angle: {}, color: {} }}",
963        format_angle_value(&g.angle),
964        format_color_or_system(g.color),
965    )
966}
967
968fn format_style_filter(st: &StyleFilter, tabs: usize) -> String {
969    match st {
970        StyleFilter::Blend(mb) => format!("StyleFilter::Blend({})", mb.format_as_rust_code(tabs)),
971        StyleFilter::Flood(c) => format!("StyleFilter::Flood({})", format_color_value(c)),
972        StyleFilter::Blur(m) => format!(
973            "StyleFilter::Blur(StyleBlur {{ width: {}, height: {} }})",
974            format_pixel_value(&m.width),
975            format_pixel_value(&m.height)
976        ),
977        StyleFilter::Opacity(pct) => {
978            format!("StyleFilter::Opacity({})", format_percentage_value(pct))
979        }
980        StyleFilter::ColorMatrix(cm) => format!(
981            "StyleFilter::ColorMatrix(StyleColorMatrix {{ m0: {}, m1: {}, m2: {}, m3: {}, m4: {}, \
982             m5: {}, m6: {}, m7: {}, m8: {}, m9: {}, m10: {}, m11: {}, m12: {}, m13: {}, m14: {}, \
983             m15: {}, m16: {}, m17: {}, m18: {}, m19: {} }})",
984            format_float_value(&cm.m0),
985            format_float_value(&cm.m1),
986            format_float_value(&cm.m2),
987            format_float_value(&cm.m3),
988            format_float_value(&cm.m4),
989            format_float_value(&cm.m5),
990            format_float_value(&cm.m6),
991            format_float_value(&cm.m7),
992            format_float_value(&cm.m8),
993            format_float_value(&cm.m9),
994            format_float_value(&cm.m10),
995            format_float_value(&cm.m11),
996            format_float_value(&cm.m12),
997            format_float_value(&cm.m13),
998            format_float_value(&cm.m14),
999            format_float_value(&cm.m15),
1000            format_float_value(&cm.m16),
1001            format_float_value(&cm.m17),
1002            format_float_value(&cm.m18),
1003            format_float_value(&cm.m19)
1004        ),
1005        StyleFilter::DropShadow(m) => {
1006            format!("StyleFilter::DropShadow({})", m.format_as_rust_code(tabs))
1007        }
1008        StyleFilter::ComponentTransfer => "StyleFilter::ComponentTransfer".to_string(),
1009        StyleFilter::Offset(o) => format!(
1010            "StyleFilter::Offset(StyleFilterOffset {{ x: {}, y: {} }})",
1011            format_pixel_value(&o.x),
1012            format_pixel_value(&o.y)
1013        ),
1014        StyleFilter::Composite(StyleCompositeFilter::Over) => {
1015            "StyleFilter::Composite(StyleCompositeFilter::Over)".to_string()
1016        }
1017        StyleFilter::Composite(StyleCompositeFilter::In) => {
1018            "StyleFilter::Composite(StyleCompositeFilter::In)".to_string()
1019        }
1020        StyleFilter::Composite(StyleCompositeFilter::Atop) => {
1021            "StyleFilter::Composite(StyleCompositeFilter::Atop)".to_string()
1022        }
1023        StyleFilter::Composite(StyleCompositeFilter::Out) => {
1024            "StyleFilter::Composite(StyleCompositeFilter::Out)".to_string()
1025        }
1026        StyleFilter::Composite(StyleCompositeFilter::Xor) => {
1027            "StyleFilter::Composite(StyleCompositeFilter::Xor)".to_string()
1028        }
1029        StyleFilter::Composite(StyleCompositeFilter::Lighter) => {
1030            "StyleFilter::Composite(StyleCompositeFilter::Lighter)".to_string()
1031        }
1032        StyleFilter::Composite(StyleCompositeFilter::Arithmetic(fv)) => format!(
1033            "StyleFilter::Composite(StyleCompositeFilter::Arithmetic(ArithmeticCoefficients {{ \
1034             k1: {}, k2: {}, k3: {}, k4: {} }}))",
1035            format_float_value(&fv.k1),
1036            format_float_value(&fv.k2),
1037            format_float_value(&fv.k3),
1038            format_float_value(&fv.k4)
1039        ),
1040        StyleFilter::Brightness(v) => format!("StyleFilter::Brightness({})", format_percentage_value(v)),
1041        StyleFilter::Contrast(v) => format!("StyleFilter::Contrast({})", format_percentage_value(v)),
1042        StyleFilter::Grayscale(v) => format!("StyleFilter::Grayscale({})", format_percentage_value(v)),
1043        StyleFilter::HueRotate(a) => format!("StyleFilter::HueRotate({})", format_angle_value(a)),
1044        StyleFilter::Invert(v) => format!("StyleFilter::Invert({})", format_percentage_value(v)),
1045        StyleFilter::Saturate(v) => format!("StyleFilter::Saturate({})", format_percentage_value(v)),
1046        StyleFilter::Sepia(v) => format!("StyleFilter::Sepia({})", format_percentage_value(v)),
1047    }
1048}
1049
1050fn format_style_transforms(stops: &[StyleTransform], tabs: usize) -> String {
1051    let t = String::from("    ").repeat(tabs);
1052    stops
1053        .iter()
1054        .map(|s| format_style_transform(s, tabs))
1055        .collect::<Vec<_>>()
1056        .join(&format!(",\r\n{t}"))
1057}
1058
1059#[allow(clippy::too_many_lines)] // large but cohesive: single-purpose CSS parser/formatter/dispatch table (one branch per property/variant)
1060fn format_style_transform(st: &StyleTransform, tabs: usize) -> String {
1061    let tabs_minus_one = String::from("    ").repeat(tabs);
1062    let tabs = String::from("    ").repeat(tabs + 1);
1063    match st {
1064        StyleTransform::Matrix(m) => format!(
1065            "StyleTransform::Matrix(StyleTransformMatrix2D {{ a: {}, b: {}, c: {}, d: {}, tx: {}, \
1066             ty: {} }})",
1067            format_float_value(&m.a),
1068            format_float_value(&m.b),
1069            format_float_value(&m.c),
1070            format_float_value(&m.d),
1071            format_float_value(&m.tx),
1072            format_float_value(&m.ty)
1073        ),
1074        StyleTransform::Matrix3D(m) => format!(
1075            "StyleTransform::Matrix3D(StyleTransformMatrix3D {{\r\n{tabs}m11: {},\r\n{tabs}m12: \
1076             {},\r\n{tabs}m13: {},\r\n{tabs}m14: {},\r\n{tabs}m21: {},\r\n{tabs}m22: \
1077             {},\r\n{tabs}m23: {},\r\n{tabs}m24: {},\r\n{tabs}m31: {},\r\n{tabs}m32: \
1078             {},\r\n{tabs}m33: {},\r\n{tabs}m34: {},\r\n{tabs}m41: {},\r\n{tabs}m42: \
1079             {},\r\n{tabs}m43: {},\r\n{tabs}m44: {}\r\n{tabs_minus_one}}})",
1080            format_float_value(&m.m11),
1081            format_float_value(&m.m12),
1082            format_float_value(&m.m13),
1083            format_float_value(&m.m14),
1084            format_float_value(&m.m21),
1085            format_float_value(&m.m22),
1086            format_float_value(&m.m23),
1087            format_float_value(&m.m24),
1088            format_float_value(&m.m31),
1089            format_float_value(&m.m32),
1090            format_float_value(&m.m33),
1091            format_float_value(&m.m34),
1092            format_float_value(&m.m41),
1093            format_float_value(&m.m42),
1094            format_float_value(&m.m43),
1095            format_float_value(&m.m44),
1096            tabs = tabs,
1097            tabs_minus_one = tabs_minus_one,
1098        ),
1099        StyleTransform::Translate(t) => format!(
1100            "StyleTransform::Translate(StyleTransformTranslate2D {{ x: {}, y: {} }})",
1101            format_pixel_value(&t.x),
1102            format_pixel_value(&t.y)
1103        ),
1104        StyleTransform::Translate3D(t) => format!(
1105            "StyleTransform::Translate3D(StyleTransformTranslate3D {{ x: {}, y: {}, z: {} }})",
1106            format_pixel_value(&t.x),
1107            format_pixel_value(&t.y),
1108            format_pixel_value(&t.z)
1109        ),
1110        StyleTransform::TranslateX(x) => {
1111            format!("StyleTransform::TranslateX({})", format_pixel_value(x))
1112        }
1113        StyleTransform::TranslateY(y) => {
1114            format!("StyleTransform::TranslateY({})", format_pixel_value(y))
1115        }
1116        StyleTransform::TranslateZ(z) => {
1117            format!("StyleTransform::TranslateZ({})", format_pixel_value(z))
1118        }
1119        StyleTransform::Rotate(r) => format!("StyleTransform::Rotate({})", format_angle_value(r)),
1120        StyleTransform::Rotate3D(r) => format!(
1121            "StyleTransform::Rotate3D(StyleTransformRotate3D {{ {}, {}, {}, {} }})",
1122            format_float_value(&r.x),
1123            format_float_value(&r.y),
1124            format_float_value(&r.z),
1125            format_angle_value(&r.angle)
1126        ),
1127        StyleTransform::RotateX(x) => {
1128            format!("StyleTransform::RotateX({})", format_angle_value(x))
1129        }
1130        StyleTransform::RotateY(y) => {
1131            format!("StyleTransform::RotateY({})", format_angle_value(y))
1132        }
1133        StyleTransform::RotateZ(z) => {
1134            format!("StyleTransform::RotateZ({})", format_angle_value(z))
1135        }
1136        StyleTransform::Scale(s) => format!(
1137            "StyleTransform::Scale(StyleTransformScale2D {{ x: {}, y: {} }})",
1138            format_float_value(&s.x),
1139            format_float_value(&s.y)
1140        ),
1141        StyleTransform::Scale3D(s) => format!(
1142            "StyleTransform::Scale3D(StyleTransformScale3D {{ x: {}, y: {}, z: {} }})",
1143            format_float_value(&s.x),
1144            format_float_value(&s.y),
1145            format_float_value(&s.z)
1146        ),
1147        StyleTransform::ScaleX(x) => {
1148            format!("StyleTransform::ScaleX({})", format_percentage_value(x))
1149        }
1150        StyleTransform::ScaleY(y) => {
1151            format!("StyleTransform::ScaleY({})", format_percentage_value(y))
1152        }
1153        StyleTransform::ScaleZ(z) => {
1154            format!("StyleTransform::ScaleZ({})", format_percentage_value(z))
1155        }
1156        StyleTransform::Skew(sk) => format!(
1157            "StyleTransform::Skew(StyleTransformSkew2D {{ x: {}, y: {} }})",
1158            format_angle_value(&sk.x),
1159            format_angle_value(&sk.y)
1160        ),
1161        StyleTransform::SkewX(x) => {
1162            format!("StyleTransform::SkewX({})", format_angle_value(x))
1163        }
1164        StyleTransform::SkewY(y) => {
1165            format!("StyleTransform::SkewY({})", format_angle_value(y))
1166        }
1167        StyleTransform::Perspective(dist) => {
1168            format!("StyleTransform::Perspective({})", format_pixel_value(dist))
1169        }
1170    }
1171}
1172
1173fn format_font_ids(font_ids: &[StyleFontFamily], tabs: usize) -> String {
1174    let t = String::from("    ").repeat(tabs);
1175    font_ids
1176        .iter()
1177        .map(|s| s.format_as_rust_code(tabs + 1))
1178        .collect::<Vec<_>>()
1179        .join(&format!(",\r\n{t}"))
1180}
1181
1182fn format_style_background_position(b: &StyleBackgroundPosition, tabs: usize) -> String {
1183    let t = String::from("    ").repeat(tabs);
1184    let t1 = String::from("    ").repeat(tabs + 1);
1185    format!(
1186        "StyleBackgroundPosition {{\r\n{}horizontal: {},\r\n{}vertical: {},\r\n{}}}",
1187        t1,
1188        format_background_position_horizontal(&b.horizontal),
1189        t1,
1190        format_background_position_vertical(&b.vertical),
1191        t
1192    )
1193}
1194
1195fn format_background_position_horizontal(b: &BackgroundPositionHorizontal) -> String {
1196    match b {
1197        BackgroundPositionHorizontal::Left => "BackgroundPositionHorizontal::Left".to_string(),
1198        BackgroundPositionHorizontal::Center => "BackgroundPositionHorizontal::Center".to_string(),
1199        BackgroundPositionHorizontal::Right => "BackgroundPositionHorizontal::Right".to_string(),
1200        BackgroundPositionHorizontal::Exact(p) => format!(
1201            "BackgroundPositionHorizontal::Exact({})",
1202            format_pixel_value(p)
1203        ),
1204    }
1205}
1206
1207fn format_background_position_vertical(b: &BackgroundPositionVertical) -> String {
1208    match b {
1209        BackgroundPositionVertical::Top => "BackgroundPositionVertical::Top".to_string(),
1210        BackgroundPositionVertical::Center => "BackgroundPositionVertical::Center".to_string(),
1211        BackgroundPositionVertical::Bottom => "BackgroundPositionVertical::Bottom".to_string(),
1212        BackgroundPositionVertical::Exact(p) => format!(
1213            "BackgroundPositionVertical::Exact({})",
1214            format_pixel_value(p)
1215        ),
1216    }
1217}
1218
1219// Border side style impls moved to `props/style/border.rs`.
1220
1221// Several small impls were moved to their respective modules.
1222
1223// StyleTransformOrigin impl moved to `props/style/transform.rs`.
1224
1225// Implementations for small property types were moved to their respective
1226// modules to keep the implementations next to the type definitions.