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.
Files changed (64) hide show
  1. package/README.md +6 -6
  2. package/THIRD_PARTY_NOTICES.md +5 -2
  3. package/docs/layout-regressions.md +65 -0
  4. package/docs/pagination-regressions.md +7 -6
  5. package/package.json +1 -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,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
+ }