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
279 let mut block = layout_block(registry, params, available_width, self.scale_factor);
280 block.y = old_y;
281
282 if (block.top_margin - old_top_margin).abs() > 0.001 {
283 let prev_bm = self.prev_block_bottom_margin(block_id).unwrap_or(0.0);
284 let old_collapsed = prev_bm.max(old_top_margin);
285 let new_collapsed = prev_bm.max(block.top_margin);
286 block.y = old_y + (new_collapsed - old_collapsed);
287 }
288
289 let new_content = block.height - block.top_margin - block.bottom_margin;
290 let new_end = block.y + new_content + block.bottom_margin;
291 let delta = new_end - old_end;
292
293 let new_y = block.y;
294 let new_height = block.height;
295 self.update_max_width_for_block(&block);
296 self.blocks.insert(block_id, block);
297
298 for item in &mut self.flow_order {
300 if let FlowItem::Block {
301 block_id: id,
302 y,
303 height,
304 } = item
305 && *id == block_id
306 {
307 *y = new_y;
308 *height = new_height;
309 break;
310 }
311 }
312
313 self.shift_items_after_block(block_id, delta);
314 }
315
316 fn relayout_table_block(
319 &mut self,
320 registry: &FontRegistry,
321 params: &BlockLayoutParams,
322 table_id: usize,
323 row: usize,
324 col: usize,
325 ) {
326 let table = match self.tables.get_mut(&table_id) {
327 Some(t) => t,
328 None => return,
329 };
330
331 let cell_width = table
332 .column_content_widths
333 .get(col)
334 .copied()
335 .unwrap_or(200.0);
336 let old_table_height = table.total_height;
337
338 let cell = match table
340 .cell_layouts
341 .iter_mut()
342 .find(|c| c.row == row && c.column == col)
343 {
344 Some(c) => c,
345 None => return,
346 };
347
348 let new_block = layout_block(registry, params, cell_width, self.scale_factor);
349 if let Some(old) = cell
350 .blocks
351 .iter_mut()
352 .find(|b| b.block_id == params.block_id)
353 {
354 *old = new_block;
355 }
356
357 let mut block_y = 0.0f32;
359 for block in &mut cell.blocks {
360 block.y = block_y;
361 block_y += block.height;
362 }
363 let cell_height = block_y;
364
365 if row < table.row_heights.len() {
367 let mut max_h = 0.0f32;
368 for c in &table.cell_layouts {
369 if c.row == row {
370 let h: f32 = c.blocks.iter().map(|b| b.height).sum();
371 max_h = max_h.max(h);
372 }
373 }
374 max_h = max_h.max(cell_height);
376 table.row_heights[row] = max_h;
377 }
378
379 let border = table.border_width;
381 let padding = table.cell_padding;
382 let spacing = if table.row_ys.len() > 1 {
383 if table.row_ys.len() >= 2 && !table.row_heights.is_empty() {
385 let expected = table.row_ys[0] + padding + table.row_heights[0] + padding;
386 (table.row_ys.get(1).copied().unwrap_or(expected) - expected).max(0.0)
387 } else {
388 0.0
389 }
390 } else {
391 0.0
392 };
393 let mut y = border;
394 for (r, &row_h) in table.row_heights.iter().enumerate() {
395 if r < table.row_ys.len() {
396 table.row_ys[r] = y + padding;
397 }
398 y += padding * 2.0 + row_h;
399 if r < table.row_heights.len() - 1 {
400 y += spacing;
401 }
402 }
403 table.total_height = y + border;
404
405 let delta = table.total_height - old_table_height;
406
407 for item in &mut self.flow_order {
409 if let FlowItem::Table {
410 table_id: id,
411 height,
412 ..
413 } = item
414 && *id == table_id
415 {
416 *height = table.total_height;
417 break;
418 }
419 }
420
421 self.shift_items_after_table(table_id, delta);
422 }
423
424 fn relayout_frame_block(
427 &mut self,
428 registry: &FontRegistry,
429 params: &BlockLayoutParams,
430 frame_id: usize,
431 ) {
432 let frame = match self.frames.get_mut(&frame_id) {
433 Some(f) => f,
434 None => return,
435 };
436
437 let old_total_height = frame.total_height;
438 let new_block = layout_block(registry, params, frame.content_width, self.scale_factor);
439
440 relayout_block_in_frame(frame, params.block_id, new_block);
441
442 let delta = frame.total_height - old_total_height;
443
444 for item in &mut self.flow_order {
445 if let FlowItem::Frame {
446 frame_id: id,
447 height,
448 ..
449 } = item
450 && *id == frame_id
451 {
452 *height = frame.total_height;
453 break;
454 }
455 }
456
457 self.shift_items_after_frame(frame_id, delta);
458 }
459
460 fn shift_items_after_block(&mut self, block_id: usize, delta: f32) {
462 if delta.abs() <= 0.001 {
463 return;
464 }
465 let mut found = false;
466 for item in &mut self.flow_order {
467 match item {
468 FlowItem::Block {
469 block_id: id, y, ..
470 } => {
471 if found {
472 *y += delta;
473 if let Some(b) = self.blocks.get_mut(id) {
474 b.y += delta;
475 }
476 }
477 if *id == block_id {
478 found = true;
479 }
480 }
481 FlowItem::Table {
482 table_id: id, y, ..
483 } => {
484 if found {
485 *y += delta;
486 if let Some(t) = self.tables.get_mut(id) {
487 t.y += delta;
488 }
489 }
490 }
491 FlowItem::Frame {
492 frame_id: id, y, ..
493 } => {
494 if found {
495 *y += delta;
496 if let Some(f) = self.frames.get_mut(id) {
497 f.y += delta;
498 }
499 }
500 }
501 }
502 }
503 self.content_height += delta;
504 }
505
506 fn shift_items_after_table(&mut self, table_id: usize, delta: f32) {
508 if delta.abs() <= 0.001 {
509 return;
510 }
511 let mut found = false;
512 for item in &mut self.flow_order {
513 match item {
514 FlowItem::Table {
515 table_id: id, y, ..
516 } => {
517 if *id == table_id {
518 found = true;
519 continue;
520 }
521 if found {
522 *y += delta;
523 if let Some(t) = self.tables.get_mut(id) {
524 t.y += delta;
525 }
526 }
527 }
528 FlowItem::Block {
529 block_id: id, y, ..
530 } => {
531 if found {
532 *y += delta;
533 if let Some(b) = self.blocks.get_mut(id) {
534 b.y += delta;
535 }
536 }
537 }
538 FlowItem::Frame {
539 frame_id: id, y, ..
540 } => {
541 if found {
542 *y += delta;
543 if let Some(f) = self.frames.get_mut(id) {
544 f.y += delta;
545 }
546 }
547 }
548 }
549 }
550 self.content_height += delta;
551 }
552
553 fn shift_items_after_frame(&mut self, frame_id: usize, delta: f32) {
555 if delta.abs() <= 0.001 {
556 return;
557 }
558 let mut found = false;
559 for item in &mut self.flow_order {
560 match item {
561 FlowItem::Frame {
562 frame_id: id, y, ..
563 } => {
564 if *id == frame_id {
565 found = true;
566 continue;
567 }
568 if found {
569 *y += delta;
570 if let Some(f) = self.frames.get_mut(id) {
571 f.y += delta;
572 }
573 }
574 }
575 FlowItem::Block {
576 block_id: id, y, ..
577 } => {
578 if found {
579 *y += delta;
580 if let Some(b) = self.blocks.get_mut(id) {
581 b.y += delta;
582 }
583 }
584 }
585 FlowItem::Table {
586 table_id: id, y, ..
587 } => {
588 if found {
589 *y += delta;
590 if let Some(t) = self.tables.get_mut(id) {
591 t.y += delta;
592 }
593 }
594 }
595 }
596 }
597 self.content_height += delta;
598 }
599
600 fn update_max_width_for_block(&mut self, block: &BlockLayout) {
602 for line in &block.lines {
603 let w = line.width + block.left_margin + block.right_margin;
604 if w > self.cached_max_content_width {
605 self.cached_max_content_width = w;
606 }
607 }
608 }
609
610 fn prev_block_bottom_margin(&self, block_id: usize) -> Option<f32> {
612 let mut prev_bm = None;
613 for item in &self.flow_order {
614 match item {
615 FlowItem::Block { block_id: id, .. } => {
616 if *id == block_id {
617 return prev_bm;
618 }
619 if let Some(b) = self.blocks.get(id) {
620 prev_bm = Some(b.bottom_margin);
621 }
622 }
623 _ => {
624 prev_bm = None;
626 }
627 }
628 }
629 None
630 }
631}
632
633pub(crate) fn frame_contains_block(frame: &FrameLayout, block_id: usize) -> bool {
635 if frame.blocks.iter().any(|b| b.block_id == block_id) {
636 return true;
637 }
638 frame
639 .frames
640 .iter()
641 .any(|nested| frame_contains_block(nested, block_id))
642}
643
644fn relayout_block_in_frame(frame: &mut FrameLayout, block_id: usize, new_block: BlockLayout) {
647 let old_content_height = frame.content_height;
648
649 if let Some(old) = frame.blocks.iter_mut().find(|b| b.block_id == block_id) {
651 *old = new_block;
652 } else {
653 for nested in &mut frame.frames {
655 if frame_contains_block(nested, block_id) {
656 relayout_block_in_frame(nested, block_id, new_block);
657 break;
658 }
659 }
660 }
661
662 let mut content_y = 0.0f32;
664 for block in &mut frame.blocks {
665 block.y = content_y + block.top_margin;
666 let block_content = block.height - block.top_margin - block.bottom_margin;
667 content_y = block.y + block_content + block.bottom_margin;
668 }
669 for table in &mut frame.tables {
670 table.y = content_y;
671 content_y += table.total_height;
672 }
673 for nested in &mut frame.frames {
674 nested.y = content_y;
675 content_y += nested.total_height;
676 }
677
678 frame.content_height = content_y;
679 frame.total_height += content_y - old_content_height;
680}