blitz-pdf 0.1.3 → 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 +6 -6
- package/THIRD_PARTY_NOTICES.md +5 -2
- package/docs/layout-regressions.md +65 -0
- package/docs/pagination-regressions.md +7 -6
- package/package.json +1 -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,437 @@
|
|
|
1
|
+
//! CSS vertical writing modes (`writing-mode` feature).
|
|
2
|
+
//!
|
|
3
|
+
//! Taffy is a horizontal-tb engine: `width`/x is the inline axis and `height`/+y the block axis.
|
|
4
|
+
//! Blitz runs each box's layout algorithm in the axes of the box's own writing mode and translates at
|
|
5
|
+
//! the boundary (see [`stylo_taffy::writing_mode`] for the axis mapping):
|
|
6
|
+
//!
|
|
7
|
+
//! - Style getters: a box's own algorithm reads its style in its own writing mode
|
|
8
|
+
//! (`get_core_container_style`); the algorithm laying it out reads it in *that* algorithm's writing mode
|
|
9
|
+
//! (`get_*_child_style`, `get_oof_item_style`, [`BaseDocument::child_layout_style`]).
|
|
10
|
+
//! [`LayoutPassState::layout_wm`] tracks the writing mode of the running algorithm.
|
|
11
|
+
//! - Orthogonal flows ([`compute_child_layout_in_own_wm`]): when a box's writing mode differs from its
|
|
12
|
+
//! parent's, the `LayoutInput` is transposed into the box's axes, its auto inline size is
|
|
13
|
+
//! resolved to fit-content (css-writing-modes-3 §7.3.1), and the `LayoutOutput` is transposed
|
|
14
|
+
//! back. Percentage padding on such a box resolves against the containing block's inline size
|
|
15
|
+
//! ([`LayoutPassState::orthogonal_percent_basis`]).
|
|
16
|
+
//! - Geometry: `unrounded_layout` is written by the placing algorithm in *its* writing mode.
|
|
17
|
+
//! [`physicalise_and_round_layout`] converts every layout to physical coordinates (mirroring
|
|
18
|
+
//! the block axis for `*-rl` modes) and rounds it, replacing `taffy::round_layout`.
|
|
19
|
+
//!
|
|
20
|
+
//! [`compute_child_layout_in_own_wm`]: LayoutPassState::compute_child_layout_in_own_wm
|
|
21
|
+
//! [`physicalise_and_round_layout`]: LayoutPassState::physicalise_and_round_layout
|
|
22
|
+
|
|
23
|
+
use super::{BlockContext, LayoutPassState};
|
|
24
|
+
use crate::document::BaseDocument;
|
|
25
|
+
use crate::dom_node_id;
|
|
26
|
+
use crate::node::Node;
|
|
27
|
+
use stylo_taffy::WritingMode;
|
|
28
|
+
use stylo_taffy::WritingModeExt;
|
|
29
|
+
use taffy::{
|
|
30
|
+
AvailableSpace, AxisStaticEdge, AxisStaticPosition, Layout, LayoutInput, LayoutOutput, NodeId,
|
|
31
|
+
Point, RoundTree, RunMode, Size, TraversePartialTree,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
impl BaseDocument {
|
|
35
|
+
/// The writing mode in which the root element's layout algorithm runs: the HTML special case of
|
|
36
|
+
/// css-writing-modes-3 §8 uses the `writing-mode` of its `<body>` child instead of its own.
|
|
37
|
+
pub(crate) fn root_layout_wm(&self, root_id: crate::NodeId) -> WritingMode {
|
|
38
|
+
let root = &self.nodes[root_id];
|
|
39
|
+
let wm = root.writing_mode();
|
|
40
|
+
if root.has_containment() {
|
|
41
|
+
return wm;
|
|
42
|
+
}
|
|
43
|
+
root.children
|
|
44
|
+
.iter()
|
|
45
|
+
.map(|&child| &self.nodes[child])
|
|
46
|
+
.find(|child| {
|
|
47
|
+
child
|
|
48
|
+
.data
|
|
49
|
+
.downcast_element()
|
|
50
|
+
.is_some_and(|el| *el.name.local == *"body")
|
|
51
|
+
})
|
|
52
|
+
.filter(|body| !body.has_containment())
|
|
53
|
+
.map(|body| body.writing_mode())
|
|
54
|
+
.unwrap_or(wm)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
impl LayoutPassState<'_> {
|
|
59
|
+
/// The writing mode in which `node_id`'s layout algorithm runs (the root's is resolved once per
|
|
60
|
+
/// pass by [`BaseDocument::root_layout_wm`]).
|
|
61
|
+
#[inline]
|
|
62
|
+
pub(crate) fn layout_wm_of(&self, node_id: crate::NodeId) -> WritingMode {
|
|
63
|
+
if Some(node_id) == self.root_id {
|
|
64
|
+
self.root_wm
|
|
65
|
+
} else {
|
|
66
|
+
self.nodes[node_id].writing_mode()
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/// Lay out `node_id`, translating between the parent's writing mode and the node's own at an
|
|
71
|
+
/// orthogonal-flow boundary.
|
|
72
|
+
pub(crate) fn compute_child_layout_in_own_wm(
|
|
73
|
+
&mut self,
|
|
74
|
+
node_id: NodeId,
|
|
75
|
+
inputs: LayoutInput,
|
|
76
|
+
block_ctx: Option<&mut BlockContext<'_>>,
|
|
77
|
+
) -> LayoutOutput {
|
|
78
|
+
let node_wm = self.layout_wm_of(dom_node_id(node_id));
|
|
79
|
+
let parent_wm = core::mem::replace(&mut self.layout_wm, node_wm);
|
|
80
|
+
// Set by `dispatch_child_layout` when the node turns out to be a column flex container.
|
|
81
|
+
let parent_align_axis_is_inline =
|
|
82
|
+
core::mem::replace(&mut self.current_align_axis_is_inline, false);
|
|
83
|
+
|
|
84
|
+
let output = if node_wm.is_vertical() == parent_wm.is_vertical() {
|
|
85
|
+
let mut output = self.compute_child_layout_in_current_wm(node_id, inputs, block_ctx);
|
|
86
|
+
if node_wm.is_vertical() && node_wm.is_vertical_lr() != parent_wm.is_vertical_lr() {
|
|
87
|
+
// Parallel writing modes with opposite block directions (`vertical-lr` in `vertical-rl`
|
|
88
|
+
// or vice versa) share Taffy's axes but run the block axis the other way.
|
|
89
|
+
let extent = output.size.height;
|
|
90
|
+
for candidate in output.oof_candidates.as_mut_slice() {
|
|
91
|
+
candidate.static_position.y =
|
|
92
|
+
mirror_static_position(candidate.static_position.y, extent);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
output
|
|
96
|
+
} else {
|
|
97
|
+
self.compute_orthogonal_child_layout(node_id, inputs, parent_wm)
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
self.layout_wm = parent_wm;
|
|
101
|
+
self.current_align_axis_is_inline = parent_align_axis_is_inline;
|
|
102
|
+
output
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
fn compute_orthogonal_child_layout(
|
|
106
|
+
&mut self,
|
|
107
|
+
node_id: NodeId,
|
|
108
|
+
inputs: LayoutInput,
|
|
109
|
+
parent_wm: WritingMode,
|
|
110
|
+
) -> LayoutOutput {
|
|
111
|
+
let percent_basis = self.orthogonal_percent_basis.replace((
|
|
112
|
+
dom_node_id(node_id),
|
|
113
|
+
inputs.parent_size.width.unwrap_or(0.0),
|
|
114
|
+
));
|
|
115
|
+
|
|
116
|
+
let mut inputs = inputs.transpose();
|
|
117
|
+
|
|
118
|
+
// A block container stretches its in-flow children's inline size and passes it as a known
|
|
119
|
+
// dimension. For an orthogonal child that is its block size, which is content-sized.
|
|
120
|
+
// (An out-of-flow box's known dimensions come from its insets and are kept.)
|
|
121
|
+
let node = &self.nodes[dom_node_id(node_id)];
|
|
122
|
+
let placed_by_block_container = !self.is_out_of_flow(node_id)
|
|
123
|
+
&& node
|
|
124
|
+
.layout_parent
|
|
125
|
+
.get()
|
|
126
|
+
.map(|parent| self.nodes[parent].taffy_display() == taffy::Display::Block)
|
|
127
|
+
.unwrap_or(true);
|
|
128
|
+
if placed_by_block_container
|
|
129
|
+
&& taffy::CoreStyle::size(&node.layout_style())
|
|
130
|
+
.height
|
|
131
|
+
.is_auto()
|
|
132
|
+
{
|
|
133
|
+
inputs.known_dimensions.height = None;
|
|
134
|
+
inputs.known_dimensions_are_definite.height = false;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
let is_intrinsic_inline_measurement = inputs.axis == taffy::RequestedAxis::Horizontal
|
|
138
|
+
&& matches!(
|
|
139
|
+
inputs.available_space.width,
|
|
140
|
+
AvailableSpace::MinContent | AvailableSpace::MaxContent
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
if inputs.run_mode != RunMode::PerformHiddenLayout
|
|
144
|
+
&& inputs.known_dimensions.width.is_none()
|
|
145
|
+
&& !is_intrinsic_inline_measurement
|
|
146
|
+
{
|
|
147
|
+
let inline_size_is_auto =
|
|
148
|
+
taffy::CoreStyle::size(&self.nodes[dom_node_id(node_id)].layout_style())
|
|
149
|
+
.width
|
|
150
|
+
.is_auto();
|
|
151
|
+
if inline_size_is_auto {
|
|
152
|
+
// css-writing-modes-3 §7.3.1: an auto inline size in an orthogonal flow is
|
|
153
|
+
// fit-content against the available space in that axis: the containing block's
|
|
154
|
+
// block size if definite, else the initial containing block (nearest fixed-size
|
|
155
|
+
// scroll container not implemented).
|
|
156
|
+
let available = match (inputs.available_space.width, inputs.parent_size.width) {
|
|
157
|
+
(AvailableSpace::Definite(size), _) => size,
|
|
158
|
+
(_, Some(size)) => size,
|
|
159
|
+
_ => {
|
|
160
|
+
let viewport = self.stylist.device().au_viewport_size();
|
|
161
|
+
if parent_wm.is_vertical() {
|
|
162
|
+
viewport.width.to_f32_px()
|
|
163
|
+
} else {
|
|
164
|
+
viewport.height.to_f32_px()
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
let measure = |doc: &mut Self, available_space: AvailableSpace| {
|
|
169
|
+
let measure_inputs = LayoutInput {
|
|
170
|
+
run_mode: RunMode::ComputeSize,
|
|
171
|
+
axis: taffy::RequestedAxis::Horizontal,
|
|
172
|
+
known_dimensions: Size {
|
|
173
|
+
width: None,
|
|
174
|
+
height: inputs.known_dimensions.height,
|
|
175
|
+
},
|
|
176
|
+
available_space: Size {
|
|
177
|
+
width: available_space,
|
|
178
|
+
height: inputs.available_space.height,
|
|
179
|
+
},
|
|
180
|
+
..inputs
|
|
181
|
+
};
|
|
182
|
+
doc.compute_child_layout_in_current_wm(node_id, measure_inputs, None)
|
|
183
|
+
.size
|
|
184
|
+
.width
|
|
185
|
+
};
|
|
186
|
+
let max_content = measure(self, AvailableSpace::MaxContent);
|
|
187
|
+
let min_content = measure(self, AvailableSpace::MinContent);
|
|
188
|
+
inputs.known_dimensions.width = Some(available.min(max_content).max(min_content));
|
|
189
|
+
inputs.known_dimensions_are_definite.width = true;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
let mut output = self.compute_child_layout_in_current_wm(node_id, inputs, None);
|
|
194
|
+
self.orthogonal_percent_basis = percent_basis;
|
|
195
|
+
transpose_static_positions(&mut output, self.layout_wm, parent_wm);
|
|
196
|
+
output.transpose()
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/// Convert every `unrounded_layout` (written in the writing mode of the box that placed it) to
|
|
200
|
+
/// physical coordinates and round it into `final_layout`. Mirrors `taffy::round_layout`,
|
|
201
|
+
/// which it replaces: rounding is applied to cumulative physical positions so that adjacent
|
|
202
|
+
/// boxes never gain gaps or overlaps.
|
|
203
|
+
pub(crate) fn physicalise_and_round_layout(&mut self, root: NodeId) {
|
|
204
|
+
let viewport = self.stylist.device().au_viewport_size();
|
|
205
|
+
let viewport = Size {
|
|
206
|
+
width: viewport.width.to_f32_px(),
|
|
207
|
+
height: viewport.height.to_f32_px(),
|
|
208
|
+
};
|
|
209
|
+
let root_wm = self.layout_wm_of(dom_node_id(root));
|
|
210
|
+
self.physicalise_and_round_inner(root, root_wm, viewport, Point::ZERO, Point::ZERO);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
fn physicalise_and_round_inner(
|
|
214
|
+
&mut self,
|
|
215
|
+
node_id: NodeId,
|
|
216
|
+
placer_wm: WritingMode,
|
|
217
|
+
placer_size: Size<f32>,
|
|
218
|
+
parent_pos: Point<f32>,
|
|
219
|
+
rounded_parent_pos: Point<f32>,
|
|
220
|
+
) {
|
|
221
|
+
let unrounded = physical_layout(self.get_unrounded_layout(node_id), placer_wm, placer_size);
|
|
222
|
+
|
|
223
|
+
let pos = Point {
|
|
224
|
+
x: parent_pos.x + unrounded.location.x,
|
|
225
|
+
y: parent_pos.y + unrounded.location.y,
|
|
226
|
+
};
|
|
227
|
+
let size = unrounded.size;
|
|
228
|
+
let round_layout = self.round_layout;
|
|
229
|
+
let round = |v: f32| if round_layout { v.round() } else { v };
|
|
230
|
+
// As in `taffy::round_layout`: the box's outer edges are rounded in absolute coordinates and
|
|
231
|
+
// every inner edge is rounded relative to them, so adjacent boxes never overlap or gap.
|
|
232
|
+
let rounded_pos = Point {
|
|
233
|
+
x: round(pos.x),
|
|
234
|
+
y: round(pos.y),
|
|
235
|
+
};
|
|
236
|
+
let rounded_end = Point {
|
|
237
|
+
x: round(pos.x + size.width),
|
|
238
|
+
y: round(pos.y + size.height),
|
|
239
|
+
};
|
|
240
|
+
let from_left = |v: f32| round(pos.x + v) - rounded_pos.x;
|
|
241
|
+
let from_top = |v: f32| round(pos.y + v) - rounded_pos.y;
|
|
242
|
+
let from_right = |v: f32| rounded_end.x - round(pos.x + size.width - v);
|
|
243
|
+
let from_bottom = |v: f32| rounded_end.y - round(pos.y + size.height - v);
|
|
244
|
+
|
|
245
|
+
let mut layout = unrounded;
|
|
246
|
+
layout.location = Point {
|
|
247
|
+
x: rounded_pos.x - rounded_parent_pos.x,
|
|
248
|
+
y: rounded_pos.y - rounded_parent_pos.y,
|
|
249
|
+
};
|
|
250
|
+
layout.size = Size {
|
|
251
|
+
width: rounded_end.x - rounded_pos.x,
|
|
252
|
+
height: rounded_end.y - rounded_pos.y,
|
|
253
|
+
};
|
|
254
|
+
layout.scrollbar_size = unrounded.scrollbar_size.map(f32::round);
|
|
255
|
+
layout.border = taffy::Rect {
|
|
256
|
+
left: from_left(unrounded.border.left),
|
|
257
|
+
right: from_right(unrounded.border.right),
|
|
258
|
+
top: from_top(unrounded.border.top),
|
|
259
|
+
bottom: from_bottom(unrounded.border.bottom),
|
|
260
|
+
};
|
|
261
|
+
layout.padding = taffy::Rect {
|
|
262
|
+
left: from_left(unrounded.padding.left),
|
|
263
|
+
right: from_right(unrounded.padding.right),
|
|
264
|
+
top: from_top(unrounded.padding.top),
|
|
265
|
+
bottom: from_bottom(unrounded.padding.bottom),
|
|
266
|
+
};
|
|
267
|
+
let overflow = unrounded.scrollable_overflow_rect;
|
|
268
|
+
layout.scrollable_overflow_rect = taffy::Rect {
|
|
269
|
+
left: from_left(overflow.left),
|
|
270
|
+
right: from_left(overflow.right),
|
|
271
|
+
top: from_top(overflow.top),
|
|
272
|
+
bottom: from_top(overflow.bottom),
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
// `unrounded_layout` is left in the placer's writing mode: Taffy's cache may skip re-placing
|
|
276
|
+
// this node on a later layout, and only `final_layout` is read downstream.
|
|
277
|
+
self.set_final_layout(node_id, &layout);
|
|
278
|
+
|
|
279
|
+
let wm = self.layout_wm_of(dom_node_id(node_id));
|
|
280
|
+
self.node_from_id_mut(node_id)
|
|
281
|
+
.layout_data_mut()
|
|
282
|
+
.writing_mode = wm;
|
|
283
|
+
for index in 0..self.child_count(node_id) {
|
|
284
|
+
let child = self.get_child_id(node_id, index);
|
|
285
|
+
if !self.is_out_of_flow(child) {
|
|
286
|
+
self.physicalise_and_round_inner(child, wm, size, pos, rounded_pos);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
for index in 0..self.hoisted_child_count(node_id) {
|
|
290
|
+
let child = self.get_hoisted_child_id(node_id, index);
|
|
291
|
+
self.physicalise_and_round_inner(child, wm, size, pos, rounded_pos);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/// Map the static positions of `output`'s out-of-flow candidates (relative to the border box of a
|
|
297
|
+
/// node laid out in `child_wm`) into `parent_wm`, ahead of [`LayoutOutput::transpose`],
|
|
298
|
+
/// which leaves them untouched. The block axis of `vertical-rl` runs against Taffy's
|
|
299
|
+
/// coordinate, so that coordinate is mirrored within the node's size.
|
|
300
|
+
fn transpose_static_positions(
|
|
301
|
+
output: &mut LayoutOutput,
|
|
302
|
+
child_wm: WritingMode,
|
|
303
|
+
parent_wm: WritingMode,
|
|
304
|
+
) {
|
|
305
|
+
if output.oof_candidates.is_empty() {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
let vertical_wm = if child_wm.is_vertical() {
|
|
309
|
+
child_wm
|
|
310
|
+
} else {
|
|
311
|
+
parent_wm
|
|
312
|
+
};
|
|
313
|
+
let mirror_block = !vertical_wm.is_vertical_lr();
|
|
314
|
+
let size = output.size;
|
|
315
|
+
for candidate in output.oof_candidates.as_mut_slice() {
|
|
316
|
+
let Point { mut x, mut y } = candidate.static_position;
|
|
317
|
+
if mirror_block {
|
|
318
|
+
if child_wm.is_vertical() {
|
|
319
|
+
y = mirror_static_position(y, size.height);
|
|
320
|
+
} else {
|
|
321
|
+
x = mirror_static_position(x, size.width);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
candidate.static_position = Point { x: y, y: x };
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
fn mirror_static_position(position: AxisStaticPosition, extent: f32) -> AxisStaticPosition {
|
|
329
|
+
let flip = |edge: AxisStaticEdge| match edge {
|
|
330
|
+
AxisStaticEdge::Start => AxisStaticEdge::End,
|
|
331
|
+
AxisStaticEdge::End => AxisStaticEdge::Start,
|
|
332
|
+
AxisStaticEdge::Center => AxisStaticEdge::Center,
|
|
333
|
+
};
|
|
334
|
+
AxisStaticPosition {
|
|
335
|
+
area: taffy::Line {
|
|
336
|
+
start: extent - position.area.end,
|
|
337
|
+
end: extent - position.area.start,
|
|
338
|
+
},
|
|
339
|
+
align: taffy::AxisStaticAlign {
|
|
340
|
+
keyword: flip(position.align.keyword),
|
|
341
|
+
fallback: flip(position.align.fallback),
|
|
342
|
+
..position.align
|
|
343
|
+
},
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/// `logical`, written by a placing algorithm running in `placer_wm` within a box of physical size
|
|
348
|
+
/// `placer_size`, in physical coordinates.
|
|
349
|
+
fn physical_layout(logical: Layout, placer_wm: WritingMode, placer_size: Size<f32>) -> Layout {
|
|
350
|
+
if !placer_wm.is_vertical() {
|
|
351
|
+
return logical;
|
|
352
|
+
}
|
|
353
|
+
Layout {
|
|
354
|
+
location: Point {
|
|
355
|
+
x: if placer_wm.is_vertical_lr() {
|
|
356
|
+
logical.location.y
|
|
357
|
+
} else {
|
|
358
|
+
placer_size.width - logical.location.y - logical.size.height
|
|
359
|
+
},
|
|
360
|
+
y: logical.location.x,
|
|
361
|
+
},
|
|
362
|
+
size: logical.size.transpose(),
|
|
363
|
+
scrollbar_size: logical.scrollbar_size.transpose(),
|
|
364
|
+
scrollable_overflow_rect: logical.scrollable_overflow_rect.transpose(),
|
|
365
|
+
border: placer_wm.physical_rect(logical.border),
|
|
366
|
+
padding: placer_wm.physical_rect(logical.padding),
|
|
367
|
+
margin: placer_wm.physical_rect(logical.margin),
|
|
368
|
+
..logical
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
impl BaseDocument {
|
|
373
|
+
/// `node_id`'s document-relative position and unrounded layout in physical coordinates, for
|
|
374
|
+
/// CSSOM geometry. `unrounded_layout` itself is left in the placing algorithm's writing mode (see
|
|
375
|
+
/// [`LayoutPassState::physicalise_and_round_layout`]), so the containing-block chain is
|
|
376
|
+
/// physicalised root-down in a single descent, each box in its placer's writing mode and size.
|
|
377
|
+
pub(crate) fn physical_unrounded_geometry(
|
|
378
|
+
&self,
|
|
379
|
+
node_id: crate::NodeId,
|
|
380
|
+
) -> (crate::util::Point<f32>, Layout) {
|
|
381
|
+
// Without a vertical box on the containing-block chain there is nothing to physicalise.
|
|
382
|
+
let mut pos = crate::util::Point { x: 0.0, y: 0.0 };
|
|
383
|
+
let mut has_vertical = false;
|
|
384
|
+
let mut current = Some(node_id);
|
|
385
|
+
while let Some(id) = current {
|
|
386
|
+
let node = &self.nodes[id];
|
|
387
|
+
pos.x += node.unrounded_layout().location.x - node.scroll_offset().x as f32;
|
|
388
|
+
pos.y += node.unrounded_layout().location.y - node.scroll_offset().y as f32;
|
|
389
|
+
has_vertical |= node.layout_data().writing_mode.is_vertical();
|
|
390
|
+
current = node.containing_block();
|
|
391
|
+
}
|
|
392
|
+
if !has_vertical {
|
|
393
|
+
return (pos, *self.nodes[node_id].unrounded_layout());
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
let root_id = self.try_root_element().map(|root| root.id);
|
|
397
|
+
let node = &self.nodes[node_id];
|
|
398
|
+
let (mut pos, placer_wm, placer_size) = self.placer_geometry(node, node_id, root_id);
|
|
399
|
+
let layout = physical_layout(*node.unrounded_layout(), placer_wm, placer_size);
|
|
400
|
+
pos.x += layout.location.x - node.scroll_offset().x as f32;
|
|
401
|
+
pos.y += layout.location.y - node.scroll_offset().y as f32;
|
|
402
|
+
(pos, layout)
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/// Document-relative position, writing mode and physical size of the box that places `node`.
|
|
406
|
+
fn placer_geometry(
|
|
407
|
+
&self,
|
|
408
|
+
node: &Node,
|
|
409
|
+
node_id: crate::NodeId,
|
|
410
|
+
root_id: Option<crate::NodeId>,
|
|
411
|
+
) -> (crate::util::Point<f32>, WritingMode, Size<f32>) {
|
|
412
|
+
let Some(placer_id) = node.containing_block().filter(|_| Some(node_id) != root_id) else {
|
|
413
|
+
let viewport = self.stylist.device().au_viewport_size();
|
|
414
|
+
let viewport = Size {
|
|
415
|
+
width: viewport.width.to_f32_px(),
|
|
416
|
+
height: viewport.height.to_f32_px(),
|
|
417
|
+
};
|
|
418
|
+
let wm = root_id.map_or(WritingMode::empty(), |root| {
|
|
419
|
+
self.nodes[root].layout_data().writing_mode
|
|
420
|
+
});
|
|
421
|
+
return (crate::util::Point { x: 0.0, y: 0.0 }, wm, viewport);
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
let placer = &self.nodes[placer_id];
|
|
425
|
+
let (mut pos, grand_wm, grand_size) = self.placer_geometry(placer, placer_id, root_id);
|
|
426
|
+
let logical = placer.unrounded_layout();
|
|
427
|
+
let (location, size) = if grand_wm.is_vertical() {
|
|
428
|
+
let physical = physical_layout(*logical, grand_wm, grand_size);
|
|
429
|
+
(physical.location, physical.size)
|
|
430
|
+
} else {
|
|
431
|
+
(logical.location, logical.size)
|
|
432
|
+
};
|
|
433
|
+
pos.x += location.x - placer.scroll_offset().x as f32;
|
|
434
|
+
pos.y += location.y - placer.scroll_offset().y as f32;
|
|
435
|
+
(pos, placer.layout_data().writing_mode, size)
|
|
436
|
+
}
|
|
437
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
//! The core DOM abstraction in Blitz
|
|
2
|
+
//!
|
|
3
|
+
//! This crate implements a flexible headless DOM ([`BaseDocument`]), which is designed to emebedded in and "driven" by external code. Most users will want
|
|
4
|
+
//! to use a wrapper:
|
|
5
|
+
//!
|
|
6
|
+
//! - [`HtmlDocument`](https://docs.rs/blitz-html/latest/blitz_html/struct.HtmlDocument.html) from the [blitz-html](https://docs.rs/blitz-html) crate.
|
|
7
|
+
//! Allows you to parse HTML (or XHTML) into a Blitz [`BaseDocument`], and can be combined with a markdown-to-html converter like [comrak](https://docs.rs/comrak)
|
|
8
|
+
//! or [pulldown-cmark](https://docs.rs/pulldown-cmark) to render/process markdown.
|
|
9
|
+
//! - [`DioxusDocument`](https://docs.rs/dioxus-native/latest/dioxus_native/struct.DioxusDocument.html) from the [dioxus-native](https://docs.rs/dioxus-native) crate.
|
|
10
|
+
//! Combines a [`BaseDocument`] with a Dioxus `VirtualDom` to enable dynamic rendering and event handling.
|
|
11
|
+
//!
|
|
12
|
+
//! It includes: A DOM tree respresentation, CSS parsing and resolution, layout and event handling. Additional functionality is available in
|
|
13
|
+
//! separate crates, including html parsing ([blitz-html](https://docs.rs/blitz-html)), networking ([blitz-net](https://docs.rs/blitz-html)),
|
|
14
|
+
//! rendering ([blitz-paint](https://docs.rs/blitz-paint)) and windowing ([blitz-shell](https://docs.rs/blitz-shell)).
|
|
15
|
+
//!
|
|
16
|
+
//! Most of the functionality in this crates is provided through the struct.
|
|
17
|
+
//!
|
|
18
|
+
//! `blitz-dom` has a native Rust API that is designed for higher-level abstractions to be built on top (although it can also be used directly).
|
|
19
|
+
//!
|
|
20
|
+
//! The goal behind this crate is that any implementor can interact with the DOM and render it out using any renderer
|
|
21
|
+
//! they want.
|
|
22
|
+
//!
|
|
23
|
+
|
|
24
|
+
#![allow(clippy::collapsible_if)]
|
|
25
|
+
|
|
26
|
+
// TODO: Document features
|
|
27
|
+
// ## Feature flags
|
|
28
|
+
// - `default`: Enables the features listed below.
|
|
29
|
+
// - `tracing`: Enables tracing support.
|
|
30
|
+
|
|
31
|
+
pub const DEFAULT_CSS: &str = include_str!("../assets/default.css");
|
|
32
|
+
pub const BULLET_FONT: &[u8] = include_bytes!("../assets/moz-bullet-font.otf");
|
|
33
|
+
|
|
34
|
+
/// The DOM implementation.
|
|
35
|
+
///
|
|
36
|
+
/// This is the primary entry point for this crate.
|
|
37
|
+
mod document;
|
|
38
|
+
|
|
39
|
+
/// The nodes themsleves, and their data.
|
|
40
|
+
pub mod node;
|
|
41
|
+
|
|
42
|
+
mod config;
|
|
43
|
+
/// CSSOM stylesheet access (`document.styleSheets`, `CSSStyleSheet`, `CSSRule`)
|
|
44
|
+
mod cssom;
|
|
45
|
+
mod debug;
|
|
46
|
+
mod events;
|
|
47
|
+
mod font_metrics;
|
|
48
|
+
mod form;
|
|
49
|
+
mod html;
|
|
50
|
+
/// Loading of `<iframe>` elements into sub-documents.
|
|
51
|
+
mod iframe;
|
|
52
|
+
/// Integration of taffy and the DOM.
|
|
53
|
+
mod layout;
|
|
54
|
+
mod mutator;
|
|
55
|
+
mod query_selector;
|
|
56
|
+
mod resolve;
|
|
57
|
+
/// Computation of resolved CSS property values (`getComputedStyle()`)
|
|
58
|
+
mod resolved_style;
|
|
59
|
+
/// Scrolling of nodes and the viewport, and scroll animations.
|
|
60
|
+
mod scrolling;
|
|
61
|
+
mod selection;
|
|
62
|
+
/// Implementations that interact with servo's style engine
|
|
63
|
+
mod stylo;
|
|
64
|
+
mod stylo_device;
|
|
65
|
+
mod stylo_to_cursor_icon;
|
|
66
|
+
mod stylo_to_kurbo;
|
|
67
|
+
mod stylo_to_parley;
|
|
68
|
+
pub mod traversal;
|
|
69
|
+
/// Versioned storage for the nodes of the DOM tree.
|
|
70
|
+
mod tree;
|
|
71
|
+
|
|
72
|
+
mod url;
|
|
73
|
+
|
|
74
|
+
pub use cssom::{CssRuleInfo, CssomError};
|
|
75
|
+
pub use resolved_style::{
|
|
76
|
+
css_property_is_supported, parse_transform_matrix, resolved_style_property_names,
|
|
77
|
+
};
|
|
78
|
+
pub use stylo_to_kurbo::resolve_2d_transform;
|
|
79
|
+
|
|
80
|
+
pub mod net;
|
|
81
|
+
pub mod util;
|
|
82
|
+
|
|
83
|
+
#[cfg(feature = "accessibility")]
|
|
84
|
+
mod accessibility;
|
|
85
|
+
|
|
86
|
+
pub use crate::layout::paint_tree::{HoistedPaintChild, StackingContext, hoisted_child_position};
|
|
87
|
+
pub use crate::layout::replaced::IntrinsicSizes;
|
|
88
|
+
pub use crate::layout::text_transform::{full_size_kana, full_width};
|
|
89
|
+
#[cfg(feature = "custom-widget")]
|
|
90
|
+
pub use crate::node::Widget;
|
|
91
|
+
|
|
92
|
+
pub use blitz_traits::node_id::NodeId;
|
|
93
|
+
// Re-export taffy: it is part of blitz-dom's public API (e.g. `Node::style`,
|
|
94
|
+
// `Node::final_layout`)
|
|
95
|
+
pub use config::{DocumentConfig, StyleThreading};
|
|
96
|
+
pub use document::{
|
|
97
|
+
BaseDocument, BoundingRect, DocGuard, DocGuardMut, Document, PlainDocument, UserAgentStylesheet,
|
|
98
|
+
};
|
|
99
|
+
pub use markup5ever::{
|
|
100
|
+
LocalName, Namespace, NamespaceStaticSet, Prefix, PrefixStaticSet, QualName, local_name,
|
|
101
|
+
namespace_prefix, namespace_url, ns,
|
|
102
|
+
};
|
|
103
|
+
pub use mutator::DocumentMutator;
|
|
104
|
+
pub use node::{Attribute, DocumentData, ElementData, Node, NodeData, TextNodeData};
|
|
105
|
+
pub use parley::FontContext;
|
|
106
|
+
pub use scrolling::{ScrollBehavior, ScrollLogicalPosition};
|
|
107
|
+
pub use tree::NodeTree;
|
|
108
|
+
|
|
109
|
+
/// Convert a Blitz [`NodeId`] into a [`taffy::NodeId`] (which wraps a `u64`).
|
|
110
|
+
#[inline]
|
|
111
|
+
pub fn taffy_node_id(id: NodeId) -> taffy::NodeId {
|
|
112
|
+
taffy::NodeId::from(id.as_u64())
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/// Convert a [`taffy::NodeId`] produced by [`taffy_node_id`] back into a Blitz [`NodeId`].
|
|
116
|
+
#[inline]
|
|
117
|
+
pub fn dom_node_id(id: taffy::NodeId) -> NodeId {
|
|
118
|
+
NodeId::from_u64(u64::from(id))
|
|
119
|
+
}
|
|
120
|
+
pub use style::Atom;
|
|
121
|
+
pub use style::invalidation::element::restyle_hints::RestyleHint;
|
|
122
|
+
pub use style::media_queries::MediaType;
|
|
123
|
+
pub use style::stylist::RegisterCustomPropertyResult;
|
|
124
|
+
pub type SelectorList = selectors::SelectorList<style::selector_parser::SelectorImpl>;
|
|
125
|
+
pub use events::{EventDriver, EventHandler, NoopEventHandler};
|
|
126
|
+
pub use html::{DummyHtmlParserProvider, HtmlParserProvider};
|
|
127
|
+
pub use util::{Point, decode_font_bytes};
|
|
128
|
+
|
|
129
|
+
/// Convenience builder for the one-font case: produces a [`FontContext`] with
|
|
130
|
+
/// system-font discovery disabled and the supplied font registered as the
|
|
131
|
+
/// fallback for every generic family. The standard setup for WASM, where
|
|
132
|
+
/// browsers don't expose system fonts. WOFF/WOFF2 inputs are decoded
|
|
133
|
+
/// automatically.
|
|
134
|
+
pub fn build_single_font_ctx(font_data: &[u8]) -> FontContext {
|
|
135
|
+
use parley::fontique::{Blob, Collection, CollectionOptions, GenericFamily, SourceCache};
|
|
136
|
+
use std::sync::Arc;
|
|
137
|
+
|
|
138
|
+
let mut ctx = FontContext {
|
|
139
|
+
source_cache: SourceCache::new_shared(),
|
|
140
|
+
collection: Collection::new(CollectionOptions {
|
|
141
|
+
shared: false,
|
|
142
|
+
system_fonts: false,
|
|
143
|
+
}),
|
|
144
|
+
};
|
|
145
|
+
let decoded = decode_font_bytes(font_data).into_owned();
|
|
146
|
+
let registered = ctx
|
|
147
|
+
.collection
|
|
148
|
+
.register_fonts(Blob::new(Arc::new(decoded) as _), None);
|
|
149
|
+
let family_ids: Vec<_> = registered.iter().map(|(id, _)| *id).collect();
|
|
150
|
+
for generic in [
|
|
151
|
+
GenericFamily::SansSerif,
|
|
152
|
+
GenericFamily::Serif,
|
|
153
|
+
GenericFamily::Monospace,
|
|
154
|
+
GenericFamily::SystemUi,
|
|
155
|
+
] {
|
|
156
|
+
ctx.collection
|
|
157
|
+
.append_generic_families(generic, family_ids.iter().copied());
|
|
158
|
+
}
|
|
159
|
+
ctx
|
|
160
|
+
}
|