use super::{
check_alpha_pm, check_alpha_range, check_channel_pm, check_channel_range,
check_expl_pct, check_hue, check_pct, check_pct_range, expected_to,
CallError, CheckedArg, FunctionMap, Name,
};
use crate::css::{CallArgs, Value};
use crate::value::{Color, Hsla, Hwba, Numeric, RgbFormat, Rgba};
use crate::Scope;
pub fn register(f: &mut Scope) {
def_va!(f, adjust(color, kwargs), |s| {
fn opt_add(a: f64, b: Option<f64>) -> f64 {
if let Some(b) = b {
a + b
} else {
a
}
}
let color: Color = s.get(name!(color))?;
let mut args = s.get_map(name!(kwargs), CallArgs::from_value)?;
no_more_positional(&args)?;
let a_adj = take_opt(&mut args, name!(alpha), check_alpha_pm)?;
let red = take_opt(&mut args, name!(red), check_channel_pm)?;
let gre = take_opt(&mut args, name!(green), check_channel_pm)?;
let blu = take_opt(&mut args, name!(blue), check_channel_pm)?;
if red.is_some() || gre.is_some() || blu.is_some() {
no_more_in_space(&args, "rgb")?;
let rgba = color.to_rgba();
Ok(Rgba::new(
opt_add(rgba.red(), red),
opt_add(rgba.green(), gre),
opt_add(rgba.blue(), blu),
opt_add(rgba.alpha(), a_adj),
rgba.source(),
)
.into())
} else {
let hue = take_opt(&mut args, name!(hue), check_hue)?;
let sat = take_opt(&mut args, name!(saturation), check_pct)?;
let lig = take_opt(&mut args, name!(lightness), check_pct)?;
if sat.is_some() || lig.is_some() {
no_more_in_space(&args, "hsl")?;
}
let bla =
take_opt(&mut args, name!(blackness), check_pct_expl_pm)?;
let whi =
take_opt(&mut args, name!(whiteness), check_pct_expl_pm)?;
no_more_in_space(&args, "rgb")?;
if bla.is_some() || whi.is_some() {
let hwba = color.to_hwba();
let hwba = Hwba::new(
opt_add(hwba.hue(), hue),
opt_add(hwba.whiteness(), whi),
opt_add(hwba.blackness(), bla),
opt_add(hwba.alpha(), a_adj),
);
Ok(Rgba::from(&hwba).into())
} else if hue.is_some() || sat.is_some() || lig.is_some() {
let hsla = color.to_hsla();
let sat = opt_add(hsla.sat(), sat);
let lum = opt_add(hsla.lum(), lig);
Ok(Hsla::new(
opt_add(hsla.hue(), hue),
sat,
lum,
opt_add(hsla.alpha(), a_adj),
hsla.hsla_format
|| sat > 1.
|| !(0. ..=1.).contains(&lum),
)
.into())
} else {
let mut color = color.clone();
color.set_alpha(opt_add(color.get_alpha(), a_adj));
Ok(color.into())
}
}
});
def_va!(f, scale(color, kwargs), |s| {
let cmb = |orig: f64, x: Option<f64>, max: f64| match x {
None => orig,
Some(x) => {
if x.is_sign_positive() {
orig + (max - orig) * x
} else {
orig + orig * x
}
}
};
let one = 1.0;
let ff = 255.0;
let color: Color = s.get(name!(color))?;
let mut args = s.get_map(name!(kwargs), CallArgs::from_value)?;
no_more_positional(&args)?;
let a_adj = take_opt(&mut args, name!(alpha), check_pct_expl_pm)?;
take_opt(&mut args, name!(hue), check_none_scalable)?;
let red = take_opt(&mut args, name!(red), check_pct_expl_pm)?;
let gre = take_opt(&mut args, name!(green), check_pct_expl_pm)?;
let blu = take_opt(&mut args, name!(blue), check_pct_expl_pm)?;
if red.is_some() || gre.is_some() || blu.is_some() {
no_more_in_space(&args, "rgb")?;
let rgba = color.to_rgba();
let rgba = Rgba::new(
cmb(rgba.red(), red, ff),
cmb(rgba.green(), gre, ff),
cmb(rgba.blue(), blu, ff),
cmb(rgba.alpha(), a_adj, one),
RgbFormat::Name,
);
return Ok(rgba.into());
}
let sat = take_opt(&mut args, name!(saturation), check_pct_expl_pm)?;
let lig = take_opt(&mut args, name!(lightness), check_pct_expl_pm)?;
if sat.is_some() || lig.is_some() {
no_more_in_space(&args, "hsl")?;
}
let bla = take_opt(&mut args, name!(blackness), check_pct_expl_pm)?;
let whi = take_opt(&mut args, name!(whiteness), check_pct_expl_pm)?;
no_more_in_space(&args, "rgb")?;
if bla.is_none() && whi.is_none() {
let hsla = color.to_hsla();
Ok(Hsla::new(
hsla.hue(),
cmb(hsla.sat(), sat, one),
cmb(hsla.lum(), lig, one),
cmb(hsla.alpha(), a_adj, one),
hsla.hsla_format,
)
.into())
} else {
let is_rgb = color.is_rgb();
let hwba = color.to_hwba();
let hwba = Hwba::new(
hwba.hue(),
cmb(hwba.whiteness(), whi, one),
cmb(hwba.blackness(), bla, one),
cmb(hwba.alpha(), a_adj, one),
);
if is_rgb {
Ok(Rgba::from(&hwba).into())
} else {
Ok(hwba.into())
}
}
});
def!(f, opacity(color), |s| match s.get(name!(color))? {
Value::Color(ref col, _) => Ok(Value::scalar(col.get_alpha())),
v => Ok(Value::call("opacity", [v])),
});
def!(f, alpha(color), |s| {
let v = s.get(name!(color))?;
if ok_as_filterarg(&v) {
Ok(Value::call("alpha", [v]))
} else {
let color = Color::try_from(v).named(name!(color))?;
Ok(Value::scalar(color.get_alpha()))
}
});
def_va!(f, change(color, kwargs), |s| {
let rgba: Color = s.get(name!(color))?;
let mut args = s.get_map(name!(kwargs), CallArgs::from_value)?;
no_more_positional(&args)?;
let alp = take_opt(&mut args, name!(alpha), check_alpha_range)?;
let red = take_opt(&mut args, name!(red), check_channel_range)?;
let gre = take_opt(&mut args, name!(green), check_channel_range)?;
let blu = take_opt(&mut args, name!(blue), check_channel_range)?;
if red.is_some() || gre.is_some() || blu.is_some() {
no_more_in_space(&args, "rgb")?;
let rgba = rgba.to_rgba();
let rgba = Rgba::new(
red.unwrap_or_else(|| rgba.red()),
gre.unwrap_or_else(|| rgba.green()),
blu.unwrap_or_else(|| rgba.blue()),
alp.unwrap_or_else(|| rgba.alpha()),
rgba.source(),
);
return Ok(rgba.into());
}
let hue = take_opt(&mut args, name!(hue), check_hue)?;
let sat = take_opt(&mut args, name!(saturation), check_pct_range)?;
let lig = take_opt(&mut args, name!(lightness), check_pct_range)?;
if sat.is_some() || lig.is_some() {
no_more_in_space(&args, "hsl")?;
}
let bla = take_opt(&mut args, name!(blackness), check_expl_pct)?;
let whi = take_opt(&mut args, name!(whiteness), check_expl_pct)?;
no_more_in_space(&args, "rgb")?;
if bla.is_none() && whi.is_none() {
let hsla = rgba.to_hsla();
Ok(Hsla::new(
hue.unwrap_or_else(|| hsla.hue()),
sat.unwrap_or_else(|| hsla.sat()),
lig.unwrap_or_else(|| hsla.lum()),
alp.unwrap_or_else(|| hsla.alpha()),
false, )
.into())
} else {
let hwba = rgba.to_hwba();
let hwba = Hwba::new(
hue.unwrap_or_else(|| hwba.hue()),
whi.unwrap_or_else(|| hwba.whiteness()),
bla.unwrap_or_else(|| hwba.blackness()),
alp.unwrap_or_else(|| hwba.alpha()),
);
Ok(Rgba::from(&hwba).into())
}
});
def!(f, ie_hex_str(color), |s| {
let (r, g, b, alpha) =
Color::to_rgba(&s.get(name!(color))?).to_bytes();
Ok(format!("#{alpha:02X}{r:02X}{g:02X}{b:02X}").into())
});
}
pub fn expose(m: &Scope, global: &mut FunctionMap) {
for (gname, lname) in &[
(name!(adjust_color), name!(adjust)),
(name!(alpha), name!(alpha)),
(name!(opacity), name!(opacity)),
(name!(change_color), name!(change)),
(name!(ie_hex_str), name!(ie_hex_str)),
(name!(scale_color), name!(scale)),
] {
global.insert(gname.clone(), m.get_lfunction(lname));
}
let mut f = Scope::builtin_module("sass:color");
def!(f, fade_in(color, amount), |s| {
let mut col: Color = s.get(name!(color))?;
let amount = s.get_map(name!(amount), check_alpha_range)?;
col.set_alpha(col.get_alpha() + amount);
Ok(col.into())
});
def!(f, fade_out(color, amount), |s| {
let mut col: Color = s.get(name!(color))?;
let amount = s.get_map(name!(amount), check_alpha_range)?;
col.set_alpha(col.get_alpha() - amount);
Ok(col.into())
});
for (gname, lname) in &[
(name!(fade_in), name!(fade_in)),
(name!(fade_out), name!(fade_out)),
(name!(opacify), name!(fade_in)),
(name!(transparentize), name!(fade_out)),
] {
global.insert(gname.clone(), f.get_lfunction(lname));
}
}
fn check_none_scalable(_: Value) -> Result<(), String> {
Err("Channel isn't scalable.".into())
}
fn no_more_positional(args: &CallArgs) -> Result<(), CallError> {
if !args.positional.is_empty() {
Err(CallError::msg(
"Only one positional argument is allowed. \
All other arguments must be passed by name.",
))
} else {
Ok(())
}
}
fn no_more_in_space(args: &CallArgs, space: &str) -> Result<(), CallError> {
if let Some((name, _)) = args.named.iter().next() {
Err(CallError::msg(format!("${name}: Color space {space} doesn\'t have a channel with this name.")))
} else {
Ok(())
}
}
fn take_opt<T, F>(
args: &mut CallArgs,
name: Name,
check: F,
) -> Result<Option<T>, CallError>
where
F: Fn(Value) -> Result<T, String>,
{
args.named.remove(&name).map(check).transpose().named(name)
}
fn check_pct_expl_pm(v: Value) -> Result<f64, String> {
let val = Numeric::try_from(v)?;
if !val.unit.is_percent() {
return Err(expected_to(val, "have unit \"%\""));
}
if f64::from(val.value.abs()) > 100. {
Err(expected_to(val, "be within -100% and 100%"))
} else {
Ok(f64::from(val.value) / 100.)
}
}
fn ok_as_filterarg(v: &Value) -> bool {
match v {
Value::Literal(ref s) if s.quotes().is_none() => {
use crate::parser::strings::unitname;
use crate::parser::{code_span, util::opt_spacelike};
use nom::bytes::complete::tag;
use nom::Parser as _;
let span = code_span(s.value().as_ref());
let b = (unitname, opt_spacelike, tag("="))
.parse(span.borrow())
.is_ok();
b
}
Value::List(..) => true,
_ => false,
}
}
#[cfg(test)]
mod test {
use crate::variablescope::test::do_evaluate;
#[test]
fn ie_hex_str_a() {
assert_eq!(do_evaluate(&[], b"ie-hex-str(#abc);"), "#FFAABBCC")
}
#[test]
fn ie_hex_str_b() {
assert_eq!(do_evaluate(&[], b"ie-hex-str(#3322BB);"), "#FF3322BB")
}
#[test]
fn ie_hex_str_c() {
assert_eq!(
do_evaluate(&[], b"ie-hex-str(rgba(0, 255, 0, 0.5));"),
"#8000FF00"
)
}
}