use crate::generator::images::{Image, ImageEffect};
use crate::core::escape_xml;
pub fn generate_image_xml(image: &Image, shape_id: usize, rel_id: usize) -> String {
let rel_id_str = format!("rId{}", rel_id);
let blip_fill = if let Some(crop) = &image.crop {
let l = (crop.left * 100_000.0) as u32;
let t = (crop.top * 100_000.0) as u32;
let r = (crop.right * 100_000.0) as u32;
let b = (crop.bottom * 100_000.0) as u32;
format!(
r#"<p:blipFill>
<a:blip r:embed="{}"/>
<a:srcRect l="{}" t="{}" r="{}" b="{}"/>
<a:stretch>
<a:fillRect/>
</a:stretch>
</p:blipFill>"#,
rel_id_str, l, t, r, b
)
} else {
format!(
r#"<p:blipFill>
<a:blip r:embed="{}"/>
<a:stretch>
<a:fillRect/>
</a:stretch>
</p:blipFill>"#,
rel_id_str
)
};
let mut effects_xml = String::new();
if !image.effects.is_empty() {
effects_xml.push_str("<a:effectLst>");
for effect in &image.effects {
match effect {
ImageEffect::Shadow => {
effects_xml.push_str(r#"<a:outerShdw blurRad="40000" dist="20000" dir="5400000" rotWithShape="0"><a:srgbClr val="000000"><a:alpha val="40000"/></a:srgbClr></a:outerShdw>"#);
}
ImageEffect::Reflection => {
effects_xml.push_str(r#"<a:reflection blurRad="6350" stA="50000" endA="300" endPos="35000" dist="0" dir="5400000" sy="-100000" algn="bl" rotWithShape="0"/>"#);
}
ImageEffect::Glow => {
effects_xml.push_str(r#"<a:glow rad="50800"><a:srgbClr val="FFD700"><a:alpha val="60000"/></a:srgbClr></a:glow>"#);
}
ImageEffect::SoftEdges => {
effects_xml.push_str(r#"<a:softEdge rad="50800"/>"#);
}
ImageEffect::InnerShadow => {
effects_xml.push_str(r#"<a:innerShdw blurRad="40000" dist="20000" dir="2700000"><a:srgbClr val="000000"><a:alpha val="50000"/></a:srgbClr></a:innerShdw>"#);
}
ImageEffect::Blur => {
effects_xml.push_str(r#"<a:blur rad="38100" grow="1"/>"#);
}
}
}
effects_xml.push_str("</a:effectLst>");
}
format!(
r#"<p:pic>
<p:nvPicPr>
<p:cNvPr id="{}" name="{}"/>
<p:cNvPicPr>
<a:picLocks noChangeAspect="1"/>
</p:cNvPicPr>
<p:nvPr/>
</p:nvPicPr>
{}
<p:spPr>
<a:xfrm>
<a:off x="{}" y="{}"/>
<a:ext cx="{}" cy="{}"/>
</a:xfrm>
<a:prstGeom prst="rect">
<a:avLst/>
</a:prstGeom>
{}
</p:spPr>
</p:pic>"#,
shape_id,
escape_xml(&image.filename),
blip_fill,
image.x,
image.y,
image.width,
image.height,
effects_xml
)
}
pub fn generate_image_relationship(rel_id: usize, image_path: &str) -> String {
format!(
r#"<Relationship Id="rId{}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="{}"/>"#,
rel_id,
escape_xml(image_path)
)
}
pub fn generate_image_content_type(extension: &str) -> String {
let mime_type = match extension.to_lowercase().as_str() {
"png" => "image/png",
"jpg" | "jpeg" => "image/jpeg",
"gif" => "image/gif",
"bmp" => "image/bmp",
"tiff" => "image/tiff",
"svg" => "image/svg+xml",
_ => "application/octet-stream",
};
format!(
r#"<Default Extension="{}" ContentType="{}"/>"#,
extension.to_lowercase(),
mime_type
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::generator::images::Image;
#[test]
fn test_generate_simple_image_xml() {
let img = Image::new("photo.png", 1920000, 1080000, "PNG");
let xml = generate_image_xml(&img, 1, 1);
assert!(xml.contains("p:pic"));
assert!(xml.contains("photo.png"));
assert!(xml.contains("rId1"));
}
#[test]
fn test_generate_image_with_position() {
let img = Image::new("photo.png", 1920000, 1080000, "PNG")
.position(500000, 1000000);
let xml = generate_image_xml(&img, 1, 1);
assert!(xml.contains("x=\"500000\""));
assert!(xml.contains("y=\"1000000\""));
}
#[test]
fn test_generate_image_with_dimensions() {
let img = Image::new("photo.png", 1920000, 1080000, "PNG");
let xml = generate_image_xml(&img, 1, 1);
assert!(xml.contains("cx=\"1920000\""));
assert!(xml.contains("cy=\"1080000\""));
}
#[test]
fn test_generate_image_relationship() {
let rel = generate_image_relationship(1, "../media/image1.png");
assert!(rel.contains("rId1"));
assert!(rel.contains("../media/image1.png"));
}
#[test]
fn test_generate_image_content_type_png() {
let ct = generate_image_content_type("png");
assert!(ct.contains("image/png"));
}
#[test]
fn test_generate_image_content_type_jpg() {
let ct = generate_image_content_type("jpg");
assert!(ct.contains("image/jpeg"));
}
#[test]
fn test_generate_image_content_type_gif() {
let ct = generate_image_content_type("gif");
assert!(ct.contains("image/gif"));
}
#[test]
fn test_escape_xml_in_filename() {
let img = Image::new("photo & <test>.png", 100, 100, "PNG");
let xml = generate_image_xml(&img, 1, 1);
assert!(xml.contains("&"));
assert!(xml.contains("<"));
assert!(xml.contains(">"));
}
#[test]
fn test_generate_image_xml_structure() {
let img = Image::new("photo.png", 1920000, 1080000, "PNG");
let xml = generate_image_xml(&img, 1, 1);
assert!(xml.contains("p:nvPicPr"));
assert!(xml.contains("p:cNvPicPr"));
assert!(xml.contains("a:picLocks"));
assert!(xml.contains("p:blipFill"));
assert!(xml.contains("a:blip"));
assert!(xml.contains("a:stretch"));
assert!(xml.contains("p:spPr"));
assert!(xml.contains("a:xfrm"));
assert!(xml.contains("a:prstGeom"));
}
}