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

Rich text #1245

Merged
merged 18 commits into from
Jan 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
40 changes: 3 additions & 37 deletions crates/bevy_text/src/draw.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use bevy_math::{Mat4, Vec3};
use bevy_render::{
color::Color,
draw::{Draw, DrawContext, DrawError, Drawable},
mesh,
mesh::Mesh,
Expand All @@ -9,47 +8,14 @@ use bevy_render::{
renderer::{BindGroup, RenderResourceBindings, RenderResourceId},
};
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(),
}
}
}
use crate::{PositionedGlyph, TextSection};

pub struct DrawableText<'a> {
pub render_resource_bindings: &'a mut RenderResourceBindings,
pub position: Vec3,
pub scale_factor: f32,
pub style: &'a TextStyle,
pub sections: &'a [TextSection],
pub text_glyphs: &'a Vec<PositionedGlyph>,
pub msaa: &'a Msaa,
pub font_quad_vertex_descriptor: &'a VertexBufferDescriptor,
Expand Down Expand Up @@ -103,7 +69,7 @@ impl<'a> Drawable for DrawableText<'a> {

let sprite = TextureAtlasSprite {
index: tv.atlas_info.glyph_index,
color: self.style.color,
color: self.sections[tv.section_index].style.color,
};

// To get the rendering right for non-one scaling factors, we need
Expand Down
38 changes: 26 additions & 12 deletions crates/bevy_text/src/glyph_brush.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use bevy_math::{Size, Vec2};
use bevy_render::prelude::Texture;
use bevy_sprite::TextureAtlas;
use glyph_brush_layout::{
FontId, GlyphPositioner, Layout, SectionGeometry, SectionGlyph, ToSectionText,
FontId, GlyphPositioner, Layout, SectionGeometry, SectionGlyph, SectionText, ToSectionText,
};

use crate::{error::TextError, Font, FontAtlasSet, GlyphAtlasInfo, TextAlignment};
Expand Down Expand Up @@ -46,6 +46,7 @@ impl GlyphBrush {
pub fn process_glyphs(
&self,
glyphs: Vec<SectionGlyph>,
sections: &[SectionText],
font_atlas_set_storage: &mut Assets<FontAtlasSet>,
fonts: &Assets<Font>,
texture_atlases: &mut Assets<TextureAtlas>,
Expand All @@ -55,16 +56,26 @@ impl GlyphBrush {
return Ok(Vec::new());
}

let first_glyph = glyphs.first().expect("Must have at least one glyph.");
let font_id = first_glyph.font_id.0;
let handle = &self.handles[font_id];
let font = fonts.get(handle).ok_or(TextError::NoSuchFont)?;
let font_size = first_glyph.glyph.scale.y;
let scaled_font = ab_glyph::Font::as_scaled(&font.font, font_size);
let sections_data = sections
.iter()
.map(|section| {
let handle = &self.handles[section.font_id.0];
let font = fonts.get(handle).ok_or(TextError::NoSuchFont)?;
let font_size = section.scale.y;
Ok((
handle,
font,
font_size,
ab_glyph::Font::as_scaled(&font.font, font_size),
))
})
.collect::<Result<Vec<_>, _>>()?;

let mut max_y = std::f32::MIN;
let mut min_x = std::f32::MAX;
for section_glyph in glyphs.iter() {
let glyph = &section_glyph.glyph;
for sg in glyphs.iter() {
let glyph = &sg.glyph;
let scaled_font = sections_data[sg.section_index].3;
max_y = max_y.max(glyph.position.y - scaled_font.descent());
min_x = min_x.min(glyph.position.x);
}
Expand All @@ -82,14 +93,15 @@ impl GlyphBrush {
let glyph_id = glyph.id;
let glyph_position = glyph.position;
let adjust = GlyphPlacementAdjuster::new(&mut glyph);
if let Some(outlined_glyph) = font.font.outline_glyph(glyph) {
let section_data = sections_data[sg.section_index];
if let Some(outlined_glyph) = section_data.1.font.outline_glyph(glyph) {
let bounds = outlined_glyph.px_bounds();
let handle_font_atlas: Handle<FontAtlasSet> = handle.as_weak();
let handle_font_atlas: Handle<FontAtlasSet> = section_data.0.as_weak();
let font_atlas_set = font_atlas_set_storage
.get_or_insert_with(handle_font_atlas, FontAtlasSet::default);

let atlas_info = font_atlas_set
.get_glyph_atlas_info(font_size, glyph_id, glyph_position)
.get_glyph_atlas_info(section_data.2, glyph_id, glyph_position)
.map(Ok)
.unwrap_or_else(|| {
font_atlas_set.add_glyph_to_atlas(texture_atlases, textures, outlined_glyph)
Expand All @@ -107,6 +119,7 @@ impl GlyphBrush {
positioned_glyphs.push(PositionedGlyph {
position,
atlas_info,
section_index: sg.section_index,
});
}
}
Expand All @@ -126,6 +139,7 @@ impl GlyphBrush {
pub struct PositionedGlyph {
pub position: Vec2,
pub atlas_info: GlyphAtlasInfo,
pub section_index: usize,
}

#[cfg(feature = "subpixel_glyph_atlas")]
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_text/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub use text::*;
pub use text2d::*;

pub mod prelude {
pub use crate::{Font, Text, Text2dBundle, TextAlignment, TextError, TextStyle};
pub use crate::{Font, Text, Text2dBundle, TextAlignment, TextError, TextSection, TextStyle};
pub use glyph_brush_layout::{HorizontalAlign, VerticalAlign};
}

Expand Down
49 changes: 31 additions & 18 deletions crates/bevy_text/src/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ use bevy_utils::HashMap;
use glyph_brush_layout::{FontId, SectionText};

use crate::{
error::TextError, glyph_brush::GlyphBrush, Font, FontAtlasSet, PositionedGlyph, TextAlignment,
error::TextError, glyph_brush::GlyphBrush, scale_value, Font, FontAtlasSet, PositionedGlyph,
TextAlignment, TextSection,
};

pub struct TextPipeline<ID> {
Expand All @@ -35,7 +36,7 @@ pub struct TextLayoutInfo {
}

impl<ID: Hash + Eq> TextPipeline<ID> {
pub fn get_or_insert_font_id(&mut self, handle: Handle<Font>, font: &Font) -> FontId {
pub fn get_or_insert_font_id(&mut self, handle: &Handle<Font>, font: &Font) -> FontId {
let brush = &mut self.brush;
*self
.map_font_id
Expand All @@ -51,30 +52,40 @@ impl<ID: Hash + Eq> TextPipeline<ID> {
pub fn queue_text(
&mut self,
id: ID,
font_handle: Handle<Font>,
fonts: &Assets<Font>,
text: &str,
font_size: f32,
sections: &[TextSection],
scale_factor: f64,
text_alignment: TextAlignment,
bounds: Size,
font_atlas_set_storage: &mut Assets<FontAtlasSet>,
texture_atlases: &mut Assets<TextureAtlas>,
textures: &mut Assets<Texture>,
) -> Result<(), TextError> {
let font = fonts.get(font_handle.id).ok_or(TextError::NoSuchFont)?;
let font_id = self.get_or_insert_font_id(font_handle, font);

let section = SectionText {
font_id,
scale: PxScale::from(font_size),
text,
};

let scaled_font = ab_glyph::Font::as_scaled(&font.font, font_size);
let mut scaled_fonts = Vec::new();
let sections = sections
.iter()
.map(|section| {
let font = fonts
.get(section.style.font.id)
.ok_or(TextError::NoSuchFont)?;
let font_id = self.get_or_insert_font_id(&section.style.font, font);
let font_size = scale_value(section.style.font_size, scale_factor);

scaled_fonts.push(ab_glyph::Font::as_scaled(&font.font, font_size));

let section = SectionText {
font_id,
scale: PxScale::from(font_size),
text: &section.value,
};

Ok(section)
})
.collect::<Result<Vec<_>, _>>()?;

let section_glyphs = self
.brush
.compute_glyphs(&[section], bounds, text_alignment)?;
.compute_glyphs(&sections, bounds, text_alignment)?;

if section_glyphs.is_empty() {
self.glyph_map.insert(
Expand All @@ -92,8 +103,9 @@ impl<ID: Hash + Eq> TextPipeline<ID> {
let mut max_x: f32 = std::f32::MIN;
let mut max_y: f32 = std::f32::MIN;

for section_glyph in section_glyphs.iter() {
let glyph = &section_glyph.glyph;
for sg in section_glyphs.iter() {
let scaled_font = scaled_fonts[sg.section_index];
let glyph = &sg.glyph;
min_x = min_x.min(glyph.position.x);
min_y = min_y.min(glyph.position.y - scaled_font.ascent());
max_x = max_x.max(glyph.position.x + scaled_font.h_advance(glyph.id));
Expand All @@ -104,6 +116,7 @@ impl<ID: Hash + Eq> TextPipeline<ID> {

let glyphs = self.brush.process_glyphs(
section_glyphs,
&sections,
font_atlas_set_storage,
fonts,
texture_atlases,
Expand Down
95 changes: 93 additions & 2 deletions crates/bevy_text/src/text.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,106 @@
use bevy_asset::Handle;
use bevy_math::Size;
use bevy_render::color::Color;
use glyph_brush_layout::{HorizontalAlign, VerticalAlign};

use crate::{Font, TextStyle};
use crate::Font;

#[derive(Debug, Default, Clone)]
pub struct Text {
pub sections: Vec<TextSection>,
pub alignment: TextAlignment,
}

impl Text {
/// Constructs a [`Text`] with (initially) one section.
///
/// ```
/// # use bevy_asset::{AssetServer, Handle};
/// # use bevy_render::color::Color;
/// # use bevy_text::{Font, Text, TextAlignment, TextStyle};
/// # use glyph_brush_layout::{HorizontalAlign, VerticalAlign};
/// #
/// # let font_handle: Handle<Font> = Default::default();
/// #
/// // basic usage
/// let hello_world = Text::with_section(
/// "hello world!".to_string(),
/// TextStyle {
/// font: font_handle.clone(),
/// font_size: 60.0,
/// color: Color::WHITE,
/// },
/// TextAlignment {
/// vertical: VerticalAlign::Center,
/// horizontal: HorizontalAlign::Center,
/// },
/// );
///
/// let hello_bevy = Text::with_section(
/// // accepts a String or any type that converts into a String, such as &str
/// "hello bevy!",
/// TextStyle {
/// font: font_handle,
/// font_size: 60.0,
/// color: Color::WHITE,
/// },
/// // you can still use Default
/// Default::default(),
/// );
/// ```
pub fn with_section<S: Into<String>>(
value: S,
style: TextStyle,
alignment: TextAlignment,
) -> Self {
Self {
sections: vec![TextSection {
value: value.into(),
style,
}],
alignment,
}
}
}

#[derive(Debug, Default, Clone)]
pub struct TextSection {
pub value: String,
pub font: Handle<Font>,
pub style: TextStyle,
}

#[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: Handle<Font>,
pub font_size: f32,
pub color: Color,
}

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

#[derive(Default, Copy, Clone, Debug)]
pub struct CalculatedSize {
pub size: Size,
Expand Down
Loading