dictionary::Dictionary,
format::{DisplayPlain, Type},
output::{
- Item,
- drivers::Driver,
- pivot::{ Index2},
+ drivers::Driver, table::CellPos, Item
},
util::ToSmallString as _,
variable::Variable,
write!(&mut self.file, "{}", self.options.delimiter)?;
}
- let coord = Index2::new(x, y);
+ let coord = CellPos::new(x, y);
let content = table.get(coord);
if content.is_top_left() {
let display = content.inner().value.display(pivot_table);
use crate::output::{
drivers::Driver, pivot::{
- Axis2, BorderStyle, Color, HorzAlign, Index2, IndexRect2, PivotTable, Stroke, VertAlign
- }, table::{DrawCell, Table}, Details, Item
+ Axis2, BorderStyle, Color, HorzAlign, IndexRect2, PivotTable, Stroke, VertAlign
+ }, table::{DrawCell, CellPos, Table}, Details, Item
};
#[derive(Clone, Debug, Deserialize, Serialize)]
writeln!(&mut self.writer, ">")?;
if let Some(title) = output.title {
- let cell = title.get(Index2::new(0, 0));
+ let cell = title.get(CellPos::new(0, 0));
self.put_cell(
DrawCell::new(cell.inner(), &title),
IndexRect2::new(0..1, 0..1),
for y in 0..output.body.n.y() {
writeln!(&mut self.writer, "<tr>")?;
for x in output.body.iter_x(y) {
- let cell = output.body.get(Index2::new(x, y));
+ let cell = output.body.get(CellPos::new(x, y));
if cell.is_top_left() {
let is_header = x < output.body.h[Axis2::X] || y < output.body.h[Axis2::Y];
let tag = if is_header { "th" } else { "td" };
writeln!(&mut self.writer, "<tr>")?;
if let Some(caption) = output.caption {
self.put_cell(
- DrawCell::new(caption.get(Index2::new(0, 0)).inner(), &caption),
+ DrawCell::new(caption.get(CellPos::new(0, 0)).inner(), &caption),
IndexRect2::new(0..output.body.n[Axis2::X], 0..1),
"td",
None,
&mut style,
table.get_rule(
Axis2::X,
- Index2::new(rect[Axis2::X].end, rect[Axis2::Y].start),
+ CellPos::new(rect[Axis2::X].end, rect[Axis2::Y].start),
),
"right",
);
&mut style,
table.get_rule(
Axis2::Y,
- Index2::new(rect[Axis2::X].start, rect[Axis2::Y].end),
+ CellPos::new(rect[Axis2::X].start, rect[Axis2::Y].end),
),
"bottom",
);
calendar::date_time_to_pspp,
data::{ByteString, Datum, EncodedString},
format::{
- DATETIME40_0, Decimal, F8_2, F40, F40_2, F40_3, Format, PCT40_1,
- Settings as FormatSettings, Type, UncheckedFormat,
+ Decimal, Format, Settings as FormatSettings, Type, UncheckedFormat, DATETIME40_0, F40, F40_2, F40_3, F8_2, PCT40_1
},
- output::spv::html::Markup,
+ output::{spv::html::Markup, table::CellPos},
settings::{Settings, Show},
util::ToSmallString,
variable::{VarType, Variable},
}
}
-#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
-pub struct Index2(pub EnumMap<Axis2, usize>);
-
-impl Index2 {
- pub fn new(x: usize, y: usize) -> Self {
- use Axis2::*;
- Self(enum_map! {
- X => x,
- Y => y
- })
- }
-
- pub fn for_axis((a, az): (Axis2, usize), bz: usize) -> Self {
- let mut coord = Self::default();
- coord[a] = az;
- coord[!a] = bz;
- coord
- }
-
- pub fn from_fn<F>(f: F) -> Self
- where
- F: FnMut(Axis2) -> usize,
- {
- Self(EnumMap::from_fn(f))
- }
-
- pub fn x(&self) -> usize {
- self.0[Axis2::X]
- }
-
- pub fn y(&self) -> usize {
- self.0[Axis2::Y]
- }
-
- pub fn get(&self, axis: Axis2) -> usize {
- self.0[axis]
- }
-}
-
-impl From<EnumMap<Axis2, usize>> for Index2 {
- fn from(value: EnumMap<Axis2, usize>) -> Self {
- Self(value)
- }
-}
-
-impl Index<Axis2> for Index2 {
- type Output = usize;
-
- fn index(&self, index: Axis2) -> &Self::Output {
- &self.0[index]
- }
-}
-
-impl IndexMut<Axis2> for Index2 {
- fn index_mut(&mut self, index: Axis2) -> &mut Self::Output {
- &mut self.0[index]
- }
-}
-
/// A 2-dimensional `(x,y)` pair.
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct Coord2(pub EnumMap<Axis2, isize>);
Axis2::Y => y_range.clone(),
})
}
- pub fn for_cell(cell: Index2) -> Self {
+ pub fn for_cell(cell: CellPos) -> Self {
Self::new(cell.x()..cell.x() + 1, cell.y()..cell.y() + 1)
}
pub fn for_ranges((a, a_range): (Axis2, Range<usize>), b_range: Range<usize>) -> Self {
ranges[b] = b_range;
Self(ranges)
}
- pub fn top_left(&self) -> Index2 {
+ pub fn top_left(&self) -> CellPos {
use Axis2::*;
- Index2::new(self[X].start, self[Y].start)
+ CellPos::new(self[X].start, self[Y].start)
}
pub fn from_fn<F>(f: F) -> Self
where
{
Self(EnumMap::from_fn(f))
}
- pub fn translate(self, offset: Index2) -> IndexRect2 {
+ pub fn translate(self, offset: CellPos) -> IndexRect2 {
Self::from_fn(|axis| self[axis].start + offset[axis]..self[axis].end + offset[axis])
}
pub fn is_empty(&self) -> bool {
use itertools::Itertools;
use crate::output::{
- pivot::{HeadingRegion, Index2, IndexRect2, LabelPosition, Path, RowParity},
- table::{CellInner, Table},
+ pivot::{HeadingRegion, IndexRect2, LabelPosition, Path, RowParity},
+ table::{CellInner, CellPos, Table},
};
use super::{
I: Iterator<Item = Box<Value>> + ExactSizeIterator,
{
let mut table = Table::new(
- Index2::new(1, rows.len()),
- Index2::new(0, 0),
+ CellPos::new(1, rows.len()),
+ CellPos::new(0, 0),
self.style.look.areas.clone(),
self.borders(false),
self.into_value_options(),
);
for (y, row) in rows.enumerate() {
table.put(
- IndexRect2::for_cell(Index2::new(0, y)),
+ IndexRect2::for_cell(CellPos::new(0, y)),
CellInner::new(area, row),
);
}
pub fn output_body(&self, layer_indexes: &[usize], printing: bool) -> Table {
let headings = EnumMap::from_fn(|axis| Headings::new(self, axis, layer_indexes));
- let data = Index2::from_fn(|axis| headings[axis].width());
- let mut stub = Index2::from_fn(|axis| headings[!axis].height());
+ let data = CellPos::from_fn(|axis| headings[axis].width());
+ let mut stub = CellPos::from_fn(|axis| headings[!axis].height());
if headings[Axis2::Y].row_label_position == LabelPosition::Corner && stub.y() == 0 {
stub[Axis2::Y] = 1;
}
let mut body = Table::new(
- Index2::from_fn(|axis| data[axis] + stub[axis]),
+ CellPos::from_fn(|axis| data[axis] + stub[axis]),
stub,
self.style.look.areas.clone(),
self.borders(printing),
use num::Integer;
use smallvec::SmallVec;
-use crate::output::pivot::{Index2, IndexRect2, VertAlign};
-use crate::output::table::DrawCell;
+use crate::output::pivot::{IndexRect2, VertAlign};
+use crate::output::table::{DrawCell, CellPos};
use super::pivot::{Axis2, BorderStyle, Coord2, Look, PivotTable, Rect2, Stroke};
use super::table::{Content, Table};
/// Size of the table in cells.
///
- n: Index2,
+ n: CellPos,
/// Header size. Cells `0..h[X]` are rendered horizontally, and `0..h[Y]` vertically.
- h: Index2,
+ h: CellPos,
/// Main region of cells to render.
r: IndexRect2,
/// ```
/// Each entry maps from a cell that overflows to the space that has been
/// trimmed off the cell.
- overflows: HashMap<Index2, EnumMap<Axis2, [isize; 2]>>,
+ overflows: HashMap<CellPos, EnumMap<Axis2, [isize; 2]>>,
/// If a single column (or row) is too wide (or tall) to fit on a page
/// reasonably, then [Break::next] will split a single row or column across
*self.cp[axis].last().unwrap()
}
- fn new_mappings(h: Index2, r: &IndexRect2) -> EnumMap<Axis2, [Map; 2]> {
+ fn new_mappings(h: CellPos, r: &IndexRect2) -> EnumMap<Axis2, [Map; 2]> {
EnumMap::from_fn(|axis| {
[
Map {
z + self.get_map(axis, z).ofs
}
- fn map_coord(&self, coord: Index2) -> Index2 {
- Index2::from_fn(|a| self.map_z(a, coord[a]))
+ fn map_coord(&self, coord: CellPos) -> CellPos {
+ CellPos::from_fn(|a| self.map_z(a, coord[a]))
}
- fn get_cell(&self, coord: Index2) -> RenderCell<'_> {
+ fn get_cell(&self, coord: CellPos) -> RenderCell<'_> {
let maps = EnumMap::from_fn(|axis| self.get_map(axis, coord[axis]));
let cell = self.table.get(self.map_coord(coord));
RenderCell {
if self.h[a] == 0 || z0 > self.h[a] || pixel0 > 0 {
let mut z = 0;
while z < self.n[b] {
- let d = Index2::for_axis((a, z0), z);
+ let d = CellPos::for_axis((a, z0), z);
let cell = self.get_cell(d);
let overflow0 = pixel0 > 0 || cell.rect[a].start < z0;
let overflow1 = cell.rect[a].end > z1 || (cell.rect[a].end == z1 && pixel1 > 0);
// Add overflows along the right side.
let mut z = 0;
while z < self.n[b] {
- let d = Index2::for_axis((a, z1 - 1), z);
+ let d = CellPos::for_axis((a, z1 - 1), z);
let cell = self.get_cell(d);
if cell.rect[a].end > z1
|| (cell.rect[a].end == z1 && pixel1 > 0)
let mut x = cells[X].start;
while x < cells[X].end {
if !is_rule(x) && !is_rule(y) {
- let cell = self.get_cell(Index2::new(x / 2, y / 2));
+ let cell = self.get_cell(CellPos::new(x / 2, y / 2));
self.draw_cell(device, ofs, &cell);
x = rule_ofs(cell.rect[X].end);
} else {
for y in cells[Y].clone() {
for x in cells[X].clone() {
if is_rule(x) || is_rule(y) {
- self.draw_rule(device, ofs, Index2::new(x, y));
+ self.draw_rule(device, ofs, CellPos::new(x, y));
}
}
}
}
- fn draw_rule(&self, device: &mut dyn Device, ofs: Coord2, coord: Index2) {
+ fn draw_rule(&self, device: &mut dyn Device, ofs: Coord2, coord: CellPos) {
const NO_BORDER: BorderStyle = BorderStyle::none();
let styles = EnumMap::from_fn(|a: Axis2| {
let b = !a;
}
}
- fn get_rule(&self, a: Axis2, coord: Index2) -> BorderStyle {
- let coord = Index2::from_fn(|a| coord[a] / 2);
+ fn get_rule(&self, a: Axis2, coord: CellPos) -> BorderStyle {
+ let coord = CellPos::from_fn(|a| coord[a] / 2);
let coord = self.map_coord(coord);
let border = self.table.get_rule(a, coord);
if self.h[a] > 0 && coord[a] == self.h[a] {
let border2 = self
.table
- .get_rule(a, Index2::for_axis((a, self.h[a]), coord[!a]));
+ .get_rule(a, CellPos::for_axis((a, self.h[a]), coord[!a]));
border.combine(border2)
} else {
border
z1: usize,
p0: isize,
p1: isize,
- h: Index2,
+ h: CellPos,
}
impl Selection {
///
/// `coord` must be in the selected region or the results will not make
/// sense.
- fn coord_to_subpage(&self, coord: Index2) -> Index2 {
+ fn coord_to_subpage(&self, coord: CellPos) -> CellPos {
let a = self.a;
let b = self.b;
let ha0 = self.h[a];
- Index2::for_axis((a, max(coord[a] + ha0 - self.z0, ha0)), coord[b])
+ CellPos::for_axis((a, max(coord[a] + ha0 - self.z0, ha0)), coord[b])
}
}
// Determine the types of rules that are present.
let mut rules = EnumMap::default();
for w in 0..table.n[b] {
- let stroke = table.get_rule(a, Index2::for_axis((a, z), w)).stroke;
+ let stroke = table.get_rule(a, CellPos::for_axis((a, z), w)).stroke;
rules[stroke] = true;
}
if self.axis == Axis2::Y && device.params().can_adjust_break {
let mut x = 0;
while x < self.page.n[Axis2::X] {
- let cell = self.page.get_cell(Index2::new(x, z));
+ let cell = self.page.get_cell(CellPos::new(x, z));
let better_pixel = device.adjust_break(
cell.content,
Coord2::new(
//! Some drivers use tables as an implementation detail of rendering pivot
//! tables.
-use std::{borrow::Cow, ops::Range, sync::Arc};
+use std::{
+ borrow::Cow,
+ ops::{Index, IndexMut, Range},
+ sync::Arc,
+};
use enum_map::{EnumMap, enum_map};
use ndarray::{Array, Array2};
use crate::output::{
- pivot::{
- CellStyle, DisplayValue, FontStyle, Footnote, HorzAlign, Index2, IndexRect2,
- ValueInner,
- },
+ pivot::{CellStyle, DisplayValue, FontStyle, Footnote, HorzAlign, IndexRect2, ValueInner},
spv::html,
};
Area, AreaStyle, Axis2, Border, BorderStyle, HeadingRegion, Value, ValueOptions,
};
+/// The `(x,y)` position of a cell in a [Table].
+#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
+pub struct CellPos(pub EnumMap<Axis2, usize>);
+
+impl CellPos {
+ pub fn new(x: usize, y: usize) -> Self {
+ use Axis2::*;
+ Self(enum_map! {
+ X => x,
+ Y => y
+ })
+ }
+
+ pub fn for_axis((a, az): (Axis2, usize), bz: usize) -> Self {
+ let mut coord = Self::default();
+ coord[a] = az;
+ coord[!a] = bz;
+ coord
+ }
+
+ pub fn from_fn<F>(f: F) -> Self
+ where
+ F: FnMut(Axis2) -> usize,
+ {
+ Self(EnumMap::from_fn(f))
+ }
+
+ pub fn x(&self) -> usize {
+ self.0[Axis2::X]
+ }
+
+ pub fn y(&self) -> usize {
+ self.0[Axis2::Y]
+ }
+
+ pub fn get(&self, axis: Axis2) -> usize {
+ self.0[axis]
+ }
+}
+
+impl From<EnumMap<Axis2, usize>> for CellPos {
+ fn from(value: EnumMap<Axis2, usize>) -> Self {
+ Self(value)
+ }
+}
+
+impl Index<Axis2> for CellPos {
+ type Output = usize;
+
+ fn index(&self, index: Axis2) -> &Self::Output {
+ &self.0[index]
+ }
+}
+
+impl IndexMut<Axis2> for CellPos {
+ fn index_mut(&mut self, index: Axis2) -> &mut Self::Output {
+ &mut self.0[index]
+ }
+}
+
#[derive(Clone, Debug)]
pub struct CellRef<'a> {
- pub pos: Index2,
+ pub pos: CellPos,
pub content: &'a Content,
}
/// Returns the rectangle that this cell covers. If the cell doesn't contain
/// that information, returns a rectangle containing `pos`.
- pub fn rect(&self, pos: Index2) -> IndexRect2 {
+ pub fn rect(&self, pos: CellPos) -> IndexRect2 {
match self {
Content::Join(cell) => cell.region.clone(),
_ => IndexRect2::for_cell(pos),
.map_or(x + 1, |region| region[Axis2::X].end)
}
- pub fn is_top_left(&self, pos: Index2) -> bool {
+ pub fn is_top_left(&self, pos: CellPos) -> bool {
self.joined_rect().is_none_or(|r| pos == r.top_left())
}
#[derive(derive_more::Debug)]
pub struct Table {
/// Number of rows and columns.
- pub n: Index2,
+ pub n: CellPos,
/// Table header rows and columns.
- pub h: Index2,
+ pub h: CellPos,
pub contents: Array2<Content>,
impl Table {
pub fn new(
- n: Index2,
- headers: Index2,
+ n: CellPos,
+ headers: CellPos,
areas: EnumMap<Area, AreaStyle>,
borders: EnumMap<Border, BorderStyle>,
value_options: ValueOptions,
}
}
- pub fn get(&self, coord: Index2) -> CellRef<'_> {
+ pub fn get(&self, coord: CellPos) -> CellRef<'_> {
CellRef {
pos: coord,
content: &self.contents[[coord.x(), coord.y()]],
}
}
- pub fn get_rule(&self, axis: Axis2, pos: Index2) -> BorderStyle {
+ pub fn get_rule(&self, axis: Axis2, pos: CellPos) -> BorderStyle {
self.rules[axis][[pos.x(), pos.y()]].map_or(BorderStyle::none(), |b| self.borders[b])
}
}
/// The heading region that `pos` is part of, if any.
- pub fn heading_region(&self, pos: Index2) -> Option<HeadingRegion> {
+ pub fn heading_region(&self, pos: CellPos) -> Option<HeadingRegion> {
if pos.x() < self.h.x() {
Some(HeadingRegion::Rows)
} else if pos.y() < self.h.y() {
fn next(&mut self) -> Option<Self::Item> {
let next_x = self
.x
- .map_or(0, |x| self.table.get(Index2::new(x, self.y)).next_x());
+ .map_or(0, |x| self.table.get(CellPos::new(x, self.y)).next_x());
if next_x >= self.table.n.x() {
None
} else {
next: if table.is_empty() {
None
} else {
- Some(table.get(Index2::new(0, 0)))
+ Some(table.get(CellPos::new(0, 0)))
},
}
}
self.next = loop {
let next_x = next.next_x();
let coord = if next_x < self.table.n[X] {
- Index2::new(next_x, next.pos.y())
+ CellPos::new(next_x, next.pos.y())
} else if next.pos.y() + 1 < self.table.n[Y] {
- Index2::new(0, next.pos.y() + 1)
+ CellPos::new(0, next.pos.y() + 1)
} else {
break None;
};