1use 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 #[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 }
105 NodeData::Element(element_data) | NodeData::AnonymousBlock(element_data) => {
106 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 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 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 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 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 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(); 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::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 } }
481 }
482
483 fn get_final_layout(&self, node_id: NodeId) -> Layout {
484 self.node_from_id(node_id).final_layout
485 }
486}
487
488pub 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}