#[allow(non_camel_case_types)]
#[derive(Debug, Clone)]
pub struct CT_GraphicUnit {
pub id: u32,
pub boundary: String,
pub name: Option<String>,
pub visible: bool,
pub ctm: Option<[f64; 6]>,
pub draw_param: Option<u32>,
pub line_width: Option<f64>,
pub cap: Option<LineCapType>,
pub join: Option<LineJoinType>,
pub miter_limit: Option<f64>,
pub dash_offset: Option<f64>,
pub dash_pattern: Option<String>,
pub alpha: Option<u8>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LineCapType {
Butt,
Round,
Square,
}
impl LineCapType {
#[must_use]
pub fn as_str(&self) -> &'static str {
match self {
Self::Butt => "Butt",
Self::Round => "Round",
Self::Square => "Square",
}
}
}
impl std::fmt::Display for LineCapType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LineJoinType {
Miter,
Round,
Bevel,
}
impl LineJoinType {
#[must_use]
pub fn as_str(&self) -> &'static str {
match self {
Self::Miter => "Miter",
Self::Round => "Round",
Self::Bevel => "Bevel",
}
}
}
impl std::fmt::Display for LineJoinType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
impl CT_GraphicUnit {
#[must_use]
pub fn new(id: u32, boundary: impl Into<String>) -> Self {
Self {
id,
boundary: boundary.into(),
name: None,
visible: true,
ctm: None,
draw_param: None,
line_width: None,
cap: None,
join: None,
miter_limit: None,
dash_offset: None,
dash_pattern: None,
alpha: None,
}
}
#[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
}
#[must_use]
pub fn ctm(mut self, ctm: [f64; 6]) -> Self {
self.ctm = Some(ctm);
self
}
#[must_use]
pub fn draw_param(mut self, id: u32) -> Self {
self.draw_param = Some(id);
self
}
#[must_use]
pub fn line_width(mut self, width: f64) -> Self {
self.line_width = Some(width);
self
}
#[must_use]
pub fn cap(mut self, cap: LineCapType) -> Self {
self.cap = Some(cap);
self
}
#[must_use]
pub fn join(mut self, join: LineJoinType) -> Self {
self.join = Some(join);
self
}
#[must_use]
pub fn miter_limit(mut self, limit: f64) -> Self {
self.miter_limit = Some(limit);
self
}
#[must_use]
pub fn dash_offset(mut self, offset: f64) -> Self {
self.dash_offset = Some(offset);
self
}
#[must_use]
pub fn dash_pattern(mut self, pattern: impl Into<String>) -> Self {
self.dash_pattern = Some(pattern.into());
self
}
#[must_use]
pub fn alpha(mut self, alpha: u8) -> Self {
self.alpha = Some(alpha);
self
}
#[must_use]
pub fn get_id(&self) -> u32 {
self.id
}
#[must_use]
pub fn get_boundary(&self) -> &str {
&self.boundary
}
#[must_use]
pub fn get_name(&self) -> Option<&str> {
self.name.as_deref()
}
#[must_use]
pub fn get_visible(&self) -> bool {
self.visible
}
#[must_use]
pub fn get_ctm(&self) -> Option<[f64; 6]> {
self.ctm
}
#[must_use]
pub fn get_alpha(&self) -> Option<u8> {
self.alpha
}
#[must_use]
pub fn to_xml_string(&self) -> String {
use std::fmt::Write;
let mut xml = format!(
"<ofd:CT_GraphicUnit 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\"");
}
if let Some(ctm) = self.ctm {
write!(
xml,
" CTM=\"{} {} {} {} {} {}\"",
ctm[0], ctm[1], ctm[2], ctm[3], ctm[4], ctm[5]
)
.expect("写入内存缓冲区不会失败");
}
if let Some(dp) = self.draw_param {
write!(xml, " DrawParam=\"{dp}\"").expect("写入内存缓冲区不会失败");
}
if let Some(lw) = self.line_width {
write!(xml, " LineWidth=\"{lw}\"").expect("写入内存缓冲区不会失败");
}
if let Some(cap) = self.cap {
write!(xml, " Cap=\"{}\"", cap.as_str()).expect("写入内存缓冲区不会失败");
}
if let Some(join) = self.join {
write!(xml, " Join=\"{}\"", join.as_str()).expect("写入内存缓冲区不会失败");
}
if let Some(ml) = self.miter_limit {
write!(xml, " MiterLimit=\"{ml}\"").expect("写入内存缓冲区不会失败");
}
if let Some(doff) = self.dash_offset {
write!(xml, " DashOffset=\"{doff}\"").expect("写入内存缓冲区不会失败");
}
if let Some(ref dp) = self.dash_pattern {
write!(xml, " DashPattern=\"{dp}\"").expect("写入内存缓冲区不会失败");
}
if let Some(a) = self.alpha {
write!(xml, " Alpha=\"{a}\"").expect("写入内存缓冲区不会失败");
}
xml.push_str(" />");
xml
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ct_graphic_unit_new() {
let gu = CT_GraphicUnit::new(1, "0 0 100 50");
assert_eq!(gu.id, 1);
assert_eq!(gu.boundary, "0 0 100 50");
assert!(gu.name.is_none());
assert!(gu.visible);
assert!(gu.ctm.is_none());
assert!(gu.alpha.is_none());
}
#[test]
fn test_ct_graphic_unit_builder_chaining() {
let gu = CT_GraphicUnit::new(2, "10 20 50 50")
.name("rect1")
.visible(false)
.line_width(1.5)
.cap(LineCapType::Round)
.join(LineJoinType::Bevel)
.alpha(128);
assert_eq!(gu.get_name(), Some("rect1"));
assert!(!gu.get_visible());
assert!((gu.line_width.unwrap() - 1.5).abs() < f64::EPSILON);
assert_eq!(gu.cap, Some(LineCapType::Round));
assert_eq!(gu.join, Some(LineJoinType::Bevel));
assert_eq!(gu.get_alpha(), Some(128));
}
#[test]
fn test_ct_graphic_unit_ctm() {
let gu = CT_GraphicUnit::new(3, "0 0 10 10").ctm([1.0, 0.0, 0.0, 1.0, 5.0, 5.0]);
let ctm = gu.get_ctm().unwrap();
assert!((ctm[4] - 5.0).abs() < f64::EPSILON);
}
#[test]
fn test_line_cap_type_display() {
assert_eq!(LineCapType::Butt.to_string(), "Butt");
assert_eq!(LineCapType::Round.to_string(), "Round");
assert_eq!(LineCapType::Square.to_string(), "Square");
}
#[test]
fn test_line_join_type_display() {
assert_eq!(LineJoinType::Miter.to_string(), "Miter");
assert_eq!(LineJoinType::Round.to_string(), "Round");
assert_eq!(LineJoinType::Bevel.to_string(), "Bevel");
}
#[test]
fn test_ct_graphic_unit_to_xml_minimal() {
let gu = CT_GraphicUnit::new(1, "0 0 100 50");
let xml = gu.to_xml_string();
assert!(xml.contains("ID=\"1\""));
assert!(xml.contains("Boundary=\"0 0 100 50\""));
assert!(xml.contains("<ofd:CT_GraphicUnit"));
assert!(xml.ends_with(" />"));
}
#[test]
fn test_ct_graphic_unit_to_xml_full() {
let gu = CT_GraphicUnit::new(5, "0 0 200 100")
.name("myUnit")
.visible(false)
.ctm([2.0, 0.0, 0.0, 2.0, 10.0, 20.0])
.line_width(0.5)
.cap(LineCapType::Square)
.join(LineJoinType::Miter)
.miter_limit(4.0)
.dash_offset(1.0)
.dash_pattern("4 2")
.alpha(200);
let xml = gu.to_xml_string();
assert!(xml.contains("Name=\"myUnit\""));
assert!(xml.contains("Visible=\"false\""));
assert!(xml.contains("CTM=\"2 0 0 2 10 20\""));
assert!(xml.contains("LineWidth=\"0.5\""));
assert!(xml.contains("Cap=\"Square\""));
assert!(xml.contains("Join=\"Miter\""));
assert!(xml.contains("MiterLimit=\"4\""));
assert!(xml.contains("DashOffset=\"1\""));
assert!(xml.contains("DashPattern=\"4 2\""));
assert!(xml.contains("Alpha=\"200\""));
}
#[test]
fn test_ct_graphic_unit_clone_debug() {
let gu = CT_GraphicUnit::new(1, "0 0 1 1");
let gu2 = gu.clone();
assert_eq!(gu2.id, 1);
assert!(format!("{gu:?}").contains("CT_GraphicUnit"));
}
}