use crate::color::conv::IntoLinSrgba;
use crate::draw::drawing::DrawingContext;
use crate::draw::primitive::Primitive;
use crate::draw::properties::spatial::{self, dimension, orientation, position};
use crate::draw::properties::{
ColorScalar, LinSrgba, SetColor, SetDimensions, SetOrientation, SetPosition,
};
use crate::draw::{self, theme, Drawing};
use crate::geom::{self, Point2};
use crate::text::{self, Align, Font, FontSize, Justify, Layout, Scalar, Wrap};
#[derive(Clone, Debug)]
pub struct Text {
spatial: spatial::Properties,
style: Style,
text: std::ops::Range<usize>,
}
#[derive(Clone, Debug, Default)]
pub struct Style {
pub color: Option<LinSrgba>,
pub glyph_colors: Vec<LinSrgba>, pub layout: text::layout::Builder,
}
pub type DrawingText<'a> = Drawing<'a, Text>;
impl Text {
pub fn new(ctxt: DrawingContext, text: &str) -> Self {
let start = ctxt.text_buffer.len();
ctxt.text_buffer.push_str(text);
let end = ctxt.text_buffer.len();
let text = start..end;
let spatial = Default::default();
let style = Default::default();
Text {
spatial,
style,
text,
}
}
fn map_layout<F>(mut self, map: F) -> Self
where
F: FnOnce(text::layout::Builder) -> text::layout::Builder,
{
self.style.layout = map(self.style.layout);
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 with_style(mut self, style: Style) -> Self {
self.style = style;
self
}
pub fn glyph_colors(mut self, colors: Vec<LinSrgba>) -> Self {
self.style.glyph_colors = colors;
self
}
}
impl<'a> DrawingText<'a> {
pub fn font_size(self, size: text::FontSize) -> Self {
self.map_ty(|ty| ty.font_size(size))
}
pub fn no_line_wrap(self) -> Self {
self.map_ty(|ty| ty.no_line_wrap())
}
pub fn wrap_by_word(self) -> Self {
self.map_ty(|ty| ty.wrap_by_word())
}
pub fn wrap_by_character(self) -> Self {
self.map_ty(|ty| ty.wrap_by_character())
}
pub fn font(self, font: text::Font) -> Self {
self.map_ty(|ty| ty.font(font))
}
pub fn with_style(self, style: Style) -> Self {
self.map_ty(|ty| ty.with_style(style))
}
pub fn justify(self, justify: text::Justify) -> Self {
self.map_ty(|ty| ty.justify(justify))
}
pub fn left_justify(self) -> Self {
self.map_ty(|ty| ty.left_justify())
}
pub fn center_justify(self) -> Self {
self.map_ty(|ty| ty.center_justify())
}
pub fn right_justify(self) -> Self {
self.map_ty(|ty| ty.right_justify())
}
pub fn line_spacing(self, spacing: text::Scalar) -> Self {
self.map_ty(|ty| ty.line_spacing(spacing))
}
pub fn y_align_text(self, align: Align) -> Self {
self.map_ty(|ty| ty.y_align(align))
}
pub fn align_text_top(self) -> Self {
self.map_ty(|ty| ty.align_top())
}
pub fn align_text_middle_y(self) -> Self {
self.map_ty(|ty| ty.align_middle_y())
}
pub fn align_text_bottom(self) -> Self {
self.map_ty(|ty| ty.align_bottom())
}
pub fn layout(self, layout: &Layout) -> Self {
self.map_ty(|ty| ty.layout(layout))
}
pub fn glyph_colors<I, C>(self, glyph_colors: I) -> Self
where
I: IntoIterator<Item = C>,
C: IntoLinSrgba<ColorScalar>,
{
let glyph_colors = glyph_colors
.into_iter()
.map(|c| c.into_lin_srgba())
.collect();
self.map_ty(|ty| ty.glyph_colors(glyph_colors))
}
}
impl draw::renderer::RenderPrimitive for Text {
fn render_primitive(
self,
ctxt: draw::renderer::RenderContext,
mesh: &mut draw::Mesh,
) -> draw::renderer::PrimitiveRender {
let Text {
spatial,
style,
text,
} = self;
let Style {
color,
glyph_colors,
layout,
} = style;
let layout = layout.build();
let (maybe_x, maybe_y, maybe_z) = (
spatial.dimensions.x,
spatial.dimensions.y,
spatial.dimensions.z,
);
assert!(
maybe_z.is_none(),
"z dimension support for text is unimplemented"
);
let w = maybe_x.unwrap_or(200.0);
let h = maybe_y.unwrap_or(200.0);
let rect: geom::Rect = geom::Rect::from_wh([w, h].into());
let color = color.unwrap_or_else(|| ctxt.theme.fill_lin_srgba(&theme::Primitive::Text));
let text_str = &ctxt.text_buffer[text.clone()];
let text = text::text(text_str).layout(&layout).build(rect);
let font_id = text::font::id(text.font());
let positioned_glyphs: Vec<_> = text
.rt_glyphs(
ctxt.output_attachment_size,
ctxt.output_attachment_scale_factor,
)
.collect();
for glyph in positioned_glyphs.iter() {
ctxt.glyph_cache.queue_glyph(font_id.index(), glyph.clone());
}
let (glyph_cache_w, _) = ctxt.glyph_cache.dimensions();
{
let draw::renderer::RenderContext {
glyph_cache:
&mut draw::renderer::GlyphCache {
ref mut cache,
ref mut pixel_buffer,
ref mut requires_upload,
..
},
..
} = ctxt;
let glyph_cache_w = glyph_cache_w as usize;
let res = cache.cache_queued(|rect, data| {
let width = (rect.max.x - rect.min.x) as usize;
let height = (rect.max.y - rect.min.y) as usize;
let mut dst_ix = rect.min.y as usize * glyph_cache_w + rect.min.x as usize;
let mut src_ix = 0;
for _ in 0..height {
let dst_range = dst_ix..dst_ix + width;
let src_range = src_ix..src_ix + width;
let dst_slice = &mut pixel_buffer[dst_range];
let src_slice = &data[src_range];
dst_slice.copy_from_slice(src_slice);
dst_ix += glyph_cache_w;
src_ix += width;
}
*requires_upload = true;
});
if let Err(err) = res {
eprintln!("failed to cache queued glyphs: {}", err);
}
}
let global_transform = *ctxt.transform;
let local_transform = spatial.position.transform() * spatial.orientation.transform();
let transform = global_transform * local_transform;
let scale_factor = ctxt.output_attachment_scale_factor;
let (out_w, out_h) = ctxt.output_attachment_size.into();
let [half_out_w, half_out_h] = [out_w as f32 / 2.0, out_h as f32 / 2.0];
let to_nannou_rect = |screen_rect: text::rt::Rect<i32>| {
let l = screen_rect.min.x as f32 / scale_factor - half_out_w;
let r = screen_rect.max.x as f32 / scale_factor - half_out_w;
let t = -(screen_rect.min.y as f32 / scale_factor - half_out_h);
let b = -(screen_rect.max.y as f32 / scale_factor - half_out_h);
geom::Rect::from_corners([l, b].into(), [r, t].into())
};
let glyph_colors_iter = text
.line_infos()
.iter()
.flat_map(|li| li.char_range())
.take_while(|&i| i < glyph_colors.len())
.map(|i| &glyph_colors[i])
.chain(std::iter::repeat(&color));
for (g, g_color) in positioned_glyphs.iter().zip(glyph_colors_iter) {
if let Ok(Some((uv_rect, screen_rect))) = ctxt.glyph_cache.rect_for(font_id.index(), &g)
{
let rect = to_nannou_rect(screen_rect);
let v = |p: Point2, tex_coords: [f32; 2]| -> draw::mesh::Vertex {
let p = transform.transform_point3([p.x, p.y, 0.0].into());
let point = draw::mesh::vertex::Point::from(p);
draw::mesh::vertex::new(point, g_color.to_owned(), tex_coords.into())
};
let uv_l = uv_rect.min.x;
let uv_t = uv_rect.min.y;
let uv_r = uv_rect.max.x;
let uv_b = uv_rect.max.y;
let bottom_left = v(rect.bottom_left(), [uv_l, uv_b]);
let bottom_right = v(rect.bottom_right(), [uv_r, uv_b]);
let top_left = v(rect.top_left(), [uv_l, uv_t]);
let top_right = v(rect.top_right(), [uv_r, uv_t]);
let start_ix = mesh.points().len() as u32;
mesh.push_vertex(top_left);
mesh.push_vertex(bottom_left);
mesh.push_vertex(bottom_right);
mesh.push_vertex(top_right);
let tl_ix = start_ix;
let bl_ix = start_ix + 1;
let br_ix = start_ix + 2;
let tr_ix = start_ix + 3;
mesh.push_index(tl_ix);
mesh.push_index(bl_ix);
mesh.push_index(br_ix);
mesh.push_index(tl_ix);
mesh.push_index(br_ix);
mesh.push_index(tr_ix);
}
}
draw::renderer::PrimitiveRender::text()
}
}
impl SetOrientation for Text {
fn properties(&mut self) -> &mut orientation::Properties {
SetOrientation::properties(&mut self.spatial)
}
}
impl SetPosition for Text {
fn properties(&mut self) -> &mut position::Properties {
SetPosition::properties(&mut self.spatial)
}
}
impl SetDimensions for Text {
fn properties(&mut self) -> &mut dimension::Properties {
SetDimensions::properties(&mut self.spatial)
}
}
impl SetColor<ColorScalar> for Text {
fn rgba_mut(&mut self) -> &mut Option<LinSrgba> {
SetColor::rgba_mut(&mut self.style.color)
}
}
impl From<Text> for Primitive {
fn from(prim: Text) -> Self {
Primitive::Text(prim)
}
}
impl Into<Option<Text>> for Primitive {
fn into(self) -> Option<Text> {
match self {
Primitive::Text(prim) => Some(prim),
_ => None,
}
}
}