1use super::Table;
26
27pub const MAX_GRID_POSITIONS: u64 = 1_048_576;
33
34#[derive(
36 Debug,
37 Clone,
38 Copy,
39 PartialEq,
40 Eq,
41 PartialOrd,
42 Ord,
43 Hash,
44 serde::Serialize,
45 serde::Deserialize,
46 schemars::JsonSchema,
47)]
48pub struct GridCoord {
49 pub row: u32,
51 pub col: u32,
53}
54
55impl GridCoord {
56 #[must_use]
58 pub const fn new(row: u32, col: u32) -> Self {
59 Self { row, col }
60 }
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub struct GridCell {
69 pub anchor: GridCoord,
71 pub row_idx: usize,
73 pub cell_idx: usize,
75 pub row_span: u16,
77 pub col_span: u16,
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
83#[non_exhaustive]
84pub enum GridError {
85 TooLarge {
87 rows: u64,
89 cols: u64,
91 },
92 Overlap {
94 at: GridCoord,
96 },
97 OverhangsBottom {
99 at: GridCoord,
101 },
102 NotTiled {
104 at: GridCoord,
106 },
107}
108
109impl core::fmt::Display for GridError {
110 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
111 match self {
112 Self::TooLarge { rows, cols } => write!(
113 f,
114 "table grid {rows}x{cols} exceeds the {MAX_GRID_POSITIONS}-position limit"
115 ),
116 Self::Overlap { at } => {
117 write!(f, "cell spans overlap at logical position ({}, {})", at.row, at.col)
118 }
119 Self::OverhangsBottom { at } => write!(
120 f,
121 "cell row span extends past the last table row at ({}, {})",
122 at.row, at.col
123 ),
124 Self::NotTiled { at } => {
125 write!(f, "no cell covers logical position ({}, {})", at.row, at.col)
126 }
127 }
128 }
129}
130
131impl std::error::Error for GridError {}
132
133#[derive(Debug, Clone, Copy, PartialEq, Eq)]
135pub struct PlacedCell {
136 pub at: GridCoord,
138 pub row_idx: usize,
140 pub cell_idx: usize,
142}
143
144#[derive(Debug, Clone, PartialEq, Eq)]
146pub struct GridPlacements {
147 pub cells: Vec<PlacedCell>,
149 pub cols: u32,
153}
154
155#[must_use]
163pub fn grid_placements(table: &Table) -> GridPlacements {
164 let mut occupied = std::collections::HashSet::<(u32, u32)>::new();
165 let mut cells = Vec::new();
166 let mut cols: u32 = 0;
167
168 for (row_idx, row) in table.rows.iter().enumerate() {
169 let mut cursor: u32 = 0;
170 for (cell_idx, cell) in row.cells.iter().enumerate() {
171 while occupied.contains(&(row_idx as u32, cursor)) {
172 cursor += 1;
173 }
174 cells.push(PlacedCell {
175 at: GridCoord::new(row_idx as u32, cursor),
176 row_idx,
177 cell_idx,
178 });
179 let col_span = u32::from(cell.col_span).max(1);
180 let row_span = u32::from(cell.row_span).max(1);
181 for dr in 0..row_span {
182 for dc in 0..col_span {
183 occupied.insert((row_idx as u32 + dr, cursor + dc));
184 }
185 }
186 cursor += col_span;
187 }
188 cols = cols.max(cursor);
189 }
190
191 GridPlacements { cells, cols }
192}
193
194#[derive(Debug, Clone, Copy, PartialEq, Eq)]
197struct RowInterval {
198 start: u32,
199 end: u32,
200 idx: usize,
201}
202
203#[derive(Debug, Clone, PartialEq, Eq)]
205pub struct TableGrid {
206 rows: u32,
207 cols: u32,
208 anchors: Vec<GridCell>,
209 coverage: Vec<Vec<RowInterval>>,
211}
212
213impl TableGrid {
214 pub fn from_table(table: &Table) -> Result<Self, GridError> {
223 let rows = table.rows.len() as u32;
224
225 let mut covered_area: u64 = 0;
228 for row in &table.rows {
229 for cell in &row.cells {
230 let area = u64::from(cell.col_span.max(1)) * u64::from(cell.row_span.max(1));
231 covered_area = covered_area.saturating_add(area);
232 }
233 }
234 if covered_area > MAX_GRID_POSITIONS {
235 return Err(GridError::TooLarge { rows: u64::from(rows), cols: covered_area });
236 }
237
238 let mut anchors: Vec<GridCell> = Vec::new();
239 let mut coverage: Vec<Vec<RowInterval>> = vec![Vec::new(); table.rows.len()];
240 let mut cols: u32 = 0;
241
242 for (row_idx, row) in table.rows.iter().enumerate() {
243 let mut cursor: u32 = 0;
244 for (cell_idx, cell) in row.cells.iter().enumerate() {
245 while covered(&coverage[row_idx], cursor) {
246 cursor += 1;
247 }
248 let col_span = cell.col_span.max(1);
249 let row_span = cell.row_span.max(1);
250 let idx = anchors.len();
251 let anchor = GridCoord::new(row_idx as u32, cursor);
252
253 let end_row = row_idx as u64 + u64::from(row_span);
254 if end_row > u64::from(rows) {
255 return Err(GridError::OverhangsBottom {
256 at: GridCoord::new(rows, anchor.col),
257 });
258 }
259 let end_col = u64::from(cursor) + u64::from(col_span);
260 if u64::from(rows) * end_col > MAX_GRID_POSITIONS {
261 return Err(GridError::TooLarge { rows: u64::from(rows), cols: end_col });
262 }
263
264 for dr in 0..u32::from(row_span) {
265 let target = &mut coverage[row_idx + dr as usize];
266 let start = cursor;
267 let end = cursor + u32::from(col_span);
268 if let Some(col) = first_covered_in(target, start, end) {
269 return Err(GridError::Overlap {
270 at: GridCoord::new(row_idx as u32 + dr, col),
271 });
272 }
273 let pos = target.partition_point(|iv| iv.start < start);
274 target.insert(pos, RowInterval { start, end, idx });
275 }
276
277 anchors.push(GridCell { anchor, row_idx, cell_idx, row_span, col_span });
278 cols = cols.max(cursor + u32::from(col_span));
279 cursor += u32::from(col_span);
280 }
281 }
282
283 for (row_idx, intervals) in coverage.iter().enumerate() {
286 let mut expected: u32 = 0;
287 for iv in intervals {
288 if iv.start != expected {
289 return Err(GridError::NotTiled {
290 at: GridCoord::new(row_idx as u32, expected),
291 });
292 }
293 expected = iv.end;
294 }
295 if expected != cols {
296 return Err(GridError::NotTiled { at: GridCoord::new(row_idx as u32, expected) });
297 }
298 }
299
300 Ok(Self { rows, cols, anchors, coverage })
301 }
302
303 #[must_use]
305 pub fn dimensions(&self) -> (u32, u32) {
306 (self.rows, self.cols)
307 }
308
309 #[must_use]
314 pub fn resolve(&self, at: GridCoord) -> Option<&GridCell> {
315 let intervals = self.coverage.get(at.row as usize)?;
316 let idx = interval_at(intervals, at.col)?;
317 Some(&self.anchors[idx])
318 }
319
320 pub fn iter_anchors(&self) -> impl Iterator<Item = &GridCell> {
322 self.anchors.iter()
323 }
324}
325
326fn covered(intervals: &[RowInterval], col: u32) -> bool {
328 interval_at(intervals, col).is_some()
329}
330
331fn interval_at(intervals: &[RowInterval], col: u32) -> Option<usize> {
333 let pos = intervals.partition_point(|iv| iv.end <= col);
334 let iv = intervals.get(pos)?;
335 (iv.start <= col).then_some(iv.idx)
336}
337
338fn first_covered_in(intervals: &[RowInterval], start: u32, end: u32) -> Option<u32> {
340 let pos = intervals.partition_point(|iv| iv.end <= start);
341 let iv = intervals.get(pos)?;
342 (iv.start < end).then_some(iv.start.max(start))
343}
344
345#[cfg(test)]
346mod tests {
347 use super::*;
348 use crate::paragraph::Paragraph;
349 use crate::table::{TableCell, TableRow};
350 use hwpforge_foundation::{HwpUnit, ParaShapeIndex};
351
352 fn cell(row_span: u16, col_span: u16) -> TableCell {
353 TableCell::with_span(
354 vec![Paragraph::new(ParaShapeIndex::new(0))],
355 HwpUnit::from_mm(10.0).unwrap(),
356 col_span,
357 row_span,
358 )
359 }
360
361 fn table(rows: Vec<Vec<TableCell>>) -> Table {
362 Table::new(rows.into_iter().map(TableRow::new).collect())
363 }
364
365 #[test]
368 fn empty_table_yields_zero_dimensions() {
369 let grid = TableGrid::from_table(&table(vec![])).unwrap();
370 assert_eq!(grid.dimensions(), (0, 0));
371 assert_eq!(grid.iter_anchors().count(), 0);
372 assert_eq!(grid.resolve(GridCoord::new(0, 0)), None);
373 }
374
375 #[test]
376 fn all_empty_rows_yield_degenerate_zero_width_grid() {
377 let grid = TableGrid::from_table(&table(vec![vec![], vec![]])).unwrap();
381 assert_eq!(grid.dimensions(), (2, 0));
382 assert_eq!(grid.iter_anchors().count(), 0);
383 }
384
385 #[test]
386 fn pathological_span_rejected_as_too_large() {
387 let t = table(vec![vec![cell(u16::MAX, u16::MAX)]]);
388 assert!(matches!(TableGrid::from_table(&t), Err(GridError::TooLarge { .. })));
389 }
390
391 #[test]
392 fn zero_span_normalized_to_one() {
393 let t = table(vec![vec![cell(0, 0)]]);
394 let grid = TableGrid::from_table(&t).unwrap();
395 assert_eq!(grid.dimensions(), (1, 1));
396 }
397
398 #[test]
399 fn row_span_overhang_rejected() {
400 let t = table(vec![vec![cell(2, 1)]]);
401 assert_eq!(
402 TableGrid::from_table(&t),
403 Err(GridError::OverhangsBottom { at: GridCoord::new(1, 0) })
404 );
405 }
406
407 #[test]
408 fn ragged_rows_rejected_as_not_tiled() {
409 let t = table(vec![vec![cell(1, 1), cell(1, 1)], vec![cell(1, 1)]]);
410 assert_eq!(
411 TableGrid::from_table(&t),
412 Err(GridError::NotTiled { at: GridCoord::new(1, 1) })
413 );
414 }
415
416 #[test]
417 fn uncovered_empty_row_rejected_as_not_tiled() {
418 let t = table(vec![vec![cell(1, 1)], vec![]]);
419 assert_eq!(
420 TableGrid::from_table(&t),
421 Err(GridError::NotTiled { at: GridCoord::new(1, 0) })
422 );
423 }
424
425 #[test]
426 fn overlapping_spans_rejected() {
427 let t = table(vec![vec![cell(2, 1), cell(1, 1), cell(2, 1)], vec![cell(1, 2)]]);
430 assert_eq!(TableGrid::from_table(&t), Err(GridError::Overlap { at: GridCoord::new(1, 2) }));
431 }
432
433 #[test]
436 fn fully_covered_empty_row_accepted() {
437 let t = table(vec![vec![cell(2, 1)], vec![]]);
438 let grid = TableGrid::from_table(&t).unwrap();
439 assert_eq!(grid.dimensions(), (2, 1));
440 let anchor = grid.resolve(GridCoord::new(1, 0)).unwrap();
441 assert_eq!(anchor.anchor, GridCoord::new(0, 0));
442 assert_eq!((anchor.row_idx, anchor.cell_idx), (0, 0));
443 }
444
445 #[test]
446 fn hpc_form_layout_resolves_covered_positions_to_anchors() {
447 let t = table(vec![
454 vec![cell(2, 1), cell(1, 2)],
455 vec![cell(1, 1), cell(1, 1)],
456 vec![cell(1, 1), cell(1, 1), cell(1, 1)],
457 vec![cell(1, 3)],
458 ]);
459 let grid = TableGrid::from_table(&t).unwrap();
460 assert_eq!(grid.dimensions(), (4, 3));
461 assert_eq!(grid.iter_anchors().count(), 8);
462
463 let a = grid.resolve(GridCoord::new(1, 0)).unwrap();
465 assert_eq!(a.anchor, GridCoord::new(0, 0));
466 let b = grid.resolve(GridCoord::new(0, 2)).unwrap();
467 assert_eq!(b.anchor, GridCoord::new(0, 1));
468 let c = grid.resolve(GridCoord::new(3, 2)).unwrap();
469 assert_eq!(c.anchor, GridCoord::new(3, 0));
470 assert_eq!((c.row_idx, c.cell_idx), (3, 0));
471
472 let x = grid.resolve(GridCoord::new(1, 1)).unwrap();
474 assert_eq!((x.row_idx, x.cell_idx), (1, 0));
475 assert_eq!(x.anchor, GridCoord::new(1, 1));
476
477 assert_eq!(grid.resolve(GridCoord::new(4, 0)), None);
479 assert_eq!(grid.resolve(GridCoord::new(0, 3)), None);
480 }
481
482 #[test]
485 fn lenient_placement_matches_strict_for_well_formed_tables() {
486 let t = table(vec![
487 vec![cell(2, 1), cell(1, 2)],
488 vec![cell(1, 1), cell(1, 1)],
489 vec![cell(1, 1), cell(1, 1), cell(1, 1)],
490 vec![cell(1, 3)],
491 ]);
492 let placements = grid_placements(&t);
493 let grid = TableGrid::from_table(&t).unwrap();
494 assert_eq!(placements.cols, grid.dimensions().1);
495 let strict: Vec<_> =
496 grid.iter_anchors().map(|a| (a.anchor, a.row_idx, a.cell_idx)).collect();
497 let lenient: Vec<_> =
498 placements.cells.iter().map(|p| (p.at, p.row_idx, p.cell_idx)).collect();
499 assert_eq!(strict, lenient);
500 }
501
502 #[test]
503 fn lenient_placement_tolerates_malformed_tables() {
504 let t = table(vec![vec![cell(2, 1), cell(1, 1), cell(2, 1)], vec![cell(1, 2)]]);
507 let placements = grid_placements(&t);
508 assert_eq!(placements.cells.len(), 4);
509 assert_eq!(placements.cells[3].at, GridCoord::new(1, 1));
510 assert_eq!(placements.cols, 3);
511 }
512}