1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
use crate::{dstv_element::DstvElement, get_f64_from_str, validate_flange};
/// A struct representing the outer border of a DSTV file
/// A DSTV file can have multiple outer borders
pub struct OuterBorder {
/// A vector of border points, representing the contour of the outer border
pub contour: Vec<BorderPoint>,
}
/// A struct representing the inner border of a DSTV file
/// A DSTV file can have multiple inner borders
pub struct InnerBorder {
/// A vector of border points, representing the contour of the inner border
pub contour: Vec<BorderPoint>,
}
/// A struct representing a border point
/// A border point is a point on the contour of a border
/// It has an x and y coordinate and a radius
#[derive(Clone, Debug, Default)]
pub struct BorderPoint {
/// The flange code of the border point
pub fl_code: String,
/// The x coordinate of the border point
pub x_coord: f64,
/// The y coordinate of the border point
pub y_coord: f64,
/// The radius of the border point
pub radius: f64,
pub bevel: f64,
}
/// Reads the contour of a border from a DSTV file.
/// The contour is represented by a vector of BorderPoints
/// # Arguments
/// * `lines` - A vector of strings, representing the lines of the DSTV file
/// # Returns
/// A vector of BorderPoints, representing the contour of the border
/// # Panics
/// Panics if the flange code of a border point is invalid
/// Panics if the x coordinate of a border point is invalid
/// Panics if the y coordinate of a border point is invalid
/// Panics if the radius of a border point is invalid
fn read_contour(lines: &[&str]) -> Vec<BorderPoint> {
lines
.iter()
.map(|line| {
let mut iter = line.split_whitespace().peekable();
let first = iter.peek();
let fl_code = match validate_flange(first.unwrap_or(&"x")) {
true => iter.next().unwrap(),
false => "x",
};
let x_coord = get_f64_from_str(iter.next(), "x_coord");
let y_coord = get_f64_from_str(iter.next(), "y_coord");
let radius = get_f64_from_str(iter.next(), "radius");
let bevel = match iter.peek().is_some() {
true => get_f64_from_str(iter.next(), "bevel"),
false => 0.0,
};
BorderPoint {
fl_code: fl_code.to_string(),
x_coord,
y_coord,
radius,
bevel,
}
})
.collect()
}
/// calculates the bend between two border points if the previous border point has a radius.
/// # Arguments
/// * `point` - The current border point
/// * `prev` - The previous border point
/// # Returns
/// A tuple of four f64 values representing the bend
fn get_bend(point: &BorderPoint, prev: &BorderPoint) -> (f64, f64, f64, f64) {
match (prev.y_coord > point.y_coord, point.x_coord > prev.x_coord) {
(true, true) => (prev.x_coord, point.y_coord, point.x_coord, point.y_coord), // left-top corner
(false, true) => (point.x_coord, prev.y_coord, point.x_coord, point.y_coord), // top-right corner
(false, false) => (prev.x_coord, point.y_coord, point.x_coord, point.y_coord), // right-bottom corner
(true, false) => (point.x_coord, prev.y_coord, point.x_coord, point.y_coord), // bottom-left corner
}
}
/// Converts a contour to an SVG path
/// # Arguments
/// * `contour` - A vector of BorderPoints, representing the contour of a border
/// * `color` - A string representing the color of the border
/// # Returns
/// A string representing the SVG path of the border
fn contour_to_svg(contour: &Vec<BorderPoint>, color: &str) -> String {
let mut bevel_lines = Vec::new();
let (path_str, _) = contour.iter().enumerate().fold(
(String::new(), BorderPoint::default()),
|(mut path, prev), (i, point)| {
let segment = if i == 0 {
format!("M{},{} ", point.x_coord - point.radius, point.y_coord)
} else if prev.radius > 0.0 {
let (x1, y1, x2, y2) = get_bend(point, &prev);
format!("Q{},{},{},{} ", x1, y1, x2, y2)
} else {
format!("L{},{} ", point.x_coord, point.y_coord)
};
if prev.bevel > 0.0 {
let bevel_line = format!(
"<line x1=\"{}\" y1=\"{}\" x2=\"{}\" y2=\"{}\" stroke=\"red\" stroke-width=\"4\" />",
prev.x_coord, prev.y_coord, point.x_coord, point.y_coord
);
bevel_lines.push(bevel_line);
}
path.push_str(&segment);
(path, point.clone())
},
);
format!(
"<path d=\"{}\" fill=\"{}\" stroke=\"black\" stroke-width=\"0.5\" />{}",
path_str,
color,
bevel_lines.join("")
)
}
impl OuterBorder {
/// Creates a new OuterBorder from a vector of BorderPoints
/// # Arguments
/// * `lines` - A vector of string slices, representing the contour of the border
pub fn from_lines(lines: &[&str]) -> Self {
Self {
contour: read_contour(lines),
}
}
}
impl InnerBorder {
/// Creates a new InnerBorder from a vector of BorderPoints
/// # Arguments
/// * `lines` - A vector of string slices, representing the contour of the border
pub fn from_lines(lines: &[&str]) -> Self {
Self {
contour: read_contour(lines),
}
}
}
impl DstvElement for OuterBorder {
/// Converts the outer border to an SVG path
/// # Returns
/// A string representing the SVG path of the outer border
fn to_svg(&self) -> String {
contour_to_svg(&self.contour, "grey")
}
fn from_str(_line: &str) -> Result<Self, &'static str> {
todo!("Find out how to split traits and casts when when calling in a idiomatic way");
}
fn get_index(&self) -> usize {
0
}
}
impl DstvElement for InnerBorder {
/// Converts the inner border to an SVG path
/// # Returns
/// A string representing the SVG path of the inner border
fn to_svg(&self) -> String {
contour_to_svg(&self.contour, "white")
}
fn from_str(_line: &str) -> Result<Self, &'static str> {
todo!("Find out how to split traits and casts when when calling in a idiomatic way");
}
fn get_index(&self) -> usize {
1
}
}