Skip to main content

easyofd_core/graph/tight/method/
quadratic_bezier.rs

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