use std::path::{Path, PathBuf};
use odox_core::doc::Presentation;
use odox_core::{Anchor, Color, Family, Fill, GradientStyle, Length, Ns, Transform};
fn fixture(name: &str) -> Option<Presentation> {
let path: PathBuf = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../../corpus/libreoffice")
.join(name);
let bytes = std::fs::read(path).ok()?;
Some(Presentation::read(&bytes).expect("a readable presentation"))
}
#[test]
fn a_plain_deck_takes_its_masters_white_ground_and_has_no_decorations() {
let Some(deck) = fixture("deck.odp") else {
return;
};
let slides = deck.slides();
let first = &slides[0];
assert_eq!(first.master_page, Some("Title_20_Slide"));
assert!(
deck.master(first).is_some(),
"the master page was not found"
);
assert_eq!(
deck.background(first),
Fill::Solid(Color {
r: 0xff,
g: 0xff,
b: 0xff
})
);
assert!(deck.background_objects(first).is_empty());
}
#[test]
fn a_template_contributes_its_decorations_and_nothing_it_only_prompts_with() {
let Some(deck) = fixture("blue-curve.odp") else {
return;
};
let slides = deck.slides();
let first = &slides[0];
assert_eq!(deck.background(first), Fill::None);
let objects = deck.background_objects(first);
assert_eq!(objects.len(), 1, "the prompts or the fields were kept");
assert!(objects[0].is(&Ns::Draw, "custom-shape"));
let style = objects[0]
.attr(&Ns::Draw, "style-name")
.expect("the shape names a graphic style");
let properties = deck
.document
.styles
.resolve(&odox_core::Family::Graphic, style);
let Fill::Gradient(name) = properties.graphic.fill() else {
panic!("expected a gradient, got {:?}", properties.graphic.fill());
};
let gradient = deck
.document
.styles
.gradient(&name)
.expect("the gradient it names");
assert_eq!(gradient.style, GradientStyle::Linear);
assert_eq!(
gradient.start,
Color {
r: 0x77,
g: 0xca,
b: 0xee
}
);
assert_eq!(
gradient.end,
Color {
r: 0x00,
g: 0x9b,
b: 0xdd
}
);
assert!((gradient.angle - 270.0).abs() < 0.01, "{}", gradient.angle);
}
#[test]
fn a_template_of_polygons_hands_over_all_of_them() {
let Some(deck) = fixture("focus.odp") else {
return;
};
let slides = deck.slides();
let objects = deck.background_objects(&slides[0]);
assert_eq!(objects.len(), 6, "not the six polygons the master draws");
for shape in &objects {
assert!(shape.is(&Ns::Draw, "polygon"), "{:?}", shape.name.local);
assert!(shape.attr(&Ns::Svg, "viewBox").is_some());
assert!(shape.attr(&Ns::Draw, "points").is_some());
}
}
#[test]
fn a_numbered_circle_asks_for_its_numeral_in_the_middle() {
let Some(deck) = fixture("growing-liberty.odp") else {
return;
};
let slides = deck.slides();
let mut anchored = 0;
for slide in &slides {
for shape in slide.element.elements() {
if !shape.is(&Ns::Draw, "custom-shape") {
continue;
}
if shape.child(&Ns::Text, "p").is_none() {
continue;
}
assert!(
shape.child(&Ns::Draw, "text-box").is_none(),
"a drawing shape put a box around its label"
);
let style = shape.attr(&Ns::Draw, "style-name").unwrap_or_default();
let properties = deck.document.styles.resolve(&Family::Graphic, style);
if properties.graphic.text_anchor == Some(Anchor::Middle) {
anchored += 1;
}
}
}
assert!(anchored > 0, "no labelled shape asked to be centred");
}
#[test]
fn a_decoration_offers_a_second_picture_where_the_first_is_a_format_nothing_reads() {
let Some(deck) = fixture("growing-liberty.odp") else {
return;
};
let slides = deck.slides();
let mut alternatives = 0;
for slide in &slides {
for shape in deck.background_objects(slide) {
let images: Vec<_> = shape
.elements()
.filter(|child| child.is(&Ns::Draw, "image"))
.collect();
if images.len() < 2 {
continue;
}
alternatives += 1;
let types: Vec<_> = images
.iter()
.filter_map(|image| image.attr(&Ns::Draw, "mime-type"))
.collect();
assert!(
types.contains(&"image/png"),
"no readable alternative among {types:?}"
);
}
}
assert!(alternatives > 0, "the template offers no alternatives");
}
#[test]
fn every_transform_in_the_corpus_places_the_shape_on_the_page() {
let Some(deck) = fixture("growing-liberty.odp") else {
return;
};
let mut found = 0;
for slide in &deck.slides() {
let page = deck.page_layout(slide);
let (wide, high) = (page.width.points(), page.height.points());
for shape in deck.background_objects(slide) {
let Some(text) = shape.attr(&Ns::Draw, "transform") else {
continue;
};
found += 1;
let placed = Transform::parse(text).expect("a readable transform");
let at = |name: &str| {
shape
.attr(&Ns::Svg, name)
.and_then(Length::parse)
.map_or(0.0, |l| l.points())
};
let (width, height) = (at("width"), at("height"));
let corners = [(0.0, 0.0), (width, 0.0), (width, height), (0.0, height)]
.map(|corner| placed.apply(corner));
let left = corners.iter().map(|c| c.0).fold(f32::MAX, f32::min);
let right = corners.iter().map(|c| c.0).fold(f32::MIN, f32::max);
let top = corners.iter().map(|c| c.1).fold(f32::MAX, f32::min);
let bottom = corners.iter().map(|c| c.1).fold(f32::MIN, f32::max);
assert!(
right > 0.0 && left < wide && bottom > 0.0 && top < high,
"a decoration landed at ({left}, {top})-({right}, {bottom}) \
outside a page of {wide} by {high}"
);
}
}
assert!(found > 0, "no decoration in the corpus is placed this way");
}
#[test]
fn a_shape_is_moved_and_sized_in_the_unit_it_was_written_in() {
let Some(mut deck) = fixture("deck.odp") else {
return;
};
let (slide, shape) = {
let slides = deck.slides();
let slide = slides.first().expect("a slide");
let (index, frame) = slide
.shapes_indexed()
.find(|(_, s)| s.attr(&Ns::Svg, "x").is_some())
.expect("a placed shape");
assert!(frame.attr(&Ns::Svg, "x").expect("x").ends_with("cm"));
(slide.position, index)
};
deck.set_geometry(
slide,
shape,
Length(72.0),
Length(36.0),
Length(144.0),
Length(72.0),
)
.expect("a movable shape");
let slides = deck.slides();
let frame = slides[0]
.shapes_indexed()
.find(|(i, _)| *i == shape)
.map(|(_, s)| s)
.expect("the shape");
assert_eq!(frame.attr(&Ns::Svg, "x"), Some("2.54cm"));
assert_eq!(frame.attr(&Ns::Svg, "y"), Some("1.27cm"));
assert_eq!(frame.attr(&Ns::Svg, "width"), Some("5.08cm"));
assert_eq!(frame.attr(&Ns::Svg, "height"), Some("2.54cm"));
assert_eq!(
deck.set_geometry(
slide,
9999,
Length(0.0),
Length(0.0),
Length(1.0),
Length(1.0)
),
Err(odox_core::Refused::NotFound)
);
deck.document.write_verified().expect("saves");
}