use rctree::Node; use svg::{ node::{ element::{path::Data as PathData, Definitions, Path, Text, TextPath}, Text as TextNode, }, Document, }; use usvg::Tree; pub const HOUR_NAMES: [&str; 24] = [ "Candle", "Ice", "Comet", "Thimble", "Root", "Mist", "Sprout", "Rainbow", "Worm", "Bud", "Blossom", "Ladybug", "Geese", "Dust", "Peach", "Fog", "Acorn", "Gourd", "Soup", "Crow", "Mushroom", "Thunder", "Frost", "Lantern", ]; pub const IMAGE_WIDTH: u32 = 700; pub const HOUR_NAME_FONT_SIZE: f32 = IMAGE_WIDTH as f32 * 0.019109; pub const OUTER_R: f32 = (IMAGE_WIDTH as f32) / 2.0 - 3.0 * HOUR_NAME_FONT_SIZE; pub const RING_WIDTH: f32 = HOUR_NAME_FONT_SIZE * 3.0; pub const UTC_HOUR_FONT_SIZE: f32 = IMAGE_WIDTH as f32 * 0.021462; pub fn svg_to_usvg(document: Document) -> Tree { let doc_str = document.to_string(); let mut opt = usvg::Options::default(); opt.fontdb.load_system_fonts(); opt.font_family = "Liberation Serif".to_string(); Tree::from_str(&doc_str, &opt.to_ref()).unwrap() } fn hour_name_path() -> Path { let radius = OUTER_R - RING_WIDTH / 2.0; let delta_x = radius * (15.0_f32.to_radians() / 2.0).sin(); let delta_y = radius * (1.0 - (15.0_f32.to_radians() / 2.0).cos()); let x1 = (IMAGE_WIDTH as f32) / 2.0 - delta_x; let y1 = ((IMAGE_WIDTH as f32) / 2.0 - radius) + delta_y; let path_data = PathData::new().move_to((x1, y1)).elliptical_arc_by(( radius, radius, 15, 0, 1, 2.0 * delta_x, 0, )); Path::new().set("id", "hour-name-path").set("d", path_data) } fn create_temp_document(hour: usize) -> Document { let definitions = Definitions::new().add(hour_name_path()); let utc_hour_y = IMAGE_WIDTH as f32 / 2.0 - OUTER_R + RING_WIDTH + UTC_HOUR_FONT_SIZE; let hour_name_text_path = TextPath::new() .set("xlink:href", "#hour-name-path") .set("startOffset", "50%") .add(TextNode::new(HOUR_NAMES[hour])); let hour_name_text = Text::new() .set("id", "hour-name") .set("text-anchor", "middle") .set("dominant-baseline", "mathematical") .set("font-size", HOUR_NAME_FONT_SIZE) .add(hour_name_text_path); let utc_hour_text = Text::new() .set("id", "utc-hour") .set( "transform", format!("rotate(-7.5, {}, {})", IMAGE_WIDTH / 2, IMAGE_WIDTH / 2), ) .set("x", IMAGE_WIDTH / 2) .set("y", utc_hour_y) .set("text-anchor", "middle") .set("dominant-baseline", "mathematical") .set("font-size", UTC_HOUR_FONT_SIZE) .add(TextNode::new(format!("U {:02}", hour))); Document::new() .set("viewBox", (0i32, 0i32, 700i32, 700i32)) .set("width", 700i32) .set("height", 700i32) .set("xmlns:xlink", "http://www.w3.org/1999/xlink") .add(definitions) .add(hour_name_text) .add(utc_hour_text) } fn node_to_path(node: Node) -> PathData { let mut svg_path_data = PathData::new(); match *node.borrow() { usvg::NodeKind::Path(ref path) => { let path_data = &path.data; for segment in path_data.0.iter() { match segment { usvg::PathSegment::MoveTo { x, y } => { svg_path_data = svg_path_data.move_to((*x, *y)); } usvg::PathSegment::LineTo { x, y } => { svg_path_data = svg_path_data.line_to((*x, *y)); } usvg::PathSegment::CurveTo { x1, y1, x2, y2, x, y, } => { svg_path_data = svg_path_data.cubic_curve_to((*x1, *y1, *x2, *y2, *x, *y)); } usvg::PathSegment::ClosePath => { svg_path_data = svg_path_data.close(); } } } } _ => { unreachable!() } } svg_path_data } fn cache_hour_name_path(hour: usize) -> (PathData, PathData) { let tree = svg_to_usvg(create_temp_document(hour)); let text_node = tree.node_by_id("hour-name").unwrap(); let utc_text_node = tree.node_by_id("utc-hour").unwrap(); (node_to_path(text_node), node_to_path(utc_text_node)) } pub fn cache_hour_name_paths() -> [(PathData, PathData); 24] { [ cache_hour_name_path(0), cache_hour_name_path(1), cache_hour_name_path(2), cache_hour_name_path(3), cache_hour_name_path(4), cache_hour_name_path(5), cache_hour_name_path(6), cache_hour_name_path(7), cache_hour_name_path(8), cache_hour_name_path(9), cache_hour_name_path(10), cache_hour_name_path(11), cache_hour_name_path(12), cache_hour_name_path(13), cache_hour_name_path(14), cache_hour_name_path(15), cache_hour_name_path(16), cache_hour_name_path(17), cache_hour_name_path(18), cache_hour_name_path(19), cache_hour_name_path(20), cache_hour_name_path(21), cache_hour_name_path(22), cache_hour_name_path(23), ] }