1use crate::float::FloatContext;
7use crate::fragment::{Fragment, FragmentKind};
8use crate::geometry::{Point, Size};
9use crate::layout::LayoutContext;
10use crate::style::{TextAlign, VerticalAlign, WhiteSpace};
11use crate::tree::NodeId;
12
13#[derive(Debug, Clone)]
15struct InlineItem {
16 node: NodeId,
17 size: Size,
18 baseline: f32,
19}
20
21#[derive(Debug)]
23struct LineBox {
24 items: Vec<InlineItem>,
25 width: f32,
26 height: f32,
27 baseline: f32,
28}
29
30impl LineBox {
31 fn new() -> Self {
32 Self {
33 items: Vec::new(),
34 width: 0.0,
35 height: 0.0,
36 baseline: 0.0,
37 }
38 }
39
40 fn add_item(&mut self, item: InlineItem) {
41 self.width += item.size.width;
42 self.height = self.height.max(item.size.height);
43 self.baseline = self.baseline.max(item.baseline);
44 self.items.push(item);
45 }
46
47 fn is_empty(&self) -> bool {
48 self.items.is_empty()
49 }
50
51 fn used_width(&self) -> f32 {
52 self.width
53 }
54}
55
56pub fn layout_inline_formatting_context(
73 ctx: &LayoutContext,
74 parent: NodeId,
75 containing_width: f32,
76 fragments: &mut Vec<Fragment>,
77 _float_ctx: &mut FloatContext,
78) -> f32 {
79 let parent_style = ctx.tree.style(parent);
80 let text_align = parent_style.text_align;
81 let line_height = parent_style.line_height;
82 let white_space = parent_style.white_space;
83
84 let mut inline_items = Vec::new();
86 collect_inline_items(ctx, parent, containing_width, &mut inline_items);
87
88 let mut line_boxes = Vec::new();
90 break_into_lines(
91 &inline_items,
92 containing_width,
93 white_space,
94 &mut line_boxes,
95 );
96
97 let mut current_y = 0.0;
99
100 for line_box in &line_boxes {
101 let line_height_actual = line_box.height.max(line_height);
102
103 let line_offset_x = calculate_text_align_offset(
105 text_align,
106 containing_width,
107 line_box.used_width(),
108 line_box.items.len(),
109 );
110
111 let mut line_fragment = Fragment::new(parent, FragmentKind::LineBox);
113 line_fragment.position = Point::new(0.0, current_y);
114 line_fragment.size = Size::new(containing_width, line_height_actual);
115
116 let mut current_x = line_offset_x;
118
119 for (idx, item) in line_box.items.iter().enumerate() {
120 let mut item_fragment = Fragment::new(item.node, FragmentKind::TextRun);
121 item_fragment.size = item.size;
122
123 let item_style = ctx.tree.style(item.node);
125 let vertical_offset = calculate_vertical_align_offset(
126 item_style.vertical_align,
127 line_box.baseline,
128 item.baseline,
129 item.size.height,
130 line_height_actual,
131 );
132
133 item_fragment.position = Point::new(current_x, vertical_offset);
134
135 let extra_space = if text_align == TextAlign::Justify
137 && line_box.items.len() > 1
138 && idx < line_box.items.len() - 1
139 {
140 let remaining = containing_width - line_box.used_width();
141 let gaps = (line_box.items.len() - 1) as f32;
142 remaining / gaps
143 } else {
144 0.0
145 };
146
147 current_x += item.size.width + extra_space;
148
149 line_fragment.children.push(item_fragment);
150 }
151
152 current_y += line_height_actual;
153 fragments.push(line_fragment);
154 }
155
156 current_y
157}
158
159fn collect_inline_items(
161 ctx: &LayoutContext,
162 parent: NodeId,
163 max_width: f32,
164 items: &mut Vec<InlineItem>,
165) {
166 let children = ctx.tree.children(parent);
167
168 for &child in children {
169 let node = ctx.tree.node(child);
170 let style = ctx.tree.style(child);
171
172 if node.is_text() {
173 if let Some(text) = node.text_content() {
174 let font_size = style.line_height / 1.2;
176 let size = ctx.text_measure.measure(text, font_size, max_width);
177
178 let baseline = size.height * 0.8;
181
182 items.push(InlineItem {
183 node: child,
184 size,
185 baseline,
186 });
187 }
188 } else {
189 let font_size = style.line_height / 1.2;
193 let size = ctx.text_measure.measure("X", font_size, max_width);
194 let baseline = size.height * 0.8;
195
196 items.push(InlineItem {
197 node: child,
198 size,
199 baseline,
200 });
201 }
202 }
203}
204
205fn break_into_lines(
207 items: &[InlineItem],
208 containing_width: f32,
209 white_space: WhiteSpace,
210 line_boxes: &mut Vec<LineBox>,
211) {
212 if items.is_empty() {
213 return;
214 }
215
216 let allow_wrapping = white_space.wraps();
217 let mut current_line = LineBox::new();
218
219 for item in items {
220 let item_width = item.size.width;
221
222 let fits =
224 current_line.is_empty() || current_line.used_width() + item_width <= containing_width;
225
226 if fits || !allow_wrapping {
227 current_line.add_item(item.clone());
229 } else {
230 if !current_line.is_empty() {
232 line_boxes.push(current_line);
233 }
234 current_line = LineBox::new();
235 current_line.add_item(item.clone());
236 }
237 }
238
239 if !current_line.is_empty() {
241 line_boxes.push(current_line);
242 }
243}
244
245fn calculate_text_align_offset(
247 align: TextAlign,
248 containing_width: f32,
249 line_width: f32,
250 _item_count: usize,
251) -> f32 {
252 match align {
253 TextAlign::Left => 0.0,
254 TextAlign::Right => (containing_width - line_width).max(0.0),
255 TextAlign::Center => ((containing_width - line_width) / 2.0).max(0.0),
256 TextAlign::Justify => {
257 0.0
259 }
260 }
261}
262
263fn calculate_vertical_align_offset(
265 align: VerticalAlign,
266 line_baseline: f32,
267 item_baseline: f32,
268 item_height: f32,
269 line_height: f32,
270) -> f32 {
271 match align {
272 VerticalAlign::Baseline => line_baseline - item_baseline,
273 VerticalAlign::Top => 0.0,
274 VerticalAlign::Bottom => line_height - item_height,
275 VerticalAlign::Middle => (line_height - item_height) / 2.0,
276 VerticalAlign::Length(offset) => line_baseline - item_baseline + offset,
277 }
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283 use crate::layout::FixedWidthTextMeasure;
284 use crate::style::ComputedStyle;
285 use crate::tree::{BoxTreeBuilder, NodeKind, TextContent};
286
287 #[test]
288 fn test_single_line_layout() {
289 let mut builder = BoxTreeBuilder::new();
290 let root = builder.root(ComputedStyle::block());
291 builder.text(root, "Hello");
292 let tree = builder.build();
293
294 let ctx = LayoutContext {
295 tree: &tree,
296 text_measure: &FixedWidthTextMeasure,
297 viewport: Size::new(800.0, 600.0),
298 };
299
300 let mut fragments = Vec::new();
301 let mut float_ctx = FloatContext::new(800.0);
302 let height =
303 layout_inline_formatting_context(&ctx, root, 800.0, &mut fragments, &mut float_ctx);
304
305 assert_eq!(fragments.len(), 1); assert!(height > 0.0);
307 assert_eq!(fragments[0].kind, FragmentKind::LineBox);
308 assert_eq!(fragments[0].children.len(), 1); }
310
311 #[test]
312 fn test_line_wrapping() {
313 let mut builder = BoxTreeBuilder::new();
314 let root = builder.root(ComputedStyle::block());
315 builder.text(root, "Hello");
316 builder.text(root, "World");
317 builder.text(root, "Test");
318 let tree = builder.build();
319
320 let ctx = LayoutContext {
321 tree: &tree,
322 text_measure: &FixedWidthTextMeasure,
323 viewport: Size::new(800.0, 600.0),
324 };
325
326 let mut fragments = Vec::new();
327 let mut float_ctx = FloatContext::new(50.0);
328
329 let height =
331 layout_inline_formatting_context(&ctx, root, 50.0, &mut fragments, &mut float_ctx);
332
333 assert!(fragments.len() >= 2);
337 assert!(height > 1.2); }
339
340 #[test]
341 fn test_text_align_center() {
342 let mut builder = BoxTreeBuilder::new();
343 let mut style = ComputedStyle::block();
344 style.text_align = TextAlign::Center;
345 let root = builder.root(style);
346 builder.text(root, "Hi");
347 let tree = builder.build();
348
349 let ctx = LayoutContext {
350 tree: &tree,
351 text_measure: &FixedWidthTextMeasure,
352 viewport: Size::new(800.0, 600.0),
353 };
354
355 let mut fragments = Vec::new();
356 let mut float_ctx = FloatContext::new(800.0);
357 layout_inline_formatting_context(&ctx, root, 800.0, &mut fragments, &mut float_ctx);
358
359 let line = &fragments[0];
361 let text_run = &line.children[0];
362 assert!((text_run.position.x - 392.0).abs() < 1.0);
364 }
365
366 #[test]
367 fn test_text_align_right() {
368 let mut builder = BoxTreeBuilder::new();
369 let mut style = ComputedStyle::block();
370 style.text_align = TextAlign::Right;
371 let root = builder.root(style);
372 builder.text(root, "Hi");
373 let tree = builder.build();
374
375 let ctx = LayoutContext {
376 tree: &tree,
377 text_measure: &FixedWidthTextMeasure,
378 viewport: Size::new(800.0, 600.0),
379 };
380
381 let mut fragments = Vec::new();
382 let mut float_ctx = FloatContext::new(800.0);
383 layout_inline_formatting_context(&ctx, root, 800.0, &mut fragments, &mut float_ctx);
384
385 let line = &fragments[0];
387 let text_run = &line.children[0];
388 assert!((text_run.position.x - 784.0).abs() < 1.0);
389 }
390
391 #[test]
392 fn test_text_align_justify() {
393 let mut builder = BoxTreeBuilder::new();
394 let mut style = ComputedStyle::block();
395 style.text_align = TextAlign::Justify;
396 let root = builder.root(style);
397 builder.text(root, "A");
398 builder.text(root, "B");
399 let tree = builder.build();
400
401 let ctx = LayoutContext {
402 tree: &tree,
403 text_measure: &FixedWidthTextMeasure,
404 viewport: Size::new(800.0, 600.0),
405 };
406
407 let mut fragments = Vec::new();
408 let mut float_ctx = FloatContext::new(100.0);
409 layout_inline_formatting_context(&ctx, root, 100.0, &mut fragments, &mut float_ctx);
410
411 let line = &fragments[0];
412 assert_eq!(line.children.len(), 2);
413
414 assert_eq!(line.children[0].position.x, 0.0);
416
417 assert!((line.children[1].position.x - 92.0).abs() < 1.0);
422 }
423
424 #[test]
425 fn test_vertical_align_baseline() {
426 let mut builder = BoxTreeBuilder::new();
427 let root = builder.root(ComputedStyle::block());
428 builder.text(root, "Test");
429 let tree = builder.build();
430
431 let ctx = LayoutContext {
432 tree: &tree,
433 text_measure: &FixedWidthTextMeasure,
434 viewport: Size::new(800.0, 600.0),
435 };
436
437 let mut fragments = Vec::new();
438 let mut float_ctx = FloatContext::new(800.0);
439 layout_inline_formatting_context(&ctx, root, 800.0, &mut fragments, &mut float_ctx);
440
441 let line = &fragments[0];
443 let text_run = &line.children[0];
444
445 assert!(text_run.position.y >= 0.0);
447 }
448
449 #[test]
450 fn test_vertical_align_top() {
451 let mut builder = BoxTreeBuilder::new();
452 let style = ComputedStyle::block();
453 let root = builder.root(style);
454
455 let mut text_style = ComputedStyle::inline();
456 text_style.vertical_align = VerticalAlign::Top;
457
458 let text_id = builder.tree.add_node(
460 NodeKind::Text(TextContent {
461 text: "Test".to_string(),
462 }),
463 text_style,
464 );
465 builder.tree.append_child(root, text_id);
466
467 let tree = builder.build();
468
469 let ctx = LayoutContext {
470 tree: &tree,
471 text_measure: &FixedWidthTextMeasure,
472 viewport: Size::new(800.0, 600.0),
473 };
474
475 let mut fragments = Vec::new();
476 let mut float_ctx = FloatContext::new(800.0);
477 layout_inline_formatting_context(&ctx, root, 800.0, &mut fragments, &mut float_ctx);
478
479 let line = &fragments[0];
480 let text_run = &line.children[0];
481
482 assert_eq!(text_run.position.y, 0.0);
484 }
485
486 #[test]
487 fn test_empty_inline_context() {
488 let mut builder = BoxTreeBuilder::new();
489 let root = builder.root(ComputedStyle::block());
490 let tree = builder.build();
491
492 let ctx = LayoutContext {
493 tree: &tree,
494 text_measure: &FixedWidthTextMeasure,
495 viewport: Size::new(800.0, 600.0),
496 };
497
498 let mut fragments = Vec::new();
499 let mut float_ctx = FloatContext::new(800.0);
500 let height =
501 layout_inline_formatting_context(&ctx, root, 800.0, &mut fragments, &mut float_ctx);
502
503 assert_eq!(height, 0.0);
504 assert_eq!(fragments.len(), 0);
505 }
506
507 #[test]
508 fn test_white_space_nowrap() {
509 let mut builder = BoxTreeBuilder::new();
510 let mut style = ComputedStyle::block();
511 style.white_space = WhiteSpace::Nowrap;
512 let root = builder.root(style);
513 builder.text(root, "A");
514 builder.text(root, "B");
515 builder.text(root, "C");
516 let tree = builder.build();
517
518 let ctx = LayoutContext {
519 tree: &tree,
520 text_measure: &FixedWidthTextMeasure,
521 viewport: Size::new(800.0, 600.0),
522 };
523
524 let mut fragments = Vec::new();
525 let mut float_ctx = FloatContext::new(10.0);
526
527 layout_inline_formatting_context(&ctx, root, 10.0, &mut fragments, &mut float_ctx);
529
530 assert_eq!(fragments.len(), 1);
532 assert_eq!(fragments[0].children.len(), 3);
533 }
534}