use std::path::Path;
use dxpdf::render::layout::draw_command::{DrawCommand, LayoutedPage};
const FIXTURE: &str = concat!(
env!("CARGO_MANIFEST_DIR"),
"/test-files/issue-157-tblprex-bidi.docx"
);
fn layout() -> Vec<LayoutedPage> {
let bytes = std::fs::read(Path::new(FIXTURE)).expect("fixture is committed");
let doc = dxpdf::docx::parse(&bytes).expect("parse");
dxpdf::render::resolve_and_layout(doc).1
}
type Rect = (f32, f32, f32, f32);
type Colour = (u8, u8, u8);
type Shaded = (Colour, Rect);
const A: Colour = (0xF8, 0xCB, 0xAD);
const B: Colour = (0xC6, 0xE0, 0xB4);
const C: Colour = (0xBD, 0xD7, 0xEE);
fn rows_by_band(pages: &[LayoutedPage]) -> Vec<std::collections::HashMap<Colour, Rect>> {
let mut shaded: Vec<Shaded> = pages
.iter()
.flat_map(|p| &p.commands)
.filter_map(|c| match c {
DrawCommand::Rect { rect, color } => Some((
(color.r, color.g, color.b),
(
rect.origin.x.raw(),
rect.origin.y.raw(),
rect.size.width.raw(),
rect.size.height.raw(),
),
)),
_ => None,
})
.filter(|(colour, _)| [A, B, C].contains(colour))
.collect();
shaded.sort_by(|a, b| a.1 .1.total_cmp(&b.1 .1));
let mut rows: Vec<Vec<Shaded>> = Vec::new();
for (colour, rect) in shaded {
match rows.last_mut() {
Some(last) if (rect.1 - last[0].1 .1).abs() < 1.0 => last.push((colour, rect)),
_ => rows.push(vec![(colour, rect)]),
}
}
rows.into_iter().map(|r| r.into_iter().collect()).collect()
}
#[test]
fn a_flipped_rows_cell_keeps_its_own_declared_width_not_the_slots() {
let bands = rows_by_band(&layout());
assert_eq!(bands.len(), 3, "three rows, three distinct y-bands");
for (label, band) in [("row 1", &bands[0]), ("row 3", &bands[1])] {
let (_, _, w, _) = band[&A];
assert!(
(w - 50.0).abs() < 0.5,
"{label} (unflipped): cell A should be 50pt, got {w}pt"
);
}
let (_, _, flipped_w, _) = bands[2][&A];
assert!(
(flipped_w - 50.0).abs() < 0.5,
"row 2 (flipped): cell A should still be 50pt (its own declared \
width), not resized to the 150pt slot it visually lands in — got \
{flipped_w}pt"
);
}
#[test]
fn a_flipped_rows_cells_run_right_to_left_at_their_own_widths() {
let bands = rows_by_band(&layout());
let flipped = &bands[2];
let (cx, _, cw, _) = flipped[&C];
let (bx, _, bw, _) = flipped[&B];
let (ax, _, aw, _) = flipped[&A];
assert!(cx < bx && bx < ax, "left to right the row reads C, B, A");
for (label, w, want) in [("C", cw, 150.0), ("B", bw, 100.0), ("A", aw, 50.0)] {
assert!(
(w - want).abs() < 0.5,
"cell {label} should keep its own {want}pt, got {w}pt"
);
}
assert!((cx + cw - bx).abs() < 0.5, "C and B meet exactly");
assert!((bx + bw - ax).abs() < 0.5, "B and A meet exactly");
}
#[test]
fn a_flipped_row_reaches_the_pages_content_width_not_the_tables_own() {
let bands = rows_by_band(&layout());
let control_right = {
let (x, _, w, _) = bands[0][&C];
x + w
};
let flipped_right = {
let (x, _, w, _) = bands[2][&A];
x + w
};
assert!(
(control_right - 371.5).abs() < 0.1,
"control table's own right edge should be at 72 + 300, shifted half a \
leading border left, got {control_right}"
);
let page_right_margin = 612.0 - 72.0; let expected_flipped_right = page_right_margin - 0.5; assert!(
(flipped_right - expected_flipped_right).abs() < 0.1,
"flipped row's right edge should reach the page's content width \
(expected {expected_flipped_right}), not the table's own \
{control_right} — got {flipped_right}"
);
}
#[test]
fn a_flipped_row_paints_after_the_row_that_would_otherwise_follow_it() {
let bands = rows_by_band(&layout());
for colour in [A, B, C] {
let (x0, _, w0, _) = bands[0][&colour];
let (x1, _, w1, _) = bands[1][&colour];
assert!(
(x0 - x1).abs() < 0.5 && (w0 - w1).abs() < 0.5,
"{colour:?}: band 1 (row 3) should be an unflipped row in the \
same columns as band 0 (row 1) at ({x0}, {w0}pt), got \
({x1}, {w1}pt)"
);
}
let (ax, _, aw, _) = bands[2][&A];
let (ax0, _, aw0, _) = bands[0][&A];
assert!(
(ax - ax0).abs() > 50.0 || (aw - aw0).abs() > 0.5,
"band 2 should be the flipped row, visibly different from the \
unflipped control"
);
}