Skip to main content

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}