1use std::collections::HashMap;
2
3use crate::font::registry::FontRegistry;
4use crate::layout::block::{BlockLayout, BlockLayoutParams, layout_block};
5use crate::layout::frame::{FrameLayout, FrameLayoutParams, layout_frame};
6use crate::layout::table::{TableLayout, TableLayoutParams, layout_table};
7
8pub enum FlowItem {
9 Block {
10 block_id: usize,
11 y: f32,
12 height: f32,
13 },
14 Table {
15 table_id: usize,
16 y: f32,
17 height: f32,
18 },
19 Frame {
20 frame_id: usize,
21 y: f32,
22 height: f32,
23 },
24}
25
26pub struct FlowLayout {
27 pub blocks: HashMap<usize, BlockLayout>,
28 pub tables: HashMap<usize, TableLayout>,
29 pub frames: HashMap<usize, FrameLayout>,
30 pub flow_order: Vec<FlowItem>,
31 pub content_height: f32,
32 pub viewport_width: f32,
33 pub viewport_height: f32,
34 pub cached_max_content_width: f32,
35}
36
37impl Default for FlowLayout {
38 fn default() -> Self {
39 Self::new()
40 }
41}
42
43impl FlowLayout {
44 pub fn new() -> Self {
45 Self {
46 blocks: HashMap::new(),
47 tables: HashMap::new(),
48 frames: HashMap::new(),
49 flow_order: Vec::new(),
50 content_height: 0.0,
51 viewport_width: 0.0,
52 viewport_height: 0.0,
53 cached_max_content_width: 0.0,
54 }
55 }
56
57 pub fn add_table(
59 &mut self,
60 registry: &FontRegistry,
61 params: &TableLayoutParams,
62 available_width: f32,
63 ) {
64 let mut table = layout_table(registry, params, available_width);
65
66 let mut y = self.content_height;
67 table.y = y;
68 y += table.total_height;
69
70 self.flow_order.push(FlowItem::Table {
71 table_id: table.table_id,
72 y: table.y,
73 height: table.total_height,
74 });
75 if table.total_width > self.cached_max_content_width {
76 self.cached_max_content_width = table.total_width;
77 }
78 self.tables.insert(table.table_id, table);
79 self.content_height = y;
80 }
81
82 pub fn add_frame(
91 &mut self,
92 registry: &FontRegistry,
93 params: &FrameLayoutParams,
94 available_width: f32,
95 ) {
96 use crate::layout::frame::FramePosition;
97
98 let mut frame = layout_frame(registry, params, available_width);
99
100 match params.position {
101 FramePosition::Inline => {
102 frame.y = self.content_height;
103 frame.x = 0.0;
104 self.content_height += frame.total_height;
105 }
106 FramePosition::FloatLeft => {
107 frame.y = self.content_height;
108 frame.x = 0.0;
109 self.content_height += frame.total_height;
114 }
115 FramePosition::FloatRight => {
116 frame.y = self.content_height;
117 frame.x = (available_width - frame.total_width).max(0.0);
118 self.content_height += frame.total_height;
119 }
120 FramePosition::Absolute => {
121 frame.y = params.margin_top;
124 frame.x = params.margin_left;
125 }
127 }
128
129 self.flow_order.push(FlowItem::Frame {
130 frame_id: frame.frame_id,
131 y: frame.y,
132 height: frame.total_height,
133 });
134 if frame.total_width > self.cached_max_content_width {
135 self.cached_max_content_width = frame.total_width;
136 }
137 self.frames.insert(frame.frame_id, frame);
138 }
139
140 pub fn clear(&mut self) {
142 self.blocks.clear();
143 self.tables.clear();
144 self.frames.clear();
145 self.flow_order.clear();
146 self.content_height = 0.0;
147 self.cached_max_content_width = 0.0;
148 }
149
150 pub fn add_block(
152 &mut self,
153 registry: &FontRegistry,
154 params: &BlockLayoutParams,
155 available_width: f32,
156 ) {
157 let mut block = layout_block(registry, params, available_width);
158
159 let mut y = self.content_height;
161 if let Some(FlowItem::Block {
162 block_id: prev_id, ..
163 }) = self.flow_order.last()
164 {
165 if let Some(prev_block) = self.blocks.get(prev_id) {
166 let collapsed = prev_block.bottom_margin.max(block.top_margin);
167 y -= prev_block.bottom_margin;
168 y += collapsed;
169 } else {
170 y += block.top_margin;
171 }
172 } else {
173 y += block.top_margin;
174 }
175
176 block.y = y;
177 let block_content = block.height - block.top_margin - block.bottom_margin;
178 y += block_content + block.bottom_margin;
179
180 self.flow_order.push(FlowItem::Block {
181 block_id: block.block_id,
182 y: block.y,
183 height: block.height,
184 });
185 self.update_max_width_for_block(&block);
186 self.blocks.insert(block.block_id, block);
187 self.content_height = y;
188 }
189
190 pub fn layout_blocks(
192 &mut self,
193 registry: &FontRegistry,
194 block_params: Vec<BlockLayoutParams>,
195 available_width: f32,
196 ) {
197 self.clear();
198 for params in &block_params {
203 self.add_block(registry, params, available_width);
204 }
205 }
206
207 pub fn relayout_block(
212 &mut self,
213 registry: &FontRegistry,
214 params: &BlockLayoutParams,
215 available_width: f32,
216 ) {
217 let block_id = params.block_id;
218
219 if self.blocks.contains_key(&block_id) {
221 self.relayout_top_level_block(registry, params, available_width);
222 return;
223 }
224
225 let table_match = self.tables.iter().find_map(|(&tid, table)| {
227 for cell in &table.cell_layouts {
228 if cell.blocks.iter().any(|b| b.block_id == block_id) {
229 return Some((tid, cell.row, cell.column));
230 }
231 }
232 None
233 });
234 if let Some((table_id, row, col)) = table_match {
235 self.relayout_table_block(registry, params, table_id, row, col);
236 return;
237 }
238
239 let frame_match = self.frames.iter().find_map(|(&fid, frame)| {
241 if frame_contains_block(frame, block_id) {
242 return Some(fid);
243 }
244 None
245 });
246 if let Some(frame_id) = frame_match {
247 self.relayout_frame_block(registry, params, frame_id);
248 }
249 }
250
251 fn relayout_top_level_block(
253 &mut self,
254 registry: &FontRegistry,
255 params: &BlockLayoutParams,
256 available_width: f32,
257 ) {
258 let block_id = params.block_id;
259 let old_y = self.blocks.get(&block_id).map(|b| b.y).unwrap_or(0.0);
260 let old_height = self.blocks.get(&block_id).map(|b| b.height).unwrap_or(0.0);
261 let old_top_margin = self
262 .blocks
263 .get(&block_id)
264 .map(|b| b.top_margin)
265 .unwrap_or(0.0);
266 let old_bottom_margin = self
267 .blocks
268 .get(&block_id)
269 .map(|b| b.bottom_margin)
270 .unwrap_or(0.0);
271 let old_content = old_height - old_top_margin - old_bottom_margin;
272 let old_end = old_y + old_content + old_bottom_margin;
273
274 let mut block = layout_block(registry, params, available_width);
275 block.y = old_y;
276
277 if (block.top_margin - old_top_margin).abs() > 0.001 {
278 let prev_bm = self.prev_block_bottom_margin(block_id).unwrap_or(0.0);
279 let old_collapsed = prev_bm.max(old_top_margin);
280 let new_collapsed = prev_bm.max(block.top_margin);
281 block.y = old_y + (new_collapsed - old_collapsed);
282 }
283
284 let new_content = block.height - block.top_margin - block.bottom_margin;
285 let new_end = block.y + new_content + block.bottom_margin;
286 let delta = new_end - old_end;
287
288 let new_y = block.y;
289 let new_height = block.height;
290 self.update_max_width_for_block(&block);
291 self.blocks.insert(block_id, block);
292
293 for item in &mut self.flow_order {
295 if let FlowItem::Block {
296 block_id: id,
297 y,
298 height,
299 } = item
300 && *id == block_id
301 {
302 *y = new_y;
303 *height = new_height;
304 break;
305 }
306 }
307
308 self.shift_items_after_block(block_id, delta);
309 }
310
311 fn relayout_table_block(
314 &mut self,
315 registry: &FontRegistry,
316 params: &BlockLayoutParams,
317 table_id: usize,
318 row: usize,
319 col: usize,
320 ) {
321 let table = match self.tables.get_mut(&table_id) {
322 Some(t) => t,
323 None => return,
324 };
325
326 let cell_width = table
327 .column_content_widths
328 .get(col)
329 .copied()
330 .unwrap_or(200.0);
331 let old_table_height = table.total_height;
332
333 let cell = match table
335 .cell_layouts
336 .iter_mut()
337 .find(|c| c.row == row && c.column == col)
338 {
339 Some(c) => c,
340 None => return,
341 };
342
343 let new_block = layout_block(registry, params, cell_width);
344 if let Some(old) = cell
345 .blocks
346 .iter_mut()
347 .find(|b| b.block_id == params.block_id)
348 {
349 *old = new_block;
350 }
351
352 let mut block_y = 0.0f32;
354 for block in &mut cell.blocks {
355 block.y = block_y;
356 block_y += block.height;
357 }
358 let cell_height = block_y;
359
360 if row < table.row_heights.len() {
362 let mut max_h = 0.0f32;
363 for c in &table.cell_layouts {
364 if c.row == row {
365 let h: f32 = c.blocks.iter().map(|b| b.height).sum();
366 max_h = max_h.max(h);
367 }
368 }
369 max_h = max_h.max(cell_height);
371 table.row_heights[row] = max_h;
372 }
373
374 let border = table.border_width;
376 let padding = table.cell_padding;
377 let spacing = if table.row_ys.len() > 1 {
378 if table.row_ys.len() >= 2 && !table.row_heights.is_empty() {
380 let expected = table.row_ys[0] + padding + table.row_heights[0] + padding;
381 (table.row_ys.get(1).copied().unwrap_or(expected) - expected).max(0.0)
382 } else {
383 0.0
384 }
385 } else {
386 0.0
387 };
388 let mut y = border;
389 for (r, &row_h) in table.row_heights.iter().enumerate() {
390 if r < table.row_ys.len() {
391 table.row_ys[r] = y + padding;
392 }
393 y += padding * 2.0 + row_h;
394 if r < table.row_heights.len() - 1 {
395 y += spacing;
396 }
397 }
398 table.total_height = y + border;
399
400 let delta = table.total_height - old_table_height;
401
402 for item in &mut self.flow_order {
404 if let FlowItem::Table {
405 table_id: id,
406 height,
407 ..
408 } = item
409 && *id == table_id
410 {
411 *height = table.total_height;
412 break;
413 }
414 }
415
416 self.shift_items_after_table(table_id, delta);
417 }
418
419 fn relayout_frame_block(
422 &mut self,
423 registry: &FontRegistry,
424 params: &BlockLayoutParams,
425 frame_id: usize,
426 ) {
427 let frame = match self.frames.get_mut(&frame_id) {
428 Some(f) => f,
429 None => return,
430 };
431
432 let old_total_height = frame.total_height;
433 let new_block = layout_block(registry, params, frame.content_width);
434
435 relayout_block_in_frame(frame, params.block_id, new_block);
436
437 let delta = frame.total_height - old_total_height;
438
439 for item in &mut self.flow_order {
440 if let FlowItem::Frame {
441 frame_id: id,
442 height,
443 ..
444 } = item
445 && *id == frame_id
446 {
447 *height = frame.total_height;
448 break;
449 }
450 }
451
452 self.shift_items_after_frame(frame_id, delta);
453 }
454
455 fn shift_items_after_block(&mut self, block_id: usize, delta: f32) {
457 if delta.abs() <= 0.001 {
458 return;
459 }
460 let mut found = false;
461 for item in &mut self.flow_order {
462 match item {
463 FlowItem::Block {
464 block_id: id, y, ..
465 } => {
466 if found {
467 *y += delta;
468 if let Some(b) = self.blocks.get_mut(id) {
469 b.y += delta;
470 }
471 }
472 if *id == block_id {
473 found = true;
474 }
475 }
476 FlowItem::Table {
477 table_id: id, y, ..
478 } => {
479 if found {
480 *y += delta;
481 if let Some(t) = self.tables.get_mut(id) {
482 t.y += delta;
483 }
484 }
485 }
486 FlowItem::Frame {
487 frame_id: id, y, ..
488 } => {
489 if found {
490 *y += delta;
491 if let Some(f) = self.frames.get_mut(id) {
492 f.y += delta;
493 }
494 }
495 }
496 }
497 }
498 self.content_height += delta;
499 }
500
501 fn shift_items_after_table(&mut self, table_id: usize, delta: f32) {
503 if delta.abs() <= 0.001 {
504 return;
505 }
506 let mut found = false;
507 for item in &mut self.flow_order {
508 match item {
509 FlowItem::Table {
510 table_id: id, y, ..
511 } => {
512 if *id == table_id {
513 found = true;
514 continue;
515 }
516 if found {
517 *y += delta;
518 if let Some(t) = self.tables.get_mut(id) {
519 t.y += delta;
520 }
521 }
522 }
523 FlowItem::Block {
524 block_id: id, y, ..
525 } => {
526 if found {
527 *y += delta;
528 if let Some(b) = self.blocks.get_mut(id) {
529 b.y += delta;
530 }
531 }
532 }
533 FlowItem::Frame {
534 frame_id: id, y, ..
535 } => {
536 if found {
537 *y += delta;
538 if let Some(f) = self.frames.get_mut(id) {
539 f.y += delta;
540 }
541 }
542 }
543 }
544 }
545 self.content_height += delta;
546 }
547
548 fn shift_items_after_frame(&mut self, frame_id: usize, delta: f32) {
550 if delta.abs() <= 0.001 {
551 return;
552 }
553 let mut found = false;
554 for item in &mut self.flow_order {
555 match item {
556 FlowItem::Frame {
557 frame_id: id, y, ..
558 } => {
559 if *id == frame_id {
560 found = true;
561 continue;
562 }
563 if found {
564 *y += delta;
565 if let Some(f) = self.frames.get_mut(id) {
566 f.y += delta;
567 }
568 }
569 }
570 FlowItem::Block {
571 block_id: id, y, ..
572 } => {
573 if found {
574 *y += delta;
575 if let Some(b) = self.blocks.get_mut(id) {
576 b.y += delta;
577 }
578 }
579 }
580 FlowItem::Table {
581 table_id: id, y, ..
582 } => {
583 if found {
584 *y += delta;
585 if let Some(t) = self.tables.get_mut(id) {
586 t.y += delta;
587 }
588 }
589 }
590 }
591 }
592 self.content_height += delta;
593 }
594
595 fn update_max_width_for_block(&mut self, block: &BlockLayout) {
597 for line in &block.lines {
598 let w = line.width + block.left_margin + block.right_margin;
599 if w > self.cached_max_content_width {
600 self.cached_max_content_width = w;
601 }
602 }
603 }
604
605 fn prev_block_bottom_margin(&self, block_id: usize) -> Option<f32> {
607 let mut prev_bm = None;
608 for item in &self.flow_order {
609 match item {
610 FlowItem::Block { block_id: id, .. } => {
611 if *id == block_id {
612 return prev_bm;
613 }
614 if let Some(b) = self.blocks.get(id) {
615 prev_bm = Some(b.bottom_margin);
616 }
617 }
618 _ => {
619 prev_bm = None;
621 }
622 }
623 }
624 None
625 }
626}
627
628pub(crate) fn frame_contains_block(frame: &FrameLayout, block_id: usize) -> bool {
630 if frame.blocks.iter().any(|b| b.block_id == block_id) {
631 return true;
632 }
633 frame
634 .frames
635 .iter()
636 .any(|nested| frame_contains_block(nested, block_id))
637}
638
639fn relayout_block_in_frame(frame: &mut FrameLayout, block_id: usize, new_block: BlockLayout) {
642 let old_content_height = frame.content_height;
643
644 if let Some(old) = frame.blocks.iter_mut().find(|b| b.block_id == block_id) {
646 *old = new_block;
647 } else {
648 for nested in &mut frame.frames {
650 if frame_contains_block(nested, block_id) {
651 relayout_block_in_frame(nested, block_id, new_block);
652 break;
653 }
654 }
655 }
656
657 let mut content_y = 0.0f32;
659 for block in &mut frame.blocks {
660 block.y = content_y + block.top_margin;
661 let block_content = block.height - block.top_margin - block.bottom_margin;
662 content_y = block.y + block_content + block.bottom_margin;
663 }
664 for table in &mut frame.tables {
665 table.y = content_y;
666 content_y += table.total_height;
667 }
668 for nested in &mut frame.frames {
669 nested.y = content_y;
670 content_y += nested.total_height;
671 }
672
673 frame.content_height = content_y;
674 frame.total_height += content_y - old_content_height;
675}