use std::cmp::{max, min};
use std::iter::{once, zip};
use std::ops::Range;
use std::sync::Arc;
use enum_map::{Enum, EnumMap, enum_map};
use itertools::{Itertools, interleave};
use num::Integer;
use smallvec::SmallVec;
use crate::output::pivot::look::Color;
use crate::output::{
pivot::{
Axis2, Coord2, PivotTable, Rect2,
look::{BorderStyle, Look, Stroke, VertAlign},
},
table::{CellPos, CellRect, Content, DrawCell, Table},
};
pub struct Params {
pub size: Coord2,
pub font_size: EnumMap<Axis2, isize>,
pub line_widths: EnumMap<Stroke, isize>,
pub px_size: Option<isize>,
pub min_break: EnumMap<Axis2, isize>,
pub supports_margins: bool,
pub rtl: bool,
pub printing: bool,
pub can_adjust_break: bool,
pub can_scale: bool,
}
impl Params {
fn em(&self) -> isize {
self.font_size[Axis2::X]
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Enum)]
pub enum Extreme {
Min,
Max,
}
pub trait Device {
fn params(&self) -> &Params;
fn measure_cell_width(&self, cell: &DrawCell) -> EnumMap<Extreme, isize>;
fn measure_cell_height(&self, cell: &DrawCell, width: isize) -> isize;
fn adjust_break(&self, cell: &Content, size: Coord2) -> isize;
fn draw_line(&mut self, bb: Rect2, styles: EnumMap<Axis2, [BorderStyle; 2]>, bg: Color);
fn draw_cell(
&mut self,
draw_cell: &DrawCell,
bb: Rect2,
valign_offset: isize,
spill: EnumMap<Axis2, [isize; 2]>,
clip: &Rect2,
);
fn scale(&mut self, factor: f64);
}
#[derive(Debug)]
struct RenderedTable {
table: Table,
cp: EnumMap<Axis2, Vec<isize>>,
}
impl RenderedTable {
fn new(table: Table, device: &dyn Device, min_width: Option<isize>, look: &Look) -> Self {
use Axis2::*;
use Extreme::*;
let n = table.n;
let rules = EnumMap::from_fn(|axis| {
(0..=n[axis])
.map(|z| measure_rule(device, &table, axis, z))
.collect::<Vec<_>>()
});
let px_size = device.params().px_size.unwrap_or_default();
let heading_widths = look.heading_widths.clone().map(|_region, range| {
enum_map![
Min => *range.start() * px_size,
Max => *range.end() * px_size
]
});
let mut unspanned_columns = EnumMap::from_fn(|_| vec![0; n.x]);
for cell in table.cells().filter(|cell| cell.col_span() == 1) {
let mut w = device.measure_cell_width(&DrawCell::new(cell.inner(), &table));
if device.params().px_size.is_some() {
if let Some(region) = table.heading_region(cell.pos) {
let wr = &heading_widths[region];
if w[Min] < wr[Min] {
w[Min] = wr[Min];
if w[Min] > w[Max] {
w[Max] = w[Min];
}
} else if w[Max] > wr[Max] {
w[Max] = wr[Max];
if w[Max] < w[Min] {
w[Min] = w[Max];
}
}
}
}
let x = cell.pos[X];
for ext in [Min, Max] {
if unspanned_columns[ext][x] < w[ext] {
unspanned_columns[ext][x] = w[ext];
}
}
}
let mut columns = unspanned_columns.clone();
for cell in table.cells().filter(|cell| cell.col_span() > 1) {
let rect = cell.rect();
let w = device.measure_cell_width(&DrawCell::new(cell.inner(), &table));
for ext in [Min, Max] {
distribute_spanned_width(
w[ext],
&unspanned_columns[ext][rect.x.clone()],
&mut columns[ext][rect.x.clone()],
&rules[X][rect.x.start..rect.x.end + 1],
);
}
}
if let Some(min_width) = min_width
&& min_width > 0
{
for ext in [Min, Max] {
distribute_spanned_width(
min_width,
&unspanned_columns[ext],
&mut columns[ext],
&rules[X],
);
}
}
for i in 0..n.x {
if columns[Min][i] > columns[Max][i] {
columns[Max][i] = columns[Min][i];
}
}
let rule_widths = rules[X].iter().copied().sum::<isize>();
let table_widths = EnumMap::from_fn(|ext| columns[ext].iter().sum::<isize>() + rule_widths);
let cp_x = if table_widths[Max] <= device.params().size[X] {
Self::use_row_widths(&columns[Max], &rules[X])
} else if device.params().size[X] > table_widths[Min] {
Self::interpolate_column_widths(
device.params().size[Axis2::X],
&columns,
&table_widths,
&rules[X],
)
} else {
Self::use_row_widths(&columns[Min], &rules[X])
};
let mut unspanned_rows = vec![0; n[Y]];
for cell in table.cells().filter(|cell| cell.row_span() == 1) {
let rect = cell.rect();
let w = joined_width(&cp_x, rect.x.clone());
let h = device.measure_cell_height(&DrawCell::new(cell.inner(), &table), w);
let row = &mut unspanned_rows[cell.pos.y];
if h > *row {
*row = h;
}
}
let mut rows = unspanned_rows.clone();
for cell in table.cells().filter(|cell| cell.row_span() > 1) {
let rect = cell.rect();
let w = joined_width(&cp_x, rect.x.clone());
let h = device.measure_cell_height(&DrawCell::new(cell.inner(), &table), w);
distribute_spanned_width(
h,
&unspanned_rows[rect.y.clone()],
&mut rows[rect.y.clone()],
&rules[Y][rect.y.start..rect.y.end + 1],
);
}
let cp_y = Self::use_row_widths(&rows, &rules[Y]);
let mut h = table.h;
for (axis, cp) in [(X, cp_x.as_slice()), (Y, cp_y.as_slice())] {
let header_width = axis_width(cp, 0..table.h[axis]);
let max_cell_width = (table.h[axis]..n[axis])
.map(|z| cell_width(cp, z))
.max()
.unwrap_or(0);
let threshold = device.params().size[axis];
if header_width * 2 >= threshold || header_width + max_cell_width > threshold {
h[axis] = 0;
}
}
Self {
table,
cp: Axis2::new_enum(cp_x, cp_y),
}
}
fn h(&self) -> CellPos {
self.table.h
}
fn n(&self) -> CellPos {
self.table.n
}
fn use_row_widths(rows: &[isize], rules: &[isize]) -> Vec<isize> {
let mut vec = once(0)
.chain(interleave(rules, rows).copied())
.collect::<Vec<_>>();
for i in 1..vec.len() {
vec[i] += vec[i - 1]
}
vec
}
fn interpolate_column_widths(
target: isize,
columns: &EnumMap<Extreme, Vec<isize>>,
widths: &EnumMap<Extreme, isize>,
rules: &[isize],
) -> Vec<isize> {
use Extreme::*;
let avail = target - widths[Min];
let wanted = widths[Max] - widths[Min];
let mut w = wanted / 2;
let rows_mid = zip(columns[Min].iter().copied(), columns[Max].iter().copied())
.map(|(min, max)| {
w += avail * (max - min);
let extra = w / wanted;
w -= extra * wanted;
min + extra
})
.collect::<Vec<_>>();
Self::use_row_widths(&rows_mid, rules)
}
fn axis_width(&self, axis: Axis2, extent: Range<usize>) -> isize {
axis_width(&self.cp[axis], extent)
}
fn joined_width(&self, axis: Axis2, extent: Range<usize>) -> isize {
joined_width(&self.cp[axis], extent)
}
fn headers_width(&self, axis: Axis2) -> isize {
self.axis_width(axis, rule_ofs(0)..cell_ofs(self.h()[axis]))
}
fn rule_width(&self, axis: Axis2, z: usize) -> isize {
let ofs = rule_ofs(z);
self.axis_width(axis, ofs..ofs + 1)
}
fn rule_width_r(&self, axis: Axis2, z: usize) -> isize {
let ofs = self.rule_ofs_r(axis, z);
self.axis_width(axis, ofs..ofs + 1)
}
fn rule_ofs_r(&self, axis: Axis2, rule_index_r: usize) -> usize {
(self.table.n[axis] - rule_index_r) * 2
}
fn cell_width(&self, axis: Axis2, z: usize) -> isize {
let ofs = cell_ofs(z);
self.axis_width(axis, ofs..ofs + 1)
}
fn max_cell_width(&self, axis: Axis2) -> isize {
(self.h()[axis]..self.n()[axis])
.map(|z| self.cell_width(axis, z))
.max()
.unwrap_or(0)
}
}
#[derive(Clone, Debug)]
struct Page {
table: Arc<RenderedTable>,
ranges: EnumMap<Axis2, Range<isize>>,
}
impl Page {
pub fn new(table: Table, device: &dyn Device, min_width: Option<isize>, look: &Look) -> Self {
let table = Arc::new(RenderedTable::new(table, device, min_width, look));
let ranges = EnumMap::from_fn(|axis| {
table.cp[axis][1 + table.h()[axis] * 2]..table.cp[axis].last().copied().unwrap()
});
Self { table, ranges }
}
pub fn split(&self, axis: Axis2) -> Break {
Break::new(self.clone(), axis)
}
fn width(&self, axis: Axis2) -> isize {
self.table.cp[axis].last().copied().unwrap()
}
fn draw(&self, device: &mut dyn Device, ofs: Coord2) {
fn overlap(a: &Range<isize>, b: &Range<isize>) -> bool {
a.contains(&b.start) || b.contains(&a.start)
}
use Axis2::*;
let cp = &self.table.cp;
let headers = Coord2::from_fn(|a| self.table.headers_width(a));
for (y, yr) in self.table.cp[Y]
.iter()
.copied()
.tuple_windows()
.map(|(y0, y1)| y0..y1)
.enumerate()
.filter_map(|(y, yr)| if y % 2 == 1 { Some((y / 2, yr)) } else { None })
{
if yr.start >= headers[Y] && !overlap(&yr, &self.ranges[Y]) {
continue;
}
for (x, xr) in self.table.cp[X]
.iter()
.copied()
.tuple_windows()
.map(|(x0, x1)| x0..x1)
.enumerate()
.filter_map(|(x, xr)| if x % 2 == 1 { Some((x / 2, xr)) } else { None })
{
if xr.start >= headers[X] && !overlap(&xr, &self.ranges[X]) {
continue;
}
let cell = self.table.table.get(CellPos { x, y });
let rect = cell.rect();
let mut bb = Rect2::from_fn(|a| {
cp[a][rect[a].start * 2 + 1]..cp[a][(rect[a].end - 1) * 2 + 2]
});
let mut clip = if y < self.table.h().y {
if x < self.table.h().x {
bb.clone()
} else {
Rect2::new(
max(bb[X].start, self.ranges[X].start)
..min(bb[X].end, self.ranges[X].end),
bb[Y].clone(),
)
}
} else if x < self.table.h().x {
Rect2::new(
bb[X].clone(),
max(bb[Y].start, self.ranges[Y].start)..min(bb[Y].end, self.ranges[Y].end),
)
} else {
Rect2::from_fn(|a| {
max(bb[a].start, self.ranges[a].start)..min(bb[a].end, self.ranges[a].end)
})
};
if clip[X].start >= clip[X].end || clip[Y].start >= clip[Y].end {
continue;
}
for a in [X, Y] {
if bb[a].start >= self.ranges[a].start {
let h = self.ranges[a].start - self.table.headers_width(a);
bb[a].start -= h;
bb[a].end -= h;
clip[a].start -= h;
clip[a].end -= h;
}
}
let draw_cell = DrawCell::new(cell.content.inner(), &self.table.table);
let valign_offset = match draw_cell.cell_style.vert_align {
VertAlign::Top => 0,
VertAlign::Middle => self.extra_height(device, &bb, &draw_cell) / 2,
VertAlign::Bottom => self.extra_height(device, &bb, &draw_cell),
};
device.draw_cell(
&draw_cell,
bb.translate(ofs),
valign_offset,
EnumMap::from_fn(|_| [0, 0]),
&clip.translate(ofs),
)
}
}
for (y, yr) in self.table.cp[Y]
.iter()
.copied()
.tuple_windows()
.map(|(y0, y1)| y0..y1)
.enumerate()
{
for (x, xr) in self.table.cp[X]
.iter()
.copied()
.tuple_windows()
.map(|(x0, x1)| x0..x1)
.enumerate()
.filter(|(x, _)| *x % 2 == 0 || y % 2 == 0)
{
let mut bb = Rect2::new(xr.clone(), yr.clone());
let h = self.table.headers_width(X);
if xr.start < h {
} else if self.ranges[X].contains(&xr.start) {
bb[X].start -= self.ranges[X].start - h;
bb[X].end -= self.ranges[X].start - h;
} else {
continue;
}
let h = self.table.headers_width(Y);
if yr.start < h {
} else if self.ranges[Y].contains(&yr.start) {
bb[Y].start -= self.ranges[Y].start - h;
bb[Y].end -= self.ranges[Y].start - h;
} else {
continue;
}
let bg = if !self.table.table.is_empty() {
let x = (x / 2).min(self.table.n().x - 1);
let y = (y / 2).min(self.table.n().y - 1);
let cell = self.table.table.get(CellPos::new(x, y));
let area = cell.inner().area;
self.table.table.areas[area].font_style.bg
} else {
Color::WHITE
};
self.draw_rule(device, ofs, CellPos { x, y }, bb, bg);
}
}
}
fn draw_rule(
&self,
device: &mut dyn Device,
ofs: Coord2,
coord: CellPos,
bb: Rect2,
bg: Color,
) {
let styles = EnumMap::from_fn(|a: Axis2| {
let b = !a;
if !is_rule(coord[a]) {
[None, None]
} else if is_rule(coord[b]) {
let first = if coord[b] > 0 {
let mut e = coord;
e[b] -= 1;
self.get_rule(a, e)
} else {
None
};
let second = if coord[b] / 2 < self.table.n()[b] {
self.get_rule(a, coord)
} else {
None
};
[first, second]
} else {
let rule = self.get_rule(a, coord);
[rule, rule]
}
});
if styles.values().any(|[a, b]| a.is_some() || b.is_some()) {
const NO_BORDER: BorderStyle = BorderStyle::none();
let styles = styles.map(|_, [a, b]| [a.unwrap_or(NO_BORDER), b.unwrap_or(NO_BORDER)]);
device.draw_line(bb.translate(ofs), styles, bg);
}
}
fn get_rule(&self, a: Axis2, coord: CellPos) -> Option<BorderStyle> {
let coord = CellPos::from_fn(|a| coord[a] / 2);
self.table.table.get_rule(a, coord)
}
fn extra_height(&self, device: &dyn Device, bb: &Rect2, cell: &DrawCell) -> isize {
use Axis2::*;
let height = device.measure_cell_height(cell, bb[X].len() as isize);
bb[Y].len() as isize - height
}
}
fn axis_width(cp: &[isize], extent: Range<usize>) -> isize {
cp[extent.end] - cp[extent.start]
}
fn joined_width(cp: &[isize], extent: Range<usize>) -> isize {
axis_width(cp, cell_ofs(extent.start)..cell_ofs(extent.end) - 1)
}
fn cell_ofs(cell_index: usize) -> usize {
cell_index * 2 + 1
}
fn rule_ofs(rule_index: usize) -> usize {
rule_index * 2
}
fn cell_width(cp: &[isize], z: usize) -> isize {
let ofs = cell_ofs(z);
axis_width(cp, ofs..ofs + 1)
}
fn is_rule(z: usize) -> bool {
z.is_even()
}
#[derive(Clone)]
pub struct RenderCell<'a> {
rect: CellRect,
content: &'a Content,
}
struct Selection {
a: Axis2,
b: Axis2,
z0: usize,
z1: usize,
p0: isize,
p1: isize,
h: CellPos,
}
impl Selection {
fn coord_to_subpage(&self, coord: CellPos) -> CellPos {
let a = self.a;
let b = self.b;
let ha0 = self.h[a];
let z = coord[a];
let z_subpage = if (0..ha0).contains(&z) {
z
} else if (self.z0..self.z1).contains(&z) {
z - self.z0 + ha0
} else {
unreachable!("{z} is not in {:?} or {:?}", 0..ha0, self.z0..self.z1);
};
CellPos::for_axis((a, z_subpage), coord[b])
}
}
#[derive(Copy, Clone, Debug)]
struct Map {
p0: usize,
t0: usize,
ofs: usize,
n: usize,
}
fn distribute_spanned_width(
width: isize,
unspanned: &[isize],
spanned: &mut [isize],
rules: &[isize],
) {
let n = unspanned.len();
if n == 0 {
return;
}
debug_assert_eq!(spanned.len(), n);
debug_assert_eq!(rules.len(), n + 1);
let total_unspanned = unspanned.iter().sum::<isize>()
+ rules
.get(1..n)
.map_or(0, |rules| rules.iter().copied().sum::<isize>());
if total_unspanned >= width {
return;
}
let d0 = n as isize;
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 = unspanned[x] * 2;
if x + 1 < n {
unspanned += rules[x + 1];
}
if x > 0 {
unspanned += rules[x];
}
w += width * unspanned * d0;
}
spanned[x] = max(spanned[x], w / d);
w = w.checked_sub(spanned[x] * d).unwrap();
}
}
fn measure_rule(device: &dyn Device, table: &Table, a: Axis2, z: usize) -> isize {
let b = !a;
let mut rules = EnumMap::default();
for w in 0..table.n[b] {
if let Some(border) = table.get_rule(a, CellPos::for_axis((a, z), w)) {
rules[border.stroke] = true;
}
}
if rules[Stroke::None] {
rules[Stroke::None] = false;
if z > 0 && z < table.n[a] && !device.params().supports_margins && a == Axis2::X {
rules[Stroke::Solid] = true;
}
}
let line_widths = &device.params().line_widths;
rules
.into_iter()
.map(
|(rule, present)| {
if present { line_widths[rule] } else { 0 }
},
)
.max()
.unwrap_or(0)
}
#[derive(Debug)]
pub struct Break {
page: Page,
axis: Axis2,
}
impl Break {
fn new(page: Page, axis: Axis2) -> Self {
Self { page, axis }
}
fn has_next(&self) -> bool {
!self.page.ranges[self.axis].is_empty()
}
fn next(&mut self, device: &dyn Device, size: isize) -> Result<Option<Page>, ()> {
if !self.has_next() {
return Ok(None);
}
let target = size - self.page.table.headers_width(self.axis);
if target <= 0 {
return Err(());
}
let start = self.page.ranges[self.axis].start;
let (end, next_start) = self.find_breakpoint(start..start + target, device);
let result = Page {
table: self.page.table.clone(),
ranges: EnumMap::from_fn(|axis| {
if axis == self.axis {
start..end
} else {
self.page.ranges[axis].clone()
}
}),
};
self.page.ranges[self.axis].start = next_start;
Ok(Some(result))
}
fn find_breakpoint(&self, range: Range<isize>, device: &dyn Device) -> (isize, isize) {
let cp = &self.page.table.cp[self.axis];
let max = cp.last().copied().unwrap();
if range.end >= max {
return (max, max);
}
for c in 0..self.page.table.n()[self.axis] {
let position = cp[c * 2 + 3];
if position > range.end {
if c == 0
|| self.page.table.cell_width(self.axis, c)
>= device.params().min_break[self.axis]
{
return (range.end, range.end);
} else {
return (cp[(c - 1) * 2 + 3], cp[(c - 1) * 2 + 2]);
}
}
}
unreachable!()
}
}
pub struct Pager {
scale: f64,
pages: SmallVec<[Page; 5]>,
x_break: Option<Break>,
y_break: Option<Break>,
}
impl Pager {
pub fn new(
device: &dyn Device,
pivot_table: &PivotTable,
layer_indexes: Option<&[usize]>,
) -> Self {
let output = pivot_table.output(
layer_indexes.unwrap_or(pivot_table.layer()),
device.params().printing,
);
let body_page = Page::new(output.body, device, None, &pivot_table.style.look);
let body_width = body_page.width(Axis2::X).min(device.params().size.x());
let mut scale = if body_width > device.params().size[Axis2::X]
&& pivot_table.style.look.shrink_to_fit[Axis2::X]
&& device.params().can_scale
{
device.params().size[Axis2::X] as f64 / body_width as f64
} else {
1.0
};
let mut pages = SmallVec::new();
if let Some(title) = output.title {
pages.push(Page::new(
title,
device,
Some(body_width),
&pivot_table.style.look,
));
}
for layer in output.layers {
pages.push(Page::new(layer, device, None, &pivot_table.style.look));
}
pages.push(body_page);
for table in [output.caption, output.footnotes].into_iter().flatten() {
pages.push(Page::new(table, device, None, &pivot_table.style.look));
}
pages.reverse();
if pivot_table.style.look.shrink_to_fit[Axis2::Y] && device.params().can_scale {
let total_height = pages
.iter()
.map(|page: &Page| page.width(Axis2::Y))
.sum::<isize>() as f64;
let max_height = device.params().size[Axis2::Y] as f64;
if total_height * scale >= max_height {
scale *= max_height / total_height;
}
}
Self {
scale,
pages,
x_break: None,
y_break: None,
}
}
pub fn has_next(&mut self, device: &dyn Device) -> Option<&mut Break> {
if let Some(y_break) = self.y_break.as_mut()
&& y_break.has_next()
{
return self.y_break.as_mut();
}
loop {
if let Some(x_break) = &mut self.x_break
&& let Some(page) = x_break
.next(
device,
(device.params().size[Axis2::X] as f64 / self.scale) as isize,
)
.unwrap()
{
self.y_break = Some(page.split(Axis2::Y));
return self.y_break.as_mut();
}
self.x_break = Some(self.pages.pop()?.split(Axis2::X));
}
}
pub fn draw_next(&mut self, device: &mut dyn Device, mut space: isize) -> isize {
use Axis2::*;
if self.scale != 1.0 {
device.scale(self.scale);
space = (space as f64 / self.scale) as isize;
}
let mut ofs = Coord2::new(0, 0);
while let Some(y_break) = self.has_next(device) {
let Some(page) = y_break.next(device, space - ofs[Y]).unwrap_or_default() else {
break;
};
page.draw(device, ofs);
ofs[Y] += page.width(Y);
}
if self.scale != 1.0 {
ofs[Y] = (ofs[Y] as f64 * self.scale) as isize;
}
ofs[Y]
}
}