use std::io::Write;
use dxpdf::render::layout::draw_command::{DrawCommand, LayoutedPage};
fn make_docx(document_xml: &str) -> Vec<u8> {
let mut buf = Vec::new();
{
let mut zip = zip::ZipWriter::new(std::io::Cursor::new(&mut buf));
let o = zip::write::SimpleFileOptions::default()
.compression_method(zip::CompressionMethod::Deflated);
zip.start_file("[Content_Types].xml", o).unwrap();
zip.write_all(
br#"<?xml version="1.0" encoding="UTF-8"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
<Default Extension="xml" ContentType="application/xml"/>
<Override PartName="/word/document.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"/>
</Types>"#,
)
.unwrap();
zip.start_file("_rels/.rels", o).unwrap();
zip.write_all(
br#"<?xml version="1.0" encoding="UTF-8"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="word/document.xml"/>
</Relationships>"#,
)
.unwrap();
zip.start_file("word/document.xml", o).unwrap();
zip.write_all(document_xml.as_bytes()).unwrap();
zip.finish().unwrap();
}
buf
}
fn layout(section_rtl: bool, tbl_pr: &str) -> Vec<LayoutedPage> {
let flag = if section_rtl { "<w:bidi/>" } else { "" };
let document_xml = format!(
r#"<?xml version="1.0" encoding="UTF-8"?>
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
<w:body>
<w:tbl>
<w:tblPr>
<w:tblW w:w="4000" w:type="dxa"/>
<w:tblLayout w:type="fixed"/>
{tbl_pr}
</w:tblPr>
<w:tblGrid><w:gridCol w:w="4000"/></w:tblGrid>
<w:tr><w:tc>
<w:tcPr>
<w:tcW w:w="4000" w:type="dxa"/>
<w:shd w:val="clear" w:color="auto" w:fill="C0FFEE"/>
</w:tcPr>
<w:p><w:r><w:t>x</w:t></w:r></w:p>
</w:tc></w:tr>
</w:tbl>
<w:sectPr>
{flag}
<w:pgSz w:w="12240" w:h="15840"/>
<w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440"
w:header="720" w:footer="720" w:gutter="0"/>
</w:sectPr>
</w:body>
</w:document>"#
);
let doc = dxpdf::docx::parse(&make_docx(&document_xml)).expect("parse");
dxpdf::render::resolve_and_layout(doc).1
}
fn table_box(pages: &[LayoutedPage]) -> (f32, f32) {
pages
.iter()
.flat_map(|p| &p.commands)
.find_map(|c| match c {
DrawCommand::Rect { rect, color }
if (color.r, color.g, color.b) == (0xC0, 0xFF, 0xEE) =>
{
Some((rect.origin.x.raw(), rect.size.width.raw()))
}
_ => None,
})
.expect("the shaded table")
}
const CONTENT_LEFT: f32 = 72.0;
const CONTENT_RIGHT: f32 = 72.0 + 468.0;
const RTL_TABLE: &str = "<w:bidiVisual/>";
#[test]
fn a_table_with_no_alignment_sits_at_the_sections_leading_margin() {
let (ltr_x, w) = table_box(&layout(false, ""));
let (rtl_x, rtl_w) = table_box(&layout(true, ""));
assert_eq!(w, 200.0, "4000 twips");
assert_eq!(rtl_w, w, "the table itself does not change size");
assert_eq!(ltr_x, CONTENT_LEFT, "flush left without w:bidi");
assert_eq!(rtl_x + w, CONTENT_RIGHT, "flush right with it");
}
#[test]
fn jc_left_is_the_start_edge_and_follows_the_section() {
let (ltr_x, w) = table_box(&layout(false, r#"<w:jc w:val="left"/>"#));
let (rtl_x, _) = table_box(&layout(true, r#"<w:jc w:val="left"/>"#));
assert_eq!(ltr_x, CONTENT_LEFT);
assert_eq!(
rtl_x + w,
CONTENT_RIGHT,
"`left` means `start`, not the left"
);
}
#[test]
fn jc_right_is_the_end_edge_and_follows_the_section() {
let (ltr_x, w) = table_box(&layout(false, r#"<w:jc w:val="right"/>"#));
let (rtl_x, _) = table_box(&layout(true, r#"<w:jc w:val="right"/>"#));
assert_eq!(ltr_x + w, CONTENT_RIGHT);
assert_eq!(rtl_x, CONTENT_LEFT, "`right` means `end`");
}
#[test]
fn a_centred_table_does_not_move_with_the_section() {
let (ltr_x, _) = table_box(&layout(false, r#"<w:jc w:val="center"/>"#));
let (rtl_x, _) = table_box(&layout(true, r#"<w:jc w:val="center"/>"#));
assert_eq!(rtl_x, ltr_x, "centre is centre in either direction");
}
#[test]
fn tbl_ind_measures_from_the_sections_leading_margin() {
let ind = r#"<w:tblInd w:w="1440" w:type="dxa"/>"#; let (ltr_x, w) = table_box(&layout(false, ind));
let (rtl_x, _) = table_box(&layout(true, ind));
assert_eq!(ltr_x - CONTENT_LEFT, 72.0, "72pt in from the left");
assert_eq!(
CONTENT_RIGHT - (rtl_x + w),
72.0,
"and 72pt in from the right once the section is RTL"
);
}
#[test]
fn a_bidi_visual_table_sits_at_the_right_margin() {
let (plain_x, w) = table_box(&layout(false, ""));
let (rtl_x, rtl_w) = table_box(&layout(false, RTL_TABLE));
assert_eq!(rtl_w, w, "the table itself does not change size");
assert_eq!(plain_x, CONTENT_LEFT, "the control stays where it was");
assert_eq!(
rtl_x + w,
CONTENT_RIGHT,
"a right-to-left table starts at the right margin"
);
}
#[test]
fn jc_right_on_a_bidi_visual_table_is_the_left_margin() {
let jc = r#"<w:jc w:val="right"/>"#;
let (ltr_x, w) = table_box(&layout(false, jc));
let (rtl_x, _) = table_box(&layout(false, &format!("{RTL_TABLE}{jc}")));
assert_eq!(ltr_x + w, CONTENT_RIGHT);
assert_eq!(
rtl_x, CONTENT_LEFT,
"`end` is the left edge of an RTL table"
);
}
#[test]
fn a_centred_bidi_visual_table_does_not_move() {
let jc = r#"<w:jc w:val="center"/>"#;
let (ltr_x, _) = table_box(&layout(false, jc));
let (rtl_x, _) = table_box(&layout(false, &format!("{RTL_TABLE}{jc}")));
assert_eq!(rtl_x, ltr_x, "centre is centre in either direction");
}
#[test]
fn tbl_ind_measures_from_the_tables_own_leading_margin() {
let ind = r#"<w:tblInd w:w="1440" w:type="dxa"/>"#; let (ltr_x, w) = table_box(&layout(false, ind));
let (rtl_x, _) = table_box(&layout(false, &format!("{RTL_TABLE}{ind}")));
assert_eq!(ltr_x - CONTENT_LEFT, 72.0);
assert_eq!(
CONTENT_RIGHT - (rtl_x + w),
72.0,
"72pt in from the right once the table itself is RTL"
);
}
#[test]
fn a_table_that_declares_no_direction_follows_its_section() {
let (in_ltr, w) = table_box(&layout(false, ""));
let (in_rtl, _) = table_box(&layout(true, ""));
assert_eq!(in_ltr, CONTENT_LEFT);
assert_eq!(in_rtl + w, CONTENT_RIGHT);
}
#[test]
fn a_bidi_visual_table_in_a_bidi_section_stays_at_the_right_margin() {
let (x, w) = table_box(&layout(true, RTL_TABLE));
assert_eq!(x + w, CONTENT_RIGHT);
}
#[test]
fn an_explicitly_left_to_right_table_overrides_a_right_to_left_section() {
let (x, _) = table_box(&layout(true, r#"<w:bidiVisual w:val="0"/>"#));
assert_eq!(
x, CONTENT_LEFT,
"the table's own `w:val=\"0\"` wins over the section"
);
}