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