blitz-pdf 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +26 -0
  2. package/THIRD_PARTY_NOTICES.md +5 -2
  3. package/docs/layout-regressions.md +65 -0
  4. package/docs/pagination-regressions.md +89 -0
  5. package/package.json +2 -1
  6. package/vendor/README.md +35 -0
  7. package/vendor/blitz-dom/Cargo.toml +113 -0
  8. package/vendor/blitz-dom/LICENSE-APACHE +176 -0
  9. package/vendor/blitz-dom/LICENSE-MIT +23 -0
  10. package/vendor/blitz-dom/assets/default.css +1142 -0
  11. package/vendor/blitz-dom/assets/moz-bullet-font.otf +0 -0
  12. package/vendor/blitz-dom/src/accessibility.rs +255 -0
  13. package/vendor/blitz-dom/src/config.rs +97 -0
  14. package/vendor/blitz-dom/src/cssom.rs +971 -0
  15. package/vendor/blitz-dom/src/debug.rs +206 -0
  16. package/vendor/blitz-dom/src/document.rs +3395 -0
  17. package/vendor/blitz-dom/src/events/driver.rs +427 -0
  18. package/vendor/blitz-dom/src/events/focus.rs +40 -0
  19. package/vendor/blitz-dom/src/events/ime.rs +31 -0
  20. package/vendor/blitz-dom/src/events/keyboard.rs +174 -0
  21. package/vendor/blitz-dom/src/events/mod.rs +315 -0
  22. package/vendor/blitz-dom/src/events/pointer.rs +849 -0
  23. package/vendor/blitz-dom/src/font_metrics.rs +177 -0
  24. package/vendor/blitz-dom/src/form.rs +463 -0
  25. package/vendor/blitz-dom/src/html.rs +38 -0
  26. package/vendor/blitz-dom/src/iframe.rs +186 -0
  27. package/vendor/blitz-dom/src/layout/construct.rs +1328 -0
  28. package/vendor/blitz-dom/src/layout/damage.rs +642 -0
  29. package/vendor/blitz-dom/src/layout/inline.rs +1160 -0
  30. package/vendor/blitz-dom/src/layout/list.rs +237 -0
  31. package/vendor/blitz-dom/src/layout/mod.rs +969 -0
  32. package/vendor/blitz-dom/src/layout/paint_tree.rs +462 -0
  33. package/vendor/blitz-dom/src/layout/replaced.rs +439 -0
  34. package/vendor/blitz-dom/src/layout/table.rs +967 -0
  35. package/vendor/blitz-dom/src/layout/text_transform.rs +979 -0
  36. package/vendor/blitz-dom/src/layout/writing_mode.rs +437 -0
  37. package/vendor/blitz-dom/src/lib.rs +160 -0
  38. package/vendor/blitz-dom/src/mutator.rs +1818 -0
  39. package/vendor/blitz-dom/src/net.rs +649 -0
  40. package/vendor/blitz-dom/src/node/attributes.rs +60 -0
  41. package/vendor/blitz-dom/src/node/custom_widget.rs +178 -0
  42. package/vendor/blitz-dom/src/node/element.rs +1106 -0
  43. package/vendor/blitz-dom/src/node/mod.rs +30 -0
  44. package/vendor/blitz-dom/src/node/node.rs +2036 -0
  45. package/vendor/blitz-dom/src/node/scrollbar.rs +237 -0
  46. package/vendor/blitz-dom/src/node/serialize.rs +225 -0
  47. package/vendor/blitz-dom/src/node/stylo_data.rs +142 -0
  48. package/vendor/blitz-dom/src/node/svg.rs +265 -0
  49. package/vendor/blitz-dom/src/node/text.rs +804 -0
  50. package/vendor/blitz-dom/src/query_selector.rs +275 -0
  51. package/vendor/blitz-dom/src/resolve.rs +474 -0
  52. package/vendor/blitz-dom/src/resolved_style.rs +797 -0
  53. package/vendor/blitz-dom/src/scrolling.rs +780 -0
  54. package/vendor/blitz-dom/src/selection.rs +122 -0
  55. package/vendor/blitz-dom/src/stylo.rs +1518 -0
  56. package/vendor/blitz-dom/src/stylo_device.rs +88 -0
  57. package/vendor/blitz-dom/src/stylo_to_cursor_icon.rs +49 -0
  58. package/vendor/blitz-dom/src/stylo_to_kurbo.rs +129 -0
  59. package/vendor/blitz-dom/src/stylo_to_parley.rs +655 -0
  60. package/vendor/blitz-dom/src/traversal.rs +600 -0
  61. package/vendor/blitz-dom/src/tree.rs +136 -0
  62. package/vendor/blitz-dom/src/url.rs +63 -0
  63. package/vendor/blitz-dom/src/util.rs +256 -0
  64. package/wasm/blitz_pdf_bg.wasm +0 -0
@@ -0,0 +1,969 @@
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
+
7
+ use crate::node::{ComputedStyleRef, ImageData, NodeData, SpecialElementData};
8
+ use crate::{document::BaseDocument, dom_node_id, node::Node, taffy_node_id};
9
+ use markup5ever::{LocalName, local_name};
10
+ use std::cell::Ref;
11
+ use std::ops::{Deref, DerefMut};
12
+ use std::sync::Arc;
13
+ use style::Atom;
14
+ use style::values::computed::CSSPixelLength;
15
+ use style::values::computed::length_percentage::CalcLengthPercentage;
16
+ use stylo_taffy::TaffyStyloStyle;
17
+ use taffy::{
18
+ AxisStaticEdge, AxisStaticPosition, BlockContext, CoreStyle as _, DetailedLayoutInfo,
19
+ FlexDirection, LayoutContainingBlock, LayoutPartialTree, MaybeMath as _, NodeId, OofCandidate,
20
+ ResolveOrZero, RoundTree, RunMode, TraversePartialTree, TraverseTree, compute_block_layout,
21
+ compute_cached_layout, compute_flexbox_layout, compute_grid_layout, compute_leaf_layout,
22
+ compute_oof_layout, prelude::*,
23
+ };
24
+
25
+ pub(crate) mod construct;
26
+ pub(crate) mod damage;
27
+ pub(crate) mod inline;
28
+ pub(crate) mod list;
29
+ pub(crate) mod paint_tree;
30
+ pub(crate) mod replaced;
31
+ pub(crate) mod table;
32
+ pub(crate) mod text_transform;
33
+ #[cfg(feature = "writing-mode")]
34
+ pub(crate) mod writing_mode;
35
+
36
+ use self::replaced::{
37
+ IntrinsicSizes, ReplacedContext, compute_replaced_layout, is_replaced_element,
38
+ };
39
+ use self::table::TableTreeWrapper;
40
+
41
+ /// The default object size for replaced elements
42
+ /// (https://drafts.csswg.org/css-images/#default-object-size).
43
+ const DEFAULT_OBJECT_SIZE: taffy::Size<f32> = taffy::Size {
44
+ width: 300.0,
45
+ height: 150.0,
46
+ };
47
+
48
+ /// The intrinsic dimensions and default object size for a replaced element
49
+ /// whose intrinsic dimensions are determined by its tag: an image with no
50
+ /// loaded resource has no intrinsic dimensions and a zero default object size;
51
+ /// a canvas has an intrinsic size and aspect ratio given by its width/height
52
+ /// attributes (defaulting to 300x150); other replaced elements (video, iframe,
53
+ /// embed) have no intrinsic dimensions and the 300x150 default object size.
54
+ fn tag_intrinsic_sizes(
55
+ tag_name: &LocalName,
56
+ attr_size: taffy::Size<Option<f32>>,
57
+ ) -> (IntrinsicSizes, taffy::Size<f32>) {
58
+ if *tag_name == local_name!("img") || *tag_name == local_name!("svg") {
59
+ return (IntrinsicSizes::default(), taffy::Size::ZERO);
60
+ }
61
+ if *tag_name == local_name!("canvas") {
62
+ let width = attr_size.width.unwrap_or(300.0);
63
+ let height = attr_size.height.unwrap_or(150.0);
64
+ return (
65
+ IntrinsicSizes {
66
+ width: Some(width),
67
+ height: Some(height),
68
+ ratio: Some(width / height),
69
+ },
70
+ DEFAULT_OBJECT_SIZE,
71
+ );
72
+ }
73
+ (IntrinsicSizes::default(), DEFAULT_OBJECT_SIZE)
74
+ }
75
+
76
+ pub(crate) fn resolve_calc_value(calc_ptr: *const (), parent_size: f32) -> f32 {
77
+ let calc = unsafe { &*(calc_ptr as *const CalcLengthPercentage) };
78
+ let result = calc.resolve(CSSPixelLength::new(parent_size));
79
+ result.px()
80
+ }
81
+
82
+ /// Per-pass layout state: wraps the document for the duration of a layout pass and carries
83
+ /// the Taffy tree trait implementations. Derefs to [`BaseDocument`].
84
+ pub(crate) struct LayoutPassState<'doc> {
85
+ doc: &'doc mut BaseDocument,
86
+ /// The writing mode of the box whose layout algorithm is currently running (see
87
+ /// `layout::writing_mode`). Child styles read during that algorithm are expressed in its axes.
88
+ #[cfg(feature = "writing-mode")]
89
+ pub(crate) layout_wm: stylo_taffy::WritingMode,
90
+ /// The root element and the writing mode its algorithm runs in (see `BaseDocument::root_layout_wm`).
91
+ #[cfg(feature = "writing-mode")]
92
+ root_id: Option<crate::NodeId>,
93
+ #[cfg(feature = "writing-mode")]
94
+ root_wm: stylo_taffy::WritingMode,
95
+ /// Whether the node whose layout algorithm is running aligns its children's `align-self` in its
96
+ /// inline axis (a column flex container).
97
+ #[cfg(feature = "writing-mode")]
98
+ pub(crate) current_align_axis_is_inline: bool,
99
+ /// The box currently being laid out in an orthogonal flow and its containing block's inline
100
+ /// size, against which its percentage padding resolves (see `layout::writing_mode`).
101
+ #[cfg(feature = "writing-mode")]
102
+ pub(crate) orthogonal_percent_basis: Option<(crate::NodeId, f32)>,
103
+ }
104
+
105
+ impl<'doc> LayoutPassState<'doc> {
106
+ /// The containing block's inline size when `dom_id` is the box currently laid out in an orthogonal
107
+ /// flow: its percentage padding resolves against it (see `layout::writing_mode`).
108
+ #[inline]
109
+ fn orthogonal_percent_basis_of(&self, dom_id: crate::NodeId) -> Option<f32> {
110
+ #[cfg(feature = "writing-mode")]
111
+ {
112
+ self.orthogonal_percent_basis
113
+ .filter(|(node, _)| *node == dom_id)
114
+ .map(|(_, basis)| basis)
115
+ }
116
+ #[cfg(not(feature = "writing-mode"))]
117
+ {
118
+ let _ = dom_id;
119
+ None
120
+ }
121
+ }
122
+
123
+ pub(crate) fn new(doc: &'doc mut BaseDocument) -> Self {
124
+ #[cfg(feature = "writing-mode")]
125
+ let root_id = doc.try_root_element().map(|root| root.id);
126
+ Self {
127
+ #[cfg(feature = "writing-mode")]
128
+ root_wm: root_id.map_or(stylo_taffy::WritingMode::empty(), |id| {
129
+ doc.root_layout_wm(id)
130
+ }),
131
+ doc,
132
+ #[cfg(feature = "writing-mode")]
133
+ root_id,
134
+ #[cfg(feature = "writing-mode")]
135
+ layout_wm: stylo_taffy::WritingMode::empty(),
136
+ #[cfg(feature = "writing-mode")]
137
+ current_align_axis_is_inline: false,
138
+ #[cfg(feature = "writing-mode")]
139
+ orthogonal_percent_basis: None,
140
+ }
141
+ }
142
+ }
143
+
144
+ impl Deref for LayoutPassState<'_> {
145
+ type Target = BaseDocument;
146
+ fn deref(&self) -> &BaseDocument {
147
+ self.doc
148
+ }
149
+ }
150
+
151
+ impl DerefMut for LayoutPassState<'_> {
152
+ fn deref_mut(&mut self) -> &mut BaseDocument {
153
+ self.doc
154
+ }
155
+ }
156
+
157
+ impl LayoutPassState<'_> {
158
+ fn node_from_id(&self, node_id: taffy::prelude::NodeId) -> &Node {
159
+ &self.nodes[dom_node_id(node_id)]
160
+ }
161
+ fn node_from_id_mut(&mut self, node_id: taffy::prelude::NodeId) -> &mut Node {
162
+ &mut self.nodes[dom_node_id(node_id)]
163
+ }
164
+ }
165
+
166
+ impl LayoutPassState<'_> {
167
+ /// Run the node's layout algorithm, then lay out the out-of-flow (absolute/fixed)
168
+ /// boxes for which it is the containing block. Must be called inside the layout
169
+ /// cache wrapper so that cache hits do not re-run the out-of-flow pass.
170
+ fn compute_child_layout_internal(
171
+ &mut self,
172
+ node_id: NodeId,
173
+ inputs: taffy::tree::LayoutInput,
174
+ block_ctx: Option<&mut BlockContext<'_>>,
175
+ ) -> taffy::tree::LayoutOutput {
176
+ #[cfg(feature = "writing-mode")]
177
+ {
178
+ self.compute_child_layout_in_own_wm(node_id, inputs, block_ctx)
179
+ }
180
+ #[cfg(not(feature = "writing-mode"))]
181
+ {
182
+ self.compute_child_layout_in_current_wm(node_id, inputs, block_ctx)
183
+ }
184
+ }
185
+
186
+ /// Lay out `node_id` in the writing mode of the currently running algorithm (its parent's)
187
+ pub(crate) fn compute_child_layout_in_current_wm(
188
+ &mut self,
189
+ node_id: NodeId,
190
+ inputs: taffy::tree::LayoutInput,
191
+ block_ctx: Option<&mut BlockContext<'_>>,
192
+ ) -> taffy::tree::LayoutOutput {
193
+ let mut output = self.dispatch_child_layout(node_id, inputs, block_ctx);
194
+ if inputs.run_mode == RunMode::PerformLayout {
195
+ compute_oof_layout(self, node_id, &mut output);
196
+ }
197
+ output
198
+ }
199
+
200
+ /// The style of a node being laid out *by* the node whose algorithm is currently running,
201
+ /// expressed in that node's writing mode (see `layout::writing_mode`).
202
+ #[inline]
203
+ pub(crate) fn child_layout_style<'a>(
204
+ &self,
205
+ node: &'a Node,
206
+ ) -> stylo_taffy::TaffyStyloStyle<ComputedStyleRef<'a>> {
207
+ #[cfg(feature = "writing-mode")]
208
+ {
209
+ let mut style = node.layout_style_in(self.layout_wm);
210
+ style.align_axis_is_inline = self.current_align_axis_is_inline;
211
+ style
212
+ }
213
+ #[cfg(not(feature = "writing-mode"))]
214
+ {
215
+ node.layout_style()
216
+ }
217
+ }
218
+
219
+ fn dispatch_child_layout(
220
+ &mut self,
221
+ node_id: NodeId,
222
+ inputs: taffy::tree::LayoutInput,
223
+ block_ctx: Option<&mut BlockContext<'_>>,
224
+ ) -> taffy::tree::LayoutOutput {
225
+ let orthogonal_percent_basis = self.orthogonal_percent_basis_of(dom_node_id(node_id));
226
+ let node = &mut self.nodes[dom_node_id(node_id)];
227
+
228
+ let font_styles = node.primary_styles().map(|style| {
229
+ use style::values::computed::font::LineHeight;
230
+
231
+ let font_size = style.clone_font_size().used_size().px();
232
+ let line_height = match style.clone_line_height() {
233
+ LineHeight::Normal => font_size * 1.2,
234
+ LineHeight::Number(num) => font_size * num.0,
235
+ LineHeight::Length(value) => value.0.px(),
236
+ };
237
+
238
+ (font_size, line_height)
239
+ });
240
+ let font_size = font_styles.map(|s| s.0);
241
+ let resolved_line_height = font_styles.map(|s| s.1);
242
+
243
+ match &mut node.data {
244
+ NodeData::Text(data) => {
245
+ // With the new "inline context" architecture all text nodes should be wrapped in an "inline layout context"
246
+ // and should therefore never be measured individually.
247
+ #[cfg(feature = "tracing")]
248
+ tracing::error!(
249
+ node_id = ?dom_node_id(node_id),
250
+ data = ?data,
251
+ "Tried to lay out text node individually",
252
+ );
253
+
254
+ #[cfg(not(feature = "tracing"))]
255
+ let _ = data;
256
+
257
+ taffy::LayoutOutput::HIDDEN
258
+ // unreachable!();
259
+
260
+ // compute_leaf_layout(inputs, &node.style, |known_dimensions, available_space| {
261
+ // let context = TextContext {
262
+ // text_content: &data.content.trim(),
263
+ // writing_mode: WritingMode::Horizontal,
264
+ // };
265
+ // let font_metrics = FontMetrics {
266
+ // char_width: 8.0,
267
+ // char_height: 16.0,
268
+ // };
269
+ // text_measure_function(
270
+ // known_dimensions,
271
+ // available_space,
272
+ // &context,
273
+ // &font_metrics,
274
+ // )
275
+ // })
276
+ }
277
+ NodeData::Element(element_data) | NodeData::AnonymousBlock(element_data) => {
278
+ // TODO: deduplicate with single-line text input
279
+ if *element_data.name.local == *"textarea" {
280
+ let rows = element_data
281
+ .attr(local_name!("rows"))
282
+ .and_then(|val| val.parse::<f32>().ok())
283
+ .unwrap_or(2.0);
284
+
285
+ let cols = element_data
286
+ .attr(local_name!("cols"))
287
+ .and_then(|val| val.parse::<f32>().ok());
288
+
289
+ let output = compute_leaf_layout(
290
+ inputs,
291
+ &node.layout_style(),
292
+ resolve_calc_value,
293
+ |_known_size, _available_space| taffy::Size {
294
+ width: cols
295
+ .map(|cols| cols * font_size.unwrap_or(16.0) * 0.6)
296
+ .unwrap_or(300.0),
297
+ height: resolved_line_height.unwrap_or(16.0) * rows,
298
+ },
299
+ );
300
+ if inputs.run_mode == RunMode::PerformLayout {
301
+ let pb = {
302
+ let style = node.layout_style();
303
+ style
304
+ .padding()
305
+ .resolve_or_zero(inputs.parent_size.width, resolve_calc_value)
306
+ + style
307
+ .border()
308
+ .resolve_or_zero(inputs.parent_size.width, resolve_calc_value)
309
+ };
310
+ let content_width = (output.size.width - pb.horizontal_axis_sum()).max(0.0);
311
+ let doc = &mut *self.doc;
312
+ let scale = doc.viewport.scale();
313
+ let node = &mut doc.nodes[dom_node_id(node_id)];
314
+ if let Some(input) = node
315
+ .data
316
+ .downcast_element_mut()
317
+ .and_then(|el| el.text_input_data_mut())
318
+ {
319
+ input.editor.set_width(Some(content_width * scale));
320
+ input.editor.refresh_layout(
321
+ &mut doc.font_ctx.lock().unwrap(),
322
+ &mut doc.layout_ctx,
323
+ );
324
+ }
325
+ }
326
+ return output;
327
+ }
328
+
329
+ if *element_data.name.local == *"input" {
330
+ match element_data.attr(local_name!("type")) {
331
+ // if the input type is hidden, hide it
332
+ Some("hidden") => {
333
+ return taffy::LayoutOutput::HIDDEN;
334
+ }
335
+ Some("checkbox") => {
336
+ return compute_leaf_layout(
337
+ inputs,
338
+ &node.layout_style(),
339
+ resolve_calc_value,
340
+ |_known_size, _available_space| {
341
+ let size = node.layout_style().size();
342
+ let width = size.width.resolve_or_zero(
343
+ inputs.parent_size.width,
344
+ resolve_calc_value,
345
+ );
346
+ let height = size.height.resolve_or_zero(
347
+ inputs.parent_size.height,
348
+ resolve_calc_value,
349
+ );
350
+ let min_size = width.min(height);
351
+ taffy::Size {
352
+ width: min_size,
353
+ height: min_size,
354
+ }
355
+ },
356
+ );
357
+ }
358
+ None | Some("text" | "password" | "email" | "tel" | "url" | "search") => {
359
+ return compute_leaf_layout(
360
+ inputs,
361
+ &node.layout_style(),
362
+ resolve_calc_value,
363
+ |_known_size, _available_space| taffy::Size {
364
+ width: match inputs.available_space.width {
365
+ AvailableSpace::Definite(limit) => limit.min(300.0),
366
+ AvailableSpace::MinContent => 0.0,
367
+ AvailableSpace::MaxContent => 300.0,
368
+ },
369
+ height: resolved_line_height.unwrap_or(16.0),
370
+ },
371
+ );
372
+ }
373
+ _ => {}
374
+ }
375
+ }
376
+
377
+ if is_replaced_element(&element_data.name.local) {
378
+ // Width/height attributes are presentational hints mapped to the CSS
379
+ // width/height properties by
380
+ // `synthesize_presentational_hints_for_legacy_attributes`, so they
381
+ // are already part of the style. They are only read here for the
382
+ // elements whose attributes determine their *intrinsic* size
383
+ // (canvas, and custom widgets on canvas tags).
384
+ let attr_size = taffy::Size {
385
+ width: element_data
386
+ .attr(local_name!("width"))
387
+ .and_then(|val| val.parse::<f32>().ok()),
388
+ height: element_data
389
+ .attr(local_name!("height"))
390
+ .and_then(|val| val.parse::<f32>().ok()),
391
+ };
392
+
393
+ // Get the element's intrinsic dimensions and default object size
394
+ let (intrinsic_sizes, default_object_size) = match &element_data.special_data {
395
+ SpecialElementData::Image(image_data) => match &**image_data {
396
+ ImageData::Raster(image) => {
397
+ let (width, height) = (image.width as f32, image.height as f32);
398
+ (
399
+ IntrinsicSizes {
400
+ width: Some(width),
401
+ height: Some(height),
402
+ ratio: Some(width / height),
403
+ },
404
+ DEFAULT_OBJECT_SIZE,
405
+ )
406
+ }
407
+ #[cfg(feature = "svg")]
408
+ ImageData::Svg(svg) => {
409
+ let mut width = svg.intrinsic_width();
410
+ let mut height = svg.intrinsic_height();
411
+ // An SVG with no declared dimensions and no viewBox has no
412
+ // intrinsic dimensions per CSS, but usvg still resolves a
413
+ // concrete size; use it in place of the default object size.
414
+ if width.is_none()
415
+ && height.is_none()
416
+ && svg.viewbox_aspect_ratio().is_none()
417
+ {
418
+ let size = svg.tree.size();
419
+ width = Some(size.width());
420
+ height = Some(size.height());
421
+ }
422
+ (
423
+ IntrinsicSizes {
424
+ width,
425
+ height,
426
+ ratio: Some(svg.aspect_ratio()),
427
+ },
428
+ DEFAULT_OBJECT_SIZE,
429
+ )
430
+ }
431
+ ImageData::None => (IntrinsicSizes::default(), taffy::Size::ZERO),
432
+ },
433
+ SpecialElementData::Canvas(_)
434
+ | SpecialElementData::SubDocument(_)
435
+ | SpecialElementData::None => {
436
+ tag_intrinsic_sizes(&element_data.name.local, attr_size)
437
+ }
438
+ #[cfg(feature = "custom-widget")]
439
+ SpecialElementData::CustomWidget(widget_data) => {
440
+ let (fallback, default_object_size) =
441
+ tag_intrinsic_sizes(&element_data.name.local, attr_size);
442
+ // A canvas's content attributes determine its intrinsic size,
443
+ // overriding the widget-reported one; the widget-reported size
444
+ // in turn overrides the tag's fallback.
445
+ let attr_intrinsic =
446
+ if *element_data.name.local == local_name!("canvas") {
447
+ attr_size
448
+ } else {
449
+ taffy::Size::NONE
450
+ };
451
+ let attr_ratio = match (attr_intrinsic.width, attr_intrinsic.height) {
452
+ (Some(w), Some(h)) => Some(w / h),
453
+ _ => None,
454
+ };
455
+ let widget_sizes = widget_data.widget.intrinsic_sizes();
456
+ (
457
+ IntrinsicSizes {
458
+ width: attr_intrinsic
459
+ .width
460
+ .or(widget_sizes.width)
461
+ .or(fallback.width),
462
+ height: attr_intrinsic
463
+ .height
464
+ .or(widget_sizes.height)
465
+ .or(fallback.height),
466
+ ratio: attr_ratio.or(widget_sizes.ratio).or(fallback.ratio),
467
+ },
468
+ default_object_size,
469
+ )
470
+ }
471
+ _ => unreachable!(),
472
+ };
473
+
474
+ #[cfg(feature = "writing-mode")]
475
+ let (intrinsic_sizes, default_object_size) =
476
+ if node.writing_mode().is_vertical() {
477
+ // Intrinsic sizes are physical; the element's algorithm runs in its own writing mode
478
+ (
479
+ crate::layout::replaced::IntrinsicSizes {
480
+ width: intrinsic_sizes.height,
481
+ height: intrinsic_sizes.width,
482
+ ratio: intrinsic_sizes.ratio.map(|ratio| 1.0 / ratio),
483
+ },
484
+ default_object_size.transpose(),
485
+ )
486
+ } else {
487
+ (intrinsic_sizes, default_object_size)
488
+ };
489
+
490
+ let replaced_context = ReplacedContext {
491
+ intrinsic_sizes,
492
+ default_object_size,
493
+ };
494
+
495
+ let mut style = node.layout_style();
496
+ style.set_percent_basis(orthogonal_percent_basis);
497
+ return compute_replaced_layout(
498
+ inputs,
499
+ &style,
500
+ resolve_calc_value,
501
+ &replaced_context,
502
+ );
503
+ }
504
+
505
+ if node.flags.is_table_root() {
506
+ // The space available to an absolutely positioned table never exceeds
507
+ // the size of its containing block (less the table's margins), even
508
+ // when negative insets would otherwise add to it
509
+ // (https://drafts.csswg.org/css-tables-3/#abspos).
510
+ let mut inputs = inputs;
511
+ if self.nodes[dom_node_id(node_id)].is_out_of_flow() {
512
+ let margin = self.nodes[dom_node_id(node_id)]
513
+ .layout_style()
514
+ .margin()
515
+ .resolve_or_zero(inputs.parent_size.width, resolve_calc_value);
516
+ let max_available_space = inputs
517
+ .parent_size
518
+ .maybe_sub(margin.sum_axes())
519
+ .maybe_max(taffy::Size::ZERO);
520
+ inputs.available_space =
521
+ inputs.available_space.maybe_min(max_available_space);
522
+ }
523
+
524
+ let SpecialElementData::TableRoot(context) = &self.nodes[dom_node_id(node_id)]
525
+ .data
526
+ .downcast_element()
527
+ .unwrap()
528
+ .special_data
529
+ else {
530
+ panic!("Node marked as table root but doesn't have TableContext");
531
+ };
532
+ let context = Arc::clone(context);
533
+
534
+ let mut table_wrapper = TableTreeWrapper {
535
+ doc: self,
536
+ ctx: context,
537
+ };
538
+ let mut output = compute_grid_layout(&mut table_wrapper, node_id, inputs);
539
+
540
+ // Absolutely positioned children take no part in the table grid. Their
541
+ // static position is the top-left of the table's content box.
542
+ let context = table_wrapper.ctx;
543
+ if inputs.run_mode == RunMode::PerformLayout && !context.oof_children.is_empty()
544
+ {
545
+ let style = &context.style;
546
+ let parent_width = inputs.parent_size.width;
547
+ let padding = style
548
+ .padding
549
+ .resolve_or_zero(parent_width, resolve_calc_value);
550
+ let border = style
551
+ .border
552
+ .resolve_or_zero(parent_width, resolve_calc_value);
553
+ let static_position = taffy::Point {
554
+ x: padding.left + border.left,
555
+ y: padding.top + border.top,
556
+ }
557
+ .map(|pos| AxisStaticPosition::from_edge(pos, AxisStaticEdge::Start));
558
+ let in_flow_count = context.cells.len();
559
+ for (idx, (child_id, position)) in context.oof_children.iter().enumerate() {
560
+ output.oof_candidates.push(OofCandidate {
561
+ node: taffy_node_id(*child_id),
562
+ order: (in_flow_count + idx) as u32,
563
+ position: *position,
564
+ static_position,
565
+ });
566
+ }
567
+ }
568
+
569
+ // HACK: Cap scrollable overflow at node size to prevent scrolling
570
+ output.scrollable_overflow_rect.left = 0.0;
571
+ output.scrollable_overflow_rect.top = 0.0;
572
+ output.scrollable_overflow_rect.right =
573
+ output.scrollable_overflow_rect.right.min(output.size.width);
574
+ output.scrollable_overflow_rect.bottom = output
575
+ .scrollable_overflow_rect
576
+ .bottom
577
+ .min(output.size.height);
578
+
579
+ return output;
580
+ }
581
+
582
+ if node.flags.is_inline_root() {
583
+ return self.compute_inline_layout(dom_node_id(node_id), inputs, block_ctx);
584
+ }
585
+
586
+ // The default CSS file will set
587
+ match node.taffy_display() {
588
+ Display::Block => compute_block_layout(self, node_id, inputs, block_ctx),
589
+ Display::FlowRoot => compute_block_layout(self, node_id, inputs, None),
590
+ Display::Flex => {
591
+ #[cfg(feature = "writing-mode")]
592
+ {
593
+ self.current_align_axis_is_inline = node.is_column_flex_container();
594
+ }
595
+ compute_flexbox_layout(self, node_id, inputs)
596
+ }
597
+ Display::Grid => compute_grid_layout(self, node_id, inputs),
598
+ Display::None => taffy::LayoutOutput::HIDDEN,
599
+ }
600
+ }
601
+ NodeData::Document(_) => compute_block_layout(self, node_id, inputs, None),
602
+
603
+ _ => taffy::LayoutOutput::HIDDEN,
604
+ }
605
+ }
606
+ }
607
+
608
+ impl TraversePartialTree for LayoutPassState<'_> {
609
+ type ChildIter<'a>
610
+ = RefCellChildIter<'a>
611
+ where
612
+ Self: 'a;
613
+
614
+ fn child_ids(&self, node_id: NodeId) -> Self::ChildIter<'_> {
615
+ let layout_children = self.node_from_id(node_id).layout_children.borrow(); //.unwrap().as_ref();
616
+ RefCellChildIter::new(Ref::map(layout_children, |children| {
617
+ children.as_ref().map(|c| c.as_slice()).unwrap_or(&[])
618
+ }))
619
+ }
620
+
621
+ fn child_count(&self, node_id: NodeId) -> usize {
622
+ self.node_from_id(node_id)
623
+ .layout_children
624
+ .borrow()
625
+ .as_ref()
626
+ .map(|c| c.len())
627
+ .unwrap_or(0)
628
+ }
629
+
630
+ fn get_child_id(&self, node_id: NodeId, index: usize) -> NodeId {
631
+ taffy_node_id(
632
+ self.node_from_id(node_id)
633
+ .layout_children
634
+ .borrow()
635
+ .as_ref()
636
+ .unwrap()[index],
637
+ )
638
+ }
639
+ }
640
+ impl TraverseTree for LayoutPassState<'_> {}
641
+
642
+ impl LayoutPartialTree for LayoutPassState<'_> {
643
+ type CoreContainerStyle<'a>
644
+ = TaffyStyloStyle<ComputedStyleRef<'a>>
645
+ where
646
+ Self: 'a;
647
+
648
+ type CustomIdent = Atom;
649
+
650
+ fn get_core_container_style(&self, node_id: NodeId) -> Self::CoreContainerStyle<'_> {
651
+ #[cfg(feature = "writing-mode")]
652
+ {
653
+ // Container styles are read by the node's own algorithm (or, for the containing block
654
+ // of an out-of-flow box, by that box's algorithm) and are expressed in the node's writing mode.
655
+ let dom_id = dom_node_id(node_id);
656
+ let mut style = self.nodes[dom_id].layout_style_in(self.layout_wm_of(dom_id));
657
+ style.set_percent_basis(self.orthogonal_percent_basis_of(dom_id));
658
+ style
659
+ }
660
+ #[cfg(not(feature = "writing-mode"))]
661
+ {
662
+ self.node_from_id(node_id).layout_style()
663
+ }
664
+ }
665
+
666
+ fn set_unrounded_layout(&mut self, node_id: NodeId, layout: &Layout) {
667
+ *self.node_from_id_mut(node_id).unrounded_layout_mut() = *layout;
668
+ }
669
+
670
+ fn resolve_calc_value(&self, calc_ptr: *const (), parent_size: f32) -> f32 {
671
+ resolve_calc_value(calc_ptr, parent_size)
672
+ }
673
+
674
+ #[inline(always)]
675
+ fn compute_child_layout(
676
+ &mut self,
677
+ node_id: NodeId,
678
+ inputs: taffy::LayoutInput,
679
+ ) -> taffy::LayoutOutput {
680
+ compute_cached_layout(self, node_id, inputs, |tree, node_id, inputs| {
681
+ tree.compute_child_layout_internal(node_id, inputs, None)
682
+ })
683
+ }
684
+ }
685
+
686
+ impl LayoutContainingBlock for LayoutPassState<'_> {
687
+ type OofItemStyle<'a>
688
+ = TaffyStyloStyle<ComputedStyleRef<'a>>
689
+ where
690
+ Self: 'a;
691
+
692
+ fn get_oof_item_style(&self, node_id: NodeId) -> Self::OofItemStyle<'_> {
693
+ self.child_layout_style(self.node_from_id(node_id))
694
+ }
695
+
696
+ fn clear_hoisted_children(&mut self, node_id: NodeId) {
697
+ let containing_block = dom_node_id(node_id);
698
+ let mut hoisted = self.node_from_id(node_id).hoisted_children.borrow_mut();
699
+ for &hoisted_id in hoisted.iter() {
700
+ // A box which has since been claimed by another containing block
701
+ // must keep pointing at that one.
702
+ if let Some(node) = self.nodes.get(hoisted_id) {
703
+ if node.oof_containing_block.get() == Some(containing_block) {
704
+ node.oof_containing_block.set(None);
705
+ }
706
+ }
707
+ }
708
+ hoisted.clear();
709
+ }
710
+
711
+ fn add_hoisted_children(&mut self, node_id: NodeId, hoisted: &[NodeId]) {
712
+ let containing_block = dom_node_id(node_id);
713
+ let node = self.node_from_id(node_id);
714
+ node.hoisted_children
715
+ .borrow_mut()
716
+ .extend(hoisted.iter().copied().map(dom_node_id));
717
+ for &hoisted_id in hoisted {
718
+ self.node_from_id(hoisted_id)
719
+ .oof_containing_block
720
+ .set(Some(containing_block));
721
+ }
722
+ }
723
+
724
+ fn get_detailed_layout_info(&self, node_id: NodeId) -> &DetailedLayoutInfo<Atom> {
725
+ self.node_from_id(node_id)
726
+ .element_data()
727
+ .map(|element| &element.detailed_layout_info)
728
+ .unwrap_or(&DetailedLayoutInfo::None)
729
+ }
730
+ }
731
+
732
+ impl taffy::CacheTree for LayoutPassState<'_> {
733
+ #[inline]
734
+ fn cache_get(
735
+ &mut self,
736
+ node_id: NodeId,
737
+ inputs: &taffy::LayoutInput,
738
+ ) -> Option<taffy::LayoutOutput> {
739
+ self.node_from_id_mut(node_id)
740
+ .try_layout_data_mut()?
741
+ .cache
742
+ .get(inputs)
743
+ }
744
+
745
+ #[inline]
746
+ fn cache_store(
747
+ &mut self,
748
+ node_id: NodeId,
749
+ inputs: &taffy::LayoutInput,
750
+ layout_output: taffy::LayoutOutput,
751
+ ) {
752
+ self.node_from_id_mut(node_id)
753
+ .cache_mut()
754
+ .store(inputs, layout_output);
755
+ }
756
+
757
+ #[inline]
758
+ fn cache_clear(&mut self, node_id: NodeId) {
759
+ self.node_from_id_mut(node_id).clear_layout_cache();
760
+ }
761
+ }
762
+
763
+ impl taffy::LayoutBlockContainer for LayoutPassState<'_> {
764
+ type BlockContainerStyle<'a>
765
+ = TaffyStyloStyle<ComputedStyleRef<'a>>
766
+ where
767
+ Self: 'a;
768
+
769
+ type BlockItemStyle<'a>
770
+ = TaffyStyloStyle<ComputedStyleRef<'a>>
771
+ where
772
+ Self: 'a;
773
+
774
+ fn get_block_container_style(&self, node_id: NodeId) -> Self::BlockContainerStyle<'_> {
775
+ self.get_core_container_style(node_id)
776
+ }
777
+
778
+ fn get_block_child_style(&self, child_node_id: NodeId) -> Self::BlockItemStyle<'_> {
779
+ self.child_layout_style(self.node_from_id(child_node_id))
780
+ }
781
+
782
+ #[inline(always)]
783
+ fn compute_block_child_layout(
784
+ &mut self,
785
+ node_id: NodeId,
786
+ inputs: taffy::LayoutInput,
787
+ block_ctx: Option<&mut BlockContext<'_>>,
788
+ ) -> taffy::LayoutOutput {
789
+ compute_cached_layout(self, node_id, inputs, |tree, node_id, inputs| {
790
+ tree.compute_child_layout_internal(node_id, inputs, block_ctx)
791
+ })
792
+ }
793
+ }
794
+
795
+ impl taffy::LayoutFlexboxContainer for LayoutPassState<'_> {
796
+ type FlexboxContainerStyle<'a>
797
+ = TaffyStyloStyle<ComputedStyleRef<'a>>
798
+ where
799
+ Self: 'a;
800
+
801
+ type FlexboxItemStyle<'a>
802
+ = TaffyStyloStyle<ComputedStyleRef<'a>>
803
+ where
804
+ Self: 'a;
805
+
806
+ fn get_flexbox_container_style(&self, node_id: NodeId) -> Self::FlexboxContainerStyle<'_> {
807
+ self.get_core_container_style(node_id)
808
+ }
809
+
810
+ fn get_flexbox_child_style(&self, child_node_id: NodeId) -> Self::FlexboxItemStyle<'_> {
811
+ self.child_layout_style(self.node_from_id(child_node_id))
812
+ }
813
+ }
814
+
815
+ impl taffy::LayoutGridContainer for LayoutPassState<'_> {
816
+ type GridContainerStyle<'a>
817
+ = TaffyStyloStyle<ComputedStyleRef<'a>>
818
+ where
819
+ Self: 'a;
820
+
821
+ type GridItemStyle<'a>
822
+ = TaffyStyloStyle<ComputedStyleRef<'a>>
823
+ where
824
+ Self: 'a;
825
+
826
+ fn get_grid_container_style(&self, node_id: NodeId) -> Self::GridContainerStyle<'_> {
827
+ self.get_core_container_style(node_id)
828
+ }
829
+
830
+ fn get_grid_child_style(&self, child_node_id: NodeId) -> Self::GridItemStyle<'_> {
831
+ self.child_layout_style(self.node_from_id(child_node_id))
832
+ }
833
+
834
+ fn set_detailed_grid_info(
835
+ &mut self,
836
+ node_id: NodeId,
837
+ detailed_grid_info: taffy::DetailedGridInfo<Atom>,
838
+ ) {
839
+ let node = self.node_from_id_mut(node_id);
840
+ if let Some(element) = node.element_data_mut() {
841
+ element.detailed_layout_info = DetailedLayoutInfo::Grid(Box::new(detailed_grid_info));
842
+ }
843
+ }
844
+ }
845
+
846
+ impl RoundTree for LayoutPassState<'_> {
847
+ fn get_unrounded_layout(&self, node_id: NodeId) -> Layout {
848
+ *self.node_from_id(node_id).unrounded_layout()
849
+ }
850
+
851
+ fn set_final_layout(&mut self, node_id: NodeId, layout: &Layout) {
852
+ *self.node_from_id_mut(node_id).final_layout_mut() = *layout;
853
+ }
854
+
855
+ fn is_out_of_flow(&self, node_id: NodeId) -> bool {
856
+ self.node_from_id(node_id).is_out_of_flow()
857
+ }
858
+
859
+ fn hoisted_child_count(&self, node_id: NodeId) -> usize {
860
+ self.node_from_id(node_id).hoisted_children.borrow().len()
861
+ }
862
+
863
+ fn get_hoisted_child_id(&self, node_id: NodeId, index: usize) -> NodeId {
864
+ taffy_node_id(self.node_from_id(node_id).hoisted_children.borrow()[index])
865
+ }
866
+ }
867
+
868
+ pub(crate) struct TaffyDebugTree<'doc>(pub(crate) &'doc BaseDocument);
869
+
870
+ impl TaffyDebugTree<'_> {
871
+ fn node_from_id(&self, node_id: NodeId) -> &Node {
872
+ &self.0.nodes[dom_node_id(node_id)]
873
+ }
874
+ }
875
+
876
+ impl TraversePartialTree for TaffyDebugTree<'_> {
877
+ type ChildIter<'a>
878
+ = RefCellChildIter<'a>
879
+ where
880
+ Self: 'a;
881
+
882
+ fn child_ids(&self, node_id: NodeId) -> Self::ChildIter<'_> {
883
+ let layout_children = self.node_from_id(node_id).layout_children.borrow();
884
+ RefCellChildIter::new(Ref::map(layout_children, |children| {
885
+ children.as_ref().map(|c| c.as_slice()).unwrap_or(&[])
886
+ }))
887
+ }
888
+
889
+ fn child_count(&self, node_id: NodeId) -> usize {
890
+ self.node_from_id(node_id)
891
+ .layout_children
892
+ .borrow()
893
+ .as_ref()
894
+ .map(|c| c.len())
895
+ .unwrap_or(0)
896
+ }
897
+
898
+ fn get_child_id(&self, node_id: NodeId, index: usize) -> NodeId {
899
+ taffy_node_id(
900
+ self.node_from_id(node_id)
901
+ .layout_children
902
+ .borrow()
903
+ .as_ref()
904
+ .unwrap()[index],
905
+ )
906
+ }
907
+ }
908
+
909
+ impl TraverseTree for TaffyDebugTree<'_> {}
910
+
911
+ impl PrintTree for TaffyDebugTree<'_> {
912
+ fn get_debug_label(&self, node_id: NodeId) -> &'static str {
913
+ let node = &self.node_from_id(node_id);
914
+
915
+ match node.data {
916
+ NodeData::Document(_) => "DOCUMENT",
917
+ // NodeData::Doctype { .. } => return "DOCTYPE",
918
+ NodeData::Text { .. } => node.node_debug_str().leak(),
919
+ NodeData::Comment { .. } => "COMMENT",
920
+ NodeData::AnonymousBlock(_) => "ANONYMOUS BLOCK",
921
+ NodeData::Element(_) => {
922
+ let style = node.layout_style();
923
+ let display = match node.taffy_display() {
924
+ Display::Flex => match taffy::FlexboxContainerStyle::flex_direction(&style) {
925
+ FlexDirection::Row | FlexDirection::RowReverse => "FLEX ROW",
926
+ FlexDirection::Column | FlexDirection::ColumnReverse => "FLEX COL",
927
+ },
928
+ Display::Grid => "GRID",
929
+ Display::Block => "BLOCK",
930
+ Display::FlowRoot => "FLOW ROOT",
931
+ Display::None => "NONE",
932
+ };
933
+ format!("{} ({})", node.node_debug_str(), display).leak()
934
+ } // NodeData::ProcessingInstruction { .. } => return "PROCESSING INSTRUCTION",
935
+ }
936
+ }
937
+
938
+ fn get_final_layout(&self, node_id: NodeId) -> Layout {
939
+ *self.node_from_id(node_id).final_layout()
940
+ }
941
+ }
942
+
943
+ // pub struct ChildIter<'a>(std::slice::Iter<'a, usize>);
944
+ // impl<'a> Iterator for ChildIter<'a> {
945
+ // type Item = NodeId;
946
+ // fn next(&mut self) -> Option<Self::Item> {
947
+ // self.0.next().copied().map(NodeId::from)
948
+ // }
949
+ // }
950
+
951
+ pub struct RefCellChildIter<'a> {
952
+ items: Ref<'a, [crate::NodeId]>,
953
+ idx: usize,
954
+ }
955
+ impl<'a> RefCellChildIter<'a> {
956
+ fn new(items: Ref<'a, [crate::NodeId]>) -> RefCellChildIter<'a> {
957
+ RefCellChildIter { items, idx: 0 }
958
+ }
959
+ }
960
+
961
+ impl Iterator for RefCellChildIter<'_> {
962
+ type Item = NodeId;
963
+ fn next(&mut self) -> Option<Self::Item> {
964
+ self.items.get(self.idx).map(|id| {
965
+ self.idx += 1;
966
+ taffy_node_id(*id)
967
+ })
968
+ }
969
+ }