#[allow(non_camel_case_types)]
#[derive(Debug, Clone)]
pub struct CT_Composite {
pub id: u32,
pub boundary: String,
pub name: Option<String>,
pub visible: bool,
pub children: Vec<CompositeChild>,
}
#[derive(Debug, Clone)]
pub enum CompositeChild {
Text {
content: String,
x: f64,
y: f64,
font_size: f64,
},
Path {
data: String,
stroke_color: u32,
},
Composite(CT_Composite),
}
impl CT_Composite {
#[must_use]
pub fn new(id: u32, boundary: impl Into<String>) -> Self {
Self {
id,
boundary: boundary.into(),
name: None,
visible: true,
children: Vec::new(),
}
}
#[must_use]
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
#[must_use]
pub fn visible(mut self, visible: bool) -> Self {
self.visible = visible;
self
}
pub fn add_text(&mut self, content: impl Into<String>, x: f64, y: f64, font_size: f64) {
self.children.push(CompositeChild::Text {
content: content.into(),
x,
y,
font_size,
});
}
pub fn add_path(&mut self, data: impl Into<String>, stroke_color: u32) {
self.children.push(CompositeChild::Path {
data: data.into(),
stroke_color,
});
}
pub fn add_composite(&mut self, child: CT_Composite) {
self.children.push(CompositeChild::Composite(child));
}
#[must_use]
pub fn child_count(&self) -> usize {
self.children.len()
}
#[must_use]
pub fn to_xml_string(&self) -> String {
use std::fmt::Write;
let mut xml = format!(
"<ofd:CT_Composite ID=\"{}\" Boundary=\"{}\"",
self.id, self.boundary
);
if let Some(ref name) = self.name {
write!(xml, " Name=\"{name}\"").expect("写入内存缓冲区不会失败");
}
if !self.visible {
xml.push_str(" Visible=\"false\"");
}
xml.push_str(">\n");
for child in &self.children {
xml.push_str(&child.to_xml_string());
}
xml.push_str("</ofd:CT_Composite>\n");
xml
}
}
impl CompositeChild {
#[must_use]
pub fn to_xml_string(&self) -> String {
match self {
Self::Text {
content,
x,
y,
font_size,
} => {
format!(
" <ofd:TextObject X=\"{x}\" Y=\"{y}\" FontSize=\"{font_size}\">\
{content}</ofd:TextObject>\n"
)
}
Self::Path { data, stroke_color } => {
format!(
" <ofd:PathObject StrokeColor=\"{stroke_color}\">\
<ofd:AbbreviatedData>{data}</ofd:AbbreviatedData>\
</ofd:PathObject>\n"
)
}
Self::Composite(inner) => {
use std::fmt::Write;
let inner_xml = inner.to_xml_string();
let mut out = String::new();
for line in inner_xml.lines() {
writeln!(out, " {line}").expect("写入内存缓冲区不会失败");
}
out
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ct_composite_new() {
let c = CT_Composite::new(1, "0 0 100 100");
assert_eq!(c.id, 1);
assert_eq!(c.boundary, "0 0 100 100");
assert!(c.name.is_none());
assert!(c.visible);
assert!(c.children.is_empty());
}
#[test]
fn test_ct_composite_builder() {
let c = CT_Composite::new(2, "10 20 50 50")
.name("group1")
.visible(false);
assert_eq!(c.name.as_deref(), Some("group1"));
assert!(!c.visible);
}
#[test]
fn test_ct_composite_add_children() {
let mut c = CT_Composite::new(3, "0 0 200 200");
c.add_text("hello", 10.0, 20.0, 12.0);
c.add_path("M0 0L10 10", 0x00_0000);
assert_eq!(c.child_count(), 2);
}
#[test]
fn test_ct_composite_nested() {
let inner = CT_Composite::new(4, "5 5 10 10");
let mut outer = CT_Composite::new(5, "0 0 100 100");
outer.add_composite(inner);
assert_eq!(outer.child_count(), 1);
}
#[test]
fn test_ct_composite_to_xml_string() {
let c = CT_Composite::new(10, "0 0 50 50").name("myGroup");
let xml = c.to_xml_string();
assert!(xml.contains("ID=\"10\""));
assert!(xml.contains("Boundary=\"0 0 50 50\""));
assert!(xml.contains("Name=\"myGroup\""));
assert!(xml.contains("<ofd:CT_Composite"));
assert!(xml.contains("</ofd:CT_Composite>"));
}
#[test]
fn test_ct_composite_to_xml_with_children() {
let mut c = CT_Composite::new(11, "0 0 100 100");
c.add_text("test", 1.0, 2.0, 14.0);
c.add_path("M0 0", 0xFF_0000);
let xml = c.to_xml_string();
assert!(xml.contains("ofd:TextObject"));
assert!(xml.contains("ofd:PathObject"));
assert!(xml.contains("test"));
}
#[test]
fn test_ct_composite_to_xml_hidden() {
let c = CT_Composite::new(12, "0 0 10 10").visible(false);
let xml = c.to_xml_string();
assert!(xml.contains("Visible=\"false\""));
}
#[test]
fn test_ct_composite_clone_debug() {
let c = CT_Composite::new(1, "0 0 1 1");
let c2 = c.clone();
assert_eq!(c2.id, 1);
assert!(format!("{c:?}").contains("CT_Composite"));
}
}