1use std::collections::HashMap;
2
3use crate::font::registry::FontRegistry;
4use crate::layout::block::{
5 BlockLayout, BlockLayoutParams, PaintSpan, apply_paint_spans, layout_block,
6};
7use crate::layout::frame::{FrameLayout, FrameLayoutParams, layout_frame};
8use crate::layout::table::{TableLayout, TableLayoutParams, layout_table};
9
10pub enum FlowItem {
11 Block {
12 block_id: usize,
13 y: f32,
14 height: f32,
15 },
16 Table {
17 table_id: usize,
18 y: f32,
19 height: f32,
20 },
21 Frame {
22 frame_id: usize,
23 y: f32,
24 height: f32,
25 },
26}
27
28pub struct FlowLayout {
29 pub blocks: HashMap<usize, BlockLayout>,
30 pub tables: HashMap<usize, TableLayout>,
31 pub frames: HashMap<usize, FrameLayout>,
32 pub flow_order: Vec<FlowItem>,
33 pub content_height: f32,
34 pub viewport_width: f32,
35 pub viewport_height: f32,
36 pub cached_max_content_width: f32,
37 pub scale_factor: f32,
41 pub font_scale: f32,
46 base_blocks: HashMap<usize, BlockLayout>,
52 pending_paint_spans: HashMap<usize, Vec<PaintSpan>>,
56}
57
58impl Default for FlowLayout {
59 fn default() -> Self {
60 Self::new()
61 }
62}
63
64impl FlowLayout {
65 pub fn new() -> Self {
66 Self {
67 blocks: HashMap::new(),
68 tables: HashMap::new(),
69 frames: HashMap::new(),
70 flow_order: Vec::new(),
71 content_height: 0.0,
72 viewport_width: 0.0,
73 viewport_height: 0.0,
74 cached_max_content_width: 0.0,
75 scale_factor: 1.0,
76 font_scale: 1.0,
77 base_blocks: HashMap::new(),
78 pending_paint_spans: HashMap::new(),
79 }
80 }
81
82 pub fn add_table(
84 &mut self,
85 registry: &FontRegistry,
86 params: &TableLayoutParams,
87 available_width: f32,
88 ) {
89 let mut table = layout_table(
90 registry,
91 params,
92 available_width,
93 self.scale_factor,
94 self.font_scale,
95 );
96
97 let mut y = self.content_height;
98 table.y = y;
99 y += table.total_height;
100
101 self.flow_order.push(FlowItem::Table {
102 table_id: table.table_id,
103 y: table.y,
104 height: table.total_height,
105 });
106 if table.total_width > self.cached_max_content_width {
107 self.cached_max_content_width = table.total_width;
108 }
109 self.tables.insert(table.table_id, table);
110 self.content_height = y;
111 }
112
113 pub fn add_frame(
122 &mut self,
123 registry: &FontRegistry,
124 params: &FrameLayoutParams,
125 available_width: f32,
126 ) {
127 use crate::layout::frame::FramePosition;
128
129 let mut frame = layout_frame(
130 registry,
131 params,
132 available_width,
133 self.scale_factor,
134 self.font_scale,
135 );
136
137 match params.position {
138 FramePosition::Inline => {
139 frame.y = self.content_height;
140 frame.x = 0.0;
141 self.content_height += frame.total_height;
142 }
143 FramePosition::FloatLeft => {
144 frame.y = self.content_height;
145 frame.x = 0.0;
146 self.content_height += frame.total_height;
151 }
152 FramePosition::FloatRight => {
153 frame.y = self.content_height;
154 frame.x = (available_width - frame.total_width).max(0.0);
155 self.content_height += frame.total_height;
156 }
157 FramePosition::Absolute => {
158 frame.y = params.margin_top;
161 frame.x = params.margin_left;
162 }
164 }
165
166 self.flow_order.push(FlowItem::Frame {
167 frame_id: frame.frame_id,
168 y: frame.y,
169 height: frame.total_height,
170 });
171 if frame.total_width > self.cached_max_content_width {
172 self.cached_max_content_width = frame.total_width;
173 }
174 self.frames.insert(frame.frame_id, frame);
175 }
176
177 pub fn clear(&mut self) {
179 self.blocks.clear();
180 self.tables.clear();
181 self.frames.clear();
182 self.flow_order.clear();
183 self.content_height = 0.0;
184 self.cached_max_content_width = 0.0;
185 self.base_blocks.clear();
186 self.pending_paint_spans.clear();
187 }
188
189 pub(crate) fn refresh_base_blocks(&mut self) {
192 self.base_blocks.clear();
193 let mut collected: Vec<(usize, BlockLayout)> = Vec::new();
194 for b in self.blocks.values() {
195 collected.push((b.block_id, b.clone()));
196 }
197 for t in self.tables.values() {
198 collect_table_base(t, &mut collected);
199 }
200 for f in self.frames.values() {
201 collect_frame_base(f, &mut collected);
202 }
203 for (id, b) in collected {
204 self.base_blocks.insert(id, b);
205 }
206 }
207
208 pub fn apply_paint_spans_for(&mut self, spans_by_block: HashMap<usize, Vec<PaintSpan>>) {
216 self.pending_paint_spans = spans_by_block;
217 let base = &self.base_blocks;
218 let pending = &self.pending_paint_spans;
219 for b in self.blocks.values_mut() {
220 overlay_block_in_place(b, base, pending);
221 }
222 for t in self.tables.values_mut() {
223 for c in &mut t.cell_layouts {
224 for b in &mut c.blocks {
225 overlay_block_in_place(b, base, pending);
226 }
227 }
228 }
229 for f in self.frames.values_mut() {
230 overlay_frame_in_place(f, base, pending);
231 }
232 }
233
234 pub fn apply_block_paint_spans(&mut self, block_id: usize, spans: &[PaintSpan]) -> bool {
238 if !self.base_blocks.contains_key(&block_id) {
239 return false;
240 }
241 if spans.is_empty() {
242 self.pending_paint_spans.remove(&block_id);
243 } else {
244 self.pending_paint_spans.insert(block_id, spans.to_vec());
245 }
246 let base = &self.base_blocks;
247 let pending = &self.pending_paint_spans;
248 if let Some(b) = self.blocks.get_mut(&block_id) {
249 overlay_block_in_place(b, base, pending);
250 return true;
251 }
252 for t in self.tables.values_mut() {
253 for c in &mut t.cell_layouts {
254 for b in &mut c.blocks {
255 if b.block_id == block_id {
256 overlay_block_in_place(b, base, pending);
257 return true;
258 }
259 }
260 }
261 }
262 for f in self.frames.values_mut() {
263 if overlay_one_in_frame(f, block_id, base, pending) {
264 return true;
265 }
266 }
267 true
268 }
269
270 fn refresh_base_and_overlay_block(&mut self, block_id: usize) {
282 let fresh = find_block_ref(self, block_id).cloned();
283 if let Some(b) = fresh {
284 self.base_blocks.insert(block_id, b);
285 }
286 if self.pending_paint_spans.is_empty() {
289 return;
290 }
291 let base = &self.base_blocks;
292 let pending = &self.pending_paint_spans;
293 if let Some(b) = self.blocks.get_mut(&block_id) {
294 overlay_block_in_place(b, base, pending);
295 return;
296 }
297 for t in self.tables.values_mut() {
298 for c in &mut t.cell_layouts {
299 for b in &mut c.blocks {
300 if b.block_id == block_id {
301 overlay_block_in_place(b, base, pending);
302 return;
303 }
304 }
305 }
306 }
307 for f in self.frames.values_mut() {
308 if overlay_one_in_frame(f, block_id, base, pending) {
309 return;
310 }
311 }
312 }
313
314 pub fn add_block(
328 &mut self,
329 registry: &FontRegistry,
330 params: &BlockLayoutParams,
331 available_width: f32,
332 ) {
333 let mut block = layout_block(
334 registry,
335 params,
336 available_width,
337 self.scale_factor,
338 self.font_scale,
339 );
340
341 let mut y = self.content_height;
343 if let Some(FlowItem::Block {
344 block_id: prev_id, ..
345 }) = self.flow_order.last()
346 {
347 if let Some(prev_block) = self.blocks.get(prev_id) {
348 let collapsed = prev_block.bottom_margin.max(block.top_margin);
349 y -= prev_block.bottom_margin;
350 y += collapsed;
351 } else {
352 y += block.top_margin;
353 }
354 } else {
355 y += block.top_margin;
356 }
357
358 block.y = y;
359 let block_content = block.height - block.top_margin - block.bottom_margin;
360 y += block_content + block.bottom_margin;
361
362 self.flow_order.push(FlowItem::Block {
363 block_id: block.block_id,
364 y: block.y,
365 height: block.height,
366 });
367 self.update_max_width_for_block(&block);
368 self.blocks.insert(block.block_id, block);
369 self.content_height = y;
370 }
371
372 pub fn append_block(
391 &mut self,
392 registry: &FontRegistry,
393 params: &BlockLayoutParams,
394 available_width: f32,
395 ) {
396 self.add_block(registry, params, available_width);
397 self.refresh_base_and_overlay_block(params.block_id);
398 }
399
400 pub fn remove_leading(&mut self, n: usize) -> usize {
425 let mut removed = 0;
426 let mut evicted_widest = false;
427
428 for item in self.flow_order.iter().take(n) {
429 let FlowItem::Block { block_id, .. } = item else {
430 break;
431 };
432 if let Some(block) = self.blocks.remove(block_id) {
433 if block_max_width(&block) >= self.cached_max_content_width {
437 evicted_widest = true;
438 }
439 }
440 self.base_blocks.remove(block_id);
441 self.pending_paint_spans.remove(block_id);
442 removed += 1;
443 }
444
445 self.flow_order.drain(..removed);
446 if evicted_widest {
447 self.recompute_max_content_width();
448 }
449 removed
450 }
451
452 pub(crate) fn recompute_max_content_width(&mut self) {
459 let mut max: f32 = 0.0;
460 for block in self.blocks.values() {
461 max = max.max(block_max_width(block));
462 }
463 for table in self.tables.values() {
464 max = max.max(table.total_width);
465 }
466 for frame in self.frames.values() {
467 max = max.max(frame.total_width);
468 }
469 self.cached_max_content_width = max;
470 }
471
472 pub fn set_uniform_extent(&mut self, total_rows: usize, row_height: f32) {
482 self.content_height = total_rows as f32 * row_height;
483 }
484
485 pub fn layout_window(
538 &mut self,
539 registry: &FontRegistry,
540 window: &[(usize, BlockLayoutParams)],
541 total_rows: usize,
542 row_height: f32,
543 available_width: f32,
544 ) {
545 debug_assert!(
546 window.windows(2).all(|w| w[0].0 < w[1].0),
547 "layout_window: window must be sorted ascending by row index, else \
548 flow_order stops being ascending by y and hit_test's binary search \
549 silently misreports rows"
550 );
551 debug_assert!(
552 window.last().is_none_or(|(last, _)| *last < total_rows),
553 "layout_window: window reaches row {:?} but the document declares \
554 only {total_rows} rows — those rows would sit below content_height \
555 and be unreachable by scrolling",
556 window.last().map(|(i, _)| *i)
557 );
558
559 let max_width_seen = self.cached_max_content_width;
564 self.clear();
565 self.cached_max_content_width = max_width_seen;
566
567 for (index, params) in window {
568 let mut block = layout_block(
569 registry,
570 params,
571 available_width,
572 self.scale_factor,
573 self.font_scale,
574 );
575 debug_assert!(
576 (block.height - row_height).abs() < 0.5,
577 "layout_window: row {index} laid out {:.2}px tall against a \
578 declared row_height of {row_height:.2}px — the row is not a \
579 single unwrapped line, so arithmetic y placement would drift",
580 block.height
581 );
582
583 let y = *index as f32 * row_height;
584 block.y = y;
585 self.flow_order.push(FlowItem::Block {
586 block_id: block.block_id,
587 y,
588 height: block.height,
589 });
590 self.update_max_width_for_block(&block);
591 self.blocks.insert(block.block_id, block);
592 }
593
594 self.set_uniform_extent(total_rows, row_height);
597 self.refresh_base_blocks();
600 }
601
602 pub fn layout_blocks(
604 &mut self,
605 registry: &FontRegistry,
606 block_params: Vec<BlockLayoutParams>,
607 available_width: f32,
608 ) {
609 self.clear();
610 for params in &block_params {
615 self.add_block(registry, params, available_width);
616 }
617 self.refresh_base_blocks();
622 }
623
624 pub fn relayout_block(
629 &mut self,
630 registry: &FontRegistry,
631 params: &BlockLayoutParams,
632 available_width: f32,
633 ) {
634 let block_id = params.block_id;
635
636 if self.blocks.contains_key(&block_id) {
638 self.relayout_top_level_block(registry, params, available_width);
639 self.refresh_base_and_overlay_block(block_id);
640 return;
641 }
642
643 let table_match = self.tables.iter().find_map(|(&tid, table)| {
645 for cell in &table.cell_layouts {
646 if cell.blocks.iter().any(|b| b.block_id == block_id) {
647 return Some((tid, cell.row, cell.column));
648 }
649 }
650 None
651 });
652 if let Some((table_id, row, col)) = table_match {
653 let old_char_len = block_char_len(find_block_ref(self, block_id));
654 self.relayout_table_block(registry, params, table_id, row, col);
655 let new_char_len = block_char_len(find_block_ref(self, block_id));
656 let char_delta = new_char_len as isize - old_char_len as isize;
657 self.shift_block_positions_after_table_block(table_id, block_id, char_delta);
658 self.refresh_base_and_overlay_block(block_id);
659 return;
660 }
661
662 let frame_match = self.frames.iter().find_map(|(&fid, frame)| {
664 if frame_contains_block(frame, block_id) {
665 return Some(fid);
666 }
667 None
668 });
669 if let Some(frame_id) = frame_match {
670 let old_char_len = block_char_len(find_block_ref(self, block_id));
671 self.relayout_frame_block(registry, params, frame_id);
672 let new_char_len = block_char_len(find_block_ref(self, block_id));
673 let char_delta = new_char_len as isize - old_char_len as isize;
674 self.shift_block_positions_after_frame_block(frame_id, block_id, char_delta);
675 self.refresh_base_and_overlay_block(block_id);
676 }
677 }
678
679 fn relayout_top_level_block(
681 &mut self,
682 registry: &FontRegistry,
683 params: &BlockLayoutParams,
684 available_width: f32,
685 ) {
686 let block_id = params.block_id;
687 let old_y = self.blocks.get(&block_id).map(|b| b.y).unwrap_or(0.0);
688 let old_height = self.blocks.get(&block_id).map(|b| b.height).unwrap_or(0.0);
689 let old_top_margin = self
690 .blocks
691 .get(&block_id)
692 .map(|b| b.top_margin)
693 .unwrap_or(0.0);
694 let old_bottom_margin = self
695 .blocks
696 .get(&block_id)
697 .map(|b| b.bottom_margin)
698 .unwrap_or(0.0);
699 let old_content = old_height - old_top_margin - old_bottom_margin;
700 let old_end = old_y + old_content + old_bottom_margin;
701 let old_char_len = block_char_len(self.blocks.get(&block_id));
702
703 let mut block = layout_block(
704 registry,
705 params,
706 available_width,
707 self.scale_factor,
708 self.font_scale,
709 );
710 block.y = old_y;
711
712 if (block.top_margin - old_top_margin).abs() > 0.001 {
713 let prev_bm = self.prev_block_bottom_margin(block_id).unwrap_or(0.0);
714 let old_collapsed = prev_bm.max(old_top_margin);
715 let new_collapsed = prev_bm.max(block.top_margin);
716 block.y = old_y + (new_collapsed - old_collapsed);
717 }
718
719 let new_content = block.height - block.top_margin - block.bottom_margin;
720 let new_end = block.y + new_content + block.bottom_margin;
721 let delta = new_end - old_end;
722 let new_char_len = block_char_len(Some(&block));
723 let char_delta = new_char_len as isize - old_char_len as isize;
724
725 let new_y = block.y;
726 let new_height = block.height;
727 self.update_max_width_for_block(&block);
728 self.blocks.insert(block_id, block);
729
730 for item in &mut self.flow_order {
732 if let FlowItem::Block {
733 block_id: id,
734 y,
735 height,
736 } = item
737 && *id == block_id
738 {
739 *y = new_y;
740 *height = new_height;
741 break;
742 }
743 }
744
745 self.shift_items_after_block(block_id, delta);
746 self.shift_block_positions_after_block(block_id, char_delta);
747 }
748
749 fn relayout_table_block(
752 &mut self,
753 registry: &FontRegistry,
754 params: &BlockLayoutParams,
755 table_id: usize,
756 row: usize,
757 col: usize,
758 ) {
759 let table = match self.tables.get_mut(&table_id) {
760 Some(t) => t,
761 None => return,
762 };
763
764 let old_table_height = table.total_height;
765 recompute_table_cell(
766 table,
767 registry,
768 params,
769 row,
770 col,
771 self.scale_factor,
772 self.font_scale,
773 );
774 let delta = table.total_height - old_table_height;
775
776 for item in &mut self.flow_order {
778 if let FlowItem::Table {
779 table_id: id,
780 height,
781 ..
782 } = item
783 && *id == table_id
784 {
785 *height = table.total_height;
786 break;
787 }
788 }
789
790 self.shift_items_after_table(table_id, delta);
791 }
792
793 fn relayout_frame_block(
798 &mut self,
799 registry: &FontRegistry,
800 params: &BlockLayoutParams,
801 frame_id: usize,
802 ) {
803 let old_total_height = match self.frames.get(&frame_id) {
804 Some(f) => f.total_height,
805 None => return,
806 };
807
808 {
809 let frame = self.frames.get_mut(&frame_id).unwrap();
810 relayout_block_deep_in_frame(
811 frame,
812 registry,
813 params,
814 self.scale_factor,
815 self.font_scale,
816 );
817 }
818
819 let new_total_height = self.frames[&frame_id].total_height;
820 let delta = new_total_height - old_total_height;
821
822 for item in &mut self.flow_order {
823 if let FlowItem::Frame {
824 frame_id: id,
825 height,
826 ..
827 } = item
828 && *id == frame_id
829 {
830 *height = new_total_height;
831 break;
832 }
833 }
834
835 self.shift_items_after_frame(frame_id, delta);
836 }
837
838 fn shift_block_positions_after_block(&mut self, block_id: usize, char_delta: isize) {
847 self.shift_block_positions_after_flow_item(FlowItemRef::Block(block_id), char_delta);
848 }
849
850 fn shift_block_positions_after_flow_item(&mut self, target: FlowItemRef, char_delta: isize) {
853 if char_delta == 0 {
854 return;
855 }
856 let refs: Vec<FlowItemRef> = self
859 .flow_order
860 .iter()
861 .map(|item| match item {
862 FlowItem::Block { block_id, .. } => FlowItemRef::Block(*block_id),
863 FlowItem::Table { table_id, .. } => FlowItemRef::Table(*table_id),
864 FlowItem::Frame { frame_id, .. } => FlowItemRef::Frame(*frame_id),
865 })
866 .collect();
867 let mut found = false;
868 for r in refs {
869 if found {
870 match r {
871 FlowItemRef::Block(id) => {
872 if let Some(b) = self.blocks.get_mut(&id) {
873 b.position = apply_char_delta(b.position, char_delta);
874 }
875 }
876 FlowItemRef::Table(id) => {
877 if let Some(t) = self.tables.get_mut(&id) {
878 shift_block_positions_in_table(t, char_delta);
879 }
880 }
881 FlowItemRef::Frame(id) => {
882 if let Some(f) = self.frames.get_mut(&id) {
883 shift_block_positions_in_frame(f, char_delta);
884 }
885 }
886 }
887 } else if r == target {
888 found = true;
889 }
890 }
891 }
892
893 fn shift_block_positions_after_table_block(
897 &mut self,
898 table_id: usize,
899 block_id: usize,
900 char_delta: isize,
901 ) {
902 if char_delta == 0 {
903 return;
904 }
905 if let Some(table) = self.tables.get_mut(&table_id) {
906 shift_table_positions_after_block(table, block_id, char_delta);
907 }
908 self.shift_block_positions_after_flow_item(FlowItemRef::Table(table_id), char_delta);
909 }
910
911 fn shift_block_positions_after_frame_block(
915 &mut self,
916 frame_id: usize,
917 block_id: usize,
918 char_delta: isize,
919 ) {
920 if char_delta == 0 {
921 return;
922 }
923 if let Some(frame) = self.frames.get_mut(&frame_id) {
924 shift_frame_positions_after_block(frame, block_id, char_delta);
925 }
926 self.shift_block_positions_after_flow_item(FlowItemRef::Frame(frame_id), char_delta);
927 }
928
929 fn shift_items_after_block(&mut self, block_id: usize, delta: f32) {
931 if delta.abs() <= 0.001 {
932 return;
933 }
934 let mut found = false;
935 for item in &mut self.flow_order {
936 match item {
937 FlowItem::Block {
938 block_id: id, y, ..
939 } => {
940 if found {
941 *y += delta;
942 if let Some(b) = self.blocks.get_mut(id) {
943 b.y += delta;
944 }
945 }
946 if *id == block_id {
947 found = true;
948 }
949 }
950 FlowItem::Table {
951 table_id: id, y, ..
952 } => {
953 if found {
954 *y += delta;
955 if let Some(t) = self.tables.get_mut(id) {
956 t.y += delta;
957 }
958 }
959 }
960 FlowItem::Frame {
961 frame_id: id, y, ..
962 } => {
963 if found {
964 *y += delta;
965 if let Some(f) = self.frames.get_mut(id) {
966 f.y += delta;
967 }
968 }
969 }
970 }
971 }
972 self.content_height += delta;
973 }
974
975 fn shift_items_after_table(&mut self, table_id: usize, delta: f32) {
977 if delta.abs() <= 0.001 {
978 return;
979 }
980 let mut found = false;
981 for item in &mut self.flow_order {
982 match item {
983 FlowItem::Table {
984 table_id: id, y, ..
985 } => {
986 if *id == table_id {
987 found = true;
988 continue;
989 }
990 if found {
991 *y += delta;
992 if let Some(t) = self.tables.get_mut(id) {
993 t.y += delta;
994 }
995 }
996 }
997 FlowItem::Block {
998 block_id: id, y, ..
999 } => {
1000 if found {
1001 *y += delta;
1002 if let Some(b) = self.blocks.get_mut(id) {
1003 b.y += delta;
1004 }
1005 }
1006 }
1007 FlowItem::Frame {
1008 frame_id: id, y, ..
1009 } => {
1010 if found {
1011 *y += delta;
1012 if let Some(f) = self.frames.get_mut(id) {
1013 f.y += delta;
1014 }
1015 }
1016 }
1017 }
1018 }
1019 self.content_height += delta;
1020 }
1021
1022 fn shift_items_after_frame(&mut self, frame_id: usize, delta: f32) {
1024 if delta.abs() <= 0.001 {
1025 return;
1026 }
1027 let mut found = false;
1028 for item in &mut self.flow_order {
1029 match item {
1030 FlowItem::Frame {
1031 frame_id: id, y, ..
1032 } => {
1033 if *id == frame_id {
1034 found = true;
1035 continue;
1036 }
1037 if found {
1038 *y += delta;
1039 if let Some(f) = self.frames.get_mut(id) {
1040 f.y += delta;
1041 }
1042 }
1043 }
1044 FlowItem::Block {
1045 block_id: id, y, ..
1046 } => {
1047 if found {
1048 *y += delta;
1049 if let Some(b) = self.blocks.get_mut(id) {
1050 b.y += delta;
1051 }
1052 }
1053 }
1054 FlowItem::Table {
1055 table_id: id, y, ..
1056 } => {
1057 if found {
1058 *y += delta;
1059 if let Some(t) = self.tables.get_mut(id) {
1060 t.y += delta;
1061 }
1062 }
1063 }
1064 }
1065 }
1066 self.content_height += delta;
1067 }
1068
1069 fn update_max_width_for_block(&mut self, block: &BlockLayout) {
1071 let w = block_max_width(block);
1072 if w > self.cached_max_content_width {
1073 self.cached_max_content_width = w;
1074 }
1075 }
1076
1077 fn prev_block_bottom_margin(&self, block_id: usize) -> Option<f32> {
1079 let mut prev_bm = None;
1080 for item in &self.flow_order {
1081 match item {
1082 FlowItem::Block { block_id: id, .. } => {
1083 if *id == block_id {
1084 return prev_bm;
1085 }
1086 if let Some(b) = self.blocks.get(id) {
1087 prev_bm = Some(b.bottom_margin);
1088 }
1089 }
1090 _ => {
1091 prev_bm = None;
1093 }
1094 }
1095 }
1096 None
1097 }
1098}
1099
1100#[derive(Clone, Copy, PartialEq, Eq)]
1101enum FlowItemRef {
1102 Block(usize),
1103 Table(usize),
1104 Frame(usize),
1105}
1106
1107fn block_char_len(block: Option<&BlockLayout>) -> usize {
1108 block
1109 .and_then(|b| b.lines.last().map(|l| l.char_range.end))
1110 .unwrap_or(0)
1111}
1112
1113fn apply_char_delta(position: usize, delta: isize) -> usize {
1114 if delta >= 0 {
1115 position + delta as usize
1116 } else {
1117 position.saturating_sub((-delta) as usize)
1118 }
1119}
1120
1121fn shift_block_positions_in_slice(blocks: &mut [BlockLayout], delta: isize) {
1122 for block in blocks {
1123 block.position = apply_char_delta(block.position, delta);
1124 }
1125}
1126
1127fn shift_block_positions_in_table(table: &mut TableLayout, delta: isize) {
1128 for cell in &mut table.cell_layouts {
1129 shift_block_positions_in_slice(&mut cell.blocks, delta);
1130 }
1131}
1132
1133fn shift_block_positions_in_frame(frame: &mut FrameLayout, delta: isize) {
1134 shift_block_positions_in_slice(&mut frame.blocks, delta);
1135 for table in &mut frame.tables {
1136 shift_block_positions_in_table(table, delta);
1137 }
1138 for nested in &mut frame.frames {
1139 shift_block_positions_in_frame(nested, delta);
1140 }
1141}
1142
1143fn shift_table_positions_after_block(
1147 table: &mut TableLayout,
1148 block_id: usize,
1149 delta: isize,
1150) -> bool {
1151 let mut found = false;
1152 for cell in &mut table.cell_layouts {
1153 for b in &mut cell.blocks {
1154 if found {
1155 b.position = apply_char_delta(b.position, delta);
1156 } else if b.block_id == block_id {
1157 found = true;
1158 }
1159 }
1160 }
1161 found
1162}
1163
1164fn shift_frame_positions_after_block(
1169 frame: &mut FrameLayout,
1170 block_id: usize,
1171 delta: isize,
1172) -> bool {
1173 let order = frame.flow_order.clone();
1174 let mut found = false;
1175 for child in &order {
1176 match child {
1177 crate::layout::frame::FrameChildRef::Block(bid) => {
1178 if found {
1179 if let Some(b) = frame.blocks.iter_mut().find(|b| b.block_id == *bid) {
1180 b.position = apply_char_delta(b.position, delta);
1181 }
1182 } else if *bid == block_id {
1183 found = true;
1184 }
1185 }
1186 crate::layout::frame::FrameChildRef::Table(tid) => {
1187 if let Some(t) = frame.tables.iter_mut().find(|t| t.table_id == *tid) {
1188 if found {
1189 shift_block_positions_in_table(t, delta);
1190 } else {
1191 found = shift_table_positions_after_block(t, block_id, delta);
1192 }
1193 }
1194 }
1195 crate::layout::frame::FrameChildRef::Frame(fid) => {
1196 if let Some(nested) = frame.frames.iter_mut().find(|f| f.frame_id == *fid) {
1197 if found {
1198 shift_block_positions_in_frame(nested, delta);
1199 } else {
1200 found = shift_frame_positions_after_block(nested, block_id, delta);
1201 }
1202 }
1203 }
1204 }
1205 }
1206 found
1207}
1208
1209fn overlay_block_in_place(
1214 b: &mut BlockLayout,
1215 base: &HashMap<usize, BlockLayout>,
1216 pending: &HashMap<usize, Vec<PaintSpan>>,
1217) {
1218 if let Some(base_b) = base.get(&b.block_id) {
1219 let empty: Vec<PaintSpan> = Vec::new();
1220 let spans = pending.get(&b.block_id).unwrap_or(&empty);
1221 *b = apply_paint_spans(base_b, spans);
1222 }
1223}
1224
1225fn overlay_frame_in_place(
1227 frame: &mut FrameLayout,
1228 base: &HashMap<usize, BlockLayout>,
1229 pending: &HashMap<usize, Vec<PaintSpan>>,
1230) {
1231 for b in &mut frame.blocks {
1232 overlay_block_in_place(b, base, pending);
1233 }
1234 for t in &mut frame.tables {
1235 for c in &mut t.cell_layouts {
1236 for b in &mut c.blocks {
1237 overlay_block_in_place(b, base, pending);
1238 }
1239 }
1240 }
1241 for nested in &mut frame.frames {
1242 overlay_frame_in_place(nested, base, pending);
1243 }
1244}
1245
1246fn overlay_one_in_frame(
1249 frame: &mut FrameLayout,
1250 block_id: usize,
1251 base: &HashMap<usize, BlockLayout>,
1252 pending: &HashMap<usize, Vec<PaintSpan>>,
1253) -> bool {
1254 for b in &mut frame.blocks {
1255 if b.block_id == block_id {
1256 overlay_block_in_place(b, base, pending);
1257 return true;
1258 }
1259 }
1260 for t in &mut frame.tables {
1261 for c in &mut t.cell_layouts {
1262 for b in &mut c.blocks {
1263 if b.block_id == block_id {
1264 overlay_block_in_place(b, base, pending);
1265 return true;
1266 }
1267 }
1268 }
1269 }
1270 for nested in &mut frame.frames {
1271 if overlay_one_in_frame(nested, block_id, base, pending) {
1272 return true;
1273 }
1274 }
1275 false
1276}
1277
1278fn collect_table_base(t: &TableLayout, out: &mut Vec<(usize, BlockLayout)>) {
1279 for c in &t.cell_layouts {
1280 for b in &c.blocks {
1281 out.push((b.block_id, b.clone()));
1282 }
1283 }
1284}
1285
1286fn collect_frame_base(f: &FrameLayout, out: &mut Vec<(usize, BlockLayout)>) {
1287 for b in &f.blocks {
1288 out.push((b.block_id, b.clone()));
1289 }
1290 for t in &f.tables {
1291 collect_table_base(t, out);
1292 }
1293 for nested in &f.frames {
1294 collect_frame_base(nested, out);
1295 }
1296}
1297
1298fn block_max_width(block: &BlockLayout) -> f32 {
1306 block
1307 .lines
1308 .iter()
1309 .map(|line| line.width + block.left_margin + block.right_margin)
1310 .fold(0.0_f32, f32::max)
1311}
1312
1313fn find_block_ref(flow: &FlowLayout, block_id: usize) -> Option<&BlockLayout> {
1314 if let Some(b) = flow.blocks.get(&block_id) {
1315 return Some(b);
1316 }
1317 for t in flow.tables.values() {
1318 for c in &t.cell_layouts {
1319 for b in &c.blocks {
1320 if b.block_id == block_id {
1321 return Some(b);
1322 }
1323 }
1324 }
1325 }
1326 for f in flow.frames.values() {
1327 if let Some(b) = find_block_in_frame(f, block_id) {
1328 return Some(b);
1329 }
1330 }
1331 None
1332}
1333
1334fn find_block_in_frame(frame: &FrameLayout, block_id: usize) -> Option<&BlockLayout> {
1335 for b in &frame.blocks {
1336 if b.block_id == block_id {
1337 return Some(b);
1338 }
1339 }
1340 for t in &frame.tables {
1341 for c in &t.cell_layouts {
1342 for b in &c.blocks {
1343 if b.block_id == block_id {
1344 return Some(b);
1345 }
1346 }
1347 }
1348 }
1349 for nested in &frame.frames {
1350 if let Some(b) = find_block_in_frame(nested, block_id) {
1351 return Some(b);
1352 }
1353 }
1354 None
1355}
1356
1357pub(crate) fn frame_contains_block(frame: &FrameLayout, block_id: usize) -> bool {
1361 if frame.blocks.iter().any(|b| b.block_id == block_id) {
1362 return true;
1363 }
1364 for table in &frame.tables {
1365 for cell in &table.cell_layouts {
1366 if cell.blocks.iter().any(|b| b.block_id == block_id) {
1367 return true;
1368 }
1369 }
1370 }
1371 frame
1372 .frames
1373 .iter()
1374 .any(|nested| frame_contains_block(nested, block_id))
1375}
1376
1377pub(crate) enum FrameBlockLocation {
1380 DirectBlock,
1382 TableCell(usize, usize, usize),
1385 NestedFrame(usize),
1388}
1389
1390pub(crate) fn find_block_location_in_frame(
1392 frame: &FrameLayout,
1393 block_id: usize,
1394) -> Option<FrameBlockLocation> {
1395 if frame.blocks.iter().any(|b| b.block_id == block_id) {
1396 return Some(FrameBlockLocation::DirectBlock);
1397 }
1398 for table in &frame.tables {
1399 for cell in &table.cell_layouts {
1400 if cell.blocks.iter().any(|b| b.block_id == block_id) {
1401 return Some(FrameBlockLocation::TableCell(
1402 table.table_id,
1403 cell.row,
1404 cell.column,
1405 ));
1406 }
1407 }
1408 }
1409 for nested in &frame.frames {
1410 if frame_contains_block(nested, block_id) {
1411 return Some(FrameBlockLocation::NestedFrame(nested.frame_id));
1412 }
1413 }
1414 None
1415}
1416
1417fn relayout_block_in_frame(frame: &mut FrameLayout, block_id: usize, new_block: BlockLayout) {
1422 if let Some(old) = frame.blocks.iter_mut().find(|b| b.block_id == block_id) {
1423 *old = new_block;
1424 }
1425 reposition_frame_children(frame);
1426}
1427
1428fn relayout_block_deep_in_frame(
1434 frame: &mut FrameLayout,
1435 registry: &FontRegistry,
1436 params: &BlockLayoutParams,
1437 scale_factor: f32,
1438 font_scale: f32,
1439) {
1440 match find_block_location_in_frame(frame, params.block_id) {
1441 None => {}
1442 Some(FrameBlockLocation::DirectBlock) => {
1443 let new_block = layout_block(
1444 registry,
1445 params,
1446 frame.content_width,
1447 scale_factor,
1448 font_scale,
1449 );
1450 relayout_block_in_frame(frame, params.block_id, new_block);
1451 }
1452 Some(FrameBlockLocation::TableCell(table_id, row, col)) => {
1453 if let Some(table) = frame.tables.iter_mut().find(|t| t.table_id == table_id) {
1454 recompute_table_cell(table, registry, params, row, col, scale_factor, font_scale);
1455 }
1456 reposition_frame_children(frame);
1457 }
1458 Some(FrameBlockLocation::NestedFrame(nested_frame_id)) => {
1459 if let Some(nested) = frame
1460 .frames
1461 .iter_mut()
1462 .find(|f| f.frame_id == nested_frame_id)
1463 {
1464 relayout_block_deep_in_frame(nested, registry, params, scale_factor, font_scale);
1465 }
1466 reposition_frame_children(frame);
1467 }
1468 }
1469}
1470
1471fn recompute_table_cell(
1477 table: &mut crate::layout::table::TableLayout,
1478 registry: &FontRegistry,
1479 params: &BlockLayoutParams,
1480 row: usize,
1481 col: usize,
1482 scale_factor: f32,
1483 font_scale: f32,
1484) {
1485 let cell_width = table
1486 .column_content_widths
1487 .get(col)
1488 .copied()
1489 .unwrap_or(200.0);
1490
1491 let cell = match table
1493 .cell_layouts
1494 .iter_mut()
1495 .find(|c| c.row == row && c.column == col)
1496 {
1497 Some(c) => c,
1498 None => return,
1499 };
1500
1501 let new_block = layout_block(registry, params, cell_width, scale_factor, font_scale);
1502 if let Some(old) = cell
1503 .blocks
1504 .iter_mut()
1505 .find(|b| b.block_id == params.block_id)
1506 {
1507 *old = new_block;
1508 }
1509
1510 let mut block_y = 0.0f32;
1512 for block in &mut cell.blocks {
1513 block.y = block_y;
1514 block_y += block.height;
1515 }
1516 let cell_height = block_y;
1517
1518 if row < table.row_heights.len() {
1520 let mut max_h = 0.0f32;
1521 for c in &table.cell_layouts {
1522 if c.row == row {
1523 let h: f32 = c.blocks.iter().map(|b| b.height).sum();
1524 max_h = max_h.max(h);
1525 }
1526 }
1527 max_h = max_h.max(cell_height);
1529 table.row_heights[row] = max_h;
1530 }
1531
1532 let border = table.border_width;
1534 let padding = table.cell_padding;
1535 let spacing = if table.row_ys.len() > 1 {
1536 if table.row_ys.len() >= 2 && !table.row_heights.is_empty() {
1538 let expected = table.row_ys[0] + padding + table.row_heights[0] + padding;
1539 (table.row_ys.get(1).copied().unwrap_or(expected) - expected).max(0.0)
1540 } else {
1541 0.0
1542 }
1543 } else {
1544 0.0
1545 };
1546 let mut y = border;
1547 for (r, &row_h) in table.row_heights.iter().enumerate() {
1548 if r < table.row_ys.len() {
1549 table.row_ys[r] = y + padding;
1550 }
1551 y += padding * 2.0 + row_h;
1552 if r < table.row_heights.len() - 1 {
1553 y += spacing;
1554 }
1555 }
1556 table.total_height = y + border;
1557}
1558
1559pub(crate) fn reposition_frame_children(frame: &mut FrameLayout) {
1569 let old_content_height = frame.content_height;
1570 let order = frame.flow_order.clone();
1571 let mut content_y = 0.0f32;
1572
1573 for child in &order {
1574 match child {
1575 crate::layout::frame::FrameChildRef::Block(bid) => {
1576 if let Some(block) = frame.blocks.iter_mut().find(|b| b.block_id == *bid) {
1577 block.y = content_y + block.top_margin;
1578 let block_content = block.height - block.top_margin - block.bottom_margin;
1579 content_y = block.y + block_content + block.bottom_margin;
1580 }
1581 }
1582 crate::layout::frame::FrameChildRef::Table(tid) => {
1583 if let Some(table) = frame.tables.iter_mut().find(|t| t.table_id == *tid) {
1584 table.y = content_y;
1585 content_y += table.total_height;
1586 }
1587 }
1588 crate::layout::frame::FrameChildRef::Frame(fid) => {
1589 if let Some(nested) = frame.frames.iter_mut().find(|f| f.frame_id == *fid) {
1590 nested.y = content_y;
1591 content_y += nested.total_height;
1592 }
1593 }
1594 }
1595 }
1596
1597 frame.content_height = content_y;
1598 frame.total_height += content_y - old_content_height;
1599}