1#[cfg(not(feature = "std"))]
24use alloc::{format, string::String, vec::Vec};
25use core::{fmt, ops::Range};
26#[cfg(feature = "std")]
27use std::{format, string::String, vec::Vec};
28
29pub mod benchmark_generators;
30pub mod errors;
31pub mod hashers;
32pub mod utf8;
33
34pub use benchmark_generators::{
35 create_test_event, generate_overlapping_script, generate_script_with_issues, ComplexityLevel,
36 ScriptGenerator,
37};
38pub use errors::CoreError;
39pub use hashers::{create_hash_map, create_hash_map_with_capacity, create_hasher, hash_value};
40pub use utf8::{detect_encoding, normalize_line_endings, recover_utf8, strip_bom, validate_utf8};
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub struct Spans<'a> {
48 source: &'a str,
50}
51
52impl<'a> Spans<'a> {
53 #[must_use]
55 pub const fn new(source: &'a str) -> Self {
56 Self { source }
57 }
58
59 #[must_use]
64 pub fn validate_span(&self, span: &str) -> bool {
65 let source_start = self.source.as_ptr() as usize;
66 let source_end = source_start + self.source.len();
67
68 let span_start = span.as_ptr() as usize;
69 let span_end = span_start + span.len();
70
71 span_start >= source_start && span_end <= source_end
72 }
73
74 #[must_use]
76 pub fn span_offset(&self, span: &str) -> Option<usize> {
77 let source_start = self.source.as_ptr() as usize;
78 let span_start = span.as_ptr() as usize;
79
80 if self.validate_span(span) {
81 Some(span_start - source_start)
82 } else {
83 None
84 }
85 }
86
87 #[must_use]
89 pub fn span_line(&self, span: &str) -> Option<usize> {
90 let offset = self.span_offset(span)?;
91 Some(self.source[..offset].chars().filter(|&c| c == '\n').count() + 1)
92 }
93
94 #[must_use]
96 pub fn span_column(&self, span: &str) -> Option<usize> {
97 let offset = self.span_offset(span)?;
98 let line_start = self.source[..offset].rfind('\n').map_or(0, |pos| pos + 1);
99
100 Some(self.source[line_start..offset].chars().count() + 1)
101 }
102
103 #[must_use]
105 pub fn substring(&self, range: Range<usize>) -> Option<&'a str> {
106 self.source.get(range)
107 }
108}
109
110pub fn parse_bgr_color(color_str: &str) -> Result<[u8; 4], CoreError> {
137 let trimmed = color_str.trim();
138
139 let hex_part =
140 if (trimmed.starts_with("&H") || trimmed.starts_with("&h")) && trimmed.ends_with('&') {
141 &trimmed[2..trimmed.len() - 1]
142 } else if let Some(stripped) = trimmed.strip_prefix("&H") {
143 stripped
144 } else if let Some(stripped) = trimmed.strip_prefix("&h") {
145 stripped
146 } else if let Some(stripped) = trimmed.strip_prefix("0x") {
147 stripped
148 } else if trimmed.chars().all(|c| c.is_ascii_hexdigit()) {
149 trimmed
150 } else {
151 return Err(CoreError::InvalidColor(format!(
152 "Invalid color format: {color_str}"
153 )));
154 };
155
156 let hex_value = u32::from_str_radix(hex_part, 16)
157 .map_err(|_| CoreError::InvalidColor(format!("Invalid hex value: {hex_part}")))?;
158
159 let color_array = match hex_part.len() {
160 6 => {
161 let blue = ((hex_value >> 16) & 0xFF) as u8;
162 let green = ((hex_value >> 8) & 0xFF) as u8;
163 let red = (hex_value & 0xFF) as u8;
164 [red, green, blue, 0]
165 }
166 8 => {
167 let alpha = ((hex_value >> 24) & 0xFF) as u8;
168 let blue = ((hex_value >> 16) & 0xFF) as u8;
169 let green = ((hex_value >> 8) & 0xFF) as u8;
170 let red = (hex_value & 0xFF) as u8;
171 [red, green, blue, alpha]
172 }
173 _ => {
174 return Err(CoreError::InvalidColor(format!(
175 "Invalid color length: {}",
176 hex_part.len()
177 )))
178 }
179 };
180
181 Ok(color_array)
182}
183
184pub fn parse_numeric<T>(value_str: &str) -> Result<T, CoreError>
193where
194 T: core::str::FromStr,
195 T::Err: fmt::Display,
196{
197 value_str
198 .trim()
199 .parse()
200 .map_err(|e| CoreError::InvalidNumeric(format!("Failed to parse '{value_str}': {e}")))
201}
202
203#[must_use]
217pub fn eval_cubic_bezier(
218 p0: (f32, f32),
219 p1: (f32, f32),
220 p2: (f32, f32),
221 p3: (f32, f32),
222 t: f32,
223) -> (f32, f32) {
224 let t2 = t * t;
225 let t3 = t2 * t;
226 let mt = 1.0 - t;
227 let mt2 = mt * mt;
228 let mt3 = mt2 * mt;
229
230 let x = t3.mul_add(
231 p3.0,
232 (3.0 * mt * t2).mul_add(p2.0, mt3.mul_add(p0.0, 3.0 * mt2 * t * p1.0)),
233 );
234 let y = t3.mul_add(
235 p3.1,
236 (3.0 * mt * t2).mul_add(p2.1, mt3.mul_add(p0.1, 3.0 * mt2 * t * p1.1)),
237 );
238
239 (x, y)
240}
241
242pub fn parse_ass_time(time_str: &str) -> Result<u32, CoreError> {
259 let parts: Vec<&str> = time_str.split(':').collect();
260 if parts.len() != 3 {
261 return Err(CoreError::InvalidTime(format!(
262 "Invalid time format: {time_str}"
263 )));
264 }
265
266 let hours: u32 = parts[0]
267 .parse()
268 .map_err(|_| CoreError::InvalidTime(format!("Invalid hours: {}", parts[0])))?;
269
270 let minutes: u32 = parts[1]
271 .parse()
272 .map_err(|_| CoreError::InvalidTime(format!("Invalid minutes: {}", parts[1])))?;
273
274 let seconds_parts: Vec<&str> = parts[2].split('.').collect();
275 let seconds: u32 = seconds_parts[0]
276 .parse()
277 .map_err(|_| CoreError::InvalidTime(format!("Invalid seconds: {}", seconds_parts[0])))?;
278
279 let centiseconds = if seconds_parts.len() > 1 {
280 let frac_str = &seconds_parts[1];
281 let frac_val: u32 = frac_str
282 .parse()
283 .map_err(|_| CoreError::InvalidTime(format!("Invalid centiseconds: {frac_str}")))?;
284
285 match frac_str.len() {
286 1 => frac_val * 10,
287 2 => frac_val,
288 _ => {
289 return Err(CoreError::InvalidTime(format!(
290 "Too many decimal places: {frac_str}"
291 )))
292 }
293 }
294 } else {
295 0
296 };
297
298 if minutes >= 60 {
299 return Err(CoreError::InvalidTime(format!(
300 "Minutes must be < 60: {minutes}"
301 )));
302 }
303 if seconds >= 60 {
304 return Err(CoreError::InvalidTime(format!(
305 "Seconds must be < 60: {seconds}"
306 )));
307 }
308 if centiseconds >= 100 {
309 return Err(CoreError::InvalidTime(format!(
310 "Centiseconds must be < 100: {centiseconds}"
311 )));
312 }
313
314 Ok(hours * 360_000 + minutes * 6_000 + seconds * 100 + centiseconds)
315}
316
317#[must_use]
321pub fn format_ass_time(centiseconds: u32) -> String {
322 let hours = centiseconds / 360_000;
323 let remainder = centiseconds % 360_000;
324 let minutes = remainder / 6000;
325 let remainder = remainder % 6000;
326 let seconds = remainder / 100;
327 let cs = remainder % 100;
328
329 format!("{hours}:{minutes:02}:{seconds:02}.{cs:02}")
330}
331
332#[must_use]
337pub fn normalize_field_value(value: &str) -> &str {
338 value.trim()
339}
340
341#[must_use]
345pub fn validate_ass_name(name: &str) -> bool {
346 !name.is_empty()
347 && !name.contains(',') && !name.contains(':') && !name.contains('{') && !name.contains('}') && name.chars().all(|c| !c.is_control() || c == '\t')
352}
353
354#[allow(clippy::similar_names)]
382pub fn decode_uu_data<'a, I>(lines: I) -> Result<Vec<u8>, CoreError>
383where
384 I: Iterator<Item = &'a str>,
385{
386 let mut result = Vec::new();
387
388 for line in lines {
389 let line = line.trim_start().trim_end_matches(['\n', '\r']);
390 if line.is_empty() {
391 continue;
392 }
393
394 if line == "end" || line.starts_with("end ") {
396 break;
397 }
398
399 let input_bytes = line.as_bytes();
400 if input_bytes.is_empty() {
401 continue;
402 }
403
404 let expected_length = (input_bytes[0].wrapping_sub(b' ')) as usize;
406
407 if expected_length > 45 {
410 continue;
411 }
412
413 if expected_length == 0 {
415 break;
416 }
417
418 let data_part = &input_bytes[1..];
419 let mut decoded_bytes = Vec::new();
420
421 for chunk in data_part.chunks(4) {
423 let mut group = [b' '; 4];
424 for (i, &byte) in chunk.iter().enumerate() {
425 group[i] = byte;
426 }
427
428 let c1 = group[0].wrapping_sub(b' ');
430 let c2 = group[1].wrapping_sub(b' ');
431 let c3 = group[2].wrapping_sub(b' ');
432 let c4 = group[3].wrapping_sub(b' ');
433
434 let decoded_byte1 = (c1 << 2) | (c2 >> 4);
435 let decoded_byte2 = ((c2 & 0x0F) << 4) | (c3 >> 2);
436 let decoded_byte3 = ((c3 & 0x03) << 6) | c4;
437
438 decoded_bytes.push(decoded_byte1);
440 decoded_bytes.push(decoded_byte2);
441 decoded_bytes.push(decoded_byte3);
442 }
443
444 decoded_bytes.truncate(expected_length);
446 result.extend_from_slice(&decoded_bytes);
447 }
448 Ok(result)
449}
450
451#[cfg(test)]
452mod tests {
453 use super::*;
454 #[cfg(not(feature = "std"))]
455 use alloc::vec;
456
457 #[test]
458 fn spans_validation() {
459 let source = "Hello, World!";
460 let spans = Spans::new(source);
461
462 let valid_span = &source[0..5]; assert!(spans.validate_span(valid_span));
464 assert_eq!(spans.span_offset(valid_span), Some(0));
465 assert_eq!(spans.span_line(valid_span), Some(1));
466 assert_eq!(spans.span_column(valid_span), Some(1));
467
468 let another_span = &source[7..12]; assert!(spans.validate_span(another_span));
470 assert_eq!(spans.span_offset(another_span), Some(7));
471 }
472
473 #[test]
474 fn spans_multiline() {
475 let source = "Line 1\nLine 2\nLine 3";
476 let spans = Spans::new(source);
477
478 let line2_span = &source[7..13]; assert_eq!(spans.span_line(line2_span), Some(2));
480 assert_eq!(spans.span_column(line2_span), Some(1));
481 }
482
483 #[test]
484 fn parse_bgr_colors() {
485 assert_eq!(parse_bgr_color("&H000000FF&").unwrap(), [255, 0, 0, 0]);
486 assert_eq!(parse_bgr_color("&H0000FF00&").unwrap(), [0, 255, 0, 0]);
487 assert_eq!(parse_bgr_color("&H00FF0000&").unwrap(), [0, 0, 255, 0]);
488
489 assert_eq!(parse_bgr_color("&HFF000000&").unwrap(), [0, 0, 0, 255]);
490
491 assert_eq!(parse_bgr_color("0x000000FF").unwrap(), [255, 0, 0, 0]);
492 assert_eq!(parse_bgr_color("000000FF").unwrap(), [255, 0, 0, 0]);
493 }
494
495 #[test]
496 fn parse_bgr_colors_invalid() {
497 assert!(parse_bgr_color("invalid").is_err());
498 assert!(parse_bgr_color("&HZZZZ&").is_err());
499 assert!(parse_bgr_color("").is_err());
500 }
501
502 #[test]
503 fn parse_bgr_colors_without_trailing_ampersand() {
504 assert_eq!(parse_bgr_color("&H000000FF").unwrap(), [255, 0, 0, 0]);
505 assert_eq!(parse_bgr_color("&H00FFFFFF").unwrap(), [255, 255, 255, 0]);
506 assert_eq!(parse_bgr_color("&H00000000").unwrap(), [0, 0, 0, 0]);
507 assert_eq!(parse_bgr_color("&HFF000000").unwrap(), [0, 0, 0, 255]);
508 }
509
510 #[test]
511 fn parse_ass_times() {
512 assert_eq!(parse_ass_time("0:00:00.00").unwrap(), 0);
513 assert_eq!(parse_ass_time("0:00:01.00").unwrap(), 100);
514 assert_eq!(parse_ass_time("0:01:00.00").unwrap(), 6000);
515 assert_eq!(parse_ass_time("1:00:00.00").unwrap(), 360_000);
516 assert_eq!(parse_ass_time("0:01:30.50").unwrap(), 9050);
517 }
518
519 #[test]
520 fn parse_ass_times_invalid() {
521 assert!(parse_ass_time("invalid").is_err());
522 assert!(parse_ass_time("0:60:00.00").is_err()); assert!(parse_ass_time("0:00:60.00").is_err()); assert!(parse_ass_time("0:00:00.100").is_err()); }
526
527 #[test]
528 fn format_ass_times() {
529 assert_eq!(format_ass_time(0), "0:00:00.00");
530 assert_eq!(format_ass_time(100), "0:00:01.00");
531 assert_eq!(format_ass_time(6000), "0:01:00.00");
532 assert_eq!(format_ass_time(360_000), "1:00:00.00");
533 assert_eq!(format_ass_time(9050), "0:01:30.50");
534 }
535
536 #[test]
537 fn bezier_evaluation() {
538 let p0 = (0.0, 0.0);
539 let p1 = (0.33, 0.0);
540 let p2 = (0.67, 1.0);
541 let p3 = (1.0, 1.0);
542
543 let start = eval_cubic_bezier(p0, p1, p2, p3, 0.0);
544 assert_eq!(start, p0);
545
546 let end = eval_cubic_bezier(p0, p1, p2, p3, 1.0);
547 assert_eq!(end, p3);
548
549 let mid = eval_cubic_bezier(p0, p1, p2, p3, 0.5);
550 assert!(mid.0 > 0.0 && mid.0 < 1.0);
551 assert!(mid.1 > 0.0 && mid.1 < 1.0);
552 }
553
554 #[test]
555 fn validate_ass_names() {
556 assert!(validate_ass_name("Default"));
557 assert!(validate_ass_name("MyStyle"));
558 assert!(validate_ass_name("Style with spaces"));
559
560 assert!(!validate_ass_name("")); assert!(!validate_ass_name("Style,Name")); assert!(!validate_ass_name("Style:Name")); assert!(!validate_ass_name("Style{Name")); assert!(!validate_ass_name("Style\nName")); }
566
567 #[test]
568 fn normalize_field_values() {
569 assert_eq!(normalize_field_value(" value "), "value");
570 assert_eq!(normalize_field_value("\tvalue\t"), "value");
571 assert_eq!(normalize_field_value("value"), "value");
572 }
573
574 #[test]
575 fn numeric_parsing() {
576 assert_eq!(parse_numeric::<i32>("42").unwrap(), 42);
577 assert!((parse_numeric::<f32>("3.15").unwrap() - 3.15).abs() < f32::EPSILON);
578 assert!(parse_numeric::<i32>("invalid").is_err());
579 }
580
581 #[test]
582 fn decode_uu_data_empty_input() {
583 let lines: Vec<&str> = vec![];
584 let decoded = decode_uu_data(lines.iter().copied()).unwrap();
585 assert_eq!(decoded, Vec::<u8>::new());
586 }
587
588 #[test]
589 fn decode_uu_data_known_encoding() {
590 let lines = ["#0V%T"];
592 let decoded = decode_uu_data(lines.iter().copied()).unwrap();
593 assert_eq!(decoded, b"Cat");
594 }
595
596 #[test]
597 fn decode_uu_data_known_encoding_png() {
598 let lines = ["#4$Y'"];
600 let decoded = decode_uu_data(lines.iter().copied()).unwrap();
601 assert_eq!(decoded, b"PNG");
602 }
603
604 #[test]
605 fn decode_uu_data_multiline() {
606 let lines = ["#0V%T", "#0V%T"];
608 let decoded = decode_uu_data(lines.iter().copied()).unwrap();
609 assert_eq!(decoded, b"CatCat");
610 }
611
612 #[test]
613 fn decode_uu_data_with_end_marker() {
614 let lines = ["#0V%T", "end"];
615 let decoded = decode_uu_data(lines.iter().copied()).unwrap();
616 assert_eq!(decoded, b"Cat");
617 }
618
619 #[test]
620 fn decode_uu_data_with_end_marker_spaced() {
621 let lines = ["#0V%T", "end 644"];
622 let decoded = decode_uu_data(lines.iter().copied()).unwrap();
623 assert_eq!(decoded, b"Cat");
624 }
625
626 #[test]
627 fn decode_uu_data_zero_length_line() {
628 let lines = ["#0V%T", " "];
630 let decoded = decode_uu_data(lines.iter().copied()).unwrap();
631 assert_eq!(decoded, b"Cat");
632 }
633
634 #[test]
635 fn decode_uu_data_whitespace_lines() {
636 let lines = [" #0V%T ", "\t", ""];
637 let decoded = decode_uu_data(lines.iter().copied()).unwrap();
638 assert_eq!(decoded, b"Cat");
639 }
640
641 #[test]
642 fn decode_uu_data_length_validation() {
643 let lines = ["! "]; let decoded = decode_uu_data(lines.iter().copied()).unwrap();
646 assert_eq!(decoded.len(), 1); }
648
649 #[test]
650 fn decode_uu_data_partial_chunks() {
651 let lines = ["\"``"]; let decoded = decode_uu_data(lines.iter().copied()).unwrap();
654 assert_eq!(decoded.len(), 2); }
656
657 #[test]
658 fn decode_uu_data_large_line() {
659 let line = format!("M{}", "!!!!".repeat(15)); let lines = [line.as_str()];
662 let decoded = decode_uu_data(lines.iter().copied()).unwrap();
663 assert_eq!(decoded.len(), 45);
664 }
665
666 #[test]
667 fn decode_uu_data_mixed_content() {
668 let lines = [
669 "begin 644 test.txt", "#0V%T", "| comment", "#4$Y'", "end", ];
675 let decoded = decode_uu_data(lines.iter().copied()).unwrap();
676 assert_eq!(decoded, b"CatPNG");
677 }
678
679 #[test]
680 fn decode_uu_data_all_printable_chars() {
681 let lines = ["@ !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"];
683 let _decoded = decode_uu_data(lines.iter().copied()).unwrap();
684 }
686
687 #[test]
688 fn decode_uu_data_boundary_lengths() {
689 let single_byte = ["! "]; let two_bytes = ["\"`` "]; let three_bytes = ["#```"]; let decoded1 = decode_uu_data(single_byte.iter().copied()).unwrap();
695 assert_eq!(decoded1.len(), 1);
696
697 let decoded2 = decode_uu_data(two_bytes.iter().copied()).unwrap();
698 assert_eq!(decoded2.len(), 2);
699
700 let decoded3 = decode_uu_data(three_bytes.iter().copied()).unwrap();
701 assert_eq!(decoded3.len(), 3);
702 }
703
704 #[test]
705 fn decode_uu_data_handles_invalid_gracefully() {
706 let lines = ["#\x01\x02\x03"]; let _result = decode_uu_data(lines.iter().copied());
709 }
711
712 #[test]
713 fn parse_bgr_color_edge_cases() {
714 assert_eq!(parse_bgr_color("&h000000").unwrap(), [0, 0, 0, 0]);
716 assert_eq!(parse_bgr_color("&hFFFFFF").unwrap(), [255, 255, 255, 0]);
717
718 assert_eq!(parse_bgr_color("0x000000").unwrap(), [0, 0, 0, 0]);
720 assert_eq!(parse_bgr_color("0xFFFFFF").unwrap(), [255, 255, 255, 0]);
721
722 assert_eq!(parse_bgr_color("000000").unwrap(), [0, 0, 0, 0]);
724 assert_eq!(parse_bgr_color("FFFFFF").unwrap(), [255, 255, 255, 0]);
725
726 assert_eq!(parse_bgr_color(" &H000000 ").unwrap(), [0, 0, 0, 0]);
728 assert_eq!(parse_bgr_color("\t&H000000\t").unwrap(), [0, 0, 0, 0]);
729
730 assert_eq!(parse_bgr_color("&H000000&").unwrap(), [0, 0, 0, 0]);
732 assert_eq!(parse_bgr_color("&h000000&").unwrap(), [0, 0, 0, 0]);
733
734 assert_eq!(parse_bgr_color("&HaAbBcC").unwrap(), [204, 187, 170, 0]);
736 assert_eq!(parse_bgr_color("&HFFaaBBcc").unwrap(), [204, 187, 170, 255]);
737
738 assert!(parse_bgr_color("&H00000").is_err()); assert!(parse_bgr_color("&H0000000").is_err()); assert!(parse_bgr_color("&H000000000").is_err()); assert!(parse_bgr_color("&H00000G").is_err());
745 assert!(parse_bgr_color("&H00Z000").is_err());
746
747 assert!(parse_bgr_color("&H").is_err());
749 assert!(parse_bgr_color("0x").is_err());
750
751 assert!(parse_bgr_color("&H000000X").is_err());
753 assert!(parse_bgr_color("X&H000000").is_err());
754 }
755
756 #[test]
757 fn spans_edge_cases() {
758 let source = "line1\nline2\nline3";
759 let spans = Spans::new(source);
760
761 let line1 = &source[0..5]; let line2 = &source[6..11]; let line3 = &source[12..17]; assert!(spans.validate_span(line1));
767 assert!(spans.validate_span(line2));
768 assert!(spans.validate_span(line3));
769 assert!(spans.validate_span(source)); assert_eq!(spans.span_offset(line1), Some(0));
773 assert_eq!(spans.span_offset(line2), Some(6));
774 assert_eq!(spans.span_offset(line3), Some(12));
775
776 assert_eq!(spans.span_line(line1), Some(1));
778 assert_eq!(spans.span_line(line2), Some(2));
779 assert_eq!(spans.span_line(line3), Some(3));
780
781 assert_eq!(spans.span_column(line1), Some(1));
783 assert_eq!(spans.span_column(line2), Some(1));
784 assert_eq!(spans.span_column(line3), Some(1));
785
786 assert_eq!(spans.substring(0..5), Some("line1"));
788 assert_eq!(spans.substring(6..11), Some("line2"));
789 assert_eq!(spans.substring(12..17), Some("line3"));
790 assert_eq!(spans.substring(0..source.len()), Some(source));
791
792 assert_eq!(spans.substring(0..100), None);
794 }
795
796 #[test]
797 fn parse_ass_time_edge_cases() {
798 assert!(parse_ass_time("23:59:59.99").is_ok());
800
801 assert_eq!(parse_ass_time("0:0:0.0").unwrap(), 0);
803 assert_eq!(parse_ass_time("0:00:00.0").unwrap(), 0);
804 assert_eq!(parse_ass_time("0:00:00.00").unwrap(), 0);
805
806 assert!(parse_ass_time("0:00").is_err());
808 assert!(parse_ass_time("0").is_err());
809 assert!(parse_ass_time("").is_err());
810
811 assert!(parse_ass_time("0:0:0:0.0").is_err());
813 assert!(parse_ass_time("0:0:0.0.0").is_ok());
815
816 assert!(parse_ass_time("-1:00:00.00").is_err());
818 assert!(parse_ass_time("0:-1:00.00").is_err());
819 assert!(parse_ass_time("0:00:-1.00").is_err());
820 assert!(parse_ass_time("0:00:00.-1").is_err());
821
822 assert!(parse_ass_time("a:00:00.00").is_err());
824 assert!(parse_ass_time("0:b:00.00").is_err());
825 assert!(parse_ass_time("0:00:c.00").is_err());
826 assert!(parse_ass_time("0:00:00.d").is_err());
827
828 assert!(parse_ass_time("0:60:00.00").is_err()); assert!(parse_ass_time("0:00:60.00").is_err()); assert!(parse_ass_time("0:00:00.100").is_err()); }
833
834 #[test]
835 fn format_ass_time_edge_cases() {
836 assert_eq!(format_ass_time(u32::MAX), "11930:27:52.95");
838
839 assert_eq!(format_ass_time(99), "0:00:00.99");
841 assert_eq!(format_ass_time(5999), "0:00:59.99");
842 assert_eq!(format_ass_time(359_999), "0:59:59.99");
843
844 assert_eq!(format_ass_time(1), "0:00:00.01");
846 assert_eq!(format_ass_time(10), "0:00:00.10");
847 assert_eq!(format_ass_time(601), "0:00:06.01");
848 assert_eq!(format_ass_time(3661), "0:00:36.61");
849 }
850
851 #[test]
852 fn validate_ass_name_edge_cases() {
853 assert!(validate_ass_name("Style\tName"));
855
856 assert!(!validate_ass_name("Style\nName")); assert!(!validate_ass_name("Style\rName")); assert!(!validate_ass_name("Style\x00Name")); assert!(!validate_ass_name("Style\x7FName")); assert!(!validate_ass_name(",Style")); assert!(!validate_ass_name("Style,")); assert!(!validate_ass_name(":Style")); assert!(!validate_ass_name("Style:")); assert!(!validate_ass_name("{Style")); assert!(!validate_ass_name("Style}")); let long_name = "a".repeat(1000);
872 assert!(validate_ass_name(&long_name));
873
874 assert!(validate_ass_name("Style中文"));
876 assert!(validate_ass_name("Style🎭"));
877 assert!(validate_ass_name("Стиль"));
878 }
879
880 #[test]
881 fn normalize_field_value_edge_cases() {
882 assert_eq!(normalize_field_value(""), "");
884
885 assert_eq!(normalize_field_value(" "), "");
887 assert_eq!(normalize_field_value("\t\t\t"), "");
888 assert_eq!(normalize_field_value(" \t \t "), "");
889
890 assert_eq!(normalize_field_value(" \t value \t "), "value");
892 assert_eq!(normalize_field_value("\n\rvalue\n\r"), "value");
893
894 assert_eq!(normalize_field_value(" val ue "), "val ue");
896 assert_eq!(normalize_field_value(" val\tue "), "val\tue");
897 }
898
899 #[test]
900 #[allow(clippy::float_cmp, clippy::approx_constant)]
901 fn parse_numeric_edge_cases() {
902 assert_eq!(parse_numeric::<u8>("255").unwrap(), 255u8);
904 assert!(parse_numeric::<u8>("256").is_err());
905 assert_eq!(parse_numeric::<i8>("127").unwrap(), 127i8);
906 assert_eq!(parse_numeric::<i8>("-128").unwrap(), -128i8);
907 assert!(parse_numeric::<i8>("128").is_err());
908
909 assert_eq!(parse_numeric::<f32>("0.0").unwrap(), 0.0f32);
911 assert_eq!(parse_numeric::<f32>("-0.0").unwrap(), -0.0f32);
912 assert!(parse_numeric::<f32>("inf").is_ok());
913 assert!(parse_numeric::<f32>("-inf").is_ok());
914
915 assert_eq!(parse_numeric::<i32>(" 42 ").unwrap(), 42i32);
917 assert_eq!(parse_numeric::<f32>(" \t 3.14 \t ").unwrap(), 3.14f32);
918
919 assert_eq!(parse_numeric::<i32>("00042").unwrap(), 42i32);
921 assert_eq!(parse_numeric::<f32>("0003.140").unwrap(), 3.14f32);
922
923 assert_eq!(parse_numeric::<f32>("1e2").unwrap(), 100.0f32);
925 assert_eq!(parse_numeric::<f32>("1.5e-2").unwrap(), 0.015f32);
926
927 assert!(parse_numeric::<i32>("").is_err());
929 assert!(parse_numeric::<i32>("abc").is_err());
930 assert!(parse_numeric::<i32>("12.34").is_err()); assert!(parse_numeric::<f32>("12.34.56").is_err()); }
933
934 #[test]
935 fn eval_cubic_bezier_edge_cases() {
936 let linear_result = eval_cubic_bezier((0.0, 0.0), (0.0, 0.0), (1.0, 1.0), (1.0, 1.0), 0.5);
938 assert!((linear_result.0 - 0.5).abs() < f32::EPSILON);
939 assert!((linear_result.1 - 0.5).abs() < f32::EPSILON);
940
941 let p0 = (0.0, 0.0);
943 let p1 = (0.25, 0.5);
944 let p2 = (0.75, 0.5);
945 let p3 = (1.0, 1.0);
946
947 let result_0 = eval_cubic_bezier(p0, p1, p2, p3, 0.0);
949 assert!((result_0.0 - p0.0).abs() < f32::EPSILON);
950 assert!((result_0.1 - p0.1).abs() < f32::EPSILON);
951
952 let result_1 = eval_cubic_bezier(p0, p1, p2, p3, 1.0);
954 assert!((result_1.0 - p3.0).abs() < f32::EPSILON);
955 assert!((result_1.1 - p3.1).abs() < f32::EPSILON);
956
957 let neg_result = eval_cubic_bezier((-1.0, -1.0), (-0.5, -0.5), (0.5, 0.5), (1.0, 1.0), 0.5);
959 assert!(neg_result.0 > -1.0 && neg_result.0 < 1.0);
960 assert!(neg_result.1 > -1.0 && neg_result.1 < 1.0);
961
962 let large_result = eval_cubic_bezier(
964 (0.0, 0.0),
965 (1000.0, 1000.0),
966 (2000.0, 2000.0),
967 (3000.0, 3000.0),
968 0.5,
969 );
970 assert!(large_result.0 > 0.0 && large_result.0 < 3000.0);
971 assert!(large_result.1 > 0.0 && large_result.1 < 3000.0);
972 }
973
974 #[test]
975 fn decode_uu_data_error_conditions() {
976 let invalid_lines = ["invalid", "also invalid", "still invalid"];
978 let result = decode_uu_data(invalid_lines.iter().copied()).unwrap();
979 assert!(result.is_empty());
980
981 let malformed_length = ["\x7F!!!!"]; let _result = decode_uu_data(malformed_length.iter().copied());
984 let short_lines = ["!"]; let result = decode_uu_data(short_lines.iter().copied()).unwrap();
989 assert!(result.is_empty() || result.len() <= 1);
990
991 let unicode_lines = ["#🎭🎭🎭"];
993 let _result = decode_uu_data(unicode_lines.iter().copied());
994 }
996}