Make CosmicFontSystem and SwashCache pub resources. (#15479)

# Objective

In nannou, we'd like to be able to access the [outline
commands](https://docs.rs/cosmic-text/latest/cosmic_text/struct.SwashCache.html#method.get_outline_commands)
from swash, while still benefit from Bevy's management of font assets.

## Solution

Make `CosmicFontSystem` and  `SwashCache` pub resources.

## Testing

Ran some examples.
This commit is contained in:
charlotte 2024-09-27 17:00:27 -07:00 committed by GitHub
parent 9b4d2de215
commit df23b937cc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 73 additions and 36 deletions

View File

@ -109,7 +109,9 @@ impl Plugin for TextPlugin {
.register_type::<TextBounds>() .register_type::<TextBounds>()
.init_asset_loader::<FontLoader>() .init_asset_loader::<FontLoader>()
.init_resource::<FontAtlasSets>() .init_resource::<FontAtlasSets>()
.insert_resource(TextPipeline::default()) .init_resource::<TextPipeline>()
.init_resource::<CosmicFontSystem>()
.init_resource::<SwashCache>()
.add_systems( .add_systems(
PostUpdate, PostUpdate,
( (

View File

@ -20,8 +20,13 @@ use crate::{
PositionedGlyph, TextBounds, TextSection, YAxisOrientation, PositionedGlyph, TextBounds, TextSection, YAxisOrientation,
}; };
/// A wrapper around a [`cosmic_text::FontSystem`] /// A wrapper resource around a [`cosmic_text::FontSystem`]
struct CosmicFontSystem(cosmic_text::FontSystem); ///
/// The font system is used to retrieve fonts and their information, including glyph outlines.
///
/// This resource is updated by the [`TextPipeline`] resource.
#[derive(Resource)]
pub struct CosmicFontSystem(pub cosmic_text::FontSystem);
impl Default for CosmicFontSystem { impl Default for CosmicFontSystem {
fn default() -> Self { fn default() -> Self {
@ -32,8 +37,13 @@ impl Default for CosmicFontSystem {
} }
} }
/// A wrapper around a [`cosmic_text::SwashCache`] /// A wrapper resource around a [`cosmic_text::SwashCache`]
struct SwashCache(cosmic_text::SwashCache); ///
/// The swash cache rasterizer is used to rasterize glyphs
///
/// This resource is updated by the [`TextPipeline`] resource.
#[derive(Resource)]
pub struct SwashCache(pub cosmic_text::SwashCache);
impl Default for SwashCache { impl Default for SwashCache {
fn default() -> Self { fn default() -> Self {
@ -48,14 +58,6 @@ impl Default for SwashCache {
pub struct TextPipeline { pub struct TextPipeline {
/// Identifies a font [`ID`](cosmic_text::fontdb::ID) by its [`Font`] [`Asset`](bevy_asset::Asset). /// Identifies a font [`ID`](cosmic_text::fontdb::ID) by its [`Font`] [`Asset`](bevy_asset::Asset).
map_handle_to_font_id: HashMap<AssetId<Font>, (cosmic_text::fontdb::ID, String)>, map_handle_to_font_id: HashMap<AssetId<Font>, (cosmic_text::fontdb::ID, String)>,
/// The font system is used to retrieve fonts and their information, including glyph outlines.
///
/// See [`cosmic_text::FontSystem`] for more information.
font_system: CosmicFontSystem,
/// The swash cache rasterizer is used to rasterize glyphs
///
/// See [`cosmic_text::SwashCache`] for more information.
swash_cache: SwashCache,
/// Buffered vec for collecting spans. /// Buffered vec for collecting spans.
/// ///
/// See [this dark magic](https://users.rust-lang.org/t/how-to-cache-a-vectors-capacity/94478/10). /// See [this dark magic](https://users.rust-lang.org/t/how-to-cache-a-vectors-capacity/94478/10).
@ -76,8 +78,9 @@ impl TextPipeline {
scale_factor: f64, scale_factor: f64,
buffer: &mut CosmicBuffer, buffer: &mut CosmicBuffer,
alignment: JustifyText, alignment: JustifyText,
font_system: &mut CosmicFontSystem,
) -> Result<(), TextError> { ) -> Result<(), TextError> {
let font_system = &mut self.font_system.0; let font_system = &mut font_system.0;
// return early if the fonts are not loaded yet // return early if the fonts are not loaded yet
let mut font_size = 0.; let mut font_size = 0.;
@ -188,6 +191,8 @@ impl TextPipeline {
textures: &mut Assets<Image>, textures: &mut Assets<Image>,
y_axis_orientation: YAxisOrientation, y_axis_orientation: YAxisOrientation,
buffer: &mut CosmicBuffer, buffer: &mut CosmicBuffer,
font_system: &mut CosmicFontSystem,
swash_cache: &mut SwashCache,
) -> Result<(), TextError> { ) -> Result<(), TextError> {
layout_info.glyphs.clear(); layout_info.glyphs.clear();
layout_info.size = Default::default(); layout_info.size = Default::default();
@ -204,11 +209,10 @@ impl TextPipeline {
scale_factor, scale_factor,
buffer, buffer,
text_alignment, text_alignment,
font_system,
)?; )?;
let box_size = buffer_dimensions(buffer); let box_size = buffer_dimensions(buffer);
let font_system = &mut self.font_system.0;
let swash_cache = &mut self.swash_cache.0;
buffer buffer
.layout_runs() .layout_runs()
@ -250,8 +254,8 @@ impl TextPipeline {
font_atlas_set.add_glyph_to_atlas( font_atlas_set.add_glyph_to_atlas(
texture_atlases, texture_atlases,
textures, textures,
font_system, &mut font_system.0,
swash_cache, &mut swash_cache.0,
layout_glyph, layout_glyph,
font_smoothing, font_smoothing,
) )
@ -300,6 +304,7 @@ impl TextPipeline {
linebreak_behavior: BreakLineOn, linebreak_behavior: BreakLineOn,
buffer: &mut CosmicBuffer, buffer: &mut CosmicBuffer,
text_alignment: JustifyText, text_alignment: JustifyText,
font_system: &mut CosmicFontSystem,
) -> Result<TextMeasureInfo, TextError> { ) -> Result<TextMeasureInfo, TextError> {
const MIN_WIDTH_CONTENT_BOUNDS: TextBounds = TextBounds::new_horizontal(0.0); const MIN_WIDTH_CONTENT_BOUNDS: TextBounds = TextBounds::new_horizontal(0.0);
@ -311,12 +316,13 @@ impl TextPipeline {
scale_factor, scale_factor,
buffer, buffer,
text_alignment, text_alignment,
font_system,
)?; )?;
let min_width_content_size = buffer_dimensions(buffer); let min_width_content_size = buffer_dimensions(buffer);
let max_width_content_size = { let max_width_content_size = {
let font_system = &mut self.font_system.0; let font_system = &mut font_system.0;
buffer.set_size(font_system, None, None); buffer.set_size(font_system, None, None);
buffer_dimensions(buffer) buffer_dimensions(buffer)
}; };
@ -328,11 +334,12 @@ impl TextPipeline {
}) })
} }
/// Get a mutable reference to the [`cosmic_text::FontSystem`]. /// Returns the [`cosmic_text::fontdb::ID`] for a given [`Font`] asset.
/// pub fn get_font_id(&self, asset_id: AssetId<Font>) -> Option<cosmic_text::fontdb::ID> {
/// Used internally. self.map_handle_to_font_id
pub fn font_system_mut(&mut self) -> &mut cosmic_text::FontSystem { .get(&asset_id)
&mut self.font_system.0 .cloned()
.map(|(id, _)| id)
} }
} }
@ -442,11 +449,11 @@ fn buffer_dimensions(buffer: &Buffer) -> Vec2 {
} }
/// Discards stale data cached in `FontSystem`. /// Discards stale data cached in `FontSystem`.
pub(crate) fn trim_cosmic_cache(mut pipeline: ResMut<TextPipeline>) { pub(crate) fn trim_cosmic_cache(mut font_system: ResMut<CosmicFontSystem>) {
// A trim age of 2 was found to reduce frame time variance vs age of 1 when tested with dynamic text. // A trim age of 2 was found to reduce frame time variance vs age of 1 when tested with dynamic text.
// See https://github.com/bevyengine/bevy/pull/15037 // See https://github.com/bevyengine/bevy/pull/15037
// //
// We assume only text updated frequently benefits from the shape cache (e.g. animated text, or // We assume only text updated frequently benefits from the shape cache (e.g. animated text, or
// text that is dynamically measured for UI). // text that is dynamically measured for UI).
pipeline.font_system_mut().shape_run_cache.trim(2); font_system.0.shape_run_cache.trim(2);
} }

View File

@ -1,6 +1,7 @@
use crate::pipeline::CosmicFontSystem;
use crate::{ use crate::{
BreakLineOn, CosmicBuffer, Font, FontAtlasSets, PositionedGlyph, Text, TextBounds, TextError, BreakLineOn, CosmicBuffer, Font, FontAtlasSets, PositionedGlyph, SwashCache, Text, TextBounds,
TextLayoutInfo, TextPipeline, YAxisOrientation, TextError, TextLayoutInfo, TextPipeline, YAxisOrientation,
}; };
use bevy_asset::Assets; use bevy_asset::Assets;
use bevy_color::LinearRgba; use bevy_color::LinearRgba;
@ -158,6 +159,8 @@ pub fn update_text2d_layout(
&mut TextLayoutInfo, &mut TextLayoutInfo,
&mut CosmicBuffer, &mut CosmicBuffer,
)>, )>,
mut font_system: ResMut<CosmicFontSystem>,
mut swash_cache: ResMut<SwashCache>,
) { ) {
// We need to consume the entire iterator, hence `last` // We need to consume the entire iterator, hence `last`
let factor_changed = scale_factor_changed.read().last().is_some(); let factor_changed = scale_factor_changed.read().last().is_some();
@ -198,6 +201,8 @@ pub fn update_text2d_layout(
&mut textures, &mut textures,
YAxisOrientation::BottomToTop, YAxisOrientation::BottomToTop,
buffer.as_mut(), buffer.as_mut(),
&mut font_system,
&mut swash_cache,
) { ) {
Err(TextError::NoSuchFont) => { Err(TextError::NoSuchFont) => {
// There was an error processing the text layout, let's add this entity to the // There was an error processing the text layout, let's add this entity to the
@ -274,7 +279,9 @@ mod tests {
.init_resource::<Assets<TextureAtlasLayout>>() .init_resource::<Assets<TextureAtlasLayout>>()
.init_resource::<FontAtlasSets>() .init_resource::<FontAtlasSets>()
.init_resource::<Events<WindowScaleFactorChanged>>() .init_resource::<Events<WindowScaleFactorChanged>>()
.insert_resource(TextPipeline::default()) .init_resource::<TextPipeline>()
.init_resource::<CosmicFontSystem>()
.init_resource::<SwashCache>()
.add_systems( .add_systems(
Update, Update,
( (

View File

@ -22,7 +22,9 @@ use thiserror::Error;
use ui_surface::UiSurface; use ui_surface::UiSurface;
#[cfg(feature = "bevy_text")] #[cfg(feature = "bevy_text")]
use bevy_text::{CosmicBuffer, TextPipeline}; use bevy_text::CosmicBuffer;
#[cfg(feature = "bevy_text")]
use bevy_text::CosmicFontSystem;
mod convert; mod convert;
pub mod debug; pub mod debug;
@ -124,7 +126,7 @@ pub fn ui_layout_system(
Option<&ScrollPosition>, Option<&ScrollPosition>,
)>, )>,
#[cfg(feature = "bevy_text")] mut buffer_query: Query<&mut CosmicBuffer>, #[cfg(feature = "bevy_text")] mut buffer_query: Query<&mut CosmicBuffer>,
#[cfg(feature = "bevy_text")] mut text_pipeline: ResMut<TextPipeline>, #[cfg(feature = "bevy_text")] mut font_system: ResMut<CosmicFontSystem>,
) { ) {
let UiLayoutSystemBuffers { let UiLayoutSystemBuffers {
interned_root_nodes, interned_root_nodes,
@ -250,8 +252,6 @@ pub fn ui_layout_system(
#[cfg(feature = "bevy_text")] #[cfg(feature = "bevy_text")]
let text_buffers = &mut buffer_query; let text_buffers = &mut buffer_query;
#[cfg(feature = "bevy_text")]
let font_system = text_pipeline.font_system_mut();
// clean up removed nodes after syncing children to avoid potential panic (invalid SlotMap key used) // clean up removed nodes after syncing children to avoid potential panic (invalid SlotMap key used)
ui_surface.remove_entities(removed_components.removed_nodes.read()); ui_surface.remove_entities(removed_components.removed_nodes.read());
@ -271,7 +271,7 @@ pub fn ui_layout_system(
#[cfg(feature = "bevy_text")] #[cfg(feature = "bevy_text")]
text_buffers, text_buffers,
#[cfg(feature = "bevy_text")] #[cfg(feature = "bevy_text")]
font_system, &mut font_system.0,
); );
for root in &camera.root_nodes { for root in &camera.root_nodes {
@ -523,6 +523,10 @@ mod tests {
world.init_resource::<ManualTextureViews>(); world.init_resource::<ManualTextureViews>();
#[cfg(feature = "bevy_text")] #[cfg(feature = "bevy_text")]
world.init_resource::<bevy_text::TextPipeline>(); world.init_resource::<bevy_text::TextPipeline>();
#[cfg(feature = "bevy_text")]
world.init_resource::<bevy_text::CosmicFontSystem>();
#[cfg(feature = "bevy_text")]
world.init_resource::<bevy_text::SwashCache>();
// spawn a dummy primary window and camera // spawn a dummy primary window and camera
world.spawn(( world.spawn((
@ -1160,6 +1164,10 @@ mod tests {
world.init_resource::<ManualTextureViews>(); world.init_resource::<ManualTextureViews>();
#[cfg(feature = "bevy_text")] #[cfg(feature = "bevy_text")]
world.init_resource::<bevy_text::TextPipeline>(); world.init_resource::<bevy_text::TextPipeline>();
#[cfg(feature = "bevy_text")]
world.init_resource::<bevy_text::CosmicFontSystem>();
#[cfg(feature = "bevy_text")]
world.init_resource::<bevy_text::SwashCache>();
// spawn a dummy primary window and camera // spawn a dummy primary window and camera
world.spawn(( world.spawn((

View File

@ -16,8 +16,9 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::{camera::Camera, texture::Image}; use bevy_render::{camera::Camera, texture::Image};
use bevy_sprite::TextureAtlasLayout; use bevy_sprite::TextureAtlasLayout;
use bevy_text::{ use bevy_text::{
scale_value, BreakLineOn, CosmicBuffer, Font, FontAtlasSets, JustifyText, Text, TextBounds, scale_value, BreakLineOn, CosmicBuffer, CosmicFontSystem, Font, FontAtlasSets, JustifyText,
TextError, TextLayoutInfo, TextMeasureInfo, TextPipeline, YAxisOrientation, SwashCache, Text, TextBounds, TextError, TextLayoutInfo, TextMeasureInfo, TextPipeline,
YAxisOrientation,
}; };
use bevy_utils::{tracing::error, Entry}; use bevy_utils::{tracing::error, Entry};
use taffy::style::AvailableSpace; use taffy::style::AvailableSpace;
@ -112,6 +113,7 @@ fn create_text_measure(
mut text_flags: Mut<TextFlags>, mut text_flags: Mut<TextFlags>,
buffer: &mut CosmicBuffer, buffer: &mut CosmicBuffer,
text_alignment: JustifyText, text_alignment: JustifyText,
font_system: &mut CosmicFontSystem,
) { ) {
match text_pipeline.create_text_measure( match text_pipeline.create_text_measure(
entity, entity,
@ -121,6 +123,7 @@ fn create_text_measure(
text.linebreak_behavior, text.linebreak_behavior,
buffer, buffer,
text_alignment, text_alignment,
font_system,
) { ) {
Ok(measure) => { Ok(measure) => {
if text.linebreak_behavior == BreakLineOn::NoWrap { if text.linebreak_behavior == BreakLineOn::NoWrap {
@ -173,6 +176,7 @@ pub fn measure_text_system(
With<Node>, With<Node>,
>, >,
mut text_pipeline: ResMut<TextPipeline>, mut text_pipeline: ResMut<TextPipeline>,
mut font_system: ResMut<CosmicFontSystem>,
) { ) {
scale_factors_buffer.clear(); scale_factors_buffer.clear();
@ -208,6 +212,7 @@ pub fn measure_text_system(
text_flags, text_flags,
buffer.as_mut(), buffer.as_mut(),
text_alignment, text_alignment,
&mut font_system,
); );
} }
} }
@ -229,6 +234,8 @@ fn queue_text(
mut text_flags: Mut<TextFlags>, mut text_flags: Mut<TextFlags>,
text_layout_info: Mut<TextLayoutInfo>, text_layout_info: Mut<TextLayoutInfo>,
buffer: &mut CosmicBuffer, buffer: &mut CosmicBuffer,
font_system: &mut CosmicFontSystem,
swash_cache: &mut SwashCache,
) { ) {
// Skip the text node if it is waiting for a new measure func // Skip the text node if it is waiting for a new measure func
if !text_flags.needs_new_measure_func { if !text_flags.needs_new_measure_func {
@ -258,6 +265,8 @@ fn queue_text(
textures, textures,
YAxisOrientation::TopToBottom, YAxisOrientation::TopToBottom,
buffer, buffer,
font_system,
swash_cache,
) { ) {
Err(TextError::NoSuchFont) => { Err(TextError::NoSuchFont) => {
// There was an error processing the text layout, try again next frame // There was an error processing the text layout, try again next frame
@ -305,6 +314,8 @@ pub fn text_system(
Option<&TargetCamera>, Option<&TargetCamera>,
&mut CosmicBuffer, &mut CosmicBuffer,
)>, )>,
mut font_system: ResMut<CosmicFontSystem>,
mut swash_cache: ResMut<SwashCache>,
) { ) {
scale_factors_buffer.clear(); scale_factors_buffer.clear();
@ -343,6 +354,8 @@ pub fn text_system(
text_flags, text_flags,
text_layout_info, text_layout_info,
buffer.as_mut(), buffer.as_mut(),
&mut font_system,
&mut swash_cache,
); );
} }
} }