1#[cfg(not(feature = "std"))]
7extern crate alloc;
8
9#[cfg(not(feature = "std"))]
10use alloc::format;
11use alloc::vec::Vec;
12
13use super::Span;
14#[cfg(debug_assertions)]
15use core::ops::Range;
16
17#[derive(Debug, Clone, PartialEq, Eq)]
39pub struct Font<'a> {
40 pub filename: &'a str,
42
43 pub data_lines: Vec<&'a str>,
45
46 pub span: Span,
48}
49
50impl Font<'_> {
51 pub fn decode_data(&self) -> Result<Vec<u8>, crate::utils::CoreError> {
75 crate::utils::decode_uu_data(self.data_lines.iter().copied())
76 }
77
78 #[must_use]
96 pub fn to_ass_string(&self) -> alloc::string::String {
97 let mut result = format!("fontname: {}\n", self.filename);
98 for line in &self.data_lines {
99 result.push_str(line);
100 result.push('\n');
101 }
102 result
103 }
104
105 #[cfg(debug_assertions)]
113 #[must_use]
114 pub fn validate_spans(&self, source_range: &Range<usize>) -> bool {
115 let filename_ptr = self.filename.as_ptr() as usize;
116 let filename_valid = source_range.contains(&filename_ptr);
117
118 let data_valid = self.data_lines.iter().all(|line| {
119 let ptr = line.as_ptr() as usize;
120 source_range.contains(&ptr)
121 });
122
123 filename_valid && data_valid
124 }
125}
126
127#[derive(Debug, Clone, PartialEq, Eq)]
148pub struct Graphic<'a> {
149 pub filename: &'a str,
151
152 pub data_lines: Vec<&'a str>,
154
155 pub span: Span,
157}
158
159impl Graphic<'_> {
160 pub fn decode_data(&self) -> Result<Vec<u8>, crate::utils::CoreError> {
173 crate::utils::decode_uu_data(self.data_lines.iter().copied())
174 }
175
176 #[must_use]
194 pub fn to_ass_string(&self) -> alloc::string::String {
195 let mut result = format!("filename: {}\n", self.filename);
196 for line in &self.data_lines {
197 result.push_str(line);
198 result.push('\n');
199 }
200 result
201 }
202
203 #[cfg(debug_assertions)]
211 #[must_use]
212 pub fn validate_spans(&self, source_range: &Range<usize>) -> bool {
213 let filename_ptr = self.filename.as_ptr() as usize;
214 let filename_valid = source_range.contains(&filename_ptr);
215
216 let data_valid = self.data_lines.iter().all(|line| {
217 let ptr = line.as_ptr() as usize;
218 source_range.contains(&ptr)
219 });
220
221 filename_valid && data_valid
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228 #[cfg(not(feature = "std"))]
229 use alloc::{format, vec};
230
231 #[test]
232 fn font_creation() {
233 let font = Font {
234 filename: "test.ttf",
235 data_lines: vec!["line1", "line2"],
236 span: Span::new(0, 0, 0, 0),
237 };
238
239 assert_eq!(font.filename, "test.ttf");
240 assert_eq!(font.data_lines.len(), 2);
241 assert_eq!(font.data_lines[0], "line1");
242 assert_eq!(font.data_lines[1], "line2");
243 }
244
245 #[test]
246 fn graphic_creation() {
247 let graphic = Graphic {
248 filename: "logo.png",
249 data_lines: vec!["data1", "data2", "data3"],
250 span: Span::new(0, 0, 0, 0),
251 };
252
253 assert_eq!(graphic.filename, "logo.png");
254 assert_eq!(graphic.data_lines.len(), 3);
255 assert_eq!(graphic.data_lines[0], "data1");
256 }
257
258 #[test]
259 fn font_clone_eq() {
260 let font = Font {
261 filename: "test.ttf",
262 data_lines: vec!["data"],
263 span: Span::new(0, 0, 0, 0),
264 };
265
266 let cloned = font.clone();
267 assert_eq!(font, cloned);
268 }
269
270 #[test]
271 fn graphic_clone_eq() {
272 let graphic = Graphic {
273 filename: "test.png",
274 data_lines: vec!["data"],
275 span: Span::new(0, 0, 0, 0),
276 };
277
278 let cloned = graphic.clone();
279 assert_eq!(graphic, cloned);
280 }
281
282 #[test]
283 fn font_debug() {
284 let font = Font {
285 filename: "debug.ttf",
286 data_lines: vec!["test"],
287 span: Span::new(0, 0, 0, 0),
288 };
289
290 let debug_str = format!("{font:?}");
291 assert!(debug_str.contains("Font"));
292 assert!(debug_str.contains("debug.ttf"));
293 }
294
295 #[test]
296 fn graphic_debug() {
297 let graphic = Graphic {
298 filename: "debug.png",
299 data_lines: vec!["test"],
300 span: Span::new(0, 0, 0, 0),
301 };
302
303 let debug_str = format!("{graphic:?}");
304 assert!(debug_str.contains("Graphic"));
305 assert!(debug_str.contains("debug.png"));
306 }
307
308 #[test]
309 fn empty_data_lines() {
310 let font = Font {
311 filename: "empty.ttf",
312 data_lines: Vec::new(),
313 span: Span::new(0, 0, 0, 0),
314 };
315
316 let graphic = Graphic {
317 filename: "empty.png",
318 data_lines: Vec::new(),
319 span: Span::new(0, 0, 0, 0),
320 };
321
322 assert!(font.data_lines.is_empty());
323 assert!(graphic.data_lines.is_empty());
324 }
325
326 #[test]
327 fn media_inequality() {
328 let font1 = Font {
329 filename: "font1.ttf",
330 data_lines: vec!["data"],
331 span: Span::new(0, 0, 0, 0),
332 };
333
334 let font2 = Font {
335 filename: "font2.ttf",
336 data_lines: vec!["data"],
337 span: Span::new(0, 0, 0, 0),
338 };
339
340 assert_ne!(font1, font2);
341 }
342
343 #[test]
344 fn font_decode_data_valid() {
345 let font = Font {
347 filename: "test.ttf",
348 data_lines: vec!["#0V%T", "`"],
349 span: Span::new(0, 0, 0, 0),
350 };
351 let decoded = font.decode_data().unwrap();
352 assert_eq!(decoded, b"Cat");
353 }
354
355 #[test]
356 fn font_decode_data_empty_lines() {
357 let font = Font {
358 filename: "test.ttf",
359 data_lines: vec![],
360 span: Span::new(0, 0, 0, 0),
361 };
362 let decoded = font.decode_data().unwrap();
363 assert!(decoded.is_empty());
364 }
365
366 #[test]
367 fn font_decode_data_whitespace_lines() {
368 let font = Font {
369 filename: "test.ttf",
370 data_lines: vec![" ", "\t\n", ""],
371 span: Span::new(0, 0, 0, 0),
372 };
373 let decoded = font.decode_data().unwrap();
374 assert!(decoded.is_empty());
375 }
376
377 #[test]
378 fn font_decode_data_with_end_marker() {
379 let font = Font {
380 filename: "test.ttf",
381 data_lines: vec!["#0V%T", "end"],
382 span: Span::new(0, 0, 0, 0),
383 };
384 let decoded = font.decode_data().unwrap();
385 assert_eq!(decoded, b"Cat");
386 }
387
388 #[test]
389 fn font_decode_data_zero_length_line() {
390 let font = Font {
391 filename: "test.ttf",
392 data_lines: vec!["#0V%T", " "],
393 span: Span::new(0, 0, 0, 0),
394 };
395 let decoded = font.decode_data().unwrap();
396 assert_eq!(decoded, b"Cat");
397 }
398
399 #[test]
400 fn font_decode_data_multiline() {
401 let font = Font {
403 filename: "test.ttf",
404 data_lines: vec!["$4F3\"", "$4F3\""],
405 span: Span::new(0, 0, 0, 0),
406 };
407 let decoded = font.decode_data().unwrap();
408 assert_eq!(decoded.len(), 6); }
411
412 #[test]
413 fn graphic_decode_data_valid() {
414 let graphic = Graphic {
416 filename: "test.png",
417 data_lines: vec!["#4$Y'"],
418 span: Span::new(0, 0, 0, 0),
419 };
420 let decoded = graphic.decode_data().unwrap();
421 assert_eq!(decoded, b"PNG");
422 }
423
424 #[test]
425 fn graphic_decode_data_empty_lines() {
426 let graphic = Graphic {
427 filename: "test.png",
428 data_lines: vec![],
429 span: Span::new(0, 0, 0, 0),
430 };
431 let decoded = graphic.decode_data().unwrap();
432 assert!(decoded.is_empty());
433 }
434
435 #[test]
436 fn graphic_decode_data_with_end_marker() {
437 let graphic = Graphic {
438 filename: "test.png",
439 data_lines: vec!["#4$Y'", "end"],
440 span: Span::new(0, 0, 0, 0),
441 };
442 let decoded = graphic.decode_data().unwrap();
443 assert_eq!(decoded, b"PNG");
444 }
445
446 #[test]
447 fn graphic_decode_data_whitespace_handling() {
448 let graphic = Graphic {
449 filename: "test.png",
450 data_lines: vec!["#4$Y' ", "\t\n", ""],
451 span: Span::new(0, 0, 0, 0),
452 };
453 let decoded = graphic.decode_data().unwrap();
454 assert_eq!(decoded, b"PNG");
455 }
456
457 #[test]
458 fn font_decode_data_handles_malformed_gracefully() {
459 let font = Font {
461 filename: "test.ttf",
462 data_lines: vec!["invalid-characters-here"],
463 span: Span::new(0, 0, 0, 0),
464 };
465 let _result = font.decode_data();
467 }
468
469 #[test]
470 fn graphic_decode_data_handles_malformed_gracefully() {
471 let graphic = Graphic {
473 filename: "test.png",
474 data_lines: vec!["!@#$%^&*()"],
475 span: Span::new(0, 0, 0, 0),
476 };
477 let _result = graphic.decode_data();
479 }
480
481 #[test]
482 fn font_decode_data_length_validation() {
483 let font = Font {
485 filename: "test.ttf",
486 data_lines: vec!["! "], span: Span::new(0, 0, 0, 0),
488 };
489 let decoded = font.decode_data().unwrap();
490 assert_eq!(decoded.len(), 1); }
492
493 #[test]
494 fn graphic_decode_data_length_validation() {
495 let graphic = Graphic {
497 filename: "test.png",
498 data_lines: vec!["\"````"], span: Span::new(0, 0, 0, 0),
500 };
501 let decoded = graphic.decode_data().unwrap();
502 assert_eq!(decoded.len(), 2); }
504
505 #[cfg(debug_assertions)]
506 #[test]
507 fn font_validate_spans() {
508 let source = "fontname: test.ttf\ndata1\ndata2";
509 let font = Font {
510 filename: &source[10..18], data_lines: vec![&source[19..24], &source[25..30]], span: Span::new(0, 0, 0, 0),
513 };
514
515 let source_range = (source.as_ptr() as usize)..(source.as_ptr() as usize + source.len());
516 assert!(font.validate_spans(&source_range));
517 }
518
519 #[cfg(debug_assertions)]
520 #[test]
521 fn graphic_validate_spans() {
522 let source = "filename: logo.png\nimage1\nimage2";
523 let graphic = Graphic {
524 filename: &source[10..18], data_lines: vec![&source[19..25], &source[26..32]], span: Span::new(0, 0, 0, 0),
527 };
528
529 let source_range = (source.as_ptr() as usize)..(source.as_ptr() as usize + source.len());
530 assert!(graphic.validate_spans(&source_range));
531 }
532
533 #[cfg(debug_assertions)]
534 #[test]
535 fn font_validate_spans_invalid() {
536 let source1 = "fontname: test.ttf";
537 let source2 = "different source";
538
539 let font = Font {
540 filename: &source1[10..18], data_lines: vec![&source2[0..9]], span: Span::new(0, 0, 0, 0),
543 };
544
545 let source1_range =
546 (source1.as_ptr() as usize)..(source1.as_ptr() as usize + source1.len());
547 assert!(!font.validate_spans(&source1_range)); }
549}