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_common::{Spanned, DUMMY_SP};
|
||||||
use swc_css_ast::{
|
use swc_css_ast::{
|
||||||
AtRule, MediaAnd, MediaCondition, MediaConditionAllType, MediaConditionWithoutOr,
|
AtRule, ComponentValue, MediaAnd, MediaCondition, MediaConditionAllType,
|
||||||
MediaInParens, MediaQuery, Rule,
|
MediaConditionWithoutOr, MediaInParens, MediaQuery, Rule, SupportsCondition,
|
||||||
};
|
};
|
||||||
use swc_css_visit::{VisitMut, VisitMutWith};
|
use swc_css_visit::{VisitMut, VisitMutWith};
|
||||||
|
|
||||||
use self::custom_media::CustomMediaHandler;
|
use self::custom_media::CustomMediaHandler;
|
||||||
use crate::feature::Features;
|
use crate::feature::Features;
|
||||||
|
|
||||||
|
mod color_hex_alpha;
|
||||||
mod custom_media;
|
mod custom_media;
|
||||||
mod media_query_ranges;
|
mod media_query_ranges;
|
||||||
|
|
||||||
@ -17,6 +18,7 @@ pub struct Compiler {
|
|||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
c: Config,
|
c: Config,
|
||||||
custom_media: CustomMediaHandler,
|
custom_media: CustomMediaHandler,
|
||||||
|
in_supports_condition: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -30,6 +32,7 @@ impl Compiler {
|
|||||||
Self {
|
Self {
|
||||||
c: config,
|
c: config,
|
||||||
custom_media: Default::default(),
|
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) {
|
fn visit_mut_media_query(&mut self, n: &mut MediaQuery) {
|
||||||
n.visit_mut_children_with(self);
|
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 NESTING = 1 << 0;
|
||||||
const CUSTOM_MEDIA = 1 << 1;
|
const CUSTOM_MEDIA = 1 << 1;
|
||||||
const MEDIA_QUERY_RANGES = 1 << 2;
|
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();
|
.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