1use super::supports::SupportsRule;
4use super::{CssRule, CssRuleList, Location, MinifyContext};
5use crate::error::{ParserError, PrinterError};
6use crate::printer::Printer;
7use crate::properties::custom::CustomProperty;
8use crate::properties::font::FontFamily;
9use crate::stylesheet::ParserOptions;
10use crate::targets::Targets;
11use crate::traits::{Parse, ToCss};
12use crate::values::color::{ColorFallbackKind, CssColor};
13use crate::values::ident::DashedIdent;
14use crate::values::number::CSSInteger;
15#[cfg(feature = "visitor")]
16use crate::visitor::Visit;
17use cssparser::*;
18
19#[derive(Debug, PartialEq, Clone)]
21#[cfg_attr(feature = "visitor", derive(Visit))]
22#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
23#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
24#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
25pub struct FontPaletteValuesRule<'i> {
26 pub name: DashedIdent<'i>,
28 #[cfg_attr(feature = "serde", serde(borrow))]
30 pub properties: Vec<FontPaletteValuesProperty<'i>>,
31 #[cfg_attr(feature = "visitor", skip_visit)]
33 pub loc: Location,
34}
35
36#[derive(Debug, Clone, PartialEq)]
40#[cfg_attr(feature = "visitor", derive(Visit))]
41#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
42#[cfg_attr(
43 feature = "serde",
44 derive(serde::Serialize, serde::Deserialize),
45 serde(tag = "type", content = "value", rename_all = "kebab-case")
46)]
47#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
48pub enum FontPaletteValuesProperty<'i> {
49 #[cfg_attr(feature = "serde", serde(borrow))]
51 FontFamily(FontFamily<'i>),
52 BasePalette(BasePalette),
54 OverrideColors(Vec<OverrideColors>),
56 Custom(CustomProperty<'i>),
58}
59
60#[derive(Debug, PartialEq, Clone)]
63#[cfg_attr(feature = "visitor", derive(Visit))]
64#[cfg_attr(
65 feature = "serde",
66 derive(serde::Serialize, serde::Deserialize),
67 serde(tag = "type", content = "value", rename_all = "kebab-case")
68)]
69#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
70#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
71pub enum BasePalette {
72 Light,
74 Dark,
76 Integer(u16),
78}
79
80#[derive(Debug, PartialEq, Clone)]
83#[cfg_attr(feature = "visitor", derive(Visit))]
84#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
85#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
86#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
87pub struct OverrideColors {
88 index: u16,
90 color: CssColor,
92}
93
94pub(crate) struct FontPaletteValuesDeclarationParser;
95
96impl<'i> cssparser::DeclarationParser<'i> for FontPaletteValuesDeclarationParser {
97 type Declaration = FontPaletteValuesProperty<'i>;
98 type Error = ParserError<'i>;
99
100 fn parse_value<'t>(
101 &mut self,
102 name: CowRcStr<'i>,
103 input: &mut cssparser::Parser<'i, 't>,
104 _declaration_start: &ParserState,
105 ) -> Result<Self::Declaration, cssparser::ParseError<'i, Self::Error>> {
106 let state = input.state();
107 match_ignore_ascii_case! { &name,
108 "font-family" => {
109 if let Ok(font_family) = FontFamily::parse(input) {
111 return match font_family {
112 FontFamily::Generic(_) => Err(input.new_custom_error(ParserError::InvalidDeclaration)),
113 _ => Ok(FontPaletteValuesProperty::FontFamily(font_family))
114 }
115 }
116 },
117 "base-palette" => {
118 if let Ok(base_palette) = BasePalette::parse(input) {
120 return Ok(FontPaletteValuesProperty::BasePalette(base_palette))
121 }
122 },
123 "override-colors" => {
124 if let Ok(override_colors) = input.parse_comma_separated(OverrideColors::parse) {
126 return Ok(FontPaletteValuesProperty::OverrideColors(override_colors))
127 }
128 },
129 _ => return Err(input.new_custom_error(ParserError::InvalidDeclaration))
130 }
131
132 input.reset(&state);
133 return Ok(FontPaletteValuesProperty::Custom(CustomProperty::parse(
134 name.into(),
135 input,
136 &ParserOptions::default(),
137 )?));
138 }
139}
140
141impl<'i> AtRuleParser<'i> for FontPaletteValuesDeclarationParser {
143 type Prelude = ();
144 type AtRule = FontPaletteValuesProperty<'i>;
145 type Error = ParserError<'i>;
146}
147
148impl<'i> QualifiedRuleParser<'i> for FontPaletteValuesDeclarationParser {
149 type Prelude = ();
150 type QualifiedRule = FontPaletteValuesProperty<'i>;
151 type Error = ParserError<'i>;
152}
153
154impl<'i> RuleBodyItemParser<'i, FontPaletteValuesProperty<'i>, ParserError<'i>>
155 for FontPaletteValuesDeclarationParser
156{
157 fn parse_qualified(&self) -> bool {
158 false
159 }
160
161 fn parse_declarations(&self) -> bool {
162 true
163 }
164}
165
166impl<'i> FontPaletteValuesRule<'i> {
167 pub(crate) fn parse<'t>(
168 name: DashedIdent<'i>,
169 input: &mut Parser<'i, 't>,
170 loc: Location,
171 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
172 let mut decl_parser = FontPaletteValuesDeclarationParser;
173 let mut parser = RuleBodyParser::new(input, &mut decl_parser);
174 let mut properties = vec![];
175 while let Some(decl) = parser.next() {
176 if let Ok(decl) = decl {
177 properties.push(decl);
178 }
179 }
180
181 Ok(FontPaletteValuesRule { name, properties, loc })
182 }
183}
184
185impl<'i> Parse<'i> for BasePalette {
186 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
187 if let Ok(i) = input.try_parse(CSSInteger::parse) {
188 if i.is_negative() {
189 return Err(input.new_custom_error(ParserError::InvalidValue));
190 }
191 return Ok(BasePalette::Integer(i as u16));
192 }
193
194 let location = input.current_source_location();
195 let ident = input.expect_ident()?;
196 match_ignore_ascii_case! { &*ident,
197 "light" => Ok(BasePalette::Light),
198 "dark" => Ok(BasePalette::Dark),
199 _ => Err(location.new_unexpected_token_error(Token::Ident(ident.clone())))
200 }
201 }
202}
203
204impl ToCss for BasePalette {
205 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
206 where
207 W: std::fmt::Write,
208 {
209 match self {
210 BasePalette::Light => dest.write_str("light"),
211 BasePalette::Dark => dest.write_str("dark"),
212 BasePalette::Integer(i) => (*i as CSSInteger).to_css(dest),
213 }
214 }
215}
216
217impl<'i> Parse<'i> for OverrideColors {
218 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
219 let index = CSSInteger::parse(input)?;
220 if index.is_negative() {
221 return Err(input.new_custom_error(ParserError::InvalidValue));
222 }
223
224 let color = CssColor::parse(input)?;
225 if matches!(color, CssColor::CurrentColor) {
226 return Err(input.new_custom_error(ParserError::InvalidValue));
227 }
228
229 Ok(OverrideColors {
230 index: index as u16,
231 color,
232 })
233 }
234}
235
236impl ToCss for OverrideColors {
237 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
238 where
239 W: std::fmt::Write,
240 {
241 (self.index as CSSInteger).to_css(dest)?;
242 dest.write_char(' ')?;
243 self.color.to_css(dest)
244 }
245}
246
247impl OverrideColors {
248 fn get_fallback(&self, kind: ColorFallbackKind) -> OverrideColors {
249 OverrideColors {
250 index: self.index,
251 color: self.color.get_fallback(kind),
252 }
253 }
254}
255
256impl<'i> FontPaletteValuesRule<'i> {
257 pub(crate) fn minify(&mut self, context: &mut MinifyContext<'_, 'i>, _: bool) {
258 let mut properties = Vec::with_capacity(self.properties.len());
259 for property in &self.properties {
260 match property {
261 FontPaletteValuesProperty::OverrideColors(override_colors) => {
262 let mut fallbacks = ColorFallbackKind::empty();
264 for o in override_colors {
265 fallbacks |= o.color.get_necessary_fallbacks(context.targets.current);
266 }
267
268 if fallbacks.contains(ColorFallbackKind::RGB) {
269 properties.push(FontPaletteValuesProperty::OverrideColors(
270 override_colors.iter().map(|o| o.get_fallback(ColorFallbackKind::RGB)).collect(),
271 ));
272 }
273
274 if fallbacks.contains(ColorFallbackKind::P3) {
275 properties.push(FontPaletteValuesProperty::OverrideColors(
276 override_colors.iter().map(|o| o.get_fallback(ColorFallbackKind::P3)).collect(),
277 ));
278 }
279
280 let override_colors = if fallbacks.contains(ColorFallbackKind::LAB) {
281 override_colors.iter().map(|o| o.get_fallback(ColorFallbackKind::P3)).collect()
282 } else {
283 override_colors.clone()
284 };
285
286 properties.push(FontPaletteValuesProperty::OverrideColors(override_colors));
287 }
288 _ => properties.push(property.clone()),
289 }
290 }
291
292 self.properties = properties;
293 }
294
295 pub(crate) fn get_fallbacks<T>(&mut self, targets: Targets) -> Vec<CssRule<'i, T>> {
296 let mut fallbacks = ColorFallbackKind::empty();
299 for property in &self.properties {
300 match property {
301 FontPaletteValuesProperty::Custom(CustomProperty { value, .. }) => {
302 fallbacks |= value.get_necessary_fallbacks(targets);
303 }
304 _ => {}
305 }
306 }
307
308 let mut res = Vec::new();
309 let lowest_fallback = fallbacks.lowest();
310 fallbacks.remove(lowest_fallback);
311
312 if fallbacks.contains(ColorFallbackKind::P3) {
313 res.push(self.get_fallback(ColorFallbackKind::P3));
314 }
315
316 if fallbacks.contains(ColorFallbackKind::LAB)
317 || (!lowest_fallback.is_empty() && lowest_fallback != ColorFallbackKind::LAB)
318 {
319 res.push(self.get_fallback(ColorFallbackKind::LAB));
320 }
321
322 if !lowest_fallback.is_empty() {
323 for property in &mut self.properties {
324 match property {
325 FontPaletteValuesProperty::Custom(CustomProperty { value, .. }) => {
326 *value = value.get_fallback(lowest_fallback);
327 }
328 _ => {}
329 }
330 }
331 }
332
333 res
334 }
335
336 fn get_fallback<T>(&self, kind: ColorFallbackKind) -> CssRule<'i, T> {
337 let properties = self
338 .properties
339 .iter()
340 .map(|property| match property {
341 FontPaletteValuesProperty::Custom(custom) => FontPaletteValuesProperty::Custom(CustomProperty {
342 name: custom.name.clone(),
343 value: custom.value.get_fallback(kind),
344 }),
345 _ => property.clone(),
346 })
347 .collect();
348 CssRule::Supports(SupportsRule {
349 condition: kind.supports_condition(),
350 rules: CssRuleList(vec![CssRule::FontPaletteValues(FontPaletteValuesRule {
351 name: self.name.clone(),
352 properties,
353 loc: self.loc.clone(),
354 })]),
355 loc: self.loc.clone(),
356 })
357 }
358}
359
360impl<'i> ToCss for FontPaletteValuesRule<'i> {
361 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
362 where
363 W: std::fmt::Write,
364 {
365 #[cfg(feature = "sourcemap")]
366 dest.add_mapping(self.loc);
367 dest.write_str("@font-palette-values ")?;
368 self.name.to_css(dest)?;
369 dest.whitespace()?;
370 dest.write_char('{')?;
371 dest.indent();
372 let len = self.properties.len();
373 for (i, prop) in self.properties.iter().enumerate() {
374 dest.newline()?;
375 prop.to_css(dest)?;
376 if i != len - 1 || !dest.minify {
377 dest.write_char(';')?;
378 }
379 }
380 dest.dedent();
381 dest.newline()?;
382 dest.write_char('}')
383 }
384}
385
386impl<'i> ToCss for FontPaletteValuesProperty<'i> {
387 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
388 where
389 W: std::fmt::Write,
390 {
391 macro_rules! property {
392 ($prop: literal, $value: expr) => {{
393 dest.write_str($prop)?;
394 dest.delim(':', false)?;
395 $value.to_css(dest)
396 }};
397 }
398
399 match self {
400 FontPaletteValuesProperty::FontFamily(f) => property!("font-family", f),
401 FontPaletteValuesProperty::BasePalette(b) => property!("base-palette", b),
402 FontPaletteValuesProperty::OverrideColors(o) => property!("override-colors", o),
403 FontPaletteValuesProperty::Custom(custom) => {
404 dest.write_str(custom.name.as_ref())?;
405 dest.delim(':', false)?;
406 custom.value.to_css(dest, true)
407 }
408 }
409 }
410}