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}