Skip to main content

ass_editor/commands/fonts_graphics_commands/
add.rs

1//! Commands for adding embedded fonts and graphics to ASS documents
2
3use super::uuencode_data;
4use crate::commands::{CommandResult, EditorCommand};
5use crate::core::{EditorDocument, Position, Range, Result};
6
7#[cfg(not(feature = "std"))]
8use alloc::{
9    format,
10    string::{String, ToString},
11    vec::Vec,
12};
13
14/// Command to add an embedded font to the ASS document
15///
16/// Fonts are embedded using UU-encoding in the `[Fonts]` section.
17/// This command supports both pre-encoded data and raw binary data.
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct AddFontCommand {
20    /// Font filename
21    pub filename: String,
22    /// UU-encoded font data lines
23    pub data_lines: Vec<String>,
24}
25
26impl AddFontCommand {
27    /// Create a new add font command
28    pub fn new(filename: String, data_lines: Vec<String>) -> Self {
29        Self {
30            filename,
31            data_lines,
32        }
33    }
34
35    /// Create from raw binary data (will UU-encode it)
36    pub fn from_binary(filename: String, data: &[u8]) -> Self {
37        let encoded = uuencode_data(&filename, data);
38        Self {
39            filename,
40            data_lines: encoded,
41        }
42    }
43}
44
45impl EditorCommand for AddFontCommand {
46    fn execute(&self, document: &mut EditorDocument) -> Result<CommandResult> {
47        let content = document.text().to_string();
48
49        // Find or create [Fonts] section
50        let fonts_section = content.find("[Fonts]");
51
52        let insert_pos = if let Some(section_start) = fonts_section {
53            // Find the end of the Fonts section
54            content[section_start..]
55                .find("\n[")
56                .map(|pos| section_start + pos)
57                .unwrap_or(content.len())
58        } else {
59            // Create new Fonts section at the end
60            let mut insert_pos = content.len();
61
62            // Add newline if needed
63            if !content.ends_with('\n') {
64                document.insert_raw(Position::new(insert_pos), "\n")?;
65                insert_pos += 1;
66            }
67
68            // Add section header
69            document.insert_raw(Position::new(insert_pos), "[Fonts]\n")?;
70            insert_pos + "[Fonts]\n".len()
71        };
72
73        // Build font entry
74        let filename = &self.filename;
75        let mut font_entry = format!("fontname: {filename}\n");
76        for line in &self.data_lines {
77            font_entry.push_str(line);
78            font_entry.push('\n');
79        }
80
81        // Insert font data
82        document.insert_raw(Position::new(insert_pos), &font_entry)?;
83
84        Ok(CommandResult::success_with_change(
85            Range::new(
86                Position::new(insert_pos),
87                Position::new(insert_pos + font_entry.len()),
88            ),
89            Position::new(insert_pos + font_entry.len()),
90        ))
91    }
92
93    fn description(&self) -> &str {
94        "Add font"
95    }
96
97    fn memory_usage(&self) -> usize {
98        core::mem::size_of::<Self>()
99            + self.filename.len()
100            + self.data_lines.iter().map(|l| l.len()).sum::<usize>()
101    }
102}
103
104/// Command to add an embedded graphic to the ASS document
105///
106/// Graphics are embedded using UU-encoding in the `[Graphics]` section.
107/// This command supports both pre-encoded data and raw binary data.
108#[derive(Debug, Clone, PartialEq, Eq)]
109pub struct AddGraphicCommand {
110    /// Graphic filename
111    pub filename: String,
112    /// UU-encoded graphic data lines
113    pub data_lines: Vec<String>,
114}
115
116impl AddGraphicCommand {
117    /// Create a new add graphic command
118    pub fn new(filename: String, data_lines: Vec<String>) -> Self {
119        Self {
120            filename,
121            data_lines,
122        }
123    }
124
125    /// Create from raw binary data (will UU-encode it)
126    pub fn from_binary(filename: String, data: &[u8]) -> Self {
127        let encoded = uuencode_data(&filename, data);
128        Self {
129            filename,
130            data_lines: encoded,
131        }
132    }
133}
134
135impl EditorCommand for AddGraphicCommand {
136    fn execute(&self, document: &mut EditorDocument) -> Result<CommandResult> {
137        let content = document.text().to_string();
138
139        // Find or create [Graphics] section
140        let graphics_section = content.find("[Graphics]");
141
142        let insert_pos = if let Some(section_start) = graphics_section {
143            // Find the end of the Graphics section
144            content[section_start..]
145                .find("\n[")
146                .map(|pos| section_start + pos)
147                .unwrap_or(content.len())
148        } else {
149            // Create new Graphics section at the end
150            let mut insert_pos = content.len();
151
152            // Add newline if needed
153            if !content.ends_with('\n') {
154                document.insert_raw(Position::new(insert_pos), "\n")?;
155                insert_pos += 1;
156            }
157
158            // Add section header
159            document.insert_raw(Position::new(insert_pos), "[Graphics]\n")?;
160            insert_pos + "[Graphics]\n".len()
161        };
162
163        // Build graphic entry
164        let filename = &self.filename;
165        let mut graphic_entry = format!("filename: {filename}\n");
166        for line in &self.data_lines {
167            graphic_entry.push_str(line);
168            graphic_entry.push('\n');
169        }
170
171        // Insert graphic data
172        document.insert_raw(Position::new(insert_pos), &graphic_entry)?;
173
174        Ok(CommandResult::success_with_change(
175            Range::new(
176                Position::new(insert_pos),
177                Position::new(insert_pos + graphic_entry.len()),
178            ),
179            Position::new(insert_pos + graphic_entry.len()),
180        ))
181    }
182
183    fn description(&self) -> &str {
184        "Add graphic"
185    }
186
187    fn memory_usage(&self) -> usize {
188        core::mem::size_of::<Self>()
189            + self.filename.len()
190            + self.data_lines.iter().map(|l| l.len()).sum::<usize>()
191    }
192}