1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
use once_cell::sync::Lazy; use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET}; use regex::{Captures, Regex}; use std::borrow::Cow; static WHITESPACES_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"\s+").unwrap()); static URL_HEX_PAIRS_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"%[\dA-F]{2}").unwrap()); trait SvgDataUriUtils: AsRef<str> { fn trim_byte_order_mark(&self) -> &str { let string = self.as_ref(); match string.chars().nth(0) { Some('\u{FEFF}') => &string[1..], _ => string, } } fn collapse_whitespace(&self) -> Cow<str> { WHITESPACES_REGEX.replace_all(self.as_ref(), " ") } fn encode_uri_components(&self) -> Cow<str> { let string = self.as_ref(); utf8_percent_encode(string, DEFAULT_ENCODE_SET).collect() } fn special_hex_encode(&self) -> Cow<str> { let string = self.as_ref(); URL_HEX_PAIRS_REGEX.replace_all(string, |captures: &Captures| { match captures.get(0).map(|capture| capture.as_str()) { Some("%20") => " ".to_string(), Some("%3D") => "=".to_string(), Some("%3A") => ":".to_string(), Some("%2F") => "/".to_string(), Some(hex) => hex.to_lowercase(), None => "".to_string(), } }) } } impl<T: AsRef<str>> SvgDataUriUtils for T {} pub fn svg_str_to_data_uri(svg: impl AsRef<str>) -> String { format!( "data:image/svg+xml,{}", svg.trim_byte_order_mark() .trim() .collapse_whitespace() .replace('"', "'") .encode_uri_components() .special_hex_encode() ) } pub fn image_to_png_data_uri<T>(image: &T) -> image::ImageResult<String> where T: image::GenericImageView, T::InnerImageView: std::ops::Deref<Target = [u8]>, { use image::Pixel; let mut buffer = Vec::new(); let encoder = image::png::PNGEncoder::new(&mut buffer); encoder.encode( image.inner(), image.width(), image.height(), T::Pixel::color_type(), )?; Ok([ "data:", mime::IMAGE_PNG.as_ref(), ";base64,", base64::encode(&buffer).as_ref(), ] .iter() .cloned() .collect()) } pub fn image_to_jpeg_data_uri<T>(image: &T, quality: u8) -> image::ImageResult<String> where T: image::GenericImageView, T::InnerImageView: std::ops::Deref<Target = [u8]>, { use image::Pixel; let mut buffer = Vec::new(); let mut encoder = image::jpeg::JPEGEncoder::new_with_quality(&mut buffer, quality); encoder.encode( image.inner(), image.width(), image.height(), T::Pixel::color_type(), )?; Ok([ "data:", mime::IMAGE_JPEG.as_ref(), ";base64,", base64::encode(&buffer).as_ref(), ] .iter() .cloned() .collect()) } #[cfg(test)] mod tests { use crate::*; #[test] fn full_test() { let svg = r##" <svg xmlns="http://www.w3.org/2000/svg" viewBox="#000000 0 50 50"> <path d="M22 38V51L32 32l19-19v12C44 26 43 10 38 0 52 15 49 39 22 38z"/> </svg>"##; let expected = r#"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 50 50'%3e %3cpath d='M22 38V51L32 32l19-19v12C44 26 43 10 38 0 52 15 49 39 22 38z'/%3e %3c/svg%3e"#; let result = svg_str_to_data_uri(svg); assert_eq!(result, expected); } }