Skip to content
Snippets Groups Projects
Commit 95f57142 authored by Gino Valente's avatar Gino Valente
Browse files

Updated and combined text layout and measurement

parent 38969f62
No related branches found
No related tags found
No related merge requests found
......@@ -1846,6 +1846,7 @@ dependencies = [
"serde_json",
"serde_path_to_error",
"unicode-segmentation",
"xi-unicode",
]
[[package]]
......
......@@ -4,7 +4,7 @@ use bevy::{
sprite::Rect,
};
use kayak_core::render_primitive::RenderPrimitive;
use kayak_font::{Alignment, CoordinateSystem, KayakFont};
use kayak_font::{Alignment, KayakFont, TextProperties};
use crate::to_bevy_color;
use bevy_kayak_renderer::{
......@@ -52,19 +52,27 @@ pub fn extract_texts(
let font = font.unwrap();
let chars_layouts = font.get_layout(
CoordinateSystem::PositiveYDown,
Alignment::Start,
(layout.posx, layout.posy + font_size),
(parent_size.0, parent_size.1),
content,
*line_height,
let properties = TextProperties {
alignment: Alignment::Start,
font_size,
line_height: *line_height,
max_size: (parent_size.0, parent_size.1),
..Default::default()
};
let text_layout = font.measure(
content,
properties,
);
for char_layout in chars_layouts {
let position = char_layout.position.into();
let size: Vec2 = char_layout.size.into();
let base_position = Vec2::new(layout.posx, layout.posy + font_size);
for glyph_rect in text_layout.glyphs() {
let mut position = Vec2::from(glyph_rect.position);
position += base_position;
let size = Vec2::from(glyph_rect.size);
extracted_texts.push(ExtractQuadBundle {
extracted_quad: ExtractedQuad {
font_handle: Some(font_handle.clone()),
......@@ -74,7 +82,7 @@ pub fn extract_texts(
},
color: to_bevy_color(background_color),
vertex_index: 0,
char_id: font.get_char_id(char_layout.content).unwrap(),
char_id: font.get_char_id(glyph_rect.content).unwrap(),
z_index: layout.z_index,
quad_type: UIQuadType::Text,
type_index: 0,
......
......@@ -17,3 +17,6 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_path_to_error = "0.1"
unicode-segmentation = "1.9"
# Provides UAX #14 line break segmentation
xi-unicode = "0.3"
......@@ -3,7 +3,7 @@ use bevy::{
prelude::{Assets, Commands, Handle, Query, Res},
sprite::Rect,
};
use kayak_font::{CoordinateSystem, KayakFont};
use kayak_font::{Alignment, KayakFont, TextProperties};
use super::{
pipeline::{ExtractCharBundle, ExtractedChar},
......@@ -19,19 +19,26 @@ pub fn extract(
for (text, font_handle) in texts.iter() {
if let Some(font) = fonts.get(font_handle) {
let layouts = font.get_layout(
CoordinateSystem::PositiveYUp,
text.horz_alignment,
(text.position.x, text.position.y),
(text.size.x, text.size.y),
let properties = TextProperties {
font_size: text.font_size,
line_height: text.line_height,
max_size: (text.size.x, text.size.y),
alignment: text.horz_alignment,
..Default::default()
};
let text_layout = font.measure(
&text.content,
text.line_height,
text.font_size,
properties
);
for layout in layouts {
let position = layout.position.into();
let size: Vec2 = layout.size.into();
for glyph_rect in text_layout.glyphs() {
let mut position = Vec2::from(glyph_rect.position);
position.y *= -1.0;
position += text.position;
let size = Vec2::from(glyph_rect.size);
extracted_texts.push(ExtractCharBundle {
extracted_quad: ExtractedChar {
......@@ -42,7 +49,7 @@ pub fn extract(
},
color: text.color,
vertex_index: 0,
char_id: font.get_char_id(layout.content).unwrap(),
char_id: font.get_char_id(glyph_rect.content).unwrap(),
z_index: 0.0,
},
});
......
use bevy::{math::Vec2, prelude::Component, render::color::Color};
use kayak_font::layout::Alignment;
use kayak_font::Alignment;
#[derive(Component)]
pub struct Text {
......
......@@ -24,7 +24,8 @@ pub struct Atlas {
pub sdf_type: SDFType,
#[serde(alias = "distanceRange")]
pub distance_range: f32,
pub size: f32,
#[serde(alias = "size")]
pub font_size: f32,
pub width: u32,
pub height: u32,
#[serde(alias = "yOrigin")]
......
This diff is collapsed.
use std::cmp::Ordering;
#[derive(Default, Debug, Clone, Copy, PartialEq)]
pub struct GlyphRect {
pub position: (f32, f32),
pub size: (f32, f32),
pub content: char,
}
/// The text alignment.
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Alignment {
......@@ -16,25 +23,43 @@ pub struct TextProperties {
/// The line height (in pixels).
pub line_height: f32,
/// The maximum width and height a block of text can take up (in pixels).
pub max_size: Option<(f32, f32)>,
pub max_size: (f32, f32),
/// The text alignment.
pub alignment: Alignment,
/// The size of a tab (`'\t'`) character in equivalent spaces.
pub tab_size: u8,
}
impl Default for TextProperties {
fn default() -> Self {
Self {
font_size: 14.0,
line_height: 14.0 * 1.2,
max_size: (f32::MAX, f32::MAX),
tab_size: 4,
alignment: Alignment::Start,
}
}
}
/// Contains details for a calculated line of text.
#[derive(Copy, Clone, Debug, Default, PartialEq)]
pub struct Line {
/// The index of the starting grapheme cluster within the text content.
pub index: usize,
pub grapheme_index: usize,
/// The index of the starting char within the text content.
pub char_index: usize,
/// The total number of grapheme clusters in this line.
pub len: usize,
pub grapheme_len: usize,
/// The total number of chars in this line.
pub char_len: usize,
/// The total width of this line (in pixels).
pub width: f32,
}
impl PartialOrd for Line {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.index.partial_cmp(&other.index)
self.grapheme_index.partial_cmp(&other.grapheme_index)
}
}
......@@ -43,6 +68,7 @@ impl PartialOrd for Line {
/// This can be retrieved using [`measure`](crate::KayakFont::measure).
#[derive(Clone, Debug, PartialEq)]
pub struct TextLayout {
glyphs: Vec<GlyphRect>,
lines: Vec<Line>,
size: (f32, f32),
properties: TextProperties,
......@@ -50,12 +76,8 @@ pub struct TextLayout {
impl TextLayout {
/// Create a new [`TextLayout`].
pub fn new(lines: Vec<Line>, size: (f32, f32), properties: TextProperties) -> Self {
Self {
lines,
size,
properties,
}
pub fn new(glyphs: Vec<GlyphRect>, lines: Vec<Line>, size: (f32, f32), properties: TextProperties) -> Self {
Self { glyphs, lines, size, properties }
}
/// Returns the calculated lines for the text content.
......@@ -63,6 +85,11 @@ impl TextLayout {
&self.lines
}
/// Returns the calculated glyph rects for the text content.
pub fn glyphs(&self) -> &Vec<GlyphRect> {
&self.glyphs
}
/// Returns the total width and height of the text content (in pixels).
pub fn size(&self) -> (f32, f32) {
self.size
......
......@@ -171,10 +171,8 @@ pub mod bevy {
load_context.get_handle(atlas_image_path.clone()),
);
font.generate_char_ids();
load_context
.set_default_asset(LoadedAsset::new(font).with_dependency(atlas_image_path));
let asset = LoadedAsset::new(font).with_dependency(atlas_image_path);
load_context.set_default_asset(asset);
Ok(())
})
......
......@@ -20,7 +20,7 @@ pub trait FontRenderingPipeline {
fn get_font_image_layout(&self) -> &BindGroupLayout;
}
pub const MAX_CHARACTERS: u32 = 100;
pub const MAX_CHARACTERS: u32 = 500;
pub struct FontTextureCache {
images: HashMap<Handle<KayakFont>, GpuImage>,
......
use std::str::{CharIndices, Split};
use unicode_segmentation::UnicodeSegmentation;
use xi_unicode::LineBreakIterator;
pub const NEWLINE: char = '\n';
pub const SPACE: char = ' ';
pub const NBSP: char = '\u{a0}';
pub const TAB: char = '\t';
/// Returns true if the given character is a newline.
pub fn is_newline(c: char) -> bool {
c == '\n'
c == NEWLINE
}
/// Returns true if the given character is a space.
///
/// Includes the non-breaking space ([`NBSP`]).
pub fn is_space(c: char) -> bool {
c == SPACE || c == NBSP
}
/// Returns true if the given character is a tab.
pub fn is_tab(c: char) -> bool {
c == TAB
}
/// Split a string into a collection of "words" that may be followed by a line break,
/// according to [UAX #14](https://www.unicode.org/reports/tr14/).
///
/// For example, `"Hello, world!"` would be broken into `["Hello, ", "world!"]`. And
/// `"A-rather-long-word"` would be broken into `["A-", "rather-", "long-", "word"]`.
pub fn split_breakable_words(text: &str) -> BreakableWordIter {
BreakableWordIter::new(text)
}
/// A "word" (or, rather substring) that may be followed by a line break,
/// according to [UAX #14](https://www.unicode.org/reports/tr14/).
#[derive(Copy, Clone, Debug)]
pub struct BreakableWord<'a> {
/// The index of the last character in this word.
pub char_index: usize,
/// The content of this word.
pub content: &'a str,
/// If true, this word __must__ be followed by a line break.
pub hard_break: bool,
}
/// An iterator over [`BreakableWord`].
#[derive(Copy, Clone)]
pub struct BreakableWordIter<'a> {
text: &'a str,
iter: LineBreakIterator<'a>,
index: usize,
}
impl<'a> BreakableWordIter<'a> {
pub fn new(text: &'a str) -> Self {
Self { text, iter: LineBreakIterator::new(text), index: 0 }
}
}
impl<'a> Iterator for BreakableWordIter<'a> {
type Item = BreakableWord<'a>;
fn next(&mut self) -> Option<Self::Item> {
let (next_idx, is_hard) = self.iter.next()?;
let word = self.text.get(self.index..next_idx)?;
self.index = next_idx;
Some(BreakableWord {
char_index: next_idx,
content: word,
hard_break: is_hard,
})
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment