remove treehouse-format crate and collapse everything into src
This commit is contained in:
parent
ca127a9411
commit
b792688776
66 changed files with 145 additions and 112 deletions
141
src/vfs/image_size_cache.rs
Normal file
141
src/vfs/image_size_cache.rs
Normal file
|
@ -0,0 +1,141 @@
|
|||
use std::{fmt, io::Cursor};
|
||||
|
||||
use anyhow::Context;
|
||||
use dashmap::DashMap;
|
||||
use tracing::{info_span, instrument, warn};
|
||||
|
||||
use crate::config;
|
||||
|
||||
use super::{query, Content, Dir, ImageSize, Query, VPath, VPathBuf};
|
||||
|
||||
pub struct ImageSizeCache<T> {
|
||||
inner: T,
|
||||
cache: DashMap<VPathBuf, ImageSize>,
|
||||
}
|
||||
|
||||
impl<T> ImageSizeCache<T> {
|
||||
pub fn new(inner: T) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
cache: DashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ImageSizeCache<T>
|
||||
where
|
||||
T: Dir,
|
||||
{
|
||||
fn compute_image_size(&self, path: &VPath) -> anyhow::Result<Option<ImageSize>> {
|
||||
if path.extension().is_some_and(config::is_image_file) {
|
||||
if let Some(content) = query::<Content>(&self.inner, path) {
|
||||
if path.extension() == Some("svg") {
|
||||
return Ok(svg_size(&content.string()?));
|
||||
} else {
|
||||
let _span = info_span!("raster_image_size").entered();
|
||||
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 }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
#[instrument("ImageSizeCache::image_size", skip(self))]
|
||||
fn image_size(&self, path: &VPath) -> Option<ImageSize> {
|
||||
self.cache.get(path).map(|x| *x).or_else(|| {
|
||||
let _span = info_span!("cache_miss").entered();
|
||||
|
||||
let image_size = self
|
||||
.compute_image_size(path)
|
||||
.inspect_err(|err| warn!(%path, ?err, "compute_image_size failure"))
|
||||
.ok()
|
||||
.flatten();
|
||||
if let Some(image_size) = image_size {
|
||||
self.cache.insert(path.to_owned(), image_size);
|
||||
}
|
||||
image_size
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Dir for ImageSizeCache<T>
|
||||
where
|
||||
T: Dir,
|
||||
{
|
||||
fn query(&self, path: &VPath, query: &mut Query) {
|
||||
query.try_provide(|| self.image_size(path));
|
||||
|
||||
self.inner.query(path, query);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for ImageSizeCache<T>
|
||||
where
|
||||
T: fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
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…
Add table
Add a link
Reference in a new issue