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.
- package/README.md +26 -0
- package/THIRD_PARTY_NOTICES.md +5 -2
- package/docs/layout-regressions.md +65 -0
- package/docs/pagination-regressions.md +89 -0
- package/package.json +2 -1
- package/vendor/README.md +35 -0
- package/vendor/blitz-dom/Cargo.toml +113 -0
- package/vendor/blitz-dom/LICENSE-APACHE +176 -0
- package/vendor/blitz-dom/LICENSE-MIT +23 -0
- package/vendor/blitz-dom/assets/default.css +1142 -0
- package/vendor/blitz-dom/assets/moz-bullet-font.otf +0 -0
- package/vendor/blitz-dom/src/accessibility.rs +255 -0
- package/vendor/blitz-dom/src/config.rs +97 -0
- package/vendor/blitz-dom/src/cssom.rs +971 -0
- package/vendor/blitz-dom/src/debug.rs +206 -0
- package/vendor/blitz-dom/src/document.rs +3395 -0
- package/vendor/blitz-dom/src/events/driver.rs +427 -0
- package/vendor/blitz-dom/src/events/focus.rs +40 -0
- package/vendor/blitz-dom/src/events/ime.rs +31 -0
- package/vendor/blitz-dom/src/events/keyboard.rs +174 -0
- package/vendor/blitz-dom/src/events/mod.rs +315 -0
- package/vendor/blitz-dom/src/events/pointer.rs +849 -0
- package/vendor/blitz-dom/src/font_metrics.rs +177 -0
- package/vendor/blitz-dom/src/form.rs +463 -0
- package/vendor/blitz-dom/src/html.rs +38 -0
- package/vendor/blitz-dom/src/iframe.rs +186 -0
- package/vendor/blitz-dom/src/layout/construct.rs +1328 -0
- package/vendor/blitz-dom/src/layout/damage.rs +642 -0
- package/vendor/blitz-dom/src/layout/inline.rs +1160 -0
- package/vendor/blitz-dom/src/layout/list.rs +237 -0
- package/vendor/blitz-dom/src/layout/mod.rs +969 -0
- package/vendor/blitz-dom/src/layout/paint_tree.rs +462 -0
- package/vendor/blitz-dom/src/layout/replaced.rs +439 -0
- package/vendor/blitz-dom/src/layout/table.rs +967 -0
- package/vendor/blitz-dom/src/layout/text_transform.rs +979 -0
- package/vendor/blitz-dom/src/layout/writing_mode.rs +437 -0
- package/vendor/blitz-dom/src/lib.rs +160 -0
- package/vendor/blitz-dom/src/mutator.rs +1818 -0
- package/vendor/blitz-dom/src/net.rs +649 -0
- package/vendor/blitz-dom/src/node/attributes.rs +60 -0
- package/vendor/blitz-dom/src/node/custom_widget.rs +178 -0
- package/vendor/blitz-dom/src/node/element.rs +1106 -0
- package/vendor/blitz-dom/src/node/mod.rs +30 -0
- package/vendor/blitz-dom/src/node/node.rs +2036 -0
- package/vendor/blitz-dom/src/node/scrollbar.rs +237 -0
- package/vendor/blitz-dom/src/node/serialize.rs +225 -0
- package/vendor/blitz-dom/src/node/stylo_data.rs +142 -0
- package/vendor/blitz-dom/src/node/svg.rs +265 -0
- package/vendor/blitz-dom/src/node/text.rs +804 -0
- package/vendor/blitz-dom/src/query_selector.rs +275 -0
- package/vendor/blitz-dom/src/resolve.rs +474 -0
- package/vendor/blitz-dom/src/resolved_style.rs +797 -0
- package/vendor/blitz-dom/src/scrolling.rs +780 -0
- package/vendor/blitz-dom/src/selection.rs +122 -0
- package/vendor/blitz-dom/src/stylo.rs +1518 -0
- package/vendor/blitz-dom/src/stylo_device.rs +88 -0
- package/vendor/blitz-dom/src/stylo_to_cursor_icon.rs +49 -0
- package/vendor/blitz-dom/src/stylo_to_kurbo.rs +129 -0
- package/vendor/blitz-dom/src/stylo_to_parley.rs +655 -0
- package/vendor/blitz-dom/src/traversal.rs +600 -0
- package/vendor/blitz-dom/src/tree.rs +136 -0
- package/vendor/blitz-dom/src/url.rs +63 -0
- package/vendor/blitz-dom/src/util.rs +256 -0
- package/wasm/blitz_pdf_bg.wasm +0 -0
|
@@ -0,0 +1,1160 @@
|
|
|
1
|
+
use blitz_traits::node_id::NodeId;
|
|
2
|
+
use parley::{AlignmentOptions, BreakReason, IndentOptions};
|
|
3
|
+
use style::values::specified::box_::{DisplayInside, DisplayOutside};
|
|
4
|
+
use style::values::{
|
|
5
|
+
computed::{BaselineSource, CSSPixelLength, Contain},
|
|
6
|
+
generics::text::GenericTextIndent,
|
|
7
|
+
};
|
|
8
|
+
use taffy::{
|
|
9
|
+
AvailableSpace, AxisStaticPosition, BlockContainerStyle, BlockContext, BlockFormattingContext,
|
|
10
|
+
BoxSizing, CollapsibleMarginSet, CompactLength, CoreStyle as _, Direction, LayoutInput,
|
|
11
|
+
LayoutOutput, LayoutPartialTree as _, MaybeMath as _, MaybeResolve as _, OofCandidate,
|
|
12
|
+
OofCandidates, OofItemStyle, OofPositioningArea, Overflow, Point, RequestedAxis,
|
|
13
|
+
ResolveOrZero as _, RunMode, Size, SizingMode,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
#[cfg(feature = "floats")]
|
|
17
|
+
use parley::YieldData;
|
|
18
|
+
#[cfg(feature = "floats")]
|
|
19
|
+
use taffy::{BlockItemStyle as _, Clear, Float, prelude::TaffyMaxContent};
|
|
20
|
+
|
|
21
|
+
use stylo_taffy::StyleFlags;
|
|
22
|
+
|
|
23
|
+
use super::resolve_calc_value;
|
|
24
|
+
use crate::layout::LayoutPassState;
|
|
25
|
+
use crate::stylo_to_parley;
|
|
26
|
+
|
|
27
|
+
/// Subtract a child's margins from the definite axes of the available space it is laid out in.
|
|
28
|
+
/// Taffy's convention is that the parent subtracts a child's margins from the available space
|
|
29
|
+
/// before passing it to the child, and that the child does not subtract them again.
|
|
30
|
+
fn subtract_margins(mut child_inputs: LayoutInput, margin: taffy::Rect<f32>) -> LayoutInput {
|
|
31
|
+
child_inputs.available_space = child_inputs
|
|
32
|
+
.available_space
|
|
33
|
+
.maybe_sub(margin.sum_axes())
|
|
34
|
+
.maybe_max(Size::ZERO);
|
|
35
|
+
child_inputs
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/// Layout inputs for an atomic inline box, with any sizing keyword on its `width` style
|
|
39
|
+
/// (`min-content`, `max-content`, `fit-content`, `fit-content(...)`, `stretch`) resolved
|
|
40
|
+
/// into the available space or known width the box is measured with.
|
|
41
|
+
fn inline_box_inputs(
|
|
42
|
+
width_style: taffy::Dimension,
|
|
43
|
+
margin: taffy::Rect<f32>,
|
|
44
|
+
child_inputs: LayoutInput,
|
|
45
|
+
) -> LayoutInput {
|
|
46
|
+
let mut inputs = subtract_margins(child_inputs, margin);
|
|
47
|
+
let stretch_width = inputs.available_space.width.into_option();
|
|
48
|
+
let percent_basis = child_inputs.parent_size.width;
|
|
49
|
+
|
|
50
|
+
match width_style.tag() {
|
|
51
|
+
CompactLength::MIN_CONTENT_TAG => inputs.available_space.width = AvailableSpace::MinContent,
|
|
52
|
+
CompactLength::MAX_CONTENT_TAG => inputs.available_space.width = AvailableSpace::MaxContent,
|
|
53
|
+
CompactLength::FIT_CONTENT_PX_TAG => {
|
|
54
|
+
inputs.available_space.width = AvailableSpace::Definite(width_style.value())
|
|
55
|
+
}
|
|
56
|
+
CompactLength::FIT_CONTENT_PERCENT_TAG => {
|
|
57
|
+
if let Some(basis) = percent_basis {
|
|
58
|
+
inputs.available_space.width =
|
|
59
|
+
AvailableSpace::Definite(basis * width_style.value());
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
CompactLength::FIT_CONTENT_KEYWORD_TAG => {
|
|
63
|
+
if let Some(width) = stretch_width {
|
|
64
|
+
inputs.available_space.width = AvailableSpace::Definite(width);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
_ if width_style.is_fit_content_calc() => {
|
|
68
|
+
if let Some(basis) = percent_basis {
|
|
69
|
+
inputs.available_space.width =
|
|
70
|
+
AvailableSpace::Definite(resolve_calc_value(width_style.calc_value(), basis));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
CompactLength::STRETCH_TAG => {
|
|
74
|
+
if let Some(width) = stretch_width {
|
|
75
|
+
inputs.known_dimensions.width = Some(width);
|
|
76
|
+
inputs.available_space.width = AvailableSpace::Definite(width);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
_ => {}
|
|
80
|
+
}
|
|
81
|
+
inputs
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
impl LayoutPassState<'_> {
|
|
85
|
+
pub(crate) fn compute_inline_layout(
|
|
86
|
+
&mut self,
|
|
87
|
+
node_id: NodeId,
|
|
88
|
+
inputs: taffy::tree::LayoutInput,
|
|
89
|
+
block_ctx: Option<&mut BlockContext<'_>>,
|
|
90
|
+
) -> taffy::LayoutOutput {
|
|
91
|
+
let LayoutInput {
|
|
92
|
+
known_dimensions,
|
|
93
|
+
parent_size,
|
|
94
|
+
run_mode,
|
|
95
|
+
..
|
|
96
|
+
} = inputs;
|
|
97
|
+
let style = self.nodes[node_id].layout_style();
|
|
98
|
+
|
|
99
|
+
// Pull these out earlier to avoid borrowing issues
|
|
100
|
+
let is_scroll_container =
|
|
101
|
+
style.overflow().x.is_scroll_container() || style.overflow().y.is_scroll_container();
|
|
102
|
+
let padding = style
|
|
103
|
+
.padding()
|
|
104
|
+
.resolve_or_zero(parent_size.width, resolve_calc_value);
|
|
105
|
+
let border = style
|
|
106
|
+
.border()
|
|
107
|
+
.resolve_or_zero(parent_size.width, resolve_calc_value);
|
|
108
|
+
let padding_border_size = (padding + border).sum_axes();
|
|
109
|
+
let box_sizing_adjustment = if style.box_sizing() == BoxSizing::ContentBox {
|
|
110
|
+
padding_border_size
|
|
111
|
+
} else {
|
|
112
|
+
Size::ZERO
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// Resolve node's preferred/min/max sizes (width/heights) against the available space (percentages resolve to pixel values)
|
|
116
|
+
// For ContentSize mode, we pretend that the node has no size styles as these should be ignored.
|
|
117
|
+
let (clamped_style_size, min_size, max_size, _aspect_ratio) = match inputs.sizing_mode {
|
|
118
|
+
SizingMode::ContentSize => {
|
|
119
|
+
let node_size = known_dimensions;
|
|
120
|
+
let node_min_size = Size::NONE;
|
|
121
|
+
let node_max_size = Size::NONE;
|
|
122
|
+
(node_size, node_min_size, node_max_size, None)
|
|
123
|
+
}
|
|
124
|
+
SizingMode::InherentSize => {
|
|
125
|
+
let aspect_ratio = style.aspect_ratio();
|
|
126
|
+
let style_size = style
|
|
127
|
+
.size()
|
|
128
|
+
.maybe_resolve(parent_size, resolve_calc_value)
|
|
129
|
+
.maybe_apply_aspect_ratio(aspect_ratio)
|
|
130
|
+
.maybe_add(box_sizing_adjustment);
|
|
131
|
+
let style_min_size = style
|
|
132
|
+
.min_size()
|
|
133
|
+
.maybe_resolve(parent_size, resolve_calc_value)
|
|
134
|
+
.maybe_apply_aspect_ratio(aspect_ratio)
|
|
135
|
+
.maybe_add(box_sizing_adjustment);
|
|
136
|
+
let style_max_size = style
|
|
137
|
+
.max_size()
|
|
138
|
+
.maybe_resolve(parent_size, resolve_calc_value)
|
|
139
|
+
.maybe_add(box_sizing_adjustment);
|
|
140
|
+
|
|
141
|
+
let node_size =
|
|
142
|
+
known_dimensions.or(style_size.maybe_clamp(style_min_size, style_max_size));
|
|
143
|
+
(node_size, style_min_size, style_max_size, aspect_ratio)
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// If both min and max in a given axis are set and max <= min then this determines the size in that axis
|
|
148
|
+
let min_max_definite_size = min_size.zip_map(max_size, |min, max| match (min, max) {
|
|
149
|
+
(Some(min), Some(max)) if max <= min => Some(min),
|
|
150
|
+
_ => None,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
let styled_based_known_dimensions = known_dimensions
|
|
154
|
+
.or(min_max_definite_size)
|
|
155
|
+
.or(clamped_style_size)
|
|
156
|
+
.maybe_max(padding_border_size);
|
|
157
|
+
|
|
158
|
+
// Short-circuit layout if the container's size is fully determined by the container's size and the run mode
|
|
159
|
+
// is ComputeSize (and thus the container's size is all that we're interested in).
|
|
160
|
+
// A zero-height inline context must still compute its collapsing margins;
|
|
161
|
+
// from_outer_size would incorrectly prevent collapse through an empty span.
|
|
162
|
+
if run_mode == RunMode::ComputeSize {
|
|
163
|
+
if let Size {
|
|
164
|
+
width: Some(width),
|
|
165
|
+
height: Some(height),
|
|
166
|
+
} = styled_based_known_dimensions
|
|
167
|
+
&& height > 0.0
|
|
168
|
+
{
|
|
169
|
+
return LayoutOutput::from_outer_size(Size { width, height });
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
drop(style);
|
|
174
|
+
|
|
175
|
+
// Unwrap the block formatting context if one was passed, or else create a new one
|
|
176
|
+
match block_ctx {
|
|
177
|
+
Some(inherited_bfc) if !is_scroll_container => self.compute_inline_layout_inner(
|
|
178
|
+
node_id,
|
|
179
|
+
LayoutInput {
|
|
180
|
+
known_dimensions: styled_based_known_dimensions,
|
|
181
|
+
..inputs
|
|
182
|
+
},
|
|
183
|
+
inherited_bfc,
|
|
184
|
+
),
|
|
185
|
+
_ => {
|
|
186
|
+
let mut root_bfc = BlockFormattingContext::new();
|
|
187
|
+
let mut root_ctx = root_bfc.root_block_context();
|
|
188
|
+
self.compute_inline_layout_inner(
|
|
189
|
+
node_id,
|
|
190
|
+
LayoutInput {
|
|
191
|
+
known_dimensions: styled_based_known_dimensions,
|
|
192
|
+
..inputs
|
|
193
|
+
},
|
|
194
|
+
&mut root_ctx,
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/// The out-of-flow positions for which an inline span between the inline box `box_id` and its
|
|
201
|
+
/// inline root `root_id` is a containing block (as `SPAN_*_CB` flags)
|
|
202
|
+
fn inline_span_cb_flags(&self, root_id: NodeId, box_id: NodeId) -> StyleFlags {
|
|
203
|
+
let root = &self.nodes[root_id];
|
|
204
|
+
// The spans of an anonymous inline root are descendants of its parent
|
|
205
|
+
let root_parent = root.is_anonymous().then_some(root.parent).flatten();
|
|
206
|
+
|
|
207
|
+
let mut flags = StyleFlags::empty();
|
|
208
|
+
let mut current = self.nodes[box_id].parent;
|
|
209
|
+
while let Some(id) = current {
|
|
210
|
+
if id == root_id || Some(id) == root_parent {
|
|
211
|
+
return flags;
|
|
212
|
+
}
|
|
213
|
+
let ancestor = &self.nodes[id];
|
|
214
|
+
if let Some(style) = ancestor.primary_styles() {
|
|
215
|
+
let display = style.clone_display();
|
|
216
|
+
if display.outside() == DisplayOutside::Inline
|
|
217
|
+
&& display.inside() == DisplayInside::Flow
|
|
218
|
+
{
|
|
219
|
+
let claims = stylo_taffy::convert::inline_containing_block_claims(&style);
|
|
220
|
+
if claims.absolute {
|
|
221
|
+
flags |= StyleFlags::SPAN_ABSOLUTE_CB;
|
|
222
|
+
}
|
|
223
|
+
if claims.fixed {
|
|
224
|
+
flags |= StyleFlags::SPAN_FIXED_CB;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
current = ancestor.parent;
|
|
229
|
+
}
|
|
230
|
+
StyleFlags::empty()
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
fn compute_inline_layout_inner(
|
|
234
|
+
&mut self,
|
|
235
|
+
node_id: NodeId,
|
|
236
|
+
inputs: taffy::tree::LayoutInput,
|
|
237
|
+
block_ctx: &mut BlockContext<'_>,
|
|
238
|
+
) -> taffy::LayoutOutput {
|
|
239
|
+
let scale = self.viewport.scale();
|
|
240
|
+
let LayoutInput {
|
|
241
|
+
known_dimensions,
|
|
242
|
+
parent_size,
|
|
243
|
+
available_space,
|
|
244
|
+
sizing_mode,
|
|
245
|
+
..
|
|
246
|
+
} = inputs;
|
|
247
|
+
|
|
248
|
+
// Take inline layout to satisfy borrow checker
|
|
249
|
+
let mut inline_layout = self.nodes[node_id]
|
|
250
|
+
.data
|
|
251
|
+
.downcast_element_mut()
|
|
252
|
+
.unwrap()
|
|
253
|
+
.take_inline_layout()
|
|
254
|
+
.unwrap();
|
|
255
|
+
|
|
256
|
+
let style = self.nodes[node_id].layout_style();
|
|
257
|
+
|
|
258
|
+
// Note: both horizontal and vertical percentage padding/borders are resolved against the container's inline size (i.e. width).
|
|
259
|
+
// This is not a bug, but is how CSS is specified (see: https://developer.mozilla.org/en-US/docs/Web/CSS/padding#values)
|
|
260
|
+
let padding = style
|
|
261
|
+
.padding()
|
|
262
|
+
.resolve_or_zero(parent_size.width, resolve_calc_value);
|
|
263
|
+
let border = style
|
|
264
|
+
.border()
|
|
265
|
+
.resolve_or_zero(parent_size.width, resolve_calc_value);
|
|
266
|
+
let container_pb = padding + border;
|
|
267
|
+
let pb_sum = container_pb.sum_axes();
|
|
268
|
+
let box_sizing_adjustment = if style.box_sizing() == BoxSizing::ContentBox {
|
|
269
|
+
pb_sum
|
|
270
|
+
} else {
|
|
271
|
+
Size::ZERO
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
// Scrollbar gutters are reserved when the `overflow` property is set to `Overflow::Scroll`.
|
|
275
|
+
// However, the axis are switched (transposed) because a node that scrolls vertically needs
|
|
276
|
+
// *horizontal* space to be reserved for a scrollbar
|
|
277
|
+
let scrollbar_gutter = style.overflow().transpose().map(|overflow| match overflow {
|
|
278
|
+
Overflow::Scroll => style.scrollbar_width(),
|
|
279
|
+
_ => 0.0,
|
|
280
|
+
});
|
|
281
|
+
// TODO: make side configurable based on the `direction` property
|
|
282
|
+
let mut content_box_inset = container_pb;
|
|
283
|
+
content_box_inset.right += scrollbar_gutter.x;
|
|
284
|
+
content_box_inset.bottom += scrollbar_gutter.y;
|
|
285
|
+
|
|
286
|
+
let has_styles_preventing_being_collapsed_through = !style.is_block()
|
|
287
|
+
|| style.overflow().x.is_scroll_container()
|
|
288
|
+
|| style.overflow().y.is_scroll_container()
|
|
289
|
+
|| style.position().is_out_of_flow()
|
|
290
|
+
|| padding.top > 0.0
|
|
291
|
+
|| padding.bottom > 0.0
|
|
292
|
+
|| border.top > 0.0
|
|
293
|
+
|| border.bottom > 0.0;
|
|
294
|
+
// || matches!(node_size.height, Some(h) if h > 0.0)
|
|
295
|
+
// || matches!(node_min_size.height, Some(h) if h > 0.0)
|
|
296
|
+
// || !inline_layout.text.is_empty();
|
|
297
|
+
// || inline_layout.layout.inline_boxes().len() > 0;
|
|
298
|
+
|
|
299
|
+
// Resolve node's preferred/min/max sizes (width/heights) against the available space (percentages resolve to pixel values)
|
|
300
|
+
// For ContentSize mode, we pretend that the node has no size styles as these should be ignored.
|
|
301
|
+
let (node_size, node_min_size, node_max_size, aspect_ratio) = match sizing_mode {
|
|
302
|
+
SizingMode::ContentSize => {
|
|
303
|
+
let node_size = known_dimensions;
|
|
304
|
+
let node_min_size = Size::NONE;
|
|
305
|
+
let node_max_size = Size::NONE;
|
|
306
|
+
(node_size, node_min_size, node_max_size, None)
|
|
307
|
+
}
|
|
308
|
+
SizingMode::InherentSize => {
|
|
309
|
+
let aspect_ratio = style.aspect_ratio();
|
|
310
|
+
let style_size = style
|
|
311
|
+
.size()
|
|
312
|
+
.maybe_resolve(parent_size, resolve_calc_value)
|
|
313
|
+
.maybe_apply_aspect_ratio(aspect_ratio)
|
|
314
|
+
.maybe_add(box_sizing_adjustment);
|
|
315
|
+
let style_min_size = style
|
|
316
|
+
.min_size()
|
|
317
|
+
.maybe_resolve(parent_size, resolve_calc_value)
|
|
318
|
+
.maybe_apply_aspect_ratio(aspect_ratio)
|
|
319
|
+
.maybe_add(box_sizing_adjustment);
|
|
320
|
+
let style_max_size = style
|
|
321
|
+
.max_size()
|
|
322
|
+
.maybe_resolve(parent_size, resolve_calc_value)
|
|
323
|
+
.maybe_add(box_sizing_adjustment);
|
|
324
|
+
|
|
325
|
+
let node_size =
|
|
326
|
+
known_dimensions.or(style_size.maybe_clamp(style_min_size, style_max_size));
|
|
327
|
+
(node_size, style_min_size, style_max_size, aspect_ratio)
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
let suppresses_baseline = style.contain().suppresses_baseline();
|
|
332
|
+
drop(style);
|
|
333
|
+
|
|
334
|
+
// Compute available space
|
|
335
|
+
let available_space = Size {
|
|
336
|
+
width: known_dimensions
|
|
337
|
+
.width
|
|
338
|
+
.map(AvailableSpace::from)
|
|
339
|
+
.unwrap_or(available_space.width)
|
|
340
|
+
.maybe_set(known_dimensions.width)
|
|
341
|
+
.maybe_set(node_size.width)
|
|
342
|
+
.map_definite_value(|size| {
|
|
343
|
+
(size.maybe_clamp(node_min_size.width, node_max_size.width)
|
|
344
|
+
- content_box_inset.horizontal_axis_sum())
|
|
345
|
+
.max(0.0)
|
|
346
|
+
}),
|
|
347
|
+
height: known_dimensions
|
|
348
|
+
.height
|
|
349
|
+
.map(AvailableSpace::from)
|
|
350
|
+
.unwrap_or(available_space.height)
|
|
351
|
+
.maybe_set(known_dimensions.height)
|
|
352
|
+
.maybe_set(node_size.height)
|
|
353
|
+
.map_definite_value(|size| {
|
|
354
|
+
(size.maybe_clamp(node_min_size.height, node_max_size.height)
|
|
355
|
+
- content_box_inset.vertical_axis_sum())
|
|
356
|
+
.max(0.0)
|
|
357
|
+
}),
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
// Compute size of inline boxes
|
|
361
|
+
let child_inputs = taffy::tree::LayoutInput {
|
|
362
|
+
known_dimensions: Size::NONE,
|
|
363
|
+
available_space,
|
|
364
|
+
sizing_mode: SizingMode::InherentSize,
|
|
365
|
+
parent_size: Size {
|
|
366
|
+
width: available_space.width.into_option(),
|
|
367
|
+
// Anonymous blocks do not establish the containing block for percentages.
|
|
368
|
+
height: if self.nodes[node_id].is_anonymous() {
|
|
369
|
+
parent_size.height
|
|
370
|
+
} else {
|
|
371
|
+
available_space.height.into_option()
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
// Atomic inlines (e.g. inline-block) establish independent formatting
|
|
375
|
+
// contexts: their margins never collapse with their children's margins.
|
|
376
|
+
vertical_margins_are_collapsible: taffy::Line::FALSE,
|
|
377
|
+
..inputs
|
|
378
|
+
};
|
|
379
|
+
#[cfg(feature = "floats")]
|
|
380
|
+
let float_child_inputs = taffy::tree::LayoutInput {
|
|
381
|
+
available_space: Size::MAX_CONTENT,
|
|
382
|
+
..child_inputs
|
|
383
|
+
};
|
|
384
|
+
|
|
385
|
+
// Update inline boxes
|
|
386
|
+
for ibox in inline_layout.layout.inline_boxes_mut() {
|
|
387
|
+
let style = self.child_layout_style(&self.nodes[NodeId::from_u64(ibox.id)]);
|
|
388
|
+
let margin = style
|
|
389
|
+
.margin()
|
|
390
|
+
.resolve_or_zero(inputs.parent_size, resolve_calc_value);
|
|
391
|
+
|
|
392
|
+
#[cfg(feature = "floats")]
|
|
393
|
+
let is_floated = style.float().is_floated();
|
|
394
|
+
#[cfg(not(feature = "floats"))]
|
|
395
|
+
let is_floated = false;
|
|
396
|
+
|
|
397
|
+
let is_out_of_flow = style.position().is_out_of_flow();
|
|
398
|
+
// `baseline-source: auto` selects the last baseline for inline-blocks and the first
|
|
399
|
+
// for other atomic inlines. A block-axis scroll-container inline-block (except buttons)
|
|
400
|
+
// instead uses its bottom margin edge, but only with `auto` (css-align-3 §9.1).
|
|
401
|
+
// `overflow: clip` is not a scroll container. Exported scroll-container baselines
|
|
402
|
+
// are clamped to the border box.
|
|
403
|
+
// A layout-contained box is treated as having no baseline (css-contain-1 §3.3).
|
|
404
|
+
let overflow = style.overflow();
|
|
405
|
+
let box_style = style.style.get_box();
|
|
406
|
+
let is_flow = matches!(
|
|
407
|
+
style.effective_display().inside(),
|
|
408
|
+
DisplayInside::Flow | DisplayInside::FlowRoot
|
|
409
|
+
);
|
|
410
|
+
let baseline_source = box_style.clone_baseline_source();
|
|
411
|
+
let uses_last_baseline = match baseline_source {
|
|
412
|
+
BaselineSource::Auto => is_flow,
|
|
413
|
+
BaselineSource::First => false,
|
|
414
|
+
BaselineSource::Last => true,
|
|
415
|
+
};
|
|
416
|
+
let is_scroll_container = !matches!(overflow.y, Overflow::Visible | Overflow::Clip);
|
|
417
|
+
let is_block_axis_scroll_container = is_flow
|
|
418
|
+
&& is_scroll_container
|
|
419
|
+
&& !style
|
|
420
|
+
.flags
|
|
421
|
+
.intersects(StyleFlags::IS_BUTTON | StyleFlags::IS_BUTTON_INPUT);
|
|
422
|
+
let contain_layout = box_style.clone_contain().contains(Contain::LAYOUT);
|
|
423
|
+
let exports_baseline = !(baseline_source == BaselineSource::Auto
|
|
424
|
+
&& is_block_axis_scroll_container)
|
|
425
|
+
&& !contain_layout;
|
|
426
|
+
let box_inputs = inline_box_inputs(style.size().width, margin, child_inputs);
|
|
427
|
+
drop(style);
|
|
428
|
+
|
|
429
|
+
if is_out_of_flow || is_floated {
|
|
430
|
+
ibox.width = 0.0;
|
|
431
|
+
ibox.height = 0.0;
|
|
432
|
+
ibox.baseline = None;
|
|
433
|
+
} else {
|
|
434
|
+
let output = self.compute_child_layout(taffy::NodeId::from(ibox.id), box_inputs);
|
|
435
|
+
ibox.width = (margin.left + margin.right + output.size.width) * scale;
|
|
436
|
+
ibox.baseline = if exports_baseline {
|
|
437
|
+
let baseline = if uses_last_baseline {
|
|
438
|
+
output.baselines.last.or(output.baselines.first)
|
|
439
|
+
} else {
|
|
440
|
+
output.baselines.first.or(output.baselines.last)
|
|
441
|
+
};
|
|
442
|
+
baseline.map(|baseline| {
|
|
443
|
+
let baseline = if is_scroll_container {
|
|
444
|
+
baseline.clamp(0.0, output.size.height)
|
|
445
|
+
} else {
|
|
446
|
+
baseline
|
|
447
|
+
};
|
|
448
|
+
(margin.top + baseline) * scale
|
|
449
|
+
})
|
|
450
|
+
} else {
|
|
451
|
+
None
|
|
452
|
+
};
|
|
453
|
+
// Vertical margins adjust the space the box reserves in the line. A box with a
|
|
454
|
+
// baseline splits that space into ascent (`margin.top + baseline`) and descent
|
|
455
|
+
// (`margin.bottom + height - baseline`), either of which may be negative. A box
|
|
456
|
+
// without a baseline sits on the baseline and cannot reserve negative space.
|
|
457
|
+
// Kept finite: huge author lengths can sum to infinity, which is taller than
|
|
458
|
+
// the line breaker's `f32::MAX` height limit, and it then yields
|
|
459
|
+
// `MaxHeightExceeded` for this box forever without advancing.
|
|
460
|
+
let margin_box_height = margin.top + margin.bottom + output.size.height;
|
|
461
|
+
ibox.height = if ibox.baseline.is_some() {
|
|
462
|
+
(margin_box_height * scale).min(f32::MAX)
|
|
463
|
+
} else {
|
|
464
|
+
(margin_box_height.max(0.0) * scale).min(f32::MAX)
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
let text_indent = self.nodes[node_id]
|
|
470
|
+
.primary_styles()
|
|
471
|
+
.map(|s| s.clone_text_indent())
|
|
472
|
+
.unwrap_or_else(GenericTextIndent::zero);
|
|
473
|
+
let indent_options = IndentOptions {
|
|
474
|
+
each_line: text_indent.each_line,
|
|
475
|
+
hanging: text_indent.hanging,
|
|
476
|
+
};
|
|
477
|
+
// Percentage indents do not contribute to intrinsic widths.
|
|
478
|
+
inline_layout.layout.set_text_indent(
|
|
479
|
+
text_indent.length.resolve(CSSPixelLength::new(0.0)).px() * scale,
|
|
480
|
+
indent_options,
|
|
481
|
+
);
|
|
482
|
+
|
|
483
|
+
let pbw = container_pb.horizontal_components().sum() * scale;
|
|
484
|
+
let width = known_dimensions
|
|
485
|
+
.width
|
|
486
|
+
.map(|w| (w * scale) - pbw)
|
|
487
|
+
.unwrap_or_else(|| {
|
|
488
|
+
// TODO: Cache content widths.
|
|
489
|
+
//
|
|
490
|
+
// This is a little tricky as the size of the inline boxes may depend on whether we are sizing under
|
|
491
|
+
// and a min-content or max-content constraint. So if we want to compute both widths in one pass then
|
|
492
|
+
// we need to store both a min-content and max-content size on each box.
|
|
493
|
+
let content_sizes = inline_layout.layout.calculate_content_widths();
|
|
494
|
+
let min_content_width = content_sizes.min;
|
|
495
|
+
let max_content_width = content_sizes.max;
|
|
496
|
+
|
|
497
|
+
#[cfg(feature = "floats")]
|
|
498
|
+
let float_width = match available_space.width {
|
|
499
|
+
AvailableSpace::Definite(_) => 0.0,
|
|
500
|
+
AvailableSpace::MinContent => {
|
|
501
|
+
let mut width: f32 = 0.0;
|
|
502
|
+
for ibox in inline_layout.layout.inline_boxes_mut() {
|
|
503
|
+
let (is_floated, margin) = {
|
|
504
|
+
let style =
|
|
505
|
+
self.child_layout_style(&self.nodes[NodeId::from_u64(ibox.id)]);
|
|
506
|
+
(
|
|
507
|
+
style.float().is_floated(),
|
|
508
|
+
style
|
|
509
|
+
.margin()
|
|
510
|
+
.resolve_or_zero(inputs.parent_size, resolve_calc_value),
|
|
511
|
+
)
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
if is_floated {
|
|
515
|
+
let output = self.compute_child_layout(
|
|
516
|
+
taffy::NodeId::from(ibox.id),
|
|
517
|
+
subtract_margins(child_inputs, margin),
|
|
518
|
+
);
|
|
519
|
+
width = width.max(output.size.width + margin.left + margin.right);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
width * scale
|
|
524
|
+
}
|
|
525
|
+
AvailableSpace::MaxContent => {
|
|
526
|
+
// When computing a max-content size the available width is effectively
|
|
527
|
+
// infinite, so floats never wrap onto a new "band" due to a lack of
|
|
528
|
+
// horizontal space. They only move below preceding floats when the `clear`
|
|
529
|
+
// property forces them to.
|
|
530
|
+
//
|
|
531
|
+
// Floats that share a band sit side-by-side and so their widths sum, whereas
|
|
532
|
+
// floats pushed onto a new band (via `clear`) stack vertically and so we
|
|
533
|
+
// take the maximum extent across bands rather than summing.
|
|
534
|
+
let mut left_band: f32 = 0.0;
|
|
535
|
+
let mut right_band: f32 = 0.0;
|
|
536
|
+
let mut width: f32 = 0.0;
|
|
537
|
+
for ibox in inline_layout.layout.inline_boxes_mut() {
|
|
538
|
+
let (float, clear, margin) = {
|
|
539
|
+
let style =
|
|
540
|
+
self.child_layout_style(&self.nodes[NodeId::from_u64(ibox.id)]);
|
|
541
|
+
(
|
|
542
|
+
style.float(),
|
|
543
|
+
style.clear(),
|
|
544
|
+
style
|
|
545
|
+
.margin()
|
|
546
|
+
.resolve_or_zero(inputs.parent_size, resolve_calc_value),
|
|
547
|
+
)
|
|
548
|
+
};
|
|
549
|
+
|
|
550
|
+
if float.is_floated() {
|
|
551
|
+
if matches!(clear, Clear::Left | Clear::Both) {
|
|
552
|
+
left_band = 0.0;
|
|
553
|
+
}
|
|
554
|
+
if matches!(clear, Clear::Right | Clear::Both) {
|
|
555
|
+
right_band = 0.0;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
let output = self.compute_child_layout(
|
|
559
|
+
taffy::NodeId::from(ibox.id),
|
|
560
|
+
subtract_margins(child_inputs, margin),
|
|
561
|
+
);
|
|
562
|
+
let box_width = output.size.width + margin.left + margin.right;
|
|
563
|
+
|
|
564
|
+
match float {
|
|
565
|
+
Float::Left => left_band += box_width,
|
|
566
|
+
Float::Right => right_band += box_width,
|
|
567
|
+
Float::None => {}
|
|
568
|
+
}
|
|
569
|
+
width = width.max(left_band + right_band);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
width * scale
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
#[cfg(not(feature = "floats"))]
|
|
578
|
+
let float_width = 0.0;
|
|
579
|
+
|
|
580
|
+
let computed_width = match available_space.width {
|
|
581
|
+
AvailableSpace::MinContent => min_content_width.max(float_width),
|
|
582
|
+
AvailableSpace::MaxContent => max_content_width + float_width,
|
|
583
|
+
AvailableSpace::Definite(limit) => (limit * scale)
|
|
584
|
+
.min(max_content_width + float_width)
|
|
585
|
+
.max(min_content_width),
|
|
586
|
+
}
|
|
587
|
+
.ceil();
|
|
588
|
+
|
|
589
|
+
let style_width = node_size.width.map(|w| w * scale);
|
|
590
|
+
let min_width = node_min_size.width.map(|w| w * scale);
|
|
591
|
+
let max_width = node_max_size.width.map(|w| w * scale);
|
|
592
|
+
|
|
593
|
+
(style_width)
|
|
594
|
+
.unwrap_or(computed_width + pbw)
|
|
595
|
+
.max(computed_width)
|
|
596
|
+
.maybe_clamp(min_width, max_width)
|
|
597
|
+
- pbw
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
#[cfg(not(feature = "floats"))]
|
|
601
|
+
let _ = block_ctx; // Suppress unused variable warning
|
|
602
|
+
|
|
603
|
+
// Set block context width if this is a block context root
|
|
604
|
+
#[cfg(feature = "floats")]
|
|
605
|
+
let is_bfc_root = block_ctx.is_bfc_root();
|
|
606
|
+
#[cfg(feature = "floats")]
|
|
607
|
+
if is_bfc_root {
|
|
608
|
+
block_ctx.set_width((width + pbw) / scale);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// Create sub-context to account for the inline layout's padding/border
|
|
612
|
+
#[cfg(feature = "floats")]
|
|
613
|
+
let outer_block_ctx = block_ctx;
|
|
614
|
+
#[cfg(feature = "floats")]
|
|
615
|
+
let mut block_ctx =
|
|
616
|
+
outer_block_ctx.sub_context(container_pb.top, [container_pb.left, container_pb.right]);
|
|
617
|
+
// block_ctx.apply_content_box_inset([container_pb.left, container_pb.right]);
|
|
618
|
+
|
|
619
|
+
if inputs.run_mode == taffy::RunMode::ComputeSize
|
|
620
|
+
&& inputs.axis == RequestedAxis::Horizontal
|
|
621
|
+
{
|
|
622
|
+
// Put layout back
|
|
623
|
+
self.nodes[node_id]
|
|
624
|
+
.data
|
|
625
|
+
.downcast_element_mut()
|
|
626
|
+
.unwrap()
|
|
627
|
+
.inline_layout_data = Some(inline_layout);
|
|
628
|
+
|
|
629
|
+
let measured_size = inputs.known_dimensions.unwrap_or(taffy::Size {
|
|
630
|
+
width: width.ceil() / scale,
|
|
631
|
+
// Height is ignored if RequestedAxis if Horizontal
|
|
632
|
+
height: 0.0,
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
let clamped_size = inputs
|
|
636
|
+
.known_dimensions
|
|
637
|
+
.or(node_size)
|
|
638
|
+
.unwrap_or(measured_size + content_box_inset.sum_axes())
|
|
639
|
+
.maybe_clamp(node_min_size, node_max_size)
|
|
640
|
+
.maybe_max(container_pb.sum_axes().map(Some));
|
|
641
|
+
|
|
642
|
+
return LayoutOutput::from_outer_size(clamped_size);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
let resolved_text_indent = text_indent
|
|
646
|
+
.length
|
|
647
|
+
.resolve(CSSPixelLength::new((width / scale).max(0.0)))
|
|
648
|
+
.px();
|
|
649
|
+
inline_layout
|
|
650
|
+
.layout
|
|
651
|
+
.set_text_indent(resolved_text_indent * scale, indent_options);
|
|
652
|
+
|
|
653
|
+
#[cfg(not(feature = "floats"))]
|
|
654
|
+
{
|
|
655
|
+
inline_layout.layout.break_all_lines(Some(width));
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// Out-of-flow candidates bubbled up from this container and its in-flow subtree.
|
|
659
|
+
// These are laid out by the out-of-flow positioning pass (`compute_oof_layout`).
|
|
660
|
+
let mut oof_candidates = OofCandidates::new();
|
|
661
|
+
|
|
662
|
+
// Perform inline layout
|
|
663
|
+
#[cfg(feature = "floats")]
|
|
664
|
+
{
|
|
665
|
+
let mut breaker = inline_layout.layout.break_lines();
|
|
666
|
+
let initial_slot = block_ctx.find_content_slot(0.0, Clear::None, None);
|
|
667
|
+
let mut has_active_floats = initial_slot.segment_id.is_some();
|
|
668
|
+
let state = breaker.state_mut();
|
|
669
|
+
state.set_layout_max_advance(width);
|
|
670
|
+
state.set_line_max_advance(initial_slot.width * scale);
|
|
671
|
+
state.set_line_x(initial_slot.x * scale);
|
|
672
|
+
state.set_line_y((initial_slot.y * scale) as f64);
|
|
673
|
+
|
|
674
|
+
// TODO: revert state and retry layout if a line doesn't fit
|
|
675
|
+
//
|
|
676
|
+
// Save initial state. Saved state is used to revert the layout to a previous state if needed
|
|
677
|
+
// (e.g. to revert a line that doesn't fit in the space it was laid out into)
|
|
678
|
+
//
|
|
679
|
+
// let mut saved_state = breaker.state().clone();
|
|
680
|
+
|
|
681
|
+
while let Some(yield_data) = breaker.break_next() {
|
|
682
|
+
match yield_data {
|
|
683
|
+
YieldData::LineBreak(_line_break_data) => {
|
|
684
|
+
let state = breaker.state_mut();
|
|
685
|
+
|
|
686
|
+
if has_active_floats {
|
|
687
|
+
// TODO: revert state and retry layout if a line doesn't fit
|
|
688
|
+
// saved_state = state.clone();
|
|
689
|
+
|
|
690
|
+
let min_y = state.line_y() / scale as f64;
|
|
691
|
+
let next_slot =
|
|
692
|
+
block_ctx.find_content_slot(min_y as f32, Clear::None, None);
|
|
693
|
+
has_active_floats = next_slot.segment_id.is_some();
|
|
694
|
+
|
|
695
|
+
state.set_line_max_advance(next_slot.width * scale);
|
|
696
|
+
state.set_line_x(next_slot.x * scale);
|
|
697
|
+
state.set_line_y((next_slot.y * scale) as f64);
|
|
698
|
+
} else {
|
|
699
|
+
state.set_line_x(0.0);
|
|
700
|
+
state.set_line_max_advance(width);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
continue;
|
|
704
|
+
}
|
|
705
|
+
YieldData::MaxHeightExceeded(_data) => {
|
|
706
|
+
// TODO
|
|
707
|
+
continue;
|
|
708
|
+
}
|
|
709
|
+
YieldData::InlineBoxBreak(box_break_data) => {
|
|
710
|
+
let state = breaker.state_mut();
|
|
711
|
+
let node_id = NodeId::from_u64(box_break_data.inline_box_id);
|
|
712
|
+
|
|
713
|
+
let (direction, clear, margin) = {
|
|
714
|
+
let style = self.child_layout_style(&self.nodes[node_id]);
|
|
715
|
+
// We can assume that the box is a float because we only set `break_on_box: true` for floats
|
|
716
|
+
let direction = match style.float() {
|
|
717
|
+
Float::Left => taffy::FloatDirection::Left,
|
|
718
|
+
Float::Right => taffy::FloatDirection::Right,
|
|
719
|
+
Float::None => unreachable!(),
|
|
720
|
+
};
|
|
721
|
+
(
|
|
722
|
+
direction,
|
|
723
|
+
style.clear(),
|
|
724
|
+
style
|
|
725
|
+
.margin()
|
|
726
|
+
.resolve_or_zero(inputs.parent_size, resolve_calc_value),
|
|
727
|
+
)
|
|
728
|
+
};
|
|
729
|
+
|
|
730
|
+
let margin_sum = margin.sum_axes();
|
|
731
|
+
|
|
732
|
+
let mut output = self.compute_child_layout(
|
|
733
|
+
crate::taffy_node_id(node_id),
|
|
734
|
+
float_child_inputs,
|
|
735
|
+
);
|
|
736
|
+
let min_y = state.line_y() as f32 / scale;
|
|
737
|
+
|
|
738
|
+
// Note: `pos` is content-box relative
|
|
739
|
+
let pos = block_ctx.place_floated_box(
|
|
740
|
+
crate::taffy_node_id(node_id).into(),
|
|
741
|
+
output.size + margin_sum,
|
|
742
|
+
min_y,
|
|
743
|
+
direction,
|
|
744
|
+
clear,
|
|
745
|
+
false,
|
|
746
|
+
);
|
|
747
|
+
|
|
748
|
+
let min_y = state.line_y() / scale as f64; //.max(pos.y as f64);
|
|
749
|
+
let next_slot =
|
|
750
|
+
block_ctx.find_content_slot(min_y as f32, Clear::None, None);
|
|
751
|
+
has_active_floats = next_slot.segment_id.is_some();
|
|
752
|
+
|
|
753
|
+
state.set_line_max_advance(next_slot.width * scale);
|
|
754
|
+
state.set_line_x(next_slot.x * scale);
|
|
755
|
+
state.set_line_y((next_slot.y * scale) as f64);
|
|
756
|
+
|
|
757
|
+
let location = taffy::Point {
|
|
758
|
+
x: pos.x + margin.left + container_pb.left,
|
|
759
|
+
y: pos.y + margin.top + container_pb.top,
|
|
760
|
+
};
|
|
761
|
+
let layout = self.nodes[node_id].unrounded_layout_mut();
|
|
762
|
+
layout.size = output.size;
|
|
763
|
+
layout.location = location;
|
|
764
|
+
layout.margin = margin;
|
|
765
|
+
|
|
766
|
+
// Translate anchors from item-relative to container-relative
|
|
767
|
+
// coordinates and collect candidates bubbled from the float's subtree
|
|
768
|
+
if !output.oof_candidates.is_empty() {
|
|
769
|
+
output.oof_candidates.translate(location);
|
|
770
|
+
oof_candidates.append(&mut output.oof_candidates);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// dbg!(&layout.size);
|
|
774
|
+
// dbg!(&layout.location);
|
|
775
|
+
|
|
776
|
+
// Floats are out-of-flow and must not contribute to the line's height.
|
|
777
|
+
state.append_inline_box_to_line(
|
|
778
|
+
box_break_data.advance,
|
|
779
|
+
f32::NEG_INFINITY,
|
|
780
|
+
f32::NEG_INFINITY,
|
|
781
|
+
);
|
|
782
|
+
|
|
783
|
+
// if float.is_floated() {
|
|
784
|
+
// println!("INLINE FLOATED BOX ({}) {:?}", ibox.id, float);
|
|
785
|
+
// println!(
|
|
786
|
+
// "w:{} h:{} x:{}, y:{}",
|
|
787
|
+
// layout.size.width, layout.size.height, 0, 0
|
|
788
|
+
// );
|
|
789
|
+
// }
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
breaker.finish();
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// Propagate the height consumed by floats placed within this container to the
|
|
797
|
+
// enclosing block context (so that the BFC root can contain them)
|
|
798
|
+
#[cfg(feature = "floats")]
|
|
799
|
+
let float_height_contribution = block_ctx.floated_content_height_contribution();
|
|
800
|
+
#[cfg(feature = "floats")]
|
|
801
|
+
outer_block_ctx.add_child_floated_content_height_contribution(
|
|
802
|
+
container_pb.top + float_height_contribution,
|
|
803
|
+
);
|
|
804
|
+
|
|
805
|
+
let (alignment, last_line_alignment) = self.nodes[node_id]
|
|
806
|
+
.primary_styles()
|
|
807
|
+
.map(|s| {
|
|
808
|
+
(
|
|
809
|
+
stylo_to_parley::text_align(s.clone_text_align()),
|
|
810
|
+
stylo_to_parley::text_align_last(s.clone_text_align_last()),
|
|
811
|
+
)
|
|
812
|
+
})
|
|
813
|
+
.unwrap_or((parley::layout::Alignment::Start, None));
|
|
814
|
+
|
|
815
|
+
inline_layout.layout.align(
|
|
816
|
+
alignment,
|
|
817
|
+
AlignmentOptions {
|
|
818
|
+
align_when_overflowing: false,
|
|
819
|
+
last_line_alignment,
|
|
820
|
+
},
|
|
821
|
+
);
|
|
822
|
+
|
|
823
|
+
// Parley lays out empty text as a single strut-height line (text-editor semantics),
|
|
824
|
+
// but a line box containing no text, inline boxes or other in-flow content is a
|
|
825
|
+
// zero-height line box in CSS (CSS2 §9.4.2).
|
|
826
|
+
let has_inline_content =
|
|
827
|
+
!inline_layout.text.is_empty() || inline_layout.layout.inline_boxes().len() > 0;
|
|
828
|
+
|
|
829
|
+
let mut height = if has_inline_content {
|
|
830
|
+
inline_layout.layout.height()
|
|
831
|
+
} else {
|
|
832
|
+
0.0
|
|
833
|
+
};
|
|
834
|
+
|
|
835
|
+
// A forced line break (e.g. `<br>` or a preserved newline) at the end of the inline
|
|
836
|
+
// content ends the final line box without starting a new one. Parley still emits an
|
|
837
|
+
// empty line after it (so that editors have a line to place the cursor on), so that
|
|
838
|
+
// line is excluded from the measured height (and from the last baseline).
|
|
839
|
+
let line_count = inline_layout.layout.len();
|
|
840
|
+
let mut trailing_empty_line = None;
|
|
841
|
+
if line_count >= 2
|
|
842
|
+
&& let (Some(prev_line), Some(last_line)) = (
|
|
843
|
+
inline_layout.layout.get(line_count - 2),
|
|
844
|
+
inline_layout.layout.get(line_count - 1),
|
|
845
|
+
)
|
|
846
|
+
&& prev_line.break_reason() == BreakReason::Explicit
|
|
847
|
+
&& last_line.text_range().is_empty()
|
|
848
|
+
&& last_line.items().next().is_none()
|
|
849
|
+
{
|
|
850
|
+
height -= last_line.metrics().line_height;
|
|
851
|
+
trailing_empty_line = Some(line_count - 1);
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
#[cfg(feature = "floats")]
|
|
855
|
+
{
|
|
856
|
+
if is_bfc_root {
|
|
857
|
+
height = height.max(float_height_contribution * scale)
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// Note: `width` and `height` are content-box measurements of the inline content.
|
|
862
|
+
// `known_dimensions` must not be substituted in here: those are border-box sizes, and
|
|
863
|
+
// using them for the scrollable overflow rect (which adds padding below) would
|
|
864
|
+
// double-count padding, incorrectly making the container's content overflow it.
|
|
865
|
+
let measured_size = taffy::Size {
|
|
866
|
+
width: width / scale,
|
|
867
|
+
height: height / scale,
|
|
868
|
+
};
|
|
869
|
+
|
|
870
|
+
// Unbreakable content (e.g. `white-space: pre` text) may overflow the max advance the
|
|
871
|
+
// lines were broken at. `width` is the advance the lines were broken at, so the actual
|
|
872
|
+
// extent of the line boxes must be taken from the laid-out lines for `content_size`.
|
|
873
|
+
let content_width = f32_max(width, inline_layout.layout.width()) / scale;
|
|
874
|
+
|
|
875
|
+
let clamped_size = inputs
|
|
876
|
+
.known_dimensions
|
|
877
|
+
.or(node_size)
|
|
878
|
+
.unwrap_or(measured_size + content_box_inset.sum_axes())
|
|
879
|
+
.maybe_clamp(node_min_size, node_max_size);
|
|
880
|
+
let final_size = Size {
|
|
881
|
+
width: clamped_size.width,
|
|
882
|
+
height: f32_max(
|
|
883
|
+
clamped_size.height,
|
|
884
|
+
aspect_ratio
|
|
885
|
+
.map(|ratio| clamped_size.width / ratio)
|
|
886
|
+
.unwrap_or(0.0),
|
|
887
|
+
),
|
|
888
|
+
}
|
|
889
|
+
.maybe_max(container_pb.sum_axes().map(Some));
|
|
890
|
+
|
|
891
|
+
let container_direction = self.nodes[node_id].layout_style().direction();
|
|
892
|
+
|
|
893
|
+
// `align-content` aligns the line boxes (as a single unit) within the content box in
|
|
894
|
+
// the block axis. Floats are positioned relative to the formatting context and are not
|
|
895
|
+
// moved. The offset is stored on the text layout for painting and hit-testing.
|
|
896
|
+
let align_content = BlockContainerStyle::align_content(&self.nodes[node_id].layout_style());
|
|
897
|
+
let block_offset = if align_content.keyword() == taffy::AlignContentKeyword::Normal {
|
|
898
|
+
0.0
|
|
899
|
+
} else {
|
|
900
|
+
let free_space =
|
|
901
|
+
final_size.height - content_box_inset.vertical_axis_sum() - measured_size.height;
|
|
902
|
+
taffy::compute_block_align_content_offset(align_content, free_space)
|
|
903
|
+
};
|
|
904
|
+
inline_layout.block_offset = block_offset;
|
|
905
|
+
let line_box_top = container_pb.top + block_offset;
|
|
906
|
+
|
|
907
|
+
// Store sizes and positions of inline boxes
|
|
908
|
+
let mut ibox_order: u32 = 0;
|
|
909
|
+
let mut span_cb_flags = StyleFlags::empty();
|
|
910
|
+
for line in inline_layout.layout.lines() {
|
|
911
|
+
for item in line.items() {
|
|
912
|
+
if let parley::layout::PositionedLayoutItem::InlineBox(ibox) = item {
|
|
913
|
+
let order = ibox_order;
|
|
914
|
+
ibox_order += 1;
|
|
915
|
+
if inputs.run_mode == RunMode::PerformLayout {
|
|
916
|
+
span_cb_flags |=
|
|
917
|
+
self.inline_span_cb_flags(node_id, NodeId::from_u64(ibox.id));
|
|
918
|
+
}
|
|
919
|
+
let node = &self.nodes[NodeId::from_u64(ibox.id)];
|
|
920
|
+
let style = self.child_layout_style(node);
|
|
921
|
+
let padding = style
|
|
922
|
+
.padding()
|
|
923
|
+
.resolve_or_zero(child_inputs.parent_size, resolve_calc_value);
|
|
924
|
+
let border = style
|
|
925
|
+
.border()
|
|
926
|
+
.resolve_or_zero(child_inputs.parent_size, resolve_calc_value);
|
|
927
|
+
let margin = style
|
|
928
|
+
.margin()
|
|
929
|
+
.resolve_or_zero(child_inputs.parent_size, resolve_calc_value);
|
|
930
|
+
|
|
931
|
+
#[cfg(feature = "floats")]
|
|
932
|
+
let is_floated = style.float() != Float::None;
|
|
933
|
+
#[cfg(not(feature = "floats"))]
|
|
934
|
+
let is_floated = false;
|
|
935
|
+
|
|
936
|
+
let position = style.position();
|
|
937
|
+
let is_absolute = position.is_out_of_flow();
|
|
938
|
+
let item_direction = style.direction();
|
|
939
|
+
// Inline formatting contexts have no `justify-items`/`align-items` for an
|
|
940
|
+
// `auto` self-alignment to defer to, so it behaves as `normal`.
|
|
941
|
+
let justify_self =
|
|
942
|
+
OofItemStyle::justify_self(&style).unwrap_or(taffy::AlignItems::NORMAL);
|
|
943
|
+
let align_self =
|
|
944
|
+
OofItemStyle::align_self(&style).unwrap_or(taffy::AlignItems::NORMAL);
|
|
945
|
+
|
|
946
|
+
// The static position of an absolutely positioned box depends on the
|
|
947
|
+
// display its hypothetical box would have had (the display specified
|
|
948
|
+
// before position:absolute blockified it): inline-level boxes sit at
|
|
949
|
+
// their position within the line, while block-level boxes start at the
|
|
950
|
+
// content-box left edge of their containing block.
|
|
951
|
+
let is_inline_level =
|
|
952
|
+
style.style.get_box().original_display.outside() == DisplayOutside::Inline;
|
|
953
|
+
|
|
954
|
+
// Resolve relative inset offsets against the containing block
|
|
955
|
+
// (the content box of the inline container).
|
|
956
|
+
let container_content_size = final_size - content_box_inset.sum_axes();
|
|
957
|
+
let inset_style = style.inset();
|
|
958
|
+
let inset = taffy::Rect {
|
|
959
|
+
left: inset_style
|
|
960
|
+
.left
|
|
961
|
+
.maybe_resolve(container_content_size.width, resolve_calc_value),
|
|
962
|
+
right: inset_style
|
|
963
|
+
.right
|
|
964
|
+
.maybe_resolve(container_content_size.width, resolve_calc_value),
|
|
965
|
+
top: inset_style
|
|
966
|
+
.top
|
|
967
|
+
.maybe_resolve(container_content_size.height, resolve_calc_value),
|
|
968
|
+
bottom: inset_style
|
|
969
|
+
.bottom
|
|
970
|
+
.maybe_resolve(container_content_size.height, resolve_calc_value),
|
|
971
|
+
};
|
|
972
|
+
let box_inputs = inline_box_inputs(style.size().width, margin, child_inputs);
|
|
973
|
+
drop(style);
|
|
974
|
+
|
|
975
|
+
if is_absolute {
|
|
976
|
+
// The static-position rectangle
|
|
977
|
+
// (https://www.w3.org/TR/css-position-3/#staticpos-rect):
|
|
978
|
+
// - An inline-level box's rectangle is zero-width at its position
|
|
979
|
+
// within the line (`ibox.y` is the baseline as out-of-flow boxes are
|
|
980
|
+
// zero-sized) and spans the line box in the block axis.
|
|
981
|
+
// - A block-level box's rectangle spans the containing block's content
|
|
982
|
+
// box in the inline axis and is zero-height below the line box.
|
|
983
|
+
let line_metrics = line.metrics();
|
|
984
|
+
let line_top = (line_metrics.block_min_coord / scale) + line_box_top;
|
|
985
|
+
let line_bottom = (line_metrics.block_max_coord / scale) + line_box_top;
|
|
986
|
+
let (inline_area, block_area) = if is_inline_level {
|
|
987
|
+
let x = (ibox.x / scale) + container_pb.left;
|
|
988
|
+
(
|
|
989
|
+
taffy::Line { start: x, end: x },
|
|
990
|
+
taffy::Line {
|
|
991
|
+
start: line_top,
|
|
992
|
+
end: line_bottom,
|
|
993
|
+
},
|
|
994
|
+
)
|
|
995
|
+
} else {
|
|
996
|
+
(
|
|
997
|
+
taffy::Line {
|
|
998
|
+
start: container_pb.left,
|
|
999
|
+
end: final_size.width - container_pb.right,
|
|
1000
|
+
},
|
|
1001
|
+
taffy::Line {
|
|
1002
|
+
start: line_bottom,
|
|
1003
|
+
end: line_bottom,
|
|
1004
|
+
},
|
|
1005
|
+
)
|
|
1006
|
+
};
|
|
1007
|
+
|
|
1008
|
+
oof_candidates.push(OofCandidate {
|
|
1009
|
+
node: taffy::NodeId::from(ibox.id),
|
|
1010
|
+
order,
|
|
1011
|
+
position,
|
|
1012
|
+
static_position: taffy::Point {
|
|
1013
|
+
x: AxisStaticPosition::from_alignment(
|
|
1014
|
+
justify_self.resolve_self_relative(
|
|
1015
|
+
item_direction,
|
|
1016
|
+
container_direction,
|
|
1017
|
+
true,
|
|
1018
|
+
),
|
|
1019
|
+
inline_area,
|
|
1020
|
+
container_direction.is_rtl(),
|
|
1021
|
+
),
|
|
1022
|
+
y: AxisStaticPosition::from_alignment(
|
|
1023
|
+
align_self.resolve_self_relative(
|
|
1024
|
+
item_direction,
|
|
1025
|
+
container_direction,
|
|
1026
|
+
false,
|
|
1027
|
+
),
|
|
1028
|
+
block_area,
|
|
1029
|
+
false,
|
|
1030
|
+
),
|
|
1031
|
+
},
|
|
1032
|
+
});
|
|
1033
|
+
} else if is_floated {
|
|
1034
|
+
let layout = self.nodes[NodeId::from_u64(ibox.id)].unrounded_layout_mut();
|
|
1035
|
+
layout.padding = padding; //.map(|p| p / scale);
|
|
1036
|
+
layout.border = border; //.map(|p| p / scale);
|
|
1037
|
+
layout.margin = margin;
|
|
1038
|
+
} else {
|
|
1039
|
+
// Re-measure the box to get its border-box size (this hits the layout
|
|
1040
|
+
// cache). The size cannot be recovered from `ibox` dimensions as the
|
|
1041
|
+
// space reserved in the line is clamped to be non-negative.
|
|
1042
|
+
let mut output =
|
|
1043
|
+
self.compute_child_layout(taffy::NodeId::from(ibox.id), box_inputs);
|
|
1044
|
+
let size = output.size;
|
|
1045
|
+
let node = &mut self.nodes[NodeId::from_u64(ibox.id)];
|
|
1046
|
+
|
|
1047
|
+
let is_relative = position == taffy::Position::Relative;
|
|
1048
|
+
let inset_offset = if is_relative {
|
|
1049
|
+
taffy::Point {
|
|
1050
|
+
x: if container_direction == Direction::Rtl {
|
|
1051
|
+
inset.right.map(|x| -x).or(inset.left).unwrap_or(0.0)
|
|
1052
|
+
} else {
|
|
1053
|
+
inset.left.or(inset.right.map(|x| -x)).unwrap_or(0.0)
|
|
1054
|
+
},
|
|
1055
|
+
y: inset.top.or(inset.bottom.map(|x| -x)).unwrap_or(0.0),
|
|
1056
|
+
}
|
|
1057
|
+
} else {
|
|
1058
|
+
taffy::Point::ZERO
|
|
1059
|
+
};
|
|
1060
|
+
|
|
1061
|
+
let layout = node.unrounded_layout_mut();
|
|
1062
|
+
layout.size = size;
|
|
1063
|
+
layout.scrollable_overflow_rect = output.scrollable_overflow_rect;
|
|
1064
|
+
layout.location.x =
|
|
1065
|
+
(ibox.x / scale) + margin.left + container_pb.left + inset_offset.x;
|
|
1066
|
+
// Parley positions the margin box; offset to the border box even
|
|
1067
|
+
// when a negative top margin makes it extend above the margin box.
|
|
1068
|
+
layout.location.y =
|
|
1069
|
+
(ibox.y / scale) + margin.top + line_box_top + inset_offset.y;
|
|
1070
|
+
layout.padding = padding; //.map(|p| p / scale);
|
|
1071
|
+
layout.border = border; //.map(|p| p / scale);
|
|
1072
|
+
layout.margin = margin;
|
|
1073
|
+
|
|
1074
|
+
// Translate anchors from item-relative to container-relative
|
|
1075
|
+
// coordinates and collect candidates bubbled from the box's subtree
|
|
1076
|
+
if !output.oof_candidates.is_empty() {
|
|
1077
|
+
let location = layout.location;
|
|
1078
|
+
output.oof_candidates.translate(location);
|
|
1079
|
+
oof_candidates.append(&mut output.oof_candidates);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
// println!("INLINE LAYOUT FOR {:?}. max_advance: {:?}", node_id, max_advance);
|
|
1087
|
+
// dbg!(&inline_layout.text);
|
|
1088
|
+
// println!("Computed: w: {} h: {}", inline_layout.layout.width(), inline_layout.layout.height());
|
|
1089
|
+
// println!("known_dimensions: w: {:?} h: {:?}", inputs.known_dimensions.width, inputs.known_dimensions.height);
|
|
1090
|
+
// println!("\n");
|
|
1091
|
+
|
|
1092
|
+
let line_baseline =
|
|
1093
|
+
|line: parley::Line<'_, _>| (line.metrics().baseline / scale) + line_box_top;
|
|
1094
|
+
let exports_baseline = has_inline_content && !suppresses_baseline;
|
|
1095
|
+
let first_baseline = exports_baseline
|
|
1096
|
+
.then(|| inline_layout.layout.lines().next().map(line_baseline))
|
|
1097
|
+
.flatten();
|
|
1098
|
+
let last_line_index = trailing_empty_line.unwrap_or(line_count).checked_sub(1);
|
|
1099
|
+
let last_baseline = exports_baseline
|
|
1100
|
+
.then(|| last_line_index.and_then(|i| inline_layout.layout.get(i)))
|
|
1101
|
+
.flatten()
|
|
1102
|
+
.map(line_baseline);
|
|
1103
|
+
|
|
1104
|
+
if inputs.run_mode == RunMode::PerformLayout {
|
|
1105
|
+
inline_layout.span_cb_flags = span_cb_flags;
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
// Put layout back
|
|
1109
|
+
self.nodes[node_id]
|
|
1110
|
+
.data
|
|
1111
|
+
.downcast_element_mut()
|
|
1112
|
+
.unwrap()
|
|
1113
|
+
.inline_layout_data = Some(inline_layout);
|
|
1114
|
+
|
|
1115
|
+
let oof_position_inset = taffy::Rect {
|
|
1116
|
+
left: border.left,
|
|
1117
|
+
right: border.right + scrollbar_gutter.x,
|
|
1118
|
+
top: border.top,
|
|
1119
|
+
bottom: border.bottom + scrollbar_gutter.y,
|
|
1120
|
+
};
|
|
1121
|
+
|
|
1122
|
+
LayoutOutput {
|
|
1123
|
+
size: final_size,
|
|
1124
|
+
scrollable_overflow_rect: {
|
|
1125
|
+
let content_extent = taffy::Size {
|
|
1126
|
+
width: content_width,
|
|
1127
|
+
height: measured_size.height,
|
|
1128
|
+
} + padding.sum_axes();
|
|
1129
|
+
taffy::Rect {
|
|
1130
|
+
left: 0.0,
|
|
1131
|
+
right: content_extent.width,
|
|
1132
|
+
top: 0.0,
|
|
1133
|
+
bottom: content_extent.height + block_offset.max(0.0),
|
|
1134
|
+
}
|
|
1135
|
+
},
|
|
1136
|
+
baselines: taffy::Baselines {
|
|
1137
|
+
first: first_baseline,
|
|
1138
|
+
last: last_baseline,
|
|
1139
|
+
},
|
|
1140
|
+
top_margin: CollapsibleMarginSet::ZERO,
|
|
1141
|
+
bottom_margin: CollapsibleMarginSet::ZERO,
|
|
1142
|
+
margins_can_collapse_through: !has_styles_preventing_being_collapsed_through
|
|
1143
|
+
&& final_size.height == 0.0
|
|
1144
|
+
&& measured_size.height == 0.0,
|
|
1145
|
+
oof_candidates,
|
|
1146
|
+
oof_positioning_area: Some(OofPositioningArea {
|
|
1147
|
+
size: final_size - oof_position_inset.sum_axes(),
|
|
1148
|
+
offset: Point {
|
|
1149
|
+
x: oof_position_inset.left,
|
|
1150
|
+
y: oof_position_inset.top,
|
|
1151
|
+
},
|
|
1152
|
+
}),
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
#[inline(always)]
|
|
1158
|
+
fn f32_max(a: f32, b: f32) -> f32 {
|
|
1159
|
+
a.max(b)
|
|
1160
|
+
}
|