0.5.0: Parse SVG size
This commit is contained in:
parent
de1284ae43
commit
99ed10ff27
5 changed files with 188 additions and 15 deletions
48
Cargo.lock
generated
48
Cargo.lock
generated
|
@ -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",
|
||||||
|
|
|
@ -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 = [
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
84
src/icon_size/svg.rs
Normal 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?,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in a new issue