use super::{CT_CGTransform, TextCode};
#[allow(non_camel_case_types)]
#[derive(Debug, Clone)]
pub struct CT_Text {
pub id: u32,
pub boundary: String,
pub font_ref: Option<u32>,
pub size: Option<f64>,
pub stroke: bool,
pub fill: bool,
pub h_scale: Option<f64>,
pub read_direction: Option<u32>,
pub char_direction: Option<u32>,
pub weight: Option<u32>,
pub italic: bool,
pub fill_color: Option<u32>,
pub stroke_color: Option<u32>,
pub cg_transforms: Vec<CT_CGTransform>,
pub text_codes: Vec<TextCode>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Direction {
LeftToRight,
TopToBottom,
RightToLeft,
BottomToTop,
}
impl Direction {
#[must_use]
pub fn as_degrees(&self) -> u32 {
match self {
Self::LeftToRight => 0,
Self::TopToBottom => 90,
Self::RightToLeft => 180,
Self::BottomToTop => 270,
}
}
}
impl CT_Text {
#[must_use]
pub fn new(id: u32, boundary: impl Into<String>) -> Self {
Self {
id,
boundary: boundary.into(),
font_ref: None,
size: None,
stroke: false,
fill: true,
h_scale: None,
read_direction: None,
char_direction: None,
weight: None,
italic: false,
fill_color: None,
stroke_color: None,
cg_transforms: Vec::new(),
text_codes: Vec::new(),
}
}
#[must_use]
pub fn font(mut self, ref_id: u32) -> Self {
self.font_ref = Some(ref_id);
self
}
#[must_use]
pub fn size(mut self, size: f64) -> Self {
self.size = Some(size);
self
}
#[must_use]
pub fn stroke(mut self, stroke: bool) -> Self {
self.stroke = stroke;
self
}
#[must_use]
pub fn fill(mut self, fill: bool) -> Self {
self.fill = fill;
self
}
#[must_use]
pub fn h_scale(mut self, scale: f64) -> Self {
self.h_scale = Some(scale);
self
}
#[must_use]
pub fn read_direction(mut self, dir: Direction) -> Self {
self.read_direction = Some(dir.as_degrees());
self
}
#[must_use]
pub fn char_direction(mut self, dir: Direction) -> Self {
self.char_direction = Some(dir.as_degrees());
self
}
#[must_use]
pub fn weight(mut self, weight: u32) -> Self {
self.weight = Some(weight);
self
}
#[must_use]
pub fn italic(mut self, italic: bool) -> Self {
self.italic = italic;
self
}
#[must_use]
pub fn fill_color(mut self, color: u32) -> Self {
self.fill_color = Some(color);
self
}
#[must_use]
pub fn stroke_color(mut self, color: u32) -> Self {
self.stroke_color = Some(color);
self
}
pub fn add_cg_transform(&mut self, cg: CT_CGTransform) {
self.cg_transforms.push(cg);
}
pub fn add_text_code(&mut self, tc: TextCode) {
self.text_codes.push(tc);
}
#[must_use]
pub fn get_font(&self) -> Option<u32> {
self.font_ref
}
#[must_use]
pub fn get_size(&self) -> Option<f64> {
self.size
}
#[must_use]
pub fn get_weight(&self) -> Option<u32> {
self.weight
}
#[must_use]
pub fn get_cg_transforms(&self) -> &[CT_CGTransform] {
&self.cg_transforms
}
#[must_use]
pub fn get_text_codes(&self) -> &[TextCode] {
&self.text_codes
}
#[must_use]
pub fn to_xml_string(&self) -> String {
use std::fmt::Write;
let mut xml = format!(
"<ofd:TextObject ID=\"{}\" Boundary=\"{}\"",
self.id, self.boundary
);
if let Some(fr) = self.font_ref {
write!(xml, " Font=\"{fr}\"").expect("写入内存缓冲区不会失败");
}
if let Some(sz) = self.size {
write!(xml, " Size=\"{sz}\"").expect("写入内存缓冲区不会失败");
}
if self.stroke {
xml.push_str(" Stroke=\"true\"");
}
if !self.fill {
xml.push_str(" Fill=\"false\"");
}
if let Some(hs) = self.h_scale {
write!(xml, " HScale=\"{hs}\"").expect("写入内存缓冲区不会失败");
}
if let Some(rd) = self.read_direction {
write!(xml, " ReadDirection=\"{rd}\"").expect("写入内存缓冲区不会失败");
}
if let Some(cd) = self.char_direction {
write!(xml, " CharDirection=\"{cd}\"").expect("写入内存缓冲区不会失败");
}
if let Some(w) = self.weight {
write!(xml, " Weight=\"{w}\"").expect("写入内存缓冲区不会失败");
}
if self.italic {
xml.push_str(" Italic=\"true\"");
}
if let Some(fc) = self.fill_color {
write!(xml, " FillColor=\"{fc}\"").expect("写入内存缓冲区不会失败");
}
if let Some(sc) = self.stroke_color {
write!(xml, " StrokeColor=\"{sc}\"").expect("写入内存缓冲区不会失败");
}
xml.push_str(">\n");
for cg in &self.cg_transforms {
xml.push_str(" ");
xml.push_str(&cg.to_xml_string());
xml.push('\n');
}
for tc in &self.text_codes {
xml.push_str(" ");
xml.push_str(&tc.to_xml_string());
xml.push('\n');
}
xml.push_str("</ofd:TextObject>\n");
xml
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ct_text_new() {
let t = CT_Text::new(1, "0 0 100 20");
assert_eq!(t.id, 1);
assert_eq!(t.boundary, "0 0 100 20");
assert!(t.font_ref.is_none());
assert!(t.fill);
assert!(!t.stroke);
assert!(!t.italic);
}
#[test]
fn test_ct_text_builder() {
let t = CT_Text::new(2, "10 20 50 15")
.font(3)
.size(14.0)
.weight(700)
.italic(true)
.fill_color(0xFF_0000)
.h_scale(1.2);
assert_eq!(t.get_font(), Some(3));
assert!((t.get_size().unwrap() - 14.0).abs() < f64::EPSILON);
assert_eq!(t.get_weight(), Some(700));
assert!(t.italic);
assert_eq!(t.fill_color, Some(0xFF_0000));
assert!((t.h_scale.unwrap() - 1.2).abs() < f64::EPSILON);
}
#[test]
fn test_ct_text_direction() {
let t = CT_Text::new(1, "0 0 10 10")
.read_direction(Direction::TopToBottom)
.char_direction(Direction::RightToLeft);
assert_eq!(t.read_direction, Some(90));
assert_eq!(t.char_direction, Some(180));
}
#[test]
fn test_ct_text_add_text_code() {
let mut t = CT_Text::new(1, "0 0 100 20");
t.add_text_code(TextCode::with_content("Hello").coordinate(0.0, 10.0));
t.add_text_code(TextCode::with_content("World").coordinate(30.0, 10.0));
assert_eq!(t.get_text_codes().len(), 2);
}
#[test]
fn test_ct_text_add_cg_transform() {
let mut t = CT_Text::new(1, "0 0 100 20");
t.add_cg_transform(
CT_CGTransform::new()
.code_position(0)
.code_count(1)
.glyph_count(1)
.glyphs(vec![42]),
);
assert_eq!(t.get_cg_transforms().len(), 1);
}
#[test]
fn test_direction_as_degrees() {
assert_eq!(Direction::LeftToRight.as_degrees(), 0);
assert_eq!(Direction::TopToBottom.as_degrees(), 90);
assert_eq!(Direction::RightToLeft.as_degrees(), 180);
assert_eq!(Direction::BottomToTop.as_degrees(), 270);
}
#[test]
fn test_ct_text_to_xml_basic() {
let t = CT_Text::new(1, "0 0 100 20");
let xml = t.to_xml_string();
assert!(xml.contains("ID=\"1\""));
assert!(xml.contains("Boundary=\"0 0 100 20\""));
assert!(xml.contains("<ofd:TextObject"));
assert!(xml.contains("</ofd:TextObject>"));
}
#[test]
fn test_ct_text_to_xml_full() {
let mut t = CT_Text::new(5, "10 20 200 30")
.font(3)
.size(12.0)
.weight(700)
.italic(true)
.stroke(true)
.fill(false)
.fill_color(0xFF_0000)
.stroke_color(0x00_FF00)
.read_direction(Direction::TopToBottom);
t.add_text_code(TextCode::with_content("test").coordinate(10.0, 30.0));
let xml = t.to_xml_string();
assert!(xml.contains("Font=\"3\""));
assert!(xml.contains("Size=\"12\""));
assert!(xml.contains("Weight=\"700\""));
assert!(xml.contains("Italic=\"true\""));
assert!(xml.contains("Stroke=\"true\""));
assert!(xml.contains("Fill=\"false\""));
assert!(xml.contains("FillColor=\"16711680\""));
assert!(xml.contains("StrokeColor=\"65280\""));
assert!(xml.contains("ReadDirection=\"90\""));
assert!(xml.contains("ofd:TextCode"));
assert!(xml.contains("test"));
}
#[test]
fn test_ct_text_clone_debug() {
let t = CT_Text::new(1, "0 0 1 1");
let t2 = t.clone();
assert_eq!(t2.id, 1);
assert!(format!("{t:?}").contains("CT_Text"));
}
}