use crate::coordinate::Rect;
use crate::core::layer::{
CircleConfig, GradientRectConfig, LineConfig, PathConfig, PolygonConfig, RectConfig,
RenderBackend, TextConfig,
};
use crate::visual::color::SingleColor;
use std::fmt::Write;
pub struct SvgBackend<'a> {
pub buffer: &'a mut String,
clip_id: Option<String>,
}
impl<'a> SvgBackend<'a> {
pub fn new(buffer: &'a mut String, panel: Option<&Rect>) -> Self {
let mut clip_id = None;
if let Some(p) = panel {
let id = "plot-clip-area".to_string();
let _ = writeln!(
buffer,
r#"<defs><clipPath id="{}"><rect x="{:.3}" y="{:.3}" width="{:.3}" height="{:.3}" /></clipPath></defs>"#,
id, p.x, p.y, p.width, p.height
);
clip_id = Some(id);
}
Self { buffer, clip_id }
}
fn write_color(&mut self, color: &SingleColor) {
if color.is_none() {
let _ = self.buffer.write_str("none");
} else {
let c = color.rgba();
let _ = write!(
self.buffer,
"rgba({},{},{},{:.3})",
(c[0] * 255.0).round() as u8,
(c[1] * 255.0).round() as u8,
(c[2] * 255.0).round() as u8,
c[3]
);
}
}
fn write_clip_attr(&mut self) {
if let Some(id) = &self.clip_id {
let _ = write!(self.buffer, r#" clip-path="url(#{})""#, id);
}
}
}
impl<'a> RenderBackend for SvgBackend<'a> {
fn draw_circle(&mut self, config: CircleConfig) {
let CircleConfig {
x,
y,
radius,
fill,
stroke,
stroke_width,
opacity,
} = config;
if fill.is_none() && stroke.is_none() {
return;
}
let _ = write!(
self.buffer,
r#"<circle cx="{:.3}" cy="{:.3}" r="{:.3}" fill=""#,
x, y, radius
);
self.write_color(&fill);
let _ = write!(self.buffer, r#"" stroke=""#);
self.write_color(&stroke);
let _ = write!(
self.buffer,
r#"" stroke-width="{:.3}" fill-opacity="{:.3}" stroke-opacity="{:.3}""#,
stroke_width, opacity, opacity
);
self.write_clip_attr();
let _ = self.buffer.write_str(" />\n");
}
fn draw_rect(&mut self, config: RectConfig) {
let RectConfig {
x,
y,
width,
height,
fill,
stroke,
stroke_width,
opacity,
} = config;
if fill.is_none() && stroke.is_none() {
return;
}
let _ = write!(
self.buffer,
r#"<rect x="{:.3}" y="{:.3}" width="{:.3}" height="{:.3}" fill=""#,
x, y, width, height
);
self.write_color(&fill);
let _ = write!(self.buffer, r#"" stroke=""#);
self.write_color(&stroke);
let _ = write!(
self.buffer,
r#"" stroke-width="{:.3}" fill-opacity="{:.3}" stroke-opacity="{:.3}""#,
stroke_width, opacity, opacity
);
self.write_clip_attr();
let _ = self.buffer.write_str(" />\n");
}
fn draw_path(&mut self, config: PathConfig) {
let PathConfig {
points,
stroke,
stroke_width,
opacity,
dash,
} = config;
if points.is_empty() || stroke.is_none() {
return;
}
let _ = self.buffer.write_str(r#"<path d=""#);
for (i, (px, py)) in points.iter().enumerate() {
if i == 0 {
let _ = write!(self.buffer, "M {:.3} {:.3}", px, py);
} else {
let _ = write!(self.buffer, " L {:.3} {:.3}", px, py);
}
}
let _ = write!(self.buffer, r#"" stroke=""#);
self.write_color(&stroke);
let _ = write!(
self.buffer,
r#"" stroke-width="{:.3}" stroke-opacity="{:.3}" fill="none" stroke-linejoin="round" stroke-linecap="round""#,
stroke_width, opacity
);
if !dash.is_empty() {
let dash_str: Vec<String> = dash.iter().map(|d| d.to_string()).collect();
let _ = write!(self.buffer, r#" stroke-dasharray="{}""#, dash_str.join(","));
}
self.write_clip_attr();
let _ = self.buffer.write_str(" />\n");
}
fn draw_polygon(&mut self, config: PolygonConfig) {
let PolygonConfig {
points,
fill,
stroke,
stroke_width,
fill_opacity,
stroke_opacity,
} = config;
if points.is_empty() {
return;
}
let _ = self.buffer.write_str(r#"<polygon points=""#);
for (i, (px, py)) in points.iter().enumerate() {
let _ = write!(
self.buffer,
"{}{:.3},{:.3}",
if i == 0 { "" } else { " " },
px,
py
);
}
let _ = write!(self.buffer, r#"" fill=""#);
self.write_color(&fill);
let _ = write!(self.buffer, r#"" stroke=""#);
self.write_color(&stroke);
let _ = write!(
self.buffer,
r#"" stroke-width="{:.3}" fill-opacity="{:.3}" stroke-opacity="{:.3}""#,
stroke_width, fill_opacity, stroke_opacity
);
self.write_clip_attr();
let _ = self.buffer.write_str(" />\n");
}
fn draw_text(&mut self, config: TextConfig) {
let TextConfig {
x,
y,
text,
font_size,
font_family,
color,
text_anchor,
font_weight,
opacity,
} = config;
let _ = write!(
self.buffer,
r#"<text x="{:.3}" y="{:.3}" font-size="{:.1}" font-family="{}" fill=""#,
x, y, font_size, font_family
);
self.write_color(&color);
let _ = write!(
self.buffer,
r#"" fill-opacity="{:.3}" text-anchor="{}" font-weight="{}""#,
opacity, text_anchor, font_weight
);
let _ = self.buffer.write_str(">");
self.buffer.push_str(&html_escape::encode_safe(&text));
let _ = self.buffer.write_str("</text>\n");
}
fn draw_line(&mut self, config: LineConfig) {
let LineConfig {
x1,
y1,
x2,
y2,
color,
width,
opacity,
dash,
} = config;
let _ = write!(
self.buffer,
r#"<line x1="{:.3}" y1="{:.3}" x2="{:.3}" y2="{:.3}" stroke=""#,
x1, y1, x2, y2
);
self.write_color(&color);
let _ = write!(
self.buffer,
r#"" stroke-width="{:.3}" stroke-opacity="{:.3}""#,
width, opacity
);
if !dash.is_empty() {
let dash_str: Vec<String> = dash.iter().map(|d| format!("{:.1}", d)).collect();
let _ = write!(self.buffer, r#" stroke-dasharray="{}""#, dash_str.join(","));
}
let _ = self.buffer.write_str(" />\n");
}
fn draw_gradient_rect(&mut self, config: GradientRectConfig) {
let GradientRectConfig {
x,
y,
width,
height,
stops,
is_vertical,
id_suffix,
} = config;
let (x2, y2) = if is_vertical {
("0%", "100%")
} else {
("100%", "0%")
};
let _ = write!(
self.buffer,
r#"<defs><linearGradient id="grad_{}" x1="0%" y1="0%" x2="{}" y2="{}">"#,
id_suffix, x2, y2
);
for (offset, color) in stops {
let _ = write!(
self.buffer,
r#"<stop offset="{:.1}%" stop-color=""#,
offset * 100.0
);
self.write_color(&color);
let _ = self.buffer.write_str(r#"" />"#);
}
let _ = write!(
self.buffer,
r#"</linearGradient></defs><rect x="{:.3}" y="{:.3}" width="{:.3}" height="{:.3}" fill="url('#grad_{}')" />"#,
x, y, width, height, id_suffix
);
}
}