一、文件结构对应关系
C++ 版本结构
rs_units.h // 头文件声明
rs_units.cpp // 实现文件
Rust 版本结构
units/ // 模块目录
├── mod.rs // 主模块(对应 rs_units.h 部分功能)
├── error.rs // 错误类型(C++ 使用异常/错误码)
├── length.rs // 长度单位(对应 RS_Units::Unit 和相关转换)
├── angle.rs // 角度处理(对应角度相关函数)
├── paper.rs // 纸张格式(对应纸张相关函数)
├── format.rs // 格式化(对应各种 format* 函数)
└── parser.rs // 解析器(对应角度解析函数)
二、核心类/结构体对应关系
1. 主类对应
| C++ 类/函数 | Rust 实现 | 说明 |
|---|---|---|
class RS_Units |
struct Units |
主管理器类 |
| 静态方法和成员 | 分离到各功能模块 | Rust 采用模块化设计 |
2. 单位类型对应
| C++ 枚举 | Rust 枚举 | 示例值 |
|---|---|---|
RS2::Unit |
LengthUnit |
Inch, Millimeter 等 |
RS2::LinearFormat |
LinearFormat |
Decimal, Architectural 等 |
RS2::AngleFormat |
AngleFormat |
DegreesDecimal, Surveyors 等 |
RS2::PaperFormat |
PaperFormat |
A4, Letter 等 |
3. 数据结构对应
| C++ 类型 | Rust 类型 | 说明 |
|---|---|---|
RS_Vector |
PaperSize / 自定义 |
简化处理,只保留必要功能 |
QString |
String / &str |
Rust 原生字符串类型 |
三、函数对应关系表
1. 单位管理函数
| C++ 函数 | Rust 函数 | 所在模块 |
|---|---|---|
setCurrentDrawingUnits() |
Units::set_current_drawing_unit() |
mod.rs |
getCurrentDrawingUnits() |
Units::current_drawing_unit() |
mod.rs |
unitToString() |
LengthUnit::to_string() |
length.rs |
stringToUnit() |
LengthUnit::from_string() |
length.rs |
unitToSign() |
LengthUnit::sign() |
length.rs |
isMetric() |
LengthUnit::is_metric() |
length.rs |
dxfint2unit() |
LengthUnit::from_dxf_int() |
length.rs |
2. 转换函数
| C++ 函数 | Rust 函数 | 所在模块 |
|---|---|---|
getFactorToMM() |
LengthUnit::factor_to_mm() |
length.rs |
convert() (double) |
LengthConverter::convert() |
length.rs |
convert() (vector) |
简化处理 | 需要时实现 |
dpiToScale() |
LengthConverter::dpi_to_scale() |
length.rs |
scaleToDpi() |
LengthConverter::scale_to_dpi() |
length.rs |
3. 格式化函数
| C++ 函数 | Rust 函数 | 所在模块 |
|---|---|---|
formatLinear() |
LinearFormatter::format_linear() |
format.rs |
formatScientific() |
内联在 format_linear() 中 |
format.rs |
formatDecimal() |
LinearFormatter::format_decimal() |
format.rs |
formatEngineering() |
LinearFormatter::format_engineering() |
format.rs |
formatArchitectural() |
LinearFormatter::format_architectural() |
format.rs |
formatFractional() |
LinearFormatter::format_fractional() |
format.rs |
formatArchitecturalMetric() |
LinearFormatter::format_architectural_metric() |
format.rs |
formatAngle() |
AngleFormatter::format_angle() |
angle.rs |
FormatUtils::format_angle() |
angle.rs |
4. 角度处理函数
| C++ 函数 | Rust 函数 | 所在模块 |
|---|---|---|
radians_to_degrees() 等 |
同名函数 | angle.rs |
numberToAngleFormat() |
AngleFormat::from_dxf_int() |
angle.rs |
replaceSurveyorsAnglesByDecimalDegrees() |
AngleParser::replace_surveyor_angles() |
parser.rs |
replaceRadiantAnglesByDecimalDegrees() |
AngleParser::replace_radiant_angles() |
parser.rs |
replaceAllPotentialAnglesByDecimalDegrees() |
AngleParser::replace_all_angles() |
parser.rs |
evalAngleValue() |
AngleParser::eval_angle_value() |
parser.rs |
5. 纸张处理函数
| C++ 函数 | Rust 函数 | 所在模块 |
|---|---|---|
paperFormatToSize() |
PaperFormat::size() |
paper.rs |
paperSizeToFormat() |
PaperFormatUtils::format_from_size() |
paper.rs |
paperFormatToString() |
PaperFormat::to_string() |
paper.rs |
stringToPaperFormat() |
PaperFormat::from_string() |
paper.rs |
6. 测试函数
| C++ 函数 | Rust 函数 | 说明 |
|---|---|---|
test() |
各模块的 #[cfg(test)] |
Rust 使用内置测试框架 |
四、实现差异对比
1. 架构设计差异
C++ 实现特点:
- 单一的大型类
RS_Units - 所有方法都是静态的
- 使用 Qt 框架的字符串和国际化
- 错误处理混合使用返回值、异常和调试输出
Rust 实现特点:
- 模块化设计,按功能分离
- 使用 trait 和结构体组织功能
- 纯 Rust 标准库,无外部 GUI 框架依赖
- 统一的错误处理机制
2. 字符串处理差异
C++:
cpp
QString RS_Units::formatDecimal(double length, RS2::Unit unit,
int prec, bool showUnit) {
QString ret = RS_Math::doubleToString(length, prec);
if(showUnit)
return ret + unitToSign(unit);
return ret;
}
Rust:
rust
impl LinearFormatter {
fn format_decimal(
length: f64,
unit: LengthUnit,
precision: i32,
show_unit: bool,
) -> Result<String, UnitError> {
let formatted = Self::double_to_string(length, precision as usize);
if show_unit {
Ok(format!("{}{}", formatted, unit.sign()))
} else {
Ok(formatted)
}
}
}
3. 正则表达式处理差异
C++:
cpp
static QRegularExpression surveyorRegExp = QRegularExpression(
"\\b(?:([NS])([+-]?)..."
QRegularExpression::CaseInsensitiveOption
);
Rust:
rust
lazy_static! {
static ref SURVEYOR_REGEX: Regex = Regex::new(
r"(?i)\b(?:([NS])([+-]?)..."
).unwrap();
}
五、Rust 版本的显著优势
1. 内存安全
- 无悬空指针:Rust 所有权系统确保内存安全
- 无数据竞争:编译器静态检查并发安全
- 自动内存管理 :无需手动
new/delete
对比示例:
C++ 可能的内存错误:
cpp
// 可能的悬空指针或内存泄漏
char* str = unit2string(unit); // 谁负责释放?
Rust 安全版本:
rust
// 自动内存管理,无泄漏风险
let sign: &'static str = unit.sign(); // 编译时确定生命周期
2. 类型系统更强大
- 代数数据类型:枚举可携带数据
- 模式匹配:更安全的条件处理
- 零成本抽象:编译时多态
示例:错误处理
rust
match LengthConverter::convert(value, from, to) {
Ok(result) => println!("结果: {}", result),
Err(UnitError::InvalidFactor) => println!("无效的转换因子"),
Err(UnitError::InvalidLength(msg)) => println!("无效长度: {}", msg),
// 编译器确保处理所有错误变体
}
3. 更好的模块化
- 清晰的模块边界:每个文件一个明确职责
- 访问控制 :
pub关键字控制可见性 - 依赖管理:明确的模块依赖关系
4. 错误处理更完善
C++ 混合方式:
cpp
// 多种错误处理方式混合
bool ok;
double value = evalAngleValue(str, &ok);
if (!ok) {
RS_DEBUG->print(RS_Debug::D_ERROR, "解析失败");
// 或者抛出异常
}
Rust 统一方式:
rust
// Result<T, E> 统一错误处理
let value = AngleParser::eval_angle_value(str)?; // ? 运算符自动传播错误
5. 并发安全性
- 无畏并发:编译器确保线程安全
- Send + Sync trait:明确标记可跨线程类型
6. 构建和依赖管理
- Cargo:一体化构建工具
- Crates.io:中央包仓库
- 版本管理:语义化版本控制
7. 测试框架集成
Rust 内置测试:
rust
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_conversion() {
let result = LengthConverter::convert(10.0, LengthUnit::Inch, LengthUnit::Millimeter);
assert!(result.is_ok());
assert!((result.unwrap() - 254.0).abs() < 1e-10);
}
}
8. 性能优势
- 零成本抽象:高级特性无运行时开销
- 更好的优化:LLVM 后端优化
- 无运行时:最小化运行时依赖
9. 文档和工具链
- rustdoc:自动生成文档
- clippy:代码检查工具
- rustfmt:自动代码格式化
六、Rust 特定改进
1. 更安全的数值处理
rust
// Rust 防止整数溢出
pub fn is_in_range(value: f64) -> bool {
if value.is_nan() || value.is_infinite() {
return false;
}
// 编译时检查类型边界
value.abs() <= u32::MAX as f64
}
2. 不可变性默认
rust
// 默认不可变,更安全
let unit = LengthUnit::Inch;
// unit = LengthUnit::Meter; // 编译错误,除非声明 mut
3. 模式匹配优势
rust
impl LengthUnit {
pub fn factor_to_mm(&self) -> f64 {
match self {
LengthUnit::Inch => 25.4,
LengthUnit::Foot => 304.8,
// 编译器确保处理所有变体
_ => 1.0,
}
}
}
4. trait 系统扩展性
rust
// 可轻松添加新功能
pub trait UnitDisplay {
fn display_name(&self) -> &str;
fn display_sign(&self) -> &str;
}
impl UnitDisplay for LengthUnit {
fn display_name(&self) -> &str {
self.to_string()
}
fn display_sign(&self) -> &str {
self.sign()
}
}
七、潜在改进方向
1. 国际化支持
可添加 i18n 模块支持多语言:
rust
pub mod i18n {
use crate::LengthUnit;
pub trait Localized {
fn localized_name(&self, locale: &str) -> &str;
}
impl Localized for LengthUnit {
fn localized_name(&self, locale: &str) -> &str {
match (self, locale) {
(LengthUnit::Inch, "zh-CN") => "英寸",
(LengthUnit::Millimeter, "zh-CN") => "毫米",
_ => self.to_string(),
}
}
}
}
2. 序列化支持
使用 serde 支持 JSON/YAML 序列化:
rust
use serde::{Serialize, Deserialize};
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum LengthUnit {
#[serde(rename = "inch")]
Inch,
#[serde(rename = "mm")]
Millimeter,
// ...
}
3. 异步支持
可支持异步计算:
rust
pub async fn convert_async(
value: f64,
from: LengthUnit,
to: LengthUnit,
) -> Result<f64, UnitError> {
// 异步转换逻辑
tokio::task::spawn_blocking(move || {
LengthConverter::convert(value, from, to)
}).await?
}
八、总结
Rust 版本的核心优势:
- 安全性:内存安全、线程安全、类型安全
- 可维护性:清晰的模块化、更好的错误处理
- 性能:零成本抽象、更好的编译器优化
- 现代性:现代化的工具链和生态系统
- 可靠性:编译器强制的最佳实践
这个 Rust 实现不仅完整对应了 C++ 版本的功能,还在安全性、可维护性和扩展性方面有显著改进,是现代化 CAD 软件开发的优秀选择。
LibreCAD 单位转换 Rust 源码
1. error.rs - 错误类型定义
rust
use thiserror::Error;
#[derive(Error, Debug, Clone, PartialEq)]
pub enum UnitError {
#[error("无效的单位转换因子")]
InvalidFactor,
#[error("无效的长度值: {0}")]
InvalidLength(String),
#[error("无效的角度格式: {0}")]
InvalidAngleFormat(String),
#[error("无效的纸张格式: {0}")]
InvalidPaperFormat(String),
#[error("解析错误: {0}")]
ParseError(String),
#[error("超出范围的数值")]
OutOfRange,
#[error("未知的单位类型")]
UnknownUnit,
}
2. length.rs - 长度单位和转换
rust
use crate::error::UnitError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LengthUnit {
None, Inch, Foot, Mile, Millimeter, Centimeter, Meter, Kilometer,
Microinch, Mil, Yard, Angstrom, Nanometer, Micron, Decimeter,
Decameter, Hectometer, Gigameter, Astro, Lightyear, Parsec,
}
impl LengthUnit {
pub fn factor_to_mm(&self) -> f64 {
match self {
Self::None | Self::Millimeter => 1.0,
Self::Inch => 25.4,
Self::Foot => 304.8,
Self::Mile => 1.609344e6,
Self::Centimeter => 10.0,
Self::Meter => 1e3,
Self::Kilometer => 1e6,
Self::Microinch => 2.54e-5,
Self::Mil => 0.0254,
Self::Yard => 914.4,
Self::Angstrom => 1e-7,
Self::Nanometer => 1e-6,
Self::Micron => 1e-3,
Self::Decimeter => 100.0,
Self::Decameter => 1e4,
Self::Hectometer => 1e5,
Self::Gigameter => 1e9,
Self::Astro => 1.495978707e14,
Self::Lightyear => 9.4607304725808e18,
Self::Parsec => 3.0856776e19,
}
}
pub fn is_metric(&self) -> bool {
matches!(
self,
Self::Millimeter | Self::Centimeter | Self::Meter | Self::Kilometer |
Self::Angstrom | Self::Nanometer | Self::Micron | Self::Decimeter |
Self::Decameter | Self::Hectometer | Self::Gigameter |
Self::Astro | Self::Lightyear | Self::Parsec
)
}
pub fn sign(&self) -> &'static str {
match self {
Self::None => "",
Self::Inch => "\"",
Self::Foot => "'",
Self::Mile => "mi",
Self::Millimeter => "mm",
Self::Centimeter => "cm",
Self::Meter => "m",
Self::Kilometer => "km",
Self::Microinch => "µ\"",
Self::Mil => "mil",
Self::Yard => "yd",
Self::Angstrom => "A",
Self::Nanometer => "nm",
Self::Micron => "µm",
Self::Decimeter => "dm",
Self::Decameter => "dam",
Self::Hectometer => "hm",
Self::Gigameter => "Gm",
Self::Astro => "astro",
Self::Lightyear => "ly",
Self::Parsec => "pc",
}
}
pub fn to_string(&self) -> &'static str {
match self {
Self::None => "None",
Self::Inch => "Inch",
Self::Foot => "Foot",
Self::Mile => "Mile",
Self::Millimeter => "Millimeter",
Self::Centimeter => "Centimeter",
Self::Meter => "Meter",
Self::Kilometer => "Kilometer",
Self::Microinch => "Microinch",
Self::Mil => "Mil",
Self::Yard => "Yard",
Self::Angstrom => "Angstrom",
Self::Nanometer => "Nanometer",
Self::Micron => "Micron",
Self::Decimeter => "Decimeter",
Self::Decameter => "Decameter",
Self::Hectometer => "Hectometer",
Self::Gigameter => "Gigameter",
Self::Astro => "Astro",
Self::Lightyear => "Lightyear",
Self::Parsec => "Parsec",
}
}
pub fn from_string(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"none" => Some(Self::None),
"inch" => Some(Self::Inch),
"foot" => Some(Self::Foot),
"mile" => Some(Self::Mile),
"millimeter" => Some(Self::Millimeter),
"centimeter" => Some(Self::Centimeter),
"meter" => Some(Self::Meter),
"kilometer" => Some(Self::Kilometer),
"microinch" => Some(Self::Microinch),
"mil" => Some(Self::Mil),
"yard" => Some(Self::Yard),
"angstrom" => Some(Self::Angstrom),
"nanometer" => Some(Self::Nanometer),
"micron" => Some(Self::Micron),
"decimeter" => Some(Self::Decimeter),
"decameter" => Some(Self::Decameter),
"hectometer" => Some(Self::Hectometer),
"gigameter" => Some(Self::Gigameter),
"astro" => Some(Self::Astro),
"lightyear" => Some(Self::Lightyear),
"parsec" => Some(Self::Parsec),
_ => None,
}
}
pub fn from_dxf_int(dxfint: i32) -> Self {
match dxfint {
0 => Self::None, 1 => Self::Inch, 2 => Self::Foot, 3 => Self::Mile,
4 => Self::Millimeter, 5 => Self::Centimeter, 6 => Self::Meter,
7 => Self::Kilometer, 8 => Self::Microinch, 9 => Self::Mil,
10 => Self::Yard, 11 => Self::Angstrom, 12 => Self::Nanometer,
13 => Self::Micron, 14 => Self::Decimeter, 15 => Self::Decameter,
16 => Self::Hectometer, 17 => Self::Gigameter, 18 => Self::Astro,
19 => Self::Lightyear, 20 => Self::Parsec, _ => Self::None,
}
}
}
pub struct LengthConverter;
impl LengthConverter {
pub fn convert(value: f64, from: LengthUnit, to: LengthUnit) -> Result<f64, UnitError> {
if value.is_nan() || value.is_infinite() {
return Err(UnitError::InvalidLength("无效的数值".to_string()));
}
let factor_to = to.factor_to_mm();
if factor_to <= 0.0 {
return Err(UnitError::InvalidFactor);
}
Ok((value * from.factor_to_mm()) / factor_to)
}
pub fn is_in_range(value: f64) -> bool {
if value.is_nan() || value.is_infinite() {
return false;
}
value.abs() <= u32::MAX as f64
}
pub fn dpi_to_scale(dpi: f64, unit: LengthUnit) -> Result<f64, UnitError> {
Self::convert(1.0, LengthUnit::Inch, unit).map(|inch_per_unit| inch_per_unit / dpi)
}
pub fn scale_to_dpi(scale: f64, unit: LengthUnit) -> Result<f64, UnitError> {
Self::convert(1.0, LengthUnit::Inch, unit).map(|inch_per_unit| inch_per_unit / scale)
}
}
3. angle.rs - 角度单位和转换
rust
use std::f64::consts::PI;
use crate::error::UnitError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AngleUnit {
Radians, Degrees, Gradians,
}
impl AngleUnit {
pub fn to_radians(&self, value: f64) -> f64 {
match self {
Self::Radians => value,
Self::Degrees => value * PI / 180.0,
Self::Gradians => value * PI / 200.0,
}
}
pub fn from_radians(&self, radians: f64) -> f64 {
match self {
Self::Radians => radians,
Self::Degrees => radians * 180.0 / PI,
Self::Gradians => radians * 200.0 / PI,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum AngleFormat {
DegreesDecimal, DegreesMinutesSeconds, Gradians, Radians, Surveyors,
}
impl AngleFormat {
pub fn from_dxf_int(num: i32) -> Self {
match num {
0 => Self::DegreesDecimal,
1 => Self::DegreesMinutesSeconds,
2 => Self::Gradians,
3 => Self::Radians,
4 => Self::Surveyors,
_ => Self::DegreesDecimal,
}
}
}
pub struct AngleFormatter;
impl AngleFormatter {
pub fn format_angle(
radians: f64,
format: AngleFormat,
precision: i32,
) -> Result<String, UnitError> {
let normalized = radians % (2.0 * PI);
match format {
AngleFormat::DegreesDecimal => {
let degrees = radians_to_degrees(normalized);
let formatted = double_to_string(degrees, precision as usize);
Ok(format!("{}°", formatted))
}
AngleFormat::DegreesMinutesSeconds => {
let mut degrees = radians_to_degrees(normalized);
let deg_whole = degrees.floor() as u32;
degrees -= deg_whole as f64;
let minutes_total = degrees * 60.0;
let min_whole = minutes_total.floor() as u32;
let seconds = (minutes_total - min_whole as f64) * 60.0;
match precision {
0 => Ok(format!("{}°", deg_whole)),
1 => Ok(format!("{}° {}'", deg_whole, min_whole)),
_ => {
let seconds_str = double_to_string(seconds, (precision - 2).max(0) as usize);
Ok(format!("{}° {}' {}\"", deg_whole, min_whole, seconds_str))
}
}
}
AngleFormat::Gradians => {
let grads = radians_to_gradians(normalized);
let formatted = double_to_string(grads, precision as usize);
Ok(format!("{}g", formatted))
}
AngleFormat::Radians => {
let formatted = double_to_string(normalized, precision as usize);
Ok(format!("{}r", formatted))
}
AngleFormat::Surveyors => {
let degrees = radians_to_degrees(normalized);
let quadrant = (degrees / 90.0).floor() as u32;
let (prefix, suffix, adjusted_degrees) = match quadrant {
0 => ("N", "E", degrees),
1 => ("S", "E", 180.0 - degrees),
2 => ("S", "W", degrees - 180.0),
3 => ("N", "W", 360.0 - degrees),
_ => ("", "", degrees),
};
let angle_str = Self::format_angle(
degrees_to_radians(adjusted_degrees),
AngleFormat::DegreesMinutesSeconds,
precision,
)?;
let cleaned = angle_str.replace("°", "d").replace(" ", "").replace("\"", "");
Ok(format!("{}{}{}", prefix, cleaned, suffix))
}
}
}
}
pub fn radians_to_degrees(radians: f64) -> f64 {
radians * 180.0 / PI
}
pub fn degrees_to_radians(degrees: f64) -> f64 {
degrees * PI / 180.0
}
pub fn radians_to_gradians(radians: f64) -> f64 {
radians * 200.0 / PI
}
pub fn gradians_to_degrees(gradians: f64) -> f64 {
gradians * 0.9
}
pub fn gradians_to_radians(gradians: f64) -> f64 {
gradians * PI / 200.0
}
fn double_to_string(value: f64, precision: usize) -> String {
format!("{:.prec$}", value, prec = precision)
}
4. paper.rs - 纸张格式处理
rust
use crate::error::UnitError;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum PaperFormat {
Custom, A0, A1, A2, A3, A4, Letter, Legal, Tabloid,
AnsiC, AnsiD, AnsiE, ArchA, ArchB, ArchC, ArchD, ArchE,
}
#[derive(Debug, Clone, Copy)]
pub struct PaperSize {
pub width: f64,
pub height: f64,
}
impl PaperSize {
pub fn new(width: f64, height: f64) -> Self {
Self { width, height }
}
pub fn distance_to(&self, other: &Self) -> f64 {
let dx = self.width - other.width;
let dy = self.height - other.height;
(dx * dx + dy * dy).sqrt()
}
}
impl PaperFormat {
pub fn size(&self) -> PaperSize {
match self {
Self::Custom => PaperSize::new(0.0, 0.0),
Self::A0 => PaperSize::new(841.0, 1189.0),
Self::A1 => PaperSize::new(594.0, 841.0),
Self::A2 => PaperSize::new(420.0, 594.0),
Self::A3 => PaperSize::new(297.0, 420.0),
Self::A4 => PaperSize::new(210.0, 297.0),
Self::Letter => PaperSize::new(215.9, 279.4),
Self::Legal => PaperSize::new(215.9, 355.6),
Self::Tabloid => PaperSize::new(279.4, 431.8),
Self::AnsiC => PaperSize::new(431.8, 558.8),
Self::AnsiD => PaperSize::new(558.8, 863.6),
Self::AnsiE => PaperSize::new(863.6, 1117.6),
Self::ArchA => PaperSize::new(228.6, 304.8),
Self::ArchB => PaperSize::new(304.8, 457.2),
Self::ArchC => PaperSize::new(457.2, 609.6),
Self::ArchD => PaperSize::new(609.6, 914.4),
Self::ArchE => PaperSize::new(914.4, 1219.2),
}
}
pub fn to_string(&self) -> &'static str {
match self {
Self::Custom => "Custom",
Self::A0 => "A0",
Self::A1 => "A1",
Self::A2 => "A2",
Self::A3 => "A3",
Self::A4 => "A4",
Self::Letter => "Letter",
Self::Legal => "Legal",
Self::Tabloid => "Tabloid",
Self::AnsiC => "ANSI C",
Self::AnsiD => "ANSI D",
Self::AnsiE => "ANSI E",
Self::ArchA => "Arch A",
Self::ArchB => "Arch B",
Self::ArchC => "Arch C",
Self::ArchD => "Arch D",
Self::ArchE => "Arch E",
}
}
pub fn from_string(s: &str) -> Result<Self, UnitError> {
match s.to_lowercase().as_str() {
"custom" => Ok(Self::Custom),
"a0" => Ok(Self::A0),
"a1" => Ok(Self::A1),
"a2" => Ok(Self::A2),
"a3" => Ok(Self::A3),
"a4" => Ok(Self::A4),
"letter" => Ok(Self::Letter),
"legal" => Ok(Self::Legal),
"tabloid" => Ok(Self::Tabloid),
"ansi c" => Ok(Self::AnsiC),
"ansi d" => Ok(Self::AnsiD),
"ansi e" => Ok(Self::AnsiE),
"arch a" => Ok(Self::ArchA),
"arch b" => Ok(Self::ArchB),
"arch c" => Ok(Self::ArchC),
"arch d" => Ok(Self::ArchD),
"arch e" => Ok(Self::ArchE),
_ => Err(UnitError::InvalidPaperFormat(s.to_string())),
}
}
}
pub struct PaperFormatUtils;
impl PaperFormatUtils {
pub fn format_from_size(size: PaperSize) -> PaperFormat {
let formats = [
PaperFormat::A0, PaperFormat::A1, PaperFormat::A2, PaperFormat::A3, PaperFormat::A4,
PaperFormat::Letter, PaperFormat::Legal, PaperFormat::Tabloid,
PaperFormat::AnsiC, PaperFormat::AnsiD, PaperFormat::AnsiE,
PaperFormat::ArchA, PaperFormat::ArchB, PaperFormat::ArchC,
PaperFormat::ArchD, PaperFormat::ArchE,
];
for format in formats.iter() {
let format_size = format.size();
if size.distance_to(&format_size) < 1e-4 {
return *format;
}
let rotated = PaperSize::new(format_size.height, format_size.width);
if size.distance_to(&rotated) < 1e-4 {
return *format;
}
}
PaperFormat::Custom
}
}
5. format.rs - 格式化功能
rust
use crate::error::UnitError;
use crate::length::{LengthUnit, LengthConverter};
use crate::angle::AngleFormatter;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum LinearFormat {
Scientific, Decimal, Engineering, Architectural, Fractional, ArchitecturalMetric,
}
pub struct LinearFormatter;
impl LinearFormatter {
pub fn format_linear(
length: f64,
unit: LengthUnit,
format: LinearFormat,
precision: i32,
show_unit: bool,
) -> Result<String, UnitError> {
if !LengthConverter::is_in_range(length) {
return Err(UnitError::InvalidLength("长度超出有效范围".to_string()));
}
match format {
LinearFormat::Scientific => Self::format_scientific(length, unit, precision, show_unit),
LinearFormat::Decimal => Self::format_decimal(length, unit, precision, show_unit),
LinearFormat::Engineering => Self::format_engineering(length, unit, precision, show_unit),
LinearFormat::Architectural => Self::format_architectural(length, unit, precision, show_unit),
LinearFormat::Fractional => Self::format_fractional(length, unit, precision, show_unit),
LinearFormat::ArchitecturalMetric => Self::format_architectural_metric(length, unit, precision, show_unit),
}
}
fn format_scientific(
length: f64,
unit: LengthUnit,
precision: i32,
show_unit: bool,
) -> Result<String, UnitError> {
let formatted = format!("{:.prec$e}", length, prec = precision as usize);
if show_unit {
Ok(format!("{}{}", formatted, unit.sign()))
} else {
Ok(formatted)
}
}
fn format_decimal(
length: f64,
unit: LengthUnit,
precision: i32,
show_unit: bool,
) -> Result<String, UnitError> {
let formatted = Self::double_to_string(length, precision as usize);
if show_unit {
Ok(format!("{}{}", formatted, unit.sign()))
} else {
Ok(formatted)
}
}
fn format_engineering(
length: f64,
unit: LengthUnit,
precision: i32,
_show_unit: bool,
) -> Result<String, UnitError> {
let sign = if length < 0.0 { "-" } else { "" };
let abs_length = length.abs();
let feet = LengthConverter::convert(abs_length, unit, LengthUnit::Foot)? as u32;
let total_inches = LengthConverter::convert(abs_length, unit, LengthUnit::Inch)?;
let inches = total_inches - (feet as f64 * 12.0);
let inches_str = Self::double_to_string(inches, precision as usize);
let result = if feet > 0 {
format!("{}{}'-{}\"", sign, feet, inches_str)
} else {
format!("{}{}\"", sign, inches_str)
};
Ok(result)
}
fn format_architectural(
length: f64,
unit: LengthUnit,
precision: i32,
_show_unit: bool,
) -> Result<String, UnitError> {
let sign = if length < 0.0 { "-" } else { "" };
let abs_length = length.abs();
let mut feet = LengthConverter::convert(abs_length, unit, LengthUnit::Foot)? as u32;
let total_inches = LengthConverter::convert(abs_length, unit, LengthUnit::Inch)?;
let mut inches = total_inches - (feet as f64 * 12.0);
if inches >= 12.0 {
let additional_feet = (inches / 12.0) as u32;
feet += additional_feet;
inches -= additional_feet as f64 * 12.0;
}
let inches_str = Self::format_fractional(inches, LengthUnit::Inch, precision, false)?;
if feet > 0 {
Ok(format!("{}{}'-{}\"", sign, feet, inches_str))
} else {
Ok(format!("{}\"", inches_str))
}
}
fn format_fractional(
length: f64,
unit: LengthUnit,
precision: i32,
_show_unit: bool,
) -> Result<String, UnitError> {
if length < 0.0 {
let sign = if length + 1e-10 < 0.0 { "- " } else { "" };
let abs_result = Self::format_fractional(length.abs(), unit, precision, false)?;
return Ok(format!("{}{}", sign, abs_result));
}
let whole = length.floor() as u32;
let denominator = 2u32.pow((precision + 1) as u32);
let fraction = length - whole as f64;
let mut numerator = (fraction * denominator as f64).round() as u32;
if numerator == denominator {
numerator = 0;
}
if numerator != 0 && denominator != 0 {
let gcd = Self::find_gcd(numerator, denominator);
if gcd > 0 {
numerator /= gcd;
let denominator = denominator / gcd;
return Self::build_fraction_string(whole, numerator, denominator);
}
}
Self::build_fraction_string(whole, numerator, denominator)
}
fn format_architectural_metric(
length: f64,
unit: LengthUnit,
precision: i32,
show_unit: bool,
) -> Result<String, UnitError> {
let sign = if length < 0.0 { "-" } else { "" };
let abs_length = length.abs();
let mut formatted = Self::double_to_string(abs_length, (precision + 1) as usize);
if let Some(last_char) = formatted.chars().last() {
if let Some(last_digit) = last_char.to_digit(10) {
if (3..8).contains(&last_digit) {
formatted.pop();
formatted.push('⁵');
}
}
}
if formatted.starts_with('0') {
if let Some(dot_pos) = formatted.find('.') {
let decimal_part = &formatted[dot_pos + 1..];
let trimmed = decimal_part.trim_start_matches('0');
formatted = trimmed.to_string();
}
}
if show_unit {
Ok(format!("{}{} {}", sign, formatted, unit.sign()))
} else {
Ok(format!("{}{}", sign, formatted))
}
}
fn build_fraction_string(whole: u32, numerator: u32, denominator: u32) -> Result<String, UnitError> {
let result = if whole > 0 && numerator > 0 {
format!("{} {}/{}", whole, numerator, denominator)
} else if numerator > 0 {
format!("{}/{}", numerator, denominator)
} else if whole > 0 {
format!("{}", whole)
} else {
"0".to_string()
};
Ok(result)
}
fn find_gcd(mut a: u32, mut b: u32) -> u32 {
while b != 0 {
let temp = b;
b = a % b;
a = temp;
}
a
}
fn double_to_string(value: f64, precision: usize) -> String {
format!("{:.prec$}", value, prec = precision)
}
}
pub struct FormatUtils;
impl FormatUtils {
pub fn format_angle(
radians: f64,
format: crate::angle::AngleFormat,
precision: i32,
) -> Result<String, UnitError> {
AngleFormatter::format_angle(radians, format, precision)
}
}
6. parser.rs - 角度解析功能
rust
use regex::Regex;
use lazy_static::lazy_static;
use crate::error::UnitError;
use crate::angle::{radians_to_degrees, gradians_to_degrees};
lazy_static! {
static ref SURVEYOR_REGEX: Regex = Regex::new(
r"(?i)\b(?:([NS])([+-]?)(?:(?:(\d*\.?\d*)[d°])?(?:(\d*\.?\d*)')?(?:(\d*\.?\d*)")?|(\d*))([EW]))?|([EW])\b"
).unwrap();
static ref RADIANT_REGEX: Regex = Regex::new(
r"((?:\.\d+)|(?:\d+\.\d*)|(?:\d+))r\b"
).unwrap();
static ref GRAD_REGEX: Regex = Regex::new(
r"((?:\.\d+)|(?:\d+\.\d*)|(?:\d+))g\b"
).unwrap();
static ref EXPLICIT_DEGREES_REGEX: Regex = Regex::new(
r"(?:[^a-zA-Z0-9]|^)((?:(?:(\d*\.?\d*)[d°])(?:(\d*\.?\d*)')?(?:(\d*\.?\d*)")?))(?:[^\d]|$)"
).unwrap();
}
pub struct AngleParser;
impl AngleParser {
pub fn replace_surveyor_angles(text: &str) -> Result<String, UnitError> {
let mut result = text.to_string();
for cap in SURVEYOR_REGEX.captures_iter(text) {
let full_match = cap.get(0).unwrap().as_str();
let mut angle = 0.0;
let mut sign = "";
if let Some(dir) = cap.get(8) {
match dir.as_str().to_uppercase().as_str() {
"E" => angle = 0.0,
"W" => angle = 180.0,
_ => return Err(UnitError::InvalidAngleFormat("无效的方位角".to_string())),
}
} else if let Some(cardinal1) = cap.get(1) {
let north = cardinal1.as_str().to_uppercase() == "N";
sign = cap.get(2).map(|s| s.as_str()).unwrap_or("");
let degrees = cap.get(3)
.or_else(|| cap.get(6))
.and_then(|m| m.as_str().parse::<f64>().ok())
.unwrap_or(0.0);
let minutes = cap.get(4)
.and_then(|m| m.as_str().parse::<f64>().ok())
.unwrap_or(0.0);
let seconds = cap.get(5)
.and_then(|m| m.as_str().parse::<f64>().ok())
.unwrap_or(0.0);
if let Some(cardinal2) = cap.get(7) {
let east = cardinal2.as_str().to_uppercase() == "E";
let base = if north { 90.0 } else { 270.0 };
let dir = if (north && east) || (!north && !east) { 1.0 } else { -1.0 };
angle = base + dir * (degrees + minutes / 60.0 + seconds / 3600.0);
}
}
let replacement = format!("{}{}", sign, angle);
result = result.replacen(full_match, &replacement, 1);
}
Ok(result)
}
pub fn replace_radiant_angles(text: &str) -> Result<String, UnitError> {
let mut result = text.to_string();
for cap in RADIANT_REGEX.captures_iter(text) {
let full_match = cap.get(0).unwrap().as_str();
if let Some(value) = cap.get(1) {
if let Ok(rad) = value.as_str().parse::<f64>() {
let degrees = radians_to_degrees(rad);
result = result.replacen(full_match, °rees.to_string(), 1);
}
}
}
Ok(result)
}
pub fn replace_grad_angles(text: &str) -> Result<String, UnitError> {
let mut result = text.to_string();
for cap in GRAD_REGEX.captures_iter(text) {
let full_match = cap.get(0).unwrap().as_str();
if let Some(value) = cap.get(1) {
if let Ok(grad) = value.as_str().parse::<f64>() {
let degrees = gradians_to_degrees(grad);
result = result.replacen(full_match, °rees.to_string(), 1);
}
}
}
Ok(result)
}
pub fn replace_explicit_degrees(text: &str) -> Result<String, UnitError> {
let mut result = text.to_string();
for cap in EXPLICIT_DEGREES_REGEX.captures_iter(text) {
let full_match = cap.get(0).unwrap().as_str();
let group = cap.get(1).unwrap();
let degrees = cap.get(2)
.and_then(|m| m.as_str().parse::<f64>().ok())
.unwrap_or(0.0);
let minutes = cap.get(3)
.and_then(|m| m.as_str().parse::<f64>().ok())
.unwrap_or(0.0);
let seconds = cap.get(4)
.and_then(|m| m.as_str().parse::<f64>().ok())
.unwrap_or(0.0);
let angle = degrees + minutes / 60.0 + seconds / 3600.0;
result = result.replacen(group.as_str(), &angle.to_string(), 1);
}
Ok(result)
}
pub fn replace_all_angles(text: &str) -> Result<String, UnitError> {
let mut result = text.to_string();
result = Self::replace_surveyor_angles(&result)?;
result = Self::replace_radiant_angles(&result)?;
result = Self::replace_grad_angles(&result)?;
result = Self::replace_explicit_degrees(&result)?;
Ok(result)
}
pub fn eval_angle_value(expr: &str) -> Result<f64, UnitError> {
let normalized = Self::replace_all_angles(expr)?;
if let Ok(value) = normalized.parse::<f64>() {
Ok(value)
} else {
Self::eval_simple_expression(&normalized)
}
}
fn eval_simple_expression(expr: &str) -> Result<f64, UnitError> {
let expr = expr.trim();
if let Some(pos) = expr.find('+') {
let left = expr[..pos].trim().parse::<f64>()
.map_err(|_| UnitError::ParseError("无法解析表达式".to_string()))?;
let right = expr[pos+1..].trim().parse::<f64>()
.map_err(|_| UnitError::ParseError("无法解析表达式".to_string()))?;
Ok(left + right)
} else if let Some(pos) = expr.find('-') {
if pos > 0 {
let left = expr[..pos].trim().parse::<f64>()
.map_err(|_| UnitError::ParseError("无法解析表达式".to_string()))?;
let right = expr[pos+1..].trim().parse::<f64>()
.map_err(|_| UnitError::ParseError("无法解析表达式".to_string()))?;
Ok(left - right)
} else {
expr.parse::<f64>()
.map_err(|_| UnitError::ParseError("无法解析表达式".to_string()))
}
} else {
expr.parse::<f64>()
.map_err(|_| UnitError::ParseError("无法解析表达式".to_string()))
}
}
}
7. mod.rs - 主模块文件
rust
mod error;
mod length;
mod angle;
mod paper;
mod format;
mod parser;
pub use error::UnitError;
pub use length::{LengthUnit, LengthConverter};
pub use angle::{AngleUnit, AngleFormat, AngleFormatter};
pub use paper::{PaperFormat, PaperSize, PaperFormatUtils};
pub use format::{LinearFormat, LinearFormatter, FormatUtils};
pub use parser::AngleParser;
#[derive(Debug)]
pub struct Units {
current_drawing_unit: LengthUnit,
}
impl Default for Units {
fn default() -> Self {
Self {
current_drawing_unit: LengthUnit::Millimeter,
}
}
}
impl Units {
pub fn new() -> Self {
Self::default()
}
pub fn set_current_drawing_unit(&mut self, unit: LengthUnit) {
self.current_drawing_unit = unit;
}
pub fn current_drawing_unit(&self) -> LengthUnit {
self.current_drawing_unit
}
pub fn convert_default(&self, value: f64) -> Result<f64, UnitError> {
LengthConverter::convert(value, LengthUnit::Millimeter, self.current_drawing_unit)
}
}
8. 使用示例
rust
// 使用示例
use units::{
Units, LengthUnit, LengthConverter, LinearFormat, LinearFormatter,
AngleFormat, FormatUtils, PaperFormat, AngleParser,
};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// 创建单位管理器
let mut units = Units::new();
units.set_current_drawing_unit(LengthUnit::Millimeter);
// 1. 单位转换
let mm = LengthConverter::convert(10.0, LengthUnit::Inch, LengthUnit::Millimeter)?;
println!("10英寸 = {:.2}毫米", mm);
// 2. 格式化显示
let formatted = LinearFormatter::format_linear(
65.125,
LengthUnit::Inch,
LinearFormat::Architectural,
3,
true,
)?;
println!("建筑格式: {}", formatted);
// 3. 角度格式化
use std::f64::consts::PI;
let angle_str = FormatUtils::format_angle(PI/4.0, AngleFormat::DegreesDecimal, 2)?;
println!("45度角度: {}", angle_str);
// 4. 纸张格式
let a4_size = PaperFormat::A4.size();
println!("A4纸张: {} x {} mm", a4_size.width, a4_size.height);
// 5. 角度解析
let angle_value = AngleParser::eval_angle_value("45°30'")?;
println!("解析的角度: {} 度", angle_value);
Ok(())
}