use crate::compat::{format, String, ToString};
use crate::core::{Color, Point, Rect};
pub fn color_to_rgba(c: &Color) -> String {
format!("rgba({},{},{},{:.2})", c.r, c.g, c.b, c.a as f32 / 255.0)
}
pub fn rect_attrs(rect: &Rect) -> String {
format!(r#"x="{}" y="{}" width="{}" height="{}""#, rect.x, rect.y, rect.width, rect.height)
}
pub fn outline_rect_attrs(rect: &Rect, width: u32) -> String {
if !can_inset(rect, width) {
return rect_attrs(rect);
}
let half = width as f64 / 2.0;
format!(
r#"x="{}" y="{}" width="{}" height="{}""#,
number_attr(rect.x as f64 + half),
number_attr(rect.y as f64 + half),
number_attr(rect.width as f64 - width as f64),
number_attr(rect.height as f64 - width as f64),
)
}
pub fn outline_radius(rect: &Rect, radius: u32, width: u32) -> f64 {
if !can_inset(rect, width) {
return radius as f64;
}
(radius as f64 - width as f64 / 2.0).max(0.0)
}
fn can_inset(rect: &Rect, width: u32) -> bool {
width > 0 && rect.width > width * 2 && rect.height > width * 2
}
pub fn number_attr(value: f64) -> String {
let mut text = format!("{value:.2}");
while text.contains('.') && text.ends_with('0') {
text.pop();
}
if text.ends_with('.') {
text.pop();
}
text
}
pub fn point_attrs(p: &Point) -> String {
format!(r#"x1="{}" y1="{}""#, p.x, p.y)
}
#[cfg(feature = "3d")]
pub fn quad_points(corners: &[Point; 4]) -> String {
format!(
"{} {} {} {} {} {} {} {}",
corners[0].x,
corners[0].y,
corners[1].x,
corners[1].y,
corners[2].x,
corners[2].y,
corners[3].x,
corners[3].y
)
}
pub fn escape_xml(s: &str) -> String {
s.chars()
.map(|ch| match ch {
'<' => "<".to_string(),
'>' => ">".to_string(),
'&' => "&".to_string(),
'"' => """.to_string(),
'\'' => "'".to_string(),
_ => ch.to_string(),
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn color_to_rgba_red() {
assert_eq!(color_to_rgba(&Color::RED), "rgba(255,0,0,1.00)");
}
#[test]
fn color_to_rgba_transparent() {
let c = Color::rgba(100, 150, 200, 0);
assert_eq!(color_to_rgba(&c), "rgba(100,150,200,0.00)");
}
#[test]
fn color_to_rgba_semi_transparent() {
let c = Color::rgba(50, 100, 150, 128);
assert_eq!(color_to_rgba(&c), "rgba(50,100,150,0.50)");
}
#[test]
fn rect_attrs_formats_correctly() {
let r = Rect::new(10, 20, 100, 50);
assert_eq!(rect_attrs(&r), r#"x="10" y="20" width="100" height="50""#);
}
#[test]
fn outline_rect_attrs_insets_the_stroke() {
let r = Rect::new(0, 0, 240, 120);
assert_eq!(outline_rect_attrs(&r, 1), r#"x="0.5" y="0.5" width="239" height="119""#);
assert_eq!(outline_rect_attrs(&r, 2), r#"x="1" y="1" width="238" height="118""#);
}
#[test]
fn outline_rect_attrs_leaves_a_tiny_rect_alone() {
let r = Rect::new(5, 5, 2, 2);
assert_eq!(outline_rect_attrs(&r, 1), rect_attrs(&r));
}
#[test]
fn outline_radius_shares_the_corner_centre() {
let r = Rect::new(0, 0, 240, 120);
assert_eq!(outline_radius(&r, 4, 1), 3.5);
assert_eq!(outline_radius(&r, 4, 2), 3.0);
assert_eq!(outline_radius(&Rect::new(0, 0, 2, 2), 4, 1), 4.0);
}
#[test]
fn number_attr_trims_trailing_zeroes() {
assert_eq!(number_attr(239.0), "239");
assert_eq!(number_attr(0.5), "0.5");
assert_eq!(number_attr(3.5), "3.5");
}
#[test]
fn point_attrs_formats_correctly() {
let p = Point::new(15, 25);
assert_eq!(point_attrs(&p), r#"x1="15" y1="25""#);
}
#[test]
fn escape_xml_handles_all_special_chars() {
assert_eq!(
escape_xml("<hello> & \"world\" 'test'"),
"<hello> & "world" 'test'"
);
}
#[test]
fn escape_xml_plain_text_unchanged() {
assert_eq!(escape_xml("Hello, World!"), "Hello, World!");
}
#[test]
fn escape_xml_empty_string() {
assert_eq!(escape_xml(""), "");
}
}