Skip to main content

leaf_raster/
path.rs

1//! Which image destinations a synchronous local loader handles.
2
3use std::path::{Path, PathBuf};
4
5/// Resolve an image destination to a readable local path, or `None` when it's
6/// not one a synchronous loader handles: a remote URL, a `data:` URI, a
7/// protocol-relative `//host/…`, or a relative path with no document directory
8/// to anchor it. The one policy every frontend that decodes local files eagerly
9/// shares — the rest stay a placeholder.
10pub fn resolve_image_path(dest: &str, doc_dir: Option<&Path>) -> Option<PathBuf> {
11    let dest = dest.trim();
12    if dest.is_empty() {
13        return None;
14    }
15    let lower = dest.to_ascii_lowercase();
16    if lower.starts_with("http://")
17        || lower.starts_with("https://")
18        || lower.starts_with("data:")
19        || dest.starts_with("//")
20    {
21        return None;
22    }
23    let raw = dest.strip_prefix("file://").unwrap_or(dest);
24    let path = Path::new(raw);
25    if path.is_absolute() {
26        Some(path.to_path_buf())
27    } else {
28        doc_dir.map(|d| d.join(path))
29    }
30}
31
32#[cfg(test)]
33mod tests {
34    use super::*;
35
36    #[test]
37    fn resolve_rejects_remote_and_anchors_relative() {
38        let dir = Path::new("/docs");
39        assert_eq!(
40            resolve_image_path("pics/cat.png", Some(dir)),
41            Some(PathBuf::from("/docs/pics/cat.png"))
42        );
43        assert_eq!(
44            resolve_image_path("/abs/cat.png", Some(dir)),
45            Some(PathBuf::from("/abs/cat.png"))
46        );
47        assert_eq!(resolve_image_path("https://x.dev/a.png", Some(dir)), None);
48        assert_eq!(
49            resolve_image_path("data:image/png;base64,AAAA", Some(dir)),
50            None
51        );
52        // A relative path with no document directory can't be anchored.
53        assert_eq!(resolve_image_path("cat.png", None), None);
54    }
55}