use super::story::{Piece, Seg, plain_seg};
use super::styles::Align;
pub(super) const MAX_SPAN: usize = 64;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub(super) enum Merge {
#[default]
None,
Restart,
Continue,
}
#[derive(Debug, Clone, Default)]
pub(super) struct RawCell {
pub segs: Vec<Seg>,
pub align: Align,
pub span: usize,
pub v_merge: Merge,
pub h_merge: Merge,
}
#[derive(Debug, Clone, Default)]
pub(super) struct RawRow {
pub before: usize,
pub after: usize,
pub cells: Vec<RawCell>,
pub header: bool,
}
#[derive(Debug, Clone, Default)]
pub(super) struct GridCell {
pub segs: Vec<Seg>,
pub align: Align,
pub col: usize,
pub colspan: usize,
pub rowspan: usize,
}
#[derive(Debug, Default)]
pub(super) struct Grid {
pub rows: Vec<Vec<GridCell>>,
pub columns: usize,
pub cells_wide: usize,
pub header_rows: usize,
pub orphan: bool,
pub covered_text: bool,
}
fn has_content(segs: &[Seg]) -> bool {
segs.iter().any(|s| match &s.piece {
Piece::Text(t) => !t.trim().is_empty(),
Piece::LineBreak => false,
Piece::Note(_) | Piece::Image { .. } => true,
})
}
fn append(into: &mut Vec<Seg>, from: Vec<Seg>) {
if has_content(into) {
into.push(plain_seg(Piece::LineBreak));
}
into.extend(from);
}
pub(super) fn resolve(raw: Vec<RawRow>) -> Grid {
let mut grid = Grid::default();
struct Folded {
cell: RawCell,
col: usize,
h_chain: bool,
}
let mut folded: Vec<(Vec<Folded>, usize)> = Vec::new();
let mut extent = 0usize;
let mut cells_in_a_row = 0usize;
for row in &raw {
let mut col = row.before.min(MAX_SPAN);
let mut cells: Vec<Folded> = Vec::new();
for cell in &row.cells {
let mut cell = cell.clone();
cell.span = cell.span.clamp(1, MAX_SPAN);
let span = cell.span;
let joins =
cell.h_merge == Merge::Continue && cells.last().is_some_and(|last| last.h_chain);
if joins && let Some(last) = cells.last_mut() {
last.cell.span = (last.cell.span + span).min(MAX_SPAN);
if has_content(&cell.segs) {
append(&mut last.cell.segs, cell.segs);
grid.covered_text = true;
}
} else {
grid.orphan |= cell.h_merge == Merge::Continue;
cells.push(Folded {
h_chain: cell.h_merge != Merge::None,
cell,
col,
});
}
col += span;
}
extent = extent.max(col + row.after.min(MAX_SPAN));
cells_in_a_row = cells_in_a_row.max(cells.len());
folded.push((cells, col));
}
grid.columns = extent.min(MAX_SPAN.max(cells_in_a_row)).max(1);
grid.cells_wide = cells_in_a_row;
let mut rows: Vec<Vec<GridCell>> = Vec::new();
let mut open: Vec<((usize, usize), (usize, usize))> = Vec::new();
for (r, (cells, end)) in folded.into_iter().enumerate() {
let before = raw[r].before.min(MAX_SPAN);
let mut out: Vec<GridCell> = Vec::new();
let mut now: Vec<((usize, usize), (usize, usize))> = Vec::new();
if before > 0 {
out.push(GridCell {
col: 0,
colspan: before,
rowspan: 1,
..GridCell::default()
});
}
for f in cells {
let key = (f.col, f.cell.span);
let above = open.iter().find(|(k, _)| *k == key).map(|(_, at)| *at);
if f.cell.v_merge == Merge::Continue {
if let Some((ar, ai)) = above {
let anchor = &mut rows[ar][ai];
anchor.rowspan += 1;
if has_content(&f.cell.segs) {
append(&mut anchor.segs, f.cell.segs);
grid.covered_text = true;
}
now.push((key, (ar, ai)));
continue;
}
grid.orphan = true;
}
if f.cell.v_merge == Merge::Restart {
now.push((key, (r, out.len())));
}
out.push(GridCell {
segs: f.cell.segs,
align: f.cell.align,
col: f.col,
colspan: f.cell.span,
rowspan: 1,
});
}
let after = raw[r].after.min(MAX_SPAN);
if after > 0 {
out.push(GridCell {
col: end,
colspan: after,
rowspan: 1,
..GridCell::default()
});
}
open = now;
rows.push(out);
}
let marked = raw.iter().take_while(|r| r.header).count();
let mut header = marked;
loop {
let grown = rows
.iter()
.take(header)
.enumerate()
.flat_map(|(r, cells)| cells.iter().map(move |c| r + c.rowspan))
.max()
.unwrap_or(0)
.min(rows.len());
if grown <= header {
break;
}
header = grown;
}
grid.header_rows = header;
grid.rows = rows;
grid
}