719 lines
28 KiB
Rust
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()),
|
|
}
|
|
}),
|
|
);
|
|
}
|