#[derive(Debug, Clone)]
pub enum Content {
Path(PathContent),
Text(TextContent),
Image(ImageContent),
}
#[derive(Debug, Clone)]
pub struct PathContent {
pub data: String,
pub stroke_color: u32,
pub line_width: f64,
pub fill_color: Option<u32>,
}
#[derive(Debug, Clone)]
pub struct TextContent {
pub text: String,
pub x: f64,
pub y: f64,
pub font_size: f64,
pub font: String,
pub color: u32,
}
#[derive(Debug, Clone)]
pub struct ImageContent {
pub data: Vec<u8>,
pub x: f64,
pub y: f64,
pub width: f64,
pub height: f64,
}
impl Content {
#[must_use]
pub fn path(data: impl Into<String>) -> Self {
Self::Path(PathContent {
data: data.into(),
stroke_color: 0x00_0000,
line_width: 0.35,
fill_color: None,
})
}
#[must_use]
pub fn text(text: impl Into<String>, x: f64, y: f64) -> Self {
Self::Text(TextContent {
text: text.into(),
x,
y,
font_size: 12.0,
font: "SimSun".into(),
color: 0x00_0000,
})
}
#[must_use]
pub fn image(data: Vec<u8>, x: f64, y: f64, width: f64, height: f64) -> Self {
Self::Image(ImageContent {
data,
x,
y,
width,
height,
})
}
#[must_use]
pub fn is_path(&self) -> bool {
matches!(self, Self::Path(_))
}
#[must_use]
pub fn is_text(&self) -> bool {
matches!(self, Self::Text(_))
}
#[must_use]
pub fn is_image(&self) -> bool {
matches!(self, Self::Image(_))
}
#[must_use]
pub fn to_xml_string(&self) -> String {
use std::fmt::Write;
match self {
Self::Path(p) => {
let mut xml = String::new();
write!(
xml,
"<ofd:PathObject StrokeColor=\"{}\" LineWidth=\"{}\"",
p.stroke_color, p.line_width
)
.expect("写入内存缓冲区不会失败");
if let Some(fc) = p.fill_color {
write!(xml, " FillColor=\"{fc}\"").expect("写入内存缓冲区不会失败");
}
writeln!(
xml,
"><ofd:AbbreviatedData>{}</ofd:AbbreviatedData></ofd:PathObject>",
p.data
)
.expect("写入内存缓冲区不会失败");
xml
}
Self::Text(t) => {
format!(
"<ofd:TextObject X=\"{}\" Y=\"{}\" FontSize=\"{}\" \
Font=\"{}\" Color=\"{}\">{}</ofd:TextObject>\n",
t.x, t.y, t.font_size, t.font, t.color, t.text
)
}
Self::Image(img) => {
format!(
"<ofd:ImageObject X=\"{}\" Y=\"{}\" Width=\"{}\" Height=\"{}\" />\n",
img.x, img.y, img.width, img.height
)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_content_path() {
let c = Content::path("M0 0L10 10");
assert!(c.is_path());
assert!(!c.is_text());
assert!(!c.is_image());
if let Content::Path(p) = &c {
assert_eq!(p.data, "M0 0L10 10");
assert_eq!(p.stroke_color, 0x00_0000);
assert!((p.line_width - 0.35).abs() < f64::EPSILON);
assert!(p.fill_color.is_none());
}
}
#[test]
fn test_content_text() {
let c = Content::text("hello", 10.0, 20.0);
assert!(!c.is_path());
assert!(c.is_text());
assert!(!c.is_image());
if let Content::Text(t) = &c {
assert_eq!(t.text, "hello");
assert!((t.x - 10.0).abs() < f64::EPSILON);
assert!((t.y - 20.0).abs() < f64::EPSILON);
assert!((t.font_size - 12.0).abs() < f64::EPSILON);
assert_eq!(t.font, "SimSun");
assert_eq!(t.color, 0x00_0000);
}
}
#[test]
fn test_content_image() {
let c = Content::image(vec![0x89, 0x50], 0.0, 0.0, 50.0, 50.0);
assert!(!c.is_path());
assert!(!c.is_text());
assert!(c.is_image());
if let Content::Image(img) = &c {
assert_eq!(img.data, vec![0x89, 0x50]);
assert!((img.width - 50.0).abs() < f64::EPSILON);
}
}
#[test]
fn test_content_path_to_xml() {
let c = Content::path("M0 0Z");
let xml = c.to_xml_string();
assert!(xml.contains("ofd:PathObject"));
assert!(xml.contains("M0 0Z"));
assert!(xml.contains("StrokeColor=\"0\""));
}
#[test]
fn test_content_text_to_xml() {
let c = Content::text("test", 1.0, 2.0);
let xml = c.to_xml_string();
assert!(xml.contains("ofd:TextObject"));
assert!(xml.contains("test"));
assert!(xml.contains("X=\"1\""));
assert!(xml.contains("Y=\"2\""));
}
#[test]
fn test_content_image_to_xml() {
let c = Content::image(vec![1, 2, 3], 5.0, 10.0, 30.0, 40.0);
let xml = c.to_xml_string();
assert!(xml.contains("ofd:ImageObject"));
assert!(xml.contains("Width=\"30\""));
assert!(xml.contains("Height=\"40\""));
}
#[test]
fn test_content_clone_debug() {
let c = Content::path("M0 0");
let c2 = c.clone();
assert!(c2.is_path());
assert!(format!("{c:?}").contains("Path"));
}
}