From 45b920141ce8129c0748e7e5a2b9210c9996364b Mon Sep 17 00:00:00 2001 From: Christopher Serr Date: Sat, 4 Dec 2021 16:54:14 +0100 Subject: [PATCH] Allow Customizing the Text Engine So far livesplit-core always brought its own text engine. However in particular the font fallback isn't great. It also always renders the text as paths instead of leveraging the underlying renderer's built-in text renderer. So this now allows the renderer to bring its own text engine implementation. The old path based text engine is now available as an optional feature that you can still use if the renderer doesn't provide its own text rendering. --- Cargo.toml | 27 +- benches/scene_management.rs | 42 +- src/layout/component.rs | 16 +- src/rendering/component/detailed_timer.rs | 36 +- src/rendering/component/graph.rs | 6 +- src/rendering/component/key_value.rs | 20 +- src/rendering/component/mod.rs | 20 +- src/rendering/component/splits.rs | 31 +- src/rendering/component/text.rs | 20 +- src/rendering/component/timer.rs | 22 +- src/rendering/component/title.rs | 21 +- src/rendering/entity.rs | 103 +++- src/rendering/font/cache.rs | 120 ++-- src/rendering/font/glyph_cache.rs | 71 --- src/rendering/font/mod.rs | 535 ++++-------------- src/rendering/mod.rs | 292 +++++----- .../color_font/colr.rs | 0 .../color_font/cpal.rs | 0 .../color_font/mod.rs | 10 +- .../color_font/parse_util.rs | 0 src/rendering/path_based_text_engine/mod.rs | 458 +++++++++++++++ src/rendering/resource/allocation.rs | 131 ++++- src/rendering/resource/handles.rs | 118 +++- src/rendering/resource/mod.rs | 2 +- src/rendering/resource/shared_ownership.rs | 2 +- src/rendering/scene.rs | 21 +- src/rendering/software.rs | 235 ++++++-- src/run/parser/flitter/s_expressions.rs | 2 +- src/settings/image/mod.rs | 2 +- tests/rendering.rs | 2 +- 30 files changed, 1407 insertions(+), 958 deletions(-) delete mode 100644 src/rendering/font/glyph_cache.rs rename src/rendering/{font => path_based_text_engine}/color_font/colr.rs (100%) rename src/rendering/{font => path_based_text_engine}/color_font/cpal.rs (100%) rename src/rendering/{font => path_based_text_engine}/color_font/mod.rs (86%) rename src/rendering/{font => path_based_text_engine}/color_font/parse_util.rs (100%) create mode 100644 src/rendering/path_based_text_engine/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 27f3d056..14f80915 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ include = [ "/LICENSE-MIT", "/README.md", ] -edition = "2018" +edition = "2021" resolver = "2" [package.metadata.docs.rs] @@ -63,15 +63,20 @@ serde_json = { version = "1.0.8", optional = true } utf-8 = { version = "0.7.4", optional = true } # Rendering -ahash = { version = "0.7.0", default-features = false, optional = true } +# Currently doesn't require any additional dependencies. + +# Path-based Text Engine rustybuzz = { version = "0.4.0", optional = true } -ttf-parser = { version = "0.12.0", optional = true } +ttf-parser = { version = "0.12.3", optional = true } # Font Loading font-kit = { version = "0.10.0", optional = true } # Software Rendering -tiny-skia = { version = "0.6.0", default-features = false, features = ["std", "simd"], optional = true } +tiny-skia = { version = "0.6.0", default-features = false, features = [ + "std", + "simd", +], optional = true } # Networking splits-io-api = { version = "0.2.0", optional = true } @@ -127,16 +132,10 @@ more-image-formats = [ "image/webp", ] image-shrinking = ["std", "bytemuck", "more-image-formats"] -rendering = [ - "ahash", - "bytemuck/derive", - "more-image-formats", - "rustybuzz", - "std", - "ttf-parser", -] -font-loading = ["std", "rendering", "font-kit"] -software-rendering = ["rendering", "tiny-skia"] +rendering = ["bytemuck/derive", "more-image-formats"] +path-based-text-engine = ["std", "rendering", "rustybuzz", "ttf-parser"] +font-loading = ["std", "path-based-text-engine", "font-kit"] +software-rendering = ["path-based-text-engine", "tiny-skia"] wasm-web = [ "js-sys", "livesplit-hotkey/wasm-web", diff --git a/benches/scene_management.rs b/benches/scene_management.rs index cc019246..51eb7ad5 100644 --- a/benches/scene_management.rs +++ b/benches/scene_management.rs @@ -1,10 +1,13 @@ +use livesplit_core::rendering::SharedOwnership; + cfg_if::cfg_if! { if #[cfg(feature = "rendering")] { use criterion::{criterion_group, criterion_main, Criterion}; use livesplit_core::{ layout::{self, Layout}, - rendering::{PathBuilder, ResourceAllocator, SceneManager}, + rendering::{PathBuilder, ResourceAllocator, SceneManager, Label, FontKind}, run::parser::livesplit, + settings::Font, Run, Segment, TimeSpan, Timer, TimingMethod, }; use std::{fs::File, io::BufReader}; @@ -14,7 +17,7 @@ cfg_if::cfg_if! { struct Dummy; - impl PathBuilder for Dummy { + impl PathBuilder for Dummy { type Path = (); fn move_to(&mut self, _: f32, _: f32) {} @@ -22,18 +25,51 @@ cfg_if::cfg_if! { fn quad_to(&mut self, _: f32, _: f32, _: f32, _: f32) {} fn curve_to(&mut self, _: f32, _: f32, _: f32, _: f32, _: f32, _: f32) {} fn close(&mut self) {} - fn finish(self, _: &mut Dummy) -> Self::Path {} + fn finish(self) -> Self::Path {} } impl ResourceAllocator for Dummy { type PathBuilder = Dummy; type Path = (); type Image = (); + type Font = (); + type Label = Dummy; fn path_builder(&mut self) -> Self::PathBuilder { Dummy } fn create_image(&mut self, _: u32, _: u32, _: &[u8]) -> Self::Image {} + fn create_font(&mut self, _: Option<&Font>, _: FontKind) -> Self::Font {} + fn create_label( + &mut self, + _: &str, + _: &mut Self::Font, + _: Option, + ) -> Self::Label { + Dummy + } + fn update_label( + &mut self, + _: &mut Self::Label, + _: &str, + _: &mut Self::Font, + _: Option, + ) {} + } + + impl Label for Dummy { + fn width(&self, _: f32) -> f32 { + 0.0 + } + fn width_without_max_width(&self, _: f32) -> f32 { + 0.0 + } + } + + impl SharedOwnership for Dummy { + fn share(&self) -> Self { + Dummy + } } fn default(c: &mut Criterion) { diff --git a/src/layout/component.rs b/src/layout/component.rs index 49c01c35..57f57ac5 100644 --- a/src/layout/component.rs +++ b/src/layout/component.rs @@ -1,12 +1,14 @@ use super::{ComponentSettings, ComponentState, GeneralSettings}; -use crate::component::{ - blank_space, current_comparison, current_pace, delta, detailed_timer, graph, pb_chance, - possible_time_save, previous_segment, segment_time, separator, splits, sum_of_best, text, - timer, title, total_playtime, +use crate::{ + component::{ + blank_space, current_comparison, current_pace, delta, detailed_timer, graph, pb_chance, + possible_time_save, previous_segment, segment_time, separator, splits, sum_of_best, text, + timer, title, total_playtime, + }, + platform::prelude::*, + settings::{SettingsDescription, Value}, + timing::Snapshot, }; -use crate::platform::prelude::*; -use crate::settings::{SettingsDescription, Value}; -use crate::timing::Snapshot; use alloc::borrow::Cow; /// A Component provides information about a run in a way that is easy to diff --git a/src/rendering/component/detailed_timer.rs b/src/rendering/component/detailed_timer.rs index c73c789b..518b69bf 100644 --- a/src/rendering/component/detailed_timer.rs +++ b/src/rendering/component/detailed_timer.rs @@ -4,7 +4,7 @@ use crate::{ rendering::{ component::timer, consts::{vertical_padding, BOTH_PADDINGS, PADDING}, - font::Label, + font::CachedLabel, icon::Icon, resource::ResourceAllocator, scene::Layer, @@ -12,35 +12,35 @@ use crate::{ }, }; -pub struct Cache { +pub struct Cache { icon: Option>, - timer: timer::Cache, - segment_timer: timer::Cache, - segment_name: Label, - comparison1_name: Label, - comparison2_name: Label, - comparison1_time: Label, - comparison2_time: Label, + timer: timer::Cache, + segment_timer: timer::Cache, + segment_name: CachedLabel, + comparison1_name: CachedLabel, + comparison2_name: CachedLabel, + comparison1_time: CachedLabel, + comparison2_time: CachedLabel, } -impl Cache { +impl Cache { pub const fn new() -> Self { Self { icon: None, timer: timer::Cache::new(), segment_timer: timer::Cache::new(), - segment_name: Label::new(), - comparison1_name: Label::new(), - comparison2_name: Label::new(), - comparison1_time: Label::new(), - comparison2_time: Label::new(), + segment_name: CachedLabel::new(), + comparison1_name: CachedLabel::new(), + comparison2_name: CachedLabel::new(), + comparison1_time: CachedLabel::new(), + comparison2_time: CachedLabel::new(), } } } -pub(in crate::rendering) fn render( - cache: &mut Cache, - context: &mut RenderContext<'_, B>, +pub(in crate::rendering) fn render( + cache: &mut Cache, + context: &mut RenderContext<'_, A>, [width, height]: [f32; 2], component: &State, layout_state: &LayoutState, diff --git a/src/rendering/component/graph.rs b/src/rendering/component/graph.rs index 4a0f05f6..5fd14273 100644 --- a/src/rendering/component/graph.rs +++ b/src/rendering/component/graph.rs @@ -56,7 +56,7 @@ pub(in crate::rendering) fn render( builder.line_to(width * p2.x, p2.y); builder.line_to(width * p2.x, component.middle); builder.close(); - let partial_fill_path = builder.finish(&mut context.handles); + let partial_fill_path = builder.finish(); context.top_layer_path(partial_fill_path, component.partial_fill_color); component.points.len() - 1 @@ -71,7 +71,7 @@ pub(in crate::rendering) fn render( } builder.line_to(width * component.points[len - 1].x, component.middle); builder.close(); - let fill_path = builder.finish(&mut context.handles); + let fill_path = builder.finish(); context.top_layer_path(fill_path, component.complete_fill_color); for points in component.points.windows(2) { @@ -85,7 +85,7 @@ pub(in crate::rendering) fn render( component.graph_lines_color }; - let line_path = builder.finish(&mut context.handles); + let line_path = builder.finish(); context.top_layer_stroke_path(line_path, color, LINE_WIDTH); } diff --git a/src/rendering/component/key_value.rs b/src/rendering/component/key_value.rs index aaf33feb..b12e8734 100644 --- a/src/rendering/component/key_value.rs +++ b/src/rendering/component/key_value.rs @@ -1,34 +1,34 @@ -use std::marker::PhantomData; +use core::marker::PhantomData; use crate::{ component::key_value::State, layout::{LayoutDirection, LayoutState}, rendering::{ - font::{AbbreviatedLabel, Label}, + font::{AbbreviatedLabel, CachedLabel}, resource::ResourceAllocator, RenderContext, }, }; -pub struct Cache { - key: AbbreviatedLabel, - value: Label, +pub struct Cache { + key: AbbreviatedLabel, + value: CachedLabel, _image: PhantomData, } -impl Cache { +impl Cache { pub const fn new() -> Self { Self { key: AbbreviatedLabel::new(), - value: Label::new(), + value: CachedLabel::new(), _image: PhantomData, } } } -pub(in crate::rendering) fn render( - cache: &mut Cache, - context: &mut RenderContext<'_, B>, +pub(in crate::rendering) fn render( + cache: &mut Cache, + context: &mut RenderContext<'_, A>, dim: [f32; 2], component: &State, layout_state: &LayoutState, diff --git a/src/rendering/component/mod.rs b/src/rendering/component/mod.rs index 8fe7034f..a225d0f6 100644 --- a/src/rendering/component/mod.rs +++ b/src/rendering/component/mod.rs @@ -16,20 +16,20 @@ pub mod text; pub mod timer; pub mod title; -pub enum Cache { +pub enum Cache { Empty, - DetailedTimer(detailed_timer::Cache), - KeyValue(key_value::Cache), - Splits(splits::Cache), - Text(text::Cache), - Timer(timer::Cache), - Title(title::Cache), + DetailedTimer(detailed_timer::Cache), + KeyValue(key_value::Cache), + Splits(splits::Cache), + Text(text::Cache), + Timer(timer::Cache), + Title(title::Cache), } macro_rules! accessors { ($($variant:ident $module:ident),*) => { $( - fn $module(&mut self) -> &mut $module::Cache { + fn $module(&mut self) -> &mut $module::Cache { match self { Self::$variant(c) => c, _ => { @@ -42,7 +42,7 @@ macro_rules! accessors { }; } -impl Cache { +impl Cache { pub const fn new(component: &ComponentState) -> Self { match component { ComponentState::DetailedTimer(_) => Self::DetailedTimer(detailed_timer::Cache::new()), @@ -134,7 +134,7 @@ pub fn height(component: &ComponentState) -> f32 { } pub(super) fn render( - cache: &mut Cache, + cache: &mut Cache, context: &mut RenderContext<'_, A>, component: &ComponentState, state: &LayoutState, diff --git a/src/rendering/component/splits.rs b/src/rendering/component/splits.rs index ed50cd8f..5f10d17a 100644 --- a/src/rendering/component/splits.rs +++ b/src/rendering/component/splits.rs @@ -6,38 +6,39 @@ use crate::{ vertical_padding, BOTH_PADDINGS, DEFAULT_COMPONENT_HEIGHT, DEFAULT_TEXT_SIZE, PADDING, TEXT_ALIGN_BOTTOM, TEXT_ALIGN_TOP, THIN_SEPARATOR_THICKNESS, TWO_ROW_HEIGHT, }, - font::Label, + font::CachedLabel, icon::Icon, resource::ResourceAllocator, scene::Layer, solid, RenderContext, }, settings::{Gradient, ListGradient}, + platform::prelude::*, }; pub const COLUMN_WIDTH: f32 = 2.75; -pub struct Cache { +pub struct Cache { icons: Vec>>, - splits: Vec, - column_labels: Vec