Skip to main content

easyofd_core/graph/tight/method/
cubic_bezier.rs

1//! 三次贝塞尔曲线路径方法。
2//!
3//! 对应 Java: org.ofdrw.core.graph.tight.method.CubicBezier
4
5use crate::xml_element::{XmlElement, XmlElementError, XmlNode};
6
7/// 三次贝塞尔曲线路径方法。
8///
9/// 图 53 三次贝塞尔曲线结构。
10/// 公式: B(t) = (1-t)^3(P0) + 3t(1-t)^2(P1) + 3t^2(1-t)(P2) + t^3(P3), t in [0,1]
11///
12/// 对应 Java: org.ofdrw.core.graph.tight.method.CubicBezier
13#[derive(Debug, Clone, PartialEq)]
14pub struct CubicBezier {
15    /// 第一个控制点 (x, y)。
16    pub point1: (f64, f64),
17    /// 第二个控制点 (x, y)。
18    pub point2: (f64, f64),
19    /// 结束点 (x, y)。
20    pub point3: (f64, f64),
21}
22
23impl CubicBezier {
24    /// 创建三次贝塞尔曲线。
25    #[must_use]
26    pub fn new(x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64) -> Self {
27        Self {
28            point1: (x1, y1),
29            point2: (x2, y2),
30            point3: (x3, y3),
31        }
32    }
33
34    /// 序列化为缩写数据字符串(B 命令格式)。
35    #[must_use]
36    pub fn to_abbreviated_string(&self) -> String {
37        format!(
38            "B {} {} {} {} {} {}",
39            self.point1.0,
40            self.point1.1,
41            self.point2.0,
42            self.point2.1,
43            self.point3.0,
44            self.point3.1
45        )
46    }
47}
48
49impl std::fmt::Display for CubicBezier {
50    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51        f.write_str(&self.to_abbreviated_string())
52    }
53}
54
55impl XmlElement for CubicBezier {
56    /// 对应 Java: CubicBezier 元素名 "CubicBezier"。
57    fn element_name(&self) -> &'static str {
58        "CubicBezier"
59    }
60
61    fn attributes(&self) -> Vec<(String, String)> {
62        Vec::new()
63    }
64
65    /// 覆写 write_xml:文本内容为 B 命令格式。
66    fn write_xml(&self, out: &mut String) {
67        out.push_str("<CubicBezier>");
68        out.push_str(&crate::xml_element::xml_escape(
69            &self.to_abbreviated_string(),
70        ));
71        out.push_str("</CubicBezier>");
72    }
73
74    fn from_xml(node: &XmlNode) -> Result<Self, XmlElementError> {
75        let text = node
76            .text
77            .as_deref()
78            .ok_or_else(|| XmlElementError("CubicBezier 缺少文本内容".to_string()))?;
79        let parts: Vec<&str> = text.split_whitespace().collect();
80        if parts.len() < 7 || parts[0] != "B" {
81            return Err(XmlElementError(format!("CubicBezier 格式错误: {text}")));
82        }
83        let x1: f64 = parts[1]
84            .parse()
85            .map_err(|e| XmlElementError(format!("解析 CubicBezier.x1 失败: {e}")))?;
86        let y1: f64 = parts[2]
87            .parse()
88            .map_err(|e| XmlElementError(format!("解析 CubicBezier.y1 失败: {e}")))?;
89        let x2: f64 = parts[3]
90            .parse()
91            .map_err(|e| XmlElementError(format!("解析 CubicBezier.x2 失败: {e}")))?;
92        let y2: f64 = parts[4]
93            .parse()
94            .map_err(|e| XmlElementError(format!("解析 CubicBezier.y2 失败: {e}")))?;
95        let x3: f64 = parts[5]
96            .parse()
97            .map_err(|e| XmlElementError(format!("解析 CubicBezier.x3 失败: {e}")))?;
98        let y3: f64 = parts[6]
99            .parse()
100            .map_err(|e| XmlElementError(format!("解析 CubicBezier.y3 失败: {e}")))?;
101        Ok(Self::new(x1, y1, x2, y2, x3, y3))
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108    use crate::xml_parse::parse_xml_to_nodes;
109
110    #[test]
111    fn cubic_bezier_new() {
112        let cb = CubicBezier::new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
113        assert_eq!(cb.point1, (1.0, 2.0));
114        assert_eq!(cb.point2, (3.0, 4.0));
115        assert_eq!(cb.point3, (5.0, 6.0));
116    }
117
118    #[test]
119    fn cubic_bezier_to_string() {
120        let cb = CubicBezier::new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
121        assert_eq!(cb.to_abbreviated_string(), "B 1 2 3 4 5 6");
122    }
123
124    #[test]
125    fn cubic_bezier_display() {
126        let cb = CubicBezier::new(0.0, 0.0, 10.0, 10.0, 20.0, 0.0);
127        let s = format!("{cb}");
128        assert!(s.starts_with("B 0 0 10 10 20 0"));
129    }
130
131    #[test]
132    fn cubic_bezier_clone_eq() {
133        let cb = CubicBezier::new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
134        let cb2 = cb.clone();
135        assert_eq!(cb, cb2);
136    }
137
138    #[test]
139    fn test_xml_element_name() {
140        let cb = CubicBezier::new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
141        assert_eq!(cb.element_name(), "CubicBezier");
142    }
143
144    #[test]
145    fn test_xml_element_roundtrip() {
146        let cb = CubicBezier::new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0);
147        let xml = cb.to_xml();
148        assert!(xml.contains("<CubicBezier>"));
149        assert!(xml.contains("B 1 2 3 4 5 6"));
150        let node = parse_xml_to_nodes(&xml).unwrap();
151        let cb2 = CubicBezier::from_xml(&node).unwrap();
152        assert_eq!(cb, cb2);
153    }
154}