Skip to content

Commit cfb897f

Browse files
bevy_text Emoji support (#23922)
# Objective Add basic emoji support. ## Solution * In `get_outlined_glyph_texture` just use the given rgba image data if the image from swash is not an alpha mask. * Add `is_alpha_mask` bool fields to `GlyphAtlasInfo` and `GlyphAtlasLocation`. * In `extract_text_sections` only apply a color tint if the glyph image is an alpha mask. ## Testing Should be compatible with all the existing features, I tested `TextShadow`s and they worked correctly. The changes here feel a bit half baked, but it's super simple and I've got leave for somewhere so hammered this out in a rush. <img width="1827" height="887" alt="emoji" src="https://github.com/user-attachments/assets/633a3711-8761-438d-9e57-928c725f24f6" /> --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
1 parent 738b147 commit cfb897f

3 files changed

Lines changed: 56 additions & 24 deletions

File tree

crates/bevy_text/src/font_atlas.rs

Lines changed: 47 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ impl FontAtlas {
9191
key: GlyphCacheKey,
9292
texture: &Image,
9393
offset: Vec2,
94+
is_alpha_mask: bool,
9495
) -> Result<(), TextError> {
9596
let mut atlas_texture = textures
9697
.get_mut(&self.texture)
@@ -106,6 +107,7 @@ impl FontAtlas {
106107
GlyphAtlasLocation {
107108
glyph_index,
108109
offset,
110+
is_alpha_mask,
109111
},
110112
);
111113
Ok(())
@@ -134,9 +136,16 @@ pub fn add_glyph_to_atlas(
134136
font_smoothing: FontSmoothing,
135137
glyph_id: u16,
136138
) -> Result<GlyphAtlasInfo, TextError> {
137-
let (glyph_texture, offset) = get_outlined_glyph_texture(scaler, glyph_id, font_smoothing)?;
139+
let (glyph_texture, offset, is_alpha_mask) =
140+
get_outlined_glyph_texture(scaler, glyph_id, font_smoothing)?;
138141
let mut add_char_to_font_atlas = |atlas: &mut FontAtlas| -> Result<(), TextError> {
139-
atlas.add_glyph(textures, GlyphCacheKey { glyph_id }, &glyph_texture, offset)
142+
atlas.add_glyph(
143+
textures,
144+
GlyphCacheKey { glyph_id },
145+
&glyph_texture,
146+
offset,
147+
is_alpha_mask,
148+
)
140149
};
141150
if !font_atlases
142151
.iter_mut()
@@ -153,7 +162,13 @@ pub fn add_glyph_to_atlas(
153162

154163
let mut new_atlas = FontAtlas::new(textures, UVec2::splat(containing), font_smoothing);
155164

156-
new_atlas.add_glyph(textures, GlyphCacheKey { glyph_id }, &glyph_texture, offset)?;
165+
new_atlas.add_glyph(
166+
textures,
167+
GlyphCacheKey { glyph_id },
168+
&glyph_texture,
169+
offset,
170+
is_alpha_mask,
171+
)?;
157172

158173
font_atlases.push(new_atlas);
159174
}
@@ -171,7 +186,7 @@ pub fn get_outlined_glyph_texture(
171186
scaler: &mut Scaler,
172187
glyph_id: u16,
173188
font_smoothing: FontSmoothing,
174-
) -> Result<(Image, Vec2), TextError> {
189+
) -> Result<(Image, Vec2, bool), TextError> {
175190
let image = swash::scale::Render::new(&[
176191
swash::scale::Source::ColorOutline(0),
177192
swash::scale::Source::ColorBitmap(swash::scale::StrikeWith::BestFit),
@@ -187,27 +202,35 @@ pub fn get_outlined_glyph_texture(
187202
let height = image.placement.height;
188203

189204
let px = (width * height) as usize;
190-
let mut rgba = vec![0u8; px * 4];
191-
match font_smoothing {
192-
FontSmoothing::AntiAliased => {
193-
for i in 0..px {
194-
let a = image.data[i];
195-
rgba[i * 4 + 0] = 255; // R
196-
rgba[i * 4 + 1] = 255; // G
197-
rgba[i * 4 + 2] = 255; // B
198-
rgba[i * 4 + 3] = a; // A from swash
205+
let rgba = match image.content {
206+
swash::scale::image::Content::Mask => {
207+
let mut rgba = vec![0u8; px * 4];
208+
match font_smoothing {
209+
FontSmoothing::AntiAliased => {
210+
for i in 0..px {
211+
let a = image.data[i];
212+
rgba[i * 4 + 0] = 255; // R
213+
rgba[i * 4 + 1] = 255; // G
214+
rgba[i * 4 + 2] = 255; // B
215+
rgba[i * 4 + 3] = a; // A from swash
216+
}
217+
}
218+
FontSmoothing::None => {
219+
for i in 0..px {
220+
let a = image.data[i];
221+
rgba[i * 4 + 0] = 255; // R
222+
rgba[i * 4 + 1] = 255; // G
223+
rgba[i * 4 + 2] = 255; // B
224+
rgba[i * 4 + 3] = if 127 < a { 255 } else { 0 }; // A from swash
225+
}
226+
}
199227
}
228+
rgba
200229
}
201-
FontSmoothing::None => {
202-
for i in 0..px {
203-
let a = image.data[i];
204-
rgba[i * 4 + 0] = 255; // R
205-
rgba[i * 4 + 1] = 255; // G
206-
rgba[i * 4 + 2] = 255; // B
207-
rgba[i * 4 + 3] = if 127 < a { 255 } else { 0 }; // A from swash
208-
}
230+
swash::scale::image::Content::Color | swash::scale::image::Content::SubpixelMask => {
231+
image.data
209232
}
210-
}
233+
};
211234

212235
Ok((
213236
Image::new(
@@ -222,6 +245,7 @@ pub fn get_outlined_glyph_texture(
222245
RenderAssetUsages::MAIN_WORLD,
223246
),
224247
Vec2::new(left as f32, -top as f32),
248+
image.content == swash::scale::image::Content::Mask,
225249
))
226250
}
227251

@@ -237,6 +261,7 @@ pub fn get_glyph_atlas_info(
237261
offset: location.offset,
238262
rect: atlas.texture_atlas.textures[location.glyph_index].as_rect(),
239263
texture: atlas.texture.id(),
264+
is_alpha_mask: location.is_alpha_mask,
240265
})
241266
})
242267
}

crates/bevy_text/src/glyph.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ pub struct GlyphAtlasInfo {
4040
pub rect: Rect,
4141
/// The required offset (relative positioning) when placed
4242
pub offset: Vec2,
43+
/// True if this glyph is stored as a tintable alpha mask
44+
pub is_alpha_mask: bool,
4345
}
4446

4547
/// The location of a glyph in an atlas,
@@ -53,4 +55,6 @@ pub struct GlyphAtlasLocation {
5355
pub glyph_index: usize,
5456
/// The required offset (relative positioning) when placed
5557
pub offset: Vec2,
58+
/// True if this glyph is stored as a tintable alpha mask
59+
pub is_alpha_mask: bool,
5660
}

crates/bevy_ui_render/src/lib.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1022,15 +1022,18 @@ pub fn extract_text_sections(
10221022
current_section_index = *section_index;
10231023
}
10241024

1025-
let color = if let Some(selected_text_color) = selected_text_color
1025+
let color = if !atlas_info.is_alpha_mask {
1026+
LinearRgba::WHITE
1027+
} else if let Some(selected_text_color) = selected_text_color
10261028
&& text_layout_info
10271029
.selection_rects
10281030
.iter()
10291031
.any(|selection_rect| {
10301032
let glyph_rect = Rect::from_center_size(*position, atlas_info.rect.size());
10311033
selection_rect.contains(glyph_rect.min)
10321034
&& selection_rect.contains(glyph_rect.max)
1033-
}) {
1035+
})
1036+
{
10341037
selected_text_color
10351038
} else {
10361039
color

0 commit comments

Comments
 (0)