use crate as css;
use crate::css_parser::CssResult as Result;
use crate::dependencies::UrlDependency;
use crate::generics::DeepClone as _;
use crate::values::color::ColorFallbackKind;
use crate::values::gradient::Gradient;
use crate::values::resolution::Resolution;
use crate::values::url::Url;
use crate::{PrintErr, VendorPrefix};
use bun_alloc::Arena;
use bun_ast::ImportKind;
#[derive(Default)]
pub enum Image {
#[default]
None,
Url(Url),
Gradient(Box<Gradient>),
ImageSet(ImageSet),
}
impl Image {
pub fn is_compatible(&self, browsers: &css::targets::Browsers) -> bool {
match self {
Image::Gradient(g) => match &**g {
Gradient::Linear(linear) => {
css::Feature::LinearGradient.is_compatible(browsers)
&& linear.is_compatible(browsers)
}
Gradient::RepeatingLinear(repeating_linear) => {
css::Feature::RepeatingLinearGradient.is_compatible(browsers)
&& repeating_linear.is_compatible(browsers)
}
Gradient::Radial(radial) => {
css::Feature::RadialGradient.is_compatible(browsers)
&& radial.is_compatible(browsers)
}
Gradient::RepeatingRadial(repeating_radial) => {
css::Feature::RepeatingRadialGradient.is_compatible(browsers)
&& repeating_radial.is_compatible(browsers)
}
Gradient::Conic(conic) => {
css::Feature::ConicGradient.is_compatible(browsers)
&& conic.is_compatible(browsers)
}
Gradient::RepeatingConic(repeating_conic) => {
css::Feature::RepeatingConicGradient.is_compatible(browsers)
&& repeating_conic.is_compatible(browsers)
}
Gradient::WebkitGradient(_) => css::prefixes::Feature::is_webkit_gradient(browsers),
},
Image::ImageSet(image_set) => image_set.is_compatible(browsers),
Image::Url(_) | Image::None => true,
}
}
pub fn get_prefixed(&self, arena: &Arena, prefix: css::VendorPrefix) -> Image {
match self {
Image::Gradient(grad) => Image::Gradient(Box::new(grad.get_prefixed(arena, prefix))),
Image::ImageSet(image_set) => Image::ImageSet(image_set.get_prefixed(arena, prefix)),
_ => self.deep_clone(arena),
}
}
pub fn get_necessary_prefixes(&self, targets: &css::targets::Targets) -> css::VendorPrefix {
match self {
Image::Gradient(grad) => grad.get_necessary_prefixes(targets),
Image::ImageSet(image_set) => image_set.get_necessary_prefixes(targets),
_ => css::VendorPrefix::NONE,
}
}
pub fn has_vendor_prefix(&self) -> bool {
let prefix = self.get_vendor_prefix();
!prefix.is_empty() && prefix != VendorPrefix::NONE
}
pub fn get_vendor_prefix(&self) -> VendorPrefix {
match self {
Image::Gradient(a) => a.get_vendor_prefix(),
Image::ImageSet(a) => a.get_vendor_prefix(),
_ => VendorPrefix::empty(),
}
}
pub fn get_image(&self) -> &Image {
self
}
pub fn with_image(&self, _arena: &Arena, image: Image) -> Self {
let _ = self;
image
}
#[inline]
pub fn eql(&self, other: &Image) -> bool {
match (self, other) {
(Image::None, Image::None) => true,
(Image::Url(a), Image::Url(b)) => a.import_record_idx == b.import_record_idx,
(Image::Gradient(a), Image::Gradient(b)) => a == b,
(Image::ImageSet(a), Image::ImageSet(b)) => a.eql(b),
_ => false,
}
}
pub fn deep_clone(&self, arena: &Arena) -> Self {
match self {
Image::None => Image::None,
Image::Url(u) => Image::Url(Url {
import_record_idx: u.import_record_idx,
loc: u.loc,
}),
Image::Gradient(g) => Image::Gradient(g.deep_clone(arena)),
Image::ImageSet(s) => Image::ImageSet(s.deep_clone(arena)),
}
}
pub fn get_legacy_webkit(&self, arena: &Arena) -> Option<Image> {
match self {
Image::Gradient(gradient) => {
Some(Image::Gradient(Box::new(
gradient.get_legacy_webkit(arena)?,
)))
}
_ => Some(self.deep_clone(arena)),
}
}
pub fn get_fallbacks(
&mut self,
arena: &Arena,
targets: &css::targets::Targets,
) -> css::SmallList<Image, 6> {
let prefixes = self.get_necessary_prefixes(targets);
let fallbacks = self.get_necessary_fallbacks(targets);
let mut res: css::SmallList<Image, 6> = css::SmallList::default();
let rgb = if fallbacks.contains(ColorFallbackKind::RGB) {
Some(self.get_fallback(arena, ColorFallbackKind::RGB))
} else {
None
};
let prefix_image: &Image = if let Some(r) = &rgb { r } else { &*self };
if prefixes.contains(VendorPrefix::WEBKIT)
&& if let Some(browsers) = targets.browsers {
css::prefixes::Feature::is_webkit_gradient(&browsers)
} else {
false && matches!(prefix_image, Image::Gradient(_))
}
{
if let Some(legacy) = prefix_image.get_legacy_webkit(arena) {
res.append(legacy);
}
}
if prefixes.contains(VendorPrefix::WEBKIT) {
res.append(prefix_image.get_prefixed(arena, css::VendorPrefix::WEBKIT));
}
if prefixes.contains(VendorPrefix::MOZ) {
res.append(prefix_image.get_prefixed(arena, css::VendorPrefix::MOZ));
}
if prefixes.contains(VendorPrefix::O) {
res.append(prefix_image.get_prefixed(arena, css::VendorPrefix::O));
}
if prefixes.contains(VendorPrefix::NONE) {
if let Some(r) = rgb {
res.append(r);
}
if fallbacks.contains(ColorFallbackKind::P3) {
res.append(self.get_fallback(arena, ColorFallbackKind::P3));
}
if fallbacks.contains(ColorFallbackKind::LAB) {
*self = self.get_fallback(arena, ColorFallbackKind::LAB);
}
} else if let Some(last) = res.pop() {
*self = last;
}
res
}
pub fn get_fallback(&self, arena: &Arena, kind: ColorFallbackKind) -> Image {
match self {
Image::Gradient(grad) => Image::Gradient(Box::new(grad.get_fallback(arena, kind))),
_ => self.deep_clone(arena),
}
}
pub fn get_necessary_fallbacks(&self, targets: &css::targets::Targets) -> ColorFallbackKind {
match self {
Image::Gradient(grad) => grad.get_necessary_fallbacks(targets),
_ => ColorFallbackKind::empty(),
}
}
pub fn parse(input: &mut css::Parser) -> Result<Image> {
if input
.try_parse(|i| i.expect_ident_matching(b"none"))
.is_ok()
{
return Ok(Image::None);
}
if let Ok(url) = input.try_parse(Url::parse) {
return Ok(Image::Url(url));
}
if let Ok(g) = input.try_parse(Gradient::parse) {
return Ok(Image::Gradient(Box::new(g)));
}
ImageSet::parse(input).map(Image::ImageSet)
}
pub fn to_css(&self, dest: &mut css::Printer) -> core::result::Result<(), css::PrintErr> {
match self {
Image::None => dest.write_str(b"none"),
Image::Url(u) => u.to_css(dest),
Image::Gradient(g) => g.to_css(dest),
Image::ImageSet(s) => s.to_css(dest),
}
}
}
impl crate::small_list::ImageFallback for Image {
#[inline]
fn get_image(&self) -> &Image {
Image::get_image(self)
}
#[inline]
fn with_image(&self, arena: &Arena, image: Image) -> Self {
Image::with_image(self, arena, image)
}
#[inline]
fn get_fallback(&self, arena: &Arena, kind: ColorFallbackKind) -> Self {
Image::get_fallback(self, arena, kind)
}
#[inline]
fn get_necessary_fallbacks(&self, targets: &css::targets::Targets) -> ColorFallbackKind {
Image::get_necessary_fallbacks(self, targets)
}
}
pub struct ImageSet {
pub options: Vec<ImageSetOption>,
pub vendor_prefix: VendorPrefix,
}
impl ImageSet {
pub(crate) fn parse(input: &mut css::Parser) -> Result<ImageSet> {
let location = input.current_source_location();
let f: &'static [u8] = unsafe { &*std::ptr::from_ref::<[u8]>(input.expect_function()?) };
let vendor_prefix = crate::match_ignore_ascii_case! { f, {
b"image-set" => VendorPrefix::NONE,
b"-webkit-image-set" => VendorPrefix::WEBKIT,
_ => return Result::Err(location.new_unexpected_token_error(css::Token::Ident(f))),
}};
let options = input.parse_nested_block(|i: &mut css::Parser| {
i.parse_comma_separated(ImageSetOption::parse)
})?;
Result::Ok(ImageSet {
options,
vendor_prefix,
})
}
pub(crate) fn to_css(&self, dest: &mut css::Printer) -> core::result::Result<(), PrintErr> {
self.vendor_prefix.to_css(dest)?;
dest.write_str("image-set(")?;
let prefixed = self.vendor_prefix != VendorPrefix::NONE;
dest.write_comma_separated(self.options.iter(), |d, opt| opt.to_css(d, prefixed))?;
dest.write_char(b')')
}
pub(crate) fn is_compatible(&self, browsers: &css::targets::Browsers) -> bool {
css::Feature::ImageSet.is_compatible(browsers)
&& 'blk: {
for opt in self.options.iter() {
if !opt.image.is_compatible(browsers) {
break 'blk false;
}
}
true
}
}
pub(crate) fn get_prefixed(&self, arena: &Arena, prefix: css::VendorPrefix) -> ImageSet {
ImageSet {
options: self.options.iter().map(|o| o.deep_clone(arena)).collect(),
vendor_prefix: prefix,
}
}
pub(crate) fn eql(&self, other: &ImageSet) -> bool {
self.vendor_prefix == other.vendor_prefix
&& self.options.len() == other.options.len()
&& self
.options
.iter()
.zip(other.options.iter())
.all(|(a, b)| a.eql(b))
}
pub(crate) fn deep_clone(&self, arena: &Arena) -> Self {
ImageSet {
options: self.options.iter().map(|o| o.deep_clone(arena)).collect(),
vendor_prefix: self.vendor_prefix,
}
}
pub(crate) fn get_vendor_prefix(&self) -> VendorPrefix {
self.vendor_prefix
}
pub(crate) fn get_necessary_prefixes(
&self,
targets: &css::targets::Targets,
) -> css::VendorPrefix {
targets.prefixes(self.vendor_prefix, css::prefixes::Feature::ImageSet)
}
}
pub struct ImageSetOption {
pub image: Image,
pub resolution: Resolution,
pub file_type: Option<*const [u8]>,
}
impl ImageSetOption {
pub(crate) fn parse(input: &mut css::Parser) -> Result<ImageSetOption> {
let start_position = input.input.tokenizer.get_position();
let loc = input.current_source_location();
let image = if let Ok(url) =
input.try_parse(|p| p.expect_url_or_string().map(std::ptr::from_ref::<[u8]>))
{
let url: &[u8] = unsafe { crate::arena_str(url) };
let record_idx = input.add_import_record(url, start_position, ImportKind::Url)?;
Image::Url(Url {
import_record_idx: record_idx,
loc: css::dependencies::Location::from_source_location(loc),
})
} else {
Image::parse(input)?
};
let (resolution, file_type): (Resolution, Option<*const [u8]>) =
if let Ok(res) = input.try_parse(Resolution::parse) {
let file_type = input.try_parse(parse_file_type).ok();
(res, file_type)
} else {
let file_type = input.try_parse(parse_file_type).ok();
let resolution = input
.try_parse(Resolution::parse)
.unwrap_or(Resolution::Dppx(1.0));
(resolution, file_type)
};
Result::Ok(ImageSetOption {
image,
resolution,
file_type,
})
}
pub(crate) fn to_css(
&self,
dest: &mut css::Printer,
is_prefixed: bool,
) -> core::result::Result<(), PrintErr> {
if matches!(self.image, Image::Url(_)) && !is_prefixed {
let Image::Url(url) = &self.image else {
unreachable!()
};
let dep_: Option<UrlDependency> = if dest.dependencies.is_some() {
let import_records = dest.get_import_records()?;
Some(UrlDependency::new(
dest.arena,
url,
dest.filename(),
import_records,
))
} else {
None
};
if let Some(dep) = dep_ {
let placeholder = unsafe { crate::arena_str(dep.placeholder) };
dest.serialize_string(placeholder)?;
if let Some(dependencies) = &mut dest.dependencies {
dependencies.push(css::Dependency::Url(dep));
}
} else {
let record_url = dest.get_import_record_url(url.import_record_idx)?;
let record_url: &[u8] = unsafe { &*std::ptr::from_ref::<[u8]>(record_url) };
dest.serialize_string(record_url)?;
}
} else {
self.image.to_css(dest)?;
}
dest.write_char(b' ')?;
let targets = {
let targets = dest.targets;
dest.targets = css::targets::Targets::default();
targets
};
self.resolution.to_css(dest)?;
dest.targets = targets;
if let Some(file_type) = self.file_type {
dest.write_str(" type(")?;
let file_type_slice = unsafe { crate::arena_str(file_type) };
dest.serialize_string(file_type_slice)?;
dest.write_char(b')')?;
}
Ok(())
}
pub(crate) fn deep_clone(&self, arena: &Arena) -> Self {
ImageSetOption {
image: self.image.deep_clone(arena),
resolution: self.resolution,
file_type: self.file_type,
}
}
pub(crate) fn eql(&self, rhs: &ImageSetOption) -> bool {
self.image.eql(&rhs.image)
&& self.resolution == rhs.resolution
&& match (self.file_type, rhs.file_type) {
(None, None) => true,
(Some(a), Some(b)) => unsafe { crate::arena_str(a) == crate::arena_str(b) },
_ => false,
}
}
}
fn parse_file_type(input: &mut css::Parser) -> Result<*const [u8]> {
input.expect_function_matching(b"type")?;
input.parse_nested_block(|i: &mut css::Parser| {
i.expect_string().map(std::ptr::from_ref::<[u8]>)
})
}