From: Ben Pfaff Date: Fri, 11 Apr 2025 22:27:05 +0000 (-0700) Subject: footnotes work X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=7ce540007eb3f35dc4e4d05f4c9cd9332337e40c;p=pspp footnotes work --- diff --git a/rust/pspp/src/output/pivot/mod.rs b/rust/pspp/src/output/pivot/mod.rs index e53858346f..04a634704b 100644 --- a/rust/pspp/src/output/pivot/mod.rs +++ b/rust/pspp/src/output/pivot/mod.rs @@ -517,7 +517,7 @@ impl From<&str> for CategoryBuilder { } } -#[derive(Default)] +#[derive(Clone, Debug, Default)] pub struct Footnotes(Vec>); impl Footnotes { @@ -605,6 +605,7 @@ impl PivotTableBuilder { table.axes = self.axes; table.cells = self.cells; table.current_layer = repeat_n(0, table.axes[Axis3::Z].dimensions.len()).collect(); + table.footnotes = self.footnotes; table } } @@ -1420,7 +1421,7 @@ pub struct PivotTable { pub dataset: Option, pub datafile: Option, pub date: Option, - pub footnotes: Vec>, + pub footnotes: Footnotes, pub title: Option>, pub subtype: Option>, pub corner_text: Option>, @@ -1505,7 +1506,7 @@ impl Default for PivotTable { dataset: None, datafile: None, date: None, - footnotes: Vec::new(), + footnotes: Footnotes::new(), subtype: None, title: None, corner_text: None, diff --git a/rust/pspp/src/output/pivot/output.rs b/rust/pspp/src/output/pivot/output.rs index a7c13c98cc..84b9d8115e 100644 --- a/rust/pspp/src/output/pivot/output.rs +++ b/rust/pspp/src/output/pivot/output.rs @@ -298,10 +298,11 @@ impl PivotTable { fn collect_footnotes<'a>(&self, tables: impl Iterator) -> Vec> { if self.footnotes.is_empty() { + dbg!(); return Vec::new(); } - let mut refs = Vec::with_capacity(self.footnotes.len()); + let mut refs = Vec::with_capacity(self.footnotes.0.len()); for table in tables { for cell in table.cells() { if let Some(styling) = &cell.inner().value.styling { @@ -309,8 +310,8 @@ impl PivotTable { } } } - refs.sort_by(|a, b| a.index.cmp(&b.index)); - refs.dedup_by(|a, b| a.index == b.index); + refs.sort_by_key(|f| f.index); + refs.dedup_by_key(|f| f.index); refs } } diff --git a/rust/pspp/src/output/pivot/test.rs b/rust/pspp/src/output/pivot/test.rs index 8c44dc8024..acc497ddfc 100644 --- a/rust/pspp/src/output/pivot/test.rs +++ b/rust/pspp/src/output/pivot/test.rs @@ -577,6 +577,7 @@ fn footnotes() { ); let pt = pt .with_look(Arc::new(look)) + .with_footnotes(footnotes) .build() .with_caption(Value::new_text("Caption").with_footnote(&f0)) .with_corner_text( diff --git a/rust/pspp/src/output/render.rs b/rust/pspp/src/output/render.rs index d5caf49965..18cdff5e88 100644 --- a/rust/pspp/src/output/render.rs +++ b/rust/pspp/src/output/render.rs @@ -4,7 +4,7 @@ use std::iter::once; use std::ops::Range; use std::sync::Arc; -use enum_map::EnumMap; +use enum_map::{enum_map, Enum, EnumMap}; use itertools::interleave; use num::Integer; use smallvec::SmallVec; @@ -79,15 +79,24 @@ impl Params { } } +#[derive(Copy, Clone, Debug, PartialEq, Eq, Enum)] +pub enum Extreme { + Min, + Max, +} + pub trait Device { fn params(&self) -> &Params; - /// Measures `cell`'s width. Returns an arary `[min_width, max_width]`, - /// where `min_width` is the minimum width required to avoid splitting a - /// single word across multiple lines (normally, this is the width of the - /// longest word in the cell) and `max_width` is the minimum width required - /// to avoid line breaks other than at new-lines. - fn measure_cell_width(&self, cell: &DrawCell) -> [usize; 2]; + /// Measures `cell`'s width. Returns `map`, where: + /// + /// - `map[Extreme::Min]` is the minimum width required to avoid splitting a + /// single word across multiple lines. This is usually the width of the + /// longest word in the cell. + /// + /// - `map[Extreme::Max]` is the minimum width required to avoid line breaks + /// other than at new-lines. + fn measure_cell_width(&self, cell: &DrawCell) -> EnumMap; /// Returns the height required to render `cell` given a width of `width`. fn measure_cell_height(&self, cell: &DrawCell, width: usize) -> usize; @@ -373,6 +382,7 @@ impl Page { /// breaking it up to fit on such a device, using the [Break] abstraction. fn new(table: Arc, device: &dyn Device, min_width: usize, look: &Look) -> Self { use Axis2::*; + use Extreme::*; let n = table.n; @@ -387,37 +397,39 @@ impl Page { }); let px_size = device.params().px_size.unwrap_or_default(); - let heading_widths = look - .heading_widths - .clone() - .map(|_region, range| [*range.start() * px_size, *range.end() * px_size]); + let heading_widths = look.heading_widths.clone().map(|_region, range| { + enum_map![ + Min => *range.start() * px_size, + Max => *range.end() * px_size + ] + }); // Calculate minimum and maximum widths of cells that do not span // multiple columns. - let mut unspanned_columns = [vec![0; n.x()], vec![0; n.x()]]; + let mut unspanned_columns = EnumMap::from_fn(|_| vec![0; n.x()]); for cell in table.cells().filter(|cell| cell.col_span() == 1) { let mut w = device.measure_cell_width(&DrawCell::new(cell.inner(), &table)); 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]; + if w[Min] < wr[Min] { + w[Min] = wr[Min]; + if w[Min] > w[Max] { + w[Max] = w[Min]; } - } else if w[1] > wr[1] { - w[1] = wr[1]; - if w[1] < w[0] { - w[0] = w[1]; + } else if w[Max] > wr[Max] { + w[Max] = wr[Max]; + if w[Max] < w[Min] { + w[Min] = w[Max]; } } } } let x = cell.coord[X]; - for i in 0..2 { - if unspanned_columns[i][x] < w[i] { - unspanned_columns[i][x] = w[i]; + for ext in [Min, Max] { + if unspanned_columns[ext][x] < w[ext] { + unspanned_columns[ext][x] = w[ext]; } } } @@ -429,21 +441,24 @@ impl Page { let rect = cell.rect(); let w = device.measure_cell_width(&DrawCell::new(cell.inner(), &table)); - for i in 0..2 { + for ext in [Min, Max] { + dbg!(&cell, ext, w[ext]); distribute_spanned_width( - w[i], - &unspanned_columns[i][rect[X].clone()], - &mut columns[i][rect[X].clone()], - &rules[X][rect[X].start..rect[X].end + 1], + w[ext], + dbg!(&unspanned_columns[ext][rect[X].clone()]), + dbg!(&mut columns[ext][rect[X].clone()]), + dbg!(&rules[X][rect[X].start..rect[X].end + 1]), ); + dbg!(&mut columns[ext][rect[X].clone()]); + eprintln!(); } } if min_width > 0 { - for i in 0..2 { + for ext in [Min, Max] { distribute_spanned_width( min_width, - &unspanned_columns[i], - &mut columns[i], + &unspanned_columns[ext], + &mut columns[ext], &rules[X], ); } @@ -454,36 +469,33 @@ impl Page { // to exceed the maximum width. This bollixes our interpolation // algorithm later, so fix it up. for i in 0..n.x() { - if columns[0][i] > columns[1][i] { - columns[1][i] = columns[0][i]; + if columns[Min][i] > columns[Max][i] { + columns[Max][i] = columns[Min][i]; } } // Decide final column widths. let rule_widths = rules[X].iter().copied().sum::(); - let table_widths = columns - .iter() - .map(|row| row.iter().sum::() + rule_widths) - .collect::>(); + let table_widths = EnumMap::from_fn(|ext| columns[ext].iter().sum::() + rule_widths); - let cp_x = if table_widths[1] <= device.params().size[X] { + let cp_x = if table_widths[Max] <= device.params().size[X] { // Fits even with maximum widths. Use them. - Self::use_row_widths(&columns[1], &rules[X]) - } else if table_widths[0] <= device.params().size[X] { + Self::use_row_widths(&columns[Max], &rules[X]) + } else if table_widths[Min] <= device.params().size[X] { // Fits with minimum widths, so distribute the leftover space. //Self::new_with_interpolated_widths() Self::interpolate_row_widths( device.params(), - &columns[0], - &columns[1], - table_widths[0], - table_widths[1], + &columns[Min], + &columns[Max], + table_widths[Min], + table_widths[Max], &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::use_row_widths(&columns[0], &rules[X]) + Self::use_row_widths(&columns[Min], &rules[X]) }; // Calculate heights of cells that do not span multiple rows. @@ -1070,14 +1082,18 @@ fn distribute_spanned_width( rules: &[usize], ) { let n = unspanned.len(); - debug_assert!(n >= 1); + if n == 0 { + return; + } + debug_assert_eq!(spanned.len(), n); debug_assert_eq!(rules.len(), n + 1); let total_unspanned = unspanned.iter().sum::() + rules - .get(1..n - 1) + .get(1..n) .map_or(0, |rules| rules.iter().copied().sum::()); + dbg!(total_unspanned, width); if total_unspanned >= width { return; } diff --git a/rust/pspp/src/output/table.rs b/rust/pspp/src/output/table.rs index 501a5e3905..797e72fad3 100644 --- a/rust/pspp/src/output/table.rs +++ b/rust/pspp/src/output/table.rs @@ -21,7 +21,7 @@ use super::pivot::{ Area, AreaStyle, Axis2, Border, BorderStyle, HeadingRegion, Rect2, Value, ValueOptions, }; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct CellRef<'a> { pub coord: Coord2, pub content: &'a Content, diff --git a/rust/pspp/src/output/text.rs b/rust/pspp/src/output/text.rs index cbecc65b2d..a47a95b288 100644 --- a/rust/pspp/src/output/text.rs +++ b/rust/pspp/src/output/text.rs @@ -7,11 +7,11 @@ use std::{ sync::{Arc, LazyLock}, }; -use enum_map::{Enum, EnumMap}; +use enum_map::{enum_map, Enum, EnumMap}; use unicode_linebreak::{linebreaks, BreakOpportunity}; use unicode_width::UnicodeWidthStr; -use crate::output::text_line::Emphasis; +use crate::output::{render::Extreme, text_line::Emphasis}; use super::{ driver::Driver, @@ -464,11 +464,12 @@ impl Device for TextRenderer { &self.params } - fn measure_cell_width(&self, cell: &DrawCell) -> [usize; 2] { + fn measure_cell_width(&self, cell: &DrawCell) -> EnumMap { let text = Self::display_cell(cell).to_string(); - let max_width = self.layout_cell(&text, Rect2::new(0..usize::MAX, 0..usize::MAX)); - let min_width = self.layout_cell(&text, Rect2::new(0..1, 0..usize::MAX)); - [min_width.x(), max_width.x()] + enum_map![ + Extreme::Min => self.layout_cell(&text, Rect2::new(0..1, 0..usize::MAX)).x(), + Extreme::Max => self.layout_cell(&text, Rect2::new(0..usize::MAX, 0..usize::MAX)).x(), + ] } fn measure_cell_height(&self, cell: &DrawCell, width: usize) -> usize {