Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor: use glyph_brush_layout and add text alignment support #765

Merged
merged 29 commits into from
Nov 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
4271842
Start refactoring to use glyph_brush_layout for text layout
AlisCode Oct 16, 2020
5cf9362
Implementation of new text pipeline
AlisCode Oct 21, 2020
96674f0
Update to match new asset server
AlisCode Oct 21, 2020
5df8cbd
Get text layout to display characters
AlisCode Oct 24, 2020
01b865c
Fix glyph positioning, use text stlye, and only recompute layout on t…
tigregalis Oct 27, 2020
3ce61ff
Clean-up and get Text Alignment to work
AlisCode Oct 30, 2020
0c28c4b
Fix examples so that they use text_alignment
AlisCode Nov 1, 2020
542870e
Fix text positioning in bevy_ui
AlisCode Nov 1, 2020
6193a08
Rebasing on next to match bevy 0.3
AlisCode Nov 6, 2020
4211099
Fix clippy
AlisCode Nov 6, 2020
801507b
Fix additional left-padding
AlisCode Nov 7, 2020
2a8d82f
Move UI example alongside the others the keep them sorted
AlisCode Nov 7, 2020
829664f
Favor Default trait over new()
AlisCode Nov 7, 2020
192c377
Using parking_lot instead of std Mutexes
AlisCode Nov 7, 2020
03f82c8
Removing unnecessary pub fields and eliminate redundant code
AlisCode Nov 7, 2020
6d8c781
Renamed TextVertex to PositionedGlyph
AlisCode Nov 7, 2020
c36a9e3
Remove dead code
AlisCode Nov 7, 2020
ec390ae
Rename "vertices" to "glyphs"
AlisCode Nov 7, 2020
cc2c9b1
Fix measurement calculation
AlisCode Nov 7, 2020
5d35848
Fix font atlas debug example
AlisCode Nov 7, 2020
b41fa67
Fix contributors example missing alignment in TextStyle
AlisCode Nov 7, 2020
eee2a95
* Store glyphs in the TextPipeline instead of inside the TextComponen…
AlisCode Nov 11, 2020
a4e5d93
Adds the queue back to bevy_ui's text_system
AlisCode Nov 11, 2020
90ad82b
Fix the text_debug example after rebase
AlisCode Nov 11, 2020
5db7c1f
Silenced clippy's too_many_arguments
AlisCode Nov 11, 2020
65ffc08
Use AHashExt instead of HashMapExt
AlisCode Nov 11, 2020
bfb37d8
Update to use new QueryFilters
AlisCode Nov 11, 2020
1bda173
Remove dependency to parking_lot, update dependency on ab_glyph
AlisCode Nov 11, 2020
286f703
minor tweaks
cart Nov 11, 2020
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
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,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 @@ -336,3 +340,4 @@ path = "examples/android/android.rs"
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