From 2fd9d4e8de6482737bb2af9ea37e9b837c6d7197 Mon Sep 17 00:00:00 2001 From: Gergely Polonkai Date: Mon, 23 May 2022 05:01:25 +0200 Subject: [PATCH] Cache the paths generated from hour name texts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rendering text along a textPath is very CPU intensive, especially since we’re doing it every second. --- Cargo.lock | 1 + Cargo.toml | 1 + src/main.rs | 79 ++++++++++++++++++++++++-------------- src/svg_clock.rs | 98 +++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 148 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2f1c9c7..821c6da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -855,6 +855,7 @@ dependencies = [ "toml", "usvg", "xdg", + "xmlwriter", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 5275d8f..8919b3e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,3 +15,4 @@ toml = "0.5" serde = { version = "1.0", features = ["derive"] } xdg = "2.4" calloop = "0.9" +xmlwriter = "0.1" \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 5c573fc..6861daa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,16 +12,15 @@ use sctk::shm::AutoMemPool; use sctk::window::{Event as WEvent, FallbackFrame}; use serde::Deserialize; use svg::node::element::path::Data as PathData; -use svg::node::element::{ - Circle, Definitions, Group, Line, Path, Rectangle, Style, Text, TextPath, -}; +use svg::node::element::{Circle, Group, Line, Path, Rectangle, Style, Text}; use svg::node::Text as TextNode; use svg::Document; mod svg_clock; use svg_clock::{ - hour_name_path, svg_to_usvg, HOUR_NAMES, HOUR_NAME_FONT_SIZE, IMAGE_WIDTH, OUTER_R, RING_WIDTH, + cache_hour_name_paths, svg_to_usvg, HOUR_NAMES, HOUR_NAME_FONT_SIZE, IMAGE_WIDTH, OUTER_R, + RING_WIDTH, }; sctk::default_environment!(SeasonalClock, desktop); @@ -69,7 +68,12 @@ fn time_to_degrees(timestamp: i32, // should be time/timestamp seconds_to_degrees(timestamp) } -fn hour_marker(hour: i32, is_current_hour: bool, utc_hour_font_size: f32) -> Group { +fn hour_marker( + hour: i32, + hour_name_path_data: &PathData, + is_current_hour: bool, + utc_hour_font_size: f32, +) -> Group { let season = match hour { 0..=5 => Season::Winter, 6..=11 => Season::Spring, @@ -106,16 +110,10 @@ fn hour_marker(hour: i32, is_current_hour: bool, utc_hour_font_size: f32) -> Gro 0, )) .close(); - let path = Path::new().set("d", path_data); - let hour_name_text_path = TextPath::new() - .set("xlink:href", "#hour-name-path") - .set("startOffset", "50%") - .add(TextNode::new(HOUR_NAMES[hour as usize])); - let hour_name_text = Text::new() - .set("text-anchor", "middle") - .set("dominant-baseline", "mathematical") - .set("font-size", HOUR_NAME_FONT_SIZE) - .add(hour_name_text_path); + let path = Path::new().set("class", "hour-outline").set("d", path_data); + let hour_name_path = Path::new() + .set("class", "hour-name") + .set("d", hour_name_path_data.clone()); let utc_hour_text = Text::new() .set("class", "utc") @@ -148,7 +146,7 @@ fn hour_marker(hour: i32, is_current_hour: bool, utc_hour_font_size: f32) -> Gro ), ) .add(path) - .add(hour_name_text) + .add(hour_name_path) .add(utc_hour_text) } @@ -222,7 +220,7 @@ fn get_moon_path(radius: f32, moon_phase: f64) -> Path { Path::new().set("class", "moon").set("d", path_data) } -fn gen_svg(config: &Option) -> Document { +fn gen_svg(config: &Option, hour_name_path_cache: &[PathData; 24]) -> Document { let local_timestamp = Local::now(); let utc_hour = local_timestamp.with_timezone(&Utc).time().hour(); let local_hour = local_timestamp.time().hour(); @@ -253,17 +251,17 @@ fn gen_svg(config: &Option) -> Document { let stylesheet = Style::new( "\ #border {stroke: none; fill: rgb(19, 17, 30); } - .hour path {stroke: rgb(0, 0, 0); stroke-width: 2px;} - .hour text {stroke: none; fill: rgb(238, 187, 85);} + .hour path.hour-outline {stroke: rgb(0, 0, 0); stroke-width: 2px;} + .hour path.hour-name {stroke: none; fill: rgb(238, 187, 85);} .hour text.utc {stroke: none; fill: rgb(91, 68, 38);} .winter path {fill: rgb(70, 62, 108);} - .active.winter path {fill: rgb(100, 92, 138);} + .active.winter path.hour-outline {fill: rgb(100, 92, 138);} .spring path {fill: rgb(55, 87, 55);} - .active.spring path {fill: rgb(85, 117, 85);} + .active.spring path.hour-outline {fill: rgb(85, 117, 85);} .summer path {fill: rgb(113, 92, 43);} - .active.summer path {fill: rgb(143, 122, 73);} + .active.summer path.hour-outline {fill: rgb(143, 122, 73);} .autumn path {fill: rgb(108, 68, 44);} - .active.autumn path {fill: rgb(138, 98, 74);} + .active.autumn.hour-outline path {fill: rgb(138, 98, 74);} .local-hour {stroke: none; fill: rgb(238, 187, 85);} .night-time {stroke: none; fill: rgb(19, 17, 30);} .blue-hour {stroke: none; fill: rgb(9, 1, 119);} @@ -279,7 +277,6 @@ fn gen_svg(config: &Option) -> Document { #current-hour-name {font-weight: bold;}", ); - let definitions = Definitions::new().add(hour_name_path()); let mut local_clock = Group::new().set("id", "local-clock"); for hour in 0i32..24 { @@ -317,6 +314,7 @@ fn gen_svg(config: &Option) -> Document { for hour in 0i32..24 { seasonal_clock = seasonal_clock.add(hour_marker( hour, + &hour_name_path_cache[hour as usize], hour == utc_hour as i32, utc_hour_font_size, )); @@ -559,7 +557,6 @@ fn gen_svg(config: &Option) -> Document { .set("height", 700i32) .set("xmlns:xlink", "http://www.w3.org/1999/xlink") .add(stylesheet) - .add(definitions) .add(border) .add(local_clock); @@ -628,8 +625,17 @@ fn main() { let mut need_redraw = false; let mut dimensions = (700, 700); + let hour_name_path_cache = cache_hour_name_paths(); + if !env.get_shell().unwrap().needs_configure() { - redraw(&mut pool, window.surface(), dimensions, &config).expect("Failed to draw"); + redraw( + &mut pool, + window.surface(), + dimensions, + &config, + &hour_name_path_cache, + ) + .expect("Failed to draw"); window.refresh() } @@ -657,7 +663,14 @@ fn main() { match next_action.take() { Some(WEvent::Close) => break, Some(WEvent::Refresh) => { - redraw(&mut pool, window.surface(), dimensions, &config).expect("Failed to draw"); + redraw( + &mut pool, + window.surface(), + dimensions, + &config, + &hour_name_path_cache, + ) + .expect("Failed to draw"); window.refresh(); window.surface().commit(); } @@ -680,7 +693,14 @@ fn main() { if need_redraw { need_redraw = false; - redraw(&mut pool, window.surface(), dimensions, &config).expect("Failed to draw") + redraw( + &mut pool, + window.surface(), + dimensions, + &config, + &hour_name_path_cache, + ) + .expect("Failed to draw") } if let Err(e) = display.flush() { @@ -698,8 +718,9 @@ fn redraw( surface: &wl_surface::WlSurface, (buf_x, buf_y): (u32, u32), config: &Option, + hour_name_path_cache: &[PathData; 24], ) -> Result<(), ::std::io::Error> { - let document = gen_svg(config); + let document = gen_svg(config, hour_name_path_cache); let svg_tree = svg_to_usvg(document); let (canvas, new_buffer) = pool.buffer( diff --git a/src/svg_clock.rs b/src/svg_clock.rs index c6f5a81..1f246f8 100644 --- a/src/svg_clock.rs +++ b/src/svg_clock.rs @@ -1,5 +1,8 @@ use svg::{ - node::element::{path::Data as PathData, Path}, + node::{ + element::{path::Data as PathData, Definitions, Path, Text, TextPath}, + Text as TextNode, + }, Document, }; use usvg::Tree; @@ -24,7 +27,7 @@ pub fn svg_to_usvg(document: Document) -> Tree { Tree::from_str(&doc_str, &opt.to_ref()).unwrap() } -pub fn hour_name_path() -> Path { +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()); @@ -43,3 +46,94 @@ pub fn hour_name_path() -> Path { Path::new().set("id", "hour-name-path").set("d", path_data) } + +fn create_temp_document(hour_name: &str) -> Document { + let definitions = Definitions::new().add(hour_name_path()); + + let hour_name_text_path = TextPath::new() + .set("xlink:href", "#hour-name-path") + .set("startOffset", "50%") + .add(TextNode::new(hour_name)); + 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); + + 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) +} + +fn cache_hour_name_path(hour_name: &str) -> PathData { + let tree = svg_to_usvg(create_temp_document(hour_name)); + let mut svg_path_data: PathData = PathData::new(); + let text_node = tree.node_by_id("hour-name").unwrap(); + match *text_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 +} + +pub fn cache_hour_name_paths() -> [PathData; 24] { + [ + cache_hour_name_path(HOUR_NAMES[0]), + cache_hour_name_path(HOUR_NAMES[1]), + cache_hour_name_path(HOUR_NAMES[2]), + cache_hour_name_path(HOUR_NAMES[3]), + cache_hour_name_path(HOUR_NAMES[4]), + cache_hour_name_path(HOUR_NAMES[5]), + cache_hour_name_path(HOUR_NAMES[6]), + cache_hour_name_path(HOUR_NAMES[7]), + cache_hour_name_path(HOUR_NAMES[8]), + cache_hour_name_path(HOUR_NAMES[9]), + cache_hour_name_path(HOUR_NAMES[10]), + cache_hour_name_path(HOUR_NAMES[11]), + cache_hour_name_path(HOUR_NAMES[12]), + cache_hour_name_path(HOUR_NAMES[13]), + cache_hour_name_path(HOUR_NAMES[14]), + cache_hour_name_path(HOUR_NAMES[15]), + cache_hour_name_path(HOUR_NAMES[16]), + cache_hour_name_path(HOUR_NAMES[17]), + cache_hour_name_path(HOUR_NAMES[18]), + cache_hour_name_path(HOUR_NAMES[19]), + cache_hour_name_path(HOUR_NAMES[20]), + cache_hour_name_path(HOUR_NAMES[21]), + cache_hour_name_path(HOUR_NAMES[22]), + cache_hour_name_path(HOUR_NAMES[23]), + ] +}