css_ast/traits/
declaration_metadata.rs

1use bitmask_enum::bitmask;
2
3use crate::{CssAtomSet, UnitlessZeroResolves};
4
5/// The CSS specification/module that a property belongs to.
6#[bitmask(u128)]
7#[bitmask_config(vec_debug)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9pub enum PropertyGroup {
10	Align,
11	AnchorPosition,
12	Animations,
13	Backgrounds,
14	Borders,
15	Box,
16	Break,
17	Cascade,
18	Color,
19	ColorAdjust,
20	ColorHdr,
21	Compositing,
22	Conditional,
23	Contain,
24	Content,
25	CounterStyle,
26	Display,
27	Exclusions,
28	FillStroke,
29	FilterEffects,
30	Flexbox,
31	Fonts,
32	Forms,
33	Gaps,
34	Gcpm,
35	Grid,
36	Images,
37	ImageAnimation,
38	Inline,
39	LineGrid,
40	LinkParams,
41	Lists,
42	Logical,
43	Masking,
44	Motion,
45	Multicol,
46	Nav,
47	Overflow,
48	Overscroll,
49	Page,
50	PageFloats,
51	PointerAnimations,
52	Position,
53	Regions,
54	Rhythm,
55	RoundDisplay,
56	Ruby,
57	ScrollAnchoring,
58	ScrollAnimations,
59	ScrollSnap,
60	Scrollbars,
61	Shaders,
62	Shapes,
63	SizeAdjust,
64	Sizing,
65	Speech,
66	Tables,
67	Text,
68	TextDecor,
69	Transforms,
70	Transitions,
71	Ui,
72	Values,
73	Variables,
74	ViewTransitions,
75	Viewport,
76	WillChange,
77	WritingModes,
78}
79
80pub enum Inherits {
81	False,
82	True,
83	Unknown,
84}
85
86impl Inherits {
87	pub fn to_bool(self, unknown: bool) -> bool {
88		match self {
89			Self::False => false,
90			Self::True => true,
91			Self::Unknown => unknown,
92		}
93	}
94}
95
96pub enum Percentages {
97	/// This style value has no way of expressing values as a percentage.
98	None,
99	/// Any percentage expressed in this value pertains to the size of the containing block.
100	ContainingBlock,
101	/// Any percentage expressed in this value pertains to the size of the border box.
102	BorderBox,
103	/// Any percentage expressed in this value is a syntax affordance; a Number token would be the equivalent value.
104	Number,
105	/// Relative to the 1em Font-Size
106	FontSize,
107	/// Relative to the Font-Size of the parent element
108	ParentFontSize,
109	/// Relative to the scroll container's scrollport
110	Scrollport,
111	/// Relative to the content area dimension
112	ContentArea,
113	/// Relative to the border-edge side length
114	BorderEdge,
115	/// Relative to the background positioning area
116	BackgroundPositioningArea,
117	/// Relative to the reference box size
118	ReferenceBox,
119	/// Relative to the element's own dimensions
120	SelfSize,
121	/// Relative to the line box
122	LineBox,
123	/// Relative to the flex container
124	FlexContainer,
125	/// Relative to the border image area
126	BorderImageArea,
127	/// Map to a normalized range (e.g., `[0,1]`)
128	NormalizedRange,
129	/// Unknown or complex percentage resolution
130	Unknown,
131}
132
133/// The type of element or container this style value applies to.
134#[bitmask(u16)]
135#[bitmask_config(vec_debug)]
136#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
137pub enum AppliesTo {
138	/// Any element which is `display: block` or equivalent.
139	Block,
140	/// Any element which is `display: grid` or equivalent.
141	Grid,
142	/// Any element which is `display: flex` or equivalent.
143	Flex,
144	/// Any inline-level box.
145	Inline,
146	/// Any floated element.
147	Float,
148	/// Any Ruby container
149	Ruby,
150	/// Any absolutely positioned element.
151	AbsPos,
152	/// Any text node.
153	Text,
154	/// Any Pseudo Elements
155	PseudoElements,
156	/// Any Element
157	Elements,
158	/// What this applies to still needs to be established.
159	Unknown,
160}
161
162pub enum AnimationType {
163	/// This property is not animatable.
164	None,
165	/// This property animates between discrete values.
166	Discrete,
167	/// Animates by interpolating computed values
168	ByComputedValue,
169	/// Each item in a list animates independently
170	RepeatableList,
171	/// Animates as a transform list
172	TransformList,
173	/// Animates as a shadow list
174	ShadowList,
175	/// Animates as a length value
176	Length,
177	/// Animates as a number value
178	Number,
179	/// Unknown or complex animation behavior
180	Unknown,
181}
182
183/// How the computed value is calculated from the specified value
184pub enum ComputedValueType {
185	/// The computed value is the same as the specified value
186	AsSpecified,
187	/// Computed to an absolute length
188	AbsoluteLength,
189	/// Computed to an absolute length or percentage
190	AbsoluteLengthOrPercentage,
191	/// Computed to an absolute length or 'none'
192	AbsoluteLengthOrNone,
193	/// A specified keyword plus an absolute length
194	SpecifiedKeywordPlusAbsoluteLength,
195	/// Two absolute lengths (e.g., for background-position)
196	TwoAbsoluteLengths,
197	/// A list of absolute lengths
198	ListOfAbsoluteLengths,
199	/// Computed as specified, but with relative lengths converted to absolute
200	SpecifiedWithAbsoluteLengths,
201	/// Computed as specified, but with relative URLs converted to absolute
202	SpecifiedWithAbsoluteUrls,
203	/// Special computation rules - see spec
204	SeeIndividualProperties,
205	/// Computed value calculation is complex or spec-specific
206	Complex,
207	/// Not yet categorized
208	Unknown,
209}
210
211/// Which side(s) of the box a property applies to.
212/// This is a bitmask so properties can apply to multiple sides.
213#[bitmask(u8)]
214#[bitmask_config(vec_debug)]
215#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
216pub enum BoxSide {
217	/// Applies to the physical top side
218	Top = 0b00000001,
219	/// Applies to the physical bottom side
220	Bottom = 0b00000010,
221	/// Applies to the physical left side
222	Left = 0b00000100,
223	/// Applies to the physical right side
224	Right = 0b00001000,
225	/// Applies to the logical block-start side
226	BlockStart = 0b00010000,
227	/// Applies to the logical block-end side
228	BlockEnd = 0b00100000,
229	/// Applies to the logical inline-start side
230	InlineStart = 0b01000000,
231	/// Applies to the logical inline-end side
232	InlineEnd = 0b10000000,
233}
234
235impl BoxSide {
236	#[inline]
237	pub fn num_sides(&self, logical: bool) -> u32 {
238		if logical { (self.bits() & 0b11110000).count_ones() } else { (self.bits() & 0b00001111).count_ones() }
239	}
240}
241
242/// Which portion(s) of the box model a property affects.
243/// This is a bitmask so properties can affect multiple portions.
244#[bitmask(u8)]
245#[bitmask_config(vec_debug)]
246#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
247pub enum BoxPortion {
248	/// Affects the content size (width/height)
249	Size,
250	/// Affects the margin area
251	Margin,
252	/// Affects the padding area
253	Padding,
254	/// Affects the border area
255	Border,
256	/// Affects the position/placement of the box
257	Position,
258}
259
260pub trait DeclarationMetadata: Sized {
261	/// Returns the initial value of this property, as a string
262	fn initial() -> &'static str;
263
264	/// Determines if this style value inherits from parent rules
265	fn inherits() -> Inherits {
266		// Most properties do not inherit, so this is a sensible default
267		Inherits::False
268	}
269
270	/// Determines what types of frames this rule applies to
271	fn applies_to() -> AppliesTo {
272		AppliesTo::none()
273	}
274
275	/// Determines how this style value resolves percentages, if they are allowed as values
276	fn percentages() -> Percentages {
277		Percentages::None
278	}
279
280	/// Returns how this style value animates
281	fn animation_type() -> AnimationType {
282		// Most properties do not animate, so this is a sensible default
283		AnimationType::None
284	}
285
286	/// Determines if this style value is a "shorthand" value, meaning it is comprised of other "longhand" style values.
287	fn is_shorthand() -> bool {
288		false
289	}
290
291	/// Determines if this style value is a "longhand" value, meaning a "shorthand" style value exists that could also
292	/// express this.
293	fn is_longhand() -> bool {
294		Self::shorthand_group() == CssAtomSet::_None
295	}
296
297	/// Returns all transitive longhands for a shorthand property.
298	/// For nested shorthands (e.g., `border-width`), this recursively expands to include
299	/// all nested longhands (e.g., `border-top-width`, `border-left-width`, etc.).
300	fn longhands() -> Option<&'static [CssAtomSet]> {
301		None
302	}
303
304	/// Returns the declaration ID of the shorthand that this property is part of.
305	/// If this is not a longhand then it will be `CssAtomSet::_None`.
306	fn shorthand_group() -> CssAtomSet {
307		CssAtomSet::_None
308	}
309
310	/// Returns which CSS specification(s) this property belongs to.
311	/// This allows tracking which CSS modules are used in a stylesheet.
312	fn property_group() -> PropertyGroup {
313		PropertyGroup::none()
314	}
315
316	/// Returns how the computed value is calculated from the specified value.
317	fn computed_value_type() -> ComputedValueType {
318		ComputedValueType::Unknown
319	}
320
321	/// Returns the canonical order for serialization (e.g., "per grammar", "unique").
322	/// Returns None if not specified or not applicable.
323	fn canonical_order() -> Option<&'static str> {
324		None
325	}
326
327	/// Returns the logical property group this property belongs to (e.g., "Margin", "Border").
328	/// This groups related logical/physical properties together.
329	/// Returns None if this is not part of a logical property group.
330	fn logical_property_group() -> Option<CssAtomSet> {
331		None
332	}
333
334	/// Returns which side(s) of the box this property applies to.
335	/// For example, `margin-top` returns BoxSide::Top, while `margin` returns all sides.
336	/// Returns BoxSide::none() if the property doesn't apply to a specific side.
337	fn box_side() -> BoxSide {
338		BoxSide::none()
339	}
340
341	/// Returns which portion(s) of the box model this property affects.
342	/// For example, `margin-top` returns BoxPortion::Margin, `border-width` returns BoxPortion::Border.
343	/// Returns BoxPortion::none() if the property doesn't affect the box model.
344	fn box_portion() -> BoxPortion {
345		BoxPortion::none()
346	}
347
348	/// Returns how unitless zero resolves for this property.
349	///
350	/// For properties that accept both `<number>` and `<length>`, unitless zero
351	/// may resolve to a number rather than a length. This affects whether the
352	/// minifier can safely reduce `0px` to `0`.
353	///
354	/// Examples where unitless zero resolves to Number (NOT safe to reduce):
355	/// - `line-height: 0` means 0x font-size multiplier
356	/// - `tab-size: 0` means 0 tab characters
357	/// - `border-image-outset: 0` means 0x border-width
358	fn unitless_zero_resolves() -> UnitlessZeroResolves {
359		// Default: most properties accept unitless zero as length
360		UnitlessZeroResolves::Length
361	}
362}
363
364#[cfg(test)]
365mod test {
366	use crate::*;
367
368	#[test]
369	fn test_box_side_count() {
370		assert_eq!(BoxSide::Top.num_sides(false), 1);
371		assert_eq!((BoxSide::Top | BoxSide::Right).num_sides(false), 2);
372		assert_eq!((BoxSide::Top | BoxSide::Right | BoxSide::Bottom).num_sides(false), 3);
373		assert_eq!((BoxSide::Top | BoxSide::Right | BoxSide::Bottom | BoxSide::Left).num_sides(false), 4);
374		assert_eq!((BoxSide::Top | BoxSide::Right | BoxSide::Bottom | BoxSide::Left).num_sides(true), 0);
375
376		assert_eq!(BoxSide::all_bits().num_sides(false), 4);
377		assert_eq!(BoxSide::all_bits().num_sides(true), 4);
378
379		assert_eq!(BoxSide::BlockStart.num_sides(true), 1);
380		assert_eq!((BoxSide::BlockStart | BoxSide::BlockEnd).num_sides(true), 2);
381		assert_eq!((BoxSide::BlockStart | BoxSide::BlockEnd | BoxSide::InlineStart).num_sides(true), 3);
382		assert_eq!(
383			(BoxSide::BlockStart | BoxSide::BlockEnd | BoxSide::InlineStart | BoxSide::InlineEnd).num_sides(true),
384			4
385		);
386		assert_eq!(
387			(BoxSide::BlockStart | BoxSide::BlockEnd | BoxSide::InlineStart | BoxSide::InlineEnd).num_sides(false),
388			0
389		);
390	}
391}