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,1328 @@
|
|
|
1
|
+
use blitz_traits::node_id::NodeId;
|
|
2
|
+
use core::str;
|
|
3
|
+
use std::sync::Arc;
|
|
4
|
+
|
|
5
|
+
use markup5ever::{QualName, local_name};
|
|
6
|
+
use parley::{
|
|
7
|
+
FontContext, InlineBox, InlineBoxKind, LayoutContext, StyleProperty, TreeBuilder,
|
|
8
|
+
WhiteSpaceCollapse,
|
|
9
|
+
};
|
|
10
|
+
use style::{
|
|
11
|
+
computed_values::position::T as PositionProperty,
|
|
12
|
+
data::ElementData as StyloElementData,
|
|
13
|
+
shared_lock::StylesheetGuards,
|
|
14
|
+
values::{
|
|
15
|
+
computed::{Content, ContentItem, Display, Float},
|
|
16
|
+
specified::box_::{DisplayInside, DisplayOutside},
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
use thin_vec::ThinVec;
|
|
20
|
+
|
|
21
|
+
use crate::{
|
|
22
|
+
BaseDocument, ElementData, Node, NodeData,
|
|
23
|
+
layout::damage::{
|
|
24
|
+
CONSTRUCT_BOX, CONSTRUCT_DESCENDENT, CONSTRUCT_FC, sort_layout_children_by_order,
|
|
25
|
+
},
|
|
26
|
+
node::{
|
|
27
|
+
ListItemLayout, ListItemLayoutPosition, Marker, NodeFlags, NodeKind, SpecialElementData,
|
|
28
|
+
TextBrush, TextInputData, TextLayout,
|
|
29
|
+
},
|
|
30
|
+
qual_name, stylo_to_parley,
|
|
31
|
+
traversal::{iter_children, iter_children_and_pseudos},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
use super::{
|
|
35
|
+
damage::ALL_DAMAGE,
|
|
36
|
+
list::{BULLET_FONT_FAMILY, collect_list_item_children},
|
|
37
|
+
replaced::is_inline_box_element,
|
|
38
|
+
table::build_table_context,
|
|
39
|
+
text_transform::{CaseTransform, TextTransformer},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const DUMMY_NAME: QualName = qual_name!("div", html);
|
|
43
|
+
|
|
44
|
+
#[derive(Clone)]
|
|
45
|
+
pub(crate) struct ConstructionTask {
|
|
46
|
+
pub(crate) node_id: NodeId,
|
|
47
|
+
pub(crate) data: ConstructionTaskData,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
pub(crate) struct ConstructionTaskResult {
|
|
51
|
+
pub(crate) node_id: NodeId,
|
|
52
|
+
pub(crate) data: ConstructionTaskResultData,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
#[derive(Clone)]
|
|
56
|
+
pub(crate) enum ConstructionTaskData {
|
|
57
|
+
InlineLayout(Box<TextLayout>),
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
pub(crate) enum ConstructionTaskResultData {
|
|
61
|
+
InlineLayout(Box<TextLayout>),
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/// Accumulator threaded through layout-child collection.
|
|
65
|
+
///
|
|
66
|
+
/// `children` is the list of layout children being built up, and
|
|
67
|
+
/// `anonymous_block_id` is the currently-open anonymous block container (if
|
|
68
|
+
/// any) that wrapping children are being appended to.
|
|
69
|
+
#[derive(Default)]
|
|
70
|
+
pub(crate) struct LayoutChildren {
|
|
71
|
+
pub(crate) children: ThinVec<NodeId>,
|
|
72
|
+
pub(crate) anonymous_block_id: Option<NodeId>,
|
|
73
|
+
/// All anonymous blocks created while collecting these layout children.
|
|
74
|
+
///
|
|
75
|
+
/// These are recorded on the container node so they can be deallocated the
|
|
76
|
+
/// next time it is reconstructed.
|
|
77
|
+
pub(crate) anonymous_blocks: ThinVec<NodeId>,
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
impl LayoutChildren {
|
|
81
|
+
/// Append a single layout child.
|
|
82
|
+
fn push(&mut self, child_id: NodeId, doc: &mut BaseDocument) {
|
|
83
|
+
self.maybe_push_anon_block(doc);
|
|
84
|
+
self.children.push(child_id);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/// Append all layout children in `slice`.
|
|
88
|
+
fn extend(&mut self, slice: &[NodeId], doc: &mut BaseDocument) {
|
|
89
|
+
self.maybe_push_anon_block(doc);
|
|
90
|
+
self.children.extend_from_slice(slice);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
fn maybe_push_anon_block(&mut self, doc: &mut BaseDocument) {
|
|
94
|
+
fn block_is_only_whitespace(doc: &BaseDocument, node_id: NodeId) -> bool {
|
|
95
|
+
for child_id in doc.nodes[node_id].children.iter().copied() {
|
|
96
|
+
let child = &doc.nodes[child_id];
|
|
97
|
+
if !child.is_collapsible_whitespace_node() {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
true
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// If anonymous block node only contains whitespace then delete it
|
|
106
|
+
if let Some(anon_id) = self.anonymous_block_id {
|
|
107
|
+
if block_is_only_whitespace(doc, anon_id) {
|
|
108
|
+
// Remove by identity, not pop(): hoisted display:contents
|
|
109
|
+
// children may have been pushed after the anon block.
|
|
110
|
+
if let Some(pos) = self.children.iter().rposition(|id| *id == anon_id) {
|
|
111
|
+
self.children.remove(pos);
|
|
112
|
+
}
|
|
113
|
+
self.anonymous_blocks.retain(|id| *id != anon_id);
|
|
114
|
+
doc.remove_node_from_tree(anon_id);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
self.anonymous_block_id = None;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
fn push_wrapped(
|
|
122
|
+
&mut self,
|
|
123
|
+
container_node_id: NodeId,
|
|
124
|
+
child_id: NodeId,
|
|
125
|
+
doc: &mut BaseDocument,
|
|
126
|
+
) {
|
|
127
|
+
if self.anonymous_block_id.is_none() {
|
|
128
|
+
self.create_anonymous_block(container_node_id, doc);
|
|
129
|
+
}
|
|
130
|
+
doc.nodes[self.anonymous_block_id.unwrap()]
|
|
131
|
+
.children
|
|
132
|
+
.push(child_id);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
fn create_anonymous_block(&mut self, container_node_id: NodeId, doc: &mut BaseDocument) {
|
|
136
|
+
let node_id = create_anonymous_box(doc, container_node_id, Display::Block);
|
|
137
|
+
self.children.push(node_id);
|
|
138
|
+
self.anonymous_block_id = Some(node_id);
|
|
139
|
+
self.anonymous_blocks.push(node_id);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
pub(super) fn anonymous_box_style(
|
|
144
|
+
doc: &BaseDocument,
|
|
145
|
+
parent_id: NodeId,
|
|
146
|
+
display: Display,
|
|
147
|
+
) -> style::servo_arc::Arc<style::properties::ComputedValues> {
|
|
148
|
+
let parent_style = doc.nodes[parent_id].primary_styles().unwrap();
|
|
149
|
+
let read_guard = doc.guard.read();
|
|
150
|
+
let guards = StylesheetGuards::same(&read_guard);
|
|
151
|
+
let mut style = doc.stylist.style_for_anonymous::<&Node>(
|
|
152
|
+
&guards,
|
|
153
|
+
&style::selector_parser::PseudoElement::ServoAnonymousBox,
|
|
154
|
+
&parent_style,
|
|
155
|
+
);
|
|
156
|
+
let box_style = style::servo_arc::Arc::make_mut(&mut style).mutate_box();
|
|
157
|
+
box_style.set_display(display);
|
|
158
|
+
box_style.original_display = display;
|
|
159
|
+
style
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/// Create a layout-only wrapper without changing the DOM or selector matching.
|
|
163
|
+
/// Its owner must retain the ID in `anonymous_blocks` for reconstruction cleanup.
|
|
164
|
+
pub(super) fn create_anonymous_box(
|
|
165
|
+
doc: &mut BaseDocument,
|
|
166
|
+
parent_id: NodeId,
|
|
167
|
+
display: Display,
|
|
168
|
+
) -> NodeId {
|
|
169
|
+
let style = anonymous_box_style(doc, parent_id, display);
|
|
170
|
+
let node_id = doc.create_node(NodeData::AnonymousBlock(Box::new(ElementData::new(
|
|
171
|
+
DUMMY_NAME.clone(),
|
|
172
|
+
Vec::new(),
|
|
173
|
+
))));
|
|
174
|
+
let mut data = StyloElementData {
|
|
175
|
+
damage: ALL_DAMAGE,
|
|
176
|
+
..Default::default()
|
|
177
|
+
};
|
|
178
|
+
data.styles.primary = Some(style);
|
|
179
|
+
data.set_restyled();
|
|
180
|
+
*doc.nodes[node_id]
|
|
181
|
+
.stylo_element_data_mut()
|
|
182
|
+
.ensure_init_mut() = data;
|
|
183
|
+
if doc.nodes[parent_id]
|
|
184
|
+
.flags
|
|
185
|
+
.contains(NodeFlags::IS_IN_DOCUMENT)
|
|
186
|
+
{
|
|
187
|
+
doc.nodes[node_id].flags.insert(NodeFlags::IS_IN_DOCUMENT);
|
|
188
|
+
}
|
|
189
|
+
doc.nodes[node_id].parent = Some(parent_id);
|
|
190
|
+
doc.nodes[node_id].layout_parent.set(Some(parent_id));
|
|
191
|
+
node_id
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
fn push_children_and_pseudos(layout_children: &mut ThinVec<NodeId>, node: &Node) {
|
|
195
|
+
if let Some(before) = node.before() {
|
|
196
|
+
layout_children.push(before);
|
|
197
|
+
}
|
|
198
|
+
layout_children.extend(node.children.iter().copied().filter(|child_id| {
|
|
199
|
+
let child_node = node.with(*child_id);
|
|
200
|
+
child_node.data.kind() != NodeKind::Comment
|
|
201
|
+
}));
|
|
202
|
+
if let Some(after) = node.after() {
|
|
203
|
+
layout_children.push(after);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/// Wrapping policy of the nearest non-contents ancestor container. Children
|
|
208
|
+
/// hoisted out of display:contents nodes must be wrapped in anonymous blocks
|
|
209
|
+
/// exactly as if they were direct children of that container: without this,
|
|
210
|
+
/// a bare text node could end up as the layout child of a block/flex/grid
|
|
211
|
+
/// container, which cannot be laid out (text nodes carry no style).
|
|
212
|
+
#[derive(Copy, Clone)]
|
|
213
|
+
struct WrapContext {
|
|
214
|
+
/// The container whose style anonymous blocks inherit from.
|
|
215
|
+
container_node_id: NodeId,
|
|
216
|
+
needs_wrap: fn(NodeKind, DisplayOutside) -> bool,
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
fn block_item_needs_wrap(child_node_kind: NodeKind, display_outside: DisplayOutside) -> bool {
|
|
220
|
+
child_node_kind == NodeKind::Text || display_outside == DisplayOutside::Inline
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/// Used for flex/grid containers, and for flow containers whose in-flow
|
|
224
|
+
/// children are all out-of-flow (where inline elements are pushed raw, but
|
|
225
|
+
/// bare text still cannot be laid out directly).
|
|
226
|
+
fn text_item_needs_wrap(child_node_kind: NodeKind, _display_outside: DisplayOutside) -> bool {
|
|
227
|
+
child_node_kind == NodeKind::Text
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/// Push a single hoisted child, recursing through display:contents nodes and
|
|
231
|
+
/// wrapping text/inline children per the ancestor container's `WrapContext`.
|
|
232
|
+
fn push_hoisted_child(
|
|
233
|
+
doc: &mut BaseDocument,
|
|
234
|
+
child_id: NodeId,
|
|
235
|
+
out: &mut LayoutChildren,
|
|
236
|
+
wrap: Option<WrapContext>,
|
|
237
|
+
) {
|
|
238
|
+
let child = &doc.nodes[child_id];
|
|
239
|
+
let child_display = child.display_style().unwrap_or(Display::inline());
|
|
240
|
+
if matches!(child_display.inside(), DisplayInside::Contents) {
|
|
241
|
+
collect_layout_children_with_wrap(doc, child_id, out, wrap);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if let Some(wrap_ctx) = wrap {
|
|
245
|
+
let child_node_kind = child.data.kind();
|
|
246
|
+
let display_outside = if child.is_or_contains_block() {
|
|
247
|
+
DisplayOutside::Block
|
|
248
|
+
} else {
|
|
249
|
+
child_display.outside()
|
|
250
|
+
};
|
|
251
|
+
if (wrap_ctx.needs_wrap)(child_node_kind, display_outside) {
|
|
252
|
+
out.push_wrapped(wrap_ctx.container_node_id, child_id, doc);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
out.push(child_id, doc);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/// Push the container's children (and ::before/::after pseudos) as layout
|
|
260
|
+
/// children, hoisting transparently through display:contents nodes and
|
|
261
|
+
/// filtering out comments and whitespace.
|
|
262
|
+
fn push_hoisted_children_and_pseudos(
|
|
263
|
+
doc: &mut BaseDocument,
|
|
264
|
+
container_node_id: NodeId,
|
|
265
|
+
out: &mut LayoutChildren,
|
|
266
|
+
wrap: Option<WrapContext>,
|
|
267
|
+
) {
|
|
268
|
+
if let Some(before) = doc.nodes[container_node_id].before() {
|
|
269
|
+
push_hoisted_child(doc, before, out, wrap);
|
|
270
|
+
}
|
|
271
|
+
// Take children array from node to avoid borrow checker issues.
|
|
272
|
+
let children = std::mem::take(&mut doc.nodes[container_node_id].children);
|
|
273
|
+
for child_id in children.iter().copied() {
|
|
274
|
+
let child = &doc.nodes[child_id];
|
|
275
|
+
if child.data.kind() == NodeKind::Comment || child.is_collapsible_whitespace_node() {
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
push_hoisted_child(doc, child_id, out, wrap);
|
|
279
|
+
}
|
|
280
|
+
doc.nodes[container_node_id].children = children;
|
|
281
|
+
if let Some(after) = doc.nodes[container_node_id].after() {
|
|
282
|
+
push_hoisted_child(doc, after, out, wrap);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
fn push_non_whitespace_children_and_pseudos(layout_children: &mut ThinVec<NodeId>, node: &Node) {
|
|
287
|
+
if let Some(before) = node.before() {
|
|
288
|
+
layout_children.push(before);
|
|
289
|
+
}
|
|
290
|
+
layout_children.extend(node.children.iter().copied().filter(|child_id| {
|
|
291
|
+
let child_node = node.with(*child_id);
|
|
292
|
+
!child_node.is_whitespace_node() && child_node.data.kind() != NodeKind::Comment
|
|
293
|
+
}));
|
|
294
|
+
if let Some(after) = node.after() {
|
|
295
|
+
layout_children.push(after);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/// Result of classifying the in-flow children of a flow container as
|
|
300
|
+
/// all-block, all-inline and/or all-out-of-flow.
|
|
301
|
+
struct FlowClassification {
|
|
302
|
+
all_block: bool,
|
|
303
|
+
all_inline: bool,
|
|
304
|
+
all_out_of_flow: bool,
|
|
305
|
+
has_contents: bool,
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
impl Default for FlowClassification {
|
|
309
|
+
fn default() -> Self {
|
|
310
|
+
Self {
|
|
311
|
+
all_block: true,
|
|
312
|
+
all_inline: true,
|
|
313
|
+
all_out_of_flow: true,
|
|
314
|
+
has_contents: false,
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/// Classify a flow container's children (including its ::before/::after
|
|
320
|
+
/// pseudo-elements) for inline-vs-block layout, recursing transparently
|
|
321
|
+
/// through display:contents nodes (whose children participate in the
|
|
322
|
+
/// container's formatting context).
|
|
323
|
+
fn classify_flow_children(
|
|
324
|
+
doc: &BaseDocument,
|
|
325
|
+
container_node_id: NodeId,
|
|
326
|
+
classification: &mut FlowClassification,
|
|
327
|
+
) {
|
|
328
|
+
let node = &doc.nodes[container_node_id];
|
|
329
|
+
// ::before/::after pseudos with display:contents are transparent for box
|
|
330
|
+
// generation, so their children (e.g. generated text) participate in the
|
|
331
|
+
// container's formatting context and vote in the classification. Pseudos
|
|
332
|
+
// with any other display value generate their own box, which every
|
|
333
|
+
// construction arm already pushes explicitly, so they cast no vote.
|
|
334
|
+
let pseudo_ids = node
|
|
335
|
+
.before()
|
|
336
|
+
.into_iter()
|
|
337
|
+
.chain(node.after())
|
|
338
|
+
.filter(|pe_id| {
|
|
339
|
+
let display = doc.nodes[*pe_id]
|
|
340
|
+
.display_style()
|
|
341
|
+
.unwrap_or(Display::inline());
|
|
342
|
+
matches!(display.inside(), DisplayInside::Contents)
|
|
343
|
+
});
|
|
344
|
+
let child_ids = node.children.iter().copied().chain(pseudo_ids);
|
|
345
|
+
for child_id in child_ids {
|
|
346
|
+
let child = &doc.nodes[child_id];
|
|
347
|
+
|
|
348
|
+
// Comment nodes generate no boxes and must not affect the
|
|
349
|
+
// inline-vs-block classification: an unstyled comment would
|
|
350
|
+
// default to display:inline below and force an inline
|
|
351
|
+
// formatting context on the container, swallowing element
|
|
352
|
+
// siblings into the inline layout (zero-sizing any
|
|
353
|
+
// out-of-flow ones).
|
|
354
|
+
if child.data.kind() == NodeKind::Comment {
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Unwraps on Text and SVG nodes
|
|
359
|
+
let style = child.primary_styles();
|
|
360
|
+
let style = style.as_ref();
|
|
361
|
+
let display = child.display_style().unwrap_or(Display::inline());
|
|
362
|
+
if matches!(display.inside(), DisplayInside::Contents) {
|
|
363
|
+
// Transparent for box generation: the contents node casts
|
|
364
|
+
// no vote itself — its children decide.
|
|
365
|
+
classification.has_contents = true;
|
|
366
|
+
classify_flow_children(doc, child_id, classification);
|
|
367
|
+
} else if matches!(display.inside(), DisplayInside::None) {
|
|
368
|
+
// display:none children generate no boxes and cast no vote.
|
|
369
|
+
continue;
|
|
370
|
+
} else {
|
|
371
|
+
let position = style
|
|
372
|
+
.map(|s| s.clone_position())
|
|
373
|
+
.unwrap_or(PositionProperty::Static);
|
|
374
|
+
let float = style.map(|s| s.clone_float()).unwrap_or(Float::None);
|
|
375
|
+
|
|
376
|
+
// Ignore nodes whose whitespace is entirely collapsed away
|
|
377
|
+
if child.is_collapsible_whitespace_node() {
|
|
378
|
+
continue;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// display:none children generate no boxes and cast no vote
|
|
382
|
+
if matches!(display.outside(), DisplayOutside::None) {
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
let is_in_flow = matches!(
|
|
387
|
+
position,
|
|
388
|
+
PositionProperty::Static | PositionProperty::Relative | PositionProperty::Sticky
|
|
389
|
+
) && matches!(float, Float::None);
|
|
390
|
+
|
|
391
|
+
if !is_in_flow {
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
classification.all_out_of_flow = false;
|
|
396
|
+
match display.outside() {
|
|
397
|
+
DisplayOutside::None => {}
|
|
398
|
+
DisplayOutside::Block
|
|
399
|
+
| DisplayOutside::TableCaption
|
|
400
|
+
| DisplayOutside::InternalTable => classification.all_inline = false,
|
|
401
|
+
DisplayOutside::Inline => {
|
|
402
|
+
classification.all_block = false;
|
|
403
|
+
|
|
404
|
+
// We need the "complex" tree fixing when an inline contains a block
|
|
405
|
+
if child.is_or_contains_block() {
|
|
406
|
+
classification.all_inline = false;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
pub(crate) fn collect_layout_children(
|
|
415
|
+
doc: &mut BaseDocument,
|
|
416
|
+
container_node_id: NodeId,
|
|
417
|
+
out: &mut LayoutChildren,
|
|
418
|
+
) {
|
|
419
|
+
collect_layout_children_with_wrap(doc, container_node_id, out, None)
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
fn collect_layout_children_with_wrap(
|
|
423
|
+
doc: &mut BaseDocument,
|
|
424
|
+
container_node_id: NodeId,
|
|
425
|
+
out: &mut LayoutChildren,
|
|
426
|
+
wrap: Option<WrapContext>,
|
|
427
|
+
) {
|
|
428
|
+
// Record the `display` the box is being constructed with
|
|
429
|
+
if let Some(display) = doc.nodes[container_node_id].display_style() {
|
|
430
|
+
*doc.nodes[container_node_id].display_constructed_as_mut() = display;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Reset construction flags
|
|
434
|
+
// TODO: make incremental and only remove this if the element is no longer an inline root
|
|
435
|
+
doc.nodes[container_node_id]
|
|
436
|
+
.flags
|
|
437
|
+
.reset_construction_flags();
|
|
438
|
+
if let Some(element_data) = doc.nodes[container_node_id].element_data_mut() {
|
|
439
|
+
element_data.take_inline_layout();
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
flush_pseudo_elements(doc, container_node_id);
|
|
443
|
+
|
|
444
|
+
if let Some(el) = doc.nodes[container_node_id].data.downcast_element() {
|
|
445
|
+
// Handle text inputs
|
|
446
|
+
let tag_name = el.name.local.as_ref();
|
|
447
|
+
if matches!(tag_name, "input" | "textarea") {
|
|
448
|
+
let type_attr: Option<&str> = doc.nodes[container_node_id]
|
|
449
|
+
.data
|
|
450
|
+
.downcast_element()
|
|
451
|
+
.and_then(|el| el.attr(local_name!("type")));
|
|
452
|
+
if tag_name == "textarea" {
|
|
453
|
+
create_text_editor(doc, container_node_id, true);
|
|
454
|
+
return;
|
|
455
|
+
} else if matches!(
|
|
456
|
+
type_attr,
|
|
457
|
+
None | Some("text" | "password" | "email" | "number" | "search" | "tel" | "url")
|
|
458
|
+
) {
|
|
459
|
+
create_text_editor(doc, container_node_id, false);
|
|
460
|
+
return;
|
|
461
|
+
} else if matches!(type_attr, Some("checkbox" | "radio")) {
|
|
462
|
+
create_checkbox_input(doc, container_node_id);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
#[cfg(feature = "svg")]
|
|
468
|
+
if matches!(tag_name, "svg") {
|
|
469
|
+
let outer_html = doc.get_node(container_node_id).unwrap().svg_source();
|
|
470
|
+
|
|
471
|
+
// Remove contruction damage from subtree
|
|
472
|
+
doc.iter_subtree_mut(container_node_id, |id: NodeId, doc: &mut BaseDocument| {
|
|
473
|
+
doc.nodes[id].remove_damage(CONSTRUCT_BOX | CONSTRUCT_DESCENDENT | CONSTRUCT_FC);
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
match crate::util::parse_svg_image(outer_html.as_bytes()) {
|
|
477
|
+
Ok(svg) => {
|
|
478
|
+
doc.get_node_mut(container_node_id)
|
|
479
|
+
.unwrap()
|
|
480
|
+
.element_data_mut()
|
|
481
|
+
.unwrap()
|
|
482
|
+
.special_data =
|
|
483
|
+
SpecialElementData::Image(Box::new(crate::node::ImageData::Svg(svg)));
|
|
484
|
+
}
|
|
485
|
+
Err(err) => {
|
|
486
|
+
#[cfg(feature = "tracing")]
|
|
487
|
+
tracing::warn!(
|
|
488
|
+
node_id = ?container_node_id,
|
|
489
|
+
html = outer_html,
|
|
490
|
+
error = ?err,
|
|
491
|
+
"SVG parse failed",
|
|
492
|
+
);
|
|
493
|
+
#[cfg(not(feature = "tracing"))]
|
|
494
|
+
let _ = err;
|
|
495
|
+
}
|
|
496
|
+
};
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
//Only ol tags have start and reversed attributes
|
|
501
|
+
let (mut index, reversed) = if tag_name == "ol" {
|
|
502
|
+
(
|
|
503
|
+
el.attr_parsed(local_name!("start"))
|
|
504
|
+
.map(|start: usize| start - 1)
|
|
505
|
+
.unwrap_or(0),
|
|
506
|
+
el.attr_parsed(local_name!("reversed")).unwrap_or(false),
|
|
507
|
+
)
|
|
508
|
+
} else {
|
|
509
|
+
(0, false)
|
|
510
|
+
};
|
|
511
|
+
collect_list_item_children(doc, &mut index, reversed, container_node_id);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// Skip further construction if the node has no children or psuedo-children
|
|
515
|
+
{
|
|
516
|
+
let node = &doc.nodes[container_node_id];
|
|
517
|
+
if node.children.is_empty() && node.before().is_none() && node.after().is_none() {
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
let container_display = doc.nodes[container_node_id].display_style().unwrap_or(
|
|
523
|
+
match doc.nodes[container_node_id].data.kind() {
|
|
524
|
+
NodeKind::AnonymousBlock => Display::Block,
|
|
525
|
+
_ => Display::Inline,
|
|
526
|
+
},
|
|
527
|
+
);
|
|
528
|
+
|
|
529
|
+
match container_display.inside() {
|
|
530
|
+
DisplayInside::None => {}
|
|
531
|
+
DisplayInside::Contents => {
|
|
532
|
+
doc.nodes[container_node_id]
|
|
533
|
+
.remove_damage(CONSTRUCT_BOX | CONSTRUCT_DESCENDENT | CONSTRUCT_FC);
|
|
534
|
+
// display:contents is transparent for box generation: hoist the
|
|
535
|
+
// children THEMSELVES (not their layout children) into the
|
|
536
|
+
// parent, recursing only through nested contents nodes.
|
|
537
|
+
push_hoisted_children_and_pseudos(doc, container_node_id, out, wrap);
|
|
538
|
+
}
|
|
539
|
+
DisplayInside::Flex | DisplayInside::Grid => {
|
|
540
|
+
// ::before/::after pseudos must be checked too: a pseudo with
|
|
541
|
+
// display:contents hoists its text content into the container.
|
|
542
|
+
let container = &doc.nodes[container_node_id];
|
|
543
|
+
let has_text_node_or_contents = container
|
|
544
|
+
.children
|
|
545
|
+
.iter()
|
|
546
|
+
.copied()
|
|
547
|
+
.chain(container.before())
|
|
548
|
+
.chain(container.after())
|
|
549
|
+
.map(|child_id| &doc.nodes[child_id])
|
|
550
|
+
.any(|child| {
|
|
551
|
+
let display = child.display_style().unwrap_or(Display::inline());
|
|
552
|
+
let node_kind = child.data.kind();
|
|
553
|
+
display.inside() == DisplayInside::Contents || node_kind == NodeKind::Text
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
if !has_text_node_or_contents {
|
|
557
|
+
push_non_whitespace_children_and_pseudos(
|
|
558
|
+
&mut out.children,
|
|
559
|
+
&doc.nodes[container_node_id],
|
|
560
|
+
);
|
|
561
|
+
} else {
|
|
562
|
+
collect_complex_layout_children(
|
|
563
|
+
doc,
|
|
564
|
+
container_node_id,
|
|
565
|
+
out,
|
|
566
|
+
true,
|
|
567
|
+
text_item_needs_wrap,
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// Flex/grid items are laid out in order-modified document order.
|
|
572
|
+
sort_layout_children_by_order(&doc.nodes, &mut out.children);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
DisplayInside::Table => {
|
|
576
|
+
let (table_context, tlayout_children) =
|
|
577
|
+
build_table_context(doc, container_node_id, &mut out.anonymous_blocks);
|
|
578
|
+
#[allow(clippy::arc_with_non_send_sync)]
|
|
579
|
+
let data = SpecialElementData::TableRoot(Arc::new(table_context));
|
|
580
|
+
doc.nodes[container_node_id]
|
|
581
|
+
.flags
|
|
582
|
+
.insert(NodeFlags::IS_TABLE_ROOT);
|
|
583
|
+
doc.nodes[container_node_id]
|
|
584
|
+
.data
|
|
585
|
+
.downcast_element_mut()
|
|
586
|
+
.unwrap()
|
|
587
|
+
.special_data = data;
|
|
588
|
+
if let Some(before) = doc.nodes[container_node_id].before() {
|
|
589
|
+
out.push(before, doc);
|
|
590
|
+
}
|
|
591
|
+
out.extend(&tlayout_children, doc);
|
|
592
|
+
if let Some(after) = doc.nodes[container_node_id].after() {
|
|
593
|
+
out.push(after, doc);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// Flow, FlowRoot and TableCell, plus internal table displays (row,
|
|
598
|
+
// row group, column, ...) occurring outside of a table. Blitz does
|
|
599
|
+
// not yet generate anonymous table wrapper boxes for the latter, so
|
|
600
|
+
// they are laid out as flow containers, which crucially ensures
|
|
601
|
+
// their text children get wrapped rather than being pushed as bare
|
|
602
|
+
// layout children (text nodes carry no style and cannot be laid out
|
|
603
|
+
// as block/flex/grid items).
|
|
604
|
+
_ => {
|
|
605
|
+
let mut classification = FlowClassification::default();
|
|
606
|
+
classify_flow_children(doc, container_node_id, &mut classification);
|
|
607
|
+
|
|
608
|
+
if classification.all_out_of_flow {
|
|
609
|
+
// Contents-transparent: a display:contents child may be
|
|
610
|
+
// holding the out-of-flow elements (otherwise the contents
|
|
611
|
+
// node itself would be pushed as a layout box).
|
|
612
|
+
let wrap = Some(WrapContext {
|
|
613
|
+
container_node_id,
|
|
614
|
+
needs_wrap: text_item_needs_wrap,
|
|
615
|
+
});
|
|
616
|
+
return push_hoisted_children_and_pseudos(doc, container_node_id, out, wrap);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// TODO: fix display:contents
|
|
620
|
+
if classification.all_inline {
|
|
621
|
+
let existing_layout = doc.nodes[container_node_id]
|
|
622
|
+
.element_data_mut()
|
|
623
|
+
.and_then(|el| el.inline_layout_data.take());
|
|
624
|
+
let layout = existing_layout.unwrap_or_else(|| Box::new(TextLayout::new()));
|
|
625
|
+
|
|
626
|
+
// Queue node for inline layout construction. Deferring construction of inline layouts to a
|
|
627
|
+
// dedicated phase allows us to multithread the expensive text shaping step.
|
|
628
|
+
doc.deferred_construction_nodes.push(ConstructionTask {
|
|
629
|
+
node_id: container_node_id,
|
|
630
|
+
data: ConstructionTaskData::InlineLayout(layout),
|
|
631
|
+
});
|
|
632
|
+
doc.nodes[container_node_id]
|
|
633
|
+
.flags
|
|
634
|
+
.insert(NodeFlags::IS_INLINE_ROOT);
|
|
635
|
+
|
|
636
|
+
find_inline_layout_embedded_boxes(doc, container_node_id, &mut out.children);
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// If the children are either all inline or all block then simply return the regular children
|
|
641
|
+
// as the layout children
|
|
642
|
+
if classification.all_block & !classification.has_contents {
|
|
643
|
+
return push_non_whitespace_children_and_pseudos(
|
|
644
|
+
&mut out.children,
|
|
645
|
+
&doc.nodes[container_node_id],
|
|
646
|
+
);
|
|
647
|
+
} else if classification.all_inline & !classification.has_contents {
|
|
648
|
+
return push_children_and_pseudos(&mut out.children, &doc.nodes[container_node_id]);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
collect_complex_layout_children(
|
|
652
|
+
doc,
|
|
653
|
+
container_node_id,
|
|
654
|
+
out,
|
|
655
|
+
false,
|
|
656
|
+
block_item_needs_wrap,
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/// Extract the text generated by a pseudo-element's `content` property
|
|
663
|
+
/// (only string content items are currently supported; other item types
|
|
664
|
+
/// are skipped).
|
|
665
|
+
fn pe_content_text(style: &style::properties::ComputedValues) -> Option<String> {
|
|
666
|
+
match &style.get_counters().content {
|
|
667
|
+
Content::Items(item_data) => {
|
|
668
|
+
let items = &item_data.items[0..item_data.alt_start];
|
|
669
|
+
let mut text = String::new();
|
|
670
|
+
let mut has_string = false;
|
|
671
|
+
for item in items {
|
|
672
|
+
// TODO: other types of content
|
|
673
|
+
if let ContentItem::String(owned_str) = item {
|
|
674
|
+
text.push_str(owned_str);
|
|
675
|
+
has_string = true;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
has_string.then_some(text)
|
|
679
|
+
}
|
|
680
|
+
_ => None,
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
fn flush_pseudo_elements(doc: &mut BaseDocument, node_id: NodeId) {
|
|
685
|
+
let (before_style, after_style, before_node_id, after_node_id) = {
|
|
686
|
+
let node = &doc.nodes[node_id];
|
|
687
|
+
|
|
688
|
+
let before_node_id = node.before();
|
|
689
|
+
let after_node_id = node.after();
|
|
690
|
+
|
|
691
|
+
// Note: yes these are kinda backwards
|
|
692
|
+
let style_data = node.try_stylo_element_data().and_then(|s| s.get());
|
|
693
|
+
let before_style = style_data
|
|
694
|
+
.as_ref()
|
|
695
|
+
.and_then(|d| d.styles.pseudos.as_array()[1].clone());
|
|
696
|
+
let after_style = style_data
|
|
697
|
+
.as_ref()
|
|
698
|
+
.and_then(|d| d.styles.pseudos.as_array()[0].clone());
|
|
699
|
+
|
|
700
|
+
(before_style, after_style, before_node_id, after_node_id)
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
// Sync pseudo element
|
|
704
|
+
// TODO: Make incremental
|
|
705
|
+
for (idx, pe_style, pe_node_id) in [
|
|
706
|
+
(1, before_style, before_node_id),
|
|
707
|
+
(0, after_style, after_node_id),
|
|
708
|
+
] {
|
|
709
|
+
// Delete psuedo element if it exists but shouldn't
|
|
710
|
+
if let (Some(pe_node_id), None) = (pe_node_id, &pe_style) {
|
|
711
|
+
doc.remove_and_drop_pe(pe_node_id);
|
|
712
|
+
let node = &mut doc.nodes[node_id];
|
|
713
|
+
node.set_pe_by_index(idx, None);
|
|
714
|
+
node.insert_damage(ALL_DAMAGE);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// Create pseudo element if it should exist but doesn't
|
|
718
|
+
if let (None, Some(pe_style)) = (pe_node_id, &pe_style) {
|
|
719
|
+
let new_node_id = doc.create_node(NodeData::AnonymousBlock(Box::new(
|
|
720
|
+
ElementData::new(DUMMY_NAME, Vec::new()),
|
|
721
|
+
)));
|
|
722
|
+
doc.nodes[new_node_id].parent = Some(node_id);
|
|
723
|
+
doc.nodes[new_node_id].layout_parent.set(Some(node_id));
|
|
724
|
+
if doc.nodes[node_id].flags.contains(NodeFlags::IS_IN_DOCUMENT) {
|
|
725
|
+
doc.nodes[new_node_id]
|
|
726
|
+
.flags
|
|
727
|
+
.insert(NodeFlags::IS_IN_DOCUMENT);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
if let Some(text) = pe_content_text(pe_style) {
|
|
731
|
+
let text_node_id = doc.create_text_node(&text);
|
|
732
|
+
doc.nodes[text_node_id].parent = Some(new_node_id);
|
|
733
|
+
doc.nodes[new_node_id].children.push(text_node_id);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
let mut element_data = StyloElementData::default();
|
|
737
|
+
element_data.styles.primary = Some(pe_style.clone());
|
|
738
|
+
element_data.set_restyled();
|
|
739
|
+
element_data.damage = ALL_DAMAGE;
|
|
740
|
+
*doc.nodes[new_node_id]
|
|
741
|
+
.stylo_element_data_mut()
|
|
742
|
+
.ensure_init_mut() = element_data;
|
|
743
|
+
|
|
744
|
+
let node = &mut doc.nodes[node_id];
|
|
745
|
+
node.set_pe_by_index(idx, Some(new_node_id));
|
|
746
|
+
node.insert_damage(ALL_DAMAGE);
|
|
747
|
+
|
|
748
|
+
doc.pending_style_image_nodes.push(new_node_id);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// Else: Update psuedo element
|
|
752
|
+
if let (Some(pe_node_id), Some(pe_style)) = (pe_node_id, pe_style) {
|
|
753
|
+
// Sync the pseudo-element's text node with its `content` style, which
|
|
754
|
+
// may have changed (e.g. `details[open] summary::after { content: ... }`).
|
|
755
|
+
//
|
|
756
|
+
// Note: this deliberately compares the text itself rather than relying on
|
|
757
|
+
// the style-pointer comparison below, as the pseudo-element's style may
|
|
758
|
+
// already have been updated by `sync_pseudo_element_styles` during the
|
|
759
|
+
// style traversal without the text having been updated.
|
|
760
|
+
let new_text = pe_content_text(&pe_style);
|
|
761
|
+
let existing_text_node_id = doc.nodes[pe_node_id]
|
|
762
|
+
.children
|
|
763
|
+
.first()
|
|
764
|
+
.copied()
|
|
765
|
+
.filter(|&child_id| doc.nodes[child_id].is_text_node());
|
|
766
|
+
match (existing_text_node_id, new_text) {
|
|
767
|
+
(Some(text_node_id), Some(new_text)) => {
|
|
768
|
+
let text_data = doc.nodes[text_node_id].text_data_mut().unwrap();
|
|
769
|
+
if text_data.content != new_text {
|
|
770
|
+
text_data.content = new_text;
|
|
771
|
+
doc.nodes[node_id].insert_damage(ALL_DAMAGE);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
(None, Some(new_text)) => {
|
|
775
|
+
let text_node_id = doc.create_text_node(&new_text);
|
|
776
|
+
doc.nodes[text_node_id].parent = Some(pe_node_id);
|
|
777
|
+
doc.nodes[pe_node_id].children.push(text_node_id);
|
|
778
|
+
doc.nodes[node_id].insert_damage(ALL_DAMAGE);
|
|
779
|
+
}
|
|
780
|
+
(Some(text_node_id), None) => {
|
|
781
|
+
doc.nodes[pe_node_id]
|
|
782
|
+
.children
|
|
783
|
+
.retain(|&child_id| child_id != text_node_id);
|
|
784
|
+
doc.remove_node_from_tree(text_node_id);
|
|
785
|
+
doc.nodes[node_id].insert_damage(ALL_DAMAGE);
|
|
786
|
+
}
|
|
787
|
+
(None, None) => {}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
let mut node_styles = doc.nodes[pe_node_id]
|
|
791
|
+
.try_stylo_element_data_mut()
|
|
792
|
+
.and_then(|s| s.get_mut());
|
|
793
|
+
let node_styles = &mut node_styles.as_mut().unwrap();
|
|
794
|
+
node_styles.damage.insert(ALL_DAMAGE);
|
|
795
|
+
let primary_styles = &mut node_styles.styles.primary;
|
|
796
|
+
|
|
797
|
+
if !std::ptr::eq(&**primary_styles.as_ref().unwrap(), &*pe_style) {
|
|
798
|
+
*primary_styles = Some(pe_style);
|
|
799
|
+
node_styles.set_restyled();
|
|
800
|
+
doc.pending_style_image_nodes.push(pe_node_id);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
/// Handles the cases where there are text nodes or inline nodes that need to be wrapped in an anonymous block node
|
|
807
|
+
fn collect_complex_layout_children(
|
|
808
|
+
doc: &mut BaseDocument,
|
|
809
|
+
container_node_id: NodeId,
|
|
810
|
+
out: &mut LayoutChildren,
|
|
811
|
+
hide_whitespace: bool,
|
|
812
|
+
needs_wrap: fn(NodeKind, DisplayOutside) -> bool,
|
|
813
|
+
) {
|
|
814
|
+
doc.iter_children_and_pseudos_mut(container_node_id, |child_id, doc| {
|
|
815
|
+
// Get node kind (text, element, comment, etc)
|
|
816
|
+
let child_node_kind = doc.nodes[child_id].data.kind();
|
|
817
|
+
|
|
818
|
+
// Get Display style. Default to inline because nodes without styles are probably text nodes
|
|
819
|
+
let contains_block = doc.nodes[child_id].is_or_contains_block();
|
|
820
|
+
let child_display = &doc.nodes[child_id]
|
|
821
|
+
.display_style()
|
|
822
|
+
.unwrap_or(Display::inline());
|
|
823
|
+
let display_inside = child_display.inside();
|
|
824
|
+
let display_outside = if contains_block {
|
|
825
|
+
DisplayOutside::Block
|
|
826
|
+
} else {
|
|
827
|
+
child_display.outside()
|
|
828
|
+
};
|
|
829
|
+
|
|
830
|
+
let is_whitespace_node = doc.nodes[child_id].is_whitespace_node();
|
|
831
|
+
|
|
832
|
+
// Skip comment nodes. Note that we do *not* skip `Display::None` nodes as they may need to be hidden.
|
|
833
|
+
// Taffy knows how to deal with `Display::None` children.
|
|
834
|
+
//
|
|
835
|
+
// Also hide all-whitespace flexbox children as these should be ignored
|
|
836
|
+
if child_node_kind == NodeKind::Comment || (hide_whitespace && is_whitespace_node) {
|
|
837
|
+
// return;
|
|
838
|
+
}
|
|
839
|
+
// Recurse into `Display::Contents` nodes, wrapping hoisted text and
|
|
840
|
+
// inline children as if they were direct children of this container
|
|
841
|
+
else if display_inside == DisplayInside::Contents {
|
|
842
|
+
let wrap = Some(WrapContext {
|
|
843
|
+
container_node_id,
|
|
844
|
+
needs_wrap,
|
|
845
|
+
});
|
|
846
|
+
collect_layout_children_with_wrap(doc, child_id, out, wrap)
|
|
847
|
+
}
|
|
848
|
+
// Push nodes that need wrapping into the current "anonymous block container".
|
|
849
|
+
// If there is not an open one then we create one.
|
|
850
|
+
else if needs_wrap(child_node_kind, display_outside) {
|
|
851
|
+
out.push_wrapped(container_node_id, child_id, doc);
|
|
852
|
+
}
|
|
853
|
+
// Else push the child directly (and close any open "anonymous block container")
|
|
854
|
+
else {
|
|
855
|
+
out.push(child_id, doc);
|
|
856
|
+
}
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
// If anonymous block node only contains whitespace then delete it, else push it
|
|
860
|
+
out.maybe_push_anon_block(doc);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
fn create_text_editor(doc: &mut BaseDocument, input_element_id: NodeId, is_multiline: bool) {
|
|
864
|
+
let node = &mut doc.nodes[input_element_id];
|
|
865
|
+
let parley_style = node
|
|
866
|
+
.primary_styles()
|
|
867
|
+
.as_ref()
|
|
868
|
+
.map(|s| stylo_to_parley::style(node.id, s))
|
|
869
|
+
.unwrap_or_default();
|
|
870
|
+
let alignment = node
|
|
871
|
+
.primary_styles()
|
|
872
|
+
.map(|s| stylo_to_parley::text_align(s.clone_text_align()))
|
|
873
|
+
.unwrap_or(parley::layout::Alignment::Start);
|
|
874
|
+
let base_direction = node
|
|
875
|
+
.primary_styles()
|
|
876
|
+
.map(|s| stylo_to_parley::base_direction(s.clone_direction(), s.clone_unicode_bidi()))
|
|
877
|
+
.unwrap_or(parley::BaseDirection::Auto);
|
|
878
|
+
|
|
879
|
+
let initial_text = if is_multiline {
|
|
880
|
+
node.text_content()
|
|
881
|
+
} else {
|
|
882
|
+
node.attr(local_name!("value")).unwrap_or("").to_string()
|
|
883
|
+
};
|
|
884
|
+
let element = &mut node.data.downcast_element_mut().unwrap();
|
|
885
|
+
if !matches!(element.special_data, SpecialElementData::TextInput(_)) {
|
|
886
|
+
let mut text_input_data = TextInputData::new(is_multiline);
|
|
887
|
+
let editor = &mut text_input_data.editor;
|
|
888
|
+
editor.set_text(&initial_text);
|
|
889
|
+
element.special_data = SpecialElementData::TextInput(text_input_data);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
let SpecialElementData::TextInput(text_input_data) = &mut element.special_data else {
|
|
893
|
+
unreachable!();
|
|
894
|
+
};
|
|
895
|
+
|
|
896
|
+
let editor = &mut text_input_data.editor;
|
|
897
|
+
editor.set_scale(doc.viewport.scale_f64() as f32);
|
|
898
|
+
editor.set_width(None);
|
|
899
|
+
|
|
900
|
+
let styles = editor.edit_styles();
|
|
901
|
+
styles.retain(|_| false);
|
|
902
|
+
styles.insert(StyleProperty::FontFamily(parley_style.font_family));
|
|
903
|
+
styles.insert(StyleProperty::FontSize(parley_style.font_size));
|
|
904
|
+
styles.insert(StyleProperty::FontWidth(parley_style.font_width));
|
|
905
|
+
styles.insert(StyleProperty::FontStyle(parley_style.font_style));
|
|
906
|
+
styles.insert(StyleProperty::FontWeight(parley_style.font_weight));
|
|
907
|
+
styles.insert(StyleProperty::FontVariations(parley_style.font_variations));
|
|
908
|
+
styles.insert(StyleProperty::FontFeatures(parley_style.font_features));
|
|
909
|
+
styles.insert(StyleProperty::LineHeight(parley_style.line_height));
|
|
910
|
+
styles.insert(StyleProperty::WordSpacing(parley_style.word_spacing));
|
|
911
|
+
styles.insert(StyleProperty::LetterSpacing(parley_style.letter_spacing));
|
|
912
|
+
styles.insert(StyleProperty::WordBreak(parley_style.word_break));
|
|
913
|
+
styles.insert(StyleProperty::OverflowWrap(parley_style.overflow_wrap));
|
|
914
|
+
styles.insert(StyleProperty::TextWrapMode(parley_style.text_wrap_mode));
|
|
915
|
+
styles.insert(StyleProperty::WhiteSpaceCollapse(
|
|
916
|
+
parley_style.white_space_collapse,
|
|
917
|
+
));
|
|
918
|
+
styles.insert(StyleProperty::Brush(parley_style.brush));
|
|
919
|
+
editor.set_alignment(alignment);
|
|
920
|
+
editor.set_base_direction(base_direction);
|
|
921
|
+
|
|
922
|
+
editor.refresh_layout(&mut doc.font_ctx.lock().unwrap(), &mut doc.layout_ctx);
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
fn create_checkbox_input(doc: &mut BaseDocument, input_element_id: NodeId) {
|
|
926
|
+
let node = &mut doc.nodes[input_element_id];
|
|
927
|
+
|
|
928
|
+
let element = &mut node.data.downcast_element_mut().unwrap();
|
|
929
|
+
if !matches!(element.special_data, SpecialElementData::CheckboxInput(_)) {
|
|
930
|
+
let checked = element.has_attr(local_name!("checked"));
|
|
931
|
+
element.special_data = SpecialElementData::CheckboxInput(checked);
|
|
932
|
+
element.set_checkbox_input_checked(checked);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
/// Find and return the "layout_children" (inline boxes) for an inline layout
|
|
937
|
+
/// without actually constructing the layout. This allows us to defer the expensive
|
|
938
|
+
/// construction of the Parley layout (which invokes text shaping) to a paralell phase.
|
|
939
|
+
pub(crate) fn find_inline_layout_embedded_boxes(
|
|
940
|
+
doc: &mut BaseDocument,
|
|
941
|
+
inline_context_root_node_id: NodeId,
|
|
942
|
+
layout_children: &mut ThinVec<NodeId>,
|
|
943
|
+
) {
|
|
944
|
+
flush_inline_pseudos_recursive(doc, inline_context_root_node_id);
|
|
945
|
+
|
|
946
|
+
iter_children_and_pseudos!(doc.nodes[inline_context_root_node_id], |child_id| {
|
|
947
|
+
find_inline_layout_embedded_boxes_recursive(
|
|
948
|
+
&mut doc.nodes,
|
|
949
|
+
inline_context_root_node_id,
|
|
950
|
+
child_id,
|
|
951
|
+
layout_children,
|
|
952
|
+
);
|
|
953
|
+
});
|
|
954
|
+
|
|
955
|
+
fn flush_inline_pseudos_recursive(doc: &mut BaseDocument, node_id: NodeId) {
|
|
956
|
+
doc.iter_children_mut(node_id, |child_id, doc| {
|
|
957
|
+
flush_pseudo_elements(doc, child_id);
|
|
958
|
+
let display = doc.nodes[node_id]
|
|
959
|
+
.display_style()
|
|
960
|
+
.unwrap_or(Display::inline());
|
|
961
|
+
let do_recurse = match (display.outside(), display.inside()) {
|
|
962
|
+
(DisplayOutside::None, DisplayInside::Contents) => true,
|
|
963
|
+
(DisplayOutside::Inline, DisplayInside::Flow) => true,
|
|
964
|
+
(_, _) => false,
|
|
965
|
+
};
|
|
966
|
+
if do_recurse {
|
|
967
|
+
flush_inline_pseudos_recursive(doc, child_id);
|
|
968
|
+
}
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
fn find_inline_layout_embedded_boxes_recursive(
|
|
973
|
+
nodes: &mut crate::NodeTree,
|
|
974
|
+
parent_id: NodeId,
|
|
975
|
+
node_id: NodeId,
|
|
976
|
+
layout_children: &mut ThinVec<NodeId>,
|
|
977
|
+
) {
|
|
978
|
+
let node = &mut nodes[node_id];
|
|
979
|
+
|
|
980
|
+
// Set layout_parent for node.
|
|
981
|
+
node.layout_parent.set(Some(parent_id));
|
|
982
|
+
|
|
983
|
+
match &node.data {
|
|
984
|
+
NodeData::Element(element_data) | NodeData::AnonymousBlock(element_data) => {
|
|
985
|
+
// if the input type is hidden, hide it
|
|
986
|
+
if *element_data.name.local == *"input" {
|
|
987
|
+
if let Some("hidden") = element_data.attr(local_name!("type")) {
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
let display = node.display_style().unwrap_or(Display::inline());
|
|
993
|
+
|
|
994
|
+
match (display.outside(), display.inside()) {
|
|
995
|
+
(DisplayOutside::None, DisplayInside::None) => {
|
|
996
|
+
node.remove_damage(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
|
|
997
|
+
}
|
|
998
|
+
(DisplayOutside::None, DisplayInside::Contents) => {
|
|
999
|
+
node.remove_damage(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
|
|
1000
|
+
iter_children!(nodes[node_id], |child_id| {
|
|
1001
|
+
find_inline_layout_embedded_boxes_recursive(
|
|
1002
|
+
nodes,
|
|
1003
|
+
parent_id,
|
|
1004
|
+
child_id,
|
|
1005
|
+
layout_children,
|
|
1006
|
+
);
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
1009
|
+
(DisplayOutside::Inline, DisplayInside::Flow) => {
|
|
1010
|
+
let tag_name = &element_data.name.local;
|
|
1011
|
+
|
|
1012
|
+
if is_inline_box_element(tag_name) {
|
|
1013
|
+
layout_children.push(node_id);
|
|
1014
|
+
} else if *tag_name == local_name!("br") {
|
|
1015
|
+
node.remove_damage(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
|
|
1016
|
+
} else {
|
|
1017
|
+
node.remove_damage(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
|
|
1018
|
+
iter_children_and_pseudos!(nodes[node_id], |child_id| {
|
|
1019
|
+
find_inline_layout_embedded_boxes_recursive(
|
|
1020
|
+
nodes,
|
|
1021
|
+
node_id,
|
|
1022
|
+
child_id,
|
|
1023
|
+
layout_children,
|
|
1024
|
+
);
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
// Inline box
|
|
1029
|
+
(_, _) => {
|
|
1030
|
+
layout_children.push(node_id);
|
|
1031
|
+
}
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
NodeData::Comment { .. } | NodeData::Text(_) => {
|
|
1035
|
+
node.remove_damage(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
|
|
1036
|
+
}
|
|
1037
|
+
NodeData::Document(_) => unreachable!(),
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
pub(crate) fn build_inline_layout_into(
|
|
1043
|
+
nodes: &crate::NodeTree,
|
|
1044
|
+
layout_ctx: &mut LayoutContext<TextBrush>,
|
|
1045
|
+
font_ctx: &mut FontContext,
|
|
1046
|
+
text_layout: &mut TextLayout,
|
|
1047
|
+
scale: f32,
|
|
1048
|
+
inline_context_root_node_id: NodeId,
|
|
1049
|
+
) {
|
|
1050
|
+
// Get the inline context's root node's text styles
|
|
1051
|
+
let root_node = &nodes[inline_context_root_node_id];
|
|
1052
|
+
let root_node_style = root_node.primary_styles().or_else(|| {
|
|
1053
|
+
root_node
|
|
1054
|
+
.parent
|
|
1055
|
+
.and_then(|parent_id| nodes[parent_id].primary_styles())
|
|
1056
|
+
});
|
|
1057
|
+
|
|
1058
|
+
let mut parley_style = root_node_style
|
|
1059
|
+
.as_ref()
|
|
1060
|
+
.map(|s| stylo_to_parley::style(inline_context_root_node_id, s))
|
|
1061
|
+
.unwrap_or_else(|| parley::TextStyle {
|
|
1062
|
+
white_space_collapse: WhiteSpaceCollapse::Collapse,
|
|
1063
|
+
..Default::default()
|
|
1064
|
+
});
|
|
1065
|
+
|
|
1066
|
+
// The formatting-context root's vertical-align positions its box in its
|
|
1067
|
+
// parent (for example a middle-aligned table cell), not its own text runs.
|
|
1068
|
+
// Passing it to Parley shifts the root against its own strut and inflates
|
|
1069
|
+
// every line. Descendant inline spans still use their own vertical-align.
|
|
1070
|
+
parley_style.vertical_align = parley::VerticalAlign::BASELINE;
|
|
1071
|
+
|
|
1072
|
+
// Create a parley tree builder
|
|
1073
|
+
let mut builder = layout_ctx.tree_builder(font_ctx, scale, true, &parley_style);
|
|
1074
|
+
if let Some(style) = root_node_style.as_deref() {
|
|
1075
|
+
builder.set_base_direction(stylo_to_parley::base_direction(
|
|
1076
|
+
style.clone_direction(),
|
|
1077
|
+
style.clone_unicode_bidi(),
|
|
1078
|
+
));
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
let text_transform = root_node_style
|
|
1082
|
+
.as_deref()
|
|
1083
|
+
.map(|s| CaseTransform::from_style(s))
|
|
1084
|
+
.unwrap_or(CaseTransform::NONE);
|
|
1085
|
+
|
|
1086
|
+
// Render position-inside list items
|
|
1087
|
+
if let Some(ListItemLayout {
|
|
1088
|
+
marker,
|
|
1089
|
+
position: ListItemLayoutPosition::Inside,
|
|
1090
|
+
}) = root_node
|
|
1091
|
+
.element_data()
|
|
1092
|
+
.and_then(|el| el.list_item_data.as_deref())
|
|
1093
|
+
{
|
|
1094
|
+
match marker {
|
|
1095
|
+
// Bullet glyphs live in the bundled bullet font. The position-outside
|
|
1096
|
+
// path already asks for it; without the same span here a marker like
|
|
1097
|
+
// disclosure-closed (U+25B8) falls back to the element's own font and
|
|
1098
|
+
// renders as a missing glyph.
|
|
1099
|
+
Marker::Char(char) => {
|
|
1100
|
+
let mut marker_style = parley_style.clone();
|
|
1101
|
+
marker_style.font_family = BULLET_FONT_FAMILY.into();
|
|
1102
|
+
builder.push_style_span(marker_style);
|
|
1103
|
+
builder.push_text(&format!("{char} "));
|
|
1104
|
+
builder.pop_style_span();
|
|
1105
|
+
}
|
|
1106
|
+
Marker::String(str) => builder.push_text(str),
|
|
1107
|
+
}
|
|
1108
|
+
};
|
|
1109
|
+
// The marker is a separate box, so words in the content don't continue from it.
|
|
1110
|
+
let mut text_transformer = TextTransformer::default();
|
|
1111
|
+
text_transformer.word_break(&builder);
|
|
1112
|
+
|
|
1113
|
+
if let Some(before_id) = root_node.before() {
|
|
1114
|
+
build_inline_layout_recursive(
|
|
1115
|
+
&mut builder,
|
|
1116
|
+
&mut text_transformer,
|
|
1117
|
+
nodes,
|
|
1118
|
+
before_id,
|
|
1119
|
+
&text_transform,
|
|
1120
|
+
);
|
|
1121
|
+
}
|
|
1122
|
+
for child_id in root_node.children.iter().copied() {
|
|
1123
|
+
build_inline_layout_recursive(
|
|
1124
|
+
&mut builder,
|
|
1125
|
+
&mut text_transformer,
|
|
1126
|
+
nodes,
|
|
1127
|
+
child_id,
|
|
1128
|
+
&text_transform,
|
|
1129
|
+
);
|
|
1130
|
+
}
|
|
1131
|
+
if let Some(after_id) = root_node.after() {
|
|
1132
|
+
build_inline_layout_recursive(
|
|
1133
|
+
&mut builder,
|
|
1134
|
+
&mut text_transformer,
|
|
1135
|
+
nodes,
|
|
1136
|
+
after_id,
|
|
1137
|
+
&text_transform,
|
|
1138
|
+
);
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
text_layout.text = builder.build_into(&mut text_layout.layout);
|
|
1142
|
+
return;
|
|
1143
|
+
|
|
1144
|
+
fn build_inline_layout_recursive(
|
|
1145
|
+
builder: &mut TreeBuilder<TextBrush>,
|
|
1146
|
+
text_transformer: &mut TextTransformer,
|
|
1147
|
+
nodes: &crate::NodeTree,
|
|
1148
|
+
node_id: NodeId,
|
|
1149
|
+
parent_text_transform: &CaseTransform,
|
|
1150
|
+
) {
|
|
1151
|
+
let node = &nodes[node_id];
|
|
1152
|
+
|
|
1153
|
+
let style = node.primary_styles();
|
|
1154
|
+
let style = style.as_ref();
|
|
1155
|
+
|
|
1156
|
+
let text_transform = style
|
|
1157
|
+
.map(|s| CaseTransform::from_style(s))
|
|
1158
|
+
.unwrap_or(CaseTransform::NONE);
|
|
1159
|
+
|
|
1160
|
+
match &node.data {
|
|
1161
|
+
NodeData::Element(element_data) | NodeData::AnonymousBlock(element_data) => {
|
|
1162
|
+
// if the input type is hidden, hide it
|
|
1163
|
+
if *element_data.name.local == *"input" {
|
|
1164
|
+
if let Some("hidden") = element_data.attr(local_name!("type")) {
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
let display = node.display_style().unwrap_or(Display::inline());
|
|
1170
|
+
let position = style
|
|
1171
|
+
.map(|s| s.clone_position())
|
|
1172
|
+
.unwrap_or(PositionProperty::Static);
|
|
1173
|
+
let float = style.map(|s| s.clone_float()).unwrap_or(Float::None);
|
|
1174
|
+
let box_kind = if position.is_absolutely_positioned() {
|
|
1175
|
+
InlineBoxKind::OutOfFlow
|
|
1176
|
+
} else if float.is_floating() {
|
|
1177
|
+
InlineBoxKind::CustomOutOfFlow
|
|
1178
|
+
} else {
|
|
1179
|
+
InlineBoxKind::InFlow
|
|
1180
|
+
};
|
|
1181
|
+
|
|
1182
|
+
match (display.outside(), display.inside()) {
|
|
1183
|
+
(DisplayOutside::None, DisplayInside::None) => {
|
|
1184
|
+
// node.remove_damage(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
|
|
1185
|
+
}
|
|
1186
|
+
(DisplayOutside::None, DisplayInside::Contents) => {
|
|
1187
|
+
let whitespace = style.map(|s| {
|
|
1188
|
+
[
|
|
1189
|
+
parley::StyleProperty::WhiteSpaceCollapse(
|
|
1190
|
+
stylo_to_parley::white_space_collapse(
|
|
1191
|
+
s.clone_white_space_collapse(),
|
|
1192
|
+
),
|
|
1193
|
+
),
|
|
1194
|
+
parley::StyleProperty::TextWrapMode(
|
|
1195
|
+
stylo_to_parley::text_wrap_mode(s.clone_text_wrap_mode()),
|
|
1196
|
+
),
|
|
1197
|
+
]
|
|
1198
|
+
});
|
|
1199
|
+
builder.push_style_modification_span(
|
|
1200
|
+
whitespace
|
|
1201
|
+
.as_ref()
|
|
1202
|
+
.map_or(&[][..], |styles| styles.as_slice()),
|
|
1203
|
+
);
|
|
1204
|
+
for child_id in node.children.iter().copied() {
|
|
1205
|
+
// node.remove_damage(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
|
|
1206
|
+
build_inline_layout_recursive(
|
|
1207
|
+
builder,
|
|
1208
|
+
text_transformer,
|
|
1209
|
+
nodes,
|
|
1210
|
+
child_id,
|
|
1211
|
+
&text_transform,
|
|
1212
|
+
);
|
|
1213
|
+
}
|
|
1214
|
+
builder.pop_style_span();
|
|
1215
|
+
}
|
|
1216
|
+
(DisplayOutside::Inline, DisplayInside::Flow) => {
|
|
1217
|
+
let tag_name = &element_data.name.local;
|
|
1218
|
+
|
|
1219
|
+
if is_inline_box_element(tag_name) {
|
|
1220
|
+
if box_kind == InlineBoxKind::InFlow {
|
|
1221
|
+
text_transformer.word_break(builder);
|
|
1222
|
+
}
|
|
1223
|
+
builder.push_inline_box(InlineBox {
|
|
1224
|
+
id: node_id.as_u64(),
|
|
1225
|
+
kind: box_kind,
|
|
1226
|
+
// Overridden by push_inline_box method
|
|
1227
|
+
index: 0,
|
|
1228
|
+
// Width and height are set during layout
|
|
1229
|
+
width: 0.0,
|
|
1230
|
+
height: 0.0,
|
|
1231
|
+
baseline: None,
|
|
1232
|
+
vertical_align: node
|
|
1233
|
+
.primary_styles()
|
|
1234
|
+
.map(|s| stylo_to_parley::vertical_align(&s))
|
|
1235
|
+
.unwrap_or_default(),
|
|
1236
|
+
});
|
|
1237
|
+
} else if *tag_name == local_name!("br") {
|
|
1238
|
+
// node.remove_damage(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
|
|
1239
|
+
// TODO: update span id for br spans
|
|
1240
|
+
builder.push_style_modification_span(&[
|
|
1241
|
+
parley::StyleProperty::WhiteSpaceCollapse(
|
|
1242
|
+
WhiteSpaceCollapse::Preserve,
|
|
1243
|
+
),
|
|
1244
|
+
]);
|
|
1245
|
+
builder.push_text("\n");
|
|
1246
|
+
builder.pop_style_span();
|
|
1247
|
+
text_transformer.word_break(builder);
|
|
1248
|
+
} else {
|
|
1249
|
+
// node.remove_damage(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
|
|
1250
|
+
let style = node
|
|
1251
|
+
.primary_styles()
|
|
1252
|
+
.map(|s| stylo_to_parley::style(node.id, &s))
|
|
1253
|
+
.unwrap_or_else(|| parley::TextStyle {
|
|
1254
|
+
white_space_collapse: WhiteSpaceCollapse::Collapse,
|
|
1255
|
+
..Default::default()
|
|
1256
|
+
});
|
|
1257
|
+
|
|
1258
|
+
builder.push_style_span(style);
|
|
1259
|
+
|
|
1260
|
+
if let Some(before_id) = node.before() {
|
|
1261
|
+
build_inline_layout_recursive(
|
|
1262
|
+
builder,
|
|
1263
|
+
text_transformer,
|
|
1264
|
+
nodes,
|
|
1265
|
+
before_id,
|
|
1266
|
+
&text_transform,
|
|
1267
|
+
);
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
for child_id in node.children.iter().copied() {
|
|
1271
|
+
build_inline_layout_recursive(
|
|
1272
|
+
builder,
|
|
1273
|
+
text_transformer,
|
|
1274
|
+
nodes,
|
|
1275
|
+
child_id,
|
|
1276
|
+
&text_transform,
|
|
1277
|
+
);
|
|
1278
|
+
}
|
|
1279
|
+
if let Some(after_id) = node.after() {
|
|
1280
|
+
build_inline_layout_recursive(
|
|
1281
|
+
builder,
|
|
1282
|
+
text_transformer,
|
|
1283
|
+
nodes,
|
|
1284
|
+
after_id,
|
|
1285
|
+
&text_transform,
|
|
1286
|
+
);
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
builder.pop_style_span();
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
// Inline box
|
|
1293
|
+
(_, _) => {
|
|
1294
|
+
if box_kind == InlineBoxKind::InFlow {
|
|
1295
|
+
text_transformer.word_break(builder);
|
|
1296
|
+
}
|
|
1297
|
+
builder.push_inline_box(InlineBox {
|
|
1298
|
+
id: node_id.as_u64(),
|
|
1299
|
+
kind: box_kind,
|
|
1300
|
+
// Overridden by push_inline_box method
|
|
1301
|
+
index: 0,
|
|
1302
|
+
// Width and height are set during layout
|
|
1303
|
+
width: 0.0,
|
|
1304
|
+
height: 0.0,
|
|
1305
|
+
baseline: None,
|
|
1306
|
+
vertical_align: node
|
|
1307
|
+
.primary_styles()
|
|
1308
|
+
.map(|s| stylo_to_parley::vertical_align(&s))
|
|
1309
|
+
.unwrap_or_default(),
|
|
1310
|
+
});
|
|
1311
|
+
}
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
NodeData::Text(data) => {
|
|
1315
|
+
// node.remove_damage(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
|
|
1316
|
+
// dbg!(&data.content);
|
|
1317
|
+
|
|
1318
|
+
let text =
|
|
1319
|
+
text_transformer.transform(&data.content, parent_text_transform, builder);
|
|
1320
|
+
builder.push_text(text);
|
|
1321
|
+
}
|
|
1322
|
+
NodeData::Comment { .. } => {
|
|
1323
|
+
// node.remove_damage(CONSTRUCT_DESCENDENT | CONSTRUCT_FC | CONSTRUCT_BOX);
|
|
1324
|
+
}
|
|
1325
|
+
NodeData::Document(_) => unreachable!(),
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
}
|