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::fmt::Write;
12
use 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)]
17
use crate::{
18
    corety::*,
19
    css::*,
20
    props::{basic::*, layout::*, property::*, style::*},
21
};
22

            
23
/// Formats a value as const-compatible Rust source code.
24
pub 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.
29
pub trait GetHash {
30
    fn get_hash(&self) -> u64;
31
}
32

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

            
42
// In order to generate the Rust code, all items that implement Drop
43
// have to be declared before being used.
44

            
45
impl<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)]
54
pub 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

            
68
impl 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
70
#[must_use] pub fn format_pixel_value(p: &PixelValue) -> String {
272
70
    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
70
    let abs_val = libm::fabsf(value);
278
70
    let sign: isize = if value < 0.0 { -1 } else { 1 };
279
70
    let pre_comma = f32_to_isize(libm::floorf(abs_val)) * sign;
280
70
    let post_comma = f32_to_isize(libm::roundf((abs_val - libm::floorf(abs_val)) * 100.0));
281

            
282
70
    match p.metric {
283
1
        SizeMetric::Pt => format!(
284
1
            "PixelValue::const_pt_fractional({pre_comma}, {post_comma})"
285
        ),
286
1
        SizeMetric::Em => format!(
287
1
            "PixelValue::const_em_fractional({pre_comma}, {post_comma})"
288
        ),
289
68
        other => format!(
290
68
            "PixelValue::const_from_metric_fractional(SizeMetric::{other:?}, {pre_comma}, {post_comma})"
291
        ),
292
    }
293
70
}
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)]
301
140
const fn f32_to_isize(v: f32) -> isize {
302
140
    v as isize
303
140
}
304

            
305
/// Formats a `PixelValueNoPercent` as a const-compatible Rust constructor call.
306
36
#[must_use] pub fn format_pixel_value_no_percent(p: &PixelValueNoPercent) -> String {
307
36
    format!(
308
36
        "PixelValueNoPercent {{ inner: {} }}",
309
36
        format_pixel_value(&p.inner)
310
    )
311
36
}
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
44
#[must_use] pub fn format_color_value(c: &ColorU) -> String {
352
44
    format!(
353
44
        "ColorU {{ r: {}, g: {}, b: {}, a: {} }}",
354
        c.r, c.g, c.b, c.a
355
    )
356
44
}
357

            
358
fn 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

            
371
fn 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

            
394
macro_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

            
408
impl_float_value_fmt!(LayoutFlexGrow);
409
impl_float_value_fmt!(LayoutFlexShrink);
410

            
411
macro_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

            
425
impl_percentage_value_fmt!(StyleLineHeight);
426
impl_percentage_value_fmt!(StyleOpacity);
427

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

            
442
impl_pixel_value_fmt!(StyleTabSize);
443
impl_pixel_value_fmt!(StyleBorderTopLeftRadius);
444
impl_pixel_value_fmt!(StyleBorderBottomLeftRadius);
445
impl_pixel_value_fmt!(StyleBorderTopRightRadius);
446
impl_pixel_value_fmt!(StyleBorderBottomRightRadius);
447

            
448
impl_pixel_value_fmt!(LayoutBorderTopWidth);
449
impl_pixel_value_fmt!(LayoutBorderLeftWidth);
450
impl_pixel_value_fmt!(LayoutBorderRightWidth);
451
impl_pixel_value_fmt!(LayoutBorderBottomWidth);
452
impl_pixel_value_fmt!(StyleLetterSpacing);
453
impl_pixel_value_fmt!(StyleWordSpacing);
454
impl_pixel_value_fmt!(StyleFontSize);
455

            
456
impl_pixel_value_fmt!(LayoutMarginTop);
457
impl_pixel_value_fmt!(LayoutMarginBottom);
458
impl_pixel_value_fmt!(LayoutMarginRight);
459
impl_pixel_value_fmt!(LayoutMarginLeft);
460

            
461
impl_pixel_value_fmt!(LayoutPaddingTop);
462
impl_pixel_value_fmt!(LayoutPaddingBottom);
463
impl_pixel_value_fmt!(LayoutPaddingRight);
464
impl_pixel_value_fmt!(LayoutPaddingLeft);
465
impl_pixel_value_fmt!(LayoutPaddingInlineStart);
466
impl_pixel_value_fmt!(LayoutPaddingInlineEnd);
467

            
468
impl FormatAsRustCode for LayoutWidth {
469
6
    fn format_as_rust_code(&self, _tabs: usize) -> String {
470
6
        match self {
471
            Self::Auto => "LayoutWidth::Auto".to_string(),
472
6
            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
6
    }
479
}
480

            
481
impl 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

            
494
impl_pixel_value_fmt!(LayoutMinHeight);
495
impl_pixel_value_fmt!(LayoutMinWidth);
496
impl_pixel_value_fmt!(LayoutMaxWidth);
497
impl_pixel_value_fmt!(LayoutMaxHeight);
498
impl_pixel_value_fmt!(LayoutTop);
499
impl_pixel_value_fmt!(LayoutInsetBottom);
500

            
501
impl_pixel_value_fmt!(LayoutRight);
502
impl_pixel_value_fmt!(LayoutLeft);
503

            
504
// LayoutFlexBasis implementation moved to `props/layout/flex.rs`.
505

            
506
impl_pixel_value_fmt!(LayoutColumnGap);
507
impl_pixel_value_fmt!(LayoutRowGap);
508

            
509
impl 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

            
524
impl 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

            
534
impl_color_value_fmt!(StyleTextColor);
535
impl_color_value_fmt!(StyleBorderTopColor);
536
impl_color_value_fmt!(StyleBorderLeftColor);
537
impl_color_value_fmt!(StyleBorderRightColor);
538
impl_color_value_fmt!(StyleBorderBottomColor);
539

            
540
impl_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

            
560
impl_enum_fmt!(StyleHyphens, None, Manual, Auto);
561
impl_enum_fmt!(StyleWordBreak, Normal, BreakAll, KeepAll, BreakWord);
562
impl_enum_fmt!(StyleOverflowWrap, Normal, Anywhere, BreakWord);
563
impl_enum_fmt!(StyleLineBreak, Auto, Loose, Normal, Strict, Anywhere);
564
impl_enum_fmt!(StyleObjectFit, Fill, Contain, Cover, None, ScaleDown);
565
impl_enum_fmt!(StyleTextOverflow, Clip, Ellipsis);
566

            
567
impl FormatAsRustCode for StyleObjectPosition {
568
    fn format_as_rust_code(&self, _tabs: usize) -> String {
569
        format!("StyleObjectPosition {{ horizontal: {}, vertical: {} }}",
570
            format_background_position_horizontal(&self.horizontal),
571
            format_background_position_vertical(&self.vertical))
572
    }
573
}
574

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

            
584
impl_enum_fmt!(StyleTextOrientation, Mixed, Upright, Sideways);
585
impl_enum_fmt!(StyleTextAlignLast, Auto, Start, End, Left, Right, Center, Justify);
586

            
587
impl_enum_fmt!(StyleTextTransform, None, Capitalize, Uppercase, Lowercase, FullWidth);
588

            
589
impl_enum_fmt!(StyleDirection, Ltr, Rtl);
590

            
591
impl_enum_fmt!(StyleWhiteSpace, Normal, Pre, Nowrap, PreWrap, PreLine, BreakSpaces);
592

            
593
impl_enum_fmt!(StyleVisibility, Visible, Hidden, Collapse);
594

            
595
impl_enum_fmt!(LayoutWritingMode, HorizontalTb, VerticalRl, VerticalLr);
596

            
597
impl_enum_fmt!(LayoutClear, None, Left, Right, Both);
598

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

            
633
impl_enum_fmt!(
634
    BorderStyle,
635
    None,
636
    Solid,
637
    Double,
638
    Dotted,
639
    Dashed,
640
    Hidden,
641
    Groove,
642
    Ridge,
643
    Inset,
644
    Outset
645
);
646

            
647
impl_enum_fmt!(
648
    StyleBackgroundRepeat,
649
    NoRepeat,
650
    PatternRepeat,
651
    RepeatX,
652
    RepeatY
653
);
654

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

            
682
impl_enum_fmt!(LayoutFloat, Left, Right, None);
683

            
684
impl_enum_fmt!(LayoutBoxSizing, ContentBox, BorderBox);
685

            
686
impl_enum_fmt!(LayoutFlexDirection, Row, RowReverse, Column, ColumnReverse);
687

            
688
impl_enum_fmt!(LayoutFlexWrap, Wrap, NoWrap, WrapReverse);
689

            
690
impl_enum_fmt!(
691
    LayoutJustifyContent,
692
    Start,
693
    End,
694
    FlexStart,
695
    FlexEnd,
696
    Center,
697
    SpaceBetween,
698
    SpaceAround,
699
    SpaceEvenly
700
);
701

            
702
impl_enum_fmt!(LayoutAlignItems, Stretch, Center, Start, End, Baseline);
703

            
704
impl_enum_fmt!(
705
    LayoutAlignContent,
706
    Start,
707
    End,
708
    Stretch,
709
    Center,
710
    SpaceBetween,
711
    SpaceAround
712
);
713

            
714
impl_enum_fmt!(Shape, Circle, Ellipse);
715

            
716
impl_enum_fmt!(LayoutOverflow, Auto, Scroll, Visible, Hidden, Clip);
717

            
718
impl_enum_fmt!(StyleTextAlign, Center, Left, Right, Justify, Start, End);
719

            
720
impl_enum_fmt!(StyleUserSelect, Auto, Text, None, All);
721

            
722
impl_enum_fmt!(StyleTextDecoration, None, Underline, Overline, LineThrough);
723

            
724
impl_enum_fmt!(
725
    DirectionCorner,
726
    Right,
727
    Left,
728
    Top,
729
    Bottom,
730
    TopRight,
731
    TopLeft,
732
    BottomRight,
733
    BottomLeft
734
);
735

            
736
impl_enum_fmt!(ExtendMode, Clamp, Repeat);
737

            
738
impl_enum_fmt!(StyleBackfaceVisibility, Visible, Hidden);
739

            
740
impl_enum_fmt!(StyleUnicodeBidi, Normal, Embed, Isolate, BidiOverride, IsolateOverride, Plaintext);
741
impl_enum_fmt!(StyleTextBoxTrim, None, TrimStart, TrimEnd, TrimBoth);
742
impl_enum_fmt!(TextBoxEdgeOver, Auto, Text, Cap, Ex, Ideographic, IdeographicInk);
743
impl_enum_fmt!(TextBoxEdgeUnder, Auto, Text, Alphabetic, Ideographic, IdeographicInk);
744
impl FormatAsRustCode for StyleTextBoxEdge {
745
    fn format_as_rust_code(&self, tabs: usize) -> String {
746
        format!(
747
            "StyleTextBoxEdge {{ over: {}, under: {} }}",
748
            self.over.format_as_rust_code(tabs),
749
            self.under.format_as_rust_code(tabs)
750
        )
751
    }
752
}
753
impl_enum_fmt!(StyleDominantBaseline, Auto, TextBottom, Alphabetic, Ideographic, Middle, Central, Mathematical, Hanging, TextTop);
754
impl_enum_fmt!(StyleAlignmentBaseline, Baseline, TextBottom, Alphabetic, Ideographic, Middle, Central, Mathematical, TextTop);
755
impl_enum_fmt!(StyleBaselineSource, Auto, First, Last);
756
impl_enum_fmt!(StyleLineFitEdge, Leading, Text, Cap, Ex, Ideographic, IdeographicInk, Alphabetic);
757
impl_enum_fmt!(StyleInitialLetterAlign, Auto, Alphabetic, Hanging, Ideographic);
758
impl_enum_fmt!(StyleInitialLetterWrap, None, First, All, Grid);
759
impl_enum_fmt!(StyleScrollbarGutter, Auto, Stable, StableBothEdges);
760

            
761
impl FormatAsRustCode for StyleOverflowClipMargin {
762
    fn format_as_rust_code(&self, tabs: usize) -> String {
763
        format!("StyleOverflowClipMargin {{ inner: {} }}", self.inner.format_as_rust_code(tabs))
764
    }
765
}
766

            
767
impl FormatAsRustCode for StyleClipRect {
768
    fn format_as_rust_code(&self, _tabs: usize) -> String {
769
        fn fmt_edge(o: OptionF32) -> String {
770
            match o {
771
                OptionF32::None => String::from("OptionF32::None"),
772
                OptionF32::Some(v) => format!("OptionF32::Some({v:?})"),
773
            }
774
        }
775
        format!(
776
            "StyleClipRect {{ top: {}, right: {}, bottom: {}, left: {} }}",
777
            fmt_edge(self.top),
778
            fmt_edge(self.right),
779
            fmt_edge(self.bottom),
780
            fmt_edge(self.left),
781
        )
782
    }
783
}
784

            
785
// Complex type implementations
786

            
787
fn format_style_background_size(c: &StyleBackgroundSize) -> String {
788
    match c {
789
        StyleBackgroundSize::Contain => String::from("StyleBackgroundSize::Contain"),
790
        StyleBackgroundSize::Cover => String::from("StyleBackgroundSize::Cover"),
791
        StyleBackgroundSize::ExactSize(size) => format!(
792
            "StyleBackgroundSize::ExactSize(PixelValueSize {{ width: {}, height: {} }})",
793
            format_pixel_value(&size.width),
794
            format_pixel_value(&size.height)
795
        ),
796
    }
797
}
798

            
799
// Scrollbar-related impls moved to `props/style/scrollbar.rs`
800

            
801
/// Formats a `ScrollbarInfo` as a const-compatible Rust struct literal.
802
6
#[must_use] pub fn format_scrollbar_info(s: &ScrollbarInfo, tabs: usize) -> String {
803
6
    let t = String::from("    ").repeat(tabs);
804
6
    let t1 = String::from("    ").repeat(tabs + 1);
805
6
    format!(
806
6
        "ScrollbarInfo {{\r\n{}width: {},\r\n{}padding_left: {},\r\n{}padding_right: \
807
6
         {},\r\n{}track: {},\r\n{}thumb: {},\r\n{}button: {},\r\n{}button: {},\r\n{}resizer: \
808
6
         {},\r\n{}}}",
809
        t1,
810
6
        s.width.format_as_rust_code(tabs + 1),
811
        t1,
812
6
        s.padding_left.format_as_rust_code(tabs + 1),
813
        t1,
814
6
        s.padding_right.format_as_rust_code(tabs + 1),
815
        t1,
816
6
        format_style_background_content(&s.track, tabs + 1),
817
        t1,
818
6
        format_style_background_content(&s.thumb, tabs + 1),
819
        t1,
820
6
        format_style_background_content(&s.button, tabs + 1),
821
        t1,
822
6
        format_style_background_content(&s.corner, tabs + 1),
823
        t1,
824
6
        format_style_background_content(&s.resizer, tabs + 1),
825
        t
826
    )
827
6
}
828

            
829
// Background/filter vec impls moved to their respective modules.
830

            
831
30
fn format_style_background_content(content: &StyleBackgroundContent, tabs: usize) -> String {
832
30
    match content {
833
        StyleBackgroundContent::LinearGradient(l) => format!(
834
            "StyleBackgroundContent::LinearGradient({})",
835
            format_linear_gradient(l, tabs)
836
        ),
837
        StyleBackgroundContent::RadialGradient(r) => format!(
838
            "StyleBackgroundContent::RadialGradient({})",
839
            format_radial_gradient(r, tabs)
840
        ),
841
        StyleBackgroundContent::ConicGradient(r) => format!(
842
            "StyleBackgroundContent::ConicGradient({})",
843
            format_conic_gradient(r, tabs)
844
        ),
845
        StyleBackgroundContent::Image(id) => format!("StyleBackgroundContent::Image({id:?})"),
846
30
        StyleBackgroundContent::Color(c) => {
847
30
            format!("StyleBackgroundContent::Color({})", format_color_value(c))
848
        }
849
        StyleBackgroundContent::SystemColor(s) => {
850
            use crate::props::basic::color::SystemColorRef;
851
            let variant = match s {
852
                SystemColorRef::Text => "Text",
853
                SystemColorRef::Background => "Background",
854
                SystemColorRef::Accent => "Accent",
855
                SystemColorRef::AccentText => "AccentText",
856
                SystemColorRef::ButtonFace => "ButtonFace",
857
                SystemColorRef::ButtonText => "ButtonText",
858
                SystemColorRef::WindowBackground => "WindowBackground",
859
                SystemColorRef::SelectionBackground => "SelectionBackground",
860
                SystemColorRef::SelectionText => "SelectionText",
861
            };
862
            format!("StyleBackgroundContent::SystemColor(SystemColorRef::{variant})")
863
        }
864
    }
865
30
}
866

            
867
fn format_direction(d: &Direction, tabs: usize) -> String {
868
    match d {
869
        Direction::Angle(fv) => format!("Direction::Angle({})", format_angle_value(fv)),
870
        Direction::FromTo(DirectionCorners { dir_from, dir_to }) => format!(
871
            "Direction::FromTo(DirectionCorners {{ dir_from: {}, dir_to: {} }})",
872
            dir_from.format_as_rust_code(tabs + 1),
873
            dir_to.format_as_rust_code(tabs + 1)
874
        ),
875
    }
876
}
877

            
878
fn format_linear_gradient(l: &LinearGradient, tabs: usize) -> String {
879
    let t = String::from("    ").repeat(tabs);
880
    let t1 = String::from("    ").repeat(tabs + 1);
881
    format!(
882
        "LinearGradient {{\r\n{}direction: {},\r\n{}extend_mode: {},\r\n{}stops: \
883
         NormalizedLinearColorStopVec::from_const_slice(LINEAR_COLOR_STOP_{}_ITEMS),\r\n{}}}",
884
        t1,
885
        format_direction(&l.direction, tabs + 1),
886
        t1,
887
        l.extend_mode.format_as_rust_code(tabs + 1),
888
        t1,
889
        l.stops.get_hash(),
890
        t,
891
    )
892
}
893

            
894
fn format_conic_gradient(r: &ConicGradient, tabs: usize) -> String {
895
    let t = String::from("    ").repeat(tabs);
896
    let t1 = String::from("    ").repeat(tabs + 1);
897

            
898
    format!(
899
        "ConicGradient {{\r\n{}extend_mode: {},\r\n{}center: {},\r\n{}angle: {},\r\n{}stops: \
900
         NormalizedRadialColorStopVec::from_const_slice(RADIAL_COLOR_STOP_{}_ITEMS),\r\n{}}}",
901
        t1,
902
        r.extend_mode.format_as_rust_code(tabs + 1),
903
        t1,
904
        format_style_background_position(&r.center, tabs + 1),
905
        t1,
906
        format_angle_value(&r.angle),
907
        t1,
908
        r.stops.get_hash(),
909
        t,
910
    )
911
}
912

            
913
fn format_radial_gradient(r: &RadialGradient, tabs: usize) -> String {
914
    let t = String::from("    ").repeat(tabs);
915
    let t1 = String::from("    ").repeat(tabs + 1);
916
    format!(
917
        "RadialGradient {{\r\n{}shape: {},\r\n{}extend_mode: {},\r\n{}position: {},\r\n{}size: \
918
         RadialGradientSize::{:?},\r\n{}stops: \
919
         NormalizedLinearColorStopVec::from_const_slice(LINEAR_COLOR_STOP_{}_ITEMS),\r\n{}}}",
920
        t1,
921
        r.shape.format_as_rust_code(tabs + 1),
922
        t1,
923
        r.extend_mode.format_as_rust_code(tabs + 1),
924
        t1,
925
        format_style_background_position(&r.position, tabs + 1),
926
        t1,
927
        r.size,
928
        t1,
929
        r.stops.get_hash(),
930
        t,
931
    )
932
}
933

            
934
fn format_linear_color_stops(stops: &[NormalizedLinearColorStop], tabs: usize) -> String {
935
    let t = String::from("    ").repeat(tabs);
936
    stops
937
        .iter()
938
        .map(format_linear_color_stop)
939
        .collect::<Vec<_>>()
940
        .join(&format!(",\r\n{t}"))
941
}
942

            
943
fn format_linear_color_stop(g: &NormalizedLinearColorStop) -> String {
944
    format!(
945
        "NormalizedLinearColorStop {{ offset: {}, color: {} }}",
946
        format_percentage_value(&g.offset),
947
        format_color_or_system(g.color),
948
    )
949
}
950

            
951
fn format_radial_color_stops(stops: &[NormalizedRadialColorStop], tabs: usize) -> String {
952
    let t = String::from("    ").repeat(tabs);
953
    stops
954
        .iter()
955
        .map(format_radial_color_stop)
956
        .collect::<Vec<_>>()
957
        .join(&format!(",\r\n{t}"))
958
}
959

            
960
fn format_radial_color_stop(g: &NormalizedRadialColorStop) -> String {
961
    format!(
962
        "RadialColorStop {{ angle: {}, color: {} }}",
963
        format_angle_value(&g.angle),
964
        format_color_or_system(g.color),
965
    )
966
}
967

            
968
fn format_style_filter(st: &StyleFilter, tabs: usize) -> String {
969
    match st {
970
        StyleFilter::Blend(mb) => format!("StyleFilter::Blend({})", mb.format_as_rust_code(tabs)),
971
        StyleFilter::Flood(c) => format!("StyleFilter::Flood({})", format_color_value(c)),
972
        StyleFilter::Blur(m) => format!(
973
            "StyleFilter::Blur(StyleBlur {{ width: {}, height: {} }})",
974
            format_pixel_value(&m.width),
975
            format_pixel_value(&m.height)
976
        ),
977
        StyleFilter::Opacity(pct) => {
978
            format!("StyleFilter::Opacity({})", format_percentage_value(pct))
979
        }
980
        StyleFilter::ColorMatrix(cm) => format!(
981
            "StyleFilter::ColorMatrix(StyleColorMatrix {{ m0: {}, m1: {}, m2: {}, m3: {}, m4: {}, \
982
             m5: {}, m6: {}, m7: {}, m8: {}, m9: {}, m10: {}, m11: {}, m12: {}, m13: {}, m14: {}, \
983
             m15: {}, m16: {}, m17: {}, m18: {}, m19: {} }})",
984
            format_float_value(&cm.m0),
985
            format_float_value(&cm.m1),
986
            format_float_value(&cm.m2),
987
            format_float_value(&cm.m3),
988
            format_float_value(&cm.m4),
989
            format_float_value(&cm.m5),
990
            format_float_value(&cm.m6),
991
            format_float_value(&cm.m7),
992
            format_float_value(&cm.m8),
993
            format_float_value(&cm.m9),
994
            format_float_value(&cm.m10),
995
            format_float_value(&cm.m11),
996
            format_float_value(&cm.m12),
997
            format_float_value(&cm.m13),
998
            format_float_value(&cm.m14),
999
            format_float_value(&cm.m15),
            format_float_value(&cm.m16),
            format_float_value(&cm.m17),
            format_float_value(&cm.m18),
            format_float_value(&cm.m19)
        ),
        StyleFilter::DropShadow(m) => {
            format!("StyleFilter::DropShadow({})", m.format_as_rust_code(tabs))
        }
        StyleFilter::ComponentTransfer => "StyleFilter::ComponentTransfer".to_string(),
        StyleFilter::Offset(o) => format!(
            "StyleFilter::Offset(StyleFilterOffset {{ x: {}, y: {} }})",
            format_pixel_value(&o.x),
            format_pixel_value(&o.y)
        ),
        StyleFilter::Composite(StyleCompositeFilter::Over) => {
            "StyleFilter::Composite(StyleCompositeFilter::Over)".to_string()
        }
        StyleFilter::Composite(StyleCompositeFilter::In) => {
            "StyleFilter::Composite(StyleCompositeFilter::In)".to_string()
        }
        StyleFilter::Composite(StyleCompositeFilter::Atop) => {
            "StyleFilter::Composite(StyleCompositeFilter::Atop)".to_string()
        }
        StyleFilter::Composite(StyleCompositeFilter::Out) => {
            "StyleFilter::Composite(StyleCompositeFilter::Out)".to_string()
        }
        StyleFilter::Composite(StyleCompositeFilter::Xor) => {
            "StyleFilter::Composite(StyleCompositeFilter::Xor)".to_string()
        }
        StyleFilter::Composite(StyleCompositeFilter::Lighter) => {
            "StyleFilter::Composite(StyleCompositeFilter::Lighter)".to_string()
        }
        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}"))
}
#[allow(clippy::too_many_lines)] // large but cohesive: single-purpose CSS parser/formatter/dispatch table (one branch per property/variant)
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))
        .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.