vecslide_core/
typst_split.rs1const SLIDE_PREAMBLE: &str = "#set page(width: 1920pt, height: 1080pt, margin: 2em, fill: none)\n\
4 #set text(font: \"Noto Sans\", weight: \"bold\", size: 24pt)\n\
5 #show math.equation: set text(font: \"New Computer Modern Sans Math\", weight: \"regular\")\n\
6 #set heading(numbering: none)\n";
7
8#[derive(Debug, Default)]
10pub struct TypstFrontmatter {
11 pub title: Option<String>,
12 pub author: Option<String>,
13}
14
15pub fn split_typst_slides(source: &str) -> (TypstFrontmatter, Vec<String>) {
21 let (frontmatter, body) = parse_frontmatter(source);
22
23 let slides: Vec<String> = body
24 .split("\n----\n")
25 .map(str::trim)
26 .filter(|s| !s.is_empty())
27 .map(|s| format!("{SLIDE_PREAMBLE}\n{s}"))
28 .collect();
29
30 (frontmatter, slides)
31}
32
33fn parse_frontmatter(source: &str) -> (TypstFrontmatter, &str) {
35 if !source.starts_with("---\n") {
37 return (TypstFrontmatter::default(), source);
38 }
39
40 let rest = &source[4..]; if let Some(end_pos) = rest.find("\n---\n") {
43 let yaml_str = &rest[..end_pos];
44 let body = &rest[end_pos + 5..]; let frontmatter = parse_yaml_frontmatter(yaml_str);
47 (frontmatter, body)
48 } else {
49 (TypstFrontmatter::default(), source)
51 }
52}
53
54fn parse_yaml_frontmatter(yaml: &str) -> TypstFrontmatter {
55 let mut fm = TypstFrontmatter::default();
56 for line in yaml.lines() {
57 if let Some(rest) = line.strip_prefix("title:") {
58 fm.title = Some(rest.trim().trim_matches('"').to_string());
59 } else if let Some(rest) = line.strip_prefix("author:") {
60 fm.author = Some(rest.trim().trim_matches('"').to_string());
61 }
62 }
63 fm
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69
70 #[test]
71 fn splits_on_dashes() {
72 let src = "slide one\n----\nslide two\n----\nslide three";
73 let (_, slides) = split_typst_slides(src);
74 assert_eq!(slides.len(), 3);
75 assert!(slides[0].contains("slide one"));
76 assert!(slides[1].contains("slide two"));
77 assert!(slides[2].contains("slide three"));
78 }
79
80 #[test]
81 fn each_slide_has_preamble() {
82 let src = "content";
83 let (_, slides) = split_typst_slides(src);
84 assert_eq!(slides.len(), 1);
85 assert!(slides[0].contains("#set page"));
86 }
87
88 #[test]
89 fn empty_sections_are_ignored() {
90 let src = "a\n----\n\n----\nb";
91 let (_, slides) = split_typst_slides(src);
92 assert_eq!(slides.len(), 2);
93 }
94
95 #[test]
96 fn frontmatter_is_parsed() {
97 let src = "---\ntitle: My Talk\nauthor: Prof. Rossi\n---\nslide one";
98 let (fm, slides) = split_typst_slides(src);
99 assert_eq!(fm.title.as_deref(), Some("My Talk"));
100 assert_eq!(fm.author.as_deref(), Some("Prof. Rossi"));
101 assert_eq!(slides.len(), 1);
102 assert!(slides[0].contains("slide one"));
103 }
104
105 #[test]
106 fn no_frontmatter_works() {
107 let src = "slide one\n----\nslide two";
108 let (fm, slides) = split_typst_slides(src);
109 assert!(fm.title.is_none());
110 assert_eq!(slides.len(), 2);
111 }
112}