diff --git a/crates/mdbook-html/src/html_handlebars/hbs_renderer.rs b/crates/mdbook-html/src/html_handlebars/hbs_renderer.rs index 8edac3cace..42917b89b6 100644 --- a/crates/mdbook-html/src/html_handlebars/hbs_renderer.rs +++ b/crates/mdbook-html/src/html_handlebars/hbs_renderer.rs @@ -332,7 +332,35 @@ 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..4d9f3f6ac9 100644 --- a/crates/mdbook-html/src/theme/mod.rs +++ b/crates/mdbook-html/src/theme/mod.rs @@ -64,7 +64,17 @@ 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 +125,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 { + 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 +184,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 { + png = override_binary(&dir.join("favicon.png"), favicon_png) || png; + svg = override_binary(&dir.join("favicon.svg"), favicon_svg) || svg; + } + match (png, svg) { (true, true) | (false, false) => {} (true, false) => {