Cache the paths generated from hour name texts

Rendering text along a textPath is very CPU intensive, especially since we’re doing it every second.
This commit is contained in:
Gergely Polonkai 2022-05-23 05:01:25 +02:00
parent 7e8f62b811
commit 2fd9d4e8de
No known key found for this signature in database
GPG Key ID: 2D2885533B869ED4
4 changed files with 148 additions and 31 deletions

1
Cargo.lock generated
View File

@ -855,6 +855,7 @@ dependencies = [
"toml", "toml",
"usvg", "usvg",
"xdg", "xdg",
"xmlwriter",
] ]
[[package]] [[package]]

View File

@ -15,3 +15,4 @@ toml = "0.5"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
xdg = "2.4" xdg = "2.4"
calloop = "0.9" calloop = "0.9"
xmlwriter = "0.1"

View File

@ -12,16 +12,15 @@ use sctk::shm::AutoMemPool;
use sctk::window::{Event as WEvent, FallbackFrame}; use sctk::window::{Event as WEvent, FallbackFrame};
use serde::Deserialize; use serde::Deserialize;
use svg::node::element::path::Data as PathData; use svg::node::element::path::Data as PathData;
use svg::node::element::{ use svg::node::element::{Circle, Group, Line, Path, Rectangle, Style, Text};
Circle, Definitions, Group, Line, Path, Rectangle, Style, Text, TextPath,
};
use svg::node::Text as TextNode; use svg::node::Text as TextNode;
use svg::Document; use svg::Document;
mod svg_clock; mod svg_clock;
use 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); sctk::default_environment!(SeasonalClock, desktop);
@ -69,7 +68,12 @@ fn time_to_degrees(timestamp: i32, // should be time/timestamp
seconds_to_degrees(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 { let season = match hour {
0..=5 => Season::Winter, 0..=5 => Season::Winter,
6..=11 => Season::Spring, 6..=11 => Season::Spring,
@ -106,16 +110,10 @@ fn hour_marker(hour: i32, is_current_hour: bool, utc_hour_font_size: f32) -> Gro
0, 0,
)) ))
.close(); .close();
let path = Path::new().set("d", path_data); let path = Path::new().set("class", "hour-outline").set("d", path_data);
let hour_name_text_path = TextPath::new() let hour_name_path = Path::new()
.set("xlink:href", "#hour-name-path") .set("class", "hour-name")
.set("startOffset", "50%") .set("d", hour_name_path_data.clone());
.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 utc_hour_text = Text::new() let utc_hour_text = Text::new()
.set("class", "utc") .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(path)
.add(hour_name_text) .add(hour_name_path)
.add(utc_hour_text) .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) Path::new().set("class", "moon").set("d", path_data)
} }
fn gen_svg(config: &Option<Config>) -> Document { fn gen_svg(config: &Option<Config>, hour_name_path_cache: &[PathData; 24]) -> Document {
let local_timestamp = Local::now(); let local_timestamp = Local::now();
let utc_hour = local_timestamp.with_timezone(&Utc).time().hour(); let utc_hour = local_timestamp.with_timezone(&Utc).time().hour();
let local_hour = local_timestamp.time().hour(); let local_hour = local_timestamp.time().hour();
@ -253,17 +251,17 @@ fn gen_svg(config: &Option<Config>) -> Document {
let stylesheet = Style::new( let stylesheet = Style::new(
"\ "\
#border {stroke: none; fill: rgb(19, 17, 30); } #border {stroke: none; fill: rgb(19, 17, 30); }
.hour path {stroke: rgb(0, 0, 0); stroke-width: 2px;} .hour path.hour-outline {stroke: rgb(0, 0, 0); stroke-width: 2px;}
.hour text {stroke: none; fill: rgb(238, 187, 85);} .hour path.hour-name {stroke: none; fill: rgb(238, 187, 85);}
.hour text.utc {stroke: none; fill: rgb(91, 68, 38);} .hour text.utc {stroke: none; fill: rgb(91, 68, 38);}
.winter path {fill: rgb(70, 62, 108);} .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);} .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);} .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);} .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);} .local-hour {stroke: none; fill: rgb(238, 187, 85);}
.night-time {stroke: none; fill: rgb(19, 17, 30);} .night-time {stroke: none; fill: rgb(19, 17, 30);}
.blue-hour {stroke: none; fill: rgb(9, 1, 119);} .blue-hour {stroke: none; fill: rgb(9, 1, 119);}
@ -279,7 +277,6 @@ fn gen_svg(config: &Option<Config>) -> Document {
#current-hour-name {font-weight: bold;}", #current-hour-name {font-weight: bold;}",
); );
let definitions = Definitions::new().add(hour_name_path());
let mut local_clock = Group::new().set("id", "local-clock"); let mut local_clock = Group::new().set("id", "local-clock");
for hour in 0i32..24 { for hour in 0i32..24 {
@ -317,6 +314,7 @@ fn gen_svg(config: &Option<Config>) -> Document {
for hour in 0i32..24 { for hour in 0i32..24 {
seasonal_clock = seasonal_clock.add(hour_marker( seasonal_clock = seasonal_clock.add(hour_marker(
hour, hour,
&hour_name_path_cache[hour as usize],
hour == utc_hour as i32, hour == utc_hour as i32,
utc_hour_font_size, utc_hour_font_size,
)); ));
@ -559,7 +557,6 @@ fn gen_svg(config: &Option<Config>) -> Document {
.set("height", 700i32) .set("height", 700i32)
.set("xmlns:xlink", "http://www.w3.org/1999/xlink") .set("xmlns:xlink", "http://www.w3.org/1999/xlink")
.add(stylesheet) .add(stylesheet)
.add(definitions)
.add(border) .add(border)
.add(local_clock); .add(local_clock);
@ -628,8 +625,17 @@ fn main() {
let mut need_redraw = false; let mut need_redraw = false;
let mut dimensions = (700, 700); let mut dimensions = (700, 700);
let hour_name_path_cache = cache_hour_name_paths();
if !env.get_shell().unwrap().needs_configure() { 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() window.refresh()
} }
@ -657,7 +663,14 @@ fn main() {
match next_action.take() { match next_action.take() {
Some(WEvent::Close) => break, Some(WEvent::Close) => break,
Some(WEvent::Refresh) => { 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.refresh();
window.surface().commit(); window.surface().commit();
} }
@ -680,7 +693,14 @@ fn main() {
if need_redraw { if need_redraw {
need_redraw = false; 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() { if let Err(e) = display.flush() {
@ -698,8 +718,9 @@ fn redraw(
surface: &wl_surface::WlSurface, surface: &wl_surface::WlSurface,
(buf_x, buf_y): (u32, u32), (buf_x, buf_y): (u32, u32),
config: &Option<Config>, config: &Option<Config>,
hour_name_path_cache: &[PathData; 24],
) -> Result<(), ::std::io::Error> { ) -> 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 svg_tree = svg_to_usvg(document);
let (canvas, new_buffer) = pool.buffer( let (canvas, new_buffer) = pool.buffer(

View File

@ -1,5 +1,8 @@
use svg::{ use svg::{
node::element::{path::Data as PathData, Path}, node::{
element::{path::Data as PathData, Definitions, Path, Text, TextPath},
Text as TextNode,
},
Document, Document,
}; };
use usvg::Tree; use usvg::Tree;
@ -24,7 +27,7 @@ pub fn svg_to_usvg(document: Document) -> Tree {
Tree::from_str(&doc_str, &opt.to_ref()).unwrap() 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 radius = OUTER_R - RING_WIDTH / 2.0;
let delta_x = radius * (15.0_f32.to_radians() / 2.0).sin(); 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 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) 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]),
]
}