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 eprintln!(
76 "bliss-dom: text node {} laid out individually (should be wrapped in inline context)",
77 usize::from(node_id)
78 );
79 taffy::LayoutOutput::HIDDEN
80 }
99 NodeData::Element(element_data) | NodeData::AnonymousBlock(element_data) => {
100 if *element_data.name.local == *"textarea" {
102 let rows = element_data
103 .attr(local_name!("rows"))
104 .and_then(|val| val.parse::<f32>().ok())
105 .unwrap_or(2.0);
106
107 let cols = element_data
108 .attr(local_name!("cols"))
109 .and_then(|val| val.parse::<f32>().ok());
110
111 return compute_leaf_layout(
112 inputs,
113 &node.style,
114 resolve_calc_value,
115 |_known_size, _available_space| taffy::Size {
116 width: cols
117 .map(|cols| cols * font_size.unwrap_or(16.0) * 0.6)
118 .unwrap_or(300.0),
119 height: resolved_line_height.unwrap_or(16.0) * rows,
120 },
121 );
122 }
123
124 if *element_data.name.local == *"input" {
125 match element_data.attr(local_name!("type")) {
126 Some("hidden") => {
128 node.style.display = Display::None;
129 return taffy::LayoutOutput::HIDDEN;
130 }
131 Some("checkbox") => {
132 return compute_leaf_layout(
133 inputs,
134 &node.style,
135 resolve_calc_value,
136 |_known_size, _available_space| {
137 let width = node.style.size.width.resolve_or_zero(
138 inputs.parent_size.width,
139 resolve_calc_value,
140 );
141 let height = node.style.size.height.resolve_or_zero(
142 inputs.parent_size.height,
143 resolve_calc_value,
144 );
145 let min_size = width.min(height);
146 taffy::Size {
147 width: min_size,
148 height: min_size,
149 }
150 },
151 );
152 }
153 None | Some("text" | "password" | "email" | "tel" | "url" | "search") => {
154 return compute_leaf_layout(
155 inputs,
156 &node.style,
157 resolve_calc_value,
158 |_known_size, _available_space| taffy::Size {
159 width: 300.0,
160 height: resolved_line_height.unwrap_or(16.0),
161 },
162 );
163 }
164 _ => {}
165 }
166 }
167
168 if *element_data.name.local == *"img"
169 || *element_data.name.local == *"canvas"
170 || (cfg!(feature = "svg") && *element_data.name.local == *"svg")
171 {
172 let attr_size = taffy::Size {
177 width: element_data
178 .attr(local_name!("width"))
179 .and_then(|val| val.parse::<f32>().ok()),
180 height: element_data
181 .attr(local_name!("height"))
182 .and_then(|val| val.parse::<f32>().ok()),
183 };
184
185 let inherent_size = match &element_data.special_data {
187 SpecialElementData::Image(image_data) => match &**image_data {
188 ImageData::Raster(image) => taffy::Size {
189 width: image.width as f32,
190 height: image.height as f32,
191 },
192 #[cfg(feature = "svg")]
193 ImageData::Svg(svg) => {
194 let size = svg.size();
195 taffy::Size {
196 width: size.width(),
197 height: size.height(),
198 }
199 }
200 ImageData::None => taffy::Size::ZERO,
201 },
202 SpecialElementData::Canvas(_) => taffy::Size::ZERO,
203 SpecialElementData::None => taffy::Size::ZERO,
204 _ => {
205 taffy::Size::ZERO
208 }
209 };
210
211 let replaced_context = ReplacedContext {
212 inherent_size,
213 attr_size,
214 };
215
216 let computed = replaced_measure_function(
217 inputs.known_dimensions,
218 inputs.parent_size,
219 inputs.available_space,
220 &replaced_context,
221 &node.style,
222 false,
223 );
224
225 return taffy::LayoutOutput {
226 size: computed,
227 content_size: computed,
228 first_baselines: taffy::Point::NONE,
229 top_margin: CollapsibleMarginSet::ZERO,
230 bottom_margin: CollapsibleMarginSet::ZERO,
231 margins_can_collapse_through: false,
232 };
233 }
234
235 if node.flags.is_table_root() {
236 let Some(el) = self.nodes[node_id.into()].data.downcast_element() else {
237 return taffy::LayoutOutput::HIDDEN;
240 };
241 let SpecialElementData::TableRoot(ref ctx) = el.special_data else {
242 return taffy::LayoutOutput::HIDDEN;
245 };
246 let context = Arc::clone(ctx);
247
248 let mut table_wrapper = TableTreeWrapper {
249 doc: self,
250 ctx: context,
251 };
252 let mut output = compute_grid_layout(&mut table_wrapper, node_id, inputs);
253
254 output.content_size.width = output.content_size.width.min(output.size.width);
256 output.content_size.height = output.content_size.height.min(output.size.height);
257
258 return output;
259 }
260
261 if node.flags.is_inline_root() {
262 return self.compute_inline_layout(usize::from(node_id), inputs, block_ctx);
263 }
264
265 match node.style.display {
267 Display::Block => compute_block_layout(self, node_id, inputs, block_ctx),
268 Display::Flex => compute_flexbox_layout(self, node_id, inputs),
269 Display::Grid => compute_grid_layout(self, node_id, inputs),
270 Display::None => taffy::LayoutOutput::HIDDEN,
271 }
272 }
273 NodeData::Document => compute_block_layout(self, node_id, inputs, None),
274
275 _ => taffy::LayoutOutput::HIDDEN,
276 }
277 }
278}
279
280impl TraversePartialTree for BaseDocument {
281 type ChildIter<'a> = RefCellChildIter<'a>;
282
283 fn child_ids(&self, node_id: NodeId) -> Self::ChildIter<'_> {
284 let layout_children = self.node_from_id(node_id).layout_children.borrow(); RefCellChildIter::new(Ref::map(layout_children, |children| {
286 children.as_ref().map(|c| c.as_slice()).unwrap_or(&[])
287 }))
288 }
289
290 fn child_count(&self, node_id: NodeId) -> usize {
291 self.node_from_id(node_id)
292 .layout_children
293 .borrow()
294 .as_ref()
295 .map(|c| c.len())
296 .unwrap_or(0)
297 }
298
299 fn get_child_id(&self, node_id: NodeId, index: usize) -> NodeId {
300 NodeId::from(
301 self.node_from_id(node_id)
302 .layout_children
303 .borrow()
304 .as_ref()
305 .unwrap()[index],
306 )
307 }
308}
309impl TraverseTree for BaseDocument {}
310
311impl LayoutPartialTree for BaseDocument {
312 type CoreContainerStyle<'a>
313 = &'a taffy::Style<Atom>
314 where
315 Self: 'a;
316
317 type CustomIdent = Atom;
318
319 fn get_core_container_style(&self, node_id: NodeId) -> &Style<Atom> {
320 &self.node_from_id(node_id).style
321 }
322
323 fn set_unrounded_layout(&mut self, node_id: NodeId, layout: &Layout) {
324 self.node_from_id_mut(node_id).unrounded_layout = *layout;
325 }
326
327 fn resolve_calc_value(&self, calc_ptr: *const (), parent_size: f32) -> f32 {
328 resolve_calc_value(calc_ptr, parent_size)
329 }
330
331 #[inline(always)]
332 fn compute_child_layout(
333 &mut self,
334 node_id: NodeId,
335 inputs: taffy::LayoutInput,
336 ) -> taffy::LayoutOutput {
337 compute_cached_layout(self, node_id, inputs, |tree, node_id, inputs| {
338 tree.compute_child_layout_internal(node_id, inputs, None)
339 })
340 }
341}
342
343impl taffy::CacheTree for BaseDocument {
344 #[inline]
345 fn cache_get(
346 &self,
347 node_id: NodeId,
348 inputs: &taffy::LayoutInput,
349 ) -> Option<taffy::LayoutOutput> {
350 self.node_from_id(node_id).cache.get(inputs)
351 }
352
353 #[inline]
354 fn cache_store(
355 &mut self,
356 node_id: NodeId,
357 inputs: &taffy::LayoutInput,
358 layout_output: taffy::LayoutOutput,
359 ) {
360 self.node_from_id_mut(node_id).cache.store(inputs, layout_output);
361 }
362
363 #[inline]
364 fn cache_clear(&mut self, node_id: NodeId) {
365 self.node_from_id_mut(node_id).cache.clear();
366 }
367}
368
369impl taffy::LayoutBlockContainer for BaseDocument {
370 type BlockContainerStyle<'a>
371 = &'a Style<Atom>
372 where
373 Self: 'a;
374
375 type BlockItemStyle<'a>
376 = &'a Style<Atom>
377 where
378 Self: 'a;
379
380 fn get_block_container_style(&self, node_id: NodeId) -> Self::BlockContainerStyle<'_> {
381 self.get_core_container_style(node_id)
382 }
383
384 fn get_block_child_style(&self, child_node_id: NodeId) -> Self::BlockItemStyle<'_> {
385 self.get_core_container_style(child_node_id)
386 }
387
388 #[inline(always)]
389 fn compute_block_child_layout(
390 &mut self,
391 node_id: NodeId,
392 inputs: taffy::LayoutInput,
393 block_ctx: Option<&mut BlockContext<'_>>,
394 ) -> taffy::LayoutOutput {
395 compute_cached_layout(self, node_id, inputs, |tree, node_id, inputs| {
396 tree.compute_child_layout_internal(node_id, inputs, block_ctx)
397 })
398 }
399}
400
401impl taffy::LayoutFlexboxContainer for BaseDocument {
402 type FlexboxContainerStyle<'a>
403 = &'a Style<Atom>
404 where
405 Self: 'a;
406
407 type FlexboxItemStyle<'a>
408 = &'a Style<Atom>
409 where
410 Self: 'a;
411
412 fn get_flexbox_container_style(&self, node_id: NodeId) -> Self::FlexboxContainerStyle<'_> {
413 self.get_core_container_style(node_id)
414 }
415
416 fn get_flexbox_child_style(&self, child_node_id: NodeId) -> Self::FlexboxItemStyle<'_> {
417 self.get_core_container_style(child_node_id)
418 }
419}
420
421impl taffy::LayoutGridContainer for BaseDocument {
422 type GridContainerStyle<'a>
423 = &'a Style<Atom>
424 where
425 Self: 'a;
426
427 type GridItemStyle<'a>
428 = &'a Style<Atom>
429 where
430 Self: 'a;
431
432 fn get_grid_container_style(&self, node_id: NodeId) -> Self::GridContainerStyle<'_> {
433 self.get_core_container_style(node_id)
434 }
435
436 fn get_grid_child_style(&self, child_node_id: NodeId) -> Self::GridItemStyle<'_> {
437 self.get_core_container_style(child_node_id)
438 }
439}
440
441impl RoundTree for BaseDocument {
442 fn get_unrounded_layout(&self, node_id: NodeId) -> Layout {
443 self.node_from_id(node_id).unrounded_layout
444 }
445
446 fn set_final_layout(&mut self, node_id: NodeId, layout: &Layout) {
447 self.node_from_id_mut(node_id).final_layout = *layout;
448 }
449}
450
451impl PrintTree for BaseDocument {
452 fn get_debug_label(&self, node_id: NodeId) -> &'static str {
453 let node = &self.node_from_id(node_id);
454 let style = &node.style;
455
456 match node.data {
457 NodeData::Document => "DOCUMENT",
458 NodeData::Text { .. } => node.node_debug_str().leak(),
460 NodeData::Comment => "COMMENT",
461 NodeData::AnonymousBlock(_) => "ANONYMOUS BLOCK",
462 NodeData::ShadowRoot { .. } => "SHADOW ROOT",
463 NodeData::Element(_) => {
464 let display = match style.display {
465 Display::Flex => match style.flex_direction {
466 FlexDirection::Row | FlexDirection::RowReverse => "FLEX ROW",
467 FlexDirection::Column | FlexDirection::ColumnReverse => "FLEX COL",
468 },
469 Display::Grid => "GRID",
470 Display::Block => "BLOCK",
471 Display::None => "NONE",
472 };
473 format!("{} ({})", node.node_debug_str(), display).leak()
474 } }
476 }
477
478 fn get_final_layout(&self, node_id: NodeId) -> Layout {
479 self.node_from_id(node_id).final_layout
480 }
481}
482
483pub struct RefCellChildIter<'a> {
492 items: Ref<'a, [usize]>,
493 idx: usize,
494}
495impl<'a> RefCellChildIter<'a> {
496 fn new(items: Ref<'a, [usize]>) -> RefCellChildIter<'a> {
497 RefCellChildIter { items, idx: 0 }
498 }
499}
500
501impl Iterator for RefCellChildIter<'_> {
502 type Item = NodeId;
503 fn next(&mut self) -> Option<Self::Item> {
504 self.items.get(self.idx).map(|id| {
505 self.idx += 1;
506 NodeId::from(*id)
507 })
508 }
509}