pub row_labels_in_corner: bool,
/// Ranges of column widths in the two heading regions, in 1/96" units.
- pub heading_widths: EnumMap<HeadingRegion, RangeInclusive<usize>>,
+ pub heading_widths: EnumMap<HeadingRegion, RangeInclusive<usize>>,
/// Kind of markers to use for footnotes.
pub footnote_marker_type: FootnoteMarkerType,
}
}
+impl IndexMut<Axis2> for Rect2 {
+ fn index_mut(&mut self, index: Axis2) -> &mut Self::Output {
+ &mut self.0[index]
+ }
+}
+
#[derive(Copy, Clone, Debug, Default)]
pub enum FootnoteMarkerType {
/// a, b, c, ...
#[derive(Clone, Debug)]
pub struct PivotTable {
- look: Arc<Look>,
+ pub look: Arc<Look>,
- rotate_inner_column_labels: bool,
+ pub rotate_inner_column_labels: bool,
- rotate_outer_row_labels: bool,
+ pub rotate_outer_row_labels: bool,
- show_grid_lines: bool,
+ pub show_grid_lines: bool,
- show_title: bool,
+ pub show_title: bool,
- show_caption: bool,
+ pub show_caption: bool,
- show_values: Option<Show>,
+ pub show_values: Option<Show>,
- show_variables: Option<Show>,
+ pub show_variables: Option<Show>,
- weight_format: Format,
+ pub weight_format: Format,
/// Current layer indexes, with `axes[Axis3::Z].dimensions.len()` elements.
/// `current_layer[i]` is an offset into
pub current_layer: Vec<usize>,
/// Column and row sizing and page breaks.
- sizing: EnumMap<Axis2, Sizing>,
+ pub sizing: EnumMap<Axis2, Sizing>,
/// Format settings.
- settings: FormatSettings,
+ pub settings: FormatSettings,
/// Numeric grouping character (usually `.` or `,`).
- grouping: Option<char>,
-
- small: f64,
-
- command_local: Option<String>,
- command_c: Option<String>,
- language: Option<String>,
- locale: Option<String>,
- dataset: Option<String>,
- datafile: Option<String>,
- date: Option<NaiveDateTime>,
- footnotes: Vec<Footnote>,
- title: Option<Value>,
- subtype: Option<Value>,
- corner_text: Option<Value>,
- caption: Option<Value>,
- notes: Option<String>,
- dimensions: Vec<Arc<Dimension>>,
- axes: EnumMap<Axis3, Axis>,
- cells: HashMap<usize, Value>,
+ pub grouping: Option<char>,
+
+ pub small: f64,
+
+ pub command_local: Option<String>,
+ pub command_c: Option<String>,
+ pub language: Option<String>,
+ pub locale: Option<String>,
+ pub dataset: Option<String>,
+ pub datafile: Option<String>,
+ pub date: Option<NaiveDateTime>,
+ pub footnotes: Vec<Footnote>,
+ pub title: Option<Value>,
+ pub subtype: Option<Value>,
+ pub corner_text: Option<Value>,
+ pub caption: Option<Value>,
+ pub notes: Option<String>,
+ pub dimensions: Vec<Arc<Dimension>>,
+ pub axes: EnumMap<Axis3, Axis>,
+ pub cells: HashMap<usize, Value>,
}
impl PivotTable {
+use std::cmp::max;
use std::collections::HashMap;
-use std::ops::RangeInclusive;
+use std::ops::Range;
use std::sync::Arc;
-use enum_map::EnumMap;
+use enum_map::{enum_map, EnumMap};
use smallvec::SmallVec;
-use super::pivot::{Axis2, BorderStyle, Coord2, HeadingRegion, Look, PivotTable, Rect2, Stroke};
+use super::pivot::{Axis2, BorderStyle, Coord2, Look, PivotTable, Rect2, Stroke};
use super::table::{Cell, CellInner, Table};
/// Parameters for rendering a table_item to a device.
/// A page's size is not limited to the size passed in as part of [Params].
/// [Pager] breaks a [Page] into smaller [page]s that will fit in the available
/// space.
+///
+/// # Rendered cells
+///
+/// The horizontal cells rendered are the leftmost `h[X]`, then `r[X]`.
+/// The vertical cells rendered are the topmost `h[Y]`, then `r[Y]`.
+/// `n[i]` is the sum of `h[i]` and `r[i].len()`.
struct Page {
- params: Arc<Params>,
+ device: Arc<dyn Device>,
table: Arc<Table>,
- /// Region of `table` to render.
- ///
- /// The horizontal cells rendered are the leftmost h[H][0], then
- /// r[H][0] through r[H][1], exclusive, then the rightmost h[H][1].
- ///
- /// The vertical cells rendered are the topmost h[V][0], then r[V][0]
- /// through r[V][1], exclusive, then the bottommost h[V][1].
+ /// Size of the table in cells.
///
- /// n[H] = h[H][0] + (r[H][1] - r[H][0]) + h[H][1]
- /// n[V] = h[V][0] + (r[V][1] - r[V][0]) + h[V][1]
n: Coord2,
- /*
- int h[TABLE_N_AXES][2];
- int r[TABLE_N_AXES][2];
- int n[TABLE_N_AXES];*/
+
+ /// Header size. Cells `0..h[X]` are rendered horizontally, and `0..h[Y]` vertically.
+ h: Coord2,
+
+ /// Main region of cells to render.
+ r: Rect2,
+
/// "Cell positions".
///
/// cp[H] represents x positions within the table.
/// When is_edge_cutoff is true for a given edge, the 'overflows' hmap will
/// contain a node for each cell along that edge.
is_edge_cutoff: EnumMap<Axis2, [bool; 2]>,
-
- /// If part of a joined cell would be cut off by breaking a table along
- /// 'axis' at the rule with offset 'z' (where 0 <= z <= n[axis]), then
- /// join_crossing[axis][z] is the thickness of the rule that would be cut
- /// off.
- ///
- /// This is used to know to allocate extra space for breaking at such a
- /// position, so that part of the cell's content is not lost.
- ///
- /// This affects breaking a table only when headers are present. When
- /// headers are not present, the rule's thickness is used for cell content,
- /// so no part of the cell's content is lost (and in fact it is duplicated
- /// across both pages).
- join_crossing: EnumMap<Axis2, usize>,
-
- /// Minimum and maximum widths of columns based on their headings.
- width_ranges: EnumMap<HeadingRegion, RangeInclusive<usize>>,
}
impl Page {
/// The new [Page] will be suitable for rendering on a device whose page
/// size is `params.size`, but the caller is responsible for actually
/// breaking it up to fit on such a device, using the [Break] abstraction.
- fn new(table: &Table, device: &dyn Device, min_width: usize, look: &Look) -> Self {
+ fn new(table: &Arc<Table>, device: &Arc<dyn Device>, min_width: usize, look: &Look) -> Self {
+ use Axis2::*;
+
// Figure out rule widths.
let rules = EnumMap::from_fn(|axis| {
let n = table.n[axis];
(0..n)
- .map(|z| measure_rule(table, device, axis, z))
+ .map(|z| measure_rule(table, &**device, axis, z))
.collect::<Vec<_>>()
});
let heading_widths = look
.heading_widths
.clone()
- .map(|region, range| [*range.start() * px_size, *range.end() * px_size]);
+ .map(|_region, range| [*range.start() * px_size, *range.end() * px_size]);
// Calculate minimum and maximum widths of cells that do not span
// multiple columns.
let mut columns = [vec![0; table.n.x()], vec![0; table.n.x()]];
- for y in 0..table.n[Axis2::Y] {
+ for y in 0..table.n[Y] {
for x in table.iter_x(y) {
let coord = Coord2::new(x, y);
let contents = table.get(coord);
- if !contents.is_top_left(coord) || contents.col_span() != 1 {
+ if !contents.is_top_left() || contents.col_span() != 1 {
continue;
}
let mut w = device.measure_cell_width(contents.inner());
- if let Some(px_size) = device.params().px_size {
+ if device.params().px_size.is_some() {
if let Some(region) = table.heading_region(coord) {
let wr = &heading_widths[region];
if w[0] < wr[0] {
}
// Distribute widths of spanned columns.
- let columns = columns.map(|widths| {
+ let mut columns = columns.map(|widths| {
widths
.into_iter()
.map(|width| Row {
})
.collect::<Vec<_>>()
});
- for y in 0..table.n[Axis2::Y] {
+ for y in 0..table.n[Y] {
for x in table.iter_x(y) {
let coord = Coord2::new(x, y);
let contents = table.get(coord);
- if !contents.is_top_left(coord) || contents.col_span() == 1 {
+ if !contents.is_top_left() || contents.col_span() == 1 {
continue;
}
+ let rect = contents.rect();
let w = device.measure_cell_width(contents.inner());
for i in 0..2 {
- //distribute_spanned_width(w[i],
+ distribute_spanned_width(
+ w[i],
+ &mut columns[i][rect[X].clone()],
+ &rules[X][rect[X].start..rect[X].end + 1],
+ );
}
}
}
+ if min_width > 0 {
+ for i in 0..2 {
+ distribute_spanned_width(min_width, columns[i].as_mut_slice(), &rules[X]);
+ }
+ }
- todo!()
+ // In pathological cases, spans can cause the minimum width of a column
+ // to exceed the maximum width. This bollixes our interpolation
+ // algorithm later, so fix it up.
+ for i in 0..table.n.x() {
+ if columns[0][i].width > columns[1][i].width {
+ columns[1][i].width = columns[0][i].width;
+ }
+ }
+
+ // Decide final column widths.
+ let rule_widths = rules[X].iter().copied().sum::<usize>();
+ let table_widths = columns
+ .iter()
+ .map(|row| row.iter().map(|row| row.width).sum::<usize>() + rule_widths)
+ .collect::<SmallVec<[usize; 2]>>();
+
+ let mut page = if table_widths[1] <= device.params().size[X] {
+ // Fits even with maximum widths. Use them.
+ Self::new_with_exact_widths(device, table, &columns[1], &rules[X])
+ } else if table_widths[0] <= device.params().size[X] {
+ // Fits with minimum widths, so distribute the leftover space.
+ //Self::new_with_interpolated_widths()
+ todo!()
+ } else {
+ // Doesn't fit even with minimum widths. Assign minimums for now, and
+ // later we can break it horizontally into multiple pages.
+ Self::new_with_exact_widths(device, table, &columns[0], &rules[X])
+ };
+
+ // Calculate heights of cells that do not span multiple rows.
+ let mut rows = vec![Row::default(); table.n[Y]];
+ for y in 0..table.n[Y] {
+ let row = &mut rows[y];
+ for x in table.iter_x(y) {
+ let coord = Coord2::new(x, y);
+ let contents = table.get(coord);
+ if !contents.is_top_left() {
+ continue;
+ }
+ let rect = contents.rect();
+
+ if contents.row_span() == 1 {
+ let w = page.joined_width(X, rect[X].clone());
+ let h = device.measure_cell_height(contents.inner(), w);
+ if h > row.unspanned {
+ row.unspanned = h;
+ row.width = h;
+ }
+ }
+ }
+ }
+
+ // Distribute heights of spanned rows.
+ for y in 0..table.n[Y] {
+ for x in table.iter_x(y) {
+ let coord = Coord2::new(x, y);
+ let contents = table.get(coord);
+ if contents.is_top_left() && contents.row_span() > 1 {
+ let rect = contents.rect();
+ let w = page.joined_width(X, rect[X].clone());
+ let h = device.measure_cell_height(contents.inner(), w);
+ distribute_spanned_width(
+ h,
+ &mut rows[rect[Y].clone()],
+ &rules[Y][rect[Y].start..rect[Y].end + 1],
+ );
+ }
+ }
+ }
+
+ // Decide final row heights.
+ page.cp[Y] = Self::accumulate_row_widths(&rows, &rules[Y]);
+
+ // Measure headers. If they are "too big", get rid of them.
+ for axis in [X, Y] {
+ let hw = page.headers_width(axis);
+ let threshold = device.params().size[axis];
+ if hw * 2 >= threshold || hw + page.max_cell_width(axis) > threshold {
+ page.h[axis] = 0;
+ page.r[axis] = 0..page.n[axis];
+ }
+ }
+ page
+ }
+
+ fn accumulate_row_widths(rows: &[Row], rules: &[usize]) -> Vec<usize> {
+ debug_assert_eq!(rows.len() + 1, rules.len());
+ let n = 2 * (rows.len()) + 1;
+ let mut cp = Vec::with_capacity(n);
+ let mut total = 0;
+ cp.push(total);
+ for (rule, row) in rules.iter().zip(rows.iter()) {
+ total += *rule;
+ cp.push(total);
+ total += row.width;
+ cp.push(total);
+ }
+ total += rules.last().unwrap();
+ cp.push(total);
+ debug_assert_eq!(cp.len(), n);
+ cp
+ }
+
+ fn new_with_exact_widths(
+ device: &Arc<dyn Device>,
+ table: &Arc<Table>,
+ rows: &[Row],
+ rules: &[usize],
+ ) -> Self {
+ use Axis2::*;
+ Self {
+ device: device.clone(),
+ table: table.clone(),
+ n: table.n,
+ h: table.h,
+ r: Rect2::new(table.h[X]..table.n[X], table.h[Y]..table.n[Y]),
+ cp: enum_map! {
+ X => Self::accumulate_row_widths(rows, rules),
+ Y => Vec::new(),
+ },
+ overflows: HashMap::new(),
+ is_edge_cutoff: EnumMap::default(),
+ }
+ }
+
+ /// Returns the width of `extent` along `axis`.
+ fn axis_width(&self, axis: Axis2, extent: Range<usize>) -> usize {
+ self.cp[axis][extent.end] - self.cp[axis][extent.start]
+ }
+
+ /// Returns the width of cells within `extent` along `axis`.
+ fn joined_width(&self, axis: Axis2, extent: Range<usize>) -> usize {
+ self.axis_width(
+ axis,
+ Self::cell_ofs(extent.start)..Self::cell_ofs(extent.end) - 1,
+ )
+ }
+
+ /// Returns the width of the headers along `axis`.
+ fn headers_width(&self, axis: Axis2) -> usize {
+ self.axis_width(axis, Self::rule_ofs(0)..Self::cell_ofs(self.h[axis]))
+ }
+
+ /// Returns the width of rule `z` along `axis`.
+ fn rule_width(&self, axis: Axis2, z: usize) -> usize {
+ self.axis_width(axis, Self::rule_ofs(z + 1)..Self::rule_ofs(z + 1))
+ }
+
+ /// Returns the width of rule `z` along `axis`, counting in reverse order.
+ fn rule_width_r(&self, axis: Axis2, z: usize) -> usize {
+ let ofs = self.rule_ofs_r(axis, z);
+ self.axis_width(axis, ofs..ofs + 1)
+ }
+
+ /// Returns the offset in [Self::cp] of the cell with index `cell_index`.
+ /// That is, if `cell_index` is 0, then the offset is 1, that of the leftmost
+ /// or topmost cell; if `cell_index` is 1, then the offset is 3, that of the
+ /// next cell to the right (or below); and so on. */
+ fn cell_ofs(cell_index: usize) -> usize {
+ cell_index * 2 + 1
+ }
+
+ /// Returns the offset in [Self::cp] of the rule with index `rule_index`.
+ /// That is, if `rule_index` is 0, then the offset is that of the leftmost
+ /// or topmost rule; if `rule_index` is 1, then the offset is that of the
+ /// next rule to the right (or below); and so on.
+ fn rule_ofs(rule_index: usize) -> usize {
+ rule_index * 2
+ }
+
+ /// Returns the offset in [Self::cp] of the rule with
+ /// index `rule_index_r`, which counts from the right side (or bottom) of the page
+ /// left (or up), according to `axis`, respectively. That is,
+ /// if `rule_index_r` is 0, then the offset is that of the rightmost or bottommost
+ /// rule; if `rule_index_r` is 1, then the offset is that of the next rule to the left
+ /// (or above); and so on.
+ fn rule_ofs_r(&self, axis: Axis2, rule_index_r: usize) -> usize {
+ (self.n[axis] - rule_index_r) * 2
+ }
+
+ /// Returns the width of cell `z` along `axis`.
+ fn cell_width(&self, axis: Axis2, z: usize) -> usize {
+ let ofs = Self::cell_ofs(z);
+ self.axis_width(axis, ofs..ofs + 1)
+ }
+
+ /// Returns the width of the widest cell, excluding headers, along `axis`.
+ fn max_cell_width(&self, axis: Axis2) -> usize {
+ (self.h[axis]..self.n[axis])
+ .map(|z| self.cell_width(axis, z))
+ .max()
+ .unwrap_or(0)
+ }
+
+ fn width(&self, axis: Axis2) -> usize {
+ *self.cp[axis].last().unwrap()
+ }
+
+ fn get_map(&self, a: Axis2, z: usize) -> Map {
+ if z < self.h[a] {
+ Map {
+ p0: 0,
+ t0: 0,
+ n: self.h[a],
+ }
+ } else {
+ Map {
+ p0: self.h[a],
+ t0: self.r[a].start,
+ n: self.r[a].len(),
+ }
+ }
+ }
+}
+
+/// Maps a contiguous range of cells from a page to the underlying table along
+/// the horizontal or vertical dimension.
+struct Map {
+ /// First ordinate in the page.
+ p0: usize,
+
+ /// First ordinate in the table.
+ t0: usize,
+
+ /// Number of ordinates in page and table.
+ n: usize,
+}
+
+/// Modifies the 'width' members of `rows` so that their sum, when added to rule
+/// widths `rules[1..n - 1]`, where n is rows.len(), is at least `width`.
+///
+/// # Implementation
+///
+/// The algorithm used here is based on the following description from HTML 4:
+///
+/// > For cells that span multiple columns, a simple approach consists of
+/// > apportioning the min/max widths evenly to each of the constituent
+/// > columns. A slightly more complex approach is to use the min/max
+/// > widths of unspanned cells to weight how spanned widths are
+/// > apportioned. Experiments suggest that a blend of the two approaches
+/// > gives good results for a wide range of tables.
+///
+/// We blend the two approaches half-and-half, except that we cannot use the
+/// unspanned weights when 'total_unspanned' is 0 (because that would cause a
+/// division by zero).
+///
+/// The calculation we want to do is this:
+///
+/// ```text
+/// w0 = width / n
+/// w1 = width * (column's unspanned width) / (total unspanned width)
+/// (column's width) = (w0 + w1) / 2
+/// ```
+///
+/// We implement it as a precise calculation in integers by multiplying `w0` and
+/// `w1` by the common denominator of all three calculations (`d`), dividing
+/// that out in the column width calculation, and then keeping the remainder for
+/// the next iteration.
+///
+/// (We actually compute the unspanned width of a column as twice the unspanned
+/// width, plus the width of the rule on the left, plus the width of the rule on
+/// the right. That way each rule contributes to both the cell on its left and
+/// on its right.)
+fn distribute_spanned_width(width: usize, rows: &mut [Row], rules: &[usize]) {
+ debug_assert!(rows.len() >= 2);
+ debug_assert!(rules.len() == rows.len() + 1);
+
+ let n = rows.len();
+ let total_unspanned = rows.iter().map(|row| row.unspanned).sum::<usize>()
+ + (&rules[1..n - 1]).iter().copied().sum::<usize>();
+ if total_unspanned >= width {
+ return;
+ }
+
+ let d0 = rows.len();
+ let d1 = 2 * total_unspanned.max(1);
+ let d = if total_unspanned > 0 {
+ d0 * d1 * 2
+ } else {
+ d0 * d1
+ };
+ let mut w = d / 2;
+ for x in 0..n {
+ w += width * d1;
+ if total_unspanned > 0 {
+ let mut unspanned = rows[x].unspanned * 2;
+ if x < n - 1 {
+ unspanned += rules[x + 1];
+ }
+ if x > 0 {
+ unspanned += rules[x];
+ }
+ w += width * unspanned * d0;
+ }
+ rows[x].width = max(rows[x].width, w / d);
+ w = w.checked_sub(rows[x].width * d).unwrap();
}
}
hw: usize,
}
+impl Break {
+ fn new(page: &Arc<Page>, axis: Axis2) -> Self {
+ Self {
+ page: page.clone(),
+ axis,
+ z: page.h[axis],
+ pixel: 0,
+ hw: page.headers_width(axis),
+ }
+ }
+
+ fn has_next(&self) -> bool {
+ self.z < self.page.n[self.axis]
+ }
+
+ /// Returns the width that would be required along this breaker's axis to
+ /// render a page from the current position up to but not including `cell`.
+ fn needed_size(&self, cell: usize) -> usize {
+ // Width of header not including its rightmost rule.
+ let mut size = self
+ .page
+ .axis_width(self.axis, 0..Page::rule_ofs(self.page.h[self.axis]));
+
+ // If we have a pixel offset and there is no header, then we omit
+ // the leftmost rule of the body. Otherwise the rendering is deceptive
+ // because it looks like the whole cell is present instead of a partial
+ // cell.
+ //
+ // Otherwise (if there is a header) we will be merging two rules: the
+ // rightmost rule in the header and the leftmost rule in the body. We
+ // assume that the width of a merged rule is the larger of the widths of
+ // either rule individually.
+ if self.pixel == 0 || self.page.h[self.axis] > 0 {
+ size += max(
+ self.page.rule_width(self.axis, self.page.h[self.axis]),
+ self.page.rule_width(self.axis, self.z),
+ );
+ }
+
+ // Width of body, minus any pixel offset in the leftmost cell.
+ size += self
+ .page
+ .joined_width(self.axis, self.z..cell)
+ .checked_sub(self.pixel)
+ .unwrap();
+
+ // Width of rightmost rule in body merged with leftmost rule in headers.
+ size += max(
+ self.page.rule_width_r(self.axis, 0),
+ self.page.rule_width(self.axis, cell),
+ );
+
+ size
+ }
+
+ /// Returns a new [Page] that is up to `size` pixels wide along the axis
+ /// used for breaking. Returns `None` if the page has already been
+ /// completely broken up, or if `size` is too small to reasonably render any
+ /// cells. The latter will never happen if `size` is at least as large as
+ /// the page size passed to [Page::new] along the axis using for breaking.
+ fn next(&mut self, size: usize) -> Option<Page> {
+ if !self.has_next() {
+ return None;
+ }
+
+ // A small but visible width.
+ let em = self.page.device.params().font_size[Axis2::X];
+
+ let mut pixel = 0;
+ for z in self.z..self.page.n[self.axis] {
+ let needed = self.needed_size(z + 1);
+ if needed > size {
+ if self.cell_is_breakable(z) {
+ // If there is no right header and we render a partial cell
+ // on the right side of the body, then we omit the rightmost
+ // rule of the body. Otherwise the rendering is deceptive
+ // because it looks like the whole cell is present instead
+ // of a partial cell.
+ //
+ // This is similar to code for the left side in
+ // [Self::needed_size].
+ let rule_allowance = self.page.rule_width(self.axis, z);
+
+ // The amount that, if we added cell `z`, the rendering
+ // would overfill the allocated `size`.
+ let overhang = needed - size - rule_allowance; // XXX could go negative
+
+ // The width of cell `z`.
+ let cell_size = self.page.cell_width(self.axis, z);
+
+ // The amount trimmed off the left side of `z`, and the
+ // amount left to render.
+ let cell_ofs = if z == self.z { self.pixel } else { 0 };
+ let cell_left = cell_size - cell_ofs;
+
+ // If some of the cell remains to render, and there would
+ // still be some of the cell left afterward, then partially
+ // render that much of the cell.
+ let mut pixel = if cell_left > 0 && cell_left > overhang {
+ cell_left - overhang + cell_ofs
+ } else {
+ 0
+ };
+
+ // If there would be only a tiny amount of the cell left
+ // after rendering it partially, reduce the amount rendered
+ // slightly to make the output look a little better.
+ if pixel + em > cell_size {
+ pixel = pixel.saturating_sub(em);
+ }
+
+ // If we're breaking vertically, then consider whether the
+ // cells being broken have a better internal breakpoint than
+ // the exact number of pixels available, which might look
+ // bad e.g. because it breaks in the middle of a line of
+ // text.
+ if self.axis == Axis2::Y && self.page.device.params().can_adjust_break {
+ for x in self.page.table.iter_x(z) {}
+ }
+ }
+ break;
+ }
+ }
+
+ todo!()
+ }
+
+ /// Returns true if `cell` along this breaker's axis may be broken across a
+ /// page boundary.
+ ///
+ /// This is just a heuristic. Breaking cells across page boundaries can
+ /// save space, but it looks ugly.
+ fn cell_is_breakable(&self, cell: usize) -> bool {
+ self.page.cell_width(self.axis, cell) >= self.page.device.params().min_break[self.axis]
+ }
+}
+
pub struct Pager {
- device: Box<dyn Device>,
+ device: Arc<dyn Device>,
scale: f64,
/// [Page]s to be rendered, in order, vertically. There may be up to 5
impl Pager {
pub fn new(
- device: Box<dyn Device>,
+ device: Arc<dyn Device>,
pivot_table: &PivotTable,
layer_indexes: Option<&[usize]>,
) -> Self {
device.params().printing,
);
+ // Figure out the width of the body of the table. Use this to determine
+ // the base scale.
+ let body = Arc::new(output.body);
+ let body_page = Page::new(&body, &device, 0, &pivot_table.look);
+ let body_width = body_page.width(Axis2::X);
+
todo!()
}
}
use crate::output::pivot::Coord2;
-use super::pivot::{
- Area, AreaStyle, Axis2, Border, BorderStyle, CellStyle, FontStyle, HeadingRegion, Rect2, Value,
-};
+use super::pivot::{Area, AreaStyle, Axis2, Border, BorderStyle, HeadingRegion, Rect2, Value};
+
+pub struct CellRef<'a> {
+ coord: Coord2,
+ content: &'a Content,
+}
+
+impl<'a> CellRef<'a> {
+ pub fn inner(&self) -> &CellInner {
+ self.content.inner()
+ }
+
+ pub fn is_empty(&self) -> bool {
+ self.content.is_empty()
+ }
+
+ pub fn rect(&self) -> Rect2 {
+ self.content.rect(self.coord)
+ }
+
+ pub fn next_x(&self) -> usize {
+ self.content.next_x(self.coord.x())
+ }
+
+ pub fn is_top_left(&self) -> bool {
+ self.content.is_top_left(self.coord)
+ }
+
+ pub fn span(&self, axis: Axis2) -> usize {
+ self.content.span(axis)
+ }
+ pub fn col_span(&self) -> usize {
+ self.span(Axis2::X)
+ }
+ pub fn row_span(&self) -> usize {
+ self.span(Axis2::Y)
+ }
+}
#[derive(Clone)]
pub enum Content {
}
}
- pub fn region(&self) -> Option<&Rect2> {
- if let Content::Join(cell) = self {
- Some(&cell.region)
- } else {
- None
+ /// Returns the rectangle that this cell covers, only if the cell contains
+ /// that information. (Joined cells always do, and other cells usually
+ /// don't.)
+ pub fn joined_rect(&self) -> Option<&Rect2> {
+ match self {
+ Content::Join(cell) => Some(&cell.region),
+ _ => None,
+ }
+ }
+
+ /// Returns the rectangle that this cell covers. If the cell doesn't contain
+ /// that information, returns a rectangle containing `coord`.
+ pub fn rect(&self, coord: Coord2) -> Rect2 {
+ match self {
+ Content::Join(cell) => cell.region.clone(),
+ _ => Rect2::for_cell(coord),
}
}
pub fn next_x(&self, x: usize) -> usize {
- self.region().map_or(x + 1, |region| region[Axis2::X].end)
+ self.joined_rect()
+ .map_or(x + 1, |region| region[Axis2::X].end)
}
pub fn is_top_left(&self, coord: Coord2) -> bool {
- self.region().map_or(true, |r| coord == r.top_left())
+ self.joined_rect().map_or(true, |r| coord == r.top_left())
}
- pub fn col_span(&self) -> usize {
- self.region().map_or(1, |r| {
- let range = &r.0[Axis2::X];
+ pub fn span(&self, axis: Axis2) -> usize {
+ self.joined_rect().map_or(1, |r| {
+ let range = &r.0[axis];
range.end - range.start
})
}
+
+ pub fn col_span(&self) -> usize {
+ self.span(Axis2::X)
+ }
+ pub fn row_span(&self) -> usize {
+ self.span(Axis2::Y)
+ }
}
#[derive(Clone)]
/// Occupied table region.
region: Rect2,
- font_style: Option<Box<FontStyle>>,
- cell_style: Option<Box<CellStyle>>,
}
impl Cell {
fn new(inner: CellInner, region: Rect2) -> Self {
- Self {
- inner,
- region,
- font_style: None,
- cell_style: None,
- }
+ Self { inner, region }
}
}
pub n: Coord2,
/// Table header rows and columns.
- pub headers: Coord2,
+ pub h: Coord2,
pub contents: Vec<Content>,
) -> Self {
Self {
n,
- headers,
+ h: headers,
contents: vec![Content::Empty; n.y() * n.x()],
areas,
borders,
pos.x() + self.n.x() * pos.y()
}
- pub fn get(&self, pos: Coord2) -> &Content {
- &self.contents[self.offset(pos)]
+ pub fn get(&self, coord: Coord2) -> CellRef<'_> {
+ CellRef {
+ coord,
+ content: &self.contents[self.offset(coord)],
+ }
}
pub fn get_rule(&self, axis: Axis2, pos: Coord2) -> BorderStyle {
for x in self.iter_x(y) {
let coord = Coord2::new(x, y);
let content = self.get(coord);
- if !content.is_empty() && content.is_top_left(coord) {
+ if !content.is_empty() && content.is_top_left() {
f(content.inner());
}
}
/// The heading region that `pos` is part of, if any.
pub fn heading_region(&self, pos: Coord2) -> Option<HeadingRegion> {
- if pos.x() < self.headers.x() {
+ if pos.x() < self.h.x() {
Some(HeadingRegion::RowHeadings)
- } else if pos.y() < self.headers.y() {
+ } else if pos.y() < self.h.y() {
Some(HeadingRegion::ColumnHeadings)
} else {
None
fn next(&mut self) -> Option<Self::Item> {
let next_x = self
.x
- .map_or(0, |x| self.table.get(Coord2::new(x, self.y)).next_x(x));
+ .map_or(0, |x| self.table.get(Coord2::new(x, self.y)).next_x());
if next_x >= self.table.n.x() {
None
} else {