use std::borrow::Cow;
use bevy::prelude::*;
use nannou_core::geom;
use parley::Alignment;
use parley::style::{FontFamily, FontFamilyName, StyleProperty, WordBreak};
pub use self::layout::Layout;
pub mod font;
pub mod glyph;
pub mod layout;
pub type Scalar = nannou_core::geom::scalar::Default;
pub type Point = nannou_core::geom::Point2;
pub type FontSize = u32;
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)]
pub enum Align {
Start,
Middle,
End,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Justify {
Left,
Center,
Right,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum Wrap {
Character,
Whitespace,
}
pub struct Builder<'a> {
text: Cow<'a, str>,
layout_builder: layout::Builder,
text_cx: font::SharedTextCx,
}
pub struct Text {
string: String,
parley_layout: parley::Layout<Color>,
layout: Layout,
rect: geom::Rect,
scale: f32,
}
impl<'a> Builder<'a> {
pub fn new(s: &'a str, text_cx: font::SharedTextCx) -> Self {
Builder {
text: Cow::Borrowed(s),
layout_builder: Default::default(),
text_cx,
}
}
fn map_layout<F>(mut self, map: F) -> Self
where
F: FnOnce(layout::Builder) -> layout::Builder,
{
self.layout_builder = map(self.layout_builder);
self
}
pub fn font_size(self, size: FontSize) -> Self {
self.map_layout(|l| l.font_size(size))
}
pub fn line_wrap(self, line_wrap: Option<Wrap>) -> Self {
self.map_layout(|l| l.line_wrap(line_wrap))
}
pub fn no_line_wrap(self) -> Self {
self.map_layout(|l| l.no_line_wrap())
}
pub fn wrap_by_word(self) -> Self {
self.map_layout(|l| l.wrap_by_word())
}
pub fn wrap_by_character(self) -> Self {
self.map_layout(|l| l.wrap_by_character())
}
pub fn font_family(self, family: impl Into<String>) -> Self {
self.map_layout(|l| l.font_family(family))
}
pub fn justify(self, justify: Justify) -> Self {
self.map_layout(|l| l.justify(justify))
}
pub fn left_justify(self) -> Self {
self.map_layout(|l| l.left_justify())
}
pub fn center_justify(self) -> Self {
self.map_layout(|l| l.center_justify())
}
pub fn right_justify(self) -> Self {
self.map_layout(|l| l.right_justify())
}
pub fn line_spacing(self, spacing: Scalar) -> Self {
self.map_layout(|l| l.line_spacing(spacing))
}
pub fn y_align(self, align: Align) -> Self {
self.map_layout(|l| l.y_align(align))
}
pub fn align_top(self) -> Self {
self.map_layout(|l| l.align_top())
}
pub fn align_middle_y(self) -> Self {
self.map_layout(|l| l.align_middle_y())
}
pub fn align_bottom(self) -> Self {
self.map_layout(|l| l.align_bottom())
}
pub fn layout(self, layout: &Layout) -> Self {
self.map_layout(|l| l.layout(layout))
}
pub fn build(self, rect: geom::Rect) -> Text {
let layout = self.layout_builder.build();
let mut inner = self.text_cx.0.lock().unwrap();
Text::layout_with_inner(&mut inner, &self.text, &layout, rect, 1.0)
}
}
impl Text {
pub(crate) fn layout_with_inner(
inner: &mut font::NannouTextCxInner,
text: &str,
layout: &Layout,
rect: geom::Rect,
scale: f32,
) -> Self {
let font_size = layout.font_size as f32;
let mut builder = inner
.layout
.ranged_builder(&mut inner.font, text, scale, true);
builder.push_default(StyleProperty::FontSize(font_size));
if let Some(ref family) = layout.font_family {
let name = FontFamilyName::parse(family).unwrap_or(FontFamilyName::named(family));
builder.push_default(StyleProperty::FontFamily(FontFamily::Single(name)));
}
if let Some(spacing) = (layout.line_spacing != 0.0).then_some(layout.line_spacing) {
builder.push_default(StyleProperty::LineHeight(
parley::style::LineHeight::Absolute(font_size + spacing),
));
}
if let Some(Wrap::Character) = layout.line_wrap {
builder.push_default(StyleProperty::WordBreak(WordBreak::BreakAll));
}
let mut parley_layout = builder.build(text);
match layout.line_wrap {
None => parley_layout.break_all_lines(None),
Some(Wrap::Whitespace) | Some(Wrap::Character) => {
parley_layout.break_all_lines(Some(rect.w() * scale));
}
}
let alignment = match layout.justify {
Justify::Left => Alignment::Start,
Justify::Center => Alignment::Center,
Justify::Right => Alignment::End,
};
parley_layout.align(alignment, parley::AlignmentOptions::default());
Text {
string: text.to_string(),
parley_layout,
layout: layout.clone(),
rect,
scale,
}
}
pub fn layout(&self) -> &Layout {
&self.layout
}
pub fn layout_rect(&self) -> geom::Rect {
self.rect
}
pub fn width(&self) -> Scalar {
self.parley_layout.width() / self.scale
}
pub fn height(&self) -> Scalar {
self.parley_layout.height() / self.scale
}
pub fn num_lines(&self) -> usize {
self.parley_layout.len()
}
pub fn bounding_rect(&self) -> geom::Rect {
let w = self.width();
let h = self.height();
if w == 0.0 && h == 0.0 {
return geom::Rect::from_w_h(0.0, 0.0);
}
let offset = self.position_offset();
let x = geom::Range::new(offset.x, offset.x + w);
let y = geom::Range::new(offset.y - h, offset.y);
geom::Rect { x, y }
}
pub fn line_rects(&self) -> Vec<geom::Rect> {
let offset = self.position_offset();
let scale = self.scale;
self.parley_layout
.lines()
.map(|line| {
let metrics = line.metrics();
let top = offset.y + (metrics.ascent - metrics.baseline) / scale;
let bottom = offset.y - (metrics.baseline + metrics.descent) / scale;
let line_x = offset.x + metrics.offset / scale;
let line_w = (metrics.advance - metrics.trailing_whitespace) / scale;
let x = geom::Range::new(line_x, line_x + line_w);
let y = geom::Range::new(bottom, top);
geom::Rect { x, y }
})
.collect()
}
pub fn lines(&self) -> Vec<&str> {
self.parley_layout
.lines()
.map(|line| {
let range = line.text_range();
&self.string[range]
})
.collect()
}
pub fn glyphs(&self) -> Vec<geom::Rect> {
let offset = self.position_offset();
let scale = self.scale;
let mut rects = Vec::new();
for line in self.parley_layout.lines() {
for item in line.items() {
let parley::PositionedLayoutItem::GlyphRun(glyph_run) = item else {
continue;
};
let run_metrics = glyph_run.run().metrics();
for glyph in glyph_run.positioned_glyphs() {
let gx = offset.x + glyph.x / scale;
let gy = offset.y - glyph.y / scale;
let x = geom::Range::new(gx, gx + glyph.advance / scale);
let y = geom::Range::new(
gy - run_metrics.descent / scale,
gy + run_metrics.ascent / scale,
);
rects.push(geom::Rect { x, y });
}
}
}
rects
}
pub fn path_events(&self) -> Vec<lyon::path::PathEvent> {
glyph::text_path_events(&self.parley_layout, self.position_offset(), self.scale)
}
pub(crate) fn parley_layout(&self) -> &parley::Layout<Color> {
&self.parley_layout
}
pub(crate) fn position_offset_value(&self) -> Vec2 {
self.position_offset()
}
fn position_offset(&self) -> Vec2 {
let text_h = self.height();
let rect_h = self.rect.h();
let rect_w = self.rect.w();
let y_offset = match self.layout.y_align {
Align::End => rect_h / 2.0,
Align::Middle => text_h / 2.0,
Align::Start => -rect_h / 2.0 + text_h,
};
let x_offset = match self.layout.line_wrap {
Some(_) => -rect_w / 2.0,
None => {
let align_w = rect_w - self.width();
let factor = match self.layout.justify {
Justify::Left => 0.0,
Justify::Center => 0.5,
Justify::Right => 1.0,
};
-rect_w / 2.0 + align_w * factor
}
};
Vec2::new(x_offset, y_offset)
}
}
pub fn height_by_lines(num_lines: usize, font_size: FontSize, line_spacing: Scalar) -> Scalar {
if num_lines > 0 {
num_lines as Scalar * font_size as Scalar + (num_lines - 1) as Scalar * line_spacing
} else {
0.0
}
}
pub fn position_offset(
num_lines: usize,
font_size: FontSize,
line_spacing: f32,
bounding_rect: geom::Rect,
y_align: Align,
) -> Vec2 {
let x_offset = bounding_rect.x.start;
let y_offset = {
let total_text_height = height_by_lines(num_lines, font_size, line_spacing);
let total_text_y_range = geom::Range::new(0.0, total_text_height);
let total_text_y = match y_align {
Align::Start => total_text_y_range.align_start_of(bounding_rect.y),
Align::Middle => total_text_y_range.align_middle_of(bounding_rect.y),
Align::End => total_text_y_range.align_end_of(bounding_rect.y),
};
total_text_y.end
};
geom::vec2(x_offset, y_offset)
}