-use std::cmp::max;
+use std::cmp::{max, min};
use std::collections::HashMap;
use std::iter::once;
use std::ops::Range;
use smallvec::SmallVec;
use super::pivot::{Axis2, BorderStyle, Coord2, Look, PivotTable, Rect2, Stroke};
-use super::table::{Cell, CellInner, Table};
+use super::table::{Cell, CellInner, Content, Table};
/// Parameters for rendering a table_item to a device.
///
/// Returns the height required to render `cell` given a width of `width`.
fn measure_cell_height(&self, cell: &CellInner, width: usize) -> usize;
- /// Given that there is space measuring `width` by `height` to render
- /// `cell`, where `height` is insufficient to render the entire height of
- /// the cell, returns the largest height less than `height` at which it is
+ /// Given that there is space measuring `size` to render `cell`, where
+ /// `size.y()` is insufficient to render the entire height of the cell,
+ /// returns the largest height less than `size.y()` at which it is
/// appropriate to break the cell. For example, if breaking at the
- /// specified `height` would break in the middle of a line of text, the
+ /// specified `size.y()` would break in the middle of a line of text, the
/// return value would be just sufficiently less that the breakpoint would
/// be between lines of text.
///
/// Optional. If [RenderParams::can_adjust_break] is false, the rendering
/// engine assumes that all breakpoints are acceptable.
- fn adjust_break(&self, cell: &Cell, size: Coord2) -> usize;
+ fn adjust_break(&self, cell: &Content, size: Coord2) -> usize;
/// Draws a generalized intersection of lines in `bb`.
///
axis_width(cp, ofs..ofs + 1)
}
+#[derive(Clone)]
+pub struct RenderCell<'a> {
+ rect: Rect2,
+ content: &'a Content,
+}
+
impl Page {
/// Creates and returns a new [Page] for rendering `table` with the given
/// `look` on `device`.
/// 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: &Arc<Table>, device: &Arc<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::*;
let n = table.n.clone();
// Figure out rule widths.
let rules = EnumMap::from_fn(|axis| {
(0..n[axis])
- .map(|z| measure_rule(table, &**device, axis, z))
+ .map(|z| measure_rule(&*table, &*device, axis, z))
.collect::<Vec<_>>()
});
let r = Rect2::new(h[X]..n[X], h[Y]..n[Y]);
let maps = Self::new_mappings(h, &r);
Self {
- device: device.clone(),
- table: table.clone(),
+ device,
+ table,
n,
h,
r,
*self.cp[axis].last().unwrap()
}
- /// XXX This could return a
- 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(),
- }
- }
- }
-
fn new_mappings(h: Coord2, r: &Rect2) -> EnumMap<Axis2, [Map; 2]> {
EnumMap::from_fn(|axis| {
[
Map {
p0: 0,
t0: 0,
+ ofs: 0,
n: h[axis],
},
Map {
p0: h[axis],
t0: r[axis].start,
+ ofs: r[axis].start - h[axis],
n: r[axis].len(),
},
]
})
}
- /*
- fn get_cell(&self, coord: Coord2) -> RenderCell<'_> {
+ fn get_map(&self, axis: Axis2, ordinate: usize) -> &Map {
+ if ordinate < self.h[axis] {
+ &self.maps[axis][0]
+ } else {
+ &self.maps[axis][1]
+ }
+ }
- }*/
+ fn get_cell(&self, coord: Coord2) -> RenderCell<'_> {
+ let maps = EnumMap::from_fn(|axis| self.get_map(axis, coord[axis]));
+ let coord = Coord2(coord.0.map(|axis, ordinate| ordinate + maps[axis].ofs));
+ let cell = self.table.get(coord);
+ RenderCell {
+ rect: Rect2(cell.rect().0.map(|axis, Range { start, end }| {
+ let m = &maps[axis];
+ max(m.p0, start - m.ofs)..min(m.p0 + m.n, end - m.ofs)
+ })),
+ content: cell.content,
+ }
+ }
- /// Creates and returns a new [Page] whose contents are a subregion of
- /// thispage's contents. The new page includes cells `extent` (exclusive)
- /// along `axis`, plus any headers on `axis`.
+ /// Creates and returns a new [Page] whose contents are a subregion of this
+ /// page's contents. The new page includes cells `extent` (exclusive) along
+ /// `axis`, plus any headers on `axis`.
///
/// If `pixel0` is nonzero, then it is a number of pixels to exclude from
/// the left or top (according to `axis`) of cell `extent.start`.
/// render cells that are too large to fit on a single page.)
///
/// The whole of axis `!axis` is included. (The caller may follow up with
- /// another call to select on `!axis` to select on that axis as well.)
+ /// another call to select on `!axis`.)
fn select(
self: &Arc<Self>,
- axis: Axis2,
+ a: Axis2,
extent: Range<usize>,
pixel0: usize,
pixel1: usize,
) -> Arc<Self> {
+ let b = !a;
let z0 = extent.start;
let z1 = extent.end;
// If all of the page is selected, just make a copy.
- if z0 == self.h[axis] && z1 == self.n[axis] && pixel0 == 0 && pixel1 == 0 {
+ if z0 == self.h[a] && z1 == self.n[a] && pixel0 == 0 && pixel1 == 0 {
return self.clone();
}
// Figure out `n`, `h`, `r` for the subpage.
- let trim = [z0 - self.h[axis], self.n[axis] - z1];
+ let trim = [z0 - self.h[a], self.n[a] - z1];
let mut n = self.n;
- n[axis] -= trim[0] + trim[1];
- let mut h = self.h;
+ n[a] -= trim[0] + trim[1];
+ let h = self.h;
let mut r = self.r.clone();
- r[axis].start += trim[0];
- r[axis].end -= trim[1];
+ r[a].start += trim[0];
+ r[a].end -= trim[1];
// An edge is cut off if it was cut off in `self` or if we're trimming
// pixels off that side of the page and there are no headers.
let mut is_edge_cutoff = self.is_edge_cutoff.clone();
- is_edge_cutoff[axis][0] =
- h[axis] == 0 && (pixel0 > 0 || (z0 == 0 && self.is_edge_cutoff[axis][0]));
- is_edge_cutoff[axis][1] =
- pixel1 > 0 || (z1 == self.n[axis] && self.is_edge_cutoff[axis][1]);
+ is_edge_cutoff[a][0] = h[a] == 0 && (pixel0 > 0 || (z0 == 0 && self.is_edge_cutoff[a][0]));
+ is_edge_cutoff[a][1] = pixel1 > 0 || (z1 == self.n[a] && self.is_edge_cutoff[a][1]);
// Select widths from `self` into subpage.
- let scp = self.cp[axis].as_slice();
- let mut dcp = Vec::with_capacity(2 * n[axis] + 1);
+ let scp = self.cp[a].as_slice();
+ let mut dcp = Vec::with_capacity(2 * n[a] + 1);
dcp.push(0);
let mut total = 0;
- for z in 0..=rule_ofs(h[axis]) {
- total += if z == 0 && is_edge_cutoff[axis][0] {
+ for z in 0..=rule_ofs(h[a]) {
+ total += if z == 0 && is_edge_cutoff[a][0] {
0
} else {
scp[z + 1] - scp[z]
}
dcp.push(total);
}
- let z = self.rule_ofs_r(axis, 0);
- if !is_edge_cutoff[axis][1] {
+ let z = self.rule_ofs_r(a, 0);
+ if !is_edge_cutoff[a][1] {
total += scp[z + 1] - scp[z];
}
dcp.push(total);
- debug_assert_eq!(dcp.len(), 2 * n[axis] + 1);
+ debug_assert_eq!(dcp.len(), 2 * n[a] + 1);
let mut cp = EnumMap::default();
- cp[axis] = dcp;
- cp[!axis] = self.cp[!axis].clone();
+ cp[a] = dcp;
+ cp[!a] = self.cp[!a].clone();
+
+ let mut overflows = HashMap::new();
// Add new overflows.
- let s = Selection;
- if self.h[axis] == 0 || z0 > self.h[axis] || pixel0 > 0 {
- let b = !axis;
+ let s = Selection {
+ a,
+ b,
+ h,
+ z0,
+ z1,
+ p0: pixel0,
+ p1: pixel1,
+ };
+ if self.h[a] == 0 || z0 > self.h[a] || pixel0 > 0 {
let mut z = 0;
while z < self.n[b] {
- let d = Coord2::for_axis((axis, z0), z);
+ let d = Coord2::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);
+ if overflow0 || overflow1 {
+ let mut overflow = self.overflows.get(&d).cloned().unwrap_or_default();
+ if overflow0 {
+ overflow[a][0] +=
+ pixel0 + self.axis_width(a, cell_ofs(cell.rect[a].start)..cell_ofs(z0));
+ }
+ if overflow1 {
+ overflow[a][1] +=
+ pixel1 + self.axis_width(a, cell_ofs(z1)..cell_ofs(cell.rect[a].end));
+ }
+ assert!(overflows
+ .insert(s.coord_to_subpage(cell.rect.top_left()), overflow)
+ .is_none());
+ }
+ z += cell.rect[b].len();
}
}
- todo!()
+ let mut z = 0;
+ while z < self.n[b] {
+ let d = Coord2::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)
+ && overflows
+ .get(&s.coord_to_subpage(cell.rect.top_left()))
+ .is_none()
+ {
+ let mut overflow = self.overflows.get(&d).cloned().unwrap_or_default();
+ overflow[a][1] +=
+ pixel1 + self.axis_width(a, cell_ofs(z1)..cell_ofs(cell.rect[a].end));
+ assert!(overflows
+ .insert(s.coord_to_subpage(cell.rect.top_left()), overflow)
+ .is_none());
+ }
+ z += cell.rect[b].len();
+ }
+
+ // Copy overflows from `self` into the subpage.
+ // XXX this could be done at the start, which would simplify the while loops above
+ for (coord, overflow) in self.overflows.iter() {
+ let cell = self.table.get(*coord);
+ let rect = cell.rect();
+ if rect[a].end > z0 && rect[a].start < z1 {
+ overflows
+ .entry(s.coord_to_subpage(rect.top_left()))
+ .or_insert(overflow.clone());
+ }
+ }
+
+ let maps = Self::new_mappings(h, &r);
+ Arc::new(Self {
+ device: self.device.clone(),
+ table: self.table.clone(),
+ n,
+ h,
+ r,
+ maps,
+ cp,
+ overflows,
+ is_edge_cutoff,
+ })
}
+
+ fn table_width(&self, axis: Axis2) -> usize {
+ self.cp[axis].last().copied().unwrap()
+ }
+}
+
+struct Selection {
+ a: Axis2,
+ b: Axis2,
+ z0: usize,
+ z1: usize,
+ p0: usize,
+ p1: usize,
+ h: Coord2,
}
-struct Selection;
+impl Selection {
+ /// Returns the coordinates of `coord` as it will appear in this subpage.
+ ///
+ /// `coord` must be in the selected region or the results will not make
+ /// sense.
+ fn coord_to_subpage(&self, coord: Coord2) -> Coord2 {
+ let a = self.a;
+ let b = self.b;
+ let ha0 = self.h[a];
+ Coord2::for_axis((a, max(coord[a] + ha0 - self.z0, ha0)), coord[b])
+ }
+}
/// Maps a contiguous range of cells from a page to the underlying table along
/// the horizontal or vertical dimension.
/// First ordinate in the table.
t0: usize,
+ /// `t0 - p0`.
+ ofs: usize,
+
/// Number of ordinates in page and table.
n: usize,
}
}
impl Break {
- fn new(page: &Arc<Page>, axis: Axis2) -> Self {
+ fn new(page: Arc<Page>, axis: Axis2) -> Self {
+ let z = page.h[axis];
+ let hw = page.headers_width(axis);
Self {
- page: page.clone(),
+ page,
axis,
- z: page.h[axis],
+ z,
pixel: 0,
- hw: page.headers_width(axis),
+ hw,
}
}
/// 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> {
+ fn next(&mut self, size: usize) -> Option<Arc<Page>> {
if !self.has_next() {
return None;
}
- // A small but visible width.
- let em = self.page.device.params().font_size[Axis2::X];
+ self.find_breakpoint(size).map(|(z, pixel)| match pixel {
+ 0 => self.page.select(self.axis, self.z..z, self.pixel, 0),
+ pixel => self.page.select(
+ self.axis,
+ self.z..z + 1,
+ pixel,
+ self.page.cell_width(self.axis, z) - pixel,
+ ),
+ })
+ }
- 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);
- }
+ /// Returns a small but visible width.
+ fn em(&self) -> usize {
+ self.page.device.params().font_size[Axis2::X]
+ }
- // 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) {}
+ fn break_cell(&self, z: usize, overflow: usize) -> usize {
+ 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 = overflow - 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.
+ let em = self.em();
+ 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 {
+ let mut x = 0;
+ while x < self.page.n[Axis2::X] {
+ let cell = self.page.get_cell(Coord2::new(x, z));
+ let better_pixel = self.page.device.adjust_break(
+ cell.content,
+ Coord2::new(
+ self.page
+ .joined_width(Axis2::X, cell.rect[Axis2::X].clone()),
+ pixel,
+ ),
+ );
+ x += cell.rect[Axis2::X].len();
+
+ if better_pixel < pixel {
+ let start_pixel = if z > self.z { self.pixel } else { 0 };
+ if better_pixel > start_pixel {
+ pixel = better_pixel;
+ break;
+ } else if better_pixel == 0 && z != self.z {
+ pixel = 0;
+ break;
+ }
}
}
- break;
}
+
+ pixel
+ } else {
+ 0
}
+ }
- todo!()
+ fn find_breakpoint(&mut self, size: usize) -> Option<(usize, usize)> {
+ for z in self.z..self.page.n[self.axis] {
+ let needed = self.needed_size(z + 1);
+ if needed > size {
+ let pixel = self.break_cell(z, needed - size);
+ if z == self.z && pixel == 0 {
+ return None;
+ } else {
+ return Some((z, pixel));
+ }
+ }
+ }
+ Some((self.page.n[self.axis], 0))
}
/// Returns true if `cell` along this breaker's axis may be broken across a
/// [Page]s to be rendered, in order, vertically. There may be up to 5
/// pages, for the pivot table's title, layers, body, captions, and
/// footnotes.
- pages: SmallVec<[Box<Page>; 5]>,
+ pages: SmallVec<[Arc<Page>; 5]>,
- cur_page: usize,
- x_break: Break,
- y_break: Break,
+ x_break: Option<Break>,
+ y_break: Option<Break>,
}
impl Pager {
// 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_page = Page::new(Arc::new(output.body), device.clone(), 0, &pivot_table.look);
let body_width = body_page.width(Axis2::X);
+ let mut scale = if body_width > device.params().size[Axis2::X]
+ && pivot_table.look.shrink_to_fit[Axis2::X]
+ && device.params().can_scale
+ {
+ device.params().size[Axis2::X] as f64 / body_width as f64
+ } else {
+ 1.0
+ };
- todo!()
+ let mut pages = SmallVec::new();
+ for table in [output.title, output.layers].into_iter().flatten() {
+ pages.push(Arc::new(Page::new(
+ Arc::new(table),
+ device.clone(),
+ body_width,
+ &pivot_table.look,
+ )));
+ }
+ pages.push(Arc::new(body_page));
+ for table in [output.caption, output.footnotes].into_iter().flatten() {
+ pages.push(Arc::new(Page::new(
+ Arc::new(table),
+ device.clone(),
+ 0,
+ &pivot_table.look,
+ )));
+ }
+ pages.reverse();
+
+ // If we're shrinking tables to fit the page length, then adjust the
+ // scale factor.
+ //
+ // XXX This will sometimes shrink more than needed, because adjusting
+ // the scale factor allows for cells to be "wider", which means that
+ // sometimes they won't break across as much vertical space, thus
+ // shrinking the table vertically more than the scale would imply.
+ // Shrinking only as much as necessary would require an iterative
+ // search.
+ 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))
+ .sum::<usize>() as f64;
+ let max_height = device.params().size[Axis2::Y] as f64;
+ if total_height * scale >= max_height {
+ scale *= max_height / total_height;
+ }
+ }
+
+ Self {
+ device,
+ scale,
+ pages,
+ x_break: None,
+ y_break: None,
+ }
+ }
+
+ /// True if there's content left to rnder.
+ fn has_next(&mut self) -> bool {
+ while self
+ .y_break
+ .as_mut()
+ .is_none_or(|y_break| !y_break.has_next())
+ {
+ self.y_break = self
+ .x_break
+ .as_mut()
+ .and_then(|x_break| {
+ x_break.next(
+ (self.device.params().size[Axis2::X] as f64 / self.scale as f64) as usize,
+ )
+ })
+ .map(|page| Break::new(page, Axis2::Y));
+ if self.y_break.is_none() {
+ match self.pages.pop() {
+ Some(page) => self.x_break = Some(Break::new(page, Axis2::X)),
+ _ => return false,
+ }
+ }
+ }
+ false
}
}