use crate::vector::Color;
#[cfg(feature = "tracing")]
use tracing::instrument;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum TextAlignment {
#[default]
Left,
Center,
Right,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum TextRenderingMode {
#[default]
Fill,
Stroke,
FillStroke,
Invisible,
FillClip,
StrokeClip,
FillStrokeClip,
Clip,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum FontWeight {
Thin,
ExtraLight,
Light,
#[default]
Normal,
Medium,
SemiBold,
Bold,
ExtraBold,
Black,
}
#[derive(Clone, Debug)]
pub struct TextBuilder {
commands: Vec<String>,
}
impl TextBuilder {
pub fn new() -> Self {
Self {
commands: vec!["BT".to_string()],
}
}
pub fn font(mut self, name: &str, size: f64) -> Self {
self.commands.push(format!("/{} {} Tf", name, size));
self
}
pub fn next_line(mut self, offset_x: f64, offset_y: f64) -> Self {
self.commands.push(format!("{} {} Td", offset_x, offset_y));
self
}
pub fn position(mut self, x: f64, y: f64) -> Self {
self.commands.push(format!("{} {} Td", x, y));
self
}
pub fn text_matrix(mut self, a: f64, b: f64, c: f64, d: f64, e: f64, f: f64) -> Self {
self.commands
.push(format!("{} {} {} {} {} {} Tm", a, b, c, d, e, f));
self
}
pub fn text(mut self, text: &str) -> Self {
let escaped = text
.chars()
.map(|c| match c {
'(' => "\\(".to_string(),
')' => "\\)".to_string(),
'\\' => "\\\\".to_string(),
'\n' => "\\n".to_string(),
'\r' => "\\r".to_string(),
'\t' => "\\t".to_string(),
'\x08' => "\\b".to_string(),
'\x0c' => "\\f".to_string(),
_ => c.to_string(),
})
.collect::<String>();
self.commands.push(format!("({}) Tj", escaped));
self
}
pub fn text_line(mut self, text: &str) -> Self {
let escaped = text
.chars()
.map(|c| match c {
'(' => "\\(".to_string(),
')' => "\\)".to_string(),
'\\' => "\\\\".to_string(),
'\n' => "\\n".to_string(),
'\r' => "\\r".to_string(),
'\t' => "\\t".to_string(),
'\x08' => "\\b".to_string(),
'\x0c' => "\\f".to_string(),
_ => c.to_string(),
})
.collect::<String>();
self.commands.push(format!("({}) '", escaped));
self
}
pub fn text_line_spacing(mut self, text: &str, word_spacing: f64, char_spacing: f64) -> Self {
let escaped = text
.chars()
.map(|c| match c {
'(' => "\\(".to_string(),
')' => "\\)".to_string(),
'\\' => "\\\\".to_string(),
'\n' => "\\n".to_string(),
'\r' => "\\r".to_string(),
'\t' => "\\t".to_string(),
'\x08' => "\\b".to_string(),
'\x0c' => "\\f".to_string(),
_ => c.to_string(),
})
.collect::<String>();
self.commands.push(format!(
"{} {} ({}) \"",
word_spacing, char_spacing, escaped
));
self
}
pub fn char_spacing(mut self, spacing: f64) -> Self {
self.commands.push(format!("{} Tc", spacing));
self
}
pub fn word_spacing(mut self, spacing: f64) -> Self {
self.commands.push(format!("{} Tw", spacing));
self
}
pub fn horizontal_scaling(mut self, scale: f64) -> Self {
self.commands.push(format!("{} Tz", scale * 100.0));
self
}
pub fn leading(mut self, leading: f64) -> Self {
self.commands.push(format!("{} TL", leading));
self
}
pub fn rendering_mode(mut self, mode: TextRenderingMode) -> Self {
let code = match mode {
TextRenderingMode::Fill => 0,
TextRenderingMode::Stroke => 1,
TextRenderingMode::FillStroke => 2,
TextRenderingMode::Invisible => 3,
TextRenderingMode::FillClip => 4,
TextRenderingMode::StrokeClip => 5,
TextRenderingMode::FillStrokeClip => 6,
TextRenderingMode::Clip => 7,
};
self.commands.push(format!("{} Tr", code));
self
}
pub fn text_rise(mut self, rise: f64) -> Self {
self.commands.push(format!("{} Ts", rise));
self
}
pub fn set_color(mut self, color: Color) -> Self {
self.commands
.push(format!("{} {} {} rg", color.r, color.g, color.b));
self
}
pub fn translate(mut self, tx: f64, ty: f64) -> Self {
self.commands.push(format!("1 0 0 1 {} {} Tm", tx, ty));
self
}
pub fn rotate(mut self, angle: f64) -> Self {
let cos_a = angle.cos();
let sin_a = angle.sin();
self.commands
.push(format!("{} {} {} {} 0 0 Tm", cos_a, sin_a, -sin_a, cos_a));
self
}
pub fn scale(mut self, sx: f64, sy: f64) -> Self {
self.commands.push(format!("{} 0 0 {} 0 0 Tm", sx, sy));
self
}
pub fn skew(mut self, ax: f64, ay: f64) -> Self {
let tan_x = ax.tan();
let tan_y = ay.tan();
self.commands
.push(format!("1 {} {} 1 0 0 Tm", tan_x, tan_y));
self
}
#[cfg_attr(feature = "tracing", instrument)]
pub fn finish(self) -> Vec<u8> {
let mut cmds = self.commands;
cmds.push("ET".to_string());
let mut content = cmds.join("\n");
content.push('\n');
content.into_bytes()
}
pub fn extend(mut self, other: TextBuilder) -> Self {
self.commands.extend(other.commands);
self
}
}
impl Default for TextBuilder {
fn default() -> Self {
Self::new()
}
}