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

feat(css/compat): function notation #6651

Merged
merged 13 commits into from Dec 16, 2022
47 changes: 47 additions & 0 deletions crates/swc_css_compat/src/compiler/color_alpha_parameter.rs
@@ -0,0 +1,47 @@
use swc_atoms::js_word;
use swc_css_ast::{AbsoluteColorBase, ComponentValue};

use crate::compiler::Compiler;

impl Compiler {
pub(crate) fn process_color_alpha_parameter(&mut self, n: &mut AbsoluteColorBase) {
if let AbsoluteColorBase::Function(function) = n {
let name = function.name.value.to_ascii_lowercase();

if let Some(ComponentValue::AlphaValue(_) | ComponentValue::Function(_)) =
function.value.last()
{
if !matches!(
name,
js_word!("rgb") | js_word!("rgba") | js_word!("hsl") | js_word!("hsla")
) {
return;
}

match name {
js_word!("rgb") => {
function.name.value = js_word!("rgba");
function.name.raw = None;
}
js_word!("hsl") => {
function.name.value = js_word!("hsla");
function.name.raw = None;
}
_ => {}
}
} else {
match name {
js_word!("rgba") => {
function.name.value = js_word!("rgb");
function.name.raw = None;
}
js_word!("hsla") => {
function.name.value = js_word!("hsl");
function.name.raw = None;
}
_ => {}
}
}
}
}
}
26 changes: 5 additions & 21 deletions crates/swc_css_compat/src/compiler/color_hex_alpha.rs
Expand Up @@ -5,7 +5,7 @@ use swc_css_ast::{
Ident, Number,
};

use crate::compiler::Compiler;
use crate::compiler::{utils::round_alpha, Compiler};

#[inline]
fn from_hex(c: u8) -> u8 {
Expand All @@ -19,11 +19,6 @@ fn from_hex(c: u8) -> u8 {
}
}

#[inline]
fn clamp_unit_f32(val: f64) -> u8 {
(val * 255.).round().max(0.).min(255.) as u8
}

fn shorten_hex_color(value: &str) -> Option<&str> {
let length = value.len();
let chars = value.as_bytes();
Expand Down Expand Up @@ -86,17 +81,6 @@ impl Compiler {

let rgba = hex_to_rgba(&hex_color.value);

let r = rgba.0 as f64;
let g = rgba.1 as f64;
let b = rgba.2 as f64;
let a = rgba.3;

let mut rounded_alpha = (a * 100.).round() / 100.;

if clamp_unit_f32(rounded_alpha) != clamp_unit_f32(a) {
rounded_alpha = (a * 1000.).round() / 1000.;
}

*n = ComponentValue::Color(Box::new(Color::AbsoluteColorBase(
AbsoluteColorBase::Function(Function {
span: hex_color.span,
Expand All @@ -108,7 +92,7 @@ impl Compiler {
value: vec![
ComponentValue::Number(Box::new(Number {
span: DUMMY_SP,
value: r,
value: rgba.0 as f64,
raw: None,
})),
ComponentValue::Delimiter(Box::new(Delimiter {
Expand All @@ -117,7 +101,7 @@ impl Compiler {
})),
ComponentValue::Number(Box::new(Number {
span: DUMMY_SP,
value: g,
value: rgba.1 as f64,
raw: None,
})),
ComponentValue::Delimiter(Box::new(Delimiter {
Expand All @@ -126,7 +110,7 @@ impl Compiler {
})),
ComponentValue::Number(Box::new(Number {
span: DUMMY_SP,
value: b,
value: rgba.2 as f64,
raw: None,
})),
ComponentValue::Delimiter(Box::new(Delimiter {
Expand All @@ -135,7 +119,7 @@ impl Compiler {
})),
ComponentValue::AlphaValue(Box::new(AlphaValue::Number(Number {
span: DUMMY_SP,
value: rounded_alpha,
value: round_alpha(rgba.3),
raw: None,
}))),
],
Expand Down
@@ -0,0 +1,72 @@
use std::mem::take;

use swc_atoms::js_word;
use swc_common::DUMMY_SP;
use swc_css_ast::{AbsoluteColorBase, ComponentValue, Delimiter, DelimiterValue};

use crate::compiler::Compiler;

impl Compiler {
pub(crate) fn process_color_space_separated_function_notation(
&mut self,
n: &mut AbsoluteColorBase,
) {
if let AbsoluteColorBase::Function(function) = n {
let name = function.name.value.to_ascii_lowercase();

if !matches!(
name,
js_word!("rgb") | js_word!("rgba") | js_word!("hsl") | js_word!("hsla")
) {
return;
}

if function.value.len() != 3 && function.value.len() != 5 {
return;
}

if function.value.iter().any(|n| {
matches!(
n,
ComponentValue::Delimiter(box Delimiter {
value: DelimiterValue::Comma,
..
})
)
}) {
return;
}

let new_value: Vec<ComponentValue> = take(&mut function.value)
.into_iter()
.enumerate()
.flat_map(|(idx, node)| {
if matches!(idx, 0 | 1) {
vec![
node,
ComponentValue::Delimiter(Box::new(Delimiter {
value: DelimiterValue::Comma,
span: DUMMY_SP,
})),
]
} else if matches!(
node,
ComponentValue::Delimiter(box Delimiter {
value: DelimiterValue::Solidus,
..
})
) {
vec![ComponentValue::Delimiter(Box::new(Delimiter {
value: DelimiterValue::Comma,
span: DUMMY_SP,
}))]
} else {
vec![node]
}
})
.collect::<Vec<_>>();

function.value = new_value;
}
}
}
89 changes: 89 additions & 0 deletions crates/swc_css_compat/src/compiler/legacy_rgb_and_hsl.rs
@@ -0,0 +1,89 @@
use std::f64::consts::PI;

use swc_atoms::js_word;
use swc_css_ast::{AbsoluteColorBase, AlphaValue, Angle, ComponentValue, Hue, Number, Percentage};

use crate::compiler::{
utils::{clamp_unit_f32, round_alpha},
Compiler,
};

impl Compiler {
pub(crate) fn process_rgb_and_hsl(&mut self, n: &mut AbsoluteColorBase) {
if let AbsoluteColorBase::Function(function) = n {
let name = function.name.value.to_ascii_lowercase();

let is_rgb = matches!(name, js_word!("rgb") | js_word!("rgba"));
let is_hsl = matches!(name, js_word!("hsl") | js_word!("hsla"));

if is_rgb {
function.value = function
.value
.drain(..)
.into_iter()
.map(|n| match n {
ComponentValue::Percentage(box Percentage {
span,
value: Number { value, .. },
..
}) => ComponentValue::Number(Box::new(Number {
span,
value: clamp_unit_f32(value / 100.0) as f64,
raw: None,
})),
_ => n,
})
.collect();
} else if is_hsl {
function.value = function
.value
.drain(..)
.into_iter()
.map(|n| match n {
ComponentValue::Hue(box Hue::Angle(Angle {
span,
value: Number { value, .. },
unit,
..
})) => {
let value = match unit.value.to_ascii_lowercase() {
js_word!("deg") => value,
js_word!("grad") => value * 180.0 / 200.0,
js_word!("rad") => value * 180.0 / PI,
js_word!("turn") => value * 360.0,
_ => {
unreachable!();
}
};

ComponentValue::Number(Box::new(Number {
span,
value: value.round(),
raw: None,
}))
}
_ => n,
})
.collect();
}

if is_rgb || is_hsl {
if let Some(ComponentValue::AlphaValue(box alpha_value)) = function.value.last_mut()
{
if let AlphaValue::Percentage(Percentage {
span,
value: Number { value: a, .. },
..
}) = alpha_value
{
*alpha_value = AlphaValue::Number(Number {
span: *span,
value: round_alpha(*a / 100.0),
raw: None,
});
}
}
}
}
}
}
31 changes: 30 additions & 1 deletion crates/swc_css_compat/src/compiler/mod.rs
@@ -1,16 +1,20 @@
use swc_common::{Spanned, DUMMY_SP};
use swc_css_ast::{
AtRule, ComponentValue, MediaAnd, MediaCondition, MediaConditionAllType,
AbsoluteColorBase, AtRule, ComponentValue, MediaAnd, MediaCondition, MediaConditionAllType,
MediaConditionWithoutOr, MediaInParens, MediaQuery, Rule, SupportsCondition,
};
use swc_css_visit::{VisitMut, VisitMutWith};

use self::custom_media::CustomMediaHandler;
use crate::feature::Features;

mod color_alpha_parameter;
mod color_hex_alpha;
mod color_space_separated_parameters;
mod custom_media;
mod legacy_rgb_and_hsl;
mod media_query_ranges;
mod utils;

/// Compiles a modern CSS file to a CSS file which works with old browsers.
#[derive(Debug)]
Expand Down Expand Up @@ -130,4 +134,29 @@ impl VisitMut for Compiler {
self.process_color_hex_alpha(n);
}
}

fn visit_mut_absolute_color_base(&mut self, n: &mut AbsoluteColorBase) {
n.visit_mut_children_with(self);

if self.in_supports_condition {
return;
}

// TODO handle color functions in custom variables under the option
// TODO implement the `preserve` option to preserve the original color

let process = self.c.process;

if process.contains(Features::COLOR_SPACE_SEPARATED_PARAMETERS) {
self.process_color_space_separated_function_notation(n);
}

if process.contains(Features::COLOR_ALPHA_PARAMETER) {
self.process_color_alpha_parameter(n);
}

if process.contains(Features::COLOR_LEGACY_RGB_AND_HSL) {
self.process_rgb_and_hsl(n);
}
}
}
15 changes: 15 additions & 0 deletions crates/swc_css_compat/src/compiler/utils.rs
@@ -0,0 +1,15 @@
#[inline]
pub(crate) fn clamp_unit_f32(val: f64) -> u8 {
(val * 255.).round().max(0.).min(255.) as u8
}

#[inline]
pub(crate) fn round_alpha(alpha: f64) -> f64 {
let mut rounded_alpha = (alpha * 100.).round() / 100.;

if clamp_unit_f32(rounded_alpha) != clamp_unit_f32(alpha) {
rounded_alpha = (alpha * 1000.).round() / 1000.;
}

rounded_alpha
}
3 changes: 3 additions & 0 deletions crates/swc_css_compat/src/feature.rs
Expand Up @@ -6,5 +6,8 @@ bitflags! {
const CUSTOM_MEDIA = 1 << 1;
const MEDIA_QUERY_RANGES = 1 << 2;
const COLOR_HEX_ALPHA = 1 << 3;
const COLOR_ALPHA_PARAMETER = 1 << 4;
const COLOR_SPACE_SEPARATED_PARAMETERS = 1 << 5;
const COLOR_LEGACY_RGB_AND_HSL = 1 << 6;
}
}