1use std::borrow::Cow;
2use std::marker::PhantomData;
3
4use taffy::{Line, Point, Rect, Size};
5
6use super::ComputedStyle;
7use crate::style::SizingContext;
8use crate::style::properties::*;
9
10impl ComputedStyle {
11 pub fn make_computed(&mut self, sizing: &SizingContext) {
13 let dpr = sizing.viewport.device_pixel_ratio;
16 self.font_size = if dpr > 0.0 {
17 FontSize::Length(Length::Px(sizing.font_size / dpr))
18 } else {
19 FontSize::Length(Length::Px(sizing.font_size))
20 };
21
22 self.make_computed_values(sizing);
23
24 if self.position.is_out_of_flow() || self.float != Float::None {
27 self.display.blockify();
28 }
29 }
30
31 pub fn is_invisible(&self) -> bool {
32 self.opacity.0 == 0.0 || self.display == Display::None || self.visibility == Visibility::Hidden
33 }
34
35 pub fn is_z_index_applicable(&self, is_flex_or_grid_item: bool) -> bool {
36 !matches!(self.z_index, ZIndex::Auto) && (self.position.is_positioned() || is_flex_or_grid_item)
37 }
38
39 pub fn participates_in_positioned_paint_bucket(&self, is_flex_or_grid_item: bool) -> bool {
40 self.position.is_positioned() || self.is_z_index_applicable(is_flex_or_grid_item)
41 }
42
43 pub fn creates_stacking_context(
44 &self,
45 border_box: Size<f32>,
46 sizing: &SizingContext,
47 is_flex_or_grid_item: bool,
48 ) -> bool {
49 self.isolation == Isolation::Isolate
50 || self.is_z_index_applicable(is_flex_or_grid_item)
51 || self.has_non_identity_transform(border_box, sizing)
52 || self.needs_offscreen_compositing()
53 }
54
55 pub fn needs_offscreen_compositing(&self) -> bool {
56 self.isolation == Isolation::Isolate
57 || *self.opacity < 1.0
58 || !self.filter.is_empty()
59 || !self.backdrop_filter.is_empty()
60 || self.mix_blend_mode != BlendMode::Normal
61 || self.clip_path.is_some()
62 || self.mask_image.as_ref().is_some_and(|images| {
63 images
64 .iter()
65 .any(|image| !matches!(image, BackgroundImage::None))
66 })
67 }
68
69 pub fn has_non_identity_transform(&self, border_box: Size<f32>, sizing: &SizingContext) -> bool {
70 let transform_origin = self.transform_origin;
71 let origin = transform_origin.to_point(sizing, border_box);
72
73 let mut local = Affine::translation(origin.x, origin.y);
74
75 let translate = self.translate;
76 if translate != SpacePair::default() {
77 local *= Affine::translation(
78 translate.x.to_px(sizing, border_box.width),
79 translate.y.to_px(sizing, border_box.height),
80 );
81 }
82
83 if let Some(rotate) = self.rotate {
84 local *= Affine::rotation(rotate);
85 }
86
87 let scale = self.scale;
88 if scale != SpacePair::default() {
89 local *= Affine::scale(scale.x.0, scale.y.0);
90 }
91
92 if let Some(node_transform) = &self.transform {
93 local *= Affine::from_transforms(node_transform.iter(), sizing, border_box);
94 }
95
96 local *= Affine::translation(-origin.x, -origin.y);
97
98 !local.is_identity()
99 }
100
101 pub fn resolve_overflows(&self) -> SpacePair<Overflow> {
102 SpacePair::from_pair(self.overflow_x, self.overflow_y)
103 }
104
105 pub fn ellipsis_char(&self) -> &str {
106 const ELLIPSIS_CHAR: &str = "…";
107
108 match &self.text_overflow {
109 TextOverflow::Ellipsis => return ELLIPSIS_CHAR,
110 TextOverflow::Custom(custom) => return custom.as_str(),
111 _ => {}
112 }
113
114 if let Some(clamp) = &self
115 .line_clamp
116 .as_ref()
117 .and_then(|clamp| clamp.ellipsis.as_deref())
118 {
119 return clamp;
120 }
121
122 ELLIPSIS_CHAR
123 }
124
125 pub fn text_wrap_mode_and_line_clamp(&self) -> (TextWrapMode, Option<Cow<'_, LineClamp>>) {
126 let mut text_wrap_mode = self.text_wrap_mode;
127 let mut line_clamp = self.line_clamp.as_ref().map(Cow::Borrowed);
128
129 if text_wrap_mode == TextWrapMode::NoWrap && self.text_overflow == TextOverflow::Ellipsis {
132 line_clamp = Some(Cow::Owned(self.single_line_ellipsis_clamp()));
133
134 text_wrap_mode = TextWrapMode::Wrap;
135 }
136
137 (text_wrap_mode, line_clamp)
138 }
139
140 #[inline]
141 fn single_line_ellipsis_clamp(&self) -> LineClamp {
142 LineClamp {
143 count: 1,
144 ellipsis: Some(self.ellipsis_char().to_string()),
145 }
146 }
147
148 #[inline]
149 fn grid_template(
150 components: &Option<GridTemplateComponents>,
151 sizing: &SizingContext,
152 ) -> (Vec<taffy::GridTemplateComponent<String>>, Vec<Vec<String>>) {
153 components.as_deref().map_or_else(
154 || (Vec::new(), vec![Vec::new()]),
155 |components| collect_components_and_names(components, sizing),
156 )
157 }
158
159 #[inline]
160 pub fn resolved_text_decoration_thickness(
161 &self,
162 sizing: &SizingContext,
163 ) -> SizedTextDecorationThickness {
164 match self.text_decoration_thickness {
165 TextDecorationThickness::Length(Length::Auto) | TextDecorationThickness::FromFont => {
166 SizedTextDecorationThickness::FromFont
167 }
168 TextDecorationThickness::Length(thickness) => {
169 SizedTextDecorationThickness::Value(thickness.to_px(sizing, sizing.font_size))
170 }
171 }
172 }
173
174 pub fn to_taffy_style(&self, sizing: &SizingContext) -> taffy::Style {
175 let (grid_template_columns, grid_template_column_names) =
177 Self::grid_template(&self.grid_template_columns, sizing);
178 let (grid_template_rows, grid_template_row_names) =
179 Self::grid_template(&self.grid_template_rows, sizing);
180
181 taffy::Style {
182 float: self.float.resolve(self.direction),
183 clear: self.clear.resolve(self.direction),
184 direction: self.direction.into(),
185 box_sizing: self.box_sizing.into(),
186 size: Size {
187 width: self.width,
188 height: self.height,
189 }
190 .map(|length| length.resolve_to_dimension(sizing)),
191 border: Rect {
192 top: if !self.border_top_style.is_rendered() {
193 Length::default()
194 } else {
195 self.border_top_width
196 },
197 right: if !self.border_right_style.is_rendered() {
198 Length::default()
199 } else {
200 self.border_right_width
201 },
202 bottom: if !self.border_bottom_style.is_rendered() {
203 Length::default()
204 } else {
205 self.border_bottom_width
206 },
207 left: if !self.border_left_style.is_rendered() {
208 Length::default()
209 } else {
210 self.border_left_width
211 },
212 }
213 .map(|border| border.resolve_to_length_percentage(sizing)),
214 padding: Rect {
215 top: self.padding_top,
216 right: self.padding_right,
217 bottom: self.padding_bottom,
218 left: self.padding_left,
219 }
220 .map(|padding| padding.resolve_to_length_percentage(sizing)),
221 inset: if self.position == Position::Static {
222 Rect::auto()
223 } else {
224 Rect {
225 top: self.top,
226 right: self.right,
227 bottom: self.bottom,
228 left: self.left,
229 }
230 .map(|inset| inset.resolve_to_length_percentage_auto(sizing))
231 },
232 margin: Rect {
233 top: self.margin_top,
234 right: self.margin_right,
235 bottom: self.margin_bottom,
236 left: self.margin_left,
237 }
238 .map(|margin| margin.resolve_to_length_percentage_auto(sizing)),
239 display: self.display.into(),
240 flex_direction: self.flex_direction.into(),
241 position: self.position.into(),
242 justify_content: self.justify_content.into(),
243 align_content: self.align_content.into(),
244 justify_items: self.justify_items.into(),
245 flex_grow: self.flex_grow.map(|grow| grow.0).unwrap_or(0.0),
246 align_items: self.align_items.into(),
247 gap: Size {
248 width: self.column_gap.resolve_to_length_percentage(sizing),
249 height: self.row_gap.resolve_to_length_percentage(sizing),
250 },
251 flex_basis: self
252 .flex_basis
253 .unwrap_or(Length::Auto)
254 .resolve_to_dimension(sizing),
255 flex_shrink: self.flex_shrink.map(|shrink| shrink.0).unwrap_or(1.0),
256 flex_wrap: self.flex_wrap.into(),
257 min_size: Size {
258 width: self.min_width,
259 height: self.min_height,
260 }
261 .map(|length| length.resolve_to_dimension(sizing)),
262 max_size: Size {
263 width: self.max_width,
264 height: self.max_height,
265 }
266 .map(|length| length.resolve_to_dimension(sizing)),
267 grid_auto_columns: self
268 .grid_auto_columns
269 .as_ref()
270 .map_or_else(Vec::new, |tracks| {
271 tracks
272 .iter()
273 .map(|track| track.to_min_max(sizing))
274 .collect()
275 }),
276 grid_auto_rows: self
277 .grid_auto_rows
278 .as_ref()
279 .map_or_else(Vec::new, |tracks| {
280 tracks
281 .iter()
282 .map(|track| track.to_min_max(sizing))
283 .collect()
284 }),
285 grid_auto_flow: self.grid_auto_flow.into(),
286 grid_column: Line {
287 start: self.grid_column_start.clone().into(),
288 end: self.grid_column_end.clone().into(),
289 },
290 grid_row: Line {
291 start: self.grid_row_start.clone().into(),
292 end: self.grid_row_end.clone().into(),
293 },
294 grid_template_columns,
295 grid_template_rows,
296 grid_template_column_names,
297 grid_template_row_names,
298 grid_template_areas: self
299 .grid_template_areas
300 .as_ref()
301 .cloned()
302 .unwrap_or_default()
303 .into(),
304 aspect_ratio: self.aspect_ratio.into(),
305 align_self: self.align_self.into(),
306 justify_self: self.justify_self.into(),
307 overflow: Point::from(self.resolve_overflows()).map(Into::into),
308 dummy: PhantomData,
309 item_is_table: false,
310 item_is_replaced: false,
311 scrollbar_width: 0.0,
312 text_align: taffy::TextAlign::Auto,
313 }
314 }
315}