1use std::fmt::Write as _;
4
5use crate::condition::{Condition, Conditions, GROUP, PEER};
6use crate::generated::{Property, PseudoElement};
7use crate::keyframes::Keyframes;
8use crate::token::{Token, TokenValue};
9use crate::value::CssWideKeyword;
10
11#[derive(Debug, Clone, Default, PartialEq)]
31pub struct Style {
32 atoms: Vec<Atom>,
33 keyframes: Vec<Keyframes>,
34 markers: Vec<&'static str>,
35}
36
37#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
39pub(crate) struct Atom {
40 pub(crate) conditions: Conditions,
41 pub(crate) property: Prop,
42 pub(crate) value: String,
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
48pub(crate) enum Prop {
49 Standard(Property),
50 Custom(&'static str),
51}
52
53impl Prop {
54 pub(crate) fn write_name(self, out: &mut String) {
57 match self {
58 Self::Standard(p) => out.push_str(p.name()),
59 Self::Custom(name) => {
60 out.push_str("--");
61 out.push_str(name);
62 }
63 }
64 }
65
66 pub(crate) fn rank(self) -> u32 {
68 match self {
69 Self::Standard(p) => p.rank(),
70 Self::Custom(_) => 0,
71 }
72 }
73}
74
75impl Atom {
76 pub(crate) fn class_name(&self, toggled: bool) -> String {
83 let mut name = String::new();
84 if toggled {
85 name.push_str("when:");
86 }
87 for m in &self.conditions.media {
88 name.push_str(m.name());
89 name.push(':');
90 }
91 for c in &self.conditions.container {
92 name.push_str(c.name());
93 name.push(':');
94 }
95 for (_, p) in &self.conditions.group {
96 name.push_str("group-");
97 name.push_str(p.name());
98 name.push(':');
99 }
100 for (_, p) in &self.conditions.peer {
101 name.push_str("peer-");
102 name.push_str(p.name());
103 name.push(':');
104 }
105 for (_, p) in &self.conditions.pseudo {
106 name.push_str(p.name());
107 name.push(':');
108 }
109 if let Some(e) = self.conditions.element {
110 name.push_str(e.name());
111 name.push(':');
112 }
113 self.property.write_name(&mut name);
114 name.push('_');
115 let readable = self
116 .value
117 .bytes()
118 .all(|b| b.is_ascii_alphanumeric() || matches!(b, b'.' | b'#' | b'%' | b'-'));
119 if readable {
120 name.push_str(&self.value);
121 } else {
122 let _ = write!(name, "h{:016x}", fnv1a(self.value.as_bytes()));
123 }
124 name
125 }
126}
127
128fn fnv1a(bytes: &[u8]) -> u64 {
131 const OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
132 const PRIME: u64 = 0x0100_0000_01b3;
133 bytes
134 .iter()
135 .fold(OFFSET, |h, &b| (h ^ u64::from(b)).wrapping_mul(PRIME))
136}
137
138impl Style {
139 #[must_use]
141 pub fn new() -> Self {
142 Self::default()
143 }
144
145 pub(crate) fn atoms(&self) -> &[Atom] {
146 &self.atoms
147 }
148
149 pub(crate) fn keyframes_used(&self) -> &[Keyframes] {
150 &self.keyframes
151 }
152
153 #[must_use]
156 pub fn keyframes(mut self, keyframes: &Keyframes) -> Self {
157 if !self.keyframes.iter().any(|k| k.name() == keyframes.name()) {
158 self.keyframes.push(keyframes.clone());
159 }
160 self.with(Property::AnimationName, keyframes.name().to_owned())
161 }
162
163 fn insert(&mut self, atom: Atom) {
164 if let Some(old) = self
165 .atoms
166 .iter_mut()
167 .find(|a| a.property == atom.property && a.conditions == atom.conditions)
168 {
169 *old = atom;
170 } else {
171 self.atoms.push(atom);
172 }
173 }
174
175 pub(crate) fn with(mut self, property: Property, value: String) -> Self {
176 self.insert(Atom {
177 conditions: Conditions::default(),
178 property: Prop::Standard(property),
179 value,
180 });
181 self
182 }
183
184 #[must_use]
187 pub fn token<T: TokenValue>(mut self, token: &Token<T>, value: impl Into<T>) -> Self {
188 self.insert(Atom {
189 conditions: Conditions::default(),
190 property: Prop::Custom(token.name()),
191 value: value.into().to_string(),
192 });
193 self
194 }
195
196 #[must_use]
198 pub fn keyword(self, property: Property, value: CssWideKeyword) -> Self {
199 self.with(property, value.to_string())
200 }
201
202 #[must_use]
204 pub fn merge(mut self, style: Self) -> Self {
205 for atom in style.atoms {
206 self.insert(atom);
207 }
208 self.keyframes.extend(style.keyframes);
209 for marker in style.markers {
210 if !self.markers.contains(&marker) {
211 self.markers.push(marker);
212 }
213 }
214 self
215 }
216
217 #[must_use]
224 pub fn on(mut self, condition: impl Into<Condition>, style: Self) -> Self {
225 let condition = condition.into();
226 for mut atom in style.atoms {
227 atom.conditions.add(condition);
228 self.insert(atom);
229 }
230 self.keyframes.extend(style.keyframes);
231 self
232 }
233
234 #[must_use]
245 pub fn pseudo(mut self, element: PseudoElement, style: Self) -> Self {
246 for mut atom in style.atoms {
247 assert!(
248 atom.conditions.element.is_none(),
249 "a pseudo-element holds no pseudo-element"
250 );
251 atom.conditions.element = Some(element);
252 self.insert(atom);
253 }
254 self.keyframes.extend(style.keyframes);
255 self
256 }
257
258 #[must_use]
262 pub fn group(mut self) -> Self {
263 if !self.markers.contains(&GROUP) {
264 self.markers.push(GROUP);
265 }
266 self
267 }
268
269 #[must_use]
272 pub fn peer(mut self) -> Self {
273 if !self.markers.contains(&PEER) {
274 self.markers.push(PEER);
275 }
276 self
277 }
278
279 pub fn class_names(&self) -> impl Iterator<Item = String> + '_ {
281 self.markers
282 .iter()
283 .map(|&m| m.to_owned())
284 .chain(self.atoms.iter().map(|a| a.class_name(false)))
285 }
286
287 pub fn toggled_class_names(&self) -> impl Iterator<Item = String> + '_ {
292 self.markers
293 .iter()
294 .map(|&m| m.to_owned())
295 .chain(self.atoms.iter().map(|a| a.class_name(true)))
296 }
297
298 #[must_use]
300 pub fn is_empty(&self) -> bool {
301 self.atoms.is_empty() && self.markers.is_empty()
302 }
303}