1use crate::context::PropertyHandlerContext;
4use crate::declaration::{DeclarationBlock, DeclarationList};
5use crate::error::{ParserError, PrinterError};
6use crate::prefixes::Feature;
7use crate::printer::Printer;
8use crate::properties::{Property, PropertyId, VendorPrefix};
9use crate::targets::{Browsers, Targets};
10use crate::traits::{IsCompatible, Parse, PropertyHandler, Shorthand, ToCss};
11use crate::values::image::Image;
12use crate::values::number::CSSNumber;
13use crate::values::rect::Rect;
14#[cfg(feature = "visitor")]
15use crate::visitor::Visit;
16use crate::{compat, macros::*};
17use crate::{
18 traits::FallbackValues,
19 values::{
20 length::*,
21 percentage::{NumberOrPercentage, Percentage},
22 },
23};
24use cssparser::*;
25
26enum_property! {
27 pub enum BorderImageRepeatKeyword {
29 Stretch,
31 Repeat,
33 Round,
35 Space,
37 }
38}
39
40impl IsCompatible for BorderImageRepeatKeyword {
41 fn is_compatible(&self, browsers: Browsers) -> bool {
42 use BorderImageRepeatKeyword::*;
43 match self {
44 Round => compat::Feature::BorderImageRepeatRound.is_compatible(browsers),
45 Space => compat::Feature::BorderImageRepeatSpace.is_compatible(browsers),
46 Stretch | Repeat => true,
47 }
48 }
49}
50
51#[derive(Debug, Clone, PartialEq)]
53#[cfg_attr(feature = "visitor", derive(Visit))]
54#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
55#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
56#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
57pub struct BorderImageRepeat {
58 pub horizontal: BorderImageRepeatKeyword,
60 pub vertical: BorderImageRepeatKeyword,
62}
63
64impl Default for BorderImageRepeat {
65 fn default() -> BorderImageRepeat {
66 BorderImageRepeat {
67 horizontal: BorderImageRepeatKeyword::Stretch,
68 vertical: BorderImageRepeatKeyword::Stretch,
69 }
70 }
71}
72
73impl<'i> Parse<'i> for BorderImageRepeat {
74 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
75 let horizontal = BorderImageRepeatKeyword::parse(input)?;
76 let vertical = input.try_parse(BorderImageRepeatKeyword::parse).ok();
77 Ok(BorderImageRepeat {
78 horizontal,
79 vertical: vertical.unwrap_or(horizontal),
80 })
81 }
82}
83
84impl ToCss for BorderImageRepeat {
85 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
86 where
87 W: std::fmt::Write,
88 {
89 self.horizontal.to_css(dest)?;
90 if self.horizontal != self.vertical {
91 dest.write_str(" ")?;
92 self.vertical.to_css(dest)?;
93 }
94 Ok(())
95 }
96}
97
98impl IsCompatible for BorderImageRepeat {
99 fn is_compatible(&self, browsers: Browsers) -> bool {
100 self.horizontal.is_compatible(browsers) && self.vertical.is_compatible(browsers)
101 }
102}
103
104#[derive(Debug, Clone, PartialEq, Parse, ToCss)]
106#[cfg_attr(feature = "visitor", derive(Visit))]
107#[cfg_attr(
108 feature = "serde",
109 derive(serde::Serialize, serde::Deserialize),
110 serde(tag = "type", content = "value", rename_all = "kebab-case")
111)]
112#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
113#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
114pub enum BorderImageSideWidth {
115 Number(CSSNumber),
117 LengthPercentage(LengthPercentage),
119 Auto,
121}
122
123impl Default for BorderImageSideWidth {
124 fn default() -> BorderImageSideWidth {
125 BorderImageSideWidth::Number(1.0)
126 }
127}
128
129impl IsCompatible for BorderImageSideWidth {
130 fn is_compatible(&self, browsers: Browsers) -> bool {
131 match self {
132 BorderImageSideWidth::LengthPercentage(l) => l.is_compatible(browsers),
133 _ => true,
134 }
135 }
136}
137
138#[derive(Debug, Clone, PartialEq)]
140#[cfg_attr(feature = "visitor", derive(Visit))]
141#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
142#[cfg_attr(feature = "jsonschema", derive(schemars::JsonSchema))]
143#[cfg_attr(feature = "into_owned", derive(static_self::IntoOwned))]
144pub struct BorderImageSlice {
145 pub offsets: Rect<NumberOrPercentage>,
147 pub fill: bool,
149}
150
151impl Default for BorderImageSlice {
152 fn default() -> BorderImageSlice {
153 BorderImageSlice {
154 offsets: Rect::all(NumberOrPercentage::Percentage(Percentage(1.0))),
155 fill: false,
156 }
157 }
158}
159
160impl<'i> Parse<'i> for BorderImageSlice {
161 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
162 let mut fill = input.try_parse(|i| i.expect_ident_matching("fill")).is_ok();
163 let offsets = Rect::parse(input)?;
164 if !fill {
165 fill = input.try_parse(|i| i.expect_ident_matching("fill")).is_ok();
166 }
167 Ok(BorderImageSlice { offsets, fill })
168 }
169}
170
171impl ToCss for BorderImageSlice {
172 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
173 where
174 W: std::fmt::Write,
175 {
176 self.offsets.to_css(dest)?;
177 if self.fill {
178 dest.write_str(" fill")?;
179 }
180 Ok(())
181 }
182}
183
184impl IsCompatible for BorderImageSlice {
185 fn is_compatible(&self, _browsers: Browsers) -> bool {
186 true
187 }
188}
189
190define_shorthand! {
191 #[derive(Default)]
193 pub struct BorderImage<'i>(VendorPrefix) {
194 #[cfg_attr(feature = "serde", serde(borrow))]
196 source: BorderImageSource(Image<'i>),
197 slice: BorderImageSlice(BorderImageSlice),
199 width: BorderImageWidth(Rect<BorderImageSideWidth>),
201 outset: BorderImageOutset(Rect<LengthOrNumber>),
203 repeat: BorderImageRepeat(BorderImageRepeat),
205 }
206}
207
208impl<'i> Parse<'i> for BorderImage<'i> {
209 fn parse<'t>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i, ParserError<'i>>> {
210 BorderImage::parse_with_callback(input, |_| false)
211 }
212}
213
214impl<'i> BorderImage<'i> {
215 pub(crate) fn parse_with_callback<'t, F>(
216 input: &mut Parser<'i, 't>,
217 mut callback: F,
218 ) -> Result<Self, ParseError<'i, ParserError<'i>>>
219 where
220 F: FnMut(&mut Parser<'i, 't>) -> bool,
221 {
222 let mut source: Option<Image> = None;
223 let mut slice: Option<BorderImageSlice> = None;
224 let mut width: Option<Rect<BorderImageSideWidth>> = None;
225 let mut outset: Option<Rect<LengthOrNumber>> = None;
226 let mut repeat: Option<BorderImageRepeat> = None;
227 loop {
228 if slice.is_none() {
229 if let Ok(value) = input.try_parse(|input| BorderImageSlice::parse(input)) {
230 slice = Some(value);
231 let maybe_width_outset: Result<_, cssparser::ParseError<'_, ParserError<'i>>> =
233 input.try_parse(|input| {
234 input.expect_delim('/')?;
235
236 let w = input.try_parse(|input| Rect::parse(input)).ok();
238
239 let o = input
241 .try_parse(|input| {
242 input.expect_delim('/')?;
243 Rect::parse(input)
244 })
245 .ok();
246 if w.is_none() && o.is_none() {
247 Err(input.new_custom_error(ParserError::InvalidDeclaration))
248 } else {
249 Ok((w, o))
250 }
251 });
252 if let Ok((w, o)) = maybe_width_outset {
253 width = w;
254 outset = o;
255 }
256 continue;
257 }
258 }
259
260 if source.is_none() {
261 if let Ok(value) = input.try_parse(|input| Image::parse(input)) {
262 source = Some(value);
263 continue;
264 }
265 }
266
267 if repeat.is_none() {
268 if let Ok(value) = input.try_parse(|input| BorderImageRepeat::parse(input)) {
269 repeat = Some(value);
270 continue;
271 }
272 }
273
274 if callback(input) {
275 continue;
276 }
277
278 break;
279 }
280
281 if source.is_some() || slice.is_some() || width.is_some() || outset.is_some() || repeat.is_some() {
282 Ok(BorderImage {
283 source: source.unwrap_or_default(),
284 slice: slice.unwrap_or_default(),
285 width: width.unwrap_or(Rect::all(BorderImageSideWidth::default())),
286 outset: outset.unwrap_or(Rect::all(LengthOrNumber::default())),
287 repeat: repeat.unwrap_or_default(),
288 })
289 } else {
290 Err(input.new_custom_error(ParserError::InvalidDeclaration))
291 }
292 }
293
294 pub(crate) fn to_css_internal<W>(
295 source: &Image<'i>,
296 slice: &BorderImageSlice,
297 width: &Rect<BorderImageSideWidth>,
298 outset: &Rect<LengthOrNumber>,
299 repeat: &BorderImageRepeat,
300 dest: &mut Printer<W>,
301 ) -> Result<(), PrinterError>
302 where
303 W: std::fmt::Write,
304 {
305 if *source != Image::default() {
306 source.to_css(dest)?;
307 }
308 let has_slice = *slice != BorderImageSlice::default();
309 let has_width = *width != Rect::all(BorderImageSideWidth::default());
310 let has_outset = *outset != Rect::all(LengthOrNumber::Number(0.0));
311 if has_slice || has_width || has_outset {
312 dest.write_str(" ")?;
313 slice.to_css(dest)?;
314 if has_width || has_outset {
315 dest.delim('/', true)?;
316 }
317 if has_width {
318 width.to_css(dest)?;
319 }
320
321 if has_outset {
322 dest.delim('/', true)?;
323 outset.to_css(dest)?;
324 }
325 }
326
327 if *repeat != BorderImageRepeat::default() {
328 dest.write_str(" ")?;
329 repeat.to_css(dest)?;
330 }
331
332 Ok(())
333 }
334}
335
336impl<'i> ToCss for BorderImage<'i> {
337 fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
338 where
339 W: std::fmt::Write,
340 {
341 BorderImage::to_css_internal(&self.source, &self.slice, &self.width, &self.outset, &self.repeat, dest)
342 }
343}
344
345impl<'i> FallbackValues for BorderImage<'i> {
346 fn get_fallbacks(&mut self, targets: Targets) -> Vec<Self> {
347 self
348 .source
349 .get_fallbacks(targets)
350 .into_iter()
351 .map(|source| BorderImage { source, ..self.clone() })
352 .collect()
353 }
354}
355
356property_bitflags! {
357 #[derive(Default, Debug)]
358 struct BorderImageProperty: u16 {
359 const BorderImageSource = 1 << 0;
360 const BorderImageSlice = 1 << 1;
361 const BorderImageWidth = 1 << 2;
362 const BorderImageOutset = 1 << 3;
363 const BorderImageRepeat = 1 << 4;
364 const BorderImage(_vp) = Self::BorderImageSource.bits() | Self::BorderImageSlice.bits() | Self::BorderImageWidth.bits() | Self::BorderImageOutset.bits() | Self::BorderImageRepeat.bits();
365 }
366}
367
368#[derive(Debug)]
369pub(crate) struct BorderImageHandler<'i> {
370 source: Option<Image<'i>>,
371 slice: Option<BorderImageSlice>,
372 width: Option<Rect<BorderImageSideWidth>>,
373 outset: Option<Rect<LengthOrNumber>>,
374 repeat: Option<BorderImageRepeat>,
375 vendor_prefix: VendorPrefix,
376 flushed_properties: BorderImageProperty,
377 has_any: bool,
378}
379
380impl<'i> Default for BorderImageHandler<'i> {
381 fn default() -> Self {
382 BorderImageHandler {
383 vendor_prefix: VendorPrefix::empty(),
384 source: None,
385 slice: None,
386 width: None,
387 outset: None,
388 repeat: None,
389 flushed_properties: BorderImageProperty::empty(),
390 has_any: false,
391 }
392 }
393}
394
395impl<'i> PropertyHandler<'i> for BorderImageHandler<'i> {
396 fn handle_property(
397 &mut self,
398 property: &Property<'i>,
399 dest: &mut DeclarationList<'i>,
400 context: &mut PropertyHandlerContext<'i, '_>,
401 ) -> bool {
402 use Property::*;
403 macro_rules! property {
404 ($name: ident, $val: ident) => {{
405 if self.vendor_prefix != VendorPrefix::None {
406 self.flush(dest, context);
407 }
408 flush!($name, $val);
409 self.vendor_prefix = VendorPrefix::None;
410 self.$name = Some($val.clone());
411 self.has_any = true;
412 }};
413 }
414
415 macro_rules! flush {
416 ($name: ident, $val: expr) => {{
417 if self.$name.is_some() && self.$name.as_ref().unwrap() != $val && matches!(context.targets.browsers, Some(targets) if !$val.is_compatible(targets)) {
418 self.flush(dest, context);
419 }
420 }};
421 }
422
423 match property {
424 BorderImageSource(val) => property!(source, val),
425 BorderImageSlice(val) => property!(slice, val),
426 BorderImageWidth(val) => property!(width, val),
427 BorderImageOutset(val) => property!(outset, val),
428 BorderImageRepeat(val) => property!(repeat, val),
429 BorderImage(val, vp) => {
430 flush!(source, &val.source);
431 flush!(slice, &val.slice);
432 flush!(width, &val.width);
433 flush!(outset, &val.outset);
434 flush!(repeat, &val.repeat);
435 self.source = Some(val.source.clone());
436 self.slice = Some(val.slice.clone());
437 self.width = Some(val.width.clone());
438 self.outset = Some(val.outset.clone());
439 self.repeat = Some(val.repeat.clone());
440 self.vendor_prefix |= *vp;
441 self.has_any = true;
442 }
443 Unparsed(val) if is_border_image_property(&val.property_id) => {
444 self.flush(dest, context);
445
446 let mut unparsed = if matches!(val.property_id, PropertyId::BorderImage(_)) {
449 val.get_prefixed(context.targets, Feature::BorderImage)
450 } else {
451 val.clone()
452 };
453
454 context.add_unparsed_fallbacks(&mut unparsed);
455 self
456 .flushed_properties
457 .insert(BorderImageProperty::try_from(&unparsed.property_id).unwrap());
458 dest.push(Property::Unparsed(unparsed));
459 }
460 _ => return false,
461 }
462
463 true
464 }
465
466 fn finalize(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {
467 self.flush(dest, context);
468 self.flushed_properties = BorderImageProperty::empty();
469 }
470}
471
472impl<'i> BorderImageHandler<'i> {
473 pub fn reset(&mut self) {
474 self.source = None;
475 self.slice = None;
476 self.width = None;
477 self.outset = None;
478 self.repeat = None;
479 }
480
481 pub fn will_flush(&self, property: &Property<'i>) -> bool {
482 use Property::*;
483 match property {
484 BorderImageSource(_) | BorderImageSlice(_) | BorderImageWidth(_) | BorderImageOutset(_)
485 | BorderImageRepeat(_) => self.vendor_prefix != VendorPrefix::None,
486 Unparsed(val) => is_border_image_property(&val.property_id),
487 _ => false,
488 }
489 }
490
491 fn flush(&mut self, dest: &mut DeclarationList<'i>, context: &mut PropertyHandlerContext<'i, '_>) {
492 if !self.has_any {
493 return;
494 }
495
496 self.has_any = false;
497
498 macro_rules! push {
499 ($prop: ident, $val: expr) => {
500 dest.push(Property::$prop($val));
501 self.flushed_properties.insert(BorderImageProperty::$prop);
502 };
503 }
504
505 let source = std::mem::take(&mut self.source);
506 let slice = std::mem::take(&mut self.slice);
507 let width = std::mem::take(&mut self.width);
508 let outset = std::mem::take(&mut self.outset);
509 let repeat = std::mem::take(&mut self.repeat);
510
511 if source.is_some() && slice.is_some() && width.is_some() && outset.is_some() && repeat.is_some() {
512 let mut border_image = BorderImage {
513 source: source.unwrap(),
514 slice: slice.unwrap(),
515 width: width.unwrap(),
516 outset: outset.unwrap(),
517 repeat: repeat.unwrap(),
518 };
519
520 let mut prefix = self.vendor_prefix;
521 if prefix.contains(VendorPrefix::None) && !border_image.slice.fill {
522 prefix = context.targets.prefixes(self.vendor_prefix, Feature::BorderImage);
523 if !self.flushed_properties.intersects(BorderImageProperty::BorderImage) {
524 let fallbacks = border_image.get_fallbacks(context.targets);
525 for fallback in fallbacks {
526 let mut p = fallback.source.get_vendor_prefix() & prefix;
530 if p.is_empty() {
531 p = prefix;
532 }
533 dest.push(Property::BorderImage(fallback, p));
534 }
535 }
536 }
537
538 let p = border_image.source.get_vendor_prefix() & prefix;
539 if !p.is_empty() {
540 prefix = p;
541 }
542
543 dest.push(Property::BorderImage(border_image, prefix));
544 self.flushed_properties.insert(BorderImageProperty::BorderImage);
545 } else {
546 if let Some(mut source) = source {
547 if !self.flushed_properties.contains(BorderImageProperty::BorderImageSource) {
548 let fallbacks = source.get_fallbacks(context.targets);
549 for fallback in fallbacks {
550 dest.push(Property::BorderImageSource(fallback));
551 }
552 }
553
554 push!(BorderImageSource, source);
555 }
556
557 if let Some(slice) = slice {
558 push!(BorderImageSlice, slice);
559 }
560
561 if let Some(width) = width {
562 push!(BorderImageWidth, width);
563 }
564
565 if let Some(outset) = outset {
566 push!(BorderImageOutset, outset);
567 }
568
569 if let Some(repeat) = repeat {
570 push!(BorderImageRepeat, repeat);
571 }
572 }
573
574 self.vendor_prefix = VendorPrefix::empty();
575 }
576}
577
578#[inline]
579fn is_border_image_property(property_id: &PropertyId) -> bool {
580 match property_id {
581 PropertyId::BorderImageSource
582 | PropertyId::BorderImageSlice
583 | PropertyId::BorderImageWidth
584 | PropertyId::BorderImageOutset
585 | PropertyId::BorderImageRepeat
586 | PropertyId::BorderImage(_) => true,
587 _ => false,
588 }
589}