use std::num::NonZeroUsize;
use std::sync::Arc;
use super::cells::CellGrid;
use super::layout::RowPiece;
use super::repeated::Repeatable;
use crate::foundations::{AlternativeFold, Fold};
use crate::layout::Abs;
use crate::visualize::Stroke;
pub struct Line {
pub index: usize,
pub start: usize,
pub end: Option<NonZeroUsize>,
pub stroke: Option<Arc<Stroke<Abs>>>,
pub position: LinePosition,
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum LinePosition {
Before,
After,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub(super) enum StrokePriority {
GridStroke = 0,
CellStroke = 1,
ExplicitLine = 2,
}
#[derive(Debug, PartialEq, Eq)]
pub(super) struct LineSegment {
pub(super) stroke: Arc<Stroke<Abs>>,
pub(super) offset: Abs,
pub(super) length: Abs,
pub(super) priority: StrokePriority,
}
pub(super) fn generate_line_segments<'grid, F, I, L>(
grid: &'grid CellGrid,
tracks: I,
index: usize,
lines: L,
line_stroke_at_track: F,
) -> impl Iterator<Item = LineSegment> + 'grid
where
F: Fn(
&CellGrid,
usize,
usize,
Option<Option<Arc<Stroke<Abs>>>>,
) -> Option<(Arc<Stroke<Abs>>, StrokePriority)>
+ 'grid,
I: IntoIterator<Item = (usize, Abs)>,
I::IntoIter: 'grid,
L: IntoIterator<Item = &'grid Line>,
L::IntoIter: Clone + 'grid,
{
let mut current_segment: Option<LineSegment> = None;
let mut offset = Abs::zero();
let gutter_factor = if grid.has_gutter { 2 } else { 1 };
let mut tracks = tracks.into_iter();
let lines = lines.into_iter();
std::iter::from_fn(move || {
for (track, size) in &mut tracks {
let mut line_strokes = lines
.clone()
.filter(|line| {
line.end
.map(|end| {
let end = if grid.has_gutter {
2 * end.get() - 1
} else {
end.get()
};
(gutter_factor * line.start..end).contains(&track)
})
.unwrap_or_else(|| track >= gutter_factor * line.start)
})
.map(|line| line.stroke.clone());
let line_stroke = line_strokes.next().map(|first_stroke| {
line_strokes.fold(first_stroke, |acc, line_stroke| line_stroke.fold(acc))
});
if let Some((stroke, priority)) =
line_stroke_at_track(grid, index, track, line_stroke)
{
if let Some(current_segment) = &mut current_segment {
if current_segment.stroke == stroke
&& current_segment.priority == priority
{
current_segment.length += size;
} else {
let new_segment =
LineSegment { stroke, offset, length: size, priority };
let old_segment = std::mem::replace(current_segment, new_segment);
offset += size;
return Some(old_segment);
}
} else {
current_segment =
Some(LineSegment { stroke, offset, length: size, priority });
}
} else if let Some(old_segment) = current_segment.take() {
offset += size;
return Some(old_segment);
}
offset += size;
}
current_segment.take()
})
}
pub(super) fn vline_stroke_at_row(
grid: &CellGrid,
x: usize,
y: usize,
stroke: Option<Option<Arc<Stroke<Abs>>>>,
) -> Option<(Arc<Stroke<Abs>>, StrokePriority)> {
if x != 0 && x != grid.cols.len() {
if let Some(parent) = grid.effective_parent_cell_position(x, y) {
if parent.x < x {
return None;
}
}
}
let (left_cell_stroke, left_cell_prioritized) = x
.checked_sub(1)
.and_then(|left_x| {
grid.effective_parent_cell_position(left_x, y)
})
.map(|parent| {
let left_cell = grid.cell(parent.x, parent.y).unwrap();
(left_cell.stroke.right.clone(), left_cell.stroke_overridden.right)
})
.unwrap_or((None, false));
let (right_cell_stroke, right_cell_prioritized) = if x < grid.cols.len() {
grid.effective_parent_cell_position(x, y)
.map(|parent| {
let right_cell = grid.cell(parent.x, parent.y).unwrap();
(right_cell.stroke.left.clone(), right_cell.stroke_overridden.left)
})
.unwrap_or((None, false))
} else {
(None, false)
};
let priority = if stroke.is_some() {
StrokePriority::ExplicitLine
} else if left_cell_prioritized || right_cell_prioritized {
StrokePriority::CellStroke
} else {
StrokePriority::GridStroke
};
let (prioritized_cell_stroke, deprioritized_cell_stroke) =
if left_cell_prioritized && !right_cell_prioritized {
(left_cell_stroke, right_cell_stroke)
} else {
(right_cell_stroke, left_cell_stroke)
};
let cell_stroke = prioritized_cell_stroke.fold_or(deprioritized_cell_stroke);
let final_stroke = stroke.fold_or(Some(cell_stroke)).flatten();
final_stroke.zip(Some(priority))
}
pub(super) fn hline_stroke_at_column(
grid: &CellGrid,
rows: &[RowPiece],
local_top_y: Option<usize>,
in_last_region: bool,
y: usize,
x: usize,
stroke: Option<Option<Arc<Stroke<Abs>>>>,
) -> Option<(Arc<Stroke<Abs>>, StrokePriority)> {
if y != 0 && y != grid.rows.len() {
if let Some(parent) = grid.effective_parent_cell_position(x, y) {
if parent.y < y {
let local_parent_y = rows
.iter()
.find(|row| row.y >= parent.y)
.map(|row| row.y)
.unwrap_or(y);
if local_parent_y < y {
return None;
}
}
}
}
let use_top_border_stroke = local_top_y.is_none() && y != 0;
let (top_cell_stroke, top_cell_prioritized) = local_top_y
.or(use_top_border_stroke.then_some(0))
.and_then(|top_y| {
grid.effective_parent_cell_position(x, top_y)
})
.map(|parent| {
let top_cell = grid.cell(parent.x, parent.y).unwrap();
if use_top_border_stroke {
(top_cell.stroke.top.clone(), top_cell.stroke_overridden.top)
} else {
(top_cell.stroke.bottom.clone(), top_cell.stroke_overridden.bottom)
}
})
.unwrap_or((None, false));
let use_bottom_border_stroke = !in_last_region
&& local_top_y.map_or(true, |top_y| top_y + 1 != grid.rows.len())
&& y == grid.rows.len();
let bottom_y =
if use_bottom_border_stroke { grid.rows.len().saturating_sub(1) } else { y };
let (bottom_cell_stroke, bottom_cell_prioritized) = if bottom_y < grid.rows.len() {
grid.effective_parent_cell_position(x, bottom_y)
.map(|parent| {
let bottom_cell = grid.cell(parent.x, parent.y).unwrap();
if use_bottom_border_stroke {
(
bottom_cell.stroke.bottom.clone(),
bottom_cell.stroke_overridden.bottom,
)
} else {
(bottom_cell.stroke.top.clone(), bottom_cell.stroke_overridden.top)
}
})
.unwrap_or((None, false))
} else {
(None, false)
};
let priority = if stroke.is_some() {
StrokePriority::ExplicitLine
} else if top_cell_prioritized || bottom_cell_prioritized {
StrokePriority::CellStroke
} else {
StrokePriority::GridStroke
};
let top_stroke_comes_from_header = grid
.header
.as_ref()
.and_then(Repeatable::as_repeated)
.zip(local_top_y)
.is_some_and(|(header, local_top_y)| {
local_top_y < header.end && y > header.end
});
let bottom_stroke_comes_from_footer = grid
.footer
.as_ref()
.and_then(Repeatable::as_repeated)
.is_some_and(|footer| {
local_top_y.unwrap_or(0) + 1 < footer.start && y >= footer.start
});
let (prioritized_cell_stroke, deprioritized_cell_stroke) =
if !use_bottom_border_stroke
&& !bottom_stroke_comes_from_footer
&& (use_top_border_stroke
|| top_stroke_comes_from_header
|| top_cell_prioritized && !bottom_cell_prioritized)
{
(top_cell_stroke, bottom_cell_stroke)
} else {
(bottom_cell_stroke, top_cell_stroke)
};
let cell_stroke = prioritized_cell_stroke.fold_or(deprioritized_cell_stroke);
let final_stroke = stroke.fold_or(Some(cell_stroke)).flatten();
final_stroke.zip(Some(priority))
}
#[cfg(test)]
mod test {
use super::super::cells::Entry;
use super::*;
use crate::foundations::Content;
use crate::introspection::Locator;
use crate::layout::{Axes, Cell, Sides, Sizing};
use crate::utils::NonZeroExt;
fn sample_cell() -> Cell<'static> {
Cell {
body: Content::default(),
locator: Locator::root(),
fill: None,
colspan: NonZeroUsize::ONE,
rowspan: NonZeroUsize::ONE,
stroke: Sides::splat(Some(Arc::new(Stroke::default()))),
stroke_overridden: Sides::splat(false),
breakable: true,
}
}
fn cell_with_colspan_rowspan(colspan: usize, rowspan: usize) -> Cell<'static> {
Cell {
body: Content::default(),
locator: Locator::root(),
fill: None,
colspan: NonZeroUsize::try_from(colspan).unwrap(),
rowspan: NonZeroUsize::try_from(rowspan).unwrap(),
stroke: Sides::splat(Some(Arc::new(Stroke::default()))),
stroke_overridden: Sides::splat(false),
breakable: true,
}
}
fn sample_grid_for_vlines(gutters: bool) -> CellGrid<'static> {
const COLS: usize = 4;
const ROWS: usize = 6;
let entries = vec![
Entry::Cell(sample_cell()),
Entry::Cell(sample_cell()),
Entry::Cell(cell_with_colspan_rowspan(2, 1)),
Entry::Merged { parent: 2 },
Entry::Cell(sample_cell()),
Entry::Cell(cell_with_colspan_rowspan(3, 1)),
Entry::Merged { parent: 5 },
Entry::Merged { parent: 5 },
Entry::Merged { parent: 4 },
Entry::Cell(sample_cell()),
Entry::Cell(cell_with_colspan_rowspan(2, 1)),
Entry::Merged { parent: 10 },
Entry::Cell(sample_cell()),
Entry::Cell(cell_with_colspan_rowspan(3, 2)),
Entry::Merged { parent: 13 },
Entry::Merged { parent: 13 },
Entry::Cell(sample_cell()),
Entry::Merged { parent: 13 },
Entry::Merged { parent: 13 },
Entry::Merged { parent: 13 },
Entry::Cell(sample_cell()),
Entry::Cell(sample_cell()),
Entry::Cell(cell_with_colspan_rowspan(2, 1)),
Entry::Merged { parent: 22 },
];
CellGrid::new_internal(
Axes::with_x(&[Sizing::Auto; COLS]),
if gutters {
Axes::new(&[Sizing::Auto; COLS - 1], &[Sizing::Auto; ROWS - 1])
} else {
Axes::default()
},
vec![],
vec![],
None,
None,
entries,
)
}
#[test]
fn test_vline_splitting_without_gutter() {
let stroke = Arc::new(Stroke::default());
let grid = sample_grid_for_vlines(false);
let rows = &[
RowPiece { height: Abs::pt(1.0), y: 0 },
RowPiece { height: Abs::pt(2.0), y: 1 },
RowPiece { height: Abs::pt(4.0), y: 2 },
RowPiece { height: Abs::pt(8.0), y: 3 },
RowPiece { height: Abs::pt(16.0), y: 4 },
RowPiece { height: Abs::pt(32.0), y: 5 },
];
let expected_vline_splits = &[
vec![LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(0.),
length: Abs::pt(1. + 2. + 4. + 8. + 16. + 32.),
priority: StrokePriority::GridStroke,
}],
vec![LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(0.),
length: Abs::pt(1. + 2. + 4. + 8. + 16. + 32.),
priority: StrokePriority::GridStroke,
}],
vec![
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(0.),
length: Abs::pt(1.),
priority: StrokePriority::GridStroke,
},
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(1. + 2.),
length: Abs::pt(4.),
priority: StrokePriority::GridStroke,
},
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(1. + 2. + 4. + 8. + 16.),
length: Abs::pt(32.),
priority: StrokePriority::GridStroke,
},
],
vec![],
vec![LineSegment {
stroke,
offset: Abs::pt(0.),
length: Abs::pt(1. + 2. + 4. + 8. + 16. + 32.),
priority: StrokePriority::GridStroke,
}],
];
for (x, expected_splits) in expected_vline_splits.iter().enumerate() {
let tracks = rows.iter().map(|row| (row.y, row.height));
assert_eq!(
expected_splits,
&generate_line_segments(&grid, tracks, x, &[], vline_stroke_at_row)
.collect::<Vec<_>>(),
);
}
}
#[test]
fn test_vline_splitting_with_gutter_and_per_cell_stroke() {
let stroke = Arc::new(Stroke::default());
let grid = sample_grid_for_vlines(true);
let rows = &[
RowPiece { height: Abs::pt(1.0), y: 0 },
RowPiece { height: Abs::pt(2.0), y: 1 },
RowPiece { height: Abs::pt(4.0), y: 2 },
RowPiece { height: Abs::pt(8.0), y: 3 },
RowPiece { height: Abs::pt(16.0), y: 4 },
RowPiece { height: Abs::pt(32.0), y: 5 },
RowPiece { height: Abs::pt(64.0), y: 6 },
RowPiece { height: Abs::pt(128.0), y: 7 },
RowPiece { height: Abs::pt(256.0), y: 8 },
RowPiece { height: Abs::pt(512.0), y: 9 },
RowPiece { height: Abs::pt(1024.0), y: 10 },
];
let expected_vline_splits = &[
vec![
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(0.),
length: Abs::pt(1.),
priority: StrokePriority::GridStroke,
},
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(1. + 2.),
length: Abs::pt(4. + 8. + 16.),
priority: StrokePriority::GridStroke,
},
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(1. + 2. + 4. + 8. + 16. + 32.),
length: Abs::pt(64.),
priority: StrokePriority::GridStroke,
},
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(1. + 2. + 4. + 8. + 16. + 32. + 64. + 128.),
length: Abs::pt(256.),
priority: StrokePriority::GridStroke,
},
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(
1. + 2. + 4. + 8. + 16. + 32. + 64. + 128. + 256. + 512.,
),
length: Abs::pt(1024.),
priority: StrokePriority::GridStroke,
},
],
vec![
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(0.),
length: Abs::pt(1.),
priority: StrokePriority::GridStroke,
},
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(1. + 2.),
length: Abs::pt(4. + 8. + 16.),
priority: StrokePriority::GridStroke,
},
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(1. + 2. + 4. + 8. + 16. + 32.),
length: Abs::pt(64.),
priority: StrokePriority::GridStroke,
},
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(1. + 2. + 4. + 8. + 16. + 32. + 64. + 128.),
length: Abs::pt(256.),
priority: StrokePriority::GridStroke,
},
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(
1. + 2. + 4. + 8. + 16. + 32. + 64. + 128. + 256. + 512.,
),
length: Abs::pt(1024.),
priority: StrokePriority::GridStroke,
},
],
vec![
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(0.),
length: Abs::pt(1.),
priority: StrokePriority::GridStroke,
},
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(1. + 2.),
length: Abs::pt(4.),
priority: StrokePriority::GridStroke,
},
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(1. + 2. + 4. + 8.),
length: Abs::pt(16.),
priority: StrokePriority::GridStroke,
},
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(1. + 2. + 4. + 8. + 16. + 32.),
length: Abs::pt(64. + 128. + 256.),
priority: StrokePriority::GridStroke,
},
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(
1. + 2. + 4. + 8. + 16. + 32. + 64. + 128. + 256. + 512.,
),
length: Abs::pt(1024.),
priority: StrokePriority::GridStroke,
},
],
vec![
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(0.),
length: Abs::pt(1.),
priority: StrokePriority::GridStroke,
},
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(1. + 2. + 4. + 8.),
length: Abs::pt(16.),
priority: StrokePriority::GridStroke,
},
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(
1. + 2. + 4. + 8. + 16. + 32. + 64. + 128. + 256. + 512.,
),
length: Abs::pt(1024.),
priority: StrokePriority::GridStroke,
},
],
vec![
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(0.),
length: Abs::pt(1.),
priority: StrokePriority::GridStroke,
},
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(1. + 2. + 4. + 8.),
length: Abs::pt(16.),
priority: StrokePriority::GridStroke,
},
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(
1. + 2. + 4. + 8. + 16. + 32. + 64. + 128. + 256. + 512.,
),
length: Abs::pt(1024.),
priority: StrokePriority::GridStroke,
},
],
vec![],
vec![],
vec![
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(0.),
length: Abs::pt(1.),
priority: StrokePriority::GridStroke,
},
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(1. + 2.),
length: Abs::pt(4.),
priority: StrokePriority::GridStroke,
},
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(1. + 2. + 4. + 8.),
length: Abs::pt(16.),
priority: StrokePriority::GridStroke,
},
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(1. + 2. + 4. + 8. + 16. + 32.),
length: Abs::pt(64. + 128. + 256.),
priority: StrokePriority::GridStroke,
},
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(
1. + 2. + 4. + 8. + 16. + 32. + 64. + 128. + 256. + 512.,
),
length: Abs::pt(1024.),
priority: StrokePriority::GridStroke,
},
],
];
for (x, expected_splits) in expected_vline_splits.iter().enumerate() {
let tracks = rows.iter().map(|row| (row.y, row.height));
assert_eq!(
expected_splits,
&generate_line_segments(&grid, tracks, x, &[], vline_stroke_at_row)
.collect::<Vec<_>>(),
);
}
}
#[test]
fn test_vline_splitting_with_gutter_and_explicit_vlines() {
let stroke = Arc::new(Stroke::default());
let grid = sample_grid_for_vlines(true);
let rows = &[
RowPiece { height: Abs::pt(1.0), y: 0 },
RowPiece { height: Abs::pt(2.0), y: 1 },
RowPiece { height: Abs::pt(4.0), y: 2 },
RowPiece { height: Abs::pt(8.0), y: 3 },
RowPiece { height: Abs::pt(16.0), y: 4 },
RowPiece { height: Abs::pt(32.0), y: 5 },
RowPiece { height: Abs::pt(64.0), y: 6 },
RowPiece { height: Abs::pt(128.0), y: 7 },
RowPiece { height: Abs::pt(256.0), y: 8 },
RowPiece { height: Abs::pt(512.0), y: 9 },
RowPiece { height: Abs::pt(1024.0), y: 10 },
];
let expected_vline_splits = &[
vec![LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(0.),
length: Abs::pt(
1. + 2. + 4. + 8. + 16. + 32. + 64. + 128. + 256. + 512. + 1024.,
),
priority: StrokePriority::ExplicitLine,
}],
vec![LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(0.),
length: Abs::pt(
1. + 2. + 4. + 8. + 16. + 32. + 64. + 128. + 256. + 512. + 1024.,
),
priority: StrokePriority::ExplicitLine,
}],
vec![LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(0.),
length: Abs::pt(
1. + 2. + 4. + 8. + 16. + 32. + 64. + 128. + 256. + 512. + 1024.,
),
priority: StrokePriority::ExplicitLine,
}],
vec![
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(0.),
length: Abs::pt(1. + 2.),
priority: StrokePriority::ExplicitLine,
},
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(1. + 2. + 4.),
length: Abs::pt(8. + 16. + 32.),
priority: StrokePriority::ExplicitLine,
},
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(1. + 2. + 4. + 8. + 16. + 32. + 64. + 128. + 256.),
length: Abs::pt(512. + 1024.),
priority: StrokePriority::ExplicitLine,
},
],
vec![
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(0.),
length: Abs::pt(1. + 2.),
priority: StrokePriority::ExplicitLine,
},
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(1. + 2. + 4.),
length: Abs::pt(8. + 16. + 32.),
priority: StrokePriority::ExplicitLine,
},
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(1. + 2. + 4. + 8. + 16. + 32. + 64. + 128. + 256.),
length: Abs::pt(512. + 1024.),
priority: StrokePriority::ExplicitLine,
},
],
vec![
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(1.),
length: Abs::pt(2.),
priority: StrokePriority::ExplicitLine,
},
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(1. + 2. + 4.),
length: Abs::pt(8.),
priority: StrokePriority::ExplicitLine,
},
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(1. + 2. + 4. + 8. + 16.),
length: Abs::pt(32.),
priority: StrokePriority::ExplicitLine,
},
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(1. + 2. + 4. + 8. + 16. + 32. + 64. + 128. + 256.),
length: Abs::pt(512.),
priority: StrokePriority::ExplicitLine,
},
],
vec![
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(1.),
length: Abs::pt(2.),
priority: StrokePriority::ExplicitLine,
},
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(1. + 2. + 4.),
length: Abs::pt(8.),
priority: StrokePriority::ExplicitLine,
},
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(1. + 2. + 4. + 8. + 16.),
length: Abs::pt(32.),
priority: StrokePriority::ExplicitLine,
},
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(1. + 2. + 4. + 8. + 16. + 32. + 64. + 128. + 256.),
length: Abs::pt(512.),
priority: StrokePriority::ExplicitLine,
},
],
vec![LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(0.),
length: Abs::pt(
1. + 2. + 4. + 8. + 16. + 32. + 64. + 128. + 256. + 512. + 1024.,
),
priority: StrokePriority::ExplicitLine,
}],
];
for (x, expected_splits) in expected_vline_splits.iter().enumerate() {
let tracks = rows.iter().map(|row| (row.y, row.height));
assert_eq!(
expected_splits,
&generate_line_segments(
&grid,
tracks,
x,
&[
Line {
index: x,
start: 0,
end: None,
stroke: Some(stroke.clone()),
position: LinePosition::Before
},
Line {
index: x,
start: 0,
end: None,
stroke: Some(stroke.clone()),
position: LinePosition::After
},
],
vline_stroke_at_row
)
.collect::<Vec<_>>(),
);
}
}
fn sample_grid_for_hlines(gutters: bool) -> CellGrid<'static> {
const COLS: usize = 4;
const ROWS: usize = 9;
let entries = vec![
Entry::Cell(cell_with_colspan_rowspan(1, 2)),
Entry::Cell(sample_cell()),
Entry::Cell(cell_with_colspan_rowspan(2, 2)),
Entry::Merged { parent: 2 },
Entry::Merged { parent: 0 },
Entry::Cell(sample_cell()),
Entry::Merged { parent: 2 },
Entry::Merged { parent: 2 },
Entry::Cell(sample_cell()),
Entry::Cell(sample_cell()),
Entry::Cell(sample_cell()),
Entry::Cell(sample_cell()),
Entry::Cell(cell_with_colspan_rowspan(4, 2)),
Entry::Merged { parent: 12 },
Entry::Merged { parent: 12 },
Entry::Merged { parent: 12 },
Entry::Merged { parent: 12 },
Entry::Merged { parent: 12 },
Entry::Merged { parent: 12 },
Entry::Merged { parent: 12 },
Entry::Cell(sample_cell()),
Entry::Cell(cell_with_colspan_rowspan(1, 2)),
Entry::Cell(cell_with_colspan_rowspan(2, 1)),
Entry::Merged { parent: 22 },
Entry::Cell(sample_cell()),
Entry::Merged { parent: 21 },
Entry::Cell(sample_cell()),
Entry::Cell(sample_cell()),
Entry::Cell(cell_with_colspan_rowspan(2, 2)),
Entry::Merged { parent: 28 },
Entry::Cell(cell_with_colspan_rowspan(2, 2)),
Entry::Merged { parent: 30 },
Entry::Merged { parent: 28 },
Entry::Merged { parent: 28 },
Entry::Merged { parent: 30 },
Entry::Merged { parent: 30 },
];
CellGrid::new_internal(
Axes::with_x(&[Sizing::Auto; COLS]),
if gutters {
Axes::new(&[Sizing::Auto; COLS - 1], &[Sizing::Auto; ROWS - 1])
} else {
Axes::default()
},
vec![],
vec![],
None,
None,
entries,
)
}
#[test]
fn test_hline_splitting_without_gutter() {
let stroke = Arc::new(Stroke::default());
let grid = sample_grid_for_hlines(false);
let columns = &[Abs::pt(1.), Abs::pt(2.), Abs::pt(4.), Abs::pt(8.)];
let rows = grid
.rows
.iter()
.enumerate()
.map(|(y, _)| RowPiece { height: Abs::pt(f64::from(2u32.pow(y as u32))), y })
.collect::<Vec<_>>();
let expected_hline_splits = &[
vec![LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(0.),
length: Abs::pt(1. + 2. + 4. + 8.),
priority: StrokePriority::GridStroke,
}],
vec![LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(1.),
length: Abs::pt(2.),
priority: StrokePriority::GridStroke,
}],
vec![LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(0.),
length: Abs::pt(1. + 2. + 4. + 8.),
priority: StrokePriority::GridStroke,
}],
vec![LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(0.),
length: Abs::pt(1. + 2. + 4. + 8.),
priority: StrokePriority::GridStroke,
}],
vec![],
vec![LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(0.),
length: Abs::pt(1. + 2. + 4. + 8.),
priority: StrokePriority::GridStroke,
}],
vec![
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(0.),
length: Abs::pt(1.),
priority: StrokePriority::GridStroke,
},
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(1. + 2.),
length: Abs::pt(4. + 8.),
priority: StrokePriority::GridStroke,
},
],
vec![LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(0.),
length: Abs::pt(1. + 2. + 4. + 8.),
priority: StrokePriority::GridStroke,
}],
vec![],
vec![LineSegment {
stroke,
offset: Abs::pt(0.),
length: Abs::pt(1. + 2. + 4. + 8.),
priority: StrokePriority::GridStroke,
}],
];
for (y, expected_splits) in expected_hline_splits.iter().enumerate() {
let tracks = columns.iter().copied().enumerate();
assert_eq!(
expected_splits,
&generate_line_segments(&grid, tracks, y, &[], |grid, y, x, stroke| {
hline_stroke_at_column(
grid,
&rows,
y.checked_sub(1),
true,
y,
x,
stroke,
)
})
.collect::<Vec<_>>(),
);
}
}
#[test]
fn test_hline_splitting_with_gutter_and_explicit_hlines() {
let stroke = Arc::new(Stroke::default());
let grid = sample_grid_for_hlines(true);
let columns = &[
Abs::pt(1.0),
Abs::pt(2.0),
Abs::pt(4.0),
Abs::pt(8.0),
Abs::pt(16.0),
Abs::pt(32.0),
Abs::pt(64.0),
];
let rows = grid
.rows
.iter()
.enumerate()
.map(|(y, _)| RowPiece { height: Abs::pt(f64::from(2u32.pow(y as u32))), y })
.collect::<Vec<_>>();
let expected_hline_splits = &[
vec![LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(0.),
length: Abs::pt(1. + 2. + 4. + 8. + 16. + 32. + 64.),
priority: StrokePriority::ExplicitLine,
}],
vec![LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(1.),
length: Abs::pt(2. + 4. + 8.),
priority: StrokePriority::ExplicitLine,
}],
vec![LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(1.),
length: Abs::pt(2. + 4. + 8.),
priority: StrokePriority::ExplicitLine,
}],
vec![LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(0.),
length: Abs::pt(1. + 2. + 4. + 8. + 16. + 32. + 64.),
priority: StrokePriority::ExplicitLine,
}],
vec![LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(0.),
length: Abs::pt(1. + 2. + 4. + 8. + 16. + 32. + 64.),
priority: StrokePriority::ExplicitLine,
}],
vec![LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(0.),
length: Abs::pt(1. + 2. + 4. + 8. + 16. + 32. + 64.),
priority: StrokePriority::ExplicitLine,
}],
vec![LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(0.),
length: Abs::pt(1. + 2. + 4. + 8. + 16. + 32. + 64.),
priority: StrokePriority::ExplicitLine,
}],
vec![],
vec![],
vec![LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(0.),
length: Abs::pt(1. + 2. + 4. + 8. + 16. + 32. + 64.),
priority: StrokePriority::ExplicitLine,
}],
vec![LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(0.),
length: Abs::pt(1. + 2. + 4. + 8. + 16. + 32. + 64.),
priority: StrokePriority::ExplicitLine,
}],
vec![
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(0.),
length: Abs::pt(1. + 2.),
priority: StrokePriority::ExplicitLine,
},
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(1. + 2. + 4.),
length: Abs::pt(8. + 16. + 32. + 64.),
priority: StrokePriority::ExplicitLine,
},
],
vec![
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(0.),
length: Abs::pt(1. + 2.),
priority: StrokePriority::ExplicitLine,
},
LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(1. + 2. + 4.),
length: Abs::pt(8. + 16. + 32. + 64.),
priority: StrokePriority::ExplicitLine,
},
],
vec![LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(0.),
length: Abs::pt(1. + 2. + 4. + 8. + 16. + 32. + 64.),
priority: StrokePriority::ExplicitLine,
}],
vec![LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(0.),
length: Abs::pt(1. + 2. + 4. + 8. + 16. + 32. + 64.),
priority: StrokePriority::ExplicitLine,
}],
vec![LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(1. + 2. + 4.),
length: Abs::pt(8.),
priority: StrokePriority::ExplicitLine,
}],
vec![LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(1. + 2. + 4.),
length: Abs::pt(8.),
priority: StrokePriority::ExplicitLine,
}],
vec![LineSegment {
stroke: stroke.clone(),
offset: Abs::pt(0.),
length: Abs::pt(1. + 2. + 4. + 8. + 16. + 32. + 64.),
priority: StrokePriority::ExplicitLine,
}],
];
for (y, expected_splits) in expected_hline_splits.iter().enumerate() {
let tracks = columns.iter().copied().enumerate();
assert_eq!(
expected_splits,
&generate_line_segments(
&grid,
tracks,
y,
&[
Line {
index: y,
start: 0,
end: None,
stroke: Some(stroke.clone()),
position: LinePosition::Before
},
Line {
index: y,
start: 0,
end: None,
stroke: Some(stroke.clone()),
position: LinePosition::After
},
],
|grid, y, x, stroke| hline_stroke_at_column(
grid,
&rows,
y.checked_sub(1),
true,
y,
x,
stroke
)
)
.collect::<Vec<_>>(),
);
}
}
#[test]
fn test_hline_splitting_considers_absent_rows() {
let grid = sample_grid_for_hlines(false);
let columns = &[Abs::pt(1.), Abs::pt(2.), Abs::pt(4.), Abs::pt(8.)];
let rows = grid
.rows
.iter()
.enumerate()
.filter(|(y, _)| *y != 3)
.map(|(y, _)| RowPiece { height: Abs::pt(f64::from(2u32.pow(y as u32))), y })
.collect::<Vec<_>>();
assert_eq!(
&vec![LineSegment {
stroke: Arc::new(Stroke::default()),
offset: Abs::pt(0.),
length: Abs::pt(1. + 2. + 4. + 8.),
priority: StrokePriority::GridStroke
}],
&generate_line_segments(
&grid,
columns.iter().copied().enumerate(),
4,
&[],
|grid, y, x, stroke| hline_stroke_at_column(
grid,
&rows,
if y == 4 { Some(2) } else { y.checked_sub(1) },
true,
y,
x,
stroke
)
)
.collect::<Vec<_>>()
);
}
}