Skip to content

Commit 5986d5d

Browse files
TotalKrilltigregalisnicoburnsbytemunchDimchikkk
authored
Cosmic text (#10193)
# Replace ab_glyph with the more capable cosmic-text Fixes #7616. Cosmic-text is a more mature text-rendering library that handles scripts and ligatures better than ab_glyph, it can also handle system fonts which can be implemented in bevy in the future Rebase of #8808 ## Changelog Replaces text renderer ab_glyph with cosmic-text The definition of the font size has changed with the migration to cosmic text. The behavior is now consistent with other platforms (e.g. the web), where the font size in pixels measures the height of the font (the distance between the top of the highest ascender and the bottom of the lowest descender). Font sizes in your app need to be rescaled to approximately 1.2x smaller; for example, if you were using a font size of 60.0, you should now use a font size of 50.0. ## Migration guide - `Text2dBounds` has been replaced with `TextBounds`, and it now accepts `Option`s to the bounds, instead of using `f32::INFINITY` to inidicate lack of bounds - Textsizes should be changed, dividing the current size with 1.2 will result in the same size as before. - `TextSettings` struct is removed - Feature `subpixel_alignment` has been removed since cosmic-text already does this automatically - TextBundles and things rendering texts requires the `CosmicBuffer` Component on them as well ## Suggested followups: - TextPipeline: reconstruct byte indices for keeping track of eventual cursors in text input - TextPipeline: (future work) split text entities into section entities - TextPipeline: (future work) text editing - Support line height as an option. Unitless `1.2` is the default used in browsers (1.2x font size). - Support System Fonts and font families - Example showing of animated text styles. Eg. throbbing hyperlinks --------- Co-authored-by: tigregalis <[email protected]> Co-authored-by: Nico Burns <[email protected]> Co-authored-by: sam edelsten <[email protected]> Co-authored-by: Dimchikkk <[email protected]> Co-authored-by: Alice Cecile <[email protected]> Co-authored-by: Rob Parrett <[email protected]>
1 parent 1c2f687 commit 5986d5d

33 files changed

+1079
-851
lines changed

Cargo.toml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -271,9 +271,6 @@ wayland = ["bevy_internal/wayland"]
271271
# X11 display server support
272272
x11 = ["bevy_internal/x11"]
273273

274-
# Enable rendering of font glyphs using subpixel accuracy
275-
subpixel_glyph_atlas = ["bevy_internal/subpixel_glyph_atlas"]
276-
277274
# Enable systems that allow for automated testing on CI
278275
bevy_ci_testing = ["bevy_internal/bevy_ci_testing"]
279276

crates/bevy_internal/Cargo.toml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,6 @@ async-io = ["bevy_tasks/async-io"]
9696
wayland = ["bevy_winit/wayland"]
9797
x11 = ["bevy_winit/x11"]
9898

99-
# enable rendering of font glyphs using subpixel accuracy
100-
subpixel_glyph_atlas = ["bevy_text/subpixel_glyph_atlas"]
101-
10299
# Transmission textures in `StandardMaterial`:
103100
pbr_transmission_textures = [
104101
"bevy_pbr?/pbr_transmission_textures",

crates/bevy_sprite/src/render/mod.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -375,14 +375,15 @@ pub fn extract_sprites(
375375
.map(|e| (commands.spawn_empty().id(), e)),
376376
);
377377
} else {
378-
let atlas_rect = sheet.and_then(|s| s.texture_rect(&texture_atlases));
378+
let atlas_rect =
379+
sheet.and_then(|s| s.texture_rect(&texture_atlases).map(|r| r.as_rect()));
379380
let rect = match (atlas_rect, sprite.rect) {
380381
(None, None) => None,
381382
(None, Some(sprite_rect)) => Some(sprite_rect),
382-
(Some(atlas_rect), None) => Some(atlas_rect.as_rect()),
383+
(Some(atlas_rect), None) => Some(atlas_rect),
383384
(Some(atlas_rect), Some(mut sprite_rect)) => {
384-
sprite_rect.min += atlas_rect.min.as_vec2();
385-
sprite_rect.max += atlas_rect.min.as_vec2();
385+
sprite_rect.min += atlas_rect.min;
386+
sprite_rect.max += atlas_rect.min;
386387

387388
Some(sprite_rect)
388389
}

crates/bevy_sprite/src/texture_atlas.rs

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -31,26 +31,6 @@ pub struct TextureAtlasLayout {
3131
pub(crate) texture_handles: Option<HashMap<AssetId<Image>, usize>>,
3232
}
3333

34-
/// Component used to draw a specific section of a texture.
35-
///
36-
/// It stores a handle to [`TextureAtlasLayout`] and the index of the current section of the atlas.
37-
/// The texture atlas contains various *sections* of a given texture, allowing users to have a single
38-
/// image file for either sprite animation or global mapping.
39-
/// You can change the texture [`index`](Self::index) of the atlas to animate the sprite or display only a *section* of the texture
40-
/// for efficient rendering of related game objects.
41-
///
42-
/// Check the following examples for usage:
43-
/// - [`animated sprite sheet example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_sheet.rs)
44-
/// - [`sprite animation event example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_animation.rs)
45-
/// - [`texture atlas example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/texture_atlas.rs)
46-
#[derive(Component, Default, Debug, Clone, Reflect)]
47-
pub struct TextureAtlas {
48-
/// Texture atlas layout handle
49-
pub layout: Handle<TextureAtlasLayout>,
50-
/// Texture atlas section index
51-
pub index: usize,
52-
}
53-
5434
impl TextureAtlasLayout {
5535
/// Create a new empty layout with custom `dimensions`
5636
pub fn new_empty(dimensions: UVec2) -> Self {
@@ -149,6 +129,26 @@ impl TextureAtlasLayout {
149129
}
150130
}
151131

132+
/// Component used to draw a specific section of a texture.
133+
///
134+
/// It stores a handle to [`TextureAtlasLayout`] and the index of the current section of the atlas.
135+
/// The texture atlas contains various *sections* of a given texture, allowing users to have a single
136+
/// image file for either sprite animation or global mapping.
137+
/// You can change the texture [`index`](Self::index) of the atlas to animate the sprite or display only a *section* of the texture
138+
/// for efficient rendering of related game objects.
139+
///
140+
/// Check the following examples for usage:
141+
/// - [`animated sprite sheet example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_sheet.rs)
142+
/// - [`sprite animation event example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_animation.rs)
143+
/// - [`texture atlas example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/texture_atlas.rs)
144+
#[derive(Component, Default, Debug, Clone, Reflect)]
145+
pub struct TextureAtlas {
146+
/// Texture atlas layout handle
147+
pub layout: Handle<TextureAtlasLayout>,
148+
/// Texture atlas section index
149+
pub index: usize,
150+
}
151+
152152
impl TextureAtlas {
153153
/// Retrieves the current texture [`URect`] of the sprite sheet according to the section `index`
154154
pub fn texture_rect(&self, texture_atlases: &Assets<TextureAtlasLayout>) -> Option<URect> {

crates/bevy_text/Cargo.toml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ license = "MIT OR Apache-2.0"
99
keywords = ["bevy"]
1010

1111
[features]
12-
subpixel_glyph_atlas = []
1312
default_font = []
1413

1514
[dependencies]
1615
# bevy
1716
bevy_app = { path = "../bevy_app", version = "0.14.0-dev" }
1817
bevy_asset = { path = "../bevy_asset", version = "0.14.0-dev" }
1918
bevy_color = { path = "../bevy_color", version = "0.14.0-dev" }
19+
bevy_derive = { path = "../bevy_derive", version = "0.14.0-dev" }
2020
bevy_ecs = { path = "../bevy_ecs", version = "0.14.0-dev" }
2121
bevy_math = { path = "../bevy_math", version = "0.14.0-dev" }
2222
bevy_reflect = { path = "../bevy_reflect", version = "0.14.0-dev", features = [
@@ -29,10 +29,11 @@ bevy_window = { path = "../bevy_window", version = "0.14.0-dev" }
2929
bevy_utils = { path = "../bevy_utils", version = "0.14.0-dev" }
3030

3131
# other
32-
ab_glyph = "0.2.6"
33-
glyph_brush_layout = "0.2.1"
32+
cosmic-text = "0.12"
3433
thiserror = "1.0"
3534
serde = { version = "1", features = ["derive"] }
35+
unicode-bidi = "0.3.13"
36+
sys-locale = "0.3.0"
3637

3738
[dev-dependencies]
3839
approx = "0.5.1"

crates/bevy_text/src/bounds.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
use bevy_ecs::{component::Component, reflect::ReflectComponent};
2+
use bevy_math::Vec2;
3+
use bevy_reflect::Reflect;
4+
5+
/// The maximum width and height of text. The text will wrap according to the specified size.
6+
/// Characters out of the bounds after wrapping will be truncated. Text is aligned according to the
7+
/// specified [`JustifyText`](crate::text::JustifyText).
8+
///
9+
/// Note: only characters that are completely out of the bounds will be truncated, so this is not a
10+
/// reliable limit if it is necessary to contain the text strictly in the bounds. Currently this
11+
/// component is mainly useful for text wrapping only.
12+
#[derive(Component, Copy, Clone, Debug, Reflect)]
13+
#[reflect(Component)]
14+
pub struct TextBounds {
15+
/// The maximum width of text in logical pixels.
16+
/// If `None`, the width is unbounded.
17+
pub width: Option<f32>,
18+
/// The maximum height of text in logical pixels.
19+
/// If `None`, the height is unbounded.
20+
pub height: Option<f32>,
21+
}
22+
23+
impl Default for TextBounds {
24+
#[inline]
25+
fn default() -> Self {
26+
Self::UNBOUNDED
27+
}
28+
}
29+
30+
impl TextBounds {
31+
/// Unbounded text will not be truncated or wrapped.
32+
pub const UNBOUNDED: Self = Self {
33+
width: None,
34+
height: None,
35+
};
36+
37+
/// Creates a new `TextBounds`, bounded with the specified width and height values.
38+
#[inline]
39+
pub const fn new(width: f32, height: f32) -> Self {
40+
Self {
41+
width: Some(width),
42+
height: Some(height),
43+
}
44+
}
45+
46+
/// Creates a new `TextBounds`, bounded with the specified width value and unbounded on height.
47+
#[inline]
48+
pub const fn new_horizontal(width: f32) -> Self {
49+
Self {
50+
width: Some(width),
51+
height: None,
52+
}
53+
}
54+
55+
/// Creates a new `TextBounds`, bounded with the specified height value and unbounded on width.
56+
#[inline]
57+
pub const fn new_vertical(height: f32) -> Self {
58+
Self {
59+
width: None,
60+
height: Some(height),
61+
}
62+
}
63+
}
64+
65+
impl From<Vec2> for TextBounds {
66+
#[inline]
67+
fn from(v: Vec2) -> Self {
68+
Self::new(v.x, v.y)
69+
}
70+
}

crates/bevy_text/src/error.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
1-
use ab_glyph::GlyphId;
1+
use cosmic_text::CacheKey;
22
use thiserror::Error;
33

44
#[derive(Debug, PartialEq, Eq, Error)]
5+
/// Errors related to the textsystem
56
pub enum TextError {
7+
/// Font was not found, this could be that the font has not yet been loaded, or
8+
/// that the font failed to load for some other reason
69
#[error("font not found")]
710
NoSuchFont,
11+
/// Failed to add glyph to a newly created atlas for some reason
812
#[error("failed to add glyph to newly-created atlas {0:?}")]
9-
FailedToAddGlyph(GlyphId),
13+
FailedToAddGlyph(u16),
14+
/// Failed to get scaled glyph image for cache key
15+
#[error("failed to get scaled glyph image for cache key: {0:?}")]
16+
FailedToGetGlyphImage(CacheKey),
1017
}

crates/bevy_text/src/font.rs

Lines changed: 26 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,35 @@
1-
use ab_glyph::{FontArc, FontVec, InvalidFont, OutlinedGlyph};
1+
use std::sync::Arc;
2+
23
use bevy_asset::Asset;
34
use bevy_reflect::TypePath;
4-
use bevy_render::{
5-
render_asset::RenderAssetUsages,
6-
render_resource::{Extent3d, TextureDimension, TextureFormat},
7-
texture::Image,
8-
};
95

10-
#[derive(Asset, TypePath, Debug, Clone)]
6+
/// An [`Asset`] that contains the data for a loaded font, if loaded as an asset.
7+
///
8+
/// Loaded by [`FontLoader`](crate::FontLoader).
9+
///
10+
/// # A note on fonts
11+
///
12+
/// `Font` may differ from the everyday notion of what a "font" is.
13+
/// A font *face* (e.g. Fira Sans Semibold Italic) is part of a font *family* (e.g. Fira Sans),
14+
/// and is distinguished from other font faces in the same family
15+
/// by its style (e.g. italic), its weight (e.g. bold) and its stretch (e.g. condensed).
16+
///
17+
/// Bevy currently loads a single font face as a single `Font` asset.
18+
#[derive(Debug, TypePath, Clone, Asset)]
1119
pub struct Font {
12-
pub font: FontArc,
20+
/// Content of a font file as bytes
21+
pub data: Arc<Vec<u8>>,
1322
}
1423

1524
impl Font {
16-
pub fn try_from_bytes(font_data: Vec<u8>) -> Result<Self, InvalidFont> {
17-
let font = FontVec::try_from_vec(font_data)?;
18-
let font = FontArc::new(font);
19-
Ok(Font { font })
20-
}
21-
22-
pub fn get_outlined_glyph_texture(outlined_glyph: OutlinedGlyph) -> Image {
23-
let bounds = outlined_glyph.px_bounds();
24-
// Increase the length of the glyph texture by 2-pixels on each axis to make space
25-
// for a pixel wide transparent border along its edges.
26-
let width = bounds.width() as usize + 2;
27-
let height = bounds.height() as usize + 2;
28-
let mut alpha = vec![0.0; width * height];
29-
outlined_glyph.draw(|x, y, v| {
30-
// Displace the glyph by 1 pixel on each axis so that it is drawn in the center of the texture.
31-
// This leaves a pixel wide transparent border around the glyph.
32-
alpha[(y + 1) as usize * width + x as usize + 1] = v;
33-
});
34-
35-
// TODO: make this texture grayscale
36-
Image::new(
37-
Extent3d {
38-
width: width as u32,
39-
height: height as u32,
40-
depth_or_array_layers: 1,
41-
},
42-
TextureDimension::D2,
43-
alpha
44-
.iter()
45-
.flat_map(|a| vec![255, 255, 255, (*a * 255.0) as u8])
46-
.collect::<Vec<u8>>(),
47-
TextureFormat::Rgba8UnormSrgb,
48-
// This glyph image never needs to reach the render world because it's placed
49-
// into a font texture atlas that'll be used for rendering.
50-
RenderAssetUsages::MAIN_WORLD,
51-
)
25+
/// Creates a [`Font`] from bytes
26+
pub fn try_from_bytes(
27+
font_data: Vec<u8>,
28+
) -> Result<Self, cosmic_text::ttf_parser::FaceParsingError> {
29+
use cosmic_text::ttf_parser;
30+
ttf_parser::Face::parse(&font_data, 0)?;
31+
Ok(Self {
32+
data: Arc::new(font_data),
33+
})
5234
}
5335
}

0 commit comments

Comments
 (0)