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#[must_use]
203pub fn covered_area(table: &Table) -> u64 {
204 let mut covered: u64 = 0;
205 for row in &table.rows {
206 for cell in &row.cells {
207 let area = u64::from(cell.col_span.max(1)) * u64::from(cell.row_span.max(1));
208 covered = covered.saturating_add(area);
209 }
210 }
211 covered
212}
213
214#[derive(Debug, Clone, Copy, PartialEq, Eq)]
217struct RowInterval {
218 start: u32,
219 end: u32,
220 idx: usize,
221}
222
223#[derive(Debug, Clone, PartialEq, Eq)]
225pub struct TableGrid {
226 rows: u32,
227 cols: u32,
228 anchors: Vec<GridCell>,
229 coverage: Vec<Vec<RowInterval>>,
231}
232
233impl TableGrid {
234 pub fn from_table(table: &Table) -> Result<Self, GridError> {
243 let rows = table.rows.len() as u32;
244
245 let area = covered_area(table);
248 if area > MAX_GRID_POSITIONS {
249 return Err(GridError::TooLarge { rows: u64::from(rows), cols: area });
250 }
251
252 let mut anchors: Vec<GridCell> = Vec::new();
253 let mut coverage: Vec<Vec<RowInterval>> = vec![Vec::new(); table.rows.len()];
254 let mut cols: u32 = 0;
255
256 for (row_idx, row) in table.rows.iter().enumerate() {
257 let mut cursor: u32 = 0;
258 for (cell_idx, cell) in row.cells.iter().enumerate() {
259 while covered(&coverage[row_idx], cursor) {
260 cursor += 1;
261 }
262 let col_span = cell.col_span.max(1);
263 let row_span = cell.row_span.max(1);
264 let idx = anchors.len();
265 let anchor = GridCoord::new(row_idx as u32, cursor);
266
267 let end_row = row_idx as u64 + u64::from(row_span);
268 if end_row > u64::from(rows) {
269 return Err(GridError::OverhangsBottom {
270 at: GridCoord::new(rows, anchor.col),
271 });
272 }
273 let end_col = u64::from(cursor) + u64::from(col_span);
274 if u64::from(rows) * end_col > MAX_GRID_POSITIONS {
275 return Err(GridError::TooLarge { rows: u64::from(rows), cols: end_col });
276 }
277
278 for dr in 0..u32::from(row_span) {
279 let target = &mut coverage[row_idx + dr as usize];
280 let start = cursor;
281 let end = cursor + u32::from(col_span);
282 if let Some(col) = first_covered_in(target, start, end) {
283 return Err(GridError::Overlap {
284 at: GridCoord::new(row_idx as u32 + dr, col),
285 });
286 }
287 let pos = target.partition_point(|iv| iv.start < start);
288 target.insert(pos, RowInterval { start, end, idx });
289 }
290
291 anchors.push(GridCell { anchor, row_idx, cell_idx, row_span, col_span });
292 cols = cols.max(cursor + u32::from(col_span));
293 cursor += u32::from(col_span);
294 }
295 }
296
297 for (row_idx, intervals) in coverage.iter().enumerate() {
300 let mut expected: u32 = 0;
301 for iv in intervals {
302 if iv.start != expected {
303 return Err(GridError::NotTiled {
304 at: GridCoord::new(row_idx as u32, expected),
305 });
306 }
307 expected = iv.end;
308 }
309 if expected != cols {
310 return Err(GridError::NotTiled { at: GridCoord::new(row_idx as u32, expected) });
311 }
312 }
313
314 Ok(Self { rows, cols, anchors, coverage })
315 }
316
317 #[must_use]
319 pub fn dimensions(&self) -> (u32, u32) {
320 (self.rows, self.cols)
321 }
322
323 #[must_use]
328 pub fn resolve(&self, at: GridCoord) -> Option<&GridCell> {
329 let intervals = self.coverage.get(at.row as usize)?;
330 let idx = interval_at(intervals, at.col)?;
331 Some(&self.anchors[idx])
332 }
333
334 pub fn iter_anchors(&self) -> impl Iterator<Item = &GridCell> {
336 self.anchors.iter()
337 }
338}
339
340fn covered(intervals: &[RowInterval], col: u32) -> bool {
342 interval_at(intervals, col).is_some()
343}
344
345fn interval_at(intervals: &[RowInterval], col: u32) -> Option<usize> {
347 let pos = intervals.partition_point(|iv| iv.end <= col);
348 let iv = intervals.get(pos)?;
349 (iv.start <= col).then_some(iv.idx)
350}
351
352fn first_covered_in(intervals: &[RowInterval], start: u32, end: u32) -> Option<u32> {
354 let pos = intervals.partition_point(|iv| iv.end <= start);
355 let iv = intervals.get(pos)?;
356 (iv.start < end).then_some(iv.start.max(start))
357}
358
359#[cfg(test)]
360mod tests {
361 use super::*;
362 use crate::paragraph::Paragraph;
363 use crate::table::{TableCell, TableRow};
364 use hwpforge_foundation::{HwpUnit, ParaShapeIndex};
365
366 fn cell(row_span: u16, col_span: u16) -> TableCell {
367 TableCell::with_span(
368 vec![Paragraph::new(ParaShapeIndex::new(0))],
369 HwpUnit::from_mm(10.0).unwrap(),
370 col_span,
371 row_span,
372 )
373 }
374
375 fn table(rows: Vec<Vec<TableCell>>) -> Table {
376 Table::new(rows.into_iter().map(TableRow::new).collect())
377 }
378
379 #[test]
382 fn empty_table_yields_zero_dimensions() {
383 let grid = TableGrid::from_table(&table(vec![])).unwrap();
384 assert_eq!(grid.dimensions(), (0, 0));
385 assert_eq!(grid.iter_anchors().count(), 0);
386 assert_eq!(grid.resolve(GridCoord::new(0, 0)), None);
387 }
388
389 #[test]
390 fn all_empty_rows_yield_degenerate_zero_width_grid() {
391 let grid = TableGrid::from_table(&table(vec![vec![], vec![]])).unwrap();
395 assert_eq!(grid.dimensions(), (2, 0));
396 assert_eq!(grid.iter_anchors().count(), 0);
397 }
398
399 #[test]
400 fn pathological_span_rejected_as_too_large() {
401 let t = table(vec![vec![cell(u16::MAX, u16::MAX)]]);
402 assert!(matches!(TableGrid::from_table(&t), Err(GridError::TooLarge { .. })));
403 }
404
405 #[test]
406 fn zero_span_normalized_to_one() {
407 let t = table(vec![vec![cell(0, 0)]]);
408 let grid = TableGrid::from_table(&t).unwrap();
409 assert_eq!(grid.dimensions(), (1, 1));
410 }
411
412 #[test]
413 fn row_span_overhang_rejected() {
414 let t = table(vec![vec![cell(2, 1)]]);
415 assert_eq!(
416 TableGrid::from_table(&t),
417 Err(GridError::OverhangsBottom { at: GridCoord::new(1, 0) })
418 );
419 }
420
421 #[test]
422 fn ragged_rows_rejected_as_not_tiled() {
423 let t = table(vec![vec![cell(1, 1), cell(1, 1)], vec![cell(1, 1)]]);
424 assert_eq!(
425 TableGrid::from_table(&t),
426 Err(GridError::NotTiled { at: GridCoord::new(1, 1) })
427 );
428 }
429
430 #[test]
431 fn uncovered_empty_row_rejected_as_not_tiled() {
432 let t = table(vec![vec![cell(1, 1)], vec![]]);
433 assert_eq!(
434 TableGrid::from_table(&t),
435 Err(GridError::NotTiled { at: GridCoord::new(1, 0) })
436 );
437 }
438
439 #[test]
440 fn overlapping_spans_rejected() {
441 let t = table(vec![vec![cell(2, 1), cell(1, 1), cell(2, 1)], vec![cell(1, 2)]]);
444 assert_eq!(TableGrid::from_table(&t), Err(GridError::Overlap { at: GridCoord::new(1, 2) }));
445 }
446
447 #[test]
450 fn fully_covered_empty_row_accepted() {
451 let t = table(vec![vec![cell(2, 1)], vec![]]);
452 let grid = TableGrid::from_table(&t).unwrap();
453 assert_eq!(grid.dimensions(), (2, 1));
454 let anchor = grid.resolve(GridCoord::new(1, 0)).unwrap();
455 assert_eq!(anchor.anchor, GridCoord::new(0, 0));
456 assert_eq!((anchor.row_idx, anchor.cell_idx), (0, 0));
457 }
458
459 #[test]
460 fn hpc_form_layout_resolves_covered_positions_to_anchors() {
461 let t = table(vec![
468 vec![cell(2, 1), cell(1, 2)],
469 vec![cell(1, 1), cell(1, 1)],
470 vec![cell(1, 1), cell(1, 1), cell(1, 1)],
471 vec![cell(1, 3)],
472 ]);
473 let grid = TableGrid::from_table(&t).unwrap();
474 assert_eq!(grid.dimensions(), (4, 3));
475 assert_eq!(grid.iter_anchors().count(), 8);
476
477 let a = grid.resolve(GridCoord::new(1, 0)).unwrap();
479 assert_eq!(a.anchor, GridCoord::new(0, 0));
480 let b = grid.resolve(GridCoord::new(0, 2)).unwrap();
481 assert_eq!(b.anchor, GridCoord::new(0, 1));
482 let c = grid.resolve(GridCoord::new(3, 2)).unwrap();
483 assert_eq!(c.anchor, GridCoord::new(3, 0));
484 assert_eq!((c.row_idx, c.cell_idx), (3, 0));
485
486 let x = grid.resolve(GridCoord::new(1, 1)).unwrap();
488 assert_eq!((x.row_idx, x.cell_idx), (1, 0));
489 assert_eq!(x.anchor, GridCoord::new(1, 1));
490
491 assert_eq!(grid.resolve(GridCoord::new(4, 0)), None);
493 assert_eq!(grid.resolve(GridCoord::new(0, 3)), None);
494 }
495
496 #[test]
499 fn lenient_placement_matches_strict_for_well_formed_tables() {
500 let t = table(vec![
501 vec![cell(2, 1), cell(1, 2)],
502 vec![cell(1, 1), cell(1, 1)],
503 vec![cell(1, 1), cell(1, 1), cell(1, 1)],
504 vec![cell(1, 3)],
505 ]);
506 let placements = grid_placements(&t);
507 let grid = TableGrid::from_table(&t).unwrap();
508 assert_eq!(placements.cols, grid.dimensions().1);
509 let strict: Vec<_> =
510 grid.iter_anchors().map(|a| (a.anchor, a.row_idx, a.cell_idx)).collect();
511 let lenient: Vec<_> =
512 placements.cells.iter().map(|p| (p.at, p.row_idx, p.cell_idx)).collect();
513 assert_eq!(strict, lenient);
514 }
515
516 #[test]
517 fn lenient_placement_tolerates_malformed_tables() {
518 let t = table(vec![vec![cell(2, 1), cell(1, 1), cell(2, 1)], vec![cell(1, 2)]]);
521 let placements = grid_placements(&t);
522 assert_eq!(placements.cells.len(), 4);
523 assert_eq!(placements.cells[3].at, GridCoord::new(1, 1));
524 assert_eq!(placements.cols, 3);
525 }
526
527 #[test]
530 fn covered_area_sums_span_areas() {
531 let t = table(vec![vec![cell(2, 1), cell(1, 2)], vec![cell(1, 1), cell(1, 1)]]);
533 assert_eq!(covered_area(&t), 6);
534 assert_eq!(covered_area(&table(vec![])), 0);
536 }
537
538 #[test]
539 fn covered_area_floors_zero_spans_at_one() {
540 let t = table(vec![vec![cell(0, 0), cell(0, 3)]]);
543 assert_eq!(covered_area(&t), 1 + 3);
544 }
545
546 #[test]
547 fn covered_area_boundary_sits_exactly_at_cap() {
548 let t = table(vec![vec![cell(1024, 1024)]]);
551 assert_eq!(covered_area(&t), MAX_GRID_POSITIONS);
552 }
553
554 #[test]
555 fn covered_area_saturates_instead_of_overflowing() {
556 let row: Vec<TableCell> = (0..8).map(|_| cell(u16::MAX, u16::MAX)).collect();
557 let t = table(vec![row]);
558 assert_eq!(covered_area(&t), 8 * 4_294_836_225u64);
559 assert!(covered_area(&t) > MAX_GRID_POSITIONS);
560 }
561}