1use crate::core::errors::{EditorError, Result};
7use ass_core::parser::ast::EventType;
8use ass_core::ScriptVersion;
9
10#[cfg(feature = "std")]
11use std::borrow::Cow;
12
13#[cfg(not(feature = "std"))]
14use alloc::{borrow::Cow, format, string::ToString, vec};
15
16#[cfg(not(feature = "std"))]
17use alloc::{string::String, vec::Vec};
18
19#[derive(Debug, Default)]
46pub struct EventBuilder<'a> {
47 event_type: Option<EventType>,
48 start: Option<Cow<'a, str>>,
49 end: Option<Cow<'a, str>>,
50 style: Option<Cow<'a, str>>,
51 name: Option<Cow<'a, str>>,
52 text: Option<Cow<'a, str>>,
53 layer: Option<Cow<'a, str>>,
54 margin_l: Option<Cow<'a, str>>,
55 margin_r: Option<Cow<'a, str>>,
56 margin_v: Option<Cow<'a, str>>,
57 margin_t: Option<Cow<'a, str>>,
58 margin_b: Option<Cow<'a, str>>,
59 effect: Option<Cow<'a, str>>,
60}
61
62impl<'a> EventBuilder<'a> {
63 pub fn new() -> Self {
65 Self::default()
66 }
67
68 pub fn dialogue() -> Self {
70 Self {
71 event_type: Some(EventType::Dialogue),
72 ..Self::default()
73 }
74 }
75
76 pub fn comment() -> Self {
78 Self {
79 event_type: Some(EventType::Comment),
80 ..Self::default()
81 }
82 }
83
84 pub fn start_time<S: Into<Cow<'a, str>>>(mut self, time: S) -> Self {
86 self.start = Some(time.into());
87 self
88 }
89
90 pub fn end_time<S: Into<Cow<'a, str>>>(mut self, time: S) -> Self {
92 self.end = Some(time.into());
93 self
94 }
95
96 pub fn speaker<S: Into<Cow<'a, str>>>(mut self, name: S) -> Self {
98 self.name = Some(name.into());
99 self
100 }
101
102 pub fn text<S: Into<Cow<'a, str>>>(mut self, text: S) -> Self {
104 self.text = Some(text.into());
105 self
106 }
107
108 pub fn style<S: Into<Cow<'a, str>>>(mut self, style: S) -> Self {
110 self.style = Some(style.into());
111 self
112 }
113
114 pub fn layer(mut self, layer: u32) -> Self {
116 self.layer = Some(Cow::Owned(layer.to_string()));
117 self
118 }
119
120 pub fn margin_left(mut self, margin: u32) -> Self {
122 self.margin_l = Some(Cow::Owned(margin.to_string()));
123 self
124 }
125
126 pub fn margin_right(mut self, margin: u32) -> Self {
128 self.margin_r = Some(Cow::Owned(margin.to_string()));
129 self
130 }
131
132 pub fn margin_vertical(mut self, margin: u32) -> Self {
134 self.margin_v = Some(Cow::Owned(margin.to_string()));
135 self
136 }
137
138 pub fn margin_top(mut self, margin: u32) -> Self {
140 self.margin_t = Some(Cow::Owned(margin.to_string()));
141 self
142 }
143
144 pub fn margin_bottom(mut self, margin: u32) -> Self {
146 self.margin_b = Some(Cow::Owned(margin.to_string()));
147 self
148 }
149
150 pub fn effect<S: Into<Cow<'a, str>>>(mut self, effect: S) -> Self {
152 self.effect = Some(effect.into());
153 self
154 }
155
156 pub fn build(self) -> Result<String> {
158 self.build_with_version(ScriptVersion::AssV4)
160 }
161
162 pub fn build_with_version(self, version: ScriptVersion) -> Result<String> {
164 let event_type = self.event_type.unwrap_or(EventType::Dialogue);
165 let start = self.start.unwrap_or(Cow::Borrowed("0:00:00.00"));
166 let end = self.end.unwrap_or(Cow::Borrowed("0:00:05.00"));
167 let style = self.style.unwrap_or(Cow::Borrowed("Default"));
168 let name = self.name.unwrap_or(Cow::Borrowed(""));
169 let text = self.text.unwrap_or(Cow::Borrowed(""));
170 let layer = self.layer.unwrap_or(Cow::Borrowed("0"));
171 let margin_l = self.margin_l.unwrap_or(Cow::Borrowed("0"));
172 let margin_r = self.margin_r.unwrap_or(Cow::Borrowed("0"));
173 let margin_v = self.margin_v.unwrap_or(Cow::Borrowed("0"));
174 let effect = self.effect.unwrap_or(Cow::Borrowed(""));
175
176 let event_type_str = event_type.as_str();
178 let line = match version {
179 ScriptVersion::SsaV4 => {
180 format!(
182 "{event_type_str}: Marked=0,{start},{end},{style},{name},{margin_l},{margin_r},{margin_v},{effect},{text}"
183 )
184 }
185 ScriptVersion::AssV4 => {
186 format!(
188 "{event_type_str}: {layer},{start},{end},{style},{name},{margin_l},{margin_r},{margin_v},{effect},{text}"
189 )
190 }
191 ScriptVersion::AssV4Plus => {
192 format!(
195 "{event_type_str}: {layer},{start},{end},{style},{name},{margin_l},{margin_r},{margin_v},{effect},{text}"
196 )
197 }
198 };
199
200 Ok(line)
201 }
202
203 pub fn build_with_format(&self, format: &[&str]) -> Result<String> {
206 if format.is_empty() {
207 return Err(EditorError::FormatLineError {
208 message: "Format line cannot be empty".to_string(),
209 });
210 }
211
212 let event_type = self.event_type.unwrap_or(EventType::Dialogue);
213 let event_type_str = event_type.as_str();
214
215 let mut field_values = Vec::with_capacity(format.len());
217
218 for field in format {
219 let value = match *field {
220 "Layer" => self.layer.as_ref().map(|c| c.as_ref()).unwrap_or("0"),
221 "Start" => self
222 .start
223 .as_ref()
224 .map(|c| c.as_ref())
225 .unwrap_or("0:00:00.00"),
226 "End" => self
227 .end
228 .as_ref()
229 .map(|c| c.as_ref())
230 .unwrap_or("0:00:05.00"),
231 "Style" => self.style.as_ref().map(|c| c.as_ref()).unwrap_or("Default"),
232 "Name" | "Actor" => self.name.as_ref().map(|c| c.as_ref()).unwrap_or(""),
233 "MarginL" => self.margin_l.as_ref().map(|c| c.as_ref()).unwrap_or("0"),
234 "MarginR" => self.margin_r.as_ref().map(|c| c.as_ref()).unwrap_or("0"),
235 "MarginV" => self.margin_v.as_ref().map(|c| c.as_ref()).unwrap_or("0"),
236 "MarginT" => self.margin_t.as_ref().map(|c| c.as_ref()).unwrap_or("0"),
237 "MarginB" => self.margin_b.as_ref().map(|c| c.as_ref()).unwrap_or("0"),
238 "Effect" => self.effect.as_ref().map(|c| c.as_ref()).unwrap_or(""),
239 "Text" => self.text.as_ref().map(|c| c.as_ref()).unwrap_or(""),
240 _ => {
241 return Err(EditorError::FormatLineError {
242 message: format!("Unknown event field: {field}"),
243 })
244 }
245 };
246 field_values.push(value.to_string());
247 }
248
249 let line = format!("{event_type_str}: {}", field_values.join(","));
251 Ok(line)
252 }
253}
254
255#[derive(Debug, Default, Clone)]
257pub struct StyleBuilder {
258 name: Option<String>,
259 fontname: Option<String>,
260 fontsize: Option<u32>,
261 primary_colour: Option<String>,
262 secondary_colour: Option<String>,
263 outline_colour: Option<String>,
264 back_colour: Option<String>,
265 bold: Option<bool>,
266 italic: Option<bool>,
267 underline: Option<bool>,
268 strikeout: Option<bool>,
269 scale_x: Option<f32>,
270 scale_y: Option<f32>,
271 spacing: Option<f32>,
272 angle: Option<f32>,
273 border_style: Option<u32>,
274 outline: Option<f32>,
275 shadow: Option<f32>,
276 alignment: Option<u32>,
277 margin_l: Option<u32>,
278 margin_r: Option<u32>,
279 margin_v: Option<u32>,
280 margin_t: Option<u32>,
281 margin_b: Option<u32>,
282 encoding: Option<u32>,
283 alpha_level: Option<u32>,
284 relative_to: Option<String>,
285}
286
287impl StyleBuilder {
288 pub fn new() -> Self {
290 Self::default()
291 }
292
293 pub fn default_style() -> Self {
295 Self {
296 fontname: Some("Arial".to_string()),
297 fontsize: Some(20),
298 primary_colour: Some("&Hffffff".to_string()),
299 secondary_colour: Some("&Hff0000".to_string()),
300 outline_colour: Some("&H0".to_string()),
301 back_colour: Some("&H0".to_string()),
302 bold: Some(false),
303 italic: Some(false),
304 underline: Some(false),
305 strikeout: Some(false),
306 scale_x: Some(100.0),
307 scale_y: Some(100.0),
308 spacing: Some(0.0),
309 angle: Some(0.0),
310 border_style: Some(1),
311 outline: Some(2.0),
312 shadow: Some(0.0),
313 alignment: Some(2),
314 margin_l: Some(10),
315 margin_r: Some(10),
316 margin_v: Some(10),
317 encoding: Some(1),
318 ..Self::default()
319 }
320 }
321
322 pub fn name(mut self, name: &str) -> Self {
324 self.name = Some(name.to_string());
325 self
326 }
327
328 pub fn font(mut self, font: &str) -> Self {
330 self.fontname = Some(font.to_string());
331 self
332 }
333
334 pub fn size(mut self, size: u32) -> Self {
336 self.fontsize = Some(size);
337 self
338 }
339
340 pub fn color(mut self, color: &str) -> Self {
342 self.primary_colour = Some(color.to_string());
343 self
344 }
345
346 pub fn bold(mut self, bold: bool) -> Self {
348 self.bold = Some(bold);
349 self
350 }
351
352 pub fn italic(mut self, italic: bool) -> Self {
354 self.italic = Some(italic);
355 self
356 }
357
358 pub fn align(mut self, alignment: u32) -> Self {
360 self.alignment = Some(alignment);
361 self
362 }
363
364 pub fn secondary_color(mut self, color: &str) -> Self {
366 self.secondary_colour = Some(color.to_string());
367 self
368 }
369
370 pub fn outline_color(mut self, color: &str) -> Self {
372 self.outline_colour = Some(color.to_string());
373 self
374 }
375
376 pub fn back_color(mut self, color: &str) -> Self {
378 self.back_colour = Some(color.to_string());
379 self
380 }
381
382 pub fn underline(mut self, underline: bool) -> Self {
384 self.underline = Some(underline);
385 self
386 }
387
388 pub fn strikeout(mut self, strikeout: bool) -> Self {
390 self.strikeout = Some(strikeout);
391 self
392 }
393
394 pub fn scale_x(mut self, scale: f32) -> Self {
396 self.scale_x = Some(scale);
397 self
398 }
399
400 pub fn scale_y(mut self, scale: f32) -> Self {
402 self.scale_y = Some(scale);
403 self
404 }
405
406 pub fn spacing(mut self, spacing: f32) -> Self {
408 self.spacing = Some(spacing);
409 self
410 }
411
412 pub fn angle(mut self, angle: f32) -> Self {
414 self.angle = Some(angle);
415 self
416 }
417
418 pub fn border_style(mut self, style: u32) -> Self {
420 self.border_style = Some(style);
421 self
422 }
423
424 pub fn outline(mut self, width: f32) -> Self {
426 self.outline = Some(width);
427 self
428 }
429
430 pub fn shadow(mut self, depth: f32) -> Self {
432 self.shadow = Some(depth);
433 self
434 }
435
436 pub fn margin_left(mut self, margin: u32) -> Self {
438 self.margin_l = Some(margin);
439 self
440 }
441
442 pub fn margin_right(mut self, margin: u32) -> Self {
444 self.margin_r = Some(margin);
445 self
446 }
447
448 pub fn margin_vertical(mut self, margin: u32) -> Self {
450 self.margin_v = Some(margin);
451 self
452 }
453
454 pub fn margin_top(mut self, margin: u32) -> Self {
456 self.margin_t = Some(margin);
457 self
458 }
459
460 pub fn margin_bottom(mut self, margin: u32) -> Self {
462 self.margin_b = Some(margin);
463 self
464 }
465
466 pub fn encoding(mut self, encoding: u32) -> Self {
468 self.encoding = Some(encoding);
469 self
470 }
471
472 pub fn alpha_level(mut self, alpha: u32) -> Self {
474 self.alpha_level = Some(alpha);
475 self
476 }
477
478 pub fn relative_to(mut self, relative: &str) -> Self {
480 self.relative_to = Some(relative.to_string());
481 self
482 }
483
484 pub fn build(self) -> Result<String> {
486 let name = self.name.unwrap_or_else(|| "NewStyle".to_string());
487 let fontname = self.fontname.unwrap_or_else(|| "Arial".to_string());
488 let fontsize = self.fontsize.unwrap_or(20);
489 let primary_colour = self
490 .primary_colour
491 .unwrap_or_else(|| "&Hffffff".to_string());
492 let secondary_colour = self
493 .secondary_colour
494 .unwrap_or_else(|| "&Hff0000".to_string());
495 let outline_colour = self.outline_colour.unwrap_or_else(|| "&H0".to_string());
496 let back_colour = self.back_colour.unwrap_or_else(|| "&H0".to_string());
497 let bold = if self.bold.unwrap_or(false) {
498 "-1"
499 } else {
500 "0"
501 };
502 let italic = if self.italic.unwrap_or(false) {
503 "-1"
504 } else {
505 "0"
506 };
507 let underline = if self.underline.unwrap_or(false) {
508 "-1"
509 } else {
510 "0"
511 };
512 let strikeout = if self.strikeout.unwrap_or(false) {
513 "-1"
514 } else {
515 "0"
516 };
517 let scale_x = self.scale_x.unwrap_or(100.0);
518 let scale_y = self.scale_y.unwrap_or(100.0);
519 let spacing = self.spacing.unwrap_or(0.0);
520 let angle = self.angle.unwrap_or(0.0);
521 let border_style = self.border_style.unwrap_or(1);
522 let outline = self.outline.unwrap_or(2.0);
523 let shadow = self.shadow.unwrap_or(0.0);
524 let alignment = self.alignment.unwrap_or(2);
525 let margin_l = self.margin_l.unwrap_or(10);
526 let margin_r = self.margin_r.unwrap_or(10);
527 let margin_v = self.margin_v.unwrap_or(10);
528 let encoding = self.encoding.unwrap_or(1);
529
530 let line = format!(
537 "Style: {name},{fontname},{fontsize},{primary_colour},{secondary_colour},{outline_colour},{back_colour},{bold},{italic},{underline},{strikeout},{scale_x},{scale_y},{spacing},{angle},{border_style},{outline},{shadow},{alignment},{margin_l},{margin_r},{margin_v},{encoding}"
538 );
539
540 Ok(line)
541 }
542
543 pub fn build_with_version(self, version: ScriptVersion) -> Result<String> {
545 let format = match version {
547 ScriptVersion::SsaV4 => {
548 vec![
550 "Name",
551 "Fontname",
552 "Fontsize",
553 "PrimaryColour",
554 "SecondaryColour",
555 "TertiaryColour",
556 "BackColour",
557 "Bold",
558 "Italic",
559 "BorderStyle",
560 "Outline",
561 "Shadow",
562 "Alignment",
563 "MarginL",
564 "MarginR",
565 "MarginV",
566 "AlphaLevel",
567 "Encoding",
568 ]
569 }
570 ScriptVersion::AssV4 => {
571 vec![
573 "Name",
574 "Fontname",
575 "Fontsize",
576 "PrimaryColour",
577 "SecondaryColour",
578 "OutlineColour",
579 "BackColour",
580 "Bold",
581 "Italic",
582 "Underline",
583 "StrikeOut",
584 "ScaleX",
585 "ScaleY",
586 "Spacing",
587 "Angle",
588 "BorderStyle",
589 "Outline",
590 "Shadow",
591 "Alignment",
592 "MarginL",
593 "MarginR",
594 "MarginV",
595 "Encoding",
596 ]
597 }
598 ScriptVersion::AssV4Plus => {
599 vec![
601 "Name",
602 "Fontname",
603 "Fontsize",
604 "PrimaryColour",
605 "SecondaryColour",
606 "OutlineColour",
607 "BackColour",
608 "Bold",
609 "Italic",
610 "Underline",
611 "StrikeOut",
612 "ScaleX",
613 "ScaleY",
614 "Spacing",
615 "Angle",
616 "BorderStyle",
617 "Outline",
618 "Shadow",
619 "Alignment",
620 "MarginL",
621 "MarginR",
622 "MarginV",
623 "MarginT",
624 "MarginB",
625 "Encoding",
626 "RelativeTo",
627 ]
628 }
629 };
630
631 self.build_with_format(&format)
632 }
633
634 pub fn build_with_format(&self, format: &[&str]) -> Result<String> {
637 if format.is_empty() {
638 return Err(EditorError::FormatLineError {
639 message: "Format line cannot be empty".to_string(),
640 });
641 }
642
643 let mut field_values = Vec::with_capacity(format.len());
645
646 for field in format {
647 let value = match *field {
648 "Name" => self.name.clone().unwrap_or_else(|| "NewStyle".to_string()),
649 "Fontname" => self.fontname.clone().unwrap_or_else(|| "Arial".to_string()),
650 "Fontsize" => self.fontsize.unwrap_or(20).to_string(),
651 "PrimaryColour" => self
652 .primary_colour
653 .clone()
654 .unwrap_or_else(|| "&Hffffff".to_string()),
655 "SecondaryColour" => self
656 .secondary_colour
657 .clone()
658 .unwrap_or_else(|| "&Hff0000".to_string()),
659 "OutlineColour" | "TertiaryColour" => self
660 .outline_colour
661 .clone()
662 .unwrap_or_else(|| "&H0".to_string()),
663 "BackColour" => self
664 .back_colour
665 .clone()
666 .unwrap_or_else(|| "&H0".to_string()),
667 "Bold" => if self.bold.unwrap_or(false) {
668 "-1"
669 } else {
670 "0"
671 }
672 .to_string(),
673 "Italic" => if self.italic.unwrap_or(false) {
674 "-1"
675 } else {
676 "0"
677 }
678 .to_string(),
679 "Underline" => if self.underline.unwrap_or(false) {
680 "-1"
681 } else {
682 "0"
683 }
684 .to_string(),
685 "Strikeout" | "StrikeOut" => if self.strikeout.unwrap_or(false) {
686 "-1"
687 } else {
688 "0"
689 }
690 .to_string(),
691 "ScaleX" => self.scale_x.unwrap_or(100.0).to_string(),
692 "ScaleY" => self.scale_y.unwrap_or(100.0).to_string(),
693 "Spacing" => self.spacing.unwrap_or(0.0).to_string(),
694 "Angle" => self.angle.unwrap_or(0.0).to_string(),
695 "BorderStyle" => self.border_style.unwrap_or(1).to_string(),
696 "Outline" => self.outline.unwrap_or(2.0).to_string(),
697 "Shadow" => self.shadow.unwrap_or(0.0).to_string(),
698 "Alignment" => self.alignment.unwrap_or(2).to_string(),
699 "MarginL" => self.margin_l.unwrap_or(10).to_string(),
700 "MarginR" => self.margin_r.unwrap_or(10).to_string(),
701 "MarginV" => self.margin_v.unwrap_or(10).to_string(),
702 "MarginT" => self.margin_t.unwrap_or(0).to_string(),
703 "MarginB" => self.margin_b.unwrap_or(0).to_string(),
704 "Encoding" => self.encoding.unwrap_or(1).to_string(),
705 "AlphaLevel" => self.alpha_level.unwrap_or(0).to_string(),
706 "RelativeTo" => self.relative_to.clone().unwrap_or_else(|| "0".to_string()),
707 _ => {
708 return Err(EditorError::FormatLineError {
709 message: format!("Unknown style field: {field}"),
710 })
711 }
712 };
713 field_values.push(value);
714 }
715
716 let line = format!("Style: {}", field_values.join(","));
718 Ok(line)
719 }
720}
721
722#[cfg(test)]
723mod tests {
724 use super::*;
725 #[cfg(not(feature = "std"))]
726 use alloc::string::ToString;
727
728 #[test]
729 fn event_builder_dialogue() {
730 let event = EventBuilder::dialogue()
731 .start_time("0:00:05.00")
732 .end_time("0:00:10.00")
733 .speaker("John")
734 .text("Hello world!")
735 .build()
736 .unwrap();
737
738 assert!(event.contains("Dialogue:"));
739 assert!(event.contains("0:00:05.00"));
740 assert!(event.contains("Hello world!"));
741 }
742
743 #[test]
744 fn event_builder_comment() {
745 let event = EventBuilder::comment()
746 .text("This is a comment")
747 .build()
748 .unwrap();
749
750 assert!(event.contains("Comment:"));
751 assert!(event.contains("This is a comment"));
752 }
753
754 #[test]
755 fn style_builder_default() {
756 let style = StyleBuilder::default_style()
757 .name("TestStyle")
758 .font("Comic Sans MS")
759 .size(24)
760 .bold(true)
761 .build()
762 .unwrap();
763
764 assert!(style.contains("Style: TestStyle"));
765 assert!(style.contains("Comic Sans MS"));
766 assert!(style.contains("24"));
767 assert!(style.contains("-1")); }
769
770 #[test]
771 fn style_builder_minimal() {
772 let style = StyleBuilder::new().name("Minimal").build().unwrap();
773
774 assert!(style.contains("Style: Minimal"));
775 assert!(style.contains("Arial")); }
777
778 #[test]
779 fn event_builder_with_margins() {
780 let event = EventBuilder::dialogue()
781 .start_time("0:00:05.00")
782 .end_time("0:00:10.00")
783 .margin_left(15)
784 .margin_right(20)
785 .margin_vertical(25)
786 .margin_top(30)
787 .margin_bottom(35)
788 .text("Testing margins")
789 .build()
790 .unwrap();
791
792 assert!(event.contains("Dialogue:"));
793 assert!(event.contains("15")); assert!(event.contains("20")); assert!(event.contains("25")); }
798
799 #[test]
800 fn style_builder_all_fields() {
801 let style = StyleBuilder::new()
802 .name("Complete")
803 .font("Helvetica")
804 .size(18)
805 .color("&Hffffff")
806 .secondary_color("&H00ff00")
807 .outline_color("&H0000ff")
808 .back_color("&H808080")
809 .bold(true)
810 .italic(false)
811 .underline(true)
812 .strikeout(false)
813 .scale_x(95.5)
814 .scale_y(105.0)
815 .spacing(1.5)
816 .angle(15.0)
817 .border_style(3)
818 .outline(2.5)
819 .shadow(1.0)
820 .align(7)
821 .margin_left(5)
822 .margin_right(15)
823 .margin_vertical(20)
824 .margin_top(25)
825 .margin_bottom(30)
826 .encoding(0)
827 .relative_to("video")
828 .build()
829 .unwrap();
830
831 assert!(style.contains("Style: Complete"));
832 assert!(style.contains("Helvetica"));
833 assert!(style.contains("18"));
834 assert!(style.contains("&Hffffff"));
835 assert!(style.contains("&H00ff00"));
836 assert!(style.contains("&H0000ff"));
837 assert!(style.contains("&H808080"));
838 assert!(style.contains("-1")); assert!(style.contains("95.5"));
840 assert!(style.contains("105"));
841 assert!(style.contains("1.5"));
842 assert!(style.contains("15")); assert!(style.contains("3")); assert!(style.contains("2.5")); assert!(style.contains("7")); }
848
849 #[test]
850 fn event_builder_with_format_v4plus() {
851 let event = EventBuilder::dialogue()
852 .start_time("0:00:05.00")
853 .end_time("0:00:10.00")
854 .style("Main")
855 .layer(1)
856 .text("Test with format")
857 .build_with_format(&[
858 "Layer", "Start", "End", "Style", "Name", "MarginL", "MarginR", "MarginV",
859 "Effect", "Text",
860 ])
861 .unwrap();
862
863 assert_eq!(
864 event,
865 "Dialogue: 1,0:00:05.00,0:00:10.00,Main,,0,0,0,,Test with format"
866 );
867 }
868
869 #[test]
870 fn event_builder_with_format_v4plusplus() {
871 let event = EventBuilder::dialogue()
872 .start_time("0:00:05.00")
873 .end_time("0:00:10.00")
874 .style("Main")
875 .margin_top(5)
876 .margin_bottom(10)
877 .text("V4++ format")
878 .build_with_format(&[
879 "Layer", "Start", "End", "Style", "Name", "MarginL", "MarginR", "MarginT",
880 "MarginB", "Effect", "Text",
881 ])
882 .unwrap();
883
884 assert_eq!(
885 event,
886 "Dialogue: 0,0:00:05.00,0:00:10.00,Main,,0,0,5,10,,V4++ format"
887 );
888 }
889
890 #[test]
891 fn event_builder_with_format_custom() {
892 let event = EventBuilder::comment()
893 .text("Simple comment")
894 .build_with_format(&["Start", "End", "Text"])
895 .unwrap();
896
897 assert_eq!(event, "Comment: 0:00:00.00,0:00:05.00,Simple comment");
898 }
899
900 #[test]
901 fn event_builder_with_format_error() {
902 let result = EventBuilder::dialogue()
903 .text("Test")
904 .build_with_format(&["InvalidField"]);
905
906 assert!(result.is_err());
907 assert!(result
908 .unwrap_err()
909 .to_string()
910 .contains("Unknown event field"));
911 }
912
913 #[test]
914 fn style_builder_with_format_v4plus() {
915 let style = StyleBuilder::new()
916 .name("TestStyle")
917 .font("Arial")
918 .size(20)
919 .build_with_format(&[
920 "Name",
921 "Fontname",
922 "Fontsize",
923 "PrimaryColour",
924 "SecondaryColour",
925 "OutlineColour",
926 "BackColour",
927 "Bold",
928 "Italic",
929 "Underline",
930 "StrikeOut",
931 "ScaleX",
932 "ScaleY",
933 "Spacing",
934 "Angle",
935 "BorderStyle",
936 "Outline",
937 "Shadow",
938 "Alignment",
939 "MarginL",
940 "MarginR",
941 "MarginV",
942 "Encoding",
943 ])
944 .unwrap();
945
946 assert_eq!(style, "Style: TestStyle,Arial,20,&Hffffff,&Hff0000,&H0,&H0,0,0,0,0,100,100,0,0,1,2,0,2,10,10,10,1");
947 }
948
949 #[test]
950 fn style_builder_with_format_v4plusplus() {
951 let style = StyleBuilder::new()
952 .name("V4++Style")
953 .margin_top(15)
954 .margin_bottom(20)
955 .relative_to("video")
956 .build_with_format(&[
957 "Name",
958 "Fontname",
959 "Fontsize",
960 "PrimaryColour",
961 "SecondaryColour",
962 "OutlineColour",
963 "BackColour",
964 "Bold",
965 "Italic",
966 "Underline",
967 "StrikeOut",
968 "ScaleX",
969 "ScaleY",
970 "Spacing",
971 "Angle",
972 "BorderStyle",
973 "Outline",
974 "Shadow",
975 "Alignment",
976 "MarginL",
977 "MarginR",
978 "MarginT",
979 "MarginB",
980 "Encoding",
981 "RelativeTo",
982 ])
983 .unwrap();
984
985 assert!(style.contains("V4++Style"));
986 assert!(style.contains("15")); assert!(style.contains("20")); assert!(style.contains("video")); }
990
991 #[test]
992 fn style_builder_with_format_minimal() {
993 let style = StyleBuilder::new()
994 .name("MinimalStyle")
995 .build_with_format(&["Name", "Fontname", "Fontsize"])
996 .unwrap();
997
998 assert_eq!(style, "Style: MinimalStyle,Arial,20");
999 }
1000
1001 #[test]
1002 fn event_builder_with_script_version() {
1003 let event_ssa = EventBuilder::dialogue()
1005 .text("SSA Format")
1006 .start_time("0:00:01.00")
1007 .end_time("0:00:03.00")
1008 .build_with_version(ScriptVersion::SsaV4)
1009 .unwrap();
1010 assert!(event_ssa.contains("SSA Format"));
1011
1012 let event_ass = EventBuilder::dialogue()
1014 .text("ASS Format")
1015 .build_with_version(ScriptVersion::AssV4Plus)
1016 .unwrap();
1017 assert!(event_ass.contains("ASS Format"));
1018 }
1019
1020 #[test]
1021 fn style_builder_with_script_version() {
1022 let style_ssa = StyleBuilder::new()
1024 .name("TestSSA")
1025 .font("Arial")
1026 .size(18)
1027 .build_with_version(ScriptVersion::SsaV4)
1028 .unwrap();
1029 assert!(style_ssa.contains("TestSSA"));
1031 assert!(style_ssa.contains("Arial"));
1032
1033 let style_ass = StyleBuilder::new()
1035 .name("TestASS")
1036 .font("Verdana")
1037 .size(20)
1038 .build_with_version(ScriptVersion::AssV4Plus)
1039 .unwrap();
1040 assert!(style_ass.contains("TestASS"));
1041 assert!(style_ass.contains("Verdana"));
1042 }
1043}