pub mod cursor;
pub mod font;
pub mod glyph;
pub mod layout;
pub mod line;
pub mod rt {
pub use rusttype::{gpu_cache, point, vector, Point, Rect, Vector};
}
pub use self::layout::Layout;
pub use rusttype::gpu_cache::Cache as GlyphCache;
pub use rusttype::{Glyph, GlyphId, GlyphIter, LayoutIter, Scale, ScaledGlyph};
use crate::geom;
use std::borrow::Cow;
pub type FontCollection = rusttype::FontCollection<'static>;
pub type Font = rusttype::Font<'static>;
pub type PositionedGlyph = rusttype::PositionedGlyph<'static>;
pub type Scalar = crate::geom::scalar::Default;
pub type Point<S = Scalar> = crate::geom::Point2<S>;
pub type FontSize = u32;
pub struct Builder<'a> {
text: Cow<'a, str>,
layout_builder: layout::Builder,
}
#[derive(Clone)]
pub struct Text<'a> {
text: Cow<'a, str>,
font: Font,
layout: Layout,
line_infos: Vec<line::Info>,
rect: geom::Rect,
}
#[derive(Clone)]
pub struct Lines<'a, I> {
text: &'a str,
ranges: I,
}
pub type TextLineInfos<'a> = line::Infos<'a, line::NextBreakFnPtr>;
pub type TextLines<'a> = Lines<
'a,
std::iter::Map<std::slice::Iter<'a, line::Info>, fn(&line::Info) -> std::ops::Range<usize>>,
>;
type LineRects<'a> = line::Rects<std::iter::Cloned<std::slice::Iter<'a, line::Info>>>;
#[derive(Clone)]
pub struct TextLineRects<'a> {
line_rects: LineRects<'a>,
offset: geom::Vector2,
}
pub type TextLinesWithRects<'a> = std::iter::Zip<TextLines<'a>, TextLineRects<'a>>;
pub type TextGlyphsPerLine<'a> = glyph::RectsPerLine<'a, TextLinesWithRects<'a>>;
pub type TextGlyphs<'a> = std::iter::FlatMap<
TextGlyphsPerLine<'a>,
glyph::Rects<'a, 'a>,
fn(glyph::Rects<'a, 'a>) -> glyph::Rects<'a, 'a>,
>;
#[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,
}
impl<'a> From<Cow<'a, str>> for Builder<'a> {
fn from(text: Cow<'a, str>) -> Self {
let layout_builder = Default::default();
Builder {
text,
layout_builder,
}
}
}
impl<'a> From<&'a str> for Builder<'a> {
fn from(s: &'a str) -> Self {
let text = Cow::Borrowed(s);
Self::from(text)
}
}
impl From<String> for Builder<'static> {
fn from(s: String) -> Self {
let text = Cow::Owned(s);
Self::from(text)
}
}
impl<'a> Builder<'a> {
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(self, font: Font) -> Self {
self.map_layout(|l| l.font(font))
}
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<'a> {
let text = self.text;
let layout = self.layout_builder.build();
#[allow(unreachable_code)]
let font = layout.font.clone().unwrap_or_else(|| {
#[cfg(feature = "notosans")]
{
return font::default_notosans();
}
let assets = crate::app::find_assets_path()
.expect("failed to detect the assets directory when searching for a default font");
font::default(&assets).expect("failed to detect a default font")
});
let max_width = rect.w();
let line_infos =
line::infos_maybe_wrapped(&text, &font, layout.font_size, layout.line_wrap, max_width)
.collect();
Text {
text,
font,
layout,
line_infos,
rect,
}
}
}
impl<'a> Text<'a> {
pub fn line_infos(&self) -> &[line::Info] {
&self.line_infos
}
pub fn text(&self) -> &str {
&self.text
}
pub fn layout(&self) -> &Layout {
&self.layout
}
pub fn font(&self) -> &Font {
&self.font
}
pub fn num_lines(&self) -> usize {
self.line_infos.len()
}
pub fn layout_rect(&self) -> geom::Rect {
self.rect
}
pub fn bounding_rect(&self) -> geom::Rect {
let mut r = self.bounding_rect_by_lines();
let info = match self.line_infos.first() {
None => return geom::Rect::from_w_h(0.0, 0.0),
Some(info) => info,
};
let line_h = self.layout.font_size as Scalar;
r.y.end -= line_h - info.height;
r
}
pub fn bounding_rect_by_lines(&self) -> geom::Rect {
let mut lrs = self.line_rects();
let lr = match lrs.next() {
None => return geom::Rect::from_w_h(0.0, 0.0),
Some(lr) => lr,
};
lrs.fold(lr, |acc, lr| {
let x = geom::Range::new(acc.x.start.min(lr.x.start), acc.x.end.max(lr.x.end));
let y = geom::Range::new(acc.y.start.min(lr.y.start), acc.y.end.max(lr.y.end));
geom::Rect { x, y }
})
}
pub fn width(&self) -> Scalar {
self.line_infos
.iter()
.fold(0.0, |max, info| max.max(info.width))
}
pub fn height(&self) -> Scalar {
let info = match self.line_infos.first() {
None => return 0.0,
Some(info) => info,
};
exact_height(
info.height,
self.num_lines(),
self.layout.font_size,
self.layout.line_spacing,
)
}
pub fn height_by_lines(&self) -> Scalar {
height_by_lines(
self.num_lines(),
self.layout.font_size,
self.layout.line_spacing,
)
}
pub fn lines(&self) -> TextLines {
fn info_byte_range(info: &line::Info) -> std::ops::Range<usize> {
info.byte_range()
}
lines(&self.text, self.line_infos.iter().map(info_byte_range))
}
pub fn line_rects(&self) -> TextLineRects {
let offset = self.position_offset();
let line_rects = line::rects(
self.line_infos.iter().cloned(),
self.layout.font_size,
self.rect.w(),
self.layout.justify,
self.layout.line_spacing,
);
TextLineRects { line_rects, offset }
}
pub fn lines_with_rects(&self) -> TextLinesWithRects {
self.lines().zip(self.line_rects())
}
pub fn glyphs_per_line(&self) -> TextGlyphsPerLine {
glyph::rects_per_line(self.lines_with_rects(), &self.font, self.layout.font_size)
}
pub fn glyphs(&self) -> TextGlyphs {
self.glyphs_per_line().flat_map(std::convert::identity)
}
pub fn path_events<'b>(&'b self) -> impl 'b + Iterator<Item = lyon::path::PathEvent> {
use lyon::path::PathEvent;
fn trans_lyon_point(p: &lyon::math::Point, v: geom::Vector2) -> lyon::math::Point {
lyon::math::point(p.x + v.x, p.y + v.y)
}
fn trans_path_event(e: &PathEvent, v: geom::Vector2) -> PathEvent {
match *e {
PathEvent::Begin { ref at } => PathEvent::Begin {
at: trans_lyon_point(at, v),
},
PathEvent::Line { ref from, ref to } => PathEvent::Line {
from: trans_lyon_point(from, v),
to: trans_lyon_point(to, v),
},
PathEvent::Quadratic {
ref from,
ref ctrl,
ref to,
} => PathEvent::Quadratic {
from: trans_lyon_point(from, v),
ctrl: trans_lyon_point(ctrl, v),
to: trans_lyon_point(to, v),
},
PathEvent::Cubic {
ref from,
ref ctrl1,
ref ctrl2,
ref to,
} => PathEvent::Cubic {
from: trans_lyon_point(from, v),
ctrl1: trans_lyon_point(ctrl1, v),
ctrl2: trans_lyon_point(ctrl2, v),
to: trans_lyon_point(to, v),
},
PathEvent::End {
ref last,
ref first,
ref close,
} => PathEvent::End {
last: trans_lyon_point(last, v),
first: trans_lyon_point(first, v),
close: *close,
},
}
}
self.glyphs().flat_map(|(g, r)| {
glyph::path_events(g)
.into_iter()
.flat_map(|es| es)
.map(move |e| trans_path_event(&e, r.bottom_left()))
})
}
pub fn rt_glyphs<'b: 'a>(
&'b self,
window_size: geom::Vector2,
scale_factor: Scalar,
) -> impl 'a + 'b + Iterator<Item = PositionedGlyph> {
rt_positioned_glyphs(
self.lines_with_rects(),
&self.font,
self.layout.font_size,
window_size,
scale_factor,
)
}
pub fn into_owned(self) -> Text<'static> {
let Text {
text,
font,
layout,
line_infos,
rect,
} = self;
let text = Cow::Owned(text.into_owned());
Text {
text,
font,
layout,
line_infos,
rect,
}
}
fn position_offset(&self) -> geom::Vector2 {
position_offset(
self.num_lines(),
self.layout.font_size,
self.layout.line_spacing,
self.rect,
self.layout.y_align,
)
}
}
impl<'a, I> Iterator for Lines<'a, I>
where
I: Iterator<Item = std::ops::Range<usize>>,
{
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
let Lines {
text,
ref mut ranges,
} = *self;
ranges.next().map(|range| &text[range])
}
}
impl<'a> Iterator for TextLineRects<'a> {
type Item = geom::Rect;
fn next(&mut self) -> Option<Self::Item> {
self.line_rects.next().map(|r| r.shift(self.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 exact_height(
first_line_height: Scalar,
num_lines: usize,
font_size: FontSize,
line_spacing: Scalar,
) -> Scalar {
if num_lines > 0 {
let lt_num_lines = num_lines - 1;
let other_lines_height = lt_num_lines as Scalar * font_size as Scalar;
let space_height = lt_num_lines as Scalar * line_spacing;
first_line_height + other_lines_height + space_height
} else {
0.0
}
}
pub fn lines<I>(text: &str, ranges: I) -> Lines<I>
where
I: Iterator<Item = std::ops::Range<usize>>,
{
Lines {
text: text,
ranges: ranges,
}
}
pub fn position_offset(
num_lines: usize,
font_size: FontSize,
line_spacing: f32,
bounding_rect: geom::Rect,
y_align: Align,
) -> geom::Vector2 {
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)
}
pub fn rt_positioned_glyphs<'a, I>(
lines_with_rects: I,
font: &'a Font,
font_size: FontSize,
window_size: geom::Vector2,
scale_factor: Scalar,
) -> impl 'a + Iterator<Item = PositionedGlyph>
where
I: IntoIterator<Item = (&'a str, geom::Rect)>,
I::IntoIter: 'a,
{
let trans_x = move |x: Scalar| (x + window_size.x / 2.0) * scale_factor as Scalar;
let trans_y = move |y: Scalar| ((-y) + window_size.y / 2.0) * scale_factor as Scalar;
let scale = f32_pt_to_scale(font_size as f32 * scale_factor);
lines_with_rects
.into_iter()
.flat_map(move |(line, line_rect)| {
let (x, y) = (
trans_x(line_rect.left()) as f32,
trans_y(line_rect.bottom()) as f32,
);
let point = rt::Point { x: x, y: y };
font.layout(line, scale, point).map(|g| g.standalone())
})
}
pub fn f32_pt_to_px(font_size_in_points: f32) -> f32 {
font_size_in_points * 4.0 / 3.0
}
pub fn f32_pt_to_scale(font_size_in_points: f32) -> Scale {
Scale::uniform(f32_pt_to_px(font_size_in_points))
}
pub fn pt_to_px(font_size_in_points: FontSize) -> f32 {
f32_pt_to_px(font_size_in_points as f32)
}
pub fn pt_to_scale(font_size_in_points: FontSize) -> Scale {
Scale::uniform(pt_to_px(font_size_in_points))
}
pub fn text(s: &str) -> Builder {
Builder::from(s)
}