1#![deny(missing_docs)]
10
11use crate::Atom;
12use crate::derives::*;
13use crate::parser::{Parse, ParserContext};
14use crate::typed_om::{KeywordValue, NumericType, NumericValue, ToTyped, TypedValue, UnitValue};
15use crate::values::distance::{ComputeSquaredDistance, SquaredDistance};
16use crate::values::generics::position::IsTreeScoped;
17pub use cssparser::{CowRcStr, Parser, serialize_identifier, serialize_name};
18pub use cssparser::{SourceLocation, Token};
19use num_traits::Zero;
20use precomputed_hash::PrecomputedHash;
21use selectors::parser::SelectorParseErrorKind;
22use std::fmt::{self, Debug, Write};
23use style_traits::{CssString, CssWriter, ParseError, StyleParseErrorKind, ToCss};
24use thin_vec::ThinVec;
25use to_shmem::impl_trivial_to_shmem;
26
27pub use crate::url::CssUrl;
28
29pub mod animated;
30pub mod computed;
31pub mod distance;
32pub mod generics;
33pub mod resolved;
34pub mod specified;
35pub mod tagged_numeric;
36
37pub type CSSFloat = f32;
39
40#[inline]
43pub fn normalize(v: CSSFloat) -> CSSFloat {
44 if v.is_nan() { 0.0 } else { v }
45}
46
47#[inline]
51pub fn calc_min(a: CSSFloat, b: CSSFloat) -> CSSFloat {
52 match (a.is_sign_negative(), b.is_sign_negative()) {
53 (true, false) => a,
54 (false, true) => b,
55 _ => a.min(b),
56 }
57}
58
59#[inline]
63pub fn calc_max(a: CSSFloat, b: CSSFloat) -> CSSFloat {
64 match (a.is_sign_negative(), b.is_sign_negative()) {
65 (true, false) => b,
66 (false, true) => a,
67 _ => a.max(b),
68 }
69}
70
71#[inline]
76pub fn calc_sign(value: CSSFloat) -> CSSFloat {
77 if value.is_nan() {
78 f32::NAN
79 } else if value.is_zero() {
80 value
81 } else if value.is_sign_negative() {
82 -1.0
83 } else {
84 1.0
85 }
86}
87
88pub fn calc_random_integer(base: f32, limit: f32) -> f32 {
91 (base * limit).floor()
92}
93
94pub fn calc_random(base: f32, min: f32, max: f32, step: Option<f32>) -> f32 {
97 if base.is_nan() || min.is_nan() || max.is_nan() || step.is_some_and(f32::is_nan) {
98 return f32::NAN;
99 }
100
101 let base = normalize(base.max(0.).min((1.0f32).next_down()));
107
108 if min.is_infinite() {
110 return min;
111 }
112
113 if !(max - min).is_finite() {
117 return f32::NAN;
118 }
119
120 let max = if max < min { min } else { max };
123 let range = max - min;
124
125 let Some(step) = step else {
126 return min + base * range;
127 };
128
129 if step.is_infinite() {
131 return min;
132 }
133
134 if step <= 0.0 {
138 return min + base * range;
139 }
140
141 let epsilon = match step / 1000.0 {
144 e if e > 0.0 => e,
145 _ => (0.0f32).next_up(),
146 };
147
148 let mut n = (range / step).floor();
151 if n.is_infinite() {
152 return min + base * range;
153 }
154
155 if (min + n * step - max).abs() > epsilon && (min + (n + 1.0) * step - max).abs() <= epsilon {
158 n += 1.0;
159 }
160
161 let step_index = calc_random_integer(base, n + 1.0);
164 let value = min + step_index * step;
165
166 if step_index == n && (value - max).abs() <= epsilon {
168 return max;
169 }
170
171 value
172}
173
174pub type CSSInteger = i32;
176
177#[cfg(feature = "gecko")]
179pub fn serialize_atom_identifier<W>(ident: &Atom, dest: &mut W) -> fmt::Result
180where
181 W: Write,
182{
183 ident.with_str(|s| serialize_identifier(s, dest))
184}
185
186#[cfg(feature = "servo")]
188pub fn serialize_atom_identifier<Static, W>(
189 ident: &::string_cache::Atom<Static>,
190 dest: &mut W,
191) -> fmt::Result
192where
193 Static: string_cache::StaticAtomSet,
194 W: Write,
195{
196 serialize_identifier(&ident, dest)
197}
198
199#[cfg(feature = "gecko")]
201pub fn serialize_atom_name<W>(ident: &Atom, dest: &mut W) -> fmt::Result
202where
203 W: Write,
204{
205 ident.with_str(|s| serialize_name(s, dest))
206}
207
208#[cfg(feature = "servo")]
210pub fn serialize_atom_name<Static, W>(
211 ident: &::string_cache::Atom<Static>,
212 dest: &mut W,
213) -> fmt::Result
214where
215 Static: string_cache::StaticAtomSet,
216 W: Write,
217{
218 serialize_name(&ident, dest)
219}
220
221pub fn serialize_number<W>(v: f32, dest: &mut CssWriter<W>) -> fmt::Result
223where
224 W: Write,
225{
226 serialize_specified_dimension(v, "", false, dest)
227}
228
229pub fn serialize_specified_dimension<W>(
231 v: f32,
232 unit: &str,
233 was_calc: bool,
234 dest: &mut CssWriter<W>,
235) -> fmt::Result
236where
237 W: Write,
238{
239 if was_calc {
240 dest.write_str("calc(")?;
241 }
242
243 if !v.is_finite() {
244 if v.is_nan() {
250 dest.write_str("NaN")?;
251 } else if v == f32::INFINITY {
252 dest.write_str("infinity")?;
253 } else if v == f32::NEG_INFINITY {
254 dest.write_str("-infinity")?;
255 }
256
257 if !unit.is_empty() {
258 dest.write_str(" * 1")?;
259 }
260 } else {
261 v.to_css(dest)?;
262 }
263
264 dest.write_str(unit)?;
265
266 if was_calc {
267 dest.write_char(')')?;
268 }
269 Ok(())
270}
271
272#[repr(transparent)]
274#[derive(
275 Clone,
276 Debug,
277 Default,
278 Deref,
279 Eq,
280 Hash,
281 MallocSizeOf,
282 PartialEq,
283 SpecifiedValueInfo,
284 ToAnimatedValue,
285 ToComputedValue,
286 ToResolvedValue,
287 ToShmem,
288)]
289pub struct AtomString(pub Atom);
290
291#[cfg(feature = "servo")]
292impl AsRef<str> for AtomString {
293 fn as_ref(&self) -> &str {
294 &*self.0
295 }
296}
297
298impl Parse for AtomString {
299 fn parse(_: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
300 Ok(Self(Atom::from(input.expect_string()?.as_ref())))
301 }
302}
303
304impl cssparser::ToCss for AtomString {
305 fn to_css<W>(&self, dest: &mut W) -> fmt::Result
306 where
307 W: Write,
308 {
309 dest.write_char('"')?;
311 #[cfg(feature = "servo")]
312 {
313 cssparser::CssStringWriter::new(dest).write_str(self.as_ref())?;
314 }
315 #[cfg(feature = "gecko")]
316 {
317 self.0
318 .with_str(|s| cssparser::CssStringWriter::new(dest).write_str(s))?;
319 }
320 dest.write_char('"')
321 }
322}
323
324impl style_traits::ToCss for AtomString {
325 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
326 where
327 W: Write,
328 {
329 cssparser::ToCss::to_css(self, dest)
330 }
331}
332
333impl PrecomputedHash for AtomString {
334 #[inline]
335 fn precomputed_hash(&self) -> u32 {
336 self.0.precomputed_hash()
337 }
338}
339
340impl From<&str> for AtomString {
341 #[inline]
342 fn from(string: &str) -> Self {
343 Self(Atom::from(string))
344 }
345}
346
347#[cfg(feature = "servo")]
349#[repr(transparent)]
350#[derive(Deref)]
351pub struct GenericAtomIdent<Set>(pub string_cache::Atom<Set>)
352where
353 Set: string_cache::StaticAtomSet;
354
355#[cfg(feature = "servo")]
357pub type AtomIdent = GenericAtomIdent<stylo_atoms::AtomStaticSet>;
358
359#[cfg(feature = "servo")]
360impl<Set: string_cache::StaticAtomSet> style_traits::SpecifiedValueInfo for GenericAtomIdent<Set> {}
361
362#[cfg(feature = "servo")]
363impl<Set: string_cache::StaticAtomSet> Default for GenericAtomIdent<Set> {
364 fn default() -> Self {
365 Self(string_cache::Atom::default())
366 }
367}
368
369#[cfg(feature = "servo")]
370impl<Set: string_cache::StaticAtomSet> std::fmt::Debug for GenericAtomIdent<Set> {
371 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
372 self.0.fmt(f)
373 }
374}
375
376#[cfg(feature = "servo")]
377impl<Set: string_cache::StaticAtomSet> std::hash::Hash for GenericAtomIdent<Set> {
378 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
379 self.0.hash(state)
380 }
381}
382
383#[cfg(feature = "servo")]
384impl<Set: string_cache::StaticAtomSet> Eq for GenericAtomIdent<Set> {}
385
386#[cfg(feature = "servo")]
387impl<Set: string_cache::StaticAtomSet> PartialEq for GenericAtomIdent<Set> {
388 fn eq(&self, other: &Self) -> bool {
389 self.0 == other.0
390 }
391}
392
393#[cfg(feature = "servo")]
394impl<Set: string_cache::StaticAtomSet> Clone for GenericAtomIdent<Set> {
395 fn clone(&self) -> Self {
396 Self(self.0.clone())
397 }
398}
399
400#[cfg(feature = "servo")]
401impl<Set: string_cache::StaticAtomSet> to_shmem::ToShmem for GenericAtomIdent<Set> {
402 fn to_shmem(&self, builder: &mut to_shmem::SharedMemoryBuilder) -> to_shmem::Result<Self> {
403 use std::mem::ManuallyDrop;
404
405 let atom = self.0.to_shmem(builder)?;
406 Ok(ManuallyDrop::new(Self(ManuallyDrop::into_inner(atom))))
407 }
408}
409
410#[cfg(feature = "servo")]
411impl<Set: string_cache::StaticAtomSet> malloc_size_of::MallocSizeOf for GenericAtomIdent<Set> {
412 fn size_of(&self, ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
413 self.0.size_of(ops)
414 }
415}
416
417#[cfg(feature = "servo")]
418impl<Set: string_cache::StaticAtomSet> cssparser::ToCss for GenericAtomIdent<Set> {
419 fn to_css<W>(&self, dest: &mut W) -> fmt::Result
420 where
421 W: Write,
422 {
423 serialize_atom_identifier(&self.0, dest)
424 }
425}
426
427#[cfg(feature = "servo")]
428impl<Set: string_cache::StaticAtomSet> style_traits::ToCss for GenericAtomIdent<Set> {
429 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
430 where
431 W: Write,
432 {
433 serialize_atom_identifier(&self.0, dest)
434 }
435}
436
437#[cfg(feature = "servo")]
438impl<Set: string_cache::StaticAtomSet> PrecomputedHash for GenericAtomIdent<Set> {
439 #[inline]
440 fn precomputed_hash(&self) -> u32 {
441 self.0.precomputed_hash()
442 }
443}
444
445#[cfg(feature = "servo")]
446impl<'a, Set: string_cache::StaticAtomSet> From<&'a str> for GenericAtomIdent<Set> {
447 #[inline]
448 fn from(string: &str) -> Self {
449 Self(string_cache::Atom::from(string))
450 }
451}
452
453#[cfg(feature = "servo")]
454impl<Set: string_cache::StaticAtomSet> std::borrow::Borrow<string_cache::Atom<Set>>
455 for GenericAtomIdent<Set>
456{
457 #[inline]
458 fn borrow(&self) -> &string_cache::Atom<Set> {
459 &self.0
460 }
461}
462
463#[cfg(feature = "servo")]
464impl<Set: string_cache::StaticAtomSet> GenericAtomIdent<Set> {
465 #[inline]
467 pub fn new(atom: string_cache::Atom<Set>) -> Self {
468 Self(atom)
469 }
470
471 #[inline]
473 pub fn cast<'a>(atom: &'a string_cache::Atom<Set>) -> &'a Self {
474 let ptr = atom as *const _ as *const Self;
475 unsafe { &*ptr }
477 }
478}
479
480#[cfg(feature = "gecko")]
482#[repr(transparent)]
483#[derive(
484 Clone, Debug, Default, Deref, Eq, Hash, MallocSizeOf, PartialEq, SpecifiedValueInfo, ToShmem,
485)]
486pub struct AtomIdent(pub Atom);
487
488#[cfg(feature = "gecko")]
489impl cssparser::ToCss for AtomIdent {
490 fn to_css<W>(&self, dest: &mut W) -> fmt::Result
491 where
492 W: Write,
493 {
494 serialize_atom_identifier(&self.0, dest)
495 }
496}
497
498#[cfg(feature = "gecko")]
499impl style_traits::ToCss for AtomIdent {
500 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
501 where
502 W: Write,
503 {
504 cssparser::ToCss::to_css(self, dest)
505 }
506}
507
508#[cfg(feature = "gecko")]
509impl PrecomputedHash for AtomIdent {
510 #[inline]
511 fn precomputed_hash(&self) -> u32 {
512 self.0.precomputed_hash()
513 }
514}
515
516#[cfg(feature = "gecko")]
517impl From<&str> for AtomIdent {
518 #[inline]
519 fn from(string: &str) -> Self {
520 Self(Atom::from(string))
521 }
522}
523
524#[cfg(feature = "gecko")]
525impl AtomIdent {
526 #[inline]
528 pub fn new(atom: Atom) -> Self {
529 Self(atom)
530 }
531
532 pub unsafe fn with<F, R>(ptr: *const crate::gecko_bindings::structs::nsAtom, callback: F) -> R
534 where
535 F: FnOnce(&Self) -> R,
536 {
537 unsafe {
538 Atom::with(ptr, |atom: &Atom| {
539 let atom = atom as *const Atom as *const AtomIdent;
541 callback(&*atom)
542 })
543 }
544 }
545
546 #[inline]
548 pub fn cast(atom: &Atom) -> &Self {
549 let ptr = atom as *const _ as *const Self;
550 unsafe { &*ptr }
552 }
553}
554
555#[cfg(feature = "gecko")]
556impl std::borrow::Borrow<crate::gecko_string_cache::WeakAtom> for AtomIdent {
557 #[inline]
558 fn borrow(&self) -> &crate::gecko_string_cache::WeakAtom {
559 self.0.borrow()
560 }
561}
562
563pub fn serialize_percentage<W>(value: CSSFloat, dest: &mut CssWriter<W>) -> fmt::Result
565where
566 W: Write,
567{
568 serialize_specified_dimension(value * 100., "%", false, dest)
569}
570
571pub fn serialize_normalized_percentage<W>(value: CSSFloat, dest: &mut CssWriter<W>) -> fmt::Result
573where
574 W: Write,
575{
576 (value * 100.).to_css(dest)?;
577 dest.write_char('%')
578}
579
580pub fn reify_percentage(value: CSSFloat, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
582 let numeric_value = NumericValue::Unit(UnitValue {
583 numeric_type: NumericType::percent(),
584 value: value * 100.,
585 unit: CssString::from("percent"),
586 });
587
588 dest.push(TypedValue::Numeric(numeric_value));
589 Ok(())
590}
591
592#[derive(
594 Clone,
595 Copy,
596 Debug,
597 Deserialize,
598 MallocSizeOf,
599 PartialEq,
600 Serialize,
601 SpecifiedValueInfo,
602 ToAnimatedValue,
603 ToComputedValue,
604 ToCss,
605 ToResolvedValue,
606)]
607pub enum Impossible {}
608
609impl ComputeSquaredDistance for Impossible {
612 #[inline]
613 fn compute_squared_distance(&self, _other: &Self) -> Result<SquaredDistance, ()> {
614 match *self {}
615 }
616}
617
618impl_trivial_to_shmem!(Impossible);
619
620impl Parse for Impossible {
621 fn parse(_context: &ParserContext, _input: &mut Parser) -> Result<Self, ParseError> {
622 Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError))
623 }
624}
625
626#[derive(
628 Animate,
629 Clone,
630 ComputeSquaredDistance,
631 Copy,
632 MallocSizeOf,
633 PartialEq,
634 Parse,
635 SpecifiedValueInfo,
636 ToAnimatedValue,
637 ToAnimatedZero,
638 ToComputedValue,
639 ToCss,
640 ToResolvedValue,
641 ToShmem,
642)]
643pub enum Either<A, B> {
644 First(A),
646 Second(B),
648}
649
650impl<A: Debug, B: Debug> Debug for Either<A, B> {
651 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
652 match *self {
653 Either::First(ref v) => v.fmt(f),
654 Either::Second(ref v) => v.fmt(f),
655 }
656 }
657}
658
659#[derive(
661 Clone,
662 Debug,
663 Default,
664 Deserialize,
665 Eq,
666 Hash,
667 MallocSizeOf,
668 PartialEq,
669 Serialize,
670 SpecifiedValueInfo,
671 ToAnimatedValue,
672 ToComputedValue,
673 ToResolvedValue,
674 ToShmem,
675)]
676#[repr(C)]
677pub struct CustomIdent(pub Atom);
678
679impl CustomIdent {
680 pub fn parse(input: &mut Parser, invalid: &[&str]) -> Result<Self, ParseError> {
685 let ident = input.expect_ident()?;
686 CustomIdent::from_ident(ident, invalid)
687 }
688
689 pub fn from_ident<'i>(ident: &CowRcStr<'i>, excluding: &[&str]) -> Result<Self, ParseError> {
691 if !Self::is_valid(ident, excluding) {
692 return Err(ParseError::custom(SelectorParseErrorKind::UnexpectedIdent));
693 }
694 if excluding.iter().any(|s| ident.eq_ignore_ascii_case(s)) {
695 Err(ParseError::custom(StyleParseErrorKind::UnspecifiedError))
696 } else {
697 Ok(CustomIdent(Atom::from(ident.as_ref())))
698 }
699 }
700
701 fn is_valid(ident: &str, excluding: &[&str]) -> bool {
702 use crate::properties::CSSWideKeyword;
703 if CSSWideKeyword::from_ident(ident).is_ok() || ident.eq_ignore_ascii_case("default") {
708 return false;
709 }
710
711 !excluding.iter().any(|s| ident.eq_ignore_ascii_case(s))
715 }
716}
717
718impl ToCss for CustomIdent {
719 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
720 where
721 W: Write,
722 {
723 serialize_atom_identifier(&self.0, dest)
724 }
725}
726
727impl ToTyped for CustomIdent {
728 fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
729 let s = ToCss::to_css_cssstring(self);
731 dest.push(TypedValue::Keyword(KeywordValue(s)));
732 Ok(())
733 }
734}
735
736#[repr(transparent)]
739#[derive(
740 Clone,
741 Debug,
742 Eq,
743 Hash,
744 MallocSizeOf,
745 PartialEq,
746 SpecifiedValueInfo,
747 ToAnimatedValue,
748 ToComputedValue,
749 ToResolvedValue,
750 ToShmem,
751 Serialize,
752 Deserialize,
753)]
754pub struct DashedIdent(pub Atom);
755
756impl DashedIdent {
757 pub fn from_ident<'i>(ident: &CowRcStr<'i>) -> Result<Self, ParseError> {
759 if !ident.starts_with("--") {
760 return Err(ParseError::custom(SelectorParseErrorKind::UnexpectedIdent));
761 }
762 Ok(Self(Atom::from(ident.as_ref())))
763 }
764
765 pub fn empty() -> Self {
767 Self(atom!(""))
768 }
769
770 pub fn is_empty(&self) -> bool {
772 self.0 == atom!("")
773 }
774
775 pub(crate) fn undashed(&self) -> Atom {
781 assert!(!self.is_empty(), "Can't undash the empty DashedIdent");
782 #[cfg(feature = "gecko")]
783 let name = &self.0.as_slice()[2..];
784 #[cfg(feature = "servo")]
785 let name = &self.0[2..];
786 Atom::from(name)
787 }
788}
789
790impl IsTreeScoped for DashedIdent {
791 fn is_tree_scoped(&self) -> bool {
792 !self.is_empty()
793 }
794}
795
796impl Parse for DashedIdent {
797 fn parse(_: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
798 let ident = input.expect_ident()?;
799 Self::from_ident(ident)
800 }
801}
802
803impl ToCss for DashedIdent {
804 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
805 where
806 W: Write,
807 {
808 serialize_atom_identifier(&self.0, dest)
809 }
810}
811
812#[repr(transparent)]
818#[derive(
819 Clone,
820 Debug,
821 Eq,
822 Hash,
823 PartialEq,
824 MallocSizeOf,
825 SpecifiedValueInfo,
826 ToComputedValue,
827 ToResolvedValue,
828 ToShmem,
829)]
830pub struct KeyframesName(Atom);
831
832impl KeyframesName {
833 pub fn from_ident(value: &str) -> Self {
835 Self(Atom::from(value))
836 }
837
838 pub fn none() -> Self {
840 Self(atom!(""))
841 }
842
843 pub fn is_none(&self) -> bool {
845 self.0 == atom!("")
846 }
847
848 #[cfg(feature = "gecko")]
850 pub fn from_atom(atom: Atom) -> Self {
851 Self(atom)
852 }
853
854 pub fn as_atom(&self) -> &Atom {
856 &self.0
857 }
858}
859
860impl Parse for KeyframesName {
861 fn parse(_: &ParserContext, input: &mut Parser) -> Result<Self, ParseError> {
862 Ok(match *input.next()? {
863 Token::Ident(ref s) => Self(CustomIdent::from_ident(s, &["none"])?.0),
864 Token::QuotedString(ref s) if !s.as_ref().is_empty() => Self(Atom::from(s.as_ref())),
866 _ => return Err(ParseError::unexpected_token()),
867 })
868 }
869}
870
871impl ToCss for KeyframesName {
872 fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result
873 where
874 W: Write,
875 {
876 if self.is_none() {
877 return dest.write_str("none");
878 }
879
880 fn serialize<W: Write>(string: &str, dest: &mut CssWriter<W>) -> fmt::Result {
881 if CustomIdent::is_valid(string, &["none"]) {
882 serialize_identifier(string, dest)
883 } else {
884 string.to_css(dest)
885 }
886 }
887
888 #[cfg(feature = "gecko")]
889 return self.0.with_str(|s| serialize(s, dest));
890
891 #[cfg(feature = "servo")]
892 return serialize(self.0.as_ref(), dest);
893 }
894}
895
896impl ToTyped for KeyframesName {
897 fn to_typed(&self, dest: &mut ThinVec<TypedValue>) -> Result<(), ()> {
898 let s = ToCss::to_css_cssstring(self);
899 dest.push(TypedValue::Keyword(KeywordValue(s)));
900 Ok(())
901 }
902}