Skip to content
Open
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
a2fc8e4
Added text module to gizmos crate
ickshonpe Jan 26, 2026
a3e25b4
Added example and basic implementation with just two glyphs.
ickshonpe Jan 26, 2026
7c051ce
Added simplex font coordinate data
ickshonpe Jan 28, 2026
863ac8c
Added simplex font rendering
ickshonpe Jan 28, 2026
e77a956
Fixed orientation
ickshonpe Jan 28, 2026
edf970d
Cleanup
ickshonpe Jan 28, 2026
c8697c7
update examples, draw text for 2d_gizmos with text_2d gizmo function
ickshonpe Jan 28, 2026
9700bbb
Add all glyphs display to text_gizmos example, increased LINE_HEIGHT …
ickshonpe Jan 28, 2026
668ed7f
larger glyphs in example
ickshonpe Jan 28, 2026
169c5e2
Added `text_3d` method impl.
ickshonpe Jan 29, 2026
23efa88
Renamed `text_3d` to `text`
ickshonpe Jan 29, 2026
5beef21
cargo run -p build-templated-pages -- update examples
ickshonpe Jan 29, 2026
59fa5ad
Fixed errors in comments
ickshonpe Jan 29, 2026
7cc29e8
use core instead of std
ickshonpe Jan 29, 2026
678d96d
Merge branch 'main' into text-gizmos
ickshonpe Jan 29, 2026
01d2e18
Added `text_gizmos_font` example
ickshonpe Jan 29, 2026
23c0b0b
Merge branch 'text-gizmos' of https://github.com/ickshonpe/bevy into …
ickshonpe Jan 29, 2026
1668ad7
Added `text_font` module, and moved font data into it.
ickshonpe Jan 29, 2026
b0c5a21
reorder text and text2d functions
ickshonpe Jan 29, 2026
df6bd54
cargo run -p build-templated-pages -- update examples
ickshonpe Jan 29, 2026
c467b57
Fixed misnaming in text doc example
ickshonpe Jan 29, 2026
0130b43
removed redundant static lifetime
ickshonpe Jan 29, 2026
0cde043
removed redundant static lifetime
ickshonpe Jan 29, 2026
1e2c588
Document supported character range
ickshonpe Jan 29, 2026
97d5d42
Documented supported character range.
ickshonpe Jan 29, 2026
f732b7b
Implemented anchoring. Added `anchor` params to `text` and `text_2d` …
ickshonpe Jan 29, 2026
4dba3f7
removed uneeded line
ickshonpe Jan 29, 2026
335fa3a
Merge branch 'main' into text-gizmos
ickshonpe Jan 30, 2026
070e1cb
Removed unused import
ickshonpe Jan 30, 2026
1961e98
Added anchor argument to doc tests
ickshonpe Jan 30, 2026
075517c
reordered lib imports
ickshonpe Jan 30, 2026
e42db9d
Merge branch 'main' into text-gizmos
ickshonpe Feb 2, 2026
a976648
reverted 2d_gizmos changes
ickshonpe Feb 2, 2026
7a1ecac
Merge branch 'text-gizmos' of https://github.com/ickshonpe/bevy into …
ickshonpe Feb 2, 2026
4b6281f
Merge branch 'main' into text-gizmos
ickshonpe Feb 3, 2026
4994789
The stroke font metrics have been moved into the new `StrokeTextMetri…
ickshonpe Feb 3, 2026
6471390
Removed empty impl block
ickshonpe Feb 3, 2026
7e0304e
Added `StrokeFont` type
ickshonpe Feb 3, 2026
f8761cf
use metrics when creating `StrokeFontIterator`
ickshonpe Feb 3, 2026
ef34864
Add a third iterator for the stroke line strips instead of allocating…
ickshonpe Feb 3, 2026
9c21222
Removed unneeded `into_iter`s
ickshonpe Feb 3, 2026
abd3660
Added `ScaledStrokeFont` wrapper around `StrokeFont`
ickshonpe Feb 3, 2026
31f461e
Clean up, added `StrokeTextLayout` API.
ickshonpe Feb 3, 2026
cb2b104
Added metrics to `StrokeTextLayout`, removed `StrokeFontMetrics`.
ickshonpe Feb 3, 2026
e167889
Added doc comments
ickshonpe Feb 3, 2026
28acd59
More doc comments
ickshonpe Feb 3, 2026
1bd3d28
More doc comments
ickshonpe Feb 3, 2026
7d691ae
Simplify by removing multiple iterator types
ickshonpe Feb 3, 2026
62d79eb
Clean up
ickshonpe Feb 3, 2026
8a1f2fc
Use an inner while loop to skip multiple invalid strokes
ickshonpe Feb 3, 2026
61c0438
use empty range to signal no strokes
ickshonpe Feb 3, 2026
a40bf3d
clean up
ickshonpe Feb 3, 2026
9be9ae3
Removed advance function
ickshonpe Feb 3, 2026
79cedd6
Improved doc comment for render function
ickshonpe Feb 3, 2026
69a1ce5
use for loop instead of while
ickshonpe Feb 3, 2026
24e792f
Removed needless borrows
ickshonpe Feb 3, 2026
c2e04fe
renamed modules to disambiguate them from bevy_text text
ickshonpe Feb 3, 2026
d61edb4
Merge branch 'main' into text-gizmos
ickshonpe Feb 3, 2026
1f671b1
renamed text_gizmos example to 2d_text_gizmos
ickshonpe Feb 3, 2026
cae73cf
Fixed anchor application.
ickshonpe Feb 3, 2026
926c0fd
Merge branch 'text-gizmos' of https://github.com/ickshonpe/bevy into …
ickshonpe Feb 3, 2026
6465be1
color the text in the 3d example
ickshonpe Feb 3, 2026
b110746
Thicker text in 3d example
ickshonpe Feb 3, 2026
397b8fd
rearranged 2d example
ickshonpe Feb 3, 2026
530e09b
Added `anchored_text_gizmos` example
ickshonpe Feb 4, 2026
50bb766
Merge branch 'main' into text-gizmos
ickshonpe Feb 4, 2026
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
24 changes: 24 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4534,6 +4534,30 @@ description = "A scene showcasing light gizmos"
category = "Gizmos"
wasm = true

[[example]]
name = "text_gizmos"
path = "examples/gizmos/text_gizmos.rs"
# Causes an ICE on docs.rs
doc-scrape-examples = false

[package.metadata.example.text_gizmos]
name = "Text Gizmos"
description = "A scene showcasing text gizmos"
category = "Gizmos"
wasm = true

[[example]]
name = "text_gizmos_font"
path = "examples/gizmos/text_gizmos_font.rs"
# Causes an ICE on docs.rs
doc-scrape-examples = false

[package.metadata.example.text_gizmos_font]
name = "Text Gizmos Font"
description = "Example displaying the font used by text gizmos"
category = "Gizmos"
wasm = true

[[example]]
name = "custom_gltf_vertex_attribute"
path = "examples/gltf/custom_gltf_vertex_attribute.rs"
Expand Down
2 changes: 2 additions & 0 deletions crates/bevy_gizmos/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ pub mod grid;
pub mod primitives;
pub mod retained;
pub mod rounded_box;
pub mod text;
mod text_font;

#[cfg(feature = "bevy_mesh")]
pub mod skinned_mesh_bounds;
Expand Down
223 changes: 223 additions & 0 deletions crates/bevy_gizmos/src/text.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
//! This module draws text gizmos using a stroke font.

use crate::text_font::*;
use crate::{gizmos::GizmoBuffer, prelude::GizmoConfigGroup};
use bevy_color::Color;
use bevy_math::{vec2, Isometry2d, Isometry3d, Vec2, Vec3};

impl<Config, Clear> GizmoBuffer<Config, Clear>
where
Config: GizmoConfigGroup,
Clear: 'static + Send + Sync,
{
/// Draw text using a stroke font with the given isometry applied.
///
/// Only ASCII characters in the range 32–126 are supported.
///
/// # Arguments
///
/// - `isometry`: defines the translation and rotation of the text.
/// - `text`: the text to be drawn.
/// - `size`: the size of the text in pixels.
/// - `anchor`: anchor point relative to the center of the text.
/// - `color`: the color of the text.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::Color;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.text(Isometry3d::IDENTITY, "text gizmo", 25., Vec2::ZERO, Color::WHITE);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
pub fn text(
&mut self,
isometry: impl Into<Isometry3d>,
text: &str,
size: f32,
anchor: Vec2,
color: impl Into<Color>,
) {
let color = color.into();
let scale = size / SIMPLEX_CAP_HEIGHT;
let band = (SIMPLEX_CAP_HEIGHT + SIMPLEX_DESCENDER_DEPTH) * scale;
let line_height = LINE_HEIGHT * band;
let margin_top = 0.5 * (line_height - band);
let space_advance = SIMPLEX_GLYPHS[0].0 as f32 * scale;

let mut layout_size = vec2(0., line_height);

let mut w = 0.;
for c in text.chars() {
if c == '\n' {
layout_size.x = layout_size.x.max(w);
w = 0.;
layout_size.y += line_height;
continue;
}

let code_point = c as usize;
if !(SIMPLEX_ASCII_START..=SIMPLEX_ASCII_END).contains(&code_point) {
w += space_advance;
continue;
}

let glyph = &SIMPLEX_GLYPHS[code_point - SIMPLEX_ASCII_START];
w += glyph.0 as f32 * scale;
}

layout_size.x = layout_size.x.max(w);
let mut isometry = isometry.into();

isometry.translation.x += layout_size.x * (-anchor.x - 0.5);
isometry.translation.y += layout_size.y * (-anchor.y + 0.5);

let mut rx = 0.0;
let mut ry = -margin_top;

for c in text.chars() {
if c == '\n' {
rx = 0.0;
ry -= line_height;
continue;
}

let code_point = c as usize;
if !(SIMPLEX_ASCII_START..=SIMPLEX_ASCII_END).contains(&code_point) {
rx += space_advance;
continue;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it make sense to display a replacement character for characters outside the range which are not whitespaces (if the font does have such a character) ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I thought about this, possibly? It wouldn't need an extra character, we could draw the question mark character from the font and then add a diamond border outline around it like the � character (except not filled in).

It might be best leaving it to a follow up PR, and keep this one simple.


let glyph = &SIMPLEX_GLYPHS[code_point - SIMPLEX_ASCII_START];
let advance = glyph.0 as f32 * scale;

for stroke_index in glyph.1.clone() {
let stroke = SIMPLEX_STROKES[stroke_index].clone();
if stroke.len() < 2 {
continue;
}

self.linestrip(
SIMPLEX_POSITIONS[stroke].iter().map(|&[x, y]| {
isometry
* Vec3::new(
rx + scale * x as f32,
ry - scale * (SIMPLEX_CAP_HEIGHT - y as f32),
0.0,
)
}),
color,
);
}

rx += advance;
}
}

/// Draw text using a stroke font in 2d with the given isometry applied.
///
/// Only ASCII characters in the range 32–126 are supported.
///
/// # Arguments
///
/// - `isometry`: defines the translation and rotation of the text.
/// - `text`: the text to be drawn.
/// - `size`: the size of the text.
/// - `anchor`: anchor point relative to the center of the text.
/// - `color`: the color of the text.
///
/// # Example
/// ```
/// # use bevy_gizmos::prelude::*;
/// # use bevy_math::prelude::*;
/// # use bevy_color::Color;
/// fn system(mut gizmos: Gizmos) {
/// gizmos.text_2d(Isometry2d::IDENTITY, "2D text gizmo", 25., Vec2::ZERO, Color::WHITE);
/// }
/// # bevy_ecs::system::assert_is_system(system);
/// ```
pub fn text_2d(
&mut self,
isometry: impl Into<Isometry2d>,
text: &str,
size: f32,
anchor: Vec2,
color: impl Into<Color>,
) {
let color = color.into();
let scale = size / SIMPLEX_CAP_HEIGHT;
let band = (SIMPLEX_CAP_HEIGHT + SIMPLEX_DESCENDER_DEPTH) * scale;
let line_height = LINE_HEIGHT * band;
let margin = line_height - band;
let space_advance = SIMPLEX_GLYPHS[0].0 as f32 * scale;

let mut layout_size = vec2(0., line_height);

let mut w = 0.;
for c in text.chars() {
if c == '\n' {
layout_size.x = layout_size.x.max(w);
w = 0.;
layout_size.y += line_height;
continue;
}

let code_point = c as usize;
if !(SIMPLEX_ASCII_START..=SIMPLEX_ASCII_END).contains(&code_point) {
w += space_advance;
continue;
}

let glyph = &SIMPLEX_GLYPHS[code_point - SIMPLEX_ASCII_START];
w += glyph.0 as f32 * scale;
}

layout_size.x = layout_size.x.max(w);
let mut isometry: Isometry2d = isometry.into();

isometry.translation.x += layout_size.x * (-anchor.x - 0.5);
isometry.translation.y += layout_size.y * (-anchor.y + 0.5);

let mut rx = 0.0;
let mut ry = -margin;

for c in text.chars() {
if c == '\n' {
rx = 0.0;
ry -= line_height;
continue;
}

let code_point = c as usize;
if !(SIMPLEX_ASCII_START..=SIMPLEX_ASCII_END).contains(&code_point) {
rx += space_advance;
continue;
}

let glyph = &SIMPLEX_GLYPHS[code_point - SIMPLEX_ASCII_START];
let advance = glyph.0 as f32 * scale;

for stroke_index in glyph.1.clone() {
let stroke = SIMPLEX_STROKES[stroke_index].clone();
if stroke.len() < 2 {
continue;
}

self.linestrip_2d(
SIMPLEX_POSITIONS[stroke].iter().map(|&[x, y]| {
isometry
* Vec2::new(
rx + scale * x as f32,
ry - scale * (SIMPLEX_CAP_HEIGHT - y as f32),
)
}),
color,
);
}

rx += advance;
}
}
}
Loading