Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3940,6 +3940,17 @@ description = "Demonstrates how to use generic font families"
category = "UI (User Interface)"
wasm = true

[[example]]
name = "font_lists"
path = "examples/ui/text/font_lists.rs"
doc-scrape-examples = true

[package.metadata.example.font_lists]
name = "Font Lists"
description = "Demonstrates how to select fonts using CSS font-family lists"
category = "UI (User Interface)"
wasm = true

[[example]]
name = "text"
path = "examples/ui/text/text.rs"
Expand Down
97 changes: 74 additions & 23 deletions crates/bevy_text/src/font.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
use crate::ComputedTextBlock;
use crate::FontCx;
use crate::FontFamilyEntry;
use crate::FontSource;
use crate::TextFont;
use bevy_asset::Asset;
use bevy_asset::AssetId;
use bevy_asset::Assets;
use bevy_ecs::change_detection::DetectChangesMut;
use bevy_ecs::system::Local;
use bevy_ecs::system::Query;
use bevy_ecs::system::Res;
use bevy_ecs::system::ResMut;
use bevy_platform::collections::HashSet;
use bevy_reflect::TypePath;
use parley::fontique::Blob;
use parley::fontique::FontInfoOverride;
use parley::FontFamilyName;
use smol_str::SmolStr;

/// An [`Asset`] that contains the data for a loaded font, if loaded as an asset.
Expand All @@ -29,8 +31,9 @@ use smol_str::SmolStr;
pub struct Font {
/// Content of a font file as bytes
pub data: Blob<u8>,
/// Font family name.
/// If the font file is a collection with multiple families, the first family name from the last font is used.
/// Font family name used to resolve this asset when referenced by handle.
/// If the font file is a collection with multiple families, this is the family name from the
/// first font face in the collection.
pub family_name: SmolStr,
}

Expand All @@ -46,32 +49,80 @@ impl Font {

/// Add new font assets to the internal font collection.
pub fn load_font_assets_into_font_collection(
fonts: Res<Assets<Font>>,
mut fonts: ResMut<Assets<Font>>,
mut loaded_fonts: Local<HashSet<AssetId<Font>>>,
mut font_cx: ResMut<FontCx>,
mut text_block_query: Query<&mut ComputedTextBlock>,
mut text_font_query: Query<&mut TextFont>,
) {
let mut new_fonts_added = false;

loaded_fonts.retain(|id| fonts.contains(*id));

for (id, font) in fonts.iter() {
if loaded_fonts.insert(id) {
font_cx.0.collection.register_fonts(
font.data.clone(),
Some(FontInfoOverride {
family_name: Some(font.family_name.as_str()),
..Default::default()
}),
);
new_fonts_added = true;
let new_asset_ids: Vec<_> = fonts.ids().filter(|id| loaded_fonts.insert(*id)).collect();

if new_asset_ids.is_empty() {
return;
}

let mut new_family_ids = Vec::new();
for asset_id in new_asset_ids.iter() {
let font_data = fonts
.get(*asset_id)
.expect("AssetId should have a corresponding asset")
.data
.clone();

let new_fonts = font_cx.collection.register_fonts(font_data, None);

if let Some((_, family_id)) = new_fonts
.iter()
.flat_map(|(family_id, fonts)| {
fonts
.iter()
.map(move |font_info| (font_info.index(), *family_id))
})
.min_by_key(|(index, _)| *index)
&& let Some(family_name) = font_cx.0.collection.family_name(family_id)
&& let Some(font) = fonts.get_mut_untracked(*asset_id)
{
font.family_name = family_name.into();
new_family_ids.extend(new_fonts.iter().map(|(family_id, _)| *family_id));
}
}

// Whenever new fonts are added, update all text blocks so they use the new fonts.
if new_fonts_added {
for mut block in text_block_query.iter_mut() {
block.needs_rerender = true;
for mut text_font in text_font_query.iter_mut() {
if match &text_font.font {
FontSource::Handle(handle) => new_asset_ids.contains(&handle.id()),
FontSource::Family(name) => font_cx
.collection
.family_id(name.as_str())
.is_some_and(|id| new_family_ids.contains(&id)),
FontSource::Css(source) => FontFamilyName::parse_css_list(source.as_str())
.map_while(Result::ok)
.any(|family| match family {
FontFamilyName::Named(name) => font_cx
.collection
.family_id(name.as_ref())
.is_some_and(|id| new_family_ids.contains(&id)),
FontFamilyName::Generic(generic_family) => font_cx
.collection
.generic_families(generic_family)
.any(|id| new_family_ids.contains(&id)),
}),
FontSource::List(items) => items.iter().any(|family| match family {
FontFamilyEntry::Named(name) => font_cx
.collection
.family_id(name.as_str())
.is_some_and(|id| new_family_ids.contains(&id)),
FontFamilyEntry::Generic(generic_family) => font_cx
.collection
.generic_families((*generic_family).into())
.any(|id| new_family_ids.contains(&id)),
}),
FontSource::Generic(generic_family) => font_cx
.collection
.generic_families((*generic_family).into())
.any(|id| new_family_ids.contains(&id)),
} {
text_font.set_changed();
}
}
}
7 changes: 4 additions & 3 deletions crates/bevy_text/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,10 @@ pub use text_edit::*;
pub mod prelude {
#[doc(hidden)]
pub use crate::{
Font, FontHinting, FontSize, FontSmoothing, FontSource, FontStyle, FontWeight, FontWidth,
Justify, LineBreak, Strikethrough, StrikethroughColor, TextColor, TextError, TextFont,
TextLayout, TextSpan, Underline, UnderlineColor,
Font, FontFamilyEntry, FontHinting, FontSize, FontSmoothing, FontSource, FontStyle,
FontWeight, FontWidth, GenericFontFamily, Justify, LineBreak, Strikethrough,
StrikethroughColor, TextColor, TextError, TextFont, TextLayout, TextSpan, Underline,
UnderlineColor,
};
}

Expand Down
56 changes: 24 additions & 32 deletions crates/bevy_text/src/parley_context.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use crate::TextError;
use crate::{FontSmoothing, FontSource};
use crate::{FontSmoothing, FontSource, GenericFontFamily};
use bevy_derive::Deref;
use bevy_derive::DerefMut;
use bevy_ecs::resource::Resource;
use parley::FontContext;
use parley::LayoutContext;
use parley::{FontContext, GenericFamily};
use swash::scale::ScaleContext;

#[derive(Copy, Clone, PartialEq, Default, Debug)]
Expand Down Expand Up @@ -42,24 +42,16 @@ impl FontCx {
/// up the `Font` asset instead.
pub fn get_family<'a>(&'a mut self, source: &'a FontSource) -> Option<&'a str> {
let generic_family = match source {
FontSource::Handle(_) => return None,
FontSource::Family(family) => return Some(family.as_str()),
FontSource::Serif => GenericFamily::Serif,
FontSource::SansSerif => GenericFamily::SansSerif,
FontSource::Cursive => GenericFamily::Cursive,
FontSource::Fantasy => GenericFamily::Fantasy,
FontSource::Monospace => GenericFamily::Monospace,
FontSource::SystemUi => GenericFamily::SystemUi,
FontSource::UiSerif => GenericFamily::UiSerif,
FontSource::UiSansSerif => GenericFamily::UiSansSerif,
FontSource::UiMonospace => GenericFamily::UiMonospace,
FontSource::UiRounded => GenericFamily::UiRounded,
FontSource::Emoji => GenericFamily::Emoji,
FontSource::Math => GenericFamily::Math,
FontSource::FangSong => GenericFamily::FangSong,
FontSource::Handle(_) | FontSource::Css(_) | FontSource::List(_) => return None,
FontSource::Generic(generic_family) => *generic_family,
};

let family_id = self.0.collection.generic_families(generic_family).next();
let family_id = self
.0
.collection
.generic_families(generic_family.into())
.next();
family_id.and_then(|id| self.0.collection.family_name(id))
}

Expand All @@ -73,81 +65,81 @@ impl FontCx {
/// These methods will return an error if the provided family name does not already exist in the font collection.
pub fn set_generic_family(
&mut self,
generic: GenericFamily,
generic: impl Into<parley::GenericFamily>,
family_name: &str,
) -> Result<(), TextError> {
self.collection
.family_id(family_name)
.ok_or(TextError::NoSuchFontFamily(family_name.to_string()))
.map(|id| {
self.collection
.set_generic_families(generic, core::iter::once(id));
.set_generic_families(generic.into(), core::iter::once(id));
})
}

/// Sets the serif generic family mapping.
pub fn set_serif_family(&mut self, family_name: &str) -> Result<(), TextError> {
self.set_generic_family(GenericFamily::Serif, family_name)
self.set_generic_family(GenericFontFamily::Serif, family_name)
}

/// Sets the sans-serif generic family mapping.
pub fn set_sans_serif_family(&mut self, family_name: &str) -> Result<(), TextError> {
self.set_generic_family(GenericFamily::SansSerif, family_name)
self.set_generic_family(GenericFontFamily::SansSerif, family_name)
}

/// Sets the cursive generic family mapping.
pub fn set_cursive_family(&mut self, family_name: &str) -> Result<(), TextError> {
self.set_generic_family(GenericFamily::Cursive, family_name)
self.set_generic_family(GenericFontFamily::Cursive, family_name)
}

/// Sets the fantasy generic family mapping.
pub fn set_fantasy_family(&mut self, family_name: &str) -> Result<(), TextError> {
self.set_generic_family(GenericFamily::Fantasy, family_name)
self.set_generic_family(GenericFontFamily::Fantasy, family_name)
}

/// Sets the monospace generic family mapping.
pub fn set_monospace_family(&mut self, family_name: &str) -> Result<(), TextError> {
self.set_generic_family(GenericFamily::Monospace, family_name)
self.set_generic_family(GenericFontFamily::Monospace, family_name)
}

/// Sets the system-ui generic family mapping.
pub fn set_system_ui_family(&mut self, family_name: &str) -> Result<(), TextError> {
self.set_generic_family(GenericFamily::SystemUi, family_name)
self.set_generic_family(GenericFontFamily::SystemUi, family_name)
}

/// Sets the ui-serif generic family mapping.
pub fn set_ui_serif_family(&mut self, family_name: &str) -> Result<(), TextError> {
self.set_generic_family(GenericFamily::UiSerif, family_name)
self.set_generic_family(GenericFontFamily::UiSerif, family_name)
}

/// Sets the ui-sans-serif generic family mapping.
pub fn set_ui_sans_serif_family(&mut self, family_name: &str) -> Result<(), TextError> {
self.set_generic_family(GenericFamily::UiSansSerif, family_name)
self.set_generic_family(GenericFontFamily::UiSansSerif, family_name)
}

/// Sets the ui-monospace generic family mapping.
pub fn set_ui_monospace_family(&mut self, family_name: &str) -> Result<(), TextError> {
self.set_generic_family(GenericFamily::UiMonospace, family_name)
self.set_generic_family(GenericFontFamily::UiMonospace, family_name)
}

/// Sets the ui-rounded generic family mapping.
pub fn set_ui_rounded_family(&mut self, family_name: &str) -> Result<(), TextError> {
self.set_generic_family(GenericFamily::UiRounded, family_name)
self.set_generic_family(GenericFontFamily::UiRounded, family_name)
}

/// Sets the emoji generic family mapping.
pub fn set_emoji_family(&mut self, family_name: &str) -> Result<(), TextError> {
self.set_generic_family(GenericFamily::Emoji, family_name)
self.set_generic_family(GenericFontFamily::Emoji, family_name)
}

/// Sets the math generic family mapping.
pub fn set_math_family(&mut self, family_name: &str) -> Result<(), TextError> {
self.set_generic_family(GenericFamily::Math, family_name)
self.set_generic_family(GenericFontFamily::Math, family_name)
}

/// Sets the fangsong generic family mapping.
pub fn set_fang_song_family(&mut self, family_name: &str) -> Result<(), TextError> {
self.set_generic_family(GenericFamily::FangSong, family_name)
self.set_generic_family(GenericFontFamily::FangSong, family_name)
}
}

Expand Down
44 changes: 23 additions & 21 deletions crates/bevy_text/src/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -425,29 +425,31 @@ pub fn resolve_font_source<'a>(
)))
}
FontSource::Family(family) => FontFamily::named(family.as_str()),
generic => {
FontSource::Css(source) => FontFamily::Source(Cow::Borrowed(source.as_str())),
FontSource::List(list) => FontFamily::List(Cow::Owned(
list.iter()
.map(|family| match family {
crate::FontFamilyEntry::Named(name) => {
parley::FontFamilyName::Named(Cow::Borrowed(name.as_str()))
}
crate::FontFamilyEntry::Generic(generic_family) => {
#[cfg(not(feature = "system_font_discovery"))]
bevy_log::error_once!( "A generic FontSource ({generic_family:?}) was used, but the `system_font_discovery` \
feature is not enabled. Text may not render. Enable the feature to allow Bevy \
to discover system fonts.");
parley::FontFamilyName::Generic((*generic_family).into())
}
})
.collect(),
)),
FontSource::Generic(generic_family) => {
#[cfg(not(feature = "system_font_discovery"))]
bevy_log::error_once!(
"A generic FontSource ({generic:?}) was used, but the `system_font_discovery` \
bevy_log::error_once!( "A generic FontSource ({generic_family:?}) was used, but the `system_font_discovery` \
feature is not enabled. Text may not render. Enable the feature to allow Bevy \
to discover system fonts."
);
match generic {
FontSource::Handle(_) | FontSource::Family(_) => unreachable!(),
FontSource::Serif => parley::GenericFamily::Serif.into(),
FontSource::SansSerif => parley::GenericFamily::SansSerif.into(),
FontSource::Cursive => parley::GenericFamily::Cursive.into(),
FontSource::Fantasy => parley::GenericFamily::Fantasy.into(),
FontSource::Monospace => parley::GenericFamily::Monospace.into(),
FontSource::SystemUi => parley::GenericFamily::SystemUi.into(),
FontSource::UiSerif => parley::GenericFamily::UiSerif.into(),
FontSource::UiSansSerif => parley::GenericFamily::UiSansSerif.into(),
FontSource::UiMonospace => parley::GenericFamily::UiMonospace.into(),
FontSource::UiRounded => parley::GenericFamily::UiRounded.into(),
FontSource::Emoji => parley::GenericFamily::Emoji.into(),
FontSource::Math => parley::GenericFamily::Math.into(),
FontSource::FangSong => parley::GenericFamily::FangSong.into(),
}
to discover system fonts.");
FontFamily::Single(parley::FontFamilyName::Generic(
(*generic_family).into(),
))
}
})
}
Expand Down
Loading
Loading