#![doc = include_str!("../README.md")]
#![allow(clippy::type_complexity)]
#![allow(clippy::too_many_arguments)]
#![allow(clippy::collapsible_if)]
mod atlas;
mod change_detection;
mod color_table;
mod emoji;
mod export;
mod fetch;
mod layers;
mod line;
mod loading;
mod mesh_util;
mod misc;
mod parse;
mod prepare;
mod render;
mod styling;
mod tess;
mod text3d;
pub use prepare::{DrawStyle, FontSystemGuard, TextProgressReportCallback, TextRenderer};
pub use atlas::{TextAtlas, TextAtlasHandle};
#[cfg(feature = "reflect")]
use bevy::prelude::{Reflect, ReflectDefault, ReflectResource};
use bevy::{
app::{App, First, Plugin, PostUpdate},
asset::{AssetApp, AssetId, Assets},
ecs::{
query::With,
resource::Resource,
schedule::{common_conditions::resource_exists, IntoScheduleConfigs, SystemSet},
system::{Query, ResMut},
world::Ref,
},
image::Image,
math::FloatOrd,
transform::TransformSystems,
window::{PrimaryWindow, Window},
};
use change_detection::TouchMaterialSet;
#[cfg(feature = "2d")]
pub use change_detection::TouchTextMaterial2dPlugin;
#[cfg(feature = "3d")]
pub use change_detection::TouchTextMaterial3dPlugin;
pub use export::{GlyphMeta, MeshExport, MeshExportEntry};
pub use fetch::{FetchedTextSegment, SharedTextSegment, TextFetch};
use loading::{load_cosmic_fonts_system, LoadCosmicFonts};
pub use misc::*;
pub use parse::ParseError;
pub use styling::{SegmentSize, SegmentStyle, Text3dStyling};
pub use text3d::{Text3d, Text3dSegment};
fn synchronize_scale_factor(
mut settings: ResMut<Text3dPlugin>,
main_window: Query<Ref<Window>, With<PrimaryWindow>>,
mut atlases: ResMut<Assets<TextAtlas>>,
mut images: ResMut<Assets<Image>>,
) {
if settings.sync_scale_factor_with_main_window {
if let Ok(window) = main_window.single() {
if window.scale_factor() != settings.scale_factor {
settings.scale_factor = window.scale_factor();
for (_, atlas) in atlases.iter_mut() {
atlas.clear(&mut images);
}
}
}
}
}
#[derive(Debug, Resource, Clone)]
#[cfg_attr(feature = "reflect", derive(Reflect))]
#[cfg_attr(feature = "reflect", reflect(Resource, Default))]
pub struct Text3dPlugin {
pub default_atlas_dimension: (usize, usize),
pub scale_factor: f32,
pub sync_scale_factor_with_main_window: bool,
pub double_scale_factor_threshold: f32,
pub locale: Option<String>,
pub load_system_fonts: bool,
pub asynchronous_load: bool,
pub placeholder_family: String,
pub placeholder_glyph_widths: Vec<f32>,
pub placeholder_glyph_origin: char,
pub placeholder_glyphs_generated: Vec<(f32, String)>,
pub serif_family: String,
pub sans_serif_family: String,
pub cursive_family: String,
pub monospace_family: String,
pub fantasy_family: String,
}
impl Text3dPlugin {
pub fn get_placeholder_glyph(&self, size: f32) -> &str {
let Some((_, s)) = self
.placeholder_glyphs_generated
.iter()
.min_by_key(|(s, _)| FloatOrd((size - *s).abs()))
else {
return " ";
};
s
}
}
#[derive(Debug, Resource, Default, Clone)]
pub struct LoadFonts {
pub font_paths: Vec<String>,
pub font_directories: Vec<String>,
pub font_embedded: Vec<&'static [u8]>,
}
impl Default for Text3dPlugin {
fn default() -> Self {
Self {
default_atlas_dimension: (1024, 1024),
scale_factor: 1.0,
double_scale_factor_threshold: f32::NEG_INFINITY,
sync_scale_factor_with_main_window: true,
load_system_fonts: false,
asynchronous_load: false,
locale: None,
serif_family: String::new(),
sans_serif_family: String::new(),
cursive_family: String::new(),
monospace_family: String::new(),
fantasy_family: String::new(),
placeholder_family: "_Placeholder".into(),
placeholder_glyph_widths: vec![
0.1,
0.2,
0.25,
1.0 / 3.0,
0.4,
0.5,
2.0 / 3.0,
0.75,
std::f32::consts::GOLDEN_RATIO / 2.,
1.0,
std::f32::consts::SQRT_2,
1.5,
std::f32::consts::GOLDEN_RATIO,
2.0,
2.5,
3.0,
3.5,
4.0,
5.0,
6.0,
],
placeholder_glyph_origin: '!',
placeholder_glyphs_generated: Vec::new(),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, SystemSet)]
pub struct Text3dSet;
impl Plugin for Text3dPlugin {
fn build(&self, app: &mut App) {
app.init_asset::<TextAtlas>();
app.init_resource::<LoadFonts>();
let mut res = self.clone();
res.placeholder_glyphs_generated = self
.placeholder_glyph_widths
.iter()
.copied()
.enumerate()
.map(|(i, x)| {
(
x,
char::from_u32(self.placeholder_glyph_origin as u32 + i as u32)
.unwrap_or(' ')
.to_string(),
)
})
.collect();
app.insert_resource::<Text3dPlugin>(res);
let (x, y) = self.default_atlas_dimension;
let _ = app
.world_mut()
.resource_mut::<Assets<Image>>()
.insert(&TextAtlas::DEFAULT_IMAGE, TextAtlas::empty_image(x, y));
let _ = app
.world_mut()
.resource_mut::<Assets<TextAtlas>>()
.insert(AssetId::default(), TextAtlas::new(TextAtlas::DEFAULT_IMAGE));
app.add_systems(First, synchronize_scale_factor);
app.add_systems(
First,
load_cosmic_fonts_system.run_if(resource_exists::<LoadCosmicFonts>),
);
app.add_systems(
PostUpdate,
(
fetch::text_fetch_system,
render::text_render.run_if(resource_exists::<TextRenderer>),
)
.chain()
.in_set(Text3dSet)
.before(TouchMaterialSet),
);
app.configure_sets(PostUpdate, Text3dSet.before(TransformSystems::Propagate));
app.configure_sets(PostUpdate, TouchMaterialSet.in_set(Text3dSet));
#[cfg(feature = "2d")]
app.add_plugins(TouchTextMaterial2dPlugin::<
bevy::sprite_render::ColorMaterial,
>::default());
#[cfg(feature = "3d")]
app.add_plugins(TouchTextMaterial3dPlugin::<bevy::pbr::StandardMaterial>::default());
#[cfg(feature = "reflect")]
app.register_type::<Text3d>()
.register_type::<Text3dStyling>()
.register_type::<Text3dSegment>()
.register_type::<SharedTextSegment>()
.register_type::<FetchedTextSegment>()
.register_type::<Text3dPlugin>();
}
fn cleanup(&self, app: &mut App) {
let fonts = app
.world_mut()
.remove_resource::<LoadFonts>()
.unwrap_or_default();
if self.asynchronous_load {
app.insert_resource(self.load_fonts_concurrent(fonts));
} else {
app.insert_resource(self.load_fonts_blocking(fonts));
}
}
}