Skip to main content

ass_core/analysis/events/text_analysis/
helpers.rs

1//! Accessors and Unicode helpers for [`TextAnalysis`].
2//!
3//! Provides the read-only getters over the analysis result plus the internal
4//! helpers used by the parser for drawing-mode tracking, line counting, and
5//! Unicode complexity detection.
6
7use super::TextAnalysis;
8use crate::analysis::events::tags::{OverrideTag, TagDiagnostic};
9use alloc::{string::String, vec::Vec};
10
11impl<'a> TextAnalysis<'a> {
12    /// Get plain text without override tags
13    #[must_use]
14    pub fn plain_text(&self) -> &str {
15        &self.plain_text
16    }
17
18    /// Get Unicode character count
19    #[must_use]
20    pub const fn char_count(&self) -> usize {
21        self.char_count
22    }
23
24    /// Get line count after processing linebreaks
25    #[must_use]
26    pub const fn line_count(&self) -> usize {
27        self.line_count
28    }
29
30    /// Check if text contains bidirectional content
31    #[must_use]
32    pub const fn has_bidi_text(&self) -> bool {
33        self.has_bidi_text
34    }
35
36    /// Check if text contains complex Unicode beyond basic Latin
37    #[must_use]
38    pub const fn has_complex_unicode(&self) -> bool {
39        self.has_complex_unicode
40    }
41
42    /// Get parsed override tags
43    #[must_use]
44    pub fn override_tags(&self) -> &[OverrideTag<'a>] {
45        &self.override_tags
46    }
47
48    /// Get parse diagnostics collected during analysis
49    #[must_use]
50    pub fn diagnostics(&self) -> &[TagDiagnostic<'a>] {
51        &self.parse_diagnostics
52    }
53
54    /// Update drawing mode state based on override tag content
55    pub(super) fn update_drawing_mode(tag_content: &str, current_mode: bool) -> bool {
56        let mut pos = 0;
57        let chars: Vec<char> = tag_content.chars().collect();
58        let mut drawing_mode = current_mode;
59
60        while pos < chars.len() {
61            if chars[pos] == '\\' && pos + 1 < chars.len() && chars[pos + 1] == 'p' {
62                pos += 2;
63                let mut number_str = String::new();
64
65                while pos < chars.len() && (chars[pos].is_ascii_digit() || chars[pos] == '-') {
66                    number_str.push(chars[pos]);
67                    pos += 1;
68                }
69
70                if let Ok(p_value) = number_str.parse::<i32>() {
71                    drawing_mode = p_value > 0;
72                }
73            } else {
74                pos += 1;
75            }
76        }
77
78        drawing_mode
79    }
80
81    /// Count lines correctly, handling empty lines and trailing newlines
82    pub(super) fn count_lines(text: &str) -> usize {
83        if text.is_empty() {
84            return 1;
85        }
86
87        // For ASS subtitles, count newlines and add 1, but handle special cases
88        let newline_count = text.chars().filter(|&ch| ch == '\n').count();
89
90        if newline_count == 0 {
91            // No newlines means 1 line
92            1
93        } else if text.trim_end_matches('\n').is_empty() {
94            // Text is only newlines - each newline creates a line boundary
95            newline_count + 1
96        } else {
97            // Text has content - trailing newlines don't create additional lines
98            // Use lines() count which handles this correctly
99            text.lines().count().max(1)
100        }
101    }
102
103    /// Detect bidirectional text (RTL scripts)
104    pub(super) fn detect_bidi_text(text: &str) -> bool {
105        text.chars().any(|ch| matches!(ch as u32, 0x0590..=0x05FF | 0x0600..=0x06FF | 0x0750..=0x077F | 0x08A0..=0x08FF))
106    }
107
108    /// Detect complex Unicode beyond basic Latin
109    pub(super) fn detect_complex_unicode(text: &str) -> bool {
110        text.chars().any(|ch| {
111            let code = ch as u32;
112            code > 0x00FF || matches!(code, 0x0000..=0x001F | 0x007F..=0x009F | 0x200C..=0x200D | 0x2060..=0x206F)
113        })
114    }
115}