Skip to content

Commit 2c710d3

Browse files
authored
Merge pull request #1987 from ehuss/theme-fonts
Make fonts part of the theme.
2 parents 581ab2c + c2d9739 commit 2c710d3

File tree

7 files changed

+204
-8
lines changed

7 files changed

+204
-8
lines changed

guide/src/format/configuration/renderers.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,10 @@ The following configuration options are available:
126126
that occur in code blocks and code spans. Defaults to `false`.
127127
- **mathjax-support:** Adds support for [MathJax](../mathjax.md). Defaults to
128128
`false`.
129-
- **copy-fonts:** Copies fonts.css and respective font files to the output directory and use them in the default theme. Defaults to `true`.
129+
- **copy-fonts:** (**Deprecated**) If `true` (the default), mdBook uses its built-in fonts which are copied to the output directory.
130+
If `false`, the built-in fonts will not be used.
131+
This option is deprecated. If you want to define your own custom fonts,
132+
create a `theme/fonts/fonts.css` file and store the fonts in the `theme/fonts/` directory.
130133
- **google-analytics:** This field has been deprecated and will be removed in a future release.
131134
Use the `theme/head.hbs` file to add the appropriate Google Analytics code instead.
132135
- **additional-css:** If you need to slightly change the appearance of your book

guide/src/format/theme/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ Here are the files you can override:
2626
- **_highlight.css_** is the theme used for the code highlighting.
2727
- **_favicon.svg_** and **_favicon.png_** the favicon that will be used. The SVG
2828
version is used by [newer browsers].
29+
- **fonts/fonts.css** contains the definition of which fonts to load.
30+
Custom fonts can be included in the `fonts` directory.
2931

3032
Generally, when you want to tweak the theme, you don't need to override all the
3133
files. If you only need changes in the stylesheet, there is no point in

src/book/init.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use super::MDBook;
66
use crate::config::Config;
77
use crate::errors::*;
88
use crate::theme;
9+
use crate::utils::fs::write_file;
910
use log::{debug, error, info, trace};
1011

1112
/// A helper for setting up a new book and its directory structure.
@@ -160,6 +161,19 @@ impl BookBuilder {
160161
let mut highlight_js = File::create(themedir.join("highlight.js"))?;
161162
highlight_js.write_all(theme::HIGHLIGHT_JS)?;
162163

164+
write_file(&themedir.join("fonts"), "fonts.css", theme::fonts::CSS)?;
165+
for (file_name, contents) in theme::fonts::LICENSES {
166+
write_file(&themedir, file_name, contents)?;
167+
}
168+
for (file_name, contents) in theme::fonts::OPEN_SANS.iter() {
169+
write_file(&themedir, file_name, contents)?;
170+
}
171+
write_file(
172+
&themedir,
173+
theme::fonts::SOURCE_CODE_PRO.0,
174+
theme::fonts::SOURCE_CODE_PRO.1,
175+
)?;
176+
163177
Ok(())
164178
}
165179

src/renderer/html_handlebars/hbs_renderer.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,31 @@ impl HtmlHandlebars {
289289
theme::fonts::SOURCE_CODE_PRO.1,
290290
)?;
291291
}
292+
if let Some(fonts_css) = &theme.fonts_css {
293+
if !fonts_css.is_empty() {
294+
if html_config.copy_fonts {
295+
warn!(
296+
"output.html.copy_fonts is deprecated.\n\
297+
Set copy_fonts=false and ensure the fonts you want are in \
298+
the `theme/fonts/` directory."
299+
);
300+
}
301+
write_file(destination, "fonts/fonts.css", &fonts_css)?;
302+
}
303+
}
304+
if !html_config.copy_fonts && theme.fonts_css.is_none() {
305+
warn!(
306+
"output.html.copy_fonts is deprecated.\n\
307+
This book appears to have copy_fonts=false without a fonts.css file.\n\
308+
Add an empty `theme/fonts/fonts.css` file to squelch this warning."
309+
);
310+
}
311+
for font_file in &theme.font_files {
312+
let contents = fs::read(font_file)?;
313+
let filename = font_file.file_name().unwrap();
314+
let filename = Path::new("fonts").join(filename);
315+
write_file(destination, filename, &contents)?;
316+
}
292317

293318
let playground_config = &html_config.playground;
294319

@@ -656,7 +681,8 @@ fn make_data(
656681
data.insert("mathjax_support".to_owned(), json!(true));
657682
}
658683

659-
if html_config.copy_fonts {
684+
// This `matches!` checks for a non-empty file.
685+
if html_config.copy_fonts || matches!(theme.fonts_css.as_deref(), Some([_, ..])) {
660686
data.insert("copy_fonts".to_owned(), json!(true));
661687
}
662688

src/theme/mod.rs

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ pub mod searcher;
99

1010
use std::fs::File;
1111
use std::io::Read;
12-
use std::path::Path;
12+
use std::path::{Path, PathBuf};
1313

1414
use crate::errors::*;
1515
use log::warn;
@@ -54,6 +54,8 @@ pub struct Theme {
5454
pub general_css: Vec<u8>,
5555
pub print_css: Vec<u8>,
5656
pub variables_css: Vec<u8>,
57+
pub fonts_css: Option<Vec<u8>>,
58+
pub font_files: Vec<PathBuf>,
5759
pub favicon_png: Option<Vec<u8>>,
5860
pub favicon_svg: Option<Vec<u8>>,
5961
pub js: Vec<u8>,
@@ -104,7 +106,7 @@ impl Theme {
104106
),
105107
];
106108

107-
let load_with_warn = |filename: &Path, dest| {
109+
let load_with_warn = |filename: &Path, dest: &mut Vec<u8>| {
108110
if !filename.exists() {
109111
// Don't warn if the file doesn't exist.
110112
return false;
@@ -121,6 +123,29 @@ impl Theme {
121123
load_with_warn(&filename, dest);
122124
}
123125

126+
let fonts_dir = theme_dir.join("fonts");
127+
if fonts_dir.exists() {
128+
let mut fonts_css = Vec::new();
129+
if load_with_warn(&fonts_dir.join("fonts.css"), &mut fonts_css) {
130+
theme.fonts_css.replace(fonts_css);
131+
}
132+
if let Ok(entries) = fonts_dir.read_dir() {
133+
theme.font_files = entries
134+
.filter_map(|entry| {
135+
let entry = entry.ok()?;
136+
if entry.file_name() == "fonts.css" {
137+
None
138+
} else if entry.file_type().ok()?.is_dir() {
139+
log::info!("skipping font directory {:?}", entry.path());
140+
None
141+
} else {
142+
Some(entry.path())
143+
}
144+
})
145+
.collect();
146+
}
147+
}
148+
124149
// If the user overrides one favicon, but not the other, do not
125150
// copy the default for the other.
126151
let favicon_png = &mut theme.favicon_png.as_mut().unwrap();
@@ -153,6 +178,8 @@ impl Default for Theme {
153178
general_css: GENERAL_CSS.to_owned(),
154179
print_css: PRINT_CSS.to_owned(),
155180
variables_css: VARIABLES_CSS.to_owned(),
181+
fonts_css: None,
182+
font_files: Vec::new(),
156183
favicon_png: Some(FAVICON_PNG.to_owned()),
157184
favicon_svg: Some(FAVICON_SVG.to_owned()),
158185
js: JS.to_owned(),
@@ -209,10 +236,10 @@ mod tests {
209236
"favicon.png",
210237
"favicon.svg",
211238
"css/chrome.css",
212-
"css/fonts.css",
213239
"css/general.css",
214240
"css/print.css",
215241
"css/variables.css",
242+
"fonts/fonts.css",
216243
"book.js",
217244
"highlight.js",
218245
"tomorrow-night.css",
@@ -223,6 +250,7 @@ mod tests {
223250

224251
let temp = TempFileBuilder::new().prefix("mdbook-").tempdir().unwrap();
225252
fs::create_dir(temp.path().join("css")).unwrap();
253+
fs::create_dir(temp.path().join("fonts")).unwrap();
226254

227255
// "touch" all of the special files so we have empty copies
228256
for file in &files {
@@ -240,6 +268,8 @@ mod tests {
240268
general_css: Vec::new(),
241269
print_css: Vec::new(),
242270
variables_css: Vec::new(),
271+
fonts_css: Some(Vec::new()),
272+
font_files: Vec::new(),
243273
favicon_png: Some(Vec::new()),
244274
favicon_svg: Some(Vec::new()),
245275
js: Vec::new(),

tests/init.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use mdbook::config::Config;
22
use mdbook::MDBook;
3+
use pretty_assertions::assert_eq;
34
use std::fs;
45
use std::fs::File;
56
use std::io::prelude::*;
@@ -121,6 +122,20 @@ fn copy_theme() {
121122
"css/variables.css",
122123
"favicon.png",
123124
"favicon.svg",
125+
"fonts/OPEN-SANS-LICENSE.txt",
126+
"fonts/SOURCE-CODE-PRO-LICENSE.txt",
127+
"fonts/fonts.css",
128+
"fonts/open-sans-v17-all-charsets-300.woff2",
129+
"fonts/open-sans-v17-all-charsets-300italic.woff2",
130+
"fonts/open-sans-v17-all-charsets-600.woff2",
131+
"fonts/open-sans-v17-all-charsets-600italic.woff2",
132+
"fonts/open-sans-v17-all-charsets-700.woff2",
133+
"fonts/open-sans-v17-all-charsets-700italic.woff2",
134+
"fonts/open-sans-v17-all-charsets-800.woff2",
135+
"fonts/open-sans-v17-all-charsets-800italic.woff2",
136+
"fonts/open-sans-v17-all-charsets-italic.woff2",
137+
"fonts/open-sans-v17-all-charsets-regular.woff2",
138+
"fonts/source-code-pro-v11-all-charsets-500.woff2",
124139
"highlight.css",
125140
"highlight.js",
126141
"index.hbs",

tests/rendered_output.rs

Lines changed: 109 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
#[macro_use]
2-
extern crate pretty_assertions;
3-
41
mod dummy_book;
52

63
use crate::dummy_book::{assert_contains_strings, assert_doesnt_contain_strings, DummyBook};
@@ -10,6 +7,7 @@ use mdbook::config::Config;
107
use mdbook::errors::*;
118
use mdbook::utils::fs::write_file;
129
use mdbook::MDBook;
10+
use pretty_assertions::assert_eq;
1311
use select::document::Document;
1412
use select::predicate::{Class, Name, Predicate};
1513
use std::collections::HashMap;
@@ -842,3 +840,111 @@ mod search {
842840
}
843841
}
844842
}
843+
844+
#[test]
845+
fn custom_fonts() {
846+
// Tests to ensure custom fonts are copied as expected.
847+
let builtin_fonts = [
848+
"OPEN-SANS-LICENSE.txt",
849+
"SOURCE-CODE-PRO-LICENSE.txt",
850+
"fonts.css",
851+
"open-sans-v17-all-charsets-300.woff2",
852+
"open-sans-v17-all-charsets-300italic.woff2",
853+
"open-sans-v17-all-charsets-600.woff2",
854+
"open-sans-v17-all-charsets-600italic.woff2",
855+
"open-sans-v17-all-charsets-700.woff2",
856+
"open-sans-v17-all-charsets-700italic.woff2",
857+
"open-sans-v17-all-charsets-800.woff2",
858+
"open-sans-v17-all-charsets-800italic.woff2",
859+
"open-sans-v17-all-charsets-italic.woff2",
860+
"open-sans-v17-all-charsets-regular.woff2",
861+
"source-code-pro-v11-all-charsets-500.woff2",
862+
];
863+
let actual_files = |path: &Path| -> Vec<String> {
864+
let mut actual: Vec<_> = path
865+
.read_dir()
866+
.unwrap()
867+
.map(|entry| entry.unwrap().file_name().into_string().unwrap())
868+
.collect();
869+
actual.sort();
870+
actual
871+
};
872+
let has_fonts_css = |path: &Path| -> bool {
873+
let contents = fs::read_to_string(path.join("book/index.html")).unwrap();
874+
contents.contains("fonts/fonts.css")
875+
};
876+
877+
// No theme:
878+
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
879+
let p = temp.path();
880+
MDBook::init(p).build().unwrap();
881+
MDBook::load(p).unwrap().build().unwrap();
882+
assert_eq!(actual_files(&p.join("book/fonts")), &builtin_fonts);
883+
assert!(has_fonts_css(p));
884+
885+
// Full theme.
886+
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
887+
let p = temp.path();
888+
MDBook::init(p).copy_theme(true).build().unwrap();
889+
assert_eq!(actual_files(&p.join("theme/fonts")), &builtin_fonts);
890+
MDBook::load(p).unwrap().build().unwrap();
891+
assert_eq!(actual_files(&p.join("book/fonts")), &builtin_fonts);
892+
assert!(has_fonts_css(p));
893+
894+
// Mixed with copy_fonts=true
895+
// This should generate a deprecation warning.
896+
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
897+
let p = temp.path();
898+
MDBook::init(p).build().unwrap();
899+
write_file(&p.join("theme/fonts"), "fonts.css", b"/*custom*/").unwrap();
900+
write_file(&p.join("theme/fonts"), "myfont.woff", b"").unwrap();
901+
MDBook::load(p).unwrap().build().unwrap();
902+
assert!(has_fonts_css(p));
903+
let mut expected = Vec::from(builtin_fonts);
904+
expected.push("myfont.woff");
905+
expected.sort();
906+
assert_eq!(actual_files(&p.join("book/fonts")), expected.as_slice());
907+
908+
// copy-fonts=false, no theme
909+
// This should generate a deprecation warning.
910+
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
911+
let p = temp.path();
912+
MDBook::init(p).build().unwrap();
913+
let config = Config::from_str("output.html.copy-fonts = false").unwrap();
914+
MDBook::load_with_config(p, config)
915+
.unwrap()
916+
.build()
917+
.unwrap();
918+
assert!(!has_fonts_css(p));
919+
assert!(!p.join("book/fonts").exists());
920+
921+
// copy-fonts=false with empty fonts.css
922+
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
923+
let p = temp.path();
924+
MDBook::init(p).build().unwrap();
925+
write_file(&p.join("theme/fonts"), "fonts.css", b"").unwrap();
926+
let config = Config::from_str("output.html.copy-fonts = false").unwrap();
927+
MDBook::load_with_config(p, config)
928+
.unwrap()
929+
.build()
930+
.unwrap();
931+
assert!(!has_fonts_css(p));
932+
assert!(!p.join("book/fonts").exists());
933+
934+
// copy-fonts=false with fonts theme
935+
let temp = TempFileBuilder::new().prefix("mdbook").tempdir().unwrap();
936+
let p = temp.path();
937+
MDBook::init(p).build().unwrap();
938+
write_file(&p.join("theme/fonts"), "fonts.css", b"/*custom*/").unwrap();
939+
write_file(&p.join("theme/fonts"), "myfont.woff", b"").unwrap();
940+
let config = Config::from_str("output.html.copy-fonts = false").unwrap();
941+
MDBook::load_with_config(p, config)
942+
.unwrap()
943+
.build()
944+
.unwrap();
945+
assert!(has_fonts_css(p));
946+
assert_eq!(
947+
actual_files(&p.join("book/fonts")),
948+
&["fonts.css", "myfont.woff"]
949+
);
950+
}

0 commit comments

Comments
 (0)