use super::channels::Channels;
use super::{
check_alpha, check_amount, check_hue, check_pct, eval_inner, is_not,
is_special, make_call, CallError, CheckedArg, FunctionMap, ResolvedArgs,
};
use crate::css::{CallArgs, Value};
use crate::output::Format;
use crate::sass::{ArgsError, FormalArgs, Name};
use crate::value::{Color, Hsla, Numeric, Rational, Unit};
use crate::Scope;
use num_traits::zero;
pub fn register(f: &mut Scope) {
def_va!(f, _hsl(kwargs), |s| do_hsla(&name!(hsl), s));
def_va!(f, _hsla(kwargs), |s| do_hsla(&name!(hsla), s));
def!(f, complement(color), |s| {
Ok(s.get::<Color>(name!(color))?.rotate_hue(180.into()).into())
});
def!(f, hue(color), |s| {
let col = s.get::<Color>(name!(color))?;
let hsla = col.to_hsla();
Ok(Value::Numeric(Numeric::new(hsla.hue(), Unit::Deg), true))
});
def!(f, saturation(color), |s| {
Ok(percentage(s.get::<Color>(name!(color))?.to_hsla().sat()))
});
def!(f, lightness(color), |s| {
Ok(percentage(s.get::<Color>(name!(color))?.to_hsla().lum()))
});
def!(f, grayscale(color), |args| match args.get(name!(color))? {
Value::Color(col, _) => {
let col = col.to_hsla();
Ok(Hsla::new(col.hue(), zero(), col.lum(), col.alpha(), false)
.into())
}
v @ Value::Numeric(..) => Ok(make_call("grayscale", vec![v])),
v => Err(is_not(&v, "a color")).named(name!(color)),
});
}
pub fn expose(m: &Scope, global: &mut FunctionMap) {
for (gname, lname) in &[
(name!(hsl), name!(_hsl)),
(name!(hsla), name!(_hsla)),
(name!(complement), name!(complement)),
(name!(grayscale), name!(grayscale)),
(name!(hue), name!(hue)),
(name!(lightness), name!(lightness)),
(name!(saturation), name!(saturation)),
] {
global.insert(gname.clone(), m.get_lfunction(lname));
}
let mut f = Scope::builtin_module("sass:color");
def!(f, adjust_hue(color, degrees), |s| {
let col = s.get::<Color>(name!(color))?;
if let Some(adj) = s.get_opt_map(name!(degrees), check_hue)? {
Ok(col.rotate_hue(adj).into())
} else {
Ok(col.into())
}
});
def!(f, darken(color, amount), |s| {
let col = s.get::<Color>(name!(color))?;
let col = col.to_hsla();
let lum = col.lum() - s.get_map(name!(amount), check_amount)?;
Ok(Hsla::new(col.hue(), col.sat(), lum, col.alpha(), false).into())
});
def!(f, desaturate(color, amount), |s| {
let col = s.get::<Color>(name!(color))?;
let col = col.to_hsla();
let sat = col.sat() - s.get_map(name!(amount), check_amount)?;
Ok(Hsla::new(col.hue(), sat, col.lum(), col.alpha(), false).into())
});
def_va!(f, saturate(kwargs), |s| {
let a1 = FormalArgs::new(vec![one_arg!(color), one_arg!(amount)]);
let a2 = FormalArgs::new(vec![one_arg!(amount)]);
let args = s.get_map(name!(kwargs), CallArgs::from_value)?;
match eval_inner(&name!(saturate), &a1, s, args.clone()) {
Ok(s) => {
let col = s.get::<Color>(name!(color))?;
let sat = s.get_map(name!(amount), check_amount)?;
let col = col.to_hsla();
let sat = col.sat() + sat;
Ok(Hsla::new(col.hue(), sat, col.lum(), col.alpha(), false)
.into())
}
Err(CallError::Args(ArgsError::Missing(_), _)) => {
let s = eval_inner(&name!(saturate), &a2, s, args)?;
let sat = s.get_map(name!(amount), |v| {
check_pct(v.clone()).map(|_| v) })?;
Ok(make_call("saturate", vec![sat]))
}
Err(ae) => Err(ae),
}
});
def!(f, lighten(color, amount), |s| {
let col = s.get::<Color>(name!(color))?;
let col = col.to_hsla();
let lum = col.lum() + s.get_map(name!(amount), check_amount)?;
Ok(Hsla::new(col.hue(), col.sat(), lum, col.alpha(), false).into())
});
for (gname, lname) in &[
(name!(adjust_hue), name!(adjust_hue)),
(name!(darken), name!(darken)),
(name!(desaturate), name!(desaturate)),
(name!(lighten), name!(lighten)),
(name!(saturate), name!(saturate)),
] {
global.insert(gname.clone(), f.get_lfunction(lname));
}
}
fn do_hsla(fn_name: &Name, s: &ResolvedArgs) -> Result<Value, CallError> {
let a1 = FormalArgs::new(vec![one_arg!(channels)]);
let a2 = FormalArgs::new(vec![
one_arg!(hue),
one_arg!(saturation),
one_arg!(lightness = b"null"),
one_arg!(alpha = b"null"),
]);
let a2_show = FormalArgs::new(vec![
one_arg!(hue),
one_arg!(saturation),
one_arg!(lightness),
one_arg!(alpha),
]);
let args = s.get_map(name!(kwargs), CallArgs::from_value)?;
match eval_inner(fn_name, &a1, s, args.clone()) {
Ok(s) => Channels::try_from(s.get::<Value>(name!(channels))?)
.map_err(|e| e.conv(&["hue", "saturation", "lightness"]))
.and_then(|c| match c {
Channels::Data([h, s, l, a]) => {
hsla_from_values(fn_name, h, s, l, a)
}
Channels::Special(channels) => {
Ok(make_call(fn_name.as_ref(), vec![channels]))
}
}),
Err(err @ CallError::Args(ArgsError::Missing(_), _)) => Err(err),
Err(_) => {
let s = eval_inner(fn_name, &a2_show, s, args.clone()).or_else(
|e| eval_inner(fn_name, &a2, s, args).map_err(|_| e),
)?;
hsla_from_values(
fn_name,
s.get(name!(hue))?,
s.get(name!(saturation))?,
s.get(name!(lightness))?,
s.get(name!(alpha))?,
)
}
}
}
fn hsla_from_values(
fn_name: &Name,
h: Value,
s: Value,
l: Value,
a: Value,
) -> Result<Value, CallError> {
if is_special(&h) || is_special(&s) || is_special(&l) || is_special(&a) {
Ok(make_call(fn_name.as_ref(), vec![h, s, l, a]))
} else if l == Value::Null {
Err(CallError::msg("Missing argument $lightness."))
} else {
Ok(Hsla::new(
check_hue(h).named(name!(hue))?,
check_pct_opt(s).named(name!(saturation))?,
check_pct_opt(l).named(name!(lightness))?,
check_alpha(a).named(name!(alpha))?,
true, )
.into())
}
}
pub fn percentage(v: Rational) -> Value {
Numeric::new(v * 100, Unit::Percent).into()
}
fn check_pct_opt(v: Value) -> Result<Rational, String> {
let v = Numeric::try_from(v)?;
if !v.unit.is_percent() {
dep_warn!(
"Passing a number without unit % ({}) is deprecated.\
\nTo preserve current behavior: calc($weight / 1{} * 1%)\
\nMore info: https://sass-lang.com/d/function-units",
v.format(Format::introspect()),
v.unit
);
}
Ok(v.as_ratio()? / 100)
}
#[test]
fn test_hsl_black() {
assert_eq!(
do_evaluate(&[], b"hsl(17, 32%, 0%);"),
"hsl(17deg, 32%, 0%)"
)
}
#[test]
fn test_hsl_white() {
assert_eq!(
do_evaluate(&[], b"hsl(300, 82%, 100%);"),
"hsl(300deg, 82%, 100%)"
)
}
#[test]
fn test_hsl_gray() {
assert_eq!(
do_evaluate(&[], b"hsl(300, 0%, 50%);"),
"hsl(300deg, 0%, 50%)"
)
}
#[test]
fn test_hsl_red() {
assert_eq!(
do_evaluate(&[], b"hsl(0, 75%, 88%);"),
"hsl(0deg, 75%, 88%)"
)
}
#[test]
fn test_hsl_yellow() {
assert_eq!(
do_evaluate(&[], b"hsl(60, 100%, 63%);"),
"hsl(60deg, 100%, 63%)"
)
}
#[test]
fn test_hsl_blue_magenta() {
assert_eq!(
do_evaluate(&[], b"hsl(270, 75%, 38%);"),
"hsl(270deg, 75%, 38%)"
)
}
#[cfg(test)]
use crate::variablescope::test::do_evaluate;