use std::sync::Arc;
use crate::color::Color;
use crate::draw_ctx::{DrawCtx, FillRule};
use crate::geometry::Rect;
pub mod path_data;
mod registry;
#[cfg(test)]
mod tests;
pub use path_data::{parse_path, IconPathError};
pub use registry::{icon, icon_ids, register_icon};
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum IconColor {
Ink,
Literal(Color),
}
impl IconColor {
pub fn resolve(self, ink: Color) -> Color {
match self {
IconColor::Ink => ink,
IconColor::Literal(c) => c,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct IconPath {
pub contours: Vec<Vec<[f64; 2]>>,
pub fill: IconColor,
pub fill_rule: FillRule,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct VectorIcon {
pub view_box: f64,
pub paths: Vec<IconPath>,
}
impl VectorIcon {
pub fn new(view_box: f64) -> Self {
Self {
view_box,
paths: Vec::new(),
}
}
pub fn with_svg_path(
mut self,
d: &str,
fill: IconColor,
fill_rule: FillRule,
) -> Result<Self, IconPathError> {
let contours = parse_path(d)?;
self.paths.push(IconPath {
contours,
fill,
fill_rule,
});
Ok(self)
}
pub fn with_svg_path_nonzero(self, d: &str, fill: IconColor) -> Result<Self, IconPathError> {
self.with_svg_path(d, fill, FillRule::NonZero)
}
pub fn point_count(&self) -> usize {
self.paths
.iter()
.flat_map(|p| p.contours.iter())
.map(|c| c.len())
.sum()
}
pub fn bounds(&self) -> Option<[f64; 4]> {
let mut b: Option<[f64; 4]> = None;
for p in &self.paths {
for c in &p.contours {
for pt in c {
b = Some(match b {
None => [pt[0], pt[1], pt[0], pt[1]],
Some(v) => [
v[0].min(pt[0]),
v[1].min(pt[1]),
v[2].max(pt[0]),
v[3].max(pt[1]),
],
});
}
}
}
b
}
pub fn paint(&self, ctx: &mut dyn DrawCtx, rect: Rect, ink: Color) {
if self.view_box <= 0.0 || self.paths.is_empty() {
return;
}
let side = rect.width.min(rect.height);
if side <= 0.0 {
return;
}
let k = side / self.view_box;
let ox = rect.x + (rect.width - side) * 0.5;
let top = rect.y + (rect.height + side) * 0.5;
for path in &self.paths {
if path.contours.iter().all(|c| c.len() < 2) {
continue;
}
ctx.set_fill_color(path.fill.resolve(ink));
ctx.set_fill_rule(path.fill_rule);
ctx.begin_path();
for contour in &path.contours {
if contour.len() < 2 {
continue;
}
for (i, pt) in contour.iter().enumerate() {
let x = ox + pt[0] * k;
let y = top - pt[1] * k;
if i == 0 {
ctx.move_to(x, y);
} else {
ctx.line_to(x, y);
}
}
ctx.close_path();
}
ctx.fill();
}
ctx.set_fill_rule(FillRule::NonZero);
}
}
pub fn icon_from_svg_paths(
view_box: f64,
paths: &[(&str, IconColor, FillRule)],
) -> Result<VectorIcon, IconPathError> {
let mut icon = VectorIcon::new(view_box);
for (d, fill, rule) in paths {
icon = icon.with_svg_path(d, *fill, *rule)?;
}
Ok(icon)
}
pub fn register_svg_icon(
id: impl Into<Arc<str>>,
view_box: f64,
paths: &[(&str, IconColor, FillRule)],
) -> Result<(), IconPathError> {
let icon = icon_from_svg_paths(view_box, paths)?;
register_icon(id, icon);
Ok(())
}