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);
565
566impl FormatAsRustCode for StyleObjectPosition {
567    fn format_as_rust_code(&self, _tabs: usize) -> String {
568        format!("StyleObjectPosition {{ horizontal: {}, vertical: {} }}",
569            format_background_position_horizontal(&self.horizontal),
570            format_background_position_vertical(&self.vertical))
571    }
572}
573
574impl FormatAsRustCode for StyleAspectRatio {
575    fn format_as_rust_code(&self, _tabs: usize) -> String {
576        match self {
577            Self::Auto => "StyleAspectRatio::Auto".to_string(),
578            Self::Ratio(r) => format!("StyleAspectRatio::Ratio(AspectRatioValue {{ width: {}, height: {} }})", r.width, r.height),
579        }
580    }
581}
582
583impl_enum_fmt!(StyleTextOrientation, Mixed, Upright, Sideways);
584impl_enum_fmt!(StyleTextAlignLast, Auto, Start, End, Left, Right, Center, Justify);
585
586impl_enum_fmt!(StyleTextTransform, None, Capitalize, Uppercase, Lowercase, FullWidth);
587
588impl_enum_fmt!(StyleDirection, Ltr, Rtl);
589
590impl_enum_fmt!(StyleWhiteSpace, Normal, Pre, Nowrap, PreWrap, PreLine, BreakSpaces);
591
592impl_enum_fmt!(StyleVisibility, Visible, Hidden, Collapse);
593
594impl_enum_fmt!(LayoutWritingMode, HorizontalTb, VerticalRl, VerticalLr);
595
596impl_enum_fmt!(LayoutClear, None, Left, Right, Both);
597
598impl_enum_fmt!(
599    StyleCursor,
600    Alias,
601    AllScroll,
602    Cell,
603    ColResize,
604    ContextMenu,
605    Copy,
606    Crosshair,
607    Default,
608    EResize,
609    EwResize,
610    Grab,
611    Grabbing,
612    Help,
613    Move,
614    NResize,
615    NsResize,
616    NeswResize,
617    NwseResize,
618    Pointer,
619    Progress,
620    RowResize,
621    SResize,
622    SeResize,
623    Text,
624    Unset,
625    VerticalText,
626    WResize,
627    Wait,
628    ZoomIn,
629    ZoomOut
630);
631
632impl_enum_fmt!(
633    BorderStyle,
634    None,
635    Solid,
636    Double,
637    Dotted,
638    Dashed,
639    Hidden,
640    Groove,
641    Ridge,
642    Inset,
643    Outset
644);
645
646impl_enum_fmt!(
647    StyleBackgroundRepeat,
648    NoRepeat,
649    PatternRepeat,
650    RepeatX,
651    RepeatY
652);
653
654impl_enum_fmt!(
655    LayoutDisplay,
656    None,
657    Block,
658    Inline,
659    InlineBlock,
660    Flex,
661    InlineFlex,
662    Table,
663    InlineTable,
664    TableRowGroup,
665    TableHeaderGroup,
666    TableFooterGroup,
667    TableRow,
668    TableColumnGroup,
669    TableColumn,
670    TableCell,
671    TableCaption,
672    ListItem,
673    RunIn,
674    Marker,
675    Grid,
676    InlineGrid,
677    FlowRoot,
678    Contents
679);
680
681impl_enum_fmt!(LayoutFloat, Left, Right, None);
682
683impl_enum_fmt!(LayoutBoxSizing, ContentBox, BorderBox);
684
685impl_enum_fmt!(LayoutFlexDirection, Row, RowReverse, Column, ColumnReverse);
686
687impl_enum_fmt!(LayoutFlexWrap, Wrap, NoWrap, WrapReverse);
688
689impl_enum_fmt!(
690    LayoutJustifyContent,
691    Start,
692    End,
693    FlexStart,
694    FlexEnd,
695    Center,
696    SpaceBetween,
697    SpaceAround,
698    SpaceEvenly
699);
700
701impl_enum_fmt!(LayoutAlignItems, Stretch, Center, Start, End, Baseline);
702
703impl_enum_fmt!(
704    LayoutAlignContent,
705    Start,
706    End,
707    Stretch,
708    Center,
709    SpaceBetween,
710    SpaceAround
711);
712
713impl_enum_fmt!(Shape, Circle, Ellipse);
714
715impl_enum_fmt!(LayoutOverflow, Auto, Scroll, Visible, Hidden, Clip);
716
717impl_enum_fmt!(StyleTextAlign, Center, Left, Right, Justify, Start, End);
718
719impl_enum_fmt!(StyleUserSelect, Auto, Text, None, All);
720
721impl_enum_fmt!(StyleTextDecoration, None, Underline, Overline, LineThrough);
722
723impl_enum_fmt!(
724    DirectionCorner,
725    Right,
726    Left,
727    Top,
728    Bottom,
729    TopRight,
730    TopLeft,
731    BottomRight,
732    BottomLeft
733);
734
735impl_enum_fmt!(ExtendMode, Clamp, Repeat);
736
737impl_enum_fmt!(StyleBackfaceVisibility, Visible, Hidden);
738
739impl_enum_fmt!(StyleUnicodeBidi, Normal, Embed, Isolate, BidiOverride, IsolateOverride, Plaintext);
740impl_enum_fmt!(StyleTextBoxTrim, None, TrimStart, TrimEnd, TrimBoth);
741impl_enum_fmt!(StyleTextBoxEdge, Auto, TextEdge, CapHeight, ExHeight);
742impl_enum_fmt!(StyleDominantBaseline, Auto, TextBottom, Alphabetic, Ideographic, Middle, Central, Mathematical, Hanging, TextTop);
743impl_enum_fmt!(StyleAlignmentBaseline, Baseline, TextBottom, Alphabetic, Ideographic, Middle, Central, Mathematical, TextTop);
744impl_enum_fmt!(StyleInitialLetterAlign, Auto, Alphabetic, Hanging, Ideographic);
745impl_enum_fmt!(StyleInitialLetterWrap, None, First, All, Grid);
746impl_enum_fmt!(StyleScrollbarGutter, Auto, Stable, StableBothEdges);
747
748impl FormatAsRustCode for StyleOverflowClipMargin {
749    fn format_as_rust_code(&self, tabs: usize) -> String {
750        format!("StyleOverflowClipMargin {{ inner: {} }}", self.inner.format_as_rust_code(tabs))
751    }
752}
753
754impl FormatAsRustCode for StyleClipRect {
755    fn format_as_rust_code(&self, _tabs: usize) -> String {
756        fn fmt_edge(o: OptionF32) -> String {
757            match o {
758                OptionF32::None => String::from("OptionF32::None"),
759                OptionF32::Some(v) => format!("OptionF32::Some({v:?})"),
760            }
761        }
762        format!(
763            "StyleClipRect {{ top: {}, right: {}, bottom: {}, left: {} }}",
764            fmt_edge(self.top),
765            fmt_edge(self.right),
766            fmt_edge(self.bottom),
767            fmt_edge(self.left),
768        )
769    }
770}
771
772// Complex type implementations
773
774fn format_style_background_size(c: &StyleBackgroundSize) -> String {
775    match c {
776        StyleBackgroundSize::Contain => String::from("StyleBackgroundSize::Contain"),
777        StyleBackgroundSize::Cover => String::from("StyleBackgroundSize::Cover"),
778        StyleBackgroundSize::ExactSize(size) => format!(
779            "StyleBackgroundSize::ExactSize(PixelValueSize {{ width: {}, height: {} }})",
780            format_pixel_value(&size.width),
781            format_pixel_value(&size.height)
782        ),
783    }
784}
785
786// Scrollbar-related impls moved to `props/style/scrollbar.rs`
787
788/// Formats a `ScrollbarInfo` as a const-compatible Rust struct literal.
789#[must_use] pub fn format_scrollbar_info(s: &ScrollbarInfo, tabs: usize) -> String {
790    let t = String::from("    ").repeat(tabs);
791    let t1 = String::from("    ").repeat(tabs + 1);
792    format!(
793        "ScrollbarInfo {{\r\n{}width: {},\r\n{}padding_left: {},\r\n{}padding_right: \
794         {},\r\n{}track: {},\r\n{}thumb: {},\r\n{}button: {},\r\n{}button: {},\r\n{}resizer: \
795         {},\r\n{}}}",
796        t1,
797        s.width.format_as_rust_code(tabs + 1),
798        t1,
799        s.padding_left.format_as_rust_code(tabs + 1),
800        t1,
801        s.padding_right.format_as_rust_code(tabs + 1),
802        t1,
803        format_style_background_content(&s.track, tabs + 1),
804        t1,
805        format_style_background_content(&s.thumb, tabs + 1),
806        t1,
807        format_style_background_content(&s.button, tabs + 1),
808        t1,
809        format_style_background_content(&s.corner, tabs + 1),
810        t1,
811        format_style_background_content(&s.resizer, tabs + 1),
812        t
813    )
814}
815
816// Background/filter vec impls moved to their respective modules.
817
818fn format_style_background_content(content: &StyleBackgroundContent, tabs: usize) -> String {
819    match content {
820        StyleBackgroundContent::LinearGradient(l) => format!(
821            "StyleBackgroundContent::LinearGradient({})",
822            format_linear_gradient(l, tabs)
823        ),
824        StyleBackgroundContent::RadialGradient(r) => format!(
825            "StyleBackgroundContent::RadialGradient({})",
826            format_radial_gradient(r, tabs)
827        ),
828        StyleBackgroundContent::ConicGradient(r) => format!(
829            "StyleBackgroundContent::ConicGradient({})",
830            format_conic_gradient(r, tabs)
831        ),
832        StyleBackgroundContent::Image(id) => format!("StyleBackgroundContent::Image({id:?})"),
833        StyleBackgroundContent::Color(c) => {
834            format!("StyleBackgroundContent::Color({})", format_color_value(c))
835        }
836        StyleBackgroundContent::SystemColor(s) => {
837            use crate::props::basic::color::SystemColorRef;
838            let variant = match s {
839                SystemColorRef::Text => "Text",
840                SystemColorRef::Background => "Background",
841                SystemColorRef::Accent => "Accent",
842                SystemColorRef::AccentText => "AccentText",
843                SystemColorRef::ButtonFace => "ButtonFace",
844                SystemColorRef::ButtonText => "ButtonText",
845                SystemColorRef::WindowBackground => "WindowBackground",
846                SystemColorRef::SelectionBackground => "SelectionBackground",
847                SystemColorRef::SelectionText => "SelectionText",
848            };
849            format!("StyleBackgroundContent::SystemColor(SystemColorRef::{variant})")
850        }
851    }
852}
853
854fn format_direction(d: &Direction, tabs: usize) -> String {
855    match d {
856        Direction::Angle(fv) => format!("Direction::Angle({})", format_angle_value(fv)),
857        Direction::FromTo(DirectionCorners { dir_from, dir_to }) => format!(
858            "Direction::FromTo(DirectionCorners {{ dir_from: {}, dir_to: {} }})",
859            dir_from.format_as_rust_code(tabs + 1),
860            dir_to.format_as_rust_code(tabs + 1)
861        ),
862    }
863}
864
865fn format_linear_gradient(l: &LinearGradient, tabs: usize) -> String {
866    let t = String::from("    ").repeat(tabs);
867    let t1 = String::from("    ").repeat(tabs + 1);
868    format!(
869        "LinearGradient {{\r\n{}direction: {},\r\n{}extend_mode: {},\r\n{}stops: \
870         NormalizedLinearColorStopVec::from_const_slice(LINEAR_COLOR_STOP_{}_ITEMS),\r\n{}}}",
871        t1,
872        format_direction(&l.direction, tabs + 1),
873        t1,
874        l.extend_mode.format_as_rust_code(tabs + 1),
875        t1,
876        l.stops.get_hash(),
877        t,
878    )
879}
880
881fn format_conic_gradient(r: &ConicGradient, tabs: usize) -> String {
882    let t = String::from("    ").repeat(tabs);
883    let t1 = String::from("    ").repeat(tabs + 1);
884
885    format!(
886        "ConicGradient {{\r\n{}extend_mode: {},\r\n{}center: {},\r\n{}angle: {},\r\n{}stops: \
887         NormalizedRadialColorStopVec::from_const_slice(RADIAL_COLOR_STOP_{}_ITEMS),\r\n{}}}",
888        t1,
889        r.extend_mode.format_as_rust_code(tabs + 1),
890        t1,
891        format_style_background_position(&r.center, tabs + 1),
892        t1,
893        format_angle_value(&r.angle),
894        t1,
895        r.stops.get_hash(),
896        t,
897    )
898}
899
900fn format_radial_gradient(r: &RadialGradient, tabs: usize) -> String {
901    let t = String::from("    ").repeat(tabs);
902    let t1 = String::from("    ").repeat(tabs + 1);
903    format!(
904        "RadialGradient {{\r\n{}shape: {},\r\n{}extend_mode: {},\r\n{}position: {},\r\n{}size: \
905         RadialGradientSize::{:?},\r\n{}stops: \
906         NormalizedLinearColorStopVec::from_const_slice(LINEAR_COLOR_STOP_{}_ITEMS),\r\n{}}}",
907        t1,
908        r.shape.format_as_rust_code(tabs + 1),
909        t1,
910        r.extend_mode.format_as_rust_code(tabs + 1),
911        t1,
912        format_style_background_position(&r.position, tabs + 1),
913        t1,
914        r.size,
915        t1,
916        r.stops.get_hash(),
917        t,
918    )
919}
920
921fn format_linear_color_stops(stops: &[NormalizedLinearColorStop], tabs: usize) -> String {
922    let t = String::from("    ").repeat(tabs);
923    stops
924        .iter()
925        .map(format_linear_color_stop)
926        .collect::<Vec<_>>()
927        .join(&format!(",\r\n{t}"))
928}
929
930fn format_linear_color_stop(g: &NormalizedLinearColorStop) -> String {
931    format!(
932        "NormalizedLinearColorStop {{ offset: {}, color: {} }}",
933        format_percentage_value(&g.offset),
934        format_color_or_system(g.color),
935    )
936}
937
938fn format_radial_color_stops(stops: &[NormalizedRadialColorStop], tabs: usize) -> String {
939    let t = String::from("    ").repeat(tabs);
940    stops
941        .iter()
942        .map(format_radial_color_stop)
943        .collect::<Vec<_>>()
944        .join(&format!(",\r\n{t}"))
945}
946
947fn format_radial_color_stop(g: &NormalizedRadialColorStop) -> String {
948    format!(
949        "RadialColorStop {{ angle: {}, color: {} }}",
950        format_angle_value(&g.angle),
951        format_color_or_system(g.color),
952    )
953}
954
955fn format_style_filter(st: &StyleFilter, tabs: usize) -> String {
956    match st {
957        StyleFilter::Blend(mb) => format!("StyleFilter::Blend({})", mb.format_as_rust_code(tabs)),
958        StyleFilter::Flood(c) => format!("StyleFilter::Flood({})", format_color_value(c)),
959        StyleFilter::Blur(m) => format!(
960            "StyleFilter::Blur(StyleBlur {{ width: {}, height: {} }})",
961            format_pixel_value(&m.width),
962            format_pixel_value(&m.height)
963        ),
964        StyleFilter::Opacity(pct) => {
965            format!("StyleFilter::Opacity({})", format_percentage_value(pct))
966        }
967        StyleFilter::ColorMatrix(cm) => format!(
968            "StyleFilter::ColorMatrix(StyleColorMatrix {{ m0: {}, m1: {}, m2: {}, m3: {}, m4: {}, \
969             m5: {}, m6: {}, m7: {}, m8: {}, m9: {}, m10: {}, m11: {}, m12: {}, m13: {}, m14: {}, \
970             m15: {}, m16: {}, m17: {}, m18: {}, m19: {} }})",
971            format_float_value(&cm.m0),
972            format_float_value(&cm.m1),
973            format_float_value(&cm.m2),
974            format_float_value(&cm.m3),
975            format_float_value(&cm.m4),
976            format_float_value(&cm.m5),
977            format_float_value(&cm.m6),
978            format_float_value(&cm.m7),
979            format_float_value(&cm.m8),
980            format_float_value(&cm.m9),
981            format_float_value(&cm.m10),
982            format_float_value(&cm.m11),
983            format_float_value(&cm.m12),
984            format_float_value(&cm.m13),
985            format_float_value(&cm.m14),
986            format_float_value(&cm.m15),
987            format_float_value(&cm.m16),
988            format_float_value(&cm.m17),
989            format_float_value(&cm.m18),
990            format_float_value(&cm.m19)
991        ),
992        StyleFilter::DropShadow(m) => {
993            format!("StyleFilter::DropShadow({})", m.format_as_rust_code(tabs))
994        }
995        StyleFilter::ComponentTransfer => "StyleFilter::ComponentTransfer".to_string(),
996        StyleFilter::Offset(o) => format!(
997            "StyleFilter::Offset(StyleFilterOffset {{ x: {}, y: {} }})",
998            format_pixel_value(&o.x),
999            format_pixel_value(&o.y)
1000        ),
1001        StyleFilter::Composite(StyleCompositeFilter::Over) => {
1002            "StyleFilter::Composite(StyleCompositeFilter::Over)".to_string()
1003        }
1004        StyleFilter::Composite(StyleCompositeFilter::In) => {
1005            "StyleFilter::Composite(StyleCompositeFilter::In)".to_string()
1006        }
1007        StyleFilter::Composite(StyleCompositeFilter::Atop) => {
1008            "StyleFilter::Composite(StyleCompositeFilter::Atop)".to_string()
1009        }
1010        StyleFilter::Composite(StyleCompositeFilter::Out) => {
1011            "StyleFilter::Composite(StyleCompositeFilter::Out)".to_string()
1012        }
1013        StyleFilter::Composite(StyleCompositeFilter::Xor) => {
1014            "StyleFilter::Composite(StyleCompositeFilter::Xor)".to_string()
1015        }
1016        StyleFilter::Composite(StyleCompositeFilter::Lighter) => {
1017            "StyleFilter::Composite(StyleCompositeFilter::Lighter)".to_string()
1018        }
1019        StyleFilter::Composite(StyleCompositeFilter::Arithmetic(fv)) => format!(
1020            "StyleFilter::Composite(StyleCompositeFilter::Arithmetic(ArithmeticCoefficients {{ \
1021             k1: {}, k2: {}, k3: {}, k4: {} }}))",
1022            format_float_value(&fv.k1),
1023            format_float_value(&fv.k2),
1024            format_float_value(&fv.k3),
1025            format_float_value(&fv.k4)
1026        ),
1027        StyleFilter::Brightness(v) => format!("StyleFilter::Brightness({})", format_percentage_value(v)),
1028        StyleFilter::Contrast(v) => format!("StyleFilter::Contrast({})", format_percentage_value(v)),
1029        StyleFilter::Grayscale(v) => format!("StyleFilter::Grayscale({})", format_percentage_value(v)),
1030        StyleFilter::HueRotate(a) => format!("StyleFilter::HueRotate({})", format_angle_value(a)),
1031        StyleFilter::Invert(v) => format!("StyleFilter::Invert({})", format_percentage_value(v)),
1032        StyleFilter::Saturate(v) => format!("StyleFilter::Saturate({})", format_percentage_value(v)),
1033        StyleFilter::Sepia(v) => format!("StyleFilter::Sepia({})", format_percentage_value(v)),
1034    }
1035}
1036
1037fn format_style_transforms(stops: &[StyleTransform], tabs: usize) -> String {
1038    let t = String::from("    ").repeat(tabs);
1039    stops
1040        .iter()
1041        .map(|s| format_style_transform(s, tabs))
1042        .collect::<Vec<_>>()
1043        .join(&format!(",\r\n{t}"))
1044}
1045
1046#[allow(clippy::too_many_lines)] // large but cohesive: single-purpose CSS parser/formatter/dispatch table (one branch per property/variant)
1047fn format_style_transform(st: &StyleTransform, tabs: usize) -> String {
1048    let tabs_minus_one = String::from("    ").repeat(tabs);
1049    let tabs = String::from("    ").repeat(tabs + 1);
1050    match st {
1051        StyleTransform::Matrix(m) => format!(
1052            "StyleTransform::Matrix(StyleTransformMatrix2D {{ a: {}, b: {}, c: {}, d: {}, tx: {}, \
1053             ty: {} }})",
1054            format_float_value(&m.a),
1055            format_float_value(&m.b),
1056            format_float_value(&m.c),
1057            format_float_value(&m.d),
1058            format_float_value(&m.tx),
1059            format_float_value(&m.ty)
1060        ),
1061        StyleTransform::Matrix3D(m) => format!(
1062            "StyleTransform::Matrix3D(StyleTransformMatrix3D {{\r\n{tabs}m11: {},\r\n{tabs}m12: \
1063             {},\r\n{tabs}m13: {},\r\n{tabs}m14: {},\r\n{tabs}m21: {},\r\n{tabs}m22: \
1064             {},\r\n{tabs}m23: {},\r\n{tabs}m24: {},\r\n{tabs}m31: {},\r\n{tabs}m32: \
1065             {},\r\n{tabs}m33: {},\r\n{tabs}m34: {},\r\n{tabs}m41: {},\r\n{tabs}m42: \
1066             {},\r\n{tabs}m43: {},\r\n{tabs}m44: {}\r\n{tabs_minus_one}}})",
1067            format_float_value(&m.m11),
1068            format_float_value(&m.m12),
1069            format_float_value(&m.m13),
1070            format_float_value(&m.m14),
1071            format_float_value(&m.m21),
1072            format_float_value(&m.m22),
1073            format_float_value(&m.m23),
1074            format_float_value(&m.m24),
1075            format_float_value(&m.m31),
1076            format_float_value(&m.m32),
1077            format_float_value(&m.m33),
1078            format_float_value(&m.m34),
1079            format_float_value(&m.m41),
1080            format_float_value(&m.m42),
1081            format_float_value(&m.m43),
1082            format_float_value(&m.m44),
1083            tabs = tabs,
1084            tabs_minus_one = tabs_minus_one,
1085        ),
1086        StyleTransform::Translate(t) => format!(
1087            "StyleTransform::Translate(StyleTransformTranslate2D {{ x: {}, y: {} }})",
1088            format_pixel_value(&t.x),
1089            format_pixel_value(&t.y)
1090        ),
1091        StyleTransform::Translate3D(t) => format!(
1092            "StyleTransform::Translate3D(StyleTransformTranslate3D {{ x: {}, y: {}, z: {} }})",
1093            format_pixel_value(&t.x),
1094            format_pixel_value(&t.y),
1095            format_pixel_value(&t.z)
1096        ),
1097        StyleTransform::TranslateX(x) => {
1098            format!("StyleTransform::TranslateX({})", format_pixel_value(x))
1099        }
1100        StyleTransform::TranslateY(y) => {
1101            format!("StyleTransform::TranslateY({})", format_pixel_value(y))
1102        }
1103        StyleTransform::TranslateZ(z) => {
1104            format!("StyleTransform::TranslateZ({})", format_pixel_value(z))
1105        }
1106        StyleTransform::Rotate(r) => format!("StyleTransform::Rotate({})", format_angle_value(r)),
1107        StyleTransform::Rotate3D(r) => format!(
1108            "StyleTransform::Rotate3D(StyleTransformRotate3D {{ {}, {}, {}, {} }})",
1109            format_float_value(&r.x),
1110            format_float_value(&r.y),
1111            format_float_value(&r.z),
1112            format_angle_value(&r.angle)
1113        ),
1114        StyleTransform::RotateX(x) => {
1115            format!("StyleTransform::RotateX({})", format_angle_value(x))
1116        }
1117        StyleTransform::RotateY(y) => {
1118            format!("StyleTransform::RotateY({})", format_angle_value(y))
1119        }
1120        StyleTransform::RotateZ(z) => {
1121            format!("StyleTransform::RotateZ({})", format_angle_value(z))
1122        }
1123        StyleTransform::Scale(s) => format!(
1124            "StyleTransform::Scale(StyleTransformScale2D {{ x: {}, y: {} }})",
1125            format_float_value(&s.x),
1126            format_float_value(&s.y)
1127        ),
1128        StyleTransform::Scale3D(s) => format!(
1129            "StyleTransform::Scale3D(StyleTransformScale3D {{ x: {}, y: {}, z: {} }})",
1130            format_float_value(&s.x),
1131            format_float_value(&s.y),
1132            format_float_value(&s.z)
1133        ),
1134        StyleTransform::ScaleX(x) => {
1135            format!("StyleTransform::ScaleX({})", format_percentage_value(x))
1136        }
1137        StyleTransform::ScaleY(y) => {
1138            format!("StyleTransform::ScaleY({})", format_percentage_value(y))
1139        }
1140        StyleTransform::ScaleZ(z) => {
1141            format!("StyleTransform::ScaleZ({})", format_percentage_value(z))
1142        }
1143        StyleTransform::Skew(sk) => format!(
1144            "StyleTransform::Skew(StyleTransformSkew2D {{ x: {}, y: {} }})",
1145            format_angle_value(&sk.x),
1146            format_angle_value(&sk.y)
1147        ),
1148        StyleTransform::SkewX(x) => {
1149            format!("StyleTransform::SkewX({})", format_angle_value(x))
1150        }
1151        StyleTransform::SkewY(y) => {
1152            format!("StyleTransform::SkewY({})", format_angle_value(y))
1153        }
1154        StyleTransform::Perspective(dist) => {
1155            format!("StyleTransform::Perspective({})", format_pixel_value(dist))
1156        }
1157    }
1158}
1159
1160fn format_font_ids(font_ids: &[StyleFontFamily], tabs: usize) -> String {
1161    let t = String::from("    ").repeat(tabs);
1162    font_ids
1163        .iter()
1164        .map(|s| s.format_as_rust_code(tabs + 1))
1165        .collect::<Vec<_>>()
1166        .join(&format!(",\r\n{t}"))
1167}
1168
1169fn format_style_background_position(b: &StyleBackgroundPosition, tabs: usize) -> String {
1170    let t = String::from("    ").repeat(tabs);
1171    let t1 = String::from("    ").repeat(tabs + 1);
1172    format!(
1173        "StyleBackgroundPosition {{\r\n{}horizontal: {},\r\n{}vertical: {},\r\n{}}}",
1174        t1,
1175        format_background_position_horizontal(&b.horizontal),
1176        t1,
1177        format_background_position_vertical(&b.vertical),
1178        t
1179    )
1180}
1181
1182fn format_background_position_horizontal(b: &BackgroundPositionHorizontal) -> String {
1183    match b {
1184        BackgroundPositionHorizontal::Left => "BackgroundPositionHorizontal::Left".to_string(),
1185        BackgroundPositionHorizontal::Center => "BackgroundPositionHorizontal::Center".to_string(),
1186        BackgroundPositionHorizontal::Right => "BackgroundPositionHorizontal::Right".to_string(),
1187        BackgroundPositionHorizontal::Exact(p) => format!(
1188            "BackgroundPositionHorizontal::Exact({})",
1189            format_pixel_value(p)
1190        ),
1191    }
1192}
1193
1194fn format_background_position_vertical(b: &BackgroundPositionVertical) -> String {
1195    match b {
1196        BackgroundPositionVertical::Top => "BackgroundPositionVertical::Top".to_string(),
1197        BackgroundPositionVertical::Center => "BackgroundPositionVertical::Center".to_string(),
1198        BackgroundPositionVertical::Bottom => "BackgroundPositionVertical::Bottom".to_string(),
1199        BackgroundPositionVertical::Exact(p) => format!(
1200            "BackgroundPositionVertical::Exact({})",
1201            format_pixel_value(p)
1202        ),
1203    }
1204}
1205
1206// Border side style impls moved to `props/style/border.rs`.
1207
1208// Several small impls were moved to their respective modules.
1209
1210// StyleTransformOrigin impl moved to `props/style/transform.rs`.
1211
1212// Implementations for small property types were moved to their respective
1213// modules to keep the implementations next to the type definitions.