From e58e443c21fa0641cb53c60b0737b25d60fe7f61 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Tue, 14 Jan 2025 09:44:36 -0800 Subject: [PATCH] work --- rust/pspp/src/output/pivot/mod.rs | 6 + rust/pspp/src/output/render.rs | 235 +++++++++++++++++++----------- 2 files changed, 158 insertions(+), 83 deletions(-) diff --git a/rust/pspp/src/output/pivot/mod.rs b/rust/pspp/src/output/pivot/mod.rs index cc8a3b093b..2853360680 100644 --- a/rust/pspp/src/output/pivot/mod.rs +++ b/rust/pspp/src/output/pivot/mod.rs @@ -714,6 +714,12 @@ pub enum Axis2 { Y, } +impl Axis2 { + pub fn new_enum(x: T, y: T) -> EnumMap { + EnumMap::from_array([x, y]) + } +} + impl Not for Axis2 { type Output = Self; diff --git a/rust/pspp/src/output/render.rs b/rust/pspp/src/output/render.rs index dcc1da6acc..6a8752cde3 100644 --- a/rust/pspp/src/output/render.rs +++ b/rust/pspp/src/output/render.rs @@ -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, + /// "Cell positions". /// /// cp[X] represents x positions within the table. @@ -273,6 +276,37 @@ struct Page { is_edge_cutoff: EnumMap, } +/// Returns the width of `extent` along `axis`. +fn axis_width(cp: &[usize], extent: Range) -> usize { + cp[extent.end] - cp[extent.start] +} + +/// Returns the width of cells within `extent` along `axis`. +fn joined_width(cp: &[usize], extent: Range) -> 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, device: &Arc, 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::>() }); @@ -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::() + rule_widths) .collect::>(); - 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 { + fn accumulate_vec(mut vec: Vec) -> Vec { + for i in 1..vec.len() { + vec[i] += vec[i - 1] + } + vec + } + + fn use_row_widths(rows: &[Row], rules: &[usize]) -> Vec { 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, - table: &Arc
, - 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 { + 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::>(); + return Self::use_row_widths(&rows_mid, rules); } /// Returns the width of `extent` along `axis`. fn axis_width(&self, axis: Axis2, extent: Range) -> 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 { - 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 { + 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 -- 2.30.2