From: Ben Pfaff Date: Fri, 11 Apr 2025 20:53:25 +0000 (-0700) Subject: work on footnotes X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e07b08e8d34b4481abc7164a67a57979a8f6f658;p=pspp work on footnotes --- diff --git a/rust/pspp/src/output/pivot/mod.rs b/rust/pspp/src/output/pivot/mod.rs index ecf71afeab..e53858346f 100644 --- a/rust/pspp/src/output/pivot/mod.rs +++ b/rust/pspp/src/output/pivot/mod.rs @@ -516,12 +516,33 @@ impl From<&str> for CategoryBuilder { } } } + +#[derive(Default)] +pub struct Footnotes(Vec>); + +impl Footnotes { + pub fn new() -> Self { + Self::default() + } + + pub fn push(&mut self, footnote: Footnote) -> Arc { + let footnote = Arc::new(footnote.with_index(self.0.len())); + self.0.push(footnote.clone()); + footnote + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + pub struct PivotTableBuilder { look: Arc, title: Box, axes: EnumMap, dimensions: Vec, cells: HashMap, + footnotes: Footnotes, } impl PivotTableBuilder { @@ -540,6 +561,7 @@ impl PivotTableBuilder { axes, dimensions, cells: HashMap::new(), + footnotes: Footnotes::new(), } } pub fn with_look(mut self, look: Arc) -> Self { @@ -572,6 +594,11 @@ impl PivotTableBuilder { value, ); } + pub fn with_footnotes(mut self, footnotes: Footnotes) -> Self { + debug_assert!(self.footnotes.is_empty()); + self.footnotes = footnotes; + self + } pub fn build(self) -> PivotTable { let mut table = PivotTable::new(self.title, self.look.clone()); table.dimensions = self.dimensions; @@ -1417,6 +1444,11 @@ impl PivotTable { self } + fn with_corner_text(mut self, corner_text: Value) -> Self { + self.corner_text = Some(Box::new(corner_text)); + self + } + fn with_show_title(mut self, show_title: bool) -> Self { self.show_title = show_title; self @@ -1624,17 +1656,35 @@ impl PivotTable { } } -pub struct Layers {} - #[derive(Clone, Debug)] pub struct Footnote { index: usize, - content: Value, - marker: Option, + content: Box, + marker: Option>, show: bool, } impl Footnote { + pub fn new(content: impl Into) -> Self { + Self { + index: 0, + content: Box::new(content.into()), + marker: None, + show: true, + } + } + pub fn with_marker(mut self, marker: impl Into) -> Self { + self.marker = Some(Box::new(marker.into())); + self + } + pub fn with_show(mut self, show: bool) -> Self { + self.show = show; + self + } + pub fn with_index(mut self, index: usize) -> Self { + self.index = index; + self + } pub fn display_marker(&self, options: impl IntoValueOptions) -> DisplayMarker<'_> { DisplayMarker { footnote: self, @@ -1763,6 +1813,12 @@ impl Value { id: s.clone(), }) } + pub fn with_footnote(mut self, footnote: &Arc) -> Self { + let footnotes = &mut self.styling.get_or_insert_default().footnotes; + footnotes.push(footnote.clone()); + footnotes.sort_by_key(|f| f.index); + self + } } impl From<&str> for Value { @@ -1790,13 +1846,13 @@ impl<'a> DisplayValue<'a> { } } - pub fn with_styling(self, styling: &'a ValueStyle) -> Self { - Self { - markup: styling.style.font_style.markup, - subscripts: styling.subscripts.as_slice(), - footnotes: styling.footnotes.as_slice(), - ..self + pub fn with_styling(mut self, styling: &'a ValueStyle) -> Self { + if let Some(area_style) = &styling.style { + self.markup = area_style.font_style.markup; } + self.subscripts = styling.subscripts.as_slice(); + self.footnotes = styling.footnotes.as_slice(); + self } pub fn with_font_style(self, font_style: &FontStyle) -> Self { @@ -2118,9 +2174,9 @@ impl ValueInner { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub struct ValueStyle { - pub style: AreaStyle, + pub style: Option, pub subscripts: Vec, pub footnotes: Vec>, } diff --git a/rust/pspp/src/output/pivot/test.rs b/rust/pspp/src/output/pivot/test.rs index b4748c007c..8c44dc8024 100644 --- a/rust/pspp/src/output/pivot/test.rs +++ b/rust/pspp/src/output/pivot/test.rs @@ -3,8 +3,8 @@ use std::sync::Arc; use enum_map::EnumMap; use crate::output::pivot::{ - Area, Axis2, Border, BorderStyle, Class, Color, Dimension, Group, HeadingRegion, LabelPosition, - Look, PivotTable, RowColBorder, Stroke, + Area, Axis2, Border, BorderStyle, Class, Color, Dimension, Footnote, Footnotes, Group, + HeadingRegion, LabelPosition, Look, PivotTable, RowColBorder, Stroke, }; use super::{Axis3, DimensionBuilder, GroupBuilder, PivotTableBuilder, Value}; @@ -542,6 +542,67 @@ Caption ); } +#[test] +fn footnotes() { + let mut footnotes = Footnotes::new(); + let f0 = footnotes.push(Footnote::new("First footnote").with_marker("*")); + let f1 = footnotes.push(Footnote::new("Second footnote")); + let a = Dimension::builder( + Axis3::X, + Group::builder(Value::new_text("A").with_footnote(&f0)) + .with_label_shown() + .with(Value::new_text("B").with_footnote(&f1)) + .with(Value::new_text("C").with_footnote(&f0).with_footnote(&f1)), + ); + let d = Dimension::builder( + Axis3::Y, + Group::builder(Value::new_text("D").with_footnote(&f1)) + .with_label_shown() + .with(Value::new_text("E").with_footnote(&f0)) + .with(Value::new_text("F").with_footnote(&f1).with_footnote(&f0)), + ); + let look = test_look().with_row_label_position(LabelPosition::Nested); + let mut pt = PivotTable::builder( + Value::new_text("Pivot Table with Alphabetic Subscript Footnotes").with_footnote(&f0), + vec![a, d], + ); + pt.insert(&[0, 0], Value::new_number(Some(0.0))); + pt.insert(&[1, 0], Value::new_number(Some(1.0)).with_footnote(&f0)); + pt.insert(&[0, 1], Value::new_number(Some(2.0)).with_footnote(&f1)); + pt.insert( + &[1, 1], + Value::new_number(Some(3.0)) + .with_footnote(&f0) + .with_footnote(&f1), + ); + let pt = pt + .with_look(Arc::new(look)) + .build() + .with_caption(Value::new_text("Caption").with_footnote(&f0)) + .with_corner_text( + Value::new_text("Corner") + .with_footnote(&f0) + .with_footnote(&f1), + ); + assert_rendering( + &pt, + "\ +Pivot Table with Alphabetic Subscript Footnotes[*] +╭────────────┬──────────────────╮ +│ │ A[*] │ +│ ├───────┬──────────┤ +│Corner[*][b]│ B[b] │ C[*][b] │ +├────────────┼───────┼──────────┤ +│D[b] E[*] │ .00│ 1.00[*]│ +│ F[*][b]│2.00[b]│3.00[*][b]│ +╰────────────┴───────┴──────────╯ +Caption[*] +*. First footnote +b. Second footnote +", + ); +} + #[test] fn no_dimension() { let pivot_table = PivotTableBuilder::new(Value::new_text("No Dimensions"), vec![]) diff --git a/rust/pspp/src/output/render.rs b/rust/pspp/src/output/render.rs index 8d2e518c7e..d5caf49965 100644 --- a/rust/pspp/src/output/render.rs +++ b/rust/pspp/src/output/render.rs @@ -155,14 +155,15 @@ pub struct DrawCell<'a> { impl<'a> DrawCell<'a> { fn new(inner: &'a CellInner, table: &'a Table) -> Self { - let (style, subscripts, footnotes) = if let Some(styling) = inner.value.styling.as_ref() { + let default_area_style = &table.areas[inner.area]; + let (style, subscripts, footnotes) = if let Some(styling) = &inner.value.styling { ( - &styling.style, + styling.style.as_ref().unwrap_or(default_area_style), styling.subscripts.as_slice(), styling.footnotes.as_slice(), ) } else { - (&table.areas[inner.area], [].as_slice(), [].as_slice()) + (default_area_style, [].as_slice(), [].as_slice()) }; Self { rotate: inner.rotate, @@ -422,6 +423,7 @@ impl Page { } // Distribute widths of spanned columns. + dbg!(&unspanned_columns); let mut columns = unspanned_columns.clone(); for cell in table.cells().filter(|cell| cell.col_span() > 1) { let rect = cell.rect(); @@ -446,6 +448,7 @@ impl Page { ); } } + dbg!(&columns); // In pathological cases, spans can cause the minimum width of a column // to exceed the maximum width. This bollixes our interpolation