Merge pull request #1987 from ehuss/theme-fonts
Make fonts part of the theme.
This commit is contained in:
commit
2c710d3b7d
|
@ -126,7 +126,10 @@ The following configuration options are available:
|
||||||
that occur in code blocks and code spans. Defaults to `false`.
|
that occur in code blocks and code spans. Defaults to `false`.
|
||||||
- **mathjax-support:** Adds support for [MathJax](../mathjax.md). Defaults to
|
- **mathjax-support:** Adds support for [MathJax](../mathjax.md). Defaults to
|
||||||
`false`.
|
`false`.
|
||||||
- **copy-fonts:** Copies fonts.css and respective font files to the output directory and use them in the default theme. Defaults to `true`.
|
- **copy-fonts:** (**Deprecated**) If `true` (the default), mdBook uses its built-in fonts which are copied to the output directory.
|
||||||
|
If `false`, the built-in fonts will not be used.
|
||||||
|
This option is deprecated. If you want to define your own custom fonts,
|
||||||
|
create a `theme/fonts/fonts.css` file and store the fonts in the `theme/fonts/` directory.
|
||||||
- **google-analytics:** This field has been deprecated and will be removed in a future release.
|
- **google-analytics:** This field has been deprecated and will be removed in a future release.
|
||||||
Use the `theme/head.hbs` file to add the appropriate Google Analytics code instead.
|
Use the `theme/head.hbs` file to add the appropriate Google Analytics code instead.
|
||||||
- **additional-css:** If you need to slightly change the appearance of your book
|
- **additional-css:** If you need to slightly change the appearance of your book
|
||||||
|
|
|
@ -26,6 +26,8 @@ Here are the files you can override:
|
||||||
- **_highlight.css_** is the theme used for the code highlighting.
|
- **_highlight.css_** is the theme used for the code highlighting.
|
||||||
- **_favicon.svg_** and **_favicon.png_** the favicon that will be used. The SVG
|
- **_favicon.svg_** and **_favicon.png_** the favicon that will be used. The SVG
|
||||||
version is used by [newer browsers].
|
version is used by [newer browsers].
|
||||||
|
- **fonts/fonts.css** contains the definition of which fonts to load.
|
||||||
|
Custom fonts can be included in the `fonts` directory.
|
||||||
|
|
||||||
Generally, when you want to tweak the theme, you don't need to override all the
|
Generally, when you want to tweak the theme, you don't need to override all the
|
||||||
files. If you only need changes in the stylesheet, there is no point in
|
files. If you only need changes in the stylesheet, there is no point in
|
||||||
|
|
|
@ -6,6 +6,7 @@ use super::MDBook;
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::errors::*;
|
use crate::errors::*;
|
||||||
use crate::theme;
|
use crate::theme;
|
||||||
|
use crate::utils::fs::write_file;
|
||||||
use log::{debug, error, info, trace};
|
use log::{debug, error, info, trace};
|
||||||
|
|
||||||
/// A helper for setting up a new book and its directory structure.
|
/// A helper for setting up a new book and its directory structure.
|
||||||
|
@ -160,6 +161,19 @@ impl BookBuilder {
|
||||||
let mut highlight_js = File::create(themedir.join("highlight.js"))?;
|
let mut highlight_js = File::create(themedir.join("highlight.js"))?;
|
||||||
highlight_js.write_all(theme::HIGHLIGHT_JS)?;
|
highlight_js.write_all(theme::HIGHLIGHT_JS)?;
|
||||||
|
|
||||||
|
write_file(&themedir.join("fonts"), "fonts.css", theme::fonts::CSS)?;
|
||||||
|
for (file_name, contents) in theme::fonts::LICENSES {
|
||||||
|
write_file(&themedir, file_name, contents)?;
|
||||||
|
}
|
||||||
|
for (file_name, contents) in theme::fonts::OPEN_SANS.iter() {
|
||||||
|
write_file(&themedir, file_name, contents)?;
|
||||||
|
}
|
||||||
|
write_file(
|
||||||
|
&themedir,
|
||||||
|
theme::fonts::SOURCE_CODE_PRO.0,
|
||||||
|
theme::fonts::SOURCE_CODE_PRO.1,
|
||||||
|
)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -289,6 +289,31 @@ impl HtmlHandlebars {
|
||||||
theme::fonts::SOURCE_CODE_PRO.1,
|
theme::fonts::SOURCE_CODE_PRO.1,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
if let Some(fonts_css) = &theme.fonts_css {
|
||||||
|
if !fonts_css.is_empty() {
|
||||||
|
if html_config.copy_fonts {
|
||||||
|
warn!(
|
||||||
|
"output.html.copy_fonts is deprecated.\n\
|
||||||
|
Set copy_fonts=false and ensure the fonts you want are in \
|
||||||
|
the `theme/fonts/` directory."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
write_file(destination, "fonts/fonts.css", &fonts_css)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !html_config.copy_fonts && theme.fonts_css.is_none() {
|
||||||
|
warn!(
|
||||||
|
"output.html.copy_fonts is deprecated.\n\
|
||||||
|
This book appears to have copy_fonts=false without a fonts.css file.\n\
|
||||||
|
Add an empty `theme/fonts/fonts.css` file to squelch this warning."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for font_file in &theme.font_files {
|
||||||
|
let contents = fs::read(font_file)?;
|
||||||
|
let filename = font_file.file_name().unwrap();
|
||||||
|
let filename = Path::new("fonts").join(filename);
|
||||||
|
write_file(destination, filename, &contents)?;
|
||||||
|
}
|
||||||
|
|
||||||
let playground_config = &html_config.playground;
|
let playground_config = &html_config.playground;
|
||||||
|
|
||||||
|
@ -656,7 +681,8 @@ fn make_data(
|
||||||
data.insert("mathjax_support".to_owned(), json!(true));
|
data.insert("mathjax_support".to_owned(), json!(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
if html_config.copy_fonts {
|
// This `matches!` checks for a non-empty file.
|
||||||
|
if html_config.copy_fonts || matches!(theme.fonts_css.as_deref(), Some([_, ..])) {
|
||||||
data.insert("copy_fonts".to_owned(), json!(true));
|
data.insert("copy_fonts".to_owned(), json!(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ pub mod searcher;
|
||||||
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::path::Path;
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use crate::errors::*;
|
use crate::errors::*;
|
||||||
use log::warn;
|
use log::warn;
|
||||||
|
@ -54,6 +54,8 @@ pub struct Theme {
|
||||||
pub general_css: Vec<u8>,
|
pub general_css: Vec<u8>,
|
||||||
pub print_css: Vec<u8>,
|
pub print_css: Vec<u8>,
|
||||||
pub variables_css: Vec<u8>,
|
pub variables_css: Vec<u8>,
|
||||||
|
pub fonts_css: Option<Vec<u8>>,
|
||||||
|
pub font_files: Vec<PathBuf>,
|
||||||
pub favicon_png: Option<Vec<u8>>,
|
pub favicon_png: Option<Vec<u8>>,
|
||||||
pub favicon_svg: Option<Vec<u8>>,
|
pub favicon_svg: Option<Vec<u8>>,
|
||||||
pub js: Vec<u8>,
|
pub js: Vec<u8>,
|
||||||
|
@ -104,7 +106,7 @@ impl Theme {
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
let load_with_warn = |filename: &Path, dest| {
|
let load_with_warn = |filename: &Path, dest: &mut Vec<u8>| {
|
||||||
if !filename.exists() {
|
if !filename.exists() {
|
||||||
// Don't warn if the file doesn't exist.
|
// Don't warn if the file doesn't exist.
|
||||||
return false;
|
return false;
|
||||||
|
@ -121,6 +123,29 @@ impl Theme {
|
||||||
load_with_warn(&filename, dest);
|
load_with_warn(&filename, dest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let fonts_dir = theme_dir.join("fonts");
|
||||||
|
if fonts_dir.exists() {
|
||||||
|
let mut fonts_css = Vec::new();
|
||||||
|
if load_with_warn(&fonts_dir.join("fonts.css"), &mut fonts_css) {
|
||||||
|
theme.fonts_css.replace(fonts_css);
|
||||||
|
}
|
||||||
|
if let Ok(entries) = fonts_dir.read_dir() {
|
||||||
|
theme.font_files = entries
|
||||||
|
.filter_map(|entry| {
|
||||||
|
let entry = entry.ok()?;
|
||||||
|
if entry.file_name() == "fonts.css" {
|
||||||
|
None
|
||||||
|
} else if entry.file_type().ok()?.is_dir() {
|
||||||
|
log::info!("skipping font directory {:?}", entry.path());
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(entry.path())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If the user overrides one favicon, but not the other, do not
|
// If the user overrides one favicon, but not the other, do not
|
||||||
// copy the default for the other.
|
// copy the default for the other.
|
||||||
let favicon_png = &mut theme.favicon_png.as_mut().unwrap();
|
let favicon_png = &mut theme.favicon_png.as_mut().unwrap();
|
||||||
|
@ -153,6 +178,8 @@ impl Default for Theme {
|
||||||
general_css: GENERAL_CSS.to_owned(),
|
general_css: GENERAL_CSS.to_owned(),
|
||||||
print_css: PRINT_CSS.to_owned(),
|
print_css: PRINT_CSS.to_owned(),
|
||||||
variables_css: VARIABLES_CSS.to_owned(),
|
variables_css: VARIABLES_CSS.to_owned(),
|
||||||
|
fonts_css: None,
|
||||||
|
font_files: Vec::new(),
|
||||||
favicon_png: Some(FAVICON_PNG.to_owned()),
|
favicon_png: Some(FAVICON_PNG.to_owned()),
|
||||||
favicon_svg: Some(FAVICON_SVG.to_owned()),
|
favicon_svg: Some(FAVICON_SVG.to_owned()),
|
||||||
js: JS.to_owned(),
|
js: JS.to_owned(),
|
||||||
|
@ -209,10 +236,10 @@ mod tests {
|
||||||
"favicon.png",
|
"favicon.png",
|
||||||
"favicon.svg",
|
"favicon.svg",
|
||||||
"css/chrome.css",
|
"css/chrome.css",
|
||||||
"css/fonts.css",
|
|
||||||
"css/general.css",
|
"css/general.css",
|
||||||
"css/print.css",
|
"css/print.css",
|
||||||
"css/variables.css",
|
"css/variables.css",
|
||||||
|
"fonts/fonts.css",
|
||||||
"book.js",
|
"book.js",
|
||||||
"highlight.js",
|
"highlight.js",
|
||||||
"tomorrow-night.css",
|
"tomorrow-night.css",
|
||||||
|
@ -223,6 +250,7 @@ mod tests {
|
||||||
|
|
||||||
let temp = TempFileBuilder::new().prefix("mdbook-").tempdir().unwrap();
|
let temp = TempFileBuilder::new().prefix("mdbook-").tempdir().unwrap();
|
||||||
fs::create_dir(temp.path().join("css")).unwrap();
|
fs::create_dir(temp.path().join("css")).unwrap();
|
||||||
|
fs::create_dir(temp.path().join("fonts")).unwrap();
|
||||||
|
|
||||||
// "touch" all of the special files so we have empty copies
|
// "touch" all of the special files so we have empty copies
|
||||||
for file in &files {
|
for file in &files {
|
||||||
|
@ -240,6 +268,8 @@ mod tests {
|
||||||
general_css: Vec::new(),
|
general_css: Vec::new(),
|
||||||
print_css: Vec::new(),
|
print_css: Vec::new(),
|
||||||
variables_css: Vec::new(),
|
variables_css: Vec::new(),
|
||||||
|
fonts_css: Some(Vec::new()),
|
||||||
|
font_files: Vec::new(),
|
||||||
favicon_png: Some(Vec::new()),
|
favicon_png: Some(Vec::new()),
|
||||||
favicon_svg: Some(Vec::new()),
|
favicon_svg: Some(Vec::new()),
|
||||||
js: Vec::new(),
|
js: Vec::new(),
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use mdbook::config::Config;
|
use mdbook::config::Config;
|
||||||
use mdbook::MDBook;
|
use mdbook::MDBook;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
|
@ -121,6 +122,20 @@ fn copy_theme() {
|
||||||
"css/variables.css",
|
"css/variables.css",
|
||||||
"favicon.png",
|
"favicon.png",
|
||||||
"favicon.svg",
|
"favicon.svg",
|
||||||
|
"fonts/OPEN-SANS-LICENSE.txt",
|
||||||
|
"fonts/SOURCE-CODE-PRO-LICENSE.txt",
|
||||||
|
"fonts/fonts.css",
|
||||||
|
"fonts/open-sans-v17-all-charsets-300.woff2",
|
||||||
|
"fonts/open-sans-v17-all-charsets-300italic.woff2",
|
||||||
|
"fonts/open-sans-v17-all-charsets-600.woff2",
|
||||||
|
"fonts/open-sans-v17-all-charsets-600italic.woff2",
|
||||||
|
"fonts/open-sans-v17-all-charsets-700.woff2",
|
||||||
|
"fonts/open-sans-v17-all-charsets-700italic.woff2",
|
||||||
|
"fonts/open-sans-v17-all-charsets-800.woff2",
|
||||||
|
"fonts/open-sans-v17-all-charsets-800italic.woff2",
|
||||||
|
"fonts/open-sans-v17-all-charsets-italic.woff2",
|
||||||
|
"fonts/open-sans-v17-all-charsets-regular.woff2",
|
||||||
|
"fonts/source-code-pro-v11-all-charsets-500.woff2",
|
||||||
"highlight.css",
|
"highlight.css",
|
||||||
"highlight.js",
|
"highlight.js",
|
||||||
"index.hbs",
|
"index.hbs",
|
||||||
|
|
|
@ -1,6 +1,3 @@
|
||||||
#[macro_use]
|
|
||||||
extern crate pretty_assertions;
|
|
||||||
|
|
||||||
mod dummy_book;
|
mod dummy_book;
|
||||||
|
|
||||||
use crate::dummy_book::{assert_contains_strings, assert_doesnt_contain_strings, DummyBook};
|
use crate::dummy_book::{assert_contains_strings, assert_doesnt_contain_strings, DummyBook};
|
||||||
|
@ -10,6 +7,7 @@ use mdbook::config::Config;
|
||||||
use mdbook::errors::*;
|
use mdbook::errors::*;
|
||||||
use mdbook::utils::fs::write_file;
|
use mdbook::utils::fs::write_file;
|
||||||
use mdbook::MDBook;
|
use mdbook::MDBook;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
use select::document::Document;
|
use select::document::Document;
|
||||||
use select::predicate::{Class, Name, Predicate};
|
use select::predicate::{Class, Name, Predicate};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
@ -842,3 +840,111 @@ mod search {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn custom_fonts() {
|
||||||
|
// Tests to ensure custom fonts are copied as expected.
|
||||||
|
let builtin_fonts = [
|
||||||
|
"OPEN-SANS-LICENSE.txt",
|
||||||
|
"SOURCE-CODE-PRO-LICENSE.txt",
|
||||||
|
"fonts.css",
|
||||||
|
"open-sans-v17-all-charsets-300.woff2",
|
||||||
|
"open-sans-v17-all-charsets-300italic.woff2",
|
||||||
|
"open-sans-v17-all-charsets-600.woff2",
|
||||||
|
"open-sans-v17-all-charsets-600italic.woff2",
|
||||||
|
"open-sans-v17-all-charsets-700.woff2",
|
||||||
|
"open-sans-v17-all-charsets-700italic.woff2",
|
||||||
|
"open-sans-v17-all-charsets-800.woff2",
|
||||||
|
"open-sans-v17-all-charsets-800italic.woff2",
|
||||||
|
"open-sans-v17-all-charsets-italic.woff2",
|
||||||
|
"open-sans-v17-all-charsets-regular.woff2",
|
||||||
|
"source-code-pro-v11-all-charsets-500.woff2",
|
||||||
|
];
|
||||||
|
let actual_files = |path: &Path| -> Vec<String> {
|
||||||
|
let mut actual: Vec<_> = path
|
||||||
|
.read_dir()
|
||||||
|
.unwrap()
|
||||||
|
.map(|entry| entry.unwrap().file_name().into_string().unwrap())
|
||||||
|
.collect();
|
||||||
|
actual.sort();
|
||||||
|
actual
|
||||||
|
};
|
||||||
|
let has_fonts_css = |path: &Path| -> bool {
|
||||||
|
let contents = fs::read_to_string(path.join("book/index.html")).unwrap();
|
||||||
|
contents.contains("fonts/fonts.css")
|
||||||
|
};
|
||||||
|
|
||||||
|
// No theme:
|
||||||
|
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
|
||||||
|
let p = temp.path();
|
||||||
|
MDBook::init(p).build().unwrap();
|
||||||
|
MDBook::load(p).unwrap().build().unwrap();
|
||||||
|
assert_eq!(actual_files(&p.join("book/fonts")), &builtin_fonts);
|
||||||
|
assert!(has_fonts_css(p));
|
||||||
|
|
||||||
|
// Full theme.
|
||||||
|
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
|
||||||
|
let p = temp.path();
|
||||||
|
MDBook::init(p).copy_theme(true).build().unwrap();
|
||||||
|
assert_eq!(actual_files(&p.join("theme/fonts")), &builtin_fonts);
|
||||||
|
MDBook::load(p).unwrap().build().unwrap();
|
||||||
|
assert_eq!(actual_files(&p.join("book/fonts")), &builtin_fonts);
|
||||||
|
assert!(has_fonts_css(p));
|
||||||
|
|
||||||
|
// Mixed with copy_fonts=true
|
||||||
|
// This should generate a deprecation warning.
|
||||||
|
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
|
||||||
|
let p = temp.path();
|
||||||
|
MDBook::init(p).build().unwrap();
|
||||||
|
write_file(&p.join("theme/fonts"), "fonts.css", b"/*custom*/").unwrap();
|
||||||
|
write_file(&p.join("theme/fonts"), "myfont.woff", b"").unwrap();
|
||||||
|
MDBook::load(p).unwrap().build().unwrap();
|
||||||
|
assert!(has_fonts_css(p));
|
||||||
|
let mut expected = Vec::from(builtin_fonts);
|
||||||
|
expected.push("myfont.woff");
|
||||||
|
expected.sort();
|
||||||
|
assert_eq!(actual_files(&p.join("book/fonts")), expected.as_slice());
|
||||||
|
|
||||||
|
// copy-fonts=false, no theme
|
||||||
|
// This should generate a deprecation warning.
|
||||||
|
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
|
||||||
|
let p = temp.path();
|
||||||
|
MDBook::init(p).build().unwrap();
|
||||||
|
let config = Config::from_str("output.html.copy-fonts = false").unwrap();
|
||||||
|
MDBook::load_with_config(p, config)
|
||||||
|
.unwrap()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
assert!(!has_fonts_css(p));
|
||||||
|
assert!(!p.join("book/fonts").exists());
|
||||||
|
|
||||||
|
// copy-fonts=false with empty fonts.css
|
||||||
|
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
|
||||||
|
let p = temp.path();
|
||||||
|
MDBook::init(p).build().unwrap();
|
||||||
|
write_file(&p.join("theme/fonts"), "fonts.css", b"").unwrap();
|
||||||
|
let config = Config::from_str("output.html.copy-fonts = false").unwrap();
|
||||||
|
MDBook::load_with_config(p, config)
|
||||||
|
.unwrap()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
assert!(!has_fonts_css(p));
|
||||||
|
assert!(!p.join("book/fonts").exists());
|
||||||
|
|
||||||
|
// copy-fonts=false with fonts theme
|
||||||
|
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
|
||||||
|
let p = temp.path();
|
||||||
|
MDBook::init(p).build().unwrap();
|
||||||
|
write_file(&p.join("theme/fonts"), "fonts.css", b"/*custom*/").unwrap();
|
||||||
|
write_file(&p.join("theme/fonts"), "myfont.woff", b"").unwrap();
|
||||||
|
let config = Config::from_str("output.html.copy-fonts = false").unwrap();
|
||||||
|
MDBook::load_with_config(p, config)
|
||||||
|
.unwrap()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
assert!(has_fonts_css(p));
|
||||||
|
assert_eq!(
|
||||||
|
actual_files(&p.join("book/fonts")),
|
||||||
|
&["fonts.css", "myfont.woff"]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue