fleuron/style/properties/background.rs
1//! What is painted behind a box: a tint, and an image over it.
2//!
3//! The same four properties answer for a block and for `@page`, and
4//! the two paint the same way, so one value carries both.
5
6use serde::Serialize;
7
8use super::exclusion::Coord;
9use super::value::{Color, Length};
10
11/// A url the sheet named, and where it was written.
12///
13/// The position travels with the url because the sheet is the only
14/// place that knows it: by the time a missing image is noticed, the
15/// declaration it was written in is long parsed.
16#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
17pub struct Url {
18 /// The url as the sheet wrote it. The engine opens nothing
19 /// itself: this is the name the host resolves.
20 pub value: String,
21 /// Sheet, line and column, as a diagnostic spells them.
22 #[serde(skip_serializing_if = "Option::is_none")]
23 pub origin: Option<String>,
24}
25
26impl Url {
27 /// A url with no position on it, which is what a host building a
28 /// style by hand has.
29 pub fn new(value: impl Into<String>) -> Url {
30 Url {
31 value: value.into(),
32 origin: None,
33 }
34 }
35}
36
37/// Whether the image repeats to fill the box, from
38/// `background-repeat`.
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
40#[serde(rename_all = "kebab-case")]
41pub enum BackgroundRepeat {
42 /// `repeat`: the image tiles across and down the box.
43 Repeat,
44 /// `no-repeat`: one copy of the image.
45 NoRepeat,
46}
47
48/// How large the image is drawn, from `background-size`.
49#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
50#[serde(rename_all = "snake_case")]
51pub enum BackgroundSize {
52 /// `auto`: the size the image's own header asks for.
53 Auto,
54 /// `cover`: the smallest size that covers the box, cropped by it.
55 Cover,
56 /// `contain`: the largest size that fits inside the box.
57 Contain,
58 /// One or two lengths. An axis left `auto` follows the image's
59 /// own ratio.
60 Fixed {
61 /// Across the box.
62 #[serde(skip_serializing_if = "Option::is_none")]
63 width: Option<Coord>,
64 /// Down the box.
65 #[serde(skip_serializing_if = "Option::is_none")]
66 height: Option<Coord>,
67 },
68}
69
70/// `background-size` as the sheet wrote it, before the cascade knows
71/// the font size its lengths are relative to.
72#[derive(Debug, Clone, Copy, PartialEq)]
73pub enum SizeSource {
74 /// `auto`.
75 Auto,
76 /// `cover`.
77 Cover,
78 /// `contain`.
79 Contain,
80 /// One or two written lengths, `None` on an axis written `auto`.
81 Fixed(Option<Length>, Option<Length>),
82}
83
84/// Where the image sits in the box, from `background-position`.
85///
86/// A percentage aligns that fraction of the image with the same
87/// fraction of the box, which is what makes `50%` centre it. A length
88/// is an offset from the box's top left corner.
89#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
90pub struct BackgroundPosition {
91 /// Across the box.
92 pub x: Coord,
93 /// Down the box.
94 pub y: Coord,
95}
96
97impl BackgroundPosition {
98 /// `0% 0%`: the image's top left corner in the box's own.
99 pub const ORIGIN: BackgroundPosition = BackgroundPosition {
100 x: Coord::Percent(0.0),
101 y: Coord::Percent(0.0),
102 };
103}
104
105/// What one box paints behind its content.
106#[derive(Debug, Clone, PartialEq, Serialize)]
107pub struct Background {
108 /// What the box is tinted, from `background-color`. The image is
109 /// painted over it.
110 #[serde(skip_serializing_if = "Option::is_none")]
111 pub color: Option<Color>,
112 /// The image, from `background-image`.
113 #[serde(skip_serializing_if = "Option::is_none")]
114 pub image: Option<Url>,
115 /// Whether that image repeats.
116 #[serde(skip_serializing_if = "repeats")]
117 pub repeat: BackgroundRepeat,
118 /// How large it is drawn.
119 #[serde(skip_serializing_if = "auto_size")]
120 pub size: BackgroundSize,
121 /// Where it sits.
122 #[serde(skip_serializing_if = "at_origin")]
123 pub position: BackgroundPosition,
124}
125
126impl Background {
127 /// Nothing behind the box: the initial value of all four
128 /// properties.
129 pub const NONE: Background = Background {
130 color: None,
131 image: None,
132 repeat: BackgroundRepeat::Repeat,
133 size: BackgroundSize::Auto,
134 position: BackgroundPosition::ORIGIN,
135 };
136
137 /// Whether anything is painted behind the box at all.
138 pub fn paints(&self) -> bool {
139 self.color.is_some() || self.image.is_some()
140 }
141}
142
143impl Default for Background {
144 fn default() -> Background {
145 Background::NONE
146 }
147}
148
149fn repeats(repeat: &BackgroundRepeat) -> bool {
150 *repeat == BackgroundRepeat::Repeat
151}
152
153fn auto_size(size: &BackgroundSize) -> bool {
154 *size == BackgroundSize::Auto
155}
156
157fn at_origin(position: &BackgroundPosition) -> bool {
158 *position == BackgroundPosition::ORIGIN
159}
160
161/// Whether a background is the initial one, which is what keeps a
162/// style that declares none out of the serialized tree.
163pub(crate) fn no_background(background: &Background) -> bool {
164 *background == Background::NONE
165}