Skip to content

Commit

Permalink
Use embedded SVG instead of fonts for icons
Browse files Browse the repository at this point in the history
The [downsides of icon fonts] are well-documented, and also, why ship all of
the icons when it only uses 14?

[downsides of icon fonts]: https://speakerdeck.com/ninjanails/death-to-icon-fonts
  • Loading branch information
notriddle committed Jun 8, 2024
1 parent 94b922d commit 8763755
Show file tree
Hide file tree
Showing 21 changed files with 223 additions and 2,766 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ clap = { version = "4.3.12", features = ["cargo", "wrap_help"] }
clap_complete = "4.3.2"
once_cell = "1.17.1"
env_logger = "0.11.1"
font-awesome-as-a-crate = "0.3.0"
handlebars = "5.0"
log = "0.4.17"
memchr = "2.5.0"
Expand Down
4 changes: 2 additions & 2 deletions guide/src/format/configuration/renderers.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ additional-css = ["custom.css", "custom2.css"]
additional-js = ["custom.js"]
no-section-label = false
git-repository-url = "https://github.com/rust-lang/mdBook"
git-repository-icon = "fa-github"
git-repository-icon = "fab-github"
edit-url-template = "https://github.com/rust-lang/mdBook/edit/master/guide/{path}"
site-url = "/example-book/"
cname = "myproject.rs"
Expand Down Expand Up @@ -147,7 +147,7 @@ The following configuration options are available:
- **git-repository-url:** A url to the git repository for the book. If provided
an icon link will be output in the menu bar of the book.
- **git-repository-icon:** The FontAwesome icon class to use for the git
repository link. Defaults to `fa-github` which looks like <i class="fa fa-github"></i>.
repository link. Defaults to `fab-github` which looks like <i class="fa fab-github"></i>.
If you are not using GitHub, another option to consider is `fa-code-fork` which looks like <i class="fa fa-code-fork"></i>.
- **edit-url-template:** Edit url template, when provided shows a
"Suggest an edit" button (which looks like <i class="fa fa-edit"></i>) for directly jumping to editing the currently
Expand Down
15 changes: 15 additions & 0 deletions guide/src/format/mdbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -362,3 +362,18 @@ fatigue," where people are trained to ignore them because they usually don't
matter for what they're doing.

</div>

## Font-Awesome icons

mdBook includes a copy of [Font Awesome Free's](https://fontawesome.com)
MIT-licensed SVG files. It emulates the `<i>` syntax, but converts the results
to inline SVG. Only the regular, solid, and brands icons are included; paid
features like the light icons are not.

For example, given this HTML syntax:

```hbs
The result looks like this: <i class="fas fa-print"></i>
```

The result looks like this: <i class="fas fa-print"></i>
24 changes: 23 additions & 1 deletion guide/src/format/theme/index-hbs.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ They are used like this
```handlebars
{{#previous}}
<a href="{{link}}" class="nav-chapters previous">
<i class="fa fa-angle-left"></i> {{title}}
{{fa "solid" "angle-left"}} {{title}}
</a>
{{/previous}}
```
Expand All @@ -99,3 +99,25 @@ Of course the inner html can be changed to your liking.

*If you would like other properties or helpers exposed, please [create a new
issue](https://github.com/rust-lang/mdBook/issues)*

### 3. fa

mdBook includes a copy of [Font Awesome Free's](https://fontawesome.com)
MIT-licensed SVG files. It accepts three positional arguments:

1. Type: one of "solid", "regular", and "brands" (light and duotone are not
currently supported)
2. Icon: anything chosen from the
[free icon set](https://fontawesome.com/icons?d=gallery&m=free)
3. ID (optional): if included, an HTML ID attribute will be added to the
icon's wrapping `<span>` tag

For example, this handlebars syntax will become this HTML:

```handlebars
{{fa "solid" "print" "print-button"}}
```

```html
<span class=fa-svg id="print-button"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M448 192V77.25c0-8.49-3.37-16.62-9.37-22.63L393.37 9.37c-6-6-14.14-9.37-22.63-9.37H96C78.33 0 64 14.33 64 32v160c-35.35 0-64 28.65-64 64v112c0 8.84 7.16 16 16 16h48v96c0 17.67 14.33 32 32 32h320c17.67 0 32-14.33 32-32v-96h48c8.84 0 16-7.16 16-16V256c0-35.35-28.65-64-64-64zm-64 256H128v-96h256v96zm0-224H128V64h192v48c0 8.84 7.16 16 16 16h48v96zm48 72c-13.25 0-24-10.75-24-24 0-13.26 10.75-24 24-24s24 10.74 24 24c0 13.25-10.75 24-24 24z"/></svg></span>
```
16 changes: 8 additions & 8 deletions guide/src/guide/reading.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ The icons displayed will depend on the settings of how the book was generated.

| Icon | Description |
|------|-------------|
| <i class="fa fa-bars"></i> | Opens and closes the chapter listing sidebar. |
| <i class="fa fa-paint-brush"></i> | Opens a picker to choose a different color theme. |
| <i class="fa fa-search"></i> | Opens a search bar for searching within the book. |
| <i class="fa fa-print"></i> | Instructs the web browser to print the entire book. |
| <i class="fa fa-github"></i> | Opens a link to the website that hosts the source code of the book. |
| <i class="fa fa-edit"></i> | Opens a page to directly edit the source of the page you are currently reading. |
| <i class="fas fa-bars"></i> | Opens and closes the chapter listing sidebar. |
| <i class="fas fa-paintbrush"></i> | Opens a picker to choose a different color theme. |
| <i class="fas fa-magnifying-glass"></i> | Opens a search bar for searching within the book. |
| <i class="fas fa-print"></i> | Instructs the web browser to print the entire book. |
| <i class="fab fa-github"></i> | Opens a link to the website that hosts the source code of the book. |
| <i class="fas fa-pencil"></i> | Opens a page to directly edit the source of the page you are currently reading. |

Tapping the menu bar will scroll the page to the top.

Expand All @@ -59,9 +59,9 @@ Code blocks may contain several different icons for interacting with them:
| Icon | Description |
|------|-------------|
| <i class="fa fa-copy"></i> | Copies the code block into your local clipboard, to allow pasting into another application. |
| <i class="fa fa-play"></i> | For Rust code examples, this will execute the sample code and display the compiler output just below the example (see [playground]). |
| <i class="fas fa-play"></i> | For Rust code examples, this will execute the sample code and display the compiler output just below the example (see [playground]). |
| <i class="fa fa-eye"></i> | For Rust code examples, this will toggle visibility of "hidden" lines. Sometimes, larger examples will hide lines which are not particularly relevant to what is being illustrated (see [hiding code lines]). |
| <i class="fa fa-history"></i> | For [editable code examples][editor], this will undo any changes you have made. |
| <i class="fas fa-clock-rotate-left"></i> | For [editable code examples][editor], this will undo any changes you have made. |

Here's an example:

Expand Down
97 changes: 61 additions & 36 deletions src/renderer/html_handlebars/hbs_renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ impl HtmlHandlebars {
let rendered = fix_code_blocks(&rendered);
let rendered = add_playground_pre(&rendered, playground_config, edition);
let rendered = hide_lines(&rendered, code_config);
let rendered = convert_fontawesome(&rendered);

rendered
}
Expand Down Expand Up @@ -258,41 +259,6 @@ impl HtmlHandlebars {
write_file(destination, "ayu-highlight.css", &theme.ayu_highlight_css)?;
write_file(destination, "highlight.js", &theme.highlight_js)?;
write_file(destination, "clipboard.min.js", &theme.clipboard_js)?;
write_file(
destination,
"FontAwesome/css/font-awesome.css",
theme::FONT_AWESOME,
)?;
write_file(
destination,
"FontAwesome/fonts/fontawesome-webfont.eot",
theme::FONT_AWESOME_EOT,
)?;
write_file(
destination,
"FontAwesome/fonts/fontawesome-webfont.svg",
theme::FONT_AWESOME_SVG,
)?;
write_file(
destination,
"FontAwesome/fonts/fontawesome-webfont.ttf",
theme::FONT_AWESOME_TTF,
)?;
write_file(
destination,
"FontAwesome/fonts/fontawesome-webfont.woff",
theme::FONT_AWESOME_WOFF,
)?;
write_file(
destination,
"FontAwesome/fonts/fontawesome-webfont.woff2",
theme::FONT_AWESOME_WOFF2,
)?;
write_file(
destination,
"FontAwesome/fonts/FontAwesome.ttf",
theme::FONT_AWESOME_TTF,
)?;
// Don't copy the stock fonts if the user has specified their own fonts to use.
if html_config.copy_fonts && theme.fonts_css.is_none() {
write_file(destination, "fonts/fonts.css", theme::fonts::CSS)?;
Expand Down Expand Up @@ -379,6 +345,7 @@ impl HtmlHandlebars {
handlebars.register_helper("next", Box::new(helpers::navigation::next));
// TODO: remove theme_option in 0.5, it is not needed.
handlebars.register_helper("theme_option", Box::new(helpers::theme::theme_option));
handlebars.register_helper("fa", Box::new(helpers::fontawesome::fa_helper));
}

/// Copy across any additional CSS and JavaScript files which the book
Expand Down Expand Up @@ -737,9 +704,19 @@ fn make_data(

let git_repository_icon = match html_config.git_repository_icon {
Some(ref git_repository_icon) => git_repository_icon,
None => "fa-github",
None => "fab-github",
};
let git_repository_icon_class = match git_repository_icon.split('-').next() {
Some("fa") => "regular",
Some("fas") => "solid",
Some("fab") => "brands",
_ => "regular",
};
data.insert("git_repository_icon".to_owned(), json!(git_repository_icon));
data.insert(
"git_repository_icon_class".to_owned(),
json!(git_repository_icon_class),
);

let mut chapters = vec![];

Expand Down Expand Up @@ -842,6 +819,54 @@ fn insert_link_into_header(
)
}

// Convert fontawesome `<i>` tags to inline SVG
fn convert_fontawesome(html: &str) -> String {
use font_awesome_as_a_crate as fa;

let regex = Regex::new(r##"<i([^>]+)class="([^"]+)"([^>]*)></i>"##).unwrap();
regex
.replace_all(html, |caps: &Captures<'_>| {
let text = &caps[0];
let before = &caps[1];
let classes = &caps[2];
let after = &caps[3];

let mut icon = String::new();
let mut type_ = fa::Type::Regular;
let mut other_classes = String::new();

for class in classes.split(" ") {
if let Some(class) = class.strip_prefix("fa-") {
icon = class.to_owned();
} else if class == "fa" {
type_ = fa::Type::Regular;
} else if class == "fas" {
type_ = fa::Type::Solid;
} else if class == "fab" {
type_ = fa::Type::Brands;
} else {
other_classes += " ";
other_classes += class;
}
}

if icon.is_empty() {
text.to_owned()
} else if let Ok(svg) = fa::svg(type_, &icon) {
format!(
r#"<span{before}class="fa-svg{other_classes}"{after}>{svg}</span>"#,
before = before,
other_classes = other_classes,
after = after,
svg = svg
)
} else {
text.to_owned()
}
})
.into_owned()
}

// The rust book uses annotations for rustdoc to test code snippets,
// like the following:
// ```rust,should_panic
Expand Down
53 changes: 53 additions & 0 deletions src/renderer/html_handlebars/helpers/fontawesome.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use font_awesome_as_a_crate as fa;
use handlebars::{
Context, Handlebars, Helper, Output, RenderContext, RenderError, RenderErrorReason,
};
use log::trace;
use std::str::FromStr;

pub fn fa_helper(
h: &Helper<'_>,
_r: &Handlebars<'_>,
_ctx: &Context,
_rc: &mut RenderContext<'_, '_>,
out: &mut dyn Output,
) -> Result<(), RenderError> {
trace!("fa_helper (handlebars helper)");

let type_ = h
.param(0)
.and_then(|v| v.value().as_str())
.and_then(|v| fa::Type::from_str(v).ok())
.ok_or_else(|| {
RenderErrorReason::Other(
"Param 0 with String type is required for fontawesome helper.".to_owned(),
)
})?;

let name = h.param(1).and_then(|v| v.value().as_str()).ok_or_else(|| {
RenderErrorReason::Other(
"Param 1 with String type is required for fontawesome helper.".to_owned(),
)
})?;

trace!("fa_helper: {} {}", type_, name);

let name = name
.strip_prefix("fa-")
.or_else(|| name.strip_prefix("fab-"))
.or_else(|| name.strip_prefix("fas-"))
.unwrap_or(name);

if let Some(id) = h.param(2).and_then(|v| v.value().as_str()) {
out.write(&format!("<span class=fa-svg id=\"{}\">", id))?;
} else {
out.write("<span class=fa-svg>")?;
}
out.write(
fa::svg(type_, name)
.map_err(|_| RenderErrorReason::Other(format!("Missing font {}", name)))?,
)?;
out.write("</span>")?;

Ok(())
}
1 change: 1 addition & 0 deletions src/renderer/html_handlebars/helpers/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod fontawesome;
pub mod navigation;
pub mod theme;
pub mod toc;
4 changes: 0 additions & 4 deletions src/theme/FontAwesome/css/font-awesome.min.css

This file was deleted.

Binary file removed src/theme/FontAwesome/fonts/FontAwesome.otf
Binary file not shown.
Binary file removed src/theme/FontAwesome/fonts/fontawesome-webfont.eot
Binary file not shown.
Loading

0 comments on commit 8763755

Please sign in to comment.