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

            
10
use alloc::{collections::btree_map::BTreeMap, format, string::String, string::ToString, vec::Vec};
11
use core::hash::Hash;
12

            
13
use crate::{
14
    corety::*,
15
    css::*,
16
    props::{basic::*, layout::*, property::*, style::*},
17
};
18

            
19
/// Formats a value as const-compatible Rust source code.
20
pub trait FormatAsRustCode {
21
    fn format_as_rust_code(&self, tabs: usize) -> String;
22
}
23

            
24
/// Returns a deterministic 64-bit hash for content-based deduplication.
25
pub trait GetHash {
26
    fn get_hash(&self) -> u64;
27
}
28

            
29
impl<T: Hash> GetHash for T {
30
    fn get_hash(&self) -> u64 {
31
        use core::hash::Hasher;
32
        let mut hasher = std::hash::DefaultHasher::new();
33
        self.hash(&mut hasher);
34
        hasher.finish()
35
    }
36
}
37

            
38
// In order to generate the Rust code, all items that implement Drop
39
// have to be declared before being used.
40

            
41
impl<T: FormatAsRustCode + 'static> FormatAsRustCode for BoxOrStatic<T> {
42
    fn format_as_rust_code(&self, tabs: usize) -> String {
43
        self.as_ref().format_as_rust_code(tabs)
44
    }
45
}
46

            
47
/// Collects heap-allocated CSS values (strings, vecs) that must be emitted as
48
/// `const` slice declarations before the generated Rust code can reference them.
49
#[derive(Default)]
50
pub struct VecContents {
51
    // the u64 is the hash of the type (generated by string.get_hash())
52
    pub strings: BTreeMap<u64, AzString>,
53
    pub style_filters: BTreeMap<u64, StyleFilterVec>,
54
    pub style_background_sizes: BTreeMap<u64, StyleBackgroundSizeVec>,
55
    pub style_background_repeats: BTreeMap<u64, StyleBackgroundRepeatVec>,
56
    pub style_background_contents: BTreeMap<u64, StyleBackgroundContentVec>,
57
    pub style_background_positions: BTreeMap<u64, StyleBackgroundPositionVec>,
58
    pub style_transforms: BTreeMap<u64, StyleTransformVec>,
59
    pub font_families: BTreeMap<u64, StyleFontFamilyVec>,
60
    pub linear_color_stops: BTreeMap<u64, NormalizedLinearColorStopVec>,
61
    pub radial_color_stops: BTreeMap<u64, NormalizedRadialColorStopVec>,
62
}
63

            
64
impl VecContents {
65
    pub fn format(&self, tabs: usize) -> String {
66
        let mut result = String::new();
67
        let t = "    ".repeat(tabs);
68
        let t2 = "    ".repeat(tabs + 1);
69

            
70
        for (key, item) in self.strings.iter() {
71
            result.push_str(&format!(
72
                "\r\n    const STRING_{}: AzString = AzString::from_const_str(\"{}\");",
73
                key,
74
                item.as_str()
75
            ));
76
        }
77

            
78
        for (key, item) in self.style_filters.iter() {
79
            let val = item
80
                .iter()
81
                .map(|filter| format_style_filter(filter, tabs + 1))
82
                .collect::<Vec<_>>()
83
                .join(&format!(",\r\n{}", t));
84

            
85
            result.push_str(&format!(
86
                "\r\n    const STYLE_FILTER_{}_ITEMS: &[StyleFilter] = &[\r\n{}{}\r\n{}];",
87
                key, t2, val, t
88
            ));
89
        }
90

            
91
        for (key, item) in self.style_background_sizes.iter() {
92
            let val = item
93
                .iter()
94
                .map(format_style_background_size)
95
                .collect::<Vec<_>>()
96
                .join(&format!(",\r\n{}", t));
97

            
98
            result.push_str(&format!(
99
                "\r\n    const STYLE_BACKGROUND_SIZE_{}_ITEMS: &[StyleBackgroundSize] = \
100
                 &[\r\n{}{}\r\n{}];",
101
                key, t2, val, t
102
            ));
103
        }
104

            
105
        for (key, item) in self.style_background_repeats.iter() {
106
            let val = item
107
                .iter()
108
                .map(|bgr| bgr.format_as_rust_code(tabs + 1))
109
                .collect::<Vec<_>>()
110
                .join(&format!(",\r\n{}", t));
111

            
112
            result.push_str(&format!(
113
                "\r\n    const STYLE_BACKGROUND_REPEAT_{}_ITEMS: &[StyleBackgroundRepeat] = \
114
                 &[\r\n{}{}\r\n{}];",
115
                key, t2, val, t
116
            ));
117
        }
118

            
119
        for (key, item) in self.style_background_contents.iter() {
120
            let val = item
121
                .iter()
122
                .map(|bgc| format_style_background_content(bgc, tabs + 1))
123
                .collect::<Vec<_>>()
124
                .join(&format!(",\r\n{}", t));
125

            
126
            result.push_str(&format!(
127
                "\r\n    const STYLE_BACKGROUND_CONTENT_{}_ITEMS: &[StyleBackgroundContent] = \
128
                 &[\r\n{}{}\r\n{}];",
129
                key, t2, val, t
130
            ));
131
        }
132

            
133
        for (key, item) in self.style_background_positions.iter() {
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
            result.push_str(&format!(
141
                "\r\n    const STYLE_BACKGROUND_POSITION_{}_ITEMS: &[StyleBackgroundPosition] = \
142
                 &[\r\n{}{}\r\n{}];",
143
                key, t2, val, t
144
            ));
145
        }
146

            
147
        for (key, item) in self.style_transforms.iter() {
148
            let val = format_style_transforms(item.as_ref(), tabs + 1);
149

            
150
            result.push_str(&format!(
151
                "\r\n    const STYLE_TRANSFORM_{}_ITEMS: &[StyleTransform] = &[\r\n{}{}\r\n{}];",
152
                key, t2, val, t
153
            ));
154
        }
155

            
156
        for (key, item) in self.font_families.iter() {
157
            let val = format_font_ids(item.as_ref(), tabs + 1);
158

            
159
            result.push_str(&format!(
160
                "\r\n    const STYLE_FONT_FAMILY_{}_ITEMS: &[StyleFontFamily] = &[\r\n{}{}\r\n{}];",
161
                key, t2, val, t
162
            ));
163
        }
164

            
165
        for (key, item) in self.linear_color_stops.iter() {
166
            let val = format_linear_color_stops(item.as_ref(), 1);
167

            
168
            result.push_str(&format!(
169
                "\r\n    const LINEAR_COLOR_STOP_{}_ITEMS: &[NormalizedLinearColorStop] = \
170
                 &[\r\n{}{}\r\n{}];",
171
                key, t2, val, t
172
            ));
173
        }
174

            
175
        for (key, item) in self.radial_color_stops.iter() {
176
            let val = format_radial_color_stops(item.as_ref(), tabs);
177

            
178
            result.push_str(&format!(
179
                "\r\n    const RADIAL_COLOR_STOP_{}_ITEMS: &[NormalizedRadialColorStop] = \
180
                 &[\r\n{}{}\r\n{}];",
181
                key, t2, val, t
182
            ));
183
        }
184

            
185
        result
186
    }
187

            
188
    // given a CSS property, clones all the necessary strings (see class documentation)
189
    pub fn insert_from_css_property(&mut self, prop: &CssProperty) {
190
        match prop {
191
            CssProperty::FontFamily(CssPropertyValue::Exact(v)) => {
192
                for family in v.iter() {
193
                    match family {
194
                        StyleFontFamily::System(s) => {
195
                            // if the font-family is surrounded by quotes, strip them ("Arial" ->
196
                            // Arial)
197
                            let s = s.as_str();
198
                            let s = s.trim();
199
                            let s = s.trim_start_matches('\"');
200
                            let s = s.trim_end_matches('\"');
201
                            let s = s.trim_start_matches('\'');
202
                            let s = s.trim_end_matches('\'');
203

            
204
                            self.strings.insert(s.get_hash(), s.to_string().into());
205
                        }
206
                        StyleFontFamily::File(s) => {
207
                            let s = s.as_str();
208
                            let s = s.trim();
209
                            let s = s.trim_start_matches('\"');
210
                            let s = s.trim_end_matches('\"');
211
                            let s = s.trim_start_matches('\'');
212
                            let s = s.trim_end_matches('\'');
213

            
214
                            self.strings.insert(s.get_hash(), s.to_string().into());
215
                        }
216
                        _ => {}
217
                    }
218
                }
219
                self.font_families.insert(v.get_hash(), v.clone());
220
            }
221
            CssProperty::Transform(CssPropertyValue::Exact(v)) => {
222
                self.style_transforms.insert(v.get_hash(), v.clone());
223
            }
224
            CssProperty::BackgroundRepeat(CssPropertyValue::Exact(v)) => {
225
                self.style_background_repeats
226
                    .insert(v.get_hash(), v.clone());
227
            }
228
            CssProperty::BackgroundSize(CssPropertyValue::Exact(v)) => {
229
                self.style_background_sizes.insert(v.get_hash(), v.clone());
230
            }
231
            CssProperty::BackgroundPosition(CssPropertyValue::Exact(v)) => {
232
                self.style_background_positions
233
                    .insert(v.get_hash(), v.clone());
234
            }
235
            CssProperty::BackgroundContent(CssPropertyValue::Exact(ref v)) => {
236
                for background in v.iter() {
237
                    match background {
238
                        StyleBackgroundContent::Image(id) => {
239
                            self.strings.insert(id.get_hash(), id.clone());
240
                        }
241
                        StyleBackgroundContent::LinearGradient(lg) => {
242
                            self.linear_color_stops
243
                                .insert(lg.stops.get_hash(), lg.stops.clone());
244
                        }
245
                        StyleBackgroundContent::RadialGradient(rg) => {
246
                            self.linear_color_stops
247
                                .insert(rg.stops.get_hash(), rg.stops.clone());
248
                        }
249
                        StyleBackgroundContent::ConicGradient(lg) => {
250
                            self.radial_color_stops
251
                                .insert(lg.stops.get_hash(), lg.stops.clone());
252
                        }
253
                        _ => {}
254
                    }
255
                }
256
                self.style_background_contents
257
                    .insert(v.get_hash(), v.clone());
258
            }
259
            CssProperty::Filter(CssPropertyValue::Exact(v)) => {
260
                self.style_filters.insert(v.get_hash(), v.clone());
261
            }
262
            CssProperty::BackdropFilter(CssPropertyValue::Exact(v)) => {
263
                self.style_filters.insert(v.get_hash(), v.clone());
264
            }
265
            _ => {}
266
        }
267
    }
268
}
269

            
270
// Helper functions for formatting values
271

            
272
/// Formats a `PixelValue` as a const-compatible Rust constructor call.
273
pub fn format_pixel_value(p: &PixelValue) -> String {
274
    let value = p.number.get();
275

            
276
    // Decompose into integer + fractional parts using the absolute value so
277
    // that negative numbers are handled correctly.  Previously, floorf on a
278
    // negative value (e.g. -1.5 → -2) caused the wrong reconstruction.
279
    let abs_val = libm::fabsf(value);
280
    let sign: isize = if value < 0.0 { -1 } else { 1 };
281
    let pre_comma = libm::floorf(abs_val) as isize * sign;
282
    let post_comma = libm::roundf((abs_val - libm::floorf(abs_val)) * 100.0) as isize;
283

            
284
    match p.metric {
285
        SizeMetric::Pt => format!(
286
            "PixelValue::const_pt_fractional({}, {})",
287
            pre_comma, post_comma
288
        ),
289
        SizeMetric::Em => format!(
290
            "PixelValue::const_em_fractional({}, {})",
291
            pre_comma, post_comma
292
        ),
293
        other => format!(
294
            "PixelValue::const_from_metric_fractional(SizeMetric::{:?}, {}, {})",
295
            other, pre_comma, post_comma
296
        ),
297
    }
298
}
299

            
300
/// Formats a `PixelValueNoPercent` as a const-compatible Rust constructor call.
301
pub fn format_pixel_value_no_percent(p: &PixelValueNoPercent) -> String {
302
    format!(
303
        "PixelValueNoPercent {{ inner: {} }}",
304
        format_pixel_value(&p.inner)
305
    )
306
}
307

            
308
/// Formats a `FloatValue` as a const-compatible Rust constructor call.
309
pub fn format_float_value(f: &FloatValue) -> String {
310
    let value = f.get();
311
    let abs_val = libm::fabsf(value);
312
    let sign: isize = if value < 0.0 { -1 } else { 1 };
313
    let pre_comma = libm::floorf(abs_val) as isize * sign;
314
    let post_comma = libm::roundf((abs_val - libm::floorf(abs_val)) * 100.0) as isize;
315
    format!(
316
        "FloatValue::const_new_fractional({}, {})",
317
        pre_comma, post_comma
318
    )
319
}
320

            
321
/// Formats a `PercentageValue` as a const-compatible Rust constructor call.
322
pub fn format_percentage_value(f: &PercentageValue) -> String {
323
    let value = f.normalized() * 100.0;
324
    let abs_val = libm::fabsf(value);
325
    let sign: isize = if value < 0.0 { -1 } else { 1 };
326
    let pre_comma = libm::floorf(abs_val) as isize * sign;
327
    let post_comma = libm::roundf((abs_val - libm::floorf(abs_val)) * 100.0) as isize;
328
    format!(
329
        "PercentageValue::const_new_fractional({}, {})",
330
        pre_comma, post_comma
331
    )
332
}
333

            
334
/// Formats an `AngleValue` as a const-compatible Rust constructor call.
335
pub fn format_angle_value(f: &AngleValue) -> String {
336
    let value = f.number.get();
337
    let abs_val = libm::fabsf(value);
338
    let sign: isize = if value < 0.0 { -1 } else { 1 };
339
    let pre_comma = libm::floorf(abs_val) as isize * sign;
340
    let post_comma = libm::roundf((abs_val - libm::floorf(abs_val)) * 100.0) as isize;
341
    format!(
342
        "AngleValue::const_from_metric_fractional(AngleMetric::{:?}, {}, {})",
343
        f.metric, pre_comma, post_comma
344
    )
345
}
346

            
347
/// Formats a `ColorU` as a const-compatible Rust struct literal.
348
pub fn format_color_value(c: &ColorU) -> String {
349
    format!(
350
        "ColorU {{ r: {}, g: {}, b: {}, a: {} }}",
351
        c.r, c.g, c.b, c.a
352
    )
353
}
354

            
355
fn format_grid_line(line: &GridLine, _tabs: usize) -> String {
356
    match line {
357
        GridLine::Auto => "GridLine::Auto".to_string(),
358
        GridLine::Line(n) => format!("GridLine::Line({})", n),
359
        GridLine::Named(named) => format!(
360
            "GridLine::Named(NamedGridLine {{ grid_line_name: AzString::from_const_str({:?}), span_count: {} }})",
361
            named.grid_line_name.as_ref(),
362
            named.span_count,
363
        ),
364
        GridLine::Span(n) => format!("GridLine::Span({})", n),
365
    }
366
}
367

            
368
fn format_color_or_system(c: &crate::props::basic::color::ColorOrSystem) -> String {
369
    use crate::props::basic::color::{ColorOrSystem, SystemColorRef};
370
    match c {
371
        ColorOrSystem::Color(color) => format!("ColorOrSystem::Color({})", format_color_value(color)),
372
        ColorOrSystem::System(system_ref) => {
373
            let variant = match system_ref {
374
                SystemColorRef::Text => "Text",
375
                SystemColorRef::Background => "Background",
376
                SystemColorRef::Accent => "Accent",
377
                SystemColorRef::AccentText => "AccentText",
378
                SystemColorRef::ButtonFace => "ButtonFace",
379
                SystemColorRef::ButtonText => "ButtonText",
380
                SystemColorRef::WindowBackground => "WindowBackground",
381
                SystemColorRef::SelectionBackground => "SelectionBackground",
382
                SystemColorRef::SelectionText => "SelectionText",
383
            };
384
            format!("ColorOrSystem::System(SystemColorRef::{})", variant)
385
        }
386
    }
387
}
388

            
389
// Macro implementations for common patterns
390

            
391
macro_rules! impl_float_value_fmt {
392
    ($struct_name:ident) => {
393
        impl FormatAsRustCode for $struct_name {
394
            fn format_as_rust_code(&self, _tabs: usize) -> String {
395
                format!(
396
                    "{} {{ inner: {} }}",
397
                    stringify!($struct_name),
398
                    format_float_value(&self.inner)
399
                )
400
            }
401
        }
402
    };
403
}
404

            
405
impl_float_value_fmt!(LayoutFlexGrow);
406
impl_float_value_fmt!(LayoutFlexShrink);
407

            
408
macro_rules! impl_percentage_value_fmt {
409
    ($struct_name:ident) => {
410
        impl FormatAsRustCode for $struct_name {
411
            fn format_as_rust_code(&self, _tabs: usize) -> String {
412
                format!(
413
                    "{} {{ inner: {} }}",
414
                    stringify!($struct_name),
415
                    format_percentage_value(&self.inner)
416
                )
417
            }
418
        }
419
    };
420
}
421

            
422
impl_percentage_value_fmt!(StyleLineHeight);
423
impl_percentage_value_fmt!(StyleOpacity);
424

            
425
macro_rules! impl_pixel_value_fmt {
426
    ($struct_name:ident) => {
427
        impl FormatAsRustCode for $struct_name {
428
            fn format_as_rust_code(&self, _tabs: usize) -> String {
429
                format!(
430
                    "{} {{ inner: {} }}",
431
                    stringify!($struct_name),
432
                    format_pixel_value(&self.inner)
433
                )
434
            }
435
        }
436
    };
437
}
438

            
439
impl_pixel_value_fmt!(StyleTabSize);
440
impl_pixel_value_fmt!(StyleBorderTopLeftRadius);
441
impl_pixel_value_fmt!(StyleBorderBottomLeftRadius);
442
impl_pixel_value_fmt!(StyleBorderTopRightRadius);
443
impl_pixel_value_fmt!(StyleBorderBottomRightRadius);
444

            
445
impl_pixel_value_fmt!(LayoutBorderTopWidth);
446
impl_pixel_value_fmt!(LayoutBorderLeftWidth);
447
impl_pixel_value_fmt!(LayoutBorderRightWidth);
448
impl_pixel_value_fmt!(LayoutBorderBottomWidth);
449
impl_pixel_value_fmt!(StyleLetterSpacing);
450
impl_pixel_value_fmt!(StyleWordSpacing);
451
impl_pixel_value_fmt!(StyleFontSize);
452

            
453
impl_pixel_value_fmt!(LayoutMarginTop);
454
impl_pixel_value_fmt!(LayoutMarginBottom);
455
impl_pixel_value_fmt!(LayoutMarginRight);
456
impl_pixel_value_fmt!(LayoutMarginLeft);
457

            
458
impl_pixel_value_fmt!(LayoutPaddingTop);
459
impl_pixel_value_fmt!(LayoutPaddingBottom);
460
impl_pixel_value_fmt!(LayoutPaddingRight);
461
impl_pixel_value_fmt!(LayoutPaddingLeft);
462
impl_pixel_value_fmt!(LayoutPaddingInlineStart);
463
impl_pixel_value_fmt!(LayoutPaddingInlineEnd);
464

            
465
impl FormatAsRustCode for LayoutWidth {
466
    fn format_as_rust_code(&self, _tabs: usize) -> String {
467
        match self {
468
            LayoutWidth::Auto => "LayoutWidth::Auto".to_string(),
469
            LayoutWidth::Px(px) => format!("LayoutWidth::Px({})", format_pixel_value(px)),
470
            LayoutWidth::MinContent => "LayoutWidth::MinContent".to_string(),
471
            LayoutWidth::MaxContent => "LayoutWidth::MaxContent".to_string(),
472
            LayoutWidth::FitContent(px) => format!("LayoutWidth::FitContent({})", format_pixel_value(px)),
473
            LayoutWidth::Calc(items) => format!("LayoutWidth::Calc(/* {} items */)", items.len()),
474
        }
475
    }
476
}
477

            
478
impl FormatAsRustCode for LayoutHeight {
479
    fn format_as_rust_code(&self, _tabs: usize) -> String {
480
        match self {
481
            LayoutHeight::Auto => "LayoutHeight::Auto".to_string(),
482
            LayoutHeight::Px(px) => format!("LayoutHeight::Px({})", format_pixel_value(px)),
483
            LayoutHeight::MinContent => "LayoutHeight::MinContent".to_string(),
484
            LayoutHeight::MaxContent => "LayoutHeight::MaxContent".to_string(),
485
            LayoutHeight::FitContent(px) => format!("LayoutHeight::FitContent({})", format_pixel_value(px)),
486
            LayoutHeight::Calc(items) => format!("LayoutHeight::Calc(/* {} items */)", items.len()),
487
        }
488
    }
489
}
490

            
491
impl_pixel_value_fmt!(LayoutMinHeight);
492
impl_pixel_value_fmt!(LayoutMinWidth);
493
impl_pixel_value_fmt!(LayoutMaxWidth);
494
impl_pixel_value_fmt!(LayoutMaxHeight);
495
impl_pixel_value_fmt!(LayoutTop);
496
impl_pixel_value_fmt!(LayoutInsetBottom);
497

            
498
impl_pixel_value_fmt!(LayoutRight);
499
impl_pixel_value_fmt!(LayoutLeft);
500

            
501
// LayoutFlexBasis implementation moved to `props/layout/flex.rs`.
502

            
503
impl_pixel_value_fmt!(LayoutColumnGap);
504
impl_pixel_value_fmt!(LayoutRowGap);
505

            
506
impl FormatAsRustCode for GridTemplate {
507
    fn format_as_rust_code(&self, tabs: usize) -> String {
508
        let tracks: Vec<String> = self
509
            .tracks
510
            .as_ref()
511
            .iter()
512
            .map(|t| t.format_as_rust_code(tabs))
513
            .collect();
514
        format!(
515
            "GridTemplate {{ tracks: GridTrackSizingVec::from_vec(vec![{}]) }}",
516
            tracks.join(", ")
517
        )
518
    }
519
}
520

            
521
impl FormatAsRustCode for GridPlacement {
522
    fn format_as_rust_code(&self, tabs: usize) -> String {
523
        format!(
524
            "GridPlacement {{ grid_start: {}, grid_end: {} }}",
525
            format_grid_line(&self.grid_start, tabs),
526
            format_grid_line(&self.grid_end, tabs),
527
        )
528
    }
529
}
530

            
531
impl_color_value_fmt!(StyleTextColor);
532
impl_color_value_fmt!(StyleBorderTopColor);
533
impl_color_value_fmt!(StyleBorderLeftColor);
534
impl_color_value_fmt!(StyleBorderRightColor);
535
impl_color_value_fmt!(StyleBorderBottomColor);
536

            
537
impl_enum_fmt!(
538
    StyleMixBlendMode,
539
    Normal,
540
    Multiply,
541
    Screen,
542
    Overlay,
543
    Darken,
544
    Lighten,
545
    ColorDodge,
546
    ColorBurn,
547
    HardLight,
548
    SoftLight,
549
    Difference,
550
    Exclusion,
551
    Hue,
552
    Saturation,
553
    Color,
554
    Luminosity
555
);
556

            
557
impl_enum_fmt!(StyleHyphens, None, Manual, Auto);
558
impl_enum_fmt!(StyleWordBreak, Normal, BreakAll, KeepAll, BreakWord);
559
impl_enum_fmt!(StyleOverflowWrap, Normal, Anywhere, BreakWord);
560
impl_enum_fmt!(StyleLineBreak, Auto, Loose, Normal, Strict, Anywhere);
561
impl_enum_fmt!(StyleObjectFit, Fill, Contain, Cover, None, ScaleDown);
562

            
563
impl FormatAsRustCode for StyleObjectPosition {
564
    fn format_as_rust_code(&self, _tabs: usize) -> String {
565
        format!("StyleObjectPosition {{ horizontal: {}, vertical: {} }}",
566
            format_background_position_horizontal(&self.horizontal),
567
            format_background_position_vertical(&self.vertical))
568
    }
569
}
570

            
571
impl FormatAsRustCode for StyleAspectRatio {
572
    fn format_as_rust_code(&self, _tabs: usize) -> String {
573
        match self {
574
            StyleAspectRatio::Auto => "StyleAspectRatio::Auto".to_string(),
575
            StyleAspectRatio::Ratio(r) => format!("StyleAspectRatio::Ratio(AspectRatioValue {{ width: {}, height: {} }})", r.width, r.height),
576
        }
577
    }
578
}
579

            
580
impl_enum_fmt!(StyleTextOrientation, Mixed, Upright, Sideways);
581
impl_enum_fmt!(StyleTextAlignLast, Auto, Start, End, Left, Right, Center, Justify);
582

            
583
impl_enum_fmt!(StyleDirection, Ltr, Rtl);
584

            
585
impl_enum_fmt!(StyleWhiteSpace, Normal, Pre, Nowrap, PreWrap, PreLine, BreakSpaces);
586

            
587
impl_enum_fmt!(StyleVisibility, Visible, Hidden, Collapse);
588

            
589
impl_enum_fmt!(LayoutWritingMode, HorizontalTb, VerticalRl, VerticalLr);
590

            
591
impl_enum_fmt!(LayoutClear, None, Left, Right, Both);
592

            
593
impl_enum_fmt!(
594
    StyleCursor,
595
    Alias,
596
    AllScroll,
597
    Cell,
598
    ColResize,
599
    ContextMenu,
600
    Copy,
601
    Crosshair,
602
    Default,
603
    EResize,
604
    EwResize,
605
    Grab,
606
    Grabbing,
607
    Help,
608
    Move,
609
    NResize,
610
    NsResize,
611
    NeswResize,
612
    NwseResize,
613
    Pointer,
614
    Progress,
615
    RowResize,
616
    SResize,
617
    SeResize,
618
    Text,
619
    Unset,
620
    VerticalText,
621
    WResize,
622
    Wait,
623
    ZoomIn,
624
    ZoomOut
625
);
626

            
627
impl_enum_fmt!(
628
    BorderStyle,
629
    None,
630
    Solid,
631
    Double,
632
    Dotted,
633
    Dashed,
634
    Hidden,
635
    Groove,
636
    Ridge,
637
    Inset,
638
    Outset
639
);
640

            
641
impl_enum_fmt!(
642
    StyleBackgroundRepeat,
643
    NoRepeat,
644
    PatternRepeat,
645
    RepeatX,
646
    RepeatY
647
);
648

            
649
impl_enum_fmt!(
650
    LayoutDisplay,
651
    None,
652
    Block,
653
    Inline,
654
    InlineBlock,
655
    Flex,
656
    InlineFlex,
657
    Table,
658
    InlineTable,
659
    TableRowGroup,
660
    TableHeaderGroup,
661
    TableFooterGroup,
662
    TableRow,
663
    TableColumnGroup,
664
    TableColumn,
665
    TableCell,
666
    TableCaption,
667
    ListItem,
668
    RunIn,
669
    Marker,
670
    Grid,
671
    InlineGrid,
672
    FlowRoot,
673
    Contents
674
);
675

            
676
impl_enum_fmt!(LayoutFloat, Left, Right, None);
677

            
678
impl_enum_fmt!(LayoutBoxSizing, ContentBox, BorderBox);
679

            
680
impl_enum_fmt!(LayoutFlexDirection, Row, RowReverse, Column, ColumnReverse);
681

            
682
impl_enum_fmt!(LayoutFlexWrap, Wrap, NoWrap, WrapReverse);
683

            
684
impl_enum_fmt!(
685
    LayoutJustifyContent,
686
    Start,
687
    End,
688
    FlexStart,
689
    FlexEnd,
690
    Center,
691
    SpaceBetween,
692
    SpaceAround,
693
    SpaceEvenly
694
);
695

            
696
impl_enum_fmt!(LayoutAlignItems, Stretch, Center, Start, End, Baseline);
697

            
698
impl_enum_fmt!(
699
    LayoutAlignContent,
700
    Start,
701
    End,
702
    Stretch,
703
    Center,
704
    SpaceBetween,
705
    SpaceAround
706
);
707

            
708
impl_enum_fmt!(Shape, Circle, Ellipse);
709

            
710
impl_enum_fmt!(LayoutOverflow, Auto, Scroll, Visible, Hidden, Clip);
711

            
712
impl_enum_fmt!(StyleTextAlign, Center, Left, Right, Justify, Start, End);
713

            
714
impl_enum_fmt!(StyleUserSelect, Auto, Text, None, All);
715

            
716
impl_enum_fmt!(StyleTextDecoration, None, Underline, Overline, LineThrough);
717

            
718
impl_enum_fmt!(
719
    DirectionCorner,
720
    Right,
721
    Left,
722
    Top,
723
    Bottom,
724
    TopRight,
725
    TopLeft,
726
    BottomRight,
727
    BottomLeft
728
);
729

            
730
impl_enum_fmt!(ExtendMode, Clamp, Repeat);
731

            
732
impl_enum_fmt!(StyleBackfaceVisibility, Visible, Hidden);
733

            
734
impl_enum_fmt!(StyleUnicodeBidi, Normal, Embed, Isolate, BidiOverride, IsolateOverride, Plaintext);
735
impl_enum_fmt!(StyleTextBoxTrim, None, TrimStart, TrimEnd, TrimBoth);
736
impl_enum_fmt!(StyleTextBoxEdge, Auto, TextEdge, CapHeight, ExHeight);
737
impl_enum_fmt!(StyleDominantBaseline, Auto, TextBottom, Alphabetic, Ideographic, Middle, Central, Mathematical, Hanging, TextTop);
738
impl_enum_fmt!(StyleAlignmentBaseline, Baseline, TextBottom, Alphabetic, Ideographic, Middle, Central, Mathematical, TextTop);
739
impl_enum_fmt!(StyleInitialLetterAlign, Auto, Alphabetic, Hanging, Ideographic);
740
impl_enum_fmt!(StyleInitialLetterWrap, None, First, All, Grid);
741
impl_enum_fmt!(StyleScrollbarGutter, Auto, Stable, StableBothEdges);
742

            
743
impl FormatAsRustCode for StyleOverflowClipMargin {
744
    fn format_as_rust_code(&self, tabs: usize) -> String {
745
        format!("StyleOverflowClipMargin {{ inner: {} }}", self.inner.format_as_rust_code(tabs))
746
    }
747
}
748

            
749
impl FormatAsRustCode for StyleClipRect {
750
    fn format_as_rust_code(&self, _tabs: usize) -> String {
751
        fn fmt_edge(o: &OptionF32) -> String {
752
            match o {
753
                OptionF32::None => String::from("OptionF32::None"),
754
                OptionF32::Some(v) => format!("OptionF32::Some({:?})", v),
755
            }
756
        }
757
        format!(
758
            "StyleClipRect {{ top: {}, right: {}, bottom: {}, left: {} }}",
759
            fmt_edge(&self.top),
760
            fmt_edge(&self.right),
761
            fmt_edge(&self.bottom),
762
            fmt_edge(&self.left),
763
        )
764
    }
765
}
766

            
767
// Complex type implementations
768

            
769
fn format_style_background_size(c: &StyleBackgroundSize) -> String {
770
    match c {
771
        StyleBackgroundSize::Contain => String::from("StyleBackgroundSize::Contain"),
772
        StyleBackgroundSize::Cover => String::from("StyleBackgroundSize::Cover"),
773
        StyleBackgroundSize::ExactSize(size) => format!(
774
            "StyleBackgroundSize::ExactSize(PixelValueSize {{ width: {}, height: {} }})",
775
            format_pixel_value(&size.width),
776
            format_pixel_value(&size.height)
777
        ),
778
    }
779
}
780

            
781
// Scrollbar-related impls moved to `props/style/scrollbar.rs`
782

            
783
/// Formats a `ScrollbarInfo` as a const-compatible Rust struct literal.
784
pub fn format_scrollbar_info(s: &ScrollbarInfo, tabs: usize) -> String {
785
    let t = String::from("    ").repeat(tabs);
786
    let t1 = String::from("    ").repeat(tabs + 1);
787
    format!(
788
        "ScrollbarInfo {{\r\n{}width: {},\r\n{}padding_left: {},\r\n{}padding_right: \
789
         {},\r\n{}track: {},\r\n{}thumb: {},\r\n{}button: {},\r\n{}button: {},\r\n{}resizer: \
790
         {},\r\n{}}}",
791
        t1,
792
        s.width.format_as_rust_code(tabs + 1),
793
        t1,
794
        s.padding_left.format_as_rust_code(tabs + 1),
795
        t1,
796
        s.padding_right.format_as_rust_code(tabs + 1),
797
        t1,
798
        format_style_background_content(&s.track, tabs + 1),
799
        t1,
800
        format_style_background_content(&s.thumb, tabs + 1),
801
        t1,
802
        format_style_background_content(&s.button, tabs + 1),
803
        t1,
804
        format_style_background_content(&s.corner, tabs + 1),
805
        t1,
806
        format_style_background_content(&s.resizer, tabs + 1),
807
        t
808
    )
809
}
810

            
811
// Background/filter vec impls moved to their respective modules.
812

            
813
fn format_style_background_content(content: &StyleBackgroundContent, tabs: usize) -> String {
814
    match content {
815
        StyleBackgroundContent::LinearGradient(l) => format!(
816
            "StyleBackgroundContent::LinearGradient({})",
817
            format_linear_gradient(l, tabs)
818
        ),
819
        StyleBackgroundContent::RadialGradient(r) => format!(
820
            "StyleBackgroundContent::RadialGradient({})",
821
            format_radial_gradient(r, tabs)
822
        ),
823
        StyleBackgroundContent::ConicGradient(r) => format!(
824
            "StyleBackgroundContent::ConicGradient({})",
825
            format_conic_gradient(r, tabs)
826
        ),
827
        StyleBackgroundContent::Image(id) => format!("StyleBackgroundContent::Image({:?})", id),
828
        StyleBackgroundContent::Color(c) => {
829
            format!("StyleBackgroundContent::Color({})", format_color_value(c))
830
        }
831
    }
832
}
833

            
834
fn format_direction(d: &Direction, tabs: usize) -> String {
835
    match d {
836
        Direction::Angle(fv) => format!("Direction::Angle({})", format_angle_value(fv)),
837
        Direction::FromTo(DirectionCorners { dir_from, dir_to }) => format!(
838
            "Direction::FromTo(DirectionCorners {{ dir_from: {}, dir_to: {} }})",
839
            dir_from.format_as_rust_code(tabs + 1),
840
            dir_to.format_as_rust_code(tabs + 1)
841
        ),
842
    }
843
}
844

            
845
fn format_linear_gradient(l: &LinearGradient, tabs: usize) -> String {
846
    let t = String::from("    ").repeat(tabs);
847
    let t1 = String::from("    ").repeat(tabs + 1);
848
    format!(
849
        "LinearGradient {{\r\n{}direction: {},\r\n{}extend_mode: {},\r\n{}stops: \
850
         NormalizedLinearColorStopVec::from_const_slice(LINEAR_COLOR_STOP_{}_ITEMS),\r\n{}}}",
851
        t1,
852
        format_direction(&l.direction, tabs + 1),
853
        t1,
854
        l.extend_mode.format_as_rust_code(tabs + 1),
855
        t1,
856
        l.stops.get_hash(),
857
        t,
858
    )
859
}
860

            
861
fn format_conic_gradient(r: &ConicGradient, tabs: usize) -> String {
862
    let t = String::from("    ").repeat(tabs);
863
    let t1 = String::from("    ").repeat(tabs + 1);
864

            
865
    format!(
866
        "ConicGradient {{\r\n{}extend_mode: {},\r\n{}center: {},\r\n{}angle: {},\r\n{}stops: \
867
         NormalizedRadialColorStopVec::from_const_slice(RADIAL_COLOR_STOP_{}_ITEMS),\r\n{}}}",
868
        t1,
869
        r.extend_mode.format_as_rust_code(tabs + 1),
870
        t1,
871
        format_style_background_position(&r.center, tabs + 1),
872
        t1,
873
        format_angle_value(&r.angle),
874
        t1,
875
        r.stops.get_hash(),
876
        t,
877
    )
878
}
879

            
880
fn format_radial_gradient(r: &RadialGradient, tabs: usize) -> String {
881
    let t = String::from("    ").repeat(tabs);
882
    let t1 = String::from("    ").repeat(tabs + 1);
883
    format!(
884
        "RadialGradient {{\r\n{}shape: {},\r\n{}extend_mode: {},\r\n{}position: {},\r\n{}size: \
885
         RadialGradientSize::{:?},\r\n{}stops: \
886
         NormalizedLinearColorStopVec::from_const_slice(LINEAR_COLOR_STOP_{}_ITEMS),\r\n{}}}",
887
        t1,
888
        r.shape.format_as_rust_code(tabs + 1),
889
        t1,
890
        r.extend_mode.format_as_rust_code(tabs + 1),
891
        t1,
892
        format_style_background_position(&r.position, tabs + 1),
893
        t1,
894
        r.size,
895
        t1,
896
        r.stops.get_hash(),
897
        t,
898
    )
899
}
900

            
901
fn format_linear_color_stops(stops: &[NormalizedLinearColorStop], tabs: usize) -> String {
902
    let t = String::from("    ").repeat(tabs);
903
    stops
904
        .iter()
905
        .map(format_linear_color_stop)
906
        .collect::<Vec<_>>()
907
        .join(&format!(",\r\n{}", t))
908
}
909

            
910
fn format_linear_color_stop(g: &NormalizedLinearColorStop) -> String {
911
    format!(
912
        "NormalizedLinearColorStop {{ offset: {}, color: {} }}",
913
        format_percentage_value(&g.offset),
914
        format_color_or_system(&g.color),
915
    )
916
}
917

            
918
fn format_radial_color_stops(stops: &[NormalizedRadialColorStop], tabs: usize) -> String {
919
    let t = String::from("    ").repeat(tabs);
920
    stops
921
        .iter()
922
        .map(format_radial_color_stop)
923
        .collect::<Vec<_>>()
924
        .join(&format!(",\r\n{}", t))
925
}
926

            
927
fn format_radial_color_stop(g: &NormalizedRadialColorStop) -> String {
928
    format!(
929
        "RadialColorStop {{ angle: {}, color: {} }}",
930
        format_angle_value(&g.angle),
931
        format_color_or_system(&g.color),
932
    )
933
}
934

            
935
fn format_style_filter(st: &StyleFilter, tabs: usize) -> String {
936
    match st {
937
        StyleFilter::Blend(mb) => format!("StyleFilter::Blend({})", mb.format_as_rust_code(tabs)),
938
        StyleFilter::Flood(c) => format!("StyleFilter::Flood({})", format_color_value(c)),
939
        StyleFilter::Blur(m) => format!(
940
            "StyleFilter::Blur(StyleBlur {{ width: {}, height: {} }})",
941
            format_pixel_value(&m.width),
942
            format_pixel_value(&m.height)
943
        ),
944
        StyleFilter::Opacity(pct) => {
945
            format!("StyleFilter::Opacity({})", format_percentage_value(pct))
946
        }
947
        StyleFilter::ColorMatrix(cm) => format!(
948
            "StyleFilter::ColorMatrix(StyleColorMatrix {{ m0: {}, m1: {}, m2: {}, m3: {}, m4: {}, \
949
             m5: {}, m6: {}, m7: {}, m8: {}, m9: {}, m10: {}, m11: {}, m12: {}, m13: {}, m14: {}, \
950
             m15: {}, m16: {}, m17: {}, m18: {}, m19: {} }})",
951
            format_float_value(&cm.m0),
952
            format_float_value(&cm.m1),
953
            format_float_value(&cm.m2),
954
            format_float_value(&cm.m3),
955
            format_float_value(&cm.m4),
956
            format_float_value(&cm.m5),
957
            format_float_value(&cm.m6),
958
            format_float_value(&cm.m7),
959
            format_float_value(&cm.m8),
960
            format_float_value(&cm.m9),
961
            format_float_value(&cm.m10),
962
            format_float_value(&cm.m11),
963
            format_float_value(&cm.m12),
964
            format_float_value(&cm.m13),
965
            format_float_value(&cm.m14),
966
            format_float_value(&cm.m15),
967
            format_float_value(&cm.m16),
968
            format_float_value(&cm.m17),
969
            format_float_value(&cm.m18),
970
            format_float_value(&cm.m19)
971
        ),
972
        StyleFilter::DropShadow(m) => {
973
            format!("StyleFilter::DropShadow({})", m.format_as_rust_code(tabs))
974
        }
975
        StyleFilter::ComponentTransfer => "StyleFilter::ComponentTransfer".to_string(),
976
        StyleFilter::Offset(o) => format!(
977
            "StyleFilter::Offset(StyleFilterOffset {{ x: {}, y: {} }})",
978
            format_pixel_value(&o.x),
979
            format_pixel_value(&o.y)
980
        ),
981
        StyleFilter::Composite(StyleCompositeFilter::Over) => {
982
            "StyleFilter::Composite(StyleCompositeFilter::Over)".to_string()
983
        }
984
        StyleFilter::Composite(StyleCompositeFilter::In) => {
985
            "StyleFilter::Composite(StyleCompositeFilter::In)".to_string()
986
        }
987
        StyleFilter::Composite(StyleCompositeFilter::Atop) => {
988
            "StyleFilter::Composite(StyleCompositeFilter::Atop)".to_string()
989
        }
990
        StyleFilter::Composite(StyleCompositeFilter::Out) => {
991
            "StyleFilter::Composite(StyleCompositeFilter::Out)".to_string()
992
        }
993
        StyleFilter::Composite(StyleCompositeFilter::Xor) => {
994
            "StyleFilter::Composite(StyleCompositeFilter::Xor)".to_string()
995
        }
996
        StyleFilter::Composite(StyleCompositeFilter::Lighter) => {
997
            "StyleFilter::Composite(StyleCompositeFilter::Lighter)".to_string()
998
        }
999
        StyleFilter::Composite(StyleCompositeFilter::Arithmetic(fv)) => format!(
            "StyleFilter::Composite(StyleCompositeFilter::Arithmetic(ArithmeticCoefficients {{ \
             k1: {}, k2: {}, k3: {}, k4: {} }}))",
            format_float_value(&fv.k1),
            format_float_value(&fv.k2),
            format_float_value(&fv.k3),
            format_float_value(&fv.k4)
        ),
        StyleFilter::Brightness(v) => format!("StyleFilter::Brightness({})", format_percentage_value(v)),
        StyleFilter::Contrast(v) => format!("StyleFilter::Contrast({})", format_percentage_value(v)),
        StyleFilter::Grayscale(v) => format!("StyleFilter::Grayscale({})", format_percentage_value(v)),
        StyleFilter::HueRotate(a) => format!("StyleFilter::HueRotate({})", format_angle_value(a)),
        StyleFilter::Invert(v) => format!("StyleFilter::Invert({})", format_percentage_value(v)),
        StyleFilter::Saturate(v) => format!("StyleFilter::Saturate({})", format_percentage_value(v)),
        StyleFilter::Sepia(v) => format!("StyleFilter::Sepia({})", format_percentage_value(v)),
    }
}
fn format_style_transforms(stops: &[StyleTransform], tabs: usize) -> String {
    let t = String::from("    ").repeat(tabs);
    stops
        .iter()
        .map(|s| format_style_transform(s, tabs))
        .collect::<Vec<_>>()
        .join(&format!(",\r\n{}", t))
}
fn format_style_transform(st: &StyleTransform, tabs: usize) -> String {
    let tabs_minus_one = String::from("    ").repeat(tabs);
    let tabs = String::from("    ").repeat(tabs + 1);
    match st {
        StyleTransform::Matrix(m) => format!(
            "StyleTransform::Matrix(StyleTransformMatrix2D {{ a: {}, b: {}, c: {}, d: {}, tx: {}, \
             ty: {} }})",
            format_float_value(&m.a),
            format_float_value(&m.b),
            format_float_value(&m.c),
            format_float_value(&m.d),
            format_float_value(&m.tx),
            format_float_value(&m.ty)
        ),
        StyleTransform::Matrix3D(m) => format!(
            "StyleTransform::Matrix3D(StyleTransformMatrix3D {{\r\n{tabs}m11: {},\r\n{tabs}m12: \
             {},\r\n{tabs}m13: {},\r\n{tabs}m14: {},\r\n{tabs}m21: {},\r\n{tabs}m22: \
             {},\r\n{tabs}m23: {},\r\n{tabs}m24: {},\r\n{tabs}m31: {},\r\n{tabs}m32: \
             {},\r\n{tabs}m33: {},\r\n{tabs}m34: {},\r\n{tabs}m41: {},\r\n{tabs}m42: \
             {},\r\n{tabs}m43: {},\r\n{tabs}m44: {}\r\n{tabs_minus_one}}})",
            format_float_value(&m.m11),
            format_float_value(&m.m12),
            format_float_value(&m.m13),
            format_float_value(&m.m14),
            format_float_value(&m.m21),
            format_float_value(&m.m22),
            format_float_value(&m.m23),
            format_float_value(&m.m24),
            format_float_value(&m.m31),
            format_float_value(&m.m32),
            format_float_value(&m.m33),
            format_float_value(&m.m34),
            format_float_value(&m.m41),
            format_float_value(&m.m42),
            format_float_value(&m.m43),
            format_float_value(&m.m44),
            tabs = tabs,
            tabs_minus_one = tabs_minus_one,
        ),
        StyleTransform::Translate(t) => format!(
            "StyleTransform::Translate(StyleTransformTranslate2D {{ x: {}, y: {} }})",
            format_pixel_value(&t.x),
            format_pixel_value(&t.y)
        ),
        StyleTransform::Translate3D(t) => format!(
            "StyleTransform::Translate3D(StyleTransformTranslate3D {{ x: {}, y: {}, z: {} }})",
            format_pixel_value(&t.x),
            format_pixel_value(&t.y),
            format_pixel_value(&t.z)
        ),
        StyleTransform::TranslateX(x) => {
            format!("StyleTransform::TranslateX({})", format_pixel_value(x))
        }
        StyleTransform::TranslateY(y) => {
            format!("StyleTransform::TranslateY({})", format_pixel_value(y))
        }
        StyleTransform::TranslateZ(z) => {
            format!("StyleTransform::TranslateZ({})", format_pixel_value(z))
        }
        StyleTransform::Rotate(r) => format!("StyleTransform::Rotate({})", format_angle_value(r)),
        StyleTransform::Rotate3D(r) => format!(
            "StyleTransform::Rotate3D(StyleTransformRotate3D {{ {}, {}, {}, {} }})",
            format_float_value(&r.x),
            format_float_value(&r.y),
            format_float_value(&r.z),
            format_angle_value(&r.angle)
        ),
        StyleTransform::RotateX(x) => {
            format!("StyleTransform::RotateX({})", format_angle_value(x))
        }
        StyleTransform::RotateY(y) => {
            format!("StyleTransform::RotateY({})", format_angle_value(y))
        }
        StyleTransform::RotateZ(z) => {
            format!("StyleTransform::RotateZ({})", format_angle_value(z))
        }
        StyleTransform::Scale(s) => format!(
            "StyleTransform::Scale(StyleTransformScale2D {{ x: {}, y: {} }})",
            format_float_value(&s.x),
            format_float_value(&s.y)
        ),
        StyleTransform::Scale3D(s) => format!(
            "StyleTransform::Scale3D(StyleTransformScale3D {{ x: {}, y: {}, z: {} }})",
            format_float_value(&s.x),
            format_float_value(&s.y),
            format_float_value(&s.z)
        ),
        StyleTransform::ScaleX(x) => {
            format!("StyleTransform::ScaleX({})", format_percentage_value(x))
        }
        StyleTransform::ScaleY(y) => {
            format!("StyleTransform::ScaleY({})", format_percentage_value(y))
        }
        StyleTransform::ScaleZ(z) => {
            format!("StyleTransform::ScaleZ({})", format_percentage_value(z))
        }
        StyleTransform::Skew(sk) => format!(
            "StyleTransform::Skew(StyleTransformSkew2D {{ x: {}, y: {} }})",
            format_angle_value(&sk.x),
            format_angle_value(&sk.y)
        ),
        StyleTransform::SkewX(x) => {
            format!("StyleTransform::SkewX({})", format_angle_value(x))
        }
        StyleTransform::SkewY(y) => {
            format!("StyleTransform::SkewY({})", format_angle_value(y))
        }
        StyleTransform::Perspective(dist) => {
            format!("StyleTransform::Perspective({})", format_pixel_value(dist))
        }
    }
}
fn format_font_ids(font_ids: &[StyleFontFamily], tabs: usize) -> String {
    let t = String::from("    ").repeat(tabs);
    font_ids
        .iter()
        .map(|s| s.format_as_rust_code(tabs + 1).to_string())
        .collect::<Vec<_>>()
        .join(&format!(",\r\n{}", t))
}
fn format_style_background_position(b: &StyleBackgroundPosition, tabs: usize) -> String {
    let t = String::from("    ").repeat(tabs);
    let t1 = String::from("    ").repeat(tabs + 1);
    format!(
        "StyleBackgroundPosition {{\r\n{}horizontal: {},\r\n{}vertical: {},\r\n{}}}",
        t1,
        format_background_position_horizontal(&b.horizontal),
        t1,
        format_background_position_vertical(&b.vertical),
        t
    )
}
fn format_background_position_horizontal(b: &BackgroundPositionHorizontal) -> String {
    match b {
        BackgroundPositionHorizontal::Left => "BackgroundPositionHorizontal::Left".to_string(),
        BackgroundPositionHorizontal::Center => "BackgroundPositionHorizontal::Center".to_string(),
        BackgroundPositionHorizontal::Right => "BackgroundPositionHorizontal::Right".to_string(),
        BackgroundPositionHorizontal::Exact(p) => format!(
            "BackgroundPositionHorizontal::Exact({})",
            format_pixel_value(p)
        ),
    }
}
fn format_background_position_vertical(b: &BackgroundPositionVertical) -> String {
    match b {
        BackgroundPositionVertical::Top => "BackgroundPositionVertical::Top".to_string(),
        BackgroundPositionVertical::Center => "BackgroundPositionVertical::Center".to_string(),
        BackgroundPositionVertical::Bottom => "BackgroundPositionVertical::Bottom".to_string(),
        BackgroundPositionVertical::Exact(p) => format!(
            "BackgroundPositionVertical::Exact({})",
            format_pixel_value(p)
        ),
    }
}
// Border side style impls moved to `props/style/border.rs`.
// Several small impls were moved to their respective modules.
// StyleTransformOrigin impl moved to `props/style/transform.rs`.
// Implementations for small property types were moved to their respective
// modules to keep the implementations next to the type definitions.