use super::*;
pub(super) fn sized(tag: &str, path: &str, base: &str, dir: &Path) -> Option<String> {
if tag.contains(" width=") || tag.contains(" loading=") {
return None;
}
let rel = path.strip_prefix(base)?;
if rel.is_empty() || rel.contains("..") || rel.starts_with('/') {
return None;
}
let ext = ext_of(rel);
if !is_image_ext(&ext) || ext == "svg" {
return None;
}
let (w, h) = picture_size_of(&dir.join(rel))?;
let body = tag
.strip_suffix("/>")
.or_else(|| tag.strip_suffix('>'))?
.trim_end();
Some(format!(
"{body} width=\"{w}\" height=\"{h}\" loading=\"lazy\" decoding=\"async\">"
))
}
const PICTURE_HEAD: usize = 64 * 1024;
fn picture_size_of(file: &Path) -> Option<(u32, u32)> {
use std::io::Read;
let f = std::fs::File::open(file).ok()?;
let mut head = Vec::with_capacity(PICTURE_HEAD);
f.take(PICTURE_HEAD as u64).read_to_end(&mut head).ok()?;
picture_size(&head)
}
pub(super) fn picture_size(b: &[u8]) -> Option<(u32, u32)> {
let be32 = |at: usize| -> Option<u32> {
Some(u32::from_be_bytes(b.get(at..at + 4)?.try_into().ok()?))
};
let be16 = |at: usize| -> Option<u32> {
Some(u32::from(u16::from_be_bytes(
b.get(at..at + 2)?.try_into().ok()?,
)))
};
let le16 = |at: usize| -> Option<u32> {
Some(u32::from(u16::from_le_bytes(
b.get(at..at + 2)?.try_into().ok()?,
)))
};
let le24 = |at: usize| -> Option<u32> {
let x = b.get(at..at + 3)?;
Some(u32::from(x[0]) | u32::from(x[1]) << 8 | u32::from(x[2]) << 16)
};
let some = |w: u32, h: u32| (w > 0 && h > 0).then_some((w, h));
if b.starts_with(b"\x89PNG\r\n\x1a\n") && b.get(12..16) == Some(b"IHDR") {
return some(be32(16)?, be32(20)?);
}
if b.starts_with(b"GIF87a") || b.starts_with(b"GIF89a") {
return some(le16(6)?, le16(8)?);
}
if b.starts_with(b"RIFF") && b.get(8..12) == Some(b"WEBP") {
return match b.get(12..16)? {
b"VP8 " => some(le16(26)? & 0x3fff, le16(28)? & 0x3fff),
b"VP8L" if b.get(20) == Some(&0x2f) => {
let x = b.get(21..25)?;
let w = (u32::from(x[0]) | (u32::from(x[1]) & 0x3f) << 8) + 1;
let h =
(u32::from(x[1]) >> 6 | u32::from(x[2]) << 2 | (u32::from(x[3]) & 0x0f) << 10)
+ 1;
some(w, h)
}
b"VP8X" => some(le24(24)? + 1, le24(27)? + 1),
_ => None,
};
}
if b.starts_with(b"\xff\xd8") {
let mut at = 2;
while at + 4 <= b.len() {
if b[at] != 0xff {
return None;
}
let marker = b[at + 1];
if marker == 0xff {
at += 1;
continue;
}
let len = be16(at + 2)? as usize;
if (0xc0..=0xcf).contains(&marker) && !matches!(marker, 0xc4 | 0xc8 | 0xcc) {
return some(be16(at + 7)?, be16(at + 5)?);
}
if marker == 0x01 || (0xd0..=0xd7).contains(&marker) {
at += 2;
continue;
}
if len < 2 {
return None;
}
at += 2 + len;
}
return None;
}
None
}