#[path = "cls_text_builder/col_pad_buffer_for_shadow.rs"]
mod col_pad_buffer_for_shadow;
#[path = "cls_text_builder/col_word_color_buffer.rs"]
mod col_word_color_buffer;
#[cfg(test)]
#[path = "cls_text_builder/test_cls_text_builder.rs"]
mod tests;
use std::io::Write;
use crate::color::ColorMode;
use crate::font::{render_text, Font, RenderOptions};
use crate::render::{apply_color, apply_shadow, write_ansi, ColorFill, RenderBuffer};
use crate::{Align, Color, Error, FallbackMode, GradientDirection, TextStyle};
use col_pad_buffer_for_shadow::pad_buffer_for_shadow;
use col_word_color_buffer::build_word_color_buffer;
#[derive(Clone, Debug)]
pub struct TextBuilder<'a> {
font: &'a Font,
text: String,
fill: Option<ColorFillInput>,
word_colors: Option<Vec<Color>>,
shadow: Option<ShadowInput>,
style: TextStyle,
spacing: i8,
align: Align,
fallback: FallbackMode,
color_mode: ColorMode,
}
#[derive(Clone, Debug)]
enum ColorFillInput {
Solid(Color),
Gradient {
start: Color,
end: Color,
vertical: bool,
},
}
#[derive(Clone, Debug)]
struct ShadowInput {
dx: i8,
dy: i8,
color: Color,
}
impl<'a> TextBuilder<'a> {
pub(crate) fn new(font: &'a Font, text: &str) -> Self {
Self {
font,
text: text.to_string(),
fill: None,
word_colors: None,
shadow: None,
style: TextStyle::empty(),
spacing: 0,
align: Align::Left,
fallback: FallbackMode::Error,
color_mode: ColorMode::TrueColor,
}
}
pub fn color(mut self, color: impl Into<Color>) -> Self {
self.fill = Some(ColorFillInput::Solid(color.into()));
self
}
pub fn gradient(mut self, start: impl Into<Color>, end: impl Into<Color>) -> Self {
self.fill = Some(ColorFillInput::Gradient {
start: start.into(),
end: end.into(),
vertical: false,
});
self
}
pub fn vertical_gradient(mut self, top: impl Into<Color>, bottom: impl Into<Color>) -> Self {
self.fill = Some(ColorFillInput::Gradient {
start: top.into(),
end: bottom.into(),
vertical: true,
});
self
}
pub fn gradient_direction(
mut self,
start: impl Into<Color>,
end: impl Into<Color>,
direction: GradientDirection,
) -> Self {
let vertical = matches!(direction, GradientDirection::Vertical);
self.fill = Some(ColorFillInput::Gradient {
start: start.into(),
end: end.into(),
vertical,
});
self
}
pub fn word_colors<I, C>(mut self, colors: I) -> Self
where
I: IntoIterator<Item = C>,
C: Into<Color>,
{
let collected: Vec<Color> = colors.into_iter().map(Into::into).collect();
self.word_colors = Some(collected);
self
}
pub fn shadow(mut self, dx: i8, dy: i8, color: impl Into<Color>) -> Self {
self.shadow = Some(ShadowInput {
dx,
dy,
color: color.into(),
});
self
}
pub fn drop_shadow(mut self) -> Self {
self.shadow = Some(ShadowInput {
dx: 1,
dy: 1,
color: Color::rgb(0, 0, 0),
});
self
}
pub fn style(mut self, style: TextStyle) -> Self {
self.style = style;
self
}
pub fn spacing(mut self, adjust: i8) -> Self {
self.spacing = adjust.clamp(-10, 100);
self
}
pub fn align(mut self, align: Align) -> Self {
self.align = align;
self
}
pub fn fallback(mut self, mode: FallbackMode) -> Self {
self.fallback = mode;
self
}
pub fn color_mode(mut self, mode: ColorMode) -> Self {
self.color_mode = mode;
self
}
pub fn build(self) -> Result<String, Error> {
let buffer = self.build_buffer_ref()?;
let mut bytes = Vec::new();
write_ansi(&buffer, self.color_mode, &mut bytes)?;
String::from_utf8(bytes).map_err(|err| Error::InvalidFormat {
message: err.to_string(),
})
}
pub fn write_to<W: Write>(self, w: &mut W) -> std::io::Result<()> {
let buffer = self
.build_buffer_ref()
.map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidData, err))?;
write_ansi(&buffer, self.color_mode, w)
}
pub fn write_to_string(self, s: &mut String) -> Result<(), Error> {
let output = self.build()?;
s.push_str(&output);
Ok(())
}
pub fn build_buffer(self) -> Result<RenderBuffer, Error> {
self.build_buffer_ref()
}
fn build_buffer_ref(&self) -> Result<RenderBuffer, Error> {
let options = RenderOptions {
style: self.style,
spacing: self.spacing,
align: self.align,
fallback: self.fallback,
};
let mut buffer = if let Some(colors) = &self.word_colors {
build_word_color_buffer(self.font, &self.text, &options, colors)?
} else {
let mut buffer = render_text(self.font, &self.text, &options)?;
if let Some(fill) = self.resolve_fill()? {
apply_color(&mut buffer, &fill);
}
buffer
};
if let Some(shadow) = &self.shadow {
pad_buffer_for_shadow(&mut buffer, shadow.dx, shadow.dy);
let shadow_color = shadow.color.to_rgb()?;
apply_shadow(&mut buffer, shadow.dx, shadow.dy, shadow_color);
}
Ok(buffer)
}
fn resolve_fill(&self) -> Result<Option<ColorFill>, Error> {
match &self.fill {
None => Ok(None),
Some(ColorFillInput::Solid(color)) => Ok(Some(ColorFill::Solid(color.to_rgb()?))),
Some(ColorFillInput::Gradient {
start,
end,
vertical,
}) => Ok(Some(ColorFill::Gradient {
start: start.to_rgb()?,
end: end.to_rgb()?,
vertical: *vertical,
})),
}
}
}