Skip to main content

blitz_dom/layout/
mod.rs

1//! Enable the dom to lay itself out using taffy
2//!
3//! In servo, style and layout happen together during traversal
4//! However, in Blitz, we do a style pass then a layout pass.
5//! This is slower, yes, but happens fast enough that it's not a huge issue.
6
7use crate::node::{ImageData, NodeData, SpecialElementData};
8use crate::{document::BaseDocument, node::Node};
9use markup5ever::local_name;
10use std::cell::Ref;
11use std::sync::Arc;
12use style::Atom;
13use style::values::computed::CSSPixelLength;
14use style::values::computed::length_percentage::CalcLengthPercentage;
15use taffy::{
16    BlockContext, CollapsibleMarginSet, FlexDirection, LayoutPartialTree, NodeId, ResolveOrZero,
17    RoundTree, Style, TraversePartialTree, TraverseTree, compute_block_layout,
18    compute_cached_layout, compute_flexbox_layout, compute_grid_layout, compute_leaf_layout,
19    prelude::*,
20};
21
22pub(crate) mod construct;
23pub(crate) mod damage;
24pub(crate) mod inline;
25pub(crate) mod list;
26pub(crate) mod replaced;
27pub(crate) mod table;
28
29use self::replaced::{ReplacedContext, replaced_measure_function};
30use self::table::TableTreeWrapper;
31
32pub(crate) fn resolve_calc_value(calc_ptr: *const (), parent_size: f32) -> f32 {
33    let calc = unsafe { &*(calc_ptr as *const CalcLengthPercentage) };
34    let result = calc.resolve(CSSPixelLength::new(parent_size));
35    result.px()
36}
37
38impl BaseDocument {
39    fn node_from_id(&self, node_id: taffy::prelude::NodeId) -> &Node {
40        &self.nodes[node_id.into()]
41    }
42    fn node_from_id_mut(&mut self, node_id: taffy::prelude::NodeId) -> &mut Node {
43        &mut self.nodes[node_id.into()]
44    }
45}
46
47impl BaseDocument {
48    fn compute_child_layout_internal(
49        &mut self,
50        node_id: NodeId,
51        inputs: taffy::tree::LayoutInput,
52        block_ctx: Option<&mut BlockContext<'_>>,
53    ) -> taffy::tree::LayoutOutput {
54        let node = &mut self.nodes[node_id.into()];
55
56        let font_styles = node.primary_styles().map(|style| {
57            use style::values::computed::font::LineHeight;
58
59            let font_size = style.clone_font_size().used_size().px();
60            let line_height = match style.clone_line_height() {
61                LineHeight::Normal => font_size * 1.2,
62                LineHeight::Number(num) => font_size * num.0,
63                LineHeight::Length(value) => value.0.px(),
64            };
65
66            (font_size, line_height)
67        });
68        let font_size = font_styles.map(|s| s.0);
69        let resolved_line_height = font_styles.map(|s| s.1);
70
71        match &mut node.data {
72            NodeData::Text(data) => {
73                // With the new "inline context" architecture all text nodes should be wrapped in an "inline layout context"
74                // and should therefore never be measured individually.
75                #[cfg(feature = "tracing")]
76                tracing::error!(
77                    node_id = usize::from(node_id),
78                    data = ?data,
79                    "Tried to lay out text node individually",
80                );
81
82                #[cfg(not(feature = "tracing"))]
83                let _ = data;
84
85                taffy::LayoutOutput::HIDDEN
86                // unreachable!();
87
88                // compute_leaf_layout(inputs, &node.style, |known_dimensions, available_space| {
89                //     let context = TextContext {
90                //         text_content: &data.content.trim(),
91                //         writing_mode: WritingMode::Horizontal,
92                //     };
93                //     let font_metrics = FontMetrics {
94                //         char_width: 8.0,
95                //         char_height: 16.0,
96                //     };
97                //     text_measure_function(
98                //         known_dimensions,
99                //         available_space,
100                //         &context,
101                //         &font_metrics,
102                //     )
103                // })
104            }
105            NodeData::Element(element_data) | NodeData::AnonymousBlock(element_data) => {
106                // TODO: deduplicate with single-line text input
107                if *element_data.name.local == *"textarea" {
108                    let rows = element_data
109                        .attr(local_name!("rows"))
110                        .and_then(|val| val.parse::<f32>().ok())
111                        .unwrap_or(2.0);
112
113                    let cols = element_data
114                        .attr(local_name!("cols"))
115                        .and_then(|val| val.parse::<f32>().ok());
116
117                    return compute_leaf_layout(
118                        inputs,
119                        &node.style,
120                        resolve_calc_value,
121                        |_known_size, _available_space| taffy::Size {
122                            width: cols
123                                .map(|cols| cols * font_size.unwrap_or(16.0) * 0.6)
124                                .unwrap_or(300.0),
125                            height: resolved_line_height.unwrap_or(16.0) * rows,
126                        },
127                    );
128                }
129
130                if *element_data.name.local == *"input" {
131                    match element_data.attr(local_name!("type")) {
132                        // if the input type is hidden, hide it
133                        Some("hidden") => {
134                            node.style.display = Display::None;
135                            return taffy::LayoutOutput::HIDDEN;
136                        }
137                        Some("checkbox") => {
138                            return compute_leaf_layout(
139                                inputs,
140                                &node.style,
141                                resolve_calc_value,
142                                |_known_size, _available_space| {
143                                    let width = node.style.size.width.resolve_or_zero(
144                                        inputs.parent_size.width,
145                                        resolve_calc_value,
146                                    );
147                                    let height = node.style.size.height.resolve_or_zero(
148                                        inputs.parent_size.height,
149                                        resolve_calc_value,
150                                    );
151                                    let min_size = width.min(height);
152                                    taffy::Size {
153                                        width: min_size,
154                                        height: min_size,
155                                    }
156                                },
157                            );
158                        }
159                        None | Some("text" | "password" | "email" | "tel" | "url" | "search") => {
160                            return compute_leaf_layout(
161                                inputs,
162                                &node.style,
163                                resolve_calc_value,
164                                |_known_size, _available_space| taffy::Size {
165                                    width: match inputs.available_space.width {
166                                        AvailableSpace::Definite(limit) => limit.min(300.0),
167                                        AvailableSpace::MinContent => 0.0,
168                                        AvailableSpace::MaxContent => 300.0,
169                                    },
170                                    height: resolved_line_height.unwrap_or(16.0),
171                                },
172                            );
173                        }
174                        _ => {}
175                    }
176                }
177
178                if *element_data.name.local == *"img"
179                    || *element_data.name.local == *"canvas"
180                    || (cfg!(feature = "svg") && *element_data.name.local == *"svg")
181                {
182                    // Get width and height attributes on image element
183                    //
184                    // TODO: smarter sizing using these (depending on object-fit, they shouldn't
185                    // necessarily just override the native size)
186                    let attr_size = taffy::Size {
187                        width: element_data
188                            .attr(local_name!("width"))
189                            .and_then(|val| val.parse::<f32>().ok()),
190                        height: element_data
191                            .attr(local_name!("height"))
192                            .and_then(|val| val.parse::<f32>().ok()),
193                    };
194
195                    // Get image's native sizespecial_data
196                    let inherent_size = match &element_data.special_data {
197                        SpecialElementData::Image(image_data) => match &**image_data {
198                            ImageData::Raster(image) => taffy::Size {
199                                width: image.width as f32,
200                                height: image.height as f32,
201                            },
202                            #[cfg(feature = "svg")]
203                            ImageData::Svg(svg) => {
204                                let size = svg.size();
205                                taffy::Size {
206                                    width: size.width(),
207                                    height: size.height(),
208                                }
209                            }
210                            ImageData::None => taffy::Size::ZERO,
211                        },
212                        SpecialElementData::Canvas(_) => taffy::Size::ZERO,
213                        SpecialElementData::None => taffy::Size::ZERO,
214                        _ => unreachable!(),
215                    };
216
217                    let replaced_context = ReplacedContext {
218                        inherent_size,
219                        attr_size,
220                    };
221
222                    let computed = replaced_measure_function(
223                        inputs.known_dimensions,
224                        inputs.parent_size,
225                        inputs.available_space,
226                        &replaced_context,
227                        &node.style,
228                        false,
229                    );
230
231                    return taffy::LayoutOutput {
232                        size: computed,
233                        content_size: computed,
234                        first_baselines: taffy::Point::NONE,
235                        top_margin: CollapsibleMarginSet::ZERO,
236                        bottom_margin: CollapsibleMarginSet::ZERO,
237                        margins_can_collapse_through: false,
238                    };
239                }
240
241                if node.flags.is_table_root() {
242                    let SpecialElementData::TableRoot(context) = &self.nodes[node_id.into()]
243                        .data
244                        .downcast_element()
245                        .unwrap()
246                        .special_data
247                    else {
248                        panic!("Node marked as table root but doesn't have TableContext");
249                    };
250                    let context = Arc::clone(context);
251
252                    let mut table_wrapper = TableTreeWrapper {
253                        doc: self,
254                        ctx: context,
255                    };
256                    let mut output = compute_grid_layout(&mut table_wrapper, node_id, inputs);
257
258                    // HACK: Cap content size at node size to prevent scrolling
259                    output.content_size.width = output.content_size.width.min(output.size.width);
260                    output.content_size.height = output.content_size.height.min(output.size.height);
261
262                    return output;
263                }
264
265                if node.flags.is_inline_root() {
266                    return self.compute_inline_layout(usize::from(node_id), inputs, block_ctx);
267                }
268
269                // The default CSS file will set
270                match node.style.display {
271                    Display::Block => compute_block_layout(self, node_id, inputs, block_ctx),
272                    Display::Flex => compute_flexbox_layout(self, node_id, inputs),
273                    Display::Grid => compute_grid_layout(self, node_id, inputs),
274                    Display::None => taffy::LayoutOutput::HIDDEN,
275                }
276            }
277            NodeData::Document => compute_block_layout(self, node_id, inputs, None),
278
279            _ => taffy::LayoutOutput::HIDDEN,
280        }
281    }
282}
283
284impl TraversePartialTree for BaseDocument {
285    type ChildIter<'a> = RefCellChildIter<'a>;
286
287    fn child_ids(&self, node_id: NodeId) -> Self::ChildIter<'_> {
288        let layout_children = self.node_from_id(node_id).layout_children.borrow(); //.unwrap().as_ref();
289        RefCellChildIter::new(Ref::map(layout_children, |children| {
290            children.as_ref().map(|c| c.as_slice()).unwrap_or(&[])
291        }))
292    }
293
294    fn child_count(&self, node_id: NodeId) -> usize {
295        self.node_from_id(node_id)
296            .layout_children
297            .borrow()
298            .as_ref()
299            .map(|c| c.len())
300            .unwrap_or(0)
301    }
302
303    fn get_child_id(&self, node_id: NodeId, index: usize) -> NodeId {
304        NodeId::from(
305            self.node_from_id(node_id)
306                .layout_children
307                .borrow()
308                .as_ref()
309                .unwrap()[index],
310        )
311    }
312}
313impl TraverseTree for BaseDocument {}
314
315impl LayoutPartialTree for BaseDocument {
316    type CoreContainerStyle<'a>
317        = &'a taffy::Style<Atom>
318    where
319        Self: 'a;
320
321    type CustomIdent = Atom;
322
323    fn get_core_container_style(&self, node_id: NodeId) -> &Style<Atom> {
324        &self.node_from_id(node_id).style
325    }
326
327    fn set_unrounded_layout(&mut self, node_id: NodeId, layout: &Layout) {
328        self.node_from_id_mut(node_id).unrounded_layout = *layout;
329    }
330
331    fn resolve_calc_value(&self, calc_ptr: *const (), parent_size: f32) -> f32 {
332        resolve_calc_value(calc_ptr, parent_size)
333    }
334
335    #[inline(always)]
336    fn compute_child_layout(
337        &mut self,
338        node_id: NodeId,
339        inputs: taffy::LayoutInput,
340    ) -> taffy::LayoutOutput {
341        compute_cached_layout(self, node_id, inputs, |tree, node_id, inputs| {
342            tree.compute_child_layout_internal(node_id, inputs, None)
343        })
344    }
345}
346
347impl taffy::CacheTree for BaseDocument {
348    #[inline]
349    fn cache_get(
350        &self,
351        node_id: NodeId,
352        inputs: &taffy::LayoutInput,
353    ) -> Option<taffy::LayoutOutput> {
354        self.node_from_id(node_id).cache.get(inputs)
355    }
356
357    #[inline]
358    fn cache_store(
359        &mut self,
360        node_id: NodeId,
361        inputs: &taffy::LayoutInput,
362        layout_output: taffy::LayoutOutput,
363    ) {
364        self.node_from_id_mut(node_id)
365            .cache
366            .store(inputs, layout_output);
367    }
368
369    #[inline]
370    fn cache_clear(&mut self, node_id: NodeId) {
371        self.node_from_id_mut(node_id).cache.clear();
372    }
373}
374
375impl taffy::LayoutBlockContainer for BaseDocument {
376    type BlockContainerStyle<'a>
377        = &'a Style<Atom>
378    where
379        Self: 'a;
380
381    type BlockItemStyle<'a>
382        = &'a Style<Atom>
383    where
384        Self: 'a;
385
386    fn get_block_container_style(&self, node_id: NodeId) -> Self::BlockContainerStyle<'_> {
387        self.get_core_container_style(node_id)
388    }
389
390    fn get_block_child_style(&self, child_node_id: NodeId) -> Self::BlockItemStyle<'_> {
391        self.get_core_container_style(child_node_id)
392    }
393
394    #[inline(always)]
395    fn compute_block_child_layout(
396        &mut self,
397        node_id: NodeId,
398        inputs: taffy::LayoutInput,
399        block_ctx: Option<&mut BlockContext<'_>>,
400    ) -> taffy::LayoutOutput {
401        compute_cached_layout(self, node_id, inputs, |tree, node_id, inputs| {
402            tree.compute_child_layout_internal(node_id, inputs, block_ctx)
403        })
404    }
405}
406
407impl taffy::LayoutFlexboxContainer for BaseDocument {
408    type FlexboxContainerStyle<'a>
409        = &'a Style<Atom>
410    where
411        Self: 'a;
412
413    type FlexboxItemStyle<'a>
414        = &'a Style<Atom>
415    where
416        Self: 'a;
417
418    fn get_flexbox_container_style(&self, node_id: NodeId) -> Self::FlexboxContainerStyle<'_> {
419        self.get_core_container_style(node_id)
420    }
421
422    fn get_flexbox_child_style(&self, child_node_id: NodeId) -> Self::FlexboxItemStyle<'_> {
423        self.get_core_container_style(child_node_id)
424    }
425}
426
427impl taffy::LayoutGridContainer for BaseDocument {
428    type GridContainerStyle<'a>
429        = &'a Style<Atom>
430    where
431        Self: 'a;
432
433    type GridItemStyle<'a>
434        = &'a Style<Atom>
435    where
436        Self: 'a;
437
438    fn get_grid_container_style(&self, node_id: NodeId) -> Self::GridContainerStyle<'_> {
439        self.get_core_container_style(node_id)
440    }
441
442    fn get_grid_child_style(&self, child_node_id: NodeId) -> Self::GridItemStyle<'_> {
443        self.get_core_container_style(child_node_id)
444    }
445}
446
447impl RoundTree for BaseDocument {
448    fn get_unrounded_layout(&self, node_id: NodeId) -> Layout {
449        self.node_from_id(node_id).unrounded_layout
450    }
451
452    fn set_final_layout(&mut self, node_id: NodeId, layout: &Layout) {
453        self.node_from_id_mut(node_id).final_layout = *layout;
454    }
455}
456
457impl PrintTree for BaseDocument {
458    fn get_debug_label(&self, node_id: NodeId) -> &'static str {
459        let node = &self.node_from_id(node_id);
460        let style = &node.style;
461
462        match node.data {
463            NodeData::Document => "DOCUMENT",
464            // NodeData::Doctype { .. } => return "DOCTYPE",
465            NodeData::Text { .. } => node.node_debug_str().leak(),
466            NodeData::Comment => "COMMENT",
467            NodeData::AnonymousBlock(_) => "ANONYMOUS BLOCK",
468            NodeData::Element(_) => {
469                let display = match style.display {
470                    Display::Flex => match style.flex_direction {
471                        FlexDirection::Row | FlexDirection::RowReverse => "FLEX ROW",
472                        FlexDirection::Column | FlexDirection::ColumnReverse => "FLEX COL",
473                    },
474                    Display::Grid => "GRID",
475                    Display::Block => "BLOCK",
476                    Display::None => "NONE",
477                };
478                format!("{} ({})", node.node_debug_str(), display).leak()
479            } // NodeData::ProcessingInstruction { .. } => return "PROCESSING INSTRUCTION",
480        }
481    }
482
483    fn get_final_layout(&self, node_id: NodeId) -> Layout {
484        self.node_from_id(node_id).final_layout
485    }
486}
487
488// pub struct ChildIter<'a>(std::slice::Iter<'a, usize>);
489// impl<'a> Iterator for ChildIter<'a> {
490//     type Item = NodeId;
491//     fn next(&mut self) -> Option<Self::Item> {
492//         self.0.next().copied().map(NodeId::from)
493//     }
494// }
495
496pub struct RefCellChildIter<'a> {
497    items: Ref<'a, [usize]>,
498    idx: usize,
499}
500impl<'a> RefCellChildIter<'a> {
501    fn new(items: Ref<'a, [usize]>) -> RefCellChildIter<'a> {
502        RefCellChildIter { items, idx: 0 }
503    }
504}
505
506impl Iterator for RefCellChildIter<'_> {
507    type Item = NodeId;
508    fn next(&mut self) -> Option<Self::Item> {
509        self.items.get(self.idx).map(|id| {
510            self.idx += 1;
511            NodeId::from(*id)
512        })
513    }
514}