Compare commits

...

2 commits

Author SHA1 Message Date
953db2f968
this resolves the crash but not the weird flicker 2024-12-16 19:11:13 +01:00
d9fae875f4
it crashes past ~60 images 2024-12-16 19:11:00 +01:00
6 changed files with 146 additions and 66 deletions

View file

@ -4,6 +4,7 @@ use comrak::nodes::{AstNode, NodeValue};
use comrak::{format_html, parse_document, Arena, ComrakOptions, ListStyleType, Options}; use comrak::{format_html, parse_document, Arena, ComrakOptions, ListStyleType, Options};
use lol_html::html_content::ContentType; use lol_html::html_content::ContentType;
use lol_html::{element, rewrite_str, HtmlRewriter, RewriteStrSettings, Settings}; use lol_html::{element, rewrite_str, HtmlRewriter, RewriteStrSettings, Settings};
use mime_guess::mime;
use ramhorns::Content; use ramhorns::Content;
use std::fmt::Debug; use std::fmt::Debug;
use std::fs; use std::fs;
@ -162,10 +163,15 @@ fn custom_img_size(html: &str) -> String {
rewrite_str( rewrite_str(
html, html,
RewriteStrSettings { RewriteStrSettings {
element_content_handlers: vec![element!("img[alt]", |el| { element_content_handlers: vec![element!("img[alt], svg", |el| {
let alt = el.get_attribute("alt").unwrap(); let alt = el.get_attribute("alt").unwrap_or_default();
let possible_piece = alt.split('|').collect::<Vec<&str>>(); let possible_piece = alt.split('|').collect::<Vec<&str>>();
if el.tag_name() == *"svg" {
el.remove_attribute("width");
el.remove_attribute("height");
}
if possible_piece.len() > 1 { if possible_piece.len() > 1 {
let data = possible_piece.last().unwrap().trim(); let data = possible_piece.last().unwrap().trim();
@ -255,7 +261,7 @@ fn fix_images_and_integration(
.unwrap() .unwrap()
.to_string(); .to_string();
if let Ok(file_contents) = fs::read(&img_path) { if let Ok(mut file_contents) = fs::read(&img_path) {
let mime = mime_guess::from_path(&img_path).first_or_octet_stream(); let mime = mime_guess::from_path(&img_path).first_or_octet_stream();
if recursive && mime == "text/markdown" { if recursive && mime == "text/markdown" {
let file_str = String::from_utf8_lossy(&file_contents).into_owned(); let file_str = String::from_utf8_lossy(&file_contents).into_owned();
@ -286,6 +292,16 @@ fn fix_images_and_integration(
// Store the metadata for later merging // Store the metadata for later merging
additional_metadata.push(data.metadata); additional_metadata.push(data.metadata);
} else if mime == mime::IMAGE_SVG {
let alt = el.get_attribute("alt").unwrap_or_default();
file_contents.drain(0..3);
el.replace(
&format!(
"<svg alt='{alt}'{}",
String::from_utf8(file_contents).unwrap()
),
ContentType::Html,
);
} else { } else {
let image = general_purpose::STANDARD.encode(&file_contents); let image = general_purpose::STANDARD.encode(&file_contents);
el.set_attribute("src", &format!("data:{mime};base64,{image}")) el.set_attribute("src", &format!("data:{mime};base64,{image}"))

View file

@ -58,7 +58,8 @@ main {
} }
/* Images */ /* Images */
img { img,
svg {
max-width: var(--max-width); max-width: var(--max-width);
} }

View file

@ -70,7 +70,8 @@ aside a {
} }
/* Image */ /* Image */
main img { main img,
main svg {
max-width: 100%; max-width: 100%;
} }

View file

@ -37,7 +37,8 @@ main a.anchor {
} }
/* Images */ /* Images */
main img { main img,
main svg {
display: block; display: block;
margin: auto; margin: auto;
} }
@ -249,6 +250,7 @@ table:not(.hljs-ln) tr th:last-child {
/* Prevent figures from splitting accross pages */ /* Prevent figures from splitting accross pages */
article *:has(img), article *:has(img),
article *:has(svg),
table:not(.hljs-ln), table:not(.hljs-ln),
table:not(.hljs-ln) > * { table:not(.hljs-ln) > * {
page-break-inside: avoid; page-break-inside: avoid;

View file

@ -83,7 +83,7 @@ header nav a:hover {
} }
/* Add links */ /* Add links */
a:not(:where([href^="#"], [href^="/"])):not(:has(img))::after { a:not(:where([href^="#"], [href^="/"])):not(:has(img)):not(:has(svg))::after {
content: " (" attr(href) ")"; content: " (" attr(href) ")";
display: inline-block; display: inline-block;
white-space: pre; white-space: pre;
@ -96,7 +96,8 @@ header nav a:hover {
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
img { img,
svg {
filter: brightness(0.8) contrast(1.2); filter: brightness(0.8) contrast(1.2);
} }
} }

View file

@ -4,36 +4,56 @@ const Mode = {
}; };
/** /**
* Change the svg color theme based on the mode * Convert to grayscale
* @param {Mode} mode * @param {{r: number, g: number, b: number}} color
* @returns Number between 0 and 255
*/ */
const svgChangeTheme = (mode) => { const colorToGrayscale = (color) => {
for (const item of document.getElementsByTagName("img")) { return 0.3 * color.r + 0.59 * color.g + 0.11 * color.b;
if (!item.src.startsWith("data:image/svg+xml;base64,")) { };
// Exclude image who aren't SVG and base64 encoded
continue; /**
* Extract colors from an image or SVG element using canvas
* @param {HTMLImageElement | SVGSVGElement} element Image or SVG source element
* @returns {Promise<Array>} Colors representing the element
*/
const extractColors = (element) => {
return new Promise((resolve, reject) => {
// Create a canvas with the same dimensions as the element
const canvas = document.createElement("canvas");
let imageSource;
if (element instanceof SVGSVGElement) {
// SVG element handling
canvas.width = element.viewBox.baseVal.width;
canvas.height = element.viewBox.baseVal.height;
const svgString = new XMLSerializer().serializeToString(element);
const svgBlob = new Blob([svgString], { type: "image/svg+xml" });
const svgUrl = URL.createObjectURL(svgBlob);
imageSource = new Image();
imageSource.src = svgUrl;
imageSource.onload = () => {
processImage(imageSource);
URL.revokeObjectURL(svgUrl);
};
imageSource.onerror = (error) => {
URL.revokeObjectURL(svgUrl);
reject(error);
};
} else {
// Regular image handling
canvas.width = element.naturalWidth || element.width;
canvas.height = element.naturalHeight || element.height;
processImage(element);
} }
/** const processImage = (img) => {
* Convert to grayscale
* @param {{r: number, g: number, b: number}} color
* @returns Number between 0 and 255
*/
const colorToGrayscale = (color) => {
return 0.3 * color.r + 0.59 * color.g + 0.11 * color.b;
};
/**
* Extract color using canvas2d
* @param {HTMLImageElement} image Image source
* @returns Colors represeting the image
*/
const extractColors = (image) => {
const canvas = document.createElement("canvas");
canvas.width = image.naturalWidth;
canvas.height = image.naturalHeight;
const ctx = canvas.getContext("2d"); const ctx = canvas.getContext("2d");
ctx.drawImage(image, 0, 0); ctx.drawImage(img, 0, 0);
const imageData = ctx.getImageData( const imageData = ctx.getImageData(
0, 0,
@ -54,44 +74,83 @@ const svgChangeTheme = (mode) => {
} }
} }
return colors; resolve(colors);
}; };
});
};
// Extract colors /**
const colors = extractColors(item); * Change the color theme based on the mode
* @param {Mode} mode
// Calculate the average grayscale value */
const grayscaleValues = colors.map(colorToGrayscale); const changeTheme = (mode) => {
const totalGrayscale = grayscaleValues.reduce((acc, val) => acc + val, 0); // Process SVG elements
const averageGrayscale = totalGrayscale / grayscaleValues.length; for (const svgElement of document.getElementsByTagName("svg")) {
extractColors(svgElement)
const treshold = 128; .then((colors) => {
applyFilter(mode, svgElement, colors);
const style = "filter: "; })
const dim = "brightness(0.8) contrast(1.2)"; .catch((error) => {
console.error("Error extracting colors:", error);
if (averageGrayscale < treshold && mode === Mode.Dark) { });
item.style = style + dim + " invert(1);";
continue;
}
if (averageGrayscale > treshold && mode === Mode.Light) {
item.style = style + "invert(1);";
continue;
}
if (mode === Mode.Dark) {
item.style = style + `${dim};`;
continue;
}
item.style = "";
} }
// Process SVG images embedded as base64
for (const item of document.getElementsByTagName("img")) {
if (!item.src.startsWith("data:image/svg+xml;base64,")) {
// Exclude images that aren't SVG and base64 encoded
continue;
}
extractColors(item)
.then((colors) => {
applyFilter(mode, item, colors);
})
.catch((error) => {
console.error("Error extracting colors:", error);
});
}
};
/**
* Apply the filter based on the selected mode
* @param {Mode} mode Current theme
* @param {HTMLImageElement | SVGSVGElement} item Element
* @param {{r: number, g: number, b: number}} colors Array of the colors of the element
* @returns
*/
const applyFilter = (mode, item, colors) => {
// Calculate the average grayscale value
const grayscaleValues = colors.map(colorToGrayscale);
const totalGrayscale = grayscaleValues.reduce((acc, val) => acc + val, 0);
const averageGrayscale = totalGrayscale / grayscaleValues.length;
const treshold = 128;
const style = "filter: ";
const dim = "brightness(0.8) contrast(1.2)";
if (averageGrayscale < treshold && mode === Mode.Dark) {
item.style = style + dim + " invert(1);";
return;
}
if (averageGrayscale > treshold && mode === Mode.Light) {
item.style = style + "invert(1);";
return;
}
if (mode === Mode.Dark) {
item.style = style + `${dim};`;
return;
}
item.style = "";
}; };
window.addEventListener("load", () => { window.addEventListener("load", () => {
// Fix SVG images // Fix SVG images
svgChangeTheme( changeTheme(
window.matchMedia("(prefers-color-scheme: dark)").matches window.matchMedia("(prefers-color-scheme: dark)").matches
? Mode.Dark ? Mode.Dark
: Mode.Light : Mode.Light
@ -101,5 +160,5 @@ window.addEventListener("load", () => {
window window
.matchMedia("(prefers-color-scheme: dark)") .matchMedia("(prefers-color-scheme: dark)")
.addEventListener("change", (event) => .addEventListener("change", (event) =>
svgChangeTheme(event.matches ? Mode.Dark : Mode.Light) changeTheme(event.matches ? Mode.Dark : Mode.Light)
); );