Skip to main content

easyofd_core/graph/tight/method/
arc.rs

1//! 圆弧路径方法。
2//!
3//! 对应 Java: org.ofdrw.core.graph.tight.method.Arc
4
5use crate::xml_element::{XmlElement, XmlElementError, XmlNode};
6
7/// 圆弧路径方法。
8///
9/// 图 56 圆弧的结构。用于描述椭圆弧线段。
10///
11/// 对应 Java: org.ofdrw.core.graph.tight.method.Arc
12#[derive(Debug, Clone, PartialEq)]
13pub struct Arc {
14    /// 椭圆长轴半径。
15    pub rx: f64,
16    /// 椭圆短轴半径。
17    pub ry: f64,
18    /// 旋转角度(度),正值顺时针。
19    pub rotation_angle: f64,
20    /// 是否大圆弧(角度 > 180)。
21    pub large_arc: bool,
22    /// 是否顺时针方向。
23    pub sweep_direction: bool,
24    /// 结束点 (x, y)。
25    pub end_point: (f64, f64),
26}
27
28impl Arc {
29    /// 创建圆弧。
30    #[must_use]
31    pub fn new(
32        rx: f64,
33        ry: f64,
34        rotation_angle: f64,
35        large_arc: bool,
36        sweep_direction: bool,
37        end_x: f64,
38        end_y: f64,
39    ) -> Self {
40        Self {
41            rx,
42            ry,
43            rotation_angle: rotation_angle % 360.0,
44            large_arc,
45            sweep_direction,
46            end_point: (end_x, end_y),
47        }
48    }
49
50    /// 序列化为缩写数据字符串(A 命令格式)。
51    #[must_use]
52    pub fn to_abbreviated_string(&self) -> String {
53        let large = i32::from(self.large_arc);
54        let sweep = i32::from(self.sweep_direction);
55        format!(
56            "A {} {} {} {} {} {} {}",
57            self.rx, self.ry, self.rotation_angle, large, sweep, self.end_point.0, self.end_point.1
58        )
59    }
60}
61
62impl std::fmt::Display for Arc {
63    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64        f.write_str(&self.to_abbreviated_string())
65    }
66}
67
68impl XmlElement for Arc {
69    /// 对应 Java: Arc 元素名 "Arc"。
70    fn element_name(&self) -> &'static str {
71        "Arc"
72    }
73
74    fn attributes(&self) -> Vec<(String, String)> {
75        Vec::new()
76    }
77
78    /// 覆写 write_xml:文本内容为 A 命令格式。
79    fn write_xml(&self, out: &mut String) {
80        out.push_str("<Arc>");
81        out.push_str(&crate::xml_element::xml_escape(
82            &self.to_abbreviated_string(),
83        ));
84        out.push_str("</Arc>");
85    }
86
87    fn from_xml(node: &XmlNode) -> Result<Self, XmlElementError> {
88        let text = node
89            .text
90            .as_deref()
91            .ok_or_else(|| XmlElementError("Arc 缺少文本内容".to_string()))?;
92        // 解析 "A rx ry angle large sweep x y"
93        let parts: Vec<&str> = text.split_whitespace().collect();
94        if parts.len() < 8 || parts[0] != "A" {
95            return Err(XmlElementError(format!("Arc 格式错误: {text}")));
96        }
97        let rx: f64 = parts[1]
98            .parse()
99            .map_err(|e| XmlElementError(format!("解析 Arc.rx 失败: {e}")))?;
100        let ry: f64 = parts[2]
101            .parse()
102            .map_err(|e| XmlElementError(format!("解析 Arc.ry 失败: {e}")))?;
103        let rotation_angle: f64 = parts[3]
104            .parse()
105            .map_err(|e| XmlElementError(format!("解析 Arc.rotation_angle 失败: {e}")))?;
106        let large_arc: i32 = parts[4]
107            .parse()
108            .map_err(|e| XmlElementError(format!("解析 Arc.large_arc 失败: {e}")))?;
109        let sweep: i32 = parts[5]
110            .parse()
111            .map_err(|e| XmlElementError(format!("解析 Arc.sweep 失败: {e}")))?;
112        let end_x: f64 = parts[6]
113            .parse()
114            .map_err(|e| XmlElementError(format!("解析 Arc.end_x 失败: {e}")))?;
115        let end_y: f64 = parts[7]
116            .parse()
117            .map_err(|e| XmlElementError(format!("解析 Arc.end_y 失败: {e}")))?;
118        Ok(Self::new(
119            rx,
120            ry,
121            rotation_angle,
122            large_arc != 0,
123            sweep != 0,
124            end_x,
125            end_y,
126        ))
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133    use crate::xml_parse::parse_xml_to_nodes;
134
135    #[test]
136    fn arc_new() {
137        let a = Arc::new(5.0, 5.0, 0.0, true, false, 10.0, 10.0);
138        assert!((a.rx - 5.0).abs() < f64::EPSILON);
139        assert!((a.ry - 5.0).abs() < f64::EPSILON);
140        assert!(a.large_arc);
141        assert!(!a.sweep_direction);
142        assert!((a.end_point.0 - 10.0).abs() < f64::EPSILON);
143    }
144
145    #[test]
146    fn arc_rotation_modulo() {
147        let a = Arc::new(1.0, 1.0, 720.0, false, false, 0.0, 0.0);
148        assert!((a.rotation_angle - 0.0).abs() < f64::EPSILON);
149    }
150
151    #[test]
152    fn arc_to_string() {
153        let a = Arc::new(5.0, 5.0, 0.0, true, false, 10.0, 10.0);
154        let s = a.to_abbreviated_string();
155        assert!(s.starts_with("A 5 5 0 1 0 10 10"));
156    }
157
158    #[test]
159    fn arc_display() {
160        let a = Arc::new(1.0, 2.0, 45.0, false, true, 3.0, 4.0);
161        let s = format!("{a}");
162        assert!(s.contains("A 1 2 45 0 1 3 4"));
163    }
164
165    #[test]
166    fn arc_clone_eq() {
167        let a = Arc::new(1.0, 2.0, 30.0, true, true, 5.0, 6.0);
168        let b = a.clone();
169        assert!((a.rx - b.rx).abs() < f64::EPSILON);
170    }
171
172    #[test]
173    fn test_xml_element_name() {
174        let a = Arc::new(1.0, 2.0, 0.0, false, false, 3.0, 4.0);
175        assert_eq!(a.element_name(), "Arc");
176    }
177
178    #[test]
179    fn test_xml_element_roundtrip() {
180        let a = Arc::new(5.0, 5.0, 45.0, true, false, 10.0, 10.0);
181        let xml = a.to_xml();
182        assert!(xml.contains("<Arc>"));
183        assert!(xml.contains("A 5 5 45 1 0 10 10"));
184        let node = parse_xml_to_nodes(&xml).unwrap();
185        let a2 = Arc::from_xml(&node).unwrap();
186        assert!((a.rx - a2.rx).abs() < f64::EPSILON);
187        assert!((a.ry - a2.ry).abs() < f64::EPSILON);
188        assert_eq!(a.large_arc, a2.large_arc);
189        assert_eq!(a.sweep_direction, a2.sweep_direction);
190    }
191}