use crate::output::pivot::Coord2;
-use super::{driver::Driver, pivot::PivotTable, table::Table, Details, Item, TextType};
+use super::{
+ driver::Driver,
+ pivot::{PivotTable, ValueOptions},
+ table::Table,
+ Details, Item, TextType,
+};
struct CsvDriver {
file: File,
let coord = Coord2::new(x, y);
let content = table.get(coord);
if content.is_top_left() {
- if let Some(value) = &content.inner().value {
- let display = value.display(Some(pivot_table));
- let s = match leader {
- Some(leader) if x == 0 && y == 0 => format!("{leader}: {display}"),
- _ => display.to_string(),
- };
- write!(&mut self.file, "{}", CsvField::new(&s, self.options))?;
- }
+ let display = content.inner().value.display(pivot_table);
+ let s = match leader {
+ Some(leader) if x == 0 && y == 0 => format!("{leader}: {display}"),
+ _ => display.to_string(),
+ };
+ write!(&mut self.file, "{}", CsvField::new(&s, self.options))?;
}
}
writeln!(&mut self.file)?;
TextType::Syntax | TextType::PageTitle => (),
TextType::Title | TextType::Log => {
self.start_item();
- for line in text.content.display(None).to_string().lines() {
+ for line in text
+ .content
+ .display(ValueOptions::default())
+ .to_string()
+ .lines()
+ {
writeln!(&self.file, "{}", CsvField::new(line, self.options)).unwrap();
}
}
#[derive(Clone, Debug)]
pub struct AreaStyle {
- cell_style: CellStyle,
- font_style: FontStyle,
+ pub cell_style: CellStyle,
+ pub font_style: FontStyle,
}
#[derive(Clone, Debug)]
pub struct CellStyle {
- horz_align: HorzAlign,
- vert_align: VertAlign,
+ pub horz_align: HorzAlign,
+ pub vert_align: VertAlign,
/// Margins in 1/96" units.
///
/// `margins[Axis2::X][1]` is the right margin.
/// `margins[Axis2::Y][0]` is the top margin.
/// `margins[Axis2::Y][1]` is the bottom margin.
- margins: EnumMap<Axis2, [i32; 2]>,
+ pub margins: EnumMap<Axis2, [i32; 2]>,
}
#[derive(Copy, Clone, Debug, PartialEq)]
use Axis2::*;
Coord2::new(self[X].start, self[Y].start)
}
+ pub fn from_fn<F>(f: F) -> Self
+ where
+ F: FnMut(Axis2) -> Range<usize>,
+ {
+ Self(EnumMap::from_fn(f))
+ }
+ pub fn offset(self, offset: Coord2) -> Rect2 {
+ Self::from_fn(|axis| self[axis].start + offset[axis]..self[axis].end + offset[axis])
+ }
}
impl From<EnumMap<Axis2, Range<usize>>> for Rect2 {
Superscript,
}
+#[derive(Copy, Clone, Debug)]
+pub struct ValueOptions {
+ pub show_values: Option<Show>,
+
+ pub show_variables: Option<Show>,
+
+ pub small: f64,
+
+ /// Where to put the footnote markers.
+ pub footnote_marker_type: FootnoteMarkerType,
+}
+
+impl Default for ValueOptions {
+ fn default() -> Self {
+ Self {
+ show_values: None,
+ show_variables: None,
+ small: 0.0001,
+ footnote_marker_type: FootnoteMarkerType::default(),
+ }
+ }
+}
+
+pub trait AsValueOptions {
+ fn as_value_options(self) -> ValueOptions;
+}
+
+impl AsValueOptions for () {
+ fn as_value_options(self) -> ValueOptions {
+ ValueOptions::default()
+ }
+}
+
+impl AsValueOptions for &PivotTable {
+ fn as_value_options(self) -> ValueOptions {
+ self.value_options()
+ }
+}
+
+impl AsValueOptions for ValueOptions {
+ fn as_value_options(self) -> ValueOptions {
+ self
+ }
+}
+
#[derive(Clone, Debug)]
pub struct PivotTable {
pub look: Arc<Look>,
pub dataset: Option<String>,
pub datafile: Option<String>,
pub date: Option<NaiveDateTime>,
- pub footnotes: Vec<Footnote>,
+ pub footnotes: Vec<Arc<Footnote>>,
pub title: Option<Box<Value>>,
pub subtype: Option<Box<Value>>,
pub corner_text: Option<Box<Value>>,
Box::new(once(SmallVec::from_slice(&self.current_layer)))
}
}
+
+ pub fn value_options(&self) -> ValueOptions {
+ ValueOptions {
+ show_values: self.show_values,
+ show_variables: self.show_variables,
+ small: self.small,
+ footnote_marker_type: self.look.footnote_marker_type,
+ }
+ }
}
pub struct Layers {}
}
impl Footnote {
- pub fn display_marker<'a, 'b>(
- &'a self,
- table: Option<&'b PivotTable>,
- ) -> DisplayMarker<'a, 'b> {
+ pub fn display_marker<'a, 'b>(&'a self, options: impl AsValueOptions) -> DisplayMarker<'a> {
DisplayMarker {
footnote: self,
- table,
+ options: options.as_value_options(),
}
}
- pub fn display_content<'a, 'b>(
- &'a self,
- table: Option<&'b PivotTable>,
- ) -> DisplayValue<'a, 'b> {
- self.content.display(table)
+ pub fn display_content<'a, 'b>(&'a self, options: impl AsValueOptions) -> DisplayValue<'a> {
+ self.content.display(options)
}
}
-pub struct DisplayMarker<'a, 'b> {
+pub struct DisplayMarker<'a> {
footnote: &'a Footnote,
- table: Option<&'b PivotTable>,
+ options: ValueOptions,
}
-impl<'a, 'b> DisplayMarker<'a, 'b> {
- fn marker_type(&self) -> FootnoteMarkerType {
- if let Some(table) = self.table {
- table.look.footnote_marker_type
- } else {
- FootnoteMarkerType::default()
- }
- }
-}
-
-impl<'a, 'b> Display for DisplayMarker<'a, 'b> {
+impl<'a> Display for DisplayMarker<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(marker) = &self.footnote.marker {
- write!(f, "{}", marker.display(self.table).without_suffixes())
+ write!(f, "{}", marker.display(self.options).without_suffixes())
} else {
let i = self.footnote.index + 1;
- match self.marker_type() {
+ match self.options.footnote_marker_type {
FootnoteMarkerType::Alphabetic => write!(f, "{}", Display26Adic(i)),
FootnoteMarkerType::Numeric => write!(f, "{i}"),
}
///
/// 5. A template. PSPP doesn't create these itself yet, but it can read and
/// interpret those created by SPSS.
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, Default)]
pub struct Value {
- inner: ValueInner,
- styling: Option<Box<ValueStyle>>,
+ pub inner: ValueInner,
+ pub styling: Option<Box<ValueStyle>>,
}
impl Value {
}
}
-pub struct DisplayValue<'a, 'b> {
- value: &'a Value,
- table: Option<&'b PivotTable>,
-
- /// Whether to show subscripts and footnotes (which follow the body).
- show_suffixes: bool,
+pub struct DisplayValue<'a> {
+ inner: &'a ValueInner,
+ markup: bool,
+ subscripts: &'a [String],
+ footnotes: &'a [Arc<Footnote>],
+ options: ValueOptions,
}
-impl<'a, 'b> DisplayValue<'a, 'b> {
+impl<'a> DisplayValue<'a> {
pub fn without_suffixes(self) -> Self {
Self {
- show_suffixes: false,
+ subscripts: &[],
+ footnotes: &[],
..self
}
}
- fn show(&self) -> (bool, Option<&String>) {
- match &self.value.inner {
- ValueInner::Number {
- value_label: None, ..
- }
- | ValueInner::String {
- value_label: None, ..
- }
- | ValueInner::Variable {
- variable_label: None,
- ..
- }
- | ValueInner::Text { .. }
- | ValueInner::Template { .. } => (true, None),
+ 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
+ }
+ }
- ValueInner::Number {
- show,
- value_label: Some(label),
- ..
- }
- | ValueInner::String {
- show,
- value_label: Some(label),
- ..
- } => interpret_show(
+ pub fn with_font_style(self, font_style: &FontStyle) -> Self {
+ Self {
+ markup: font_style.markup,
+ ..self
+ }
+ }
+
+ pub fn with_subscripts(self, subscripts: &'a [String]) -> Self {
+ Self { subscripts, ..self }
+ }
+
+ pub fn with_footnotes(self, footnotes: &'a [Arc<Footnote>]) -> Self {
+ 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.table.map_or(None, |table| table.show_values),
- *show,
- label,
- ),
-
- ValueInner::Variable {
- show,
- variable_label: Some(label),
- ..
- } => interpret_show(
+ 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.table.map_or(None, |table| table.show_variables),
- *show,
- label,
- ),
+ self.options.show_variables,
+ self.inner.show(),
+ variable_label,
+ )
+ } else {
+ (true, None)
}
}
fn small(&self) -> f64 {
- self.table.map_or(0.0, |table| table.small)
+ self.options.small
}
fn template(
continue;
};
if let Some(arg) = arg.get(0) {
- write!(f, "{}", arg.display(self.table))?;
+ write!(f, "{}", arg.display(self.options))?;
}
}
b'[' => {
continue;
};
args_consumed = args_consumed.max(index);
- write!(f, "{}", arg.display(self.table))?;
+ write!(f, "{}", arg.display(self.options))?;
}
c => write!(f, "{c}")?,
}
fn interpret_show(
global_show: impl Fn() -> Show,
- table_show: impl Fn() -> Option<Show>,
+ table_show: Option<Show>,
value_show: Option<Show>,
- label: &String,
-) -> (bool, Option<&String>) {
- match value_show.or_else(table_show).unwrap_or_else(global_show) {
+ label: &str,
+) -> (bool, Option<&str>) {
+ match value_show.or(table_show).unwrap_or_else(global_show) {
Show::Value => (true, None),
Show::Label => (false, Some(label)),
Show::Both => (true, Some(label)),
}
}
-impl<'a, 'b> Display for DisplayValue<'a, 'b> {
+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.value.inner {
+ match self.inner {
ValueInner::Number {
format,
honor_small,
}
ValueInner::Text { local, .. } => {
+ /*
if self
- .value
+ .inner
.styling
.as_ref()
- .is_some_and(|styling| styling.font_style.markup)
+ .is_some_and(|styling| styling.style.font_style.markup)
{
todo!();
- }
+ }*/
f.write_str(&local)
}
ValueInner::Template { args, local, .. } => self.template(f, &local, args),
+
+ ValueInner::Empty => Ok(()),
}?;
- if self.show_suffixes {
- if let Some(styling) = &self.value.styling {
- for (subscript, delimiter) in
- styling.subscripts.iter().zip(once('_').chain(repeat(',')))
- {
- write!(f, "{delimiter}{subscript}")?;
- }
+ for (subscript, delimiter) in self.subscripts.iter().zip(once('_').chain(repeat(','))) {
+ write!(f, "{delimiter}{subscript}")?;
+ }
- for footnote_index in styling.footnote_indexes.iter().copied() {
- if let Some(table) = self.table {
- if let Some(footnote) = table.footnotes.get(footnote_index) {
- write!(f, "[{}]", footnote.display_marker(self.table))?;
- }
- }
- }
- }
+ for footnote in self.footnotes {
+ write!(f, "[{}]", footnote.display_marker(self.options))?;
}
Ok(())
impl Value {
// Returns an object that will format this value, including subscripts and
- // superscripts and footnotes. Settings on `table` control whether variable
- // and value labels are included; if `table` is not provided, then defaults
- // are used. `table` is also needed to display footnote markers.
- pub fn display<'a, 'b>(&'a self, table: Option<&'b PivotTable>) -> DisplayValue<'a, 'b> {
- DisplayValue {
- value: self,
- table,
- show_suffixes: true,
+ // superscripts and footnotes. `options` controls whether variable and
+ // value labels are included.
+ pub fn display<'a, 'b>(&'a self, options: impl AsValueOptions) -> DisplayValue<'a> {
+ let display = self.inner.display(options.as_value_options());
+ match &self.styling {
+ Some(styling) => display.with_styling(&*styling),
+ None => display,
}
}
}
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, Default)]
pub enum ValueInner {
Number {
show: Option<Show>,
local: String,
id: String,
},
+
+ #[default]
+ Empty,
+}
+
+impl ValueInner {
+ pub fn is_empty(&self) -> bool {
+ match self {
+ Self::Empty => true,
+ _ => false,
+ }
+ }
+ fn show(&self) -> Option<Show> {
+ match self {
+ ValueInner::Number { show, .. }
+ | ValueInner::String { show, .. }
+ | ValueInner::Variable { show, .. } => *show,
+ _ => None,
+ }
+ }
+
+ fn label(&self) -> Option<&str> {
+ self.value_label().or_else(|| self.variable_label())
+ }
+
+ fn value_label(&self) -> Option<&str> {
+ match self {
+ ValueInner::Number { value_label, .. } | ValueInner::String { value_label, .. } => {
+ value_label.as_ref().map(String::as_str)
+ }
+ _ => None,
+ }
+ }
+
+ fn variable_label(&self) -> Option<&str> {
+ match self {
+ ValueInner::Variable { variable_label, .. } => {
+ variable_label.as_ref().map(String::as_str)
+ }
+ _ => None,
+ }
+ }
}
#[derive(Clone, Debug)]
pub struct ValueStyle {
- font_style: FontStyle,
- cell_style: CellStyle,
- subscripts: Vec<String>,
- footnote_indexes: Vec<usize>,
+ pub style: AreaStyle,
+ pub subscripts: Vec<String>,
+ pub footnotes: Vec<Arc<Footnote>>,
+}
+
+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> {
+ DisplayValue {
+ inner: &self,
+ markup: false,
+ subscripts: &[],
+ footnotes: &[],
+ options: options.as_value_options(),
+ }
+ }
}
}
}
- fn create_aux_table(&self, n: Coord2) -> Table {
- Table::new(
- n,
+ fn create_aux_table3<I>(&self, area: Area, rows: I) -> Table
+ where
+ I: Iterator<Item = Box<Value>> + ExactSizeIterator,
+ {
+ let mut table = Table::new(
+ Coord2::new(0, rows.len()),
Coord2::new(0, 0),
self.look.areas.clone(),
self.borders(false),
- )
+ );
+ for (y, row) in rows.enumerate() {
+ table.put(
+ Rect2::for_cell(Coord2::new(0, y)),
+ CellInner::new(area, row),
+ );
+ }
+ table
+ }
+
+ fn create_aux_table_if_nonempty<I>(&self, area: Area, rows: I) -> Option<Table>
+ where
+ I: Iterator<Item = Box<Value>> + ExactSizeIterator,
+ {
+ if rows.len() > 0 {
+ Some(self.create_aux_table3(area, rows))
+ } else {
+ None
+ }
}
fn borders(&self, printing: bool) -> EnumMap<Border, BorderStyle> {
})
}
- pub fn output(&self, layer_indexes: &[usize], printing: bool) -> OutputTables {
+ pub fn output_body(&self, layer_indexes: &[usize], printing: bool) -> Table {
let column_enumeration = self.enumerate_axis(Axis3::X, layer_indexes, self.look.omit_empty);
let row_enumeration = self.enumerate_axis(Axis3::Y, layer_indexes, self.look.omit_empty);
let data = Coord2::new(column_enumeration.len(), row_enumeration.len());
CellInner {
rotate: false,
area: Area::Data,
- value: value.map(|value| Box::new(value.clone())),
+ value: Box::new(value.cloned().unwrap_or_default()),
},
);
}
}
-
if (self.corner_text.is_some() || self.look.row_labels_in_corner)
&& stub.x() > 0
&& stub.y() > 0
{
body.put(
Rect2::new(0..stub.x(), 0..stub.y()),
- CellInner::new(Area::Corner, self.corner_text.clone()),
+ CellInner::new(Area::Corner, self.corner_text.clone().unwrap_or_default()),
);
}
body.v_line(Border::DataLeft, stub.x(), 0..body.n.y());
}
}
+ body
+ }
- // Title.
- let title = if self.title.is_some() && self.show_title {
- let mut title = self.create_aux_table(Coord2::new(0, 0));
- title.put(
- Rect2::new(0..1, 0..1),
- CellInner::new(Area::Title, self.title.clone()),
- );
- Some(title)
- } else {
- None
- };
+ pub fn output_title(&self) -> Option<Table> {
+ Some(self.create_aux_table3(Area::Title, [self.title.as_ref()?.clone()].into_iter()))
+ }
- // Layers.
- let n_layers: usize = self.nonempty_layer_dimensions().count();
- let layers = if n_layers > 0 {
- let mut layers = self.create_aux_table(Coord2::new(1, n_layers));
- for (y, dimension) in self.nonempty_layer_dimensions().enumerate() {
- layers.put(
- Rect2::for_cell(Coord2::new(0, y)),
- CellInner::new(
- Area::Layers,
- Some(dimension.data_leaves[layer_indexes[y]].name.clone()),
- ),
- );
- }
- Some(layers)
- } else {
- None
- };
+ pub fn output_layers(&self, layer_indexes: &[usize]) -> Option<Table> {
+ self.create_aux_table_if_nonempty(
+ Area::Layers,
+ self.nonempty_layer_dimensions()
+ .collect::<Vec<_>>()
+ .into_iter()
+ .enumerate()
+ .map(|(i, dimension)| dimension.data_leaves[layer_indexes[i]].name.clone()),
+ )
+ }
- // Caption.
- let caption = if self.caption.is_some() && self.show_caption {
- let mut caption = self.create_aux_table(Coord2::new(1, 1));
- caption.put(
- Rect2::for_cell(Coord2::new(0, 0)),
- CellInner::new(Area::Caption, self.caption.clone()),
- );
- Some(caption)
- } else {
- None
- };
+ pub fn output_caption(&self) -> Option<Table> {
+ Some(self.create_aux_table3(Area::Caption, [self.caption.as_ref()?.clone()].into_iter()))
+ }
- // Footnotes.
- let f = self.collect_footnotes(&[
+ pub fn output_footnotes<'a>(&self, footnotes: &[Arc<Footnote>]) -> Option<Table> {
+ self.create_aux_table_if_nonempty(
+ Area::Footer,
+ footnotes.into_iter().map(|f| {
+ Box::new(Value::new_user_text(format!(
+ "{}. {}",
+ f.display_marker(self),
+ f.display_content(self)
+ )))
+ }),
+ )
+ }
+
+ pub fn output(&self, layer_indexes: &[usize], printing: bool) -> OutputTables {
+ // Produce most of the tables.
+ let title = self.show_title.then(|| self.output_title()).flatten();
+ let layers = self.output_layers(layer_indexes);
+ let body = self.output_body(layer_indexes, printing);
+ let caption = self.show_caption.then(|| self.output_caption()).flatten();
+
+ // Then collect the footnotes from those tables.
+ let tables = [
title.as_ref(),
layers.as_ref(),
Some(&body),
caption.as_ref(),
- ]);
- let footnotes = if !f.is_empty() {
- let mut footnotes = self.create_aux_table(Coord2::new(1, f.len()));
- for (y, f) in f.into_iter().enumerate() {
- let s = format!(
- "{}. {}",
- f.display_marker(Some(self)),
- f.display_content(Some(self))
- );
- let value = Some(Box::new(Value::new_user_text(s)));
- footnotes.put(
- Rect2::for_cell(Coord2::new(0, y)),
- CellInner::new(Area::Footer, value),
- );
- }
- Some(footnotes)
- } else {
- None
- };
+ ];
+ let footnotes =
+ self.output_footnotes(&self.collect_footnotes(tables.into_iter().flatten()));
OutputTables {
title,
.filter(|d| !d.data_leaves.is_empty())
}
- fn collect_footnotes<'a>(&'a self, tables: &[Option<&Table>]) -> Vec<&'a Footnote> {
+ fn collect_footnotes<'a>(&self, tables: impl Iterator<Item = &'a Table>) -> Vec<Arc<Footnote>> {
if self.footnotes.is_empty() {
return Vec::new();
}
- let mut refs = vec![false; self.footnotes.len()];
- for table in tables.into_iter().flatten() {
+ let mut refs = Vec::with_capacity(self.footnotes.len());
+ for table in tables {
for cell in table.cells() {
- if let Some(value) = &cell.inner().value {
- if let Some(styling) = &value.styling {
- for index in &styling.footnote_indexes {
- refs[*index] = true;
- }
- }
+ if let Some(styling) = &cell.inner().value.styling {
+ refs.extend(styling.footnotes.iter().cloned());
}
}
}
- refs.iter()
- .enumerate()
- .filter_map(|(index, r)| (*r).then_some(&self.footnotes[index]))
- .collect()
+ refs.sort_by(|a, b| a.index.cmp(&b.index));
+ refs.dedup_by(|a, b| a.index == b.index);
+ refs
}
}
rotate: (rotate_inner_labels && is_inner_row)
|| (rotate_outer_labels && is_outer_row),
area,
- value: Some(Box::new(c.name().clone())),
+ value: Box::new(c.name().clone()),
},
);
if d.root.show_label_in_corner && h_ofs > 0 {
table.put(
Rect2::for_ranges((h, 0..h_ofs), top_row..top_row + d.label_depth),
- CellInner::new(Area::Corner, Some(d.root.name.clone())),
+ CellInner::new(Area::Corner, d.root.name.clone()),
);
}
use enum_map::EnumMap;
use itertools::interleave;
+use num::Integer;
use smallvec::SmallVec;
-use super::pivot::{Axis2, BorderStyle, Coord2, Look, PivotTable, Rect2, Stroke};
-use super::table::{Cell, CellInner, Content, Table};
+use crate::output::pivot::VertAlign;
+
+use super::pivot::{
+ AreaStyle, Axis2, BorderStyle, Coord2, Footnote, Look, PivotTable, Rect2, Stroke, ValueInner,
+};
+use super::table::{CellInner, Content, Table};
/// Parameters for rendering a table_item to a device.
///
can_scale: bool,
}
+/*
+pub struct DeviceCell {
+ /// Rotate cell contents 90 degrees?
+ rotate: bool,
+
+ /// Value to render.
+ value: &ValueInner,
+
+
+}*/
pub trait Device {
fn params(&self) -> &Params;
/// Optional. If [RenderParams::can_adjust_break] is false, the rendering
/// engine assumes that all breakpoints are acceptable.
fn adjust_break(&self, cell: &Content, size: Coord2) -> usize;
+}
+trait Draw {
/// Draws a generalized intersection of lines in `bb`.
///
/// `styles` is interpreted this way:
/// alignment itself.
fn draw_cell(
&mut self,
- cell: &Cell,
- color_idx: usize,
+ draw_cell: &DrawCell,
+ alternate_row: bool,
bb: &Rect2,
valign_offset: usize,
spill: EnumMap<Axis2, [usize; 2]>,
fn scale(&mut self, factor: f64);
}
+struct DrawCell<'a> {
+ pub rotate: bool,
+ pub inner: &'a ValueInner,
+ pub style: &'a AreaStyle,
+ pub subscripts: &'a [String],
+ pub footnotes: &'a [Arc<Footnote>],
+}
+
/// A layout for rendering a specific table on a specific device.
///
/// May represent the layout of an entire table presented to [Pager::new], or a
axis_width(cp, ofs..ofs + 1)
}
+/// Is `ofs` the offset of a rule in `cp`?
+fn is_rule(z: usize) -> bool {
+ z.is_even()
+}
+
#[derive(Clone)]
pub struct RenderCell<'a> {
rect: Rect2,
})
}
- fn table_width(&self, axis: Axis2) -> usize {
+ fn total_size(&self, axis: Axis2) -> usize {
self.cp[axis].last().copied().unwrap()
}
+
+ fn draw(&self, draw: &mut dyn Draw, ofs: Coord2) {
+ use Axis2::*;
+ self.draw_cells(
+ ofs,
+ draw,
+ Rect2::new(0..self.n[X] * 2 + 1, 0..self.n[Y] * 2 + 1),
+ );
+ }
+
+ fn draw_cells(&self, ofs: Coord2, draw: &mut dyn Draw, cells: Rect2) {
+ use Axis2::*;
+ for y in cells[Y].clone() {
+ let mut x = cells[X].start;
+ while x < cells[X].end {
+ if !is_rule(x) && !is_rule(y) {
+ let cell = self.get_cell(Coord2::new(x, y));
+ self.draw_cell(ofs, draw, &cell);
+ x = rule_ofs(cell.rect[X].end);
+ } else {
+ x += 1;
+ }
+ }
+ }
+
+ /*
+ for y in cells[Y] {
+ for x in cells[X] {
+ if is_rule(x) && is_rule(y) {
+ self.draw_rule(ofs, draw, Coord2::new(x, y));
+ }
+ }
+ }*/
+ }
+
+ fn extra_height(&self, bb: &Rect2, inner: &CellInner) -> usize {
+ use Axis2::*;
+ let height = self.device.measure_cell_height(inner, bb[X].len());
+ usize::saturating_sub(bb[Y].len(), height)
+ }
+ fn draw_cell(&self, ofs: Coord2, draw: &mut dyn Draw, cell: &RenderCell) {
+ use Axis2::*;
+
+ let inner = cell.content.inner();
+ let (style, subscripts, footnotes) = if let Some(styling) = inner.value.styling.as_ref() {
+ (
+ &styling.style,
+ styling.subscripts.as_slice(),
+ styling.footnotes.as_slice(),
+ )
+ } else {
+ (&self.table.areas[inner.area], [].as_slice(), [].as_slice())
+ };
+ let draw_cell = DrawCell {
+ rotate: inner.rotate,
+ inner: &inner.value.inner,
+ style,
+ subscripts,
+ footnotes,
+ };
+ let mut bb = Rect2::from_fn(|a| {
+ self.cp[a][cell.rect[a].start * 2 + 1]..self.cp[a][cell.rect[a].end * 2]
+ })
+ .offset(ofs);
+ let spill = EnumMap::from_fn(|a| {
+ [
+ self.rule_width(a, cell.rect[a].start) / 2,
+ self.rule_width(a, cell.rect[a].end) / 2,
+ ]
+ });
+
+ let clip = if let Some(overflow) = self.overflows.get(&cell.rect.top_left()) {
+ Rect2::from_fn(|a| {
+ let mut clip = bb[a].clone();
+ if overflow[a][0] > 0 {
+ bb[a].start -= overflow[a][0];
+ if cell.rect[a].start == 0 && !self.is_edge_cutoff[a][0] {
+ clip.start = ofs[a] + self.cp[a][cell.rect[a].start * 2];
+ }
+ }
+
+ if overflow[a][1] > 0 {
+ bb[a].end += overflow[a][1];
+ if cell.rect[a].end == self.n[a] && !self.is_edge_cutoff[a][1] {
+ clip.end = ofs[a] + self.cp[a][cell.rect[a].end * 2 + 1];
+ }
+ }
+
+ clip
+ })
+ } else {
+ bb.clone()
+ };
+
+ let valign_offset = match style.cell_style.vert_align {
+ VertAlign::Top => 0,
+ VertAlign::Middle => self.extra_height(&bb, inner) / 2,
+ VertAlign::Bottom => self.extra_height(&bb, inner),
+ };
+
+ // Header rows are never alternate rows.
+ let alternate_row =
+ usize::checked_sub(cell.rect[Y].start, self.h[Y]).is_some_and(|row| row % 2 == 1);
+
+ draw.draw_cell(&draw_cell, alternate_row, &bb, valign_offset, spill, &clip)
+ }
}
struct Selection {
if pivot_table.look.shrink_to_fit[Axis2::Y] && device.params().can_scale {
let total_height = pages
.iter()
- .map(|page: &Arc<Page>| page.table_width(Axis2::Y))
+ .map(|page: &Arc<Page>| page.total_size(Axis2::Y))
.sum::<usize>() as f64;
let max_height = device.params().size[Axis2::Y] as f64;
if total_height * scale >= max_height {
}
false
}
+
+ /// Draws a chunk of content to fit in a space that has vertical size
+ /// `space` and the horizontal size specified in the device parameters.
+ /// Returns the amount of vertical space actually used by the rendered
+ /// chunk, which will be 0 if `space` is too small to render anything or if
+ /// no content remains (use [Self::has_next] to distinguish these cases).
+ fn draw_next(&mut self, mut space: usize, draw: &mut dyn Draw) -> usize {
+ use Axis2::*;
+
+ if self.scale != 1.0 {
+ draw.scale(self.scale);
+ space = (space as f64 / self.scale) as usize;
+ }
+
+ let mut ofs = Coord2::new(0, 0);
+ let mut n_pages = None;
+ while self.has_next() && n_pages == Some(self.pages.len()) {
+ n_pages = Some(self.pages.len());
+
+ let Some(page) = self
+ .y_break
+ .as_mut()
+ .and_then(|y_break| y_break.next(space - ofs[Y]))
+ else {
+ break;
+ };
+ page.draw(draw, ofs);
+ ofs[Y] += page.total_size(Y);
+ }
+
+ if self.scale != 1.0 {
+ ofs[Y] = (ofs[Y] as f64 * self.scale) as usize;
+ }
+ ofs[Y]
+ }
}
/// The area that the cell belongs to.
pub area: Area,
- pub value: Option<Box<Value>>,
+ pub value: Box<Value>,
}
impl CellInner {
- pub fn new(area: Area, value: Option<Box<Value>>) -> Self {
+ pub fn new(area: Area, value: Box<Value>) -> Self {
Self {
rotate: false,
area,
}
pub fn is_empty(&self) -> bool {
- self.value.is_none()
+ self.value.inner.is_empty()
}
}
/// name.
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub enum Show {
- /// Value or variable name only.
+ /// Value (or variable name) only.
Value,
/// Label only.
+ ///
+ /// The value will be shown if no label is available.
#[default]
Label,
/// Value (or variable name) and label.
+ ///
+ /// Just the value will be shown, if no label is available.
Both,
}