1use core::fmt;
12
13use crate::data_type::{CssString, FitContent, Length, LengthPercentage, MaxContent, Percentage};
14use crate::to_css::{write_number, ToCss};
15
16#[derive(Clone, Debug, PartialEq)]
21pub enum Size {
22 Auto,
24 LengthPercentage(LengthPercentage),
26 MaxContent,
28 MinContent,
30 FitContent(FitContent),
32 None,
34}
35
36impl ToCss for Size {
37 fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
38 match self {
39 Size::Auto => dest.write_str("auto"),
40 Size::LengthPercentage(lp) => lp.to_css(dest),
41 Size::MaxContent => dest.write_str("max-content"),
42 Size::MinContent => dest.write_str("min-content"),
43 Size::FitContent(fc) => fc.to_css(dest),
44 Size::None => dest.write_str("none"),
45 }
46 }
47}
48
49impl From<Length> for Size {
50 fn from(l: Length) -> Self {
51 Self::LengthPercentage(l.into())
52 }
53}
54
55impl From<Percentage> for Size {
56 fn from(p: Percentage) -> Self {
57 Self::LengthPercentage(p.into())
58 }
59}
60
61impl From<LengthPercentage> for Size {
62 fn from(lp: LengthPercentage) -> Self {
63 Self::LengthPercentage(lp)
64 }
65}
66
67impl From<MaxContent> for Size {
68 fn from(_: MaxContent) -> Self {
69 Self::MaxContent
70 }
71}
72
73impl From<FitContent> for Size {
74 fn from(fc: FitContent) -> Self {
75 Self::FitContent(fc)
76 }
77}
78
79#[derive(Clone, Debug, PartialEq)]
83pub enum FlexBasis {
84 Auto,
86 Content,
88 LengthPercentage(LengthPercentage),
90}
91
92impl ToCss for FlexBasis {
93 fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
94 match self {
95 FlexBasis::Auto => dest.write_str("auto"),
96 FlexBasis::Content => dest.write_str("content"),
97 FlexBasis::LengthPercentage(lp) => lp.to_css(dest),
98 }
99 }
100}
101
102impl From<Length> for FlexBasis {
103 fn from(l: Length) -> Self {
104 Self::LengthPercentage(l.into())
105 }
106}
107
108impl From<Percentage> for FlexBasis {
109 fn from(p: Percentage) -> Self {
110 Self::LengthPercentage(p.into())
111 }
112}
113
114impl From<LengthPercentage> for FlexBasis {
115 fn from(lp: LengthPercentage) -> Self {
116 Self::LengthPercentage(lp)
117 }
118}
119
120#[derive(Clone, Debug, PartialEq)]
124pub enum LineHeight {
125 Normal,
127 Number(f32),
129 LengthPercentage(LengthPercentage),
131}
132
133impl ToCss for LineHeight {
134 fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
135 match self {
136 LineHeight::Normal => dest.write_str("normal"),
137 LineHeight::Number(n) => write_number(dest, *n),
138 LineHeight::LengthPercentage(lp) => lp.to_css(dest),
139 }
140 }
141}
142
143impl From<Length> for LineHeight {
144 fn from(l: Length) -> Self {
145 Self::LengthPercentage(l.into())
146 }
147}
148
149impl From<Percentage> for LineHeight {
150 fn from(p: Percentage) -> Self {
151 Self::LengthPercentage(p.into())
152 }
153}
154
155impl From<LengthPercentage> for LineHeight {
156 fn from(lp: LengthPercentage) -> Self {
157 Self::LengthPercentage(lp)
158 }
159}
160
161impl From<f32> for LineHeight {
162 fn from(v: f32) -> Self {
163 Self::Number(v)
164 }
165}
166
167#[derive(Clone, Debug, PartialEq)]
173pub enum ImageRef {
174 None,
176 Url(CssString),
178 Gradient(crate::Gradient),
180}
181
182impl ToCss for ImageRef {
183 fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
184 match self {
185 ImageRef::None => dest.write_str("none"),
186 ImageRef::Url(s) => {
187 dest.write_str("url(")?;
188 s.to_css(dest)?;
189 dest.write_char(')')
190 }
191 ImageRef::Gradient(g) => g.to_css(dest),
192 }
193 }
194}
195
196impl From<crate::Gradient> for ImageRef {
197 fn from(g: crate::Gradient) -> Self {
198 Self::Gradient(g)
199 }
200}
201
202#[derive(Clone, Debug, PartialEq)]
207pub struct BorderRadius {
208 pub horizontal: [LengthPercentage; 4],
210 pub vertical: Option<[LengthPercentage; 4]>,
212}
213
214impl BorderRadius {
215 pub fn all(v: impl Into<LengthPercentage>) -> Self {
217 let v = v.into();
218 Self {
219 horizontal: [v.clone(), v.clone(), v.clone(), v],
220 vertical: None,
221 }
222 }
223
224 pub fn corners(
226 tl: impl Into<LengthPercentage>,
227 tr: impl Into<LengthPercentage>,
228 br: impl Into<LengthPercentage>,
229 bl: impl Into<LengthPercentage>,
230 ) -> Self {
231 Self {
232 horizontal: [tl.into(), tr.into(), br.into(), bl.into()],
233 vertical: None,
234 }
235 }
236
237 pub fn elliptical(horizontal: [LengthPercentage; 4], vertical: [LengthPercentage; 4]) -> Self {
239 Self {
240 horizontal,
241 vertical: Some(vertical),
242 }
243 }
244}
245
246impl ToCss for BorderRadius {
247 fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
248 write_four(dest, &self.horizontal)?;
249 if let Some(v) = &self.vertical {
250 dest.write_str(" / ")?;
251 write_four(dest, v)?;
252 }
253 Ok(())
254 }
255}
256
257fn write_four(dest: &mut dyn fmt::Write, v: &[LengthPercentage; 4]) -> fmt::Result {
258 for (i, item) in v.iter().enumerate() {
259 if i > 0 {
260 dest.write_char(' ')?;
261 }
262 item.to_css(dest)?;
263 }
264 Ok(())
265}
266
267#[derive(Copy, Clone, Debug, PartialEq)]
273pub enum GridLine {
274 Auto,
276 Number(i32),
278 Span(u32),
280}
281
282impl ToCss for GridLine {
283 fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
284 match self {
285 GridLine::Auto => dest.write_str("auto"),
286 GridLine::Number(n) => write!(dest, "{n}"),
287 GridLine::Span(n) => write!(dest, "span {n}"),
288 }
289 }
290}
291
292#[derive(Clone, Debug, PartialEq, Eq, Hash)]
297pub struct GridTemplate(pub String);
298
299impl GridTemplate {
300 pub fn tracks(tracks: impl IntoIterator<Item = impl Into<String>>) -> Self {
303 let mut out = String::new();
304 for (i, t) in tracks.into_iter().enumerate() {
305 if i > 0 {
306 out.push(' ');
307 }
308 out.push_str(&t.into());
309 }
310 Self(out)
311 }
312}
313
314impl ToCss for GridTemplate {
315 fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
316 dest.write_str(&self.0)
317 }
318}
319
320#[derive(Clone, Debug, PartialEq)]
325pub struct Repeated<T>(pub Vec<T>);
326
327impl<T> Repeated<T> {
328 pub fn new(v: impl IntoIterator<Item = T>) -> Self {
330 Self(v.into_iter().collect())
331 }
332}
333
334impl<T: ToCss> ToCss for Repeated<T> {
335 fn to_css(&self, dest: &mut dyn fmt::Write) -> fmt::Result {
336 for (i, item) in self.0.iter().enumerate() {
337 if i > 0 {
338 dest.write_str(", ")?;
339 }
340 item.to_css(dest)?;
341 }
342 Ok(())
343 }
344}
345
346#[cfg(test)]
347mod tests {
348 use super::*;
349 use crate::data_type::{ColorStop, Gradient, Length, NamedColor};
350 use crate::ext::*;
351
352 #[test]
353 fn size_keywords() {
354 assert_eq!(Size::Auto.to_css_string(), "auto");
355 assert_eq!(Size::MaxContent.to_css_string(), "max-content");
356 assert_eq!(Size::MinContent.to_css_string(), "min-content");
357 assert_eq!(Size::None.to_css_string(), "none");
358 }
359
360 #[test]
361 fn size_from_lengths_and_percentages() {
362 let from_len: Size = px(8).into();
363 let from_pct: Size = 50.percent().into();
364 let from_lp: Size = LengthPercentage::Length(Length::Px(4.0)).into();
365 let from_mc: Size = MaxContent.into();
366 let from_fc: Size = FitContent::keyword().into();
367 assert_eq!(from_len.to_css_string(), "8px");
368 assert_eq!(from_pct.to_css_string(), "50%");
369 assert_eq!(from_lp.to_css_string(), "4px");
370 assert_eq!(from_mc.to_css_string(), "max-content");
371 assert_eq!(from_fc.to_css_string(), "fit-content");
372 }
373
374 #[test]
375 fn size_fit_content_with_limit() {
376 let s = Size::FitContent(FitContent::with_limit(px(200)));
377 assert_eq!(s.to_css_string(), "fit-content(200px)");
378 }
379
380 #[test]
381 fn flex_basis_variants() {
382 assert_eq!(FlexBasis::Auto.to_css_string(), "auto");
383 assert_eq!(FlexBasis::Content.to_css_string(), "content");
384 let from_len: FlexBasis = px(120).into();
385 let from_pct: FlexBasis = 25.percent().into();
386 let from_lp: FlexBasis = LengthPercentage::Length(Length::Px(8.0)).into();
387 assert_eq!(from_len.to_css_string(), "120px");
388 assert_eq!(from_pct.to_css_string(), "25%");
389 assert_eq!(from_lp.to_css_string(), "8px");
390 }
391
392 #[test]
393 fn line_height_variants() {
394 assert_eq!(LineHeight::Normal.to_css_string(), "normal");
395 let n: LineHeight = 1.5_f32.into();
396 let from_len: LineHeight = px(20).into();
397 let from_pct: LineHeight = 150.percent().into();
398 let from_lp: LineHeight = LengthPercentage::Length(Length::Px(10.0)).into();
399 assert_eq!(n.to_css_string(), "1.5");
400 assert_eq!(from_len.to_css_string(), "20px");
401 assert_eq!(from_pct.to_css_string(), "150%");
402 assert_eq!(from_lp.to_css_string(), "10px");
403 }
404
405 #[test]
406 fn image_ref_variants() {
407 assert_eq!(ImageRef::None.to_css_string(), "none");
408 assert_eq!(
409 ImageRef::Url(CssString::new("a.png")).to_css_string(),
410 "url(\"a.png\")"
411 );
412 let g = Gradient::linear_to_bottom([ColorStop::new(NamedColor::Red.into())]);
413 let r: ImageRef = g.into();
414 assert_eq!(r.to_css_string(), "linear-gradient(to bottom, red)");
415 }
416
417 #[test]
418 fn border_radius_uniform() {
419 let r = BorderRadius::all(px(8));
420 assert_eq!(r.to_css_string(), "8px 8px 8px 8px");
421 }
422
423 #[test]
424 fn border_radius_corners() {
425 let r = BorderRadius::corners(px(2), px(4), px(6), px(8));
426 assert_eq!(r.to_css_string(), "2px 4px 6px 8px");
427 }
428
429 #[test]
430 fn border_radius_elliptical() {
431 let h = [px(2).into(), px(4).into(), px(6).into(), px(8).into()];
432 let v = [px(20).into(), px(40).into(), px(60).into(), px(80).into()];
433 let r = BorderRadius::elliptical(h, v);
434 assert_eq!(r.to_css_string(), "2px 4px 6px 8px / 20px 40px 60px 80px");
435 }
436
437 #[test]
438 fn grid_line_variants() {
439 assert_eq!(GridLine::Auto.to_css_string(), "auto");
440 assert_eq!(GridLine::Number(1).to_css_string(), "1");
441 assert_eq!(GridLine::Number(-1).to_css_string(), "-1");
442 assert_eq!(GridLine::Span(2).to_css_string(), "span 2");
443 }
444
445 #[test]
446 fn grid_template_joins_tracks() {
447 let t = GridTemplate::tracks(["1fr", "auto", "2fr"]);
448 assert_eq!(t.to_css_string(), "1fr auto 2fr");
449 }
450
451 #[test]
452 fn repeated_serializes_with_commas() {
453 let r = Repeated::new([Length::Px(8.0), Length::Px(16.0)]);
454 assert_eq!(r.to_css_string(), "8px, 16px");
455 }
456}