0.5.0: Parse SVG size

This commit is contained in:
Sam Denty 2022-12-28 21:15:01 +00:00
parent de1284ae43
commit 99ed10ff27
No known key found for this signature in database
GPG key ID: 7B4EAF7B9E291B79
5 changed files with 188 additions and 15 deletions

48
Cargo.lock generated
View file

@ -2,6 +2,17 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "ahash"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
dependencies = [
"getrandom 0.2.8",
"once_cell",
"version_check",
]
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "0.7.19" version = "0.7.19"
@ -566,6 +577,9 @@ name = "hashbrown"
version = "0.12.3" version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
dependencies = [
"ahash",
]
[[package]] [[package]]
name = "heck" name = "heck"
@ -792,6 +806,12 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lazycell"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.137" version = "0.2.137"
@ -826,6 +846,25 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "lol_html"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b16d7d17063591adf0017a068a8ce2ed29a4f5402b9e6cac01888ff9c0527ff7"
dependencies = [
"bitflags",
"cfg-if",
"cssparser",
"encoding_rs",
"hashbrown",
"lazy_static",
"lazycell",
"memchr",
"safemem",
"selectors",
"thiserror",
]
[[package]] [[package]]
name = "mac" name = "mac"
version = "0.1.1" version = "0.1.1"
@ -1453,6 +1492,12 @@ version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
[[package]]
name = "safemem"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
[[package]] [[package]]
name = "schannel" name = "schannel"
version = "0.1.20" version = "0.1.20"
@ -1638,7 +1683,7 @@ checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
[[package]] [[package]]
name = "site_icons" name = "site_icons"
version = "0.4.13" version = "0.5.0"
dependencies = [ dependencies = [
"byteorder", "byteorder",
"clap", "clap",
@ -1648,6 +1693,7 @@ dependencies = [
"html5ever", "html5ever",
"itertools", "itertools",
"log", "log",
"lol_html",
"mime_4", "mime_4",
"once_cell", "once_cell",
"percent-encoding", "percent-encoding",

View file

@ -1,6 +1,6 @@
[package] [package]
name = "site_icons" name = "site_icons"
version = "0.4.13" version = "0.5.0"
authors = ["Sam Denty <sam@samdenty.com>"] authors = ["Sam Denty <sam@samdenty.com>"]
edition = "2018" edition = "2018"
license = "GPL-3.0" license = "GPL-3.0"
@ -35,6 +35,7 @@ serde = { version = "1.0", features = ["derive", "rc"] }
serde_json = "1.0" serde_json = "1.0"
futures = "0.3.25" futures = "0.3.25"
tldextract = "0.6.0" tldextract = "0.6.0"
lol_html = "0.3.2"
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]
reqwest = { package = "reqwest-wasm", version = "0.11.16", features = [ reqwest = { package = "reqwest-wasm", version = "0.11.16", features = [

View file

@ -11,9 +11,8 @@ use std::{
io, io,
}; };
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
enum IconKind { enum IconKind {
SVG,
PNG, PNG,
JPEG, JPEG,
ICO, ICO,
@ -28,7 +27,7 @@ pub enum IconInfo {
JPEG { size: IconSize }, JPEG { size: IconSize },
ICO { sizes: IconSizes }, ICO { sizes: IconSizes },
GIF { size: IconSize }, GIF { size: IconSize },
SVG, SVG { size: Option<IconSize> },
} }
impl IconInfo { impl IconInfo {
@ -40,6 +39,14 @@ impl IconInfo {
reader.read_exact(&mut header).await?; reader.read_exact(&mut header).await?;
match (kind, &header) { match (kind, &header) {
(Some(IconKind::SVG), bytes) => {
let size = get_svg_size(bytes, reader).await?;
Ok(IconInfo::SVG { size })
}
(_, &[0x60, byte_two]) => {
let size = get_svg_size(&[0x60, byte_two], reader).await?;
Ok(IconInfo::SVG { size })
}
(Some(IconKind::PNG), _) | (_, b"\x89P") => { (Some(IconKind::PNG), _) | (_, b"\x89P") => {
let size = get_png_size(reader).await?; let size = get_png_size(reader).await?;
Ok(IconInfo::PNG { size }) Ok(IconInfo::PNG { size })
@ -135,7 +142,25 @@ impl IconInfo {
Some(IconKind::ICO) Some(IconKind::ICO)
} }
(mime::IMAGE, mime::SVG) | (mime::TEXT, mime::PLAIN) => return Ok(IconInfo::SVG), (mime::IMAGE, mime::GIF) => {
if let Some(sizes) = sizes {
return Ok(IconInfo::GIF {
size: *sizes.largest(),
});
}
Some(IconKind::GIF)
}
(mime::IMAGE, mime::SVG) | (mime::TEXT, mime::PLAIN) => {
if let Some(sizes) = sizes {
return Ok(IconInfo::SVG {
size: Some(*sizes.largest()),
});
}
Some(IconKind::SVG)
}
_ => None, _ => None,
}; };
@ -147,7 +172,7 @@ impl IconInfo {
match self { match self {
IconInfo::ICO { sizes } => Some(sizes.largest()), IconInfo::ICO { sizes } => Some(sizes.largest()),
IconInfo::PNG { size } | IconInfo::JPEG { size } | IconInfo::GIF { size } => Some(size), IconInfo::PNG { size } | IconInfo::JPEG { size } | IconInfo::GIF { size } => Some(size),
IconInfo::SVG => None, IconInfo::SVG { size } => size.as_ref(),
} }
} }
@ -157,7 +182,7 @@ impl IconInfo {
IconInfo::PNG { size } | IconInfo::JPEG { size } | IconInfo::GIF { size } => { IconInfo::PNG { size } | IconInfo::JPEG { size } | IconInfo::GIF { size } => {
Some((*size).into()) Some((*size).into())
} }
IconInfo::SVG => None, IconInfo::SVG { size } => size.map(|size| size.into()),
} }
} }
@ -167,7 +192,7 @@ impl IconInfo {
IconInfo::JPEG { .. } => "image/jpeg", IconInfo::JPEG { .. } => "image/jpeg",
IconInfo::ICO { .. } => "image/x-icon", IconInfo::ICO { .. } => "image/x-icon",
IconInfo::GIF { .. } => "image/gif", IconInfo::GIF { .. } => "image/gif",
IconInfo::SVG => "image/svg+xml", IconInfo::SVG { .. } => "image/svg+xml",
} }
} }
} }
@ -179,7 +204,17 @@ impl Display for IconInfo {
IconInfo::JPEG { size } => write!(f, "jpeg {}", size), IconInfo::JPEG { size } => write!(f, "jpeg {}", size),
IconInfo::GIF { size } => write!(f, "gif {}", size), IconInfo::GIF { size } => write!(f, "gif {}", size),
IconInfo::ICO { sizes } => write!(f, "ico {}", sizes), IconInfo::ICO { sizes } => write!(f, "ico {}", sizes),
IconInfo::SVG => write!(f, "svg"), IconInfo::SVG { size } => {
write!(
f,
"svg{}",
if let Some(size) = size {
format!(" {}", size)
} else {
"".to_string()
}
)
}
} }
} }
} }
@ -187,15 +222,20 @@ impl Display for IconInfo {
impl Ord for IconInfo { impl Ord for IconInfo {
fn cmp(&self, other: &Self) -> Ordering { fn cmp(&self, other: &Self) -> Ordering {
match (self, other) { match (self, other) {
(IconInfo::SVG, IconInfo::SVG) => Ordering::Equal, (IconInfo::SVG { size }, IconInfo::SVG { size: other_size }) => match (size, other_size) {
(IconInfo::SVG, _) => Ordering::Less, (Some(_), None) => Ordering::Less,
(_, IconInfo::SVG) => Ordering::Greater, (None, Some(_)) => Ordering::Greater,
(Some(size), Some(other_size)) => size.cmp(other_size),
(None, None) => Ordering::Equal,
},
(IconInfo::SVG { .. }, _) => Ordering::Less,
(_, IconInfo::SVG { .. }) => Ordering::Greater,
_ => { _ => {
let this_size = self.size().unwrap(); let size = self.size().unwrap();
let other_size = other.size().unwrap(); let other_size = other.size().unwrap();
this_size.cmp(other_size).then_with(|| match (self, other) { size.cmp(other_size).then_with(|| match (self, other) {
(IconInfo::PNG { .. }, IconInfo::PNG { .. }) => Ordering::Equal, (IconInfo::PNG { .. }, IconInfo::PNG { .. }) => Ordering::Equal,
(IconInfo::PNG { .. }, _) => Ordering::Less, (IconInfo::PNG { .. }, _) => Ordering::Less,
(_, IconInfo::PNG { .. }) => Ordering::Greater, (_, IconInfo::PNG { .. }) => Ordering::Greater,

View file

@ -3,12 +3,14 @@ mod ico;
mod icon_sizes; mod icon_sizes;
mod jpeg; mod jpeg;
mod png; mod png;
mod svg;
pub use gif::*; pub use gif::*;
pub use ico::*; pub use ico::*;
pub use icon_sizes::*; pub use icon_sizes::*;
pub use jpeg::*; pub use jpeg::*;
pub use png::*; pub use png::*;
pub use svg::*;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use std::{ use std::{

84
src/icon_size/svg.rs Normal file
View file

@ -0,0 +1,84 @@
use super::IconSize;
use futures::prelude::*;
use lol_html::{element, errors::RewritingError, HtmlRewriter, Settings};
use std::{
error::Error,
fmt::{self, Display},
};
#[derive(Debug)]
struct SizeResult(Option<IconSize>);
impl Display for SizeResult {
fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result {
Ok(())
}
}
impl Error for SizeResult {}
fn parse_size<S: ToString>(size: S) -> Option<u32> {
size
.to_string()
.parse::<f64>()
.ok()
.map(|size| size.round() as u32)
}
pub async fn get_svg_size<R: AsyncRead + Unpin>(
first_bytes: &[u8; 2],
reader: &mut R,
) -> Result<Option<IconSize>, Box<dyn Error>> {
let mut rewriter = HtmlRewriter::new(
Settings {
element_content_handlers: vec![
// Rewrite insecure hyperlinks
element!("svg", |el| {
let viewbox = el.get_attribute("viewbox");
let width = el.get_attribute("width").and_then(parse_size);
let height = el.get_attribute("height").and_then(parse_size);
Err(Box::new(SizeResult(
if let (Some(width), Some(height)) = (width, height) {
Some(IconSize::new(width, height))
} else if let Some(viewbox) = viewbox {
regex!(r"^\d+\s+\d+\s+(\d+\.?[\d]?)\s+(\d+\.?[\d]?)")
.captures(&viewbox)
.map(|captures| {
let width = parse_size(captures.get(1).unwrap().as_str()).unwrap();
let height = parse_size(captures.get(2).unwrap().as_str()).unwrap();
IconSize::new(width, height)
})
} else {
None
},
)))
}),
],
..Settings::default()
},
|_: &[u8]| {},
);
rewriter.write(first_bytes)?;
let mut buffer = [0; 100];
loop {
let n = reader.read(&mut buffer).await?;
if n == 0 {
return Err("invalid svg".into());
}
match rewriter.write(&buffer[..n]) {
Err(RewritingError::ContentHandlerError(result)) => {
let result = result.downcast::<SizeResult>().unwrap();
return Ok(result.0);
}
result => result?,
}
}
}