Add cells iterator
authorBen Pfaff <blp@cs.stanford.edu>
Sun, 12 Jan 2025 18:33:33 +0000 (10:33 -0800)
committerBen Pfaff <blp@cs.stanford.edu>
Sun, 12 Jan 2025 18:33:33 +0000 (10:33 -0800)
rust/pspp/src/output/pivot/output.rs
rust/pspp/src/output/render.rs
rust/pspp/src/output/table.rs

index 59bc362074b9ff45cedd46c95fe30782f3b418f5..678350ec06d919d7019df5b69791a5dd08529ade 100644 (file)
@@ -310,15 +310,15 @@ impl PivotTable {
 
         let mut refs = vec![false; self.footnotes.len()];
         for table in tables.into_iter().flatten() {
-            table.visit_cells(|inner| {
-                if let Some(value) = &inner.value {
+            for cell in table.cells() {
+                if let Some(value) = &cell.inner().value {
                     if let Some(styling) = &value.styling {
                         for index in &styling.footnote_indexes {
                             refs[*index] = true;
                         }
                     }
                 }
-            });
+            }
         }
         refs.iter()
             .enumerate()
index 3a2e84e1acf1694cfc00433686a117ec76bd29a0..f417dc17c1da0aaf9ec8d19766a2056364ba4508 100644 (file)
@@ -300,36 +300,29 @@ impl Page {
         // Calculate minimum and maximum widths of cells that do not span
         // multiple columns.
         let mut columns = [vec![0; table.n.x()], vec![0; table.n.x()]];
-        for y in 0..table.n[Y] {
-            for x in table.iter_x(y) {
-                let coord = Coord2::new(x, y);
-                let contents = table.get(coord);
-                if !contents.is_top_left() || contents.col_span() != 1 {
-                    continue;
-                }
-
-                let mut w = device.measure_cell_width(contents.inner());
-                if device.params().px_size.is_some() {
-                    if let Some(region) = table.heading_region(coord) {
-                        let wr = &heading_widths[region];
-                        if w[0] < wr[0] {
-                            w[0] = wr[0];
-                            if w[0] > w[1] {
-                                w[1] = w[0];
-                            }
-                        } else if w[1] > wr[1] {
-                            w[1] = wr[1];
-                            if w[1] < w[0] {
-                                w[0] = w[1];
-                            }
+        for cell in table.cells().filter(|cell| cell.col_span() == 1) {
+            let mut w = device.measure_cell_width(cell.inner());
+            if device.params().px_size.is_some() {
+                if let Some(region) = table.heading_region(cell.coord) {
+                    let wr = &heading_widths[region];
+                    if w[0] < wr[0] {
+                        w[0] = wr[0];
+                        if w[0] > w[1] {
+                            w[1] = w[0];
+                        }
+                    } else if w[1] > wr[1] {
+                        w[1] = wr[1];
+                        if w[1] < w[0] {
+                            w[0] = w[1];
                         }
                     }
                 }
+            }
 
-                for i in 0..2 {
-                    if columns[i][x] < w[i] {
-                        columns[i][x] = w[i];
-                    }
+            let x = cell.coord[X];
+            for i in 0..2 {
+                if columns[i][x] < w[i] {
+                    columns[i][x] = w[i];
                 }
             }
         }
@@ -344,23 +337,16 @@ impl Page {
                 })
                 .collect::<Vec<_>>()
         });
-        for y in 0..table.n[Y] {
-            for x in table.iter_x(y) {
-                let coord = Coord2::new(x, y);
-                let contents = table.get(coord);
-                if !contents.is_top_left() || contents.col_span() == 1 {
-                    continue;
-                }
-                let rect = contents.rect();
-
-                let w = device.measure_cell_width(contents.inner());
-                for i in 0..2 {
-                    distribute_spanned_width(
-                        w[i],
-                        &mut columns[i][rect[X].clone()],
-                        &rules[X][rect[X].start..rect[X].end + 1],
-                    );
-                }
+        for cell in table.cells().filter(|cell| cell.col_span() > 1) {
+            let rect = cell.rect();
+
+            let w = device.measure_cell_width(cell.inner());
+            for i in 0..2 {
+                distribute_spanned_width(
+                    w[i],
+                    &mut columns[i][rect[X].clone()],
+                    &rules[X][rect[X].start..rect[X].end + 1],
+                );
             }
         }
         if min_width > 0 {
@@ -400,43 +386,29 @@ impl Page {
 
         // Calculate heights of cells that do not span multiple rows.
         let mut rows = vec![Row::default(); table.n[Y]];
-        for y in 0..table.n[Y] {
-            let row = &mut rows[y];
-            for x in table.iter_x(y) {
-                let coord = Coord2::new(x, y);
-                let contents = table.get(coord);
-                if !contents.is_top_left() {
-                    continue;
-                }
-                let rect = contents.rect();
-
-                if contents.row_span() == 1 {
-                    let w = page.joined_width(X, rect[X].clone());
-                    let h = device.measure_cell_height(contents.inner(), w);
-                    if h > row.unspanned {
-                        row.unspanned = h;
-                        row.width = h;
-                    }
-                }
+        for cell in table.cells().filter(|cell| cell.row_span() == 1) {
+            let rect = cell.rect();
+
+            let w = page.joined_width(X, rect[X].clone());
+            let h = device.measure_cell_height(cell.inner(), w);
+
+            let row = &mut rows[cell.coord.y()];
+            if h > row.unspanned {
+                row.unspanned = h;
+                row.width = h;
             }
         }
 
         // Distribute heights of spanned rows.
-        for y in 0..table.n[Y] {
-            for x in table.iter_x(y) {
-                let coord = Coord2::new(x, y);
-                let contents = table.get(coord);
-                if contents.is_top_left() && contents.row_span() > 1 {
-                    let rect = contents.rect();
-                    let w = page.joined_width(X, rect[X].clone());
-                    let h = device.measure_cell_height(contents.inner(), w);
-                    distribute_spanned_width(
-                        h,
-                        &mut rows[rect[Y].clone()],
-                        &rules[Y][rect[Y].start..rect[Y].end + 1],
-                    );
-                }
-            }
+        for cell in table.cells().filter(|cell| cell.row_span() > 1) {
+            let rect = cell.rect();
+            let w = page.joined_width(X, rect[X].clone());
+            let h = device.measure_cell_height(cell.inner(), w);
+            distribute_spanned_width(
+                h,
+                &mut rows[rect[Y].clone()],
+                &rules[Y][rect[Y].start..rect[Y].end + 1],
+            );
         }
 
         // Decide final row heights.
index d7ae0505a61acd290cb55bda9be216c05e56a547..4b3ba58dfea478edfe41e3b5a1555799a177453d 100644 (file)
@@ -18,8 +18,9 @@ use crate::output::pivot::Coord2;
 
 use super::pivot::{Area, AreaStyle, Axis2, Border, BorderStyle, HeadingRegion, Rect2, Value};
 
+#[derive(Clone)]
 pub struct CellRef<'a> {
-    coord: Coord2,
+    pub coord: Coord2,
     content: &'a Content,
 }
 
@@ -281,19 +282,6 @@ impl Table {
         }
     }
 
-    /// Visits all the nonempty cells once.
-    pub fn visit_cells(&self, mut f: impl FnMut(&CellInner)) {
-        for y in 0..self.n.y() {
-            for x in self.iter_x(y) {
-                let coord = Coord2::new(x, y);
-                let content = self.get(coord);
-                if !content.is_empty() && content.is_top_left() {
-                    f(content.inner());
-                }
-            }
-        }
-    }
-
     /// The heading region that `pos` is part of, if any.
     pub fn heading_region(&self, pos: Coord2) -> Option<HeadingRegion> {
         if pos.x() < self.h.x() {
@@ -304,6 +292,18 @@ impl Table {
             None
         }
     }
+
+    /// Iterates across all of the cells in the table, visiting each of them
+    /// once in top-down, left-to-right order. Spanned cells are visited once,
+    /// at the point in the iteration where their top-left cell would appear if
+    /// they were not spanned.
+    pub fn cells(&self) -> Cells<'_> {
+        Cells::new(self)
+    }
+
+    pub fn is_empty(&self) -> bool {
+        self.n[Axis2::X] == 0 || self.n[Axis2::Y] == 0
+    }
 }
 
 pub struct XIter<'a> {
@@ -327,3 +327,48 @@ impl<'a> Iterator for XIter<'a> {
         }
     }
 }
+
+/// Iterator for all of the cells in a table (see [Table::cells]).
+pub struct Cells<'a> {
+    table: &'a Table,
+    next: Option<CellRef<'a>>,
+}
+
+impl<'a> Cells<'a> {
+    fn new(table: &'a Table) -> Self {
+        Self {
+            table,
+            next: if table.is_empty() {
+                None
+            } else {
+                Some(table.get(Coord2::new(0, 0)))
+            },
+        }
+    }
+}
+
+impl<'a> Iterator for Cells<'a> {
+    type Item = CellRef<'a>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        use Axis2::*;
+        let this = self.next.as_ref()?.clone();
+
+        let mut next = this.clone();
+        self.next = loop {
+            let next_x = next.next_x();
+            let coord = if next_x < self.table.n[X] {
+                Coord2::new(next_x, next.coord.y())
+            } else if next.coord.y() + 1 < self.table.n[Y] {
+                Coord2::new(0, next.coord.y() + 1)
+            } else {
+                break None;
+            };
+            next = self.table.get(coord);
+            if next.is_top_left() {
+                break Some(next);
+            }
+        };
+        Some(this)
+    }
+}