use super::package::Result;
use super::super::consts::PptRecordType;
use super::records::PptRecord;
use crate::ole::binary::{parse_utf16le_string, parse_windows1252_string_len};
#[derive(Debug, Clone, Default)]
pub struct TextRunFormatting {
pub font_size: Option<u16>,
pub font_color: Option<u32>,
pub bold: bool,
pub italic: bool,
pub underline: bool,
pub font_name: Option<String>,
}
#[derive(Debug, Clone)]
pub struct TextRun {
pub text: String,
pub formatting: TextRunFormatting,
pub start_index: usize,
pub length: usize,
#[cfg(feature = "formula")]
mtef_formula_ast: Option<Vec<crate::formula::MathNode<'static>>>,
#[cfg(not(feature = "formula"))]
mtef_formula_ast: Option<Vec<()>>,
}
impl TextRun {
pub fn new(text: String, start_index: usize) -> Self {
let length = text.chars().count();
Self {
text,
formatting: TextRunFormatting::default(),
start_index,
length,
mtef_formula_ast: None,
}
}
pub fn with_formatting(text: String, start_index: usize, formatting: TextRunFormatting) -> Self {
let length = text.chars().count();
Self {
text,
formatting,
start_index,
length,
mtef_formula_ast: None,
}
}
#[cfg(feature = "formula")]
pub fn with_mtef_formula(
text: String,
start_index: usize,
formatting: TextRunFormatting,
mtef_ast: Vec<crate::formula::MathNode<'static>>,
) -> Self {
let length = text.chars().count();
Self {
text,
formatting,
start_index,
length,
mtef_formula_ast: Some(mtef_ast),
}
}
#[cfg(not(feature = "formula"))]
pub fn with_mtef_formula(
text: String,
start_index: usize,
formatting: TextRunFormatting,
_mtef_ast: Vec<()>,
) -> Self {
let length = text.chars().count();
Self {
text,
formatting,
start_index,
length,
mtef_formula_ast: None,
}
}
pub fn has_mtef_formula(&self) -> bool {
self.mtef_formula_ast.is_some()
}
#[cfg(feature = "formula")]
pub fn mtef_formula_ast(&self) -> Option<&Vec<crate::formula::MathNode<'static>>> {
self.mtef_formula_ast.as_ref()
}
#[cfg(not(feature = "formula"))]
pub fn mtef_formula_ast(&self) -> Option<&Vec<()>> {
self.mtef_formula_ast.as_ref()
}
#[cfg(feature = "formula")]
pub fn mtef_formula_ast_mut(&mut self) -> &mut Option<Vec<crate::formula::MathNode<'static>>> {
&mut self.mtef_formula_ast
}
#[cfg(not(feature = "formula"))]
pub fn mtef_formula_ast_mut(&mut self) -> &mut Option<Vec<()>> {
&mut self.mtef_formula_ast
}
}
pub struct TextRunExtractor {
text: String,
runs: Vec<TextRun>,
}
impl TextRunExtractor {
pub fn new() -> Self {
Self {
text: String::new(),
runs: Vec::new(),
}
}
pub fn extract_from_records(&mut self, records: &[PptRecord]) -> Result<()> {
for record in records {
self.process_record(record)?;
}
Ok(())
}
fn process_record(&mut self, record: &PptRecord) -> Result<()> {
match record.record_type {
PptRecordType::TextCharsAtom => {
let text = parse_utf16le_string(&record.data);
if !text.is_empty() {
let start_index = self.text.len();
self.text.push_str(&text);
self.runs.push(TextRun::new(text, start_index));
}
}
PptRecordType::TextBytesAtom => {
let text = parse_windows1252_string_len(&record.data, 0, record.data.len());
if !text.is_empty() {
let start_index = self.text.len();
self.text.push_str(&text);
self.runs.push(TextRun::new(text, start_index));
}
}
PptRecordType::StyleTextPropAtom => {
self.apply_style_properties(record)?;
}
_ => {
for child in &record.children {
self.process_record(child)?;
}
}
}
Ok(())
}
fn apply_style_properties(&mut self, record: &PptRecord) -> Result<()> {
if record.data.len() < 10 {
return Ok(()); }
let (_paragraph_styles, character_styles) = super::text_prop::parse_style_text_prop_atom(
&record.data,
self.text.chars().count(),
);
for (style_idx, char_style) in character_styles.iter().enumerate() {
if style_idx < self.runs.len() {
let run = &mut self.runs[style_idx];
let mut formatting = TextRunFormatting::default();
if let Some(size) = char_style.get_value("font.size") {
formatting.font_size = Some(size as u16);
}
if let Some(color) = char_style.get_value("font.color") {
formatting.font_color = Some(color as u32);
}
if let Some(flags) = char_style.get_value("char.flags") {
let (bold, italic, underline) = super::text_prop::extract_char_flags(flags);
formatting.bold = bold;
formatting.italic = italic;
formatting.underline = underline;
}
run.formatting = formatting;
}
}
Ok(())
}
pub fn text(&self) -> &str {
&self.text
}
pub fn runs(&self) -> &[TextRun] {
&self.runs
}
pub fn run_count(&self) -> usize {
self.runs.len()
}
}
impl Default for TextRunExtractor {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_text_run_creation() {
let run = TextRun::new("Hello".to_string(), 0);
assert_eq!(run.text, "Hello");
assert_eq!(run.start_index, 0);
assert_eq!(run.length, 5);
}
#[test]
fn test_text_run_extractor() {
let mut extractor = TextRunExtractor::new();
let text_data = vec![
0x48, 0x00, 0x65, 0x00, 0x6C, 0x00, 0x6C, 0x00, 0x6F, 0x00, 0x00, 0x00, ];
let record = PptRecord {
record_type: PptRecordType::TextCharsAtom,
record_type_raw: 4000,
version: 0,
instance: 0,
data_length: text_data.len() as u32,
data: text_data,
children: Vec::new(),
};
extractor.extract_from_records(&[record]).unwrap();
assert_eq!(extractor.text(), "Hello");
assert_eq!(extractor.run_count(), 1);
}
}