Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve colors #198

Merged
merged 4 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ project adheres to

### Other changes:

* Lots of color handling.
- Spec changes for traditional css colors (PR #198).
* Updated sass-spec test suite to 2024-09-13.


Expand Down
4 changes: 2 additions & 2 deletions rsass/src/css/valueformat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ impl<'a> Display for Formatted<'a, Value> {
write!(out, "get-function(\"{name}\")")
}
Value::Numeric(ref num, _) => num.format(self.format).fmt(out),
Value::Color(ref rgba, ref name) => {
Value::Color(ref col, ref name) => {
if let Some(ref name) = *name {
name.fmt(out)
} else {
rgba.format(self.format).fmt(out)
col.format(self.format).fmt(out)
}
}
Value::List(ref v, sep, brackets) => {
Expand Down
90 changes: 32 additions & 58 deletions rsass/src/sass/functions/color/channels.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,67 +14,41 @@ pub enum Channels {
impl TryFrom<Value> for Channels {
type Error = ChaError;
fn try_from(channels: Value) -> Result<Self, ChaError> {
if is_special(&channels) {
Ok(Self::Special(channels))
} else if let Value::List(vec, sep, bracketed) = channels.clone() {
use crate::value::Operator::Div;
use Value::{BinOp, Numeric};
if bracketed {
return Err(ChaError::Bracketed);
use crate::value::Operator::Div;
match &channels {
c if is_special(c) => Ok(Self::Special(channels)),
Value::List(_, _, true) => Err(ChaError::Bracketed),
Value::List(_, Some(ListSeparator::Comma), _) => {
Err(ChaError::BadSep)
}
if sep == Some(ListSeparator::Comma) {
return Err(ChaError::BadSep);
}
if sep == Some(ListSeparator::Slash) {
match vec.as_slice() {
[Value::List(inner, i_s, i_b), a] => {
if *i_b {
return Err(ChaError::Bracketed);
}
if i_s.unwrap_or_default() != ListSeparator::Space {
return Err(ChaError::BadSep);
}
Ok(inner_channels(inner)?.map_or_else(
|| Self::Special(channels),
|[c1, c2, c3]| {
Self::Data([c1, c2, c3, a.clone()])
},
))
}
[h, _a] => {
if is_special(h) {
Ok(Self::Special(channels))
} else {
Err(ChaError::Missing1)
}
}
list => Err(ChaError::SlashBadNum(list.len())),
Value::List(v, Some(ListSeparator::Slash), _) => match &v[..] {
[Value::List(_, _, true), _] => Err(ChaError::Bracketed),
[Value::List(_, Some(i_s), _), _]
if *i_s != ListSeparator::Space =>
{
Err(ChaError::BadSep)
}
} else {
match vec.as_slice() {
[r, g, BinOp(op)] if op.op() == Div => Ok(
if let (b @ Numeric(..), a @ Numeric(..)) =
(op.a(), op.b())
{
Self::Data([
r.clone(),
g.clone(),
b.clone(),
a.clone(),
])
} else {
Self::Special(channels)
},
),
other => Ok(inner_channels(other)?
.map(|[c1, c2, c3]| {
Self::Data([c1, c2, c3, Value::Null])
})
.unwrap_or_else(|| Self::Special(channels))),
[Value::List(inner, _, _), a] => Ok(inner_channels(inner)?
.map(|[c1, c2, c3]| Self::Data([c1, c2, c3, a.clone()]))
.unwrap_or_else(|| Self::Special(channels))),
[h, _a] if is_special(h) => Ok(Self::Special(channels)),
[_, _] => Err(ChaError::Missing1),
list => Err(ChaError::SlashBadNum(list.len())),
},
Value::List(vec, _, false) => match &vec[..] {
[r, g, Value::BinOp(op)] if op.op() == Div => {
Ok(Self::Data([
r.clone(),
g.clone(),
op.a().clone(),
op.b().clone(),
]))
}
}
} else {
Err(ChaError::Missing1)
other => Ok(inner_channels(other)?
.map(|[c1, c2, c3]| Self::Data([c1, c2, c3, Value::Null]))
.unwrap_or_else(|| Self::Special(channels))),
},
_ => Err(ChaError::Missing1),
}
}
}
Expand Down
11 changes: 7 additions & 4 deletions rsass/src/sass/functions/color/hsl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ 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;
use num_traits::{one, zero};

pub fn register(f: &mut Scope) {
def_va!(f, _hsl(kwargs), |s| do_hsla(&name!(hsl), s));
Expand All @@ -30,9 +30,12 @@ pub fn register(f: &mut Scope) {
});
def!(f, grayscale(color), |args| match args.get(name!(color))? {
Value::Color(col, _) => {
let is_rgb = col.is_rgb();
let col = col.to_hsla();
Ok(Hsla::new(col.hue(), zero(), col.lum(), col.alpha(), false)
.into())
Ok(
Hsla::new(col.hue(), zero(), col.lum(), col.alpha(), !is_rgb)
.into(),
)
}
v @ Value::Numeric(..) => Ok(Value::call("grayscale", [v])),
v => Err(is_not(&v, "a color")).named(name!(color)),
Expand Down Expand Up @@ -91,7 +94,7 @@ pub fn expose(m: &Scope, global: &mut FunctionMap) {
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;
let sat = (col.sat() + sat).clamp(zero(), one());
Ok(Hsla::new(col.hue(), sat, col.lum(), col.alpha(), false)
.into())
}
Expand Down
41 changes: 29 additions & 12 deletions rsass/src/sass/functions/color/hwb.rs
Original file line number Diff line number Diff line change
@@ -1,32 +1,41 @@
use super::super::FunctionMap;
use super::hsl::percentage;
use super::{
check_alpha, check_expl_pct, eval_inner, is_not, CallError, CheckedArg,
ResolvedArgs,
check_alpha, check_expl_pct, eval_inner, is_not, relative_color,
CallError, CheckedArg, ResolvedArgs,
};
use crate::css::{CallArgs, Value};
use crate::output::Format;
use crate::sass::FormalArgs;
use crate::value::{Color, Hwba, ListSeparator, Numeric, Rational, Unit};
use crate::value::{
Color, Hwba, ListSeparator, Numeric, Rational, Rgba, Unit,
};
use crate::Scope;
use num_traits::{one, zero};

pub fn register(f: &mut Scope) {
def_va!(f, hwb(kwargs), hwb);
def!(f, blackness(color), |s| {
// Blackness of the rgb approximation that can be represented in css.
let (r, g, b, _a) = Color::to_rgba(&s.get(name!(color))?).to_bytes();
let max_c = *[r, g, b].iter().max().unwrap();
Ok(percentage(Rational::new((255 - max_c).into(), 255)))
let color: Color = s.get(name!(color))?;
let hwb = color.to_hwba();
Ok(percentage(hwb.blackness()))
});
def!(f, whiteness(color), |s| {
// Whiteness of the rgb approximation that can be represented in css.
let (r, g, b, _a) = Color::to_rgba(&s.get(name!(color))?).to_bytes();
let min_c = *[r, g, b].iter().min().unwrap();
Ok(percentage(Rational::new(min_c.into(), 255)))
let color: Color = s.get(name!(color))?;
let hwb = color.to_hwba();
Ok(percentage(hwb.whiteness()))
});
}

pub fn expose(m: &Scope, global: &mut FunctionMap) {
global.insert(name!(hwb), m.get_lfunction(&name!(hwb)));
}

fn hwb(s: &ResolvedArgs) -> Result<Value, CallError> {
let args = s.get_map(name!(kwargs), CallArgs::from_value)?;
if relative_color(&args) {
return Ok(Value::Call("hwb".to_string(), args));
}
let (hue, w, b, a) = if args.len() < 3 {
let a1 = FormalArgs::new(vec![one_arg!(channels)]);
eval_inner(&name!(hwb), &a1, s, args)
Expand All @@ -51,7 +60,15 @@ fn hwb(s: &ResolvedArgs) -> Result<Value, CallError> {
let w = check_expl_pct(w).named(name!(whiteness))?;
let b = check_expl_pct(b).named(name!(blackness))?;
let a = check_alpha(a).named(name!(alpha))?;
Ok(Hwba::new(hue, w, b, a).into())
// I don't really agree with this, but it makes tests pass.
let hue = if w + b >= one() { zero() } else { hue };
let hwba = Hwba::new(hue, w, b, a);
let rgba = Rgba::from(&hwba);
if rgba.is_integer() {
Ok(rgba.into())
} else {
Ok(hwba.into())
}
}

fn hwb_from_channels(
Expand Down
1 change: 1 addition & 0 deletions rsass/src/sass/functions/color/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ pub fn create_module() -> Scope {
pub fn expose(m: &Scope, global: &mut FunctionMap) {
rgb::expose(m, global);
hsl::expose(m, global);
hwb::expose(m, global);
other::expose(m, global);
}

Expand Down
Loading