add SVG size computation to ImageSizeCache
based on a very simple reading of the SVG's XML to find out its `width`, `height` and `viewBox` no further parsing is required or will be provided and you are an excellent test subject
This commit is contained in:
parent
b4927261db
commit
2e1e64ae8c
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -2154,6 +2154,7 @@ dependencies = [
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"treehouse-format",
|
"treehouse-format",
|
||||||
"ulid",
|
"ulid",
|
||||||
|
"xmlparser",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2491,6 +2492,12 @@ version = "0.5.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
|
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xmlparser"
|
||||||
|
version = "0.13.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yoke"
|
name = "yoke"
|
||||||
version = "0.7.5"
|
version = "0.7.5"
|
||||||
|
|
|
@ -31,3 +31,4 @@ tracing.workspace = true
|
||||||
tracing-chrome = "0.7.2"
|
tracing-chrome = "0.7.2"
|
||||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||||
ulid = "1.0.0"
|
ulid = "1.0.0"
|
||||||
|
xmlparser = "0.13.6"
|
||||||
|
|
|
@ -29,11 +29,16 @@ where
|
||||||
fn compute_image_size(&self, path: &VPath) -> anyhow::Result<Option<ImageSize>> {
|
fn compute_image_size(&self, path: &VPath) -> anyhow::Result<Option<ImageSize>> {
|
||||||
if path.extension().is_some_and(config::is_image_file) {
|
if path.extension().is_some_and(config::is_image_file) {
|
||||||
if let Some(content) = query::<Content>(&self.inner, path) {
|
if let Some(content) = query::<Content>(&self.inner, path) {
|
||||||
let reader = image::ImageReader::new(Cursor::new(content.bytes()))
|
if path.extension() == Some("svg") {
|
||||||
.with_guessed_format()
|
return Ok(svg_size(&content.string()?));
|
||||||
.context("cannot guess image format")?;
|
} else {
|
||||||
let (width, height) = reader.into_dimensions()?;
|
let _span = info_span!("raster_image_size").entered();
|
||||||
return Ok(Some(ImageSize { width, height }));
|
let reader = image::ImageReader::new(Cursor::new(content.bytes()))
|
||||||
|
.with_guessed_format()
|
||||||
|
.context("cannot guess image format")?;
|
||||||
|
let (width, height) = reader.into_dimensions()?;
|
||||||
|
return Ok(Some(ImageSize { width, height }));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,3 +82,60 @@ where
|
||||||
write!(f, "ImageSizeCache({:?})", self.inner)
|
write!(f, "ImageSizeCache({:?})", self.inner)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Quickly determine the size of an SVG without parsing it into a DOM.
|
||||||
|
///
|
||||||
|
/// This method is a tentative check; the point is to return an image size that's _good enough
|
||||||
|
/// default_ rather than what the size is going to be in the user's web browser.
|
||||||
|
#[instrument(skip(svg))]
|
||||||
|
fn svg_size(svg: &str) -> Option<ImageSize> {
|
||||||
|
let mut tokenizer = xmlparser::Tokenizer::from(svg);
|
||||||
|
|
||||||
|
fn parse_view_box(s: &str) -> Option<[u32; 4]> {
|
||||||
|
let mut iter = s.split_whitespace();
|
||||||
|
let min_x = iter.next()?.parse().ok()?;
|
||||||
|
let min_y = iter.next()?.parse().ok()?;
|
||||||
|
let width = iter.next()?.parse().ok()?;
|
||||||
|
let height = iter.next()?.parse().ok()?;
|
||||||
|
Some([min_x, min_y, width, height])
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut in_svg = false;
|
||||||
|
let mut width: Option<u32> = None;
|
||||||
|
let mut height: Option<u32> = None;
|
||||||
|
let mut view_box: Option<[u32; 4]> = None;
|
||||||
|
while let Some(Ok(token)) = tokenizer.next() {
|
||||||
|
if let xmlparser::Token::ElementStart { local, .. } = &token {
|
||||||
|
if local == "svg" {
|
||||||
|
in_svg = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if in_svg {
|
||||||
|
// If another element starts, we're no longer in the root <svg>.
|
||||||
|
if let xmlparser::Token::ElementStart { .. } = &token {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let xmlparser::Token::Attribute { local, value, .. } = &token {
|
||||||
|
match local.as_str() {
|
||||||
|
"width" => width = value.parse().ok(),
|
||||||
|
"height" => height = value.parse().ok(),
|
||||||
|
"viewBox" => {
|
||||||
|
view_box = parse_view_box(value);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match (width, height, view_box) {
|
||||||
|
(Some(width), Some(height), _) | (_, _, Some([_, _, width, height])) => {
|
||||||
|
Some(ImageSize { width, height })
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue