#[derive(Debug, Clone, Default)]
pub struct TextStyle {
pub font_name: Option<String>,
pub font_size: Option<f32>,
pub bold: bool,
pub italic: bool,
pub underline: bool,
pub strikethrough: bool,
pub color: Option<(f32, f32, f32)>,
}
impl TextStyle {
pub fn pdf_font_name(&self) -> &str {
let base = self.font_name.as_deref().unwrap_or("Helvetica");
let mapped = match base.to_lowercase().as_str() {
"times new roman" | "times" => "Times-Roman",
"arial" | "helvetica" => "Helvetica",
"courier new" | "courier" => "Courier",
"symbol" => "Symbol",
"zapfdingbats" | "wingdings" => "ZapfDingbats",
_ => "Helvetica",
};
match (self.bold, self.italic) {
(true, true) => match mapped {
"Helvetica" => "Helvetica-BoldOblique",
"Times-Roman" => "Times-BoldItalic",
"Courier" => "Courier-BoldOblique",
_ => mapped,
},
(true, false) => match mapped {
"Helvetica" => "Helvetica-Bold",
"Times-Roman" => "Times-Bold",
"Courier" => "Courier-Bold",
_ => mapped,
},
(false, true) => match mapped {
"Helvetica" => "Helvetica-Oblique",
"Times-Roman" => "Times-Italic",
"Courier" => "Courier-Oblique",
_ => mapped,
},
(false, false) => mapped,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct ParagraphStyle {
pub alignment: ParagraphAlignment,
pub space_before: f32,
pub space_after: f32,
pub heading_level: Option<u8>,
pub list_level: Option<u8>,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum ParagraphAlignment {
#[default]
Left,
Center,
Right,
Justify,
}
pub fn parse_color(color_str: &str) -> Option<(f32, f32, f32)> {
if color_str.len() == 6 {
let r = u8::from_str_radix(&color_str[0..2], 16).ok()?;
let g = u8::from_str_radix(&color_str[2..4], 16).ok()?;
let b = u8::from_str_radix(&color_str[4..6], 16).ok()?;
return Some((r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0));
}
match color_str.to_lowercase().as_str() {
"black" => Some((0.0, 0.0, 0.0)),
"white" => Some((1.0, 1.0, 1.0)),
"red" => Some((1.0, 0.0, 0.0)),
"green" => Some((0.0, 1.0, 0.0)),
"blue" => Some((0.0, 0.0, 1.0)),
"yellow" => Some((1.0, 1.0, 0.0)),
"cyan" => Some((0.0, 1.0, 1.0)),
"magenta" => Some((1.0, 0.0, 1.0)),
"gray" | "grey" => Some((0.5, 0.5, 0.5)),
_ => None,
}
}
pub fn half_points_to_points(half_points: i32) -> f32 {
half_points as f32 / 2.0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_text_style_font_mapping() {
let mut style = TextStyle::default();
assert_eq!(style.pdf_font_name(), "Helvetica");
style.bold = true;
assert_eq!(style.pdf_font_name(), "Helvetica-Bold");
style.italic = true;
assert_eq!(style.pdf_font_name(), "Helvetica-BoldOblique");
style.bold = false;
assert_eq!(style.pdf_font_name(), "Helvetica-Oblique");
}
#[test]
fn test_text_style_times_font() {
let mut style = TextStyle {
font_name: Some("Times New Roman".to_string()),
..Default::default()
};
assert_eq!(style.pdf_font_name(), "Times-Roman");
style.bold = true;
assert_eq!(style.pdf_font_name(), "Times-Bold");
style.italic = true;
assert_eq!(style.pdf_font_name(), "Times-BoldItalic");
}
#[test]
fn test_parse_color_hex() {
let color = parse_color("FF0000").unwrap();
assert!((color.0 - 1.0).abs() < 0.01);
assert!(color.1 < 0.01);
assert!(color.2 < 0.01);
}
#[test]
fn test_parse_color_named() {
assert_eq!(parse_color("black"), Some((0.0, 0.0, 0.0)));
assert_eq!(parse_color("white"), Some((1.0, 1.0, 1.0)));
assert_eq!(parse_color("red"), Some((1.0, 0.0, 0.0)));
}
#[test]
fn test_half_points_to_points() {
assert_eq!(half_points_to_points(24), 12.0);
assert_eq!(half_points_to_points(22), 11.0);
}
}