use anyhow::{Context, Result};
use norad::{Codepoints, Contour, ContourPoint, Glyph, PointType};
use std::fs;
use std::path::Path;
use usvg::Node;
pub struct ConversionConfig {
pub em_size: f32,
pub descent: f32,
pub unicode: Option<String>,
}
impl ConversionConfig {
pub fn new(em_size: f32, descent: f32) -> Self {
Self {
em_size,
descent,
unicode: None,
}
}
pub fn with_unicode(mut self, unicode: String) -> Self {
self.unicode = Some(unicode);
self
}
}
pub fn convert_svg_to_glyph(svg_path: &Path, config: &ConversionConfig) -> Result<Glyph> {
let svg_data = fs::read_to_string(svg_path).context("reading input svg")?;
convert_svg_string_to_glyph(&svg_data, svg_path, config)
}
pub fn convert_svg_string_to_glyph(
svg_data: &str,
svg_path: &Path,
config: &ConversionConfig,
) -> Result<Glyph> {
let opt = usvg::Options::default();
let rtree = usvg::Tree::from_str(svg_data, &opt).context("parsing svg")?;
let svg_size = rtree.size();
let svg_width: f32 = svg_size.width();
let svg_height: f32 = svg_size.height();
let scale = config.em_size / svg_height;
let advance_width = (svg_width * scale).round();
let advance_height = (svg_height * scale).round();
let glyph_name = svg_path
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("svgglyph")
.to_string();
let mut glyph = Glyph::new(glyph_name.as_str());
glyph.width = advance_width as f64;
glyph.height = advance_height as f64;
if let Some(ref unicode_hex) = config.unicode {
if let Ok(codepoint) = u32::from_str_radix(unicode_hex, 16) {
if let Some(c) = char::from_u32(codepoint) {
let codepoints = Codepoints::new([c]);
glyph.codepoints = codepoints;
}
}
}
for node in rtree.root().children() {
if let Node::Path(ref path) = *node {
let contours = process_path(path, svg_height, config.descent, scale);
if !glyph.contours.is_empty() {
glyph.contours.extend(contours);
} else {
glyph.contours = contours;
}
}
}
Ok(glyph)
}
pub fn convert_svg_to_glif_file(
svg_path: &Path,
glif_path: &Path,
config: &ConversionConfig,
) -> Result<()> {
let glyph = convert_svg_to_glyph(svg_path, config)?;
let glif_data = glyph.encode_xml()?;
fs::write(glif_path, glif_data)?;
Ok(())
}
fn process_path(path: &usvg::Path, svg_height: f32, descent: f32, scale: f32) -> Vec<Contour> {
let mut contours = Vec::new();
let mut current_contour: Vec<ContourPoint> = Vec::new();
let path_data: &usvg::tiny_skia_path::Path = path.data();
let segments: usvg::tiny_skia_path::PathSegmentsIter<'_> = path_data.segments();
for seg in segments {
match seg {
usvg::tiny_skia_path::PathSegment::MoveTo(p) => {
if !current_contour.is_empty() {
contours.push(Contour::new(current_contour, None));
current_contour = Vec::new();
}
let (x, y) = svg_to_ufo(p.x, p.y, svg_height, descent, scale);
current_contour.push(ContourPoint::new(x, y, PointType::Curve, true, None, None));
}
usvg::tiny_skia_path::PathSegment::LineTo(p) => {
let (x, y) = svg_to_ufo(p.x, p.y, svg_height, descent, scale);
current_contour.push(ContourPoint::new(x, y, PointType::Line, false, None, None));
}
usvg::tiny_skia_path::PathSegment::CubicTo(p1, p2, p) => {
let (cx1, cy1) = svg_to_ufo(p1.x, p1.y, svg_height, descent, scale);
current_contour.push(ContourPoint::new(
cx1,
cy1,
PointType::OffCurve,
false,
None,
None,
));
let (cx2, cy2) = svg_to_ufo(p2.x, p2.y, svg_height, descent, scale);
current_contour.push(ContourPoint::new(
cx2,
cy2,
PointType::OffCurve,
false,
None,
None,
));
let (px, py) = svg_to_ufo(p.x, p.y, svg_height, descent, scale);
current_contour.push(ContourPoint::new(
px,
py,
PointType::Curve,
true, None,
None,
));
}
usvg::tiny_skia_path::PathSegment::Close => {
if !current_contour.is_empty() {
contours.push(Contour::new(current_contour, None));
current_contour = Vec::new();
}
}
usvg::tiny_skia_path::PathSegment::QuadTo(_, _) => {
}
}
}
if !current_contour.is_empty() {
contours.push(Contour::new(current_contour, None));
}
for contour in &mut contours {
if contour.points.len() > 1 {
let first = &contour.points[0];
let last = &contour.points[contour.points.len() - 1];
if first.x == last.x && first.y == last.y {
contour.points.pop();
}
}
}
contours
}
fn svg_to_ufo(sx: f32, sy: f32, svg_height: f32, descent: f32, scale: f32) -> (f64, f64) {
let x = sx * scale;
let y = (svg_height - descent - sy) * scale;
(x.round() as f64, y.round() as f64)
}