From b8a8bbbb9fd6939f8d41ddeb357c7a139f9c970f Mon Sep 17 00:00:00 2001 From: matheusds4 Date: Thu, 5 Feb 2026 13:47:25 -0300 Subject: [PATCH 1/3] allow overriding subtheme --- .../src/html_handlebars/hbs_renderer.rs | 18 ++++++- crates/mdbook-html/src/theme/mod.rs | 47 ++++++++++++++++++- 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/crates/mdbook-html/src/html_handlebars/hbs_renderer.rs b/crates/mdbook-html/src/html_handlebars/hbs_renderer.rs index 8edac3cace..bd61572412 100644 --- a/crates/mdbook-html/src/html_handlebars/hbs_renderer.rs +++ b/crates/mdbook-html/src/html_handlebars/hbs_renderer.rs @@ -332,7 +332,23 @@ impl Renderer for HtmlHandlebars { None => ctx.root.join("theme"), }; - let theme = Theme::new(theme_dir); + // the user-specified theme directory may still be overridden + // by an additional "theme/" directory. + // this is useful when one needs to integrate an external + // theme project, but still modify certain parts of it. + use std::str::FromStr; + let override_theme_dir = ctx.root.join("theme"); + let mut canon_theme_dir = theme_dir.canonicalize().unwrap_or(theme_dir.clone()); + let mut canon_override_theme_dir = override_theme_dir.canonicalize().unwrap_or(override_theme_dir.clone()); + let re = regex::Regex::new(r###"[\\/]$"###).unwrap(); + canon_theme_dir = PathBuf::from_str(&re.replace(canon_theme_dir.to_str().unwrap(), "").into_owned()).unwrap(); + canon_override_theme_dir = PathBuf::from_str(&re.replace(canon_override_theme_dir.to_str().unwrap(), "").into_owned()).unwrap(); + let mut override_theme_dir = Some(override_theme_dir); + if canon_theme_dir == canon_override_theme_dir || override_theme_dir.as_ref().map_or(true, |d| !d.is_dir()) { + override_theme_dir = None; + } + + let theme = Theme::new_with_override(theme_dir, override_theme_dir); debug!("Register the index handlebars template"); handlebars.register_template_string("index", String::from_utf8(theme.index.clone())?)?; diff --git a/crates/mdbook-html/src/theme/mod.rs b/crates/mdbook-html/src/theme/mod.rs index c55a30eff3..7e9d6e74f4 100644 --- a/crates/mdbook-html/src/theme/mod.rs +++ b/crates/mdbook-html/src/theme/mod.rs @@ -64,7 +64,14 @@ impl Theme { /// Creates a `Theme` from the given `theme_dir`. /// If a file is found in the theme dir, it will override the default version. pub fn new>(theme_dir: P) -> Self { + Self::new_with_override::<_, &str>(theme_dir, None) + } + + /// Creates a `Theme` from the given `theme_dir` and, optionally, + /// a semi-overriding `override_theme_dir`. + pub fn new_with_override, O: AsRef>(theme_dir: P, override_theme_dir: Option) -> Self { let theme_dir = theme_dir.as_ref(); + let override_theme_dir = override_theme_dir.as_ref().map(|p| p.as_ref()); let mut theme = Theme::default(); // If the theme directory doesn't exist there's no point continuing... @@ -115,10 +122,39 @@ impl Theme { } }; + let override_binary = |filename: &Path, dest: &mut Vec| { + if !filename.exists() { + return false; + } + load_file_contents(filename, dest).is_ok() + }; + + let combine_utf = |filename: &Path, dest: &mut Vec| { + if !filename.exists() { + return false; + } + let mut tmp: Vec = Vec::new(); + if load_file_contents(filename, &mut tmp).is_err() { + false + } else { + let mut new_str = String::from_utf8(dest.clone()).unwrap(); + new_str.push_str("\n\n"); + new_str.push_str(&String::from_utf8(tmp).unwrap()); + *dest = new_str.as_bytes().iter().cloned().collect::<_>(); + true + } + }; + for (filename, dest) in files { load_with_warn(&filename, dest); } + // extra theme/ overrides + if let Some(dir) = override_theme_dir.clone() { + combine_utf(&dir.join("head.hbs"), &mut theme.head); + override_binary(&dir.join("highlight.js"), &mut theme.highlight_js); + } + let fonts_dir = theme_dir.join("fonts"); if fonts_dir.exists() { let mut fonts_css = Vec::new(); @@ -145,9 +181,16 @@ impl Theme { // If the user overrides one favicon, but not the other, do not // copy the default for the other. let favicon_png = &mut theme.favicon_png.as_mut().unwrap(); - let png = load_with_warn(&theme_dir.join("favicon.png"), favicon_png); + let mut png = load_with_warn(&theme_dir.join("favicon.png"), favicon_png); let favicon_svg = &mut theme.favicon_svg.as_mut().unwrap(); - let svg = load_with_warn(&theme_dir.join("favicon.svg"), favicon_svg); + let mut svg = load_with_warn(&theme_dir.join("favicon.svg"), favicon_svg); + + // one more overrider for the favicon + if let Some(dir) = override_theme_dir.clone() { + png = png || override_binary(&dir.join("favicon.png"), favicon_png); + svg = svg || override_binary(&dir.join("favicon.svg"), favicon_svg); + } + match (png, svg) { (true, true) | (false, false) => {} (true, false) => { From b812ef00161127ab437464d420a1f8c7467e698f Mon Sep 17 00:00:00 2001 From: matheusds4 Date: Thu, 5 Feb 2026 13:59:03 -0300 Subject: [PATCH 2/3] run fmt and clippy --- .../src/html_handlebars/hbs_renderer.rs | 20 +++++++++++++++---- crates/mdbook-html/src/theme/mod.rs | 9 ++++++--- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/crates/mdbook-html/src/html_handlebars/hbs_renderer.rs b/crates/mdbook-html/src/html_handlebars/hbs_renderer.rs index bd61572412..42917b89b6 100644 --- a/crates/mdbook-html/src/html_handlebars/hbs_renderer.rs +++ b/crates/mdbook-html/src/html_handlebars/hbs_renderer.rs @@ -339,12 +339,24 @@ impl Renderer for HtmlHandlebars { use std::str::FromStr; let override_theme_dir = ctx.root.join("theme"); let mut canon_theme_dir = theme_dir.canonicalize().unwrap_or(theme_dir.clone()); - let mut canon_override_theme_dir = override_theme_dir.canonicalize().unwrap_or(override_theme_dir.clone()); + let mut canon_override_theme_dir = override_theme_dir + .canonicalize() + .unwrap_or(override_theme_dir.clone()); let re = regex::Regex::new(r###"[\\/]$"###).unwrap(); - canon_theme_dir = PathBuf::from_str(&re.replace(canon_theme_dir.to_str().unwrap(), "").into_owned()).unwrap(); - canon_override_theme_dir = PathBuf::from_str(&re.replace(canon_override_theme_dir.to_str().unwrap(), "").into_owned()).unwrap(); + canon_theme_dir = PathBuf::from_str( + &re.replace(canon_theme_dir.to_str().unwrap(), "") + .into_owned(), + ) + .unwrap(); + canon_override_theme_dir = PathBuf::from_str( + &re.replace(canon_override_theme_dir.to_str().unwrap(), "") + .into_owned(), + ) + .unwrap(); let mut override_theme_dir = Some(override_theme_dir); - if canon_theme_dir == canon_override_theme_dir || override_theme_dir.as_ref().map_or(true, |d| !d.is_dir()) { + if canon_theme_dir == canon_override_theme_dir + || override_theme_dir.as_ref().map_or(true, |d| !d.is_dir()) + { override_theme_dir = None; } diff --git a/crates/mdbook-html/src/theme/mod.rs b/crates/mdbook-html/src/theme/mod.rs index 7e9d6e74f4..1095df1fa4 100644 --- a/crates/mdbook-html/src/theme/mod.rs +++ b/crates/mdbook-html/src/theme/mod.rs @@ -69,7 +69,10 @@ impl Theme { /// Creates a `Theme` from the given `theme_dir` and, optionally, /// a semi-overriding `override_theme_dir`. - pub fn new_with_override, O: AsRef>(theme_dir: P, override_theme_dir: Option) -> Self { + pub fn new_with_override, O: AsRef>( + theme_dir: P, + override_theme_dir: Option, + ) -> Self { let theme_dir = theme_dir.as_ref(); let override_theme_dir = override_theme_dir.as_ref().map(|p| p.as_ref()); let mut theme = Theme::default(); @@ -150,7 +153,7 @@ impl Theme { } // extra theme/ overrides - if let Some(dir) = override_theme_dir.clone() { + if let Some(dir) = override_theme_dir { combine_utf(&dir.join("head.hbs"), &mut theme.head); override_binary(&dir.join("highlight.js"), &mut theme.highlight_js); } @@ -186,7 +189,7 @@ impl Theme { let mut svg = load_with_warn(&theme_dir.join("favicon.svg"), favicon_svg); // one more overrider for the favicon - if let Some(dir) = override_theme_dir.clone() { + if let Some(dir) = override_theme_dir { png = png || override_binary(&dir.join("favicon.png"), favicon_png); svg = svg || override_binary(&dir.join("favicon.svg"), favicon_svg); } From b66fa5a1220bd6a1a1b84248d06af1f82ad188ac Mon Sep 17 00:00:00 2001 From: matheusds4 Date: Fri, 6 Feb 2026 14:45:22 -0300 Subject: [PATCH 3/3] handle suboverride favicons better --- crates/mdbook-html/src/theme/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/mdbook-html/src/theme/mod.rs b/crates/mdbook-html/src/theme/mod.rs index 1095df1fa4..4d9f3f6ac9 100644 --- a/crates/mdbook-html/src/theme/mod.rs +++ b/crates/mdbook-html/src/theme/mod.rs @@ -190,8 +190,8 @@ impl Theme { // one more overrider for the favicon if let Some(dir) = override_theme_dir { - png = png || override_binary(&dir.join("favicon.png"), favicon_png); - svg = svg || override_binary(&dir.join("favicon.svg"), favicon_svg); + png = override_binary(&dir.join("favicon.png"), favicon_png) || png; + svg = override_binary(&dir.join("favicon.svg"), favicon_svg) || svg; } match (png, svg) {