···11+use std::{fs::File, io::BufWriter, path::Path};
22+33+use image::ImageReader;
44+use webp::{Encoder, WebPMemory};
55+66+use crate::assets::{Asset, Image, ImageFormat, ImageOptions};
77+88+pub fn process_image(image: &Image, dest_path: &Path, image_options: &ImageOptions) {
99+ let mut img = ImageReader::open(image.path()).unwrap().decode().unwrap();
1010+1111+ let new_format = image_options.format.clone().unwrap_or(ImageFormat::Webp);
1212+ let new_width = image_options.width.unwrap_or(img.width());
1313+ let new_height = image_options.height.unwrap_or(img.height());
1414+1515+ if new_width != img.width() || new_height != img.height() {
1616+ img = img.resize(new_width, new_height, image::imageops::FilterType::Lanczos3);
1717+ }
1818+1919+ // image doesn't support lossy WebP encoding, so we'll use webp directly for that to avoid huge files
2020+ // TODO: Add a feature so that people can choose not to depend on libwebp
2121+ // TODO: Add a way for people to choose lossless WebP encoding, despite the larger file sizes
2222+ if new_format == ImageFormat::Webp {
2323+ let encoder: Encoder = Encoder::from_image(&img).unwrap();
2424+ let webp: WebPMemory = encoder.encode(90f32); // TODO: Allow configuring quality
2525+ std::fs::write(dest_path, &*webp).unwrap();
2626+ } else {
2727+ let file = File::create(dest_path).unwrap();
2828+2929+ let mut writer = BufWriter::new(file);
3030+ img.write_to(&mut writer, new_format.into())
3131+ .unwrap_or_else(|e| {
3232+ panic!(
3333+ "Failed to process image from {} to {}: {}",
3434+ image.path().to_string_lossy(),
3535+ dest_path.to_string_lossy(),
3636+ e
3737+ )
3838+ });
3939+ }
4040+}
+9-9
crates/maudit/src/content/markdown.rs
···2233use glob::glob as glob_fs;
44use log::warn;
55-use pulldown_cmark::{html::push_html, CodeBlockKind, Event, Options, Parser, Tag, TagEnd};
55+use pulldown_cmark::{CodeBlockKind, Event, Options, Parser, Tag, TagEnd, html::push_html};
66use serde::de::DeserializeOwned;
7788pub mod components;
···11111212use crate::{assets::Asset, page::RouteContext};
13131414-use super::{highlight::CodeBlock, slugger, ContentEntry};
1414+use super::{ContentEntry, highlight::CodeBlock, slugger};
15151616/// Represents a Markdown heading.
1717///
···185185 for entry in glob_fs(pattern).unwrap() {
186186 let entry = entry.unwrap();
187187188188- if let Some(extension) = entry.extension() {
189189- if extension != "md" {
190190- warn!("Other file types than Markdown are not supported yet");
191191- continue;
192192- }
188188+ if let Some(extension) = entry.extension()
189189+ && extension != "md"
190190+ {
191191+ warn!("Other file types than Markdown are not supported yet");
192192+ continue;
193193 }
194194195195 let id = entry.file_stem().unwrap().to_str().unwrap().to_string();
···361361 }
362362363363 // TODO: Handle this differently so it's compatible with the component system - erika, 2025-08-24
364364- Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(ref fence))) => {
364364+ Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(fence))) => {
365365 let (block, begin) = CodeBlock::new(fence);
366366 code_block = Some(block);
367367 events[i] = Event::Html(begin.into());
···378378 }
379379380380 // TODO: User should be able to replace the text component too perhaps, but it'd require merging the text events
381381- Event::Text(ref text) => {
381381+ Event::Text(text) => {
382382 if !in_frontmatter {
383383 if in_image {
384384 // This seem to work to create "an empty event", but it's not ideal. Using `events.remove` is probably