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 ) -> Result<Self::Declaration, cssparser::ParseError<'i, Self::Error>> {
105 let state = input.state();
106 match_ignore_ascii_case! { &name,
107 "font-family" => {
108 if let Ok(font_family) = FontFamily::parse(input) {
110 return match font_family {
111 FontFamily::Generic(_) => Err(input.new_custom_error(ParserError::InvalidDeclaration)),
112 _ => Ok(FontPaletteValuesProperty::FontFamily(font_family))
113 }
114 }
115 },
116 "base-palette" => {
117 if let Ok(base_palette) = BasePalette::parse(input) {
119 return Ok(FontPaletteValuesProperty::BasePalette(base_palette))
120 }
121 },
122 "override-colors" => {
123 if let Ok(override_colors) = input.parse_comma_separated(OverrideColors::parse) {
125 return Ok(FontPaletteValuesProperty::OverrideColors(override_colors))
126 }
127 },
128 _ => return Err(input.new_custom_error(ParserError::InvalidDeclaration))
129 }
130
131 input.reset(&state);
132 return Ok(FontPaletteValuesProperty::Custom(CustomProperty::parse(
133 name.into(),
134 input,
135 &ParserOptions::default(),
136 )?));
137 }
138}
139
140impl<'i> AtRuleParser<'i> for FontPaletteValuesDeclarationParser {
142 type Prelude = ();
143 type AtRule = FontPaletteValuesProperty<'i>;
144 type Error = ParserError<'i>;
145}
146
147impl<'i> QualifiedRuleParser<'i> for FontPaletteValuesDeclarationParser {
148 type Prelude = ();
149 type QualifiedRule = FontPaletteValuesProperty<'i>;
150 type Error = ParserError<'i>;
151}
152
153impl<'i> RuleBodyItemParser<'i, FontPaletteValuesProperty<'i>, ParserError<'i>>
154 for FontPaletteValuesDeclarationParser
155{
156 fn parse_qualified(&self) -> bool {
157 false
158 }
159
160 fn parse_declarations(&self) -> bool {
161 true
162 }
163}
164
165impl<'i> FontPaletteValuesRule<'i> {
166 pub(crate) fn parse<'t>(
167 name: DashedIdent<'i>,
168 input: &mut Parser<'i, 't>,
169 loc: Location,
170 ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
171 let mut decl_parser = FontPaletteValuesDeclarationParser;
172 let mut parser = RuleBodyParser::new(input, &mut decl_parser);
173 let mut properties = vec![];
174 while let Some(decl) = parser.next() {
175 if let Ok(decl) = decl {
176 properties.push(decl);
177 }
178 }
179
180 Ok(FontPaletteValuesRule { name, properties, loc })
181 }
182}
183
184impl<'i> Parse<'i> for BasePalette {
185 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
186 if let Ok(i) = input.try_parse(CSSInteger::parse) {
187 if i.is_negative() {
188 return Err(input.new_custom_error(ParserError::InvalidValue));
189 }
190 return Ok(BasePalette::Integer(i as u16));
191 }
192
193 let location = input.current_source_location();
194 let ident = input.expect_ident()?;
195 match_ignore_ascii_case! { &*ident,
196 "light" => Ok(BasePalette::Light),
197 "dark" => Ok(BasePalette::Dark),
198 _ => Err(location.new_unexpected_token_error(Token::Ident(ident.clone())))
199 }
200 }
201}
202
203impl ToCss for BasePalette {
204 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
205 where
206 W: std::fmt::Write,
207 {
208 match self {
209 BasePalette::Light => dest.write_str("light"),
210 BasePalette::Dark => dest.write_str("dark"),
211 BasePalette::Integer(i) => (*i as CSSInteger).to_css(dest),
212 }
213 }
214}
215
216impl<'i> Parse<'i> for OverrideColors {
217 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
218 let index = CSSInteger::parse(input)?;
219 if index.is_negative() {
220 return Err(input.new_custom_error(ParserError::InvalidValue));
221 }
222
223 let color = CssColor::parse(input)?;
224 if matches!(color, CssColor::CurrentColor) {
225 return Err(input.new_custom_error(ParserError::InvalidValue));
226 }
227
228 Ok(OverrideColors {
229 index: index as u16,
230 color,
231 })
232 }
233}
234
235impl ToCss for OverrideColors {
236 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
237 where
238 W: std::fmt::Write,
239 {
240 (self.index as CSSInteger).to_css(dest)?;
241 dest.write_char(' ')?;
242 self.color.to_css(dest)
243 }
244}
245
246impl OverrideColors {
247 fn get_fallback(&self, kind: ColorFallbackKind) -> OverrideColors {
248 OverrideColors {
249 index: self.index,
250 color: self.color.get_fallback(kind),
251 }
252 }
253}
254
255impl<'i> FontPaletteValuesRule<'i> {
256 pub(crate) fn minify(&mut self, context: &mut MinifyContext<'_, 'i>, _: bool) {
257 let mut properties = Vec::with_capacity(self.properties.len());
258 for property in &self.properties {
259 match property {
260 FontPaletteValuesProperty::OverrideColors(override_colors) => {
261 let mut fallbacks = ColorFallbackKind::empty();
263 for o in override_colors {
264 fallbacks |= o.color.get_necessary_fallbacks(*context.targets);
265 }
266
267 if fallbacks.contains(ColorFallbackKind::RGB) {
268 properties.push(FontPaletteValuesProperty::OverrideColors(
269 override_colors.iter().map(|o| o.get_fallback(ColorFallbackKind::RGB)).collect(),
270 ));
271 }
272
273 if fallbacks.contains(ColorFallbackKind::P3) {
274 properties.push(FontPaletteValuesProperty::OverrideColors(
275 override_colors.iter().map(|o| o.get_fallback(ColorFallbackKind::P3)).collect(),
276 ));
277 }
278
279 let override_colors = if fallbacks.contains(ColorFallbackKind::LAB) {
280 override_colors.iter().map(|o| o.get_fallback(ColorFallbackKind::P3)).collect()
281 } else {
282 override_colors.clone()
283 };
284
285 properties.push(FontPaletteValuesProperty::OverrideColors(override_colors));
286 }
287 _ => properties.push(property.clone()),
288 }
289 }
290
291 self.properties = properties;
292 }
293
294 pub(crate) fn get_fallbacks<T>(&mut self, targets: Targets) -> Vec<CssRule<'i, T>> {
295 let mut fallbacks = ColorFallbackKind::empty();
298 for property in &self.properties {
299 match property {
300 FontPaletteValuesProperty::Custom(CustomProperty { value, .. }) => {
301 fallbacks |= value.get_necessary_fallbacks(targets);
302 }
303 _ => {}
304 }
305 }
306
307 let mut res = Vec::new();
308 let lowest_fallback = fallbacks.lowest();
309 fallbacks.remove(lowest_fallback);
310
311 if fallbacks.contains(ColorFallbackKind::P3) {
312 res.push(self.get_fallback(ColorFallbackKind::P3));
313 }
314
315 if fallbacks.contains(ColorFallbackKind::LAB)
316 || (!lowest_fallback.is_empty() && lowest_fallback != ColorFallbackKind::LAB)
317 {
318 res.push(self.get_fallback(ColorFallbackKind::LAB));
319 }
320
321 if !lowest_fallback.is_empty() {
322 for property in &mut self.properties {
323 match property {
324 FontPaletteValuesProperty::Custom(CustomProperty { value, .. }) => {
325 *value = value.get_fallback(lowest_fallback);
326 }
327 _ => {}
328 }
329 }
330 }
331
332 res
333 }
334
335 fn get_fallback<T>(&self, kind: ColorFallbackKind) -> CssRule<'i, T> {
336 let properties = self
337 .properties
338 .iter()
339 .map(|property| match property {
340 FontPaletteValuesProperty::Custom(custom) => FontPaletteValuesProperty::Custom(CustomProperty {
341 name: custom.name.clone(),
342 value: custom.value.get_fallback(kind),
343 }),
344 _ => property.clone(),
345 })
346 .collect();
347 CssRule::Supports(SupportsRule {
348 condition: kind.supports_condition(),
349 rules: CssRuleList(vec![CssRule::FontPaletteValues(FontPaletteValuesRule {
350 name: self.name.clone(),
351 properties,
352 loc: self.loc.clone(),
353 })]),
354 loc: self.loc.clone(),
355 })
356 }
357}
358
359impl<'i> ToCss for FontPaletteValuesRule<'i> {
360 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
361 where
362 W: std::fmt::Write,
363 {
364 #[cfg(feature = "sourcemap")]
365 dest.add_mapping(self.loc);
366 dest.write_str("@font-palette-values ")?;
367 self.name.to_css(dest)?;
368 dest.whitespace()?;
369 dest.write_char('{')?;
370 dest.indent();
371 let len = self.properties.len();
372 for (i, prop) in self.properties.iter().enumerate() {
373 dest.newline()?;
374 prop.to_css(dest)?;
375 if i != len - 1 || !dest.minify {
376 dest.write_char(';')?;
377 }
378 }
379 dest.dedent();
380 dest.newline()?;
381 dest.write_char('}')
382 }
383}
384
385impl<'i> ToCss for FontPaletteValuesProperty<'i> {
386 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
387 where
388 W: std::fmt::Write,
389 {
390 macro_rules! property {
391 ($prop: literal, $value: expr) => {{
392 dest.write_str($prop)?;
393 dest.delim(':', false)?;
394 $value.to_css(dest)
395 }};
396 }
397
398 match self {
399 FontPaletteValuesProperty::FontFamily(f) => property!("font-family", f),
400 FontPaletteValuesProperty::BasePalette(b) => property!("base-palette", b),
401 FontPaletteValuesProperty::OverrideColors(o) => property!("override-colors", o),
402 FontPaletteValuesProperty::Custom(custom) => {
403 dest.write_str(custom.name.as_ref())?;
404 dest.delim(':', false)?;
405 custom.value.to_css(dest, true)
406 }
407 }
408 }
409}