Skip to content

Commit

Permalink
Use glyph_brush_layout and add text alignment support (#765)
Browse files Browse the repository at this point in the history
Use glyph_brush_layout and add text alignment support

Co-authored-by: Olivier Pinon <[email protected]>
Co-authored-by: tigregalis <[email protected]>
Co-authored-by: Carter Anderson <[email protected]>
  • Loading branch information
4 people authored Nov 13, 2020
1 parent 1eff534 commit 465c3d4
Show file tree
Hide file tree
Showing 19 changed files with 641 additions and 338 deletions.
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,10 @@ path = "examples/ui/button.rs"
name = "text"
path = "examples/ui/text.rs"

[[example]]
name = "text_debug"
path = "examples/ui/text_debug.rs"

[[example]]
name = "font_atlas_debug"
path = "examples/ui/font_atlas_debug.rs"
Expand Down Expand Up @@ -335,3 +339,4 @@ icon = "@mipmap/ic_launcher"
build_targets = ["aarch64-linux-android", "armv7-linux-androideabi"]
min_sdk_version = 16
target_sdk_version = 29

5 changes: 4 additions & 1 deletion crates/bevy_text/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@ keywords = ["bevy"]
bevy_app = { path = "../bevy_app", version = "0.3.0" }
bevy_asset = { path = "../bevy_asset", version = "0.3.0" }
bevy_core = { path = "../bevy_core", version = "0.3.0" }
bevy_ecs = { path = "../bevy_ecs", version = "0.3.0" }
bevy_math = { path = "../bevy_math", version = "0.3.0" }
bevy_render = { path = "../bevy_render", version = "0.3.0" }
bevy_sprite = { path = "../bevy_sprite", version = "0.3.0" }
bevy_type_registry = { path = "../bevy_type_registry", version = "0.3.0" }
bevy_utils = { path = "../bevy_utils", version = "0.3.0" }

# other
ab_glyph = "0.2.5"
anyhow = "1.0"
ab_glyph = "0.2.6"
glyph_brush_layout = "0.2.1"
thiserror = "1.0"
134 changes: 52 additions & 82 deletions crates/bevy_text/src/draw.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
use crate::{Font, FontAtlasSet};
use ab_glyph::{Glyph, PxScale, ScaleFont};
use bevy_asset::Assets;
use bevy_math::{Mat4, Vec2, Vec3};
use bevy_math::{Mat4, Vec3};
use bevy_render::{
color::Color,
draw::{Draw, DrawContext, DrawError, Drawable},
Expand All @@ -13,33 +10,49 @@ use bevy_render::{
RenderResourceId,
},
};
use bevy_sprite::{TextureAtlas, TextureAtlasSprite};
use bevy_sprite::TextureAtlasSprite;
use glyph_brush_layout::{HorizontalAlign, VerticalAlign};

use crate::PositionedGlyph;

#[derive(Debug, Clone, Copy)]
pub struct TextAlignment {
pub vertical: VerticalAlign,
pub horizontal: HorizontalAlign,
}

impl Default for TextAlignment {
fn default() -> Self {
TextAlignment {
vertical: VerticalAlign::Top,
horizontal: HorizontalAlign::Left,
}
}
}

#[derive(Clone, Debug)]
pub struct TextStyle {
pub font_size: f32,
pub color: Color,
pub alignment: TextAlignment,
}

impl Default for TextStyle {
fn default() -> Self {
Self {
color: Color::WHITE,
font_size: 12.0,
alignment: TextAlignment::default(),
}
}
}

pub struct DrawableText<'a> {
pub font: &'a Font,
pub font_atlas_set: &'a FontAtlasSet,
pub texture_atlases: &'a Assets<TextureAtlas>,
pub render_resource_bindings: &'a mut RenderResourceBindings,
pub asset_render_resource_bindings: &'a mut AssetRenderResourceBindings,
pub position: Vec3,
pub container_size: Vec2,
pub style: &'a TextStyle,
pub text: &'a str,
pub text_glyphs: &'a Vec<PositionedGlyph>,
pub msaa: &'a Msaa,
pub font_quad_vertex_descriptor: &'a VertexBufferDescriptor,
}
Expand Down Expand Up @@ -81,80 +94,37 @@ impl<'a> Drawable for DrawableText<'a> {
// set global bindings
context.set_bind_groups_from_bindings(draw, &mut [self.render_resource_bindings])?;

// NOTE: this uses ab_glyph apis directly. it _might_ be a good idea to add our own layer on top
let font = &self.font.font;
let scale = PxScale::from(self.style.font_size);
let scaled_font = ab_glyph::Font::as_scaled(&font, scale);
let mut caret = self.position;
let mut last_glyph: Option<Glyph> = None;

// set local per-character bindings
for character in self.text.chars() {
if character.is_control() {
if character == '\n' {
caret.set_x(self.position.x());
// TODO: Necessary to also calculate scaled_font.line_gap() in here?
caret.set_y(caret.y() - scaled_font.height());
}
continue;
}
for tv in self.text_glyphs {
let atlas_render_resource_bindings = self
.asset_render_resource_bindings
.get_mut(&tv.atlas_info.texture_atlas)
.unwrap();
context.set_bind_groups_from_bindings(draw, &mut [atlas_render_resource_bindings])?;

let glyph = scaled_font.scaled_glyph(character);
if let Some(last_glyph) = last_glyph.take() {
caret.set_x(caret.x() + scaled_font.kern(last_glyph.id, glyph.id));
}
if let Some(glyph_atlas_info) = self
.font_atlas_set
.get_glyph_atlas_info(self.style.font_size, character)
{
if let Some(outlined) = scaled_font.outline_glyph(glyph.clone()) {
let texture_atlas = self
.texture_atlases
.get(&glyph_atlas_info.texture_atlas)
.unwrap();
let glyph_rect = texture_atlas.textures[glyph_atlas_info.char_index as usize];
let glyph_width = glyph_rect.width();
let glyph_height = glyph_rect.height();
let atlas_render_resource_bindings = self
.asset_render_resource_bindings
.get_mut(&glyph_atlas_info.texture_atlas)
.unwrap();
context.set_bind_groups_from_bindings(
draw,
&mut [atlas_render_resource_bindings],
)?;

let bounds = outlined.px_bounds();
let x = bounds.min.x + glyph_width / 2.0;
// the 0.5 accounts for odd-numbered heights (bump up by 1 pixel)
let y = -bounds.max.y + glyph_height / 2.0 - scaled_font.descent() + 0.5;
let transform = Mat4::from_translation(caret + Vec3::new(x, y, 0.0));
let sprite = TextureAtlasSprite {
index: glyph_atlas_info.char_index,
color: self.style.color,
};

let transform_buffer = context
.shared_buffers
.get_buffer(&transform, BufferUsage::UNIFORM)
.unwrap();
let sprite_buffer = context
.shared_buffers
.get_buffer(&sprite, BufferUsage::UNIFORM)
.unwrap();
let sprite_bind_group = BindGroup::build()
.add_binding(0, transform_buffer)
.add_binding(1, sprite_buffer)
.finish();

context.create_bind_group_resource(2, &sprite_bind_group)?;
draw.set_bind_group(2, &sprite_bind_group);
draw.draw_indexed(indices.clone(), 0, 0..1);
}
}
caret.set_x(caret.x() + scaled_font.h_advance(glyph.id));
last_glyph = Some(glyph);
let sprite = TextureAtlasSprite {
index: tv.atlas_info.glyph_index,
color: self.style.color,
};

let transform = Mat4::from_translation(self.position + tv.position.extend(0.));

let transform_buffer = context
.shared_buffers
.get_buffer(&transform, BufferUsage::UNIFORM)
.unwrap();
let sprite_buffer = context
.shared_buffers
.get_buffer(&sprite, BufferUsage::UNIFORM)
.unwrap();
let sprite_bind_group = BindGroup::build()
.add_binding(0, transform_buffer)
.add_binding(1, sprite_buffer)
.finish();
context.create_bind_group_resource(2, &sprite_bind_group)?;
draw.set_bind_group(2, &sprite_bind_group);
draw.draw_indexed(indices.clone(), 0, 0..1);
}

Ok(())
}
}
10 changes: 10 additions & 0 deletions crates/bevy_text/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use ab_glyph::GlyphId;
use thiserror::Error;

#[derive(Debug, PartialEq, Eq, Error)]
pub enum TextError {
#[error("Font not found")]
NoSuchFont,
#[error("Failed to add glyph to newly-created atlas {0:?}")]
FailedToAddGlyph(GlyphId),
}
113 changes: 3 additions & 110 deletions crates/bevy_text/src/font.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use ab_glyph::{FontVec, Glyph, InvalidFont, OutlinedGlyph, Point, PxScale, ScaleFont};
use ab_glyph::{FontArc, FontVec, InvalidFont, OutlinedGlyph};
use bevy_math::Vec2;
use bevy_render::{
color::Color,
Expand All @@ -9,15 +9,13 @@ use bevy_type_registry::TypeUuid;
#[derive(Debug, TypeUuid)]
#[uuid = "97059ac6-c9ba-4da9-95b6-bed82c3ce198"]
pub struct Font {
pub font: FontVec,
pub font: FontArc,
}

unsafe impl Send for Font {}
unsafe impl Sync for Font {}

impl Font {
pub fn try_from_bytes(font_data: Vec<u8>) -> Result<Self, InvalidFont> {
let font = FontVec::try_from_vec(font_data)?;
let font = FontArc::new(font);
Ok(Font { font })
}

Expand Down Expand Up @@ -54,109 +52,4 @@ impl Font {
TextureFormat::Rgba8UnormSrgb,
)
}

// adapted from ab_glyph example: https://github.com/alexheretic/ab-glyph/blob/master/dev/examples/image.rs
pub fn render_text(
&self,
text: &str,
color: Color,
font_size: f32,
width: usize,
height: usize,
) -> Texture {
let scale = PxScale::from(font_size);

let scaled_font = ab_glyph::Font::as_scaled(&self.font, scale);

let mut glyphs = Vec::new();
layout_paragraph(
scaled_font,
ab_glyph::point(0.0, 0.0),
width as f32,
text,
&mut glyphs,
);

let color_u8 = [
(color.r() * 255.0) as u8,
(color.g() * 255.0) as u8,
(color.b() * 255.0) as u8,
];

// TODO: this offset is a bit hackey
let mut alpha = vec![0.0; width * height];
for glyph in glyphs {
if let Some(outlined) = scaled_font.outline_glyph(glyph) {
let bounds = outlined.px_bounds();
// Draw the glyph into the image per-pixel by using the draw closure
outlined.draw(|x, y, v| {
// Offset the position by the glyph bounding box
// Turn the coverage into an alpha value (blended with any previous)
let offset_x = x as usize + bounds.min.x as usize;
let offset_y = y as usize + bounds.min.y as usize;
if offset_x >= width || offset_y >= height {
return;
}
alpha[offset_y * width + offset_x] = v;
});
}
}

Texture::new(
Vec2::new(width as f32, height as f32),
alpha
.iter()
.map(|a| {
vec![
color_u8[0],
color_u8[1],
color_u8[2],
(color.a() * a * 255.0) as u8,
]
})
.flatten()
.collect::<Vec<u8>>(),
TextureFormat::Rgba8UnormSrgb,
)
}
}

fn layout_paragraph<F, SF>(
font: SF,
position: Point,
max_width: f32,
text: &str,
target: &mut Vec<Glyph>,
) where
F: ab_glyph::Font,
SF: ScaleFont<F>,
{
let v_advance = font.height() + font.line_gap();
let mut caret = position + ab_glyph::point(0.0, font.ascent());
let mut last_glyph: Option<Glyph> = None;
for c in text.chars() {
if c.is_control() {
if c == '\n' {
caret = ab_glyph::point(position.x, caret.y + v_advance);
last_glyph = None;
}
continue;
}
let mut glyph = font.scaled_glyph(c);
if let Some(previous) = last_glyph.take() {
caret.x += font.kern(previous.id, glyph.id);
}
glyph.position = caret;

last_glyph = Some(glyph.clone());
caret.x += font.h_advance(glyph.id);

if !c.is_whitespace() && caret.x > position.x + max_width {
caret = ab_glyph::point(position.x, caret.y + v_advance);
glyph.position = caret;
last_glyph = None;
}

target.push(glyph);
}
}
Loading

0 comments on commit 465c3d4

Please sign in to comment.