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 pub scale_factor: f32,
39}
40
41impl Default for FlowLayout {
42 fn default() -> Self {
43 Self::new()
44 }
45}
46
47impl FlowLayout {
48 pub fn new() -> Self {
49 Self {
50 blocks: HashMap::new(),
51 tables: HashMap::new(),
52 frames: HashMap::new(),
53 flow_order: Vec::new(),
54 content_height: 0.0,
55 viewport_width: 0.0,
56 viewport_height: 0.0,
57 cached_max_content_width: 0.0,
58 scale_factor: 1.0,
59 }
60 }
61
62 pub fn add_table(
64 &mut self,
65 registry: &FontRegistry,
66 params: &TableLayoutParams,
67 available_width: f32,
68 ) {
69 let mut table = layout_table(registry, params, available_width, self.scale_factor);
70
71 let mut y = self.content_height;
72 table.y = y;
73 y += table.total_height;
74
75 self.flow_order.push(FlowItem::Table {
76 table_id: table.table_id,
77 y: table.y,
78 height: table.total_height,
79 });
80 if table.total_width > self.cached_max_content_width {
81 self.cached_max_content_width = table.total_width;
82 }
83 self.tables.insert(table.table_id, table);
84 self.content_height = y;
85 }
86
87 pub fn add_frame(
96 &mut self,
97 registry: &FontRegistry,
98 params: &FrameLayoutParams,
99 available_width: f32,
100 ) {
101 use crate::layout::frame::FramePosition;
102
103 let mut frame = layout_frame(registry, params, available_width, self.scale_factor);
104
105 match params.position {
106 FramePosition::Inline => {
107 frame.y = self.content_height;
108 frame.x = 0.0;
109 self.content_height += frame.total_height;
110 }
111 FramePosition::FloatLeft => {
112 frame.y = self.content_height;
113 frame.x = 0.0;
114 self.content_height += frame.total_height;
119 }
120 FramePosition::FloatRight => {
121 frame.y = self.content_height;
122 frame.x = (available_width - frame.total_width).max(0.0);
123 self.content_height += frame.total_height;
124 }
125 FramePosition::Absolute => {
126 frame.y = params.margin_top;
129 frame.x = params.margin_left;
130 }
132 }
133
134 self.flow_order.push(FlowItem::Frame {
135 frame_id: frame.frame_id,
136 y: frame.y,
137 height: frame.total_height,
138 });
139 if frame.total_width > self.cached_max_content_width {
140 self.cached_max_content_width = frame.total_width;
141 }
142 self.frames.insert(frame.frame_id, frame);
143 }
144
145 pub fn clear(&mut self) {
147 self.blocks.clear();
148 self.tables.clear();
149 self.frames.clear();
150 self.flow_order.clear();
151 self.content_height = 0.0;
152 self.cached_max_content_width = 0.0;
153 }
154
155 pub fn add_block(
157 &mut self,
158 registry: &FontRegistry,
159 params: &BlockLayoutParams,
160 available_width: f32,
161 ) {
162 let mut block = layout_block(registry, params, available_width, self.scale_factor);
163
164 let mut y = self.content_height;
166 if let Some(FlowItem::Block {
167 block_id: prev_id, ..
168 }) = self.flow_order.last()
169 {
170 if let Some(prev_block) = self.blocks.get(prev_id) {
171 let collapsed = prev_block.bottom_margin.max(block.top_margin);
172 y -= prev_block.bottom_margin;
173 y += collapsed;
174 } else {
175 y += block.top_margin;
176 }
177 } else {
178 y += block.top_margin;
179 }
180
181 block.y = y;
182 let block_content = block.height - block.top_margin - block.bottom_margin;
183 y += block_content + block.bottom_margin;
184
185 self.flow_order.push(FlowItem::Block {
186 block_id: block.block_id,
187 y: block.y,
188 height: block.height,
189 });
190 self.update_max_width_for_block(&block);
191 self.blocks.insert(block.block_id, block);
192 self.content_height = y;
193 }
194
195 pub fn layout_blocks(
197 &mut self,
198 registry: &FontRegistry,
199 block_params: Vec<BlockLayoutParams>,
200 available_width: f32,
201 ) {
202 self.clear();
203 for params in &block_params {
208 self.add_block(registry, params, available_width);
209 }
210 }
211
212 pub fn relayout_block(
217 &mut self,
218 registry: &FontRegistry,
219 params: &BlockLayoutParams,
220 available_width: f32,
221 ) {
222 let block_id = params.block_id;
223
224 if self.blocks.contains_key(&block_id) {
226 self.relayout_top_level_block(registry, params, available_width);
227 return;
228 }
229
230 let table_match = self.tables.iter().find_map(|(&tid, table)| {
232 for cell in &table.cell_layouts {
233 if cell.blocks.iter().any(|b| b.block_id == block_id) {
234 return Some((tid, cell.row, cell.column));
235 }
236 }
237 None
238 });
239 if let Some((table_id, row, col)) = table_match {
240 self.relayout_table_block(registry, params, table_id, row, col);
241 return;
242 }
243
244 let frame_match = self.frames.iter().find_map(|(&fid, frame)| {
246 if frame_contains_block(frame, block_id) {
247 return Some(fid);
248 }
249 None
250 });
251 if let Some(frame_id) = frame_match {
252 self.relayout_frame_block(registry, params, frame_id);
253 }
254 }
255
256 fn relayout_top_level_block(
258 &mut self,
259 registry: &FontRegistry,
260 params: &BlockLayoutParams,
261 available_width: f32,
262 ) {
263 let block_id = params.block_id;
264 let old_y = self.blocks.get(&block_id).map(|b| b.y).unwrap_or(0.0);
265 let old_height = self.blocks.get(&block_id).map(|b| b.height).unwrap_or(0.0);
266 let old_top_margin = self
267 .blocks
268 .get(&block_id)
269 .map(|b| b.top_margin)
270 .unwrap_or(0.0);
271 let old_bottom_margin = self
272 .blocks
273 .get(&block_id)
274 .map(|b| b.bottom_margin)
275 .unwrap_or(0.0);
276 let old_content = old_height - old_top_margin - old_bottom_margin;
277 let old_end = old_y + old_content + old_bottom_margin;
278 let old_char_len = block_char_len(self.blocks.get(&block_id));
279
280 let mut block = layout_block(registry, params, available_width, self.scale_factor);
281 block.y = old_y;
282
283 if (block.top_margin - old_top_margin).abs() > 0.001 {
284 let prev_bm = self.prev_block_bottom_margin(block_id).unwrap_or(0.0);
285 let old_collapsed = prev_bm.max(old_top_margin);
286 let new_collapsed = prev_bm.max(block.top_margin);
287 block.y = old_y + (new_collapsed - old_collapsed);
288 }
289
290 let new_content = block.height - block.top_margin - block.bottom_margin;
291 let new_end = block.y + new_content + block.bottom_margin;
292 let delta = new_end - old_end;
293 let new_char_len = block_char_len(Some(&block));
294 let char_delta = new_char_len as isize - old_char_len as isize;
295
296 let new_y = block.y;
297 let new_height = block.height;
298 self.update_max_width_for_block(&block);
299 self.blocks.insert(block_id, block);
300
301 for item in &mut self.flow_order {
303 if let FlowItem::Block {
304 block_id: id,
305 y,
306 height,
307 } = item
308 && *id == block_id
309 {
310 *y = new_y;
311 *height = new_height;
312 break;
313 }
314 }
315
316 self.shift_items_after_block(block_id, delta);
317 self.shift_block_positions_after_block(block_id, char_delta);
318 }
319
320 fn relayout_table_block(
323 &mut self,
324 registry: &FontRegistry,
325 params: &BlockLayoutParams,
326 table_id: usize,
327 row: usize,
328 col: usize,
329 ) {
330 let table = match self.tables.get_mut(&table_id) {
331 Some(t) => t,
332 None => return,
333 };
334
335 let cell_width = table
336 .column_content_widths
337 .get(col)
338 .copied()
339 .unwrap_or(200.0);
340 let old_table_height = table.total_height;
341
342 let cell = match table
344 .cell_layouts
345 .iter_mut()
346 .find(|c| c.row == row && c.column == col)
347 {
348 Some(c) => c,
349 None => return,
350 };
351
352 let new_block = layout_block(registry, params, cell_width, self.scale_factor);
353 if let Some(old) = cell
354 .blocks
355 .iter_mut()
356 .find(|b| b.block_id == params.block_id)
357 {
358 *old = new_block;
359 }
360
361 let mut block_y = 0.0f32;
363 for block in &mut cell.blocks {
364 block.y = block_y;
365 block_y += block.height;
366 }
367 let cell_height = block_y;
368
369 if row < table.row_heights.len() {
371 let mut max_h = 0.0f32;
372 for c in &table.cell_layouts {
373 if c.row == row {
374 let h: f32 = c.blocks.iter().map(|b| b.height).sum();
375 max_h = max_h.max(h);
376 }
377 }
378 max_h = max_h.max(cell_height);
380 table.row_heights[row] = max_h;
381 }
382
383 let border = table.border_width;
385 let padding = table.cell_padding;
386 let spacing = if table.row_ys.len() > 1 {
387 if table.row_ys.len() >= 2 && !table.row_heights.is_empty() {
389 let expected = table.row_ys[0] + padding + table.row_heights[0] + padding;
390 (table.row_ys.get(1).copied().unwrap_or(expected) - expected).max(0.0)
391 } else {
392 0.0
393 }
394 } else {
395 0.0
396 };
397 let mut y = border;
398 for (r, &row_h) in table.row_heights.iter().enumerate() {
399 if r < table.row_ys.len() {
400 table.row_ys[r] = y + padding;
401 }
402 y += padding * 2.0 + row_h;
403 if r < table.row_heights.len() - 1 {
404 y += spacing;
405 }
406 }
407 table.total_height = y + border;
408
409 let delta = table.total_height - old_table_height;
410
411 for item in &mut self.flow_order {
413 if let FlowItem::Table {
414 table_id: id,
415 height,
416 ..
417 } = item
418 && *id == table_id
419 {
420 *height = table.total_height;
421 break;
422 }
423 }
424
425 self.shift_items_after_table(table_id, delta);
426 }
427
428 fn relayout_frame_block(
431 &mut self,
432 registry: &FontRegistry,
433 params: &BlockLayoutParams,
434 frame_id: usize,
435 ) {
436 let frame = match self.frames.get_mut(&frame_id) {
437 Some(f) => f,
438 None => return,
439 };
440
441 let old_total_height = frame.total_height;
442 let new_block = layout_block(registry, params, frame.content_width, self.scale_factor);
443
444 relayout_block_in_frame(frame, params.block_id, new_block);
445
446 let delta = frame.total_height - old_total_height;
447
448 for item in &mut self.flow_order {
449 if let FlowItem::Frame {
450 frame_id: id,
451 height,
452 ..
453 } = item
454 && *id == frame_id
455 {
456 *height = frame.total_height;
457 break;
458 }
459 }
460
461 self.shift_items_after_frame(frame_id, delta);
462 }
463
464 fn shift_block_positions_after_block(&mut self, block_id: usize, char_delta: isize) {
473 if char_delta == 0 {
474 return;
475 }
476 let refs: Vec<FlowItemRef> = self
479 .flow_order
480 .iter()
481 .map(|item| match item {
482 FlowItem::Block { block_id, .. } => FlowItemRef::Block(*block_id),
483 FlowItem::Table { table_id, .. } => FlowItemRef::Table(*table_id),
484 FlowItem::Frame { frame_id, .. } => FlowItemRef::Frame(*frame_id),
485 })
486 .collect();
487 let mut found = false;
488 for r in refs {
489 match r {
490 FlowItemRef::Block(id) => {
491 if found && let Some(b) = self.blocks.get_mut(&id) {
492 b.position = apply_char_delta(b.position, char_delta);
493 }
494 if id == block_id {
495 found = true;
496 }
497 }
498 FlowItemRef::Table(id) => {
499 if found && let Some(t) = self.tables.get_mut(&id) {
500 shift_block_positions_in_table(t, char_delta);
501 }
502 }
503 FlowItemRef::Frame(id) => {
504 if found && let Some(f) = self.frames.get_mut(&id) {
505 shift_block_positions_in_frame(f, char_delta);
506 }
507 }
508 }
509 }
510 }
511
512 fn shift_items_after_block(&mut self, block_id: usize, delta: f32) {
514 if delta.abs() <= 0.001 {
515 return;
516 }
517 let mut found = false;
518 for item in &mut self.flow_order {
519 match item {
520 FlowItem::Block {
521 block_id: id, y, ..
522 } => {
523 if found {
524 *y += delta;
525 if let Some(b) = self.blocks.get_mut(id) {
526 b.y += delta;
527 }
528 }
529 if *id == block_id {
530 found = true;
531 }
532 }
533 FlowItem::Table {
534 table_id: id, y, ..
535 } => {
536 if found {
537 *y += delta;
538 if let Some(t) = self.tables.get_mut(id) {
539 t.y += delta;
540 }
541 }
542 }
543 FlowItem::Frame {
544 frame_id: id, y, ..
545 } => {
546 if found {
547 *y += delta;
548 if let Some(f) = self.frames.get_mut(id) {
549 f.y += delta;
550 }
551 }
552 }
553 }
554 }
555 self.content_height += delta;
556 }
557
558 fn shift_items_after_table(&mut self, table_id: usize, delta: f32) {
560 if delta.abs() <= 0.001 {
561 return;
562 }
563 let mut found = false;
564 for item in &mut self.flow_order {
565 match item {
566 FlowItem::Table {
567 table_id: id, y, ..
568 } => {
569 if *id == table_id {
570 found = true;
571 continue;
572 }
573 if found {
574 *y += delta;
575 if let Some(t) = self.tables.get_mut(id) {
576 t.y += delta;
577 }
578 }
579 }
580 FlowItem::Block {
581 block_id: id, y, ..
582 } => {
583 if found {
584 *y += delta;
585 if let Some(b) = self.blocks.get_mut(id) {
586 b.y += delta;
587 }
588 }
589 }
590 FlowItem::Frame {
591 frame_id: id, y, ..
592 } => {
593 if found {
594 *y += delta;
595 if let Some(f) = self.frames.get_mut(id) {
596 f.y += delta;
597 }
598 }
599 }
600 }
601 }
602 self.content_height += delta;
603 }
604
605 fn shift_items_after_frame(&mut self, frame_id: usize, delta: f32) {
607 if delta.abs() <= 0.001 {
608 return;
609 }
610 let mut found = false;
611 for item in &mut self.flow_order {
612 match item {
613 FlowItem::Frame {
614 frame_id: id, y, ..
615 } => {
616 if *id == frame_id {
617 found = true;
618 continue;
619 }
620 if found {
621 *y += delta;
622 if let Some(f) = self.frames.get_mut(id) {
623 f.y += delta;
624 }
625 }
626 }
627 FlowItem::Block {
628 block_id: id, y, ..
629 } => {
630 if found {
631 *y += delta;
632 if let Some(b) = self.blocks.get_mut(id) {
633 b.y += delta;
634 }
635 }
636 }
637 FlowItem::Table {
638 table_id: id, y, ..
639 } => {
640 if found {
641 *y += delta;
642 if let Some(t) = self.tables.get_mut(id) {
643 t.y += delta;
644 }
645 }
646 }
647 }
648 }
649 self.content_height += delta;
650 }
651
652 fn update_max_width_for_block(&mut self, block: &BlockLayout) {
654 for line in &block.lines {
655 let w = line.width + block.left_margin + block.right_margin;
656 if w > self.cached_max_content_width {
657 self.cached_max_content_width = w;
658 }
659 }
660 }
661
662 fn prev_block_bottom_margin(&self, block_id: usize) -> Option<f32> {
664 let mut prev_bm = None;
665 for item in &self.flow_order {
666 match item {
667 FlowItem::Block { block_id: id, .. } => {
668 if *id == block_id {
669 return prev_bm;
670 }
671 if let Some(b) = self.blocks.get(id) {
672 prev_bm = Some(b.bottom_margin);
673 }
674 }
675 _ => {
676 prev_bm = None;
678 }
679 }
680 }
681 None
682 }
683}
684
685enum FlowItemRef {
686 Block(usize),
687 Table(usize),
688 Frame(usize),
689}
690
691fn block_char_len(block: Option<&BlockLayout>) -> usize {
692 block
693 .and_then(|b| b.lines.last().map(|l| l.char_range.end))
694 .unwrap_or(0)
695}
696
697fn apply_char_delta(position: usize, delta: isize) -> usize {
698 if delta >= 0 {
699 position + delta as usize
700 } else {
701 position.saturating_sub((-delta) as usize)
702 }
703}
704
705fn shift_block_positions_in_slice(blocks: &mut [BlockLayout], delta: isize) {
706 for block in blocks {
707 block.position = apply_char_delta(block.position, delta);
708 }
709}
710
711fn shift_block_positions_in_table(table: &mut TableLayout, delta: isize) {
712 for cell in &mut table.cell_layouts {
713 shift_block_positions_in_slice(&mut cell.blocks, delta);
714 }
715}
716
717fn shift_block_positions_in_frame(frame: &mut FrameLayout, delta: isize) {
718 shift_block_positions_in_slice(&mut frame.blocks, delta);
719 for table in &mut frame.tables {
720 shift_block_positions_in_table(table, delta);
721 }
722 for nested in &mut frame.frames {
723 shift_block_positions_in_frame(nested, delta);
724 }
725}
726
727pub(crate) fn frame_contains_block(frame: &FrameLayout, block_id: usize) -> bool {
729 if frame.blocks.iter().any(|b| b.block_id == block_id) {
730 return true;
731 }
732 frame
733 .frames
734 .iter()
735 .any(|nested| frame_contains_block(nested, block_id))
736}
737
738fn relayout_block_in_frame(frame: &mut FrameLayout, block_id: usize, new_block: BlockLayout) {
741 let old_content_height = frame.content_height;
742
743 if let Some(old) = frame.blocks.iter_mut().find(|b| b.block_id == block_id) {
745 *old = new_block;
746 } else {
747 for nested in &mut frame.frames {
749 if frame_contains_block(nested, block_id) {
750 relayout_block_in_frame(nested, block_id, new_block);
751 break;
752 }
753 }
754 }
755
756 let mut content_y = 0.0f32;
758 for block in &mut frame.blocks {
759 block.y = content_y + block.top_margin;
760 let block_content = block.height - block.top_margin - block.bottom_margin;
761 content_y = block.y + block_content + block.bottom_margin;
762 }
763 for table in &mut frame.tables {
764 table.y = content_y;
765 content_y += table.total_height;
766 }
767 for nested in &mut frame.frames {
768 nested.y = content_y;
769 content_y += nested.total_height;
770 }
771
772 frame.content_height = content_y;
773 frame.total_height += content_y - old_content_height;
774}