Skip to main content

takumi_css/style/
stylesheets_query.rs

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  /// Normalize inheritable text-related values to computed values for this node.
12  pub fn make_computed(&mut self, sizing: &SizingContext) {
13    // `font-size` computed value is already resolved in `sizing.font_size`.
14    // Keep it as css-px in style to avoid re-resolving descendant inheritance.
15    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    // https://www.w3.org/TR/css-display-3/#transformations
25    // Elements with position: absolute or fixed are blockified
26    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    // Special case: when nowrap + ellipsis, parley will layout all the text even when it overflows.
130    // So we need to use a fixed line clamp of 1 instead.
131    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    // Convert grid templates and associated line names
176    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}