c2pa_structured_text/
embed.rs1use crate::codec;
13
14const BEGIN: &str = "-----BEGIN C2PA MANIFEST-----";
15const END: &str = "-----END C2PA MANIFEST-----";
16
17pub enum ManifestRef<'a> {
21 Url(&'a str),
22 Embedded(&'a [u8]),
23}
24
25impl ManifestRef<'_> {
26 fn render(&self) -> String {
27 match self {
28 ManifestRef::Url(url) => (*url).to_string(),
29 ManifestRef::Embedded(bytes) => {
30 format!("data:application/c2pa;base64,{}", codec::encode(bytes))
31 }
32 }
33 }
34}
35
36fn single_line(reference: &str, comment_prefix: &str, comment_suffix: Option<&str>) -> String {
37 let suffix = comment_suffix.unwrap_or("");
38 format!("{comment_prefix} {BEGIN} {reference} {END} {suffix}")
39 .trim_end()
40 .to_string()
41}
42
43pub fn embed_manifest(
46 text: &str,
47 manifest: ManifestRef<'_>,
48 comment_prefix: &str,
49 comment_suffix: Option<&str>,
50) -> String {
51 let line = single_line(&manifest.render(), comment_prefix, comment_suffix);
52 format!("{line}\n{text}")
53}
54
55pub fn embed_manifest_at_end(
63 text: &str,
64 manifest: ManifestRef<'_>,
65 comment_prefix: &str,
66 comment_suffix: Option<&str>,
67) -> String {
68 let line = single_line(&manifest.render(), comment_prefix, comment_suffix);
69 if text.is_empty() {
70 return line;
71 }
72 if text.ends_with('\n') {
73 format!("{text}{line}")
74 } else {
75 format!("{text}\n{line}")
76 }
77}
78
79pub fn embed_front_matter(text: &str, manifest: ManifestRef<'_>, fm_delim: &str) -> String {
86 let reference = manifest.render();
87 let block = format!("{BEGIN}\n{reference}\n{END}");
88
89 let opening = format!("{fm_delim}\n");
90 if let Some(rest) = text.strip_prefix(&opening) {
91 format!("{opening}{block}\n{rest}")
92 } else {
93 format!("{fm_delim}\n{block}\n{fm_delim}\n{text}")
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 #[test]
102 fn embed_url_python() {
103 let text = "print('hello')\n";
104 let result = embed_manifest(
105 text,
106 ManifestRef::Url("https://example.com/m.c2pa"),
107 "#",
108 None,
109 );
110 assert!(result.starts_with("# -----BEGIN C2PA MANIFEST-----"));
111 assert!(result.contains("https://example.com/m.c2pa"));
112 assert!(result.contains("-----END C2PA MANIFEST-----"));
113 assert!(result.ends_with("print('hello')\n"));
114 }
115
116 #[test]
117 fn embed_url_css() {
118 let result = embed_manifest(
119 "body {}",
120 ManifestRef::Url("https://example.com/m.c2pa"),
121 "/*",
122 Some("*/"),
123 );
124 assert!(result.starts_with("/* -----BEGIN C2PA MANIFEST-----"));
125 assert!(result.contains("-----END C2PA MANIFEST----- */"));
126 }
127
128 #[test]
129 fn embed_data_uri() {
130 let bytes = b"test manifest";
131 let result = embed_manifest("content", ManifestRef::Embedded(bytes), "#", None);
132 assert!(result.contains("data:application/c2pa;base64,"));
133 }
134
135 #[test]
136 fn embed_at_end_after_shebang() {
137 let text = "#!/usr/bin/env python3\nprint('hi')\n";
138 let result = embed_manifest_at_end(
139 text,
140 ManifestRef::Url("https://example.com/m.c2pa"),
141 "#",
142 None,
143 );
144 assert!(result.starts_with("#!/usr/bin/env python3"));
145 assert!(result.ends_with("-----END C2PA MANIFEST-----"));
146 }
147
148 #[test]
149 fn embed_at_end_adds_newline_separator() {
150 let result =
151 embed_manifest_at_end("no trailing newline", ManifestRef::Url("u"), "//", None);
152 assert_eq!(
153 result,
154 "no trailing newline\n// -----BEGIN C2PA MANIFEST----- u -----END C2PA MANIFEST-----"
155 );
156 }
157
158 #[test]
159 fn embed_front_matter_into_existing() {
160 let text = "---\ntitle: doc\n---\nbody\n";
161 let result =
162 embed_front_matter(text, ManifestRef::Url("https://example.com/m.c2pa"), "---");
163 assert!(result.starts_with("---\n-----BEGIN C2PA MANIFEST-----\n"));
164 assert!(result.contains("\ntitle: doc\n"));
165 }
166
167 #[test]
168 fn embed_front_matter_creates_section() {
169 let result = embed_front_matter("# Heading\n", ManifestRef::Url("u"), "---");
170 assert!(result.starts_with(
171 "---\n-----BEGIN C2PA MANIFEST-----\nu\n-----END C2PA MANIFEST-----\n---\n"
172 ));
173 }
174}