work
authorBen Pfaff <blp@cs.stanford.edu>
Tue, 14 Jan 2025 17:44:36 +0000 (09:44 -0800)
committerBen Pfaff <blp@cs.stanford.edu>
Tue, 14 Jan 2025 17:44:36 +0000 (09:44 -0800)
rust/pspp/src/output/pivot/mod.rs
rust/pspp/src/output/render.rs

index cc8a3b093bb10e94759070142e4eeca73949a92e..28533606805aba9a05b42eb019acf4ed8ce1fea0 100644 (file)
@@ -714,6 +714,12 @@ pub enum Axis2 {
     Y,
 }
 
+impl Axis2 {
+    pub fn new_enum<T>(x: T, y: T) -> EnumMap<Axis2, T> {
+        EnumMap::from_array([x, y])
+    }
+}
+
 impl Not for Axis2 {
     type Output = Self;
 
index dcc1da6acc73040be6fade1635b01b5c167901f0..6a8752cde33f0644e660766552bf105f245d97d9 100644 (file)
@@ -3,7 +3,7 @@ use std::collections::HashMap;
 use std::ops::Range;
 use std::sync::Arc;
 
-use enum_map::{enum_map, EnumMap};
+use enum_map::EnumMap;
 use smallvec::SmallVec;
 
 use super::pivot::{Axis2, BorderStyle, Coord2, Look, PivotTable, Rect2, Stroke};
@@ -158,6 +158,9 @@ struct Page {
     /// Main region of cells to render.
     r: Rect2,
 
+    /// Mappings from [Page] positions to those in the underlying [Table].
+    maps: EnumMap<Axis2, [Map; 2]>,
+
     /// "Cell positions".
     ///
     /// cp[X] represents x positions within the table.
@@ -273,6 +276,37 @@ struct Page {
     is_edge_cutoff: EnumMap<Axis2, [bool; 2]>,
 }
 
+/// Returns the width of `extent` along `axis`.
+fn axis_width(cp: &[usize], extent: Range<usize>) -> usize {
+    cp[extent.end] - cp[extent.start]
+}
+
+/// Returns the width of cells within `extent` along `axis`.
+fn joined_width(cp: &[usize], extent: Range<usize>) -> usize {
+    axis_width(cp, cell_ofs(extent.start)..cell_ofs(extent.end) - 1)
+}
+/// Returns the offset in [Self::cp] of the cell with index `cell_index`.
+/// That is, if `cell_index` is 0, then the offset is 1, that of the leftmost
+/// or topmost cell; if `cell_index` is 1, then the offset is 3, that of the
+/// next cell to the right (or below); and so on. */
+fn cell_ofs(cell_index: usize) -> usize {
+    cell_index * 2 + 1
+}
+
+/// Returns the offset in [Self::cp] of the rule with index `rule_index`.
+/// That is, if `rule_index` is 0, then the offset is that of the leftmost
+/// or topmost rule; if `rule_index` is 1, then the offset is that of the
+/// next rule to the right (or below); and so on.
+fn rule_ofs(rule_index: usize) -> usize {
+    rule_index * 2
+}
+
+/// Returns the width of cell `z` along `axis`.
+fn cell_width(cp: &[usize], z: usize) -> usize {
+    let ofs = cell_ofs(z);
+    axis_width(cp, ofs..ofs + 1)
+}
+
 impl Page {
     /// Creates and returns a new [Page] for rendering `table` with the given
     /// `look` on `device`.
@@ -283,10 +317,11 @@ impl Page {
     fn new(table: &Arc<Table>, device: &Arc<dyn Device>, min_width: usize, look: &Look) -> Self {
         use Axis2::*;
 
+        let n = table.n.clone();
+
         // Figure out rule widths.
         let rules = EnumMap::from_fn(|axis| {
-            let n = table.n[axis];
-            (0..n)
+            (0..n[axis])
                 .map(|z| measure_rule(table, &**device, axis, z))
                 .collect::<Vec<_>>()
         });
@@ -299,7 +334,7 @@ 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()]];
+        let mut columns = [vec![0; n.x()], vec![0; n.x()]];
         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() {
@@ -358,7 +393,7 @@ impl Page {
         // In pathological cases, spans can cause the minimum width of a column
         // to exceed the maximum width.  This bollixes our interpolation
         // algorithm later, so fix it up.
-        for i in 0..table.n.x() {
+        for i in 0..n.x() {
             if columns[0][i].width > columns[1][i].width {
                 columns[1][i].width = columns[0][i].width;
             }
@@ -371,25 +406,33 @@ impl Page {
             .map(|row| row.iter().map(|row| row.width).sum::<usize>() + rule_widths)
             .collect::<SmallVec<[usize; 2]>>();
 
-        let mut page = if table_widths[1] <= device.params().size[X] {
+        let cp_x = if table_widths[1] <= device.params().size[X] {
             // Fits even with maximum widths.  Use them.
-            Self::new_with_exact_widths(device, table, &columns[1], &rules[X])
+            Self::use_row_widths(&columns[1], &rules[X])
         } else if table_widths[0] <= device.params().size[X] {
             // Fits with minimum widths, so distribute the leftover space.
             //Self::new_with_interpolated_widths()
-            todo!()
+            Self::interpolate_row_widths(
+                &table,
+                device.params(),
+                &columns[0],
+                &columns[1],
+                table_widths[0],
+                table_widths[1],
+                &rules[X],
+            )
         } else {
             // Doesn't fit even with minimum widths.  Assign minimums for now, and
             // later we can break it horizontally into multiple pages.
-            Self::new_with_exact_widths(device, table, &columns[0], &rules[X])
+            Self::use_row_widths(&columns[0], &rules[X])
         };
 
         // Calculate heights of cells that do not span multiple rows.
-        let mut rows = vec![Row::default(); table.n[Y]];
+        let mut rows = vec![Row::default(); n[Y]];
         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 w = joined_width(&cp_x, rect[X].clone());
             let h = device.measure_cell_height(cell.inner(), w);
 
             let row = &mut rows[cell.coord.y()];
@@ -402,7 +445,7 @@ impl Page {
         // Distribute heights of spanned rows.
         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 w = joined_width(&cp_x, rect[X].clone());
             let h = device.measure_cell_height(cell.inner(), w);
             distribute_spanned_width(
                 h,
@@ -412,81 +455,106 @@ impl Page {
         }
 
         // Decide final row heights.
-        page.cp[Y] = Self::accumulate_row_widths(&rows, &rules[Y]);
+        let cp_y = Self::use_row_widths(&rows, &rules[Y]);
 
         // Measure headers.  If they are "too big", get rid of them.
-        for axis in [X, Y] {
-            let hw = page.headers_width(axis);
+        let mut h = table.h.clone();
+        for (axis, cp) in [(X, cp_x.as_slice()), (Y, cp_y.as_slice())] {
+            let header_width = axis_width(cp, 0..table.h[axis]);
+            let max_cell_width = (table.h[axis]..n[axis])
+                .map(|z| cell_width(cp, z))
+                .max()
+                .unwrap_or(0);
             let threshold = device.params().size[axis];
-            if hw * 2 >= threshold || hw + page.max_cell_width(axis) > threshold {
-                page.h[axis] = 0;
-                page.r[axis] = 0..page.n[axis];
+            if header_width * 2 >= threshold || header_width + max_cell_width > threshold {
+                h[axis] = 0;
             }
         }
-        page
+        let r = Rect2::new(h[X]..n[X], h[Y]..n[Y]);
+        let maps = Self::new_mappings(h, &r);
+        Self {
+            device: device.clone(),
+            table: table.clone(),
+            n,
+            h,
+            r,
+            cp: Axis2::new_enum(cp_x, cp_y),
+            overflows: HashMap::new(),
+            is_edge_cutoff: EnumMap::default(),
+            maps,
+        }
     }
 
-    fn accumulate_row_widths(rows: &[Row], rules: &[usize]) -> Vec<usize> {
+    fn accumulate_vec(mut vec: Vec<usize>) -> Vec<usize> {
+        for i in 1..vec.len() {
+            vec[i] += vec[i - 1]
+        }
+        vec
+    }
+
+    fn use_row_widths(rows: &[Row], rules: &[usize]) -> Vec<usize> {
         debug_assert_eq!(rows.len() + 1, rules.len());
-        let n = 2 * (rows.len()) + 1;
-        let mut cp = Vec::with_capacity(n);
-        let mut total = 0;
-        cp.push(total);
+        let mut cp = Vec::with_capacity(2 * (rows.len()) + 1);
+
+        cp.push(0);
         for (rule, row) in rules.iter().zip(rows.iter()) {
-            total += *rule;
-            cp.push(total);
-            total += row.width;
-            cp.push(total);
+            cp.push(*rule);
+            cp.push(row.width);
         }
-        total += rules.last().unwrap();
-        cp.push(total);
-        debug_assert_eq!(cp.len(), n);
-        cp
+        cp.push(*rules.last().unwrap());
+
+        Self::accumulate_vec(cp)
     }
 
-    fn new_with_exact_widths(
-        device: &Arc<dyn Device>,
-        table: &Arc<Table>,
-        rows: &[Row],
+    fn interpolate_row_widths(
+        table: &Table,
+        params: &Params,
+        rows_min: &[Row],
+        rows_max: &[Row],
+        w_min: usize,
+        w_max: usize,
         rules: &[usize],
-    ) -> Self {
-        use Axis2::*;
-        Self {
-            device: device.clone(),
-            table: table.clone(),
-            n: table.n,
-            h: table.h,
-            r: Rect2::new(table.h[X]..table.n[X], table.h[Y]..table.n[Y]),
-            cp: enum_map! {
-                X => Self::accumulate_row_widths(rows, rules),
-                Y => Vec::new(),
-            },
-            overflows: HashMap::new(),
-            is_edge_cutoff: EnumMap::default(),
-        }
+    ) -> Vec<usize> {
+        let avail = params.size[Axis2::X] - w_min;
+        let wanted = w_max - w_min;
+        let mut w = wanted / 2;
+        let rows_mid = rows_min
+            .iter()
+            .zip(rows_max.iter())
+            .map(|(min, max)| {
+                w += avail * (max.width - min.width);
+                let extra = w / wanted;
+                w -= extra * wanted;
+                Row {
+                    width: min.width + extra,
+                    unspanned: 0,
+                }
+            })
+            .collect::<Vec<_>>();
+        return Self::use_row_widths(&rows_mid, rules);
     }
 
     /// Returns the width of `extent` along `axis`.
     fn axis_width(&self, axis: Axis2, extent: Range<usize>) -> usize {
-        self.cp[axis][extent.end] - self.cp[axis][extent.start]
+        axis_width(&self.cp[axis], extent)
     }
 
     /// Returns the width of cells within `extent` along `axis`.
     fn joined_width(&self, axis: Axis2, extent: Range<usize>) -> usize {
-        self.axis_width(
-            axis,
-            Self::cell_ofs(extent.start)..Self::cell_ofs(extent.end) - 1,
+        joined_width(
+            &self.cp[axis],
+            cell_ofs(extent.start)..cell_ofs(extent.end) - 1,
         )
     }
 
     /// Returns the width of the headers along `axis`.
     fn headers_width(&self, axis: Axis2) -> usize {
-        self.axis_width(axis, Self::rule_ofs(0)..Self::cell_ofs(self.h[axis]))
+        self.axis_width(axis, rule_ofs(0)..cell_ofs(self.h[axis]))
     }
 
     /// Returns the width of rule `z` along `axis`.
     fn rule_width(&self, axis: Axis2, z: usize) -> usize {
-        self.axis_width(axis, Self::rule_ofs(z + 1)..Self::rule_ofs(z + 1))
+        self.axis_width(axis, rule_ofs(z + 1)..rule_ofs(z + 1))
     }
 
     /// Returns the width of rule `z` along `axis`, counting in reverse order.
@@ -495,22 +563,6 @@ impl Page {
         self.axis_width(axis, ofs..ofs + 1)
     }
 
-    /// Returns the offset in [Self::cp] of the cell with index `cell_index`.
-    /// That is, if `cell_index` is 0, then the offset is 1, that of the leftmost
-    /// or topmost cell; if `cell_index` is 1, then the offset is 3, that of the
-    /// next cell to the right (or below); and so on. */
-    fn cell_ofs(cell_index: usize) -> usize {
-        cell_index * 2 + 1
-    }
-
-    /// Returns the offset in [Self::cp] of the rule with index `rule_index`.
-    /// That is, if `rule_index` is 0, then the offset is that of the leftmost
-    /// or topmost rule; if `rule_index` is 1, then the offset is that of the
-    /// next rule to the right (or below); and so on.
-    fn rule_ofs(rule_index: usize) -> usize {
-        rule_index * 2
-    }
-
     /// Returns the offset in [Self::cp] of the rule with
     /// index `rule_index_r`, which counts from the right side (or bottom) of the page
     /// left (or up), according to `axis`, respectively.  That is,
@@ -523,7 +575,7 @@ impl Page {
 
     /// Returns the width of cell `z` along `axis`.
     fn cell_width(&self, axis: Axis2, z: usize) -> usize {
-        let ofs = Self::cell_ofs(z);
+        let ofs = cell_ofs(z);
         self.axis_width(axis, ofs..ofs + 1)
     }
 
@@ -539,7 +591,7 @@ impl Page {
         *self.cp[axis].last().unwrap()
     }
 
-    /// XXX This could return a 
+    /// XXX This could return a
     fn get_map(&self, a: Axis2, z: usize) -> Map {
         if z < self.h[a] {
             Map {
@@ -556,9 +608,26 @@ impl Page {
         }
     }
 
-/*
+    fn new_mappings(h: Coord2, r: &Rect2) -> EnumMap<Axis2, [Map; 2]> {
+        EnumMap::from_fn(|axis| {
+            [
+                Map {
+                    p0: 0,
+                    t0: 0,
+                    n: h[axis],
+                },
+                Map {
+                    p0: h[axis],
+                    t0: r[axis].start,
+                    n: r[axis].len(),
+                },
+            ]
+        })
+    }
+
+    /*
     fn get_cell(&self, coord: Coord2) -> RenderCell<'_> {
-        
+
     }*/
 
     /// Creates and returns a new [Page] whose contents are a subregion of
@@ -610,7 +679,7 @@ impl Page {
         let mut dcp = Vec::with_capacity(2 * n[axis] + 1);
         dcp.push(0);
         let mut total = 0;
-        for z in 0..=Self::rule_ofs(h[axis]) {
+        for z in 0..=rule_ofs(h[axis]) {
             total += if z == 0 && is_edge_cutoff[axis][0] {
                 0
             } else {
@@ -618,12 +687,12 @@ impl Page {
             };
             dcp.push(total);
         }
-        for z in Self::cell_ofs(z0)..=Self::cell_ofs(z1 - 1) {
+        for z in cell_ofs(z0)..=cell_ofs(z1 - 1) {
             total += scp[z + 1] - scp[z];
-            if z== Self::cell_ofs(z0) {
+            if z == cell_ofs(z0) {
                 total -= pixel0;
             }
-            if z == Self::cell_ofs(z1 - 1) {
+            if z == cell_ofs(z1 - 1) {
                 total -= pixel1;
             }
             dcp.push(total);
@@ -646,7 +715,6 @@ impl Page {
             let mut z = 0;
             while z < self.n[b] {
                 let d = Coord2::for_axis((axis, z0), z);
-
             }
         }
 
@@ -658,6 +726,7 @@ struct Selection;
 
 /// Maps a contiguous range of cells from a page to the underlying table along
 /// the horizontal or vertical dimension.
+#[derive(Copy, Clone)]
 struct Map {
     /// First ordinate in the page.
     p0: usize,
@@ -828,7 +897,7 @@ impl Break {
         // Width of header not including its rightmost rule.
         let mut size = self
             .page
-            .axis_width(self.axis, 0..Page::rule_ofs(self.page.h[self.axis]));
+            .axis_width(self.axis, 0..rule_ofs(self.page.h[self.axis]));
 
         // If we have a pixel offset and there is no header, then we omit
         // the leftmost rule of the body.  Otherwise the rendering is deceptive