use dxpdf::render::layout::draw_command::{DrawCommand, LayoutedPage};
const FIXTURE: &str = concat!(
env!("CARGO_MANIFEST_DIR"),
"/test-files/issue-165-cellspacing-scale.docx"
);
struct Table {
verticals: Vec<(f32, f32)>,
first_cell_text_x: f32,
}
fn tables() -> Vec<Table> {
let bytes = std::fs::read(FIXTURE).expect("fixture is committed");
let doc = dxpdf::docx::parse(&bytes).expect("fixture parses");
let pages: Vec<LayoutedPage> = dxpdf::render::resolve_and_layout(doc).1;
let mut out: Vec<Table> = Vec::new();
for page in &pages {
let mut v: Vec<(f32, f32, f32, f32)> = page
.commands
.iter()
.filter_map(|c| match c {
DrawCommand::Rect { rect, .. } => Some((
rect.origin.x.raw(),
rect.origin.y.raw(),
rect.size.width.raw(),
rect.size.height.raw(),
)),
_ => None,
})
.filter(|r| r.2 < r.3)
.collect();
v.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
let mut bands: Vec<Vec<(f32, f32, f32, f32)>> = Vec::new();
let mut bottom = f32::NEG_INFINITY;
for r in v {
if r.1 <= bottom + 0.01 {
bands.last_mut().unwrap().push(r);
bottom = bottom.max(r.1 + r.3);
} else {
bands.push(vec![r]);
bottom = r.1 + r.3;
}
}
for band in bands {
let (top, bot) = band.iter().fold((f32::MAX, f32::MIN), |(t, b), r| {
(t.min(r.1), b.max(r.1 + r.3))
});
let first_cell_text_x = page
.commands
.iter()
.filter_map(|c| match c {
DrawCommand::Text { position, .. }
if top <= position.y.raw() && position.y.raw() <= bot =>
{
Some(position.x.raw())
}
_ => None,
})
.fold(f32::MAX, f32::min);
let mut verticals: Vec<(f32, f32)> = band.iter().map(|r| (r.0, r.0 + r.2)).collect();
verticals.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
out.push(Table {
verticals,
first_cell_text_x,
});
}
}
assert_eq!(out.len(), 4, "four tables");
out
}
impl Table {
fn table_box(&self) -> (f32, f32) {
(
self.verticals[0].0,
self.verticals[self.verticals.len() - 1].1,
)
}
fn cell_boxes(&self) -> Vec<(f32, f32)> {
let n = self.verticals.len();
assert!(n >= 4 && n.is_multiple_of(2), "{n} verticals");
self.verticals[1..n - 1]
.chunks(2)
.map(|p| (p[0].0, p[1].1))
.collect()
}
fn gaps(&self) -> Vec<f32> {
let (left, right) = self.table_box();
let cells = self.cell_boxes();
let mut g = vec![cells[0].0 - left];
g.extend(cells.windows(2).map(|w| w[1].0 - w[0].1));
g.push(right - cells[cells.len() - 1].1);
g
}
}
fn close(a: f32, b: f32) -> bool {
(a - b).abs() < 0.02
}
#[test]
fn the_rendered_gap_is_twice_the_declared_spacing() {
let t = tables();
for (idx, declared_pt, label) in [(1usize, 10.0_f32, "T2"), (2, 20.0, "T3")] {
for (i, g) in t[idx].gaps().into_iter().enumerate() {
assert!(
close(g, 2.0 * declared_pt),
"{label} gap {i}: {g}pt against a declared {declared_pt}pt"
);
}
}
}
#[test]
fn the_gap_at_the_table_edge_equals_the_gap_between_cells() {
let t = tables();
for (idx, label) in [(1usize, "T2"), (2, "T3"), (3, "T4")] {
let gaps = t[idx].gaps();
for (i, g) in gaps.iter().enumerate() {
assert!(
close(*g, gaps[0]),
"{label}: gap {i} is {g}pt where the leading edge is {}pt",
gaps[0]
);
}
}
}
#[test]
fn doubling_the_declared_spacing_doubles_the_gap() {
let t = tables();
let (at_200, at_400) = (t[1].gaps()[0], t[2].gaps()[0]);
assert!(
close(at_400, at_200 * 2.0),
"400 twips must give exactly twice the gap of 200: {at_200}pt vs {at_400}pt"
);
}
#[test]
fn spacing_is_carved_out_of_the_declared_table_width() {
let t = tables();
let border = t[0].verticals[0].1 - t[0].verticals[0].0;
let (left, right) = t[0].table_box();
assert!(
close(right - left - border, 360.0),
"T1 spans {}pt centre-to-centre, not the declared tblW of 360",
right - left - border
);
for (idx, label) in [(1usize, "T2"), (2, "T3"), (3, "T4")] {
let (left, right) = t[idx].table_box();
assert!(
close(right - left, 360.0),
"{label} spans {}pt, not the declared tblW of 360",
right - left
);
}
}
#[test]
fn a_row_level_spacing_supersedes_the_table_level_one() {
let t = tables();
for (i, g) in t[3].gaps().into_iter().enumerate() {
assert!(close(g, 80.0), "gap {i}: {g}pt against the row's 800 twips");
}
for (i, c) in t[3].cell_boxes().iter().enumerate() {
assert!(
close(c.1 - c.0, (360.0 - 4.0 * 80.0) / 3.0),
"cell {i} is {}pt wide, not the 13.3 Word draws",
c.1 - c.0
);
}
}
#[test]
fn the_first_cells_text_moves_with_the_spacing() {
let t = tables();
for (from, to, step_pt, label) in [(1usize, 2usize, 10.0_f32, "T2→T3"), (2, 3, 20.0, "T3→T4")]
{
let shift = t[to].first_cell_text_x - t[from].first_cell_text_x;
assert!(
close(shift, 2.0 * step_pt),
"{label}: the text should start {}pt further in, got {shift}pt",
2.0 * step_pt
);
}
}