Skip to content
Merged
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 @@ -3808,6 +3808,17 @@ description = "Demonstrates how to use font weights."
category = "UI (User Interface)"
wasm = true

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

[package.metadata.example.font_variations]
name = "Font Variations"
description = "Demonstrates how to use OpenType font variations."
category = "UI (User Interface)"
wasm = true

[[example]]
name = "window_fallthrough"
path = "examples/ui/window_fallthrough.rs"
Expand Down
4 changes: 4 additions & 0 deletions crates/bevy_text/src/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,10 @@ impl TextPipeline {
);
builder.push(
StyleProperty::FontFeatures((&section.text_font.font_features).into()),
range.clone(),
);
builder.push(
StyleProperty::FontVariations((&section.text_font.font_variations).into()),
range,
);
}
Expand Down
101 changes: 100 additions & 1 deletion crates/bevy_text/src/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use bevy_utils::{default, once};
use core::fmt::{Debug, Formatter};
use core::str::from_utf8;
use parley::setting::Tag;
use parley::{FontFeature, Layout};
use parley::{FontFeature, FontVariation, Layout};
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use smol_str::SmolStr;
Expand Down Expand Up @@ -404,6 +404,8 @@ pub struct TextFont {
pub font_smoothing: FontSmoothing,
/// OpenType features for .otf fonts that support them.
pub font_features: FontFeatures,
/// OpenType variations for variable fonts that support them.
pub font_variations: FontVariations,
}

impl TextFont {
Expand Down Expand Up @@ -466,6 +468,7 @@ impl Default for TextFont {
weight: FontWeight::NORMAL,
width: FontWidth::NORMAL,
font_features: FontFeatures::default(),
font_variations: FontVariations::default(),
font_smoothing: Default::default(),
}
}
Expand Down Expand Up @@ -915,6 +918,102 @@ impl From<&FontFeatures> for parley::style::FontFeatures<'static> {
}
}

/// An OpenType font variation tag.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Reflect)]
pub struct FontVariationTag([u8; 4]);

impl FontVariationTag {
/// Varies the stroke thickness. The range is typically 1 to 1000.
pub const WEIGHT: FontVariationTag = FontVariationTag::new(b"wght");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels a bit empty just to have the wght tag, it would be nice to have a few more here.
And the doc comment for weight could mention the values will be clamped to some subrange inside 1..1000.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Otherwise, no complaints.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


/// Varies the width of glyphs from narrower to wider. The range is typically 50 to 200 with
/// 100 being standard width.
pub const WIDTH: FontVariationTag = FontVariationTag::new(b"wdth");

/// Varies between upright and slanted glyphs. The range is typically between -90 and +90 degrees,
/// where 0 is upright.
pub const SLANT: FontVariationTag = FontVariationTag::new(b"slnt");

/// Varies the design of glyphs for different optical sizes (physical font size).
/// The range is typically 6 to 72.
pub const OPTICAL_SIZE: FontVariationTag = FontVariationTag::new(b"opsz");

/// Create a new [`FontVariationTag`] from raw bytes.
pub const fn new(src: &[u8; 4]) -> Self {
Self(*src)
}
}

impl Debug for FontVariationTag {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match from_utf8(&self.0) {
Ok(s) => write!(f, "FontVariationTag(\"{}\")", s),
Err(_) => write!(f, "FontVariationTag({:?})", self.0),
}
}
}

/// OpenType font variations for variable fonts that support them.
///
/// Variable fonts expose named axes (e.g. `wght`, `FILL`) that accept continuous `f32` values.
/// This is distinct from [`FontFeatures`], which mainly controls on/off OpenType layout features.
///
/// # Usage
/// ```
/// use bevy_text::{FontVariationTag, FontVariations};
///
/// let variations = FontVariations::builder()
/// .set(FontVariationTag::WEIGHT, 400.0)
/// .build();
/// ```
#[derive(Clone, Debug, Default, Reflect, PartialEq)]
pub struct FontVariations {
variations: Vec<(FontVariationTag, f32)>,
}

impl FontVariations {
/// Create a new [`FontVariationsBuilder`].
pub fn builder() -> FontVariationsBuilder {
FontVariationsBuilder::default()
}
}

/// A builder for [`FontVariations`].
#[derive(Clone, Default)]
pub struct FontVariationsBuilder {
variations: Vec<(FontVariationTag, f32)>,
}

impl FontVariationsBuilder {
/// Set a font variation to a specific value.
pub fn set(mut self, tag: FontVariationTag, value: f32) -> Self {
self.variations.push((tag, value));
self
}

/// Build a [`FontVariations`] from the values set within this builder.
pub fn build(self) -> FontVariations {
FontVariations {
variations: self.variations,
}
}
}

impl From<&FontVariations> for parley::style::FontVariations<'static> {
fn from(font_variations: &FontVariations) -> Self {
parley::style::FontVariations::List(
font_variations
.variations
.iter()
.map(|(tag, value)| FontVariation {
tag: Tag::new(&tag.0),
value: *value,
})
.collect(),
)
}
}

/// Specifies the height of each line of text for `Text` and `Text2d`
///
/// Default is 1.2x the font size
Expand Down
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,7 @@ Example | Description
[Flex Layout](../examples/ui/layout/flex_layout.rs) | Demonstrates how the AlignItems and JustifyContent properties can be composed to layout nodes and position text
[Font Atlas Debug](../examples/ui/text/font_atlas_debug.rs) | Illustrates how FontAtlases are populated (used to optimize text rendering internally)
[Font Queries](../examples/ui/text/font_query.rs) | Demonstrates font querying
[Font Variations](../examples/ui/text/font_variations.rs) | Demonstrates how to use OpenType font variations.
[Font Weights](../examples/ui/text/font_weights.rs) | Demonstrates how to use font weights.
[Generic Font Families](../examples/ui/text/generic_font_families.rs) | Demonstrates how to use generic font families
[Ghost Nodes](../examples/ui/layout/ghost_nodes.rs) | Demonstrates the use of Ghost Nodes to skip entities in the UI layout hierarchy
Expand Down
61 changes: 61 additions & 0 deletions examples/ui/text/font_variations.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//! This example demonstrates how to use font variations to control variable font axes.

use bevy::prelude::*;
use bevy::text::{FontVariationTag, FontVariations};

fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.run();
}

fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
let font: FontSource = asset_server.load("fonts/MonaSans-VariableFont.ttf").into();

commands.spawn(Camera2d);

commands.spawn((
Node {
flex_direction: FlexDirection::Column,
align_self: AlignSelf::Center,
justify_self: JustifySelf::Center,
align_items: AlignItems::Center,
..default()
},
children![
(
Text::new("Font Variations (wght axis)"),
TextFont {
font: font.clone(),
font_size: FontSize::Px(32.0),
..default()
},
Underline,
),
(
Node {
flex_direction: FlexDirection::Column,
padding: px(8.).all(),
row_gap: px(8.),
..default()
},
Children::spawn(SpawnIter(
[100, 200, 300, 400, 500, 600, 700, 800, 900]
.into_iter()
.map(move |weight| (
Text(format!("wght {weight}")),
TextFont {
font: font.clone(),
font_size: FontSize::Px(32.0),
font_variations: FontVariations::builder()
.set(FontVariationTag::WEIGHT, weight as f32)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels like a code smell that we should just be unifying the APIs here somehow.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can totally do that.

The question is what would we call the unified struct and builder. I was thinking

struct FontVariables {
    features: Vec<...>,
    variations: Vec<...>
}

and a corresponding builder.

However, this is less discoverable than FontVariations and FontFeatures, which match the OpenType names.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not so sure about unifying the APIs. I agree it would be more user friendly, but parley maintains separate lists and it might complicate font inheritance and change detection. So personally I think it would be better to leave it like this for now.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be okay though, I'm not certain.

.build(),
..default()
},
))
)),
),
],
));
}
Loading