mirror of
https://github.com/swc-project/swc.git
synced 2024-11-24 10:12:42 +03:00
feat(css/compat): Support color with hex alpha (#6643)
This commit is contained in:
parent
d831fb3cdd
commit
fe06b8061c
146
crates/swc_css_compat/src/compiler/color_hex_alpha.rs
Normal file
146
crates/swc_css_compat/src/compiler/color_hex_alpha.rs
Normal file
@ -0,0 +1,146 @@
|
||||
use swc_atoms::js_word;
|
||||
use swc_common::DUMMY_SP;
|
||||
use swc_css_ast::{
|
||||
AbsoluteColorBase, AlphaValue, Color, ComponentValue, Delimiter, DelimiterValue, Function,
|
||||
Ident, Number,
|
||||
};
|
||||
|
||||
use crate::compiler::Compiler;
|
||||
|
||||
#[inline]
|
||||
fn from_hex(c: u8) -> u8 {
|
||||
match c {
|
||||
b'0'..=b'9' => c - b'0',
|
||||
b'a'..=b'f' => c - b'a' + 10,
|
||||
b'A'..=b'F' => c - b'A' + 10,
|
||||
_ => {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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();
|
||||
|
||||
if length == 8
|
||||
&& (chars[6] == b'f' || chars[6] == b'F')
|
||||
&& (chars[7] == b'f' || chars[7] == b'F')
|
||||
{
|
||||
return Some(&value[0..6]);
|
||||
} else if length == 4 && chars[3] == b'f' || chars[3] == b'F' {
|
||||
return Some(&value[0..3]);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn hex_to_rgba(hex: &str) -> (u8, u8, u8, f64) {
|
||||
let hex = hex.as_bytes();
|
||||
|
||||
match hex.len() {
|
||||
8 => {
|
||||
let r = from_hex(hex[0]) * 16 + from_hex(hex[1]);
|
||||
let g = from_hex(hex[2]) * 16 + from_hex(hex[3]);
|
||||
let b = from_hex(hex[4]) * 16 + from_hex(hex[5]);
|
||||
let a = (from_hex(hex[6]) * 16 + from_hex(hex[7])) as f64 / 255.0;
|
||||
|
||||
(r, g, b, a)
|
||||
}
|
||||
4 => {
|
||||
let r = from_hex(hex[0]) * 17;
|
||||
let g = from_hex(hex[1]) * 17;
|
||||
let b = from_hex(hex[2]) * 17;
|
||||
let a = (from_hex(hex[3]) * 17) as f64 / 255.0;
|
||||
|
||||
(r, g, b, a)
|
||||
}
|
||||
|
||||
_ => {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Compiler {
|
||||
pub(crate) fn process_color_hex_alpha(&mut self, n: &mut ComponentValue) {
|
||||
if let ComponentValue::Color(box Color::AbsoluteColorBase(AbsoluteColorBase::HexColor(
|
||||
hex_color,
|
||||
))) = n
|
||||
{
|
||||
if hex_color.value.len() != 4 && hex_color.value.len() != 8 {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(shortened) = shorten_hex_color(&hex_color.value) {
|
||||
hex_color.value = shortened.into();
|
||||
hex_color.raw = None;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
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,
|
||||
name: Ident {
|
||||
span: DUMMY_SP,
|
||||
value: js_word!("rgba"),
|
||||
raw: None,
|
||||
},
|
||||
value: vec![
|
||||
ComponentValue::Number(Box::new(Number {
|
||||
span: DUMMY_SP,
|
||||
value: r,
|
||||
raw: None,
|
||||
})),
|
||||
ComponentValue::Delimiter(Box::new(Delimiter {
|
||||
span: DUMMY_SP,
|
||||
value: DelimiterValue::Comma,
|
||||
})),
|
||||
ComponentValue::Number(Box::new(Number {
|
||||
span: DUMMY_SP,
|
||||
value: g,
|
||||
raw: None,
|
||||
})),
|
||||
ComponentValue::Delimiter(Box::new(Delimiter {
|
||||
span: DUMMY_SP,
|
||||
value: DelimiterValue::Comma,
|
||||
})),
|
||||
ComponentValue::Number(Box::new(Number {
|
||||
span: DUMMY_SP,
|
||||
value: b,
|
||||
raw: None,
|
||||
})),
|
||||
ComponentValue::Delimiter(Box::new(Delimiter {
|
||||
span: DUMMY_SP,
|
||||
value: DelimiterValue::Comma,
|
||||
})),
|
||||
ComponentValue::AlphaValue(Box::new(AlphaValue::Number(Number {
|
||||
span: DUMMY_SP,
|
||||
value: rounded_alpha,
|
||||
raw: None,
|
||||
}))),
|
||||
],
|
||||
}),
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +1,14 @@
|
||||
use swc_common::{Spanned, DUMMY_SP};
|
||||
use swc_css_ast::{
|
||||
AtRule, MediaAnd, MediaCondition, MediaConditionAllType, MediaConditionWithoutOr,
|
||||
MediaInParens, MediaQuery, Rule,
|
||||
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_hex_alpha;
|
||||
mod custom_media;
|
||||
mod media_query_ranges;
|
||||
|
||||
@ -17,6 +18,7 @@ pub struct Compiler {
|
||||
#[allow(unused)]
|
||||
c: Config,
|
||||
custom_media: CustomMediaHandler,
|
||||
in_supports_condition: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -30,6 +32,7 @@ impl Compiler {
|
||||
Self {
|
||||
c: config,
|
||||
custom_media: Default::default(),
|
||||
in_supports_condition: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -43,6 +46,16 @@ impl VisitMut for Compiler {
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_mut_supports_condition(&mut self, n: &mut SupportsCondition) {
|
||||
let old_in_support_condition = self.in_supports_condition;
|
||||
|
||||
self.in_supports_condition = true;
|
||||
|
||||
n.visit_mut_children_with(self);
|
||||
|
||||
self.in_supports_condition = old_in_support_condition;
|
||||
}
|
||||
|
||||
fn visit_mut_media_query(&mut self, n: &mut MediaQuery) {
|
||||
n.visit_mut_children_with(self);
|
||||
|
||||
@ -105,4 +118,16 @@ impl VisitMut for Compiler {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_mut_component_value(&mut self, n: &mut ComponentValue) {
|
||||
n.visit_mut_children_with(self);
|
||||
|
||||
if self.in_supports_condition {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.c.process.contains(Features::COLOR_HEX_ALPHA) {
|
||||
self.process_color_hex_alpha(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,5 +5,6 @@ bitflags! {
|
||||
const NESTING = 1 << 0;
|
||||
const CUSTOM_MEDIA = 1 << 1;
|
||||
const MEDIA_QUERY_RANGES = 1 << 2;
|
||||
const COLOR_HEX_ALPHA = 1 << 3;
|
||||
}
|
||||
}
|
||||
|
52
crates/swc_css_compat/tests/color-hex-alpha/input.css
Normal file
52
crates/swc_css_compat/tests/color-hex-alpha/input.css
Normal file
@ -0,0 +1,52 @@
|
||||
body {
|
||||
background: #9d9 linear-gradient(#9823f8a9, #9823f834);
|
||||
color: red;
|
||||
color: #f00;
|
||||
color: #f00f;
|
||||
color: #FC0F;
|
||||
color: #0000ff;
|
||||
color: #0000ff00;
|
||||
color: #FFFFFF;
|
||||
content: "#f00";
|
||||
content: "#0000ff00";
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #f3f3f3f3;
|
||||
color: #0003;
|
||||
}
|
||||
|
||||
#svg-element {
|
||||
clip-path: url(#SVGID_1_);
|
||||
clip-path: url(#aaaa);
|
||||
}
|
||||
|
||||
.other {
|
||||
background: #ff0099;
|
||||
background: #ff0099ff;
|
||||
background: #ff009900;
|
||||
}
|
||||
|
||||
.short {
|
||||
color: #ffff;
|
||||
color: #ffffffff;
|
||||
color: #abdabcff;
|
||||
color: #000000FF;
|
||||
color: #abcf;
|
||||
color: #ff0f;
|
||||
color: #000f;
|
||||
color: #0000;
|
||||
color: #001e;
|
||||
}
|
||||
|
||||
@supports (color: #1111) {
|
||||
.a {
|
||||
color: #1111;
|
||||
}
|
||||
}
|
||||
|
||||
@supports (color: #11111111) {
|
||||
.a {
|
||||
color: #11111111;
|
||||
}
|
||||
}
|
46
crates/swc_css_compat/tests/color-hex-alpha/input.expect.css
Normal file
46
crates/swc_css_compat/tests/color-hex-alpha/input.expect.css
Normal file
@ -0,0 +1,46 @@
|
||||
body {
|
||||
background: #9d9 linear-gradient(rgba(152, 35, 248, 0.663), rgba(152, 35, 248, 0.204));
|
||||
color: red;
|
||||
color: #f00;
|
||||
color: #f00;
|
||||
color: #FC0;
|
||||
color: #0000ff;
|
||||
color: rgba(0, 0, 255, 0);
|
||||
color: #FFFFFF;
|
||||
content: "#f00";
|
||||
content: "#0000ff00";
|
||||
}
|
||||
body {
|
||||
background-color: rgba(243, 243, 243, 0.953);
|
||||
color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
#svg-element {
|
||||
clip-path: url(#SVGID_1_);
|
||||
clip-path: url(#aaaa);
|
||||
}
|
||||
.other {
|
||||
background: #ff0099;
|
||||
background: #ff0099;
|
||||
background: rgba(255, 0, 153, 0);
|
||||
}
|
||||
.short {
|
||||
color: #fff;
|
||||
color: #ffffff;
|
||||
color: #abdabc;
|
||||
color: #000000;
|
||||
color: #abc;
|
||||
color: #ff0;
|
||||
color: #000;
|
||||
color: rgba(0, 0, 0, 0);
|
||||
color: rgba(0, 0, 17, 0.933);
|
||||
}
|
||||
@supports (color: #1111) {
|
||||
.a {
|
||||
color: rgba(17, 17, 17, 0.067);
|
||||
}
|
||||
}
|
||||
@supports (color: #11111111) {
|
||||
.a {
|
||||
color: rgba(17, 17, 17, 0.067);
|
||||
}
|
||||
}
|
@ -125,3 +125,25 @@ fn test_media_query_ranges(input: PathBuf) {
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[testing::fixture("tests/color-hex-alpha/**/*.css", exclude("expect.css"))]
|
||||
fn test_color_hex_alpha(input: PathBuf) {
|
||||
let output = input.with_extension("expect.css");
|
||||
|
||||
testing::run_test(false, |cm, _| {
|
||||
//
|
||||
let fm = cm.load_file(&input).unwrap();
|
||||
let mut ss = parse_stylesheet(&fm);
|
||||
|
||||
ss.visit_mut_with(&mut Compiler::new(Config {
|
||||
process: Features::COLOR_HEX_ALPHA,
|
||||
}));
|
||||
|
||||
let s = print_stylesheet(&ss);
|
||||
|
||||
NormalizedOutput::from(s).compare_to_file(&output).unwrap();
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user