1use crate::node::{ImageData, NodeData, SpecialElementData};
8use crate::{document::BaseDocument, dom_node_id, node::Node, taffy_node_id};
9use markup5ever::{LocalName, 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, FlexDirection, LayoutPartialTree, NodeId, ResolveOrZero, RoundTree, Style,
17 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::{
29 IntrinsicSizes, ReplacedContext, compute_replaced_layout, is_replaced_element,
30};
31use self::table::TableTreeWrapper;
32
33const DEFAULT_OBJECT_SIZE: taffy::Size<f32> = taffy::Size {
36 width: 300.0,
37 height: 150.0,
38};
39
40fn tag_intrinsic_sizes(
47 tag_name: &LocalName,
48 attr_size: taffy::Size<Option<f32>>,
49) -> (IntrinsicSizes, taffy::Size<f32>) {
50 if *tag_name == local_name!("img") || *tag_name == local_name!("svg") {
51 return (IntrinsicSizes::default(), taffy::Size::ZERO);
52 }
53 if *tag_name == local_name!("canvas") {
54 let width = attr_size.width.unwrap_or(300.0);
55 let height = attr_size.height.unwrap_or(150.0);
56 return (
57 IntrinsicSizes {
58 width: Some(width),
59 height: Some(height),
60 ratio: Some(width / height),
61 },
62 DEFAULT_OBJECT_SIZE,
63 );
64 }
65 (IntrinsicSizes::default(), DEFAULT_OBJECT_SIZE)
66}
67
68pub(crate) fn resolve_calc_value(calc_ptr: *const (), parent_size: f32) -> f32 {
69 let calc = unsafe { &*(calc_ptr as *const CalcLengthPercentage) };
70 let result = calc.resolve(CSSPixelLength::new(parent_size));
71 result.px()
72}
73
74impl BaseDocument {
75 fn node_from_id(&self, node_id: taffy::prelude::NodeId) -> &Node {
76 &self.nodes[dom_node_id(node_id)]
77 }
78 fn node_from_id_mut(&mut self, node_id: taffy::prelude::NodeId) -> &mut Node {
79 &mut self.nodes[dom_node_id(node_id)]
80 }
81}
82
83impl BaseDocument {
84 fn compute_child_layout_internal(
85 &mut self,
86 node_id: NodeId,
87 inputs: taffy::tree::LayoutInput,
88 block_ctx: Option<&mut BlockContext<'_>>,
89 ) -> taffy::tree::LayoutOutput {
90 let node = &mut self.nodes[dom_node_id(node_id)];
91
92 let font_styles = node.primary_styles().map(|style| {
93 use style::values::computed::font::LineHeight;
94
95 let font_size = style.clone_font_size().used_size().px();
96 let line_height = match style.clone_line_height() {
97 LineHeight::Normal => font_size * 1.2,
98 LineHeight::Number(num) => font_size * num.0,
99 LineHeight::Length(value) => value.0.px(),
100 };
101
102 (font_size, line_height)
103 });
104 let font_size = font_styles.map(|s| s.0);
105 let resolved_line_height = font_styles.map(|s| s.1);
106
107 match &mut node.data {
108 NodeData::Text(data) => {
109 #[cfg(feature = "tracing")]
112 tracing::error!(
113 node_id = ?dom_node_id(node_id),
114 data = ?data,
115 "Tried to lay out text node individually",
116 );
117
118 #[cfg(not(feature = "tracing"))]
119 let _ = data;
120
121 taffy::LayoutOutput::HIDDEN
122 }
141 NodeData::Element(element_data) | NodeData::AnonymousBlock(element_data) => {
142 if *element_data.name.local == *"textarea" {
144 let rows = element_data
145 .attr(local_name!("rows"))
146 .and_then(|val| val.parse::<f32>().ok())
147 .unwrap_or(2.0);
148
149 let cols = element_data
150 .attr(local_name!("cols"))
151 .and_then(|val| val.parse::<f32>().ok());
152
153 return compute_leaf_layout(
154 inputs,
155 node.style(),
156 resolve_calc_value,
157 |_known_size, _available_space| taffy::Size {
158 width: cols
159 .map(|cols| cols * font_size.unwrap_or(16.0) * 0.6)
160 .unwrap_or(300.0),
161 height: resolved_line_height.unwrap_or(16.0) * rows,
162 },
163 );
164 }
165
166 if *element_data.name.local == *"input" {
167 match element_data.attr(local_name!("type")) {
168 Some("hidden") => {
170 node.style_mut().display = Display::None;
171 return taffy::LayoutOutput::HIDDEN;
172 }
173 Some("checkbox") => {
174 return compute_leaf_layout(
175 inputs,
176 node.style(),
177 resolve_calc_value,
178 |_known_size, _available_space| {
179 let width = node.style().size.width.resolve_or_zero(
180 inputs.parent_size.width,
181 resolve_calc_value,
182 );
183 let height = node.style().size.height.resolve_or_zero(
184 inputs.parent_size.height,
185 resolve_calc_value,
186 );
187 let min_size = width.min(height);
188 taffy::Size {
189 width: min_size,
190 height: min_size,
191 }
192 },
193 );
194 }
195 None | Some("text" | "password" | "email" | "tel" | "url" | "search") => {
196 return compute_leaf_layout(
197 inputs,
198 node.style(),
199 resolve_calc_value,
200 |_known_size, _available_space| taffy::Size {
201 width: match inputs.available_space.width {
202 AvailableSpace::Definite(limit) => limit.min(300.0),
203 AvailableSpace::MinContent => 0.0,
204 AvailableSpace::MaxContent => 300.0,
205 },
206 height: resolved_line_height.unwrap_or(16.0),
207 },
208 );
209 }
210 _ => {}
211 }
212 }
213
214 if is_replaced_element(&element_data.name.local) {
215 let attr_size = taffy::Size {
222 width: element_data
223 .attr(local_name!("width"))
224 .and_then(|val| val.parse::<f32>().ok()),
225 height: element_data
226 .attr(local_name!("height"))
227 .and_then(|val| val.parse::<f32>().ok()),
228 };
229
230 let (intrinsic_sizes, default_object_size) = match &element_data.special_data {
232 SpecialElementData::Image(image_data) => match &**image_data {
233 ImageData::Raster(image) => {
234 let (width, height) = (image.width as f32, image.height as f32);
235 (
236 IntrinsicSizes {
237 width: Some(width),
238 height: Some(height),
239 ratio: Some(width / height),
240 },
241 DEFAULT_OBJECT_SIZE,
242 )
243 }
244 #[cfg(feature = "svg")]
245 ImageData::Svg(svg) => {
246 let mut width = svg.intrinsic_width();
247 let mut height = svg.intrinsic_height();
248 if width.is_none()
252 && height.is_none()
253 && svg.viewbox_aspect_ratio().is_none()
254 {
255 let size = svg.tree.size();
256 width = Some(size.width());
257 height = Some(size.height());
258 }
259 (
260 IntrinsicSizes {
261 width,
262 height,
263 ratio: Some(svg.aspect_ratio()),
264 },
265 DEFAULT_OBJECT_SIZE,
266 )
267 }
268 ImageData::None => (IntrinsicSizes::default(), taffy::Size::ZERO),
269 },
270 SpecialElementData::Canvas(_)
271 | SpecialElementData::SubDocument(_)
272 | SpecialElementData::None => {
273 tag_intrinsic_sizes(&element_data.name.local, attr_size)
274 }
275 #[cfg(feature = "custom-widget")]
276 SpecialElementData::CustomWidget(widget_data) => {
277 let (fallback, default_object_size) =
278 tag_intrinsic_sizes(&element_data.name.local, attr_size);
279 let attr_intrinsic =
283 if *element_data.name.local == local_name!("canvas") {
284 attr_size
285 } else {
286 taffy::Size::NONE
287 };
288 let attr_ratio = match (attr_intrinsic.width, attr_intrinsic.height) {
289 (Some(w), Some(h)) => Some(w / h),
290 _ => None,
291 };
292 let widget_sizes = widget_data.widget.intrinsic_sizes();
293 (
294 IntrinsicSizes {
295 width: attr_intrinsic
296 .width
297 .or(widget_sizes.width)
298 .or(fallback.width),
299 height: attr_intrinsic
300 .height
301 .or(widget_sizes.height)
302 .or(fallback.height),
303 ratio: attr_ratio.or(widget_sizes.ratio).or(fallback.ratio),
304 },
305 default_object_size,
306 )
307 }
308 _ => unreachable!(),
309 };
310
311 let replaced_context = ReplacedContext {
312 intrinsic_sizes,
313 default_object_size,
314 };
315
316 return compute_replaced_layout(
317 inputs,
318 node.style(),
319 resolve_calc_value,
320 &replaced_context,
321 );
322 }
323
324 if node.flags.is_table_root() {
325 let SpecialElementData::TableRoot(context) = &self.nodes[dom_node_id(node_id)]
326 .data
327 .downcast_element()
328 .unwrap()
329 .special_data
330 else {
331 panic!("Node marked as table root but doesn't have TableContext");
332 };
333 let context = Arc::clone(context);
334
335 let mut table_wrapper = TableTreeWrapper {
336 doc: self,
337 ctx: context,
338 };
339 let mut output = compute_grid_layout(&mut table_wrapper, node_id, inputs);
340
341 output.scrollable_overflow_rect.left = 0.0;
343 output.scrollable_overflow_rect.top = 0.0;
344 output.scrollable_overflow_rect.right =
345 output.scrollable_overflow_rect.right.min(output.size.width);
346 output.scrollable_overflow_rect.bottom = output
347 .scrollable_overflow_rect
348 .bottom
349 .min(output.size.height);
350
351 return output;
352 }
353
354 if node.flags.is_inline_root() {
355 return self.compute_inline_layout(dom_node_id(node_id), inputs, block_ctx);
356 }
357
358 match node.style().display {
360 Display::Block => compute_block_layout(self, node_id, inputs, block_ctx),
361 Display::FlowRoot => compute_block_layout(self, node_id, inputs, None),
362 Display::Flex => compute_flexbox_layout(self, node_id, inputs),
363 Display::Grid => compute_grid_layout(self, node_id, inputs),
364 Display::None => taffy::LayoutOutput::HIDDEN,
365 }
366 }
367 NodeData::Document(_) => compute_block_layout(self, node_id, inputs, None),
368
369 _ => taffy::LayoutOutput::HIDDEN,
370 }
371 }
372}
373
374impl TraversePartialTree for BaseDocument {
375 type ChildIter<'a> = RefCellChildIter<'a>;
376
377 fn child_ids(&self, node_id: NodeId) -> Self::ChildIter<'_> {
378 let layout_children = self.node_from_id(node_id).layout_children.borrow(); RefCellChildIter::new(Ref::map(layout_children, |children| {
380 children.as_ref().map(|c| c.as_slice()).unwrap_or(&[])
381 }))
382 }
383
384 fn child_count(&self, node_id: NodeId) -> usize {
385 self.node_from_id(node_id)
386 .layout_children
387 .borrow()
388 .as_ref()
389 .map(|c| c.len())
390 .unwrap_or(0)
391 }
392
393 fn get_child_id(&self, node_id: NodeId, index: usize) -> NodeId {
394 taffy_node_id(
395 self.node_from_id(node_id)
396 .layout_children
397 .borrow()
398 .as_ref()
399 .unwrap()[index],
400 )
401 }
402}
403impl TraverseTree for BaseDocument {}
404
405impl LayoutPartialTree for BaseDocument {
406 type CoreContainerStyle<'a>
407 = &'a taffy::Style<Atom>
408 where
409 Self: 'a;
410
411 type CustomIdent = Atom;
412
413 fn get_core_container_style(&self, node_id: NodeId) -> &Style<Atom> {
414 self.node_from_id(node_id).style()
415 }
416
417 fn set_unrounded_layout(&mut self, node_id: NodeId, layout: &Layout) {
418 *self.node_from_id_mut(node_id).unrounded_layout_mut() = *layout;
419 }
420
421 fn resolve_calc_value(&self, calc_ptr: *const (), parent_size: f32) -> f32 {
422 resolve_calc_value(calc_ptr, parent_size)
423 }
424
425 #[inline(always)]
426 fn compute_child_layout(
427 &mut self,
428 node_id: NodeId,
429 inputs: taffy::LayoutInput,
430 ) -> taffy::LayoutOutput {
431 compute_cached_layout(self, node_id, inputs, |tree, node_id, inputs| {
432 tree.compute_child_layout_internal(node_id, inputs, None)
433 })
434 }
435}
436
437impl taffy::CacheTree for BaseDocument {
438 #[inline]
439 fn cache_get(
440 &mut self,
441 node_id: NodeId,
442 inputs: &taffy::LayoutInput,
443 ) -> Option<taffy::LayoutOutput> {
444 self.node_from_id_mut(node_id).cache_mut().get(inputs)
445 }
446
447 #[inline]
448 fn cache_store(
449 &mut self,
450 node_id: NodeId,
451 inputs: &taffy::LayoutInput,
452 layout_output: taffy::LayoutOutput,
453 ) {
454 self.node_from_id_mut(node_id)
455 .cache_mut()
456 .store(inputs, layout_output);
457 }
458
459 #[inline]
460 fn cache_clear(&mut self, node_id: NodeId) {
461 self.node_from_id_mut(node_id).cache_mut().clear();
462 }
463}
464
465impl taffy::LayoutBlockContainer for BaseDocument {
466 type BlockContainerStyle<'a>
467 = &'a Style<Atom>
468 where
469 Self: 'a;
470
471 type BlockItemStyle<'a>
472 = &'a Style<Atom>
473 where
474 Self: 'a;
475
476 fn get_block_container_style(&self, node_id: NodeId) -> Self::BlockContainerStyle<'_> {
477 self.get_core_container_style(node_id)
478 }
479
480 fn get_block_child_style(&self, child_node_id: NodeId) -> Self::BlockItemStyle<'_> {
481 self.get_core_container_style(child_node_id)
482 }
483
484 #[inline(always)]
485 fn compute_block_child_layout(
486 &mut self,
487 node_id: NodeId,
488 inputs: taffy::LayoutInput,
489 block_ctx: Option<&mut BlockContext<'_>>,
490 ) -> taffy::LayoutOutput {
491 compute_cached_layout(self, node_id, inputs, |tree, node_id, inputs| {
492 tree.compute_child_layout_internal(node_id, inputs, block_ctx)
493 })
494 }
495}
496
497impl taffy::LayoutFlexboxContainer for BaseDocument {
498 type FlexboxContainerStyle<'a>
499 = &'a Style<Atom>
500 where
501 Self: 'a;
502
503 type FlexboxItemStyle<'a>
504 = &'a Style<Atom>
505 where
506 Self: 'a;
507
508 fn get_flexbox_container_style(&self, node_id: NodeId) -> Self::FlexboxContainerStyle<'_> {
509 self.get_core_container_style(node_id)
510 }
511
512 fn get_flexbox_child_style(&self, child_node_id: NodeId) -> Self::FlexboxItemStyle<'_> {
513 self.get_core_container_style(child_node_id)
514 }
515}
516
517impl taffy::LayoutGridContainer for BaseDocument {
518 type GridContainerStyle<'a>
519 = &'a Style<Atom>
520 where
521 Self: 'a;
522
523 type GridItemStyle<'a>
524 = &'a Style<Atom>
525 where
526 Self: 'a;
527
528 fn get_grid_container_style(&self, node_id: NodeId) -> Self::GridContainerStyle<'_> {
529 self.get_core_container_style(node_id)
530 }
531
532 fn get_grid_child_style(&self, child_node_id: NodeId) -> Self::GridItemStyle<'_> {
533 self.get_core_container_style(child_node_id)
534 }
535
536 fn set_detailed_grid_info(
537 &mut self,
538 node_id: NodeId,
539 detailed_grid_info: taffy::DetailedGridInfo<Atom>,
540 ) {
541 let node = self.node_from_id_mut(node_id);
542 if let Some(element) = node.element_data_mut() {
543 element.detailed_grid_info = Some(Box::new(detailed_grid_info));
544 }
545 }
546}
547
548impl RoundTree for BaseDocument {
549 fn get_unrounded_layout(&self, node_id: NodeId) -> Layout {
550 *self.node_from_id(node_id).unrounded_layout()
551 }
552
553 fn set_final_layout(&mut self, node_id: NodeId, layout: &Layout) {
554 *self.node_from_id_mut(node_id).final_layout_mut() = *layout;
555 }
556}
557
558impl PrintTree for BaseDocument {
559 fn get_debug_label(&self, node_id: NodeId) -> &'static str {
560 let node = &self.node_from_id(node_id);
561
562 match node.data {
563 NodeData::Document(_) => "DOCUMENT",
564 NodeData::Text { .. } => node.node_debug_str().leak(),
566 NodeData::Comment { .. } => "COMMENT",
567 NodeData::AnonymousBlock(_) => "ANONYMOUS BLOCK",
568 NodeData::Element(_) => {
569 let style = node.style();
570 let display = match style.display {
571 Display::Flex => match style.flex_direction {
572 FlexDirection::Row | FlexDirection::RowReverse => "FLEX ROW",
573 FlexDirection::Column | FlexDirection::ColumnReverse => "FLEX COL",
574 },
575 Display::Grid => "GRID",
576 Display::Block => "BLOCK",
577 Display::FlowRoot => "FLOW ROOT",
578 Display::None => "NONE",
579 };
580 format!("{} ({})", node.node_debug_str(), display).leak()
581 } }
583 }
584
585 fn get_final_layout(&self, node_id: NodeId) -> Layout {
586 *self.node_from_id(node_id).final_layout()
587 }
588}
589
590pub struct RefCellChildIter<'a> {
599 items: Ref<'a, [crate::NodeId]>,
600 idx: usize,
601}
602impl<'a> RefCellChildIter<'a> {
603 fn new(items: Ref<'a, [crate::NodeId]>) -> RefCellChildIter<'a> {
604 RefCellChildIter { items, idx: 0 }
605 }
606}
607
608impl Iterator for RefCellChildIter<'_> {
609 type Item = NodeId;
610 fn next(&mut self) -> Option<Self::Item> {
611 self.items.get(self.idx).map(|id| {
612 self.idx += 1;
613 taffy_node_id(*id)
614 })
615 }
616}