719 lines
28 KiB
Rust

use super::GlobalFunctionMap;
use num_traits::One;
use super::Builtin;
use crate::color::Color;
use crate::common::QuoteKind;
use crate::unit::Unit;
use crate::value::{Number, Value};
pub(crate) fn register(f: &mut GlobalFunctionMap) {
f.insert(
"hsl",
Builtin::new(|mut args, scope, super_selector| {
if args.is_empty() {
return Err(("Missing argument $channels.", args.span()).into());
}
if args.len() == 1 {
let mut channels = match arg!(args, scope, super_selector, 0, "channels") {
Value::List(v, ..) => v,
_ => return Err(("Missing argument $channels.", args.span()).into()),
};
if channels.len() > 3 {
return Err((
format!(
"Only 3 elements allowed, but {} were passed.",
channels.len()
),
args.span(),
)
.into());
}
let lightness = match channels.pop() {
Some(Value::Dimension(n, _)) => n / Number::from(100),
Some(v) => {
return Err((
format!(
"$lightness: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
None => return Err(("Missing element $lightness.", args.span()).into()),
};
let saturation = match channels.pop() {
Some(Value::Dimension(n, _)) => n / Number::from(100),
Some(v) => {
return Err((
format!(
"$saturation: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
None => return Err(("Missing element $saturation.", args.span()).into()),
};
let hue = match channels.pop() {
Some(Value::Dimension(n, _)) => n,
Some(v) => {
return Err((
format!("$hue: {} is not a number.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
None => return Err(("Missing element $hue.", args.span()).into()),
};
Ok(Value::Color(Box::new(Color::from_hsla(
hue,
saturation,
lightness,
Number::one(),
))))
} else {
let hue = match arg!(args, scope, super_selector, 0, "hue") {
Value::Dimension(n, _) => n,
v if v.is_special_function() => {
let saturation = arg!(args, scope, super_selector, 1, "saturation");
let lightness = arg!(args, scope, super_selector, 2, "lightness");
let mut string = format!(
"hsl({}, {}, {}",
v.to_css_string(args.span())?,
saturation.to_css_string(args.span())?,
lightness.to_css_string(args.span())?
);
if !args.is_empty() {
string.push_str(", ");
string.push_str(
&arg!(args, scope, super_selector, 3, "alpha")
.to_css_string(args.span())?,
);
}
string.push(')');
return Ok(Value::Ident(string, QuoteKind::None));
}
v => {
return Err((
format!("$hue: {} is not a number.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
let saturation = match arg!(args, scope, super_selector, 1, "saturation") {
Value::Dimension(n, _) => n / Number::from(100),
v if v.is_special_function() => {
let lightness = arg!(args, scope, super_selector, 2, "lightness");
let mut string = format!(
"hsl({}, {}, {}",
hue,
v.to_css_string(args.span())?,
lightness.to_css_string(args.span())?
);
if !args.is_empty() {
string.push_str(", ");
string.push_str(
&arg!(args, scope, super_selector, 3, "alpha")
.to_css_string(args.span())?,
);
}
string.push(')');
return Ok(Value::Ident(string, QuoteKind::None));
}
v => {
return Err((
format!(
"$saturation: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
};
let lightness = match arg!(args, scope, super_selector, 2, "lightness") {
Value::Dimension(n, _) => n / Number::from(100),
v if v.is_special_function() => {
let mut string = format!(
"hsl({}, {}, {}",
hue,
saturation,
v.to_css_string(args.span())?
);
if !args.is_empty() {
string.push_str(", ");
string.push_str(
&arg!(args, scope, super_selector, 3, "alpha")
.to_css_string(args.span())?,
);
}
string.push(')');
return Ok(Value::Ident(string, QuoteKind::None));
}
v => {
return Err((
format!(
"$lightness: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
};
let alpha = match arg!(
args,
scope,
super_selector,
3,
"alpha" = Value::Dimension(Number::one(), Unit::None)
) {
Value::Dimension(n, Unit::None) => n,
Value::Dimension(n, Unit::Percent) => n / Number::from(100),
v @ Value::Dimension(..) => {
return Err((
format!(
"$alpha: Expected {} to have no units or \"%\".",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
v if v.is_special_function() => {
return Ok(Value::Ident(
format!(
"hsl({}, {}, {}, {})",
hue,
saturation,
lightness,
v.to_css_string(args.span())?
),
QuoteKind::None,
));
}
v => {
return Err((
format!("$alpha: {} is not a number.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
Ok(Value::Color(Box::new(Color::from_hsla(
hue, saturation, lightness, alpha,
))))
}
}),
);
f.insert(
"hsla",
Builtin::new(|mut args, scope, super_selector| {
if args.is_empty() {
return Err(("Missing argument $channels.", args.span()).into());
}
if args.len() == 1 {
let mut channels = match arg!(args, scope, super_selector, 0, "channels") {
Value::List(v, ..) => v,
_ => return Err(("Missing argument $channels.", args.span()).into()),
};
if channels.len() > 3 {
return Err((
format!(
"Only 3 elements allowed, but {} were passed.",
channels.len()
),
args.span(),
)
.into());
}
let lightness = match channels.pop() {
Some(Value::Dimension(n, _)) => n / Number::from(100),
Some(v) => {
return Err((
format!(
"$lightness: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
None => return Err(("Missing element $lightness.", args.span()).into()),
};
let saturation = match channels.pop() {
Some(Value::Dimension(n, _)) => n / Number::from(100),
Some(v) => {
return Err((
format!(
"$saturation: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
None => return Err(("Missing element $saturation.", args.span()).into()),
};
let hue = match channels.pop() {
Some(Value::Dimension(n, _)) => n,
Some(v) => {
return Err((
format!("$hue: {} is not a number.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
None => return Err(("Missing element $hue.", args.span()).into()),
};
Ok(Value::Color(Box::new(Color::from_hsla(
hue,
saturation,
lightness,
Number::one(),
))))
} else {
let hue = match arg!(args, scope, super_selector, 0, "hue") {
Value::Dimension(n, _) => n,
v if v.is_special_function() => {
let saturation = arg!(args, scope, super_selector, 1, "saturation");
let lightness = arg!(args, scope, super_selector, 2, "lightness");
let mut string = format!(
"hsla({}, {}, {}",
v.to_css_string(args.span())?,
saturation.to_css_string(args.span())?,
lightness.to_css_string(args.span())?
);
if !args.is_empty() {
string.push_str(", ");
string.push_str(
&arg!(args, scope, super_selector, 3, "alpha")
.to_css_string(args.span())?,
);
}
string.push(')');
return Ok(Value::Ident(string, QuoteKind::None));
}
v => {
return Err((
format!("$hue: {} is not a number.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
let saturation = match arg!(args, scope, super_selector, 1, "saturation") {
Value::Dimension(n, _) => n / Number::from(100),
v if v.is_special_function() => {
let lightness = arg!(args, scope, super_selector, 2, "lightness");
let mut string = format!(
"hsla({}, {}, {}",
hue,
v.to_css_string(args.span())?,
lightness.to_css_string(args.span())?
);
if !args.is_empty() {
string.push_str(", ");
string.push_str(
&arg!(args, scope, super_selector, 3, "alpha")
.to_css_string(args.span())?,
);
}
string.push(')');
return Ok(Value::Ident(string, QuoteKind::None));
}
v => {
return Err((
format!(
"$saturation: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
};
let lightness = match arg!(args, scope, super_selector, 2, "lightness") {
Value::Dimension(n, _) => n / Number::from(100),
v if v.is_special_function() => {
let mut string = format!(
"hsla({}, {}, {}",
hue,
saturation,
v.to_css_string(args.span())?
);
if !args.is_empty() {
string.push_str(", ");
string.push_str(
&arg!(args, scope, super_selector, 3, "alpha")
.to_css_string(args.span())?,
);
}
string.push(')');
return Ok(Value::Ident(string, QuoteKind::None));
}
v => {
return Err((
format!(
"$lightness: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
};
let alpha = match arg!(
args,
scope,
super_selector,
3,
"alpha" = Value::Dimension(Number::one(), Unit::None)
) {
Value::Dimension(n, Unit::None) => n,
Value::Dimension(n, Unit::Percent) => n / Number::from(100),
v @ Value::Dimension(..) => {
return Err((
format!(
"$alpha: Expected {} to have no units or \"%\".",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
v if v.is_special_function() => {
return Ok(Value::Ident(
format!(
"hsl({}, {}, {}, {})",
hue,
saturation,
lightness,
v.to_css_string(args.span())?
),
QuoteKind::None,
));
}
v => {
return Err((
format!("$alpha: {} is not a number.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
Ok(Value::Color(Box::new(Color::from_hsla(
hue, saturation, lightness, alpha,
))))
}
}),
);
f.insert(
"hue",
Builtin::new(|mut args, scope, super_selector| {
args.max_args(1)?;
match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => Ok(Value::Dimension(c.hue(), Unit::Deg)),
v => Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into()),
}
}),
);
f.insert(
"saturation",
Builtin::new(|mut args, scope, super_selector| {
args.max_args(1)?;
match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => Ok(Value::Dimension(c.saturation(), Unit::Percent)),
v => Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into()),
}
}),
);
f.insert(
"lightness",
Builtin::new(|mut args, scope, super_selector| {
args.max_args(1)?;
match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => Ok(Value::Dimension(c.lightness(), Unit::Percent)),
v => Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into()),
}
}),
);
f.insert(
"adjust-hue",
Builtin::new(|mut args, scope, super_selector| {
args.max_args(2)?;
let color = match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => c,
v => {
return Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
let degrees = match arg!(args, scope, super_selector, 1, "degrees") {
Value::Dimension(n, _) => n,
v => {
return Err((
format!(
"$degrees: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
};
Ok(Value::Color(Box::new(color.adjust_hue(degrees))))
}),
);
f.insert(
"lighten",
Builtin::new(|mut args, scope, super_selector| {
args.max_args(2)?;
let color = match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => c,
v => {
return Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
let amount = match arg!(args, scope, super_selector, 1, "amount") {
Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 100) / Number::from(100),
v => {
return Err((
format!(
"$amount: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
};
Ok(Value::Color(Box::new(color.lighten(amount))))
}),
);
f.insert(
"darken",
Builtin::new(|mut args, scope, super_selector| {
args.max_args(2)?;
let color = match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => c,
v => {
return Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
let amount = match arg!(args, scope, super_selector, 1, "amount") {
Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 100) / Number::from(100),
v => {
return Err((
format!(
"$amount: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
};
Ok(Value::Color(Box::new(color.darken(amount))))
}),
);
f.insert(
"saturate",
Builtin::new(|mut args, scope, super_selector| {
args.max_args(2)?;
if args.len() == 1 {
return Ok(Value::Ident(
format!(
"saturate({})",
arg!(args, scope, super_selector, 0, "amount")
.to_css_string(args.span())?
),
QuoteKind::None,
));
}
let amount = match arg!(args, scope, super_selector, 1, "amount") {
Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 100) / Number::from(100),
v => {
return Err((
format!(
"$amount: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
};
let color = match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => c,
Value::Dimension(n, u) => {
return Ok(Value::Ident(
format!("saturate({}{})", n, u),
QuoteKind::None,
))
}
v => {
return Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
Ok(Value::Color(Box::new(color.saturate(amount))))
}),
);
f.insert(
"desaturate",
Builtin::new(|mut args, scope, super_selector| {
args.max_args(2)?;
let color = match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => c,
v => {
return Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
let amount = match arg!(args, scope, super_selector, 1, "amount") {
Value::Dimension(n, u) => bound!(args, "amount", n, u, 0, 100) / Number::from(100),
v => {
return Err((
format!(
"$amount: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
};
Ok(Value::Color(Box::new(color.desaturate(amount))))
}),
);
f.insert(
"grayscale",
Builtin::new(|mut args, scope, super_selector| {
args.max_args(1)?;
let color = match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => c,
Value::Dimension(n, u) => {
return Ok(Value::Ident(
format!("grayscale({}{})", n, u),
QuoteKind::None,
))
}
v => {
return Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
Ok(Value::Color(Box::new(color.desaturate(Number::one()))))
}),
);
f.insert(
"complement",
Builtin::new(|mut args, scope, super_selector| {
args.max_args(1)?;
let color = match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => c,
v => {
return Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into())
}
};
Ok(Value::Color(Box::new(color.complement())))
}),
);
f.insert(
"invert",
Builtin::new(|mut args, scope, super_selector| {
args.max_args(2)?;
let weight = match arg!(
args,
scope,
super_selector,
1,
"weight" = Value::Dimension(Number::from(100), Unit::Percent)
) {
Value::Dimension(n, u) => bound!(args, "weight", n, u, 0, 100) / Number::from(100),
v => {
return Err((
format!(
"$weight: {} is not a number.",
v.to_css_string(args.span())?
),
args.span(),
)
.into())
}
};
match arg!(args, scope, super_selector, 0, "color") {
Value::Color(c) => Ok(Value::Color(Box::new(c.invert(weight)))),
Value::Dimension(n, Unit::Percent) => {
Ok(Value::Ident(format!("invert({}%)", n), QuoteKind::None))
}
Value::Dimension(..) => Err((
"Only one argument may be passed to the plain-CSS invert() function.",
args.span(),
)
.into()),
v => Err((
format!("$color: {} is not a color.", v.to_css_string(args.span())?),
args.span(),
)
.into()),
}
}),
);
}