data_uri_utils/
lib.rs

1use image::ImageEncoder;
2use image::PixelWithColorType;
3use once_cell::sync::Lazy;
4use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
5use regex::Regex;
6use std::borrow::Cow;
7
8static WHITESPACES_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"\s+").unwrap());
9
10trait SvgDataUriUtils: AsRef<str> {
11    fn trim_byte_order_mark(&self) -> &str {
12        let string = self.as_ref();
13        match string.chars().next() {
14            Some('\u{FEFF}') => &string[1..],
15            _ => string,
16        }
17    }
18
19    fn collapse_whitespace(&self) -> Cow<str> {
20        WHITESPACES_REGEX.replace_all(self.as_ref(), " ")
21    }
22
23    fn encode_uri_components(&self) -> Cow<str> {
24        let string = self.as_ref();
25        utf8_percent_encode(string, NON_ALPHANUMERIC).collect()
26    }
27}
28
29impl<T: AsRef<str>> SvgDataUriUtils for T {}
30
31pub fn svg_str_to_data_uri(svg: impl AsRef<str>) -> String {
32    format!(
33        "data:image/svg+xml,{}",
34        svg.trim_byte_order_mark()
35            .trim()
36            .collapse_whitespace()
37            .encode_uri_components()
38    )
39}
40
41pub fn image_to_png_data_uri<T>(image: &T) -> image::ImageResult<String>
42where
43    T: image::GenericImageView + image::EncodableLayout,
44    <T as image::GenericImageView>::Pixel: image::PixelWithColorType,
45{
46    let mut buffer = Vec::new();
47    let encoder = image::codecs::png::PngEncoder::new(&mut buffer);
48    encoder.write_image(
49        image.as_bytes(),
50        image.width(),
51        image.height(),
52        <T as image::GenericImageView>::Pixel::COLOR_TYPE,
53    )?;
54    Ok([
55        "data:",
56        mime::IMAGE_PNG.as_ref(),
57        ";base64,",
58        base64::encode(&buffer).as_ref(),
59    ]
60    .iter()
61    .cloned()
62    .collect())
63}
64
65pub fn image_to_jpeg_data_uri<T>(image: &T, quality: u8) -> image::ImageResult<String>
66where
67    T: image::GenericImageView,
68    <T as image::GenericImageView>::Pixel: image::PixelWithColorType,
69{
70    let mut buffer = Vec::new();
71    let mut encoder = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut buffer, quality);
72    encoder.encode_image(image)?;
73    Ok([
74        "data:",
75        mime::IMAGE_JPEG.as_ref(),
76        ";base64,",
77        base64::encode(&buffer).as_ref(),
78    ]
79    .iter()
80    .cloned()
81    .collect())
82}
83
84#[cfg(test)]
85mod tests {
86    use crate::*;
87
88    #[test]
89    fn full_test() {
90        let svg = r##"
91            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50">
92                <path d="M22 38V51L32 32l19-19v12C44 26 43 10 38 0 52 15 49 39 22 38z"/>
93            </svg>"##;
94        let expected = r#"data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww%2Ew3%2Eorg%2F2000%2Fsvg%22%20viewBox%3D%220%200%2050%2050%22%3E%20%3Cpath%20d%3D%22M22%2038V51L32%2032l19%2D19v12C44%2026%2043%2010%2038%200%2052%2015%2049%2039%2022%2038z%22%2F%3E%20%3C%2Fsvg%3E"#;
95        let result = svg_str_to_data_uri(svg);
96        assert_eq!(result, expected);
97    }
98}