feat(css/compat): Support color with hex alpha (#6643)

This commit is contained in:
Alexander Akait 2022-12-14 08:21:17 +03:00 committed by GitHub
parent d831fb3cdd
commit fe06b8061c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 294 additions and 2 deletions

View 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,
}))),
],
}),
)));
}
}
}

View File

@ -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);
}
}
} }

View File

@ -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;
} }
} }

View 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;
}
}

View 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);
}
}

View File

@ -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();
}