From 42d3c16acf85a27b46c80940495213dfd15e7a47 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Sun, 2 Mar 2025 15:07:42 -0800 Subject: [PATCH] more work on text driver --- rust/pspp/src/output/pivot/mod.rs | 99 ++++++++++++++++++------------- rust/pspp/src/output/text.rs | 57 ++++++++++++++---- 2 files changed, 102 insertions(+), 54 deletions(-) diff --git a/rust/pspp/src/output/pivot/mod.rs b/rust/pspp/src/output/pivot/mod.rs index d6f8108305..971d9392d4 100644 --- a/rust/pspp/src/output/pivot/mod.rs +++ b/rust/pspp/src/output/pivot/mod.rs @@ -74,6 +74,7 @@ use smallvec::{smallvec, SmallVec}; use crate::{ dictionary::Value as DataValue, format::{Format, Settings as FormatSettings, Type, UncheckedFormat}, + raw::VarType, settings::{Settings, Show}, }; @@ -104,19 +105,19 @@ impl Area { fn default_cell_style(self) -> CellStyle { use HorzAlign::*; use VertAlign::*; - let (halign, valign, hmargins, vmargins) = match self { - Area::Title => (Center, Middle, [8, 11], [1, 8]), - Area::Caption => (Left, Top, [8, 11], [1, 1]), - Area::Footer => (Left, Top, [11, 8], [2, 3]), - Area::Corner => (Left, Bottom, [8, 11], [1, 1]), - Area::ColumnLabels => (Left, Top, [8, 11], [1, 3]), - Area::RowLabels => (Left, Top, [8, 11], [1, 3]), - Area::Data => (Mixed, Top, [8, 11], [1, 1]), - Area::Layers => (Left, Bottom, [8, 11], [1, 3]), + let (horz_align, vert_align, hmargins, vmargins) = match self { + Area::Title => (Some(Center), Middle, [8, 11], [1, 8]), + Area::Caption => (Some(Left), Top, [8, 11], [1, 1]), + Area::Footer => (Some(Left), Top, [11, 8], [2, 3]), + Area::Corner => (Some(Left), Bottom, [8, 11], [1, 1]), + Area::ColumnLabels => (Some(Left), Top, [8, 11], [1, 3]), + Area::RowLabels => (Some(Left), Top, [8, 11], [1, 3]), + Area::Data => (None, Top, [8, 11], [1, 1]), + Area::Layers => (Some(Left), Bottom, [8, 11], [1, 3]), }; CellStyle { - horz_align: halign, - vert_align: valign, + horz_align, + vert_align, margins: enum_map! { Axis2::X => hmargins, Axis2::Y => vmargins }, } } @@ -598,7 +599,9 @@ pub struct AreaStyle { #[derive(Clone, Debug)] pub struct CellStyle { - pub horz_align: HorzAlign, + /// `None` means "mixed" alignment: align strings to the left, numbers to + /// the right. + pub horz_align: Option, pub vert_align: VertAlign, /// Margins in 1/96" units. @@ -621,9 +624,6 @@ pub enum HorzAlign { /// Centered. Center, - /// Align strings to the left, other formats to the right. - Mixed, - /// Align the decimal point at the specified position. Decimal { /// Decimal offset from the right side of the cell, in 1/96" units. @@ -634,6 +634,15 @@ pub enum HorzAlign { }, } +impl HorzAlign { + fn for_mixed(var_type: VarType) -> Self { + match var_type { + VarType::Numeric => Self::Right, + VarType::String => Self::Left, + } + } +} + #[derive(Copy, Clone, Debug)] pub enum VertAlign { /// Top alignment. @@ -1215,6 +1224,8 @@ pub struct DisplayValue<'a> { subscripts: &'a [String], footnotes: &'a [Arc], options: ValueOptions, + show_value: bool, + show_label: Option<&'a str>, } impl<'a> DisplayValue<'a> { @@ -1250,30 +1261,17 @@ impl<'a> DisplayValue<'a> { Self { footnotes, ..self } } - fn show(&self) -> (bool, Option<&str>) { - if let Some(value_label) = self.inner.value_label() { - interpret_show( - || Settings::global().show_values, - self.options.show_values, - self.inner.show(), - value_label, - ) - } else if let Some(variable_label) = self.inner.variable_label() { - interpret_show( - || Settings::global().show_variables, - self.options.show_variables, - self.inner.show(), - variable_label, - ) - } else { - (true, None) - } - } - fn small(&self) -> f64 { self.options.small } + fn var_type(&self) -> VarType { + match self.inner { + ValueInner::Number { .. } if self.show_label.is_none() => VarType::Numeric, + _ => VarType::String, + } + } + fn template( &self, f: &mut std::fmt::Formatter<'_>, @@ -1395,7 +1393,6 @@ fn interpret_show( impl<'a, 'b> Display for DisplayValue<'a> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let (show_value, label) = self.show(); match self.inner { ValueInner::Number { format, @@ -1403,7 +1400,7 @@ impl<'a, 'b> Display for DisplayValue<'a> { value, .. } => { - if show_value { + if self.show_value { let format = if format.type_() == Type::F && *honor_small && value.is_some_and(|value| value != 0.0 && value.abs() < self.small()) @@ -1421,8 +1418,8 @@ impl<'a, 'b> Display for DisplayValue<'a> { .unwrap(); write!(f, "{}", buf.trim_start_matches(' '))?; } - if let Some(label) = label { - if show_value { + if let Some(label) = self.show_label { + if self.show_value { write!(f, " ")?; } f.write_str(label)?; @@ -1431,7 +1428,7 @@ impl<'a, 'b> Display for DisplayValue<'a> { } ValueInner::String { s, .. } | ValueInner::Variable { var_name: s, .. } => { - match (show_value, label) { + match (self.show_value, self.show_label) { (true, None) => write!(f, "{s}"), (false, Some(label)) => write!(f, "{label}"), (true, Some(label)) => write!(f, "{s} {label}"), @@ -1576,12 +1573,32 @@ impl ValueInner { // Returns an object that will format this value. Settings on `options` // control whether variable and value labels are included. pub fn display<'a>(&'a self, options: impl AsValueOptions) -> DisplayValue<'a> { + let options = options.as_value_options(); + let (show_value, show_label) = if let Some(value_label) = self.value_label() { + interpret_show( + || Settings::global().show_values, + options.show_values, + self.show(), + value_label, + ) + } else if let Some(variable_label) = self.variable_label() { + interpret_show( + || Settings::global().show_variables, + options.show_variables, + self.show(), + variable_label, + ) + } else { + (true, None) + }; DisplayValue { inner: &self, markup: false, subscripts: &[], footnotes: &[], - options: options.as_value_options(), + options, + show_value, + show_label, } } } diff --git a/rust/pspp/src/output/text.rs b/rust/pspp/src/output/text.rs index 9f788bd07d..048e045019 100644 --- a/rust/pspp/src/output/text.rs +++ b/rust/pspp/src/output/text.rs @@ -267,14 +267,34 @@ impl TextDriver { } } - fn layout_cell(&self, cell: &DrawCell, mut text: &str, bb: Rect2) -> Coord2 { + fn cell_to_text(cell: &DrawCell) -> String { + cell.inner + .display(() /* XXX */) + .with_font_style(&cell.style.font_style) + .with_subscripts(cell.subscripts) + .with_footnotes(cell.footnotes) + .to_string() + } + + fn layout_cell(&self, cell: &DrawCell, mut text: &str, bb: Rect2, clip: Rect2) -> Coord2 { if text.is_empty() { return Coord2::default(); } - let mut breaks = new_line_breaks(text, bb[Axis2::X].len()); + use Axis2::*; + let mut breaks = new_line_breaks(text, bb[X].len()); let mut size = Coord2::new(0, 0); - for text in breaks { + for (text, y) in breaks.zip(bb[Y].clone()) { + let width = text.width(); + if width > size[X] { + size[X] = width; + } + size[Y] += 1; + + if !clip[Y].contains(&y) { + continue; + } + } todo!() @@ -433,20 +453,31 @@ impl Device for TextDriver { } fn measure_cell_width(&self, cell: &DrawCell) -> [usize; 2] { - let text = cell - .inner - .display(() /* XXX */) - .with_font_style(&cell.style.font_style) - .with_subscripts(cell.subscripts) - .with_footnotes(cell.footnotes) - .to_string(); - let max_width = self.layout_cell(cell, &text, Rect2::new(0..usize::MAX, 0..usize::MAX)); - let min_width = self.layout_cell(cell, &text, Rect2::new(0..1, 0..usize::MAX)); + let text = Self::cell_to_text(cell); + let max_width = self.layout_cell( + cell, + &text, + Rect2::new(0..usize::MAX, 0..usize::MAX), + Rect2::default(), + ); + let min_width = self.layout_cell( + cell, + &text, + Rect2::new(0..1, 0..usize::MAX), + Rect2::default(), + ); [min_width.x(), max_width.x()] } fn measure_cell_height(&self, cell: &DrawCell, width: usize) -> usize { - todo!() + let text = Self::cell_to_text(cell); + self.layout_cell( + cell, + &text, + Rect2::new(0..width, 0..usize::MAX), + Rect2::default(), + ) + .y() } fn adjust_break(&self, cell: &Content, size: Coord2) -> usize { -- 2.30.2