Skip to main content

ass_core/parser/ast/media/
font.rs

1//! Embedded font AST node for the `[Fonts]` section.
2//!
3//! Defines the [`Font`] struct with lazy UU-decoding helpers and zero-copy
4//! spans over the original ASS source text.
5
6#[cfg(not(feature = "std"))]
7use alloc::format;
8use alloc::vec::Vec;
9
10use super::super::Span;
11#[cfg(debug_assertions)]
12use core::ops::Range;
13
14/// Embedded font from `[Fonts\]` section
15///
16/// Represents a font file embedded in the ASS script using UU-encoding.
17/// Provides lazy decoding to avoid processing overhead unless the font
18/// data is actually needed.
19///
20/// # Examples
21///
22/// ```rust
23/// use ass_core::parser::ast::{Font, Span};
24///
25/// let font = Font {
26///     filename: "custom.ttf",
27///     data_lines: vec!["begin 644 custom.ttf", "M'XL..."],
28///     span: Span::new(0, 0, 0, 0),
29/// };
30///
31/// // Decode when needed
32/// let decoded = font.decode_data()?;
33/// # Ok::<(), Box<dyn std::error::Error>>(())
34/// ```
35#[derive(Debug, Clone, PartialEq, Eq)]
36#[cfg_attr(feature = "serde", derive(serde::Serialize))]
37pub struct Font<'a> {
38    /// Font filename as it appears in the `[Fonts\]` section
39    pub filename: &'a str,
40
41    /// UU-encoded font data lines as zero-copy spans
42    pub data_lines: Vec<&'a str>,
43
44    /// Span in source text where this font is defined
45    pub span: Span,
46}
47
48impl Font<'_> {
49    /// Decode UU-encoded font data with lazy evaluation
50    ///
51    /// Converts the UU-encoded data lines to raw binary font data.
52    /// This is expensive so it's only done when explicitly requested.
53    ///
54    /// # Returns
55    ///
56    /// Decoded binary font data on success, error if UU-decoding fails
57    ///
58    /// # Errors
59    ///
60    /// Returns an error if the UU-encoded data is malformed or cannot be decoded.
61    ///
62    /// # Examples
63    ///
64    /// ```rust
65    /// # use ass_core::parser::ast::{Font, Span};
66    /// # let font = Font { filename: "test.ttf", data_lines: vec![], span: Span::new(0, 0, 0, 0) };
67    /// match font.decode_data() {
68    ///     Ok(data) => println!("Font size: {} bytes", data.len()),
69    ///     Err(e) => eprintln!("Decode error: {}", e),
70    /// }
71    /// ```
72    pub fn decode_data(&self) -> Result<Vec<u8>, crate::utils::CoreError> {
73        crate::utils::decode_uu_data(self.data_lines.iter().copied())
74    }
75
76    /// Convert font to ASS string representation
77    ///
78    /// Generates the font entry as it appears in the `[Fonts\]` section.
79    ///
80    /// # Examples
81    ///
82    /// ```rust
83    /// # use ass_core::parser::ast::{Font, Span};
84    /// let font = Font {
85    ///     filename: "custom.ttf",
86    ///     data_lines: vec!["begin 644 custom.ttf", "M'XL...", "end"],
87    ///     span: Span::new(0, 0, 0, 0),
88    /// };
89    /// let ass_string = font.to_ass_string();
90    /// assert!(ass_string.starts_with("fontname: custom.ttf\n"));
91    /// assert!(ass_string.contains("M'XL..."));
92    /// ```
93    #[must_use]
94    pub fn to_ass_string(&self) -> alloc::string::String {
95        let mut result = format!("fontname: {}\n", self.filename);
96        for line in &self.data_lines {
97            result.push_str(line);
98            result.push('\n');
99        }
100        result
101    }
102
103    /// Validate all spans in this Font reference valid source
104    ///
105    /// Debug helper to ensure zero-copy invariants are maintained.
106    /// Validates that filename and all data line references point to
107    /// memory within the specified source range.
108    ///
109    /// Only available in debug builds to avoid performance overhead.
110    #[cfg(debug_assertions)]
111    #[must_use]
112    pub fn validate_spans(&self, source_range: &Range<usize>) -> bool {
113        let filename_ptr = self.filename.as_ptr() as usize;
114        let filename_valid = source_range.contains(&filename_ptr);
115
116        let data_valid = self.data_lines.iter().all(|line| {
117            let ptr = line.as_ptr() as usize;
118            source_range.contains(&ptr)
119        });
120
121        filename_valid && data_valid
122    }
123}