diff --git a/egui/src/context.rs b/egui/src/context.rs index a982ebfa..88794251 100644 --- a/egui/src/context.rs +++ b/egui/src/context.rs @@ -93,7 +93,7 @@ impl ContextImpl { new_font_definitions.unwrap_or_else(|| { self.fonts .as_ref() - .map(|font| font.definitions().clone()) + .map(|font| font.lock().definitions().clone()) .unwrap_or_default() }), )); @@ -521,7 +521,7 @@ impl Context { pub fn set_fonts(&self, font_definitions: FontDefinitions) { if let Some(current_fonts) = &*self.fonts_mut() { // NOTE: this comparison is expensive since it checks TTF data for equality - if current_fonts.definitions() == &font_definitions { + if current_fonts.lock().definitions() == &font_definitions { return; // no change - save us from reloading font textures } } @@ -700,7 +700,7 @@ impl Context { self.request_repaint(); } - self.fonts().end_frame(); + self.fonts().lock().end_frame(); { let ctx_impl = &mut *self.write(); @@ -708,7 +708,7 @@ impl Context { .memory .end_frame(&ctx_impl.input, &ctx_impl.frame_state.used_ids); - let font_image_delta = ctx_impl.fonts.as_ref().unwrap().font_image_delta(); + let font_image_delta = ctx_impl.fonts.as_ref().unwrap().lock().font_image_delta(); if let Some(font_image_delta) = font_image_delta { ctx_impl .tex_manager @@ -754,7 +754,7 @@ impl Context { let clipped_meshes = tessellator::tessellate_shapes( shapes, tessellation_options, - self.fonts().font_image_size(), + self.fonts().lock().font_image_size(), ); self.write().paint_stats = paint_stats.with_clipped_meshes(&clipped_meshes); clipped_meshes @@ -956,9 +956,9 @@ impl Context { CollapsingHeader::new("🔠 Fonts") .default_open(false) .show(ui, |ui| { - let mut font_definitions = self.fonts().definitions().clone(); + let mut font_definitions = self.fonts().lock().definitions().clone(); font_definitions.ui(ui); - let font_image_size = self.fonts().font_image_size(); + let font_image_size = self.fonts().lock().font_image_size(); crate::introspection::font_texture_ui(ui, font_image_size); self.set_fonts(font_definitions); }); @@ -1015,7 +1015,7 @@ impl Context { ui.label(format!( "There are {} text galleys in the layout cache", - self.fonts().num_galleys_in_cache() + self.fonts().lock().num_galleys_in_cache() )) .on_hover_text("This is approximately the number of text strings on screen"); ui.add_space(16.0); diff --git a/egui_demo_lib/benches/benchmark.rs b/egui_demo_lib/benches/benchmark.rs index d1d549bd..828e378e 100644 --- a/egui_demo_lib/benches/benchmark.rs +++ b/egui_demo_lib/benches/benchmark.rs @@ -91,19 +91,22 @@ pub fn criterion_benchmark(c: &mut Criterion) { let color = egui::Color32::WHITE; let fonts = egui::epaint::text::Fonts::new(pixels_per_point, egui::FontDefinitions::default()); - c.bench_function("text_layout_uncached", |b| { - b.iter(|| { - use egui::epaint::text::{layout, LayoutJob}; + { + let mut fonts_impl = fonts.lock(); + c.bench_function("text_layout_uncached", |b| { + b.iter(|| { + use egui::epaint::text::{layout, LayoutJob}; - let job = LayoutJob::simple( - LOREM_IPSUM_LONG.to_owned(), - egui::TextStyle::Body, - color, - wrap_width, - ); - layout(&fonts, job.into()) - }) - }); + let job = LayoutJob::simple( + LOREM_IPSUM_LONG.to_owned(), + egui::TextStyle::Body, + color, + wrap_width, + ); + layout(&mut fonts_impl, job.into()) + }) + }); + } c.bench_function("text_layout_cached", |b| { b.iter(|| fonts.layout(LOREM_IPSUM_LONG.to_owned(), text_style, color, wrap_width)) }); @@ -112,9 +115,10 @@ pub fn criterion_benchmark(c: &mut Criterion) { let mut tessellator = egui::epaint::Tessellator::from_options(Default::default()); let mut mesh = egui::epaint::Mesh::default(); let text_shape = TextShape::new(egui::Pos2::ZERO, galley); + let font_image_size = fonts.lock().font_image_size(); c.bench_function("tessellate_text", |b| { b.iter(|| { - tessellator.tessellate_text(fonts.font_image_size(), text_shape.clone(), &mut mesh); + tessellator.tessellate_text(font_image_size, text_shape.clone(), &mut mesh); mesh.clear(); }) }); diff --git a/egui_demo_lib/src/apps/demo/font_book.rs b/egui_demo_lib/src/apps/demo/font_book.rs index e3573a8b..08fe47a5 100644 --- a/egui_demo_lib/src/apps/demo/font_book.rs +++ b/egui_demo_lib/src/apps/demo/font_book.rs @@ -72,6 +72,7 @@ impl super::View for FontBook { let filter = &self.filter; let named_chars = self.named_chars.entry(text_style).or_insert_with(|| { ui.fonts() + .lock() .font(text_style) .characters() .iter() diff --git a/epaint/src/text/fonts.rs b/epaint/src/text/fonts.rs index eb1df31e..9583b54b 100644 --- a/epaint/src/text/fonts.rs +++ b/epaint/src/text/fonts.rs @@ -230,7 +230,106 @@ impl Default for FontDefinitions { /// The collection of fonts used by `epaint`. /// /// Required in order to paint text. -pub struct Fonts { +/// Create one and reuse. Cheap to clone. +/// +/// Wrapper for `Arc>`. +pub struct Fonts(Arc>); + +impl Fonts { + /// Create a new [`Fonts`] for text layout. + /// This call is expensive, so only create one [`Fonts`] and then reuse it. + pub fn new(pixels_per_point: f32, definitions: FontDefinitions) -> Self { + Self(Arc::new(Mutex::new(FontsImpl::new( + pixels_per_point, + definitions, + )))) + } + + /// Access the underlying [`FontsImpl`]. + #[doc(hidden)] + #[inline] + pub fn lock(&self) -> crate::mutex::MutexGuard<'_, FontsImpl> { + self.0.lock() + } + + #[inline] + pub fn pixels_per_point(&self) -> f32 { + self.lock().pixels_per_point + } + + /// Width of this character in points. + #[inline] + pub fn glyph_width(&self, text_style: TextStyle, c: char) -> f32 { + self.lock().glyph_width(text_style, c) + } + + /// Height of one row of text. In points + #[inline] + pub fn row_height(&self, text_style: TextStyle) -> f32 { + self.lock().row_height(text_style) + } + + /// Layout some text. + /// This is the most advanced layout function. + /// See also [`Self::layout`], [`Self::layout_no_wrap`] and + /// [`Self::layout_delayed_color`]. + /// + /// The implementation uses memoization so repeated calls are cheap. + /// #[inline] + pub fn layout_job(&self, job: LayoutJob) -> Arc { + self.lock().layout_job(job) + } + + /// Will wrap text at the given width and line break at `\n`. + /// + /// The implementation uses memoization so repeated calls are cheap. + pub fn layout( + &self, + text: String, + text_style: TextStyle, + color: crate::Color32, + wrap_width: f32, + ) -> Arc { + let job = LayoutJob::simple(text, text_style, color, wrap_width); + self.layout_job(job) + } + + /// Will line break at `\n`. + /// + /// The implementation uses memoization so repeated calls are cheap. + pub fn layout_no_wrap( + &self, + text: String, + text_style: TextStyle, + color: crate::Color32, + ) -> Arc { + let job = LayoutJob::simple(text, text_style, color, f32::INFINITY); + self.layout_job(job) + } + + /// Like [`Self::layout`], made for when you want to pick a color for the text later. + /// + /// The implementation uses memoization so repeated calls are cheap. + pub fn layout_delayed_color( + &self, + text: String, + text_style: TextStyle, + wrap_width: f32, + ) -> Arc { + self.layout_job(LayoutJob::simple( + text, + text_style, + crate::Color32::TEMPORARY_COLOR, + wrap_width, + )) + } +} +// ---------------------------------------------------------------------------- + +/// The collection of fonts used by `epaint`. +/// +/// Required in order to paint text. +pub struct FontsImpl { pixels_per_point: f32, definitions: FontDefinitions, fonts: BTreeMap, @@ -238,9 +337,9 @@ pub struct Fonts { galley_cache: Mutex, } -impl Fonts { - /// Create a new [`Fonts`] for text layout. - /// This call is expensive, so only create one [`Fonts`] and then reuse it. +impl FontsImpl { + /// Create a new [`FontsImpl`] for text layout. + /// This call is expensive, so only create one [`FontsImpl`] and then reuse it. pub fn new(pixels_per_point: f32, definitions: FontDefinitions) -> Self { assert!( 0.0 < pixels_per_point && pixels_per_point < 100.0, @@ -332,51 +431,6 @@ impl Fonts { pub fn layout_job(&self, job: LayoutJob) -> Arc { self.galley_cache.lock().layout(self, job) } - - /// Will wrap text at the given width and line break at `\n`. - /// - /// The implementation uses memoization so repeated calls are cheap. - pub fn layout( - &self, - text: String, - text_style: TextStyle, - color: crate::Color32, - wrap_width: f32, - ) -> Arc { - let job = LayoutJob::simple(text, text_style, color, wrap_width); - self.layout_job(job) - } - - /// Will line break at `\n`. - /// - /// The implementation uses memoization so repeated calls are cheap. - pub fn layout_no_wrap( - &self, - text: String, - text_style: TextStyle, - color: crate::Color32, - ) -> Arc { - let job = LayoutJob::simple(text, text_style, color, f32::INFINITY); - self.layout_job(job) - } - - /// Like [`Self::layout`], made for when you want to pick a color for the text later. - /// - /// The implementation uses memoization so repeated calls are cheap. - pub fn layout_delayed_color( - &self, - text: String, - text_style: TextStyle, - wrap_width: f32, - ) -> Arc { - self.layout_job(LayoutJob::simple( - text, - text_style, - crate::Color32::TEMPORARY_COLOR, - wrap_width, - )) - } - pub fn num_galleys_in_cache(&self) -> usize { self.galley_cache.lock().num_galleys_in_cache() } @@ -403,7 +457,7 @@ struct GalleyCache { } impl GalleyCache { - fn layout(&mut self, fonts: &Fonts, job: LayoutJob) -> Arc { + fn layout(&mut self, fonts: &FontsImpl, job: LayoutJob) -> Arc { let hash = crate::util::hash(&job); // TODO: even faster hasher? match self.cache.entry(hash) { diff --git a/epaint/src/text/mod.rs b/epaint/src/text/mod.rs index ee2b5c07..97a42dcf 100644 --- a/epaint/src/text/mod.rs +++ b/epaint/src/text/mod.rs @@ -10,7 +10,7 @@ mod text_layout_types; pub const TAB_SIZE: usize = 4; pub use { - fonts::{FontData, FontDefinitions, FontFamily, Fonts, TextStyle}, + fonts::{FontData, FontDefinitions, FontFamily, Fonts, FontsImpl, TextStyle}, text_layout::layout, text_layout_types::*, }; diff --git a/epaint/src/text/text_layout.rs b/epaint/src/text/text_layout.rs index 574e4619..a63170c0 100644 --- a/epaint/src/text/text_layout.rs +++ b/epaint/src/text/text_layout.rs @@ -1,6 +1,6 @@ use std::ops::RangeInclusive; -use super::{Fonts, Galley, Glyph, LayoutJob, LayoutSection, Row, RowVisuals}; +use super::{FontsImpl, Galley, Glyph, LayoutJob, LayoutSection, Row, RowVisuals}; use crate::{mutex::Arc, Color32, Mesh, Stroke, Vertex}; use emath::*; @@ -50,7 +50,7 @@ struct Paragraph { /// /// In most cases you should use [`Fonts::layout_job`] instead /// since that memoizes the input, making subsequent layouting of the same text much faster. -pub fn layout(fonts: &Fonts, job: Arc) -> Galley { +pub fn layout(fonts: &FontsImpl, job: Arc) -> Galley { let mut paragraphs = vec![Paragraph::default()]; for (section_index, section) in job.sections.iter().enumerate() { layout_section(fonts, &job, section_index as u32, section, &mut paragraphs); @@ -75,7 +75,7 @@ pub fn layout(fonts: &Fonts, job: Arc) -> Galley { } fn layout_section( - fonts: &Fonts, + fonts: &FontsImpl, job: &LayoutJob, section_index: u32, section: &LayoutSection, diff --git a/epi/src/lib.rs b/epi/src/lib.rs index 95fce001..a7a15b49 100644 --- a/epi/src/lib.rs +++ b/epi/src/lib.rs @@ -285,7 +285,7 @@ impl Frame { Self(Arc::new(Mutex::new(frame_data))) } - /// Convenience to access the underlying `backend::FrameData`. + /// Access the underlying [`backend::FrameData`]. #[doc(hidden)] #[inline] pub fn lock(&self) -> std::sync::MutexGuard<'_, backend::FrameData> {